vite-plus 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/AGENTS.md +4 -0
  2. package/LICENSE +1 -878
  3. package/README.md +1 -1
  4. package/bin/vpx +9 -0
  5. package/binding/index.cjs +59 -54
  6. package/binding/index.d.cts +159 -2
  7. package/dist/{agent-97KmGCyL.js → agent-DkQD3xqe.js} +5 -7
  8. package/dist/bin.js +35 -34
  9. package/dist/{cli-parse-DHE8NrBU.js → cli-parse-aeUeFDlC.js} +2 -3
  10. package/dist/command-TjS_6y9O.js +1 -0
  11. package/dist/{command-CdMBoZFD.js → command-rY79K5NI.js} +28 -30
  12. package/dist/{comparison-CmVirWIW-DUGaiYqF.js → comparison-CmVirWIW-UlEf2maz.js} +1 -2
  13. package/dist/config/bin.js +8 -10
  14. package/dist/{constants-DR55DpRm.js → constants-D2P2aVKb.js} +8 -6
  15. package/dist/create/bin.d.ts +4 -8
  16. package/dist/create/bin.js +151 -120
  17. package/dist/{define-config-Bexdd6RZ.d.ts → define-config-BrvHRi-G.d.ts} +31 -13
  18. package/dist/{define-config-BxDc_9BK.cjs → define-config-CwtQqYC1.cjs} +41 -14
  19. package/dist/{define-config-CvE5pHqB.js → define-config-wR57OZG_.js} +34 -12
  20. package/dist/define-config.cjs +2 -1
  21. package/dist/define-config.d.ts +2 -2
  22. package/dist/define-config.js +1 -1
  23. package/dist/{dist-DIpm7mot.js → dist-Bs0SObNC.js} +73 -145
  24. package/dist/{dist-Cqz8OtiO.js → dist-CdkMvIiQ.js} +11 -5
  25. package/dist/dist-zDZV5eEc.js +3 -0
  26. package/dist/{documentation-DqcZSthV.js → documentation-cv_y0Auv.js} +1 -2
  27. package/dist/{editor-CtJGQP7C.js → editor-aOy1T15H.js} +2735 -5072
  28. package/dist/{help-BmKpeOP9.js → help-B03t_iQ4.js} +1 -2
  29. package/dist/hooks/bin.js +4 -5
  30. package/dist/{hooks-DhwsWb46.js → hooks-w_e2whkQ.js} +45 -25
  31. package/dist/index.cjs +5 -5
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.js +2 -2
  34. package/dist/{json-JP24qHUF.js → json-kl84kDyH.js} +9 -18
  35. package/dist/lint-plugins.cjs +1 -1
  36. package/dist/migration/bin.js +1782 -256
  37. package/dist/migration/compat/worker.js +4 -6
  38. package/dist/{oxlint-plugin-config-BIzrO2y2.js → oxlint-plugin-config-CHsI_zKo.js} +3 -4
  39. package/dist/oxlint-plugin.d.ts +0 -2
  40. package/dist/oxlint-plugin.js +33 -9
  41. package/dist/{pack-BK38MLVZ.d.ts → pack-ARPFMefW.d.ts} +0 -2
  42. package/dist/pack-bin.d.ts +1 -3
  43. package/dist/pack-bin.js +4 -5
  44. package/dist/pack.d.ts +1 -1
  45. package/dist/{package-CQDXTm6j.js → package-DfFo6-E-.js} +10 -95
  46. package/dist/{package-overrides-D1RcXSFl.js → package-overrides-Ci8GXouY.js} +5 -8
  47. package/dist/picomatch-DEUoj81q.js +1916 -0
  48. package/dist/{prompts-1xNz_ne0.js → prompts-C7DI63WP.js} +30 -50
  49. package/dist/{protocol-D99W10Qi.js → protocol-CkNmY-Mr.js} +1 -2
  50. package/dist/register.d.ts +1 -0
  51. package/dist/register.js +12 -0
  52. package/dist/{report-NhQk8VHb.js → report-BEdpIQvC.js} +4 -2
  53. package/dist/resolve-vite-config-33bzSaeT.js +1 -0
  54. package/dist/{resolve-vite-config-BVr1SUNg.js → resolve-vite-config-Cx3cnVVI.js} +9 -25
  55. package/dist/{rolldown-runtime-kJ_i4yPo.js → rolldown-runtime-DegdyBIC.js} +1 -2
  56. package/dist/script-esm-hooks.d.ts +11 -0
  57. package/dist/script-esm-hooks.js +27 -0
  58. package/dist/script-hooks-9Fof5UMY.js +176 -0
  59. package/dist/script-preload.cjs +491 -0
  60. package/dist/script-register.d.ts +1 -0
  61. package/dist/script-register.js +27 -0
  62. package/dist/script-watch.d.ts +1 -0
  63. package/dist/script-watch.js +2002 -0
  64. package/dist/staged/bin.js +53 -2011
  65. package/dist/sync-versions/bin.js +26 -48
  66. package/dist/sync-versions/bin.mjs +109 -217
  67. package/dist/{terminal-MKGAuy-p.js → terminal-BcuPUy-j.js} +1 -2
  68. package/dist/toolchain.js +38 -12
  69. package/dist/toolchain.json +38 -12
  70. package/dist/{tsconfig-zWgHiQ3X.js → tsconfig-BE3Wj9OX.js} +1 -1
  71. package/dist/{tsconfig-BZu7kl-x.js → tsconfig-shNmmCE_.js} +8 -10
  72. package/dist/version.d.ts +1 -3
  73. package/dist/version.js +5 -6
  74. package/dist/versions.js +7 -7
  75. package/dist/{vitest-ecosystem-BU0AqZq-.js → vitest-ecosystem-BeEYz-qF.js} +1 -2
  76. package/dist/{vitest-v5-entry-points-DthU1e_O.js → vitest-v5-entry-points-NyJsFeux.js} +1 -2
  77. package/dist/vpx-bin.d.ts +1 -0
  78. package/dist/vpx-bin.js +232 -0
  79. package/dist/vpx-script-EXgV5QOf.js +434 -0
  80. package/docs/config/check.md +2 -0
  81. package/docs/config/fmt.md +1 -1
  82. package/docs/config/lint.md +1 -1
  83. package/docs/config/run.md +10 -2
  84. package/docs/config/staged.md +1 -1
  85. package/docs/guide/check.md +6 -1
  86. package/docs/guide/ci.md +13 -3
  87. package/docs/guide/create.md +1 -1
  88. package/docs/guide/env.md +11 -4
  89. package/docs/guide/global-cli.md +59 -3
  90. package/docs/guide/homebrew.md +127 -0
  91. package/docs/guide/ide-integration.md +1 -3
  92. package/docs/guide/implode.md +5 -3
  93. package/docs/guide/local-cli.md +21 -0
  94. package/docs/guide/migrate-rules.md +120 -18
  95. package/docs/guide/migrate.md +9 -9
  96. package/docs/guide/package-management.md +1 -1
  97. package/docs/guide/run.md +23 -1
  98. package/docs/guide/test.md +1 -1
  99. package/docs/guide/troubleshooting.md +1 -1
  100. package/docs/guide/upgrade.md +12 -6
  101. package/docs/guide/vitest-v5.md +2 -2
  102. package/docs/guide/vpx.md +210 -9
  103. package/package.json +43 -34
  104. package/dist/command-DVTILtS-.js +0 -1
  105. package/dist/dist-Dhu07wmN.js +0 -3
  106. package/dist/resolve-vite-config-7WDtXO4G.js +0 -1
@@ -0,0 +1,2002 @@
1
+ import path, { isAbsolute, join, resolve } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { parseEnv } from "node:util";
4
+ import { existsSync, lstatSync, readFileSync, readdirSync, readlinkSync, realpathSync, statSync, unlinkSync, unwatchFile, watch, watchFile } from "node:fs";
5
+ import process$1 from "node:process";
6
+ import { isatty } from "node:tty";
7
+ import { randomBytes } from "node:crypto";
8
+ import os, { constants as constants$1 } from "node:os";
9
+ import { execFileSync, spawn } from "node:child_process";
10
+ import { createServer } from "node:net";
11
+ // ../../oxc-node/packages/cli/src/watch/help.ts
12
+ const DEFAULT_NAMES = {
13
+ name: "oxnode watch",
14
+ label: "oxnode",
15
+ nodeWatch: "oxnode --watch"
16
+ };
17
+ /** The names for `name`. Only oxnode's own name points to `oxnode --watch`. */
18
+ function namesFor(name = DEFAULT_NAMES.name) {
19
+ if (name === DEFAULT_NAMES.name) return DEFAULT_NAMES;
20
+ return {
21
+ name,
22
+ label: name.split(" ")[0],
23
+ nodeWatch: null
24
+ };
25
+ }
26
+ const usage = (n = DEFAULT_NAMES) => `Usage: ${n.name} [options] [node options] [--] <entry> [script args...]`;
27
+ const help = (n = DEFAULT_NAMES) => `${usage(n)}
28
+
29
+ Run <entry> with oxc-node and run it again when a file it uses changes.
30
+
31
+ Options:
32
+ --include <glob> Also watch files matching <glob> (repeatable; "!x" excludes)
33
+ --exclude <glob> Never watch files matching <glob> (repeatable)
34
+ --clear-screen=<bool> Clear the screen before each rerun (default: true, TTY only)
35
+ --no-clear-screen Same as --clear-screen=false
36
+ --kill-signal <signal> Signal that stops the app before a rerun (default: SIGTERM)
37
+ --kill-timeout <ms> Force kill the app if it is still running after <ms> (default: 5000)
38
+ --rerun-on-enter=<bool> Rerun when Return is pressed (default: true on a terminal)
39
+ --no-rerun-on-enter Never read the terminal; the app keeps stdin to itself
40
+ --poll[=<ms>] Poll files instead of using file system events (Docker, WSL, network drives)
41
+ --tsconfig <path> Use this tsconfig (same as OXC_TSCONFIG_PATH)
42
+ -h, --help Show this help
43
+
44
+ Node.js options go before <entry> (use --flag=value); everything after <entry> goes to the script.
45
+ Globs are relative to the current folder. node_modules is not watched unless --include names it.
46
+
47
+ Watched: every file the app imports or requires (worker threads on Node 24+), the tsconfig files
48
+ oxc-node read (with "extends"), files the resolver looked for and did not find, --env-file files,
49
+ Node.js config files (--experimental-config-file).
50
+
51
+ Keys: Return rerun now (press again while stopping to force kill)
52
+ Ctrl+C stop (press again to force kill)
53
+
54
+ Examples:
55
+ ${n.name} ./src/server.ts
56
+ ${n.name} --include "./config/*.json" --exclude "src/**/*.test.ts" ./src/server.ts --port 3000
57
+ ${n.name} --inspect --clear-screen=false ./src/index.ts
58
+ ${n.nodeWatch ? `\n\`${n.nodeWatch} <entry>\` still runs Node.js' own watch mode.\n` : ""}`;
59
+ // ../../oxc-node/packages/cli/src/watch/args.ts
60
+ /**
61
+ * Node.js options that take their value as the next token: those `node --help` shows as
62
+ * `--name=...` on Node.js 22, 24 and 26, less test, bench and build-only ones.
63
+ */
64
+ const NODE_VALUE_FLAGS = /* @__PURE__ */ new Set([
65
+ "-r",
66
+ "--require",
67
+ "--import",
68
+ "--loader",
69
+ "--experimental-loader",
70
+ "-C",
71
+ "--conditions",
72
+ "--env-file",
73
+ "--env-file-if-exists",
74
+ "--input-type",
75
+ "--inspect-port",
76
+ "--debug-port",
77
+ "--title",
78
+ "--disable-warning",
79
+ "--redirect-warnings",
80
+ "--report-dir",
81
+ "--report-directory",
82
+ "--diagnostic-dir",
83
+ "--allow-fs-read",
84
+ "--allow-fs-write",
85
+ "--cpu-prof-dir",
86
+ "--cpu-prof-interval",
87
+ "--cpu-prof-name",
88
+ "--disable-proto",
89
+ "--dns-result-order",
90
+ "--experimental-config-file",
91
+ "--experimental-default-type",
92
+ "--experimental-package-map",
93
+ "--heap-prof-dir",
94
+ "--heap-prof-interval",
95
+ "--heap-prof-name",
96
+ "--heapsnapshot-near-heap-limit",
97
+ "--heapsnapshot-signal",
98
+ "--icu-data-dir",
99
+ "--inspect-publish-uid",
100
+ "--localstorage-file",
101
+ "--max-http-header-size",
102
+ "--max-old-space-size-percentage",
103
+ "--network-family-autoselection-attempt-timeout",
104
+ "--openssl-config",
105
+ "--report-filename",
106
+ "--report-signal",
107
+ "--secure-heap",
108
+ "--secure-heap-min",
109
+ "--security-revert",
110
+ "--snapshot-blob",
111
+ "--tls-cipher-list",
112
+ "--tls-keylog",
113
+ "--trace-event-categories",
114
+ "--trace-event-file-pattern",
115
+ "--trace-require-module",
116
+ "--unhandled-rejections",
117
+ "--use-largepages",
118
+ "--v8-pool-size",
119
+ "--vfs-load",
120
+ "--watch-kill-signal"
121
+ ]);
122
+ const DEFAULT_POLL_MS = 300;
123
+ const MAX_TIMER_MS = 2 ** 31 - 1;
124
+ const MAX_POLL_MS = Math.floor(MAX_TIMER_MS / 10);
125
+ const needsEntry = (n) => n.nodeWatch ? `${n.name} needs an entry file; for tests use \`${n.nodeWatch} --test\`` : `${n.name} needs an entry file`;
126
+ const REJECTED = {
127
+ "--watch": "use --include",
128
+ "--watch-path": "use --include",
129
+ "-e": null,
130
+ "--eval": null,
131
+ "-p": null,
132
+ "--print": null,
133
+ "-pe": null,
134
+ "-i": null,
135
+ "--interactive": null,
136
+ "--test": null,
137
+ "--run": null
138
+ };
139
+ function parseBool(name, value) {
140
+ if (value === void 0 || value === "true") return true;
141
+ if (value === "false") return false;
142
+ return { error: `${name} takes true or false, got "${value}"` };
143
+ }
144
+ /** NODE_OPTIONS split as Node.js does: at spaces, but not inside `"…"`, where `\` escapes. */
145
+ function nodeOptionsTokens(s) {
146
+ const out = [];
147
+ let cur;
148
+ let quoted = false;
149
+ for (let i = 0; i < s.length; i++) {
150
+ let c = s[i];
151
+ if (c === "\\" && quoted) c = s[++i] ?? "";
152
+ else if (c === " " && !quoted) {
153
+ if (cur !== void 0) out.push(cur);
154
+ cur = void 0;
155
+ continue;
156
+ } else if (c === "\"") {
157
+ quoted = !quoted;
158
+ continue;
159
+ }
160
+ cur = (cur ?? "") + c;
161
+ }
162
+ if (cur !== void 0) out.push(cur);
163
+ return out;
164
+ }
165
+ const NO_WATCH = /^--no[-_]watch(=|$)/;
166
+ /**
167
+ * The last word on watch mode in NODE_OPTIONS: true for --watch or --watch-path (which implies
168
+ * --watch), false for --no-watch. Node.js applies them in order, so the last one wins.
169
+ */
170
+ function watchFlag(nodeOptions) {
171
+ let on;
172
+ for (const t of nodeOptionsTokens(nodeOptions ?? "")) if (/^--watch([-_]path)?(=|$)/.test(t)) on = true;
173
+ else if (NO_WATCH.test(t)) on = false;
174
+ return on;
175
+ }
176
+ const NESTED = "NODE_OPTIONS contains --watch, which would start a nested watcher";
177
+ const readEnvFile = (file) => {
178
+ try {
179
+ return readFileSync(file, "utf8");
180
+ } catch {
181
+ return;
182
+ }
183
+ };
184
+ const NODE_VERSION = process.versions.node;
185
+ const majorOf = (version) => Number(version.split(".")[0]);
186
+ /** Whether `version` has a change released in each `[major, minor]` given, and every later major. */
187
+ const atLeast = (version, ...mins) => {
188
+ const [major, minor] = version.split(".").map(Number);
189
+ return mins.some(([m, n]) => major === m && minor >= n) || major > mins[mins.length - 1][0];
190
+ };
191
+ /** Node.js 24.19 and 26.1 read a bare `--experimental-config-file` as `node.config.json`. */
192
+ const bareConfigFlag = (version) => atLeast(version, [24, 19], [26, 1]);
193
+ /** Before 26.12, Node.js reads a config flag anywhere in argv: after the entry and `--` too. */
194
+ const configFlagsAnywhere = (version) => !atLeast(version, [26, 12]);
195
+ /** Node.js 24.16 and 26.1 apply NODE_OPTIONS from an env file over the config file. */
196
+ const dotenvBeatsConfig = (version) => atLeast(version, [24, 16], [26, 1]);
197
+ /** Node.js 25.4 turns a flag on when the config file has a section named after it. */
198
+ const sectionTurnsOn = (version) => atLeast(version, [25, 4], [26, 0]);
199
+ function parseWatchArgs(argv, env, read = readEnvFile, nodeVersion = NODE_VERSION, names = DEFAULT_NAMES) {
200
+ const out = {
201
+ include: [],
202
+ exclude: [],
203
+ clearScreen: true,
204
+ killSignal: "SIGTERM",
205
+ killTimeout: 5e3,
206
+ rerunOnEnter: void 0,
207
+ poll: env.CHOKIDAR_USEPOLLING === "1" || env.CHOKIDAR_USEPOLLING === "true" ? DEFAULT_POLL_MS : false,
208
+ help: false,
209
+ nodeArgs: [],
210
+ entry: "",
211
+ envFiles: [],
212
+ configFiles: [],
213
+ noWatch: false,
214
+ notes: []
215
+ };
216
+ const configs = [];
217
+ for (let i = 0; i < argv.length; i++) {
218
+ const arg = argv[i];
219
+ if (arg === "--") {
220
+ if (i + 1 < argv.length) {
221
+ out.entry = argv[i + 1];
222
+ out.nodeArgs.push(...argv.slice(i));
223
+ }
224
+ if (configFlagsAnywhere(nodeVersion)) scanConfigs(argv.slice(i + 1), nodeVersion, configs);
225
+ break;
226
+ }
227
+ if (arg === "-") return { error: `- (stdin): ${needsEntry(names)}` };
228
+ if (!arg.startsWith("-")) {
229
+ out.entry = arg;
230
+ out.nodeArgs.push(...argv.slice(i));
231
+ if (configFlagsAnywhere(nodeVersion)) scanConfigs(argv.slice(i + 1), nodeVersion, configs);
232
+ break;
233
+ }
234
+ const eq = arg.indexOf("=");
235
+ const name = eq === -1 ? arg : arg.slice(0, eq);
236
+ const inline = eq === -1 ? void 0 : arg.slice(eq + 1);
237
+ const nodeName = name.startsWith("--") ? `--${name.slice(2).replaceAll("_", "-")}` : name;
238
+ const value = () => inline ?? argv[++i];
239
+ switch (nodeName) {
240
+ case "-h":
241
+ case "--help":
242
+ out.help = true;
243
+ continue;
244
+ case "--include":
245
+ case "--exclude":
246
+ case "--ignore": {
247
+ const glob = value();
248
+ if (!glob) return { error: `${name} needs a glob` };
249
+ const negated = glob.startsWith("!") && !glob.startsWith("!(");
250
+ if (nodeName === "--include" && !negated) out.include.push(glob);
251
+ else out.exclude.push(nodeName === "--include" ? glob.slice(1) : glob);
252
+ continue;
253
+ }
254
+ case "--clear-screen": {
255
+ const b = parseBool(name, inline);
256
+ if (typeof b !== "boolean") return b;
257
+ out.clearScreen = b;
258
+ continue;
259
+ }
260
+ case "--no-clear-screen":
261
+ case "--watch-preserve-output":
262
+ out.clearScreen = false;
263
+ continue;
264
+ case "--rerun-on-enter": {
265
+ const b = parseBool(name, inline);
266
+ if (typeof b !== "boolean") return b;
267
+ out.rerunOnEnter = b;
268
+ continue;
269
+ }
270
+ case "--no-rerun-on-enter":
271
+ out.rerunOnEnter = false;
272
+ continue;
273
+ case "--kill-signal":
274
+ case "--watch-kill-signal": {
275
+ const sig = value();
276
+ if (!sig || !Object.hasOwn(constants$1.signals, sig)) return { error: `${name} needs a signal name such as SIGTERM, got "${sig ?? ""}"` };
277
+ out.killSignal = sig;
278
+ continue;
279
+ }
280
+ case "--kill-timeout": {
281
+ const ms = value();
282
+ if (!ms || !/^\d+$/.test(ms)) return { error: `${name} needs a whole number of milliseconds, got "${ms ?? ""}"` };
283
+ out.killTimeout = Number(ms);
284
+ if (out.killTimeout > MAX_TIMER_MS) return { error: `${name} must be at most ${MAX_TIMER_MS} ms, got "${ms}"` };
285
+ continue;
286
+ }
287
+ case "--poll":
288
+ if (inline !== void 0 && !/^\d+$/.test(inline)) return { error: `--poll takes a whole number of milliseconds, got "${inline}"` };
289
+ out.poll = inline === void 0 ? DEFAULT_POLL_MS : Number(inline);
290
+ if (out.poll < 1 || out.poll > MAX_POLL_MS) return { error: `--poll takes 1 to ${MAX_POLL_MS} ms, got "${inline}"` };
291
+ continue;
292
+ case "--tsconfig": {
293
+ const p = value();
294
+ if (!p) return { error: "--tsconfig needs a path" };
295
+ out.tsconfig = p;
296
+ continue;
297
+ }
298
+ case "--no-cache":
299
+ out.notes.push("oxc-node has no transform cache");
300
+ continue;
301
+ }
302
+ if (Object.hasOwn(REJECTED, nodeName)) {
303
+ const why = REJECTED[nodeName] ?? needsEntry(names);
304
+ return { error: `${name} is not supported by ${names.name}: ${why}` };
305
+ }
306
+ out.nodeArgs.push(arg);
307
+ if (NO_WATCH.test(nodeName)) out.noWatch = true;
308
+ let nodeValue = inline;
309
+ const configFlag = nodeName === "--experimental-config-file" || nodeName === "--config-file" || nodeName === "--experimental-default-config-file";
310
+ if (inline === void 0 && takesNextWord(nodeName, nodeVersion) && i + 1 < argv.length) {
311
+ nodeValue = argv[++i];
312
+ out.nodeArgs.push(nodeValue);
313
+ }
314
+ if ((nodeName === "--env-file" || nodeName === "--env-file-if-exists") && nodeValue !== void 0) out.envFiles.push(nodeValue);
315
+ if (configFlag) addConfig(configs, nodeName, nodeValue);
316
+ }
317
+ if (!out.entry && !out.help) return { error: "missing <entry>" };
318
+ out.configFiles = pickConfig(configs, nodeVersion);
319
+ const nested = out.help ? void 0 : nestedWatcher(out, env, read, nodeVersion);
320
+ return nested ? { error: nested } : out;
321
+ }
322
+ function addConfig(configs, nodeName, nodeValue) {
323
+ if (nodeValue === "") return;
324
+ const file = nodeName === "--experimental-default-config-file" ? void 0 : nodeValue;
325
+ configs.push({
326
+ file: file ?? "node.config.json",
327
+ named: file !== void 0
328
+ });
329
+ }
330
+ /**
331
+ * The one config file Node.js reads. From 24.19 and 26.1 the last config flag wins. Before, the
332
+ * first file named wins, and the default one counts only when none is named.
333
+ */
334
+ function pickConfig(configs, nodeVersion) {
335
+ const config = bareConfigFlag(nodeVersion) ? configs.at(-1) : configs.find((c) => c.named) ?? configs[0];
336
+ return config ? [config.file] : [];
337
+ }
338
+ /** Normalized long flag name and inline value: `--watch_path=x` is `--watch-path`, `x`. */
339
+ /** Whether Node.js takes the next word as the value of `arg`, a flag with no `=value`. */
340
+ function takesNextWord(arg, nodeVersion = NODE_VERSION) {
341
+ if (arg.includes("=")) return false;
342
+ const name = arg.startsWith("--") ? `--${arg.slice(2).replaceAll("_", "-")}` : arg;
343
+ return NODE_VALUE_FLAGS.has(name) && !(name === "--experimental-config-file" && bareConfigFlag(nodeVersion));
344
+ }
345
+ function splitFlag(arg) {
346
+ const eq = arg.indexOf("=");
347
+ return [`--${(eq === -1 ? arg : arg.slice(0, eq)).slice(2).replaceAll("_", "-")}`, eq === -1 ? void 0 : arg.slice(eq + 1)];
348
+ }
349
+ const isConfigFlag = (name) => name === "--experimental-config-file" || name === "--config-file" || name === "--experimental-default-config-file";
350
+ /** Add the config flags in `args`, read as `nodeVersion` reads them. */
351
+ function scanConfigs(args, nodeVersion, configs) {
352
+ for (let i = 0; i < args.length; i++) {
353
+ if (!args[i].startsWith("--")) continue;
354
+ const [name, inline] = splitFlag(args[i]);
355
+ if (!isConfigFlag(name)) continue;
356
+ let value = inline;
357
+ const takesValue = name === "--experimental-config-file" && !bareConfigFlag(nodeVersion);
358
+ if (value === void 0 && takesValue && i + 1 < args.length) value = args[++i];
359
+ addConfig(configs, name, value);
360
+ }
361
+ }
362
+ /**
363
+ * Why the child would start a watcher of its own. The command line beats NODE_OPTIONS, which
364
+ * beats the config file. NODE_OPTIONS from an env file beats the config file from 24.16 and 26.1;
365
+ * before, the config file wins. Checked before each start too, as the files may change.
366
+ */
367
+ function nestedWatcher(args, env, read = readEnvFile, nodeVersion = NODE_VERSION) {
368
+ if (args.noWatch) return void 0;
369
+ const fromEnv = watchFlag(env.NODE_OPTIONS);
370
+ if (fromEnv !== void 0) return fromEnv ? NESTED : void 0;
371
+ const fromFile = env.NODE_OPTIONS === void 0 ? watchFlag(childNodeOptions(env, args.envFiles, read)) : void 0;
372
+ const config = configFileWatch(args.configFiles, read, nodeVersion);
373
+ if (dotenvBeatsConfig(nodeVersion) ? fromFile !== void 0 : config === void 0) return fromFile ? `${NESTED} (from --env-file)` : void 0;
374
+ return config?.on ? configNested(config.file) : void 0;
375
+ }
376
+ /**
377
+ * What the Node.js config file says about watch mode: on, off, or nothing (undefined). It applies
378
+ * `nodeOptions` and the `watch` section as flags, and from 25.4 a `watch` section turns on --watch
379
+ * by being there (unless it holds `"watch": false`). With `configs`, Node.js takes the first entry
380
+ * for its major version.
381
+ */
382
+ function configFileWatch(configFiles, read = readEnvFile, nodeVersion = NODE_VERSION) {
383
+ const nodeMajor = majorOf(nodeVersion);
384
+ const isObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
385
+ const watchOf = (config) => {
386
+ if (!isObject(config)) return void 0;
387
+ const { nodeOptions, watch } = config;
388
+ const last = (o) => {
389
+ let on;
390
+ for (const [k, v] of Object.entries(o)) if (/^watch([-_]path)?$/.test(k) && v != null) on = v !== false;
391
+ return on;
392
+ };
393
+ const sets = [nodeOptions, watch].filter(isObject).map(last);
394
+ if (sets.includes(true)) return true;
395
+ if (sectionTurnsOn(nodeVersion) && isObject(watch) && watch.watch !== false) return true;
396
+ return sets.includes(false) ? false : void 0;
397
+ };
398
+ for (const file of configFiles) {
399
+ const text = read(file);
400
+ if (text === void 0) continue;
401
+ let config;
402
+ try {
403
+ config = JSON.parse(text.replace(/^\uFEFF/, ""));
404
+ } catch {
405
+ continue;
406
+ }
407
+ if (isObject(config) && Array.isArray(config.configs)) {
408
+ const entry = config.configs.find((c) => isObject(c) && c.nodeVersion === nodeMajor);
409
+ config = isObject(entry) ? entry.config : void 0;
410
+ }
411
+ const on = watchOf(config);
412
+ if (on !== void 0) return {
413
+ file,
414
+ on
415
+ };
416
+ }
417
+ }
418
+ const configNested = (file) => `the Node.js config file ${file} turns on --watch, which would start a nested watcher`;
419
+ /** NODE_OPTIONS as the child gets it: from --env-file unless it is set already; the last file wins. */
420
+ function childNodeOptions(env, envFiles, read) {
421
+ if (env.NODE_OPTIONS !== void 0) return env.NODE_OPTIONS;
422
+ let fromFiles;
423
+ for (const file of envFiles) {
424
+ const text = read(file);
425
+ const value = text === void 0 ? void 0 : parseEnv(text).NODE_OPTIONS;
426
+ if (value !== void 0) fromFiles = value;
427
+ }
428
+ return fromFiles;
429
+ }
430
+ /**
431
+ * The child env for `--tsconfig`. oxc-node reads TS_NODE_PROJECT before OXC_TSCONFIG_PATH,
432
+ * so both name the config: Node.js keeps a variable the environment already has, so an
433
+ * `--env-file` that sets either one cannot override it (issue #876).
434
+ */
435
+ function tsconfigEnv(tsconfig, cwd) {
436
+ if (!tsconfig) return {};
437
+ const path = resolve(cwd, tsconfig);
438
+ return {
439
+ OXC_TSCONFIG_PATH: path,
440
+ TS_NODE_PROJECT: path
441
+ };
442
+ }
443
+ // ../../oxc-node/packages/cli/src/run.ts
444
+ /** The signals oxnode passes on to the app. On Windows it only listens, so it outlives the app. */
445
+ const FORWARDED = process$1.platform === "win32" ? [
446
+ "SIGINT",
447
+ "SIGBREAK",
448
+ "SIGHUP"
449
+ ] : [
450
+ "SIGINT",
451
+ "SIGTERM",
452
+ "SIGHUP"
453
+ ];
454
+ /**
455
+ * A unix socket the child writes "I" to when the terminal already sent it SIGINT
456
+ * (`@oxc-node/core/signal-ack.mjs`), so oxnode does not send it a second one.
457
+ */
458
+ function createSignalAck() {
459
+ const callbacks = [];
460
+ const none = {
461
+ env: {},
462
+ onAck: () => {},
463
+ close: () => {}
464
+ };
465
+ if (process$1.platform === "win32") return none;
466
+ const path = join(os.tmpdir(), `oxnode-${process$1.pid}-${randomBytes(4).toString("hex")}.sock`);
467
+ if (Buffer.byteLength(path) > 100) return none;
468
+ const server = createServer((socket) => {
469
+ socket.unref();
470
+ socket.on("error", () => {});
471
+ socket.on("data", (data) => {
472
+ for (const byte of data) if (byte === 73) for (const cb of callbacks) cb();
473
+ });
474
+ });
475
+ let closed = false;
476
+ const close = () => {
477
+ if (closed) return;
478
+ closed = true;
479
+ process$1.off("exit", close);
480
+ process$1.off("SIGQUIT", onQuit);
481
+ server.close();
482
+ try {
483
+ unlinkSync(path);
484
+ } catch {}
485
+ };
486
+ server.on("error", close);
487
+ try {
488
+ server.listen(path);
489
+ } catch {
490
+ return none;
491
+ }
492
+ server.unref();
493
+ process$1.on("exit", close);
494
+ function onQuit() {
495
+ close();
496
+ process$1.kill(process$1.pid, "SIGQUIT");
497
+ }
498
+ process$1.once("SIGQUIT", onQuit);
499
+ return {
500
+ env: { OXNODE_SIGNAL_ACK: path },
501
+ onAck: (cb) => void callbacks.push(cb),
502
+ close
503
+ };
504
+ }
505
+ /** Whether oxnode is in the terminal's foreground group: reading stdin from the background stops it (SIGTTIN). */
506
+ function isForeground() {
507
+ try {
508
+ if (process$1.platform === "win32") return true;
509
+ if (process$1.platform === "linux") {
510
+ const stat = readFileSync("/proc/self/stat", "utf8");
511
+ const f = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/);
512
+ return f[2] === f[5];
513
+ }
514
+ const [pgid, tpgid] = execFileSync("ps", [
515
+ "-o",
516
+ "pgid=,tpgid=",
517
+ "-p",
518
+ String(process$1.pid)
519
+ ], { encoding: "utf8" }).trim().split(/\s+/);
520
+ return pgid === tpgid;
521
+ } catch {
522
+ return false;
523
+ }
524
+ }
525
+ /**
526
+ * Whether a SIGINT oxnode gets comes from the terminal's Ctrl+C, which signals the whole
527
+ * foreground group, the child included. Ask it once the SIGINT is in: a Ctrl+C during the
528
+ * check also kills the `ps` it runs on macOS, and the answer would be false.
529
+ */
530
+ const sigintFromTerminal = () => isatty(0) && isForeground();
531
+ /** Decides whether a signal oxnode got goes on to the child. Pure: the caller owns the timers. */
532
+ var SignalGate = class {
533
+ #nodeWatch;
534
+ #platform;
535
+ #terminal;
536
+ #waitMs;
537
+ #recentMs;
538
+ #coalesceMs;
539
+ #last = /* @__PURE__ */ new Map();
540
+ #lastAck = -Infinity;
541
+ #waiting = false;
542
+ constructor(o) {
543
+ this.#nodeWatch = o.nodeWatch;
544
+ this.#platform = o.platform;
545
+ const terminal = o.terminal ?? false;
546
+ this.#terminal = typeof terminal === "function" ? terminal : () => terminal;
547
+ this.#waitMs = o.waitMs ?? 50;
548
+ this.#recentMs = o.recentMs ?? 100;
549
+ this.#coalesceMs = o.coalesceMs ?? 500;
550
+ }
551
+ onSignal(sig, now) {
552
+ if (this.#platform === "win32") return { action: "skip" };
553
+ const prev = this.#last.get(sig);
554
+ this.#last.set(sig, now);
555
+ if (prev !== void 0 && now - prev < this.#coalesceMs) return { action: "skip" };
556
+ if (sig === "SIGINT") {
557
+ if (this.#terminal()) return { action: "skip" };
558
+ if (now - this.#lastAck <= this.#recentMs) return { action: "skip" };
559
+ this.#waiting = true;
560
+ return {
561
+ action: "wait",
562
+ ms: this.#waitMs
563
+ };
564
+ }
565
+ if (sig === "SIGHUP" && this.#nodeWatch) return {
566
+ action: "forward",
567
+ as: "SIGTERM"
568
+ };
569
+ return {
570
+ action: "forward",
571
+ as: sig
572
+ };
573
+ }
574
+ onAck(now) {
575
+ this.#lastAck = now;
576
+ this.#waiting = false;
577
+ }
578
+ onWaitExpired() {
579
+ if (!this.#waiting) return { action: "skip" };
580
+ this.#waiting = false;
581
+ return {
582
+ action: "forward",
583
+ as: "SIGINT"
584
+ };
585
+ }
586
+ };
587
+ /** Runs `onSignal` decisions against a child, including the SIGINT wait. */
588
+ function gateSignals(gate, ack, kill, debug = () => {}) {
589
+ let timer;
590
+ const act = (r) => {
591
+ if (r.action === "forward") {
592
+ debug(`forward ${r.as}`);
593
+ kill(r.as);
594
+ } else if (r.action === "wait") {
595
+ clearTimeout(timer);
596
+ timer = setTimeout(() => {
597
+ timer = void 0;
598
+ act(gate.onWaitExpired());
599
+ }, r.ms);
600
+ }
601
+ };
602
+ ack.onAck(() => {
603
+ debug("ack");
604
+ gate.onAck(Date.now());
605
+ clearTimeout(timer);
606
+ timer = void 0;
607
+ });
608
+ return {
609
+ signal: (sig) => act(gate.onSignal(sig, Date.now())),
610
+ dispose: () => clearTimeout(timer)
611
+ };
612
+ }
613
+ // ../../oxc-node/packages/cli/src/watch/glob.ts
614
+ const GLOB_CHARS = /[*?[\]{}]|[+@!]\(/;
615
+ const MAX_EXPANSIONS = 64;
616
+ /**
617
+ * `a/{b,c/d}/e` → `a/b/e`, `a/c/d/e`. Groups without a comma (`{1..3}`) stay. Too many
618
+ * alternatives: the pattern as it is. `\\` escapes, except on Windows, where it is a separator.
619
+ */
620
+ function expandBraces(pattern, win) {
621
+ let depth = 0;
622
+ let start = -1;
623
+ for (let i = 0; i < pattern.length; i++) {
624
+ const c = pattern[i];
625
+ if (c === "\\" && !win) i++;
626
+ else if (c === "{") {
627
+ if (depth++ === 0) start = i;
628
+ } else if (c === "}" && depth > 0 && --depth === 0) {
629
+ const alts = splitTop(pattern.slice(start + 1, i), win);
630
+ if (alts.length < 2) continue;
631
+ const [pre, post] = [pattern.slice(0, start), pattern.slice(i + 1)];
632
+ const out = [];
633
+ for (const alt of alts) for (const one of expandBraces(pre + alt + post, win)) {
634
+ out.push(one);
635
+ if (out.length > MAX_EXPANSIONS) return [pattern];
636
+ }
637
+ return out;
638
+ }
639
+ }
640
+ return [pattern];
641
+ }
642
+ function splitTop(body, win) {
643
+ const out = [];
644
+ let depth = 0;
645
+ let from = 0;
646
+ for (let i = 0; i < body.length; i++) {
647
+ const c = body[i];
648
+ if (c === "\\" && !win) i++;
649
+ else if (c === "{") depth++;
650
+ else if (c === "}" && depth > 0) depth--;
651
+ else if (c === "," && depth === 0) {
652
+ out.push(body.slice(from, i));
653
+ from = i + 1;
654
+ }
655
+ }
656
+ out.push(body.slice(from));
657
+ return out;
658
+ }
659
+ const isDirOnDisk = (p) => {
660
+ try {
661
+ return statSync(p).isDirectory();
662
+ } catch {
663
+ return false;
664
+ }
665
+ };
666
+ function makeMatcher(opts) {
667
+ const win = opts.platform === "win32";
668
+ const native = win ? path.win32 : path.posix;
669
+ const dirGlob = (p) => GLOB_CHARS.test(p) && (win ? /[\\/]$/ : /\/$/).test(p) ? `${p}**` : p;
670
+ const include = opts.include.map(dirGlob);
671
+ const exclude = opts.exclude.map(dirGlob);
672
+ const isDir = opts.isDir ?? isDirOnDisk;
673
+ const norm = (p) => win ? p.replaceAll("\\", "/").toLowerCase() : p;
674
+ const cwd = norm(opts.cwd);
675
+ const drive = win ? native.resolve(opts.cwd, "/").replace(/[\\/]$/, "") : "";
676
+ const rooted = (pattern) => !win ? pattern : /^[\\/](?![\\/])/.test(pattern) ? drive + pattern : /^[A-Za-z]:(?![\\/])/.test(pattern) ? native.resolve(opts.cwd, pattern) : pattern;
677
+ const absolute = (pattern) => {
678
+ const abs = win ? norm(native.resolve(opts.cwd, rooted(pattern))) : path.posix.isAbsolute(pattern) ? pattern : path.posix.join(cwd, pattern);
679
+ return /^([a-z]:)?\/+$/i.test(abs) ? abs : abs.replace(/\/+$/, "");
680
+ };
681
+ const globs = [...include, ...exclude].filter((p) => GLOB_CHARS.test(p));
682
+ const matchesGlob = path.posix.matchesGlob;
683
+ if (globs.length && typeof matchesGlob !== "function") throw new Error(`--include and --exclude globs need Node.js 22.5 or later (path.matchesGlob)`);
684
+ const literal = (lit) => {
685
+ const below = lit.endsWith("/") ? lit : `${lit}/`;
686
+ return (n) => n === lit || n.startsWith(below);
687
+ };
688
+ const glob = (pattern) => {
689
+ if (native.isAbsolute(rooted(pattern))) {
690
+ const g = norm(rooted(pattern));
691
+ return (n) => matchesGlob(n, g);
692
+ }
693
+ const g = path.posix.normalize(norm(pattern));
694
+ return (n) => matchesGlob(path.posix.relative(cwd, n), g);
695
+ };
696
+ const excludes = exclude.map((p) => GLOB_CHARS.test(p) ? glob(p) : literal(absolute(p)));
697
+ const treeExcludes = exclude.filter((p) => /[\\/]\*\*$/.test(p)).map(glob);
698
+ const includes = [];
699
+ const named = [];
700
+ const roots = [];
701
+ const literals = [];
702
+ for (const pattern of include) {
703
+ if (!GLOB_CHARS.test(pattern)) {
704
+ const abs = native.resolve(opts.cwd, pattern);
705
+ if (isDir(abs)) {
706
+ includes.push(literal(norm(abs)));
707
+ roots.push({
708
+ base: abs,
709
+ depth: Infinity
710
+ });
711
+ } else {
712
+ includes.push(literal(norm(abs)));
713
+ literals.push(abs);
714
+ roots.push({
715
+ base: abs,
716
+ depth: Infinity
717
+ });
718
+ }
719
+ continue;
720
+ }
721
+ includes.push(glob(pattern));
722
+ for (const one of expandBraces(pattern, win)) {
723
+ const parts = one.split(win ? /[\\/]/ : "/");
724
+ let baseLen = 0;
725
+ while (baseLen < parts.length - 1 && !GLOB_CHARS.test(parts[baseLen])) baseLen++;
726
+ const rest = parts.slice(baseLen);
727
+ const fixed = baseLen === 0 ? "." : parts.slice(0, baseLen).join("/") || "/";
728
+ const base = native.resolve(opts.cwd, /^[A-Za-z]:$/.test(fixed) ? `${fixed}/` : fixed);
729
+ named.push({
730
+ base: norm(base),
731
+ parts: rest.map(norm)
732
+ });
733
+ roots.push({
734
+ base,
735
+ depth: rest.includes("**") ? Infinity : rest.length
736
+ });
737
+ }
738
+ }
739
+ const ignoreDirs = (opts.ignoreDirs ?? []).map(norm);
740
+ const excluded = (n) => excludes.some((m) => m(n));
741
+ return {
742
+ classify(absPath, source) {
743
+ const n = norm(absPath);
744
+ if (excluded(n)) return "exclude";
745
+ if (includes.some((m) => m(n))) return "watch";
746
+ if (source === "literal") return "watch";
747
+ if (source === "walk") return "default-ignore";
748
+ if (source !== "oxc" && n.split("/").includes("node_modules") || ignoreDirs.some((d) => literal(d)(n))) return "default-ignore";
749
+ return "watch";
750
+ },
751
+ roots: () => roots,
752
+ literals: () => literals,
753
+ pruneDir(absDir) {
754
+ const n = norm(absDir);
755
+ const name = path.posix.basename(n);
756
+ const fits = (dirs, parts, i = 0, j = 0) => {
757
+ if (i === dirs.length) return true;
758
+ const part = parts[j];
759
+ if (part === void 0 || j === parts.length - 1 && part !== "**") return false;
760
+ const last = i === dirs.length - 1;
761
+ if (part === "**") return fits(dirs, parts, i, j + 1) || !last && fits(dirs, parts, i + 1, j);
762
+ return (GLOB_CHARS.test(part) ? matchesGlob(dirs[i], part) && !(last && matchesGlob("oxnode-any-name", part)) : part === dirs[i]) && fits(dirs, parts, i + 1, j + 1);
763
+ };
764
+ return (name === "node_modules" || name.startsWith(".")) && !named.some((include) => n === include.base || n.startsWith(include.base.endsWith("/") ? include.base : `${include.base}/`) && fits(n.slice(include.base.replace(/\/$/, "").length + 1).split("/"), include.parts)) || excluded(n) || treeExcludes.some((m) => m(`${n}/__probe__`));
765
+ }
766
+ };
767
+ }
768
+ // ../../oxc-node/packages/cli/src/watch/dirs.ts
769
+ function isRealDir(p) {
770
+ try {
771
+ return lstatSync(p).isDirectory();
772
+ } catch {
773
+ return false;
774
+ }
775
+ }
776
+ function isLink(p) {
777
+ try {
778
+ return lstatSync(p).isSymbolicLink();
779
+ } catch {
780
+ return false;
781
+ }
782
+ }
783
+ function sigOf(p) {
784
+ try {
785
+ const s = statSync(p, { bigint: true });
786
+ if (s.isDirectory()) return null;
787
+ return {
788
+ ino: s.ino,
789
+ size: s.size,
790
+ mtimeNs: s.mtimeNs,
791
+ ctimeNs: s.ctimeNs
792
+ };
793
+ } catch {
794
+ return null;
795
+ }
796
+ }
797
+ /** The links between a link and the file it ends at: `a -> b -> c -> file` gives `b`, `c`. */
798
+ function linkHops(p) {
799
+ const hops = [];
800
+ let cur = p;
801
+ for (let n = 0; n < 32; n++) {
802
+ try {
803
+ cur = path.resolve(path.dirname(cur), readlinkSync(cur));
804
+ } catch {
805
+ break;
806
+ }
807
+ if (!isLink(cur)) break;
808
+ hops.push(cur);
809
+ }
810
+ return hops;
811
+ }
812
+ /** The stat of a link itself: it changes when the link is removed or pointed elsewhere. */
813
+ function linkSigOf(p) {
814
+ try {
815
+ const s = lstatSync(p, { bigint: true });
816
+ return {
817
+ ino: s.ino,
818
+ size: s.size,
819
+ mtimeNs: s.mtimeNs,
820
+ ctimeNs: s.ctimeNs
821
+ };
822
+ } catch {
823
+ return null;
824
+ }
825
+ }
826
+ /**
827
+ * Whether a file changed, from its stat before and after. `event`: an event named it, rather
828
+ * than a look made without one.
829
+ */
830
+ function changed(prev, next, event = true) {
831
+ if (!prev || !next) return prev !== next;
832
+ if (prev.ino !== next.ino || prev.size !== next.size || prev.mtimeNs !== next.mtimeNs || prev.ctimeNs !== next.ctimeNs) return true;
833
+ return event && next.mtimeNs % 1000000000n === 0n;
834
+ }
835
+ function idOf(dir) {
836
+ try {
837
+ const s = statSync(dir, { bigint: true });
838
+ return `${s.ino}:${s.birthtimeNs}`;
839
+ } catch {
840
+ return;
841
+ }
842
+ }
843
+ /**
844
+ * One non-recursive `fs.watch` per folder that holds a wanted file. A file whose folder does
845
+ * not exist yet is watched at the nearest existing ancestor, one level deeper as folders appear.
846
+ * `--include` roots are walked, and new folders in them are walked as they appear.
847
+ */
848
+ var DirWatcher = class {
849
+ #o;
850
+ #win;
851
+ /**
852
+ * `shown` is the name a change is reported under: a link, for a link's target.
853
+ * `own`: wanted itself, for the whole session or only by the current run's imports.
854
+ * `target` is the key of the file a link points to; `links`, the keys of the links a target is
855
+ * watched for.
856
+ */
857
+ #files = /* @__PURE__ */ new Map();
858
+ #byDir = /* @__PURE__ */ new Map();
859
+ #roots = [];
860
+ #walked = /* @__PURE__ */ new Map();
861
+ #handles = /* @__PURE__ */ new Map();
862
+ #awaited = /* @__PURE__ */ new Set();
863
+ #dropped = [];
864
+ #droppedSigs = /* @__PURE__ */ new Map();
865
+ #dropping;
866
+ #failed = /* @__PURE__ */ new Set();
867
+ #warned = /* @__PURE__ */ new Set();
868
+ #syncTimer;
869
+ #pollTimer;
870
+ #settleTimer;
871
+ #closed = false;
872
+ constructor(o) {
873
+ this.#o = o;
874
+ this.#win = o.platform === "win32";
875
+ if (o.poll !== false) this.#pollTimer = setInterval(() => this.#rewalk(), o.poll * 10);
876
+ }
877
+ #key(p) {
878
+ return this.#win ? p.toLowerCase() : p;
879
+ }
880
+ /**
881
+ * Watch one file, which may not exist yet. `run`: only the current run's imports want it, so
882
+ * `dropRun` drops it. `keepGone`: once deleted, it stays until it is back, for a file the next
883
+ * run looks for but cannot report (a discovered tsconfig.json). A link's target is watched for
884
+ * as long as the link points to it.
885
+ */
886
+ add(file, run = false, keepGone = false) {
887
+ const have = this.#files.get(this.#key(file));
888
+ if (have) {
889
+ if (have.own !== "static") have.own = run ? "run" : "static";
890
+ have.shown = file;
891
+ if (keepGone) have.keepGone = true;
892
+ if (have.target === void 0 && isLink(file)) this.#follow(have);
893
+ return;
894
+ }
895
+ if (this.#closed) return;
896
+ const entry = this.#put(file, file, run ? "run" : "static");
897
+ if (keepGone) entry.keepGone = true;
898
+ if (isLink(file)) this.#follow(entry);
899
+ }
900
+ /** Drop what only the last run's imports wanted: the next run reports its own. */
901
+ dropRun() {
902
+ const dirs = /* @__PURE__ */ new Set();
903
+ const sigs = /* @__PURE__ */ new Map();
904
+ this.#dropping = {
905
+ sigs,
906
+ dirs
907
+ };
908
+ for (const [k, f] of this.#files) {
909
+ if (f.own !== "run") continue;
910
+ if (f.gone && f.keepGone) continue;
911
+ f.own = void 0;
912
+ const [link] = f.links ?? [];
913
+ if (link !== void 0) {
914
+ f.shown = this.#files.get(link)?.path ?? f.shown;
915
+ continue;
916
+ }
917
+ this.#remove(k);
918
+ }
919
+ this.#dropping = void 0;
920
+ this.#droppedSigs = sigs;
921
+ if (this.#dropped.length > 0 && this.#o.poll === false) this.#scheduleSync();
922
+ this.#dropped = [...dirs];
923
+ }
924
+ #put(file, shown, own) {
925
+ const k = this.#key(file);
926
+ const entry = {
927
+ path: file,
928
+ shown,
929
+ sig: sigOf(file),
930
+ own
931
+ };
932
+ this.#files.set(k, entry);
933
+ if (this.#droppedSigs.has(k)) {
934
+ const before = this.#droppedSigs.get(k);
935
+ this.#droppedSigs.delete(k);
936
+ if (changed(before, entry.sig, false)) queueMicrotask(() => this.#closed || this.#o.onChange(shown));
937
+ }
938
+ const dk = this.#key(path.dirname(file));
939
+ const set = this.#byDir.get(dk);
940
+ if (set) set.add(k);
941
+ else this.#byDir.set(dk, /* @__PURE__ */ new Set([k]));
942
+ this.#o.debug(`watch ${file}`);
943
+ if (this.#o.poll === false) {
944
+ this.#scheduleSync();
945
+ return entry;
946
+ }
947
+ watchFile(file, {
948
+ interval: this.#o.poll,
949
+ bigint: true
950
+ }, () => {
951
+ if (this.#files.has(k)) this.#check(k);
952
+ });
953
+ return entry;
954
+ }
955
+ /** Walk `base` to `depth` levels for files the matcher accepts, now and as they appear. */
956
+ addRoot(base, depth) {
957
+ const root = {
958
+ base,
959
+ depth,
960
+ missing: false
961
+ };
962
+ this.#roots.push(root);
963
+ this.#walkRoot(root);
964
+ if (this.#o.poll === false) this.#scheduleSync();
965
+ }
966
+ close() {
967
+ this.#closed = true;
968
+ clearTimeout(this.#syncTimer);
969
+ clearInterval(this.#pollTimer);
970
+ clearTimeout(this.#settleTimer);
971
+ for (const h of this.#handles.values()) h.w.close();
972
+ this.#handles.clear();
973
+ if (this.#o.poll !== false) for (const f of this.#files.values()) unwatchFile(f.path);
974
+ }
975
+ #scheduleSync() {
976
+ if (!this.#syncTimer && !this.#closed) this.#syncTimer = setTimeout(() => this.#sync(), 20);
977
+ }
978
+ #walkRoot(root) {
979
+ if (!isDirOnDisk(root.base)) {
980
+ root.missing = true;
981
+ return false;
982
+ }
983
+ this.#walk(root.base, 0, root.depth, root.missing);
984
+ root.missing = false;
985
+ return true;
986
+ }
987
+ #walk(dir, level, depth, fire) {
988
+ const k = this.#key(dir);
989
+ const seen = this.#walked.get(k);
990
+ if (seen && seen.depth - seen.level >= depth - level) return;
991
+ this.#walked.set(k, {
992
+ dir,
993
+ depth,
994
+ level
995
+ });
996
+ let entries;
997
+ try {
998
+ entries = readdirSync(dir, { withFileTypes: true });
999
+ } catch {
1000
+ return;
1001
+ }
1002
+ for (const e of entries) {
1003
+ const full = path.join(dir, e.name);
1004
+ if (e.isDirectory()) {
1005
+ if (level + 1 < depth && !this.#o.matcher.pruneDir(full)) this.#walk(full, level + 1, depth, fire);
1006
+ } else this.#found(full, fire);
1007
+ }
1008
+ }
1009
+ #found(file, fire) {
1010
+ const have = this.#files.get(this.#key(file));
1011
+ if (have?.own === "static") return;
1012
+ try {
1013
+ if (!statSync(file).isFile()) return;
1014
+ } catch {
1015
+ return;
1016
+ }
1017
+ if (this.#o.matcher.classify(file, "walk") !== "watch") return;
1018
+ this.add(file);
1019
+ if (have) return;
1020
+ this.#follow(this.#files.get(this.#key(file)));
1021
+ if (fire) this.#o.onChange(file);
1022
+ }
1023
+ #follow(entry) {
1024
+ let real;
1025
+ try {
1026
+ real = realpathSync.native(entry.path);
1027
+ } catch {}
1028
+ const next = real && this.#key(real) !== this.#key(entry.path) ? this.#key(real) : void 0;
1029
+ entry.linkSig = next ? linkSigOf(entry.path) : void 0;
1030
+ const hops = next ? linkHops(entry.path).filter((h) => this.#key(h) !== next) : [];
1031
+ const hopKeys = hops.map((h) => this.#key(h));
1032
+ if (next === entry.target && hopKeys.join("\0") === (entry.hops ?? []).join("\0")) return;
1033
+ const link = this.#key(entry.path);
1034
+ for (const k of entry.hops ?? []) this.#release(k, link);
1035
+ if (entry.target !== void 0) this.#release(entry.target, link);
1036
+ entry.target = next;
1037
+ entry.hops = hopKeys;
1038
+ if (real && next && !this.#closed) {
1039
+ const target = this.#files.get(next) ?? this.#put(real, entry.path, void 0);
1040
+ (target.links ??= /* @__PURE__ */ new Set()).add(link);
1041
+ hops.forEach((h, i) => {
1042
+ const hop = this.#files.get(hopKeys[i]) ?? this.#put(h, entry.path, void 0);
1043
+ hop.linkSig ??= linkSigOf(h);
1044
+ (hop.links ??= /* @__PURE__ */ new Set()).add(link);
1045
+ });
1046
+ }
1047
+ }
1048
+ #release(k, link) {
1049
+ const target = this.#files.get(k);
1050
+ if (!target?.links) return;
1051
+ target.links.delete(link);
1052
+ if (target.own) return;
1053
+ const [rest] = target.links;
1054
+ if (rest === void 0) this.#remove(k);
1055
+ else target.shown = this.#files.get(rest)?.path ?? target.shown;
1056
+ }
1057
+ #remove(k) {
1058
+ const entry = this.#files.get(k);
1059
+ if (!entry) return;
1060
+ this.#files.delete(k);
1061
+ if (this.#dropping) {
1062
+ this.#dropping.sigs.set(k, entry.sig);
1063
+ this.#dropping.dirs.add(path.dirname(entry.path));
1064
+ }
1065
+ for (const h of entry.hops ?? []) this.#release(h, k);
1066
+ if (entry.target !== void 0) this.#release(entry.target, k);
1067
+ const dk = this.#key(path.dirname(entry.path));
1068
+ const set = this.#byDir.get(dk);
1069
+ set?.delete(k);
1070
+ if (set?.size === 0) this.#byDir.delete(dk);
1071
+ if (this.#o.poll !== false) unwatchFile(entry.path);
1072
+ else this.#scheduleSync();
1073
+ }
1074
+ #rewalk() {
1075
+ for (const k of this.#files.keys()) this.#check(k, false);
1076
+ this.#walked.clear();
1077
+ for (const root of this.#roots) {
1078
+ root.missing = true;
1079
+ this.#walkRoot(root);
1080
+ }
1081
+ }
1082
+ #sync() {
1083
+ this.#syncTimer = void 0;
1084
+ if (this.#closed) return;
1085
+ const exists = /* @__PURE__ */ new Map();
1086
+ const cachedExists = (d) => {
1087
+ let e = exists.get(d);
1088
+ if (e === void 0) exists.set(d, e = existsSync(d));
1089
+ return e;
1090
+ };
1091
+ const needed = /* @__PURE__ */ new Map();
1092
+ const awaited = /* @__PURE__ */ new Set();
1093
+ const place = (dir) => {
1094
+ let d = dir;
1095
+ let below;
1096
+ while (!cachedExists(d)) {
1097
+ const up = path.dirname(d);
1098
+ if (up === d) return;
1099
+ below = d;
1100
+ d = up;
1101
+ }
1102
+ if (below) awaited.add(this.#key(below));
1103
+ needed.set(this.#key(d), d);
1104
+ };
1105
+ for (const [k, w] of this.#walked) if (!existsSync(w.dir)) this.#walked.delete(k);
1106
+ for (const root of this.#roots) if (!this.#walkRoot(root)) place(root.base);
1107
+ for (const f of this.#files.values()) place(path.dirname(f.path));
1108
+ for (const d of this.#dropped) place(d);
1109
+ for (const [k, w] of this.#walked) needed.set(k, w.dir);
1110
+ this.#awaited = awaited;
1111
+ for (const [k, h] of this.#handles) if (!needed.has(k) || idOf(h.dir) !== h.id) this.#drop(k, h);
1112
+ const fresh = [];
1113
+ for (const [k, dir] of needed) if (!this.#handles.has(k) && !this.#failed.has(k) && this.#open(k, dir)) fresh.push(dir);
1114
+ for (const dir of fresh) {
1115
+ this.#checkDir(dir, false);
1116
+ const walked = this.#walked.get(this.#key(dir));
1117
+ if (walked) this.#rescan(walked);
1118
+ }
1119
+ }
1120
+ #drop(k, h) {
1121
+ h.w.close();
1122
+ this.#handles.delete(k);
1123
+ this.#restarted();
1124
+ this.#checkDir(h.dir, false);
1125
+ }
1126
+ #restarted() {
1127
+ if (this.#o.platform !== "darwin" || this.#closed) return;
1128
+ clearTimeout(this.#settleTimer);
1129
+ this.#settleTimer = setTimeout(() => {
1130
+ this.#settleTimer = void 0;
1131
+ for (const k of this.#files.keys()) this.#check(k, false);
1132
+ this.#scheduleSync();
1133
+ }, 250);
1134
+ }
1135
+ #rescan(walked) {
1136
+ let entries;
1137
+ try {
1138
+ entries = readdirSync(walked.dir);
1139
+ } catch {
1140
+ return;
1141
+ }
1142
+ for (const name of entries) this.#onWalkedEntry(walked, path.join(walked.dir, name));
1143
+ }
1144
+ #open(k, dir) {
1145
+ try {
1146
+ const id = idOf(dir);
1147
+ const w = (this.#o.watch ?? watch)(dir, (_type, name) => this.#onEvent(dir, w, name));
1148
+ const h = {
1149
+ w,
1150
+ dir,
1151
+ id
1152
+ };
1153
+ w.on("error", () => {
1154
+ if (this.#handles.get(k) === h) this.#drop(k, h);
1155
+ else w.close();
1156
+ this.#scheduleSync();
1157
+ });
1158
+ this.#handles.set(k, h);
1159
+ this.#restarted();
1160
+ this.#o.debug(`ready ${dir}`);
1161
+ return true;
1162
+ } catch (error) {
1163
+ const code = error.code ?? String(error);
1164
+ if (code === "ENOENT") {
1165
+ this.#scheduleSync();
1166
+ return false;
1167
+ }
1168
+ this.#failed.add(k);
1169
+ if (!this.#warned.has(code)) {
1170
+ this.#warned.add(code);
1171
+ const hint = code === "ENOSPC" || code === "EMFILE" ? " — raise fs.inotify.max_user_watches, or use --poll" : "";
1172
+ this.#o.warn(`cannot watch ${dir}: ${code}${hint}`);
1173
+ }
1174
+ return false;
1175
+ }
1176
+ }
1177
+ #onEvent(dir, w, name) {
1178
+ if (this.#closed) return;
1179
+ if (name === null) {
1180
+ this.#checkDir(dir);
1181
+ const walked = this.#walked.get(this.#key(dir));
1182
+ if (walked) this.#rescan(walked);
1183
+ this.#scheduleSync();
1184
+ return;
1185
+ }
1186
+ const full = path.join(dir, name);
1187
+ const k = this.#key(full);
1188
+ if (this.#files.has(k)) this.#check(k);
1189
+ else {
1190
+ const walked = this.#walked.get(this.#key(dir));
1191
+ if (walked) this.#onWalkedEntry(walked, full);
1192
+ }
1193
+ if (this.#awaited.has(k) || this.#handles.has(k) || this.#walked.has(k)) this.#scheduleSync();
1194
+ if (name === path.basename(dir) && !this.#files.has(k) && !existsSync(full)) {
1195
+ const dk = this.#key(dir);
1196
+ const h = this.#handles.get(dk);
1197
+ if (h?.w === w && this.#o.platform === "linux") this.#drop(dk, h);
1198
+ this.#scheduleSync();
1199
+ }
1200
+ const dk = this.#key(dir);
1201
+ const h = this.#handles.get(dk);
1202
+ if (h?.w !== w) return;
1203
+ if (idOf(dir) !== h.id) {
1204
+ this.#drop(dk, h);
1205
+ this.#scheduleSync();
1206
+ } else if (!existsSync(full)) this.#scheduleSync();
1207
+ }
1208
+ #onWalkedEntry(walked, full) {
1209
+ if (isRealDir(full)) {
1210
+ if (walked.level + 1 < walked.depth && !this.#o.matcher.pruneDir(full)) {
1211
+ this.#walk(full, walked.level + 1, walked.depth, true);
1212
+ this.#scheduleSync();
1213
+ }
1214
+ } else this.#found(full, true);
1215
+ }
1216
+ #checkDir(dir, event = true) {
1217
+ for (const k of this.#byDir.get(this.#key(dir)) ?? []) this.#check(k, event);
1218
+ }
1219
+ #check(k, event = true) {
1220
+ const entry = this.#files.get(k);
1221
+ const next = sigOf(entry.path);
1222
+ if (!changed(entry.sig, next, event)) {
1223
+ if (entry.linkSig !== void 0 && changed(entry.linkSig, linkSigOf(entry.path), false)) {
1224
+ let moved = false;
1225
+ const follow = (link) => {
1226
+ const before = link.target;
1227
+ this.#follow(link);
1228
+ if (next !== null && link.target !== before) moved = true;
1229
+ };
1230
+ if (entry.target !== void 0) follow(entry);
1231
+ else entry.linkSig = linkSigOf(entry.path);
1232
+ for (const l of Array.from(entry.links ?? [])) {
1233
+ const through = this.#files.get(l);
1234
+ if (through?.hops?.includes(k)) follow(through);
1235
+ }
1236
+ if (moved) this.#o.onChange(entry.shown);
1237
+ }
1238
+ return;
1239
+ }
1240
+ if (entry.sig && !next) entry.gone = true;
1241
+ else if (next) entry.gone = false;
1242
+ entry.sig = next;
1243
+ for (const l of entry.links ?? []) {
1244
+ const link = this.#files.get(l);
1245
+ if (link) link.sig = sigOf(link.path);
1246
+ }
1247
+ if (entry.own && (entry.target !== void 0 || isLink(entry.path))) this.#follow(entry);
1248
+ if (entry.links) {
1249
+ const link = isLink(entry.path);
1250
+ for (const l of Array.from(entry.links)) {
1251
+ const through = this.#files.get(l);
1252
+ if (through && (link || next && through.hops?.includes(k))) this.#follow(through);
1253
+ }
1254
+ }
1255
+ this.#o.onChange(entry.shown);
1256
+ }
1257
+ };
1258
+ // ../../oxc-node/packages/cli/src/watch/kill-tree.ts
1259
+ /** One `/proc/<pid>/stat` line. The name in parentheses may hold spaces and `)`. */
1260
+ function parseProcStat(text) {
1261
+ const close = text.lastIndexOf(")");
1262
+ if (close === -1) return null;
1263
+ const pid = Number.parseInt(text, 10);
1264
+ const fields = text.slice(close + 1).trim().split(/\s+/);
1265
+ const ppid = Number(fields[1]);
1266
+ if (!Number.isInteger(pid) || !Number.isInteger(ppid)) return null;
1267
+ return {
1268
+ pid,
1269
+ ppid,
1270
+ zombie: /^[ZXx]/.test(fields[0]) || void 0,
1271
+ start: fields[19]
1272
+ };
1273
+ }
1274
+ /** The output of `ps -A -o pid=,ppid=,stat=,lstart=`, or of `ps -A -o pid=,ppid=`. */
1275
+ function parsePs(text) {
1276
+ const rows = [];
1277
+ for (const line of text.split("\n")) {
1278
+ const m = /^\s*(\d+)\s+(\d+)(?:\s+(\S+)(?:\s+(\S.*?))?)?\s*$/.exec(line);
1279
+ if (m) rows.push({
1280
+ pid: Number(m[1]),
1281
+ ppid: Number(m[2]),
1282
+ zombie: m[3]?.startsWith("Z") || void 0,
1283
+ start: m[4]
1284
+ });
1285
+ }
1286
+ return rows;
1287
+ }
1288
+ /** Every process below `pid`, parents before their children. */
1289
+ function descendants(table, pid) {
1290
+ const children = /* @__PURE__ */ new Map();
1291
+ for (const row of table) {
1292
+ const list = children.get(row.ppid);
1293
+ if (list) list.push(row.pid);
1294
+ else children.set(row.ppid, [row.pid]);
1295
+ }
1296
+ const out = [];
1297
+ const seen = /* @__PURE__ */ new Set([pid]);
1298
+ const queue = [pid];
1299
+ while (queue.length) for (const child of children.get(queue.shift()) ?? []) {
1300
+ if (seen.has(child)) continue;
1301
+ seen.add(child);
1302
+ out.push(child);
1303
+ queue.push(child);
1304
+ }
1305
+ return out;
1306
+ }
1307
+ /** The rows of every process below `pid`, parents first. */
1308
+ function subtree(table, pid) {
1309
+ const byPid = new Map(table.map((r) => [r.pid, r]));
1310
+ return descendants(table, pid).map((p) => byPid.get(p));
1311
+ }
1312
+ /** Every process on the machine: `/proc` on Linux, where `ps` may be missing (slim containers), `ps` elsewhere. */
1313
+ function processTable() {
1314
+ if (process.platform === "linux") try {
1315
+ const rows = [];
1316
+ for (const name of readdirSync("/proc")) {
1317
+ if (!/^\d+$/.test(name)) continue;
1318
+ try {
1319
+ const row = parseProcStat(readFileSync(`/proc/${name}/stat`, "utf8"));
1320
+ if (row) rows.push(row);
1321
+ } catch {}
1322
+ }
1323
+ return rows;
1324
+ } catch {
1325
+ return [];
1326
+ }
1327
+ for (const columns of ["pid=,ppid=,stat=,lstart=", "pid=,ppid="]) try {
1328
+ return parsePs(execFileSync("ps", [
1329
+ "-A",
1330
+ "-o",
1331
+ columns
1332
+ ], {
1333
+ encoding: "utf8",
1334
+ stdio: [
1335
+ "ignore",
1336
+ "pipe",
1337
+ "ignore"
1338
+ ]
1339
+ }));
1340
+ } catch {}
1341
+ return [];
1342
+ }
1343
+ /**
1344
+ * Which of `rows` still run, by `table`, plus what they started since, parents first.
1345
+ * Gone: no row, a zombie, or another start (the PID was reused). A failed read (`[]`) means none.
1346
+ */
1347
+ function stillRunning(rows, table) {
1348
+ const now = new Map(table.map((r) => [r.pid, r]));
1349
+ const out = [];
1350
+ const seen = /* @__PURE__ */ new Set();
1351
+ for (const r of rows) {
1352
+ const row = now.get(r.pid);
1353
+ if (!row || row.start !== r.start || seen.has(r.pid)) continue;
1354
+ for (const p of [row.pid, ...descendants(table, row.pid)]) {
1355
+ const found = now.get(p);
1356
+ if (seen.has(p) || found.zombie) continue;
1357
+ seen.add(p);
1358
+ out.push(found);
1359
+ }
1360
+ }
1361
+ return out;
1362
+ }
1363
+ /** Send `sig` to each, and keep the ones it reached: ESRCH is gone, EPERM is not ours to wait on. */
1364
+ function signalAll(rows, sig) {
1365
+ return rows.filter((r) => {
1366
+ try {
1367
+ process.kill(r.pid, sig);
1368
+ return true;
1369
+ } catch {
1370
+ return false;
1371
+ }
1372
+ });
1373
+ }
1374
+ /**
1375
+ * SIGKILL the child, everything it started, and what is left of `known` with what that
1376
+ * started since, so a grandchild cannot keep a port busy. Returns what it reached besides
1377
+ * the child.
1378
+ */
1379
+ function killTree(child, known = []) {
1380
+ const pid = child?.pid;
1381
+ if (process.platform === "win32") {
1382
+ if (child && pid !== void 0) spawn("taskkill", [
1383
+ "/pid",
1384
+ String(pid),
1385
+ "/T",
1386
+ "/F"
1387
+ ], {
1388
+ stdio: "ignore",
1389
+ windowsHide: true
1390
+ }).on("error", () => child.kill());
1391
+ return [];
1392
+ }
1393
+ const table = processTable();
1394
+ const rows = stillRunning(pid === void 0 ? known : [...subtree(table, pid), ...known], table);
1395
+ child?.kill("SIGKILL");
1396
+ return signalAll(rows, "SIGKILL");
1397
+ }
1398
+ // ../../oxc-node/packages/cli/src/watch/log.ts
1399
+ let color;
1400
+ const pad = (n) => String(n).padStart(2, "0");
1401
+ /** A logger printing `HH:MM:SS [label] msg` on stdout, like tsx and Node.js. */
1402
+ function createLog(label) {
1403
+ return (msg) => {
1404
+ const d = /* @__PURE__ */ new Date();
1405
+ const time = `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1406
+ color ??= Boolean(process$1.stdout.isTTY && process$1.stdout.hasColors?.());
1407
+ process$1.stdout.write(color ? `\x1b[90m${time}\x1b[39m \x1b[36m[${label}]\x1b[39m ${msg}\n` : `${time} [${label}] ${msg}\n`);
1408
+ };
1409
+ }
1410
+ const debugOn = Boolean(process$1.env.OXNODE_WATCH_DEBUG);
1411
+ /** Lines on stderr that tests wait for (OXNODE_WATCH_DEBUG=1). */
1412
+ function debug(line) {
1413
+ if (debugOn) process$1.stderr.write(`${line}\n`);
1414
+ }
1415
+ const ms = (n) => n >= 1e3 && n % 1e3 === 0 ? `${n / 1e3}s` : `${n}ms`;
1416
+ function exitReason(code, signal) {
1417
+ if (signal) return `killed by ${signal}`;
1418
+ return code ? `exited with code ${code}` : "completed";
1419
+ }
1420
+ function trigger(s, reason, cfg) {
1421
+ const clear = cfg.clear ? [{ e: "clearScreen" }] : [];
1422
+ if (s.k === "idle") return {
1423
+ state: { k: "running" },
1424
+ effects: [
1425
+ ...clear,
1426
+ {
1427
+ e: "log",
1428
+ msg: `${reason}, rerunning`
1429
+ },
1430
+ { e: "spawn" }
1431
+ ]
1432
+ };
1433
+ const effects = [...clear, {
1434
+ e: "log",
1435
+ msg: `${reason}, restarting`
1436
+ }];
1437
+ if (cfg.killTimeout === 0) effects.push({ e: "killTree" });
1438
+ else effects.push({
1439
+ e: "kill",
1440
+ sig: cfg.killSignal
1441
+ }, {
1442
+ e: "startTimer",
1443
+ id: "kill",
1444
+ ms: cfg.killTimeout
1445
+ });
1446
+ return {
1447
+ state: {
1448
+ k: "stopping",
1449
+ after: "restart"
1450
+ },
1451
+ effects
1452
+ };
1453
+ }
1454
+ function step(s, ev, cfg) {
1455
+ const same = {
1456
+ state: s,
1457
+ effects: []
1458
+ };
1459
+ const hint = cfg.enter ? " (Enter to rerun)" : "";
1460
+ if (s.k === "done") return same;
1461
+ if (ev.t === "spawnError") return {
1462
+ state: { k: "done" },
1463
+ effects: [
1464
+ {
1465
+ e: "clearTimer",
1466
+ id: "kill"
1467
+ },
1468
+ {
1469
+ e: "log",
1470
+ msg: `cannot start: ${ev.err}`
1471
+ },
1472
+ {
1473
+ e: "exit",
1474
+ code: 1
1475
+ }
1476
+ ]
1477
+ };
1478
+ switch (s.k) {
1479
+ case "idle":
1480
+ case "running": switch (ev.t) {
1481
+ case "start":
1482
+ if (s.k !== "idle") return same;
1483
+ return {
1484
+ state: { k: "running" },
1485
+ effects: [{
1486
+ e: "log",
1487
+ msg: `watching for changes${hint}`
1488
+ }, { e: "spawn" }]
1489
+ };
1490
+ case "change": {
1491
+ const db = s.debounce;
1492
+ if (!db) return {
1493
+ state: {
1494
+ ...s,
1495
+ debounce: {
1496
+ firstAt: ev.now,
1497
+ paths: [ev.path]
1498
+ }
1499
+ },
1500
+ effects: [{
1501
+ e: "startTimer",
1502
+ id: "debounce",
1503
+ ms: 100
1504
+ }]
1505
+ };
1506
+ const paths = db.paths.includes(ev.path) ? db.paths : [...db.paths, ev.path];
1507
+ const left = Math.max(0, 500 - (ev.now - db.firstAt));
1508
+ return {
1509
+ state: {
1510
+ ...s,
1511
+ debounce: {
1512
+ firstAt: db.firstAt,
1513
+ paths
1514
+ }
1515
+ },
1516
+ effects: [{
1517
+ e: "startTimer",
1518
+ id: "debounce",
1519
+ ms: Math.min(100, left)
1520
+ }]
1521
+ };
1522
+ }
1523
+ case "debounceFired": {
1524
+ const db = s.debounce;
1525
+ if (!db) return same;
1526
+ const more = db.paths.length > 1 ? ` (+${db.paths.length - 1} more)` : "";
1527
+ return trigger(s, `change in ${db.paths[0]}${more}`, cfg);
1528
+ }
1529
+ case "enter": {
1530
+ const r = trigger(s, "Enter", cfg);
1531
+ return s.debounce ? {
1532
+ ...r,
1533
+ effects: [{
1534
+ e: "clearTimer",
1535
+ id: "debounce"
1536
+ }, ...r.effects]
1537
+ } : r;
1538
+ }
1539
+ case "childExit": {
1540
+ if (s.k !== "running") return same;
1541
+ const lastExit = exitReason(ev.code, ev.signal);
1542
+ return {
1543
+ state: {
1544
+ k: "idle",
1545
+ debounce: s.debounce
1546
+ },
1547
+ effects: [{
1548
+ e: "log",
1549
+ msg: `${lastExit} — waiting for changes${hint}`
1550
+ }]
1551
+ };
1552
+ }
1553
+ case "signal":
1554
+ if (s.k === "idle") return {
1555
+ state: { k: "done" },
1556
+ effects: [{
1557
+ e: "exit",
1558
+ code: 0
1559
+ }]
1560
+ };
1561
+ return {
1562
+ state: {
1563
+ k: "stopping",
1564
+ after: "exit",
1565
+ firstSig: ev.sig,
1566
+ firstSigAt: ev.now
1567
+ },
1568
+ effects: [
1569
+ {
1570
+ e: "clearTimer",
1571
+ id: "debounce"
1572
+ },
1573
+ {
1574
+ e: "forwardSignal",
1575
+ sig: ev.sig
1576
+ },
1577
+ {
1578
+ e: "startTimer",
1579
+ id: "kill",
1580
+ ms: cfg.killTimeout
1581
+ }
1582
+ ]
1583
+ };
1584
+ default: return same;
1585
+ }
1586
+ case "stopping": switch (ev.t) {
1587
+ case "change": return same;
1588
+ case "enter": return {
1589
+ state: s,
1590
+ effects: [{
1591
+ e: "log",
1592
+ msg: "force killing"
1593
+ }, { e: "killTree" }]
1594
+ };
1595
+ case "killTimeout": return {
1596
+ state: s,
1597
+ effects: [{
1598
+ e: "log",
1599
+ msg: `still running after ${ms(cfg.killTimeout)}, force killing`
1600
+ }, { e: "killTree" }]
1601
+ };
1602
+ case "childExit":
1603
+ if (s.after === "restart") return {
1604
+ state: { k: "running" },
1605
+ effects: [{
1606
+ e: "clearTimer",
1607
+ id: "kill"
1608
+ }, { e: "spawn" }]
1609
+ };
1610
+ return {
1611
+ state: { k: "done" },
1612
+ effects: [{
1613
+ e: "clearTimer",
1614
+ id: "kill"
1615
+ }, {
1616
+ e: "exit",
1617
+ code: ev.code ?? 0
1618
+ }]
1619
+ };
1620
+ case "signal": {
1621
+ if (s.after === "restart") return {
1622
+ state: {
1623
+ ...s,
1624
+ after: "exit",
1625
+ firstSig: ev.sig,
1626
+ firstSigAt: ev.now
1627
+ },
1628
+ effects: [{
1629
+ e: "forwardSignal",
1630
+ sig: ev.sig
1631
+ }]
1632
+ };
1633
+ const since = ev.now - (s.firstSigAt ?? ev.now);
1634
+ if (ev.sig === "SIGINT" && since > 500) return {
1635
+ state: s,
1636
+ effects: [{
1637
+ e: "log",
1638
+ msg: "force killing"
1639
+ }, { e: "killTree" }]
1640
+ };
1641
+ if (ev.sig === s.firstSig && since <= 500) return same;
1642
+ return {
1643
+ state: s,
1644
+ effects: [{
1645
+ e: "forwardSignal",
1646
+ sig: ev.sig
1647
+ }]
1648
+ };
1649
+ }
1650
+ default: return same;
1651
+ }
1652
+ }
1653
+ }
1654
+ // ../../oxc-node/packages/cli/src/watch/messages.ts
1655
+ /** Whether oxc-node sent this report: a tsconfig it read or a path a failed resolve tried. */
1656
+ function fromOxcNode(msg) {
1657
+ return typeof msg === "object" && msg !== null && msg["oxc-node"] === true;
1658
+ }
1659
+ /**
1660
+ * The paths in a `watch:require` / `watch:import` message, or null when it is not one.
1661
+ * Each entry is read on its own: Node.js drops the whole array when one entry is bad.
1662
+ */
1663
+ function parseWatchMessage(msg) {
1664
+ if (typeof msg !== "object" || msg === null) return null;
1665
+ const record = msg;
1666
+ const lists = [record["watch:require"], record["watch:import"]];
1667
+ if (lists.every((list) => list === void 0)) return null;
1668
+ const paths = [];
1669
+ for (const list of lists) {
1670
+ if (!Array.isArray(list)) continue;
1671
+ for (const entry of list) try {
1672
+ if (typeof entry !== "string" || entry === "") continue;
1673
+ if (entry.startsWith("file:")) {
1674
+ paths.push(fileURLToPath(entry));
1675
+ continue;
1676
+ }
1677
+ if (/^[a-z][a-z\d+.-]+:/i.test(entry)) continue;
1678
+ if (!isAbsolute(entry)) continue;
1679
+ paths.push(resolve(entry));
1680
+ } catch {}
1681
+ }
1682
+ return paths;
1683
+ }
1684
+ // ../../oxc-node/packages/cli/src/watch/command.ts
1685
+ const isBareReturn = (chunk) => /[\r\n]/.test(chunk) && chunk.replace(/[\r\n]/g, "") === "";
1686
+ /** How soon a rerun looks again for what the app left running: fast first, then every 250 ms. */
1687
+ const TREE_POLL_MS = 25;
1688
+ const TREE_POLL_MAX_MS = 250;
1689
+ /** How long after a force kill oxnode waits for what is left: SIGKILL cannot end a process stuck on a hung mount. */
1690
+ const FORCE_WAIT_MS = 1e3;
1691
+ /**
1692
+ * Run the watcher until it stops, and resolve with the exit code. Until then it owns
1693
+ * SIGINT, SIGTERM and SIGHUP, stdin (for Return) and the process's IPC channel.
1694
+ */
1695
+ function runWatch(argv, options = {}) {
1696
+ const names = namesFor(options.name);
1697
+ const log = createLog(names.label);
1698
+ const args = parseWatchArgs(argv, process$1.env, void 0, void 0, names);
1699
+ if ("error" in args) {
1700
+ process$1.stderr.write(`${names.name}: ${args.error}\n${usage(names)}\nRun "${names.name} --help" for the options.\n`);
1701
+ return Promise.resolve(1);
1702
+ }
1703
+ if (args.help) {
1704
+ process$1.stdout.write(help(names));
1705
+ return Promise.resolve(0);
1706
+ }
1707
+ const cwd = process$1.cwd();
1708
+ let { loaderArgs, ignoreDirs: given = [] } = options;
1709
+ if (!loaderArgs) {
1710
+ const registerURL = import.meta.resolve("@oxc-node/core/register");
1711
+ loaderArgs = [
1712
+ `--enable-source-maps`,
1713
+ `--import`,
1714
+ registerURL
1715
+ ];
1716
+ if (!options.ignoreDirs) given = [path.dirname(fileURLToPath(registerURL))];
1717
+ }
1718
+ const ignoreDirs = [];
1719
+ for (const dir of given) {
1720
+ ignoreDirs.push(dir);
1721
+ try {
1722
+ const real = realpathSync.native(dir);
1723
+ if (real !== dir) ignoreDirs.push(real);
1724
+ } catch {}
1725
+ }
1726
+ let matcher;
1727
+ try {
1728
+ matcher = makeMatcher({
1729
+ cwd,
1730
+ include: args.include,
1731
+ exclude: args.exclude,
1732
+ platform: process$1.platform,
1733
+ ignoreDirs
1734
+ });
1735
+ } catch (error) {
1736
+ process$1.stderr.write(`${names.name}: ${error.message}\n`);
1737
+ return Promise.resolve(1);
1738
+ }
1739
+ for (const note of args.notes) log(note);
1740
+ return new Promise((resolve) => {
1741
+ const ack = createSignalAck();
1742
+ let child;
1743
+ let latest;
1744
+ const signals = gateSignals(new SignalGate({
1745
+ nodeWatch: false,
1746
+ platform: process$1.platform,
1747
+ terminal: sigintFromTerminal
1748
+ }), ack, (sig) => child?.kill(sig), debug);
1749
+ const enter = args.rerunOnEnter ?? sigintFromTerminal();
1750
+ const cfg = {
1751
+ killSignal: args.killSignal,
1752
+ killTimeout: args.killTimeout,
1753
+ clear: args.clearScreen && Boolean(process$1.stdout.isTTY),
1754
+ enter
1755
+ };
1756
+ const env = {
1757
+ ...process$1.env,
1758
+ WATCH_REPORT_DEPENDENCIES: "1",
1759
+ ...ack.env,
1760
+ ...tsconfigEnv(args.tsconfig, cwd)
1761
+ };
1762
+ const nodeArgs = [...loaderArgs, ...args.nodeArgs];
1763
+ const timers = /* @__PURE__ */ new Map();
1764
+ let state = { k: "idle" };
1765
+ let tree = [];
1766
+ let forcedAt;
1767
+ let treeTimer;
1768
+ /** Signal what is left of `tree` once, and call `then` when all of it is gone. */
1769
+ const afterTree = (then, tick = 0) => {
1770
+ if (tree.length) {
1771
+ tree = stillRunning(tree, processTable());
1772
+ if (tick === 0 || forcedAt !== void 0) {
1773
+ const sig = forcedAt === void 0 ? cfg.killSignal : "SIGKILL";
1774
+ tree = signalAll(tree, sig);
1775
+ }
1776
+ }
1777
+ if (tree.length && forcedAt !== void 0 && Date.now() - forcedAt >= FORCE_WAIT_MS) {
1778
+ log(`${tree.map((r) => r.pid).join(" ")} did not stop after a force kill, going on`);
1779
+ tree = [];
1780
+ }
1781
+ if (tree.length === 0) return then();
1782
+ if (tick === 0) debug(`waiting for ${tree.map((r) => r.pid).join(" ")}`);
1783
+ treeTimer = setTimeout(() => afterTree(then, tick + 1), Math.min(TREE_POLL_MAX_MS, TREE_POLL_MS * 2 ** tick));
1784
+ };
1785
+ const display = (p) => {
1786
+ const rel = path.relative(cwd, p);
1787
+ return rel && !rel.startsWith("..") && !path.isAbsolute(rel) ? `.${path.sep}${rel}` : p;
1788
+ };
1789
+ const fold = (p) => process$1.platform === "win32" ? p.toLowerCase() : p;
1790
+ const reported = /* @__PURE__ */ new Map();
1791
+ const runAliases = /* @__PURE__ */ new Set();
1792
+ const watcher = new DirWatcher({
1793
+ poll: args.poll,
1794
+ platform: process$1.platform,
1795
+ matcher,
1796
+ onChange: (p) => dispatch({
1797
+ t: "change",
1798
+ path: display(reported.get(fold(p)) ?? p),
1799
+ now: Date.now()
1800
+ }),
1801
+ debug,
1802
+ warn: log
1803
+ });
1804
+ const seen = /* @__PURE__ */ new Set();
1805
+ const runSeen = /* @__PURE__ */ new Set();
1806
+ const want = (p, source) => {
1807
+ const key = source === "oxc" ? `oxc:${p}` : p;
1808
+ if (seen.has(p) || runSeen.has(key)) return;
1809
+ const run = source === "graph" || source === "oxc";
1810
+ (run ? runSeen : seen).add(key);
1811
+ const verdict = matcher.classify(p, source);
1812
+ if (verdict === "watch") watcher.add(p, run, source === "oxc");
1813
+ else debug(`skip ${p} (${verdict === "exclude" ? "exclude" : "default"})`);
1814
+ if (verdict === "exclude") return;
1815
+ try {
1816
+ const real = realpathSync.native(p);
1817
+ if (real !== p) {
1818
+ if (!reported.has(fold(real))) {
1819
+ reported.set(fold(real), p);
1820
+ if (run) runAliases.add(fold(real));
1821
+ }
1822
+ if (source === "literal" && lstatSync(p).isSymbolicLink()) return;
1823
+ want(real, source);
1824
+ }
1825
+ } catch {}
1826
+ };
1827
+ const newRun = () => {
1828
+ runSeen.clear();
1829
+ for (const k of runAliases) reported.delete(k);
1830
+ runAliases.clear();
1831
+ watcher.dropRun();
1832
+ };
1833
+ const spawnChild = () => {
1834
+ const nested = nestedWatcher(args, process$1.env);
1835
+ if (nested) {
1836
+ queueMicrotask(() => dispatch({
1837
+ t: "spawnError",
1838
+ err: nested
1839
+ }));
1840
+ return;
1841
+ }
1842
+ tree = [];
1843
+ forcedAt = void 0;
1844
+ newRun();
1845
+ const c = spawn(process$1.execPath, nodeArgs, {
1846
+ env,
1847
+ stdio: [
1848
+ "inherit",
1849
+ "inherit",
1850
+ "inherit",
1851
+ "ipc"
1852
+ ],
1853
+ windowsHide: false
1854
+ });
1855
+ child = c;
1856
+ latest = c;
1857
+ c.on("message", (msg) => {
1858
+ const paths = parseWatchMessage(msg);
1859
+ if (paths) {
1860
+ const source = fromOxcNode(msg) ? "oxc" : "graph";
1861
+ if (c === latest) for (const p of paths) want(p, source);
1862
+ } else if (process$1.send && process$1.connected) process$1.send(msg, () => {});
1863
+ });
1864
+ c.on("error", (error) => {
1865
+ if (c.pid === void 0 && child === c) {
1866
+ child = void 0;
1867
+ dispatch({
1868
+ t: "spawnError",
1869
+ err: error.message
1870
+ });
1871
+ }
1872
+ });
1873
+ c.on("exit", (code, signal) => {
1874
+ if (child !== c) return;
1875
+ child = void 0;
1876
+ afterTree(() => dispatch({
1877
+ t: "childExit",
1878
+ code,
1879
+ signal
1880
+ }));
1881
+ });
1882
+ if (c.pid !== void 0) debug(`spawn ${c.pid}`);
1883
+ };
1884
+ const run = (e) => {
1885
+ switch (e.e) {
1886
+ case "spawn":
1887
+ spawnChild();
1888
+ break;
1889
+ case "kill":
1890
+ if (!child) break;
1891
+ if (process$1.platform === "win32") killTree(child);
1892
+ else {
1893
+ if (child.pid !== void 0) tree = subtree(processTable(), child.pid);
1894
+ child.kill(e.sig);
1895
+ }
1896
+ break;
1897
+ case "killTree":
1898
+ forcedAt ??= Date.now();
1899
+ tree = killTree(child, tree);
1900
+ break;
1901
+ case "clearScreen":
1902
+ process$1.stdout.write("\x1Bc");
1903
+ break;
1904
+ case "log":
1905
+ log(e.msg);
1906
+ break;
1907
+ case "startTimer": {
1908
+ clearTimeout(timers.get(e.id));
1909
+ const ev = e.id === "debounce" ? { t: "debounceFired" } : { t: "killTimeout" };
1910
+ timers.set(e.id, setTimeout(() => {
1911
+ timers.delete(e.id);
1912
+ dispatch(ev);
1913
+ }, e.ms));
1914
+ break;
1915
+ }
1916
+ case "clearTimer":
1917
+ clearTimeout(timers.get(e.id));
1918
+ timers.delete(e.id);
1919
+ break;
1920
+ case "forwardSignal":
1921
+ signals.signal(e.sig);
1922
+ break;
1923
+ case "exit": finish(e.code);
1924
+ }
1925
+ };
1926
+ function dispatch(ev) {
1927
+ const r = step(state, ev, cfg);
1928
+ state = r.state;
1929
+ for (const e of r.effects) run(e);
1930
+ }
1931
+ const onSignal = (sig) => dispatch({
1932
+ t: "signal",
1933
+ sig,
1934
+ now: Date.now()
1935
+ });
1936
+ const onParentMessage = (msg) => {
1937
+ if (child?.connected) child.send(msg, () => {});
1938
+ };
1939
+ let reading = false;
1940
+ const onData = (chunk) => {
1941
+ if (isBareReturn(chunk)) dispatch({ t: "enter" });
1942
+ };
1943
+ const stopReading = () => {
1944
+ if (!reading) return;
1945
+ reading = false;
1946
+ process$1.stdin.off("data", onData).off("end", stopReading).off("error", stopReading);
1947
+ process$1.stdin.pause();
1948
+ process$1.stdin.unref?.();
1949
+ };
1950
+ function finish(code) {
1951
+ clearTimeout(treeTimer);
1952
+ tree = [];
1953
+ for (const t of timers.values()) clearTimeout(t);
1954
+ timers.clear();
1955
+ signals.dispose();
1956
+ ack.close();
1957
+ watcher.close();
1958
+ stopReading();
1959
+ for (const sig of FORWARDED) process$1.off(sig, onSignal);
1960
+ process$1.off("message", onParentMessage);
1961
+ resolve(code);
1962
+ }
1963
+ for (const sig of FORWARDED) process$1.on(sig, onSignal);
1964
+ if (process$1.send) process$1.on("message", onParentMessage);
1965
+ if (enter) {
1966
+ reading = true;
1967
+ process$1.stdin.setEncoding("utf8");
1968
+ process$1.stdin.on("data", onData).on("end", stopReading).on("error", stopReading);
1969
+ }
1970
+ want(path.resolve(cwd, args.entry), "literal");
1971
+ for (const file of args.envFiles) want(path.resolve(cwd, file), "literal");
1972
+ for (const file of args.configFiles) want(path.resolve(cwd, file), "literal");
1973
+ if (args.tsconfig) want(path.resolve(cwd, args.tsconfig), "literal");
1974
+ for (const file of matcher.literals()) want(file, "literal");
1975
+ for (const root of matcher.roots()) watcher.addRoot(root.base, root.depth);
1976
+ dispatch({ t: "start" });
1977
+ });
1978
+ }
1979
+ // src/script-watch.ts
1980
+ /**
1981
+ * `vpx --watch`: oxc-node's watcher (`@oxc-node/cli/watch`), bundled from the vendored
1982
+ * source, running the script with the `vpx <script>` loader instead of Node.js' own watch
1983
+ * mode. Built to `dist/script-watch.js`.
1984
+ *
1985
+ * The global `vpx` runs `node dist/script-watch.js <args>`, and the project-local bin
1986
+ * imports it in process; either way the watcher's arguments are `process.argv.slice(2)`,
1987
+ * the `vpx` arguments without `--watch` and `--tsconfig`, which the CLIs pass on as
1988
+ * `VP_SCRIPT_TSCONFIG`, as for a script. The watcher owns SIGINT, SIGTERM, SIGHUP,
1989
+ * stdin, and the IPC channel until it stops.
1990
+ */
1991
+ const dist = path.dirname(fileURLToPath(import.meta.url));
1992
+ process.exitCode = await runWatch(process.argv.slice(2), {
1993
+ name: "vpx --watch",
1994
+ loaderArgs: [
1995
+ "--require",
1996
+ path.join(dist, "script-preload.cjs"),
1997
+ "--import",
1998
+ new URL("./script-register.js", import.meta.url).href
1999
+ ],
2000
+ ignoreDirs: [path.dirname(dist)]
2001
+ });
2002
+ export {};