From aee82fe9ef65af92438de8733fbb843a782e0d6d Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 1 Oct 2026 09:22:45 +0200 Subject: [PATCH 1/2] Angular-Vite: Require @analogjs/vite-plugin-angular 2.7.3 or later Analog before 2.7.3 uses the pre-22.2 JavaScriptTransformer API, so on @angular/build 22.2 build-storybook fails with 'Hash utility must be initialized by awaiting initializeHash()'. Raise the peer floor and the version that init and the angular-to-angular-vite automigration install. Closes #36453 Co-Authored-By: Claude Opus 5.5 (1M context) --- code/core/src/cli/angular/helpers.ts | 4 ++-- code/frameworks/angular-vite/package.json | 2 +- yarn.lock | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/code/core/src/cli/angular/helpers.ts b/code/core/src/cli/angular/helpers.ts index 39a8eacc2651..9a00c6be5b3c 100644 --- a/code/core/src/cli/angular/helpers.ts +++ b/code/core/src/cli/angular/helpers.ts @@ -16,8 +16,8 @@ import semver from 'semver'; export const ANGULAR_JSON_PATH = 'angular.json'; -/** Must stay inside the `>=2.0.0` peer range that `@storybook/angular-vite` declares. */ -export const ANALOG_VITE_PLUGIN_ANGULAR_VERSION = '^2.5.2'; +/** Must stay inside the `>=2.7.3` peer range that `@storybook/angular-vite` declares. */ +export const ANALOG_VITE_PLUGIN_ANGULAR_VERSION = '^2.7.3'; export const toDevkitVersion = (ngRange?: string | null): string | undefined => { if (!ngRange) { diff --git a/code/frameworks/angular-vite/package.json b/code/frameworks/angular-vite/package.json index d52d203c4e57..1166ac9f5fe2 100644 --- a/code/frameworks/angular-vite/package.json +++ b/code/frameworks/angular-vite/package.json @@ -92,7 +92,7 @@ "zone.js": "^0.16.2" }, "peerDependencies": { - "@analogjs/vite-plugin-angular": ">=2.0.0", + "@analogjs/vite-plugin-angular": ">=2.7.3", "@angular-devkit/architect": ">=0.2100.0 < 0.2300.0", "@angular-devkit/core": ">=21.0.0 < 23.0.0", "@angular/build": ">=21.0.0 < 23.0.0", diff --git a/yarn.lock b/yarn.lock index b0536a27ed40..ca8d1bad6387 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10098,7 +10098,7 @@ __metadata: vite: "npm:>=8.0.0" zone.js: "npm:^0.16.2" peerDependencies: - "@analogjs/vite-plugin-angular": ">=2.0.0" + "@analogjs/vite-plugin-angular": ">=2.7.3" "@angular-devkit/architect": ">=0.2100.0 < 0.2300.0" "@angular-devkit/core": ">=21.0.0 < 23.0.0" "@angular/build": ">=21.0.0 < 23.0.0" From 31e22b0b4b568d2ae65c98caccf7b1dfc802e6ee Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 1 Oct 2026 09:53:01 +0200 Subject: [PATCH 2/2] build(sandbox): exempt Analog from the age gate in angular-vite templates @analogjs/vite-plugin-angular 2.7.3 is the first release compatible with @angular/build 22.2, but it clears the 7-day gate after Angular does. CI sandbox installs now honour each template's minAgeGateExemptions, so dependencies added by storybook init can be exempted too. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../cli-storybook/src/sandbox-templates.ts | 7 +++++- scripts/tasks/sandbox-parts.ts | 2 +- scripts/utils/preapprove-local-packages.ts | 11 ++++++--- scripts/utils/yarn.test.ts | 24 +++++++++++++++---- scripts/utils/yarn.ts | 9 +++++-- 5 files changed, 41 insertions(+), 12 deletions(-) diff --git a/code/lib/cli-storybook/src/sandbox-templates.ts b/code/lib/cli-storybook/src/sandbox-templates.ts index d4f49ac813e9..6be18b16b15a 100644 --- a/code/lib/cli-storybook/src/sandbox-templates.ts +++ b/code/lib/cli-storybook/src/sandbox-templates.ts @@ -121,7 +121,9 @@ export type Template = { * * The gate is enforced transitively, so this has to name the whole family of * packages published in lockstep with the prerelease, not just the direct - * dependency. Stable templates should leave this unset. + * dependency. Stable templates should leave this unset, unless a dependency + * must adopt a compatibility release as soon as a peer it tracks clears the + * gate. */ minAgeGateExemptions?: string[]; /** Additional options to pass to the initiate command when initializing Storybook. */ @@ -683,6 +685,7 @@ export const baseTemplates = { extraCiSteps: { ensureMinNodeVersion: true, }, + minAgeGateExemptions: ['@analogjs/vite-plugin-angular'], expected: { framework: '@storybook/angular-vite', renderer: '@storybook/angular-vite', @@ -715,6 +718,7 @@ export const baseTemplates = { extraCiSteps: { ensureMinNodeVersion: true, }, + minAgeGateExemptions: ['@analogjs/vite-plugin-angular'], expected: { framework: '@storybook/angular-vite', renderer: '@storybook/angular-vite', @@ -755,6 +759,7 @@ export const baseTemplates = { extraCiSteps: { ensureMinNodeVersion: true, }, + minAgeGateExemptions: ['@analogjs/vite-plugin-angular'], expected: { framework: '@storybook/angular-vite', renderer: '@storybook/angular-vite', diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index bdd04fc18f58..f486a1a9f6c1 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -239,7 +239,7 @@ export const create: Task['run'] = async ({ key, template, sandboxDir }, { dryRu export const install: Task['run'] = async ({ sandboxDir, key }, { link, dryRun, debug }) => { const cwd = sandboxDir; - await installYarn2({ cwd, dryRun, debug }); + await installYarn2({ cwd, dryRun, debug, key }); if (link) { await executeCLIStep(steps.link, { diff --git a/scripts/utils/preapprove-local-packages.ts b/scripts/utils/preapprove-local-packages.ts index 2d174055bc66..91a92163c0ff 100644 --- a/scripts/utils/preapprove-local-packages.ts +++ b/scripts/utils/preapprove-local-packages.ts @@ -23,9 +23,14 @@ export const LOCALLY_PUBLISHED_PACKAGE_PATTERNS = [ * quarantined. * * Merges with whatever is already allowed, so a template that names its own prerelease - * packages does not lose them. + * packages does not lose them. `extraPatterns` carries the template's `minAgeGateExemptions` + * for dependencies that only `storybook init` adds, which the published `.yarnrc.yml` cannot + * know about. */ -export async function preapproveLocallyPublishedPackages(cwd: string) { +export async function preapproveLocallyPublishedPackages( + cwd: string, + extraPatterns: string[] = [] +) { const configPath = join(cwd, '.yarnrc.yml'); let config: Record = {}; @@ -40,7 +45,7 @@ export async function preapproveLocallyPublishedPackages(cwd: string) { : []; config.npmPreapprovedPackages = Array.from( - new Set([...existing, ...LOCALLY_PUBLISHED_PACKAGE_PATTERNS]) + new Set([...existing, ...LOCALLY_PUBLISHED_PACKAGE_PATTERNS, ...extraPatterns]) ); await writeFile(configPath, yml.stringify(config)); diff --git a/scripts/utils/yarn.test.ts b/scripts/utils/yarn.test.ts index cfa713feb014..6dad9621fbb9 100644 --- a/scripts/utils/yarn.test.ts +++ b/scripts/utils/yarn.test.ts @@ -47,7 +47,7 @@ describe('installYarn2', () => { it('keeps the published lockfile instead of resolving from scratch', async () => { publishedSandbox(); - await installYarn2({ cwd: SANDBOX, dryRun: false, debug: false }); + await installYarn2({ cwd: SANDBOX, dryRun: false, debug: false, key: 'react-vite/default-ts' }); // Emptying this made every CI run re-resolve the whole tree against live npm. expect(vol.readFileSync(`${SANDBOX}/yarn.lock`, 'utf-8')).toContain('version: 4.1.2'); @@ -56,7 +56,7 @@ describe('installYarn2', () => { it('leaves the age gate in force', async () => { publishedSandbox(); - await installYarn2({ cwd: SANDBOX, dryRun: false, debug: false }); + await installYarn2({ cwd: SANDBOX, dryRun: false, debug: false, key: 'react-vite/default-ts' }); expect(readConfig().npmMinimalAgeGate).toBe(10080); expect(yarnCommands()).not.toContain('npmMinimalAgeGate 0'); @@ -65,7 +65,7 @@ describe('installYarn2', () => { it('preapproves the locally published Storybook packages instead', async () => { publishedSandbox(); - await installYarn2({ cwd: SANDBOX, dryRun: false, debug: false }); + await installYarn2({ cwd: SANDBOX, dryRun: false, debug: false, key: 'react-vite/default-ts' }); // They are published to Verdaccio seconds before this install, so they can never // satisfy the gate on their own. @@ -83,16 +83,30 @@ describe('installYarn2', () => { it('keeps a template allowlist rather than replacing it', async () => { publishedSandbox({ npmPreapprovedPackages: ['next', '@next/*'] }); - await installYarn2({ cwd: SANDBOX, dryRun: false, debug: false }); + await installYarn2({ cwd: SANDBOX, dryRun: false, debug: false, key: 'react-vite/default-ts' }); const approved: string[] = readConfig().npmPreapprovedPackages; expect(approved).toEqual(expect.arrayContaining(['next', '@next/*', 'storybook'])); }); + it('preapproves the template exemptions for dependencies that init adds', async () => { + publishedSandbox(); + + await installYarn2({ + cwd: SANDBOX, + dryRun: false, + debug: false, + key: 'angular-vite/default-ts', + }); + + // The published sandbox predates Storybook, so its `.yarnrc.yml` cannot name them. + expect(readConfig().npmPreapprovedPackages).toContain('@analogjs/vite-plugin-angular'); + }); + it('does not write a yarnPath next to the pinned packageManager', async () => { publishedSandbox(); - await installYarn2({ cwd: SANDBOX, dryRun: false, debug: false }); + await installYarn2({ cwd: SANDBOX, dryRun: false, debug: false, key: 'react-vite/default-ts' }); // corepack aborts when `yarnPath` and `packageManager` disagree. expect(yarnCommands()).not.toContain('yarn set version'); diff --git a/scripts/utils/yarn.ts b/scripts/utils/yarn.ts index 8ff03717ad66..f550d3c879a8 100644 --- a/scripts/utils/yarn.ts +++ b/scripts/utils/yarn.ts @@ -37,7 +37,12 @@ export const addPackageResolutions = async ({ cwd, dryRun }: YarnOptions) => { await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); }; -export const installYarn2 = async ({ cwd, dryRun, debug }: YarnOptions) => { +export const installYarn2 = async ({ + cwd, + dryRun, + debug, + key, +}: YarnOptions & { key: AllTemplatesKey }) => { await mkdir(cwd, { recursive: true }); // The published sandbox ships a lockfile and a `.yarnrc.yml` carrying the age gate. @@ -47,7 +52,7 @@ export const installYarn2 = async ({ cwd, dryRun, debug }: YarnOptions) => { // Our own Storybook packages are published to Verdaccio seconds before this install, // so they can never satisfy the gate. Name them instead of switching it off, exactly // as sandbox generation does for the `after-storybook` install. - await preapproveLocallyPublishedPackages(cwd); + await preapproveLocallyPublishedPackages(cwd, allTemplates[key].minAgeGateExemptions); const command = [ // No `yarn set version` here: the sandbox pins Yarn through the `packageManager`