diff --git a/.circleci/README.md b/.circleci/README.md deleted file mode 100644 index 20c66ce653fe..000000000000 --- a/.circleci/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# CircleCI configuration - -This directory contains the CircleCI configuration for the Storybook project. The CircleCI configuration is split across multiple files and packed into a single `config.yml` using the `circleci config pack` command. This approach follows the [FYAML specification](https://github.com/CircleCI-Public/fyaml) for decomposing large YAML documents into multiple files. - -### Packing process - -To regenerate (pack) the CircleCI config file and validate it: - -```bash -circleci config pack .circleci/src > .circleci/config.yml -circleci config validate .circleci/config.yml -``` - -Commit the updated `config.yml` like any other file. - -You will need the [CircleCI local CLI](https://circleci.com/docs/local-cli/#installation). For more details on config packing, see the [CircleCI documentation](https://circleci.com/docs/how-to-use-the-circleci-local-cli/#packing-a-config). - -### Local testing - -To test individual jobs locally: - -```bash -circleci config process .circleci/config.yml > process.yml -circleci local execute -c process.yml -``` - -You will need to have Docker installed to be able to do this. See the [CircleCI docs](https://circleci.com/docs/how-to-use-the-circleci-local-cli/#run-a-job-in-a-container-on-your-machine) for details and limitations. - -## Configuration overview - -The Storybook CircleCI setup is a multi-workflow system designed to test Storybook across multiple frameworks, bundlers, and package managers. - -### Parameters - -The configuration accepts several pipeline parameters: - -- **`workflow`**: Which workflow to run (`normal`, `merged`, `daily`, `skipped`, `docs`) -- **`ghPrNumber`**: GitHub PR number for PR-specific testing -- **`ghBaseBranch`**: Base branch name for comparison testing - -### Workflows - -- **`normal`**: Standard PR checks, running the most important sandboxes -- **`merged`**: Post-merge PR checks, running against more sandboxes -- **`daily`**: Daily job, running against even more sandboxes and empty directory -- **`docs`**: Documentation checks - -### Jobs - -- **`build`**: Compiles code, publishes to local Verdaccio registry -- **`lint`**: ESLint + Prettier checks -- **`knip`**: Unused dependency detection -- **`check`**: Type checking and validation -- **`unit-tests`**: Vitest-based unit tests -- **`e2e-ui`**: End-to-end tests for Storybook's manager UI -- **`e2e-ui-vitest-3`**: End-to-end tests for Storybook's manager UI using Vitest 3 -- **`test-init-empty`**: Tests Storybook init from empty directories -- **`test-init-features`**: Tests Storybook initialization with features -- **`test-portable-stories`**: Tests portable stories across frameworks -- **`create-sandboxes`**: Generates framework-specific test environments (sandboxes) -- **`chromatic-sandboxes`**: Visual regression testing against each sandbox -- **`e2e-dev`**: End-to-end tests against a Storybook dev server for each sandbox -- **`e2e-production`**: End-to-end tests against static production Storybooks for each sandbox -- **`test-runner-production`**: Run the Test Runner against each sandbox -- **`vitest-integration`**: Run the Vitest tests of each sandbox - -### TODOs - -Several jobs are currently disabled due to flakiness: -- `bench-sandboxes`: Performance benchmarking -- `test-runner-dev`: Dev mode test runner -- `smoke-test-sandboxes`: Quick smoke tests -- Some package managers in daily workflow diff --git a/.circleci/config.yml b/.circleci/config.yml index 138b235ab8cc..59dd245f7676 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,1441 +1,54 @@ -commands: - cancel-workflow-on-failure: - description: Cancels the entire workflow in case the previous step has failed - steps: - - run: - command: | - echo "Canceling workflow as previous step resulted in failure." - echo "To execute all checks locally, please run yarn ci-tests" - curl -X POST --header "Content-Type: application/json" "https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}/cancel?circle-token=${WORKFLOW_CANCELER}" - name: Cancel current workflow - when: on_fail - report-workflow-on-failure: - description: Reports failures to discord - parameters: - template: - default: none - description: | - Which template to report in discord. Applicable for parallel sandbox jobs - type: string - steps: - - run: - command: git fetch --unshallow - when: on_fail - - discord/status: - fail_only: true - failure_message: $(yarn get-report-message << pipeline.parameters.workflow >> << parameters.template >>) - only_for_branches: main,next,next-release,latest-release - start-event-collector: - description: Starts the event collector - steps: - - run: - background: true - command: yarn jiti ./event-log-collector.ts - name: Start Event Collector - working_directory: scripts -executors: - sb_node_18_browsers: - docker: - - environment: - NODE_OPTIONS: --max_old_space_size=6144 - image: cimg/node:18.20.3-browsers - parameters: - class: - default: small - description: The Resource class - enum: - - small - - medium - - medium+ - - large - - xlarge - type: enum - resource_class: <> - working_directory: /tmp/storybook - sb_node_22_browsers: - docker: - - environment: - NODE_OPTIONS: --max_old_space_size=6144 - image: cimg/node:22.15.0-browsers - parameters: - class: - default: small - description: The Resource class - enum: - - small - - medium - - medium+ - - large - - xlarge - type: enum - resource_class: <> - working_directory: /tmp/storybook - sb_node_22_classic: - docker: - - environment: - NODE_OPTIONS: --max_old_space_size=6144 - image: cimg/node:22.15.0 - parameters: - class: - default: small - description: The Resource class - enum: - - small - - medium - - medium+ - - large - - xlarge - type: enum - resource_class: <> - working_directory: /tmp/storybook - sb_playwright: - docker: - - environment: - NODE_OPTIONS: --max_old_space_size=6144 - image: mcr.microsoft.com/playwright:v1.52.0-noble - parameters: - class: - default: small - description: The Resource class - enum: - - small - - medium - - medium+ - - large - - xlarge - type: enum - resource_class: <> - working_directory: /tmp/storybook -jobs: - bench-packages: - executor: - class: small - name: sb_node_22_classic - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - when: - condition: - and: - - << pipeline.parameters.ghBaseBranch >> - - << pipeline.parameters.ghPrNumber >> - steps: - - run: - background: true - command: | - cd code - yarn local-registry --open - name: Verdaccio - - run: - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - name: Wait on Verdaccio - - run: - command: yarn bench-packages --base-branch << pipeline.parameters.ghBaseBranch >> --pull-request << pipeline.parameters.ghPrNumber >> --upload - name: Benchmarking packages against base branch - working_directory: scripts - - when: - condition: - or: - - not: << pipeline.parameters.ghBaseBranch >> - - not: << pipeline.parameters.ghPrNumber >> - steps: - - run: - background: true - command: | - cd code - yarn local-registry --open - name: Verdaccio - - run: - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - name: Wait on Verdaccio - - run: - command: yarn bench-packages --upload - name: Uploading package benchmarks for branch - working_directory: scripts - - store_artifacts: - path: storybook/bench/packages/results.json - - report-workflow-on-failure - - cancel-workflow-on-failure - bench-sandboxes: - executor: - class: small - name: sb_playwright - parallelism: << parameters.parallelism >> - parameters: - parallelism: - type: integer - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task bench) - cd $(yarn get-sandbox-dir --template $TEMPLATE) && yarn - name: Install sandbox dependencies - - run: - command: yarn task --task bench --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task bench) --no-link --start-from=never --junit - name: Running Bench - - run: - command: yarn upload-bench $(yarn get-template --cadence << pipeline.parameters.workflow >> --task bench) << pipeline.parameters.ghPrNumber >> << pipeline.parameters.ghBaseBranch >> - name: Uploading results - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task bench) - build: - executor: - class: xlarge - name: sb_node_22_classic - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - restore_cache: - keys: - - build-yarn-2-cache-v5--{{ checksum "yarn.lock" }} - name: Restore Yarn cache - - run: - command: | - yarn - yarn task --task compile --start-from=auto --no-link --debug - git diff --exit-code - yarn dedupe --check - name: Compile - - run: - command: | - cd code - yarn local-registry --publish - name: Publish to Verdaccio - - report-workflow-on-failure - - store_artifacts: - path: storybook/code/bench/esbuild-metafiles - - save_cache: - key: build-yarn-2-cache-v5--{{ checksum "yarn.lock" }} - name: Save Yarn cache - paths: - - ~/.yarn/berry/cache - - persist_to_workspace: - paths: - - storybook/node_modules - - storybook/code/node_modules - - storybook/code/addons - - storybook/scripts/node_modules - - storybook/code/bench - - storybook/code/examples - - storybook/code/frameworks - - storybook/code/lib - - storybook/code/core - - storybook/code/builders - - storybook/code/renderers - - storybook/code/presets - - storybook/.verdaccio-cache - root: /tmp - check: - executor: - class: xlarge - name: sb_node_22_classic - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - nx/set-shas: - main-branch-name: next - workflow-name: << pipeline.parameters.workflow >> - - restore_cache: - keys: - - build-yarn-2-cache-v5--{{ checksum "yarn.lock" }} - name: Restore Yarn cache - - run: - command: | - yarn - yarn task --task check --start-from=auto --no-link --debug - name: Check - - run: - command: | - git diff --exit-code - 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: /tmp - - run: - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task check-sandbox) - cd $(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: storybook/test-results - chromatic-internal-storybook: - environment: - NODE_OPTIONS: --max_old_space_size=4096 - executor: - class: large - name: sb_node_22_browsers - steps: - - checkout - - attach_workspace: - at: /tmp - - run: - command: yarn storybook:ui:build - name: Build Storybook - working_directory: code - - run: - command: yarn storybook:ui:chromatic - name: Running Chromatic - working_directory: code - - report-workflow-on-failure - - store_test_results: - path: storybook/test-results - chromatic-sandboxes: - executor: - class: medium - name: sb_node_22_browsers - parallelism: << parameters.parallelism >> - parameters: - parallelism: - type: integer - steps: - - checkout - - attach_workspace: - at: /tmp - - run: - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task chromatic) - cd $(yarn get-sandbox-dir --template $TEMPLATE) && yarn - cp /tmp/storybook-sandboxes /tmp/storybook/sandbox -r --remove-destination - name: Install sandbox dependencies - - run: - command: STORYBOOK_SANDBOX_ROOT=./sandbox yarn task --task chromatic --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task chromatic) --no-link --start-from=never --junit - name: Running Chromatic - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task chromatic) - - store_test_results: - path: storybook/test-results - coverage: - executor: - class: small - name: sb_node_22_browsers - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - codecov/upload - - report-workflow-on-failure - create-sandboxes: - executor: - class: large - name: sb_node_22_browsers - parallelism: << parameters.parallelism >> - parameters: - parallelism: - type: integer - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: | - # Enable corepack - sudo corepack enable +version: 2.1 + +# https://circleci.com/docs/guides/orchestrate/dynamic-config/ +setup: true - # Verify yarn is working - which yarn - yarn --version - name: Setup Corepack - - start-event-collector - - run: - command: | - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task sandbox) - yarn task --task build --template $TEMPLATE --no-link --start-from=sandbox --junit - if [[ $TEMPLATE != bench/* ]]; then - yarn --cwd scripts jiti ./event-log-checker.ts build $TEMPLATE - fi - cd $(yarn get-sandbox-dir --template $TEMPLATE) && rm -rf node_modules - environment: - STORYBOOK_TELEMETRY_DEBUG: 1 - STORYBOOK_TELEMETRY_URL: http://localhost:6007/event-log - name: Create Sandboxes - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task sandbox) - - persist_to_workspace: - paths: storybook-sandboxes/** - root: /tmp - - store_test_results: - path: storybook/test-results - e2e-dev: - executor: - class: medium+ - name: sb_playwright - parallelism: << parameters.parallelism >> - parameters: - parallelism: - type: integer - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests-dev) - cd $(yarn get-sandbox-dir --template $TEMPLATE) && yarn - name: Install sandbox dependencies - - run: - command: | - TEST_FILES=$(circleci tests glob "code/e2e-tests/*.{test,spec}.{ts,js,mjs}") - echo "$TEST_FILES" | circleci tests run --command="xargs yarn task --task e2e-tests-dev --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests-dev) --no-link --start-from=never --junit" --verbose --index=0 --total=1 - name: Running E2E Tests - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests-dev) - - store_test_results: - path: test-results - - store_artifacts: - destination: playwright - path: code/playwright-results/ - e2e-production: - executor: - class: medium - name: sb_playwright - parallelism: << parameters.parallelism >> - parameters: - parallelism: - type: integer - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests) - cd $(yarn get-sandbox-dir --template $TEMPLATE) && yarn - name: Install sandbox dependencies - - run: - command: | - TEST_FILES=$(circleci tests glob "code/e2e-tests/*.{test,spec}.{ts,js,mjs}") - echo "$TEST_FILES" | circleci tests run --command="xargs yarn task --task e2e-tests --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests) --no-link --start-from=never --junit" --verbose --index=0 --total=1 - name: Running E2E Tests - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests) - - store_test_results: - path: test-results - - store_artifacts: - destination: playwright - path: code/playwright-results/ - e2e-ui: - executor: - class: medium - name: sb_playwright - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: yarn install --no-immutable - environment: - YARN_ENABLE_IMMUTABLE_INSTALLS: false - name: Install dependencies - working_directory: test-storybooks/portable-stories-kitchen-sink/react - - run: - command: yarn playwright-e2e - name: Run E2E tests - working_directory: test-storybooks/portable-stories-kitchen-sink/react - - store_test_results: - path: storybook/test-results - - store_artifacts: - destination: playwright - path: storybook/test-storybooks/portable-stories-kitchen-sink/react/test-results/ - - report-workflow-on-failure - e2e-ui-vitest-3: - executor: - class: medium - name: sb_playwright - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: yarn install --no-immutable - environment: - YARN_ENABLE_IMMUTABLE_INSTALLS: false - name: Install dependencies - working_directory: test-storybooks/portable-stories-kitchen-sink/react-vitest-3 - - run: - command: yarn playwright-e2e - name: Run E2E tests - working_directory: test-storybooks/portable-stories-kitchen-sink/react-vitest-3 - - store_test_results: - path: storybook/test-results - - store_artifacts: - destination: playwright - path: storybook/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/test-results/ - - report-workflow-on-failure - knip: - executor: - class: large - name: sb_node_22_classic - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: | - cd code - yarn knip --no-exit-code - name: Knip - - report-workflow-on-failure - - cancel-workflow-on-failure - lint: - executor: - class: medium+ - name: sb_node_22_classic - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: | - cd code - yarn lint - name: Lint - - report-workflow-on-failure - - cancel-workflow-on-failure - pretty-docs: - executor: - class: medium+ - name: sb_node_22_classic - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - restore_cache: - keys: - - prettydocs-yarn-2-cache-v8--{{ checksum "yarn.lock" }} - name: Restore Yarn cache - - run: - command: | - yarn install - name: Install - - save_cache: - key: prettydocs-yarn-2-cache-v8--{{ checksum "yarn.lock" }} - name: Save Yarn cache - paths: - - ~/.yarn/berry/cache - - run: - command: | - cd scripts - yarn docs:prettier:check - name: Prettier - script-checks: - executor: sb_node_22_browsers - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: | - cd scripts - yarn get-template --check - name: Check parallelism count - - run: - command: | - cd scripts - yarn check - name: Type check - - run: - command: | - cd scripts - yarn test --coverage - name: Run tests - - store_test_results: - path: storybook/scripts/junit.xml - - report-workflow-on-failure - - cancel-workflow-on-failure - smoke-test-sandboxes: - executor: - class: medium - name: sb_node_18_browsers - parallelism: << parameters.parallelism >> - parameters: - parallelism: - type: integer - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: yarn task --task smoke-test --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task smoke-test) --no-link --start-from=never --junit - name: Smoke Testing Sandboxes - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task smoke-test) - - store_test_results: - path: storybook/test-results - stories-tests: - executor: - class: xlarge - name: sb_playwright - parallelism: 2 - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: | - cd code - TEST_FILES=$(circleci tests glob "**/*.{stories}.{ts,tsx,js,jsx,cjs}" | sed "/^e2e-tests\//d" | sed "/^node_modules\//d") - echo "$TEST_FILES" | circleci tests run --command="xargs yarn test --reporter=junit --reporter=default --outputFile=../test-results/junit-${CIRCLE_NODE_INDEX}.xml" --verbose - name: Run tests - - store_test_results: - path: storybook/test-results - - report-workflow-on-failure - - cancel-workflow-on-failure - test-init-empty: - executor: - class: small - name: sb_node_22_browsers - parameters: - packageManager: - type: string - template: - type: string - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - when: - condition: - equal: - - npm - - << parameters.packageManager >> - steps: - - run: - background: true - command: | - cd code - yarn local-registry --open - name: Verdaccio - - run: - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - name: Wait on Verdaccio - - run: - command: | - cd .. - mkdir empty-<< parameters.template >> - cd empty-<< parameters.template >> - npm set registry http://localhost:6001 - npx storybook init --yes --package-manager npm - npm run storybook -- --smoke-test - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_DISABLE_TELEMETRY: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - name: Storybook init from empty directory (NPM) - - when: - condition: - equal: - - yarn2 - - << parameters.packageManager >> - steps: - - run: - background: true - command: | - cd code - yarn local-registry --open - name: Verdaccio - - run: - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - name: Wait on Verdaccio - - run: - command: | - cd .. - mkdir empty-<< parameters.template >> - cd empty-<< parameters.template >> - yarn set version berry - yarn config set registry http://localhost:6001 - yarn dlx storybook init --yes --package-manager yarn2 - yarn storybook --smoke-test - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_DISABLE_TELEMETRY: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - name: Storybook init from empty directory (Yarn 2) - - when: - condition: - equal: - - pnpm - - << parameters.packageManager >> - steps: - - run: - background: true - command: | - cd code - yarn local-registry --open - name: Verdaccio - - run: - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - name: Wait on Verdaccio - - run: - command: | - cd .. - mkdir empty-<< parameters.template >> - cd empty-<< parameters.template >> - npm i -g pnpm - pnpm config set registry http://localhost:6001 - pnpm dlx storybook init --yes --package-manager pnpm - pnpm run storybook --smoke-test - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_DISABLE_TELEMETRY: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - name: Storybook init from empty directory (PNPM) - - when: - condition: - equal: - - react-vite-ts - - << parameters.template >> - steps: - - run: - background: true - command: | - cd code - yarn local-registry --open - name: Verdaccio - - run: - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - name: Wait on Verdaccio - - run: - command: | - cd .. - mkdir empty-<< parameters.template >>-no-install - cd empty-<< parameters.template >>-no-install - npx storybook init --yes --skip-install - npm install - npm run build-storybook - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_DISABLE_TELEMETRY: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - name: Storybook init from empty directory (--skip-install) - - report-workflow-on-failure - test-init-empty-windows: - executor: win/default - parameters: - packageManager: - type: string - template: - type: string - steps: - - checkout - - attach_workspace: - at: C:\Users\circleci - - run: - command: | - choco install nodejs-lts --version=22.11.0 -y - corepack enable - name: Setup Node & Yarn on Windows - shell: bash.exe - - run: - command: yarn install - name: Install dependencies - shell: bash.exe - - when: - condition: - equal: - - npm - - << parameters.packageManager >> - steps: - - run: - background: true - command: | - cd code - yarn local-registry --open - name: Verdaccio - shell: bash.exe - - run: - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - name: Wait on Verdaccio - shell: bash.exe - - run: - command: | - cd .. - mkdir empty-<< parameters.template >> - cd empty-<< parameters.template >> - npm set registry http://localhost:6001 - npx storybook init --yes --package-manager npm - npm run storybook -- --smoke-test - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_DISABLE_TELEMETRY: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - name: Storybook init from empty directory (Windows NPM) - shell: bash.exe - - when: - condition: - equal: - - yarn2 - - << parameters.packageManager >> - steps: - - run: - background: true - command: | - cd code - yarn local-registry --open - name: Verdaccio - shell: bash.exe - - run: - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - name: Wait on Verdaccio - shell: bash.exe - - run: - command: | - cd .. - mkdir empty-<< parameters.template >> - cd empty-<< parameters.template >> - yarn set version berry - yarn config set registry http://localhost:6001 - yarn dlx storybook init --yes --package-manager yarn2 - yarn storybook --smoke-test - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_DISABLE_TELEMETRY: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - name: Storybook init from empty directory (Windows Yarn 2) - shell: bash.exe - - when: - condition: - equal: - - pnpm - - << parameters.packageManager >> - steps: - - run: - background: true - command: | - cd code - yarn local-registry --open - name: Verdaccio - shell: bash.exe - - run: - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - name: Wait on Verdaccio - shell: bash.exe - - run: - command: | - cd .. - mkdir empty-<< parameters.template >> - cd empty-<< parameters.template >> - npm i -g pnpm - pnpm config set registry http://localhost:6001 - pnpm dlx storybook init --yes --package-manager pnpm - pnpm run storybook --smoke-test - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_DISABLE_TELEMETRY: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - name: Storybook init from empty directory (Windows PNPM) - shell: bash.exe - - when: - condition: - equal: - - react-vite-ts - - << parameters.template >> - steps: - - run: - background: true - command: | - cd code - yarn local-registry --open - name: Verdaccio - shell: bash.exe - - run: - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - name: Wait on Verdaccio - shell: bash.exe - - run: - command: | - cd .. - mkdir empty-<< parameters.template >>-no-install - cd empty-<< parameters.template >>-no-install - npx storybook init --yes --skip-install - npm install - npm run build-storybook - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_DISABLE_TELEMETRY: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - name: Storybook init from empty directory (Windows --skip-install) - shell: bash.exe - working_directory: C:\Users\circleci\storybook - test-init-features: - executor: - class: small - name: sb_node_22_browsers - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - background: true - command: | - cd code - yarn local-registry --open - name: Verdaccio - - run: - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - name: Wait on Verdaccio - - run: - command: | - cd .. - mkdir features-1 - cd features-1 - npm set registry http://localhost:6001 - npx create-storybook --yes --package-manager npm --features docs test a11y --loglevel=debug - npx vitest - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_DISABLE_TELEMETRY: true - STORYBOOK_INIT_EMPTY_TYPE: react-vite-ts - name: Storybook init for features - test-portable-stories: - executor: - class: medium+ - name: sb_playwright - parameters: - directory: - type: string - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: yarn install --no-immutable - environment: - YARN_ENABLE_IMMUTABLE_INSTALLS: false - name: Install dependencies - working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >> - - run: - command: yarn jest - name: Run Jest tests - working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >> - - run: - command: yarn vitest - name: Run Vitest tests - working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >> - - run: - command: yarn playwright-ct - name: Run Playwright CT tests - working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >> - - run: - command: yarn cypress - name: Run Cypress CT tests - working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >> - - report-workflow-on-failure - test-runner-dev: - executor: - class: large - name: sb_playwright - parallelism: << parameters.parallelism >> - parameters: - parallelism: - type: integer - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: yarn task --task test-runner-dev --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner-dev) --no-link --start-from=never --junit - name: Running Test Runner in Dev mode - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner-dev) - - store_test_results: - path: storybook/test-results - test-runner-production: - executor: - class: medium+ - name: sb_playwright - parallelism: << parameters.parallelism >> - parameters: - parallelism: - type: integer - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner) - cd $(yarn get-sandbox-dir --template $TEMPLATE) && yarn - name: Install sandbox dependencies - - start-event-collector - - run: - command: yarn task --task test-runner --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner) --no-link --start-from=never --junit - environment: - STORYBOOK_TELEMETRY_DEBUG: 1 - STORYBOOK_TELEMETRY_URL: http://localhost:6007/event-log - name: Running Test Runner - - run: - command: yarn --cwd scripts jiti ./event-log-checker.ts test-run $(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner) - name: Check Telemetry - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner) - - store_test_results: - path: storybook/test-results - test-yarn-pnp: - executor: - class: medium - name: sb_playwright - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: yarn install --no-immutable - environment: - YARN_ENABLE_IMMUTABLE_INSTALLS: false - name: Install dependencies - working_directory: test-storybooks/yarn-pnp - - run: - command: yarn storybook --smoke-test - name: Run Storybook smoke test - working_directory: test-storybooks/yarn-pnp - - report-workflow-on-failure - unit-tests: - executor: - class: xlarge - name: sb_playwright - parallelism: 2 - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: | - cd code - TEST_FILES=$(circleci tests glob "**/*.{test,spec,stories}.{ts,tsx,js,jsx,cjs}" | sed "/^e2e-tests\//d" | sed "/^node_modules\//d") - echo "$TEST_FILES" | circleci tests run --command="xargs yarn test --reporter=junit --reporter=default --outputFile=../test-results/junit-${CIRCLE_NODE_INDEX}.xml" --verbose - name: Run tests - - store_test_results: - path: storybook/test-results - - report-workflow-on-failure - - cancel-workflow-on-failure - vitest-integration: - executor: - class: xlarge - name: sb_playwright - parallelism: << parameters.parallelism >> - parameters: - parallelism: - type: integer - steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task vitest-integration) - cd $(yarn get-sandbox-dir --template $TEMPLATE) && yarn - name: Install sandbox dependencies - - start-event-collector - - run: - command: yarn task --task vitest-integration --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task vitest-integration) --no-link --start-from=never --junit - environment: - STORYBOOK_TELEMETRY_DEBUG: 1 - STORYBOOK_TELEMETRY_URL: http://localhost:6007/event-log - name: Running story tests in Vitest - - run: - command: yarn --cwd scripts jiti ./event-log-checker.ts test-run $(yarn get-template --cadence << pipeline.parameters.workflow >> --task vitest-integration) - name: Check Telemetry - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task vitest-integration) - - store_test_results: - path: storybook/test-results orbs: - browser-tools: circleci/browser-tools@1.4.1 - codecov: codecov/codecov@3.2.4 - discord: antonioned/discord@0.1.0 - git-shallow-clone: guitarrapc/git-shallow-clone@2.5.0 - node: circleci/node@5.2.0 - nx: nrwl/nx@1.6.2 - win: circleci/windows@5.0.0 + git-shallow-clone: guitarrapc/git-shallow-clone@2.8.0 + continuation: circleci/continuation@2.0.1 + node: circleci/node@7.2.1 + parameters: - ghBaseBranch: - default: next - description: The name of the base branch (the target of the PR) - type: string - ghPrNumber: - default: "" - description: The PR number - type: string - workflow: - default: skipped - description: Which workflow to run - enum: - - normal - - merged - - daily - - skipped - - docs - type: enum -version: 2.1 -workflows: - daily: - jobs: - - pretty-docs - - build - - lint: - requires: - - build - - knip: - requires: - - build - - bench-packages: - requires: - - build - - check - - unit-tests: - requires: - - build - - stories-tests: - requires: - - build - - script-checks: - requires: - - build - - chromatic-internal-storybook: - requires: - - build - - create-sandboxes: - parallelism: 39 - requires: - - build - - check-sandboxes: - parallelism: 1 - requires: - - create-sandboxes - - chromatic-sandboxes: - parallelism: 36 - requires: - - create-sandboxes - - e2e-production: - parallelism: 7 - requires: - - create-sandboxes - - e2e-dev: - parallelism: 28 - requires: - - create-sandboxes - - test-runner-production: - parallelism: 34 - requires: - - create-sandboxes - - vitest-integration: - parallelism: 13 - requires: - - create-sandboxes - - test-portable-stories: - matrix: - parameters: - directory: - - react - - vue3 - - nextjs - - svelte - requires: - - build - - test-yarn-pnp: - requires: - - build - - e2e-ui: - requires: - - build - - e2e-ui-vitest-3: - requires: - - build - - test-init-features: - requires: - - build - - test-init-empty: - matrix: - parameters: - packageManager: - - npm - template: - - react-vite-ts - - nextjs-ts - - vue-vite-ts - - lit-vite-ts - requires: - - build - - test-init-empty-windows: - matrix: - parameters: - packageManager: - - npm - template: - - react-vite-ts - - nextjs-ts - - vue-vite-ts - - lit-vite-ts - requires: - - build - when: - equal: - - daily - - << pipeline.parameters.workflow >> - docs: - jobs: - - pretty-docs - when: - equal: - - docs - - << pipeline.parameters.workflow >> - merged: - jobs: - - pretty-docs - - build - - lint: - requires: - - build - - knip: - requires: - - build - - bench-packages: - requires: - - build - - check - - unit-tests: - requires: - - build - - stories-tests: - requires: - - build - - script-checks: - requires: - - build - - chromatic-internal-storybook: - requires: - - build - - coverage: - requires: - - unit-tests - - create-sandboxes: - parallelism: 22 - requires: - - build - - chromatic-sandboxes: - parallelism: 19 - requires: - - create-sandboxes - - e2e-production: - parallelism: 6 - requires: - - create-sandboxes - - e2e-dev: - parallelism: 14 - requires: - - create-sandboxes - - test-runner-production: - parallelism: 17 - requires: - - create-sandboxes - - vitest-integration: - parallelism: 7 - requires: - - create-sandboxes - - check-sandboxes: - parallelism: 1 - requires: - - create-sandboxes - - test-portable-stories: - matrix: - parameters: - directory: - - react - - vue3 - - nextjs - - svelte - requires: - - build - - test-yarn-pnp: - requires: - - build - - e2e-ui: - requires: - - build - - e2e-ui-vitest-3: - requires: - - build - - test-init-features: - requires: - - build - - test-init-empty-windows: - matrix: - parameters: - packageManager: - - npm - template: - - react-vite-ts - - nextjs-ts - - vue-vite-ts - - lit-vite-ts - requires: - - build - when: - equal: - - merged - - << pipeline.parameters.workflow >> - normal: - jobs: - - pretty-docs - - build - - lint: - requires: - - build - - knip: - requires: - - build - - bench-packages: - requires: - - build - - check - - unit-tests: - requires: - - build - - stories-tests: - requires: - - build - - script-checks: - requires: - - build - - chromatic-internal-storybook: - requires: - - build - - coverage: - requires: - - unit-tests - - create-sandboxes: - parallelism: 14 - requires: - - build - - chromatic-sandboxes: - parallelism: 11 - requires: - - create-sandboxes - - e2e-production: - parallelism: 6 - requires: - - create-sandboxes - - e2e-dev: - parallelism: 8 - requires: - - create-sandboxes - - test-runner-production: - parallelism: 9 - requires: - - create-sandboxes - - vitest-integration: - parallelism: 5 - requires: - - create-sandboxes - - check-sandboxes: - parallelism: 1 - requires: - - create-sandboxes - - test-yarn-pnp: - requires: - - build - - e2e-ui: - requires: - - build - - e2e-ui-vitest-3: - requires: - - build - - test-init-features: - requires: - - build - - test-portable-stories: - matrix: - parameters: - directory: - - react - - vue3 - - nextjs - - svelte - requires: - - build - when: - equal: - - normal - - << pipeline.parameters.workflow >> + ghBaseBranch: + default: next + description: The name of the base branch (the target of the PR) + type: string + ghPrNumber: + default: '' + description: The PR number + type: string + workflow: + default: skipped + description: Which workflow to run + enum: + - normal + - merged + - daily + - skipped + - docs + type: enum +jobs: + generate-and-run-config: + executor: + name: node/default + resource_class: small + steps: + - node/install: + install-yarn: true + - git-shallow-clone/checkout_advanced: + clone_options: '--depth 1' + - run: + name: Install dependencies + command: yarn workspaces focus @storybook/scripts + - run: + name: Generate config + command: | + yarn dlx jiti ./scripts/ci/main.ts --workflow=<< pipeline.parameters.workflow >> + - continuation/continue: + configuration_path: .circleci/config.generated.yml +workflows: + setup: + jobs: + - generate-and-run-config + when: pipeline.parameters.workflow != "skipped" diff --git a/.circleci/src/@orbs.yml b/.circleci/src/@orbs.yml deleted file mode 100644 index e0e9c2823f11..000000000000 --- a/.circleci/src/@orbs.yml +++ /dev/null @@ -1,15 +0,0 @@ -orbs: - # Efficient git cloning - git-shallow-clone: guitarrapc/git-shallow-clone@2.5.0 - # Browser automation support - browser-tools: circleci/browser-tools@1.4.1 - # Failure notifications via Discord - discord: antonioned/discord@0.1.0 - # Code coverage reporting - codecov: codecov/codecov@3.2.4 - # Node.js utilities - node: circleci/node@5.2.0 - # Monorepo build optimization - nx: nrwl/nx@1.6.2 - # Windows support - win: circleci/windows@5.0.0 diff --git a/.circleci/src/@parameters.yml b/.circleci/src/@parameters.yml deleted file mode 100644 index fb2727cee8ae..000000000000 --- a/.circleci/src/@parameters.yml +++ /dev/null @@ -1,14 +0,0 @@ -parameters: - workflow: - description: Which workflow to run - type: enum - enum: ['normal', 'merged', 'daily', 'skipped', 'docs'] - default: 'skipped' - ghPrNumber: - description: The PR number - type: string - default: '' - ghBaseBranch: - description: The name of the base branch (the target of the PR) - type: string - default: 'next' diff --git a/.circleci/src/@version.yml b/.circleci/src/@version.yml deleted file mode 100644 index 3879be90472a..000000000000 --- a/.circleci/src/@version.yml +++ /dev/null @@ -1 +0,0 @@ -version: 2.1 diff --git a/.circleci/src/commands/cancel-workflow-on-failure.yml b/.circleci/src/commands/cancel-workflow-on-failure.yml deleted file mode 100644 index 233e75fca22f..000000000000 --- a/.circleci/src/commands/cancel-workflow-on-failure.yml +++ /dev/null @@ -1,10 +0,0 @@ -description: 'Cancels the entire workflow in case the previous step has failed' - -steps: - - run: - name: Cancel current workflow - when: on_fail - command: | - echo "Canceling workflow as previous step resulted in failure." - echo "To execute all checks locally, please run yarn ci-tests" - curl -X POST --header "Content-Type: application/json" "https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}/cancel?circle-token=${WORKFLOW_CANCELER}" diff --git a/.circleci/src/commands/report-workflow-on-failure.yml b/.circleci/src/commands/report-workflow-on-failure.yml deleted file mode 100644 index 4fb3d7f1b565..000000000000 --- a/.circleci/src/commands/report-workflow-on-failure.yml +++ /dev/null @@ -1,17 +0,0 @@ -description: 'Reports failures to discord' - -parameters: - template: - description: | - Which template to report in discord. Applicable for parallel sandbox jobs - type: string - default: 'none' - -steps: - - run: - when: on_fail - command: git fetch --unshallow - - discord/status: - only_for_branches: main,next,next-release,latest-release - fail_only: true - failure_message: $(yarn get-report-message << pipeline.parameters.workflow >> << parameters.template >>) diff --git a/.circleci/src/commands/start-event-collector.yml b/.circleci/src/commands/start-event-collector.yml deleted file mode 100644 index 6d0177f9955b..000000000000 --- a/.circleci/src/commands/start-event-collector.yml +++ /dev/null @@ -1,8 +0,0 @@ -description: 'Starts the event collector' - -steps: - - run: - name: Start Event Collector - command: yarn jiti ./event-log-collector.ts - working_directory: scripts - background: true diff --git a/.circleci/src/executors/sb_node_18_browsers.yml b/.circleci/src/executors/sb_node_18_browsers.yml deleted file mode 100644 index 0f5bbbc795bd..000000000000 --- a/.circleci/src/executors/sb_node_18_browsers.yml +++ /dev/null @@ -1,15 +0,0 @@ -docker: - - image: cimg/node:18.20.3-browsers - environment: - NODE_OPTIONS: --max_old_space_size=6144 - -parameters: - class: - description: The Resource class - type: enum - enum: ['small', 'medium', 'medium+', 'large', 'xlarge'] - default: 'small' - -resource_class: <> - -working_directory: /tmp/storybook diff --git a/.circleci/src/executors/sb_node_22_browsers.yml b/.circleci/src/executors/sb_node_22_browsers.yml deleted file mode 100644 index affdeeccab53..000000000000 --- a/.circleci/src/executors/sb_node_22_browsers.yml +++ /dev/null @@ -1,15 +0,0 @@ -docker: - - image: cimg/node:22.15.0-browsers - environment: - NODE_OPTIONS: --max_old_space_size=6144 - -parameters: - class: - description: The Resource class - type: enum - enum: ['small', 'medium', 'medium+', 'large', 'xlarge'] - default: 'small' - -resource_class: <> - -working_directory: /tmp/storybook diff --git a/.circleci/src/executors/sb_node_22_classic.yml b/.circleci/src/executors/sb_node_22_classic.yml deleted file mode 100644 index 99584951908d..000000000000 --- a/.circleci/src/executors/sb_node_22_classic.yml +++ /dev/null @@ -1,15 +0,0 @@ -docker: - - image: cimg/node:22.15.0 - environment: - NODE_OPTIONS: --max_old_space_size=6144 - -parameters: - class: - description: The Resource class - type: enum - enum: ['small', 'medium', 'medium+', 'large', 'xlarge'] - default: 'small' - -resource_class: <> - -working_directory: /tmp/storybook diff --git a/.circleci/src/executors/sb_playwright.yml b/.circleci/src/executors/sb_playwright.yml deleted file mode 100644 index 7c8261103401..000000000000 --- a/.circleci/src/executors/sb_playwright.yml +++ /dev/null @@ -1,15 +0,0 @@ -docker: - - image: mcr.microsoft.com/playwright:v1.52.0-noble - environment: - NODE_OPTIONS: --max_old_space_size=6144 - -parameters: - class: - description: The Resource class - type: enum - enum: ['small', 'medium', 'medium+', 'large', 'xlarge'] - default: 'small' - -resource_class: <> - -working_directory: /tmp/storybook diff --git a/.circleci/src/jobs/bench-packages.yml b/.circleci/src/jobs/bench-packages.yml deleted file mode 100644 index 399cbe69d4d3..000000000000 --- a/.circleci/src/jobs/bench-packages.yml +++ /dev/null @@ -1,61 +0,0 @@ -executor: - class: small - name: sb_node_22_classic - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - # 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: Verdaccio - background: true - command: | - cd code - yarn local-registry --open - - run: - name: Wait on Verdaccio - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - - run: - name: Benchmarking packages against base branch - working_directory: scripts - command: 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 run directly on branches, like next or main - - when: - condition: - or: - - not: << pipeline.parameters.ghBaseBranch >> - - not: << pipeline.parameters.ghPrNumber >> - steps: - - run: - name: Verdaccio - background: true - command: | - cd code - yarn local-registry --open - - run: - name: Wait on Verdaccio - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - - run: - name: Uploading package benchmarks for branch - working_directory: scripts - command: yarn bench-packages --upload - - store_artifacts: - path: storybook/bench/packages/results.json - - report-workflow-on-failure - - cancel-workflow-on-failure diff --git a/.circleci/src/jobs/bench-sandboxes.yml b/.circleci/src/jobs/bench-sandboxes.yml deleted file mode 100644 index 759c5a9704c2..000000000000 --- a/.circleci/src/jobs/bench-sandboxes.yml +++ /dev/null @@ -1,29 +0,0 @@ -executor: - class: small - name: sb_playwright - -parameters: - parallelism: - type: integer - -parallelism: << parameters.parallelism >> - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Install sandbox dependencies - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task bench) - cd $(yarn get-sandbox-dir --template $TEMPLATE) && yarn - - run: - name: Running Bench - command: yarn task --task bench --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task bench) --no-link --start-from=never --junit - - run: - name: Uploading results - command: yarn upload-bench $(yarn get-template --cadence << pipeline.parameters.workflow >> --task bench) << pipeline.parameters.ghPrNumber >> << pipeline.parameters.ghBaseBranch >> - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task bench) diff --git a/.circleci/src/jobs/build.yml b/.circleci/src/jobs/build.yml deleted file mode 100644 index ca7f988c4b2f..000000000000 --- a/.circleci/src/jobs/build.yml +++ /dev/null @@ -1,47 +0,0 @@ -executor: - class: xlarge - name: sb_node_22_classic - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - restore_cache: - name: Restore Yarn cache - keys: - - build-yarn-2-cache-v5--{{ checksum "yarn.lock" }} - - run: - name: Compile - command: | - yarn - yarn task --task compile --start-from=auto --no-link --debug - git diff --exit-code - yarn dedupe --check - - run: - name: Publish to Verdaccio - command: | - cd code - yarn local-registry --publish - - report-workflow-on-failure - - store_artifacts: - path: storybook/code/bench/esbuild-metafiles - - save_cache: - name: Save Yarn cache - key: build-yarn-2-cache-v5--{{ checksum "yarn.lock" }} - paths: - - ~/.yarn/berry/cache - - persist_to_workspace: - root: /tmp - paths: - - storybook/node_modules - - storybook/code/node_modules - - storybook/code/addons - - storybook/scripts/node_modules - - storybook/code/bench - - storybook/code/examples - - storybook/code/frameworks - - storybook/code/lib - - storybook/code/core - - storybook/code/builders - - storybook/code/renderers - - storybook/code/presets - - storybook/.verdaccio-cache diff --git a/.circleci/src/jobs/check-sandboxes.yml b/.circleci/src/jobs/check-sandboxes.yml deleted file mode 100644 index f078631cc820..000000000000 --- a/.circleci/src/jobs/check-sandboxes.yml +++ /dev/null @@ -1,28 +0,0 @@ -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: /tmp - - run: - name: Install sandbox dependencies - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task check-sandbox) - cd $(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: storybook/test-results diff --git a/.circleci/src/jobs/check.yml b/.circleci/src/jobs/check.yml deleted file mode 100644 index e61743164b41..000000000000 --- a/.circleci/src/jobs/check.yml +++ /dev/null @@ -1,25 +0,0 @@ -executor: - class: xlarge - name: sb_node_22_classic - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - nx/set-shas: - main-branch-name: 'next' - workflow-name: << pipeline.parameters.workflow >> - - restore_cache: - name: Restore Yarn cache - keys: - - build-yarn-2-cache-v5--{{ checksum "yarn.lock" }} - - run: - name: Check - command: | - yarn - yarn task --task check --start-from=auto --no-link --debug - - run: - name: Ensure no changes pending - command: | - git diff --exit-code - - report-workflow-on-failure - - cancel-workflow-on-failure diff --git a/.circleci/src/jobs/chromatic-internal-storybook.yml b/.circleci/src/jobs/chromatic-internal-storybook.yml deleted file mode 100644 index 7c4643b45460..000000000000 --- a/.circleci/src/jobs/chromatic-internal-storybook.yml +++ /dev/null @@ -1,23 +0,0 @@ -executor: - class: large - name: sb_node_22_browsers - -environment: - NODE_OPTIONS: --max_old_space_size=4096 - -steps: - # switched this to the CircleCI helper to get the full git history for TurboSnap - - checkout - - attach_workspace: - at: /tmp - - run: - name: Build Storybook - command: yarn storybook:ui:build - working_directory: code - - run: - name: Running Chromatic - command: yarn storybook:ui:chromatic - working_directory: code - - report-workflow-on-failure - - store_test_results: - path: storybook/test-results diff --git a/.circleci/src/jobs/chromatic-sandboxes.yml b/.circleci/src/jobs/chromatic-sandboxes.yml deleted file mode 100644 index ef0bd31c8567..000000000000 --- a/.circleci/src/jobs/chromatic-sandboxes.yml +++ /dev/null @@ -1,29 +0,0 @@ -executor: - class: medium - name: sb_node_22_browsers - -parameters: - parallelism: - type: integer - -parallelism: << parameters.parallelism >> - -steps: - - checkout - - attach_workspace: - at: /tmp - - run: - name: Install sandbox dependencies - # chromatic can only run in a git directory - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task chromatic) - cd $(yarn get-sandbox-dir --template $TEMPLATE) && yarn - cp /tmp/storybook-sandboxes /tmp/storybook/sandbox -r --remove-destination - - run: - name: Running Chromatic - command: STORYBOOK_SANDBOX_ROOT=./sandbox yarn task --task chromatic --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task chromatic) --no-link --start-from=never --junit - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task chromatic) - - store_test_results: - path: storybook/test-results diff --git a/.circleci/src/jobs/coverage.yml b/.circleci/src/jobs/coverage.yml deleted file mode 100644 index ba3fcc1bb856..000000000000 --- a/.circleci/src/jobs/coverage.yml +++ /dev/null @@ -1,11 +0,0 @@ -executor: - class: small - name: sb_node_22_browsers - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - codecov/upload - - report-workflow-on-failure diff --git a/.circleci/src/jobs/create-sandboxes.yml b/.circleci/src/jobs/create-sandboxes.yml deleted file mode 100644 index 9fd949dc8b3b..000000000000 --- a/.circleci/src/jobs/create-sandboxes.yml +++ /dev/null @@ -1,45 +0,0 @@ -executor: - class: large - name: sb_node_22_browsers - -parameters: - parallelism: - type: integer - -parallelism: << parameters.parallelism >> - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Setup Corepack - command: | - # Enable corepack - sudo corepack enable - - # Verify yarn is working - which yarn - yarn --version - - start-event-collector - - run: - name: Create Sandboxes - command: | - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task sandbox) - yarn task --task build --template $TEMPLATE --no-link --start-from=sandbox --junit - if [[ $TEMPLATE != bench/* ]]; then - yarn --cwd scripts jiti ./event-log-checker.ts build $TEMPLATE - fi - cd $(yarn get-sandbox-dir --template $TEMPLATE) && rm -rf node_modules - environment: - STORYBOOK_TELEMETRY_DEBUG: 1 - STORYBOOK_TELEMETRY_URL: 'http://localhost:6007/event-log' - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task sandbox) - - persist_to_workspace: - root: /tmp - paths: - storybook-sandboxes/** - - store_test_results: - path: storybook/test-results diff --git a/.circleci/src/jobs/e2e-dev.yml b/.circleci/src/jobs/e2e-dev.yml deleted file mode 100644 index 9b02ddac07c1..000000000000 --- a/.circleci/src/jobs/e2e-dev.yml +++ /dev/null @@ -1,36 +0,0 @@ -executor: - class: medium+ - name: sb_playwright - -parameters: - parallelism: - type: integer - -parallelism: << parameters.parallelism >> - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Install sandbox dependencies - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests-dev) - cd $(yarn get-sandbox-dir --template $TEMPLATE) && yarn - - run: - name: Running E2E Tests - # TEST_FILES should include all relevant test files, in this case we use the default playwright pattern - # `circleci tests run` allows rerunning failed tests by passing a subset of test files to the command via `xargs` - # --index=0 --total=1 ensures we run all tests on all runners, since each runner handles a different sandbox - command: | - TEST_FILES=$(circleci tests glob "code/e2e-tests/*.{test,spec}.{ts,js,mjs}") - echo "$TEST_FILES" | circleci tests run --command="xargs yarn task --task e2e-tests-dev --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests-dev) --no-link --start-from=never --junit" --verbose --index=0 --total=1 - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests-dev) - - store_test_results: - path: test-results - - store_artifacts: # this is where playwright puts more complex stuff - path: code/playwright-results/ - destination: playwright diff --git a/.circleci/src/jobs/e2e-production.yml b/.circleci/src/jobs/e2e-production.yml deleted file mode 100644 index edd99661e433..000000000000 --- a/.circleci/src/jobs/e2e-production.yml +++ /dev/null @@ -1,36 +0,0 @@ -executor: - class: medium - name: sb_playwright - -parameters: - parallelism: - type: integer - -parallelism: << parameters.parallelism >> - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Install sandbox dependencies - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests) - cd $(yarn get-sandbox-dir --template $TEMPLATE) && yarn - - run: - name: Running E2E Tests - # TEST_FILES should include all relevant test files, in this case we use the default playwright pattern - # `circleci tests run` allows rerunning failed tests by passing a subset of test files to the command via `xargs` - # --index=0 --total=1 ensures we run all tests on all runners, since each runner handles a different sandbox - command: | - TEST_FILES=$(circleci tests glob "code/e2e-tests/*.{test,spec}.{ts,js,mjs}") - echo "$TEST_FILES" | circleci tests run --command="xargs yarn task --task e2e-tests --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests) --no-link --start-from=never --junit" --verbose --index=0 --total=1 - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task e2e-tests) - - store_test_results: - path: test-results - - store_artifacts: # this is where playwright puts more complex stuff - path: code/playwright-results/ - destination: playwright diff --git a/.circleci/src/jobs/e2e-ui-vitest-3.yml b/.circleci/src/jobs/e2e-ui-vitest-3.yml deleted file mode 100644 index b848e4d13e89..000000000000 --- a/.circleci/src/jobs/e2e-ui-vitest-3.yml +++ /dev/null @@ -1,25 +0,0 @@ -executor: - name: sb_playwright - class: medium - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Install dependencies - command: yarn install --no-immutable - working_directory: test-storybooks/portable-stories-kitchen-sink/react-vitest-3 - environment: - YARN_ENABLE_IMMUTABLE_INSTALLS: false - - run: - name: Run E2E tests - command: yarn playwright-e2e - working_directory: test-storybooks/portable-stories-kitchen-sink/react-vitest-3 - - store_test_results: - path: storybook/test-results - - store_artifacts: - path: storybook/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/test-results/ - destination: playwright - - report-workflow-on-failure diff --git a/.circleci/src/jobs/e2e-ui.yml b/.circleci/src/jobs/e2e-ui.yml deleted file mode 100644 index 4157d49dcfff..000000000000 --- a/.circleci/src/jobs/e2e-ui.yml +++ /dev/null @@ -1,25 +0,0 @@ -executor: - name: sb_playwright - class: medium - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Install dependencies - command: yarn install --no-immutable - working_directory: test-storybooks/portable-stories-kitchen-sink/react - environment: - YARN_ENABLE_IMMUTABLE_INSTALLS: false - - run: - name: Run E2E tests - command: yarn playwright-e2e - working_directory: test-storybooks/portable-stories-kitchen-sink/react - - store_test_results: - path: storybook/test-results - - store_artifacts: - path: storybook/test-storybooks/portable-stories-kitchen-sink/react/test-results/ - destination: playwright - - report-workflow-on-failure diff --git a/.circleci/src/jobs/knip.yml b/.circleci/src/jobs/knip.yml deleted file mode 100644 index 89f9201820bb..000000000000 --- a/.circleci/src/jobs/knip.yml +++ /dev/null @@ -1,16 +0,0 @@ -executor: - class: large - name: sb_node_22_classic - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Knip - command: | - cd code - yarn knip --no-exit-code - - report-workflow-on-failure - - cancel-workflow-on-failure diff --git a/.circleci/src/jobs/lint.yml b/.circleci/src/jobs/lint.yml deleted file mode 100644 index d27535998dcd..000000000000 --- a/.circleci/src/jobs/lint.yml +++ /dev/null @@ -1,16 +0,0 @@ -executor: - class: medium+ - name: sb_node_22_classic - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Lint - command: | - cd code - yarn lint - - report-workflow-on-failure - - cancel-workflow-on-failure diff --git a/.circleci/src/jobs/pretty-docs.yml b/.circleci/src/jobs/pretty-docs.yml deleted file mode 100644 index 6e1c5f18ad6e..000000000000 --- a/.circleci/src/jobs/pretty-docs.yml +++ /dev/null @@ -1,25 +0,0 @@ -executor: - class: medium+ - name: sb_node_22_classic - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - restore_cache: - name: Restore Yarn cache - keys: - - prettydocs-yarn-2-cache-v8--{{ checksum "yarn.lock" }} - - run: - name: Install - command: | - yarn install - - save_cache: - name: Save Yarn cache - key: prettydocs-yarn-2-cache-v8--{{ checksum "yarn.lock" }} - paths: - - ~/.yarn/berry/cache - - run: - name: Prettier - command: | - cd scripts - yarn docs:prettier:check diff --git a/.circleci/src/jobs/script-checks.yml b/.circleci/src/jobs/script-checks.yml deleted file mode 100644 index bc5d22e1d6e3..000000000000 --- a/.circleci/src/jobs/script-checks.yml +++ /dev/null @@ -1,26 +0,0 @@ -executor: sb_node_22_browsers - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Check parallelism count - command: | - cd scripts - yarn get-template --check - - run: - name: Type check - command: | - cd scripts - yarn check - - run: - name: Run tests - command: | - cd scripts - yarn test --coverage - - store_test_results: - path: storybook/scripts/junit.xml - - report-workflow-on-failure - - cancel-workflow-on-failure diff --git a/.circleci/src/jobs/smoke-test-sandboxes.yml b/.circleci/src/jobs/smoke-test-sandboxes.yml deleted file mode 100644 index adb0bc7302f2..000000000000 --- a/.circleci/src/jobs/smoke-test-sandboxes.yml +++ /dev/null @@ -1,22 +0,0 @@ -executor: - class: medium - name: sb_node_18_browsers - -parameters: - parallelism: - type: integer - -parallelism: << parameters.parallelism >> - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Smoke Testing Sandboxes - command: yarn task --task smoke-test --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task smoke-test) --no-link --start-from=never --junit - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task smoke-test) - - store_test_results: - path: storybook/test-results diff --git a/.circleci/src/jobs/stories-tests.yml b/.circleci/src/jobs/stories-tests.yml deleted file mode 100644 index bb1f1d2bc9a4..000000000000 --- a/.circleci/src/jobs/stories-tests.yml +++ /dev/null @@ -1,19 +0,0 @@ -executor: - class: xlarge - name: sb_playwright -parallelism: 2 -steps: - - git-shallow-clone/checkout_advanced: - clone_options: --depth 1 --verbose - - attach_workspace: - at: /tmp - - run: - command: | - cd code - TEST_FILES=$(circleci tests glob "**/*.{stories}.{ts,tsx,js,jsx,cjs}" | sed "/^e2e-tests\//d" | sed "/^node_modules\//d") - echo "$TEST_FILES" | circleci tests run --command="xargs yarn test --reporter=junit --reporter=default --outputFile=../test-results/junit-${CIRCLE_NODE_INDEX}.xml" --verbose - name: Run tests - - store_test_results: - path: storybook/test-results - - report-workflow-on-failure - - cancel-workflow-on-failure diff --git a/.circleci/src/jobs/test-init-empty-windows.yml b/.circleci/src/jobs/test-init-empty-windows.yml deleted file mode 100644 index 29896855b782..000000000000 --- a/.circleci/src/jobs/test-init-empty-windows.yml +++ /dev/null @@ -1,159 +0,0 @@ -executor: win/default - -parameters: - packageManager: - type: string - template: - type: string - -working_directory: 'C:\Users\circleci\storybook' - -steps: - - checkout - - attach_workspace: - at: 'C:\Users\circleci' - - run: - name: Setup Node & Yarn on Windows - shell: bash.exe - # We pin to Node 22 because of npm issues with Node 24 as it isn't fully stable yet and it causes issues when running npx - command: | - choco install nodejs-lts --version=22.11.0 -y - corepack enable - # Make sure to install dependencies again because we are now running on Windows and the attached workspace had dependencies installed for Linux - - run: - name: Install dependencies - shell: bash.exe - command: yarn install - - when: - condition: - equal: ['npm', << parameters.packageManager >>] - steps: - - run: - name: Verdaccio - background: true - shell: bash.exe - command: | - cd code - yarn local-registry --open - - run: - name: Wait on Verdaccio - shell: bash.exe - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - - run: - name: Storybook init from empty directory (Windows NPM) - shell: bash.exe - command: | - cd .. - mkdir empty-<< parameters.template >> - cd empty-<< parameters.template >> - npm set registry http://localhost:6001 - npx storybook init --yes --package-manager npm - npm run storybook -- --smoke-test - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - STORYBOOK_DISABLE_TELEMETRY: true - - when: - condition: - equal: ['yarn2', << parameters.packageManager >>] - steps: - - run: - name: Verdaccio - background: true - shell: bash.exe - command: | - cd code - yarn local-registry --open - - run: - name: Wait on Verdaccio - shell: bash.exe - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - - run: - name: Storybook init from empty directory (Windows Yarn 2) - shell: bash.exe - command: | - cd .. - mkdir empty-<< parameters.template >> - cd empty-<< parameters.template >> - yarn set version berry - yarn config set registry http://localhost:6001 - yarn dlx storybook init --yes --package-manager yarn2 - yarn storybook --smoke-test - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - STORYBOOK_DISABLE_TELEMETRY: true - - when: - condition: - equal: ['pnpm', << parameters.packageManager >>] - steps: - - run: - name: Verdaccio - background: true - shell: bash.exe - command: | - cd code - yarn local-registry --open - - run: - name: Wait on Verdaccio - shell: bash.exe - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - - run: - name: Storybook init from empty directory (Windows PNPM) - shell: bash.exe - command: | - cd .. - mkdir empty-<< parameters.template >> - cd empty-<< parameters.template >> - npm i -g pnpm - pnpm config set registry http://localhost:6001 - pnpm dlx storybook init --yes --package-manager pnpm - pnpm run storybook --smoke-test - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - STORYBOOK_DISABLE_TELEMETRY: true - - when: - condition: - equal: ['react-vite-ts', << parameters.template >>] - steps: - - run: - name: Verdaccio - background: true - shell: bash.exe - command: | - cd code - yarn local-registry --open - - run: - name: Wait on Verdaccio - shell: bash.exe - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - - run: - name: Storybook init from empty directory (Windows --skip-install) - shell: bash.exe - command: | - cd .. - mkdir empty-<< parameters.template >>-no-install - cd empty-<< parameters.template >>-no-install - npx storybook init --yes --skip-install - npm install - npm run build-storybook - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - STORYBOOK_DISABLE_TELEMETRY: true - # TODO: Find out a solution for this. The Discord orb we use isn't powershell compatible. - # For now this will just not report on Discord, but it will fail correctly in CircleCI. - # - report-workflow-on-failure diff --git a/.circleci/src/jobs/test-init-empty.yml b/.circleci/src/jobs/test-init-empty.yml deleted file mode 100644 index 6f73dfec572d..000000000000 --- a/.circleci/src/jobs/test-init-empty.yml +++ /dev/null @@ -1,134 +0,0 @@ -executor: - class: small - name: sb_node_22_browsers - -parameters: - packageManager: - type: string - template: - type: string - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - when: - condition: - equal: ['npm', << parameters.packageManager >>] - steps: - - run: - name: Verdaccio - background: true - command: | - cd code - yarn local-registry --open - - run: - name: Wait on Verdaccio - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - - run: - name: Storybook init from empty directory (NPM) - command: | - cd .. - mkdir empty-<< parameters.template >> - cd empty-<< parameters.template >> - npm set registry http://localhost:6001 - npx storybook init --yes --package-manager npm - npm run storybook -- --smoke-test - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - STORYBOOK_DISABLE_TELEMETRY: true - - when: - condition: - equal: ['yarn2', << parameters.packageManager >>] - steps: - - run: - name: Verdaccio - background: true - command: | - cd code - yarn local-registry --open - - run: - name: Wait on Verdaccio - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - - run: - name: Storybook init from empty directory (Yarn 2) - command: | - cd .. - mkdir empty-<< parameters.template >> - cd empty-<< parameters.template >> - yarn set version berry - yarn config set registry http://localhost:6001 - yarn dlx storybook init --yes --package-manager yarn2 - yarn storybook --smoke-test - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - STORYBOOK_DISABLE_TELEMETRY: true - - when: - condition: - equal: ['pnpm', << parameters.packageManager >>] - steps: - - run: - name: Verdaccio - background: true - command: | - cd code - yarn local-registry --open - - run: - name: Wait on Verdaccio - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - - run: - name: Storybook init from empty directory (PNPM) - command: | - cd .. - mkdir empty-<< parameters.template >> - cd empty-<< parameters.template >> - npm i -g pnpm - pnpm config set registry http://localhost:6001 - pnpm dlx storybook init --yes --package-manager pnpm - pnpm run storybook --smoke-test - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - STORYBOOK_DISABLE_TELEMETRY: true - - when: - condition: - equal: ['react-vite-ts', << parameters.template >>] - steps: - - run: - name: Verdaccio - background: true - command: | - cd code - yarn local-registry --open - - run: - name: Wait on Verdaccio - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - - run: - name: Storybook init from empty directory (--skip-install) - command: | - cd .. - mkdir empty-<< parameters.template >>-no-install - cd empty-<< parameters.template >>-no-install - npx storybook init --yes --skip-install - npm install - npm run build-storybook - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_INIT_EMPTY_TYPE: << parameters.template >> - STORYBOOK_DISABLE_TELEMETRY: true - - report-workflow-on-failure diff --git a/.circleci/src/jobs/test-init-features.yml b/.circleci/src/jobs/test-init-features.yml deleted file mode 100644 index 4e84e5a281ec..000000000000 --- a/.circleci/src/jobs/test-init-features.yml +++ /dev/null @@ -1,34 +0,0 @@ -executor: - class: small - name: sb_node_22_browsers - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Verdaccio - background: true - command: | - cd code - yarn local-registry --open - - run: - name: Wait on Verdaccio - command: | - cd code - yarn wait-on tcp:127.0.0.1:6001 - yarn wait-on tcp:127.0.0.1:6002 - - run: - name: Storybook init for features - command: | - cd .. - mkdir features-1 - cd features-1 - npm set registry http://localhost:6001 - npx create-storybook --yes --package-manager npm --features docs test a11y --loglevel=debug - npx vitest - environment: - IN_STORYBOOK_SANDBOX: true - STORYBOOK_INIT_EMPTY_TYPE: react-vite-ts - STORYBOOK_DISABLE_TELEMETRY: true diff --git a/.circleci/src/jobs/test-portable-stories.yml b/.circleci/src/jobs/test-portable-stories.yml deleted file mode 100644 index a1fa5696cc89..000000000000 --- a/.circleci/src/jobs/test-portable-stories.yml +++ /dev/null @@ -1,36 +0,0 @@ -executor: - name: sb_playwright - class: medium+ - -parameters: - directory: - type: string - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Install dependencies - command: yarn install --no-immutable - working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >> - environment: - YARN_ENABLE_IMMUTABLE_INSTALLS: false - - run: - name: Run Jest tests - command: yarn jest - working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >> - - run: - name: Run Vitest tests - command: yarn vitest - working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >> - - run: - name: Run Playwright CT tests - command: yarn playwright-ct - working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >> - - run: - name: Run Cypress CT tests - command: yarn cypress - working_directory: test-storybooks/portable-stories-kitchen-sink/<< parameters.directory >> - - report-workflow-on-failure diff --git a/.circleci/src/jobs/test-runner-dev.yml b/.circleci/src/jobs/test-runner-dev.yml deleted file mode 100644 index 698f06ee96f6..000000000000 --- a/.circleci/src/jobs/test-runner-dev.yml +++ /dev/null @@ -1,22 +0,0 @@ -executor: - class: large - name: sb_playwright - -parameters: - parallelism: - type: integer - -parallelism: << parameters.parallelism >> - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Running Test Runner in Dev mode - command: yarn task --task test-runner-dev --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner-dev) --no-link --start-from=never --junit - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner-dev) - - store_test_results: - path: storybook/test-results diff --git a/.circleci/src/jobs/test-runner-production.yml b/.circleci/src/jobs/test-runner-production.yml deleted file mode 100644 index 7dc4f5650138..000000000000 --- a/.circleci/src/jobs/test-runner-production.yml +++ /dev/null @@ -1,35 +0,0 @@ -executor: - class: medium+ - name: sb_playwright - -parameters: - parallelism: - type: integer - -parallelism: << parameters.parallelism >> - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Install sandbox dependencies - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner) - cd $(yarn get-sandbox-dir --template $TEMPLATE) && yarn - - start-event-collector - - run: - name: Running Test Runner - command: yarn task --task test-runner --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner) --no-link --start-from=never --junit - environment: - STORYBOOK_TELEMETRY_DEBUG: 1 - STORYBOOK_TELEMETRY_URL: 'http://localhost:6007/event-log' - - run: - name: 'Check Telemetry' - command: yarn --cwd scripts jiti ./event-log-checker.ts test-run $(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner) - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task test-runner) - - store_test_results: - path: storybook/test-results diff --git a/.circleci/src/jobs/test-yarn-pnp.yml b/.circleci/src/jobs/test-yarn-pnp.yml deleted file mode 100644 index 9310787ddd5f..000000000000 --- a/.circleci/src/jobs/test-yarn-pnp.yml +++ /dev/null @@ -1,21 +0,0 @@ -executor: - name: sb_playwright - class: medium - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Install dependencies - command: yarn install --no-immutable - working_directory: test-storybooks/yarn-pnp - environment: - YARN_ENABLE_IMMUTABLE_INSTALLS: false - - run: - name: Run Storybook smoke test - command: yarn storybook --smoke-test - working_directory: test-storybooks/yarn-pnp - - - report-workflow-on-failure diff --git a/.circleci/src/jobs/unit-tests.yml b/.circleci/src/jobs/unit-tests.yml deleted file mode 100644 index f9ff1afec308..000000000000 --- a/.circleci/src/jobs/unit-tests.yml +++ /dev/null @@ -1,31 +0,0 @@ -executor: - class: xlarge - name: sb_playwright - -parallelism: 2 - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Run tests - # TEST_FILES should include all relevant test files, including story files, but not e2e-tests - # `circleci tests run` shards tests across parallel runners and allows rerunning failed tests by passing a subset of test files to the command via `xargs` - command: | - cd code - TEST_FILES=$(circleci tests glob "**/*.{test,spec,stories}.{ts,tsx,js,jsx,cjs}" | sed "/^e2e-tests\//d" | sed "/^node_modules\//d") - echo "$TEST_FILES" | circleci tests run --command="xargs yarn test --reporter=junit --reporter=default --outputFile=../test-results/junit-${CIRCLE_NODE_INDEX}.xml" --verbose - - store_test_results: - path: storybook/test-results - # TODO: bring coverage back later. This has caused flakiness in the tests because - # Somehow Vitest reports coverage while some tests are still running, - # then it tries to report coverage again and as result it crashes like this: - # https://app.circleci.com/pipelines/github/storybookjs/storybook/85043/workflows/4ddf7907-b93c-4b17-8fdf-fe0bd7fde905/jobs/715446 - # - persist_to_workspace: - # root: /tmp - # paths: - # - storybook/code/coverage - - report-workflow-on-failure - - cancel-workflow-on-failure diff --git a/.circleci/src/jobs/vitest-integration.yml b/.circleci/src/jobs/vitest-integration.yml deleted file mode 100644 index 4a7180765e46..000000000000 --- a/.circleci/src/jobs/vitest-integration.yml +++ /dev/null @@ -1,35 +0,0 @@ -executor: - class: xlarge - name: sb_playwright - -parameters: - parallelism: - type: integer - -parallelism: << parameters.parallelism >> - -steps: - - git-shallow-clone/checkout_advanced: - clone_options: '--depth 1 --verbose' - - attach_workspace: - at: /tmp - - run: - name: Install sandbox dependencies - command: | - corepack enable - TEMPLATE=$(yarn get-template --cadence << pipeline.parameters.workflow >> --task vitest-integration) - cd $(yarn get-sandbox-dir --template $TEMPLATE) && yarn - - start-event-collector - - run: - name: Running story tests in Vitest - command: yarn task --task vitest-integration --template $(yarn get-template --cadence << pipeline.parameters.workflow >> --task vitest-integration) --no-link --start-from=never --junit - environment: - STORYBOOK_TELEMETRY_DEBUG: 1 - STORYBOOK_TELEMETRY_URL: 'http://localhost:6007/event-log' - - run: - name: 'Check Telemetry' - command: yarn --cwd scripts jiti ./event-log-checker.ts test-run $(yarn get-template --cadence << pipeline.parameters.workflow >> --task vitest-integration) - - report-workflow-on-failure: - template: $(yarn get-template --cadence << pipeline.parameters.workflow >> --task vitest-integration) - - store_test_results: - path: storybook/test-results diff --git a/.circleci/src/workflows/daily.yml b/.circleci/src/workflows/daily.yml deleted file mode 100644 index e16835808ae4..000000000000 --- a/.circleci/src/workflows/daily.yml +++ /dev/null @@ -1,122 +0,0 @@ -# Comprehensive daily testing workflow, for the most exhaustive (but expensive) checks. -# Uses the `daily` sandboxes from `sandbox-templates.ts`, which includes the `normal` and `merged` sandboxes -# Triggered via the `ci:daily` pull request label - -when: - equal: [daily, << pipeline.parameters.workflow >>] - -jobs: - - pretty-docs - - build - - lint: - requires: - - build - - knip: - requires: - - build - - bench-packages: - requires: - - build - - check - - unit-tests: - requires: - - build - - stories-tests: - requires: - - build - - script-checks: - requires: - - build - - chromatic-internal-storybook: - requires: - - build - - create-sandboxes: - parallelism: 39 - requires: - - build - - check-sandboxes: - parallelism: 1 - requires: - - create-sandboxes - # - smoke-test-sandboxes: # disabled for now - # requires: - # - create-sandboxes - - chromatic-sandboxes: - parallelism: 36 - requires: - - create-sandboxes - - e2e-production: - parallelism: 7 - requires: - - create-sandboxes - - e2e-dev: - parallelism: 28 - requires: - - create-sandboxes - - test-runner-production: - parallelism: 34 - requires: - - create-sandboxes - - vitest-integration: - parallelism: 13 - requires: - - create-sandboxes - - test-portable-stories: - requires: - - build - matrix: - parameters: - directory: ['react', 'vue3', 'nextjs', 'svelte'] - - test-yarn-pnp: - requires: - - build - - e2e-ui: - requires: - - build - - e2e-ui-vitest-3: - requires: - - build - - test-init-features: - requires: - - build - - test-init-empty: - requires: - - build - matrix: - parameters: - packageManager: - - 'npm' - # TODO: reenable once we find out the source of failure - # - "yarn1" - # - "yarn2" - # - "pnpm" - template: - - 'react-vite-ts' - - 'nextjs-ts' - - 'vue-vite-ts' - # --smoke-test is not supported for the angular builder right now - # - "angular-cli" - - 'lit-vite-ts' - - test-init-empty-windows: - requires: - - build - matrix: - parameters: - packageManager: - - 'npm' - template: - - 'react-vite-ts' - - 'nextjs-ts' - - 'vue-vite-ts' - - 'lit-vite-ts' -# TODO: don't forget to reenable this -# - bench-sandboxes: -# parallelism: 5 -# requires: -# - create-sandboxes - -# TODO: reenable once we find out the source of flakyness -# - test-runner-dev: -# parallelism: 4 -# requires: -# - create-sandboxes diff --git a/.circleci/src/workflows/docs.yml b/.circleci/src/workflows/docs.yml deleted file mode 100644 index 2ee7a11790d8..000000000000 --- a/.circleci/src/workflows/docs.yml +++ /dev/null @@ -1,8 +0,0 @@ -# Documentation checks -# Triggered via the `ci:docs` pull request label - -when: - equal: [docs, << pipeline.parameters.workflow >>] - -jobs: - - pretty-docs diff --git a/.circleci/src/workflows/merged.yml b/.circleci/src/workflows/merged.yml deleted file mode 100644 index cf0078883d76..000000000000 --- a/.circleci/src/workflows/merged.yml +++ /dev/null @@ -1,103 +0,0 @@ -# Post-merge validation workflow, runs after a pull request is merged -# Uses the `merged` sandboxes from `sandbox-templates.ts`, which includes the `normal` sandboxes -# Can be triggered via the `ci:merged` pull request label - -when: - equal: [merged, << pipeline.parameters.workflow >>] - -jobs: - - pretty-docs - - build - - lint: - requires: - - build - - knip: - requires: - - build - - bench-packages: - requires: - - build - - check - - unit-tests: - requires: - - build - - stories-tests: - requires: - - build - - script-checks: - requires: - - build - - chromatic-internal-storybook: - requires: - - build - - coverage: - requires: - - unit-tests - - create-sandboxes: - parallelism: 22 - requires: - - build - - chromatic-sandboxes: - parallelism: 19 - requires: - - create-sandboxes - - e2e-production: - parallelism: 6 - requires: - - create-sandboxes - - e2e-dev: - parallelism: 14 - requires: - - create-sandboxes - - test-runner-production: - parallelism: 17 - requires: - - create-sandboxes - - vitest-integration: - parallelism: 7 - requires: - - create-sandboxes - - check-sandboxes: - parallelism: 1 - requires: - - create-sandboxes - - test-portable-stories: - requires: - - build - matrix: - parameters: - directory: ['react', 'vue3', 'nextjs', 'svelte'] - - test-yarn-pnp: - requires: - - build - - e2e-ui: - requires: - - build - - e2e-ui-vitest-3: - requires: - - build - - test-init-features: - requires: - - build - - test-init-empty-windows: - requires: - - build - matrix: - parameters: - packageManager: - - 'npm' - template: - - 'react-vite-ts' - - 'nextjs-ts' - - 'vue-vite-ts' - - 'lit-vite-ts' -# TODO: don't forget to reenable this -# - bench-sandboxes: -# parallelism: 5 -# requires: -# - create-sandboxes -# TODO: reenable once we find out the source of flakyness -# - test-runner-dev: -# parallelism: 4 -# requires: -# - create-sandboxes diff --git a/.circleci/src/workflows/normal.yml b/.circleci/src/workflows/normal.yml deleted file mode 100644 index a3f4ed1de6f6..000000000000 --- a/.circleci/src/workflows/normal.yml +++ /dev/null @@ -1,90 +0,0 @@ -# Standard PR check workflow, applicable to most pull requests -# Uses the `normal` sandboxes from `sandbox-templates.ts` -# Can be triggered via the `ci:normal` pull request label - -when: - equal: [normal, << pipeline.parameters.workflow >>] - -jobs: - - pretty-docs - - build - - lint: - requires: - - build - - knip: - requires: - - build - - bench-packages: - requires: - - build - - check - - unit-tests: - requires: - - build - - stories-tests: - requires: - - build - - script-checks: - requires: - - build - - chromatic-internal-storybook: - requires: - - build - - coverage: - requires: - - unit-tests - - create-sandboxes: - parallelism: 14 - requires: - - build - - chromatic-sandboxes: - parallelism: 11 - requires: - - create-sandboxes - - e2e-production: - parallelism: 6 - requires: - - create-sandboxes - - e2e-dev: - parallelism: 8 - requires: - - create-sandboxes - - test-runner-production: - parallelism: 9 - requires: - - create-sandboxes - - vitest-integration: - parallelism: 5 - requires: - - create-sandboxes - - check-sandboxes: - parallelism: 1 - requires: - - create-sandboxes - # TODO: don't forget to reenable this - # - bench-sandboxes: - # parallelism: 5 - # requires: - # - create-sandboxes - - test-yarn-pnp: - requires: - - build - - e2e-ui: - requires: - - build - - e2e-ui-vitest-3: - requires: - - build - - test-init-features: - requires: - - build - - test-portable-stories: - requires: - - build - matrix: - parameters: - directory: ['react', 'vue3', 'nextjs', 'svelte'] - # TODO: reenable once we find out the source of flakyness - # - test-runner-dev: - # requires: - # - create-sandboxes diff --git a/.github/workflows/tests-unit.yml b/.github/workflows/tests-unit.yml deleted file mode 100644 index cf48ebae5015..000000000000 --- a/.github/workflows/tests-unit.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Unit tests - -on: - push: - branches: - - next - pull_request: - types: [opened, synchronize, reopened] - -env: - NODE_OPTIONS: '--max_old_space_size=4096' - -jobs: - build: - name: Core Unit Tests, windows-latest - if: github.repository_owner == 'storybookjs' - runs-on: windows-11-arm - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: Setup Node.js and Install Dependencies - uses: ./.github/actions/setup-node-and-install - with: - install-code-deps: true - - - name: compile - run: yarn task --task compile --start-from=compile - - - name: Install Playwright Dependencies - run: cd code && yarn exec playwright install chromium --with-deps - - - name: test - run: yarn test diff --git a/.gitignore b/.gitignore index 6a7de9e3cf28..edc2353dc080 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,7 @@ tsconfig.vitest-temp.json test-storybooks/ember-cli/ember-output test-storybooks/angular-cli/addon-jest.testresults.json - +.circleci/*.generated.yml npm-shrinkwrap.json .tern-port .parcel-cache diff --git a/CHANGELOG.prerelease.md b/CHANGELOG.prerelease.md index 2eda235c9da1..3a2aeed6987e 100644 --- a/CHANGELOG.prerelease.md +++ b/CHANGELOG.prerelease.md @@ -1,3 +1,8 @@ +## 10.2.0-beta.0 + +- Manager: Fix system query parameters being overridable - [#33535](https://github.com/storybookjs/storybook/pull/33535), thanks @JReinhold! +- NextJSVite: Upgrade plugin - [#33538](https://github.com/storybookjs/storybook/pull/33538), thanks @ndelangen! + ## 10.2.0-alpha.18 - Build: Fix `ejslint` execution path in lint-staged - [#33504](https://github.com/storybookjs/storybook/pull/33504), thanks @Yeonny0723! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c3fa90bab337..5472f281b06e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -206,6 +206,17 @@ yarn build --prod --watch angular storybook addon-docs You can pick a specific template to use as your sandbox by running `yarn task`, which will prompt you to make further choices about which template you want and which task you want to run. +### Focussing on fixing a sandbox in CI + +Our CI runs many sandboxes, especially when selecting the `ci:daily` workflow. + +When a particular sandbox is failing, it's preferred to debug locally, but if this is somehow not possible, you can force the Ci to focus on a selection of sandboxes instead of running all. Here's the process of how: + +Inside of here you can edit the filter-function: +https://github.com/storybookjs/storybook/blob/3d49093954243d4d520774243866de840f298bf4/scripts/ci/main.ts#L70-L88 + +In fact you can filter on any job you wish, only running `test-runner`, `e2e`, `vite`-sandboxes, etc. + ## Troubleshooting ### The initialization process throws an error diff --git a/code/core/src/cli/AddonVitestService.ts b/code/core/src/cli/AddonVitestService.ts index b4c2a8e64c5f..a8375d0fa5f0 100644 --- a/code/core/src/cli/AddonVitestService.ts +++ b/code/core/src/cli/AddonVitestService.ts @@ -124,6 +124,11 @@ export class AddonVitestService { let result: 'installed' | 'skipped' | 'aborted' | 'failed'; + if (process.env.STORYBOOK_CLI_SKIP_PLAYWRIGHT_INSTALLATION) { + result = 'skipped'; + return { errors, result }; + } + try { const shouldBeInstalled = options.yes ? true diff --git a/code/core/src/manager-api/modules/url.ts b/code/core/src/manager-api/modules/url.ts index c37e95537bf3..9e4e3570b58a 100644 --- a/code/core/src/manager-api/modules/url.ts +++ b/code/core/src/manager-api/modules/url.ts @@ -12,6 +12,7 @@ import type { API_Layout, API_UI, API_ViewMode, Args } from 'storybook/internal/ import { global } from '@storybook/global'; import { dequal as deepEqual } from 'dequal'; +import { omit } from 'es-toolkit/object'; import { stringify } from 'picoquery'; import type { ModuleArgs, ModuleFn } from '../lib/types'; @@ -261,18 +262,23 @@ export const init: ModuleFn = (moduleArgs) => { let globalsParam = inheritGlobals ? mergeSerializedParams(customQueryParams?.globals ?? '', globals) : globals; - let customParams = stringify(otherParams, { + let customManagerParams = stringify(otherParams, { + nesting: true, + nestingSyntax: 'js', + }); + let customPreviewParams = stringify(omit(otherParams, ['id', 'viewMode']), { nesting: true, nestingSyntax: 'js', }); argsParam = argsParam && `&args=${argsParam}`; globalsParam = globalsParam && `&globals=${globalsParam}`; - customParams = customParams && `&${customParams}`; + customManagerParams = customManagerParams && `&${customManagerParams}`; + customPreviewParams = customPreviewParams && `&${customPreviewParams}`; return { - managerHref: `${managerBase}?path=/${viewMode}/${refId ? `${refId}_` : ''}${storyId}${argsParam}${globalsParam}${customParams}`, - previewHref: `${previewBase}?id=${storyId}&viewMode=${viewMode}${refParam}${argsParam}${refId ? '' : globalsParam}${customParams}`, + managerHref: `${managerBase}?path=/${viewMode}/${refId ? `${refId}_` : ''}${storyId}${argsParam}${globalsParam}${customManagerParams}`, + previewHref: `${previewBase}?id=${storyId}&viewMode=${viewMode}${refParam}${argsParam}${refId ? '' : globalsParam}${customPreviewParams}`, }; }, getQueryParam(key) { diff --git a/code/core/src/manager-api/tests/url.test.js b/code/core/src/manager-api/tests/url.test.js index 1b095ff0de11..c17db1bf357e 100644 --- a/code/core/src/manager-api/tests/url.test.js +++ b/code/core/src/manager-api/tests/url.test.js @@ -364,10 +364,21 @@ describe('getStoryHrefs', () => { store.setState(state); const { managerHref, previewHref } = api.getStoryHrefs('test--story', { - queryParams: { one: 1, foo: { bar: 'baz' } }, + queryParams: { + one: 1, + foo: { bar: 'baz' }, + id: 'not-allowed-in-preview', + viewMode: 'not-allowed-in-preview', + }, }); expect(managerHref).toContain('&args=a:1&globals=b:2&one=1&foo.bar=baz'); expect(previewHref).toContain('&args=a:1&globals=b:2&one=1&foo.bar=baz'); + + expect(managerHref).toContain('id=not-allowed-in-preview'); + expect(previewHref).not.toContain('id=not-allowed-in-preview'); + + expect(managerHref).toContain('viewMode=not-allowed-in-preview'); + expect(previewHref).not.toContain('viewMode=not-allowed-in-preview'); }); it('correctly preserves args and globals encoding', () => { diff --git a/code/core/src/node-logger/logger/logger.ts b/code/core/src/node-logger/logger/logger.ts index 3514e949f5c5..197c1333c091 100644 --- a/code/core/src/node-logger/logger/logger.ts +++ b/code/core/src/node-logger/logger/logger.ts @@ -166,16 +166,26 @@ type BoxOptions = { } & clack.BoxOptions; export const logBox = (message: string, { title, ...options }: BoxOptions = {}) => { - if (shouldLog('info')) { - logTracker.addLog('info', message); - if (isClackEnabled()) { - clack.box(message, title, { - ...options, - width: options.width ?? 'auto', - }); - } else { - console.log(message); + try { + if (shouldLog('info')) { + logTracker.addLog('info', message); + if (isClackEnabled()) { + clack.box(message, title, { + ...options, + width: options.width ?? 'auto', + }); + } else { + console.log(message); + } } + } catch { + /** + * Clack.logBox can throw with "Invalid count value"-errors + * + * Possibly it may only happen on CI, but considering rendering a box is not critical, we will + * just log the message to the console and discard the error. + */ + clack.log.message(message); } }; diff --git a/code/e2e-tests/addon-mcp.spec.ts b/code/e2e-tests/addon-mcp.spec.ts new file mode 100644 index 000000000000..4f7db4fdc984 --- /dev/null +++ b/code/e2e-tests/addon-mcp.spec.ts @@ -0,0 +1,273 @@ +/* eslint-disable local-rules/no-uncategorized-errors */ +import type { APIRequestContext } from '@playwright/test'; +import { expect, test } from '@playwright/test'; +import process from 'process'; + +const storybookUrl = process.env.STORYBOOK_URL || 'http://localhost:8001'; +const templateName = process.env.STORYBOOK_TEMPLATE_NAME || ''; +const type = process.env.STORYBOOK_TYPE || 'dev'; + +const MCP_ENDPOINT = `${storybookUrl}/mcp`; + +/** Helper to make MCP requests and parse SSE response */ +async function mcpRequest( + request: APIRequestContext, + method: string, + params: Record = {}, + id = 1, + headers: Record = {} +) { + const response = await request.post(MCP_ENDPOINT, { + headers: { + 'Content-Type': 'application/json', + ...headers, + }, + data: { + jsonrpc: '2.0', + id, + method, + params, + }, + }); + + if (!response.ok()) { + throw new Error(`HTTP error! status: ${response.status()}`); + } + + // MCP responses come as SSE (Server-Sent Events) format + // Format: "event: message\ndata: {...}" + const text = await response.text(); + // Extract the JSON from the "data: " line + const dataMatch = text.match(/^data: (.+)$/m); + if (!dataMatch) { + throw new Error(`Invalid SSE response format: ${text}`); + } + return JSON.parse(dataMatch[1]); +} + +test.describe('addon-mcp', () => { + test.skip( + templateName !== 'react-vite/default-ts', + 'Only run for sandboxes with addon-mcp configured' + ); + + test.describe('Manifests', () => { + test.describe('Component Manifest', () => { + test('should have valid components.json structure', async ({ request }) => { + const response = await request.get(`${storybookUrl}/manifests/components.json`); + const json = await response.json(); + + // Check basic structure + expect(json).toHaveProperty('v'); + expect(typeof json.v).toBe('number'); + expect(json).toHaveProperty('components'); + expect(typeof json.components).toBe('object'); + }); + + test('should contain the example Button component', async ({ request }) => { + const response = await request.get(`${storybookUrl}/manifests/components.json`); + const json = await response.json(); + + // Check for example-button component + expect(json.components).toHaveProperty('example-button'); + + const button = json.components['example-button']; + expect(button).toMatchObject({ + id: 'example-button', + name: 'Button', + path: expect.stringContaining('Button.stories'), + }); + + // Should have stories + expect(button.stories).toBeInstanceOf(Array); + expect(button.stories.length).toBeGreaterThan(0); + + // Should have reactDocgen info with props + expect(button).toHaveProperty('reactDocgen'); + expect(button.reactDocgen).toHaveProperty('props'); + expect(button.reactDocgen.props).toHaveProperty('primary'); + }); + }); + + test.describe('Docs Manifest', () => { + test('should have valid docs.json structure', async ({ request }) => { + const response = await request.get(`${storybookUrl}/manifests/docs.json`); + const json = await response.json(); + + // Check basic structure + expect(json).toHaveProperty('v'); + expect(typeof json.v).toBe('number'); + expect(json).toHaveProperty('docs'); + expect(typeof json.docs).toBe('object'); + }); + + test('should contain the "Configure your project" docs entry', async ({ request }) => { + const response = await request.get(`${storybookUrl}/manifests/docs.json`); + const json = await response.json(); + + // Check for configure-your-project--docs entry + expect(json.docs).toHaveProperty('configure-your-project--docs'); + + const configureDoc = json.docs['configure-your-project--docs']; + expect(configureDoc).toMatchObject({ + id: 'configure-your-project--docs', + name: 'Docs', + path: expect.stringContaining('Configure.mdx'), + title: 'Configure your project', + }); + + // Should have content + expect(configureDoc).toHaveProperty('content'); + expect(typeof configureDoc.content).toBe('string'); + expect(configureDoc.content.length).toBeGreaterThan(0); + }); + }); + }); + + test.describe('MCP', () => { + test.skip(type !== 'dev', 'MCP server only runs in dev mode'); + + test.describe('Info Page', () => { + test('should show both toolsets as enabled', async ({ page }) => { + await page.goto(MCP_ENDPOINT); + + // Both toolsets should show as enabled + const enabledStatuses = page.locator('.toolset-status.enabled'); + await expect(enabledStatuses).toHaveCount(2); + + // Check that dev toolset is listed with its tools + const devToolset = page.locator('.toolset', { has: page.locator('text=dev') }); + await expect(devToolset).toBeVisible(); + await expect(devToolset.locator('.toolset-status')).toHaveText('enabled'); + + // Check that docs toolset is listed with its tools + const docsToolset = page.locator('.toolset', { has: page.locator('text=docs') }); + await expect(docsToolset).toBeVisible(); + await expect(docsToolset.locator('.toolset-status')).toHaveText('enabled'); + }); + }); + + test.describe('Session Initialization', () => { + test('should successfully initialize an MCP session', async ({ request }) => { + const response = await mcpRequest(request, 'initialize', { + protocolVersion: '2025-06-18', + capabilities: {}, + clientInfo: { + name: 'e2e-test-client', + version: '1.0.0', + }, + }); + + expect(response).toMatchObject({ + jsonrpc: '2.0', + id: 1, + result: { + protocolVersion: '2025-06-18', + capabilities: { + tools: { listChanged: true }, + }, + serverInfo: { + name: '@storybook/addon-mcp', + description: expect.stringContaining('agents'), + }, + }, + }); + + expect(response.result.serverInfo.version).toBeDefined(); + }); + }); + + test.describe('Tools Discovery', () => { + test('should list all available tools', async ({ request }) => { + const response = await mcpRequest(request, 'tools/list'); + + expect(response.result).toHaveProperty('tools'); + // Dev and docs tools should be present (4 total) + expect(response.result.tools).toHaveLength(4); + + const toolNames = response.result.tools.map((tool: { name: string }) => tool.name); + expect(toolNames).toContain('get-story-urls'); + expect(toolNames).toContain('get-ui-building-instructions'); + expect(toolNames).toContain('list-all-documentation'); + expect(toolNames).toContain('get-documentation'); + }); + }); + + test.describe('Tool: get-story-urls', () => { + test('should return story URLs for valid stories', async ({ request }) => { + // Use a path pattern that works regardless of sandbox location + const response = await mcpRequest(request, 'tools/call', { + name: 'get-story-urls', + arguments: { + stories: [ + { + exportName: 'Primary', + // Use a relative-style path that the tool should recognize + absoluteStoryPath: '/src/stories/Button.stories.ts', + }, + ], + }, + }); + + expect(response.result).toHaveProperty('content'); + expect(response.result.content).toHaveLength(1); + // Should contain either a valid URL or an error message about the story + expect(response.result.content[0]).toHaveProperty('text'); + }); + }); + + test.describe('Tool: get-ui-building-instructions', () => { + test('should return UI building instructions', async ({ request }) => { + const response = await mcpRequest(request, 'tools/call', { + name: 'get-ui-building-instructions', + arguments: {}, + }); + + expect(response.result).toHaveProperty('content'); + expect(response.result.content[0]).toHaveProperty('type', 'text'); + + const text = response.result.content[0].text; + expect(text).toContain('stories'); + expect(text.length).toBeGreaterThan(100); + }); + }); + + test.describe('Tool: list-all-documentation', () => { + test('should list all documentation from manifest', async ({ request }) => { + const response = await mcpRequest(request, 'tools/call', { + name: 'list-all-documentation', + arguments: {}, + }); + + expect(response.result).toHaveProperty('content'); + expect(response.result.content[0]).toHaveProperty('type', 'text'); + + const text = response.result.content[0].text; + // Should contain components section with Button + expect(text).toContain('Button'); + expect(text).toContain('example-button'); + }); + }); + + test.describe('Tool: get-documentation', () => { + test('should return documentation for a specific component', async ({ request }) => { + const response = await mcpRequest(request, 'tools/call', { + name: 'get-documentation', + arguments: { + id: 'example-button', + }, + }); + + expect(response.result).toHaveProperty('content'); + expect(response.result.content[0]).toHaveProperty('type', 'text'); + + const text = response.result.content[0].text; + // Should contain component info + expect(text).toContain('Button'); + expect(text).toContain('example-button'); + // Should contain stories + expect(text).toContain('Primary'); + }); + }); + }); +}); diff --git a/code/frameworks/nextjs-vite/package.json b/code/frameworks/nextjs-vite/package.json index 32e7e3b245f5..c8bfa4956e35 100644 --- a/code/frameworks/nextjs-vite/package.json +++ b/code/frameworks/nextjs-vite/package.json @@ -79,7 +79,7 @@ "@storybook/react": "workspace:*", "@storybook/react-vite": "workspace:*", "styled-jsx": "5.1.6", - "vite-plugin-storybook-nextjs": "^3.1.7" + "vite-plugin-storybook-nextjs": "^3.1.9" }, "devDependencies": { "@types/node": "^22.19.1", diff --git a/code/lib/cli-storybook/src/sandbox-templates.ts b/code/lib/cli-storybook/src/sandbox-templates.ts index 8fb6f7222a02..5b58066b35f4 100644 --- a/code/lib/cli-storybook/src/sandbox-templates.ts +++ b/code/lib/cli-storybook/src/sandbox-templates.ts @@ -4,6 +4,10 @@ import { type StoriesEntry, type StorybookConfigRaw } from 'storybook/internal/t import { ProjectType } from '../../../core/src/cli/projectTypes'; import { SupportedBuilder } from '../../../core/src/types/modules/builders'; +export type TemplateType = Pick; +export type AllTemplatesKey = keyof typeof allTemplates; +export type AllTemplatesType = Record; + export type SkippableTask = | 'smoke-test' | 'test-runner' @@ -18,6 +22,7 @@ export type TemplateKey = | keyof typeof baseTemplates | keyof typeof internalTemplates | keyof typeof benchTemplates; + export type Cadence = keyof typeof templatesByCadence; // Some properties e.g. experimentalTestSyntax are only available in framework specific types for StorybookConfig, therefore we loosen the type here otherwise it would always fail @@ -369,11 +374,13 @@ export const baseTemplates = { }, modifications: { useCsfFactory: true, - extraDependencies: ['prop-types', '@types/prop-types'], + extraDependencies: ['prop-types', '@types/prop-types', '@storybook/addon-mcp'], + editAddons: (addons) => [...addons, '@storybook/addon-mcp'], mainConfig: { features: { developmentModeForBuild: true, experimentalTestSyntax: true, + experimentalComponentsManifest: true, }, }, }, @@ -742,19 +749,20 @@ export const baseTemplates = { }, skipTasks: ['e2e-tests', 'bench'], }, - 'qwik-vite/default-ts': { - name: 'Qwik CLI Latest (Vite | TypeScript)', - script: 'npm create qwik playground {{beforeDir}}', - // TODO: The community template does not provide standard stories, which is required for e2e tests. Reenable once it does. - inDevelopment: true, - expected: { - framework: 'storybook-framework-qwik', - renderer: 'storybook-framework-qwik', - builder: 'storybook-framework-qwik', - }, - // TODO: The community template does not provide standard stories, which is required for e2e tests. - skipTasks: ['e2e-tests-dev', 'e2e-tests', 'bench', 'vitest-integration'], - }, + /** This is currently broken, we generate components and stories that do not work */ + // 'qwik-vite/default-ts': { + // name: 'Qwik CLI Latest (Vite | TypeScript)', + // script: 'npm create qwik playground {{beforeDir}}', + // // TODO: The community template does not provide standard stories, which is required for e2e tests. Reenable once it does. + // inDevelopment: true, + // expected: { + // framework: 'storybook-framework-qwik', + // renderer: 'storybook-framework-qwik', + // builder: 'storybook-framework-qwik', + // }, + // // TODO: The community template does not provide standard stories, which is required for e2e tests. + // skipTasks: ['e2e-tests-dev', 'e2e-tests', 'bench', 'vitest-integration', 'chromatic'], + // }, 'ember/3-js': { name: 'Ember v3 (Webpack | JavaScript)', script: 'npx --package ember-cli@3.28.1 ember new {{beforeDir}}', @@ -1041,7 +1049,7 @@ export const daily: TemplateKey[] = [ 'lit-vite/default-js', 'svelte-vite/default-js', 'nextjs/prerelease', - 'qwik-vite/default-ts', + // 'qwik-vite/default-ts', 'preact-vite/default-js', 'html-vite/default-js', 'internal/react16-webpack', diff --git a/code/lib/create-storybook/src/initiate.test.ts b/code/lib/create-storybook/src/initiate.test.ts index 17c50007e59c..7d9b1799ef4e 100644 --- a/code/lib/create-storybook/src/initiate.test.ts +++ b/code/lib/create-storybook/src/initiate.test.ts @@ -18,6 +18,10 @@ const getCliIntegrationFromAncestry = vi.mock('storybook/internal/telemetry'); +vi.mock('storybook/internal/core-server', () => ({ + getServerPort: vi.fn().mockResolvedValue(6006), +})); + describe('getStorybookVersionFromAncestry', () => { it('possible storybook path', () => { const ancestry = [{ command: 'node' }, { command: 'storybook@7.0.0' }, { command: 'npm' }]; diff --git a/code/lib/create-storybook/src/initiate.ts b/code/lib/create-storybook/src/initiate.ts index 3377dc488495..c94160f0d65d 100644 --- a/code/lib/create-storybook/src/initiate.ts +++ b/code/lib/create-storybook/src/initiate.ts @@ -176,41 +176,45 @@ async function runStorybookDev(result: { try { const supportsOnboarding = FeatureCompatibilityService.supportsOnboarding(projectType); - const flags = []; + const parts = storybookCommand.split(' '); if (packageManager.type === 'npm') { - flags.push('--silent'); + parts.push('--silent'); } - // npm needs extra -- to pass flags to the command - // in the case of Angular, we are calling `ng run` which doesn't need the extra `--` - const doesNeedExtraDash = - packageManager.type === PackageManagerName.NPM || - packageManager.type === PackageManagerName.BUN; + const supportSbFlags = projectType !== ProjectType.ANGULAR; - if (doesNeedExtraDash && projectType !== ProjectType.ANGULAR) { - flags.push('--'); - } + if (supportSbFlags) { + // npm needs extra -- to pass flags to the command + // in the case of Angular, we are calling `ng run` which doesn't need the extra `--` + const doesNeedExtraDash = + packageManager.type === PackageManagerName.NPM || + packageManager.type === PackageManagerName.BUN; - if (supportsOnboarding && shouldOnboard) { - flags.push('--initial-path=/onboarding'); - } + if (doesNeedExtraDash) { + parts.push('--'); + } - // Check if default port 6006 is available - const defaultPort = 6006; - const availablePort = await getServerPort(defaultPort); - const useAlternativePort = availablePort !== defaultPort; + const defaultPort = 6006; + const availablePort = await getServerPort(defaultPort); + const useAlternativePort = availablePort !== defaultPort; - if (useAlternativePort) { - flags.push(`--port=${availablePort}`); - } + if (useAlternativePort) { + parts.push(`-p`, `${availablePort}`); - flags.push('--quiet'); + if (supportsOnboarding && shouldOnboard) { + parts.push('--initial-path=/onboarding'); + } + + parts.push('--quiet'); + } + } // instead of calling 'dev' automatically, we spawn a subprocess so that it gets // executed directly in the user's project directory. This avoid potential issues // with packages running in npxs' node_modules - const [command, ...args] = [...storybookCommand.split(' '), ...flags]; + const [command, ...args] = [...parts]; + await executeCommand({ command: command, args, diff --git a/code/package.json b/code/package.json index 0ad1cad39cd2..a0f1e37203c0 100644 --- a/code/package.json +++ b/code/package.json @@ -220,5 +220,6 @@ "Dependency Upgrades" ] ] - } + }, + "deferredNextVersion": "10.2.0-beta.0" } diff --git a/docs/versions/next.json b/docs/versions/next.json index 3463827282f5..ee1e4f0d7b2a 100644 --- a/docs/versions/next.json +++ b/docs/versions/next.json @@ -1 +1 @@ -{"version":"10.2.0-alpha.18","info":{"plain":"- Build: Fix `ejslint` execution path in lint-staged - [#33504](https://github.com/storybookjs/storybook/pull/33504), thanks @Yeonny0723!\n- CLI: Detect free port when running dev during initiate - [#33532](https://github.com/storybookjs/storybook/pull/33532), thanks @ndelangen!\n- Core: Improve path handling in arg types data extraction - [#33536](https://github.com/storybookjs/storybook/pull/33536), thanks @yannbf!\n- Core: Refactor channel initialization - [#33520](https://github.com/storybookjs/storybook/pull/33520), thanks @yannbf!\n- Telemetry: Add `packageJson.type` - [#33525](https://github.com/storybookjs/storybook/pull/33525), thanks @ndelangen!\n- UI: Improve landmark navigation - [#33457](https://github.com/storybookjs/storybook/pull/33457), thanks @Sidnioulz!"}} \ No newline at end of file +{"version":"10.2.0-beta.0","info":{"plain":"- CLIInitiate: Simplify the CLI init port flag detection - [#33546](https://github.com/storybookjs/storybook/pull/33546), thanks @ndelangen!\n- Initiate: Fix used port flag handling - [#33544](https://github.com/storybookjs/storybook/pull/33544), thanks @ndelangen!\n- Manager: Fix system query parameters being overridable - [#33535](https://github.com/storybookjs/storybook/pull/33535), thanks @JReinhold!\n- NextJSVite: Upgrade plugin - [#33538](https://github.com/storybookjs/storybook/pull/33538), thanks @ndelangen!"}} \ No newline at end of file diff --git a/package.json b/package.json index c3c5f85b7bf4..f4873d84f2e8 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "ci-tests": "cd code; yarn ci-tests", "get-report-message": "cd scripts; yarn get-report-message", "get-sandbox-dir": "cd scripts; yarn get-sandbox-dir", - "get-template": "cd scripts; yarn get-template", "i": "yarn", "knip": "cd code; yarn knip", "lint": "cd code; yarn lint", diff --git a/scripts/build/utils/generate-types.ts b/scripts/build/utils/generate-types.ts index 937afb46c1e5..28cfd0004852 100644 --- a/scripts/build/utils/generate-types.ts +++ b/scripts/build/utils/generate-types.ts @@ -83,7 +83,7 @@ export async function generateTypesFiles(cwd: string, data: BuildEntries) { processes.forEach((p) => p.kill()); processes = []; process.exit(dtsProcess.exitCode || 1); - } else { + } else if(!process.env.CI) { console.log('✅ Generated types for', picocolors.cyan(join(DIR_REL, entryPoint))); } }); diff --git a/scripts/check/check-package.ts b/scripts/check/check-package.ts index a433078a644e..1fa0d89e1923 100755 --- a/scripts/check/check-package.ts +++ b/scripts/check/check-package.ts @@ -26,8 +26,8 @@ const tsDiagnostics = getTSDiagnostics(program, normalizedCwd, host); if (tsDiagnostics.length > 0) { console.log(tsDiagnostics); process.exit(1); -} else { - console.log('no type errors'); +} else if (!process.env.CI) { + console.log('✅ No type errors'); } // TODO, add more package checks here, like: diff --git a/scripts/ci/common-jobs.ts b/scripts/ci/common-jobs.ts new file mode 100644 index 000000000000..47c3ff2471a6 --- /dev/null +++ b/scripts/ci/common-jobs.ts @@ -0,0 +1,341 @@ +// eslint-disable-next-line depend/ban-dependencies +import glob from 'fast-glob'; +import { join } from 'path/posix'; + +import { WINDOWS_ROOT_DIR, WORKING_DIR } from './utils/constants'; +import { + CACHE_KEYS, + CACHE_PATHS, + artifact, + cache, + git, + node, + npm, + server, + testResults, + verdaccio, + workflow, + workspace, +} from './utils/helpers'; +import { defineJob, defineNoOpJob } from './utils/types'; + +const dirname = import.meta.dirname; + +export const build_linux = defineJob('Build (linux)', (workflowName) => ({ + executor: { + name: 'sb_node_22_classic', + class: 'xlarge', + }, + steps: [ + git.checkout(), + npm.install('.'), + cache.persist(CACHE_PATHS, CACHE_KEYS()[0]), + git.check(), + npm.check(), + { + run: { + name: 'Compile', + working_directory: `code`, + command: 'yarn task --task compile --start-from=auto --no-link --debug', + }, + }, + { + run: { + name: 'Publish to Verdaccio', + working_directory: `code`, + command: 'yarn local-registry --publish', + }, + }, + ...workflow.reportOnFailure(workflowName), + artifact.persist(`code/bench/esbuild-metafiles`, 'bench'), + workspace.persist([ + ...glob + .sync(['*/src', '*/*/src'], { + cwd: join(dirname, '../../code'), + onlyDirectories: true, + }) + .flatMap((p) => [ + `${WORKING_DIR}/code/${p.replace('src', 'dist')}`, + `${WORKING_DIR}/code/${p.replace('src', 'node_modules')}`, + ]), + `${WORKING_DIR}/.verdaccio-cache`, + `${WORKING_DIR}/code/bench`, + ]), + ], +})); + +export const prettyDocs = defineJob('Prettify docs', () => ({ + executor: { + name: 'sb_node_22_classic', + class: 'medium+', + }, + steps: [ + git.checkout(), + npm.installScripts(), + { + run: { + name: 'Prettier', + working_directory: `scripts`, + command: 'yarn docs:prettier:check', + }, + }, + ], +})); + +export const build_windows = defineJob('Build (windows)', () => ({ + executor: { + name: 'win/default', + size: 'xlarge', + shell: 'bash.exe', + }, + steps: [ + git.checkout({ forceHttps: true }), + node.installOnWindows(), + npm.install('.'), + { + run: { + name: 'Compile', + working_directory: `code`, + command: 'yarn task --task compile --start-from=auto --no-link --debug', + }, + }, + verdaccio.start(), + workspace.persist( + [ + ...glob + .sync(['*/src', '*/*/src'], { + cwd: join(dirname, '../../code'), + onlyDirectories: true, + }) + .flatMap((p) => [ + `code/${p.replace('src', 'dist')}`, + `code/${p.replace('src', 'node_modules')}`, + ]), + `.verdaccio-cache`, + `code/bench`, + ], + `${WINDOWS_ROOT_DIR}\\${WORKING_DIR}` + ), + ], +})); + +export const commonJobsNoOpJob = defineNoOpJob('Common Jobs', [build_linux]); + +export const storybookChromatic = defineJob( + 'Local storybook & chromatic', + () => ({ + executor: { + name: 'sb_node_22_classic', + class: 'medium+', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Build internal storybook', + command: 'yarn storybook:ui:build', + working_directory: 'code', + }, + }, + { + run: { + name: 'Run Chromatic', + command: 'yarn storybook:ui:chromatic', + working_directory: 'code', + }, + }, + ], + }), + [commonJobsNoOpJob] +); + +export const check = defineJob( + 'TypeScript validation', + (workflowName) => ({ + executor: { + name: 'sb_node_22_classic', + class: 'xlarge', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'TypeCheck code', + working_directory: `code`, + command: 'yarn task --task check --no-link', + }, + }, + { + run: { + name: 'TypeCheck scripts', + working_directory: `scripts`, + command: 'yarn check', + }, + }, + ...workflow.reportOnFailure(workflowName), + ...workflow.cancelOnFailure(), + ], + }), + [commonJobsNoOpJob] +); + +export const lint = defineJob( + 'EsLint & Prettier validation', + () => ({ + executor: { + name: 'sb_node_22_classic', + class: 'xlarge', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Lint code', + working_directory: `code`, + command: 'yarn lint', + }, + }, + { + run: { + name: 'Lint scripts', + working_directory: `scripts`, + command: 'yarn lint', + }, + }, + ], + }), + [commonJobsNoOpJob] +); + +export const knip = defineJob( + 'Knip validation', + () => ({ + executor: { + name: 'sb_node_22_classic', + class: 'medium', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Run Knip', + working_directory: `code`, + command: 'yarn knip --no-exit-code', + }, + }, + ], + }), + [commonJobsNoOpJob] +); + +export const testsUnit_linux = defineJob( + 'Tests (linux)', + (workflowName) => ({ + executor: { + name: 'sb_node_22_classic', + class: 'large', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Run tests', + working_directory: `code`, + command: [ + 'TEST_FILES=$(circleci tests glob "**/*.{test,spec}.{ts,tsx,js,jsx,cjs}" | sed "/^e2e-tests\\//d" | sed "/^node_modules\\//d")', + 'echo "$TEST_FILES" | circleci tests run --command="xargs yarn test --reporter=junit --reporter=default --outputFile=./test-results/junit.xml" --verbose', + ].join('\n'), + }, + }, + testResults.persist(`code/test-results`), + + git.check(), + ...workflow.reportOnFailure(workflowName), + ...workflow.cancelOnFailure(), + ], + }), + [commonJobsNoOpJob] +); + +export const testsStories_linux = defineJob( + 'Tests stories (linux)', + (workflowName) => ({ + executor: { + name: 'sb_playwright', + class: 'xlarge', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Run stories tests', + working_directory: `code`, + command: [ + 'TEST_FILES=$(circleci tests glob "**/*.{stories}.{ts,tsx,js,jsx,cjs}" | sed "/^e2e-tests\\//d" | sed "/^node_modules\\//d")', + 'echo "$TEST_FILES" | circleci tests run --command="xargs yarn test --reporter=junit --reporter=default --outputFile=./test-results/junit.xml" --verbose', + ].join('\n'), + }, + }, + testResults.persist(`code/test-results`), + + git.check(), + ...workflow.reportOnFailure(workflowName), + ...workflow.cancelOnFailure(), + ], + }), + [commonJobsNoOpJob] +); + +export const testUnit_windows = defineJob( + 'Tests unit (windows)', + () => ({ + executor: { + name: 'win/default', + size: 'large', + shell: 'bash.exe', + }, + steps: [ + ...workflow.restoreWindows(`${WINDOWS_ROOT_DIR}\\${WORKING_DIR}`), + { + run: { + command: 'yarn install', + name: 'Install dependencies', + }, + }, + { + run: { + command: + 'yarn test --reporter=junit --reporter=default --outputFile=./test-results/junit.xml', + name: 'Run unit tests', + working_directory: `code`, + }, + }, + testResults.persist(`code/test-results`), + ], + }), + [build_windows] +); + +export const benchmarkPackages = defineJob( + 'Benchmark packages', + () => ({ + executor: { + name: 'sb_node_22_classic', + class: 'large', + }, + steps: [ + ...workflow.restoreLinux(), + verdaccio.start(), + server.wait([...verdaccio.ports]), + { + run: { + name: 'Benchmarking packages against base branch', + command: + 'yarn bench-packages --base-branch << pipeline.parameters.ghBaseBranch >> --pull-request << pipeline.parameters.ghPrNumber >> --upload', + working_directory: 'scripts', + }, + }, + ], + }), + [commonJobsNoOpJob] +); diff --git a/scripts/ci/init-empty.ts b/scripts/ci/init-empty.ts new file mode 100644 index 000000000000..caa22590a8dc --- /dev/null +++ b/scripts/ci/init-empty.ts @@ -0,0 +1,155 @@ +import { build_linux } from './common-jobs'; +import { WINDOWS_ROOT_DIR } from './utils/constants'; +import { server, verdaccio, workflow } from './utils/helpers'; +import { + type JobOrNoOpJob, + type Workflow, + defineJob, + defineNoOpJob, + isWorkflowOrAbove, +} from './utils/types'; + +export const defineEmptyInitFlow = (template: string) => + defineJob( + `init-empty-${template}`, + () => ({ + executor: { + name: 'sb_node_22_classic', + class: 'medium', + }, + steps: [ + ...workflow.restoreLinux(), + verdaccio.start(), + server.wait([...verdaccio.ports]), + { + run: { + name: 'Storybook init from empty directory (Linux NPM)', + working_directory: '/tmp', + command: [ + `mkdir empty-${template}`, + `cd empty-${template}`, + `npm set registry http://localhost:6001`, + `npx storybook init --yes --package-manager npm`, + ].join('\n'), + environment: { + IN_STORYBOOK_SANDBOX: true, + STORYBOOK_DISABLE_TELEMETRY: true, + STORYBOOK_INIT_EMPTY_TYPE: template, + }, + }, + }, + { + run: { + name: 'Run storybook smoke test', + working_directory: `/tmp/empty-${template}`, + command: 'npm run storybook -- --smoke-test', + }, + }, + ], + }), + + [initEmptyNoOpJob] + ); + +export function defineEmptyInitFeatures() { + return defineJob( + 'init-features', + () => ({ + executor: { + name: 'sb_node_22_classic', + class: 'medium', + }, + steps: [ + ...workflow.restoreLinux(), + verdaccio.start(), + server.wait([...verdaccio.ports]), + { + run: { + name: 'Storybook init from empty directory (Linux NPM)', + working_directory: '/tmp', + command: [ + `mkdir empty-react-vite-ts`, + `cd empty-react-vite-ts`, + `npm set registry http://localhost:6001`, + `npx create-storybook --yes --package-manager npm --features docs test a11y --loglevel=debug`, + ].join('\n'), + environment: { + IN_STORYBOOK_SANDBOX: true, + STORYBOOK_DISABLE_TELEMETRY: true, + STORYBOOK_INIT_EMPTY_TYPE: 'react-vite-ts', + }, + }, + }, + { + run: { + name: 'Run storybook smoke test', + working_directory: `/tmp/empty-react-vite-ts`, + command: 'npx vitest', + }, + }, + ], + }), + [initEmptyNoOpJob] + ); +} + +export function defineEmptyInitWindows() { + return defineJob( + 'init-empty-windows', + () => ({ + executor: { + name: 'win/default', + size: 'medium', + shell: 'bash.exe', + }, + steps: [ + ...workflow.restoreWindows(), + verdaccio.start(), + server.wait([...verdaccio.ports]), + { + run: { + name: 'Storybook init from empty directory (Windows NPM)', + working_directory: WINDOWS_ROOT_DIR, + command: [ + `mkdir empty-react-vite-ts`, + `cd empty-react-vite-ts`, + `npm set registry http://localhost:6001`, + `npx storybook init --yes --package-manager npm`, + ].join('\n'), + environment: { + IN_STORYBOOK_SANDBOX: true, + STORYBOOK_DISABLE_TELEMETRY: true, + STORYBOOK_INIT_EMPTY_TYPE: 'react-vite-ts', + }, + }, + }, + { + run: { + name: 'Run storybook smoke test', + working_directory: `${WINDOWS_ROOT_DIR}\\empty-react-vite-ts`, + command: 'npm run storybook -- --smoke-test', + }, + }, + ], + }), + [initEmptyNoOpJob] + ); +} + +export const initEmptyNoOpJob = defineNoOpJob('init-empty', [build_linux]); + +export function getInitEmpty(workflow: Workflow) { + const initEmpty: JobOrNoOpJob[] = ['react-vite-ts'].map(defineEmptyInitFlow); + + if (isWorkflowOrAbove(workflow, 'merged')) { + initEmpty.push(...['nextjs-ts', 'vue-vite-ts', 'lit-vite-ts'].map(defineEmptyInitFlow)); + } + if (isWorkflowOrAbove(workflow, 'daily')) { + initEmpty.push(defineEmptyInitWindows()); + } + if (isWorkflowOrAbove(workflow, 'normal')) { + initEmpty.push(defineEmptyInitFeatures()); + } + + return initEmpty; +} diff --git a/scripts/ci/main.ts b/scripts/ci/main.ts new file mode 100644 index 000000000000..ee479158b032 --- /dev/null +++ b/scripts/ci/main.ts @@ -0,0 +1,155 @@ +import fs from 'node:fs/promises'; +import { join } from 'node:path'; + +import { program } from 'commander'; +import yml from 'yaml'; + +import { + benchmarkPackages, + build_linux, + build_windows, + check, + commonJobsNoOpJob, + knip, + lint, + prettyDocs, + storybookChromatic, + testUnit_windows, + testsStories_linux, + testsUnit_linux, +} from './common-jobs'; +import { getInitEmpty, initEmptyNoOpJob } from './init-empty'; +import { getSandboxes, sandboxesNoOpJob } from './sandboxes'; +import { getTestStorybooks, testStorybooksNoOpJob } from './test-storybooks'; +import { executors } from './utils/executors'; +import { ensureRequiredJobs } from './utils/helpers'; +import { orbs } from './utils/orbs'; +import { parameters } from './utils/parameters'; +import type { JobImplementationObj, JobOrNoOpJob, NoOpJobImplementationObj } from './utils/types'; +import { type Workflow, isWorkflowOrAbove } from './utils/types'; + +const dirname = import.meta.dirname; + +/** + * Generate the CircleCI config for a given workflow. + * + * @param workflow - The workflow to generate the config for. + * @returns The generated config for CircleCI in JS format. + */ +function generateConfig(workflow: Workflow) { + const jobs: JobOrNoOpJob[] = []; + if (isWorkflowOrAbove(workflow, 'docs')) { + jobs.push(prettyDocs); + } else { + const sandboxes = getSandboxes(workflow); + const testStorybooks = getTestStorybooks(workflow); + const initEmpty = getInitEmpty(workflow); + + if (isWorkflowOrAbove(workflow, 'daily')) { + jobs.push(build_windows, testUnit_windows); + } + + jobs.push( + build_linux, + testsUnit_linux, + testsStories_linux, + + commonJobsNoOpJob, + lint, + check, + knip, + + storybookChromatic, + benchmarkPackages, + + sandboxesNoOpJob, + ...sandboxes, + + testStorybooksNoOpJob, + ...testStorybooks, + + initEmptyNoOpJob, + ...initEmpty + ); + } + + /** + * If you want to filter down to a particular job, e.g.for debugging purposes.. you can do that + * here. + * + * You can filter on the `job.id` for example. + * + * Though is also possible to comment-out certain sandboxes in`sandbox-templates.ts`, or comment + * out `todos.push`-statements above. + * + * You do not need to consider the `requires` field, as the `ensureRequiredJobs` function will + * handle that for you. + * + * @example + * + * ```ts + * const filteredTodos = todos.filter((job) => !!job.id.includes('qwik')); + * ``` + */ + const filteredJobs = jobs.filter((job) => !!job); + + const isDebugging = filteredJobs.length !== jobs.length; + + const ensuredJobs = ensureRequiredJobs(filteredJobs); + + const sortedJobs = ensuredJobs.sort((a, b) => { + if (a.requires.length && b.requires.length) { + return a.requires.length - b.requires.length; + } + if (a.requires.length) { + return 1; + } + if (b.requires.length) { + return -1; + } + return a.id.localeCompare(b.id); + }); + + return { + version: 2.1, + orbs, + executors, + parameters, + + jobs: sortedJobs.reduce( + (acc, job) => { + acc[job.id] = + typeof job.implementation === 'function' + ? job.implementation(workflow) + : job.implementation; + return acc; + }, + {} as Record + ), + workflows: { + [`${workflow}-generated${isDebugging ? '-debug' : ''}`]: { + jobs: sortedJobs.map((t) => + t.requires && t.requires.length > 0 + ? { [t.id]: { requires: t.requires.map((r) => r.id) } } + : t.id + ), + }, + }, + }; +} + +console.log('Generating CircleCI config...'); +console.log('--------------------------------'); + +program + .description('Generate CircleCI config') + .requiredOption('-w, --workflow ', 'Workflow to generate config for') + .parse(process.argv); + +await fs.writeFile( + join(dirname, '../../.circleci/config.generated.yml'), + yml.stringify(generateConfig(program.opts().workflow), null, { + lineWidth: 1200, + indent: 4, + }) +); diff --git a/scripts/ci/sandboxes.ts b/scripts/ci/sandboxes.ts new file mode 100644 index 000000000000..81c622d83506 --- /dev/null +++ b/scripts/ci/sandboxes.ts @@ -0,0 +1,499 @@ +import { join } from 'path'; + +import * as sandboxTemplates from '../../code/lib/cli-storybook/src/sandbox-templates'; +import { build_linux } from './common-jobs'; +import { LINUX_ROOT_DIR, SANDBOX_DIR, WINDOWS_ROOT_DIR, WORKING_DIR } from './utils/constants'; +import { + CACHE_KEYS, + artifact, + cache, + server, + testResults, + toId, + verdaccio, + workflow, + workspace, +} from './utils/helpers'; +import { defineJob, defineNoOpJob, isWorkflowOrAbove } from './utils/types'; +import type { JobOrNoOpJob, Workflow } from './utils/types'; + +function defineSandboxJob_build({ + directory, + name, + template, + requires, +}: { + directory: string; + name: string; + requires: JobOrNoOpJob[]; + template: string; +}) { + return defineJob( + name, + () => ({ + executor: { + name: 'sb_node_22_classic', + class: 'large', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Build storybook', + command: `yarn task build --template ${template} --no-link -s build`, + }, + }, + workspace.persist([`${SANDBOX_DIR}/${directory}/storybook-static`]), + ], + }), + requires + ); +} +function defineSandboxJob_dev({ + name, + template, + requires, + options, +}: { + name: string; + requires: JobOrNoOpJob[]; + template: string; + options: { + e2e: boolean; + }; +}) { + return defineJob( + name, + () => ({ + executor: options.e2e + ? { + name: 'sb_playwright', + class: 'xlarge', + } + : { + name: 'sb_node_22_classic', + class: 'large', + }, + steps: [ + ...workflow.restoreLinux(), + ...(options.e2e + ? [ + { + run: { + name: 'Run storybook', + working_directory: 'code', + background: true, + command: `yarn task dev --template ${template} --no-link -s dev`, + }, + }, + server.wait(['6006']), + { + run: { + name: 'Running E2E Tests', + command: [ + 'TEST_FILES=$(circleci tests glob "code/e2e-tests/*.{test,spec}.{ts,js,mjs}")', + `echo "$TEST_FILES" | circleci tests run --command="xargs yarn task e2e-tests-dev --template ${template} --no-link -s e2e-tests-dev --junit" --verbose --index=0 --total=1`, + ].join('\n'), + }, + }, + artifact.persist(join(LINUX_ROOT_DIR, WORKING_DIR, 'test-results'), 'test-results'), + testResults.persist(join(LINUX_ROOT_DIR, WORKING_DIR, 'test-results')), + ] + : [ + { + run: { + name: 'Run storybook smoke test', + working_directory: 'code', + background: true, + command: `yarn task smoke-test --template ${template} --no-link -s dev`, + }, + }, + ]), + ], + }), + requires + ); +} + +export function defineSandboxFlow(key: Key) { + const id = toId(key); + const data = sandboxTemplates.allTemplates[key as keyof typeof sandboxTemplates.allTemplates]; + const { skipTasks = [], name } = data; + + const path = key.replace('/', '-'); + + const createJob = defineJob( + `${name} (create)`, + () => ({ + executor: { + name: 'sb_node_22_browsers', + class: 'large', + }, + steps: [ + ...workflow.restoreLinux(), + verdaccio.start(), + { + run: { + name: 'Start Event Collector', + working_directory: `scripts`, + background: true, + command: 'yarn jiti ./event-log-collector.ts', + }, + }, + server.wait([...verdaccio.ports, '6007']), + { + run: { + name: 'Setup Corepack', + command: [ + // + 'sudo corepack enable', + 'which yarn', + 'yarn --version', + ].join('\n'), + }, + }, + ...('inDevelopment' in data && data.inDevelopment + ? [ + { + run: { + name: 'Generate Sandbox', + command: `yarn task generate --template ${key} --no-link -s generate --debug`, + environment: { + STORYBOOK_SANDBOX_GENERATE: 1, + STORYBOOK_TELEMETRY_DEBUG: 1, + STORYBOOK_TELEMETRY_URL: 'http://127.0.0.1:6007/event-log', + }, + }, + }, + ] + : []), + { + run: { + name: 'Create Sandbox', + command: `yarn task sandbox --template ${key} --no-link -s sandbox --debug`, + environment: { + STORYBOOK_CLI_SKIP_PLAYWRIGHT_INSTALLATION: 1, + STORYBOOK_TELEMETRY_DEBUG: 1, + STORYBOOK_TELEMETRY_URL: 'http://127.0.0.1:6007/event-log', + }, + }, + }, + /** + * Due to the way we create sandboxes, a unique situation arises where a sveltekit + * cache-config-file is missing. This generates it. + */ + ...(id.includes('svelte-kit') + ? [ + { + run: { + name: 'Run prepare', + working_directory: `${LINUX_ROOT_DIR}/${SANDBOX_DIR}/${id}`, + command: `yarn prepare`, + }, + }, + ] + : []), + artifact.persist(`${LINUX_ROOT_DIR}/${SANDBOX_DIR}/${id}/debug-storybook.log`, 'logs'), + workspace.persist([`${SANDBOX_DIR}/${id}`]), + ], + }), + [sandboxesNoOpJob] + ); + const buildJob = defineSandboxJob_build({ + name: `${name} (build)`, + template: key, + directory: id, + requires: [createJob], + }); + const devJob = defineSandboxJob_dev({ + name: `${name} (dev)`, + template: key, + requires: [createJob], + options: { e2e: !skipTasks?.includes('e2e-tests-dev') }, + }); + const chromaticJob = defineJob( + `${name} (chromatic)`, + () => ({ + executor: { + name: 'sb_node_22_classic', + class: 'medium', + }, + steps: [ + 'checkout', // we need the full git history for chromatic + workspace.attach(), + cache.attach(CACHE_KEYS()), + { + // we copy to the working directory to get git history, which chromatic needs for baselines + run: { + name: 'Copy sandbox to working directory', + command: `cp ${join(LINUX_ROOT_DIR, SANDBOX_DIR)} ${join(LINUX_ROOT_DIR, WORKING_DIR, 'sandbox')} -r --remove-destination`, + }, + }, + { + run: { + name: 'Running Chromatic', + command: `yarn task chromatic --template ${key} --no-link -s chromatic`, + environment: { + STORYBOOK_SANDBOX_ROOT: `./sandbox`, + }, + }, + }, + ], + }), + [buildJob] + ); + const vitestJob = defineJob( + `${name} (vitest)`, + () => ({ + executor: { + name: 'sb_playwright', + class: 'medium', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Running Vitest', + command: `yarn task vitest-integration --template ${key} --no-link -s vitest-integration --junit`, + }, + }, + testResults.persist(join(LINUX_ROOT_DIR, WORKING_DIR, 'test-results')), + ], + }), + [buildJob] + ); + const e2eJob = defineJob( + `${name} (e2e)`, + () => ({ + executor: { + name: 'sb_playwright', + class: 'xlarge', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Serve storybook', + background: true, + command: `yarn task serve --template ${key} --no-link -s serve`, + }, + }, + server.wait(['8001']), + { + run: { + name: 'Running E2E Tests', + command: [ + `TEST_FILES=$(circleci tests glob "code/e2e-tests/*.{test,spec}.{ts,js,mjs}")`, + `echo "$TEST_FILES" | circleci tests run --command="xargs yarn task e2e-tests --template ${key} --no-link -s e2e-tests --junit" --verbose --index=0 --total=1`, + ].join('\n'), + }, + }, + artifact.persist(join(LINUX_ROOT_DIR, WORKING_DIR, 'test-results'), 'test-results'), + testResults.persist(join(LINUX_ROOT_DIR, WORKING_DIR, 'test-results')), + ], + }), + [buildJob] + ); + const testRunnerJob = defineJob( + `${name} (test-runner)`, + () => ({ + executor: { + name: 'sb_playwright', + class: 'medium', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Running test-runner', + command: `yarn task test-runner --template ${key} --no-link -s test-runner --junit`, + }, + }, + testResults.persist(join(LINUX_ROOT_DIR, WORKING_DIR, 'test-results')), + ], + }), + [buildJob] + ); + + const jobs = [ + createJob, + buildJob, + devJob, + !skipTasks?.includes('chromatic') ? chromaticJob : undefined, + !skipTasks?.includes('vitest-integration') ? vitestJob : undefined, + !skipTasks?.includes('e2e-tests') ? e2eJob : undefined, + + /** + * Question: What is this for? Do we want to know if the test-runner works? Or do we want to + * know if the sandbox works? + * + * If it's the first, we actually only need to run the test-runner job once, on any sandbox. If + * it's the second, we need to run the test-runner job for each sandbox, but then we don't need + * to run it when we're already running the chromatic job. + */ + !skipTasks?.includes('test-runner') && skipTasks.includes('chromatic') + ? testRunnerJob + : undefined, + ].filter(Boolean); + return { + name: key, + path, + jobs, + }; +} + +export function defineSandboxTestRunner(sandbox: ReturnType) { + return defineJob( + `${sandbox.jobs[1].id}-test-runner`, + () => ({ + executor: { + name: 'sb_playwright', + class: 'medium', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Running test-runner', + command: `yarn task test-runner --template ${sandbox.name} --no-link -s test-runner --junit`, + }, + }, + testResults.persist(join(LINUX_ROOT_DIR, WORKING_DIR, 'test-results')), + ], + }), + [sandbox.jobs[1]] + ); +} + +export function defineWindowsSandboxDev(sandbox: ReturnType) { + return defineJob( + `${sandbox.jobs[2].id}-windows`, + () => ({ + executor: { + name: 'win/default', + size: 'xlarge', + shell: 'bash.exe', + }, + steps: [ + ...workflow.restoreWindows(), + verdaccio.start(), + server.wait([...verdaccio.ports]), + { + run: { + name: 'Run Install', + working_directory: `${WINDOWS_ROOT_DIR}\\${SANDBOX_DIR}\\${sandbox.path}`, + command: 'yarn install', + }, + }, + { + run: { + name: 'Install playwright', + command: 'yarn playwright install chromium --with-deps', + }, + }, + { + run: { + name: 'Run storybook', + background: true, + working_directory: `${WINDOWS_ROOT_DIR}\\${SANDBOX_DIR}\\${sandbox.path}`, + command: 'yarn storybook --port 8001', + }, + }, + server.wait(['8001']), + { + run: { + name: 'Running E2E Tests', + working_directory: 'code', + command: `yarn task e2e-tests-dev --template ${sandbox.name} --no-link -s e2e-tests-dev --junit`, + }, + }, + testResults.persist(`${WINDOWS_ROOT_DIR}\\${WORKING_DIR}\\test-results`), + ], + }), + [sandbox.jobs[0]] + ); +} + +export function defineWindowsSandboxBuild(sandbox: ReturnType) { + return defineJob( + `${sandbox.jobs[1].id}-windows`, + () => ({ + executor: { + name: 'win/default', + size: 'xlarge', + shell: 'bash.exe', + }, + steps: [ + ...workflow.restoreWindows(), + verdaccio.start(), + server.wait([...verdaccio.ports]), + { + run: { + name: 'Run Install', + working_directory: `${WINDOWS_ROOT_DIR}\\${SANDBOX_DIR}\\${sandbox.path}`, + command: 'yarn install', + }, + }, + { + run: { + name: 'Install playwright', + command: 'yarn playwright install chromium --with-deps', + }, + }, + { + run: { + name: 'Build storybook', + command: `yarn task build --template ${sandbox.name} --no-link -s build`, + }, + }, + { + run: { + name: 'Serve storybook', + background: true, + command: `yarn task serve --template ${sandbox.name} --no-link -s serve`, + }, + }, + server.wait(['8001']), + { + run: { + name: 'Running E2E Tests', + working_directory: 'code', + command: `yarn task e2e-tests --template ${sandbox.name} --no-link -s e2e-tests`, + }, + }, + ], + }), + [sandbox.jobs[0]] + ); +} + +export const sandboxesNoOpJob = defineNoOpJob('sandboxes', [build_linux]); + +const getListOfSandboxes = (workflow: Workflow) => { + switch (workflow) { + case 'normal': + return sandboxTemplates.normal; + case 'merged': + return sandboxTemplates.merged; + case 'daily': + return sandboxTemplates.daily; + default: + return []; + } +}; + +export function getSandboxes(workflow: Workflow) { + const sandboxes = getListOfSandboxes(workflow).map(defineSandboxFlow); + + const list: JobOrNoOpJob[] = sandboxes.flatMap((sandbox) => sandbox.jobs); + + if (isWorkflowOrAbove(workflow, 'daily')) { + const windows_sandbox_build = defineWindowsSandboxBuild(sandboxes[0]); + const windows_sandbox_dev = defineWindowsSandboxDev(sandboxes[0]); + const testRunner = defineSandboxTestRunner(sandboxes[0]); + + list.push(windows_sandbox_build, windows_sandbox_dev, testRunner); + } + + return list; +} diff --git a/scripts/ci/test-storybooks.ts b/scripts/ci/test-storybooks.ts new file mode 100644 index 000000000000..548ecbd85562 --- /dev/null +++ b/scripts/ci/test-storybooks.ts @@ -0,0 +1,166 @@ +import { readFileSync } from 'fs'; +import { join } from 'path/posix'; + +import { build_linux } from './common-jobs'; +import { artifact, workflow } from './utils/helpers'; +import { + type JobOrNoOpJob, + type Workflow, + defineJob, + defineNoOpJob, + isWorkflowOrAbove, +} from './utils/types'; + +export function definePortableStoryTest(directory: string) { + const working_directory = `test-storybooks/portable-stories-kitchen-sink/${directory}`; + + const { scripts } = JSON.parse( + readFileSync(join(import.meta.dirname, '..', '..', working_directory, 'package.json'), 'utf8') + ); + + return defineJob( + `test-storybooks-portable-${directory}`, + () => ({ + executor: { + name: 'sb_playwright', + class: 'medium', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Install dependencies', + working_directory, + command: 'yarn install --no-immutable', + environment: { + YARN_ENABLE_IMMUTABLE_INSTALLS: false, + }, + }, + }, + { + run: { + name: 'Run Jest tests', + working_directory, + command: 'yarn jest', + }, + }, + { + run: { + name: 'Run Vitest tests', + working_directory, + command: 'yarn vitest', + }, + }, + { + run: { + name: 'Run Playwright CT tests', + working_directory, + command: 'yarn playwright-ct', + }, + }, + ...(scripts['playwright-e2e'] + ? [ + { + run: { + name: 'Run Playwright E2E tests', + working_directory, + command: 'yarn playwright-e2e', + }, + }, + artifact.persist(join(working_directory, 'test-results'), 'playwright'), + ] + : []), + { + run: { + name: 'Run Cypress CT tests', + working_directory, + command: 'yarn cypress', + }, + }, + ], + }), + [testStorybooksNoOpJob] + ); +} + +export function definePortableStoryTestPNP() { + return defineJob( + 'test-storybooks-pnp', + () => ({ + executor: { + name: 'sb_node_22_classic', + class: 'medium', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Install dependencies', + working_directory: 'test-storybooks/yarn-pnp', + command: 'yarn install --no-immutable', + environment: { + YARN_ENABLE_IMMUTABLE_INSTALLS: false, + }, + }, + }, + { + run: { + name: 'Run Storybook smoke test', + working_directory: 'test-storybooks/yarn-pnp', + command: 'yarn storybook --smoke-test', + }, + }, + ], + }), + [testStorybooksNoOpJob] + ); +} + +export function definePortableStoryTestVitest3() { + return defineJob( + 'test-storybooks-portable-vitest3', + () => ({ + executor: { + name: 'sb_playwright', + class: 'medium', + }, + steps: [ + ...workflow.restoreLinux(), + { + run: { + name: 'Install dependencies', + working_directory: 'test-storybooks/portable-stories-kitchen-sink/react-vitest-3', + command: 'yarn install --no-immutable', + environment: { + YARN_ENABLE_IMMUTABLE_INSTALLS: false, + }, + }, + }, + { + run: { + name: 'Run Playwright E2E tests', + working_directory: 'test-storybooks/portable-stories-kitchen-sink/react-vitest-3', + command: 'yarn playwright-e2e', + }, + }, + ], + }), + [testStorybooksNoOpJob] + ); +} + +export const testStorybooksNoOpJob = defineNoOpJob('test-storybooks', [build_linux]); + +export function getTestStorybooks(workflow: Workflow) { + const testStorybooks: JobOrNoOpJob[] = ['react', 'vue3'].map(definePortableStoryTest); + + if (isWorkflowOrAbove(workflow, 'daily')) { + testStorybooks.push(definePortableStoryTestPNP()); + } + + if (isWorkflowOrAbove(workflow, 'merged')) { + testStorybooks.push(definePortableStoryTestVitest3()); + } + + return testStorybooks; +} diff --git a/scripts/ci/utils/constants.ts b/scripts/ci/utils/constants.ts new file mode 100644 index 000000000000..19c5af9d52ab --- /dev/null +++ b/scripts/ci/utils/constants.ts @@ -0,0 +1,19 @@ +/** + * This is the root directory for Linux CI jobs. + * + * Windows CI jobs use a different root directory! + * + * @example /tmp/project/code + * + * @example C:\Users\circleci\project\code + * + * @example /tmp/storybook-sandboxes + * + * @example C:\Users\circleci\storybook-sandboxes + * + * To ensure the correct paths are used across config generation, we use the following constants. + */ +export const LINUX_ROOT_DIR = '/tmp'; +export const WINDOWS_ROOT_DIR = 'C:\\Users\\circleci'; +export const WORKING_DIR = `project`; +export const SANDBOX_DIR = `storybook-sandboxes`; diff --git a/scripts/ci/utils/executors.ts b/scripts/ci/utils/executors.ts new file mode 100644 index 000000000000..a342eac36c30 --- /dev/null +++ b/scripts/ci/utils/executors.ts @@ -0,0 +1,64 @@ +import { LINUX_ROOT_DIR, WORKING_DIR } from './constants'; + +export const executors = { + sb_node_22_browsers: { + docker: [ + { + environment: { + NODE_OPTIONS: '--max_old_space_size=6144', + }, + image: 'cimg/node:22.15.0-browsers', + }, + ], + parameters: { + class: { + default: 'small', + description: 'The Resource class', + enum: ['small', 'medium', 'medium+', 'large', 'xlarge'], + type: 'enum', + }, + }, + resource_class: '<>', + working_directory: `${LINUX_ROOT_DIR}/${WORKING_DIR}`, + }, + sb_node_22_classic: { + docker: [ + { + environment: { + NODE_OPTIONS: '--max_old_space_size=6144', + }, + image: 'cimg/node:22.15.0', + }, + ], + parameters: { + class: { + default: 'small', + description: 'The Resource class', + enum: ['small', 'medium', 'medium+', 'large', 'xlarge'], + type: 'enum', + }, + }, + resource_class: '<>', + working_directory: `${LINUX_ROOT_DIR}/${WORKING_DIR}`, + }, + sb_playwright: { + docker: [ + { + environment: { + NODE_OPTIONS: '--max_old_space_size=6144', + }, + image: 'mcr.microsoft.com/playwright:v1.52.0-noble', + }, + ], + parameters: { + class: { + default: 'small', + description: 'The Resource class', + enum: ['small', 'medium', 'medium+', 'large', 'xlarge'], + type: 'enum', + }, + }, + resource_class: '<>', + working_directory: `${LINUX_ROOT_DIR}/${WORKING_DIR}`, + }, +} as const; diff --git a/scripts/ci/utils/helpers.ts b/scripts/ci/utils/helpers.ts new file mode 100644 index 000000000000..bfac9289b28e --- /dev/null +++ b/scripts/ci/utils/helpers.ts @@ -0,0 +1,281 @@ +import { LINUX_ROOT_DIR, WINDOWS_ROOT_DIR } from './constants'; +import { type JobOrNoOpJob, type Workflow } from './types'; + +export const workspace = { + attach: (at = LINUX_ROOT_DIR) => { + return { + attach_workspace: { + at, + }, + }; + }, + persist: (paths: string[], root = LINUX_ROOT_DIR) => { + return { + persist_to_workspace: { + paths, + root, + }, + }; + }, +}; + +export const cache = { + attach: (keys: string[]) => { + return { + restore_cache: { + keys, + }, + }; + }, + persist: (paths: string[], key: string) => { + return { + save_cache: { + paths, + key, + }, + }; + }, +}; + +export const artifact = { + persist: (path: string, destination: string) => { + return { + store_artifacts: { + path, + destination, + }, + }; + }, +}; + +export const git = { + checkout: ({ forceHttps = false, shallow = true } = {}) => { + const flags = []; + if (shallow) { + flags.push('--depth 1'); + } else { + flags.push('--depth 500'); + } + + if (forceHttps) { + flags.push('--config url."https://github.com/".insteadOf=ssh://git@github.com/'); + flags.push('--config url."https://github.com/".insteadOf=git@github.com:'); + } + return { + 'git-shallow-clone/checkout_advanced': { + clone_options: flags.join(' '), + }, + }; + }, + check: () => { + return { + run: { + name: 'Ensure no changes pending', + command: 'git diff --exit-code', + }, + }; + }, +}; + +export const node = { + installOnWindows: () => { + return { + run: { + name: 'Install Node + Yarn', + shell: 'powershell.exe', + command: [ + '$nodeVersion = Get-Content .nvmrc | Select-Object -First 1', + 'nvm install $nodeVersion', + 'nvm use $nodeVersion', + 'corepack enable', + 'corepack prepare yarn@stable --activate', + ].join('\n'), + }, + }; + }, +}; + +export const npm = { + installScripts: () => { + return { + run: { + name: 'Install scripts', + command: 'yarn workspaces focus @storybook/scripts', + }, + }; + }, + install: (appDir: string, pkgManager: string = 'yarn') => { + return { + 'node/install-packages': { + 'app-dir': appDir, + 'pkg-manager': pkgManager, + 'cache-only-lockfile': true, + }, + }; + }, + check: () => { + return { + run: { + name: 'Check for dedupe', + command: 'yarn dedupe --check', + }, + }; + }, +}; + +export function toId(name: string) { + // replace all non-alphanumeric characters with a hyphen + // trim leading and trailing hyphens + return name + .toLowerCase() + .replace(/[^a-z0-9]/g, '-') + .replace(/^-+|-+$/g, ''); +} + +export const server = { + wait: (ports: string[]) => { + return { + run: { + name: 'Wait on servers', + working_directory: `code`, + command: ports.map((port) => `yarn wait-on tcp:127.0.0.1:${port}`).join('\n'), + }, + }; + }, +}; + +export const verdaccio = { + start: () => { + return { + run: { + name: 'Verdaccio', + working_directory: `code`, + background: true, + command: 'yarn local-registry --open', + }, + }; + }, + publish: () => { + return { + run: { + name: 'Publish to Verdaccio', + working_directory: `code`, + command: 'yarn local-registry --publish', + }, + }; + }, + ports: ['6001', '6002'], +}; + +export const workflow = { + restoreLinux: () => [ + // + git.checkout(), + workspace.attach(), + cache.attach(CACHE_KEYS()), + ], + restoreWindows: (at = WINDOWS_ROOT_DIR) => [ + git.checkout({ forceHttps: true }), + node.installOnWindows(), + workspace.attach(at), + /** + * I really wish this wasn't needed, but it is. I tried a lot of things to get it to not be + * needed, but ultimately, something kept failing. At this point I gave up: + * https://app.circleci.com/pipelines/github/storybookjs/storybook/110923/workflows/50076187-a5a7-4955-bff4-30bf9aec465c/jobs/976355 + * + * So if you see a way to debug/solve those failing tests, please do so. + */ + { + run: { + name: 'Install dependencies', + command: 'yarn install', + }, + }, + ], + cancelOnFailure: () => { + return [ + { + run: { + name: 'Cancel current workflow', + when: 'on_fail', + command: + 'curl -X POST --header "Content-Type: application/json" "https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}/cancel?circle-token=${WORKFLOW_CANCELER}"', + }, + }, + ]; + }, + reportOnFailure: (workflow: Workflow, template: string = 'none') => { + return [ + { + run: { + name: 'Un-shallow git', + when: 'on_fail', + command: 'git fetch --unshallow', + }, + }, + { + run: { + name: 'Report failure', + when: 'on_fail', + command: 'echo "Workflow failed"', + }, + }, + { + 'discord/status': { + only_for_branches: ['main', 'next', 'next-release', 'latest-release'].join(','), + fail_only: true, + failure_message: `yarn get-report-message ${workflow} ${template}`, + }, + }, + ]; + }, +}; + +export const CACHE_KEYS = (platform = 'linux') => + [ + `v5-${platform}-node_modules`, + '{{ checksum ".nvmrc" }}', + '{{ checksum ".yarnrc.yml" }}', + '{{ checksum "yarn.lock" }}', + ].map((_, index, list) => { + return list.slice(0, list.length - index).join('/'); + }); + +export const CACHE_PATHS = [ + '.yarn/cache', + '.yarn/unplugged', + '.yarn/build-state.yml', + '.yarn/root-install-state.gz', + 'node_modules', + 'code/node_modules', + 'scripts/node_modules', +]; + +export const testResults = { + persist: (path: string) => { + return { + store_test_results: { + path, + }, + }; + }, +}; + +/** + * We ensure that if (due to filtering for example) any required jobs are not present in the todos + * array, we add them back in. This is recursive, as a required job can have required jobs itself. + */ +export function ensureRequiredJobs(jobs: JobOrNoOpJob[]): JobOrNoOpJob[] { + const results: JobOrNoOpJob[] = []; + while (jobs.length > 0) { + const job = jobs.shift(); + if (job) { + if (results.find((r) => r.id === job.id)) { + continue; + } + results.push(job); + jobs.push(...job.requires); + } + } + return results; +} diff --git a/scripts/ci/utils/orbs.ts b/scripts/ci/utils/orbs.ts new file mode 100644 index 000000000000..9c6306a6ddc7 --- /dev/null +++ b/scripts/ci/utils/orbs.ts @@ -0,0 +1,29 @@ +/** + * This is the list of orbs that are used in the CI config. + * + * These should be regularly updated to the latest version. + * + * @see https://circleci.com/developer/orbs + */ +export const orbs = { + // https://circleci.com/developer/orbs/orb/circleci/browser-tools + 'browser-tools': 'circleci/browser-tools@2.3.2', + + // https://circleci.com/developer/orbs/orb/codecov/codecov + codecov: 'codecov/codecov@5.4.3', + + // https://circleci.com/developer/orbs/orb/antonioned/discord + discord: 'antonioned/discord@0.1.0', + + // https://circleci.com/developer/orbs/orb/guitarrapc/git-shallow-clone + 'git-shallow-clone': 'guitarrapc/git-shallow-clone@2.8.0', + + // https://circleci.com/developer/orbs/orb/circleci/node + node: 'circleci/node@7.2.1', + + // https://circleci.com/developer/orbs/orb/nrwl/nx + nx: 'nrwl/nx@1.7.0', + + // https://circleci.com/developer/orbs/orb/circleci/windows + win: 'circleci/windows@5.1.1', +}; diff --git a/scripts/ci/utils/parameters.ts b/scripts/ci/utils/parameters.ts new file mode 100644 index 000000000000..1518f67a1f49 --- /dev/null +++ b/scripts/ci/utils/parameters.ts @@ -0,0 +1,19 @@ +/** These are parameters that the CircleCI API is called with. */ +export const parameters = { + ghBaseBranch: { + default: 'next', + description: 'The name of the base branch (the target of the PR)', + type: 'string', + }, + ghPrNumber: { + default: '', + description: 'The PR number', + type: 'string', + }, + workflow: { + default: 'skipped', + description: 'Which workflow to run', + enum: ['normal', 'merged', 'daily', 'skipped', 'docs'] as const, + type: 'enum', + }, +}; diff --git a/scripts/ci/utils/types.ts b/scripts/ci/utils/types.ts new file mode 100644 index 000000000000..1cb87a4ae856 --- /dev/null +++ b/scripts/ci/utils/types.ts @@ -0,0 +1,146 @@ +import type { executors } from './executors'; +import { toId } from './helpers'; +import type { parameters } from './parameters'; + +export type Job = { + id: string; + name: K; + implementation: (workflow: Workflow) => JobImplementationObj | NoOpJobImplementationObj; + requires: JobOrNoOpJob[]; +}; + +export type JobOrNoOpJob = Job; + +export type NoOpJobImplementationObj = { + type: 'no-op'; +}; + +export type NoOpJobImplementation = (workflow: Workflow) => NoOpJobImplementationObj; + +export type JobImplementationObj = { + executor: + | { + name: keyof typeof executors; + class: 'small' | 'medium' | 'medium+' | 'large' | 'xlarge'; + } + | { + name: 'win/default'; + size: 'small' | 'medium' | 'medium+' | 'large' | 'xlarge'; + }; + /** + * The steps to run in the job. + * + * @example + * + * ```ts + * { + * run: { + * name: string, + * working_directory: string, + * command: string, + * background: boolean, + * shell: string, + * env: Record, + * }, + * } + * ``` + * + * @see https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview + * @todo Make this more type-strict, maybe with a union type of step objects. See the example + * above. + */ + steps: unknown[]; + + /** I think we generally want to avoid this, since we're generating the jobs dynamically. */ + parameters?: Record; + + /** + * We don't use this today, but it's available for future use. We might want to use it when + * running many many unit tests in parallel. + */ + parallelism?: number; +}; + +export type JobImplementation = (workflow: Workflow) => JobImplementationObj; + +/** + * This function ensures the jobs adhere to the expected interface and that the job's ID is valid. + * (i.e. no special characters, no spaces, etc.) Thus the ID can be referenced by other jobs in the + * `requires` field. + * + * @param name - The name of the job + * @param implementation - The implementation of the job + * @param requires - The jobs that this job depends on + * @returns The job's id, name, implementation, requires + */ +export function defineJob( + name: K, + implementation: I, + requires = [] as JobOrNoOpJob[] +): Job { + return { + id: toId(name), + name: name, + implementation: (workflow) => ({ + description: name, + ...implementation(workflow), + }), + requires, + }; +} + +/** + * A NoOpJob is a special type of job that is used to group other jobs together. It cannot contain + * any steps/implementation. + * + * @param name - The name of the NoOpJob + * @param requires - The jobs that this NoOpJob depends on + */ +export function defineNoOpJob(name: K, requires = [] as JobOrNoOpJob[]): Job { + return { + id: toId(name), + name, + implementation: () => + ({ + type: 'no-op', + }) as const, + requires, + }; +} + +export type Workflow = (typeof parameters.workflow.enum)[number]; + +/** + * Checks if the current workflow is at least the minimum workflow. + * + * `normal` → `merged` → `daily` + * + * `docs` is unique, in that it's not considered below of above anything. + * + * @example + * + * ```ts + * isWorkflowOrAbove('normal', 'normal'); // true + * isWorkflowOrAbove('normal', 'merged'); // false + * isWorkflowOrAbove('normal', 'daily'); // false + * isWorkflowOrAbove('daily', 'normal'); // true + * ``` + * + * @param current - The current workflow + * @param minimum - The minimum workflow + * @returns True if the current workflow is at least the minimum workflow, false otherwise + */ +export function isWorkflowOrAbove(current: Workflow, minimum: Workflow): boolean { + switch (current) { + case 'normal': + return minimum === 'normal'; + case 'merged': + return minimum === 'normal' || minimum === 'merged'; + case 'daily': + return minimum === 'normal' || minimum === 'merged' || minimum === 'daily'; + case 'docs': + return minimum === 'docs'; + default: + return false; + } +} diff --git a/scripts/get-template.ts b/scripts/get-template.ts deleted file mode 100644 index f8dd1aa1cdcf..000000000000 --- a/scripts/get-template.ts +++ /dev/null @@ -1,327 +0,0 @@ -import { readFile, writeFile } from 'node:fs/promises'; - -import { program } from 'commander'; -import picocolors from 'picocolors'; -import { dedent } from 'ts-dedent'; -import yaml from 'yaml'; - -import { - type Cadence, - type SkippableTask, - type Template as TTemplate, - allTemplates, - templatesByCadence, -} from '../code/lib/cli-storybook/src/sandbox-templates'; -import { esMain } from './utils/esmain'; - -type Template = Pick; -export type TemplateKey = keyof typeof allTemplates; -export type Templates = Record; - -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, - { index, total }: { index: number; total: number } -) { - const cadenceTemplates = Object.entries(allTemplates).filter(([key]) => - templatesByCadence[cadence].includes(key as TemplateKey) - ); - - const potentialTemplateKeys = (cadenceTemplates.map(([k]) => k) as TemplateKey[]).filter((t) => { - const currentTemplate = allTemplates[t] as Template; - return isTaskSkipped(currentTemplate, scriptName); - }); - - if (potentialTemplateKeys.length !== total) { - throw new Error(dedent`Circle parallelism set incorrectly. - - Parallelism is set to ${total}, but there are ${ - potentialTemplateKeys.length - } templates to run for the "${scriptName}" task: - ${potentialTemplateKeys.map((v) => `- ${v}`).join('\n')} - - ${await checkParallelism(cadence)} - `); - } - - return potentialTemplateKeys[index]; -} - -const tasksMap = { - sandbox: 'create-sandboxes', - build: 'build-sandboxes', - 'check-sandbox': 'check-sandboxes', - chromatic: 'chromatic-sandboxes', - 'e2e-tests': 'e2e-production', - 'e2e-tests-dev': 'e2e-dev', - 'test-runner': 'test-runner-production', - // 'test-runner-dev', TODO: bring this back when the task is enabled again - bench: 'bench', - 'vitest-integration': 'vitest-integration', -} as const; - -type TaskKey = keyof typeof tasksMap; - -const tasks = Object.keys(tasksMap) as TaskKey[]; - -const CONFIG_YML_FILE = '../.circleci/config.yml'; -const WORKFLOWS_DIR = '../.circleci/src/workflows'; - -async function checkParallelism(cadence?: Cadence, scriptName?: TaskKey, fix: boolean = false) { - const configYml = await readFile(CONFIG_YML_FILE, 'utf-8'); - const data = yaml.parse(configYml); - - let potentialTemplateKeys: TemplateKey[] = []; - const cadences = cadence ? [cadence] : (Object.keys(templatesByCadence) as Cadence[]); - const scripts = scriptName ? [scriptName] : tasks; - const summary = []; - let isIncorrect = false; - const fixes: Array<{ - cadence: string; - job: string; - oldParallelism: number; - newParallelism: number; - }> = []; - - cadences.forEach((cad) => { - summary.push(`\n${picocolors.bold(cad)}`); - const cadenceTemplates = Object.entries(allTemplates).filter(([key]) => - templatesByCadence[cad].includes(key as TemplateKey) - ); - potentialTemplateKeys = cadenceTemplates.map(([k]) => k) as TemplateKey[]; - - scripts.forEach((script) => { - const templateKeysPerScript = potentialTemplateKeys.filter((t) => { - const currentTemplate = allTemplates[t] as Template; - - return isTaskSkipped(currentTemplate, script); - }); - const workflowJobsRaw: (string | { [key: string]: any })[] = data.workflows[cad].jobs; - const workflowJobs = workflowJobsRaw - .filter((item) => typeof item === 'object' && item !== null) - .reduce((result, item) => Object.assign(result, item), {}) as Record; - - if (templateKeysPerScript.length > 0 && workflowJobs[tasksMap[script]]) { - const currentParallelism = workflowJobs[tasksMap[script]].parallelism || 2; - const newParallelism = templateKeysPerScript.length; - - if (newParallelism !== currentParallelism) { - summary.push( - `-- ❌ ${tasksMap[script]} - parallelism: ${currentParallelism} ${picocolors.bgRed( - `(should be ${newParallelism})` - )}` - ); - fixes.push({ - cadence: cad, - job: tasksMap[script], - oldParallelism: currentParallelism, - newParallelism, - }); - isIncorrect = true; - } else { - summary.push( - `-- ✅ ${tasksMap[script]} - parallelism: ${templateKeysPerScript.length}${ - templateKeysPerScript.length === 2 ? ' (default)' : '' - }` - ); - } - } else { - summary.push(`-- ${script} - this script is fully skipped for this cadence.`); - } - }); - }); - - if (isIncorrect) { - if (fix) { - // Apply fixes to individual workflow files - const fixesByFile: Record< - string, - Array<{ job: string; oldParallelism: number; newParallelism: number }> - > = {}; - - // Group fixes by workflow file - fixes.forEach(({ cadence: fixCadence, job, oldParallelism, newParallelism }) => { - const workflowFile = `${fixCadence}.yml`; - if (!fixesByFile[workflowFile]) { - fixesByFile[workflowFile] = []; - } - fixesByFile[workflowFile].push({ job, oldParallelism, newParallelism }); - }); - - // Apply fixes to each workflow file - for (const [workflowFile, fileFixes] of Object.entries(fixesByFile)) { - const workflowPath = `${WORKFLOWS_DIR}/${workflowFile}`; - let workflowContent = await readFile(workflowPath, 'utf-8'); - - // Apply fixes using string manipulation to preserve comments and formatting - fileFixes.forEach(({ job, newParallelism }) => { - // Find the job definition in the YAML content - const jobRegex = new RegExp(`^\\s*-\\s+${job}:\\s*$`, 'm'); - const jobMatch = workflowContent.match(jobRegex); - - if (jobMatch) { - const jobStartIndex = jobMatch.index!; - const jobStartLine = workflowContent.substring(0, jobStartIndex).split('\n').length - 1; - const lines = workflowContent.split('\n'); - - // Find the parallelism line for this job - let parallelismLineIndex = -1; - let indentLevel = 0; - - for (let i = jobStartLine + 1; i < lines.length; i++) { - const line = lines[i]; - const trimmedLine = line.trim(); - - // If we hit another job or top-level key, stop looking - if ( - trimmedLine.startsWith('- ') || - (trimmedLine && !line.startsWith(' ') && !trimmedLine.startsWith('#')) - ) { - break; - } - - // Track indentation level - if (trimmedLine && !trimmedLine.startsWith('#')) { - const currentIndent = line.length - line.trimStart().length; - if (indentLevel === 0) { - indentLevel = currentIndent; - } - } - - // Look for parallelism line - if (trimmedLine.startsWith('parallelism:')) { - parallelismLineIndex = i; - break; - } - } - - if (parallelismLineIndex !== -1) { - // Update existing parallelism line - const indent = lines[parallelismLineIndex].match(/^(\s*)/)?.[1] || ''; - lines[parallelismLineIndex] = `${indent}parallelism: ${newParallelism}`; - } else { - // Add parallelism line after the job name - const indent = lines[jobStartLine].match(/^(\s*)/)?.[1] || ''; - const jobIndent = indent + ' '; - lines.splice(jobStartLine + 1, 0, `${jobIndent}parallelism: ${newParallelism}`); - } - - workflowContent = lines.join('\n'); - } - }); - - // Write the updated workflow file back with preserved comments and formatting - await writeFile(workflowPath, workflowContent, 'utf-8'); - } - - summary.unshift( - `🔧 ${picocolors.green('Fixed')} parallelism counts for ${fixes.length} job${fixes.length === 1 ? '' : 's'} in workflow files:` - ); - summary.push(''); - summary.push('✅ The parallelism of the following jobs was fixed:'); - fixes.forEach(({ job, oldParallelism, newParallelism, cadence }) => { - summary.push(` - ${cadence}/${job}: ${oldParallelism} → ${newParallelism}`); - }); - summary.push(''); - summary.push( - `${picocolors.yellow('⚠️ Important:')} You must regenerate the main config file by running:` - ); - summary.push(''); - summary.push( - `${picocolors.cyan(' circleci config pack .circleci/src > .circleci/config.yml')}` - ); - summary.push(`${picocolors.cyan(' circleci config validate .circleci/config.yml')}`); - summary.push(''); - summary.push( - `${picocolors.gray('See .circleci/README.md for more details about the packing process.')}` - ); - console.log(summary.concat('\n').join('\n')); - } else { - summary.unshift( - 'The parallelism count is incorrect for some jobs in .circleci/config.yml, you have to update them:' - ); - summary.push(''); - summary.push( - `${picocolors.yellow('💡 Tip:')} Use the ${picocolors.cyan('--fix')} flag to automatically fix these issues.` - ); - summary.push(''); - summary.push( - `${picocolors.gray('Note: The fix will update the workflow files in .circleci/src/workflows/ and you will need to regenerate the main config.yml file. See .circleci/README.md for details.')}` - ); - throw new Error(summary.concat('\n').join('\n')); - } - } else { - summary.unshift('✅ The parallelism count is correct for all jobs in .circleci/config.yml:'); - console.log(summary.concat('\n').join('\n')); - } - - const inDevelopmentTemplates = Object.entries(allTemplates) - .filter(([, t]) => t.inDevelopment) - .map(([k]) => k); - - if (inDevelopmentTemplates.length > 0) { - console.log( - `👇 Some templates were skipped as they are flagged to be in development. Please review if they should still contain this flag:\n${inDevelopmentTemplates - .map((k) => `- ${k}`) - .join('\n')}` - ); - } -} - -type RunOptions = { - cadence?: Cadence; - task?: TaskKey; - check: boolean; - fix: boolean; -}; -async function run({ cadence, task, check, fix }: RunOptions) { - if (check || fix) { - if (task && !tasks.includes(task)) { - throw new Error( - dedent`The "${task}" task you provided is not valid. Valid tasks (found in .circleci/config.yml) are: - ${tasks.map((v) => `- ${v}`).join('\n')}` - ); - } - await checkParallelism(cadence as Cadence, task, fix); - return; - } - - if (!cadence) { - throw new Error('Need to supply cadence to get template script'); - } - - const { CIRCLE_NODE_INDEX = 0, CIRCLE_NODE_TOTAL = 1 } = process.env; - console.log( - await getTemplate(cadence as Cadence, task, { - // Convert to integer - index: +CIRCLE_NODE_INDEX, - total: +CIRCLE_NODE_TOTAL, - }) - ); -} - -if (esMain(import.meta.url)) { - program - .description('Retrieve the template to run for a given cadence and task') - .option('--cadence ', 'Which cadence you want to run the script for') - .option('--task ', 'Which task you want to run the script for') - .option('--check', 'Throws an error when the parallelism counts for tasks are incorrect', false) - .option('--fix', 'Automatically fix parallelism counts in workflow files', false); - - program.parse(process.argv); - - const options = program.opts() as RunOptions; - - run(options).catch((err) => { - console.error(err); - process.exit(1); - }); -} diff --git a/scripts/package.json b/scripts/package.json index d8bfcb34fddf..79b3a1f2ca54 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -14,7 +14,6 @@ "generate-sandboxes": "jiti ./sandbox/generate.ts", "get-report-message": "jiti ./get-report-message.ts", "get-sandbox-dir": "jiti ./get-sandbox-dir.ts", - "get-template": "jiti ./get-template.ts", "lint": "yarn lint:js", "lint:js": "yarn lint:js:cmd . --quiet", "lint:js:cmd": "cross-env NODE_ENV=production eslint --cache --cache-location=../.cache/eslint --ext .js,.jsx,.json,.html,.ts,.tsx,.mjs --report-unused-disable-directives", diff --git a/scripts/sandbox/generate.ts b/scripts/sandbox/generate.ts index c64fa191d4fd..75d105ae4260 100755 --- a/scripts/sandbox/generate.ts +++ b/scripts/sandbox/generate.ts @@ -321,7 +321,7 @@ const runGenerators = async ( const hasGenerationErrors = generationResults.some((result) => result.status === 'rejected'); - if (!isCI) { + if (!isCI || process.env.STORYBOOK_SANDBOX_GENERATE) { if (hasGenerationErrors) { console.log('failed:'); console.log( diff --git a/scripts/tasks/dev.ts b/scripts/tasks/dev.ts index f070d147fe51..71c0bc8f6a0c 100644 --- a/scripts/tasks/dev.ts +++ b/scripts/tasks/dev.ts @@ -1,8 +1,8 @@ import detectFreePort from 'detect-port'; import waitOn from 'wait-on'; +import type { AllTemplatesKey } from '../../code/lib/cli-storybook/src/sandbox-templates'; import { now, saveBench } from '../bench/utils'; -import type { TemplateKey } from '../get-template'; import { getPort } from '../sandbox/utils/getPort'; import type { Task } from '../task'; import { exec } from '../utils/exec'; @@ -11,7 +11,7 @@ export const PORT = process.env.STORYBOOK_SERVE_PORT ? parseInt(process.env.STORYBOOK_SERVE_PORT, 10) : 6006; -function getDevPort(key: TemplateKey) { +function getDevPort(key: AllTemplatesKey) { return process.env.NX_CLI_SET === 'true' ? getPort({ selectedTask: 'dev', key }) : PORT; } diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index c54139869ba9..6e26c07b7bac 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -884,7 +884,11 @@ export const extendPreview: Task['run'] = async ({ template, sandboxDir }) => { previewConfig.setFieldValue(['tags'], ['vitest']); } - if (template.modifications?.skipMocking) { + const isCoreRenderer = + template.expected.renderer.startsWith('@storybook/') && + template.expected.renderer !== '@storybook/server'; + + if (template.modifications?.skipMocking || !isCoreRenderer) { await writeConfig(previewConfig); return; } diff --git a/scripts/tasks/sandbox.ts b/scripts/tasks/sandbox.ts index d02d798a8b78..e11c13ec1330 100644 --- a/scripts/tasks/sandbox.ts +++ b/scripts/tasks/sandbox.ts @@ -6,7 +6,7 @@ import dirSize from 'fast-folder-size'; import { now, saveBench } from '../bench/utils'; import type { Task, TaskKey } from '../task'; -import { ROOT_DIRECTORY } from '../utils/constants'; +import { ROOT_DIRECTORY, SANDBOX_DIRECTORY } from '../utils/constants'; const logger = console; @@ -21,8 +21,12 @@ const pathExists = async (path: string) => { export const sandbox: Task = { description: 'Create the sandbox from a template', - dependsOn: ({ template }, { link }) => { + dependsOn: ({ template, key }, { link }) => { if ('inDevelopment' in template && template.inDevelopment) { + if (pathExists(join(SANDBOX_DIRECTORY, key))) { + return ['run-registry']; + } + return ['run-registry', 'generate']; } diff --git a/scripts/tasks/serve.ts b/scripts/tasks/serve.ts index c70ded8a4a48..05e80006d620 100644 --- a/scripts/tasks/serve.ts +++ b/scripts/tasks/serve.ts @@ -1,7 +1,7 @@ import detectFreePort from 'detect-port'; import waitOn from 'wait-on'; -import type { TemplateKey } from '../get-template'; +import type { AllTemplatesKey } from '../../code/lib/cli-storybook/src/sandbox-templates'; import { getPort } from '../sandbox/utils/getPort'; import { type Task } from '../task'; import { ROOT_DIRECTORY } from '../utils/constants'; @@ -11,7 +11,7 @@ export const PORT = process.env.STORYBOOK_SERVE_PORT ? parseInt(process.env.STORYBOOK_SERVE_PORT, 10) : 8001; -function getServePort(key: TemplateKey) { +function getServePort(key: AllTemplatesKey) { return process.env.NX_CLI_SET === 'true' ? getPort({ selectedTask: 'serve', key }) : PORT; } diff --git a/scripts/utils/yarn.ts b/scripts/utils/yarn.ts index 9e7237713a08..16015b518f81 100644 --- a/scripts/utils/yarn.ts +++ b/scripts/utils/yarn.ts @@ -3,7 +3,7 @@ import { join } from 'node:path'; // TODO -- should we generate this file a second time outside of CLI? import storybookVersions from '../../code/core/src/common/versions'; -import type { TemplateKey } from '../get-template'; +import type { AllTemplatesKey } from '../../code/lib/cli-storybook/src/sandbox-templates'; import { exec } from './exec'; export type YarnOptions = { @@ -84,7 +84,7 @@ export const addWorkaroundResolutions = async ({ cwd, dryRun, key, -}: YarnOptions & { key?: TemplateKey }) => { +}: YarnOptions & { key?: AllTemplatesKey }) => { logger.info(`🔢 Adding resolutions for workarounds`); if (dryRun) { @@ -131,7 +131,7 @@ export const configureYarn2ForVerdaccio = async ({ dryRun, debug, key, -}: YarnOptions & { key: TemplateKey }) => { +}: YarnOptions & { key: AllTemplatesKey }) => { const command = [ // We don't want to use the cache or we might get older copies of our built packages // (with identical versions), as yarn (correctly I guess) assumes the same version hasn't changed diff --git a/scripts/verdaccio.yaml b/scripts/verdaccio.yaml index 1854b60bfc41..ca2064f8f25a 100644 --- a/scripts/verdaccio.yaml +++ b/scripts/verdaccio.yaml @@ -186,6 +186,14 @@ packages: access: $all publish: $all proxy: npmjs + '@storybook/addon-mcp': + access: $all + publish: $all + proxy: npmjs + '@storybook/mcp': + access: $all + publish: $all + proxy: npmjs # storybook packages are NOT proxied to global registry # allowing us to republish any version during tests diff --git a/yarn.lock b/yarn.lock index 11a301fbd37d..9b83c0ecf2e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8156,7 +8156,7 @@ __metadata: semver: "npm:^7.7.3" styled-jsx: "npm:5.1.6" typescript: "npm:^5.9.3" - vite-plugin-storybook-nextjs: "npm:^3.1.7" + vite-plugin-storybook-nextjs: "npm:^3.1.9" peerDependencies: next: ^14.1.0 || ^15.0.0 || ^16.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -30720,9 +30720,9 @@ __metadata: languageName: node linkType: hard -"vite-plugin-storybook-nextjs@npm:^3.1.7": - version: 3.1.7 - resolution: "vite-plugin-storybook-nextjs@npm:3.1.7" +"vite-plugin-storybook-nextjs@npm:^3.1.9": + version: 3.1.9 + resolution: "vite-plugin-storybook-nextjs@npm:3.1.9" dependencies: "@next/env": "npm:16.0.0" image-size: "npm:^2.0.0" @@ -30734,7 +30734,7 @@ __metadata: next: ^14.1.0 || ^15.0.0 || ^16.0.0 storybook: ^0.0.0-0 || ^9.0.0 || ^10.0.0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/5254ff5cd9168740659752053316b7d14f270ebc41134926adc02cb2d162674f268b0b4a808b01694d1d7a3f958b694de3dc4132137d6e337c78f4a5b8fee50c + checksum: 10c0/2d854d47debd556154bd575f1e0d962561a73a88e2cdeb0b1719e3510a6b77475a75667213568466866c08e6c21c3c4a50e982814823f24de00e3759c30872d5 languageName: node linkType: hard