Skip to content

cli: generated code is only formatter-clean by approximation — x g hand-wraps instead of formatting what it writes #127

Closed

Description

@sebyx07

x g emits pre-formatted source by hand-wrapping calls, arrays, parameter lists and imports to a 100-column target. That approximation fails, and it cannot be made to work.

Measured

Errors from a scaffolded app's own biome check, on the slice a generator just wrote:

feature-name length biome errors
12 2
25 6
35 19
45 20 (18 format + 2 organizeImports)

biome check --write closes 100% of them in 52ms across 47 files, so cost is not the objection.

Why hand-wrapping cannot close it

Biome joins a short wrapped line back and breaks a long joined one. Any fixed emitted shape is therefore wrong for some feature-name length. The generator would have to reimplement the formatter to get this right — and packages/cli/src/templates/wrap.ts, permissionSet's wrapping and tableLine are three partial reimplementations that already exist.

The change, and why it did not land in the scaffold-first-run PR

Run the formatter over what was just written, in writeFiles — then delete all three hand-rolled wrappers.

Four reasons it was held back, all of which stand:

  1. Only coherent as writer + templates together. Landing the writeFiles half alone leaves three hand-rolled wrappers plus a formatter — two mechanisms for one job, strictly worse than either alone, and the exact duplication the change exists to remove.
  2. writeFiles currently guarantees decide-then-write, never half-write. A post-write shell-out is a third pass that can fail after 25 files have landed — a state the design explicitly rules out.
  3. "Skipped when the binary is absent" makes the guarantee conditional. Two apps, same command, different bytes, and x verify's lint red for one. Axiom 3 inverted.
  4. biome check --write applies safe lint fixes too, not only formatting. Generated code becomes a function of the app's biome.json and Biome's version rather than the generator's inputs — and every templates/*.test.ts asserts on pre-format bytes, so nothing would describe what actually lands.

Doing it properly means: the writer change, deleting wrap.ts / permissionSet's wrapping / tableLine, and re-pinning every template test that asserts wrapped output. That is a coherent PR of its own.

Related, already fixed

A name-dependent instance of the same class was cheap enough to close directly: templates/action.ts emitted import { can<Name>Write, <name>Tag }, unsorted for any feature starting a or b — ordinary short names, red lint. Fixed in the scaffold-first-run PR.

Also worth folding in

The repo's own Biome is ^2.4.15 (resolving to 2.5.5); the scaffold pins 2.5.8 (templates/scaffold-repo.ts, BIOME_VERSION). So the framework's gate judges generated code with a different binary than the generated app uses. Aligning them belongs with this work.

Activity

  1. developerz-ai commented on Aug 17, 2026

    @developerz-ai
    Contributor

    Scoped plan

    Problem: CLI generator hand-wraps code to 100 columns but Biome reformats differently, causing biome check errors in generated code.

    Acceptance criteria

    1. Generated code passes biome check without errors after generation
    2. All three hand-rolled wrappers (wrap.ts, permissionSet wrapping, tableLine) are deleted
    3. Template tests re-pinned to assert on post-format output
    4. writeFiles runs biome formatter after writing, with decide-then-write guarantee preserved
    5. Generated code is deterministic (not dependent on biome.json or biome version)

    Areas

    • packages/cli/src/templates/wrap.ts
    • packages/cli/src/templates/ (permissionSet wrapping)
    • packages/cli/src/templates/ (tableLine)
    • packages/cli/src/commands/generate/ (writeFiles function)
    • packages/cli/src/templates/*.test.ts (test assertions)

    Groups

    # Title Detail
    g1 Remove hand-rolled wrappers Delete wrap.ts, permissionSet wrapping, tableLine; re-pin template tests
    g2 Integrate biome format into writeFiles Post-write formatting, preserve decide-then-write guarantee
    g3 Align Biome version Use same version in scaffold as framework uses

    Confidence

    85% — Well-specified with measured data, clear problem, four documented reasons for prior deferral.

    🤖 Posted by developerz.ai — the maintainer agent, not a human.

  2. developerz-ai commented on Aug 17, 2026

    @developerz-ai
    Contributor

    Want to submit a fix? I'll scope it and flag it for the maintainer to pick up.

    🤖 Posted by developerz.ai — the maintainer agent, not a human.

  3. sebyx07 commented on Aug 20, 2026

    @sebyx07
    ContributorAuthor

    Fixed in #219 — as a build error, not a runtime format, and the measurement is why.

    Measured first: every file x g emits is already biome-clean today — 48 files, zero findings. Nothing is broken; what was missing is anything that keeps it that way.

    The runtime option, measured: biome check --write --stdin-file-path through exec.ts costs 239ms per file, and one x g resource emits 24 ts/tsx files — 5.7 seconds added to a command that is currently instant. Batching to one subprocess is fast enough (~250ms), but it puts a formatter inside x g, where a biome version skew or an unhappy app config turns "write eight files" into a failure the author cannot act on. That failure was hit twice while building this: biome refused to start because the sandbox had no VCS ignore file, and bunx biome in a temp directory re-downloaded biome and answered with the installer's output instead of the checker's.

    So: packages/cli/src/generate-format.test.ts. The defect is introduced here, when a template is edited; catching it here costs one subprocess, once (~1s), and nothing on a user's machine. That is axiom 3.

    Three things make it non-vacuous, and the third was only found by writing it:

    • it runs the repo's own biome binary by path (BIOME_BIN), not bunx — the version x verify's lint step runs;
    • the config lives outside the checked tree; a copy written beside the output was the one file the test reported on;
    • it asserts biome actually read the files (checked >= typescriptPaths(...)). The first version checked only the exit code, and a debug run proved it was reporting checked: 0 while passing. A vacuous test, caught by instrumenting rather than assuming.

    It covers x new's scaffold too — that is generated code as well, and it is clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions