diff --git a/packages/create-nx-workspace/bin/create-nx-workspace.ts b/packages/create-nx-workspace/bin/create-nx-workspace.ts index 012caec132..cedb373b58 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -41,7 +41,7 @@ export const commandsObject: yargs.Argv = yargs 'strip-dashed': true, 'dot-notation': true, }) - .command( + .command( // this is the default and only command '$0 [name] [options]', 'Create a new Nx workspace', @@ -152,7 +152,7 @@ export const commandsObject: yargs.Argv = yargs throw error; }); }, - [normalizeArgsMiddleware] + [normalizeArgsMiddleware as yargs.MiddlewareFunction<{}>] ) .help('help', chalk.dim`Show help`) .updateLocale(yargsDecorator) @@ -195,7 +195,7 @@ async function normalizeArgsMiddleware( "Let's create a new workspace [https://nx.dev/getting-started/intro]", }); - let thirdPartyPreset: string; + let thirdPartyPreset: string | null; try { thirdPartyPreset = await getThirdPartyPreset(argv.preset); } catch (e) { @@ -262,7 +262,7 @@ async function normalizeArgsMiddleware( } } else { name = await determineRepoName(argv); - appName = await determineAppName(preset, argv); + appName = await determineAppName(preset as Preset, argv); if (preset === Preset.ReactMonorepo) { bundler = await determineBundler(argv); } @@ -276,7 +276,7 @@ async function normalizeArgsMiddleware( (argv.interactive ? await determineRouting(argv) : true); } } - style = await determineStyle(preset, argv); + style = await determineStyle(preset as Preset, argv); } const packageManager = await determinePackageManager(argv); @@ -439,7 +439,7 @@ async function determinePackageManager( ], }, ]) - .then((a: { packageManager }) => a.packageManager); + .then((a) => a.packageManager); } return Promise.resolve(detectInvokedPackageManager()); @@ -453,7 +453,7 @@ async function determineDefaultBase( } if (parsedArgs.allPrompts) { return enquirer - .prompt([ + .prompt<{ DefaultBase: string }>([ { name: 'DefaultBase', message: `Main branch name `, @@ -461,7 +461,7 @@ async function determineDefaultBase( type: 'input', }, ]) - .then((a: { DefaultBase: string }) => { + .then((a) => { if (!a.DefaultBase) { output.error({ title: 'Invalid branch name', @@ -524,14 +524,14 @@ async function determineAppName( } return enquirer - .prompt([ + .prompt<{ AppName: string }>([ { name: 'AppName', message: `Application name `, type: 'input', }, ]) - .then((a: { AppName: string }) => { + .then((a) => { if (!a.AppName) { output.error({ title: 'Invalid name', @@ -571,7 +571,7 @@ async function determineFramework( if (!parsedArgs.framework) { return enquirer - .prompt([ + .prompt<{ framework: Framework }>([ { message: 'What framework should be used?', type: 'autocomplete', @@ -579,7 +579,7 @@ async function determineFramework( choices: frameworkChoices, }, ]) - .then((a: { framework: string }) => a.framework); + .then((a) => a.framework); } const foundFramework = frameworkChoices @@ -607,7 +607,7 @@ async function determineStandaloneApi( ): Promise { if (parsedArgs.standaloneApi === undefined) { return enquirer - .prompt([ + .prompt<{ standaloneApi: 'Yes' | 'No' }>([ { name: 'standaloneApi', message: @@ -625,7 +625,7 @@ async function determineStandaloneApi( initial: 'No' as any, }, ]) - .then((a: { standaloneApi: 'Yes' | 'No' }) => a.standaloneApi === 'Yes'); + .then((a) => a.standaloneApi === 'Yes'); } return parsedArgs.standaloneApi; @@ -636,7 +636,7 @@ async function determineDockerfile( ): Promise { if (parsedArgs.docker === undefined) { return enquirer - .prompt([ + .prompt<{ docker: 'Yes' | 'No' }>([ { name: 'docker', message: @@ -654,7 +654,7 @@ async function determineDockerfile( initial: 'No' as any, }, ]) - .then((a: { docker: 'Yes' | 'No' }) => a.docker === 'Yes'); + .then((a) => a.docker === 'Yes'); } else { return Promise.resolve(parsedArgs.docker); } @@ -663,7 +663,7 @@ async function determineDockerfile( async function determineStyle( preset: Preset, parsedArgs: yargs.Arguments -): Promise { +): Promise { if ( preset === Preset.Apps || preset === Preset.Core || @@ -727,7 +727,7 @@ async function determineStyle( if (!parsedArgs.style) { return enquirer - .prompt([ + .prompt<{ style: string }>([ { name: 'style', message: `Default stylesheet format `, @@ -762,7 +762,7 @@ async function determineRouting( ): Promise { if (!parsedArgs.routing) { return enquirer - .prompt([ + .prompt<{ routing: 'Yes' | 'No' }>([ { name: 'routing', message: 'Would you like to add routing?', @@ -779,7 +779,7 @@ async function determineRouting( initial: 'Yes' as any, }, ]) - .then((a: { routing: 'Yes' | 'No' }) => a.routing === 'Yes'); + .then((a) => a.routing === 'Yes'); } return parsedArgs.routing; @@ -801,7 +801,7 @@ async function determineBundler( if (!parsedArgs.bundler) { return enquirer - .prompt([ + .prompt<{ bundler: Bundler }>([ { name: 'bundler', message: `Bundler to be used to build the application`, @@ -810,7 +810,7 @@ async function determineBundler( choices: choices, }, ]) - .then((a: { bundler: 'vite' | 'webpack' }) => a.bundler); + .then((a) => a.bundler); } const foundBundler = choices.find( @@ -838,7 +838,7 @@ async function determineNxCloud( ): Promise { if (parsedArgs.nxCloud === undefined) { return enquirer - .prompt([ + .prompt<{ NxCloud: 'Yes' | 'No' }>([ { name: 'NxCloud', message: messages.getPromptMessage('nxCloudCreation'), @@ -856,7 +856,7 @@ async function determineNxCloud( initial: 'Yes' as any, }, ]) - .then((a: { NxCloud: 'Yes' | 'No' }) => a.NxCloud === 'Yes'); + .then((a) => a.NxCloud === 'Yes'); } else { return parsedArgs.nxCloud; } @@ -886,7 +886,7 @@ async function determineCI( if (parsedArgs.allPrompts) { return ( enquirer - .prompt([ + .prompt<{ CI: string }>([ { name: 'CI', message: `CI workflow file to generate? `, diff --git a/packages/create-nx-workspace/src/create-empty-workspace.ts b/packages/create-nx-workspace/src/create-empty-workspace.ts index f72b23834c..ab673a60f0 100644 --- a/packages/create-nx-workspace/src/create-empty-workspace.ts +++ b/packages/create-nx-workspace/src/create-empty-workspace.ts @@ -2,13 +2,14 @@ import * as ora from 'ora'; import { join } from 'path'; import { CreateWorkspaceOptions } from './create-workspace-options'; import { execAndWait } from './utils/child-process-utils'; +import { mapErrorToBodyLines } from './utils/error-utils'; import { output } from './utils/output'; import { getPackageManagerCommand, getPackageManagerVersion, PackageManager, } from './utils/package-manager'; -import { getFileName, mapErrorToBodyLines } from './utils/string-utils'; +import { getFileName } from './utils/string-utils'; import { unparse } from './utils/unparse'; /** @@ -67,10 +68,14 @@ export async function createEmptyWorkspace( ); } catch (e) { workspaceSetupSpinner.fail(); - output.error({ - title: `Nx failed to create a workspace.`, - bodyLines: mapErrorToBodyLines(e), - }); + if (e instanceof Error) { + output.error({ + title: `Nx failed to create a workspace.`, + bodyLines: mapErrorToBodyLines(e), + }); + } else { + console.error(e); + } process.exit(1); } finally { workspaceSetupSpinner.stop(); diff --git a/packages/create-nx-workspace/src/create-sandbox.ts b/packages/create-nx-workspace/src/create-sandbox.ts index 2b460e91ae..fd27bad941 100644 --- a/packages/create-nx-workspace/src/create-sandbox.ts +++ b/packages/create-nx-workspace/src/create-sandbox.ts @@ -10,8 +10,8 @@ import { } from './utils/package-manager'; import { execAndWait } from './utils/child-process-utils'; import { output } from './utils/output'; -import { mapErrorToBodyLines } from './utils/string-utils'; import { nxVersion } from './utils/nx/nx-version'; +import { mapErrorToBodyLines } from './utils/error-utils'; /** * Creates a temporary directory and installs Nx in it. @@ -44,10 +44,14 @@ export async function createSandbox(packageManager: PackageManager) { installSpinner.succeed(); } catch (e) { installSpinner.fail(); - output.error({ - title: `Nx failed to install dependencies`, - bodyLines: mapErrorToBodyLines(e), - }); + if (e instanceof Error) { + output.error({ + title: `Nx failed to install dependencies`, + bodyLines: mapErrorToBodyLines(e), + }); + } else { + console.error(e); + } process.exit(1); } finally { installSpinner.stop(); diff --git a/packages/create-nx-workspace/src/create-workspace.ts b/packages/create-nx-workspace/src/create-workspace.ts index b2a02cc9ff..41676f263d 100644 --- a/packages/create-nx-workspace/src/create-workspace.ts +++ b/packages/create-nx-workspace/src/create-workspace.ts @@ -10,6 +10,7 @@ import { messages, recordStat } from './utils/nx/ab-testing'; import { initializeGitRepo } from './utils/git/git'; import { nxVersion } from './utils/nx/nx-version'; import { getThirdPartyPreset } from './utils/preset/get-third-party-preset'; +import { mapErrorToBodyLines } from './utils/error-utils'; export async function createWorkspace( preset: string, @@ -59,23 +60,27 @@ export async function createWorkspace( name, ci, packageManager, - nxCloud && nxCloudInstallRes.code === 0 + nxCloud && nxCloudInstallRes?.code === 0 ); } if (!skipGit) { try { await initializeGitRepo(directory, { defaultBase, commit }); } catch (e) { - output.error({ - title: 'Could not initialize git repository', - bodyLines: [e.message], - }); + if (e instanceof Error) { + output.error({ + title: 'Could not initialize git repository', + bodyLines: mapErrorToBodyLines(e), + }); + } else { + console.error(e); + } } } showNxWarning(name); - if (nxCloud && nxCloudInstallRes.code === 0) { + if (nxCloud && nxCloudInstallRes?.code === 0) { printNxCloudSuccessMessage(nxCloudInstallRes.stdout); } diff --git a/packages/create-nx-workspace/src/utils/child-process-utils.ts b/packages/create-nx-workspace/src/utils/child-process-utils.ts index 10633c25f6..708e12aa85 100644 --- a/packages/create-nx-workspace/src/utils/child-process-utils.ts +++ b/packages/create-nx-workspace/src/utils/child-process-utils.ts @@ -1,6 +1,7 @@ import { spawn, exec } from 'child_process'; import { writeFileSync } from 'fs'; import { join } from 'path'; +import { CreateNxWorkspaceError } from './error-utils'; /** * Use spawn only for interactive shells @@ -24,7 +25,7 @@ export function spawnAndWait(command: string, args: string[], cwd: string) { } export function execAndWait(command: string, cwd: string) { - return new Promise((res, rej) => { + return new Promise<{ code: number; stdout: string }>((res, rej) => { exec( command, { cwd, env: { ...process.env, NX_DAEMON: 'false' } }, @@ -32,7 +33,7 @@ export function execAndWait(command: string, cwd: string) { if (error) { const logFile = join(cwd, 'error.log'); writeFileSync(logFile, `${stdout}\n${stderr}`); - rej({ code: error.code, logFile, logMessage: stderr }); + rej(new CreateNxWorkspaceError(stderr, error.code, logFile)); } else { res({ code: 0, stdout }); } diff --git a/packages/create-nx-workspace/src/utils/ci/setup-ci.ts b/packages/create-nx-workspace/src/utils/ci/setup-ci.ts index 7aca172a66..485cdef08e 100644 --- a/packages/create-nx-workspace/src/utils/ci/setup-ci.ts +++ b/packages/create-nx-workspace/src/utils/ci/setup-ci.ts @@ -2,9 +2,10 @@ import * as ora from 'ora'; import { join } from 'path'; import { execAndWait } from '../child-process-utils'; +import { mapErrorToBodyLines } from '../error-utils'; import { output } from '../output'; import { getPackageManagerCommand, PackageManager } from '../package-manager'; -import { getFileName, mapErrorToBodyLines } from '../string-utils'; +import { getFileName } from '../string-utils'; export async function setupCI( name: string, @@ -32,11 +33,14 @@ export async function setupCI( return res; } catch (e) { ciSpinner.fail(); - - output.error({ - title: `Nx failed to generate CI workflow`, - bodyLines: mapErrorToBodyLines(e), - }); + if (e instanceof Error) { + output.error({ + title: `Nx failed to generate CI workflow`, + bodyLines: mapErrorToBodyLines(e), + }); + } else { + console.error(e); + } process.exit(1); } finally { diff --git a/packages/create-nx-workspace/src/utils/error-utils.ts b/packages/create-nx-workspace/src/utils/error-utils.ts new file mode 100644 index 0000000000..d36f589a15 --- /dev/null +++ b/packages/create-nx-workspace/src/utils/error-utils.ts @@ -0,0 +1,32 @@ +export class CreateNxWorkspaceError extends Error { + constructor( + public logMessage: string, + public code: number | null | undefined, + public logFile: string + ) { + super(logMessage); + this.name = 'CreateNxWorkspaceError'; + } +} + +export function mapErrorToBodyLines(error: Error): string[] { + const errorLines = error.message?.split('\n').filter((line) => !!line); + if (errorLines.length < 3) { + const lines = [`Error: ${error.message}`]; + if (process.env.NX_VERBOSE_LOGGING) { + lines.push(`Stack: ${error.stack}`); + } + return lines; + } + + const lines = + error instanceof CreateNxWorkspaceError + ? [`Exit code: ${error.code}`, `Log file: ${error.logFile}`] + : []; + + if (process.env.NX_VERBOSE_LOGGING) { + lines.push(`Error: ${error.message}`); + lines.push(`Stack: ${error.stack}`); + } + return lines; +} diff --git a/packages/create-nx-workspace/src/utils/git/git.ts b/packages/create-nx-workspace/src/utils/git/git.ts index 9050a7bb7e..0668ffef41 100644 --- a/packages/create-nx-workspace/src/utils/git/git.ts +++ b/packages/create-nx-workspace/src/utils/git/git.ts @@ -2,10 +2,10 @@ import { execSync, spawn, SpawnOptions } from 'child_process'; import { deduceDefaultBase } from './default-base'; import { output } from '../output'; -export function checkGitVersion(): string | null { +export function checkGitVersion(): string | null | undefined { try { let gitVersionOutput = execSync('git --version').toString().trim(); - return gitVersionOutput.match(/[0-9]+\.[0-9]+\.+[0-9]+/)[0]; + return gitVersionOutput.match(/[0-9]+\.[0-9]+\.+[0-9]+/)?.[0]; } catch { return null; } @@ -15,7 +15,7 @@ export async function initializeGitRepo( directory: string, options: { defaultBase: string; - commit: { message: string; name: string; email: string }; + commit?: { message: string; name: string; email: string }; } ) { const execute = (args: ReadonlyArray, ignoreErrorStream = false) => { @@ -27,13 +27,13 @@ export async function initializeGitRepo( cwd: directory, env: { ...process.env, - ...(options.commit.name + ...(options.commit?.name ? { GIT_AUTHOR_NAME: options.commit.name, GIT_COMMITTER_NAME: options.commit.name, } : {}), - ...(options.commit.email + ...(options.commit?.email ? { GIT_AUTHOR_EMAIL: options.commit.email, GIT_COMMITTER_EMAIL: options.commit.email, diff --git a/packages/create-nx-workspace/src/utils/nx/ab-testing.ts b/packages/create-nx-workspace/src/utils/nx/ab-testing.ts index 763ef5d2e1..49943624ac 100644 --- a/packages/create-nx-workspace/src/utils/nx/ab-testing.ts +++ b/packages/create-nx-workspace/src/utils/nx/ab-testing.ts @@ -1,39 +1,45 @@ import { isCI } from '../ci/is-ci'; +const messageOptions = { + nxCloudCreation: [ + { + code: 'set-up-distributed-caching-ci', + message: `Enable distributed caching to make your CI faster`, + }, + ], + nxCloudMigration: [ + { + code: 'make-ci-faster', + message: `Enable distributed caching to make your CI faster?`, + }, + ], +} as const; + +type MessageKey = keyof typeof messageOptions; + export class PromptMessages { - private messages = { - nxCloudCreation: [ - { - code: 'set-up-distributed-caching-ci', - message: `Enable distributed caching to make your CI faster`, - }, - ], - nxCloudMigration: [ - { - code: 'make-ci-faster', - message: `Enable distributed caching to make your CI faster?`, - }, - ], - }; + private selectedMessages: { [key in MessageKey]?: number } = {}; - private selectedMessages = {}; - - getPromptMessage(key: string): string { + getPromptMessage(key: MessageKey): string { if (this.selectedMessages[key] === undefined) { if (process.env.NX_GENERATE_DOCS_PROCESS === 'true') { this.selectedMessages[key] = 0; } else { this.selectedMessages[key] = Math.floor( - Math.random() * this.messages[key].length + Math.random() * messageOptions[key].length ); } } - return this.messages[key][this.selectedMessages[key]].message; + return messageOptions[key][this.selectedMessages[key]!].message; } - codeOfSelectedPromptMessage(key: string): string { - if (this.selectedMessages[key] === undefined) return null; - return this.messages[key][this.selectedMessages[key]].code; + codeOfSelectedPromptMessage(key: MessageKey): string { + const selected = this.selectedMessages[key]; + if (selected === undefined) { + return messageOptions[key][0].code; + } else { + return messageOptions[key][selected].code; + } } } diff --git a/packages/create-nx-workspace/src/utils/nx/nx-cloud.ts b/packages/create-nx-workspace/src/utils/nx/nx-cloud.ts index 4454943308..afc1de5a91 100644 --- a/packages/create-nx-workspace/src/utils/nx/nx-cloud.ts +++ b/packages/create-nx-workspace/src/utils/nx/nx-cloud.ts @@ -3,7 +3,8 @@ import { join } from 'path'; import { execAndWait } from '../child-process-utils'; import { output } from '../output'; import { getPackageManagerCommand, PackageManager } from '../package-manager'; -import { getFileName, mapErrorToBodyLines } from '../string-utils'; +import { getFileName } from '../string-utils'; +import { mapErrorToBodyLines } from '../error-utils'; export async function setupNxCloud( name: string, @@ -21,10 +22,14 @@ export async function setupNxCloud( } catch (e) { nxCloudSpinner.fail(); - output.error({ - title: `Nx failed to setup NxCloud`, - bodyLines: mapErrorToBodyLines(e), - }); + if (e instanceof Error) { + output.error({ + title: `Nx failed to setup NxCloud`, + bodyLines: mapErrorToBodyLines(e), + }); + } else { + console.error(e); + } process.exit(1); } finally { diff --git a/packages/create-nx-workspace/src/utils/output.ts b/packages/create-nx-workspace/src/utils/output.ts index ee60bc7427..429bc8586a 100644 --- a/packages/create-nx-workspace/src/utils/output.ts +++ b/packages/create-nx-workspace/src/utils/output.ts @@ -95,10 +95,10 @@ class CLIOutput { applyNxPrefix(color = 'cyan', text: string): string { let nxPrefix = ''; - if (chalk[color]) { - nxPrefix = `${chalk[color]('>')} ${chalk.reset.inverse.bold[color]( - ' NX ' - )}`; + if ((chalk as any)[color]) { + nxPrefix = `${(chalk as any)[color]('>')} ${( + chalk as any + ).reset.inverse.bold[color](' NX ')}`; } else { nxPrefix = `${chalk.keyword(color)( '>' @@ -119,7 +119,9 @@ class CLIOutput { addVerticalSeparatorWithoutNewLines(color = 'gray') { this.writeToStdOut( - `${this.X_PADDING}${chalk.dim[color](this.VERTICAL_SEPARATOR)}${EOL}` + `${this.X_PADDING}${(chalk as any).dim[color]( + this.VERTICAL_SEPARATOR + )}${EOL}` ); } @@ -217,7 +219,7 @@ class CLIOutput { this.writeOutputTitle({ color: 'cyan', - title: color ? chalk[color](title) : title, + title: color ? (chalk as any)[color](title) : title, }); this.writeOptionalOutputBody(bodyLines); diff --git a/packages/create-nx-workspace/src/utils/preset/get-third-party-preset.ts b/packages/create-nx-workspace/src/utils/preset/get-third-party-preset.ts index 205f284b7e..182bbf30c7 100644 --- a/packages/create-nx-workspace/src/utils/preset/get-third-party-preset.ts +++ b/packages/create-nx-workspace/src/utils/preset/get-third-party-preset.ts @@ -24,7 +24,7 @@ export async function getThirdPartyPreset( bodyLines: [ `There was an error with the preset npm package you provided:`, '', - ...validateResult.errors, + ...(validateResult.errors ?? []), ], }); throw new Error('Invalid preset npm package'); diff --git a/packages/create-nx-workspace/src/utils/string-utils.ts b/packages/create-nx-workspace/src/utils/string-utils.ts index 70b93505d7..e4de827f90 100644 --- a/packages/create-nx-workspace/src/utils/string-utils.ts +++ b/packages/create-nx-workspace/src/utils/string-utils.ts @@ -8,19 +8,3 @@ export function getFileName(name: string) { .toLowerCase() .replace(/[ _]/g, '-'); } - -export function mapErrorToBodyLines(error: { - logMessage: string; - code: number; - logFile: string; -}): string[] { - if (error.logMessage?.split('\n').filter((line) => !!line).length < 3) { - // print entire log message only if it's only a single message - return [`Error: ${error.logMessage}`]; - } - const lines = [`Exit code: ${error.code}`, `Log file: ${error.logFile}`]; - if (process.env.NX_VERBOSE_LOGGING) { - lines.push(`Error: ${error.logMessage}`); - } - return lines; -} diff --git a/packages/create-nx-workspace/src/utils/unparse.ts b/packages/create-nx-workspace/src/utils/unparse.ts index 7e9c0b2494..362b704737 100644 --- a/packages/create-nx-workspace/src/utils/unparse.ts +++ b/packages/create-nx-workspace/src/utils/unparse.ts @@ -1,9 +1,9 @@ import { flatten } from 'flat'; export function unparse(options: Object): string[] { - const unparsed = []; + const unparsed: string[] = []; for (const key of Object.keys(options)) { - const value = options[key]; + const value = options[key as keyof typeof options]; unparseOption(key, value, unparsed); } diff --git a/packages/create-nx-workspace/src/utils/validate-npm-package.ts b/packages/create-nx-workspace/src/utils/validate-npm-package.ts index e5082ea104..b7220c1ac5 100644 --- a/packages/create-nx-workspace/src/utils/validate-npm-package.ts +++ b/packages/create-nx-workspace/src/utils/validate-npm-package.ts @@ -14,8 +14,8 @@ export interface ValidateNpmResult { } export function validateNpmPackage(name: string): ValidateNpmResult { - let warnings = []; - let errors = []; + let warnings: string[] = []; + let errors: string[] = []; if (name === null) { errors.push('name cannot be null'); diff --git a/packages/create-nx-workspace/tsconfig.json b/packages/create-nx-workspace/tsconfig.json index 58bd2c97a6..5c8f09ef77 100644 --- a/packages/create-nx-workspace/tsconfig.json +++ b/packages/create-nx-workspace/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "types": ["node", "jest"] + "types": ["node", "jest"], + "strict": true }, "include": [], "files": [],