@getmonoceros/workbench 1.36.8 → 1.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +423 -638
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
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";
|
|
@@ -6739,9 +6672,10 @@ function openInBrowser(url) {
|
|
|
6739
6672
|
return false;
|
|
6740
6673
|
}
|
|
6741
6674
|
})());
|
|
6742
|
-
const
|
|
6675
|
+
const psOpen = `Start-Process '${url.replace(/'/g, "''")}'`;
|
|
6676
|
+
const [cmd, args] = platform === "darwin" ? ["open", [url]] : platform === "win32" || wsl ? ["powershell.exe", ["-NoProfile", "-Command", psOpen]] : ["xdg-open", [url]];
|
|
6743
6677
|
try {
|
|
6744
|
-
const child =
|
|
6678
|
+
const child = spawn4(cmd, args, {
|
|
6745
6679
|
stdio: "ignore",
|
|
6746
6680
|
detached: true
|
|
6747
6681
|
});
|
|
@@ -6947,7 +6881,7 @@ var init_runtime_pull_hint = __esm({
|
|
|
6947
6881
|
});
|
|
6948
6882
|
|
|
6949
6883
|
// src/devcontainer/cli.ts
|
|
6950
|
-
import { spawn as
|
|
6884
|
+
import { spawn as spawn5 } from "child_process";
|
|
6951
6885
|
import { readFileSync as readFileSync6 } from "fs";
|
|
6952
6886
|
import { createRequire } from "module";
|
|
6953
6887
|
import path17 from "path";
|
|
@@ -6975,7 +6909,7 @@ var init_cli = __esm({
|
|
|
6975
6909
|
const env = { ...process.env, DOCKER_CLI_HINTS: "false" };
|
|
6976
6910
|
return new Promise((resolve, reject) => {
|
|
6977
6911
|
if (options.interactive) {
|
|
6978
|
-
const child2 =
|
|
6912
|
+
const child2 = spawn5(process.execPath, [binPath, ...args], {
|
|
6979
6913
|
cwd,
|
|
6980
6914
|
env,
|
|
6981
6915
|
stdio: "inherit"
|
|
@@ -6984,7 +6918,7 @@ var init_cli = __esm({
|
|
|
6984
6918
|
child2.on("exit", (code) => resolve(code ?? 0));
|
|
6985
6919
|
return;
|
|
6986
6920
|
}
|
|
6987
|
-
const child =
|
|
6921
|
+
const child = spawn5(process.execPath, [binPath, ...args], {
|
|
6988
6922
|
cwd,
|
|
6989
6923
|
env,
|
|
6990
6924
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -7032,14 +6966,14 @@ var init_cli = __esm({
|
|
|
7032
6966
|
});
|
|
7033
6967
|
|
|
7034
6968
|
// src/devcontainer/compose.ts
|
|
7035
|
-
import { spawn as
|
|
6969
|
+
import { spawn as spawn6 } from "child_process";
|
|
7036
6970
|
import { existsSync as existsSync10 } from "fs";
|
|
7037
6971
|
import path18 from "path";
|
|
7038
6972
|
import { Writable } from "stream";
|
|
7039
6973
|
import { consola as consola10 } from "consola";
|
|
7040
6974
|
function spawnDockerComposeTo(opts) {
|
|
7041
6975
|
return (args, cwd) => new Promise((resolve, reject) => {
|
|
7042
|
-
const child =
|
|
6976
|
+
const child = spawn6("docker", ["compose", ...args], {
|
|
7043
6977
|
cwd,
|
|
7044
6978
|
stdio: ["inherit", "pipe", "pipe"]
|
|
7045
6979
|
});
|
|
@@ -7367,7 +7301,7 @@ var init_compose = __esm({
|
|
|
7367
7301
|
init_proxy();
|
|
7368
7302
|
spawnDockerCompose = (args, cwd) => {
|
|
7369
7303
|
return new Promise((resolve, reject) => {
|
|
7370
|
-
const child =
|
|
7304
|
+
const child = spawn6("docker", ["compose", ...args], {
|
|
7371
7305
|
cwd,
|
|
7372
7306
|
stdio: ["inherit", "pipe", "pipe"]
|
|
7373
7307
|
});
|
|
@@ -7379,7 +7313,7 @@ var init_compose = __esm({
|
|
|
7379
7313
|
};
|
|
7380
7314
|
spawnDocker = (args) => {
|
|
7381
7315
|
return new Promise((resolve, reject) => {
|
|
7382
|
-
const child =
|
|
7316
|
+
const child = spawn6("docker", args, {
|
|
7383
7317
|
stdio: ["ignore", "pipe", "pipe"]
|
|
7384
7318
|
});
|
|
7385
7319
|
let stdout = "";
|
|
@@ -7404,7 +7338,7 @@ var init_compose = __esm({
|
|
|
7404
7338
|
});
|
|
7405
7339
|
|
|
7406
7340
|
// src/devcontainer/bridge-daemon.ts
|
|
7407
|
-
import { spawn as
|
|
7341
|
+
import { spawn as spawn7 } from "child_process";
|
|
7408
7342
|
import {
|
|
7409
7343
|
existsSync as existsSync11,
|
|
7410
7344
|
mkdirSync,
|
|
@@ -7441,7 +7375,7 @@ function spawnBridgeDaemon(root) {
|
|
|
7441
7375
|
const self = process.argv[1];
|
|
7442
7376
|
if (!self) return;
|
|
7443
7377
|
mkdirSync(relayDir(root), { recursive: true });
|
|
7444
|
-
const child =
|
|
7378
|
+
const child = spawn7(
|
|
7445
7379
|
process.execPath,
|
|
7446
7380
|
[...process.execArgv, self, "__bridge", root],
|
|
7447
7381
|
{
|
|
@@ -7670,8 +7604,11 @@ function createApplyProgress(opts) {
|
|
|
7670
7604
|
tail.push(line);
|
|
7671
7605
|
if (tail.length > TAIL_LINES) tail.shift();
|
|
7672
7606
|
for (const trig of PHASE_TRIGGERS) {
|
|
7673
|
-
|
|
7674
|
-
|
|
7607
|
+
const m = line.match(trig.pattern);
|
|
7608
|
+
if (m) {
|
|
7609
|
+
setPhase(
|
|
7610
|
+
typeof trig.label === "function" ? trig.label(m) : trig.label
|
|
7611
|
+
);
|
|
7675
7612
|
break;
|
|
7676
7613
|
}
|
|
7677
7614
|
}
|
|
@@ -7767,6 +7704,15 @@ var init_apply_progress = __esm({
|
|
|
7767
7704
|
pattern: /Start: Run: docker (?:run\b|compose\b.* up\b)/i,
|
|
7768
7705
|
label: "starting container\u2026"
|
|
7769
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
|
+
},
|
|
7770
7716
|
{ pattern: /Running the postCreateCommand/i, label: "running postCreate\u2026" }
|
|
7771
7717
|
];
|
|
7772
7718
|
}
|
|
@@ -7940,7 +7886,7 @@ var init_machine_state = __esm({
|
|
|
7940
7886
|
});
|
|
7941
7887
|
|
|
7942
7888
|
// src/devcontainer/docker-mode.ts
|
|
7943
|
-
import { spawn as
|
|
7889
|
+
import { spawn as spawn8 } from "child_process";
|
|
7944
7890
|
async function detectDockerMode(options = {}) {
|
|
7945
7891
|
const spawnFn = options.spawn ?? realDockerInfo;
|
|
7946
7892
|
try {
|
|
@@ -7990,7 +7936,7 @@ var init_docker_mode = __esm({
|
|
|
7990
7936
|
init_format();
|
|
7991
7937
|
realDockerInfo = () => {
|
|
7992
7938
|
return new Promise((resolve, reject) => {
|
|
7993
|
-
const child =
|
|
7939
|
+
const child = spawn8(
|
|
7994
7940
|
"docker",
|
|
7995
7941
|
["info", "--format", "{{json .SecurityOptions}}"],
|
|
7996
7942
|
{
|
|
@@ -8009,8 +7955,8 @@ var init_docker_mode = __esm({
|
|
|
8009
7955
|
});
|
|
8010
7956
|
|
|
8011
7957
|
// src/devcontainer/identity.ts
|
|
8012
|
-
import { spawn as
|
|
8013
|
-
import { promises as
|
|
7958
|
+
import { spawn as spawn9 } from "child_process";
|
|
7959
|
+
import { promises as fs14 } from "fs";
|
|
8014
7960
|
import path22 from "path";
|
|
8015
7961
|
import { consola as consola11 } from "consola";
|
|
8016
7962
|
async function resolveIdentityWithPrompt(options = {}) {
|
|
@@ -8081,8 +8027,8 @@ async function collectGitIdentity(devContainerRoot, options = {}) {
|
|
|
8081
8027
|
const lines = ["[user]"];
|
|
8082
8028
|
if (resolved.name !== void 0) lines.push(` name = ${resolved.name}`);
|
|
8083
8029
|
if (resolved.email !== void 0) lines.push(` email = ${resolved.email}`);
|
|
8084
|
-
await
|
|
8085
|
-
await
|
|
8030
|
+
await fs14.mkdir(gitconfigDir, { recursive: true });
|
|
8031
|
+
await fs14.writeFile(gitconfigPath, lines.join("\n") + "\n");
|
|
8086
8032
|
return {
|
|
8087
8033
|
...resolved.name !== void 0 ? { name: resolved.name } : {},
|
|
8088
8034
|
...resolved.email !== void 0 ? { email: resolved.email } : {},
|
|
@@ -8125,7 +8071,7 @@ async function readKeyFromHost(spawnFn, key, logger) {
|
|
|
8125
8071
|
}
|
|
8126
8072
|
async function readExistingGitconfig(filePath) {
|
|
8127
8073
|
try {
|
|
8128
|
-
const content = await
|
|
8074
|
+
const content = await fs14.readFile(filePath, "utf8");
|
|
8129
8075
|
const result = {};
|
|
8130
8076
|
const nameMatch = /^\s*name\s*=\s*(.+?)\s*$/m.exec(content);
|
|
8131
8077
|
const emailMatch = /^\s*email\s*=\s*(.+?)\s*$/m.exec(content);
|
|
@@ -8142,7 +8088,7 @@ var init_identity = __esm({
|
|
|
8142
8088
|
"use strict";
|
|
8143
8089
|
realGitConfigGet = (key) => {
|
|
8144
8090
|
return new Promise((resolve, reject) => {
|
|
8145
|
-
const child =
|
|
8091
|
+
const child = spawn9("git", ["config", "--global", "--get", key], {
|
|
8146
8092
|
stdio: ["ignore", "pipe", "inherit"]
|
|
8147
8093
|
});
|
|
8148
8094
|
let stdout = "";
|
|
@@ -8202,7 +8148,7 @@ var init_identity = __esm({
|
|
|
8202
8148
|
});
|
|
8203
8149
|
|
|
8204
8150
|
// src/apply/index.ts
|
|
8205
|
-
import { existsSync as existsSync12, promises as
|
|
8151
|
+
import { existsSync as existsSync12, promises as fs15 } from "fs";
|
|
8206
8152
|
import { consola as consola12 } from "consola";
|
|
8207
8153
|
async function runApply(opts) {
|
|
8208
8154
|
const home = opts.monocerosHome ?? monocerosHome();
|
|
@@ -8242,9 +8188,18 @@ ${sectionLine(label)}
|
|
|
8242
8188
|
warnOnDeprecatedFeatureRefs(parsed.config.features, globalConfig, logger);
|
|
8243
8189
|
const envPath = containerEnvPath(opts.name, home);
|
|
8244
8190
|
await ensureEnvGitignored(containerConfigsDir(home));
|
|
8245
|
-
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
|
+
}
|
|
8246
8201
|
const resolvedFeatures = interpolateFeatureOptions(
|
|
8247
|
-
|
|
8202
|
+
repoTokens.features,
|
|
8248
8203
|
envVars
|
|
8249
8204
|
);
|
|
8250
8205
|
const createOpts = normalizeOptions(
|
|
@@ -8326,13 +8281,11 @@ Fix the value in the env file (or the yml).`
|
|
|
8326
8281
|
throw new Error(formatUnknownProviderError(unknownProviderHosts));
|
|
8327
8282
|
}
|
|
8328
8283
|
if (hostsToFetch.length > 0) {
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
if (missing.length > 0) {
|
|
8335
|
-
throw new Error(formatMissingCredentialsError(missing));
|
|
8284
|
+
if (repoTokens.hostTokens.size > 0) {
|
|
8285
|
+
await collectGitCredentials(targetDir, hostsToFetch, {
|
|
8286
|
+
patByHost: repoTokens.hostTokens,
|
|
8287
|
+
logger: idLogger
|
|
8288
|
+
});
|
|
8336
8289
|
}
|
|
8337
8290
|
}
|
|
8338
8291
|
section("Scaffold");
|
|
@@ -8342,7 +8295,7 @@ Fix the value in the env file (or the yml).`
|
|
|
8342
8295
|
if (dockerMode === "rootless") {
|
|
8343
8296
|
throw new Error(formatRootlessNotSupportedError());
|
|
8344
8297
|
}
|
|
8345
|
-
await
|
|
8298
|
+
await fs15.mkdir(targetDir, { recursive: true });
|
|
8346
8299
|
await writeScaffold(createOpts, targetDir, { dockerMode });
|
|
8347
8300
|
await writeStateFile(
|
|
8348
8301
|
targetDir,
|
|
@@ -8566,6 +8519,15 @@ ${stripAnsi(formatted)}
|
|
|
8566
8519
|
`);
|
|
8567
8520
|
}
|
|
8568
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
|
+
}
|
|
8569
8531
|
await applyLog.close();
|
|
8570
8532
|
progressOut.write(`
|
|
8571
8533
|
${dim(`log: ${prettyPath(applyLog.path)}`)}
|
|
@@ -8581,7 +8543,7 @@ ${stripAnsi(formatted)}
|
|
|
8581
8543
|
}
|
|
8582
8544
|
async function assertSafeTargetDir(targetDir, expectedOrigin) {
|
|
8583
8545
|
if (!existsSync12(targetDir)) return;
|
|
8584
|
-
const entries = await
|
|
8546
|
+
const entries = await fs15.readdir(targetDir);
|
|
8585
8547
|
if (entries.length === 0) return;
|
|
8586
8548
|
const state = await readStateFile(targetDir);
|
|
8587
8549
|
if (state) {
|
|
@@ -8651,7 +8613,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
8651
8613
|
}
|
|
8652
8614
|
if (wantContainer) {
|
|
8653
8615
|
try {
|
|
8654
|
-
const text = await
|
|
8616
|
+
const text = await fs15.readFile(ymlPath, "utf8");
|
|
8655
8617
|
const parsed = parseConfig(text, ymlPath);
|
|
8656
8618
|
const changed = setContainerGitUserInDoc(parsed.doc, {
|
|
8657
8619
|
name: prompted.name,
|
|
@@ -8659,7 +8621,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
8659
8621
|
});
|
|
8660
8622
|
if (changed) {
|
|
8661
8623
|
const out = stringifyConfig(parsed.doc);
|
|
8662
|
-
await
|
|
8624
|
+
await fs15.writeFile(ymlPath, out, "utf8");
|
|
8663
8625
|
logger.info(
|
|
8664
8626
|
`Saved identity in this container \u2014 wrote git.user into ${prettyPath(ymlPath)}.`
|
|
8665
8627
|
);
|
|
@@ -8705,6 +8667,7 @@ var init_apply = __esm({
|
|
|
8705
8667
|
init_identity();
|
|
8706
8668
|
init_global();
|
|
8707
8669
|
init_yml();
|
|
8670
|
+
init_repo_token();
|
|
8708
8671
|
}
|
|
8709
8672
|
});
|
|
8710
8673
|
|
|
@@ -8759,7 +8722,7 @@ var init_shell = __esm({
|
|
|
8759
8722
|
});
|
|
8760
8723
|
|
|
8761
8724
|
// src/open/index.ts
|
|
8762
|
-
import { spawn as
|
|
8725
|
+
import { spawn as spawn10 } from "child_process";
|
|
8763
8726
|
import { accessSync, constants, existsSync as existsSync14 } from "fs";
|
|
8764
8727
|
import path24 from "path";
|
|
8765
8728
|
import { consola as consola13 } from "consola";
|
|
@@ -8795,7 +8758,7 @@ async function resolveEditorBinary(tool) {
|
|
|
8795
8758
|
return null;
|
|
8796
8759
|
}
|
|
8797
8760
|
function realLaunch(bin, args) {
|
|
8798
|
-
const child =
|
|
8761
|
+
const child = spawn10(bin, args, {
|
|
8799
8762
|
detached: true,
|
|
8800
8763
|
stdio: "ignore"
|
|
8801
8764
|
});
|
|
@@ -8879,7 +8842,7 @@ var CLI_VERSION;
|
|
|
8879
8842
|
var init_version = __esm({
|
|
8880
8843
|
"src/version.ts"() {
|
|
8881
8844
|
"use strict";
|
|
8882
|
-
CLI_VERSION = true ? "1.
|
|
8845
|
+
CLI_VERSION = true ? "1.37.0" : "dev";
|
|
8883
8846
|
}
|
|
8884
8847
|
});
|
|
8885
8848
|
|
|
@@ -9113,7 +9076,7 @@ var init_bridge = __esm({
|
|
|
9113
9076
|
});
|
|
9114
9077
|
|
|
9115
9078
|
// src/config/launch-config.ts
|
|
9116
|
-
import { promises as
|
|
9079
|
+
import { promises as fs16 } from "fs";
|
|
9117
9080
|
import path25 from "path";
|
|
9118
9081
|
function launchConfigPath(name, appRel, home) {
|
|
9119
9082
|
return path25.join(
|
|
@@ -9166,7 +9129,7 @@ async function readLaunchConfig(name, appRel, home) {
|
|
|
9166
9129
|
const file = launchConfigPath(name, appRel, home);
|
|
9167
9130
|
let content;
|
|
9168
9131
|
try {
|
|
9169
|
-
content = await
|
|
9132
|
+
content = await fs16.readFile(file, "utf8");
|
|
9170
9133
|
} catch {
|
|
9171
9134
|
return void 0;
|
|
9172
9135
|
}
|
|
@@ -9208,7 +9171,7 @@ async function listApps(name, home) {
|
|
|
9208
9171
|
async function walk(at, rel, depth) {
|
|
9209
9172
|
let entries;
|
|
9210
9173
|
try {
|
|
9211
|
-
entries = await
|
|
9174
|
+
entries = await fs16.readdir(at, { withFileTypes: true });
|
|
9212
9175
|
} catch {
|
|
9213
9176
|
return;
|
|
9214
9177
|
}
|
|
@@ -9217,7 +9180,7 @@ async function listApps(name, home) {
|
|
|
9217
9180
|
);
|
|
9218
9181
|
if (hasLaunch) {
|
|
9219
9182
|
try {
|
|
9220
|
-
await
|
|
9183
|
+
await fs16.access(path25.join(at, LAUNCH_DIRNAME, LAUNCH_FILENAME));
|
|
9221
9184
|
if (rel) out.push(rel);
|
|
9222
9185
|
} catch {
|
|
9223
9186
|
}
|
|
@@ -9248,7 +9211,7 @@ var init_launch_config = __esm({
|
|
|
9248
9211
|
});
|
|
9249
9212
|
|
|
9250
9213
|
// src/completion/resolve.ts
|
|
9251
|
-
import { existsSync as existsSync15, promises as
|
|
9214
|
+
import { existsSync as existsSync15, promises as fs17 } from "fs";
|
|
9252
9215
|
import path26 from "path";
|
|
9253
9216
|
async function resolveCompletions(line, point, opts = {}) {
|
|
9254
9217
|
const { prev, current } = parseCompletionLine(line, point);
|
|
@@ -9400,7 +9363,7 @@ async function listContainerNames(ctx) {
|
|
|
9400
9363
|
const home = ctx.opts.monocerosHome ?? monocerosHome();
|
|
9401
9364
|
const dir = path26.join(home, "container-configs");
|
|
9402
9365
|
if (!existsSync15(dir)) return [];
|
|
9403
|
-
const entries = await
|
|
9366
|
+
const entries = await fs17.readdir(dir);
|
|
9404
9367
|
return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length)).sort();
|
|
9405
9368
|
}
|
|
9406
9369
|
function containerNameFromCtx(ctx) {
|
|
@@ -9463,7 +9426,7 @@ async function collectDirs(dir, maxDepth) {
|
|
|
9463
9426
|
async function walk(at, rel, depth) {
|
|
9464
9427
|
let entries;
|
|
9465
9428
|
try {
|
|
9466
|
-
entries = await
|
|
9429
|
+
entries = await fs17.readdir(at, { withFileTypes: true });
|
|
9467
9430
|
} catch {
|
|
9468
9431
|
return;
|
|
9469
9432
|
}
|
|
@@ -9807,7 +9770,7 @@ var init_complete = __esm({
|
|
|
9807
9770
|
});
|
|
9808
9771
|
|
|
9809
9772
|
// src/update/notifier.ts
|
|
9810
|
-
import { spawn as
|
|
9773
|
+
import { spawn as spawn11 } from "child_process";
|
|
9811
9774
|
function isNewerVersion(latest, current) {
|
|
9812
9775
|
const parse = (v) => {
|
|
9813
9776
|
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
|
|
@@ -9866,7 +9829,7 @@ function spawnBackgroundCheck() {
|
|
|
9866
9829
|
const self = process.argv[1];
|
|
9867
9830
|
if (!self) return;
|
|
9868
9831
|
try {
|
|
9869
|
-
const child =
|
|
9832
|
+
const child = spawn11(process.execPath, [self, "__update-check"], {
|
|
9870
9833
|
detached: true,
|
|
9871
9834
|
stdio: "ignore"
|
|
9872
9835
|
});
|
|
@@ -10060,162 +10023,6 @@ function generateComposedYml(name, composed, lookupManifest, repoUrls = [], port
|
|
|
10060
10023
|
}
|
|
10061
10024
|
return ensureTrailingNewline(lines.join("\n"));
|
|
10062
10025
|
}
|
|
10063
|
-
function generateDocumentedYml(name, catalog, lookupManifest, repoUrls = [], ports = []) {
|
|
10064
|
-
const byCategory = groupByCategory(catalog);
|
|
10065
|
-
const lines = [];
|
|
10066
|
-
pushHeader(lines, SCHEMA_HEADER_DOCUMENTED, name);
|
|
10067
|
-
lines.push("");
|
|
10068
|
-
lines.push("schemaVersion: 1");
|
|
10069
|
-
lines.push(`name: ${name}`);
|
|
10070
|
-
lines.push(
|
|
10071
|
-
"# Pinned runtime base image, reused on every apply (never auto-bumped)."
|
|
10072
|
-
);
|
|
10073
|
-
lines.push(
|
|
10074
|
-
"# `monoceros upgrade <name>` refreshes the tooling and moves this to the"
|
|
10075
|
-
);
|
|
10076
|
-
lines.push("# latest runtime when a newer one exists.");
|
|
10077
|
-
lines.push(`runtimeVersion: ${DEFAULT_RUNTIME_VERSION}`);
|
|
10078
|
-
lines.push("");
|
|
10079
|
-
if (byCategory.language.length > 0) {
|
|
10080
|
-
pushSectionHeader(
|
|
10081
|
-
lines,
|
|
10082
|
-
LANGUAGES_HEADER,
|
|
10083
|
-
/* commented */
|
|
10084
|
-
true
|
|
10085
|
-
);
|
|
10086
|
-
lines.push("# languages:");
|
|
10087
|
-
for (const c of byCategory.language) {
|
|
10088
|
-
for (const lang of c.file.contributes.languages ?? []) {
|
|
10089
|
-
const ver = LANGUAGE_CATALOG[lang]?.defaultVersion;
|
|
10090
|
-
lines.push(`# - ${ver ? `${lang}:${ver}` : lang}`);
|
|
10091
|
-
}
|
|
10092
|
-
}
|
|
10093
|
-
lines.push("");
|
|
10094
|
-
}
|
|
10095
|
-
if (byCategory.service.length > 0) {
|
|
10096
|
-
pushSectionHeader(
|
|
10097
|
-
lines,
|
|
10098
|
-
SERVICES_HEADER,
|
|
10099
|
-
/* commented */
|
|
10100
|
-
true
|
|
10101
|
-
);
|
|
10102
|
-
lines.push("# services:");
|
|
10103
|
-
for (const c of byCategory.service) {
|
|
10104
|
-
for (const svc of c.file.contributes.services ?? []) {
|
|
10105
|
-
const body = renderServiceObjectBody(expandCuratedService(svc));
|
|
10106
|
-
lines.push(`# - ${body[0]}`);
|
|
10107
|
-
for (const line of body.slice(1)) lines.push(`# ${line}`);
|
|
10108
|
-
const exComment = exampleVolumesComment(
|
|
10109
|
-
curatedServiceExampleVolumes(svc)
|
|
10110
|
-
);
|
|
10111
|
-
if (exComment) {
|
|
10112
|
-
for (const cl of exComment.split("\n")) lines.push(`# ${cl}`);
|
|
10113
|
-
}
|
|
10114
|
-
}
|
|
10115
|
-
}
|
|
10116
|
-
lines.push("");
|
|
10117
|
-
}
|
|
10118
|
-
if (byCategory.feature.length > 0) {
|
|
10119
|
-
pushSectionHeader(
|
|
10120
|
-
lines,
|
|
10121
|
-
FEATURES_HEADER_DOCUMENTED,
|
|
10122
|
-
/* commented */
|
|
10123
|
-
true
|
|
10124
|
-
);
|
|
10125
|
-
lines.push("# features:");
|
|
10126
|
-
const renderedRefs = /* @__PURE__ */ new Set();
|
|
10127
|
-
const topLevel = byCategory.feature.filter((c) => !c.name.includes("/"));
|
|
10128
|
-
for (const c of topLevel) {
|
|
10129
|
-
for (const f of c.file.contributes.features ?? []) {
|
|
10130
|
-
if (renderedRefs.has(f.ref)) continue;
|
|
10131
|
-
renderedRefs.add(f.ref);
|
|
10132
|
-
lines.push("#");
|
|
10133
|
-
renderFeatureBlock(
|
|
10134
|
-
lines,
|
|
10135
|
-
f,
|
|
10136
|
-
lookupManifest(f.ref),
|
|
10137
|
-
/* commented */
|
|
10138
|
-
true
|
|
10139
|
-
);
|
|
10140
|
-
}
|
|
10141
|
-
}
|
|
10142
|
-
for (const c of byCategory.feature) {
|
|
10143
|
-
if (!c.name.includes("/")) continue;
|
|
10144
|
-
for (const f of c.file.contributes.features ?? []) {
|
|
10145
|
-
if (renderedRefs.has(f.ref)) continue;
|
|
10146
|
-
renderedRefs.add(f.ref);
|
|
10147
|
-
lines.push("#");
|
|
10148
|
-
renderFeatureBlock(
|
|
10149
|
-
lines,
|
|
10150
|
-
f,
|
|
10151
|
-
lookupManifest(f.ref),
|
|
10152
|
-
/* commented */
|
|
10153
|
-
true
|
|
10154
|
-
);
|
|
10155
|
-
}
|
|
10156
|
-
}
|
|
10157
|
-
lines.push("");
|
|
10158
|
-
}
|
|
10159
|
-
if (repoUrls.length > 0) {
|
|
10160
|
-
pushGitIdentityBlock(lines);
|
|
10161
|
-
pushSectionHeader(
|
|
10162
|
-
lines,
|
|
10163
|
-
REPOS_HEADER,
|
|
10164
|
-
/* commented */
|
|
10165
|
-
false
|
|
10166
|
-
);
|
|
10167
|
-
lines.push("repos:");
|
|
10168
|
-
for (const url of repoUrls) {
|
|
10169
|
-
lines.push(` - url: ${url}`);
|
|
10170
|
-
}
|
|
10171
|
-
lines.push("");
|
|
10172
|
-
} else {
|
|
10173
|
-
pushSectionHeader(
|
|
10174
|
-
lines,
|
|
10175
|
-
REPOS_HEADER,
|
|
10176
|
-
/* commented */
|
|
10177
|
-
true
|
|
10178
|
-
);
|
|
10179
|
-
lines.push("# repos:");
|
|
10180
|
-
lines.push("# - url: https://github.com/<org>/<repo>.git");
|
|
10181
|
-
lines.push("# path: <folder>");
|
|
10182
|
-
lines.push("# provider: github");
|
|
10183
|
-
lines.push("# git:");
|
|
10184
|
-
lines.push("# user:");
|
|
10185
|
-
lines.push("# name: Your Name");
|
|
10186
|
-
lines.push("# email: you@example.com");
|
|
10187
|
-
lines.push("");
|
|
10188
|
-
}
|
|
10189
|
-
if (ports.length > 0) {
|
|
10190
|
-
pushSectionHeader(
|
|
10191
|
-
lines,
|
|
10192
|
-
routingHeader(name),
|
|
10193
|
-
/* commented */
|
|
10194
|
-
false
|
|
10195
|
-
);
|
|
10196
|
-
lines.push("routing:");
|
|
10197
|
-
lines.push(" ports:");
|
|
10198
|
-
for (const port of ports) {
|
|
10199
|
-
lines.push(` - ${port}`);
|
|
10200
|
-
}
|
|
10201
|
-
lines.push(" # vscodeAutoForward: false");
|
|
10202
|
-
lines.push("");
|
|
10203
|
-
} else {
|
|
10204
|
-
pushSectionHeader(
|
|
10205
|
-
lines,
|
|
10206
|
-
routingHeader(name),
|
|
10207
|
-
/* commented */
|
|
10208
|
-
true
|
|
10209
|
-
);
|
|
10210
|
-
lines.push("# routing:");
|
|
10211
|
-
lines.push("# ports:");
|
|
10212
|
-
lines.push("# - 3000");
|
|
10213
|
-
lines.push("# - 5173");
|
|
10214
|
-
lines.push("# vscodeAutoForward: false");
|
|
10215
|
-
lines.push("");
|
|
10216
|
-
}
|
|
10217
|
-
return ensureTrailingNewline(lines.join("\n"));
|
|
10218
|
-
}
|
|
10219
10026
|
function pushServiceEntry(out, svc) {
|
|
10220
10027
|
if (svc.kind === "custom") {
|
|
10221
10028
|
const { bodyLines, comment } = renderCustomService(
|
|
@@ -10299,24 +10106,10 @@ function renderScalarValue(value) {
|
|
|
10299
10106
|
}
|
|
10300
10107
|
return String(value);
|
|
10301
10108
|
}
|
|
10302
|
-
function groupByCategory(catalog) {
|
|
10303
|
-
const out = {
|
|
10304
|
-
language: [],
|
|
10305
|
-
service: [],
|
|
10306
|
-
feature: []
|
|
10307
|
-
};
|
|
10308
|
-
const sorted = [...catalog.values()].sort(
|
|
10309
|
-
(a, b) => a.name.localeCompare(b.name)
|
|
10310
|
-
);
|
|
10311
|
-
for (const c of sorted) {
|
|
10312
|
-
out[c.file.category].push(c);
|
|
10313
|
-
}
|
|
10314
|
-
return out;
|
|
10315
|
-
}
|
|
10316
10109
|
function ensureTrailingNewline(s) {
|
|
10317
10110
|
return s.endsWith("\n") ? s : s + "\n";
|
|
10318
10111
|
}
|
|
10319
|
-
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;
|
|
10320
10113
|
var init_generator = __esm({
|
|
10321
10114
|
"src/init/generator.ts"() {
|
|
10322
10115
|
"use strict";
|
|
@@ -10324,21 +10117,19 @@ var init_generator = __esm({
|
|
|
10324
10117
|
init_catalog();
|
|
10325
10118
|
init_service_doc();
|
|
10326
10119
|
init_env_file();
|
|
10327
|
-
SCHEMA_HEADER_ACTIVE = "# Solution-config
|
|
10328
|
-
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>`.";
|
|
10329
10121
|
COMMENT_WIDTH = 76;
|
|
10330
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`.";
|
|
10331
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.";
|
|
10332
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.";
|
|
10333
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`.";
|
|
10334
|
-
|
|
10335
|
-
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`.";
|
|
10336
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.";
|
|
10337
10128
|
}
|
|
10338
10129
|
});
|
|
10339
10130
|
|
|
10340
10131
|
// src/init/index.ts
|
|
10341
|
-
import { existsSync as existsSync16, promises as
|
|
10132
|
+
import { existsSync as existsSync16, promises as fs18 } from "fs";
|
|
10342
10133
|
import path27 from "path";
|
|
10343
10134
|
import { consola as consola16 } from "consola";
|
|
10344
10135
|
async function runInit(opts) {
|
|
@@ -10411,22 +10202,24 @@ async function runInit(opts) {
|
|
|
10411
10202
|
seenPorts.add(raw);
|
|
10412
10203
|
ports.push(raw);
|
|
10413
10204
|
}
|
|
10414
|
-
|
|
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));
|
|
10415
10213
|
const composed = resolveComposedInit(catalog, {
|
|
10416
10214
|
languages: opts.languages ?? [],
|
|
10417
|
-
features:
|
|
10215
|
+
features: [...explicitFeatures, ...repoFeatures],
|
|
10418
10216
|
services: opts.services ?? [],
|
|
10419
10217
|
aptPackages: opts.aptPackages ?? []
|
|
10420
10218
|
});
|
|
10421
|
-
const
|
|
10422
|
-
|
|
10423
|
-
text = generateDocumentedYml(opts.name, catalog, lookup, repos, ports);
|
|
10424
|
-
} else {
|
|
10425
|
-
text = generateComposedYml(opts.name, composed, lookup, repos, ports);
|
|
10426
|
-
}
|
|
10427
|
-
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 });
|
|
10428
10221
|
await ensureEnvGitignored(containerConfigsDir(home));
|
|
10429
|
-
await
|
|
10222
|
+
await fs18.writeFile(dest, text, "utf8");
|
|
10430
10223
|
const envPath = containerEnvPath(opts.name, home);
|
|
10431
10224
|
const seedVars = {};
|
|
10432
10225
|
for (const f of composed.features) {
|
|
@@ -10448,21 +10241,13 @@ async function runInit(opts) {
|
|
|
10448
10241
|
seedVars[GIT_IDENTITY_VAR.email] = "";
|
|
10449
10242
|
}
|
|
10450
10243
|
await ensureEnvVars(envPath, opts.name, seedVars);
|
|
10451
|
-
const documented = !anyComposed;
|
|
10452
10244
|
const ymlRel = path27.relative(home, dest);
|
|
10453
10245
|
const envRel = path27.relative(home, envPath);
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
} else {
|
|
10460
|
-
logger.success(`Composed into ${ymlRel} and ${envRel}.`);
|
|
10461
|
-
logger.info(
|
|
10462
|
-
`Edit the files if you need to tweak, then \`monoceros apply ${opts.name}\`.`
|
|
10463
|
-
);
|
|
10464
|
-
}
|
|
10465
|
-
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 };
|
|
10466
10251
|
}
|
|
10467
10252
|
function resolveComposedInit(catalog, raw) {
|
|
10468
10253
|
return {
|
|
@@ -11009,14 +10794,14 @@ var init_list_components = __esm({
|
|
|
11009
10794
|
});
|
|
11010
10795
|
|
|
11011
10796
|
// src/commands/logs.ts
|
|
11012
|
-
import { spawn as
|
|
10797
|
+
import { spawn as spawn12 } from "child_process";
|
|
11013
10798
|
import path28 from "path";
|
|
11014
10799
|
import { defineCommand as defineCommand16 } from "citty";
|
|
11015
10800
|
import { consola as consola20 } from "consola";
|
|
11016
10801
|
function tailLogFile(file, follow) {
|
|
11017
10802
|
const [cmd, args] = follow ? ["tail", ["-F", file]] : ["cat", [file]];
|
|
11018
10803
|
return new Promise((resolve, reject) => {
|
|
11019
|
-
const child =
|
|
10804
|
+
const child = spawn12(cmd, args, { stdio: "inherit" });
|
|
11020
10805
|
child.on("error", reject);
|
|
11021
10806
|
child.on("exit", (code) => resolve(code ?? 0));
|
|
11022
10807
|
});
|
|
@@ -11314,7 +11099,7 @@ var init_remove_feature = __esm({
|
|
|
11314
11099
|
});
|
|
11315
11100
|
|
|
11316
11101
|
// src/remove/index.ts
|
|
11317
|
-
import { existsSync as existsSync17, promises as
|
|
11102
|
+
import { existsSync as existsSync17, promises as fs19 } from "fs";
|
|
11318
11103
|
import path29 from "path";
|
|
11319
11104
|
import { consola as consola24 } from "consola";
|
|
11320
11105
|
async function runRemove(opts) {
|
|
@@ -11361,17 +11146,17 @@ async function runRemove(opts) {
|
|
|
11361
11146
|
if (!opts.noBackup && (hasYml || hasContainer)) {
|
|
11362
11147
|
const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
11363
11148
|
backupPath = path29.join(home, "container-backups", `${opts.name}-${ts}`);
|
|
11364
|
-
await
|
|
11149
|
+
await fs19.mkdir(backupPath, { recursive: true });
|
|
11365
11150
|
if (hasYml) {
|
|
11366
|
-
await
|
|
11151
|
+
await fs19.copyFile(ymlPath, path29.join(backupPath, `${opts.name}.yml`));
|
|
11367
11152
|
}
|
|
11368
11153
|
if (hasEnv) {
|
|
11369
|
-
await
|
|
11154
|
+
await fs19.copyFile(envPath, path29.join(backupPath, `${opts.name}.env`));
|
|
11370
11155
|
}
|
|
11371
11156
|
if (hasContainer) {
|
|
11372
11157
|
const dst = path29.join(backupPath, "container");
|
|
11373
11158
|
try {
|
|
11374
|
-
await
|
|
11159
|
+
await fs19.cp(containerPath, dst, { recursive: true });
|
|
11375
11160
|
} catch (err) {
|
|
11376
11161
|
const code = err.code;
|
|
11377
11162
|
if (code !== "EACCES" && code !== "EPERM") {
|
|
@@ -11380,7 +11165,7 @@ async function runRemove(opts) {
|
|
|
11380
11165
|
logger.info(
|
|
11381
11166
|
`[remove] backup hit ${code} on root-owned files; copying via a throw-away alpine container\u2026`
|
|
11382
11167
|
);
|
|
11383
|
-
await
|
|
11168
|
+
await fs19.mkdir(dst, { recursive: true });
|
|
11384
11169
|
const { exitCode, stderr } = await dockerExec([
|
|
11385
11170
|
"run",
|
|
11386
11171
|
"--rm",
|
|
@@ -11406,14 +11191,14 @@ async function runRemove(opts) {
|
|
|
11406
11191
|
await stopBridgeDaemon(containerPath);
|
|
11407
11192
|
}
|
|
11408
11193
|
if (hasYml) {
|
|
11409
|
-
await
|
|
11194
|
+
await fs19.rm(ymlPath, { force: true });
|
|
11410
11195
|
}
|
|
11411
11196
|
if (hasEnv) {
|
|
11412
|
-
await
|
|
11197
|
+
await fs19.rm(envPath, { force: true });
|
|
11413
11198
|
}
|
|
11414
11199
|
if (hasContainer) {
|
|
11415
11200
|
try {
|
|
11416
|
-
await
|
|
11201
|
+
await fs19.rm(containerPath, { recursive: true, force: true });
|
|
11417
11202
|
} catch (err) {
|
|
11418
11203
|
const code = err.code;
|
|
11419
11204
|
if (code !== "EACCES" && code !== "EPERM") {
|
|
@@ -11435,7 +11220,7 @@ async function runRemove(opts) {
|
|
|
11435
11220
|
"-delete"
|
|
11436
11221
|
]);
|
|
11437
11222
|
try {
|
|
11438
|
-
await
|
|
11223
|
+
await fs19.rm(containerPath, { recursive: true, force: true });
|
|
11439
11224
|
} catch (err2) {
|
|
11440
11225
|
const code2 = err2.code;
|
|
11441
11226
|
throw new Error(
|
|
@@ -11570,7 +11355,7 @@ var init_remove2 = __esm({
|
|
|
11570
11355
|
});
|
|
11571
11356
|
|
|
11572
11357
|
// src/restore/index.ts
|
|
11573
|
-
import { existsSync as existsSync18, promises as
|
|
11358
|
+
import { existsSync as existsSync18, promises as fs20 } from "fs";
|
|
11574
11359
|
import path30 from "path";
|
|
11575
11360
|
import { consola as consola26 } from "consola";
|
|
11576
11361
|
async function runRestore(opts) {
|
|
@@ -11583,11 +11368,11 @@ async function runRestore(opts) {
|
|
|
11583
11368
|
if (!existsSync18(backup)) {
|
|
11584
11369
|
throw new Error(`Backup not found: ${backup}.`);
|
|
11585
11370
|
}
|
|
11586
|
-
const stat = await
|
|
11371
|
+
const stat = await fs20.stat(backup);
|
|
11587
11372
|
if (!stat.isDirectory()) {
|
|
11588
11373
|
throw new Error(`Backup path is not a directory: ${backup}.`);
|
|
11589
11374
|
}
|
|
11590
|
-
const entries = await
|
|
11375
|
+
const entries = await fs20.readdir(backup);
|
|
11591
11376
|
const ymlFiles = entries.filter((f) => f.endsWith(".yml"));
|
|
11592
11377
|
if (ymlFiles.length === 0) {
|
|
11593
11378
|
throw new Error(
|
|
@@ -11617,13 +11402,13 @@ async function runRestore(opts) {
|
|
|
11617
11402
|
`Refusing to restore: ${destContainer} already exists. Remove the current container first (\`monoceros remove ${name}\`).`
|
|
11618
11403
|
);
|
|
11619
11404
|
}
|
|
11620
|
-
await
|
|
11621
|
-
await
|
|
11405
|
+
await fs20.mkdir(containerConfigsDir(home), { recursive: true });
|
|
11406
|
+
await fs20.copyFile(path30.join(backup, ymlFile), destYml);
|
|
11622
11407
|
if (hasEnv) {
|
|
11623
|
-
await
|
|
11408
|
+
await fs20.copyFile(envInBackup, containerEnvPath(name, home));
|
|
11624
11409
|
}
|
|
11625
11410
|
if (hasContainer) {
|
|
11626
|
-
await
|
|
11411
|
+
await fs20.cp(containerInBackup, destContainer, { recursive: true });
|
|
11627
11412
|
}
|
|
11628
11413
|
logger.success(`Restored '${name}' from ${prettyPath(backup)}.`);
|
|
11629
11414
|
logger.info(
|
|
@@ -12333,7 +12118,7 @@ var init_port_check2 = __esm({
|
|
|
12333
12118
|
});
|
|
12334
12119
|
|
|
12335
12120
|
// src/tunnel/run.ts
|
|
12336
|
-
import { spawn as
|
|
12121
|
+
import { spawn as spawn13 } from "child_process";
|
|
12337
12122
|
import { consola as consola34 } from "consola";
|
|
12338
12123
|
function signalNumber(signal) {
|
|
12339
12124
|
switch (signal) {
|
|
@@ -12423,7 +12208,7 @@ var init_run3 = __esm({
|
|
|
12423
12208
|
init_port_check2();
|
|
12424
12209
|
SOCAT_IMAGE = "alpine/socat:1.8.0.3";
|
|
12425
12210
|
defaultDockerSpawn = (args) => {
|
|
12426
|
-
const child =
|
|
12211
|
+
const child = spawn13("docker", args, {
|
|
12427
12212
|
stdio: "inherit"
|
|
12428
12213
|
});
|
|
12429
12214
|
const exited = new Promise((resolve, reject) => {
|
|
@@ -13350,7 +13135,7 @@ var init_prune = __esm({
|
|
|
13350
13135
|
});
|
|
13351
13136
|
|
|
13352
13137
|
// src/upgrade/index.ts
|
|
13353
|
-
import { existsSync as existsSync21, promises as
|
|
13138
|
+
import { existsSync as existsSync21, promises as fs21 } from "fs";
|
|
13354
13139
|
import { consola as consola41 } from "consola";
|
|
13355
13140
|
async function fetchRuntimeVersions() {
|
|
13356
13141
|
const tokenUrl = `https://ghcr.io/token?service=ghcr.io&scope=repository:${RUNTIME_REPO}:pull`;
|
|
@@ -13467,10 +13252,10 @@ async function runUpgrade(opts) {
|
|
|
13467
13252
|
for (const name of targets) {
|
|
13468
13253
|
const ymlPath = containerConfigPath(name, home);
|
|
13469
13254
|
if (!existsSync21(ymlPath)) continue;
|
|
13470
|
-
const raw = await
|
|
13255
|
+
const raw = await fs21.readFile(ymlPath, "utf8");
|
|
13471
13256
|
const updated = setRuntimeVersion(raw, pinVersion);
|
|
13472
13257
|
if (updated !== raw) {
|
|
13473
|
-
await
|
|
13258
|
+
await fs21.writeFile(ymlPath, updated);
|
|
13474
13259
|
bumped += 1;
|
|
13475
13260
|
logger.info(`Pinned '${name}' to runtime ${pinVersion}.`);
|
|
13476
13261
|
}
|
|
@@ -13512,7 +13297,7 @@ function formatPruneLine(prune) {
|
|
|
13512
13297
|
}
|
|
13513
13298
|
async function listContainerNames2(home) {
|
|
13514
13299
|
try {
|
|
13515
|
-
const entries = await
|
|
13300
|
+
const entries = await fs21.readdir(containerConfigsDir(home));
|
|
13516
13301
|
return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length));
|
|
13517
13302
|
} catch {
|
|
13518
13303
|
return [];
|