Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for generating a d2l.dev app #90

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"create-brightspace-ui": "./dist/create.js"
},
"scripts": {
"build": "rm -rf dist && babel src --out-dir dist --copy-files --include-dotfiles",
"build": "rimraf dist && babel src --out-dir dist --copy-files --include-dotfiles",
"lint:eslint": "eslint . --ext .js,.html",
"test": "npm run lint:eslint"
},
Expand All @@ -30,6 +30,7 @@
"babel-eslint": "^10",
"babel-plugin-transform-dynamic-import": "^2",
"eslint": "^8",
"eslint-config-brightspace": "^1"
"eslint-config-brightspace": "^1",
"rimraf": "^5.0.7"
}
}
205 changes: 202 additions & 3 deletions src/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,77 @@ import { run as setupDemo } from './generators/demo/index.js';
import { run as setupElement } from './generators/wc-lit-element/index.js';
import { run as setupLocalization } from './generators/localization/index.js';
import { run as setupRelease } from './generators/release/index.js';
import { run as setupStaticSite } from './generators/static-site/index.js';
import { run as setupTestUnit } from './generators/test-unit/index.js';
import { run as setupTestVdiff } from './generators/test-vdiff/index.js';

const generatorTypes = {
component: 'component',
staticSite: 'staticSite',
bsiApp: 'bsiApp'
};

const projectTypes = {
component: 'component',
app: 'app'
};

const applicationTypes = {
bsi: 'bsi',
staticSite: 'staticSite'
};

function getClassName(hyphenatedName) {
const hyphenRemoved = hyphenatedName.replace(/-([a-z])/g, (g) => { return g[1].toUpperCase(); });
return hyphenRemoved.charAt(0).toUpperCase() + hyphenRemoved.slice(1);
}

async function getOptions() {
async function getGeneratorType() {
const { projectType } = await prompts([
{
type: 'select',
name: 'projectType',
message: 'What type of project is this?',
choices: [
{ title: 'Component', value: projectTypes.component },
{ title: 'Application', value: projectTypes.app }
]
}
], {
onCancel: () => {
process.exit();
},
});

if (projectType === projectTypes.component) return generatorTypes.component;

const { applicationType } = await prompts([
{
type: 'select',
name: 'applicationType',
message: 'What type of application is this?',
choices: [
{ title: 'BSI', value: applicationTypes.bsi },
{ title: 'Static Site', value: applicationTypes.staticSite }
]
}
], {
onCancel: () => {
process.exit();
},
});

switch (applicationType) {
case applicationTypes.staticSite:
return generatorTypes.staticSite;
case applicationTypes.bsi:
return generatorTypes.bsiApp;
default:
break;
}
}

async function getComponentOptions() {
const questions = [
{
type: 'text',
Expand Down Expand Up @@ -67,9 +129,9 @@ async function getOptions() {
});
}

async function executeGenerator() {
async function executeComponentGenerator() {

const options = await getOptions();
const options = await getComponentOptions();

/**
* hyphenatedName = my-element
Expand All @@ -94,6 +156,143 @@ async function executeGenerator() {

}

async function executeStaticSiteGenerator() {
const {
hyphenatedNameRaw,
description,
codeowners,
hostingTarget
} = await prompts([
{
type: 'text',
name: 'hyphenatedNameRaw',
message: 'What would you like to name your app? Use hyphenation instead of camelcase.'
},
{
type: 'text',
name: 'description',
message: 'What is the app description?'
},
{
type: 'text',
name: 'codeowners',
message: 'What is/are the GitHub username(s) of the codeowner(s)? (e.g., @janesmith, @johnsmith)'
},
{
type: 'select',
name: 'hostingTarget',
message: 'Where would you like to host your app?',
choices: [
{ title: 'd2l.dev', value: 'd2ldev' },
{ title: 'S3 Bucket', value: 's3' },
{ title: 'Other', value: 'other' }
]
}
], {
onCancel: () => {
process.exit();
},
});

const hyphenatedName = hyphenatedNameRaw.toLowerCase();

let roleToAssume, awsRegion, bucketPath, d2ldevSubdomain;
if (hostingTarget === 'd2ldev') {
const { subdomain } = await prompts([
{
type: 'text',
name: 'subdomain',
message: 'What is the d2l.dev subdomain you want to publish this app to?'
},
], {
onCancel: () => {
process.exit();
},
});

roleToAssume = `"arn:aws:iam::022062736489:role/r+Brightspace+${hyphenatedName}+repo"`;
awsRegion = 'ca-central-1';
bucketPath = `s3://d2l.dev/${subdomain}/main`;
d2ldevSubdomain = subdomain;
} else if (hostingTarget === 's3') {
const { role, region, path } = await prompts([
{
type: 'text',
name: 'role',
message: 'What is the AWS ARN of the role you wish to authenticate as? (e.g. arn:aws:iam::123456789012:role/r+my+role)'
},
{
type: 'text',
name: 'region',
message: 'What AWS region do you want to authenticate into? (e.g. us-east-1)'
},
{
type: 'text',
name: 'path',
message: 'What is the S3 bucket path you wish to publish to? (e.g. s3://my-bucket/my-path)'
}
], {
onCancel: () => {
process.exit();
},
});
roleToAssume = `"${role}"`;
awsRegion = region;
bucketPath = path;
}

const templateData = {
hyphenatedName,
className: getClassName(hyphenatedName),
tagName: `d2l-${hyphenatedName}`,
repoName: hyphenatedName,
description,
codeowners,
hostingTarget,
roleToAssume,
awsRegion,
bucketPath,
d2ldevSubdomain
};

setupStaticSite(templateData);

console.log('\nTemplate setup complete!\n');

if (hostingTarget === 'd2ldev') {
console.log('Note: Make sure you have set up the appropriate permissions in github.com/Brightspace/repo-settings so that your repo can publish to d2l.dev.');
console.log('For details on how to do this, please review the d2l.dev setup guide (https://desire2learn.atlassian.net/wiki/x/H4A70).\n');
} else if (hostingTarget === 's3') {
console.log('Note: Make sure you have set up the appropriate permissions in github.com/Brightspace/repo-settings so that your repo can publish to the S3 bucket you specified.\n');
} else {
console.log('Note: Since you did not specify a publishing target, the generated .github/workflows/publish.yml file doesn\'t include an actual publishing step.');
console.log('In order to publish your site, you\'ll have to add your own publishing step.\n');
}
}

async function executeBsiAppGenerator() {
console.log('Sorry, the BSI Application template is not yet available.');
console.log('In the meantime, please create a Component project type and modify it to work as a BSI App.');
}

async function executeGenerator() {
const generatorType = await getGeneratorType();

switch (generatorType) {
case generatorTypes.component:
await executeComponentGenerator();
break;
case generatorTypes.staticSite:
await executeStaticSiteGenerator();
break;
case generatorTypes.bsiApp:
await executeBsiAppGenerator();
break;
default:
break;
}
}

(async() => {
try {
await executeGenerator();
Expand Down
84 changes: 84 additions & 0 deletions src/generators/static-site/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
copyAndProcessDir,
getDestinationPath,
movePlugin,
replaceTextPlugin
} from '../../helper.js';
import path from 'path';

function genD2ldevReleaseTargetMessage(subdomain) {
return ` to [${subdomain}.d2l.dev](https://${subdomain}.d2l.dev/)`;
}

function genS3PublishTemplate(roleToAssume, awsRegion, bucketPath) {
// NOTE: Indentation levels in yml files are important and the publish.yml file uses 2 spaces.
return `
- name: Assume role
if: github.ref == 'refs/heads/main'
uses: Brightspace/third-party-actions@aws-actions/configure-aws-credentials
with:
aws-access-key-id: \${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-session-token: \${{ secrets.AWS_SESSION_TOKEN }}
role-to-assume: ${roleToAssume}
role-duration-seconds: 3600
aws-region: ${awsRegion}

- name: Publish
uses: BrightspaceUI/actions/publish-to-s3@main
with:
bucket-path: ${bucketPath}
publish-directory: ./dist/
`;
}

export function run(templateData) {
const {
hyphenatedName,
hostingTarget,
roleToAssume,
awsRegion,
bucketPath,
d2ldevSubdomain
} = templateData;

const templateRoot = path.join(__dirname, 'templates');
const destinationRoot = getDestinationPath(hyphenatedName);

const releaseTargetMessage = d2ldevSubdomain
? genD2ldevReleaseTargetMessage(d2ldevSubdomain)
: '';
const publishStep = hostingTarget === 'other'
? ''
: genS3PublishTemplate(roleToAssume, awsRegion, bucketPath);
const elementFile = `${hyphenatedName}.js`;

copyAndProcessDir(templateRoot, destinationRoot, [
movePlugin({
'_package.json': 'package.json',
'_gitignore': '.gitignore',
'_browserslistrc': '.browserslistrc',
'_CODEOWNERS': 'CODEOWNERS',
'_README.md': 'README.md',
'src/components/_element.js': `src/components/${elementFile}`
}),
replaceTextPlugin({
'_package.json': templateData,
'_CODEOWNERS': templateData,
'_README.md': {
...templateData,
releaseTargetMessage
},
'.github/workflows/publish.yml': {
...templateData,
publishStep
},
'src/index.html': templateData,
'src/index.js': {
...templateData,
elementFile
},
'src/components/_element.js': templateData,
})
]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Publish
on:
push:
branches:
- main
- '[0-9]+.x'
- '[0-9]+.[0-9]+.x'
jobs:
publish:
name: Publish
runs-on: [self-hosted, AWS, Linux]
timeout-minutes: 10
steps:
- name: Checkout
uses: Brightspace/third-party-actions@actions/checkout
- name: Setup Node
uses: Brightspace/third-party-actions@actions/setup-node
with:
node-version-file: .nvmrc
- name: Install dependencies
run: npm install
- name: Build
run: npm run build
<%= publishStep %>
1 change: 1 addition & 0 deletions src/generators/static-site/templates/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
1 change: 1 addition & 0 deletions src/generators/static-site/templates/_CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* <%= codeowners %>
12 changes: 12 additions & 0 deletions src/generators/static-site/templates/_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# <%= hyphenatedName %>

## Developing and Contributing

* Clone the repo
* Run `npx d2l-npm-login` to support private npm packages (note: this requires your Okta credentials)
* Run `npm install` to install dependencies
* Run `npm start` to run the local web-dev-server

## Releasing

After merging your code changes to the `main` branch, the `Publish` Github action will run and publish your changes<%= releaseTargetMessage %>.
1 change: 1 addition & 0 deletions src/generators/static-site/templates/_browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extends @brightspace-ui/browserslist-config
3 changes: 3 additions & 0 deletions src/generators/static-site/templates/_gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
package-lock.json
dist/
Loading