Skip to content

Commit 407202a

Browse files
committed
no-mistakes(review): fix perPackage effective-package grouping and validate template-spec format
1 parent 197aeeb commit 407202a

4 files changed

Lines changed: 37 additions & 6 deletions

File tree

server/typescript/packages/codegen-ts/src/generator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { MetaObject, MetaRoot } from "@metaobjectsdev/metadata";
22
import type { RenderContext } from "./render-context.js";
33
import type { ResolvedGenConfig } from "./metaobjects-config.js";
4+
import { effectivePackage } from "./docs-paths.js";
45

56
export interface EmittedFile {
67
/** Path relative to ResolvedGenConfig.outDir. */
@@ -107,7 +108,7 @@ export function perPackage(
107108
const matched = ctx.entities.filter(ctx.matches);
108109
const byPkg = new Map<string, MetaObject[]>();
109110
for (const e of matched) {
110-
const pkg = e.package ?? "";
111+
const pkg = effectivePackage(e) ?? "";
111112
let bucket = byPkg.get(pkg);
112113
if (bucket === undefined) { bucket = []; byPkg.set(pkg, bucket); }
113114
bucket.push(e);

server/typescript/packages/codegen-ts/src/template-codegen/template-spec.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
// The declarative JSON template-spec the CLI ports (C#/Python) consume, and TS
22
// can spread into `generators`. The JSON shape is the cross-port contract
33
// (SP-1 §4); a JSON Schema (template-spec.schema.json) sits beside it.
4-
import type { RenderFormat } from "@metaobjectsdev/render";
4+
import { ESCAPERS, type RenderFormat } from "@metaobjectsdev/render";
55
import type { Generator } from "../generator.js";
66
import { templateGenerator, type TemplateScope } from "../generators/template-generator.js";
77

88
const SCOPES = ["perEntity", "perPackage", "perModel"] as const satisfies readonly TemplateScope[];
9+
const FORMATS = Object.keys(ESCAPERS) as readonly RenderFormat[];
910

1011
export interface TemplateSpecEntry {
1112
name: string;
@@ -45,7 +46,14 @@ export function parseTemplateSpec(json: unknown): TemplateSpecFile {
4546
scope: raw.scope as TemplateScope,
4647
outputPattern: raw.outputPattern as string,
4748
};
48-
if (typeof raw.format === "string") entry.format = raw.format as RenderFormat;
49+
if (raw.format !== undefined) {
50+
if (typeof raw.format !== "string" || !FORMATS.includes(raw.format as RenderFormat)) {
51+
throw new Error(
52+
`template-spec generators[${i}]: format must be one of ${FORMATS.join(" | ")}, got '${String(raw.format)}'`,
53+
);
54+
}
55+
entry.format = raw.format as RenderFormat;
56+
}
4957
if (typeof raw.target === "string") entry.target = raw.target;
5058
return entry;
5159
});

server/typescript/packages/codegen-ts/test/template-codegen/scope-helpers.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ describe("perPackage", () => {
2727
return { path: `${pkg || "_"}/out.txt`, content: `${ents.length}` } as EmittedFile;
2828
});
2929
const files = await gen(ctx);
30-
const pkgs = [...new Set(ctx.entities.map((e) => e.package ?? ""))].sort();
31-
expect(seen).toEqual(pkgs);
32-
expect(files.length).toBe(pkgs.length);
30+
expect(seen).toEqual(["demo"]);
31+
expect(files.length).toBe(1);
32+
expect(ctx.entities.every((e) => e.package == null)).toBe(true);
3333
});
3434
});
3535

server/typescript/packages/codegen-ts/test/template-codegen/template-spec.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ describe("parseTemplateSpec", () => {
2727
test("rejects a non-object", () => {
2828
expect(() => parseTemplateSpec(null)).toThrow();
2929
});
30+
test("rejects an unknown format", () => {
31+
expect(() => parseTemplateSpec({
32+
generators: [{ name: "x", template: "t", scope: "perModel", outputPattern: "x", format: "xml-typo" }],
33+
})).toThrow(/format/i);
34+
});
35+
test("accepts every schema-enumerated format", () => {
36+
for (const fmt of ["text", "html", "xml", "csv", "json", "markdown", "spreadsheet"] as const) {
37+
const spec = parseTemplateSpec({
38+
generators: [{ name: "x", template: "t", scope: "perModel", outputPattern: "x", format: fmt }],
39+
});
40+
expect(spec.generators[0]!.format).toBe(fmt);
41+
}
42+
});
3043
});
3144

3245
describe("templateSpecToGenerators", () => {
@@ -42,4 +55,13 @@ describe("schema ↔ parser drift", () => {
4255
.properties.generators.items.properties.scope.enum;
4356
expect(scopeEnum).toEqual(["perEntity", "perPackage", "perModel"]);
4457
});
58+
test("schema enumerates the same formats the parser accepts", () => {
59+
const formatEnum = (schema as { properties: { generators: { items: { properties: { format: { enum: string[] } } } } } })
60+
.properties.generators.items.properties.format.enum;
61+
for (const fmt of formatEnum) {
62+
expect(() => parseTemplateSpec({
63+
generators: [{ name: "x", template: "t", scope: "perModel", outputPattern: "x", format: fmt }],
64+
})).not.toThrow();
65+
}
66+
});
4567
});

0 commit comments

Comments
 (0)