codegen-patch: real .ts templates, AST splice anchors, watch-mode dev orchestration#9
Merged
Merged
Conversation
… splice points by AST The static body of every block spliced into Panda's `css.d.ts` / `css.mjs` now lives in a real `.ts` template under `src/templates/`, read at runtime by `codegen-templates.ts` and rendered by `codegen-patch-render.ts` via sentinel substitution. Editors highlight the templates as TypeScript and diffs to the emitted shape look like TS edits, not array-of-strings prose edits. Splice points are located by AST shape (`@babel/parser` → `magic-string`, both already runtime deps) instead of exact string match, so benign whitespace / quote-style / comment drift in Panda's emit no longer breaks the patch. Snapshot stability is enforced by piping the patched output through oxfmt before comparison; the AST-drift fixtures cover the new robustness. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ader works
Panda's config loader bundles consumers via esbuild and may emit CJS,
which strips `import.meta.url` to an empty string and breaks the
`fs.readFileSync(new URL("./templates/...", import.meta.url))` scheme.
Symptom in `vp run dev`:
[WARNING] "import.meta" is not available with the "cjs" output format
🐼 error [hooks] Error in plugin "__panda.config__": Invalid URL
Inline `src/templates/*.ts` into `src/templates.generated.ts` (gitignored)
via `scripts/generate-templates.mjs`. The loader imports the constants
directly — no runtime fs, no `import.meta.url`. Generation runs as a
vitest globalSetup before tests and chains in front of `vp pack` in the
build task. The post-pack `dist/templates/` copy is gone.
Editor TypeScript highlighting on the templates is preserved — they remain
real `.ts` files in `src/templates/`. Only the runtime path changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…the website Three coordinated changes so a single `vp run dev` from the workspace root picks up template / source edits in `@bearbones/vite` without a manual restart: 1. `@bearbones/vite`'s pack config gains a `buildStart` tsdown plugin (`inlineTemplatesPlugin`) that regenerates `src/templates.generated.ts` and registers each `src/templates/*.ts` file via `this.addWatchFile`. In `vp pack --watch`, editing a template now triggers a rebuild. Generation moves out of the `dev` / `build` shell prefix and into the bundler's lifecycle. The script exposes `generateTemplates()` for both vitest globalSetup and the plugin. 2. `@bearbones/vite`'s pack sets `clean: false`. tsdown's default cleans `dist/` before each (re)build — including the first build of a watch session, even if `dist/` already holds a fresh bundle. That open window broke the website's Vite/Panda config loader at startup with `Failed to resolve entry for package \"@bearbones/vite\"`. New builds atomically overwrite, so skipping the wipe is safe. 3. Root `dev` script now does `vp run --filter '@bearbones/*' build && vp run --filter website --filter @bearbones/vite --parallel dev`. The pre-step builds every workspace bearbones package in dep order (so the website's config loader has dist on first attempt); the parallel step then runs the website's dev server alongside bearbones-vite's watch. Pure vite-plus task orchestration, no shell tricks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…watch mode
Replaces the shell-chain orchestration (`vp run --filter '@bearbones/*' build && vp run --filter ... --parallel dev`) with a single `dev` task in the workspace-root `vite.config.ts`:
- `dependsOn: ["@bearbones/vite#build", "@bearbones/preset#build"]` pre-builds every workspace dist the website's `vite.config.ts` and `panda.config.ts` import at config-load time. vp respects task ordering (and dedups via the same dependsOn declared on `@bearbones/vite#build`).
- The command then launches both long-running watchers as explicit `vp run @bearbones/vite#dev` and `vp run website#dev` invocations in parallel, with `trap 'kill 0' INT TERM; ... & wait` so Ctrl-C cleanly stops the whole group.
`@bearbones/vite`'s pack now sets `clean: !process.argv.includes("--watch")`. Real one-shot builds still wipe stale output. `vp pack --watch` skips the wipe so its incremental builds atomically overwrite the bundle the dependsOn pre-build just produced — no race against the website's Vite/Panda config loader, no wait-for-deps wrapper.
Removes the prior `dev` script from the root `package.json` (the new task supersedes it; same name on both sides is rejected by vp's task-graph validator).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verified empirically that vp + the shell propagate Ctrl-C (process-group SIGINT) to both backgrounded children without an explicit `trap 'kill 0'`. Also confirmed the foreground `vp run website#dev` keeps the shell alive on its own; no `& wait` needed. Edge case the simpler form gives up: if the foreground watcher exits unexpectedly mid-session, the backgrounded one orphans — acceptable for two long-running dev watchers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…infinite watch loop) The previous plugin called \`generateTemplates()\` on every \`buildStart\` and wrote \`templates.generated.ts\` unconditionally. Since that file is imported by \`codegen-templates.ts\`, rolldown's import-graph watcher saw the write as a change → fired another \`buildStart\` → wrote again → infinite rebuild loop. Symptom in \`vp run dev\`: panda re-extracting CSS dozens of times per second forever. Fix the root cause instead of papering over with a write-content-equality check: gate regen behind a \`pendingGen\` flag that's flipped to true ONLY when \`watchChange\` fires for a real template source file under \`src/templates/\`. \`buildStart\` consults the flag and skips the regen otherwise. Edits to \`templates.generated.ts\` that the plugin itself just wrote are correctly ignored — they're not in the template-files list — so the loop is broken. Verified: \`vp pack --watch\` settles to 1 Rebuilt and stays there; touching a template triggers exactly 2 rebuilds (one for the source change, one as rolldown's import-graph picks up the regen-written file) then settles again. Full \`vp run dev\` orchestration: 1 Rebuilt total over 35s, zero panda \`ctx:change\` events in the steady state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
Summary
Two themes, one branch:
1. Refactor
codegen-patch.tsto read real.tstemplate files instead of joined string arrays. The static body of every block spliced into Panda'scss.d.ts/css.mjsnow lives in real TypeScript files under packages/bearbones-vite/src/templates/. Editors highlight them as TS, reviewers see meaningful diffs, and a small renderer fills in the two dynamic bits (utility-name union members + condition-vocabulary map entries) via sentinel substitution. Splice points in Panda's emit are located by AST shape (@babel/parser+magic-string, both already runtime deps) instead of exact-string match, so benign whitespace / quote-style / comment drift no longer breaks the patch. Snapshot stability is enforced by piping the patched output through oxfmt before comparison; new fixtures cover the AST-locator robustness.2. Make
vp run devfrom the workspace root pick up template / source edits in@bearbones/viteautomatically. A singledevtask in the root vite.config.ts declaresdependsOn: [\"@bearbones/vite#build\", \"@bearbones/preset#build\"]to pre-build every workspace dist the website's Vite/Panda config loaders import at config-load time, then launches@bearbones/vite#devandwebsite#devin parallel via explicitvp run pkg#taskinvocations.@bearbones/vite's pack usesclean: !process.argv.includes(\"--watch\")so real one-shot builds still wipe stale output but watch mode atomically overwrites the bundle the dependsOn pre-build just produced — no consumer race. Pure vite-plus task orchestration; no shell tricks.Key design choices (why this is a few commits, not one)
.tsfiles insrc/templates/are excluded from this package's tsconfig + lint config — they reference types that only exist in Panda's emitted directory layout. Each carries@ts-nocheckandeslint-disabledirectives above a// ---bearbones-template-emit-below---fence; the renderer strips everything up to the fence so those template-author directives never reach Panda's emitted output.fs.readFileSync(new URL('./templates/...', import.meta.url)), but Panda's config loader bundles consumers via esbuild and may emit CJS, which stripsimport.meta.urlto an empty string and throwsInvalid URL. Templates are now inlined intosrc/templates.generated.ts(gitignored) by scripts/generate-templates.mjs and the loader just imports the constants. The bundle has zero runtimeimport.meta.urlreferences.inlineTemplatesPluginin packages/bearbones-vite/vite.config.ts runs gen only whenwatchChangefires for a file insrc/templates/(tracked via apendingGenflag), not on everybuildStart. Without this, the plugin wrotetemplates.generated.tson every build — and since that file is imported bycodegen-templates.ts, rolldown's import-graph watcher saw the write as a change → infinite rebuild loop.@babel/parserandmagic-stringare both already runtime deps of this package (used bytransform.ts), so the AST swap added no new dep weight.What changed
packages/bearbones-vite/src/templates/{css-d-ts-injected,css-d-ts-marker,css-mjs-marker-stub}.tssrc/codegen-templates.ts,src/codegen-patch-render.ts,src/codegen-patch-ast.tsscripts/generate-templates.mjssrc/codegen-patch.ts(270 → 176 lines, orchestration only)tests/codegen-patch.test.ts(oxfmt normalization, AST-drift fixtures,loadTemplatecontract); snapshot re-baselinedpackages/bearbones-vite/vite.config.ts, rootvite.config.ts, rootpackage.json,tsconfig.jsonTest plan
vp testinpackages/bearbones-vite— 65/65 pass (3 test files)vp check— formatter, linter, type-check all cleanvp pack— bundle produced;dist/index.mjshas zero runtimeimport.meta.urlrefs (only doc-comment mentions)patchCssArtifactruns; marker block + condition map presentvp run devfrom worktree root — dev server up atlocalhost:5173, zeroFailed to resolveerrors, zero rebuild loops; bearbones-vite watch and panda steady statevp pack --watch— exactly 2 rebuilds, then steady (one for the source change, one as rolldown's import-graph picks up the regen-written file)