Skip to content

feat: add dev script for previewing changes - #226

Open
TusharThakur04 wants to merge 6 commits into
webpack:mainfrom
TusharThakur04:feat/preview-changes
Open

feat: add dev script for previewing changes#226
TusharThakur04 wants to merge 6 commits into
webpack:mainfrom
TusharThakur04:feat/preview-changes

Conversation

@TusharThakur04

@TusharThakur04 TusharThakur04 commented Jul 30, 2026

Copy link
Copy Markdown
Member

Summary
This PR adds a dev script for previewing changes in dev environment

What kind of change does this PR introduce?
feat

Did you add tests for your changes?
no

Does this PR introduce a breaking change?
no

If relevant, what needs to be documented once your changes are merged or what have you already documented?

Use of AI

Summary by CodeRabbit

  • New Features
    • Added a development command that builds the documentation site and automatically rebuilds it when source files change.
    • Added a local preview server for viewing generated documentation during development.
    • Changes to individual pages trigger targeted updates, while broader changes trigger a full rebuild.

@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
webpack-doc-kit Ready Ready Preview Aug 15, 2026 3:32pm

Request Review

Comment thread scripts/dev/index.mjs
Comment thread scripts/dev/index.mjs Outdated
Comment thread scripts/dev/index.mjs Outdated

// --- LOCAL SERVER ---
console.log('\nStarting local server...');
spawn('npx', ['serve', './out'], { stdio: 'inherit', shell: true });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we hold on to these PIDs so if something happens we can force kill them, e.g.

const spawn = (cmd, args) => {
  const child = spawn(cmd, args, {
    stdio: 'inherit',
    windowsHide: true,
  });

  children.add(child);
  child.once('close', () => children.delete(child));
  child.once('error', () => children.delete(child));

  return child;
}

Comment thread scripts/dev/index.mjs
Comment on lines +25 to +35
args.push('-i', filePath);

// Calculate the matching output directory
const normalizedPath = filePath.replace(/\\/g, '/');
const dir = dirname(normalizedPath);

const relativeDir = dir.replace(/^pages\/?/, '');

const outPath = relativeDir ? join('./out', relativeDir) : './out';

args.push('-o', outPath);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the sidebar vanishes here ? maybe we can include it,wdyt?

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.

Can u elaborate, its just putting the modified html file into the respective ./out directly

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Pinging you on discord

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Did u reach anything for this ?!

@ryzrr

ryzrr commented Aug 6, 2026

Copy link
Copy Markdown
Member

btw @TusharThakur04 can you please recheck the package-lock file as i think it is downgrading many versions like shikhi and more

@github-actions github-actions Bot added dependencies Pull requests that update a dependency file tooling labels Aug 14, 2026
@socket-security

socket-security Bot commented Aug 14, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​vercel/​functions@​3.9.1 ⏵ 3.9.3991007499 +1100
Addedtree-kill@​1.2.21001009775100
Updatedglobals@​17.9.0 ⏵ 17.11.010010086 +196 +1100

View full report

@TusharThakur04

Copy link
Copy Markdown
Member Author

@webpack/gsoc-2026 PTAL

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR adds a dev npm script that runs a new development entrypoint. The entrypoint performs an initial DocKit build, watches pages and configured shared directories, and debounces file changes. Markdown and MDX changes trigger targeted builds. Other changes trigger full builds. The script starts a local server for ./out and cleans up tracked child processes on termination.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the added development script for previewing changes, which is the main purpose of the pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2c8d59c5-07f0-4e52-9f67-2fe75c6fe469

📥 Commits

Reviewing files that changed from the base of the PR and between c252791 and 40f6906.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • package.json
  • scripts/dev/index.mjs

Comment thread scripts/dev/index.mjs
args.push('-o', outPath);
}

await execFileAsync(docKitBin, args, { shell: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1,140p' scripts/dev/index.mjs
printf '\n--- package metadata and relevant references ---\n'
rg -n --glob 'package.json' --glob '*.mjs' --glob '*.js' 'doc-kit|runDocKit|execFileAsync|fs\.watch|scripts' . | head -200

Repository: webpack/webpack-doc-kit

Length of output: 7443


🏁 Script executed:

sed -n '1,140p' scripts/dev/index.mjs
printf '\n--- references ---\n'
rg -n --glob 'package.json' --glob '*.mjs' --glob '*.js' 'doc-kit|runDocKit|execFileAsync|fs\.watch' .

Repository: webpack/webpack-doc-kit

Length of output: 6292


🏁 Script executed:

nl -ba scripts/dev/index.mjs | sed -n '1,140p'

Repository: webpack/webpack-doc-kit

Length of output: 197


🌐 Web query:

Node.js child_process.execFile shell true argument handling shell metacharacters Windows POSIX documentation

💡 Result:

In Node.js, the child_process.execFile function spawns a command directly by default, without a shell [1][2]. When the shell option is set to true, the command is instead executed within a shell, which interprets shell metacharacters [1][3]. Key details regarding shell handling: Execution Mechanism: When shell is true, Node.js uses /bin/sh on POSIX (Unix, Linux, macOS) systems and the value of process.env.ComSpec on Windows (typically cmd.exe) [1][4]. You can also specify a custom shell by providing a string instead of a boolean [2][5]. Security Warning: The official documentation explicitly warns that if the shell option is enabled, you must not pass unsanitized user input to the function [1][2]. Because the shell interprets metacharacters (such as &, |,;, $, >, etc.), any unsanitized input containing these characters can be exploited to trigger arbitrary command execution [1][4][6]. Argument Handling: When shell is true, Node.js joins the provided arguments with spaces and passes the resulting string to the shell [7]. This behavior means that the shell will parse the entire string, including the joined arguments, which is why it is susceptible to metacharacter injection [2][7]. Platform Nuance: On Windows, the shell option is sometimes necessary to execute.bat or.cmd files, as they are not directly executable as standalone processes [3][4]. However, developers should remain aware that relying on shell execution introduces the aforementioned security risks and behavioral differences regarding how arguments are parsed [2][7].

Citations:


🏁 Script executed:

node - <<'JS'
const { execFile } = require('node:child_process');
const marker = `/tmp/coderabbit-shell-probe-${process.pid}`;
execFile('printf', ['%s', `safe; touch ${marker}`], { shell: true }, (error) => {
  const fs = require('node:fs');
  console.log(JSON.stringify({
    error: error && error.message,
    injectedCommandExecuted: fs.existsSync(marker)
  }));
  try { fs.unlinkSync(marker); } catch {}
});
JS

Repository: webpack/webpack-doc-kit

Length of output: 467


Injection (CWE-78): Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

Reachability: External

Remove shell execution from runDocKit.

fs.watch supplies a filename that reaches execFileAsync with shell: true. Shell metacharacters in a watched filename can execute an additional command during a rebuild. Invoke DocKit without a shell.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What if we use execa ? then we can remove shell: true.

Comment thread scripts/dev/index.mjs
Comment on lines +69 to +87
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
if (isBuilding) return;

isBuilding = true;

if (ext === '.md' || ext === '.mdx') {
// Wrap the single file in an array to match the new function signature
console.log(
`\nFile changed: ${fullPath} \nRunning fast partial build...`
);
await runDocKit(fullPath);
} else {
// Any other file change (jsx, css, js, mjs, png in public, etc) triggers a full build
console.log(`\nFile changed: ${fullPath} \nRunning full build...`);
await runDocKit();
}

isBuilding = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not discard rebuild requests while a build runs.

If a second change occurs after the first build starts, Line 71 returns without recording it. When the first build finishes, no timer remains for the second change. The preview can stay stale until another file change occurs.

If runDocKit rejects, Line 87 does not run. isBuilding then remains true and all later changes are skipped. Store a pending rebuild request. Reset isBuilding in finally, and catch build errors before scheduling the pending rebuild.

Comment thread scripts/dev/index.mjs Outdated
Comment on lines +107 to +137
const child = nativeSpawn(cmd, args, {
stdio: 'inherit',
shell: true,
});

children.add(child);
child.once('close', () => children.delete(child));
child.once('error', () => children.delete(child));

return child;
};

const cleanup = () => {
for (const child of children) {
if (!child.killed) {
child.kill('SIGINT');
}
}
process.exit();
};

process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
process.on('exit', () => {
for (const child of children) {
if (!child.killed) child.kill();
}
});

console.log('\nStarting local server...');
spawn('npx', ['serve', './out']);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- file inventory ---'
git ls-files scripts/dev

printf '%s\n' '--- AST outline ---'
ast-grep outline scripts/dev/index.mjs --view expanded || true

printf '%s\n' '--- relevant source ---'
cat -n scripts/dev/index.mjs | sed -n '1,180p'

printf '%s\n' '--- process API references ---'
rg -n -C 4 'spawn|execFile|execFileAsync|children|cleanup|SIGINT|SIGTERM|process\.exit|shell:' scripts/dev package.json

Repository: webpack/webpack-doc-kit

Length of output: 7925


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- npx resolution ---'
command -v npx || true
if command -v npx >/dev/null 2>&1; then
  file "$(command -v npx)" || true
fi

printf '%s\n' '--- shell child and descendant probe ---'
node --input-type=module <<'JS'
import { spawn } from 'node:child_process';

const child = spawn('sh', ['-c', 'sleep 30'], {
  stdio: 'ignore',
  shell: true,
});

const sleepPid = await new Promise((resolve, reject) => {
  const timer = setInterval(() => {
    const result = spawn('ps', ['-eo', 'pid=,ppid=,pgid=,comm=,args='], {
      stdio: ['ignore', 'pipe', 'ignore'],
    });
    let output = '';
    result.stdout.on('data', chunk => { output += chunk; });
    result.once('close', () => {
      const row = output.split('\n').find(line =>
        line.trim().endsWith('sleep 30')
      );
      if (row) {
        clearInterval(timer);
        resolve(Number(row.trim().split(/\s+/)[0]));
      }
    });
  }, 20);
  child.once('error', reject);
});

const ps = () => new Promise((resolve, reject) => {
  const result = spawn('ps', ['-o', 'pid=,ppid=,pgid=,comm=,args=', '-p', `${child.pid},${sleepPid}`], {
    stdio: ['ignore', 'pipe', 'ignore'],
  });
  let output = '';
  result.stdout.on('data', chunk => { output += chunk; });
  result.once('error', reject);
  result.once('close', () => resolve(output.trim()));
});

console.log(`tracked_pid=${child.pid}`);
console.log(`descendant_pid=${sleepPid}`);
console.log('before_signal:');
console.log(await ps());

const killResult = child.kill('SIGINT');
console.log(`kill_return=${killResult}`);
console.log(`child_killed_immediately=${child.killed}`);

await new Promise(resolve => setTimeout(resolve, 150));
console.log('after_signal:');
console.log(await ps());

try { process.kill(sleepPid, 'SIGKILL'); } catch {}
try { child.kill('SIGKILL'); } catch {}
JS

Repository: webpack/webpack-doc-kit

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- npx resolution ---'
command -v npx || true
if command -v npx >/dev/null 2>&1; then
  file "$(command -v npx)" || true
fi

printf '%s\n' '--- leftover probe processes ---'
ps -eo pid=,ppid=,pgid=,comm=,args= | awk '$0 ~ /[s]leep 30/ || $0 ~ /[n]ode --input-type=module/ {print}' || true

printf '%s\n' '--- bounded shell-child probe ---'
node --input-type=module <<'JS'
import { spawn } from 'node:child_process';

const runPs = () => new Promise((resolve) => {
  const ps = spawn('ps', ['-eo', 'pid=,ppid=,pgid=,comm=,args='], {
    stdio: ['ignore', 'pipe', 'ignore'],
  });
  let output = '';
  ps.stdout.on('data', chunk => { output += chunk; });
  ps.once('close', () => resolve(output));
});

const tracked = spawn('sh', ['-c', 'sleep 5'], {
  stdio: 'ignore',
  shell: true,
});

try {
  await new Promise(resolve => setTimeout(resolve, 100));
  const before = (await runPs()).split('\n')
    .filter(line => line.includes(` ${tracked.pid} `) || line.trim().endsWith('sleep 5'))
    .join('\n');

  console.log(`tracked_pid=${tracked.pid}`);
  console.log('before_signal:');
  console.log(before || '<no matching rows>');

  const killReturn = tracked.kill('SIGINT');
  console.log(`kill_return=${killReturn}`);
  console.log(`child_killed_immediately=${tracked.killed}`);

  await new Promise(resolve => setTimeout(resolve, 100));
  const after = (await runPs()).split('\n')
    .filter(line => line.trim().endsWith('sleep 5'))
    .join('\n');

  console.log('after_signal_descendant:');
  console.log(after || '<no sleep descendant>');
} finally {
  try { tracked.kill('SIGKILL'); } catch {}
  await new Promise(resolve => setTimeout(resolve, 100));
}
JS

Repository: webpack/webpack-doc-kit

Length of output: 7451


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- npx launcher ---'
ls -l /usr/local/bin/npx || true
head -n 8 /usr/local/bin/npx 2>/dev/null || true

printf '%s\n' '--- corrected process-group probe ---'
node --input-type=module <<'JS'
import { spawn } from 'node:child_process';

const readPs = () => new Promise(resolve => {
  const ps = spawn('ps', ['-eo', 'pid=,ppid=,pgid=,comm=,args='], {
    stdio: ['ignore', 'pipe', 'ignore'],
  });
  let text = '';
  ps.stdout.on('data', chunk => { text += chunk; });
  ps.once('close', () => resolve(text));
});

const rowsFor = (text, pids) => text.split('\n').filter(line => {
  const fields = line.trim().split(/\s+/);
  return fields.length >= 2 &&
    (pids.has(Number(fields[0])) || pids.has(Number(fields[1])));
}).join('\n');

const tracked = spawn("sh -c 'sleep 5'", [], {
  stdio: 'ignore',
  shell: true,
});

const probePids = new Set([tracked.pid]);
try {
  await new Promise(resolve => setTimeout(resolve, 100));
  const beforeText = await readPs();
  const beforeRows = beforeText.split('\n').filter(line => {
    const fields = line.trim().split(/\s+/);
    return fields.length >= 2 &&
      (Number(fields[0]) === tracked.pid || Number(fields[1]) === tracked.pid);
  });
  for (const line of beforeRows) {
    const pid = Number(line.trim().split(/\s+/)[0]);
    if (pid) probePids.add(pid);
  }

  console.log(`tracked_pid=${tracked.pid}`);
  console.log('before_signal:');
  console.log(beforeRows.join('\n') || '<no matching rows>');

  const result = tracked.kill('SIGINT');
  console.log(`kill_return=${result}`);
  console.log(`child_killed_immediately=${tracked.killed}`);

  await new Promise(resolve => setTimeout(resolve, 100));
  console.log('after_signal:');
  console.log(rowsFor(await readPs(), probePids) || '<no matching rows>');
} finally {
  for (const pid of probePids) {
    try { process.kill(pid, 'SIGKILL'); } catch {}
  }
  try { tracked.kill('SIGKILL'); } catch {}
  await new Promise(resolve => setTimeout(resolve, 100));
}
JS

Repository: webpack/webpack-doc-kit

Length of output: 667


🏁 Script executed:

#!/bin/bash
set -eu

node --input-type=module <<'JS'
import { spawn } from 'node:child_process';

const snapshot = () => new Promise(resolve => {
  const ps = spawn('ps', ['-eo', 'pid=,ppid=,pgid=,comm=,args='], {
    stdio: ['ignore', 'pipe', 'ignore'],
  });
  let text = '';
  ps.stdout.on('data', chunk => { text += chunk; });
  ps.once('close', () => resolve(text));
});

const descendantsOf = (text, rootPid) => {
  const rows = text.split('\n').map(line => {
    const fields = line.trim().split(/\s+/);
    return fields.length >= 2
      ? { pid: Number(fields[0]), ppid: Number(fields[1]), line }
      : null;
  }).filter(Boolean);

  const pids = new Set([rootPid]);
  let changed = true;
  while (changed) {
    changed = false;
    for (const row of rows) {
      if (pids.has(row.ppid) && !pids.has(row.pid)) {
        pids.add(row.pid);
        changed = true;
      }
    }
  }
  return rows.filter(row => pids.has(row.pid)).map(row => row.line).join('\n');
};

const tracked = spawn('npx', ['--no-install', '--', '/bin/sleep', '5'], {
  stdio: 'ignore',
  shell: true,
});

try {
  await new Promise(resolve => setTimeout(resolve, 300));
  console.log(`tracked_pid=${tracked.pid}`);
  console.log('before_signal:');
  console.log(descendantsOf(await snapshot(), tracked.pid) || '<no matching rows>');

  console.log(`kill_return=${tracked.kill('SIGINT')}`);
  console.log(`child_killed_immediately=${tracked.killed}`);

  await new Promise(resolve => setTimeout(resolve, 300));
  console.log('after_signal:');
  console.log(descendantsOf(await snapshot(), tracked.pid) || '<no matching rows>');
} finally {
  try { tracked.kill('SIGKILL'); } catch {}
  const remaining = (await snapshot()).split('\n');
  for (const line of remaining) {
    const fields = line.trim().split(/\s+/);
    if (fields.length >= 2 && Number(fields[1]) === tracked.pid) {
      try { process.kill(Number(fields[0]), 'SIGKILL'); } catch {}
    }
  }
  await new Promise(resolve => setTimeout(resolve, 100));
}
JS

Repository: webpack/webpack-doc-kit

Length of output: 587


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- package metadata and scripts ---'
cat -n package.json | sed -n '1,220p'

printf '%s\n' '--- serve references and platform references ---'
rg -n -i -C 3 'serve|windows|win32|linux|macos|darwin|platform|npm run dev|development environment' \
  README.md CONTRIBUTING.md docs package.json package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null || true

printf '%s\n' '--- lockfile entries for serve ---'
rg -n -C 5 '"serve"|"node_modules/serve"|bin/serve' package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null || true

Repository: webpack/webpack-doc-kit

Length of output: 50381


🌐 Web query:

Node.js child_process spawn shell true ChildProcess kill process group official documentation

💡 Result:

In Node.js, the child_process.spawn method, when used with the shell option set to true, creates a new process inside a shell [1]. Official Node.js documentation does not provide a built-in method to automatically kill a process group or a process's children when using spawn [1]. Calling child.kill on the child process instance only signals the immediate shell process spawned by Node.js, often leaving its child processes (the actual commands executed within the shell) orphaned or running [1][2]. To terminate a process group or tree, common community-supported approaches include: 1. Detached Process Group (Unix-like systems): By setting the detached option to true in the spawn options, the child process becomes the leader of a new process group [1][3]. You can then attempt to kill the entire group by passing a negative PID to process.kill (e.g., process.kill(-child.pid, signal)) [3][2]. 2. Tree-Killing Utilities: For cross-platform support and easier management, many developers use third-party packages such as "tree-kill," which are designed to recursively identify and terminate all processes in a process tree started by a parent [4]. The official Node.js documentation specifically cautions that when running processes in a shell, sub-processes of those child processes may not be signaled when attempting to kill the parent [1]. Always exercise caution when using the shell option due to security risks associated with passing unsanitized user input [1].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

node --input-type=module <<'JS'
import { spawn } from 'node:child_process';

const snapshot = () => new Promise(resolve => {
  const ps = spawn('ps', ['-eo', 'pid=,ppid=,pgid=,comm=,args='], {
    stdio: ['ignore', 'pipe', 'ignore'],
  });
  let text = '';
  ps.stdout.on('data', chunk => { text += chunk; });
  ps.once('close', () => resolve(text));
});

const tree = (text, rootPid) => {
  const rows = text.split('\n').map(line => {
    const fields = line.trim().split(/\s+/);
    return fields.length >= 2
      ? { pid: Number(fields[0]), ppid: Number(fields[1]), line }
      : null;
  }).filter(Boolean);
  const pids = new Set([rootPid]);
  for (let changed = true; changed;) {
    changed = false;
    for (const row of rows) {
      if (pids.has(row.ppid) && !pids.has(row.pid)) {
        pids.add(row.pid);
        changed = true;
      }
    }
  }
  return { rows: rows.filter(row => pids.has(row.pid)), pids };
};

const tracked = spawn("sh -c 'sleep 5; printf done >/dev/null'", [], {
  stdio: 'ignore',
  shell: true,
});
const probePids = new Set([tracked.pid]);

try {
  await new Promise(resolve => setTimeout(resolve, 100));
  let current = tree(await snapshot(), tracked.pid);
  for (const row of current.rows) probePids.add(row.pid);

  console.log(`tracked_pid=${tracked.pid}`);
  console.log('before_signal:');
  console.log(current.rows.map(row => row.line).join('\n') || '<no matching rows>');

  console.log(`kill_return=${tracked.kill('SIGINT')}`);
  console.log(`child_killed_immediately=${tracked.killed}`);

  await new Promise(resolve => setTimeout(resolve, 150));
  current = tree(await snapshot(), tracked.pid);
  console.log('after_signal:');
  console.log(current.rows.map(row => row.line).join('\n') || '<no matching rows>');
  for (const row of current.rows) probePids.add(row.pid);
} finally {
  for (const pid of probePids) {
    try { process.kill(pid, 'SIGKILL'); } catch {}
  }
  await new Promise(resolve => setTimeout(resolve, 100));
}
JS

Repository: webpack/webpack-doc-kit

Length of output: 480


Terminate the complete server process tree before exiting. With shell: true, children tracks only the direct child. The shell can remain alive with its server descendant after child.kill('SIGINT'); child.killed becomes true immediately, so the exit handler skips it. Spawn without a shell where possible, or terminate the process group/tree and await child closure before calling process.exit().

Comment thread scripts/dev/index.mjs
Comment on lines +59 to +90
let debounceTimer = null;
let isBuilding = false;

const handleFileChange = (baseDir, filename) => {
// Ignore hidden files / temp editor files
if (!filename || filename.startsWith('.')) return;

const fullPath = join(baseDir, filename);
const ext = extname(filename);

// Debounce rapid save events from editors
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
if (isBuilding) return;

isBuilding = true;

if (ext === '.md' || ext === '.mdx') {
// Wrap the single file in an array to match the new function signature
console.log(
`\nFile changed: ${fullPath} \nRunning fast partial build...`
);
await runDocKit(fullPath);
} else {
// Any other file change (jsx, css, js, mjs, png in public, etc) triggers a full build
console.log(`\nFile changed: ${fullPath} \nRunning full build...`);
await runDocKit();
}

isBuilding = false;
}, 500);
};

@moshams272 moshams272 Aug 16, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
let debounceTimer = null;
let isBuilding = false;
const handleFileChange = (baseDir, filename) => {
// Ignore hidden files / temp editor files
if (!filename || filename.startsWith('.')) return;
const fullPath = join(baseDir, filename);
const ext = extname(filename);
// Debounce rapid save events from editors
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
if (isBuilding) return;
isBuilding = true;
if (ext === '.md' || ext === '.mdx') {
// Wrap the single file in an array to match the new function signature
console.log(
`\nFile changed: ${fullPath} \nRunning fast partial build...`
);
await runDocKit(fullPath);
} else {
// Any other file change (jsx, css, js, mjs, png in public, etc) triggers a full build
console.log(`\nFile changed: ${fullPath} \nRunning full build...`);
await runDocKit();
}
isBuilding = false;
}, 500);
};
let debounceTimer = null;
let isBuilding = false;
let pendingFiles = new Set();
let needsFullBuild = false;
const handleFileChange = (baseDir, filename) => {
if (!filename || filename.startsWith('.')) return;
const fullPath = join(baseDir, filename);
const ext = extname(filename);
if (ext === '.md' || ext === '.mdx') {
pendingFiles.add(fullPath);
} else {
needsFullBuild = true;
}
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
if (isBuilding) return;
while (pendingFiles.size > 0 || needsFullBuild) {
if (needsFullBuild) {
needsFullBuild = false;
pendingFiles.clear();
isBuilding = true;
try {
await runDocKit();
} catch (err) {
console.error('\nFull build failed:', err.message);
} finally {
isBuilding = false;
}
} else {
const singleFile = pendingFiles.values().next().value;
pendingFiles.delete(singleFile);
isBuilding = true;
try {
await runDocKit(singleFile);
} catch (err) {
console.error(`\nPartial build failed for ${singleFile}:`, err.message);
} finally {
isBuilding = false;
}
}
}
}, 500);
};

Solving coderabbitai review, by saving the coming requests in a unique queue.

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.

should we do this? i mean we can wait for the ongoing build to complete and then hit another one cuz its mainly for markdown changes in dev environment so will mostly be dealing with a single page at a time.
what we can do is lets say a build is going on and we edit the same page during build and hit save, we can cancel the ongoing run and start the new build with prev and current changes instead of queuing.
WDYT?

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

Labels

dependencies Pull requests that update a dependency file tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants