@rtrentjones/greenlight 0.2.19 → 0.2.20

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
@@ -5,7 +5,7 @@ import {
5
5
  loadConfig,
6
6
  resolveUrl,
7
7
  verifyAll
8
- } from "./chunk-6USV5AQV.js";
8
+ } from "./chunk-PSNO7F4Q.js";
9
9
  import "./chunk-HX7VA25D.js";
10
10
  import "./chunk-N3IKUCSF.js";
11
11
  import "./chunk-KP3Y6WRU.js";
@@ -58,6 +58,11 @@ function serializeTool(t) {
58
58
  if (pv.path !== void 0) pvParts.push(`path: ${q(pv.path)}`);
59
59
  parts.push(`preview: { ${pvParts.join(", ")} }`);
60
60
  }
61
+ if (t.tokens?.length) parts.push(`tokens: [${t.tokens.map(q).join(", ")}]`);
62
+ if (t.tokenOverrides && Object.keys(t.tokenOverrides).length) {
63
+ const ov = Object.entries(t.tokenOverrides).map(([k, v]) => `${k}: ${q(v)}`).join(", ");
64
+ parts.push(`tokenOverrides: { ${ov} }`);
65
+ }
61
66
  return ` { ${parts.join(", ")} },`;
62
67
  }
63
68
  function serializeConfig(c) {
@@ -103,7 +108,9 @@ function addTool(config, t) {
103
108
  ...t.adopted ? { adopted: true } : {},
104
109
  ...t.external ? { external: true } : {},
105
110
  ...t.port !== void 0 ? { port: t.port } : {},
106
- ...t.preview ? { preview: t.preview } : {}
111
+ ...t.preview ? { preview: t.preview } : {},
112
+ ...t.tokens?.length ? { tokens: t.tokens } : {},
113
+ ...t.tokenOverrides && Object.keys(t.tokenOverrides).length ? { tokenOverrides: t.tokenOverrides } : {}
107
114
  }
108
115
  ]
109
116
  };
@@ -129,7 +136,9 @@ function upsertTool(config, t) {
129
136
  ...t.adopted ? { adopted: true } : {},
130
137
  ...t.external ? { external: true } : {},
131
138
  ...t.port !== void 0 ? { port: t.port } : {},
132
- ...t.preview ? { preview: t.preview } : {}
139
+ ...t.preview ? { preview: t.preview } : {},
140
+ ...t.tokens?.length ? { tokens: t.tokens } : {},
141
+ ...t.tokenOverrides && Object.keys(t.tokenOverrides).length ? { tokenOverrides: t.tokenOverrides } : {}
133
142
  };
134
143
  const tools = config.tools.some((x) => x.name === t.name) ? config.tools.map((x) => x.name === t.name ? entry : x) : [...config.tools, entry];
135
144
  const result = ConfigSchema.safeParse({ ...config, tools });
@@ -186,7 +195,9 @@ function resolveEntry(config, name) {
186
195
  dir: tool.dir ?? `tools/${tool.name}`,
187
196
  external: tool.external,
188
197
  port: tool.port,
189
- preview: tool.preview
198
+ preview: tool.preview,
199
+ tokens: tool.tokens,
200
+ tokenOverrides: tool.tokenOverrides
190
201
  };
191
202
  }
192
203
  var VERIFY_MODES = /* @__PURE__ */ new Set(["api", "mcp", "playwright", "test", "agent-web", "eval"]);
@@ -401,6 +412,12 @@ var PACKS = [
401
412
  tfModules: ["tool", "tunnel", "oci-network", "oci-container-instance"]
402
413
  }
403
414
  ];
415
+ function secretKeyFor(tok, toolName, overrides) {
416
+ const override = overrides?.[tok.envVar];
417
+ if (override) return override;
418
+ const suffix = `_${toolName.toUpperCase().replace(/-/g, "_")}`;
419
+ return tok.envVar.toUpperCase() + (tok.perTool ? suffix : "");
420
+ }
404
421
  function packsForTool(tool) {
405
422
  return PACKS.filter((p) => p.always || (tool ? p.appliesTo(tool) : false));
406
423
  }
@@ -426,7 +443,7 @@ function tokensForTool(tool) {
426
443
  }
427
444
 
428
445
  // src/version.ts
429
- var MODULE_REF = "v0.2.19";
446
+ var MODULE_REF = "v0.2.20";
430
447
  var MODULE_SOURCE_BASE = "git::https://github.com/RTrentJones/greenlight.git//infra/modules";
431
448
  function moduleSource(module, ref = MODULE_REF) {
432
449
  return `${MODULE_SOURCE_BASE}/${module}?ref=${ref}`;
@@ -441,6 +458,7 @@ function emitToolTf(opts) {
441
458
  const useSupabase = data === "supabase";
442
459
  const useVercel = target === "vercel";
443
460
  const useOci = target === "oci";
461
+ const supabaseOverride = opts.tokenOverrides?.SUPABASE_ACCESS_TOKEN;
444
462
  const envList = envs.map((e) => `"${e}"`).join(", ");
445
463
  const blocks = [];
446
464
  const assumes = ["var.cloudflare_zone_id"];
@@ -455,9 +473,25 @@ function emitToolTf(opts) {
455
473
  # External tool: app code + deploy live in ${slug}; this manages only its infra here.` : ""}`
456
474
  );
457
475
  if (useSupabase) {
476
+ const providersLine = supabaseOverride ? `
477
+ providers = { supabase = supabase.${name} }` : "";
478
+ const overrideBlock = supabaseOverride ? `
479
+
480
+ # Multi-account: ${name}'s Supabase lives in a SECOND account \u2014 an aliased provider authenticates
481
+ # with its own token. In infra.yml: TF_VAR_${name}_supabase_access_token: \${{ secrets.${supabaseOverride} }}
482
+ provider "supabase" {
483
+ alias = "${name}"
484
+ access_token = var.${name}_supabase_access_token
485
+ }
486
+
487
+ variable "${name}_supabase_access_token" {
488
+ type = string
489
+ sensitive = true
490
+ description = "Supabase Management API token for ${name}'s account (scoped secret ${supabaseOverride})."
491
+ }` : "";
458
492
  blocks.push(`# One Supabase project (schema-per-env), kept declarative + recreatable + kept alive.
459
493
  module "${name}_supabase" {
460
- source = "${moduleSource("supabase", ref)}"
494
+ source = "${moduleSource("supabase", ref)}"${providersLine}
461
495
 
462
496
  name = "${name}"
463
497
  project_name = "${name}-db"
@@ -473,7 +507,7 @@ variable "${name}_supabase_database_password" {
473
507
  type = string
474
508
  sensitive = true
475
509
  default = "import-placeholder" # ignored when importing an existing project
476
- }`);
510
+ }${overrideBlock}`);
477
511
  }
478
512
  if (useVercel) {
479
513
  const env = useSupabase ? `
@@ -918,8 +952,7 @@ async function gatherSecrets(name, repo, env, prefill) {
918
952
  for (const pack of packs) {
919
953
  console.log(`\u2500\u2500 ${pack.name}${pack.setupUrl ? ` \u2192 ${pack.setupUrl}` : ""}`);
920
954
  for (const tok of pack.tokens) {
921
- const suffix = `_${name.toUpperCase().replace(/-/g, "_")}`;
922
- const key = tok.envVar.toUpperCase() + (tok.perTool ? suffix : "");
955
+ const key = secretKeyFor(tok, name, entry.tokenOverrides);
923
956
  if (key === "GITHUB_TOKEN") {
924
957
  console.log(" \xB7 GITHUB_TOKEN \u2014 provided automatically by Actions; skipping");
925
958
  continue;
@@ -1070,7 +1103,16 @@ async function addCommand(args) {
1070
1103
  } else {
1071
1104
  writeFileSync2(
1072
1105
  toolTf,
1073
- emitToolTf({ name, domain: config.domain, lane, target, data, envs, port: entry?.port })
1106
+ emitToolTf({
1107
+ name,
1108
+ domain: config.domain,
1109
+ lane,
1110
+ target,
1111
+ data,
1112
+ envs,
1113
+ port: entry?.port,
1114
+ tokenOverrides: entry?.tokenOverrides
1115
+ })
1074
1116
  );
1075
1117
  console.log(`\u2714 wrote infra/${name}.tf (modules: ${providers.join(", ")})`);
1076
1118
  }
@@ -1976,6 +2018,16 @@ function conformanceChecks(t, root) {
1976
2018
  status: gateable ? "ok" : "warn",
1977
2019
  detail: platformPreview ? "vercel per-PR preview + deployment_status verify" : gateable ? void 0 : `no built-in serve for ${t.external ? "an external " : ""}${t.target} tool \u2014 add preview:{ command, \u2026 } so \`greenlight preview ${t.name}\` works`
1978
2020
  });
2021
+ const declared = [...t.tokens ?? [], ...Object.values(t.tokenOverrides ?? {})];
2022
+ if (declared.length) {
2023
+ const tag = t.name.toUpperCase().replace(/-/g, "_");
2024
+ const generic = declared.filter((s) => !s.toUpperCase().includes(tag));
2025
+ out.push({
2026
+ name: `${t.name}: token scoping`,
2027
+ status: generic.length ? "warn" : "ok",
2028
+ detail: generic.length ? `not tool-scoped (should contain ${tag}): ${generic.join(", ")}` : `${declared.length} scoped secret name(s)`
2029
+ });
2030
+ }
1979
2031
  return out;
1980
2032
  }
1981
2033
  function runDoctor(config, root) {
@@ -56,7 +56,16 @@ var ToolSchema = z.object({
56
56
  // local port (default: tool.port ?? lane default)
57
57
  path: z.string().optional()
58
58
  // connect path (default: lane default, e.g. `/mcp`)
59
- }).optional()
59
+ }).optional(),
60
+ // The project-scoped secret names this tool needs (e.g. ['TF_VAR_HEISTMIND_GITHUB_ADMIN_TOKEN']).
61
+ // The convention (docs/tokens-reference.md): a project-scoped secret carries the uppercased tool name.
62
+ // `doctor` warns on a name that doesn't — documentation + conformance, no behavior.
63
+ tokens: z.array(z.string()).optional(),
64
+ // Opt-in per-tool provider-token OVERRIDES (multi-account). Maps a provider's default token env
65
+ // var to an alternate secret name, so this tool authenticates that provider with a SECOND account
66
+ // — e.g. { SUPABASE_ACCESS_TOKEN: 'SUPABASE_ACCESS_TOKEN_HEISTMIND' }. Absent ⇒ unchanged (the
67
+ // default token). `add`/`adopt` emit an aliased provider + scoped var/secret for an overridden token.
68
+ tokenOverrides: z.record(z.string(), z.string()).optional()
60
69
  }).superRefine((tool, ctx) => {
61
70
  const rule = MATRIX[tool.lane];
62
71
  if (!rule.targets.includes(tool.target)) {
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  defineConfig,
3
3
  defineVerify,
4
4
  loadConfig
5
- } from "./chunk-6USV5AQV.js";
5
+ } from "./chunk-PSNO7F4Q.js";
6
6
  import "./chunk-HX7VA25D.js";
7
7
  import "./chunk-N3IKUCSF.js";
8
8
  import "./chunk-KP3Y6WRU.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rtrentjones/greenlight",
3
- "version": "0.2.19",
3
+ "version": "0.2.20",
4
4
  "description": "Greenlight CLI — setup and lifecycle for the harness.",
5
5
  "license": "MIT",
6
6
  "repository": {