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}` + ); +}