Skip to content

Commit

Permalink
Merge pull request #25573 from storybookjs/jeppe/upgrade-versioned-main
Browse files Browse the repository at this point in the history
CLI: Versioned upgrade of monorepo packages (main)
  • Loading branch information
ndelangen authored Jan 15, 2024
2 parents 9a628b5 + 894825d commit 5ac3a15
Show file tree
Hide file tree
Showing 14 changed files with 370 additions and 116 deletions.
2 changes: 1 addition & 1 deletion code/frameworks/nextjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ npx storybook@latest init
This framework is designed to work with Storybook 7. If you’re not already using v7, upgrade with this command:

```bash
npx storybook@latest upgrade --prerelease
npx storybook@latest upgrade
```

#### Automatic migration
Expand Down
2 changes: 1 addition & 1 deletion code/frameworks/preact-vite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ npx storybook@latest init
This framework is designed to work with Storybook 7. If you’re not already using v7, upgrade with this command:

```bash
npx storybook@latest upgrade --prerelease
npx storybook@latest upgrade
```

#### Manual migration
Expand Down
3 changes: 1 addition & 2 deletions code/frameworks/sveltekit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ Check out our [Frameworks API](https://storybook.js.org/blog/framework-api/) ann
- [Mocking links](#mocking-links)
- [Troubleshooting](#troubleshooting)
- [Error: `ERR! SyntaxError: Identifier '__esbuild_register_import_meta_url__' has already been declared` when starting Storybook](#error-err-syntaxerror-identifier-__esbuild_register_import_meta_url__-has-already-been-declared-when-starting-storybook)
- [Error: `Cannot read properties of undefined (reading 'disable_scroll_handling')` in preview](#error-cannot-read-properties-of-undefined-reading-disable_scroll_handling-in-preview)
- [Acknowledgements](#acknowledgements)

## Supported features
Expand Down Expand Up @@ -64,7 +63,7 @@ npx storybook@latest init
This framework is designed to work with Storybook 7. If you’re not already using v7, upgrade with this command:

```bash
npx storybook@latest upgrade --prerelease
npx storybook@latest upgrade
```

#### Automatic migration
Expand Down
6 changes: 5 additions & 1 deletion code/lib/cli/src/automigrate/fixes/builder-vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { writeConfig } from '@storybook/csf-tools';
import type { Fix } from '../types';
import type { PackageJson } from '../../js-package-manager';
import { updateMainConfig } from '../helpers/mainConfigFile';
import { getStorybookVersionSpecifier } from '../../helpers';

const logger = console;

Expand Down Expand Up @@ -68,8 +69,11 @@ export const builderVite: Fix<BuilderViteOptions> = {

logger.info(`✅ Adding '@storybook/builder-vite' as dev dependency`);
if (!dryRun) {
const versionToInstall = getStorybookVersionSpecifier(
await packageManager.retrievePackageJson()
);
await packageManager.addDependencies({ installAsDevDependencies: true }, [
'@storybook/builder-vite',
`@storybook/builder-vite@${versionToInstall}`,
]);
}

Expand Down
6 changes: 4 additions & 2 deletions code/lib/cli/src/automigrate/helpers/checkWebpack5Builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ export const checkWebpack5Builder = async ({
To upgrade to the latest stable release, run this from your project directory:
${chalk.cyan('npx storybook upgrade')}
${chalk.cyan('npx storybook@latest upgrade')}
Add the ${chalk.cyan('--prerelease')} flag to get the latest prerelease.
To upgrade to the latest pre-release, run this from your project directory:
${chalk.cyan('npx storybook@next upgrade')}
`.trim()
);
return null;
Expand Down
11 changes: 7 additions & 4 deletions code/lib/cli/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,19 @@ command('babelrc')
.action(() => generateStorybookBabelConfigInCWD());

command('upgrade')
.description('Upgrade your Storybook packages to the latest')
.description(`Upgrade your Storybook packages to v${versions.storybook}`)
.option(
'--package-manager <npm|pnpm|yarn1|yarn2>',
'Force package manager for installing dependencies'
)
.option('-N --use-npm', 'Use NPM to install dependencies (deprecated)')
.option('-y --yes', 'Skip prompting the user')
.option('-n --dry-run', 'Only check for upgrades, do not install')
.option('-t --tag <tag>', 'Upgrade to a certain npm dist-tag (e.g. next, prerelease)')
.option('-p --prerelease', 'Upgrade to the pre-release packages')
.option(
'-t --tag <tag>',
'Upgrade to a certain npm dist-tag (e.g. next, prerelease) (deprecated)'
)
.option('-p --prerelease', 'Upgrade to the pre-release packages (deprecated)')
.option('-s --skip-check', 'Skip postinstall version and automigration checks')
.option('-c, --config-dir <dir-name>', 'Directory where to load Storybook configurations from')
.action(async (options: UpgradeOptions) => upgrade(options).catch(() => process.exit(1)));
Expand Down Expand Up @@ -153,7 +156,7 @@ command('sandbox [filterValue]')
.option('-b --branch <branch>', 'Define the branch to download from', 'next')
.option('--no-init', 'Whether to download a template without an initialized Storybook', false)
.action((filterValue, options) =>
sandbox({ filterValue, ...options }).catch((e) => {
sandbox({ filterValue, ...options }, pkg).catch((e) => {
logger.error(e);
process.exit(1);
})
Expand Down
2 changes: 1 addition & 1 deletion code/lib/cli/src/initiate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ const getEmptyDirMessage = (packageManagerType: PackageManagerName) => {
`;
};

async function doInitiate(
export async function doInitiate(
options: CommandOptions,
pkg: PackageJson
): Promise<
Expand Down
2 changes: 2 additions & 0 deletions code/lib/cli/src/js-package-manager/JsPackageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ export abstract class JsPackageManager {

done = commandLog('Installing dependencies');

logger.log();

try {
await this.runInstall();
done();
Expand Down
112 changes: 89 additions & 23 deletions code/lib/cli/src/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,78 @@ import { dedent } from 'ts-dedent';
import { downloadTemplate } from 'giget';

import { existsSync, readdir } from 'fs-extra';
import { lt, prerelease } from 'semver';
import type { Template, TemplateKey } from './sandbox-templates';
import { allTemplates as TEMPLATES } from './sandbox-templates';

import type { PackageJson, PackageManagerName } from './js-package-manager';
import { JsPackageManagerFactory } from './js-package-manager';
import versions from './versions';
import { doInitiate } from './initiate';

const logger = console;

interface SandboxOptions {
filterValue?: string;
output?: string;
branch?: string;
init?: boolean;
packageManager: PackageManagerName;
}
type Choice = keyof typeof TEMPLATES;

const toChoices = (c: Choice): prompts.Choice => ({ title: TEMPLATES[c].name, value: c });

export const sandbox = async ({
output: outputDirectory,
filterValue,
branch,
init,
}: SandboxOptions) => {
export const sandbox = async (
{ output: outputDirectory, filterValue, init, ...options }: SandboxOptions,
pkg: PackageJson
) => {
// Either get a direct match when users pass a template id, or filter through all templates
let selectedConfig: Template | undefined = TEMPLATES[filterValue as TemplateKey];
let selectedTemplate: Choice | null = selectedConfig ? (filterValue as TemplateKey) : null;

let templateId: Choice | null = selectedConfig ? (filterValue as TemplateKey) : null;

const { packageManager: pkgMgr } = options;

const packageManager = JsPackageManagerFactory.getPackageManager({
force: pkgMgr,
});
const latestVersion = await packageManager.latestVersion('@storybook/cli');
// In verdaccio we often only have the latest tag, so this will fail.
const nextVersion = await packageManager
.latestVersion('@storybook/cli@next')
.catch((e) => '0.0.0');
const currentVersion = versions['@storybook/cli'];
const isPrerelease = prerelease(currentVersion);
const isOutdated = lt(currentVersion, isPrerelease ? nextVersion : latestVersion);
const borderColor = isOutdated ? '#FC521F' : '#F1618C';

const downloadType = !isOutdated && init ? 'after-storybook' : 'before-storybook';
const branch = isPrerelease ? 'next' : 'main';

const messages = {
welcome: `Creating a Storybook ${chalk.bold(currentVersion)} sandbox..`,
notLatest: chalk.red(dedent`
This version is behind the latest release, which is: ${chalk.bold(latestVersion)}!
You likely ran the init command through npx, which can use a locally cached version, to get the latest please run:
${chalk.bold('npx storybook@latest sandbox')}
You may want to CTRL+C to stop, and run with the latest version instead.
`),
longInitTime: chalk.yellow(
'The creation of the sandbox will take longer, because we will need to run init.'
),
prerelease: chalk.yellow('This is a pre-release version.'),
};

logger.log(
boxen(
[messages.welcome]
.concat(isOutdated && !isPrerelease ? [messages.notLatest] : [])
.concat(init && (isOutdated || isPrerelease) ? [messages.longInitTime] : [])
.concat(isPrerelease ? [messages.prerelease] : [])
.join('\n'),
{ borderStyle: 'round', padding: 1, borderColor }
)
);
if (!selectedConfig) {
const filterRegex = new RegExp(`^${filterValue || ''}`, 'i');

Expand Down Expand Up @@ -78,7 +125,7 @@ export const sandbox = async ({
}

if (choices.length === 1) {
[selectedTemplate] = choices;
[templateId] = choices;
} else {
logger.info(
boxen(
Expand All @@ -96,24 +143,24 @@ export const sandbox = async ({
)
);

selectedTemplate = await promptSelectedTemplate(choices);
templateId = await promptSelectedTemplate(choices);
}

const hasSelectedTemplate = !!(selectedTemplate ?? null);
const hasSelectedTemplate = !!(templateId ?? null);
if (!hasSelectedTemplate) {
logger.error('Somehow we got no templates. Please rerun this command!');
return;
}

selectedConfig = TEMPLATES[selectedTemplate];
selectedConfig = TEMPLATES[templateId];

if (!selectedConfig) {
throw new Error('🚨 Sandbox: please specify a valid template type');
}
}

let selectedDirectory = outputDirectory;
const outputDirectoryName = outputDirectory || selectedTemplate;
const outputDirectoryName = outputDirectory || templateId;
if (selectedDirectory && existsSync(`${selectedDirectory}`)) {
logger.info(`⚠️ ${selectedDirectory} already exists! Overwriting...`);
}
Expand Down Expand Up @@ -146,32 +193,51 @@ export const sandbox = async ({
: path.join(process.cwd(), selectedDirectory);

logger.info(`🏃 Adding ${selectedConfig.name} into ${templateDestination}`);
logger.log(`📦 Downloading sandbox template (${chalk.bold(downloadType)})...`);

logger.log('📦 Downloading sandbox template...');
try {
const templateType = init ? 'after-storybook' : 'before-storybook';
// Download the sandbox based on subfolder "after-storybook" and selected branch
const gitPath = `github:storybookjs/sandboxes/${selectedTemplate}/${templateType}#${branch}`;
const gitPath = `github:storybookjs/sandboxes/${templateId}/${downloadType}#${branch}`;
await downloadTemplate(gitPath, {
force: true,
dir: templateDestination,
});
// throw an error if templateDestination is an empty directory using fs-extra
if ((await readdir(templateDestination)).length === 0) {
throw new Error(
dedent`Template downloaded from ${chalk.blue(gitPath)} is empty.
Are you use it exists? Or did you want to set ${chalk.yellow(
selectedTemplate
)} to inDevelopment first?`
const selected = chalk.yellow(templateId);
throw new Error(dedent`
Template downloaded from ${chalk.blue(gitPath)} is empty.
Are you use it exists? Or did you want to set ${selected} to inDevelopment first?
`);
}

// when user wanted an sandbox that has been initiated, but force-downloaded the before-storybook directory
// then we need to initiate the sandbox
// this is to ensure we DO get the latest version of the template (output of the generator), but we initialize using the version of storybook that the CLI is.
// we warned the user about the fact they are running an old version of storybook
// we warned the user the sandbox step would take longer
if ((isOutdated || isPrerelease) && init) {
// we run doInitiate, instead of initiate, to avoid sending this init event to telemetry, because it's not a real world project
const before = process.cwd();
process.chdir(templateDestination);
await doInitiate(
{
...options,
},
pkg
);
process.chdir(before);
}
} catch (err) {
logger.error(`🚨 Failed to download sandbox template: ${err.message}`);
throw err;
}

const initMessage = init
? chalk.yellow(`yarn install\nyarn storybook`)
? chalk.yellow(dedent`
yarn install
yarn storybook
`)
: `Recreate your setup, then ${chalk.yellow(`npx storybook@latest init`)}`;

logger.info(
Expand Down
Loading

0 comments on commit 5ac3a15

Please sign in to comment.