From 63525a9c024431c5501d33b0eb3cb47c9cbe882a Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 15 May 2023 08:33:41 +0200 Subject: [PATCH 1/7] pipe package install logs into a stream for reduced noise --- code/lib/cli/src/generators/baseGenerator.ts | 1 + .../js-package-manager/JsPackageManager.ts | 3 +- .../src/js-package-manager/NPMProxy.test.ts | 68 ++++++++++++ .../cli/src/js-package-manager/NPMProxy.ts | 82 +++++++++++++- .../src/js-package-manager/PNPMProxy.test.ts | 38 +++++++ .../cli/src/js-package-manager/PNPMProxy.ts | 65 ++++++++++- .../src/js-package-manager/Yarn1Proxy.test.ts | 26 +++++ .../cli/src/js-package-manager/Yarn1Proxy.ts | 46 +++++++- .../src/js-package-manager/Yarn2Proxy.test.ts | 34 ++++++ .../cli/src/js-package-manager/Yarn2Proxy.ts | 103 +++++++++++++++++- code/lib/cli/src/utils.ts | 67 ++++++++++++ 11 files changed, 512 insertions(+), 21 deletions(-) diff --git a/code/lib/cli/src/generators/baseGenerator.ts b/code/lib/cli/src/generators/baseGenerator.ts index f4231f56c21b..4e10e803e833 100644 --- a/code/lib/cli/src/generators/baseGenerator.ts +++ b/code/lib/cli/src/generators/baseGenerator.ts @@ -254,6 +254,7 @@ export async function baseGenerator( (packageToInstall) => !installedDependencies.has(getPackageDetails(packageToInstall)[0]) ); + paddedLog(`\nGetting the correct version of ${packages.length} packages`); const versionedPackages = await packageManager.getVersionedPackages(packages); await fse.ensureDir(`./${storybookConfigFolder}`); diff --git a/code/lib/cli/src/js-package-manager/JsPackageManager.ts b/code/lib/cli/src/js-package-manager/JsPackageManager.ts index bde42863f8db..7b6d0478f5c4 100644 --- a/code/lib/cli/src/js-package-manager/JsPackageManager.ts +++ b/code/lib/cli/src/js-package-manager/JsPackageManager.ts @@ -218,7 +218,7 @@ export abstract class JsPackageManager { try { await this.runAddDeps(dependencies, options.installAsDevDependencies); } catch (e) { - logger.error('An error occurred while installing dependencies.'); + logger.error('\nAn error occurred while installing dependencies:'); logger.log(e.message); throw new HandledError(e); } @@ -421,6 +421,7 @@ export abstract class JsPackageManager { stdio?: 'inherit' | 'pipe' ): string; public abstract findInstallations(pattern?: string[]): Promise; + public abstract parseErrorFromLogs(logs?: string): string; public executeCommandSync({ command, diff --git a/code/lib/cli/src/js-package-manager/NPMProxy.test.ts b/code/lib/cli/src/js-package-manager/NPMProxy.test.ts index c0c8cb63be52..8524a1113f55 100644 --- a/code/lib/cli/src/js-package-manager/NPMProxy.test.ts +++ b/code/lib/cli/src/js-package-manager/NPMProxy.test.ts @@ -1,5 +1,15 @@ import { NPMProxy } from './NPMProxy'; +// mock createLogStream +jest.mock('../utils', () => ({ + createLogStream: jest.fn(() => ({ + logStream: '', + readLogFile: jest.fn(), + moveLogFile: jest.fn(), + removeLogFile: jest.fn(), + })), +})); + describe('NPM Proxy', () => { let npmProxy: NPMProxy; @@ -426,4 +436,62 @@ describe('NPM Proxy', () => { `); }); }); + + describe('parseErrors', () => { + it('should parse npm errors', () => { + const NPM_ERROR_SAMPLE = ` + npm ERR! code ERESOLVE + npm ERR! ERESOLVE unable to resolve dependency tree + npm ERR! + npm ERR! While resolving: before-storybook@1.0.0 + npm ERR! Found: react@undefined + npm ERR! node_modules/react + npm ERR! react@"30" from the root project + npm ERR! + npm ERR! Could not resolve dependency: + npm ERR! peer react@"^16.8.0 || ^17.0.0 || ^18.0.0" from @storybook/react@7.1.0-alpha.17 + npm ERR! node_modules/@storybook/react + npm ERR! dev @storybook/react@"^7.1.0-alpha.17" from the root project + npm ERR! + npm ERR! Fix the upstream dependency conflict, or retry + npm ERR! this command with --force or --legacy-peer-deps + npm ERR! to accept an incorrect (and potentially broken) dependency resolution. + npm ERR! + npm ERR! + npm ERR! For a full report see: + npm ERR! /Users/yannbraga/.npm/_logs/2023-05-12T08_38_18_464Z-eresolve-report.txt + + npm ERR! A complete log of this run can be found in: + npm ERR! /Users/yannbraga/.npm/_logs/2023-05-12T08_38_18_464Z-debug-0.log + `; + + expect(npmProxy.parseErrorFromLogs(NPM_ERROR_SAMPLE)).toEqual( + 'ERESOLVE: Dependency resolution error.' + ); + }); + + it('should show unknown npm error', () => { + const NPM_ERROR_SAMPLE = ` + npm ERR! + npm ERR! While resolving: before-storybook@1.0.0 + npm ERR! Found: react@undefined + npm ERR! node_modules/react + npm ERR! react@"30" from the root project + `; + + expect(npmProxy.parseErrorFromLogs(NPM_ERROR_SAMPLE)).toEqual(`Unknown NPM error`); + }); + + it('should show unknown npm error with code if it at least matches the pattern', () => { + const NPM_ERROR_SAMPLE = ` + npm ERR! code ESOMETHING + npm ERR! ESOMETHING something something + npm ERR! + `; + + expect(npmProxy.parseErrorFromLogs(NPM_ERROR_SAMPLE)).toEqual( + `Unknown NPM error: ESOMETHING` + ); + }); + }); }); diff --git a/code/lib/cli/src/js-package-manager/NPMProxy.ts b/code/lib/cli/src/js-package-manager/NPMProxy.ts index 5dd0d63bd262..ff9a1c675490 100644 --- a/code/lib/cli/src/js-package-manager/NPMProxy.ts +++ b/code/lib/cli/src/js-package-manager/NPMProxy.ts @@ -1,8 +1,10 @@ import sort from 'semver/functions/sort'; import { platform } from 'os'; +import dedent from 'ts-dedent'; import { JsPackageManager } from './JsPackageManager'; import type { PackageJson } from './PackageJson'; import type { InstallationMetadata, PackageMetadata } from './types'; +import { createLogStream } from '../utils'; type NpmDependency = { version: string; @@ -19,6 +21,41 @@ export type NpmListOutput = { dependencies: NpmDependencies; }; +const NPM_ERROR_REGEX = /\bERR! code\s+([A-Z]+)\b/; +const NPM_ERROR_CODES = { + E401: 'Authentication failed or is required.', + E403: 'Access to the resource is forbidden.', + E404: 'Requested resource not found.', + EACCES: 'Permission issue.', + EAI_FAIL: 'DNS lookup failed.', + EBADENGINE: 'Engine compatibility check failed.', + EBADPLATFORM: 'Platform not supported.', + ECONNREFUSED: 'Connection refused.', + ECONNRESET: 'Connection reset.', + EEXIST: 'File or directory already exists.', + EINVALIDTYPE: 'Invalid type encountered.', + EISGIT: 'Git operation failed or conflicts with an existing file.', + EJSONPARSE: 'Error parsing JSON data.', + EMISSINGARG: 'Required argument missing.', + ENEEDAUTH: 'Authentication needed.', + ENOAUDIT: 'No audit available.', + ENOENT: 'File or directory does not exist.', + ENOGIT: 'Git not found or failed to run.', + ENOLOCK: 'Lockfile missing.', + ENOSPC: 'Insufficient disk space.', + ENOTFOUND: 'Resource not found.', + EOTP: 'One-time password required.', + EPERM: 'Permission error.', + EPUBLISHCONFLICT: 'Conflict during package publishing.', + ERESOLVE: 'Dependency resolution error.', + EROFS: 'File system is read-only.', + ERR_SOCKET_TIMEOUT: 'Socket timed out.', + ETARGET: 'Package target not found.', + ETIMEDOUT: 'Operation timed out.', + ETOOMANYARGS: 'Too many arguments provided.', + EUNKNOWNTYPE: 'Unknown type encountered.', +}; + export class NPMProxy extends JsPackageManager { readonly type = 'npm'; @@ -104,17 +141,38 @@ export class NPMProxy extends JsPackageManager { } protected async runAddDeps(dependencies: string[], installAsDevDependencies: boolean) { + const { logStream, readLogFile, moveLogFile, removeLogFile } = await createLogStream( + 'init-storybook.log' + ); let args = [...dependencies]; if (installAsDevDependencies) { args = ['-D', ...args]; } - await this.executeCommand({ - command: 'npm', - args: ['install', ...this.getInstallArgs(), ...args], - stdio: 'inherit', - }); + logStream.write(`\n THIS IS CUSTOM! Installing dependencies:\n${args.join('\n')}\n\n`); + + try { + await this.executeCommand({ + command: 'npm', + args: ['install', ...args, ...this.getInstallArgs()], + stdio: ['ignore', logStream, logStream], + }); + } catch (err) { + const stdout = await readLogFile(); + + const errorMessage = this.parseErrorFromLogs(stdout); + + await moveLogFile(); + + throw new Error( + dedent`${errorMessage} + + Please check the logfile generated at ./init-install.log for troubleshooting and try again.` + ); + } + + await removeLogFile(); } protected async runRemoveDeps(dependencies: string[]) { @@ -191,4 +249,18 @@ export class NPMProxy extends JsPackageManager { infoCommand: 'npm ls --depth=1', }; } + + public parseErrorFromLogs(logs: string): string { + const match = logs.match(NPM_ERROR_REGEX); + let errorCode; + if (match) { + errorCode = match[1] as keyof typeof NPM_ERROR_CODES; + const errorMessage = NPM_ERROR_CODES[errorCode]; + if (errorCode && errorMessage) { + return `${errorCode}: ${errorMessage}`.trim(); + } + } + + return `Unknown NPM error${errorCode ? `: ${errorCode}` : ''}`; + } } diff --git a/code/lib/cli/src/js-package-manager/PNPMProxy.test.ts b/code/lib/cli/src/js-package-manager/PNPMProxy.test.ts index eb82f1a06465..2300c5dc3a7c 100644 --- a/code/lib/cli/src/js-package-manager/PNPMProxy.test.ts +++ b/code/lib/cli/src/js-package-manager/PNPMProxy.test.ts @@ -375,4 +375,42 @@ describe('NPM Proxy', () => { `); }); }); + + describe('parseErrors', () => { + it('should parse pnpm errors', () => { + const PNPM_ERROR_SAMPLE = ` + ERR_PNPM_NO_MATCHING_VERSION No matching version found for react@29.2.0 + + This error happened while installing a direct dependency of /Users/yannbraga/open-source/sandboxes/react-vite/default-js/before-storybook + + The latest release of react is "18.2.0". + `; + + expect(pnpmProxy.parseErrorFromLogs(PNPM_ERROR_SAMPLE)).toEqual( + 'ERR_PNPM_NO_MATCHING_VERSION: No matching version found for react@29.2.0' + ); + }); + + it('should show unknown pnpm error', () => { + const PNPM_ERROR_SAMPLE = ` + This error happened while installing a direct dependency of /Users/yannbraga/open-source/sandboxes/react-vite/default-js/before-storybook + + The latest release of react is "18.2.0". + `; + + expect(pnpmProxy.parseErrorFromLogs(PNPM_ERROR_SAMPLE)).toEqual(`Unknown PNPM error`); + }); + + it('should show unknown pnpm error with code if it at least matches the pattern', () => { + const PNPM_ERROR_SAMPLE = ` + ERR_PNPM_SOMETHING No matching version found for react@29.2.0 + + This error happened while installing a direct dependency of /Users/yannbraga/open-source/sandboxes/react-vite/default-js/before-storybook + `; + + expect(pnpmProxy.parseErrorFromLogs(PNPM_ERROR_SAMPLE)).toEqual( + `Unknown PNPM error: ERR_PNPM_SOMETHING` + ); + }); + }); }); diff --git a/code/lib/cli/src/js-package-manager/PNPMProxy.ts b/code/lib/cli/src/js-package-manager/PNPMProxy.ts index 8e8a4dce793e..14c08067bee0 100644 --- a/code/lib/cli/src/js-package-manager/PNPMProxy.ts +++ b/code/lib/cli/src/js-package-manager/PNPMProxy.ts @@ -1,7 +1,9 @@ import { pathExistsSync } from 'fs-extra'; +import dedent from 'ts-dedent'; import { JsPackageManager } from './JsPackageManager'; import type { PackageJson } from './PackageJson'; import type { InstallationMetadata, PackageMetadata } from './types'; +import { createLogStream } from '../utils'; type PnpmDependency = { from: string; @@ -22,6 +24,26 @@ type PnpmListItem = { export type PnpmListOutput = PnpmListItem[]; +const PNPM_ERROR_REGEX = /(ELIFECYCLE|ERR_PNPM_[A-Z_]+)\s+(.*)/i; +const PNPM_ERROR_CODES = { + ELIFECYCLE: 'Lifecycle error', + ERR_PNPM_BAD_TARBALL_SIZE: 'Bad tarball size error', + ERR_PNPM_DEDUPE_CHECK_ISSUES: 'Dedupe check issues error', + ERR_PNPM_FETCH_401: 'Fetch 401 error', + ERR_PNPM_FETCH_403: 'Fetch 403 error', + ERR_PNPM_LOCKFILE_BREAKING_CHANGE: 'Lockfile breaking change error', + ERR_PNPM_MODIFIED_DEPENDENCY: 'Modified dependency error', + ERR_PNPM_MODULES_BREAKING_CHANGE: 'Modules breaking change error', + ERR_PNPM_NO_MATCHING_VERSION: 'No matching version error', + ERR_PNPM_PEER_DEP_ISSUES: 'Peer dependency issues error', + ERR_PNPM_RECURSIVE_FAIL: 'Recursive command failed error', + ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT: 'Recursive run no script error', + ERR_PNPM_STORE_BREAKING_CHANGE: 'Store breaking change error', + ERR_PNPM_UNEXPECTED_STORE: 'Unexpected store error', + ERR_PNPM_UNEXPECTED_VIRTUAL_STORE: 'Unexpected virtual store error', + ERR_PNPM_UNSUPPORTED_ENGINE: 'Unsupported engine error', +}; + export class PNPMProxy extends JsPackageManager { readonly type = 'pnpm'; @@ -126,12 +148,31 @@ export class PNPMProxy extends JsPackageManager { if (installAsDevDependencies) { args = ['-D', ...args]; } + const { logStream, readLogFile, moveLogFile, removeLogFile } = await createLogStream( + 'init-storybook.log' + ); - await this.executeCommand({ - command: 'pnpm', - args: ['add', ...args, ...this.getInstallArgs()], - stdio: 'inherit', - }); + try { + await this.executeCommand({ + command: 'pnpm', + args: ['add', ...args, ...this.getInstallArgs()], + stdio: ['ignore', logStream, logStream], + }); + } catch (err) { + const stdout = await readLogFile(); + + const errorMessage = this.parseErrorFromLogs(stdout); + + await moveLogFile(); + + throw new Error( + dedent`${errorMessage} + + Please check the logfile generated at ./init-install.log for troubleshooting and try again.` + ); + } + + await removeLogFile(); } protected async runRemoveDeps(dependencies: string[]) { @@ -212,4 +253,18 @@ export class PNPMProxy extends JsPackageManager { infoCommand: 'pnpm list --depth=1', }; } + + public parseErrorFromLogs(logs: string): string { + const match = logs.match(PNPM_ERROR_REGEX); + let errorCode; + if (match) { + errorCode = match[1] as keyof typeof PNPM_ERROR_CODES; + const errorMessage = match[2]; + const errorType = PNPM_ERROR_CODES[errorCode]; + if (errorType && errorMessage) { + return `${errorCode}: ${errorMessage}`.trim(); + } + } + return `Unknown PNPM error${errorCode ? `: ${errorCode}` : ''}`; + } } diff --git a/code/lib/cli/src/js-package-manager/Yarn1Proxy.test.ts b/code/lib/cli/src/js-package-manager/Yarn1Proxy.test.ts index fb9edaef3cc0..0da683dfadb8 100644 --- a/code/lib/cli/src/js-package-manager/Yarn1Proxy.test.ts +++ b/code/lib/cli/src/js-package-manager/Yarn1Proxy.test.ts @@ -1,3 +1,4 @@ +import dedent from 'ts-dedent'; import { Yarn1Proxy } from './Yarn1Proxy'; describe('Yarn 1 Proxy', () => { @@ -275,4 +276,29 @@ describe('Yarn 1 Proxy', () => { `); }); }); + + describe('parseErrors', () => { + it('should parse yarn1 errors', () => { + const YARN1_ERROR_SAMPLE = dedent` + yarn add v1.22.19 + [1/4] Resolving packages... + error Couldn't find any versions for "react" that matches "28.2.0" + info Visit https://yarnpkg.com/en/docs/cli/add for documentation about this command. + `; + + expect(yarn1Proxy.parseErrorFromLogs(YARN1_ERROR_SAMPLE)).toEqual( + `YARN1: Couldn't find any versions for "react" that matches "28.2.0"` + ); + }); + + it('should show unknown yarn1 error', () => { + const YARN1_ERROR_SAMPLE = dedent` + yarn install v1.22.19 + [1/4] 🔍 Resolving packages... + info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command. + `; + + expect(yarn1Proxy.parseErrorFromLogs(YARN1_ERROR_SAMPLE)).toEqual(`Unknown Yarn1 error`); + }); + }); }); diff --git a/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts b/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts index 00de17ded091..6683ff014b9c 100644 --- a/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts +++ b/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts @@ -1,3 +1,5 @@ +import dedent from 'ts-dedent'; +import { createLogStream } from '../utils'; import { JsPackageManager } from './JsPackageManager'; import type { PackageJson } from './PackageJson'; import type { InstallationMetadata, PackageMetadata } from './types'; @@ -18,6 +20,8 @@ export type Yarn1ListOutput = { data: Yarn1ListData; }; +const YARN1_ERROR_REGEX = /^error\s(.*)$/gm; + export class Yarn1Proxy extends JsPackageManager { readonly type = 'yarn1'; @@ -93,11 +97,31 @@ export class Yarn1Proxy extends JsPackageManager { args = ['-D', ...args]; } - await this.executeCommand({ - command: 'yarn', - args: ['add', ...this.getInstallArgs(), ...args], - stdio: 'inherit', - }); + const { logStream, readLogFile, moveLogFile, removeLogFile } = await createLogStream( + 'init-storybook.log' + ); + + try { + await this.executeCommand({ + command: 'yarn', + args: ['add', ...this.getInstallArgs(), ...args], + stdio: ['ignore', logStream, logStream], + }); + } catch (err) { + const stdout = await readLogFile(); + + const errorMessage = this.parseErrorFromLogs(stdout); + + await moveLogFile(); + + throw new Error( + dedent`${errorMessage} + + Please check the logfile generated at ./init-install.log for troubleshooting and try again.` + ); + } + + await removeLogFile(); } protected async runRemoveDeps(dependencies: string[]) { @@ -170,4 +194,16 @@ export class Yarn1Proxy extends JsPackageManager { throw new Error('Something went wrong while parsing yarn output'); } + + public parseErrorFromLogs(logs: string): string { + const match = logs.match(YARN1_ERROR_REGEX); + if (match) { + const errorMessage = match[0].replace(/^error\s(.*)$/, '$1'); + if (errorMessage) { + return `YARN1: ${errorMessage}`.trim(); + } + } + + return `Unknown Yarn1 error`; + } } diff --git a/code/lib/cli/src/js-package-manager/Yarn2Proxy.test.ts b/code/lib/cli/src/js-package-manager/Yarn2Proxy.test.ts index f875254a6858..521cdf1241ea 100644 --- a/code/lib/cli/src/js-package-manager/Yarn2Proxy.test.ts +++ b/code/lib/cli/src/js-package-manager/Yarn2Proxy.test.ts @@ -1,3 +1,4 @@ +import dedent from 'ts-dedent'; import { Yarn2Proxy } from './Yarn2Proxy'; describe('Yarn 2 Proxy', () => { @@ -271,4 +272,37 @@ describe('Yarn 2 Proxy', () => { `); }); }); + + describe('parseErrors', () => { + it('should parse yarn2 errors', () => { + const YARN2_ERROR_SAMPLE = ` + ➤ YN0000: ┌ Resolution step + ➤ YN0001: │ Error: react@npm:28.2.0: No candidates found + at ge (/Users/yannbraga/.cache/node/corepack/yarn/3.5.1/yarn.js:439:8124) + at process.processTicksAndRejections (node:internal/process/task_queues:95:5) + at async Promise.allSettled (index 8) + at async io (/Users/yannbraga/.cache/node/corepack/yarn/3.5.1/yarn.js:390:10398) + ➤ YN0000: └ Completed in 2s 369ms + ➤ YN0000: Failed with errors in 2s 372ms + ➤ YN0032: fsevents@npm:2.3.2: Implicit dependencies on node-gyp are discouraged + ➤ YN0061: @npmcli/move-file@npm:2.0.1 is deprecated: This functionality has been moved to @npmcli/fs + `; + + expect(yarn2Proxy.parseErrorFromLogs(YARN2_ERROR_SAMPLE)).toEqual( + 'YN0001 - EXCEPTION: react@npm:28.2.0: No candidates found' + ); + }); + + it('should show unknown yarn2 error', () => { + const YARN2_ERROR_SAMPLE = dedent` + ➤ YN0000: ┌ Resolution step + ➤ YN0000: └ Completed in 2s 369ms + ➤ YN0000: Failed with errors in 2s 372ms + ➤ YN0032: fsevents@npm:2.3.2: Implicit dependencies on node-gyp are discouraged + ➤ YN0061: @npmcli/move-file@npm:2.0.1 is deprecated: This functionality has been moved to @npmcli/fs + `; + + expect(yarn2Proxy.parseErrorFromLogs(YARN2_ERROR_SAMPLE)).toEqual(`Unknown Yarn2 error`); + }); + }); }); diff --git a/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts b/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts index 7005052b0d0a..57a9d02eff0d 100644 --- a/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts +++ b/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts @@ -1,8 +1,65 @@ +import dedent from 'ts-dedent'; +import { createLogStream } from '../utils'; import { JsPackageManager } from './JsPackageManager'; import type { PackageJson } from './PackageJson'; import type { InstallationMetadata, PackageMetadata } from './types'; import { parsePackageData } from './util'; +const YARN2_ERROR_REGEX = /(YN\d{4}):.*?Error:\s+(.*)/i; +const YARN2_ERROR_CODES = { + YN0000: 'UNNAMED', + YN0001: 'EXCEPTION', + YN0002: 'MISSING_PEER_DEPENDENCY', + YN0003: 'CYCLIC_DEPENDENCIES', + YN0004: 'DISABLED_BUILD_SCRIPTS', + YN0005: 'BUILD_DISABLED', + YN0006: 'SOFT_LINK_BUILD', + YN0007: 'MUST_BUILD', + YN0008: 'MUST_REBUILD', + YN0009: 'BUILD_FAILED', + YN0010: 'RESOLVER_NOT_FOUND', + YN0011: 'FETCHER_NOT_FOUND', + YN0012: 'LINKER_NOT_FOUND', + YN0013: 'FETCH_NOT_CACHED', + YN0014: 'YARN_IMPORT_FAILED', + YN0015: 'REMOTE_INVALID', + YN0016: 'REMOTE_NOT_FOUND', + YN0017: 'RESOLUTION_PACK', + YN0018: 'CACHE_CHECKSUM_MISMATCH', + YN0019: 'UNUSED_CACHE_ENTRY', + YN0020: 'MISSING_LOCKFILE_ENTRY', + YN0021: 'WORKSPACE_NOT_FOUND', + YN0022: 'TOO_MANY_MATCHING_WORKSPACES', + YN0023: 'CONSTRAINTS_MISSING_DEPENDENCY', + YN0024: 'CONSTRAINTS_INCOMPATIBLE_DEPENDENCY', + YN0025: 'CONSTRAINTS_EXTRANEOUS_DEPENDENCY', + YN0026: 'CONSTRAINTS_INVALID_DEPENDENCY', + YN0027: 'CANT_SUGGEST_RESOLUTIONS', + YN0028: 'FROZEN_LOCKFILE_EXCEPTION', + YN0029: 'CROSS_DRIVE_VIRTUAL_LOCAL', + YN0030: 'FETCH_FAILED', + YN0031: 'DANGEROUS_NODE_MODULES', + YN0032: 'NODE_GYP_INJECTED', + YN0046: 'AUTOMERGE_FAILED_TO_PARSE', + YN0047: 'AUTOMERGE_IMMUTABLE', + YN0048: 'AUTOMERGE_SUCCESS', + YN0049: 'AUTOMERGE_REQUIRED', + YN0050: 'DEPRECATED_CLI_SETTINGS', + YN0059: 'INVALID_RANGE_PEER_DEPENDENCY', + YN0060: 'INCOMPATIBLE_PEER_DEPENDENCY', + YN0061: 'DEPRECATED_PACKAGE', + YN0062: 'INCOMPATIBLE_OS', + YN0063: 'INCOMPATIBLE_CPU', + YN0068: 'UNUSED_PACKAGE_EXTENSION', + YN0069: 'REDUNDANT_PACKAGE_EXTENSION', + YN0071: 'NM_CANT_INSTALL_EXTERNAL_SOFT_LINK', + YN0072: 'NM_PRESERVE_SYMLINKS_REQUIRED', + YN0074: 'NM_HARDLINKS_MODE_DOWNGRADED', + YN0075: 'PROLOG_INSTANTIATION_ERROR', + YN0076: 'INCOMPATIBLE_ARCHITECTURE', + YN0077: 'GHOST_ARCHITECTURE', +}; + // This encompasses both yarn 2 and yarn 3 export class Yarn2Proxy extends JsPackageManager { readonly type = 'yarn2'; @@ -84,11 +141,31 @@ export class Yarn2Proxy extends JsPackageManager { args = ['-D', ...args]; } - await this.executeCommand({ - command: 'yarn', - args: ['add', ...this.getInstallArgs(), ...args], - stdio: 'inherit', - }); + const { logStream, readLogFile, moveLogFile, removeLogFile } = await createLogStream( + 'init-storybook.log' + ); + + try { + await this.executeCommand({ + command: 'yarn', + args: ['add', ...this.getInstallArgs(), ...args], + stdio: ['ignore', logStream, logStream], + }); + } catch (err) { + const stdout = await readLogFile(); + + const errorMessage = this.parseErrorFromLogs(stdout); + + await moveLogFile(); + + throw new Error( + dedent`${errorMessage} + + Please check the logfile generated at ./init-install.log for troubleshooting and try again.` + ); + } + + await removeLogFile(); } protected async runRemoveDeps(dependencies: string[]) { @@ -153,4 +230,20 @@ export class Yarn2Proxy extends JsPackageManager { infoCommand: 'yarn why', }; } + + public parseErrorFromLogs(logs: string): string { + const match = logs.match(YARN2_ERROR_REGEX); + let errorCode; + + if (match) { + errorCode = match[1] as keyof typeof YARN2_ERROR_CODES; + const errorMessage = match[2]; + const errorType = YARN2_ERROR_CODES[errorCode]; + if (errorCode && errorMessage) { + return `${errorCode} - ${errorType}: ${errorMessage}`.trim(); + } + } + + return `Unknown Yarn2 error${errorCode ? `: ${errorCode}` : ''}`; + } } diff --git a/code/lib/cli/src/utils.ts b/code/lib/cli/src/utils.ts index 4650fd055dcf..8a62c49c2f92 100644 --- a/code/lib/cli/src/utils.ts +++ b/code/lib/cli/src/utils.ts @@ -1,3 +1,8 @@ +import type { WriteStream } from 'fs-extra'; +import { move, remove, writeFile, readFile, createWriteStream } from 'fs-extra'; +import { join } from 'path'; +import tempy from 'tempy'; + export function parseList(str: string): string[] { return str .split(',') @@ -15,3 +20,65 @@ export function getEnvConfig(program: Record, configEnv: Record Promise; + removeLogFile: () => Promise; + clearLogFile: () => Promise; + readLogFile: () => Promise; + logStream: WriteStream; +}> => { + const finalLogPath = join(process.cwd(), logFileName); + const temporaryLogPath = tempy.file({ name: logFileName }); + + const logStream = createWriteStream(temporaryLogPath, { encoding: 'utf8' }); + + return new Promise((resolve, reject) => { + logStream.once('open', () => { + const moveLogFile = async () => move(temporaryLogPath, finalLogPath, { overwrite: true }); + const clearLogFile = async () => writeFile(temporaryLogPath, ''); + const removeLogFile = async () => remove(temporaryLogPath); + const readLogFile = async () => { + return readFile(temporaryLogPath, 'utf8'); // .replace(/[\x00-\x1F\x7F]/g, ''); + }; + resolve({ logStream, moveLogFile, clearLogFile, removeLogFile, readLogFile }); + }); + logStream.once('error', reject); + }); +}; From d12eca562164813e61f610ebeedbeeebc428d4e8 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 15 May 2023 10:50:58 +0200 Subject: [PATCH 2/7] cleanup --- code/lib/cli/src/js-package-manager/NPMProxy.ts | 2 -- code/lib/cli/src/utils.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/code/lib/cli/src/js-package-manager/NPMProxy.ts b/code/lib/cli/src/js-package-manager/NPMProxy.ts index ff9a1c675490..a79584dabd3f 100644 --- a/code/lib/cli/src/js-package-manager/NPMProxy.ts +++ b/code/lib/cli/src/js-package-manager/NPMProxy.ts @@ -150,8 +150,6 @@ export class NPMProxy extends JsPackageManager { args = ['-D', ...args]; } - logStream.write(`\n THIS IS CUSTOM! Installing dependencies:\n${args.join('\n')}\n\n`); - try { await this.executeCommand({ command: 'npm', diff --git a/code/lib/cli/src/utils.ts b/code/lib/cli/src/utils.ts index 8a62c49c2f92..0ec96e84b7a2 100644 --- a/code/lib/cli/src/utils.ts +++ b/code/lib/cli/src/utils.ts @@ -75,7 +75,7 @@ export const createLogStream = async ( const clearLogFile = async () => writeFile(temporaryLogPath, ''); const removeLogFile = async () => remove(temporaryLogPath); const readLogFile = async () => { - return readFile(temporaryLogPath, 'utf8'); // .replace(/[\x00-\x1F\x7F]/g, ''); + return readFile(temporaryLogPath, 'utf8'); }; resolve({ logStream, moveLogFile, clearLogFile, removeLogFile, readLogFile }); }); From dd4f6eaf83c3d68f1fc108147bf34203ffba1d63 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Mon, 15 May 2023 18:55:07 +0200 Subject: [PATCH 3/7] improve wording --- .../src/js-package-manager/NPMProxy.test.ts | 16 ++-------- .../cli/src/js-package-manager/NPMProxy.ts | 15 ++++++--- .../src/js-package-manager/PNPMProxy.test.ts | 16 ++-------- .../cli/src/js-package-manager/PNPMProxy.ts | 31 ++++--------------- .../src/js-package-manager/Yarn1Proxy.test.ts | 4 +-- .../cli/src/js-package-manager/Yarn1Proxy.ts | 8 +++-- .../src/js-package-manager/Yarn2Proxy.test.ts | 4 +-- .../cli/src/js-package-manager/Yarn2Proxy.ts | 20 ++++++++---- 8 files changed, 43 insertions(+), 71 deletions(-) diff --git a/code/lib/cli/src/js-package-manager/NPMProxy.test.ts b/code/lib/cli/src/js-package-manager/NPMProxy.test.ts index 8524a1113f55..9de82b3114ba 100644 --- a/code/lib/cli/src/js-package-manager/NPMProxy.test.ts +++ b/code/lib/cli/src/js-package-manager/NPMProxy.test.ts @@ -466,7 +466,7 @@ describe('NPM Proxy', () => { `; expect(npmProxy.parseErrorFromLogs(NPM_ERROR_SAMPLE)).toEqual( - 'ERESOLVE: Dependency resolution error.' + 'NPM error ERESOLVE - Dependency resolution error.' ); }); @@ -479,19 +479,7 @@ describe('NPM Proxy', () => { npm ERR! react@"30" from the root project `; - expect(npmProxy.parseErrorFromLogs(NPM_ERROR_SAMPLE)).toEqual(`Unknown NPM error`); - }); - - it('should show unknown npm error with code if it at least matches the pattern', () => { - const NPM_ERROR_SAMPLE = ` - npm ERR! code ESOMETHING - npm ERR! ESOMETHING something something - npm ERR! - `; - - expect(npmProxy.parseErrorFromLogs(NPM_ERROR_SAMPLE)).toEqual( - `Unknown NPM error: ESOMETHING` - ); + expect(npmProxy.parseErrorFromLogs(NPM_ERROR_SAMPLE)).toEqual(`NPM error`); }); }); }); diff --git a/code/lib/cli/src/js-package-manager/NPMProxy.ts b/code/lib/cli/src/js-package-manager/NPMProxy.ts index a79584dabd3f..e54305d36a64 100644 --- a/code/lib/cli/src/js-package-manager/NPMProxy.ts +++ b/code/lib/cli/src/js-package-manager/NPMProxy.ts @@ -249,16 +249,21 @@ export class NPMProxy extends JsPackageManager { } public parseErrorFromLogs(logs: string): string { + let finalMessage = 'NPM error'; const match = logs.match(NPM_ERROR_REGEX); - let errorCode; + if (match) { - errorCode = match[1] as keyof typeof NPM_ERROR_CODES; + const errorCode = match[1] as keyof typeof NPM_ERROR_CODES; + if (errorCode) { + finalMessage = `${finalMessage} ${errorCode}`; + } + const errorMessage = NPM_ERROR_CODES[errorCode]; - if (errorCode && errorMessage) { - return `${errorCode}: ${errorMessage}`.trim(); + if (errorMessage) { + finalMessage = `${finalMessage} - ${errorMessage}`; } } - return `Unknown NPM error${errorCode ? `: ${errorCode}` : ''}`; + return finalMessage.trim(); } } diff --git a/code/lib/cli/src/js-package-manager/PNPMProxy.test.ts b/code/lib/cli/src/js-package-manager/PNPMProxy.test.ts index 2300c5dc3a7c..04b35ccdab37 100644 --- a/code/lib/cli/src/js-package-manager/PNPMProxy.test.ts +++ b/code/lib/cli/src/js-package-manager/PNPMProxy.test.ts @@ -387,7 +387,7 @@ describe('NPM Proxy', () => { `; expect(pnpmProxy.parseErrorFromLogs(PNPM_ERROR_SAMPLE)).toEqual( - 'ERR_PNPM_NO_MATCHING_VERSION: No matching version found for react@29.2.0' + 'PNPM error ERR_PNPM_NO_MATCHING_VERSION No matching version found for react@29.2.0' ); }); @@ -398,19 +398,7 @@ describe('NPM Proxy', () => { The latest release of react is "18.2.0". `; - expect(pnpmProxy.parseErrorFromLogs(PNPM_ERROR_SAMPLE)).toEqual(`Unknown PNPM error`); - }); - - it('should show unknown pnpm error with code if it at least matches the pattern', () => { - const PNPM_ERROR_SAMPLE = ` - ERR_PNPM_SOMETHING No matching version found for react@29.2.0 - - This error happened while installing a direct dependency of /Users/yannbraga/open-source/sandboxes/react-vite/default-js/before-storybook - `; - - expect(pnpmProxy.parseErrorFromLogs(PNPM_ERROR_SAMPLE)).toEqual( - `Unknown PNPM error: ERR_PNPM_SOMETHING` - ); + expect(pnpmProxy.parseErrorFromLogs(PNPM_ERROR_SAMPLE)).toEqual(`PNPM error`); }); }); }); diff --git a/code/lib/cli/src/js-package-manager/PNPMProxy.ts b/code/lib/cli/src/js-package-manager/PNPMProxy.ts index 14c08067bee0..bbd0a1bc8967 100644 --- a/code/lib/cli/src/js-package-manager/PNPMProxy.ts +++ b/code/lib/cli/src/js-package-manager/PNPMProxy.ts @@ -25,24 +25,6 @@ type PnpmListItem = { export type PnpmListOutput = PnpmListItem[]; const PNPM_ERROR_REGEX = /(ELIFECYCLE|ERR_PNPM_[A-Z_]+)\s+(.*)/i; -const PNPM_ERROR_CODES = { - ELIFECYCLE: 'Lifecycle error', - ERR_PNPM_BAD_TARBALL_SIZE: 'Bad tarball size error', - ERR_PNPM_DEDUPE_CHECK_ISSUES: 'Dedupe check issues error', - ERR_PNPM_FETCH_401: 'Fetch 401 error', - ERR_PNPM_FETCH_403: 'Fetch 403 error', - ERR_PNPM_LOCKFILE_BREAKING_CHANGE: 'Lockfile breaking change error', - ERR_PNPM_MODIFIED_DEPENDENCY: 'Modified dependency error', - ERR_PNPM_MODULES_BREAKING_CHANGE: 'Modules breaking change error', - ERR_PNPM_NO_MATCHING_VERSION: 'No matching version error', - ERR_PNPM_PEER_DEP_ISSUES: 'Peer dependency issues error', - ERR_PNPM_RECURSIVE_FAIL: 'Recursive command failed error', - ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT: 'Recursive run no script error', - ERR_PNPM_STORE_BREAKING_CHANGE: 'Store breaking change error', - ERR_PNPM_UNEXPECTED_STORE: 'Unexpected store error', - ERR_PNPM_UNEXPECTED_VIRTUAL_STORE: 'Unexpected virtual store error', - ERR_PNPM_UNSUPPORTED_ENGINE: 'Unsupported engine error', -}; export class PNPMProxy extends JsPackageManager { readonly type = 'pnpm'; @@ -255,16 +237,15 @@ export class PNPMProxy extends JsPackageManager { } public parseErrorFromLogs(logs: string): string { + let finalMessage = 'PNPM error'; const match = logs.match(PNPM_ERROR_REGEX); - let errorCode; if (match) { - errorCode = match[1] as keyof typeof PNPM_ERROR_CODES; - const errorMessage = match[2]; - const errorType = PNPM_ERROR_CODES[errorCode]; - if (errorType && errorMessage) { - return `${errorCode}: ${errorMessage}`.trim(); + const [errorCode] = match; + if (errorCode) { + finalMessage = `${finalMessage} ${errorCode}`; } } - return `Unknown PNPM error${errorCode ? `: ${errorCode}` : ''}`; + + return finalMessage.trim(); } } diff --git a/code/lib/cli/src/js-package-manager/Yarn1Proxy.test.ts b/code/lib/cli/src/js-package-manager/Yarn1Proxy.test.ts index 0da683dfadb8..3edf536f2a96 100644 --- a/code/lib/cli/src/js-package-manager/Yarn1Proxy.test.ts +++ b/code/lib/cli/src/js-package-manager/Yarn1Proxy.test.ts @@ -287,7 +287,7 @@ describe('Yarn 1 Proxy', () => { `; expect(yarn1Proxy.parseErrorFromLogs(YARN1_ERROR_SAMPLE)).toEqual( - `YARN1: Couldn't find any versions for "react" that matches "28.2.0"` + `YARN1 error: Couldn't find any versions for "react" that matches "28.2.0"` ); }); @@ -298,7 +298,7 @@ describe('Yarn 1 Proxy', () => { info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command. `; - expect(yarn1Proxy.parseErrorFromLogs(YARN1_ERROR_SAMPLE)).toEqual(`Unknown Yarn1 error`); + expect(yarn1Proxy.parseErrorFromLogs(YARN1_ERROR_SAMPLE)).toEqual(`YARN1 error`); }); }); }); diff --git a/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts b/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts index 6683ff014b9c..b31372502d0b 100644 --- a/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts +++ b/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts @@ -196,14 +196,16 @@ export class Yarn1Proxy extends JsPackageManager { } public parseErrorFromLogs(logs: string): string { + let finalMessage = 'YARN1 error'; const match = logs.match(YARN1_ERROR_REGEX); + if (match) { - const errorMessage = match[0].replace(/^error\s(.*)$/, '$1'); + const errorMessage = match[0]?.replace(/^error\s(.*)$/, '$1'); if (errorMessage) { - return `YARN1: ${errorMessage}`.trim(); + finalMessage = `${finalMessage}: ${errorMessage}`; } } - return `Unknown Yarn1 error`; + return finalMessage.trim(); } } diff --git a/code/lib/cli/src/js-package-manager/Yarn2Proxy.test.ts b/code/lib/cli/src/js-package-manager/Yarn2Proxy.test.ts index 521cdf1241ea..51e5c1235cd4 100644 --- a/code/lib/cli/src/js-package-manager/Yarn2Proxy.test.ts +++ b/code/lib/cli/src/js-package-manager/Yarn2Proxy.test.ts @@ -289,7 +289,7 @@ describe('Yarn 2 Proxy', () => { `; expect(yarn2Proxy.parseErrorFromLogs(YARN2_ERROR_SAMPLE)).toEqual( - 'YN0001 - EXCEPTION: react@npm:28.2.0: No candidates found' + 'YARN2 error YN0001 - EXCEPTION: react@npm:28.2.0: No candidates found' ); }); @@ -302,7 +302,7 @@ describe('Yarn 2 Proxy', () => { ➤ YN0061: @npmcli/move-file@npm:2.0.1 is deprecated: This functionality has been moved to @npmcli/fs `; - expect(yarn2Proxy.parseErrorFromLogs(YARN2_ERROR_SAMPLE)).toEqual(`Unknown Yarn2 error`); + expect(yarn2Proxy.parseErrorFromLogs(YARN2_ERROR_SAMPLE)).toEqual(`YARN2 error`); }); }); }); diff --git a/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts b/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts index 57a9d02eff0d..67452e6889c2 100644 --- a/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts +++ b/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts @@ -232,18 +232,26 @@ export class Yarn2Proxy extends JsPackageManager { } public parseErrorFromLogs(logs: string): string { + let finalMessage = 'YARN2 error'; const match = logs.match(YARN2_ERROR_REGEX); - let errorCode; if (match) { - errorCode = match[1] as keyof typeof YARN2_ERROR_CODES; - const errorMessage = match[2]; + const errorCode = match[1] as keyof typeof YARN2_ERROR_CODES; + if (errorCode) { + finalMessage = `${finalMessage} ${errorCode}`; + } + const errorType = YARN2_ERROR_CODES[errorCode]; - if (errorCode && errorMessage) { - return `${errorCode} - ${errorType}: ${errorMessage}`.trim(); + if (errorType) { + finalMessage = `${finalMessage} - ${errorType}`; + } + + const errorMessage = match[2]; + if (errorMessage) { + finalMessage = `${finalMessage}: ${errorMessage}`; } } - return `Unknown Yarn2 error${errorCode ? `: ${errorCode}` : ''}`; + return finalMessage.trim(); } } From 1fab54d477fc22da1e7d11df3da9c5f13db8bcaa Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 May 2023 09:16:40 +0200 Subject: [PATCH 4/7] handle ctrl c --- code/lib/cli/src/initiate.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/code/lib/cli/src/initiate.ts b/code/lib/cli/src/initiate.ts index 97b2683e0983..1182a61003b5 100644 --- a/code/lib/cli/src/initiate.ts +++ b/code/lib/cli/src/initiate.ts @@ -238,6 +238,12 @@ const projectTypeInquirer = async ( }; async function doInitiate(options: CommandOptions, pkg: PackageJson): Promise { + process.on('SIGINT', () => { + logger.log(); + logger.log('Storybook init canceled by the user.'); + process.exit(0); + }); + let { packageManager: pkgMgr } = options; if (options.useNpm) { useNpmWarning(); From b2012260029eac2ff7e7f99a5320b829653a19cd Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 May 2023 09:17:15 +0200 Subject: [PATCH 5/7] add spinners when installing dependencies --- code/lib/cli/package.json | 1 + code/lib/cli/src/generators/baseGenerator.ts | 18 +++++++++++++++--- code/yarn.lock | 1 + 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/code/lib/cli/package.json b/code/lib/cli/package.json index a319bfdb21c7..b76bf57dffeb 100644 --- a/code/lib/cli/package.json +++ b/code/lib/cli/package.json @@ -81,6 +81,7 @@ "globby": "^11.0.2", "jscodeshift": "^0.14.0", "leven": "^3.1.0", + "ora": "^5.4.1", "prettier": "^2.8.0", "prompts": "^2.4.0", "puppeteer-core": "^2.1.1", diff --git a/code/lib/cli/src/generators/baseGenerator.ts b/code/lib/cli/src/generators/baseGenerator.ts index 4e10e803e833..db8a50a54d88 100644 --- a/code/lib/cli/src/generators/baseGenerator.ts +++ b/code/lib/cli/src/generators/baseGenerator.ts @@ -1,10 +1,11 @@ import path from 'path'; import fse from 'fs-extra'; import { dedent } from 'ts-dedent'; +import ora from 'ora'; import type { NpmOptions } from '../NpmOptions'; import type { SupportedRenderers, SupportedFrameworks, Builder } from '../project_types'; import { SupportedLanguage, externalFrameworks, CoreBuilder } from '../project_types'; -import { copyTemplateFiles, paddedLog } from '../helpers'; +import { copyTemplateFiles } from '../helpers'; import { configureMain, configurePreview } from './configure'; import type { JsPackageManager } from '../js-package-manager'; import { getPackageDetails } from '../js-package-manager'; @@ -17,6 +18,8 @@ import { suggestESLintPlugin, } from '../automigrate/helpers/eslintPlugin'; +const logger = console; + const defaultOptions: FrameworkOptions = { extraPackages: [], extraAddons: [], @@ -254,8 +257,13 @@ export async function baseGenerator( (packageToInstall) => !installedDependencies.has(getPackageDetails(packageToInstall)[0]) ); - paddedLog(`\nGetting the correct version of ${packages.length} packages`); + logger.log(); + const versionedPackagesSpinner = ora({ + indent: 2, + text: `Getting the correct version of ${packages.length} packages`, + }).start(); const versionedPackages = await packageManager.getVersionedPackages(packages); + versionedPackagesSpinner.succeed(); await fse.ensureDir(`./${storybookConfigFolder}`); @@ -334,8 +342,12 @@ export async function baseGenerator( } if (depsToInstall.length > 0) { - paddedLog('Installing Storybook dependencies'); + const addDependenciesSpinner = ora({ + indent: 2, + text: 'Installing Storybook dependencies', + }).start(); await packageManager.addDependencies({ ...npmOptions, packageJson }, depsToInstall); + addDependenciesSpinner.succeed(); } if (addScripts) { diff --git a/code/yarn.lock b/code/yarn.lock index cc7d4a77c89a..a665642aab59 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5742,6 +5742,7 @@ __metadata: globby: ^11.0.2 jscodeshift: ^0.14.0 leven: ^3.1.0 + ora: ^5.4.1 prettier: ^2.8.0 prompts: ^2.4.0 puppeteer-core: ^2.1.1 From 156209cc53a671b425f361a148b2a5fca66505ab Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Tue, 16 May 2023 12:23:56 +0200 Subject: [PATCH 6/7] unify logfile name --- code/lib/cli/src/js-package-manager/NPMProxy.ts | 7 +++---- code/lib/cli/src/js-package-manager/PNPMProxy.ts | 6 ++---- code/lib/cli/src/js-package-manager/Yarn1Proxy.ts | 6 ++---- code/lib/cli/src/js-package-manager/Yarn2Proxy.ts | 6 ++---- code/lib/cli/src/utils.ts | 2 +- 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/code/lib/cli/src/js-package-manager/NPMProxy.ts b/code/lib/cli/src/js-package-manager/NPMProxy.ts index e54305d36a64..e0aa5460da12 100644 --- a/code/lib/cli/src/js-package-manager/NPMProxy.ts +++ b/code/lib/cli/src/js-package-manager/NPMProxy.ts @@ -141,9 +141,7 @@ export class NPMProxy extends JsPackageManager { } protected async runAddDeps(dependencies: string[], installAsDevDependencies: boolean) { - const { logStream, readLogFile, moveLogFile, removeLogFile } = await createLogStream( - 'init-storybook.log' - ); + const { logStream, readLogFile, moveLogFile, removeLogFile } = await createLogStream(); let args = [...dependencies]; if (installAsDevDependencies) { @@ -166,7 +164,7 @@ export class NPMProxy extends JsPackageManager { throw new Error( dedent`${errorMessage} - Please check the logfile generated at ./init-install.log for troubleshooting and try again.` + Please check the logfile generated at ./storybook.log for troubleshooting and try again.` ); } @@ -250,6 +248,7 @@ export class NPMProxy extends JsPackageManager { public parseErrorFromLogs(logs: string): string { let finalMessage = 'NPM error'; + console.log({ logs }); const match = logs.match(NPM_ERROR_REGEX); if (match) { diff --git a/code/lib/cli/src/js-package-manager/PNPMProxy.ts b/code/lib/cli/src/js-package-manager/PNPMProxy.ts index bbd0a1bc8967..345cf77cfa1d 100644 --- a/code/lib/cli/src/js-package-manager/PNPMProxy.ts +++ b/code/lib/cli/src/js-package-manager/PNPMProxy.ts @@ -130,9 +130,7 @@ export class PNPMProxy extends JsPackageManager { if (installAsDevDependencies) { args = ['-D', ...args]; } - const { logStream, readLogFile, moveLogFile, removeLogFile } = await createLogStream( - 'init-storybook.log' - ); + const { logStream, readLogFile, moveLogFile, removeLogFile } = await createLogStream(); try { await this.executeCommand({ @@ -150,7 +148,7 @@ export class PNPMProxy extends JsPackageManager { throw new Error( dedent`${errorMessage} - Please check the logfile generated at ./init-install.log for troubleshooting and try again.` + Please check the logfile generated at ./storybook.log for troubleshooting and try again.` ); } diff --git a/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts b/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts index b31372502d0b..be7103d47a36 100644 --- a/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts +++ b/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts @@ -97,9 +97,7 @@ export class Yarn1Proxy extends JsPackageManager { args = ['-D', ...args]; } - const { logStream, readLogFile, moveLogFile, removeLogFile } = await createLogStream( - 'init-storybook.log' - ); + const { logStream, readLogFile, moveLogFile, removeLogFile } = await createLogStream(); try { await this.executeCommand({ @@ -117,7 +115,7 @@ export class Yarn1Proxy extends JsPackageManager { throw new Error( dedent`${errorMessage} - Please check the logfile generated at ./init-install.log for troubleshooting and try again.` + Please check the logfile generated at ./storybook.log for troubleshooting and try again.` ); } diff --git a/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts b/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts index 67452e6889c2..f6675b57574c 100644 --- a/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts +++ b/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts @@ -141,9 +141,7 @@ export class Yarn2Proxy extends JsPackageManager { args = ['-D', ...args]; } - const { logStream, readLogFile, moveLogFile, removeLogFile } = await createLogStream( - 'init-storybook.log' - ); + const { logStream, readLogFile, moveLogFile, removeLogFile } = await createLogStream(); try { await this.executeCommand({ @@ -161,7 +159,7 @@ export class Yarn2Proxy extends JsPackageManager { throw new Error( dedent`${errorMessage} - Please check the logfile generated at ./init-install.log for troubleshooting and try again.` + Please check the logfile generated at ./storybook.log for troubleshooting and try again.` ); } diff --git a/code/lib/cli/src/utils.ts b/code/lib/cli/src/utils.ts index 0ec96e84b7a2..4ae92e61ad66 100644 --- a/code/lib/cli/src/utils.ts +++ b/code/lib/cli/src/utils.ts @@ -56,7 +56,7 @@ export function getEnvConfig(program: Record, configEnv: Record Promise; removeLogFile: () => Promise; From b64d9acc6c2b8bb8efadc9a93d245c2469f87323 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Fri, 19 May 2023 14:52:57 +0200 Subject: [PATCH 7/7] gracefully handle ctrl + c in init --- code/lib/cli/src/generators/baseGenerator.ts | 58 ++++++++++++++++---- code/lib/cli/src/initiate.ts | 10 +--- code/lib/core-server/src/withTelemetry.ts | 16 +++++- code/lib/telemetry/src/types.ts | 1 + 4 files changed, 65 insertions(+), 20 deletions(-) diff --git a/code/lib/cli/src/generators/baseGenerator.ts b/code/lib/cli/src/generators/baseGenerator.ts index db8a50a54d88..760ee4348072 100644 --- a/code/lib/cli/src/generators/baseGenerator.ts +++ b/code/lib/cli/src/generators/baseGenerator.ts @@ -17,6 +17,7 @@ import { extractEslintInfo, suggestESLintPlugin, } from '../automigrate/helpers/eslintPlugin'; +import { HandledError } from '../HandledError'; const logger = console; @@ -171,6 +172,31 @@ export async function baseGenerator( options: FrameworkOptions = defaultOptions, framework?: SupportedFrameworks ) { + // This is added so that we can handle the scenario where the user presses Ctrl+C and report a canceled event. + // Given that there are subprocesses running as part of this function, we need to handle the signal ourselves otherwise it might run into race conditions. + // TODO: This should be revisited once we have a better way to handle this. + let isNodeProcessExiting = false; + const setNodeProcessExiting = () => { + isNodeProcessExiting = true; + }; + process.on('SIGINT', setNodeProcessExiting); + + const stopIfExiting = async (callback: () => Promise) => { + if (isNodeProcessExiting) { + throw new HandledError('Canceled by the user'); + } + + try { + return await callback(); + } catch (error) { + if (isNodeProcessExiting) { + throw new HandledError('Canceled by the user'); + } + + throw error; + } + }; + const { extraAddons: extraAddonPackages, extraPackages, @@ -262,7 +288,9 @@ export async function baseGenerator( indent: 2, text: `Getting the correct version of ${packages.length} packages`, }).start(); - const versionedPackages = await packageManager.getVersionedPackages(packages); + const versionedPackages = await stopIfExiting(async () => + packageManager.getVersionedPackages(packages) + ); versionedPackagesSpinner.succeed(); await fse.ensureDir(`./${storybookConfigFolder}`); @@ -346,23 +374,31 @@ export async function baseGenerator( indent: 2, text: 'Installing Storybook dependencies', }).start(); - await packageManager.addDependencies({ ...npmOptions, packageJson }, depsToInstall); + await stopIfExiting(async () => + packageManager.addDependencies({ ...npmOptions, packageJson }, depsToInstall) + ); addDependenciesSpinner.succeed(); } if (addScripts) { - await packageManager.addStorybookCommandInScripts({ - port: 6006, - }); + await stopIfExiting(async () => + packageManager.addStorybookCommandInScripts({ + port: 6006, + }) + ); } if (addComponents) { const templateLocation = hasFrameworkTemplates(framework) ? framework : rendererId; - await copyTemplateFiles({ - renderer: templateLocation, - packageManager, - language, - destination: componentsDestinationPath, - }); + await stopIfExiting(async () => + copyTemplateFiles({ + renderer: templateLocation, + packageManager, + language, + destination: componentsDestinationPath, + }) + ); } + + process.off('SIGINT', setNodeProcessExiting); } diff --git a/code/lib/cli/src/initiate.ts b/code/lib/cli/src/initiate.ts index 1182a61003b5..b49ebc6a4d9a 100644 --- a/code/lib/cli/src/initiate.ts +++ b/code/lib/cli/src/initiate.ts @@ -199,7 +199,7 @@ const installStorybook = async ( try { return await runGenerator(); } catch (err) { - if (err?.stack) { + if (err?.message !== 'Canceled by the user' && err?.stack) { logger.error(`\n ${chalk.red(err.stack)}`); } throw new HandledError(err); @@ -238,12 +238,6 @@ const projectTypeInquirer = async ( }; async function doInitiate(options: CommandOptions, pkg: PackageJson): Promise { - process.on('SIGINT', () => { - logger.log(); - logger.log('Storybook init canceled by the user.'); - process.exit(0); - }); - let { packageManager: pkgMgr } = options; if (options.useNpm) { useNpmWarning(); @@ -292,7 +286,7 @@ async function doInitiate(options: CommandOptions, pkg: PackageJson): Promise( options: TelemetryOptions, run: () => Promise ): Promise { + // We catch Ctrl+C user interactions to be able to detect a cancel event + process.on('SIGINT', async () => { + if (!options.cliOptions.disableTelemetry) { + await telemetry('canceled', { eventType }, { stripMetadata: true, immediate: true }); + process.exit(0); + } + + process.exit(0); + }); + if (!options.cliOptions.disableTelemetry) telemetry('boot', { eventType }, { stripMetadata: true }); try { return await run(); } catch (error) { + if (error?.message === 'Canceled by the user') { + return undefined; + } + const { printError = logger.error } = options; printError(error); - await sendTelemetryError(error, eventType, options); + throw error; } } diff --git a/code/lib/telemetry/src/types.ts b/code/lib/telemetry/src/types.ts index d33b89677343..e65da2747a34 100644 --- a/code/lib/telemetry/src/types.ts +++ b/code/lib/telemetry/src/types.ts @@ -9,6 +9,7 @@ export type EventType = | 'build' | 'upgrade' | 'init' + | 'canceled' | 'error' | 'error-metadata' | 'version-update';