From f34122d3561ef4245c139b2b0df9acd40e76ca58 Mon Sep 17 00:00:00 2001 From: Bill Collins Date: Fri, 12 Sep 2025 08:55:38 +0100 Subject: [PATCH 01/10] Create check-sandbox task --- scripts/task.ts | 2 ++ scripts/tasks/check-sandbox.ts | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 scripts/tasks/check-sandbox.ts diff --git a/scripts/task.ts b/scripts/task.ts index c2e477953964..42a235448b70 100644 --- a/scripts/task.ts +++ b/scripts/task.ts @@ -17,6 +17,7 @@ import { version } from '../code/package.json'; import { bench } from './tasks/bench'; import { build } from './tasks/build'; import { check } from './tasks/check'; +import { checkSandbox } from './tasks/check-sandbox'; import { chromatic } from './tasks/chromatic'; import { compile } from './tasks/compile'; import { dev } from './tasks/dev'; @@ -91,6 +92,7 @@ export const tasks = { // These tasks pertain to a single sandbox in the ../sandboxes dir generate, sandbox, + 'check-sandbox': checkSandbox, dev, 'smoke-test': smokeTest, build, diff --git a/scripts/tasks/check-sandbox.ts b/scripts/tasks/check-sandbox.ts new file mode 100644 index 000000000000..cefb36fd930a --- /dev/null +++ b/scripts/tasks/check-sandbox.ts @@ -0,0 +1,24 @@ +import { access } from 'node:fs/promises'; + +import type { Task } from '../task'; +import { exec } from '../utils/exec'; + +async function pathExists(path: string) { + try { + await access(path); + return true; + } catch { + return false; + } +} + +export const checkSandbox: Task = { + description: 'Typecheck the a sandbox', + dependsOn: ['sandbox'], + async ready({ builtSandboxDir }) { + return pathExists(builtSandboxDir); + }, + async run({ sandboxDir }, { dryRun, debug }) { + await exec(`yarn tsc -p tsconfig.app.json`, { cwd: sandboxDir }, { dryRun, debug }); + }, +}; From 4c198edb01999ad3f329350937f041f938e1d7bf Mon Sep 17 00:00:00 2001 From: Bill Collins Date: Fri, 12 Sep 2025 09:00:32 +0100 Subject: [PATCH 02/10] Remove typescript version override We were pinning to a version that did not support a feature we were setting --- scripts/utils/yarn.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/utils/yarn.ts b/scripts/utils/yarn.ts index 4c3967876d44..0d3a2c2b791c 100644 --- a/scripts/utils/yarn.ts +++ b/scripts/utils/yarn.ts @@ -108,7 +108,6 @@ export const addWorkaroundResolutions = async ({ '@testing-library/dom': '^9.3.4', '@testing-library/jest-dom': '^6.6.3', '@testing-library/user-event': '^14.5.2', - typescript: '~5.7.3', rollup: '4.44.2', }; From a2ac7dac4eba73458d25ad9d661047a3a209c086 Mon Sep 17 00:00:00 2001 From: Bill Collins Date: Fri, 12 Sep 2025 14:06:00 +0100 Subject: [PATCH 03/10] Fixes --- .../cli-storybook/src/sandbox-templates.ts | 2 +- .../react/template/stories/csf4.stories.tsx | 3 ++- .../template/stories/decorators.stories.tsx | 2 +- .../10278-ts-multiple-components/input.tsx | 4 +-- .../8143-ts-react-fc-generics/input.tsx | 2 ++ .../8740-ts-multi-props/input.tsx | 2 ++ .../9575-ts-camel-case/input.tsx | 4 ++- .../ts-function-component/input.tsx | 4 ++- .../docgen-components/ts-react-fc/input.tsx | 2 +- .../docgen-components/ts-types/input.tsx | 2 ++ scripts/tasks/check-sandbox.ts | 2 +- scripts/tasks/sandbox-parts.ts | 25 +++++++++++++++++++ scripts/tasks/sandbox.ts | 1 + 13 files changed, 46 insertions(+), 9 deletions(-) diff --git a/code/lib/cli-storybook/src/sandbox-templates.ts b/code/lib/cli-storybook/src/sandbox-templates.ts index c41dba130757..79a4052865b3 100644 --- a/code/lib/cli-storybook/src/sandbox-templates.ts +++ b/code/lib/cli-storybook/src/sandbox-templates.ts @@ -278,7 +278,7 @@ export const baseTemplates = { }, modifications: { useCsfFactory: true, - extraDependencies: ['prop-types'], + extraDependencies: ['prop-types', '@types/prop-types'], mainConfig: { features: { developmentModeForBuild: true, diff --git a/code/renderers/react/template/stories/csf4.stories.tsx b/code/renderers/react/template/stories/csf4.stories.tsx index ae0e65e08a30..2a68cfef500b 100644 --- a/code/renderers/react/template/stories/csf4.stories.tsx +++ b/code/renderers/react/template/stories/csf4.stories.tsx @@ -1,4 +1,5 @@ -// @ts-expect-error this will be part of the package.json of the sandbox +// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- we can't expect error as it is an error in development but it isn't sandbox +// @ts-ignore only present in sandbox import preview from '#.storybook/preview'; const meta = preview.meta({ diff --git a/code/renderers/react/template/stories/decorators.stories.tsx b/code/renderers/react/template/stories/decorators.stories.tsx index e1014e7ee911..86a9a9cbf438 100644 --- a/code/renderers/react/template/stories/decorators.stories.tsx +++ b/code/renderers/react/template/stories/decorators.stories.tsx @@ -43,7 +43,7 @@ export const Context: StoryObj = { ), ], - render: function Render(args, context) { + render: function Render() { const value = useContext(TestContext); if (!value) { diff --git a/code/renderers/react/template/stories/docgen-components/10278-ts-multiple-components/input.tsx b/code/renderers/react/template/stories/docgen-components/10278-ts-multiple-components/input.tsx index 4490901b9790..476a6ef2d22c 100644 --- a/code/renderers/react/template/stories/docgen-components/10278-ts-multiple-components/input.tsx +++ b/code/renderers/react/template/stories/docgen-components/10278-ts-multiple-components/input.tsx @@ -10,12 +10,12 @@ interface IBProps { } /** A component */ -const A = (props: IAProps): JSX.Element => { +const A = (props: IAProps): React.JSX.Element => { return <>Hi {props.aProperty}; }; /** B component */ -const B = (props: IBProps): JSX.Element => { +const B = (props: IBProps): React.JSX.Element => { return <>Hi {props.bProperty}; }; diff --git a/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/input.tsx b/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/input.tsx index 9116328f1937..8f13c9ab08c7 100644 --- a/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/input.tsx +++ b/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/input.tsx @@ -5,6 +5,8 @@ interface Props { margin: number; } +// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- we can't expect error as it isn't an error in development but it is in sandbox +// @ts-ignore unused props export const Text: React.FC = ({ padding = '0', margin }) => <>Text; export const component = Text; diff --git a/code/renderers/react/template/stories/docgen-components/8740-ts-multi-props/input.tsx b/code/renderers/react/template/stories/docgen-components/8740-ts-multi-props/input.tsx index 1fb6607d0b71..9813008e9ef0 100644 --- a/code/renderers/react/template/stories/docgen-components/8740-ts-multi-props/input.tsx +++ b/code/renderers/react/template/stories/docgen-components/8740-ts-multi-props/input.tsx @@ -18,6 +18,8 @@ export const Paragraph: React.FC = ({ size, children }) => (
{children}
); +// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- we can't expect error as it isn't an error in 18 (development) but it is in 19 (sandbox) +// @ts-ignore not present on react 19 Paragraph.defaultProps = { size: 'md' }; export const component = Header; diff --git a/code/renderers/react/template/stories/docgen-components/9575-ts-camel-case/input.tsx b/code/renderers/react/template/stories/docgen-components/9575-ts-camel-case/input.tsx index feee06599e1f..fd721e12299c 100644 --- a/code/renderers/react/template/stories/docgen-components/9575-ts-camel-case/input.tsx +++ b/code/renderers/react/template/stories/docgen-components/9575-ts-camel-case/input.tsx @@ -8,7 +8,7 @@ export interface IProps { color?: string; } -const iconButton: FC = function IconButton(props) { +const iconButton: FC = function IconButton() { return
icon-button
; }; @@ -17,6 +17,8 @@ iconButton.propTypes = { color: PropTypes.string, }; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- we can't expect error as it isn't an error in 18 (development) but it is in 19 (sandbox) +// @ts-ignore not present on react 19 iconButton.defaultProps = { color: 'primary', }; diff --git a/code/renderers/react/template/stories/docgen-components/ts-function-component/input.tsx b/code/renderers/react/template/stories/docgen-components/ts-function-component/input.tsx index ba15a430f025..14b249d1ea2f 100644 --- a/code/renderers/react/template/stories/docgen-components/ts-function-component/input.tsx +++ b/code/renderers/react/template/stories/docgen-components/ts-function-component/input.tsx @@ -5,7 +5,7 @@ import React from 'react'; import { imported } from '../imported'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore (css import not supported in TS) -import * as styles from '../imported.module.css'; +import styles from '../imported.module.css'; const local = 'local-value'; @@ -37,6 +37,8 @@ export const PropsWriter: React.FC = (props: PropsWriterProps)
{JSON.stringify(props)}
); +// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- we can't expect error as it isn't an error in 18 (development) but it is in 19 (sandbox) +// @ts-ignore not present on react 19 PropsWriter.defaultProps = { numberOptional: 1, stringOptional: 'stringOptional', diff --git a/code/renderers/react/template/stories/docgen-components/ts-react-fc/input.tsx b/code/renderers/react/template/stories/docgen-components/ts-react-fc/input.tsx index 2e6e3d6821b1..56ad85711dc4 100644 --- a/code/renderers/react/template/stories/docgen-components/ts-react-fc/input.tsx +++ b/code/renderers/react/template/stories/docgen-components/ts-react-fc/input.tsx @@ -88,6 +88,6 @@ interface TypeScriptPropsProps { inlinedNumericLiteralUnion: 0 | 1 | 2; } -export const TypeScriptProps: FC = (props) =>
TypeScript!
; +export const TypeScriptProps: FC = () =>
TypeScript!
; export const component = TypeScriptProps; diff --git a/code/renderers/react/template/stories/docgen-components/ts-types/input.tsx b/code/renderers/react/template/stories/docgen-components/ts-types/input.tsx index 4cb8703a9daa..23c7682a57ea 100644 --- a/code/renderers/react/template/stories/docgen-components/ts-types/input.tsx +++ b/code/renderers/react/template/stories/docgen-components/ts-types/input.tsx @@ -93,6 +93,8 @@ interface TypeScriptPropsProps { } export const TypeScriptProps: FC = () =>
TypeScript!
; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- we can't expect error as it isn't an error in 18 (development) but it is in 19 (sandbox) +// @ts-ignore not present on react 19 TypeScriptProps.defaultProps = { any: 'Any value', string: 'A string value', diff --git a/scripts/tasks/check-sandbox.ts b/scripts/tasks/check-sandbox.ts index cefb36fd930a..aa4084b3ba67 100644 --- a/scripts/tasks/check-sandbox.ts +++ b/scripts/tasks/check-sandbox.ts @@ -19,6 +19,6 @@ export const checkSandbox: Task = { return pathExists(builtSandboxDir); }, async run({ sandboxDir }, { dryRun, debug }) { - await exec(`yarn tsc -p tsconfig.app.json`, { cwd: sandboxDir }, { dryRun, debug }); + await exec(`yarn typecheck`, { cwd: sandboxDir }, { dryRun, debug }); }, }; diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 6d354b340fc5..c383724ebdcb 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -217,6 +217,9 @@ export const init: Task['run'] = async ( case '@storybook/angular': await prepareAngularSandbox(cwd, template.name); break; + case '@storybook/react-vite': + await prepareViteSandbox(cwd); + break; default: } @@ -878,6 +881,28 @@ async function prepareReactNativeWebSandbox(cwd: string) { } } +async function prepareViteSandbox(cwd: string) { + const packageJsonPath = join(cwd, 'package.json'); + const packageJson = await readJson(packageJsonPath); + + packageJson.scripts = { + ...packageJson.scripts, + typecheck: 'yarn tsc -p tsconfig.app.json', + }; + await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); + + const tsConfigPath = join(cwd, 'tsconfig.app.json'); + const tsConfigContent = await readFile(tsConfigPath, { encoding: 'utf-8' }); + // This does not preserve comments, but that shouldn't be an issue for sandboxes + const tsConfigJson = JSON5.parse(tsConfigContent); + + // We use enums + tsConfigJson.compilerOptions.erasableSyntaxOnly = false; + // Lots of unnecessary imports of react that need fixing + tsConfigJson.compilerOptions.noUnusedLocals = false; + await writeFile(tsConfigPath, JSON.stringify(tsConfigJson, null, 2)); +} + async function prepareAngularSandbox(cwd: string, templateName: string) { const angularJson = await readJson(join(cwd, 'angular.json')); diff --git a/scripts/tasks/sandbox.ts b/scripts/tasks/sandbox.ts index 9078d5c4be23..dece41e01f59 100644 --- a/scripts/tasks/sandbox.ts +++ b/scripts/tasks/sandbox.ts @@ -84,6 +84,7 @@ export const sandbox: Task = { // Adding the dep makes sure that even npx will use the linked workspace version. '@storybook/cli', 'lodash-es', + '@types/lodash-es', 'uuid', ]; From 6b6e9b6487f006250584ae1e25e4326af5f121e3 Mon Sep 17 00:00:00 2001 From: Bill Collins Date: Fri, 12 Sep 2025 14:35:38 +0100 Subject: [PATCH 04/10] Disable skipLibCheck --- scripts/tasks/sandbox-parts.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index c383724ebdcb..e574c3291314 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -900,6 +900,8 @@ async function prepareViteSandbox(cwd: string) { tsConfigJson.compilerOptions.erasableSyntaxOnly = false; // Lots of unnecessary imports of react that need fixing tsConfigJson.compilerOptions.noUnusedLocals = false; + // Means we can check our own public types + tsConfigJson.compilerOptions.skipLibCheck = false; await writeFile(tsConfigPath, JSON.stringify(tsConfigJson, null, 2)); } From 730f2792c211cd4fce9c4643a4d5c3d200c2b1b2 Mon Sep 17 00:00:00 2001 From: Bill Collins Date: Fri, 12 Sep 2025 17:45:46 +0100 Subject: [PATCH 05/10] Let @types/mdx know we're using React --- code/addons/docs/src/index.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/code/addons/docs/src/index.ts b/code/addons/docs/src/index.ts index 20953c9bc649..3a13c486dde2 100644 --- a/code/addons/docs/src/index.ts +++ b/code/addons/docs/src/index.ts @@ -1,3 +1,5 @@ +import type React from 'react'; + import { definePreviewAddon } from 'storybook/internal/csf'; import * as addonAnnotations from './preview'; @@ -6,4 +8,13 @@ import type { DocsTypes } from './types'; export { DocsRenderer } from './DocsRenderer'; export type { DocsTypes }; +declare module 'mdx/types' { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace JSX { + type Element = React.JSX.Element; + type ElementClass = React.JSX.ElementClass; + type IntrinsicElements = React.JSX.IntrinsicElements; + } +} + export default () => definePreviewAddon(addonAnnotations); From 6fa69bd524c1f3547ff37ead1f5673667b398171 Mon Sep 17 00:00:00 2001 From: Bill Collins Date: Fri, 12 Sep 2025 19:31:03 +0100 Subject: [PATCH 06/10] snaps --- .../8143-ts-react-fc-generics/docgen.snapshot | 2 ++ .../docgen-components/8740-ts-multi-props/docgen.snapshot | 3 +++ .../docgen-components/9575-ts-camel-case/docgen.snapshot | 5 ++++- .../stories/docgen-components/ts-react-fc/docgen.snapshot | 2 +- .../stories/docgen-components/ts-types/docgen.snapshot | 2 ++ 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/docgen.snapshot b/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/docgen.snapshot index 769a757e1873..0502c3a4b835 100644 --- a/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/docgen.snapshot +++ b/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/docgen.snapshot @@ -1,4 +1,6 @@ import React from 'react'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- we can't expect error as it isn't an error in development but it is in sandbox +// @ts-ignore unused props export const Text = ({ padding = '0', margin diff --git a/code/renderers/react/template/stories/docgen-components/8740-ts-multi-props/docgen.snapshot b/code/renderers/react/template/stories/docgen-components/8740-ts-multi-props/docgen.snapshot index 12be491a61b6..a865c06da03b 100644 --- a/code/renderers/react/template/stories/docgen-components/8740-ts-multi-props/docgen.snapshot +++ b/code/renderers/react/template/stories/docgen-components/8740-ts-multi-props/docgen.snapshot @@ -11,6 +11,9 @@ export const Paragraph = ({ }) => /*#__PURE__*/React.createElement("div", { className: size }, children); + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- we can't expect error as it isn't an error in 18 (development) but it is in 19 (sandbox) +// @ts-ignore not present on react 19 Paragraph.defaultProps = { size: 'md' }; diff --git a/code/renderers/react/template/stories/docgen-components/9575-ts-camel-case/docgen.snapshot b/code/renderers/react/template/stories/docgen-components/9575-ts-camel-case/docgen.snapshot index 71d4150ec9d1..347886754338 100644 --- a/code/renderers/react/template/stories/docgen-components/9575-ts-camel-case/docgen.snapshot +++ b/code/renderers/react/template/stories/docgen-components/9575-ts-camel-case/docgen.snapshot @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -const iconButton = function IconButton(props) { +const iconButton = function IconButton() { return /*#__PURE__*/React.createElement("div", { className: "icon-button" }, "icon-button"); @@ -9,6 +9,9 @@ iconButton.propTypes = { // deepscan-disable-next-line color: PropTypes.string }; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- we can't expect error as it isn't an error in 18 (development) but it is in 19 (sandbox) +// @ts-ignore not present on react 19 iconButton.defaultProps = { color: 'primary' }; diff --git a/code/renderers/react/template/stories/docgen-components/ts-react-fc/docgen.snapshot b/code/renderers/react/template/stories/docgen-components/ts-react-fc/docgen.snapshot index 1bbff1967396..fca0d799199d 100644 --- a/code/renderers/react/template/stories/docgen-components/ts-react-fc/docgen.snapshot +++ b/code/renderers/react/template/stories/docgen-components/ts-react-fc/docgen.snapshot @@ -17,7 +17,7 @@ var StringEnum = /*#__PURE__*/function (StringEnum) { StringEnum["TopCenter"] = "top-center"; return StringEnum; }(StringEnum || {}); -export const TypeScriptProps = props => /*#__PURE__*/React.createElement("div", null, "TypeScript!"); +export const TypeScriptProps = () => /*#__PURE__*/React.createElement("div", null, "TypeScript!"); export const component = TypeScriptProps; TypeScriptProps.__docgenInfo = { "description": "", diff --git a/code/renderers/react/template/stories/docgen-components/ts-types/docgen.snapshot b/code/renderers/react/template/stories/docgen-components/ts-types/docgen.snapshot index 8b77d5696cdb..2a865e70565e 100644 --- a/code/renderers/react/template/stories/docgen-components/ts-types/docgen.snapshot +++ b/code/renderers/react/template/stories/docgen-components/ts-types/docgen.snapshot @@ -21,6 +21,8 @@ var StringEnum = /*#__PURE__*/function (StringEnum) { return StringEnum; }(StringEnum || {}); export const TypeScriptProps = () => /*#__PURE__*/React.createElement("div", null, "TypeScript!"); +// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- we can't expect error as it isn't an error in 18 (development) but it is in 19 (sandbox) +// @ts-ignore not present on react 19 TypeScriptProps.defaultProps = { any: 'Any value', string: 'A string value', From cabc59e9314bd658c6f8a2b8985856592dd3c02b Mon Sep 17 00:00:00 2001 From: Bill Collins Date: Mon, 15 Sep 2025 17:11:39 +0100 Subject: [PATCH 07/10] Add circle ci config --- .circleci/config.yml | 32 +++++++++++++++++++ .circleci/src/jobs/check-sandboxes.yml | 22 +++++++++++++ .circleci/src/workflows/daily.yml | 4 +++ .circleci/src/workflows/merged.yml | 4 +++ .circleci/src/workflows/normal.yml | 4 +++ .../cli-storybook/src/sandbox-templates.ts | 9 ++++++ scripts/get-template.ts | 21 ++++++------ scripts/tasks/check-sandbox.ts | 17 ++-------- scripts/tasks/sandbox.ts | 1 + 9 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 .circleci/src/jobs/check-sandboxes.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index de37dcb620e1..195acb7711ff 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -263,6 +263,26 @@ jobs: name: Ensure no changes pending - report-workflow-on-failure - cancel-workflow-on-failure + check-sandboxes: + executor: + class: medium + name: sb_node_22_classic + parallelism: << parameters.parallelism >> + parameters: + parallelism: + type: integer + steps: + - git-shallow-clone/checkout_advanced: + clone_options: --depth 1 --verbose + - attach_workspace: + at: . + - run: + command: yarn task --task check-sandbox --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task check-sandbox) --no-link --start-from=never --junit + name: Type check Sandboxes + - report-workflow-on-failure: + template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task check-sandbox) + - store_test_results: + path: test-results chromatic-internal-storybook: environment: NODE_OPTIONS: --max_old_space_size=4096 @@ -969,6 +989,10 @@ workflows: parallelism: 30 requires: - build + - check-sandboxes: + parallelism: 1 + requires: + - create-sandboxes - chromatic-sandboxes: parallelism: 27 requires: @@ -1081,6 +1105,10 @@ workflows: parallelism: 5 requires: - create-sandboxes + - check-sandboxes: + parallelism: 1 + requires: + - create-sandboxes - test-portable-stories: matrix: parameters: @@ -1154,6 +1182,10 @@ workflows: parallelism: 5 requires: - create-sandboxes + - check-sandboxes: + parallelism: 1 + requires: + - create-sandboxes - e2e-ui: requires: - build diff --git a/.circleci/src/jobs/check-sandboxes.yml b/.circleci/src/jobs/check-sandboxes.yml new file mode 100644 index 000000000000..09ceab18f054 --- /dev/null +++ b/.circleci/src/jobs/check-sandboxes.yml @@ -0,0 +1,22 @@ +executor: + class: medium + name: sb_node_22_classic + +parameters: + parallelism: + type: integer + +parallelism: << parameters.parallelism >> + +steps: + - git-shallow-clone/checkout_advanced: + clone_options: '--depth 1 --verbose' + - attach_workspace: + at: . + - run: + name: Type check Sandboxes + command: yarn task --task check-sandbox --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task check-sandbox) --no-link --start-from=never --junit + - report-workflow-on-failure: + template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task check-sandbox) + - store_test_results: + path: test-results diff --git a/.circleci/src/workflows/daily.yml b/.circleci/src/workflows/daily.yml index a4048ab24a50..550149c3d0d8 100644 --- a/.circleci/src/workflows/daily.yml +++ b/.circleci/src/workflows/daily.yml @@ -31,6 +31,10 @@ jobs: parallelism: 30 requires: - build + - check-sandboxes: + parallelism: 1 + requires: + - create-sandboxes # - smoke-test-sandboxes: # disabled for now # requires: # - create-sandboxes diff --git a/.circleci/src/workflows/merged.yml b/.circleci/src/workflows/merged.yml index 587cb6414117..75c7336ea9a8 100644 --- a/.circleci/src/workflows/merged.yml +++ b/.circleci/src/workflows/merged.yml @@ -54,6 +54,10 @@ jobs: parallelism: 5 requires: - create-sandboxes + - check-sandboxes: + parallelism: 1 + requires: + - create-sandboxes - test-portable-stories: requires: - build diff --git a/.circleci/src/workflows/normal.yml b/.circleci/src/workflows/normal.yml index b6e566bc67ff..1b4ae5007d3b 100644 --- a/.circleci/src/workflows/normal.yml +++ b/.circleci/src/workflows/normal.yml @@ -54,6 +54,10 @@ jobs: parallelism: 5 requires: - create-sandboxes + - check-sandboxes: + parallelism: 1 + requires: + - create-sandboxes # TODO: don't forget to reenable this # - bench-sandboxes: # parallelism: 5 diff --git a/code/lib/cli-storybook/src/sandbox-templates.ts b/code/lib/cli-storybook/src/sandbox-templates.ts index 79a4052865b3..378e67241df8 100644 --- a/code/lib/cli-storybook/src/sandbox-templates.ts +++ b/code/lib/cli-storybook/src/sandbox-templates.ts @@ -57,6 +57,11 @@ export type Template = { * to run the other tasks. Set the ones to skip in this property. */ skipTasks?: SkippableTask[]; + /** + * Should the sandbox be type checked after build. Not part of skipTasks as the default answer + * will be 'no', at least initially + */ + typeCheck?: boolean; /** * Set this only while developing a newly created framework, to avoid using it in CI. NOTE: Make * sure to always add a TODO comment to remove this flag in a subsequent PR. @@ -286,6 +291,7 @@ export const baseTemplates = { }, }, skipTasks: ['bench'], + typeCheck: true, }, 'react-vite/prerelease-ts': { name: 'React Prerelease (Vite | TypeScript)', @@ -702,6 +708,7 @@ const benchTemplates = { 'chromatic', 'vitest-integration', ], + typeCheck: false, }, 'bench/react-webpack-18-ts': { ...baseTemplates['react-webpack/18-ts'], @@ -735,6 +742,7 @@ const benchTemplates = { 'chromatic', 'vitest-integration', ], + typeCheck: false, }, 'bench/react-vite-default-ts-test-build': { ...baseTemplates['react-vite/default-ts'], @@ -751,6 +759,7 @@ const benchTemplates = { 'e2e-tests-dev', 'vitest-integration', ], + typeCheck: false, }, 'bench/react-webpack-18-ts-test-build': { ...baseTemplates['react-webpack/18-ts'], diff --git a/scripts/get-template.ts b/scripts/get-template.ts index edba3a3b071f..984325dbf9eb 100644 --- a/scripts/get-template.ts +++ b/scripts/get-template.ts @@ -17,7 +17,7 @@ import { esMain } from './utils/esmain'; const sandboxDir = process.env.SANDBOX_ROOT || SANDBOX_DIRECTORY; -type Template = Pick; +type Template = Pick; export type TemplateKey = keyof typeof allTemplates; export type Templates = Record; @@ -36,6 +36,14 @@ async function pathExists(path: string) { } } +function isTaskSkipped(template: Template, script: string): boolean { + return ( + template.inDevelopment !== true && + !template.skipTasks?.includes(script as SkippableTask) && + (script !== 'check-sandbox' || template.typeCheck) + ); +} + export async function getTemplate( cadence: Cadence, scriptName: string, @@ -62,10 +70,7 @@ export async function getTemplate( potentialTemplateKeys = potentialTemplateKeys.filter((t) => { const currentTemplate = allTemplates[t] as Template; - return ( - currentTemplate.inDevelopment !== true && - !currentTemplate.skipTasks?.includes(scriptName as SkippableTask) - ); + return isTaskSkipped(currentTemplate, scriptName); }); if (potentialTemplateKeys.length !== total) { @@ -86,6 +91,7 @@ export async function getTemplate( const tasksMap = { sandbox: 'create-sandboxes', build: 'build-sandboxes', + 'check-sandbox': 'check-sandboxes', chromatic: 'chromatic-sandboxes', 'e2e-tests': 'e2e-production', 'e2e-tests-dev': 'e2e-dev', @@ -122,10 +128,7 @@ async function checkParallelism(cadence?: Cadence, scriptName?: TaskKey) { const templateKeysPerScript = potentialTemplateKeys.filter((t) => { const currentTemplate = allTemplates[t] as Template; - return ( - currentTemplate.inDevelopment !== true && - !currentTemplate.skipTasks?.includes(script as SkippableTask) - ); + return isTaskSkipped(currentTemplate, script); }); const workflowJobsRaw: (string | { [key: string]: any })[] = data.workflows[cad].jobs; const workflowJobs = workflowJobsRaw diff --git a/scripts/tasks/check-sandbox.ts b/scripts/tasks/check-sandbox.ts index aa4084b3ba67..86932f724ee7 100644 --- a/scripts/tasks/check-sandbox.ts +++ b/scripts/tasks/check-sandbox.ts @@ -1,22 +1,11 @@ -import { access } from 'node:fs/promises'; - import type { Task } from '../task'; import { exec } from '../utils/exec'; -async function pathExists(path: string) { - try { - await access(path); - return true; - } catch { - return false; - } -} - export const checkSandbox: Task = { - description: 'Typecheck the a sandbox', + description: 'Typecheck a sandbox', dependsOn: ['sandbox'], - async ready({ builtSandboxDir }) { - return pathExists(builtSandboxDir); + async ready() { + return false; }, async run({ sandboxDir }, { dryRun, debug }) { await exec(`yarn typecheck`, { cwd: sandboxDir }, { dryRun, debug }); diff --git a/scripts/tasks/sandbox.ts b/scripts/tasks/sandbox.ts index dece41e01f59..19c274b47cbf 100644 --- a/scripts/tasks/sandbox.ts +++ b/scripts/tasks/sandbox.ts @@ -46,6 +46,7 @@ export const sandbox: Task = { 'serve', 'chromatic', 'bench', + 'check-sandbox', ]; const isSelectedTaskAfterSandboxCreation = tasksAfterSandbox.includes(selectedTask); return isSelectedTaskAfterSandboxCreation && pathExists(sandboxDir); From f6763fb36665bf002d2707bdaa064ba50bd023a1 Mon Sep 17 00:00:00 2001 From: Bill Collins Date: Tue, 16 Sep 2025 08:01:24 +0100 Subject: [PATCH 08/10] Install sandbox dependencies first --- .circleci/config.yml | 5 +++++ .circleci/src/jobs/check-sandboxes.yml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 195acb7711ff..f219e17df4a4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -276,6 +276,11 @@ jobs: clone_options: --depth 1 --verbose - attach_workspace: at: . + - run: + command: | + TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task check-sandbox) + cd sandbox/$(yarn get-sandbox-dir --template $TEMPLATE) && yarn + name: Install sandbox dependencies - run: command: yarn task --task check-sandbox --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task check-sandbox) --no-link --start-from=never --junit name: Type check Sandboxes diff --git a/.circleci/src/jobs/check-sandboxes.yml b/.circleci/src/jobs/check-sandboxes.yml index 09ceab18f054..bbc893b9fad1 100644 --- a/.circleci/src/jobs/check-sandboxes.yml +++ b/.circleci/src/jobs/check-sandboxes.yml @@ -13,6 +13,11 @@ steps: clone_options: '--depth 1 --verbose' - attach_workspace: at: . + - run: + name: Install sandbox dependencies + command: | + TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task check-sandbox) + cd sandbox/$(yarn get-sandbox-dir --template $TEMPLATE) && yarn - run: name: Type check Sandboxes command: yarn task --task check-sandbox --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task check-sandbox) --no-link --start-from=never --junit From d5a5789c82b3792fa544dbc921d3d1dcd0546050 Mon Sep 17 00:00:00 2001 From: Bill Collins Date: Tue, 16 Sep 2025 08:50:08 +0100 Subject: [PATCH 09/10] Improve preparation of sandboxes for typechecking --- .../8143-ts-react-fc-generics/docgen.snapshot | 2 -- .../8143-ts-react-fc-generics/input.tsx | 2 -- scripts/tasks/sandbox-parts.ts | 21 +++++++++++++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/docgen.snapshot b/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/docgen.snapshot index 0502c3a4b835..769a757e1873 100644 --- a/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/docgen.snapshot +++ b/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/docgen.snapshot @@ -1,6 +1,4 @@ import React from 'react'; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- we can't expect error as it isn't an error in development but it is in sandbox -// @ts-ignore unused props export const Text = ({ padding = '0', margin diff --git a/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/input.tsx b/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/input.tsx index 8f13c9ab08c7..9116328f1937 100644 --- a/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/input.tsx +++ b/code/renderers/react/template/stories/docgen-components/8143-ts-react-fc-generics/input.tsx @@ -5,8 +5,6 @@ interface Props { margin: number; } -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- we can't expect error as it isn't an error in development but it is in sandbox -// @ts-ignore unused props export const Text: React.FC = ({ padding = '0', margin }) => <>Text; export const component = Text; diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index e574c3291314..68f8bbd09e5c 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -217,12 +217,13 @@ export const init: Task['run'] = async ( case '@storybook/angular': await prepareAngularSandbox(cwd, template.name); break; - case '@storybook/react-vite': - await prepareViteSandbox(cwd); - break; default: } + if (template.typeCheck) { + await prepareTypeChecking(cwd); + } + if (!skipTemplateStories) { for (const addon of addons) { await executeCLIStep(steps.add, { @@ -881,7 +882,17 @@ async function prepareReactNativeWebSandbox(cwd: string) { } } -async function prepareViteSandbox(cwd: string) { +/** + * Prepare a sandbox for typechecking. + * + * 1. Add a typecheck script + * 2. Ensure typescript compiler options compatible with our example code + * 3. Set skipLibCheck to false to test storybook's public types + * + * This is currently configured for manipulating the output of `create vite` so will need some + * adjustment when we extend to type checking webpack sandboxes (if we ever do). + */ +async function prepareTypeChecking(cwd: string) { const packageJsonPath = join(cwd, 'package.json'); const packageJson = await readJson(packageJsonPath); @@ -900,6 +911,8 @@ async function prepareViteSandbox(cwd: string) { tsConfigJson.compilerOptions.erasableSyntaxOnly = false; // Lots of unnecessary imports of react that need fixing tsConfigJson.compilerOptions.noUnusedLocals = false; + // This is much better done by eslint + tsConfigJson.compilerOptions.noUnusedParameters = false; // Means we can check our own public types tsConfigJson.compilerOptions.skipLibCheck = false; await writeFile(tsConfigPath, JSON.stringify(tsConfigJson, null, 2)); From 8db83ae27d2c07ff922c26bc0546e00f92b64bd6 Mon Sep 17 00:00:00 2001 From: Bill Collins Date: Mon, 27 Oct 2025 17:59:13 +0000 Subject: [PATCH 10/10] Add test types --- scripts/tasks/sandbox-parts.ts | 2 ++ scripts/tasks/sandbox.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 1aa7ecdc9962..a80d72cca95d 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -954,6 +954,8 @@ async function prepareTypeChecking(cwd: string) { tsConfigJson.compilerOptions.noUnusedParameters = false; // Means we can check our own public types tsConfigJson.compilerOptions.skipLibCheck = false; + // Add chai global types + (tsConfigJson.compilerOptions.types ??= []).push('chai'); await writeFile(tsConfigPath, JSON.stringify(tsConfigJson, null, 2)); } diff --git a/scripts/tasks/sandbox.ts b/scripts/tasks/sandbox.ts index 9b09a209d912..fca0e61dabee 100644 --- a/scripts/tasks/sandbox.ts +++ b/scripts/tasks/sandbox.ts @@ -86,6 +86,7 @@ export const sandbox: Task = { '@storybook/cli', 'lodash-es', '@types/lodash-es', + '@types/aria-query', 'uuid', ];