From e6470d88b36c115706fcc81bc77bab505f73ffdf Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Mon, 16 Mar 2026 10:23:41 +0100 Subject: [PATCH 1/7] Prevent story-local viewport from persisting in URL by only syncing user globals rather than all globals --- code/core/src/manager-api/modules/globals.ts | 12 ++++---- .../src/manager-api/tests/globals.test.ts | 28 ++++++++++++++++++- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/code/core/src/manager-api/modules/globals.ts b/code/core/src/manager-api/modules/globals.ts index 4c706a19ee28..d27a33da8ce3 100644 --- a/code/core/src/manager-api/modules/globals.ts +++ b/code/core/src/manager-api/modules/globals.ts @@ -130,7 +130,7 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) = SET_GLOBALS, function handleSetGlobals(this: any, { globals, globalTypes }: SetGlobalsPayload) { const { ref } = getEventMetadata(this, fullAPI)!; - const currentGlobals = store.getState()?.globals; + const currentUserGlobals = store.getState()?.userGlobals; if (!ref) { store.setState({ globals, userGlobals: globals, globalTypes }); @@ -138,14 +138,12 @@ export const init: ModuleFn = ({ store, fullAPI, provider }) = logger.warn('received globals from a non-local ref. This is not currently supported.'); } - // If we have stored globals different to what the preview just inited with, - // we should update it to those values if ( - currentGlobals && - Object.keys(currentGlobals).length !== 0 && - !deepEqual(globals, currentGlobals) + currentUserGlobals && + Object.keys(currentUserGlobals).length !== 0 && + !deepEqual(globals, currentUserGlobals) ) { - api.updateGlobals(currentGlobals); + api.updateGlobals(currentUserGlobals); } } ); diff --git a/code/core/src/manager-api/tests/globals.test.ts b/code/core/src/manager-api/tests/globals.test.ts index 829e771ae4d8..264df9d676e2 100644 --- a/code/core/src/manager-api/tests/globals.test.ts +++ b/code/core/src/manager-api/tests/globals.test.ts @@ -71,7 +71,7 @@ describe('globals API', () => { }); }); - it('emits UPDATE_GLOBALS if retains a globals value different to what receives on SET_GLOBALS', () => { + it('emits UPDATE_GLOBALS if retains a user globals value different to what receives on SET_GLOBALS', () => { const channel = new EventEmitter(); const listener = vi.fn(); channel.on(UPDATE_GLOBALS, listener); @@ -83,6 +83,7 @@ describe('globals API', () => { } as unknown as ModuleArgs); store.setState({ ...state, + userGlobals: { a: 'c' }, globals: { a: 'c' }, }); @@ -103,6 +104,31 @@ describe('globals API', () => { }); }); + it('does not push story globals to preview when SET_GLOBALS fires with empty globals', () => { + const channel = new EventEmitter(); + const listener = vi.fn(); + channel.on(UPDATE_GLOBALS, listener); + + const store = createMockStore(); + const { state } = initModule({ + store, + provider: { channel }, + } as unknown as ModuleArgs); + store.setState({ + ...state, + userGlobals: {}, + storyGlobals: { viewport: 'mobile1' }, + globals: { viewport: 'mobile1' }, + }); + + channel.emit(SET_GLOBALS, { + globals: {}, + globalTypes: {}, + } satisfies SetGlobalsPayload); + + expect(listener).not.toHaveBeenCalled(); + }); + it('ignores SET_STORIES from other refs', () => { const channel = new EventEmitter(); const api = { findRef: vi.fn() }; From c56cda2170e3104dd978f8cb0cc49c7cc155bfc3 Mon Sep 17 00:00:00 2001 From: Maks Pikov Date: Mon, 16 Mar 2026 22:20:39 +0000 Subject: [PATCH 2/7] chore: remove dead-code copy of wrap-getAbsolutePath-utils --- .../fixes/wrap-getAbsolutePath-utils.ts | 216 ------------------ 1 file changed, 216 deletions(-) delete mode 100644 code/lib/cli-storybook/src/automigrate/fixes/wrap-getAbsolutePath-utils.ts diff --git a/code/lib/cli-storybook/src/automigrate/fixes/wrap-getAbsolutePath-utils.ts b/code/lib/cli-storybook/src/automigrate/fixes/wrap-getAbsolutePath-utils.ts deleted file mode 100644 index 2d14ac1d4302..000000000000 --- a/code/lib/cli-storybook/src/automigrate/fixes/wrap-getAbsolutePath-utils.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { types as t } from 'storybook/internal/babel'; -import type { ConfigFile } from 'storybook/internal/csf-tools'; - -const PREFERRED_GET_ABSOLUTE_PATH_WRAPPER_NAME = 'getAbsolutePath'; -// TODO: Remove in SB11 -const ALTERNATIVE_GET_ABSOLUTE_PATH_WRAPPER_NAME = 'wrapForPnp'; - -/** - * Checks if the following node declarations exists in the main config file. - * - * @example - * - * ```ts - * const = () => {}; - * function () {} - * ``` - */ -export function doesVariableOrFunctionDeclarationExist(node: t.Node, name: string) { - return ( - (t.isVariableDeclaration(node) && - node.declarations.length === 1 && - t.isVariableDeclarator(node.declarations[0]) && - t.isIdentifier(node.declarations[0].id) && - node.declarations[0].id?.name === name) || - (t.isFunctionDeclaration(node) && t.isIdentifier(node.id) && node.id.name === name) - ); -} - -/** - * Wrap a value with getAbsolutePath wrapper. - * - * @example - * - * ```ts - * // Before - * { - * framework: '@storybook/react-vite'; - * } - * - * // After - * { - * framework: getAbsolutePath('@storybook/react-vite'); - * } - * ``` - */ -function getReferenceToGetAbsolutePathWrapper(config: ConfigFile, value: string) { - return t.callExpression( - t.identifier(getAbsolutePathWrapperName(config) ?? PREFERRED_GET_ABSOLUTE_PATH_WRAPPER_NAME), - [t.stringLiteral(value)] - ); -} - -/** - * Returns the name of the getAbsolutePath wrapper function if it exists in the main config file. - * - * @returns Name of the getAbsolutePath wrapper function (e.g. `getAbsolutePath`). - */ -export function getAbsolutePathWrapperName(config: ConfigFile) { - const declarationName = config - .getBodyDeclarations() - .flatMap((node) => - doesVariableOrFunctionDeclarationExist(node, ALTERNATIVE_GET_ABSOLUTE_PATH_WRAPPER_NAME) - ? [ALTERNATIVE_GET_ABSOLUTE_PATH_WRAPPER_NAME] - : doesVariableOrFunctionDeclarationExist(node, PREFERRED_GET_ABSOLUTE_PATH_WRAPPER_NAME) - ? [PREFERRED_GET_ABSOLUTE_PATH_WRAPPER_NAME] - : [] - ); - - if (declarationName.length) { - return declarationName[0]; - } - - return null; -} - -/** Check if the node needs to be wrapped with getAbsolutePath wrapper. */ -export function isGetAbsolutePathWrapperNecessary( - node: t.Node, - cb: (node: t.StringLiteral | t.ObjectProperty | t.ArrayExpression) => void = () => {} -) { - if (t.isStringLiteral(node)) { - // value will be converted from StringLiteral to CallExpression. - cb(node); - return true; - } - - if (t.isObjectExpression(node)) { - const nameProperty = node.properties.find( - (property) => - t.isObjectProperty(property) && t.isIdentifier(property.key) && property.key.name === 'name' - ) as t.ObjectProperty; - - if (nameProperty && t.isStringLiteral(nameProperty.value)) { - cb(nameProperty); - return true; - } - } - - if ( - t.isArrayExpression(node) && - node.elements.some((element) => element && isGetAbsolutePathWrapperNecessary(element)) - ) { - cb(node); - return true; - } - - return false; -} - -/** - * Get all fields that need to be wrapped with getAbsolutePath wrapper. - * - * @returns Array of fields that need to be wrapped with getAbsolutePath wrapper. - */ -export function getFieldsForGetAbsolutePathWrapper(config: ConfigFile): t.Node[] { - const frameworkNode = config.getFieldNode(['framework']); - const builderNode = config.getFieldNode(['core', 'builder']); - const rendererNode = config.getFieldNode(['core', 'renderer']); - const addons = config.getFieldNode(['addons']); - - const fieldsWithRequireWrapper = [ - ...(frameworkNode ? [frameworkNode] : []), - ...(builderNode ? [builderNode] : []), - ...(rendererNode ? [rendererNode] : []), - ...(addons && t.isArrayExpression(addons) ? [addons] : []), - ]; - - return fieldsWithRequireWrapper; -} - -/** - * Returns AST for the following function - * - * @example - * - * ```ts - * function getAbsolutePath(value) { - * return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`))); - * } - * ``` - */ -export function getAbsolutePathWrapperAsCallExpression( - isConfigTypescript: boolean -): t.FunctionDeclaration { - const functionDeclaration = { - ...t.functionDeclaration( - t.identifier(PREFERRED_GET_ABSOLUTE_PATH_WRAPPER_NAME), - [ - { - ...t.identifier('value'), - ...(isConfigTypescript - ? { typeAnnotation: t.tsTypeAnnotation(t.tSStringKeyword()) } - : {}), - }, - ], - t.blockStatement([ - t.returnStatement( - t.callExpression(t.identifier('dirname'), [ - t.callExpression(t.identifier('fileURLToPath'), [ - t.callExpression( - t.memberExpression( - t.metaProperty(t.identifier('import'), t.identifier('meta')), - t.identifier('resolve') - ), - [ - t.templateLiteral( - [ - t.templateElement({ raw: '' }), - t.templateElement({ raw: '/package.json' }, true), - ], - [t.identifier('value')] - ), - ] - ), - ]), - ]) - ), - ]) - ), - ...(isConfigTypescript ? { returnType: t.tSTypeAnnotation(t.tsAnyKeyword()) } : {}), - }; - - t.addComment( - functionDeclaration, - 'leading', - '*\n * This function is used to resolve the absolute path of a package.\n * It is needed in projects that use Yarn PnP or are set up within a monorepo.\n' - ); - - return functionDeclaration; -} - -export function wrapValueWithGetAbsolutePathWrapper(config: ConfigFile, node: t.Node) { - isGetAbsolutePathWrapperNecessary(node, (n) => { - if (t.isStringLiteral(n)) { - const wrapperNode = getReferenceToGetAbsolutePathWrapper(config, n.value); - Object.keys(n).forEach((k) => { - delete n[k as keyof typeof n]; - }); - Object.keys(wrapperNode).forEach((k) => { - (n as any)[k] = wrapperNode[k as keyof typeof wrapperNode]; - }); - } - - if (t.isObjectProperty(n) && t.isStringLiteral(n.value)) { - n.value = getReferenceToGetAbsolutePathWrapper(config, n.value.value) as any; - } - - if (t.isArrayExpression(n)) { - n.elements.forEach((element) => { - if (element && isGetAbsolutePathWrapperNecessary(element)) { - wrapValueWithGetAbsolutePathWrapper(config, element); - } - }); - } - }); -} From 6677dc79893897d5daa6ad6acd6b9159d2d70ad9 Mon Sep 17 00:00:00 2001 From: Leonardo Stenico Date: Mon, 16 Mar 2026 23:47:24 +0100 Subject: [PATCH 3/7] chore: Makes sure `serialize-javascript` is at latest version - Remove vulnerable serialize-javascript ^3.1.0 from root package.json - Upgrade terser-webpack-plugin to ^5.3.17 (brings serialize-javascript 6.x) - Update WebpackConfiguration devtool type for compatibility - Update existing-resolutions.js Made-with: Cursor --- code/builders/builder-webpack5/package.json | 2 +- code/lib/core-webpack/src/types.ts | 2 +- package.json | 1 - scripts/ecosystem-ci/existing-resolutions.js | 1 - yarn.lock | 33 ++++++++++++++++---- 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index c9f494c25b02..030056df200a 100644 --- a/code/builders/builder-webpack5/package.json +++ b/code/builders/builder-webpack5/package.json @@ -61,7 +61,7 @@ "html-webpack-plugin": "^5.5.0", "magic-string": "^0.30.5", "style-loader": "^4.0.0", - "terser-webpack-plugin": "^5.3.14", + "terser-webpack-plugin": "^5.3.17", "ts-dedent": "^2.0.0", "webpack": "5", "webpack-dev-middleware": "^6.1.2", diff --git a/code/lib/core-webpack/src/types.ts b/code/lib/core-webpack/src/types.ts index d05caa081c96..c02a1a416867 100644 --- a/code/lib/core-webpack/src/types.ts +++ b/code/lib/core-webpack/src/types.ts @@ -19,7 +19,7 @@ export interface WebpackConfiguration { module?: ModuleConfig; resolve?: ResolveConfig; optimization?: any; - devtool?: false | string; + devtool?: string | false | { type: "all" | "javascript" | "css"; use: string | false }[]; } export type BuilderOptions = { diff --git a/package.json b/package.json index 00526f3ff9e2..74dfd9dc4b11 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "playwright": "1.58.2", "playwright-core": "1.58.2", "react": "^18.2.0", - "serialize-javascript": "^3.1.0", "type-fest": "~2.19", "typescript": "^5.9.3" }, diff --git a/scripts/ecosystem-ci/existing-resolutions.js b/scripts/ecosystem-ci/existing-resolutions.js index 604f017f90c9..730972ec8982 100644 --- a/scripts/ecosystem-ci/existing-resolutions.js +++ b/scripts/ecosystem-ci/existing-resolutions.js @@ -23,7 +23,6 @@ export const EXISTING_RESOLUTIONS = new Set([ 'playwright', 'playwright-core', 'react', - 'serialize-javascript', 'type-fest', 'typescript', ]); diff --git a/yarn.lock b/yarn.lock index 728a4a89bdc6..66a521e7d931 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7865,7 +7865,7 @@ __metadata: sirv: "npm:^2.0.4" slash: "npm:^5.0.0" style-loader: "npm:^4.0.0" - terser-webpack-plugin: "npm:^5.3.14" + terser-webpack-plugin: "npm:^5.3.17" ts-dedent: "npm:^2.0.0" typescript: "npm:^5.9.3" webpack: "npm:5" @@ -27413,12 +27413,12 @@ __metadata: languageName: node linkType: hard -"serialize-javascript@npm:^3.1.0": - version: 3.1.0 - resolution: "serialize-javascript@npm:3.1.0" +"serialize-javascript@npm:^6.0.2": + version: 6.0.2 + resolution: "serialize-javascript@npm:6.0.2" dependencies: randombytes: "npm:^2.1.0" - checksum: 10c0/ca8a6bb29891e524e36451d685827ab7e7f50171e5aebe99504d07ae97308a9faa880e0df0517d75ed8efb09991454eb8cb969cecfad82478fc0591938a3909c + checksum: 10c0/2dd09ef4b65a1289ba24a788b1423a035581bef60817bea1f01eda8e3bda623f86357665fe7ac1b50f6d4f583f97db9615b3f07b2a2e8cbcb75033965f771dd2 languageName: node linkType: hard @@ -29123,7 +29123,7 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:^5.3.11, terser-webpack-plugin@npm:^5.3.14": +"terser-webpack-plugin@npm:^5.3.11": version: 5.3.14 resolution: "terser-webpack-plugin@npm:5.3.14" dependencies: @@ -29145,6 +29145,27 @@ __metadata: languageName: node linkType: hard +"terser-webpack-plugin@npm:^5.3.17": + version: 5.4.0 + resolution: "terser-webpack-plugin@npm:5.4.0" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.25" + jest-worker: "npm:^27.4.5" + schema-utils: "npm:^4.3.0" + terser: "npm:^5.31.1" + peerDependencies: + webpack: ^5.1.0 + peerDependenciesMeta: + "@swc/core": + optional: true + esbuild: + optional: true + uglify-js: + optional: true + checksum: 10c0/1feed4b9575af795dae6af0c8f0d76d6e1fb7b357b8628d90e834c23a651b918a58cdc48d0ae6c1f0581f74bc8169b33c3b8d049f2d2190bac4e310964e59fde + languageName: node + linkType: hard + "terser@npm:5.39.0": version: 5.39.0 resolution: "terser@npm:5.39.0" From 76c41b7707d3c1619ecca6534f54d58725d56a0b Mon Sep 17 00:00:00 2001 From: Leonardo Stenico Date: Tue, 17 Mar 2026 00:09:39 +0100 Subject: [PATCH 4/7] Dedupe --- yarn.lock | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/yarn.lock b/yarn.lock index 66a521e7d931..87ddc3986b00 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29123,29 +29123,7 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:^5.3.11": - version: 5.3.14 - resolution: "terser-webpack-plugin@npm:5.3.14" - dependencies: - "@jridgewell/trace-mapping": "npm:^0.3.25" - jest-worker: "npm:^27.4.5" - schema-utils: "npm:^4.3.0" - serialize-javascript: "npm:^6.0.2" - terser: "npm:^5.31.1" - peerDependencies: - webpack: ^5.1.0 - peerDependenciesMeta: - "@swc/core": - optional: true - esbuild: - optional: true - uglify-js: - optional: true - checksum: 10c0/9b060947241af43bd6fd728456f60e646186aef492163672a35ad49be6fbc7f63b54a7356c3f6ff40a8f83f00a977edc26f044b8e106cc611c053c8c0eaf8569 - languageName: node - linkType: hard - -"terser-webpack-plugin@npm:^5.3.17": +"terser-webpack-plugin@npm:^5.3.11, terser-webpack-plugin@npm:^5.3.17": version: 5.4.0 resolution: "terser-webpack-plugin@npm:5.4.0" dependencies: From 5c739b6fef5988c361267e661c8c4590a4531331 Mon Sep 17 00:00:00 2001 From: yannbf Date: Thu, 19 Mar 2026 10:50:27 +0100 Subject: [PATCH 5/7] CLI: use npm info to fetch versions in repro command --- code/lib/cli-storybook/src/sandbox.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/lib/cli-storybook/src/sandbox.ts b/code/lib/cli-storybook/src/sandbox.ts index 5bfcc854048f..003841ea8bbd 100644 --- a/code/lib/cli-storybook/src/sandbox.ts +++ b/code/lib/cli-storybook/src/sandbox.ts @@ -2,7 +2,7 @@ import { existsSync } from 'node:fs'; import { mkdir, readdir, rm } from 'node:fs/promises'; import { isAbsolute } from 'node:path'; -import type { PackageManagerName } from 'storybook/internal/common'; +import { PackageManagerName } from 'storybook/internal/common'; import { JsPackageManagerFactory, isCI, @@ -42,10 +42,10 @@ export const sandbox = async ({ let selectedConfig: Template | undefined = TEMPLATES[filterValue as TemplateKey]; let templateId: Choice | null = selectedConfig ? (filterValue as TemplateKey) : null; - const { packageManager: pkgMgr } = options; - + // Always use npm to fetch versions, as other package manager commands may fail when running in + // non-project directories (e.g. parent sandbox directory). We just need to use npm info for this use case. const packageManager = JsPackageManagerFactory.getPackageManager({ - force: pkgMgr, + force: PackageManagerName.NPM, }); const latestVersion = (await packageManager.latestVersion('storybook'))!; const nextVersion = (await packageManager.latestVersion('storybook@next')) ?? '0.0.0'; From 7f19dd606c6ce12bfe9a78567be192529cb720a0 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Thu, 19 Mar 2026 12:26:09 +0100 Subject: [PATCH 6/7] Reverting devtool change to focus on the main change --- code/lib/core-webpack/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/lib/core-webpack/src/types.ts b/code/lib/core-webpack/src/types.ts index c02a1a416867..d05caa081c96 100644 --- a/code/lib/core-webpack/src/types.ts +++ b/code/lib/core-webpack/src/types.ts @@ -19,7 +19,7 @@ export interface WebpackConfiguration { module?: ModuleConfig; resolve?: ResolveConfig; optimization?: any; - devtool?: string | false | { type: "all" | "javascript" | "css"; use: string | false }[]; + devtool?: false | string; } export type BuilderOptions = { From fdfdd9bb1750bbeb5592320d85d8fbb14eb42d3b Mon Sep 17 00:00:00 2001 From: storybook-bot <32066757+storybook-bot@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:17:55 +0000 Subject: [PATCH 7/7] Write changelog for 10.4.0-alpha.2 [skip ci] --- CHANGELOG.prerelease.md | 7 +++++++ code/package.json | 3 ++- docs/versions/next.json | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 6280affeb4b3..4a9dd489a3d3 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,10 @@ +## 10.4.0-alpha.2 + +- CLI: Use npm info to fetch versions in repro command - [#34214](https://github.com/storybookjs/storybook/pull/34214), thanks @yannbf! +- Core: Prevent story-local viewport from persisting in URL - [#34153](https://github.com/storybookjs/storybook/pull/34153), thanks @ghengeveld! +- Maintenance: Remove dead-code copy of wrap-getAbsolutePath-utils - [#34168](https://github.com/storybookjs/storybook/pull/34168), thanks @mixelburg! +- Security: Makes sure `serialize-javascript` is at latest version - [#34034](https://github.com/storybookjs/storybook/pull/34034), thanks @50bbx! + ## 10.4.0-alpha.1 - Docs: Ensure unique control id attributes across multiple Controls blocks - [#34021](https://github.com/storybookjs/storybook/pull/34021), thanks @TheSeydiCharyyev! diff --git a/code/package.json b/code/package.json index 9494005527c9..d574dc34d0cd 100644 --- a/code/package.json +++ b/code/package.json @@ -209,5 +209,6 @@ "Dependency Upgrades" ] ] - } + }, + "deferredNextVersion": "10.4.0-alpha.2" } diff --git a/docs/versions/next.json b/docs/versions/next.json index 14e663050a09..59bb1f6dd62c 100644 --- a/docs/versions/next.json +++ b/docs/versions/next.json @@ -1 +1 @@ -{"version":"10.4.0-alpha.1","info":{"plain":"- Docs: Ensure unique control id attributes across multiple Controls blocks - [#34021](https://github.com/storybookjs/storybook/pull/34021), thanks @TheSeydiCharyyev!"}} \ No newline at end of file +{"version":"10.4.0-alpha.2","info":{"plain":"- CLI: Use npm info to fetch versions in repro command - [#34214](https://github.com/storybookjs/storybook/pull/34214), thanks @yannbf!\n- Core: Prevent story-local viewport from persisting in URL - [#34153](https://github.com/storybookjs/storybook/pull/34153), thanks @ghengeveld!\n- Maintenance: Remove dead-code copy of wrap-getAbsolutePath-utils - [#34168](https://github.com/storybookjs/storybook/pull/34168), thanks @mixelburg!\n- Security: Makes sure `serialize-javascript` is at latest version - [#34034](https://github.com/storybookjs/storybook/pull/34034), thanks @50bbx!"}} \ No newline at end of file