Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI: Versioned upgrade of monorepo packages (main) #25573

Merged
merged 13 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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