diff --git a/code/frameworks/angular/src/builders/utils/run-compodoc.spec.ts b/code/frameworks/angular/src/builders/utils/run-compodoc.spec.ts index 9daf6b6a86d3..ed247a6d0482 100644 --- a/code/frameworks/angular/src/builders/utils/run-compodoc.spec.ts +++ b/code/frameworks/angular/src/builders/utils/run-compodoc.spec.ts @@ -8,6 +8,16 @@ jest.doMock('child_process', () => cpSpawnMock); const { runCompodoc } = require('./run-compodoc'); +const mockRunScript = jest.fn(); + +jest.mock('@storybook/cli', () => ({ + JsPackageManagerFactory: { + getPackageManager: () => ({ + runPackageCommandSync: mockRunScript, + }), + }, +})); + const builderContextLoggerMock: LoggerApi = { createChild: jest.fn(), log: jest.fn(), diff --git a/code/frameworks/angular/src/builders/utils/run-compodoc.ts b/code/frameworks/angular/src/builders/utils/run-compodoc.ts index 6f167070789c..ebec682ba2e1 100644 --- a/code/frameworks/angular/src/builders/utils/run-compodoc.ts +++ b/code/frameworks/angular/src/builders/utils/run-compodoc.ts @@ -28,7 +28,7 @@ export const runCompodoc = ( const packageManager = JsPackageManagerFactory.getPackageManager(); try { - const stdout = packageManager.runPackageCommand( + const stdout = packageManager.runPackageCommandSync( 'compodoc', finalCompodocArgs, context.workspaceRoot diff --git a/code/frameworks/angular/template/cli/header.component.ts b/code/frameworks/angular/template/cli/header.component.ts index 603ac7cfeef5..2980d3b3202e 100644 --- a/code/frameworks/angular/template/cli/header.component.ts +++ b/code/frameworks/angular/template/cli/header.component.ts @@ -4,7 +4,7 @@ import type { User } from './User'; @Component({ selector: 'storybook-header', template: `
-
+
diff --git a/code/frameworks/angular/template/cli/page.component.ts b/code/frameworks/angular/template/cli/page.component.ts index 02cc3f06abcd..f3ae4868549e 100644 --- a/code/frameworks/angular/template/cli/page.component.ts +++ b/code/frameworks/angular/template/cli/page.component.ts @@ -10,7 +10,7 @@ import type { User } from './User'; (onLogin)="doLogin()" (onCreateAccount)="doCreateAccount()" > -
+

Pages in Storybook

We recommend building UIs with a diff --git a/code/frameworks/nextjs/template/cli/js/Header.jsx b/code/frameworks/nextjs/template/cli/js/Header.jsx index 3862422ed8ec..39e5226cffc1 100644 --- a/code/frameworks/nextjs/template/cli/js/Header.jsx +++ b/code/frameworks/nextjs/template/cli/js/Header.jsx @@ -6,7 +6,7 @@ import './header.css'; export const Header = ({ user, onLogin, onLogout, onCreateAccount }) => (

-
+
diff --git a/code/frameworks/nextjs/template/cli/js/Page.jsx b/code/frameworks/nextjs/template/cli/js/Page.jsx index c5fffe953be5..6db1e0ac3f36 100644 --- a/code/frameworks/nextjs/template/cli/js/Page.jsx +++ b/code/frameworks/nextjs/template/cli/js/Page.jsx @@ -14,8 +14,7 @@ export const Page = () => { onLogout={() => setUser(undefined)} onCreateAccount={() => setUser({ name: 'Jane Doe' })} /> - -
+

Pages in Storybook

We recommend building UIs with a{' '} diff --git a/code/frameworks/nextjs/template/cli/ts-3-8/Header.tsx b/code/frameworks/nextjs/template/cli/ts-3-8/Header.tsx index dc3f3c19c31a..01504601311d 100644 --- a/code/frameworks/nextjs/template/cli/ts-3-8/Header.tsx +++ b/code/frameworks/nextjs/template/cli/ts-3-8/Header.tsx @@ -16,7 +16,7 @@ interface HeaderProps { export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => (

-
+
diff --git a/code/frameworks/nextjs/template/cli/ts-3-8/Page.tsx b/code/frameworks/nextjs/template/cli/ts-3-8/Page.tsx index ec054e813895..e11748301390 100644 --- a/code/frameworks/nextjs/template/cli/ts-3-8/Page.tsx +++ b/code/frameworks/nextjs/template/cli/ts-3-8/Page.tsx @@ -19,7 +19,7 @@ export const Page: React.FC = () => { onCreateAccount={() => setUser({ name: 'Jane Doe' })} /> -
+

Pages in Storybook

We recommend building UIs with a{' '} diff --git a/code/frameworks/nextjs/template/cli/ts/Header.tsx b/code/frameworks/nextjs/template/cli/ts/Header.tsx index dc3f3c19c31a..01504601311d 100644 --- a/code/frameworks/nextjs/template/cli/ts/Header.tsx +++ b/code/frameworks/nextjs/template/cli/ts/Header.tsx @@ -16,7 +16,7 @@ interface HeaderProps { export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => (

-
+
diff --git a/code/frameworks/nextjs/template/cli/ts/Page.tsx b/code/frameworks/nextjs/template/cli/ts/Page.tsx index ec054e813895..e11748301390 100644 --- a/code/frameworks/nextjs/template/cli/ts/Page.tsx +++ b/code/frameworks/nextjs/template/cli/ts/Page.tsx @@ -19,7 +19,7 @@ export const Page: React.FC = () => { onCreateAccount={() => setUser({ name: 'Jane Doe' })} /> -
+

Pages in Storybook

We recommend building UIs with a{' '} diff --git a/code/lib/cli/rendererAssets/common/header.css b/code/lib/cli/rendererAssets/common/header.css index 44c549da27ce..d9a70528a3a1 100644 --- a/code/lib/cli/rendererAssets/common/header.css +++ b/code/lib/cli/rendererAssets/common/header.css @@ -1,4 +1,4 @@ -.wrapper { +.storybook-header { font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; border-bottom: 1px solid rgba(0, 0, 0, 0.1); padding: 15px 20px; @@ -7,12 +7,12 @@ justify-content: space-between; } -svg { +.storybook-header svg { display: inline-block; vertical-align: top; } -h1 { +.storybook-header h1 { font-weight: 700; font-size: 20px; line-height: 1; @@ -21,11 +21,11 @@ h1 { vertical-align: top; } -button + button { +.storybook-header button + button { margin-left: 10px; } -.welcome { +.storybook-header .welcome { color: #333; font-size: 14px; margin-right: 10px; diff --git a/code/lib/cli/rendererAssets/common/page.css b/code/lib/cli/rendererAssets/common/page.css index fb64fe462943..098dad118500 100644 --- a/code/lib/cli/rendererAssets/common/page.css +++ b/code/lib/cli/rendererAssets/common/page.css @@ -1,4 +1,4 @@ -section { +.storybook-page { font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; line-height: 24px; @@ -8,7 +8,7 @@ section { color: #333; } -section h2 { +.storybook-page h2 { font-weight: 700; font-size: 32px; line-height: 1; @@ -17,25 +17,25 @@ section h2 { vertical-align: top; } -section p { +.storybook-page p { margin: 1em 0; } -section a { +.storybook-page a { text-decoration: none; color: #1ea7fd; } -section ul { +.storybook-page ul { padding-left: 30px; margin: 1em 0; } -section li { +.storybook-page li { margin-bottom: 8px; } -section .tip { +.storybook-page .tip { display: inline-block; border-radius: 1em; font-size: 11px; @@ -48,14 +48,14 @@ section .tip { vertical-align: top; } -section .tip-wrapper { +.storybook-page .tip-wrapper { font-size: 13px; line-height: 20px; margin-top: 40px; margin-bottom: 40px; } -section .tip-wrapper svg { +.storybook-page .tip-wrapper svg { display: inline-block; height: 12px; width: 12px; @@ -64,6 +64,6 @@ section .tip-wrapper svg { margin-top: 3px; } -section .tip-wrapper svg path { +.storybook-page .tip-wrapper svg path { fill: #1ea7fd; } diff --git a/code/lib/cli/src/add.ts b/code/lib/cli/src/add.ts index df350c7e1151..efced649c957 100644 --- a/code/lib/cli/src/add.ts +++ b/code/lib/cli/src/add.ts @@ -80,7 +80,7 @@ export async function add( pkgMgr = 'npm'; } const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr }); - const packageJson = packageManager.retrievePackageJson(); + const packageJson = await packageManager.retrievePackageJson(); const [addonName, versionSpecifier] = getVersionSpecifier(addon); const { mainConfig, version: storybookVersion } = getStorybookInfo(packageJson); @@ -90,7 +90,7 @@ export async function add( } const main = await readConfig(mainConfig); logger.log(`Verifying ${addonName}`); - const latestVersion = packageManager.latestVersion(addonName); + const latestVersion = await packageManager.latestVersion(addonName); if (!latestVersion) { logger.error(`Unknown addon ${addonName}`); } @@ -100,7 +100,7 @@ export async function add( const version = versionSpecifier || (isStorybookAddon ? storybookVersion : latestVersion); const addonWithVersion = `${addonName}@${version}`; logger.log(`Installing ${addonWithVersion}`); - packageManager.addDependencies({ installAsDevDependencies: true }, [addonWithVersion]); + await packageManager.addDependencies({ installAsDevDependencies: true }, [addonWithVersion]); // add to main.js logger.log(`Adding '${addon}' to main.js addons field.`); diff --git a/code/lib/cli/src/automigrate/fixes/add-react.test.ts b/code/lib/cli/src/automigrate/fixes/add-react.test.ts index f497f4013016..42bb20b60e2e 100644 --- a/code/lib/cli/src/automigrate/fixes/add-react.test.ts +++ b/code/lib/cli/src/automigrate/fixes/add-react.test.ts @@ -3,7 +3,7 @@ import { addReact } from './add-react'; const checkAddReact = async (packageJson: PackageJson) => { const packageManager = { - retrievePackageJson: () => ({ dependencies: {}, devDependencies: {}, ...packageJson }), + retrievePackageJson: async () => ({ dependencies: {}, devDependencies: {}, ...packageJson }), } as JsPackageManager; return addReact.check({ packageManager }); }; diff --git a/code/lib/cli/src/automigrate/fixes/add-react.ts b/code/lib/cli/src/automigrate/fixes/add-react.ts index 420be48912f1..25ad88df2571 100644 --- a/code/lib/cli/src/automigrate/fixes/add-react.ts +++ b/code/lib/cli/src/automigrate/fixes/add-react.ts @@ -14,7 +14,7 @@ export const addReact: Fix = { id: 'addReact', async check({ packageManager }) { - const packageJson = packageManager.retrievePackageJson(); + const packageJson = await packageManager.retrievePackageJson(); const installedDependencies = new Set( Object.keys({ ...packageJson.dependencies, ...packageJson.devDependencies }) ); @@ -63,7 +63,10 @@ export const addReact: Fix = { async run({ packageManager, result: { additionalDependencies }, dryRun }) { if (!dryRun) { - packageManager.addDependencies({ installAsDevDependencies: true }, additionalDependencies); + await packageManager.addDependencies( + { installAsDevDependencies: true }, + additionalDependencies + ); } }, }; diff --git a/code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.ts b/code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.ts index 14d767e1ca59..2cd0a42fa987 100644 --- a/code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.ts +++ b/code/lib/cli/src/automigrate/fixes/angular-builders-multiproject.ts @@ -12,14 +12,14 @@ export const angularBuildersMultiproject: Fix = { id: 'angular-builders', async check({ packageManager, configDir }) { - const packageJSON = packageManager.retrievePackageJson(); + const packageJSON = await packageManager.retrievePackageJson(); // Skip in case of NX if (isNxProject(packageJSON)) { return null; } - const allDependencies = packageManager.getAllDependencies(); + const allDependencies = await packageManager.getAllDependencies(); const angularVersion = allDependencies['@angular/core']; const angularCoerced = semver.coerce(angularVersion)?.version; @@ -98,7 +98,7 @@ export const angularBuilders: Fix = { angularJSON.write(); - packageManager.addScripts({ + await packageManager.addScripts({ storybook: `ng run ${angularProjectName}:storybook`, 'build-storybook': `ng run ${angularProjectName}:build-storybook`, }); diff --git a/code/lib/cli/src/automigrate/fixes/angular12.ts b/code/lib/cli/src/automigrate/fixes/angular12.ts index a778556e9207..c8b98e6b0f00 100644 --- a/code/lib/cli/src/automigrate/fixes/angular12.ts +++ b/code/lib/cli/src/automigrate/fixes/angular12.ts @@ -21,7 +21,7 @@ export const angular12: Fix = { id: 'angular12', async check({ packageManager, configDir }) { - const allDependencies = packageManager.getAllDependencies(); + const allDependencies = await packageManager.getAllDependencies(); const angularVersion = allDependencies['@angular/core']; const angularCoerced = semver.coerce(angularVersion)?.version; diff --git a/code/lib/cli/src/automigrate/fixes/builder-vite.ts b/code/lib/cli/src/automigrate/fixes/builder-vite.ts index c107cc6d7d17..9fef70b8b9d6 100644 --- a/code/lib/cli/src/automigrate/fixes/builder-vite.ts +++ b/code/lib/cli/src/automigrate/fixes/builder-vite.ts @@ -27,7 +27,7 @@ export const builderVite: Fix = { id: 'builder-vite', async check({ configDir, packageManager }) { - const packageJson = packageManager.retrievePackageJson(); + const packageJson = await packageManager.retrievePackageJson(); const { mainConfig } = await getStorybookData({ configDir, packageManager }); const builder = mainConfig.core?.builder; const builderName = typeof builder === 'string' ? builder : builder?.name; @@ -64,12 +64,12 @@ export const builderVite: Fix = { if (!dryRun) { delete dependencies['storybook-builder-vite']; delete devDependencies['storybook-builder-vite']; - packageManager.writePackageJson(packageJson); + await packageManager.writePackageJson(packageJson); } logger.info(`✅ Adding '@storybook/builder-vite' as dev dependency`); if (!dryRun) { - packageManager.addDependencies({ installAsDevDependencies: true }, [ + await packageManager.addDependencies({ installAsDevDependencies: true }, [ '@storybook/builder-vite', ]); } diff --git a/code/lib/cli/src/automigrate/fixes/cra5.ts b/code/lib/cli/src/automigrate/fixes/cra5.ts index 04ca458d6871..1280a5de3155 100644 --- a/code/lib/cli/src/automigrate/fixes/cra5.ts +++ b/code/lib/cli/src/automigrate/fixes/cra5.ts @@ -21,7 +21,7 @@ export const cra5: Fix = { id: 'cra5', async check({ packageManager, configDir }) { - const allDependencies = packageManager.getAllDependencies(); + const allDependencies = await packageManager.getAllDependencies(); const craVersion = allDependencies['react-scripts']; const craCoerced = semver.coerce(craVersion)?.version; diff --git a/code/lib/cli/src/automigrate/fixes/eslint-plugin.ts b/code/lib/cli/src/automigrate/fixes/eslint-plugin.ts index c5b155ff5366..9b461e97d1a7 100644 --- a/code/lib/cli/src/automigrate/fixes/eslint-plugin.ts +++ b/code/lib/cli/src/automigrate/fixes/eslint-plugin.ts @@ -25,7 +25,7 @@ export const eslintPlugin: Fix = { id: 'eslintPlugin', async check({ packageManager }) { - const allDependencies = packageManager.getAllDependencies(); + const allDependencies = await packageManager.getAllDependencies(); const eslintPluginStorybook = allDependencies['eslint-plugin-storybook']; const eslintDependency = allDependencies.eslint; @@ -64,8 +64,9 @@ export const eslintPlugin: Fix = { const deps = [`eslint-plugin-storybook`]; logger.info(`✅ Adding dependencies: ${deps}`); - if (!dryRun) - packageManager.addDependencies({ installAsDevDependencies: true, skipInstall }, deps); + if (!dryRun) { + await packageManager.addDependencies({ installAsDevDependencies: true, skipInstall }, deps); + } if (!dryRun && unsupportedExtension) { logger.info(dedent` diff --git a/code/lib/cli/src/automigrate/fixes/mdx-gfm.ts b/code/lib/cli/src/automigrate/fixes/mdx-gfm.ts index e4f0f6b8b7cd..7989b5c1517a 100644 --- a/code/lib/cli/src/automigrate/fixes/mdx-gfm.ts +++ b/code/lib/cli/src/automigrate/fixes/mdx-gfm.ts @@ -81,8 +81,10 @@ export const mdxgfm: Fix = { async run({ packageManager, dryRun, mainConfigPath, skipInstall }) { if (!dryRun) { - const packageJson = packageManager.retrievePackageJson(); - const versionToInstall = getStorybookVersionSpecifier(packageManager.retrievePackageJson()); + const packageJson = await packageManager.retrievePackageJson(); + const versionToInstall = getStorybookVersionSpecifier( + await packageManager.retrievePackageJson() + ); await packageManager.addDependencies( { installAsDevDependencies: true, skipInstall, packageJson }, [`@storybook/addon-mdx-gfm@${versionToInstall}`] diff --git a/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts index 07f94b1f4d50..2e13685fd8b4 100644 --- a/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts +++ b/code/lib/cli/src/automigrate/fixes/missing-babelrc.ts @@ -24,7 +24,7 @@ export const missingBabelRc: Fix = { id: 'missing-babelrc', async check({ configDir, packageManager }) { - const packageJson = packageManager.retrievePackageJson(); + const packageJson = await packageManager.retrievePackageJson(); const { mainConfig, storybookVersion } = await getStorybookData({ configDir, packageManager }); if (!semver.gte(storybookVersion, '7.0.0')) { diff --git a/code/lib/cli/src/automigrate/fixes/new-frameworks.ts b/code/lib/cli/src/automigrate/fixes/new-frameworks.ts index 11c23cc5b348..d2be64bf877a 100644 --- a/code/lib/cli/src/automigrate/fixes/new-frameworks.ts +++ b/code/lib/cli/src/automigrate/fixes/new-frameworks.ts @@ -67,7 +67,7 @@ export const newFrameworks: Fix = { configDir: userDefinedConfigDir, packageManager, }) { - const packageJson = packageManager.retrievePackageJson(); + const packageJson = await packageManager.retrievePackageJson(); const { storybookVersion, mainConfig, mainConfigPath, configDir } = await getStorybookData({ packageManager, configDir: userDefinedConfigDir, @@ -108,7 +108,7 @@ export const newFrameworks: Fix = { return null; } - const allDependencies = packageManager.getAllDependencies(); + const allDependencies = await packageManager.getAllDependencies(); const builderInfo = await detectBuilderInfo({ mainConfig, @@ -448,7 +448,7 @@ export const newFrameworks: Fix = { if (dependenciesToRemove.length > 0) { logger.info(`✅ Removing dependencies: ${dependenciesToRemove.join(', ')}`); if (!dryRun) { - packageManager.removeDependencies( + await packageManager.removeDependencies( { skipInstall: skipInstall || dependenciesToAdd.length > 0, packageJson }, dependenciesToRemove ); @@ -460,7 +460,7 @@ export const newFrameworks: Fix = { if (!dryRun) { const versionToInstall = getStorybookVersionSpecifier(packageJson); const depsToAdd = dependenciesToAdd.map((dep) => `${dep}@${versionToInstall}`); - packageManager.addDependencies( + await packageManager.addDependencies( { installAsDevDependencies: true, skipInstall, packageJson }, depsToAdd ); diff --git a/code/lib/cli/src/automigrate/fixes/remove-global-client-apis.test.ts b/code/lib/cli/src/automigrate/fixes/remove-global-client-apis.test.ts index 0ae9971db053..a8fa9d050b9c 100644 --- a/code/lib/cli/src/automigrate/fixes/remove-global-client-apis.test.ts +++ b/code/lib/cli/src/automigrate/fixes/remove-global-client-apis.test.ts @@ -16,7 +16,7 @@ const check = async ({ packageJson = {}, contents }: any) => { }); } const packageManager = { - retrievePackageJson: () => ({ dependencies: {}, devDependencies: {}, ...packageJson }), + retrievePackageJson: async () => ({ dependencies: {}, devDependencies: {}, ...packageJson }), } as JsPackageManager; return migration.check({ packageManager }); }; diff --git a/code/lib/cli/src/automigrate/fixes/remove-global-client-apis.ts b/code/lib/cli/src/automigrate/fixes/remove-global-client-apis.ts index 9e725b810d73..9888b22a6be9 100644 --- a/code/lib/cli/src/automigrate/fixes/remove-global-client-apis.ts +++ b/code/lib/cli/src/automigrate/fixes/remove-global-client-apis.ts @@ -23,7 +23,7 @@ export const removedGlobalClientAPIs: Fix = { promptOnly: true, async check({ packageManager, configDir }) { - const packageJson = packageManager.retrievePackageJson(); + const packageJson = await packageManager.retrievePackageJson(); const { previewConfig } = getStorybookInfo(packageJson, configDir); diff --git a/code/lib/cli/src/automigrate/fixes/sb-binary.ts b/code/lib/cli/src/automigrate/fixes/sb-binary.ts index b97d1faa5536..22d0283e3de3 100644 --- a/code/lib/cli/src/automigrate/fixes/sb-binary.ts +++ b/code/lib/cli/src/automigrate/fixes/sb-binary.ts @@ -26,8 +26,8 @@ export const sbBinary: Fix = { id: 'storybook-binary', async check({ packageManager, configDir }) { - const packageJson = packageManager.retrievePackageJson(); - const allDependencies = packageManager.getAllDependencies(); + const packageJson = await packageManager.retrievePackageJson(); + const allDependencies = await packageManager.getAllDependencies(); const { storybookVersion } = await getStorybookData({ packageManager, configDir }); // Nx provides their own binary, so we don't need to do anything @@ -82,7 +82,7 @@ export const sbBinary: Fix = { if (hasSbBinary) { logger.info(`✅ Removing 'sb' dependency`); if (!dryRun) { - packageManager.removeDependencies( + await packageManager.removeDependencies( { skipInstall: skipInstall || !hasStorybookBinary, packageJson }, ['sb'] ); @@ -95,7 +95,7 @@ export const sbBinary: Fix = { logger.log(); if (!dryRun) { const versionToInstall = getStorybookVersionSpecifier(packageJson); - packageManager.addDependencies( + await packageManager.addDependencies( { installAsDevDependencies: true, packageJson, skipInstall }, [`storybook@${versionToInstall}`] ); diff --git a/code/lib/cli/src/automigrate/fixes/sb-scripts.ts b/code/lib/cli/src/automigrate/fixes/sb-scripts.ts index 9c84d7b75062..b624d494af5a 100644 --- a/code/lib/cli/src/automigrate/fixes/sb-scripts.ts +++ b/code/lib/cli/src/automigrate/fixes/sb-scripts.ts @@ -72,7 +72,7 @@ export const sbScripts: Fix = { id: 'sb-scripts', async check({ packageManager, configDir }) { - const packageJson = packageManager.retrievePackageJson(); + const packageJson = await packageManager.retrievePackageJson(); const { scripts = {} } = packageJson; const { storybookVersion } = await getStorybookData({ packageManager, configDir }); @@ -133,7 +133,7 @@ export const sbScripts: Fix = { logger.log(); - packageManager.addScripts(newScripts); + await packageManager.addScripts(newScripts); } }, }; diff --git a/code/lib/cli/src/automigrate/fixes/vue3.ts b/code/lib/cli/src/automigrate/fixes/vue3.ts index f1c5041e1885..0d3aaca104af 100644 --- a/code/lib/cli/src/automigrate/fixes/vue3.ts +++ b/code/lib/cli/src/automigrate/fixes/vue3.ts @@ -20,7 +20,7 @@ export const vue3: Fix = { id: 'vue3', async check({ configDir, packageManager }) { - const allDependencies = packageManager.getAllDependencies(); + const allDependencies = await packageManager.getAllDependencies(); const vueVersion = allDependencies.vue; const vueCoerced = semver.coerce(vueVersion)?.version; diff --git a/code/lib/cli/src/automigrate/fixes/webpack5.ts b/code/lib/cli/src/automigrate/fixes/webpack5.ts index edac5e468696..c60dc9f0eed1 100644 --- a/code/lib/cli/src/automigrate/fixes/webpack5.ts +++ b/code/lib/cli/src/automigrate/fixes/webpack5.ts @@ -26,7 +26,7 @@ export const webpack5: Fix = { id: 'webpack5', async check({ configDir, packageManager }) { - const allDependencies = packageManager.retrievePackageJson().dependencies; + const allDependencies = (await packageManager.retrievePackageJson()).dependencies; const webpackVersion = allDependencies.webpack; const webpackCoerced = semver.coerce(webpackVersion)?.version; @@ -72,7 +72,9 @@ export const webpack5: Fix = { deps.push('webpack@5'); } logger.info(`✅ Adding dependencies: ${deps}`); - if (!dryRun) packageManager.addDependencies({ installAsDevDependencies: true }, deps); + if (!dryRun) { + await packageManager.addDependencies({ installAsDevDependencies: true }, deps); + } logger.info('✅ Setting `core.builder` to `@storybook/builder-webpack5` in main.js'); if (!dryRun) { diff --git a/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts b/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts index c16b97afebd6..f843f57097f9 100644 --- a/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts +++ b/code/lib/cli/src/automigrate/helpers/mainConfigFile.ts @@ -16,7 +16,7 @@ export const getStorybookData = async ({ packageManager: JsPackageManager; configDir: string; }) => { - const packageJson = packageManager.retrievePackageJson(); + const packageJson = await packageManager.retrievePackageJson(); const { mainConfig: mainConfigPath, version: storybookVersionSpecifier, diff --git a/code/lib/cli/src/automigrate/helpers/testing-helpers.ts b/code/lib/cli/src/automigrate/helpers/testing-helpers.ts index 2c9cd34d6251..3651fe472caf 100644 --- a/code/lib/cli/src/automigrate/helpers/testing-helpers.ts +++ b/code/lib/cli/src/automigrate/helpers/testing-helpers.ts @@ -15,8 +15,8 @@ jest.mock('@storybook/core-common', () => ({ export const makePackageManager = (packageJson: PackageJson) => { const { dependencies = {}, devDependencies = {}, peerDependencies = {} } = packageJson; return { - retrievePackageJson: () => ({ dependencies: {}, devDependencies: {}, ...packageJson }), - getAllDependencies: () => ({ + retrievePackageJson: async () => ({ dependencies: {}, devDependencies: {}, ...packageJson }), + getAllDependencies: async () => ({ ...dependencies, ...devDependencies, ...peerDependencies, diff --git a/code/lib/cli/src/automigrate/index.ts b/code/lib/cli/src/automigrate/index.ts index e412f483fcc8..14c1865262a8 100644 --- a/code/lib/cli/src/automigrate/index.ts +++ b/code/lib/cli/src/automigrate/index.ts @@ -148,7 +148,7 @@ export async function runFixes({ configDir: inferredConfigDir, mainConfig: mainConfigPath, version: storybookVersion, - } = getStorybookInfo(packageManager.retrievePackageJson(), userSpecifiedConfigDir); + } = getStorybookInfo(await packageManager.retrievePackageJson(), userSpecifiedConfigDir); const sbVersionCoerced = storybookVersion && semver.coerce(storybookVersion)?.version; if (!sbVersionCoerced) { diff --git a/code/lib/cli/src/babel-config.ts b/code/lib/cli/src/babel-config.ts index 70bc127ae9ee..e97b51f0328d 100644 --- a/code/lib/cli/src/babel-config.ts +++ b/code/lib/cli/src/babel-config.ts @@ -90,7 +90,7 @@ export const generateStorybookBabelConfig = async ({ target }: { target: string const packageManager = JsPackageManagerFactory.getPackageManager(); - packageManager.addDependencies({ installAsDevDependencies: true }, added); + await packageManager.addDependencies({ installAsDevDependencies: true }, added); } else { logger.info( `⚠️ Please remember to install the required dependencies yourself: (${added.join(', ')})` diff --git a/code/lib/cli/src/detect-webpack.ts b/code/lib/cli/src/detect-webpack.ts index 992eb0c09968..d0a980567d29 100644 --- a/code/lib/cli/src/detect-webpack.ts +++ b/code/lib/cli/src/detect-webpack.ts @@ -1,18 +1,18 @@ import type { JsPackageManager } from './js-package-manager'; -export const detectWebpack = (packageManager: JsPackageManager): number | false => { +export const detectWebpack = async (packageManager: JsPackageManager): Promise => { try { let out = ''; if (packageManager.type === 'npm') { try { // npm <= v7 - out = packageManager.executeCommand('npm', ['ls', 'webpack']); + out = await packageManager.executeCommand({ command: 'npm', args: ['ls', 'webpack'] }); } catch (e2) { // npm >= v8 - out = packageManager.executeCommand('npm', ['why', 'webpack']); + out = await packageManager.executeCommand({ command: 'npm', args: ['why', 'webpack'] }); } } else { - out = packageManager.executeCommand('yarn', ['why', 'webpack']); + out = await packageManager.executeCommand({ command: 'yarn', args: ['why', 'webpack'] }); } // if the user has BOTH webpack 4 and 5 installed already, we'll pick the safest options (4) diff --git a/code/lib/cli/src/dirs.ts b/code/lib/cli/src/dirs.ts index e5baef79237f..6f3fa0e06864 100644 --- a/code/lib/cli/src/dirs.ts +++ b/code/lib/cli/src/dirs.ts @@ -20,7 +20,9 @@ const resolveUsingBranchInstall = async (packageManager: JsPackageManager, reque // FIXME: this might not be the right version for community packages const version = versions[name] || (await packageManager.latestVersion(request)); - const url = getNpmTarballUrl(request, version, { registry: packageManager.getRegistryURL() }); + const url = getNpmTarballUrl(request, version, { + registry: await packageManager.getRegistryURL(), + }); // this unzips the tarball into the temp directory await downloadTarball({ url, dir: tempDirectory }); diff --git a/code/lib/cli/src/generators/ANGULAR/helpers.ts b/code/lib/cli/src/generators/ANGULAR/helpers.ts index ece1881d843f..d1774e583017 100644 --- a/code/lib/cli/src/generators/ANGULAR/helpers.ts +++ b/code/lib/cli/src/generators/ANGULAR/helpers.ts @@ -2,8 +2,6 @@ import fs from 'fs'; import prompts from 'prompts'; import dedent from 'ts-dedent'; -import { commandLog } from '../../helpers'; - export const ANGULAR_JSON_PATH = 'angular.json'; export const compoDocPreviewPrefix = dedent` @@ -29,11 +27,9 @@ export class AngularJSON { constructor() { if (!fs.existsSync(ANGULAR_JSON_PATH)) { - commandLog( - 'An angular.json file was not found in the current directory. Storybook needs it to work properly.' + throw new Error( + 'An angular.json file was not found in the current working directory. Storybook needs it to work properly, so please rerun the command at the root of your project, where the angular.json file is located. More info: https://storybook.js.org/docs/angular/faq#error-no-angularjson-file-found' ); - - throw new Error('No angular.json file found'); } const jsonContent = fs.readFileSync(ANGULAR_JSON_PATH, 'utf8'); diff --git a/code/lib/cli/src/generators/ANGULAR/index.ts b/code/lib/cli/src/generators/ANGULAR/index.ts index 2740be11589a..361b93107370 100644 --- a/code/lib/cli/src/generators/ANGULAR/index.ts +++ b/code/lib/cli/src/generators/ANGULAR/index.ts @@ -14,11 +14,11 @@ const generator: Generator<{ projectName: string }> = async ( commandOptions ) => { const angularVersionFromDependencies = semver.coerce( - packageManager.retrievePackageJson().dependencies['@angular/core'] + (await packageManager.retrievePackageJson()).dependencies['@angular/core'] )?.version; const angularVersionFromDevDependencies = semver.coerce( - packageManager.retrievePackageJson().devDependencies['@angular/core'] + (await packageManager.retrievePackageJson()).devDependencies['@angular/core'] )?.version; const angularVersion = angularVersionFromDependencies || angularVersionFromDevDependencies; @@ -79,8 +79,15 @@ const generator: Generator<{ projectName: string }> = async ( }); } - const templateDir = join(getCliDir(), 'templates', 'angular', projectType || 'application'); - copyTemplate(templateDir, root || undefined); + let projectTypeValue = projectType || 'application'; + if (projectTypeValue !== 'application' && projectTypeValue !== 'library') { + projectTypeValue = 'application'; + } + + const templateDir = join(getCliDir(), 'templates', 'angular', projectTypeValue); + if (templateDir) { + copyTemplate(templateDir, root || undefined); + } return { projectName: angularProjectName, diff --git a/code/lib/cli/src/generators/AURELIA/index.ts b/code/lib/cli/src/generators/AURELIA/index.ts index bb7c66d4d0be..04101f26c857 100644 --- a/code/lib/cli/src/generators/AURELIA/index.ts +++ b/code/lib/cli/src/generators/AURELIA/index.ts @@ -25,8 +25,11 @@ const generator: Generator = async (packageManager, npmOptions, options) => { await baseGenerator(packageManager, npmOptions, options, 'aurelia', { extraPackages: ['aurelia'], }); + const templateDir = join(getCliDir(), 'templates', 'aurelia'); - copyTemplate(templateDir); + if (templateDir) { + copyTemplate(templateDir); + } }; export default generator; diff --git a/code/lib/cli/src/generators/RAX/index.ts b/code/lib/cli/src/generators/RAX/index.ts index 44243b677d06..e3a0acefc2f8 100644 --- a/code/lib/cli/src/generators/RAX/index.ts +++ b/code/lib/cli/src/generators/RAX/index.ts @@ -3,7 +3,7 @@ import type { Generator } from '../types'; const generator: Generator = async (packageManager, npmOptions, options) => { const [latestRaxVersion] = await packageManager.getVersions('rax'); - const packageJson = packageManager.retrievePackageJson(); + const packageJson = await packageManager.retrievePackageJson(); const raxVersion = packageJson.dependencies.rax || latestRaxVersion; @@ -16,7 +16,7 @@ const generator: Generator = async (packageManager, npmOptions, options) => { packageJson.dependencies['rax-text'] = packageJson.dependencies['rax-text'] || raxVersion; packageJson.dependencies['rax-view'] = packageJson.dependencies['rax-view'] || raxVersion; - packageManager.writePackageJson(packageJson); + await packageManager.writePackageJson(packageJson); await baseGenerator(packageManager, npmOptions, options, 'rax', { extraPackages: ['rax'], diff --git a/code/lib/cli/src/generators/REACT_NATIVE/index.ts b/code/lib/cli/src/generators/REACT_NATIVE/index.ts index f3cc42668ddf..dc3e14ed0f7e 100644 --- a/code/lib/cli/src/generators/REACT_NATIVE/index.ts +++ b/code/lib/cli/src/generators/REACT_NATIVE/index.ts @@ -7,7 +7,7 @@ const generator = async ( packageManager: JsPackageManager, npmOptions: NpmOptions ): Promise => { - const packageJson = packageManager.retrievePackageJson(); + const packageJson = await packageManager.retrievePackageJson(); const missingReactDom = !packageJson.dependencies['react-dom'] && !packageJson.devDependencies['react-dom']; @@ -41,7 +41,7 @@ const generator = async ( missingReactDom && reactVersion && `react-dom@${reactVersion}`, ].filter(Boolean); - packageManager.addDependencies({ ...npmOptions, packageJson }, packages); + await packageManager.addDependencies({ ...npmOptions, packageJson }, packages); packageManager.addScripts({ 'storybook-generate': 'sb-rn-get-stories', 'storybook-watch': 'sb-rn-watcher', diff --git a/code/lib/cli/src/generators/REACT_SCRIPTS/index.ts b/code/lib/cli/src/generators/REACT_SCRIPTS/index.ts index c8001d54f2a4..1871a13faedb 100644 --- a/code/lib/cli/src/generators/REACT_SCRIPTS/index.ts +++ b/code/lib/cli/src/generators/REACT_SCRIPTS/index.ts @@ -25,9 +25,8 @@ const generator: Generator = async (packageManager, npmOptions, options) => { } : {}; - const craVersion = semver.coerce( - packageManager.retrievePackageJson().dependencies['react-scripts'] - )?.version; + const packageJson = await packageManager.retrievePackageJson(); + const craVersion = semver.coerce(packageJson.dependencies['react-scripts'])?.version; const isCra5OrHigher = craVersion && semver.gte(craVersion, '5.0.0'); const updatedOptions = isCra5OrHigher ? { ...options, builder: CoreBuilder.Webpack5 } : options; diff --git a/code/lib/cli/src/generators/SERVER/index.ts b/code/lib/cli/src/generators/SERVER/index.ts index 8653c25dba25..92bda9526d18 100755 --- a/code/lib/cli/src/generators/SERVER/index.ts +++ b/code/lib/cli/src/generators/SERVER/index.ts @@ -1,16 +1,10 @@ -import { join } from 'path'; import { baseGenerator } from '../baseGenerator'; import type { Generator } from '../types'; -import { copyTemplate } from '../../helpers'; -import { getCliDir } from '../../dirs'; const generator: Generator = async (packageManager, npmOptions, options) => { await baseGenerator(packageManager, npmOptions, options, 'server', { extensions: ['json'], }); - - const templateDir = join(getCliDir(), 'templates', 'server'); - copyTemplate(templateDir); }; export default generator; diff --git a/code/lib/cli/src/generators/baseGenerator.ts b/code/lib/cli/src/generators/baseGenerator.ts index 65f732b97f20..9a44785b7146 100644 --- a/code/lib/cli/src/generators/baseGenerator.ts +++ b/code/lib/cli/src/generators/baseGenerator.ts @@ -197,7 +197,7 @@ export async function baseGenerator( const files = await fse.readdir(process.cwd()); - const packageJson = packageManager.retrievePackageJson(); + const packageJson = await packageManager.retrievePackageJson(); const installedDependencies = new Set( Object.keys({ ...packageJson.dependencies, ...packageJson.devDependencies }) ); @@ -274,17 +274,17 @@ export async function baseGenerator( const depsToInstall = [...versionedPackages, ...babelDependencies]; if (depsToInstall.length > 0) { - packageManager.addDependencies({ ...npmOptions, packageJson }, depsToInstall); + await packageManager.addDependencies({ ...npmOptions, packageJson }, depsToInstall); } if (addScripts) { - packageManager.addStorybookCommandInScripts({ + await packageManager.addStorybookCommandInScripts({ port: 6006, }); } if (addESLint) { - packageManager.addESLintConfig(); + await packageManager.addESLintConfig(); } if (addComponents) { diff --git a/code/lib/cli/src/helpers.test.ts b/code/lib/cli/src/helpers.test.ts index db242f85f52b..4f5c3d37afee 100644 --- a/code/lib/cli/src/helpers.test.ts +++ b/code/lib/cli/src/helpers.test.ts @@ -33,7 +33,7 @@ jest.mock('path', () => { }); const packageManagerMock = { - retrievePackageJson: () => ({ dependencies: {}, devDependencies: {} }), + retrievePackageJson: async () => ({ dependencies: {}, devDependencies: {} }), } as JsPackageManager; describe('Helpers', () => { diff --git a/code/lib/cli/src/initiate.ts b/code/lib/cli/src/initiate.ts index bd7b5712ac93..1d02074d40fa 100644 --- a/code/lib/cli/src/initiate.ts +++ b/code/lib/cli/src/initiate.ts @@ -41,7 +41,7 @@ import { HandledError } from './HandledError'; const logger = console; -const installStorybook = ( +const installStorybook = async ( projectType: Project, packageManager: JsPackageManager, options: CommandOptions @@ -53,7 +53,7 @@ const installStorybook = ( let packageJson; try { - packageJson = packageManager.readPackageJson(); + packageJson = await packageManager.readPackageJson(); } catch (err) { // } @@ -228,7 +228,7 @@ const installStorybook = ( }; try { - return runGenerator(); + return await runGenerator(); } catch (err) { logger.error(`\n ${chalk.red(err.stack)}`); throw new HandledError(err); @@ -291,7 +291,7 @@ async function doInitiate(options: CommandOptions, pkg: PackageJson): Promise; public abstract getRunStorybookCommand(): string; public abstract getRunCommand(command: string): string; + public readonly cwd?: string; + // NOTE: for some reason yarn prefers the npm registry in // local development, so always use npm - setRegistryURL(url: string) { + async setRegistryURL(url: string) { if (url) { - this.executeCommand('npm', ['config', 'set', 'registry', url]); + await this.executeCommand({ command: 'npm', args: ['config', 'set', 'registry', url] }); } else { - this.executeCommand('npm', ['config', 'delete', 'registry']); + await this.executeCommand({ command: 'npm', args: ['config', 'delete', 'registry'] }); } } - getRegistryURL() { - const url = this.executeCommand('npm', ['config', 'get', 'registry']).trim(); + async getRegistryURL() { + const res = await this.executeCommand({ command: 'npm', args: ['config', 'get', 'registry'] }); + const url = res.trim(); return url === 'undefined' ? undefined : url; } - public readonly cwd?: string; - constructor(options?: JsPackageManagerOptions) { this.cwd = options?.cwd; } @@ -68,7 +72,7 @@ export abstract class JsPackageManager { /** * Install dependencies listed in `package.json` */ - public installDependencies(): void { + public async installDependencies() { let done = commandLog('Preparing to install dependencies'); done(); logger.log(); @@ -77,7 +81,7 @@ export abstract class JsPackageManager { done = commandLog('Installing dependencies'); try { - this.runInstall(); + await this.runInstall(); } catch (e) { done('An error occurred while installing dependencies.'); throw new HandledError(e); @@ -89,17 +93,17 @@ export abstract class JsPackageManager { return this.cwd ? path.resolve(this.cwd, 'package.json') : path.resolve('package.json'); } - readPackageJson(): PackageJson { + async readPackageJson(): Promise { const packageJsonPath = this.packageJsonPath(); if (!fs.existsSync(packageJsonPath)) { throw new Error(`Could not read package.json file at ${packageJsonPath}`); } - const jsonContent = fs.readFileSync(packageJsonPath, 'utf8'); + const jsonContent = await readFile(packageJsonPath, 'utf8'); return JSON.parse(jsonContent); } - writePackageJson(packageJson: PackageJson) { + async writePackageJson(packageJson: PackageJson) { const packageJsonToWrite = { ...packageJson }; // make sure to not accidentally add empty fields if ( @@ -122,20 +126,31 @@ export abstract class JsPackageManager { } const content = `${JSON.stringify(packageJsonToWrite, null, 2)}\n`; - fs.writeFileSync(this.packageJsonPath(), content, 'utf8'); + await writeFile(this.packageJsonPath(), content, 'utf8'); } /** * Read the `package.json` file available in the directory the command was call from * If there is no `package.json` it will create one. */ - public retrievePackageJson(): PackageJsonWithDepsAndDevDeps { + public async retrievePackageJson(): Promise { let packageJson; try { - packageJson = this.readPackageJson(); + packageJson = await this.readPackageJson(); } catch (err) { - this.initPackageJson(); - packageJson = this.readPackageJson(); + if (err.message.includes('Could not read package.json')) { + await this.initPackageJson(); + packageJson = await this.readPackageJson(); + } else { + throw new Error( + dedent` + There was an error while reading the package.json file at ${this.packageJsonPath()}: ${ + err.message + } + Please fix the error and try again. + ` + ); + } } return { @@ -146,8 +161,8 @@ export abstract class JsPackageManager { }; } - public getAllDependencies(): Record { - const { dependencies, devDependencies, peerDependencies } = this.retrievePackageJson(); + public async getAllDependencies(): Promise> { + const { dependencies, devDependencies, peerDependencies } = await this.retrievePackageJson(); return { ...dependencies, @@ -169,14 +184,14 @@ export abstract class JsPackageManager { * `@storybook/preview-api@${addonsVersion}`, * ]); */ - public addDependencies( + public async addDependencies( options: { skipInstall?: boolean; installAsDevDependencies?: boolean; packageJson?: PackageJson; }, dependencies: string[] - ): void { + ) { const { skipInstall } = options; if (skipInstall) { @@ -198,10 +213,10 @@ export abstract class JsPackageManager { ...dependenciesMap, }; } - this.writePackageJson(packageJson); + await this.writePackageJson(packageJson); } else { try { - this.runAddDeps(dependencies, options.installAsDevDependencies); + await this.runAddDeps(dependencies, options.installAsDevDependencies); } catch (e) { logger.error('An error occurred while installing dependencies.'); logger.log(e.message); @@ -337,22 +352,22 @@ export abstract class JsPackageManager { return versions.reverse().find((version) => satisfies(version, constraint)); } - public addStorybookCommandInScripts(options?: { port: number; preCommand?: string }) { + public async addStorybookCommandInScripts(options?: { port: number; preCommand?: string }) { const sbPort = options?.port ?? 6006; const storybookCmd = `storybook dev -p ${sbPort}`; const buildStorybookCmd = `storybook build`; const preCommand = options?.preCommand ? this.getRunCommand(options.preCommand) : undefined; - this.addScripts({ + await this.addScripts({ storybook: [preCommand, storybookCmd].filter(Boolean).join(' && '), 'build-storybook': [preCommand, buildStorybookCmd].filter(Boolean).join(' && '), }); } - public addESLintConfig() { - const packageJson = this.retrievePackageJson(); - this.writePackageJson({ + public async addESLintConfig() { + const packageJson = await this.retrievePackageJson(); + await this.writePackageJson({ ...packageJson, eslintConfig: { ...packageJson.eslintConfig, @@ -369,9 +384,9 @@ export abstract class JsPackageManager { }); } - public addScripts(scripts: Record) { - const packageJson = this.retrievePackageJson(); - this.writePackageJson({ + public async addScripts(scripts: Record) { + const packageJson = await this.retrievePackageJson(); + await this.writePackageJson({ ...packageJson, scripts: { ...packageJson.scripts, @@ -380,17 +395,20 @@ export abstract class JsPackageManager { }); } - public addPackageResolutions(versions: Record) { - const packageJson = this.retrievePackageJson(); + public async addPackageResolutions(versions: Record) { + const packageJson = await this.retrievePackageJson(); const resolutions = this.getResolutions(packageJson, versions); this.writePackageJson({ ...packageJson, ...resolutions }); } - protected abstract runInstall(): void; + protected abstract runInstall(): Promise; - protected abstract runAddDeps(dependencies: string[], installAsDevDependencies: boolean): void; + protected abstract runAddDeps( + dependencies: string[], + installAsDevDependencies: boolean + ): Promise; - protected abstract runRemoveDeps(dependencies: string[]): void; + protected abstract runRemoveDeps(dependencies: string[]): Promise; protected abstract getResolutions( packageJson: PackageJson, @@ -409,27 +427,73 @@ export abstract class JsPackageManager { ): // Use generic and conditional type to force `string[]` if fetchAllVersions is true and `string` if false Promise; - public abstract runPackageCommand(command: string, args: string[], cwd?: string): string; - public abstract findInstallations(pattern?: string[]): InstallationMetadata | undefined; - - public executeCommand( - command: string, - args: string[], - stdio?: 'pipe' | 'inherit', - cwd?: string, - ignoreError?: boolean - ): string { - const commandResult = spawnSync(command, args, { - cwd: cwd ?? this.cwd, - stdio: stdio ?? 'pipe', - encoding: 'utf-8', - shell: true, - }); + public abstract runPackageCommand(command: string, args: string[], cwd?: string): Promise; + public abstract runPackageCommandSync(command: string, args: string[], cwd?: string): string; + public abstract findInstallations(pattern?: string[]): Promise; + + public executeCommandSync({ + command, + args = [], + stdio, + cwd, + ignoreError = false, + env, + ...execaOptions + }: CommonOptions & { + command: string; + args: string[]; + cwd?: string; + ignoreError?: boolean; + }): string { + try { + const commandResult = execaCommandSync(command, args, { + cwd: cwd ?? this.cwd, + stdio: stdio ?? 'pipe', + encoding: 'utf-8', + shell: true, + env, + ...execaOptions, + }); - if (commandResult.status !== 0 && ignoreError !== true) { - throw new Error(commandResult.stderr ?? ''); + return commandResult.stdout ?? ''; + } catch (err) { + if (ignoreError !== true) { + throw err; + } + return ''; } + } + + public async executeCommand({ + command, + args = [], + stdio, + cwd, + ignoreError = false, + env, + ...execaOptions + }: CommonOptions & { + command: string; + args: string[]; + cwd?: string; + ignoreError?: boolean; + }): Promise { + try { + const commandResult = await execaCommand([command, ...args].join(' '), { + cwd: cwd ?? this.cwd, + stdio: stdio ?? 'pipe', + encoding: 'utf-8', + shell: true, + env, + ...execaOptions, + }); - return commandResult.stdout ?? ''; + return commandResult.stdout ?? ''; + } catch (err) { + if (ignoreError !== true) { + throw err; + } + return ''; + } } } 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 aa40df1a80cd..c0c8cb63be52 100644 --- a/code/lib/cli/src/js-package-manager/NPMProxy.test.ts +++ b/code/lib/cli/src/js-package-manager/NPMProxy.test.ts @@ -12,77 +12,91 @@ describe('NPM Proxy', () => { }); describe('initPackageJson', () => { - it('should run `npm init -y`', () => { - const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue(''); + it('should run `npm init -y`', async () => { + const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockResolvedValueOnce(''); - npmProxy.initPackageJson(); + await npmProxy.initPackageJson(); - expect(executeCommandSpy).toHaveBeenCalledWith('npm', ['init', '-y']); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ command: 'npm', args: ['init', '-y'] }) + ); }); }); describe('setRegistryUrl', () => { - it('should run `npm config set registry https://foo.bar`', () => { - const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue(''); + it('should run `npm config set registry https://foo.bar`', async () => { + const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockResolvedValueOnce(''); - npmProxy.setRegistryURL('https://foo.bar'); + await npmProxy.setRegistryURL('https://foo.bar'); - expect(executeCommandSpy).toHaveBeenCalledWith('npm', [ - 'config', - 'set', - 'registry', - 'https://foo.bar', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'npm', + args: ['config', 'set', 'registry', 'https://foo.bar'], + }) + ); }); }); describe('installDependencies', () => { describe('npm6', () => { - it('should run `npm install`', () => { - const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('6.0.0'); + it('should run `npm install`', async () => { + const executeCommandSpy = jest + .spyOn(npmProxy, 'executeCommand') + .mockResolvedValueOnce('6.0.0'); - npmProxy.installDependencies(); + await npmProxy.installDependencies(); - expect(executeCommandSpy).toHaveBeenLastCalledWith('npm', ['install'], expect.any(String)); + expect(executeCommandSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ command: 'npm', args: ['install'] }) + ); }); }); describe('npm7', () => { - it('should run `npm install`', () => { - const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('7.1.0'); + it('should run `npm install`', async () => { + const executeCommandSpy = jest + .spyOn(npmProxy, 'executeCommand') + .mockResolvedValueOnce('7.1.0'); - npmProxy.installDependencies(); + await npmProxy.installDependencies(); - expect(executeCommandSpy).toHaveBeenLastCalledWith('npm', ['install'], expect.any(String)); + expect(executeCommandSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ command: 'npm', args: ['install'] }) + ); }); }); }); describe('runScript', () => { describe('npm6', () => { - it('should execute script `npm exec -- compodoc -e json -d .`', () => { - const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('6.0.0'); + it('should execute script `npm exec -- compodoc -e json -d .`', async () => { + const executeCommandSpy = jest + .spyOn(npmProxy, 'executeCommand') + .mockResolvedValueOnce('6.0.0'); npmProxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']); expect(executeCommandSpy).toHaveBeenLastCalledWith( - 'npm', - ['exec', '--', 'compodoc', '-e', 'json', '-d', '.'], - undefined, - undefined + expect.objectContaining({ + command: 'npm', + args: ['exec', '--', 'compodoc', '-e', 'json', '-d', '.'], + }) ); }); }); describe('npm7', () => { - it('should execute script `npm run compodoc -- -e json -d .`', () => { - const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('7.1.0'); + it('should execute script `npm run compodoc -- -e json -d .`', async () => { + const executeCommandSpy = jest + .spyOn(npmProxy, 'executeCommand') + .mockResolvedValueOnce('7.1.0'); - npmProxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']); + await npmProxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']); expect(executeCommandSpy).toHaveBeenLastCalledWith( - 'npm', - ['exec', '--', 'compodoc', '-e', 'json', '-d', '.'], - undefined, - undefined + expect.objectContaining({ + command: 'npm', + args: ['exec', '--', 'compodoc', '-e', 'json', '-d', '.'], + }) ); }); }); @@ -90,28 +104,38 @@ describe('NPM Proxy', () => { describe('addDependencies', () => { describe('npm6', () => { - it('with devDep it should run `npm install -D @storybook/preview-api`', () => { - const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('6.0.0'); + it('with devDep it should run `npm install -D @storybook/preview-api`', async () => { + const executeCommandSpy = jest + .spyOn(npmProxy, 'executeCommand') + .mockResolvedValueOnce('6.0.0'); - npmProxy.addDependencies({ installAsDevDependencies: true }, ['@storybook/preview-api']); + await npmProxy.addDependencies({ installAsDevDependencies: true }, [ + '@storybook/preview-api', + ]); expect(executeCommandSpy).toHaveBeenLastCalledWith( - 'npm', - ['install', '-D', '@storybook/preview-api'], - expect.any(String) + expect.objectContaining({ + command: 'npm', + args: ['install', '-D', '@storybook/preview-api'], + }) ); }); }); describe('npm7', () => { - it('with devDep it should run `npm install -D @storybook/preview-api`', () => { - const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('7.0.0'); + it('with devDep it should run `npm install -D @storybook/preview-api`', async () => { + const executeCommandSpy = jest + .spyOn(npmProxy, 'executeCommand') + .mockResolvedValueOnce('7.0.0'); - npmProxy.addDependencies({ installAsDevDependencies: true }, ['@storybook/preview-api']); + await npmProxy.addDependencies({ installAsDevDependencies: true }, [ + '@storybook/preview-api', + ]); expect(executeCommandSpy).toHaveBeenLastCalledWith( - 'npm', - ['install', '-D', '@storybook/preview-api'], - expect.any(String) + expect.objectContaining({ + command: 'npm', + args: ['install', '-D', '@storybook/preview-api'], + }) ); }); }); @@ -119,39 +143,41 @@ describe('NPM Proxy', () => { describe('removeDependencies', () => { describe('npm6', () => { - it('with devDep it should run `npm uninstall @storybook/preview-api`', () => { - const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('6.0.0'); + it('with devDep it should run `npm uninstall @storybook/preview-api`', async () => { + const executeCommandSpy = jest + .spyOn(npmProxy, 'executeCommand') + .mockResolvedValueOnce('6.0.0'); npmProxy.removeDependencies({}, ['@storybook/preview-api']); expect(executeCommandSpy).toHaveBeenLastCalledWith( - 'npm', - ['uninstall', '@storybook/preview-api'], - expect.any(String) + expect.objectContaining({ command: 'npm', args: ['uninstall', '@storybook/preview-api'] }) ); }); }); describe('npm7', () => { - it('with devDep it should run `npm uninstall @storybook/preview-api`', () => { - const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('7.0.0'); + it('with devDep it should run `npm uninstall @storybook/preview-api`', async () => { + const executeCommandSpy = jest + .spyOn(npmProxy, 'executeCommand') + .mockResolvedValueOnce('7.0.0'); - npmProxy.removeDependencies({}, ['@storybook/preview-api']); + await npmProxy.removeDependencies({}, ['@storybook/preview-api']); expect(executeCommandSpy).toHaveBeenLastCalledWith( - 'npm', - ['uninstall', '@storybook/preview-api'], - expect.any(String) + expect.objectContaining({ command: 'npm', args: ['uninstall', '@storybook/preview-api'] }) ); }); }); describe('skipInstall', () => { - it('should only change package.json without running install', () => { - const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('7.0.0'); + it('should only change package.json without running install', async () => { + const executeCommandSpy = jest + .spyOn(npmProxy, 'executeCommand') + .mockResolvedValueOnce('7.0.0'); const writePackageSpy = jest .spyOn(npmProxy, 'writePackageJson') - .mockImplementation(jest.fn); + .mockImplementation(jest.fn()); - npmProxy.removeDependencies( + await npmProxy.removeDependencies( { skipInstall: true, packageJson: { @@ -176,37 +202,39 @@ describe('NPM Proxy', () => { describe('latestVersion', () => { it('without constraint it returns the latest version', async () => { - const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('"5.3.19"'); + const executeCommandSpy = jest + .spyOn(npmProxy, 'executeCommand') + .mockResolvedValueOnce('"5.3.19"'); const version = await npmProxy.latestVersion('@storybook/preview-api'); - expect(executeCommandSpy).toHaveBeenCalledWith('npm', [ - 'info', - '@storybook/preview-api', - 'version', - '--json', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'npm', + args: ['info', '@storybook/preview-api', 'version', '--json'], + }) + ); expect(version).toEqual('5.3.19'); }); it('with constraint it returns the latest version satisfying the constraint', async () => { const executeCommandSpy = jest .spyOn(npmProxy, 'executeCommand') - .mockReturnValue('["4.25.3","5.3.19","6.0.0-beta.23"]'); + .mockResolvedValueOnce('["4.25.3","5.3.19","6.0.0-beta.23"]'); const version = await npmProxy.latestVersion('@storybook/preview-api', '5.X'); - expect(executeCommandSpy).toHaveBeenCalledWith('npm', [ - 'info', - '@storybook/preview-api', - 'versions', - '--json', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'npm', + args: ['info', '@storybook/preview-api', 'versions', '--json'], + }) + ); expect(version).toEqual('5.3.19'); }); it('throws an error if command output is not a valid JSON', async () => { - jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('NOT A JSON'); + jest.spyOn(npmProxy, 'executeCommand').mockResolvedValueOnce('NOT A JSON'); await expect(npmProxy.latestVersion('@storybook/preview-api')).rejects.toThrow(); }); @@ -216,16 +244,18 @@ describe('NPM Proxy', () => { it('with a Storybook package listed in versions.json it returns the version', async () => { // eslint-disable-next-line global-require const storybookAngularVersion = require('../versions').default['@storybook/angular']; - const executeCommandSpy = jest.spyOn(npmProxy, 'executeCommand').mockReturnValue('"5.3.19"'); + const executeCommandSpy = jest + .spyOn(npmProxy, 'executeCommand') + .mockResolvedValueOnce('"5.3.19"'); const version = await npmProxy.getVersion('@storybook/angular'); - expect(executeCommandSpy).toHaveBeenCalledWith('npm', [ - 'info', - '@storybook/angular', - 'version', - '--json', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'npm', + args: ['info', '@storybook/angular', 'version', '--json'], + }) + ); expect(version).toEqual(`^${storybookAngularVersion}`); }); @@ -233,26 +263,28 @@ describe('NPM Proxy', () => { const packageVersion = '5.3.19'; const executeCommandSpy = jest .spyOn(npmProxy, 'executeCommand') - .mockReturnValue(`"${packageVersion}"`); + .mockResolvedValueOnce(`"${packageVersion}"`); const version = await npmProxy.getVersion('@storybook/react-native'); - expect(executeCommandSpy).toHaveBeenCalledWith('npm', [ - 'info', - '@storybook/react-native', - 'version', - '--json', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'npm', + args: ['info', '@storybook/react-native', 'version', '--json'], + }) + ); expect(version).toEqual(`^${packageVersion}`); }); }); describe('addPackageResolutions', () => { - it('adds resolutions to package.json and account for existing resolutions', () => { - const writePackageSpy = jest.spyOn(npmProxy, 'writePackageJson').mockImplementation(jest.fn); + it('adds resolutions to package.json and account for existing resolutions', async () => { + const writePackageSpy = jest + .spyOn(npmProxy, 'writePackageJson') + .mockImplementation(jest.fn()); jest.spyOn(npmProxy, 'retrievePackageJson').mockImplementation( - jest.fn(() => ({ + jest.fn(async () => ({ dependencies: {}, devDependencies: {}, overrides: { @@ -264,7 +296,7 @@ describe('NPM Proxy', () => { const versions = { foo: 'x.x.x', }; - npmProxy.addPackageResolutions(versions); + await npmProxy.addPackageResolutions(versions); expect(writePackageSpy).toHaveBeenCalledWith({ dependencies: {}, @@ -280,7 +312,7 @@ describe('NPM Proxy', () => { describe('mapDependencies', () => { it('should display duplicated dependencies based on npm output', async () => { // npm ls --depth 10 --json - jest.spyOn(npmProxy, 'executeCommand').mockReturnValue(` + jest.spyOn(npmProxy, 'executeCommand').mockResolvedValueOnce(` { "dependencies": { "unrelated-and-should-be-filtered": { diff --git a/code/lib/cli/src/js-package-manager/NPMProxy.ts b/code/lib/cli/src/js-package-manager/NPMProxy.ts index f8f7eb9f231f..090bc93fed16 100644 --- a/code/lib/cli/src/js-package-manager/NPMProxy.ts +++ b/code/lib/cli/src/js-package-manager/NPMProxy.ts @@ -24,8 +24,8 @@ export class NPMProxy extends JsPackageManager { installArgs: string[] | undefined; - initPackageJson() { - return this.executeCommand('npm', ['init', '-y']); + async initPackageJson() { + await this.executeCommand({ command: 'npm', args: ['init', '-y'] }); } getRunStorybookCommand(): string { @@ -36,8 +36,8 @@ export class NPMProxy extends JsPackageManager { return `npm run ${command}`; } - getNpmVersion(): string { - return this.executeCommand('npm', ['--version']); + async getNpmVersion(): Promise { + return this.executeCommand({ command: 'npm', args: ['--version'] }); } getInstallArgs(): string[] { @@ -47,20 +47,30 @@ export class NPMProxy extends JsPackageManager { return this.installArgs; } - public runPackageCommand(command: string, args: string[], cwd?: string): string { - return this.executeCommand('npm', ['exec', '--', command, ...args], undefined, cwd); + public runPackageCommandSync(command: string, args: string[], cwd?: string): string { + return this.executeCommandSync({ + command: 'npm', + args: ['exec', '--', command, ...args], + cwd, + }); } - public findInstallations() { + public async runPackageCommand(command: string, args: string[], cwd?: string): Promise { + return this.executeCommand({ + command: 'npm', + args: ['exec', '--', command, ...args], + cwd, + }); + } + + public async findInstallations() { const pipeToNull = platform() === 'win32' ? '2>NUL' : '2>/dev/null'; - const commandResult = this.executeCommand( - 'npm', - ['ls', '--json', '--depth=99', pipeToNull], - undefined, - undefined, + const commandResult = await this.executeCommand({ + command: 'npm', + args: ['ls', '--json', '--depth=99', pipeToNull], // ignore errors, because npm ls will exit with code 1 if there are e.g. unmet peer dependencies - true - ); + ignoreError: true, + }); try { const parsedOutput = JSON.parse(commandResult); @@ -79,33 +89,48 @@ export class NPMProxy extends JsPackageManager { }; } - protected runInstall(): void { - this.executeCommand('npm', ['install', ...this.getInstallArgs()], 'inherit'); + protected async runInstall() { + await this.executeCommand({ + command: 'npm', + args: ['install', ...this.getInstallArgs()], + stdio: 'inherit', + }); } - protected runAddDeps(dependencies: string[], installAsDevDependencies: boolean): void { + protected async runAddDeps(dependencies: string[], installAsDevDependencies: boolean) { let args = [...dependencies]; if (installAsDevDependencies) { args = ['-D', ...args]; } - this.executeCommand('npm', ['install', ...this.getInstallArgs(), ...args], 'inherit'); + await this.executeCommand({ + command: 'npm', + args: ['install', ...this.getInstallArgs(), ...args], + stdio: 'inherit', + }); } - protected runRemoveDeps(dependencies: string[]): void { + protected async runRemoveDeps(dependencies: string[]) { const args = [...dependencies]; - this.executeCommand('npm', ['uninstall', ...this.getInstallArgs(), ...args], 'inherit'); + await this.executeCommand({ + command: 'npm', + args: ['uninstall', ...this.getInstallArgs(), ...args], + stdio: 'inherit', + }); } - protected runGetVersions( + protected async runGetVersions( packageName: string, fetchAllVersions: T ): Promise { const args = [fetchAllVersions ? 'versions' : 'version', '--json']; - const commandResult = this.executeCommand('npm', ['info', packageName, ...args]); + const commandResult = await this.executeCommand({ + command: 'npm', + args: ['info', packageName, ...args], + }); try { const parsedOutput = JSON.parse(commandResult); 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 84edf7fd98ab..eb82f1a06465 100644 --- a/code/lib/cli/src/js-package-manager/PNPMProxy.test.ts +++ b/code/lib/cli/src/js-package-manager/PNPMProxy.test.ts @@ -12,90 +12,102 @@ describe('NPM Proxy', () => { }); describe('initPackageJson', () => { - it('should run `npm init -y`', () => { - const executeCommandSpy = jest.spyOn(pnpmProxy, 'executeCommand').mockReturnValue(''); + it('should run `npm init -y`', async () => { + const executeCommandSpy = jest.spyOn(pnpmProxy, 'executeCommand').mockResolvedValueOnce(''); - pnpmProxy.initPackageJson(); + await pnpmProxy.initPackageJson(); - expect(executeCommandSpy).toHaveBeenCalledWith('pnpm', ['init', '-y']); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ command: 'pnpm', args: ['init', '-y'] }) + ); }); }); describe('setRegistryUrl', () => { - it('should run `npm config set registry https://foo.bar`', () => { - const executeCommandSpy = jest.spyOn(pnpmProxy, 'executeCommand').mockReturnValue(''); + it('should run `npm config set registry https://foo.bar`', async () => { + const executeCommandSpy = jest.spyOn(pnpmProxy, 'executeCommand').mockResolvedValueOnce(''); - pnpmProxy.setRegistryURL('https://foo.bar'); + await pnpmProxy.setRegistryURL('https://foo.bar'); - expect(executeCommandSpy).toHaveBeenCalledWith('npm', [ - 'config', - 'set', - 'registry', - 'https://foo.bar', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'npm', + args: ['config', 'set', 'registry', 'https://foo.bar'], + }) + ); }); }); describe('installDependencies', () => { - it('should run `pnpm install`', () => { - const executeCommandSpy = jest.spyOn(pnpmProxy, 'executeCommand').mockReturnValue('7.1.0'); + it('should run `pnpm install`', async () => { + const executeCommandSpy = jest + .spyOn(pnpmProxy, 'executeCommand') + .mockResolvedValueOnce('7.1.0'); - pnpmProxy.installDependencies(); + await pnpmProxy.installDependencies(); - expect(executeCommandSpy).toHaveBeenLastCalledWith('pnpm', ['install'], expect.any(String)); + expect(executeCommandSpy).toHaveBeenLastCalledWith( + expect.objectContaining({ command: 'pnpm', args: ['install'] }) + ); }); }); describe('runScript', () => { - it('should execute script `yarn compodoc -- -e json -d .`', () => { - const executeCommandSpy = jest.spyOn(pnpmProxy, 'executeCommand').mockReturnValue('7.1.0'); + it('should execute script `yarn compodoc -- -e json -d .`', async () => { + const executeCommandSpy = jest + .spyOn(pnpmProxy, 'executeCommand') + .mockResolvedValueOnce('7.1.0'); - pnpmProxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']); + await pnpmProxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']); expect(executeCommandSpy).toHaveBeenLastCalledWith( - 'pnpm', - ['exec', 'compodoc', '-e', 'json', '-d', '.'], - undefined, - undefined + expect.objectContaining({ + command: 'pnpm', + args: ['exec', 'compodoc', '-e', 'json', '-d', '.'], + }) ); }); }); describe('addDependencies', () => { - it('with devDep it should run `pnpm add -D @storybook/preview-api`', () => { - const executeCommandSpy = jest.spyOn(pnpmProxy, 'executeCommand').mockReturnValue('6.0.0'); + it('with devDep it should run `pnpm add -D @storybook/preview-api`', async () => { + const executeCommandSpy = jest + .spyOn(pnpmProxy, 'executeCommand') + .mockResolvedValueOnce('6.0.0'); - pnpmProxy.addDependencies({ installAsDevDependencies: true }, ['@storybook/preview-api']); + await pnpmProxy.addDependencies({ installAsDevDependencies: true }, [ + '@storybook/preview-api', + ]); expect(executeCommandSpy).toHaveBeenLastCalledWith( - 'pnpm', - ['add', '-D', '@storybook/preview-api'], - expect.any(String) + expect.objectContaining({ command: 'pnpm', args: ['add', '-D', '@storybook/preview-api'] }) ); }); }); describe('removeDependencies', () => { - it('with devDep it should run `npm uninstall @storybook/preview-api`', () => { - const executeCommandSpy = jest.spyOn(pnpmProxy, 'executeCommand').mockReturnValue('6.0.0'); + it('with devDep it should run `npm uninstall @storybook/preview-api`', async () => { + const executeCommandSpy = jest + .spyOn(pnpmProxy, 'executeCommand') + .mockResolvedValueOnce('6.0.0'); - pnpmProxy.removeDependencies({}, ['@storybook/preview-api']); + await pnpmProxy.removeDependencies({}, ['@storybook/preview-api']); expect(executeCommandSpy).toHaveBeenLastCalledWith( - 'pnpm', - ['remove', '@storybook/preview-api'], - expect.any(String) + expect.objectContaining({ command: 'pnpm', args: ['remove', '@storybook/preview-api'] }) ); }); describe('skipInstall', () => { - it('should only change package.json without running install', () => { - const executeCommandSpy = jest.spyOn(pnpmProxy, 'executeCommand').mockReturnValue('7.0.0'); + it('should only change package.json without running install', async () => { + const executeCommandSpy = jest + .spyOn(pnpmProxy, 'executeCommand') + .mockResolvedValueOnce('7.0.0'); const writePackageSpy = jest .spyOn(pnpmProxy, 'writePackageJson') - .mockImplementation(jest.fn); + .mockImplementation(jest.fn()); - pnpmProxy.removeDependencies( + await pnpmProxy.removeDependencies( { skipInstall: true, packageJson: { @@ -120,37 +132,39 @@ describe('NPM Proxy', () => { describe('latestVersion', () => { it('without constraint it returns the latest version', async () => { - const executeCommandSpy = jest.spyOn(pnpmProxy, 'executeCommand').mockReturnValue('"5.3.19"'); + const executeCommandSpy = jest + .spyOn(pnpmProxy, 'executeCommand') + .mockResolvedValueOnce('"5.3.19"'); const version = await pnpmProxy.latestVersion('@storybook/preview-api'); - expect(executeCommandSpy).toHaveBeenCalledWith('pnpm', [ - 'info', - '@storybook/preview-api', - 'version', - '--json', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'pnpm', + args: ['info', '@storybook/preview-api', 'version', '--json'], + }) + ); expect(version).toEqual('5.3.19'); }); it('with constraint it returns the latest version satisfying the constraint', async () => { const executeCommandSpy = jest .spyOn(pnpmProxy, 'executeCommand') - .mockReturnValue('["4.25.3","5.3.19","6.0.0-beta.23"]'); + .mockResolvedValueOnce('["4.25.3","5.3.19","6.0.0-beta.23"]'); const version = await pnpmProxy.latestVersion('@storybook/preview-api', '5.X'); - expect(executeCommandSpy).toHaveBeenCalledWith('pnpm', [ - 'info', - '@storybook/preview-api', - 'versions', - '--json', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'pnpm', + args: ['info', '@storybook/preview-api', 'versions', '--json'], + }) + ); expect(version).toEqual('5.3.19'); }); it('throws an error if command output is not a valid JSON', async () => { - jest.spyOn(pnpmProxy, 'executeCommand').mockReturnValue('NOT A JSON'); + jest.spyOn(pnpmProxy, 'executeCommand').mockResolvedValueOnce('NOT A JSON'); await expect(pnpmProxy.latestVersion('@storybook/preview-api')).rejects.toThrow(); }); @@ -160,16 +174,18 @@ describe('NPM Proxy', () => { it('with a Storybook package listed in versions.json it returns the version', async () => { // eslint-disable-next-line global-require const storybookAngularVersion = require('../versions').default['@storybook/angular']; - const executeCommandSpy = jest.spyOn(pnpmProxy, 'executeCommand').mockReturnValue('"5.3.19"'); + const executeCommandSpy = jest + .spyOn(pnpmProxy, 'executeCommand') + .mockResolvedValueOnce('"5.3.19"'); const version = await pnpmProxy.getVersion('@storybook/angular'); - expect(executeCommandSpy).toHaveBeenCalledWith('pnpm', [ - 'info', - '@storybook/angular', - 'version', - '--json', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'pnpm', + args: ['info', '@storybook/angular', 'version', '--json'], + }) + ); expect(version).toEqual(`^${storybookAngularVersion}`); }); @@ -177,23 +193,25 @@ describe('NPM Proxy', () => { const packageVersion = '5.3.19'; const executeCommandSpy = jest .spyOn(pnpmProxy, 'executeCommand') - .mockReturnValue(`"${packageVersion}"`); + .mockResolvedValueOnce(`"${packageVersion}"`); const version = await pnpmProxy.getVersion('@storybook/react-native'); - expect(executeCommandSpy).toHaveBeenCalledWith('pnpm', [ - 'info', - '@storybook/react-native', - 'version', - '--json', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'pnpm', + args: ['info', '@storybook/react-native', 'version', '--json'], + }) + ); expect(version).toEqual(`^${packageVersion}`); }); }); describe('addPackageResolutions', () => { - it('adds resolutions to package.json and account for existing resolutions', () => { - const writePackageSpy = jest.spyOn(pnpmProxy, 'writePackageJson').mockImplementation(jest.fn); + it('adds resolutions to package.json and account for existing resolutions', async () => { + const writePackageSpy = jest + .spyOn(pnpmProxy, 'writePackageJson') + .mockImplementation(jest.fn()); jest.spyOn(pnpmProxy, 'retrievePackageJson').mockImplementation( // @ts-expect-error (not strict) @@ -207,7 +225,7 @@ describe('NPM Proxy', () => { const versions = { foo: 'x.x.x', }; - pnpmProxy.addPackageResolutions(versions); + await pnpmProxy.addPackageResolutions(versions); expect(writePackageSpy).toHaveBeenCalledWith({ overrides: { @@ -221,7 +239,7 @@ describe('NPM Proxy', () => { describe('mapDependencies', () => { it('should display duplicated dependencies based on pnpm output', async () => { // pnpm list "@storybook/*" "storybook" --depth 10 --json - jest.spyOn(pnpmProxy, 'executeCommand').mockReturnValue(` + jest.spyOn(pnpmProxy, 'executeCommand').mockResolvedValueOnce(` [ { "peerDependencies": { diff --git a/code/lib/cli/src/js-package-manager/PNPMProxy.ts b/code/lib/cli/src/js-package-manager/PNPMProxy.ts index 05482391057a..1c644cdb387c 100644 --- a/code/lib/cli/src/js-package-manager/PNPMProxy.ts +++ b/code/lib/cli/src/js-package-manager/PNPMProxy.ts @@ -34,8 +34,11 @@ export class PNPMProxy extends JsPackageManager { return pathExistsSync(pnpmWorkspaceYaml); } - initPackageJson() { - return this.executeCommand('pnpm', ['init', '-y']); + async initPackageJson() { + await this.executeCommand({ + command: 'pnpm', + args: ['init', '-y'], + }); } getRunStorybookCommand(): string { @@ -46,8 +49,11 @@ export class PNPMProxy extends JsPackageManager { return `pnpm run ${command}`; } - getPnpmVersion(): string { - return this.executeCommand('pnpm', ['--version']); + async getPnpmVersion(): Promise { + return this.executeCommand({ + command: 'pnpm', + args: ['--version'], + }); } getInstallArgs(): string[] { @@ -61,17 +67,27 @@ export class PNPMProxy extends JsPackageManager { return this.installArgs; } - runPackageCommand(command: string, args: string[], cwd?: string): string { - return this.executeCommand(`pnpm`, ['exec', command, ...args], undefined, cwd); + public runPackageCommandSync(command: string, args: string[], cwd?: string): string { + return this.executeCommandSync({ + command: 'pnpm', + args: ['exec', command, ...args], + cwd, + }); } - public findInstallations(pattern: string[]) { - const commandResult = this.executeCommand('pnpm', [ - 'list', - pattern.map((p) => `"${p}"`).join(' '), - '--json', - '--depth=99', - ]); + async runPackageCommand(command: string, args: string[], cwd?: string): Promise { + return this.executeCommand({ + command: 'pnpm', + args: ['exec', command, ...args], + cwd, + }); + } + + public async findInstallations(pattern: string[]) { + const commandResult = await this.executeCommand({ + command: 'pnpm', + args: ['list', pattern.map((p) => `"${p}"`).join(' '), '--json', '--depth=99'], + }); try { const parsedOutput = JSON.parse(commandResult); @@ -90,33 +106,48 @@ export class PNPMProxy extends JsPackageManager { }; } - protected runInstall(): void { - this.executeCommand('pnpm', ['install', ...this.getInstallArgs()], 'inherit'); + protected async runInstall() { + await this.executeCommand({ + command: 'pnpm', + args: ['install', ...this.getInstallArgs()], + stdio: 'inherit', + }); } - protected runAddDeps(dependencies: string[], installAsDevDependencies: boolean): void { + protected async runAddDeps(dependencies: string[], installAsDevDependencies: boolean) { let args = [...dependencies]; if (installAsDevDependencies) { args = ['-D', ...args]; } - this.executeCommand('pnpm', ['add', ...args, ...this.getInstallArgs()], 'inherit'); + await this.executeCommand({ + command: 'pnpm', + args: ['add', ...args, ...this.getInstallArgs()], + stdio: 'inherit', + }); } - protected runRemoveDeps(dependencies: string[]): void { + protected async runRemoveDeps(dependencies: string[]) { const args = [...dependencies]; - this.executeCommand('pnpm', ['remove', ...args, ...this.getInstallArgs()], 'inherit'); + await this.executeCommand({ + command: 'pnpm', + args: ['remove', ...args, ...this.getInstallArgs()], + stdio: 'inherit', + }); } - protected runGetVersions( + protected async runGetVersions( packageName: string, fetchAllVersions: T ): Promise { const args = [fetchAllVersions ? 'versions' : 'version', '--json']; - const commandResult = this.executeCommand('pnpm', ['info', packageName, ...args]); + const commandResult = await this.executeCommand({ + command: 'pnpm', + args: ['info', packageName, ...args], + }); try { const parsedOutput = JSON.parse(commandResult); 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 d7c853cc32f3..fb9edaef3cc0 100644 --- a/code/lib/cli/src/js-package-manager/Yarn1Proxy.test.ts +++ b/code/lib/cli/src/js-package-manager/Yarn1Proxy.test.ts @@ -12,93 +12,101 @@ describe('Yarn 1 Proxy', () => { }); describe('initPackageJson', () => { - it('should run `yarn init -y`', () => { - const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockReturnValue(''); + it('should run `yarn init -y`', async () => { + const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockResolvedValueOnce(''); - yarn1Proxy.initPackageJson(); + await yarn1Proxy.initPackageJson(); - expect(executeCommandSpy).toHaveBeenCalledWith('yarn', ['init', '-y']); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ command: 'yarn', args: ['init', '-y'] }) + ); }); }); describe('setRegistryUrl', () => { - it('should run `yarn config set npmRegistryServer https://foo.bar`', () => { - const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockReturnValue(''); + it('should run `yarn config set npmRegistryServer https://foo.bar`', async () => { + const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockResolvedValueOnce(''); - yarn1Proxy.setRegistryURL('https://foo.bar'); + await yarn1Proxy.setRegistryURL('https://foo.bar'); - expect(executeCommandSpy).toHaveBeenCalledWith('npm', [ - 'config', - 'set', - 'registry', - 'https://foo.bar', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'npm', + args: ['config', 'set', 'registry', 'https://foo.bar'], + }) + ); }); }); describe('installDependencies', () => { - it('should run `yarn`', () => { - const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockReturnValue(''); + it('should run `yarn`', async () => { + const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockResolvedValueOnce(''); - yarn1Proxy.installDependencies(); + await yarn1Proxy.installDependencies(); expect(executeCommandSpy).toHaveBeenCalledWith( - 'yarn', - ['install', '--ignore-workspace-root-check'], - expect.any(String) + expect.objectContaining({ + command: 'yarn', + args: ['install', '--ignore-workspace-root-check'], + }) ); }); }); describe('runScript', () => { - it('should execute script `yarn compodoc -- -e json -d .`', () => { - const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockReturnValue('7.1.0'); + it('should execute script `yarn compodoc -- -e json -d .`', async () => { + const executeCommandSpy = jest + .spyOn(yarn1Proxy, 'executeCommand') + .mockResolvedValueOnce('7.1.0'); - yarn1Proxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']); + await yarn1Proxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']); expect(executeCommandSpy).toHaveBeenLastCalledWith( - 'yarn', - ['compodoc', '-e', 'json', '-d', '.'], - undefined, - undefined + expect.objectContaining({ command: 'yarn', args: ['compodoc', '-e', 'json', '-d', '.'] }) ); }); }); describe('addDependencies', () => { - it('with devDep it should run `yarn install -D --ignore-workspace-root-check @storybook/preview-api`', () => { - const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockReturnValue(''); + it('with devDep it should run `yarn install -D --ignore-workspace-root-check @storybook/preview-api`', async () => { + const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockResolvedValueOnce(''); - yarn1Proxy.addDependencies({ installAsDevDependencies: true }, ['@storybook/preview-api']); + await yarn1Proxy.addDependencies({ installAsDevDependencies: true }, [ + '@storybook/preview-api', + ]); expect(executeCommandSpy).toHaveBeenCalledWith( - 'yarn', - ['add', '--ignore-workspace-root-check', '-D', '@storybook/preview-api'], - expect.any(String) + expect.objectContaining({ + command: 'yarn', + args: ['add', '--ignore-workspace-root-check', '-D', '@storybook/preview-api'], + }) ); }); }); describe('removeDependencies', () => { - it('should run `yarn remove --ignore-workspace-root-check @storybook/preview-api`', () => { - const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockReturnValue(''); + it('should run `yarn remove --ignore-workspace-root-check @storybook/preview-api`', async () => { + const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockResolvedValueOnce(''); yarn1Proxy.removeDependencies({}, ['@storybook/preview-api']); expect(executeCommandSpy).toHaveBeenCalledWith( - 'yarn', - ['remove', '--ignore-workspace-root-check', '@storybook/preview-api'], - expect.any(String) + expect.objectContaining({ + command: 'yarn', + args: ['remove', '--ignore-workspace-root-check', '@storybook/preview-api'], + }) ); }); - it('skipInstall should only change package.json without running install', () => { - const executeCommandSpy = jest.spyOn(yarn1Proxy, 'executeCommand').mockReturnValue('7.0.0'); + it('skipInstall should only change package.json without running install', async () => { + const executeCommandSpy = jest + .spyOn(yarn1Proxy, 'executeCommand') + .mockResolvedValueOnce('7.0.0'); const writePackageSpy = jest .spyOn(yarn1Proxy, 'writePackageJson') - .mockImplementation(jest.fn); + .mockImplementation(jest.fn()); - yarn1Proxy.removeDependencies( + await yarn1Proxy.removeDependencies( { skipInstall: true, packageJson: { @@ -124,50 +132,50 @@ describe('Yarn 1 Proxy', () => { it('without constraint it returns the latest version', async () => { const executeCommandSpy = jest .spyOn(yarn1Proxy, 'executeCommand') - .mockReturnValue('{"type":"inspect","data":"5.3.19"}'); + .mockResolvedValueOnce('{"type":"inspect","data":"5.3.19"}'); const version = await yarn1Proxy.latestVersion('@storybook/preview-api'); - expect(executeCommandSpy).toHaveBeenCalledWith('yarn', [ - 'info', - '@storybook/preview-api', - 'version', - '--json', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'yarn', + args: ['info', '@storybook/preview-api', 'version', '--json'], + }) + ); expect(version).toEqual('5.3.19'); }); it('with constraint it returns the latest version satisfying the constraint', async () => { const executeCommandSpy = jest .spyOn(yarn1Proxy, 'executeCommand') - .mockReturnValue('{"type":"inspect","data":["4.25.3","5.3.19","6.0.0-beta.23"]}'); + .mockResolvedValueOnce('{"type":"inspect","data":["4.25.3","5.3.19","6.0.0-beta.23"]}'); const version = await yarn1Proxy.latestVersion('@storybook/preview-api', '5.X'); - expect(executeCommandSpy).toHaveBeenCalledWith('yarn', [ - 'info', - '@storybook/preview-api', - 'versions', - '--json', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'yarn', + args: ['info', '@storybook/preview-api', 'versions', '--json'], + }) + ); expect(version).toEqual('5.3.19'); }); it('throws an error if command output is not a valid JSON', async () => { - jest.spyOn(yarn1Proxy, 'executeCommand').mockReturnValue('NOT A JSON'); + jest.spyOn(yarn1Proxy, 'executeCommand').mockResolvedValueOnce('NOT A JSON'); await expect(yarn1Proxy.latestVersion('@storybook/preview-api')).rejects.toThrow(); }); }); describe('addPackageResolutions', () => { - it('adds resolutions to package.json and account for existing resolutions', () => { + it('adds resolutions to package.json and account for existing resolutions', async () => { const writePackageSpy = jest .spyOn(yarn1Proxy, 'writePackageJson') - .mockImplementation(jest.fn); + .mockImplementation(jest.fn()); jest.spyOn(yarn1Proxy, 'retrievePackageJson').mockImplementation( - jest.fn(() => ({ + jest.fn(async () => ({ dependencies: {}, devDependencies: {}, resolutions: { @@ -179,7 +187,7 @@ describe('Yarn 1 Proxy', () => { const versions = { foo: 'x.x.x', }; - yarn1Proxy.addPackageResolutions(versions); + await yarn1Proxy.addPackageResolutions(versions); expect(writePackageSpy).toHaveBeenCalledWith({ dependencies: {}, @@ -195,7 +203,7 @@ describe('Yarn 1 Proxy', () => { describe('mapDependencies', () => { it('should display duplicated dependencies based on yarn output', async () => { // yarn list --pattern "@storybook/*" "@storybook/react" --recursive --json - jest.spyOn(yarn1Proxy, 'executeCommand').mockReturnValue(` + jest.spyOn(yarn1Proxy, 'executeCommand').mockResolvedValueOnce(` { "type": "tree", "data": { diff --git a/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts b/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts index 46d811aaa579..ad85c1f5452b 100644 --- a/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts +++ b/code/lib/cli/src/js-package-manager/Yarn1Proxy.ts @@ -30,8 +30,8 @@ export class Yarn1Proxy extends JsPackageManager { return this.installArgs; } - initPackageJson() { - return this.executeCommand('yarn', ['init', '-y']); + async initPackageJson() { + await this.executeCommand({ command: 'yarn', args: ['init', '-y'] }); } getRunStorybookCommand(): string { @@ -42,18 +42,19 @@ export class Yarn1Proxy extends JsPackageManager { return `yarn ${command}`; } - runPackageCommand(command: string, args: string[], cwd?: string): string { - return this.executeCommand(`yarn`, [command, ...args], undefined, cwd); + public runPackageCommandSync(command: string, args: string[], cwd?: string): string { + return this.executeCommandSync({ command: `yarn`, args: [command, ...args], cwd }); } - public findInstallations(pattern: string[]) { - const commandResult = this.executeCommand('yarn', [ - 'list', - '--pattern', - pattern.map((p) => `"${p}"`).join(' '), - '--recursive', - '--json', - ]); + async runPackageCommand(command: string, args: string[], cwd?: string): Promise { + return this.executeCommand({ command: `yarn`, args: [command, ...args], cwd }); + } + + public async findInstallations(pattern: string[]) { + const commandResult = await this.executeCommand({ + command: 'yarn', + args: ['list', '--pattern', pattern.map((p) => `"${p}"`).join(' '), '--recursive', '--json'], + }); try { const parsedOutput = JSON.parse(commandResult); @@ -72,33 +73,48 @@ export class Yarn1Proxy extends JsPackageManager { }; } - protected runInstall(): void { - this.executeCommand('yarn', ['install', ...this.getInstallArgs()], 'inherit'); + protected async runInstall() { + await this.executeCommand({ + command: 'yarn', + args: ['install', ...this.getInstallArgs()], + stdio: 'inherit', + }); } - protected runAddDeps(dependencies: string[], installAsDevDependencies: boolean): void { + protected async runAddDeps(dependencies: string[], installAsDevDependencies: boolean) { let args = [...dependencies]; if (installAsDevDependencies) { args = ['-D', ...args]; } - this.executeCommand('yarn', ['add', ...this.getInstallArgs(), ...args], 'inherit'); + await this.executeCommand({ + command: 'yarn', + args: ['add', ...this.getInstallArgs(), ...args], + stdio: 'inherit', + }); } - protected runRemoveDeps(dependencies: string[]): void { + protected async runRemoveDeps(dependencies: string[]) { const args = [...dependencies]; - this.executeCommand('yarn', ['remove', ...this.getInstallArgs(), ...args], 'inherit'); + await this.executeCommand({ + command: 'yarn', + args: ['remove', ...this.getInstallArgs(), ...args], + stdio: 'inherit', + }); } - protected runGetVersions( + protected async runGetVersions( packageName: string, fetchAllVersions: T ): Promise { const args = [fetchAllVersions ? 'versions' : 'version', '--json']; - const commandResult = this.executeCommand('yarn', ['info', packageName, ...args]); + const commandResult = await this.executeCommand({ + command: 'yarn', + args: ['info', packageName, ...args], + }); try { const parsedOutput = JSON.parse(commandResult); 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 7ae7aae5675b..f875254a6858 100644 --- a/code/lib/cli/src/js-package-manager/Yarn2Proxy.test.ts +++ b/code/lib/cli/src/js-package-manager/Yarn2Proxy.test.ts @@ -12,89 +12,98 @@ describe('Yarn 2 Proxy', () => { }); describe('initPackageJson', () => { - it('should run `yarn init`', () => { - const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockReturnValue(''); + it('should run `yarn init`', async () => { + const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockResolvedValueOnce(''); - yarn2Proxy.initPackageJson(); + await yarn2Proxy.initPackageJson(); - expect(executeCommandSpy).toHaveBeenCalledWith('yarn', ['init']); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ command: 'yarn', args: ['init'] }) + ); }); }); describe('installDependencies', () => { - it('should run `yarn`', () => { - const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockReturnValue(''); + it('should run `yarn`', async () => { + const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockResolvedValueOnce(''); - yarn2Proxy.installDependencies(); + await yarn2Proxy.installDependencies(); - expect(executeCommandSpy).toHaveBeenCalledWith('yarn', ['install'], expect.any(String)); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ command: 'yarn', args: ['install'] }) + ); }); }); describe('runScript', () => { - it('should execute script `yarn compodoc -- -e json -d .`', () => { - const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockReturnValue('7.1.0'); + it('should execute script `yarn compodoc -- -e json -d .`', async () => { + const executeCommandSpy = jest + .spyOn(yarn2Proxy, 'executeCommand') + .mockResolvedValueOnce('7.1.0'); - yarn2Proxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']); + await yarn2Proxy.runPackageCommand('compodoc', ['-e', 'json', '-d', '.']); expect(executeCommandSpy).toHaveBeenLastCalledWith( - 'yarn', - ['compodoc', '-e', 'json', '-d', '.'], - undefined, - undefined + expect.objectContaining({ + command: 'yarn', + args: ['compodoc', '-e', 'json', '-d', '.'], + }) ); }); }); describe('setRegistryUrl', () => { - it('should run `yarn config set npmRegistryServer https://foo.bar`', () => { - const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockReturnValue(''); + it('should run `yarn config set npmRegistryServer https://foo.bar`', async () => { + const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockResolvedValueOnce(''); - yarn2Proxy.setRegistryURL('https://foo.bar'); + await yarn2Proxy.setRegistryURL('https://foo.bar'); - expect(executeCommandSpy).toHaveBeenCalledWith('npm', [ - 'config', - 'set', - 'registry', - 'https://foo.bar', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'npm', + args: ['config', 'set', 'registry', 'https://foo.bar'], + }) + ); }); }); describe('addDependencies', () => { - it('with devDep it should run `yarn install -D @storybook/preview-api`', () => { - const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockReturnValue(''); + it('with devDep it should run `yarn install -D @storybook/preview-api`', async () => { + const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockResolvedValueOnce(''); - yarn2Proxy.addDependencies({ installAsDevDependencies: true }, ['@storybook/preview-api']); + await yarn2Proxy.addDependencies({ installAsDevDependencies: true }, [ + '@storybook/preview-api', + ]); expect(executeCommandSpy).toHaveBeenCalledWith( - 'yarn', - ['add', '-D', '@storybook/preview-api'], - expect.any(String) + expect.objectContaining({ command: 'yarn', args: ['add', '-D', '@storybook/preview-api'] }) ); }); }); describe('removeDependencies', () => { - it('should run `yarn remove @storybook/preview-api`', () => { - const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockReturnValue(''); + it('should run `yarn remove @storybook/preview-api`', async () => { + const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockResolvedValueOnce(''); - yarn2Proxy.removeDependencies({}, ['@storybook/preview-api']); + await yarn2Proxy.removeDependencies({}, ['@storybook/preview-api']); expect(executeCommandSpy).toHaveBeenCalledWith( - 'yarn', - ['remove', '@storybook/preview-api'], - expect.any(String) + expect.objectContaining({ + command: 'yarn', + args: ['remove', '@storybook/preview-api'], + }) ); }); - it('skipInstall should only change package.json without running install', () => { - const executeCommandSpy = jest.spyOn(yarn2Proxy, 'executeCommand').mockReturnValue('7.0.0'); + it('skipInstall should only change package.json without running install', async () => { + const executeCommandSpy = jest + .spyOn(yarn2Proxy, 'executeCommand') + .mockResolvedValueOnce('7.0.0'); const writePackageSpy = jest .spyOn(yarn2Proxy, 'writePackageJson') - .mockImplementation(jest.fn); + .mockImplementation(jest.fn()); - yarn2Proxy.removeDependencies( + await yarn2Proxy.removeDependencies( { skipInstall: true, packageJson: { @@ -120,56 +129,52 @@ describe('Yarn 2 Proxy', () => { it('without constraint it returns the latest version', async () => { const executeCommandSpy = jest .spyOn(yarn2Proxy, 'executeCommand') - .mockReturnValue('{"name":"@storybook/preview-api","version":"5.3.19"}'); + .mockResolvedValueOnce('{"name":"@storybook/preview-api","version":"5.3.19"}'); const version = await yarn2Proxy.latestVersion('@storybook/preview-api'); - expect(executeCommandSpy).toHaveBeenCalledWith('yarn', [ - 'npm', - 'info', - '@storybook/preview-api', - '--fields', - 'version', - '--json', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'yarn', + args: ['npm', 'info', '@storybook/preview-api', '--fields', 'version', '--json'], + }) + ); expect(version).toEqual('5.3.19'); }); it('with constraint it returns the latest version satisfying the constraint', async () => { const executeCommandSpy = jest .spyOn(yarn2Proxy, 'executeCommand') - .mockReturnValue( + .mockResolvedValueOnce( '{"name":"@storybook/preview-api","versions":["4.25.3","5.3.19","6.0.0-beta.23"]}' ); const version = await yarn2Proxy.latestVersion('@storybook/preview-api', '5.X'); - expect(executeCommandSpy).toHaveBeenCalledWith('yarn', [ - 'npm', - 'info', - '@storybook/preview-api', - '--fields', - 'versions', - '--json', - ]); + expect(executeCommandSpy).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'yarn', + args: ['npm', 'info', '@storybook/preview-api', '--fields', 'versions', '--json'], + }) + ); expect(version).toEqual('5.3.19'); }); it('throws an error if command output is not a valid JSON', async () => { - jest.spyOn(yarn2Proxy, 'executeCommand').mockReturnValue('NOT A JSON'); + jest.spyOn(yarn2Proxy, 'executeCommand').mockResolvedValueOnce('NOT A JSON'); await expect(yarn2Proxy.latestVersion('@storybook/preview-api')).rejects.toThrow(); }); }); describe('addPackageResolutions', () => { - it('adds resolutions to package.json and account for existing resolutions', () => { + it('adds resolutions to package.json and account for existing resolutions', async () => { const writePackageSpy = jest .spyOn(yarn2Proxy, 'writePackageJson') - .mockImplementation(jest.fn); + .mockImplementation(jest.fn()); jest.spyOn(yarn2Proxy, 'retrievePackageJson').mockImplementation( - jest.fn(() => ({ + jest.fn(async () => ({ dependencies: {}, devDependencies: {}, resolutions: { @@ -181,7 +186,8 @@ describe('Yarn 2 Proxy', () => { const versions = { foo: 'x.x.x', }; - yarn2Proxy.addPackageResolutions(versions); + + await yarn2Proxy.addPackageResolutions(versions); expect(writePackageSpy).toHaveBeenCalledWith({ dependencies: {}, @@ -197,7 +203,7 @@ describe('Yarn 2 Proxy', () => { describe('mapDependencies', () => { it('should display duplicated dependencies based on yarn2 output', async () => { // yarn info --name-only --recursive "@storybook/*" "storybook" - jest.spyOn(yarn2Proxy, 'executeCommand').mockReturnValue(` + jest.spyOn(yarn2Proxy, 'executeCommand').mockResolvedValueOnce(` "unrelated-and-should-be-filtered@npm:1.0.0" "@storybook/global@npm:5.0.0" "@storybook/instrumenter@npm:7.0.0-beta.12" diff --git a/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts b/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts index d5ffc42c08fa..e565e7f12ee2 100644 --- a/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts +++ b/code/lib/cli/src/js-package-manager/Yarn2Proxy.ts @@ -16,8 +16,8 @@ export class Yarn2Proxy extends JsPackageManager { return this.installArgs; } - initPackageJson() { - return this.executeCommand('yarn', ['init']); + async initPackageJson() { + await this.executeCommand({ command: 'yarn', args: ['init'] }); } getRunStorybookCommand(): string { @@ -28,18 +28,25 @@ export class Yarn2Proxy extends JsPackageManager { return `yarn ${command}`; } - runPackageCommand(command: string, args: string[], cwd?: string): string { - return this.executeCommand(`yarn`, [command, ...args], undefined, cwd); + public runPackageCommandSync(command: string, args: string[], cwd?: string) { + return this.executeCommandSync({ command: 'yarn', args: [command, ...args], cwd }); } - public findInstallations(pattern: string[]) { - const commandResult = this.executeCommand('yarn', [ - 'info', - '--name-only', - '--recursive', - pattern.map((p) => `"${p}"`).join(' '), - `"${pattern}"`, - ]); + async runPackageCommand(command: string, args: string[], cwd?: string) { + return this.executeCommand({ command: 'yarn', args: [command, ...args], cwd }); + } + + public async findInstallations(pattern: string[]) { + const commandResult = await this.executeCommand({ + command: 'yarn', + args: [ + 'info', + '--name-only', + '--recursive', + pattern.map((p) => `"${p}"`).join(' '), + `"${pattern}"`, + ], + }); try { return this.mapDependencies(commandResult); @@ -57,34 +64,49 @@ export class Yarn2Proxy extends JsPackageManager { }; } - protected runInstall(): void { - this.executeCommand('yarn', ['install', ...this.getInstallArgs()], 'inherit'); + protected async runInstall() { + await this.executeCommand({ + command: 'yarn', + args: ['install', ...this.getInstallArgs()], + stdio: 'inherit', + }); } - protected runAddDeps(dependencies: string[], installAsDevDependencies: boolean): void { + protected async runAddDeps(dependencies: string[], installAsDevDependencies: boolean) { let args = [...dependencies]; if (installAsDevDependencies) { args = ['-D', ...args]; } - this.executeCommand('yarn', ['add', ...this.getInstallArgs(), ...args], 'inherit'); + await this.executeCommand({ + command: 'yarn', + args: ['add', ...this.getInstallArgs(), ...args], + stdio: 'inherit', + }); } - protected runRemoveDeps(dependencies: string[]): void { + protected async runRemoveDeps(dependencies: string[]) { const args = [...dependencies]; - this.executeCommand('yarn', ['remove', ...this.getInstallArgs(), ...args], 'inherit'); + await this.executeCommand({ + command: 'yarn', + args: ['remove', ...this.getInstallArgs(), ...args], + stdio: 'inherit', + }); } - protected runGetVersions( + protected async runGetVersions( packageName: string, fetchAllVersions: T ): Promise { const field = fetchAllVersions ? 'versions' : 'version'; const args = ['--fields', field, '--json']; - const commandResult = this.executeCommand('yarn', ['npm', 'info', packageName, ...args]); + const commandResult = await this.executeCommand({ + command: 'yarn', + args: ['npm', 'info', packageName, ...args], + }); try { const parsedOutput = JSON.parse(commandResult); diff --git a/code/lib/cli/src/migrate.ts b/code/lib/cli/src/migrate.ts index 7162e205b1f5..2d0a3b6af16d 100644 --- a/code/lib/cli/src/migrate.ts +++ b/code/lib/cli/src/migrate.ts @@ -22,8 +22,8 @@ export async function migrate(migration: any, { glob, dryRun, list, rename, pars export async function addStorybookBlocksPackage() { const packageManager = JsPackageManagerFactory.getPackageManager(); - const packageJson = packageManager.retrievePackageJson(); - const versionToInstall = getStorybookVersionSpecifier(packageManager.retrievePackageJson()); + const packageJson = await packageManager.retrievePackageJson(); + const versionToInstall = getStorybookVersionSpecifier(await packageManager.retrievePackageJson()); logger.info(`✅ Adding "@storybook/blocks" package`); await packageManager.addDependencies({ installAsDevDependencies: true, packageJson }, [ `@storybook/blocks@${versionToInstall}`, diff --git a/code/lib/cli/src/sandbox-templates.ts b/code/lib/cli/src/sandbox-templates.ts index 2ad5ec4c6876..ff8a46362afa 100644 --- a/code/lib/cli/src/sandbox-templates.ts +++ b/code/lib/cli/src/sandbox-templates.ts @@ -454,6 +454,17 @@ const internalTemplates = { }, }, }, + 'internal/server-webpack5': { + name: 'Server Webpack5', + script: 'yarn init -y', + expected: { + framework: '@storybook/server-webpack5', + renderer: '@storybook/server', + builder: '@storybook/builder-webpack5', + }, + isInternal: true, + inDevelopment: true, + }, // 'internal/pnp': { // ...baseTemplates['cra/default-ts'], // name: 'PNP (cra/default-ts)', diff --git a/code/lib/cli/src/upgrade.ts b/code/lib/cli/src/upgrade.ts index 6d25ed46c138..72fbab8545dd 100644 --- a/code/lib/cli/src/upgrade.ts +++ b/code/lib/cli/src/upgrade.ts @@ -189,7 +189,7 @@ export const doUpgrade = async ({ if (!dryRun) flags.push('--upgrade'); flags.push('--target'); flags.push(target); - flags = addExtraFlags(EXTRA_FLAGS, flags, packageManager.retrievePackageJson()); + flags = addExtraFlags(EXTRA_FLAGS, flags, await packageManager.retrievePackageJson()); const check = spawnSync('npx', ['npm-check-updates@latest', '/storybook/', ...flags], { stdio: 'pipe', shell: true, @@ -204,7 +204,7 @@ export const doUpgrade = async ({ if (!dryRun) { commandLog(`Installing upgrades`); - packageManager.installDependencies(); + await packageManager.installDependencies(); } let automigrationResults; diff --git a/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts b/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts index 13cf81424441..955c19eec9f2 100644 --- a/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts +++ b/code/lib/preview-api/src/modules/store/csf/testing-utils/index.ts @@ -21,7 +21,7 @@ import { normalizeComponentAnnotations } from '../normalizeComponentAnnotations' import { getValuesFromArgTypes } from '../getValuesFromArgTypes'; import { normalizeProjectAnnotations } from '../normalizeProjectAnnotations'; -let GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = {}; +let GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS = composeConfigs([]); export function setProjectAnnotations( projectAnnotations: ProjectAnnotations | ProjectAnnotations[] @@ -33,7 +33,7 @@ export function setProjectAnnotations( export function composeStory( storyAnnotations: LegacyStoryAnnotationsOrFn, componentAnnotations: ComponentAnnotations, - projectAnnotations: ProjectAnnotations = GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS, + projectAnnotations: ProjectAnnotations = GLOBAL_STORYBOOK_PROJECT_ANNOTATIONS as ProjectAnnotations, defaultConfig: ProjectAnnotations = {}, exportsName?: string ): PreparedStoryFn> { @@ -60,7 +60,7 @@ export function composeStory({ ...projectAnnotations, ...defaultConfig, }); diff --git a/code/renderers/html/template/cli/js/Header.js b/code/renderers/html/template/cli/js/Header.js index 925652350ede..30b4c5156ad6 100644 --- a/code/renderers/html/template/cli/js/Header.js +++ b/code/renderers/html/template/cli/js/Header.js @@ -5,7 +5,7 @@ export const createHeader = ({ user, onLogout, onLogin, onCreateAccount }) => { const header = document.createElement('header'); const wrapper = document.createElement('div'); - wrapper.className = 'wrapper'; + wrapper.className = 'storybook-header'; const logo = `

diff --git a/code/renderers/html/template/cli/js/Page.js b/code/renderers/html/template/cli/js/Page.js index a2b56d7cd5f6..227cddeb0255 100644 --- a/code/renderers/html/template/cli/js/Page.js +++ b/code/renderers/html/template/cli/js/Page.js @@ -34,7 +34,7 @@ export const createPage = () => { article.appendChild(header); const section = ` -
+

Pages in Storybook

We recommend building UIs with a diff --git a/code/renderers/html/template/cli/ts-3-8/Header.ts b/code/renderers/html/template/cli/ts-3-8/Header.ts index 94a31e6b5317..7bee76259651 100644 --- a/code/renderers/html/template/cli/ts-3-8/Header.ts +++ b/code/renderers/html/template/cli/ts-3-8/Header.ts @@ -12,7 +12,7 @@ export const createHeader = ({ user, onLogout, onLogin, onCreateAccount }: Heade const header = document.createElement('header'); const wrapper = document.createElement('div'); - wrapper.className = 'wrapper'; + wrapper.className = 'storybook-header'; const logo = `

diff --git a/code/renderers/html/template/cli/ts-3-8/Page.ts b/code/renderers/html/template/cli/ts-3-8/Page.ts index 4c4028ff1d99..3ff4211d8a8a 100644 --- a/code/renderers/html/template/cli/ts-3-8/Page.ts +++ b/code/renderers/html/template/cli/ts-3-8/Page.ts @@ -38,7 +38,7 @@ export const createPage = () => { article.appendChild(header); const section = ` -
+

Pages in Storybook

We recommend building UIs with a diff --git a/code/renderers/html/template/cli/ts-4-9/Header.ts b/code/renderers/html/template/cli/ts-4-9/Header.ts index 94a31e6b5317..7bee76259651 100644 --- a/code/renderers/html/template/cli/ts-4-9/Header.ts +++ b/code/renderers/html/template/cli/ts-4-9/Header.ts @@ -12,7 +12,7 @@ export const createHeader = ({ user, onLogout, onLogin, onCreateAccount }: Heade const header = document.createElement('header'); const wrapper = document.createElement('div'); - wrapper.className = 'wrapper'; + wrapper.className = 'storybook-header'; const logo = `

diff --git a/code/renderers/html/template/cli/ts-4-9/Page.ts b/code/renderers/html/template/cli/ts-4-9/Page.ts index 4c4028ff1d99..3ff4211d8a8a 100644 --- a/code/renderers/html/template/cli/ts-4-9/Page.ts +++ b/code/renderers/html/template/cli/ts-4-9/Page.ts @@ -38,7 +38,7 @@ export const createPage = () => { article.appendChild(header); const section = ` -
+

Pages in Storybook

We recommend building UIs with a diff --git a/code/renderers/preact/template/cli/Header.jsx b/code/renderers/preact/template/cli/Header.jsx index 99ee8c54b51f..8a722e57d331 100644 --- a/code/renderers/preact/template/cli/Header.jsx +++ b/code/renderers/preact/template/cli/Header.jsx @@ -5,7 +5,7 @@ import './header.css'; export const Header = ({ user, onLogin, onLogout, onCreateAccount }) => (

-
+
diff --git a/code/renderers/preact/template/cli/Page.jsx b/code/renderers/preact/template/cli/Page.jsx index 4344c071980c..12ca119d0b04 100644 --- a/code/renderers/preact/template/cli/Page.jsx +++ b/code/renderers/preact/template/cli/Page.jsx @@ -14,7 +14,7 @@ export const Page = () => { onCreateAccount={() => setUser({ name: 'Jane Doe' })} /> -
+

Pages in Storybook

We recommend building UIs with a{' '} diff --git a/code/renderers/react/src/__test__/composeStories.test.tsx b/code/renderers/react/src/__test__/composeStories.test.tsx index ad5543822b19..8c97d906ab22 100644 --- a/code/renderers/react/src/__test__/composeStories.test.tsx +++ b/code/renderers/react/src/__test__/composeStories.test.tsx @@ -9,8 +9,6 @@ import { setProjectAnnotations, composeStories, composeStory } from '..'; import type { Button } from './Button'; import * as stories from './Button.stories'; -setProjectAnnotations([]); - // example with composeStories, returns an object with all stories composed with args/decorators const { CSF3Primary } = composeStories(stories); @@ -43,15 +41,15 @@ test('reuses args from composeStories', () => { expect(buttonElement).not.toBeNull(); }); -describe('GlobalConfig', () => { - test('renders with default globalConfig', () => { +describe('projectAnnotations', () => { + test('renders with default projectAnnotations', () => { const WithEnglishText = composeStory(stories.CSF2StoryWithLocale, stories.default); const { getByText } = render(); const buttonElement = getByText('Hello!'); expect(buttonElement).not.toBeNull(); }); - test('renders with custom globalConfig', () => { + test('renders with custom projectAnnotations via composeStory params', () => { const WithPortugueseText = composeStory(stories.CSF2StoryWithLocale, stories.default, { globalTypes: { locale: { defaultValue: 'pt' } } as any, }); @@ -59,6 +57,12 @@ describe('GlobalConfig', () => { const buttonElement = getByText('Olá!'); expect(buttonElement).not.toBeNull(); }); + + test('renders with custom projectAnnotations via setProjectAnnotations', () => { + setProjectAnnotations([{ parameters: { injected: true } }]); + const Story = composeStory(stories.CSF2StoryWithLocale, stories.default); + expect(Story.parameters?.injected).toBe(true); + }); }); describe('CSF3', () => { diff --git a/code/renderers/react/template/cli/js/Header.jsx b/code/renderers/react/template/cli/js/Header.jsx index 3862422ed8ec..39e5226cffc1 100644 --- a/code/renderers/react/template/cli/js/Header.jsx +++ b/code/renderers/react/template/cli/js/Header.jsx @@ -6,7 +6,7 @@ import './header.css'; export const Header = ({ user, onLogin, onLogout, onCreateAccount }) => (

-
+
diff --git a/code/renderers/react/template/cli/js/Page.jsx b/code/renderers/react/template/cli/js/Page.jsx index c5fffe953be5..c421401138ed 100644 --- a/code/renderers/react/template/cli/js/Page.jsx +++ b/code/renderers/react/template/cli/js/Page.jsx @@ -15,7 +15,7 @@ export const Page = () => { onCreateAccount={() => setUser({ name: 'Jane Doe' })} /> -
+

Pages in Storybook

We recommend building UIs with a{' '} diff --git a/code/renderers/react/template/cli/ts-3-8/Header.tsx b/code/renderers/react/template/cli/ts-3-8/Header.tsx index dc3f3c19c31a..01504601311d 100644 --- a/code/renderers/react/template/cli/ts-3-8/Header.tsx +++ b/code/renderers/react/template/cli/ts-3-8/Header.tsx @@ -16,7 +16,7 @@ interface HeaderProps { export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => (

-
+
diff --git a/code/renderers/react/template/cli/ts-3-8/Page.tsx b/code/renderers/react/template/cli/ts-3-8/Page.tsx index 885f2b41f1ee..994d8908ed4b 100644 --- a/code/renderers/react/template/cli/ts-3-8/Page.tsx +++ b/code/renderers/react/template/cli/ts-3-8/Page.tsx @@ -19,7 +19,7 @@ export const Page: React.FC = () => { onCreateAccount={() => setUser({ name: 'Jane Doe' })} /> -
+

Pages in Storybook

We recommend building UIs with a{' '} diff --git a/code/renderers/react/template/cli/ts-4-9/Header.tsx b/code/renderers/react/template/cli/ts-4-9/Header.tsx index dc3f3c19c31a..01504601311d 100644 --- a/code/renderers/react/template/cli/ts-4-9/Header.tsx +++ b/code/renderers/react/template/cli/ts-4-9/Header.tsx @@ -16,7 +16,7 @@ interface HeaderProps { export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => (

-
+
diff --git a/code/renderers/react/template/cli/ts-4-9/Page.tsx b/code/renderers/react/template/cli/ts-4-9/Page.tsx index ec054e813895..e11748301390 100644 --- a/code/renderers/react/template/cli/ts-4-9/Page.tsx +++ b/code/renderers/react/template/cli/ts-4-9/Page.tsx @@ -19,7 +19,7 @@ export const Page: React.FC = () => { onCreateAccount={() => setUser({ name: 'Jane Doe' })} /> -
+

Pages in Storybook

We recommend building UIs with a{' '} diff --git a/code/renderers/server/package.json b/code/renderers/server/package.json index 2262b1df2cc7..c46797804f68 100644 --- a/code/renderers/server/package.json +++ b/code/renderers/server/package.json @@ -31,6 +31,11 @@ "require": "./dist/config.js", "import": "./dist/config.mjs" }, + "./preset": { + "types": "./dist/preset.d.ts", + "require": "./dist/preset.js", + "import": "./dist/preset.mjs" + }, "./package.json": "./package.json" }, "main": "dist/index.js", @@ -49,9 +54,12 @@ }, "dependencies": { "@storybook/core-client": "7.0.9", + "@storybook/csf": "^0.1.0", "@storybook/global": "^5.0.0", "@storybook/preview-api": "7.0.9", "@storybook/types": "7.0.9", + "@types/fs-extra": "^11.0.1", + "fs-extra": "^11.1.0", "ts-dedent": "^2.0.0" }, "devDependencies": { @@ -66,7 +74,8 @@ "bundler": { "entries": [ "./src/index.ts", - "./src/config.ts" + "./src/config.ts", + "./src/preset.ts" ], "platform": "browser" }, diff --git a/code/renderers/server/preset.js b/code/renderers/server/preset.js new file mode 100644 index 000000000000..a83f95279e7f --- /dev/null +++ b/code/renderers/server/preset.js @@ -0,0 +1 @@ +module.exports = require('./dist/preset'); diff --git a/code/renderers/server/src/preset.ts b/code/renderers/server/src/preset.ts new file mode 100644 index 000000000000..fced651731c3 --- /dev/null +++ b/code/renderers/server/src/preset.ts @@ -0,0 +1,33 @@ +import fs from 'fs-extra'; +import { toId } from '@storybook/csf'; +import type { StaticMeta } from '@storybook/csf-tools'; +import type { IndexerOptions, IndexedStory, StoryIndexer } from '@storybook/types'; + +export const storyIndexers = (indexers: StoryIndexer[] | null) => { + const jsonIndexer = async (fileName: string, opts: IndexerOptions) => { + const json = await fs.readJson(fileName, 'utf-8'); + const meta: StaticMeta = { + title: json.title, + }; + const stories: IndexedStory[] = json.stories.map((story: { name: string }) => { + const id = toId(meta.title, story.name); + const { name } = story; + const indexedStory: IndexedStory = { + id, + name, + }; + return indexedStory; + }); + return { + meta, + stories, + }; + }; + return [ + { + test: /(stories|story)\.json$/, + indexer: jsonIndexer, + }, + ...(indexers || []), + ]; +}; diff --git a/code/renderers/server/template/cli/button.stories.json b/code/renderers/server/template/cli/button.stories.json index 3a9f73934385..f42672368f8c 100644 --- a/code/renderers/server/template/cli/button.stories.json +++ b/code/renderers/server/template/cli/button.stories.json @@ -1,7 +1,10 @@ { "title": "Example/Button", "parameters": { - "server": { "id": "button" } + "server": { + "url": "https://storybook-server-demo.netlify.app/api", + "id": "button" + } }, "args": { "label": "Button" }, "argTypes": { diff --git a/code/renderers/server/template/cli/header.stories.json b/code/renderers/server/template/cli/header.stories.json index e51cbfac7669..ee9aaae50acc 100644 --- a/code/renderers/server/template/cli/header.stories.json +++ b/code/renderers/server/template/cli/header.stories.json @@ -1,7 +1,10 @@ { "title": "Example/Header", "parameters": { - "server": { "id": "header" } + "server": { + "url": "https://storybook-server-demo.netlify.app/api", + "id": "header" + } }, "stories": [ { diff --git a/code/renderers/server/template/cli/page.stories.json b/code/renderers/server/template/cli/page.stories.json index 2245cb3f9db8..abbc4e809188 100644 --- a/code/renderers/server/template/cli/page.stories.json +++ b/code/renderers/server/template/cli/page.stories.json @@ -1,7 +1,10 @@ { "title": "Example/Page", "parameters": { - "server": { "id": "page" } + "server": { + "url": "https://storybook-server-demo.netlify.app/api", + "id": "page" + } }, "stories": [ { diff --git a/code/renderers/svelte/template/cli/js/Header.svelte b/code/renderers/svelte/template/cli/js/Header.svelte index 8350b35fd664..a9c08f75db7d 100644 --- a/code/renderers/svelte/template/cli/js/Header.svelte +++ b/code/renderers/svelte/template/cli/js/Header.svelte @@ -20,7 +20,7 @@

-
+
diff --git a/code/renderers/svelte/template/cli/js/Page.svelte b/code/renderers/svelte/template/cli/js/Page.svelte index d545d908e500..acc473c2518f 100644 --- a/code/renderers/svelte/template/cli/js/Page.svelte +++ b/code/renderers/svelte/template/cli/js/Page.svelte @@ -13,7 +13,7 @@ on:createAccount={() => (user = { name: 'Jane Doe' })} /> -
+

Pages in Storybook

We recommend building UIs with a diff --git a/code/renderers/svelte/template/cli/ts-3-8/Header.svelte b/code/renderers/svelte/template/cli/ts-3-8/Header.svelte index e3b8d039f864..cb6f82d5e666 100644 --- a/code/renderers/svelte/template/cli/ts-3-8/Header.svelte +++ b/code/renderers/svelte/template/cli/ts-3-8/Header.svelte @@ -20,7 +20,7 @@

-
+
diff --git a/code/renderers/svelte/template/cli/ts-3-8/Page.svelte b/code/renderers/svelte/template/cli/ts-3-8/Page.svelte index c2c6369f565e..94cdb07ecd39 100644 --- a/code/renderers/svelte/template/cli/ts-3-8/Page.svelte +++ b/code/renderers/svelte/template/cli/ts-3-8/Page.svelte @@ -13,7 +13,7 @@ on:createAccount={() => (user = { name: 'Jane Doe' })} /> -
+

Pages in Storybook

We recommend building UIs with a diff --git a/code/renderers/svelte/template/cli/ts-4-9/Header.svelte b/code/renderers/svelte/template/cli/ts-4-9/Header.svelte index e3b8d039f864..cb6f82d5e666 100644 --- a/code/renderers/svelte/template/cli/ts-4-9/Header.svelte +++ b/code/renderers/svelte/template/cli/ts-4-9/Header.svelte @@ -20,7 +20,7 @@

-
+
diff --git a/code/renderers/svelte/template/cli/ts-4-9/Page.svelte b/code/renderers/svelte/template/cli/ts-4-9/Page.svelte index c2c6369f565e..94cdb07ecd39 100644 --- a/code/renderers/svelte/template/cli/ts-4-9/Page.svelte +++ b/code/renderers/svelte/template/cli/ts-4-9/Page.svelte @@ -13,7 +13,7 @@ on:createAccount={() => (user = { name: 'Jane Doe' })} /> -
+

Pages in Storybook

We recommend building UIs with a diff --git a/code/renderers/vue/template/cli/Header.vue b/code/renderers/vue/template/cli/Header.vue index bb35153df5fd..4164c64bb144 100644 --- a/code/renderers/vue/template/cli/Header.vue +++ b/code/renderers/vue/template/cli/Header.vue @@ -1,6 +1,6 @@