forgemap 0.5.0-dev.89-5f2e4a1 → 0.5.0-dev.90-1fa1595
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 +8 -7
- package/dist/bin/forgemap.mjs +441 -331
- package/dist/bin/forgemap.mjs.map +1 -1
- package/package.json +1 -1
package/dist/bin/forgemap.mjs
CHANGED
|
@@ -1335,146 +1335,6 @@ async function installRcBlock(shell, label, lines, legacyLabels = []) {
|
|
|
1335
1335
|
};
|
|
1336
1336
|
}
|
|
1337
1337
|
//#endregion
|
|
1338
|
-
//#region src/commands/completion.ts
|
|
1339
|
-
var SUBCOMMANDS = [
|
|
1340
|
-
"clone",
|
|
1341
|
-
"import",
|
|
1342
|
-
"cleanup",
|
|
1343
|
-
"delete",
|
|
1344
|
-
"cd",
|
|
1345
|
-
"path",
|
|
1346
|
-
"open",
|
|
1347
|
-
"search",
|
|
1348
|
-
"pick",
|
|
1349
|
-
"status",
|
|
1350
|
-
"sync",
|
|
1351
|
-
"validate",
|
|
1352
|
-
"shell-init",
|
|
1353
|
-
"completion",
|
|
1354
|
-
"config"
|
|
1355
|
-
];
|
|
1356
|
-
var SLUG_COMMANDS = [
|
|
1357
|
-
"clone",
|
|
1358
|
-
"cd",
|
|
1359
|
-
"path",
|
|
1360
|
-
"open",
|
|
1361
|
-
"search",
|
|
1362
|
-
"pick",
|
|
1363
|
-
"delete"
|
|
1364
|
-
];
|
|
1365
|
-
function renderBash() {
|
|
1366
|
-
return `# forgemap bash completion — drop into your ~/.bashrc:
|
|
1367
|
-
# eval "$(forgemap completion bash)"
|
|
1368
|
-
_forgemap_completion() {
|
|
1369
|
-
local cur prev cmd words
|
|
1370
|
-
COMPREPLY=()
|
|
1371
|
-
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
1372
|
-
cmd="\${COMP_WORDS[1]}"
|
|
1373
|
-
|
|
1374
|
-
if [ "$COMP_CWORD" = "1" ]; then
|
|
1375
|
-
COMPREPLY=( $(compgen -W "${SUBCOMMANDS.join(" ")}" -- "$cur") )
|
|
1376
|
-
return
|
|
1377
|
-
fi
|
|
1378
|
-
|
|
1379
|
-
case "$cmd" in
|
|
1380
|
-
${SLUG_COMMANDS.join("|")})
|
|
1381
|
-
local slugs
|
|
1382
|
-
slugs=$(forgemap search '' --format slug 2>/dev/null)
|
|
1383
|
-
COMPREPLY=( $(compgen -W "$slugs" -- "$cur") )
|
|
1384
|
-
;;
|
|
1385
|
-
esac
|
|
1386
|
-
}
|
|
1387
|
-
complete -F _forgemap_completion forgemap
|
|
1388
|
-
`;
|
|
1389
|
-
}
|
|
1390
|
-
function renderZsh() {
|
|
1391
|
-
return `# forgemap zsh completion — drop into your ~/.zshrc:
|
|
1392
|
-
# eval "$(forgemap completion zsh)"
|
|
1393
|
-
_forgemap() {
|
|
1394
|
-
local context state line
|
|
1395
|
-
local -a subcommands slug_cmds
|
|
1396
|
-
subcommands=(${SUBCOMMANDS.map((s) => `'${s}'`).join(" ")})
|
|
1397
|
-
slug_cmds=(${SLUG_COMMANDS.map((s) => `'${s}'`).join(" ")})
|
|
1398
|
-
|
|
1399
|
-
_arguments -C \\
|
|
1400
|
-
'1: :->cmd' \\
|
|
1401
|
-
'*::arg:->args'
|
|
1402
|
-
|
|
1403
|
-
case "$state" in
|
|
1404
|
-
cmd) _describe 'forgemap subcommand' subcommands ;;
|
|
1405
|
-
args)
|
|
1406
|
-
if (( $slug_cmds[(I)$words[1]] )); then
|
|
1407
|
-
local -a slugs
|
|
1408
|
-
slugs=("\${(@f)$(forgemap search '' --format slug 2>/dev/null)}")
|
|
1409
|
-
_describe 'slug' slugs
|
|
1410
|
-
fi
|
|
1411
|
-
;;
|
|
1412
|
-
esac
|
|
1413
|
-
}
|
|
1414
|
-
compdef _forgemap forgemap
|
|
1415
|
-
`;
|
|
1416
|
-
}
|
|
1417
|
-
function renderFish$1() {
|
|
1418
|
-
const slugCmdsList = SLUG_COMMANDS.map((s) => `"${s}"`).join(" ");
|
|
1419
|
-
return `# forgemap fish completion — drop into your ~/.config/fish/config.fish:
|
|
1420
|
-
# forgemap completion fish | source
|
|
1421
|
-
|
|
1422
|
-
# Subcommands (depth 1).
|
|
1423
|
-
complete -c forgemap -f -n '__fish_use_subcommand' -a '${SUBCOMMANDS.join(" ")}'
|
|
1424
|
-
|
|
1425
|
-
# Slugs (depth 2) for commands that take one.
|
|
1426
|
-
function __forgemap_needs_slug
|
|
1427
|
-
set -l tokens (commandline -opc)
|
|
1428
|
-
set -l slug_cmds ${slugCmdsList}
|
|
1429
|
-
if test (count $tokens) -ge 2; and contains $tokens[2] $slug_cmds
|
|
1430
|
-
return 0
|
|
1431
|
-
end
|
|
1432
|
-
return 1
|
|
1433
|
-
end
|
|
1434
|
-
|
|
1435
|
-
complete -c forgemap -f -n '__forgemap_needs_slug' \\
|
|
1436
|
-
-a '(forgemap search "" --format slug 2>/dev/null)'
|
|
1437
|
-
`;
|
|
1438
|
-
}
|
|
1439
|
-
var completionCommand = defineCommand({
|
|
1440
|
-
meta: {
|
|
1441
|
-
name: "completion",
|
|
1442
|
-
description: "Print a shell completion script. Source via `eval \"$(forgemap completion)\"`."
|
|
1443
|
-
},
|
|
1444
|
-
args: {
|
|
1445
|
-
shell: {
|
|
1446
|
-
type: "positional",
|
|
1447
|
-
description: `Shell flavor (${SUPPORTED_SHELLS.join(", ")}). Auto-detected from $SHELL if omitted.`,
|
|
1448
|
-
required: false
|
|
1449
|
-
},
|
|
1450
|
-
install: {
|
|
1451
|
-
type: "boolean",
|
|
1452
|
-
description: "Append the completion loader to your shell's rc file (idempotent) instead of printing",
|
|
1453
|
-
default: false
|
|
1454
|
-
}
|
|
1455
|
-
},
|
|
1456
|
-
async run({ args }) {
|
|
1457
|
-
const requested = args.shell ?? detectShell();
|
|
1458
|
-
if (!SUPPORTED_SHELLS.includes(requested)) {
|
|
1459
|
-
consola.error(`Unsupported shell "${requested}". Supported: ${SUPPORTED_SHELLS.join(", ")}.`);
|
|
1460
|
-
process.exitCode = 1;
|
|
1461
|
-
return;
|
|
1462
|
-
}
|
|
1463
|
-
if (args.install) {
|
|
1464
|
-
const { status, rcFile } = await installRcBlock(requested, "completion", [requested === "fish" ? "forgemap completion fish | source" : `eval "$(forgemap completion ${requested})"`]);
|
|
1465
|
-
if (status === "present") consola.info(`forgemap completion already present in ${rcFile}.`);
|
|
1466
|
-
else {
|
|
1467
|
-
const verb = status === "updated" ? "Updated" : "Added";
|
|
1468
|
-
consola.success(`${verb} forgemap completion in ${rcFile}.`);
|
|
1469
|
-
consola.info(`Run \`source ${rcFile}\` or restart your shell to activate it.`);
|
|
1470
|
-
}
|
|
1471
|
-
return;
|
|
1472
|
-
}
|
|
1473
|
-
const out = requested === "fish" ? renderFish$1() : requested === "zsh" ? renderZsh() : renderBash();
|
|
1474
|
-
process.stdout.write(out);
|
|
1475
|
-
}
|
|
1476
|
-
});
|
|
1477
|
-
//#endregion
|
|
1478
1338
|
//#region src/config/write.ts
|
|
1479
1339
|
var HEADER = `/**
|
|
1480
1340
|
* forgemap configuration.
|
|
@@ -2419,7 +2279,7 @@ var infoCommand = defineCommand({
|
|
|
2419
2279
|
async run({ args }) {
|
|
2420
2280
|
const binary = resolveBinary(process.argv[1]);
|
|
2421
2281
|
const info = {
|
|
2422
|
-
version: "0.5.0-dev.
|
|
2282
|
+
version: "0.5.0-dev.90-1fa1595",
|
|
2423
2283
|
build: detectBuild(binary.resolved),
|
|
2424
2284
|
binary,
|
|
2425
2285
|
node: process.version,
|
|
@@ -2433,9 +2293,74 @@ var infoCommand = defineCommand({
|
|
|
2433
2293
|
}
|
|
2434
2294
|
});
|
|
2435
2295
|
//#endregion
|
|
2296
|
+
//#region src/repos/filter.ts
|
|
2297
|
+
var FLAG = "--filter";
|
|
2298
|
+
/**
|
|
2299
|
+
* Shared `--filter` option for the commands that enumerate repos
|
|
2300
|
+
* (`status`, `sync`, `list`), so the flag reads identically everywhere.
|
|
2301
|
+
*/
|
|
2302
|
+
var filterArg = {
|
|
2303
|
+
type: "string",
|
|
2304
|
+
description: "Restrict to repos whose owner or forge name matches. Repeatable; a repo passes if it matches any value."
|
|
2305
|
+
};
|
|
2306
|
+
/**
|
|
2307
|
+
* Recover every `--filter` occurrence from the raw argv.
|
|
2308
|
+
*
|
|
2309
|
+
* citty (0.2.2) parses through `node:util` `parseArgs` and never sets
|
|
2310
|
+
* `multiple: true`, so Node keeps only the **last** value of a repeated option:
|
|
2311
|
+
* `--filter a --filter b` reaches `args.filter` as `'b'`, silently dropping
|
|
2312
|
+
* `a`. The raw argv is the only place the full list survives.
|
|
2313
|
+
*/
|
|
2314
|
+
function collectFilterArgs(rawArgs) {
|
|
2315
|
+
const values = [];
|
|
2316
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
2317
|
+
const arg = rawArgs[i];
|
|
2318
|
+
if (arg === "--") break;
|
|
2319
|
+
if (arg === FLAG) {
|
|
2320
|
+
const next = rawArgs[i + 1];
|
|
2321
|
+
if (next !== void 0 && !next.startsWith("-")) {
|
|
2322
|
+
values.push(next);
|
|
2323
|
+
i++;
|
|
2324
|
+
}
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
if (arg.startsWith(`${FLAG}=`)) values.push(arg.slice(9));
|
|
2328
|
+
}
|
|
2329
|
+
return values;
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* Normalize the shapes a filter value arrives in — absent, a single string, or
|
|
2333
|
+
* a list — into a list, dropping blanks (`--filter ''`).
|
|
2334
|
+
*/
|
|
2335
|
+
function normalizeFilters(value) {
|
|
2336
|
+
if (value === void 0) return [];
|
|
2337
|
+
return (Array.isArray(value) ? value : [value]).map((v) => v.trim()).filter((v) => v.length > 0);
|
|
2338
|
+
}
|
|
2339
|
+
/**
|
|
2340
|
+
* The filter values for a run: the raw argv wins, since it is the only shape
|
|
2341
|
+
* that survives repetition. Fall back to the parsed value when the argv carries
|
|
2342
|
+
* no `--filter` at all, which is how the command is driven programmatically
|
|
2343
|
+
* (and in tests), where `rawArgs` may be empty.
|
|
2344
|
+
*/
|
|
2345
|
+
function resolveFilters(rawArgs, value) {
|
|
2346
|
+
const fromRawArgs = collectFilterArgs(rawArgs);
|
|
2347
|
+
return normalizeFilters(fromRawArgs.length > 0 ? fromRawArgs : value);
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Keep the repos matching any of `filters` (OR-combined). A value matches when
|
|
2351
|
+
* it equals the repo's owner or its forge name, compared case-insensitively —
|
|
2352
|
+
* forge and owner names are case-preserving but not case-significant. An empty
|
|
2353
|
+
* filter list is a no-op, so an unused flag never narrows the output.
|
|
2354
|
+
*/
|
|
2355
|
+
function filterRepos(repos, filters) {
|
|
2356
|
+
if (filters.length === 0) return repos;
|
|
2357
|
+
const wanted = new Set(filters.map((f) => f.toLowerCase()));
|
|
2358
|
+
return repos.filter((r) => wanted.has(r.owner.toLowerCase()) || wanted.has(r.forgeName.toLowerCase()));
|
|
2359
|
+
}
|
|
2360
|
+
//#endregion
|
|
2436
2361
|
//#region src/repos/match.ts
|
|
2437
2362
|
/**
|
|
2438
|
-
* The one Fuse configuration every fuzzy lookup shares — `
|
|
2363
|
+
* The one Fuse configuration every fuzzy lookup shares — `list`, `pick`
|
|
2439
2364
|
* and the fuzzy slug fallback in `path`/`open`. Keeping it in one place is
|
|
2440
2365
|
* what makes a query rank identically no matter which command runs it.
|
|
2441
2366
|
*/
|
|
@@ -2456,6 +2381,88 @@ function matchRepos(repos, query, limit) {
|
|
|
2456
2381
|
return createRepoFuse(repos).search(query, limit ? { limit } : void 0).map((r) => r.item);
|
|
2457
2382
|
}
|
|
2458
2383
|
//#endregion
|
|
2384
|
+
//#region src/commands/list.ts
|
|
2385
|
+
function renderTree$1(repos) {
|
|
2386
|
+
const byForge = /* @__PURE__ */ new Map();
|
|
2387
|
+
for (const r of repos) {
|
|
2388
|
+
let owners = byForge.get(r.forgeName);
|
|
2389
|
+
if (!owners) {
|
|
2390
|
+
owners = /* @__PURE__ */ new Map();
|
|
2391
|
+
byForge.set(r.forgeName, owners);
|
|
2392
|
+
}
|
|
2393
|
+
const list = owners.get(r.owner);
|
|
2394
|
+
if (list) list.push(r);
|
|
2395
|
+
else owners.set(r.owner, [r]);
|
|
2396
|
+
}
|
|
2397
|
+
return formatTree(Array.from(byForge, ([forge, owners]) => ({
|
|
2398
|
+
text: colors.bold(forge),
|
|
2399
|
+
children: Array.from(owners, ([owner, items]) => ({
|
|
2400
|
+
text: owner,
|
|
2401
|
+
children: items.map((r) => ({ text: `${colors.cyan(r.repo)} ${colors.dim(r.localPath)}` }))
|
|
2402
|
+
}))
|
|
2403
|
+
})));
|
|
2404
|
+
}
|
|
2405
|
+
var listCommand = defineCommand({
|
|
2406
|
+
meta: {
|
|
2407
|
+
name: "list",
|
|
2408
|
+
description: "List cloned repos; with a query, fuzzy-match by owner/repo and print matches"
|
|
2409
|
+
},
|
|
2410
|
+
args: {
|
|
2411
|
+
query: {
|
|
2412
|
+
type: "positional",
|
|
2413
|
+
description: "Optional search term (matched fuzzily against <owner>/<repo>). Omit to list every repo.",
|
|
2414
|
+
required: false
|
|
2415
|
+
},
|
|
2416
|
+
format: {
|
|
2417
|
+
type: "string",
|
|
2418
|
+
description: "Output format: auto (default), pretty, path, or slug. auto picks pretty in a TTY, path when piped.",
|
|
2419
|
+
default: "auto"
|
|
2420
|
+
},
|
|
2421
|
+
filter: filterArg,
|
|
2422
|
+
limit: {
|
|
2423
|
+
type: "string",
|
|
2424
|
+
description: "Maximum number of matches to print (default: unlimited)"
|
|
2425
|
+
},
|
|
2426
|
+
config: {
|
|
2427
|
+
type: "string",
|
|
2428
|
+
description: "Path to forgemap.config.ts (overrides walk-up discovery)"
|
|
2429
|
+
}
|
|
2430
|
+
},
|
|
2431
|
+
async run({ args, rawArgs }) {
|
|
2432
|
+
const loaded = await loadForgeMapConfig({ configFile: args.config });
|
|
2433
|
+
const configDir = loaded.configFile ? dirname(loaded.configFile) : loaded.cwd;
|
|
2434
|
+
const repos = filterRepos(await scanRepos({
|
|
2435
|
+
config: loaded.config,
|
|
2436
|
+
configDir
|
|
2437
|
+
}), resolveFilters(rawArgs, args.filter));
|
|
2438
|
+
const limit = args.limit ? Number.parseInt(args.limit, 10) : void 0;
|
|
2439
|
+
const query = args.query ?? "";
|
|
2440
|
+
const items = matchRepos(repos, query, limit);
|
|
2441
|
+
const allowed = [
|
|
2442
|
+
"auto",
|
|
2443
|
+
"pretty",
|
|
2444
|
+
"path",
|
|
2445
|
+
"slug"
|
|
2446
|
+
];
|
|
2447
|
+
if (!allowed.includes(args.format)) {
|
|
2448
|
+
consola.error(`Invalid --format value "${args.format}". Allowed: ${allowed.join(", ")}.`);
|
|
2449
|
+
process.exitCode = 1;
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
const requested = args.format;
|
|
2453
|
+
const format = requested === "auto" ? process.stdout.isTTY ? "pretty" : "path" : requested;
|
|
2454
|
+
if (items.length === 0) {
|
|
2455
|
+
if (format === "pretty") consola.info(query ? `No matches for "${query}".` : "No repos found.");
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
if (format === "pretty") {
|
|
2459
|
+
process.stdout.write(`${renderTree$1(items)}\n`);
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
for (const item of items) process.stdout.write(`${format === "slug" ? item.slug : item.localPath}\n`);
|
|
2463
|
+
}
|
|
2464
|
+
});
|
|
2465
|
+
//#endregion
|
|
2459
2466
|
//#region src/repos/picker.ts
|
|
2460
2467
|
/**
|
|
2461
2468
|
* Show the interactive repo picker and return the chosen local path
|
|
@@ -2712,7 +2719,7 @@ var pickCommand = defineCommand({
|
|
|
2712
2719
|
return;
|
|
2713
2720
|
}
|
|
2714
2721
|
if (!canPrompt()) {
|
|
2715
|
-
consola.error("pick requires an interactive terminal. Use `forgemap
|
|
2722
|
+
consola.error("pick requires an interactive terminal. Use `forgemap list` for non-interactive output.");
|
|
2716
2723
|
process.exitCode = 1;
|
|
2717
2724
|
return;
|
|
2718
2725
|
}
|
|
@@ -2721,152 +2728,6 @@ var pickCommand = defineCommand({
|
|
|
2721
2728
|
}
|
|
2722
2729
|
});
|
|
2723
2730
|
//#endregion
|
|
2724
|
-
//#region src/repos/filter.ts
|
|
2725
|
-
var FLAG = "--filter";
|
|
2726
|
-
/**
|
|
2727
|
-
* Shared `--filter` option for the commands that enumerate repos
|
|
2728
|
-
* (`status`, `sync`, `search`), so the flag reads identically everywhere.
|
|
2729
|
-
*/
|
|
2730
|
-
var filterArg = {
|
|
2731
|
-
type: "string",
|
|
2732
|
-
description: "Restrict to repos whose owner or forge name matches. Repeatable; a repo passes if it matches any value."
|
|
2733
|
-
};
|
|
2734
|
-
/**
|
|
2735
|
-
* Recover every `--filter` occurrence from the raw argv.
|
|
2736
|
-
*
|
|
2737
|
-
* citty (0.2.2) parses through `node:util` `parseArgs` and never sets
|
|
2738
|
-
* `multiple: true`, so Node keeps only the **last** value of a repeated option:
|
|
2739
|
-
* `--filter a --filter b` reaches `args.filter` as `'b'`, silently dropping
|
|
2740
|
-
* `a`. The raw argv is the only place the full list survives.
|
|
2741
|
-
*/
|
|
2742
|
-
function collectFilterArgs(rawArgs) {
|
|
2743
|
-
const values = [];
|
|
2744
|
-
for (let i = 0; i < rawArgs.length; i++) {
|
|
2745
|
-
const arg = rawArgs[i];
|
|
2746
|
-
if (arg === "--") break;
|
|
2747
|
-
if (arg === FLAG) {
|
|
2748
|
-
const next = rawArgs[i + 1];
|
|
2749
|
-
if (next !== void 0 && !next.startsWith("-")) {
|
|
2750
|
-
values.push(next);
|
|
2751
|
-
i++;
|
|
2752
|
-
}
|
|
2753
|
-
continue;
|
|
2754
|
-
}
|
|
2755
|
-
if (arg.startsWith(`${FLAG}=`)) values.push(arg.slice(9));
|
|
2756
|
-
}
|
|
2757
|
-
return values;
|
|
2758
|
-
}
|
|
2759
|
-
/**
|
|
2760
|
-
* Normalize the shapes a filter value arrives in — absent, a single string, or
|
|
2761
|
-
* a list — into a list, dropping blanks (`--filter ''`).
|
|
2762
|
-
*/
|
|
2763
|
-
function normalizeFilters(value) {
|
|
2764
|
-
if (value === void 0) return [];
|
|
2765
|
-
return (Array.isArray(value) ? value : [value]).map((v) => v.trim()).filter((v) => v.length > 0);
|
|
2766
|
-
}
|
|
2767
|
-
/**
|
|
2768
|
-
* The filter values for a run: the raw argv wins, since it is the only shape
|
|
2769
|
-
* that survives repetition. Fall back to the parsed value when the argv carries
|
|
2770
|
-
* no `--filter` at all, which is how the command is driven programmatically
|
|
2771
|
-
* (and in tests), where `rawArgs` may be empty.
|
|
2772
|
-
*/
|
|
2773
|
-
function resolveFilters(rawArgs, value) {
|
|
2774
|
-
const fromRawArgs = collectFilterArgs(rawArgs);
|
|
2775
|
-
return normalizeFilters(fromRawArgs.length > 0 ? fromRawArgs : value);
|
|
2776
|
-
}
|
|
2777
|
-
/**
|
|
2778
|
-
* Keep the repos matching any of `filters` (OR-combined). A value matches when
|
|
2779
|
-
* it equals the repo's owner or its forge name, compared case-insensitively —
|
|
2780
|
-
* forge and owner names are case-preserving but not case-significant. An empty
|
|
2781
|
-
* filter list is a no-op, so an unused flag never narrows the output.
|
|
2782
|
-
*/
|
|
2783
|
-
function filterRepos(repos, filters) {
|
|
2784
|
-
if (filters.length === 0) return repos;
|
|
2785
|
-
const wanted = new Set(filters.map((f) => f.toLowerCase()));
|
|
2786
|
-
return repos.filter((r) => wanted.has(r.owner.toLowerCase()) || wanted.has(r.forgeName.toLowerCase()));
|
|
2787
|
-
}
|
|
2788
|
-
//#endregion
|
|
2789
|
-
//#region src/commands/search.ts
|
|
2790
|
-
function renderTree$1(repos) {
|
|
2791
|
-
const byForge = /* @__PURE__ */ new Map();
|
|
2792
|
-
for (const r of repos) {
|
|
2793
|
-
let owners = byForge.get(r.forgeName);
|
|
2794
|
-
if (!owners) {
|
|
2795
|
-
owners = /* @__PURE__ */ new Map();
|
|
2796
|
-
byForge.set(r.forgeName, owners);
|
|
2797
|
-
}
|
|
2798
|
-
const list = owners.get(r.owner);
|
|
2799
|
-
if (list) list.push(r);
|
|
2800
|
-
else owners.set(r.owner, [r]);
|
|
2801
|
-
}
|
|
2802
|
-
return formatTree(Array.from(byForge, ([forge, owners]) => ({
|
|
2803
|
-
text: colors.bold(forge),
|
|
2804
|
-
children: Array.from(owners, ([owner, items]) => ({
|
|
2805
|
-
text: owner,
|
|
2806
|
-
children: items.map((r) => ({ text: `${colors.cyan(r.repo)} ${colors.dim(r.localPath)}` }))
|
|
2807
|
-
}))
|
|
2808
|
-
})));
|
|
2809
|
-
}
|
|
2810
|
-
var searchCommand = defineCommand({
|
|
2811
|
-
meta: {
|
|
2812
|
-
name: "search",
|
|
2813
|
-
description: "Fuzzy-search cloned repos by owner/repo and print matching repos"
|
|
2814
|
-
},
|
|
2815
|
-
args: {
|
|
2816
|
-
query: {
|
|
2817
|
-
type: "positional",
|
|
2818
|
-
description: "Search term (matched fuzzily against <owner>/<repo>)",
|
|
2819
|
-
required: true
|
|
2820
|
-
},
|
|
2821
|
-
format: {
|
|
2822
|
-
type: "string",
|
|
2823
|
-
description: "Output format: auto (default), pretty, path, or slug. auto picks pretty in a TTY, path when piped.",
|
|
2824
|
-
default: "auto"
|
|
2825
|
-
},
|
|
2826
|
-
filter: filterArg,
|
|
2827
|
-
limit: {
|
|
2828
|
-
type: "string",
|
|
2829
|
-
description: "Maximum number of matches to print (default: unlimited)"
|
|
2830
|
-
},
|
|
2831
|
-
config: {
|
|
2832
|
-
type: "string",
|
|
2833
|
-
description: "Path to forgemap.config.ts (overrides walk-up discovery)"
|
|
2834
|
-
}
|
|
2835
|
-
},
|
|
2836
|
-
async run({ args, rawArgs }) {
|
|
2837
|
-
const loaded = await loadForgeMapConfig({ configFile: args.config });
|
|
2838
|
-
const configDir = loaded.configFile ? dirname(loaded.configFile) : loaded.cwd;
|
|
2839
|
-
const repos = filterRepos(await scanRepos({
|
|
2840
|
-
config: loaded.config,
|
|
2841
|
-
configDir
|
|
2842
|
-
}), resolveFilters(rawArgs, args.filter));
|
|
2843
|
-
const limit = args.limit ? Number.parseInt(args.limit, 10) : void 0;
|
|
2844
|
-
const items = matchRepos(repos, args.query, limit);
|
|
2845
|
-
const allowed = [
|
|
2846
|
-
"auto",
|
|
2847
|
-
"pretty",
|
|
2848
|
-
"path",
|
|
2849
|
-
"slug"
|
|
2850
|
-
];
|
|
2851
|
-
if (!allowed.includes(args.format)) {
|
|
2852
|
-
consola.error(`Invalid --format value "${args.format}". Allowed: ${allowed.join(", ")}.`);
|
|
2853
|
-
process.exitCode = 1;
|
|
2854
|
-
return;
|
|
2855
|
-
}
|
|
2856
|
-
const requested = args.format;
|
|
2857
|
-
const format = requested === "auto" ? process.stdout.isTTY ? "pretty" : "path" : requested;
|
|
2858
|
-
if (items.length === 0) {
|
|
2859
|
-
if (format === "pretty") consola.info(`No matches for "${args.query}".`);
|
|
2860
|
-
return;
|
|
2861
|
-
}
|
|
2862
|
-
if (format === "pretty") {
|
|
2863
|
-
process.stdout.write(`${renderTree$1(items)}\n`);
|
|
2864
|
-
return;
|
|
2865
|
-
}
|
|
2866
|
-
for (const item of items) process.stdout.write(`${format === "slug" ? item.slug : item.localPath}\n`);
|
|
2867
|
-
}
|
|
2868
|
-
});
|
|
2869
|
-
//#endregion
|
|
2870
2731
|
//#region src/commands/shell-init.ts
|
|
2871
2732
|
/** Append (idempotently) a loader that, plus completion, sets up the shell so
|
|
2872
2733
|
* the user only has to re-source their rc file. */
|
|
@@ -2896,7 +2757,7 @@ ${name}() {
|
|
|
2896
2757
|
target=$(command forgemap pick) || return $?
|
|
2897
2758
|
else
|
|
2898
2759
|
local matches
|
|
2899
|
-
matches=$(command forgemap
|
|
2760
|
+
matches=$(command forgemap list "$1" --format path)
|
|
2900
2761
|
local count
|
|
2901
2762
|
count=$(printf '%s' "$matches" | grep -c '^/' || true)
|
|
2902
2763
|
if [ "$count" = "1" ]; then
|
|
@@ -2915,7 +2776,7 @@ ${name}() {
|
|
|
2915
2776
|
}
|
|
2916
2777
|
`;
|
|
2917
2778
|
}
|
|
2918
|
-
function renderFish(name) {
|
|
2779
|
+
function renderFish$1(name) {
|
|
2919
2780
|
return `# forgemap shell integration — drop into your ~/.config/fish/config.fish:
|
|
2920
2781
|
# forgemap shell-init fish | source
|
|
2921
2782
|
|
|
@@ -2926,7 +2787,7 @@ function ${name} --description "forgemap with cd interception"
|
|
|
2926
2787
|
if test (count $argv) -eq 0
|
|
2927
2788
|
set target (command forgemap pick); or return $status
|
|
2928
2789
|
else
|
|
2929
|
-
set matches (command forgemap
|
|
2790
|
+
set matches (command forgemap list $argv[1] --format path)
|
|
2930
2791
|
set count (count $matches)
|
|
2931
2792
|
if test $count -eq 1
|
|
2932
2793
|
set target $matches[1]
|
|
@@ -2978,7 +2839,7 @@ var shellInitCommand = defineCommand({
|
|
|
2978
2839
|
await install(requested, name);
|
|
2979
2840
|
return;
|
|
2980
2841
|
}
|
|
2981
|
-
const out = requested === "fish" ? renderFish(name) : renderPosix(name);
|
|
2842
|
+
const out = requested === "fish" ? renderFish$1(name) : renderPosix(name);
|
|
2982
2843
|
process.stdout.write(out);
|
|
2983
2844
|
}
|
|
2984
2845
|
});
|
|
@@ -3326,12 +3187,296 @@ function severitySymbol(severity) {
|
|
|
3326
3187
|
if (severity === "warn") return colors.yellow("!");
|
|
3327
3188
|
return colors.red("✗");
|
|
3328
3189
|
}
|
|
3190
|
+
var validateCommand = defineCommand({
|
|
3191
|
+
meta: {
|
|
3192
|
+
name: "validate",
|
|
3193
|
+
description: "Preflight: check the config schema, required CLI tools, and root directory"
|
|
3194
|
+
},
|
|
3195
|
+
args: {
|
|
3196
|
+
json: {
|
|
3197
|
+
type: "boolean",
|
|
3198
|
+
description: "Emit a machine-readable JSON report",
|
|
3199
|
+
default: false
|
|
3200
|
+
},
|
|
3201
|
+
config: {
|
|
3202
|
+
type: "string",
|
|
3203
|
+
description: "Path to forgemap.config.ts (overrides walk-up discovery)"
|
|
3204
|
+
}
|
|
3205
|
+
},
|
|
3206
|
+
async run({ args }) {
|
|
3207
|
+
const loaded = await loadForgeMapConfig({ configFile: args.config });
|
|
3208
|
+
const configDir = loaded.configFile ? dirname(loaded.configFile) : loaded.cwd;
|
|
3209
|
+
const checks = await runChecks(loaded.config, configDir);
|
|
3210
|
+
const ok = checks.every((c) => c.severity !== "fail");
|
|
3211
|
+
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
3212
|
+
ok,
|
|
3213
|
+
checks
|
|
3214
|
+
}, null, 2)}\n`);
|
|
3215
|
+
else {
|
|
3216
|
+
for (const c of checks) process.stdout.write(`${severitySymbol(c.severity)} ${c.name.padEnd(22)} ${colors.dim(c.message)}\n`);
|
|
3217
|
+
process.stdout.write(`\n${ok ? colors.green("All checks passed.") : colors.red("Validation failed.")}\n`);
|
|
3218
|
+
}
|
|
3219
|
+
if (!ok) {
|
|
3220
|
+
process.exitCode = 1;
|
|
3221
|
+
return;
|
|
3222
|
+
}
|
|
3223
|
+
if (!loaded.configFile) consola.warn("No forgemap.config.ts found — using built-in defaults. Run `forgemap config init` to materialize one.");
|
|
3224
|
+
}
|
|
3225
|
+
});
|
|
3226
|
+
//#endregion
|
|
3227
|
+
//#region src/commands/completion.ts
|
|
3228
|
+
var SLUG_COMMANDS = [
|
|
3229
|
+
"clone",
|
|
3230
|
+
"cd",
|
|
3231
|
+
"path",
|
|
3232
|
+
"open",
|
|
3233
|
+
"list",
|
|
3234
|
+
"pick",
|
|
3235
|
+
"delete"
|
|
3236
|
+
];
|
|
3237
|
+
var STATIC_FLAG_VALUES = {
|
|
3238
|
+
list: { "--format": [
|
|
3239
|
+
"auto",
|
|
3240
|
+
"pretty",
|
|
3241
|
+
"path",
|
|
3242
|
+
"slug"
|
|
3243
|
+
] },
|
|
3244
|
+
status: { "--format": ["pretty", "json"] },
|
|
3245
|
+
import: {
|
|
3246
|
+
"--format": ["pretty", "json"],
|
|
3247
|
+
"--type": ["forgemap"]
|
|
3248
|
+
}
|
|
3249
|
+
};
|
|
3250
|
+
var SHELL_POSITIONAL = /* @__PURE__ */ new Set(["completion", "shell-init"]);
|
|
3251
|
+
/** Every forgemap command declares `args` as a plain object literal (or omits
|
|
3252
|
+
* it, like `config`), never a thunk — so no `Resolvable` unwrapping is needed. */
|
|
3253
|
+
function argsOf(cmd) {
|
|
3254
|
+
return cmd.args ?? {};
|
|
3255
|
+
}
|
|
3256
|
+
/** Flag names a command exposes, derived from its `defineCommand` args so a new
|
|
3257
|
+
* flag surfaces in completion automatically. Positionals are handled
|
|
3258
|
+
* separately; negatable booleans (those with a `negativeDescription`) also get
|
|
3259
|
+
* their `--no-<flag>` form. */
|
|
3260
|
+
function flagsOf(cmd) {
|
|
3261
|
+
const flags = [];
|
|
3262
|
+
for (const [name, def] of Object.entries(argsOf(cmd))) {
|
|
3263
|
+
if (def.type === "positional") continue;
|
|
3264
|
+
flags.push(`--${name}`);
|
|
3265
|
+
if (def.type === "boolean" && def.negativeDescription) flags.push(`--no-${name}`);
|
|
3266
|
+
}
|
|
3267
|
+
return flags;
|
|
3268
|
+
}
|
|
3269
|
+
/** The ordered subcommand registry, mirroring `rootCommand.subCommands` in
|
|
3270
|
+
* cli.ts. It is kept here rather than imported from cli.ts because that would
|
|
3271
|
+
* form a cycle (cli → completion → cli). Built lazily so the self-reference to
|
|
3272
|
+
* `completionCommand` resolves after the module finishes initializing. */
|
|
3273
|
+
function commandSpecs() {
|
|
3274
|
+
return [
|
|
3275
|
+
["clone", cloneCommand],
|
|
3276
|
+
["import", importCommand],
|
|
3277
|
+
["cleanup", cleanupCommand],
|
|
3278
|
+
["delete", deleteCommand],
|
|
3279
|
+
["cd", cdCommand],
|
|
3280
|
+
["path", pathCommand],
|
|
3281
|
+
["open", openCommand],
|
|
3282
|
+
["list", listCommand],
|
|
3283
|
+
["pick", pickCommand],
|
|
3284
|
+
["status", statusCommand],
|
|
3285
|
+
["sync", syncCommand],
|
|
3286
|
+
["validate", validateCommand],
|
|
3287
|
+
["info", infoCommand],
|
|
3288
|
+
["completion", completionCommand],
|
|
3289
|
+
["shell-init", shellInitCommand],
|
|
3290
|
+
["config", configCommand]
|
|
3291
|
+
].map(([name, cmd]) => ({
|
|
3292
|
+
name,
|
|
3293
|
+
flags: flagsOf(cmd),
|
|
3294
|
+
flagValues: STATIC_FLAG_VALUES[name] ?? {},
|
|
3295
|
+
positionalValues: SHELL_POSITIONAL.has(name) ? [...SUPPORTED_SHELLS] : [],
|
|
3296
|
+
slugs: SLUG_COMMANDS.includes(name)
|
|
3297
|
+
}));
|
|
3298
|
+
}
|
|
3299
|
+
function flagValuePairs(specs) {
|
|
3300
|
+
return specs.flatMap((s) => Object.entries(s.flagValues).map(([flag, values]) => [
|
|
3301
|
+
s.name,
|
|
3302
|
+
flag,
|
|
3303
|
+
values
|
|
3304
|
+
]));
|
|
3305
|
+
}
|
|
3306
|
+
function renderBash(specs) {
|
|
3307
|
+
const names = specs.map((s) => s.name).join(" ");
|
|
3308
|
+
const valueArms = flagValuePairs(specs).map(([cmd, flag, values]) => ` ${cmd}:${flag}) COMPREPLY=( $(compgen -W "${values.join(" ")}" -- "$cur") ); return ;;`).join("\n");
|
|
3309
|
+
const flagArms = specs.filter((s) => s.flags.length > 0).map((s) => ` ${s.name}) flags="${s.flags.join(" ")}" ;;`).join("\n");
|
|
3310
|
+
const slugCmds = specs.filter((s) => s.slugs).map((s) => s.name);
|
|
3311
|
+
return `# forgemap bash completion — drop into your ~/.bashrc:
|
|
3312
|
+
# eval "$(forgemap completion bash)"
|
|
3313
|
+
_forgemap_completion() {
|
|
3314
|
+
local cur prev cmd flags
|
|
3315
|
+
COMPREPLY=()
|
|
3316
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
3317
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
3318
|
+
cmd="\${COMP_WORDS[1]}"
|
|
3319
|
+
|
|
3320
|
+
if [ "$COMP_CWORD" = "1" ]; then
|
|
3321
|
+
COMPREPLY=( $(compgen -W "${names}" -- "$cur") )
|
|
3322
|
+
return
|
|
3323
|
+
fi
|
|
3324
|
+
|
|
3325
|
+
# Values for flags with a fixed set (e.g. --format), keyed by "<cmd>:<flag>".
|
|
3326
|
+
case "$cmd:$prev" in
|
|
3327
|
+
${valueArms}
|
|
3328
|
+
esac
|
|
3329
|
+
|
|
3330
|
+
# Flag names for the current subcommand.
|
|
3331
|
+
if [[ "$cur" == -* ]]; then
|
|
3332
|
+
case "$cmd" in
|
|
3333
|
+
${flagArms}
|
|
3334
|
+
esac
|
|
3335
|
+
COMPREPLY=( $(compgen -W "$flags" -- "$cur") )
|
|
3336
|
+
return
|
|
3337
|
+
fi
|
|
3338
|
+
|
|
3339
|
+
# Positional values (repo slugs, or a shell name).
|
|
3340
|
+
case "$cmd" in
|
|
3341
|
+
${[slugCmds.length > 0 ? ` ${slugCmds.join("|")})
|
|
3342
|
+
local slugs
|
|
3343
|
+
slugs=$(forgemap list --format slug 2>/dev/null)
|
|
3344
|
+
COMPREPLY=( $(compgen -W "$slugs" -- "$cur") )
|
|
3345
|
+
;;` : "", ...specs.filter((s) => s.positionalValues.length > 0).map((s) => ` ${s.name}) COMPREPLY=( $(compgen -W "${s.positionalValues.join(" ")}" -- "$cur") ) ;;`)].filter(Boolean).join("\n")}
|
|
3346
|
+
esac
|
|
3347
|
+
}
|
|
3348
|
+
complete -F _forgemap_completion forgemap
|
|
3349
|
+
`;
|
|
3350
|
+
}
|
|
3351
|
+
function renderZsh(specs) {
|
|
3352
|
+
return `# forgemap zsh completion — drop into your ~/.zshrc:
|
|
3353
|
+
# eval "$(forgemap completion zsh)"
|
|
3354
|
+
_forgemap() {
|
|
3355
|
+
local -a subcommands
|
|
3356
|
+
subcommands=(${specs.map((s) => `'${s.name}'`).join(" ")})
|
|
3357
|
+
local cmd="\${words[2]}"
|
|
3358
|
+
local prev="\${words[CURRENT-1]}"
|
|
3359
|
+
local cur="\${words[CURRENT]}"
|
|
3360
|
+
|
|
3361
|
+
if (( CURRENT == 2 )); then
|
|
3362
|
+
_describe 'forgemap subcommand' subcommands
|
|
3363
|
+
return
|
|
3364
|
+
fi
|
|
3365
|
+
|
|
3366
|
+
# Values for flags with a fixed set (e.g. --format), keyed by "<cmd>:<flag>".
|
|
3367
|
+
case "$cmd:$prev" in
|
|
3368
|
+
${flagValuePairs(specs).map(([cmd, flag, values]) => ` ${cmd}:${flag}) compadd ${values.join(" ")}; return ;;`).join("\n")}
|
|
3369
|
+
esac
|
|
3370
|
+
|
|
3371
|
+
# Flag names for the current subcommand.
|
|
3372
|
+
if [[ "$cur" == -* ]]; then
|
|
3373
|
+
case "$cmd" in
|
|
3374
|
+
${specs.filter((s) => s.flags.length > 0).map((s) => ` ${s.name}) compadd -- ${s.flags.join(" ")}; return ;;`).join("\n")}
|
|
3375
|
+
esac
|
|
3376
|
+
return
|
|
3377
|
+
fi
|
|
3378
|
+
|
|
3379
|
+
# Positional values (repo slugs, or a shell name).
|
|
3380
|
+
case "$cmd" in
|
|
3381
|
+
${specs.filter((s) => s.slugs).map((s) => s.name).join("|")})
|
|
3382
|
+
local -a slugs
|
|
3383
|
+
slugs=("\${(@f)$(forgemap list --format slug 2>/dev/null)}")
|
|
3384
|
+
_describe 'slug' slugs
|
|
3385
|
+
;;
|
|
3386
|
+
${specs.filter((s) => s.positionalValues.length > 0).map((s) => ` ${s.name}) compadd ${s.positionalValues.join(" ")} ;;`).join("\n")}
|
|
3387
|
+
esac
|
|
3388
|
+
}
|
|
3389
|
+
compdef _forgemap forgemap
|
|
3390
|
+
`;
|
|
3391
|
+
}
|
|
3392
|
+
function renderFish(specs) {
|
|
3393
|
+
const names = specs.map((s) => s.name).join(" ");
|
|
3394
|
+
const flagLines = specs.flatMap((s) => s.flags.map((flag) => {
|
|
3395
|
+
const long = flag.replace(/^--/, "");
|
|
3396
|
+
const values = s.flagValues[flag];
|
|
3397
|
+
const valuePart = values ? ` -x -a '${values.join(" ")}'` : "";
|
|
3398
|
+
return `complete -c forgemap -n '__fish_seen_subcommand_from ${s.name}' -l ${long}${valuePart}`;
|
|
3399
|
+
})).join("\n");
|
|
3400
|
+
const shellCmds = specs.filter((s) => s.positionalValues.length > 0);
|
|
3401
|
+
return `# forgemap fish completion — drop into your ~/.config/fish/config.fish:
|
|
3402
|
+
# forgemap completion fish | source
|
|
3403
|
+
|
|
3404
|
+
# Subcommands (depth 1).
|
|
3405
|
+
complete -c forgemap -f -n '__fish_use_subcommand' -a '${names}'
|
|
3406
|
+
|
|
3407
|
+
# Flags per subcommand (with fixed value sets where applicable).
|
|
3408
|
+
${flagLines}
|
|
3409
|
+
|
|
3410
|
+
# Shell flavor for completion / shell-init (depth 2).
|
|
3411
|
+
function __forgemap_needs_shell
|
|
3412
|
+
set -l tokens (commandline -opc)
|
|
3413
|
+
set -l shell_cmds ${shellCmds.map((s) => `"${s.name}"`).join(" ")}
|
|
3414
|
+
if test (count $tokens) -ge 2; and contains $tokens[2] $shell_cmds
|
|
3415
|
+
return 0
|
|
3416
|
+
end
|
|
3417
|
+
return 1
|
|
3418
|
+
end
|
|
3419
|
+
complete -c forgemap -f -n '__forgemap_needs_shell' -a '${shellCmds[0]?.positionalValues.join(" ") ?? ""}'
|
|
3420
|
+
|
|
3421
|
+
# Slugs (depth 2) for commands that take one.
|
|
3422
|
+
function __forgemap_needs_slug
|
|
3423
|
+
set -l tokens (commandline -opc)
|
|
3424
|
+
set -l slug_cmds ${specs.filter((s) => s.slugs).map((s) => `"${s.name}"`).join(" ")}
|
|
3425
|
+
if test (count $tokens) -ge 2; and contains $tokens[2] $slug_cmds
|
|
3426
|
+
return 0
|
|
3427
|
+
end
|
|
3428
|
+
return 1
|
|
3429
|
+
end
|
|
3430
|
+
|
|
3431
|
+
complete -c forgemap -f -n '__forgemap_needs_slug' \\
|
|
3432
|
+
-a '(forgemap list --format slug 2>/dev/null)'
|
|
3433
|
+
`;
|
|
3434
|
+
}
|
|
3435
|
+
var completionCommand = defineCommand({
|
|
3436
|
+
meta: {
|
|
3437
|
+
name: "completion",
|
|
3438
|
+
description: "Print a shell completion script. Source via `eval \"$(forgemap completion)\"`."
|
|
3439
|
+
},
|
|
3440
|
+
args: {
|
|
3441
|
+
shell: {
|
|
3442
|
+
type: "positional",
|
|
3443
|
+
description: `Shell flavor (${SUPPORTED_SHELLS.join(", ")}). Auto-detected from $SHELL if omitted.`,
|
|
3444
|
+
required: false
|
|
3445
|
+
},
|
|
3446
|
+
install: {
|
|
3447
|
+
type: "boolean",
|
|
3448
|
+
description: "Append the completion loader to your shell's rc file (idempotent) instead of printing",
|
|
3449
|
+
default: false
|
|
3450
|
+
}
|
|
3451
|
+
},
|
|
3452
|
+
async run({ args }) {
|
|
3453
|
+
const requested = args.shell ?? detectShell();
|
|
3454
|
+
if (!SUPPORTED_SHELLS.includes(requested)) {
|
|
3455
|
+
consola.error(`Unsupported shell "${requested}". Supported: ${SUPPORTED_SHELLS.join(", ")}.`);
|
|
3456
|
+
process.exitCode = 1;
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
if (args.install) {
|
|
3460
|
+
const { status, rcFile } = await installRcBlock(requested, "completion", [requested === "fish" ? "forgemap completion fish | source" : `eval "$(forgemap completion ${requested})"`]);
|
|
3461
|
+
if (status === "present") consola.info(`forgemap completion already present in ${rcFile}.`);
|
|
3462
|
+
else {
|
|
3463
|
+
const verb = status === "updated" ? "Updated" : "Added";
|
|
3464
|
+
consola.success(`${verb} forgemap completion in ${rcFile}.`);
|
|
3465
|
+
consola.info(`Run \`source ${rcFile}\` or restart your shell to activate it.`);
|
|
3466
|
+
}
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
3469
|
+
const specs = commandSpecs();
|
|
3470
|
+
const out = requested === "fish" ? renderFish(specs) : requested === "zsh" ? renderZsh(specs) : renderBash(specs);
|
|
3471
|
+
process.stdout.write(out);
|
|
3472
|
+
}
|
|
3473
|
+
});
|
|
3329
3474
|
//#endregion
|
|
3330
3475
|
//#region src/bin/forgemap.ts
|
|
3331
3476
|
runMain(defineCommand({
|
|
3332
3477
|
meta: {
|
|
3333
3478
|
name: "forgemap",
|
|
3334
|
-
version: "0.5.0-dev.
|
|
3479
|
+
version: "0.5.0-dev.90-1fa1595",
|
|
3335
3480
|
description: "Manage a local repo layout of the form <root>/<forge.dir>/<owner>/<repo>"
|
|
3336
3481
|
},
|
|
3337
3482
|
subCommands: {
|
|
@@ -3342,46 +3487,11 @@ runMain(defineCommand({
|
|
|
3342
3487
|
cd: cdCommand,
|
|
3343
3488
|
path: pathCommand,
|
|
3344
3489
|
open: openCommand,
|
|
3345
|
-
|
|
3490
|
+
list: listCommand,
|
|
3346
3491
|
pick: pickCommand,
|
|
3347
3492
|
status: statusCommand,
|
|
3348
3493
|
sync: syncCommand,
|
|
3349
|
-
validate:
|
|
3350
|
-
meta: {
|
|
3351
|
-
name: "validate",
|
|
3352
|
-
description: "Preflight: check the config schema, required CLI tools, and root directory"
|
|
3353
|
-
},
|
|
3354
|
-
args: {
|
|
3355
|
-
json: {
|
|
3356
|
-
type: "boolean",
|
|
3357
|
-
description: "Emit a machine-readable JSON report",
|
|
3358
|
-
default: false
|
|
3359
|
-
},
|
|
3360
|
-
config: {
|
|
3361
|
-
type: "string",
|
|
3362
|
-
description: "Path to forgemap.config.ts (overrides walk-up discovery)"
|
|
3363
|
-
}
|
|
3364
|
-
},
|
|
3365
|
-
async run({ args }) {
|
|
3366
|
-
const loaded = await loadForgeMapConfig({ configFile: args.config });
|
|
3367
|
-
const configDir = loaded.configFile ? dirname(loaded.configFile) : loaded.cwd;
|
|
3368
|
-
const checks = await runChecks(loaded.config, configDir);
|
|
3369
|
-
const ok = checks.every((c) => c.severity !== "fail");
|
|
3370
|
-
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
3371
|
-
ok,
|
|
3372
|
-
checks
|
|
3373
|
-
}, null, 2)}\n`);
|
|
3374
|
-
else {
|
|
3375
|
-
for (const c of checks) process.stdout.write(`${severitySymbol(c.severity)} ${c.name.padEnd(22)} ${colors.dim(c.message)}\n`);
|
|
3376
|
-
process.stdout.write(`\n${ok ? colors.green("All checks passed.") : colors.red("Validation failed.")}\n`);
|
|
3377
|
-
}
|
|
3378
|
-
if (!ok) {
|
|
3379
|
-
process.exitCode = 1;
|
|
3380
|
-
return;
|
|
3381
|
-
}
|
|
3382
|
-
if (!loaded.configFile) consola.warn("No forgemap.config.ts found — using built-in defaults. Run `forgemap config init` to materialize one.");
|
|
3383
|
-
}
|
|
3384
|
-
}),
|
|
3494
|
+
validate: validateCommand,
|
|
3385
3495
|
info: infoCommand,
|
|
3386
3496
|
completion: completionCommand,
|
|
3387
3497
|
"shell-init": shellInitCommand,
|