Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/skills/contributing-to-gittensory/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ path filter matched; on push to `main`, everything runs.
| changes | `git diff --check` + path filter | `git diff --check` | trailing whitespace / conflict markers |
| lint → actionlint | workflow lint | `npm run actionlint` | any `.github/workflows/*.yml` violation |
| lint → migrations | migration guard | `npm run db:migrations:check` | duplicate/gap/misnamed migration number |
| lint → cf-typegen | worker types drift | `npm run cf-typegen:check` | committed `worker-configuration.d.ts` is stale (run `npm run cf-typegen`) |
| lint → typecheck | `tsc --noEmit` | `npm run typecheck` | any backend type error |
| test (1/2) | sharded vitest + coverage | `npm run test:coverage` (unsharded) | any failing `test/**/*.test.ts` (excl. `test/workers/**`) |
| workers | workers-pool vitest | `npm run test:workers` | any failing `test/workers/**` |
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ jobs:
- 'scripts/**'
- 'migrations/**'
- '.github/workflows/**'
- 'wrangler.jsonc'
- 'worker-configuration.d.ts'
# The UI's own app/extension code -- triggers the FULL toolchain (lint/typecheck/test/build).
# A dependency bump (package.json/package-lock.json) stays here too since it can break the UI
# build or types in ways only that full toolchain would catch.
Expand Down Expand Up @@ -190,6 +192,12 @@ jobs:
- name: Check migrations
if: ${{ github.event_name == 'push' || needs.changes.outputs.backend == 'true' }}
run: npm run db:migrations:check
# Guards against the same staleness-drift class as db:migrations:check/ui:openapi:check: two PRs that
# each independently add a wrangler.jsonc binding can both pass CI in isolation, then merge sequentially
# and leave a stale committed worker-configuration.d.ts with zero prior gate signal (#2557).
- name: cf-typegen drift check
if: ${{ github.event_name == 'push' || needs.changes.outputs.backend == 'true' }}
run: npm run cf-typegen:check
- name: Typecheck
if: ${{ github.event_name == 'push' || needs.changes.outputs.backend == 'true' }}
run: npm run typecheck
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"deploy:api": "wrangler d1 migrations apply gittensory --remote && wrangler deploy",
"selfhost:postgres:migrate": "tsx scripts/migrate-selfhost-sqlite-to-postgres.ts",
"cf-typegen": "wrangler types && perl -pi -e 's/[[:blank:]]+$//' worker-configuration.d.ts",
"cf-typegen:check": "wrangler types --check",
"db:migrate:local": "wrangler d1 migrations apply gittensory --local",
"db:migrate:remote": "wrangler d1 migrations apply gittensory --remote",
"drizzle:generate": "drizzle-kit generate",
Expand Down Expand Up @@ -62,7 +63,7 @@
"test:smoke:observability": "node scripts/smoke-observability-traces.mjs",
"test:smoke:browser:install": "playwright install chromium",
"test:smoke:browser": "node scripts/smoke-ui-browser.mjs",
"test:ci": "git diff --check && npm run actionlint && npm run db:migrations:check && npm run typecheck && npm run test:coverage && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run build:miner && npm run rees:test && npm run ui:openapi:check && npm run ui:version-audit && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build",
"test:ci": "git diff --check && npm run actionlint && npm run db:migrations:check && npm run cf-typegen:check && npm run typecheck && npm run test:coverage && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run build:miner && npm run rees:test && npm run ui:openapi:check && npm run ui:version-audit && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build",
"test:release": "npm run test:ci && npm run changelog:check",
"test:release:mcp": "npm run test:ci && npm run changelog:check:mcp",
"test:watch": "vitest",
Expand Down
70 changes: 70 additions & 0 deletions test/unit/ci-cf-typegen-check.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { readFileSync } from "node:fs";
import { parse } from "yaml";
import { describe, expect, it } from "vitest";

function readYaml(path: string): Record<string, unknown> {
return record(parse(readFileSync(path, "utf8")), path);
}

function record(value: unknown, label: string): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error(`${label} must be an object`);
}
return value as Record<string, unknown>;
}

function recordArray(value: unknown, label: string): Array<Record<string, unknown>> {
if (!Array.isArray(value)) throw new Error(`${label} must be an array`);
return value.map((entry, index) => record(entry, `${label}[${index}]`));
}

function nestedRecord(source: Record<string, unknown>, path: string[]): Record<string, unknown> {
return path.reduce((current, key) => record(current[key], path.join(".")), source);
}

// #2557: worker-configuration.d.ts is generated from wrangler.jsonc via `npm run cf-typegen`, but unlike its
// sibling generated artifacts (openapi.json, migrations) it had no CI drift guard -- two independently-valid
// binding additions could merge sequentially and leave the committed types silently stale.
describe("cf-typegen staleness guard (#2557)", () => {
it("package.json defines cf-typegen:check and wires it into test:ci", () => {
const pkg = record(JSON.parse(readFileSync("package.json", "utf8")), "package.json");
const scripts = record(pkg.scripts, "package.json.scripts");

expect(scripts["cf-typegen:check"]).toBe("wrangler types --check");
expect(String(scripts["test:ci"])).toContain("npm run cf-typegen:check");
// Must run before typecheck, mirroring db:migrations:check's position -- a drift-check failure should
// surface before the more expensive type/test/build steps run.
const ciChain = String(scripts["test:ci"]);
expect(ciChain.indexOf("cf-typegen:check")).toBeLessThan(ciChain.indexOf("npm run typecheck"));
});

it("ci.yml's changes job treats wrangler.jsonc and worker-configuration.d.ts as backend paths", () => {
const workflow = readYaml(".github/workflows/ci.yml");
const changesJob = nestedRecord(workflow, ["jobs", "changes"]);
const steps = recordArray(changesJob.steps, "jobs.changes.steps");
const filterStep = steps.find((step) => step.id === "filter");
expect(filterStep).toBeDefined();
const withBlock = record(filterStep!.with, "filter.with");
const filters = String(withBlock.filters);
const backendBlock = filters.slice(filters.indexOf("backend:"), filters.indexOf("ui:"));

expect(backendBlock).toContain("wrangler.jsonc");
expect(backendBlock).toContain("worker-configuration.d.ts");
});

it("ci.yml's validate-code job runs the drift check gated the same as the migrations check", () => {
const workflow = readYaml(".github/workflows/ci.yml");
const validateCode = nestedRecord(workflow, ["jobs", "validate-code"]);
const steps = recordArray(validateCode.steps, "jobs.validate-code.steps");

const migrationsIndex = steps.findIndex((step) => step.name === "Check migrations");
const cfTypegenIndex = steps.findIndex((step) => step.name === "cf-typegen drift check");
expect(migrationsIndex).toBeGreaterThan(-1);
expect(cfTypegenIndex).toBeGreaterThan(migrationsIndex);

const migrationsStep = steps[migrationsIndex]!;
const cfTypegenStep = steps[cfTypegenIndex]!;
expect(String(cfTypegenStep.if)).toBe(String(migrationsStep.if));
expect(String(cfTypegenStep.run)).toBe("npm run cf-typegen:check");
});
});
Loading