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/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index 608e56844183..dc74568c1860 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/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() }; 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); - } - }); - } - }); -} 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'; 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 diff --git a/package.json b/package.json index 8f741ed893ea..e2cfbd500458 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,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 283a4d020bf3..424d3e2b366a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8021,7 +8021,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" @@ -27774,12 +27774,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 @@ -29484,14 +29484,13 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:^5.3.11, terser-webpack-plugin@npm:^5.3.14": - version: 5.3.14 - resolution: "terser-webpack-plugin@npm:5.3.14" +"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: "@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 @@ -29502,7 +29501,7 @@ __metadata: optional: true uglify-js: optional: true - checksum: 10c0/9b060947241af43bd6fd728456f60e646186aef492163672a35ad49be6fbc7f63b54a7356c3f6ff40a8f83f00a977edc26f044b8e106cc611c053c8c0eaf8569 + checksum: 10c0/1feed4b9575af795dae6af0c8f0d76d6e1fb7b357b8628d90e834c23a651b918a58cdc48d0ae6c1f0581f74bc8169b33c3b8d049f2d2190bac4e310964e59fde languageName: node linkType: hard