Skip to content

Conversation

@kasperpeulen
Copy link
Contributor

@kasperpeulen kasperpeulen commented Nov 18, 2025

Closes #

What I did

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • New Features

    • Added workspace orchestration and many new sandbox project configurations for previews and testing.
  • Build & Infrastructure

    • Simplified package/workspace manifests and CI cache keys; streamlined Yarn config and improved sandbox lifecycle with port management, longer timeouts, and sandbox caching.
  • Documentation

    • Minor snippet, formatting, and example fixes.
  • Tests

    • Added portable storybook test projects and skipped a flaky test suite.
  • Chores

    • Updated environment defaults and cleaned up various scripts and manifest entries.

✏️ Tip: You can customize this high-level summary in your review settings.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR restructures the Storybook monorepo by consolidating the Yarn workspace root from the code/ directory to the git repository root. This simplifies the workspace structure by having a single workspace configuration instead of multiple nested ones.

Key changes:

  • Consolidated workspace definitions and dependency resolutions to the root package.json
  • Removed redundant .yarnrc.yml files from code/ and scripts/ subdirectories
  • Updated build utilities to reference the new workspace root location

Reviewed Changes

Copilot reviewed 8 out of 18 changed files in this pull request and generated no comments.

Show a summary per file
File Description
package.json Added workspace configuration and resolutions, making it the new workspace root
code/package.json Removed workspace definitions, resolutions, and packageManager as it's no longer the workspace root
code/core/package.json Removed resolutions that are now consolidated at the root level
scripts/package.json Removed resolutions that are now consolidated at the root level
scripts/utils/tools.ts Updated getWorkspace() to read workspace config from ROOT_DIRECTORY instead of CODE_DIRECTORY
scripts/utils/yarn.ts Removed yarn set version berry command as yarn version is now managed at root level
code/.yarnrc.yml Deleted - no longer needed with single workspace root
scripts/.yarnrc.yml Deleted - no longer needed with single workspace root
.gitignore Updated yarn-related patterns to reflect new structure with root-level .yarn directory
node_modules/.yarn-state.yml Deleted - part of cleanup
node_modules/.package-lock.json Deleted - part of cleanup
.yarn/patches/*.patch Added patch files at root level with updated paths from nested structure

@kasperpeulen kasperpeulen added build Internal-facing build tooling & test updates ci:normal labels Nov 18, 2025
@nx-cloud
Copy link

nx-cloud bot commented Nov 18, 2025

View your CI Pipeline Execution ↗ for commit 74699b7

Command Status Duration Result
nx run-many -t check -c production --parallel=7 ✅ Succeeded 1m 25s View ↗
nx run-many -t build -c production --parallel=3 ✅ Succeeded 3m 58s View ↗

☁️ Nx Cloud last updated this comment at 2025-11-24 12:52:12 UTC

@kasperpeulen kasperpeulen force-pushed the kasper/ai-friendly-setup branch from 276f911 to 1c1ffd8 Compare November 18, 2025 15:45
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Add a root Nx workspace and consolidate workspace tooling: unify Yarn config to root, rename workspace utilities, introduce dynamic sandbox port allocation and caching, update many package.json/project.json manifests and CI cache keys, and refactor scripts to use ROOT_DIRECTORY and sandbox orchestration.

Changes

Cohort / File(s) Summary
Root workspace & manifests
package.json, nx.json, .yarnrc.yml, .env, .gitignore, .nx/workflows/agents.yaml
Add root nx.json and workspace-style root package.json; add/modify root .yarnrc.yml, .env, .gitignore, and .nx workflow fragments.
Yarn config consolidation
code/.yarnrc.yml, scripts/.yarnrc.yml, ./.yarnrc.yml
Remove per-subfolder Yarn configs and provide a consolidated root .yarnrc.yml with updated settings (compression, cache, log filters, registry, architectures).
Code package and manifests
code/package.json, code/core/package.json, code/.nxignore
Rename package name, remove nested workspaces/resolutions/packageManager, add nx block to code/package.json, and adjust .nxignore entries.
Bulk Nx project.json normalization
code/**/project.json, code/sandbox/*/project.json, test-storybooks/*/project.json
Normalize $schema paths, add/standardize check targets, add tags: ["ci:normal"], expand sandbox targets to a common richer set and reformat many project.json files.
Remove per-package check scripts
many code/**/package.json files
Remove "check" scripts invoking the package-check helper across numerous packages.
Scripts & workspace utilities rename
scripts/utils/workspace.ts, scripts/utils/tools.ts, scripts/knip.config.ts, scripts/package.json, scripts/*
Rename getWorkspacesgetCodeWorkspaces, replace CODE_DIRECTORY with ROOT_DIRECTORY, update knip/workspace lookups and scripts (jiti/yarn invocations and nx blocks).
Sandbox porting, caching & orchestration
scripts/sandbox/utils/getPort.ts, scripts/task.ts, scripts/tasks/{serve,dev,build,e2e-tests-build,test-runner-build,prepare-sandbox,sandbox}.ts, scripts/tasks/sandbox-parts.ts
Add getPort utility, dynamic port allocation (getPort/detectFreePort/killPort), pass ports in task signatures, SKIP_SANDBOX/SKIP_COMPILE guards, and sandbox cache sync using ROOT_DIRECTORY.
Registry & publish orchestration
scripts/run-registry.ts, scripts/utils/workspace.ts, scripts/release/version.ts, scripts/build-package.ts, scripts/check-package.ts
Add Servers handle for Verdaccio+proxy, graceful shutdown and signal handlers, switch workspace discovery to getCodeWorkspaces and ROOT_DIRECTORY.
Build tooling & timeouts
scripts/build/utils/generate-types.ts, scripts/build/build-package.ts, scripts/build-package.ts
Adjust jiti binary path, increase dts-process timeout, add SKIP_COMPILE guard, and update workspace calls to getCodeWorkspaces.
CI changes (CircleCI / GitHub Actions)
.circleci/config.yml, .circleci/src/jobs/*.yml, .github/actions/setup-node-and-install/action.yml, .github/workflows/nx.yml
Simplify cache keys to root yarn.lock, change sandbox path usage, persist node_modules to workspace, add explicit yarn invocations, and add GitHub nx workflow.
Test-storybooks & Playwright
test-storybooks/*/package.json, test-storybooks/*/project.json, test-storybooks/*/playwright-e2e.config.ts
Add nx.includedScripts fields and new project.json descriptors for examples; update Playwright junit outputFile paths.
Typing/source/test minor fixes
code/core/src/.../TabsView*.tsx, code/frameworks/nextjs/*, code/renderers/react/src/entry-preview.tsx, docs/_snippets/*, code/renderers/vue3/template/*, code/renderers/web-components/src/docs/*.test.ts
Add explicit type casts, remove @ts-expect-error suppressions, small snippet formatting changes, adjust global typing, and skip one failing test suite.
Utilities and small tweaks
scripts/utils/filterExistsInRootDir.ts, scripts/utils/kill-process-on-port.ts, scripts/utils/yarn.ts, scripts/utils/main-js.ts
Rename filterExists*, swap CODE_DIRECTORY→ROOT_DIRECTORY, increase kill delay 500→1000ms, add small comments/ESLint directive.
Removed files
scripts/.yarnrc.yml, code/.yarnrc.yml, .ignore, some sandbox project.json deletions
Remove obsolete per-subproject config files and a few sandbox project entries.

Sequence Diagram(s)

sequenceDiagram
    participant User as CLI/User
    participant NX as Nx Runner
    participant Scripts as scripts/*
    participant Sandbox as Sandbox process
    participant Registry as Local Verdaccio

    User->>NX: run workspace task (build/dev/e2e)
    NX->>Scripts: invoke task (passes key, template, selectedTask)
    Scripts->>Scripts: port = getPort({key, selectedTask})
    Scripts->>Scripts: detectFreePort(port)
    alt port free
        Scripts->>Sandbox: start server on port
        Sandbox->>Scripts: wait-on ready
        Scripts->>Registry: optionally publish via Verdaccio
        Scripts->>NX: report success
    else port occupied
        Scripts->>Scripts: killPort(port)
        Scripts->>Scripts: retry or fail
        Scripts-->>User: error or retry result
    end
Loading
sequenceDiagram
    participant Runner as scripts/run-registry.ts
    participant Kill as killPort
    participant Verdaccio as Verdaccio
    participant Proxy as proxy

    Runner->>Kill: clear ports (6001/6002)
    Runner->>Verdaccio: start Verdaccio (6001)
    Runner->>Proxy: start proxy (6002)
    Verdaccio-->>Runner: ready
    Proxy-->>Runner: ready
    Runner->>Runner: return Servers handle
    Note right of Runner: on signal/error -> Servers.close() -> kill ports and exit
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Focus areas for review:

  • Sandbox port allocation and lifecycle: scripts/sandbox/utils/getPort.ts, scripts/task.ts, scripts/tasks/{serve,dev,build,e2e-tests-build,test-runner-build,prepare-sandbox}.
  • Registry lifecycle and graceful shutdown: scripts/run-registry.ts (Servers handle, signal handlers, port cleanup).
  • Workspace resolution rename and ROOT_DIRECTORY propagation: scripts/utils/workspace.ts, scripts/utils/tools.ts, scripts/build-package.ts, scripts/check-package.ts, and related test mocks.
  • Bulk manifest edits: verify $schema paths, targets shapes and options.dir, and intentional removal of check scripts.
  • CI adjustments: .circleci/*, .github/actions/*, .github/workflows/nx.yml — ensure cache keys and path changes align with mono-repo layout.

Possibly related PRs

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ead2a8 and 74699b7.

📒 Files selected for processing (21)
  • .circleci/config.yml (13 hunks)
  • .circleci/src/jobs/bench-sandboxes.yml (1 hunks)
  • .circleci/src/jobs/check-sandboxes.yml (1 hunks)
  • .circleci/src/jobs/chromatic-sandboxes.yml (1 hunks)
  • .circleci/src/jobs/create-sandboxes.yml (2 hunks)
  • .circleci/src/jobs/e2e-dev.yml (1 hunks)
  • .circleci/src/jobs/e2e-production.yml (1 hunks)
  • .circleci/src/jobs/test-runner-production.yml (1 hunks)
  • .circleci/src/jobs/vitest-integration.yml (1 hunks)
  • .env (1 hunks)
  • code/e2e-tests/storybook-hooks.spec.ts (2 hunks)
  • package.json (1 hunks)
  • scripts/ecosystem-ci/before-test.js (1 hunks)
  • scripts/event-log-checker.ts (2 hunks)
  • scripts/get-sandbox-dir.ts (2 hunks)
  • scripts/get-template.ts (1 hunks)
  • scripts/prepare-sandbox.ts (1 hunks)
  • scripts/task.ts (8 hunks)
  • scripts/tasks/test-runner-build.ts (2 hunks)
  • scripts/upload-bench.ts (1 hunks)
  • scripts/utils/constants.ts (2 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
code/core/src/components/components/Tabs/TabsView.tsx (1)

8-8: LGTM! Type cast resolves styled-components compatibility.

The CSSObject import and cast when spreading barInnerStyle fix type incompatibility between React.CSSProperties (line 91) and the styled-components system. This mirrors the change in StatelessTabsView.tsx and maintains consistent typing across both Tab components.

Optionally, you could update the barInnerStyle prop type definition on line 91 from React.CSSProperties to CSSObject to eliminate the need for the cast, though the current approach maintains compatibility with callers passing standard React styles.

Also applies to: 153-153

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e79c9b2 and 2389161.

⛔ Files ignored due to path filters (9)
  • .yarn/patches/@testing-library-user-event-npm-14.6.1-5da7e1d4e2.patch is excluded by !**/.yarn/**
  • .yarn/patches/@types-babel__traverse-npm-7.20.6-fac4243243.patch is excluded by !**/.yarn/**
  • .yarn/patches/@vitest-expect-npm-3.2.4-97c526d5cc.patch is excluded by !**/.yarn/**
  • .yarn/patches/react-aria-components-npm-1.12.2-6c5dcdafab.patch is excluded by !**/.yarn/**
  • code/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • node_modules/.package-lock.json is excluded by !**/node_modules/**
  • node_modules/.yarn-state.yml is excluded by !**/node_modules/**
  • scripts/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (13)
  • .gitignore (2 hunks)
  • code/.yarnrc.yml (0 hunks)
  • code/core/package.json (0 hunks)
  • code/core/src/components/components/Tabs/StatelessTabsView.tsx (2 hunks)
  • code/core/src/components/components/Tabs/TabsView.tsx (2 hunks)
  • code/package.json (1 hunks)
  • nx.json (3 hunks)
  • package.json (2 hunks)
  • scripts/.yarnrc.yml (0 hunks)
  • scripts/build/utils/generate-types.ts (2 hunks)
  • scripts/package.json (0 hunks)
  • scripts/utils/tools.ts (2 hunks)
  • scripts/utils/yarn.ts (0 hunks)
💤 Files with no reviewable changes (5)
  • code/core/package.json
  • code/.yarnrc.yml
  • scripts/package.json
  • scripts/.yarnrc.yml
  • scripts/utils/yarn.ts
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-10-02T09:22:13.215Z
Learnt from: JReinhold
Repo: storybookjs/storybook PR: 32607
File: code/package.json:243-243
Timestamp: 2025-10-02T09:22:13.215Z
Learning: The Storybook repository uses Yarn v^4 (any 4.x version) as the package manager, configured via .yarnrc.yml and package.json packageManager field. Specific patch versions within v4 can be upgraded as needed.

Applied to files:

  • .gitignore
  • code/package.json
  • package.json
📚 Learning: 2025-09-29T13:20:23.346Z
Learnt from: mrginglymus
Repo: storybookjs/storybook PR: 32556
File: code/core/package.json:309-313
Timestamp: 2025-09-29T13:20:23.346Z
Learning: The `fast-printf` dependency in Storybook's core package is bundled into the final distribution during the build process, so it should remain in devDependencies rather than being moved to dependencies, following the same pattern as other bundled dependencies like `open`.

Applied to files:

  • .gitignore
  • code/package.json
📚 Learning: 2025-11-05T09:38:47.712Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Select/Select.tsx:200-204
Timestamp: 2025-11-05T09:38:47.712Z
Learning: Repo: storybookjs/storybook — Guidance: Until Storybook 11 is released, do not suggest using React.useId anywhere (e.g., in code/core/src/components/components/Select/Select.tsx) to maintain compatibility with React 17 runtimes. Prefer advising: accept a caller-provided props.id and, if needed, generate a client-only fallback id to minimize SSR hydration issues — but avoid useId. Resume prompting for useId after Storybook 11.

Applied to files:

  • code/core/src/components/components/Tabs/StatelessTabsView.tsx
📚 Learning: 2025-10-01T15:24:01.060Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32594
File: code/core/src/components/components/Popover/WithPopover.tsx:7-9
Timestamp: 2025-10-01T15:24:01.060Z
Learning: In the Storybook repository, "react-aria-components/patched-dist/*" (e.g., "react-aria-components/patched-dist/Dialog", "react-aria-components/patched-dist/Popover", "react-aria-components/patched-dist/Tooltip") are valid import paths created by a patch applied to the react-aria-components package. These imports should not be flagged as broken or invalid until a maintainer explicitly states they are no longer acceptable.

Applied to files:

  • code/core/src/components/components/Tabs/StatelessTabsView.tsx
📚 Learning: 2025-09-17T07:31:04.432Z
Learnt from: ndelangen
Repo: storybookjs/storybook PR: 32484
File: code/core/package.json:326-326
Timestamp: 2025-09-17T07:31:04.432Z
Learning: In Storybook's core package, dependencies like `open` are bundled into the final distribution during the build process, so they should remain in devDependencies rather than being moved to dependencies. End users don't need these packages as separate runtime dependencies since they're included in the bundled code.

Applied to files:

  • code/package.json
  • package.json
📚 Learning: 2025-11-05T09:37:25.920Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/tooltip/WithTooltip.tsx:54-96
Timestamp: 2025-11-05T09:37:25.920Z
Learning: Repo: storybookjs/storybook — In code/core/src/components/components/tooltip/WithTooltip.tsx, the legacy WithTooltip implementation is intentionally reintroduced for backward compatibility and is deprecated; maintainers (per Sidnioulz) do not want maintenance or improvements on it. Prefer WithTooltipNew/Popover; avoid suggesting changes to WithTooltip.* going forward.

Applied to files:

  • code/package.json
📚 Learning: 2025-09-24T09:39:39.233Z
Learnt from: ndelangen
Repo: storybookjs/storybook PR: 32507
File: code/core/src/manager/globals/globals-module-info.ts:25-33
Timestamp: 2025-09-24T09:39:39.233Z
Learning: In Storybook, storybook/actions/decorator is a preview-only entrypoint and should not be included in manager globals configuration. The duplicatedKeys array in code/core/src/manager/globals/globals-module-info.ts is specifically for manager-side externalization, not preview entrypoints.

Applied to files:

  • package.json
🧬 Code graph analysis (1)
scripts/utils/tools.ts (1)
scripts/utils/constants.ts (1)
  • ROOT_DIRECTORY (6-6)
🔇 Additional comments (11)
code/core/src/components/components/Tabs/StatelessTabsView.tsx (1)

5-5: LGTM! Type cast improves compatibility with styled-components.

The addition of the CSSObject import and the type cast when spreading barInnerStyle resolves type incompatibility between React.CSSProperties and the styled-components system. This change is consistent with the identical modification in TabsView.tsx and has no runtime impact.

Also applies to: 77-77

scripts/build/utils/generate-types.ts (2)

68-68: Clarify the 10x timeout increase from 2 to 20 minutes.

The timeout has been dramatically increased from 120,000ms (2 minutes) to 1,200,000ms (20 minutes). This is a substantial change that could mask performance issues or indicate a real need for extended processing time.

Please clarify:

  • Is this timeout increase necessary due to larger codebases or more complex type generation?
  • Have you observed type generation processes timing out at 2 minutes?
  • Could this hide underlying performance problems that should be addressed?

Consider whether a more moderate timeout (e.g., 5 minutes) would be sufficient, or if performance optimizations should be investigated instead.


32-32: This review comment is incorrect and should be ignored.

The file scripts/build/utils/generate-types.ts has not been modified in this PR. Git diff shows no changes to this file, and the path using three directory levels (join(import.meta.dirname, '..', '..', '..', 'node_modules', '.bin', 'jiti')) already existed in the previous commit. The review comment's claim that the path "now goes up three levels instead of two" is inaccurate—there is no change to this line as part of the current PR.

Likely an incorrect or invalid review comment.

scripts/utils/tools.ts (1)

23-23: LGTM: Consistent path constant rename aligns with new workspace structure.

The renaming from CODE_DIRECTORY to ROOT_DIRECTORY is consistent across all usages (import, workspace reading, globbing, and path construction). This correctly reflects the workspace root moving from code/ to the git repository root.

Also applies to: 134-146

.gitignore (1)

22-28: LGTM: Gitignore updates align with centralized Yarn workspace.

The changes correctly update Yarn-related ignore patterns for a workspace root at the git repository level:

  • Changed from recursive /**/.yarn to top-level .yarn with selective negations for necessary Yarn directories
  • Added node_modules/.yarn-state.yml which is a Yarn 4 state file
  • Added .junie entry

These changes are appropriate for the new workspace structure.

Also applies to: 67-69

code/package.json (2)

1-252: LGTM: Removal of workspace configuration is correct.

The removal of workspaces, resolutions, and packageManager fields from code/package.json is appropriate since this package is no longer the workspace root. These configurations have been moved to the root package.json.


171-171: No issues found — react-popper-tooltip placement is correct.

The package is properly placed in dependencies at the monorepo root. It's a runtime dependency used in code/core/src/components/components/tooltip/WithTooltip.tsx (lines 10–11) and exported as part of the public API via ./internal/components. Since it's not bundled into the distribution (unlike bundled packages like fast-printf and open which remain in devDependencies), it should be available as an external dependency to consumers.

package.json (3)

2-17: LGTM: Workspace configuration correctly moved to repository root.

The package has been appropriately renamed to @storybook/super-root and configured as the new workspace root with:

  • Private flag set (correct for workspace roots)
  • Workspace packages listing code, code/* subdirectories, and scripts

This aligns with the PR objective of making the git root the Yarn workspace root.


43-67: Resolutions and nx dependencies moved to workspace root.

The resolutions block and nx-related devDependencies have been appropriately moved from code/package.json to the root workspace configuration. This centralizes dependency resolution and build tooling at the workspace level.


4-4: I need to check if these CommonJS files are isolated in subdirectories with their own package.json files, as that would affect whether the root-level "type": "module" change impacts them.

Now I need to check if the subdirectories with CommonJS files have their own package.json files, which would shield them from the root-level "type": "module" change.

Now I need to check if these test directories have their own package.json files that would override the root setting:

Based on my verification, I can now provide the rewritten review comment. The original concern is partially valid: .babelrc.js and babel.config.js behave like the .mjs equivalents when your package.json file contains the "type": "module" option, and configuration files being treated as ES modules when package.json contains "type": "module" requires renaming them to use the .cjs file extension. However, the script output reveals that most CommonJS files are in test-storybooks/ subdirectories, which may have their own package.json files that override the root setting. Additionally, subdirectories are considered ESM only "until either another package.json or node_modules directory is encountered".


Verify root-level configuration files for ES module compatibility after adding "type": "module".

Setting "type": "module" at the root level affects .js files in the root scope, particularly configuration files. Files like jest.config.js, babel.config.js, and .babelrc.js will be interpreted as ES modules, which breaks CommonJS patterns like module.exports and require().

The script found CommonJS files mostly within test-storybooks/ subdirectories; these may be shielded by their own package.json files if present. However, ensure that any root-level .js configuration files either:

  • Use .cjs extension (e.g., jest.config.cjs, babel.config.cjs)
  • Use .json format (e.g., babel.config.json)
  • Use .mjs extension if ES module syntax is appropriate

Verify the repository structure for root-level configuration files that need migration before this change is applied.

nx.json (1)

30-33: LGTM: Nx configuration correctly updated for new workspace structure.

All path adjustments consistently add the code/ prefix or adjust working directories to {workspaceRoot}/code, correctly reflecting that the workspace root has moved up one level. Key changes:

  • Build outputs: code/bench/esbuild-metafiles/{projectName}
  • Test working directory and inputs: prefixed with code/
  • sb:serve working directory: {workspaceRoot}/code
  • Shared globals: updated to code/tsconfig.json and added scripts/**/* monitoring

The removal of the explicit inputs entry from the check configuration (line 54) appears to be a cleanup, relying on the default and ^production inputs.

Also applies to: 39-46, 54-54, 96-96, 105-105

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
.circleci/config.yml (1)

204-210: Confirm bare yarn invocation is intentional and idempotent.

Lines 206, 255, and 283 use bare yarn commands (which implicitly run yarn install). Verify that:

  1. This is intentional and matches your project's Yarn configuration (e.g., immutable mode, frozen lockfile flags).
  2. The command is idempotent so that re-runs don't fail if dependencies are already installed.

If Yarn is configured with immutable mode (common for CI), consider explicitly adding --immutable or --frozen-lockfile flags to lines 206, 255, and 283 for clarity and to prevent accidental lock file modifications.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87248f1 and 4ed1f03.

📒 Files selected for processing (1)
  • .circleci/config.yml (5 hunks)
🔇 Additional comments (4)
.circleci/config.yml (4)

200-210: Verify yarn.lock is at repository root for cache key resolution.

Cache keys using {{ checksum "yarn.lock" }} for Yarn 2.x assume the file path is relative to the executor's working directory (set to /tmp/storybook on line 54). The cache key change from separate code/yarn.lock and scripts/yarn.lock to a single yarn.lock requires this file to exist at the repository root. Confirm that the monorepo restructuring includes a root-level yarn.lock and that prior per-directory Yarn configurations have been removed.


224-237: Verify workspace persistence paths align with root-level yarn consolidation.

The persist_to_workspace step (lines 224–237) includes scripts/node_modules alongside code/node_modules. If the PR consolidates to a single root-level yarn workspace, verify that:

  1. scripts/node_modules should still be a separate persisted path, or
  2. All workspace packages (including scripts) share the root node_modules and only the root should be persisted.

If moving to a unified root workspace, you may need to remove scripts/node_modules from line 228 and ensure downstream jobs reference the correct node_modules location.


543-555: LGTM: Cache strategy properly supports yarn workspaces.

The pretty-docs job demonstrates correct handling of yarn workspaces: yarn workspaces use a single yarn.lock at the root with separate node_modules directories per workspace package, requiring each workspace's node_modules to be included in the cache. The pattern of restoring from a root yarn.lock checksum, then running yarn install in a workspace subdirectory, is appropriate for this setup.


1-1150: Cache consolidation verified and aligned with monorepo structure.

The monorepo structure fully supports the cache consolidation changes:

  1. Root yarn.lock is the canonical dependency source — The workspaces field in root package.json declares all workspace packages including scripts, ensuring all dependencies (from code, scripts, and other workspaces) are captured in the single root yarn.lock.

  2. All cache keys correctly reference root yarn.lock — The build, check, and pretty-docs jobs all use {{ checksum "yarn.lock" }} which resolves to the repository root. This is consistent and correct.

  3. scripts workspace is properly integrated — The pretty-docs job's cd scripts && yarn install step is safe because scripts is declared as a workspace, so its dependencies are already in the root yarn.lock and will be restored when the cache is populated.

The changes align the CircleCI configuration with the monorepo's workspace structure. The implementation is correct.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/utils/workspace.ts (1)

9-18: Fix workspace path resolution - critical double-prefix bug.

The change to getWorkspaces() cwd introduces a breaking bug. When yarn workspaces list runs from ROOT_DIRECTORY, it returns paths like "code/addons/a11y" (relative to root). However, consumers combine these with CODE_DIRECTORY, creating double-prefixed paths like "code/code/addons/a11y".

The workspacePath() function must normalize returned locations to be relative to CODE_DIRECTORY:

export async function workspacePath(type: string, packageName: string) {
  const workspaces = await getWorkspacesMemo();
  const workspace = workspaces.find((w) => w.name === packageName);
  if (!workspace) {
    throw new Error(`Unknown ${type} '${packageName}', not in yarn workspace!`);
  }
  // Strip 'code/' prefix since locations are now relative to ROOT_DIRECTORY
  return workspace.location.replace(/^code\//, '');
}

This breaks calls at scripts/tasks/sandbox-parts.ts lines 611, 647, 680, 686, and 719 where workspacePath() results are joined with CODE_DIRECTORY.

🧹 Nitpick comments (1)
scripts/run-registry.ts (1)

145-145: Simplify by reusing the root constant.

Line 145 recomputes resolvePath(join(__dirname, '..')), which is identical to the root constant defined on line 26.

Apply this diff:

-            const loggedLocation = location.replace(resolvePath(join(__dirname, '..')), '.');
+            const loggedLocation = location.replace(root, '.');
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ed1f03 and 60585da.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • scripts/run-registry.ts (1 hunks)
  • scripts/utils/workspace.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: JReinhold
Repo: storybookjs/storybook PR: 32607
File: code/package.json:243-243
Timestamp: 2025-10-02T09:22:13.215Z
Learning: The Storybook repository uses Yarn v^4 (any 4.x version) as the package manager, configured via .yarnrc.yml and package.json packageManager field. Specific patch versions within v4 can be upgraded as needed.
🧬 Code graph analysis (1)
scripts/utils/workspace.ts (1)
scripts/utils/constants.ts (1)
  • ROOT_DIRECTORY (6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: normal
🔇 Additional comments (3)
scripts/run-registry.ts (2)

112-116: <!-- [scratchpad_start]
[task verify_workspace_restructuring_final]
Analyze the workspace structure and review consistency.

[observations]
From root package.json:

  • Workspaces still defined as: ["code", "code/core", "code/addons/*", ...]
  • Root package.json is "@storybook/super-root", private: true
  • This is the workspace definition

From code/package.json:

  • "@storybook/root", version: "10.1.0-alpha.11"
  • This is where the version comes from

From script context:

  • Line 187: version = currentVersion() reads from code/package.json
  • Line 201: version is displayed in log: "found X packages at version Y"
  • Line 146: resolvedLocation = resolvePath('..', location)
  • location comes from packages returned by getWorkspaces(false)
  • packages would be paths like "code", "code/core", "code/addons/...", etc.

[analysis]
CRITICAL INSIGHT - The review comment is INCORRECT:

  1. The workspaces array in root package.json STILL references "code" directory
  2. code/ is NOT being removed - it's still a workspace package directory
  3. Line 113 reading from code/package.json is CORRECT because code/package.json is the actual Storybook version
  4. Line 146's change from resolvePath('../code', location) to resolvePath('..', location) is CORRECT because:
    • Old: join(__dirname, '..', 'code') + location would cause double 'code' if location contains 'code'
    • New: join(__dirname, '..') + location is correct since location from getWorkspaces includes 'code'
  5. Both lines are consistent and correct with current workspace structure

[conclusion]
The PR did NOT move packages OUT of code/ directory. The workspace root remains at code/ for Storybook packages. The change on line 146 is a bug fix to path resolution, not a root change. Line 113 is correct as-is.

[tag]

[done]

The review comment is incorrect. The workspace structure has not changed to move packages out of the code/ directory.

Analysis:

  • The root package.json workspaces still define packages as ["code", "code/core", "code/addons/*", ...], confirming code/ remains the workspace package directory.
  • code/package.json contains the actual Storybook version (10.1.0-alpha.11), which currentVersion() correctly reads from at line 113.
  • The change on line 146 from resolvePath('../code', location) to resolvePath('..', location) is a bug fix: since location from getWorkspaces() already includes 'code' as a prefix, the old code would have created double 'code' in the path. The new code is correct.
  • Both line 113 and line 146 are consistent with the current workspace structure and work correctly together.

Likely an incorrect or invalid review comment.


146-146: Path resolution change is correct and verified.

The workspace location values returned by getWorkspaces() still include the "code/" prefix (e.g., "code/addons/a11y", "code/builders/builder-vite"). The change from resolvePath('../code', location) to resolvePath('..', location) properly resolves these locations to the correct package directories:

  • With resolvePath('..', location) where location is "code/addons/a11y", the resolved path correctly points to /home/jailuser/git/code/addons/a11y

The previous path would have resulted in a double code/ directory in the path, which would be incorrect. This change aligns with the PR objective without requiring any additional modifications.

scripts/utils/workspace.ts (1)

5-5: LGTM: Import updated to ROOT_DIRECTORY.

The import change aligns with the PR objective to make the git root the workspace root.

This commit sets up Nx Cloud for your Nx workspace, enabling distributed caching and the Nx Cloud GitHub integration for fast CI and improved developer experience.

You can access your Nx Cloud workspace by going to
https://cloud.nx.app/orgs/65e9fd00a2d2dea5ebe4e94c/workspaces/691dbcdcb2822ca4fdc727c4

**Note:** This commit attempts to maintain formatting of the nx.json file, however you may need to correct formatting by running an nx format command and committing the changes.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90a98b1 and c5b8f6f.

📒 Files selected for processing (2)
  • .github/workflows/nx.yml (1 hunks)
  • nx.json (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: JReinhold
Repo: storybookjs/storybook PR: 32607
File: code/package.json:243-243
Timestamp: 2025-10-02T09:22:13.215Z
Learning: The Storybook repository uses Yarn v^4 (any 4.x version) as the package manager, configured via .yarnrc.yml and package.json packageManager field. Specific patch versions within v4 can be upgraded as needed.
🪛 Gitleaks (8.29.0)
nx.json

[high] 3-3: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: nx
🔇 Additional comments (7)
nx.json (3)

41-41: Verify workspace path correctness after consolidation.

The PR objective mentions making the git root the yarn workspace root. Lines 41 and 57 reference {workspaceRoot}/code/... paths—confirm whether the /code subdirectory is still the correct location post-consolidation, or if paths should be adjusted to the root level.

Also applies to: 57-57


20-52: Nx targetDefaults and cache configuration look appropriate.

The build and check targets are well-configured with proper inputs, outputs, dependencies, and caching rules aligned with the monorepo structure.


54-68: namedInputs configuration is well-structured.

The exclusion rules for production builds appropriately avoid unnecessary cache invalidations from test, spec, stories, and config file changes.

.github/workflows/nx.yml (4)

25-26: Inconsistent state: Distributed CI is uncommented despite comment guidance.

Line 25 states "Uncomment this line to enable task distribution," but line 26 is already uncommented. Clarify the intent: should distributed CI be enabled by default, or should this line remain commented out pending explicit enablement?


29-34: Node setup and dependency installation are well-configured.

The caching strategy using cache: 'yarn' will automatically use the repo root yarn.lock, aligning with the PR's objective to standardize CI cache keys. The --immutable flag ensures reproducible installs.


35-39: Nx command configuration looks correct.

The nx-set-shas step and nx affected command are properly configured to detect and build only changed packages with production optimization flags.


7-9: Permissions are appropriately minimal.

The workflow only requests read permissions for actions and contents, following least-privilege security practices.

# Conflicts:
#	code/addons/vitest/src/postinstall.ts
#	code/nx.json
#	code/package.json
#	code/yarn.lock
#	scripts/npm-auth.ts
#	scripts/yarn.lock
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 15211b2 and b4dd932.

📒 Files selected for processing (26)
  • .circleci/config.yml (4 hunks)
  • code/addons/a11y/package.json (0 hunks)
  • code/addons/docs/package.json (0 hunks)
  • code/addons/links/package.json (0 hunks)
  • code/addons/onboarding/package.json (0 hunks)
  • code/addons/pseudo-states/package.json (0 hunks)
  • code/addons/themes/package.json (0 hunks)
  • code/addons/vitest/package.json (0 hunks)
  • code/addons/vitest/src/postinstall.ts (1 hunks)
  • code/builders/builder-vite/package.json (0 hunks)
  • code/builders/builder-webpack5/package.json (0 hunks)
  • code/core/package.json (0 hunks)
  • code/frameworks/angular/package.json (0 hunks)
  • code/frameworks/ember/package.json (0 hunks)
  • code/frameworks/html-vite/package.json (0 hunks)
  • code/frameworks/nextjs-vite/package.json (0 hunks)
  • code/frameworks/nextjs/package.json (0 hunks)
  • code/frameworks/preact-vite/package.json (0 hunks)
  • code/frameworks/react-native-web-vite/package.json (0 hunks)
  • code/frameworks/react-vite/package.json (0 hunks)
  • code/frameworks/react-webpack5/package.json (0 hunks)
  • code/frameworks/server-webpack5/package.json (0 hunks)
  • code/frameworks/svelte-vite/package.json (0 hunks)
  • code/frameworks/sveltekit/package.json (0 hunks)
  • code/frameworks/vue3-vite/package.json (0 hunks)
  • code/frameworks/web-components-vite/package.json (0 hunks)
💤 Files with no reviewable changes (24)
  • code/frameworks/vue3-vite/package.json
  • code/frameworks/react-webpack5/package.json
  • code/addons/onboarding/package.json
  • code/frameworks/angular/package.json
  • code/builders/builder-vite/package.json
  • code/addons/docs/package.json
  • code/builders/builder-webpack5/package.json
  • code/core/package.json
  • code/addons/links/package.json
  • code/addons/pseudo-states/package.json
  • code/frameworks/sveltekit/package.json
  • code/frameworks/nextjs-vite/package.json
  • code/frameworks/svelte-vite/package.json
  • code/addons/a11y/package.json
  • code/frameworks/preact-vite/package.json
  • code/frameworks/server-webpack5/package.json
  • code/frameworks/ember/package.json
  • code/frameworks/web-components-vite/package.json
  • code/addons/vitest/package.json
  • code/addons/themes/package.json
  • code/frameworks/nextjs/package.json
  • code/frameworks/react-vite/package.json
  • code/frameworks/html-vite/package.json
  • code/frameworks/react-native-web-vite/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • .circleci/config.yml
🧰 Additional context used
🪛 Biome (2.1.2)
code/addons/vitest/src/postinstall.ts

[error] 122-122: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 122-122: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 122-122: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 122-122: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 122-122: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 123-123: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 123-123: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 123-123: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 123-123: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 123-123: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 123-123: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 123-123: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 126-126: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 126-126: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 126-126: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 126-126: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 126-127: Expected a semicolon or an implicit semicolon after a statement, but found none

An explicit or implicit semicolon is expected here...

...Which is required to end this statement

(parse)


[error] 129-129: Shouldn't redeclare 'versionedDependencies'. Consider to delete it or rename it.

'versionedDependencies' is defined here:

(lint/suspicious/noRedeclare)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: nx

Comment on lines 129 to 132
const versionedDependencies = uniqueDependencies.map((p) => {
if (p.includes('vitest')) {
return vitestVersionSpecifier ? `${p}@${vitestVersionSpecifier.split('::')[0]}` : p;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Critical logic error: variable redeclaration and undefined reference.

Line 129 redeclares versionedDependencies, which was already defined at line 100 from addonVitestService.collectDependencies(). The code also attempts to map over uniqueDependencies, which is not defined anywhere in the visible scope. This would cause a ReferenceError at runtime.

The intended logic appears to be adding version specifiers to vitest-related dependencies. However, the implementation has several issues:

  1. Should reassign rather than redeclare: versionedDependencies = versionedDependencies.map(...)
  2. If coverage-v8 needs to be conditionally added (per the AI summary), it should be pushed to versionedDependencies before the mapping
  3. The coverage addition logic from lines 122-127 should be integrated properly

Proposed fix (after removing lines 122-127):

+    if (!v8Version && !istanbulVersion) {
+      versionedDependencies.push('@vitest/coverage-v8');
+    }
+  }
+
-  const versionedDependencies = uniqueDependencies.map((p) => {
+  const versionedDependenciesWithVersions = versionedDependencies.map((p) => {
     if (p.includes('vitest')) {
       return vitestVersionSpecifier ? `${p}@${vitestVersionSpecifier.split('::')[0]}` : p;
     }
+    return p;

Note: Line 132 appears incomplete - it should have a return p; statement to handle non-vitest packages. Please verify the complete mapping logic and update line 136 to use versionedDependenciesWithVersions instead of versionedDependencies.

Run the following script to check if there are other references to these variables:


🏁 Script executed:

#!/bin/bash
# Search for all references to uniqueDependencies and versionedDependencies
rg -n -C3 'uniqueDependencies|versionedDependencies' --type ts

Length of output: 3212


🏁 Script executed:

#!/bin/bash
# Get more context around lines 95-145 to see full variable declarations and usage
cat code/addons/vitest/src/postinstall.ts | sed -n '95,145p' | cat -n

Length of output: 2450


Critical runtime errors: undefined variable, redeclaration, incomplete mapping, and misplaced logic.

The code at lines 129-141 contains multiple fatal errors:

  1. Undefined variable: Line 126 references uniqueDependencies, which is never declared. This should be versionedDependencies (the variable assigned at line 100).

  2. Redeclaration: Line 129 redeclares versionedDependencies, shadowing the original from line 100, and attempts to map the undefined uniqueDependencies.

  3. Incomplete mapping: Lines 130-132 are missing a return statement for non-vitest packages, causing the map to return undefined for those entries.

  4. Structural error: Lines 134-141 (the installation logic) are incorrectly nested inside the map callback. They should execute after the mapping completes.

Proposed fix:

-    uniqueDependencies.push(`@vitest/coverage-v8`);
+    versionedDependencies.push(`@vitest/coverage-v8`);
   }
 
-  const versionedDependencies = uniqueDependencies.map((p) => {
+  versionedDependencies = versionedDependencies.map((p) => {
     if (p.includes('vitest')) {
       return vitestVersionSpecifier ? `${p}@${vitestVersionSpecifier.split('::')[0]}` : p;
     }
+    return p;
-
+  });
+
   if (versionedDependencies.length > 0) {
     logger.step('Adding dependencies to your package.json');
     logger.log('  ' + versionedDependencies.join(', '));

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (2.1.2)

[error] 129-129: Shouldn't redeclare 'versionedDependencies'. Consider to delete it or rename it.

'versionedDependencies' is defined here:

(lint/suspicious/noRedeclare)

🤖 Prompt for AI Agents
In code/addons/vitest/src/postinstall.ts around lines 129-132 (and referencing
the earlier declaration at ~line 100 and the install logic at ~lines 134-141):
the map is redeclaring and shadowing versionedDependencies and is iterating an
undefined uniqueDependencies, missing a return for non-vitest packages, and
contains installation logic inside the map callback; change the map to operate
on the existing versionedDependencies (do not redeclare the variable), iterate
the correct array (the one declared at line ~100), ensure the callback returns p
for non-vitest entries (i.e., return p when not matching 'vitest'), and move the
installation logic currently nested inside the map callback so it runs after the
mapping completes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
scripts/tasks/dev.ts (1)

19-60: Tighten dry-run behavior and add visibility when reusing an existing port

The new run implementation looks consistent with the port-aware ready check and correctly waits for both preview and manager before recording benchmarks. Two follow-ups you might consider:

  • In dryRun mode we still call waitOn(...) and will likely hit the 10s timeout because no server is actually started. Short‑circuiting when dryRun is true (e.g., return controller immediately after computing devCommand) would make dry runs faster and less surprising.
  • When detectFreePort(port) !== port and we decide not to start Storybook, we silently no-op. Emitting a debug/info log in that branch would make it clearer in CI/dev logs that we’re intentionally reusing an existing listener rather than silently skipping startup.

If you’d like, I can sketch a small diff for both tweaks.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0eda2f9 and f37eaf4.

📒 Files selected for processing (5)
  • .circleci/config.yml (4 hunks)
  • scripts/tasks/dev.ts (1 hunks)
  • scripts/tasks/e2e-tests-build.ts (3 hunks)
  • scripts/tasks/serve.ts (2 hunks)
  • scripts/tasks/test-runner-build.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • scripts/tasks/e2e-tests-build.ts
  • .circleci/config.yml
  • scripts/tasks/test-runner-build.ts
🧰 Additional context used
🧬 Code graph analysis (1)
scripts/tasks/dev.ts (1)
scripts/ecosystem-ci/before-test.js (1)
  • sandboxDir (34-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: nx
🔇 Additional comments (5)
scripts/tasks/dev.ts (1)

16-18: Port-aware readiness logic is correct and verified

The ready({ port }) implementation correctly checks whether the requested port is already in use by comparing the detected port against the requested port. This matches the detect-port 1.6.1 contract, where detect(port) resolves to the same port when free and a different port when already in use.

scripts/tasks/serve.ts (4)

4-6: LGTM: Imports support the dynamic port workflow.

The new imports for getPort and ROOT_DIRECTORY are necessary for the per-sandbox port allocation and workspace root changes.


17-20: LGTM: Ready check correctly uses dynamic port.

The ready function correctly determines if the service is already running by checking if the dynamically computed port is occupied. The logic is sound: detectFreePort(port) !== port returns true when the port is in use (service ready).


9-11: The PORT constant is actively used and should not be removed.

The review comment incorrectly assumes PORT is dead code because it's not used within serve.ts itself. However, verification shows PORT is exported and actively imported in three other files:

  • scripts/tasks/test-runner-build.ts
  • scripts/tasks/e2e-tests-build.ts
  • scripts/tasks/bench.ts

The fact that serve.ts internally uses getPort() instead does not make the exported PORT constant unused.

Likely an incorrect or invalid review comment.


21-39: ****

The builtSandboxDir is always constructed as an absolute path via join(path.isAbsolute(sandboxDir) ? sandboxDir : join(ROOT_DIRECTORY, sandboxDir), dir ?? templateKey.replace('/', '-')). Since the first argument to join() is always absolute (either sandboxDir itself if absolute, or join(ROOT_DIRECTORY, sandboxDir) if relative), the resulting path is always absolute. Absolute paths resolve consistently regardless of the working directory set in exec(), so there is no path resolution concern in serve.ts or elsewhere.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
scripts/tasks/sandbox-parts.ts (3)

413-413: Remove debug console.log statement.

This debug logging was previously flagged for removal before merging.


555-555: Remove debug console.log statement.

This debug logging was previously flagged for removal before merging.


262-262: Path remains incorrect—esbuild-loader will not be found at runtime.

This issue was previously flagged: the path assumes esbuild-loader is installed at root node_modules, but it's declared in code/package.json and will be at code/node_modules/esbuild-loader. The path ../../node_modules/esbuild-loader from scripts/tasks/ will fail with MODULE_NOT_FOUND.

🧹 Nitpick comments (1)
scripts/utils/main-js.ts (1)

3-4: Consider using Node.js built-in path utilities.

The ESLint disable suggests slash is flagged by project dependency rules. Modern Node.js provides path.posix.normalize() or similar utilities that could potentially replace this external dependency, eliminating the need for the disable comment.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f37eaf4 and 0bcff4b.

📒 Files selected for processing (2)
  • scripts/tasks/sandbox-parts.ts (11 hunks)
  • scripts/utils/main-js.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-24T13:04:58.631Z
Learnt from: cylewaitforit
Repo: storybookjs/storybook PR: 31965
File: code/lib/eslint-plugin/src/rules/only-csf3.ts:31-33
Timestamp: 2025-09-24T13:04:58.631Z
Learning: The Storybook ESLint plugin supports ESLint v8.57+ where context.sourceCode is already available as a property since it was introduced in v8.40.0, so no fallback to context.getSourceCode() is needed in rules.

Applied to files:

  • scripts/utils/main-js.ts
📚 Learning: 2025-10-01T15:24:01.060Z
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32594
File: code/core/src/components/components/Popover/WithPopover.tsx:7-9
Timestamp: 2025-10-01T15:24:01.060Z
Learning: In the Storybook repository, "react-aria-components/patched-dist/*" (e.g., "react-aria-components/patched-dist/Dialog", "react-aria-components/patched-dist/Popover", "react-aria-components/patched-dist/Tooltip") are valid import paths created by a patch applied to the react-aria-components package. These imports should not be flagged as broken or invalid until a maintainer explicitly states they are no longer acceptable.

Applied to files:

  • scripts/tasks/sandbox-parts.ts
🧬 Code graph analysis (1)
scripts/tasks/sandbox-parts.ts (1)
scripts/utils/filterExistsInRootDir.ts (1)
  • filterExistsInRootDir (17-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: nx
🔇 Additional comments (4)
scripts/tasks/sandbox-parts.ts (4)

5-5: Import updates align with workspace restructuring.

The import changes correctly support the monorepo restructuring:

  • Added path default import for path.sep usage in path manipulation
  • Updated SupportedLanguage to import from src instead of dist
  • Added ROOT_DIRECTORY constant for new root-based path resolution
  • Switched to filterExistsInRootDir utility for the new structure

Also applies to: 24-24, 29-29, 31-31


556-556: Improved comma handling logic.

The updated logic correctly handles both cases where a comma is not needed:

  • After an opening brace { (newly created object)
  • After an existing comma ,

This prevents syntax errors when inserting the tags property into the storybookTest configuration.


650-650: ROOT_DIRECTORY migrations are consistent with workspace restructuring.

All references have been correctly updated from CODE_DIRECTORY to ROOT_DIRECTORY:

  • Renderer and framework template path resolutions now use root-relative paths
  • Story existence filtering switched to filterExistsInRootDir
  • Angular compodoc script references the scripts directory at root level

These changes align with the PR objective of making the git root the workspace root.

Also applies to: 666-666, 689-689, 701-701, 777-777, 1043-1043


405-408: Path stripping logic is correct and requires no changes.

Verification confirms the implementation properly handles the workspace restructuring:

  • All packageDir inputs originate from workspacePath(), which returns paths with the "code/" prefix per the workspace configuration ("code/core", "code/addons/*", etc.)
  • The filter condition correctly removes only the first "code" segment: it === 'code' && i === 0
  • Path reconstruction via join(CODE_DIRECTORY, packageDir, ...) where CODE_DIRECTORY = ROOT_DIRECTORY/code yields correct filesystem paths
  • No edge cases exist since all workspace paths consistently follow the "code/*" pattern

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
scripts/tasks/e2e-tests-build.ts (1)

36-36: Consider making the timeout configurable for slower environments.

The 20-second timeout is reasonable for most cases, but might be insufficient for slow CI runners or complex sandboxes with many dependencies. Consider extracting this to a constant or environment variable to allow adjustment without code changes.

Example:

+const WAIT_TIMEOUT = process.env.E2E_WAIT_TIMEOUT 
+  ? parseInt(process.env.E2E_WAIT_TIMEOUT, 10) 
+  : 20000;
+
-    await waitOn({ resources: [`http://localhost:${port}`], interval: 16, timeout: 20000 });
+    await waitOn({ resources: [`http://localhost:${port}`], interval: 16, timeout: WAIT_TIMEOUT });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0bcff4b and 7955c88.

📒 Files selected for processing (1)
  • scripts/tasks/e2e-tests-build.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
scripts/tasks/e2e-tests-build.ts (1)
scripts/sandbox/utils/getPort.ts (1)
  • getPort (4-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: nx
🔇 Additional comments (2)
scripts/tasks/e2e-tests-build.ts (2)

1-54: Good improvements to E2E test reliability with dynamic port management.

The addition of waitOn to ensure the server is ready before running tests is a solid improvement that should reduce flaky test failures. The dynamic port computation via getPort aligns with the broader port-management patterns mentioned in the PR objectives and integrates well with the monorepo restructuring.

Subject to verification of the port computation logic, these changes enhance the robustness of the E2E testing infrastructure.


20-21: No issues found—port computation logic is correct.

Verification shows that selectedTask will always be 'e2e-tests' when this task runs (set at task.ts:414 to the current taskKey). The ternary correctly evaluates to getPort({ key, selectedTask: 'serve' }), which aligns with the task's dependsOn: ['serve']. The else branch is unreachable in normal execution and serves as defensive fallback. The code is safe as written.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (3)
scripts/run-registry.ts (3)

63-67: Fix minor log message formatting

The redirect log is missing a space after the emoji, which reads a bit cramped in logs.

-          logger.log(`🌿redirecting 6001 request to verdaccio on 6002`);
+          logger.log(`🌿 redirecting 6001 request to verdaccio on 6002`);

163-170: Use ROOT_DIRECTORY when resolving package locations for publish (cwd‑dependent bug)

resolvedLocation = resolvePath('../code', location) depends on process.cwd(). If this script is run from the repo root (likely after moving the Yarn root there), path.resolve('../code', location) will resolve relative to / and can point outside the repo (e.g., /code/...) instead of <repo>/code/....

Since location from getCodeWorkspaces(false) is now relative to the code directory, it’s safer to resolve from the known root:

-            const loggedLocation = location.replace(resolvePath(join(__dirname, '..')), '.');
-            const resolvedLocation = resolvePath('../code', location);
+            const resolvedLocation = resolvePath(ROOT_DIRECTORY, 'code', location);
+            const loggedLocation = resolvedLocation.replace(ROOT_DIRECTORY, '.');

This makes resolution independent of cwd and consistent with other uses of ROOT_DIRECTORY.

#!/bin/bash
# Sanity-check how path.resolve behaves from different cwd values
node - << 'EOF'
const path = require('node:path');

for (const cwd of ['/repo', '/repo/scripts']) {
  process.chdir(cwd);
  console.log(`\nCWD = ${cwd}`);
  console.log('resolve("../code", "addons/a11y") =',
    path.resolve('../code', 'addons/a11y'));
}
EOF

254-259: Signal handlers are functional; optional centralization of shutdown logic

The signal handlers correctly invoke servers?.close() and then process.exit(0), reusing the Servers.close abstraction. If you want to avoid duplicate shutdown paths and make behavior uniform with the error handler, you could factor a shared shutdown() helper that closes servers and exits with the desired code, then call it from both the signal handlers and run().catch.

Not strictly necessary, but it would centralize lifecycle behavior.

🧹 Nitpick comments (2)
scripts/utils/workspace.ts (1)

11-37: getCodeWorkspaces behavior matches callers, minor docstring nit

The new getCodeWorkspaces implementation (running yarn workspaces list from ROOT_DIRECTORY, excluding @storybook/scripts/@storybook/code, and stripping the leading code/ segment) correctly produces locations that are relative to the code directory, which matches usages in scripts/release/version.ts and other scripts that prefix with code/. The memoized wrapper is also updated appropriately.

Minor: the docblock could be clarified to explicitly state that location is “relative to the /code directory” to avoid confusion for future consumers.

scripts/run-registry.ts (1)

40-48: Servers abstraction and startup race look correct; consider tightening typing

The Servers type and the startVerdaccio implementation (resolving once both proxy and Verdaccio are listening, plus a 10s timeout) look reasonable, and servers.close() properly closes both servers and verifies their ports are free.

For clarity and stronger typing, you could:

  • Type the first promise explicitly as new Promise<Servers>((resolve) => { … }), and
  • Return await Promise.race<Servers>([…]) (dropping the as Promise<Servers> cast),

to avoid any ambiguity around the inferred return type of startVerdaccio.

Also applies to: 76-95, 96-127

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bef07e7 and 86ecf6e.

📒 Files selected for processing (9)
  • .yarnrc.yml (1 hunks)
  • scripts/build-package.ts (1 hunks)
  • scripts/check-package.ts (1 hunks)
  • scripts/release/version.ts (2 hunks)
  • scripts/run-registry.ts (7 hunks)
  • scripts/tasks/e2e-tests-build.ts (3 hunks)
  • scripts/tasks/sandbox-parts.ts (4 hunks)
  • scripts/utils/workspace.ts (1 hunks)
  • scripts/utils/yarn.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/utils/yarn.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-02T09:22:13.215Z
Learnt from: JReinhold
Repo: storybookjs/storybook PR: 32607
File: code/package.json:243-243
Timestamp: 2025-10-02T09:22:13.215Z
Learning: The Storybook repository uses Yarn v^4 (any 4.x version) as the package manager, configured via .yarnrc.yml and package.json packageManager field. Specific patch versions within v4 can be upgraded as needed.

Applied to files:

  • .yarnrc.yml
  • scripts/release/version.ts
🧬 Code graph analysis (5)
scripts/build-package.ts (1)
scripts/utils/workspace.ts (1)
  • getCodeWorkspaces (15-35)
scripts/tasks/e2e-tests-build.ts (1)
scripts/sandbox/utils/getPort.ts (1)
  • getPort (4-9)
scripts/run-registry.ts (2)
scripts/npm-auth.ts (1)
  • npmAuth (21-52)
scripts/utils/workspace.ts (1)
  • getCodeWorkspaces (15-35)
scripts/release/version.ts (2)
scripts/utils/workspace.ts (1)
  • getCodeWorkspaces (15-35)
code/lib/create-storybook/src/services/VersionService.ts (1)
  • getCurrentVersion (10-12)
scripts/check-package.ts (1)
scripts/utils/workspace.ts (1)
  • getCodeWorkspaces (15-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: nx
🔇 Additional comments (12)
scripts/tasks/sandbox-parts.ts (3)

540-554: LGTM—comma insertion logic is correct.

The logic for determining whether a comma is needed when inserting the tags property into the storybookTest() configuration correctly handles the cases where before ends with { (empty object, first property) or , (already has trailing comma).


1035-1035: ROOT_DIRECTORY usage aligns with workspace restructuring.

The use of ROOT_DIRECTORY to construct the path to the scripts directory is consistent with this PR's objective of making the git root the yarn workspace root. The change correctly points to the scripts directory at the repository root.


31-31: The import statement is correct. The verification confirms that the file filterExistsInRootDir.ts exports a function named filterExistsInCodeDir (line 17: export const filterExistsInCodeDir = async (...)). The import at line 31 correctly imports this function by its actual export name, and the usage at line 769 correctly calls it. The function export name was not changed—only the filename was updated. There is no runtime error or named export mismatch.

Likely an incorrect or invalid review comment.

scripts/tasks/e2e-tests-build.ts (4)

2-4: LGTM! Appropriate imports for dynamic port management.

The addition of waitOn and getPort imports aligns well with the new dynamic port allocation and server readiness checks introduced in this PR.


20-20: LGTM! Function signature appropriately extended.

The addition of key, sandboxDir, and selectedTask parameters provides the necessary context for dynamic port computation and sandbox management.


36-36: Verify the 200-second timeout is appropriate.

The waitOn timeout of 200 seconds (3.3 minutes) is quite long. While this ensures tolerance for slow environments, it could mask underlying server startup issues and delay feedback in CI/CD pipelines. Please confirm this timeout is intentional and appropriate for your infrastructure.


41-42: No issues found—STORYBOOK_TYPE is correct.

STORYBOOK_TYPE: this.type correctly sets 'build' because the task depends on 'serve', which serves production builds. The conditional port selection on line 21 determines which server port to connect to based on selectedTask, not the build type. Tests correctly use STORYBOOK_TYPE to detect production vs. dev environments, and the code handles both scenarios appropriately.

.yarnrc.yml (1)

1-35: Centralized Yarn 4 root config looks consistent

compressionLevel, enableGlobalCache, tsEnableAutoTypes, npmRegistryServer, supportedArchitectures, and unsafeHttpWhitelist are coherent for a Yarn v4 monorepo root and match the repo’s Yarn‑4 usage. Based on learnings, this looks aligned with the intended single-root setup.

scripts/release/version.ts (1)

14-14: Versioning script correctly switched to getCodeWorkspaces

Swapping to getCodeWorkspaces() here is consistent with the new workspace helper, and bumpAllPackageJsons’s join(CODE_DIR_PATH, pkg.location, 'package.json') matches the updated convention where location is relative to the code directory. This should avoid the previous code/code/... path duplication.

No further changes needed from this file’s side.

Also applies to: 246-246

scripts/build-package.ts (3)

24-24: LGTM!

The import change from getWorkspaces to getCodeWorkspaces correctly aligns with the workspace restructuring changes in scripts/utils/workspace.ts.


27-27: LGTM!

The function call correctly uses getCodeWorkspaces(), maintaining the same filtering logic while adapting to the workspace restructuring.


168-176: The hardcoded 'code' paths are correct and intentionally designed.

Verification confirms that getCodeWorkspaces() explicitly strips the 'code' prefix from workspace locations (with a documented backwards compatibility comment), and the hardcoded paths in lines 168 and 176 correctly add it back. This coupling is intentional and working properly with the new workspace structure at the git root. The paths resolve correctly to actual package.json files and working directories.

Comment on lines +14 to 18
import { getCodeWorkspaces } from './utils/workspace';

async function run() {
const packages = await getWorkspaces();
const packages = await getCodeWorkspaces();
const packageTasks = packages
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Align package.json path resolution with new getCodeWorkspaces locations

Now that getCodeWorkspaces() returns location relative to the code directory (e.g. addons/a11y), the readFile path:

const content = await readFile(resolve('../code', v.location, 'package.json'), 'utf-8');

is still dependent on process.cwd(). If the script is run from the repo root instead of the scripts directory, this can resolve outside the repo (/code/...) rather than <repo>/code/....

You’re already using an __dirname‑based path for cwd; it’s safer to mirror that here:

-    const content = await readFile(resolve('../code', v.location, 'package.json'), 'utf-8');
+    const content = await readFile(
+      resolve(__dirname, '..', 'code', v.location, 'package.json'),
+      'utf-8'
+    );

This keeps the behavior consistent and independent of where the command is invoked from.

Also applies to: 115-119


🏁 Script executed:

#!/bin/bash
# Demonstrate cwd-dependent resolution
node - << 'EOF'
const path = require('node:path');

for (const cwd of ['/repo', '/repo/scripts']) {
  process.chdir(cwd);
  console.log(`\nCWD = ${cwd}`);
  console.log('resolve("../code", "addons/a11y", "package.json") =',
    path.resolve('../code', 'addons/a11y', 'package.json'));
}
EOF

Length of output: 856


🏁 Script executed:

#!/bin/bash
# Inspect the check-package.ts file to verify the code and lines mentioned
cat scripts/check-package.ts

Length of output: 4092


🏁 Script executed:

#!/bin/bash
# Find and inspect the getCodeWorkspaces function definition
find . -name "*.ts" -o -name "*.js" | xargs grep -l "getCodeWorkspaces" | head -5

Length of output: 137


🏁 Script executed:

#!/bin/bash
# Check the workspace utilities
cat scripts/utils/workspace.ts 2>/dev/null || cat scripts/utils/workspace.js 2>/dev/null || echo "File not found in standard locations"

Length of output: 1495


Keep the package.json path resolution consistent with the cwd-based approach

The review comment is accurate. The readFile call at line 113 uses a cwd-dependent relative path, while the execa cwd at line 115 already uses __dirname for independence. Since getCodeWorkspaces() returns location with the code/ prefix stripped (e.g., addons/a11y), the inconsistency means the script behaves differently depending on the working directory from which it's invoked.

Apply the suggested fix to line 113:

-    const content = await readFile(resolve('../code', v.location, 'package.json'), 'utf-8');
+    const content = await readFile(
+      resolve(__dirname, '..', 'code', v.location, 'package.json'),
+      'utf-8'
+    );

This aligns the path resolution with the cwd logic and makes the script independent of the invocation directory.

🤖 Prompt for AI Agents
In scripts/check-package.ts around lines 14 to 18, the readFile call constructs
package.json with a relative ../code path which is cwd-dependent while later
commands set cwd using resolve(__dirname, '..', 'code', v.location); change the
package.json path construction to use the same resolve(__dirname, '..', 'code',
v.location, 'package.json') pattern (or derive it from the same cwd variable) so
both file reads and execa invocations use identical,
invocation-directory-independent paths.

Comment on lines +215 to +245
if (opts.publish) {
// when running e2e locally, clear cache to avoid EPUBLISHCONFLICT errors
const verdaccioCache = resolvePath(__dirname, '..', '.verdaccio-cache');
const verdaccioCache = join(ROOT_DIRECTORY, '.verdaccio-cache');
if (await pathExists(verdaccioCache)) {
logger.log(`🗑 cleaning up cache`);
await rm(verdaccioCache, { force: true, recursive: true });
}
}

logger.log(`🎬 starting verdaccio (this takes ±5 seconds, so be patient)`);

const [verdaccioServer, packages, version] = await Promise.all([
startVerdaccio(),
getWorkspaces(false),
currentVersion(),
]);

logger.log(`🌿 verdaccio running on ${verdaccioUrl}`);

logger.log(`👤 add temp user to verdaccio`);
// Use npmAuth helper to authenticate to the local Verdaccio registry
// This will create a .npmrc file in the root directory
await npmAuth({
username: 'foo',
password: 's3cret',
email: '[email protected]',
registry: 'http://localhost:6002',
outputDir: root,
});

logger.log(
`📦 found ${packages.length} storybook packages at version ${picocolors.blue(version)}`
);

if (opts.publish) {
await publish(packages, 'http://localhost:6002');
}

await rm(join(root, '.npmrc'), { force: true });
// Use npmAuth helper to authenticate to the local Verdaccio registry
// This will create a .npmrc file in the root directory
logger.log(`👤 add temp user to verdaccio`);
await npmAuth({
username: 'foo',
password: 's3cret',
email: '[email protected]',
registry: 'http://localhost:6002',
outputDir: root,
});
try {
const [packages, version] = await Promise.all([getCodeWorkspaces(false), currentVersion()]);
logger.log(
`📦 found ${packages.length} storybook packages at version ${picocolors.blue(version)}`
);
await publish(packages, 'http://localhost:6002');
} finally {
await rm(join(root, '.npmrc'), { force: true });
}

if (!opts.open) {
verdaccioServer.close();
process.exit(0);
if (!opts.open) {
await servers?.close();
process.exit(0);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Ensure failures cause a non‑zero exit code and always clean up Verdaccio

Two related concerns in the new run path:

  1. In the success path with opts.publish && !opts.open, you call await servers?.close(); process.exit(0);, which is fine.
  2. In the error path (run().catch(async (e) => { … })), you log the error and close servers, but do not exit with a non‑zero status, so CI or callers may treat failures as success.

To make failures visible and keep cleanup consistent, consider:

run().catch(async (e) => {
  logger.error(e);
  await servers?.close();
-});
+  process.exit(1);
+});

This ensures any error in run() results in a failing exit code after attempting a graceful shutdown.

Also applies to: 249-252


🏁 Script executed:

#!/bin/bash
# Example: simulate a failing run() to see current exit code
node - << 'EOF'
(async () => {
  try {
    // Simulate run() throwing
    throw new Error('simulated failure');
  } catch (e) {
    console.error('logged error:', e.message);
    // Note: no process.exit(1) here would yield exit code 0
  }
})();
EOF
echo "Actual exit code: $?"

Length of output: 51


I need to inspect the actual error handler in the codebase to verify the current state.


🏁 Script executed:

#!/bin/bash
# Inspect the run().catch() handler in scripts/run-registry.ts
rg -n "run\(\)\.catch" scripts/run-registry.ts -A 5 -B 2

Length of output: 198


🏁 Script executed:

#!/bin/bash
# Also search for the error handler pattern more broadly
rg -n "\.catch\(" scripts/run-registry.ts -A 8 -B 2

Length of output: 790


🏁 Script executed:

#!/bin/bash
# Check lines 249-252 specifically
sed -n '245,255p' scripts/run-registry.ts

Length of output: 208


🏁 Script executed:

#!/bin/bash
# Get broader context around the run() function call
sed -n '240,260p' scripts/run-registry.ts

Length of output: 347


Add process.exit(1) to the error handler for proper failure signaling

The error handler at lines 249-252 logs errors and closes servers but exits with code 0, causing CI and callers to treat failures as success. Add process.exit(1) after cleanup:

run().catch(async (e) => {
  logger.error(e);
  await servers?.close();
+  process.exit(1);
});

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In scripts/run-registry.ts around lines 215 to 245, the error handler currently
logs the error and closes servers but exits with code 0 which signals success;
modify the catch/finally/error block to call process.exit(1) after awaiting
servers?.close() (or ensuring servers are closed) so the process exits with a
non-zero status on failure; ensure any async cleanup completes before calling
process.exit(1).

},
async run({ codeDir, junitFilename, key, sandboxDir }, { dryRun, debug }) {
async run({ codeDir, junitFilename, key, sandboxDir, selectedTask }, { dryRun, debug }) {
const port = getPort({ key, selectedTask: selectedTask === 'e2e-tests' ? 'serve' : 'dev' });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Clarify the conditional task mapping logic.

The conditional selectedTask === 'e2e-tests' ? 'serve' : 'dev' is unclear. Since this is the e2eTestsBuild task, when would selectedTask be anything other than 'e2e-tests'? Please clarify the execution scenarios where this conditional matters.

Additionally, verify whether the static port: PORT property on line 15 is still used elsewhere, as it appears to be superseded by the dynamic port calculation.

Run this script to check for references to the static port property:


🏁 Script executed:

#!/bin/bash
# Description: Find usages of e2eTestsBuild.port to verify if static port is still needed

# Search for references to e2eTestsBuild.port
rg -nP --type=ts 'e2eTestsBuild\.port\b' -C3

Length of output: 0


I need to run additional verification scripts to understand the context better.


🏁 Script executed:

#!/bin/bash
# Search for broader port references and how e2eTestsBuild is used

# 1. Find all references to port in the e2e-tests-build.ts file
echo "=== Port references in e2e-tests-build.ts ==="
rg -n 'port' scripts/tasks/e2e-tests-build.ts

# 2. Find where e2eTestsBuild is instantiated or used
echo -e "\n=== References to e2eTestsBuild ==="
rg -nP --type=ts '\be2eTestsBuild\b' -C2

# 3. Search for selectedTask usage to understand when it differs from 'e2e-tests'
echo -e "\n=== selectedTask usage ==="
rg -nP --type=ts 'selectedTask' -C2

Length of output: 5767


🏁 Script executed:

#!/bin/bash
# Verify the exact port values and how they relate to each other

# 1. Check what PORT values are imported from serve and dev
echo "=== PORT values from serve and dev ==="
rg -n 'export.*PORT' scripts/tasks/serve.ts scripts/tasks/dev.ts

# 2. Check how e2eTestsDev is structured
echo -e "\n=== e2eTestsDev full definition ==="
cat scripts/tasks/e2e-tests-dev.ts

# 3. Verify how port is accessed at the TemplateDetails level
echo -e "\n=== Port usage in TemplateDetails context ==="
rg -nP --type=ts 'template\.port|details\.port|TemplateDetails.*port' -C2

Length of output: 545


Remove or update the static port property—it doesn't match the dynamic port calculation.

The conditional selectedTask === 'e2e-tests' ? 'serve' : 'dev' is intentional: e2eTestsBuild's run function is reused by both the 'e2e-tests' task (which depends on serve) and e2eTestsDev (which depends on dev). When the selected task is 'e2e-tests-dev', the conditional maps to 'dev' for the port calculation.

However, the static port: PORT property on line 15 is misleading. The actual port used by the run function comes from getPort(), which calculates the port as 3000 + templateIndex * 100 + taskIndex—not from the hardcoded PORT environment variable. This static property should either be removed or updated to reflect the actual dynamic calculation. Consider removing it if it's not consumed elsewhere.

🤖 Prompt for AI Agents
In scripts/tasks/e2e-tests-build.ts around lines 15 and 21, the file defines a
static port property (line 15) that is misleading because the run function uses
getPort(...) (line 21) to compute a dynamic port based on templateIndex and
taskIndex; remove the static port: PORT property if it is not used elsewhere, or
replace it with a getter/function that returns the same dynamic calculation as
getPort(...) so the exported port value always matches the actual port logic
used by run. Ensure any consumer of the static property is updated to call the
new getter or to rely on getPort(...) so there is no mismatch.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/tasks/sandbox-parts.ts (1)

259-275: Escape esbuild-loader path before embedding into generated webpack code.

The code at line 262–273 directly interpolates require.resolve() output into a template string without escaping. On Windows, require.resolve('../../node_modules/esbuild-loader') returns a path with backslashes (e.g., C:\path\to\esbuild-loader), which when embedded as loader: '${esbuildLoaderPath}' produces invalid JavaScript string literals with unintended escape sequences. This causes babelParse() at line 300 to fail on Windows builds.

Wrap the resolved path with JSON.stringify() to ensure proper escaping:

  const require = createRequire(import.meta.url);
  const esbuildLoaderPath = require.resolve('../../node_modules/esbuild-loader');
+  const esbuildLoaderPathLiteral = JSON.stringify(esbuildLoaderPath);
  const webpackFinalCode = `
  (config) => ({
    ...config,
    module: {
      ...config.module,
      rules: [
        // Ensure esbuild-loader applies to all files in ./template-stories
        {
          test: [/\\/template-stories\\//],
          exclude: [/\\.mdx$/],
-          loader: '${esbuildLoaderPath}',
+          loader: ${esbuildLoaderPathLiteral},
🧹 Nitpick comments (2)
scripts/tasks/sandbox-parts.ts (2)

24-31: Align helper name with new root-based utility (and verify export).

You’re importing filterExistsInCodeDir from ../utils/filterExistsInRootDir. Even if that module re‑exports the symbol under this name, the mismatch between the import name and the file’s purpose is confusing and could be a compile error if the export was renamed to filterExistsInRootDir.

Consider either:

  • Renaming the import (and call sites) to filterExistsInRootDir, or
  • Re‑exporting under both names in filterExistsInRootDir.ts and importing the root‑dir name here.

This keeps naming consistent with the new root‑based workspace layout and avoids surprises if the helper’s export name changes.


1033-1038: Quote --cwd path in docs:json script to handle spaces.

The new script:

'docs:json': `DIR=$PWD; yarn --cwd ${join(ROOT_DIRECTORY, 'scripts')} jiti combine-compodoc $DIR`,

will break if ROOT_DIRECTORY contains spaces (e.g., .../My Projects/storybook), since the shell will split the --cwd argument.

Safer version:

-    'docs:json': `DIR=$PWD; yarn --cwd ${join(ROOT_DIRECTORY, 'scripts')} jiti combine-compodoc $DIR`,
+    'docs:json': `DIR=$PWD; yarn --cwd "${join(ROOT_DIRECTORY, 'scripts')}" jiti combine-compodoc $DIR`,

This keeps the --cwd argument robust regardless of where the repo is checked out.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38d10a7 and 07d59d1.

📒 Files selected for processing (2)
  • scripts/release/__tests__/version.test.ts (1 hunks)
  • scripts/tasks/sandbox-parts.ts (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: nx
🔇 Additional comments (2)
scripts/release/__tests__/version.test.ts (1)

21-28: Workspace util mock rename matches new API

Renaming the mocked function to getCodeWorkspaces keeps the test aligned with the updated workspace utility without changing behavior. Looks good as-is.

scripts/tasks/sandbox-parts.ts (1)

541-551: Improved comma handling when injecting tags into storybookTest config.

The new needsComma logic correctly treats both an empty object ({}) and a trailing comma before } as cases where no extra comma should be inserted, reducing the chance of syntactically invalid configs when augmenting storybookTest({...}).

Looks good as-is.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
scripts/tasks/test-runner-build.ts (2)

8-13: Clarify testRunnerBuild.port vs dynamic getPort usage

testRunnerBuild still exposes port: PORT (from serve.ts), but the actual runtime port is now computed via getPort({ key, selectedTask: ... }) inside run. If any caller or tooling still relies on testRunnerBuild.port as the authoritative port, it will diverge from what run uses.

Consider either:

  • Dropping the { port: number } extension from this task, or
  • Clearly documenting that port is a legacy/static hint and not authoritative, or
  • Refactoring consumers to derive the port via getPort (with key/task) instead of reading testRunnerBuild.port.

This will avoid subtle mismatches between metadata and actual behavior.

Also applies to: 16-18


16-17: Ensure key/selectedTask are always provided and consider hardening getPort usage

The dynamic port resolution via:

const port = getPort({ key, selectedTask: selectedTask === 'test-runner' ? 'serve' : 'dev' });

is consistent with the serve/dev tasks, and the waitOn call before running tests is a good safety net. The main fragility is that this assumes run is always invoked with a valid key and selectedTask:

  • If either is undefined, getPort will compute a port using an invalid template index, which may be surprising and hard to debug.
  • The conditional mapping from 'test-runner' to 'serve' is also implicit knowledge; if new task names are introduced, this may need updating.

If the Task type allows it, you might:

  • Strengthen the type for run’s first argument here (e.g., a dedicated interface that requires key and selectedTask), and/or
  • Add a small runtime assertion that key and selectedTask are defined before calling getPort, failing fast with a clear error.

This will make misconfigurations much easier to diagnose.

Also applies to: 21-21, 28-28

scripts/tasks/serve.ts (1)

4-6: Dynamic serve ports look good; double‑check key contract and builtSandboxDir relative to ROOT_DIRECTORY

The switch to getPort({ key, selectedTask: 'serve' }) in both ready and run, plus the detectFreePort guard and waitOn on tcp:127.0.0.1:${port}, makes the serve task behave consistently with the new per-sandbox port scheme.

Two things to verify:

  1. key availability
    ready({ key }) and run({ builtSandboxDir, key }, ...) now both depend on key being present and valid for getPort. If any existing caller was invoking serve without a key, this will now fail or produce an unexpected port. It’s worth confirming that all orchestrators (Nx tasks, scripts, etc.) are updated to always pass key.

  2. CWD vs path semantics
    http-server is now run with cwd: ROOT_DIRECTORY and the directory argument set to builtSandboxDir. This is fine as long as builtSandboxDir is either:

    • Absolute, or
    • Relative to ROOT_DIRECTORY.

If builtSandboxDir used to be resolved relative to some other directory, you may want to ensure all call sites now construct it with ROOT_DIRECTORY in mind, or normalize it with path.resolve(ROOT_DIRECTORY, builtSandboxDir).

Nothing here looks blocking, but a quick check of those assumptions would prevent subtle CI issues.

Also applies to: 17-19, 21-29, 35-36

scripts/tasks/dev.ts (1)

16-18: Port‑driven dev flow is consistent; confirm callers always provide the expected port

The refactor to make dev fully port-driven (ready({ port }), run({ sandboxDir, port }, ...), and using that port for detectFreePort, waitOn, and bench metrics) lines up with the new dynamic port orchestration.

A couple of points to validate:

  • Caller contract: All existing invocation sites need to provide a concrete port now (most likely via getPort). If any older code was relying on dev inferring its own port (e.g., via a constant), it will now misbehave. Strengthening the type of the first argument to require port (or adding a short runtime assertion) would help catch misuses early.

  • Readiness semantics: ready({ port }) returns true when detectFreePort(port) !== port, i.e., something is already listening on that port. This matches the usual “service is up” meaning, and run will then no-op when the port is busy. Just confirm that your task runner never calls run when ready is already true for this task, so you don’t end up returning a controller that isn’t tied to any process.

Functionally this looks solid; these are mostly about making the contract explicit and future‑proof.

Also applies to: 19-23, 35-50, 52-59

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07d59d1 and 1ead2a8.

📒 Files selected for processing (3)
  • scripts/tasks/dev.ts (1 hunks)
  • scripts/tasks/serve.ts (2 hunks)
  • scripts/tasks/test-runner-build.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
scripts/tasks/test-runner-build.ts (2)
scripts/sandbox/utils/getPort.ts (1)
  • getPort (4-9)
scripts/ecosystem-ci/before-test.js (1)
  • sandboxDir (34-34)
scripts/tasks/serve.ts (1)
scripts/sandbox/utils/getPort.ts (1)
  • getPort (4-9)
scripts/tasks/dev.ts (1)
scripts/ecosystem-ci/before-test.js (1)
  • sandboxDir (34-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: nx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build Internal-facing build tooling & test updates ci:normal

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants