@metaobjectsdev/codegen-ts 0.7.0-rc.11 → 0.7.0-rc.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/generators/docs-data-builder.d.ts +16 -0
  2. package/dist/generators/docs-data-builder.d.ts.map +1 -0
  3. package/dist/generators/docs-data-builder.js +375 -0
  4. package/dist/generators/docs-data-builder.js.map +1 -0
  5. package/dist/generators/docs-data.d.ts +87 -0
  6. package/dist/generators/docs-data.d.ts.map +1 -0
  7. package/dist/generators/docs-data.js +13 -0
  8. package/dist/generators/docs-data.js.map +1 -0
  9. package/dist/generators/docs-file.d.ts +1 -1
  10. package/dist/generators/docs-file.d.ts.map +1 -1
  11. package/dist/generators/docs-file.js +50 -33
  12. package/dist/generators/docs-file.js.map +1 -1
  13. package/dist/generators/index.d.ts +3 -0
  14. package/dist/generators/index.d.ts.map +1 -1
  15. package/dist/generators/index.js +2 -0
  16. package/dist/generators/index.js.map +1 -1
  17. package/dist/generators/template-generator.d.ts +40 -0
  18. package/dist/generators/template-generator.d.ts.map +1 -0
  19. package/dist/generators/template-generator.js +43 -0
  20. package/dist/generators/template-generator.js.map +1 -0
  21. package/dist/index.d.ts +6 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +7 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/overwrite-policy.d.ts +39 -2
  26. package/dist/overwrite-policy.d.ts.map +1 -1
  27. package/dist/overwrite-policy.js +233 -13
  28. package/dist/overwrite-policy.js.map +1 -1
  29. package/dist/render-engine/framework-provider.d.ts +28 -0
  30. package/dist/render-engine/framework-provider.d.ts.map +1 -0
  31. package/dist/render-engine/framework-provider.js +99 -0
  32. package/dist/render-engine/framework-provider.js.map +1 -0
  33. package/dist/runner.d.ts +15 -1
  34. package/dist/runner.d.ts.map +1 -1
  35. package/dist/runner.js +43 -6
  36. package/dist/runner.js.map +1 -1
  37. package/dist/templates/docs-file.d.ts +5 -36
  38. package/dist/templates/docs-file.d.ts.map +1 -1
  39. package/dist/templates/docs-file.js +33 -447
  40. package/dist/templates/docs-file.js.map +1 -1
  41. package/package.json +5 -5
  42. package/src/generators/docs-data-builder.ts +467 -0
  43. package/src/generators/docs-data.ts +113 -0
  44. package/src/generators/docs-file.ts +64 -43
  45. package/src/generators/index.ts +15 -0
  46. package/src/generators/template-generator.ts +84 -0
  47. package/src/index.ts +33 -2
  48. package/src/overwrite-policy.ts +325 -14
  49. package/src/render-engine/framework-provider.ts +98 -0
  50. package/src/runner.ts +64 -6
  51. package/src/templates/docs-file.ts +36 -537
  52. package/templates/docs/entity-page.md.mustache +54 -0
@@ -0,0 +1,98 @@
1
+ // FrameworkTemplateProvider — resolves template refs (e.g. "docs/entity-page.md")
2
+ // against the codegen-ts package's `templates/` directory. Adopters who want
3
+ // to override a framework template create their own `templates/<ref>.mustache`
4
+ // file in their project; `ProviderChain` (below) consults the project first
5
+ // and falls back to the framework defaults.
6
+ //
7
+ // Decision D1 from the template-driven-codegen design — hybrid: framework
8
+ // ships defaults, adopters override by file-system convention.
9
+ //
10
+ // The framework Provider is filesystem-backed so it works identically whether
11
+ // codegen-ts runs from source (bun, dev) or from `dist/` (npm install). The
12
+ // `templates/` directory is included in the published tarball via
13
+ // package.json `files: ["dist", "src", "templates", ...]`.
14
+
15
+ import type { Provider } from "@metaobjectsdev/render";
16
+ import { existsSync, readFileSync } from "node:fs";
17
+ import { join, resolve, dirname } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+
20
+ /** Walk up from `start` until we find a `package.json` (i.e., the codegen-ts
21
+ * package root), then return `<pkg-root>/templates`. Works the same way
22
+ * from `src/render-engine/framework-provider.ts` (during dev) and
23
+ * `dist/render-engine/framework-provider.js` (after `npm install`) because
24
+ * in both cases we shipped at the package root. */
25
+ function findFrameworkTemplatesDir(start: string): string {
26
+ let dir = start;
27
+ for (let i = 0; i < 12; i++) {
28
+ const pkgJson = join(dir, "package.json");
29
+ if (existsSync(pkgJson)) {
30
+ return join(dir, "templates");
31
+ }
32
+ const parent = dirname(dir);
33
+ if (parent === dir) break;
34
+ dir = parent;
35
+ }
36
+ // Last resort — caller will get an unresolved-ref error when it tries to
37
+ // use a framework template that isn't on disk.
38
+ return join(start, "templates");
39
+ }
40
+
41
+ const SELF_DIR = (() => {
42
+ try {
43
+ return dirname(fileURLToPath(import.meta.url));
44
+ } catch {
45
+ return process.cwd();
46
+ }
47
+ })();
48
+
49
+ const FRAMEWORK_TEMPLATES_DIR = findFrameworkTemplatesDir(SELF_DIR);
50
+
51
+ /** Provider backed by an arbitrary on-disk template directory. References
52
+ * resolve as `<dir>/<ref>.mustache`. Used by both the framework default
53
+ * and adopter override paths. */
54
+ export class FileSystemProvider implements Provider {
55
+ constructor(private readonly root: string) {}
56
+ resolve(ref: string): string | undefined {
57
+ const path = join(this.root, `${ref}.mustache`);
58
+ if (!existsSync(path)) return undefined;
59
+ return readFileSync(path, "utf-8");
60
+ }
61
+ }
62
+
63
+ /** The framework defaults provider — resolves refs against codegen-ts's own
64
+ * `templates/` directory. */
65
+ export const frameworkTemplatesProvider: Provider = new FileSystemProvider(
66
+ FRAMEWORK_TEMPLATES_DIR,
67
+ );
68
+
69
+ /** Compose providers: first match wins. Adopters typically chain
70
+ * `[projectProvider, frameworkTemplatesProvider]` so their own templates
71
+ * override the framework defaults. */
72
+ export class ProviderChain implements Provider {
73
+ constructor(private readonly providers: readonly Provider[]) {}
74
+ resolve(ref: string): string | undefined {
75
+ for (const p of this.providers) {
76
+ const text = p.resolve(ref);
77
+ if (text !== undefined) return text;
78
+ }
79
+ return undefined;
80
+ }
81
+ }
82
+
83
+ /** Build a project-scoped Provider: layers an optional project `templates/`
84
+ * directory over the framework defaults. Returns just the framework provider
85
+ * when `projectRoot` is undefined / its `templates/` dir doesn't exist. */
86
+ export function projectProvider(projectRoot?: string): Provider {
87
+ if (projectRoot === undefined) return frameworkTemplatesProvider;
88
+ const projTemplates = resolve(projectRoot, "templates");
89
+ if (!existsSync(projTemplates)) return frameworkTemplatesProvider;
90
+ return new ProviderChain([
91
+ new FileSystemProvider(projTemplates),
92
+ frameworkTemplatesProvider,
93
+ ]);
94
+ }
95
+
96
+ /** Exposed for tests that want to inspect / clear the resolved framework
97
+ * templates directory (don't use outside tests). */
98
+ export const __frameworkTemplatesDirForTests = FRAMEWORK_TEMPLATES_DIR;
package/src/runner.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { join } from "node:path";
1
+ import { join, relative, resolve, isAbsolute } from "node:path";
2
+ import { tmpdir } from "node:os";
2
3
  import type { MetaData, MetaObject } from "@metaobjectsdev/metadata";
3
4
  import { MetaRoot } from "@metaobjectsdev/metadata";
4
5
  import type { Generator, GenContext, EmittedFile } from "./generator.js";
@@ -8,7 +9,12 @@ import type { ResolvedTarget } from "./import-path.js";
8
9
  import { buildPkMap } from "./pk-resolver.js";
9
10
  import { buildRelationMap } from "./relation-resolver.js";
10
11
  import { makeRenderContext } from "./render-context.js";
11
- import { decideAndWrite, type WriteResult, type MergeStrategy } from "./overwrite-policy.js";
12
+ import {
13
+ decideAndWrite,
14
+ type WriteResult,
15
+ type MergeStrategy,
16
+ type BaselineMode,
17
+ } from "./overwrite-policy.js";
12
18
 
13
19
  /** JS-identifier-shape only. Prevents filesystem traversal when metadata comes
14
20
  * from untrusted sources (e.g. MCP). Mirrors the guard in legacy generate.ts. */
@@ -21,16 +27,48 @@ export interface RunGenOpts {
21
27
  entityFilter?: string[];
22
28
  /** Overwrite strategy passed to decideAndWrite. Defaults to "overwrite". */
23
29
  mergeStrategy?: MergeStrategy;
30
+ /** Project root (used to derive the .gen-state/ snapshot directory and to
31
+ * key snapshots by project-relative output path). When omitted, falls back
32
+ * to process.cwd(). */
33
+ projectRoot?: string;
34
+ /** Override the snapshot directory location. Defaults to
35
+ * `<projectRoot>/.metaobjects/.gen-state/`. */
36
+ genStateDir?: string;
37
+ /** First-time-on-existing-file behavior. Defaults to "default" (write-if-
38
+ * different). "fresh" → overwrite and re-baseline (the `--baseline=fresh`
39
+ * CLI flag). */
40
+ baseline?: BaselineMode;
24
41
  }
25
42
 
26
43
  export interface RunGenResult {
27
44
  files: WriteResult[];
28
45
  warnings: string[];
46
+ /** Subset of `files` with status "conflict" — surfaced separately so the
47
+ * CLI can print the end-of-run summary. */
48
+ conflicts: WriteResult[];
29
49
  }
30
50
 
31
51
  export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
32
52
  const warnings: string[] = [];
33
53
  const strategy = opts.mergeStrategy ?? "overwrite";
54
+ const baseline = opts.baseline ?? "default";
55
+ // When projectRoot is not supplied we DON'T fall back to process.cwd() —
56
+ // that would leak .gen-state/ into whatever directory happens to be cwd
57
+ // (e.g. the package dir during a unit-test run). Instead, fall back to a
58
+ // process-isolated tmpdir, which gives the new-write semantics every call
59
+ // (the snapshot exists only for the current process). The CLI's
60
+ // `genCommand` always passes a real projectRoot, so this fallback only
61
+ // affects programmatic callers (tests, library embedding).
62
+ const projectRoot = opts.projectRoot !== undefined
63
+ ? (isAbsolute(opts.projectRoot) ? opts.projectRoot : resolve(opts.projectRoot))
64
+ : undefined;
65
+ const genStateDir = opts.genStateDir !== undefined
66
+ ? (isAbsolute(opts.genStateDir)
67
+ ? opts.genStateDir
68
+ : resolve(projectRoot ?? process.cwd(), opts.genStateDir))
69
+ : (projectRoot !== undefined
70
+ ? join(projectRoot, ".metaobjects", ".gen-state")
71
+ : join(tmpdir(), `meta-gen-state-${process.pid}`));
34
72
 
35
73
  // loadMemory now returns MetaRoot; guard here also covers callers that pass a
36
74
  // plain MetaData (e.g. test helpers that build trees programmatically).
@@ -50,7 +88,7 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
50
88
  ? "no object children match the provided entityFilter"
51
89
  : "root has no object children";
52
90
  warnings.push(`No entities to generate — ${reason}.`);
53
- return { files: [], warnings };
91
+ return { files: [], warnings, conflicts: [] };
54
92
  }
55
93
 
56
94
  const safeEntities: MetaObject[] = [];
@@ -64,7 +102,7 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
64
102
  safeEntities.push(entity);
65
103
  }
66
104
  if (safeEntities.length === 0) {
67
- return { files: [], warnings };
105
+ return { files: [], warnings, conflicts: [] };
68
106
  }
69
107
 
70
108
  // 2. Resolve targets + entity-module target.
@@ -163,9 +201,29 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
163
201
 
164
202
  // 5. Write phase.
165
203
  const writes: WriteResult[] = [];
204
+ const conflicts: WriteResult[] = [];
166
205
  for (const file of emitted) {
167
- const result = decideAndWrite(file.fullPath, file.content, strategy);
206
+ // Key the snapshot by project-relative path so multi-target projects keep
207
+ // distinct entries (e.g. `database/Post.ts` vs `web/Post.queries.ts`).
208
+ // Without an explicit projectRoot we let decideAndWrite derive a stable
209
+ // hash-of-path key — fine for ephemeral test runs.
210
+ const policyOpts: import("./overwrite-policy.js").DecideAndWriteOpts = {
211
+ strategy,
212
+ genStateDir,
213
+ baseline,
214
+ };
215
+ if (projectRoot !== undefined) {
216
+ policyOpts.outputRelPath = relative(projectRoot, file.fullPath);
217
+ }
218
+ const result = decideAndWrite(file.fullPath, file.content, policyOpts);
168
219
  writes.push(result);
220
+ if (result.status === "conflict") {
221
+ conflicts.push(result);
222
+ warnings.push(
223
+ `Merge conflict in ${file.fullPath}: resolve diff3 markers and re-run ` +
224
+ `'meta gen' to advance the canonical state.`,
225
+ );
226
+ }
169
227
  if (result.status === "refused") {
170
228
  warnings.push(
171
229
  `Refused to overwrite ${file.fullPath}: file exists without @generated header. ` +
@@ -174,5 +232,5 @@ export async function runGen(opts: RunGenOpts): Promise<RunGenResult> {
174
232
  }
175
233
  }
176
234
 
177
- return { files: writes, warnings };
235
+ return { files: writes, warnings, conflicts };
178
236
  }