facult 2.13.2 → 2.13.4
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 +5 -0
- package/docs/reference.md +4 -0
- package/package.json +1 -1
- package/src/self-update.ts +202 -21
package/README.md
CHANGED
|
@@ -100,6 +100,11 @@ fclt self-update
|
|
|
100
100
|
fclt self-update --version 2.12.0
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
+
`self-update` follows the active install mode. It updates release-script binaries
|
|
104
|
+
directly, npm/Bun global installs through their package manager, and
|
|
105
|
+
mise-managed npm installs with `mise use -g --pin npm:facult@<version>`, then
|
|
106
|
+
verifies the active `fclt --version`.
|
|
107
|
+
|
|
103
108
|
## Quick start
|
|
104
109
|
|
|
105
110
|
### 1. Inspect existing AI state
|
package/docs/reference.md
CHANGED
|
@@ -108,6 +108,10 @@ fclt audit [--non-interactive]
|
|
|
108
108
|
fclt self-update
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
+
`self-update` detects release-script, npm/Bun, and mise-managed npm installs.
|
|
112
|
+
For mise installs it updates the global `npm:facult` pin and verifies the
|
|
113
|
+
resolved `fclt` version through mise.
|
|
114
|
+
|
|
111
115
|
Use `--strict-source-trust` when installing or updating remote capability from catalogs.
|
|
112
116
|
|
|
113
117
|
## Root Selection
|
package/package.json
CHANGED
package/src/self-update.ts
CHANGED
|
@@ -19,6 +19,7 @@ type InstallMethod =
|
|
|
19
19
|
| "script-bin"
|
|
20
20
|
| "release-script"
|
|
21
21
|
| "npm-binary-cache"
|
|
22
|
+
| "mise-npm"
|
|
22
23
|
| "unknown";
|
|
23
24
|
|
|
24
25
|
interface InstallState {
|
|
@@ -113,21 +114,27 @@ export function detectInstallMethod(
|
|
|
113
114
|
if (envMethod === "script-dev" || envMethod === "script-bin") {
|
|
114
115
|
return envMethod;
|
|
115
116
|
}
|
|
116
|
-
if (envMethod === "npm-binary-cache") {
|
|
117
|
-
return
|
|
117
|
+
if (envMethod === "npm-binary-cache" || envMethod === "mise-npm") {
|
|
118
|
+
return envMethod;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const exec = context.executablePath ?? process.execPath;
|
|
122
|
+
const home = context.homeDir ?? homedir();
|
|
123
|
+
if (looksLikeMiseNpmFacultExecutable(exec)) {
|
|
124
|
+
return "mise-npm";
|
|
118
125
|
}
|
|
126
|
+
|
|
119
127
|
const raw = state?.method?.trim();
|
|
120
128
|
if (
|
|
121
129
|
raw === "script-dev" ||
|
|
122
130
|
raw === "script-bin" ||
|
|
123
131
|
raw === "release-script" ||
|
|
124
|
-
raw === "npm-binary-cache"
|
|
132
|
+
raw === "npm-binary-cache" ||
|
|
133
|
+
raw === "mise-npm"
|
|
125
134
|
) {
|
|
126
135
|
return raw;
|
|
127
136
|
}
|
|
128
137
|
|
|
129
|
-
const exec = context.executablePath ?? process.execPath;
|
|
130
|
-
const home = context.homeDir ?? homedir();
|
|
131
138
|
const facultBins = [
|
|
132
139
|
join(preferredGlobalFacultStateDir(home), "bin"),
|
|
133
140
|
join(legacyExternalFacultStateDir(home), "bin"),
|
|
@@ -145,6 +152,82 @@ export function detectInstallMethod(
|
|
|
145
152
|
return "unknown";
|
|
146
153
|
}
|
|
147
154
|
|
|
155
|
+
async function detectActiveInstallMethod(
|
|
156
|
+
state: InstallState | null
|
|
157
|
+
): Promise<InstallMethod> {
|
|
158
|
+
const method = detectInstallMethod(state);
|
|
159
|
+
if (method !== "npm-binary-cache" && method !== "unknown") {
|
|
160
|
+
return method;
|
|
161
|
+
}
|
|
162
|
+
if (await activeFcltUsesMiseNpmFacult()) {
|
|
163
|
+
return "mise-npm";
|
|
164
|
+
}
|
|
165
|
+
return method;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function looksLikeMiseNpmFacultExecutable(executablePath: string): boolean {
|
|
169
|
+
const normalized = executablePath.split("\\").join(sep);
|
|
170
|
+
return (
|
|
171
|
+
normalized.includes(`${sep}mise${sep}installs${sep}npm-facult${sep}`) &&
|
|
172
|
+
CLI_BASENAME_PATTERN.test(basename(normalized))
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function looksLikeMiseShim(
|
|
177
|
+
pathValue: string | null | undefined
|
|
178
|
+
): boolean {
|
|
179
|
+
if (!pathValue) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
const normalized = pathValue.split("\\").join(sep);
|
|
183
|
+
return (
|
|
184
|
+
normalized.includes(`${sep}mise${sep}shims${sep}`) &&
|
|
185
|
+
CLI_BASENAME_PATTERN.test(basename(normalized))
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function activeFcltUsesMiseNpmFacult(): Promise<boolean> {
|
|
190
|
+
if (!Bun.which("mise")) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
const fcltPath = Bun.which("fclt");
|
|
194
|
+
if (looksLikeMiseNpmFacultExecutable(fcltPath ?? "")) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
if (await miseHasCurrentFacultTool()) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
if (!looksLikeMiseShim(fcltPath)) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
const proc = Bun.spawn({
|
|
204
|
+
cmd: ["mise", "which", "fclt"],
|
|
205
|
+
stdin: "ignore",
|
|
206
|
+
stdout: "pipe",
|
|
207
|
+
stderr: "ignore",
|
|
208
|
+
env: process.env,
|
|
209
|
+
});
|
|
210
|
+
const [stdout, code] = await Promise.all([
|
|
211
|
+
new Response(proc.stdout).text(),
|
|
212
|
+
proc.exited,
|
|
213
|
+
]);
|
|
214
|
+
if (code !== 0) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
return looksLikeMiseNpmFacultExecutable(stdout.trim());
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function miseHasCurrentFacultTool(): Promise<boolean> {
|
|
221
|
+
const proc = Bun.spawn({
|
|
222
|
+
cmd: ["mise", "current", `npm:${PACKAGE_NAME}`],
|
|
223
|
+
stdin: "ignore",
|
|
224
|
+
stdout: "ignore",
|
|
225
|
+
stderr: "ignore",
|
|
226
|
+
env: process.env,
|
|
227
|
+
});
|
|
228
|
+
return (await proc.exited) === 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
148
231
|
function resolvePlatformTarget(): {
|
|
149
232
|
platform: string;
|
|
150
233
|
arch: string;
|
|
@@ -284,14 +367,17 @@ async function selfUpdateBinary(args: {
|
|
|
284
367
|
console.log(`Path: ${binaryPath}`);
|
|
285
368
|
}
|
|
286
369
|
|
|
287
|
-
type PackageManager = "npm" | "bun";
|
|
370
|
+
type PackageManager = "npm" | "bun" | "mise";
|
|
288
371
|
|
|
289
372
|
function chooseGlobalPackageManager(preferred?: string): PackageManager {
|
|
290
373
|
const forced = process.env.FACULT_INSTALL_PM?.trim();
|
|
291
|
-
if (forced === "npm" || forced === "bun") {
|
|
374
|
+
if (forced === "npm" || forced === "bun" || forced === "mise") {
|
|
292
375
|
return forced;
|
|
293
376
|
}
|
|
294
377
|
|
|
378
|
+
if (preferred === "mise" && Bun.which("mise")) {
|
|
379
|
+
return "mise";
|
|
380
|
+
}
|
|
295
381
|
if (preferred === "npm" && Bun.which("npm")) {
|
|
296
382
|
return "npm";
|
|
297
383
|
}
|
|
@@ -305,25 +391,58 @@ function chooseGlobalPackageManager(preferred?: string): PackageManager {
|
|
|
305
391
|
if (Bun.which("bun")) {
|
|
306
392
|
return "bun";
|
|
307
393
|
}
|
|
394
|
+
if (Bun.which("mise")) {
|
|
395
|
+
return "mise";
|
|
396
|
+
}
|
|
308
397
|
return "npm";
|
|
309
398
|
}
|
|
310
399
|
|
|
400
|
+
export function buildPackageManagerUpdateCommand(args: {
|
|
401
|
+
packageManager: PackageManager;
|
|
402
|
+
version: string;
|
|
403
|
+
}): string[] {
|
|
404
|
+
const installSpec = `${PACKAGE_NAME}@${args.version}`;
|
|
405
|
+
if (args.packageManager === "npm") {
|
|
406
|
+
return ["npm", "install", "-g", installSpec];
|
|
407
|
+
}
|
|
408
|
+
if (args.packageManager === "bun") {
|
|
409
|
+
return ["bun", "add", "-g", installSpec];
|
|
410
|
+
}
|
|
411
|
+
return ["mise", "use", "-g", "--pin", `npm:${PACKAGE_NAME}@${args.version}`];
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function resolvePackageTargetVersion(requestedVersion?: string): Promise<{
|
|
415
|
+
version: string;
|
|
416
|
+
resolvedFromLatest: boolean;
|
|
417
|
+
}> {
|
|
418
|
+
if (requestedVersion && requestedVersion !== "latest") {
|
|
419
|
+
return {
|
|
420
|
+
version: stripTagPrefix(requestedVersion),
|
|
421
|
+
resolvedFromLatest: false,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const tag = await resolveLatestTag();
|
|
425
|
+
return { version: stripTagPrefix(tag), resolvedFromLatest: true };
|
|
426
|
+
}
|
|
427
|
+
|
|
311
428
|
async function selfUpdateViaPackageManager(args: {
|
|
312
429
|
requestedVersion?: string;
|
|
313
430
|
dryRun: boolean;
|
|
314
431
|
preferredPackageManager?: string;
|
|
432
|
+
method?: InstallMethod;
|
|
315
433
|
}) {
|
|
316
|
-
const pm = chooseGlobalPackageManager(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
434
|
+
const pm = chooseGlobalPackageManager(
|
|
435
|
+
args.method === "mise-npm" ? "mise" : args.preferredPackageManager
|
|
436
|
+
);
|
|
437
|
+
const target =
|
|
438
|
+
args.dryRun &&
|
|
439
|
+
(!args.requestedVersion || args.requestedVersion === "latest")
|
|
440
|
+
? { version: "latest", resolvedFromLatest: false }
|
|
441
|
+
: await resolvePackageTargetVersion(args.requestedVersion);
|
|
442
|
+
const cmd = buildPackageManagerUpdateCommand({
|
|
443
|
+
packageManager: pm,
|
|
444
|
+
version: target.version,
|
|
445
|
+
});
|
|
327
446
|
|
|
328
447
|
if (args.dryRun) {
|
|
329
448
|
console.log(`[dry-run] Would run: ${cmd.join(" ")}`);
|
|
@@ -341,7 +460,68 @@ async function selfUpdateViaPackageManager(args: {
|
|
|
341
460
|
if (code !== 0) {
|
|
342
461
|
throw new Error(`Self-update failed via ${pm} (exit ${code}).`);
|
|
343
462
|
}
|
|
344
|
-
|
|
463
|
+
if (pm === "mise") {
|
|
464
|
+
await runBestEffort(["mise", "reshim", `npm:${PACKAGE_NAME}`]);
|
|
465
|
+
}
|
|
466
|
+
await assertActiveFcltVersion(target.version, pm);
|
|
467
|
+
console.log(`Updated fclt via ${pm}: ${PACKAGE_NAME}@${target.version}`);
|
|
468
|
+
if (target.resolvedFromLatest) {
|
|
469
|
+
console.log(`Resolved latest release to ${target.version}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async function runBestEffort(cmd: string[]): Promise<void> {
|
|
474
|
+
const proc = Bun.spawn({
|
|
475
|
+
cmd,
|
|
476
|
+
stdin: "ignore",
|
|
477
|
+
stdout: "ignore",
|
|
478
|
+
stderr: "ignore",
|
|
479
|
+
env: process.env,
|
|
480
|
+
});
|
|
481
|
+
await proc.exited;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async function assertActiveFcltVersion(
|
|
485
|
+
expectedVersion: string,
|
|
486
|
+
packageManager: PackageManager
|
|
487
|
+
): Promise<void> {
|
|
488
|
+
const cmd =
|
|
489
|
+
packageManager === "mise"
|
|
490
|
+
? [
|
|
491
|
+
"mise",
|
|
492
|
+
"exec",
|
|
493
|
+
`npm:${PACKAGE_NAME}@${expectedVersion}`,
|
|
494
|
+
"--",
|
|
495
|
+
"fclt",
|
|
496
|
+
"--version",
|
|
497
|
+
]
|
|
498
|
+
: ["fclt", "--version"];
|
|
499
|
+
const proc = Bun.spawn({
|
|
500
|
+
cmd,
|
|
501
|
+
stdin: "ignore",
|
|
502
|
+
stdout: "pipe",
|
|
503
|
+
stderr: "pipe",
|
|
504
|
+
env: process.env,
|
|
505
|
+
});
|
|
506
|
+
const [stdout, stderr, code] = await Promise.all([
|
|
507
|
+
new Response(proc.stdout).text(),
|
|
508
|
+
new Response(proc.stderr).text(),
|
|
509
|
+
proc.exited,
|
|
510
|
+
]);
|
|
511
|
+
const actual = stdout.trim();
|
|
512
|
+
if (code !== 0) {
|
|
513
|
+
throw new Error(
|
|
514
|
+
`Updated package, but could not verify active fclt version: ${stderr.trim()}`
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
if (actual !== expectedVersion) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
[
|
|
520
|
+
`Updated package to ${expectedVersion}, but active fclt is still ${actual}.`,
|
|
521
|
+
"Your PATH may be resolving an older shim. Run `mise which fclt` or `which fclt` to inspect it.",
|
|
522
|
+
].join("\n")
|
|
523
|
+
);
|
|
524
|
+
}
|
|
345
525
|
}
|
|
346
526
|
|
|
347
527
|
async function fetchReleaseBinaryWithRetry(url: string): Promise<ArrayBuffer> {
|
|
@@ -424,7 +604,7 @@ export async function selfUpdateCommand(argv: string[]) {
|
|
|
424
604
|
|
|
425
605
|
const home = homedir();
|
|
426
606
|
const state = await loadInstallState(home);
|
|
427
|
-
const method =
|
|
607
|
+
const method = await detectActiveInstallMethod(state);
|
|
428
608
|
|
|
429
609
|
try {
|
|
430
610
|
if (method === "script-dev") {
|
|
@@ -434,9 +614,10 @@ export async function selfUpdateCommand(argv: string[]) {
|
|
|
434
614
|
);
|
|
435
615
|
return;
|
|
436
616
|
}
|
|
437
|
-
if (method === "npm-binary-cache") {
|
|
617
|
+
if (method === "npm-binary-cache" || method === "mise-npm") {
|
|
438
618
|
await selfUpdateViaPackageManager({
|
|
439
619
|
...parsed,
|
|
620
|
+
method,
|
|
440
621
|
preferredPackageManager:
|
|
441
622
|
process.env.FACULT_INSTALL_PM?.trim() || state?.packageManager,
|
|
442
623
|
});
|