From a164169ae8b70c46352aaacd09cc7fdcdf3809c0 Mon Sep 17 00:00:00 2001 From: Vianney MORAIN Date: Fri, 17 Jul 2026 22:08:06 +0200 Subject: [PATCH] feat(fixtures): deterministic bundleVersion re-stamp script + sync automation The fork's self-hosting schema patches move the bundleVersion contract hash away from upstream's committed fixture stamps, failing protocol/fixtures.test.ts on every build (and recurring at each upstream sync). Rather than re-syncing fixtures against a builder, re-stamp the value deterministically from the schema. - packages/protocol/scripts/restamp-fixtures.ts: surgical text-replace of the bundleVersion field in each fixtures/*/.webstudio/data.json to the value computed from this checkout's schema. `--check` mode reports drift (exit 1). - package.json: `pnpm fixtures:restamp`. - sync-upstream.yml: after a clean rebase, run the re-stamp and commit/push so develop stays green with no manual step. - CLAUDE.md: document the workflow. The existing fixtures.test.ts keeps guarding structural validity (publishedProjectBundle.parse); this only refreshes the version stamp. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/sync-upstream.yml | 19 +++++ CLAUDE.md | 4 + package.json | 1 + packages/protocol/scripts/restamp-fixtures.ts | 77 +++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 packages/protocol/scripts/restamp-fixtures.ts diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index 4159c98eba96..333f28f4374b 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -114,6 +114,25 @@ jobs: git push --force-with-lease origin develop + # After a clean rebase, the fork's schema patches may have moved the + # bundleVersion contract hash away from the (upstream-generated) fixture + # stamps, which would fail packages/protocol/fixtures.test.ts. Re-stamp + # them deterministically so develop stays green with no manual step. + # See packages/protocol/scripts/restamp-fixtures.ts. + - uses: ./.github/actions/ci-setup + if: steps.rebase.outcome == 'success' + + - name: Re-stamp fixtures for fork schema + if: steps.rebase.outcome == 'success' + run: | + pnpm fixtures:restamp + if git diff --quiet; then + echo "Fixture bundleVersion already current." + else + git commit -am "chore(fixtures): re-stamp bundleVersion for fork schema" + git push origin develop + fi + - name: Open issue on rebase failure if: steps.rebase.outcome == 'failure' env: diff --git a/CLAUDE.md b/CLAUDE.md index 22a7a734246c..fad5c4012f98 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,6 +6,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co This is a community fork. `develop` is maintained as `upstream/main` + a small patch series of thematic commits (listed in `COMMUNITY_CHANGES.md`), rebased on top of upstream at every sync. New PRs are squash-merged on `develop`, then folded into the relevant patch (or added as a new one, updating the list in `COMMUNITY_CHANGES.md`) at the next upstream sync. When a contribution is accepted upstream, drop it from the series and from the list. +### Fixture bundleVersion + +`bundleVersion` (in `packages/protocol/src/schema.ts`) is a contract hash of the schema. The fork's self-hosting patches change the schema, so its hash differs from the (upstream-generated) stamps committed in `fixtures/*/.webstudio/data.json`, failing `fixtures.test.ts`. After changing the bundle schema — or after an upstream sync — run `pnpm fixtures:restamp` and commit. The `sync-upstream` workflow already does this automatically after a clean rebase; `pnpm fixtures:restamp --check` fails on drift (CI). + ## Overview Webstudio is an Open Source Visual Development Platform. This is a pnpm monorepo with three workspace types: `apps/`, `packages/`, and `fixtures/`. diff --git a/package.json b/package.json index 24f34e280eb1..15f4d23adde3 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "check:generated-api": "pnpm --filter @webstudio-is/project-build generate-runtime-operation-contracts && pnpm --filter @webstudio-is/builder generate-server-only-api-metadata && node scripts/check-clean-files.mjs packages/project-build/src/contracts/__generated__/runtime-operation-contracts.ts packages/protocol/src/builder-api/__generated__/server-only-router-operation-metadata.ts", "check:generated-docs": "pnpm --filter @webstudio-is/project-build generate-docs && pnpm --filter webstudio generate-docs && node scripts/check-clean-files.mjs packages/project-build/src/docs.generated.ts packages/cli/src/docs.generated.ts", "fixtures": "pnpm -r fixtures:link && pnpm -r fixtures:sync && pnpm -r fixtures:build", + "fixtures:restamp": "NODE_OPTIONS='--conditions=webstudio --import=tsx' node packages/protocol/scripts/restamp-fixtures.ts", "checks": "pnpm -r test && pnpm -r typecheck && pnpm lint && pnpm check:package-boundaries && pnpm check:generated-api && pnpm check:generated-docs && pnpm fixtures", "e2e:builder": "bash ./apps/builder/e2e/run-local.sh", "e2e:builder:dev:backend": "E2E_SKIP_CLEANUP=true E2E_RUN_TESTS=false E2E_START_POSTGREST=true E2E_DB_BOOTSTRAP=if-empty E2E_INSTALL_PLAYWRIGHT=false bash ./apps/builder/e2e/run-local.sh", diff --git a/packages/protocol/scripts/restamp-fixtures.ts b/packages/protocol/scripts/restamp-fixtures.ts new file mode 100644 index 000000000000..e25fe9214732 --- /dev/null +++ b/packages/protocol/scripts/restamp-fixtures.ts @@ -0,0 +1,77 @@ +/** + * Re-stamp the `bundleVersion` of every fixture bundle to the value computed + * from THIS checkout's schema. + * + * Why: `bundleVersion` is a contract hash derived from the schema + * (see ./src/schema.ts). This fork's self-hosting patches change the schema, so + * the hash differs from upstream's — but the committed fixtures under + * `fixtures/*` are generated by upstream and carry upstream's value. That makes + * `fixtures.test.ts` fail on every fork build. Rather than re-sync fixtures + * against a builder (needs infra), we deterministically overwrite just the + * `bundleVersion` field. The fixture data still parses under the fork schema + * (the test's `publishedProjectBundle.parse` keeps guarding that), so only the + * stamp needs refreshing. + * + * Usage: + * pnpm fixtures:restamp # rewrite stale stamps in place + * pnpm fixtures:restamp --check # report drift and exit non-zero (CI) + * + * The rewrite is a surgical text replace of the `bundleVersion` value only, so + * the rest of each data.json keeps its exact (oxfmt) formatting. + */ +import { readdirSync, readFileSync, writeFileSync, existsSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { bundleVersion } from "../src/schema"; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const fixturesRoot = resolve(scriptDir, "../../../fixtures"); +const check = process.argv.includes("--check"); + +const target = JSON.stringify(bundleVersion); +const pattern = /("bundleVersion"\s*:\s*)("[^"]*"|\d+)/; + +let drift = 0; +let updated = 0; + +for (const entry of readdirSync(fixturesRoot, { withFileTypes: true })) { + if (entry.isDirectory() === false) { + continue; + } + const file = join(fixturesRoot, entry.name, ".webstudio", "data.json"); + if (existsSync(file) === false) { + continue; + } + const text = readFileSync(file, "utf8"); + const match = text.match(pattern); + if (match === null) { + continue; + } + if (match[2] === target) { + continue; + } + drift += 1; + if (check) { + console.error(`drift: ${entry.name} has ${match[2]}, expected ${target}`); + continue; + } + writeFileSync(file, text.replace(pattern, `$1${target}`)); + updated += 1; + console.info(`re-stamped ${entry.name} → ${target}`); +} + +if (check) { + if (drift > 0) { + console.error( + `${drift} fixture bundle(s) out of date. Run: pnpm fixtures:restamp` + ); + process.exit(1); + } + console.info(`fixtures bundleVersion up to date (${target})`); +} else { + console.info( + updated === 0 + ? `fixtures already current (${target})` + : `re-stamped ${updated} fixture bundle(s) → ${target}` + ); +}