@nimbus-sh/core 0.4.0 → 0.6.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/README.md +35 -7
- package/dist/_shared/cache-stats.d.ts +23 -1
- package/dist/_shared/cache-stats.d.ts.map +1 -1
- package/dist/_shared/cache-stats.js +13 -1
- package/dist/_shared/error-text.d.ts +15 -0
- package/dist/_shared/error-text.d.ts.map +1 -0
- package/dist/_shared/error-text.js +21 -0
- package/dist/_shared/exports-resolver.d.ts +19 -7
- package/dist/_shared/exports-resolver.d.ts.map +1 -1
- package/dist/_shared/exports-resolver.js +1 -1
- package/dist/_shared/retry.d.ts.map +1 -1
- package/dist/_shared/retry.js +4 -2
- package/dist/constants.d.ts +5 -11
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +23 -49
- package/dist/runtime/assets-loader.js +1 -1
- package/dist/runtime/esbuild-service.d.ts +4 -3
- package/dist/runtime/esbuild-service.d.ts.map +1 -1
- package/dist/runtime/esbuild-service.js +9 -4
- package/dist/runtime/installed-runtimes.d.ts +4 -3
- package/dist/runtime/installed-runtimes.d.ts.map +1 -1
- package/dist/runtime/long-running-handle.d.ts +3 -3
- package/dist/runtime/long-running-handle.d.ts.map +1 -1
- package/dist/runtime/os-contracts.d.ts +7 -5
- package/dist/runtime/os-contracts.d.ts.map +1 -1
- package/dist/runtime/port-registry.d.ts +28 -0
- package/dist/runtime/port-registry.d.ts.map +1 -1
- package/dist/runtime/port-registry.js +79 -9
- package/dist/runtime/process-logs.d.ts +9 -1
- package/dist/runtime/process-logs.d.ts.map +1 -1
- package/dist/runtime/project-detect.d.ts +6 -1
- package/dist/runtime/project-detect.d.ts.map +1 -1
- package/dist/runtime/require-resolver.d.ts.map +1 -1
- package/dist/runtime/require-resolver.js +3 -4
- package/dist/runtime/runtime-registry.d.ts +4 -3
- package/dist/runtime/runtime-registry.d.ts.map +1 -1
- package/dist/runtime/runtime-registry.js +2 -1
- package/dist/runtime/sqlite-runtime-fs-bridge.js +1 -5
- package/dist/runtime/virtual-socket-kernel.d.ts.map +1 -1
- package/dist/runtime/virtual-socket-kernel.generated.d.ts +2 -2
- package/dist/runtime/virtual-socket-kernel.generated.d.ts.map +1 -1
- package/dist/runtime/virtual-socket-kernel.generated.js +3 -3
- package/dist/runtime/virtual-socket-kernel.js +10 -5
- package/dist/runtime/wasm-runner.d.ts.map +1 -1
- package/dist/runtime/wasm-runner.js +22 -14
- package/dist/shell/features.d.ts.map +1 -1
- package/dist/shell/features.js +5 -4
- package/dist/shell/unix-commands.d.ts +14 -1
- package/dist/shell/unix-commands.d.ts.map +1 -1
- package/dist/shell/unix-commands.js +115 -69
- package/dist/substrate/lifo/commands/net/ip.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/system/ps.d.ts +2 -1
- package/dist/substrate/lifo/commands/system/ps.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/ServiceManager.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/ServiceManager.js +0 -2
- package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts +22 -11
- package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/network/NetworkStack.js +0 -2
- package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts +2 -1
- package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.js +3 -4
- package/dist/substrate/lifo/kernel/vfs/VFS.d.ts +17 -0
- package/dist/substrate/lifo/kernel/vfs/VFS.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/vfs/VFS.js +33 -0
- package/dist/substrate/lifo/node-compat/esbuild.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/esbuild.js +2 -4
- package/dist/substrate/lifo/node-compat/index.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/index.js +3 -2
- package/dist/substrate/lifo/node-compat/loose-equality.d.ts +19 -0
- package/dist/substrate/lifo/node-compat/loose-equality.d.ts.map +1 -0
- package/dist/substrate/lifo/node-compat/loose-equality.js +91 -0
- package/dist/substrate/lifo/node-compat/process.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/process.js +6 -3
- package/dist/substrate/lifo/shell/Shell.d.ts +19 -0
- package/dist/substrate/lifo/shell/Shell.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/Shell.js +86 -75
- package/dist/substrate/lifo/shell/lexer.d.ts +9 -0
- package/dist/substrate/lifo/shell/lexer.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/lexer.js +40 -0
- package/dist/vfs/seed-project.d.ts.map +1 -1
- package/dist/vfs/seed-project.js +9 -3
- package/dist/vfs/sqlite-vfs.d.ts +63 -31
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
- package/dist/vfs/sqlite-vfs.js +206 -41
- package/package.json +3 -2
- package/src/_shared/cache-stats.ts +27 -1
- package/src/_shared/error-text.ts +21 -0
- package/src/_shared/exports-resolver.ts +24 -4
- package/src/_shared/retry.ts +5 -3
- package/src/constants.ts +37 -53
- package/src/runtime/assets-loader.ts +1 -1
- package/src/runtime/esbuild-service.ts +16 -11
- package/src/runtime/installed-runtimes.ts +4 -3
- package/src/runtime/long-running-handle.ts +3 -3
- package/src/runtime/os-contracts.ts +7 -5
- package/src/runtime/port-registry.ts +97 -9
- package/src/runtime/process-logs.ts +12 -3
- package/src/runtime/project-detect.ts +7 -1
- package/src/runtime/require-resolver.ts +7 -7
- package/src/runtime/runtime-registry.ts +9 -7
- package/src/runtime/sqlite-runtime-fs-bridge.ts +9 -6
- package/src/runtime/virtual-socket-kernel.generated.ts +3 -3
- package/src/runtime/virtual-socket-kernel.ts +10 -5
- package/src/runtime/wasm-runner.ts +55 -29
- package/src/shell/features.ts +5 -4
- package/src/shell/unix-commands.ts +273 -128
- package/src/substrate/lifo/commands/net/ip.ts +15 -15
- package/src/substrate/lifo/commands/system/ps.ts +2 -1
- package/src/substrate/lifo/kernel/ServiceManager.ts +0 -2
- package/src/substrate/lifo/kernel/network/NetworkStack.ts +24 -17
- package/src/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.ts +37 -12
- package/src/substrate/lifo/kernel/vfs/VFS.ts +34 -0
- package/src/substrate/lifo/node-compat/esbuild.ts +16 -8
- package/src/substrate/lifo/node-compat/index.ts +3 -2
- package/src/substrate/lifo/node-compat/loose-equality.ts +81 -0
- package/src/substrate/lifo/node-compat/process.ts +5 -2
- package/src/substrate/lifo/shell/Shell.ts +92 -70
- package/src/substrate/lifo/shell/lexer.ts +38 -0
- package/src/vfs/seed-project.ts +13 -6
- package/src/vfs/sqlite-vfs.ts +233 -77
- package/dist/_shared/install-phase.d.ts +0 -47
- package/dist/_shared/install-phase.d.ts.map +0 -1
- package/dist/_shared/install-phase.js +0 -1
- package/dist/_shared/rpc-dispose.d.ts +0 -4
- package/dist/_shared/rpc-dispose.d.ts.map +0 -1
- package/dist/_shared/rpc-dispose.js +0 -30
- package/dist/_shared/w7-frame.d.ts +0 -79
- package/dist/_shared/w7-frame.d.ts.map +0 -1
- package/dist/_shared/w7-frame.js +0 -839
- package/dist/_shared/weighted-credit-pool.d.ts +0 -61
- package/dist/_shared/weighted-credit-pool.d.ts.map +0 -1
- package/dist/_shared/weighted-credit-pool.js +0 -196
- package/dist/observability/diag-counters.d.ts +0 -236
- package/dist/observability/diag-counters.d.ts.map +0 -1
- package/dist/observability/diag-counters.js +0 -195
- package/dist/observability/heap-estimate.d.ts +0 -225
- package/dist/observability/heap-estimate.d.ts.map +0 -1
- package/dist/observability/heap-estimate.js +0 -185
- package/dist/observability/heavy-alloc-coord.d.ts +0 -93
- package/dist/observability/heavy-alloc-coord.d.ts.map +0 -1
- package/dist/observability/heavy-alloc-coord.js +0 -181
- package/dist/observability/oom-classify.d.ts +0 -138
- package/dist/observability/oom-classify.d.ts.map +0 -1
- package/dist/observability/oom-classify.js +0 -261
- package/dist/observability/oom-discriminator.d.ts +0 -176
- package/dist/observability/oom-discriminator.d.ts.map +0 -1
- package/dist/observability/oom-discriminator.js +0 -272
- package/src/_shared/install-phase.ts +0 -56
- package/src/_shared/rpc-dispose.ts +0 -31
- package/src/_shared/w7-frame.ts +0 -1012
- package/src/_shared/weighted-credit-pool.ts +0 -244
- package/src/observability/diag-counters.ts +0 -343
- package/src/observability/heap-estimate.ts +0 -331
- package/src/observability/heavy-alloc-coord.ts +0 -241
- package/src/observability/oom-classify.ts +0 -253
- package/src/observability/oom-discriminator.ts +0 -405
package/src/vfs/sqlite-vfs.ts
CHANGED
|
@@ -43,27 +43,33 @@
|
|
|
43
43
|
import { VfsEventEmitter, type VfsEventType } from './events.js';
|
|
44
44
|
import { normalizeVfsPath } from './path.js';
|
|
45
45
|
import {
|
|
46
|
-
CHUNK_SIZE,
|
|
47
46
|
LRU_MAX_ENTRIES,
|
|
48
47
|
BATCH_SIZE,
|
|
48
|
+
FS_LIST_PAGE_LIMIT,
|
|
49
|
+
} from '../constants.js';
|
|
50
|
+
import {
|
|
51
|
+
CHUNK_SIZE,
|
|
49
52
|
MAX_TX_BLOB_BYTES,
|
|
50
53
|
MAX_TX_LOGICAL_ROWS,
|
|
51
54
|
MAX_TX_SQL_EXECS,
|
|
52
55
|
MAX_GLOBAL_WRITE_STREAM_CREDIT_BYTES,
|
|
53
|
-
|
|
54
|
-
} from '
|
|
55
|
-
import {
|
|
56
|
-
import {
|
|
57
|
-
import { acquireSupervisorAllocation } from '../observability/heavy-alloc-coord.js';
|
|
56
|
+
} from '@nimbus-sh/platform/limits.js';
|
|
57
|
+
import { recordFailure } from '@nimbus-sh/platform/oom-discriminator.js';
|
|
58
|
+
import { classifyError } from '@nimbus-sh/platform/oom-classify.js';
|
|
59
|
+
import { acquireSupervisorAllocation } from '@nimbus-sh/platform/heavy-alloc-coord.js';
|
|
58
60
|
import { enc, dec } from '../_shared/bytes.js';
|
|
59
61
|
import {
|
|
60
62
|
decodeWriteBatchStream,
|
|
63
|
+
type BatchChunkEntry,
|
|
64
|
+
type BatchInodeEntry,
|
|
65
|
+
type BatchWritePayload,
|
|
66
|
+
type VfsInodeKind,
|
|
61
67
|
type W7DecodedRecord,
|
|
62
|
-
} from '
|
|
68
|
+
} from '@nimbus-sh/platform/w7-frame.js';
|
|
63
69
|
import {
|
|
64
70
|
WeightedCreditPool,
|
|
65
71
|
type CreditLease,
|
|
66
|
-
} from '
|
|
72
|
+
} from '@nimbus-sh/platform/weighted-credit-pool.js';
|
|
67
73
|
import { LEGACY_SYMLINK_REGISTRY_PATH } from './symlink-registry.js';
|
|
68
74
|
import {
|
|
69
75
|
CRED_KERNEL,
|
|
@@ -83,9 +89,37 @@ const CONTENT_ID_ALLOCATION_ATTEMPTS = 8;
|
|
|
83
89
|
// generateGitNetworkFacetCode (git-network-facet.ts) and the parallel
|
|
84
90
|
// preamble (parallel/generated-workers.ts).
|
|
85
91
|
|
|
86
|
-
|
|
92
|
+
/**
|
|
93
|
+
* The Node `process` global as far as this file probes it. workerd provides
|
|
94
|
+
* no `process`, so every member stays optional and every read stays guarded.
|
|
95
|
+
*/
|
|
96
|
+
interface NodeProcessLike {
|
|
97
|
+
env?: Record<string, string | undefined>;
|
|
98
|
+
memoryUsage?: () => { heapUsed: number };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Live view of the global object. `process` is not in the Workers lib, so its
|
|
103
|
+
* shape is declared here rather than assumed present.
|
|
104
|
+
*/
|
|
105
|
+
const nodeHost = globalThis as { process?: NodeProcessLike };
|
|
106
|
+
|
|
107
|
+
/** Single gate for the W2.5b install-pipeline diagnostics below. */
|
|
108
|
+
function installPipelineDiagEnabled(): boolean {
|
|
109
|
+
return nodeHost.process?.env?.NIMBUS_DIAG_INSTALL_PIPELINE === '1';
|
|
110
|
+
}
|
|
87
111
|
|
|
88
|
-
|
|
112
|
+
// ── Types ───────────────────────────────────────────────────────────────────
|
|
113
|
+
// VfsInodeKind and the writeBatch payload types (BatchInodeEntry,
|
|
114
|
+
// BatchChunkEntry, BatchWritePayload) live with the wire format that encodes
|
|
115
|
+
// them: @nimbus-sh/platform/w7-frame.js. Forwarded here because core@0.5.0
|
|
116
|
+
// published them from this module; platform stays the definition.
|
|
117
|
+
export type {
|
|
118
|
+
BatchChunkEntry,
|
|
119
|
+
BatchInodeEntry,
|
|
120
|
+
BatchWritePayload,
|
|
121
|
+
VfsInodeKind,
|
|
122
|
+
} from '@nimbus-sh/platform/w7-frame.js';
|
|
89
123
|
|
|
90
124
|
export interface ExclusiveMutationLease {
|
|
91
125
|
readonly root: string;
|
|
@@ -113,22 +147,6 @@ interface INode {
|
|
|
113
147
|
contentId: string | null;
|
|
114
148
|
}
|
|
115
149
|
|
|
116
|
-
/** Entry for bulk inode creation via writeBatch(). */
|
|
117
|
-
export interface BatchInodeEntry {
|
|
118
|
-
path: string;
|
|
119
|
-
parentPath: string;
|
|
120
|
-
/** Defaults to isDir ? directory : file for legacy/non-symlink producers. */
|
|
121
|
-
kind?: VfsInodeKind;
|
|
122
|
-
isDir: boolean;
|
|
123
|
-
size: number;
|
|
124
|
-
atime?: number;
|
|
125
|
-
mtime: number;
|
|
126
|
-
mode: number;
|
|
127
|
-
uid?: number;
|
|
128
|
-
gid?: number;
|
|
129
|
-
chunkCount: number;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
150
|
export interface VfsStat {
|
|
133
151
|
type: VfsInodeKind;
|
|
134
152
|
size: number;
|
|
@@ -213,21 +231,6 @@ export interface CredentialedVfs {
|
|
|
213
231
|
readonly epoch: string;
|
|
214
232
|
}
|
|
215
233
|
|
|
216
|
-
/** Entry for bulk chunk creation via writeBatch(). */
|
|
217
|
-
export interface BatchChunkEntry {
|
|
218
|
-
path: string;
|
|
219
|
-
chunkId: number;
|
|
220
|
-
data: Uint8Array;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/** Payload for writeBatch() — all inodes + chunks written in ONE transactionSync(). */
|
|
224
|
-
export interface BatchWritePayload {
|
|
225
|
-
inodes: BatchInodeEntry[];
|
|
226
|
-
chunks: BatchChunkEntry[];
|
|
227
|
-
/** Paths to delete before writing (for clean reinstall). */
|
|
228
|
-
deletePaths?: string[];
|
|
229
|
-
}
|
|
230
|
-
|
|
231
234
|
export interface WriteBatchStreamProgress {
|
|
232
235
|
/** 1-based sequence of the last durable publish group; zero means none. */
|
|
233
236
|
committedGroupSequence: number;
|
|
@@ -254,6 +257,8 @@ const CHUNK_ROWS_PER_SQL_EXEC = 33;
|
|
|
254
257
|
const CONTENT_IDS_PER_SQL_EXEC = 50;
|
|
255
258
|
const TRANSACTION_DURATION_SAMPLE_COUNT = 128;
|
|
256
259
|
const CONTENT_SCHEMA_MIGRATION = 'content_generations_v1';
|
|
260
|
+
/** Storage key of the shared scratch tree. `normalizeVfsPath` drops the slash. */
|
|
261
|
+
const TMP_ROOT = 'tmp';
|
|
257
262
|
export const VFS_APPEND_RECEIPT_LIMIT = 2048;
|
|
258
263
|
const INODE_KIND_FILE = 0;
|
|
259
264
|
const INODE_KIND_DIRECTORY = 1;
|
|
@@ -679,6 +684,9 @@ export class SqliteVFS {
|
|
|
679
684
|
private readonly activeStagingContentIds = new Set<string>();
|
|
680
685
|
/** True only while durable GC work or a known abandoned staging row exists. */
|
|
681
686
|
private maintenancePending = false;
|
|
687
|
+
// Keyset cursor for the orphan scan: each maintenance call reads one bounded
|
|
688
|
+
// page of distinct content ids past this bound, wrapping to '' at the end.
|
|
689
|
+
private orphanScanCursor = '';
|
|
682
690
|
|
|
683
691
|
// Stage 2 transaction/phase telemetry. Scalar writes stay cheap; the
|
|
684
692
|
// percentile is computed from the fixed ring only when diagnostics read it.
|
|
@@ -1317,7 +1325,7 @@ export class SqliteVFS {
|
|
|
1317
1325
|
private blobToUint8Array(blob: unknown): Uint8Array {
|
|
1318
1326
|
if (blob instanceof Uint8Array) return blob;
|
|
1319
1327
|
if (blob instanceof ArrayBuffer) return new Uint8Array(blob);
|
|
1320
|
-
if (ArrayBuffer.isView(blob)) return new Uint8Array(
|
|
1328
|
+
if (ArrayBuffer.isView(blob)) return new Uint8Array(blob.buffer, blob.byteOffset, blob.byteLength);
|
|
1321
1329
|
return new Uint8Array(0);
|
|
1322
1330
|
}
|
|
1323
1331
|
|
|
@@ -1338,6 +1346,82 @@ export class SqliteVFS {
|
|
|
1338
1346
|
return this.blobToUint8Array(rows[0].data);
|
|
1339
1347
|
}
|
|
1340
1348
|
|
|
1349
|
+
/**
|
|
1350
|
+
* Principals whose `/tmp` is private, keyed by uid, valued by the storage
|
|
1351
|
+
* root their `/tmp` resolves to.
|
|
1352
|
+
*
|
|
1353
|
+
* `/tmp` keeps its path in every view and only the bytes behind it differ,
|
|
1354
|
+
* so nothing has to be told which principal it is. The credential is the
|
|
1355
|
+
* only per-process state visible where paths are RESOLVED — there is no pid
|
|
1356
|
+
* and no cwd down here — so it is what the private view is keyed on.
|
|
1357
|
+
*
|
|
1358
|
+
* Resolution rather than a mount, because a mount diverges the two planes:
|
|
1359
|
+
* the shell writing `/tmp/a` and the file API writing `/tmp/b` landed in
|
|
1360
|
+
* different trees under the same name. `resolvePath` already takes `cred`,
|
|
1361
|
+
* and every plane goes through it.
|
|
1362
|
+
*
|
|
1363
|
+
* Registration is also what makes a principal CONFINED for `chmod`. The two
|
|
1364
|
+
* properties travel together because they answer one question: is this
|
|
1365
|
+
* principal a guest in this filesystem. Nothing here applies to an
|
|
1366
|
+
* unregistered credential, so the ordinary session user is untouched.
|
|
1367
|
+
*/
|
|
1368
|
+
private confinedTmpRoots = new Map<number, string>();
|
|
1369
|
+
|
|
1370
|
+
/**
|
|
1371
|
+
* Confine a principal. `tmpRoot` is a storage key, not a logical path — the
|
|
1372
|
+
* caller owns creating and chowning it, because a per-principal `chown` is
|
|
1373
|
+
* uid-0 only and a guest cannot provision its own.
|
|
1374
|
+
*/
|
|
1375
|
+
confinePrincipal(uid: number, tmpRoot: string): void {
|
|
1376
|
+
const root = normalizeVfsPath(tmpRoot);
|
|
1377
|
+
if (root === '') throw vfsError('EINVAL', 'a private /tmp root cannot be the filesystem root');
|
|
1378
|
+
this.confinedTmpRoots.set(uid, root);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
/** Drop a confinement. A principal's `/tmp` dies with it; its home does not. */
|
|
1382
|
+
releasePrincipal(uid: number): void {
|
|
1383
|
+
this.confinedTmpRoots.delete(uid);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
/**
|
|
1387
|
+
* Logical path -> storage key, for one credential.
|
|
1388
|
+
*
|
|
1389
|
+
* Everything under `/tmp` belongs to the caller's own private root, which is
|
|
1390
|
+
* what makes the same path mean different bytes per principal. Idempotent: a
|
|
1391
|
+
* key already inside that root is returned untouched, so the several methods
|
|
1392
|
+
* that derive a key before handing it on cannot stack the rewrite.
|
|
1393
|
+
*/
|
|
1394
|
+
private storageKey(path: string, cred: VfsCred): string {
|
|
1395
|
+
const key = normalizeVfsPath(path);
|
|
1396
|
+
const root = this.confinedTmpRoots.get(cred.uid);
|
|
1397
|
+
if (root === undefined) return key;
|
|
1398
|
+
if (key === root || key.startsWith(`${root}/`)) return key;
|
|
1399
|
+
if (key === TMP_ROOT) return root;
|
|
1400
|
+
if (!key.startsWith(`${TMP_ROOT}/`)) return key;
|
|
1401
|
+
return `${root}/${key.slice(TMP_ROOT.length + 1)}`;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
/**
|
|
1405
|
+
* Storage key -> the name this credential knows it by, or `null` when it has
|
|
1406
|
+
* none. The inverse of {@link storageKey}, for the one surface that reports
|
|
1407
|
+
* paths it was not asked about: {@link list}.
|
|
1408
|
+
*
|
|
1409
|
+
* A confined caller has no name for the shared scratch tree — `/tmp` is its
|
|
1410
|
+
* own root — nor for another principal's, so both answer `null` and are
|
|
1411
|
+
* omitted. An unconfined caller sees storage as it is, which is what the
|
|
1412
|
+
* kernel and the session user need.
|
|
1413
|
+
*/
|
|
1414
|
+
private logicalPath(key: string, cred: VfsCred): string | null {
|
|
1415
|
+
const root = this.confinedTmpRoots.get(cred.uid);
|
|
1416
|
+
if (root === undefined) return key;
|
|
1417
|
+
if (key === root) return TMP_ROOT;
|
|
1418
|
+
if (key.startsWith(`${root}/`)) return `${TMP_ROOT}/${key.slice(root.length + 1)}`;
|
|
1419
|
+
// The SHARED scratch root has no name here — this caller's `/tmp` is its
|
|
1420
|
+
// own root, which already supplied that entry. Returning it too would
|
|
1421
|
+
// enumerate one name twice, for two different directories.
|
|
1422
|
+
return key === TMP_ROOT || key.startsWith(`${TMP_ROOT}/`) ? null : key;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1341
1425
|
// ── Filesystem operations ─────────────────────────────────────────────
|
|
1342
1426
|
|
|
1343
1427
|
as(cred: VfsCred): CredentialedVfs {
|
|
@@ -1399,7 +1483,7 @@ export class SqliteVFS {
|
|
|
1399
1483
|
writeBatch: (payload) => this.writeBatch(payload, bound),
|
|
1400
1484
|
writeStream: (stream, options) => this.writeStream(stream, options, bound),
|
|
1401
1485
|
mkdirBatch: (paths) => this.mkdirBatch(paths, bound),
|
|
1402
|
-
revision: (path) => this.revision(path),
|
|
1486
|
+
revision: (path) => this.revision(path, bound),
|
|
1403
1487
|
epoch: this._epoch,
|
|
1404
1488
|
};
|
|
1405
1489
|
}
|
|
@@ -1430,7 +1514,7 @@ export class SqliteVFS {
|
|
|
1430
1514
|
followLeaf: boolean,
|
|
1431
1515
|
allowMissing: boolean,
|
|
1432
1516
|
): { path: string; inode: INode | undefined } {
|
|
1433
|
-
let current =
|
|
1517
|
+
let current = this.storageKey(path, cred);
|
|
1434
1518
|
const seen = new Set<string>();
|
|
1435
1519
|
for (let hops = 0; hops <= 40; hops++) {
|
|
1436
1520
|
const parts = current.split('/').filter(Boolean);
|
|
@@ -1449,8 +1533,13 @@ export class SqliteVFS {
|
|
|
1449
1533
|
seen.add(prefix);
|
|
1450
1534
|
const target = dec.decode(this.readInodeBytes(prefix, inode));
|
|
1451
1535
|
const suffix = parts.slice(index + 1).join('/');
|
|
1536
|
+
// An ABSOLUTE target is a logical path the same way the caller's
|
|
1537
|
+
// was, so it goes through the same rewrite: otherwise a symlink to
|
|
1538
|
+
// /tmp/y stored inside a private tree would read the shared one,
|
|
1539
|
+
// which is the one way out of the confinement. A RELATIVE target
|
|
1540
|
+
// resolves against a key that is already private, so it stays there.
|
|
1452
1541
|
const resolvedTarget = target.startsWith('/')
|
|
1453
|
-
?
|
|
1542
|
+
? this.storageKey(target, cred)
|
|
1454
1543
|
: normalizeVfsPath(`${this.parentPath(prefix)}/${target}`);
|
|
1455
1544
|
current = suffix ? normalizeVfsPath(`${resolvedTarget}/${suffix}`) : resolvedTarget;
|
|
1456
1545
|
restarted = true;
|
|
@@ -1496,6 +1585,27 @@ export class SqliteVFS {
|
|
|
1496
1585
|
if (resolved.inode?.kind !== 'directory') throw vfsError('ENOTDIR', parent);
|
|
1497
1586
|
}
|
|
1498
1587
|
|
|
1588
|
+
/**
|
|
1589
|
+
* POSIX sticky-bit restriction on a shared directory.
|
|
1590
|
+
*
|
|
1591
|
+
* Write permission on a directory is normally enough to remove or rename
|
|
1592
|
+
* anything inside it, which is why `/tmp` is `1777` and not `0777`: the
|
|
1593
|
+
* sticky bit narrows that to the entry's owner, the directory's owner, and
|
|
1594
|
+
* root. Nothing enforced it here, so a world-writable shared directory gave
|
|
1595
|
+
* every principal the ability to delete every other principal's files —
|
|
1596
|
+
* the mode said one thing and the filesystem did another.
|
|
1597
|
+
*/
|
|
1598
|
+
private checkStickyParentMutation(path: string, inode: INode, cred: VfsCred): void {
|
|
1599
|
+
if (cred.uid === 0) return;
|
|
1600
|
+
const parent = this.parentPath(normalizeVfsPath(path));
|
|
1601
|
+
if (parent === '') return;
|
|
1602
|
+
const parentInode = this.inodes.get(parent);
|
|
1603
|
+
if (!parentInode || (parentInode.mode & 0o1000) === 0) return;
|
|
1604
|
+
if (cred.uid !== parentInode.uid && cred.uid !== inode.uid) {
|
|
1605
|
+
throw vfsError('EPERM', normalizeVfsPath(path));
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1499
1609
|
/**
|
|
1500
1610
|
* Shared resolver for the boolean probes (exists/isDirectory/isFile/
|
|
1501
1611
|
* isSymlink). Resolution-structure failures — a missing or non-directory
|
|
@@ -1536,9 +1646,11 @@ export class SqliteVFS {
|
|
|
1536
1646
|
* under it changed in this DO lifetime). `revision('')` equals the
|
|
1537
1647
|
* global clock by construction (every mutation stamps all ancestors).
|
|
1538
1648
|
*/
|
|
1539
|
-
revision(path?: string): number {
|
|
1649
|
+
revision(path?: string, cred?: VfsCred): number {
|
|
1540
1650
|
if (path === undefined) return this._revision;
|
|
1541
|
-
|
|
1651
|
+
// A credentialed caller asks about its own view; a confined one asking
|
|
1652
|
+
// after /tmp/x means its own file, so the counter must be that file's.
|
|
1653
|
+
const p = cred === undefined ? normalizeVfsPath(path) : this.storageKey(path, cred);
|
|
1542
1654
|
if (p === '') return this._revision;
|
|
1543
1655
|
return this._pathRevisions.get(p) ?? 0;
|
|
1544
1656
|
}
|
|
@@ -1726,7 +1838,7 @@ export class SqliteVFS {
|
|
|
1726
1838
|
}
|
|
1727
1839
|
|
|
1728
1840
|
private mkdir(path: string, options: { recursive?: boolean; mode?: number } | undefined, cred: VfsCred): void {
|
|
1729
|
-
const normalized =
|
|
1841
|
+
const normalized = this.storageKey(path, cred);
|
|
1730
1842
|
this.assertMutationsAllowed([normalized]);
|
|
1731
1843
|
if (this.exists(normalized, cred)) return;
|
|
1732
1844
|
|
|
@@ -1832,7 +1944,7 @@ export class SqliteVFS {
|
|
|
1832
1944
|
|
|
1833
1945
|
private symlink(target: string, path: string, cred: VfsCred): void {
|
|
1834
1946
|
this.assertMutationsAllowed([path]);
|
|
1835
|
-
const normalized =
|
|
1947
|
+
const normalized = this.storageKey(path, cred);
|
|
1836
1948
|
const prior = this.checkAccess(normalized, 0, cred, { followLeaf: false, allowMissingLeaf: true });
|
|
1837
1949
|
if (prior.inode) throw vfsError('EEXIST', normalized);
|
|
1838
1950
|
this.checkParentAccess(normalized, cred);
|
|
@@ -2816,6 +2928,25 @@ export class SqliteVFS {
|
|
|
2816
2928
|
const inode = resolved.inode;
|
|
2817
2929
|
if (!inode) throw vfsError('ENOENT', path);
|
|
2818
2930
|
if (cred.uid !== 0 && cred.uid !== inode.uid) throw vfsError('EPERM', resolved.path);
|
|
2931
|
+
// A confined principal owns its own triad and nothing else. Refusing chmod
|
|
2932
|
+
// outright would be simpler and wrong: execution is gated on the x bit, so
|
|
2933
|
+
// a guest that writes build.sh and cannot chmod it cannot run it. What
|
|
2934
|
+
// must not happen is WIDENING past its own principal, so the group, other
|
|
2935
|
+
// and setuid/setgid/sticky bits keep the value they were provisioned with
|
|
2936
|
+
// while the owner triad moves freely. `u+x` works; `+x` and `777` do not.
|
|
2937
|
+
//
|
|
2938
|
+
// Refused, never clamped. Quietly narrowing a mutation to the part that
|
|
2939
|
+
// was allowed reports success for something other than what was asked, so
|
|
2940
|
+
// the refusal names the spelling that works instead.
|
|
2941
|
+
if (cred.uid !== 0 && this.confinedTmpRoots.has(cred.uid)) {
|
|
2942
|
+
const beyondOwner = (mode & 0o7777) & ~0o700;
|
|
2943
|
+
if (beyondOwner !== ((inode.mode & 0o7777) & ~0o700)) {
|
|
2944
|
+
throw vfsError(
|
|
2945
|
+
'EPERM',
|
|
2946
|
+
`${resolved.path}: mode change would grant permission outside your own principal; use u+x`,
|
|
2947
|
+
);
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2819
2950
|
this.assertMutationsAllowed([inode.path]);
|
|
2820
2951
|
const full = inodeTypeBits(inode.kind) | (mode & 0o7777);
|
|
2821
2952
|
inode.mode = full;
|
|
@@ -2886,7 +3017,7 @@ export class SqliteVFS {
|
|
|
2886
3017
|
// Before the walk, deliberately — see VfsListPage.
|
|
2887
3018
|
const epoch = this._epoch;
|
|
2888
3019
|
const rev = this._revision;
|
|
2889
|
-
const from = after === null || after === undefined ? '' :
|
|
3020
|
+
const from = after === null || after === undefined ? '' : this.storageKey(after, cred);
|
|
2890
3021
|
// this.inodes is insertion-ordered, not path-ordered, so the sort is what
|
|
2891
3022
|
// makes `after` a resume key at all. Paid once per page against a map the
|
|
2892
3023
|
// DO already holds whole.
|
|
@@ -2908,8 +3039,15 @@ export class SqliteVFS {
|
|
|
2908
3039
|
} catch {
|
|
2909
3040
|
continue;
|
|
2910
3041
|
}
|
|
3042
|
+
// Reported in the caller's OWN path space. Enumerating raw storage keys
|
|
3043
|
+
// would name a private root the caller cannot address and does not know
|
|
3044
|
+
// it has, and a caller feeding such a path back would be asking about
|
|
3045
|
+
// someone else's tree. `null` means the path has no name for this
|
|
3046
|
+
// caller, which is the same OMIT the access check above performs.
|
|
3047
|
+
const logical = this.logicalPath(path, cred);
|
|
3048
|
+
if (logical === null) continue;
|
|
2911
3049
|
entries.push({
|
|
2912
|
-
path,
|
|
3050
|
+
path: logical,
|
|
2913
3051
|
kind: inode.kind,
|
|
2914
3052
|
size: inode.size,
|
|
2915
3053
|
rev: this._pathRevisions.get(path) ?? 0,
|
|
@@ -2919,7 +3057,7 @@ export class SqliteVFS {
|
|
|
2919
3057
|
}
|
|
2920
3058
|
|
|
2921
3059
|
private readdir(path: string, cred: VfsCred): { name: string; type: VfsInodeKind }[] {
|
|
2922
|
-
const np =
|
|
3060
|
+
const np = this.storageKey(path, cred);
|
|
2923
3061
|
const resolved = np ? this.checkAccess(np, 0o4, cred) : { path: '', inode: undefined };
|
|
2924
3062
|
const inode = resolved.inode;
|
|
2925
3063
|
if (inode && inode.kind !== 'directory') throw vfsError('ENOTDIR', path);
|
|
@@ -2927,7 +3065,7 @@ export class SqliteVFS {
|
|
|
2927
3065
|
if (!kids) {
|
|
2928
3066
|
// W2.5b diagnostic: empty children-set for a directory we expected
|
|
2929
3067
|
// to be populated.
|
|
2930
|
-
if ((
|
|
3068
|
+
if (installPipelineDiagEnabled()) {
|
|
2931
3069
|
// eslint-disable-next-line no-console
|
|
2932
3070
|
console.warn(
|
|
2933
3071
|
'[sqlite-vfs/W2.5b] readdir miss path=' + np +
|
|
@@ -2949,7 +3087,7 @@ export class SqliteVFS {
|
|
|
2949
3087
|
// This distinguishes (a) "children index broken" from (b) "inodes
|
|
2950
3088
|
// map lost entries".
|
|
2951
3089
|
if (
|
|
2952
|
-
(
|
|
3090
|
+
installPipelineDiagEnabled() &&
|
|
2953
3091
|
kids.size !== results.length
|
|
2954
3092
|
) {
|
|
2955
3093
|
// eslint-disable-next-line no-console
|
|
@@ -2977,22 +3115,24 @@ export class SqliteVFS {
|
|
|
2977
3115
|
const inode = resolved.inode;
|
|
2978
3116
|
if (!inode) throw vfsError('ENOENT', path);
|
|
2979
3117
|
this.checkParentAccess(resolved.path, cred);
|
|
3118
|
+
this.checkStickyParentMutation(resolved.path, inode, cred);
|
|
2980
3119
|
if (inode.isDir) throw vfsError('EISDIR', resolved.path);
|
|
2981
3120
|
this.writeBatch({ inodes: [], chunks: [], deletePaths: [resolved.path] }, cred);
|
|
2982
3121
|
}
|
|
2983
3122
|
|
|
2984
3123
|
private rmdir(path: string, cred: VfsCred): void {
|
|
2985
3124
|
this.assertMutationsAllowed([path]);
|
|
2986
|
-
const np =
|
|
3125
|
+
const np = this.storageKey(path, cred);
|
|
2987
3126
|
const resolved = this.checkAccess(np, 0, cred, { followLeaf: false });
|
|
2988
3127
|
this.checkParentAccess(resolved.path, cred);
|
|
3128
|
+
const inode = this.inodes.get(np);
|
|
3129
|
+
if (!inode) throw vfsError('ENOENT', path);
|
|
3130
|
+
this.checkStickyParentMutation(resolved.path, inode, cred);
|
|
2989
3131
|
// Check if empty using children index (O(1) instead of O(N))
|
|
2990
3132
|
const kids = this.children.get(np);
|
|
2991
3133
|
if (kids && kids.size > 0) {
|
|
2992
3134
|
throw vfsError('ENOTEMPTY', path);
|
|
2993
3135
|
}
|
|
2994
|
-
const inode = this.inodes.get(np);
|
|
2995
|
-
if (!inode) throw vfsError('ENOENT', path);
|
|
2996
3136
|
if (!inode.isDir) throw vfsError('ENOTDIR', path);
|
|
2997
3137
|
this.writeBatch({ inodes: [], chunks: [], deletePaths: [np] }, cred);
|
|
2998
3138
|
}
|
|
@@ -3018,6 +3158,7 @@ export class SqliteVFS {
|
|
|
3018
3158
|
const resolved = this.checkAccess(path, 0, cred, { followLeaf: false });
|
|
3019
3159
|
if (!resolved.inode) throw vfsError('ENOENT', normalizeVfsPath(path));
|
|
3020
3160
|
this.checkParentAccess(resolved.path, cred);
|
|
3161
|
+
this.checkStickyParentMutation(resolved.path, resolved.inode, cred);
|
|
3021
3162
|
|
|
3022
3163
|
const removable = this.collectSubtreeInodes([resolved.path]);
|
|
3023
3164
|
// The directories the recursive walk used to enumerate are exactly the
|
|
@@ -3067,6 +3208,7 @@ export class SqliteVFS {
|
|
|
3067
3208
|
}
|
|
3068
3209
|
const inode = this.inodes.get(oldPath);
|
|
3069
3210
|
if (!inode) throw new Error("ENOENT: " + oldPath);
|
|
3211
|
+
this.checkStickyParentMutation(oldPath, inode, cred);
|
|
3070
3212
|
|
|
3071
3213
|
// W-3 (WASI filesystem WASI): if newPath already exists, unlink it first so the
|
|
3072
3214
|
// SQL UPDATE doesn't conflict on inodes.path uniqueness. POSIX rename(2)
|
|
@@ -3077,6 +3219,7 @@ export class SqliteVFS {
|
|
|
3077
3219
|
// (the latter is rare but POSIX permits it for empty dirs).
|
|
3078
3220
|
const destInode = this.inodes.get(newPath);
|
|
3079
3221
|
if (destInode) {
|
|
3222
|
+
this.checkStickyParentMutation(newPath, destInode, cred);
|
|
3080
3223
|
if (destInode.isDir) {
|
|
3081
3224
|
// POSIX semantics: rename onto a non-empty dir is an error
|
|
3082
3225
|
// (ENOTEMPTY); rename onto an empty dir is allowed. We surface
|
|
@@ -3283,14 +3426,14 @@ export class SqliteVFS {
|
|
|
3283
3426
|
// ── Batch write (npm install fast path) ───────────────────────────────
|
|
3284
3427
|
|
|
3285
3428
|
private normalizeBatchInode(entry: BatchInodeEntry, cred: VfsCred): NormalizedBatchInodeEntry {
|
|
3286
|
-
const path =
|
|
3429
|
+
const path = this.storageKey(entry.path, cred);
|
|
3287
3430
|
const prior = this.inodes.get(path);
|
|
3288
3431
|
const newUid = cred.uid === 0 ? (entry.uid ?? 1000) : cred.uid;
|
|
3289
3432
|
const newGid = cred.uid === 0 ? (entry.gid ?? 1000) : cred.gid;
|
|
3290
3433
|
return {
|
|
3291
3434
|
...entry,
|
|
3292
3435
|
path,
|
|
3293
|
-
parentPath:
|
|
3436
|
+
parentPath: this.storageKey(entry.parentPath, cred),
|
|
3294
3437
|
mode: prior
|
|
3295
3438
|
? prior.mode
|
|
3296
3439
|
: inodeKind(entry) === 'symlink'
|
|
@@ -3324,7 +3467,7 @@ export class SqliteVFS {
|
|
|
3324
3467
|
};
|
|
3325
3468
|
|
|
3326
3469
|
for (const path of payload.deletePaths ?? []) {
|
|
3327
|
-
const normalized =
|
|
3470
|
+
const normalized = this.storageKey(path, cred);
|
|
3328
3471
|
const existing = this.checkAccess(normalized, 0, cred, {
|
|
3329
3472
|
followLeaf: false,
|
|
3330
3473
|
allowMissingLeaf: true,
|
|
@@ -3337,13 +3480,13 @@ export class SqliteVFS {
|
|
|
3337
3480
|
else checkParent(entry.path);
|
|
3338
3481
|
}
|
|
3339
3482
|
for (const chunk of payload.chunks) {
|
|
3340
|
-
const path =
|
|
3483
|
+
const path = this.storageKey(chunk.path, cred);
|
|
3341
3484
|
if (!pending.has(path)) this.checkAccess(path, 0o2, cred, { followLeaf: false });
|
|
3342
3485
|
}
|
|
3343
3486
|
return {
|
|
3344
3487
|
...payload,
|
|
3345
3488
|
inodes,
|
|
3346
|
-
deletePaths: payload.deletePaths?.map(
|
|
3489
|
+
deletePaths: payload.deletePaths?.map((path) => this.storageKey(path, cred)),
|
|
3347
3490
|
};
|
|
3348
3491
|
}
|
|
3349
3492
|
|
|
@@ -4118,21 +4261,34 @@ export class SqliteVFS {
|
|
|
4118
4261
|
let stagingScanComplete = true;
|
|
4119
4262
|
|
|
4120
4263
|
if (transactions < maximum) {
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4264
|
+
// The page is selected before the orphan predicates so a scan that finds
|
|
4265
|
+
// nothing still reads only one page, not the whole covering index.
|
|
4266
|
+
const pageRows = [...this.sql.exec(
|
|
4267
|
+
`SELECT page.content_id AS content_id,
|
|
4268
|
+
(NOT EXISTS (
|
|
4269
|
+
SELECT 1 FROM content_lifecycle AS lifecycle
|
|
4270
|
+
WHERE lifecycle.content_id = page.content_id
|
|
4271
|
+
)
|
|
4272
|
+
AND ${sqlNoInodeContentReference('page.content_id')}) AS orphaned
|
|
4273
|
+
FROM (
|
|
4274
|
+
SELECT chunks.content_id
|
|
4275
|
+
FROM file_chunks AS chunks
|
|
4276
|
+
WHERE chunks.content_id > ?
|
|
4277
|
+
GROUP BY chunks.content_id
|
|
4278
|
+
ORDER BY chunks.content_id
|
|
4279
|
+
LIMIT ?
|
|
4280
|
+
) AS page
|
|
4281
|
+
ORDER BY page.content_id`,
|
|
4282
|
+
this.orphanScanCursor,
|
|
4132
4283
|
CONTENT_IDS_PER_SQL_EXEC,
|
|
4133
4284
|
)];
|
|
4134
|
-
orphanScanComplete =
|
|
4135
|
-
|
|
4285
|
+
orphanScanComplete = pageRows.length < CONTENT_IDS_PER_SQL_EXEC;
|
|
4286
|
+
this.orphanScanCursor = orphanScanComplete
|
|
4287
|
+
? ''
|
|
4288
|
+
: String(pageRows[pageRows.length - 1].content_id);
|
|
4289
|
+
const contentIds = pageRows
|
|
4290
|
+
.filter((row) => Number(row.orphaned) === 1)
|
|
4291
|
+
.map((row) => String(row.content_id));
|
|
4136
4292
|
if (contentIds.length > 0) {
|
|
4137
4293
|
const candidates = contentIds
|
|
4138
4294
|
.map((_, index) => index === 0 ? 'SELECT ? AS content_id' : 'SELECT ?')
|
|
@@ -4341,7 +4497,7 @@ export class SqliteVFS {
|
|
|
4341
4497
|
/** Best-effort process.memoryUsage().heapUsed; 0 in DO contexts. */
|
|
4342
4498
|
private _safeHeapUsed(): number {
|
|
4343
4499
|
try {
|
|
4344
|
-
const mu =
|
|
4500
|
+
const mu = nodeHost.process?.memoryUsage?.();
|
|
4345
4501
|
return Number(mu?.heapUsed) || 0;
|
|
4346
4502
|
} catch {
|
|
4347
4503
|
return 0;
|
|
@@ -4536,7 +4692,7 @@ export class SqliteVFS {
|
|
|
4536
4692
|
// `_addToChildrenIndex` uses Set.add so repeated calls are idempotent;
|
|
4537
4693
|
// gating it on `prior === undefined` was the bug. Counters remain
|
|
4538
4694
|
// gated correctly so they don't double-count.
|
|
4539
|
-
const __diag = (
|
|
4695
|
+
const __diag = installPipelineDiagEnabled();
|
|
4540
4696
|
const replacedPaths = new Set<string>();
|
|
4541
4697
|
for (const entry of plan.inodes) {
|
|
4542
4698
|
const prior = this.inodes.get(entry.path);
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* _shared/install-phase.ts — single source of truth for the npm-install
|
|
3
|
-
* state-machine phase enum.
|
|
4
|
-
*
|
|
5
|
-
* History: CLN-1 (2026-05-11) — consolidated from two diverged copies.
|
|
6
|
-
* - `npm/installer.ts:93-95` was the progress-event variant. Included
|
|
7
|
-
* 'lock-check' (a real progress event emitted while parsing
|
|
8
|
-
* package-lock.json) but lacked 'idle'.
|
|
9
|
-
* - `observability/diag-counters.ts:33-35` was the counter-state variant.
|
|
10
|
-
* Included 'idle' (the resting state when no install is in flight)
|
|
11
|
-
* but lacked 'lock-check'. The diag-counter's `setInstallPhase()`
|
|
12
|
-
* was actually called with 'idle' (installer.ts:184,201) — passing
|
|
13
|
-
* a value not declared in installer.ts's local type. The type check
|
|
14
|
-
* fell through to diag-counters' type because installer.ts imports
|
|
15
|
-
* setInstallPhase from there; the local `InstallPhase` definition
|
|
16
|
-
* in installer.ts was only used by `InstallProgress.phase`.
|
|
17
|
-
*
|
|
18
|
-
* Going forward both consumers import from this module; any new phase
|
|
19
|
-
* value added here is automatically available everywhere.
|
|
20
|
-
*
|
|
21
|
-
* Phase semantics (chronological order for a single install run):
|
|
22
|
-
*
|
|
23
|
-
* idle resting state, no install in flight
|
|
24
|
-
* ↓
|
|
25
|
-
* lock-check parsing existing package-lock.json (if present)
|
|
26
|
-
* ↓
|
|
27
|
-
* resolve walking the dep tree, fetching packuments
|
|
28
|
-
* ↓
|
|
29
|
-
* hoist hoisting peer-deps + flattening node_modules
|
|
30
|
-
* ↓
|
|
31
|
-
* diff comparing target tree against current install state
|
|
32
|
-
* ↓
|
|
33
|
-
* fetch downloading missing tarballs
|
|
34
|
-
* ↓
|
|
35
|
-
* write extracting tarballs into the VFS
|
|
36
|
-
* ↓
|
|
37
|
-
* link-bins creating bin/ symlinks
|
|
38
|
-
* ↓
|
|
39
|
-
* bundle (post-install) pre-bundling for esbuild fast path
|
|
40
|
-
* ↓
|
|
41
|
-
* done install complete; about to return to 'idle'
|
|
42
|
-
*
|
|
43
|
-
* The phase strings are surfaced via /api/_diag/install-pipeline and
|
|
44
|
-
* displayed in the install-progress UI; treat them as a public API.
|
|
45
|
-
*/
|
|
46
|
-
export type InstallPhase = 'idle' | 'lock-check' | 'resolve' | 'hoist' | 'diff' | 'fetch' | 'write' | 'link-bins' | 'bundle' | 'done';
|
|
47
|
-
//# sourceMappingURL=install-phase.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"install-phase.d.ts","sourceRoot":"","sources":["../../src/_shared/install-phase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,YAAY,GACZ,SAAS,GACT,OAAO,GACP,MAAM,GACN,OAAO,GACP,OAAO,GACP,WAAW,GACX,QAAQ,GACR,MAAM,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export declare function disposeRpcResource(value: unknown): boolean;
|
|
2
|
-
export declare function disposeRpcResources(values: Iterable<unknown>): void;
|
|
3
|
-
export declare function useRpcResource<T, R>(promise: Promise<T>, use: (value: T) => R | Promise<R>): Promise<R>;
|
|
4
|
-
//# sourceMappingURL=rpc-dispose.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-dispose.d.ts","sourceRoot":"","sources":["../../src/_shared/rpc-dispose.ts"],"names":[],"mappings":"AAEA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAY1D;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,IAAI,CAEnE;AAED,wBAAsB,cAAc,CAAC,CAAC,EAAE,CAAC,EACvC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAChC,OAAO,CAAC,CAAC,CAAC,CAOZ"}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
export function disposeRpcResource(value) {
|
|
2
|
-
if ((typeof value !== 'object' && typeof value !== 'function') || value === null)
|
|
3
|
-
return false;
|
|
4
|
-
const disposerKey = Symbol.dispose;
|
|
5
|
-
if (!disposerKey)
|
|
6
|
-
return false;
|
|
7
|
-
const disposer = Reflect.get(value, disposerKey);
|
|
8
|
-
if (typeof disposer !== 'function')
|
|
9
|
-
return false;
|
|
10
|
-
try {
|
|
11
|
-
Reflect.apply(disposer, value, []);
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
export function disposeRpcResources(values) {
|
|
19
|
-
for (const value of values)
|
|
20
|
-
disposeRpcResource(value);
|
|
21
|
-
}
|
|
22
|
-
export async function useRpcResource(promise, use) {
|
|
23
|
-
const value = await promise;
|
|
24
|
-
try {
|
|
25
|
-
return await use(value);
|
|
26
|
-
}
|
|
27
|
-
finally {
|
|
28
|
-
disposeRpcResource(value);
|
|
29
|
-
}
|
|
30
|
-
}
|