@mawesome/npm-vm 0.0.0-alpha.0 → 0.1.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/LICENSE +21 -0
- package/README.md +50 -1
- package/dist/cli.js +4157 -0
- package/dist/cli.js.map +1 -0
- package/dist/index-B071ZJDk.d.cts +164 -0
- package/dist/index-B071ZJDk.d.ts +164 -0
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/internal.cjs +2709 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +139 -0
- package/dist/internal.d.ts +139 -0
- package/dist/internal.js +2706 -0
- package/dist/internal.js.map +1 -0
- package/dist/provenance--sOYCeF2.js +322 -0
- package/dist/provenance--sOYCeF2.js.map +1 -0
- package/dist/provenance-DDegWCyS.js +2 -0
- package/dist/runtime/atomic-C3shKdH9.cjs +88 -0
- package/dist/runtime/bootstrap.cjs +271 -0
- package/dist/runtime/commonjs-AANzyd21.cjs +7393 -0
- package/dist/runtime/dispatch-B9OSfqsn.cjs +25156 -0
- package/dist/runtime/dist-CXwBT6rE.cjs +30766 -0
- package/dist/runtime/home-BaoZZSBy.cjs +91 -0
- package/dist/runtime/install-C_M5WK98.cjs +13598 -0
- package/dist/runtime/journal-_bs-eJRJ.cjs +282 -0
- package/dist/runtime/launchers/cmd.tmpl +2 -0
- package/dist/runtime/launchers/ps1.tmpl +5 -0
- package/dist/runtime/launchers/sh.tmpl +3 -0
- package/dist/runtime/lease-HBD4CmBx.cjs +224 -0
- package/dist/runtime/lock-DBeYN-Zg.cjs +141 -0
- package/dist/runtime/messages-vUDkl1-0.cjs +174 -0
- package/dist/runtime/p-map-b8pXdaFZ.cjs +105 -0
- package/dist/runtime/proc-hB8N9WjN.cjs +127 -0
- package/dist/runtime/provenance-B4bajGXQ.cjs +2 -0
- package/dist/runtime/rolldown-runtime-BGT596CS.cjs +64 -0
- package/dist/runtime/runtime.cjs +143 -0
- package/dist/runtime/src-SpeGbMJx.cjs +314 -0
- package/dist/src-Bc9nN1Kl.cjs +2125 -0
- package/dist/src-Bc9nN1Kl.cjs.map +1 -0
- package/dist/src-NhNG2mnQ.js +1798 -0
- package/dist/src-NhNG2mnQ.js.map +1 -0
- package/package.json +87 -17
- package/index.js +0 -3
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-BGT596CS.cjs");
|
|
2
|
+
const require_proc = require("./proc-hB8N9WjN.cjs");
|
|
3
|
+
const require_lease = require("./lease-HBD4CmBx.cjs");
|
|
4
|
+
const require_atomic = require("./atomic-C3shKdH9.cjs");
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
let node_fs = require("node:fs");
|
|
7
|
+
let node_crypto = require("node:crypto");
|
|
8
|
+
//#region src/state/meta.ts
|
|
9
|
+
function sha256(content) {
|
|
10
|
+
return (0, node_crypto.createHash)("sha256").update(content).digest("hex");
|
|
11
|
+
}
|
|
12
|
+
function isUtf8(content) {
|
|
13
|
+
return !content.includes(0) && Buffer.from(content.toString("utf8"), "utf8").equals(content);
|
|
14
|
+
}
|
|
15
|
+
function lineEndingsOf(text) {
|
|
16
|
+
const crlf = (text.match(/\r\n/g) ?? []).length;
|
|
17
|
+
const lf = (text.match(/\n/g) ?? []).length - crlf;
|
|
18
|
+
if (crlf === 0 && lf === 0) return "none";
|
|
19
|
+
if (crlf > 0 && lf > 0) return "mixed";
|
|
20
|
+
return crlf > 0 ? "crlf" : "lf";
|
|
21
|
+
}
|
|
22
|
+
const MISSING = {
|
|
23
|
+
type: "missing",
|
|
24
|
+
mode: 0,
|
|
25
|
+
sha256: null,
|
|
26
|
+
encoding: null,
|
|
27
|
+
lineEndings: null,
|
|
28
|
+
symlinkTarget: null
|
|
29
|
+
};
|
|
30
|
+
/** Captures `path` without following symlinks; a missing path is a valid state, not an error. */
|
|
31
|
+
function captureMeta(path) {
|
|
32
|
+
let stat;
|
|
33
|
+
try {
|
|
34
|
+
stat = (0, node_fs.lstatSync)(path);
|
|
35
|
+
} catch {
|
|
36
|
+
return MISSING;
|
|
37
|
+
}
|
|
38
|
+
const mode = process.platform === "win32" ? 0 : stat.mode & 4095;
|
|
39
|
+
if (stat.isSymbolicLink()) return {
|
|
40
|
+
type: "symlink",
|
|
41
|
+
mode,
|
|
42
|
+
sha256: null,
|
|
43
|
+
encoding: null,
|
|
44
|
+
lineEndings: null,
|
|
45
|
+
symlinkTarget: (0, node_fs.readlinkSync)(path)
|
|
46
|
+
};
|
|
47
|
+
if (stat.isDirectory()) return {
|
|
48
|
+
type: "directory",
|
|
49
|
+
mode,
|
|
50
|
+
sha256: null,
|
|
51
|
+
encoding: null,
|
|
52
|
+
lineEndings: null,
|
|
53
|
+
symlinkTarget: null
|
|
54
|
+
};
|
|
55
|
+
if (!stat.isFile()) return {
|
|
56
|
+
type: "other",
|
|
57
|
+
mode,
|
|
58
|
+
sha256: null,
|
|
59
|
+
encoding: null,
|
|
60
|
+
lineEndings: null,
|
|
61
|
+
symlinkTarget: null
|
|
62
|
+
};
|
|
63
|
+
const content = (0, node_fs.readFileSync)(path);
|
|
64
|
+
const utf8 = isUtf8(content);
|
|
65
|
+
return {
|
|
66
|
+
type: "file",
|
|
67
|
+
mode,
|
|
68
|
+
sha256: sha256(content),
|
|
69
|
+
encoding: utf8 ? "utf8" : "binary",
|
|
70
|
+
lineEndings: utf8 ? lineEndingsOf(content.toString("utf8")) : null,
|
|
71
|
+
symlinkTarget: null
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Whether the path still holds exactly what `meta` recorded (spec MUT-5): same type, same bytes or link target. */
|
|
75
|
+
function metaMatches(path, meta) {
|
|
76
|
+
const now = captureMeta(path);
|
|
77
|
+
return now.type === meta.type && now.sha256 === meta.sha256 && now.symlinkTarget === meta.symlinkTarget;
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/state/journal.ts
|
|
81
|
+
var journal_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
82
|
+
hasJournals: () => hasJournals,
|
|
83
|
+
journalPath: () => journalPath,
|
|
84
|
+
listJournals: () => listJournals,
|
|
85
|
+
mutateFile: () => mutateFile,
|
|
86
|
+
readJournal: () => readJournal,
|
|
87
|
+
recoverDeadJournals: () => recoverDeadJournals,
|
|
88
|
+
recoverForeignJournal: () => recoverForeignJournal,
|
|
89
|
+
recoverJournal: () => recoverJournal
|
|
90
|
+
});
|
|
91
|
+
function journalPath(journalDir, id) {
|
|
92
|
+
return (0, node_path.join)(journalDir, `${id}.json`);
|
|
93
|
+
}
|
|
94
|
+
function readJournal(file) {
|
|
95
|
+
try {
|
|
96
|
+
const parsed = JSON.parse((0, node_fs.readFileSync)(file, "utf8"));
|
|
97
|
+
return typeof parsed.id === "string" && typeof parsed.target === "string" ? parsed : null;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function listJournals(journalDir) {
|
|
103
|
+
let names;
|
|
104
|
+
try {
|
|
105
|
+
names = (0, node_fs.readdirSync)(journalDir);
|
|
106
|
+
} catch {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
return names.filter((name) => name.endsWith(".json")).map((name) => readJournal((0, node_path.join)(journalDir, name))).filter((journal) => journal !== null);
|
|
110
|
+
}
|
|
111
|
+
function hasJournals(journalDir) {
|
|
112
|
+
return listJournals(journalDir).length > 0;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Applies one file change under a journal: backup and staged copy first, the journal, then the write.
|
|
116
|
+
* The journal and its sidecars disappear only after the target holds the intended bytes.
|
|
117
|
+
*/
|
|
118
|
+
function mutateFile(journalDir, kind, owner, target, content, mode = 420) {
|
|
119
|
+
(0, node_fs.mkdirSync)(journalDir, {
|
|
120
|
+
recursive: true,
|
|
121
|
+
mode: 448
|
|
122
|
+
});
|
|
123
|
+
const id = `${kind}-${require_lease.newToken()}`;
|
|
124
|
+
const original = captureMeta(target);
|
|
125
|
+
let backup = null;
|
|
126
|
+
if (original.type === "file") {
|
|
127
|
+
backup = (0, node_path.join)(journalDir, `${id}.bak`);
|
|
128
|
+
(0, node_fs.copyFileSync)(target, backup);
|
|
129
|
+
}
|
|
130
|
+
let staged = null;
|
|
131
|
+
if (content !== null) {
|
|
132
|
+
staged = (0, node_path.join)(journalDir, `${id}.new`);
|
|
133
|
+
require_atomic.atomicWrite(staged, content, 384);
|
|
134
|
+
}
|
|
135
|
+
const journal = {
|
|
136
|
+
id,
|
|
137
|
+
kind,
|
|
138
|
+
owner,
|
|
139
|
+
target,
|
|
140
|
+
original,
|
|
141
|
+
intended: content === null ? null : sha256(content),
|
|
142
|
+
staged,
|
|
143
|
+
backup,
|
|
144
|
+
mode,
|
|
145
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
146
|
+
};
|
|
147
|
+
require_atomic.atomicWrite(journalPath(journalDir, id), JSON.stringify(journal), 384);
|
|
148
|
+
apply(journal, content);
|
|
149
|
+
discard(journalDir, journal);
|
|
150
|
+
}
|
|
151
|
+
function apply(journal, content) {
|
|
152
|
+
if (content === null) {
|
|
153
|
+
require_atomic.withSharingRetry(() => {
|
|
154
|
+
if ((0, node_fs.existsSync)(journal.target)) (0, node_fs.unlinkSync)(journal.target);
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
require_atomic.atomicWrite(journal.target, content, journal.mode);
|
|
159
|
+
}
|
|
160
|
+
function discard(journalDir, journal) {
|
|
161
|
+
for (const sidecar of [journal.backup, journal.staged]) if (sidecar !== null) (0, node_fs.rmSync)(sidecar, { force: true });
|
|
162
|
+
(0, node_fs.rmSync)(journalPath(journalDir, journal.id), { force: true });
|
|
163
|
+
}
|
|
164
|
+
function targetSha(target) {
|
|
165
|
+
const meta = captureMeta(target);
|
|
166
|
+
return meta.type === "file" ? meta.sha256 : null;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Finishes one journal (spec MUT-2): a target already at the intended bytes is done; one still at the
|
|
170
|
+
* original is replayed from the staged copy; anything else is left alone and reported as changed (MUT-5).
|
|
171
|
+
*/
|
|
172
|
+
function recoverJournal(journalDir, journal, report) {
|
|
173
|
+
const current = targetSha(journal.target);
|
|
174
|
+
const exists = captureMeta(journal.target).type !== "missing";
|
|
175
|
+
if (journal.intended === null ? !exists : current === journal.intended) {
|
|
176
|
+
discard(journalDir, journal);
|
|
177
|
+
report.finished.push(journal.id);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (metaMatches(journal.target, journal.original)) {
|
|
181
|
+
if (journal.intended === null) {
|
|
182
|
+
apply(journal, null);
|
|
183
|
+
report.replayed.push(journal.id);
|
|
184
|
+
} else if (journal.staged !== null && (0, node_fs.existsSync)(journal.staged) && sha256((0, node_fs.readFileSync)(journal.staged)) === journal.intended) {
|
|
185
|
+
apply(journal, (0, node_fs.readFileSync)(journal.staged));
|
|
186
|
+
report.replayed.push(journal.id);
|
|
187
|
+
} else report.rolledBack.push(journal.id);
|
|
188
|
+
discard(journalDir, journal);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
report.changed.push(journal.target);
|
|
192
|
+
discard(journalDir, journal);
|
|
193
|
+
}
|
|
194
|
+
/** Recovers every journal whose owner is dead on this machine; foreign and live ones are skipped. */
|
|
195
|
+
function recoverDeadJournals(journalDir, machineId, kinds) {
|
|
196
|
+
const report = {
|
|
197
|
+
finished: [],
|
|
198
|
+
replayed: [],
|
|
199
|
+
rolledBack: [],
|
|
200
|
+
changed: [],
|
|
201
|
+
skipped: []
|
|
202
|
+
};
|
|
203
|
+
for (const journal of listJournals(journalDir)) {
|
|
204
|
+
if (kinds !== void 0 && !kinds.includes(journal.kind)) continue;
|
|
205
|
+
if (journal.owner.machineId !== machineId || require_proc.isLive(journal.owner)) {
|
|
206
|
+
report.skipped.push(journal.id);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
recoverJournal(journalDir, journal, report);
|
|
210
|
+
}
|
|
211
|
+
return report;
|
|
212
|
+
}
|
|
213
|
+
/** Recovers one journal the operator vouched for, whatever machine owns it (the `recover` command). */
|
|
214
|
+
function recoverForeignJournal(journalDir, id, confirmedMachineId) {
|
|
215
|
+
const report = {
|
|
216
|
+
finished: [],
|
|
217
|
+
replayed: [],
|
|
218
|
+
rolledBack: [],
|
|
219
|
+
changed: [],
|
|
220
|
+
skipped: []
|
|
221
|
+
};
|
|
222
|
+
const journal = readJournal(journalPath(journalDir, id));
|
|
223
|
+
if (journal === null) throw new Error(`no journal ${id} in ${journalDir}`);
|
|
224
|
+
if (journal.owner.machineId !== confirmedMachineId) throw new Error(`journal ${id} belongs to machine ${journal.owner.machineId}, not ${confirmedMachineId}`);
|
|
225
|
+
recoverJournal(journalDir, journal, report);
|
|
226
|
+
return report;
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
Object.defineProperty(exports, "MISSING", {
|
|
230
|
+
enumerable: true,
|
|
231
|
+
get: function() {
|
|
232
|
+
return MISSING;
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
Object.defineProperty(exports, "captureMeta", {
|
|
236
|
+
enumerable: true,
|
|
237
|
+
get: function() {
|
|
238
|
+
return captureMeta;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
Object.defineProperty(exports, "journal_exports", {
|
|
242
|
+
enumerable: true,
|
|
243
|
+
get: function() {
|
|
244
|
+
return journal_exports;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
Object.defineProperty(exports, "listJournals", {
|
|
248
|
+
enumerable: true,
|
|
249
|
+
get: function() {
|
|
250
|
+
return listJournals;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
Object.defineProperty(exports, "metaMatches", {
|
|
254
|
+
enumerable: true,
|
|
255
|
+
get: function() {
|
|
256
|
+
return metaMatches;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
Object.defineProperty(exports, "mutateFile", {
|
|
260
|
+
enumerable: true,
|
|
261
|
+
get: function() {
|
|
262
|
+
return mutateFile;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
Object.defineProperty(exports, "recoverDeadJournals", {
|
|
266
|
+
enumerable: true,
|
|
267
|
+
get: function() {
|
|
268
|
+
return recoverDeadJournals;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
Object.defineProperty(exports, "recoverForeignJournal", {
|
|
272
|
+
enumerable: true,
|
|
273
|
+
get: function() {
|
|
274
|
+
return recoverForeignJournal;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
Object.defineProperty(exports, "sha256", {
|
|
278
|
+
enumerable: true,
|
|
279
|
+
get: function() {
|
|
280
|
+
return sha256;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# npm-vm launcher abi={{ABI}}
|
|
2
|
+
$node = Get-Command node.exe -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
3
|
+
if (-not $node) { [Console]::Error.WriteLine('npm-vm: node.exe is not on PATH'); exit 1 }
|
|
4
|
+
& $node.Source '{{BOOTSTRAP}}' {{SELECTOR}} @args
|
|
5
|
+
exit $LASTEXITCODE
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-BGT596CS.cjs");
|
|
2
|
+
const require_proc = require("./proc-hB8N9WjN.cjs");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
let node_fs = require("node:fs");
|
|
5
|
+
let node_crypto = require("node:crypto");
|
|
6
|
+
let node_os = require("node:os");
|
|
7
|
+
//#region src/state/lease.ts
|
|
8
|
+
var lease_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
9
|
+
acquireShared: () => acquireShared,
|
|
10
|
+
createGate: () => createGate,
|
|
11
|
+
createLease: () => createLease,
|
|
12
|
+
gatePath: () => gatePath,
|
|
13
|
+
leaseSurvived: () => leaseSurvived,
|
|
14
|
+
leasesDir: () => leasesDir,
|
|
15
|
+
listLeases: () => listLeases,
|
|
16
|
+
newToken: () => newToken,
|
|
17
|
+
ownerLive: () => ownerLive,
|
|
18
|
+
ownerRecord: () => ownerRecord,
|
|
19
|
+
readOwner: () => readOwner,
|
|
20
|
+
removeGate: () => removeGate
|
|
21
|
+
});
|
|
22
|
+
function leasesDir(entry) {
|
|
23
|
+
return `${entry}.leases`;
|
|
24
|
+
}
|
|
25
|
+
function gatePath(entry) {
|
|
26
|
+
return `${entry}.prune`;
|
|
27
|
+
}
|
|
28
|
+
function newToken() {
|
|
29
|
+
return (0, node_crypto.randomBytes)(8).toString("hex");
|
|
30
|
+
}
|
|
31
|
+
function ownerRecord(machineId, identity = require_proc.ownIdentity()) {
|
|
32
|
+
return {
|
|
33
|
+
...identity,
|
|
34
|
+
host: (0, node_os.hostname)(),
|
|
35
|
+
machineId,
|
|
36
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function readOwner(file) {
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse((0, node_fs.readFileSync)(file, "utf8"));
|
|
42
|
+
if (typeof parsed.pid !== "number") return null;
|
|
43
|
+
return {
|
|
44
|
+
pid: parsed.pid,
|
|
45
|
+
start: typeof parsed.start === "string" ? parsed.start : null,
|
|
46
|
+
host: typeof parsed.host === "string" ? parsed.host : "?",
|
|
47
|
+
machineId: typeof parsed.machineId === "string" ? parsed.machineId : "?",
|
|
48
|
+
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : ""
|
|
49
|
+
};
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** A file in the per-machine cache belongs to a dead owner only when PROC-1 says so; unreadable means live. */
|
|
55
|
+
function ownerLive(file) {
|
|
56
|
+
const owner = readOwner(file);
|
|
57
|
+
return owner === null ? true : require_proc.isLive(owner);
|
|
58
|
+
}
|
|
59
|
+
/** Creates a shared lease on `entry`; the file name is unique, so a prune can never delete a live one by mistake. */
|
|
60
|
+
function createLease(entry, owner) {
|
|
61
|
+
const dir = leasesDir(entry);
|
|
62
|
+
for (let attempt = 0;; attempt += 1) {
|
|
63
|
+
(0, node_fs.mkdirSync)(dir, {
|
|
64
|
+
recursive: true,
|
|
65
|
+
mode: 448
|
|
66
|
+
});
|
|
67
|
+
const path = (0, node_path.join)(dir, newToken());
|
|
68
|
+
try {
|
|
69
|
+
(0, node_fs.writeFileSync)(path, JSON.stringify(owner), {
|
|
70
|
+
mode: 384,
|
|
71
|
+
flag: "wx"
|
|
72
|
+
});
|
|
73
|
+
} catch (error) {
|
|
74
|
+
(0, node_fs.rmSync)(path, { force: true });
|
|
75
|
+
if (error.code === "ENOENT" && attempt < 3) continue;
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
path,
|
|
80
|
+
release() {
|
|
81
|
+
(0, node_fs.rmSync)(path, { force: true });
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Whether a lease created before a gate check is still in place (spec LEASE-1).
|
|
88
|
+
* A prune that passed its own lease check first moves the whole leases dir away, taking the file with it.
|
|
89
|
+
*/
|
|
90
|
+
function leaseSurvived(lease) {
|
|
91
|
+
return (0, node_fs.existsSync)(lease.path);
|
|
92
|
+
}
|
|
93
|
+
function listLeases(entry) {
|
|
94
|
+
try {
|
|
95
|
+
return (0, node_fs.readdirSync)(leasesDir(entry)).map((name) => (0, node_path.join)(leasesDir(entry), name));
|
|
96
|
+
} catch {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function sleep(ms) {
|
|
101
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
102
|
+
}
|
|
103
|
+
const GATE_WAIT_MS = 5e3;
|
|
104
|
+
/**
|
|
105
|
+
* The runtime's shared lease (spec LEASE-1): lease first, then look for a live prune gate.
|
|
106
|
+
* A live gate means the entry is going away: wait briefly, retry once, else return null so the caller fetches a private entry.
|
|
107
|
+
*/
|
|
108
|
+
async function acquireShared(entry, owner, options = {}) {
|
|
109
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
110
|
+
const lease = createLease(entry, owner);
|
|
111
|
+
const gate = gatePath(entry);
|
|
112
|
+
let gated = false;
|
|
113
|
+
try {
|
|
114
|
+
gated = ownerLive(gate);
|
|
115
|
+
} catch {
|
|
116
|
+
gated = false;
|
|
117
|
+
}
|
|
118
|
+
if (!gated || !gateExists(gate)) {
|
|
119
|
+
if (leaseSurvived(lease)) return lease;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
lease.release();
|
|
123
|
+
const deadline = Date.now() + (options.waitMs ?? GATE_WAIT_MS);
|
|
124
|
+
while (Date.now() < deadline && gateExists(gate)) await sleep(50);
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
function gateExists(gate) {
|
|
129
|
+
try {
|
|
130
|
+
return readOwner(gate) !== null;
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Creates the prune gate with O_EXCL; false when another gate exists (there is only ever one prune, so a leftover). */
|
|
136
|
+
function createGate(entry, owner) {
|
|
137
|
+
try {
|
|
138
|
+
(0, node_fs.writeFileSync)(gatePath(entry), JSON.stringify(owner), {
|
|
139
|
+
flag: "wx",
|
|
140
|
+
mode: 384
|
|
141
|
+
});
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (error.code === "EEXIST") return false;
|
|
144
|
+
(0, node_fs.rmSync)(gatePath(entry), { force: true });
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
function removeGate(entry) {
|
|
150
|
+
(0, node_fs.rmSync)(gatePath(entry), { force: true });
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
Object.defineProperty(exports, "acquireShared", {
|
|
154
|
+
enumerable: true,
|
|
155
|
+
get: function() {
|
|
156
|
+
return acquireShared;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
Object.defineProperty(exports, "createGate", {
|
|
160
|
+
enumerable: true,
|
|
161
|
+
get: function() {
|
|
162
|
+
return createGate;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
Object.defineProperty(exports, "createLease", {
|
|
166
|
+
enumerable: true,
|
|
167
|
+
get: function() {
|
|
168
|
+
return createLease;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
Object.defineProperty(exports, "gatePath", {
|
|
172
|
+
enumerable: true,
|
|
173
|
+
get: function() {
|
|
174
|
+
return gatePath;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
Object.defineProperty(exports, "leaseSurvived", {
|
|
178
|
+
enumerable: true,
|
|
179
|
+
get: function() {
|
|
180
|
+
return leaseSurvived;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
Object.defineProperty(exports, "lease_exports", {
|
|
184
|
+
enumerable: true,
|
|
185
|
+
get: function() {
|
|
186
|
+
return lease_exports;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
Object.defineProperty(exports, "leasesDir", {
|
|
190
|
+
enumerable: true,
|
|
191
|
+
get: function() {
|
|
192
|
+
return leasesDir;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
Object.defineProperty(exports, "listLeases", {
|
|
196
|
+
enumerable: true,
|
|
197
|
+
get: function() {
|
|
198
|
+
return listLeases;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
Object.defineProperty(exports, "newToken", {
|
|
202
|
+
enumerable: true,
|
|
203
|
+
get: function() {
|
|
204
|
+
return newToken;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
Object.defineProperty(exports, "ownerLive", {
|
|
208
|
+
enumerable: true,
|
|
209
|
+
get: function() {
|
|
210
|
+
return ownerLive;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
Object.defineProperty(exports, "ownerRecord", {
|
|
214
|
+
enumerable: true,
|
|
215
|
+
get: function() {
|
|
216
|
+
return ownerRecord;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
Object.defineProperty(exports, "removeGate", {
|
|
220
|
+
enumerable: true,
|
|
221
|
+
get: function() {
|
|
222
|
+
return removeGate;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const require_rolldown_runtime = require("./rolldown-runtime-BGT596CS.cjs");
|
|
2
|
+
const require_messages = require("./messages-vUDkl1-0.cjs");
|
|
3
|
+
const require_proc = require("./proc-hB8N9WjN.cjs");
|
|
4
|
+
const require_lease = require("./lease-HBD4CmBx.cjs");
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
let node_fs = require("node:fs");
|
|
7
|
+
//#region src/state/lock.ts
|
|
8
|
+
var lock_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
9
|
+
LOCK_FILE: () => LOCK_FILE,
|
|
10
|
+
acquireLock: () => acquireLock,
|
|
11
|
+
lockOwnerLive: () => lockOwnerLive,
|
|
12
|
+
lockPath: () => lockPath,
|
|
13
|
+
readLock: () => readLock,
|
|
14
|
+
tryLock: () => tryLock
|
|
15
|
+
});
|
|
16
|
+
const LOCK_FILE = "lock";
|
|
17
|
+
const WAIT_LIMIT_MS = 1e4;
|
|
18
|
+
function lockPath(stateDir) {
|
|
19
|
+
return (0, node_path.join)(stateDir, LOCK_FILE);
|
|
20
|
+
}
|
|
21
|
+
function readLock(stateDir) {
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse((0, node_fs.readFileSync)(lockPath(stateDir), "utf8"));
|
|
24
|
+
if (typeof parsed.pid !== "number" || typeof parsed.token !== "string") return null;
|
|
25
|
+
return {
|
|
26
|
+
pid: parsed.pid,
|
|
27
|
+
start: typeof parsed.start === "string" ? parsed.start : null,
|
|
28
|
+
host: typeof parsed.host === "string" ? parsed.host : "?",
|
|
29
|
+
machineId: typeof parsed.machineId === "string" ? parsed.machineId : "?",
|
|
30
|
+
createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : "",
|
|
31
|
+
token: parsed.token
|
|
32
|
+
};
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Whether the lock owner can be shown to be dead: only on this machine, and only by PROC-1. */
|
|
38
|
+
function lockOwnerLive(record, machineId) {
|
|
39
|
+
if (record === null) return true;
|
|
40
|
+
if (record.machineId !== machineId) return true;
|
|
41
|
+
return require_proc.isLive(record);
|
|
42
|
+
}
|
|
43
|
+
function tryCreate(path, record) {
|
|
44
|
+
try {
|
|
45
|
+
(0, node_fs.writeFileSync)(path, JSON.stringify(record), {
|
|
46
|
+
flag: "wx",
|
|
47
|
+
mode: 384
|
|
48
|
+
});
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error.code === "EEXIST") return false;
|
|
51
|
+
(0, node_fs.rmSync)(path, { force: true });
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
function sleep(ms) {
|
|
57
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Takes the exclusive lock (spec MUT-1): created with O_EXCL, released only by its owner, never reclaimed.
|
|
61
|
+
* Waits with backoff for up to 10 s, then fails with M-LOCKED naming the file and whether the owner is live.
|
|
62
|
+
*/
|
|
63
|
+
async function acquireLock(stateDir, owner, options = {}) {
|
|
64
|
+
(0, node_fs.mkdirSync)(stateDir, {
|
|
65
|
+
recursive: true,
|
|
66
|
+
mode: 448
|
|
67
|
+
});
|
|
68
|
+
const path = lockPath(stateDir);
|
|
69
|
+
const record = {
|
|
70
|
+
...owner,
|
|
71
|
+
token: require_lease.newToken()
|
|
72
|
+
};
|
|
73
|
+
const deadline = Date.now() + (options.waitMs ?? WAIT_LIMIT_MS);
|
|
74
|
+
let delay = 50;
|
|
75
|
+
for (;;) {
|
|
76
|
+
if (tryCreate(path, record)) return {
|
|
77
|
+
path,
|
|
78
|
+
release() {
|
|
79
|
+
if (readLock(stateDir)?.token === record.token) (0, node_fs.rmSync)(path, { force: true });
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
if (Date.now() + delay > deadline) {
|
|
83
|
+
const holder = readLock(stateDir);
|
|
84
|
+
const foreign = holder !== null && holder.machineId !== owner.machineId;
|
|
85
|
+
const live = lockOwnerLive(holder, owner.machineId);
|
|
86
|
+
throw new require_messages.NpmVmError("M-LOCKED", require_messages.EXIT.problems, require_messages.lockedMessage(path, holder, live, foreign));
|
|
87
|
+
}
|
|
88
|
+
await sleep(delay);
|
|
89
|
+
delay = Math.min(delay * 2, 1e3);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** One attempt, no waiting; for the launcher's recovery check, which must never delay npm. */
|
|
93
|
+
function tryLock(stateDir, owner) {
|
|
94
|
+
(0, node_fs.mkdirSync)(stateDir, {
|
|
95
|
+
recursive: true,
|
|
96
|
+
mode: 448
|
|
97
|
+
});
|
|
98
|
+
const path = lockPath(stateDir);
|
|
99
|
+
const record = {
|
|
100
|
+
...owner,
|
|
101
|
+
token: require_lease.newToken()
|
|
102
|
+
};
|
|
103
|
+
if (!tryCreate(path, record)) return null;
|
|
104
|
+
return {
|
|
105
|
+
path,
|
|
106
|
+
release() {
|
|
107
|
+
if (readLock(stateDir)?.token === record.token) (0, node_fs.rmSync)(path, { force: true });
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
Object.defineProperty(exports, "acquireLock", {
|
|
113
|
+
enumerable: true,
|
|
114
|
+
get: function() {
|
|
115
|
+
return acquireLock;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
Object.defineProperty(exports, "lockOwnerLive", {
|
|
119
|
+
enumerable: true,
|
|
120
|
+
get: function() {
|
|
121
|
+
return lockOwnerLive;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
Object.defineProperty(exports, "lockPath", {
|
|
125
|
+
enumerable: true,
|
|
126
|
+
get: function() {
|
|
127
|
+
return lockPath;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
Object.defineProperty(exports, "lock_exports", {
|
|
131
|
+
enumerable: true,
|
|
132
|
+
get: function() {
|
|
133
|
+
return lock_exports;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
Object.defineProperty(exports, "readLock", {
|
|
137
|
+
enumerable: true,
|
|
138
|
+
get: function() {
|
|
139
|
+
return readLock;
|
|
140
|
+
}
|
|
141
|
+
});
|