ci(examples): build every starter with its own build script (DEV-2737) - #302
Merged
Conversation
Nothing in CI ran a starter's own `build`, so the type-checking half of
`examples/` was uncovered. ci.yml's jobs are all `working-directory:
runner`; e2e-starter-matrix.yml boots a dev server and skips even a
workspace build; import-starters.yml only ever runs `pnpm install
--lockfile-only`. That gap is how DEV-2727 and DEV-2730 shipped.
examples-build.yml mirrors server-examples-build.yml: a discover job
emits the 16 starters from the checkout, a frozen-lockfile lane guards
starter edits on `paths: examples/**`, and a weekly lane moves
Handsontable to the current release to detect upstream type tightening
the day it lands.
Three design points that are easy to get wrong, all measured rather than
assumed:
The oracle is the starter's own `package.json` build, not the generated
bucket artifact. The bucket looks more faithful — it is what ships — but
the only product path that builds (share.ts) passes the command through
`snapshotBuildCommand`, which strips a leading tsc / tsc -b / vue-tsc.
Building what ships would reproduce the stripping and stay green on the
whole class of defect.
The build runs outside the repo tree. With the pre-DEV-2730
tsconfig.node.json restored, `examples/base-web` emits dist in-tree and
fails `TS2688` from a copy under a clean parent — same starter, same
config. An in-tree job would have missed it.
The canary lane uses `pnpm update --latest`, not `pnpm add <pkg>@latest`.
Every starter's committed spec is already "latest", against which `add`
is a no-op: on a copy whose lock pinned 18.0.0 with 18.1.0 on the
registry it reported "Already up to date" and left 18.0.0 installed. A
canary that rebuilds the pinned version is worse than none, so the lane
also asserts the resolved version equals npm's dist-tag — printing it
proves nothing, since a silent no-op prints a plausible number.
Also fixes a drift the mapping turned up: BUILD_CONFIG's vue entry was
`vite build` while examples/vue builds with `run-p type-check
"build-only {@}" --`, so the vue-tsc pass existed in the starter and
nowhere in our config. Declared as `vue-tsc --build --force && vite
build`, which `snapshotBuildCommand` reduces to exactly `vite build` —
container behaviour is unchanged — and pinned by
build-command-drift.test.mjs as a property rather than string equality,
since the one framework an equality allowlist would have to exempt is
the defect itself.
Coverage is partial and the workflow header says so: ant-design,
javascript and react-js have no TS surface, astro ships no `astro
check`, and nuxt cannot type-check without adding vue-tsc. remix is
covered through its own `typecheck` script.
Verified: all 16 starters build green out of tree at the frozen
resolution; the two reintroduced defects (TS2688 for DEV-2730, TS2322 on
the GridSettings holder for DEV-2727) each fail with no output emitted;
the output-presence guard exits 1 on an empty build; the dist-tag
assertion fails on the no-op install and passes after `update --latest`;
969 unit tests pass, and the drift test is red before this fix and green
after.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes DEV-2737.
The gap
No workflow ran a starter's own
build, so the type-checking half ofexamples/was uncovered.ci.yml's jobs are allworking-directory: runnerand never enterexamples/*;e2e-starter-matrix.ymlbootsFRAMEWORK_DEV.cmd, a dev server, and explicitly skips even a workspace build;import-starters.ymlonly ever runspnpm install --lockfile-only, which resolves without building. That is how DEV-2727 (18.1.0 tightened a public type,examples/angularstopped compiling, found by a person opening the demo after release) and DEV-2730 (TS2688inexamples/base-weboutside the repo tree) both shipped.server-examples-build.ymlalready closed the same gap forserver-examples/when DEV-2731 landed. This is theexamples/half, and that workflow is the template: adiscoverjob emits the 16 starters from the checkout, a frozen-lockfile lane guards starter edits onpaths: examples/**, and a weekly lane moves Handsontable to the current release.Three design points, all measured rather than assumed
The oracle is the starter's own
package.jsonbuild, not the generated bucket artifact. The bucket looks like the more faithful target — it is what ships — but the only product path that runs a build (share.ts:266) passes the command throughsnapshotBuildCommand, which strips a leadingtsc/tsc -b/vue-tsc. Building what ships would reproduce the stripping and stay green on the entire class of defect this workflow exists to catch. Worth flagging because the ticket's own text proposed the bucket.The build runs outside the repo tree. With the pre-DEV-2730
tsconfig.node.jsonrestored,examples/base-webemitsdistin-tree and failsTS2688from a copy under a clean parent — same starter, same config, because atypesentry resolves through this repo's untracked rootnode_modules/@types/node. An in-tree job would have missed DEV-2730 entirely.The canary lane uses
pnpm update --latest, notpnpm add <pkg>@latest. Every starter's committed spec is already"latest", against whichaddis a no-op: on a copy whose lock pinned 18.0.0 with 18.1.0 on the registry, it reported "Already up to date" and left 18.0.0 installed. A canary that rebuilds the pinned version forever is worse than no canary, so the lane also asserts the resolved version equals npm's dist-tag — printing the version proves nothing, since a silent no-op prints a plausible number.Also in here: a real drift in
BUILD_CONFIGBUILD_CONFIG.vue.buildCommandwasvite build, whileexamples/vuebuilds withrun-p type-check "build-only {@}" --. Thevue-tscpass existed in the starter and nowhere in our config — dropped inconfig/frameworks.json, upstream of any stripping.Declared now as
vue-tsc --build --force && vite build, whichsnapshotBuildCommandreduces to exactlyvite build, so container behaviour is byte-identical to today. Deliberately not the starter's literalrun-pform: that string does not match the strip regex, so the share path would start running a type-check in ephemeral containers — the thing the strip exists to prevent — and would neednpm-run-all2present there.build-command-drift.test.mjspins it as a property (BUILD_CONFIGnever drops a type-check the starter's own build performs) rather than string equality, because the one framework an equality allowlist would have to exempt is the defect itself. The property also letsnuxt's intentionalnuxt generatedivergence through on its own merit, with no allowlist at all.Coverage is partial, and the header says so
10 of 16 starters type-check on
build;remixis covered through its owntypecheckscript, which the workflow runs when a starter ships one. Genuinely uncovered:ant-design,javascript,react-js(no TS surface at all — zero tsconfigs),astro(ships noastro check), andnuxt, which cannot type-check without addingvue-tscto it. Worth doing, but not in this workflow's scope.All 16 are built regardless, not just the type-checking ones: a curated list would drift, and the other six still get their bundler and config exercised for the price of one short job.
This does not duplicate
pipeline/starter-type-roots.test.mjs— it complements it. That test is the fast static half and declares two limits for itself (unfollowedextendschains, top-level tsconfigs only, soexamples/nuxt/server/tsconfig.jsonis never inspected). A real build covers both empirically for all 16 starters, while the static test stays green-on-PR in seconds and rides the cherry-pick ontoprod-examples/*.Verification
Every lane has a must-fail proof, because a job that cannot go red is decorative.
remix typecheckexits 0tsconfig.node.json)TS2688: Cannot find type definition file for 'node', nodistdist— the out-of-tree requirement, measuredGridSettingsannotation dropped)TS2322: … not assignable to type 'GridSettings'in the component template, nodistpnpm addupdate --latestpnpm testpnpm typecheckThe one thing not verified locally is the workflow itself on a real runner — this PR touches a path in its own trigger filter, so the 16
buildjobs run here.build-latestis non-PR only and needs aworkflow_dispatchor the Monday 05:00 UTC cron.Two notes on runner specifics, both deliberate: no
cache: pnpm(the restore runs before the extraction and resolves the store relative to the checkout, so an install underRUNNER_TEMPcan miss it with no signal — 16 cold installs is the cheaper trade), andtarrather thanrsync, which is not guaranteed on theubuntu-latestimage.Ticket corrections
Three premises in DEV-2737 did not survive contact, and the description should be updated:
3aa26d22changed zero tsconfigs; all sevenserver-examplesAngular frontends already carry"types": []. It was 18.1 type tightening.build, not 11.remix vite:builddoes not, which is why it ships a separatetypecheckscript.server-examples/has had its own build workflow since DEV-2731.🤖 Generated with Claude Code
Note
Low Risk
Primarily new CI gates and metadata alignment; share-time container builds stay unchanged because vue-tsc is still stripped before execution.
Overview
Adds
.github/workflows/examples-build.ymlso everyexamples/*starter with abuildscript is installed and built in CI—something existing workflows never did. A discover job auto-matrices starters;buildruns on PRs (examples/**) with frozen lockfile, copies each starter outside the repo tree (so ancestornode_modulescannot mask missing@types), runspnpm build, optionaltypecheck/type-checkscripts, and asserts Handsontable resolves and output dirs are non-empty.build-latest(schedule/dispatch only) bumps Handsontable packages viapnpm update --latestand asserts the resolved version matches npm’s dist-tag.Aligns Vue
buildCommandinframeworks.json/ generated catalog andBUILD_CONFIGwith the starter’s type-check (vue-tsc --build --force && vite build); share snapshots still run onlyvite buildaftersnapshotBuildCommandstrips the prefix. Addsbuild-command-drift.test.mjssoBUILD_CONFIGcannot drop type-check steps that starters perform, and documents the workflow inTESTING.md.Reviewed by Cursor Bugbot for commit b86c0b4. Bugbot is set up for automated code reviews on this repo. Configure here.