@getmonoceros/workbench 1.13.2 → 1.13.3

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/dist/bin.js CHANGED
@@ -129,19 +129,19 @@ function wrapText(text, width, continuationIndent) {
129
129
  if (current.length > 0) lines.push(current.replace(/\s+$/, ""));
130
130
  return lines.map((l, i) => i === 0 ? l : continuationIndent + l).join("\n");
131
131
  }
132
- function alignTable(rows, indent) {
132
+ function alignTable(rows, indent, opts = {}) {
133
133
  if (rows.length === 0) return "";
134
- const labelWidth = Math.max(...rows.map((r) => visibleLen(r[0])));
134
+ const labelWidth = opts.fixedLabelWidth ?? Math.max(...rows.map((r) => visibleLen(r[0])));
135
135
  const gutter = " ";
136
136
  const descWidth = terminalWidth() - indent.length - labelWidth - gutter.length;
137
137
  const continuationIndent = " ".repeat(
138
138
  indent.length + labelWidth + gutter.length
139
139
  );
140
140
  return rows.map(([left, right]) => {
141
- const pad = " ".repeat(labelWidth - visibleLen(left));
141
+ const pad = " ".repeat(Math.max(0, labelWidth - visibleLen(left)));
142
142
  const wrapped = wrapText(right, descWidth, continuationIndent);
143
143
  return `${indent}${left}${pad}${gutter}${wrapped}`;
144
- }).join("\n");
144
+ }).join(opts.rowGap ? "\n\n" : "\n");
145
145
  }
146
146
  function collectSubCommands(cmd) {
147
147
  const subs = cmd.subCommands ?? {};
@@ -167,6 +167,7 @@ function renderCommandsBlock(entries) {
167
167
  arr.push(entry2);
168
168
  byGroup.set(entry2.group, arr);
169
169
  }
170
+ const labelWidth = Math.max(...entries.map((e) => visibleLen(cyan(e.name))));
170
171
  const renderSection = (label, items) => {
171
172
  if (items.length === 0) return;
172
173
  lines.push("");
@@ -176,7 +177,9 @@ function renderCommandsBlock(entries) {
176
177
  cyan(e.name),
177
178
  e.description
178
179
  ]);
179
- lines.push(alignTable(rows, ""));
180
+ lines.push(
181
+ alignTable(rows, "", { fixedLabelWidth: labelWidth, rowGap: true })
182
+ );
180
183
  };
181
184
  for (const { key, label } of GROUPS) {
182
185
  renderSection(label, byGroup.get(key) ?? []);
@@ -251,25 +254,25 @@ function detectHelpRequest(argv, main2) {
251
254
  const separatorIdx = argv.indexOf("--");
252
255
  if (helpIdx === -1) return null;
253
256
  if (separatorIdx !== -1 && separatorIdx < helpIdx) return null;
254
- const path21 = [];
257
+ const path20 = [];
255
258
  const tokens = argv.slice(
256
259
  0,
257
260
  separatorIdx === -1 ? argv.length : separatorIdx
258
261
  );
259
262
  let cursor = main2;
260
263
  const mainName = (main2.meta ?? {}).name ?? "monoceros";
261
- path21.push(mainName);
264
+ path20.push(mainName);
262
265
  for (const tok of tokens) {
263
266
  if (tok.startsWith("-")) continue;
264
267
  const subs = cursor.subCommands ?? {};
265
268
  if (tok in subs) {
266
269
  cursor = subs[tok];
267
- path21.push(tok);
270
+ path20.push(tok);
268
271
  continue;
269
272
  }
270
273
  break;
271
274
  }
272
- return { path: path21, cmd: cursor };
275
+ return { path: path20, cmd: cursor };
273
276
  }
274
277
  async function maybeRenderHelp(argv, main2) {
275
278
  const hit = detectHelpRequest(argv, main2);
@@ -3167,8 +3170,8 @@ function removeRepoFromDoc(doc, urlOrPath) {
3167
3170
  if (!isMap2(item)) return false;
3168
3171
  const url = item.get("url");
3169
3172
  if (url === urlOrPath) return true;
3170
- const path21 = item.get("path");
3171
- const effectivePath = typeof path21 === "string" ? path21 : typeof url === "string" ? deriveRepoName(url) : void 0;
3173
+ const path20 = item.get("path");
3174
+ const effectivePath = typeof path20 === "string" ? path20 : typeof url === "string" ? deriveRepoName(url) : void 0;
3172
3175
  return effectivePath === urlOrPath;
3173
3176
  });
3174
3177
  if (idx < 0) return false;
@@ -3261,7 +3264,7 @@ async function runAddRepo(input) {
3261
3264
  "Missing repo URL. Usage: monoceros add-repo <containername> <url>."
3262
3265
  );
3263
3266
  }
3264
- const path21 = (input.path ?? deriveRepoName(url)).trim();
3267
+ const path20 = (input.path ?? deriveRepoName(url)).trim();
3265
3268
  const hasName = typeof input.gitName === "string" && input.gitName.trim().length > 0;
3266
3269
  const hasEmail = typeof input.gitEmail === "string" && input.gitEmail.trim().length > 0;
3267
3270
  if (hasName !== hasEmail) {
@@ -3298,7 +3301,7 @@ async function runAddRepo(input) {
3298
3301
  const providerToWrite = !canonical && explicitProvider ? explicitProvider : void 0;
3299
3302
  const entry2 = {
3300
3303
  url,
3301
- path: path21,
3304
+ path: path20,
3302
3305
  ...hasName && hasEmail ? {
3303
3306
  gitUser: {
3304
3307
  name: input.gitName.trim(),
@@ -4137,7 +4140,7 @@ var addServiceCommand = defineCommand7({
4137
4140
  import { defineCommand as defineCommand8 } from "citty";
4138
4141
 
4139
4142
  // src/apply/index.ts
4140
- import { existsSync as existsSync8, promises as fs12 } from "fs";
4143
+ import { existsSync as existsSync7, promises as fs11 } from "fs";
4141
4144
  import { consola as consola11 } from "consola";
4142
4145
 
4143
4146
  // src/config/state.ts
@@ -4579,230 +4582,11 @@ function runLogs(opts) {
4579
4582
  );
4580
4583
  }
4581
4584
 
4582
- // src/devcontainer/repo-reachability.ts
4583
- import { spawn as spawn6 } from "child_process";
4584
- var realGitLsRemote = (url) => {
4585
- return new Promise((resolve, reject) => {
4586
- const child = spawn6("git", ["ls-remote", "--heads", "--", url], {
4587
- stdio: ["ignore", "pipe", "pipe"],
4588
- env: {
4589
- ...process.env,
4590
- GIT_TERMINAL_PROMPT: "0"
4591
- }
4592
- });
4593
- let stdout = "";
4594
- let stderr = "";
4595
- child.stdout.on("data", (chunk) => {
4596
- stdout += chunk.toString();
4597
- });
4598
- child.stderr.on("data", (chunk) => {
4599
- stderr += chunk.toString();
4600
- });
4601
- child.on("error", reject);
4602
- child.on(
4603
- "exit",
4604
- (code) => resolve({ stdout, stderr, exitCode: code ?? 0 })
4605
- );
4606
- });
4607
- };
4608
- function classifyStderr(stderr) {
4609
- const s = stderr.toLowerCase();
4610
- if (s.includes("could not resolve host") || s.includes("name or service not known") || s.includes("temporary failure in name resolution") || s.includes("no address associated with hostname")) {
4611
- return "dns";
4612
- }
4613
- if (s.includes("repository not found") || s.includes("may not have access") || s.includes("no longer exists") || s.includes("don't have permission") || s.includes("could not be found") || s.includes("the requested url returned error: 404")) {
4614
- return "not-found-or-no-access";
4615
- }
4616
- if (s.includes("authentication failed") || s.includes("could not read username") || s.includes("incorrect username or password") || s.includes("the requested url returned error: 401") || s.includes("the requested url returned error: 403")) {
4617
- return "auth-failed";
4618
- }
4619
- return "unknown";
4620
- }
4621
- async function checkRepoReachability(repos, options = {}) {
4622
- const spawnFn = options.spawn ?? realGitLsRemote;
4623
- const results = [];
4624
- for (const repo of repos) {
4625
- let result;
4626
- try {
4627
- result = await spawnFn(repo.url);
4628
- } catch (err) {
4629
- results.push({
4630
- url: repo.url,
4631
- ok: false,
4632
- kind: "unknown",
4633
- detail: err instanceof Error ? err.message : String(err)
4634
- });
4635
- continue;
4636
- }
4637
- if (result.exitCode === 0) {
4638
- results.push({ url: repo.url, ok: true, detail: "" });
4639
- continue;
4640
- }
4641
- results.push({
4642
- url: repo.url,
4643
- ok: false,
4644
- kind: classifyStderr(result.stderr),
4645
- detail: result.stderr.trim()
4646
- });
4647
- }
4648
- return results;
4649
- }
4650
- function formatUnreachableReposError(failures) {
4651
- const byKind = /* @__PURE__ */ new Map();
4652
- for (const f of failures) {
4653
- const kind = f.kind ?? "unknown";
4654
- const list = byKind.get(kind) ?? [];
4655
- list.push(f);
4656
- byKind.set(kind, list);
4657
- }
4658
- const totalUrls = failures.length;
4659
- const lines = [
4660
- totalUrls === 1 ? `Cannot reach declared repo: ${failures[0].url}` : `Cannot reach ${totalUrls} declared repos:`,
4661
- ""
4662
- ];
4663
- const sectionOrder = [
4664
- "not-found-or-no-access",
4665
- "auth-failed",
4666
- "dns",
4667
- "unknown"
4668
- ];
4669
- for (const kind of sectionOrder) {
4670
- const entries = byKind.get(kind);
4671
- if (!entries || entries.length === 0) continue;
4672
- lines.push(headerForKind(kind));
4673
- for (const e of entries) {
4674
- lines.push(` \u2022 ${e.url}`);
4675
- if (e.detail) {
4676
- for (const detailLine of e.detail.split("\n")) {
4677
- const trimmed = detailLine.trim();
4678
- if (trimmed) lines.push(` git: ${trimmed}`);
4679
- }
4680
- }
4681
- }
4682
- for (const advice of adviceForKind(kind)) {
4683
- lines.push(` - ${advice}`);
4684
- }
4685
- lines.push("");
4686
- }
4687
- lines.push(`Then re-run ${cyan2("monoceros apply")}.`);
4688
- return lines.join("\n");
4689
- }
4690
- function headerForKind(kind) {
4691
- switch (kind) {
4692
- case "not-found-or-no-access":
4693
- return "Repository not found (or your credentials don't grant access):";
4694
- case "auth-failed":
4695
- return "Authentication failed (credentials are present but rejected):";
4696
- case "dns":
4697
- return "Host unreachable (DNS / VPN / offline \u2014 git couldn't resolve the hostname):";
4698
- case "unknown":
4699
- return "Unrecognised git error:";
4700
- }
4701
- }
4702
- function adviceForKind(kind) {
4703
- switch (kind) {
4704
- case "not-found-or-no-access":
4705
- return [
4706
- "Re-check the URL for typos (case-sensitive on most hosts).",
4707
- "Verify the repo still exists / is not archived in a way that hides it.",
4708
- "Ensure your token covers this org / workspace and has read scope (GitHub: `repo`; GitLab: `read_repository`; Bitbucket: repo read)."
4709
- ];
4710
- case "auth-failed":
4711
- return [
4712
- "Token may be expired or revoked \u2014 regenerate it from the provider UI.",
4713
- "Re-run the provider CLI login (gh auth login / glab auth login) \u2014 Monoceros picks up the refreshed token on the next apply."
4714
- ];
4715
- case "dns":
4716
- return [
4717
- "Check your internet / VPN \u2014 corporate Git hosts often require VPN.",
4718
- "Verify the hostname spelling in the yml."
4719
- ];
4720
- case "unknown":
4721
- return [
4722
- "Run `git ls-remote <url>` manually on the host to see the raw error."
4723
- ];
4724
- }
4725
- }
4726
-
4727
- // src/devcontainer/repo-clone.ts
4728
- import { spawn as spawn7 } from "child_process";
4729
- import { existsSync as existsSync7, promises as fs10 } from "fs";
4730
- import path13 from "path";
4731
- var realGitClone = (url, dest) => {
4732
- return new Promise((resolve, reject) => {
4733
- const child = spawn7("git", ["clone", "--", url, dest], {
4734
- stdio: ["ignore", "pipe", "pipe"],
4735
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
4736
- });
4737
- let stdout = "";
4738
- let stderr = "";
4739
- child.stdout.on("data", (c) => {
4740
- stdout += c.toString();
4741
- });
4742
- child.stderr.on("data", (c) => {
4743
- stderr += c.toString();
4744
- });
4745
- child.on("error", reject);
4746
- child.on(
4747
- "exit",
4748
- (code) => resolve({ stdout, stderr, exitCode: code ?? 0 })
4749
- );
4750
- });
4751
- };
4752
- async function cloneReposHostSide(containerRoot, repos, options = {}) {
4753
- const spawnFn = options.spawn ?? realGitClone;
4754
- const results = [];
4755
- for (const repo of repos) {
4756
- const dest = path13.join(containerRoot, "projects", repo.path);
4757
- if (existsSync7(dest)) {
4758
- results.push({ path: repo.path, url: repo.url, status: "skipped" });
4759
- continue;
4760
- }
4761
- await fs10.mkdir(path13.dirname(dest), { recursive: true });
4762
- let r;
4763
- try {
4764
- r = await spawnFn(repo.url, dest);
4765
- } catch (err) {
4766
- results.push({
4767
- path: repo.path,
4768
- url: repo.url,
4769
- status: "failed",
4770
- detail: err instanceof Error ? err.message : String(err)
4771
- });
4772
- continue;
4773
- }
4774
- results.push(
4775
- r.exitCode === 0 ? { path: repo.path, url: repo.url, status: "cloned" } : {
4776
- path: repo.path,
4777
- url: repo.url,
4778
- status: "failed",
4779
- detail: r.stderr.trim()
4780
- }
4781
- );
4782
- }
4783
- return results;
4784
- }
4785
- function formatCloneFailuresError(failures) {
4786
- const lines = failures.length === 1 ? [`Failed to clone declared repo: ${failures[0].url}`, ""] : [`Failed to clone ${failures.length} declared repos:`, ""];
4787
- for (const f of failures) {
4788
- lines.push(` \u2022 ${f.url} \u2192 projects/${f.path}`);
4789
- if (f.detail) lines.push(` ${f.detail}`);
4790
- }
4791
- lines.push("");
4792
- lines.push(
4793
- "Reachability was confirmed earlier, so this is usually a local issue"
4794
- );
4795
- lines.push(
4796
- "(disk space, a leftover non-empty target dir). Fix it and re-run " + cyan2("monoceros apply") + "."
4797
- );
4798
- return lines.join("\n");
4799
- }
4800
-
4801
4585
  // src/devcontainer/docker-mode.ts
4802
- import { spawn as spawn8 } from "child_process";
4586
+ import { spawn as spawn6 } from "child_process";
4803
4587
  var realDockerInfo = () => {
4804
4588
  return new Promise((resolve, reject) => {
4805
- const child = spawn8(
4589
+ const child = spawn6(
4806
4590
  "docker",
4807
4591
  ["info", "--format", "{{json .SecurityOptions}}"],
4808
4592
  {
@@ -4861,13 +4645,13 @@ function formatRootlessNotSupportedError() {
4861
4645
  }
4862
4646
 
4863
4647
  // src/devcontainer/identity.ts
4864
- import { spawn as spawn9 } from "child_process";
4865
- import { promises as fs11 } from "fs";
4866
- import path14 from "path";
4648
+ import { spawn as spawn7 } from "child_process";
4649
+ import { promises as fs10 } from "fs";
4650
+ import path13 from "path";
4867
4651
  import { consola as consola10 } from "consola";
4868
4652
  var realGitConfigGet = (key) => {
4869
4653
  return new Promise((resolve, reject) => {
4870
- const child = spawn9("git", ["config", "--global", "--get", key], {
4654
+ const child = spawn7("git", ["config", "--global", "--get", key], {
4871
4655
  stdio: ["ignore", "pipe", "inherit"]
4872
4656
  });
4873
4657
  let stdout = "";
@@ -4977,8 +4761,8 @@ async function resolveIdentityWithPrompt(options = {}) {
4977
4761
  };
4978
4762
  }
4979
4763
  async function collectGitIdentity(devContainerRoot, options = {}) {
4980
- const gitconfigDir = path14.join(devContainerRoot, ".monoceros");
4981
- const gitconfigPath = path14.join(gitconfigDir, "gitconfig");
4764
+ const gitconfigDir = path13.join(devContainerRoot, ".monoceros");
4765
+ const gitconfigPath = path13.join(gitconfigDir, "gitconfig");
4982
4766
  const logger = options.logger ?? { info: () => {
4983
4767
  }, warn: () => {
4984
4768
  } };
@@ -4991,8 +4775,8 @@ async function collectGitIdentity(devContainerRoot, options = {}) {
4991
4775
  const lines = ["[user]"];
4992
4776
  if (resolved.name !== void 0) lines.push(` name = ${resolved.name}`);
4993
4777
  if (resolved.email !== void 0) lines.push(` email = ${resolved.email}`);
4994
- await fs11.mkdir(gitconfigDir, { recursive: true });
4995
- await fs11.writeFile(gitconfigPath, lines.join("\n") + "\n");
4778
+ await fs10.mkdir(gitconfigDir, { recursive: true });
4779
+ await fs10.writeFile(gitconfigPath, lines.join("\n") + "\n");
4996
4780
  return {
4997
4781
  ...resolved.name !== void 0 ? { name: resolved.name } : {},
4998
4782
  ...resolved.email !== void 0 ? { email: resolved.email } : {},
@@ -5035,7 +4819,7 @@ async function readKeyFromHost(spawnFn, key, logger) {
5035
4819
  }
5036
4820
  async function readExistingGitconfig(filePath) {
5037
4821
  try {
5038
- const content = await fs11.readFile(filePath, "utf8");
4822
+ const content = await fs10.readFile(filePath, "utf8");
5039
4823
  const result = {};
5040
4824
  const nameMatch = /^\s*name\s*=\s*(.+?)\s*$/m.exec(content);
5041
4825
  const emailMatch = /^\s*email\s*=\s*(.+?)\s*$/m.exec(content);
@@ -5068,7 +4852,7 @@ ${sectionLine(label)}
5068
4852
  );
5069
4853
  }
5070
4854
  const ymlPath = containerConfigPath(opts.name, home);
5071
- if (!existsSync8(ymlPath)) {
4855
+ if (!existsSync7(ymlPath)) {
5072
4856
  throw new Error(
5073
4857
  `No such config: ${ymlPath}. Run \`monoceros init <template> ${opts.name}\` first.`
5074
4858
  );
@@ -5174,16 +4958,6 @@ Fix the value in the env file (or the yml).`
5174
4958
  throw new Error(formatMissingCredentialsError(missing));
5175
4959
  }
5176
4960
  }
5177
- const declaredRepos = createOpts.repos ?? [];
5178
- if (declaredRepos.length > 0) {
5179
- const reachability = await checkRepoReachability(declaredRepos, {
5180
- ...opts.reachabilitySpawn ? { spawn: opts.reachabilitySpawn } : {}
5181
- });
5182
- const unreachable = reachability.filter((r) => !r.ok);
5183
- if (unreachable.length > 0) {
5184
- throw new Error(formatUnreachableReposError(unreachable));
5185
- }
5186
- }
5187
4961
  section("Scaffold");
5188
4962
  const dockerMode = await detectDockerMode({
5189
4963
  ...opts.dockerInfoSpawn ? { spawn: opts.dockerInfoSpawn } : {}
@@ -5191,7 +4965,7 @@ Fix the value in the env file (or the yml).`
5191
4965
  if (dockerMode === "rootless") {
5192
4966
  throw new Error(formatRootlessNotSupportedError());
5193
4967
  }
5194
- await fs12.mkdir(targetDir, { recursive: true });
4968
+ await fs11.mkdir(targetDir, { recursive: true });
5195
4969
  await writeScaffold(createOpts, targetDir, { dockerMode });
5196
4970
  await writeStateFile(
5197
4971
  targetDir,
@@ -5202,23 +4976,6 @@ Fix the value in the env file (or the yml).`
5202
4976
  })
5203
4977
  );
5204
4978
  logger.success(`materialized into ${prettyPath(targetDir)}`);
5205
- const reposToClone = createOpts.repos ?? [];
5206
- if (reposToClone.length > 0) {
5207
- const cloneResults = await cloneReposHostSide(targetDir, reposToClone, {
5208
- ...opts.cloneSpawn ? { spawn: opts.cloneSpawn } : {}
5209
- });
5210
- for (const r of cloneResults) {
5211
- if (r.status === "cloned") {
5212
- logger.success(`cloned ${cyan2(r.path)} ${dim(`(${r.url})`)}`);
5213
- } else if (r.status === "skipped") {
5214
- logger.info(`projects/${r.path} already present \u2014 skipped clone`);
5215
- }
5216
- }
5217
- const cloneFailures = cloneResults.filter((r) => r.status === "failed");
5218
- if (cloneFailures.length > 0) {
5219
- throw new Error(formatCloneFailuresError(cloneFailures));
5220
- }
5221
- }
5222
4979
  section("Container");
5223
4980
  const featureRefs = parsed.config.features.map((f) => f.ref);
5224
4981
  if (featureRefs.length > 0) {
@@ -5266,8 +5023,8 @@ Fix the value in the env file (or the yml).`
5266
5023
  return { targetDir, configPath: ymlPath, containerExitCode: exitCode };
5267
5024
  }
5268
5025
  async function assertSafeTargetDir(targetDir, expectedOrigin) {
5269
- if (!existsSync8(targetDir)) return;
5270
- const entries = await fs12.readdir(targetDir);
5026
+ if (!existsSync7(targetDir)) return;
5027
+ const entries = await fs11.readdir(targetDir);
5271
5028
  if (entries.length === 0) return;
5272
5029
  const state = await readStateFile(targetDir);
5273
5030
  if (state) {
@@ -5337,7 +5094,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
5337
5094
  }
5338
5095
  if (wantContainer) {
5339
5096
  try {
5340
- const text = await fs12.readFile(ymlPath, "utf8");
5097
+ const text = await fs11.readFile(ymlPath, "utf8");
5341
5098
  const parsed = parseConfig(text, ymlPath);
5342
5099
  const changed = setContainerGitUserInDoc(parsed.doc, {
5343
5100
  name: prompted.name,
@@ -5345,7 +5102,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
5345
5102
  });
5346
5103
  if (changed) {
5347
5104
  const out = stringifyConfig(parsed.doc);
5348
- await fs12.writeFile(ymlPath, out, "utf8");
5105
+ await fs11.writeFile(ymlPath, out, "utf8");
5349
5106
  logger.info(
5350
5107
  `Saved identity in this container \u2014 wrote git.user into ${prettyPath(ymlPath)}.`
5351
5108
  );
@@ -5359,7 +5116,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
5359
5116
  }
5360
5117
 
5361
5118
  // src/version.ts
5362
- var CLI_VERSION = true ? "1.13.2" : "dev";
5119
+ var CLI_VERSION = true ? "1.13.3" : "dev";
5363
5120
 
5364
5121
  // src/commands/_dispatch.ts
5365
5122
  import { consola as consola12 } from "consola";
@@ -5420,7 +5177,7 @@ function renderCompletionScript(shell) {
5420
5177
  ' COMPREPLY=( $(compgen -W "$candidates" -- "$cur") )',
5421
5178
  " # Suppress the trailing space when bash narrowed the candidate",
5422
5179
  " # set to a single token that ends with `=` \u2014 those are value-",
5423
- " # flags (`--with=`, `--with-ports=`, \u2026) where the user types the",
5180
+ " # flags (`--with-features=`, `--with-ports=`, \u2026) where the user types the",
5424
5181
  " # value immediately after.",
5425
5182
  ' if [[ ${#COMPREPLY[@]} -eq 1 && "${COMPREPLY[0]}" == *= ]]; then',
5426
5183
  " compopt -o nospace",
@@ -5493,6 +5250,10 @@ var completionCommand = defineCommand9({
5493
5250
  meta: {
5494
5251
  name: "completion",
5495
5252
  group: "tooling",
5253
+ // Hidden from `monoceros --help`: the install scripts wire up
5254
+ // completion automatically; manual setup is documented in
5255
+ // docs/commands/completion.md. Still runnable directly.
5256
+ hidden: true,
5496
5257
  description: "Print a shell completion script for bash, zsh or PowerShell to stdout. Pipe the output into a file your shell loads at startup. The install scripts (install.sh / install.ps1) call this automatically."
5497
5258
  },
5498
5259
  args: {
@@ -5519,8 +5280,8 @@ var completionCommand = defineCommand9({
5519
5280
  import { defineCommand as defineCommand10 } from "citty";
5520
5281
 
5521
5282
  // src/completion/resolve.ts
5522
- import { existsSync as existsSync9, promises as fs13 } from "fs";
5523
- import path15 from "path";
5283
+ import { existsSync as existsSync8, promises as fs12 } from "fs";
5284
+ import path14 from "path";
5524
5285
  async function resolveCompletions(line, point, opts = {}) {
5525
5286
  const { prev, current } = parseCompletionLine(line, point);
5526
5287
  const ctx = { prev, current, opts };
@@ -5668,9 +5429,9 @@ function filterPrefix(values, fragment) {
5668
5429
  }
5669
5430
  async function listContainerNames(ctx) {
5670
5431
  const home = ctx.opts.monocerosHome ?? monocerosHome();
5671
- const dir = path15.join(home, "container-configs");
5672
- if (!existsSync9(dir)) return [];
5673
- const entries = await fs13.readdir(dir);
5432
+ const dir = path14.join(home, "container-configs");
5433
+ if (!existsSync8(dir)) return [];
5434
+ const entries = await fs12.readdir(dir);
5674
5435
  return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length)).sort();
5675
5436
  }
5676
5437
  async function listFeatureComponents() {
@@ -5888,6 +5649,8 @@ var __completeCommand = defineCommand10({
5888
5649
  meta: {
5889
5650
  name: "__complete",
5890
5651
  group: "internal",
5652
+ // Internal plumbing for the shell wrappers — never shown in help.
5653
+ hidden: true,
5891
5654
  description: "Internal \u2014 shell completion engine. Used by the wrappers emitted by `monoceros completion <shell>`. Output one candidate completion per line."
5892
5655
  },
5893
5656
  args: {
@@ -5925,8 +5688,8 @@ import { defineCommand as defineCommand11 } from "citty";
5925
5688
  import { consola as consola14 } from "consola";
5926
5689
 
5927
5690
  // src/init/index.ts
5928
- import { existsSync as existsSync10, promises as fs14 } from "fs";
5929
- import path16 from "path";
5691
+ import { existsSync as existsSync9, promises as fs13 } from "fs";
5692
+ import path15 from "path";
5930
5693
  import { consola as consola13 } from "consola";
5931
5694
 
5932
5695
  // src/init/generator.ts
@@ -6292,7 +6055,7 @@ async function runInit(opts) {
6292
6055
  );
6293
6056
  }
6294
6057
  const dest = containerConfigPath(opts.name, home);
6295
- if (existsSync10(dest)) {
6058
+ if (existsSync9(dest)) {
6296
6059
  throw new Error(
6297
6060
  `Config already exists: ${dest}. Delete it manually before re-running \`monoceros init\` \u2014 this protects any hand-edits.`
6298
6061
  );
@@ -6363,9 +6126,9 @@ async function runInit(opts) {
6363
6126
  } else {
6364
6127
  text = generateComposedYml(opts.name, composed, lookup, repos, ports);
6365
6128
  }
6366
- await fs14.mkdir(containerConfigsDir(home), { recursive: true });
6129
+ await fs13.mkdir(containerConfigsDir(home), { recursive: true });
6367
6130
  await ensureEnvGitignored(containerConfigsDir(home));
6368
- await fs14.writeFile(dest, text, "utf8");
6131
+ await fs13.writeFile(dest, text, "utf8");
6369
6132
  const envPath = containerEnvPath(opts.name, home);
6370
6133
  const seedVars = {};
6371
6134
  for (const f of composed.features) {
@@ -6388,8 +6151,8 @@ async function runInit(opts) {
6388
6151
  }
6389
6152
  await ensureEnvVars(envPath, opts.name, seedVars);
6390
6153
  const documented = !anyComposed;
6391
- const ymlRel = path16.relative(home, dest);
6392
- const envRel = path16.relative(home, envPath);
6154
+ const ymlRel = path15.relative(home, dest);
6155
+ const envRel = path15.relative(home, envPath);
6393
6156
  if (documented) {
6394
6157
  logger.success(`Wrote documented default to ${ymlRel} and ${envRel}.`);
6395
6158
  logger.info(
@@ -6655,7 +6418,7 @@ var listComponentsCommand = defineCommand12({
6655
6418
  meta: {
6656
6419
  name: "list-components",
6657
6420
  group: "discovery",
6658
- description: "Print the components catalog used by `monoceros init --with=\u2026`, grouped by category (Languages, Services, Features). Component names render in cyan, descriptions in default colour; when piped, the formatting drops out and lines become `name<TAB>description` for grep/awk-friendly consumption."
6421
+ description: "Print the components catalog used by `monoceros init --with-languages=\u2026 / --with-services=\u2026 / --with-features=\u2026`, grouped by category (Languages, Services, Features). Component names render in cyan, descriptions in default colour; when piped, the formatting drops out and lines become `name<TAB>description` for grep/awk-friendly consumption."
6659
6422
  },
6660
6423
  args: {},
6661
6424
  async run() {
@@ -6924,8 +6687,8 @@ import { consola as consola20 } from "consola";
6924
6687
  import { createInterface } from "readline/promises";
6925
6688
 
6926
6689
  // src/remove/index.ts
6927
- import { existsSync as existsSync11, promises as fs15 } from "fs";
6928
- import path17 from "path";
6690
+ import { existsSync as existsSync10, promises as fs14 } from "fs";
6691
+ import path16 from "path";
6929
6692
  import { consola as consola19 } from "consola";
6930
6693
  async function runRemove(opts) {
6931
6694
  const home = opts.monocerosHome ?? monocerosHome();
@@ -6942,9 +6705,9 @@ async function runRemove(opts) {
6942
6705
  const ymlPath = containerConfigPath(opts.name, home);
6943
6706
  const envPath = containerEnvPath(opts.name, home);
6944
6707
  const containerPath = containerDir(opts.name, home);
6945
- const hasYml = existsSync11(ymlPath);
6946
- const hasEnv = existsSync11(envPath);
6947
- const hasContainer = existsSync11(containerPath);
6708
+ const hasYml = existsSync10(ymlPath);
6709
+ const hasEnv = existsSync10(envPath);
6710
+ const hasContainer = existsSync10(containerPath);
6948
6711
  if (!hasYml && !hasContainer) {
6949
6712
  throw new Error(
6950
6713
  `Nothing to remove for '${opts.name}': neither ${ymlPath} nor ${containerPath} exists.`
@@ -6968,30 +6731,30 @@ async function runRemove(opts) {
6968
6731
  let backupPath = null;
6969
6732
  if (!opts.noBackup && (hasYml || hasContainer)) {
6970
6733
  const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
6971
- backupPath = path17.join(home, "container-backups", `${opts.name}-${ts}`);
6972
- await fs15.mkdir(backupPath, { recursive: true });
6734
+ backupPath = path16.join(home, "container-backups", `${opts.name}-${ts}`);
6735
+ await fs14.mkdir(backupPath, { recursive: true });
6973
6736
  if (hasYml) {
6974
- await fs15.copyFile(ymlPath, path17.join(backupPath, `${opts.name}.yml`));
6737
+ await fs14.copyFile(ymlPath, path16.join(backupPath, `${opts.name}.yml`));
6975
6738
  }
6976
6739
  if (hasEnv) {
6977
- await fs15.copyFile(envPath, path17.join(backupPath, `${opts.name}.env`));
6740
+ await fs14.copyFile(envPath, path16.join(backupPath, `${opts.name}.env`));
6978
6741
  }
6979
6742
  if (hasContainer) {
6980
- await fs15.cp(containerPath, path17.join(backupPath, "container"), {
6743
+ await fs14.cp(containerPath, path16.join(backupPath, "container"), {
6981
6744
  recursive: true
6982
6745
  });
6983
6746
  }
6984
6747
  logger.info(`Backup written to ${prettyPath(backupPath)}.`);
6985
6748
  }
6986
6749
  if (hasYml) {
6987
- await fs15.rm(ymlPath, { force: true });
6750
+ await fs14.rm(ymlPath, { force: true });
6988
6751
  }
6989
6752
  if (hasEnv) {
6990
- await fs15.rm(envPath, { force: true });
6753
+ await fs14.rm(envPath, { force: true });
6991
6754
  }
6992
6755
  if (hasContainer) {
6993
6756
  try {
6994
- await fs15.rm(containerPath, { recursive: true, force: true });
6757
+ await fs14.rm(containerPath, { recursive: true, force: true });
6995
6758
  } catch (err) {
6996
6759
  const code = err.code;
6997
6760
  if (code !== "EACCES" && code !== "EPERM") {
@@ -7017,7 +6780,7 @@ async function runRemove(opts) {
7017
6780
  `docker-based cleanup of ${containerPath} exited ${exit}. Inspect with \`sudo ls -la ${containerPath}\` and clean manually.`
7018
6781
  );
7019
6782
  }
7020
- await fs15.rm(containerPath, { recursive: true, force: true });
6783
+ await fs14.rm(containerPath, { recursive: true, force: true });
7021
6784
  }
7022
6785
  }
7023
6786
  logger.success(
@@ -7120,8 +6883,8 @@ import { defineCommand as defineCommand18 } from "citty";
7120
6883
  import { consola as consola22 } from "consola";
7121
6884
 
7122
6885
  // src/restore/index.ts
7123
- import { existsSync as existsSync12, promises as fs16 } from "fs";
7124
- import path18 from "path";
6886
+ import { existsSync as existsSync11, promises as fs15 } from "fs";
6887
+ import path17 from "path";
7125
6888
  import { consola as consola21 } from "consola";
7126
6889
  async function runRestore(opts) {
7127
6890
  const home = opts.monocerosHome ?? monocerosHome();
@@ -7129,15 +6892,15 @@ async function runRestore(opts) {
7129
6892
  info: (msg) => consola21.info(msg),
7130
6893
  success: (msg) => consola21.success(msg)
7131
6894
  };
7132
- const backup = path18.resolve(opts.backupPath);
7133
- if (!existsSync12(backup)) {
6895
+ const backup = path17.resolve(opts.backupPath);
6896
+ if (!existsSync11(backup)) {
7134
6897
  throw new Error(`Backup not found: ${backup}.`);
7135
6898
  }
7136
- const stat = await fs16.stat(backup);
6899
+ const stat = await fs15.stat(backup);
7137
6900
  if (!stat.isDirectory()) {
7138
6901
  throw new Error(`Backup path is not a directory: ${backup}.`);
7139
6902
  }
7140
- const entries = await fs16.readdir(backup);
6903
+ const entries = await fs15.readdir(backup);
7141
6904
  const ymlFiles = entries.filter((f) => f.endsWith(".yml"));
7142
6905
  if (ymlFiles.length === 0) {
7143
6906
  throw new Error(
@@ -7151,29 +6914,29 @@ async function runRestore(opts) {
7151
6914
  }
7152
6915
  const ymlFile = ymlFiles[0];
7153
6916
  const name = ymlFile.replace(/\.yml$/, "");
7154
- const containerInBackup = path18.join(backup, "container");
7155
- const hasContainer = existsSync12(containerInBackup);
7156
- const envInBackup = path18.join(backup, `${name}.env`);
7157
- const hasEnv = existsSync12(envInBackup);
6917
+ const containerInBackup = path17.join(backup, "container");
6918
+ const hasContainer = existsSync11(containerInBackup);
6919
+ const envInBackup = path17.join(backup, `${name}.env`);
6920
+ const hasEnv = existsSync11(envInBackup);
7158
6921
  const destYml = containerConfigPath(name, home);
7159
6922
  const destContainer = containerDir(name, home);
7160
- if (existsSync12(destYml)) {
6923
+ if (existsSync11(destYml)) {
7161
6924
  throw new Error(
7162
6925
  `Refusing to restore: ${destYml} already exists. Remove the current container first (\`monoceros remove ${name}\`) or rename the existing config.`
7163
6926
  );
7164
6927
  }
7165
- if (hasContainer && existsSync12(destContainer)) {
6928
+ if (hasContainer && existsSync11(destContainer)) {
7166
6929
  throw new Error(
7167
6930
  `Refusing to restore: ${destContainer} already exists. Remove the current container first (\`monoceros remove ${name}\`).`
7168
6931
  );
7169
6932
  }
7170
- await fs16.mkdir(containerConfigsDir(home), { recursive: true });
7171
- await fs16.copyFile(path18.join(backup, ymlFile), destYml);
6933
+ await fs15.mkdir(containerConfigsDir(home), { recursive: true });
6934
+ await fs15.copyFile(path17.join(backup, ymlFile), destYml);
7172
6935
  if (hasEnv) {
7173
- await fs16.copyFile(envInBackup, containerEnvPath(name, home));
6936
+ await fs15.copyFile(envInBackup, containerEnvPath(name, home));
7174
6937
  }
7175
6938
  if (hasContainer) {
7176
- await fs16.cp(containerInBackup, destContainer, { recursive: true });
6939
+ await fs15.cp(containerInBackup, destContainer, { recursive: true });
7177
6940
  }
7178
6941
  logger.success(`Restored '${name}' from ${prettyPath(backup)}.`);
7179
6942
  logger.info(
@@ -7431,8 +7194,8 @@ import { defineCommand as defineCommand24 } from "citty";
7431
7194
  import { consola as consola28 } from "consola";
7432
7195
 
7433
7196
  // src/devcontainer/shell.ts
7434
- import { existsSync as existsSync13 } from "fs";
7435
- import path19 from "path";
7197
+ import { existsSync as existsSync12 } from "fs";
7198
+ import path18 from "path";
7436
7199
  async function runShell(opts) {
7437
7200
  assertContainerExists(opts.root);
7438
7201
  const spawnFn = opts.spawn ?? spawnDevcontainer;
@@ -7455,7 +7218,7 @@ async function runShell(opts) {
7455
7218
  );
7456
7219
  }
7457
7220
  function assertContainerExists(root) {
7458
- if (!existsSync13(path19.join(root, ".devcontainer"))) {
7221
+ if (!existsSync12(path18.join(root, ".devcontainer"))) {
7459
7222
  throw new Error(
7460
7223
  `No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
7461
7224
  );
@@ -7667,15 +7430,15 @@ import { defineCommand as defineCommand29 } from "citty";
7667
7430
  import { consola as consola33 } from "consola";
7668
7431
 
7669
7432
  // src/tunnel/run.ts
7670
- import { spawn as spawn10 } from "child_process";
7433
+ import { spawn as spawn8 } from "child_process";
7671
7434
  import { consola as consola32 } from "consola";
7672
7435
 
7673
7436
  // src/tunnel/resolve.ts
7674
- import { existsSync as existsSync14 } from "fs";
7675
- import path20 from "path";
7437
+ import { existsSync as existsSync13 } from "fs";
7438
+ import path19 from "path";
7676
7439
  async function resolveTunnelTarget(opts) {
7677
7440
  const ymlPath = containerConfigPath(opts.name, opts.monocerosHome);
7678
- if (!existsSync14(ymlPath)) {
7441
+ if (!existsSync13(ymlPath)) {
7679
7442
  throw new Error(
7680
7443
  `No yml profile for '${opts.name}' at ${ymlPath}. Run \`monoceros init ${opts.name}\` first.`
7681
7444
  );
@@ -7683,13 +7446,13 @@ async function resolveTunnelTarget(opts) {
7683
7446
  const parsed = await readConfig(ymlPath);
7684
7447
  const config = parsed.config;
7685
7448
  const containerRoot = containerDir(opts.name, opts.monocerosHome);
7686
- if (!existsSync14(containerRoot)) {
7449
+ if (!existsSync13(containerRoot)) {
7687
7450
  throw new Error(
7688
7451
  `Container '${opts.name}' is not materialised at ${containerRoot}. Run \`monoceros apply ${opts.name}\` first.`
7689
7452
  );
7690
7453
  }
7691
- const composePath = path20.join(containerRoot, ".devcontainer", "compose.yaml");
7692
- const isCompose = existsSync14(composePath);
7454
+ const composePath = path19.join(containerRoot, ".devcontainer", "compose.yaml");
7455
+ const isCompose = existsSync13(composePath);
7693
7456
  const parsedTarget = parseTargetArg(opts.target, config);
7694
7457
  const docker = opts.docker ?? defaultDockerExec;
7695
7458
  if (isCompose) {
@@ -7910,7 +7673,7 @@ function formatLocalPortHeldError(port, address, result) {
7910
7673
  // src/tunnel/run.ts
7911
7674
  var SOCAT_IMAGE = "alpine/socat:1.8.0.3";
7912
7675
  var defaultDockerSpawn = (args) => {
7913
- const child = spawn10("docker", args, {
7676
+ const child = spawn8("docker", args, {
7914
7677
  stdio: "inherit"
7915
7678
  });
7916
7679
  const exited = new Promise((resolve, reject) => {