@getmonoceros/workbench 1.20.2 → 1.21.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/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.1" : "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
|
}
|
|
@@ -7043,9 +7321,12 @@ function generateComposedYml(name, composed, lookupManifest, repoUrls = [], port
|
|
|
7043
7321
|
lines.push("schemaVersion: 1");
|
|
7044
7322
|
lines.push(`name: ${name}`);
|
|
7045
7323
|
lines.push(
|
|
7046
|
-
"# Pinned runtime image
|
|
7324
|
+
"# Pinned runtime base image, reused on every apply (never auto-bumped)."
|
|
7325
|
+
);
|
|
7326
|
+
lines.push(
|
|
7327
|
+
"# `monoceros upgrade <name>` refreshes the tooling and moves this to the"
|
|
7047
7328
|
);
|
|
7048
|
-
lines.push("#
|
|
7329
|
+
lines.push("# latest runtime when a newer one exists.");
|
|
7049
7330
|
lines.push(`runtimeVersion: ${DEFAULT_RUNTIME_VERSION}`);
|
|
7050
7331
|
lines.push("");
|
|
7051
7332
|
if (composed.languages.length > 0) {
|
|
@@ -7146,9 +7427,12 @@ function generateDocumentedYml(name, catalog, lookupManifest, repoUrls = [], por
|
|
|
7146
7427
|
lines.push("schemaVersion: 1");
|
|
7147
7428
|
lines.push(`name: ${name}`);
|
|
7148
7429
|
lines.push(
|
|
7149
|
-
"# Pinned runtime image
|
|
7430
|
+
"# Pinned runtime base image, reused on every apply (never auto-bumped)."
|
|
7150
7431
|
);
|
|
7151
|
-
lines.push(
|
|
7432
|
+
lines.push(
|
|
7433
|
+
"# `monoceros upgrade <name>` refreshes the tooling and moves this to the"
|
|
7434
|
+
);
|
|
7435
|
+
lines.push("# latest runtime when a newer one exists.");
|
|
7152
7436
|
lines.push(`runtimeVersion: ${DEFAULT_RUNTIME_VERSION}`);
|
|
7153
7437
|
lines.push("");
|
|
7154
7438
|
if (byCategory.language.length > 0) {
|
|
@@ -7400,8 +7684,8 @@ var init_generator = __esm({
|
|
|
7400
7684
|
});
|
|
7401
7685
|
|
|
7402
7686
|
// src/init/index.ts
|
|
7403
|
-
import { existsSync as
|
|
7404
|
-
import
|
|
7687
|
+
import { existsSync as existsSync10, promises as fs14 } from "fs";
|
|
7688
|
+
import path19 from "path";
|
|
7405
7689
|
import { consola as consola13 } from "consola";
|
|
7406
7690
|
async function runInit(opts) {
|
|
7407
7691
|
const workbench = opts.workbenchRoot ?? workbenchRoot();
|
|
@@ -7416,7 +7700,7 @@ async function runInit(opts) {
|
|
|
7416
7700
|
);
|
|
7417
7701
|
}
|
|
7418
7702
|
const dest = containerConfigPath(opts.name, home);
|
|
7419
|
-
if (
|
|
7703
|
+
if (existsSync10(dest)) {
|
|
7420
7704
|
throw new Error(
|
|
7421
7705
|
`Config already exists: ${dest}. Delete it manually before re-running \`monoceros init\` \u2014 this protects any hand-edits.`
|
|
7422
7706
|
);
|
|
@@ -7512,8 +7796,8 @@ async function runInit(opts) {
|
|
|
7512
7796
|
}
|
|
7513
7797
|
await ensureEnvVars(envPath, opts.name, seedVars);
|
|
7514
7798
|
const documented = !anyComposed;
|
|
7515
|
-
const ymlRel =
|
|
7516
|
-
const envRel =
|
|
7799
|
+
const ymlRel = path19.relative(home, dest);
|
|
7800
|
+
const envRel = path19.relative(home, envPath);
|
|
7517
7801
|
if (documented) {
|
|
7518
7802
|
logger.success(`Wrote documented default to ${ymlRel} and ${envRel}.`);
|
|
7519
7803
|
logger.info(
|
|
@@ -7875,8 +8159,8 @@ var init_list_components = __esm({
|
|
|
7875
8159
|
|
|
7876
8160
|
// src/commands/logs.ts
|
|
7877
8161
|
import { spawn as spawn8 } from "child_process";
|
|
7878
|
-
import { existsSync as
|
|
7879
|
-
import
|
|
8162
|
+
import { existsSync as existsSync11 } from "fs";
|
|
8163
|
+
import path20 from "path";
|
|
7880
8164
|
import { defineCommand as defineCommand13 } from "citty";
|
|
7881
8165
|
function tailLogFile(file, follow) {
|
|
7882
8166
|
const [cmd, args] = follow ? ["tail", ["-F", file]] : ["cat", [file]];
|
|
@@ -7919,8 +8203,8 @@ var init_logs = __esm({
|
|
|
7919
8203
|
run({ args }) {
|
|
7920
8204
|
const service = typeof args.service === "string" ? args.service : void 0;
|
|
7921
8205
|
if (service) {
|
|
7922
|
-
const logFile =
|
|
7923
|
-
if (
|
|
8206
|
+
const logFile = path20.join(containerLogsDir(args.name), `${service}.log`);
|
|
8207
|
+
if (existsSync11(logFile)) {
|
|
7924
8208
|
return dispatch(() => tailLogFile(logFile, args.follow));
|
|
7925
8209
|
}
|
|
7926
8210
|
}
|
|
@@ -8127,8 +8411,8 @@ var init_remove_feature = __esm({
|
|
|
8127
8411
|
});
|
|
8128
8412
|
|
|
8129
8413
|
// src/remove/index.ts
|
|
8130
|
-
import { existsSync as
|
|
8131
|
-
import
|
|
8414
|
+
import { existsSync as existsSync12, promises as fs15 } from "fs";
|
|
8415
|
+
import path21 from "path";
|
|
8132
8416
|
import { consola as consola19 } from "consola";
|
|
8133
8417
|
async function runRemove(opts) {
|
|
8134
8418
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -8145,9 +8429,9 @@ async function runRemove(opts) {
|
|
|
8145
8429
|
const ymlPath = containerConfigPath(opts.name, home);
|
|
8146
8430
|
const envPath = containerEnvPath(opts.name, home);
|
|
8147
8431
|
const containerPath = containerDir(opts.name, home);
|
|
8148
|
-
const hasYml =
|
|
8149
|
-
const hasEnv =
|
|
8150
|
-
const hasContainer =
|
|
8432
|
+
const hasYml = existsSync12(ymlPath);
|
|
8433
|
+
const hasEnv = existsSync12(envPath);
|
|
8434
|
+
const hasContainer = existsSync12(containerPath);
|
|
8151
8435
|
if (!hasYml && !hasContainer) {
|
|
8152
8436
|
throw new Error(
|
|
8153
8437
|
`Nothing to remove for '${opts.name}': neither ${ymlPath} nor ${containerPath} exists.`
|
|
@@ -8173,16 +8457,16 @@ async function runRemove(opts) {
|
|
|
8173
8457
|
let backupPath = null;
|
|
8174
8458
|
if (!opts.noBackup && (hasYml || hasContainer)) {
|
|
8175
8459
|
const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
8176
|
-
backupPath =
|
|
8460
|
+
backupPath = path21.join(home, "container-backups", `${opts.name}-${ts}`);
|
|
8177
8461
|
await fs15.mkdir(backupPath, { recursive: true });
|
|
8178
8462
|
if (hasYml) {
|
|
8179
|
-
await fs15.copyFile(ymlPath,
|
|
8463
|
+
await fs15.copyFile(ymlPath, path21.join(backupPath, `${opts.name}.yml`));
|
|
8180
8464
|
}
|
|
8181
8465
|
if (hasEnv) {
|
|
8182
|
-
await fs15.copyFile(envPath,
|
|
8466
|
+
await fs15.copyFile(envPath, path21.join(backupPath, `${opts.name}.env`));
|
|
8183
8467
|
}
|
|
8184
8468
|
if (hasContainer) {
|
|
8185
|
-
await fs15.cp(containerPath,
|
|
8469
|
+
await fs15.cp(containerPath, path21.join(backupPath, "container"), {
|
|
8186
8470
|
recursive: true
|
|
8187
8471
|
});
|
|
8188
8472
|
}
|
|
@@ -8342,8 +8626,8 @@ var init_remove2 = __esm({
|
|
|
8342
8626
|
});
|
|
8343
8627
|
|
|
8344
8628
|
// src/restore/index.ts
|
|
8345
|
-
import { existsSync as
|
|
8346
|
-
import
|
|
8629
|
+
import { existsSync as existsSync13, promises as fs16 } from "fs";
|
|
8630
|
+
import path22 from "path";
|
|
8347
8631
|
import { consola as consola21 } from "consola";
|
|
8348
8632
|
async function runRestore(opts) {
|
|
8349
8633
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -8351,8 +8635,8 @@ async function runRestore(opts) {
|
|
|
8351
8635
|
info: (msg) => consola21.info(msg),
|
|
8352
8636
|
success: (msg) => consola21.success(msg)
|
|
8353
8637
|
};
|
|
8354
|
-
const backup =
|
|
8355
|
-
if (!
|
|
8638
|
+
const backup = path22.resolve(opts.backupPath);
|
|
8639
|
+
if (!existsSync13(backup)) {
|
|
8356
8640
|
throw new Error(`Backup not found: ${backup}.`);
|
|
8357
8641
|
}
|
|
8358
8642
|
const stat = await fs16.stat(backup);
|
|
@@ -8373,24 +8657,24 @@ async function runRestore(opts) {
|
|
|
8373
8657
|
}
|
|
8374
8658
|
const ymlFile = ymlFiles[0];
|
|
8375
8659
|
const name = ymlFile.replace(/\.yml$/, "");
|
|
8376
|
-
const containerInBackup =
|
|
8377
|
-
const hasContainer =
|
|
8378
|
-
const envInBackup =
|
|
8379
|
-
const hasEnv =
|
|
8660
|
+
const containerInBackup = path22.join(backup, "container");
|
|
8661
|
+
const hasContainer = existsSync13(containerInBackup);
|
|
8662
|
+
const envInBackup = path22.join(backup, `${name}.env`);
|
|
8663
|
+
const hasEnv = existsSync13(envInBackup);
|
|
8380
8664
|
const destYml = containerConfigPath(name, home);
|
|
8381
8665
|
const destContainer = containerDir(name, home);
|
|
8382
|
-
if (
|
|
8666
|
+
if (existsSync13(destYml)) {
|
|
8383
8667
|
throw new Error(
|
|
8384
8668
|
`Refusing to restore: ${destYml} already exists. Remove the current container first (\`monoceros remove ${name}\`) or rename the existing config.`
|
|
8385
8669
|
);
|
|
8386
8670
|
}
|
|
8387
|
-
if (hasContainer &&
|
|
8671
|
+
if (hasContainer && existsSync13(destContainer)) {
|
|
8388
8672
|
throw new Error(
|
|
8389
8673
|
`Refusing to restore: ${destContainer} already exists. Remove the current container first (\`monoceros remove ${name}\`).`
|
|
8390
8674
|
);
|
|
8391
8675
|
}
|
|
8392
8676
|
await fs16.mkdir(containerConfigsDir(home), { recursive: true });
|
|
8393
|
-
await fs16.copyFile(
|
|
8677
|
+
await fs16.copyFile(path22.join(backup, ymlFile), destYml);
|
|
8394
8678
|
if (hasEnv) {
|
|
8395
8679
|
await fs16.copyFile(envInBackup, containerEnvPath(name, home));
|
|
8396
8680
|
}
|
|
@@ -8701,9 +8985,9 @@ var init_remove_service = __esm({
|
|
|
8701
8985
|
|
|
8702
8986
|
// src/devcontainer/browser-bridge.ts
|
|
8703
8987
|
import { spawn as spawn9 } from "child_process";
|
|
8704
|
-
import { existsSync as
|
|
8988
|
+
import { existsSync as existsSync14, promises as fsp4, readFileSync as readFileSync6 } from "fs";
|
|
8705
8989
|
import http from "http";
|
|
8706
|
-
import
|
|
8990
|
+
import path23 from "path";
|
|
8707
8991
|
function parseCallbackTarget(authUrl) {
|
|
8708
8992
|
try {
|
|
8709
8993
|
const u = new URL(authUrl);
|
|
@@ -8733,12 +9017,12 @@ function openInBrowser(url) {
|
|
|
8733
9017
|
}
|
|
8734
9018
|
}
|
|
8735
9019
|
async function startBrowserBridge(opts) {
|
|
8736
|
-
const relayDir =
|
|
8737
|
-
const relayScript =
|
|
8738
|
-
const urlFile =
|
|
8739
|
-
await
|
|
8740
|
-
await
|
|
8741
|
-
await
|
|
9020
|
+
const relayDir = path23.join(opts.root, RELAY_DIRNAME);
|
|
9021
|
+
const relayScript = path23.join(relayDir, "xdg-open");
|
|
9022
|
+
const urlFile = path23.join(relayDir, "url");
|
|
9023
|
+
await fsp4.mkdir(relayDir, { recursive: true });
|
|
9024
|
+
await fsp4.rm(urlFile, { force: true });
|
|
9025
|
+
await fsp4.writeFile(
|
|
8742
9026
|
relayScript,
|
|
8743
9027
|
`#!/bin/sh
|
|
8744
9028
|
printf '%s\\n' "$1" > "$(dirname "$0")/url"
|
|
@@ -8746,7 +9030,7 @@ exit 0
|
|
|
8746
9030
|
`,
|
|
8747
9031
|
{ mode: 493 }
|
|
8748
9032
|
);
|
|
8749
|
-
await
|
|
9033
|
+
await fsp4.chmod(relayScript, 493);
|
|
8750
9034
|
const servers = [];
|
|
8751
9035
|
let handled = false;
|
|
8752
9036
|
const onUrl = (url) => {
|
|
@@ -8779,7 +9063,7 @@ exit 0
|
|
|
8779
9063
|
servers.push(server);
|
|
8780
9064
|
};
|
|
8781
9065
|
const poll = setInterval(() => {
|
|
8782
|
-
if (handled || !
|
|
9066
|
+
if (handled || !existsSync14(urlFile)) return;
|
|
8783
9067
|
let content = "";
|
|
8784
9068
|
try {
|
|
8785
9069
|
content = readFileSync6(urlFile, "utf8");
|
|
@@ -8795,7 +9079,7 @@ exit 0
|
|
|
8795
9079
|
async dispose() {
|
|
8796
9080
|
clearInterval(poll);
|
|
8797
9081
|
for (const s of servers) s.close();
|
|
8798
|
-
await
|
|
9082
|
+
await fsp4.rm(relayDir, { recursive: true, force: true });
|
|
8799
9083
|
}
|
|
8800
9084
|
};
|
|
8801
9085
|
}
|
|
@@ -8808,18 +9092,18 @@ var init_browser_bridge = __esm({
|
|
|
8808
9092
|
});
|
|
8809
9093
|
|
|
8810
9094
|
// src/devcontainer/claude-trust.ts
|
|
8811
|
-
import { existsSync as
|
|
8812
|
-
import
|
|
9095
|
+
import { existsSync as existsSync15, promises as fsp5 } from "fs";
|
|
9096
|
+
import path24 from "path";
|
|
8813
9097
|
function resolveContainerCwd(name, cwd) {
|
|
8814
9098
|
const workspace = `/workspaces/${name}`;
|
|
8815
9099
|
if (!cwd) return workspace;
|
|
8816
|
-
return
|
|
9100
|
+
return path24.posix.isAbsolute(cwd) ? cwd : path24.posix.join(workspace, cwd);
|
|
8817
9101
|
}
|
|
8818
9102
|
async function preApproveClaudeProject(opts) {
|
|
8819
|
-
const file =
|
|
8820
|
-
if (!
|
|
9103
|
+
const file = path24.join(opts.root, "home", ".claude.json");
|
|
9104
|
+
if (!existsSync15(file)) return;
|
|
8821
9105
|
try {
|
|
8822
|
-
const raw = await
|
|
9106
|
+
const raw = await fsp5.readFile(file, "utf8");
|
|
8823
9107
|
const config = raw.trim() ? JSON.parse(raw) : {};
|
|
8824
9108
|
if (typeof config !== "object" || config === null) return;
|
|
8825
9109
|
const dir = resolveContainerCwd(opts.name, opts.cwd);
|
|
@@ -8839,7 +9123,7 @@ async function preApproveClaudeProject(opts) {
|
|
|
8839
9123
|
if (typeof entry2.projectOnboardingSeenCount !== "number") {
|
|
8840
9124
|
entry2.projectOnboardingSeenCount = 1;
|
|
8841
9125
|
}
|
|
8842
|
-
await
|
|
9126
|
+
await fsp5.writeFile(file, `${JSON.stringify(config, null, 2)}
|
|
8843
9127
|
`);
|
|
8844
9128
|
} catch {
|
|
8845
9129
|
}
|
|
@@ -8851,8 +9135,8 @@ var init_claude_trust = __esm({
|
|
|
8851
9135
|
});
|
|
8852
9136
|
|
|
8853
9137
|
// src/devcontainer/shell.ts
|
|
8854
|
-
import { existsSync as
|
|
8855
|
-
import
|
|
9138
|
+
import { existsSync as existsSync16 } from "fs";
|
|
9139
|
+
import path25 from "path";
|
|
8856
9140
|
async function runShell(opts) {
|
|
8857
9141
|
assertContainerExists(opts.root);
|
|
8858
9142
|
const spawnFn = opts.spawn ?? spawnDevcontainer;
|
|
@@ -8875,7 +9159,7 @@ async function runShell(opts) {
|
|
|
8875
9159
|
);
|
|
8876
9160
|
}
|
|
8877
9161
|
function assertContainerExists(root) {
|
|
8878
|
-
if (!
|
|
9162
|
+
if (!existsSync16(path25.join(root, ".devcontainer"))) {
|
|
8879
9163
|
throw new Error(
|
|
8880
9164
|
`No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
|
|
8881
9165
|
);
|
|
@@ -9193,11 +9477,11 @@ var init_stop = __esm({
|
|
|
9193
9477
|
});
|
|
9194
9478
|
|
|
9195
9479
|
// src/tunnel/resolve.ts
|
|
9196
|
-
import { existsSync as
|
|
9197
|
-
import
|
|
9480
|
+
import { existsSync as existsSync17 } from "fs";
|
|
9481
|
+
import path26 from "path";
|
|
9198
9482
|
async function resolveTunnelTarget(opts) {
|
|
9199
9483
|
const ymlPath = containerConfigPath(opts.name, opts.monocerosHome);
|
|
9200
|
-
if (!
|
|
9484
|
+
if (!existsSync17(ymlPath)) {
|
|
9201
9485
|
throw new Error(
|
|
9202
9486
|
`No yml profile for '${opts.name}' at ${ymlPath}. Run \`monoceros init ${opts.name}\` first.`
|
|
9203
9487
|
);
|
|
@@ -9205,13 +9489,13 @@ async function resolveTunnelTarget(opts) {
|
|
|
9205
9489
|
const parsed = await readConfig(ymlPath);
|
|
9206
9490
|
const config = parsed.config;
|
|
9207
9491
|
const containerRoot = containerDir(opts.name, opts.monocerosHome);
|
|
9208
|
-
if (!
|
|
9492
|
+
if (!existsSync17(containerRoot)) {
|
|
9209
9493
|
throw new Error(
|
|
9210
9494
|
`Container '${opts.name}' is not materialised at ${containerRoot}. Run \`monoceros apply ${opts.name}\` first.`
|
|
9211
9495
|
);
|
|
9212
9496
|
}
|
|
9213
|
-
const composePath =
|
|
9214
|
-
const isCompose =
|
|
9497
|
+
const composePath = path26.join(containerRoot, ".devcontainer", "compose.yaml");
|
|
9498
|
+
const isCompose = existsSync17(composePath);
|
|
9215
9499
|
const parsedTarget = parseTargetArg(opts.target, config);
|
|
9216
9500
|
const docker = opts.docker ?? defaultDockerExec;
|
|
9217
9501
|
if (isCompose) {
|
|
@@ -9629,8 +9913,69 @@ var init_tunnel = __esm({
|
|
|
9629
9913
|
}
|
|
9630
9914
|
});
|
|
9631
9915
|
|
|
9916
|
+
// src/upgrade/prune.ts
|
|
9917
|
+
function selectStaleImages(registry, currentContainerNames) {
|
|
9918
|
+
const byContainer = /* @__PURE__ */ new Map();
|
|
9919
|
+
for (const rec of registry) {
|
|
9920
|
+
const list = byContainer.get(rec.container) ?? [];
|
|
9921
|
+
list.push(rec);
|
|
9922
|
+
byContainer.set(rec.container, list);
|
|
9923
|
+
}
|
|
9924
|
+
const stale = [];
|
|
9925
|
+
const keep = [];
|
|
9926
|
+
for (const [container, records] of byContainer) {
|
|
9927
|
+
if (!currentContainerNames.has(container)) {
|
|
9928
|
+
stale.push(...records);
|
|
9929
|
+
continue;
|
|
9930
|
+
}
|
|
9931
|
+
const sorted = [...records].sort(
|
|
9932
|
+
(a, b) => a.builtAt < b.builtAt ? 1 : a.builtAt > b.builtAt ? -1 : 0
|
|
9933
|
+
);
|
|
9934
|
+
keep.push(sorted[0]);
|
|
9935
|
+
stale.push(...sorted.slice(1));
|
|
9936
|
+
}
|
|
9937
|
+
return { stale, keep };
|
|
9938
|
+
}
|
|
9939
|
+
async function pruneStaleImages(opts) {
|
|
9940
|
+
const exec = opts.exec ?? spawnDocker;
|
|
9941
|
+
const state = await readMachineState(opts.home);
|
|
9942
|
+
const registry = state.builtImages ?? [];
|
|
9943
|
+
const { stale, keep } = selectStaleImages(
|
|
9944
|
+
registry,
|
|
9945
|
+
opts.currentContainerNames
|
|
9946
|
+
);
|
|
9947
|
+
const survivors = [...keep];
|
|
9948
|
+
let removed = 0;
|
|
9949
|
+
for (const rec of stale) {
|
|
9950
|
+
const outcome = await removeImage(rec.imageId, exec);
|
|
9951
|
+
if (outcome === "removed") {
|
|
9952
|
+
removed += 1;
|
|
9953
|
+
} else if (outcome === "absent") {
|
|
9954
|
+
} else {
|
|
9955
|
+
survivors.push(rec);
|
|
9956
|
+
}
|
|
9957
|
+
}
|
|
9958
|
+
state.builtImages = survivors;
|
|
9959
|
+
await writeMachineState(state, opts.home);
|
|
9960
|
+
if (removed > 0) {
|
|
9961
|
+
opts.logger?.info(
|
|
9962
|
+
`Pruned ${removed} stale Monoceros image${removed === 1 ? "" : "s"}.`
|
|
9963
|
+
);
|
|
9964
|
+
}
|
|
9965
|
+
return { removed, attempted: stale.length };
|
|
9966
|
+
}
|
|
9967
|
+
var init_prune = __esm({
|
|
9968
|
+
"src/upgrade/prune.ts"() {
|
|
9969
|
+
"use strict";
|
|
9970
|
+
init_machine_state();
|
|
9971
|
+
init_proxy();
|
|
9972
|
+
init_compose();
|
|
9973
|
+
init_images();
|
|
9974
|
+
}
|
|
9975
|
+
});
|
|
9976
|
+
|
|
9632
9977
|
// src/upgrade/index.ts
|
|
9633
|
-
import { existsSync as
|
|
9978
|
+
import { existsSync as existsSync18, promises as fs17 } from "fs";
|
|
9634
9979
|
import { consola as consola34 } from "consola";
|
|
9635
9980
|
async function fetchRuntimeVersions() {
|
|
9636
9981
|
const tokenUrl = `https://ghcr.io/token?service=ghcr.io&scope=repository:${RUNTIME_REPO}:pull`;
|
|
@@ -9683,53 +10028,113 @@ async function runUpgrade(opts) {
|
|
|
9683
10028
|
);
|
|
9684
10029
|
return 0;
|
|
9685
10030
|
}
|
|
9686
|
-
if (
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
10031
|
+
if (opts.version !== void 0) {
|
|
10032
|
+
if (!opts.name) {
|
|
10033
|
+
throw new Error(
|
|
10034
|
+
"A specific version can only be pinned for one container: `monoceros upgrade <name> <version>`."
|
|
10035
|
+
);
|
|
10036
|
+
}
|
|
10037
|
+
if (!VERSION_RE.test(opts.version)) {
|
|
10038
|
+
throw new Error(
|
|
10039
|
+
`Invalid version ${JSON.stringify(opts.version)}. Expected an exact version like '1.1.0'.`
|
|
10040
|
+
);
|
|
10041
|
+
}
|
|
9690
10042
|
}
|
|
9691
|
-
|
|
9692
|
-
if (!existsSync17(ymlPath)) {
|
|
10043
|
+
if (opts.name && !existsSync18(containerConfigPath(opts.name, home))) {
|
|
9693
10044
|
throw new Error(
|
|
9694
|
-
`No such config: ${
|
|
10045
|
+
`No such config: ${containerConfigPath(opts.name, home)}. Run \`monoceros init <template> ${opts.name}\` first.`
|
|
9695
10046
|
);
|
|
9696
10047
|
}
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
|
|
10048
|
+
const targets = opts.name ? [opts.name] : await listContainerNames2(home);
|
|
10049
|
+
const apply = opts.applyRunner ?? runApply;
|
|
10050
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
10051
|
+
const pruneAndStamp = async () => {
|
|
10052
|
+
const result = await pruneStaleImages({
|
|
10053
|
+
home,
|
|
10054
|
+
currentContainerNames: new Set(await listContainerNames2(home)),
|
|
10055
|
+
...opts.dockerExec ? { exec: opts.dockerExec } : {}
|
|
10056
|
+
});
|
|
10057
|
+
await markUpgraded(now.toISOString(), home);
|
|
10058
|
+
return result;
|
|
10059
|
+
};
|
|
10060
|
+
if (targets.length === 0) {
|
|
10061
|
+
logger.info("No containers to upgrade.");
|
|
10062
|
+
await pruneAndStamp();
|
|
10063
|
+
return 0;
|
|
9702
10064
|
}
|
|
9703
|
-
|
|
10065
|
+
let pinVersion = opts.version;
|
|
10066
|
+
if (pinVersion === void 0) {
|
|
9704
10067
|
const versions = await fetchVersions();
|
|
9705
|
-
|
|
9706
|
-
if (!
|
|
10068
|
+
pinVersion = versions[versions.length - 1];
|
|
10069
|
+
if (!pinVersion) {
|
|
9707
10070
|
throw new Error("Could not determine the latest runtime version.");
|
|
9708
10071
|
}
|
|
9709
|
-
logger.info(`Latest published runtime version: ${
|
|
10072
|
+
logger.info(`Latest published runtime version: ${pinVersion}`);
|
|
10073
|
+
}
|
|
10074
|
+
let worstExit = 0;
|
|
10075
|
+
let bumped = 0;
|
|
10076
|
+
for (const name of targets) {
|
|
10077
|
+
const ymlPath = containerConfigPath(name, home);
|
|
10078
|
+
if (!existsSync18(ymlPath)) continue;
|
|
10079
|
+
const raw = await fs17.readFile(ymlPath, "utf8");
|
|
10080
|
+
const updated = setRuntimeVersion(raw, pinVersion);
|
|
10081
|
+
if (updated !== raw) {
|
|
10082
|
+
await fs17.writeFile(ymlPath, updated);
|
|
10083
|
+
bumped += 1;
|
|
10084
|
+
logger.info(`Pinned '${name}' to runtime ${pinVersion}.`);
|
|
10085
|
+
}
|
|
10086
|
+
logger.info(`Refreshing '${name}' (rebuild \u2014 latest tools)\u2026`);
|
|
10087
|
+
const result = await apply({
|
|
10088
|
+
name,
|
|
10089
|
+
cliVersion: opts.cliVersion,
|
|
10090
|
+
monocerosHome: home,
|
|
10091
|
+
rebuild: true
|
|
10092
|
+
});
|
|
10093
|
+
if (result.containerExitCode !== 0) {
|
|
10094
|
+
worstExit = result.containerExitCode;
|
|
10095
|
+
logger.warn?.(
|
|
10096
|
+
`Upgrade of '${name}' failed (exit ${result.containerExitCode}).`
|
|
10097
|
+
);
|
|
10098
|
+
}
|
|
9710
10099
|
}
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
10100
|
+
if (worstExit === 0) {
|
|
10101
|
+
const prune = await pruneAndStamp();
|
|
10102
|
+
logger.success(
|
|
10103
|
+
`Upgraded ${opts.name ? `'${opts.name}'` : `${targets.length} container${targets.length === 1 ? "" : "s"}`}
|
|
10104
|
+
tools rebuilt \u2014 latest pulled
|
|
10105
|
+
base ${pinVersion} ${bumped > 0 ? `(${bumped} bumped)` : "(already latest)"}
|
|
10106
|
+
pruned ${formatPruneLine(prune)}
|
|
10107
|
+
recorded ${now.toISOString().slice(0, 16).replace("T", " ")} UTC`
|
|
10108
|
+
);
|
|
10109
|
+
}
|
|
10110
|
+
return worstExit;
|
|
10111
|
+
}
|
|
10112
|
+
function formatPruneLine(prune) {
|
|
10113
|
+
const gone = prune.attempted - prune.removed;
|
|
10114
|
+
if (prune.attempted === 0) return "nothing stale";
|
|
10115
|
+
if (prune.removed === 0) {
|
|
10116
|
+
return `0 removed (${gone} stale ${gone === 1 ? "entry" : "entries"} already gone)`;
|
|
10117
|
+
}
|
|
10118
|
+
const base = `${prune.removed} image${prune.removed === 1 ? "" : "s"} removed`;
|
|
10119
|
+
return gone > 0 ? `${base}, ${gone} stale ${gone === 1 ? "entry" : "entries"} cleared` : base;
|
|
10120
|
+
}
|
|
10121
|
+
async function listContainerNames2(home) {
|
|
10122
|
+
try {
|
|
10123
|
+
const entries = await fs17.readdir(containerConfigsDir(home));
|
|
10124
|
+
return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length));
|
|
10125
|
+
} catch {
|
|
10126
|
+
return [];
|
|
9718
10127
|
}
|
|
9719
|
-
const apply = opts.applyRunner ?? runApply;
|
|
9720
|
-
const result = await apply({
|
|
9721
|
-
name: opts.name,
|
|
9722
|
-
cliVersion: opts.cliVersion,
|
|
9723
|
-
monocerosHome: home
|
|
9724
|
-
});
|
|
9725
|
-
return result.containerExitCode;
|
|
9726
10128
|
}
|
|
9727
10129
|
var RUNTIME_REPO, VERSION_RE;
|
|
9728
10130
|
var init_upgrade = __esm({
|
|
9729
10131
|
"src/upgrade/index.ts"() {
|
|
9730
10132
|
"use strict";
|
|
9731
10133
|
init_paths();
|
|
10134
|
+
init_machine_state();
|
|
9732
10135
|
init_catalog();
|
|
10136
|
+
init_proxy();
|
|
10137
|
+
init_prune();
|
|
9733
10138
|
init_apply();
|
|
9734
10139
|
RUNTIME_REPO = "getmonoceros/monoceros-runtime";
|
|
9735
10140
|
VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
@@ -9749,7 +10154,7 @@ var init_upgrade2 = __esm({
|
|
|
9749
10154
|
meta: {
|
|
9750
10155
|
name: "upgrade",
|
|
9751
10156
|
group: "lifecycle",
|
|
9752
|
-
description: "
|
|
10157
|
+
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."
|
|
9753
10158
|
},
|
|
9754
10159
|
args: {
|
|
9755
10160
|
name: {
|
|
@@ -10119,25 +10524,25 @@ function detectHelpRequest(argv, main2) {
|
|
|
10119
10524
|
const separatorIdx = argv.indexOf("--");
|
|
10120
10525
|
if (helpIdx === -1) return null;
|
|
10121
10526
|
if (separatorIdx !== -1 && separatorIdx < helpIdx) return null;
|
|
10122
|
-
const
|
|
10527
|
+
const path27 = [];
|
|
10123
10528
|
const tokens = argv.slice(
|
|
10124
10529
|
0,
|
|
10125
10530
|
separatorIdx === -1 ? argv.length : separatorIdx
|
|
10126
10531
|
);
|
|
10127
10532
|
let cursor = main2;
|
|
10128
10533
|
const mainName = (main2.meta ?? {}).name ?? "monoceros";
|
|
10129
|
-
|
|
10534
|
+
path27.push(mainName);
|
|
10130
10535
|
for (const tok of tokens) {
|
|
10131
10536
|
if (tok.startsWith("-")) continue;
|
|
10132
10537
|
const subs = cursor.subCommands ?? {};
|
|
10133
10538
|
if (tok in subs) {
|
|
10134
10539
|
cursor = subs[tok];
|
|
10135
|
-
|
|
10540
|
+
path27.push(tok);
|
|
10136
10541
|
continue;
|
|
10137
10542
|
}
|
|
10138
10543
|
break;
|
|
10139
10544
|
}
|
|
10140
|
-
return { path:
|
|
10545
|
+
return { path: path27, cmd: cursor };
|
|
10141
10546
|
}
|
|
10142
10547
|
async function maybeRenderHelp(argv, main2) {
|
|
10143
10548
|
const hit = detectHelpRequest(argv, main2);
|