Skip to content

Conversation

@yannbf
Copy link
Member

@yannbf yannbf commented Nov 11, 2025

Closes #

What I did

This PR makes it so that the infamous EADDRESS error won't occur anymore.
p.s. Needs testing on Windows

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

  • Chores
    • Optimized local registry startup with intelligent port management. The system now automatically identifies and removes processes using development ports 6001 and 6002 before initialization, reducing startup conflicts and ensuring smoother, more reliable development environment setup.

@yannbf yannbf self-assigned this Nov 11, 2025
@yannbf yannbf added build Internal-facing build tooling & test updates ci:normal labels Nov 11, 2025
@nx-cloud
Copy link

nx-cloud bot commented Nov 11, 2025

View your CI Pipeline Execution ↗ for commit 3907cd9

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

☁️ Nx Cloud last updated this comment at 2025-11-11 13:45:51 UTC

@yannbf yannbf marked this pull request as ready for review November 11, 2025 13:36
@yannbf yannbf added the empathy label Nov 11, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 11, 2025

📝 Walkthrough

Walkthrough

Adds a new cross-platform utility function to terminate processes bound to specific ports, then integrates it into the registry startup script to clean ports 6001 and 6002 before Verdaccio initialization. The utility handles both Windows and Unix-like systems with platform-specific command execution, error handling, and logging.

Changes

Cohort / File(s) Summary
New process cleanup utility
scripts/utils/kill-process-on-port.ts
Introduces killProcessOnPort() function with platform-aware process discovery using netstat -ano on Windows and lsof -ti on Unix-like systems. Terminates discovered processes via taskkill /PID or kill -9 respectively. Includes defensive error handling, logging of killed PIDs, and 500ms delay post-termination.
Registry startup integration
scripts/run-registry.ts
Imports and invokes killProcessOnPort for ports 6001 and 6002 in startVerdaccio() to ensure clean server initialization.

Sequence Diagram

sequenceDiagram
    participant User
    participant startVerdaccio
    participant killProcessOnPort
    participant OS Command
    participant Verdaccio

    User->>startVerdaccio: Call startVerdaccio()
    startVerdaccio->>killProcessOnPort: killProcessOnPort(6001)
    killProcessOnPort->>OS Command: Execute process discovery<br/>(netstat/lsof)
    OS Command-->>killProcessOnPort: Process PIDs (if any)
    alt Processes Found
        killProcessOnPort->>OS Command: Terminate via taskkill/kill -9
        OS Command-->>killProcessOnPort: Success
        Note over killProcessOnPort: Wait 500ms for port release
    else No Processes
        killProcessOnPort->>killProcessOnPort: Graceful no-op
    end
    killProcessOnPort->>startVerdaccio: Return
    startVerdaccio->>killProcessOnPort: killProcessOnPort(6002)
    killProcessOnPort-->>startVerdaccio: Return
    startVerdaccio->>Verdaccio: Initialize server
    Verdaccio-->>User: Server ready
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Regex validation for Windows netstat parsing: Verify the regex correctly extracts PIDs from netstat -ano output across Windows versions; edge cases with multiple processes or unusual formatting should be tested.
  • Cross-platform command reliability: Confirm lsof and netstat availability and parsing logic holds across different Unix-like systems (Linux, macOS) and Windows versions.
  • Error handling paths: Review whether swallowing all errors in try/catch blocks is appropriate and whether missing commands (e.g., lsof not in PATH) need more explicit handling.
  • Port release timing: Validate that the 500ms delay is sufficient across different OS/system configurations for port unbinding.
✨ Finishing touches
  • 📝 Generate docstrings

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: 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 07e8036 and 3907cd9.

📒 Files selected for processing (2)
  • scripts/run-registry.ts (2 hunks)
  • scripts/utils/kill-process-on-port.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,json,html,ts,tsx,mjs}

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

**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier formatting on changed files before committing
Run ESLint on changed files and fix all errors/warnings before committing (use yarn lint:js:cmd <file>)

Files:

  • scripts/utils/kill-process-on-port.ts
  • scripts/run-registry.ts
**/*.{ts,tsx,js,jsx,mjs}

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

Export functions from modules when they need to be unit-tested

Files:

  • scripts/utils/kill-process-on-port.ts
  • scripts/run-registry.ts
scripts/**/*.{ts,js,mjs}

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

In Node.js scripts, use storybook/internal/node-logger instead of console.*

Files:

  • scripts/utils/kill-process-on-port.ts
  • scripts/run-registry.ts
{code/**,scripts/**}/**/*.{ts,tsx,js,jsx,mjs}

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

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/utils/kill-process-on-port.ts
  • scripts/run-registry.ts
🧬 Code graph analysis (1)
scripts/run-registry.ts (1)
scripts/utils/kill-process-on-port.ts (1)
  • killProcessOnPort (7-54)
🪛 ast-grep (0.39.9)
scripts/utils/kill-process-on-port.ts

[warning] 15-15: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(TCP.*:${port}.*LISTENING\\s+(\\d+), 'i')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

Comment on lines +14 to +24
const { stdout } = await execa('netstat', ['-ano']);
const lines = stdout.split('\n');
const regex = new RegExp(`TCP.*:${port}.*LISTENING\\s+(\\d+)`, 'i');

for (const line of lines) {
const match = line.match(regex);
if (match && match[1]) {
pids.push(match[1]);
}
}
} catch {
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

Fix Windows PID parsing to avoid killing unrelated ports

The current regex matches any substring :${port}, so asking for port 6001 will also match 60012/60010 and can terminate the wrong process. Split the netstat columns and ensure the local address ends with the exact port before you add the PID to the kill list.

-        const { stdout } = await execa('netstat', ['-ano']);
-        const lines = stdout.split('\n');
-        const regex = new RegExp(`TCP.*:${port}.*LISTENING\\s+(\\d+)`, 'i');
-
-        for (const line of lines) {
-          const match = line.match(regex);
-          if (match && match[1]) {
-            pids.push(match[1]);
-          }
-        }
+        const { stdout } = await execa('netstat', ['-ano']);
+        const lines = stdout.split('\n');
+
+        for (const line of lines) {
+          const parts = line.trim().split(/\s+/);
+          if (
+            parts.length >= 5 &&
+            parts[0]?.toUpperCase().startsWith('TCP') &&
+            parts[1]?.endsWith(`:${port}`) &&
+            parts[3]?.toUpperCase() === 'LISTENING'
+          ) {
+            pids.push(parts[4]);
+          }
+        }
📝 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
const { stdout } = await execa('netstat', ['-ano']);
const lines = stdout.split('\n');
const regex = new RegExp(`TCP.*:${port}.*LISTENING\\s+(\\d+)`, 'i');
for (const line of lines) {
const match = line.match(regex);
if (match && match[1]) {
pids.push(match[1]);
}
}
} catch {
const { stdout } = await execa('netstat', ['-ano']);
const lines = stdout.split('\n');
for (const line of lines) {
const parts = line.trim().split(/\s+/);
if (
parts.length >= 5 &&
parts[0]?.toUpperCase().startsWith('TCP') &&
parts[1]?.endsWith(`:${port}`) &&
parts[3]?.toUpperCase() === 'LISTENING'
) {
pids.push(parts[4]);
}
}
} catch {
🧰 Tools
🪛 ast-grep (0.39.9)

[warning] 15-15: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(TCP.*:${port}.*LISTENING\\s+(\\d+), 'i')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🤖 Prompt for AI Agents
In scripts/utils/kill-process-on-port.ts around lines 14–24, the netstat parsing
regex can match substrings of ports (e.g., 6001 matching 60010), so replace the
regex approach with column-based parsing: split each netstat line on whitespace,
take the Local Address column (the second column), check that it ends exactly
with `:${port}` (so IPv4/IPV6 forms like [::]:80 still match), and only then
read the PID from the last column (or columns[columns.length-1]) after
validating it is numeric before pushing to pids; this prevents killing processes
bound to ports that merely contain the queried port as a substring.

Comment on lines +37 to +45
if (pids.length > 0) {
console.log(`☠️ killing process(es) on port ${port}: ${pids.join(', ')}`);

if (isWindows) {
await Promise.all(
pids.map((pid) => execa('taskkill', ['/PID', pid, '/F']).catch(() => {}))
);
} else {
await Promise.all(pids.map((pid) => execa('kill', ['-9', pid]).catch(() => {})));
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Swap to Storybook’s node logger

Per our scripts guidelines we should log through storybook/internal/node-logger, not console.log. Please route this message through the shared logger for consistency. As per coding guidelines

 import { execa } from 'execa';
+import { logger } from 'storybook/internal/node-logger';
@@
-      console.log(`☠️ killing process(es) on port ${port}: ${pids.join(', ')}`);
+      logger.info(`☠️ killing process(es) on port ${port}: ${pids.join(', ')}`);
📝 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
if (pids.length > 0) {
console.log(`☠️ killing process(es) on port ${port}: ${pids.join(', ')}`);
if (isWindows) {
await Promise.all(
pids.map((pid) => execa('taskkill', ['/PID', pid, '/F']).catch(() => {}))
);
} else {
await Promise.all(pids.map((pid) => execa('kill', ['-9', pid]).catch(() => {})));
import { execa } from 'execa';
import { logger } from 'storybook/internal/node-logger';
// ... other code ...
if (pids.length > 0) {
logger.info(`☠️ killing process(es) on port ${port}: ${pids.join(', ')}`);
if (isWindows) {
await Promise.all(
pids.map((pid) => execa('taskkill', ['/PID', pid, '/F']).catch(() => {}))
);
} else {
await Promise.all(pids.map((pid) => execa('kill', ['-9', pid]).catch(() => {})));
}
}
🤖 Prompt for AI Agents
In scripts/utils/kill-process-on-port.ts around lines 37 to 45, replace the
console.log call with Storybook’s shared node logger: import the logger from
'storybook/internal/node-logger' at the top of the file and use logger.info (or
logger.warn) to emit the "killing process(es) on port" message, keeping the same
string interpolation; leave the rest of the PID-killing logic unchanged and
ensure the import is added only once.

@yannbf yannbf merged commit 1d0c0da into next Nov 11, 2025
70 of 72 checks passed
@yannbf yannbf deleted the yann/kill-verdaccio-process branch November 11, 2025 14:19
@github-actions github-actions bot mentioned this pull request Nov 11, 2025
12 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 empathy

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants