@getmonoceros/workbench 1.9.4 → 1.9.6

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
@@ -311,7 +311,7 @@ import { consola as consola2 } from "consola";
311
311
  import { promises as fs8 } from "fs";
312
312
  import { consola } from "consola";
313
313
  import { createPatch } from "diff";
314
- import path7 from "path";
314
+ import path8 from "path";
315
315
 
316
316
  // src/config/io.ts
317
317
  import { promises as fs } from "fs";
@@ -2237,6 +2237,153 @@ import {
2237
2237
  YAMLMap as YAMLMap2,
2238
2238
  YAMLSeq
2239
2239
  } from "yaml";
2240
+
2241
+ // src/init/manifest.ts
2242
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
2243
+ import path7 from "path";
2244
+ function resolveManifestPath(name, checkoutRoot) {
2245
+ if (checkoutRoot) {
2246
+ const checkoutPath = path7.join(
2247
+ checkoutRoot,
2248
+ "images",
2249
+ "features",
2250
+ name,
2251
+ "devcontainer-feature.json"
2252
+ );
2253
+ if (existsSync4(checkoutPath)) return checkoutPath;
2254
+ }
2255
+ const bundlePath = path7.join(
2256
+ bundledFeaturesDir(),
2257
+ name,
2258
+ "devcontainer-feature.json"
2259
+ );
2260
+ if (existsSync4(bundlePath)) return bundlePath;
2261
+ return null;
2262
+ }
2263
+ function loadFeatureManifestSummary(ref, checkoutRoot = workbenchCheckoutRoot()) {
2264
+ const match = matchMonocerosFeature(ref);
2265
+ if (!match) return void 0;
2266
+ const manifestPath = resolveManifestPath(match.name, checkoutRoot);
2267
+ if (!manifestPath) return void 0;
2268
+ try {
2269
+ const text = readFileSync2(manifestPath, "utf8");
2270
+ const parsed = JSON.parse(text);
2271
+ const rawHints = parsed["x-monoceros"]?.optionHints;
2272
+ const optionHints = Array.isArray(rawHints) ? rawHints.filter(
2273
+ (x) => typeof x === "string" && x.length > 0
2274
+ ) : [];
2275
+ const rawNotes = parsed["x-monoceros"]?.usageNotes;
2276
+ const usageNotes = Array.isArray(rawNotes) ? rawNotes.filter(
2277
+ (x) => typeof x === "string" && x.length > 0
2278
+ ) : [];
2279
+ const optionDescriptions = {};
2280
+ const optionTypes = {};
2281
+ const optionNames = [];
2282
+ if (parsed.options) {
2283
+ for (const [key, opt] of Object.entries(parsed.options)) {
2284
+ if (!opt || typeof opt !== "object") continue;
2285
+ optionNames.push(key);
2286
+ if (typeof opt.description === "string" && opt.description.length > 0) {
2287
+ optionDescriptions[key] = opt.description;
2288
+ }
2289
+ if (opt.type === "boolean") {
2290
+ optionTypes[key] = "boolean";
2291
+ } else if (opt.type === "string") {
2292
+ optionTypes[key] = "string";
2293
+ }
2294
+ }
2295
+ }
2296
+ const name = typeof parsed.name === "string" ? parsed.name : "";
2297
+ const description = typeof parsed.description === "string" ? parsed.description : "";
2298
+ const rawUrl = typeof parsed.documentationURL === "string" ? parsed.documentationURL.trim() : "";
2299
+ const documentationURL = rawUrl.length > 0 && rawUrl.toLowerCase() !== "tbd" ? rawUrl : void 0;
2300
+ return {
2301
+ name,
2302
+ description,
2303
+ documentationURL,
2304
+ optionHints,
2305
+ optionDescriptions,
2306
+ optionNames,
2307
+ optionTypes,
2308
+ usageNotes
2309
+ };
2310
+ } catch {
2311
+ return void 0;
2312
+ }
2313
+ }
2314
+
2315
+ // src/init/feature-doc.ts
2316
+ function buildFeatureHeaderLines(summary, width) {
2317
+ const paragraphs = buildHeaderParagraphs(summary);
2318
+ const wrapped = [];
2319
+ for (const para of paragraphs) {
2320
+ for (const line of wrapToComment(para, width)) {
2321
+ wrapped.push(line);
2322
+ }
2323
+ }
2324
+ return wrapped;
2325
+ }
2326
+ function buildFeatureHeaderCommentBefore(summary, width) {
2327
+ const lines = buildFeatureHeaderLines(summary, width);
2328
+ return lines.map((l) => ` ${l}`).join("\n");
2329
+ }
2330
+ function buildHeaderParagraphs(summary) {
2331
+ if (!summary) return [];
2332
+ const out = [];
2333
+ const tagline = summary.name?.trim();
2334
+ const description = summary.description?.trim();
2335
+ if (tagline && description) {
2336
+ out.push(`${tagline} \u2014 ${description}`);
2337
+ } else if (tagline) {
2338
+ out.push(tagline);
2339
+ } else if (description) {
2340
+ out.push(description);
2341
+ }
2342
+ for (const note of summary.usageNotes) {
2343
+ const trimmed = note.trim();
2344
+ if (trimmed.length > 0) out.push(trimmed);
2345
+ }
2346
+ if (summary.optionHints.length > 0) {
2347
+ const parts = summary.optionHints.map((key) => {
2348
+ const desc = summary.optionDescriptions[key];
2349
+ const short = desc ? shortenOptionDescription(desc) : void 0;
2350
+ return short ? `${key} (${short})` : key;
2351
+ });
2352
+ out.push(`Options: ${parts.join(", ")}.`);
2353
+ }
2354
+ if (summary.documentationURL) {
2355
+ out.push(`See ${summary.documentationURL} for further information.`);
2356
+ }
2357
+ return out;
2358
+ }
2359
+ function shortenOptionDescription(desc) {
2360
+ const firstSentence = desc.split(/(?<=[.!?])\s+/)[0]?.trim() ?? desc.trim();
2361
+ return firstSentence.replace(/[.!?]+$/, "").trim();
2362
+ }
2363
+ function wrapToComment(text, width) {
2364
+ const words = text.split(/\s+/).filter((w) => w.length > 0);
2365
+ if (words.length === 0) return [""];
2366
+ const usable = Math.max(width, 20);
2367
+ const lines = [];
2368
+ let current = "";
2369
+ for (const w of words) {
2370
+ if (current.length === 0) {
2371
+ current = w;
2372
+ continue;
2373
+ }
2374
+ if (current.length + 1 + w.length <= usable) {
2375
+ current += " " + w;
2376
+ } else {
2377
+ lines.push(current);
2378
+ current = w;
2379
+ }
2380
+ }
2381
+ if (current.length > 0) lines.push(current);
2382
+ return lines;
2383
+ }
2384
+ var FEATURE_HEADER_WIDTH = 76 - 2;
2385
+
2386
+ // src/modify/yml.ts
2240
2387
  function ensureSeq(doc, key) {
2241
2388
  const existing = doc.get(key, true);
2242
2389
  if (existing && isSeq(existing)) return existing;
@@ -2474,6 +2621,15 @@ function addFeatureToDoc(doc, ref, options = {}, displayName) {
2474
2621
  if (Object.keys(options).length > 0) {
2475
2622
  entry2.set("options", options);
2476
2623
  }
2624
+ const summary = loadFeatureManifestSummary(ref);
2625
+ const headerBefore = buildFeatureHeaderCommentBefore(
2626
+ summary,
2627
+ FEATURE_HEADER_WIDTH
2628
+ );
2629
+ if (headerBefore.length > 0) {
2630
+ entry2.commentBefore = headerBefore;
2631
+ entry2.spaceBefore = true;
2632
+ }
2477
2633
  seq.add(entry2);
2478
2634
  return true;
2479
2635
  }
@@ -2512,7 +2668,6 @@ function addRepoToDoc(doc, repo) {
2512
2668
  } else {
2513
2669
  item.delete("provider");
2514
2670
  }
2515
- relocateLeakedSectionComments(doc);
2516
2671
  return true;
2517
2672
  }
2518
2673
  const entry2 = new YAMLMap2();
@@ -2545,7 +2700,6 @@ function addRepoToDoc(doc, repo) {
2545
2700
  entry2.comment = hintLines.join("\n");
2546
2701
  }
2547
2702
  seq.add(entry2);
2548
- relocateLeakedSectionComments(doc);
2549
2703
  return true;
2550
2704
  }
2551
2705
  function removeLanguageFromDoc(doc, lang) {
@@ -2779,7 +2933,7 @@ async function tryCloneInRunningContainer(input, entry2) {
2779
2933
  logger.info(
2780
2934
  `Cloned ${entry2.url} into /workspaces/${containerName2}/${targetRel} inside the running container.`
2781
2935
  );
2782
- void path7;
2936
+ void path8;
2783
2937
  }
2784
2938
  function shquote(value) {
2785
2939
  return `'${value.replace(/'/g, `'\\''`)}'`;
@@ -2976,6 +3130,7 @@ async function mutate(opts, apply) {
2976
3130
  logger.info("No changes \u2014 yml is already in the desired state.");
2977
3131
  return { status: "no-change" };
2978
3132
  }
3133
+ relocateLeakedSectionComments(parsed.doc);
2979
3134
  const newText = stringifyConfig(parsed.doc);
2980
3135
  parseConfig(newText, ymlPath);
2981
3136
  const out = opts.output ?? ((line) => process.stdout.write(line + "\n"));
@@ -3457,12 +3612,12 @@ var addServiceCommand = defineCommand7({
3457
3612
  import { defineCommand as defineCommand8 } from "citty";
3458
3613
 
3459
3614
  // src/apply/index.ts
3460
- import { existsSync as existsSync5, promises as fs11 } from "fs";
3615
+ import { existsSync as existsSync6, promises as fs11 } from "fs";
3461
3616
  import { consola as consola11 } from "consola";
3462
3617
 
3463
3618
  // src/config/state.ts
3464
3619
  import { promises as fs9 } from "fs";
3465
- import path8 from "path";
3620
+ import path9 from "path";
3466
3621
  function buildStateFile(opts) {
3467
3622
  return {
3468
3623
  schemaVersion: CONFIG_SCHEMA_VERSION,
@@ -3472,7 +3627,7 @@ function buildStateFile(opts) {
3472
3627
  };
3473
3628
  }
3474
3629
  function stateFilePath(targetDir) {
3475
- return path8.join(targetDir, ".monoceros", "state.json");
3630
+ return path9.join(targetDir, ".monoceros", "state.json");
3476
3631
  }
3477
3632
  async function readStateFile(targetDir) {
3478
3633
  try {
@@ -3483,7 +3638,7 @@ async function readStateFile(targetDir) {
3483
3638
  }
3484
3639
  }
3485
3640
  async function writeStateFile(targetDir, state) {
3486
- const monocerosDir = path8.join(targetDir, ".monoceros");
3641
+ const monocerosDir = path9.join(targetDir, ".monoceros");
3487
3642
  await fs9.mkdir(monocerosDir, { recursive: true });
3488
3643
  await fs9.writeFile(
3489
3644
  stateFilePath(targetDir),
@@ -3555,8 +3710,8 @@ function solutionConfigToCreateOptions(config, featureDefaults = {}) {
3555
3710
 
3556
3711
  // src/devcontainer/compose.ts
3557
3712
  import { spawn as spawn5 } from "child_process";
3558
- import { existsSync as existsSync4 } from "fs";
3559
- import path10 from "path";
3713
+ import { existsSync as existsSync5 } from "fs";
3714
+ import path11 from "path";
3560
3715
  import { consola as consola9 } from "consola";
3561
3716
 
3562
3717
  // src/util/mask-secrets.ts
@@ -3617,20 +3772,20 @@ function createSecretMaskStream() {
3617
3772
 
3618
3773
  // src/devcontainer/cli.ts
3619
3774
  import { spawn as spawn4 } from "child_process";
3620
- import { readFileSync as readFileSync2 } from "fs";
3775
+ import { readFileSync as readFileSync3 } from "fs";
3621
3776
  import { createRequire } from "module";
3622
- import path9 from "path";
3777
+ import path10 from "path";
3623
3778
  var require_ = createRequire(import.meta.url);
3624
3779
  var cachedBinaryPath = null;
3625
3780
  function devcontainerCliPath() {
3626
3781
  if (cachedBinaryPath) return cachedBinaryPath;
3627
3782
  const pkgJsonPath = require_.resolve("@devcontainers/cli/package.json");
3628
- const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf8"));
3783
+ const pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf8"));
3629
3784
  const binEntry = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.devcontainer ?? "";
3630
3785
  if (!binEntry) {
3631
3786
  throw new Error("Could not resolve @devcontainers/cli bin entry.");
3632
3787
  }
3633
- cachedBinaryPath = path9.resolve(path9.dirname(pkgJsonPath), binEntry);
3788
+ cachedBinaryPath = path10.resolve(path10.dirname(pkgJsonPath), binEntry);
3634
3789
  return cachedBinaryPath;
3635
3790
  }
3636
3791
  var spawnDevcontainer = (args, cwd, options = {}) => {
@@ -3702,16 +3857,16 @@ var spawnBash = (args, cwd) => {
3702
3857
  });
3703
3858
  };
3704
3859
  function composeProjectName(root) {
3705
- return `${path10.basename(root)}_devcontainer`;
3860
+ return `${path11.basename(root)}_devcontainer`;
3706
3861
  }
3707
3862
  function resolveCompose(root) {
3708
- if (!existsSync4(path10.join(root, ".devcontainer"))) {
3863
+ if (!existsSync5(path11.join(root, ".devcontainer"))) {
3709
3864
  throw new Error(
3710
3865
  `No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
3711
3866
  );
3712
3867
  }
3713
- const composeFile = path10.join(root, ".devcontainer", "compose.yaml");
3714
- if (!existsSync4(composeFile)) {
3868
+ const composeFile = path11.join(root, ".devcontainer", "compose.yaml");
3869
+ if (!existsSync5(composeFile)) {
3715
3870
  throw new Error(
3716
3871
  `No compose.yaml at ${composeFile}. \`start\` / \`stop\` / \`status\` / \`logs\` require services configured via \`monoceros add-service <name> <svc>\`. Use \`monoceros shell <name>\` to enter the container directly.`
3717
3872
  );
@@ -4006,7 +4161,7 @@ function formatRootlessNotSupportedError() {
4006
4161
  // src/devcontainer/identity.ts
4007
4162
  import { spawn as spawn8 } from "child_process";
4008
4163
  import { promises as fs10 } from "fs";
4009
- import path11 from "path";
4164
+ import path12 from "path";
4010
4165
  import { consola as consola10 } from "consola";
4011
4166
  var realGitConfigGet = (key) => {
4012
4167
  return new Promise((resolve, reject) => {
@@ -4120,8 +4275,8 @@ async function resolveIdentityWithPrompt(options = {}) {
4120
4275
  };
4121
4276
  }
4122
4277
  async function collectGitIdentity(devContainerRoot, options = {}) {
4123
- const gitconfigDir = path11.join(devContainerRoot, ".monoceros");
4124
- const gitconfigPath = path11.join(gitconfigDir, "gitconfig");
4278
+ const gitconfigDir = path12.join(devContainerRoot, ".monoceros");
4279
+ const gitconfigPath = path12.join(gitconfigDir, "gitconfig");
4125
4280
  const logger = options.logger ?? { info: () => {
4126
4281
  }, warn: () => {
4127
4282
  } };
@@ -4211,7 +4366,7 @@ ${sectionLine(label)}
4211
4366
  );
4212
4367
  }
4213
4368
  const ymlPath = containerConfigPath(opts.name, home);
4214
- if (!existsSync5(ymlPath)) {
4369
+ if (!existsSync6(ymlPath)) {
4215
4370
  throw new Error(
4216
4371
  `No such config: ${ymlPath}. Run \`monoceros init <template> ${opts.name}\` first.`
4217
4372
  );
@@ -4340,7 +4495,7 @@ ${sectionLine(label)}
4340
4495
  return { targetDir, configPath: ymlPath, containerExitCode: exitCode };
4341
4496
  }
4342
4497
  async function assertSafeTargetDir(targetDir, expectedOrigin) {
4343
- if (!existsSync5(targetDir)) return;
4498
+ if (!existsSync6(targetDir)) return;
4344
4499
  const entries = await fs11.readdir(targetDir);
4345
4500
  if (entries.length === 0) return;
4346
4501
  const state = await readStateFile(targetDir);
@@ -4433,7 +4588,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
4433
4588
  }
4434
4589
 
4435
4590
  // src/version.ts
4436
- var CLI_VERSION = true ? "1.9.4" : "dev";
4591
+ var CLI_VERSION = true ? "1.9.6" : "dev";
4437
4592
 
4438
4593
  // src/commands/_dispatch.ts
4439
4594
  import { consola as consola12 } from "consola";
@@ -4595,82 +4750,6 @@ import { defineCommand as defineCommand10 } from "citty";
4595
4750
  // src/completion/resolve.ts
4596
4751
  import { existsSync as existsSync7, promises as fs12 } from "fs";
4597
4752
  import path13 from "path";
4598
-
4599
- // src/init/manifest.ts
4600
- import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
4601
- import path12 from "path";
4602
- function resolveManifestPath(name, checkoutRoot) {
4603
- if (checkoutRoot) {
4604
- const checkoutPath = path12.join(
4605
- checkoutRoot,
4606
- "images",
4607
- "features",
4608
- name,
4609
- "devcontainer-feature.json"
4610
- );
4611
- if (existsSync6(checkoutPath)) return checkoutPath;
4612
- }
4613
- const bundlePath = path12.join(
4614
- bundledFeaturesDir(),
4615
- name,
4616
- "devcontainer-feature.json"
4617
- );
4618
- if (existsSync6(bundlePath)) return bundlePath;
4619
- return null;
4620
- }
4621
- function loadFeatureManifestSummary(ref, checkoutRoot = workbenchCheckoutRoot()) {
4622
- const match = matchMonocerosFeature(ref);
4623
- if (!match) return void 0;
4624
- const manifestPath = resolveManifestPath(match.name, checkoutRoot);
4625
- if (!manifestPath) return void 0;
4626
- try {
4627
- const text = readFileSync3(manifestPath, "utf8");
4628
- const parsed = JSON.parse(text);
4629
- const rawHints = parsed["x-monoceros"]?.optionHints;
4630
- const optionHints = Array.isArray(rawHints) ? rawHints.filter(
4631
- (x) => typeof x === "string" && x.length > 0
4632
- ) : [];
4633
- const rawNotes = parsed["x-monoceros"]?.usageNotes;
4634
- const usageNotes = Array.isArray(rawNotes) ? rawNotes.filter(
4635
- (x) => typeof x === "string" && x.length > 0
4636
- ) : [];
4637
- const optionDescriptions = {};
4638
- const optionTypes = {};
4639
- const optionNames = [];
4640
- if (parsed.options) {
4641
- for (const [key, opt] of Object.entries(parsed.options)) {
4642
- if (!opt || typeof opt !== "object") continue;
4643
- optionNames.push(key);
4644
- if (typeof opt.description === "string" && opt.description.length > 0) {
4645
- optionDescriptions[key] = opt.description;
4646
- }
4647
- if (opt.type === "boolean") {
4648
- optionTypes[key] = "boolean";
4649
- } else if (opt.type === "string") {
4650
- optionTypes[key] = "string";
4651
- }
4652
- }
4653
- }
4654
- const name = typeof parsed.name === "string" ? parsed.name : "";
4655
- const description = typeof parsed.description === "string" ? parsed.description : "";
4656
- const rawUrl = typeof parsed.documentationURL === "string" ? parsed.documentationURL.trim() : "";
4657
- const documentationURL = rawUrl.length > 0 && rawUrl.toLowerCase() !== "tbd" ? rawUrl : void 0;
4658
- return {
4659
- name,
4660
- description,
4661
- documentationURL,
4662
- optionHints,
4663
- optionDescriptions,
4664
- optionNames,
4665
- optionTypes,
4666
- usageNotes
4667
- };
4668
- } catch {
4669
- return void 0;
4670
- }
4671
- }
4672
-
4673
- // src/completion/resolve.ts
4674
4753
  async function resolveCompletions(line, point, opts = {}) {
4675
4754
  const { prev, current } = parseCompletionLine(line, point);
4676
4755
  const ctx = { prev, current, opts };
@@ -5306,12 +5385,8 @@ function routingHeader(name) {
5306
5385
  }
5307
5386
  function renderFeatureBlock(out, feature, summary, commented) {
5308
5387
  const yamlPrefix = commented ? "# " : "";
5309
- const headerLines = buildHeaderLines(summary);
5310
- for (const line of headerLines) {
5311
- const wrapped = wrapToComment(line, COMMENT_WIDTH - 2);
5312
- for (const wl of wrapped) {
5313
- out.push(`# ${wl}`.trimEnd());
5314
- }
5388
+ for (const wl of buildFeatureHeaderLines(summary, COMMENT_WIDTH - 2)) {
5389
+ out.push(`# ${wl}`.trimEnd());
5315
5390
  }
5316
5391
  out.push(`${yamlPrefix} - ref: ${feature.ref}`);
5317
5392
  const options = feature.options ?? {};
@@ -5341,41 +5416,6 @@ function renderFeatureBlock(out, feature, summary, commented) {
5341
5416
  }
5342
5417
  }
5343
5418
  }
5344
- function buildHeaderLines(summary) {
5345
- const out = [];
5346
- if (!summary) {
5347
- return out;
5348
- }
5349
- const tagline = summary.name?.trim();
5350
- const description = summary.description?.trim();
5351
- if (tagline && description) {
5352
- out.push(`${tagline} \u2014 ${description}`);
5353
- } else if (tagline) {
5354
- out.push(tagline);
5355
- } else if (description) {
5356
- out.push(description);
5357
- }
5358
- for (const note of summary.usageNotes) {
5359
- const trimmed = note.trim();
5360
- if (trimmed.length > 0) out.push(trimmed);
5361
- }
5362
- if (summary.optionHints.length > 0) {
5363
- const parts = summary.optionHints.map((key) => {
5364
- const desc = summary.optionDescriptions[key];
5365
- const short = desc ? shortenOptionDescription(desc) : void 0;
5366
- return short ? `${key} (${short})` : key;
5367
- });
5368
- out.push(`Options: ${parts.join(", ")}.`);
5369
- }
5370
- if (summary.documentationURL) {
5371
- out.push(`See ${summary.documentationURL} for further information.`);
5372
- }
5373
- return out;
5374
- }
5375
- function shortenOptionDescription(desc) {
5376
- const firstSentence = desc.split(/(?<=[.!?])\s+/)[0]?.trim() ?? desc.trim();
5377
- return firstSentence.replace(/[.!?]+$/, "").trim();
5378
- }
5379
5419
  function pushHeader(out, header, name) {
5380
5420
  for (const line of header.replace(/<name>/g, name).split("\n")) {
5381
5421
  out.push(line);
@@ -5388,27 +5428,6 @@ function pushSectionHeader(out, text, _commented) {
5388
5428
  out.push(`# ${wl}`.trimEnd());
5389
5429
  }
5390
5430
  }
5391
- function wrapToComment(text, width) {
5392
- const words = text.split(/\s+/).filter((w) => w.length > 0);
5393
- if (words.length === 0) return [""];
5394
- const usable = Math.max(width, 20);
5395
- const lines = [];
5396
- let current = "";
5397
- for (const w of words) {
5398
- if (current.length === 0) {
5399
- current = w;
5400
- continue;
5401
- }
5402
- if (current.length + 1 + w.length <= usable) {
5403
- current += " " + w;
5404
- } else {
5405
- lines.push(current);
5406
- current = w;
5407
- }
5408
- }
5409
- if (current.length > 0) lines.push(current);
5410
- return lines;
5411
- }
5412
5431
  function renderScalarValue(value) {
5413
5432
  if (typeof value === "string") {
5414
5433
  return /^[A-Za-z_][A-Za-z0-9._-]*$/.test(value) ? value : JSON.stringify(value);