Skip to content

feat(tooling): build-time codegen convention — perry.codegen + ajv/standalone (#1680) - #1801

Merged
proggeramlug merged 1 commit into
mainfrom
worktree-const-fold-fn-1679
May 25, 2026
Merged

feat(tooling): build-time codegen convention — perry.codegen + ajv/standalone (#1680)#1801
proggeramlug merged 1 commit into
mainfrom
worktree-const-fold-fn-1679

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Phase 2 of #1677 — build-time codegen convention (perry.codegen) + ajv/standalone

Closes #1680. Stacked on #1678 + #1679 (both merged); rebased onto current main.

What

Many libraries that would JIT a function at runtime (ajv, Prisma, Drizzle, kysely, Vue SFC) ship an eval-free build-time mode that emits plain source. Where that exists, an AOT compiler should consume the generated output instead of running a JIT — zero new evaluation infra, highest ROI (the issue's framing).

This adds a perry.codegen convention to the host package.json: an array of build commands Perry runs before module collection, then compiles the generated output natively.

{
  "perry": {
    "codegen": [
      { "label": "ajv validators", "command": "node scripts/generate-validators.mjs" }
    ]
  }
}
  • Each entry is a bare command string or { command, label }. Commands run in declaration order, cwd = the package.json's directory.
  • A failing step fails the build with its captured stdout/stderr.
  • Read only from the host package.json — never a dependency's — same trust boundary as compilePackages (a transitive dep can't smuggle in a build command).
  • --no-codegen / PERRY_SKIP_CODEGEN=1 skips the steps (reproducible / sandboxed builds with committed output).

Acceptance

  • Sample validates input via ajv/standalone, compiles, byte-parity vs node, no V8. test-files/test_ajv_standalone.ts imports a vendored real ajv/standalone validator and validates 8 inputs — byte-for-byte with node --experimental-strip-types; the binary links 0 V8 symbols (nm | grep -c v8:: → 0).
  • Convention documenteddocs/src/getting-started/project-config.md (#### codegen): the declaration format, the ajv worked example (gen script + import), and the same pattern for prisma generate / drizzle-kit introspect / kysely-codegen / Vue SFC. --no-codegen added to docs/src/cli/flags.md.
  • The hook end-to-end (declared step regenerates the validator, then Perry compiles it) was verified locally with ajv installed: an empty generated/ dir → the step runs → validator.cjs appears → compiles → correct output.

Implementation

CodegenStep + codegen_steps/codegen_dir on CompilationContext (types.rs) · parse perry.codegen (host_config.rs) · runner with 4 unit tests (codegen_steps.rs: runs/produces, skip, failure-diagnostics, no-op) · wired between config-load and module-collection (compile.rs) · --no-codegen flag.

Scope note (follow-ups filed)

The committed sample uses a structural schema (object shape / required / additionalProperties). While testing, ajv's denser generated code surfaced separate Perry codegen-correctness bugs — numeric minimum, integer % 1, per-property typeof of boolean/number values, and validate.errors (custom property on a function object). These are orthogonal to the build-convention work here and are filed as granular follow-up issues; they don't block consuming ajv/standalone output for structural validation.

Note: parity/compile-smoke are tag-gated (don't run on PRs) — the release sweep is the real Phase-2 gate before #1681 starts.

…andalone (#1680)

Phase 2 of #1677. Many libraries that would JIT a function at runtime
(`ajv`, Prisma, Drizzle, …) ship an eval-free build-time mode that emits
plain source. Where that exists, an AOT compiler should consume the
generated output instead of running a JIT — zero new evaluation infra,
highest ROI.

Adds a `perry.codegen` convention to the host package.json: an array of
build commands (bare string or `{ command, label }`) Perry runs before
module collection, in the package.json's directory, then compiles the
generated output natively. Read ONLY from the host package.json — never a
dependency's — same trust boundary as `perry.compilePackages` (a transitive
dep can't smuggle in a build command). A failing step fails the build with
its captured stdout/stderr. `--no-codegen` / `PERRY_SKIP_CODEGEN=1` skips
the steps for reproducible / sandboxed builds whose output is committed.

- types.rs: `CodegenStep { label, command }` + `codegen_steps` /
  `codegen_dir` on `CompilationContext`.
- host_config.rs: parse `perry.codegen` from the host package.json.
- codegen_steps.rs: the runner (sh -c in codegen_dir, bail on failure) +
  unit tests (runs/produces output, skip, failure-diagnostics, no-op).
- compile.rs: run the steps between config load and module collection.
- `--no-codegen` CLI flag (+ dev.rs / run/mod.rs construction sites).

Proof: `test-files/test_ajv_standalone.ts` imports a vendored
`ajv/standalone` validator (committed, real ajv output) and validates
inputs — byte-for-byte parity vs `node --experimental-strip-types`, and
the compiled binary links zero V8 symbols. The end-to-end hook (declared
step regenerates the validator, then Perry compiles it) was verified
locally with ajv installed.

Docs: project-config.md documents the convention with the ajv worked
example (gen script + import) and the same pattern for prisma generate /
drizzle-kit introspect / kysely-codegen / Vue SFC. flags.md gets
`--no-codegen`.

Scope note: the committed sample uses a structural schema (object shape /
required / additionalProperties). ajv's numeric (`minimum`), integer
(`% 1`), per-property `typeof`, and `validate.errors` (function-object
property) paths surfaced separate Perry codegen-correctness bugs, filed as
follow-ups — they are orthogonal to this build-convention work.
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Follow-up codegen bugs surfaced while testing ajv's denser output (orthogonal to this build-convention PR): #1802 (custom property on a function object reads back undefinedvalidate.errors) and #1803 (ajv per-property type guards miscompile). The committed structural sample is unaffected.

@proggeramlug
proggeramlug merged commit 22a7793 into main May 25, 2026
10 checks passed
@proggeramlug
proggeramlug deleted the worktree-const-fold-fn-1679 branch May 25, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(tooling): build-time codegen convention — ajv/standalone (+Prisma/Drizzle/Vue) AOT path

1 participant