@getmonoceros/workbench 1.36.9 → 1.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +421 -637
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
- package/templates/monoceros-config.sample.env +34 -0
package/dist/bin.js
CHANGED
|
@@ -74,7 +74,7 @@ ${issues}`);
|
|
|
74
74
|
}
|
|
75
75
|
return result.data;
|
|
76
76
|
}
|
|
77
|
-
var SOLUTION_NAME_RE, APT_PACKAGE_NAME_RE, RUNTIME_VERSION_RE, FEATURE_REF_RE, INSTALL_URL_RE, REPO_URL_RE, REPO_PATH_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, LanguageOptionValueSchema, LanguageEntrySchema, 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, REGEX, PROVIDER_VALUES, PROVIDER_LABEL, GIT_CREDENTIAL_USERNAME, REPO_DOCS_URL, KNOWN_PROVIDER_HOSTS, CONFIG_SCHEMA_VERSION, FeatureOptionValueSchema, FeatureEntrySchema, EMAIL_RE, GitUserSchema, RepoEntrySchema, PortEntrySchema, RoutingSchema, SERVICE_NAME_RE, ServiceEnvValueSchema, ServiceHealthcheckSchema, SERVICE_RESTART_VALUES, ServiceObjectSchema, LanguageOptionValueSchema, LanguageEntrySchema, SolutionConfigSchema;
|
|
78
78
|
var init_schema = __esm({
|
|
79
79
|
"src/config/schema.ts"() {
|
|
80
80
|
"use strict";
|
|
@@ -93,12 +93,18 @@ var init_schema = __esm({
|
|
|
93
93
|
repoUrl: REPO_URL_RE,
|
|
94
94
|
repoPath: REPO_PATH_RE
|
|
95
95
|
};
|
|
96
|
-
PROVIDER_VALUES = [
|
|
97
|
-
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
101
|
-
|
|
96
|
+
PROVIDER_VALUES = ["github", "gitlab", "bitbucket"];
|
|
97
|
+
PROVIDER_LABEL = {
|
|
98
|
+
github: "GitHub",
|
|
99
|
+
gitlab: "GitLab",
|
|
100
|
+
bitbucket: "Bitbucket"
|
|
101
|
+
};
|
|
102
|
+
GIT_CREDENTIAL_USERNAME = {
|
|
103
|
+
github: "oauth2",
|
|
104
|
+
gitlab: "oauth2",
|
|
105
|
+
bitbucket: "x-bitbucket-api-token-auth"
|
|
106
|
+
};
|
|
107
|
+
REPO_DOCS_URL = "https://getmonoceros.build/docs/concepts/git-and-repos/";
|
|
102
108
|
KNOWN_PROVIDER_HOSTS = {
|
|
103
109
|
"github.com": "github",
|
|
104
110
|
"gitlab.com": "gitlab",
|
|
@@ -394,6 +400,9 @@ function containerLogsDir(name, home = monocerosHome()) {
|
|
|
394
400
|
function monocerosConfigPath(home = monocerosHome()) {
|
|
395
401
|
return path.join(home, "monoceros-config.yml");
|
|
396
402
|
}
|
|
403
|
+
function globalEnvPath(home = monocerosHome()) {
|
|
404
|
+
return path.join(home, "monoceros-config.env");
|
|
405
|
+
}
|
|
397
406
|
function prettyPath(p) {
|
|
398
407
|
const home = os.homedir();
|
|
399
408
|
if (!home) return p;
|
|
@@ -645,6 +654,9 @@ function wrapToComment(text, width) {
|
|
|
645
654
|
function featureOptionVarName(ref, optionKey) {
|
|
646
655
|
const leaf = ref.split("/").pop() ?? ref;
|
|
647
656
|
const id = leaf.split("@")[0].split(":")[0];
|
|
657
|
+
if (optionKey === "apiToken" && PROVIDER_TOKEN_VAR[id]) {
|
|
658
|
+
return PROVIDER_TOKEN_VAR[id];
|
|
659
|
+
}
|
|
648
660
|
const idSnake = id.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
649
661
|
const optSnake = optionKey.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
|
|
650
662
|
return `${idSnake}_${optSnake}`;
|
|
@@ -655,11 +667,15 @@ function featureOptionHints(summary, ref, activeKeys = []) {
|
|
|
655
667
|
return { key, envVar, placeholder: `\${${envVar}}` };
|
|
656
668
|
});
|
|
657
669
|
}
|
|
658
|
-
var FEATURE_HEADER_WIDTH;
|
|
670
|
+
var FEATURE_HEADER_WIDTH, PROVIDER_TOKEN_VAR;
|
|
659
671
|
var init_feature_doc = __esm({
|
|
660
672
|
"src/init/feature-doc.ts"() {
|
|
661
673
|
"use strict";
|
|
662
674
|
FEATURE_HEADER_WIDTH = 76 - 2;
|
|
675
|
+
PROVIDER_TOKEN_VAR = {
|
|
676
|
+
"github-cli": "GITHUB_API_TOKEN",
|
|
677
|
+
"gitlab-cli": "GITLAB_API_TOKEN"
|
|
678
|
+
};
|
|
663
679
|
}
|
|
664
680
|
});
|
|
665
681
|
|
|
@@ -1167,6 +1183,7 @@ function makePalette(isTty2) {
|
|
|
1167
1183
|
underline: (s) => wrap(s, ANSI_UNDERLINE2),
|
|
1168
1184
|
cyan: (s) => wrap(s, ANSI_CYAN2),
|
|
1169
1185
|
green: (s) => wrap(s, ANSI_GREEN),
|
|
1186
|
+
yellow: (s) => wrap(s, ANSI_YELLOW),
|
|
1170
1187
|
dim: (s) => wrap(s, ANSI_GREY2),
|
|
1171
1188
|
sectionLine: (label) => wrap(`\u25B8 ${label}`, ANSI_BOLD2, ANSI_UNDERLINE2)
|
|
1172
1189
|
};
|
|
@@ -1174,7 +1191,7 @@ function makePalette(isTty2) {
|
|
|
1174
1191
|
function colorsFor(stream) {
|
|
1175
1192
|
return makePalette(stream.isTTY ?? false);
|
|
1176
1193
|
}
|
|
1177
|
-
var ESC, ANSI_BOLD2, ANSI_UNDERLINE2, ANSI_CYAN2, ANSI_GREEN, ANSI_GREY2, ANSI_RESET2, ANSI_RE2, stderrPalette, bold2, underline2, cyan2, green, dim, sectionLine;
|
|
1194
|
+
var ESC, ANSI_BOLD2, ANSI_UNDERLINE2, ANSI_CYAN2, ANSI_GREEN, ANSI_YELLOW, ANSI_GREY2, ANSI_RESET2, ANSI_RE2, stderrPalette, bold2, underline2, cyan2, green, yellow, dim, sectionLine;
|
|
1178
1195
|
var init_format = __esm({
|
|
1179
1196
|
"src/util/format.ts"() {
|
|
1180
1197
|
"use strict";
|
|
@@ -1183,6 +1200,7 @@ var init_format = __esm({
|
|
|
1183
1200
|
ANSI_UNDERLINE2 = `${ESC}4m`;
|
|
1184
1201
|
ANSI_CYAN2 = `${ESC}36m`;
|
|
1185
1202
|
ANSI_GREEN = `${ESC}32m`;
|
|
1203
|
+
ANSI_YELLOW = `${ESC}33m`;
|
|
1186
1204
|
ANSI_GREY2 = `${ESC}90m`;
|
|
1187
1205
|
ANSI_RESET2 = `${ESC}0m`;
|
|
1188
1206
|
ANSI_RE2 = /\x1b\[[0-9;]*m/g;
|
|
@@ -1191,13 +1209,13 @@ var init_format = __esm({
|
|
|
1191
1209
|
underline2 = stderrPalette.underline;
|
|
1192
1210
|
cyan2 = stderrPalette.cyan;
|
|
1193
1211
|
green = stderrPalette.green;
|
|
1212
|
+
yellow = stderrPalette.yellow;
|
|
1194
1213
|
dim = stderrPalette.dim;
|
|
1195
1214
|
sectionLine = stderrPalette.sectionLine;
|
|
1196
1215
|
}
|
|
1197
1216
|
});
|
|
1198
1217
|
|
|
1199
1218
|
// src/devcontainer/credentials.ts
|
|
1200
|
-
import { spawn } from "child_process";
|
|
1201
1219
|
import { promises as fs3 } from "fs";
|
|
1202
1220
|
import path5 from "path";
|
|
1203
1221
|
function resolveProvider(host, explicit) {
|
|
@@ -1220,128 +1238,6 @@ function uniqueHttpsHosts(repos) {
|
|
|
1220
1238
|
}
|
|
1221
1239
|
return [...byHost.values()];
|
|
1222
1240
|
}
|
|
1223
|
-
function installCommandForOS(opts) {
|
|
1224
|
-
const withBrewBootstrap = (cmd) => [
|
|
1225
|
-
"",
|
|
1226
|
-
cyan2(BREW_INSTALL_COMMAND),
|
|
1227
|
-
cyan2(cmd),
|
|
1228
|
-
"",
|
|
1229
|
-
dim("(Skip the first line if you already have Homebrew.)")
|
|
1230
|
-
].join("\n");
|
|
1231
|
-
if (process.platform === "darwin") return withBrewBootstrap(opts.brew);
|
|
1232
|
-
if (opts.linuxBrew) return withBrewBootstrap(opts.linuxBrew);
|
|
1233
|
-
return `See ${opts.linuxDocsUrl} for package instructions.`;
|
|
1234
|
-
}
|
|
1235
|
-
function providerSetupHint(host, provider) {
|
|
1236
|
-
if (provider === "github") {
|
|
1237
|
-
const isSaas = host.toLowerCase() === "github.com";
|
|
1238
|
-
const hostArg = isSaas ? "" : ` --hostname ${host}`;
|
|
1239
|
-
const install = installCommandForOS({
|
|
1240
|
-
brew: "brew install gh",
|
|
1241
|
-
linuxBrew: "brew install gh",
|
|
1242
|
-
linuxDocsUrl: "https://github.com/cli/cli#installation"
|
|
1243
|
-
});
|
|
1244
|
-
return {
|
|
1245
|
-
title: `${host} \u2014 GitHub`,
|
|
1246
|
-
body: [
|
|
1247
|
-
"Install the GitHub CLI:",
|
|
1248
|
-
install,
|
|
1249
|
-
"",
|
|
1250
|
-
"Then run once:",
|
|
1251
|
-
cyan2(`gh auth login${hostArg}`),
|
|
1252
|
-
cyan2(`gh auth setup-git${hostArg}`),
|
|
1253
|
-
"",
|
|
1254
|
-
"`gh auth login` walks through OAuth in your browser.",
|
|
1255
|
-
"`gh auth setup-git` wires gh into git as a credential helper."
|
|
1256
|
-
].join("\n")
|
|
1257
|
-
};
|
|
1258
|
-
}
|
|
1259
|
-
if (provider === "gitlab") {
|
|
1260
|
-
const isSaas = host.toLowerCase() === "gitlab.com";
|
|
1261
|
-
const hostArg = isSaas ? "" : ` --hostname ${host}`;
|
|
1262
|
-
const install = installCommandForOS({
|
|
1263
|
-
brew: "brew install glab",
|
|
1264
|
-
linuxBrew: "brew install glab",
|
|
1265
|
-
linuxDocsUrl: "https://gitlab.com/gitlab-org/cli#installation"
|
|
1266
|
-
});
|
|
1267
|
-
return {
|
|
1268
|
-
title: `${host} \u2014 GitLab`,
|
|
1269
|
-
body: [
|
|
1270
|
-
"Install the GitLab CLI (glab):",
|
|
1271
|
-
install,
|
|
1272
|
-
"",
|
|
1273
|
-
"Then run once:",
|
|
1274
|
-
cyan2(`glab auth login${hostArg}`),
|
|
1275
|
-
"",
|
|
1276
|
-
"Choose `HTTPS` when asked for git-protocol, then accept",
|
|
1277
|
-
'"Authenticate Git with your GitLab credentials" \u2014 glab',
|
|
1278
|
-
"configures itself as the git credential helper."
|
|
1279
|
-
].join("\n")
|
|
1280
|
-
};
|
|
1281
|
-
}
|
|
1282
|
-
if (provider === "bitbucket") {
|
|
1283
|
-
const isCloud = host.toLowerCase() === "bitbucket.org";
|
|
1284
|
-
if (isCloud) {
|
|
1285
|
-
return {
|
|
1286
|
-
title: `${host} \u2014 Bitbucket Cloud`,
|
|
1287
|
-
body: [
|
|
1288
|
-
"Bitbucket has no first-party CLI for git-credentials, so this",
|
|
1289
|
-
"is a manual one-time setup. Generate an Atlassian API token at",
|
|
1290
|
-
"https://id.atlassian.com/manage-profile/security/api-tokens",
|
|
1291
|
-
"",
|
|
1292
|
-
"Then store it via your OS credential helper:",
|
|
1293
|
-
cyan2(
|
|
1294
|
-
`git credential approve <<< $'protocol=https\\nhost=${host}\\nusername=<your-atlassian-email>\\npassword=<token>\\n'`
|
|
1295
|
-
)
|
|
1296
|
-
].join("\n")
|
|
1297
|
-
};
|
|
1298
|
-
}
|
|
1299
|
-
return {
|
|
1300
|
-
title: `${host} \u2014 Bitbucket Data Center`,
|
|
1301
|
-
body: [
|
|
1302
|
-
"Bitbucket has no first-party CLI for git-credentials, so this",
|
|
1303
|
-
"is a manual one-time setup. Generate a personal HTTP access",
|
|
1304
|
-
`token in your Bitbucket UI: profile picture (top right on ${host})`,
|
|
1305
|
-
"\u2192 Manage account \u2192 HTTP access tokens \u2192 Create token. Give it",
|
|
1306
|
-
"at least repo-read + repo-write scopes for the repos you need.",
|
|
1307
|
-
"",
|
|
1308
|
-
"Then store it via your OS credential helper:",
|
|
1309
|
-
cyan2(
|
|
1310
|
-
`git credential approve <<< $'protocol=https\\nhost=${host}\\nusername=<your-bitbucket-username>\\npassword=<token>\\n'`
|
|
1311
|
-
)
|
|
1312
|
-
].join("\n")
|
|
1313
|
-
};
|
|
1314
|
-
}
|
|
1315
|
-
return {
|
|
1316
|
-
title: `${host} \u2014 Gitea`,
|
|
1317
|
-
body: [
|
|
1318
|
-
"Gitea has no first-party CLI helper for git-credentials (the",
|
|
1319
|
-
"`tea` CLI logs into its own config, not into your git credential",
|
|
1320
|
-
"helper), so this is a manual one-time setup. Generate an access",
|
|
1321
|
-
`token in your Gitea UI: profile picture (top right on ${host}) \u2192`,
|
|
1322
|
-
'Settings \u2192 Applications \u2192 "Generate New Token". Give it at',
|
|
1323
|
-
"least the `read:repository` scope (add `write:repository` if you",
|
|
1324
|
-
"need push from the container).",
|
|
1325
|
-
"",
|
|
1326
|
-
"Then store it via your OS credential helper:",
|
|
1327
|
-
cyan2(
|
|
1328
|
-
`git credential approve <<< $'protocol=https\\nhost=${host}\\nusername=<your-gitea-username>\\npassword=<token>\\n'`
|
|
1329
|
-
)
|
|
1330
|
-
].join("\n")
|
|
1331
|
-
};
|
|
1332
|
-
}
|
|
1333
|
-
function parseCredentialFillOutput(output) {
|
|
1334
|
-
const result = {};
|
|
1335
|
-
for (const line of output.split("\n")) {
|
|
1336
|
-
const eqIdx = line.indexOf("=");
|
|
1337
|
-
if (eqIdx <= 0) continue;
|
|
1338
|
-
const key = line.slice(0, eqIdx);
|
|
1339
|
-
const value = line.slice(eqIdx + 1);
|
|
1340
|
-
if (key === "username") result.username = value;
|
|
1341
|
-
if (key === "password") result.password = value;
|
|
1342
|
-
}
|
|
1343
|
-
return result;
|
|
1344
|
-
}
|
|
1345
1241
|
function formatCredentialLine(host, username, password) {
|
|
1346
1242
|
const encUser = encodeURIComponent(username);
|
|
1347
1243
|
const encPass = encodeURIComponent(password);
|
|
@@ -1350,76 +1246,30 @@ function formatCredentialLine(host, username, password) {
|
|
|
1350
1246
|
async function collectGitCredentials(devContainerRoot, hosts, options = {}) {
|
|
1351
1247
|
const credsDir = path5.join(devContainerRoot, ".monoceros");
|
|
1352
1248
|
const credentialsPath = path5.join(credsDir, "git-credentials");
|
|
1353
|
-
const spawnFn = options.spawn ?? realGitCredentialFill;
|
|
1354
|
-
const approveFn = options.approve ?? realGitCredentialApprove;
|
|
1355
|
-
const logger = options.logger ?? { info: () => {
|
|
1356
|
-
}, warn: () => {
|
|
1357
|
-
} };
|
|
1358
1249
|
const lines = [];
|
|
1359
1250
|
const perHost = [];
|
|
1360
1251
|
for (const { host, provider } of hosts) {
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
continue;
|
|
1370
|
-
}
|
|
1371
|
-
logger.info(`Fetching credentials for ${host} from host git\u2026`);
|
|
1372
|
-
const input = `protocol=https
|
|
1373
|
-
host=${host}
|
|
1374
|
-
|
|
1375
|
-
`;
|
|
1376
|
-
let result;
|
|
1377
|
-
try {
|
|
1378
|
-
result = await spawnFn(input);
|
|
1379
|
-
} catch (err) {
|
|
1380
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
1381
|
-
perHost.push({ host, provider, status: "spawn-error", detail });
|
|
1382
|
-
continue;
|
|
1383
|
-
}
|
|
1384
|
-
if (result.exitCode !== 0) {
|
|
1385
|
-
perHost.push({
|
|
1386
|
-
host,
|
|
1387
|
-
provider,
|
|
1388
|
-
status: "non-zero-exit",
|
|
1389
|
-
detail: `exit code ${result.exitCode}`
|
|
1390
|
-
});
|
|
1391
|
-
continue;
|
|
1392
|
-
}
|
|
1393
|
-
const { username, password } = parseCredentialFillOutput(result.stdout);
|
|
1394
|
-
if (!username || !password) {
|
|
1252
|
+
const known = provider === "unknown" ? "github" : provider;
|
|
1253
|
+
const pat = options.patByHost?.get(host);
|
|
1254
|
+
if (pat) {
|
|
1255
|
+
lines.push(
|
|
1256
|
+
formatCredentialLine(host, GIT_CREDENTIAL_USERNAME[known], pat)
|
|
1257
|
+
);
|
|
1258
|
+
perHost.push({ host, provider: known, status: "ok", detail: "" });
|
|
1259
|
+
} else {
|
|
1395
1260
|
perHost.push({
|
|
1396
1261
|
host,
|
|
1397
|
-
provider,
|
|
1398
|
-
status: "no-
|
|
1399
|
-
detail: "
|
|
1262
|
+
provider: known,
|
|
1263
|
+
status: "no-token",
|
|
1264
|
+
detail: "no personal access token configured"
|
|
1400
1265
|
});
|
|
1401
|
-
continue;
|
|
1402
|
-
}
|
|
1403
|
-
lines.push(formatCredentialLine(host, username, password));
|
|
1404
|
-
perHost.push({ host, provider, status: "ok", detail: "" });
|
|
1405
|
-
const approveInput = `protocol=https
|
|
1406
|
-
host=${host}
|
|
1407
|
-
username=${username}
|
|
1408
|
-
password=${password}
|
|
1409
|
-
|
|
1410
|
-
`;
|
|
1411
|
-
try {
|
|
1412
|
-
await approveFn(approveInput);
|
|
1413
|
-
} catch {
|
|
1414
1266
|
}
|
|
1415
1267
|
}
|
|
1416
1268
|
await fs3.mkdir(credsDir, { recursive: true });
|
|
1417
1269
|
await fs3.writeFile(
|
|
1418
1270
|
credentialsPath,
|
|
1419
1271
|
lines.join("\n") + (lines.length > 0 ? "\n" : ""),
|
|
1420
|
-
{
|
|
1421
|
-
mode: 384
|
|
1422
|
-
}
|
|
1272
|
+
{ mode: 384 }
|
|
1423
1273
|
);
|
|
1424
1274
|
return {
|
|
1425
1275
|
hostsWritten: lines.length,
|
|
@@ -1428,95 +1278,146 @@ password=${password}
|
|
|
1428
1278
|
credentialsPath
|
|
1429
1279
|
};
|
|
1430
1280
|
}
|
|
1431
|
-
function formatMissingCredentialsError(missing) {
|
|
1432
|
-
if (missing.length === 1) {
|
|
1433
|
-
const m = missing[0];
|
|
1434
|
-
const hint = providerSetupHint(m.host, m.provider);
|
|
1435
|
-
return [
|
|
1436
|
-
`Missing Git credentials: ${hint.title}`,
|
|
1437
|
-
"",
|
|
1438
|
-
hint.body,
|
|
1439
|
-
"",
|
|
1440
|
-
`Then re-run ${cyan2("monoceros apply")}.`
|
|
1441
|
-
].join("\n");
|
|
1442
|
-
}
|
|
1443
|
-
const lines = [
|
|
1444
|
-
`Missing Git credentials for ${missing.length} hosts:`,
|
|
1445
|
-
""
|
|
1446
|
-
];
|
|
1447
|
-
for (const m of missing) {
|
|
1448
|
-
const hint = providerSetupHint(m.host, m.provider);
|
|
1449
|
-
lines.push(hint.title);
|
|
1450
|
-
lines.push("");
|
|
1451
|
-
lines.push(hint.body);
|
|
1452
|
-
lines.push("");
|
|
1453
|
-
}
|
|
1454
|
-
lines.push(`Then re-run ${cyan2("monoceros apply")}.`);
|
|
1455
|
-
return lines.join("\n");
|
|
1456
|
-
}
|
|
1457
1281
|
function formatUnknownProviderError(hosts) {
|
|
1458
1282
|
const sorted = [...new Set(hosts)].sort();
|
|
1459
1283
|
const lines = [
|
|
1460
1284
|
sorted.length === 1 ? `Unknown Git provider for host ${sorted[0]}.` : `Unknown Git provider for ${sorted.length} hosts: ${sorted.join(", ")}.`,
|
|
1461
1285
|
"",
|
|
1462
1286
|
"Monoceros auto-detects only github.com / gitlab.com / bitbucket.org.",
|
|
1463
|
-
"For any other host (self-hosted GitLab,
|
|
1464
|
-
"declare the provider explicitly in the yml. Edit the
|
|
1287
|
+
"For any other host (self-hosted GitLab, GitHub Enterprise, Bitbucket",
|
|
1288
|
+
"Data Center) declare the provider explicitly in the yml. Edit the",
|
|
1289
|
+
"repo entry:",
|
|
1465
1290
|
"",
|
|
1466
1291
|
cyan2(" repos:"),
|
|
1467
1292
|
cyan2(` - url: https://${sorted[0]}/\u2026`),
|
|
1468
|
-
cyan2(" provider: gitlab # or: github, bitbucket
|
|
1293
|
+
cyan2(" provider: gitlab # or: github, bitbucket"),
|
|
1469
1294
|
"",
|
|
1470
|
-
`Or re-add with ${cyan2("monoceros add-repo <name> <url> --provider=<github|gitlab|bitbucket
|
|
1295
|
+
`Or re-add with ${cyan2("monoceros add-repo <name> <url> --provider=<github|gitlab|bitbucket>")}.`
|
|
1471
1296
|
];
|
|
1472
1297
|
return lines.join("\n");
|
|
1473
1298
|
}
|
|
1474
|
-
var realGitCredentialFill, realGitCredentialApprove, BREW_INSTALL_COMMAND;
|
|
1475
1299
|
var init_credentials = __esm({
|
|
1476
1300
|
"src/devcontainer/credentials.ts"() {
|
|
1477
1301
|
"use strict";
|
|
1478
1302
|
init_schema();
|
|
1479
1303
|
init_format();
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
// src/apply/repo-token.ts
|
|
1308
|
+
import { promises as fs4 } from "fs";
|
|
1309
|
+
function workspaceSegment(url) {
|
|
1310
|
+
let ws;
|
|
1311
|
+
try {
|
|
1312
|
+
ws = new URL(url).pathname.split("/").filter(Boolean)[0];
|
|
1313
|
+
} catch {
|
|
1314
|
+
return void 0;
|
|
1315
|
+
}
|
|
1316
|
+
return ws ? ws.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase() : void 0;
|
|
1317
|
+
}
|
|
1318
|
+
function tokenVarCandidates(provider, url) {
|
|
1319
|
+
const p = provider.toUpperCase();
|
|
1320
|
+
const vars = [`${p}_API_TOKEN`];
|
|
1321
|
+
const segment = workspaceSegment(url);
|
|
1322
|
+
if (segment) vars.push(`GIT_TOKEN__${p}_${segment}`);
|
|
1323
|
+
vars.push(`GIT_TOKEN__${p}`);
|
|
1324
|
+
return vars;
|
|
1325
|
+
}
|
|
1326
|
+
function resolveRepoTokens(config, catalog, envVars) {
|
|
1327
|
+
const features = config.features.map((f) => ({
|
|
1328
|
+
...f,
|
|
1329
|
+
...f.options ? { options: { ...f.options } } : {}
|
|
1330
|
+
}));
|
|
1331
|
+
const hostTokens = /* @__PURE__ */ new Map();
|
|
1332
|
+
const used = [];
|
|
1333
|
+
const missing = [];
|
|
1334
|
+
for (const repo of config.repos ?? []) {
|
|
1335
|
+
if (!repo.url.startsWith("https://")) continue;
|
|
1336
|
+
let host;
|
|
1337
|
+
try {
|
|
1338
|
+
host = new URL(repo.url).hostname;
|
|
1339
|
+
} catch {
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
const provider = resolveProvider(host, repo.provider);
|
|
1343
|
+
if (provider === "unknown") continue;
|
|
1344
|
+
const ref = catalog.get(provider)?.file.contributes.features?.[0]?.ref;
|
|
1345
|
+
const tried = tokenVarCandidates(provider, repo.url);
|
|
1346
|
+
const hit = tried.find((v) => (envVars[v] ?? "").trim().length > 0);
|
|
1347
|
+
if (!hit) {
|
|
1348
|
+
missing.push({ host, provider, tried });
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
const token = envVars[hit].trim();
|
|
1352
|
+
hostTokens.set(host, token);
|
|
1353
|
+
used.push({ host, provider, varName: hit });
|
|
1354
|
+
if (ref) {
|
|
1355
|
+
const feature = features.find((f) => f.ref === ref);
|
|
1356
|
+
if (feature) feature.options = { ...feature.options, apiToken: token };
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
return {
|
|
1360
|
+
features,
|
|
1361
|
+
hostTokens,
|
|
1362
|
+
used,
|
|
1363
|
+
missing
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
async function resolveContainerRepoTokens(name, home, catalog) {
|
|
1367
|
+
const ymlPath = containerConfigPath(name, home);
|
|
1368
|
+
const { config } = parseConfig(await fs4.readFile(ymlPath, "utf8"), ymlPath);
|
|
1369
|
+
const envVars = {
|
|
1370
|
+
...readEnvFile(globalEnvPath(home)),
|
|
1371
|
+
...readEnvFile(containerEnvPath(name, home))
|
|
1372
|
+
};
|
|
1373
|
+
return resolveRepoTokens(config, catalog, envVars);
|
|
1374
|
+
}
|
|
1375
|
+
function formatTokenUse(use) {
|
|
1376
|
+
return `${PROVIDER_LABEL[use.provider]} (${use.host}) \u2192 ${use.varName}`;
|
|
1377
|
+
}
|
|
1378
|
+
function formatUnauthenticatedRepos(missing, containerName2) {
|
|
1379
|
+
const lines = [
|
|
1380
|
+
bold2(yellow("\u26A0 Repo access \u2014 action needed")),
|
|
1381
|
+
"",
|
|
1382
|
+
yellow(" Some repositories are UNAUTHENTICATED:")
|
|
1383
|
+
];
|
|
1384
|
+
for (const m of missing) {
|
|
1385
|
+
lines.push(` \u2022 ${PROVIDER_LABEL[m.provider]} (${m.host})`);
|
|
1386
|
+
}
|
|
1387
|
+
lines.push(
|
|
1388
|
+
"",
|
|
1389
|
+
bold2(" Public repositories still clone (read-only). But:"),
|
|
1390
|
+
" \u2022 gh / glab in the container are not logged in.",
|
|
1391
|
+
" \u2022 pushing, and cloning/pulling PRIVATE repositories, fails.",
|
|
1392
|
+
" \u2022 branches, PRs/MRs \u2014 anything that writes to the remote \u2014 fails.",
|
|
1393
|
+
"",
|
|
1394
|
+
bold2(" Set a token, then re-apply:")
|
|
1395
|
+
);
|
|
1396
|
+
for (const m of missing) {
|
|
1397
|
+
const featureVar = m.tried.find((v) => !v.startsWith("GIT_TOKEN__"));
|
|
1398
|
+
const sharedVar = m.tried.find((v) => v.startsWith("GIT_TOKEN__"));
|
|
1399
|
+
lines.push(
|
|
1400
|
+
` \u2022 ${PROVIDER_LABEL[m.provider]}: ${cyan2(featureVar)} in container-configs/${containerName2}.env,`,
|
|
1401
|
+
` or ${cyan2(sharedVar)} in monoceros-config.env`
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
lines.push("", ` Details: ${cyan2(REPO_DOCS_URL)}`);
|
|
1405
|
+
return lines.join("\n");
|
|
1406
|
+
}
|
|
1407
|
+
var init_repo_token = __esm({
|
|
1408
|
+
"src/apply/repo-token.ts"() {
|
|
1409
|
+
"use strict";
|
|
1410
|
+
init_schema();
|
|
1411
|
+
init_io();
|
|
1412
|
+
init_env_file();
|
|
1413
|
+
init_paths();
|
|
1414
|
+
init_credentials();
|
|
1415
|
+
init_format();
|
|
1515
1416
|
}
|
|
1516
1417
|
});
|
|
1517
1418
|
|
|
1518
1419
|
// src/devcontainer/locate-running.ts
|
|
1519
|
-
import { spawn
|
|
1420
|
+
import { spawn } from "child_process";
|
|
1520
1421
|
async function findRunningContainerByLocalFolder(containerPath, opts = {}) {
|
|
1521
1422
|
const docker = opts.docker ?? realDockerLookup;
|
|
1522
1423
|
const result = await docker([
|
|
@@ -1537,7 +1438,7 @@ var init_locate_running = __esm({
|
|
|
1537
1438
|
"use strict";
|
|
1538
1439
|
realDockerLookup = (args) => {
|
|
1539
1440
|
return new Promise((resolve, reject) => {
|
|
1540
|
-
const child =
|
|
1441
|
+
const child = spawn("docker", args, {
|
|
1541
1442
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1542
1443
|
});
|
|
1543
1444
|
let stdout = "";
|
|
@@ -1557,7 +1458,7 @@ var init_locate_running = __esm({
|
|
|
1557
1458
|
};
|
|
1558
1459
|
realContainerExec = (containerId, argv) => {
|
|
1559
1460
|
return new Promise((resolve, reject) => {
|
|
1560
|
-
const child =
|
|
1461
|
+
const child = spawn("docker", ["exec", containerId, ...argv], {
|
|
1561
1462
|
// Inherit stdio so live git output reaches the user.
|
|
1562
1463
|
stdio: ["ignore", "inherit", "inherit"]
|
|
1563
1464
|
});
|
|
@@ -1569,7 +1470,7 @@ var init_locate_running = __esm({
|
|
|
1569
1470
|
});
|
|
1570
1471
|
|
|
1571
1472
|
// src/config/global.ts
|
|
1572
|
-
import { promises as
|
|
1473
|
+
import { promises as fs5 } from "fs";
|
|
1573
1474
|
import { z as z3 } from "zod";
|
|
1574
1475
|
import { isMap, Pair, parseDocument as parseDocument2, Scalar, YAMLMap } from "yaml";
|
|
1575
1476
|
async function readMonocerosConfig(opts = {}) {
|
|
@@ -1577,7 +1478,7 @@ async function readMonocerosConfig(opts = {}) {
|
|
|
1577
1478
|
const filePath = monocerosConfigPath(home);
|
|
1578
1479
|
let text;
|
|
1579
1480
|
try {
|
|
1580
|
-
text = await
|
|
1481
|
+
text = await fs5.readFile(filePath, "utf8");
|
|
1581
1482
|
} catch {
|
|
1582
1483
|
return void 0;
|
|
1583
1484
|
}
|
|
@@ -1613,7 +1514,7 @@ async function writeGlobalDefaultGitUser(user, opts = {}) {
|
|
|
1613
1514
|
const filePath = monocerosConfigPath(home);
|
|
1614
1515
|
let text;
|
|
1615
1516
|
try {
|
|
1616
|
-
text = await
|
|
1517
|
+
text = await fs5.readFile(filePath, "utf8");
|
|
1617
1518
|
} catch {
|
|
1618
1519
|
text = void 0;
|
|
1619
1520
|
}
|
|
@@ -1630,8 +1531,8 @@ async function writeGlobalDefaultGitUser(user, opts = {}) {
|
|
|
1630
1531
|
` email: ${user.email}`,
|
|
1631
1532
|
""
|
|
1632
1533
|
].join("\n");
|
|
1633
|
-
await
|
|
1634
|
-
await
|
|
1534
|
+
await fs5.mkdir(home, { recursive: true });
|
|
1535
|
+
await fs5.writeFile(filePath, fresh, "utf8");
|
|
1635
1536
|
return { filePath, created: true, alreadySet: false };
|
|
1636
1537
|
}
|
|
1637
1538
|
const doc = parseDocument2(text, { prettyErrors: true });
|
|
@@ -1652,7 +1553,7 @@ async function writeGlobalDefaultGitUser(user, opts = {}) {
|
|
|
1652
1553
|
userMap.set("name", user.name);
|
|
1653
1554
|
userMap.set("email", user.email);
|
|
1654
1555
|
const newText = String(doc);
|
|
1655
|
-
await
|
|
1556
|
+
await fs5.writeFile(filePath, newText, "utf8");
|
|
1656
1557
|
return { filePath, created: false, alreadySet: false };
|
|
1657
1558
|
}
|
|
1658
1559
|
function ensureMap(doc, key) {
|
|
@@ -1913,8 +1814,8 @@ var init_components = __esm({
|
|
|
1913
1814
|
});
|
|
1914
1815
|
|
|
1915
1816
|
// src/proxy/index.ts
|
|
1916
|
-
import { spawn as
|
|
1917
|
-
import { promises as
|
|
1817
|
+
import { spawn as spawn2 } from "child_process";
|
|
1818
|
+
import { promises as fs6 } from "fs";
|
|
1918
1819
|
import path6 from "path";
|
|
1919
1820
|
function proxyDynamicDir(home) {
|
|
1920
1821
|
return path6.join(home ?? monocerosHome(), "traefik", "dynamic");
|
|
@@ -1922,7 +1823,7 @@ function proxyDynamicDir(home) {
|
|
|
1922
1823
|
async function ensureProxy(opts = {}) {
|
|
1923
1824
|
const docker = opts.docker ?? realDocker;
|
|
1924
1825
|
const dyn = proxyDynamicDir(opts.monocerosHome);
|
|
1925
|
-
await
|
|
1826
|
+
await fs6.mkdir(dyn, { recursive: true });
|
|
1926
1827
|
const netInspect = await docker(["network", "inspect", PROXY_NETWORK_NAME]);
|
|
1927
1828
|
if (netInspect.exitCode !== 0) {
|
|
1928
1829
|
const create = await docker(["network", "create", PROXY_NETWORK_NAME]);
|
|
@@ -2028,7 +1929,7 @@ var init_proxy = __esm({
|
|
|
2028
1929
|
PROXY_RESTART_POLICY = "unless-stopped";
|
|
2029
1930
|
defaultDockerExec = (args) => {
|
|
2030
1931
|
return new Promise((resolve, reject) => {
|
|
2031
|
-
const child =
|
|
1932
|
+
const child = spawn2("docker", args, {
|
|
2032
1933
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2033
1934
|
});
|
|
2034
1935
|
let stdout = "";
|
|
@@ -2051,7 +1952,7 @@ var init_proxy = __esm({
|
|
|
2051
1952
|
});
|
|
2052
1953
|
|
|
2053
1954
|
// src/proxy/dynamic.ts
|
|
2054
|
-
import { promises as
|
|
1955
|
+
import { promises as fs7 } from "fs";
|
|
2055
1956
|
import path7 from "path";
|
|
2056
1957
|
async function writeDynamicConfig(name, ports, opts = {}) {
|
|
2057
1958
|
if (ports.length === 0) {
|
|
@@ -2060,14 +1961,14 @@ async function writeDynamicConfig(name, ports, opts = {}) {
|
|
|
2060
1961
|
);
|
|
2061
1962
|
}
|
|
2062
1963
|
const dir = proxyDynamicDir(opts.monocerosHome);
|
|
2063
|
-
await
|
|
1964
|
+
await fs7.mkdir(dir, { recursive: true });
|
|
2064
1965
|
const file = path7.join(dir, `${name}.yml`);
|
|
2065
|
-
await
|
|
1966
|
+
await fs7.writeFile(file, renderDynamicConfig(name, ports), "utf8");
|
|
2066
1967
|
return file;
|
|
2067
1968
|
}
|
|
2068
1969
|
async function removeDynamicConfig(name, opts = {}) {
|
|
2069
1970
|
const file = path7.join(proxyDynamicDir(opts.monocerosHome), `${name}.yml`);
|
|
2070
|
-
await
|
|
1971
|
+
await fs7.rm(file, { force: true });
|
|
2071
1972
|
}
|
|
2072
1973
|
function renderDynamicConfig(name, ports) {
|
|
2073
1974
|
const lines = [];
|
|
@@ -2712,8 +2613,8 @@ var init_claude_settings = __esm({
|
|
|
2712
2613
|
});
|
|
2713
2614
|
|
|
2714
2615
|
// src/devcontainer/ssh-attach.ts
|
|
2715
|
-
import { spawn as
|
|
2716
|
-
import { promises as
|
|
2616
|
+
import { spawn as spawn3 } from "child_process";
|
|
2617
|
+
import { promises as fs8 } from "fs";
|
|
2717
2618
|
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
2718
2619
|
import os2 from "os";
|
|
2719
2620
|
import path9 from "path";
|
|
@@ -2735,7 +2636,7 @@ async function ensureKeypair(targetDir, name, keygen, logger) {
|
|
|
2735
2636
|
if (existsSync6(privateKey) && existsSync6(publicKey)) {
|
|
2736
2637
|
return { privateKey, publicKey };
|
|
2737
2638
|
}
|
|
2738
|
-
await
|
|
2639
|
+
await fs8.mkdir(path9.dirname(privateKey), { recursive: true });
|
|
2739
2640
|
try {
|
|
2740
2641
|
const res = await keygen([
|
|
2741
2642
|
"-t",
|
|
@@ -2812,10 +2713,10 @@ async function ensureInclude(userSshDir, home) {
|
|
|
2812
2713
|
const configPath = path9.join(userSshDir, "config");
|
|
2813
2714
|
const includeTarget = path9.join(sshHomeDir(home), "config.d", "*");
|
|
2814
2715
|
const includeLine = `Include "${includeTarget}"`;
|
|
2815
|
-
await
|
|
2716
|
+
await fs8.mkdir(userSshDir, { recursive: true });
|
|
2816
2717
|
let existing = "";
|
|
2817
2718
|
try {
|
|
2818
|
-
existing = await
|
|
2719
|
+
existing = await fs8.readFile(configPath, "utf8");
|
|
2819
2720
|
} catch {
|
|
2820
2721
|
existing = "";
|
|
2821
2722
|
}
|
|
@@ -2826,13 +2727,13 @@ ${includeLine}
|
|
|
2826
2727
|
`;
|
|
2827
2728
|
const next = existing.length > 0 ? `${prefix}
|
|
2828
2729
|
${existing}` : prefix;
|
|
2829
|
-
await
|
|
2830
|
-
await
|
|
2730
|
+
await fs8.writeFile(configPath, next, { mode: 384 });
|
|
2731
|
+
await fs8.chmod(configPath, 384).catch(() => {
|
|
2831
2732
|
});
|
|
2832
2733
|
}
|
|
2833
2734
|
function runCapture(cmd, args) {
|
|
2834
2735
|
return new Promise((resolve, reject) => {
|
|
2835
|
-
const child =
|
|
2736
|
+
const child = spawn3(cmd, args, {
|
|
2836
2737
|
stdio: ["ignore", "pipe", "ignore"]
|
|
2837
2738
|
});
|
|
2838
2739
|
let stdout = "";
|
|
@@ -2929,26 +2830,26 @@ async function upsertMarkedBlock(configPath, hostAlias, body) {
|
|
|
2929
2830
|
const section = `${begin}
|
|
2930
2831
|
${body}
|
|
2931
2832
|
${end}`;
|
|
2932
|
-
await
|
|
2833
|
+
await fs8.mkdir(path9.dirname(configPath), { recursive: true });
|
|
2933
2834
|
let existing = "";
|
|
2934
2835
|
try {
|
|
2935
|
-
existing = await
|
|
2836
|
+
existing = await fs8.readFile(configPath, "utf8");
|
|
2936
2837
|
} catch {
|
|
2937
2838
|
existing = "";
|
|
2938
2839
|
}
|
|
2939
2840
|
const re = new RegExp(`${escapeRegExp2(begin)}[\\s\\S]*?${escapeRegExp2(end)}`);
|
|
2940
2841
|
if (re.test(existing)) {
|
|
2941
|
-
await
|
|
2842
|
+
await fs8.writeFile(configPath, existing.replace(re, section));
|
|
2942
2843
|
return;
|
|
2943
2844
|
}
|
|
2944
2845
|
const sep = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
2945
|
-
await
|
|
2846
|
+
await fs8.writeFile(configPath, `${existing}${sep}${section}
|
|
2946
2847
|
`);
|
|
2947
2848
|
}
|
|
2948
2849
|
async function removeMarkedBlock(configPath, hostAlias) {
|
|
2949
2850
|
let existing = "";
|
|
2950
2851
|
try {
|
|
2951
|
-
existing = await
|
|
2852
|
+
existing = await fs8.readFile(configPath, "utf8");
|
|
2952
2853
|
} catch {
|
|
2953
2854
|
return;
|
|
2954
2855
|
}
|
|
@@ -2957,7 +2858,7 @@ async function removeMarkedBlock(configPath, hostAlias) {
|
|
|
2957
2858
|
`\\n?${escapeRegExp2(begin)}[\\s\\S]*?${escapeRegExp2(end)}\\n?`,
|
|
2958
2859
|
"g"
|
|
2959
2860
|
);
|
|
2960
|
-
await
|
|
2861
|
+
await fs8.writeFile(
|
|
2961
2862
|
configPath,
|
|
2962
2863
|
existing.replace(re, "\n").replace(/\n{3,}/g, "\n\n")
|
|
2963
2864
|
);
|
|
@@ -2972,10 +2873,10 @@ async function setupWindowsBridge(name, hostAlias, privateKey, directPort, deps,
|
|
|
2972
2873
|
return;
|
|
2973
2874
|
}
|
|
2974
2875
|
const monoDir = path9.join(profile.homeWsl, ".ssh", "monoceros");
|
|
2975
|
-
await
|
|
2876
|
+
await fs8.mkdir(monoDir, { recursive: true });
|
|
2976
2877
|
const keyDst = path9.join(monoDir, name);
|
|
2977
|
-
await
|
|
2978
|
-
await
|
|
2878
|
+
await fs8.rm(keyDst, { force: true });
|
|
2879
|
+
await fs8.copyFile(privateKey, keyDst);
|
|
2979
2880
|
const keyWin = `${profile.homeWin}\\.ssh\\monoceros\\${name}`;
|
|
2980
2881
|
await upsertMarkedBlock(
|
|
2981
2882
|
path9.join(profile.homeWsl, ".ssh", "config"),
|
|
@@ -2991,7 +2892,7 @@ async function removeWindowsBridge(name, hostAlias, deps) {
|
|
|
2991
2892
|
if (!deps.isWsl()) return;
|
|
2992
2893
|
const profile = await deps.resolveProfile();
|
|
2993
2894
|
if (!profile) return;
|
|
2994
|
-
await
|
|
2895
|
+
await fs8.rm(path9.join(profile.homeWsl, ".ssh", "monoceros", name), {
|
|
2995
2896
|
force: true
|
|
2996
2897
|
});
|
|
2997
2898
|
await removeMarkedBlock(
|
|
@@ -3012,16 +2913,16 @@ async function setupSshAttach(opts) {
|
|
|
3012
2913
|
const configEntry = sshConfigEntryPath(opts.home, opts.name);
|
|
3013
2914
|
const winDeps = resolveWindowsDeps(opts.windows);
|
|
3014
2915
|
const directPort = winDeps.isWsl() ? opts.windowsDirectPort ?? null : null;
|
|
3015
|
-
await
|
|
3016
|
-
await
|
|
3017
|
-
await
|
|
2916
|
+
await fs8.mkdir(path9.dirname(proxyScript), { recursive: true });
|
|
2917
|
+
await fs8.mkdir(path9.dirname(configEntry), { recursive: true });
|
|
2918
|
+
await fs8.writeFile(
|
|
3018
2919
|
proxyScript,
|
|
3019
2920
|
proxyScriptContent(opts.name, opts.targetDir),
|
|
3020
2921
|
{ mode: 493 }
|
|
3021
2922
|
);
|
|
3022
|
-
await
|
|
2923
|
+
await fs8.chmod(proxyScript, 493).catch(() => {
|
|
3023
2924
|
});
|
|
3024
|
-
await
|
|
2925
|
+
await fs8.writeFile(
|
|
3025
2926
|
configEntry,
|
|
3026
2927
|
configEntryContent(
|
|
3027
2928
|
opts.name,
|
|
@@ -3049,17 +2950,17 @@ async function setupSshAttach(opts) {
|
|
|
3049
2950
|
return { hostAlias, configured: true };
|
|
3050
2951
|
}
|
|
3051
2952
|
async function upsertKnownHost(knownHostsPath, hostId, typeAndKey) {
|
|
3052
|
-
await
|
|
2953
|
+
await fs8.mkdir(path9.dirname(knownHostsPath), { recursive: true });
|
|
3053
2954
|
let existing = "";
|
|
3054
2955
|
try {
|
|
3055
|
-
existing = await
|
|
2956
|
+
existing = await fs8.readFile(knownHostsPath, "utf8");
|
|
3056
2957
|
} catch {
|
|
3057
2958
|
existing = "";
|
|
3058
2959
|
}
|
|
3059
2960
|
const kept = existing.split("\n").filter((l) => l.trim().length > 0 && l.split(/\s+/)[0] !== hostId);
|
|
3060
2961
|
const next = `${[...kept, `${hostId} ${typeAndKey}`].join("\n")}
|
|
3061
2962
|
`;
|
|
3062
|
-
await
|
|
2963
|
+
await fs8.writeFile(knownHostsPath, next);
|
|
3063
2964
|
}
|
|
3064
2965
|
async function recordHostKey(opts) {
|
|
3065
2966
|
const pubPath = path9.join(
|
|
@@ -3071,7 +2972,7 @@ async function recordHostKey(opts) {
|
|
|
3071
2972
|
);
|
|
3072
2973
|
let line = "";
|
|
3073
2974
|
try {
|
|
3074
|
-
line = (await
|
|
2975
|
+
line = (await fs8.readFile(pubPath, "utf8")).trim();
|
|
3075
2976
|
} catch {
|
|
3076
2977
|
return;
|
|
3077
2978
|
}
|
|
@@ -3097,8 +2998,8 @@ async function recordHostKey(opts) {
|
|
|
3097
2998
|
}
|
|
3098
2999
|
}
|
|
3099
3000
|
async function removeSshAttach(home, name, windows) {
|
|
3100
|
-
await
|
|
3101
|
-
await
|
|
3001
|
+
await fs8.rm(sshProxyScriptPath(home, name), { force: true });
|
|
3002
|
+
await fs8.rm(sshConfigEntryPath(home, name), { force: true });
|
|
3102
3003
|
try {
|
|
3103
3004
|
await removeWindowsBridge(
|
|
3104
3005
|
name,
|
|
@@ -3114,7 +3015,7 @@ var init_ssh_attach = __esm({
|
|
|
3114
3015
|
"use strict";
|
|
3115
3016
|
realKeygen = (args) => {
|
|
3116
3017
|
return new Promise((resolve, reject) => {
|
|
3117
|
-
const child =
|
|
3018
|
+
const child = spawn3("ssh-keygen", args, {
|
|
3118
3019
|
stdio: ["ignore", "ignore", "pipe"]
|
|
3119
3020
|
});
|
|
3120
3021
|
let stderr = "";
|
|
@@ -3289,13 +3190,26 @@ var init_opencode_config = __esm({
|
|
|
3289
3190
|
});
|
|
3290
3191
|
|
|
3291
3192
|
// src/create/scaffold.ts
|
|
3292
|
-
import { existsSync as existsSync8, promises as
|
|
3193
|
+
import { existsSync as existsSync8, promises as fs9 } from "fs";
|
|
3293
3194
|
import path11 from "path";
|
|
3294
3195
|
function deriveRepoName(url) {
|
|
3295
3196
|
const lastSep = Math.max(url.lastIndexOf("/"), url.lastIndexOf(":"));
|
|
3296
3197
|
const tail = url.slice(lastSep + 1);
|
|
3297
3198
|
return tail.replace(/\.git$/, "");
|
|
3298
3199
|
}
|
|
3200
|
+
function cloneUrl(url) {
|
|
3201
|
+
try {
|
|
3202
|
+
const u = new URL(url);
|
|
3203
|
+
if (u.username || u.password) {
|
|
3204
|
+
u.username = "";
|
|
3205
|
+
u.password = "";
|
|
3206
|
+
return u.toString();
|
|
3207
|
+
}
|
|
3208
|
+
return url;
|
|
3209
|
+
} catch {
|
|
3210
|
+
return url;
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3299
3213
|
function validateOptions(opts) {
|
|
3300
3214
|
if (!opts.name || !/^[a-zA-Z0-9._-]+$/.test(opts.name)) {
|
|
3301
3215
|
throw new Error(
|
|
@@ -4075,10 +3989,11 @@ function buildPostCreateScript(opts) {
|
|
|
4075
3989
|
if (parent) {
|
|
4076
3990
|
lines.push(`mkdir -p "projects/${parent}"`);
|
|
4077
3991
|
}
|
|
3992
|
+
const url = cloneUrl(repo.url);
|
|
4078
3993
|
lines.push(
|
|
4079
3994
|
`if [ ! -d "projects/${repo.path}" ]; then`,
|
|
4080
|
-
` echo "\u2192 Cloning ${repo.path} from ${
|
|
4081
|
-
` git clone "${
|
|
3995
|
+
` echo "\u2192 Cloning ${repo.path} from ${url}\u2026"`,
|
|
3996
|
+
` git clone "${url}" "projects/${repo.path}"`,
|
|
4082
3997
|
`else`,
|
|
4083
3998
|
` echo "\u2192 projects/${repo.path} already exists, skipping clone"`,
|
|
4084
3999
|
`fi`
|
|
@@ -4097,15 +4012,15 @@ function buildPostCreateScript(opts) {
|
|
|
4097
4012
|
}
|
|
4098
4013
|
async function writePostCreateScript(devcontainerDir, opts) {
|
|
4099
4014
|
const dest = path11.join(devcontainerDir, "post-create.sh");
|
|
4100
|
-
await
|
|
4101
|
-
await
|
|
4015
|
+
await fs9.writeFile(dest, buildPostCreateScript(opts));
|
|
4016
|
+
await fs9.chmod(dest, 493);
|
|
4102
4017
|
}
|
|
4103
4018
|
async function writeIfChanged(filePath, content) {
|
|
4104
4019
|
try {
|
|
4105
|
-
if (await
|
|
4020
|
+
if (await fs9.readFile(filePath, "utf8") === content) return false;
|
|
4106
4021
|
} catch {
|
|
4107
4022
|
}
|
|
4108
|
-
await
|
|
4023
|
+
await fs9.writeFile(filePath, content);
|
|
4109
4024
|
return true;
|
|
4110
4025
|
}
|
|
4111
4026
|
async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
|
|
@@ -4115,36 +4030,36 @@ async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
|
|
|
4115
4030
|
const projectsDir = path11.join(targetDir, "projects");
|
|
4116
4031
|
const homeDir = path11.join(targetDir, "home");
|
|
4117
4032
|
const dataDir = path11.join(targetDir, "data");
|
|
4118
|
-
await
|
|
4119
|
-
await
|
|
4120
|
-
await
|
|
4121
|
-
await
|
|
4033
|
+
await fs9.mkdir(devcontainerDir, { recursive: true });
|
|
4034
|
+
await fs9.mkdir(monocerosDir, { recursive: true });
|
|
4035
|
+
await fs9.mkdir(projectsDir, { recursive: true });
|
|
4036
|
+
await fs9.mkdir(homeDir, { recursive: true });
|
|
4122
4037
|
if (needsCompose(opts)) {
|
|
4123
|
-
await
|
|
4038
|
+
await fs9.mkdir(dataDir, { recursive: true });
|
|
4124
4039
|
for (const svc of opts.services) {
|
|
4125
4040
|
const hasDataVolume = svc.volumes.some((v) => v.split(":")[0] === "data");
|
|
4126
4041
|
if (hasDataVolume) {
|
|
4127
|
-
await
|
|
4042
|
+
await fs9.mkdir(path11.join(dataDir, svc.name), { recursive: true });
|
|
4128
4043
|
}
|
|
4129
4044
|
}
|
|
4130
4045
|
for (const dir of serviceVolumeHostDirs(opts.services, opts.repos)) {
|
|
4131
|
-
await
|
|
4046
|
+
await fs9.mkdir(path11.join(targetDir, dir), { recursive: true });
|
|
4132
4047
|
}
|
|
4133
4048
|
}
|
|
4134
4049
|
const containerGitignore = path11.join(targetDir, ".gitignore");
|
|
4135
|
-
await
|
|
4050
|
+
await fs9.writeFile(
|
|
4136
4051
|
containerGitignore,
|
|
4137
4052
|
"/home/\n/.monoceros/\n/data/\n/AGENTS.md\n/CLAUDE.md\n"
|
|
4138
4053
|
);
|
|
4139
4054
|
const gitkeep = path11.join(projectsDir, ".gitkeep");
|
|
4140
4055
|
if (!existsSync8(gitkeep)) {
|
|
4141
|
-
await
|
|
4056
|
+
await fs9.writeFile(gitkeep, "");
|
|
4142
4057
|
}
|
|
4143
|
-
await
|
|
4058
|
+
await fs9.writeFile(
|
|
4144
4059
|
path11.join(monocerosDir, ".gitignore"),
|
|
4145
4060
|
"git-credentials*\ngitconfig\nglobal-gitignore\n"
|
|
4146
4061
|
);
|
|
4147
|
-
await
|
|
4062
|
+
await fs9.writeFile(
|
|
4148
4063
|
path11.join(monocerosDir, "global-gitignore"),
|
|
4149
4064
|
".monoceros/\n"
|
|
4150
4065
|
);
|
|
@@ -4155,26 +4070,26 @@ async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
|
|
|
4155
4070
|
);
|
|
4156
4071
|
const featuresDir = path11.join(devcontainerDir, "features");
|
|
4157
4072
|
if (existsSync8(featuresDir)) {
|
|
4158
|
-
await
|
|
4073
|
+
await fs9.rm(featuresDir, { recursive: true, force: true });
|
|
4159
4074
|
}
|
|
4160
4075
|
const resolvedFeatures = resolveFeatures(opts);
|
|
4161
4076
|
for (const f of resolvedFeatures) {
|
|
4162
4077
|
if (!f.localSourceDir || !f.localName) continue;
|
|
4163
4078
|
const dest = path11.join(featuresDir, f.localName);
|
|
4164
|
-
await
|
|
4165
|
-
const entries = await
|
|
4079
|
+
await fs9.mkdir(dest, { recursive: true });
|
|
4080
|
+
const entries = await fs9.readdir(f.localSourceDir, { withFileTypes: true });
|
|
4166
4081
|
for (const entry2 of entries) {
|
|
4167
4082
|
if (!entry2.isFile()) continue;
|
|
4168
4083
|
if (entry2.name === "component.yml" || entry2.name === "devcontainer-feature.json") {
|
|
4169
4084
|
continue;
|
|
4170
4085
|
}
|
|
4171
|
-
await
|
|
4086
|
+
await fs9.cp(
|
|
4172
4087
|
path11.join(f.localSourceDir, entry2.name),
|
|
4173
4088
|
path11.join(dest, entry2.name)
|
|
4174
4089
|
);
|
|
4175
4090
|
}
|
|
4176
4091
|
if (f.generatedManifest) {
|
|
4177
|
-
await
|
|
4092
|
+
await fs9.writeFile(
|
|
4178
4093
|
path11.join(dest, "devcontainer-feature.json"),
|
|
4179
4094
|
JSON.stringify(f.generatedManifest, null, 2) + "\n"
|
|
4180
4095
|
);
|
|
@@ -4182,13 +4097,13 @@ async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
|
|
|
4182
4097
|
}
|
|
4183
4098
|
for (const f of resolvedFeatures) {
|
|
4184
4099
|
for (const sub of f.persistentHomePaths) {
|
|
4185
|
-
await
|
|
4100
|
+
await fs9.mkdir(path11.join(homeDir, sub), { recursive: true });
|
|
4186
4101
|
}
|
|
4187
4102
|
for (const entry2 of f.persistentHomeFiles) {
|
|
4188
4103
|
const filePath = path11.join(homeDir, entry2.path);
|
|
4189
|
-
await
|
|
4104
|
+
await fs9.mkdir(path11.dirname(filePath), { recursive: true });
|
|
4190
4105
|
if (!existsSync8(filePath)) {
|
|
4191
|
-
await
|
|
4106
|
+
await fs9.writeFile(filePath, entry2.initialContent);
|
|
4192
4107
|
}
|
|
4193
4108
|
}
|
|
4194
4109
|
}
|
|
@@ -4199,29 +4114,29 @@ async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
|
|
|
4199
4114
|
if (needsCompose(opts)) {
|
|
4200
4115
|
await writeIfChanged(composePath, buildComposeYaml(opts, dockerMode));
|
|
4201
4116
|
} else if (existsSync8(composePath)) {
|
|
4202
|
-
await
|
|
4117
|
+
await fs9.rm(composePath);
|
|
4203
4118
|
}
|
|
4204
4119
|
const workspacePath = path11.join(targetDir, `${opts.name}.code-workspace`);
|
|
4205
4120
|
let existingWorkspace;
|
|
4206
4121
|
try {
|
|
4207
|
-
const raw = await
|
|
4122
|
+
const raw = await fs9.readFile(workspacePath, "utf8");
|
|
4208
4123
|
existingWorkspace = JSON.parse(raw);
|
|
4209
4124
|
} catch {
|
|
4210
4125
|
existingWorkspace = void 0;
|
|
4211
4126
|
}
|
|
4212
4127
|
const generated = buildCodeWorkspaceJson(opts);
|
|
4213
4128
|
const merged = mergeCodeWorkspace(existingWorkspace, generated);
|
|
4214
|
-
await
|
|
4129
|
+
await fs9.writeFile(workspacePath, JSON.stringify(merged, null, 2) + "\n");
|
|
4215
4130
|
const vscodeDir = path11.join(targetDir, ".vscode");
|
|
4216
4131
|
const settingsPath = path11.join(vscodeDir, "settings.json");
|
|
4217
4132
|
let existingSettings;
|
|
4218
4133
|
try {
|
|
4219
|
-
existingSettings = JSON.parse(await
|
|
4134
|
+
existingSettings = JSON.parse(await fs9.readFile(settingsPath, "utf8"));
|
|
4220
4135
|
} catch {
|
|
4221
4136
|
existingSettings = void 0;
|
|
4222
4137
|
}
|
|
4223
|
-
await
|
|
4224
|
-
await
|
|
4138
|
+
await fs9.mkdir(vscodeDir, { recursive: true });
|
|
4139
|
+
await fs9.writeFile(
|
|
4225
4140
|
settingsPath,
|
|
4226
4141
|
JSON.stringify(mergeVscodeSettings(existingSettings), null, 2) + "\n"
|
|
4227
4142
|
);
|
|
@@ -4976,7 +4891,7 @@ var init_markers = __esm({
|
|
|
4976
4891
|
});
|
|
4977
4892
|
|
|
4978
4893
|
// src/briefing/index.ts
|
|
4979
|
-
import { promises as
|
|
4894
|
+
import { promises as fs10 } from "fs";
|
|
4980
4895
|
import path12 from "path";
|
|
4981
4896
|
async function writeBriefing(input) {
|
|
4982
4897
|
const subCommands = input.subCommands ?? await loadSubCommandsDynamic();
|
|
@@ -4994,8 +4909,8 @@ async function writeBriefing(input) {
|
|
|
4994
4909
|
await writeMarkerAware(path12.join(input.targetDir, "AGENTS.md"), agentsBody);
|
|
4995
4910
|
await writeMarkerAware(path12.join(input.targetDir, "CLAUDE.md"), claudeBody);
|
|
4996
4911
|
const monocerosDir = path12.join(input.targetDir, ".monoceros");
|
|
4997
|
-
await
|
|
4998
|
-
await
|
|
4912
|
+
await fs10.mkdir(monocerosDir, { recursive: true });
|
|
4913
|
+
await fs10.writeFile(
|
|
4999
4914
|
path12.join(monocerosDir, "commands.md"),
|
|
5000
4915
|
commandsBody,
|
|
5001
4916
|
"utf8"
|
|
@@ -5016,18 +4931,18 @@ function featureDisplayMap(components) {
|
|
|
5016
4931
|
async function writeMarkerAware(filePath, body) {
|
|
5017
4932
|
let existing = null;
|
|
5018
4933
|
try {
|
|
5019
|
-
existing = await
|
|
4934
|
+
existing = await fs10.readFile(filePath, "utf8");
|
|
5020
4935
|
} catch (err) {
|
|
5021
4936
|
if (err.code !== "ENOENT") throw err;
|
|
5022
4937
|
}
|
|
5023
4938
|
if (existing) {
|
|
5024
4939
|
const updated = replaceMarkerBlock(existing, body);
|
|
5025
4940
|
if (updated !== null) {
|
|
5026
|
-
await
|
|
4941
|
+
await fs10.writeFile(filePath, updated, "utf8");
|
|
5027
4942
|
return;
|
|
5028
4943
|
}
|
|
5029
4944
|
}
|
|
5030
|
-
await
|
|
4945
|
+
await fs10.writeFile(filePath, wrapWithMarkers(body), "utf8");
|
|
5031
4946
|
}
|
|
5032
4947
|
async function loadSubCommandsDynamic() {
|
|
5033
4948
|
const mod = await Promise.resolve().then(() => (init_main(), main_exports));
|
|
@@ -5519,7 +5434,7 @@ var init_yml = __esm({
|
|
|
5519
5434
|
});
|
|
5520
5435
|
|
|
5521
5436
|
// src/modify/index.ts
|
|
5522
|
-
import { promises as
|
|
5437
|
+
import { promises as fs11 } from "fs";
|
|
5523
5438
|
import { consola as consola2 } from "consola";
|
|
5524
5439
|
import { createPatch } from "diff";
|
|
5525
5440
|
import path13 from "path";
|
|
@@ -5603,7 +5518,7 @@ function runAddAptPackages(input) {
|
|
|
5603
5518
|
async function refreshBriefingFromYml(name, home) {
|
|
5604
5519
|
const targetDir = containerDir(name, home);
|
|
5605
5520
|
try {
|
|
5606
|
-
await
|
|
5521
|
+
await fs11.access(targetDir);
|
|
5607
5522
|
} catch {
|
|
5608
5523
|
return;
|
|
5609
5524
|
}
|
|
@@ -5691,6 +5606,23 @@ async function runAddRepo(input) {
|
|
|
5691
5606
|
`Added a container git.user with \${${GIT_IDENTITY_VAR.name}}/\${${GIT_IDENTITY_VAR.email}} placeholders and seeded ${input.name}.env \u2014 fill them or leave blank to use your global git identity.`
|
|
5692
5607
|
);
|
|
5693
5608
|
}
|
|
5609
|
+
if (result.status === "updated" && host) {
|
|
5610
|
+
const home = input.monocerosHome ?? monocerosHome();
|
|
5611
|
+
const prov = resolveProvider(host, entry2.provider);
|
|
5612
|
+
const featureShort = prov === "github" ? "github" : prov === "gitlab" ? "gitlab" : void 0;
|
|
5613
|
+
if (featureShort) {
|
|
5614
|
+
const yml = await fs11.readFile(
|
|
5615
|
+
containerConfigPath(input.name, home),
|
|
5616
|
+
"utf8"
|
|
5617
|
+
);
|
|
5618
|
+
const cfg = parseConfig(yml).config;
|
|
5619
|
+
const ref = (await resolveFeatureRefOrShortname(featureShort)).ref;
|
|
5620
|
+
const present = (cfg.features ?? []).some((f) => f.ref === ref);
|
|
5621
|
+
if (!present) {
|
|
5622
|
+
await runAddFeature({ ...input, ref: featureShort });
|
|
5623
|
+
}
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5694
5626
|
if (result.status === "updated") {
|
|
5695
5627
|
await tryCloneInRunningContainer(input, entry2);
|
|
5696
5628
|
await refreshBriefingFromYml(
|
|
@@ -5738,26 +5670,25 @@ async function tryCloneInRunningContainer(input, entry2) {
|
|
|
5738
5670
|
return;
|
|
5739
5671
|
}
|
|
5740
5672
|
try {
|
|
5741
|
-
const
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5673
|
+
const { hostTokens } = await resolveContainerRepoTokens(
|
|
5674
|
+
input.name,
|
|
5675
|
+
home,
|
|
5676
|
+
await loadComponentCatalog()
|
|
5677
|
+
);
|
|
5678
|
+
if (hostTokens.get(urlHost)) {
|
|
5679
|
+
await collectGitCredentials(root, [{ host: urlHost, provider }], {
|
|
5680
|
+
patByHost: hostTokens,
|
|
5746
5681
|
logger: { info: () => {
|
|
5747
5682
|
}, warn: (m) => logger.warn(m) }
|
|
5748
|
-
}
|
|
5749
|
-
|
|
5750
|
-
const status = credsResult.perHost.find((h) => h.host === urlHost);
|
|
5751
|
-
if (!status || status.status !== "ok") {
|
|
5752
|
-
const detail = status?.detail ? `: ${status.detail}` : "";
|
|
5683
|
+
});
|
|
5684
|
+
} else {
|
|
5753
5685
|
logger.warn(
|
|
5754
|
-
`No
|
|
5686
|
+
`No access token set for ${urlHost} \u2014 cloning will fail if this repo is private. Set one (see ${REPO_DOCS_URL}) and re-run.`
|
|
5755
5687
|
);
|
|
5756
|
-
return;
|
|
5757
5688
|
}
|
|
5758
5689
|
} catch (err) {
|
|
5759
5690
|
logger.warn(
|
|
5760
|
-
`
|
|
5691
|
+
`Could not resolve a token for ${urlHost}: ${err instanceof Error ? err.message : String(err)}. The yml is updated; run \`monoceros apply ${input.name}\` to clone.`
|
|
5761
5692
|
);
|
|
5762
5693
|
return;
|
|
5763
5694
|
}
|
|
@@ -5774,7 +5705,7 @@ async function tryCloneInRunningContainer(input, entry2) {
|
|
|
5774
5705
|
` exit 0`,
|
|
5775
5706
|
`fi`,
|
|
5776
5707
|
`mkdir -p ${shquote(parentRel)}`,
|
|
5777
|
-
`git -c ${shquote(`credential.helper=${credentialHelper}`)} clone ${shquote(entry2.url)} ${shquote(targetRel)}`
|
|
5708
|
+
`git -c ${shquote(`credential.helper=${credentialHelper}`)} clone ${shquote(cloneUrl(entry2.url))} ${shquote(targetRel)}`
|
|
5778
5709
|
];
|
|
5779
5710
|
if (entry2.gitUser) {
|
|
5780
5711
|
script.push(
|
|
@@ -6021,7 +5952,7 @@ async function mutate(opts, apply) {
|
|
|
6021
5952
|
const logger = opts.logger ?? defaultLogger();
|
|
6022
5953
|
let oldText;
|
|
6023
5954
|
try {
|
|
6024
|
-
oldText = await
|
|
5955
|
+
oldText = await fs11.readFile(ymlPath, "utf8");
|
|
6025
5956
|
} catch {
|
|
6026
5957
|
throw new Error(
|
|
6027
5958
|
`No such config: ${ymlPath}. Run \`monoceros init <template> ${opts.name}\` first.`
|
|
@@ -6046,7 +5977,7 @@ async function mutate(opts, apply) {
|
|
|
6046
5977
|
return { status: "aborted" };
|
|
6047
5978
|
}
|
|
6048
5979
|
}
|
|
6049
|
-
await
|
|
5980
|
+
await fs11.writeFile(ymlPath, newText, "utf8");
|
|
6050
5981
|
logger.success(`Updated ${ymlPath}.`);
|
|
6051
5982
|
logger.info(
|
|
6052
5983
|
`Run \`monoceros apply ${opts.name}\` to rebuild the dev-container and pick up the change.`
|
|
@@ -6125,6 +6056,8 @@ var init_modify = __esm({
|
|
|
6125
6056
|
init_feature_doc();
|
|
6126
6057
|
init_manifest();
|
|
6127
6058
|
init_credentials();
|
|
6059
|
+
init_repo_token();
|
|
6060
|
+
init_schema();
|
|
6128
6061
|
init_locate_running();
|
|
6129
6062
|
init_global();
|
|
6130
6063
|
init_schema();
|
|
@@ -6400,7 +6333,7 @@ var init_add_repo = __esm({
|
|
|
6400
6333
|
},
|
|
6401
6334
|
provider: {
|
|
6402
6335
|
type: "string",
|
|
6403
|
-
description: "Git provider
|
|
6336
|
+
description: "Git provider: github | gitlab | bitbucket. Required when the URL host is not github.com, gitlab.com, or bitbucket.org, so Monoceros knows which provider token to use for cloning."
|
|
6404
6337
|
},
|
|
6405
6338
|
yes: {
|
|
6406
6339
|
type: "boolean",
|
|
@@ -6596,7 +6529,7 @@ var init_add_service = __esm({
|
|
|
6596
6529
|
});
|
|
6597
6530
|
|
|
6598
6531
|
// src/config/state.ts
|
|
6599
|
-
import { promises as
|
|
6532
|
+
import { promises as fs12 } from "fs";
|
|
6600
6533
|
import path14 from "path";
|
|
6601
6534
|
function buildStateFile(opts) {
|
|
6602
6535
|
return {
|
|
@@ -6612,7 +6545,7 @@ function stateFilePath(targetDir) {
|
|
|
6612
6545
|
}
|
|
6613
6546
|
async function readStateFile(targetDir) {
|
|
6614
6547
|
try {
|
|
6615
|
-
const content = await
|
|
6548
|
+
const content = await fs12.readFile(stateFilePath(targetDir), "utf8");
|
|
6616
6549
|
return JSON.parse(content);
|
|
6617
6550
|
} catch {
|
|
6618
6551
|
return void 0;
|
|
@@ -6620,8 +6553,8 @@ async function readStateFile(targetDir) {
|
|
|
6620
6553
|
}
|
|
6621
6554
|
async function writeStateFile(targetDir, state) {
|
|
6622
6555
|
const monocerosDir = path14.join(targetDir, ".monoceros");
|
|
6623
|
-
await
|
|
6624
|
-
await
|
|
6556
|
+
await fs12.mkdir(monocerosDir, { recursive: true });
|
|
6557
|
+
await fs12.writeFile(
|
|
6625
6558
|
stateFilePath(targetDir),
|
|
6626
6559
|
JSON.stringify(state, null, 2) + "\n"
|
|
6627
6560
|
);
|
|
@@ -6634,7 +6567,7 @@ var init_state = __esm({
|
|
|
6634
6567
|
});
|
|
6635
6568
|
|
|
6636
6569
|
// src/devcontainer/app-control.ts
|
|
6637
|
-
import { promises as
|
|
6570
|
+
import { promises as fs13 } from "fs";
|
|
6638
6571
|
import path15 from "path";
|
|
6639
6572
|
function findRunningContainer(name, opts = {}) {
|
|
6640
6573
|
return findRunningContainerByLocalFolder(containerDir(name), {
|
|
@@ -6657,7 +6590,7 @@ async function hasWantedApps(name, home) {
|
|
|
6657
6590
|
async function anyPid(dir) {
|
|
6658
6591
|
let entries;
|
|
6659
6592
|
try {
|
|
6660
|
-
entries = await
|
|
6593
|
+
entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
6661
6594
|
} catch {
|
|
6662
6595
|
return false;
|
|
6663
6596
|
}
|
|
@@ -6684,7 +6617,7 @@ var init_app_control = __esm({
|
|
|
6684
6617
|
});
|
|
6685
6618
|
|
|
6686
6619
|
// src/devcontainer/browser-bridge.ts
|
|
6687
|
-
import { spawn as
|
|
6620
|
+
import { spawn as spawn4 } from "child_process";
|
|
6688
6621
|
import { existsSync as existsSync9, promises as fsp4, readFileSync as readFileSync5 } from "fs";
|
|
6689
6622
|
import http from "http";
|
|
6690
6623
|
import path16 from "path";
|
|
@@ -6742,7 +6675,7 @@ function openInBrowser(url) {
|
|
|
6742
6675
|
const psOpen = `Start-Process '${url.replace(/'/g, "''")}'`;
|
|
6743
6676
|
const [cmd, args] = platform === "darwin" ? ["open", [url]] : platform === "win32" || wsl ? ["powershell.exe", ["-NoProfile", "-Command", psOpen]] : ["xdg-open", [url]];
|
|
6744
6677
|
try {
|
|
6745
|
-
const child =
|
|
6678
|
+
const child = spawn4(cmd, args, {
|
|
6746
6679
|
stdio: "ignore",
|
|
6747
6680
|
detached: true
|
|
6748
6681
|
});
|
|
@@ -6948,7 +6881,7 @@ var init_runtime_pull_hint = __esm({
|
|
|
6948
6881
|
});
|
|
6949
6882
|
|
|
6950
6883
|
// src/devcontainer/cli.ts
|
|
6951
|
-
import { spawn as
|
|
6884
|
+
import { spawn as spawn5 } from "child_process";
|
|
6952
6885
|
import { readFileSync as readFileSync6 } from "fs";
|
|
6953
6886
|
import { createRequire } from "module";
|
|
6954
6887
|
import path17 from "path";
|
|
@@ -6976,7 +6909,7 @@ var init_cli = __esm({
|
|
|
6976
6909
|
const env = { ...process.env, DOCKER_CLI_HINTS: "false" };
|
|
6977
6910
|
return new Promise((resolve, reject) => {
|
|
6978
6911
|
if (options.interactive) {
|
|
6979
|
-
const child2 =
|
|
6912
|
+
const child2 = spawn5(process.execPath, [binPath, ...args], {
|
|
6980
6913
|
cwd,
|
|
6981
6914
|
env,
|
|
6982
6915
|
stdio: "inherit"
|
|
@@ -6985,7 +6918,7 @@ var init_cli = __esm({
|
|
|
6985
6918
|
child2.on("exit", (code) => resolve(code ?? 0));
|
|
6986
6919
|
return;
|
|
6987
6920
|
}
|
|
6988
|
-
const child =
|
|
6921
|
+
const child = spawn5(process.execPath, [binPath, ...args], {
|
|
6989
6922
|
cwd,
|
|
6990
6923
|
env,
|
|
6991
6924
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7033,14 +6966,14 @@ var init_cli = __esm({
|
|
|
7033
6966
|
});
|
|
7034
6967
|
|
|
7035
6968
|
// src/devcontainer/compose.ts
|
|
7036
|
-
import { spawn as
|
|
6969
|
+
import { spawn as spawn6 } from "child_process";
|
|
7037
6970
|
import { existsSync as existsSync10 } from "fs";
|
|
7038
6971
|
import path18 from "path";
|
|
7039
6972
|
import { Writable } from "stream";
|
|
7040
6973
|
import { consola as consola10 } from "consola";
|
|
7041
6974
|
function spawnDockerComposeTo(opts) {
|
|
7042
6975
|
return (args, cwd) => new Promise((resolve, reject) => {
|
|
7043
|
-
const child =
|
|
6976
|
+
const child = spawn6("docker", ["compose", ...args], {
|
|
7044
6977
|
cwd,
|
|
7045
6978
|
stdio: ["inherit", "pipe", "pipe"]
|
|
7046
6979
|
});
|
|
@@ -7368,7 +7301,7 @@ var init_compose = __esm({
|
|
|
7368
7301
|
init_proxy();
|
|
7369
7302
|
spawnDockerCompose = (args, cwd) => {
|
|
7370
7303
|
return new Promise((resolve, reject) => {
|
|
7371
|
-
const child =
|
|
7304
|
+
const child = spawn6("docker", ["compose", ...args], {
|
|
7372
7305
|
cwd,
|
|
7373
7306
|
stdio: ["inherit", "pipe", "pipe"]
|
|
7374
7307
|
});
|
|
@@ -7380,7 +7313,7 @@ var init_compose = __esm({
|
|
|
7380
7313
|
};
|
|
7381
7314
|
spawnDocker = (args) => {
|
|
7382
7315
|
return new Promise((resolve, reject) => {
|
|
7383
|
-
const child =
|
|
7316
|
+
const child = spawn6("docker", args, {
|
|
7384
7317
|
stdio: ["ignore", "pipe", "pipe"]
|
|
7385
7318
|
});
|
|
7386
7319
|
let stdout = "";
|
|
@@ -7405,7 +7338,7 @@ var init_compose = __esm({
|
|
|
7405
7338
|
});
|
|
7406
7339
|
|
|
7407
7340
|
// src/devcontainer/bridge-daemon.ts
|
|
7408
|
-
import { spawn as
|
|
7341
|
+
import { spawn as spawn7 } from "child_process";
|
|
7409
7342
|
import {
|
|
7410
7343
|
existsSync as existsSync11,
|
|
7411
7344
|
mkdirSync,
|
|
@@ -7442,7 +7375,7 @@ function spawnBridgeDaemon(root) {
|
|
|
7442
7375
|
const self = process.argv[1];
|
|
7443
7376
|
if (!self) return;
|
|
7444
7377
|
mkdirSync(relayDir(root), { recursive: true });
|
|
7445
|
-
const child =
|
|
7378
|
+
const child = spawn7(
|
|
7446
7379
|
process.execPath,
|
|
7447
7380
|
[...process.execArgv, self, "__bridge", root],
|
|
7448
7381
|
{
|
|
@@ -7671,8 +7604,11 @@ function createApplyProgress(opts) {
|
|
|
7671
7604
|
tail.push(line);
|
|
7672
7605
|
if (tail.length > TAIL_LINES) tail.shift();
|
|
7673
7606
|
for (const trig of PHASE_TRIGGERS) {
|
|
7674
|
-
|
|
7675
|
-
|
|
7607
|
+
const m = line.match(trig.pattern);
|
|
7608
|
+
if (m) {
|
|
7609
|
+
setPhase(
|
|
7610
|
+
typeof trig.label === "function" ? trig.label(m) : trig.label
|
|
7611
|
+
);
|
|
7676
7612
|
break;
|
|
7677
7613
|
}
|
|
7678
7614
|
}
|
|
@@ -7768,6 +7704,15 @@ var init_apply_progress = __esm({
|
|
|
7768
7704
|
pattern: /Start: Run: docker (?:run\b|compose\b.* up\b)/i,
|
|
7769
7705
|
label: "starting container\u2026"
|
|
7770
7706
|
},
|
|
7707
|
+
// post-create.sh sub-steps. Repo clones are the long, network-bound
|
|
7708
|
+
// part; surface which repo is being cloned instead of an opaque
|
|
7709
|
+
// "running postCreate…". The marker is the `→ Cloning <path> from …`
|
|
7710
|
+
// line post-create.sh echoes (see create/scaffold.ts). A devcontainer
|
|
7711
|
+
// timestamp prefix is fine — the pattern is unanchored.
|
|
7712
|
+
{
|
|
7713
|
+
pattern: /→ Cloning (\S+) from /,
|
|
7714
|
+
label: (m) => `cloning ${m[1]}\u2026`
|
|
7715
|
+
},
|
|
7771
7716
|
{ pattern: /Running the postCreateCommand/i, label: "running postCreate\u2026" }
|
|
7772
7717
|
];
|
|
7773
7718
|
}
|
|
@@ -7941,7 +7886,7 @@ var init_machine_state = __esm({
|
|
|
7941
7886
|
});
|
|
7942
7887
|
|
|
7943
7888
|
// src/devcontainer/docker-mode.ts
|
|
7944
|
-
import { spawn as
|
|
7889
|
+
import { spawn as spawn8 } from "child_process";
|
|
7945
7890
|
async function detectDockerMode(options = {}) {
|
|
7946
7891
|
const spawnFn = options.spawn ?? realDockerInfo;
|
|
7947
7892
|
try {
|
|
@@ -7991,7 +7936,7 @@ var init_docker_mode = __esm({
|
|
|
7991
7936
|
init_format();
|
|
7992
7937
|
realDockerInfo = () => {
|
|
7993
7938
|
return new Promise((resolve, reject) => {
|
|
7994
|
-
const child =
|
|
7939
|
+
const child = spawn8(
|
|
7995
7940
|
"docker",
|
|
7996
7941
|
["info", "--format", "{{json .SecurityOptions}}"],
|
|
7997
7942
|
{
|
|
@@ -8010,8 +7955,8 @@ var init_docker_mode = __esm({
|
|
|
8010
7955
|
});
|
|
8011
7956
|
|
|
8012
7957
|
// src/devcontainer/identity.ts
|
|
8013
|
-
import { spawn as
|
|
8014
|
-
import { promises as
|
|
7958
|
+
import { spawn as spawn9 } from "child_process";
|
|
7959
|
+
import { promises as fs14 } from "fs";
|
|
8015
7960
|
import path22 from "path";
|
|
8016
7961
|
import { consola as consola11 } from "consola";
|
|
8017
7962
|
async function resolveIdentityWithPrompt(options = {}) {
|
|
@@ -8082,8 +8027,8 @@ async function collectGitIdentity(devContainerRoot, options = {}) {
|
|
|
8082
8027
|
const lines = ["[user]"];
|
|
8083
8028
|
if (resolved.name !== void 0) lines.push(` name = ${resolved.name}`);
|
|
8084
8029
|
if (resolved.email !== void 0) lines.push(` email = ${resolved.email}`);
|
|
8085
|
-
await
|
|
8086
|
-
await
|
|
8030
|
+
await fs14.mkdir(gitconfigDir, { recursive: true });
|
|
8031
|
+
await fs14.writeFile(gitconfigPath, lines.join("\n") + "\n");
|
|
8087
8032
|
return {
|
|
8088
8033
|
...resolved.name !== void 0 ? { name: resolved.name } : {},
|
|
8089
8034
|
...resolved.email !== void 0 ? { email: resolved.email } : {},
|
|
@@ -8126,7 +8071,7 @@ async function readKeyFromHost(spawnFn, key, logger) {
|
|
|
8126
8071
|
}
|
|
8127
8072
|
async function readExistingGitconfig(filePath) {
|
|
8128
8073
|
try {
|
|
8129
|
-
const content = await
|
|
8074
|
+
const content = await fs14.readFile(filePath, "utf8");
|
|
8130
8075
|
const result = {};
|
|
8131
8076
|
const nameMatch = /^\s*name\s*=\s*(.+?)\s*$/m.exec(content);
|
|
8132
8077
|
const emailMatch = /^\s*email\s*=\s*(.+?)\s*$/m.exec(content);
|
|
@@ -8143,7 +8088,7 @@ var init_identity = __esm({
|
|
|
8143
8088
|
"use strict";
|
|
8144
8089
|
realGitConfigGet = (key) => {
|
|
8145
8090
|
return new Promise((resolve, reject) => {
|
|
8146
|
-
const child =
|
|
8091
|
+
const child = spawn9("git", ["config", "--global", "--get", key], {
|
|
8147
8092
|
stdio: ["ignore", "pipe", "inherit"]
|
|
8148
8093
|
});
|
|
8149
8094
|
let stdout = "";
|
|
@@ -8203,7 +8148,7 @@ var init_identity = __esm({
|
|
|
8203
8148
|
});
|
|
8204
8149
|
|
|
8205
8150
|
// src/apply/index.ts
|
|
8206
|
-
import { existsSync as existsSync12, promises as
|
|
8151
|
+
import { existsSync as existsSync12, promises as fs15 } from "fs";
|
|
8207
8152
|
import { consola as consola12 } from "consola";
|
|
8208
8153
|
async function runApply(opts) {
|
|
8209
8154
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -8243,9 +8188,18 @@ ${sectionLine(label)}
|
|
|
8243
8188
|
warnOnDeprecatedFeatureRefs(parsed.config.features, globalConfig, logger);
|
|
8244
8189
|
const envPath = containerEnvPath(opts.name, home);
|
|
8245
8190
|
await ensureEnvGitignored(containerConfigsDir(home));
|
|
8246
|
-
const envVars =
|
|
8191
|
+
const envVars = {
|
|
8192
|
+
...readEnvFile(globalEnvPath(home)),
|
|
8193
|
+
...readEnvFile(envPath),
|
|
8194
|
+
...opts.env ?? {}
|
|
8195
|
+
};
|
|
8196
|
+
const catalog = await loadComponentCatalog();
|
|
8197
|
+
const repoTokens = resolveRepoTokens(parsed.config, catalog, envVars);
|
|
8198
|
+
for (const use of repoTokens.used) {
|
|
8199
|
+
logger.info(`Repo token: ${formatTokenUse(use)}`);
|
|
8200
|
+
}
|
|
8247
8201
|
const resolvedFeatures = interpolateFeatureOptions(
|
|
8248
|
-
|
|
8202
|
+
repoTokens.features,
|
|
8249
8203
|
envVars
|
|
8250
8204
|
);
|
|
8251
8205
|
const createOpts = normalizeOptions(
|
|
@@ -8327,13 +8281,11 @@ Fix the value in the env file (or the yml).`
|
|
|
8327
8281
|
throw new Error(formatUnknownProviderError(unknownProviderHosts));
|
|
8328
8282
|
}
|
|
8329
8283
|
if (hostsToFetch.length > 0) {
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
if (missing.length > 0) {
|
|
8336
|
-
throw new Error(formatMissingCredentialsError(missing));
|
|
8284
|
+
if (repoTokens.hostTokens.size > 0) {
|
|
8285
|
+
await collectGitCredentials(targetDir, hostsToFetch, {
|
|
8286
|
+
patByHost: repoTokens.hostTokens,
|
|
8287
|
+
logger: idLogger
|
|
8288
|
+
});
|
|
8337
8289
|
}
|
|
8338
8290
|
}
|
|
8339
8291
|
section("Scaffold");
|
|
@@ -8343,7 +8295,7 @@ Fix the value in the env file (or the yml).`
|
|
|
8343
8295
|
if (dockerMode === "rootless") {
|
|
8344
8296
|
throw new Error(formatRootlessNotSupportedError());
|
|
8345
8297
|
}
|
|
8346
|
-
await
|
|
8298
|
+
await fs15.mkdir(targetDir, { recursive: true });
|
|
8347
8299
|
await writeScaffold(createOpts, targetDir, { dockerMode });
|
|
8348
8300
|
await writeStateFile(
|
|
8349
8301
|
targetDir,
|
|
@@ -8567,6 +8519,15 @@ ${stripAnsi(formatted)}
|
|
|
8567
8519
|
`);
|
|
8568
8520
|
}
|
|
8569
8521
|
}
|
|
8522
|
+
if (repoTokens.missing.length > 0) {
|
|
8523
|
+
const warning = formatUnauthenticatedRepos(repoTokens.missing, opts.name);
|
|
8524
|
+
progressOut.write(`
|
|
8525
|
+
${warning}
|
|
8526
|
+
`);
|
|
8527
|
+
applyLog.stream.write(`
|
|
8528
|
+
${stripAnsi(warning)}
|
|
8529
|
+
`);
|
|
8530
|
+
}
|
|
8570
8531
|
await applyLog.close();
|
|
8571
8532
|
progressOut.write(`
|
|
8572
8533
|
${dim(`log: ${prettyPath(applyLog.path)}`)}
|
|
@@ -8582,7 +8543,7 @@ ${stripAnsi(formatted)}
|
|
|
8582
8543
|
}
|
|
8583
8544
|
async function assertSafeTargetDir(targetDir, expectedOrigin) {
|
|
8584
8545
|
if (!existsSync12(targetDir)) return;
|
|
8585
|
-
const entries = await
|
|
8546
|
+
const entries = await fs15.readdir(targetDir);
|
|
8586
8547
|
if (entries.length === 0) return;
|
|
8587
8548
|
const state = await readStateFile(targetDir);
|
|
8588
8549
|
if (state) {
|
|
@@ -8652,7 +8613,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
8652
8613
|
}
|
|
8653
8614
|
if (wantContainer) {
|
|
8654
8615
|
try {
|
|
8655
|
-
const text = await
|
|
8616
|
+
const text = await fs15.readFile(ymlPath, "utf8");
|
|
8656
8617
|
const parsed = parseConfig(text, ymlPath);
|
|
8657
8618
|
const changed = setContainerGitUserInDoc(parsed.doc, {
|
|
8658
8619
|
name: prompted.name,
|
|
@@ -8660,7 +8621,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
8660
8621
|
});
|
|
8661
8622
|
if (changed) {
|
|
8662
8623
|
const out = stringifyConfig(parsed.doc);
|
|
8663
|
-
await
|
|
8624
|
+
await fs15.writeFile(ymlPath, out, "utf8");
|
|
8664
8625
|
logger.info(
|
|
8665
8626
|
`Saved identity in this container \u2014 wrote git.user into ${prettyPath(ymlPath)}.`
|
|
8666
8627
|
);
|
|
@@ -8706,6 +8667,7 @@ var init_apply = __esm({
|
|
|
8706
8667
|
init_identity();
|
|
8707
8668
|
init_global();
|
|
8708
8669
|
init_yml();
|
|
8670
|
+
init_repo_token();
|
|
8709
8671
|
}
|
|
8710
8672
|
});
|
|
8711
8673
|
|
|
@@ -8760,7 +8722,7 @@ var init_shell = __esm({
|
|
|
8760
8722
|
});
|
|
8761
8723
|
|
|
8762
8724
|
// src/open/index.ts
|
|
8763
|
-
import { spawn as
|
|
8725
|
+
import { spawn as spawn10 } from "child_process";
|
|
8764
8726
|
import { accessSync, constants, existsSync as existsSync14 } from "fs";
|
|
8765
8727
|
import path24 from "path";
|
|
8766
8728
|
import { consola as consola13 } from "consola";
|
|
@@ -8796,7 +8758,7 @@ async function resolveEditorBinary(tool) {
|
|
|
8796
8758
|
return null;
|
|
8797
8759
|
}
|
|
8798
8760
|
function realLaunch(bin, args) {
|
|
8799
|
-
const child =
|
|
8761
|
+
const child = spawn10(bin, args, {
|
|
8800
8762
|
detached: true,
|
|
8801
8763
|
stdio: "ignore"
|
|
8802
8764
|
});
|
|
@@ -8880,7 +8842,7 @@ var CLI_VERSION;
|
|
|
8880
8842
|
var init_version = __esm({
|
|
8881
8843
|
"src/version.ts"() {
|
|
8882
8844
|
"use strict";
|
|
8883
|
-
CLI_VERSION = true ? "1.
|
|
8845
|
+
CLI_VERSION = true ? "1.37.1" : "dev";
|
|
8884
8846
|
}
|
|
8885
8847
|
});
|
|
8886
8848
|
|
|
@@ -9114,7 +9076,7 @@ var init_bridge = __esm({
|
|
|
9114
9076
|
});
|
|
9115
9077
|
|
|
9116
9078
|
// src/config/launch-config.ts
|
|
9117
|
-
import { promises as
|
|
9079
|
+
import { promises as fs16 } from "fs";
|
|
9118
9080
|
import path25 from "path";
|
|
9119
9081
|
function launchConfigPath(name, appRel, home) {
|
|
9120
9082
|
return path25.join(
|
|
@@ -9167,7 +9129,7 @@ async function readLaunchConfig(name, appRel, home) {
|
|
|
9167
9129
|
const file = launchConfigPath(name, appRel, home);
|
|
9168
9130
|
let content;
|
|
9169
9131
|
try {
|
|
9170
|
-
content = await
|
|
9132
|
+
content = await fs16.readFile(file, "utf8");
|
|
9171
9133
|
} catch {
|
|
9172
9134
|
return void 0;
|
|
9173
9135
|
}
|
|
@@ -9209,7 +9171,7 @@ async function listApps(name, home) {
|
|
|
9209
9171
|
async function walk(at, rel, depth) {
|
|
9210
9172
|
let entries;
|
|
9211
9173
|
try {
|
|
9212
|
-
entries = await
|
|
9174
|
+
entries = await fs16.readdir(at, { withFileTypes: true });
|
|
9213
9175
|
} catch {
|
|
9214
9176
|
return;
|
|
9215
9177
|
}
|
|
@@ -9218,7 +9180,7 @@ async function listApps(name, home) {
|
|
|
9218
9180
|
);
|
|
9219
9181
|
if (hasLaunch) {
|
|
9220
9182
|
try {
|
|
9221
|
-
await
|
|
9183
|
+
await fs16.access(path25.join(at, LAUNCH_DIRNAME, LAUNCH_FILENAME));
|
|
9222
9184
|
if (rel) out.push(rel);
|
|
9223
9185
|
} catch {
|
|
9224
9186
|
}
|
|
@@ -9249,7 +9211,7 @@ var init_launch_config = __esm({
|
|
|
9249
9211
|
});
|
|
9250
9212
|
|
|
9251
9213
|
// src/completion/resolve.ts
|
|
9252
|
-
import { existsSync as existsSync15, promises as
|
|
9214
|
+
import { existsSync as existsSync15, promises as fs17 } from "fs";
|
|
9253
9215
|
import path26 from "path";
|
|
9254
9216
|
async function resolveCompletions(line, point, opts = {}) {
|
|
9255
9217
|
const { prev, current } = parseCompletionLine(line, point);
|
|
@@ -9401,7 +9363,7 @@ async function listContainerNames(ctx) {
|
|
|
9401
9363
|
const home = ctx.opts.monocerosHome ?? monocerosHome();
|
|
9402
9364
|
const dir = path26.join(home, "container-configs");
|
|
9403
9365
|
if (!existsSync15(dir)) return [];
|
|
9404
|
-
const entries = await
|
|
9366
|
+
const entries = await fs17.readdir(dir);
|
|
9405
9367
|
return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length)).sort();
|
|
9406
9368
|
}
|
|
9407
9369
|
function containerNameFromCtx(ctx) {
|
|
@@ -9464,7 +9426,7 @@ async function collectDirs(dir, maxDepth) {
|
|
|
9464
9426
|
async function walk(at, rel, depth) {
|
|
9465
9427
|
let entries;
|
|
9466
9428
|
try {
|
|
9467
|
-
entries = await
|
|
9429
|
+
entries = await fs17.readdir(at, { withFileTypes: true });
|
|
9468
9430
|
} catch {
|
|
9469
9431
|
return;
|
|
9470
9432
|
}
|
|
@@ -9808,7 +9770,7 @@ var init_complete = __esm({
|
|
|
9808
9770
|
});
|
|
9809
9771
|
|
|
9810
9772
|
// src/update/notifier.ts
|
|
9811
|
-
import { spawn as
|
|
9773
|
+
import { spawn as spawn11 } from "child_process";
|
|
9812
9774
|
function isNewerVersion(latest, current) {
|
|
9813
9775
|
const parse = (v) => {
|
|
9814
9776
|
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
|
|
@@ -9867,7 +9829,7 @@ function spawnBackgroundCheck() {
|
|
|
9867
9829
|
const self = process.argv[1];
|
|
9868
9830
|
if (!self) return;
|
|
9869
9831
|
try {
|
|
9870
|
-
const child =
|
|
9832
|
+
const child = spawn11(process.execPath, [self, "__update-check"], {
|
|
9871
9833
|
detached: true,
|
|
9872
9834
|
stdio: "ignore"
|
|
9873
9835
|
});
|
|
@@ -10061,162 +10023,6 @@ function generateComposedYml(name, composed, lookupManifest, repoUrls = [], port
|
|
|
10061
10023
|
}
|
|
10062
10024
|
return ensureTrailingNewline(lines.join("\n"));
|
|
10063
10025
|
}
|
|
10064
|
-
function generateDocumentedYml(name, catalog, lookupManifest, repoUrls = [], ports = []) {
|
|
10065
|
-
const byCategory = groupByCategory(catalog);
|
|
10066
|
-
const lines = [];
|
|
10067
|
-
pushHeader(lines, SCHEMA_HEADER_DOCUMENTED, name);
|
|
10068
|
-
lines.push("");
|
|
10069
|
-
lines.push("schemaVersion: 1");
|
|
10070
|
-
lines.push(`name: ${name}`);
|
|
10071
|
-
lines.push(
|
|
10072
|
-
"# Pinned runtime base image, reused on every apply (never auto-bumped)."
|
|
10073
|
-
);
|
|
10074
|
-
lines.push(
|
|
10075
|
-
"# `monoceros upgrade <name>` refreshes the tooling and moves this to the"
|
|
10076
|
-
);
|
|
10077
|
-
lines.push("# latest runtime when a newer one exists.");
|
|
10078
|
-
lines.push(`runtimeVersion: ${DEFAULT_RUNTIME_VERSION}`);
|
|
10079
|
-
lines.push("");
|
|
10080
|
-
if (byCategory.language.length > 0) {
|
|
10081
|
-
pushSectionHeader(
|
|
10082
|
-
lines,
|
|
10083
|
-
LANGUAGES_HEADER,
|
|
10084
|
-
/* commented */
|
|
10085
|
-
true
|
|
10086
|
-
);
|
|
10087
|
-
lines.push("# languages:");
|
|
10088
|
-
for (const c of byCategory.language) {
|
|
10089
|
-
for (const lang of c.file.contributes.languages ?? []) {
|
|
10090
|
-
const ver = LANGUAGE_CATALOG[lang]?.defaultVersion;
|
|
10091
|
-
lines.push(`# - ${ver ? `${lang}:${ver}` : lang}`);
|
|
10092
|
-
}
|
|
10093
|
-
}
|
|
10094
|
-
lines.push("");
|
|
10095
|
-
}
|
|
10096
|
-
if (byCategory.service.length > 0) {
|
|
10097
|
-
pushSectionHeader(
|
|
10098
|
-
lines,
|
|
10099
|
-
SERVICES_HEADER,
|
|
10100
|
-
/* commented */
|
|
10101
|
-
true
|
|
10102
|
-
);
|
|
10103
|
-
lines.push("# services:");
|
|
10104
|
-
for (const c of byCategory.service) {
|
|
10105
|
-
for (const svc of c.file.contributes.services ?? []) {
|
|
10106
|
-
const body = renderServiceObjectBody(expandCuratedService(svc));
|
|
10107
|
-
lines.push(`# - ${body[0]}`);
|
|
10108
|
-
for (const line of body.slice(1)) lines.push(`# ${line}`);
|
|
10109
|
-
const exComment = exampleVolumesComment(
|
|
10110
|
-
curatedServiceExampleVolumes(svc)
|
|
10111
|
-
);
|
|
10112
|
-
if (exComment) {
|
|
10113
|
-
for (const cl of exComment.split("\n")) lines.push(`# ${cl}`);
|
|
10114
|
-
}
|
|
10115
|
-
}
|
|
10116
|
-
}
|
|
10117
|
-
lines.push("");
|
|
10118
|
-
}
|
|
10119
|
-
if (byCategory.feature.length > 0) {
|
|
10120
|
-
pushSectionHeader(
|
|
10121
|
-
lines,
|
|
10122
|
-
FEATURES_HEADER_DOCUMENTED,
|
|
10123
|
-
/* commented */
|
|
10124
|
-
true
|
|
10125
|
-
);
|
|
10126
|
-
lines.push("# features:");
|
|
10127
|
-
const renderedRefs = /* @__PURE__ */ new Set();
|
|
10128
|
-
const topLevel = byCategory.feature.filter((c) => !c.name.includes("/"));
|
|
10129
|
-
for (const c of topLevel) {
|
|
10130
|
-
for (const f of c.file.contributes.features ?? []) {
|
|
10131
|
-
if (renderedRefs.has(f.ref)) continue;
|
|
10132
|
-
renderedRefs.add(f.ref);
|
|
10133
|
-
lines.push("#");
|
|
10134
|
-
renderFeatureBlock(
|
|
10135
|
-
lines,
|
|
10136
|
-
f,
|
|
10137
|
-
lookupManifest(f.ref),
|
|
10138
|
-
/* commented */
|
|
10139
|
-
true
|
|
10140
|
-
);
|
|
10141
|
-
}
|
|
10142
|
-
}
|
|
10143
|
-
for (const c of byCategory.feature) {
|
|
10144
|
-
if (!c.name.includes("/")) continue;
|
|
10145
|
-
for (const f of c.file.contributes.features ?? []) {
|
|
10146
|
-
if (renderedRefs.has(f.ref)) continue;
|
|
10147
|
-
renderedRefs.add(f.ref);
|
|
10148
|
-
lines.push("#");
|
|
10149
|
-
renderFeatureBlock(
|
|
10150
|
-
lines,
|
|
10151
|
-
f,
|
|
10152
|
-
lookupManifest(f.ref),
|
|
10153
|
-
/* commented */
|
|
10154
|
-
true
|
|
10155
|
-
);
|
|
10156
|
-
}
|
|
10157
|
-
}
|
|
10158
|
-
lines.push("");
|
|
10159
|
-
}
|
|
10160
|
-
if (repoUrls.length > 0) {
|
|
10161
|
-
pushGitIdentityBlock(lines);
|
|
10162
|
-
pushSectionHeader(
|
|
10163
|
-
lines,
|
|
10164
|
-
REPOS_HEADER,
|
|
10165
|
-
/* commented */
|
|
10166
|
-
false
|
|
10167
|
-
);
|
|
10168
|
-
lines.push("repos:");
|
|
10169
|
-
for (const url of repoUrls) {
|
|
10170
|
-
lines.push(` - url: ${url}`);
|
|
10171
|
-
}
|
|
10172
|
-
lines.push("");
|
|
10173
|
-
} else {
|
|
10174
|
-
pushSectionHeader(
|
|
10175
|
-
lines,
|
|
10176
|
-
REPOS_HEADER,
|
|
10177
|
-
/* commented */
|
|
10178
|
-
true
|
|
10179
|
-
);
|
|
10180
|
-
lines.push("# repos:");
|
|
10181
|
-
lines.push("# - url: https://github.com/<org>/<repo>.git");
|
|
10182
|
-
lines.push("# path: <folder>");
|
|
10183
|
-
lines.push("# provider: github");
|
|
10184
|
-
lines.push("# git:");
|
|
10185
|
-
lines.push("# user:");
|
|
10186
|
-
lines.push("# name: Your Name");
|
|
10187
|
-
lines.push("# email: you@example.com");
|
|
10188
|
-
lines.push("");
|
|
10189
|
-
}
|
|
10190
|
-
if (ports.length > 0) {
|
|
10191
|
-
pushSectionHeader(
|
|
10192
|
-
lines,
|
|
10193
|
-
routingHeader(name),
|
|
10194
|
-
/* commented */
|
|
10195
|
-
false
|
|
10196
|
-
);
|
|
10197
|
-
lines.push("routing:");
|
|
10198
|
-
lines.push(" ports:");
|
|
10199
|
-
for (const port of ports) {
|
|
10200
|
-
lines.push(` - ${port}`);
|
|
10201
|
-
}
|
|
10202
|
-
lines.push(" # vscodeAutoForward: false");
|
|
10203
|
-
lines.push("");
|
|
10204
|
-
} else {
|
|
10205
|
-
pushSectionHeader(
|
|
10206
|
-
lines,
|
|
10207
|
-
routingHeader(name),
|
|
10208
|
-
/* commented */
|
|
10209
|
-
true
|
|
10210
|
-
);
|
|
10211
|
-
lines.push("# routing:");
|
|
10212
|
-
lines.push("# ports:");
|
|
10213
|
-
lines.push("# - 3000");
|
|
10214
|
-
lines.push("# - 5173");
|
|
10215
|
-
lines.push("# vscodeAutoForward: false");
|
|
10216
|
-
lines.push("");
|
|
10217
|
-
}
|
|
10218
|
-
return ensureTrailingNewline(lines.join("\n"));
|
|
10219
|
-
}
|
|
10220
10026
|
function pushServiceEntry(out, svc) {
|
|
10221
10027
|
if (svc.kind === "custom") {
|
|
10222
10028
|
const { bodyLines, comment } = renderCustomService(
|
|
@@ -10300,24 +10106,10 @@ function renderScalarValue(value) {
|
|
|
10300
10106
|
}
|
|
10301
10107
|
return String(value);
|
|
10302
10108
|
}
|
|
10303
|
-
function groupByCategory(catalog) {
|
|
10304
|
-
const out = {
|
|
10305
|
-
language: [],
|
|
10306
|
-
service: [],
|
|
10307
|
-
feature: []
|
|
10308
|
-
};
|
|
10309
|
-
const sorted = [...catalog.values()].sort(
|
|
10310
|
-
(a, b) => a.name.localeCompare(b.name)
|
|
10311
|
-
);
|
|
10312
|
-
for (const c of sorted) {
|
|
10313
|
-
out[c.file.category].push(c);
|
|
10314
|
-
}
|
|
10315
|
-
return out;
|
|
10316
|
-
}
|
|
10317
10109
|
function ensureTrailingNewline(s) {
|
|
10318
10110
|
return s.endsWith("\n") ? s : s + "\n";
|
|
10319
10111
|
}
|
|
10320
|
-
var SCHEMA_HEADER_ACTIVE,
|
|
10112
|
+
var SCHEMA_HEADER_ACTIVE, COMMENT_WIDTH, LANGUAGES_HEADER, SERVICES_HEADER, APT_PACKAGES_HEADER, FEATURES_HEADER_ACTIVE, REPOS_HEADER, GIT_IDENTITY_HEADER;
|
|
10321
10113
|
var init_generator = __esm({
|
|
10322
10114
|
"src/init/generator.ts"() {
|
|
10323
10115
|
"use strict";
|
|
@@ -10325,21 +10117,19 @@ var init_generator = __esm({
|
|
|
10325
10117
|
init_catalog();
|
|
10326
10118
|
init_service_doc();
|
|
10327
10119
|
init_env_file();
|
|
10328
|
-
SCHEMA_HEADER_ACTIVE = "# Solution-config
|
|
10329
|
-
SCHEMA_HEADER_DOCUMENTED = "# Solution-config \u2014 describes what should be inside your dev-container.\n# Every section is commented out by default; un-comment what you need\n# (strip one `#` per line of the block), then run `monoceros apply <name>`.";
|
|
10120
|
+
SCHEMA_HEADER_ACTIVE = "# Solution-config: what should be inside your dev-container.\n# Starts minimal; add languages, features, services, or repos with\n# `monoceros add-feature/add-service/add-repo <name>` (see\n# `monoceros list-components`), then `monoceros apply <name>`.";
|
|
10330
10121
|
COMMENT_WIDTH = 76;
|
|
10331
10122
|
LANGUAGES_HEADER = "Language runtimes installed inside the dev-container. Pick the ones your projects build against. The catalog of available runtimes is shown by `monoceros list-components`.";
|
|
10332
10123
|
SERVICES_HEADER = "Sibling containers that run alongside the dev-container (databases, caches, message queues, \u2026). Each service is reachable from inside the dev-container by its name as hostname (e.g. `postgres://postgres:5432`). Activating any service switches the container to docker-compose mode automatically.";
|
|
10333
10124
|
APT_PACKAGES_HEADER = "Debian/Ubuntu apt packages installed in the dev-container at build time. No curated list \u2014 any apt package name works; an invalid name surfaces as an apt error during build.";
|
|
10334
10125
|
FEATURES_HEADER_ACTIVE = "A Monoceros dev-container is shaped by features \u2014 pluggable units that drop tooling (AI assistants, language CLIs, cloud SDKs, \u2026) into the container and bring their own options. The features active for this container are listed below; adjust their options as needed. Shared credentials used across containers belong in monoceros-config.yml under `defaults.features.<ref>` rather than here. Full catalog: `monoceros list-components`.";
|
|
10335
|
-
|
|
10336
|
-
REPOS_HEADER = "Git repositories cloned into `projects/` on container start-up. HTTPS URLs only. The provider is auto-detected for github.com / gitlab.com / bitbucket.org; for any other host (self-hosted GitLab, Gitea, \u2026) declare `provider:` explicitly. Add more later with `monoceros add-repo`.";
|
|
10126
|
+
REPOS_HEADER = "Git repositories cloned into `projects/` on container start-up. HTTPS URLs only. The provider is auto-detected for github.com / gitlab.com / bitbucket.org; for any other host (self-hosted GitLab, GitHub Enterprise, \u2026) declare `provider:` explicitly. Add more later with `monoceros add-repo`.";
|
|
10337
10127
|
GIT_IDENTITY_HEADER = "Git committer identity for commits made inside the container. The ${VAR} values resolve from <name>.env at apply time \u2014 fill them there, or leave them blank to fall back to your global git config (or a one-time prompt). Override per repo under repos[].git.user.";
|
|
10338
10128
|
}
|
|
10339
10129
|
});
|
|
10340
10130
|
|
|
10341
10131
|
// src/init/index.ts
|
|
10342
|
-
import { existsSync as existsSync16, promises as
|
|
10132
|
+
import { existsSync as existsSync16, promises as fs18 } from "fs";
|
|
10343
10133
|
import path27 from "path";
|
|
10344
10134
|
import { consola as consola16 } from "consola";
|
|
10345
10135
|
async function runInit(opts) {
|
|
@@ -10412,22 +10202,24 @@ async function runInit(opts) {
|
|
|
10412
10202
|
seenPorts.add(raw);
|
|
10413
10203
|
ports.push(raw);
|
|
10414
10204
|
}
|
|
10415
|
-
|
|
10205
|
+
const explicitFeatures = opts.features ?? [];
|
|
10206
|
+
const repoFeatures = [
|
|
10207
|
+
...new Set(
|
|
10208
|
+
repos.map((u) => KNOWN_PROVIDER_HOSTS[new URL(u).hostname.toLowerCase()]).filter(
|
|
10209
|
+
(p) => p === "github" || p === "gitlab"
|
|
10210
|
+
).filter((p) => !explicitFeatures.includes(p)).map((p) => catalog.get(p)?.file.contributes.features?.[0]?.ref).filter((ref) => !!ref)
|
|
10211
|
+
)
|
|
10212
|
+
].filter((ref) => !explicitFeatures.includes(ref));
|
|
10416
10213
|
const composed = resolveComposedInit(catalog, {
|
|
10417
10214
|
languages: opts.languages ?? [],
|
|
10418
|
-
features:
|
|
10215
|
+
features: [...explicitFeatures, ...repoFeatures],
|
|
10419
10216
|
services: opts.services ?? [],
|
|
10420
10217
|
aptPackages: opts.aptPackages ?? []
|
|
10421
10218
|
});
|
|
10422
|
-
const
|
|
10423
|
-
|
|
10424
|
-
text = generateDocumentedYml(opts.name, catalog, lookup, repos, ports);
|
|
10425
|
-
} else {
|
|
10426
|
-
text = generateComposedYml(opts.name, composed, lookup, repos, ports);
|
|
10427
|
-
}
|
|
10428
|
-
await fs17.mkdir(containerConfigsDir(home), { recursive: true });
|
|
10219
|
+
const text = generateComposedYml(opts.name, composed, lookup, repos, ports);
|
|
10220
|
+
await fs18.mkdir(containerConfigsDir(home), { recursive: true });
|
|
10429
10221
|
await ensureEnvGitignored(containerConfigsDir(home));
|
|
10430
|
-
await
|
|
10222
|
+
await fs18.writeFile(dest, text, "utf8");
|
|
10431
10223
|
const envPath = containerEnvPath(opts.name, home);
|
|
10432
10224
|
const seedVars = {};
|
|
10433
10225
|
for (const f of composed.features) {
|
|
@@ -10449,21 +10241,13 @@ async function runInit(opts) {
|
|
|
10449
10241
|
seedVars[GIT_IDENTITY_VAR.email] = "";
|
|
10450
10242
|
}
|
|
10451
10243
|
await ensureEnvVars(envPath, opts.name, seedVars);
|
|
10452
|
-
const documented = !anyComposed;
|
|
10453
10244
|
const ymlRel = path27.relative(home, dest);
|
|
10454
10245
|
const envRel = path27.relative(home, envPath);
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
} else {
|
|
10461
|
-
logger.success(`Composed into ${ymlRel} and ${envRel}.`);
|
|
10462
|
-
logger.info(
|
|
10463
|
-
`Edit the files if you need to tweak, then \`monoceros apply ${opts.name}\`.`
|
|
10464
|
-
);
|
|
10465
|
-
}
|
|
10466
|
-
return { configPath: dest, documented };
|
|
10246
|
+
logger.success(`Wrote ${ymlRel} and ${envRel}.`);
|
|
10247
|
+
logger.info(
|
|
10248
|
+
`Add components with \`monoceros add-feature/add-service/add-repo ${opts.name}\` (see \`monoceros list-components\`), then \`monoceros apply ${opts.name}\`.`
|
|
10249
|
+
);
|
|
10250
|
+
return { configPath: dest };
|
|
10467
10251
|
}
|
|
10468
10252
|
function resolveComposedInit(catalog, raw) {
|
|
10469
10253
|
return {
|
|
@@ -11010,14 +10794,14 @@ var init_list_components = __esm({
|
|
|
11010
10794
|
});
|
|
11011
10795
|
|
|
11012
10796
|
// src/commands/logs.ts
|
|
11013
|
-
import { spawn as
|
|
10797
|
+
import { spawn as spawn12 } from "child_process";
|
|
11014
10798
|
import path28 from "path";
|
|
11015
10799
|
import { defineCommand as defineCommand16 } from "citty";
|
|
11016
10800
|
import { consola as consola20 } from "consola";
|
|
11017
10801
|
function tailLogFile(file, follow) {
|
|
11018
10802
|
const [cmd, args] = follow ? ["tail", ["-F", file]] : ["cat", [file]];
|
|
11019
10803
|
return new Promise((resolve, reject) => {
|
|
11020
|
-
const child =
|
|
10804
|
+
const child = spawn12(cmd, args, { stdio: "inherit" });
|
|
11021
10805
|
child.on("error", reject);
|
|
11022
10806
|
child.on("exit", (code) => resolve(code ?? 0));
|
|
11023
10807
|
});
|
|
@@ -11315,7 +11099,7 @@ var init_remove_feature = __esm({
|
|
|
11315
11099
|
});
|
|
11316
11100
|
|
|
11317
11101
|
// src/remove/index.ts
|
|
11318
|
-
import { existsSync as existsSync17, promises as
|
|
11102
|
+
import { existsSync as existsSync17, promises as fs19 } from "fs";
|
|
11319
11103
|
import path29 from "path";
|
|
11320
11104
|
import { consola as consola24 } from "consola";
|
|
11321
11105
|
async function runRemove(opts) {
|
|
@@ -11362,17 +11146,17 @@ async function runRemove(opts) {
|
|
|
11362
11146
|
if (!opts.noBackup && (hasYml || hasContainer)) {
|
|
11363
11147
|
const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
11364
11148
|
backupPath = path29.join(home, "container-backups", `${opts.name}-${ts}`);
|
|
11365
|
-
await
|
|
11149
|
+
await fs19.mkdir(backupPath, { recursive: true });
|
|
11366
11150
|
if (hasYml) {
|
|
11367
|
-
await
|
|
11151
|
+
await fs19.copyFile(ymlPath, path29.join(backupPath, `${opts.name}.yml`));
|
|
11368
11152
|
}
|
|
11369
11153
|
if (hasEnv) {
|
|
11370
|
-
await
|
|
11154
|
+
await fs19.copyFile(envPath, path29.join(backupPath, `${opts.name}.env`));
|
|
11371
11155
|
}
|
|
11372
11156
|
if (hasContainer) {
|
|
11373
11157
|
const dst = path29.join(backupPath, "container");
|
|
11374
11158
|
try {
|
|
11375
|
-
await
|
|
11159
|
+
await fs19.cp(containerPath, dst, { recursive: true });
|
|
11376
11160
|
} catch (err) {
|
|
11377
11161
|
const code = err.code;
|
|
11378
11162
|
if (code !== "EACCES" && code !== "EPERM") {
|
|
@@ -11381,7 +11165,7 @@ async function runRemove(opts) {
|
|
|
11381
11165
|
logger.info(
|
|
11382
11166
|
`[remove] backup hit ${code} on root-owned files; copying via a throw-away alpine container\u2026`
|
|
11383
11167
|
);
|
|
11384
|
-
await
|
|
11168
|
+
await fs19.mkdir(dst, { recursive: true });
|
|
11385
11169
|
const { exitCode, stderr } = await dockerExec([
|
|
11386
11170
|
"run",
|
|
11387
11171
|
"--rm",
|
|
@@ -11407,14 +11191,14 @@ async function runRemove(opts) {
|
|
|
11407
11191
|
await stopBridgeDaemon(containerPath);
|
|
11408
11192
|
}
|
|
11409
11193
|
if (hasYml) {
|
|
11410
|
-
await
|
|
11194
|
+
await fs19.rm(ymlPath, { force: true });
|
|
11411
11195
|
}
|
|
11412
11196
|
if (hasEnv) {
|
|
11413
|
-
await
|
|
11197
|
+
await fs19.rm(envPath, { force: true });
|
|
11414
11198
|
}
|
|
11415
11199
|
if (hasContainer) {
|
|
11416
11200
|
try {
|
|
11417
|
-
await
|
|
11201
|
+
await fs19.rm(containerPath, { recursive: true, force: true });
|
|
11418
11202
|
} catch (err) {
|
|
11419
11203
|
const code = err.code;
|
|
11420
11204
|
if (code !== "EACCES" && code !== "EPERM") {
|
|
@@ -11436,7 +11220,7 @@ async function runRemove(opts) {
|
|
|
11436
11220
|
"-delete"
|
|
11437
11221
|
]);
|
|
11438
11222
|
try {
|
|
11439
|
-
await
|
|
11223
|
+
await fs19.rm(containerPath, { recursive: true, force: true });
|
|
11440
11224
|
} catch (err2) {
|
|
11441
11225
|
const code2 = err2.code;
|
|
11442
11226
|
throw new Error(
|
|
@@ -11571,7 +11355,7 @@ var init_remove2 = __esm({
|
|
|
11571
11355
|
});
|
|
11572
11356
|
|
|
11573
11357
|
// src/restore/index.ts
|
|
11574
|
-
import { existsSync as existsSync18, promises as
|
|
11358
|
+
import { existsSync as existsSync18, promises as fs20 } from "fs";
|
|
11575
11359
|
import path30 from "path";
|
|
11576
11360
|
import { consola as consola26 } from "consola";
|
|
11577
11361
|
async function runRestore(opts) {
|
|
@@ -11584,11 +11368,11 @@ async function runRestore(opts) {
|
|
|
11584
11368
|
if (!existsSync18(backup)) {
|
|
11585
11369
|
throw new Error(`Backup not found: ${backup}.`);
|
|
11586
11370
|
}
|
|
11587
|
-
const stat = await
|
|
11371
|
+
const stat = await fs20.stat(backup);
|
|
11588
11372
|
if (!stat.isDirectory()) {
|
|
11589
11373
|
throw new Error(`Backup path is not a directory: ${backup}.`);
|
|
11590
11374
|
}
|
|
11591
|
-
const entries = await
|
|
11375
|
+
const entries = await fs20.readdir(backup);
|
|
11592
11376
|
const ymlFiles = entries.filter((f) => f.endsWith(".yml"));
|
|
11593
11377
|
if (ymlFiles.length === 0) {
|
|
11594
11378
|
throw new Error(
|
|
@@ -11618,13 +11402,13 @@ async function runRestore(opts) {
|
|
|
11618
11402
|
`Refusing to restore: ${destContainer} already exists. Remove the current container first (\`monoceros remove ${name}\`).`
|
|
11619
11403
|
);
|
|
11620
11404
|
}
|
|
11621
|
-
await
|
|
11622
|
-
await
|
|
11405
|
+
await fs20.mkdir(containerConfigsDir(home), { recursive: true });
|
|
11406
|
+
await fs20.copyFile(path30.join(backup, ymlFile), destYml);
|
|
11623
11407
|
if (hasEnv) {
|
|
11624
|
-
await
|
|
11408
|
+
await fs20.copyFile(envInBackup, containerEnvPath(name, home));
|
|
11625
11409
|
}
|
|
11626
11410
|
if (hasContainer) {
|
|
11627
|
-
await
|
|
11411
|
+
await fs20.cp(containerInBackup, destContainer, { recursive: true });
|
|
11628
11412
|
}
|
|
11629
11413
|
logger.success(`Restored '${name}' from ${prettyPath(backup)}.`);
|
|
11630
11414
|
logger.info(
|
|
@@ -12334,7 +12118,7 @@ var init_port_check2 = __esm({
|
|
|
12334
12118
|
});
|
|
12335
12119
|
|
|
12336
12120
|
// src/tunnel/run.ts
|
|
12337
|
-
import { spawn as
|
|
12121
|
+
import { spawn as spawn13 } from "child_process";
|
|
12338
12122
|
import { consola as consola34 } from "consola";
|
|
12339
12123
|
function signalNumber(signal) {
|
|
12340
12124
|
switch (signal) {
|
|
@@ -12424,7 +12208,7 @@ var init_run3 = __esm({
|
|
|
12424
12208
|
init_port_check2();
|
|
12425
12209
|
SOCAT_IMAGE = "alpine/socat:1.8.0.3";
|
|
12426
12210
|
defaultDockerSpawn = (args) => {
|
|
12427
|
-
const child =
|
|
12211
|
+
const child = spawn13("docker", args, {
|
|
12428
12212
|
stdio: "inherit"
|
|
12429
12213
|
});
|
|
12430
12214
|
const exited = new Promise((resolve, reject) => {
|
|
@@ -13351,7 +13135,7 @@ var init_prune = __esm({
|
|
|
13351
13135
|
});
|
|
13352
13136
|
|
|
13353
13137
|
// src/upgrade/index.ts
|
|
13354
|
-
import { existsSync as existsSync21, promises as
|
|
13138
|
+
import { existsSync as existsSync21, promises as fs21 } from "fs";
|
|
13355
13139
|
import { consola as consola41 } from "consola";
|
|
13356
13140
|
async function fetchRuntimeVersions() {
|
|
13357
13141
|
const tokenUrl = `https://ghcr.io/token?service=ghcr.io&scope=repository:${RUNTIME_REPO}:pull`;
|
|
@@ -13468,10 +13252,10 @@ async function runUpgrade(opts) {
|
|
|
13468
13252
|
for (const name of targets) {
|
|
13469
13253
|
const ymlPath = containerConfigPath(name, home);
|
|
13470
13254
|
if (!existsSync21(ymlPath)) continue;
|
|
13471
|
-
const raw = await
|
|
13255
|
+
const raw = await fs21.readFile(ymlPath, "utf8");
|
|
13472
13256
|
const updated = setRuntimeVersion(raw, pinVersion);
|
|
13473
13257
|
if (updated !== raw) {
|
|
13474
|
-
await
|
|
13258
|
+
await fs21.writeFile(ymlPath, updated);
|
|
13475
13259
|
bumped += 1;
|
|
13476
13260
|
logger.info(`Pinned '${name}' to runtime ${pinVersion}.`);
|
|
13477
13261
|
}
|
|
@@ -13513,7 +13297,7 @@ function formatPruneLine(prune) {
|
|
|
13513
13297
|
}
|
|
13514
13298
|
async function listContainerNames2(home) {
|
|
13515
13299
|
try {
|
|
13516
|
-
const entries = await
|
|
13300
|
+
const entries = await fs21.readdir(containerConfigsDir(home));
|
|
13517
13301
|
return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length));
|
|
13518
13302
|
} catch {
|
|
13519
13303
|
return [];
|