@getmonoceros/workbench 1.9.5 → 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
  }
@@ -2777,7 +2933,7 @@ async function tryCloneInRunningContainer(input, entry2) {
2777
2933
  logger.info(
2778
2934
  `Cloned ${entry2.url} into /workspaces/${containerName2}/${targetRel} inside the running container.`
2779
2935
  );
2780
- void path7;
2936
+ void path8;
2781
2937
  }
2782
2938
  function shquote(value) {
2783
2939
  return `'${value.replace(/'/g, `'\\''`)}'`;
@@ -3456,12 +3612,12 @@ var addServiceCommand = defineCommand7({
3456
3612
  import { defineCommand as defineCommand8 } from "citty";
3457
3613
 
3458
3614
  // src/apply/index.ts
3459
- import { existsSync as existsSync5, promises as fs11 } from "fs";
3615
+ import { existsSync as existsSync6, promises as fs11 } from "fs";
3460
3616
  import { consola as consola11 } from "consola";
3461
3617
 
3462
3618
  // src/config/state.ts
3463
3619
  import { promises as fs9 } from "fs";
3464
- import path8 from "path";
3620
+ import path9 from "path";
3465
3621
  function buildStateFile(opts) {
3466
3622
  return {
3467
3623
  schemaVersion: CONFIG_SCHEMA_VERSION,
@@ -3471,7 +3627,7 @@ function buildStateFile(opts) {
3471
3627
  };
3472
3628
  }
3473
3629
  function stateFilePath(targetDir) {
3474
- return path8.join(targetDir, ".monoceros", "state.json");
3630
+ return path9.join(targetDir, ".monoceros", "state.json");
3475
3631
  }
3476
3632
  async function readStateFile(targetDir) {
3477
3633
  try {
@@ -3482,7 +3638,7 @@ async function readStateFile(targetDir) {
3482
3638
  }
3483
3639
  }
3484
3640
  async function writeStateFile(targetDir, state) {
3485
- const monocerosDir = path8.join(targetDir, ".monoceros");
3641
+ const monocerosDir = path9.join(targetDir, ".monoceros");
3486
3642
  await fs9.mkdir(monocerosDir, { recursive: true });
3487
3643
  await fs9.writeFile(
3488
3644
  stateFilePath(targetDir),
@@ -3554,8 +3710,8 @@ function solutionConfigToCreateOptions(config, featureDefaults = {}) {
3554
3710
 
3555
3711
  // src/devcontainer/compose.ts
3556
3712
  import { spawn as spawn5 } from "child_process";
3557
- import { existsSync as existsSync4 } from "fs";
3558
- import path10 from "path";
3713
+ import { existsSync as existsSync5 } from "fs";
3714
+ import path11 from "path";
3559
3715
  import { consola as consola9 } from "consola";
3560
3716
 
3561
3717
  // src/util/mask-secrets.ts
@@ -3616,20 +3772,20 @@ function createSecretMaskStream() {
3616
3772
 
3617
3773
  // src/devcontainer/cli.ts
3618
3774
  import { spawn as spawn4 } from "child_process";
3619
- import { readFileSync as readFileSync2 } from "fs";
3775
+ import { readFileSync as readFileSync3 } from "fs";
3620
3776
  import { createRequire } from "module";
3621
- import path9 from "path";
3777
+ import path10 from "path";
3622
3778
  var require_ = createRequire(import.meta.url);
3623
3779
  var cachedBinaryPath = null;
3624
3780
  function devcontainerCliPath() {
3625
3781
  if (cachedBinaryPath) return cachedBinaryPath;
3626
3782
  const pkgJsonPath = require_.resolve("@devcontainers/cli/package.json");
3627
- const pkg = JSON.parse(readFileSync2(pkgJsonPath, "utf8"));
3783
+ const pkg = JSON.parse(readFileSync3(pkgJsonPath, "utf8"));
3628
3784
  const binEntry = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.devcontainer ?? "";
3629
3785
  if (!binEntry) {
3630
3786
  throw new Error("Could not resolve @devcontainers/cli bin entry.");
3631
3787
  }
3632
- cachedBinaryPath = path9.resolve(path9.dirname(pkgJsonPath), binEntry);
3788
+ cachedBinaryPath = path10.resolve(path10.dirname(pkgJsonPath), binEntry);
3633
3789
  return cachedBinaryPath;
3634
3790
  }
3635
3791
  var spawnDevcontainer = (args, cwd, options = {}) => {
@@ -3701,16 +3857,16 @@ var spawnBash = (args, cwd) => {
3701
3857
  });
3702
3858
  };
3703
3859
  function composeProjectName(root) {
3704
- return `${path10.basename(root)}_devcontainer`;
3860
+ return `${path11.basename(root)}_devcontainer`;
3705
3861
  }
3706
3862
  function resolveCompose(root) {
3707
- if (!existsSync4(path10.join(root, ".devcontainer"))) {
3863
+ if (!existsSync5(path11.join(root, ".devcontainer"))) {
3708
3864
  throw new Error(
3709
3865
  `No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
3710
3866
  );
3711
3867
  }
3712
- const composeFile = path10.join(root, ".devcontainer", "compose.yaml");
3713
- if (!existsSync4(composeFile)) {
3868
+ const composeFile = path11.join(root, ".devcontainer", "compose.yaml");
3869
+ if (!existsSync5(composeFile)) {
3714
3870
  throw new Error(
3715
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.`
3716
3872
  );
@@ -4005,7 +4161,7 @@ function formatRootlessNotSupportedError() {
4005
4161
  // src/devcontainer/identity.ts
4006
4162
  import { spawn as spawn8 } from "child_process";
4007
4163
  import { promises as fs10 } from "fs";
4008
- import path11 from "path";
4164
+ import path12 from "path";
4009
4165
  import { consola as consola10 } from "consola";
4010
4166
  var realGitConfigGet = (key) => {
4011
4167
  return new Promise((resolve, reject) => {
@@ -4119,8 +4275,8 @@ async function resolveIdentityWithPrompt(options = {}) {
4119
4275
  };
4120
4276
  }
4121
4277
  async function collectGitIdentity(devContainerRoot, options = {}) {
4122
- const gitconfigDir = path11.join(devContainerRoot, ".monoceros");
4123
- const gitconfigPath = path11.join(gitconfigDir, "gitconfig");
4278
+ const gitconfigDir = path12.join(devContainerRoot, ".monoceros");
4279
+ const gitconfigPath = path12.join(gitconfigDir, "gitconfig");
4124
4280
  const logger = options.logger ?? { info: () => {
4125
4281
  }, warn: () => {
4126
4282
  } };
@@ -4210,7 +4366,7 @@ ${sectionLine(label)}
4210
4366
  );
4211
4367
  }
4212
4368
  const ymlPath = containerConfigPath(opts.name, home);
4213
- if (!existsSync5(ymlPath)) {
4369
+ if (!existsSync6(ymlPath)) {
4214
4370
  throw new Error(
4215
4371
  `No such config: ${ymlPath}. Run \`monoceros init <template> ${opts.name}\` first.`
4216
4372
  );
@@ -4339,7 +4495,7 @@ ${sectionLine(label)}
4339
4495
  return { targetDir, configPath: ymlPath, containerExitCode: exitCode };
4340
4496
  }
4341
4497
  async function assertSafeTargetDir(targetDir, expectedOrigin) {
4342
- if (!existsSync5(targetDir)) return;
4498
+ if (!existsSync6(targetDir)) return;
4343
4499
  const entries = await fs11.readdir(targetDir);
4344
4500
  if (entries.length === 0) return;
4345
4501
  const state = await readStateFile(targetDir);
@@ -4432,7 +4588,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
4432
4588
  }
4433
4589
 
4434
4590
  // src/version.ts
4435
- var CLI_VERSION = true ? "1.9.5" : "dev";
4591
+ var CLI_VERSION = true ? "1.9.6" : "dev";
4436
4592
 
4437
4593
  // src/commands/_dispatch.ts
4438
4594
  import { consola as consola12 } from "consola";
@@ -4594,82 +4750,6 @@ import { defineCommand as defineCommand10 } from "citty";
4594
4750
  // src/completion/resolve.ts
4595
4751
  import { existsSync as existsSync7, promises as fs12 } from "fs";
4596
4752
  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
4753
  async function resolveCompletions(line, point, opts = {}) {
4674
4754
  const { prev, current } = parseCompletionLine(line, point);
4675
4755
  const ctx = { prev, current, opts };
@@ -5305,12 +5385,8 @@ function routingHeader(name) {
5305
5385
  }
5306
5386
  function renderFeatureBlock(out, feature, summary, commented) {
5307
5387
  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
- }
5388
+ for (const wl of buildFeatureHeaderLines(summary, COMMENT_WIDTH - 2)) {
5389
+ out.push(`# ${wl}`.trimEnd());
5314
5390
  }
5315
5391
  out.push(`${yamlPrefix} - ref: ${feature.ref}`);
5316
5392
  const options = feature.options ?? {};
@@ -5340,41 +5416,6 @@ function renderFeatureBlock(out, feature, summary, commented) {
5340
5416
  }
5341
5417
  }
5342
5418
  }
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
5419
  function pushHeader(out, header, name) {
5379
5420
  for (const line of header.replace(/<name>/g, name).split("\n")) {
5380
5421
  out.push(line);
@@ -5387,27 +5428,6 @@ function pushSectionHeader(out, text, _commented) {
5387
5428
  out.push(`# ${wl}`.trimEnd());
5388
5429
  }
5389
5430
  }
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
5431
  function renderScalarValue(value) {
5412
5432
  if (typeof value === "string") {
5413
5433
  return /^[A-Za-z_][A-Za-z0-9._-]*$/.test(value) ? value : JSON.stringify(value);