@lark-apaas/fullstack-cli 1.1.68-alpha.20260930060426 → 1.1.68-alpha.20261002212123
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/index.js +882 -12
- package/package.json +1 -1
- package/templates/scripts/build.sh +609 -17
- package/templates/scripts/hooks/run-precommit.js +6 -22
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import fs32 from "fs";
|
|
3
|
+
import path27 from "path";
|
|
4
4
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
5
5
|
import { config as dotenvConfig } from "dotenv";
|
|
6
6
|
|
|
@@ -5201,7 +5201,7 @@ var PROMPT_PATTERNS = [
|
|
|
5201
5201
|
{ pattern: /proceed\?/i, answer: "y\n" }
|
|
5202
5202
|
];
|
|
5203
5203
|
async function executeShadcnAdd(registryItemPath) {
|
|
5204
|
-
return new Promise((
|
|
5204
|
+
return new Promise((resolve3) => {
|
|
5205
5205
|
let output = "";
|
|
5206
5206
|
const args = ["--yes", "shadcn@3.8.2", "add", registryItemPath];
|
|
5207
5207
|
const ptyProcess = pty.spawn("npx", args, {
|
|
@@ -5227,7 +5227,7 @@ async function executeShadcnAdd(registryItemPath) {
|
|
|
5227
5227
|
});
|
|
5228
5228
|
const timeoutId = setTimeout(() => {
|
|
5229
5229
|
ptyProcess.kill();
|
|
5230
|
-
|
|
5230
|
+
resolve3({
|
|
5231
5231
|
success: false,
|
|
5232
5232
|
files: [],
|
|
5233
5233
|
error: "\u6267\u884C\u8D85\u65F6"
|
|
@@ -5238,7 +5238,7 @@ async function executeShadcnAdd(registryItemPath) {
|
|
|
5238
5238
|
const success = exitCode === 0;
|
|
5239
5239
|
const filePaths = parseOutput(output);
|
|
5240
5240
|
const files = filePaths.map(toFileInfo);
|
|
5241
|
-
|
|
5241
|
+
resolve3({
|
|
5242
5242
|
success,
|
|
5243
5243
|
files,
|
|
5244
5244
|
error: success ? void 0 : output || `Process exited with code ${exitCode}`
|
|
@@ -5249,12 +5249,12 @@ async function executeShadcnAdd(registryItemPath) {
|
|
|
5249
5249
|
|
|
5250
5250
|
// src/commands/component/add.handler.ts
|
|
5251
5251
|
function runActionPluginInit() {
|
|
5252
|
-
return new Promise((
|
|
5252
|
+
return new Promise((resolve3) => {
|
|
5253
5253
|
execFile("fullstack-cli", ["action-plugin", "init"], { cwd: process.cwd(), stdio: "ignore" }, (error) => {
|
|
5254
5254
|
if (error) {
|
|
5255
5255
|
debug("action-plugin init \u5931\u8D25: %s", error.message);
|
|
5256
5256
|
}
|
|
5257
|
-
|
|
5257
|
+
resolve3();
|
|
5258
5258
|
});
|
|
5259
5259
|
});
|
|
5260
5260
|
}
|
|
@@ -8429,6 +8429,875 @@ var buildCommandGroup = {
|
|
|
8429
8429
|
commands: [getTokenCommand, uploadStaticCommand, preUploadStaticCommand]
|
|
8430
8430
|
};
|
|
8431
8431
|
|
|
8432
|
+
// src/output-lease.ts
|
|
8433
|
+
import * as crypto from "crypto";
|
|
8434
|
+
import * as fs31 from "fs";
|
|
8435
|
+
import * as os3 from "os";
|
|
8436
|
+
import * as path26 from "path";
|
|
8437
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
8438
|
+
var OUTPUT_LEASE_PROTOCOL_VERSION = 1;
|
|
8439
|
+
var OUTPUT_LEASE_ENV = "MIAODA_PREVIEW_OUTPUT_LEASE_V1";
|
|
8440
|
+
var OUTPUT_LEASE_CONFLICT = "MIAODA_PREVIEW_OUTPUT_CONFLICT";
|
|
8441
|
+
var OUTPUT_LEASE_STATE_UNKNOWN = "MIAODA_PREVIEW_STATE_UNKNOWN";
|
|
8442
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 2e3;
|
|
8443
|
+
var DEFAULT_STALE_AFTER_MS = 15e3;
|
|
8444
|
+
var TRANSITION_RETRY_COUNT = 200;
|
|
8445
|
+
var TRANSITION_RETRY_MS = 5;
|
|
8446
|
+
var OWNER_FILE = "owner.json";
|
|
8447
|
+
var TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/;
|
|
8448
|
+
var KEY_PATTERN = /^[a-f0-9]{64}$/;
|
|
8449
|
+
var TEMP_OWNER_PATTERN = /^\.owner-[0-9]+-[a-f0-9]{16}\.tmp$/;
|
|
8450
|
+
var OutputLeaseProtocolError = class extends Error {
|
|
8451
|
+
constructor(code, detail) {
|
|
8452
|
+
super(`${code}: ${detail}`);
|
|
8453
|
+
this.name = "OutputLeaseProtocolError";
|
|
8454
|
+
this.code = code;
|
|
8455
|
+
}
|
|
8456
|
+
};
|
|
8457
|
+
function unknown(detail, cause) {
|
|
8458
|
+
const suffix = cause instanceof Error && cause.message ? ` (${cause.message})` : "";
|
|
8459
|
+
return new OutputLeaseProtocolError(
|
|
8460
|
+
OUTPUT_LEASE_STATE_UNKNOWN,
|
|
8461
|
+
`${detail}${suffix}`
|
|
8462
|
+
);
|
|
8463
|
+
}
|
|
8464
|
+
function conflict(target, owner) {
|
|
8465
|
+
return new OutputLeaseProtocolError(
|
|
8466
|
+
OUTPUT_LEASE_CONFLICT,
|
|
8467
|
+
`${target.canonicalOutDir} is held by an active ${owner.kind} owner`
|
|
8468
|
+
);
|
|
8469
|
+
}
|
|
8470
|
+
function errnoCode(error) {
|
|
8471
|
+
return error instanceof Error && "code" in error ? error.code : void 0;
|
|
8472
|
+
}
|
|
8473
|
+
function isMissing(error) {
|
|
8474
|
+
const code = errnoCode(error);
|
|
8475
|
+
return code === "ENOENT" || code === "ESRCH";
|
|
8476
|
+
}
|
|
8477
|
+
function sleepSync(milliseconds) {
|
|
8478
|
+
const signal = new Int32Array(new SharedArrayBuffer(4));
|
|
8479
|
+
Atomics.wait(signal, 0, 0, milliseconds);
|
|
8480
|
+
}
|
|
8481
|
+
function canonicalizeWithoutCreating(input) {
|
|
8482
|
+
let cursor = path26.resolve(input);
|
|
8483
|
+
const missing = [];
|
|
8484
|
+
for (; ; ) {
|
|
8485
|
+
try {
|
|
8486
|
+
fs31.lstatSync(cursor);
|
|
8487
|
+
} catch (error) {
|
|
8488
|
+
if (!isMissing(error))
|
|
8489
|
+
throw unknown(`cannot inspect path ${cursor}`, error);
|
|
8490
|
+
const parent = path26.dirname(cursor);
|
|
8491
|
+
if (parent === cursor) {
|
|
8492
|
+
throw unknown(`cannot find an existing ancestor for ${input}`);
|
|
8493
|
+
}
|
|
8494
|
+
missing.unshift(path26.basename(cursor));
|
|
8495
|
+
cursor = parent;
|
|
8496
|
+
continue;
|
|
8497
|
+
}
|
|
8498
|
+
try {
|
|
8499
|
+
return path26.resolve(fs31.realpathSync.native(cursor), ...missing);
|
|
8500
|
+
} catch (error) {
|
|
8501
|
+
throw unknown(`cannot canonicalize existing ancestor ${cursor}`, error);
|
|
8502
|
+
}
|
|
8503
|
+
}
|
|
8504
|
+
}
|
|
8505
|
+
function currentUserComponent() {
|
|
8506
|
+
if (typeof process.getuid === "function") return `uid-${process.getuid()}`;
|
|
8507
|
+
return `user-${crypto.createHash("sha256").update(os3.userInfo().username).digest("hex").slice(0, 16)}`;
|
|
8508
|
+
}
|
|
8509
|
+
function defaultLockRootForCurrentUser(temporaryRoot = os3.tmpdir()) {
|
|
8510
|
+
return path26.join(
|
|
8511
|
+
temporaryRoot,
|
|
8512
|
+
`miaoda-preview-output-leases-v${OUTPUT_LEASE_PROTOCOL_VERSION}-${currentUserComponent()}`
|
|
8513
|
+
);
|
|
8514
|
+
}
|
|
8515
|
+
function validatePrivateDirectory(directory, label, expected) {
|
|
8516
|
+
let link;
|
|
8517
|
+
let stat;
|
|
8518
|
+
try {
|
|
8519
|
+
link = fs31.lstatSync(directory);
|
|
8520
|
+
if (link.isSymbolicLink() || !link.isDirectory()) {
|
|
8521
|
+
throw new Error("not a direct directory");
|
|
8522
|
+
}
|
|
8523
|
+
stat = fs31.statSync(directory);
|
|
8524
|
+
} catch (error) {
|
|
8525
|
+
throw unknown(`untrusted ${label} ${directory}`, error);
|
|
8526
|
+
}
|
|
8527
|
+
if (!stat.isDirectory() || stat.dev !== link.dev || stat.ino !== link.ino) {
|
|
8528
|
+
throw unknown(`untrusted ${label} identity ${directory}`);
|
|
8529
|
+
}
|
|
8530
|
+
if (expected && (stat.dev !== expected.device || stat.ino !== expected.inode)) {
|
|
8531
|
+
throw unknown(`${label} changed identity ${directory}`);
|
|
8532
|
+
}
|
|
8533
|
+
if (process.platform !== "win32") {
|
|
8534
|
+
if (typeof process.getuid !== "function" || stat.uid !== process.getuid()) {
|
|
8535
|
+
throw unknown(`unexpected owner for ${label} ${directory}`);
|
|
8536
|
+
}
|
|
8537
|
+
if ((stat.mode & 63) !== 0) {
|
|
8538
|
+
throw unknown(`insecure permissions for ${label} ${directory}`);
|
|
8539
|
+
}
|
|
8540
|
+
}
|
|
8541
|
+
return { device: stat.dev, inode: stat.ino };
|
|
8542
|
+
}
|
|
8543
|
+
function prepareTrustedLockRoot(lockRoot) {
|
|
8544
|
+
try {
|
|
8545
|
+
fs31.mkdirSync(lockRoot, { mode: 448 });
|
|
8546
|
+
} catch (error) {
|
|
8547
|
+
if (errnoCode(error) !== "EEXIST") {
|
|
8548
|
+
throw unknown(`cannot prepare lease storage ${lockRoot}`, error);
|
|
8549
|
+
}
|
|
8550
|
+
}
|
|
8551
|
+
return validatePrivateDirectory(lockRoot, "lease root");
|
|
8552
|
+
}
|
|
8553
|
+
function assertTrustedRoot(target) {
|
|
8554
|
+
validatePrivateDirectory(target.lockRoot, "lease root", {
|
|
8555
|
+
device: target.lockRootDevice,
|
|
8556
|
+
inode: target.lockRootInode
|
|
8557
|
+
});
|
|
8558
|
+
}
|
|
8559
|
+
function resolveOutputLeaseTarget(rootDir, outDir, lockRoot = defaultLockRootForCurrentUser()) {
|
|
8560
|
+
const canonicalRoot = canonicalizeWithoutCreating(rootDir);
|
|
8561
|
+
const absoluteOutDir = path26.isAbsolute(outDir) ? outDir : path26.resolve(canonicalRoot, outDir);
|
|
8562
|
+
const canonicalOutDir = canonicalizeWithoutCreating(absoluteOutDir);
|
|
8563
|
+
const key = crypto.createHash("sha256").update(`miaoda-preview-output-lease:v${OUTPUT_LEASE_PROTOCOL_VERSION}\0`).update(canonicalOutDir).digest("hex");
|
|
8564
|
+
const canonicalLockRoot = path26.resolve(lockRoot);
|
|
8565
|
+
const identity = prepareTrustedLockRoot(canonicalLockRoot);
|
|
8566
|
+
const lockDir = path26.join(canonicalLockRoot, `${key}.lease`);
|
|
8567
|
+
return {
|
|
8568
|
+
key,
|
|
8569
|
+
canonicalRoot,
|
|
8570
|
+
canonicalOutDir,
|
|
8571
|
+
lockRoot: canonicalLockRoot,
|
|
8572
|
+
lockRootDevice: identity.device,
|
|
8573
|
+
lockRootInode: identity.inode,
|
|
8574
|
+
lockDir,
|
|
8575
|
+
ownerFile: path26.join(lockDir, OWNER_FILE),
|
|
8576
|
+
transitionDir: path26.join(canonicalLockRoot, `${key}.transition`)
|
|
8577
|
+
};
|
|
8578
|
+
}
|
|
8579
|
+
function acquireTransitionGuard(target) {
|
|
8580
|
+
for (let attempt = 0; attempt < TRANSITION_RETRY_COUNT; attempt += 1) {
|
|
8581
|
+
assertTrustedRoot(target);
|
|
8582
|
+
try {
|
|
8583
|
+
fs31.mkdirSync(target.transitionDir, { mode: 448 });
|
|
8584
|
+
const identity = validatePrivateDirectory(
|
|
8585
|
+
target.transitionDir,
|
|
8586
|
+
"transition guard"
|
|
8587
|
+
);
|
|
8588
|
+
assertTrustedRoot(target);
|
|
8589
|
+
return {
|
|
8590
|
+
path: target.transitionDir,
|
|
8591
|
+
device: identity.device,
|
|
8592
|
+
inode: identity.inode,
|
|
8593
|
+
poisoned: false
|
|
8594
|
+
};
|
|
8595
|
+
} catch (error) {
|
|
8596
|
+
if (errnoCode(error) !== "EEXIST") {
|
|
8597
|
+
if (error instanceof OutputLeaseProtocolError) throw error;
|
|
8598
|
+
throw unknown(
|
|
8599
|
+
`cannot acquire transition guard for ${target.key}`,
|
|
8600
|
+
error
|
|
8601
|
+
);
|
|
8602
|
+
}
|
|
8603
|
+
try {
|
|
8604
|
+
validatePrivateDirectory(target.transitionDir, "transition guard");
|
|
8605
|
+
} catch (validationError) {
|
|
8606
|
+
if (!fs31.existsSync(target.transitionDir)) continue;
|
|
8607
|
+
throw validationError;
|
|
8608
|
+
}
|
|
8609
|
+
sleepSync(TRANSITION_RETRY_MS);
|
|
8610
|
+
}
|
|
8611
|
+
}
|
|
8612
|
+
throw unknown(
|
|
8613
|
+
`transition guard is unavailable for ${target.canonicalOutDir}`
|
|
8614
|
+
);
|
|
8615
|
+
}
|
|
8616
|
+
function assertTransitionGuard(target, guard) {
|
|
8617
|
+
assertTrustedRoot(target);
|
|
8618
|
+
validatePrivateDirectory(guard.path, "transition guard", guard);
|
|
8619
|
+
}
|
|
8620
|
+
function releaseTransitionGuard(target, guard) {
|
|
8621
|
+
assertTransitionGuard(target, guard);
|
|
8622
|
+
try {
|
|
8623
|
+
fs31.rmdirSync(guard.path);
|
|
8624
|
+
} catch (error) {
|
|
8625
|
+
throw unknown(`cannot release transition guard for ${target.key}`, error);
|
|
8626
|
+
}
|
|
8627
|
+
}
|
|
8628
|
+
function withTransitionGuard(target, operation) {
|
|
8629
|
+
const guard = acquireTransitionGuard(target);
|
|
8630
|
+
let result;
|
|
8631
|
+
let failure;
|
|
8632
|
+
try {
|
|
8633
|
+
result = operation(guard);
|
|
8634
|
+
} catch (error) {
|
|
8635
|
+
failure = error;
|
|
8636
|
+
}
|
|
8637
|
+
if (!guard.poisoned) {
|
|
8638
|
+
try {
|
|
8639
|
+
releaseTransitionGuard(target, guard);
|
|
8640
|
+
} catch (error) {
|
|
8641
|
+
failure = error;
|
|
8642
|
+
}
|
|
8643
|
+
}
|
|
8644
|
+
if (failure !== void 0) throw failure;
|
|
8645
|
+
return result;
|
|
8646
|
+
}
|
|
8647
|
+
function parseLinuxStat(contents) {
|
|
8648
|
+
const end = contents.lastIndexOf(")");
|
|
8649
|
+
if (end < 0) throw new Error("missing process command terminator");
|
|
8650
|
+
const fields = contents.slice(end + 2).trim().split(/\s+/);
|
|
8651
|
+
if (fields.length < 20) throw new Error("incomplete process stat");
|
|
8652
|
+
const parentPid = Number(fields[1]);
|
|
8653
|
+
const startTime = fields[19];
|
|
8654
|
+
if (!Number.isSafeInteger(parentPid) || parentPid < 0 || !/^\d+$/.test(startTime)) {
|
|
8655
|
+
throw new Error("invalid process stat");
|
|
8656
|
+
}
|
|
8657
|
+
return { parentPid, startIdentity: `linux-proc-start:${startTime}` };
|
|
8658
|
+
}
|
|
8659
|
+
function inspectProcess(pid) {
|
|
8660
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return { state: "unknown" };
|
|
8661
|
+
if (process.platform === "linux") {
|
|
8662
|
+
try {
|
|
8663
|
+
return {
|
|
8664
|
+
state: "alive",
|
|
8665
|
+
startIdentity: parseLinuxStat(
|
|
8666
|
+
fs31.readFileSync(`/proc/${pid}/stat`, "utf8")
|
|
8667
|
+
).startIdentity
|
|
8668
|
+
};
|
|
8669
|
+
} catch (error) {
|
|
8670
|
+
return isMissing(error) ? { state: "dead" } : { state: "unknown" };
|
|
8671
|
+
}
|
|
8672
|
+
}
|
|
8673
|
+
if (process.platform !== "win32") {
|
|
8674
|
+
try {
|
|
8675
|
+
const value = execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
8676
|
+
encoding: "utf8",
|
|
8677
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
8678
|
+
}).trim();
|
|
8679
|
+
return value ? { state: "alive", startIdentity: `posix-ps-start:${value}` } : { state: "dead" };
|
|
8680
|
+
} catch {
|
|
8681
|
+
try {
|
|
8682
|
+
process.kill(pid, 0);
|
|
8683
|
+
return { state: "unknown" };
|
|
8684
|
+
} catch (error) {
|
|
8685
|
+
return isMissing(error) ? { state: "dead" } : { state: "unknown" };
|
|
8686
|
+
}
|
|
8687
|
+
}
|
|
8688
|
+
}
|
|
8689
|
+
return { state: "unknown" };
|
|
8690
|
+
}
|
|
8691
|
+
function readParentPid(pid) {
|
|
8692
|
+
if (process.platform === "linux") {
|
|
8693
|
+
try {
|
|
8694
|
+
return parseLinuxStat(fs31.readFileSync(`/proc/${pid}/stat`, "utf8")).parentPid;
|
|
8695
|
+
} catch {
|
|
8696
|
+
return null;
|
|
8697
|
+
}
|
|
8698
|
+
}
|
|
8699
|
+
if (process.platform !== "win32") {
|
|
8700
|
+
try {
|
|
8701
|
+
const parent = Number(
|
|
8702
|
+
execFileSync2("ps", ["-o", "ppid=", "-p", String(pid)], {
|
|
8703
|
+
encoding: "utf8",
|
|
8704
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
8705
|
+
}).trim()
|
|
8706
|
+
);
|
|
8707
|
+
return Number.isSafeInteger(parent) && parent >= 0 ? parent : null;
|
|
8708
|
+
} catch {
|
|
8709
|
+
return null;
|
|
8710
|
+
}
|
|
8711
|
+
}
|
|
8712
|
+
return null;
|
|
8713
|
+
}
|
|
8714
|
+
function isProcessOrDescendant(pid, ancestorPid) {
|
|
8715
|
+
let cursor = pid;
|
|
8716
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8717
|
+
for (let depth = 0; depth < 256; depth += 1) {
|
|
8718
|
+
if (cursor === ancestorPid) return true;
|
|
8719
|
+
if (cursor <= 1 || seen.has(cursor)) return false;
|
|
8720
|
+
seen.add(cursor);
|
|
8721
|
+
const parent = readParentPid(cursor);
|
|
8722
|
+
if (parent === null) return false;
|
|
8723
|
+
cursor = parent;
|
|
8724
|
+
}
|
|
8725
|
+
return false;
|
|
8726
|
+
}
|
|
8727
|
+
function validateOwner(value, target) {
|
|
8728
|
+
if (!value || typeof value !== "object") {
|
|
8729
|
+
throw unknown(`malformed owner for ${target.canonicalOutDir}`);
|
|
8730
|
+
}
|
|
8731
|
+
const owner = value;
|
|
8732
|
+
const valid = owner.version === OUTPUT_LEASE_PROTOCOL_VERSION && (owner.kind === "dev" || owner.kind === "production") && owner.key === target.key && typeof owner.canonicalRoot === "string" && path26.isAbsolute(owner.canonicalRoot) && owner.canonicalOutDir === target.canonicalOutDir && typeof owner.generation === "string" && TOKEN_PATTERN.test(owner.generation) && typeof owner.token === "string" && TOKEN_PATTERN.test(owner.token) && Number.isSafeInteger(owner.pid) && Number(owner.pid) > 0 && typeof owner.processStartIdentity === "string" && owner.processStartIdentity.length > 0 && Number.isFinite(owner.createdAt) && Number(owner.createdAt) > 0 && Number.isFinite(owner.heartbeatAt) && Number(owner.heartbeatAt) > 0;
|
|
8733
|
+
if (!valid) throw unknown(`malformed owner for ${target.canonicalOutDir}`);
|
|
8734
|
+
return owner;
|
|
8735
|
+
}
|
|
8736
|
+
function validateRegularPrivateFile(file, label) {
|
|
8737
|
+
try {
|
|
8738
|
+
const link = fs31.lstatSync(file);
|
|
8739
|
+
const stat = fs31.statSync(file);
|
|
8740
|
+
if (link.isSymbolicLink() || !link.isFile() || !stat.isFile() || link.dev !== stat.dev || link.ino !== stat.ino || stat.nlink !== 1) {
|
|
8741
|
+
throw new Error("not a direct single-link file");
|
|
8742
|
+
}
|
|
8743
|
+
if (process.platform !== "win32") {
|
|
8744
|
+
if (typeof process.getuid !== "function" || stat.uid !== process.getuid()) {
|
|
8745
|
+
throw new Error("unexpected file owner");
|
|
8746
|
+
}
|
|
8747
|
+
if ((stat.mode & 63) !== 0) throw new Error("insecure file mode");
|
|
8748
|
+
}
|
|
8749
|
+
} catch (error) {
|
|
8750
|
+
throw unknown(`untrusted ${label} ${file}`, error);
|
|
8751
|
+
}
|
|
8752
|
+
}
|
|
8753
|
+
function readOwnerFromDirectory(target, directory) {
|
|
8754
|
+
assertTrustedRoot(target);
|
|
8755
|
+
validatePrivateDirectory(directory, "lease generation");
|
|
8756
|
+
const ownerFile = path26.join(directory, OWNER_FILE);
|
|
8757
|
+
validateRegularPrivateFile(ownerFile, "owner record");
|
|
8758
|
+
try {
|
|
8759
|
+
return validateOwner(
|
|
8760
|
+
JSON.parse(fs31.readFileSync(ownerFile, "utf8")),
|
|
8761
|
+
target
|
|
8762
|
+
);
|
|
8763
|
+
} catch (error) {
|
|
8764
|
+
if (error instanceof OutputLeaseProtocolError) throw error;
|
|
8765
|
+
throw unknown(`cannot read owner for ${target.canonicalOutDir}`, error);
|
|
8766
|
+
}
|
|
8767
|
+
}
|
|
8768
|
+
function readOwner(target) {
|
|
8769
|
+
return readOwnerFromDirectory(target, target.lockDir);
|
|
8770
|
+
}
|
|
8771
|
+
function sameGeneration(owner, expected) {
|
|
8772
|
+
return owner.generation === expected.generation && owner.token === expected.token;
|
|
8773
|
+
}
|
|
8774
|
+
function writeOwnerAtomically(target, owner, guard) {
|
|
8775
|
+
assertTransitionGuard(target, guard);
|
|
8776
|
+
const temporary = path26.join(
|
|
8777
|
+
target.lockDir,
|
|
8778
|
+
`.owner-${process.pid}-${crypto.randomBytes(8).toString("hex")}.tmp`
|
|
8779
|
+
);
|
|
8780
|
+
try {
|
|
8781
|
+
fs31.writeFileSync(temporary, `${JSON.stringify(owner)}
|
|
8782
|
+
`, {
|
|
8783
|
+
encoding: "utf8",
|
|
8784
|
+
flag: "wx",
|
|
8785
|
+
mode: 384
|
|
8786
|
+
});
|
|
8787
|
+
validateRegularPrivateFile(temporary, "temporary owner record");
|
|
8788
|
+
assertTransitionGuard(target, guard);
|
|
8789
|
+
fs31.renameSync(temporary, target.ownerFile);
|
|
8790
|
+
} catch (error) {
|
|
8791
|
+
try {
|
|
8792
|
+
assertTransitionGuard(target, guard);
|
|
8793
|
+
if (fs31.existsSync(temporary)) {
|
|
8794
|
+
validateRegularPrivateFile(temporary, "temporary owner record");
|
|
8795
|
+
fs31.unlinkSync(temporary);
|
|
8796
|
+
}
|
|
8797
|
+
} catch {
|
|
8798
|
+
guard.poisoned = true;
|
|
8799
|
+
}
|
|
8800
|
+
throw unknown(`cannot update owner for ${target.canonicalOutDir}`, error);
|
|
8801
|
+
}
|
|
8802
|
+
}
|
|
8803
|
+
function createOwner(target, kind, ownerPid, now) {
|
|
8804
|
+
const inspection = inspectProcess(ownerPid);
|
|
8805
|
+
if (inspection.state !== "alive") {
|
|
8806
|
+
throw unknown(`cannot verify lease owner process ${ownerPid}`);
|
|
8807
|
+
}
|
|
8808
|
+
return {
|
|
8809
|
+
version: OUTPUT_LEASE_PROTOCOL_VERSION,
|
|
8810
|
+
kind,
|
|
8811
|
+
key: target.key,
|
|
8812
|
+
canonicalRoot: target.canonicalRoot,
|
|
8813
|
+
canonicalOutDir: target.canonicalOutDir,
|
|
8814
|
+
generation: crypto.randomBytes(32).toString("base64url"),
|
|
8815
|
+
token: crypto.randomBytes(32).toString("base64url"),
|
|
8816
|
+
pid: ownerPid,
|
|
8817
|
+
processStartIdentity: inspection.startIdentity,
|
|
8818
|
+
createdAt: now,
|
|
8819
|
+
heartbeatAt: now
|
|
8820
|
+
};
|
|
8821
|
+
}
|
|
8822
|
+
function serializeInheritedToken(owner) {
|
|
8823
|
+
return Buffer.from(
|
|
8824
|
+
JSON.stringify({
|
|
8825
|
+
version: OUTPUT_LEASE_PROTOCOL_VERSION,
|
|
8826
|
+
key: owner.key,
|
|
8827
|
+
generation: owner.generation,
|
|
8828
|
+
token: owner.token
|
|
8829
|
+
}),
|
|
8830
|
+
"utf8"
|
|
8831
|
+
).toString("base64url");
|
|
8832
|
+
}
|
|
8833
|
+
function parseInheritedToken(value) {
|
|
8834
|
+
let parsed;
|
|
8835
|
+
try {
|
|
8836
|
+
parsed = JSON.parse(
|
|
8837
|
+
Buffer.from(value, "base64url").toString("utf8")
|
|
8838
|
+
);
|
|
8839
|
+
} catch (error) {
|
|
8840
|
+
throw unknown("malformed inherited output lease token", error);
|
|
8841
|
+
}
|
|
8842
|
+
if (!parsed || typeof parsed !== "object") {
|
|
8843
|
+
throw unknown("malformed inherited output lease token");
|
|
8844
|
+
}
|
|
8845
|
+
const token = parsed;
|
|
8846
|
+
if (token.version !== OUTPUT_LEASE_PROTOCOL_VERSION || typeof token.key !== "string" || !KEY_PATTERN.test(token.key) || typeof token.generation !== "string" || !TOKEN_PATTERN.test(token.generation) || typeof token.token !== "string" || !TOKEN_PATTERN.test(token.token)) {
|
|
8847
|
+
throw unknown("malformed inherited output lease token");
|
|
8848
|
+
}
|
|
8849
|
+
return {
|
|
8850
|
+
version: token.version,
|
|
8851
|
+
key: token.key,
|
|
8852
|
+
generation: token.generation,
|
|
8853
|
+
token: token.token
|
|
8854
|
+
};
|
|
8855
|
+
}
|
|
8856
|
+
function verifyOwnerIsCurrent(owner) {
|
|
8857
|
+
const inspection = inspectProcess(owner.pid);
|
|
8858
|
+
return inspection.state === "alive" && inspection.startIdentity === owner.processStartIdentity;
|
|
8859
|
+
}
|
|
8860
|
+
function verifyInheritedGuarded(target, kind, encoded, guard) {
|
|
8861
|
+
const inherited = parseInheritedToken(encoded);
|
|
8862
|
+
if (inherited.key !== target.key) return null;
|
|
8863
|
+
assertTransitionGuard(target, guard);
|
|
8864
|
+
const owner = readOwner(target);
|
|
8865
|
+
if (owner.generation !== inherited.generation || owner.token !== inherited.token || owner.kind !== kind) {
|
|
8866
|
+
throw conflict(target, owner);
|
|
8867
|
+
}
|
|
8868
|
+
if (owner.canonicalRoot !== target.canonicalRoot) {
|
|
8869
|
+
throw unknown(
|
|
8870
|
+
`inherited owner root does not match ${target.canonicalRoot}`
|
|
8871
|
+
);
|
|
8872
|
+
}
|
|
8873
|
+
if (!verifyOwnerIsCurrent(owner)) {
|
|
8874
|
+
throw unknown(
|
|
8875
|
+
`inherited owner is not current for ${target.canonicalOutDir}`
|
|
8876
|
+
);
|
|
8877
|
+
}
|
|
8878
|
+
if (!isProcessOrDescendant(process.pid, owner.pid)) {
|
|
8879
|
+
throw unknown(
|
|
8880
|
+
`inherited token is not in the owner process tree for ${target.canonicalOutDir}`
|
|
8881
|
+
);
|
|
8882
|
+
}
|
|
8883
|
+
return owner;
|
|
8884
|
+
}
|
|
8885
|
+
function ownerState(owner, now, staleAfterMs) {
|
|
8886
|
+
const heartbeatAge = now - owner.heartbeatAt;
|
|
8887
|
+
if (!Number.isFinite(heartbeatAge) || heartbeatAge < -staleAfterMs) {
|
|
8888
|
+
return "unknown";
|
|
8889
|
+
}
|
|
8890
|
+
const inspection = inspectProcess(owner.pid);
|
|
8891
|
+
if (inspection.state === "alive" && inspection.startIdentity === owner.processStartIdentity) {
|
|
8892
|
+
return "active";
|
|
8893
|
+
}
|
|
8894
|
+
if (heartbeatAge <= staleAfterMs) return "unknown";
|
|
8895
|
+
if (inspection.state === "dead") return "stale";
|
|
8896
|
+
if (inspection.state === "alive" && inspection.startIdentity !== owner.processStartIdentity) {
|
|
8897
|
+
return "stale";
|
|
8898
|
+
}
|
|
8899
|
+
return "unknown";
|
|
8900
|
+
}
|
|
8901
|
+
function safeRemoveLeaseDirectory(target, directory, guard) {
|
|
8902
|
+
assertTransitionGuard(target, guard);
|
|
8903
|
+
if (path26.dirname(directory) !== target.lockRoot) {
|
|
8904
|
+
throw unknown("refusing to remove lease path outside trusted root");
|
|
8905
|
+
}
|
|
8906
|
+
validatePrivateDirectory(directory, "retired lease generation");
|
|
8907
|
+
for (const entry of fs31.readdirSync(directory)) {
|
|
8908
|
+
if (entry !== OWNER_FILE && !TEMP_OWNER_PATTERN.test(entry)) {
|
|
8909
|
+
throw unknown(`unexpected entry in retired lease generation ${entry}`);
|
|
8910
|
+
}
|
|
8911
|
+
const file = path26.join(directory, entry);
|
|
8912
|
+
validateRegularPrivateFile(file, "retired lease file");
|
|
8913
|
+
fs31.unlinkSync(file);
|
|
8914
|
+
}
|
|
8915
|
+
fs31.rmdirSync(directory);
|
|
8916
|
+
}
|
|
8917
|
+
function retireStaleGeneration(target, expected, guard) {
|
|
8918
|
+
assertTransitionGuard(target, guard);
|
|
8919
|
+
const current = readOwner(target);
|
|
8920
|
+
if (!sameGeneration(current, expected)) throw conflict(target, current);
|
|
8921
|
+
const retiredDir = path26.join(
|
|
8922
|
+
target.lockRoot,
|
|
8923
|
+
`${target.key}.stale-${expected.generation.slice(0, 12)}-${crypto.randomBytes(8).toString("hex")}`
|
|
8924
|
+
);
|
|
8925
|
+
try {
|
|
8926
|
+
fs31.renameSync(target.lockDir, retiredDir);
|
|
8927
|
+
guard.poisoned = true;
|
|
8928
|
+
const moved = readOwnerFromDirectory(target, retiredDir);
|
|
8929
|
+
if (!sameGeneration(moved, expected)) {
|
|
8930
|
+
throw unknown(`owner changed while retiring ${target.canonicalOutDir}`);
|
|
8931
|
+
}
|
|
8932
|
+
safeRemoveLeaseDirectory(target, retiredDir, guard);
|
|
8933
|
+
guard.poisoned = false;
|
|
8934
|
+
} catch (error) {
|
|
8935
|
+
if (error instanceof OutputLeaseProtocolError) throw error;
|
|
8936
|
+
throw unknown(
|
|
8937
|
+
`cannot atomically retire stale owner for ${target.canonicalOutDir}`,
|
|
8938
|
+
error
|
|
8939
|
+
);
|
|
8940
|
+
}
|
|
8941
|
+
}
|
|
8942
|
+
function releaseGenerationGuarded(target, expected, guard) {
|
|
8943
|
+
assertTransitionGuard(target, guard);
|
|
8944
|
+
if (!fs31.existsSync(target.lockDir)) return false;
|
|
8945
|
+
const current = readOwner(target);
|
|
8946
|
+
if (!sameGeneration(current, expected)) return false;
|
|
8947
|
+
const releasedDir = path26.join(
|
|
8948
|
+
target.lockRoot,
|
|
8949
|
+
`${target.key}.released-${expected.generation.slice(0, 12)}-${crypto.randomBytes(8).toString("hex")}`
|
|
8950
|
+
);
|
|
8951
|
+
try {
|
|
8952
|
+
assertTransitionGuard(target, guard);
|
|
8953
|
+
const exact = readOwner(target);
|
|
8954
|
+
if (!sameGeneration(exact, expected)) return false;
|
|
8955
|
+
fs31.renameSync(target.lockDir, releasedDir);
|
|
8956
|
+
guard.poisoned = true;
|
|
8957
|
+
const moved = readOwnerFromDirectory(target, releasedDir);
|
|
8958
|
+
if (!sameGeneration(moved, expected)) {
|
|
8959
|
+
throw unknown(`owner changed while releasing ${target.canonicalOutDir}`);
|
|
8960
|
+
}
|
|
8961
|
+
safeRemoveLeaseDirectory(target, releasedDir, guard);
|
|
8962
|
+
guard.poisoned = false;
|
|
8963
|
+
return true;
|
|
8964
|
+
} catch (error) {
|
|
8965
|
+
if (error instanceof OutputLeaseProtocolError) throw error;
|
|
8966
|
+
throw unknown(`cannot release owner for ${target.canonicalOutDir}`, error);
|
|
8967
|
+
}
|
|
8968
|
+
}
|
|
8969
|
+
function releaseByGeneration(target, expected) {
|
|
8970
|
+
return withTransitionGuard(
|
|
8971
|
+
target,
|
|
8972
|
+
(guard) => releaseGenerationGuarded(target, expected, guard)
|
|
8973
|
+
);
|
|
8974
|
+
}
|
|
8975
|
+
function refreshGenerationGuarded(target, expected, now, guard) {
|
|
8976
|
+
assertTransitionGuard(target, guard);
|
|
8977
|
+
const current = readOwner(target);
|
|
8978
|
+
if (!sameGeneration(current, expected)) {
|
|
8979
|
+
throw unknown(`owner changed while refreshing ${target.canonicalOutDir}`);
|
|
8980
|
+
}
|
|
8981
|
+
writeOwnerAtomically(target, { ...current, heartbeatAt: now }, guard);
|
|
8982
|
+
}
|
|
8983
|
+
function refreshByGeneration(target, expected, now) {
|
|
8984
|
+
withTransitionGuard(
|
|
8985
|
+
target,
|
|
8986
|
+
(guard) => refreshGenerationGuarded(target, expected, now, guard)
|
|
8987
|
+
);
|
|
8988
|
+
}
|
|
8989
|
+
function createLeaseHandle(target, owner, reentrant, options) {
|
|
8990
|
+
const now = options.now ?? Date.now;
|
|
8991
|
+
const manageLifetime = options.manageLifetime !== false && !reentrant;
|
|
8992
|
+
const heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
8993
|
+
const generation = {
|
|
8994
|
+
generation: owner.generation,
|
|
8995
|
+
token: owner.token
|
|
8996
|
+
};
|
|
8997
|
+
let released = false;
|
|
8998
|
+
let heartbeat;
|
|
8999
|
+
const signalHandlers = /* @__PURE__ */ new Map();
|
|
9000
|
+
const release = () => {
|
|
9001
|
+
if (released || reentrant) return false;
|
|
9002
|
+
released = true;
|
|
9003
|
+
if (heartbeat) clearInterval(heartbeat);
|
|
9004
|
+
process.removeListener("exit", onExit);
|
|
9005
|
+
for (const [signal, handler] of signalHandlers) {
|
|
9006
|
+
process.removeListener(signal, handler);
|
|
9007
|
+
}
|
|
9008
|
+
return releaseByGeneration(target, generation);
|
|
9009
|
+
};
|
|
9010
|
+
const onExit = () => {
|
|
9011
|
+
try {
|
|
9012
|
+
releaseByGeneration(target, generation);
|
|
9013
|
+
} catch {
|
|
9014
|
+
}
|
|
9015
|
+
};
|
|
9016
|
+
if (manageLifetime) {
|
|
9017
|
+
heartbeat = setInterval(() => {
|
|
9018
|
+
try {
|
|
9019
|
+
refreshByGeneration(target, generation, now());
|
|
9020
|
+
} catch {
|
|
9021
|
+
setImmediate(() => process.exit(1));
|
|
9022
|
+
}
|
|
9023
|
+
}, heartbeatIntervalMs);
|
|
9024
|
+
heartbeat.unref();
|
|
9025
|
+
process.once("exit", onExit);
|
|
9026
|
+
const signalExitCodes = {
|
|
9027
|
+
SIGHUP: 129,
|
|
9028
|
+
SIGINT: 130,
|
|
9029
|
+
SIGTERM: 143
|
|
9030
|
+
};
|
|
9031
|
+
for (const signal of Object.keys(signalExitCodes)) {
|
|
9032
|
+
const handler = () => {
|
|
9033
|
+
try {
|
|
9034
|
+
release();
|
|
9035
|
+
} finally {
|
|
9036
|
+
setImmediate(() => process.exit(signalExitCodes[signal] ?? 1));
|
|
9037
|
+
}
|
|
9038
|
+
};
|
|
9039
|
+
signalHandlers.set(signal, handler);
|
|
9040
|
+
process.once(signal, handler);
|
|
9041
|
+
}
|
|
9042
|
+
}
|
|
9043
|
+
return {
|
|
9044
|
+
target,
|
|
9045
|
+
owner,
|
|
9046
|
+
inheritedToken: serializeInheritedToken(owner),
|
|
9047
|
+
reentrant,
|
|
9048
|
+
refresh() {
|
|
9049
|
+
if (!reentrant && !released) {
|
|
9050
|
+
refreshByGeneration(target, generation, now());
|
|
9051
|
+
}
|
|
9052
|
+
},
|
|
9053
|
+
release
|
|
9054
|
+
};
|
|
9055
|
+
}
|
|
9056
|
+
function acquireOutputLease(options) {
|
|
9057
|
+
const now = options.now ?? Date.now;
|
|
9058
|
+
const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
9059
|
+
const env = options.env ?? process.env;
|
|
9060
|
+
const target = resolveOutputLeaseTarget(
|
|
9061
|
+
options.rootDir,
|
|
9062
|
+
options.outDir,
|
|
9063
|
+
options.lockRoot
|
|
9064
|
+
);
|
|
9065
|
+
return withTransitionGuard(target, (guard) => {
|
|
9066
|
+
const inheritedValue = env[OUTPUT_LEASE_ENV];
|
|
9067
|
+
if (inheritedValue) {
|
|
9068
|
+
const inheritedOwner = verifyInheritedGuarded(
|
|
9069
|
+
target,
|
|
9070
|
+
options.kind,
|
|
9071
|
+
inheritedValue,
|
|
9072
|
+
guard
|
|
9073
|
+
);
|
|
9074
|
+
if (inheritedOwner) {
|
|
9075
|
+
return createLeaseHandle(target, inheritedOwner, true, options);
|
|
9076
|
+
}
|
|
9077
|
+
}
|
|
9078
|
+
const ownerPid = options.ownerPid ?? process.pid;
|
|
9079
|
+
if (ownerPid !== process.pid) {
|
|
9080
|
+
if (options.manageLifetime !== false) {
|
|
9081
|
+
throw unknown("an external owner requires externally managed lifetime");
|
|
9082
|
+
}
|
|
9083
|
+
if (!isProcessOrDescendant(process.pid, ownerPid)) {
|
|
9084
|
+
throw unknown(`process ${ownerPid} is not an ancestor of the acquirer`);
|
|
9085
|
+
}
|
|
9086
|
+
}
|
|
9087
|
+
if (fs31.existsSync(target.lockDir)) {
|
|
9088
|
+
const existing = readOwner(target);
|
|
9089
|
+
const state = ownerState(existing, now(), staleAfterMs);
|
|
9090
|
+
if (state === "active") throw conflict(target, existing);
|
|
9091
|
+
if (state === "unknown") {
|
|
9092
|
+
throw unknown(`cannot prove owner state for ${target.canonicalOutDir}`);
|
|
9093
|
+
}
|
|
9094
|
+
retireStaleGeneration(target, existing, guard);
|
|
9095
|
+
}
|
|
9096
|
+
assertTransitionGuard(target, guard);
|
|
9097
|
+
try {
|
|
9098
|
+
fs31.mkdirSync(target.lockDir, { mode: 448 });
|
|
9099
|
+
validatePrivateDirectory(target.lockDir, "lease generation");
|
|
9100
|
+
const owner = createOwner(target, options.kind, ownerPid, now());
|
|
9101
|
+
fs31.writeFileSync(target.ownerFile, `${JSON.stringify(owner)}
|
|
9102
|
+
`, {
|
|
9103
|
+
encoding: "utf8",
|
|
9104
|
+
flag: "wx",
|
|
9105
|
+
mode: 384
|
|
9106
|
+
});
|
|
9107
|
+
validateRegularPrivateFile(target.ownerFile, "owner record");
|
|
9108
|
+
return createLeaseHandle(target, owner, false, options);
|
|
9109
|
+
} catch (error) {
|
|
9110
|
+
if (error instanceof OutputLeaseProtocolError) throw error;
|
|
9111
|
+
throw unknown(`cannot create owner for ${target.canonicalOutDir}`, error);
|
|
9112
|
+
}
|
|
9113
|
+
});
|
|
9114
|
+
}
|
|
9115
|
+
function targetAndEncoded(options) {
|
|
9116
|
+
const encoded = (options.env ?? process.env)[OUTPUT_LEASE_ENV];
|
|
9117
|
+
if (!encoded) throw unknown("inherited output lease token is missing");
|
|
9118
|
+
return {
|
|
9119
|
+
target: resolveOutputLeaseTarget(
|
|
9120
|
+
options.rootDir,
|
|
9121
|
+
options.outDir,
|
|
9122
|
+
options.lockRoot
|
|
9123
|
+
),
|
|
9124
|
+
encoded
|
|
9125
|
+
};
|
|
9126
|
+
}
|
|
9127
|
+
function verifyInheritedOutputLease(options) {
|
|
9128
|
+
const { target, encoded } = targetAndEncoded(options);
|
|
9129
|
+
return withTransitionGuard(target, (guard) => {
|
|
9130
|
+
const owner = verifyInheritedGuarded(target, options.kind, encoded, guard);
|
|
9131
|
+
if (!owner) {
|
|
9132
|
+
throw unknown("inherited token targets a different output directory");
|
|
9133
|
+
}
|
|
9134
|
+
return owner;
|
|
9135
|
+
});
|
|
9136
|
+
}
|
|
9137
|
+
function refreshInheritedOutputLease(options) {
|
|
9138
|
+
const { target, encoded } = targetAndEncoded(options);
|
|
9139
|
+
withTransitionGuard(target, (guard) => {
|
|
9140
|
+
const owner = verifyInheritedGuarded(target, options.kind, encoded, guard);
|
|
9141
|
+
if (!owner) {
|
|
9142
|
+
throw unknown("inherited token targets a different output directory");
|
|
9143
|
+
}
|
|
9144
|
+
refreshGenerationGuarded(
|
|
9145
|
+
target,
|
|
9146
|
+
{ generation: owner.generation, token: owner.token },
|
|
9147
|
+
(options.now ?? Date.now)(),
|
|
9148
|
+
guard
|
|
9149
|
+
);
|
|
9150
|
+
});
|
|
9151
|
+
}
|
|
9152
|
+
function poisonInheritedOutputLease(options) {
|
|
9153
|
+
const { target, encoded } = targetAndEncoded(options);
|
|
9154
|
+
withTransitionGuard(target, (guard) => {
|
|
9155
|
+
const owner = verifyInheritedGuarded(target, options.kind, encoded, guard);
|
|
9156
|
+
if (!owner) {
|
|
9157
|
+
throw unknown("inherited token targets a different output directory");
|
|
9158
|
+
}
|
|
9159
|
+
guard.poisoned = true;
|
|
9160
|
+
});
|
|
9161
|
+
}
|
|
9162
|
+
function releaseInheritedOutputLease(options) {
|
|
9163
|
+
const env = options.env ?? process.env;
|
|
9164
|
+
const encoded = env[OUTPUT_LEASE_ENV];
|
|
9165
|
+
if (!encoded) return false;
|
|
9166
|
+
const target = resolveOutputLeaseTarget(
|
|
9167
|
+
options.rootDir,
|
|
9168
|
+
options.outDir,
|
|
9169
|
+
options.lockRoot
|
|
9170
|
+
);
|
|
9171
|
+
return withTransitionGuard(target, (guard) => {
|
|
9172
|
+
const owner = verifyInheritedGuarded(target, options.kind, encoded, guard);
|
|
9173
|
+
if (!owner) return false;
|
|
9174
|
+
return releaseGenerationGuarded(
|
|
9175
|
+
target,
|
|
9176
|
+
{ generation: owner.generation, token: owner.token },
|
|
9177
|
+
guard
|
|
9178
|
+
);
|
|
9179
|
+
});
|
|
9180
|
+
}
|
|
9181
|
+
|
|
9182
|
+
// src/commands/output-lease/index.ts
|
|
9183
|
+
function leaseOptions(options) {
|
|
9184
|
+
return {
|
|
9185
|
+
rootDir: options.root,
|
|
9186
|
+
outDir: options.outDir,
|
|
9187
|
+
kind: "production"
|
|
9188
|
+
};
|
|
9189
|
+
}
|
|
9190
|
+
function parseOwnerPid(value) {
|
|
9191
|
+
const pid = Number(value);
|
|
9192
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) {
|
|
9193
|
+
throw new Error("MIAODA_PREVIEW_STATE_UNKNOWN: invalid wrapper owner PID");
|
|
9194
|
+
}
|
|
9195
|
+
return pid;
|
|
9196
|
+
}
|
|
9197
|
+
function acquireWrapperOutputLease(options) {
|
|
9198
|
+
const lease = acquireOutputLease({
|
|
9199
|
+
...leaseOptions(options),
|
|
9200
|
+
ownerPid: parseOwnerPid(options.ownerPid),
|
|
9201
|
+
manageLifetime: false,
|
|
9202
|
+
// Only Vite children may reenter. A second wrapper must contend normally.
|
|
9203
|
+
env: {}
|
|
9204
|
+
});
|
|
9205
|
+
return lease.inheritedToken;
|
|
9206
|
+
}
|
|
9207
|
+
function verifyWrapperOutputLease(options) {
|
|
9208
|
+
verifyInheritedOutputLease(leaseOptions(options));
|
|
9209
|
+
}
|
|
9210
|
+
function releaseWrapperOutputLease(options) {
|
|
9211
|
+
return releaseInheritedOutputLease(leaseOptions(options));
|
|
9212
|
+
}
|
|
9213
|
+
function poisonWrapperOutputLease(options) {
|
|
9214
|
+
poisonInheritedOutputLease(leaseOptions(options));
|
|
9215
|
+
}
|
|
9216
|
+
function startWrapperOutputLeaseHeartbeat(options, intervalMs = 2e3) {
|
|
9217
|
+
refreshInheritedOutputLease(leaseOptions(options));
|
|
9218
|
+
const heartbeat = setInterval(() => {
|
|
9219
|
+
try {
|
|
9220
|
+
refreshInheritedOutputLease(leaseOptions(options));
|
|
9221
|
+
} catch (error) {
|
|
9222
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
9223
|
+
process.exit(1);
|
|
9224
|
+
}
|
|
9225
|
+
}, intervalMs);
|
|
9226
|
+
return heartbeat;
|
|
9227
|
+
}
|
|
9228
|
+
function addTargetOptions(command) {
|
|
9229
|
+
return command.requiredOption("--root <dir>", "Project root").requiredOption("--out-dir <dir>", "Managed output directory");
|
|
9230
|
+
}
|
|
9231
|
+
var acquireCommand = {
|
|
9232
|
+
name: "acquire",
|
|
9233
|
+
description: "Acquire a private production output lease for the build wrapper",
|
|
9234
|
+
register(program) {
|
|
9235
|
+
addTargetOptions(program.command(this.name)).requiredOption("--owner-pid <pid>", "PID of the wrapper shell").action((options) => {
|
|
9236
|
+
process.stdout.write(`${acquireWrapperOutputLease(options)}
|
|
9237
|
+
`);
|
|
9238
|
+
});
|
|
9239
|
+
}
|
|
9240
|
+
};
|
|
9241
|
+
var verifyCommand = {
|
|
9242
|
+
name: "verify",
|
|
9243
|
+
description: "Verify the inherited wrapper lease",
|
|
9244
|
+
register(program) {
|
|
9245
|
+
addTargetOptions(program.command(this.name)).action(
|
|
9246
|
+
(options) => {
|
|
9247
|
+
verifyWrapperOutputLease(options);
|
|
9248
|
+
}
|
|
9249
|
+
);
|
|
9250
|
+
}
|
|
9251
|
+
};
|
|
9252
|
+
var heartbeatCommand = {
|
|
9253
|
+
name: "heartbeat",
|
|
9254
|
+
description: "Maintain the inherited wrapper lease",
|
|
9255
|
+
register(program) {
|
|
9256
|
+
addTargetOptions(program.command(this.name)).action(
|
|
9257
|
+
(options) => {
|
|
9258
|
+
startWrapperOutputLeaseHeartbeat(options);
|
|
9259
|
+
}
|
|
9260
|
+
);
|
|
9261
|
+
}
|
|
9262
|
+
};
|
|
9263
|
+
var poisonCommand = {
|
|
9264
|
+
name: "poison",
|
|
9265
|
+
description: "Durably quarantine an inherited wrapper lease",
|
|
9266
|
+
register(program) {
|
|
9267
|
+
addTargetOptions(program.command(this.name)).action(
|
|
9268
|
+
(options) => {
|
|
9269
|
+
poisonWrapperOutputLease(options);
|
|
9270
|
+
}
|
|
9271
|
+
);
|
|
9272
|
+
}
|
|
9273
|
+
};
|
|
9274
|
+
var releaseCommand = {
|
|
9275
|
+
name: "release",
|
|
9276
|
+
description: "Release the inherited wrapper lease",
|
|
9277
|
+
register(program) {
|
|
9278
|
+
addTargetOptions(program.command(this.name)).action(
|
|
9279
|
+
(options) => {
|
|
9280
|
+
if (!releaseWrapperOutputLease(options)) {
|
|
9281
|
+
throw new Error(
|
|
9282
|
+
`${OUTPUT_LEASE_ENV}: the current token does not own this lease`
|
|
9283
|
+
);
|
|
9284
|
+
}
|
|
9285
|
+
}
|
|
9286
|
+
);
|
|
9287
|
+
}
|
|
9288
|
+
};
|
|
9289
|
+
var outputLeaseCommandGroup = {
|
|
9290
|
+
name: "output-lease",
|
|
9291
|
+
description: "Private build output ownership protocol",
|
|
9292
|
+
commands: [
|
|
9293
|
+
acquireCommand,
|
|
9294
|
+
verifyCommand,
|
|
9295
|
+
heartbeatCommand,
|
|
9296
|
+
poisonCommand,
|
|
9297
|
+
releaseCommand
|
|
9298
|
+
]
|
|
9299
|
+
};
|
|
9300
|
+
|
|
8432
9301
|
// src/commands/index.ts
|
|
8433
9302
|
var commands = [
|
|
8434
9303
|
genDbSchemaCommand,
|
|
@@ -8439,18 +9308,19 @@ var commands = [
|
|
|
8439
9308
|
componentCommandGroup,
|
|
8440
9309
|
migrationCommand,
|
|
8441
9310
|
readLogsCommand,
|
|
8442
|
-
buildCommandGroup
|
|
9311
|
+
buildCommandGroup,
|
|
9312
|
+
outputLeaseCommandGroup
|
|
8443
9313
|
];
|
|
8444
9314
|
|
|
8445
9315
|
// src/index.ts
|
|
8446
9316
|
for (const filename of [".env.local", ".env"]) {
|
|
8447
|
-
const envPath =
|
|
8448
|
-
if (
|
|
9317
|
+
const envPath = path27.join(process.cwd(), filename);
|
|
9318
|
+
if (fs32.existsSync(envPath)) {
|
|
8449
9319
|
dotenvConfig({ path: envPath });
|
|
8450
9320
|
}
|
|
8451
9321
|
}
|
|
8452
|
-
var __dirname =
|
|
8453
|
-
var pkg = JSON.parse(
|
|
9322
|
+
var __dirname = path27.dirname(fileURLToPath5(import.meta.url));
|
|
9323
|
+
var pkg = JSON.parse(fs32.readFileSync(path27.join(__dirname, "../package.json"), "utf-8"));
|
|
8454
9324
|
var cli = new FullstackCLI(pkg.version);
|
|
8455
9325
|
cli.useAll(commands);
|
|
8456
9326
|
cli.run();
|