Skip to content

Internal: Repair local check command#33493

Merged
ndelangen merged 1 commit into
nextfrom
sidnioulz/issue-33432
Jan 9, 2026
Merged

Internal: Repair local check command#33493
ndelangen merged 1 commit into
nextfrom
sidnioulz/issue-33432

Conversation

@Sidnioulz
Copy link
Copy Markdown
Contributor

@Sidnioulz Sidnioulz commented Jan 9, 2026

What I did

Continuation of the yarn build fix PR. This PR fixes yarn check, also improves the handling of --watch and implements a global watch mode on top of TS diagnostics using watchpack (which is already a direct dep).

I settled on this approach to watch mode to ensure that we never forget to check a package that depends on types from another package. It's slow, but reliable and reasonably fast to implement.

Checklist for Contributors

Testing

Manual testing

cd code
yarn check
yarn check --watch
yarn check storybook
yarn check --watch storybook addon-docs

Documentation

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 watch mode to package checking for interactive development workflows.
  • Chores

    • Enhanced error handling in package building with improved error reporting.
    • Improved validation for sandbox template configuration with required parameter enforcement.

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

@Sidnioulz Sidnioulz added build Internal-facing build tooling & test updates ci:normal internal-qa This issue was reported by the Storybook team, potentially on unreleased or prerelease software. labels Jan 9, 2026
@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Jan 9, 2026

View your CI Pipeline Execution ↗ for commit 4daf489

Command Status Duration Result
nx run-many -t compile,check,knip,test,pretty-d... ✅ Succeeded 8m 39s View ↗

☁️ Nx Cloud last updated this comment at 2026-01-09 13:08:05 UTC

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

Three build scripts are modified: build-package.ts adds top-level error handling with stderr output and exit code 1; check-package.ts is reworked to introduce watch mode via Watchpack with dynamic task mapping, improved CLI options (--all, --watch, --no-watch), and per-package parallel execution; get-sandbox-dir.ts changes --template from optional to required.

Changes

Cohort / File(s) Summary
Error Handling
scripts/build-package.ts
Adds top-level catch handler to run() invocation that writes errors to stderr and exits with code 1
Watch Mode & Package Checking
scripts/check-package.ts
Major refactoring: introduces dynamic per-package task map with CLI flag suffixes; adds --all, --watch, --no-watch CLI options; implements Watchpack-based file monitoring with parallel per-package check execution; replaces single-pass aggregation with interactive watch workflow; improves error handling for TS config loading and watch mode runtime errors
Validation Changes
scripts/get-sandbox-dir.ts
Makes --template option required instead of optional, moving validation from runtime to parse-time

Sequence Diagram(s)

sequenceDiagram
    participant User as User/CLI
    participant Script as check-package.ts
    participant Watchpack as Watchpack
    participant Runner as Package Runner
    participant Checks as Package Checks

    User->>Script: yarn exec jiti check-package.ts --watch
    Script->>Script: Parse CLI args & collect TS files per package
    Script->>Watchpack: Initialize with aggregated file list
    Watchpack->>Script: Initial scan complete
    Script->>Runner: Run checks in parallel per package
    Runner->>Checks: Execute package check
    Checks-->>Runner: Results with per-package prefix
    Script->>User: Output status + results
    
    Note over Watchpack,User: Watch mode active...
    Watchpack->>Script: File change detected
    Script->>Script: Emit file change notification
    Script->>Runner: Trigger re-check for affected package
    Runner->>Checks: Execute package check
    Checks-->>Runner: Updated results
    Script->>User: Output changes + new results
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #33465 — The check-package.ts refactoring directly addresses watch-mode implementation, CLI parsing improvements, and package checking workflow issues reported in this issue.

Possibly related PRs

  • #33445 — Both PRs modify scripts/build-package.ts with complementary changes to CLI option handling and error management.
✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link
Copy Markdown
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

🤖 Fix all issues with AI agents
In @scripts/check-package.ts:
- Around line 193-221: The current use of selection.forEach(async (v) => { ...
}) launches all checks concurrently causing interleaved output and lack of
sequencing; replace the forEach with a sequential for...of loop (for (const v of
selection) { ... }) and await the child process (await execaCommand(...)) after
wiring up sub.stdout/.stderr handlers (symbols: selection.forEach -> replace,
lastName remains for labeling, sub = execaCommand(...) -> await sub, watchMode
ternary remove or ensure it's false since you're in non-watch branch) so each
package finishes before the next starts and the dead `${watchMode ? ' --watch' :
''}` fragment is removed.
🧹 Nitpick comments (6)
scripts/get-sandbox-dir.ts (1)

8-10: Update type to reflect required option.

Since --template is now a required option (line 21), the type should reflect this to maintain type consistency and eliminate the implicit undefined possibility.

Suggested fix
 type RunOptions = {
-  template?: string;
+  template: string;
 };
scripts/check-package.ts (5)

4-5: Consider consolidating path imports.

The file imports from both node:path (lines 4-5) and path (line 10). Consider consolidating these for consistency.

Suggested consolidation
-import { join } from 'node:path';
-import { relative } from 'node:path';
+import { join, relative, resolve } from 'node:path';

 import { program } from 'commander';
 // eslint-disable-next-line depend/ban-dependencies
 import { execaCommand } from 'execa';
-import { resolve } from 'path';

Also applies to: 10-10


174-185: Concurrent runAllChecks invocations possible during rapid file changes.

When runAllChecks() is called (lines 174, 184), it's not awaited. If a file change occurs while a check is still running, a new runAllChecks will start concurrently, leading to interleaved output.

Consider adding a guard to prevent overlapping check runs:

Suggested fix with running guard
+    let isRunning = false;
+    let pendingRun = false;
+
     async function runAllChecks() {
+      if (isRunning) {
+        pendingRun = true;
+        return;
+      }
+      isRunning = true;
+
       const timestamp = new Date().toLocaleTimeString();
       // ... existing check logic ...
+
+      isRunning = false;
+      if (pendingRun) {
+        pendingRun = false;
+        runAllChecks();
+      }
     }

     // Run initial check then watch all files
-    runAllChecks();
+    await runAllChecks();

158-167: Consider typing the caught error more specifically.

Using error: any bypasses TypeScript's type checking. Since execaCommand throws ExecaError which has typed stdout and stderr properties, consider using a type guard or the execa error type. As per coding guidelines, TypeScript strict mode is preferred.

Suggested improvement
-        } catch (error: any) {
+        } catch (error: unknown) {
           const prefix = `\n\n${picocolors.cyan(v.name)}:\n`;
           process.stdout.write(prefix);
-          if (error.stdout) {
-            process.stdout.write(error.stdout);
+          if (error && typeof error === 'object' && 'stdout' in error && error.stdout) {
+            process.stdout.write(String(error.stdout));
           }
-          if (error.stderr) {
-            process.stderr.write(error.stderr);
+          if (error && typeof error === 'object' && 'stderr' in error && error.stderr) {
+            process.stderr.write(String(error.stderr));
           }
         }

67-72: Remove redundant program.opts() call inside the loop.

The opts variable is already obtained on line 64. The inner declaration on line 68 shadows it and makes an unnecessary repeated call.

Suggested fix
   Object.keys(tasks).forEach((key) => {
-    const opts = program.opts();
     // checks if a flag is passed e.g. yarn check addon-docs --watch
     const containsFlag = program.args.includes(tasks[key].suffix);
     tasks[key].value = containsFlag || opts.all;
   });

76-103: Consider adding onCancel handler for consistent behavior with build-package.ts.

The build-package.ts file includes an onCancel handler (line 141) to exit gracefully when the user cancels the prompt. This file doesn't have one, which could lead to inconsistent behavior or unhandled promise rejections.

Suggested fix
-    ]).then(({ watch, todo }: { watch: boolean; todo: Array<string> }) => {
+    ], { onCancel: () => process.exit(0) }).then(({ watch, todo }: { watch: boolean; todo: Array<string> }) => {
       watchMode = watch;
       return todo?.map((key) => tasks[key]);
     });
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83120e5 and 4daf489.

📒 Files selected for processing (3)
  • scripts/build-package.ts
  • scripts/check-package.ts
  • scripts/get-sandbox-dir.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx,json,md,html,css,scss}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Format code using Prettier with yarn prettier --write <file>

Files:

  • scripts/check-package.ts
  • scripts/get-sandbox-dir.ts
  • scripts/build-package.ts
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Run ESLint checks using yarn lint:js:cmd <file> or the full command cross-env NODE_ENV=production eslint --cache --cache-location=../.cache/eslint --ext .js,.jsx,.json,.html,.ts,.tsx,.mjs --report-unused-disable-directives to fix linting errors before committing

Files:

  • scripts/check-package.ts
  • scripts/get-sandbox-dir.ts
  • scripts/build-package.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Enable TypeScript strict mode across all packages

Files:

  • scripts/check-package.ts
  • scripts/get-sandbox-dir.ts
  • scripts/build-package.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx,js,jsx}: Export functions from modules if they need to be tested
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • scripts/check-package.ts
  • scripts/get-sandbox-dir.ts
  • scripts/build-package.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-22T22:03:40.123Z
Learning: Applies to **/*.{js,jsx,json,html,ts,tsx,mjs} : Run ESLint checks using `yarn lint:js:cmd <file>` or the full command `cross-env NODE_ENV=production eslint --cache --cache-location=../.cache/eslint --ext .js,.jsx,.json,.html,.ts,.tsx,.mjs --report-unused-disable-directives` to fix linting errors before committing
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-22T22:03:40.123Z
Learning: Applies to **/*.{ts,tsx} : Enable TypeScript strict mode across all packages
📚 Learning: 2025-12-22T22:03:40.123Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-22T22:03:40.123Z
Learning: Applies to **/*.{ts,tsx} : Enable TypeScript strict mode across all packages

Applied to files:

  • scripts/check-package.ts
📚 Learning: 2025-12-22T22:03:40.123Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-22T22:03:40.123Z
Learning: Applies to **/*.{js,jsx,json,html,ts,tsx,mjs} : Run ESLint checks using `yarn lint:js:cmd <file>` or the full command `cross-env NODE_ENV=production eslint --cache --cache-location=../.cache/eslint --ext .js,.jsx,.json,.html,.ts,.tsx,.mjs --report-unused-disable-directives` to fix linting errors before committing

Applied to files:

  • scripts/check-package.ts
⏰ 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). (4)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest
  • GitHub Check: nx
  • GitHub Check: nx
🔇 Additional comments (3)
scripts/build-package.ts (1)

185-188: LGTM! Proper top-level error handling added.

The catch handler appropriately surfaces unhandled promise rejections to stderr and exits with a non-zero code, ensuring build failures are properly reported to CI and calling processes.

scripts/get-sandbox-dir.ts (1)

21-21: Good improvement to enforce template requirement at parse time.

Using requiredOption ensures validation happens during argument parsing rather than at runtime, providing clearer error messages to users.

scripts/check-package.ts (1)

225-228: LGTM! Error handling is consistent with the build-package.ts pattern.

Comment thread scripts/check-package.ts
Comment on lines +193 to +221
selection.forEach(async (v) => {
const script = join(ROOT_DIRECTORY, 'scripts', 'check', 'check-package.ts');
const command = `yarn exec jiti ${script}`;

const cwd = resolve(__dirname, '..', 'code', v.location);
const sub = execaCommand(`${command}${watchMode ? ' --watch' : ''}`, {
cwd,
env: {
NODE_ENV: 'production',
},
});

sub.stdout?.on('data', (data) => {
if (lastName !== v.name) {
const prefix = `${picocolors.cyan(v.name)}:\n`;
process.stdout.write(prefix);
}
lastName = v.name;
process.stdout.write(data);
});
sub.stderr?.on('data', (data) => {
if (lastName !== v.name) {
const prefix = `${picocolors.cyan(v.name)}:\n`;
process.stdout.write(prefix);
}
lastName = v.name;
process.stderr.write(data);
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Uncontrolled concurrency with forEach + async may cause race conditions in output.

Using forEach with an async callback fires off all package checks concurrently without awaiting. While the process stays alive due to the child process streams, this leads to:

  1. Interleaved output from multiple packages (the lastName guard helps but isn't sufficient for true interleaving)
  2. No guarantee of completion order
  3. If an error occurs, subsequent packages still run

If sequential execution is intended (as the comment on line 192 suggests), consider using a for...of loop with await. If parallel execution is acceptable, the current approach works but the comment should be updated.

Additionally, line 198's ${watchMode ? ' --watch' : ''} is dead code since we're in the else branch where watchMode is falsy.

Suggested fix for sequential execution
   } else {
     // If watch mode is off, check each individual package sequentially.
-    selection.forEach(async (v) => {
+    for (const v of selection) {
       const script = join(ROOT_DIRECTORY, 'scripts', 'check', 'check-package.ts');
       const command = `yarn exec jiti ${script}`;

       const cwd = resolve(__dirname, '..', 'code', v.location);
-      const sub = execaCommand(`${command}${watchMode ? ' --watch' : ''}`, {
+      const sub = await execaCommand(command, {
         cwd,
         env: {
           NODE_ENV: 'production',
         },
       });

-      sub.stdout?.on('data', (data) => {
-        if (lastName !== v.name) {
-          const prefix = `${picocolors.cyan(v.name)}:\n`;
-          process.stdout.write(prefix);
-        }
-        lastName = v.name;
-        process.stdout.write(data);
-      });
-      sub.stderr?.on('data', (data) => {
-        if (lastName !== v.name) {
-          const prefix = `${picocolors.cyan(v.name)}:\n`;
-          process.stdout.write(prefix);
-        }
-        lastName = v.name;
-        process.stderr.write(data);
-      });
-    });
+      if (sub.stdout) {
+        const prefix = `${picocolors.cyan(v.name)}:\n`;
+        process.stdout.write(prefix);
+        process.stdout.write(sub.stdout);
+      }
+      if (sub.stderr) {
+        process.stderr.write(sub.stderr);
+      }
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
selection.forEach(async (v) => {
const script = join(ROOT_DIRECTORY, 'scripts', 'check', 'check-package.ts');
const command = `yarn exec jiti ${script}`;
const cwd = resolve(__dirname, '..', 'code', v.location);
const sub = execaCommand(`${command}${watchMode ? ' --watch' : ''}`, {
cwd,
env: {
NODE_ENV: 'production',
},
});
sub.stdout?.on('data', (data) => {
if (lastName !== v.name) {
const prefix = `${picocolors.cyan(v.name)}:\n`;
process.stdout.write(prefix);
}
lastName = v.name;
process.stdout.write(data);
});
sub.stderr?.on('data', (data) => {
if (lastName !== v.name) {
const prefix = `${picocolors.cyan(v.name)}:\n`;
process.stdout.write(prefix);
}
lastName = v.name;
process.stderr.write(data);
});
});
for (const v of selection) {
const script = join(ROOT_DIRECTORY, 'scripts', 'check', 'check-package.ts');
const command = `yarn exec jiti ${script}`;
const cwd = resolve(__dirname, '..', 'code', v.location);
const sub = await execaCommand(command, {
cwd,
env: {
NODE_ENV: 'production',
},
});
if (sub.stdout) {
const prefix = `${picocolors.cyan(v.name)}:\n`;
process.stdout.write(prefix);
process.stdout.write(sub.stdout);
}
if (sub.stderr) {
process.stderr.write(sub.stderr);
}
}
🤖 Prompt for AI Agents
In @scripts/check-package.ts around lines 193 - 221, The current use of
selection.forEach(async (v) => { ... }) launches all checks concurrently causing
interleaved output and lack of sequencing; replace the forEach with a sequential
for...of loop (for (const v of selection) { ... }) and await the child process
(await execaCommand(...)) after wiring up sub.stdout/.stderr handlers (symbols:
selection.forEach -> replace, lastName remains for labeling, sub =
execaCommand(...) -> await sub, watchMode ternary remove or ensure it's false
since you're in non-watch branch) so each package finishes before the next
starts and the dead `${watchMode ? ' --watch' : ''}` fragment is removed.

@storybook-app-bot
Copy link
Copy Markdown

Package Benchmarks

Commit: 4daf489, ran on 9 January 2026 at 13:08:31 UTC

The following packages have significant changes to their size or dependencies:

@storybook/nextjs

Before After Difference
Dependency count 534 538 🚨 +4 🚨
Self size 646 KB 646 KB 0 B
Dependency size 59.16 MB 59.19 MB 🚨 +27 KB 🚨
Bundle Size Analyzer Link Link

@storybook/react-webpack5

Before After Difference
Dependency count 274 278 🚨 +4 🚨
Self size 24 KB 24 KB 🚨 +12 B 🚨
Dependency size 44.09 MB 44.12 MB 🚨 +28 KB 🚨
Bundle Size Analyzer Link Link

@storybook/preset-create-react-app

Before After Difference
Dependency count 64 68 🚨 +4 🚨
Self size 32 KB 32 KB 🚨 +30 B 🚨
Dependency size 5.95 MB 5.98 MB 🚨 +28 KB 🚨
Bundle Size Analyzer Link Link

@storybook/preset-react-webpack

Before After Difference
Dependency count 166 170 🚨 +4 🚨
Self size 18 KB 18 KB 🚨 +28 B 🚨
Dependency size 31.23 MB 31.26 MB 🚨 +28 KB 🚨
Bundle Size Analyzer Link Link

@ndelangen ndelangen merged commit dc79979 into next Jan 9, 2026
73 of 76 checks passed
@ndelangen ndelangen deleted the sidnioulz/issue-33432 branch January 9, 2026 13:41
@github-actions github-actions Bot mentioned this pull request Jan 9, 2026
19 tasks
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 internal-qa This issue was reported by the Storybook team, potentially on unreleased or prerelease software.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants