Skip to content

Commit

Permalink
Merge pull request #22997 from storybookjs/fix-release-publish
Browse files Browse the repository at this point in the history
Release: Rework versioning command
  • Loading branch information
JReinhold committed Jun 9, 2023
2 parents 3205ba3 + 95e344a commit ade5322
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 66 deletions.
9 changes: 3 additions & 6 deletions .github/workflows/prepare-patch-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ jobs:
yarn-v1-${{ hashFiles('scripts/yarn.lock') }}
yarn-v1
- name: Install Script Dependencies
- name: Install Dependencies
working-directory: .
run: |
yarn install
yarn task --task=install
- name: Check if pull request is frozen
if: github.event_name != 'workflow_dispatch'
Expand Down Expand Up @@ -85,10 +86,6 @@ jobs:
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
yarn release:pick-patches
- name: Install code dependencies
working-directory: .
run: yarn task --task=install --start-from=install

- name: Bump version
id: bump-version
if: steps.unreleased-changes.outputs.has-changes-to-release == 'true'
Expand Down
9 changes: 3 additions & 6 deletions .github/workflows/prepare-prerelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ jobs:
yarn-v1-${{ hashFiles('scripts/yarn.lock') }}
yarn-v1
- name: Install Script Dependencies
- name: Install Dependencies
working-directory: .
run: |
yarn install
yarn task --task=install
- name: Check if pull request is frozen
if: github.event_name != 'workflow_dispatch'
Expand Down Expand Up @@ -104,10 +105,6 @@ jobs:
gh run cancel ${{ github.run_id }}
gh run watch ${{ github.run_id }}
- name: Install code dependencies
working-directory: .
run: yarn task --task=install --start-from=install

- name: Bump version
id: bump-version
run: |
Expand Down
4 changes: 0 additions & 4 deletions code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -325,10 +325,6 @@
[
"dependencies",
"Dependency Upgrades"
],
[
"other",
"Other"
]
]
}
Expand Down
71 changes: 63 additions & 8 deletions scripts/release/__tests__/version.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,21 @@ jest.mock('../../../code/lib/cli/src/versions', () => ({
jest.mock('../../utils/exec');
const { execaCommand } = require('../../utils/exec');

jest.mock('../../utils/workspace', () => ({
getWorkspaces: jest.fn().mockResolvedValue([
{
name: '@storybook/addon-a11y',
location: 'addons/a11y',
},
]),
}));

jest.spyOn(console, 'log').mockImplementation(() => {});
jest.spyOn(console, 'warn').mockImplementation(() => {});
jest.spyOn(console, 'error').mockImplementation(() => {});

beforeEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});

describe('Version', () => {
Expand All @@ -29,6 +42,7 @@ describe('Version', () => {
'version.ts'
);
const VERSIONS_PATH = path.join(CODE_DIR_PATH, 'lib', 'cli', 'src', 'versions.ts');
const A11Y_PACKAGE_JSON_PATH = path.join(CODE_DIR_PATH, 'addons', 'a11y', 'package.json');

it('should throw when release type is invalid', async () => {
fsExtra.__setMockFiles({
Expand Down Expand Up @@ -152,6 +166,23 @@ describe('Version', () => {
[CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: currentVersion }),
[MANAGER_API_VERSION_PATH]: `export const version = "${currentVersion}";`,
[VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "${currentVersion}" };`,
[A11Y_PACKAGE_JSON_PATH]: JSON.stringify({
version: currentVersion,
dependencies: {
'@storybook/core-server': currentVersion,
'unrelated-package-a': '1.0.0',
},
devDependencies: {
'unrelated-package-b': currentVersion,
'@storybook/core-common': `^${currentVersion}`,
},
peerDependencies: {
'@storybook/preview-api': `*`,
'@storybook/svelte': '0.1.1',
'@storybook/manager-api': `~${currentVersion}`,
},
}),
[VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "${currentVersion}" };`,
});

await version({ releaseType, preId, exact });
Expand All @@ -161,19 +192,43 @@ describe('Version', () => {
{ version: expectedVersion },
{ spaces: 2 }
);
expect(fsExtra.writeFile).toHaveBeenCalledWith(
path.join(CODE_DIR_PATH, '.yarn', 'versions', 'generated-by-versions-script.yml'),
expect.stringContaining(expectedVersion)
);
expect(execaCommand).toHaveBeenCalledWith('yarn version apply --all', { cwd: CODE_DIR_PATH });
expect(fsExtra.writeFile).toHaveBeenCalledWith(
MANAGER_API_VERSION_PATH,
expect.stringContaining(expectedVersion)
`export const version = "${expectedVersion}";`
);
expect(fsExtra.writeFile).toHaveBeenCalledWith(
VERSIONS_PATH,
expect.stringContaining(expectedVersion)
`export default { "@storybook/addon-a11y": "${expectedVersion}" };`
);
expect(fsExtra.writeJson).toHaveBeenCalledWith(
A11Y_PACKAGE_JSON_PATH,
expect.objectContaining({
// should update package version
version: expectedVersion,
dependencies: {
// should update storybook dependencies matching current version
'@storybook/core-server': expectedVersion,
'unrelated-package-a': '1.0.0',
},
devDependencies: {
// should not update non-storybook dependencies, even if they match current version
'unrelated-package-b': currentVersion,
// should update dependencies with range modifiers correctly (e.g. ^1.0.0 -> ^2.0.0)
'@storybook/core-common': `^${expectedVersion}`,
},
peerDependencies: {
// should not update storybook depenedencies if they don't match current version
'@storybook/preview-api': `*`,
'@storybook/svelte': '0.1.1',
'@storybook/manager-api': `~${expectedVersion}`,
},
}),
{ spaces: 2 }
);
expect(execaCommand).toHaveBeenCalledWith('yarn install --mode=update-lockfile', {
cwd: path.join(CODE_DIR_PATH),
stdio: undefined,
});
}
);
});
1 change: 1 addition & 0 deletions scripts/release/generate-pr-description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const LABELS_BY_IMPORTANCE = {
'feature request': '✨ Feature Request',
bug: '🐛 Bug',
maintenance: '🔧 Maintenance',
dependencies: '📦 Dependencies',
documentation: '📝 Documentation',
build: '🏗️ Build',
unknown: '❔ Missing Label',
Expand Down
124 changes: 83 additions & 41 deletions scripts/release/version.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
/* eslint-disable no-console */
import { setOutput } from '@actions/core';
import { ensureDir, readFile, readJson, writeFile, writeJson } from 'fs-extra';
import { readFile, readJson, writeFile, writeJson } from 'fs-extra';
import chalk from 'chalk';
import path from 'path';
import program from 'commander';
import semver from 'semver';
import { z } from 'zod';
import dedent from 'ts-dedent';
import type { Workspace } from '../utils/workspace';
import { getWorkspaces } from '../utils/workspace';
import { execaCommand } from '../utils/exec';
import { listOfPackages } from '../utils/list-packages';
import packageVersionMap from '../../code/lib/cli/src/versions';

program
.name('version')
Expand Down Expand Up @@ -98,39 +97,6 @@ const bumpCodeVersion = async (nextVersion: string) => {
const bumpAllPackageVersions = async (nextVersion: string, verbose?: boolean) => {
console.log(`🤜 Bumping version of ${chalk.cyan('all packages')}...`);

/**
* This uses the release workflow outlined by Yarn documentation here:
* https://yarnpkg.com/features/release-workflow
*
* However we build the release YAML file manually instead of using the `yarn version --deferred` command
* This is super hacky, but it's also way faster than invoking `yarn version` for each package, which is 1s each
*
* A simpler alternative is to use Lerna with:
* await execaCommand(`yarn lerna version ${nextVersion} --no-git-tag-version --exact`, {
* cwd: CODE_DIR_PATH,
* stdio: verbose ? 'inherit' : undefined,
* });
* However that doesn't update peer deps. Trade offs
*/
const yarnVersionsPath = path.join(__dirname, '..', '..', 'code', '.yarn', 'versions');
let yarnDefferedVersionFileContents = dedent`# this file is auto-generated by scripts/release/version.ts
releases:
`;
Object.keys(packageVersionMap).forEach((packageName) => {
yarnDefferedVersionFileContents += ` '${packageName}': ${nextVersion}\n`;
});
await ensureDir(yarnVersionsPath);
await writeFile(
path.join(yarnVersionsPath, 'generated-by-versions-script.yml'),
yarnDefferedVersionFileContents
);

await execaCommand('yarn version apply --all', {
cwd: CODE_DIR_PATH,
stdio: verbose ? 'inherit' : undefined,
});

console.log(`✅ Bumped version of ${chalk.cyan('all packages')}`);
};

Expand All @@ -152,6 +118,76 @@ const bumpVersionSources = async (currentVersion: string, nextVersion: string) =
console.log(`✅ Bumped versions in:\n ${chalk.cyan(filesToUpdate.join('\n '))}`);
};

const bumpAllPackageJsons = async ({
packages,
currentVersion,
nextVersion,
verbose,
}: {
packages: Workspace[];
currentVersion: string;
nextVersion: string;
verbose?: boolean;
}) => {
console.log(
`🤜 Bumping versions and dependencies in ${chalk.cyan(
`all ${packages.length} package.json`
)}'s...`
);
// 1. go through all packages in the monorepo
await Promise.all(
packages.map(async (pkg) => {
// 2. get the package.json
const packageJsonPath = path.join(CODE_DIR_PATH, pkg.location, 'package.json');
const packageJson: {
version: string;
dependencies: Record<string, string>;
devDependencies: Record<string, string>;
peerDependencies: Record<string, string>;
[key: string]: any;
} = await readJson(packageJsonPath);
// 3. bump the version
packageJson.version = nextVersion;
const { dependencies, devDependencies, peerDependencies } = packageJson;
if (verbose) {
console.log(
` Bumping ${chalk.blue(pkg.name)}'s version to ${chalk.yellow(nextVersion)}`
);
}
// 4. go through all deps in the package.json
Object.entries({ dependencies, devDependencies, peerDependencies }).forEach(
([depType, deps]) => {
if (!deps) {
return;
}
// 5. find all storybook deps
Object.entries(deps)
.filter(
([depName, depVersion]) =>
depName.startsWith('@storybook/') &&
// ignore storybook dependneices that don't use the current version
depVersion.includes(currentVersion)
)
.forEach(([depName, depVersion]) => {
// 6. bump the version of any found storybook dep
const nextDepVersion = depVersion.replace(currentVersion, nextVersion);
if (verbose) {
console.log(
` Bumping ${chalk.blue(pkg.name)}'s ${chalk.red(depType)} on ${chalk.green(
depName
)} from ${chalk.yellow(depVersion)} to ${chalk.yellow(nextDepVersion)}`
);
}
packageJson[depType][depName] = nextDepVersion;
});
}
);
await writeJson(packageJsonPath, packageJson, { spaces: 2 });
})
);
console.log(`✅ Bumped peer dependency versions in ${chalk.cyan('all packages')}`);
};

export const run = async (options: unknown) => {
if (!validateOptions(options)) {
return;
Expand All @@ -160,15 +196,14 @@ export const run = async (options: unknown) => {

console.log(`🚛 Finding Storybook packages...`);

const [packages, currentVersion] = await Promise.all([listOfPackages(), getCurrentVersion()]);
const [packages, currentVersion] = await Promise.all([getWorkspaces(), getCurrentVersion()]);

console.log(
`📦 found ${packages.length} storybook packages at version ${chalk.red(currentVersion)}`
);
if (verbose) {
const formattedPackages = packages.map(
(pkg) =>
`${chalk.green(pkg.name.padEnd(60))}${chalk.red(pkg.version)}: ${chalk.cyan(pkg.location)}`
(pkg) => `${chalk.green(pkg.name.padEnd(60))}: ${chalk.cyan(pkg.location)}`
);
console.log(`📦 Packages:
${formattedPackages.join('\n ')}`);
Expand Down Expand Up @@ -200,8 +235,15 @@ export const run = async (options: unknown) => {
console.log(`⏭ Bumping all packages to ${chalk.blue(nextVersion)}...`);

await bumpCodeVersion(nextVersion);
await bumpAllPackageVersions(nextVersion, verbose);
await bumpVersionSources(currentVersion, nextVersion);
await bumpAllPackageJsons({ packages, currentVersion, nextVersion, verbose });

console.log(`⬆️ Updating lock file with ${chalk.blue('yarn install --mode=update-lockfile')}`);
await execaCommand(`yarn install --mode=update-lockfile`, {
cwd: path.join(CODE_DIR_PATH),
stdio: verbose ? 'inherit' : undefined,
});
console.log(`✅ Updated lock file with ${chalk.blue('yarn install --mode=update-lockfile')}`);

if (process.env.GITHUB_ACTIONS === 'true') {
setOutput('current-version', currentVersion);
Expand Down
2 changes: 1 addition & 1 deletion scripts/utils/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { execaCommand } from './exec';

export type Workspace = { name: string; location: string };

async function getWorkspaces() {
export async function getWorkspaces() {
const { stdout } = await execaCommand('yarn workspaces list --json', {
cwd: CODE_DIRECTORY,
shell: true,
Expand Down

0 comments on commit ade5322

Please sign in to comment.