@getmonoceros/workbench 1.37.3 → 1.37.9
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 +137 -11
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
- package/templates/monoceros-config.sample.env +8 -23
package/dist/bin.js
CHANGED
|
@@ -472,6 +472,13 @@ function interpolate(value, vars) {
|
|
|
472
472
|
});
|
|
473
473
|
return { value: out, missing };
|
|
474
474
|
}
|
|
475
|
+
function expandEnvRefs(env) {
|
|
476
|
+
const out = {};
|
|
477
|
+
for (const [key, value] of Object.entries(env)) {
|
|
478
|
+
out[key] = interpolate(value, env).value;
|
|
479
|
+
}
|
|
480
|
+
return out;
|
|
481
|
+
}
|
|
475
482
|
function interpolateServices(services, vars) {
|
|
476
483
|
const missing = [];
|
|
477
484
|
const resolved = services.map((svc) => {
|
|
@@ -560,6 +567,31 @@ async function ensureEnvVars(envPath, name, vars) {
|
|
|
560
567
|
}
|
|
561
568
|
return { created: !exists, added };
|
|
562
569
|
}
|
|
570
|
+
async function setEnvVarRef(envPath, name, key, value) {
|
|
571
|
+
const exists = existsSync2(envPath);
|
|
572
|
+
const content = exists ? readFileSync(envPath, "utf8") : buildEnvStub(name);
|
|
573
|
+
const lines = content.split(/\r?\n/);
|
|
574
|
+
let replaced = false;
|
|
575
|
+
for (let i = 0; i < lines.length; i++) {
|
|
576
|
+
const m = ENV_LINE_RE.exec(lines[i]);
|
|
577
|
+
if (m && m[1] === key) {
|
|
578
|
+
if (m[2].trim().length > 0) return false;
|
|
579
|
+
lines[i] = `${key}=${value}`;
|
|
580
|
+
replaced = true;
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
let next = replaced ? lines.join("\n") : content;
|
|
585
|
+
if (!replaced) {
|
|
586
|
+
if (next.length > 0 && !next.endsWith("\n")) next += "\n";
|
|
587
|
+
next += `${key}=${value}
|
|
588
|
+
`;
|
|
589
|
+
}
|
|
590
|
+
if (next === content) return false;
|
|
591
|
+
await fsp.mkdir(path2.dirname(envPath), { recursive: true });
|
|
592
|
+
await fsp.writeFile(envPath, next);
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
563
595
|
function formatMissingVarsError(missing, envPathPretty) {
|
|
564
596
|
const lines = missing.map((m) => ` - \${${m.name}} (${m.location})`);
|
|
565
597
|
const uniqueNames = [...new Set(missing.map((m) => m.name))];
|
|
@@ -1334,6 +1366,13 @@ function resolveRepoTokens(config, catalog, envVars) {
|
|
|
1334
1366
|
const hostTokens = /* @__PURE__ */ new Map();
|
|
1335
1367
|
const used = [];
|
|
1336
1368
|
const missing = [];
|
|
1369
|
+
const ambiguous = [];
|
|
1370
|
+
const providersWithRepo = /* @__PURE__ */ new Set();
|
|
1371
|
+
const injectFeatureToken = (ref, token) => {
|
|
1372
|
+
if (!ref) return;
|
|
1373
|
+
const feature = features.find((f) => f.ref === ref);
|
|
1374
|
+
if (feature) feature.options = { ...feature.options, apiToken: token };
|
|
1375
|
+
};
|
|
1337
1376
|
for (const repo of config.repos ?? []) {
|
|
1338
1377
|
if (!repo.url.startsWith("https://")) continue;
|
|
1339
1378
|
let host;
|
|
@@ -1344,6 +1383,7 @@ function resolveRepoTokens(config, catalog, envVars) {
|
|
|
1344
1383
|
}
|
|
1345
1384
|
const provider = resolveProvider(host, repo.provider);
|
|
1346
1385
|
if (provider === "unknown") continue;
|
|
1386
|
+
providersWithRepo.add(provider);
|
|
1347
1387
|
const ref = catalog.get(provider)?.file.contributes.features?.[0]?.ref;
|
|
1348
1388
|
const tried = tokenVarCandidates(provider, repo.url);
|
|
1349
1389
|
const hit = tried.find((v) => (envVars[v] ?? "").trim().length > 0);
|
|
@@ -1353,17 +1393,47 @@ function resolveRepoTokens(config, catalog, envVars) {
|
|
|
1353
1393
|
}
|
|
1354
1394
|
const token = envVars[hit].trim();
|
|
1355
1395
|
hostTokens.set(host, token);
|
|
1356
|
-
used.push({ host, provider, varName: hit });
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1396
|
+
used.push({ host, provider, varName: hit, source: "repo" });
|
|
1397
|
+
injectFeatureToken(ref, token);
|
|
1398
|
+
}
|
|
1399
|
+
for (const provider of PROVIDER_VALUES) {
|
|
1400
|
+
if (providersWithRepo.has(provider)) continue;
|
|
1401
|
+
const ref = catalog.get(provider)?.file.contributes.features?.[0]?.ref;
|
|
1402
|
+
if (!ref || !config.features.some((f) => f.ref === ref)) continue;
|
|
1403
|
+
const p = provider.toUpperCase();
|
|
1404
|
+
const host = CANONICAL_HOST[provider];
|
|
1405
|
+
const direct = [`${p}_API_TOKEN`, `GIT_TOKEN__${p}`].find(
|
|
1406
|
+
(v) => (envVars[v] ?? "").trim().length > 0
|
|
1407
|
+
);
|
|
1408
|
+
if (direct) {
|
|
1409
|
+
const token = envVars[direct].trim();
|
|
1410
|
+
hostTokens.set(host, token);
|
|
1411
|
+
used.push({ host, provider, varName: direct, source: "feature" });
|
|
1412
|
+
injectFeatureToken(ref, token);
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
const orgVars = Object.keys(envVars).filter((k) => k.startsWith(`GIT_TOKEN__${p}_`) && envVars[k].trim()).sort();
|
|
1416
|
+
if (orgVars.length === 1) {
|
|
1417
|
+
const token = envVars[orgVars[0]].trim();
|
|
1418
|
+
hostTokens.set(host, token);
|
|
1419
|
+
used.push({ host, provider, varName: orgVars[0], source: "feature" });
|
|
1420
|
+
injectFeatureToken(ref, token);
|
|
1421
|
+
} else if (orgVars.length > 1) {
|
|
1422
|
+
ambiguous.push({ provider, featureRef: ref, host, candidates: orgVars });
|
|
1423
|
+
} else {
|
|
1424
|
+
missing.push({
|
|
1425
|
+
host,
|
|
1426
|
+
provider,
|
|
1427
|
+
tried: [`${p}_API_TOKEN`, `GIT_TOKEN__${p}`]
|
|
1428
|
+
});
|
|
1360
1429
|
}
|
|
1361
1430
|
}
|
|
1362
1431
|
return {
|
|
1363
1432
|
features,
|
|
1364
1433
|
hostTokens,
|
|
1365
1434
|
used,
|
|
1366
|
-
missing
|
|
1435
|
+
missing,
|
|
1436
|
+
ambiguous
|
|
1367
1437
|
};
|
|
1368
1438
|
}
|
|
1369
1439
|
async function resolveContainerRepoTokens(name, home, catalog) {
|
|
@@ -1376,7 +1446,7 @@ async function resolveContainerRepoTokens(name, home, catalog) {
|
|
|
1376
1446
|
return resolveRepoTokens(config, catalog, envVars);
|
|
1377
1447
|
}
|
|
1378
1448
|
function formatTokenUse(use) {
|
|
1379
|
-
return
|
|
1449
|
+
return use.source === "feature" ? `Using ${use.varName} for the ${PROVIDER_LABEL[use.provider]} CLI` : `Using ${use.varName} for ${use.host}`;
|
|
1380
1450
|
}
|
|
1381
1451
|
function formatUnauthenticatedRepos(missing, containerName2) {
|
|
1382
1452
|
const lines = [
|
|
@@ -1407,6 +1477,7 @@ function formatUnauthenticatedRepos(missing, containerName2) {
|
|
|
1407
1477
|
lines.push("", ` Details: ${cyan2(REPO_DOCS_URL)}`);
|
|
1408
1478
|
return lines.join("\n");
|
|
1409
1479
|
}
|
|
1480
|
+
var CANONICAL_HOST;
|
|
1410
1481
|
var init_repo_token = __esm({
|
|
1411
1482
|
"src/apply/repo-token.ts"() {
|
|
1412
1483
|
"use strict";
|
|
@@ -1416,6 +1487,11 @@ var init_repo_token = __esm({
|
|
|
1416
1487
|
init_paths();
|
|
1417
1488
|
init_credentials();
|
|
1418
1489
|
init_format();
|
|
1490
|
+
CANONICAL_HOST = {
|
|
1491
|
+
github: "github.com",
|
|
1492
|
+
gitlab: "gitlab.com",
|
|
1493
|
+
bitbucket: "bitbucket.org"
|
|
1494
|
+
};
|
|
1419
1495
|
}
|
|
1420
1496
|
});
|
|
1421
1497
|
|
|
@@ -4691,7 +4767,11 @@ var init_agents_md = __esm({
|
|
|
4691
4767
|
|
|
4692
4768
|
// src/briefing/claude-md.ts
|
|
4693
4769
|
function generateClaudeMd() {
|
|
4694
|
-
return
|
|
4770
|
+
return [
|
|
4771
|
+
"Read @AGENTS.md before you do anything else. It is this container's",
|
|
4772
|
+
"briefing - the stack, services, tools, exposed ports, and how to work in",
|
|
4773
|
+
"here - and Monoceros regenerates it on every `apply`."
|
|
4774
|
+
].join("\n");
|
|
4695
4775
|
}
|
|
4696
4776
|
var init_claude_md = __esm({
|
|
4697
4777
|
"src/briefing/claude-md.ts"() {
|
|
@@ -8191,15 +8271,42 @@ ${sectionLine(label)}
|
|
|
8191
8271
|
warnOnDeprecatedFeatureRefs(parsed.config.features, globalConfig, logger);
|
|
8192
8272
|
const envPath = containerEnvPath(opts.name, home);
|
|
8193
8273
|
await ensureEnvGitignored(containerConfigsDir(home));
|
|
8194
|
-
const envVars = {
|
|
8274
|
+
const envVars = expandEnvRefs({
|
|
8195
8275
|
...readEnvFile(globalEnvPath(home)),
|
|
8196
8276
|
...readEnvFile(envPath),
|
|
8197
8277
|
...opts.env ?? {}
|
|
8198
|
-
};
|
|
8278
|
+
});
|
|
8199
8279
|
const catalog = await loadComponentCatalog();
|
|
8200
8280
|
const repoTokens = resolveRepoTokens(parsed.config, catalog, envVars);
|
|
8281
|
+
const featureTokenPrompt = opts.featureTokenPrompt ?? defaultFeatureTokenPrompt;
|
|
8282
|
+
for (const amb of repoTokens.ambiguous) {
|
|
8283
|
+
const chosen = await featureTokenPrompt(amb);
|
|
8284
|
+
if (!chosen) {
|
|
8285
|
+
const p = amb.provider.toUpperCase();
|
|
8286
|
+
repoTokens.missing.push({
|
|
8287
|
+
host: amb.host,
|
|
8288
|
+
provider: amb.provider,
|
|
8289
|
+
tried: [`${p}_API_TOKEN`, `GIT_TOKEN__${p}`]
|
|
8290
|
+
});
|
|
8291
|
+
continue;
|
|
8292
|
+
}
|
|
8293
|
+
const token = (envVars[chosen] ?? "").trim();
|
|
8294
|
+
repoTokens.hostTokens.set(amb.host, token);
|
|
8295
|
+
const feature = repoTokens.features.find((f) => f.ref === amb.featureRef);
|
|
8296
|
+
if (feature) feature.options = { ...feature.options, apiToken: token };
|
|
8297
|
+
const featureVar = `${amb.provider.toUpperCase()}_API_TOKEN`;
|
|
8298
|
+
const saved = await setEnvVarRef(
|
|
8299
|
+
envPath,
|
|
8300
|
+
opts.name,
|
|
8301
|
+
featureVar,
|
|
8302
|
+
`\${${chosen}}`
|
|
8303
|
+
);
|
|
8304
|
+
logger.info(
|
|
8305
|
+
`Using ${chosen} for the ${PROVIDER_LABEL[amb.provider]} CLI` + (saved ? ` (saved as ${featureVar} in ${prettyPath(envPath)})` : ` (${featureVar} already set in ${prettyPath(envPath)})`)
|
|
8306
|
+
);
|
|
8307
|
+
}
|
|
8201
8308
|
for (const use of repoTokens.used) {
|
|
8202
|
-
logger.info(
|
|
8309
|
+
logger.info(formatTokenUse(use));
|
|
8203
8310
|
}
|
|
8204
8311
|
const resolvedFeatures = interpolateFeatureOptions(
|
|
8205
8312
|
repoTokens.features,
|
|
@@ -8636,6 +8743,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
8636
8743
|
}
|
|
8637
8744
|
}
|
|
8638
8745
|
}
|
|
8746
|
+
var SKIP_FEATURE_TOKEN, defaultFeatureTokenPrompt;
|
|
8639
8747
|
var init_apply = __esm({
|
|
8640
8748
|
"src/apply/index.ts"() {
|
|
8641
8749
|
"use strict";
|
|
@@ -8671,6 +8779,24 @@ var init_apply = __esm({
|
|
|
8671
8779
|
init_global();
|
|
8672
8780
|
init_yml();
|
|
8673
8781
|
init_repo_token();
|
|
8782
|
+
SKIP_FEATURE_TOKEN = "__skip__";
|
|
8783
|
+
defaultFeatureTokenPrompt = async ({
|
|
8784
|
+
provider,
|
|
8785
|
+
host,
|
|
8786
|
+
candidates
|
|
8787
|
+
}) => {
|
|
8788
|
+
const choice = await consola12.prompt(
|
|
8789
|
+
`The ${PROVIDER_LABEL[provider]} CLI feature has no repo to pick a token from. Which token should authenticate ${host}?`,
|
|
8790
|
+
{
|
|
8791
|
+
type: "select",
|
|
8792
|
+
options: [
|
|
8793
|
+
...candidates.map((v) => ({ label: v, value: v })),
|
|
8794
|
+
{ label: "Skip \u2014 leave unauthenticated", value: SKIP_FEATURE_TOKEN }
|
|
8795
|
+
]
|
|
8796
|
+
}
|
|
8797
|
+
);
|
|
8798
|
+
return typeof choice === "string" && choice !== SKIP_FEATURE_TOKEN ? choice : null;
|
|
8799
|
+
};
|
|
8674
8800
|
}
|
|
8675
8801
|
});
|
|
8676
8802
|
|
|
@@ -8845,7 +8971,7 @@ var CLI_VERSION;
|
|
|
8845
8971
|
var init_version = __esm({
|
|
8846
8972
|
"src/version.ts"() {
|
|
8847
8973
|
"use strict";
|
|
8848
|
-
CLI_VERSION = true ? "1.37.
|
|
8974
|
+
CLI_VERSION = true ? "1.37.9" : "dev";
|
|
8849
8975
|
}
|
|
8850
8976
|
});
|
|
8851
8977
|
|