Merge pull request #29585 from storybookjs/version-non-patch-from-8.5.0-alpha.3

Release: Prerelease 8.5.0-alpha.4
This commit is contained in:
Yann Braga 2024-11-12 15:55:20 +01:00 committed by GitHub
commit 4a75d13e4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
67 changed files with 1625 additions and 286 deletions

View File

@ -211,6 +211,57 @@ jobs:
yarn knip --no-exit-code
- report-workflow-on-failure
- cancel-workflow-on-failure
bench-packages:
executor:
class: medium
name: sb_node_22_classic
steps:
- git-shallow-clone/checkout_advanced:
clone_options: "--depth 1 --verbose"
- attach_workspace:
at: .
# if there is a base branch AND a PR number in parameters, benchmark packages against those
# this happens when run against a PR
- when:
condition:
and:
- << pipeline.parameters.ghBaseBranch >>
- << pipeline.parameters.ghPrNumber >>
steps:
- run:
name: Benchmarking packages against base branch
working_directory: scripts
command: |
yarn local-registry --open &
until curl -s http://localhost:6001 > /dev/null; do
echo 'Waiting for local registry to be available...'
sleep 2
done
yarn bench-packages --base-branch << pipeline.parameters.ghBaseBranch >> --pull-request << pipeline.parameters.ghPrNumber >> --upload
# if there is a NOT a base branch OR NOT a PR number in parameters, just upload benchmarks for the branch
# this happens when runned directly on branches, like next or main
- when:
condition:
or:
- not: << pipeline.parameters.ghBaseBranch >>
- not: << pipeline.parameters.ghPrNumber >>
steps:
- run:
name: Uploading package benchmarks for branch
working_directory: scripts
command: |
yarn local-registry --open &
until curl -s http://localhost:6001 > /dev/null; do
echo 'Waiting for local registry to be available...'
sleep 2
done
yarn bench-packages --upload
- store_artifacts:
path: bench/packages/results.json
- store_artifacts:
path: bench/packages/compare-with-<< pipeline.parameters.ghBaseBranch >>.json
- report-workflow-on-failure
- cancel-workflow-on-failure
check:
executor:
class: xlarge
@ -543,7 +594,7 @@ jobs:
- store_artifacts: # this is where playwright puts more complex stuff
path: code/playwright-results/
destination: playwright
bench:
bench-sandboxes:
parameters:
parallelism:
type: integer
@ -753,6 +804,9 @@ workflows:
- knip:
requires:
- build
- bench-packages:
requires:
- build
- check:
requires:
- build
@ -799,7 +853,7 @@ workflows:
parallelism: 5
requires:
- create-sandboxes
- bench:
- bench-sandboxes:
parallelism: 5
requires:
- build-sandboxes
@ -828,6 +882,9 @@ workflows:
- knip:
requires:
- build
- bench-packages:
requires:
- build
- check:
requires:
- build
@ -883,7 +940,7 @@ workflows:
- test-ui-testing-module:
requires:
- build
- bench:
- bench-sandboxes:
parallelism: 5
requires:
- build-sandboxes
@ -904,6 +961,9 @@ workflows:
- knip:
requires:
- build
- bench-packages:
requires:
- build
- check:
requires:
- build
@ -920,22 +980,22 @@ workflows:
requires:
- build
- create-sandboxes:
parallelism: 38
parallelism: 37
requires:
- build
# - smoke-test-sandboxes: # disabled for now
# requires:
# - create-sandboxes
- build-sandboxes:
parallelism: 38
parallelism: 37
requires:
- create-sandboxes
- chromatic-sandboxes:
parallelism: 35
parallelism: 34
requires:
- build-sandboxes
- e2e-production:
parallelism: 33
parallelism: 32
requires:
- build-sandboxes
- e2e-dev:
@ -943,7 +1003,7 @@ workflows:
requires:
- create-sandboxes
- test-runner-production:
parallelism: 33
parallelism: 32
requires:
- build-sandboxes
- vitest-integration:
@ -977,7 +1037,7 @@ workflows:
# --smoke-test is not supported for the angular builder right now
# - "angular-cli"
- "lit-vite-ts"
- bench:
- bench-sandboxes:
parallelism: 5
requires:
- build-sandboxes

View File

@ -1,3 +1,11 @@
## 8.4.2
- Addon Test: Fix post-install logic for Next.js Vite framework support - [#29524](https://github.com/storybookjs/storybook/pull/29524), thanks @valentinpalkovic!
- Addon Test: Only render the TestingModule component in development mode - [#29501](https://github.com/storybookjs/storybook/pull/29501), thanks @yannbf!
- CLI: Fix Solid init by installing `@storybook/test` - [#29514](https://github.com/storybookjs/storybook/pull/29514), thanks @shilman!
- Core: Shim CJS-only globals in ESM output - [#29157](https://github.com/storybookjs/storybook/pull/29157), thanks @valentinpalkovic!
- Next.js: Fix bundled react and react-dom in monorepos - [#29444](https://github.com/storybookjs/storybook/pull/29444), thanks @sentience!
## 8.4.1
- Core: Relax peer dep constraint of shim packages - [#29503](https://github.com/storybookjs/storybook/pull/29503), thanks @kasperpeulen!

View File

@ -1,3 +1,8 @@
## 8.5.0-alpha.4
- Next.js: Add support for Next 15 - [#29587](https://github.com/storybookjs/storybook/pull/29587), thanks @yannbf!
- UI: Add Yarn to About Section - [#29225](https://github.com/storybookjs/storybook/pull/29225), thanks @grantwforsythe!
## 8.5.0-alpha.3
- Addon Test: Fix post-install logic for Next.js Vite framework support - [#29524](https://github.com/storybookjs/storybook/pull/29524), thanks @valentinpalkovic!

View File

@ -5,6 +5,7 @@ Storybook is developed against a specific node version which is defined in an `.
## Ensure you have the required system utilities
You will need to have the following installed:
- git
- node
- yarn
@ -20,7 +21,7 @@ You will need to have the following installed:
## Running the local development environment
- Ensure if you are using Windows to use the Windows Subsystem for Linux (WSL).
- All commands should be run in a terminal with administrator privileges in Windows environments.
- Run `yarn start` in the root directory to run a basic test Storybook "sandbox".
The `yarn start` script will generate a React Vite TypeScript sandbox with a set of test stories inside it, as well as taking all steps required to get it running (building the various packages we need etc). There is no need to run `yarn` or `yarn install` as `yarn start` will do this for you.

View File

@ -19,6 +19,7 @@
- [5. Make Manual Changes](#5-make-manual-changes)
- [6. Merge](#6-merge)
- [7. See the "Publish" Workflow Finish](#7-see-the-publish-workflow-finish)
- [Releasing changes to older minor versions](#releasing-changes-to-older-minor-versions)
- [Releasing Locally in an Emergency 🚨](#releasing-locally-in-an-emergency-)
- [Canary Releases](#canary-releases)
- [With GitHub UI](#with-github-ui)
@ -330,6 +331,59 @@ Merging the pull request will trigger [the publish workflow](https://github.com/
Done! 🚀
## Releasing changes to older minor versions
If you need to release a change to an older minor version that is not the latest, you have to do it manually, locally. The process is described below, with an example of releasing a new `v8.3.7` in a situation where `8.4.0` is currently the latest version.
1. Checkout the _existing_ tag that matches the latest minor release you want to bump, and create a new branch from it. In this case, we want to do:
1. `git fetch --all --tags`
2. `git checkout tags/v8.3.6 -b patch-8-3-7`
2. Make the changes you need to, most likely cherry-picking commits from the fix you need to back-port.
3. Run `yarn install` in `scripts` and `code`
4. Build all packages in `code` with `yarn task --task compile --no-link`
5. Commit and push your changes.
6. Trigger _daily_ CI manually on your branch:
1. Open [CircleCI](https://app.circleci.com/pipelines/github/storybookjs/storybook) and click "Trigger Pipeline" on the top right corner of the page.
2. Set the following configuration options:
- Pipeline: _"storybook default"_
- Config Source: _"storybook"_
- Branch: Your branch, eg. `patch-8-3-7`
3. Add a parameter, with _"name"_ `workflow`, _"value"_ `daily`
7. Wait for CI to finish successfully.
8. Bump all package versions:
1. `cd scripts`
2. `yarn release:version --release-type patch`
9. Commit with `git commit -m "Bump version from <CURRENT_VERSION> to <NEXT_VERSION> MANUALLY"`
10. Add a new entry to `CHANGELOG.md`, describing your changes
11. Commit with `git commit -m "Update CHANGELOG.md with <NEXT_VERSION> MANUALLY"`
12. Ensure you have the correct write permissions for all the Storybook npm packages. You need to be an admin of the _storybook_ org, and the packages that are not in the org. The simplest way to check this is to ensure you can see the _"Settings"_ tab in the following packages:
1. [`@storybook/react-vite`](https://www.npmjs.com/package/@storybook/react-vite/access)
2. [`storybook`](https://www.npmjs.com/package/storybook/access)
3. [`sb`](https://www.npmjs.com/package/sb/access)
4. [`create-storybook`](https://www.npmjs.com/package/create-storybook/access)
13. Get your npm access token or generate a new one at https://www.npmjs.com/settings/your-username/tokens. Remember to give it access to the `storybook` org and the packages not in the org, as listed above.
14. Publish all packages with `YARN_NPM_AUTH_TOKEN=<NPM_TOKEN> yarn release:publish --tag tag-for-publishing-older-releases --verbose`
- It goes through all packages and publishes them. If any number of packages fails to publish, it will retry 5 times, skipping those that have already been published.
15. Confirm the new version has been released on npm with the tag `tag-for-publishing-older-releases`:
1. [`@storybook/react-vite`](https://www.npmjs.com/package/@storybook/react-vite?activeTab=versions)
2. [`storybook`](https://www.npmjs.com/package/storybook?activeTab=versions)
3. [`sb`](https://www.npmjs.com/package/sb?activeTab=versions)
4. [`create-storybook`](https://www.npmjs.com/package/create-storybook?activeTab=versions)
16. Push
17. Manually create a GitHub Release at https://github.com/storybookjs/storybook/releases/new with:
1. Create new tag: `v<VERSION>` (e.g., `v8.3.7`)
2. Target: your branch (e.g., `patch-8-3-7`)
3. Previous tag: `v<PREVIOUS_VERSION>` (e.g., `v8.3.6`)
4. Title: `v<VERSION>` (e.g., `v8.3.7`)
5. Description: The content you added to `CHANGELOG.md`
6. Untick _"Set as the latest release"_
18. Cherry-pick your changelog changes into `next`, so they are actually visible
1. Checkout the `next` branch
2. Cherry-pick the commit you created with your changelog modifications
3. Push
Done. 🎉
## Releasing Locally in an Emergency 🚨
Things can fail, code can break, and bugs can exist. When automation is broken, there may be a need for an emergency escape hatch to release new fixes. In such a situation, it's valid to run the whole release process locally instead of relying on pull requests and workflows. You don't need to create pull requests or split preparation and publishing; you can do it all at once, but make sure you still follow the correct branching strategy.

View File

@ -124,7 +124,7 @@ const Pure = memo(function PureTool(props: PureProps) {
onHide();
},
})),
]}
].flat()}
/>
);
}}

View File

@ -36,7 +36,7 @@ Don't see your favorite tool listed? Don't worry! That doesn't mean this addon i
### ❗️ Overriding theme
If you want to override your theme for a particular component or story, you can use the `themes.themeOverride` parameter.
If you want to override your theme for a particular component or story, you can use the `globals.theme` parameter.
```js
import React from 'react';

View File

@ -139,7 +139,7 @@ const Pure = React.memo(function PureTool(props: PureProps) {
onHide();
},
})),
]}
].flat()}
/>
)}
closeOnOutsideClick

View File

@ -14,7 +14,7 @@ interface UpgradeBlockProps {
export const UpgradeBlock: FC<UpgradeBlockProps> = ({ onNavigateToWhatsNew }) => {
const api = useStorybookApi();
const [activeTab, setActiveTab] = useState<'npm' | 'pnpm'>('npm');
const [activeTab, setActiveTab] = useState<'npm' | 'yarn' | 'pnpm'>('npm');
return (
<Container>
@ -24,12 +24,17 @@ export const UpgradeBlock: FC<UpgradeBlockProps> = ({ onNavigateToWhatsNew }) =>
<ButtonTab active={activeTab === 'npm'} onClick={() => setActiveTab('npm')}>
npm
</ButtonTab>
<ButtonTab active={activeTab === 'yarn'} onClick={() => setActiveTab('yarn')}>
yarn
</ButtonTab>
<ButtonTab active={activeTab === 'pnpm'} onClick={() => setActiveTab('pnpm')}>
pnpm
</ButtonTab>
</Tabs>
<Code>
{activeTab === 'npm' ? 'npx storybook@latest upgrade' : 'pnpm dlx storybook@latest upgrade'}
{activeTab === 'npm'
? 'npx storybook@latest upgrade'
: `${activeTab} dlx storybook@latest upgrade`}
</Code>
{onNavigateToWhatsNew && (
// eslint-disable-next-line jsx-a11y/anchor-is-valid

View File

@ -189,6 +189,10 @@ test.describe('addon-docs', () => {
});
test('should resolve react to the correct version', async ({ page }) => {
test.skip(
templateName?.includes('nextjs'),
'TODO: remove this once sandboxes are synced (SOON!!)'
);
// Arrange - Navigate to MDX docs
const sbPage = new SbPage(page, expect);
await sbPage.navigateToStory('addons/docs/docs2/resolvedreact', 'mdx', 'docs');
@ -201,6 +205,7 @@ test.describe('addon-docs', () => {
} else if (templateName.includes('react16')) {
expectedReactVersionRange = /^16/;
} else if (
templateName.includes('nextjs/default-ts') ||
templateName.includes('nextjs/prerelease') ||
templateName.includes('react-vite/prerelease') ||
templateName.includes('react-webpack/prerelease')

View File

@ -53,10 +53,7 @@ test.describe('Next.js', () => {
test.beforeEach(async ({ page }) => {
sbPage = new SbPage(page, expect);
await sbPage.navigateToStory(
'stories/frameworks/nextjs-nextjs-default-ts/Navigation',
'default'
);
await sbPage.navigateToStory('stories/frameworks/nextjs/Navigation', 'default');
root = sbPage.previewRoot();
});
@ -88,7 +85,7 @@ test.describe('Next.js', () => {
test.beforeEach(async ({ page }) => {
sbPage = new SbPage(page, expect);
await sbPage.navigateToStory('stories/frameworks/nextjs-nextjs-default-ts/Router', 'default');
await sbPage.navigateToStory('stories/frameworks/nextjs/Router', 'default');
root = sbPage.previewRoot();
});

View File

@ -57,6 +57,11 @@
"import": "./dist/compatibility/redirect-status-code.compat.mjs",
"require": "./dist/compatibility/redirect-status-code.compat.js"
},
"./dist/compatibility/draft-mode.compat": {
"types": "./dist/compatibility/draft-mode.compat.d.ts",
"import": "./dist/compatibility/draft-mode.compat.mjs",
"require": "./dist/compatibility/draft-mode.compat.js"
},
"./export-mocks": {
"types": "./dist/export-mocks/index.d.ts",
"import": "./dist/export-mocks/index.mjs",
@ -171,12 +176,12 @@
"@types/babel__preset-env": "^7",
"@types/loader-utils": "^2.0.5",
"@types/react-refresh": "^0",
"next": "^14.1.0",
"next": "^15.0.3",
"typescript": "^5.3.2",
"webpack": "^5.65.0"
},
"peerDependencies": {
"next": "^13.5.0 || ^14.0.0",
"next": "^13.5.0 || ^14.0.0 || ^15.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
"storybook": "workspace:^",
@ -212,6 +217,7 @@
"./src/export-mocks/navigation/index.ts",
"./src/compatibility/segment.compat.ts",
"./src/compatibility/redirect-status-code.compat.ts",
"./src/compatibility/draft-mode.compat.ts",
"./src/next-image-loader-stub.ts",
"./src/images/decorator.tsx",
"./src/images/next-legacy-image.tsx",

View File

@ -1,6 +1,12 @@
import type { Configuration as WebpackConfig } from 'webpack';
import { configureCompatibilityAliases } from '../compatibility/compatibility-map';
import { configureNextExportMocks } from '../export-mocks/webpack';
export const configureAliases = (baseConfig: WebpackConfig): void => {
configureNextExportMocks(baseConfig);
configureCompatibilityAliases(baseConfig);
baseConfig.resolve = {
...(baseConfig.resolve ?? {}),
alias: {
@ -8,4 +14,12 @@ export const configureAliases = (baseConfig: WebpackConfig): void => {
'@opentelemetry/api': 'next/dist/compiled/@opentelemetry/api',
},
};
// remove warnings regarding compatibility paths
baseConfig.ignoreWarnings = [
...(baseConfig.ignoreWarnings ?? []),
(warning) =>
warning.message.includes("export 'draftMode'") &&
warning.message.includes('next/dist/server/request/headers'),
];
};

View File

@ -1,9 +1,9 @@
import semver from 'semver';
import type { Configuration as WebpackConfig } from 'webpack';
import { addScopedAlias, getNextjsVersion } from '../utils';
import { addScopedAlias, getNextjsVersion, setAlias } from '../utils';
const mapping: Record<string, Record<string, string>> = {
const mapping: Record<string, Record<string, string | boolean>> = {
'<14.1.0': {
// https://github.com/vercel/next.js/blob/v14.1.0/packages/next/src/shared/lib/segment.ts
'next/dist/shared/lib/segment': '@storybook/nextjs/dist/compatibility/segment.compat',
@ -13,6 +13,11 @@ const mapping: Record<string, Record<string, string>> = {
'next/dist/client/components/redirect-status-code':
'@storybook/nextjs/dist/compatibility/redirect-status-code.compat',
},
'<15.0.0': {
'next/dist/server/request/headers': 'next/dist/client/components/headers',
// this path only exists from Next 15 onwards
'next/dist/server/request/draft-mode': '@storybook/nextjs/dist/compatibility/draft-mode.compat',
},
};
export const getCompatibilityAliases = () => {
@ -32,6 +37,10 @@ export const configureCompatibilityAliases = (baseConfig: WebpackConfig): void =
const aliases = getCompatibilityAliases();
Object.entries(aliases).forEach(([name, alias]) => {
addScopedAlias(baseConfig, name, alias);
if (typeof alias === 'string') {
addScopedAlias(baseConfig, name, alias);
} else {
setAlias(baseConfig, name, alias);
}
});
};

View File

@ -0,0 +1,2 @@
// Compatibility for Next 14
export { draftMode } from 'next/dist/client/components/headers';

View File

@ -1,14 +1,16 @@
import { fn } from '@storybook/test';
import * as originalHeaders from 'next/dist/client/components/headers';
// This export won't exist in Next.js 14 but it's safe because we ignore it in Webpack when applicable
import { draftMode as originalDraftMode } from 'next/dist/server/request/draft-mode';
import * as headers from 'next/dist/server/request/headers';
// re-exports of the actual module
export * from 'next/dist/client/components/headers';
export * from 'next/dist/server/request/headers';
// mock utilities/overrides (as of Next v14.2.0)
export { headers } from './headers';
export { cookies } from './cookies';
// passthrough mocks - keep original implementation but allow for spying
const draftMode = fn(originalHeaders.draftMode).mockName('draftMode');
const draftMode = fn(originalDraftMode ?? (headers as any).draftMode).mockName('draftMode');
export { draftMode };

View File

@ -23,14 +23,14 @@ export const getPackageAliases = ({ useESM = false }: { useESM?: boolean } = {})
const packageLocation = dirname(require.resolve('@storybook/nextjs/package.json'));
const getFullPath = (path: string) =>
join(packageLocation, path.replace('@storybook/nextjs', ''));
path.startsWith('next') ? path : join(packageLocation, path.replace('@storybook/nextjs', ''));
const aliases = Object.fromEntries(
Object.entries(mapping).map(([originalPath, aliasedPath]) => [
originalPath,
// Use paths for both next/xyz and @storybook/nextjs/xyz imports
// to make sure they all serve the MJS/CJS version of the file
getFullPath(`${aliasedPath}.${extension}`),
typeof aliasedPath === 'string' ? getFullPath(`${aliasedPath}.${extension}`) : aliasedPath,
])
);

View File

@ -162,8 +162,6 @@ export const webpackFinal: StorybookConfig['webpackFinal'] = async (baseConfig,
configureStyledJsx(baseConfig);
configureNodePolyfills(baseConfig);
configureAliases(baseConfig);
configureCompatibilityAliases(baseConfig);
configureNextExportMocks(baseConfig);
if (isDevelopment) {
configureFastRefresh(baseConfig);

View File

@ -18,11 +18,14 @@ import {
PathnameContext,
SearchParamsContext,
} from 'next/dist/shared/lib/hooks-client-context.shared-runtime';
import { type Params } from 'next/dist/shared/lib/router/utils/route-matcher';
import { PAGE_SEGMENT_KEY } from 'next/dist/shared/lib/segment';
import type { RouteParams } from './types';
// Using an inline type so we can support Next 14 and lower
// from https://github.com/vercel/next.js/blob/v15.0.3/packages/next/src/server/request/params.ts#L25
type Params = Record<string, string | Array<string> | undefined>;
type AppRouterProviderProps = {
routeParams: RouteParams;
};

View File

@ -1,5 +1,7 @@
import React, { Suspense } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import dynamic from 'next/dynamic';
const DynamicComponent = dynamic(() => import('./dynamic-component'), {
@ -16,6 +18,6 @@ function Component() {
export default {
component: Component,
};
} as Meta<typeof Component>;
export const Default = {};
export const Default: StoryObj<typeof Component> = {};

View File

@ -0,0 +1,27 @@
import type { Meta, StoryObj } from '@storybook/react';
import Font from './Font';
export default {
component: Font,
} as Meta<typeof Font>;
type Story = StoryObj<typeof Font>;
export const WithClassName: Story = {
args: {
variant: 'className',
},
};
export const WithStyle: Story = {
args: {
variant: 'style',
},
};
export const WithVariable: Story = {
args: {
variant: 'variable',
},
};

View File

@ -1,4 +1,3 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { Rubik_Puddles } from 'next/font/google';
@ -15,7 +14,7 @@ export const localRubikStorm = localFont({
variable: '--font-rubik-storm',
});
export default function Font({ variant }) {
export default function Font({ variant }: { variant: 'className' | 'style' | 'variable' }) {
switch (variant) {
case 'className':
return (

View File

@ -1,5 +1,6 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { expect, waitFor } from '@storybook/test';
import Head from 'next/head';
@ -21,14 +22,14 @@ function Component() {
export default {
component: Component,
};
} as Meta<typeof Component>;
export const Default = {
export const Default: StoryObj<typeof Component> = {
play: async () => {
await waitFor(() => expect(document.title).toEqual('Next.js Head Title'));
await expect(document.querySelectorAll('meta[property="og:title"]')).toHaveLength(1);
await expect(document.querySelector('meta[property="og:title"]').content).toEqual(
'My new title'
);
await expect(
(document.querySelector('meta[property="og:title"]') as HTMLMetaElement)?.content
).toEqual('My new title');
},
};

View File

@ -1,4 +1,6 @@
import React, { useRef, useState } from 'react';
import React, { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import Image from 'next/image';
@ -11,24 +13,26 @@ export default {
src: Accessibility,
alt: 'Accessibility',
},
};
} as Meta<typeof Image>;
export const Default = {};
type Story = StoryObj<typeof Image>;
export const Avif = {
export const Default: Story = {};
export const Avif: Story = {
args: {
src: AvifImage,
alt: 'Avif Test Image',
},
};
export const BlurredPlaceholder = {
export const BlurredPlaceholder: Story = {
args: {
placeholder: 'blur',
},
};
export const BlurredAbsolutePlaceholder = {
export const BlurredAbsolutePlaceholder: Story = {
args: {
src: 'https://storybook.js.org/images/placeholders/50x50.png',
width: 50,
@ -44,26 +48,26 @@ export const BlurredAbsolutePlaceholder = {
},
};
export const FilledParent = {
export const FilledParent: Story = {
args: {
fill: true,
},
decorator: [
decorators: [
(Story) => <div style={{ width: 500, height: 500, position: 'relative' }}>{Story()}</div>,
],
};
export const Sized = {
export const Sized: Story = {
args: {
fill: true,
sizes: '(max-width: 600px) 100vw, 600px',
decorator: [
(Story) => <div style={{ width: 800, height: 800, position: 'relative' }}>{Story()}</div>,
],
},
decorators: [
(Story) => <div style={{ width: 800, height: 800, position: 'relative' }}>{Story()}</div>,
],
};
export const Lazy = {
export const Lazy: Story = {
args: {
src: 'https://storybook.js.org/images/placeholders/50x50.png',
width: 50,
@ -79,7 +83,7 @@ export const Lazy = {
],
};
export const Eager = {
export const Eager: Story = {
...Lazy,
parameters: {
nextjs: {
@ -90,9 +94,9 @@ export const Eager = {
},
};
export const WithRef = {
export const WithRef: Story = {
render() {
const [ref, setRef] = useState(null);
const [ref, setRef] = useState<HTMLImageElement | null>(null);
return (
<div>

View File

@ -1,4 +1,4 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import Image from 'next/legacy/image';
@ -10,17 +10,19 @@ export default {
src: Accessibility,
alt: 'Accessibility',
},
};
} as Meta<typeof Image>;
export const Default = {};
type Story = StoryObj<typeof Image>;
export const BlurredPlaceholder = {
export const Default: Story = {};
export const BlurredPlaceholder: Story = {
args: {
placeholder: 'blur',
},
};
export const BlurredAbsolutePlaceholder = {
export const BlurredAbsolutePlaceholder: Story = {
args: {
src: 'https://storybook.js.org/images/placeholders/50x50.png',
width: 50,

View File

@ -76,9 +76,11 @@ export default {
component: Component,
} as Meta<typeof Component>;
export const Default: StoryObj<typeof Component> = {};
type Story = StoryObj<typeof Component>;
export const InAppDir: StoryObj<typeof Component> = {
export const Default: Story = {};
export const InAppDir: Story = {
parameters: {
nextjs: {
appDirectory: true,

View File

@ -108,7 +108,7 @@ export default {
},
} as Meta<typeof Component>;
export const Default: StoryObj<typeof Component> = {
export const Default: Story = {
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
const routerMock = getRouter();

View File

@ -1,7 +0,0 @@
import React from 'react';
import 'server-only';
export const RSC = async ({ label }) => <>RSC {label}</>;
export const Nested = async ({ children }) => <>Nested {children}</>;

View File

@ -1,5 +1,8 @@
/* eslint-disable local-rules/no-uncategorized-errors */
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Nested, RSC } from './RSC';
export default {
@ -10,11 +13,13 @@ export default {
rsc: true,
},
},
};
} as Meta<typeof RSC>;
export const Default = {};
type Story = StoryObj<typeof RSC>;
export const DisableRSC = {
export const Default: Story = {};
export const DisableRSC: Story = {
tags: ['!test'],
parameters: {
chromatic: { disable: true },
@ -22,7 +27,7 @@ export const DisableRSC = {
},
};
export const Error = {
export const Errored: Story = {
tags: ['!test'],
parameters: {
chromatic: { disable: true },
@ -32,7 +37,7 @@ export const Error = {
},
};
export const NestedRSC = {
export const NestedRSC: Story = {
render: (args) => (
<Nested>
<RSC {...args} />

View File

@ -0,0 +1,7 @@
import React from 'react';
import 'server-only';
export const RSC = async ({ label }: { label: string }) => <>RSC {label}</>;
export const Nested = async ({ children }: any) => <>Nested {children}</>;

View File

@ -1,5 +1,7 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
const Component = () => (
<div>
<style jsx>{`
@ -15,6 +17,6 @@ const Component = () => (
export default {
component: Component,
};
} as Meta<typeof Component>;
export const Default = {};
export const Default: StoryObj<typeof Component> = {};

View File

@ -1,23 +0,0 @@
import Font from './Font';
export default {
component: Font,
};
export const WithClassName = {
args: {
variant: 'className',
},
};
export const WithStyle = {
args: {
variant: 'style',
},
};
export const WithVariable = {
args: {
variant: 'variable',
},
};

View File

@ -1,83 +0,0 @@
/* eslint-disable react/prop-types */
import React from 'react';
import Link from 'next/link';
import style from './Link.stories.module.css';
// `onClick`, `href`, and `ref` need to be passed to the DOM element
// for proper handling
const MyButton = React.forwardRef(function Button({ onClick, href, children }, ref) {
return (
<a href={href} onClick={onClick} ref={ref}>
{children}
</a>
);
});
const Component = () => (
<ul>
<li>
<Link href="/">Normal Link</Link>
</li>
<li>
<Link
href={{
pathname: '/with-url-object',
query: { name: 'test' },
}}
>
With URL Object
</Link>
</li>
<li>
<Link href="/replace-url" replace>
Replace the URL instead of push
</Link>
</li>
<li>
<Link href="/legacy-behaviour" legacyBehavior>
<a>Legacy behavior</a>
</Link>
</li>
<li>
<Link href="/child-is-functional-component" passHref legacyBehavior>
<MyButton>child is a functional component</MyButton>
</Link>
</li>
<li>
<Link href="/#hashid" scroll={false}>
Disables scrolling to the top
</Link>
</li>
<li>
<Link href="/no-prefetch" prefetch={false}>
No Prefetching
</Link>
</li>
<li>
<Link style={{ color: 'red' }} href="/with-style">
With style
</Link>
</li>
<li>
<Link className={style.link} href="/with-classname">
With className
</Link>
</li>
</ul>
);
export default {
component: Component,
};
export const Default = {};
export const InAppDir = {
parameters: {
nextjs: {
appDirectory: true,
},
},
};

View File

@ -1,12 +1,14 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { getImageProps } from 'next/image';
import Accessibility from '../../assets/accessibility.svg';
import Testing from '../../assets/testing.png';
// referenced from https://nextjs.org/docs/pages/api-reference/components/image#theme-detection-picture
const Component = (props) => {
const Component = (props: any) => {
const {
props: { srcSet: dark },
} = getImageProps({ src: Accessibility, ...props });
@ -29,6 +31,6 @@ export default {
args: {
alt: 'getImageProps Example',
},
};
} as Meta<typeof Component>;
export const Default = {};
export const Default: StoryObj<typeof Component> = {};

View File

@ -1,3 +0,0 @@
.link {
color: green;
}

View File

@ -1,6 +1,5 @@
import { cookies, headers } from '@storybook/nextjs/headers.mock';
import type { Meta } from '@storybook/react';
import type { StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
import NextHeader from './NextHeader';

View File

@ -5,25 +5,23 @@ import { cookies, headers } from 'next/headers';
export default async function Component() {
async function handleClick() {
'use server';
cookies().set('user-id', 'encrypted-id');
(await cookies()).set('user-id', 'encrypted-id');
}
return (
<>
<h3>Cookies:</h3>
{cookies()
.getAll()
.map(({ name, value }) => {
return (
<p key={name} style={{ display: 'flex', flexDirection: 'row', gap: 8 }}>
<strong>Name:</strong> <span>{name}</span>
<strong>Value:</strong> <span>{value}</span>
</p>
);
})}
{(await cookies()).getAll().map(({ name, value }) => {
return (
<p key={name} style={{ display: 'flex', flexDirection: 'row', gap: 8 }}>
<strong>Name:</strong> <span>{name}</span>
<strong>Value:</strong> <span>{value}</span>
</p>
);
})}
<h3>Headers:</h3>
{Array.from(headers().entries()).map(([name, value]: [string, string]) => {
{Array.from((await headers()).entries()).map(([name, value]: [string, string]) => {
return (
<p key={name} style={{ display: 'flex', flexDirection: 'row', gap: 8 }}>
<strong>Name:</strong> <span>{name}</span>

View File

@ -58,7 +58,9 @@ export default {
},
} as Meta<typeof Component>;
export const ProtectedWhileLoggedOut: StoryObj<typeof Component> = {
type Story = StoryObj<typeof Component>;
export const ProtectedWhileLoggedOut: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByText('Access protected route'));
@ -70,7 +72,7 @@ export const ProtectedWhileLoggedOut: StoryObj<typeof Component> = {
},
};
export const ProtectedWhileLoggedIn: StoryObj<typeof Component> = {
export const ProtectedWhileLoggedIn: Story = {
beforeEach() {
cookies().set('user', 'storybookjs');
},
@ -86,7 +88,7 @@ export const ProtectedWhileLoggedIn: StoryObj<typeof Component> = {
},
};
export const Logout: StoryObj<typeof Component> = {
export const Logout: Story = {
beforeEach() {
cookies().set('user', 'storybookjs');
},
@ -102,7 +104,7 @@ export const Logout: StoryObj<typeof Component> = {
},
};
export const Login: StoryObj<typeof Component> = {
export const Login: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByText('Login'));

View File

@ -5,7 +5,7 @@ import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
export async function accessRoute() {
const user = cookies().get('user');
const user = (await cookies()).get('user');
if (!user) {
redirect('/');
@ -16,13 +16,13 @@ export async function accessRoute() {
}
export async function logout() {
cookies().delete('user');
(await cookies()).delete('user');
revalidatePath('/');
redirect('/');
}
export async function login() {
cookies().set('user', 'storybookjs');
(await cookies()).set('user', 'storybookjs');
revalidatePath('/');
redirect('/');
}

View File

@ -0,0 +1,36 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { getImageProps } from 'next/image';
import Accessibility from '../../assets/accessibility.svg';
import Testing from '../../assets/testing.png';
// referenced from https://nextjs.org/docs/pages/api-reference/components/image#theme-detection-picture
const Component = (props: any) => {
const {
props: { srcSet: dark },
} = getImageProps({ src: Accessibility, ...props });
const {
// capture rest on one to spread to img as default; it doesn't matter which barring art direction
props: { srcSet: light, ...rest },
} = getImageProps({ src: Testing, ...props });
return (
<picture>
<source media="(prefers-color-scheme: dark)" srcSet={dark} />
<source media="(prefers-color-scheme: light)" srcSet={light} />
<img {...rest} />
</picture>
);
};
export default {
component: Component,
args: {
alt: 'getImageProps Example',
},
} as Meta<typeof Component>;
export const Default: StoryObj<typeof Component> = {};

View File

@ -0,0 +1,51 @@
import { cookies, headers } from '@storybook/nextjs/headers.mock';
import type { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
import NextHeader from './NextHeader';
export default {
component: NextHeader,
parameters: {
react: {
rsc: true,
},
},
} as Meta<typeof NextHeader>;
type Story = StoryObj<typeof NextHeader>;
export const Default: Story = {
loaders: async () => {
cookies().set('firstName', 'Jane');
cookies().set({
name: 'lastName',
value: 'Doe',
});
headers().set('timezone', 'Central European Summer Time');
},
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
const headersMock = headers();
const cookiesMock = cookies();
await step('Cookie and header store apis are called upon rendering', async () => {
await expect(cookiesMock.getAll).toHaveBeenCalled();
await expect(headersMock.entries).toHaveBeenCalled();
});
await step('Upon clicking on submit, the user-id cookie is set', async () => {
const submitButton = await canvas.findByRole('button');
await userEvent.click(submitButton);
await expect(cookiesMock.set).toHaveBeenCalledWith('user-id', 'encrypted-id');
});
await step('The user-id cookie is available in cookie and header stores', async () => {
await expect(headersMock.get('cookie')).toContain('user-id=encrypted-id');
await expect(cookiesMock.get('user-id')).toEqual({
name: 'user-id',
value: 'encrypted-id',
});
});
},
};

View File

@ -0,0 +1,38 @@
import React from 'react';
import { cookies, headers } from 'next/headers';
export default async function Component() {
async function handleClick() {
'use server';
(await cookies()).set('user-id', 'encrypted-id');
}
return (
<>
<h3>Cookies:</h3>
{(await cookies()).getAll().map(({ name, value }) => {
return (
<p key={name} style={{ display: 'flex', flexDirection: 'row', gap: 8 }}>
<strong>Name:</strong> <span>{name}</span>
<strong>Value:</strong> <span>{value}</span>
</p>
);
})}
<h3>Headers:</h3>
{Array.from((await headers()).entries()).map(([name, value]: [string, string]) => {
return (
<p key={name} style={{ display: 'flex', flexDirection: 'row', gap: 8 }}>
<strong>Name:</strong> <span>{name}</span>
<strong>Value:</strong> <span>{value}</span>
</p>
);
})}
<form action={handleClick}>
<button>add user-id cookie</button>
</form>
</>
);
}

View File

@ -0,0 +1,57 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { userEvent, within } from '@storybook/test';
import { redirect } from 'next/navigation';
let state = 'Bug! Not invalidated';
export default {
render() {
return (
<div>
<div>{state}</div>
<form
action={() => {
state = 'State is invalidated successfully.';
redirect('/');
}}
>
<button>Submit</button>
</form>
</div>
);
},
parameters: {
test: {
// This is needed until Next will update to the React 19 beta: https://github.com/vercel/next.js/pull/65058
// In the React 19 beta ErrorBoundary errors (such as redirect) are only logged, and not thrown.
// We will also suspress console.error logs for re the console.error logs for redirect in the next framework.
// Using the onCaughtError react root option:
// react: {
// rootOptions: {
// onCaughtError(error: unknown) {
// if (isNextRouterError(error)) return;
// console.error(error);
// },
// },
// See: code/frameworks/nextjs/src/preview.tsx
dangerouslyIgnoreUnhandledErrors: true,
},
nextjs: {
appDirectory: true,
navigation: {
pathname: '/',
},
},
},
tags: ['!test'],
} as Meta;
export const SingletonStateGetsInvalidatedAfterRedirecting: StoryObj = {
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole('button'));
},
};

View File

@ -0,0 +1,118 @@
import React from 'react';
import { revalidatePath } from '@storybook/nextjs/cache.mock';
import { cookies } from '@storybook/nextjs/headers.mock';
import { getRouter, redirect } from '@storybook/nextjs/navigation.mock';
import type { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, waitFor, within } from '@storybook/test';
import { accessRoute, login, logout } from './server-actions';
function Component() {
return (
<div style={{ display: 'flex', gap: 8 }}>
<form>
<button type="submit" formAction={login}>
Login
</button>
</form>
<form>
<button type="submit" formAction={logout}>
Logout
</button>
</form>
<form>
<button type="submit" formAction={accessRoute}>
Access protected route
</button>
</form>
</div>
);
}
export default {
component: Component,
tags: ['!test'],
parameters: {
nextjs: {
appDirectory: true,
navigation: {
pathname: '/',
},
},
test: {
// This is needed until Next will update to the React 19 beta: https://github.com/vercel/next.js/pull/65058
// In the React 19 beta ErrorBoundary errors (such as redirect) are only logged, and not thrown.
// We will also suspress console.error logs for re the console.error logs for redirect in the next framework.
// Using the onCaughtError react root option:
// react: {
// rootOptions: {
// onCaughtError(error: unknown) {
// if (isNextRouterError(error)) return;
// console.error(error);
// },
// },
// See: code/frameworks/nextjs/src/preview.tsx
dangerouslyIgnoreUnhandledErrors: true,
},
},
} as Meta<typeof Component>;
type Story = StoryObj<typeof Component>;
export const ProtectedWhileLoggedOut: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByText('Access protected route'));
await expect(cookies().get).toHaveBeenCalledWith('user');
await expect(redirect).toHaveBeenCalledWith('/');
await waitFor(() => expect(getRouter().push).toHaveBeenCalled());
},
};
export const ProtectedWhileLoggedIn: Story = {
beforeEach() {
cookies().set('user', 'storybookjs');
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByText('Access protected route'));
await expect(cookies().get).toHaveBeenLastCalledWith('user');
await expect(revalidatePath).toHaveBeenLastCalledWith('/');
await expect(redirect).toHaveBeenLastCalledWith('/protected');
await waitFor(() => expect(getRouter().push).toHaveBeenCalled());
},
};
export const Logout: Story = {
beforeEach() {
cookies().set('user', 'storybookjs');
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByText('Logout'));
await expect(cookies().delete).toHaveBeenCalled();
await expect(revalidatePath).toHaveBeenCalledWith('/');
await expect(redirect).toHaveBeenCalledWith('/');
await waitFor(() => expect(getRouter().push).toHaveBeenCalled());
},
};
export const Login: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByText('Login'));
await expect(cookies().set).toHaveBeenCalledWith('user', 'storybookjs');
await expect(revalidatePath).toHaveBeenCalledWith('/');
await expect(redirect).toHaveBeenCalledWith('/');
await waitFor(() => expect(getRouter().push).toHaveBeenCalled());
},
};

View File

@ -0,0 +1,28 @@
'use server';
import { revalidatePath } from 'next/cache';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
export async function accessRoute() {
const user = (await cookies()).get('user');
if (!user) {
redirect('/');
}
revalidatePath('/');
redirect(`/protected`);
}
export async function logout() {
(await cookies()).delete('user');
revalidatePath('/');
redirect('/');
}
export async function login() {
(await cookies()).set('user', 'storybookjs');
revalidatePath('/');
redirect('/');
}

View File

@ -0,0 +1,14 @@
declare module '*.svg' {
const content: string;
export default content;
}
declare module '*.avif' {
const content: string;
export default content;
}
declare module '*.png' {
const content: string;
export default content;
}

View File

@ -156,10 +156,10 @@ const baseTemplates = {
},
skipTasks: ['e2e-tests-dev', 'bench', 'vitest-integration'],
},
'nextjs/default-js': {
name: 'Next.js Latest (Webpack | JavaScript)',
'nextjs/14-ts': {
name: 'Next.js v14.2 (Webpack | TypeScript)',
script:
'npx create-next-app@^14 {{beforeDir}} --eslint --tailwind --app --import-alias="@/*" --src-dir',
'yarn create next-app {{beforeDir}} -e https://github.com/vercel/next.js/tree/v14.2.17/examples/hello-world && cd {{beforeDir}} && npm pkg set "dependencies.next"="^14.2.17" && yarn && git add . && git commit --amend --no-edit && cd ..',
expected: {
framework: '@storybook/nextjs',
renderer: '@storybook/react',
@ -172,11 +172,12 @@ const baseTemplates = {
extraDependencies: ['server-only', 'prop-types'],
},
skipTasks: ['e2e-tests-dev', 'bench', 'vitest-integration'],
inDevelopment: true,
},
'nextjs/default-ts': {
name: 'Next.js Latest (Webpack | TypeScript)',
script:
'npx create-next-app@^14 {{beforeDir}} --typescript --eslint --tailwind --app --import-alias="@/*" --src-dir',
'npx create-next-app {{beforeDir}} --eslint --tailwind --app --import-alias="@/*" --src-dir',
expected: {
framework: '@storybook/nextjs',
renderer: '@storybook/react',
@ -193,7 +194,7 @@ const baseTemplates = {
'nextjs/prerelease': {
name: 'Next.js Prerelease (Webpack | TypeScript)',
script:
'npx create-next-app@canary {{beforeDir}} --typescript --eslint --tailwind --app --import-alias="@/*" --src-dir',
'npx create-next-app@canary {{beforeDir}} --eslint --tailwind --app --import-alias="@/*" --src-dir',
expected: {
framework: '@storybook/nextjs',
renderer: '@storybook/react',
@ -210,7 +211,7 @@ const baseTemplates = {
'experimental-nextjs-vite/default-ts': {
name: 'Next.js Latest (Vite | TypeScript)',
script:
'npx create-next-app@^14 {{beforeDir}} --typescript --eslint --tailwind --app --import-alias="@/*" --src-dir',
'npx create-next-app@^14 {{beforeDir}} --eslint --tailwind --app --import-alias="@/*" --src-dir',
expected: {
framework: '@storybook/experimental-nextjs-vite',
renderer: '@storybook/react',
@ -785,7 +786,6 @@ export const daily: TemplateKey[] = [
'svelte-kit/prerelease-ts',
'svelte-vite/default-js',
'nextjs/13-ts',
'nextjs/default-js',
'nextjs/prerelease',
'qwik-vite/default-ts',
'preact-vite/default-js',

View File

@ -293,5 +293,6 @@
"Dependency Upgrades"
]
]
}
},
"deferredNextVersion": "8.5.0-alpha.4"
}

View File

@ -2504,6 +2504,15 @@ __metadata:
languageName: node
linkType: hard
"@emnapi/runtime@npm:^1.2.0":
version: 1.3.1
resolution: "@emnapi/runtime@npm:1.3.1"
dependencies:
tslib: "npm:^2.4.0"
checksum: 10c0/060ffede50f1b619c15083312b80a9e62a5b0c87aa8c1b54854c49766c9d69f8d1d3d87bd963a647071263a320db41b25eaa50b74d6a80dcc763c23dbeaafd6c
languageName: node
linkType: hard
"@emotion/babel-plugin@npm:^11.11.0":
version: 11.11.0
resolution: "@emotion/babel-plugin@npm:11.11.0"
@ -3429,6 +3438,18 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-darwin-arm64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-darwin-arm64@npm:0.33.5"
dependencies:
"@img/sharp-libvips-darwin-arm64": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-darwin-arm64":
optional: true
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@img/sharp-darwin-x64@npm:0.33.4":
version: 0.33.4
resolution: "@img/sharp-darwin-x64@npm:0.33.4"
@ -3441,6 +3462,18 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-darwin-x64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-darwin-x64@npm:0.33.5"
dependencies:
"@img/sharp-libvips-darwin-x64": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-darwin-x64":
optional: true
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@img/sharp-libvips-darwin-arm64@npm:1.0.2":
version: 1.0.2
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.0.2"
@ -3448,6 +3481,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-darwin-arm64@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.0.4"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@img/sharp-libvips-darwin-x64@npm:1.0.2":
version: 1.0.2
resolution: "@img/sharp-libvips-darwin-x64@npm:1.0.2"
@ -3455,6 +3495,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-darwin-x64@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-darwin-x64@npm:1.0.4"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@img/sharp-libvips-linux-arm64@npm:1.0.2":
version: 1.0.2
resolution: "@img/sharp-libvips-linux-arm64@npm:1.0.2"
@ -3462,6 +3509,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linux-arm64@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-linux-arm64@npm:1.0.4"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linux-arm@npm:1.0.2":
version: 1.0.2
resolution: "@img/sharp-libvips-linux-arm@npm:1.0.2"
@ -3469,6 +3523,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linux-arm@npm:1.0.5":
version: 1.0.5
resolution: "@img/sharp-libvips-linux-arm@npm:1.0.5"
conditions: os=linux & cpu=arm & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linux-s390x@npm:1.0.2":
version: 1.0.2
resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.2"
@ -3476,6 +3537,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linux-s390x@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-linux-s390x@npm:1.0.4"
conditions: os=linux & cpu=s390x & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linux-x64@npm:1.0.2":
version: 1.0.2
resolution: "@img/sharp-libvips-linux-x64@npm:1.0.2"
@ -3483,6 +3551,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linux-x64@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-linux-x64@npm:1.0.4"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-libvips-linuxmusl-arm64@npm:1.0.2":
version: 1.0.2
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.0.2"
@ -3490,6 +3565,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linuxmusl-arm64@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.0.4"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-libvips-linuxmusl-x64@npm:1.0.2":
version: 1.0.2
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.0.2"
@ -3497,6 +3579,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-libvips-linuxmusl-x64@npm:1.0.4":
version: 1.0.4
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.0.4"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-linux-arm64@npm:0.33.4":
version: 0.33.4
resolution: "@img/sharp-linux-arm64@npm:0.33.4"
@ -3509,6 +3598,18 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linux-arm64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-linux-arm64@npm:0.33.5"
dependencies:
"@img/sharp-libvips-linux-arm64": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-linux-arm64":
optional: true
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-arm@npm:0.33.4":
version: 0.33.4
resolution: "@img/sharp-linux-arm@npm:0.33.4"
@ -3521,6 +3622,18 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linux-arm@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-linux-arm@npm:0.33.5"
dependencies:
"@img/sharp-libvips-linux-arm": "npm:1.0.5"
dependenciesMeta:
"@img/sharp-libvips-linux-arm":
optional: true
conditions: os=linux & cpu=arm & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-s390x@npm:0.33.4":
version: 0.33.4
resolution: "@img/sharp-linux-s390x@npm:0.33.4"
@ -3533,6 +3646,18 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linux-s390x@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-linux-s390x@npm:0.33.5"
dependencies:
"@img/sharp-libvips-linux-s390x": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-linux-s390x":
optional: true
conditions: os=linux & cpu=s390x & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linux-x64@npm:0.33.4":
version: 0.33.4
resolution: "@img/sharp-linux-x64@npm:0.33.4"
@ -3545,6 +3670,18 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linux-x64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-linux-x64@npm:0.33.5"
dependencies:
"@img/sharp-libvips-linux-x64": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-linux-x64":
optional: true
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@img/sharp-linuxmusl-arm64@npm:0.33.4":
version: 0.33.4
resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.4"
@ -3557,6 +3694,18 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linuxmusl-arm64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-linuxmusl-arm64@npm:0.33.5"
dependencies:
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-linuxmusl-arm64":
optional: true
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-linuxmusl-x64@npm:0.33.4":
version: 0.33.4
resolution: "@img/sharp-linuxmusl-x64@npm:0.33.4"
@ -3569,6 +3718,18 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-linuxmusl-x64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-linuxmusl-x64@npm:0.33.5"
dependencies:
"@img/sharp-libvips-linuxmusl-x64": "npm:1.0.4"
dependenciesMeta:
"@img/sharp-libvips-linuxmusl-x64":
optional: true
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@img/sharp-wasm32@npm:0.33.4":
version: 0.33.4
resolution: "@img/sharp-wasm32@npm:0.33.4"
@ -3578,6 +3739,15 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-wasm32@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-wasm32@npm:0.33.5"
dependencies:
"@emnapi/runtime": "npm:^1.2.0"
conditions: cpu=wasm32
languageName: node
linkType: hard
"@img/sharp-win32-ia32@npm:0.33.4":
version: 0.33.4
resolution: "@img/sharp-win32-ia32@npm:0.33.4"
@ -3585,6 +3755,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-win32-ia32@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-win32-ia32@npm:0.33.5"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@img/sharp-win32-x64@npm:0.33.4":
version: 0.33.4
resolution: "@img/sharp-win32-x64@npm:0.33.4"
@ -3592,6 +3769,13 @@ __metadata:
languageName: node
linkType: hard
"@img/sharp-win32-x64@npm:0.33.5":
version: 0.33.5
resolution: "@img/sharp-win32-x64@npm:0.33.5"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@inquirer/confirm@npm:^3.0.0":
version: 3.1.20
resolution: "@inquirer/confirm@npm:3.1.20"
@ -3908,72 +4092,142 @@ __metadata:
languageName: node
linkType: hard
"@next/env@npm:14.2.5, @next/env@npm:^14.2.5":
"@next/env@npm:14.2.17":
version: 14.2.17
resolution: "@next/env@npm:14.2.17"
checksum: 10c0/181998dfe06275a7f43c56847bfbc4c521a10bd0e4a223b5b0fa1f73c24b0a993daa7ee736b82cbc3a6b64b13d965f7452dd4fc47f0a99909a1aa150862f5b1e
languageName: node
linkType: hard
"@next/env@npm:15.0.3":
version: 15.0.3
resolution: "@next/env@npm:15.0.3"
checksum: 10c0/63582fed80d6a28fff102c935095da71fd57ddf6b5f5d564e85ebdefdeb93298f7f7cf7d813c75b460c6627106717ea959b4c232939e7abb97d73d8b8467d4cd
languageName: node
linkType: hard
"@next/env@npm:^14.2.5":
version: 14.2.5
resolution: "@next/env@npm:14.2.5"
checksum: 10c0/63d8b88ac450b3c37940a9e2119a63a1074aca89908574ade6157a8aa295275dcb3ac5f69e00883fc55d0f12963b73b74e87ba32a5768a489f9609c6be57b699
languageName: node
linkType: hard
"@next/swc-darwin-arm64@npm:14.2.5":
version: 14.2.5
resolution: "@next/swc-darwin-arm64@npm:14.2.5"
"@next/swc-darwin-arm64@npm:14.2.17":
version: 14.2.17
resolution: "@next/swc-darwin-arm64@npm:14.2.17"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@next/swc-darwin-x64@npm:14.2.5":
version: 14.2.5
resolution: "@next/swc-darwin-x64@npm:14.2.5"
"@next/swc-darwin-arm64@npm:15.0.3":
version: 15.0.3
resolution: "@next/swc-darwin-arm64@npm:15.0.3"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@next/swc-darwin-x64@npm:14.2.17":
version: 14.2.17
resolution: "@next/swc-darwin-x64@npm:14.2.17"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@next/swc-linux-arm64-gnu@npm:14.2.5":
version: 14.2.5
resolution: "@next/swc-linux-arm64-gnu@npm:14.2.5"
"@next/swc-darwin-x64@npm:15.0.3":
version: 15.0.3
resolution: "@next/swc-darwin-x64@npm:15.0.3"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@next/swc-linux-arm64-gnu@npm:14.2.17":
version: 14.2.17
resolution: "@next/swc-linux-arm64-gnu@npm:14.2.17"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@next/swc-linux-arm64-musl@npm:14.2.5":
version: 14.2.5
resolution: "@next/swc-linux-arm64-musl@npm:14.2.5"
"@next/swc-linux-arm64-gnu@npm:15.0.3":
version: 15.0.3
resolution: "@next/swc-linux-arm64-gnu@npm:15.0.3"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@next/swc-linux-arm64-musl@npm:14.2.17":
version: 14.2.17
resolution: "@next/swc-linux-arm64-musl@npm:14.2.17"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@next/swc-linux-x64-gnu@npm:14.2.5":
version: 14.2.5
resolution: "@next/swc-linux-x64-gnu@npm:14.2.5"
"@next/swc-linux-arm64-musl@npm:15.0.3":
version: 15.0.3
resolution: "@next/swc-linux-arm64-musl@npm:15.0.3"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@next/swc-linux-x64-gnu@npm:14.2.17":
version: 14.2.17
resolution: "@next/swc-linux-x64-gnu@npm:14.2.17"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@next/swc-linux-x64-musl@npm:14.2.5":
version: 14.2.5
resolution: "@next/swc-linux-x64-musl@npm:14.2.5"
"@next/swc-linux-x64-gnu@npm:15.0.3":
version: 15.0.3
resolution: "@next/swc-linux-x64-gnu@npm:15.0.3"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@next/swc-linux-x64-musl@npm:14.2.17":
version: 14.2.17
resolution: "@next/swc-linux-x64-musl@npm:14.2.17"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@next/swc-win32-arm64-msvc@npm:14.2.5":
version: 14.2.5
resolution: "@next/swc-win32-arm64-msvc@npm:14.2.5"
"@next/swc-linux-x64-musl@npm:15.0.3":
version: 15.0.3
resolution: "@next/swc-linux-x64-musl@npm:15.0.3"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@next/swc-win32-arm64-msvc@npm:14.2.17":
version: 14.2.17
resolution: "@next/swc-win32-arm64-msvc@npm:14.2.17"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@next/swc-win32-ia32-msvc@npm:14.2.5":
version: 14.2.5
resolution: "@next/swc-win32-ia32-msvc@npm:14.2.5"
"@next/swc-win32-arm64-msvc@npm:15.0.3":
version: 15.0.3
resolution: "@next/swc-win32-arm64-msvc@npm:15.0.3"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@next/swc-win32-ia32-msvc@npm:14.2.17":
version: 14.2.17
resolution: "@next/swc-win32-ia32-msvc@npm:14.2.17"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@next/swc-win32-x64-msvc@npm:14.2.5":
version: 14.2.5
resolution: "@next/swc-win32-x64-msvc@npm:14.2.5"
"@next/swc-win32-x64-msvc@npm:14.2.17":
version: 14.2.17
resolution: "@next/swc-win32-x64-msvc@npm:14.2.17"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@next/swc-win32-x64-msvc@npm:15.0.3":
version: 15.0.3
resolution: "@next/swc-win32-x64-msvc@npm:15.0.3"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@ -6477,7 +6731,7 @@ __metadata:
find-up: "npm:^5.0.0"
image-size: "npm:^1.0.0"
loader-utils: "npm:^3.2.1"
next: "npm:^14.1.0"
next: "npm:^15.0.3"
node-polyfill-webpack-plugin: "npm:^2.0.1"
pnp-webpack-plugin: "npm:^1.7.0"
postcss: "npm:^8.4.38"
@ -6495,7 +6749,7 @@ __metadata:
typescript: "npm:^5.3.2"
webpack: "npm:^5.65.0"
peerDependencies:
next: ^13.5.0 || ^14.0.0
next: ^13.5.0 || ^14.0.0 || ^15.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: "workspace:^"
@ -7343,13 +7597,22 @@ __metadata:
languageName: node
linkType: hard
"@swc/counter@npm:^0.1.3":
"@swc/counter@npm:0.1.3, @swc/counter@npm:^0.1.3":
version: 0.1.3
resolution: "@swc/counter@npm:0.1.3"
checksum: 10c0/8424f60f6bf8694cfd2a9bca45845bce29f26105cda8cf19cdb9fd3e78dc6338699e4db77a89ae449260bafa1cc6bec307e81e7fb96dbf7dcfce0eea55151356
languageName: node
linkType: hard
"@swc/helpers@npm:0.5.13":
version: 0.5.13
resolution: "@swc/helpers@npm:0.5.13"
dependencies:
tslib: "npm:^2.4.0"
checksum: 10c0/b9df578401fc62405da9a6c31e79e447a2fd90f68b25b1daee12f2caf2821991bb89106f0397bc1acb4c4d84a8ce079d04b60b65f534496952e3bf8c9a52f40f
languageName: node
linkType: hard
"@swc/helpers@npm:0.5.5":
version: 0.5.5
resolution: "@swc/helpers@npm:0.5.5"
@ -21267,20 +21530,20 @@ __metadata:
languageName: node
linkType: hard
"next@npm:^14.1.0, next@npm:^14.2.5":
version: 14.2.5
resolution: "next@npm:14.2.5"
"next@npm:^14.2.5":
version: 14.2.17
resolution: "next@npm:14.2.17"
dependencies:
"@next/env": "npm:14.2.5"
"@next/swc-darwin-arm64": "npm:14.2.5"
"@next/swc-darwin-x64": "npm:14.2.5"
"@next/swc-linux-arm64-gnu": "npm:14.2.5"
"@next/swc-linux-arm64-musl": "npm:14.2.5"
"@next/swc-linux-x64-gnu": "npm:14.2.5"
"@next/swc-linux-x64-musl": "npm:14.2.5"
"@next/swc-win32-arm64-msvc": "npm:14.2.5"
"@next/swc-win32-ia32-msvc": "npm:14.2.5"
"@next/swc-win32-x64-msvc": "npm:14.2.5"
"@next/env": "npm:14.2.17"
"@next/swc-darwin-arm64": "npm:14.2.17"
"@next/swc-darwin-x64": "npm:14.2.17"
"@next/swc-linux-arm64-gnu": "npm:14.2.17"
"@next/swc-linux-arm64-musl": "npm:14.2.17"
"@next/swc-linux-x64-gnu": "npm:14.2.17"
"@next/swc-linux-x64-musl": "npm:14.2.17"
"@next/swc-win32-arm64-msvc": "npm:14.2.17"
"@next/swc-win32-ia32-msvc": "npm:14.2.17"
"@next/swc-win32-x64-msvc": "npm:14.2.17"
"@swc/helpers": "npm:0.5.5"
busboy: "npm:1.6.0"
caniuse-lite: "npm:^1.0.30001579"
@ -21321,7 +21584,68 @@ __metadata:
optional: true
bin:
next: dist/bin/next
checksum: 10c0/8df7d8ccc1a5bab03fa50dd6656c8a6f3750e81ef0b087dc329fea9346847c3094a933a890a8e87151dc32f0bc55020b8f6386d4565856d83bcc10895d29ec08
checksum: 10c0/ee350c1de7709da9a240752ebb8f56e0a01bbeae43177a43a463dd1d4ed071c414899afb3ff2d4228e38afa4029f0e0f0b56d9939b0f4a1bf383f77f32ec5ad9
languageName: node
linkType: hard
"next@npm:^15.0.3":
version: 15.0.3
resolution: "next@npm:15.0.3"
dependencies:
"@next/env": "npm:15.0.3"
"@next/swc-darwin-arm64": "npm:15.0.3"
"@next/swc-darwin-x64": "npm:15.0.3"
"@next/swc-linux-arm64-gnu": "npm:15.0.3"
"@next/swc-linux-arm64-musl": "npm:15.0.3"
"@next/swc-linux-x64-gnu": "npm:15.0.3"
"@next/swc-linux-x64-musl": "npm:15.0.3"
"@next/swc-win32-arm64-msvc": "npm:15.0.3"
"@next/swc-win32-x64-msvc": "npm:15.0.3"
"@swc/counter": "npm:0.1.3"
"@swc/helpers": "npm:0.5.13"
busboy: "npm:1.6.0"
caniuse-lite: "npm:^1.0.30001579"
postcss: "npm:8.4.31"
sharp: "npm:^0.33.5"
styled-jsx: "npm:5.1.6"
peerDependencies:
"@opentelemetry/api": ^1.1.0
"@playwright/test": ^1.41.2
babel-plugin-react-compiler: "*"
react: ^18.2.0 || 19.0.0-rc-66855b96-20241106
react-dom: ^18.2.0 || 19.0.0-rc-66855b96-20241106
sass: ^1.3.0
dependenciesMeta:
"@next/swc-darwin-arm64":
optional: true
"@next/swc-darwin-x64":
optional: true
"@next/swc-linux-arm64-gnu":
optional: true
"@next/swc-linux-arm64-musl":
optional: true
"@next/swc-linux-x64-gnu":
optional: true
"@next/swc-linux-x64-musl":
optional: true
"@next/swc-win32-arm64-msvc":
optional: true
"@next/swc-win32-x64-msvc":
optional: true
sharp:
optional: true
peerDependenciesMeta:
"@opentelemetry/api":
optional: true
"@playwright/test":
optional: true
babel-plugin-react-compiler:
optional: true
sass:
optional: true
bin:
next: dist/bin/next
checksum: 10c0/c5f6a57acb5f29063abc82d4d4417a048d0c2d5216d6ded6aa3fe32d60bb4835ed57dd34e2ef8fdda15579e97205820dc25bf34556b1d942a01a33d9ae7f88db
languageName: node
linkType: hard
@ -25724,6 +26048,75 @@ __metadata:
languageName: node
linkType: hard
"sharp@npm:^0.33.5":
version: 0.33.5
resolution: "sharp@npm:0.33.5"
dependencies:
"@img/sharp-darwin-arm64": "npm:0.33.5"
"@img/sharp-darwin-x64": "npm:0.33.5"
"@img/sharp-libvips-darwin-arm64": "npm:1.0.4"
"@img/sharp-libvips-darwin-x64": "npm:1.0.4"
"@img/sharp-libvips-linux-arm": "npm:1.0.5"
"@img/sharp-libvips-linux-arm64": "npm:1.0.4"
"@img/sharp-libvips-linux-s390x": "npm:1.0.4"
"@img/sharp-libvips-linux-x64": "npm:1.0.4"
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.0.4"
"@img/sharp-libvips-linuxmusl-x64": "npm:1.0.4"
"@img/sharp-linux-arm": "npm:0.33.5"
"@img/sharp-linux-arm64": "npm:0.33.5"
"@img/sharp-linux-s390x": "npm:0.33.5"
"@img/sharp-linux-x64": "npm:0.33.5"
"@img/sharp-linuxmusl-arm64": "npm:0.33.5"
"@img/sharp-linuxmusl-x64": "npm:0.33.5"
"@img/sharp-wasm32": "npm:0.33.5"
"@img/sharp-win32-ia32": "npm:0.33.5"
"@img/sharp-win32-x64": "npm:0.33.5"
color: "npm:^4.2.3"
detect-libc: "npm:^2.0.3"
semver: "npm:^7.6.3"
dependenciesMeta:
"@img/sharp-darwin-arm64":
optional: true
"@img/sharp-darwin-x64":
optional: true
"@img/sharp-libvips-darwin-arm64":
optional: true
"@img/sharp-libvips-darwin-x64":
optional: true
"@img/sharp-libvips-linux-arm":
optional: true
"@img/sharp-libvips-linux-arm64":
optional: true
"@img/sharp-libvips-linux-s390x":
optional: true
"@img/sharp-libvips-linux-x64":
optional: true
"@img/sharp-libvips-linuxmusl-arm64":
optional: true
"@img/sharp-libvips-linuxmusl-x64":
optional: true
"@img/sharp-linux-arm":
optional: true
"@img/sharp-linux-arm64":
optional: true
"@img/sharp-linux-s390x":
optional: true
"@img/sharp-linux-x64":
optional: true
"@img/sharp-linuxmusl-arm64":
optional: true
"@img/sharp-linuxmusl-x64":
optional: true
"@img/sharp-wasm32":
optional: true
"@img/sharp-win32-ia32":
optional: true
"@img/sharp-win32-x64":
optional: true
checksum: 10c0/6b81421ddfe6ee524d8d77e325c5e147fef22884e1c7b1656dfd89a88d7025894115da02d5f984261bf2e6daa16f98cadd1721c4ba408b4212b1d2a60f233484
languageName: node
linkType: hard
"shebang-command@npm:^1.2.0":
version: 1.2.0
resolution: "shebang-command@npm:1.2.0"

View File

@ -10,7 +10,7 @@ Contribute a new feature or bug fix to [Storybook's monorepo](https://github.com
## Prerequisites
* Ensure you have Node version 18 installed (suggestion: v18.16.0).
* Ensure if you are using Windows to use the Windows Subsystem for Linux (WSL).
* If you're working with Windows, all commands should be run in a terminal with administrator privileges.
## Initial setup

View File

@ -1 +1 @@
{"version":"8.5.0-alpha.3","info":{"plain":"- Addon Test: Fix post-install logic for Next.js Vite framework support - [#29524](https://github.com/storybookjs/storybook/pull/29524), thanks @valentinpalkovic!\n- Core: Prevent clipping box shadow on file search modal - [#29523](https://github.com/storybookjs/storybook/pull/29523), thanks @ghengeveld!"}}
{"version":"8.5.0-alpha.4","info":{"plain":"- Next.js: Add support for Next 15 - [#29587](https://github.com/storybookjs/storybook/pull/29587), thanks @yannbf!\n- UI: Add Yarn to About Section - [#29225](https://github.com/storybookjs/storybook/pull/29225), thanks @grantwforsythe!"}}

View File

@ -15,7 +15,7 @@
"upload-bench": "cd scripts; yarn upload-bench",
"vite-ecosystem-ci:before-test": "node ./scripts/vite-ecosystem-ci/before-test.js && cd ./sandbox/react-vite-default-ts && yarn install",
"vite-ecosystem-ci:build": "yarn task --task sandbox --template react-vite/default-ts --start-from=install",
"vite-ecosystem-ci:test": "yarn task --task test-runner-dev --template react-vite/default-ts --start-from=dev"
"vite-ecosystem-ci:test": "yarn task --task test-runner-dev --template react-vite/default-ts --start-from=dev && yarn task --task test-runner --template react-vite/default-ts --start-from=build"
},
"packageManager": "yarn@4.4.0+sha512.91d93b445d9284e7ed52931369bc89a663414e5582d00eea45c67ddc459a2582919eece27c412d6ffd1bd0793ff35399381cb229326b961798ce4f4cc60ddfdb"
}

View File

@ -0,0 +1,493 @@
import { BigQuery } from '@google-cloud/bigquery';
import { InvalidArgumentError, program } from 'commander';
import detectFreePort from 'detect-port';
import { mkdir, readdir, rm, stat, writeFile } from 'fs/promises';
import pLimit from 'p-limit';
import { join } from 'path';
import picocolors from 'picocolors';
import { x } from 'tinyexec';
import dedent from 'ts-dedent';
import versions from '../../code/core/src/common/versions';
import { maxConcurrentTasks } from '../utils/concurrency';
import { esMain } from '../utils/esmain';
const Thresholds = {
SELF_SIZE_RATIO: 0.1,
SELF_SIZE_ABSOLUTE: 10_000,
DEPS_SIZE_RATIO: 0.1,
DEPS_SIZE_ABSOLUTE: 10_000,
DEPS_COUNT_ABSOLUTE: 1,
} as const;
const BENCH_PACKAGES_PATH = join(__dirname, '..', '..', 'bench', 'packages');
const REGISTRY_PORT = 6001;
const GCP_CREDENTIALS = JSON.parse(process.env.GCP_CREDENTIALS || '{}');
const bigQueryBenchTable = new BigQuery({
projectId: GCP_CREDENTIALS.project_id,
credentials: GCP_CREDENTIALS,
})
.dataset('benchmark_results')
.table('package_bench');
type PackageName = keyof typeof versions;
type Result = {
package: PackageName;
dependencyCount: number;
selfSize: number;
dependencySize: number;
};
type ComparisonResult = {
package: PackageName;
dependencyCount: {
base: number;
new: number;
diff: number;
};
selfSize: {
base: number;
new: number;
diff: number;
};
dependencySize: {
base: number;
new: number;
diff: number;
};
};
type ResultMap = Record<PackageName, Result>;
type ComparisonResultMap = Record<PackageName, ComparisonResult>;
/**
* This function benchmarks the size of Storybook packages and their dependencies. For each package,
* the steps are:
*
* 1. Create a temporary directory in /bench/packages and create a package.json file that only depends
* on that one package
* 2. Install the package and its dependencies, without peer dependencies
* 3. Measure the size of the package and its dependencies, and count the number of dependencies
* (including transitive)
* 4. Print and return the results
*/
export const benchPackage = async (packageName: PackageName) => {
console.log(`Benching ${picocolors.blue(packageName)}...`);
const tmpBenchPackagePath = join(BENCH_PACKAGES_PATH, packageName.replace('@storybook', ''));
await rm(tmpBenchPackagePath, { recursive: true }).catch(() => {});
await mkdir(tmpBenchPackagePath, { recursive: true });
await writeFile(
join(tmpBenchPackagePath, 'package.json'),
JSON.stringify(
{
name: `${packageName}-bench`,
version: '1.0.0',
dependencies: {
[packageName]: versions[packageName],
},
// Overrides ensures that Storybook packages outside the monorepo are using the versions we have in the monorepo
overrides: versions,
},
null,
2
)
);
const npmInstallResult = await x(
'npm',
`install --registry http://localhost:6001 --omit peer --json`.split(' '),
{
nodeOptions: { cwd: tmpBenchPackagePath },
}
);
const { added } = JSON.parse(npmInstallResult.stdout) as { added: number };
// -1 of reported packages added because we shouldn't count the actual package as a dependency
const dependencyCount = added - 1;
const getDirSize = async (path: string) => {
const entities = await readdir(path, {
recursive: true,
withFileTypes: true,
});
const stats = await Promise.all(
entities
.filter((entity) => entity.isFile())
.map((entity) => stat(join(entity.parentPath, entity.name)))
);
return stats.reduce((acc, { size }) => acc + size, 0);
};
const nodeModulesSize = await getDirSize(join(tmpBenchPackagePath, 'node_modules'));
const selfSize = await getDirSize(join(tmpBenchPackagePath, 'node_modules', packageName));
const dependencySize = nodeModulesSize - selfSize;
const result: Result = {
package: packageName,
dependencyCount,
selfSize,
dependencySize,
};
console.log(`Done benching ${picocolors.blue(packageName)}`);
return result;
};
const toHumanReadable = (result: Partial<Result> | Partial<ComparisonResult>) => {
const formatBytes = (bytes: number, diff = false) => {
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = Math.abs(bytes);
let unitIndex = 0;
while (size >= 1000 && unitIndex < units.length - 1) {
size /= 1000;
unitIndex++;
}
// B, KB = 0 decimal places
// MB, GB, TB = 2 decimal places
const decimals = unitIndex < 2 ? 0 : 2;
const formattedSize = `${size.toFixed(decimals)} ${units[unitIndex]}`;
if (bytes < 0) {
return `-${formattedSize}`;
}
if (diff && bytes > 0) {
return `+${formattedSize}`;
}
return formattedSize;
};
if (typeof result.dependencyCount === 'number') {
const { dependencyCount, selfSize, dependencySize } = result as Result;
return {
package: result.package,
dependencyCount: dependencyCount.toString(),
selfSize: formatBytes(selfSize),
dependencySize: formatBytes(dependencySize),
totalSize: formatBytes(selfSize + dependencySize),
};
}
const { dependencyCount, selfSize, dependencySize } = result as ComparisonResult;
return {
package: result.package,
dependencyCount: {
base: dependencyCount.base.toString(),
new: dependencyCount.new.toString(),
diff: `${dependencyCount.diff > 0 ? '+' : dependencyCount.diff < 0 ? '-' : ''}${dependencyCount.diff}`,
},
selfSize: {
base: formatBytes(selfSize.base),
new: formatBytes(selfSize.new),
diff: formatBytes(selfSize.diff, true),
},
dependencySize: {
base: formatBytes(dependencySize.base),
new: formatBytes(dependencySize.new),
diff: formatBytes(dependencySize.diff, true),
},
totalSize: {
base: formatBytes(selfSize.base + dependencySize.base),
new: formatBytes(selfSize.new + dependencySize.new),
diff: formatBytes(selfSize.diff + dependencySize.diff, true),
},
};
};
const saveLocally = async ({
results,
filename,
}: {
results: Partial<ResultMap | ComparisonResultMap>;
filename: string;
diff?: boolean;
}) => {
const resultPath = join(BENCH_PACKAGES_PATH, filename);
console.log(`Saving results to ${picocolors.magenta(resultPath)}...`);
const humanReadableResults = Object.entries(results).reduce(
(acc, [packageName, result]) => {
acc[packageName as PackageName] = toHumanReadable(result);
return acc;
},
{} as Record<PackageName, ReturnType<typeof toHumanReadable>>
);
await writeFile(resultPath, JSON.stringify(humanReadableResults, null, 2));
};
const compareResults = async ({
results,
baseBranch,
}: {
results: ResultMap;
baseBranch: string;
}) => {
console.log(`Comparing results with base branch ${picocolors.magenta(baseBranch)}...`);
const [baseResults] = await bigQueryBenchTable.query({
query: `
WITH
latest_packages AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY package ORDER BY benchmarkedAt DESC) AS row_number
FROM
\`storybook-benchmark.benchmark_results.package_bench\`
WHERE
branch = @baseBranch
AND package IN UNNEST(@packages)
AND benchmarkedAt > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) )
SELECT *
FROM latest_packages
WHERE row_number = 1;`,
params: { baseBranch, packages: Object.keys(results) },
});
const comparisonResults = {} as ComparisonResultMap;
for (const result of Object.values(results)) {
let baseResult = baseResults.find((row) => row.package === result.package);
if (!baseResult) {
console.warn(
`No base result found for ${picocolors.blue(result.package)}, comparing with zero values.`
);
baseResult = {
package: result.package,
dependencyCount: 0,
selfSize: 0,
dependencySize: 0,
};
}
comparisonResults[result.package] = {
package: result.package,
dependencyCount: {
base: baseResult.dependencyCount,
new: result.dependencyCount,
diff: result.dependencyCount - baseResult.dependencyCount,
},
selfSize: {
base: baseResult.selfSize,
new: result.selfSize,
diff: result.selfSize - baseResult.selfSize,
},
dependencySize: {
base: baseResult.dependencySize,
new: result.dependencySize,
diff: result.dependencySize - baseResult.dependencySize,
},
};
}
console.log(picocolors.green('Done comparing results'));
return comparisonResults;
};
const filterResultsByThresholds = (comparisonResults: ComparisonResultMap) => {
const filteredResults: Partial<ComparisonResultMap> = {};
for (const comparisonResult of Object.values(comparisonResults)) {
const exceedsThresholds =
Math.abs(comparisonResult.selfSize.diff) > Thresholds.SELF_SIZE_ABSOLUTE ||
Math.abs(comparisonResult.selfSize.diff) / comparisonResult.selfSize.base >
Thresholds.SELF_SIZE_RATIO ||
Math.abs(comparisonResult.dependencySize.diff) > Thresholds.DEPS_SIZE_ABSOLUTE ||
Math.abs(comparisonResult.dependencySize.diff) / comparisonResult.dependencySize.base >
Thresholds.DEPS_SIZE_RATIO ||
Math.abs(comparisonResult.dependencyCount.diff) > Thresholds.DEPS_COUNT_ABSOLUTE;
if (exceedsThresholds) {
filteredResults[comparisonResult.package] = comparisonResult;
}
}
const amountAboveThreshold = Object.keys(filteredResults).length;
const color = amountAboveThreshold === 0 ? picocolors.green : picocolors.red;
console.log(color(`${amountAboveThreshold} packages exceeded the thresholds`));
return filteredResults;
};
const uploadToBigQuery = async ({
results,
branch,
commit,
benchmarkedAt,
}: {
results: ResultMap;
branch: string;
commit: string;
benchmarkedAt: Date;
}) => {
console.log('Uploading results to BigQuery...');
const rows = Object.values(results).map((result) => ({
branch,
commit,
benchmarkedAt,
package: result.package,
selfSize: result.selfSize,
dependencySize: result.dependencySize,
dependencyCount: result.dependencyCount,
}));
await bigQueryBenchTable.insert(rows);
};
const uploadToGithub = async ({
results,
headBranch,
baseBranch,
commit,
benchmarkedAt,
pullRequest,
}: {
results: Partial<ComparisonResultMap>;
headBranch: string;
baseBranch: string;
commit: string;
benchmarkedAt: Date;
pullRequest: number;
}) => {
console.log('Uploading results to GitHub...');
// const response = await fetch('http://localhost:3000/package-bench', {
const response = await fetch('https://storybook-benchmark-bot.vercel.app/package-bench', {
method: 'POST',
body: JSON.stringify({
owner: 'storybookjs',
repo: 'storybook',
issueNumber: pullRequest,
headBranch,
baseBranch,
commit,
benchmarkedAt: benchmarkedAt.toISOString(),
results,
}),
});
if (response.status < 200 || response.status >= 400) {
const body = await response.text();
throw new Error(`Failed to upload results to GitHub.
STATUS: ${response.status} - ${response.statusText}
BODY:
${body}`);
}
};
const run = async () => {
program
.option(
'-b, --base-branch <string>',
'The base branch to compare the results with. Requires GCP_CREDENTIALS env var'
)
.option(
'-p, --pull-request <number>',
'The PR number to add compare results to. Only used together with --baseBranch',
function parseInt(value) {
const parsedValue = Number.parseInt(value);
if (Number.isNaN(parsedValue)) {
throw new InvalidArgumentError('Must be a number');
}
return parsedValue;
}
)
.option('-u, --upload', 'Upload results to BigQuery. Requires GCP_CREDENTIALS env var')
.argument(
'[packages...]',
'which packages to bench. If omitted, all packages are benched',
function parsePackages(value) {
const parsedValue = value.split(' ');
parsedValue.forEach((packageName) => {
if (!Object.keys(versions).includes(packageName)) {
throw new InvalidArgumentError(`Package '${packageName}' not found in the monorepo`);
}
});
return parsedValue;
}
);
program.parse(process.argv);
const packageNames = (
program.args.length > 0 ? program.args : Object.keys(versions)
) as PackageName[];
const options = program.opts<{ pullRequest?: number; baseBranch?: string; upload?: boolean }>();
if (options.upload || options.baseBranch) {
if (!GCP_CREDENTIALS.project_id) {
throw new Error(
'GCP_CREDENTIALS env var is required to upload to BigQuery or compare against a base branch'
);
}
}
if ((await detectFreePort(REGISTRY_PORT)) === REGISTRY_PORT) {
throw new Error(dedent`The local verdaccio registry must be running in the background for package benching to work,
and packages must be published to it in --no-link mode with 'yarn --task publish --no-link'
Then run the registry with 'yarn --task run-registry --no-link'`);
}
// The amount of VCPUs for this task in CI is 2 (medium resource)
const amountOfVCPUs = 2;
const concurrentLimit = process.env.CI ? amountOfVCPUs - 1 : maxConcurrentTasks;
const limit = pLimit(concurrentLimit);
const progressIntervalId = setInterval(() => {
const doneCount = packageNames.length - limit.activeCount - limit.pendingCount;
if (doneCount === packageNames.length) {
clearInterval(progressIntervalId);
return;
}
console.log(
`Benching status: ${picocolors.red(limit.pendingCount)} pending, ${picocolors.yellow(limit.activeCount)} running, ${picocolors.green(doneCount)} done...`
);
}, 2_000);
const resultsArray = await Promise.all(
packageNames.map((packageName) => limit(() => benchPackage(packageName)))
);
const results = resultsArray.reduce((acc, result) => {
acc[result.package] = result;
return acc;
}, {} as ResultMap);
await saveLocally({
filename: `results.json`,
results,
});
const headBranch =
process.env.CIRCLE_BRANCH ||
(await x('git', 'rev-parse --abbrev-ref HEAD'.split(' '))).stdout.trim();
const commit =
process.env.CIRCLE_SHA1 || (await x('git', 'rev-parse HEAD'.split(' '))).stdout.trim();
const benchmarkedAt = new Date();
if (options.upload) {
await uploadToBigQuery({ results, branch: headBranch, commit, benchmarkedAt });
}
if (options.baseBranch) {
const comparisonResults = await compareResults({ results, baseBranch: options.baseBranch });
const resultsAboveThreshold = filterResultsByThresholds(comparisonResults);
await saveLocally({
filename: `compare-with-${options.baseBranch}.json`,
results: comparisonResults,
diff: true,
});
await saveLocally({
filename: `comparisons-above-threshold-with-${options.baseBranch}.json`,
results: resultsAboveThreshold,
diff: true,
});
if (options.pullRequest) {
await uploadToGithub({
results: resultsAboveThreshold,
pullRequest: options.pullRequest,
baseBranch: options.baseBranch,
headBranch,
commit,
benchmarkedAt,
});
}
}
console.log(picocolors.green('Done benching all packages'));
};
if (esMain(import.meta.url)) {
run().catch((err) => {
console.error(err);
process.exit(1);
});
}

View File

@ -4,6 +4,7 @@
"private": true,
"type": "module",
"scripts": {
"bench-packages": "jiti ./bench/bench-packages.ts",
"build-package": "jiti ./build-package.ts",
"check": "jiti ./prepare/check-scripts.ts",
"check-package": "jiti ./check-package.ts",
@ -174,6 +175,7 @@
"slash": "^3.0.0",
"sort-package-json": "^2.10.0",
"tiny-invariant": "^1.3.3",
"tinyexec": "^0.3.0",
"trash": "^7.2.0",
"ts-dedent": "^2.2.0",
"tsup": "^6.7.0",

View File

@ -118,7 +118,10 @@ export const nodeInternals = [
...require('module').builtinModules.flatMap((m: string) => [m, `node:${m}`]),
];
export const getWorkspace = async () => {
type PackageJson = typefest.PackageJson &
Required<Pick<typefest.PackageJson, 'name' | 'version'>> & { path: string };
export const getWorkspace = async (): Promise<PackageJson[]> => {
const codePackage = await readJson(join(CODE_DIRECTORY, 'package.json'));
const {
workspaces: { packages: patterns },
@ -142,8 +145,7 @@ export const getWorkspace = async () => {
return null;
}
const pkg = await readJson(packageJsonPath);
return { ...pkg, path: packagePath } as typefest.PackageJson &
Required<Pick<typefest.PackageJson, 'name' | 'version'>> & { path: string };
return { ...pkg, path: packagePath } as PackageJson;
})
).then((packages) => packages.filter((p) => p !== null));
};

View File

@ -1,12 +1,12 @@
import { exec } from 'node:child_process';
import { mkdir } from 'node:fs/promises';
import { mkdir, rm } from 'node:fs/promises';
import http from 'node:http';
import type { Server } from 'node:http';
import { join, resolve as resolvePath } from 'node:path';
import { program } from 'commander';
// eslint-disable-next-line depend/ban-dependencies
import { execa, execaSync } from 'execa';
import { execa } from 'execa';
// eslint-disable-next-line depend/ban-dependencies
import { pathExists, readJSON, remove } from 'fs-extra';
import pLimit from 'p-limit';
@ -207,7 +207,7 @@ const run = async () => {
await publish(packages, 'http://localhost:6002');
}
await execa('npx', ['rimraf', '.npmrc'], { cwd: root });
await rm(join(root, '.npmrc'), { force: true });
if (!opts.open) {
verdaccioServer.close();
@ -217,6 +217,7 @@ const run = async () => {
run().catch((e) => {
logger.error(e);
execaSync('npx', ['rimraf', '.npmrc'], { cwd: root });
process.exit(1);
rm(join(root, '.npmrc'), { force: true }).then(() => {
process.exit(1);
});
});

View File

@ -1660,6 +1660,7 @@ __metadata:
slash: "npm:^3.0.0"
sort-package-json: "npm:^2.10.0"
tiny-invariant: "npm:^1.3.3"
tinyexec: "npm:^0.3.0"
trash: "npm:^7.2.0"
ts-dedent: "npm:^2.2.0"
tsup: "npm:^6.7.0"
@ -13452,9 +13453,9 @@ __metadata:
linkType: hard
"tinyexec@npm:^0.3.0":
version: 0.3.1
resolution: "tinyexec@npm:0.3.1"
checksum: 10c0/11e7a7c5d8b3bddf8b5cbe82a9290d70a6fad84d528421d5d18297f165723cb53d2e737d8f58dcce5ca56f2e4aa2d060f02510b1f8971784f97eb3e9aec28f09
version: 0.3.0
resolution: "tinyexec@npm:0.3.0"
checksum: 10c0/138a4f4241aea6b6312559508468ab275a31955e66e2f57ed206e0aaabecee622624f208c5740345f0a66e33478fd065e359ed1eb1269eb6fd4fa25d44d0ba3b
languageName: node
linkType: hard

View File

@ -15,6 +15,6 @@ function Component() {
export default {
component: Component,
};
} as Meta<typeof Component>;
export const Default = {};

View File

@ -20,7 +20,7 @@ function Component() {
export default {
component: Component,
};
} as Meta<typeof Component>;
export const Default: StoryObj = {
play: async () => {

View File

@ -1,5 +1,4 @@
import type { Meta } from '@storybook/react';
import type { StoryObj } from '@storybook/react';
import type { Meta, StoryObj } from '@storybook/react';
import { expect, userEvent, within } from '@storybook/test';
import { cookies, headers } from '@storybook/nextjs/headers.mock';
import NextHeader from './NextHeader';

View File

@ -15,6 +15,6 @@ const Component = () => (
export default {
component: Component,
};
} as Meta<typeof Component>;
export const Default = {};

View File

@ -11,7 +11,7 @@ const Component = () => <button>test</button>
export default {
title: 'Addons/Test',
component: Component,
};
} as Meta<typeof Component>;
const { pass } = instrument({
pass: async () => {},