@rtrentjones/greenlight 0.2.20 → 0.2.22

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/README.md CHANGED
@@ -48,7 +48,8 @@ greenlight <command>
48
48
  | `verify <name> --env <beta\|prod>` (or `--url`) | run the shared verify harness |
49
49
  | `promote <name>` | gated `develop → main` fast-forward (after beta verify) |
50
50
  | `deploy <name>` | target deploy hook (e.g. OCI restart = re-pull) |
51
- | `doctor` / `config` | health checks / load + validate + print the manifest |
51
+ | `status <name>` | the ship deploy verify run chain across repos |
52
+ | `doctor` / `config` | health checks (incl. token-scoping conformance) / load + validate + print the manifest |
52
53
 
53
54
  ## The loop
54
55
 
@@ -70,7 +71,8 @@ import { defineConfig, defineVerify } from '@rtrentjones/greenlight';
70
71
 
71
72
  export default defineConfig({
72
73
  domain: 'you.dev',
73
- tools: { notes: { lane: 'mcp', target: 'oci', data: 'none', auth: 'bearer' } },
74
+ alerts: { sink: 'github-issue' },
75
+ tools: [{ name: 'notes', lane: 'mcp', target: 'oci', data: 'none', auth: 'bearer', envs: ['prod'] }],
74
76
  });
75
77
  ```
76
78
 
@@ -80,6 +82,7 @@ Also exported: `loadConfig`, and the `GreenlightConfig` / `VerifySpec` types.
80
82
 
81
83
  - **Repo + full docs:** <https://github.com/RTrentJones/greenlight>
82
84
  - **Architecture:** [docs/architecture.md](https://github.com/RTrentJones/greenlight/blob/main/docs/architecture.md)
83
- - **Spec:** [greenlight-v1.md](https://github.com/RTrentJones/greenlight/blob/main/greenlight-v1.md)
85
+ - **Spec:** [greenlight-v2.md](https://github.com/RTrentJones/greenlight/blob/main/greenlight-v2.md)
86
+ - **Add a provider type:** [docs/adding-a-provider.md](https://github.com/RTrentJones/greenlight/blob/main/docs/adding-a-provider.md)
84
87
 
85
88
  MIT
@@ -42,5 +42,5 @@ Migrate local → HCP with a plain `terraform init` (answer `yes` to copy state)
42
42
  plan -out → apply. This is the deploy half — the CLI only edits the `.tf`; CI applies.
43
43
 
44
44
  ## Alternatives
45
- See `docs/terraform-state-r2.md` for the full backend chooser (HCP no-CC · OCI S3-compat ·
45
+ See `docs/terraform-state.md` for the full backend chooser (HCP no-CC · OCI S3-compat ·
46
46
  R2 card-required · AWS · local).
package/dist/bin.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  loadConfig,
6
6
  resolveUrl,
7
7
  verifyAll
8
- } from "./chunk-PSNO7F4Q.js";
8
+ } from "./chunk-GO2RVNOP.js";
9
9
  import "./chunk-HX7VA25D.js";
10
10
  import "./chunk-N3IKUCSF.js";
11
11
  import "./chunk-KP3Y6WRU.js";
@@ -322,7 +322,7 @@ var PACKS = [
322
322
  always: true,
323
323
  // remote state backs every wrapper's infra
324
324
  appliesTo: () => true,
325
- guide: "docs/terraform-state-r2.md \u2014 HCP Terraform free tier (no credit card)",
325
+ guide: "docs/terraform-state.md \u2014 HCP Terraform free tier (no credit card)",
326
326
  setupUrl: "https://app.terraform.io/app/settings/tokens",
327
327
  tokens: [
328
328
  {
@@ -443,7 +443,7 @@ function tokensForTool(tool) {
443
443
  }
444
444
 
445
445
  // src/version.ts
446
- var MODULE_REF = "v0.2.20";
446
+ var MODULE_REF = "v0.2.22";
447
447
  var MODULE_SOURCE_BASE = "git::https://github.com/RTrentJones/greenlight.git//infra/modules";
448
448
  function moduleSource(module, ref = MODULE_REF) {
449
449
  return `${MODULE_SOURCE_BASE}/${module}?ref=${ref}`;
@@ -677,7 +677,7 @@ locals {
677
677
  ` : "";
678
678
  return `# Wrapper infra (singleton): providers + remote-state backend + shared variables.
679
679
  # \`greenlight add\` appends per-tool module blocks as infra/<name>.tf. Apply is CI/CD's job
680
- # (infra.yml). Fill in the HCP backend below before the first apply (docs/terraform-state-r2.md).
680
+ # (infra.yml). Fill in the HCP backend below before the first apply (docs/terraform-state.md).
681
681
 
682
682
  terraform {
683
683
  required_version = ">= 1.7"
@@ -2332,7 +2332,7 @@ async function initCommand(args) {
2332
2332
  Next:
2333
2333
  1. greenlight add <name> --lane <lane> --target <target> # scaffold a tool, emit infra, and
2334
2334
  # gather THAT tool's keys \u2192 GitHub${pushed ? "" : "\n (run `greenlight secrets sync` if base tokens were not pushed)"}
2335
- 2. set the HCP backend (cloud{} org + workspace) in infra/main.tf # docs/terraform-state-r2.md
2335
+ 2. set the HCP backend (cloud{} org + workspace) in infra/main.tf # docs/terraform-state.md
2336
2336
  3. commit + push \u2192 CI (.github/workflows/infra.yml) runs \`terraform apply\`
2337
2337
  4. greenlight verify <name> --env prod | greenlight doctor`);
2338
2338
  }
@@ -2348,7 +2348,13 @@ import { resolve as resolve9 } from "path";
2348
2348
  function defaultSpec(lane) {
2349
2349
  switch (lane) {
2350
2350
  case "astro":
2351
- return { mode: "api", checks: [{ path: "/", status: 200 }], noBrokenInternalLinks: true };
2351
+ return {
2352
+ mode: "api",
2353
+ checks: [{ path: "/", status: 200 }],
2354
+ noBrokenInternalLinks: true,
2355
+ settleRetries: 8,
2356
+ settleMs: 5e3
2357
+ };
2352
2358
  case "next":
2353
2359
  return { mode: "api", checks: [{ path: "/", status: 200 }] };
2354
2360
  case "mcp":
@@ -2601,8 +2607,21 @@ function git(repoDir, args) {
2601
2607
  function gitOut(repoDir, args) {
2602
2608
  return execFileSync5("git", args, { cwd: repoDir, encoding: "utf8" }).trim();
2603
2609
  }
2610
+ function tryRev(repoDir, ref) {
2611
+ try {
2612
+ return gitOut(repoDir, ["rev-parse", "--verify", "--quiet", ref]);
2613
+ } catch {
2614
+ return null;
2615
+ }
2616
+ }
2617
+ function fetchRefs(repoDir, branches) {
2618
+ try {
2619
+ git(repoDir, ["fetch", "--no-tags", "origin", ...branches]);
2620
+ } catch {
2621
+ }
2622
+ }
2604
2623
  function resolveRef(repoDir, branch) {
2605
- for (const ref of [branch, `origin/${branch}`]) {
2624
+ for (const ref of [`origin/${branch}`, branch]) {
2606
2625
  try {
2607
2626
  git(repoDir, ["rev-parse", "--verify", "--quiet", ref]);
2608
2627
  return ref;
@@ -2611,19 +2630,35 @@ function resolveRef(repoDir, branch) {
2611
2630
  }
2612
2631
  return null;
2613
2632
  }
2633
+ function staleLocalWarnings(repoDir, branches) {
2634
+ const warnings = [];
2635
+ for (const branch of branches) {
2636
+ const local = tryRev(repoDir, branch);
2637
+ const origin = tryRev(repoDir, `origin/${branch}`);
2638
+ if (local && origin && local !== origin) {
2639
+ warnings.push(
2640
+ `local "${branch}" (${local.slice(0, 7)}) differs from origin/${branch} (${origin.slice(0, 7)}) \u2014 promoting the origin (verified) state. Sync with \`git fetch && git branch -f ${branch} origin/${branch}\`.`
2641
+ );
2642
+ }
2643
+ }
2644
+ return warnings;
2645
+ }
2614
2646
  function canPromote(repoDir, from = "develop", to = "main") {
2647
+ fetchRefs(repoDir, [from, to]);
2615
2648
  const fromRef = resolveRef(repoDir, from);
2616
2649
  const toRef = resolveRef(repoDir, to);
2617
2650
  if (!fromRef || !toRef) {
2618
2651
  return { canPromote: false, reason: `branch "${from}" or "${to}" not found in ${repoDir}` };
2619
2652
  }
2653
+ const warnings = staleLocalWarnings(repoDir, [from, to]);
2620
2654
  try {
2621
2655
  git(repoDir, ["merge-base", "--is-ancestor", toRef, fromRef]);
2622
- return { canPromote: true, reason: `"${to}" can fast-forward to "${from}"` };
2656
+ return { canPromote: true, reason: `"${to}" can fast-forward to "${from}"`, warnings };
2623
2657
  } catch {
2624
2658
  return {
2625
2659
  canPromote: false,
2626
- reason: `"${to}" has diverged from "${from}" \u2014 fast-forward refused. Reconcile first (rebase "${from}" onto "${to}", or merge "${to}" into "${from}") before promoting.`
2660
+ reason: `"${to}" has diverged from "${from}" \u2014 fast-forward refused. Reconcile first (rebase "${from}" onto "${to}", or merge "${to}" into "${from}") before promoting.`,
2661
+ warnings
2627
2662
  };
2628
2663
  }
2629
2664
  }
@@ -2631,7 +2666,10 @@ function promote(repoDir, opts = {}) {
2631
2666
  const from = opts.from ?? "develop";
2632
2667
  const to = opts.to ?? "main";
2633
2668
  const check = canPromote(repoDir, from, to);
2634
- if (!check.canPromote) return { promoted: false, from, to, reason: check.reason };
2669
+ if (!check.canPromote) {
2670
+ return { promoted: false, from, to, reason: check.reason, warnings: check.warnings };
2671
+ }
2672
+ const warnings = check.warnings;
2635
2673
  const fromRef = resolveRef(repoDir, from);
2636
2674
  const fromCommit = gitOut(repoDir, ["rev-parse", fromRef]);
2637
2675
  const current = gitOut(repoDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
@@ -2643,14 +2681,20 @@ function promote(repoDir, opts = {}) {
2643
2681
  } catch {
2644
2682
  }
2645
2683
  }
2646
- return { promoted: true, from, to, reason: `"${to}" fast-forwarded to "${from}" and pushed` };
2684
+ return {
2685
+ promoted: true,
2686
+ from,
2687
+ to,
2688
+ reason: `"${to}" fast-forwarded to "${from}" and pushed`,
2689
+ warnings
2690
+ };
2647
2691
  }
2648
2692
  if (current === to) {
2649
2693
  git(repoDir, ["merge", "--ff-only", fromRef]);
2650
2694
  } else {
2651
2695
  git(repoDir, ["update-ref", `refs/heads/${to}`, fromCommit]);
2652
2696
  }
2653
- return { promoted: true, from, to, reason: `"${to}" fast-forwarded to "${from}"` };
2697
+ return { promoted: true, from, to, reason: `"${to}" fast-forwarded to "${from}"`, warnings };
2654
2698
  }
2655
2699
 
2656
2700
  // src/commands/promote.ts
@@ -2660,11 +2704,13 @@ async function promoteCommand(args) {
2660
2704
  const cwd = process.cwd();
2661
2705
  if (!perform) {
2662
2706
  const check = canPromote(cwd);
2707
+ for (const w of check.warnings ?? []) console.warn(`\u26A0 ${w}`);
2663
2708
  console.log(`${check.canPromote ? "\u2714" : "\u2718"} ${check.reason}`);
2664
2709
  if (check.canPromote) console.log("\nEligible. Re-run with --perform (and --push) to promote.");
2665
2710
  process.exit(check.canPromote ? 0 : 1);
2666
2711
  }
2667
2712
  const result = promote(cwd, { push });
2713
+ for (const w of result.warnings ?? []) console.warn(`\u26A0 ${w}`);
2668
2714
  console.log(`${result.promoted ? "\u2714" : "\u2718"} ${result.reason}`);
2669
2715
  process.exit(result.promoted ? 0 : 1);
2670
2716
  }
@@ -2767,7 +2813,7 @@ var HELP = `greenlight <command>
2767
2813
  doctor manifest + repo consistency checks
2768
2814
  help show this message
2769
2815
 
2770
- Real cloud deploys need the target's creds (e.g. CLOUDFLARE_API_TOKEN); see greenlight-v1.md \xA716.`;
2816
+ Real cloud deploys need the target's creds (e.g. CLOUDFLARE_API_TOKEN); see docs/archive/greenlight-v1.md \xA716.`;
2771
2817
  async function main() {
2772
2818
  const [cmd, ...args] = process.argv.slice(2);
2773
2819
  switch (cmd) {
@@ -41,7 +41,7 @@ var ToolSchema = z.object({
41
41
  // (poly-repo) tool sets '.' (the repo root).
42
42
  dir: z.string().optional(),
43
43
  // The tool's code lives in another repo — this entry is a registry pointer only,
44
- // not built/deployed here (greenlight-v1.md §15.5 poly-repo).
44
+ // not built/deployed here (docs/archive/greenlight-v1.md §15.5 poly-repo).
45
45
  external: z.boolean().default(false),
46
46
  // How `greenlight preview` spins the tool up LOCALLY for the pre-deploy gate. Optional — node
47
47
  // lanes (astro/next/mcp→workers) use the built-in build+serve path. Set it for targets with no
@@ -206,8 +206,7 @@ async function checkInternalLinks(base, max = 25) {
206
206
  return { name: "no broken internal links", pass: false, detail: msg(e) };
207
207
  }
208
208
  }
209
- async function verifyApi(baseUrl, spec) {
210
- const base = trimSlash(baseUrl);
209
+ async function runChecks(base, spec) {
211
210
  const checks = [];
212
211
  for (const c of spec.checks ?? []) checks.push(await checkRoute(base, c));
213
212
  if (spec.rssValid) {
@@ -226,6 +225,17 @@ async function verifyApi(baseUrl, spec) {
226
225
  );
227
226
  }
228
227
  if (spec.noBrokenInternalLinks) checks.push(await checkInternalLinks(base));
228
+ return checks;
229
+ }
230
+ async function verifyApi(baseUrl, spec) {
231
+ const base = trimSlash(baseUrl);
232
+ const retries = Math.max(0, spec.settleRetries ?? 0);
233
+ const delayMs = spec.settleMs ?? 5e3;
234
+ let checks = await runChecks(base, spec);
235
+ for (let i = 0; i < retries && !checks.every((c) => c.pass); i++) {
236
+ if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
237
+ checks = await runChecks(base, spec);
238
+ }
229
239
  return report("api", baseUrl, checks);
230
240
  }
231
241
 
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  defineConfig,
3
3
  defineVerify,
4
4
  loadConfig
5
- } from "./chunk-PSNO7F4Q.js";
5
+ } from "./chunk-GO2RVNOP.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.20",
3
+ "version": "0.2.22",
4
4
  "description": "Greenlight CLI — setup and lifecycle for the harness.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -31,10 +31,10 @@
31
31
  "@anthropic-ai/sdk": "^0.69.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@rtrentjones/greenlight-adapters": "0.2.4",
35
- "@rtrentjones/greenlight-loop": "0.2.4",
36
- "@rtrentjones/greenlight-shared": "0.2.4",
37
- "@rtrentjones/greenlight-verify": "0.2.4"
34
+ "@rtrentjones/greenlight-adapters": "0.2.22",
35
+ "@rtrentjones/greenlight-verify": "0.2.22",
36
+ "@rtrentjones/greenlight-loop": "0.2.22",
37
+ "@rtrentjones/greenlight-shared": "0.2.22"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "node scripts/copy-assets.mjs && tsup",
@@ -25,4 +25,4 @@ export default { mode: 'mcp', expectTools: ['<tool>'], call: { name: '<tool>' }
25
25
 
26
26
  ## Auth
27
27
 
28
- `auth: none` only for public read-only servers. Mutating/private servers default to `bearer`/`oauth` (greenlight-v1.md §6/§14).
28
+ `auth: none` only for public read-only servers. Mutating/private servers default to `bearer`/`oauth` (docs/archive/greenlight-v1.md §6/§14).
@@ -2,4 +2,4 @@
2
2
 
3
3
  Lane template for **Next.js on Vercel** with Supabase — verify mode `api + playwright`.
4
4
 
5
- > **Phase 0:** placeholder only. Real template content arrives when the `next` lane is exercised (HeistMind migration, **Phase 9** — greenlight-v1.md §16). Materialized into a tool by `greenlight add` / `greenlight adopt`.
5
+ > **Phase 0:** placeholder only. Real template content arrives when the `next` lane is exercised (HeistMind migration, **Phase 9** — docs/archive/greenlight-v1.md §16). Materialized into a tool by `greenlight add` / `greenlight adopt`.