@getmonoceros/workbench 1.2.0 → 1.3.1
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
|
-
|
|
20
|
+
# macOS / Linux
|
|
21
|
+
curl -fsSL https://raw.githubusercontent.com/getmonoceros/workbench/main/install.sh | sh
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
219
|
+
path13.push(tok);
|
|
220
220
|
continue;
|
|
221
221
|
}
|
|
222
222
|
break;
|
|
223
223
|
}
|
|
224
|
-
return { path:
|
|
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,10 @@ 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`
|
|
2329
|
+
);
|
|
2330
|
+
logger.info(
|
|
2331
|
+
'Pulling runtime image and building feature layers. First apply takes ~1\u20132 min (Docker downloads the multi-arch base); subsequent applies are cached and fast. devcontainer-cli may log a "No manifest found" line \u2014 harmless, the pull continues.'
|
|
2316
2332
|
);
|
|
2317
2333
|
const exitCode = await runContainerCycle(targetDir, {
|
|
2318
2334
|
hasCompose: needsCompose(createOpts),
|
|
@@ -2363,7 +2379,7 @@ function warnOnDeprecatedFeatureRefs(containerFeatures, globalConfig, logger) {
|
|
|
2363
2379
|
}
|
|
2364
2380
|
|
|
2365
2381
|
// src/version.ts
|
|
2366
|
-
var CLI_VERSION = "1.
|
|
2382
|
+
var CLI_VERSION = "1.3.1";
|
|
2367
2383
|
|
|
2368
2384
|
// src/commands/_dispatch.ts
|
|
2369
2385
|
import { consola as consola11 } from "consola";
|
|
@@ -2615,7 +2631,6 @@ import { consola as consola13 } from "consola";
|
|
|
2615
2631
|
|
|
2616
2632
|
// src/init/index.ts
|
|
2617
2633
|
import { existsSync as existsSync7, promises as fs10 } from "fs";
|
|
2618
|
-
import path10 from "path";
|
|
2619
2634
|
import { consola as consola12 } from "consola";
|
|
2620
2635
|
|
|
2621
2636
|
// src/init/components.ts
|
|
@@ -2814,11 +2829,10 @@ function generateComposedYml(name, components, lookupManifest) {
|
|
|
2814
2829
|
if (merged.features.length > 0) {
|
|
2815
2830
|
lines.push("features:");
|
|
2816
2831
|
for (const f of merged.features) {
|
|
2817
|
-
const hints = lookupManifest(f.ref)?.optionHints ?? [];
|
|
2818
2832
|
renderFeatureBlock(
|
|
2819
2833
|
lines,
|
|
2820
2834
|
f,
|
|
2821
|
-
|
|
2835
|
+
lookupManifest(f.ref),
|
|
2822
2836
|
/* commented */
|
|
2823
2837
|
false
|
|
2824
2838
|
);
|
|
@@ -2903,11 +2917,10 @@ function generateDocumentedYml(name, catalog, lookupManifest) {
|
|
|
2903
2917
|
for (const f of c.file.contributes.features ?? []) {
|
|
2904
2918
|
if (renderedRefs.has(f.ref)) continue;
|
|
2905
2919
|
renderedRefs.add(f.ref);
|
|
2906
|
-
const hints = lookupManifest(f.ref)?.optionHints ?? [];
|
|
2907
2920
|
renderFeatureBlock(
|
|
2908
2921
|
lines,
|
|
2909
2922
|
f,
|
|
2910
|
-
|
|
2923
|
+
lookupManifest(f.ref),
|
|
2911
2924
|
/* commented */
|
|
2912
2925
|
true
|
|
2913
2926
|
);
|
|
@@ -2918,11 +2931,10 @@ function generateDocumentedYml(name, catalog, lookupManifest) {
|
|
|
2918
2931
|
for (const f of c.file.contributes.features ?? []) {
|
|
2919
2932
|
if (renderedRefs.has(f.ref)) continue;
|
|
2920
2933
|
renderedRefs.add(f.ref);
|
|
2921
|
-
const hints = lookupManifest(f.ref)?.optionHints ?? [];
|
|
2922
2934
|
renderFeatureBlock(
|
|
2923
2935
|
lines,
|
|
2924
2936
|
f,
|
|
2925
|
-
|
|
2937
|
+
lookupManifest(f.ref),
|
|
2926
2938
|
/* commented */
|
|
2927
2939
|
true
|
|
2928
2940
|
);
|
|
@@ -2932,8 +2944,21 @@ function generateDocumentedYml(name, catalog, lookupManifest) {
|
|
|
2932
2944
|
}
|
|
2933
2945
|
return ensureTrailingNewline(lines.join("\n"));
|
|
2934
2946
|
}
|
|
2935
|
-
|
|
2947
|
+
var COMMENT_WIDTH = 72;
|
|
2948
|
+
function renderFeatureBlock(out, feature, summary, commented) {
|
|
2936
2949
|
const c = commented ? "# " : " ";
|
|
2950
|
+
const optionHints = summary?.optionHints ?? [];
|
|
2951
|
+
const optionDescriptions = summary?.optionDescriptions ?? {};
|
|
2952
|
+
const usageNotes = summary?.usageNotes ?? [];
|
|
2953
|
+
for (let i = 0; i < usageNotes.length; i++) {
|
|
2954
|
+
if (i > 0) out.push(`${c}#`);
|
|
2955
|
+
for (const line of wrapToComment(
|
|
2956
|
+
usageNotes[i],
|
|
2957
|
+
COMMENT_WIDTH - c.length
|
|
2958
|
+
)) {
|
|
2959
|
+
out.push(`${c}# ${line}`);
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2937
2962
|
out.push(`${c}- ref: ${feature.ref}`);
|
|
2938
2963
|
const options = feature.options ?? {};
|
|
2939
2964
|
const activeOptions = Object.entries(options);
|
|
@@ -2948,7 +2973,7 @@ function renderFeatureBlock(out, feature, optionHints, commented) {
|
|
|
2948
2973
|
`${c} # Optional \u2014 override monoceros-config.yml defaults.features:`
|
|
2949
2974
|
);
|
|
2950
2975
|
for (const hint of remainingHints) {
|
|
2951
|
-
out
|
|
2976
|
+
emitHint(out, hint, optionDescriptions[hint], `${c} `);
|
|
2952
2977
|
}
|
|
2953
2978
|
}
|
|
2954
2979
|
} else if (remainingHints.length > 0) {
|
|
@@ -2957,9 +2982,41 @@ function renderFeatureBlock(out, feature, optionHints, commented) {
|
|
|
2957
2982
|
);
|
|
2958
2983
|
out.push(`${c} # options:`);
|
|
2959
2984
|
for (const hint of remainingHints) {
|
|
2960
|
-
out
|
|
2985
|
+
emitHint(out, hint, optionDescriptions[hint], `${c} # `);
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
function emitHint(out, hint, description, linePrefix) {
|
|
2990
|
+
if (description) {
|
|
2991
|
+
for (const line of wrapToComment(
|
|
2992
|
+
description,
|
|
2993
|
+
COMMENT_WIDTH - linePrefix.length
|
|
2994
|
+
)) {
|
|
2995
|
+
out.push(`${linePrefix}# ${line}`);
|
|
2961
2996
|
}
|
|
2962
2997
|
}
|
|
2998
|
+
out.push(`${linePrefix}${hint}:`);
|
|
2999
|
+
}
|
|
3000
|
+
function wrapToComment(text, width) {
|
|
3001
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
3002
|
+
if (words.length === 0) return [""];
|
|
3003
|
+
const usable = Math.max(width, 20);
|
|
3004
|
+
const lines = [];
|
|
3005
|
+
let current = "";
|
|
3006
|
+
for (const w of words) {
|
|
3007
|
+
if (current.length === 0) {
|
|
3008
|
+
current = w;
|
|
3009
|
+
continue;
|
|
3010
|
+
}
|
|
3011
|
+
if (current.length + 1 + w.length <= usable) {
|
|
3012
|
+
current += " " + w;
|
|
3013
|
+
} else {
|
|
3014
|
+
lines.push(current);
|
|
3015
|
+
current = w;
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
if (current.length > 0) lines.push(current);
|
|
3019
|
+
return lines;
|
|
2963
3020
|
}
|
|
2964
3021
|
function renderScalarValue(value) {
|
|
2965
3022
|
if (typeof value === "string") {
|
|
@@ -2988,28 +3045,50 @@ function ensureTrailingNewline(s) {
|
|
|
2988
3045
|
// src/init/manifest.ts
|
|
2989
3046
|
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
2990
3047
|
import path9 from "path";
|
|
2991
|
-
function
|
|
2992
|
-
if (
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3048
|
+
function resolveManifestPath(name, checkoutRoot) {
|
|
3049
|
+
if (checkoutRoot) {
|
|
3050
|
+
const checkoutPath = path9.join(
|
|
3051
|
+
checkoutRoot,
|
|
3052
|
+
"images",
|
|
3053
|
+
"features",
|
|
3054
|
+
name,
|
|
3055
|
+
"devcontainer-feature.json"
|
|
3056
|
+
);
|
|
3057
|
+
if (existsSync6(checkoutPath)) return checkoutPath;
|
|
3058
|
+
}
|
|
3059
|
+
const bundlePath = path9.join(
|
|
3060
|
+
bundledFeaturesDir(),
|
|
3000
3061
|
name,
|
|
3001
3062
|
"devcontainer-feature.json"
|
|
3002
3063
|
);
|
|
3003
|
-
if (
|
|
3064
|
+
if (existsSync6(bundlePath)) return bundlePath;
|
|
3065
|
+
return null;
|
|
3066
|
+
}
|
|
3067
|
+
function loadFeatureManifestSummary(ref, checkoutRoot = workbenchCheckoutRoot()) {
|
|
3068
|
+
const match = matchMonocerosFeature(ref);
|
|
3069
|
+
if (!match) return void 0;
|
|
3070
|
+
const manifestPath = resolveManifestPath(match.name, checkoutRoot);
|
|
3071
|
+
if (!manifestPath) return void 0;
|
|
3004
3072
|
try {
|
|
3005
3073
|
const text = readFileSync3(manifestPath, "utf8");
|
|
3006
3074
|
const parsed = JSON.parse(text);
|
|
3007
|
-
const
|
|
3008
|
-
|
|
3009
|
-
const hints = raw.filter(
|
|
3075
|
+
const rawHints = parsed["x-monoceros"]?.optionHints;
|
|
3076
|
+
const optionHints = Array.isArray(rawHints) ? rawHints.filter(
|
|
3010
3077
|
(x) => typeof x === "string" && x.length > 0
|
|
3011
|
-
);
|
|
3012
|
-
|
|
3078
|
+
) : [];
|
|
3079
|
+
const rawNotes = parsed["x-monoceros"]?.usageNotes;
|
|
3080
|
+
const usageNotes = Array.isArray(rawNotes) ? rawNotes.filter(
|
|
3081
|
+
(x) => typeof x === "string" && x.length > 0
|
|
3082
|
+
) : [];
|
|
3083
|
+
const optionDescriptions = {};
|
|
3084
|
+
if (parsed.options) {
|
|
3085
|
+
for (const [key, opt] of Object.entries(parsed.options)) {
|
|
3086
|
+
if (opt && typeof opt === "object" && typeof opt.description === "string" && opt.description.length > 0) {
|
|
3087
|
+
optionDescriptions[key] = opt.description;
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
return { optionHints, optionDescriptions, usageNotes };
|
|
3013
3092
|
} catch {
|
|
3014
3093
|
return void 0;
|
|
3015
3094
|
}
|
|
@@ -3053,14 +3132,14 @@ async function runInit(opts) {
|
|
|
3053
3132
|
await fs10.mkdir(containerConfigsDir(home), { recursive: true });
|
|
3054
3133
|
await fs10.writeFile(dest, text, "utf8");
|
|
3055
3134
|
const documented = requested.length === 0;
|
|
3056
|
-
const
|
|
3135
|
+
const displayPath = prettyPath(dest);
|
|
3057
3136
|
if (documented) {
|
|
3058
3137
|
logger.success(
|
|
3059
|
-
`Wrote documented default to ${
|
|
3138
|
+
`Wrote documented default to ${displayPath}. Un-comment what you need, then \`monoceros apply ${opts.name}\`.`
|
|
3060
3139
|
);
|
|
3061
3140
|
} else {
|
|
3062
3141
|
logger.success(
|
|
3063
|
-
`Composed ${requested.length} component(s) into ${
|
|
3142
|
+
`Composed ${requested.length} component(s) into ${displayPath}: ${requested.join(", ")}`
|
|
3064
3143
|
);
|
|
3065
3144
|
logger.info(
|
|
3066
3145
|
`Edit the file if you need to tweak, then \`monoceros apply ${opts.name}\`.`
|
|
@@ -3297,7 +3376,7 @@ import { createInterface } from "readline/promises";
|
|
|
3297
3376
|
|
|
3298
3377
|
// src/remove/index.ts
|
|
3299
3378
|
import { existsSync as existsSync8, promises as fs11 } from "fs";
|
|
3300
|
-
import
|
|
3379
|
+
import path10 from "path";
|
|
3301
3380
|
import { consola as consola17 } from "consola";
|
|
3302
3381
|
async function runRemove(opts) {
|
|
3303
3382
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -3340,19 +3419,17 @@ async function runRemove(opts) {
|
|
|
3340
3419
|
let backupPath = null;
|
|
3341
3420
|
if (!opts.noBackup && (hasYml || hasContainer)) {
|
|
3342
3421
|
const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3343
|
-
backupPath =
|
|
3422
|
+
backupPath = path10.join(home, "container-backups", `${opts.name}-${ts}`);
|
|
3344
3423
|
await fs11.mkdir(backupPath, { recursive: true });
|
|
3345
3424
|
if (hasYml) {
|
|
3346
|
-
await fs11.copyFile(ymlPath,
|
|
3425
|
+
await fs11.copyFile(ymlPath, path10.join(backupPath, `${opts.name}.yml`));
|
|
3347
3426
|
}
|
|
3348
3427
|
if (hasContainer) {
|
|
3349
|
-
await fs11.cp(containerPath,
|
|
3428
|
+
await fs11.cp(containerPath, path10.join(backupPath, "container"), {
|
|
3350
3429
|
recursive: true
|
|
3351
3430
|
});
|
|
3352
3431
|
}
|
|
3353
|
-
logger.info(
|
|
3354
|
-
`Backup written to ${path11.relative(home, backupPath) || backupPath}.`
|
|
3355
|
-
);
|
|
3432
|
+
logger.info(`Backup written to ${prettyPath(backupPath)}.`);
|
|
3356
3433
|
}
|
|
3357
3434
|
if (hasYml) {
|
|
3358
3435
|
await fs11.rm(ymlPath, { force: true });
|
|
@@ -3443,7 +3520,7 @@ import { consola as consola20 } from "consola";
|
|
|
3443
3520
|
|
|
3444
3521
|
// src/restore/index.ts
|
|
3445
3522
|
import { existsSync as existsSync9, promises as fs12 } from "fs";
|
|
3446
|
-
import
|
|
3523
|
+
import path11 from "path";
|
|
3447
3524
|
import { consola as consola19 } from "consola";
|
|
3448
3525
|
async function runRestore(opts) {
|
|
3449
3526
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -3451,7 +3528,7 @@ async function runRestore(opts) {
|
|
|
3451
3528
|
info: (msg) => consola19.info(msg),
|
|
3452
3529
|
success: (msg) => consola19.success(msg)
|
|
3453
3530
|
};
|
|
3454
|
-
const backup =
|
|
3531
|
+
const backup = path11.resolve(opts.backupPath);
|
|
3455
3532
|
if (!existsSync9(backup)) {
|
|
3456
3533
|
throw new Error(`Backup not found: ${backup}.`);
|
|
3457
3534
|
}
|
|
@@ -3473,7 +3550,7 @@ async function runRestore(opts) {
|
|
|
3473
3550
|
}
|
|
3474
3551
|
const ymlFile = ymlFiles[0];
|
|
3475
3552
|
const name = ymlFile.replace(/\.yml$/, "");
|
|
3476
|
-
const containerInBackup =
|
|
3553
|
+
const containerInBackup = path11.join(backup, "container");
|
|
3477
3554
|
const hasContainer = existsSync9(containerInBackup);
|
|
3478
3555
|
const destYml = containerConfigPath(name, home);
|
|
3479
3556
|
const destContainer = containerDir(name, home);
|
|
@@ -3488,13 +3565,11 @@ async function runRestore(opts) {
|
|
|
3488
3565
|
);
|
|
3489
3566
|
}
|
|
3490
3567
|
await fs12.mkdir(containerConfigsDir(home), { recursive: true });
|
|
3491
|
-
await fs12.copyFile(
|
|
3568
|
+
await fs12.copyFile(path11.join(backup, ymlFile), destYml);
|
|
3492
3569
|
if (hasContainer) {
|
|
3493
3570
|
await fs12.cp(containerInBackup, destContainer, { recursive: true });
|
|
3494
3571
|
}
|
|
3495
|
-
logger.success(
|
|
3496
|
-
`Restored '${name}' from ${path12.relative(home, backup) || backup}.`
|
|
3497
|
-
);
|
|
3572
|
+
logger.success(`Restored '${name}' from ${prettyPath(backup)}.`);
|
|
3498
3573
|
logger.info(
|
|
3499
3574
|
`Run \`monoceros apply ${name}\` to bring the container back up.`
|
|
3500
3575
|
);
|
|
@@ -3703,7 +3778,7 @@ import { consola as consola25 } from "consola";
|
|
|
3703
3778
|
|
|
3704
3779
|
// src/devcontainer/shell.ts
|
|
3705
3780
|
import { existsSync as existsSync10 } from "fs";
|
|
3706
|
-
import
|
|
3781
|
+
import path12 from "path";
|
|
3707
3782
|
async function runShell(opts) {
|
|
3708
3783
|
assertContainerExists(opts.root);
|
|
3709
3784
|
const spawnFn = opts.spawn ?? spawnDevcontainer;
|
|
@@ -3718,7 +3793,7 @@ async function runShell(opts) {
|
|
|
3718
3793
|
});
|
|
3719
3794
|
}
|
|
3720
3795
|
function assertContainerExists(root) {
|
|
3721
|
-
if (!existsSync10(
|
|
3796
|
+
if (!existsSync10(path12.join(root, ".devcontainer"))) {
|
|
3722
3797
|
throw new Error(
|
|
3723
3798
|
`No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
|
|
3724
3799
|
);
|