@getmonoceros/workbench 1.20.1 → 1.21.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/dist/bin.js +621 -171
- package/dist/bin.js.map +1 -1
- package/features/claude-code/devcontainer-feature.json +7 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1415,6 +1415,12 @@ var init_global = __esm({
|
|
|
1415
1415
|
hostPort: z2.number().int().min(1).max(65535).optional().describe(
|
|
1416
1416
|
"Host port the Traefik singleton binds. Default 80. Set this when 80 is held by another service on your machine \u2014 URLs then become http://<name>.localhost:<port>/."
|
|
1417
1417
|
)
|
|
1418
|
+
}).nullish(),
|
|
1419
|
+
// Tool-freshness settings (ADR 0018). One machine-global knob.
|
|
1420
|
+
upgrade: z2.object({
|
|
1421
|
+
staleDays: z2.number().int().min(1).optional().describe(
|
|
1422
|
+
"Days after the last `monoceros upgrade` before `apply` nudges you to refresh tooling. Default 30."
|
|
1423
|
+
)
|
|
1418
1424
|
}).nullish()
|
|
1419
1425
|
});
|
|
1420
1426
|
DEFAULT_PROXY_HOST_PORT = 80;
|
|
@@ -2098,9 +2104,84 @@ var init_service_doc = __esm({
|
|
|
2098
2104
|
}
|
|
2099
2105
|
});
|
|
2100
2106
|
|
|
2101
|
-
// src/create/
|
|
2102
|
-
import { existsSync as existsSync5,
|
|
2107
|
+
// src/create/claude-settings.ts
|
|
2108
|
+
import { existsSync as existsSync5, promises as fsp2 } from "fs";
|
|
2103
2109
|
import path8 from "path";
|
|
2110
|
+
function resolveClaudeDefaultMode(raw) {
|
|
2111
|
+
switch ((raw ?? "auto").trim()) {
|
|
2112
|
+
case "ask":
|
|
2113
|
+
case "default":
|
|
2114
|
+
return "default";
|
|
2115
|
+
case "edits":
|
|
2116
|
+
case "acceptEdits":
|
|
2117
|
+
return "acceptEdits";
|
|
2118
|
+
case "plan":
|
|
2119
|
+
return "plan";
|
|
2120
|
+
case "bypass":
|
|
2121
|
+
case "bypassPermissions":
|
|
2122
|
+
return "bypassPermissions";
|
|
2123
|
+
case "auto":
|
|
2124
|
+
case "":
|
|
2125
|
+
return "auto";
|
|
2126
|
+
default:
|
|
2127
|
+
return "auto";
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
async function writeClaudePermissionMode(targetDir, features) {
|
|
2131
|
+
if (!features) return;
|
|
2132
|
+
const entry2 = Object.entries(features).find(
|
|
2133
|
+
([ref]) => matchMonocerosFeature(ref)?.name === "claude-code"
|
|
2134
|
+
);
|
|
2135
|
+
if (!entry2) return;
|
|
2136
|
+
const raw = entry2[1]?.permissionMode;
|
|
2137
|
+
const mode = resolveClaudeDefaultMode(
|
|
2138
|
+
typeof raw === "string" ? raw : void 0
|
|
2139
|
+
);
|
|
2140
|
+
const file = path8.join(targetDir, "home", ".claude", "settings.json");
|
|
2141
|
+
await fsp2.mkdir(path8.dirname(file), { recursive: true });
|
|
2142
|
+
let config = {};
|
|
2143
|
+
if (existsSync5(file)) {
|
|
2144
|
+
try {
|
|
2145
|
+
const txt = await fsp2.readFile(file, "utf8");
|
|
2146
|
+
if (txt.trim()) {
|
|
2147
|
+
const parsed = JSON.parse(txt);
|
|
2148
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
2149
|
+
config = parsed;
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
} catch {
|
|
2153
|
+
config = {};
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
const permissions = typeof config.permissions === "object" && config.permissions !== null ? config.permissions : {};
|
|
2157
|
+
permissions.defaultMode = mode;
|
|
2158
|
+
config.permissions = permissions;
|
|
2159
|
+
const env = typeof config.env === "object" && config.env !== null ? config.env : {};
|
|
2160
|
+
if (mode === "auto") {
|
|
2161
|
+
env.CLAUDE_CODE_ENABLE_AUTO_MODE = "1";
|
|
2162
|
+
} else {
|
|
2163
|
+
delete env.CLAUDE_CODE_ENABLE_AUTO_MODE;
|
|
2164
|
+
}
|
|
2165
|
+
if (Object.keys(env).length > 0) config.env = env;
|
|
2166
|
+
else delete config.env;
|
|
2167
|
+
if (mode === "bypassPermissions") {
|
|
2168
|
+
config.skipDangerousModePermissionPrompt = true;
|
|
2169
|
+
} else {
|
|
2170
|
+
delete config.skipDangerousModePermissionPrompt;
|
|
2171
|
+
}
|
|
2172
|
+
await fsp2.writeFile(file, `${JSON.stringify(config, null, 2)}
|
|
2173
|
+
`);
|
|
2174
|
+
}
|
|
2175
|
+
var init_claude_settings = __esm({
|
|
2176
|
+
"src/create/claude-settings.ts"() {
|
|
2177
|
+
"use strict";
|
|
2178
|
+
init_ref();
|
|
2179
|
+
}
|
|
2180
|
+
});
|
|
2181
|
+
|
|
2182
|
+
// src/create/scaffold.ts
|
|
2183
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, promises as fs7 } from "fs";
|
|
2184
|
+
import path9 from "path";
|
|
2104
2185
|
function deriveRepoName(url) {
|
|
2105
2186
|
const lastSep = Math.max(url.lastIndexOf("/"), url.lastIndexOf(":"));
|
|
2106
2187
|
const tail = url.slice(lastSep + 1);
|
|
@@ -2230,7 +2311,7 @@ function featuresSourceRoot() {
|
|
|
2230
2311
|
const override2 = process.env.MONOCEROS_FEATURES_DIR_OVERRIDE?.trim();
|
|
2231
2312
|
if (override2 && override2.length > 0) return override2;
|
|
2232
2313
|
const checkout = workbenchCheckoutRoot();
|
|
2233
|
-
return checkout ?
|
|
2314
|
+
return checkout ? path9.join(checkout, "images", "features") : null;
|
|
2234
2315
|
}
|
|
2235
2316
|
function resolveFeatures(opts) {
|
|
2236
2317
|
const resolved = [];
|
|
@@ -2265,8 +2346,8 @@ function resolveFeatures(opts) {
|
|
|
2265
2346
|
if (match) {
|
|
2266
2347
|
const name = match.name;
|
|
2267
2348
|
const sourceRoot = featuresSourceRoot();
|
|
2268
|
-
const localSourceDir = sourceRoot ?
|
|
2269
|
-
if (localSourceDir &&
|
|
2349
|
+
const localSourceDir = sourceRoot ? path9.join(sourceRoot, name) : null;
|
|
2350
|
+
if (localSourceDir && existsSync6(localSourceDir)) {
|
|
2270
2351
|
const { paths: paths2, files: files2 } = readPersistentHomeEntries(localSourceDir);
|
|
2271
2352
|
resolved.push({
|
|
2272
2353
|
devcontainerKey: `./features/${name}`,
|
|
@@ -2298,7 +2379,7 @@ function resolveFeatures(opts) {
|
|
|
2298
2379
|
return resolved;
|
|
2299
2380
|
}
|
|
2300
2381
|
function readPersistentHomeEntries(localSourceDir) {
|
|
2301
|
-
const manifestPath =
|
|
2382
|
+
const manifestPath = path9.join(localSourceDir, "devcontainer-feature.json");
|
|
2302
2383
|
try {
|
|
2303
2384
|
const text = readFileSync4(manifestPath, "utf8");
|
|
2304
2385
|
const parsed = JSON.parse(text);
|
|
@@ -2312,7 +2393,7 @@ function readPersistentHomeEntries(localSourceDir) {
|
|
|
2312
2393
|
}
|
|
2313
2394
|
function readBundledPersistentHomeEntries(name) {
|
|
2314
2395
|
try {
|
|
2315
|
-
return readPersistentHomeEntries(
|
|
2396
|
+
return readPersistentHomeEntries(path9.join(bundledFeaturesDir(), name));
|
|
2316
2397
|
} catch {
|
|
2317
2398
|
return { paths: [], files: [] };
|
|
2318
2399
|
}
|
|
@@ -2718,7 +2799,7 @@ function buildPostCreateScript(opts) {
|
|
|
2718
2799
|
return lines.join("\n") + "\n";
|
|
2719
2800
|
}
|
|
2720
2801
|
async function writePostCreateScript(devcontainerDir, opts) {
|
|
2721
|
-
const dest =
|
|
2802
|
+
const dest = path9.join(devcontainerDir, "post-create.sh");
|
|
2722
2803
|
await fs7.writeFile(dest, buildPostCreateScript(opts));
|
|
2723
2804
|
await fs7.chmod(dest, 493);
|
|
2724
2805
|
}
|
|
@@ -2732,11 +2813,11 @@ async function writeIfChanged(filePath, content) {
|
|
|
2732
2813
|
}
|
|
2733
2814
|
async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
|
|
2734
2815
|
const dockerMode = scaffoldOpts.dockerMode ?? "rootful";
|
|
2735
|
-
const devcontainerDir =
|
|
2736
|
-
const monocerosDir =
|
|
2737
|
-
const projectsDir =
|
|
2738
|
-
const homeDir =
|
|
2739
|
-
const dataDir =
|
|
2816
|
+
const devcontainerDir = path9.join(targetDir, ".devcontainer");
|
|
2817
|
+
const monocerosDir = path9.join(targetDir, ".monoceros");
|
|
2818
|
+
const projectsDir = path9.join(targetDir, "projects");
|
|
2819
|
+
const homeDir = path9.join(targetDir, "home");
|
|
2820
|
+
const dataDir = path9.join(targetDir, "data");
|
|
2740
2821
|
await fs7.mkdir(devcontainerDir, { recursive: true });
|
|
2741
2822
|
await fs7.mkdir(monocerosDir, { recursive: true });
|
|
2742
2823
|
await fs7.mkdir(projectsDir, { recursive: true });
|
|
@@ -2746,59 +2827,60 @@ async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
|
|
|
2746
2827
|
for (const svc of opts.services) {
|
|
2747
2828
|
const hasDataVolume = svc.volumes.some((v) => v.split(":")[0] === "data");
|
|
2748
2829
|
if (hasDataVolume) {
|
|
2749
|
-
await fs7.mkdir(
|
|
2830
|
+
await fs7.mkdir(path9.join(dataDir, svc.name), { recursive: true });
|
|
2750
2831
|
}
|
|
2751
2832
|
}
|
|
2752
2833
|
}
|
|
2753
|
-
const containerGitignore =
|
|
2834
|
+
const containerGitignore = path9.join(targetDir, ".gitignore");
|
|
2754
2835
|
await fs7.writeFile(
|
|
2755
2836
|
containerGitignore,
|
|
2756
2837
|
"/home/\n/.monoceros/\n/data/\n/AGENTS.md\n/CLAUDE.md\n"
|
|
2757
2838
|
);
|
|
2758
|
-
const gitkeep =
|
|
2759
|
-
if (!
|
|
2839
|
+
const gitkeep = path9.join(projectsDir, ".gitkeep");
|
|
2840
|
+
if (!existsSync6(gitkeep)) {
|
|
2760
2841
|
await fs7.writeFile(gitkeep, "");
|
|
2761
2842
|
}
|
|
2762
2843
|
await fs7.writeFile(
|
|
2763
|
-
|
|
2844
|
+
path9.join(monocerosDir, ".gitignore"),
|
|
2764
2845
|
"git-credentials*\ngitconfig\n"
|
|
2765
2846
|
);
|
|
2766
2847
|
const devcontainerJson = buildDevcontainerJson(opts, dockerMode);
|
|
2767
2848
|
await writeIfChanged(
|
|
2768
|
-
|
|
2849
|
+
path9.join(devcontainerDir, "devcontainer.json"),
|
|
2769
2850
|
JSON.stringify(devcontainerJson, null, 2) + "\n"
|
|
2770
2851
|
);
|
|
2771
|
-
const featuresDir =
|
|
2772
|
-
if (
|
|
2852
|
+
const featuresDir = path9.join(devcontainerDir, "features");
|
|
2853
|
+
if (existsSync6(featuresDir)) {
|
|
2773
2854
|
await fs7.rm(featuresDir, { recursive: true, force: true });
|
|
2774
2855
|
}
|
|
2775
2856
|
const resolvedFeatures = resolveFeatures(opts);
|
|
2776
2857
|
for (const f of resolvedFeatures) {
|
|
2777
2858
|
if (!f.localSourceDir || !f.localName) continue;
|
|
2778
|
-
const dest =
|
|
2859
|
+
const dest = path9.join(featuresDir, f.localName);
|
|
2779
2860
|
await fs7.mkdir(dest, { recursive: true });
|
|
2780
2861
|
await fs7.cp(f.localSourceDir, dest, { recursive: true });
|
|
2781
2862
|
}
|
|
2782
2863
|
for (const f of resolvedFeatures) {
|
|
2783
2864
|
for (const sub of f.persistentHomePaths) {
|
|
2784
|
-
await fs7.mkdir(
|
|
2865
|
+
await fs7.mkdir(path9.join(homeDir, sub), { recursive: true });
|
|
2785
2866
|
}
|
|
2786
2867
|
for (const entry2 of f.persistentHomeFiles) {
|
|
2787
|
-
const filePath =
|
|
2788
|
-
await fs7.mkdir(
|
|
2789
|
-
if (!
|
|
2868
|
+
const filePath = path9.join(homeDir, entry2.path);
|
|
2869
|
+
await fs7.mkdir(path9.dirname(filePath), { recursive: true });
|
|
2870
|
+
if (!existsSync6(filePath)) {
|
|
2790
2871
|
await fs7.writeFile(filePath, entry2.initialContent);
|
|
2791
2872
|
}
|
|
2792
2873
|
}
|
|
2793
2874
|
}
|
|
2875
|
+
await writeClaudePermissionMode(targetDir, opts.features);
|
|
2794
2876
|
await writePostCreateScript(devcontainerDir, opts);
|
|
2795
|
-
const composePath =
|
|
2877
|
+
const composePath = path9.join(devcontainerDir, "compose.yaml");
|
|
2796
2878
|
if (needsCompose(opts)) {
|
|
2797
2879
|
await writeIfChanged(composePath, buildComposeYaml(opts, dockerMode));
|
|
2798
|
-
} else if (
|
|
2880
|
+
} else if (existsSync6(composePath)) {
|
|
2799
2881
|
await fs7.rm(composePath);
|
|
2800
2882
|
}
|
|
2801
|
-
const workspacePath =
|
|
2883
|
+
const workspacePath = path9.join(targetDir, `${opts.name}.code-workspace`);
|
|
2802
2884
|
let existingWorkspace;
|
|
2803
2885
|
try {
|
|
2804
2886
|
const raw = await fs7.readFile(workspacePath, "utf8");
|
|
@@ -2809,8 +2891,8 @@ async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
|
|
|
2809
2891
|
const generated = buildCodeWorkspaceJson(opts);
|
|
2810
2892
|
const merged = mergeCodeWorkspace(existingWorkspace, generated);
|
|
2811
2893
|
await fs7.writeFile(workspacePath, JSON.stringify(merged, null, 2) + "\n");
|
|
2812
|
-
const vscodeDir =
|
|
2813
|
-
const settingsPath =
|
|
2894
|
+
const vscodeDir = path9.join(targetDir, ".vscode");
|
|
2895
|
+
const settingsPath = path9.join(vscodeDir, "settings.json");
|
|
2814
2896
|
let existingSettings;
|
|
2815
2897
|
try {
|
|
2816
2898
|
existingSettings = JSON.parse(await fs7.readFile(settingsPath, "utf8"));
|
|
@@ -2829,6 +2911,7 @@ var init_scaffold = __esm({
|
|
|
2829
2911
|
"use strict";
|
|
2830
2912
|
init_paths();
|
|
2831
2913
|
init_ref();
|
|
2914
|
+
init_claude_settings();
|
|
2832
2915
|
init_catalog();
|
|
2833
2916
|
APT_PACKAGE_NAME_RE2 = /^[a-z0-9][a-z0-9.+-]*$/;
|
|
2834
2917
|
FEATURE_REF_RE2 = /^[a-z0-9.-]+(\/[a-z0-9._-]+)+:[a-z0-9._-]+$/;
|
|
@@ -3259,8 +3342,8 @@ function removeRepoFromDoc(doc, urlOrPath) {
|
|
|
3259
3342
|
if (!isMap2(item)) return false;
|
|
3260
3343
|
const url = item.get("url");
|
|
3261
3344
|
if (url === urlOrPath) return true;
|
|
3262
|
-
const
|
|
3263
|
-
const effectivePath = typeof
|
|
3345
|
+
const path27 = item.get("path");
|
|
3346
|
+
const effectivePath = typeof path27 === "string" ? path27 : typeof url === "string" ? deriveRepoName(url) : void 0;
|
|
3264
3347
|
return effectivePath === urlOrPath;
|
|
3265
3348
|
});
|
|
3266
3349
|
if (idx < 0) return false;
|
|
@@ -3297,7 +3380,7 @@ var init_yml = __esm({
|
|
|
3297
3380
|
import { promises as fs8 } from "fs";
|
|
3298
3381
|
import { consola } from "consola";
|
|
3299
3382
|
import { createPatch } from "diff";
|
|
3300
|
-
import
|
|
3383
|
+
import path10 from "path";
|
|
3301
3384
|
function runAddLanguage(input) {
|
|
3302
3385
|
if (!BUILTIN_LANGUAGES.has(input.language) && !LANGUAGE_CATALOG[input.language]) {
|
|
3303
3386
|
throw new Error(
|
|
@@ -3372,7 +3455,7 @@ async function runAddRepo(input) {
|
|
|
3372
3455
|
"Missing repo URL. Usage: monoceros add-repo <containername> <url>."
|
|
3373
3456
|
);
|
|
3374
3457
|
}
|
|
3375
|
-
const
|
|
3458
|
+
const path27 = (input.path ?? deriveRepoName(url)).trim();
|
|
3376
3459
|
const hasName = typeof input.gitName === "string" && input.gitName.trim().length > 0;
|
|
3377
3460
|
const hasEmail = typeof input.gitEmail === "string" && input.gitEmail.trim().length > 0;
|
|
3378
3461
|
if (hasName !== hasEmail) {
|
|
@@ -3409,7 +3492,7 @@ async function runAddRepo(input) {
|
|
|
3409
3492
|
const providerToWrite = !canonical && explicitProvider ? explicitProvider : void 0;
|
|
3410
3493
|
const entry2 = {
|
|
3411
3494
|
url,
|
|
3412
|
-
path:
|
|
3495
|
+
path: path27,
|
|
3413
3496
|
...hasName && hasEmail ? {
|
|
3414
3497
|
gitUser: {
|
|
3415
3498
|
name: input.gitName.trim(),
|
|
@@ -3541,7 +3624,7 @@ async function tryCloneInRunningContainer(input, entry2) {
|
|
|
3541
3624
|
logger.info(
|
|
3542
3625
|
`Cloned ${entry2.url} into /workspaces/${containerName2}/${targetRel} inside the running container.`
|
|
3543
3626
|
);
|
|
3544
|
-
void
|
|
3627
|
+
void path10;
|
|
3545
3628
|
}
|
|
3546
3629
|
function shquote(value) {
|
|
3547
3630
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -4323,7 +4406,7 @@ var init_add_service = __esm({
|
|
|
4323
4406
|
|
|
4324
4407
|
// src/config/state.ts
|
|
4325
4408
|
import { promises as fs9 } from "fs";
|
|
4326
|
-
import
|
|
4409
|
+
import path11 from "path";
|
|
4327
4410
|
function buildStateFile(opts) {
|
|
4328
4411
|
return {
|
|
4329
4412
|
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
@@ -4334,7 +4417,7 @@ function buildStateFile(opts) {
|
|
|
4334
4417
|
};
|
|
4335
4418
|
}
|
|
4336
4419
|
function stateFilePath(targetDir) {
|
|
4337
|
-
return
|
|
4420
|
+
return path11.join(targetDir, ".monoceros", "state.json");
|
|
4338
4421
|
}
|
|
4339
4422
|
async function readStateFile(targetDir) {
|
|
4340
4423
|
try {
|
|
@@ -4345,7 +4428,7 @@ async function readStateFile(targetDir) {
|
|
|
4345
4428
|
}
|
|
4346
4429
|
}
|
|
4347
4430
|
async function writeStateFile(targetDir, state) {
|
|
4348
|
-
const monocerosDir =
|
|
4431
|
+
const monocerosDir = path11.join(targetDir, ".monoceros");
|
|
4349
4432
|
await fs9.mkdir(monocerosDir, { recursive: true });
|
|
4350
4433
|
await fs9.writeFile(
|
|
4351
4434
|
stateFilePath(targetDir),
|
|
@@ -4436,7 +4519,7 @@ var init_transform = __esm({
|
|
|
4436
4519
|
|
|
4437
4520
|
// src/apply/apply-log.ts
|
|
4438
4521
|
import { createWriteStream, mkdirSync } from "fs";
|
|
4439
|
-
import
|
|
4522
|
+
import path12 from "path";
|
|
4440
4523
|
import { Writable } from "stream";
|
|
4441
4524
|
function safeIsoStamp(d) {
|
|
4442
4525
|
return d.toISOString().replace(/[:.]/g, "-");
|
|
@@ -4446,7 +4529,7 @@ function createApplyLog(opts) {
|
|
|
4446
4529
|
const dir = containerLogsDir(opts.name, opts.home);
|
|
4447
4530
|
mkdirSync(dir, { recursive: true });
|
|
4448
4531
|
const file = `apply-${opts.name}-${safeIsoStamp(now)}.log`;
|
|
4449
|
-
const fullPath =
|
|
4532
|
+
const fullPath = path12.join(dir, file);
|
|
4450
4533
|
const stream = createWriteStream(fullPath, { flags: "w" });
|
|
4451
4534
|
const header = [
|
|
4452
4535
|
`# monoceros apply log`,
|
|
@@ -5290,7 +5373,7 @@ var init_markers = __esm({
|
|
|
5290
5373
|
|
|
5291
5374
|
// src/briefing/index.ts
|
|
5292
5375
|
import { promises as fs10 } from "fs";
|
|
5293
|
-
import
|
|
5376
|
+
import path13 from "path";
|
|
5294
5377
|
async function writeBriefing(input) {
|
|
5295
5378
|
const subCommands = input.subCommands ?? await loadSubCommandsDynamic();
|
|
5296
5379
|
const manifestLoader = input.manifestLoader ?? ((ref) => loadFeatureManifestSummary(ref));
|
|
@@ -5303,12 +5386,12 @@ async function writeBriefing(input) {
|
|
|
5303
5386
|
);
|
|
5304
5387
|
const claudeBody = generateClaudeMd();
|
|
5305
5388
|
const commandsBody = generateCommandsMd(subCommands);
|
|
5306
|
-
await writeMarkerAware(
|
|
5307
|
-
await writeMarkerAware(
|
|
5308
|
-
const monocerosDir =
|
|
5389
|
+
await writeMarkerAware(path13.join(input.targetDir, "AGENTS.md"), agentsBody);
|
|
5390
|
+
await writeMarkerAware(path13.join(input.targetDir, "CLAUDE.md"), claudeBody);
|
|
5391
|
+
const monocerosDir = path13.join(input.targetDir, ".monoceros");
|
|
5309
5392
|
await fs10.mkdir(monocerosDir, { recursive: true });
|
|
5310
5393
|
await fs10.writeFile(
|
|
5311
|
-
|
|
5394
|
+
path13.join(monocerosDir, "commands.md"),
|
|
5312
5395
|
commandsBody,
|
|
5313
5396
|
"utf8"
|
|
5314
5397
|
);
|
|
@@ -5467,7 +5550,7 @@ var init_runtime_pull_hint = __esm({
|
|
|
5467
5550
|
import { spawn as spawn4 } from "child_process";
|
|
5468
5551
|
import { readFileSync as readFileSync5 } from "fs";
|
|
5469
5552
|
import { createRequire } from "module";
|
|
5470
|
-
import
|
|
5553
|
+
import path14 from "path";
|
|
5471
5554
|
function devcontainerCliPath() {
|
|
5472
5555
|
if (cachedBinaryPath) return cachedBinaryPath;
|
|
5473
5556
|
const pkgJsonPath = require_.resolve("@devcontainers/cli/package.json");
|
|
@@ -5476,7 +5559,7 @@ function devcontainerCliPath() {
|
|
|
5476
5559
|
if (!binEntry) {
|
|
5477
5560
|
throw new Error("Could not resolve @devcontainers/cli bin entry.");
|
|
5478
5561
|
}
|
|
5479
|
-
cachedBinaryPath =
|
|
5562
|
+
cachedBinaryPath = path14.resolve(path14.dirname(pkgJsonPath), binEntry);
|
|
5480
5563
|
return cachedBinaryPath;
|
|
5481
5564
|
}
|
|
5482
5565
|
var require_, cachedBinaryPath, spawnDevcontainer;
|
|
@@ -5550,8 +5633,9 @@ var init_cli = __esm({
|
|
|
5550
5633
|
|
|
5551
5634
|
// src/devcontainer/compose.ts
|
|
5552
5635
|
import { spawn as spawn5 } from "child_process";
|
|
5553
|
-
import { existsSync as
|
|
5554
|
-
import
|
|
5636
|
+
import { existsSync as existsSync7 } from "fs";
|
|
5637
|
+
import path15 from "path";
|
|
5638
|
+
import { Writable as Writable3 } from "stream";
|
|
5555
5639
|
import { consola as consola9 } from "consola";
|
|
5556
5640
|
async function findContainerIds(filters, exec = spawnDocker) {
|
|
5557
5641
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -5591,16 +5675,16 @@ async function cleanupDockerObjects(opts) {
|
|
|
5591
5675
|
return { exitCode: rmExit, removedIds: ids };
|
|
5592
5676
|
}
|
|
5593
5677
|
function composeProjectName(root) {
|
|
5594
|
-
return `${
|
|
5678
|
+
return `${path15.basename(root)}_devcontainer`;
|
|
5595
5679
|
}
|
|
5596
5680
|
function resolveCompose(root) {
|
|
5597
|
-
if (!
|
|
5681
|
+
if (!existsSync7(path15.join(root, ".devcontainer"))) {
|
|
5598
5682
|
throw new Error(
|
|
5599
5683
|
`No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
|
|
5600
5684
|
);
|
|
5601
5685
|
}
|
|
5602
|
-
const composeFile =
|
|
5603
|
-
if (!
|
|
5686
|
+
const composeFile = path15.join(root, ".devcontainer", "compose.yaml");
|
|
5687
|
+
if (!existsSync7(composeFile)) {
|
|
5604
5688
|
throw new Error(
|
|
5605
5689
|
`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.`
|
|
5606
5690
|
);
|
|
@@ -5619,7 +5703,13 @@ async function runStart(opts) {
|
|
|
5619
5703
|
const spawnFn = opts.spawn ?? spawnDevcontainer;
|
|
5620
5704
|
logger.info(`Bringing devcontainer up at ${opts.root}\u2026`);
|
|
5621
5705
|
return spawnFn(
|
|
5622
|
-
[
|
|
5706
|
+
[
|
|
5707
|
+
"up",
|
|
5708
|
+
"--workspace-folder",
|
|
5709
|
+
opts.root,
|
|
5710
|
+
"--mount-workspace-git-root=false",
|
|
5711
|
+
...opts.noCache ? ["--build-no-cache"] : []
|
|
5712
|
+
],
|
|
5623
5713
|
opts.root,
|
|
5624
5714
|
buildSpawnOptions(opts)
|
|
5625
5715
|
);
|
|
@@ -5631,14 +5721,62 @@ function buildSpawnOptions(opts) {
|
|
|
5631
5721
|
if (opts.silent) out.silent = true;
|
|
5632
5722
|
return Object.keys(out).length > 0 ? out : void 0;
|
|
5633
5723
|
}
|
|
5724
|
+
async function runUpWithBindRetry(attempt, baseSink, logger, opts = {}) {
|
|
5725
|
+
const delayMs = opts.delayMs ?? BIND_RETRY_DELAY_MS;
|
|
5726
|
+
let code = 0;
|
|
5727
|
+
for (let i = 1; i <= BIND_RETRY_ATTEMPTS; i += 1) {
|
|
5728
|
+
let captured = "";
|
|
5729
|
+
const sink = new Writable3({
|
|
5730
|
+
write(chunk, _enc, cb) {
|
|
5731
|
+
captured += chunk.toString();
|
|
5732
|
+
if (baseSink) baseSink.write(chunk);
|
|
5733
|
+
cb();
|
|
5734
|
+
}
|
|
5735
|
+
});
|
|
5736
|
+
code = await attempt(sink);
|
|
5737
|
+
if (code === 0) return 0;
|
|
5738
|
+
if (i < BIND_RETRY_ATTEMPTS && BIND_SOURCE_MISSING_RE.test(captured)) {
|
|
5739
|
+
logger.info(
|
|
5740
|
+
`Bind source not visible yet (Docker Desktop file sync); nudging + retrying\u2026 (${i}/${BIND_RETRY_ATTEMPTS - 1})`
|
|
5741
|
+
);
|
|
5742
|
+
if (opts.onBindRetry) {
|
|
5743
|
+
try {
|
|
5744
|
+
await opts.onBindRetry();
|
|
5745
|
+
} catch {
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5748
|
+
if (delayMs > 0) await new Promise((r) => setTimeout(r, delayMs));
|
|
5749
|
+
continue;
|
|
5750
|
+
}
|
|
5751
|
+
return code;
|
|
5752
|
+
}
|
|
5753
|
+
return code;
|
|
5754
|
+
}
|
|
5634
5755
|
async function runContainerCycle(root, opts) {
|
|
5635
5756
|
const { hasCompose, logger } = opts;
|
|
5757
|
+
const exec = opts.dockerExec ?? spawnDocker;
|
|
5758
|
+
const onBindRetry = opts.prewarmImage ? async () => {
|
|
5759
|
+
await exec([
|
|
5760
|
+
"run",
|
|
5761
|
+
"--rm",
|
|
5762
|
+
"--entrypoint",
|
|
5763
|
+
"sh",
|
|
5764
|
+
"--mount",
|
|
5765
|
+
`source=${root},target=/w,type=bind`,
|
|
5766
|
+
opts.prewarmImage,
|
|
5767
|
+
"-lc",
|
|
5768
|
+
"ls -laR /w/home >/dev/null 2>&1 || true"
|
|
5769
|
+
]);
|
|
5770
|
+
} : void 0;
|
|
5771
|
+
const bindRetry = {
|
|
5772
|
+
...opts.bindRetryDelayMs !== void 0 ? { delayMs: opts.bindRetryDelayMs } : {},
|
|
5773
|
+
...onBindRetry ? { onBindRetry } : {}
|
|
5774
|
+
};
|
|
5636
5775
|
if (hasCompose) {
|
|
5637
5776
|
const projectName = composeProjectName(root);
|
|
5638
5777
|
logger.info(
|
|
5639
5778
|
`Force-removing existing ${projectName} containers (volumes preserved)\u2026`
|
|
5640
5779
|
);
|
|
5641
|
-
const exec = opts.dockerExec ?? spawnDocker;
|
|
5642
5780
|
const filters = [
|
|
5643
5781
|
`label=com.docker.compose.project=${projectName}`,
|
|
5644
5782
|
`name=^${projectName}-`
|
|
@@ -5663,27 +5801,43 @@ and retry \`monoceros apply\`.`
|
|
|
5663
5801
|
);
|
|
5664
5802
|
return 1;
|
|
5665
5803
|
}
|
|
5666
|
-
return
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5804
|
+
return runUpWithBindRetry(
|
|
5805
|
+
(logSink) => runStart({
|
|
5806
|
+
root,
|
|
5807
|
+
...opts.devcontainerSpawn ? { spawn: opts.devcontainerSpawn } : {},
|
|
5808
|
+
logSink,
|
|
5809
|
+
...opts.progressSink ? { progressSink: opts.progressSink } : {},
|
|
5810
|
+
...opts.silent ? { silent: true } : {},
|
|
5811
|
+
...opts.noCache ? { noCache: true } : {},
|
|
5812
|
+
logger
|
|
5813
|
+
}),
|
|
5814
|
+
opts.logSink,
|
|
5815
|
+
logger,
|
|
5816
|
+
bindRetry
|
|
5817
|
+
);
|
|
5674
5818
|
}
|
|
5675
5819
|
logger.info(`Recreating image-mode devcontainer at ${root}\u2026`);
|
|
5676
5820
|
const spawnFn = opts.devcontainerSpawn ?? spawnDevcontainer;
|
|
5677
|
-
return
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5821
|
+
return runUpWithBindRetry(
|
|
5822
|
+
(logSink) => spawnFn(
|
|
5823
|
+
[
|
|
5824
|
+
"up",
|
|
5825
|
+
"--workspace-folder",
|
|
5826
|
+
root,
|
|
5827
|
+
"--mount-workspace-git-root=false",
|
|
5828
|
+
"--remove-existing-container",
|
|
5829
|
+
...opts.noCache ? ["--build-no-cache"] : []
|
|
5830
|
+
],
|
|
5681
5831
|
root,
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5832
|
+
{
|
|
5833
|
+
logSink,
|
|
5834
|
+
...opts.progressSink ? { progressSink: opts.progressSink } : {},
|
|
5835
|
+
...opts.silent ? { silent: true } : {}
|
|
5836
|
+
}
|
|
5837
|
+
),
|
|
5838
|
+
opts.logSink,
|
|
5839
|
+
logger,
|
|
5840
|
+
bindRetry
|
|
5687
5841
|
);
|
|
5688
5842
|
}
|
|
5689
5843
|
function runStop(opts) {
|
|
@@ -5709,7 +5863,7 @@ function runLogs(opts) {
|
|
|
5709
5863
|
opts
|
|
5710
5864
|
);
|
|
5711
5865
|
}
|
|
5712
|
-
var spawnDockerCompose, spawnDocker;
|
|
5866
|
+
var spawnDockerCompose, spawnDocker, BIND_SOURCE_MISSING_RE, BIND_RETRY_ATTEMPTS, BIND_RETRY_DELAY_MS;
|
|
5713
5867
|
var init_compose = __esm({
|
|
5714
5868
|
"src/devcontainer/compose.ts"() {
|
|
5715
5869
|
"use strict";
|
|
@@ -5749,6 +5903,102 @@ var init_compose = __esm({
|
|
|
5749
5903
|
);
|
|
5750
5904
|
});
|
|
5751
5905
|
};
|
|
5906
|
+
BIND_SOURCE_MISSING_RE = /bind source path does not exist/i;
|
|
5907
|
+
BIND_RETRY_ATTEMPTS = 3;
|
|
5908
|
+
BIND_RETRY_DELAY_MS = 500;
|
|
5909
|
+
}
|
|
5910
|
+
});
|
|
5911
|
+
|
|
5912
|
+
// src/devcontainer/images.ts
|
|
5913
|
+
async function resolveContainerImageId(root, exec = spawnDocker) {
|
|
5914
|
+
const ids = await findContainerIds(
|
|
5915
|
+
[`label=devcontainer.local_folder=${root}`],
|
|
5916
|
+
exec
|
|
5917
|
+
);
|
|
5918
|
+
const containerId = ids[0];
|
|
5919
|
+
if (!containerId) return null;
|
|
5920
|
+
const res = await exec(["inspect", "--format", "{{.Image}}", containerId]);
|
|
5921
|
+
if (res.exitCode !== 0) return null;
|
|
5922
|
+
const imageId = res.stdout.trim();
|
|
5923
|
+
return imageId || null;
|
|
5924
|
+
}
|
|
5925
|
+
async function removeImage(imageId, exec = spawnDocker) {
|
|
5926
|
+
try {
|
|
5927
|
+
const res = await exec(["rmi", imageId]);
|
|
5928
|
+
if (res.exitCode === 0) return "removed";
|
|
5929
|
+
const err = (res.stderr ?? "").toLowerCase();
|
|
5930
|
+
if (err.includes("no such image")) return "absent";
|
|
5931
|
+
if (err.includes("is being used") || err.includes("conflict") || err.includes("in use")) {
|
|
5932
|
+
return "in-use";
|
|
5933
|
+
}
|
|
5934
|
+
return "error";
|
|
5935
|
+
} catch {
|
|
5936
|
+
return "error";
|
|
5937
|
+
}
|
|
5938
|
+
}
|
|
5939
|
+
var init_images = __esm({
|
|
5940
|
+
"src/devcontainer/images.ts"() {
|
|
5941
|
+
"use strict";
|
|
5942
|
+
init_proxy();
|
|
5943
|
+
init_compose();
|
|
5944
|
+
}
|
|
5945
|
+
});
|
|
5946
|
+
|
|
5947
|
+
// src/config/machine-state.ts
|
|
5948
|
+
import { promises as fsp3 } from "fs";
|
|
5949
|
+
import path16 from "path";
|
|
5950
|
+
function machineStatePath(home = monocerosHome()) {
|
|
5951
|
+
return path16.join(home, ".machine-state.json");
|
|
5952
|
+
}
|
|
5953
|
+
async function readMachineState(home = monocerosHome()) {
|
|
5954
|
+
try {
|
|
5955
|
+
const raw = await fsp3.readFile(machineStatePath(home), "utf8");
|
|
5956
|
+
const parsed = JSON.parse(raw);
|
|
5957
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
5958
|
+
return parsed;
|
|
5959
|
+
}
|
|
5960
|
+
} catch {
|
|
5961
|
+
}
|
|
5962
|
+
return {};
|
|
5963
|
+
}
|
|
5964
|
+
async function writeMachineState(state, home = monocerosHome()) {
|
|
5965
|
+
await fsp3.writeFile(
|
|
5966
|
+
machineStatePath(home),
|
|
5967
|
+
`${JSON.stringify(state, null, 2)}
|
|
5968
|
+
`
|
|
5969
|
+
);
|
|
5970
|
+
}
|
|
5971
|
+
async function recordBuiltImage(record, home = monocerosHome()) {
|
|
5972
|
+
const state = await readMachineState(home);
|
|
5973
|
+
const rest = (state.builtImages ?? []).filter(
|
|
5974
|
+
(r) => r.imageId !== record.imageId
|
|
5975
|
+
);
|
|
5976
|
+
state.builtImages = [...rest, record];
|
|
5977
|
+
await writeMachineState(state, home);
|
|
5978
|
+
}
|
|
5979
|
+
async function markUpgraded(nowIso, home = monocerosHome()) {
|
|
5980
|
+
const state = await readMachineState(home);
|
|
5981
|
+
state.lastUpgradeAt = nowIso;
|
|
5982
|
+
await writeMachineState(state, home);
|
|
5983
|
+
}
|
|
5984
|
+
function daysBetween(fromIso, now) {
|
|
5985
|
+
const from = new Date(fromIso).getTime();
|
|
5986
|
+
if (!Number.isFinite(from)) return 0;
|
|
5987
|
+
const ms = now.getTime() - from;
|
|
5988
|
+
return Math.max(0, Math.floor(ms / 864e5));
|
|
5989
|
+
}
|
|
5990
|
+
function upgradeNudge(state, now, thresholdDays = DEFAULT_UPGRADE_STALE_DAYS) {
|
|
5991
|
+
if (!state.lastUpgradeAt) return null;
|
|
5992
|
+
const days = daysBetween(state.lastUpgradeAt, now);
|
|
5993
|
+
if (days < thresholdDays) return null;
|
|
5994
|
+
return `Tools last refreshed ${days} days ago. Run \`monoceros upgrade\` to update them.`;
|
|
5995
|
+
}
|
|
5996
|
+
var DEFAULT_UPGRADE_STALE_DAYS;
|
|
5997
|
+
var init_machine_state = __esm({
|
|
5998
|
+
"src/config/machine-state.ts"() {
|
|
5999
|
+
"use strict";
|
|
6000
|
+
init_paths();
|
|
6001
|
+
DEFAULT_UPGRADE_STALE_DAYS = 30;
|
|
5752
6002
|
}
|
|
5753
6003
|
});
|
|
5754
6004
|
|
|
@@ -5824,7 +6074,7 @@ var init_docker_mode = __esm({
|
|
|
5824
6074
|
// src/devcontainer/identity.ts
|
|
5825
6075
|
import { spawn as spawn7 } from "child_process";
|
|
5826
6076
|
import { promises as fs11 } from "fs";
|
|
5827
|
-
import
|
|
6077
|
+
import path17 from "path";
|
|
5828
6078
|
import { consola as consola10 } from "consola";
|
|
5829
6079
|
async function resolveIdentityWithPrompt(options = {}) {
|
|
5830
6080
|
const spawnFn = options.spawn ?? realGitConfigGet;
|
|
@@ -5880,8 +6130,8 @@ async function resolveIdentityWithPrompt(options = {}) {
|
|
|
5880
6130
|
};
|
|
5881
6131
|
}
|
|
5882
6132
|
async function collectGitIdentity(devContainerRoot, options = {}) {
|
|
5883
|
-
const gitconfigDir =
|
|
5884
|
-
const gitconfigPath =
|
|
6133
|
+
const gitconfigDir = path17.join(devContainerRoot, ".monoceros");
|
|
6134
|
+
const gitconfigPath = path17.join(gitconfigDir, "gitconfig");
|
|
5885
6135
|
const logger = options.logger ?? { info: () => {
|
|
5886
6136
|
}, warn: () => {
|
|
5887
6137
|
} };
|
|
@@ -6015,7 +6265,7 @@ var init_identity = __esm({
|
|
|
6015
6265
|
});
|
|
6016
6266
|
|
|
6017
6267
|
// src/apply/index.ts
|
|
6018
|
-
import { existsSync as
|
|
6268
|
+
import { existsSync as existsSync8, promises as fs12 } from "fs";
|
|
6019
6269
|
import { consola as consola11 } from "consola";
|
|
6020
6270
|
async function runApply(opts) {
|
|
6021
6271
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -6037,7 +6287,7 @@ ${sectionLine(label)}
|
|
|
6037
6287
|
);
|
|
6038
6288
|
}
|
|
6039
6289
|
const ymlPath = containerConfigPath(opts.name, home);
|
|
6040
|
-
if (!
|
|
6290
|
+
if (!existsSync8(ymlPath)) {
|
|
6041
6291
|
throw new Error(
|
|
6042
6292
|
`No such config: ${ymlPath}. Run \`monoceros init <template> ${opts.name}\` first.`
|
|
6043
6293
|
);
|
|
@@ -6236,6 +6486,8 @@ Fix the value in the env file (or the yml).`
|
|
|
6236
6486
|
}
|
|
6237
6487
|
exitCode = await runContainerCycle(targetDir, {
|
|
6238
6488
|
hasCompose: needsCompose(createOpts),
|
|
6489
|
+
...opts.rebuild ? { noCache: true } : {},
|
|
6490
|
+
prewarmImage: resolveRuntimeImage(createOpts.runtimeVersion),
|
|
6239
6491
|
...opts.dockerExec !== void 0 ? { dockerExec: opts.dockerExec } : {},
|
|
6240
6492
|
...opts.devcontainerSpawn !== void 0 ? { devcontainerSpawn: opts.devcontainerSpawn } : {},
|
|
6241
6493
|
logSink: applyLog.sink,
|
|
@@ -6267,6 +6519,30 @@ ${formatted}
|
|
|
6267
6519
|
`);
|
|
6268
6520
|
applyLog.stream.write(`
|
|
6269
6521
|
${stripAnsi(formatted)}
|
|
6522
|
+
`);
|
|
6523
|
+
}
|
|
6524
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
6525
|
+
try {
|
|
6526
|
+
const imageId = await resolveContainerImageId(
|
|
6527
|
+
targetDir,
|
|
6528
|
+
opts.dockerExec ?? defaultDockerExec
|
|
6529
|
+
);
|
|
6530
|
+
if (imageId) {
|
|
6531
|
+
await recordBuiltImage(
|
|
6532
|
+
{ imageId, container: opts.name, builtAt: now.toISOString() },
|
|
6533
|
+
home
|
|
6534
|
+
);
|
|
6535
|
+
}
|
|
6536
|
+
} catch {
|
|
6537
|
+
}
|
|
6538
|
+
const nudge = upgradeNudge(
|
|
6539
|
+
await readMachineState(home),
|
|
6540
|
+
now,
|
|
6541
|
+
globalConfig?.upgrade?.staleDays ?? DEFAULT_UPGRADE_STALE_DAYS
|
|
6542
|
+
);
|
|
6543
|
+
if (nudge) {
|
|
6544
|
+
progressOut.write(`
|
|
6545
|
+
${dim(nudge)}
|
|
6270
6546
|
`);
|
|
6271
6547
|
}
|
|
6272
6548
|
}
|
|
@@ -6284,7 +6560,7 @@ ${stripAnsi(formatted)}
|
|
|
6284
6560
|
return { targetDir, configPath: ymlPath, containerExitCode: exitCode };
|
|
6285
6561
|
}
|
|
6286
6562
|
async function assertSafeTargetDir(targetDir, expectedOrigin) {
|
|
6287
|
-
if (!
|
|
6563
|
+
if (!existsSync8(targetDir)) return;
|
|
6288
6564
|
const entries = await fs12.readdir(targetDir);
|
|
6289
6565
|
if (entries.length === 0) return;
|
|
6290
6566
|
const state = await readStateFile(targetDir);
|
|
@@ -6395,6 +6671,8 @@ var init_apply = __esm({
|
|
|
6395
6671
|
init_briefing();
|
|
6396
6672
|
init_components();
|
|
6397
6673
|
init_compose();
|
|
6674
|
+
init_images();
|
|
6675
|
+
init_machine_state();
|
|
6398
6676
|
init_credentials();
|
|
6399
6677
|
init_docker_mode();
|
|
6400
6678
|
init_cli();
|
|
@@ -6412,7 +6690,7 @@ var CLI_VERSION;
|
|
|
6412
6690
|
var init_version = __esm({
|
|
6413
6691
|
"src/version.ts"() {
|
|
6414
6692
|
"use strict";
|
|
6415
|
-
CLI_VERSION = true ? "1.
|
|
6693
|
+
CLI_VERSION = true ? "1.21.0" : "dev";
|
|
6416
6694
|
}
|
|
6417
6695
|
});
|
|
6418
6696
|
|
|
@@ -6602,8 +6880,8 @@ var init_completion = __esm({
|
|
|
6602
6880
|
});
|
|
6603
6881
|
|
|
6604
6882
|
// src/completion/resolve.ts
|
|
6605
|
-
import { existsSync as
|
|
6606
|
-
import
|
|
6883
|
+
import { existsSync as existsSync9, promises as fs13 } from "fs";
|
|
6884
|
+
import path18 from "path";
|
|
6607
6885
|
async function resolveCompletions(line, point, opts = {}) {
|
|
6608
6886
|
const { prev, current } = parseCompletionLine(line, point);
|
|
6609
6887
|
const ctx = { prev, current, opts };
|
|
@@ -6752,8 +7030,8 @@ function filterPrefix(values, fragment) {
|
|
|
6752
7030
|
}
|
|
6753
7031
|
async function listContainerNames(ctx) {
|
|
6754
7032
|
const home = ctx.opts.monocerosHome ?? monocerosHome();
|
|
6755
|
-
const dir =
|
|
6756
|
-
if (!
|
|
7033
|
+
const dir = path18.join(home, "container-configs");
|
|
7034
|
+
if (!existsSync9(dir)) return [];
|
|
6757
7035
|
const entries = await fs13.readdir(dir);
|
|
6758
7036
|
return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length)).sort();
|
|
6759
7037
|
}
|
|
@@ -7400,8 +7678,8 @@ var init_generator = __esm({
|
|
|
7400
7678
|
});
|
|
7401
7679
|
|
|
7402
7680
|
// src/init/index.ts
|
|
7403
|
-
import { existsSync as
|
|
7404
|
-
import
|
|
7681
|
+
import { existsSync as existsSync10, promises as fs14 } from "fs";
|
|
7682
|
+
import path19 from "path";
|
|
7405
7683
|
import { consola as consola13 } from "consola";
|
|
7406
7684
|
async function runInit(opts) {
|
|
7407
7685
|
const workbench = opts.workbenchRoot ?? workbenchRoot();
|
|
@@ -7416,7 +7694,7 @@ async function runInit(opts) {
|
|
|
7416
7694
|
);
|
|
7417
7695
|
}
|
|
7418
7696
|
const dest = containerConfigPath(opts.name, home);
|
|
7419
|
-
if (
|
|
7697
|
+
if (existsSync10(dest)) {
|
|
7420
7698
|
throw new Error(
|
|
7421
7699
|
`Config already exists: ${dest}. Delete it manually before re-running \`monoceros init\` \u2014 this protects any hand-edits.`
|
|
7422
7700
|
);
|
|
@@ -7512,8 +7790,8 @@ async function runInit(opts) {
|
|
|
7512
7790
|
}
|
|
7513
7791
|
await ensureEnvVars(envPath, opts.name, seedVars);
|
|
7514
7792
|
const documented = !anyComposed;
|
|
7515
|
-
const ymlRel =
|
|
7516
|
-
const envRel =
|
|
7793
|
+
const ymlRel = path19.relative(home, dest);
|
|
7794
|
+
const envRel = path19.relative(home, envPath);
|
|
7517
7795
|
if (documented) {
|
|
7518
7796
|
logger.success(`Wrote documented default to ${ymlRel} and ${envRel}.`);
|
|
7519
7797
|
logger.info(
|
|
@@ -7875,8 +8153,8 @@ var init_list_components = __esm({
|
|
|
7875
8153
|
|
|
7876
8154
|
// src/commands/logs.ts
|
|
7877
8155
|
import { spawn as spawn8 } from "child_process";
|
|
7878
|
-
import { existsSync as
|
|
7879
|
-
import
|
|
8156
|
+
import { existsSync as existsSync11 } from "fs";
|
|
8157
|
+
import path20 from "path";
|
|
7880
8158
|
import { defineCommand as defineCommand13 } from "citty";
|
|
7881
8159
|
function tailLogFile(file, follow) {
|
|
7882
8160
|
const [cmd, args] = follow ? ["tail", ["-F", file]] : ["cat", [file]];
|
|
@@ -7919,8 +8197,8 @@ var init_logs = __esm({
|
|
|
7919
8197
|
run({ args }) {
|
|
7920
8198
|
const service = typeof args.service === "string" ? args.service : void 0;
|
|
7921
8199
|
if (service) {
|
|
7922
|
-
const logFile =
|
|
7923
|
-
if (
|
|
8200
|
+
const logFile = path20.join(containerLogsDir(args.name), `${service}.log`);
|
|
8201
|
+
if (existsSync11(logFile)) {
|
|
7924
8202
|
return dispatch(() => tailLogFile(logFile, args.follow));
|
|
7925
8203
|
}
|
|
7926
8204
|
}
|
|
@@ -8127,8 +8405,8 @@ var init_remove_feature = __esm({
|
|
|
8127
8405
|
});
|
|
8128
8406
|
|
|
8129
8407
|
// src/remove/index.ts
|
|
8130
|
-
import { existsSync as
|
|
8131
|
-
import
|
|
8408
|
+
import { existsSync as existsSync12, promises as fs15 } from "fs";
|
|
8409
|
+
import path21 from "path";
|
|
8132
8410
|
import { consola as consola19 } from "consola";
|
|
8133
8411
|
async function runRemove(opts) {
|
|
8134
8412
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -8145,9 +8423,9 @@ async function runRemove(opts) {
|
|
|
8145
8423
|
const ymlPath = containerConfigPath(opts.name, home);
|
|
8146
8424
|
const envPath = containerEnvPath(opts.name, home);
|
|
8147
8425
|
const containerPath = containerDir(opts.name, home);
|
|
8148
|
-
const hasYml =
|
|
8149
|
-
const hasEnv =
|
|
8150
|
-
const hasContainer =
|
|
8426
|
+
const hasYml = existsSync12(ymlPath);
|
|
8427
|
+
const hasEnv = existsSync12(envPath);
|
|
8428
|
+
const hasContainer = existsSync12(containerPath);
|
|
8151
8429
|
if (!hasYml && !hasContainer) {
|
|
8152
8430
|
throw new Error(
|
|
8153
8431
|
`Nothing to remove for '${opts.name}': neither ${ymlPath} nor ${containerPath} exists.`
|
|
@@ -8173,16 +8451,16 @@ async function runRemove(opts) {
|
|
|
8173
8451
|
let backupPath = null;
|
|
8174
8452
|
if (!opts.noBackup && (hasYml || hasContainer)) {
|
|
8175
8453
|
const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
8176
|
-
backupPath =
|
|
8454
|
+
backupPath = path21.join(home, "container-backups", `${opts.name}-${ts}`);
|
|
8177
8455
|
await fs15.mkdir(backupPath, { recursive: true });
|
|
8178
8456
|
if (hasYml) {
|
|
8179
|
-
await fs15.copyFile(ymlPath,
|
|
8457
|
+
await fs15.copyFile(ymlPath, path21.join(backupPath, `${opts.name}.yml`));
|
|
8180
8458
|
}
|
|
8181
8459
|
if (hasEnv) {
|
|
8182
|
-
await fs15.copyFile(envPath,
|
|
8460
|
+
await fs15.copyFile(envPath, path21.join(backupPath, `${opts.name}.env`));
|
|
8183
8461
|
}
|
|
8184
8462
|
if (hasContainer) {
|
|
8185
|
-
await fs15.cp(containerPath,
|
|
8463
|
+
await fs15.cp(containerPath, path21.join(backupPath, "container"), {
|
|
8186
8464
|
recursive: true
|
|
8187
8465
|
});
|
|
8188
8466
|
}
|
|
@@ -8342,8 +8620,8 @@ var init_remove2 = __esm({
|
|
|
8342
8620
|
});
|
|
8343
8621
|
|
|
8344
8622
|
// src/restore/index.ts
|
|
8345
|
-
import { existsSync as
|
|
8346
|
-
import
|
|
8623
|
+
import { existsSync as existsSync13, promises as fs16 } from "fs";
|
|
8624
|
+
import path22 from "path";
|
|
8347
8625
|
import { consola as consola21 } from "consola";
|
|
8348
8626
|
async function runRestore(opts) {
|
|
8349
8627
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -8351,8 +8629,8 @@ async function runRestore(opts) {
|
|
|
8351
8629
|
info: (msg) => consola21.info(msg),
|
|
8352
8630
|
success: (msg) => consola21.success(msg)
|
|
8353
8631
|
};
|
|
8354
|
-
const backup =
|
|
8355
|
-
if (!
|
|
8632
|
+
const backup = path22.resolve(opts.backupPath);
|
|
8633
|
+
if (!existsSync13(backup)) {
|
|
8356
8634
|
throw new Error(`Backup not found: ${backup}.`);
|
|
8357
8635
|
}
|
|
8358
8636
|
const stat = await fs16.stat(backup);
|
|
@@ -8373,24 +8651,24 @@ async function runRestore(opts) {
|
|
|
8373
8651
|
}
|
|
8374
8652
|
const ymlFile = ymlFiles[0];
|
|
8375
8653
|
const name = ymlFile.replace(/\.yml$/, "");
|
|
8376
|
-
const containerInBackup =
|
|
8377
|
-
const hasContainer =
|
|
8378
|
-
const envInBackup =
|
|
8379
|
-
const hasEnv =
|
|
8654
|
+
const containerInBackup = path22.join(backup, "container");
|
|
8655
|
+
const hasContainer = existsSync13(containerInBackup);
|
|
8656
|
+
const envInBackup = path22.join(backup, `${name}.env`);
|
|
8657
|
+
const hasEnv = existsSync13(envInBackup);
|
|
8380
8658
|
const destYml = containerConfigPath(name, home);
|
|
8381
8659
|
const destContainer = containerDir(name, home);
|
|
8382
|
-
if (
|
|
8660
|
+
if (existsSync13(destYml)) {
|
|
8383
8661
|
throw new Error(
|
|
8384
8662
|
`Refusing to restore: ${destYml} already exists. Remove the current container first (\`monoceros remove ${name}\`) or rename the existing config.`
|
|
8385
8663
|
);
|
|
8386
8664
|
}
|
|
8387
|
-
if (hasContainer &&
|
|
8665
|
+
if (hasContainer && existsSync13(destContainer)) {
|
|
8388
8666
|
throw new Error(
|
|
8389
8667
|
`Refusing to restore: ${destContainer} already exists. Remove the current container first (\`monoceros remove ${name}\`).`
|
|
8390
8668
|
);
|
|
8391
8669
|
}
|
|
8392
8670
|
await fs16.mkdir(containerConfigsDir(home), { recursive: true });
|
|
8393
|
-
await fs16.copyFile(
|
|
8671
|
+
await fs16.copyFile(path22.join(backup, ymlFile), destYml);
|
|
8394
8672
|
if (hasEnv) {
|
|
8395
8673
|
await fs16.copyFile(envInBackup, containerEnvPath(name, home));
|
|
8396
8674
|
}
|
|
@@ -8701,9 +8979,9 @@ var init_remove_service = __esm({
|
|
|
8701
8979
|
|
|
8702
8980
|
// src/devcontainer/browser-bridge.ts
|
|
8703
8981
|
import { spawn as spawn9 } from "child_process";
|
|
8704
|
-
import { existsSync as
|
|
8982
|
+
import { existsSync as existsSync14, promises as fsp4, readFileSync as readFileSync6 } from "fs";
|
|
8705
8983
|
import http from "http";
|
|
8706
|
-
import
|
|
8984
|
+
import path23 from "path";
|
|
8707
8985
|
function parseCallbackTarget(authUrl) {
|
|
8708
8986
|
try {
|
|
8709
8987
|
const u = new URL(authUrl);
|
|
@@ -8733,12 +9011,12 @@ function openInBrowser(url) {
|
|
|
8733
9011
|
}
|
|
8734
9012
|
}
|
|
8735
9013
|
async function startBrowserBridge(opts) {
|
|
8736
|
-
const relayDir =
|
|
8737
|
-
const relayScript =
|
|
8738
|
-
const urlFile =
|
|
8739
|
-
await
|
|
8740
|
-
await
|
|
8741
|
-
await
|
|
9014
|
+
const relayDir = path23.join(opts.root, RELAY_DIRNAME);
|
|
9015
|
+
const relayScript = path23.join(relayDir, "xdg-open");
|
|
9016
|
+
const urlFile = path23.join(relayDir, "url");
|
|
9017
|
+
await fsp4.mkdir(relayDir, { recursive: true });
|
|
9018
|
+
await fsp4.rm(urlFile, { force: true });
|
|
9019
|
+
await fsp4.writeFile(
|
|
8742
9020
|
relayScript,
|
|
8743
9021
|
`#!/bin/sh
|
|
8744
9022
|
printf '%s\\n' "$1" > "$(dirname "$0")/url"
|
|
@@ -8746,7 +9024,7 @@ exit 0
|
|
|
8746
9024
|
`,
|
|
8747
9025
|
{ mode: 493 }
|
|
8748
9026
|
);
|
|
8749
|
-
await
|
|
9027
|
+
await fsp4.chmod(relayScript, 493);
|
|
8750
9028
|
const servers = [];
|
|
8751
9029
|
let handled = false;
|
|
8752
9030
|
const onUrl = (url) => {
|
|
@@ -8779,7 +9057,7 @@ exit 0
|
|
|
8779
9057
|
servers.push(server);
|
|
8780
9058
|
};
|
|
8781
9059
|
const poll = setInterval(() => {
|
|
8782
|
-
if (handled || !
|
|
9060
|
+
if (handled || !existsSync14(urlFile)) return;
|
|
8783
9061
|
let content = "";
|
|
8784
9062
|
try {
|
|
8785
9063
|
content = readFileSync6(urlFile, "utf8");
|
|
@@ -8795,7 +9073,7 @@ exit 0
|
|
|
8795
9073
|
async dispose() {
|
|
8796
9074
|
clearInterval(poll);
|
|
8797
9075
|
for (const s of servers) s.close();
|
|
8798
|
-
await
|
|
9076
|
+
await fsp4.rm(relayDir, { recursive: true, force: true });
|
|
8799
9077
|
}
|
|
8800
9078
|
};
|
|
8801
9079
|
}
|
|
@@ -8807,9 +9085,52 @@ var init_browser_bridge = __esm({
|
|
|
8807
9085
|
}
|
|
8808
9086
|
});
|
|
8809
9087
|
|
|
9088
|
+
// src/devcontainer/claude-trust.ts
|
|
9089
|
+
import { existsSync as existsSync15, promises as fsp5 } from "fs";
|
|
9090
|
+
import path24 from "path";
|
|
9091
|
+
function resolveContainerCwd(name, cwd) {
|
|
9092
|
+
const workspace = `/workspaces/${name}`;
|
|
9093
|
+
if (!cwd) return workspace;
|
|
9094
|
+
return path24.posix.isAbsolute(cwd) ? cwd : path24.posix.join(workspace, cwd);
|
|
9095
|
+
}
|
|
9096
|
+
async function preApproveClaudeProject(opts) {
|
|
9097
|
+
const file = path24.join(opts.root, "home", ".claude.json");
|
|
9098
|
+
if (!existsSync15(file)) return;
|
|
9099
|
+
try {
|
|
9100
|
+
const raw = await fsp5.readFile(file, "utf8");
|
|
9101
|
+
const config = raw.trim() ? JSON.parse(raw) : {};
|
|
9102
|
+
if (typeof config !== "object" || config === null) return;
|
|
9103
|
+
const dir = resolveContainerCwd(opts.name, opts.cwd);
|
|
9104
|
+
if (typeof config.projects !== "object" || config.projects === null) {
|
|
9105
|
+
config.projects = {};
|
|
9106
|
+
}
|
|
9107
|
+
if (typeof config.projects[dir] !== "object" || config.projects[dir] === null) {
|
|
9108
|
+
config.projects[dir] = {};
|
|
9109
|
+
}
|
|
9110
|
+
const entry2 = config.projects[dir];
|
|
9111
|
+
if (entry2.hasTrustDialogAccepted === true && entry2.hasClaudeMdExternalIncludesApproved === true && entry2.hasClaudeMdExternalIncludesWarningShown === true) {
|
|
9112
|
+
return;
|
|
9113
|
+
}
|
|
9114
|
+
entry2.hasTrustDialogAccepted = true;
|
|
9115
|
+
entry2.hasClaudeMdExternalIncludesApproved = true;
|
|
9116
|
+
entry2.hasClaudeMdExternalIncludesWarningShown = true;
|
|
9117
|
+
if (typeof entry2.projectOnboardingSeenCount !== "number") {
|
|
9118
|
+
entry2.projectOnboardingSeenCount = 1;
|
|
9119
|
+
}
|
|
9120
|
+
await fsp5.writeFile(file, `${JSON.stringify(config, null, 2)}
|
|
9121
|
+
`);
|
|
9122
|
+
} catch {
|
|
9123
|
+
}
|
|
9124
|
+
}
|
|
9125
|
+
var init_claude_trust = __esm({
|
|
9126
|
+
"src/devcontainer/claude-trust.ts"() {
|
|
9127
|
+
"use strict";
|
|
9128
|
+
}
|
|
9129
|
+
});
|
|
9130
|
+
|
|
8810
9131
|
// src/devcontainer/shell.ts
|
|
8811
|
-
import { existsSync as
|
|
8812
|
-
import
|
|
9132
|
+
import { existsSync as existsSync16 } from "fs";
|
|
9133
|
+
import path25 from "path";
|
|
8813
9134
|
async function runShell(opts) {
|
|
8814
9135
|
assertContainerExists(opts.root);
|
|
8815
9136
|
const spawnFn = opts.spawn ?? spawnDevcontainer;
|
|
@@ -8832,7 +9153,7 @@ async function runShell(opts) {
|
|
|
8832
9153
|
);
|
|
8833
9154
|
}
|
|
8834
9155
|
function assertContainerExists(root) {
|
|
8835
|
-
if (!
|
|
9156
|
+
if (!existsSync16(path25.join(root, ".devcontainer"))) {
|
|
8836
9157
|
throw new Error(
|
|
8837
9158
|
`No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
|
|
8838
9159
|
);
|
|
@@ -8878,6 +9199,13 @@ async function runInContainer(opts) {
|
|
|
8878
9199
|
{ quiet: true }
|
|
8879
9200
|
);
|
|
8880
9201
|
if (upCode !== 0) return upCode;
|
|
9202
|
+
if (opts.name) {
|
|
9203
|
+
await preApproveClaudeProject({
|
|
9204
|
+
root: opts.root,
|
|
9205
|
+
name: opts.name,
|
|
9206
|
+
...opts.cwd ? { cwd: opts.cwd } : {}
|
|
9207
|
+
});
|
|
9208
|
+
}
|
|
8881
9209
|
const bridge = opts.name && process.stdout.isTTY ? await startBrowserBridge({
|
|
8882
9210
|
name: opts.name,
|
|
8883
9211
|
root: opts.root,
|
|
@@ -8907,6 +9235,7 @@ var init_run = __esm({
|
|
|
8907
9235
|
"src/devcontainer/run.ts"() {
|
|
8908
9236
|
"use strict";
|
|
8909
9237
|
init_browser_bridge();
|
|
9238
|
+
init_claude_trust();
|
|
8910
9239
|
init_cli();
|
|
8911
9240
|
init_shell();
|
|
8912
9241
|
}
|
|
@@ -9142,11 +9471,11 @@ var init_stop = __esm({
|
|
|
9142
9471
|
});
|
|
9143
9472
|
|
|
9144
9473
|
// src/tunnel/resolve.ts
|
|
9145
|
-
import { existsSync as
|
|
9146
|
-
import
|
|
9474
|
+
import { existsSync as existsSync17 } from "fs";
|
|
9475
|
+
import path26 from "path";
|
|
9147
9476
|
async function resolveTunnelTarget(opts) {
|
|
9148
9477
|
const ymlPath = containerConfigPath(opts.name, opts.monocerosHome);
|
|
9149
|
-
if (!
|
|
9478
|
+
if (!existsSync17(ymlPath)) {
|
|
9150
9479
|
throw new Error(
|
|
9151
9480
|
`No yml profile for '${opts.name}' at ${ymlPath}. Run \`monoceros init ${opts.name}\` first.`
|
|
9152
9481
|
);
|
|
@@ -9154,13 +9483,13 @@ async function resolveTunnelTarget(opts) {
|
|
|
9154
9483
|
const parsed = await readConfig(ymlPath);
|
|
9155
9484
|
const config = parsed.config;
|
|
9156
9485
|
const containerRoot = containerDir(opts.name, opts.monocerosHome);
|
|
9157
|
-
if (!
|
|
9486
|
+
if (!existsSync17(containerRoot)) {
|
|
9158
9487
|
throw new Error(
|
|
9159
9488
|
`Container '${opts.name}' is not materialised at ${containerRoot}. Run \`monoceros apply ${opts.name}\` first.`
|
|
9160
9489
|
);
|
|
9161
9490
|
}
|
|
9162
|
-
const composePath =
|
|
9163
|
-
const isCompose =
|
|
9491
|
+
const composePath = path26.join(containerRoot, ".devcontainer", "compose.yaml");
|
|
9492
|
+
const isCompose = existsSync17(composePath);
|
|
9164
9493
|
const parsedTarget = parseTargetArg(opts.target, config);
|
|
9165
9494
|
const docker = opts.docker ?? defaultDockerExec;
|
|
9166
9495
|
if (isCompose) {
|
|
@@ -9578,8 +9907,69 @@ var init_tunnel = __esm({
|
|
|
9578
9907
|
}
|
|
9579
9908
|
});
|
|
9580
9909
|
|
|
9910
|
+
// src/upgrade/prune.ts
|
|
9911
|
+
function selectStaleImages(registry, currentContainerNames) {
|
|
9912
|
+
const byContainer = /* @__PURE__ */ new Map();
|
|
9913
|
+
for (const rec of registry) {
|
|
9914
|
+
const list = byContainer.get(rec.container) ?? [];
|
|
9915
|
+
list.push(rec);
|
|
9916
|
+
byContainer.set(rec.container, list);
|
|
9917
|
+
}
|
|
9918
|
+
const stale = [];
|
|
9919
|
+
const keep = [];
|
|
9920
|
+
for (const [container, records] of byContainer) {
|
|
9921
|
+
if (!currentContainerNames.has(container)) {
|
|
9922
|
+
stale.push(...records);
|
|
9923
|
+
continue;
|
|
9924
|
+
}
|
|
9925
|
+
const sorted = [...records].sort(
|
|
9926
|
+
(a, b) => a.builtAt < b.builtAt ? 1 : a.builtAt > b.builtAt ? -1 : 0
|
|
9927
|
+
);
|
|
9928
|
+
keep.push(sorted[0]);
|
|
9929
|
+
stale.push(...sorted.slice(1));
|
|
9930
|
+
}
|
|
9931
|
+
return { stale, keep };
|
|
9932
|
+
}
|
|
9933
|
+
async function pruneStaleImages(opts) {
|
|
9934
|
+
const exec = opts.exec ?? spawnDocker;
|
|
9935
|
+
const state = await readMachineState(opts.home);
|
|
9936
|
+
const registry = state.builtImages ?? [];
|
|
9937
|
+
const { stale, keep } = selectStaleImages(
|
|
9938
|
+
registry,
|
|
9939
|
+
opts.currentContainerNames
|
|
9940
|
+
);
|
|
9941
|
+
const survivors = [...keep];
|
|
9942
|
+
let removed = 0;
|
|
9943
|
+
for (const rec of stale) {
|
|
9944
|
+
const outcome = await removeImage(rec.imageId, exec);
|
|
9945
|
+
if (outcome === "removed") {
|
|
9946
|
+
removed += 1;
|
|
9947
|
+
} else if (outcome === "absent") {
|
|
9948
|
+
} else {
|
|
9949
|
+
survivors.push(rec);
|
|
9950
|
+
}
|
|
9951
|
+
}
|
|
9952
|
+
state.builtImages = survivors;
|
|
9953
|
+
await writeMachineState(state, opts.home);
|
|
9954
|
+
if (removed > 0) {
|
|
9955
|
+
opts.logger?.info(
|
|
9956
|
+
`Pruned ${removed} stale Monoceros image${removed === 1 ? "" : "s"}.`
|
|
9957
|
+
);
|
|
9958
|
+
}
|
|
9959
|
+
return { removed, attempted: stale.length };
|
|
9960
|
+
}
|
|
9961
|
+
var init_prune = __esm({
|
|
9962
|
+
"src/upgrade/prune.ts"() {
|
|
9963
|
+
"use strict";
|
|
9964
|
+
init_machine_state();
|
|
9965
|
+
init_proxy();
|
|
9966
|
+
init_compose();
|
|
9967
|
+
init_images();
|
|
9968
|
+
}
|
|
9969
|
+
});
|
|
9970
|
+
|
|
9581
9971
|
// src/upgrade/index.ts
|
|
9582
|
-
import { existsSync as
|
|
9972
|
+
import { existsSync as existsSync18, promises as fs17 } from "fs";
|
|
9583
9973
|
import { consola as consola34 } from "consola";
|
|
9584
9974
|
async function fetchRuntimeVersions() {
|
|
9585
9975
|
const tokenUrl = `https://ghcr.io/token?service=ghcr.io&scope=repository:${RUNTIME_REPO}:pull`;
|
|
@@ -9632,53 +10022,113 @@ async function runUpgrade(opts) {
|
|
|
9632
10022
|
);
|
|
9633
10023
|
return 0;
|
|
9634
10024
|
}
|
|
9635
|
-
if (
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
10025
|
+
if (opts.version !== void 0) {
|
|
10026
|
+
if (!opts.name) {
|
|
10027
|
+
throw new Error(
|
|
10028
|
+
"A specific version can only be pinned for one container: `monoceros upgrade <name> <version>`."
|
|
10029
|
+
);
|
|
10030
|
+
}
|
|
10031
|
+
if (!VERSION_RE.test(opts.version)) {
|
|
10032
|
+
throw new Error(
|
|
10033
|
+
`Invalid version ${JSON.stringify(opts.version)}. Expected an exact version like '1.1.0'.`
|
|
10034
|
+
);
|
|
10035
|
+
}
|
|
9639
10036
|
}
|
|
9640
|
-
|
|
9641
|
-
if (!existsSync16(ymlPath)) {
|
|
10037
|
+
if (opts.name && !existsSync18(containerConfigPath(opts.name, home))) {
|
|
9642
10038
|
throw new Error(
|
|
9643
|
-
`No such config: ${
|
|
10039
|
+
`No such config: ${containerConfigPath(opts.name, home)}. Run \`monoceros init <template> ${opts.name}\` first.`
|
|
9644
10040
|
);
|
|
9645
10041
|
}
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
10042
|
+
const targets = opts.name ? [opts.name] : await listContainerNames2(home);
|
|
10043
|
+
const apply = opts.applyRunner ?? runApply;
|
|
10044
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
10045
|
+
const pruneAndStamp = async () => {
|
|
10046
|
+
const result = await pruneStaleImages({
|
|
10047
|
+
home,
|
|
10048
|
+
currentContainerNames: new Set(await listContainerNames2(home)),
|
|
10049
|
+
...opts.dockerExec ? { exec: opts.dockerExec } : {}
|
|
10050
|
+
});
|
|
10051
|
+
await markUpgraded(now.toISOString(), home);
|
|
10052
|
+
return result;
|
|
10053
|
+
};
|
|
10054
|
+
if (targets.length === 0) {
|
|
10055
|
+
logger.info("No containers to upgrade.");
|
|
10056
|
+
await pruneAndStamp();
|
|
10057
|
+
return 0;
|
|
9651
10058
|
}
|
|
9652
|
-
|
|
10059
|
+
let pinVersion = opts.version;
|
|
10060
|
+
if (pinVersion === void 0) {
|
|
9653
10061
|
const versions = await fetchVersions();
|
|
9654
|
-
|
|
9655
|
-
if (!
|
|
10062
|
+
pinVersion = versions[versions.length - 1];
|
|
10063
|
+
if (!pinVersion) {
|
|
9656
10064
|
throw new Error("Could not determine the latest runtime version.");
|
|
9657
10065
|
}
|
|
9658
|
-
logger.info(`Latest published runtime version: ${
|
|
10066
|
+
logger.info(`Latest published runtime version: ${pinVersion}`);
|
|
10067
|
+
}
|
|
10068
|
+
let worstExit = 0;
|
|
10069
|
+
let bumped = 0;
|
|
10070
|
+
for (const name of targets) {
|
|
10071
|
+
const ymlPath = containerConfigPath(name, home);
|
|
10072
|
+
if (!existsSync18(ymlPath)) continue;
|
|
10073
|
+
const raw = await fs17.readFile(ymlPath, "utf8");
|
|
10074
|
+
const updated = setRuntimeVersion(raw, pinVersion);
|
|
10075
|
+
if (updated !== raw) {
|
|
10076
|
+
await fs17.writeFile(ymlPath, updated);
|
|
10077
|
+
bumped += 1;
|
|
10078
|
+
logger.info(`Pinned '${name}' to runtime ${pinVersion}.`);
|
|
10079
|
+
}
|
|
10080
|
+
logger.info(`Refreshing '${name}' (rebuild \u2014 latest tools)\u2026`);
|
|
10081
|
+
const result = await apply({
|
|
10082
|
+
name,
|
|
10083
|
+
cliVersion: opts.cliVersion,
|
|
10084
|
+
monocerosHome: home,
|
|
10085
|
+
rebuild: true
|
|
10086
|
+
});
|
|
10087
|
+
if (result.containerExitCode !== 0) {
|
|
10088
|
+
worstExit = result.containerExitCode;
|
|
10089
|
+
logger.warn?.(
|
|
10090
|
+
`Upgrade of '${name}' failed (exit ${result.containerExitCode}).`
|
|
10091
|
+
);
|
|
10092
|
+
}
|
|
9659
10093
|
}
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
|
|
10094
|
+
if (worstExit === 0) {
|
|
10095
|
+
const prune = await pruneAndStamp();
|
|
10096
|
+
logger.success(
|
|
10097
|
+
`Upgraded ${opts.name ? `'${opts.name}'` : `${targets.length} container${targets.length === 1 ? "" : "s"}`}
|
|
10098
|
+
tools rebuilt \u2014 latest pulled
|
|
10099
|
+
base ${pinVersion} ${bumped > 0 ? `(${bumped} bumped)` : "(already latest)"}
|
|
10100
|
+
pruned ${formatPruneLine(prune)}
|
|
10101
|
+
recorded ${now.toISOString().slice(0, 16).replace("T", " ")} UTC`
|
|
10102
|
+
);
|
|
10103
|
+
}
|
|
10104
|
+
return worstExit;
|
|
10105
|
+
}
|
|
10106
|
+
function formatPruneLine(prune) {
|
|
10107
|
+
const gone = prune.attempted - prune.removed;
|
|
10108
|
+
if (prune.attempted === 0) return "nothing stale";
|
|
10109
|
+
if (prune.removed === 0) {
|
|
10110
|
+
return `0 removed (${gone} stale ${gone === 1 ? "entry" : "entries"} already gone)`;
|
|
10111
|
+
}
|
|
10112
|
+
const base = `${prune.removed} image${prune.removed === 1 ? "" : "s"} removed`;
|
|
10113
|
+
return gone > 0 ? `${base}, ${gone} stale ${gone === 1 ? "entry" : "entries"} cleared` : base;
|
|
10114
|
+
}
|
|
10115
|
+
async function listContainerNames2(home) {
|
|
10116
|
+
try {
|
|
10117
|
+
const entries = await fs17.readdir(containerConfigsDir(home));
|
|
10118
|
+
return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length));
|
|
10119
|
+
} catch {
|
|
10120
|
+
return [];
|
|
9667
10121
|
}
|
|
9668
|
-
const apply = opts.applyRunner ?? runApply;
|
|
9669
|
-
const result = await apply({
|
|
9670
|
-
name: opts.name,
|
|
9671
|
-
cliVersion: opts.cliVersion,
|
|
9672
|
-
monocerosHome: home
|
|
9673
|
-
});
|
|
9674
|
-
return result.containerExitCode;
|
|
9675
10122
|
}
|
|
9676
10123
|
var RUNTIME_REPO, VERSION_RE;
|
|
9677
10124
|
var init_upgrade = __esm({
|
|
9678
10125
|
"src/upgrade/index.ts"() {
|
|
9679
10126
|
"use strict";
|
|
9680
10127
|
init_paths();
|
|
10128
|
+
init_machine_state();
|
|
9681
10129
|
init_catalog();
|
|
10130
|
+
init_proxy();
|
|
10131
|
+
init_prune();
|
|
9682
10132
|
init_apply();
|
|
9683
10133
|
RUNTIME_REPO = "getmonoceros/monoceros-runtime";
|
|
9684
10134
|
VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
@@ -9698,7 +10148,7 @@ var init_upgrade2 = __esm({
|
|
|
9698
10148
|
meta: {
|
|
9699
10149
|
name: "upgrade",
|
|
9700
10150
|
group: "lifecycle",
|
|
9701
|
-
description: "
|
|
10151
|
+
description: "Refresh tooling to the latest (rebuilds feature layers, bumps the runtime base when newer) and prune stale Monoceros images. `monoceros upgrade` refreshes all containers; `monoceros upgrade <name>` one; `monoceros upgrade <name> <version>` pins that base version; `monoceros upgrade --list` lists runtime versions."
|
|
9702
10152
|
},
|
|
9703
10153
|
args: {
|
|
9704
10154
|
name: {
|
|
@@ -10068,25 +10518,25 @@ function detectHelpRequest(argv, main2) {
|
|
|
10068
10518
|
const separatorIdx = argv.indexOf("--");
|
|
10069
10519
|
if (helpIdx === -1) return null;
|
|
10070
10520
|
if (separatorIdx !== -1 && separatorIdx < helpIdx) return null;
|
|
10071
|
-
const
|
|
10521
|
+
const path27 = [];
|
|
10072
10522
|
const tokens = argv.slice(
|
|
10073
10523
|
0,
|
|
10074
10524
|
separatorIdx === -1 ? argv.length : separatorIdx
|
|
10075
10525
|
);
|
|
10076
10526
|
let cursor = main2;
|
|
10077
10527
|
const mainName = (main2.meta ?? {}).name ?? "monoceros";
|
|
10078
|
-
|
|
10528
|
+
path27.push(mainName);
|
|
10079
10529
|
for (const tok of tokens) {
|
|
10080
10530
|
if (tok.startsWith("-")) continue;
|
|
10081
10531
|
const subs = cursor.subCommands ?? {};
|
|
10082
10532
|
if (tok in subs) {
|
|
10083
10533
|
cursor = subs[tok];
|
|
10084
|
-
|
|
10534
|
+
path27.push(tok);
|
|
10085
10535
|
continue;
|
|
10086
10536
|
}
|
|
10087
10537
|
break;
|
|
10088
10538
|
}
|
|
10089
|
-
return { path:
|
|
10539
|
+
return { path: path27, cmd: cursor };
|
|
10090
10540
|
}
|
|
10091
10541
|
async function maybeRenderHelp(argv, main2) {
|
|
10092
10542
|
const hit = detectHelpRequest(argv, main2);
|