Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 70 additions & 53 deletions scripts/build/utils/generate-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import type { BuildEntries } from './entry-utils';

const DIR_CODE = join(import.meta.dirname, '..', '..', '..', 'code');

const MAX_DTS_ATTEMPTS = 2;
const RETRY_DELAY_MS = 500;

export async function generateTypesFiles(cwd: string, data: BuildEntries) {
const DIR_CWD = cwd;
const DIR_REL = relative(DIR_CODE, DIR_CWD);
Expand All @@ -27,64 +30,78 @@ export async function generateTypesFiles(cwd: string, data: BuildEntries) {
await Promise.all(
dtsEntries.map(async (entryPoint) => {
return limited(async () => {
let timer: ReturnType<typeof setTimeout> | undefined;
const dtsProcess = spawn(
`"${join(ROOT_DIRECTORY, 'node_modules', '.bin', 'jiti')}"`,
[`"${join(import.meta.dirname, 'dts-process.ts')}"`, `"${entryPoint}"`],
{
shell: true,
cwd: DIR_CWD,
stdio: ['ignore', 'inherit', 'pipe'],
}
);
processes.push(dtsProcess);
for (let attempt = 1; attempt <= MAX_DTS_ATTEMPTS; attempt++) {
let timer: ReturnType<typeof setTimeout> | undefined;
const dtsProcess = spawn(
`"${join(ROOT_DIRECTORY, 'node_modules', '.bin', 'jiti')}"`,
[`"${join(import.meta.dirname, 'dts-process.ts')}"`, `"${entryPoint}"`],
{
shell: true,
cwd: DIR_CWD,
stdio: ['ignore', 'inherit', 'pipe'],
}
);
processes.push(dtsProcess);

// Filter stderr to exclude messages containing "are imported from external module", which is an ignorable warning from rollup
dtsProcess.stderr?.on('data', (data) => {
const message = data.toString();
if (!message.includes('are imported from external module')) {
process.stderr.write(data);
}
});
// Filter stderr to exclude messages containing "are imported from external module", which is an ignorable warning from rollup
dtsProcess.stderr?.on('data', (data) => {
const message = data.toString();
if (!message.includes('are imported from external module')) {
process.stderr.write(data);
}
});

await Promise.race([
new Promise((resolve) => {
dtsProcess.on('exit', () => {
resolve(void 0);
});
dtsProcess.on('error', () => {
resolve(void 0);
});
dtsProcess.on('close', () => {
resolve(void 0);
});
}),
new Promise((resolve) => {
timer = setTimeout(() => {
console.log('⌛ Timed out generating d.ts files for', entryPoint);
await Promise.race([
new Promise((resolve) => {
dtsProcess.on('exit', () => {
resolve(void 0);
});
dtsProcess.on('error', () => {
resolve(void 0);
});
dtsProcess.on('close', () => {
resolve(void 0);
});
}),
new Promise((resolve) => {
timer = setTimeout(() => {
console.log('⌛ Timed out generating d.ts files for', entryPoint);

dtsProcess.kill(408); // timed out
resolve(void 0);
}, 120000);
}),
]);
dtsProcess.kill(408); // timed out

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if this pattern (kill(408)) exists elsewhere in the codebase and whether it's intentional
rg -n 'kill\(408\)' --type ts

Repository: storybookjs/storybook

Length of output: 156


🏁 Script executed:

cat -n scripts/build/utils/generate-types.ts | sed -n '50,85p'

Repository: storybookjs/storybook

Length of output: 1569


🏁 Script executed:

# Check Node.js documentation or comments about kill() and signal values
rg -B3 -A3 'kill\(408\)' scripts/build/utils/generate-types.ts

Repository: storybookjs/storybook

Length of output: 314


🏁 Script executed:

# Check if there are any TypeScript type definitions or comments in the file about process signals
rg -i 'signal|timeout' scripts/build/utils/generate-types.ts | head -20

Repository: storybookjs/storybook

Length of output: 273


Replace kill(408) with a valid Unix signal.

The value 408 is not a recognized Unix signal and will be silently ignored, causing the timeout mechanism to fail. Standard signals are 1–31 (e.g., SIGTERM = 15, SIGKILL = 9). Use 'SIGTERM' to gracefully terminate the process.

Suggested fix
-                dtsProcess.kill(408); // timed out
+                dtsProcess.kill('SIGTERM'); // timed out
📝 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
dtsProcess.kill(408); // timed out
dtsProcess.kill('SIGTERM'); // timed out
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/build/utils/generate-types.ts` at line 70, The timeout branch
currently calls dtsProcess.kill(408) using an invalid numeric signal; update the
timeout handler in generate-types.ts to call dtsProcess.kill('SIGTERM') (use the
string signal name) so the child is terminated with a valid Unix signal;
optionally follow up by checking the process state or falling back to
dtsProcess.kill('SIGKILL') if the graceful SIGTERM does not stop the process.

resolve(void 0);
}, 120000);
}),
]);

if (timer) {
clearTimeout(timer);
}
if (timer) {
clearTimeout(timer);
}

if (dtsProcess.exitCode !== 0) {
console.error(
'\n❌ Generating types for',
picocolors.cyan(relative(cwd, entryPoint)),
' failed'
);
// If any fail, kill all the other processes and exit (bail)
processes.forEach((p) => p.kill());
processes = [];
process.exit(dtsProcess.exitCode || 1);
} else if (!process.env.CI) {
console.log('✅ Generated types for', picocolors.cyan(join(DIR_REL, entryPoint)));
if (dtsProcess.exitCode !== 0) {
if (attempt < MAX_DTS_ATTEMPTS) {

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.

If you reach max attempts, could it be worth printing an error message that says "This part of the build is known to be flaky, but failed ${MAX_ATTEMPTS} times in a row. Consider re-running the CI job."?

It helps external human contributors if we point out, in CI failures, which ones we suspect to be flaky vs which ones are probably caused by their code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The build might also actually be broken @Sidnioulz

// Race: parallel DTS can read a .d.ts another process is still writing → invalid. Retry + delay usually fixes (flake in core:compile:production since #33759).
console.warn(
`⚠️ DTS failed for ${picocolors.cyan(relative(cwd, entryPoint))}, retrying (${attempt}/${MAX_DTS_ATTEMPTS})...`
);
processes = processes.filter((p) => p !== dtsProcess);
await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
continue;
}
console.error(
'\n❌ Generating types for',
picocolors.cyan(relative(cwd, entryPoint)),
' failed'
);
// If any fail after all retries, kill all the other processes and exit (bail)
processes.forEach((p) => p.kill());
processes = [];
process.exit(dtsProcess.exitCode || 1);
}

if (!process.env.CI) {
console.log('✅ Generated types for', picocolors.cyan(join(DIR_REL, entryPoint)));
}
break;
}
});
})
Expand Down
Loading