diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..6a1f9ccc --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,32 @@ +# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +name: Node.js CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x, 14.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - run: npm install + - run: npm test + - run: npm run cov:send + - run: npm run cov:check diff --git a/src/cli/args.ts b/src/cli/args.ts index 60002f82..234012d5 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -16,6 +16,7 @@ export async function checkArgs(): Promise { --appveyor include Appveyor for Windows CI --description, -d package.json description --dom include DOM type definitions + --githubactions include Github Actions CI --node include node.js type definitions --strict enable stricter type-checking --travis include Travis CI configuration @@ -23,7 +24,7 @@ export async function checkArgs(): Promise { --no-circleci don't include CircleCI --no-cspell don't include cspell - --no-editorconfig don't include .editorconfig + --no-editorconfig don't include .editorconfig --no-functional don't enable eslint-plugin-functional --no-install skip yarn/npm install --no-vscode don't include VS Code debugging config @@ -62,6 +63,10 @@ export async function checkArgs(): Promise { default: true, type: 'boolean', }, + githubactions: { + default: false, + type: 'boolean', + }, install: { default: true, type: 'boolean', @@ -129,6 +134,7 @@ export async function checkArgs(): Promise { domDefinitions: cli.flags.dom, editorconfig: cli.flags.editorconfig, functional: cli.flags.functional, + githubactions: cli.flags.githubactions, install: cli.flags.install, nodeDefinitions: cli.flags.node, projectName: input, diff --git a/src/cli/inquire.ts b/src/cli/inquire.ts index 1e61f8b9..ff9b8ca6 100644 --- a/src/cli/inquire.ts +++ b/src/cli/inquire.ts @@ -81,6 +81,7 @@ export async function inquire(): Promise { cspell = 'cspell', editorconfig = 'editorconfig', functional = 'functional', + githubactions = 'githubactions', strict = 'strict', travis = 'travis', vscode = 'vscode', @@ -126,6 +127,11 @@ export async function inquire(): Promise { name: 'Include Travis CI config', value: Extras.travis, }, + { + checked: false, + name: 'Include Github Actions CI config', + value: Extras.githubactions, + }, ], message: '🚀 More fun stuff:', name: 'extras', @@ -167,6 +173,7 @@ export async function inquire(): Promise { : false, editorconfig: extras.includes(Extras.editorconfig), functional: extras.includes(Extras.functional), + githubactions: extras.includes(Extras.githubactions), install: true, nodeDefinitions: definitions ? [TypeDefinitions.node, TypeDefinitions.nodeAndDom].includes( diff --git a/src/cli/tasks.ts b/src/cli/tasks.ts index 2db5c62e..43777863 100644 --- a/src/cli/tasks.ts +++ b/src/cli/tasks.ts @@ -18,72 +18,73 @@ export enum Placeholders { // We implement these as function factories to make unit testing easier. -export const cloneRepo = ( - spawner: typeof execa, - suppressOutput = false -) => async ( - repoInfo: { - readonly branch: string; - readonly repo: string; - }, - workingDirectory: string, - dir: string -) => { - const projectDir = join(workingDirectory, dir); - const gitHistoryDir = join(projectDir, '.git'); - const args = - repoInfo.branch === '.' - ? ['clone', '--depth=1', repoInfo.repo, dir] - : [ - 'clone', - '--depth=1', - `--branch=${repoInfo.branch}`, - repoInfo.repo, - dir, - ]; - try { - await spawner('git', args, { - cwd: workingDirectory, - stdio: suppressOutput ? 'pipe' : 'inherit', - }); - } catch (err) { - if (err.exitCodeName === 'ENOENT') { - // eslint-disable-next-line functional/no-throw-statement - throw new Error(` +export const cloneRepo = + (spawner: typeof execa, suppressOutput = false) => + async ( + repoInfo: { + readonly branch: string; + readonly repo: string; + }, + workingDirectory: string, + dir: string + ) => { + const projectDir = join(workingDirectory, dir); + const gitHistoryDir = join(projectDir, '.git'); + const args = + repoInfo.branch === '.' + ? ['clone', '--depth=1', repoInfo.repo, dir] + : [ + 'clone', + '--depth=1', + `--branch=${repoInfo.branch}`, + repoInfo.repo, + dir, + ]; + try { + await spawner('git', args, { + cwd: workingDirectory, + stdio: suppressOutput ? 'pipe' : 'inherit', + }); + } catch (err) { + if ((err as any).exitCodeName === 'ENOENT') { + // eslint-disable-next-line functional/no-throw-statement + throw new Error(` Git is not installed on your PATH. Please install Git and try again. For more information, visit: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git `); - } else { + } else { + // eslint-disable-next-line functional/no-throw-statement + throw new Error(`Git clone failed.`); + } + } + try { + const revParseResult = await spawner('git', ['rev-parse', 'HEAD'], { + cwd: projectDir, + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'inherit'], + }); + const commitHash = revParseResult.stdout; + return { commitHash, gitHistoryDir }; + } catch (err) { // eslint-disable-next-line functional/no-throw-statement - throw new Error(`Git clone failed.`); + throw new Error(`Git rev-parse failed.`); } - } - try { - const revParseResult = await spawner('git', ['rev-parse', 'HEAD'], { - cwd: projectDir, - encoding: 'utf8', - stdio: ['pipe', 'pipe', 'inherit'], - }); - const commitHash = revParseResult.stdout; - return { commitHash, gitHistoryDir }; - } catch (err) { - // eslint-disable-next-line functional/no-throw-statement - throw new Error(`Git rev-parse failed.`); - } -}; + }; -export const getGithubUsername = ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - fetcher: any -) => async (email: string | undefined): Promise => { - if (email === Placeholders.email) { - return Placeholders.username; - } - return fetcher(email).catch(() => { - return Placeholders.username; - }); -}; +export const getGithubUsername = + ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + fetcher: any + ) => + async (email: string | undefined): Promise => { + if (email === Placeholders.email) { + return Placeholders.username; + } + return fetcher(email).catch(() => { + return Placeholders.username; + }); + }; export const getUserInfo = (spawner: typeof execa) => async () => { const opts: Options = { @@ -105,46 +106,43 @@ export const getUserInfo = (spawner: typeof execa) => async () => { } }; -export const initialCommit = (spawner: typeof execa) => async ( - hash: string, - projectDir: string -): Promise => { - const opts: Options = { - cwd: projectDir, - encoding: 'utf8', - stdio: 'pipe', +export const initialCommit = + (spawner: typeof execa) => + async (hash: string, projectDir: string): Promise => { + const opts: Options = { + cwd: projectDir, + encoding: 'utf8', + stdio: 'pipe', + }; + await spawner('git', ['init'], opts); + await spawner('git', ['add', '-A'], opts); + await spawner( + 'git', + [ + 'commit', + '-m', + `Initial commit\n\nCreated with bitjson/typescript-starter@${hash}`, + ], + opts + ); }; - await spawner('git', ['init'], opts); - await spawner('git', ['add', '-A'], opts); - await spawner( - 'git', - [ - 'commit', - '-m', - `Initial commit\n\nCreated with bitjson/typescript-starter@${hash}`, - ], - opts - ); -}; -export const install = (spawner: typeof execa) => async ( - runner: Runner, - projectDir: string -) => { - const opts: Options = { - cwd: projectDir, - encoding: 'utf8', - stdio: 'inherit', +export const install = + (spawner: typeof execa) => async (runner: Runner, projectDir: string) => { + const opts: Options = { + cwd: projectDir, + encoding: 'utf8', + stdio: 'inherit', + }; + try { + runner === Runner.Npm + ? spawner('npm', ['install'], opts) + : spawner('yarn', opts); + } catch (err) { + // eslint-disable-next-line functional/no-throw-statement + throw new Error(`Installation failed. You'll need to install manually.`); + } }; - try { - runner === Runner.Npm - ? spawner('npm', ['install'], opts) - : spawner('yarn', opts); - } catch (err) { - // eslint-disable-next-line functional/no-throw-statement - throw new Error(`Installation failed. You'll need to install manually.`); - } -}; /** * Returns the URL and branch to clone. We clone the branch (tag) at the current @@ -210,6 +208,7 @@ export const addInferredOptions = async ( domDefinitions: userOptions.domDefinitions, editorconfig: userOptions.editorconfig, functional: userOptions.functional, + githubactions: userOptions.githubactions, install: userOptions.install, nodeDefinitions: userOptions.nodeDefinitions, projectName: userOptions.projectName, diff --git a/src/cli/tests/cli.integration.spec.ts b/src/cli/tests/cli.integration.spec.ts index b62d40c3..bdd91612 100644 --- a/src/cli/tests/cli.integration.spec.ts +++ b/src/cli/tests/cli.integration.spec.ts @@ -394,6 +394,7 @@ test(`${TestDirectories.five}: Sandboxed: npm install, initial commit`, async (t // cspell: disable-next-line fullName: 'Satoshi Nakamoto', functional: true, + githubactions: false, install: true, nodeDefinitions: false, projectName: TestDirectories.five, @@ -436,6 +437,7 @@ test(`${TestDirectories.six}: Sandboxed: yarn, no initial commit`, async (t) => email: Placeholders.email, fullName: Placeholders.name, functional: true, + githubactions: false, install: true, nodeDefinitions: true, projectName: TestDirectories.six, diff --git a/src/cli/typescript-starter.ts b/src/cli/typescript-starter.ts index a99f147e..aec9c7c3 100644 --- a/src/cli/typescript-starter.ts +++ b/src/cli/typescript-starter.ts @@ -31,6 +31,7 @@ export async function typescriptStarter( fullName, githubUsername, functional, + githubactions, install, nodeDefinitions, projectName, @@ -215,6 +216,9 @@ export async function typescriptStarter( if (!travis) { del([normalizePath(join(projectPath, '.travis.yml'))]); } + if (!githubactions) { + del([normalizePath(join(projectPath, '.github', 'workflows', 'ci.yaml'))]); + } if (!editorconfig) { del([normalizePath(join(projectPath, '.editorconfig'))]); } diff --git a/src/cli/utils.ts b/src/cli/utils.ts index 1c9863b1..b7accc94 100644 --- a/src/cli/utils.ts +++ b/src/cli/utils.ts @@ -17,6 +17,7 @@ export type TypescriptStarterCLIOptions = { readonly domDefinitions: boolean; readonly editorconfig: boolean; readonly functional: boolean; + readonly githubactions: boolean; readonly install: boolean; readonly nodeDefinitions: boolean; readonly projectName: string;