@pify/cli 0.1.2 → 0.2.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 +11 -1
- package/catalog.json +23 -23
- package/dist/cli.js +439 -124
- package/dist/index.d.ts +56 -4
- package/dist/index.js +343 -25
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -92,10 +92,29 @@ declare function piStatus(): PiStatus;
|
|
|
92
92
|
/** Latest pi version from pi's own release endpoint. Null offline/on error. */
|
|
93
93
|
declare function fetchLatestPiVersion(): Promise<string | null>;
|
|
94
94
|
declare function compareSemver(a: string, b: string): number;
|
|
95
|
+
/** The official installer one-liner for this OS, for hints and --installer. */
|
|
96
|
+
declare function officialInstallerCommand(): string;
|
|
95
97
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
98
|
+
* The npm install argv pi's official installers use:
|
|
99
|
+
* `--ignore-scripts` (supply-chain-safe; matches pi's self-update),
|
|
100
|
+
* `--min-release-age=0` (pi ships npm-shrinkwrap.json, so bypassing npm's
|
|
101
|
+
* release-age gate does not reopen transitive ranges; npm >= 11 only),
|
|
102
|
+
* `--no-fund --no-audit` (noise). Exported for tests.
|
|
103
|
+
*/
|
|
104
|
+
declare function npmInstallPiArgs(spec: string, npmMajor: number, prefix?: string): string[];
|
|
105
|
+
/**
|
|
106
|
+
* Install pi with the same decisions as pi's official installers, minus the
|
|
107
|
+
* interactive parts. This is the one operation pi cannot do for itself —
|
|
108
|
+
* `pi update` requires a working pi.
|
|
109
|
+
*
|
|
110
|
+
* Per-OS behavior (ported from install.sh / install.ps1):
|
|
111
|
+
* - POSIX, npm prefix unwritable, no global pi there: fall back to
|
|
112
|
+
* `--prefix ~/.local` — never sudo.
|
|
113
|
+
* - POSIX, npm prefix unwritable but an old global pi lives there: stop
|
|
114
|
+
* (a ~/.local copy would be shadowed by the stale global one).
|
|
115
|
+
* - No npm but bun on PATH: `bun add -g --ignore-scripts` (documented
|
|
116
|
+
* alternative channel).
|
|
117
|
+
* - Neither: point at the official installer, which can bootstrap Node too.
|
|
99
118
|
*/
|
|
100
119
|
declare function installPi(version?: string): Promise<void>;
|
|
101
120
|
/**
|
|
@@ -148,6 +167,39 @@ declare function assertSafeArg(arg: string, what?: string): void;
|
|
|
148
167
|
*/
|
|
149
168
|
declare function which(command: string): string | null;
|
|
150
169
|
|
|
170
|
+
declare function generateCompletion(shell: string): string;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Declarative command surface — the single source of truth consumed by BOTH
|
|
174
|
+
* the argument parser (cli.ts) and the completion generator (completions.ts),
|
|
175
|
+
* so completions can never drift from the real CLI (oh-my-pi's principle).
|
|
176
|
+
*/
|
|
177
|
+
interface FlagSpec {
|
|
178
|
+
name: string;
|
|
179
|
+
short?: string;
|
|
180
|
+
/** Flag takes a value (e.g. --pi-version <version>). */
|
|
181
|
+
takesValue?: boolean;
|
|
182
|
+
description: string;
|
|
183
|
+
}
|
|
184
|
+
type PositionalKind = "none" | "packages" | "updateTargets" | "directory" | "shell";
|
|
185
|
+
interface CommandSpec {
|
|
186
|
+
name: string;
|
|
187
|
+
aliases: string[];
|
|
188
|
+
description: string;
|
|
189
|
+
positional: PositionalKind;
|
|
190
|
+
flags: FlagSpec[];
|
|
191
|
+
}
|
|
192
|
+
declare const GLOBAL_FLAGS: FlagSpec[];
|
|
193
|
+
declare const COMMAND_SPECS: CommandSpec[];
|
|
194
|
+
declare const COMPLETION_SHELLS: readonly ["bash", "zsh", "fish", "powershell"];
|
|
195
|
+
type CompletionShell = (typeof COMPLETION_SHELLS)[number];
|
|
196
|
+
/** Build the strict parseArgs options table for one command. */
|
|
197
|
+
declare function parseArgsOptionsFor(spec: CommandSpec): Record<string, {
|
|
198
|
+
type: "boolean" | "string";
|
|
199
|
+
short?: string;
|
|
200
|
+
default?: boolean;
|
|
201
|
+
}>;
|
|
202
|
+
|
|
151
203
|
/**
|
|
152
204
|
* The five `pify init` file templates, embedded as string literals so the
|
|
153
205
|
* tarball ships nothing extra. Every line of the package.json template
|
|
@@ -192,4 +244,4 @@ declare class PifyError extends Error {
|
|
|
192
244
|
constructor(message: string, code?: ExitCodeValue, hint?: string);
|
|
193
245
|
}
|
|
194
246
|
|
|
195
|
-
export { type Catalog, type CatalogPackage, ExitCode, INDEX_TS, type InstallSpec, type InstallTarget, type InstalledPifyPackage, NODE_FLOOR, PI_PACKAGE, type PiSettings, type PiStatus, PifyError, README_MD, TESTED_PI_VERSION, type TemplateVars, VERSION, agentDir, assertSafeArg, compareSemver, fetchLatestPiVersion, installPi, installedPifyPackages, installedVersionOnDisk, interpolate, isOffline, loadBundledCatalog, loadCatalog, packageJsonTemplate, parseInstallSpec, piStatus, readSettings, refreshCatalog, resolveInstallTarget, resolvePifyName, updatePi, validateCatalog, which };
|
|
247
|
+
export { COMMAND_SPECS, COMPLETION_SHELLS, type Catalog, type CatalogPackage, type CommandSpec, type CompletionShell, ExitCode, type FlagSpec, GLOBAL_FLAGS, INDEX_TS, type InstallSpec, type InstallTarget, type InstalledPifyPackage, NODE_FLOOR, PI_PACKAGE, type PiSettings, type PiStatus, PifyError, README_MD, TESTED_PI_VERSION, type TemplateVars, VERSION, agentDir, assertSafeArg, compareSemver, fetchLatestPiVersion, generateCompletion, installPi, installedPifyPackages, installedVersionOnDisk, interpolate, isOffline, loadBundledCatalog, loadCatalog, npmInstallPiArgs, officialInstallerCommand, packageJsonTemplate, parseArgsOptionsFor, parseInstallSpec, piStatus, readSettings, refreshCatalog, resolveInstallTarget, resolvePifyName, updatePi, validateCatalog, which };
|
package/dist/index.js
CHANGED
|
@@ -34,21 +34,21 @@ var PifyError = class extends Error {
|
|
|
34
34
|
code;
|
|
35
35
|
/** Optional follow-up shown after the message, e.g. a command to run. */
|
|
36
36
|
hint;
|
|
37
|
-
constructor(message, code = ExitCode.FAILURE,
|
|
37
|
+
constructor(message, code = ExitCode.FAILURE, hint2) {
|
|
38
38
|
super(message);
|
|
39
39
|
this.name = "PifyError";
|
|
40
40
|
this.code = code;
|
|
41
|
-
this.hint =
|
|
41
|
+
this.hint = hint2;
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
|
-
function usageError(message,
|
|
45
|
-
return new PifyError(message, ExitCode.USAGE,
|
|
44
|
+
function usageError(message, hint2) {
|
|
45
|
+
return new PifyError(message, ExitCode.USAGE, hint2);
|
|
46
46
|
}
|
|
47
|
-
function environmentError(message,
|
|
48
|
-
return new PifyError(message, ExitCode.ENVIRONMENT,
|
|
47
|
+
function environmentError(message, hint2) {
|
|
48
|
+
return new PifyError(message, ExitCode.ENVIRONMENT, hint2);
|
|
49
49
|
}
|
|
50
|
-
function notFoundError(message,
|
|
51
|
-
return new PifyError(message, ExitCode.NOT_FOUND,
|
|
50
|
+
function notFoundError(message, hint2) {
|
|
51
|
+
return new PifyError(message, ExitCode.NOT_FOUND, hint2);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// src/exec.ts
|
|
@@ -305,9 +305,9 @@ function resolvePifyName(input) {
|
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
// src/pi.ts
|
|
308
|
-
import { readFileSync as readFileSync2, realpathSync } from "fs";
|
|
308
|
+
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync2, realpathSync, statSync as statSync2 } from "fs";
|
|
309
309
|
import { homedir as homedir2 } from "os";
|
|
310
|
-
import { join as join3 } from "path";
|
|
310
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
311
311
|
|
|
312
312
|
// src/ui.ts
|
|
313
313
|
function detectColor(stream) {
|
|
@@ -340,6 +340,10 @@ function success(msg) {
|
|
|
340
340
|
process.stdout.write(`${style.green(msg)}
|
|
341
341
|
`);
|
|
342
342
|
}
|
|
343
|
+
function hint(msg) {
|
|
344
|
+
process.stdout.write(`${style.dim(msg)}
|
|
345
|
+
`);
|
|
346
|
+
}
|
|
343
347
|
|
|
344
348
|
// src/pi.ts
|
|
345
349
|
var PI_PACKAGE = "@earendil-works/pi-coding-agent";
|
|
@@ -388,38 +392,107 @@ function assertNodeVersion() {
|
|
|
388
392
|
);
|
|
389
393
|
}
|
|
390
394
|
}
|
|
395
|
+
function officialInstallerCommand() {
|
|
396
|
+
return isWindows ? 'powershell -c "irm https://pi.dev/install.ps1 | iex"' : "curl -fsSL https://pi.dev/install.sh | sh";
|
|
397
|
+
}
|
|
398
|
+
function writableOrCreatable(path) {
|
|
399
|
+
let current = path;
|
|
400
|
+
for (; ; ) {
|
|
401
|
+
try {
|
|
402
|
+
statSync2(current);
|
|
403
|
+
break;
|
|
404
|
+
} catch {
|
|
405
|
+
const parent = dirname2(current);
|
|
406
|
+
if (parent === current) return false;
|
|
407
|
+
current = parent;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
try {
|
|
411
|
+
if (!statSync2(current).isDirectory()) return false;
|
|
412
|
+
accessSync2(current, constants2.W_OK);
|
|
413
|
+
return true;
|
|
414
|
+
} catch {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function npmInstallPiArgs(spec, npmMajor, prefix) {
|
|
419
|
+
const args = ["install", "-g", "--ignore-scripts"];
|
|
420
|
+
if (npmMajor >= 11) args.push("--min-release-age=0");
|
|
421
|
+
args.push("--no-fund", "--no-audit");
|
|
422
|
+
if (prefix) args.push("--prefix", prefix);
|
|
423
|
+
args.push(spec);
|
|
424
|
+
return args;
|
|
425
|
+
}
|
|
426
|
+
function npmMajorVersion() {
|
|
427
|
+
const res = runCapture("npm", ["--version"]);
|
|
428
|
+
const major = Number.parseInt(res.stdout.trim().split(".")[0] ?? "", 10);
|
|
429
|
+
return Number.isNaN(major) ? 0 : major;
|
|
430
|
+
}
|
|
391
431
|
async function installPi(version) {
|
|
392
432
|
assertNodeVersion();
|
|
393
|
-
if (!which("npm")) {
|
|
394
|
-
throw environmentError(
|
|
395
|
-
"npm was not found on PATH.",
|
|
396
|
-
"Install Node.js (which bundles npm) from https://nodejs.org and retry."
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
433
|
if (process.env.PI_MANAGED_INSTALL_ROOT && !piStatus().installed) {
|
|
400
434
|
throw environmentError(
|
|
401
435
|
"PI_MANAGED_INSTALL_ROOT is set but pi is not installed.",
|
|
402
|
-
|
|
436
|
+
`Use the official installer: ${officialInstallerCommand()}`
|
|
403
437
|
);
|
|
404
438
|
}
|
|
405
439
|
const spec = version ? `${PI_PACKAGE}@${version}` : PI_PACKAGE;
|
|
406
|
-
|
|
407
|
-
const
|
|
440
|
+
const npmPath = which("npm");
|
|
441
|
+
const bunPath = which("bun");
|
|
442
|
+
if (!npmPath && !bunPath) {
|
|
443
|
+
throw environmentError(
|
|
444
|
+
"Neither npm nor bun was found on PATH.",
|
|
445
|
+
`Run the official installer (it can install Node too): ${officialInstallerCommand()}`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
let command;
|
|
449
|
+
let args;
|
|
450
|
+
let localPrefix = null;
|
|
451
|
+
if (npmPath) {
|
|
452
|
+
command = "npm";
|
|
453
|
+
if (!isWindows) {
|
|
454
|
+
const prefixRes = runCapture("npm", ["prefix", "-g"]);
|
|
455
|
+
const prefix = prefixRes.status === 0 ? prefixRes.stdout.trim() : "";
|
|
456
|
+
const prefixWritable = prefix !== "" && writableOrCreatable(join3(prefix, "lib", "node_modules")) && writableOrCreatable(join3(prefix, "bin"));
|
|
457
|
+
if (prefix && !prefixWritable) {
|
|
458
|
+
let staleGlobalPi = false;
|
|
459
|
+
try {
|
|
460
|
+
statSync2(join3(prefix, "bin", "pi"));
|
|
461
|
+
staleGlobalPi = true;
|
|
462
|
+
} catch {
|
|
463
|
+
}
|
|
464
|
+
if (staleGlobalPi) {
|
|
465
|
+
throw environmentError(
|
|
466
|
+
`npm's global directory is not writable (${prefix}) and pi is already installed there.`,
|
|
467
|
+
`Update or remove it first: sudo npm install -g --ignore-scripts ${PI_PACKAGE}`
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
localPrefix = join3(homedir2(), ".local");
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
args = npmInstallPiArgs(spec, npmMajorVersion(), localPrefix ?? void 0);
|
|
474
|
+
} else {
|
|
475
|
+
command = "bun";
|
|
476
|
+
args = ["add", "-g", "--ignore-scripts", spec];
|
|
477
|
+
}
|
|
478
|
+
step(`${command} ${args.join(" ")}`);
|
|
479
|
+
const code = await runInherit(command, args);
|
|
408
480
|
if (code !== 0) {
|
|
409
481
|
throw new PifyError(
|
|
410
|
-
|
|
482
|
+
`${command} ${args[0]} exited with code ${code}.`,
|
|
411
483
|
ExitCode.SUBPROCESS,
|
|
412
|
-
isWindows ? "If this is a permissions or file-lock error, close running pi sessions and retry from an elevated terminal." :
|
|
484
|
+
isWindows ? "If this is a permissions or file-lock error, close running pi sessions and retry from an elevated terminal." : `If the problem persists, try the official installer: ${officialInstallerCommand()}`
|
|
413
485
|
);
|
|
414
486
|
}
|
|
415
487
|
const st = piStatus();
|
|
416
488
|
if (!st.installed) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
"Add npm's global prefix to PATH (npm prefix -g) and open a new terminal."
|
|
420
|
-
);
|
|
489
|
+
const pathHint = localPrefix ? `Add ${join3(localPrefix, "bin")} to PATH and open a new terminal.` : "Add the global bin directory to PATH (npm prefix -g) and open a new terminal.";
|
|
490
|
+
throw environmentError("pi was installed but is not resolvable on PATH.", pathHint);
|
|
421
491
|
}
|
|
422
492
|
success(`pi ${st.version ?? ""} installed.`.replace(" ", " "));
|
|
493
|
+
if (localPrefix) {
|
|
494
|
+
hint(`Installed under ${localPrefix} because npm's global prefix is not writable.`);
|
|
495
|
+
}
|
|
423
496
|
}
|
|
424
497
|
async function updatePi() {
|
|
425
498
|
const st = piStatus();
|
|
@@ -478,6 +551,244 @@ function installedVersionOnDisk(name, scope) {
|
|
|
478
551
|
}
|
|
479
552
|
}
|
|
480
553
|
|
|
554
|
+
// src/registry.ts
|
|
555
|
+
var GLOBAL_FLAGS = [
|
|
556
|
+
{ name: "help", short: "h", description: "Show help" },
|
|
557
|
+
{ name: "version", short: "v", description: "Print the pify version" },
|
|
558
|
+
{ name: "no-color", description: "Disable colored output" }
|
|
559
|
+
];
|
|
560
|
+
var COMMAND_SPECS = [
|
|
561
|
+
{
|
|
562
|
+
name: "setup",
|
|
563
|
+
aliases: [],
|
|
564
|
+
description: "Install the pi coding agent (safe to re-run)",
|
|
565
|
+
positional: "none",
|
|
566
|
+
flags: [
|
|
567
|
+
{ name: "force", description: "Reinstall pi even if present" },
|
|
568
|
+
{ name: "pi-version", takesValue: true, description: "Install an exact pi version" },
|
|
569
|
+
{ name: "installer", description: "Run pi's official interactive installer" }
|
|
570
|
+
]
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
name: "install",
|
|
574
|
+
aliases: ["i", "add"],
|
|
575
|
+
description: "Install @pify packages by short name",
|
|
576
|
+
positional: "packages",
|
|
577
|
+
flags: [
|
|
578
|
+
{ name: "local", short: "l", description: "Project scope" },
|
|
579
|
+
{ name: "approve", short: "a", description: "Forward --approve to pi" },
|
|
580
|
+
{ name: "dry-run", description: "Print the pi commands without running them" }
|
|
581
|
+
]
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: "remove",
|
|
585
|
+
aliases: ["rm", "uninstall"],
|
|
586
|
+
description: "Remove installed @pify packages",
|
|
587
|
+
positional: "packages",
|
|
588
|
+
flags: [
|
|
589
|
+
{ name: "local", short: "l", description: "Project scope" },
|
|
590
|
+
{ name: "approve", short: "a", description: "Forward --approve to pi" },
|
|
591
|
+
{ name: "dry-run", description: "Print the pi commands without running them" }
|
|
592
|
+
]
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
name: "update",
|
|
596
|
+
aliases: ["up"],
|
|
597
|
+
description: "Update pi and installed @pify packages",
|
|
598
|
+
positional: "updateTargets",
|
|
599
|
+
flags: [
|
|
600
|
+
{ name: "catalog", description: "Refresh the catalog cache only" },
|
|
601
|
+
{ name: "dry-run", description: "Print the update plan without executing" }
|
|
602
|
+
]
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
name: "list",
|
|
606
|
+
aliases: ["ls"],
|
|
607
|
+
description: "Show the @pify catalog with install state",
|
|
608
|
+
positional: "none",
|
|
609
|
+
flags: [{ name: "json", description: "Machine-readable output" }]
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
name: "doctor",
|
|
613
|
+
aliases: [],
|
|
614
|
+
description: "Diagnose the local pi/pify environment",
|
|
615
|
+
positional: "none",
|
|
616
|
+
flags: [{ name: "json", description: "Machine-readable output" }]
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
name: "init",
|
|
620
|
+
aliases: [],
|
|
621
|
+
description: "Scaffold a new Pi Package",
|
|
622
|
+
positional: "directory",
|
|
623
|
+
flags: [
|
|
624
|
+
{ name: "name", takesValue: true, description: "npm package name" },
|
|
625
|
+
{ name: "description", takesValue: true, description: "Package description" }
|
|
626
|
+
]
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
name: "completions",
|
|
630
|
+
aliases: [],
|
|
631
|
+
description: "Print a shell completion script",
|
|
632
|
+
positional: "shell",
|
|
633
|
+
flags: []
|
|
634
|
+
}
|
|
635
|
+
];
|
|
636
|
+
var COMPLETION_SHELLS = ["bash", "zsh", "fish", "powershell"];
|
|
637
|
+
function parseArgsOptionsFor(spec) {
|
|
638
|
+
const options = {};
|
|
639
|
+
for (const flag of spec.flags) {
|
|
640
|
+
options[flag.name] = flag.takesValue ? { type: "string", ...flag.short ? { short: flag.short } : {} } : { type: "boolean", default: false, ...flag.short ? { short: flag.short } : {} };
|
|
641
|
+
}
|
|
642
|
+
return options;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/completions.ts
|
|
646
|
+
function packageNames() {
|
|
647
|
+
return loadBundledCatalog().packages.map((p) => p.name);
|
|
648
|
+
}
|
|
649
|
+
function allNames(spec) {
|
|
650
|
+
return [spec.name, ...spec.aliases];
|
|
651
|
+
}
|
|
652
|
+
function flagTokens(spec) {
|
|
653
|
+
return spec.flags.flatMap((f) => [`--${f.name}`, ...f.short ? [`-${f.short}`] : []]);
|
|
654
|
+
}
|
|
655
|
+
function positionalWords(spec) {
|
|
656
|
+
switch (spec.positional) {
|
|
657
|
+
case "packages":
|
|
658
|
+
return packageNames();
|
|
659
|
+
case "updateTargets":
|
|
660
|
+
return ["pi", ...packageNames()];
|
|
661
|
+
case "shell":
|
|
662
|
+
return [...COMPLETION_SHELLS];
|
|
663
|
+
default:
|
|
664
|
+
return [];
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function bashScript() {
|
|
668
|
+
const commands = COMMAND_SPECS.flatMap(allNames).join(" ");
|
|
669
|
+
const cases = COMMAND_SPECS.map((spec) => {
|
|
670
|
+
const words = [...positionalWords(spec), ...flagTokens(spec)].join(" ");
|
|
671
|
+
return ` ${allNames(spec).join("|")}) words="${words}" ;;`;
|
|
672
|
+
}).join("\n");
|
|
673
|
+
return `# bash completion for pify \u2014 generated by \`pify completions bash\`
|
|
674
|
+
# Install: eval "$(pify completions bash)" (or save to a bash-completion dir)
|
|
675
|
+
_pify_completions() {
|
|
676
|
+
local cur words
|
|
677
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
678
|
+
if [ "$COMP_CWORD" -eq 1 ]; then
|
|
679
|
+
COMPREPLY=($(compgen -W "${commands} ${GLOBAL_FLAGS.map((f) => `--${f.name}`).join(" ")}" -- "$cur"))
|
|
680
|
+
return
|
|
681
|
+
fi
|
|
682
|
+
case "\${COMP_WORDS[1]}" in
|
|
683
|
+
${cases}
|
|
684
|
+
*) words="" ;;
|
|
685
|
+
esac
|
|
686
|
+
COMPREPLY=($(compgen -W "$words" -- "$cur"))
|
|
687
|
+
}
|
|
688
|
+
complete -F _pify_completions pify
|
|
689
|
+
`;
|
|
690
|
+
}
|
|
691
|
+
function zshScript() {
|
|
692
|
+
const commandDescs = COMMAND_SPECS.flatMap(
|
|
693
|
+
(spec) => allNames(spec).map((n) => ` '${n}:${spec.description.replace(/'/g, "")}'`)
|
|
694
|
+
).join("\n");
|
|
695
|
+
const cases = COMMAND_SPECS.map((spec) => {
|
|
696
|
+
const words = [...positionalWords(spec), ...flagTokens(spec)].map((w) => `'${w}'`).join(" ");
|
|
697
|
+
return ` ${allNames(spec).join("|")}) _values 'value' ${words || "''"} ;;`;
|
|
698
|
+
}).join("\n");
|
|
699
|
+
return `#compdef pify
|
|
700
|
+
# zsh completion for pify \u2014 generated by \`pify completions zsh\`
|
|
701
|
+
# Install: eval "$(pify completions zsh)" (or save into a directory on $fpath)
|
|
702
|
+
_pify() {
|
|
703
|
+
local -a commands
|
|
704
|
+
commands=(
|
|
705
|
+
${commandDescs}
|
|
706
|
+
)
|
|
707
|
+
if (( CURRENT == 2 )); then
|
|
708
|
+
_describe 'command' commands
|
|
709
|
+
return
|
|
710
|
+
fi
|
|
711
|
+
case "$words[2]" in
|
|
712
|
+
${cases}
|
|
713
|
+
esac
|
|
714
|
+
}
|
|
715
|
+
_pify "$@"
|
|
716
|
+
`;
|
|
717
|
+
}
|
|
718
|
+
function fishScript() {
|
|
719
|
+
const lines = [
|
|
720
|
+
`# fish completion for pify \u2014 generated by \`pify completions fish\``,
|
|
721
|
+
`# Install: pify completions fish > ~/.config/fish/completions/pify.fish`,
|
|
722
|
+
`complete -c pify -f`
|
|
723
|
+
];
|
|
724
|
+
for (const spec of COMMAND_SPECS) {
|
|
725
|
+
for (const name of allNames(spec)) {
|
|
726
|
+
lines.push(
|
|
727
|
+
`complete -c pify -n __fish_use_subcommand -a ${name} -d '${spec.description.replace(/'/g, "")}'`
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
const condition = `__fish_seen_subcommand_from ${allNames(spec).join(" ")}`;
|
|
731
|
+
const words = positionalWords(spec);
|
|
732
|
+
if (words.length > 0) {
|
|
733
|
+
lines.push(`complete -c pify -n '${condition}' -a '${words.join(" ")}'`);
|
|
734
|
+
}
|
|
735
|
+
for (const flag of spec.flags) {
|
|
736
|
+
const short = flag.short ? ` -s ${flag.short}` : "";
|
|
737
|
+
lines.push(
|
|
738
|
+
`complete -c pify -n '${condition}' -l ${flag.name}${short} -d '${flag.description.replace(/'/g, "")}'`
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
for (const flag of GLOBAL_FLAGS) {
|
|
743
|
+
const short = flag.short ? ` -s ${flag.short}` : "";
|
|
744
|
+
lines.push(`complete -c pify -l ${flag.name}${short} -d '${flag.description.replace(/'/g, "")}'`);
|
|
745
|
+
}
|
|
746
|
+
return `${lines.join("\n")}
|
|
747
|
+
`;
|
|
748
|
+
}
|
|
749
|
+
function powershellScript() {
|
|
750
|
+
const commands = COMMAND_SPECS.flatMap(allNames);
|
|
751
|
+
const caseBlocks = COMMAND_SPECS.map((spec) => {
|
|
752
|
+
const words = [...positionalWords(spec), ...flagTokens(spec)].map((w) => `'${w}'`).join(", ");
|
|
753
|
+
return ` ${allNames(spec).map((n) => `'${n}'`).join(" ")} { @(${words}) }`;
|
|
754
|
+
}).join("\n");
|
|
755
|
+
return `# PowerShell completion for pify \u2014 generated by \`pify completions powershell\`
|
|
756
|
+
# Install: pify completions powershell | Out-String | Invoke-Expression (add to $PROFILE)
|
|
757
|
+
Register-ArgumentCompleter -Native -CommandName pify -ScriptBlock {
|
|
758
|
+
param($wordToComplete, $commandAst, $cursorPosition)
|
|
759
|
+
$tokens = $commandAst.CommandElements | ForEach-Object { $_.ToString() }
|
|
760
|
+
$candidates = if ($tokens.Count -le 1 -or ($tokens.Count -eq 2 -and $wordToComplete)) {
|
|
761
|
+
@(${commands.map((c) => `'${c}'`).join(", ")})
|
|
762
|
+
} else {
|
|
763
|
+
switch ($tokens[1]) {
|
|
764
|
+
${caseBlocks}
|
|
765
|
+
default { @() }
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
$candidates | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
|
769
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
`;
|
|
773
|
+
}
|
|
774
|
+
function generateCompletion(shell) {
|
|
775
|
+
switch (shell) {
|
|
776
|
+
case "bash":
|
|
777
|
+
return bashScript();
|
|
778
|
+
case "zsh":
|
|
779
|
+
return zshScript();
|
|
780
|
+
case "fish":
|
|
781
|
+
return fishScript();
|
|
782
|
+
case "powershell":
|
|
783
|
+
return powershellScript();
|
|
784
|
+
default:
|
|
785
|
+
throw usageError(
|
|
786
|
+
`Unknown shell ${JSON.stringify(shell)}.`,
|
|
787
|
+
`Usage: pify completions <${COMPLETION_SHELLS.join("|")}>`
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
481
792
|
// src/templates.ts
|
|
482
793
|
function interpolate(template, vars) {
|
|
483
794
|
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
@@ -596,7 +907,10 @@ Then install anywhere with: pi install npm:{{name}}
|
|
|
596
907
|
Tested with pi {{piVersion}}.
|
|
597
908
|
`;
|
|
598
909
|
export {
|
|
910
|
+
COMMAND_SPECS,
|
|
911
|
+
COMPLETION_SHELLS,
|
|
599
912
|
ExitCode,
|
|
913
|
+
GLOBAL_FLAGS,
|
|
600
914
|
INDEX_TS,
|
|
601
915
|
NODE_FLOOR,
|
|
602
916
|
PI_PACKAGE,
|
|
@@ -608,6 +922,7 @@ export {
|
|
|
608
922
|
assertSafeArg,
|
|
609
923
|
compareSemver,
|
|
610
924
|
fetchLatestPiVersion,
|
|
925
|
+
generateCompletion,
|
|
611
926
|
installPi,
|
|
612
927
|
installedPifyPackages,
|
|
613
928
|
installedVersionOnDisk,
|
|
@@ -615,7 +930,10 @@ export {
|
|
|
615
930
|
isOffline,
|
|
616
931
|
loadBundledCatalog,
|
|
617
932
|
loadCatalog,
|
|
933
|
+
npmInstallPiArgs,
|
|
934
|
+
officialInstallerCommand,
|
|
618
935
|
packageJsonTemplate,
|
|
936
|
+
parseArgsOptionsFor,
|
|
619
937
|
parseInstallSpec,
|
|
620
938
|
piStatus,
|
|
621
939
|
readSettings,
|