@getmonoceros/workbench 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +860 -534
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -251,25 +251,25 @@ function detectHelpRequest(argv, main2) {
|
|
|
251
251
|
const separatorIdx = argv.indexOf("--");
|
|
252
252
|
if (helpIdx === -1) return null;
|
|
253
253
|
if (separatorIdx !== -1 && separatorIdx < helpIdx) return null;
|
|
254
|
-
const
|
|
254
|
+
const path17 = [];
|
|
255
255
|
const tokens = argv.slice(
|
|
256
256
|
0,
|
|
257
257
|
separatorIdx === -1 ? argv.length : separatorIdx
|
|
258
258
|
);
|
|
259
259
|
let cursor = main2;
|
|
260
260
|
const mainName = (main2.meta ?? {}).name ?? "monoceros";
|
|
261
|
-
|
|
261
|
+
path17.push(mainName);
|
|
262
262
|
for (const tok of tokens) {
|
|
263
263
|
if (tok.startsWith("-")) continue;
|
|
264
264
|
const subs = cursor.subCommands ?? {};
|
|
265
265
|
if (tok in subs) {
|
|
266
266
|
cursor = subs[tok];
|
|
267
|
-
|
|
267
|
+
path17.push(tok);
|
|
268
268
|
continue;
|
|
269
269
|
}
|
|
270
270
|
break;
|
|
271
271
|
}
|
|
272
|
-
return { path:
|
|
272
|
+
return { path: path17, cmd: cursor };
|
|
273
273
|
}
|
|
274
274
|
async function maybeRenderHelp(argv, main2) {
|
|
275
275
|
const hit = detectHelpRequest(argv, main2);
|
|
@@ -301,17 +301,17 @@ function getInnerArgs() {
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
// src/main.ts
|
|
304
|
-
import { defineCommand as
|
|
304
|
+
import { defineCommand as defineCommand29 } from "citty";
|
|
305
305
|
|
|
306
306
|
// src/commands/add-apt-packages.ts
|
|
307
307
|
import { defineCommand } from "citty";
|
|
308
308
|
import { consola as consola2 } from "consola";
|
|
309
309
|
|
|
310
310
|
// src/modify/index.ts
|
|
311
|
-
import { promises as
|
|
311
|
+
import { promises as fs8 } from "fs";
|
|
312
312
|
import { consola } from "consola";
|
|
313
313
|
import { createPatch } from "diff";
|
|
314
|
-
import
|
|
314
|
+
import path7 from "path";
|
|
315
315
|
|
|
316
316
|
// src/config/io.ts
|
|
317
317
|
import { promises as fs } from "fs";
|
|
@@ -1161,10 +1161,175 @@ ${existing}` : leakedComment;
|
|
|
1161
1161
|
}
|
|
1162
1162
|
}
|
|
1163
1163
|
|
|
1164
|
+
// src/init/components.ts
|
|
1165
|
+
import { existsSync as existsSync2, promises as fs4 } from "fs";
|
|
1166
|
+
import path3 from "path";
|
|
1167
|
+
import { z as z3 } from "zod";
|
|
1168
|
+
import { parse as parseYaml } from "yaml";
|
|
1169
|
+
var CategorySchema = z3.enum(["language", "service", "feature"]);
|
|
1170
|
+
var FeatureContributionSchema = z3.object({
|
|
1171
|
+
ref: z3.string().regex(REGEX.featureRef),
|
|
1172
|
+
options: z3.record(z3.string(), FeatureOptionValueSchema).optional()
|
|
1173
|
+
});
|
|
1174
|
+
var ComponentFileSchema = z3.object({
|
|
1175
|
+
displayName: z3.string().min(1),
|
|
1176
|
+
description: z3.string().min(1),
|
|
1177
|
+
category: CategorySchema,
|
|
1178
|
+
contributes: z3.object({
|
|
1179
|
+
languages: z3.array(z3.string().min(1)).optional(),
|
|
1180
|
+
services: z3.array(z3.string().min(1)).optional(),
|
|
1181
|
+
features: z3.array(FeatureContributionSchema).optional()
|
|
1182
|
+
})
|
|
1183
|
+
}).superRefine((data, ctx) => {
|
|
1184
|
+
const c = data.contributes;
|
|
1185
|
+
const filled = [
|
|
1186
|
+
c.languages && c.languages.length > 0 ? "languages" : null,
|
|
1187
|
+
c.services && c.services.length > 0 ? "services" : null,
|
|
1188
|
+
c.features && c.features.length > 0 ? "features" : null
|
|
1189
|
+
].filter((x) => x !== null);
|
|
1190
|
+
if (filled.length === 0) {
|
|
1191
|
+
ctx.addIssue({
|
|
1192
|
+
code: z3.ZodIssueCode.custom,
|
|
1193
|
+
message: "contributes must set at least one of languages/services/features"
|
|
1194
|
+
});
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
if (filled.length > 1) {
|
|
1198
|
+
ctx.addIssue({
|
|
1199
|
+
code: z3.ZodIssueCode.custom,
|
|
1200
|
+
message: `contributes must set exactly one of languages/services/features, got: ${filled.join(", ")}`
|
|
1201
|
+
});
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
const expected = data.category === "language" ? "languages" : data.category === "service" ? "services" : "features";
|
|
1205
|
+
if (filled[0] !== expected) {
|
|
1206
|
+
ctx.addIssue({
|
|
1207
|
+
code: z3.ZodIssueCode.custom,
|
|
1208
|
+
message: `category '${data.category}' requires contributes.${expected}, got contributes.${filled[0]}`
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
async function loadComponentCatalog(rootDir = componentsDir()) {
|
|
1213
|
+
if (!existsSync2(rootDir)) {
|
|
1214
|
+
return /* @__PURE__ */ new Map();
|
|
1215
|
+
}
|
|
1216
|
+
const out = /* @__PURE__ */ new Map();
|
|
1217
|
+
await walk(rootDir, rootDir, out);
|
|
1218
|
+
return out;
|
|
1219
|
+
}
|
|
1220
|
+
async function walk(baseDir, currentDir, out) {
|
|
1221
|
+
const entries = await fs4.readdir(currentDir, { withFileTypes: true });
|
|
1222
|
+
for (const entry2 of entries) {
|
|
1223
|
+
const full = path3.join(currentDir, entry2.name);
|
|
1224
|
+
if (entry2.isDirectory()) {
|
|
1225
|
+
await walk(baseDir, full, out);
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
if (!entry2.isFile() || !entry2.name.endsWith(".yml")) continue;
|
|
1229
|
+
const relative = path3.relative(baseDir, full);
|
|
1230
|
+
const name = relative.replace(/\.yml$/, "").split(path3.sep).join("/");
|
|
1231
|
+
const text = await fs4.readFile(full, "utf8");
|
|
1232
|
+
let raw;
|
|
1233
|
+
try {
|
|
1234
|
+
raw = parseYaml(text);
|
|
1235
|
+
} catch (err) {
|
|
1236
|
+
throw new Error(
|
|
1237
|
+
`Failed to parse component ${name} (${full}): ${err.message}`
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
const parsed = ComponentFileSchema.safeParse(raw);
|
|
1241
|
+
if (!parsed.success) {
|
|
1242
|
+
const issues = parsed.error.issues.map((issue) => {
|
|
1243
|
+
const where = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
1244
|
+
return ` - ${where}: ${issue.message}`;
|
|
1245
|
+
}).join("\n");
|
|
1246
|
+
throw new Error(`Invalid component ${name} (${full}):
|
|
1247
|
+
${issues}`);
|
|
1248
|
+
}
|
|
1249
|
+
out.set(name, { name, sourcePath: full, file: parsed.data });
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
function mergeComponents(resolved) {
|
|
1253
|
+
const languages = [];
|
|
1254
|
+
const services = [];
|
|
1255
|
+
const featureByRef = /* @__PURE__ */ new Map();
|
|
1256
|
+
for (const entry2 of resolved) {
|
|
1257
|
+
const c = isResolvedComponent(entry2) ? entry2.component : entry2;
|
|
1258
|
+
const version = isResolvedComponent(entry2) ? entry2.version : void 0;
|
|
1259
|
+
const ct = c.file.contributes;
|
|
1260
|
+
for (const lang of ct.languages ?? []) {
|
|
1261
|
+
const value = version !== void 0 ? `${lang}:${version}` : lang;
|
|
1262
|
+
if (!languages.includes(value)) languages.push(value);
|
|
1263
|
+
}
|
|
1264
|
+
for (const svc of ct.services ?? []) {
|
|
1265
|
+
if (!services.includes(svc)) services.push(svc);
|
|
1266
|
+
}
|
|
1267
|
+
for (const f of ct.features ?? []) {
|
|
1268
|
+
const existing = featureByRef.get(f.ref);
|
|
1269
|
+
if (!existing) {
|
|
1270
|
+
featureByRef.set(f.ref, {
|
|
1271
|
+
ref: f.ref,
|
|
1272
|
+
options: { ...f.options ?? {} }
|
|
1273
|
+
});
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
existing.options = mergeFeatureOptions(existing.options, f.options ?? {});
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return {
|
|
1280
|
+
languages,
|
|
1281
|
+
services,
|
|
1282
|
+
features: [...featureByRef.values()]
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
function isResolvedComponent(x) {
|
|
1286
|
+
return "component" in x;
|
|
1287
|
+
}
|
|
1288
|
+
function mergeFeatureOptions(a, b) {
|
|
1289
|
+
const result = { ...a };
|
|
1290
|
+
for (const [key, valueB] of Object.entries(b)) {
|
|
1291
|
+
const valueA = result[key];
|
|
1292
|
+
if (typeof valueA === "boolean" && typeof valueB === "boolean") {
|
|
1293
|
+
result[key] = valueA || valueB;
|
|
1294
|
+
continue;
|
|
1295
|
+
}
|
|
1296
|
+
result[key] = valueB;
|
|
1297
|
+
}
|
|
1298
|
+
return result;
|
|
1299
|
+
}
|
|
1300
|
+
function resolveComponents(catalog, names) {
|
|
1301
|
+
const unknown = [];
|
|
1302
|
+
const out = [];
|
|
1303
|
+
for (const raw of names) {
|
|
1304
|
+
const colon = raw.indexOf(":");
|
|
1305
|
+
const name = colon === -1 ? raw : raw.slice(0, colon);
|
|
1306
|
+
const version = colon === -1 ? void 0 : raw.slice(colon + 1);
|
|
1307
|
+
const c = catalog.get(name);
|
|
1308
|
+
if (!c) {
|
|
1309
|
+
unknown.push(raw);
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
if (version !== void 0 && c.file.category !== "language") {
|
|
1313
|
+
throw new Error(
|
|
1314
|
+
`Component '${name}' is a ${c.file.category}, not a language \u2014 a ':${version}' suffix has no meaning here.`
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
out.push({ component: c, ...version !== void 0 ? { version } : {} });
|
|
1318
|
+
}
|
|
1319
|
+
if (unknown.length > 0) {
|
|
1320
|
+
const available = [...catalog.keys()].sort();
|
|
1321
|
+
throw new Error(
|
|
1322
|
+
`Unknown component${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}.
|
|
1323
|
+
Available: ${available.join(", ")}.`
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
return out;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1164
1329
|
// src/proxy/index.ts
|
|
1165
1330
|
import { spawn as spawn3 } from "child_process";
|
|
1166
|
-
import { promises as
|
|
1167
|
-
import
|
|
1331
|
+
import { promises as fs5 } from "fs";
|
|
1332
|
+
import path4 from "path";
|
|
1168
1333
|
var PROXY_CONTAINER_NAME = "monoceros-proxy";
|
|
1169
1334
|
var PROXY_NETWORK_NAME = "monoceros-proxy";
|
|
1170
1335
|
var TRAEFIK_IMAGE = "traefik:v3.3";
|
|
@@ -1190,12 +1355,12 @@ var defaultDockerExec = (args) => {
|
|
|
1190
1355
|
};
|
|
1191
1356
|
var realDocker = defaultDockerExec;
|
|
1192
1357
|
function proxyDynamicDir(home) {
|
|
1193
|
-
return
|
|
1358
|
+
return path4.join(home ?? monocerosHome(), "traefik", "dynamic");
|
|
1194
1359
|
}
|
|
1195
1360
|
async function ensureProxy(opts = {}) {
|
|
1196
1361
|
const docker = opts.docker ?? realDocker;
|
|
1197
1362
|
const dyn = proxyDynamicDir(opts.monocerosHome);
|
|
1198
|
-
await
|
|
1363
|
+
await fs5.mkdir(dyn, { recursive: true });
|
|
1199
1364
|
const netInspect = await docker(["network", "inspect", PROXY_NETWORK_NAME]);
|
|
1200
1365
|
if (netInspect.exitCode !== 0) {
|
|
1201
1366
|
const create = await docker(["network", "create", PROXY_NETWORK_NAME]);
|
|
@@ -1281,8 +1446,8 @@ async function maybeStopProxy(opts = {}) {
|
|
|
1281
1446
|
}
|
|
1282
1447
|
|
|
1283
1448
|
// src/proxy/dynamic.ts
|
|
1284
|
-
import { promises as
|
|
1285
|
-
import
|
|
1449
|
+
import { promises as fs6 } from "fs";
|
|
1450
|
+
import path5 from "path";
|
|
1286
1451
|
async function writeDynamicConfig(name, ports, opts = {}) {
|
|
1287
1452
|
if (ports.length === 0) {
|
|
1288
1453
|
throw new Error(
|
|
@@ -1290,14 +1455,14 @@ async function writeDynamicConfig(name, ports, opts = {}) {
|
|
|
1290
1455
|
);
|
|
1291
1456
|
}
|
|
1292
1457
|
const dir = proxyDynamicDir(opts.monocerosHome);
|
|
1293
|
-
await
|
|
1294
|
-
const file =
|
|
1295
|
-
await
|
|
1458
|
+
await fs6.mkdir(dir, { recursive: true });
|
|
1459
|
+
const file = path5.join(dir, `${name}.yml`);
|
|
1460
|
+
await fs6.writeFile(file, renderDynamicConfig(name, ports), "utf8");
|
|
1296
1461
|
return file;
|
|
1297
1462
|
}
|
|
1298
1463
|
async function removeDynamicConfig(name, opts = {}) {
|
|
1299
|
-
const file =
|
|
1300
|
-
await
|
|
1464
|
+
const file = path5.join(proxyDynamicDir(opts.monocerosHome), `${name}.yml`);
|
|
1465
|
+
await fs6.rm(file, { force: true });
|
|
1301
1466
|
}
|
|
1302
1467
|
function renderDynamicConfig(name, ports) {
|
|
1303
1468
|
const lines = [];
|
|
@@ -1498,8 +1663,8 @@ function knownServices() {
|
|
|
1498
1663
|
}
|
|
1499
1664
|
|
|
1500
1665
|
// src/create/scaffold.ts
|
|
1501
|
-
import { existsSync as
|
|
1502
|
-
import
|
|
1666
|
+
import { existsSync as existsSync3, readFileSync, promises as fs7 } from "fs";
|
|
1667
|
+
import path6 from "path";
|
|
1503
1668
|
|
|
1504
1669
|
// src/util/ref.ts
|
|
1505
1670
|
var FEATURE_NAME_CHARSET = "[a-z0-9._-]+";
|
|
@@ -1670,8 +1835,8 @@ function resolveFeatures(opts) {
|
|
|
1670
1835
|
if (match) {
|
|
1671
1836
|
const name = match.name;
|
|
1672
1837
|
const checkout = workbenchCheckoutRoot();
|
|
1673
|
-
const localSourceDir = checkout ?
|
|
1674
|
-
if (localSourceDir &&
|
|
1838
|
+
const localSourceDir = checkout ? path6.join(checkout, "images", "features", name) : null;
|
|
1839
|
+
if (localSourceDir && existsSync3(localSourceDir)) {
|
|
1675
1840
|
const { paths, files } = readPersistentHomeEntries(localSourceDir);
|
|
1676
1841
|
resolved.push({
|
|
1677
1842
|
devcontainerKey: `./features/${name}`,
|
|
@@ -1695,7 +1860,7 @@ function resolveFeatures(opts) {
|
|
|
1695
1860
|
return resolved;
|
|
1696
1861
|
}
|
|
1697
1862
|
function readPersistentHomeEntries(localSourceDir) {
|
|
1698
|
-
const manifestPath =
|
|
1863
|
+
const manifestPath = path6.join(localSourceDir, "devcontainer-feature.json");
|
|
1699
1864
|
try {
|
|
1700
1865
|
const text = readFileSync(manifestPath, "utf8");
|
|
1701
1866
|
const parsed = JSON.parse(text);
|
|
@@ -1980,86 +2145,86 @@ function buildPostCreateScript(opts) {
|
|
|
1980
2145
|
return lines.join("\n") + "\n";
|
|
1981
2146
|
}
|
|
1982
2147
|
async function writePostCreateScript(devcontainerDir, opts) {
|
|
1983
|
-
const dest =
|
|
1984
|
-
await
|
|
1985
|
-
await
|
|
2148
|
+
const dest = path6.join(devcontainerDir, "post-create.sh");
|
|
2149
|
+
await fs7.writeFile(dest, buildPostCreateScript(opts));
|
|
2150
|
+
await fs7.chmod(dest, 493);
|
|
1986
2151
|
}
|
|
1987
2152
|
async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
|
|
1988
2153
|
const dockerMode = scaffoldOpts.dockerMode ?? "rootful";
|
|
1989
|
-
const devcontainerDir =
|
|
1990
|
-
const monocerosDir =
|
|
1991
|
-
const projectsDir =
|
|
1992
|
-
const homeDir =
|
|
1993
|
-
const dataDir =
|
|
1994
|
-
await
|
|
1995
|
-
await
|
|
1996
|
-
await
|
|
1997
|
-
await
|
|
2154
|
+
const devcontainerDir = path6.join(targetDir, ".devcontainer");
|
|
2155
|
+
const monocerosDir = path6.join(targetDir, ".monoceros");
|
|
2156
|
+
const projectsDir = path6.join(targetDir, "projects");
|
|
2157
|
+
const homeDir = path6.join(targetDir, "home");
|
|
2158
|
+
const dataDir = path6.join(targetDir, "data");
|
|
2159
|
+
await fs7.mkdir(devcontainerDir, { recursive: true });
|
|
2160
|
+
await fs7.mkdir(monocerosDir, { recursive: true });
|
|
2161
|
+
await fs7.mkdir(projectsDir, { recursive: true });
|
|
2162
|
+
await fs7.mkdir(homeDir, { recursive: true });
|
|
1998
2163
|
if (needsCompose(opts)) {
|
|
1999
|
-
await
|
|
2164
|
+
await fs7.mkdir(dataDir, { recursive: true });
|
|
2000
2165
|
for (const svcId of opts.services) {
|
|
2001
2166
|
const def = SERVICE_CATALOG[svcId];
|
|
2002
2167
|
if (def?.dataMount) {
|
|
2003
|
-
await
|
|
2168
|
+
await fs7.mkdir(path6.join(dataDir, def.id), { recursive: true });
|
|
2004
2169
|
}
|
|
2005
2170
|
}
|
|
2006
2171
|
}
|
|
2007
|
-
const containerGitignore =
|
|
2008
|
-
await
|
|
2009
|
-
const gitkeep =
|
|
2010
|
-
if (!
|
|
2011
|
-
await
|
|
2172
|
+
const containerGitignore = path6.join(targetDir, ".gitignore");
|
|
2173
|
+
await fs7.writeFile(containerGitignore, "/home/\n/.monoceros/\n/data/\n");
|
|
2174
|
+
const gitkeep = path6.join(projectsDir, ".gitkeep");
|
|
2175
|
+
if (!existsSync3(gitkeep)) {
|
|
2176
|
+
await fs7.writeFile(gitkeep, "");
|
|
2012
2177
|
}
|
|
2013
|
-
await
|
|
2014
|
-
|
|
2178
|
+
await fs7.writeFile(
|
|
2179
|
+
path6.join(monocerosDir, ".gitignore"),
|
|
2015
2180
|
"git-credentials*\ngitconfig\n"
|
|
2016
2181
|
);
|
|
2017
2182
|
const devcontainerJson = buildDevcontainerJson(opts, dockerMode);
|
|
2018
|
-
await
|
|
2019
|
-
|
|
2183
|
+
await fs7.writeFile(
|
|
2184
|
+
path6.join(devcontainerDir, "devcontainer.json"),
|
|
2020
2185
|
JSON.stringify(devcontainerJson, null, 2) + "\n"
|
|
2021
2186
|
);
|
|
2022
|
-
const featuresDir =
|
|
2023
|
-
if (
|
|
2024
|
-
await
|
|
2187
|
+
const featuresDir = path6.join(devcontainerDir, "features");
|
|
2188
|
+
if (existsSync3(featuresDir)) {
|
|
2189
|
+
await fs7.rm(featuresDir, { recursive: true, force: true });
|
|
2025
2190
|
}
|
|
2026
2191
|
const resolvedFeatures = resolveFeatures(opts);
|
|
2027
2192
|
for (const f of resolvedFeatures) {
|
|
2028
2193
|
if (!f.localSourceDir || !f.localName) continue;
|
|
2029
|
-
const dest =
|
|
2030
|
-
await
|
|
2031
|
-
await
|
|
2194
|
+
const dest = path6.join(featuresDir, f.localName);
|
|
2195
|
+
await fs7.mkdir(dest, { recursive: true });
|
|
2196
|
+
await fs7.cp(f.localSourceDir, dest, { recursive: true });
|
|
2032
2197
|
}
|
|
2033
2198
|
for (const f of resolvedFeatures) {
|
|
2034
2199
|
for (const sub of f.persistentHomePaths) {
|
|
2035
|
-
await
|
|
2200
|
+
await fs7.mkdir(path6.join(homeDir, sub), { recursive: true });
|
|
2036
2201
|
}
|
|
2037
2202
|
for (const entry2 of f.persistentHomeFiles) {
|
|
2038
|
-
const filePath =
|
|
2039
|
-
await
|
|
2040
|
-
if (!
|
|
2041
|
-
await
|
|
2203
|
+
const filePath = path6.join(homeDir, entry2.path);
|
|
2204
|
+
await fs7.mkdir(path6.dirname(filePath), { recursive: true });
|
|
2205
|
+
if (!existsSync3(filePath)) {
|
|
2206
|
+
await fs7.writeFile(filePath, entry2.initialContent);
|
|
2042
2207
|
}
|
|
2043
2208
|
}
|
|
2044
2209
|
}
|
|
2045
2210
|
await writePostCreateScript(devcontainerDir, opts);
|
|
2046
|
-
const composePath =
|
|
2211
|
+
const composePath = path6.join(devcontainerDir, "compose.yaml");
|
|
2047
2212
|
if (needsCompose(opts)) {
|
|
2048
|
-
await
|
|
2049
|
-
} else if (
|
|
2050
|
-
await
|
|
2213
|
+
await fs7.writeFile(composePath, buildComposeYaml(opts, dockerMode));
|
|
2214
|
+
} else if (existsSync3(composePath)) {
|
|
2215
|
+
await fs7.rm(composePath);
|
|
2051
2216
|
}
|
|
2052
|
-
const workspacePath =
|
|
2217
|
+
const workspacePath = path6.join(targetDir, `${opts.name}.code-workspace`);
|
|
2053
2218
|
let existingWorkspace;
|
|
2054
2219
|
try {
|
|
2055
|
-
const raw = await
|
|
2220
|
+
const raw = await fs7.readFile(workspacePath, "utf8");
|
|
2056
2221
|
existingWorkspace = JSON.parse(raw);
|
|
2057
2222
|
} catch {
|
|
2058
2223
|
existingWorkspace = void 0;
|
|
2059
2224
|
}
|
|
2060
2225
|
const generated = buildCodeWorkspaceJson(opts);
|
|
2061
2226
|
const merged = mergeCodeWorkspace(existingWorkspace, generated);
|
|
2062
|
-
await
|
|
2227
|
+
await fs7.writeFile(workspacePath, JSON.stringify(merged, null, 2) + "\n");
|
|
2063
2228
|
}
|
|
2064
2229
|
|
|
2065
2230
|
// src/modify/yml.ts
|
|
@@ -2417,8 +2582,8 @@ function removeRepoFromDoc(doc, urlOrPath) {
|
|
|
2417
2582
|
if (!isMap2(item)) return false;
|
|
2418
2583
|
const url = item.get("url");
|
|
2419
2584
|
if (url === urlOrPath) return true;
|
|
2420
|
-
const
|
|
2421
|
-
const effectivePath = typeof
|
|
2585
|
+
const path17 = item.get("path");
|
|
2586
|
+
const effectivePath = typeof path17 === "string" ? path17 : typeof url === "string" ? deriveRepoName(url) : void 0;
|
|
2422
2587
|
return effectivePath === urlOrPath;
|
|
2423
2588
|
});
|
|
2424
2589
|
if (idx < 0) return false;
|
|
@@ -2468,7 +2633,7 @@ async function runAddRepo(input) {
|
|
|
2468
2633
|
"Missing repo URL. Usage: monoceros add-repo <containername> <url>."
|
|
2469
2634
|
);
|
|
2470
2635
|
}
|
|
2471
|
-
const
|
|
2636
|
+
const path17 = (input.path ?? deriveRepoName(url)).trim();
|
|
2472
2637
|
const hasName = typeof input.gitName === "string" && input.gitName.trim().length > 0;
|
|
2473
2638
|
const hasEmail = typeof input.gitEmail === "string" && input.gitEmail.trim().length > 0;
|
|
2474
2639
|
if (hasName !== hasEmail) {
|
|
@@ -2497,7 +2662,7 @@ async function runAddRepo(input) {
|
|
|
2497
2662
|
const providerToWrite = !canonical && explicitProvider ? explicitProvider : void 0;
|
|
2498
2663
|
const entry2 = {
|
|
2499
2664
|
url,
|
|
2500
|
-
path:
|
|
2665
|
+
path: path17,
|
|
2501
2666
|
...hasName && hasEmail ? {
|
|
2502
2667
|
gitUser: {
|
|
2503
2668
|
name: input.gitName.trim(),
|
|
@@ -2573,14 +2738,14 @@ async function tryCloneInRunningContainer(input, entry2) {
|
|
|
2573
2738
|
);
|
|
2574
2739
|
return;
|
|
2575
2740
|
}
|
|
2576
|
-
const
|
|
2741
|
+
const containerName2 = input.name;
|
|
2577
2742
|
const targetRel = `projects/${entry2.path}`;
|
|
2578
2743
|
const parentRel = entry2.path.includes("/") ? `projects/${entry2.path.split("/").slice(0, -1).join("/")}` : "projects";
|
|
2579
|
-
const credentialsFile = `/workspaces/${
|
|
2744
|
+
const credentialsFile = `/workspaces/${containerName2}/.monoceros/git-credentials`;
|
|
2580
2745
|
const credentialHelper = `store --file=${credentialsFile}`;
|
|
2581
2746
|
const script = [
|
|
2582
2747
|
`set -eu`,
|
|
2583
|
-
`cd /workspaces/${
|
|
2748
|
+
`cd /workspaces/${containerName2}`,
|
|
2584
2749
|
`if [ -d ${shquote(targetRel)} ]; then`,
|
|
2585
2750
|
` echo "[add-repo] ${targetRel} already exists \u2014 skipping clone."`,
|
|
2586
2751
|
` exit 0`,
|
|
@@ -2611,9 +2776,9 @@ async function tryCloneInRunningContainer(input, entry2) {
|
|
|
2611
2776
|
return;
|
|
2612
2777
|
}
|
|
2613
2778
|
logger.info(
|
|
2614
|
-
`Cloned ${entry2.url} into /workspaces/${
|
|
2779
|
+
`Cloned ${entry2.url} into /workspaces/${containerName2}/${targetRel} inside the running container.`
|
|
2615
2780
|
);
|
|
2616
|
-
void
|
|
2781
|
+
void path7;
|
|
2617
2782
|
}
|
|
2618
2783
|
function shquote(value) {
|
|
2619
2784
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -2678,14 +2843,56 @@ function normalizePorts(raw) {
|
|
|
2678
2843
|
}
|
|
2679
2844
|
return result;
|
|
2680
2845
|
}
|
|
2681
|
-
function runAddFeature(input) {
|
|
2682
|
-
const
|
|
2683
|
-
if (
|
|
2846
|
+
async function runAddFeature(input) {
|
|
2847
|
+
const raw = input.ref.trim();
|
|
2848
|
+
if (raw.length === 0) {
|
|
2849
|
+
throw new Error(
|
|
2850
|
+
"Missing feature ref. Usage: monoceros add-feature <containername> <feature>."
|
|
2851
|
+
);
|
|
2852
|
+
}
|
|
2853
|
+
const resolved = await resolveFeatureRefOrShortname(raw);
|
|
2854
|
+
const merged = {
|
|
2855
|
+
...resolved.defaultOptions,
|
|
2856
|
+
...input.options ?? {}
|
|
2857
|
+
};
|
|
2858
|
+
return mutate(input, (doc) => addFeatureToDoc(doc, resolved.ref, merged));
|
|
2859
|
+
}
|
|
2860
|
+
async function resolveFeatureRefOrShortname(input) {
|
|
2861
|
+
if (REGEX.featureRef.test(input)) {
|
|
2862
|
+
return { ref: input, defaultOptions: {} };
|
|
2863
|
+
}
|
|
2864
|
+
const catalog = await loadComponentCatalog();
|
|
2865
|
+
const component = catalog.get(input);
|
|
2866
|
+
if (!component) {
|
|
2867
|
+
const featureShorts = [...catalog.values()].filter((c) => c.file.category === "feature").map((c) => c.name).sort();
|
|
2868
|
+
const knownList = featureShorts.length > 0 ? featureShorts.join(", ") : "(none)";
|
|
2869
|
+
throw new Error(
|
|
2870
|
+
`Unknown feature: ${JSON.stringify(input)}. Pass either a catalog short-name (one of: ${knownList}) or a full OCI ref like 'ghcr.io/getmonoceros/monoceros-features/<name>:<tag>'.`
|
|
2871
|
+
);
|
|
2872
|
+
}
|
|
2873
|
+
if (component.file.category !== "feature") {
|
|
2874
|
+
throw new Error(
|
|
2875
|
+
`'${input}' is a ${component.file.category}, not a feature. Use 'monoceros add-${component.file.category} <name> ${input}' instead.`
|
|
2876
|
+
);
|
|
2877
|
+
}
|
|
2878
|
+
const features = component.file.contributes.features ?? [];
|
|
2879
|
+
if (features.length === 0) {
|
|
2684
2880
|
throw new Error(
|
|
2685
|
-
|
|
2881
|
+
`Catalog entry '${input}' contributes no feature ref \u2014 bug or stale catalog.`
|
|
2686
2882
|
);
|
|
2687
2883
|
}
|
|
2688
|
-
|
|
2884
|
+
if (features.length > 1) {
|
|
2885
|
+
throw new Error(
|
|
2886
|
+
`'${input}' bundles multiple feature refs (${features.map((f) => f.ref).join(
|
|
2887
|
+
", "
|
|
2888
|
+
)}). add-feature handles one at a time \u2014 pass the OCI ref directly.`
|
|
2889
|
+
);
|
|
2890
|
+
}
|
|
2891
|
+
const [first] = features;
|
|
2892
|
+
return {
|
|
2893
|
+
ref: first.ref,
|
|
2894
|
+
defaultOptions: { ...first.options ?? {} }
|
|
2895
|
+
};
|
|
2689
2896
|
}
|
|
2690
2897
|
function runRemoveLanguage(input) {
|
|
2691
2898
|
return mutate(input, (doc) => removeLanguageFromDoc(doc, input.language));
|
|
@@ -2752,7 +2959,7 @@ async function mutate(opts, apply) {
|
|
|
2752
2959
|
const logger = opts.logger ?? defaultLogger();
|
|
2753
2960
|
let oldText;
|
|
2754
2961
|
try {
|
|
2755
|
-
oldText = await
|
|
2962
|
+
oldText = await fs8.readFile(ymlPath, "utf8");
|
|
2756
2963
|
} catch {
|
|
2757
2964
|
throw new Error(
|
|
2758
2965
|
`No such config: ${ymlPath}. Run \`monoceros init <template> ${opts.name}\` first.`
|
|
@@ -2776,7 +2983,7 @@ async function mutate(opts, apply) {
|
|
|
2776
2983
|
return { status: "aborted" };
|
|
2777
2984
|
}
|
|
2778
2985
|
}
|
|
2779
|
-
await
|
|
2986
|
+
await fs8.writeFile(ymlPath, newText, "utf8");
|
|
2780
2987
|
logger.success(`Updated ${ymlPath}.`);
|
|
2781
2988
|
logger.info(
|
|
2782
2989
|
`Run \`monoceros apply ${opts.name}\` to rebuild the dev-container and pick up the change.`
|
|
@@ -2912,7 +3119,7 @@ var addFeatureCommand = defineCommand2({
|
|
|
2912
3119
|
},
|
|
2913
3120
|
ref: {
|
|
2914
3121
|
type: "positional",
|
|
2915
|
-
description: "
|
|
3122
|
+
description: "Feature to add. Either a Monoceros catalog short-name (e.g. `atlassian`, `atlassian/twg`, `claude` \u2014 see `monoceros list-components`) or a full OCI feature ref (e.g. `ghcr.io/devcontainers/features/docker-in-docker:2`). The short-name brings its catalog-defined default options; `-- key=value` overrides them.",
|
|
2916
3123
|
required: true
|
|
2917
3124
|
},
|
|
2918
3125
|
yes: {
|
|
@@ -3245,12 +3452,12 @@ var addServiceCommand = defineCommand7({
|
|
|
3245
3452
|
import { defineCommand as defineCommand8 } from "citty";
|
|
3246
3453
|
|
|
3247
3454
|
// src/apply/index.ts
|
|
3248
|
-
import { existsSync as
|
|
3455
|
+
import { existsSync as existsSync5, promises as fs11 } from "fs";
|
|
3249
3456
|
import { consola as consola11 } from "consola";
|
|
3250
3457
|
|
|
3251
3458
|
// src/config/state.ts
|
|
3252
|
-
import { promises as
|
|
3253
|
-
import
|
|
3459
|
+
import { promises as fs9 } from "fs";
|
|
3460
|
+
import path8 from "path";
|
|
3254
3461
|
function buildStateFile(opts) {
|
|
3255
3462
|
return {
|
|
3256
3463
|
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
@@ -3260,20 +3467,20 @@ function buildStateFile(opts) {
|
|
|
3260
3467
|
};
|
|
3261
3468
|
}
|
|
3262
3469
|
function stateFilePath(targetDir) {
|
|
3263
|
-
return
|
|
3470
|
+
return path8.join(targetDir, ".monoceros", "state.json");
|
|
3264
3471
|
}
|
|
3265
3472
|
async function readStateFile(targetDir) {
|
|
3266
3473
|
try {
|
|
3267
|
-
const content = await
|
|
3474
|
+
const content = await fs9.readFile(stateFilePath(targetDir), "utf8");
|
|
3268
3475
|
return JSON.parse(content);
|
|
3269
3476
|
} catch {
|
|
3270
3477
|
return void 0;
|
|
3271
3478
|
}
|
|
3272
3479
|
}
|
|
3273
3480
|
async function writeStateFile(targetDir, state) {
|
|
3274
|
-
const monocerosDir =
|
|
3275
|
-
await
|
|
3276
|
-
await
|
|
3481
|
+
const monocerosDir = path8.join(targetDir, ".monoceros");
|
|
3482
|
+
await fs9.mkdir(monocerosDir, { recursive: true });
|
|
3483
|
+
await fs9.writeFile(
|
|
3277
3484
|
stateFilePath(targetDir),
|
|
3278
3485
|
JSON.stringify(state, null, 2) + "\n"
|
|
3279
3486
|
);
|
|
@@ -3343,8 +3550,8 @@ function solutionConfigToCreateOptions(config, featureDefaults = {}) {
|
|
|
3343
3550
|
|
|
3344
3551
|
// src/devcontainer/compose.ts
|
|
3345
3552
|
import { spawn as spawn5 } from "child_process";
|
|
3346
|
-
import { existsSync as
|
|
3347
|
-
import
|
|
3553
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3554
|
+
import path10 from "path";
|
|
3348
3555
|
import { consola as consola9 } from "consola";
|
|
3349
3556
|
|
|
3350
3557
|
// src/util/mask-secrets.ts
|
|
@@ -3407,7 +3614,7 @@ function createSecretMaskStream() {
|
|
|
3407
3614
|
import { spawn as spawn4 } from "child_process";
|
|
3408
3615
|
import { readFileSync as readFileSync2 } from "fs";
|
|
3409
3616
|
import { createRequire } from "module";
|
|
3410
|
-
import
|
|
3617
|
+
import path9 from "path";
|
|
3411
3618
|
var require_ = createRequire(import.meta.url);
|
|
3412
3619
|
var cachedBinaryPath = null;
|
|
3413
3620
|
function devcontainerCliPath() {
|
|
@@ -3418,7 +3625,7 @@ function devcontainerCliPath() {
|
|
|
3418
3625
|
if (!binEntry) {
|
|
3419
3626
|
throw new Error("Could not resolve @devcontainers/cli bin entry.");
|
|
3420
3627
|
}
|
|
3421
|
-
cachedBinaryPath =
|
|
3628
|
+
cachedBinaryPath = path9.resolve(path9.dirname(pkgJsonPath), binEntry);
|
|
3422
3629
|
return cachedBinaryPath;
|
|
3423
3630
|
}
|
|
3424
3631
|
var spawnDevcontainer = (args, cwd, options = {}) => {
|
|
@@ -3490,16 +3697,16 @@ var spawnBash = (args, cwd) => {
|
|
|
3490
3697
|
});
|
|
3491
3698
|
};
|
|
3492
3699
|
function composeProjectName(root) {
|
|
3493
|
-
return `${
|
|
3700
|
+
return `${path10.basename(root)}_devcontainer`;
|
|
3494
3701
|
}
|
|
3495
3702
|
function resolveCompose(root) {
|
|
3496
|
-
if (!
|
|
3703
|
+
if (!existsSync4(path10.join(root, ".devcontainer"))) {
|
|
3497
3704
|
throw new Error(
|
|
3498
3705
|
`No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
|
|
3499
3706
|
);
|
|
3500
3707
|
}
|
|
3501
|
-
const composeFile =
|
|
3502
|
-
if (!
|
|
3708
|
+
const composeFile = path10.join(root, ".devcontainer", "compose.yaml");
|
|
3709
|
+
if (!existsSync4(composeFile)) {
|
|
3503
3710
|
throw new Error(
|
|
3504
3711
|
`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.`
|
|
3505
3712
|
);
|
|
@@ -3793,8 +4000,8 @@ function formatRootlessNotSupportedError() {
|
|
|
3793
4000
|
|
|
3794
4001
|
// src/devcontainer/identity.ts
|
|
3795
4002
|
import { spawn as spawn8 } from "child_process";
|
|
3796
|
-
import { promises as
|
|
3797
|
-
import
|
|
4003
|
+
import { promises as fs10 } from "fs";
|
|
4004
|
+
import path11 from "path";
|
|
3798
4005
|
import { consola as consola10 } from "consola";
|
|
3799
4006
|
var realGitConfigGet = (key) => {
|
|
3800
4007
|
return new Promise((resolve, reject) => {
|
|
@@ -3908,8 +4115,8 @@ async function resolveIdentityWithPrompt(options = {}) {
|
|
|
3908
4115
|
};
|
|
3909
4116
|
}
|
|
3910
4117
|
async function collectGitIdentity(devContainerRoot, options = {}) {
|
|
3911
|
-
const gitconfigDir =
|
|
3912
|
-
const gitconfigPath =
|
|
4118
|
+
const gitconfigDir = path11.join(devContainerRoot, ".monoceros");
|
|
4119
|
+
const gitconfigPath = path11.join(gitconfigDir, "gitconfig");
|
|
3913
4120
|
const logger = options.logger ?? { info: () => {
|
|
3914
4121
|
}, warn: () => {
|
|
3915
4122
|
} };
|
|
@@ -3922,8 +4129,8 @@ async function collectGitIdentity(devContainerRoot, options = {}) {
|
|
|
3922
4129
|
const lines = ["[user]"];
|
|
3923
4130
|
if (resolved.name !== void 0) lines.push(` name = ${resolved.name}`);
|
|
3924
4131
|
if (resolved.email !== void 0) lines.push(` email = ${resolved.email}`);
|
|
3925
|
-
await
|
|
3926
|
-
await
|
|
4132
|
+
await fs10.mkdir(gitconfigDir, { recursive: true });
|
|
4133
|
+
await fs10.writeFile(gitconfigPath, lines.join("\n") + "\n");
|
|
3927
4134
|
return {
|
|
3928
4135
|
...resolved.name !== void 0 ? { name: resolved.name } : {},
|
|
3929
4136
|
...resolved.email !== void 0 ? { email: resolved.email } : {},
|
|
@@ -3966,7 +4173,7 @@ async function readKeyFromHost(spawnFn, key, logger) {
|
|
|
3966
4173
|
}
|
|
3967
4174
|
async function readExistingGitconfig(filePath) {
|
|
3968
4175
|
try {
|
|
3969
|
-
const content = await
|
|
4176
|
+
const content = await fs10.readFile(filePath, "utf8");
|
|
3970
4177
|
const result = {};
|
|
3971
4178
|
const nameMatch = /^\s*name\s*=\s*(.+?)\s*$/m.exec(content);
|
|
3972
4179
|
const emailMatch = /^\s*email\s*=\s*(.+?)\s*$/m.exec(content);
|
|
@@ -3999,7 +4206,7 @@ ${sectionLine(label)}
|
|
|
3999
4206
|
);
|
|
4000
4207
|
}
|
|
4001
4208
|
const ymlPath = containerConfigPath(opts.name, home);
|
|
4002
|
-
if (!
|
|
4209
|
+
if (!existsSync5(ymlPath)) {
|
|
4003
4210
|
throw new Error(
|
|
4004
4211
|
`No such config: ${ymlPath}. Run \`monoceros init <template> ${opts.name}\` first.`
|
|
4005
4212
|
);
|
|
@@ -4070,7 +4277,7 @@ ${sectionLine(label)}
|
|
|
4070
4277
|
if (dockerMode === "rootless") {
|
|
4071
4278
|
throw new Error(formatRootlessNotSupportedError());
|
|
4072
4279
|
}
|
|
4073
|
-
await
|
|
4280
|
+
await fs11.mkdir(targetDir, { recursive: true });
|
|
4074
4281
|
await writeScaffold(createOpts, targetDir, { dockerMode });
|
|
4075
4282
|
await writeStateFile(
|
|
4076
4283
|
targetDir,
|
|
@@ -4128,8 +4335,8 @@ ${sectionLine(label)}
|
|
|
4128
4335
|
return { targetDir, configPath: ymlPath, containerExitCode: exitCode };
|
|
4129
4336
|
}
|
|
4130
4337
|
async function assertSafeTargetDir(targetDir, expectedOrigin) {
|
|
4131
|
-
if (!
|
|
4132
|
-
const entries = await
|
|
4338
|
+
if (!existsSync5(targetDir)) return;
|
|
4339
|
+
const entries = await fs11.readdir(targetDir);
|
|
4133
4340
|
if (entries.length === 0) return;
|
|
4134
4341
|
const state = await readStateFile(targetDir);
|
|
4135
4342
|
if (state) {
|
|
@@ -4199,7 +4406,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
4199
4406
|
}
|
|
4200
4407
|
if (wantContainer) {
|
|
4201
4408
|
try {
|
|
4202
|
-
const text = await
|
|
4409
|
+
const text = await fs11.readFile(ymlPath, "utf8");
|
|
4203
4410
|
const parsed = parseConfig(text, ymlPath);
|
|
4204
4411
|
const changed = setContainerGitUserInDoc(parsed.doc, {
|
|
4205
4412
|
name: prompted.name,
|
|
@@ -4207,7 +4414,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
4207
4414
|
});
|
|
4208
4415
|
if (changed) {
|
|
4209
4416
|
const out = stringifyConfig(parsed.doc);
|
|
4210
|
-
await
|
|
4417
|
+
await fs11.writeFile(ymlPath, out, "utf8");
|
|
4211
4418
|
logger.info(
|
|
4212
4419
|
`Saved identity in this container \u2014 wrote git.user into ${prettyPath(ymlPath)}.`
|
|
4213
4420
|
);
|
|
@@ -4221,7 +4428,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
4221
4428
|
}
|
|
4222
4429
|
|
|
4223
4430
|
// src/version.ts
|
|
4224
|
-
var CLI_VERSION = true ? "1.
|
|
4431
|
+
var CLI_VERSION = true ? "1.9.0" : "dev";
|
|
4225
4432
|
|
|
4226
4433
|
// src/commands/_dispatch.ts
|
|
4227
4434
|
import { consola as consola12 } from "consola";
|
|
@@ -4262,96 +4469,24 @@ var applyCommand = defineCommand8({
|
|
|
4262
4469
|
|
|
4263
4470
|
// src/commands/completion.ts
|
|
4264
4471
|
import { defineCommand as defineCommand9 } from "citty";
|
|
4265
|
-
var ALL_COMMANDS = [
|
|
4266
|
-
"init",
|
|
4267
|
-
"list-components",
|
|
4268
|
-
"shell",
|
|
4269
|
-
"run",
|
|
4270
|
-
"logs",
|
|
4271
|
-
"start",
|
|
4272
|
-
"stop",
|
|
4273
|
-
"status",
|
|
4274
|
-
"apply",
|
|
4275
|
-
"remove",
|
|
4276
|
-
"restore",
|
|
4277
|
-
"add-service",
|
|
4278
|
-
"add-language",
|
|
4279
|
-
"add-apt-packages",
|
|
4280
|
-
"add-feature",
|
|
4281
|
-
"add-from-url",
|
|
4282
|
-
"add-repo",
|
|
4283
|
-
"add-port",
|
|
4284
|
-
"remove-service",
|
|
4285
|
-
"remove-language",
|
|
4286
|
-
"remove-apt-packages",
|
|
4287
|
-
"remove-feature",
|
|
4288
|
-
"remove-from-url",
|
|
4289
|
-
"remove-repo",
|
|
4290
|
-
"remove-port",
|
|
4291
|
-
"port",
|
|
4292
|
-
"completion"
|
|
4293
|
-
];
|
|
4294
|
-
var COMMANDS_WITH_CONTAINER_ARG = [
|
|
4295
|
-
"shell",
|
|
4296
|
-
"run",
|
|
4297
|
-
"logs",
|
|
4298
|
-
"start",
|
|
4299
|
-
"stop",
|
|
4300
|
-
"status",
|
|
4301
|
-
"apply",
|
|
4302
|
-
"remove",
|
|
4303
|
-
"add-service",
|
|
4304
|
-
"add-language",
|
|
4305
|
-
"add-apt-packages",
|
|
4306
|
-
"add-feature",
|
|
4307
|
-
"add-from-url",
|
|
4308
|
-
"add-repo",
|
|
4309
|
-
"add-port",
|
|
4310
|
-
"remove-service",
|
|
4311
|
-
"remove-language",
|
|
4312
|
-
"remove-apt-packages",
|
|
4313
|
-
"remove-feature",
|
|
4314
|
-
"remove-from-url",
|
|
4315
|
-
"remove-repo",
|
|
4316
|
-
"remove-port",
|
|
4317
|
-
"port"
|
|
4318
|
-
];
|
|
4319
4472
|
var SHELLS = ["bash", "zsh", "pwsh"];
|
|
4320
4473
|
function renderCompletionScript(shell) {
|
|
4321
|
-
const commands = ALL_COMMANDS.join(" ");
|
|
4322
|
-
const containerCommandsRegex = COMMANDS_WITH_CONTAINER_ARG.join("|");
|
|
4323
4474
|
if (shell === "bash") {
|
|
4324
4475
|
return [
|
|
4325
4476
|
"# bash completion for monoceros",
|
|
4326
4477
|
"# install: source this file from .bashrc, e.g.",
|
|
4327
4478
|
"# monoceros completion bash > ~/.bash_completion.d/monoceros",
|
|
4328
4479
|
'# echo "source ~/.bash_completion.d/monoceros" >> ~/.bashrc',
|
|
4480
|
+
"#",
|
|
4481
|
+
"# The work is done by `monoceros __complete --line --point`; this",
|
|
4482
|
+
"# shell wrapper only forwards the cursor view.",
|
|
4329
4483
|
"",
|
|
4330
4484
|
"_monoceros() {",
|
|
4331
|
-
" local
|
|
4332
|
-
|
|
4333
|
-
"",
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
" return",
|
|
4337
|
-
" fi",
|
|
4338
|
-
"",
|
|
4339
|
-
' cmd="${COMP_WORDS[1]}"',
|
|
4340
|
-
" if [[ $COMP_CWORD -eq 2 ]]; then",
|
|
4341
|
-
' case "$cmd" in',
|
|
4342
|
-
` ${containerCommandsRegex})`,
|
|
4343
|
-
' home="${MONOCEROS_HOME:-$HOME/.monoceros}"',
|
|
4344
|
-
' configs_dir="$home/container-configs"',
|
|
4345
|
-
' if [[ -d "$configs_dir" ]]; then',
|
|
4346
|
-
` names=$(cd "$configs_dir" && ls *.yml 2>/dev/null | sed 's/\\.yml$//')`,
|
|
4347
|
-
' COMPREPLY=( $(compgen -W "$names" -- "$cur") )',
|
|
4348
|
-
" fi",
|
|
4349
|
-
" ;;",
|
|
4350
|
-
" completion)",
|
|
4351
|
-
` COMPREPLY=( $(compgen -W "${SHELLS.join(" ")}" -- "$cur") )`,
|
|
4352
|
-
" ;;",
|
|
4353
|
-
" esac",
|
|
4354
|
-
" fi",
|
|
4485
|
+
" local IFS=$'\\n'",
|
|
4486
|
+
" local candidates",
|
|
4487
|
+
' candidates=$(monoceros __complete --line "$COMP_LINE" --point "$COMP_POINT" 2>/dev/null)',
|
|
4488
|
+
' local cur="${COMP_WORDS[COMP_CWORD]}"',
|
|
4489
|
+
' COMPREPLY=( $(compgen -W "$candidates" -- "$cur") )',
|
|
4355
4490
|
"}",
|
|
4356
4491
|
"complete -F _monoceros monoceros",
|
|
4357
4492
|
""
|
|
@@ -4363,44 +4498,20 @@ function renderCompletionScript(shell) {
|
|
|
4363
4498
|
"# install: dot-source this file from your $PROFILE, e.g.",
|
|
4364
4499
|
"# monoceros completion pwsh > $HOME/.config/monoceros/completion.ps1",
|
|
4365
4500
|
"# Add-Content $PROFILE '. $HOME/.config/monoceros/completion.ps1'",
|
|
4501
|
+
"#",
|
|
4502
|
+
"# The work is done by `monoceros __complete --line --point`; this",
|
|
4503
|
+
"# shell wrapper only forwards the cursor view.",
|
|
4366
4504
|
"",
|
|
4367
4505
|
"Register-ArgumentCompleter -Native -CommandName monoceros -ScriptBlock {",
|
|
4368
4506
|
" param($wordToComplete, $commandAst, $cursorPosition)",
|
|
4369
|
-
"",
|
|
4370
|
-
" $
|
|
4371
|
-
|
|
4372
|
-
"
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
"
|
|
4377
|
-
"",
|
|
4378
|
-
" $tokens = $commandAst.CommandElements",
|
|
4379
|
-
" $position = $tokens.Count",
|
|
4380
|
-
" if ($wordToComplete) { $position-- }",
|
|
4381
|
-
"",
|
|
4382
|
-
" if ($position -eq 1) {",
|
|
4383
|
-
' $commands | Where-Object { $_ -like "$wordToComplete*" } |',
|
|
4384
|
-
' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_) }',
|
|
4385
|
-
" return",
|
|
4386
|
-
" }",
|
|
4387
|
-
"",
|
|
4388
|
-
" if ($position -eq 2) {",
|
|
4389
|
-
" $cmd = $tokens[1].Value",
|
|
4390
|
-
" if ($containerCommands -contains $cmd) {",
|
|
4391
|
-
' $home = if ($env:MONOCEROS_HOME) { $env:MONOCEROS_HOME } else { Join-Path $env:USERPROFILE ".monoceros" }',
|
|
4392
|
-
' $configsDir = Join-Path $home "container-configs"',
|
|
4393
|
-
" if (Test-Path $configsDir) {",
|
|
4394
|
-
' Get-ChildItem -Path $configsDir -Filter "*.yml" |',
|
|
4395
|
-
" ForEach-Object { $_.BaseName } |",
|
|
4396
|
-
' Where-Object { $_ -like "$wordToComplete*" } |',
|
|
4397
|
-
' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_) }',
|
|
4398
|
-
" }",
|
|
4399
|
-
' } elseif ($cmd -eq "completion") {',
|
|
4400
|
-
' $shells | Where-Object { $_ -like "$wordToComplete*" } |',
|
|
4401
|
-
' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_) }',
|
|
4402
|
-
" }",
|
|
4403
|
-
" }",
|
|
4507
|
+
" $line = $commandAst.Extent.Text",
|
|
4508
|
+
" $point = $cursorPosition - $commandAst.Extent.StartOffset",
|
|
4509
|
+
" if ($point -lt 0) { $point = 0 }",
|
|
4510
|
+
" $raw = & monoceros __complete --line $line --point $point 2>$null",
|
|
4511
|
+
" if (-not $raw) { return }",
|
|
4512
|
+
' $raw -split "`n" |',
|
|
4513
|
+
' Where-Object { $_.Length -gt 0 -and $_ -like "$wordToComplete*" } |',
|
|
4514
|
+
' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_) }',
|
|
4404
4515
|
"}",
|
|
4405
4516
|
""
|
|
4406
4517
|
].join("\n");
|
|
@@ -4411,36 +4522,17 @@ function renderCompletionScript(shell) {
|
|
|
4411
4522
|
"# install: drop this file somewhere on your $fpath as `_monoceros`,",
|
|
4412
4523
|
"# then start a new shell (or run `compinit`). Example:",
|
|
4413
4524
|
'# monoceros completion zsh > "${fpath[1]}/_monoceros"',
|
|
4525
|
+
"#",
|
|
4526
|
+
"# The work is done by `monoceros __complete --line --point`; this",
|
|
4527
|
+
"# shell wrapper only forwards the cursor view.",
|
|
4414
4528
|
"",
|
|
4415
4529
|
"_monoceros() {",
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
"
|
|
4420
|
-
|
|
4421
|
-
"",
|
|
4422
|
-
" if (( CURRENT == 2 )); then",
|
|
4423
|
-
" _describe 'monoceros command' commands",
|
|
4424
|
-
" return",
|
|
4425
|
-
" fi",
|
|
4426
|
-
"",
|
|
4427
|
-
" local cmd=${words[2]}",
|
|
4428
|
-
" if (( CURRENT == 3 )); then",
|
|
4429
|
-
" case $cmd in",
|
|
4430
|
-
` ${containerCommandsRegex})`,
|
|
4431
|
-
' local home="${MONOCEROS_HOME:-$HOME/.monoceros}"',
|
|
4432
|
-
' local configs_dir="$home/container-configs"',
|
|
4433
|
-
" if [[ -d $configs_dir ]]; then",
|
|
4434
|
-
" local -a names",
|
|
4435
|
-
" names=(${configs_dir}/*.yml(N:t:r))",
|
|
4436
|
-
" _describe 'container' names",
|
|
4437
|
-
" fi",
|
|
4438
|
-
" ;;",
|
|
4439
|
-
" completion)",
|
|
4440
|
-
" _describe 'shell' shells",
|
|
4441
|
-
" ;;",
|
|
4442
|
-
" esac",
|
|
4443
|
-
" fi",
|
|
4530
|
+
' local line="$BUFFER"',
|
|
4531
|
+
' local point="$CURSOR"',
|
|
4532
|
+
" local -a candidates",
|
|
4533
|
+
' candidates=("${(@f)$(monoceros __complete --line "$line" --point "$point" 2>/dev/null)}")',
|
|
4534
|
+
' candidates=("${(@)candidates:#}")',
|
|
4535
|
+
" _describe 'completion' candidates",
|
|
4444
4536
|
"}",
|
|
4445
4537
|
"",
|
|
4446
4538
|
'_monoceros "$@"',
|
|
@@ -4473,178 +4565,474 @@ var completionCommand = defineCommand9({
|
|
|
4473
4565
|
}
|
|
4474
4566
|
});
|
|
4475
4567
|
|
|
4476
|
-
// src/commands/
|
|
4568
|
+
// src/commands/__complete.ts
|
|
4477
4569
|
import { defineCommand as defineCommand10 } from "citty";
|
|
4478
|
-
import { consola as consola14 } from "consola";
|
|
4479
4570
|
|
|
4480
|
-
// src/
|
|
4571
|
+
// src/completion/resolve.ts
|
|
4481
4572
|
import { existsSync as existsSync7, promises as fs12 } from "fs";
|
|
4482
|
-
import
|
|
4573
|
+
import path13 from "path";
|
|
4483
4574
|
|
|
4484
|
-
// src/init/
|
|
4485
|
-
import { existsSync as
|
|
4486
|
-
import
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
category: CategorySchema,
|
|
4498
|
-
contributes: z3.object({
|
|
4499
|
-
languages: z3.array(z3.string().min(1)).optional(),
|
|
4500
|
-
services: z3.array(z3.string().min(1)).optional(),
|
|
4501
|
-
features: z3.array(FeatureContributionSchema).optional()
|
|
4502
|
-
})
|
|
4503
|
-
}).superRefine((data, ctx) => {
|
|
4504
|
-
const c = data.contributes;
|
|
4505
|
-
const filled = [
|
|
4506
|
-
c.languages && c.languages.length > 0 ? "languages" : null,
|
|
4507
|
-
c.services && c.services.length > 0 ? "services" : null,
|
|
4508
|
-
c.features && c.features.length > 0 ? "features" : null
|
|
4509
|
-
].filter((x) => x !== null);
|
|
4510
|
-
if (filled.length === 0) {
|
|
4511
|
-
ctx.addIssue({
|
|
4512
|
-
code: z3.ZodIssueCode.custom,
|
|
4513
|
-
message: "contributes must set at least one of languages/services/features"
|
|
4514
|
-
});
|
|
4515
|
-
return;
|
|
4575
|
+
// src/init/manifest.ts
|
|
4576
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
4577
|
+
import path12 from "path";
|
|
4578
|
+
function resolveManifestPath(name, checkoutRoot) {
|
|
4579
|
+
if (checkoutRoot) {
|
|
4580
|
+
const checkoutPath = path12.join(
|
|
4581
|
+
checkoutRoot,
|
|
4582
|
+
"images",
|
|
4583
|
+
"features",
|
|
4584
|
+
name,
|
|
4585
|
+
"devcontainer-feature.json"
|
|
4586
|
+
);
|
|
4587
|
+
if (existsSync6(checkoutPath)) return checkoutPath;
|
|
4516
4588
|
}
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4589
|
+
const bundlePath = path12.join(
|
|
4590
|
+
bundledFeaturesDir(),
|
|
4591
|
+
name,
|
|
4592
|
+
"devcontainer-feature.json"
|
|
4593
|
+
);
|
|
4594
|
+
if (existsSync6(bundlePath)) return bundlePath;
|
|
4595
|
+
return null;
|
|
4596
|
+
}
|
|
4597
|
+
function loadFeatureManifestSummary(ref, checkoutRoot = workbenchCheckoutRoot()) {
|
|
4598
|
+
const match = matchMonocerosFeature(ref);
|
|
4599
|
+
if (!match) return void 0;
|
|
4600
|
+
const manifestPath = resolveManifestPath(match.name, checkoutRoot);
|
|
4601
|
+
if (!manifestPath) return void 0;
|
|
4602
|
+
try {
|
|
4603
|
+
const text = readFileSync3(manifestPath, "utf8");
|
|
4604
|
+
const parsed = JSON.parse(text);
|
|
4605
|
+
const rawHints = parsed["x-monoceros"]?.optionHints;
|
|
4606
|
+
const optionHints = Array.isArray(rawHints) ? rawHints.filter(
|
|
4607
|
+
(x) => typeof x === "string" && x.length > 0
|
|
4608
|
+
) : [];
|
|
4609
|
+
const rawNotes = parsed["x-monoceros"]?.usageNotes;
|
|
4610
|
+
const usageNotes = Array.isArray(rawNotes) ? rawNotes.filter(
|
|
4611
|
+
(x) => typeof x === "string" && x.length > 0
|
|
4612
|
+
) : [];
|
|
4613
|
+
const optionDescriptions = {};
|
|
4614
|
+
const optionTypes = {};
|
|
4615
|
+
const optionNames = [];
|
|
4616
|
+
if (parsed.options) {
|
|
4617
|
+
for (const [key, opt] of Object.entries(parsed.options)) {
|
|
4618
|
+
if (!opt || typeof opt !== "object") continue;
|
|
4619
|
+
optionNames.push(key);
|
|
4620
|
+
if (typeof opt.description === "string" && opt.description.length > 0) {
|
|
4621
|
+
optionDescriptions[key] = opt.description;
|
|
4622
|
+
}
|
|
4623
|
+
if (opt.type === "boolean") {
|
|
4624
|
+
optionTypes[key] = "boolean";
|
|
4625
|
+
} else if (opt.type === "string") {
|
|
4626
|
+
optionTypes[key] = "string";
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
const name = typeof parsed.name === "string" ? parsed.name : "";
|
|
4631
|
+
const description = typeof parsed.description === "string" ? parsed.description : "";
|
|
4632
|
+
const rawUrl = typeof parsed.documentationURL === "string" ? parsed.documentationURL.trim() : "";
|
|
4633
|
+
const documentationURL = rawUrl.length > 0 && rawUrl.toLowerCase() !== "tbd" ? rawUrl : void 0;
|
|
4634
|
+
return {
|
|
4635
|
+
name,
|
|
4636
|
+
description,
|
|
4637
|
+
documentationURL,
|
|
4638
|
+
optionHints,
|
|
4639
|
+
optionDescriptions,
|
|
4640
|
+
optionNames,
|
|
4641
|
+
optionTypes,
|
|
4642
|
+
usageNotes
|
|
4643
|
+
};
|
|
4644
|
+
} catch {
|
|
4645
|
+
return void 0;
|
|
4523
4646
|
}
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4647
|
+
}
|
|
4648
|
+
|
|
4649
|
+
// src/completion/resolve.ts
|
|
4650
|
+
async function resolveCompletions(line, point, opts = {}) {
|
|
4651
|
+
const { prev, current } = parseCompletionLine(line, point);
|
|
4652
|
+
const ctx = { prev, current, opts };
|
|
4653
|
+
const head = prev[0];
|
|
4654
|
+
const afterProgram = head === void 0 ? [] : prev.slice(1);
|
|
4655
|
+
if (afterProgram.length === 0) {
|
|
4656
|
+
return filterPrefix(ALL_COMMANDS, current);
|
|
4657
|
+
}
|
|
4658
|
+
const command = afterProgram[0];
|
|
4659
|
+
const spec = COMMAND_SPECS[command];
|
|
4660
|
+
if (!spec) {
|
|
4661
|
+
return [];
|
|
4662
|
+
}
|
|
4663
|
+
const argTokens = afterProgram.slice(1);
|
|
4664
|
+
return dispatchCommand(spec, argTokens, ctx);
|
|
4665
|
+
}
|
|
4666
|
+
function parseCompletionLine(line, point) {
|
|
4667
|
+
const before = line.slice(0, Math.max(0, Math.min(point, line.length)));
|
|
4668
|
+
const tokens = tokenize(before);
|
|
4669
|
+
const lastChar = before.length > 0 ? before[before.length - 1] : "";
|
|
4670
|
+
if (tokens.length === 0 || isShellWhitespace(lastChar)) {
|
|
4671
|
+
return { prev: tokens, current: "" };
|
|
4530
4672
|
}
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4673
|
+
return {
|
|
4674
|
+
prev: tokens.slice(0, -1),
|
|
4675
|
+
current: tokens[tokens.length - 1]
|
|
4676
|
+
};
|
|
4677
|
+
}
|
|
4678
|
+
function tokenize(text) {
|
|
4679
|
+
const out = [];
|
|
4680
|
+
let i = 0;
|
|
4681
|
+
while (i < text.length) {
|
|
4682
|
+
while (i < text.length && isShellWhitespace(text[i])) i++;
|
|
4683
|
+
if (i >= text.length) break;
|
|
4684
|
+
let token = "";
|
|
4685
|
+
let quote = null;
|
|
4686
|
+
while (i < text.length) {
|
|
4687
|
+
const ch = text[i];
|
|
4688
|
+
if (quote === null && isShellWhitespace(ch)) break;
|
|
4689
|
+
if (quote === null && (ch === '"' || ch === "'")) {
|
|
4690
|
+
quote = ch;
|
|
4691
|
+
i++;
|
|
4692
|
+
continue;
|
|
4693
|
+
}
|
|
4694
|
+
if (quote !== null && ch === quote) {
|
|
4695
|
+
quote = null;
|
|
4696
|
+
i++;
|
|
4697
|
+
continue;
|
|
4698
|
+
}
|
|
4699
|
+
token += ch;
|
|
4700
|
+
i++;
|
|
4701
|
+
}
|
|
4702
|
+
out.push(token);
|
|
4535
4703
|
}
|
|
4536
|
-
const out = /* @__PURE__ */ new Map();
|
|
4537
|
-
await walk(rootDir, rootDir, out);
|
|
4538
4704
|
return out;
|
|
4539
4705
|
}
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4706
|
+
function isShellWhitespace(ch) {
|
|
4707
|
+
return ch === " " || ch === " ";
|
|
4708
|
+
}
|
|
4709
|
+
function dispatchCommand(spec, argTokens, ctx) {
|
|
4710
|
+
const dashDashIdx = argTokens.indexOf("--");
|
|
4711
|
+
const preDash = dashDashIdx < 0 ? argTokens : argTokens.slice(0, dashDashIdx);
|
|
4712
|
+
const postDash = dashDashIdx < 0 ? [] : argTokens.slice(dashDashIdx + 1);
|
|
4713
|
+
const inPostDash = dashDashIdx >= 0;
|
|
4714
|
+
if (inPostDash && spec.innerArgs) {
|
|
4715
|
+
return resolveValues(spec.innerArgs, ctx, ctx.current);
|
|
4716
|
+
}
|
|
4717
|
+
return resolvePreDash(spec, preDash, ctx);
|
|
4718
|
+
}
|
|
4719
|
+
async function resolvePreDash(spec, preDash, ctx) {
|
|
4720
|
+
const current = ctx.current;
|
|
4721
|
+
if (current.startsWith("--") && current.includes("=")) {
|
|
4722
|
+
const eqIdx = current.indexOf("=");
|
|
4723
|
+
const flagName = current.slice(0, eqIdx);
|
|
4724
|
+
const valueFragment = current.slice(eqIdx + 1);
|
|
4725
|
+
const flag = spec.flags?.[flagName];
|
|
4726
|
+
if (!flag || flag.type !== "value" || !flag.values) return [];
|
|
4727
|
+
const completions = await resolveValues(flag.values, ctx, valueFragment);
|
|
4728
|
+
return completions.map((v) => `${flagName}=${v}`);
|
|
4729
|
+
}
|
|
4730
|
+
if (current.startsWith("-")) {
|
|
4731
|
+
const flags = spec.flags ?? {};
|
|
4732
|
+
const names = Object.keys(flags);
|
|
4733
|
+
const all = [];
|
|
4734
|
+
for (const n of names) {
|
|
4735
|
+
all.push(n);
|
|
4736
|
+
for (const a of flags[n].aliases ?? []) all.push(a);
|
|
4737
|
+
}
|
|
4738
|
+
return filterPrefix(all, current);
|
|
4739
|
+
}
|
|
4740
|
+
const lastPrev = preDash[preDash.length - 1];
|
|
4741
|
+
if (lastPrev && lastPrev.startsWith("--") && !lastPrev.includes("=")) {
|
|
4742
|
+
const flag = spec.flags?.[lastPrev];
|
|
4743
|
+
if (flag && flag.type === "value" && flag.values) {
|
|
4744
|
+
return resolveValues(flag.values, ctx, current);
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
const positionalIdx = countCompletedPositionals(preDash, spec.flags ?? {});
|
|
4748
|
+
const positional = spec.positionals?.[positionalIdx];
|
|
4749
|
+
if (positional) {
|
|
4750
|
+
return resolveValues(positional, ctx, current);
|
|
4751
|
+
}
|
|
4752
|
+
return [];
|
|
4753
|
+
}
|
|
4754
|
+
function countCompletedPositionals(preDash, flags) {
|
|
4755
|
+
let count = 0;
|
|
4756
|
+
for (let i = 0; i < preDash.length; i++) {
|
|
4757
|
+
const t = preDash[i];
|
|
4758
|
+
if (t.startsWith("--") && t.includes("=")) continue;
|
|
4759
|
+
if (t.startsWith("-")) {
|
|
4760
|
+
const flag = flags[t] ?? aliasFlag(flags, t);
|
|
4761
|
+
if (flag && flag.type === "value" && i + 1 < preDash.length) {
|
|
4762
|
+
i++;
|
|
4763
|
+
}
|
|
4546
4764
|
continue;
|
|
4547
4765
|
}
|
|
4548
|
-
|
|
4549
|
-
const relative = path11.relative(baseDir, full);
|
|
4550
|
-
const name = relative.replace(/\.yml$/, "").split(path11.sep).join("/");
|
|
4551
|
-
const text = await fs11.readFile(full, "utf8");
|
|
4552
|
-
let raw;
|
|
4553
|
-
try {
|
|
4554
|
-
raw = parseYaml(text);
|
|
4555
|
-
} catch (err) {
|
|
4556
|
-
throw new Error(
|
|
4557
|
-
`Failed to parse component ${name} (${full}): ${err.message}`
|
|
4558
|
-
);
|
|
4559
|
-
}
|
|
4560
|
-
const parsed = ComponentFileSchema.safeParse(raw);
|
|
4561
|
-
if (!parsed.success) {
|
|
4562
|
-
const issues = parsed.error.issues.map((issue) => {
|
|
4563
|
-
const where = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
4564
|
-
return ` - ${where}: ${issue.message}`;
|
|
4565
|
-
}).join("\n");
|
|
4566
|
-
throw new Error(`Invalid component ${name} (${full}):
|
|
4567
|
-
${issues}`);
|
|
4568
|
-
}
|
|
4569
|
-
out.set(name, { name, sourcePath: full, file: parsed.data });
|
|
4766
|
+
count++;
|
|
4570
4767
|
}
|
|
4768
|
+
return count;
|
|
4571
4769
|
}
|
|
4572
|
-
function
|
|
4573
|
-
const
|
|
4574
|
-
|
|
4575
|
-
const featureByRef = /* @__PURE__ */ new Map();
|
|
4576
|
-
for (const entry2 of resolved) {
|
|
4577
|
-
const c = isResolvedComponent(entry2) ? entry2.component : entry2;
|
|
4578
|
-
const version = isResolvedComponent(entry2) ? entry2.version : void 0;
|
|
4579
|
-
const ct = c.file.contributes;
|
|
4580
|
-
for (const lang of ct.languages ?? []) {
|
|
4581
|
-
const value = version !== void 0 ? `${lang}:${version}` : lang;
|
|
4582
|
-
if (!languages.includes(value)) languages.push(value);
|
|
4583
|
-
}
|
|
4584
|
-
for (const svc of ct.services ?? []) {
|
|
4585
|
-
if (!services.includes(svc)) services.push(svc);
|
|
4586
|
-
}
|
|
4587
|
-
for (const f of ct.features ?? []) {
|
|
4588
|
-
const existing = featureByRef.get(f.ref);
|
|
4589
|
-
if (!existing) {
|
|
4590
|
-
featureByRef.set(f.ref, {
|
|
4591
|
-
ref: f.ref,
|
|
4592
|
-
options: { ...f.options ?? {} }
|
|
4593
|
-
});
|
|
4594
|
-
continue;
|
|
4595
|
-
}
|
|
4596
|
-
existing.options = mergeFeatureOptions(existing.options, f.options ?? {});
|
|
4597
|
-
}
|
|
4770
|
+
function aliasFlag(flags, token) {
|
|
4771
|
+
for (const f of Object.values(flags)) {
|
|
4772
|
+
if (f.aliases?.includes(token)) return f;
|
|
4598
4773
|
}
|
|
4599
|
-
return
|
|
4600
|
-
languages,
|
|
4601
|
-
services,
|
|
4602
|
-
features: [...featureByRef.values()]
|
|
4603
|
-
};
|
|
4774
|
+
return void 0;
|
|
4604
4775
|
}
|
|
4605
|
-
function
|
|
4606
|
-
|
|
4776
|
+
async function resolveValues(source, ctx, fragment) {
|
|
4777
|
+
const values = await source(ctx);
|
|
4778
|
+
const commaIdx = fragment.lastIndexOf(",");
|
|
4779
|
+
if (commaIdx < 0) {
|
|
4780
|
+
return filterPrefix(values, fragment);
|
|
4781
|
+
}
|
|
4782
|
+
const prefix = fragment.slice(0, commaIdx + 1);
|
|
4783
|
+
const tail = fragment.slice(commaIdx + 1);
|
|
4784
|
+
return filterPrefix(values, tail).map((v) => `${prefix}${v}`);
|
|
4785
|
+
}
|
|
4786
|
+
function filterPrefix(values, fragment) {
|
|
4787
|
+
if (fragment.length === 0) return [...values];
|
|
4788
|
+
return values.filter((v) => v.startsWith(fragment));
|
|
4789
|
+
}
|
|
4790
|
+
async function listContainerNames(ctx) {
|
|
4791
|
+
const home = ctx.opts.monocerosHome ?? monocerosHome();
|
|
4792
|
+
const dir = path13.join(home, "container-configs");
|
|
4793
|
+
if (!existsSync7(dir)) return [];
|
|
4794
|
+
const entries = await fs12.readdir(dir);
|
|
4795
|
+
return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length)).sort();
|
|
4796
|
+
}
|
|
4797
|
+
async function listAllCatalogComponents() {
|
|
4798
|
+
const catalog = await loadComponentCatalog();
|
|
4799
|
+
return [...catalog.keys()].sort();
|
|
4800
|
+
}
|
|
4801
|
+
async function listFeatureComponents() {
|
|
4802
|
+
const catalog = await loadComponentCatalog();
|
|
4803
|
+
return [...catalog.values()].filter((c) => c.file.category === "feature").map((c) => c.name).sort();
|
|
4804
|
+
}
|
|
4805
|
+
function listLanguageNames() {
|
|
4806
|
+
return knownLanguages().sort();
|
|
4807
|
+
}
|
|
4808
|
+
function listServiceNames() {
|
|
4809
|
+
return knownServices().sort();
|
|
4810
|
+
}
|
|
4811
|
+
function listProviders() {
|
|
4812
|
+
return [...PROVIDER_VALUES];
|
|
4813
|
+
}
|
|
4814
|
+
function listShellNames() {
|
|
4815
|
+
return ["bash", "zsh", "pwsh"];
|
|
4816
|
+
}
|
|
4817
|
+
async function listFeatureOptionInnerArgs(ctx) {
|
|
4818
|
+
const after = ctx.prev.slice(2);
|
|
4819
|
+
let positionalCount = 0;
|
|
4820
|
+
let featureToken;
|
|
4821
|
+
for (let i = 0; i < after.length; i++) {
|
|
4822
|
+
const t = after[i];
|
|
4823
|
+
if (t === "--") break;
|
|
4824
|
+
if (t.startsWith("-")) continue;
|
|
4825
|
+
positionalCount++;
|
|
4826
|
+
if (positionalCount === 2) {
|
|
4827
|
+
featureToken = t;
|
|
4828
|
+
break;
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
if (!featureToken) return [];
|
|
4832
|
+
const ref = await resolveFeatureRefForCompletion(featureToken);
|
|
4833
|
+
if (!ref) return [];
|
|
4834
|
+
const summary = loadFeatureManifestSummary(ref);
|
|
4835
|
+
if (!summary) return [];
|
|
4836
|
+
const dashDash = ctx.prev.indexOf("--");
|
|
4837
|
+
const innerSoFar = dashDash >= 0 ? ctx.prev.slice(dashDash + 1) : [];
|
|
4838
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
4839
|
+
for (const t of innerSoFar) {
|
|
4840
|
+
const eq = t.indexOf("=");
|
|
4841
|
+
if (eq > 0) usedKeys.add(t.slice(0, eq));
|
|
4842
|
+
}
|
|
4843
|
+
const eqIdx = ctx.current.indexOf("=");
|
|
4844
|
+
if (eqIdx >= 0) {
|
|
4845
|
+
const key = ctx.current.slice(0, eqIdx);
|
|
4846
|
+
const valueFragment = ctx.current.slice(eqIdx + 1);
|
|
4847
|
+
const type = summary.optionTypes[key];
|
|
4848
|
+
if (type === "boolean") {
|
|
4849
|
+
return ["true", "false"].filter((v) => v.startsWith(valueFragment)).map((v) => `${key}=${v}`);
|
|
4850
|
+
}
|
|
4851
|
+
return [];
|
|
4852
|
+
}
|
|
4853
|
+
return summary.optionNames.filter((n) => !usedKeys.has(n));
|
|
4854
|
+
}
|
|
4855
|
+
async function resolveFeatureRefForCompletion(token) {
|
|
4856
|
+
if (REGEX.featureRef.test(token)) return token;
|
|
4857
|
+
const catalog = await loadComponentCatalog();
|
|
4858
|
+
const c = catalog.get(token);
|
|
4859
|
+
if (!c || c.file.category !== "feature") return void 0;
|
|
4860
|
+
const f = c.file.contributes.features?.[0];
|
|
4861
|
+
return f?.ref;
|
|
4607
4862
|
}
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4863
|
+
var ALL_COMMANDS = [
|
|
4864
|
+
"init",
|
|
4865
|
+
"list-components",
|
|
4866
|
+
"shell",
|
|
4867
|
+
"run",
|
|
4868
|
+
"logs",
|
|
4869
|
+
"start",
|
|
4870
|
+
"stop",
|
|
4871
|
+
"status",
|
|
4872
|
+
"apply",
|
|
4873
|
+
"remove",
|
|
4874
|
+
"restore",
|
|
4875
|
+
"add-service",
|
|
4876
|
+
"add-language",
|
|
4877
|
+
"add-apt-packages",
|
|
4878
|
+
"add-feature",
|
|
4879
|
+
"add-from-url",
|
|
4880
|
+
"add-repo",
|
|
4881
|
+
"add-port",
|
|
4882
|
+
"remove-service",
|
|
4883
|
+
"remove-language",
|
|
4884
|
+
"remove-apt-packages",
|
|
4885
|
+
"remove-feature",
|
|
4886
|
+
"remove-from-url",
|
|
4887
|
+
"remove-repo",
|
|
4888
|
+
"remove-port",
|
|
4889
|
+
"port",
|
|
4890
|
+
"completion"
|
|
4891
|
+
];
|
|
4892
|
+
var containerName = (ctx) => listContainerNames(ctx);
|
|
4893
|
+
var COMMAND_SPECS = {
|
|
4894
|
+
init: {
|
|
4895
|
+
// First positional is a FRESH name → no suggestions from existing
|
|
4896
|
+
// container-configs (would invite collisions).
|
|
4897
|
+
flags: {
|
|
4898
|
+
"--with": { type: "value", values: () => listAllCatalogComponents() },
|
|
4899
|
+
"--with-repo": { type: "value" },
|
|
4900
|
+
"--with-ports": { type: "value" }
|
|
4615
4901
|
}
|
|
4616
|
-
|
|
4902
|
+
},
|
|
4903
|
+
apply: {
|
|
4904
|
+
positionals: [containerName],
|
|
4905
|
+
flags: { "--yes": { type: "boolean", aliases: ["-y"] } }
|
|
4906
|
+
},
|
|
4907
|
+
remove: {
|
|
4908
|
+
positionals: [containerName],
|
|
4909
|
+
flags: {
|
|
4910
|
+
"--yes": { type: "boolean", aliases: ["-y"] },
|
|
4911
|
+
"--no-backup": { type: "boolean" }
|
|
4912
|
+
}
|
|
4913
|
+
},
|
|
4914
|
+
shell: { positionals: [containerName] },
|
|
4915
|
+
run: { positionals: [containerName] },
|
|
4916
|
+
logs: { positionals: [containerName] },
|
|
4917
|
+
start: { positionals: [containerName] },
|
|
4918
|
+
stop: { positionals: [containerName] },
|
|
4919
|
+
status: { positionals: [containerName] },
|
|
4920
|
+
"add-language": {
|
|
4921
|
+
positionals: [containerName, () => listLanguageNames()]
|
|
4922
|
+
},
|
|
4923
|
+
"add-service": {
|
|
4924
|
+
positionals: [containerName, () => listServiceNames()]
|
|
4925
|
+
},
|
|
4926
|
+
"add-apt-packages": {
|
|
4927
|
+
positionals: [containerName],
|
|
4928
|
+
innerArgs: () => []
|
|
4929
|
+
// freeform package names — no useful suggestion list
|
|
4930
|
+
},
|
|
4931
|
+
"add-feature": {
|
|
4932
|
+
positionals: [containerName, () => listFeatureComponents()],
|
|
4933
|
+
flags: { "--yes": { type: "boolean", aliases: ["-y"] } },
|
|
4934
|
+
innerArgs: (ctx) => listFeatureOptionInnerArgs(ctx)
|
|
4935
|
+
},
|
|
4936
|
+
"add-from-url": { positionals: [containerName] },
|
|
4937
|
+
"add-repo": {
|
|
4938
|
+
positionals: [containerName],
|
|
4939
|
+
flags: {
|
|
4940
|
+
"--path": { type: "value" },
|
|
4941
|
+
"--git-name": { type: "value" },
|
|
4942
|
+
"--git-email": { type: "value" },
|
|
4943
|
+
"--provider": { type: "value", values: () => listProviders() },
|
|
4944
|
+
"--yes": { type: "boolean", aliases: ["-y"] }
|
|
4945
|
+
}
|
|
4946
|
+
},
|
|
4947
|
+
"add-port": {
|
|
4948
|
+
positionals: [containerName],
|
|
4949
|
+
flags: {
|
|
4950
|
+
"--default": { type: "boolean" },
|
|
4951
|
+
"--yes": { type: "boolean", aliases: ["-y"] }
|
|
4952
|
+
},
|
|
4953
|
+
innerArgs: () => []
|
|
4954
|
+
},
|
|
4955
|
+
"remove-language": {
|
|
4956
|
+
positionals: [containerName, () => listLanguageNames()]
|
|
4957
|
+
},
|
|
4958
|
+
"remove-service": {
|
|
4959
|
+
positionals: [containerName, () => listServiceNames()]
|
|
4960
|
+
},
|
|
4961
|
+
"remove-apt-packages": {
|
|
4962
|
+
positionals: [containerName],
|
|
4963
|
+
innerArgs: () => []
|
|
4964
|
+
},
|
|
4965
|
+
"remove-feature": {
|
|
4966
|
+
positionals: [containerName, () => listFeatureComponents()],
|
|
4967
|
+
flags: { "--yes": { type: "boolean", aliases: ["-y"] } }
|
|
4968
|
+
},
|
|
4969
|
+
"remove-from-url": { positionals: [containerName] },
|
|
4970
|
+
"remove-repo": { positionals: [containerName] },
|
|
4971
|
+
"remove-port": {
|
|
4972
|
+
positionals: [containerName],
|
|
4973
|
+
flags: { "--yes": { type: "boolean", aliases: ["-y"] } },
|
|
4974
|
+
innerArgs: () => []
|
|
4975
|
+
},
|
|
4976
|
+
port: {
|
|
4977
|
+
positionals: [containerName],
|
|
4978
|
+
flags: { "--default": { type: "boolean" } },
|
|
4979
|
+
innerArgs: () => []
|
|
4980
|
+
},
|
|
4981
|
+
completion: {
|
|
4982
|
+
positionals: [() => listShellNames()]
|
|
4983
|
+
},
|
|
4984
|
+
"list-components": {},
|
|
4985
|
+
restore: {
|
|
4986
|
+
// First positional is a backup-path; no value suggestions today
|
|
4987
|
+
// (could plug filesystem completion later).
|
|
4617
4988
|
}
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4989
|
+
};
|
|
4990
|
+
var COMPLETION_COMMAND_SPEC_KEYS = Object.keys(COMMAND_SPECS);
|
|
4991
|
+
|
|
4992
|
+
// src/commands/__complete.ts
|
|
4993
|
+
var __completeCommand = defineCommand10({
|
|
4994
|
+
meta: {
|
|
4995
|
+
name: "__complete",
|
|
4996
|
+
group: "internal",
|
|
4997
|
+
description: "Internal \u2014 shell completion engine. Used by the wrappers emitted by `monoceros completion <shell>`. Output one candidate completion per line."
|
|
4998
|
+
},
|
|
4999
|
+
args: {
|
|
5000
|
+
line: {
|
|
5001
|
+
type: "string",
|
|
5002
|
+
description: "Full command line buffer up to (and possibly past) the cursor.",
|
|
5003
|
+
default: ""
|
|
5004
|
+
},
|
|
5005
|
+
point: {
|
|
5006
|
+
type: "string",
|
|
5007
|
+
description: "Byte offset of the cursor within --line. Default: end of line.",
|
|
5008
|
+
default: ""
|
|
4631
5009
|
}
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
5010
|
+
},
|
|
5011
|
+
async run({ args }) {
|
|
5012
|
+
const line = String(args.line ?? "");
|
|
5013
|
+
const point = args.point && String(args.point).length > 0 ? Number.parseInt(String(args.point), 10) : line.length;
|
|
5014
|
+
let candidates;
|
|
5015
|
+
try {
|
|
5016
|
+
candidates = await resolveCompletions(
|
|
5017
|
+
line,
|
|
5018
|
+
Number.isFinite(point) ? point : line.length
|
|
4635
5019
|
);
|
|
5020
|
+
} catch {
|
|
5021
|
+
candidates = [];
|
|
5022
|
+
}
|
|
5023
|
+
if (candidates.length > 0) {
|
|
5024
|
+
process.stdout.write(candidates.join("\n") + "\n");
|
|
4636
5025
|
}
|
|
4637
|
-
out.push({ component: c, ...version !== void 0 ? { version } : {} });
|
|
4638
|
-
}
|
|
4639
|
-
if (unknown.length > 0) {
|
|
4640
|
-
const available = [...catalog.keys()].sort();
|
|
4641
|
-
throw new Error(
|
|
4642
|
-
`Unknown component${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}.
|
|
4643
|
-
Available: ${available.join(", ")}.`
|
|
4644
|
-
);
|
|
4645
5026
|
}
|
|
4646
|
-
|
|
4647
|
-
|
|
5027
|
+
});
|
|
5028
|
+
|
|
5029
|
+
// src/commands/init.ts
|
|
5030
|
+
import { defineCommand as defineCommand11 } from "citty";
|
|
5031
|
+
import { consola as consola14 } from "consola";
|
|
5032
|
+
|
|
5033
|
+
// src/init/index.ts
|
|
5034
|
+
import { existsSync as existsSync8, promises as fs13 } from "fs";
|
|
5035
|
+
import { consola as consola13 } from "consola";
|
|
4648
5036
|
|
|
4649
5037
|
// src/init/generator.ts
|
|
4650
5038
|
var SCHEMA_HEADER_ACTIVE = "# Solution-config \u2014 describes what should be inside your dev-container.\n# Edit any section, then run `monoceros apply <name>` to (re-)build.";
|
|
@@ -5011,69 +5399,6 @@ function ensureTrailingNewline(s) {
|
|
|
5011
5399
|
return s.endsWith("\n") ? s : s + "\n";
|
|
5012
5400
|
}
|
|
5013
5401
|
|
|
5014
|
-
// src/init/manifest.ts
|
|
5015
|
-
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
5016
|
-
import path12 from "path";
|
|
5017
|
-
function resolveManifestPath(name, checkoutRoot) {
|
|
5018
|
-
if (checkoutRoot) {
|
|
5019
|
-
const checkoutPath = path12.join(
|
|
5020
|
-
checkoutRoot,
|
|
5021
|
-
"images",
|
|
5022
|
-
"features",
|
|
5023
|
-
name,
|
|
5024
|
-
"devcontainer-feature.json"
|
|
5025
|
-
);
|
|
5026
|
-
if (existsSync6(checkoutPath)) return checkoutPath;
|
|
5027
|
-
}
|
|
5028
|
-
const bundlePath = path12.join(
|
|
5029
|
-
bundledFeaturesDir(),
|
|
5030
|
-
name,
|
|
5031
|
-
"devcontainer-feature.json"
|
|
5032
|
-
);
|
|
5033
|
-
if (existsSync6(bundlePath)) return bundlePath;
|
|
5034
|
-
return null;
|
|
5035
|
-
}
|
|
5036
|
-
function loadFeatureManifestSummary(ref, checkoutRoot = workbenchCheckoutRoot()) {
|
|
5037
|
-
const match = matchMonocerosFeature(ref);
|
|
5038
|
-
if (!match) return void 0;
|
|
5039
|
-
const manifestPath = resolveManifestPath(match.name, checkoutRoot);
|
|
5040
|
-
if (!manifestPath) return void 0;
|
|
5041
|
-
try {
|
|
5042
|
-
const text = readFileSync3(manifestPath, "utf8");
|
|
5043
|
-
const parsed = JSON.parse(text);
|
|
5044
|
-
const rawHints = parsed["x-monoceros"]?.optionHints;
|
|
5045
|
-
const optionHints = Array.isArray(rawHints) ? rawHints.filter(
|
|
5046
|
-
(x) => typeof x === "string" && x.length > 0
|
|
5047
|
-
) : [];
|
|
5048
|
-
const rawNotes = parsed["x-monoceros"]?.usageNotes;
|
|
5049
|
-
const usageNotes = Array.isArray(rawNotes) ? rawNotes.filter(
|
|
5050
|
-
(x) => typeof x === "string" && x.length > 0
|
|
5051
|
-
) : [];
|
|
5052
|
-
const optionDescriptions = {};
|
|
5053
|
-
if (parsed.options) {
|
|
5054
|
-
for (const [key, opt] of Object.entries(parsed.options)) {
|
|
5055
|
-
if (opt && typeof opt === "object" && typeof opt.description === "string" && opt.description.length > 0) {
|
|
5056
|
-
optionDescriptions[key] = opt.description;
|
|
5057
|
-
}
|
|
5058
|
-
}
|
|
5059
|
-
}
|
|
5060
|
-
const name = typeof parsed.name === "string" ? parsed.name : "";
|
|
5061
|
-
const description = typeof parsed.description === "string" ? parsed.description : "";
|
|
5062
|
-
const rawUrl = typeof parsed.documentationURL === "string" ? parsed.documentationURL.trim() : "";
|
|
5063
|
-
const documentationURL = rawUrl.length > 0 && rawUrl.toLowerCase() !== "tbd" ? rawUrl : void 0;
|
|
5064
|
-
return {
|
|
5065
|
-
name,
|
|
5066
|
-
description,
|
|
5067
|
-
documentationURL,
|
|
5068
|
-
optionHints,
|
|
5069
|
-
optionDescriptions,
|
|
5070
|
-
usageNotes
|
|
5071
|
-
};
|
|
5072
|
-
} catch {
|
|
5073
|
-
return void 0;
|
|
5074
|
-
}
|
|
5075
|
-
}
|
|
5076
|
-
|
|
5077
5402
|
// src/init/index.ts
|
|
5078
5403
|
async function runInit(opts) {
|
|
5079
5404
|
const workbench = opts.workbenchRoot ?? workbenchRoot();
|
|
@@ -5088,7 +5413,7 @@ async function runInit(opts) {
|
|
|
5088
5413
|
);
|
|
5089
5414
|
}
|
|
5090
5415
|
const dest = containerConfigPath(opts.name, home);
|
|
5091
|
-
if (
|
|
5416
|
+
if (existsSync8(dest)) {
|
|
5092
5417
|
throw new Error(
|
|
5093
5418
|
`Config already exists: ${dest}. Delete it manually before re-running \`monoceros init\` \u2014 this protects any hand-edits.`
|
|
5094
5419
|
);
|
|
@@ -5165,8 +5490,8 @@ async function runInit(opts) {
|
|
|
5165
5490
|
const components = resolveComponents(catalog, requested);
|
|
5166
5491
|
text = generateComposedYml(opts.name, components, lookup, repos, ports);
|
|
5167
5492
|
}
|
|
5168
|
-
await
|
|
5169
|
-
await
|
|
5493
|
+
await fs13.mkdir(containerConfigsDir(home), { recursive: true });
|
|
5494
|
+
await fs13.writeFile(dest, text, "utf8");
|
|
5170
5495
|
if (promptedIdentity?.prompted) {
|
|
5171
5496
|
const { name, email, scope } = promptedIdentity.prompted;
|
|
5172
5497
|
if (scope === "g" || scope === "b") {
|
|
@@ -5196,11 +5521,11 @@ async function runInit(opts) {
|
|
|
5196
5521
|
}
|
|
5197
5522
|
if (scope === "c" || scope === "b") {
|
|
5198
5523
|
try {
|
|
5199
|
-
const written = await
|
|
5524
|
+
const written = await fs13.readFile(dest, "utf8");
|
|
5200
5525
|
const parsed = parseConfig(written, dest);
|
|
5201
5526
|
const changed = setContainerGitUserInDoc(parsed.doc, { name, email });
|
|
5202
5527
|
if (changed) {
|
|
5203
|
-
await
|
|
5528
|
+
await fs13.writeFile(dest, stringifyConfig(parsed.doc), "utf8");
|
|
5204
5529
|
logger.info(
|
|
5205
5530
|
`Saved identity in ${prettyPath(dest)} (container-level git.user).`
|
|
5206
5531
|
);
|
|
@@ -5230,7 +5555,7 @@ async function runInit(opts) {
|
|
|
5230
5555
|
}
|
|
5231
5556
|
|
|
5232
5557
|
// src/commands/init.ts
|
|
5233
|
-
var initCommand =
|
|
5558
|
+
var initCommand = defineCommand11({
|
|
5234
5559
|
meta: {
|
|
5235
5560
|
name: "init",
|
|
5236
5561
|
group: "lifecycle",
|
|
@@ -5349,7 +5674,7 @@ function collectWithList(withArg, rawArgs) {
|
|
|
5349
5674
|
}
|
|
5350
5675
|
|
|
5351
5676
|
// src/commands/list-components.ts
|
|
5352
|
-
import { defineCommand as
|
|
5677
|
+
import { defineCommand as defineCommand12 } from "citty";
|
|
5353
5678
|
import { consola as consola15 } from "consola";
|
|
5354
5679
|
var CATEGORY_LABELS = {
|
|
5355
5680
|
language: "Languages",
|
|
@@ -5361,7 +5686,7 @@ var CATEGORY_ORDER = [
|
|
|
5361
5686
|
"service",
|
|
5362
5687
|
"feature"
|
|
5363
5688
|
];
|
|
5364
|
-
var listComponentsCommand =
|
|
5689
|
+
var listComponentsCommand = defineCommand12({
|
|
5365
5690
|
meta: {
|
|
5366
5691
|
name: "list-components",
|
|
5367
5692
|
group: "discovery",
|
|
@@ -5430,8 +5755,8 @@ var listComponentsCommand = defineCommand11({
|
|
|
5430
5755
|
});
|
|
5431
5756
|
|
|
5432
5757
|
// src/commands/logs.ts
|
|
5433
|
-
import { defineCommand as
|
|
5434
|
-
var logsCommand =
|
|
5758
|
+
import { defineCommand as defineCommand13 } from "citty";
|
|
5759
|
+
var logsCommand = defineCommand13({
|
|
5435
5760
|
meta: {
|
|
5436
5761
|
name: "logs",
|
|
5437
5762
|
group: "run",
|
|
@@ -5466,7 +5791,7 @@ var logsCommand = defineCommand12({
|
|
|
5466
5791
|
});
|
|
5467
5792
|
|
|
5468
5793
|
// src/commands/port.ts
|
|
5469
|
-
import { defineCommand as
|
|
5794
|
+
import { defineCommand as defineCommand14 } from "citty";
|
|
5470
5795
|
import { consola as consola16 } from "consola";
|
|
5471
5796
|
async function runPortListing(opts) {
|
|
5472
5797
|
const out = opts.out ?? process.stdout;
|
|
@@ -5518,7 +5843,7 @@ async function runPortListing(opts) {
|
|
|
5518
5843
|
}
|
|
5519
5844
|
return 0;
|
|
5520
5845
|
}
|
|
5521
|
-
var portCommand =
|
|
5846
|
+
var portCommand = defineCommand14({
|
|
5522
5847
|
meta: {
|
|
5523
5848
|
name: "port",
|
|
5524
5849
|
group: "discovery",
|
|
@@ -5543,9 +5868,9 @@ var portCommand = defineCommand13({
|
|
|
5543
5868
|
});
|
|
5544
5869
|
|
|
5545
5870
|
// src/commands/remove-apt-packages.ts
|
|
5546
|
-
import { defineCommand as
|
|
5871
|
+
import { defineCommand as defineCommand15 } from "citty";
|
|
5547
5872
|
import { consola as consola17 } from "consola";
|
|
5548
|
-
var removeAptPackagesCommand =
|
|
5873
|
+
var removeAptPackagesCommand = defineCommand15({
|
|
5549
5874
|
meta: {
|
|
5550
5875
|
name: "remove-apt-packages",
|
|
5551
5876
|
group: "edit",
|
|
@@ -5587,9 +5912,9 @@ var removeAptPackagesCommand = defineCommand14({
|
|
|
5587
5912
|
});
|
|
5588
5913
|
|
|
5589
5914
|
// src/commands/remove-feature.ts
|
|
5590
|
-
import { defineCommand as
|
|
5915
|
+
import { defineCommand as defineCommand16 } from "citty";
|
|
5591
5916
|
import { consola as consola18 } from "consola";
|
|
5592
|
-
var removeFeatureCommand =
|
|
5917
|
+
var removeFeatureCommand = defineCommand16({
|
|
5593
5918
|
meta: {
|
|
5594
5919
|
name: "remove-feature",
|
|
5595
5920
|
group: "edit",
|
|
@@ -5629,13 +5954,13 @@ var removeFeatureCommand = defineCommand15({
|
|
|
5629
5954
|
});
|
|
5630
5955
|
|
|
5631
5956
|
// src/commands/remove.ts
|
|
5632
|
-
import { defineCommand as
|
|
5957
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
5633
5958
|
import { consola as consola20 } from "consola";
|
|
5634
5959
|
import { createInterface } from "readline/promises";
|
|
5635
5960
|
|
|
5636
5961
|
// src/remove/index.ts
|
|
5637
|
-
import { existsSync as
|
|
5638
|
-
import
|
|
5962
|
+
import { existsSync as existsSync9, promises as fs14 } from "fs";
|
|
5963
|
+
import path14 from "path";
|
|
5639
5964
|
import { consola as consola19 } from "consola";
|
|
5640
5965
|
async function runRemove(opts) {
|
|
5641
5966
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -5651,8 +5976,8 @@ async function runRemove(opts) {
|
|
|
5651
5976
|
}
|
|
5652
5977
|
const ymlPath = containerConfigPath(opts.name, home);
|
|
5653
5978
|
const containerPath = containerDir(opts.name, home);
|
|
5654
|
-
const hasYml =
|
|
5655
|
-
const hasContainer =
|
|
5979
|
+
const hasYml = existsSync9(ymlPath);
|
|
5980
|
+
const hasContainer = existsSync9(containerPath);
|
|
5656
5981
|
if (!hasYml && !hasContainer) {
|
|
5657
5982
|
throw new Error(
|
|
5658
5983
|
`Nothing to remove for '${opts.name}': neither ${ymlPath} nor ${containerPath} exists.`
|
|
@@ -5686,23 +6011,23 @@ async function runRemove(opts) {
|
|
|
5686
6011
|
let backupPath = null;
|
|
5687
6012
|
if (!opts.noBackup && (hasYml || hasContainer)) {
|
|
5688
6013
|
const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5689
|
-
backupPath =
|
|
5690
|
-
await
|
|
6014
|
+
backupPath = path14.join(home, "container-backups", `${opts.name}-${ts}`);
|
|
6015
|
+
await fs14.mkdir(backupPath, { recursive: true });
|
|
5691
6016
|
if (hasYml) {
|
|
5692
|
-
await
|
|
6017
|
+
await fs14.copyFile(ymlPath, path14.join(backupPath, `${opts.name}.yml`));
|
|
5693
6018
|
}
|
|
5694
6019
|
if (hasContainer) {
|
|
5695
|
-
await
|
|
6020
|
+
await fs14.cp(containerPath, path14.join(backupPath, "container"), {
|
|
5696
6021
|
recursive: true
|
|
5697
6022
|
});
|
|
5698
6023
|
}
|
|
5699
6024
|
logger.info(`Backup written to ${prettyPath(backupPath)}.`);
|
|
5700
6025
|
}
|
|
5701
6026
|
if (hasYml) {
|
|
5702
|
-
await
|
|
6027
|
+
await fs14.rm(ymlPath, { force: true });
|
|
5703
6028
|
}
|
|
5704
6029
|
if (hasContainer) {
|
|
5705
|
-
await
|
|
6030
|
+
await fs14.rm(containerPath, { recursive: true, force: true });
|
|
5706
6031
|
}
|
|
5707
6032
|
logger.success(
|
|
5708
6033
|
`Removed '${opts.name}': docker objects gone, container-configs entry deleted, container directory deleted.`
|
|
@@ -5739,7 +6064,7 @@ async function runRemove(opts) {
|
|
|
5739
6064
|
}
|
|
5740
6065
|
|
|
5741
6066
|
// src/commands/remove.ts
|
|
5742
|
-
var removeCommand =
|
|
6067
|
+
var removeCommand = defineCommand17({
|
|
5743
6068
|
meta: {
|
|
5744
6069
|
name: "remove",
|
|
5745
6070
|
group: "lifecycle",
|
|
@@ -5800,12 +6125,12 @@ var removeCommand = defineCommand16({
|
|
|
5800
6125
|
});
|
|
5801
6126
|
|
|
5802
6127
|
// src/commands/restore.ts
|
|
5803
|
-
import { defineCommand as
|
|
6128
|
+
import { defineCommand as defineCommand18 } from "citty";
|
|
5804
6129
|
import { consola as consola22 } from "consola";
|
|
5805
6130
|
|
|
5806
6131
|
// src/restore/index.ts
|
|
5807
|
-
import { existsSync as
|
|
5808
|
-
import
|
|
6132
|
+
import { existsSync as existsSync10, promises as fs15 } from "fs";
|
|
6133
|
+
import path15 from "path";
|
|
5809
6134
|
import { consola as consola21 } from "consola";
|
|
5810
6135
|
async function runRestore(opts) {
|
|
5811
6136
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -5813,15 +6138,15 @@ async function runRestore(opts) {
|
|
|
5813
6138
|
info: (msg) => consola21.info(msg),
|
|
5814
6139
|
success: (msg) => consola21.success(msg)
|
|
5815
6140
|
};
|
|
5816
|
-
const backup =
|
|
5817
|
-
if (!
|
|
6141
|
+
const backup = path15.resolve(opts.backupPath);
|
|
6142
|
+
if (!existsSync10(backup)) {
|
|
5818
6143
|
throw new Error(`Backup not found: ${backup}.`);
|
|
5819
6144
|
}
|
|
5820
|
-
const stat = await
|
|
6145
|
+
const stat = await fs15.stat(backup);
|
|
5821
6146
|
if (!stat.isDirectory()) {
|
|
5822
6147
|
throw new Error(`Backup path is not a directory: ${backup}.`);
|
|
5823
6148
|
}
|
|
5824
|
-
const entries = await
|
|
6149
|
+
const entries = await fs15.readdir(backup);
|
|
5825
6150
|
const ymlFiles = entries.filter((f) => f.endsWith(".yml"));
|
|
5826
6151
|
if (ymlFiles.length === 0) {
|
|
5827
6152
|
throw new Error(
|
|
@@ -5835,24 +6160,24 @@ async function runRestore(opts) {
|
|
|
5835
6160
|
}
|
|
5836
6161
|
const ymlFile = ymlFiles[0];
|
|
5837
6162
|
const name = ymlFile.replace(/\.yml$/, "");
|
|
5838
|
-
const containerInBackup =
|
|
5839
|
-
const hasContainer =
|
|
6163
|
+
const containerInBackup = path15.join(backup, "container");
|
|
6164
|
+
const hasContainer = existsSync10(containerInBackup);
|
|
5840
6165
|
const destYml = containerConfigPath(name, home);
|
|
5841
6166
|
const destContainer = containerDir(name, home);
|
|
5842
|
-
if (
|
|
6167
|
+
if (existsSync10(destYml)) {
|
|
5843
6168
|
throw new Error(
|
|
5844
6169
|
`Refusing to restore: ${destYml} already exists. Remove the current container first (\`monoceros remove ${name}\`) or rename the existing config.`
|
|
5845
6170
|
);
|
|
5846
6171
|
}
|
|
5847
|
-
if (hasContainer &&
|
|
6172
|
+
if (hasContainer && existsSync10(destContainer)) {
|
|
5848
6173
|
throw new Error(
|
|
5849
6174
|
`Refusing to restore: ${destContainer} already exists. Remove the current container first (\`monoceros remove ${name}\`).`
|
|
5850
6175
|
);
|
|
5851
6176
|
}
|
|
5852
|
-
await
|
|
5853
|
-
await
|
|
6177
|
+
await fs15.mkdir(containerConfigsDir(home), { recursive: true });
|
|
6178
|
+
await fs15.copyFile(path15.join(backup, ymlFile), destYml);
|
|
5854
6179
|
if (hasContainer) {
|
|
5855
|
-
await
|
|
6180
|
+
await fs15.cp(containerInBackup, destContainer, { recursive: true });
|
|
5856
6181
|
}
|
|
5857
6182
|
logger.success(`Restored '${name}' from ${prettyPath(backup)}.`);
|
|
5858
6183
|
logger.info(
|
|
@@ -5866,7 +6191,7 @@ async function runRestore(opts) {
|
|
|
5866
6191
|
}
|
|
5867
6192
|
|
|
5868
6193
|
// src/commands/restore.ts
|
|
5869
|
-
var restoreCommand =
|
|
6194
|
+
var restoreCommand = defineCommand18({
|
|
5870
6195
|
meta: {
|
|
5871
6196
|
name: "restore",
|
|
5872
6197
|
group: "lifecycle",
|
|
@@ -5890,9 +6215,9 @@ var restoreCommand = defineCommand17({
|
|
|
5890
6215
|
});
|
|
5891
6216
|
|
|
5892
6217
|
// src/commands/remove-from-url.ts
|
|
5893
|
-
import { defineCommand as
|
|
6218
|
+
import { defineCommand as defineCommand19 } from "citty";
|
|
5894
6219
|
import { consola as consola23 } from "consola";
|
|
5895
|
-
var removeFromUrlCommand =
|
|
6220
|
+
var removeFromUrlCommand = defineCommand19({
|
|
5896
6221
|
meta: {
|
|
5897
6222
|
name: "remove-from-url",
|
|
5898
6223
|
group: "edit",
|
|
@@ -5932,9 +6257,9 @@ var removeFromUrlCommand = defineCommand18({
|
|
|
5932
6257
|
});
|
|
5933
6258
|
|
|
5934
6259
|
// src/commands/remove-language.ts
|
|
5935
|
-
import { defineCommand as
|
|
6260
|
+
import { defineCommand as defineCommand20 } from "citty";
|
|
5936
6261
|
import { consola as consola24 } from "consola";
|
|
5937
|
-
var removeLanguageCommand =
|
|
6262
|
+
var removeLanguageCommand = defineCommand20({
|
|
5938
6263
|
meta: {
|
|
5939
6264
|
name: "remove-language",
|
|
5940
6265
|
group: "edit",
|
|
@@ -5974,9 +6299,9 @@ var removeLanguageCommand = defineCommand19({
|
|
|
5974
6299
|
});
|
|
5975
6300
|
|
|
5976
6301
|
// src/commands/remove-port.ts
|
|
5977
|
-
import { defineCommand as
|
|
6302
|
+
import { defineCommand as defineCommand21 } from "citty";
|
|
5978
6303
|
import { consola as consola25 } from "consola";
|
|
5979
|
-
var removePortCommand =
|
|
6304
|
+
var removePortCommand = defineCommand21({
|
|
5980
6305
|
meta: {
|
|
5981
6306
|
name: "remove-port",
|
|
5982
6307
|
group: "edit",
|
|
@@ -6022,9 +6347,9 @@ function coerceToken2(raw) {
|
|
|
6022
6347
|
}
|
|
6023
6348
|
|
|
6024
6349
|
// src/commands/remove-repo.ts
|
|
6025
|
-
import { defineCommand as
|
|
6350
|
+
import { defineCommand as defineCommand22 } from "citty";
|
|
6026
6351
|
import { consola as consola26 } from "consola";
|
|
6027
|
-
var removeRepoCommand =
|
|
6352
|
+
var removeRepoCommand = defineCommand22({
|
|
6028
6353
|
meta: {
|
|
6029
6354
|
name: "remove-repo",
|
|
6030
6355
|
group: "edit",
|
|
@@ -6064,9 +6389,9 @@ var removeRepoCommand = defineCommand21({
|
|
|
6064
6389
|
});
|
|
6065
6390
|
|
|
6066
6391
|
// src/commands/remove-service.ts
|
|
6067
|
-
import { defineCommand as
|
|
6392
|
+
import { defineCommand as defineCommand23 } from "citty";
|
|
6068
6393
|
import { consola as consola27 } from "consola";
|
|
6069
|
-
var removeServiceCommand =
|
|
6394
|
+
var removeServiceCommand = defineCommand23({
|
|
6070
6395
|
meta: {
|
|
6071
6396
|
name: "remove-service",
|
|
6072
6397
|
group: "edit",
|
|
@@ -6106,12 +6431,12 @@ var removeServiceCommand = defineCommand22({
|
|
|
6106
6431
|
});
|
|
6107
6432
|
|
|
6108
6433
|
// src/commands/run.ts
|
|
6109
|
-
import { defineCommand as
|
|
6434
|
+
import { defineCommand as defineCommand24 } from "citty";
|
|
6110
6435
|
import { consola as consola28 } from "consola";
|
|
6111
6436
|
|
|
6112
6437
|
// src/devcontainer/shell.ts
|
|
6113
|
-
import { existsSync as
|
|
6114
|
-
import
|
|
6438
|
+
import { existsSync as existsSync11 } from "fs";
|
|
6439
|
+
import path16 from "path";
|
|
6115
6440
|
async function runShell(opts) {
|
|
6116
6441
|
assertContainerExists(opts.root);
|
|
6117
6442
|
const spawnFn = opts.spawn ?? spawnDevcontainer;
|
|
@@ -6134,7 +6459,7 @@ async function runShell(opts) {
|
|
|
6134
6459
|
);
|
|
6135
6460
|
}
|
|
6136
6461
|
function assertContainerExists(root) {
|
|
6137
|
-
if (!
|
|
6462
|
+
if (!existsSync11(path16.join(root, ".devcontainer"))) {
|
|
6138
6463
|
throw new Error(
|
|
6139
6464
|
`No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
|
|
6140
6465
|
);
|
|
@@ -6170,7 +6495,7 @@ async function runInContainer(opts) {
|
|
|
6170
6495
|
}
|
|
6171
6496
|
|
|
6172
6497
|
// src/commands/run.ts
|
|
6173
|
-
var runCommand =
|
|
6498
|
+
var runCommand = defineCommand24({
|
|
6174
6499
|
meta: {
|
|
6175
6500
|
name: "run",
|
|
6176
6501
|
group: "run",
|
|
@@ -6205,9 +6530,9 @@ var runCommand = defineCommand23({
|
|
|
6205
6530
|
});
|
|
6206
6531
|
|
|
6207
6532
|
// src/commands/shell.ts
|
|
6208
|
-
import { defineCommand as
|
|
6533
|
+
import { defineCommand as defineCommand25 } from "citty";
|
|
6209
6534
|
import { consola as consola29 } from "consola";
|
|
6210
|
-
var shellCommand =
|
|
6535
|
+
var shellCommand = defineCommand25({
|
|
6211
6536
|
meta: {
|
|
6212
6537
|
name: "shell",
|
|
6213
6538
|
group: "run",
|
|
@@ -6232,9 +6557,9 @@ var shellCommand = defineCommand24({
|
|
|
6232
6557
|
});
|
|
6233
6558
|
|
|
6234
6559
|
// src/commands/start.ts
|
|
6235
|
-
import { defineCommand as
|
|
6560
|
+
import { defineCommand as defineCommand26 } from "citty";
|
|
6236
6561
|
import { consola as consola30 } from "consola";
|
|
6237
|
-
var startCommand =
|
|
6562
|
+
var startCommand = defineCommand26({
|
|
6238
6563
|
meta: {
|
|
6239
6564
|
name: "start",
|
|
6240
6565
|
group: "run",
|
|
@@ -6273,8 +6598,8 @@ var startCommand = defineCommand25({
|
|
|
6273
6598
|
});
|
|
6274
6599
|
|
|
6275
6600
|
// src/commands/status.ts
|
|
6276
|
-
import { defineCommand as
|
|
6277
|
-
var statusCommand =
|
|
6601
|
+
import { defineCommand as defineCommand27 } from "citty";
|
|
6602
|
+
var statusCommand = defineCommand27({
|
|
6278
6603
|
meta: {
|
|
6279
6604
|
name: "status",
|
|
6280
6605
|
group: "run",
|
|
@@ -6302,9 +6627,9 @@ var statusCommand = defineCommand26({
|
|
|
6302
6627
|
});
|
|
6303
6628
|
|
|
6304
6629
|
// src/commands/stop.ts
|
|
6305
|
-
import { defineCommand as
|
|
6630
|
+
import { defineCommand as defineCommand28 } from "citty";
|
|
6306
6631
|
import { consola as consola31 } from "consola";
|
|
6307
|
-
var stopCommand =
|
|
6632
|
+
var stopCommand = defineCommand28({
|
|
6308
6633
|
meta: {
|
|
6309
6634
|
name: "stop",
|
|
6310
6635
|
group: "run",
|
|
@@ -6342,7 +6667,7 @@ var stopCommand = defineCommand27({
|
|
|
6342
6667
|
});
|
|
6343
6668
|
|
|
6344
6669
|
// src/main.ts
|
|
6345
|
-
var main =
|
|
6670
|
+
var main = defineCommand29({
|
|
6346
6671
|
meta: {
|
|
6347
6672
|
name: "monoceros",
|
|
6348
6673
|
version: CLI_VERSION,
|
|
@@ -6375,7 +6700,8 @@ var main = defineCommand28({
|
|
|
6375
6700
|
"remove-repo": removeRepoCommand,
|
|
6376
6701
|
"remove-port": removePortCommand,
|
|
6377
6702
|
port: portCommand,
|
|
6378
|
-
completion: completionCommand
|
|
6703
|
+
completion: completionCommand,
|
|
6704
|
+
__complete: __completeCommand
|
|
6379
6705
|
}
|
|
6380
6706
|
});
|
|
6381
6707
|
|