facult 2.13.0 → 2.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/fclt.cjs +103 -56
- package/package.json +1 -1
package/bin/fclt.cjs
CHANGED
|
@@ -15,6 +15,9 @@ const REPO_NAME = "fclt";
|
|
|
15
15
|
const PACKAGE_NAME = "facult";
|
|
16
16
|
const DOWNLOAD_RETRIES = 12;
|
|
17
17
|
const DOWNLOAD_RETRY_DELAY_MS = 5000;
|
|
18
|
+
const ACTIVE_RUNTIME_WAIT_MS = 10_000;
|
|
19
|
+
const ACTIVE_RUNTIME_WAIT_INTERVAL_MS = 100;
|
|
20
|
+
const STALE_RUNTIME_TEMP_MS = 10 * 60 * 1000;
|
|
18
21
|
|
|
19
22
|
function isHelpLikeArgs(args) {
|
|
20
23
|
return (
|
|
@@ -80,70 +83,75 @@ async function main() {
|
|
|
80
83
|
let installedBinaryThisRun = false;
|
|
81
84
|
|
|
82
85
|
if (!(await fileExists(binaryPath))) {
|
|
83
|
-
|
|
84
|
-
const hasSourceFallback = await canUseSourceFallback(sourceEntry);
|
|
85
|
-
const incompleteCache = await hasIncompleteRuntimeCache({
|
|
86
|
+
await removeStaleRuntimeTemps({
|
|
86
87
|
installDir,
|
|
87
88
|
binaryName,
|
|
89
|
+
maxAgeMs: STALE_RUNTIME_TEMP_MS,
|
|
88
90
|
});
|
|
91
|
+
const packageManager = detectPackageManager();
|
|
92
|
+
const hasSourceFallback = await canUseSourceFallback(sourceEntry);
|
|
89
93
|
|
|
90
|
-
if (
|
|
91
|
-
await removeIncompleteRuntimeTemps({ installDir, binaryName });
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (hasSourceFallback && (incompleteCache || isHelpLikeArgs(args))) {
|
|
94
|
+
if (hasSourceFallback && isHelpLikeArgs(args)) {
|
|
95
95
|
return runSourceFallback({
|
|
96
96
|
sourceEntry,
|
|
97
97
|
version,
|
|
98
98
|
packageManager,
|
|
99
|
-
reason: new Error(
|
|
100
|
-
incompleteCache
|
|
101
|
-
? "incomplete cached runtime download"
|
|
102
|
-
: "runtime binary missing for help-like command"
|
|
103
|
-
),
|
|
99
|
+
reason: new Error("runtime binary missing for help-like command"),
|
|
104
100
|
});
|
|
105
101
|
}
|
|
106
102
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
await fsp.mkdir(installDir, { recursive: true });
|
|
114
|
-
await downloadWithRetry(url, tmpPath, {
|
|
115
|
-
attempts: DOWNLOAD_RETRIES,
|
|
116
|
-
delayMs: DOWNLOAD_RETRY_DELAY_MS,
|
|
103
|
+
if (await hasIncompleteRuntimeCache({ installDir, binaryName })) {
|
|
104
|
+
await waitForFile(binaryPath, {
|
|
105
|
+
timeoutMs: ACTIVE_RUNTIME_WAIT_MS,
|
|
106
|
+
intervalMs: ACTIVE_RUNTIME_WAIT_INTERVAL_MS,
|
|
117
107
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (await fileExists(binaryPath)) {
|
|
111
|
+
// Another concurrent launcher finished the runtime install while this
|
|
112
|
+
// process was waiting.
|
|
113
|
+
} else {
|
|
114
|
+
const tag = `v${version}`;
|
|
115
|
+
const assetName = `${PACKAGE_NAME}-${version}-${resolved.platform}-${resolved.arch}${resolved.ext}`;
|
|
116
|
+
const url = `https://github.com/${REPO_OWNER}/${REPO_NAME}/releases/download/${tag}/${assetName}`;
|
|
117
|
+
const tmpPath = `${binaryPath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
await fsp.mkdir(installDir, { recursive: true });
|
|
121
|
+
await downloadWithRetry(url, tmpPath, {
|
|
122
|
+
attempts: DOWNLOAD_RETRIES,
|
|
123
|
+
delayMs: DOWNLOAD_RETRY_DELAY_MS,
|
|
131
124
|
});
|
|
125
|
+
if (resolved.platform !== "windows") {
|
|
126
|
+
await fsp.chmod(tmpPath, 0o755);
|
|
127
|
+
}
|
|
128
|
+
await installDownloadedRuntime(tmpPath, binaryPath);
|
|
129
|
+
installedBinaryThisRun = true;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
await safeUnlink(tmpPath);
|
|
132
|
+
if (await canUseSourceFallback(sourceEntry)) {
|
|
133
|
+
return runSourceFallback({
|
|
134
|
+
sourceEntry,
|
|
135
|
+
version,
|
|
136
|
+
packageManager: detectPackageManager(),
|
|
137
|
+
reason: error,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const message =
|
|
141
|
+
error instanceof Error ? error.message : String(error ?? "");
|
|
142
|
+
console.error(
|
|
143
|
+
[
|
|
144
|
+
"Unable to download the fclt binary for this platform.",
|
|
145
|
+
`Expected asset: ${assetName}`,
|
|
146
|
+
`URL: ${url}`,
|
|
147
|
+
`Reason: ${message}`,
|
|
148
|
+
"",
|
|
149
|
+
"Try installing directly from releases:",
|
|
150
|
+
"https://github.com/hack-dance/fclt/releases",
|
|
151
|
+
].join("\n")
|
|
152
|
+
);
|
|
153
|
+
process.exit(1);
|
|
132
154
|
}
|
|
133
|
-
const message =
|
|
134
|
-
error instanceof Error ? error.message : String(error ?? "");
|
|
135
|
-
console.error(
|
|
136
|
-
[
|
|
137
|
-
"Unable to download the fclt binary for this platform.",
|
|
138
|
-
`Expected asset: ${assetName}`,
|
|
139
|
-
`URL: ${url}`,
|
|
140
|
-
`Reason: ${message}`,
|
|
141
|
-
"",
|
|
142
|
-
"Try installing directly from releases:",
|
|
143
|
-
"https://github.com/hack-dance/fclt/releases",
|
|
144
|
-
].join("\n")
|
|
145
|
-
);
|
|
146
|
-
process.exit(1);
|
|
147
155
|
}
|
|
148
156
|
}
|
|
149
157
|
|
|
@@ -367,19 +375,58 @@ async function hasIncompleteRuntimeCache({ installDir, binaryName }) {
|
|
|
367
375
|
}
|
|
368
376
|
}
|
|
369
377
|
|
|
370
|
-
async function
|
|
378
|
+
async function removeStaleRuntimeTemps({ installDir, binaryName, maxAgeMs }) {
|
|
371
379
|
try {
|
|
372
380
|
const entries = await fsp.readdir(installDir);
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
381
|
+
const now = Date.now();
|
|
382
|
+
const stalePaths = [];
|
|
383
|
+
for (const entry of entries) {
|
|
384
|
+
if (!entry.startsWith(`${binaryName}.tmp-`)) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const candidate = path.join(installDir, entry);
|
|
388
|
+
try {
|
|
389
|
+
const stats = await fsp.stat(candidate);
|
|
390
|
+
if (now - stats.mtimeMs > maxAgeMs) {
|
|
391
|
+
stalePaths.push(candidate);
|
|
392
|
+
}
|
|
393
|
+
} catch {
|
|
394
|
+
// Ignore temp files that disappeared while scanning.
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
await Promise.all(stalePaths.map((candidate) => safeUnlink(candidate)));
|
|
378
398
|
} catch {
|
|
379
399
|
// Ignore missing runtime dirs while cleaning stale temp files.
|
|
380
400
|
}
|
|
381
401
|
}
|
|
382
402
|
|
|
403
|
+
async function waitForFile(filePath, { timeoutMs, intervalMs }) {
|
|
404
|
+
const deadline = Date.now() + timeoutMs;
|
|
405
|
+
while (Date.now() < deadline) {
|
|
406
|
+
if (await fileExists(filePath)) {
|
|
407
|
+
return true;
|
|
408
|
+
}
|
|
409
|
+
await sleep(intervalMs);
|
|
410
|
+
}
|
|
411
|
+
return await fileExists(filePath);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function installDownloadedRuntime(tmpPath, binaryPath) {
|
|
415
|
+
if (await fileExists(binaryPath)) {
|
|
416
|
+
await safeUnlink(tmpPath);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
await fsp.rename(tmpPath, binaryPath);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
if (await fileExists(binaryPath)) {
|
|
423
|
+
await safeUnlink(tmpPath);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
383
430
|
async function safeUnlink(filePath) {
|
|
384
431
|
try {
|
|
385
432
|
await fsp.unlink(filePath);
|