@getmonoceros/workbench 1.15.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -20
- package/dist/bin.js +362 -17
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
- package/templates/components/README.md +13 -13
package/dist/bin.js
CHANGED
|
@@ -74,12 +74,13 @@ ${issues}`);
|
|
|
74
74
|
}
|
|
75
75
|
return result.data;
|
|
76
76
|
}
|
|
77
|
-
var SOLUTION_NAME_RE, APT_PACKAGE_NAME_RE, FEATURE_REF_RE, INSTALL_URL_RE, REPO_URL_RE, REPO_PATH_RE, POSTGRES_URL_RE, REGEX, PROVIDER_VALUES, KNOWN_PROVIDER_HOSTS, CONFIG_SCHEMA_VERSION, FeatureOptionValueSchema, FeatureEntrySchema, EMAIL_RE, GitUserSchema, RepoEntrySchema, PortEntrySchema, RoutingSchema, SERVICE_NAME_RE, ServiceEnvValueSchema, ServiceHealthcheckSchema, SERVICE_RESTART_VALUES, ServiceObjectSchema, ExternalServicesSchema, SolutionConfigSchema;
|
|
77
|
+
var SOLUTION_NAME_RE, APT_PACKAGE_NAME_RE, RUNTIME_VERSION_RE, FEATURE_REF_RE, INSTALL_URL_RE, REPO_URL_RE, REPO_PATH_RE, POSTGRES_URL_RE, REGEX, PROVIDER_VALUES, KNOWN_PROVIDER_HOSTS, CONFIG_SCHEMA_VERSION, FeatureOptionValueSchema, FeatureEntrySchema, EMAIL_RE, GitUserSchema, RepoEntrySchema, PortEntrySchema, RoutingSchema, SERVICE_NAME_RE, ServiceEnvValueSchema, ServiceHealthcheckSchema, SERVICE_RESTART_VALUES, ServiceObjectSchema, ExternalServicesSchema, SolutionConfigSchema;
|
|
78
78
|
var init_schema = __esm({
|
|
79
79
|
"src/config/schema.ts"() {
|
|
80
80
|
"use strict";
|
|
81
81
|
SOLUTION_NAME_RE = /^[A-Za-z0-9._-]+$/;
|
|
82
82
|
APT_PACKAGE_NAME_RE = /^[a-z0-9][a-z0-9.+-]*$/;
|
|
83
|
+
RUNTIME_VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
83
84
|
FEATURE_REF_RE = /^[a-z0-9.-]+(\/[a-z0-9._-]+)+:[a-z0-9._-]+$/;
|
|
84
85
|
INSTALL_URL_RE = /^https:\/\/[A-Za-z0-9.\-_~/:?#[\]@!&'()*+,;=%]+$/;
|
|
85
86
|
REPO_URL_RE = /^https:\/\/[A-Za-z0-9@:/+_~.#=&?-]+$/;
|
|
@@ -216,6 +217,15 @@ var init_schema = __esm({
|
|
|
216
217
|
SOLUTION_NAME_RE,
|
|
217
218
|
"Invalid solution name. Use letters, digits, '.', '_' or '-'."
|
|
218
219
|
),
|
|
220
|
+
// Pinned runtime-image version (ADR 0017). Written by `init`, reused
|
|
221
|
+
// verbatim by every subsequent `apply` (never auto-bumped), changed
|
|
222
|
+
// only by `monoceros upgrade`. Optional in the schema so a
|
|
223
|
+
// pre-pinning yml still parses — `apply` is what enforces its
|
|
224
|
+
// presence with an actionable hint.
|
|
225
|
+
runtimeVersion: z.string().regex(
|
|
226
|
+
RUNTIME_VERSION_RE,
|
|
227
|
+
"Invalid runtimeVersion. Expected an exact version like '1.1.0'."
|
|
228
|
+
).optional(),
|
|
219
229
|
languages: z.array(z.string().min(1)).default([]),
|
|
220
230
|
aptPackages: z.array(
|
|
221
231
|
z.string().regex(
|
|
@@ -1821,6 +1831,25 @@ var init_port_check = __esm({
|
|
|
1821
1831
|
});
|
|
1822
1832
|
|
|
1823
1833
|
// src/create/catalog.ts
|
|
1834
|
+
function resolveRuntimeImage(version) {
|
|
1835
|
+
const ov = process.env.MONOCEROS_BASE_IMAGE_OVERRIDE?.trim();
|
|
1836
|
+
if (ov && ov.length > 0) return ov;
|
|
1837
|
+
if (!version) return `${RUNTIME_IMAGE_REPO}:1`;
|
|
1838
|
+
return `${RUNTIME_IMAGE_REPO}:${version}`;
|
|
1839
|
+
}
|
|
1840
|
+
function compareRuntimeVersions(a, b) {
|
|
1841
|
+
const pa = a.split(".").map(Number);
|
|
1842
|
+
const pb = b.split(".").map(Number);
|
|
1843
|
+
for (let i = 0; i < 3; i++) {
|
|
1844
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1845
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
1846
|
+
}
|
|
1847
|
+
return 0;
|
|
1848
|
+
}
|
|
1849
|
+
function runtimeSupportsIdeVolumes(version) {
|
|
1850
|
+
if (!version) return false;
|
|
1851
|
+
return compareRuntimeVersions(version, MIN_RUNTIME_FOR_IDE_VOLUMES) >= 0;
|
|
1852
|
+
}
|
|
1824
1853
|
function parseLanguageSpec(spec) {
|
|
1825
1854
|
const m = LANGUAGE_SPEC_RE.exec(spec);
|
|
1826
1855
|
if (!m) return null;
|
|
@@ -1877,13 +1906,16 @@ function deriveServiceName(image) {
|
|
|
1877
1906
|
const noTag = lastSegment.split("@")[0].split(":")[0];
|
|
1878
1907
|
return noTag.toLowerCase().replace(/[^a-z0-9_-]/g, "-");
|
|
1879
1908
|
}
|
|
1880
|
-
var DEFAULT_BASE_IMAGE, override, BASE_IMAGE, BUILTIN_LANGUAGES, LANGUAGE_CATALOG, LANGUAGE_SPEC_RE, SERVICE_CATALOG;
|
|
1909
|
+
var DEFAULT_BASE_IMAGE, override, BASE_IMAGE, RUNTIME_IMAGE_REPO, DEFAULT_RUNTIME_VERSION, MIN_RUNTIME_FOR_IDE_VOLUMES, BUILTIN_LANGUAGES, LANGUAGE_CATALOG, LANGUAGE_SPEC_RE, SERVICE_CATALOG;
|
|
1881
1910
|
var init_catalog = __esm({
|
|
1882
1911
|
"src/create/catalog.ts"() {
|
|
1883
1912
|
"use strict";
|
|
1884
1913
|
DEFAULT_BASE_IMAGE = "ghcr.io/getmonoceros/monoceros-runtime:1";
|
|
1885
1914
|
override = process.env.MONOCEROS_BASE_IMAGE_OVERRIDE?.trim();
|
|
1886
1915
|
BASE_IMAGE = override && override.length > 0 ? override : DEFAULT_BASE_IMAGE;
|
|
1916
|
+
RUNTIME_IMAGE_REPO = "ghcr.io/getmonoceros/monoceros-runtime";
|
|
1917
|
+
DEFAULT_RUNTIME_VERSION = "1.1.0";
|
|
1918
|
+
MIN_RUNTIME_FOR_IDE_VOLUMES = "1.1.0";
|
|
1887
1919
|
BUILTIN_LANGUAGES = /* @__PURE__ */ new Set(["node"]);
|
|
1888
1920
|
LANGUAGE_CATALOG = {
|
|
1889
1921
|
node: { id: "node", feature: "ghcr.io/devcontainers/features/node:1" },
|
|
@@ -1921,7 +1953,8 @@ var init_catalog = __esm({
|
|
|
1921
1953
|
// /var/lib/postgresql/data directly. See
|
|
1922
1954
|
// https://github.com/docker-library/postgres/pull/1259.
|
|
1923
1955
|
dataMount: "/var/lib/postgresql",
|
|
1924
|
-
defaultPort: 5432
|
|
1956
|
+
defaultPort: 5432,
|
|
1957
|
+
vscodeExtensions: ["cweijan.vscode-database-client2"]
|
|
1925
1958
|
},
|
|
1926
1959
|
mysql: {
|
|
1927
1960
|
id: "mysql",
|
|
@@ -1946,7 +1979,8 @@ var init_catalog = __esm({
|
|
|
1946
1979
|
retries: 5
|
|
1947
1980
|
},
|
|
1948
1981
|
dataMount: "/var/lib/mysql",
|
|
1949
|
-
defaultPort: 3306
|
|
1982
|
+
defaultPort: 3306,
|
|
1983
|
+
vscodeExtensions: ["cweijan.vscode-database-client2"]
|
|
1950
1984
|
},
|
|
1951
1985
|
redis: {
|
|
1952
1986
|
id: "redis",
|
|
@@ -1958,7 +1992,8 @@ var init_catalog = __esm({
|
|
|
1958
1992
|
retries: 5
|
|
1959
1993
|
},
|
|
1960
1994
|
dataMount: "/data",
|
|
1961
|
-
defaultPort: 6379
|
|
1995
|
+
defaultPort: 6379,
|
|
1996
|
+
vscodeExtensions: ["cweijan.vscode-database-client2"]
|
|
1962
1997
|
}
|
|
1963
1998
|
};
|
|
1964
1999
|
}
|
|
@@ -2134,6 +2169,7 @@ function normalizeOptions(opts) {
|
|
|
2134
2169
|
const ports = opts.ports ? [...new Set(opts.ports)] : void 0;
|
|
2135
2170
|
return {
|
|
2136
2171
|
name: opts.name,
|
|
2172
|
+
...opts.runtimeVersion !== void 0 ? { runtimeVersion: opts.runtimeVersion } : {},
|
|
2137
2173
|
languages,
|
|
2138
2174
|
services,
|
|
2139
2175
|
postgresUrl: opts.postgresUrl,
|
|
@@ -2246,6 +2282,18 @@ function filterFileEntries(raw) {
|
|
|
2246
2282
|
function isValidHomeSubpath(p) {
|
|
2247
2283
|
return p.length > 0 && !p.startsWith("/") && !p.includes("..") && HOME_SUBPATH_RE.test(p);
|
|
2248
2284
|
}
|
|
2285
|
+
function ideStateVolumes(name) {
|
|
2286
|
+
return [
|
|
2287
|
+
{
|
|
2288
|
+
volume: `monoceros-${name}-vscode-extensions`,
|
|
2289
|
+
target: "/home/node/.vscode-server/extensions"
|
|
2290
|
+
},
|
|
2291
|
+
{
|
|
2292
|
+
volume: `monoceros-${name}-vscode-userdata`,
|
|
2293
|
+
target: "/home/node/.vscode-server/data/User"
|
|
2294
|
+
}
|
|
2295
|
+
];
|
|
2296
|
+
}
|
|
2249
2297
|
function buildDevcontainerJson(opts, dockerMode = "rootful") {
|
|
2250
2298
|
const resolvedFeatures = resolveFeatures(opts);
|
|
2251
2299
|
const features = {};
|
|
@@ -2291,7 +2339,10 @@ function buildDevcontainerJson(opts, dockerMode = "rootful") {
|
|
|
2291
2339
|
...customizationsField ?? {}
|
|
2292
2340
|
};
|
|
2293
2341
|
}
|
|
2294
|
-
const
|
|
2342
|
+
const ideMounts = runtimeSupportsIdeVolumes(opts.runtimeVersion) ? ideStateVolumes(opts.name).map(
|
|
2343
|
+
(v) => `source=${v.volume},target=${v.target},type=volume`
|
|
2344
|
+
) : [];
|
|
2345
|
+
const mounts = [...homeMounts, ...ideMounts];
|
|
2295
2346
|
const mountsField = mounts.length > 0 ? { mounts } : {};
|
|
2296
2347
|
const workspaceMountField = {
|
|
2297
2348
|
workspaceMount: `source=\${localWorkspaceFolder},target=/workspaces/${opts.name},type=bind,consistency=cached`,
|
|
@@ -2304,7 +2355,7 @@ function buildDevcontainerJson(opts, dockerMode = "rootful") {
|
|
|
2304
2355
|
}
|
|
2305
2356
|
return {
|
|
2306
2357
|
name: opts.name,
|
|
2307
|
-
image:
|
|
2358
|
+
image: resolveRuntimeImage(opts.runtimeVersion),
|
|
2308
2359
|
remoteUser: "node",
|
|
2309
2360
|
...workspaceMountField,
|
|
2310
2361
|
...mountsField,
|
|
@@ -2331,8 +2382,9 @@ function buildComposeYaml(opts, dockerMode = "rootful") {
|
|
|
2331
2382
|
void dockerMode;
|
|
2332
2383
|
const hasPorts = (opts.ports?.length ?? 0) > 0;
|
|
2333
2384
|
const lines = ["services:"];
|
|
2385
|
+
const ideVolumes = runtimeSupportsIdeVolumes(opts.runtimeVersion) ? ideStateVolumes(opts.name) : [];
|
|
2334
2386
|
lines.push(" workspace:");
|
|
2335
|
-
lines.push(` image: ${
|
|
2387
|
+
lines.push(` image: ${resolveRuntimeImage(opts.runtimeVersion)}`);
|
|
2336
2388
|
lines.push(" command: 'sleep infinity'");
|
|
2337
2389
|
lines.push(" cap_add:");
|
|
2338
2390
|
lines.push(" - NET_ADMIN");
|
|
@@ -2355,6 +2407,9 @@ function buildComposeYaml(opts, dockerMode = "rootful") {
|
|
|
2355
2407
|
lines.push(` - ../home/${sub}:/home/node/${sub}`);
|
|
2356
2408
|
}
|
|
2357
2409
|
}
|
|
2410
|
+
for (const v of ideVolumes) {
|
|
2411
|
+
lines.push(` - ${v.volume}:${v.target}`);
|
|
2412
|
+
}
|
|
2358
2413
|
for (const svc of opts.services) {
|
|
2359
2414
|
lines.push(` ${svc.name}:`);
|
|
2360
2415
|
lines.push(` image: ${svc.image}`);
|
|
@@ -2393,6 +2448,13 @@ function buildComposeYaml(opts, dockerMode = "rootful") {
|
|
|
2393
2448
|
}
|
|
2394
2449
|
}
|
|
2395
2450
|
}
|
|
2451
|
+
if (ideVolumes.length > 0) {
|
|
2452
|
+
lines.push("volumes:");
|
|
2453
|
+
for (const v of ideVolumes) {
|
|
2454
|
+
lines.push(` ${v.volume}:`);
|
|
2455
|
+
lines.push(` name: ${v.volume}`);
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2396
2458
|
if (hasPorts) {
|
|
2397
2459
|
lines.push("networks:");
|
|
2398
2460
|
lines.push(" monoceros-proxy:");
|
|
@@ -2400,8 +2462,34 @@ function buildComposeYaml(opts, dockerMode = "rootful") {
|
|
|
2400
2462
|
}
|
|
2401
2463
|
return lines.join("\n") + "\n";
|
|
2402
2464
|
}
|
|
2465
|
+
function extractRepoHost(url) {
|
|
2466
|
+
const schemeMatch = /^[a-z][a-z0-9+.-]*:\/\/(?:[^@/]+@)?([^/:]+)/i.exec(url);
|
|
2467
|
+
if (schemeMatch) return schemeMatch[1].toLowerCase();
|
|
2468
|
+
const scpMatch = /^(?:[^@/]+@)?([^/:]+):/.exec(url);
|
|
2469
|
+
if (scpMatch) return scpMatch[1].toLowerCase();
|
|
2470
|
+
return null;
|
|
2471
|
+
}
|
|
2472
|
+
function computeExtensionRecommendations(opts) {
|
|
2473
|
+
const recs = /* @__PURE__ */ new Set();
|
|
2474
|
+
for (const svc of opts.services) {
|
|
2475
|
+
const catalogEntry = SERVICE_CATALOG[svc.name];
|
|
2476
|
+
for (const ext of catalogEntry?.vscodeExtensions ?? []) {
|
|
2477
|
+
recs.add(ext);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
for (const repo of opts.repos ?? []) {
|
|
2481
|
+
const host = extractRepoHost(repo.url);
|
|
2482
|
+
if (!host) continue;
|
|
2483
|
+
for (const ext of REPO_HOST_EXTENSIONS[host] ?? []) {
|
|
2484
|
+
recs.add(ext);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
return [...recs].sort((a, b) => a.localeCompare(b));
|
|
2488
|
+
}
|
|
2403
2489
|
function buildCodeWorkspaceJson(opts) {
|
|
2404
|
-
const folders = [
|
|
2490
|
+
const folders = [
|
|
2491
|
+
{ path: ".", name: WORKSPACE_ROOT_LABEL }
|
|
2492
|
+
];
|
|
2405
2493
|
const sortedRepos = [...opts.repos ?? []].sort(
|
|
2406
2494
|
(a, b) => a.path.localeCompare(b.path)
|
|
2407
2495
|
);
|
|
@@ -2409,7 +2497,11 @@ function buildCodeWorkspaceJson(opts) {
|
|
|
2409
2497
|
const label = repo.path.split("/").pop() ?? repo.path;
|
|
2410
2498
|
folders.push({ path: `projects/${repo.path}`, name: label });
|
|
2411
2499
|
}
|
|
2412
|
-
|
|
2500
|
+
const recommendations = computeExtensionRecommendations(opts);
|
|
2501
|
+
return {
|
|
2502
|
+
folders,
|
|
2503
|
+
...recommendations.length > 0 ? { extensions: { recommendations } } : {}
|
|
2504
|
+
};
|
|
2413
2505
|
}
|
|
2414
2506
|
function mergeCodeWorkspace(existing, generated) {
|
|
2415
2507
|
if (!existing || typeof existing !== "object" || Array.isArray(existing) || !Array.isArray(existing.folders)) {
|
|
@@ -2424,10 +2516,41 @@ function mergeCodeWorkspace(existing, generated) {
|
|
|
2424
2516
|
for (const g of generated.folders) {
|
|
2425
2517
|
if (!existingPaths.has(g.path)) merged.push(g);
|
|
2426
2518
|
}
|
|
2519
|
+
const generatedRootName = generated.folders.find((f) => f.path === ".")?.name;
|
|
2520
|
+
if (generatedRootName) {
|
|
2521
|
+
const idx = merged.findIndex(
|
|
2522
|
+
(f) => f && typeof f === "object" && f.path === "."
|
|
2523
|
+
);
|
|
2524
|
+
if (idx >= 0 && !merged[idx].name) {
|
|
2525
|
+
merged[idx] = { ...merged[idx], name: generatedRootName };
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2427
2528
|
const out = { ...existingObj };
|
|
2428
2529
|
out.folders = merged;
|
|
2530
|
+
const generatedRecs = generated.extensions?.recommendations ?? [];
|
|
2531
|
+
if (generatedRecs.length > 0) {
|
|
2532
|
+
const existingExtRaw = existingObj.extensions;
|
|
2533
|
+
const existingExt = existingExtRaw && typeof existingExtRaw === "object" && !Array.isArray(existingExtRaw) ? existingExtRaw : {};
|
|
2534
|
+
const existingRecs = Array.isArray(existingExt.recommendations) ? existingExt.recommendations.filter(
|
|
2535
|
+
(r) => typeof r === "string"
|
|
2536
|
+
) : [];
|
|
2537
|
+
const existingRecSet = new Set(existingRecs);
|
|
2538
|
+
const mergedRecs = [...existingRecs];
|
|
2539
|
+
for (const r of generatedRecs) {
|
|
2540
|
+
if (!existingRecSet.has(r)) mergedRecs.push(r);
|
|
2541
|
+
}
|
|
2542
|
+
out.extensions = { ...existingExt, recommendations: mergedRecs };
|
|
2543
|
+
}
|
|
2429
2544
|
return out;
|
|
2430
2545
|
}
|
|
2546
|
+
function mergeVscodeSettings(existing) {
|
|
2547
|
+
const base = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
|
|
2548
|
+
const existingExclude = base["files.exclude"] && typeof base["files.exclude"] === "object" && !Array.isArray(base["files.exclude"]) ? base["files.exclude"] : {};
|
|
2549
|
+
return {
|
|
2550
|
+
...base,
|
|
2551
|
+
"files.exclude": { ...existingExclude, ...ROOT_DENOISE_EXCLUDES }
|
|
2552
|
+
};
|
|
2553
|
+
}
|
|
2431
2554
|
function buildPostCreateScript(opts) {
|
|
2432
2555
|
const lines = [
|
|
2433
2556
|
"#!/usr/bin/env bash",
|
|
@@ -2528,6 +2651,14 @@ async function writePostCreateScript(devcontainerDir, opts) {
|
|
|
2528
2651
|
await fs7.writeFile(dest, buildPostCreateScript(opts));
|
|
2529
2652
|
await fs7.chmod(dest, 493);
|
|
2530
2653
|
}
|
|
2654
|
+
async function writeIfChanged(filePath, content) {
|
|
2655
|
+
try {
|
|
2656
|
+
if (await fs7.readFile(filePath, "utf8") === content) return false;
|
|
2657
|
+
} catch {
|
|
2658
|
+
}
|
|
2659
|
+
await fs7.writeFile(filePath, content);
|
|
2660
|
+
return true;
|
|
2661
|
+
}
|
|
2531
2662
|
async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
|
|
2532
2663
|
const dockerMode = scaffoldOpts.dockerMode ?? "rootful";
|
|
2533
2664
|
const devcontainerDir = path8.join(targetDir, ".devcontainer");
|
|
@@ -2562,7 +2693,7 @@ async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
|
|
|
2562
2693
|
"git-credentials*\ngitconfig\n"
|
|
2563
2694
|
);
|
|
2564
2695
|
const devcontainerJson = buildDevcontainerJson(opts, dockerMode);
|
|
2565
|
-
await
|
|
2696
|
+
await writeIfChanged(
|
|
2566
2697
|
path8.join(devcontainerDir, "devcontainer.json"),
|
|
2567
2698
|
JSON.stringify(devcontainerJson, null, 2) + "\n"
|
|
2568
2699
|
);
|
|
@@ -2592,7 +2723,7 @@ async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
|
|
|
2592
2723
|
await writePostCreateScript(devcontainerDir, opts);
|
|
2593
2724
|
const composePath = path8.join(devcontainerDir, "compose.yaml");
|
|
2594
2725
|
if (needsCompose(opts)) {
|
|
2595
|
-
await
|
|
2726
|
+
await writeIfChanged(composePath, buildComposeYaml(opts, dockerMode));
|
|
2596
2727
|
} else if (existsSync5(composePath)) {
|
|
2597
2728
|
await fs7.rm(composePath);
|
|
2598
2729
|
}
|
|
@@ -2607,8 +2738,21 @@ async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
|
|
|
2607
2738
|
const generated = buildCodeWorkspaceJson(opts);
|
|
2608
2739
|
const merged = mergeCodeWorkspace(existingWorkspace, generated);
|
|
2609
2740
|
await fs7.writeFile(workspacePath, JSON.stringify(merged, null, 2) + "\n");
|
|
2741
|
+
const vscodeDir = path8.join(targetDir, ".vscode");
|
|
2742
|
+
const settingsPath = path8.join(vscodeDir, "settings.json");
|
|
2743
|
+
let existingSettings;
|
|
2744
|
+
try {
|
|
2745
|
+
existingSettings = JSON.parse(await fs7.readFile(settingsPath, "utf8"));
|
|
2746
|
+
} catch {
|
|
2747
|
+
existingSettings = void 0;
|
|
2748
|
+
}
|
|
2749
|
+
await fs7.mkdir(vscodeDir, { recursive: true });
|
|
2750
|
+
await fs7.writeFile(
|
|
2751
|
+
settingsPath,
|
|
2752
|
+
JSON.stringify(mergeVscodeSettings(existingSettings), null, 2) + "\n"
|
|
2753
|
+
);
|
|
2610
2754
|
}
|
|
2611
|
-
var APT_PACKAGE_NAME_RE2, FEATURE_REF_RE2, INSTALL_URL_RE2, REPO_URL_RE2, REPO_PATH_RE2, HOME_SUBPATH_RE;
|
|
2755
|
+
var APT_PACKAGE_NAME_RE2, FEATURE_REF_RE2, INSTALL_URL_RE2, REPO_URL_RE2, REPO_PATH_RE2, HOME_SUBPATH_RE, WORKSPACE_ROOT_LABEL, REPO_HOST_EXTENSIONS, ROOT_DENOISE_EXCLUDES;
|
|
2612
2756
|
var init_scaffold = __esm({
|
|
2613
2757
|
"src/create/scaffold.ts"() {
|
|
2614
2758
|
"use strict";
|
|
@@ -2621,6 +2765,23 @@ var init_scaffold = __esm({
|
|
|
2621
2765
|
REPO_URL_RE2 = /^[A-Za-z0-9@:/+_~.#=&?-]+$/;
|
|
2622
2766
|
REPO_PATH_RE2 = /^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/;
|
|
2623
2767
|
HOME_SUBPATH_RE = /^[A-Za-z0-9._-]+(\/[A-Za-z0-9._-]+)*$/;
|
|
2768
|
+
WORKSPACE_ROOT_LABEL = "\u{1F984} Monoceros";
|
|
2769
|
+
REPO_HOST_EXTENSIONS = {
|
|
2770
|
+
"github.com": [
|
|
2771
|
+
"github.vscode-pull-request-github",
|
|
2772
|
+
"GitHub.vscode-github-actions"
|
|
2773
|
+
],
|
|
2774
|
+
"gitlab.com": ["GitLab.gitlab-workflow"]
|
|
2775
|
+
};
|
|
2776
|
+
ROOT_DENOISE_EXCLUDES = {
|
|
2777
|
+
".devcontainer": true,
|
|
2778
|
+
".monoceros": true,
|
|
2779
|
+
".vscode": true,
|
|
2780
|
+
".gitignore": true,
|
|
2781
|
+
data: true,
|
|
2782
|
+
projects: true,
|
|
2783
|
+
"*.code-workspace": true
|
|
2784
|
+
};
|
|
2624
2785
|
}
|
|
2625
2786
|
});
|
|
2626
2787
|
|
|
@@ -4097,7 +4258,8 @@ function buildStateFile(opts) {
|
|
|
4097
4258
|
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
4098
4259
|
origin: opts.origin,
|
|
4099
4260
|
monocerosCliVersion: opts.cliVersion,
|
|
4100
|
-
materializedAt: (opts.now ?? /* @__PURE__ */ new Date()).toISOString()
|
|
4261
|
+
materializedAt: (opts.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
4262
|
+
...opts.runtimeImage ? { runtimeImage: opts.runtimeImage } : {}
|
|
4101
4263
|
};
|
|
4102
4264
|
}
|
|
4103
4265
|
function stateFilePath(targetDir) {
|
|
@@ -4138,6 +4300,7 @@ function solutionConfigToCreateOptions(config, featureDefaults = {}) {
|
|
|
4138
4300
|
}
|
|
4139
4301
|
const result = {
|
|
4140
4302
|
name: config.name,
|
|
4303
|
+
...config.runtimeVersion !== void 0 ? { runtimeVersion: config.runtimeVersion } : {},
|
|
4141
4304
|
languages: [...config.languages],
|
|
4142
4305
|
// Normalize every services[] entry (curated string or explicit
|
|
4143
4306
|
// object) to the canonical ResolvedService shape. `${VAR}` values
|
|
@@ -5726,6 +5889,11 @@ ${sectionLine(label)}
|
|
|
5726
5889
|
await assertSafeTargetDir(targetDir, opts.name);
|
|
5727
5890
|
section("Configuration");
|
|
5728
5891
|
const parsed = await readConfig(ymlPath);
|
|
5892
|
+
if (!parsed.config.runtimeVersion) {
|
|
5893
|
+
throw new Error(
|
|
5894
|
+
`No runtime pinned for '${opts.name}': the yml has no 'runtimeVersion'. Pin it with \`monoceros upgrade ${opts.name} <version>\` (or add \`runtimeVersion: <version>\` to the yml), then re-apply.`
|
|
5895
|
+
);
|
|
5896
|
+
}
|
|
5729
5897
|
const globalConfig = await readMonocerosConfig({ monocerosHome: home });
|
|
5730
5898
|
warnOnDeprecatedFeatureRefs(parsed.config.features, globalConfig, logger);
|
|
5731
5899
|
const envPath = containerEnvPath(opts.name, home);
|
|
@@ -5837,6 +6005,7 @@ Fix the value in the env file (or the yml).`
|
|
|
5837
6005
|
buildStateFile({
|
|
5838
6006
|
origin: opts.name,
|
|
5839
6007
|
cliVersion: opts.cliVersion,
|
|
6008
|
+
runtimeImage: resolveRuntimeImage(createOpts.runtimeVersion),
|
|
5840
6009
|
...opts.now ? { now: opts.now } : {}
|
|
5841
6010
|
})
|
|
5842
6011
|
);
|
|
@@ -6059,6 +6228,7 @@ var init_apply = __esm({
|
|
|
6059
6228
|
init_schema();
|
|
6060
6229
|
init_state();
|
|
6061
6230
|
init_transform();
|
|
6231
|
+
init_catalog();
|
|
6062
6232
|
init_scaffold();
|
|
6063
6233
|
init_format();
|
|
6064
6234
|
init_ref();
|
|
@@ -6085,7 +6255,7 @@ var CLI_VERSION;
|
|
|
6085
6255
|
var init_version = __esm({
|
|
6086
6256
|
"src/version.ts"() {
|
|
6087
6257
|
"use strict";
|
|
6088
|
-
CLI_VERSION = true ? "1.
|
|
6258
|
+
CLI_VERSION = true ? "1.16.0" : "dev";
|
|
6089
6259
|
}
|
|
6090
6260
|
});
|
|
6091
6261
|
|
|
@@ -6510,6 +6680,7 @@ var init_resolve = __esm({
|
|
|
6510
6680
|
"stop",
|
|
6511
6681
|
"status",
|
|
6512
6682
|
"apply",
|
|
6683
|
+
"upgrade",
|
|
6513
6684
|
"remove",
|
|
6514
6685
|
"restore",
|
|
6515
6686
|
"add-service",
|
|
@@ -6554,6 +6725,12 @@ var init_resolve = __esm({
|
|
|
6554
6725
|
positionals: [containerName],
|
|
6555
6726
|
flags: { "--yes": { type: "boolean", aliases: ["-y"] } }
|
|
6556
6727
|
},
|
|
6728
|
+
upgrade: {
|
|
6729
|
+
// First positional is a container name; the second is a version
|
|
6730
|
+
// string (no suggestions — versions live in the registry).
|
|
6731
|
+
positionals: [containerName, () => []],
|
|
6732
|
+
flags: { "--list": { type: "boolean" } }
|
|
6733
|
+
},
|
|
6557
6734
|
remove: {
|
|
6558
6735
|
positionals: [containerName],
|
|
6559
6736
|
flags: {
|
|
@@ -6704,6 +6881,11 @@ function generateComposedYml(name, composed, lookupManifest, repoUrls = [], port
|
|
|
6704
6881
|
lines.push("");
|
|
6705
6882
|
lines.push("schemaVersion: 1");
|
|
6706
6883
|
lines.push(`name: ${name}`);
|
|
6884
|
+
lines.push(
|
|
6885
|
+
"# Pinned runtime image version. Reused on every apply; change it with"
|
|
6886
|
+
);
|
|
6887
|
+
lines.push("# `monoceros upgrade <name> [version]`.");
|
|
6888
|
+
lines.push(`runtimeVersion: ${DEFAULT_RUNTIME_VERSION}`);
|
|
6707
6889
|
lines.push("");
|
|
6708
6890
|
if (composed.languages.length > 0) {
|
|
6709
6891
|
pushSectionHeader(
|
|
@@ -6802,6 +6984,11 @@ function generateDocumentedYml(name, catalog, lookupManifest, repoUrls = [], por
|
|
|
6802
6984
|
lines.push("");
|
|
6803
6985
|
lines.push("schemaVersion: 1");
|
|
6804
6986
|
lines.push(`name: ${name}`);
|
|
6987
|
+
lines.push(
|
|
6988
|
+
"# Pinned runtime image version. Reused on every apply; change it with"
|
|
6989
|
+
);
|
|
6990
|
+
lines.push("# `monoceros upgrade <name> [version]`.");
|
|
6991
|
+
lines.push(`runtimeVersion: ${DEFAULT_RUNTIME_VERSION}`);
|
|
6805
6992
|
lines.push("");
|
|
6806
6993
|
if (byCategory.language.length > 0) {
|
|
6807
6994
|
pushSectionHeader(
|
|
@@ -7802,6 +7989,8 @@ async function runRemove(opts) {
|
|
|
7802
7989
|
logger,
|
|
7803
7990
|
exec: dockerExec
|
|
7804
7991
|
});
|
|
7992
|
+
const ideVolumes = ideStateVolumes(opts.name).map((v) => v.volume);
|
|
7993
|
+
await dockerExec(["volume", "rm", "-f", ...ideVolumes]);
|
|
7805
7994
|
let backupPath = null;
|
|
7806
7995
|
if (!opts.noBackup && (hasYml || hasContainer)) {
|
|
7807
7996
|
const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -7896,6 +8085,7 @@ var init_remove = __esm({
|
|
|
7896
8085
|
init_paths();
|
|
7897
8086
|
init_schema();
|
|
7898
8087
|
init_compose();
|
|
8088
|
+
init_scaffold();
|
|
7899
8089
|
init_proxy();
|
|
7900
8090
|
init_dynamic();
|
|
7901
8091
|
}
|
|
@@ -9063,12 +9253,165 @@ var init_tunnel = __esm({
|
|
|
9063
9253
|
}
|
|
9064
9254
|
});
|
|
9065
9255
|
|
|
9256
|
+
// src/upgrade/index.ts
|
|
9257
|
+
import { existsSync as existsSync14, promises as fs17 } from "fs";
|
|
9258
|
+
import { consola as consola34 } from "consola";
|
|
9259
|
+
async function fetchRuntimeVersions() {
|
|
9260
|
+
const tokenUrl = `https://ghcr.io/token?service=ghcr.io&scope=repository:${RUNTIME_REPO}:pull`;
|
|
9261
|
+
const tokenRes = await fetch(tokenUrl);
|
|
9262
|
+
if (!tokenRes.ok) {
|
|
9263
|
+
throw new Error(
|
|
9264
|
+
`Could not get a GHCR token (HTTP ${tokenRes.status}). Pass an explicit version to skip the lookup: \`monoceros upgrade <name> <version>\`.`
|
|
9265
|
+
);
|
|
9266
|
+
}
|
|
9267
|
+
const { token } = await tokenRes.json();
|
|
9268
|
+
if (!token) throw new Error("GHCR token response contained no token.");
|
|
9269
|
+
const tagsRes = await fetch(`https://ghcr.io/v2/${RUNTIME_REPO}/tags/list`, {
|
|
9270
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
9271
|
+
});
|
|
9272
|
+
if (!tagsRes.ok) {
|
|
9273
|
+
throw new Error(
|
|
9274
|
+
`Could not list runtime versions (HTTP ${tagsRes.status}). Pass an explicit version: \`monoceros upgrade <name> <version>\`.`
|
|
9275
|
+
);
|
|
9276
|
+
}
|
|
9277
|
+
const { tags } = await tagsRes.json();
|
|
9278
|
+
return (tags ?? []).filter((t) => VERSION_RE.test(t)).sort(compareRuntimeVersions);
|
|
9279
|
+
}
|
|
9280
|
+
function setRuntimeVersion(yml, version) {
|
|
9281
|
+
if (/^runtimeVersion:.*$/m.test(yml)) {
|
|
9282
|
+
return yml.replace(/^runtimeVersion:.*$/m, `runtimeVersion: ${version}`);
|
|
9283
|
+
}
|
|
9284
|
+
if (/^schemaVersion:.*$/m.test(yml)) {
|
|
9285
|
+
return yml.replace(
|
|
9286
|
+
/^(schemaVersion:.*)$/m,
|
|
9287
|
+
`$1
|
|
9288
|
+
runtimeVersion: ${version}`
|
|
9289
|
+
);
|
|
9290
|
+
}
|
|
9291
|
+
return `runtimeVersion: ${version}
|
|
9292
|
+
${yml}`;
|
|
9293
|
+
}
|
|
9294
|
+
async function runUpgrade(opts) {
|
|
9295
|
+
const home = opts.monocerosHome ?? monocerosHome();
|
|
9296
|
+
const logger = opts.logger ?? consola34;
|
|
9297
|
+
const fetchVersions = opts.fetchVersions ?? fetchRuntimeVersions;
|
|
9298
|
+
if (opts.list) {
|
|
9299
|
+
const versions = await fetchVersions();
|
|
9300
|
+
if (versions.length === 0) {
|
|
9301
|
+
logger.info("No published runtime versions found.");
|
|
9302
|
+
return 0;
|
|
9303
|
+
}
|
|
9304
|
+
logger.info(
|
|
9305
|
+
`Available runtime versions (latest last):
|
|
9306
|
+
${versions.join("\n ")}`
|
|
9307
|
+
);
|
|
9308
|
+
return 0;
|
|
9309
|
+
}
|
|
9310
|
+
if (!opts.name) {
|
|
9311
|
+
throw new Error(
|
|
9312
|
+
"Usage: `monoceros upgrade <name> [version]` (or `monoceros upgrade --list`)."
|
|
9313
|
+
);
|
|
9314
|
+
}
|
|
9315
|
+
const ymlPath = containerConfigPath(opts.name, home);
|
|
9316
|
+
if (!existsSync14(ymlPath)) {
|
|
9317
|
+
throw new Error(
|
|
9318
|
+
`No such config: ${ymlPath}. Run \`monoceros init <template> ${opts.name}\` first.`
|
|
9319
|
+
);
|
|
9320
|
+
}
|
|
9321
|
+
let target = opts.version;
|
|
9322
|
+
if (target !== void 0 && !VERSION_RE.test(target)) {
|
|
9323
|
+
throw new Error(
|
|
9324
|
+
`Invalid version ${JSON.stringify(target)}. Expected an exact version like '1.1.0'.`
|
|
9325
|
+
);
|
|
9326
|
+
}
|
|
9327
|
+
if (target === void 0) {
|
|
9328
|
+
const versions = await fetchVersions();
|
|
9329
|
+
target = versions[versions.length - 1];
|
|
9330
|
+
if (!target) {
|
|
9331
|
+
throw new Error("Could not determine the latest runtime version.");
|
|
9332
|
+
}
|
|
9333
|
+
logger.info(`Latest published runtime version: ${target}`);
|
|
9334
|
+
}
|
|
9335
|
+
const raw = await fs17.readFile(ymlPath, "utf8");
|
|
9336
|
+
const updated = setRuntimeVersion(raw, target);
|
|
9337
|
+
if (updated === raw) {
|
|
9338
|
+
logger.info(`'${opts.name}' is already pinned to runtime ${target}.`);
|
|
9339
|
+
} else {
|
|
9340
|
+
await fs17.writeFile(ymlPath, updated);
|
|
9341
|
+
logger.success(`Pinned '${opts.name}' to runtime ${target}. Re-applying\u2026`);
|
|
9342
|
+
}
|
|
9343
|
+
const apply = opts.applyRunner ?? runApply;
|
|
9344
|
+
const result = await apply({
|
|
9345
|
+
name: opts.name,
|
|
9346
|
+
cliVersion: opts.cliVersion,
|
|
9347
|
+
monocerosHome: home
|
|
9348
|
+
});
|
|
9349
|
+
return result.containerExitCode;
|
|
9350
|
+
}
|
|
9351
|
+
var RUNTIME_REPO, VERSION_RE;
|
|
9352
|
+
var init_upgrade = __esm({
|
|
9353
|
+
"src/upgrade/index.ts"() {
|
|
9354
|
+
"use strict";
|
|
9355
|
+
init_paths();
|
|
9356
|
+
init_catalog();
|
|
9357
|
+
init_apply();
|
|
9358
|
+
RUNTIME_REPO = "getmonoceros/monoceros-runtime";
|
|
9359
|
+
VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
9360
|
+
}
|
|
9361
|
+
});
|
|
9362
|
+
|
|
9363
|
+
// src/commands/upgrade.ts
|
|
9364
|
+
import { defineCommand as defineCommand30 } from "citty";
|
|
9365
|
+
var upgradeCommand;
|
|
9366
|
+
var init_upgrade2 = __esm({
|
|
9367
|
+
"src/commands/upgrade.ts"() {
|
|
9368
|
+
"use strict";
|
|
9369
|
+
init_upgrade();
|
|
9370
|
+
init_version();
|
|
9371
|
+
init_dispatch();
|
|
9372
|
+
upgradeCommand = defineCommand30({
|
|
9373
|
+
meta: {
|
|
9374
|
+
name: "upgrade",
|
|
9375
|
+
group: "lifecycle",
|
|
9376
|
+
description: "Pin a container to a newer runtime image version and re-apply. `monoceros upgrade <name>` pins to the latest published version; `monoceros upgrade <name> <version>` pins to an exact one; `monoceros upgrade --list` lists available versions."
|
|
9377
|
+
},
|
|
9378
|
+
args: {
|
|
9379
|
+
name: {
|
|
9380
|
+
type: "positional",
|
|
9381
|
+
description: "Config name. Resolves to $MONOCEROS_HOME/container-configs/<name>.yml.",
|
|
9382
|
+
required: false
|
|
9383
|
+
},
|
|
9384
|
+
version: {
|
|
9385
|
+
type: "positional",
|
|
9386
|
+
description: "Exact runtime version to pin (e.g. 1.1.0). Omit to use the latest published version.",
|
|
9387
|
+
required: false
|
|
9388
|
+
},
|
|
9389
|
+
list: {
|
|
9390
|
+
type: "boolean",
|
|
9391
|
+
description: "List available runtime versions and exit, changing nothing.",
|
|
9392
|
+
default: false
|
|
9393
|
+
}
|
|
9394
|
+
},
|
|
9395
|
+
run({ args }) {
|
|
9396
|
+
return dispatch(
|
|
9397
|
+
() => runUpgrade({
|
|
9398
|
+
...args.name ? { name: args.name } : {},
|
|
9399
|
+
...args.version ? { version: args.version } : {},
|
|
9400
|
+
list: args.list,
|
|
9401
|
+
cliVersion: CLI_VERSION
|
|
9402
|
+
})
|
|
9403
|
+
);
|
|
9404
|
+
}
|
|
9405
|
+
});
|
|
9406
|
+
}
|
|
9407
|
+
});
|
|
9408
|
+
|
|
9066
9409
|
// src/main.ts
|
|
9067
9410
|
var main_exports = {};
|
|
9068
9411
|
__export(main_exports, {
|
|
9069
9412
|
main: () => main
|
|
9070
9413
|
});
|
|
9071
|
-
import { defineCommand as
|
|
9414
|
+
import { defineCommand as defineCommand31 } from "citty";
|
|
9072
9415
|
var main;
|
|
9073
9416
|
var init_main = __esm({
|
|
9074
9417
|
"src/main.ts"() {
|
|
@@ -9102,8 +9445,9 @@ var init_main = __esm({
|
|
|
9102
9445
|
init_status();
|
|
9103
9446
|
init_stop();
|
|
9104
9447
|
init_tunnel();
|
|
9448
|
+
init_upgrade2();
|
|
9105
9449
|
init_version();
|
|
9106
|
-
main =
|
|
9450
|
+
main = defineCommand31({
|
|
9107
9451
|
meta: {
|
|
9108
9452
|
name: "monoceros",
|
|
9109
9453
|
version: CLI_VERSION,
|
|
@@ -9119,6 +9463,7 @@ var init_main = __esm({
|
|
|
9119
9463
|
stop: stopCommand,
|
|
9120
9464
|
status: statusCommand,
|
|
9121
9465
|
apply: applyCommand,
|
|
9466
|
+
upgrade: upgradeCommand,
|
|
9122
9467
|
remove: removeCommand,
|
|
9123
9468
|
restore: restoreCommand,
|
|
9124
9469
|
"add-service": addServiceCommand,
|