@getmonoceros/workbench 1.37.3 → 1.37.7

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 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
- if (ref) {
1358
- const feature = features.find((f) => f.ref === ref);
1359
- if (feature) feature.options = { ...feature.options, apiToken: token };
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 `${PROVIDER_LABEL[use.provider]} (${use.host}) \u2192 ${use.varName}`;
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
 
@@ -8191,15 +8267,42 @@ ${sectionLine(label)}
8191
8267
  warnOnDeprecatedFeatureRefs(parsed.config.features, globalConfig, logger);
8192
8268
  const envPath = containerEnvPath(opts.name, home);
8193
8269
  await ensureEnvGitignored(containerConfigsDir(home));
8194
- const envVars = {
8270
+ const envVars = expandEnvRefs({
8195
8271
  ...readEnvFile(globalEnvPath(home)),
8196
8272
  ...readEnvFile(envPath),
8197
8273
  ...opts.env ?? {}
8198
- };
8274
+ });
8199
8275
  const catalog = await loadComponentCatalog();
8200
8276
  const repoTokens = resolveRepoTokens(parsed.config, catalog, envVars);
8277
+ const featureTokenPrompt = opts.featureTokenPrompt ?? defaultFeatureTokenPrompt;
8278
+ for (const amb of repoTokens.ambiguous) {
8279
+ const chosen = await featureTokenPrompt(amb);
8280
+ if (!chosen) {
8281
+ const p = amb.provider.toUpperCase();
8282
+ repoTokens.missing.push({
8283
+ host: amb.host,
8284
+ provider: amb.provider,
8285
+ tried: [`${p}_API_TOKEN`, `GIT_TOKEN__${p}`]
8286
+ });
8287
+ continue;
8288
+ }
8289
+ const token = (envVars[chosen] ?? "").trim();
8290
+ repoTokens.hostTokens.set(amb.host, token);
8291
+ const feature = repoTokens.features.find((f) => f.ref === amb.featureRef);
8292
+ if (feature) feature.options = { ...feature.options, apiToken: token };
8293
+ const featureVar = `${amb.provider.toUpperCase()}_API_TOKEN`;
8294
+ const saved = await setEnvVarRef(
8295
+ envPath,
8296
+ opts.name,
8297
+ featureVar,
8298
+ `\${${chosen}}`
8299
+ );
8300
+ logger.info(
8301
+ `Using ${chosen} for the ${PROVIDER_LABEL[amb.provider]} CLI` + (saved ? ` (saved as ${featureVar} in ${prettyPath(envPath)})` : ` (${featureVar} already set in ${prettyPath(envPath)})`)
8302
+ );
8303
+ }
8201
8304
  for (const use of repoTokens.used) {
8202
- logger.info(`Repo token: ${formatTokenUse(use)}`);
8305
+ logger.info(formatTokenUse(use));
8203
8306
  }
8204
8307
  const resolvedFeatures = interpolateFeatureOptions(
8205
8308
  repoTokens.features,
@@ -8636,6 +8739,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
8636
8739
  }
8637
8740
  }
8638
8741
  }
8742
+ var SKIP_FEATURE_TOKEN, defaultFeatureTokenPrompt;
8639
8743
  var init_apply = __esm({
8640
8744
  "src/apply/index.ts"() {
8641
8745
  "use strict";
@@ -8671,6 +8775,24 @@ var init_apply = __esm({
8671
8775
  init_global();
8672
8776
  init_yml();
8673
8777
  init_repo_token();
8778
+ SKIP_FEATURE_TOKEN = "__skip__";
8779
+ defaultFeatureTokenPrompt = async ({
8780
+ provider,
8781
+ host,
8782
+ candidates
8783
+ }) => {
8784
+ const choice = await consola12.prompt(
8785
+ `The ${PROVIDER_LABEL[provider]} CLI feature has no repo to pick a token from. Which token should authenticate ${host}?`,
8786
+ {
8787
+ type: "select",
8788
+ options: [
8789
+ ...candidates.map((v) => ({ label: v, value: v })),
8790
+ { label: "Skip \u2014 leave unauthenticated", value: SKIP_FEATURE_TOKEN }
8791
+ ]
8792
+ }
8793
+ );
8794
+ return typeof choice === "string" && choice !== SKIP_FEATURE_TOKEN ? choice : null;
8795
+ };
8674
8796
  }
8675
8797
  });
8676
8798
 
@@ -8845,7 +8967,7 @@ var CLI_VERSION;
8845
8967
  var init_version = __esm({
8846
8968
  "src/version.ts"() {
8847
8969
  "use strict";
8848
- CLI_VERSION = true ? "1.37.3" : "dev";
8970
+ CLI_VERSION = true ? "1.37.7" : "dev";
8849
8971
  }
8850
8972
  });
8851
8973