vite-plus 1.3.0 → 1.4.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.
- package/AGENTS.md +4 -0
- package/LICENSE +1 -878
- package/README.md +1 -1
- package/bin/vpx +9 -0
- package/binding/index.cjs +59 -54
- package/binding/index.d.cts +128 -2
- package/dist/{agent-97KmGCyL.js → agent-D8oxxhVR.js} +5 -7
- package/dist/bin.js +25 -32
- package/dist/{cli-parse-DHE8NrBU.js → cli-parse-aeUeFDlC.js} +2 -3
- package/dist/command-TjS_6y9O.js +1 -0
- package/dist/{command-CdMBoZFD.js → command-rY79K5NI.js} +28 -30
- package/dist/{comparison-CmVirWIW-DUGaiYqF.js → comparison-CmVirWIW-UlEf2maz.js} +1 -2
- package/dist/config/bin.js +8 -10
- package/dist/{constants-DR55DpRm.js → constants-DRUnJ_Dz.js} +3 -5
- package/dist/create/bin.d.ts +4 -8
- package/dist/create/bin.js +46 -65
- package/dist/{define-config-CvE5pHqB.js → define-config-ChwJcu5e.js} +6 -9
- package/dist/{define-config-BxDc_9BK.cjs → define-config-Cv2P6NT2.cjs} +7 -12
- package/dist/{define-config-Bexdd6RZ.d.ts → define-config-rPhYIFSn.d.ts} +1 -11
- package/dist/define-config.cjs +1 -1
- package/dist/define-config.d.ts +1 -1
- package/dist/define-config.js +1 -1
- package/dist/{dist-DIpm7mot.js → dist-Bs0SObNC.js} +73 -145
- package/dist/{dist-Cqz8OtiO.js → dist-CdkMvIiQ.js} +11 -5
- package/dist/dist-zDZV5eEc.js +3 -0
- package/dist/{documentation-DqcZSthV.js → documentation-cv_y0Auv.js} +1 -2
- package/dist/{editor-CtJGQP7C.js → editor-ClL78D8u.js} +1490 -4209
- package/dist/{help-BmKpeOP9.js → help-B03t_iQ4.js} +1 -2
- package/dist/hooks/bin.js +4 -5
- package/dist/{hooks-DhwsWb46.js → hooks-xRcJPc-O.js} +5 -4
- package/dist/index.cjs +2 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{json-JP24qHUF.js → json-kl84kDyH.js} +9 -18
- package/dist/lint-plugins.cjs +1 -1
- package/dist/migration/bin.js +719 -184
- package/dist/migration/compat/worker.js +4 -6
- package/dist/{oxlint-plugin-config-BIzrO2y2.js → oxlint-plugin-config-BVpqYdcC.js} +3 -4
- package/dist/oxlint-plugin.d.ts +0 -2
- package/dist/oxlint-plugin.js +6 -7
- package/dist/{pack-BK38MLVZ.d.ts → pack-ARPFMefW.d.ts} +0 -2
- package/dist/pack-bin.d.ts +1 -3
- package/dist/pack-bin.js +3 -4
- package/dist/pack.d.ts +1 -1
- package/dist/{package-CQDXTm6j.js → package-DzsomcUD.js} +10 -95
- package/dist/{package-overrides-D1RcXSFl.js → package-overrides-Ci8GXouY.js} +5 -8
- package/dist/picomatch-DEUoj81q.js +1916 -0
- package/dist/{prompts-1xNz_ne0.js → prompts-GL1vj0wk.js} +30 -50
- package/dist/{protocol-D99W10Qi.js → protocol-CkNmY-Mr.js} +1 -2
- package/dist/{report-NhQk8VHb.js → report-DLHO30yZ.js} +2 -2
- package/dist/{resolve-vite-config-BVr1SUNg.js → resolve-vite-config-Dn5896A4.js} +9 -25
- package/dist/resolve-vite-config-QO6cC0SP.js +1 -0
- package/dist/{rolldown-runtime-kJ_i4yPo.js → rolldown-runtime-DegdyBIC.js} +1 -2
- package/dist/script-esm-hooks.d.ts +8 -0
- package/dist/script-esm-hooks.js +24 -0
- package/dist/script-hooks-9Fof5UMY.js +176 -0
- package/dist/script-preload.cjs +468 -0
- package/dist/script-register.d.ts +1 -0
- package/dist/script-register.js +22 -0
- package/dist/script-watch.d.ts +1 -0
- package/dist/script-watch.js +1908 -0
- package/dist/staged/bin.js +53 -2011
- package/dist/sync-versions/bin.js +26 -48
- package/dist/sync-versions/bin.mjs +109 -217
- package/dist/{terminal-MKGAuy-p.js → terminal-BcuPUy-j.js} +1 -2
- package/dist/toolchain.js +35 -9
- package/dist/toolchain.json +35 -9
- package/dist/{tsconfig-zWgHiQ3X.js → tsconfig-Do6MAG_f.js} +1 -1
- package/dist/{tsconfig-BZu7kl-x.js → tsconfig-LE_mAwe_.js} +8 -10
- package/dist/version.d.ts +1 -3
- package/dist/version.js +5 -6
- package/dist/versions.js +4 -4
- package/dist/{vitest-ecosystem-BU0AqZq-.js → vitest-ecosystem-BeEYz-qF.js} +1 -2
- package/dist/{vitest-v5-entry-points-DthU1e_O.js → vitest-v5-entry-points-NyJsFeux.js} +1 -2
- package/dist/vpx-bin.d.ts +1 -0
- package/dist/vpx-bin.js +232 -0
- package/dist/vpx-script-EXgV5QOf.js +434 -0
- package/docs/config/run.md +4 -0
- package/docs/guide/check.md +1 -0
- package/docs/guide/ci.md +13 -3
- package/docs/guide/env.md +11 -4
- package/docs/guide/global-cli.md +52 -0
- package/docs/guide/migrate-rules.md +48 -5
- package/docs/guide/migrate.md +5 -5
- package/docs/guide/package-management.md +1 -1
- package/docs/guide/vpx.md +170 -9
- package/package.json +20 -17
- package/dist/command-DVTILtS-.js +0 -1
- package/dist/dist-Dhu07wmN.js +0 -3
- package/dist/resolve-vite-config-7WDtXO4G.js +0 -1
|
@@ -0,0 +1,1908 @@
|
|
|
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
|
+
/** The child env for `--tsconfig`: oxc-node reads TS_NODE_PROJECT before OXC_TSCONFIG_PATH. */
|
|
431
|
+
function tsconfigEnv(tsconfig, cwd) {
|
|
432
|
+
return tsconfig ? {
|
|
433
|
+
OXC_TSCONFIG_PATH: resolve(cwd, tsconfig),
|
|
434
|
+
TS_NODE_PROJECT: void 0
|
|
435
|
+
} : {};
|
|
436
|
+
}
|
|
437
|
+
// ../../oxc-node/packages/cli/src/run.ts
|
|
438
|
+
/** The signals oxnode passes on to the app. On Windows it only listens, so it outlives the app. */
|
|
439
|
+
const FORWARDED = process$1.platform === "win32" ? [
|
|
440
|
+
"SIGINT",
|
|
441
|
+
"SIGBREAK",
|
|
442
|
+
"SIGHUP"
|
|
443
|
+
] : [
|
|
444
|
+
"SIGINT",
|
|
445
|
+
"SIGTERM",
|
|
446
|
+
"SIGHUP"
|
|
447
|
+
];
|
|
448
|
+
/**
|
|
449
|
+
* A unix socket the child writes "I" to when the terminal already sent it SIGINT
|
|
450
|
+
* (`@oxc-node/core/signal-ack.mjs`), so oxnode does not send it a second one.
|
|
451
|
+
*/
|
|
452
|
+
function createSignalAck() {
|
|
453
|
+
const callbacks = [];
|
|
454
|
+
const none = {
|
|
455
|
+
env: {},
|
|
456
|
+
onAck: () => {},
|
|
457
|
+
close: () => {}
|
|
458
|
+
};
|
|
459
|
+
if (process$1.platform === "win32") return none;
|
|
460
|
+
const path = join(os.tmpdir(), `oxnode-${process$1.pid}-${randomBytes(4).toString("hex")}.sock`);
|
|
461
|
+
if (Buffer.byteLength(path) > 100) return none;
|
|
462
|
+
const server = createServer((socket) => {
|
|
463
|
+
socket.unref();
|
|
464
|
+
socket.on("error", () => {});
|
|
465
|
+
socket.on("data", (data) => {
|
|
466
|
+
for (const byte of data) if (byte === 73) for (const cb of callbacks) cb();
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
let closed = false;
|
|
470
|
+
const close = () => {
|
|
471
|
+
if (closed) return;
|
|
472
|
+
closed = true;
|
|
473
|
+
process$1.off("exit", close);
|
|
474
|
+
process$1.off("SIGQUIT", onQuit);
|
|
475
|
+
server.close();
|
|
476
|
+
try {
|
|
477
|
+
unlinkSync(path);
|
|
478
|
+
} catch {}
|
|
479
|
+
};
|
|
480
|
+
server.on("error", close);
|
|
481
|
+
try {
|
|
482
|
+
server.listen(path);
|
|
483
|
+
} catch {
|
|
484
|
+
return none;
|
|
485
|
+
}
|
|
486
|
+
server.unref();
|
|
487
|
+
process$1.on("exit", close);
|
|
488
|
+
function onQuit() {
|
|
489
|
+
close();
|
|
490
|
+
process$1.kill(process$1.pid, "SIGQUIT");
|
|
491
|
+
}
|
|
492
|
+
process$1.once("SIGQUIT", onQuit);
|
|
493
|
+
return {
|
|
494
|
+
env: { OXNODE_SIGNAL_ACK: path },
|
|
495
|
+
onAck: (cb) => void callbacks.push(cb),
|
|
496
|
+
close
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
/** Whether oxnode is in the terminal's foreground group: reading stdin from the background stops it (SIGTTIN). */
|
|
500
|
+
function isForeground() {
|
|
501
|
+
try {
|
|
502
|
+
if (process$1.platform === "win32") return true;
|
|
503
|
+
if (process$1.platform === "linux") {
|
|
504
|
+
const stat = readFileSync("/proc/self/stat", "utf8");
|
|
505
|
+
const f = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/);
|
|
506
|
+
return f[2] === f[5];
|
|
507
|
+
}
|
|
508
|
+
const [pgid, tpgid] = execFileSync("ps", [
|
|
509
|
+
"-o",
|
|
510
|
+
"pgid=,tpgid=",
|
|
511
|
+
"-p",
|
|
512
|
+
String(process$1.pid)
|
|
513
|
+
], { encoding: "utf8" }).trim().split(/\s+/);
|
|
514
|
+
return pgid === tpgid;
|
|
515
|
+
} catch {
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Whether a SIGINT oxnode gets comes from the terminal's Ctrl+C, which signals the whole
|
|
521
|
+
* foreground group, the child included. Ask it once the SIGINT is in: a Ctrl+C during the
|
|
522
|
+
* check also kills the `ps` it runs on macOS, and the answer would be false.
|
|
523
|
+
*/
|
|
524
|
+
const sigintFromTerminal = () => isatty(0) && isForeground();
|
|
525
|
+
/** Decides whether a signal oxnode got goes on to the child. Pure: the caller owns the timers. */
|
|
526
|
+
var SignalGate = class {
|
|
527
|
+
#nodeWatch;
|
|
528
|
+
#platform;
|
|
529
|
+
#terminal;
|
|
530
|
+
#waitMs;
|
|
531
|
+
#recentMs;
|
|
532
|
+
#coalesceMs;
|
|
533
|
+
#last = /* @__PURE__ */ new Map();
|
|
534
|
+
#lastAck = -Infinity;
|
|
535
|
+
#waiting = false;
|
|
536
|
+
constructor(o) {
|
|
537
|
+
this.#nodeWatch = o.nodeWatch;
|
|
538
|
+
this.#platform = o.platform;
|
|
539
|
+
const terminal = o.terminal ?? false;
|
|
540
|
+
this.#terminal = typeof terminal === "function" ? terminal : () => terminal;
|
|
541
|
+
this.#waitMs = o.waitMs ?? 50;
|
|
542
|
+
this.#recentMs = o.recentMs ?? 100;
|
|
543
|
+
this.#coalesceMs = o.coalesceMs ?? 500;
|
|
544
|
+
}
|
|
545
|
+
onSignal(sig, now) {
|
|
546
|
+
if (this.#platform === "win32") return { action: "skip" };
|
|
547
|
+
const prev = this.#last.get(sig);
|
|
548
|
+
this.#last.set(sig, now);
|
|
549
|
+
if (prev !== void 0 && now - prev < this.#coalesceMs) return { action: "skip" };
|
|
550
|
+
if (sig === "SIGINT") {
|
|
551
|
+
if (this.#terminal()) return { action: "skip" };
|
|
552
|
+
if (now - this.#lastAck <= this.#recentMs) return { action: "skip" };
|
|
553
|
+
this.#waiting = true;
|
|
554
|
+
return {
|
|
555
|
+
action: "wait",
|
|
556
|
+
ms: this.#waitMs
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
if (sig === "SIGHUP" && this.#nodeWatch) return {
|
|
560
|
+
action: "forward",
|
|
561
|
+
as: "SIGTERM"
|
|
562
|
+
};
|
|
563
|
+
return {
|
|
564
|
+
action: "forward",
|
|
565
|
+
as: sig
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
onAck(now) {
|
|
569
|
+
this.#lastAck = now;
|
|
570
|
+
this.#waiting = false;
|
|
571
|
+
}
|
|
572
|
+
onWaitExpired() {
|
|
573
|
+
if (!this.#waiting) return { action: "skip" };
|
|
574
|
+
this.#waiting = false;
|
|
575
|
+
return {
|
|
576
|
+
action: "forward",
|
|
577
|
+
as: "SIGINT"
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
/** Runs `onSignal` decisions against a child, including the SIGINT wait. */
|
|
582
|
+
function gateSignals(gate, ack, kill, debug = () => {}) {
|
|
583
|
+
let timer;
|
|
584
|
+
const act = (r) => {
|
|
585
|
+
if (r.action === "forward") {
|
|
586
|
+
debug(`forward ${r.as}`);
|
|
587
|
+
kill(r.as);
|
|
588
|
+
} else if (r.action === "wait") {
|
|
589
|
+
clearTimeout(timer);
|
|
590
|
+
timer = setTimeout(() => {
|
|
591
|
+
timer = void 0;
|
|
592
|
+
act(gate.onWaitExpired());
|
|
593
|
+
}, r.ms);
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
ack.onAck(() => {
|
|
597
|
+
debug("ack");
|
|
598
|
+
gate.onAck(Date.now());
|
|
599
|
+
clearTimeout(timer);
|
|
600
|
+
timer = void 0;
|
|
601
|
+
});
|
|
602
|
+
return {
|
|
603
|
+
signal: (sig) => act(gate.onSignal(sig, Date.now())),
|
|
604
|
+
dispose: () => clearTimeout(timer)
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
// ../../oxc-node/packages/cli/src/watch/glob.ts
|
|
608
|
+
const GLOB_CHARS = /[*?[\]{}]|[+@!]\(/;
|
|
609
|
+
const MAX_EXPANSIONS = 64;
|
|
610
|
+
/**
|
|
611
|
+
* `a/{b,c/d}/e` → `a/b/e`, `a/c/d/e`. Groups without a comma (`{1..3}`) stay. Too many
|
|
612
|
+
* alternatives: the pattern as it is. `\\` escapes, except on Windows, where it is a separator.
|
|
613
|
+
*/
|
|
614
|
+
function expandBraces(pattern, win) {
|
|
615
|
+
let depth = 0;
|
|
616
|
+
let start = -1;
|
|
617
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
618
|
+
const c = pattern[i];
|
|
619
|
+
if (c === "\\" && !win) i++;
|
|
620
|
+
else if (c === "{") {
|
|
621
|
+
if (depth++ === 0) start = i;
|
|
622
|
+
} else if (c === "}" && depth > 0 && --depth === 0) {
|
|
623
|
+
const alts = splitTop(pattern.slice(start + 1, i), win);
|
|
624
|
+
if (alts.length < 2) continue;
|
|
625
|
+
const [pre, post] = [pattern.slice(0, start), pattern.slice(i + 1)];
|
|
626
|
+
const out = [];
|
|
627
|
+
for (const alt of alts) for (const one of expandBraces(pre + alt + post, win)) {
|
|
628
|
+
out.push(one);
|
|
629
|
+
if (out.length > MAX_EXPANSIONS) return [pattern];
|
|
630
|
+
}
|
|
631
|
+
return out;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return [pattern];
|
|
635
|
+
}
|
|
636
|
+
function splitTop(body, win) {
|
|
637
|
+
const out = [];
|
|
638
|
+
let depth = 0;
|
|
639
|
+
let from = 0;
|
|
640
|
+
for (let i = 0; i < body.length; i++) {
|
|
641
|
+
const c = body[i];
|
|
642
|
+
if (c === "\\" && !win) i++;
|
|
643
|
+
else if (c === "{") depth++;
|
|
644
|
+
else if (c === "}" && depth > 0) depth--;
|
|
645
|
+
else if (c === "," && depth === 0) {
|
|
646
|
+
out.push(body.slice(from, i));
|
|
647
|
+
from = i + 1;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
out.push(body.slice(from));
|
|
651
|
+
return out;
|
|
652
|
+
}
|
|
653
|
+
const isDirOnDisk = (p) => {
|
|
654
|
+
try {
|
|
655
|
+
return statSync(p).isDirectory();
|
|
656
|
+
} catch {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
function makeMatcher(opts) {
|
|
661
|
+
const win = opts.platform === "win32";
|
|
662
|
+
const native = win ? path.win32 : path.posix;
|
|
663
|
+
const dirGlob = (p) => GLOB_CHARS.test(p) && (win ? /[\\/]$/ : /\/$/).test(p) ? `${p}**` : p;
|
|
664
|
+
const include = opts.include.map(dirGlob);
|
|
665
|
+
const exclude = opts.exclude.map(dirGlob);
|
|
666
|
+
const isDir = opts.isDir ?? isDirOnDisk;
|
|
667
|
+
const norm = (p) => win ? p.replaceAll("\\", "/").toLowerCase() : p;
|
|
668
|
+
const cwd = norm(opts.cwd);
|
|
669
|
+
const drive = win ? native.resolve(opts.cwd, "/").replace(/[\\/]$/, "") : "";
|
|
670
|
+
const rooted = (pattern) => !win ? pattern : /^[\\/](?![\\/])/.test(pattern) ? drive + pattern : /^[A-Za-z]:(?![\\/])/.test(pattern) ? native.resolve(opts.cwd, pattern) : pattern;
|
|
671
|
+
const absolute = (pattern) => {
|
|
672
|
+
const abs = win ? norm(native.resolve(opts.cwd, rooted(pattern))) : path.posix.isAbsolute(pattern) ? pattern : path.posix.join(cwd, pattern);
|
|
673
|
+
return /^([a-z]:)?\/+$/i.test(abs) ? abs : abs.replace(/\/+$/, "");
|
|
674
|
+
};
|
|
675
|
+
const globs = [...include, ...exclude].filter((p) => GLOB_CHARS.test(p));
|
|
676
|
+
const matchesGlob = path.posix.matchesGlob;
|
|
677
|
+
if (globs.length && typeof matchesGlob !== "function") throw new Error(`--include and --exclude globs need Node.js 22.5 or later (path.matchesGlob)`);
|
|
678
|
+
const literal = (lit) => {
|
|
679
|
+
const below = lit.endsWith("/") ? lit : `${lit}/`;
|
|
680
|
+
return (n) => n === lit || n.startsWith(below);
|
|
681
|
+
};
|
|
682
|
+
const glob = (pattern) => {
|
|
683
|
+
if (native.isAbsolute(rooted(pattern))) {
|
|
684
|
+
const g = norm(rooted(pattern));
|
|
685
|
+
return (n) => matchesGlob(n, g);
|
|
686
|
+
}
|
|
687
|
+
const g = path.posix.normalize(norm(pattern));
|
|
688
|
+
return (n) => matchesGlob(path.posix.relative(cwd, n), g);
|
|
689
|
+
};
|
|
690
|
+
const excludes = exclude.map((p) => GLOB_CHARS.test(p) ? glob(p) : literal(absolute(p)));
|
|
691
|
+
const treeExcludes = exclude.filter((p) => /[\\/]\*\*$/.test(p)).map(glob);
|
|
692
|
+
const includes = [];
|
|
693
|
+
const named = [];
|
|
694
|
+
const roots = [];
|
|
695
|
+
const literals = [];
|
|
696
|
+
for (const pattern of include) {
|
|
697
|
+
if (!GLOB_CHARS.test(pattern)) {
|
|
698
|
+
const abs = native.resolve(opts.cwd, pattern);
|
|
699
|
+
if (isDir(abs)) {
|
|
700
|
+
includes.push(literal(norm(abs)));
|
|
701
|
+
roots.push({
|
|
702
|
+
base: abs,
|
|
703
|
+
depth: Infinity
|
|
704
|
+
});
|
|
705
|
+
} else {
|
|
706
|
+
includes.push(literal(norm(abs)));
|
|
707
|
+
literals.push(abs);
|
|
708
|
+
roots.push({
|
|
709
|
+
base: abs,
|
|
710
|
+
depth: Infinity
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
includes.push(glob(pattern));
|
|
716
|
+
for (const one of expandBraces(pattern, win)) {
|
|
717
|
+
const parts = one.split(win ? /[\\/]/ : "/");
|
|
718
|
+
let baseLen = 0;
|
|
719
|
+
while (baseLen < parts.length - 1 && !GLOB_CHARS.test(parts[baseLen])) baseLen++;
|
|
720
|
+
const rest = parts.slice(baseLen);
|
|
721
|
+
const fixed = baseLen === 0 ? "." : parts.slice(0, baseLen).join("/") || "/";
|
|
722
|
+
const base = native.resolve(opts.cwd, /^[A-Za-z]:$/.test(fixed) ? `${fixed}/` : fixed);
|
|
723
|
+
named.push({
|
|
724
|
+
base: norm(base),
|
|
725
|
+
parts: rest.map(norm)
|
|
726
|
+
});
|
|
727
|
+
roots.push({
|
|
728
|
+
base,
|
|
729
|
+
depth: rest.includes("**") ? Infinity : rest.length
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const ignoreDirs = (opts.ignoreDirs ?? []).map(norm);
|
|
734
|
+
const excluded = (n) => excludes.some((m) => m(n));
|
|
735
|
+
return {
|
|
736
|
+
classify(absPath, source) {
|
|
737
|
+
const n = norm(absPath);
|
|
738
|
+
if (excluded(n)) return "exclude";
|
|
739
|
+
if (includes.some((m) => m(n))) return "watch";
|
|
740
|
+
if (source === "literal") return "watch";
|
|
741
|
+
if (source === "walk") return "default-ignore";
|
|
742
|
+
if (source !== "oxc" && n.split("/").includes("node_modules") || ignoreDirs.some((d) => literal(d)(n))) return "default-ignore";
|
|
743
|
+
return "watch";
|
|
744
|
+
},
|
|
745
|
+
roots: () => roots,
|
|
746
|
+
literals: () => literals,
|
|
747
|
+
pruneDir(absDir) {
|
|
748
|
+
const n = norm(absDir);
|
|
749
|
+
const name = path.posix.basename(n);
|
|
750
|
+
const fits = (dirs, parts, i = 0, j = 0) => {
|
|
751
|
+
if (i === dirs.length) return true;
|
|
752
|
+
const part = parts[j];
|
|
753
|
+
if (part === void 0 || j === parts.length - 1 && part !== "**") return false;
|
|
754
|
+
const last = i === dirs.length - 1;
|
|
755
|
+
if (part === "**") return fits(dirs, parts, i, j + 1) || !last && fits(dirs, parts, i + 1, j);
|
|
756
|
+
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);
|
|
757
|
+
};
|
|
758
|
+
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__`));
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
// ../../oxc-node/packages/cli/src/watch/dirs.ts
|
|
763
|
+
function isRealDir(p) {
|
|
764
|
+
try {
|
|
765
|
+
return lstatSync(p).isDirectory();
|
|
766
|
+
} catch {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
function isLink(p) {
|
|
771
|
+
try {
|
|
772
|
+
return lstatSync(p).isSymbolicLink();
|
|
773
|
+
} catch {
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
function sigOf(p) {
|
|
778
|
+
try {
|
|
779
|
+
const s = statSync(p, { bigint: true });
|
|
780
|
+
if (s.isDirectory()) return null;
|
|
781
|
+
return {
|
|
782
|
+
ino: s.ino,
|
|
783
|
+
size: s.size,
|
|
784
|
+
mtimeNs: s.mtimeNs,
|
|
785
|
+
ctimeNs: s.ctimeNs
|
|
786
|
+
};
|
|
787
|
+
} catch {
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
/** The links between a link and the file it ends at: `a -> b -> c -> file` gives `b`, `c`. */
|
|
792
|
+
function linkHops(p) {
|
|
793
|
+
const hops = [];
|
|
794
|
+
let cur = p;
|
|
795
|
+
for (let n = 0; n < 32; n++) {
|
|
796
|
+
try {
|
|
797
|
+
cur = path.resolve(path.dirname(cur), readlinkSync(cur));
|
|
798
|
+
} catch {
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
if (!isLink(cur)) break;
|
|
802
|
+
hops.push(cur);
|
|
803
|
+
}
|
|
804
|
+
return hops;
|
|
805
|
+
}
|
|
806
|
+
/** The stat of a link itself: it changes when the link is removed or pointed elsewhere. */
|
|
807
|
+
function linkSigOf(p) {
|
|
808
|
+
try {
|
|
809
|
+
const s = lstatSync(p, { bigint: true });
|
|
810
|
+
return {
|
|
811
|
+
ino: s.ino,
|
|
812
|
+
size: s.size,
|
|
813
|
+
mtimeNs: s.mtimeNs,
|
|
814
|
+
ctimeNs: s.ctimeNs
|
|
815
|
+
};
|
|
816
|
+
} catch {
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Whether a file changed, from its stat before and after. `event`: an event named it, rather
|
|
822
|
+
* than a look made without one.
|
|
823
|
+
*/
|
|
824
|
+
function changed(prev, next, event = true) {
|
|
825
|
+
if (!prev || !next) return prev !== next;
|
|
826
|
+
if (prev.ino !== next.ino || prev.size !== next.size || prev.mtimeNs !== next.mtimeNs || prev.ctimeNs !== next.ctimeNs) return true;
|
|
827
|
+
return event && next.mtimeNs % 1000000000n === 0n;
|
|
828
|
+
}
|
|
829
|
+
function idOf(dir) {
|
|
830
|
+
try {
|
|
831
|
+
const s = statSync(dir, { bigint: true });
|
|
832
|
+
return `${s.ino}:${s.birthtimeNs}`;
|
|
833
|
+
} catch {
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* One non-recursive `fs.watch` per folder that holds a wanted file. A file whose folder does
|
|
839
|
+
* not exist yet is watched at the nearest existing ancestor, one level deeper as folders appear.
|
|
840
|
+
* `--include` roots are walked, and new folders in them are walked as they appear.
|
|
841
|
+
*/
|
|
842
|
+
var DirWatcher = class {
|
|
843
|
+
#o;
|
|
844
|
+
#win;
|
|
845
|
+
/**
|
|
846
|
+
* `shown` is the name a change is reported under: a link, for a link's target.
|
|
847
|
+
* `own`: wanted itself, for the whole session or only by the current run's imports.
|
|
848
|
+
* `target` is the key of the file a link points to; `links`, the keys of the links a target is
|
|
849
|
+
* watched for.
|
|
850
|
+
*/
|
|
851
|
+
#files = /* @__PURE__ */ new Map();
|
|
852
|
+
#byDir = /* @__PURE__ */ new Map();
|
|
853
|
+
#roots = [];
|
|
854
|
+
#walked = /* @__PURE__ */ new Map();
|
|
855
|
+
#handles = /* @__PURE__ */ new Map();
|
|
856
|
+
#awaited = /* @__PURE__ */ new Set();
|
|
857
|
+
#dropped = [];
|
|
858
|
+
#droppedSigs = /* @__PURE__ */ new Map();
|
|
859
|
+
#dropping;
|
|
860
|
+
#failed = /* @__PURE__ */ new Set();
|
|
861
|
+
#warned = /* @__PURE__ */ new Set();
|
|
862
|
+
#syncTimer;
|
|
863
|
+
#pollTimer;
|
|
864
|
+
#settleTimer;
|
|
865
|
+
#closed = false;
|
|
866
|
+
constructor(o) {
|
|
867
|
+
this.#o = o;
|
|
868
|
+
this.#win = o.platform === "win32";
|
|
869
|
+
if (o.poll !== false) this.#pollTimer = setInterval(() => this.#rewalk(), o.poll * 10);
|
|
870
|
+
}
|
|
871
|
+
#key(p) {
|
|
872
|
+
return this.#win ? p.toLowerCase() : p;
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Watch one file, which may not exist yet. `run`: only the current run's imports want it, so
|
|
876
|
+
* `dropRun` drops it. `keepGone`: once deleted, it stays until it is back, for a file the next
|
|
877
|
+
* run looks for but cannot report (a discovered tsconfig.json). A link's target is watched for
|
|
878
|
+
* as long as the link points to it.
|
|
879
|
+
*/
|
|
880
|
+
add(file, run = false, keepGone = false) {
|
|
881
|
+
const have = this.#files.get(this.#key(file));
|
|
882
|
+
if (have) {
|
|
883
|
+
if (have.own !== "static") have.own = run ? "run" : "static";
|
|
884
|
+
have.shown = file;
|
|
885
|
+
if (keepGone) have.keepGone = true;
|
|
886
|
+
if (have.target === void 0 && isLink(file)) this.#follow(have);
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (this.#closed) return;
|
|
890
|
+
const entry = this.#put(file, file, run ? "run" : "static");
|
|
891
|
+
if (keepGone) entry.keepGone = true;
|
|
892
|
+
if (isLink(file)) this.#follow(entry);
|
|
893
|
+
}
|
|
894
|
+
/** Drop what only the last run's imports wanted: the next run reports its own. */
|
|
895
|
+
dropRun() {
|
|
896
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
897
|
+
const sigs = /* @__PURE__ */ new Map();
|
|
898
|
+
this.#dropping = {
|
|
899
|
+
sigs,
|
|
900
|
+
dirs
|
|
901
|
+
};
|
|
902
|
+
for (const [k, f] of this.#files) {
|
|
903
|
+
if (f.own !== "run") continue;
|
|
904
|
+
if (f.gone && f.keepGone) continue;
|
|
905
|
+
f.own = void 0;
|
|
906
|
+
const [link] = f.links ?? [];
|
|
907
|
+
if (link !== void 0) {
|
|
908
|
+
f.shown = this.#files.get(link)?.path ?? f.shown;
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
this.#remove(k);
|
|
912
|
+
}
|
|
913
|
+
this.#dropping = void 0;
|
|
914
|
+
this.#droppedSigs = sigs;
|
|
915
|
+
if (this.#dropped.length > 0 && this.#o.poll === false) this.#scheduleSync();
|
|
916
|
+
this.#dropped = [...dirs];
|
|
917
|
+
}
|
|
918
|
+
#put(file, shown, own) {
|
|
919
|
+
const k = this.#key(file);
|
|
920
|
+
const entry = {
|
|
921
|
+
path: file,
|
|
922
|
+
shown,
|
|
923
|
+
sig: sigOf(file),
|
|
924
|
+
own
|
|
925
|
+
};
|
|
926
|
+
this.#files.set(k, entry);
|
|
927
|
+
if (this.#droppedSigs.has(k)) {
|
|
928
|
+
const before = this.#droppedSigs.get(k);
|
|
929
|
+
this.#droppedSigs.delete(k);
|
|
930
|
+
if (changed(before, entry.sig, false)) queueMicrotask(() => this.#closed || this.#o.onChange(shown));
|
|
931
|
+
}
|
|
932
|
+
const dk = this.#key(path.dirname(file));
|
|
933
|
+
const set = this.#byDir.get(dk);
|
|
934
|
+
if (set) set.add(k);
|
|
935
|
+
else this.#byDir.set(dk, /* @__PURE__ */ new Set([k]));
|
|
936
|
+
this.#o.debug(`watch ${file}`);
|
|
937
|
+
if (this.#o.poll === false) {
|
|
938
|
+
this.#scheduleSync();
|
|
939
|
+
return entry;
|
|
940
|
+
}
|
|
941
|
+
watchFile(file, {
|
|
942
|
+
interval: this.#o.poll,
|
|
943
|
+
bigint: true
|
|
944
|
+
}, () => {
|
|
945
|
+
if (this.#files.has(k)) this.#check(k);
|
|
946
|
+
});
|
|
947
|
+
return entry;
|
|
948
|
+
}
|
|
949
|
+
/** Walk `base` to `depth` levels for files the matcher accepts, now and as they appear. */
|
|
950
|
+
addRoot(base, depth) {
|
|
951
|
+
const root = {
|
|
952
|
+
base,
|
|
953
|
+
depth,
|
|
954
|
+
missing: false
|
|
955
|
+
};
|
|
956
|
+
this.#roots.push(root);
|
|
957
|
+
this.#walkRoot(root);
|
|
958
|
+
if (this.#o.poll === false) this.#scheduleSync();
|
|
959
|
+
}
|
|
960
|
+
close() {
|
|
961
|
+
this.#closed = true;
|
|
962
|
+
clearTimeout(this.#syncTimer);
|
|
963
|
+
clearInterval(this.#pollTimer);
|
|
964
|
+
clearTimeout(this.#settleTimer);
|
|
965
|
+
for (const h of this.#handles.values()) h.w.close();
|
|
966
|
+
this.#handles.clear();
|
|
967
|
+
if (this.#o.poll !== false) for (const f of this.#files.values()) unwatchFile(f.path);
|
|
968
|
+
}
|
|
969
|
+
#scheduleSync() {
|
|
970
|
+
if (!this.#syncTimer && !this.#closed) this.#syncTimer = setTimeout(() => this.#sync(), 20);
|
|
971
|
+
}
|
|
972
|
+
#walkRoot(root) {
|
|
973
|
+
if (!isDirOnDisk(root.base)) {
|
|
974
|
+
root.missing = true;
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
977
|
+
this.#walk(root.base, 0, root.depth, root.missing);
|
|
978
|
+
root.missing = false;
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
981
|
+
#walk(dir, level, depth, fire) {
|
|
982
|
+
const k = this.#key(dir);
|
|
983
|
+
const seen = this.#walked.get(k);
|
|
984
|
+
if (seen && seen.depth - seen.level >= depth - level) return;
|
|
985
|
+
this.#walked.set(k, {
|
|
986
|
+
dir,
|
|
987
|
+
depth,
|
|
988
|
+
level
|
|
989
|
+
});
|
|
990
|
+
let entries;
|
|
991
|
+
try {
|
|
992
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
993
|
+
} catch {
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
for (const e of entries) {
|
|
997
|
+
const full = path.join(dir, e.name);
|
|
998
|
+
if (e.isDirectory()) {
|
|
999
|
+
if (level + 1 < depth && !this.#o.matcher.pruneDir(full)) this.#walk(full, level + 1, depth, fire);
|
|
1000
|
+
} else this.#found(full, fire);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
#found(file, fire) {
|
|
1004
|
+
const have = this.#files.get(this.#key(file));
|
|
1005
|
+
if (have?.own === "static") return;
|
|
1006
|
+
try {
|
|
1007
|
+
if (!statSync(file).isFile()) return;
|
|
1008
|
+
} catch {
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
if (this.#o.matcher.classify(file, "walk") !== "watch") return;
|
|
1012
|
+
this.add(file);
|
|
1013
|
+
if (have) return;
|
|
1014
|
+
this.#follow(this.#files.get(this.#key(file)));
|
|
1015
|
+
if (fire) this.#o.onChange(file);
|
|
1016
|
+
}
|
|
1017
|
+
#follow(entry) {
|
|
1018
|
+
let real;
|
|
1019
|
+
try {
|
|
1020
|
+
real = realpathSync.native(entry.path);
|
|
1021
|
+
} catch {}
|
|
1022
|
+
const next = real && this.#key(real) !== this.#key(entry.path) ? this.#key(real) : void 0;
|
|
1023
|
+
entry.linkSig = next ? linkSigOf(entry.path) : void 0;
|
|
1024
|
+
const hops = next ? linkHops(entry.path).filter((h) => this.#key(h) !== next) : [];
|
|
1025
|
+
const hopKeys = hops.map((h) => this.#key(h));
|
|
1026
|
+
if (next === entry.target && hopKeys.join("\0") === (entry.hops ?? []).join("\0")) return;
|
|
1027
|
+
const link = this.#key(entry.path);
|
|
1028
|
+
for (const k of entry.hops ?? []) this.#release(k, link);
|
|
1029
|
+
if (entry.target !== void 0) this.#release(entry.target, link);
|
|
1030
|
+
entry.target = next;
|
|
1031
|
+
entry.hops = hopKeys;
|
|
1032
|
+
if (real && next && !this.#closed) {
|
|
1033
|
+
const target = this.#files.get(next) ?? this.#put(real, entry.path, void 0);
|
|
1034
|
+
(target.links ??= /* @__PURE__ */ new Set()).add(link);
|
|
1035
|
+
hops.forEach((h, i) => {
|
|
1036
|
+
const hop = this.#files.get(hopKeys[i]) ?? this.#put(h, entry.path, void 0);
|
|
1037
|
+
hop.linkSig ??= linkSigOf(h);
|
|
1038
|
+
(hop.links ??= /* @__PURE__ */ new Set()).add(link);
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
#release(k, link) {
|
|
1043
|
+
const target = this.#files.get(k);
|
|
1044
|
+
if (!target?.links) return;
|
|
1045
|
+
target.links.delete(link);
|
|
1046
|
+
if (target.own) return;
|
|
1047
|
+
const [rest] = target.links;
|
|
1048
|
+
if (rest === void 0) this.#remove(k);
|
|
1049
|
+
else target.shown = this.#files.get(rest)?.path ?? target.shown;
|
|
1050
|
+
}
|
|
1051
|
+
#remove(k) {
|
|
1052
|
+
const entry = this.#files.get(k);
|
|
1053
|
+
if (!entry) return;
|
|
1054
|
+
this.#files.delete(k);
|
|
1055
|
+
if (this.#dropping) {
|
|
1056
|
+
this.#dropping.sigs.set(k, entry.sig);
|
|
1057
|
+
this.#dropping.dirs.add(path.dirname(entry.path));
|
|
1058
|
+
}
|
|
1059
|
+
for (const h of entry.hops ?? []) this.#release(h, k);
|
|
1060
|
+
if (entry.target !== void 0) this.#release(entry.target, k);
|
|
1061
|
+
const dk = this.#key(path.dirname(entry.path));
|
|
1062
|
+
const set = this.#byDir.get(dk);
|
|
1063
|
+
set?.delete(k);
|
|
1064
|
+
if (set?.size === 0) this.#byDir.delete(dk);
|
|
1065
|
+
if (this.#o.poll !== false) unwatchFile(entry.path);
|
|
1066
|
+
else this.#scheduleSync();
|
|
1067
|
+
}
|
|
1068
|
+
#rewalk() {
|
|
1069
|
+
for (const k of this.#files.keys()) this.#check(k, false);
|
|
1070
|
+
this.#walked.clear();
|
|
1071
|
+
for (const root of this.#roots) {
|
|
1072
|
+
root.missing = true;
|
|
1073
|
+
this.#walkRoot(root);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
#sync() {
|
|
1077
|
+
this.#syncTimer = void 0;
|
|
1078
|
+
if (this.#closed) return;
|
|
1079
|
+
const exists = /* @__PURE__ */ new Map();
|
|
1080
|
+
const cachedExists = (d) => {
|
|
1081
|
+
let e = exists.get(d);
|
|
1082
|
+
if (e === void 0) exists.set(d, e = existsSync(d));
|
|
1083
|
+
return e;
|
|
1084
|
+
};
|
|
1085
|
+
const needed = /* @__PURE__ */ new Map();
|
|
1086
|
+
const awaited = /* @__PURE__ */ new Set();
|
|
1087
|
+
const place = (dir) => {
|
|
1088
|
+
let d = dir;
|
|
1089
|
+
let below;
|
|
1090
|
+
while (!cachedExists(d)) {
|
|
1091
|
+
const up = path.dirname(d);
|
|
1092
|
+
if (up === d) return;
|
|
1093
|
+
below = d;
|
|
1094
|
+
d = up;
|
|
1095
|
+
}
|
|
1096
|
+
if (below) awaited.add(this.#key(below));
|
|
1097
|
+
needed.set(this.#key(d), d);
|
|
1098
|
+
};
|
|
1099
|
+
for (const [k, w] of this.#walked) if (!existsSync(w.dir)) this.#walked.delete(k);
|
|
1100
|
+
for (const root of this.#roots) if (!this.#walkRoot(root)) place(root.base);
|
|
1101
|
+
for (const f of this.#files.values()) place(path.dirname(f.path));
|
|
1102
|
+
for (const d of this.#dropped) place(d);
|
|
1103
|
+
for (const [k, w] of this.#walked) needed.set(k, w.dir);
|
|
1104
|
+
this.#awaited = awaited;
|
|
1105
|
+
for (const [k, h] of this.#handles) if (!needed.has(k) || idOf(h.dir) !== h.id) this.#drop(k, h);
|
|
1106
|
+
const fresh = [];
|
|
1107
|
+
for (const [k, dir] of needed) if (!this.#handles.has(k) && !this.#failed.has(k) && this.#open(k, dir)) fresh.push(dir);
|
|
1108
|
+
for (const dir of fresh) {
|
|
1109
|
+
this.#checkDir(dir, false);
|
|
1110
|
+
const walked = this.#walked.get(this.#key(dir));
|
|
1111
|
+
if (walked) this.#rescan(walked);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
#drop(k, h) {
|
|
1115
|
+
h.w.close();
|
|
1116
|
+
this.#handles.delete(k);
|
|
1117
|
+
this.#restarted();
|
|
1118
|
+
this.#checkDir(h.dir, false);
|
|
1119
|
+
}
|
|
1120
|
+
#restarted() {
|
|
1121
|
+
if (this.#o.platform !== "darwin" || this.#closed) return;
|
|
1122
|
+
clearTimeout(this.#settleTimer);
|
|
1123
|
+
this.#settleTimer = setTimeout(() => {
|
|
1124
|
+
this.#settleTimer = void 0;
|
|
1125
|
+
for (const k of this.#files.keys()) this.#check(k, false);
|
|
1126
|
+
this.#scheduleSync();
|
|
1127
|
+
}, 250);
|
|
1128
|
+
}
|
|
1129
|
+
#rescan(walked) {
|
|
1130
|
+
let entries;
|
|
1131
|
+
try {
|
|
1132
|
+
entries = readdirSync(walked.dir);
|
|
1133
|
+
} catch {
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
for (const name of entries) this.#onWalkedEntry(walked, path.join(walked.dir, name));
|
|
1137
|
+
}
|
|
1138
|
+
#open(k, dir) {
|
|
1139
|
+
try {
|
|
1140
|
+
const id = idOf(dir);
|
|
1141
|
+
const w = (this.#o.watch ?? watch)(dir, (_type, name) => this.#onEvent(dir, w, name));
|
|
1142
|
+
const h = {
|
|
1143
|
+
w,
|
|
1144
|
+
dir,
|
|
1145
|
+
id
|
|
1146
|
+
};
|
|
1147
|
+
w.on("error", () => {
|
|
1148
|
+
if (this.#handles.get(k) === h) this.#drop(k, h);
|
|
1149
|
+
else w.close();
|
|
1150
|
+
this.#scheduleSync();
|
|
1151
|
+
});
|
|
1152
|
+
this.#handles.set(k, h);
|
|
1153
|
+
this.#restarted();
|
|
1154
|
+
this.#o.debug(`ready ${dir}`);
|
|
1155
|
+
return true;
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
const code = error.code ?? String(error);
|
|
1158
|
+
if (code === "ENOENT") {
|
|
1159
|
+
this.#scheduleSync();
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
this.#failed.add(k);
|
|
1163
|
+
if (!this.#warned.has(code)) {
|
|
1164
|
+
this.#warned.add(code);
|
|
1165
|
+
const hint = code === "ENOSPC" || code === "EMFILE" ? " — raise fs.inotify.max_user_watches, or use --poll" : "";
|
|
1166
|
+
this.#o.warn(`cannot watch ${dir}: ${code}${hint}`);
|
|
1167
|
+
}
|
|
1168
|
+
return false;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
#onEvent(dir, w, name) {
|
|
1172
|
+
if (this.#closed) return;
|
|
1173
|
+
if (name === null) {
|
|
1174
|
+
this.#checkDir(dir);
|
|
1175
|
+
const walked = this.#walked.get(this.#key(dir));
|
|
1176
|
+
if (walked) this.#rescan(walked);
|
|
1177
|
+
this.#scheduleSync();
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
const full = path.join(dir, name);
|
|
1181
|
+
const k = this.#key(full);
|
|
1182
|
+
if (this.#files.has(k)) this.#check(k);
|
|
1183
|
+
else {
|
|
1184
|
+
const walked = this.#walked.get(this.#key(dir));
|
|
1185
|
+
if (walked) this.#onWalkedEntry(walked, full);
|
|
1186
|
+
}
|
|
1187
|
+
if (this.#awaited.has(k) || this.#handles.has(k) || this.#walked.has(k)) this.#scheduleSync();
|
|
1188
|
+
if (name === path.basename(dir) && !this.#files.has(k) && !existsSync(full)) {
|
|
1189
|
+
const dk = this.#key(dir);
|
|
1190
|
+
const h = this.#handles.get(dk);
|
|
1191
|
+
if (h?.w === w && this.#o.platform === "linux") this.#drop(dk, h);
|
|
1192
|
+
this.#scheduleSync();
|
|
1193
|
+
}
|
|
1194
|
+
const dk = this.#key(dir);
|
|
1195
|
+
const h = this.#handles.get(dk);
|
|
1196
|
+
if (h?.w !== w) return;
|
|
1197
|
+
if (idOf(dir) !== h.id) {
|
|
1198
|
+
this.#drop(dk, h);
|
|
1199
|
+
this.#scheduleSync();
|
|
1200
|
+
} else if (!existsSync(full)) this.#scheduleSync();
|
|
1201
|
+
}
|
|
1202
|
+
#onWalkedEntry(walked, full) {
|
|
1203
|
+
if (isRealDir(full)) {
|
|
1204
|
+
if (walked.level + 1 < walked.depth && !this.#o.matcher.pruneDir(full)) {
|
|
1205
|
+
this.#walk(full, walked.level + 1, walked.depth, true);
|
|
1206
|
+
this.#scheduleSync();
|
|
1207
|
+
}
|
|
1208
|
+
} else this.#found(full, true);
|
|
1209
|
+
}
|
|
1210
|
+
#checkDir(dir, event = true) {
|
|
1211
|
+
for (const k of this.#byDir.get(this.#key(dir)) ?? []) this.#check(k, event);
|
|
1212
|
+
}
|
|
1213
|
+
#check(k, event = true) {
|
|
1214
|
+
const entry = this.#files.get(k);
|
|
1215
|
+
const next = sigOf(entry.path);
|
|
1216
|
+
if (!changed(entry.sig, next, event)) {
|
|
1217
|
+
if (entry.linkSig !== void 0 && changed(entry.linkSig, linkSigOf(entry.path), false)) {
|
|
1218
|
+
let moved = false;
|
|
1219
|
+
const follow = (link) => {
|
|
1220
|
+
const before = link.target;
|
|
1221
|
+
this.#follow(link);
|
|
1222
|
+
if (next !== null && link.target !== before) moved = true;
|
|
1223
|
+
};
|
|
1224
|
+
if (entry.target !== void 0) follow(entry);
|
|
1225
|
+
else entry.linkSig = linkSigOf(entry.path);
|
|
1226
|
+
for (const l of Array.from(entry.links ?? [])) {
|
|
1227
|
+
const through = this.#files.get(l);
|
|
1228
|
+
if (through?.hops?.includes(k)) follow(through);
|
|
1229
|
+
}
|
|
1230
|
+
if (moved) this.#o.onChange(entry.shown);
|
|
1231
|
+
}
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
if (entry.sig && !next) entry.gone = true;
|
|
1235
|
+
else if (next) entry.gone = false;
|
|
1236
|
+
entry.sig = next;
|
|
1237
|
+
for (const l of entry.links ?? []) {
|
|
1238
|
+
const link = this.#files.get(l);
|
|
1239
|
+
if (link) link.sig = sigOf(link.path);
|
|
1240
|
+
}
|
|
1241
|
+
if (entry.own && (entry.target !== void 0 || isLink(entry.path))) this.#follow(entry);
|
|
1242
|
+
if (entry.links) {
|
|
1243
|
+
const link = isLink(entry.path);
|
|
1244
|
+
for (const l of Array.from(entry.links)) {
|
|
1245
|
+
const through = this.#files.get(l);
|
|
1246
|
+
if (through && (link || next && through.hops?.includes(k))) this.#follow(through);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
this.#o.onChange(entry.shown);
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
// ../../oxc-node/packages/cli/src/watch/kill-tree.ts
|
|
1253
|
+
/** One `/proc/<pid>/stat` line. The name in parentheses may hold spaces and `)`. */
|
|
1254
|
+
function parseProcStat(text) {
|
|
1255
|
+
const close = text.lastIndexOf(")");
|
|
1256
|
+
if (close === -1) return null;
|
|
1257
|
+
const pid = Number.parseInt(text, 10);
|
|
1258
|
+
const ppid = Number(text.slice(close + 1).trim().split(/\s+/)[1]);
|
|
1259
|
+
return Number.isInteger(pid) && Number.isInteger(ppid) ? {
|
|
1260
|
+
pid,
|
|
1261
|
+
ppid
|
|
1262
|
+
} : null;
|
|
1263
|
+
}
|
|
1264
|
+
/** The output of `ps -A -o pid=,ppid=`. */
|
|
1265
|
+
function parsePs(text) {
|
|
1266
|
+
const rows = [];
|
|
1267
|
+
for (const line of text.split("\n")) {
|
|
1268
|
+
const m = /^\s*(\d+)\s+(\d+)\s*$/.exec(line);
|
|
1269
|
+
if (m) rows.push({
|
|
1270
|
+
pid: Number(m[1]),
|
|
1271
|
+
ppid: Number(m[2])
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
return rows;
|
|
1275
|
+
}
|
|
1276
|
+
/** Every process below `pid`, parents before their children. */
|
|
1277
|
+
function descendants(table, pid) {
|
|
1278
|
+
const children = /* @__PURE__ */ new Map();
|
|
1279
|
+
for (const row of table) {
|
|
1280
|
+
const list = children.get(row.ppid);
|
|
1281
|
+
if (list) list.push(row.pid);
|
|
1282
|
+
else children.set(row.ppid, [row.pid]);
|
|
1283
|
+
}
|
|
1284
|
+
const out = [];
|
|
1285
|
+
const seen = /* @__PURE__ */ new Set([pid]);
|
|
1286
|
+
const queue = [pid];
|
|
1287
|
+
while (queue.length) for (const child of children.get(queue.shift()) ?? []) {
|
|
1288
|
+
if (seen.has(child)) continue;
|
|
1289
|
+
seen.add(child);
|
|
1290
|
+
out.push(child);
|
|
1291
|
+
queue.push(child);
|
|
1292
|
+
}
|
|
1293
|
+
return out;
|
|
1294
|
+
}
|
|
1295
|
+
/** Every process on the machine: `/proc` on Linux, where `ps` may be missing (slim containers), `ps` elsewhere. */
|
|
1296
|
+
function processTable() {
|
|
1297
|
+
try {
|
|
1298
|
+
if (process.platform === "linux") {
|
|
1299
|
+
const rows = [];
|
|
1300
|
+
for (const name of readdirSync("/proc")) {
|
|
1301
|
+
if (!/^\d+$/.test(name)) continue;
|
|
1302
|
+
try {
|
|
1303
|
+
const row = parseProcStat(readFileSync(`/proc/${name}/stat`, "utf8"));
|
|
1304
|
+
if (row) rows.push(row);
|
|
1305
|
+
} catch {}
|
|
1306
|
+
}
|
|
1307
|
+
return rows;
|
|
1308
|
+
}
|
|
1309
|
+
return parsePs(execFileSync("ps", [
|
|
1310
|
+
"-A",
|
|
1311
|
+
"-o",
|
|
1312
|
+
"pid=,ppid="
|
|
1313
|
+
], { encoding: "utf8" }));
|
|
1314
|
+
} catch {
|
|
1315
|
+
return [];
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
/** SIGKILL the child and everything it started, so a grandchild cannot keep a port busy. */
|
|
1319
|
+
function killTree(child) {
|
|
1320
|
+
const pid = child.pid;
|
|
1321
|
+
if (pid === void 0) return;
|
|
1322
|
+
if (process.platform === "win32") {
|
|
1323
|
+
spawn("taskkill", [
|
|
1324
|
+
"/pid",
|
|
1325
|
+
String(pid),
|
|
1326
|
+
"/T",
|
|
1327
|
+
"/F"
|
|
1328
|
+
], {
|
|
1329
|
+
stdio: "ignore",
|
|
1330
|
+
windowsHide: true
|
|
1331
|
+
}).on("error", () => child.kill());
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
for (const p of [pid, ...descendants(processTable(), pid)]) try {
|
|
1335
|
+
process.kill(p, "SIGKILL");
|
|
1336
|
+
} catch {}
|
|
1337
|
+
}
|
|
1338
|
+
// ../../oxc-node/packages/cli/src/watch/log.ts
|
|
1339
|
+
let color;
|
|
1340
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
1341
|
+
/** A logger printing `HH:MM:SS [label] msg` on stdout, like tsx and Node.js. */
|
|
1342
|
+
function createLog(label) {
|
|
1343
|
+
return (msg) => {
|
|
1344
|
+
const d = /* @__PURE__ */ new Date();
|
|
1345
|
+
const time = `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
1346
|
+
color ??= Boolean(process$1.stdout.isTTY && process$1.stdout.hasColors?.());
|
|
1347
|
+
process$1.stdout.write(color ? `\x1b[90m${time}\x1b[39m \x1b[36m[${label}]\x1b[39m ${msg}\n` : `${time} [${label}] ${msg}\n`);
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
const debugOn = Boolean(process$1.env.OXNODE_WATCH_DEBUG);
|
|
1351
|
+
/** Lines on stderr that tests wait for (OXNODE_WATCH_DEBUG=1). */
|
|
1352
|
+
function debug(line) {
|
|
1353
|
+
if (debugOn) process$1.stderr.write(`${line}\n`);
|
|
1354
|
+
}
|
|
1355
|
+
const ms = (n) => n >= 1e3 && n % 1e3 === 0 ? `${n / 1e3}s` : `${n}ms`;
|
|
1356
|
+
function exitReason(code, signal) {
|
|
1357
|
+
if (signal) return `killed by ${signal}`;
|
|
1358
|
+
return code ? `exited with code ${code}` : "completed";
|
|
1359
|
+
}
|
|
1360
|
+
function trigger(s, reason, cfg) {
|
|
1361
|
+
const clear = cfg.clear ? [{ e: "clearScreen" }] : [];
|
|
1362
|
+
if (s.k === "idle") return {
|
|
1363
|
+
state: { k: "running" },
|
|
1364
|
+
effects: [
|
|
1365
|
+
...clear,
|
|
1366
|
+
{
|
|
1367
|
+
e: "log",
|
|
1368
|
+
msg: `${reason}, rerunning`
|
|
1369
|
+
},
|
|
1370
|
+
{ e: "spawn" }
|
|
1371
|
+
]
|
|
1372
|
+
};
|
|
1373
|
+
const effects = [...clear, {
|
|
1374
|
+
e: "log",
|
|
1375
|
+
msg: `${reason}, restarting`
|
|
1376
|
+
}];
|
|
1377
|
+
if (cfg.killTimeout === 0) effects.push({ e: "killTree" });
|
|
1378
|
+
else effects.push({
|
|
1379
|
+
e: "kill",
|
|
1380
|
+
sig: cfg.killSignal
|
|
1381
|
+
}, {
|
|
1382
|
+
e: "startTimer",
|
|
1383
|
+
id: "kill",
|
|
1384
|
+
ms: cfg.killTimeout
|
|
1385
|
+
});
|
|
1386
|
+
return {
|
|
1387
|
+
state: {
|
|
1388
|
+
k: "stopping",
|
|
1389
|
+
after: "restart"
|
|
1390
|
+
},
|
|
1391
|
+
effects
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
function step(s, ev, cfg) {
|
|
1395
|
+
const same = {
|
|
1396
|
+
state: s,
|
|
1397
|
+
effects: []
|
|
1398
|
+
};
|
|
1399
|
+
const hint = cfg.enter ? " (Enter to rerun)" : "";
|
|
1400
|
+
if (s.k === "done") return same;
|
|
1401
|
+
if (ev.t === "spawnError") return {
|
|
1402
|
+
state: { k: "done" },
|
|
1403
|
+
effects: [
|
|
1404
|
+
{
|
|
1405
|
+
e: "clearTimer",
|
|
1406
|
+
id: "kill"
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
e: "log",
|
|
1410
|
+
msg: `cannot start: ${ev.err}`
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
e: "exit",
|
|
1414
|
+
code: 1
|
|
1415
|
+
}
|
|
1416
|
+
]
|
|
1417
|
+
};
|
|
1418
|
+
switch (s.k) {
|
|
1419
|
+
case "idle":
|
|
1420
|
+
case "running": switch (ev.t) {
|
|
1421
|
+
case "start":
|
|
1422
|
+
if (s.k !== "idle") return same;
|
|
1423
|
+
return {
|
|
1424
|
+
state: { k: "running" },
|
|
1425
|
+
effects: [{
|
|
1426
|
+
e: "log",
|
|
1427
|
+
msg: `watching for changes${hint}`
|
|
1428
|
+
}, { e: "spawn" }]
|
|
1429
|
+
};
|
|
1430
|
+
case "change": {
|
|
1431
|
+
const db = s.debounce;
|
|
1432
|
+
if (!db) return {
|
|
1433
|
+
state: {
|
|
1434
|
+
...s,
|
|
1435
|
+
debounce: {
|
|
1436
|
+
firstAt: ev.now,
|
|
1437
|
+
paths: [ev.path]
|
|
1438
|
+
}
|
|
1439
|
+
},
|
|
1440
|
+
effects: [{
|
|
1441
|
+
e: "startTimer",
|
|
1442
|
+
id: "debounce",
|
|
1443
|
+
ms: 100
|
|
1444
|
+
}]
|
|
1445
|
+
};
|
|
1446
|
+
const paths = db.paths.includes(ev.path) ? db.paths : [...db.paths, ev.path];
|
|
1447
|
+
const left = Math.max(0, 500 - (ev.now - db.firstAt));
|
|
1448
|
+
return {
|
|
1449
|
+
state: {
|
|
1450
|
+
...s,
|
|
1451
|
+
debounce: {
|
|
1452
|
+
firstAt: db.firstAt,
|
|
1453
|
+
paths
|
|
1454
|
+
}
|
|
1455
|
+
},
|
|
1456
|
+
effects: [{
|
|
1457
|
+
e: "startTimer",
|
|
1458
|
+
id: "debounce",
|
|
1459
|
+
ms: Math.min(100, left)
|
|
1460
|
+
}]
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
case "debounceFired": {
|
|
1464
|
+
const db = s.debounce;
|
|
1465
|
+
if (!db) return same;
|
|
1466
|
+
const more = db.paths.length > 1 ? ` (+${db.paths.length - 1} more)` : "";
|
|
1467
|
+
return trigger(s, `change in ${db.paths[0]}${more}`, cfg);
|
|
1468
|
+
}
|
|
1469
|
+
case "enter": {
|
|
1470
|
+
const r = trigger(s, "Enter", cfg);
|
|
1471
|
+
return s.debounce ? {
|
|
1472
|
+
...r,
|
|
1473
|
+
effects: [{
|
|
1474
|
+
e: "clearTimer",
|
|
1475
|
+
id: "debounce"
|
|
1476
|
+
}, ...r.effects]
|
|
1477
|
+
} : r;
|
|
1478
|
+
}
|
|
1479
|
+
case "childExit": {
|
|
1480
|
+
if (s.k !== "running") return same;
|
|
1481
|
+
const lastExit = exitReason(ev.code, ev.signal);
|
|
1482
|
+
return {
|
|
1483
|
+
state: {
|
|
1484
|
+
k: "idle",
|
|
1485
|
+
debounce: s.debounce
|
|
1486
|
+
},
|
|
1487
|
+
effects: [{
|
|
1488
|
+
e: "log",
|
|
1489
|
+
msg: `${lastExit} — waiting for changes${hint}`
|
|
1490
|
+
}]
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
case "signal":
|
|
1494
|
+
if (s.k === "idle") return {
|
|
1495
|
+
state: { k: "done" },
|
|
1496
|
+
effects: [{
|
|
1497
|
+
e: "exit",
|
|
1498
|
+
code: 0
|
|
1499
|
+
}]
|
|
1500
|
+
};
|
|
1501
|
+
return {
|
|
1502
|
+
state: {
|
|
1503
|
+
k: "stopping",
|
|
1504
|
+
after: "exit",
|
|
1505
|
+
firstSig: ev.sig,
|
|
1506
|
+
firstSigAt: ev.now
|
|
1507
|
+
},
|
|
1508
|
+
effects: [
|
|
1509
|
+
{
|
|
1510
|
+
e: "clearTimer",
|
|
1511
|
+
id: "debounce"
|
|
1512
|
+
},
|
|
1513
|
+
{
|
|
1514
|
+
e: "forwardSignal",
|
|
1515
|
+
sig: ev.sig
|
|
1516
|
+
},
|
|
1517
|
+
{
|
|
1518
|
+
e: "startTimer",
|
|
1519
|
+
id: "kill",
|
|
1520
|
+
ms: cfg.killTimeout
|
|
1521
|
+
}
|
|
1522
|
+
]
|
|
1523
|
+
};
|
|
1524
|
+
default: return same;
|
|
1525
|
+
}
|
|
1526
|
+
case "stopping": switch (ev.t) {
|
|
1527
|
+
case "change": return same;
|
|
1528
|
+
case "enter": return {
|
|
1529
|
+
state: s,
|
|
1530
|
+
effects: [{
|
|
1531
|
+
e: "log",
|
|
1532
|
+
msg: "force killing"
|
|
1533
|
+
}, { e: "killTree" }]
|
|
1534
|
+
};
|
|
1535
|
+
case "killTimeout": return {
|
|
1536
|
+
state: s,
|
|
1537
|
+
effects: [{
|
|
1538
|
+
e: "log",
|
|
1539
|
+
msg: `still running after ${ms(cfg.killTimeout)}, force killing`
|
|
1540
|
+
}, { e: "killTree" }]
|
|
1541
|
+
};
|
|
1542
|
+
case "childExit":
|
|
1543
|
+
if (s.after === "restart") return {
|
|
1544
|
+
state: { k: "running" },
|
|
1545
|
+
effects: [{
|
|
1546
|
+
e: "clearTimer",
|
|
1547
|
+
id: "kill"
|
|
1548
|
+
}, { e: "spawn" }]
|
|
1549
|
+
};
|
|
1550
|
+
return {
|
|
1551
|
+
state: { k: "done" },
|
|
1552
|
+
effects: [{
|
|
1553
|
+
e: "clearTimer",
|
|
1554
|
+
id: "kill"
|
|
1555
|
+
}, {
|
|
1556
|
+
e: "exit",
|
|
1557
|
+
code: ev.code ?? 0
|
|
1558
|
+
}]
|
|
1559
|
+
};
|
|
1560
|
+
case "signal": {
|
|
1561
|
+
if (s.after === "restart") return {
|
|
1562
|
+
state: {
|
|
1563
|
+
...s,
|
|
1564
|
+
after: "exit",
|
|
1565
|
+
firstSig: ev.sig,
|
|
1566
|
+
firstSigAt: ev.now
|
|
1567
|
+
},
|
|
1568
|
+
effects: [{
|
|
1569
|
+
e: "forwardSignal",
|
|
1570
|
+
sig: ev.sig
|
|
1571
|
+
}]
|
|
1572
|
+
};
|
|
1573
|
+
const since = ev.now - (s.firstSigAt ?? ev.now);
|
|
1574
|
+
if (ev.sig === "SIGINT" && since > 500) return {
|
|
1575
|
+
state: s,
|
|
1576
|
+
effects: [{
|
|
1577
|
+
e: "log",
|
|
1578
|
+
msg: "force killing"
|
|
1579
|
+
}, { e: "killTree" }]
|
|
1580
|
+
};
|
|
1581
|
+
if (ev.sig === s.firstSig && since <= 500) return same;
|
|
1582
|
+
return {
|
|
1583
|
+
state: s,
|
|
1584
|
+
effects: [{
|
|
1585
|
+
e: "forwardSignal",
|
|
1586
|
+
sig: ev.sig
|
|
1587
|
+
}]
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
default: return same;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
// ../../oxc-node/packages/cli/src/watch/messages.ts
|
|
1595
|
+
/** Whether oxc-node sent this report: a tsconfig it read or a path a failed resolve tried. */
|
|
1596
|
+
function fromOxcNode(msg) {
|
|
1597
|
+
return typeof msg === "object" && msg !== null && msg["oxc-node"] === true;
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* The paths in a `watch:require` / `watch:import` message, or null when it is not one.
|
|
1601
|
+
* Each entry is read on its own: Node.js drops the whole array when one entry is bad.
|
|
1602
|
+
*/
|
|
1603
|
+
function parseWatchMessage(msg) {
|
|
1604
|
+
if (typeof msg !== "object" || msg === null) return null;
|
|
1605
|
+
const record = msg;
|
|
1606
|
+
const lists = [record["watch:require"], record["watch:import"]];
|
|
1607
|
+
if (lists.every((list) => list === void 0)) return null;
|
|
1608
|
+
const paths = [];
|
|
1609
|
+
for (const list of lists) {
|
|
1610
|
+
if (!Array.isArray(list)) continue;
|
|
1611
|
+
for (const entry of list) try {
|
|
1612
|
+
if (typeof entry !== "string" || entry === "") continue;
|
|
1613
|
+
if (entry.startsWith("file:")) {
|
|
1614
|
+
paths.push(fileURLToPath(entry));
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
if (/^[a-z][a-z\d+.-]+:/i.test(entry)) continue;
|
|
1618
|
+
if (!isAbsolute(entry)) continue;
|
|
1619
|
+
paths.push(resolve(entry));
|
|
1620
|
+
} catch {}
|
|
1621
|
+
}
|
|
1622
|
+
return paths;
|
|
1623
|
+
}
|
|
1624
|
+
// ../../oxc-node/packages/cli/src/watch/command.ts
|
|
1625
|
+
const isBareReturn = (chunk) => /[\r\n]/.test(chunk) && chunk.replace(/[\r\n]/g, "") === "";
|
|
1626
|
+
/**
|
|
1627
|
+
* Run the watcher until it stops, and resolve with the exit code. Until then it owns
|
|
1628
|
+
* SIGINT, SIGTERM and SIGHUP, stdin (for Return) and the process's IPC channel.
|
|
1629
|
+
*/
|
|
1630
|
+
function runWatch(argv, options = {}) {
|
|
1631
|
+
const names = namesFor(options.name);
|
|
1632
|
+
const log = createLog(names.label);
|
|
1633
|
+
const args = parseWatchArgs(argv, process$1.env, void 0, void 0, names);
|
|
1634
|
+
if ("error" in args) {
|
|
1635
|
+
process$1.stderr.write(`${names.name}: ${args.error}\n${usage(names)}\nRun "${names.name} --help" for the options.\n`);
|
|
1636
|
+
return Promise.resolve(1);
|
|
1637
|
+
}
|
|
1638
|
+
if (args.help) {
|
|
1639
|
+
process$1.stdout.write(help(names));
|
|
1640
|
+
return Promise.resolve(0);
|
|
1641
|
+
}
|
|
1642
|
+
const cwd = process$1.cwd();
|
|
1643
|
+
let { loaderArgs, ignoreDirs: given = [] } = options;
|
|
1644
|
+
if (!loaderArgs) {
|
|
1645
|
+
const registerURL = import.meta.resolve("@oxc-node/core/register");
|
|
1646
|
+
loaderArgs = [
|
|
1647
|
+
`--enable-source-maps`,
|
|
1648
|
+
`--import`,
|
|
1649
|
+
registerURL
|
|
1650
|
+
];
|
|
1651
|
+
if (!options.ignoreDirs) given = [path.dirname(fileURLToPath(registerURL))];
|
|
1652
|
+
}
|
|
1653
|
+
const ignoreDirs = [];
|
|
1654
|
+
for (const dir of given) {
|
|
1655
|
+
ignoreDirs.push(dir);
|
|
1656
|
+
try {
|
|
1657
|
+
const real = realpathSync.native(dir);
|
|
1658
|
+
if (real !== dir) ignoreDirs.push(real);
|
|
1659
|
+
} catch {}
|
|
1660
|
+
}
|
|
1661
|
+
let matcher;
|
|
1662
|
+
try {
|
|
1663
|
+
matcher = makeMatcher({
|
|
1664
|
+
cwd,
|
|
1665
|
+
include: args.include,
|
|
1666
|
+
exclude: args.exclude,
|
|
1667
|
+
platform: process$1.platform,
|
|
1668
|
+
ignoreDirs
|
|
1669
|
+
});
|
|
1670
|
+
} catch (error) {
|
|
1671
|
+
process$1.stderr.write(`${names.name}: ${error.message}\n`);
|
|
1672
|
+
return Promise.resolve(1);
|
|
1673
|
+
}
|
|
1674
|
+
for (const note of args.notes) log(note);
|
|
1675
|
+
return new Promise((resolve) => {
|
|
1676
|
+
const ack = createSignalAck();
|
|
1677
|
+
let child;
|
|
1678
|
+
let latest;
|
|
1679
|
+
const signals = gateSignals(new SignalGate({
|
|
1680
|
+
nodeWatch: false,
|
|
1681
|
+
platform: process$1.platform,
|
|
1682
|
+
terminal: sigintFromTerminal
|
|
1683
|
+
}), ack, (sig) => child?.kill(sig), debug);
|
|
1684
|
+
const enter = args.rerunOnEnter ?? sigintFromTerminal();
|
|
1685
|
+
const cfg = {
|
|
1686
|
+
killSignal: args.killSignal,
|
|
1687
|
+
killTimeout: args.killTimeout,
|
|
1688
|
+
clear: args.clearScreen && Boolean(process$1.stdout.isTTY),
|
|
1689
|
+
enter
|
|
1690
|
+
};
|
|
1691
|
+
const env = {
|
|
1692
|
+
...process$1.env,
|
|
1693
|
+
WATCH_REPORT_DEPENDENCIES: "1",
|
|
1694
|
+
...ack.env,
|
|
1695
|
+
...tsconfigEnv(args.tsconfig, cwd)
|
|
1696
|
+
};
|
|
1697
|
+
const nodeArgs = [...loaderArgs, ...args.nodeArgs];
|
|
1698
|
+
const timers = /* @__PURE__ */ new Map();
|
|
1699
|
+
let state = { k: "idle" };
|
|
1700
|
+
const display = (p) => {
|
|
1701
|
+
const rel = path.relative(cwd, p);
|
|
1702
|
+
return rel && !rel.startsWith("..") && !path.isAbsolute(rel) ? `.${path.sep}${rel}` : p;
|
|
1703
|
+
};
|
|
1704
|
+
const fold = (p) => process$1.platform === "win32" ? p.toLowerCase() : p;
|
|
1705
|
+
const reported = /* @__PURE__ */ new Map();
|
|
1706
|
+
const runAliases = /* @__PURE__ */ new Set();
|
|
1707
|
+
const watcher = new DirWatcher({
|
|
1708
|
+
poll: args.poll,
|
|
1709
|
+
platform: process$1.platform,
|
|
1710
|
+
matcher,
|
|
1711
|
+
onChange: (p) => dispatch({
|
|
1712
|
+
t: "change",
|
|
1713
|
+
path: display(reported.get(fold(p)) ?? p),
|
|
1714
|
+
now: Date.now()
|
|
1715
|
+
}),
|
|
1716
|
+
debug,
|
|
1717
|
+
warn: log
|
|
1718
|
+
});
|
|
1719
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1720
|
+
const runSeen = /* @__PURE__ */ new Set();
|
|
1721
|
+
const want = (p, source) => {
|
|
1722
|
+
const key = source === "oxc" ? `oxc:${p}` : p;
|
|
1723
|
+
if (seen.has(p) || runSeen.has(key)) return;
|
|
1724
|
+
const run = source === "graph" || source === "oxc";
|
|
1725
|
+
(run ? runSeen : seen).add(key);
|
|
1726
|
+
const verdict = matcher.classify(p, source);
|
|
1727
|
+
if (verdict === "watch") watcher.add(p, run, source === "oxc");
|
|
1728
|
+
else debug(`skip ${p} (${verdict === "exclude" ? "exclude" : "default"})`);
|
|
1729
|
+
if (verdict === "exclude") return;
|
|
1730
|
+
try {
|
|
1731
|
+
const real = realpathSync.native(p);
|
|
1732
|
+
if (real !== p) {
|
|
1733
|
+
if (!reported.has(fold(real))) {
|
|
1734
|
+
reported.set(fold(real), p);
|
|
1735
|
+
if (run) runAliases.add(fold(real));
|
|
1736
|
+
}
|
|
1737
|
+
if (source === "literal" && lstatSync(p).isSymbolicLink()) return;
|
|
1738
|
+
want(real, source);
|
|
1739
|
+
}
|
|
1740
|
+
} catch {}
|
|
1741
|
+
};
|
|
1742
|
+
const newRun = () => {
|
|
1743
|
+
runSeen.clear();
|
|
1744
|
+
for (const k of runAliases) reported.delete(k);
|
|
1745
|
+
runAliases.clear();
|
|
1746
|
+
watcher.dropRun();
|
|
1747
|
+
};
|
|
1748
|
+
const spawnChild = () => {
|
|
1749
|
+
const nested = nestedWatcher(args, process$1.env);
|
|
1750
|
+
if (nested) {
|
|
1751
|
+
queueMicrotask(() => dispatch({
|
|
1752
|
+
t: "spawnError",
|
|
1753
|
+
err: nested
|
|
1754
|
+
}));
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
newRun();
|
|
1758
|
+
const c = spawn(process$1.execPath, nodeArgs, {
|
|
1759
|
+
env,
|
|
1760
|
+
stdio: [
|
|
1761
|
+
"inherit",
|
|
1762
|
+
"inherit",
|
|
1763
|
+
"inherit",
|
|
1764
|
+
"ipc"
|
|
1765
|
+
],
|
|
1766
|
+
windowsHide: false
|
|
1767
|
+
});
|
|
1768
|
+
child = c;
|
|
1769
|
+
latest = c;
|
|
1770
|
+
c.on("message", (msg) => {
|
|
1771
|
+
const paths = parseWatchMessage(msg);
|
|
1772
|
+
if (paths) {
|
|
1773
|
+
const source = fromOxcNode(msg) ? "oxc" : "graph";
|
|
1774
|
+
if (c === latest) for (const p of paths) want(p, source);
|
|
1775
|
+
} else if (process$1.send && process$1.connected) process$1.send(msg, () => {});
|
|
1776
|
+
});
|
|
1777
|
+
c.on("error", (error) => {
|
|
1778
|
+
if (c.pid === void 0 && child === c) {
|
|
1779
|
+
child = void 0;
|
|
1780
|
+
dispatch({
|
|
1781
|
+
t: "spawnError",
|
|
1782
|
+
err: error.message
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1786
|
+
c.on("exit", (code, signal) => {
|
|
1787
|
+
if (child !== c) return;
|
|
1788
|
+
child = void 0;
|
|
1789
|
+
dispatch({
|
|
1790
|
+
t: "childExit",
|
|
1791
|
+
code,
|
|
1792
|
+
signal
|
|
1793
|
+
});
|
|
1794
|
+
});
|
|
1795
|
+
if (c.pid !== void 0) debug(`spawn ${c.pid}`);
|
|
1796
|
+
};
|
|
1797
|
+
const run = (e) => {
|
|
1798
|
+
switch (e.e) {
|
|
1799
|
+
case "spawn":
|
|
1800
|
+
spawnChild();
|
|
1801
|
+
break;
|
|
1802
|
+
case "kill":
|
|
1803
|
+
if (child && process$1.platform === "win32") killTree(child);
|
|
1804
|
+
else child?.kill(e.sig);
|
|
1805
|
+
break;
|
|
1806
|
+
case "killTree":
|
|
1807
|
+
if (child) killTree(child);
|
|
1808
|
+
break;
|
|
1809
|
+
case "clearScreen":
|
|
1810
|
+
process$1.stdout.write("\x1Bc");
|
|
1811
|
+
break;
|
|
1812
|
+
case "log":
|
|
1813
|
+
log(e.msg);
|
|
1814
|
+
break;
|
|
1815
|
+
case "startTimer": {
|
|
1816
|
+
clearTimeout(timers.get(e.id));
|
|
1817
|
+
const ev = e.id === "debounce" ? { t: "debounceFired" } : { t: "killTimeout" };
|
|
1818
|
+
timers.set(e.id, setTimeout(() => {
|
|
1819
|
+
timers.delete(e.id);
|
|
1820
|
+
dispatch(ev);
|
|
1821
|
+
}, e.ms));
|
|
1822
|
+
break;
|
|
1823
|
+
}
|
|
1824
|
+
case "clearTimer":
|
|
1825
|
+
clearTimeout(timers.get(e.id));
|
|
1826
|
+
timers.delete(e.id);
|
|
1827
|
+
break;
|
|
1828
|
+
case "forwardSignal":
|
|
1829
|
+
signals.signal(e.sig);
|
|
1830
|
+
break;
|
|
1831
|
+
case "exit": finish(e.code);
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
function dispatch(ev) {
|
|
1835
|
+
const r = step(state, ev, cfg);
|
|
1836
|
+
state = r.state;
|
|
1837
|
+
for (const e of r.effects) run(e);
|
|
1838
|
+
}
|
|
1839
|
+
const onSignal = (sig) => dispatch({
|
|
1840
|
+
t: "signal",
|
|
1841
|
+
sig,
|
|
1842
|
+
now: Date.now()
|
|
1843
|
+
});
|
|
1844
|
+
const onParentMessage = (msg) => {
|
|
1845
|
+
if (child?.connected) child.send(msg, () => {});
|
|
1846
|
+
};
|
|
1847
|
+
let reading = false;
|
|
1848
|
+
const onData = (chunk) => {
|
|
1849
|
+
if (isBareReturn(chunk)) dispatch({ t: "enter" });
|
|
1850
|
+
};
|
|
1851
|
+
const stopReading = () => {
|
|
1852
|
+
if (!reading) return;
|
|
1853
|
+
reading = false;
|
|
1854
|
+
process$1.stdin.off("data", onData).off("end", stopReading).off("error", stopReading);
|
|
1855
|
+
process$1.stdin.pause();
|
|
1856
|
+
process$1.stdin.unref?.();
|
|
1857
|
+
};
|
|
1858
|
+
function finish(code) {
|
|
1859
|
+
for (const t of timers.values()) clearTimeout(t);
|
|
1860
|
+
timers.clear();
|
|
1861
|
+
signals.dispose();
|
|
1862
|
+
ack.close();
|
|
1863
|
+
watcher.close();
|
|
1864
|
+
stopReading();
|
|
1865
|
+
for (const sig of FORWARDED) process$1.off(sig, onSignal);
|
|
1866
|
+
process$1.off("message", onParentMessage);
|
|
1867
|
+
resolve(code);
|
|
1868
|
+
}
|
|
1869
|
+
for (const sig of FORWARDED) process$1.on(sig, onSignal);
|
|
1870
|
+
if (process$1.send) process$1.on("message", onParentMessage);
|
|
1871
|
+
if (enter) {
|
|
1872
|
+
reading = true;
|
|
1873
|
+
process$1.stdin.setEncoding("utf8");
|
|
1874
|
+
process$1.stdin.on("data", onData).on("end", stopReading).on("error", stopReading);
|
|
1875
|
+
}
|
|
1876
|
+
want(path.resolve(cwd, args.entry), "literal");
|
|
1877
|
+
for (const file of args.envFiles) want(path.resolve(cwd, file), "literal");
|
|
1878
|
+
for (const file of args.configFiles) want(path.resolve(cwd, file), "literal");
|
|
1879
|
+
if (args.tsconfig) want(path.resolve(cwd, args.tsconfig), "literal");
|
|
1880
|
+
for (const file of matcher.literals()) want(file, "literal");
|
|
1881
|
+
for (const root of matcher.roots()) watcher.addRoot(root.base, root.depth);
|
|
1882
|
+
dispatch({ t: "start" });
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
// src/script-watch.ts
|
|
1886
|
+
/**
|
|
1887
|
+
* `vpx --watch`: oxc-node's watcher (`@oxc-node/cli/watch`), bundled from the vendored
|
|
1888
|
+
* source, running the script with the `vpx <script>` loader instead of Node.js' own watch
|
|
1889
|
+
* mode. Built to `dist/script-watch.js`.
|
|
1890
|
+
*
|
|
1891
|
+
* The global `vpx` runs `node dist/script-watch.js <args>`, and the project-local bin
|
|
1892
|
+
* imports it in process; either way the watcher's arguments are `process.argv.slice(2)`,
|
|
1893
|
+
* the `vpx` arguments without `--watch` and `--tsconfig`, which the CLIs pass on as
|
|
1894
|
+
* `VP_SCRIPT_TSCONFIG`, as for a script. The watcher owns SIGINT, SIGTERM, SIGHUP,
|
|
1895
|
+
* stdin, and the IPC channel until it stops.
|
|
1896
|
+
*/
|
|
1897
|
+
const dist = path.dirname(fileURLToPath(import.meta.url));
|
|
1898
|
+
process.exitCode = await runWatch(process.argv.slice(2), {
|
|
1899
|
+
name: "vpx --watch",
|
|
1900
|
+
loaderArgs: [
|
|
1901
|
+
"--require",
|
|
1902
|
+
path.join(dist, "script-preload.cjs"),
|
|
1903
|
+
"--import",
|
|
1904
|
+
new URL("./script-register.js", import.meta.url).href
|
|
1905
|
+
],
|
|
1906
|
+
ignoreDirs: [path.dirname(dist)]
|
|
1907
|
+
});
|
|
1908
|
+
export {};
|