@stim-cli/core 1.20.1 → 1.21.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/dist/{core-CzCQ9yWY.mjs → core-DisO3hs1.mjs} +257 -60
- package/dist/{index-DNBFsz-Q.d.mts → index-_wkluvP_.d.mts} +341 -90
- package/dist/index.d.mts +70 -5
- package/dist/index.mjs +3 -3
- package/dist/keyed-delta-BBoOeR2J.mjs +289 -0
- package/dist/noise-DYIbymAJ.d.mts +100 -0
- package/dist/noise-session.d.mts +164 -0
- package/dist/noise-session.mjs +402 -0
- package/dist/noise.d.mts +2 -0
- package/dist/noise.mjs +393 -0
- package/dist/{ownership-claim-Xxxr9MPp.d.mts → ownership-claim-Bf8Lq6AY.d.mts} +2 -1
- package/dist/{ownership-claim-O31Y5iIb.mjs → ownership-claim-DHUs_Dir.mjs} +1 -1
- package/dist/ownership-claim.d.mts +2 -2
- package/dist/ownership-claim.mjs +2 -2
- package/dist/phone-protocol.d.mts +85 -16
- package/dist/phone-protocol.mjs +2 -1
- package/dist/{protocol-Bp-bcZIR.d.mts → protocol-D8R_3dkO.d.mts} +307 -65
- package/dist/protocol.d.mts +2 -2
- package/dist/protocol.mjs +50 -10
- package/dist/receive-protocol.d.mts +1 -1
- package/dist/receive-protocol.mjs +18062 -12744
- package/dist/state.d.mts +2 -2
- package/dist/state.mjs +1140 -334
- package/dist/toolchain.d.mts +64 -0
- package/dist/toolchain.mjs +326 -0
- package/dist/workspace-link.d.mts +16 -0
- package/dist/workspace-link.mjs +50 -0
- package/package.json +21 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { C as quotedPath, S as tryAcquireClaim, _ as releaseClaim, r as ClaimRefusedError, x as sleepSync } from "./ownership-claim-DHUs_Dir.mjs";
|
|
2
2
|
import fs, { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path, { basename, dirname, join } from "node:path";
|
|
5
|
-
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
5
|
+
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
6
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
6
7
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
7
9
|
import { spawnSync } from "node:child_process";
|
|
8
10
|
import { connect, createServer, isIP } from "node:net";
|
|
9
11
|
//#region dir-lock.ts
|
|
@@ -11,9 +13,6 @@ const DEFAULT_LOCK_WAIT_MS = 12e3;
|
|
|
11
13
|
const DEFAULT_LOCK_POLL_MS = 25;
|
|
12
14
|
const lockDepths = /* @__PURE__ */ new Map();
|
|
13
15
|
const OWNER_MARKER = /^\.stim-claim-[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/;
|
|
14
|
-
function sleepSync(ms) {
|
|
15
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
16
|
-
}
|
|
17
16
|
/**
|
|
18
17
|
* Whether Windows refused a call on the lock directory because another process is removing it at that
|
|
19
18
|
* moment. NTFS answers ERROR_ACCESS_DENIED, which Node reports as EPERM, until that removal completes,
|
|
@@ -90,57 +89,42 @@ function takeLockDirectory(lockPath, claim) {
|
|
|
90
89
|
}
|
|
91
90
|
return marker;
|
|
92
91
|
}
|
|
93
|
-
function
|
|
92
|
+
function prepareDirLock(lockPath, ensureParent) {
|
|
94
93
|
ensureParent?.();
|
|
95
94
|
statSync(dirname(lockPath));
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
}
|
|
96
|
+
function attemptDirLock(lockPath, pending) {
|
|
97
|
+
if (!pending.claim) {
|
|
98
|
+
const attempt = tryAcquireClaim({
|
|
99
|
+
root: `${lockPath}.claims`,
|
|
100
|
+
mode: "exclusive",
|
|
101
|
+
label: "directory lock"
|
|
102
|
+
});
|
|
103
|
+
pending.claim = attempt.acquired;
|
|
104
|
+
if (attempt.pending) releaseClaim(attempt.pending);
|
|
105
|
+
}
|
|
106
|
+
if (!pending.claim) return null;
|
|
107
|
+
const marker = takeLockDirectory(lockPath, pending.claim);
|
|
108
|
+
return marker ? {
|
|
109
|
+
claim: pending.claim,
|
|
110
|
+
marker
|
|
111
|
+
} : null;
|
|
112
|
+
}
|
|
113
|
+
function dirLockTimeout(lockPath, claim) {
|
|
114
|
+
const error = /* @__PURE__ */ new Error(`Timed out waiting for the lock at ${lockPath}. ` + (claim ? `No current Stim holds it: the directory is empty, or an older Stim version holds or left it. If no older Stim is running, remove it and run the command again:\n rm -rf ${quotedPath(lockPath)}` : "Another Stim process is holding it; wait for that command to finish and run the command again."));
|
|
115
|
+
error.code = "STIM_LOCK_TIMEOUT";
|
|
116
|
+
error.lockPath = lockPath;
|
|
117
|
+
return error;
|
|
118
|
+
}
|
|
119
|
+
function nestedDirLock(lockPath, fn) {
|
|
120
|
+
lockDepths.set(lockPath, (lockDepths.get(lockPath) ?? 0) + 1);
|
|
98
121
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
root: `${lockPath}.claims`,
|
|
103
|
-
mode: "exclusive",
|
|
104
|
-
label: "directory lock"
|
|
105
|
-
});
|
|
106
|
-
claim = attempt.acquired;
|
|
107
|
-
if (attempt.pending) releaseClaim(attempt.pending);
|
|
108
|
-
}
|
|
109
|
-
if (claim) {
|
|
110
|
-
const marker = takeLockDirectory(lockPath, claim);
|
|
111
|
-
if (marker) return {
|
|
112
|
-
claim,
|
|
113
|
-
marker
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
if (Date.now() >= deadline) {
|
|
117
|
-
const error = /* @__PURE__ */ new Error(`Timed out waiting for the lock at ${lockPath}. ` + (claim ? `No current Stim holds it: the directory is empty, or an older Stim version holds or left it. If no older Stim is running, remove it and run the command again:\n rm -rf ${quotedPath(lockPath)}` : "Another Stim process is holding it; wait for that command to finish and run the command again."));
|
|
118
|
-
error.code = "STIM_LOCK_TIMEOUT";
|
|
119
|
-
error.lockPath = lockPath;
|
|
120
|
-
throw error;
|
|
121
|
-
}
|
|
122
|
-
sleepSync(pollMs);
|
|
123
|
-
}
|
|
124
|
-
} catch (error) {
|
|
125
|
-
releaseClaim(claim);
|
|
126
|
-
throw error;
|
|
122
|
+
return fn();
|
|
123
|
+
} finally {
|
|
124
|
+
lockDepths.set(lockPath, (lockDepths.get(lockPath) ?? 1) - 1);
|
|
127
125
|
}
|
|
128
126
|
}
|
|
129
|
-
function
|
|
130
|
-
const depth = lockDepths.get(lockPath) || 0;
|
|
131
|
-
if (depth > 0) {
|
|
132
|
-
lockDepths.set(lockPath, depth + 1);
|
|
133
|
-
try {
|
|
134
|
-
return fn();
|
|
135
|
-
} finally {
|
|
136
|
-
lockDepths.set(lockPath, (lockDepths.get(lockPath) ?? 1) - 1);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
const { claim, marker } = acquireDirLock(lockPath, {
|
|
140
|
-
waitMs,
|
|
141
|
-
pollMs,
|
|
142
|
-
ensureParent
|
|
143
|
-
});
|
|
127
|
+
function holdDirLock(lockPath, { claim, marker }, fn) {
|
|
144
128
|
lockDepths.set(lockPath, 1);
|
|
145
129
|
try {
|
|
146
130
|
return fn();
|
|
@@ -152,6 +136,52 @@ function withDirLock(lockPath, fn, { waitMs = DEFAULT_LOCK_WAIT_MS, pollMs = DEF
|
|
|
152
136
|
releaseClaim(claim);
|
|
153
137
|
}
|
|
154
138
|
}
|
|
139
|
+
function withDirLock(lockPath, fn, { waitMs = DEFAULT_LOCK_WAIT_MS, pollMs = DEFAULT_LOCK_POLL_MS, ensureParent } = {}) {
|
|
140
|
+
if ((lockDepths.get(lockPath) || 0) > 0) return nestedDirLock(lockPath, fn);
|
|
141
|
+
prepareDirLock(lockPath, ensureParent);
|
|
142
|
+
const deadline = Date.now() + waitMs;
|
|
143
|
+
const pending = {};
|
|
144
|
+
let held;
|
|
145
|
+
try {
|
|
146
|
+
while (!(held = attemptDirLock(lockPath, pending))) {
|
|
147
|
+
if (Date.now() >= deadline) throw dirLockTimeout(lockPath, pending.claim);
|
|
148
|
+
sleepSync(pollMs);
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
releaseClaim(pending.claim);
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
return holdDirLock(lockPath, held, fn);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* `withDirLock` that waits for a contended lock on a timer instead of blocking the thread. `fn` still runs
|
|
158
|
+
* synchronously: nothing awaits between taking the lock, running `fn` and releasing the lock, so the held
|
|
159
|
+
* transaction and reentrant nesting behave exactly as with `withDirLock`.
|
|
160
|
+
* An optional signal cancels acquisition; it cannot interrupt the synchronous transaction.
|
|
161
|
+
*/
|
|
162
|
+
async function withDirLockAsync(lockPath, fn, { waitMs = DEFAULT_LOCK_WAIT_MS, pollMs = DEFAULT_LOCK_POLL_MS, ensureParent, signal } = {}) {
|
|
163
|
+
signal?.throwIfAborted();
|
|
164
|
+
if ((lockDepths.get(lockPath) || 0) > 0) return nestedDirLock(lockPath, fn);
|
|
165
|
+
prepareDirLock(lockPath, ensureParent);
|
|
166
|
+
const deadline = Date.now() + waitMs;
|
|
167
|
+
const pending = {};
|
|
168
|
+
let held;
|
|
169
|
+
try {
|
|
170
|
+
while (!(held = attemptDirLock(lockPath, pending))) {
|
|
171
|
+
if (Date.now() >= deadline) throw dirLockTimeout(lockPath, pending.claim);
|
|
172
|
+
releaseClaim(pending.claim);
|
|
173
|
+
pending.claim = void 0;
|
|
174
|
+
await setTimeout$1(pollMs, void 0, { signal });
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
releaseClaim(pending.claim);
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
return holdDirLock(lockPath, held, () => {
|
|
181
|
+
signal?.throwIfAborted();
|
|
182
|
+
return fn();
|
|
183
|
+
});
|
|
184
|
+
}
|
|
155
185
|
//#endregion
|
|
156
186
|
//#region state/json-file.ts
|
|
157
187
|
const readScope = new AsyncLocalStorage();
|
|
@@ -195,12 +225,94 @@ function readJsonObject(path) {
|
|
|
195
225
|
}
|
|
196
226
|
}
|
|
197
227
|
//#endregion
|
|
228
|
+
//#region context.ts
|
|
229
|
+
const ambient = new AsyncLocalStorage();
|
|
230
|
+
/**
|
|
231
|
+
* Runs `fn` with `context` reachable through `ambientContext()`, including after an `await` inside it. A temporary
|
|
232
|
+
* bridge for call sites that are not passed a context yet: `configDir()`, `runId()` and the exec wrapper's base
|
|
233
|
+
* environment.
|
|
234
|
+
*/
|
|
235
|
+
function withAmbientContext(context, fn) {
|
|
236
|
+
return ambient.run(context, fn);
|
|
237
|
+
}
|
|
238
|
+
/** The context set by `withAmbientContext`, for call sites that are not passed one yet. */
|
|
239
|
+
function ambientContext() {
|
|
240
|
+
return ambient.getStore();
|
|
241
|
+
}
|
|
242
|
+
/** Writes `text` to the ambient context's output, or to the process stream outside one. */
|
|
243
|
+
function ambientWrite(stream, text) {
|
|
244
|
+
const context = ambient.getStore();
|
|
245
|
+
if (context) context.output(stream, text);
|
|
246
|
+
else process[stream].write(text);
|
|
247
|
+
}
|
|
248
|
+
/** The environment of the ambient context, or `process.env` outside one. */
|
|
249
|
+
function ambientEnv() {
|
|
250
|
+
return ambient.getStore()?.env ?? process.env;
|
|
251
|
+
}
|
|
252
|
+
//#endregion
|
|
198
253
|
//#region macos-resources.ts
|
|
199
254
|
/** Whether a resource destination is a contained, non-empty path under Contents/Resources. */
|
|
200
255
|
function validMacosResourceDestination(value) {
|
|
201
256
|
return typeof value === "string" && value.length > 0 && value.length <= 1024 && !value.includes("\0") && !value.includes("\\") && !/^[A-Za-z]:/.test(value) && value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
|
|
202
257
|
}
|
|
203
258
|
//#endregion
|
|
259
|
+
//#region systemd-unit.ts
|
|
260
|
+
const ESCAPES = {
|
|
261
|
+
"\\": "\\\\",
|
|
262
|
+
"\"": "\\\"",
|
|
263
|
+
"\n": "\\n",
|
|
264
|
+
" ": "\\t",
|
|
265
|
+
"\r": "\\r"
|
|
266
|
+
};
|
|
267
|
+
const UNESCAPES = {
|
|
268
|
+
n: "\n",
|
|
269
|
+
t: " ",
|
|
270
|
+
r: "\r"
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* One double-quoted word as systemd.syntax(7) reads it: C escapes inside the quotes, `%` doubled because unit files
|
|
274
|
+
* expand specifiers, and in `ExecStart=` `$` doubled because systemd expands variables in command lines.
|
|
275
|
+
*/
|
|
276
|
+
function quoteUnitWord(value, exec) {
|
|
277
|
+
return `"${value.replace(/[\\"\n\t\r%$]/g, (char) => char === "%" ? "%%" : char === "$" ? exec ? "$$" : "$" : ESCAPES[char] ?? char)}"`;
|
|
278
|
+
}
|
|
279
|
+
/** The words of a unit value `quoteUnitWord` wrote, or null when the value is not in that form. */
|
|
280
|
+
function unitWords(line, exec) {
|
|
281
|
+
const out = [];
|
|
282
|
+
const pattern = /\s*(?:"((?:[^"\\]|\\.)*)"|([^\s"]+))/y;
|
|
283
|
+
let index = 0;
|
|
284
|
+
while (index < line.length && line.slice(index).trim()) {
|
|
285
|
+
pattern.lastIndex = index;
|
|
286
|
+
const match = pattern.exec(line);
|
|
287
|
+
if (!match) return null;
|
|
288
|
+
index = pattern.lastIndex;
|
|
289
|
+
const raw = match[1] ?? match[2] ?? "";
|
|
290
|
+
out.push(raw.replace(/\\(.)|%%|\$\$/g, (whole, escaped) => escaped !== void 0 ? UNESCAPES[escaped] ?? escaped : whole === "%%" ? "%" : exec ? "$" : whole));
|
|
291
|
+
}
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
/** The `key=value` lines of a unit file with the section each is in, skipping comments and blank lines. */
|
|
295
|
+
function unitLines(text) {
|
|
296
|
+
let section = "";
|
|
297
|
+
const out = [];
|
|
298
|
+
for (const line of text.split("\n")) {
|
|
299
|
+
const trimmed = line.trim();
|
|
300
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith(";")) continue;
|
|
301
|
+
const header = /^\[(.+)\]$/.exec(trimmed)?.[1];
|
|
302
|
+
if (header) {
|
|
303
|
+
section = header;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const equals = trimmed.indexOf("=");
|
|
307
|
+
if (equals > 0) out.push({
|
|
308
|
+
section,
|
|
309
|
+
key: trimmed.slice(0, equals).trim(),
|
|
310
|
+
value: trimmed.slice(equals + 1).trim()
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
return out;
|
|
314
|
+
}
|
|
315
|
+
//#endregion
|
|
204
316
|
//#region artifact-store.ts
|
|
205
317
|
function artifactIn(dir) {
|
|
206
318
|
if (!existsSync(dir)) return null;
|
|
@@ -298,6 +410,68 @@ function storeArtifact(dest, buildPath, { runFile, overwrite = false, writeMetad
|
|
|
298
410
|
return artifactIn(dest);
|
|
299
411
|
}
|
|
300
412
|
//#endregion
|
|
413
|
+
//#region artifact-store-async.ts
|
|
414
|
+
/** Awaits copying before atomic publication; cancellation before publication preserves the previous entry. */
|
|
415
|
+
async function storeArtifactAsync(dest, buildPath, { runFile, overwrite = false, writeMetadata, onRenameError, signal }) {
|
|
416
|
+
signal?.throwIfAborted();
|
|
417
|
+
const existing = artifactIn(dest);
|
|
418
|
+
if (existing && !overwrite) return existing;
|
|
419
|
+
const token = randomUUID();
|
|
420
|
+
const staging = `${dest}.staging-${token}`;
|
|
421
|
+
const replaced = `${dest}.replaced-${token}`;
|
|
422
|
+
const target = join(staging, basename(buildPath));
|
|
423
|
+
await mkdir(staging, { recursive: true });
|
|
424
|
+
try {
|
|
425
|
+
signal?.throwIfAborted();
|
|
426
|
+
try {
|
|
427
|
+
await runFile("cp", [
|
|
428
|
+
"-c",
|
|
429
|
+
"-R",
|
|
430
|
+
buildPath,
|
|
431
|
+
target
|
|
432
|
+
]);
|
|
433
|
+
} catch {
|
|
434
|
+
signal?.throwIfAborted();
|
|
435
|
+
await rm(target, {
|
|
436
|
+
recursive: true,
|
|
437
|
+
force: true
|
|
438
|
+
});
|
|
439
|
+
signal?.throwIfAborted();
|
|
440
|
+
await runFile("cp", [
|
|
441
|
+
"-R",
|
|
442
|
+
buildPath,
|
|
443
|
+
target
|
|
444
|
+
]);
|
|
445
|
+
}
|
|
446
|
+
signal?.throwIfAborted();
|
|
447
|
+
await writeMetadata?.(staging);
|
|
448
|
+
signal?.throwIfAborted();
|
|
449
|
+
const published = artifactIn(dest);
|
|
450
|
+
if (published && !overwrite) return published;
|
|
451
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
452
|
+
if (existsSync(dest)) renameSync(dest, replaced);
|
|
453
|
+
try {
|
|
454
|
+
renameSync(staging, dest);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
try {
|
|
457
|
+
renameSync(replaced, dest);
|
|
458
|
+
} catch {}
|
|
459
|
+
if (!onRenameError) throw error;
|
|
460
|
+
await onRenameError(staging);
|
|
461
|
+
}
|
|
462
|
+
return artifactIn(dest);
|
|
463
|
+
} finally {
|
|
464
|
+
await rm(staging, {
|
|
465
|
+
recursive: true,
|
|
466
|
+
force: true
|
|
467
|
+
});
|
|
468
|
+
await rm(replaced, {
|
|
469
|
+
recursive: true,
|
|
470
|
+
force: true
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
//#endregion
|
|
301
475
|
//#region compiled-helper.ts
|
|
302
476
|
const RECENT_MS = 6e5;
|
|
303
477
|
const pruned = /* @__PURE__ */ new Set();
|
|
@@ -343,9 +517,9 @@ function pruneCompiledHelpers(dir, currentName, name, runningPaths = runningExec
|
|
|
343
517
|
* Returns `<dir>/<name>-<hash>`, compiling it with `compile` first when it is missing. The hash covers `version` and
|
|
344
518
|
* each input's name and bytes, so a changed source or compiler builds a new helper beside the old one. `compile`
|
|
345
519
|
* writes to a private temporary path that is renamed into place, so concurrent builders never expose a partial file.
|
|
346
|
-
* Pruning is best effort and keeps this helper, the newest previous build, helpers modified in the last ten minutes and running helpers. It runs after a build and on the first call per process.
|
|
520
|
+
* Pruning is best effort and keeps this helper, the newest previous build, helpers modified in the last ten minutes and running helpers. It runs after a build and on the first call per process unless `prune` is false.
|
|
347
521
|
*/
|
|
348
|
-
async function compiledHelper({ dir, name, inputs, version, compile }) {
|
|
522
|
+
async function compiledHelper({ dir, name, inputs, version, compile, prune = true }) {
|
|
349
523
|
const hash = createHash("sha256").update(version);
|
|
350
524
|
for (const input of inputs) hash.update(basename(input)).update(readFileSync(input));
|
|
351
525
|
const helper = join(dir, `${name}-${hash.digest("hex").slice(0, 16)}`);
|
|
@@ -363,8 +537,10 @@ async function compiledHelper({ dir, name, inputs, version, compile }) {
|
|
|
363
537
|
rmSync(output, { force: true });
|
|
364
538
|
}
|
|
365
539
|
}
|
|
366
|
-
if (
|
|
367
|
-
|
|
540
|
+
if (prune) {
|
|
541
|
+
if (built || !pruned.has(`${dir}/${name}`)) pruneCompiledHelpers(dir, basename(helper), name);
|
|
542
|
+
pruned.add(`${dir}/${name}`);
|
|
543
|
+
}
|
|
368
544
|
return helper;
|
|
369
545
|
}
|
|
370
546
|
//#endregion
|
|
@@ -462,7 +638,7 @@ function createMetroBridge({ gatewayPort, peer, secret }) {
|
|
|
462
638
|
//#region index.ts
|
|
463
639
|
const warnedRelative = /* @__PURE__ */ new Set();
|
|
464
640
|
function absoluteEnvPath(name) {
|
|
465
|
-
const value =
|
|
641
|
+
const value = ambientEnv()[name];
|
|
466
642
|
if (!value) return null;
|
|
467
643
|
if (path.isAbsolute(value)) return value;
|
|
468
644
|
if (!warnedRelative.has(name)) {
|
|
@@ -472,7 +648,7 @@ function absoluteEnvPath(name) {
|
|
|
472
648
|
return null;
|
|
473
649
|
}
|
|
474
650
|
function configDir() {
|
|
475
|
-
return absoluteEnvPath("STIM_HOME") || path.join(os.homedir(), ".stim");
|
|
651
|
+
return ambientContext()?.home ?? (absoluteEnvPath("STIM_HOME") || path.join(os.homedir(), ".stim"));
|
|
476
652
|
}
|
|
477
653
|
/** Creates `dir` and its missing parents owner-only, and narrows an existing `dir` that grants group or other access when the filesystem and owner allow it. */
|
|
478
654
|
function ensurePrivateDir(dir) {
|
|
@@ -629,8 +805,29 @@ function updateCacheManifest(file, mutate) {
|
|
|
629
805
|
return next;
|
|
630
806
|
}, { ensureParent: () => fs.mkdirSync(parent, { recursive: true }) });
|
|
631
807
|
}
|
|
808
|
+
function realOrResolved(dir) {
|
|
809
|
+
try {
|
|
810
|
+
return fs.realpathSync(dir);
|
|
811
|
+
} catch {
|
|
812
|
+
return path.resolve(dir);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
function cacheDirRefusal(dir) {
|
|
816
|
+
if (!path.isAbsolute(dir)) return `${dir} is not an absolute path`;
|
|
817
|
+
const target = realOrResolved(dir);
|
|
818
|
+
if (path.dirname(target) === target) return `${dir} is a filesystem root`;
|
|
819
|
+
const guarded = [
|
|
820
|
+
os.homedir(),
|
|
821
|
+
os.tmpdir(),
|
|
822
|
+
configDir()
|
|
823
|
+
].find((held) => {
|
|
824
|
+
const inside = path.relative(target, realOrResolved(held));
|
|
825
|
+
return inside !== ".." && !inside.startsWith(`..${path.sep}`) && !path.isAbsolute(inside);
|
|
826
|
+
});
|
|
827
|
+
return guarded ? `${dir} is or contains ${guarded}` : null;
|
|
828
|
+
}
|
|
632
829
|
function registerCache({ dir, name, prune, note, entriesDepth, layout, replaces = [] }) {
|
|
633
|
-
if (
|
|
830
|
+
if (cacheDirRefusal(dir)) return;
|
|
634
831
|
try {
|
|
635
832
|
updateCacheManifest(path.join(configDir(), "caches.json"), (caches) => {
|
|
636
833
|
const others = caches.filter((cache) => cache.dir !== dir && !replaces.some((match) => matchesCache(cache, match)));
|
|
@@ -639,7 +836,7 @@ function registerCache({ dir, name, prune, note, entriesDepth, layout, replaces
|
|
|
639
836
|
name,
|
|
640
837
|
prune,
|
|
641
838
|
note,
|
|
642
|
-
registeredBy: process.cwd()
|
|
839
|
+
registeredBy: ambientContext()?.cwd ?? process.cwd()
|
|
643
840
|
};
|
|
644
841
|
if (entriesDepth) record.entriesDepth = entriesDepth;
|
|
645
842
|
if (layout) record.layout = layout;
|
|
@@ -657,4 +854,4 @@ function matchesCache(cache, match) {
|
|
|
657
854
|
return match.layout === void 0 || cache.layout === match.layout;
|
|
658
855
|
}
|
|
659
856
|
//#endregion
|
|
660
|
-
export {
|
|
857
|
+
export { artifactIn as A, withAmbientContext as B, workspaceName as C, createMetroGateway as D, createMetroBridge as E, unitWords as F, withStateReadCache as G, readJsonFile as H, validMacosResourceDestination as I, withDirLock as K, ambientContext as L, storeArtifact as M, quoteUnitWord as N, compiledHelper as O, unitLines as P, ambientEnv as R, workspaceLogDir as S, workspaceStateDir as T, readJsonObject as U, isJsonObject as V, readStateFile as W, rotatedLogPath as _, buildCacheRoot as a, updateCacheManifest as b, cacheNameSegment as c, ensurePrivateDir as d, hostSimulatorArch as f, rotateLog as g, registerCache as h, buildCacheKey as i, resolveArtifact as j, storeArtifactAsync as k, cachePathSetting as l, readCacheManifest as m, METRO_NAMED_CACHE_LAYOUT as n, cacheDirRefusal as o, metroCacheRoot as p, withDirLockAsync as q, STORE_ROOT_TAG as r, cacheManifestLockPath as s, LOG_ROTATE_BYTES as t, configDir as u, sharedStoreRoot as v, workspaceSlug as w, workspaceId as x, tagSharedStore as y, ambientWrite as z };
|