@getmonoceros/workbench 1.9.5 → 1.9.7

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
  }
@@ -2570,6 +2726,15 @@ function removeFeatureFromDoc(doc, ref) {
2570
2726
  if (!seq || !isSeq(seq)) return false;
2571
2727
  const idx = seq.items.findIndex((i) => isMap2(i) && i.get("ref") === ref);
2572
2728
  if (idx < 0) return false;
2729
+ if (idx > 0) {
2730
+ const prev = seq.items[idx - 1];
2731
+ if (prev && typeof prev.comment === "string" && prev.comment.length > 0) {
2732
+ const blank = prev.comment.match(/\n[ \t]*\n/);
2733
+ if (blank && blank.index !== void 0) {
2734
+ prev.comment = prev.comment.slice(0, blank.index);
2735
+ }
2736
+ }
2737
+ }
2573
2738
  seq.items.splice(idx, 1);
2574
2739
  pruneEmptySeq(doc, "features");
2575
2740
  return true;
@@ -2777,7 +2942,7 @@ async function tryCloneInRunningContainer(input, entry2) {
2777
2942
  logger.info(
2778
2943
  `Cloned ${entry2.url} into /workspaces/${containerName2}/${targetRel} inside the running container.`
2779
2944
  );
2780
- void path7;
2945
+ void path8;
2781
2946
  }
2782
2947
  function shquote(value) {
2783
2948
  return `'${value.replace(/'/g, `'\\''`)}'`;
@@ -3456,12 +3621,12 @@ var addServiceCommand = defineCommand7({
3456
3621
  import { defineCommand as defineCommand8 } from "citty";
3457
3622
 
3458
3623
  // src/apply/index.ts
3459
- import { existsSync as existsSync5, promises as fs11 } from "fs";
3624
+ import { existsSync as existsSync6, promises as fs11 } from "fs";
3460
3625
  import { consola as consola11 } from "consola";
3461
3626
 
3462
3627
  // src/config/state.ts
3463
3628
  import { promises as fs9 } from "fs";
3464
- import path8 from "path";
3629
+ import path9 from "path";
3465
3630
  function buildStateFile(opts) {
3466
3631
  return {
3467
3632
  schemaVersion: CONFIG_SCHEMA_VERSION,
@@ -3471,7 +3636,7 @@ function buildStateFile(opts) {
3471
3636
  };
3472
3637
  }
3473
3638
  function stateFilePath(targetDir) {
3474
- return path8.join(targetDir, ".monoceros", "state.json");
3639
+ return path9.join(targetDir, ".monoceros", "state.json");
3475
3640
  }
3476
3641
  async function readStateFile(targetDir) {
3477
3642
  try {
@@ -3482,7 +3647,7 @@ async function readStateFile(targetDir) {
3482
3647
  }
3483
3648
  }
3484
3649
  async function writeStateFile(targetDir, state) {
3485
- const monocerosDir = path8.join(targetDir, ".monoceros");
3650
+ const monocerosDir = path9.join(targetDir, ".monoceros");
3486
3651
  await fs9.mkdir(monocerosDir, { recursive: true });
3487
3652
  await fs9.writeFile(
3488
3653
  stateFilePath(targetDir),
@@ -3554,8 +3719,8 @@ function solutionConfigToCreateOptions(config, featureDefaults = {}) {
3554
3719
 
3555
3720
  // src/devcontainer/compose.ts
3556
3721
  import { spawn as spawn5 } from "child_process";
3557
- import { existsSync as existsSync4 } from "fs";
3558
- import path10 from "path";
3722
+ import { existsSync as existsSync5 } from "fs";
3723
+ import path11 from "path";
3559
3724
  import { consola as consola9 } from "consola";
3560
3725
 
3561
3726
  // src/util/mask-secrets.ts
@@ -3616,20 +3781,20 @@ function createSecretMaskStream() {
3616
3781
 
3617
3782
  // src/devcontainer/cli.ts
3618
3783
  import { spawn as spawn4 } from "child_process";
3619
- import { readFileSync as readFileSync2 } from "fs";
3784
+ import { readFileSync as readFileSync3 } from "fs";
3620
3785
  import { createRequire } from "module";
3621
- import path9 from "path";
3786
+ import path10 from "path";
3622
3787
  var require_ = createRequire(import.meta.url);
3623
3788
  var cachedBinaryPath = null;
3624
3789
  function devcontainerCliPath() {
3625
3790
  if (cachedBinaryPath) return cachedBinaryPath;
3626
3791
  const pkgJsonPath = require_.resolve("@devcontainers/cli/package.json");
3627
- const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf8"));
3792
+ const pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf8"));
3628
3793
  const binEntry = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.devcontainer ?? "";
3629
3794
  if (!binEntry) {
3630
3795
  throw new Error("Could not resolve @devcontainers/cli bin entry.");
3631
3796
  }
3632
- cachedBinaryPath = path9.resolve(path9.dirname(pkgJsonPath), binEntry);
3797
+ cachedBinaryPath = path10.resolve(path10.dirname(pkgJsonPath), binEntry);
3633
3798
  return cachedBinaryPath;
3634
3799
  }
3635
3800
  var spawnDevcontainer = (args, cwd, options = {}) => {
@@ -3701,16 +3866,16 @@ var spawnBash = (args, cwd) => {
3701
3866
  });
3702
3867
  };
3703
3868
  function composeProjectName(root) {
3704
- return `${path10.basename(root)}_devcontainer`;
3869
+ return `${path11.basename(root)}_devcontainer`;
3705
3870
  }
3706
3871
  function resolveCompose(root) {
3707
- if (!existsSync4(path10.join(root, ".devcontainer"))) {
3872
+ if (!existsSync5(path11.join(root, ".devcontainer"))) {
3708
3873
  throw new Error(
3709
3874
  `No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
3710
3875
  );
3711
3876
  }
3712
- const composeFile = path10.join(root, ".devcontainer", "compose.yaml");
3713
- if (!existsSync4(composeFile)) {
3877
+ const composeFile = path11.join(root, ".devcontainer", "compose.yaml");
3878
+ if (!existsSync5(composeFile)) {
3714
3879
  throw new Error(
3715
3880
  `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.`
3716
3881
  );
@@ -4005,7 +4170,7 @@ function formatRootlessNotSupportedError() {
4005
4170
  // src/devcontainer/identity.ts
4006
4171
  import { spawn as spawn8 } from "child_process";
4007
4172
  import { promises as fs10 } from "fs";
4008
- import path11 from "path";
4173
+ import path12 from "path";
4009
4174
  import { consola as consola10 } from "consola";
4010
4175
  var realGitConfigGet = (key) => {
4011
4176
  return new Promise((resolve, reject) => {
@@ -4119,8 +4284,8 @@ async function resolveIdentityWithPrompt(options = {}) {
4119
4284
  };
4120
4285
  }
4121
4286
  async function collectGitIdentity(devContainerRoot, options = {}) {
4122
- const gitconfigDir = path11.join(devContainerRoot, ".monoceros");
4123
- const gitconfigPath = path11.join(gitconfigDir, "gitconfig");
4287
+ const gitconfigDir = path12.join(devContainerRoot, ".monoceros");
4288
+ const gitconfigPath = path12.join(gitconfigDir, "gitconfig");
4124
4289
  const logger = options.logger ?? { info: () => {
4125
4290
  }, warn: () => {
4126
4291
  } };
@@ -4210,7 +4375,7 @@ ${sectionLine(label)}
4210
4375
  );
4211
4376
  }
4212
4377
  const ymlPath = containerConfigPath(opts.name, home);
4213
- if (!existsSync5(ymlPath)) {
4378
+ if (!existsSync6(ymlPath)) {
4214
4379
  throw new Error(
4215
4380
  `No such config: ${ymlPath}. Run \`monoceros init <template> ${opts.name}\` first.`
4216
4381
  );
@@ -4339,7 +4504,7 @@ ${sectionLine(label)}
4339
4504
  return { targetDir, configPath: ymlPath, containerExitCode: exitCode };
4340
4505
  }
4341
4506
  async function assertSafeTargetDir(targetDir, expectedOrigin) {
4342
- if (!existsSync5(targetDir)) return;
4507
+ if (!existsSync6(targetDir)) return;
4343
4508
  const entries = await fs11.readdir(targetDir);
4344
4509
  if (entries.length === 0) return;
4345
4510
  const state = await readStateFile(targetDir);
@@ -4432,7 +4597,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
4432
4597
  }
4433
4598
 
4434
4599
  // src/version.ts
4435
- var CLI_VERSION = true ? "1.9.5" : "dev";
4600
+ var CLI_VERSION = true ? "1.9.7" : "dev";
4436
4601
 
4437
4602
  // src/commands/_dispatch.ts
4438
4603
  import { consola as consola12 } from "consola";
@@ -4594,82 +4759,6 @@ import { defineCommand as defineCommand10 } from "citty";
4594
4759
  // src/completion/resolve.ts
4595
4760
  import { existsSync as existsSync7, promises as fs12 } from "fs";
4596
4761
  import path13 from "path";
4597
-
4598
- // src/init/manifest.ts
4599
- import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
4600
- import path12 from "path";
4601
- function resolveManifestPath(name, checkoutRoot) {
4602
- if (checkoutRoot) {
4603
- const checkoutPath = path12.join(
4604
- checkoutRoot,
4605
- "images",
4606
- "features",
4607
- name,
4608
- "devcontainer-feature.json"
4609
- );
4610
- if (existsSync6(checkoutPath)) return checkoutPath;
4611
- }
4612
- const bundlePath = path12.join(
4613
- bundledFeaturesDir(),
4614
- name,
4615
- "devcontainer-feature.json"
4616
- );
4617
- if (existsSync6(bundlePath)) return bundlePath;
4618
- return null;
4619
- }
4620
- function loadFeatureManifestSummary(ref, checkoutRoot = workbenchCheckoutRoot()) {
4621
- const match = matchMonocerosFeature(ref);
4622
- if (!match) return void 0;
4623
- const manifestPath = resolveManifestPath(match.name, checkoutRoot);
4624
- if (!manifestPath) return void 0;
4625
- try {
4626
- const text = readFileSync3(manifestPath, "utf8");
4627
- const parsed = JSON.parse(text);
4628
- const rawHints = parsed["x-monoceros"]?.optionHints;
4629
- const optionHints = Array.isArray(rawHints) ? rawHints.filter(
4630
- (x) => typeof x === "string" && x.length > 0
4631
- ) : [];
4632
- const rawNotes = parsed["x-monoceros"]?.usageNotes;
4633
- const usageNotes = Array.isArray(rawNotes) ? rawNotes.filter(
4634
- (x) => typeof x === "string" && x.length > 0
4635
- ) : [];
4636
- const optionDescriptions = {};
4637
- const optionTypes = {};
4638
- const optionNames = [];
4639
- if (parsed.options) {
4640
- for (const [key, opt] of Object.entries(parsed.options)) {
4641
- if (!opt || typeof opt !== "object") continue;
4642
- optionNames.push(key);
4643
- if (typeof opt.description === "string" && opt.description.length > 0) {
4644
- optionDescriptions[key] = opt.description;
4645
- }
4646
- if (opt.type === "boolean") {
4647
- optionTypes[key] = "boolean";
4648
- } else if (opt.type === "string") {
4649
- optionTypes[key] = "string";
4650
- }
4651
- }
4652
- }
4653
- const name = typeof parsed.name === "string" ? parsed.name : "";
4654
- const description = typeof parsed.description === "string" ? parsed.description : "";
4655
- const rawUrl = typeof parsed.documentationURL === "string" ? parsed.documentationURL.trim() : "";
4656
- const documentationURL = rawUrl.length > 0 && rawUrl.toLowerCase() !== "tbd" ? rawUrl : void 0;
4657
- return {
4658
- name,
4659
- description,
4660
- documentationURL,
4661
- optionHints,
4662
- optionDescriptions,
4663
- optionNames,
4664
- optionTypes,
4665
- usageNotes
4666
- };
4667
- } catch {
4668
- return void 0;
4669
- }
4670
- }
4671
-
4672
- // src/completion/resolve.ts
4673
4762
  async function resolveCompletions(line, point, opts = {}) {
4674
4763
  const { prev, current } = parseCompletionLine(line, point);
4675
4764
  const ctx = { prev, current, opts };
@@ -5305,12 +5394,8 @@ function routingHeader(name) {
5305
5394
  }
5306
5395
  function renderFeatureBlock(out, feature, summary, commented) {
5307
5396
  const yamlPrefix = commented ? "# " : "";
5308
- const headerLines = buildHeaderLines(summary);
5309
- for (const line of headerLines) {
5310
- const wrapped = wrapToComment(line, COMMENT_WIDTH - 2);
5311
- for (const wl of wrapped) {
5312
- out.push(`# ${wl}`.trimEnd());
5313
- }
5397
+ for (const wl of buildFeatureHeaderLines(summary, COMMENT_WIDTH - 2)) {
5398
+ out.push(`# ${wl}`.trimEnd());
5314
5399
  }
5315
5400
  out.push(`${yamlPrefix} - ref: ${feature.ref}`);
5316
5401
  const options = feature.options ?? {};
@@ -5340,41 +5425,6 @@ function renderFeatureBlock(out, feature, summary, commented) {
5340
5425
  }
5341
5426
  }
5342
5427
  }
5343
- function buildHeaderLines(summary) {
5344
- const out = [];
5345
- if (!summary) {
5346
- return out;
5347
- }
5348
- const tagline = summary.name?.trim();
5349
- const description = summary.description?.trim();
5350
- if (tagline && description) {
5351
- out.push(`${tagline} \u2014 ${description}`);
5352
- } else if (tagline) {
5353
- out.push(tagline);
5354
- } else if (description) {
5355
- out.push(description);
5356
- }
5357
- for (const note of summary.usageNotes) {
5358
- const trimmed = note.trim();
5359
- if (trimmed.length > 0) out.push(trimmed);
5360
- }
5361
- if (summary.optionHints.length > 0) {
5362
- const parts = summary.optionHints.map((key) => {
5363
- const desc = summary.optionDescriptions[key];
5364
- const short = desc ? shortenOptionDescription(desc) : void 0;
5365
- return short ? `${key} (${short})` : key;
5366
- });
5367
- out.push(`Options: ${parts.join(", ")}.`);
5368
- }
5369
- if (summary.documentationURL) {
5370
- out.push(`See ${summary.documentationURL} for further information.`);
5371
- }
5372
- return out;
5373
- }
5374
- function shortenOptionDescription(desc) {
5375
- const firstSentence = desc.split(/(?<=[.!?])\s+/)[0]?.trim() ?? desc.trim();
5376
- return firstSentence.replace(/[.!?]+$/, "").trim();
5377
- }
5378
5428
  function pushHeader(out, header, name) {
5379
5429
  for (const line of header.replace(/<name>/g, name).split("\n")) {
5380
5430
  out.push(line);
@@ -5387,27 +5437,6 @@ function pushSectionHeader(out, text, _commented) {
5387
5437
  out.push(`# ${wl}`.trimEnd());
5388
5438
  }
5389
5439
  }
5390
- function wrapToComment(text, width) {
5391
- const words = text.split(/\s+/).filter((w) => w.length > 0);
5392
- if (words.length === 0) return [""];
5393
- const usable = Math.max(width, 20);
5394
- const lines = [];
5395
- let current = "";
5396
- for (const w of words) {
5397
- if (current.length === 0) {
5398
- current = w;
5399
- continue;
5400
- }
5401
- if (current.length + 1 + w.length <= usable) {
5402
- current += " " + w;
5403
- } else {
5404
- lines.push(current);
5405
- current = w;
5406
- }
5407
- }
5408
- if (current.length > 0) lines.push(current);
5409
- return lines;
5410
- }
5411
5440
  function renderScalarValue(value) {
5412
5441
  if (typeof value === "string") {
5413
5442
  return /^[A-Za-z_][A-Za-z0-9._-]*$/.test(value) ? value : JSON.stringify(value);