diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d5d3d25c..807e370ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] ### Added +- **cli — `meta init` scaffolds owned codegen generators (ADR-0034 scaffold-and-own, step 2):** + `meta init` now copies the four codegen reference templates (step 1) into the + consumer repo at `codegen/generators/{entity,queries,routes,barrel}.ts` and + scaffolds `metaobjects.config.ts` to import those **local** copies, so `meta gen` + runs from generators the consumer owns and edits — not from the package. Each + generator is written only if absent, so re-running `meta init --force` never + clobbers a hand-edited generator (mirrors the existing config.ts preservation). + codegen-ts gains a small reference-template reader the CLI uses to read the + shipped assets (`resolveReferenceRoot` / `readReferenceTemplate` / + `REFERENCE_GENERATOR_NAMES`, exported from `@metaobjectsdev/codegen-ts`). - **codegen-ts — reference template library (ADR-0034 scaffold-and-own, step 1):** new in-repo, copyable reference generators under `src/reference/` (`entity` / `queries` / `routes` / `barrel`) — self-contained starting points a @@ -23,6 +33,13 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm `renderUpdateFn`, `renderDeleteByIdFn`, `getPkInfo`). (`meta init` scaffolding, generator-export deprecation, and the guidance rewrite are later steps.) +### Deprecated +- **codegen-ts — `@metaobjectsdev/codegen-ts/generators` factory re-exports + (ADR-0034 scaffold-and-own, step 2):** importing `entityFile` / `queriesFile` / + `routesFile` / `barrel` from the package `/generators` export is deprecated in + favor of the owned local copies `meta init` scaffolds. The export still works + (pre-GA latitude) but will be removed in a future major — own a copy instead. + ### Fixed - **cli — `meta init` gitignore hardening:** the scaffolded `.metaobjects/.gitignore` previously ignored only `.gen-state/`, so a diff --git a/CLAUDE.md b/CLAUDE.md index 62d73cea2..5b566d221 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -144,7 +144,11 @@ A user's `metaobjects.config.ts`: ```ts import { defineConfig } from "@metaobjectsdev/cli"; -import { entityFile, queriesFile, routesFile, barrel } from "@metaobjectsdev/codegen-ts/generators"; +// Owned generators scaffolded by `meta init` (ADR-0034 scaffold-and-own). +import { entityFile } from "./codegen/generators/entity"; +import { queriesFile } from "./codegen/generators/queries"; +import { routesFile } from "./codegen/generators/routes"; +import { barrel } from "./codegen/generators/barrel"; import { formFile } from "@metaobjectsdev/codegen-ts-react"; import { tanstackQuery, tanstackGrid } from "@metaobjectsdev/codegen-ts-tanstack"; @@ -259,13 +263,17 @@ interface Generator { Helpers `perEntity()` and `oncePerRun()` cover the common "file per entity" / "one-shot" cases. -**Built-in factories**: `entityFile`, `queriesFile`, `routesFile`, `formFile`, `barrel` — re-exported from `@metaobjectsdev/codegen-ts/generators`. +**Built-in factories**: `entityFile`, `queriesFile`, `routesFile`, `formFile`, `barrel`. Per ADR-0034 (scaffold-and-own), `meta init` copies the `entityFile`/`queriesFile`/`routesFile`/`barrel` reference templates into the consumer repo at `codegen/generators/*.ts` and the scaffolded config imports those owned local copies; importing them from `@metaobjectsdev/codegen-ts/generators` still works but is **deprecated** (removal in a future major). **User wiring** (`metaobjects.config.ts`): ```ts import { defineConfig } from "@metaobjectsdev/cli"; -import { entityFile, queriesFile, routesFile, barrel } from "@metaobjectsdev/codegen-ts/generators"; +// Owned generators scaffolded by `meta init` (ADR-0034 scaffold-and-own). +import { entityFile } from "./codegen/generators/entity"; +import { queriesFile } from "./codegen/generators/queries"; +import { routesFile } from "./codegen/generators/routes"; +import { barrel } from "./codegen/generators/barrel"; export default defineConfig({ outDir: "packages/database/src/generated", @@ -480,7 +488,7 @@ These are the load-bearing principles that have emerged through implementation. ## Useful commands ``` -meta init # scaffold metaobjects/, .metaobjects/, metaobjects.config.ts, .gitignore +meta init # scaffold metaobjects/, .metaobjects/, codegen/generators/, metaobjects.config.ts, .gitignore meta gen [...] # codegen: render templates → format → three-way merge → write meta gen --dry-run # preview without writing meta gen --watch # re-run on metadata file changes diff --git a/docs/features/codegen-concepts.md b/docs/features/codegen-concepts.md index 95afb978d..3838ad839 100644 --- a/docs/features/codegen-concepts.md +++ b/docs/features/codegen-concepts.md @@ -27,6 +27,14 @@ per-project. Don't fight a black-box generator; own a starting point and edit it Choosing and adapting a starting template is a **human/Claude judgment call**, not a CLI flag. (See ADR-0034.) +For a running start, `meta init` scaffolds a sensible default set — +`codegen/generators/{entity,queries,routes,barrel}.ts`, copied from the reference +templates — and wires `metaobjects.config.ts` to import them locally, so `meta gen` +runs from generators you own from the first run. Each file is written only if absent, +so re-running `meta init --force` never clobbers a hand-edited generator. Importing +those factories from `@metaobjectsdev/codegen-ts/generators` instead is **deprecated** +and slated for removal in a future major — own the local copy. + ## 3. Authoring mechanisms — and their tradeoffs There is no single right way to author a generator. The menu, and when to reach for diff --git a/docs/ports/typescript-client.md b/docs/ports/typescript-client.md index de5894b58..10b3746c9 100644 --- a/docs/ports/typescript-client.md +++ b/docs/ports/typescript-client.md @@ -409,7 +409,11 @@ importBase?, outputLayout?, dbImport? }`. ```ts // metaobjects.config.ts (multi-target) import { defineConfig } from "@metaobjectsdev/cli"; -import { entityFile, queriesFile, routesFile, barrel } from "@metaobjectsdev/codegen-ts/generators"; +// Owned generators scaffolded by `meta init` (ADR-0034 scaffold-and-own). +import { entityFile } from "./codegen/generators/entity"; +import { queriesFile } from "./codegen/generators/queries"; +import { routesFile } from "./codegen/generators/routes"; +import { barrel } from "./codegen/generators/barrel"; import { formFile } from "@metaobjectsdev/codegen-ts-react"; import { tanstackQuery, tanstackGrid } from "@metaobjectsdev/codegen-ts-tanstack"; @@ -579,7 +583,11 @@ Metadata (same `Author` entity as the React examples above): ```ts import { defineConfig } from "@metaobjectsdev/cli"; -import { entityFile, queriesFile, routesFile, barrel } from "@metaobjectsdev/codegen-ts/generators"; +// Owned generators scaffolded by `meta init` (ADR-0034 scaffold-and-own). +import { entityFile } from "./codegen/generators/entity"; +import { queriesFile } from "./codegen/generators/queries"; +import { routesFile } from "./codegen/generators/routes"; +import { barrel } from "./codegen/generators/barrel"; import { angularServiceFile, angularFormFile, diff --git a/docs/ports/typescript.md b/docs/ports/typescript.md index db1546f68..a70b63188 100644 --- a/docs/ports/typescript.md +++ b/docs/ports/typescript.md @@ -26,18 +26,20 @@ Two config files, by design: - **`.metaobjects/config.json`** — JSON, static project state, parseable by non-TS tooling. -`meta init` scaffolds both, the `metaobjects/` source directory, and the -`.gitignore` entries for `.metaobjects/.gen-state/`. +`meta init` scaffolds both, the `metaobjects/` source directory, the owned +codegen generators at `codegen/generators/{entity,queries,routes,barrel}.ts` +(ADR-0034 scaffold-and-own — copied from the reference templates, yours to edit), +and the `.gitignore` entries for `.metaobjects/.gen-state/`. The scaffolded config +imports those local copies; `meta gen` runs from them, not from the package. ```ts // metaobjects.config.ts import { defineConfig } from "@metaobjectsdev/cli"; -import { - entityFile, - queriesFile, - routesFile, - barrel, -} from "@metaobjectsdev/codegen-ts/generators"; +// Owned generators scaffolded by `meta init` — yours to edit (ADR-0034). +import { entityFile } from "./codegen/generators/entity"; +import { queriesFile } from "./codegen/generators/queries"; +import { routesFile } from "./codegen/generators/routes"; +import { barrel } from "./codegen/generators/barrel"; export default defineConfig({ outDir: "src/generated", @@ -150,12 +152,12 @@ difference is the framework adapter the emitted code talks to. ```ts // metaobjects.config.ts import { defineConfig } from "@metaobjectsdev/cli"; -import { - entityFile, - queriesFile, - routesFileHono, - barrel, -} from "@metaobjectsdev/codegen-ts/generators"; +// Owned generators scaffolded by `meta init` (ADR-0034 scaffold-and-own). +import { entityFile } from "./codegen/generators/entity"; +import { queriesFile } from "./codegen/generators/queries"; +import { barrel } from "./codegen/generators/barrel"; +// Hono routes have no reference template yet — still imported from the package. +import { routesFileHono } from "@metaobjectsdev/codegen-ts/generators"; export default defineConfig({ outDir: "src/generated", diff --git a/docs/recipes/extending-metaobjects-with-providers.md b/docs/recipes/extending-metaobjects-with-providers.md index 64dd91966..e125633d2 100644 --- a/docs/recipes/extending-metaobjects-with-providers.md +++ b/docs/recipes/extending-metaobjects-with-providers.md @@ -88,7 +88,11 @@ Notable bits: ```ts // metaobjects.config.ts import { defineConfig } from "@metaobjectsdev/cli"; -import { entityFile, queriesFile, barrel, promptRender } from "@metaobjectsdev/codegen-ts/generators"; +// Owned generators scaffolded by `meta init` (ADR-0034 scaffold-and-own). +import { entityFile } from "./codegen/generators/entity"; +import { queriesFile } from "./codegen/generators/queries"; +import { barrel } from "./codegen/generators/barrel"; +import { promptRender } from "@metaobjectsdev/codegen-ts/generators"; import { exampleProvider } from "./src/codegen/example-provider"; export default defineConfig({ diff --git a/docs/recipes/wiring-generated-queries.md b/docs/recipes/wiring-generated-queries.md index 71251dfc7..88a0af36d 100644 --- a/docs/recipes/wiring-generated-queries.md +++ b/docs/recipes/wiring-generated-queries.md @@ -267,7 +267,10 @@ Then wire it into `metaobjects.config.ts`: ```ts import { defineConfig } from "@metaobjectsdev/cli"; -import { entityFile, queriesFile, barrel } from "@metaobjectsdev/codegen-ts/generators"; +// Owned generators scaffolded by `meta init` (ADR-0034 scaffold-and-own). +import { entityFile } from "./codegen/generators/entity"; +import { queriesFile } from "./codegen/generators/queries"; +import { barrel } from "./codegen/generators/barrel"; import { honoRoutesFile } from "./metaobjects-routes-hono"; export default defineConfig({ diff --git a/server/java/integration-tests-kotlin/pom.xml b/server/java/integration-tests-kotlin/pom.xml index d5b22bbc8..b8671e06c 100644 --- a/server/java/integration-tests-kotlin/pom.xml +++ b/server/java/integration-tests-kotlin/pom.xml @@ -7,7 +7,7 @@ com.metaobjects metaobjects - 7.4.3-SNAPSHOT + 7.4.4-SNAPSHOT metaobjects-integration-tests-kotlin diff --git a/server/java/integration-tests/pom.xml b/server/java/integration-tests/pom.xml index c337ebee9..738551135 100644 --- a/server/java/integration-tests/pom.xml +++ b/server/java/integration-tests/pom.xml @@ -7,7 +7,7 @@ com.metaobjects metaobjects - 7.4.3-SNAPSHOT + 7.4.4-SNAPSHOT metaobjects-integration-tests diff --git a/server/typescript/packages/cli/README.md b/server/typescript/packages/cli/README.md index 51285c571..27833b3bb 100644 --- a/server/typescript/packages/cli/README.md +++ b/server/typescript/packages/cli/README.md @@ -56,7 +56,7 @@ Run schema ops from the compiled binary: ## Quick start ```bash -# 1. Scaffold metaobjects/ + .metaobjects/ + metaobjects.config.ts +# 1. Scaffold metaobjects/ + .metaobjects/ + codegen/generators/ + metaobjects.config.ts meta init # 2. Author entity metadata @@ -86,7 +86,9 @@ Running `meta` with no arguments prints a concise status line (whether a `metaob ### `meta init` -Scaffolds `metaobjects/` (visible entity declarations, with a placeholder `meta.common.json`), `.metaobjects/` (hidden tool state: `config.json`, `package.meta.json`, `AGENTS.md`, `CLAUDE.md`, `.gitignore`, `.gen-state/`), and `metaobjects.config.ts` at the repo root. +Scaffolds `metaobjects/` (visible entity declarations, with a placeholder `meta.common.json`), `.metaobjects/` (hidden tool state: `config.json`, `package.meta.json`, `AGENTS.md`, `CLAUDE.md`, `.gitignore`, `.gen-state/`), the **owned codegen generators** at `codegen/generators/{entity,queries,routes,barrel}.ts`, and `metaobjects.config.ts` at the repo root. + +The generators are copied from the codegen reference templates and are **yours to edit** (ADR-0034 scaffold-and-own); the scaffolded `metaobjects.config.ts` imports them locally, and `meta gen` runs from those local copies — not from the package. Each generator file is written only if absent, so re-running with `--force` never clobbers a hand-edited generator. Flags: - `--force` — overwrite scaffold files (memory records preserved) @@ -159,11 +161,14 @@ Flags: Two config files, by design: -**`metaobjects.config.ts`** (at repo root) — generator wiring and codegen knobs, type-checked TS: +**`metaobjects.config.ts`** (at repo root) — generator wiring and codegen knobs, type-checked TS. The generators are imported from the **owned local copies** that `meta init` scaffolded into `codegen/generators/` (ADR-0034 scaffold-and-own), not from the package: ```ts import { defineConfig } from "@metaobjectsdev/cli"; -import { entityFile, queriesFile, routesFile, barrel } from "@metaobjectsdev/codegen-ts/generators"; +import { entityFile } from "./codegen/generators/entity"; +import { queriesFile } from "./codegen/generators/queries"; +import { routesFile } from "./codegen/generators/routes"; +import { barrel } from "./codegen/generators/barrel"; export default defineConfig({ outDir: "packages/database/src/generated", @@ -175,6 +180,10 @@ export default defineConfig({ }); ``` +> Importing these generator factories from `@metaobjectsdev/codegen-ts/generators` +> still works but is **deprecated** (ADR-0034) — own a local copy instead. The +> package export will be removed in a future major. + ### Multiple output targets By default every generator writes to `outDir`. To route each generator's output @@ -183,7 +192,11 @@ concern — declare named **targets** and point generators at them with `target` ```ts import { defineConfig } from "@metaobjectsdev/cli"; -import { entityFile, queriesFile, routesFile, barrel } from "@metaobjectsdev/codegen-ts/generators"; +// Owned generators scaffolded by `meta init` (ADR-0034 scaffold-and-own). +import { entityFile } from "./codegen/generators/entity"; +import { queriesFile } from "./codegen/generators/queries"; +import { routesFile } from "./codegen/generators/routes"; +import { barrel } from "./codegen/generators/barrel"; import { formFile } from "@metaobjectsdev/codegen-ts-react"; import { tanstackQuery, tanstackGrid } from "@metaobjectsdev/codegen-ts-tanstack"; diff --git a/server/typescript/packages/cli/src/commands/init.ts b/server/typescript/packages/cli/src/commands/init.ts index 241212a60..90a0afb48 100644 --- a/server/typescript/packages/cli/src/commands/init.ts +++ b/server/typescript/packages/cli/src/commands/init.ts @@ -12,6 +12,11 @@ import { parseInitArgs } from "../lib/args.js"; import { log } from "../lib/log.js"; import { cliVersion } from "../lib/version.js"; import { findWranglerConfig, parseWranglerConfig } from "@metaobjectsdev/migrate-ts"; +import { readReferenceTemplate, REFERENCE_GENERATOR_NAMES } from "@metaobjectsdev/codegen-ts"; + +// ADR-0034 scaffold-and-own — `meta init` copies the codegen reference templates into +// the consumer's repo so they OWN them; metaobjects.config.ts imports them locally. +const OWNED_GENERATORS_DIR = "codegen/generators"; const META_COMMON_JSON = JSON.stringify( { @@ -46,13 +51,14 @@ const METAOBJECTS_GITIGNORE_BODY = `.gen-state/ function buildMetaobjectsConfigBody(dialect: "sqlite" | "postgres" | "d1" = "sqlite"): string { return `import { defineConfig } from "@metaobjectsdev/cli"; -import { - entityFile, - queriesFile, - routesFile, - // formFile, // opt-in: emit React form components - barrel, -} from "@metaobjectsdev/codegen-ts/generators"; +// Owned codegen generators (ADR-0034 scaffold-and-own). \`meta init\` copied these +// reference templates into ./codegen/generators/ — they are YOURS to edit, and +// \`meta gen\` runs from these local copies, not from the package. Read each file's +// header doc-block for what it emits and how to customize it. +import { entityFile } from "./codegen/generators/entity"; +import { queriesFile } from "./codegen/generators/queries"; +import { routesFile } from "./codegen/generators/routes"; +import { barrel } from "./codegen/generators/barrel"; export default defineConfig({ outDir: "src/generated", @@ -77,6 +83,7 @@ export default defineConfig({ const NEXT_STEPS = ` Initialized metaobjects/ + .metaobjects/ + metaobjects.config.ts +Codegen generators copied to codegen/generators/ — they're YOURS to edit (ADR-0034 scaffold-and-own). Next steps (when later sub-projects ship): meta ingest # propose entities from your existing TS code @@ -212,6 +219,27 @@ async function wireRootMemory(cwd: string, result: InitResult): Promise { } } +/** + * ADR-0034 — copy the codegen reference templates into the consumer's repo at + * `codegen/generators/.ts` so they own them. Each file is written only if absent, + * so a re-run with --force never clobbers a hand-edited generator. The scaffolded + * metaobjects.config.ts imports these local copies (not the package `/generators` export). + */ +async function writeOwnedGenerators(opts: InitOptions, result: InitResult): Promise { + const dir = join(opts.cwd, OWNED_GENERATORS_DIR); + await mkdir(dir, { recursive: true }); + for (const name of REFERENCE_GENERATOR_NAMES) { + const rel = `${OWNED_GENERATORS_DIR}/${name}.ts`; + const abs = join(dir, `${name}.ts`); + if (await fileExists(abs)) { + result.preserved.push(rel); + continue; + } + await writeFile(abs, readReferenceTemplate(name), "utf8"); + result.created.push(rel); + } +} + export async function init(opts: InitOptions): Promise { const result: InitResult = { created: [], preserved: [], warnings: [] }; const agentDir = join(opts.cwd, DEFAULT_METAOBJECTS_DIR); @@ -254,6 +282,7 @@ export async function init(opts: InitOptions): Promise { `.metaobjects/${PACKAGE_MANIFEST_FILE}`, ); result.created.push(".metaobjects/AGENTS.md", ".metaobjects/CLAUDE.md", ".claude/skills/metaobjects-*", AGENT_CONTEXT_MANIFEST_PATH); + for (const name of REFERENCE_GENERATOR_NAMES) result.created.push(`${OWNED_GENERATORS_DIR}/${name}.ts`); result.created.push("metaobjects.config.ts"); return result; } @@ -334,6 +363,10 @@ export async function init(opts: InitOptions): Promise { await writeAgentContext(opts, result); + // ADR-0034 — scaffold the OWNED codegen generators that metaobjects.config.ts imports + // locally. Done before the config so the import targets exist on first `meta gen`. + await writeOwnedGenerators(opts, result); + // Scaffold metaobjects.config.ts at the project root. Never overwrite if it exists. const forgeConfigPath = join(opts.cwd, "metaobjects.config.ts"); if (!(await fileExists(forgeConfigPath))) { diff --git a/server/typescript/packages/cli/test/unit/init-refresh-docs.test.ts b/server/typescript/packages/cli/test/unit/init-refresh-docs.test.ts index a6568838c..41605dc9e 100644 --- a/server/typescript/packages/cli/test/unit/init-refresh-docs.test.ts +++ b/server/typescript/packages/cli/test/unit/init-refresh-docs.test.ts @@ -76,7 +76,10 @@ describe("metaobjects.config.ts wiring still scaffolded", () => { await init({ cwd }); const configTs = readFileSync(join(cwd, "metaobjects.config.ts"), "utf8"); expect(configTs).toContain("defineConfig"); - expect(configTs).toContain("@metaobjectsdev/codegen-ts/generators"); + // ADR-0034 — the scaffolded config imports the OWNED local generators, never the + // deprecated package `/generators` export. + expect(configTs).toContain('from "./codegen/generators/entity"'); + expect(configTs).not.toContain("@metaobjectsdev/codegen-ts/generators"); }); }); diff --git a/server/typescript/packages/cli/test/unit/init-scaffold-config.test.ts b/server/typescript/packages/cli/test/unit/init-scaffold-config.test.ts index 71e4f1f59..b4726efde 100644 --- a/server/typescript/packages/cli/test/unit/init-scaffold-config.test.ts +++ b/server/typescript/packages/cli/test/unit/init-scaffold-config.test.ts @@ -62,3 +62,48 @@ describe("meta init scaffolds metaobjects.config.ts", () => { expect(existsSync(join(tmp, "forge.config.ts"))).toBe(false); }); }); + +// ADR-0034 scaffold-and-own — `meta init` copies the codegen reference templates into +// the consumer repo (codegen/generators/*.ts), and the scaffolded config imports those +// OWNED local copies instead of the deprecated package `/generators` export. +describe("meta init scaffolds OWNED codegen generators (ADR-0034)", () => { + const GENERATORS = ["entity", "queries", "routes", "barrel"] as const; + + test("writes codegen/generators/{entity,queries,routes,barrel}.ts", async () => { + const result = await init({ cwd: tmp, quiet: true }); + for (const name of GENERATORS) { + const rel = `codegen/generators/${name}.ts`; + expect(existsSync(join(tmp, rel))).toBe(true); + expect(result.created).toContain(rel); + } + }); + + test("owned generators are the copyable reference templates (REFERENCE TEMPLATE header)", async () => { + await init({ cwd: tmp, quiet: true }); + const entity = readFileSync(join(tmp, "codegen/generators/entity.ts"), "utf-8"); + expect(entity).toContain("REFERENCE TEMPLATE"); + // They import the stable engine, never the deprecated `/generators` export. + expect(entity).toContain('from "@metaobjectsdev/codegen-ts"'); + expect(entity).not.toContain("@metaobjectsdev/codegen-ts/generators"); + }); + + test("the scaffolded config imports each owned generator locally", async () => { + await init({ cwd: tmp, quiet: true }); + const body = readFileSync(join(tmp, "metaobjects.config.ts"), "utf-8"); + expect(body).toContain('import { entityFile } from "./codegen/generators/entity"'); + expect(body).toContain('import { queriesFile } from "./codegen/generators/queries"'); + expect(body).toContain('import { routesFile } from "./codegen/generators/routes"'); + expect(body).toContain('import { barrel } from "./codegen/generators/barrel"'); + expect(body).not.toContain("@metaobjectsdev/codegen-ts/generators"); + }); + + test("re-init with --force preserves a hand-edited owned generator", async () => { + await init({ cwd: tmp, quiet: true }); + const entityPath = join(tmp, "codegen/generators/entity.ts"); + const edited = readFileSync(entityPath, "utf-8") + "\n// HAND-EDIT-SENTINEL\n"; + writeFileSync(entityPath, edited); + const result = await init({ cwd: tmp, quiet: true, force: true }); + expect(readFileSync(entityPath, "utf-8")).toContain("HAND-EDIT-SENTINEL"); + expect(result.preserved).toContain("codegen/generators/entity.ts"); + }); +}); diff --git a/server/typescript/packages/codegen-ts/README.md b/server/typescript/packages/codegen-ts/README.md index ddbac2c74..4a5ddd286 100644 --- a/server/typescript/packages/codegen-ts/README.md +++ b/server/typescript/packages/codegen-ts/README.md @@ -72,13 +72,24 @@ dependency tree entirely: ```ts // metaobjects.config.ts import { defineConfig } from "@metaobjectsdev/cli"; -import { entityFile, queriesFile, barrel } from "@metaobjectsdev/codegen-ts/generators"; +import { entityFile } from "./codegen/generators/entity"; +import { queriesFile } from "./codegen/generators/queries"; +import { barrel } from "./codegen/generators/barrel"; export default defineConfig({ generators: [entityFile({ allowlists: false }), queriesFile(), barrel()], }); ``` +The `entityFile` / `queriesFile` / `routesFile` / `barrel` factories are imported +from the **owned local copies** that `meta init` scaffolds into +`codegen/generators/` (ADR-0034 scaffold-and-own). Importing them from +`@metaobjectsdev/codegen-ts/generators` still works but is **deprecated** — own a +copy instead; the package export will be removed in a future major. The engine and +primitives (`runGen`, `perEntity`, `oncePerRun`, `RenderContext`, the loader and +render helpers) remain the stable, versioned import from `@metaobjectsdev/codegen-ts`, +and an owned generator imports them from there. + The client-side `Filter` type is still emitted regardless — it has zero runtime-ts dependency and consumers want it for typed client calls. Default is `true` for back-compat with existing projects. @@ -153,7 +164,12 @@ For every `template.output` declared in your metadata, `outputParser()` emits ```ts // metaobjects.config.ts import { defineConfig } from "@metaobjectsdev/cli"; -import { entityFile, queriesFile, barrel, promptRender, outputParser } from "@metaobjectsdev/codegen-ts/generators"; +// Owned generators scaffolded by `meta init` (ADR-0034 scaffold-and-own). +import { entityFile } from "./codegen/generators/entity"; +import { queriesFile } from "./codegen/generators/queries"; +import { barrel } from "./codegen/generators/barrel"; +// Prompt/output generators have no reference template yet — package import. +import { promptRender, outputParser } from "@metaobjectsdev/codegen-ts/generators"; export default defineConfig({ generators: [entityFile(), queriesFile(), barrel(), promptRender(), outputParser()], diff --git a/server/typescript/packages/codegen-ts/src/generators/index.ts b/server/typescript/packages/codegen-ts/src/generators/index.ts index 31da48c7a..09c51bd97 100644 --- a/server/typescript/packages/codegen-ts/src/generators/index.ts +++ b/server/typescript/packages/codegen-ts/src/generators/index.ts @@ -1,8 +1,19 @@ +// ADR-0034 scaffold-and-own — these built-in generator factories remain the engine's +// internal composers, but importing them from `@metaobjectsdev/codegen-ts/generators` +// into a consumer's `metaobjects.config.ts` is DEPRECATED. The recommended path is to +// own copyable reference templates in your repo (`meta init` scaffolds them into +// `codegen/generators/*.ts`) and import those locally. This package export will be +// removed in a future major. See spec/decisions/ADR-0034-codegen-scaffold-and-own.md. + +/** @deprecated ADR-0034 — own a copy instead: `import { entityFile } from "./codegen/generators/entity"` (scaffolded by `meta init`). */ export { entityFile, type EntityFileOpts } from "./entity-file.js"; +/** @deprecated ADR-0034 — own a copy instead: `import { queriesFile } from "./codegen/generators/queries"` (scaffolded by `meta init`). */ export { queriesFile, type QueriesFileOpts } from "./queries-file.js"; export { callableFile, type CallableFileOpts } from "./callable-file.js"; +/** @deprecated ADR-0034 — own a copy instead: `import { routesFile } from "./codegen/generators/routes"` (scaffolded by `meta init`). */ export { routesFile, type RoutesFileOpts } from "./routes-file.js"; export { routesFileHono, type RoutesFileHonoOpts } from "./routes-file-hono.js"; +/** @deprecated ADR-0034 — own a copy instead: `import { barrel } from "./codegen/generators/barrel"` (scaffolded by `meta init`). */ export { barrel, type BarrelOpts } from "./barrel.js"; /** @deprecated ADR-0021 D1 — neutral artifact owned by `meta docs` (ADR-0020); not part of the recommended `meta gen` suite. */ export { mermaidErDiagram, type MermaidErOptions } from "./mermaid-er.js"; diff --git a/server/typescript/packages/codegen-ts/src/index.ts b/server/typescript/packages/codegen-ts/src/index.ts index 8ba38fbbb..c8132c085 100644 --- a/server/typescript/packages/codegen-ts/src/index.ts +++ b/server/typescript/packages/codegen-ts/src/index.ts @@ -76,6 +76,12 @@ export { isTphSubtype, tphDiscriminatorPin } from "./templates/zod-validators.js export { renderTphDiscriminatorUnion } from "./templates/tph-discriminator.js"; export { hasWritableRdbSource } from "./source-detect.js"; export { renderSharedEnumsFile, SHARED_ENUMS_BASENAME } from "./templates/enums-file.js"; + +// ADR-0034 scaffold-and-own — reader for the copyable reference generators in +// `src/reference/*.ts`. `meta init` uses this to copy them into the consumer's repo. +export { resolveReferenceRoot, readReferenceTemplate, REFERENCE_GENERATOR_NAMES } from "./reference-templates.js"; +export type { ReferenceGeneratorName } from "./reference-templates.js"; + export { renderFindByIdFn, renderListFn, diff --git a/server/typescript/packages/codegen-ts/src/reference-templates.ts b/server/typescript/packages/codegen-ts/src/reference-templates.ts new file mode 100644 index 000000000..8bd7cbdde --- /dev/null +++ b/server/typescript/packages/codegen-ts/src/reference-templates.ts @@ -0,0 +1,49 @@ +// ADR-0034 scaffold-and-own — locate + read the copyable reference generators that +// live (as raw source assets) in `src/reference/*.ts`. `meta init` reads them through +// here and writes them into the consumer's repo (e.g. `codegen/generators/*.ts`), which +// the consumer then OWNS. The templates import only `@metaobjectsdev/codegen-ts` (the +// stable engine), so a copied file works verbatim with no rewriting. +// +// The reference files are excluded from the tsc build (they are scaffold assets, not +// package source — see tsconfig.json). They ship to npm via the package `files: ["src"]` +// entry, so they are present at `/src/reference/*.ts` in a published install. + +import { existsSync, readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +/** Basenames (no extension) of the copyable reference generators shipped in `src/reference/`. */ +export const REFERENCE_GENERATOR_NAMES = ["entity", "queries", "routes", "barrel"] as const; +export type ReferenceGeneratorName = (typeof REFERENCE_GENERATOR_NAMES)[number]; + +/** A directory is the reference root iff it holds the entity reference template. */ +function isReferenceRoot(dir: string): boolean { + return existsSync(join(dir, "entity.ts")); +} + +/** + * Resolve the `src/reference/` directory holding the copyable reference generators. + * Works in dev (this module runs from `src/`, templates at `./reference/`) and in a + * published install (this module runs from `dist/`, templates at `../src/reference/`, + * since `src/` ships alongside `dist/`). Walks up checking both layouts at each level. + */ +export function resolveReferenceRoot(): string { + let dir = dirname(fileURLToPath(import.meta.url)); + for (let i = 0; i < 8; i++) { + for (const candidate of [join(dir, "reference"), join(dir, "src", "reference")]) { + if (isReferenceRoot(candidate)) return candidate; + } + const parent = dirname(dir); + if (parent === dir) break; + dir = parent; + } + throw new Error( + "codegen-ts reference templates not found — looked for `reference/` and `src/reference/` " + + "walking up from the codegen-ts module.", + ); +} + +/** Read the raw source of one reference generator (e.g. `"entity"` → the text of `entity.ts`). */ +export function readReferenceTemplate(name: ReferenceGeneratorName): string { + return readFileSync(join(resolveReferenceRoot(), `${name}.ts`), "utf8"); +} diff --git a/server/typescript/packages/codegen-ts/test/reference-templates.test.ts b/server/typescript/packages/codegen-ts/test/reference-templates.test.ts new file mode 100644 index 000000000..6bde121de --- /dev/null +++ b/server/typescript/packages/codegen-ts/test/reference-templates.test.ts @@ -0,0 +1,33 @@ +// ADR-0034 — the reader that `meta init` uses to copy the reference generators into a +// consumer's repo. It must locate src/reference/ and return the raw template source. +import { describe, test, expect } from "bun:test"; +import { + resolveReferenceRoot, + readReferenceTemplate, + REFERENCE_GENERATOR_NAMES, +} from "../src/index.js"; +import { existsSync } from "node:fs"; +import { join } from "node:path"; + +describe("reference-templates reader", () => { + test("exposes the four copyable generator names", () => { + expect([...REFERENCE_GENERATOR_NAMES]).toEqual(["entity", "queries", "routes", "barrel"]); + }); + + test("resolveReferenceRoot points at a dir holding the templates", () => { + const root = resolveReferenceRoot(); + for (const name of REFERENCE_GENERATOR_NAMES) { + expect(existsSync(join(root, `${name}.ts`))).toBe(true); + } + }); + + test("readReferenceTemplate returns owned-template source (engine import, copy header)", () => { + for (const name of REFERENCE_GENERATOR_NAMES) { + const src = readReferenceTemplate(name); + expect(src).toContain("REFERENCE TEMPLATE"); + expect(src).toContain('from "@metaobjectsdev/codegen-ts"'); + // Never the deprecated package `/generators` export. + expect(src).not.toContain("@metaobjectsdev/codegen-ts/generators"); + } + }); +});