forgemap 0.8.0 → 0.9.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 CHANGED
@@ -88,6 +88,7 @@ Prints where a repo lives — or *would* live, if it isn't cloned yet, which mak
88
88
  forgemap list # every repo as a pretty tree
89
89
  forgemap list forgemap # pretty tree (one line per match)
90
90
  forgemap list forgemap | fzf # pipe-friendly path output
91
+ forgemap ls # `ls` is an alias for `list`
91
92
  forgemap pick # interactive picker (consola prompt)
92
93
  forgemap pick kirch # picker pre-filtered by fuzzy query
93
94
  ```
@@ -76,7 +76,8 @@ function findConfigUp(start) {
76
76
  }
77
77
  /** Fallback config under $XDG_CONFIG_HOME/forgemap (or ~/.config/forgemap). */
78
78
  function findGlobalConfig() {
79
- const dir = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "forgemap");
79
+ const base = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
80
+ const dir = join(base, "forgemap");
80
81
  for (const baseName of CONFIG_BASENAMES) {
81
82
  const candidate = join(dir, baseName);
82
83
  if (existsSync(candidate)) return candidate;
@@ -1516,7 +1517,8 @@ function resolveSlug(parsed, options) {
1516
1517
  }
1517
1518
  const depthError = checkNamespaceDepth(forgeName, forge.type, parsed.owner);
1518
1519
  if (depthError) throw new Error(depthError);
1519
- const localPath = join(resolveRoot(config.root, configDir), forge.dir, parsed.owner, parsed.repo);
1520
+ const root = resolveRoot(config.root, configDir);
1521
+ const localPath = join(root, forge.dir, parsed.owner, parsed.repo);
1520
1522
  return {
1521
1523
  forgeName,
1522
1524
  forge,
@@ -1700,7 +1702,8 @@ ${forgeEntries}
1700
1702
  /** Write a `forgemap.config.ts` into `outDir`. Returns null when the file
1701
1703
  * already exists and `force` is not set (the caller decides how to report). */
1702
1704
  async function writeConfigFile(config, options) {
1703
- const target = join(resolve(process.cwd(), options.outDir), "forgemap.config.ts");
1705
+ const outDir = resolve(process.cwd(), options.outDir);
1706
+ const target = join(outDir, "forgemap.config.ts");
1704
1707
  await mkdir(dirname(target), { recursive: true });
1705
1708
  try {
1706
1709
  await writeFile(target, renderConfigModule(config), {
@@ -1724,6 +1727,57 @@ var DEFAULT_CONFIG = {
1724
1727
  dir: "comGithub"
1725
1728
  } }
1726
1729
  };
1730
+ var configInitCommand = defineCommand({
1731
+ meta: {
1732
+ name: "init",
1733
+ description: "Create a forgemap.config.ts in the current (or given) directory"
1734
+ },
1735
+ args: {
1736
+ out: {
1737
+ type: "string",
1738
+ description: "Directory to write forgemap.config.ts into",
1739
+ default: "."
1740
+ },
1741
+ force: {
1742
+ type: "boolean",
1743
+ description: "Overwrite if forgemap.config.ts already exists",
1744
+ default: false
1745
+ }
1746
+ },
1747
+ async run({ args }) {
1748
+ const result = await writeConfigFile(DEFAULT_CONFIG, {
1749
+ outDir: args.out,
1750
+ force: args.force
1751
+ });
1752
+ if (!result) {
1753
+ const target = join(resolve(process.cwd(), args.out), "forgemap.config.ts");
1754
+ consola.error(`${target} already exists. Use --force to overwrite.`);
1755
+ process.exitCode = 1;
1756
+ return;
1757
+ }
1758
+ consola.success(`Wrote ${result.path}`);
1759
+ }
1760
+ });
1761
+ //#endregion
1762
+ //#region src/commands/config/show.ts
1763
+ var configShowCommand = defineCommand({
1764
+ meta: {
1765
+ name: "show",
1766
+ description: "Print the resolved forgemap config and its source path"
1767
+ },
1768
+ args: { config: {
1769
+ type: "string",
1770
+ description: "Path to forgemap.config.ts (overrides walk-up discovery)"
1771
+ } },
1772
+ async run({ args }) {
1773
+ const loaded = await loadForgeMapConfig({ configFile: args.config });
1774
+ process.stdout.write(JSON.stringify({
1775
+ configFile: loaded.configFile ?? null,
1776
+ cwd: loaded.cwd,
1777
+ config: loaded.config
1778
+ }, null, 2) + "\n");
1779
+ }
1780
+ });
1727
1781
  //#endregion
1728
1782
  //#region src/commands/config/index.ts
1729
1783
  var configCommand = defineCommand({
@@ -1732,55 +1786,8 @@ var configCommand = defineCommand({
1732
1786
  description: "Manage the forgemap config file"
1733
1787
  },
1734
1788
  subCommands: {
1735
- init: defineCommand({
1736
- meta: {
1737
- name: "init",
1738
- description: "Create a forgemap.config.ts in the current (or given) directory"
1739
- },
1740
- args: {
1741
- out: {
1742
- type: "string",
1743
- description: "Directory to write forgemap.config.ts into",
1744
- default: "."
1745
- },
1746
- force: {
1747
- type: "boolean",
1748
- description: "Overwrite if forgemap.config.ts already exists",
1749
- default: false
1750
- }
1751
- },
1752
- async run({ args }) {
1753
- const result = await writeConfigFile(DEFAULT_CONFIG, {
1754
- outDir: args.out,
1755
- force: args.force
1756
- });
1757
- if (!result) {
1758
- const target = join(resolve(process.cwd(), args.out), "forgemap.config.ts");
1759
- consola.error(`${target} already exists. Use --force to overwrite.`);
1760
- process.exitCode = 1;
1761
- return;
1762
- }
1763
- consola.success(`Wrote ${result.path}`);
1764
- }
1765
- }),
1766
- show: defineCommand({
1767
- meta: {
1768
- name: "show",
1769
- description: "Print the resolved forgemap config and its source path"
1770
- },
1771
- args: { config: {
1772
- type: "string",
1773
- description: "Path to forgemap.config.ts (overrides walk-up discovery)"
1774
- } },
1775
- async run({ args }) {
1776
- const loaded = await loadForgeMapConfig({ configFile: args.config });
1777
- process.stdout.write(JSON.stringify({
1778
- configFile: loaded.configFile ?? null,
1779
- cwd: loaded.cwd,
1780
- config: loaded.config
1781
- }, null, 2) + "\n");
1782
- }
1783
- })
1789
+ init: configInitCommand,
1790
+ show: configShowCommand
1784
1791
  }
1785
1792
  });
1786
1793
  //#endregion
@@ -2498,14 +2505,16 @@ var forgeRemoveCommand = defineCommand({
2498
2505
  if (!(key in forges)) return fail(`No forge "${key}" in ${file}. Configured: ${Object.keys(forges).join(", ")}.`);
2499
2506
  const remaining = Object.keys(forges).filter((k) => k !== key);
2500
2507
  let newDefault;
2501
- if (loaded.config.defaultForge === key && remaining.length > 0) if (typeof args.default === "string") {
2502
- if (!remaining.includes(args.default)) return fail(`Cannot set default to "${args.default}" — not a remaining forge (${remaining.join(", ")}).`);
2503
- newDefault = args.default;
2504
- } else if (tty) {
2505
- const answer = await promptSelect(`"${key}" is the default forge. Pick a new default:`, [...remaining, LEAVE_UNSET]);
2506
- if (answer === null) return abort();
2507
- if (answer !== LEAVE_UNSET) newDefault = answer;
2508
- } else consola.warn(`Removing the default forge "${key}"; defaultForge now points at a missing forge. Pass --default to reassign it.`);
2508
+ if (loaded.config.defaultForge === key && remaining.length > 0) {
2509
+ if (typeof args.default === "string") {
2510
+ if (!remaining.includes(args.default)) return fail(`Cannot set default to "${args.default}" — not a remaining forge (${remaining.join(", ")}).`);
2511
+ newDefault = args.default;
2512
+ } else if (tty) {
2513
+ const answer = await promptSelect(`"${key}" is the default forge. Pick a new default:`, [...remaining, LEAVE_UNSET]);
2514
+ if (answer === null) return abort();
2515
+ if (answer !== LEAVE_UNSET) newDefault = answer;
2516
+ } else consola.warn(`Removing the default forge "${key}"; defaultForge now points at a missing forge. Pass --default to reassign it.`);
2517
+ }
2509
2518
  consola.info(`Remove forge "${key}" from ${file}${newDefault ? ` (new default: "${newDefault}")` : ""}`);
2510
2519
  if (tty && !args.yes && !await confirmChange("Apply this change?")) return abort();
2511
2520
  if (await applyChange(file, (c) => {
@@ -2721,13 +2730,11 @@ function pushRemoteFinding(report, parsed, result, path) {
2721
2730
  message: `remote ${parsed.owner}/${parsed.repo} no longer exists`
2722
2731
  });
2723
2732
  break;
2724
- case "unknown":
2725
- report.findings.push({
2726
- kind: "remote-check-unknown",
2727
- severity: "warn",
2728
- message: `remote check inconclusive: ${result.reason}`
2729
- });
2730
- break;
2733
+ case "unknown": report.findings.push({
2734
+ kind: "remote-check-unknown",
2735
+ severity: "warn",
2736
+ message: `remote check inconclusive: ${result.reason}`
2737
+ });
2731
2738
  }
2732
2739
  }
2733
2740
  /** Run the network check for one forge's repos, preferring the batched
@@ -3243,7 +3250,7 @@ var infoCommand = defineCommand({
3243
3250
  async run({ args }) {
3244
3251
  const binary = resolveBinary(process.argv[1]);
3245
3252
  const info = {
3246
- version: "0.8.0",
3253
+ version: "0.9.0",
3247
3254
  build: detectBuild(binary.resolved),
3248
3255
  binary,
3249
3256
  node: process.version,
@@ -3369,6 +3376,7 @@ function renderTree$1(repos) {
3369
3376
  var listCommand = defineCommand({
3370
3377
  meta: {
3371
3378
  name: "list",
3379
+ alias: "ls",
3372
3380
  description: "List cloned repos; with a query, fuzzy-match by owner/repo and print matches"
3373
3381
  },
3374
3382
  args: {
@@ -4064,55 +4072,59 @@ async function runChecks(config, configDir) {
4064
4072
  severity: "fail",
4065
4073
  message: "install from https://git-scm.com/"
4066
4074
  });
4067
- if (needsGh) if (await hasCommand("gh")) {
4068
- checks.push({
4069
- name: "gh CLI",
4070
- severity: "ok",
4071
- message: "on PATH"
4072
- });
4073
- const auth = await execCapture("gh", ["auth", "status"]);
4074
- checks.push(auth.code === 0 ? {
4075
- name: "gh auth",
4076
- severity: "ok",
4077
- message: "authenticated"
4078
- } : {
4079
- name: "gh auth",
4080
- severity: "warn",
4081
- message: "not logged in — run `gh auth login`"
4082
- });
4083
- } else checks.push({
4084
- name: "gh CLI",
4085
- severity: "fail",
4086
- message: "install from https://cli.github.com/"
4087
- });
4088
- if (gitlabForges.length > 0) if (await hasCommand("glab")) {
4089
- checks.push({
4090
- name: "glab CLI",
4091
- severity: "ok",
4092
- message: "on PATH"
4093
- });
4094
- for (const [name, forge] of gitlabForges) {
4095
- const auth = await execCapture("glab", [
4096
- "auth",
4097
- "status",
4098
- "--hostname",
4099
- forge.host
4100
- ]);
4075
+ if (needsGh) {
4076
+ if (await hasCommand("gh")) {
4077
+ checks.push({
4078
+ name: "gh CLI",
4079
+ severity: "ok",
4080
+ message: "on PATH"
4081
+ });
4082
+ const auth = await execCapture("gh", ["auth", "status"]);
4101
4083
  checks.push(auth.code === 0 ? {
4102
- name: `glab auth (${name})`,
4084
+ name: "gh auth",
4103
4085
  severity: "ok",
4104
- message: `authenticated at ${forge.host}`
4086
+ message: "authenticated"
4105
4087
  } : {
4106
- name: `glab auth (${name})`,
4088
+ name: "gh auth",
4107
4089
  severity: "warn",
4108
- message: `not logged in — run \`glab auth login --hostname ${forge.host}\``
4090
+ message: "not logged in — run `gh auth login`"
4109
4091
  });
4110
- }
4111
- } else checks.push({
4112
- name: "glab CLI",
4113
- severity: "fail",
4114
- message: "install from https://gitlab.com/gitlab-org/cli"
4115
- });
4092
+ } else checks.push({
4093
+ name: "gh CLI",
4094
+ severity: "fail",
4095
+ message: "install from https://cli.github.com/"
4096
+ });
4097
+ }
4098
+ if (gitlabForges.length > 0) {
4099
+ if (await hasCommand("glab")) {
4100
+ checks.push({
4101
+ name: "glab CLI",
4102
+ severity: "ok",
4103
+ message: "on PATH"
4104
+ });
4105
+ for (const [name, forge] of gitlabForges) {
4106
+ const auth = await execCapture("glab", [
4107
+ "auth",
4108
+ "status",
4109
+ "--hostname",
4110
+ forge.host
4111
+ ]);
4112
+ checks.push(auth.code === 0 ? {
4113
+ name: `glab auth (${name})`,
4114
+ severity: "ok",
4115
+ message: `authenticated at ${forge.host}`
4116
+ } : {
4117
+ name: `glab auth (${name})`,
4118
+ severity: "warn",
4119
+ message: `not logged in — run \`glab auth login --hostname ${forge.host}\``
4120
+ });
4121
+ }
4122
+ } else checks.push({
4123
+ name: "glab CLI",
4124
+ severity: "fail",
4125
+ message: "install from https://gitlab.com/gitlab-org/cli"
4126
+ });
4127
+ }
4116
4128
  checks.push(await layoutCheck(config, configDir));
4117
4129
  return checks;
4118
4130
  }
@@ -4219,6 +4231,21 @@ var SHELL_POSITIONAL = /* @__PURE__ */ new Set(["completion", "shell-init"]);
4219
4231
  function argsOf(cmd) {
4220
4232
  return cmd.args ?? {};
4221
4233
  }
4234
+ /** Subcommand aliases, read off citty's own `meta.alias` so the completion and
4235
+ * the dispatcher cannot disagree about what `ls` means. Every forgemap command
4236
+ * declares `meta` as a plain object literal, never a thunk. */
4237
+ function aliasesOf(cmd) {
4238
+ const alias = cmd.meta?.alias;
4239
+ if (!alias) return [];
4240
+ return Array.isArray(alias) ? [...alias] : [alias];
4241
+ }
4242
+ /** Every name that reaches a command on the command line: its own, plus its
4243
+ * aliases. This is what the *recognition* arms key on — `commandSpecs()`'s
4244
+ * `name` alone is what the depth-1 *suggestion* list offers, which is how an
4245
+ * alias completes its arguments without being advertised as a command. */
4246
+ function namesOf(spec) {
4247
+ return [spec.name, ...spec.aliases];
4248
+ }
4222
4249
  /** Flag names a command exposes, derived from its `defineCommand` args so a new
4223
4250
  * flag surfaces in completion automatically. Positionals are handled
4224
4251
  * separately; negatable booleans (those with a `negativeDescription`) also get
@@ -4257,6 +4284,7 @@ function commandSpecs() {
4257
4284
  ["forge", forgeCommand]
4258
4285
  ].map(([name, cmd]) => ({
4259
4286
  name,
4287
+ aliases: aliasesOf(cmd),
4260
4288
  flags: flagsOf(cmd),
4261
4289
  flagValues: STATIC_FLAG_VALUES[name] ?? {},
4262
4290
  positionalValues: SHELL_POSITIONAL.has(name) ? [...SUPPORTED_SHELLS] : [],
@@ -4264,17 +4292,17 @@ function commandSpecs() {
4264
4292
  }));
4265
4293
  }
4266
4294
  function flagValuePairs(specs) {
4267
- return specs.flatMap((s) => Object.entries(s.flagValues).map(([flag, values]) => [
4268
- s.name,
4295
+ return specs.flatMap((s) => namesOf(s).flatMap((name) => Object.entries(s.flagValues).map(([flag, values]) => [
4296
+ name,
4269
4297
  flag,
4270
4298
  values
4271
- ]));
4299
+ ])));
4272
4300
  }
4273
4301
  function renderBash(specs) {
4274
4302
  const names = specs.map((s) => s.name).join(" ");
4275
4303
  const valueArms = flagValuePairs(specs).map(([cmd, flag, values]) => ` ${cmd}:${flag}) COMPREPLY=( $(compgen -W "${values.join(" ")}" -- "$cur") ); return ;;`).join("\n");
4276
- const flagArms = specs.filter((s) => s.flags.length > 0).map((s) => ` ${s.name}) flags="${s.flags.join(" ")}" ;;`).join("\n");
4277
- const slugCmds = specs.filter((s) => s.slugs).map((s) => s.name);
4304
+ const flagArms = specs.filter((s) => s.flags.length > 0).flatMap((s) => namesOf(s).map((name) => ` ${name}) flags="${s.flags.join(" ")}" ;;`)).join("\n");
4305
+ const slugCmds = specs.filter((s) => s.slugs).flatMap(namesOf);
4278
4306
  return `# forgemap bash completion — drop into your ~/.bashrc:
4279
4307
  # eval "$(forgemap completion bash)"
4280
4308
  _forgemap_completion() {
@@ -4309,7 +4337,7 @@ ${[slugCmds.length > 0 ? ` ${slugCmds.join("|")})
4309
4337
  local slugs
4310
4338
  slugs=$(forgemap list --format slug 2>/dev/null)
4311
4339
  COMPREPLY=( $(compgen -W "$slugs" -- "$cur") )
4312
- ;;` : "", ...specs.filter((s) => s.positionalValues.length > 0).map((s) => ` ${s.name}) COMPREPLY=( $(compgen -W "${s.positionalValues.join(" ")}" -- "$cur") ) ;;`)].filter(Boolean).join("\n")}
4340
+ ;;` : "", ...specs.filter((s) => s.positionalValues.length > 0).map((s) => ` ${namesOf(s).join("|")}) COMPREPLY=( $(compgen -W "${s.positionalValues.join(" ")}" -- "$cur") ) ;;`)].filter(Boolean).join("\n")}
4313
4341
  esac
4314
4342
  }
4315
4343
  complete -F _forgemap_completion forgemap
@@ -4338,19 +4366,19 @@ ${flagValuePairs(specs).map(([cmd, flag, values]) => ` ${cmd}:${flag}) compad
4338
4366
  # Flag names for the current subcommand.
4339
4367
  if [[ "$cur" == -* ]]; then
4340
4368
  case "$cmd" in
4341
- ${specs.filter((s) => s.flags.length > 0).map((s) => ` ${s.name}) compadd -- ${s.flags.join(" ")}; return ;;`).join("\n")}
4369
+ ${specs.filter((s) => s.flags.length > 0).flatMap((s) => namesOf(s).map((name) => ` ${name}) compadd -- ${s.flags.join(" ")}; return ;;`)).join("\n")}
4342
4370
  esac
4343
4371
  return
4344
4372
  fi
4345
4373
 
4346
4374
  # Positional values (repo slugs, or a shell name).
4347
4375
  case "$cmd" in
4348
- ${specs.filter((s) => s.slugs).map((s) => s.name).join("|")})
4376
+ ${specs.filter((s) => s.slugs).flatMap(namesOf).join("|")})
4349
4377
  local -a slugs
4350
4378
  slugs=("\${(@f)$(forgemap list --format slug 2>/dev/null)}")
4351
4379
  _describe 'slug' slugs
4352
4380
  ;;
4353
- ${specs.filter((s) => s.positionalValues.length > 0).map((s) => ` ${s.name}) compadd ${s.positionalValues.join(" ")} ;;`).join("\n")}
4381
+ ${specs.filter((s) => s.positionalValues.length > 0).map((s) => ` ${namesOf(s).join("|")}) compadd ${s.positionalValues.join(" ")} ;;`).join("\n")}
4354
4382
  esac
4355
4383
  }
4356
4384
  compdef _forgemap forgemap
@@ -4362,7 +4390,7 @@ function renderFish(specs) {
4362
4390
  const long = flag.replace(/^--/, "");
4363
4391
  const values = s.flagValues[flag];
4364
4392
  const valuePart = values ? ` -x -a '${values.join(" ")}'` : "";
4365
- return `complete -c forgemap -n '__fish_seen_subcommand_from ${s.name}' -l ${long}${valuePart}`;
4393
+ return `complete -c forgemap -n '__fish_seen_subcommand_from ${namesOf(s).join(" ")}' -l ${long}${valuePart}`;
4366
4394
  })).join("\n");
4367
4395
  const shellCmds = specs.filter((s) => s.positionalValues.length > 0);
4368
4396
  return `# forgemap fish completion — drop into your ~/.config/fish/config.fish:
@@ -4377,7 +4405,7 @@ ${flagLines}
4377
4405
  # Shell flavor for completion / shell-init (depth 2).
4378
4406
  function __forgemap_needs_shell
4379
4407
  set -l tokens (commandline -opc)
4380
- set -l shell_cmds ${shellCmds.map((s) => `"${s.name}"`).join(" ")}
4408
+ set -l shell_cmds ${shellCmds.flatMap(namesOf).map((name) => `"${name}"`).join(" ")}
4381
4409
  if test (count $tokens) -ge 2; and contains $tokens[2] $shell_cmds
4382
4410
  return 0
4383
4411
  end
@@ -4388,7 +4416,7 @@ complete -c forgemap -f -n '__forgemap_needs_shell' -a '${shellCmds[0]?.position
4388
4416
  # Slugs (depth 2) for commands that take one.
4389
4417
  function __forgemap_needs_slug
4390
4418
  set -l tokens (commandline -opc)
4391
- set -l slug_cmds ${specs.filter((s) => s.slugs).map((s) => `"${s.name}"`).join(" ")}
4419
+ set -l slug_cmds ${specs.filter((s) => s.slugs).flatMap(namesOf).map((name) => `"${name}"`).join(" ")}
4392
4420
  if test (count $tokens) -ge 2; and contains $tokens[2] $slug_cmds
4393
4421
  return 0
4394
4422
  end
@@ -4439,11 +4467,11 @@ var completionCommand = defineCommand({
4439
4467
  }
4440
4468
  });
4441
4469
  //#endregion
4442
- //#region src/bin/forgemap.ts
4443
- runMain(defineCommand({
4470
+ //#region src/cli.ts
4471
+ var rootCommand = defineCommand({
4444
4472
  meta: {
4445
4473
  name: "forgemap",
4446
- version: "0.8.0",
4474
+ version: "0.9.0",
4447
4475
  description: "Manage a local repo layout of the form <root>/<forge.dir>/<owner>/<repo>"
4448
4476
  },
4449
4477
  subCommands: {
@@ -4465,7 +4493,10 @@ runMain(defineCommand({
4465
4493
  config: configCommand,
4466
4494
  forge: forgeCommand
4467
4495
  }
4468
- }));
4496
+ });
4497
+ //#endregion
4498
+ //#region src/bin/forgemap.ts
4499
+ runMain(rootCommand);
4469
4500
  //#endregion
4470
4501
 
4471
4502
  //# sourceMappingURL=forgemap.mjs.map