diff --git a/.circleci/config.yml b/.circleci/config.yml index ddc8d0667b12..a4132bb6b381 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -263,6 +263,31 @@ 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: | + 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 + - 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 @@ -1014,6 +1039,10 @@ workflows: parallelism: 34 requires: - build + - check-sandboxes: + parallelism: 1 + requires: + - create-sandboxes - chromatic-sandboxes: parallelism: 31 requires: @@ -1132,6 +1161,10 @@ workflows: parallelism: 7 requires: - create-sandboxes + - check-sandboxes: + parallelism: 1 + requires: + - create-sandboxes - test-portable-stories: matrix: parameters: @@ -1211,6 +1244,10 @@ workflows: parallelism: 5 requires: - create-sandboxes + - check-sandboxes: + parallelism: 1 + requires: + - create-sandboxes - test-yarn-pnp: requires: - build diff --git a/.circleci/src/jobs/check-sandboxes.yml b/.circleci/src/jobs/check-sandboxes.yml new file mode 100644 index 000000000000..bbc893b9fad1 --- /dev/null +++ b/.circleci/src/jobs/check-sandboxes.yml @@ -0,0 +1,27 @@ +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: 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 + - 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 e79f8479054e..1dfde022f3bc 100644 --- a/.circleci/src/workflows/daily.yml +++ b/.circleci/src/workflows/daily.yml @@ -34,6 +34,10 @@ jobs: parallelism: 34 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 5cfc1b836e33..90d9cc46fdc1 100644 --- a/.circleci/src/workflows/merged.yml +++ b/.circleci/src/workflows/merged.yml @@ -57,6 +57,10 @@ jobs: parallelism: 7 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 2a9639f46535..1fe5bf46ed63 100644 --- a/.circleci/src/workflows/normal.yml +++ b/.circleci/src/workflows/normal.yml @@ -57,6 +57,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/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); diff --git a/code/lib/cli-storybook/src/sandbox-templates.ts b/code/lib/cli-storybook/src/sandbox-templates.ts index c9f9302cfbf3..8211002ebf8c 100644 --- a/code/lib/cli-storybook/src/sandbox-templates.ts +++ b/code/lib/cli-storybook/src/sandbox-templates.ts @@ -64,6 +64,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. @@ -342,7 +347,7 @@ export const baseTemplates = { }, modifications: { useCsfFactory: true, - extraDependencies: ['prop-types'], + extraDependencies: ['prop-types', '@types/prop-types'], mainConfig: { features: { developmentModeForBuild: true, @@ -351,6 +356,7 @@ export const baseTemplates = { }, }, skipTasks: ['bench'], + typeCheck: true, }, 'react-vite/prerelease-ts': { name: 'React Prerelease (Vite | TypeScript)', @@ -791,6 +797,7 @@ const benchTemplates = { 'chromatic', 'vitest-integration', ], + typeCheck: false, }, 'bench/react-webpack-18-ts': { ...baseTemplates['react-webpack/18-ts'], @@ -824,6 +831,7 @@ const benchTemplates = { 'chromatic', 'vitest-integration', ], + typeCheck: false, }, 'bench/react-vite-default-ts-test-build': { ...baseTemplates['react-vite/default-ts'], @@ -840,6 +848,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/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 f4f9fd9e4e73..340ffaeb6606 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/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/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/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/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/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-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/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', 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/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/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..86932f724ee7 --- /dev/null +++ b/scripts/tasks/check-sandbox.ts @@ -0,0 +1,13 @@ +import type { Task } from '../task'; +import { exec } from '../utils/exec'; + +export const checkSandbox: Task = { + description: 'Typecheck a sandbox', + dependsOn: ['sandbox'], + async ready() { + return false; + }, + async run({ 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 cbdc406db623..b7f0ec391d9e 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -229,6 +229,10 @@ export const init: Task['run'] = async ( default: } + if (template.typeCheck) { + await prepareTypeChecking(cwd); + } + if (!skipTemplateStories) { for (const addon of addons) { await executeCLIStep(steps.add, { @@ -917,6 +921,44 @@ async function prepareSvelteSandbox(cwd: string) { await writeConfig(svelteConfig); } +/** + * 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); + + 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; + // This is much better done by eslint + 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)); +} + 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 df31c0aaaed8..fca0e61dabee 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); @@ -84,6 +85,8 @@ 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', + '@types/aria-query', 'uuid', ]; diff --git a/scripts/utils/yarn.ts b/scripts/utils/yarn.ts index 97a7693b289b..a69237c40d21 100644 --- a/scripts/utils/yarn.ts +++ b/scripts/utils/yarn.ts @@ -111,7 +111,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', };