@rtrentjones/greenlight 0.2.7 → 0.2.8

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.
@@ -26,6 +26,22 @@ Manages the **existing** project (nothing to import — it configures by id):
26
26
 
27
27
  The DNS CNAME is the **cloudflare** `tool` module, unproxied (`proxied = false`) → `cname.vercel-dns.com`.
28
28
 
29
+ ## The verify loop — tool-CI on `deployment_status`
30
+
31
+ Because Vercel deploys (not the wrapper), the verify gate runs in the **tool repo's own CI**, not a
32
+ wrapper deploy listener. `greenlight adopt … --target vercel` emits, into the tool repo:
33
+ - **`.github/workflows/greenlight-verify.yml`** — triggers on GitHub's **`deployment_status`** event
34
+ (Vercel posts a deployment + `target_url`); on `state == success` it runs
35
+ `npx @rtrentjones/greenlight verify --url <target_url> --spec verify/<name>.config.ts`. The result
36
+ is a check on the commit — no wrapper round-trip, no dispatch/status PATs (Vercel owns deploy + URL
37
+ + its own statuses).
38
+ - **`verify/<name>.config.ts`** — a verifyAll array: `api` (deployed URL 200) + `test` (the tool's
39
+ suite) + `agent-web` (LLM drives the live UI), where agent-web is **config-gated on
40
+ `ANTHROPIC_API_KEY`** (omitted when unset → the gate stays green on api + test alone).
41
+
42
+ `greenlight verify --url <url> --spec <path>` is the **manifest-free** mode that makes this work
43
+ without carrying the wrapper's `greenlight.config.ts` into the tool repo.
44
+
29
45
  ## MCP
30
46
 
31
47
  `.mcp.json` wires `vercel` (hosted, OAuth, read-only). Run `/mcp` and authenticate in the
package/dist/bin.js CHANGED
@@ -390,7 +390,7 @@ function tokensForTool(tool) {
390
390
  }
391
391
 
392
392
  // src/version.ts
393
- var MODULE_REF = "v0.2.7";
393
+ var MODULE_REF = "v0.2.8";
394
394
  var MODULE_SOURCE_BASE = "git::https://github.com/RTrentJones/greenlight.git//infra/modules";
395
395
  function moduleSource(module, ref = MODULE_REF) {
396
396
  return `${MODULE_SOURCE_BASE}/${module}?ref=${ref}`;
@@ -1421,6 +1421,69 @@ jobs:
1421
1421
  -f description="\${{ job.status }}"
1422
1422
  `;
1423
1423
  }
1424
+ function verifyWorkflowYml(name) {
1425
+ return `name: greenlight-verify
1426
+
1427
+ # Vercel deploys ${name} on push and posts a deployment_status to GitHub; we verify the exact
1428
+ # deployed URL. ANTHROPIC_API_KEY (optional) enables the agent-web scenarios \u2014 absent \u2192 omitted
1429
+ # (see verify/${name}.config.ts), so the gate stays green on api + test alone.
1430
+ on:
1431
+ deployment_status:
1432
+
1433
+ permissions:
1434
+ contents: read
1435
+ statuses: write
1436
+
1437
+ jobs:
1438
+ verify:
1439
+ if: \${{ github.event.deployment_status.state == 'success' }}
1440
+ runs-on: ubuntu-latest
1441
+ steps:
1442
+ - uses: actions/checkout@v4
1443
+ - uses: actions/setup-node@v4
1444
+ with:
1445
+ node-version: '24'
1446
+ - name: Install deps (for test-mode)
1447
+ run: |
1448
+ corepack enable || true
1449
+ if [ -f pnpm-lock.yaml ]; then pnpm install --frozen-lockfile;
1450
+ elif [ -f yarn.lock ]; then yarn install --frozen-lockfile;
1451
+ else npm ci; fi
1452
+ - name: Verify the deployment
1453
+ env:
1454
+ ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
1455
+ run: npx -y @rtrentjones/greenlight@latest verify --url "\${{ github.event.deployment_status.target_url }}" --spec verify/${name}.config.ts
1456
+ `;
1457
+ }
1458
+ function nextVerifyConfig(name) {
1459
+ return `// Greenlight verify spec for ${name} (next/vercel) \u2014 run by .github/workflows/greenlight-verify.yml
1460
+ // after Vercel deploys (deployment_status). An array combines modes (allPass):
1461
+ // - api: the deployed URL serves (200).
1462
+ // - test: this tool's own suite \u2014 set the real command for your package manager.
1463
+ // - agent-web: an LLM drives the live UI; runs ONLY when ANTHROPIC_API_KEY is set (else omitted,
1464
+ // so the gate stays green). Replace the scenario with real user tasks + assertions.
1465
+ const agentWeb = process.env.ANTHROPIC_API_KEY
1466
+ ? [
1467
+ {
1468
+ mode: 'agent-web',
1469
+ scenarios: [
1470
+ {
1471
+ name: 'home renders',
1472
+ task: 'Open the home page and confirm the app loads without an error screen.',
1473
+ asserts: [{ selector: 'body' }],
1474
+ },
1475
+ ],
1476
+ },
1477
+ ]
1478
+ : [];
1479
+
1480
+ export default [
1481
+ { mode: 'api', checks: [{ path: '/', status: 200 }] },
1482
+ { mode: 'test', command: 'npm test' },
1483
+ ...agentWeb,
1484
+ ];
1485
+ `;
1486
+ }
1424
1487
  function writeIfAbsent(path, contents, label) {
1425
1488
  if (existsSync7(path)) {
1426
1489
  console.log(`\xB7 ${label} exists \u2014 left as-is`);
@@ -1498,11 +1561,13 @@ async function adoptWrapper(ctx) {
1498
1561
  emitToolTf({ name, domain, lane, target, data, envs, slug, external: true }),
1499
1562
  `infra/${name}.tf`
1500
1563
  );
1501
- writeIfAbsent(
1502
- join2(cwd, `verify/${name}.config.ts`),
1503
- starterVerifyConfig(lane),
1504
- `verify/${name}.config.ts`
1505
- );
1564
+ if (target !== "vercel") {
1565
+ writeIfAbsent(
1566
+ join2(cwd, `verify/${name}.config.ts`),
1567
+ starterVerifyConfig(lane),
1568
+ `verify/${name}.config.ts`
1569
+ );
1570
+ }
1506
1571
  const providers = providersForTool({ target, data });
1507
1572
  if (existsSync7(join2(cwd, "infra/main.tf")) && providers.some((p) => p !== "cloudflare" && p !== "github")) {
1508
1573
  console.log(`\xB7 ensure infra/main.tf declares provider(s): ${providers.join(", ")}`);
@@ -1522,6 +1587,18 @@ async function adoptWrapper(ctx) {
1522
1587
  `${toolRel}/.github/workflows/greenlight-build.yml (provider-agnostic build \u2192 GHCR \u2192 dispatch)`
1523
1588
  );
1524
1589
  }
1590
+ if (target === "vercel") {
1591
+ writeIfAbsent(
1592
+ join2(dest, `verify/${name}.config.ts`),
1593
+ nextVerifyConfig(name),
1594
+ `${toolRel}/verify/${name}.config.ts (tool-CI verify spec)`
1595
+ );
1596
+ writeIfAbsent(
1597
+ join2(dest, ".github/workflows/greenlight-verify.yml"),
1598
+ verifyWorkflowYml(name),
1599
+ `${toolRel}/.github/workflows/greenlight-verify.yml (verify on Vercel deployment_status)`
1600
+ );
1601
+ }
1525
1602
  console.log(`
1526
1603
  Next:
1527
1604
  (in the tool repo) commit the Greenlight kit + build workflow so they travel with the submodule:
@@ -1531,7 +1608,10 @@ Next:
1531
1608
  git commit && git push # CI (infra.yml) applies. Tool's CI builds; wrapper deploys.${target === "oci" ? `
1532
1609
  Secrets (guided): greenlight secrets gather ${name} --repo <wrapper> # TF_VAR_OCI_* + GREENLIGHT_STATUS_TOKEN
1533
1610
  greenlight secrets gather ${name} --repo ${slug} # GREENLIGHT_DISPATCH_TOKEN
1534
- The instance OCID is auto-resolved by the deploy workflow (by display name) \u2014 nothing to set.` : ""}`);
1611
+ The instance OCID is auto-resolved by the deploy workflow (by display name) \u2014 nothing to set.` : target === "vercel" ? `
1612
+ Deploy is Vercel's git integration (no wrapper deploy). The tool's greenlight-verify.yml verifies
1613
+ each deployment (deployment_status). Optional: add ANTHROPIC_API_KEY to ${slug} to enable the
1614
+ agent-web scenarios in verify/${name}.config.ts (absent \u2192 api + test gate alone).` : ""}`);
1535
1615
  }
1536
1616
  async function adoptStandalone(ctx) {
1537
1617
  const { name, repoArg, lane, target, data, auth, envs, domain, reg, regPath } = ctx;
@@ -1936,9 +2016,30 @@ function flag6(args, name) {
1936
2016
  return i >= 0 ? args[i + 1] : void 0;
1937
2017
  }
1938
2018
  async function verifyCommand(args) {
2019
+ const specPath = flag6(args, "--spec");
2020
+ if (specPath) {
2021
+ const url2 = flag6(args, "--url");
2022
+ if (!url2) throw new Error("verify --spec needs --url <deployed-url>");
2023
+ const loaded2 = await loadVerifySpecAt(specPath);
2024
+ if (!loaded2) throw new Error(`no verify spec at ${specPath}`);
2025
+ const specs2 = Array.isArray(loaded2) ? loaded2 : [loaded2];
2026
+ const waitMs = (flag6(args, "--wait") !== void 0 ? Number(flag6(args, "--wait")) : 0) * 1e3;
2027
+ const reports2 = await verifyAll(url2, specs2, {
2028
+ reachableTimeoutMs: waitMs,
2029
+ toolDir: process.cwd()
2030
+ });
2031
+ for (const report of reports2) printReport(report);
2032
+ const pass2 = allPass(reports2);
2033
+ if (reports2.length > 1)
2034
+ console.log(`
2035
+ ${pass2 ? "\u2714 ALL PASS" : "\u2718 FAIL"} (${reports2.length} specs)`);
2036
+ process.exit(pass2 ? 0 : 1);
2037
+ }
1939
2038
  const name = args[0];
1940
2039
  if (!name || name.startsWith("-")) {
1941
- throw new Error("usage: greenlight verify <name> [--env <beta|prod> | --url <url>]");
2040
+ throw new Error(
2041
+ "usage: greenlight verify <name> [--env <beta|prod> | --url <url>] | verify --url <url> --spec <path>"
2042
+ );
1942
2043
  }
1943
2044
  const { config } = await loadManifest();
1944
2045
  const entry = resolveEntry(config, name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rtrentjones/greenlight",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Greenlight CLI — setup and lifecycle for the harness.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,9 +32,9 @@
32
32
  },
33
33
  "devDependencies": {
34
34
  "@rtrentjones/greenlight-adapters": "0.2.4",
35
- "@rtrentjones/greenlight-loop": "0.2.4",
36
35
  "@rtrentjones/greenlight-shared": "0.2.4",
37
- "@rtrentjones/greenlight-verify": "0.2.4"
36
+ "@rtrentjones/greenlight-verify": "0.2.4",
37
+ "@rtrentjones/greenlight-loop": "0.2.4"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "node scripts/copy-assets.mjs && tsup",