@theholocron/cli 4.17.2 → 4.18.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 +25 -0
- package/dist/cli.mjs +390 -41
- package/dist/cli.mjs.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -14,6 +14,27 @@ holocron --help
|
|
|
14
14
|
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
## Interactive mode
|
|
18
|
+
|
|
19
|
+
Run `holocron` with no command, a parent command with no subcommand
|
|
20
|
+
(`holocron auth`, `holocron skills`, `holocron upgrade`), or a leaf command
|
|
21
|
+
missing a required positional (`holocron deploy`, `holocron secret set`), and
|
|
22
|
+
you get a prompt instead of a `--help` dead end:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
$ holocron
|
|
26
|
+
? What would you like to do? › dep
|
|
27
|
+
────────────────────────────────────────────────
|
|
28
|
+
❯ deploy Trigger a deployment via the configured `deployment` capability
|
|
29
|
+
|
|
30
|
+
? Branch to deploy: › main
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The picked command spawns as a normal `holocron <command> …` invocation —
|
|
34
|
+
`--token`, `--org`, `--cwd`, and `--dry-run` on the original invocation carry
|
|
35
|
+
through. A non-interactive shell (CI, a script, a pipe) gets the old
|
|
36
|
+
hard-failure message instead of hanging on a prompt.
|
|
37
|
+
|
|
17
38
|
## Execution contexts
|
|
18
39
|
|
|
19
40
|
Every command is tagged with how much of a repo it needs:
|
|
@@ -426,6 +447,10 @@ holds the orchestration + the Holocron-specific credential resolution
|
|
|
426
447
|
- `src/commands/` — `setup`, `sync`, `doctor`, `deploy`, `secret set`,
|
|
427
448
|
`secrets sync`, `publish`, `bump-versions`, `sync-github`, `upgrade node`,
|
|
428
449
|
`upgrade deps`, `plugin create`, `auth`
|
|
450
|
+
- `src/interactive-menu.ts` — the interactive fallback: `COMMAND_REGISTRY`,
|
|
451
|
+
`launchMenu` (Layers 1–2, picks + spawns a fresh `holocron <command>`),
|
|
452
|
+
`promptForPositionals` (Layer 3, called inline from each leaf command's
|
|
453
|
+
`cli.ts` handler)
|
|
429
454
|
|
|
430
455
|
## Status
|
|
431
456
|
|
package/dist/cli.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import { pathToFileURL } from "node:url";
|
|
|
14
14
|
import { createLogger, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/observability/logger";
|
|
15
15
|
import ora from "ora";
|
|
16
16
|
import chalk from "chalk";
|
|
17
|
-
import { execFile, execFileSync, spawnSync } from "node:child_process";
|
|
17
|
+
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
18
18
|
import { homedir, hostname, userInfo } from "node:os";
|
|
19
19
|
import { access, copyFile, mkdir, readFile, readdir, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
20
20
|
import { createHash } from "node:crypto";
|
|
@@ -26,6 +26,7 @@ import { NoopAnalyticsSink, NoopErrorSink, redactObject } from "@theholocron/obs
|
|
|
26
26
|
import { SentrySink } from "@theholocron/observability/errors";
|
|
27
27
|
import { promisify } from "node:util";
|
|
28
28
|
import { ConfigFileError, loadConfigFile } from "@theholocron/datapad";
|
|
29
|
+
import search from "@inquirer/search";
|
|
29
30
|
//#region src/env.ts
|
|
30
31
|
/** Singleton env for simple global lookups throughout the CLI. */
|
|
31
32
|
const env = createEnvLookup();
|
|
@@ -6878,6 +6879,350 @@ function parseGitRemoteUrl(url) {
|
|
|
6878
6879
|
if (sshMatch) return sshMatch[1];
|
|
6879
6880
|
}
|
|
6880
6881
|
//#endregion
|
|
6882
|
+
//#region src/interactive-menu.ts
|
|
6883
|
+
/**
|
|
6884
|
+
* The interactive fallback — replaces every "run --help to see commands"
|
|
6885
|
+
* dead end with a prompt. Three layers, all driven by {@link COMMAND_REGISTRY}:
|
|
6886
|
+
*
|
|
6887
|
+
* 1. `holocron` with no command → searchable picker over every command
|
|
6888
|
+
* 2. a parent command with no sub → `select()` over that group's commands
|
|
6889
|
+
* 3. a leaf command missing a → `input()` / `select()` per positional
|
|
6890
|
+
* required positional
|
|
6891
|
+
*
|
|
6892
|
+
* Layers 1–2 are driven from `cli.ts`'s `$0` handlers via {@link launchMenu}:
|
|
6893
|
+
* pick a command, prompt for its required positionals, then **spawn a fresh
|
|
6894
|
+
* `holocron <command> …` child process** (`stdio: "inherit"`) rather than
|
|
6895
|
+
* re-entering yargs' own parse pipeline — see the spec's "spawn vs re-parse"
|
|
6896
|
+
* section (theholocron/holocron#438) for why. Layer 3 is driven directly from
|
|
6897
|
+
* each leaf command's own handler in `cli.ts`, calling
|
|
6898
|
+
* {@link promptForPositionals} for just that one command — no spawn needed,
|
|
6899
|
+
* the handler already has everything else it needs.
|
|
6900
|
+
*
|
|
6901
|
+
* Every prompt is preceded by a `process.stdin.isTTY` check. A non-TTY run
|
|
6902
|
+
* (CI, a script, a pipe) throws {@link NonInteractiveError} instead of
|
|
6903
|
+
* hanging on `@inquirer/prompts` (which otherwise throws its own
|
|
6904
|
+
* `ExitPromptError` the moment stdin closes) — `cli.ts`'s `USER_FACING_ERRORS`
|
|
6905
|
+
* catch prints its message cleanly, restoring the old demandCommand-style
|
|
6906
|
+
* hard failure for non-interactive callers.
|
|
6907
|
+
*/
|
|
6908
|
+
/** Raised when a prompt is needed but stdin isn't a TTY — see the module doc. */
|
|
6909
|
+
var NonInteractiveError = class extends Error {
|
|
6910
|
+
name = "NonInteractiveError";
|
|
6911
|
+
};
|
|
6912
|
+
const numeric = (value) => /^\d+$/.test(value) ? true : "enter a number";
|
|
6913
|
+
/**
|
|
6914
|
+
* The full command surface, hand-maintained in parallel with `cli.ts`'s
|
|
6915
|
+
* yargs registrations (this module does not introspect yargs at runtime).
|
|
6916
|
+
* `run <task>` is deliberately excluded — it's CI/scripting-oriented, and an
|
|
6917
|
+
* interactive prompt in front of it would work against that.
|
|
6918
|
+
*/
|
|
6919
|
+
const COMMAND_REGISTRY = [
|
|
6920
|
+
{
|
|
6921
|
+
name: "version",
|
|
6922
|
+
description: "Print the CLI version",
|
|
6923
|
+
positionals: []
|
|
6924
|
+
},
|
|
6925
|
+
{
|
|
6926
|
+
name: "clone",
|
|
6927
|
+
description: "Clone all repos in a GitHub org as siblings under a single directory",
|
|
6928
|
+
positionals: []
|
|
6929
|
+
},
|
|
6930
|
+
{
|
|
6931
|
+
name: "doctor",
|
|
6932
|
+
description: "Load the config and run a smoke check against every provider",
|
|
6933
|
+
positionals: []
|
|
6934
|
+
},
|
|
6935
|
+
{
|
|
6936
|
+
name: "setup",
|
|
6937
|
+
description: "Apply infra setup actions across every configured capability",
|
|
6938
|
+
positionals: []
|
|
6939
|
+
},
|
|
6940
|
+
{
|
|
6941
|
+
name: "secret set",
|
|
6942
|
+
description: "Set a single secret via the configured `secrets` capability",
|
|
6943
|
+
positionals: [{
|
|
6944
|
+
key: "name",
|
|
6945
|
+
message: "Secret name:",
|
|
6946
|
+
type: "input"
|
|
6947
|
+
}]
|
|
6948
|
+
},
|
|
6949
|
+
{
|
|
6950
|
+
name: "secrets sync",
|
|
6951
|
+
description: "Read a vault environment + fan KEY=VALUEs out to secrets + deployment env vars",
|
|
6952
|
+
positionals: [{
|
|
6953
|
+
key: "environmentId",
|
|
6954
|
+
message: "Vault environment id:",
|
|
6955
|
+
type: "input"
|
|
6956
|
+
}]
|
|
6957
|
+
},
|
|
6958
|
+
{
|
|
6959
|
+
name: "deploy",
|
|
6960
|
+
description: "Trigger a deployment via the configured `deployment` capability",
|
|
6961
|
+
positionals: [{
|
|
6962
|
+
key: "branch",
|
|
6963
|
+
message: "Branch to deploy:",
|
|
6964
|
+
type: "input"
|
|
6965
|
+
}]
|
|
6966
|
+
},
|
|
6967
|
+
{
|
|
6968
|
+
name: "cleanup-preview",
|
|
6969
|
+
description: "List and delete Cloudflare Pages preview deployments for a GitHub PR",
|
|
6970
|
+
positionals: [{
|
|
6971
|
+
key: "pr",
|
|
6972
|
+
message: "PR number:",
|
|
6973
|
+
type: "input",
|
|
6974
|
+
validate: numeric
|
|
6975
|
+
}]
|
|
6976
|
+
},
|
|
6977
|
+
{
|
|
6978
|
+
name: "bump-versions",
|
|
6979
|
+
description: "Bump all non-private package versions in lockstep (semantic-release prepareCmd)",
|
|
6980
|
+
positionals: [{
|
|
6981
|
+
key: "newVersion",
|
|
6982
|
+
cliArg: "new-version",
|
|
6983
|
+
message: "New version:",
|
|
6984
|
+
type: "input"
|
|
6985
|
+
}]
|
|
6986
|
+
},
|
|
6987
|
+
{
|
|
6988
|
+
name: "publish",
|
|
6989
|
+
description: "Publish @theholocron/* packages to npm",
|
|
6990
|
+
positionals: []
|
|
6991
|
+
},
|
|
6992
|
+
{
|
|
6993
|
+
name: "sync",
|
|
6994
|
+
description: "Sync state from config to the provider and local files (labels, properties, teams, topics, keywords, description, homepage, readme, workflows, scripts, wiki)",
|
|
6995
|
+
positionals: []
|
|
6996
|
+
},
|
|
6997
|
+
{
|
|
6998
|
+
name: "ci",
|
|
6999
|
+
description: "Run the merge-gating checks locally, in CI order — 'will CI pass?'",
|
|
7000
|
+
positionals: []
|
|
7001
|
+
},
|
|
7002
|
+
{
|
|
7003
|
+
name: "sync-github",
|
|
7004
|
+
description: "Sync workflow templates and composite actions to theholocron/.github",
|
|
7005
|
+
positionals: []
|
|
7006
|
+
},
|
|
7007
|
+
{
|
|
7008
|
+
name: "sync-readme",
|
|
7009
|
+
description: "Sync the Installation + Usage block in README.md from package.json",
|
|
7010
|
+
positionals: []
|
|
7011
|
+
},
|
|
7012
|
+
{
|
|
7013
|
+
name: "config show",
|
|
7014
|
+
description: "Print the resolved holocron config",
|
|
7015
|
+
positionals: []
|
|
7016
|
+
},
|
|
7017
|
+
{
|
|
7018
|
+
name: "new",
|
|
7019
|
+
description: "Scaffold a new repo from a GitHub template (e.g. cli, react, nextjs, node, monorepo, base)",
|
|
7020
|
+
positionals: []
|
|
7021
|
+
},
|
|
7022
|
+
{
|
|
7023
|
+
name: "plugin create",
|
|
7024
|
+
description: "Scaffold a new @theholocron/holocron-plugin-<slug> package",
|
|
7025
|
+
positionals: [{
|
|
7026
|
+
key: "slug",
|
|
7027
|
+
message: "Package slug (kebab-case):",
|
|
7028
|
+
type: "input"
|
|
7029
|
+
}, {
|
|
7030
|
+
key: "vendor",
|
|
7031
|
+
message: "Vendor display name (PascalCase):",
|
|
7032
|
+
type: "input"
|
|
7033
|
+
}]
|
|
7034
|
+
},
|
|
7035
|
+
{
|
|
7036
|
+
name: "skills install",
|
|
7037
|
+
description: "Copy skills from @theholocron/skills into .agents/ with agent symlinks",
|
|
7038
|
+
positionals: [],
|
|
7039
|
+
group: "skills"
|
|
7040
|
+
},
|
|
7041
|
+
{
|
|
7042
|
+
name: "skills remove",
|
|
7043
|
+
description: "Remove installed skills via npx skills remove",
|
|
7044
|
+
positionals: [],
|
|
7045
|
+
group: "skills"
|
|
7046
|
+
},
|
|
7047
|
+
{
|
|
7048
|
+
name: "skills update",
|
|
7049
|
+
description: "Update installed skills to their latest upstream versions via npx skills update",
|
|
7050
|
+
positionals: [],
|
|
7051
|
+
group: "skills"
|
|
7052
|
+
},
|
|
7053
|
+
{
|
|
7054
|
+
name: "upgrade node",
|
|
7055
|
+
description: "Scan the repo and update every Node.js version pin to a new major",
|
|
7056
|
+
positionals: [{
|
|
7057
|
+
key: "to",
|
|
7058
|
+
message: "Target Node.js major version:",
|
|
7059
|
+
type: "input",
|
|
7060
|
+
validate: numeric
|
|
7061
|
+
}],
|
|
7062
|
+
group: "upgrade"
|
|
7063
|
+
},
|
|
7064
|
+
{
|
|
7065
|
+
name: "upgrade deps",
|
|
7066
|
+
description: "Bump every @theholocron/* pin to latest and migrate holocron.config.ts to the current preset API",
|
|
7067
|
+
positionals: [],
|
|
7068
|
+
group: "upgrade"
|
|
7069
|
+
},
|
|
7070
|
+
{
|
|
7071
|
+
name: "auth set",
|
|
7072
|
+
description: "Verify + store a bootstrap token for a provider",
|
|
7073
|
+
positionals: [{
|
|
7074
|
+
key: "provider",
|
|
7075
|
+
message: "Provider name:",
|
|
7076
|
+
type: "input"
|
|
7077
|
+
}],
|
|
7078
|
+
group: "auth"
|
|
7079
|
+
},
|
|
7080
|
+
{
|
|
7081
|
+
name: "auth unset",
|
|
7082
|
+
description: "Remove a stored bootstrap token",
|
|
7083
|
+
positionals: [{
|
|
7084
|
+
key: "provider",
|
|
7085
|
+
message: "Provider:",
|
|
7086
|
+
type: "select",
|
|
7087
|
+
choices: listStoredProviders
|
|
7088
|
+
}],
|
|
7089
|
+
group: "auth"
|
|
7090
|
+
},
|
|
7091
|
+
{
|
|
7092
|
+
name: "auth check",
|
|
7093
|
+
description: "Re-verify a stored bootstrap token",
|
|
7094
|
+
positionals: [{
|
|
7095
|
+
key: "provider",
|
|
7096
|
+
message: "Provider:",
|
|
7097
|
+
type: "select",
|
|
7098
|
+
choices: listStoredProviders
|
|
7099
|
+
}],
|
|
7100
|
+
group: "auth"
|
|
7101
|
+
},
|
|
7102
|
+
{
|
|
7103
|
+
name: "auth list",
|
|
7104
|
+
description: "List every provider with a stored bootstrap token",
|
|
7105
|
+
positionals: [],
|
|
7106
|
+
group: "auth"
|
|
7107
|
+
}
|
|
7108
|
+
];
|
|
7109
|
+
/** Look up a {@link CommandEntry} by its full name — for Layer 3 call sites in `cli.ts`. */
|
|
7110
|
+
function getEntry(name) {
|
|
7111
|
+
const entry = COMMAND_REGISTRY.find((e) => e.name === name);
|
|
7112
|
+
if (!entry) throw new Error(`interactive-menu: no COMMAND_REGISTRY entry named "${name}"`);
|
|
7113
|
+
return entry;
|
|
7114
|
+
}
|
|
7115
|
+
/**
|
|
7116
|
+
* Searchable autocomplete over `entries` — the Layer 1 top-level picker, and
|
|
7117
|
+
* (passed a `group`-filtered subset) the Layer 2 parent-command picker.
|
|
7118
|
+
* `select()` would work too at these list sizes, but `search()` degrades
|
|
7119
|
+
* gracefully as the surface grows and costs nothing when it doesn't.
|
|
7120
|
+
*/
|
|
7121
|
+
async function pickCommand(entries, message = "What would you like to do?") {
|
|
7122
|
+
const byName = new Map(entries.map((e) => [e.name, e]));
|
|
7123
|
+
const picked = await search({
|
|
7124
|
+
message,
|
|
7125
|
+
source: (term) => searchChoices(entries, term)
|
|
7126
|
+
});
|
|
7127
|
+
return byName.get(picked);
|
|
7128
|
+
}
|
|
7129
|
+
/**
|
|
7130
|
+
* `@inquirer/search`'s `source` callback, factored out as a plain function —
|
|
7131
|
+
* unit-testable directly instead of only through a mocked `search()` call.
|
|
7132
|
+
* Empty/undefined `term` (nothing typed yet) returns every entry.
|
|
7133
|
+
*/
|
|
7134
|
+
function searchChoices(entries, term) {
|
|
7135
|
+
return (!term ? entries : entries.filter((e) => matches(e, term))).map((e) => ({
|
|
7136
|
+
name: e.name,
|
|
7137
|
+
value: e.name,
|
|
7138
|
+
description: e.description
|
|
7139
|
+
}));
|
|
7140
|
+
}
|
|
7141
|
+
function matches(entry, term) {
|
|
7142
|
+
const needle = term.toLowerCase();
|
|
7143
|
+
return entry.name.toLowerCase().includes(needle) || entry.description.toLowerCase().includes(needle);
|
|
7144
|
+
}
|
|
7145
|
+
/**
|
|
7146
|
+
* For each of `entry.positionals` not already present in `argv`, prompts for
|
|
7147
|
+
* it (`input()` or `select()`, per the positional's `type`) and returns the
|
|
7148
|
+
* resolved values in positional order — ready to append to a spawned child's
|
|
7149
|
+
* argv, or to use directly in the current handler (Layer 3).
|
|
7150
|
+
*
|
|
7151
|
+
* Throws {@link NonInteractiveError} the first time it would need to prompt
|
|
7152
|
+
* on a non-TTY stdin, and again if a `select` positional's choices resolve
|
|
7153
|
+
* empty (nothing to pick from — e.g. `auth unset` with no stored tokens).
|
|
7154
|
+
*/
|
|
7155
|
+
async function promptForPositionals(entry, argv) {
|
|
7156
|
+
const out = [];
|
|
7157
|
+
for (const positional of entry.positionals) {
|
|
7158
|
+
const existing = argv[positional.key];
|
|
7159
|
+
if (existing !== void 0 && existing !== "") {
|
|
7160
|
+
out.push(String(existing));
|
|
7161
|
+
continue;
|
|
7162
|
+
}
|
|
7163
|
+
if (!process.stdin.isTTY) throw new NonInteractiveError(`\`${entry.name}\` needs "${positional.key}" — pass it directly: holocron ${entry.name} <${positional.cliArg ?? positional.key}>`);
|
|
7164
|
+
if (positional.type === "select") {
|
|
7165
|
+
const choices = typeof positional.choices === "function" ? positional.choices() : positional.choices ?? [];
|
|
7166
|
+
if (choices.length === 0) throw new NonInteractiveError(`\`${entry.name}\`: no ${positional.key} to choose from — nothing stored yet`);
|
|
7167
|
+
out.push(await select({
|
|
7168
|
+
message: positional.message,
|
|
7169
|
+
choices: choices.map((c) => ({ value: c }))
|
|
7170
|
+
}));
|
|
7171
|
+
} else out.push(await input({
|
|
7172
|
+
message: positional.message,
|
|
7173
|
+
...positional.validate ? { validate: positional.validate } : {}
|
|
7174
|
+
}));
|
|
7175
|
+
}
|
|
7176
|
+
return out;
|
|
7177
|
+
}
|
|
7178
|
+
/**
|
|
7179
|
+
* `--token` (repeatable), `--org`, `--cwd`, `--dry-run` — the global flags a
|
|
7180
|
+
* menu-launched child should inherit from the parent invocation. Everything
|
|
7181
|
+
* else (command-specific options) was never captured by the top-level `$0`
|
|
7182
|
+
* handler in the first place, so there's nothing else to forward.
|
|
7183
|
+
*/
|
|
7184
|
+
function forwardedFlags(argv) {
|
|
7185
|
+
const out = [];
|
|
7186
|
+
const tokens = argv.token;
|
|
7187
|
+
for (const t of tokens ?? []) out.push("--token", t);
|
|
7188
|
+
if (typeof argv.org === "string") out.push("--org", argv.org);
|
|
7189
|
+
if (typeof argv.cwd === "string") out.push("--cwd", argv.cwd);
|
|
7190
|
+
if (argv.dryRun === true) out.push("--dry-run");
|
|
7191
|
+
return out;
|
|
7192
|
+
}
|
|
7193
|
+
/** `entry.name` split into tokens, resolved positionals appended, then forwarded flags. */
|
|
7194
|
+
function buildChildArgv(entry, positionals, parentArgv) {
|
|
7195
|
+
return [
|
|
7196
|
+
...entry.name.split(" "),
|
|
7197
|
+
...positionals,
|
|
7198
|
+
...forwardedFlags(parentArgv)
|
|
7199
|
+
];
|
|
7200
|
+
}
|
|
7201
|
+
/** Spawn `holocron <...args>` inheriting stdio; resolve with its exit code. */
|
|
7202
|
+
function spawnChild(args) {
|
|
7203
|
+
return new Promise((resolve) => {
|
|
7204
|
+
const child = spawn(process.execPath, [process.argv[1], ...args], { stdio: "inherit" });
|
|
7205
|
+
child.on("exit", (code) => resolve(code ?? 0));
|
|
7206
|
+
child.on("error", () => resolve(1));
|
|
7207
|
+
});
|
|
7208
|
+
}
|
|
7209
|
+
/**
|
|
7210
|
+
* Layers 1 and 2: pick a command from `entries`, prompt for its required
|
|
7211
|
+
* positionals, spawn it as a fresh `holocron` invocation, and set
|
|
7212
|
+
* `process.exitCode` from the child — the caller (a `$0` handler in
|
|
7213
|
+
* `cli.ts`) returns normally afterward so the parent's own telemetry
|
|
7214
|
+
* flush / update-notifier tail still runs. The parent's own
|
|
7215
|
+
* `command_completed` event fires with command name "unknown" (the
|
|
7216
|
+
* middleware ran before any command was picked) — left as-is rather than
|
|
7217
|
+
* suppressed; it's a real, useful signal ("the menu got used").
|
|
7218
|
+
*/
|
|
7219
|
+
async function launchMenu(entries, parentArgv, pickMessage, nonInteractiveMessage = "Run `holocron --help` to see available commands.") {
|
|
7220
|
+
if (!process.stdin.isTTY) throw new NonInteractiveError(nonInteractiveMessage);
|
|
7221
|
+
const picked = await pickCommand(entries, pickMessage);
|
|
7222
|
+
const childArgv = buildChildArgv(picked, await promptForPositionals(picked, {}), parentArgv);
|
|
7223
|
+
process.exitCode = await spawnChild(childArgv);
|
|
7224
|
+
}
|
|
7225
|
+
//#endregion
|
|
6881
7226
|
//#region src/update-notifier.ts
|
|
6882
7227
|
const PACKAGE_NAME = "@theholocron/cli";
|
|
6883
7228
|
const CACHE_TTL_MS = 1440 * 60 * 1e3;
|
|
@@ -7003,7 +7348,11 @@ const resolveSyncToken = createFeatureResolver({
|
|
|
7003
7348
|
* Error class names whose `.message` is a complete, actionable sentence —
|
|
7004
7349
|
* the top-level catch prints it and suppresses the stack trace.
|
|
7005
7350
|
*/
|
|
7006
|
-
const USER_FACING_ERRORS = /* @__PURE__ */ new Set([
|
|
7351
|
+
const USER_FACING_ERRORS = /* @__PURE__ */ new Set([
|
|
7352
|
+
"WorkspaceContextError",
|
|
7353
|
+
"ConfigFileError",
|
|
7354
|
+
"NonInteractiveError"
|
|
7355
|
+
]);
|
|
7007
7356
|
const { version: CLI_VERSION } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
7008
7357
|
/** Whether to print the correlation id at command end (`--debug` / `--verbose`). */
|
|
7009
7358
|
let printRunId = false;
|
|
@@ -7191,9 +7540,10 @@ try {
|
|
|
7191
7540
|
},
|
|
7192
7541
|
...argv.name ? { name: argv.name } : {}
|
|
7193
7542
|
}).status === "fail") process.exitCode = 1;
|
|
7194
|
-
}).
|
|
7543
|
+
}).command("$0", false, () => {}, async (argv) => {
|
|
7544
|
+
await launchMenu(COMMAND_REGISTRY.filter((e) => e.group === "skills"), argv, "skills — choose a subcommand:", "Run `holocron skills --help` to see available skills subcommands.");
|
|
7545
|
+
}), () => {}).command("secret set [name] [value]", "Set a single secret via the configured `secrets` capability", (y) => y.positional("name", {
|
|
7195
7546
|
type: "string",
|
|
7196
|
-
demandOption: true,
|
|
7197
7547
|
describe: "Secret name (e.g., NPM_TOKEN)"
|
|
7198
7548
|
}).positional("value", {
|
|
7199
7549
|
type: "string",
|
|
@@ -7212,6 +7562,7 @@ try {
|
|
|
7212
7562
|
}), async (argv) => {
|
|
7213
7563
|
const tokens = tokenContext(argv.token);
|
|
7214
7564
|
if (!tokens) return;
|
|
7565
|
+
const [name] = await promptForPositionals(getEntry("secret set"), argv);
|
|
7215
7566
|
const scopeArg = argv.scope;
|
|
7216
7567
|
const scope = parseScope(scopeArg);
|
|
7217
7568
|
const loaded = await loadConfig(argv.cwd);
|
|
@@ -7224,15 +7575,14 @@ try {
|
|
|
7224
7575
|
...tokens,
|
|
7225
7576
|
org: resolveOrg(argv, loaded.resolved)
|
|
7226
7577
|
},
|
|
7227
|
-
name
|
|
7578
|
+
name,
|
|
7228
7579
|
...argv.value ? { value: argv.value } : {},
|
|
7229
7580
|
...argv.fromStdin ? { fromStdin: true } : {},
|
|
7230
7581
|
...argv.fromEnv ? { fromEnv: argv.fromEnv } : {},
|
|
7231
7582
|
scope
|
|
7232
7583
|
})).status === "fail") process.exitCode = 1;
|
|
7233
|
-
}).command("secrets sync
|
|
7584
|
+
}).command("secrets sync [environmentId]", "Read a vault environment + fan KEY=VALUEs out to secrets + deployment env vars", (y) => y.positional("environmentId", {
|
|
7234
7585
|
type: "string",
|
|
7235
|
-
demandOption: true,
|
|
7236
7586
|
describe: "Vault environment id to read (1P Environment id, etc.)"
|
|
7237
7587
|
}).option("project-id", {
|
|
7238
7588
|
type: "string",
|
|
@@ -7244,6 +7594,7 @@ try {
|
|
|
7244
7594
|
}), async (argv) => {
|
|
7245
7595
|
const tokens = tokenContext(argv.token);
|
|
7246
7596
|
if (!tokens) return;
|
|
7597
|
+
const [environmentId] = await promptForPositionals(getEntry("secrets sync"), argv);
|
|
7247
7598
|
const loaded = await loadConfig(argv.cwd);
|
|
7248
7599
|
applyResolvedConfig(argv, loaded.resolved);
|
|
7249
7600
|
if ((await runSecretsSync({
|
|
@@ -7254,13 +7605,12 @@ try {
|
|
|
7254
7605
|
...tokens,
|
|
7255
7606
|
org: resolveOrg(argv, loaded.resolved)
|
|
7256
7607
|
},
|
|
7257
|
-
environmentId
|
|
7608
|
+
environmentId,
|
|
7258
7609
|
...argv.projectId ? { projectId: argv.projectId } : {},
|
|
7259
7610
|
targets: argv.target
|
|
7260
7611
|
})).summary.fail > 0) process.exitCode = 1;
|
|
7261
|
-
}).command("deploy
|
|
7612
|
+
}).command("deploy [branch]", "Trigger a deployment via the configured `deployment` capability", (y) => y.positional("branch", {
|
|
7262
7613
|
type: "string",
|
|
7263
|
-
demandOption: true,
|
|
7264
7614
|
describe: "Git branch to deploy"
|
|
7265
7615
|
}).option("project-id", {
|
|
7266
7616
|
type: "string",
|
|
@@ -7273,6 +7623,7 @@ try {
|
|
|
7273
7623
|
}), async (argv) => {
|
|
7274
7624
|
const tokens = tokenContext(argv.token);
|
|
7275
7625
|
if (!tokens) return;
|
|
7626
|
+
const [branch] = await promptForPositionals(getEntry("deploy"), argv);
|
|
7276
7627
|
const loaded = await loadConfig(argv.cwd);
|
|
7277
7628
|
applyResolvedConfig(argv, loaded.resolved);
|
|
7278
7629
|
if ((await runDeploy({
|
|
@@ -7284,12 +7635,11 @@ try {
|
|
|
7284
7635
|
org: resolveOrg(argv, loaded.resolved)
|
|
7285
7636
|
},
|
|
7286
7637
|
projectId: argv.projectId,
|
|
7287
|
-
branch
|
|
7638
|
+
branch,
|
|
7288
7639
|
...argv.target ? { target: argv.target } : {}
|
|
7289
7640
|
})).status === "fail") process.exitCode = 1;
|
|
7290
|
-
}).command("cleanup-preview
|
|
7641
|
+
}).command("cleanup-preview [pr]", "List and delete Cloudflare Pages preview deployments for a GitHub PR", (y) => y.positional("pr", {
|
|
7291
7642
|
type: "number",
|
|
7292
|
-
demandOption: true,
|
|
7293
7643
|
describe: "PR number to clean up"
|
|
7294
7644
|
}).option("project", {
|
|
7295
7645
|
type: "string",
|
|
@@ -7301,6 +7651,7 @@ try {
|
|
|
7301
7651
|
}), async (argv) => {
|
|
7302
7652
|
const tokens = tokenContext(argv.token);
|
|
7303
7653
|
if (!tokens) return;
|
|
7654
|
+
const [pr] = await promptForPositionals(getEntry("cleanup-preview"), argv);
|
|
7304
7655
|
const loaded = await loadConfig(argv.cwd);
|
|
7305
7656
|
applyResolvedConfig(argv, loaded.resolved);
|
|
7306
7657
|
if ((await runCleanupPreview({
|
|
@@ -7311,17 +7662,17 @@ try {
|
|
|
7311
7662
|
...tokens,
|
|
7312
7663
|
org: resolveOrg(argv, loaded.resolved)
|
|
7313
7664
|
},
|
|
7314
|
-
prNumber:
|
|
7665
|
+
prNumber: Number(pr),
|
|
7315
7666
|
project: argv.project,
|
|
7316
7667
|
...argv.repo ? { repo: argv.repo } : {}
|
|
7317
7668
|
})).status === "fail") process.exitCode = 1;
|
|
7318
|
-
}).command("bump-versions
|
|
7669
|
+
}).command("bump-versions [new-version]", "Bump all non-private package versions in lockstep (semantic-release prepareCmd)", (y) => y.positional("new-version", {
|
|
7319
7670
|
type: "string",
|
|
7320
|
-
demandOption: true,
|
|
7321
7671
|
describe: "Version to set (e.g., 4.2.0 or 2.0.0-alpha.1)"
|
|
7322
7672
|
}), async (argv) => {
|
|
7673
|
+
const [newVersion] = await promptForPositionals(getEntry("bump-versions"), argv);
|
|
7323
7674
|
if ((await runNpmBumpVersions({
|
|
7324
|
-
version:
|
|
7675
|
+
version: newVersion,
|
|
7325
7676
|
cwd: argv.cwd,
|
|
7326
7677
|
dryRun: argv.dryRun
|
|
7327
7678
|
})).status === "fail") process.exitCode = 1;
|
|
@@ -7744,13 +8095,11 @@ try {
|
|
|
7744
8095
|
}
|
|
7745
8096
|
throw err;
|
|
7746
8097
|
}
|
|
7747
|
-
}).command("plugin create
|
|
8098
|
+
}).command("plugin create [slug] [vendor]", "Scaffold a new @theholocron/holocron-plugin-<slug> package", (y) => y.positional("slug", {
|
|
7748
8099
|
type: "string",
|
|
7749
|
-
demandOption: true,
|
|
7750
8100
|
describe: "Package slug (kebab-case)"
|
|
7751
8101
|
}).positional("vendor", {
|
|
7752
8102
|
type: "string",
|
|
7753
|
-
demandOption: true,
|
|
7754
8103
|
describe: "Vendor display name (PascalCase)"
|
|
7755
8104
|
}).option("capability", {
|
|
7756
8105
|
type: "string",
|
|
@@ -7770,7 +8119,7 @@ try {
|
|
|
7770
8119
|
describe: "Run post-scaffold pnpm install + typecheck + lint + test (default true; --no-verify skips)"
|
|
7771
8120
|
}), async (argv) => {
|
|
7772
8121
|
try {
|
|
7773
|
-
const vendor = argv
|
|
8122
|
+
const [slug, vendor] = await promptForPositionals(getEntry("plugin create"), argv);
|
|
7774
8123
|
const { capability, vendorEnv, baseUrl } = await resolvePluginCreateInputs({
|
|
7775
8124
|
capability: argv.capability,
|
|
7776
8125
|
vendorEnv: argv.vendorEnv,
|
|
@@ -7787,7 +8136,7 @@ try {
|
|
|
7787
8136
|
inputBaseUrl: () => input({ message: `REST base URL for the ${vendor} API (e.g. https://api.myvendor.com):` })
|
|
7788
8137
|
});
|
|
7789
8138
|
if (runPluginCreate({
|
|
7790
|
-
slug
|
|
8139
|
+
slug,
|
|
7791
8140
|
vendorName: vendor,
|
|
7792
8141
|
capability,
|
|
7793
8142
|
vendorEnv,
|
|
@@ -7805,14 +8154,14 @@ try {
|
|
|
7805
8154
|
}
|
|
7806
8155
|
throw err;
|
|
7807
8156
|
}
|
|
7808
|
-
}).command("upgrade", "Upgrade toolchain version pins across the repo", (y) => y.command("node
|
|
8157
|
+
}).command("upgrade", "Upgrade toolchain version pins across the repo", (y) => y.command("node [to]", "Scan the repo and update every Node.js version pin to a new major", (yy) => yy.positional("to", {
|
|
7809
8158
|
type: "number",
|
|
7810
|
-
demandOption: true,
|
|
7811
8159
|
describe: "Target Node.js major version (e.g., 22)"
|
|
7812
8160
|
}).option("from", {
|
|
7813
8161
|
type: "number",
|
|
7814
8162
|
describe: "Current major version to replace. Auto-detected from .nvmrc / engines.node when omitted."
|
|
7815
8163
|
}), async (argv) => {
|
|
8164
|
+
const [to] = await promptForPositionals(getEntry("upgrade node"), argv);
|
|
7816
8165
|
let extra = [];
|
|
7817
8166
|
try {
|
|
7818
8167
|
const raw = readFileSync(join(argv.cwd, "holocron.config.json"), "utf8");
|
|
@@ -7820,7 +8169,7 @@ try {
|
|
|
7820
8169
|
if (Array.isArray(extraRaw)) extra = extraRaw;
|
|
7821
8170
|
} catch {}
|
|
7822
8171
|
const report = await runUpgradeNode({
|
|
7823
|
-
to:
|
|
8172
|
+
to: Number(to),
|
|
7824
8173
|
...argv.from != null ? { from: argv.from } : {},
|
|
7825
8174
|
cwd: argv.cwd,
|
|
7826
8175
|
dryRun: argv.dryRun,
|
|
@@ -7844,31 +8193,31 @@ try {
|
|
|
7844
8193
|
if (report.message) getLogger().error(`upgrade deps: ${report.message}`);
|
|
7845
8194
|
process.exitCode = 1;
|
|
7846
8195
|
}
|
|
7847
|
-
}).
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
8196
|
+
}).command("$0", false, () => {}, async (argv) => {
|
|
8197
|
+
await launchMenu(COMMAND_REGISTRY.filter((e) => e.group === "upgrade"), argv, "upgrade — choose a subcommand:", "Run `holocron upgrade --help` to see available upgrade subcommands.");
|
|
8198
|
+
}), () => {}).command("auth <subcommand>", "Manage bootstrap credentials in the OS keyring", (y) => y.command("set [provider] [value]", "Verify + store a bootstrap token for a provider", (yy) => yy.positional("provider", { type: "string" }).positional("value", { type: "string" }), async (argv) => {
|
|
8199
|
+
const [provider] = await promptForPositionals(getEntry("auth set"), argv);
|
|
7851
8200
|
if ((await runAuthSet({
|
|
7852
|
-
provider
|
|
8201
|
+
provider,
|
|
7853
8202
|
...argv.value ? { positional: argv.value } : {},
|
|
7854
8203
|
...argv.org ? { org: argv.org } : {}
|
|
7855
8204
|
})).status === "fail") process.exitCode = 1;
|
|
7856
|
-
}).command("unset
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
}), (argv) => {
|
|
7860
|
-
|
|
7861
|
-
}).command("check <provider>", "Re-verify a stored bootstrap token", (yy) => yy.positional("provider", {
|
|
7862
|
-
type: "string",
|
|
7863
|
-
demandOption: true
|
|
7864
|
-
}), async (argv) => {
|
|
8205
|
+
}).command("unset [provider]", "Remove a stored bootstrap token", (yy) => yy.positional("provider", { type: "string" }), async (argv) => {
|
|
8206
|
+
const [provider] = await promptForPositionals(getEntry("auth unset"), argv);
|
|
8207
|
+
runAuthUnset({ provider });
|
|
8208
|
+
}).command("check [provider]", "Re-verify a stored bootstrap token", (yy) => yy.positional("provider", { type: "string" }), async (argv) => {
|
|
8209
|
+
const [provider] = await promptForPositionals(getEntry("auth check"), argv);
|
|
7865
8210
|
if ((await runAuthCheck({
|
|
7866
|
-
provider
|
|
8211
|
+
provider,
|
|
7867
8212
|
...argv.org ? { org: argv.org } : {}
|
|
7868
8213
|
})).status === "fail") process.exitCode = 1;
|
|
7869
8214
|
}).command("list", "List every provider with a stored bootstrap token", () => {}, async () => {
|
|
7870
8215
|
await runAuthList();
|
|
7871
|
-
}).
|
|
8216
|
+
}).command("$0", false, () => {}, async (argv) => {
|
|
8217
|
+
await launchMenu(COMMAND_REGISTRY.filter((e) => e.group === "auth"), argv, "auth — choose a subcommand:", "Run `holocron auth --help` to see available auth subcommands.");
|
|
8218
|
+
}), () => {}).command("$0", false, () => {}, async (argv) => {
|
|
8219
|
+
await launchMenu(COMMAND_REGISTRY.filter((e) => !e.group), argv);
|
|
8220
|
+
}).strict().help().epilogue(`Execution contexts:
|
|
7872
8221
|
global works from a bare 'npm i -g': ${commandsInContext("global").join(", ")}\n repo-aware needs ./holocron.config in cwd: ${commandsInContext("repo-aware").join(", ")}\n workspace also needs the plugin packages: ${commandsInContext("workspace").join(", ")}\n https://theholocron.github.io/holocron/execution-contexts`).fail((msg, err) => {
|
|
7873
8222
|
if (err instanceof Error && USER_FACING_ERRORS.has(err.name)) {
|
|
7874
8223
|
captureException(err);
|