@getmonoceros/workbench 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,16 +17,18 @@ das vorab und geben plattform-spezifische Anleitung aus.
17
17
  ## Installation
18
18
 
19
19
  ```sh
20
- npm install -g @getmonoceros/workbench
20
+ # macOS / Linux
21
+ curl -fsSL https://raw.githubusercontent.com/getmonoceros/workbench/main/install.sh | sh
21
22
  ```
22
23
 
23
- Oder über das Install-Skript:
24
-
25
- ```sh
26
- curl -fsSL https://raw.githubusercontent.com/getmonoceros/workbench/main/install.sh | sh
24
+ ```powershell
25
+ # Windows (PowerShell)
26
+ iwr -useb https://raw.githubusercontent.com/getmonoceros/workbench/main/install.ps1 | iex
27
27
  ```
28
28
 
29
- Windows: das passende `install.ps1` über PowerShell.
29
+ Das Skript prüft Docker + Node, installiert das Paket global via
30
+ `npm install -g`, und richtet die Shell-Completion für deine Shell
31
+ ein.
30
32
 
31
33
  ## Erste Schritte
32
34
 
package/dist/bin.js CHANGED
@@ -203,25 +203,25 @@ function detectHelpRequest(argv, main2) {
203
203
  const separatorIdx = argv.indexOf("--");
204
204
  if (helpIdx === -1) return null;
205
205
  if (separatorIdx !== -1 && separatorIdx < helpIdx) return null;
206
- const path14 = [];
206
+ const path13 = [];
207
207
  const tokens = argv.slice(
208
208
  0,
209
209
  separatorIdx === -1 ? argv.length : separatorIdx
210
210
  );
211
211
  let cursor = main2;
212
212
  const mainName = (main2.meta ?? {}).name ?? "monoceros";
213
- path14.push(mainName);
213
+ path13.push(mainName);
214
214
  for (const tok of tokens) {
215
215
  if (tok.startsWith("-")) continue;
216
216
  const subs = cursor.subCommands ?? {};
217
217
  if (tok in subs) {
218
218
  cursor = subs[tok];
219
- path14.push(tok);
219
+ path13.push(tok);
220
220
  continue;
221
221
  }
222
222
  break;
223
223
  }
224
- return { path: path14, cmd: cursor };
224
+ return { path: path13, cmd: cursor };
225
225
  }
226
226
  async function maybeRenderHelp(argv, main2) {
227
227
  const hit = detectHelpRequest(argv, main2);
@@ -451,6 +451,9 @@ function workbenchCheckoutRoot() {
451
451
  function componentsDir(root = workbenchRoot()) {
452
452
  return path.join(root, "templates", "components");
453
453
  }
454
+ function bundledFeaturesDir(root = workbenchRoot()) {
455
+ return path.join(root, "features");
456
+ }
454
457
  function containerConfigsDir(home = monocerosHome()) {
455
458
  return path.join(home, "container-configs");
456
459
  }
@@ -466,6 +469,16 @@ function containerDir(name, home = monocerosHome()) {
466
469
  function monocerosConfigPath(home = monocerosHome()) {
467
470
  return path.join(home, "monoceros-config.yml");
468
471
  }
472
+ function prettyPath(p) {
473
+ const home = os.homedir();
474
+ if (!home) return p;
475
+ if (p === home) return "~";
476
+ const prefix = home.endsWith(path.sep) ? home : home + path.sep;
477
+ if (p.startsWith(prefix)) {
478
+ return "~" + path.sep + p.slice(prefix.length);
479
+ }
480
+ return p;
481
+ }
469
482
 
470
483
  // src/create/catalog.ts
471
484
  var DEFAULT_BASE_IMAGE = "ghcr.io/getmonoceros/monoceros-runtime:1";
@@ -2312,7 +2325,7 @@ async function runApply(opts) {
2312
2325
  });
2313
2326
  }
2314
2327
  logger.success(
2315
- `Materialized config '${opts.name}' into ${targetDir}. Starting container\u2026`
2328
+ `Materialized config '${opts.name}' into ${prettyPath(targetDir)}. Starting container\u2026`
2316
2329
  );
2317
2330
  const exitCode = await runContainerCycle(targetDir, {
2318
2331
  hasCompose: needsCompose(createOpts),
@@ -2363,7 +2376,7 @@ function warnOnDeprecatedFeatureRefs(containerFeatures, globalConfig, logger) {
2363
2376
  }
2364
2377
 
2365
2378
  // src/version.ts
2366
- var CLI_VERSION = "1.2.0";
2379
+ var CLI_VERSION = "1.3.0";
2367
2380
 
2368
2381
  // src/commands/_dispatch.ts
2369
2382
  import { consola as consola11 } from "consola";
@@ -2615,7 +2628,6 @@ import { consola as consola13 } from "consola";
2615
2628
 
2616
2629
  // src/init/index.ts
2617
2630
  import { existsSync as existsSync7, promises as fs10 } from "fs";
2618
- import path10 from "path";
2619
2631
  import { consola as consola12 } from "consola";
2620
2632
 
2621
2633
  // src/init/components.ts
@@ -2814,11 +2826,10 @@ function generateComposedYml(name, components, lookupManifest) {
2814
2826
  if (merged.features.length > 0) {
2815
2827
  lines.push("features:");
2816
2828
  for (const f of merged.features) {
2817
- const hints = lookupManifest(f.ref)?.optionHints ?? [];
2818
2829
  renderFeatureBlock(
2819
2830
  lines,
2820
2831
  f,
2821
- hints,
2832
+ lookupManifest(f.ref),
2822
2833
  /* commented */
2823
2834
  false
2824
2835
  );
@@ -2903,11 +2914,10 @@ function generateDocumentedYml(name, catalog, lookupManifest) {
2903
2914
  for (const f of c.file.contributes.features ?? []) {
2904
2915
  if (renderedRefs.has(f.ref)) continue;
2905
2916
  renderedRefs.add(f.ref);
2906
- const hints = lookupManifest(f.ref)?.optionHints ?? [];
2907
2917
  renderFeatureBlock(
2908
2918
  lines,
2909
2919
  f,
2910
- hints,
2920
+ lookupManifest(f.ref),
2911
2921
  /* commented */
2912
2922
  true
2913
2923
  );
@@ -2918,11 +2928,10 @@ function generateDocumentedYml(name, catalog, lookupManifest) {
2918
2928
  for (const f of c.file.contributes.features ?? []) {
2919
2929
  if (renderedRefs.has(f.ref)) continue;
2920
2930
  renderedRefs.add(f.ref);
2921
- const hints = lookupManifest(f.ref)?.optionHints ?? [];
2922
2931
  renderFeatureBlock(
2923
2932
  lines,
2924
2933
  f,
2925
- hints,
2934
+ lookupManifest(f.ref),
2926
2935
  /* commented */
2927
2936
  true
2928
2937
  );
@@ -2932,8 +2941,21 @@ function generateDocumentedYml(name, catalog, lookupManifest) {
2932
2941
  }
2933
2942
  return ensureTrailingNewline(lines.join("\n"));
2934
2943
  }
2935
- function renderFeatureBlock(out, feature, optionHints, commented) {
2944
+ var COMMENT_WIDTH = 72;
2945
+ function renderFeatureBlock(out, feature, summary, commented) {
2936
2946
  const c = commented ? "# " : " ";
2947
+ const optionHints = summary?.optionHints ?? [];
2948
+ const optionDescriptions = summary?.optionDescriptions ?? {};
2949
+ const usageNotes = summary?.usageNotes ?? [];
2950
+ for (let i = 0; i < usageNotes.length; i++) {
2951
+ if (i > 0) out.push(`${c}#`);
2952
+ for (const line of wrapToComment(
2953
+ usageNotes[i],
2954
+ COMMENT_WIDTH - c.length
2955
+ )) {
2956
+ out.push(`${c}# ${line}`);
2957
+ }
2958
+ }
2937
2959
  out.push(`${c}- ref: ${feature.ref}`);
2938
2960
  const options = feature.options ?? {};
2939
2961
  const activeOptions = Object.entries(options);
@@ -2948,7 +2970,7 @@ function renderFeatureBlock(out, feature, optionHints, commented) {
2948
2970
  `${c} # Optional \u2014 override monoceros-config.yml defaults.features:`
2949
2971
  );
2950
2972
  for (const hint of remainingHints) {
2951
- out.push(`${c} # ${hint}:`);
2973
+ emitHint(out, hint, optionDescriptions[hint], `${c} `);
2952
2974
  }
2953
2975
  }
2954
2976
  } else if (remainingHints.length > 0) {
@@ -2957,10 +2979,42 @@ function renderFeatureBlock(out, feature, optionHints, commented) {
2957
2979
  );
2958
2980
  out.push(`${c} # options:`);
2959
2981
  for (const hint of remainingHints) {
2960
- out.push(`${c} # ${hint}:`);
2982
+ emitHint(out, hint, optionDescriptions[hint], `${c} # `);
2961
2983
  }
2962
2984
  }
2963
2985
  }
2986
+ function emitHint(out, hint, description, linePrefix) {
2987
+ if (description) {
2988
+ for (const line of wrapToComment(
2989
+ description,
2990
+ COMMENT_WIDTH - linePrefix.length
2991
+ )) {
2992
+ out.push(`${linePrefix}# ${line}`);
2993
+ }
2994
+ }
2995
+ out.push(`${linePrefix}${hint}:`);
2996
+ }
2997
+ function wrapToComment(text, width) {
2998
+ const words = text.split(/\s+/).filter((w) => w.length > 0);
2999
+ if (words.length === 0) return [""];
3000
+ const usable = Math.max(width, 20);
3001
+ const lines = [];
3002
+ let current = "";
3003
+ for (const w of words) {
3004
+ if (current.length === 0) {
3005
+ current = w;
3006
+ continue;
3007
+ }
3008
+ if (current.length + 1 + w.length <= usable) {
3009
+ current += " " + w;
3010
+ } else {
3011
+ lines.push(current);
3012
+ current = w;
3013
+ }
3014
+ }
3015
+ if (current.length > 0) lines.push(current);
3016
+ return lines;
3017
+ }
2964
3018
  function renderScalarValue(value) {
2965
3019
  if (typeof value === "string") {
2966
3020
  return /^[A-Za-z_][A-Za-z0-9._-]*$/.test(value) ? value : JSON.stringify(value);
@@ -2988,28 +3042,50 @@ function ensureTrailingNewline(s) {
2988
3042
  // src/init/manifest.ts
2989
3043
  import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
2990
3044
  import path9 from "path";
2991
- function loadFeatureManifestSummary(ref, checkoutRoot = workbenchCheckoutRoot()) {
2992
- if (!checkoutRoot) return void 0;
2993
- const match = matchMonocerosFeature(ref);
2994
- if (!match) return void 0;
2995
- const name = match.name;
2996
- const manifestPath = path9.join(
2997
- checkoutRoot,
2998
- "images",
2999
- "features",
3045
+ function resolveManifestPath(name, checkoutRoot) {
3046
+ if (checkoutRoot) {
3047
+ const checkoutPath = path9.join(
3048
+ checkoutRoot,
3049
+ "images",
3050
+ "features",
3051
+ name,
3052
+ "devcontainer-feature.json"
3053
+ );
3054
+ if (existsSync6(checkoutPath)) return checkoutPath;
3055
+ }
3056
+ const bundlePath = path9.join(
3057
+ bundledFeaturesDir(),
3000
3058
  name,
3001
3059
  "devcontainer-feature.json"
3002
3060
  );
3003
- if (!existsSync6(manifestPath)) return void 0;
3061
+ if (existsSync6(bundlePath)) return bundlePath;
3062
+ return null;
3063
+ }
3064
+ function loadFeatureManifestSummary(ref, checkoutRoot = workbenchCheckoutRoot()) {
3065
+ const match = matchMonocerosFeature(ref);
3066
+ if (!match) return void 0;
3067
+ const manifestPath = resolveManifestPath(match.name, checkoutRoot);
3068
+ if (!manifestPath) return void 0;
3004
3069
  try {
3005
3070
  const text = readFileSync3(manifestPath, "utf8");
3006
3071
  const parsed = JSON.parse(text);
3007
- const raw = parsed["x-monoceros"]?.optionHints;
3008
- if (!Array.isArray(raw)) return { optionHints: [] };
3009
- const hints = raw.filter(
3072
+ const rawHints = parsed["x-monoceros"]?.optionHints;
3073
+ const optionHints = Array.isArray(rawHints) ? rawHints.filter(
3010
3074
  (x) => typeof x === "string" && x.length > 0
3011
- );
3012
- return { optionHints: hints };
3075
+ ) : [];
3076
+ const rawNotes = parsed["x-monoceros"]?.usageNotes;
3077
+ const usageNotes = Array.isArray(rawNotes) ? rawNotes.filter(
3078
+ (x) => typeof x === "string" && x.length > 0
3079
+ ) : [];
3080
+ const optionDescriptions = {};
3081
+ if (parsed.options) {
3082
+ for (const [key, opt] of Object.entries(parsed.options)) {
3083
+ if (opt && typeof opt === "object" && typeof opt.description === "string" && opt.description.length > 0) {
3084
+ optionDescriptions[key] = opt.description;
3085
+ }
3086
+ }
3087
+ }
3088
+ return { optionHints, optionDescriptions, usageNotes };
3013
3089
  } catch {
3014
3090
  return void 0;
3015
3091
  }
@@ -3053,14 +3129,14 @@ async function runInit(opts) {
3053
3129
  await fs10.mkdir(containerConfigsDir(home), { recursive: true });
3054
3130
  await fs10.writeFile(dest, text, "utf8");
3055
3131
  const documented = requested.length === 0;
3056
- const rel = path10.relative(home, dest) || dest;
3132
+ const displayPath = prettyPath(dest);
3057
3133
  if (documented) {
3058
3134
  logger.success(
3059
- `Wrote documented default to ${rel}. Un-comment what you need, then \`monoceros apply ${opts.name}\`.`
3135
+ `Wrote documented default to ${displayPath}. Un-comment what you need, then \`monoceros apply ${opts.name}\`.`
3060
3136
  );
3061
3137
  } else {
3062
3138
  logger.success(
3063
- `Composed ${requested.length} component(s) into ${rel}: ${requested.join(", ")}`
3139
+ `Composed ${requested.length} component(s) into ${displayPath}: ${requested.join(", ")}`
3064
3140
  );
3065
3141
  logger.info(
3066
3142
  `Edit the file if you need to tweak, then \`monoceros apply ${opts.name}\`.`
@@ -3297,7 +3373,7 @@ import { createInterface } from "readline/promises";
3297
3373
 
3298
3374
  // src/remove/index.ts
3299
3375
  import { existsSync as existsSync8, promises as fs11 } from "fs";
3300
- import path11 from "path";
3376
+ import path10 from "path";
3301
3377
  import { consola as consola17 } from "consola";
3302
3378
  async function runRemove(opts) {
3303
3379
  const home = opts.monocerosHome ?? monocerosHome();
@@ -3340,19 +3416,17 @@ async function runRemove(opts) {
3340
3416
  let backupPath = null;
3341
3417
  if (!opts.noBackup && (hasYml || hasContainer)) {
3342
3418
  const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3343
- backupPath = path11.join(home, "container-backups", `${opts.name}-${ts}`);
3419
+ backupPath = path10.join(home, "container-backups", `${opts.name}-${ts}`);
3344
3420
  await fs11.mkdir(backupPath, { recursive: true });
3345
3421
  if (hasYml) {
3346
- await fs11.copyFile(ymlPath, path11.join(backupPath, `${opts.name}.yml`));
3422
+ await fs11.copyFile(ymlPath, path10.join(backupPath, `${opts.name}.yml`));
3347
3423
  }
3348
3424
  if (hasContainer) {
3349
- await fs11.cp(containerPath, path11.join(backupPath, "container"), {
3425
+ await fs11.cp(containerPath, path10.join(backupPath, "container"), {
3350
3426
  recursive: true
3351
3427
  });
3352
3428
  }
3353
- logger.info(
3354
- `Backup written to ${path11.relative(home, backupPath) || backupPath}.`
3355
- );
3429
+ logger.info(`Backup written to ${prettyPath(backupPath)}.`);
3356
3430
  }
3357
3431
  if (hasYml) {
3358
3432
  await fs11.rm(ymlPath, { force: true });
@@ -3443,7 +3517,7 @@ import { consola as consola20 } from "consola";
3443
3517
 
3444
3518
  // src/restore/index.ts
3445
3519
  import { existsSync as existsSync9, promises as fs12 } from "fs";
3446
- import path12 from "path";
3520
+ import path11 from "path";
3447
3521
  import { consola as consola19 } from "consola";
3448
3522
  async function runRestore(opts) {
3449
3523
  const home = opts.monocerosHome ?? monocerosHome();
@@ -3451,7 +3525,7 @@ async function runRestore(opts) {
3451
3525
  info: (msg) => consola19.info(msg),
3452
3526
  success: (msg) => consola19.success(msg)
3453
3527
  };
3454
- const backup = path12.resolve(opts.backupPath);
3528
+ const backup = path11.resolve(opts.backupPath);
3455
3529
  if (!existsSync9(backup)) {
3456
3530
  throw new Error(`Backup not found: ${backup}.`);
3457
3531
  }
@@ -3473,7 +3547,7 @@ async function runRestore(opts) {
3473
3547
  }
3474
3548
  const ymlFile = ymlFiles[0];
3475
3549
  const name = ymlFile.replace(/\.yml$/, "");
3476
- const containerInBackup = path12.join(backup, "container");
3550
+ const containerInBackup = path11.join(backup, "container");
3477
3551
  const hasContainer = existsSync9(containerInBackup);
3478
3552
  const destYml = containerConfigPath(name, home);
3479
3553
  const destContainer = containerDir(name, home);
@@ -3488,13 +3562,11 @@ async function runRestore(opts) {
3488
3562
  );
3489
3563
  }
3490
3564
  await fs12.mkdir(containerConfigsDir(home), { recursive: true });
3491
- await fs12.copyFile(path12.join(backup, ymlFile), destYml);
3565
+ await fs12.copyFile(path11.join(backup, ymlFile), destYml);
3492
3566
  if (hasContainer) {
3493
3567
  await fs12.cp(containerInBackup, destContainer, { recursive: true });
3494
3568
  }
3495
- logger.success(
3496
- `Restored '${name}' from ${path12.relative(home, backup) || backup}.`
3497
- );
3569
+ logger.success(`Restored '${name}' from ${prettyPath(backup)}.`);
3498
3570
  logger.info(
3499
3571
  `Run \`monoceros apply ${name}\` to bring the container back up.`
3500
3572
  );
@@ -3703,7 +3775,7 @@ import { consola as consola25 } from "consola";
3703
3775
 
3704
3776
  // src/devcontainer/shell.ts
3705
3777
  import { existsSync as existsSync10 } from "fs";
3706
- import path13 from "path";
3778
+ import path12 from "path";
3707
3779
  async function runShell(opts) {
3708
3780
  assertContainerExists(opts.root);
3709
3781
  const spawnFn = opts.spawn ?? spawnDevcontainer;
@@ -3718,7 +3790,7 @@ async function runShell(opts) {
3718
3790
  });
3719
3791
  }
3720
3792
  function assertContainerExists(root) {
3721
- if (!existsSync10(path13.join(root, ".devcontainer"))) {
3793
+ if (!existsSync10(path12.join(root, ".devcontainer"))) {
3722
3794
  throw new Error(
3723
3795
  `No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
3724
3796
  );