@oh-my-pi/pi-utils 18.2.11 → 18.3.1
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/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/THIRD-PARTY-NOTICES.txt +3 -3
- package/dist/types/dirs.d.ts +12 -3
- package/dist/types/fs-open.d.ts +16 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/procmgr.d.ts +3 -0
- package/package.json +6 -3
- package/src/dirs.ts +17 -7
- package/src/fs-open.ts +31 -0
- package/src/index.ts +1 -0
- package/src/logger/rotating-file.ts +6 -9
- package/src/procmgr.ts +18 -18
- package/src/runtime-install.ts +43 -7
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
package/THIRD-PARTY-NOTICES.txt
CHANGED
|
@@ -78,8 +78,8 @@ below.
|
|
|
78
78
|
uutils coreutils (https://github.com/uutils/coreutils), MIT
|
|
79
79
|
--------------------------------------------------------------------------------
|
|
80
80
|
Covers: base32, base64, basename, cat, cksum (shared checksum machinery),
|
|
81
|
-
b2sum, md5sum, sha1sum, sha224sum, sha256sum, sha384sum, sha512sum, comm,
|
|
82
|
-
date, dirname, head, hostname, ln, ls, mkdir, mktemp, mv, nproc, paste,
|
|
81
|
+
b2sum, md5sum, sha1sum, sha224sum, sha256sum, sha384sum, sha512sum, comm, cp,
|
|
82
|
+
cut, date, dirname, head, hostname, ln, ls, mkdir, mktemp, mv, nproc, paste,
|
|
83
83
|
printenv, readlink, realpath, rm, seq, sort, stat, tac, tail, tee, touch, tr,
|
|
84
84
|
truncate, uname, uniq, wc, whoami, yes.
|
|
85
85
|
|
|
@@ -293,7 +293,7 @@ MIT License
|
|
|
293
293
|
Copyright (c) 2026 Sander Land
|
|
294
294
|
(measured tokenizer vocabulary data, reconstruction model, and reference
|
|
295
295
|
implementation: https://github.com/sanderland/ctok)
|
|
296
|
-
Copyright (c) 2026 Can Bölük and the
|
|
296
|
+
Copyright (c) 2026 Can Bölük and the omp contributors
|
|
297
297
|
Copyright (c) 2026 Stencil Labs, Inc.
|
|
298
298
|
(Rust implementation and the compact binary vocabulary encoding in
|
|
299
299
|
crates/pi-natives/src/utok/claude)
|
package/dist/types/dirs.d.ts
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
/** App name (e.g. "omp") */
|
|
14
14
|
export declare const APP_NAME: string;
|
|
15
|
+
/** Public homepage that inference gateways (OpenRouter, Vercel AI Gateway) credit omp traffic to. */
|
|
16
|
+
export declare const APP_URL: string;
|
|
15
17
|
/** Config directory name (e.g. ".omp") */
|
|
16
18
|
export declare const CONFIG_DIR_NAME: string;
|
|
17
19
|
/** Ordered main settings filenames: canonical write target first, legacy-compatible YAML fallback second. */
|
|
@@ -135,7 +137,16 @@ export declare function getProjectAgentDir(cwd?: string): string;
|
|
|
135
137
|
export declare function getReportsDir(): string;
|
|
136
138
|
/** Get the logs directory (~/.omp/logs). */
|
|
137
139
|
export declare function getLogsDir(): string;
|
|
138
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* Local-timezone `YYYY-MM-DD` day key (zero-padded), formatted exactly like
|
|
142
|
+
* the rotating log sink's file naming: log files are named `omp.<day>.<pid>.log`
|
|
143
|
+
* with the LOCAL day, not the UTC day `toISOString()` yields. Anything that
|
|
144
|
+
* computes "today's" log path or matches same-day log files by name must use
|
|
145
|
+
* this key, or between local midnight and UTC midnight it points at files that
|
|
146
|
+
* do not exist (e.g. 00:00–08:00 in UTC+8).
|
|
147
|
+
*/
|
|
148
|
+
export declare function localDay(date: Date): string;
|
|
149
|
+
/** Get this process's dated log path (~/.omp/logs/omp.YYYY-MM-DD.PID.log, local-day named like the rotating sink). */
|
|
139
150
|
export declare function getLogPath(date?: Date, pid?: number): string;
|
|
140
151
|
/**
|
|
141
152
|
* Get the plugins directory (~/.omp/plugins or its XDG equivalent).
|
|
@@ -206,8 +217,6 @@ export declare function getAutoQaDbPath(): string;
|
|
|
206
217
|
export declare function hashPath(absPath: string): string;
|
|
207
218
|
/** Get the path to a single worktree directory (~/.omp/wt/<segment>). */
|
|
208
219
|
export declare function getWorktreeDir(segment: string): string;
|
|
209
|
-
/** Get the GPU cache path (~/.omp/gpu_cache.json). */
|
|
210
|
-
export declare function getGpuCachePath(): string;
|
|
211
220
|
/**
|
|
212
221
|
* Get the GitHub view cache database path (~/.omp/cache/github-cache.db).
|
|
213
222
|
* Honors the `OMP_GITHUB_CACHE_DB` env var when set so tests can isolate the
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fs.openSync` with close-on-exec, matching Node's default.
|
|
3
|
+
*
|
|
4
|
+
* libuv adds `O_CLOEXEC` inside `uv__fs_open`, so on Node every `fs` descriptor
|
|
5
|
+
* dies at `exec`. Bun's `fs.open*` does not, and descriptors it returns survive
|
|
6
|
+
* into any child spawned through a path that does not close strays itself — the
|
|
7
|
+
* bash tool's shell `fork`/`exec`s user commands and hands them the lot. Use
|
|
8
|
+
* this for every descriptor kept open past such a spawn (session transcripts,
|
|
9
|
+
* logs, spools); a child must never hold a writable handle to them (#13224).
|
|
10
|
+
*
|
|
11
|
+
* Flags are numeric only: the bit cannot be OR-ed into `"a"`/`"w+"` strings.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const fd = openCloexecSync(logPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_APPEND);
|
|
15
|
+
*/
|
|
16
|
+
export declare function openCloexecSync(filePath: string, flags: number, mode?: number): number;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from "./file-lock.js";
|
|
|
10
10
|
export * from "./format.js";
|
|
11
11
|
export * from "./frontmatter.js";
|
|
12
12
|
export * from "./fs-error.js";
|
|
13
|
+
export * from "./fs-open.js";
|
|
13
14
|
export * from "./incoming-json.js";
|
|
14
15
|
export * from "./json.js";
|
|
15
16
|
export * from "./json-parse.js";
|
package/dist/types/procmgr.d.ts
CHANGED
|
@@ -65,6 +65,9 @@ export declare function resolveWindowsShell(env?: Record<string, string | undefi
|
|
|
65
65
|
* {@link resolveWindowsShell}) — never fails
|
|
66
66
|
* 3. On Unix: $SHELL if bash/zsh, then fallback paths
|
|
67
67
|
* 4. Fallback: sh
|
|
68
|
+
*
|
|
69
|
+
* Custom and auto-resolved configs are cached separately, so clearing
|
|
70
|
+
* `shellPath` mid-process immediately falls back to the platform default.
|
|
68
71
|
*/
|
|
69
72
|
export declare function getShellConfig(customShellPath?: string, options?: ShellConfigOptions): ShellConfig;
|
|
70
73
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-utils",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.3.1",
|
|
4
4
|
"description": "Shared utilities for pi packages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
"url": "https://github.com/can1357/oh-my-pi/issues"
|
|
14
14
|
},
|
|
15
15
|
"license": "MIT",
|
|
16
|
-
"author":
|
|
16
|
+
"author": {
|
|
17
|
+
"name": "Stencil Labs, Inc.",
|
|
18
|
+
"url": "https://stencil.so"
|
|
19
|
+
},
|
|
17
20
|
"repository": {
|
|
18
21
|
"type": "git",
|
|
19
22
|
"url": "git+https://github.com/can1357/oh-my-pi.git",
|
|
@@ -54,7 +57,7 @@
|
|
|
54
57
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
55
58
|
},
|
|
56
59
|
"dependencies": {
|
|
57
|
-
"@oh-my-pi/pi-natives": "18.
|
|
60
|
+
"@oh-my-pi/pi-natives": "18.3.1"
|
|
58
61
|
},
|
|
59
62
|
"devDependencies": {
|
|
60
63
|
"@types/bun": "^1.3.14"
|
package/src/dirs.ts
CHANGED
|
@@ -20,6 +20,9 @@ import { isEnoent, isEnotdir } from "./fs-error";
|
|
|
20
20
|
/** App name (e.g. "omp") */
|
|
21
21
|
export const APP_NAME: string = "omp";
|
|
22
22
|
|
|
23
|
+
/** Public homepage that inference gateways (OpenRouter, Vercel AI Gateway) credit omp traffic to. */
|
|
24
|
+
export const APP_URL: string = "https://omp.sh/";
|
|
25
|
+
|
|
23
26
|
/** Config directory name (e.g. ".omp") */
|
|
24
27
|
export const CONFIG_DIR_NAME: string = ".omp";
|
|
25
28
|
|
|
@@ -601,9 +604,21 @@ export function getLogsDir(): string {
|
|
|
601
604
|
return dirs.rootSubdir("logs", "state");
|
|
602
605
|
}
|
|
603
606
|
|
|
604
|
-
/**
|
|
607
|
+
/**
|
|
608
|
+
* Local-timezone `YYYY-MM-DD` day key (zero-padded), formatted exactly like
|
|
609
|
+
* the rotating log sink's file naming: log files are named `omp.<day>.<pid>.log`
|
|
610
|
+
* with the LOCAL day, not the UTC day `toISOString()` yields. Anything that
|
|
611
|
+
* computes "today's" log path or matches same-day log files by name must use
|
|
612
|
+
* this key, or between local midnight and UTC midnight it points at files that
|
|
613
|
+
* do not exist (e.g. 00:00–08:00 in UTC+8).
|
|
614
|
+
*/
|
|
615
|
+
export function localDay(date: Date): string {
|
|
616
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** Get this process's dated log path (~/.omp/logs/omp.YYYY-MM-DD.PID.log, local-day named like the rotating sink). */
|
|
605
620
|
export function getLogPath(date = new Date(), pid = process.pid): string {
|
|
606
|
-
return path.join(getLogsDir(), `${APP_NAME}.${date
|
|
621
|
+
return path.join(getLogsDir(), `${APP_NAME}.${localDay(date)}.${pid}.log`);
|
|
607
622
|
}
|
|
608
623
|
|
|
609
624
|
/**
|
|
@@ -752,11 +767,6 @@ export function getWorktreeDir(segment: string): string {
|
|
|
752
767
|
return path.join(getWorktreesDir(), segment);
|
|
753
768
|
}
|
|
754
769
|
|
|
755
|
-
/** Get the GPU cache path (~/.omp/gpu_cache.json). */
|
|
756
|
-
export function getGpuCachePath(): string {
|
|
757
|
-
return dirs.rootSubdir("gpu_cache.json", "cache");
|
|
758
|
-
}
|
|
759
|
-
|
|
760
770
|
/**
|
|
761
771
|
* Get the GitHub view cache database path (~/.omp/cache/github-cache.db).
|
|
762
772
|
* Honors the `OMP_GITHUB_CACHE_DB` env var when set so tests can isolate the
|
package/src/fs-open.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Close-on-exec file opens for descriptors held past a child spawn. */
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `O_CLOEXEC`, which neither Node nor Bun exposes in `fs.constants`.
|
|
6
|
+
*
|
|
7
|
+
* Kernel ABI values: `0o2000000` on Linux, `0x0100_0000` on Darwin. Windows has
|
|
8
|
+
* no `exec` and its CRT handles are inherited only when a spawn explicitly asks
|
|
9
|
+
* for it, so the bit is 0 there — as it is on any other platform, where adding
|
|
10
|
+
* an unknown flag bit would be worse than leaving the descriptor inheritable.
|
|
11
|
+
*/
|
|
12
|
+
const O_CLOEXEC = process.platform === "linux" ? 0o2000000 : process.platform === "darwin" ? 0x0100_0000 : 0;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* `fs.openSync` with close-on-exec, matching Node's default.
|
|
16
|
+
*
|
|
17
|
+
* libuv adds `O_CLOEXEC` inside `uv__fs_open`, so on Node every `fs` descriptor
|
|
18
|
+
* dies at `exec`. Bun's `fs.open*` does not, and descriptors it returns survive
|
|
19
|
+
* into any child spawned through a path that does not close strays itself — the
|
|
20
|
+
* bash tool's shell `fork`/`exec`s user commands and hands them the lot. Use
|
|
21
|
+
* this for every descriptor kept open past such a spawn (session transcripts,
|
|
22
|
+
* logs, spools); a child must never hold a writable handle to them (#13224).
|
|
23
|
+
*
|
|
24
|
+
* Flags are numeric only: the bit cannot be OR-ed into `"a"`/`"w+"` strings.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const fd = openCloexecSync(logPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_APPEND);
|
|
28
|
+
*/
|
|
29
|
+
export function openCloexecSync(filePath: string, flags: number, mode?: number): number {
|
|
30
|
+
return fs.openSync(filePath, flags | O_CLOEXEC, mode);
|
|
31
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/** Behavior-compatible reimplementation of winston-daily-rotate-file's used surface. */
|
|
2
|
-
import * as crypto from "node:crypto";
|
|
3
2
|
import * as fs from "node:fs";
|
|
4
3
|
import * as os from "node:os";
|
|
5
4
|
import * as path from "node:path";
|
|
5
|
+
import { localDay } from "../dirs";
|
|
6
|
+
import { openCloexecSync } from "../fs-open";
|
|
6
7
|
|
|
7
8
|
interface AuditEntry {
|
|
8
9
|
readonly date: number;
|
|
@@ -63,7 +64,7 @@ export class RotatingFileSink {
|
|
|
63
64
|
this.#maxFiles = options.maxFiles;
|
|
64
65
|
this.#files = this.#readAudit();
|
|
65
66
|
const now = new Date();
|
|
66
|
-
this.#selectFile(
|
|
67
|
+
this.#selectFile(localDay(now));
|
|
67
68
|
const activePath = this.#activePath;
|
|
68
69
|
if (activePath) {
|
|
69
70
|
this.#registerFile(activePath, now.getTime());
|
|
@@ -73,7 +74,7 @@ export class RotatingFileSink {
|
|
|
73
74
|
|
|
74
75
|
#openFd(filePath: string): void {
|
|
75
76
|
this.#closeFd();
|
|
76
|
-
this.#fd =
|
|
77
|
+
this.#fd = openCloexecSync(filePath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_APPEND);
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
#closeFd(): void {
|
|
@@ -92,7 +93,7 @@ export class RotatingFileSink {
|
|
|
92
93
|
if (this.#closed) return;
|
|
93
94
|
const prevPath = this.#activePath;
|
|
94
95
|
const now = new Date();
|
|
95
|
-
this.#selectFile(
|
|
96
|
+
this.#selectFile(localDay(now));
|
|
96
97
|
const activePath = this.#activePath;
|
|
97
98
|
if (!activePath) return;
|
|
98
99
|
// Rotation moved the active path: close the old descriptor BEFORE
|
|
@@ -120,10 +121,6 @@ export class RotatingFileSink {
|
|
|
120
121
|
this.#closeFd();
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
#localDay(date: Date): string {
|
|
124
|
-
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
124
|
#selectFile(day: string): void {
|
|
128
125
|
if (day !== this.#activeDay) {
|
|
129
126
|
this.#activeDay = day;
|
|
@@ -151,7 +148,7 @@ export class RotatingFileSink {
|
|
|
151
148
|
|
|
152
149
|
#registerFile(filePath: string, date: number): void {
|
|
153
150
|
if (this.#files.some(file => file.name === filePath)) return;
|
|
154
|
-
const hash =
|
|
151
|
+
const hash = Bun.SHA256.hash(`${filePath}LOG_FILE${date}`, "hex");
|
|
155
152
|
this.#files.push({ date, name: filePath, hash });
|
|
156
153
|
while (this.#files.length > this.#maxFiles) {
|
|
157
154
|
const removed = this.#files.shift();
|
package/src/procmgr.ts
CHANGED
|
@@ -20,7 +20,10 @@ export interface ShellConfigOptions {
|
|
|
20
20
|
/** File path or runtime layer that supplied the active shell setting. */
|
|
21
21
|
configSource?: string;
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
/** Auto-resolved shell (no custom path); stable for the process lifetime. */
|
|
24
|
+
let cachedDefaultShellConfig: ShellConfig | null = null;
|
|
25
|
+
/** Config for the most recent custom `shellPath`; never served once the path is cleared. */
|
|
26
|
+
let cachedCustomShellConfig: ShellConfig | null = null;
|
|
24
27
|
|
|
25
28
|
/**
|
|
26
29
|
* Build the spawn environment (cached).
|
|
@@ -177,6 +180,9 @@ export function resolveWindowsShell(env: Record<string, string | undefined> = Bu
|
|
|
177
180
|
* {@link resolveWindowsShell}) — never fails
|
|
178
181
|
* 3. On Unix: $SHELL if bash/zsh, then fallback paths
|
|
179
182
|
* 4. Fallback: sh
|
|
183
|
+
*
|
|
184
|
+
* Custom and auto-resolved configs are cached separately, so clearing
|
|
185
|
+
* `shellPath` mid-process immediately falls back to the platform default.
|
|
180
186
|
*/
|
|
181
187
|
export function getShellConfig(customShellPath?: string, options: ShellConfigOptions = {}): ShellConfig {
|
|
182
188
|
const configSource = options.configSource ?? path.join(getAgentDir(), MAIN_CONFIG_FILENAMES[0]);
|
|
@@ -187,36 +193,30 @@ export function getShellConfig(customShellPath?: string, options: ShellConfigOpt
|
|
|
187
193
|
if (!fs.existsSync(customShellPath)) {
|
|
188
194
|
throw new Error(`Custom shell path not found: ${customShellPath}\nPlease update shellPath in ${configSource}`);
|
|
189
195
|
}
|
|
190
|
-
if (
|
|
191
|
-
|
|
196
|
+
if (cachedCustomShellConfig?.shell !== customShellPath) {
|
|
197
|
+
cachedCustomShellConfig = buildConfig(customShellPath);
|
|
192
198
|
}
|
|
193
|
-
return
|
|
194
|
-
}
|
|
195
|
-
if (cachedShellConfig) {
|
|
196
|
-
return cachedShellConfig;
|
|
199
|
+
return cachedCustomShellConfig;
|
|
197
200
|
}
|
|
201
|
+
cachedDefaultShellConfig ??= buildConfig(resolveDefaultShell());
|
|
202
|
+
return cachedDefaultShellConfig;
|
|
203
|
+
}
|
|
198
204
|
|
|
205
|
+
/** Platform shell discovery used when no custom `shellPath` is configured. */
|
|
206
|
+
function resolveDefaultShell(): string {
|
|
199
207
|
if (process.platform === "win32") {
|
|
200
|
-
|
|
201
|
-
return cachedShellConfig;
|
|
208
|
+
return resolveWindowsShell();
|
|
202
209
|
}
|
|
203
210
|
|
|
204
211
|
// Unix: prefer user's shell from $SHELL if it's bash/zsh and executable
|
|
205
212
|
const userShell = Bun.env.SHELL;
|
|
206
213
|
const isValidShell = userShell && (userShell.includes("bash") || userShell.includes("zsh"));
|
|
207
214
|
if (isValidShell && isExecutable(userShell)) {
|
|
208
|
-
|
|
209
|
-
return cachedShellConfig;
|
|
215
|
+
return userShell;
|
|
210
216
|
}
|
|
211
217
|
|
|
212
218
|
// 4. Fallback: use basic shell
|
|
213
|
-
|
|
214
|
-
if (basicShell) {
|
|
215
|
-
cachedShellConfig = buildConfig(basicShell);
|
|
216
|
-
return cachedShellConfig;
|
|
217
|
-
}
|
|
218
|
-
cachedShellConfig = buildConfig("sh");
|
|
219
|
-
return cachedShellConfig;
|
|
219
|
+
return resolveBasicShell() ?? "sh";
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
/**
|
package/src/runtime-install.ts
CHANGED
|
@@ -172,9 +172,43 @@ function resolverRegistry(): ResolverRegistration[] {
|
|
|
172
172
|
holder[REGISTRY] ??= [];
|
|
173
173
|
return holder[REGISTRY];
|
|
174
174
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
/** Canonical form of a path, memoized: resolution is per module request, and roots repeat. */
|
|
176
|
+
const canonicalPaths = new Map<string, string>();
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Canonical form of `target` for path comparison: symlinks and Windows
|
|
180
|
+
* junctions resolved, drive/segment casing folded on Windows. Non-existent
|
|
181
|
+
* paths fall back to `path.resolve`, which still normalizes separators.
|
|
182
|
+
*
|
|
183
|
+
* Comparisons must canonicalize because the two sides come from different
|
|
184
|
+
* worlds: the stock resolver hands back realpath-resolved filenames, while a
|
|
185
|
+
* registered runtime root is whatever the caller configured (a cache under a
|
|
186
|
+
* junctioned `F:\Programs\…`, a home directory symlinked onto another volume,
|
|
187
|
+
* macOS `/tmp` → `/private/tmp`).
|
|
188
|
+
*/
|
|
189
|
+
function canonicalPath(target: string): string {
|
|
190
|
+
const cached = canonicalPaths.get(target);
|
|
191
|
+
if (cached !== undefined) return cached;
|
|
192
|
+
let canonical: string;
|
|
193
|
+
try {
|
|
194
|
+
canonical = fs.realpathSync.native(target);
|
|
195
|
+
} catch {
|
|
196
|
+
canonical = path.resolve(target);
|
|
197
|
+
}
|
|
198
|
+
if (process.platform === "win32") canonical = canonical.toLowerCase();
|
|
199
|
+
canonicalPaths.set(target, canonical);
|
|
200
|
+
return canonical;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Path of `candidate` relative to `root` when `candidate` is inside (or is)
|
|
205
|
+
* `root`, else `null`. Both sides are canonicalized, so a runtime cache
|
|
206
|
+
* reached through a link still matches the filenames the resolver reports.
|
|
207
|
+
*/
|
|
208
|
+
function relativeWithin(root: string, candidate: string): string | null {
|
|
209
|
+
const relative = path.relative(canonicalPath(root), canonicalPath(candidate));
|
|
210
|
+
if (relative !== "" && (relative.startsWith("..") || path.isAbsolute(relative))) return null;
|
|
211
|
+
return relative;
|
|
178
212
|
}
|
|
179
213
|
|
|
180
214
|
function parentFilename(parent: unknown): string | null {
|
|
@@ -243,11 +277,12 @@ export function installRuntimeModuleResolver({ runtimeNodeModules, stubs = {} }:
|
|
|
243
277
|
if (bare) {
|
|
244
278
|
const parentFile = parentFilename(parent);
|
|
245
279
|
for (const registration of resolverRegistry()) {
|
|
246
|
-
const parentInRuntime =
|
|
280
|
+
const parentInRuntime =
|
|
281
|
+
parentFile !== null && relativeWithin(registration.runtimeNodeModules, parentFile) !== null;
|
|
247
282
|
if (parentInRuntime) {
|
|
248
283
|
const stub = registration.stubs[request];
|
|
249
284
|
if (stub) return stub;
|
|
250
|
-
if (!stockResolved ||
|
|
285
|
+
if (!stockResolved || relativeWithin(registration.runtimeNodeModules, stockResolved) === null) {
|
|
251
286
|
const fallback = resolveRuntimeModule(registration.runtimeNodeModules, request);
|
|
252
287
|
if (fallback) return fallback;
|
|
253
288
|
}
|
|
@@ -260,8 +295,9 @@ export function installRuntimeModuleResolver({ runtimeNodeModules, stubs = {} }:
|
|
|
260
295
|
// it with the top-level instance would cross major versions.
|
|
261
296
|
const { packageName } = splitBareSpecifier(request);
|
|
262
297
|
const pkgDir = path.join(registration.runtimeNodeModules, ...packageName.split("/"));
|
|
263
|
-
|
|
264
|
-
if (
|
|
298
|
+
const relative = relativeWithin(pkgDir, stockResolved);
|
|
299
|
+
if (relative === null || relative === "") continue;
|
|
300
|
+
if (relative.split(path.sep).includes("node_modules")) continue;
|
|
265
301
|
const expected = resolveRuntimeModule(registration.runtimeNodeModules, request);
|
|
266
302
|
if (expected) return expected;
|
|
267
303
|
} else {
|