@metaobjectsdev/codegen-ts 0.9.0 → 0.10.0
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.
- package/README.md +1 -1
- package/dist/column-mapper.d.ts.map +1 -1
- package/dist/column-mapper.js +24 -8
- package/dist/column-mapper.js.map +1 -1
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +16 -0
- package/dist/constants.js.map +1 -1
- package/dist/docs-paths.d.ts +58 -0
- package/dist/docs-paths.d.ts.map +1 -0
- package/dist/docs-paths.js +89 -0
- package/dist/docs-paths.js.map +1 -0
- package/dist/enum-import.d.ts +14 -0
- package/dist/enum-import.d.ts.map +1 -0
- package/dist/enum-import.js +35 -0
- package/dist/enum-import.js.map +1 -0
- package/dist/enum-shared.d.ts +32 -0
- package/dist/enum-shared.d.ts.map +1 -0
- package/dist/enum-shared.js +83 -0
- package/dist/enum-shared.js.map +1 -0
- package/dist/generator-registry.d.ts +22 -0
- package/dist/generator-registry.d.ts.map +1 -0
- package/dist/generator-registry.js +161 -0
- package/dist/generator-registry.js.map +1 -0
- package/dist/generator.d.ts +6 -0
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js.map +1 -1
- package/dist/generators/api-doc-render.d.ts +17 -0
- package/dist/generators/api-doc-render.d.ts.map +1 -0
- package/dist/generators/api-doc-render.js +431 -0
- package/dist/generators/api-doc-render.js.map +1 -0
- package/dist/generators/api-docs-file.d.ts +21 -0
- package/dist/generators/api-docs-file.d.ts.map +1 -0
- package/dist/generators/api-docs-file.js +112 -0
- package/dist/generators/api-docs-file.js.map +1 -0
- package/dist/generators/api-field-shape.d.ts +39 -0
- package/dist/generators/api-field-shape.d.ts.map +1 -0
- package/dist/generators/api-field-shape.js +92 -0
- package/dist/generators/api-field-shape.js.map +1 -0
- package/dist/generators/api-label.d.ts +3 -0
- package/dist/generators/api-label.d.ts.map +1 -0
- package/dist/generators/api-label.js +8 -0
- package/dist/generators/api-label.js.map +1 -0
- package/dist/generators/api-model.d.ts +122 -0
- package/dist/generators/api-model.d.ts.map +1 -0
- package/dist/generators/api-model.js +809 -0
- package/dist/generators/api-model.js.map +1 -0
- package/dist/generators/docs-data-builder.d.ts +26 -4
- package/dist/generators/docs-data-builder.d.ts.map +1 -1
- package/dist/generators/docs-data-builder.js +436 -164
- package/dist/generators/docs-data-builder.js.map +1 -1
- package/dist/generators/docs-data.d.ts +136 -27
- package/dist/generators/docs-data.d.ts.map +1 -1
- package/dist/generators/docs-data.js +1 -1
- package/dist/generators/docs-data.js.map +1 -1
- package/dist/generators/docs-file.d.ts +19 -0
- package/dist/generators/docs-file.d.ts.map +1 -1
- package/dist/generators/docs-file.js +154 -27
- package/dist/generators/docs-file.js.map +1 -1
- package/dist/generators/entity-file.d.ts.map +1 -1
- package/dist/generators/entity-file.js +29 -14
- package/dist/generators/entity-file.js.map +1 -1
- package/dist/generators/extractor-file.d.ts.map +1 -1
- package/dist/generators/extractor-file.js +2 -1
- package/dist/generators/extractor-file.js.map +1 -1
- package/dist/generators/field-anchor.d.ts +7 -0
- package/dist/generators/field-anchor.d.ts.map +1 -0
- package/dist/generators/field-anchor.js +23 -0
- package/dist/generators/field-anchor.js.map +1 -0
- package/dist/generators/index.d.ts +8 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +6 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/mermaid-er.d.ts +14 -0
- package/dist/generators/mermaid-er.d.ts.map +1 -1
- package/dist/generators/mermaid-er.js +14 -0
- package/dist/generators/mermaid-er.js.map +1 -1
- package/dist/generators/output-parser-file.d.ts.map +1 -1
- package/dist/generators/output-parser-file.js +3 -4
- package/dist/generators/output-parser-file.js.map +1 -1
- package/dist/generators/output-prompt-file.d.ts.map +1 -1
- package/dist/generators/output-prompt-file.js +2 -2
- package/dist/generators/output-prompt-file.js.map +1 -1
- package/dist/generators/prompt-render-file.d.ts.map +1 -1
- package/dist/generators/prompt-render-file.js +3 -4
- package/dist/generators/prompt-render-file.js.map +1 -1
- package/dist/generators/queries-file.d.ts.map +1 -1
- package/dist/generators/queries-file.js +8 -3
- package/dist/generators/queries-file.js.map +1 -1
- package/dist/generators/render-helper-file.d.ts.map +1 -1
- package/dist/generators/render-helper-file.js +2 -2
- package/dist/generators/render-helper-file.js.map +1 -1
- package/dist/generators/routes-file-hono.d.ts.map +1 -1
- package/dist/generators/routes-file-hono.js +5 -1
- package/dist/generators/routes-file-hono.js.map +1 -1
- package/dist/generators/routes-file.d.ts +3 -0
- package/dist/generators/routes-file.d.ts.map +1 -1
- package/dist/generators/routes-file.js +6 -1
- package/dist/generators/routes-file.js.map +1 -1
- package/dist/generators/template-doc-builder.d.ts +19 -0
- package/dist/generators/template-doc-builder.d.ts.map +1 -0
- package/dist/generators/template-doc-builder.js +220 -0
- package/dist/generators/template-doc-builder.js.map +1 -0
- package/dist/generators/template-doc-data.d.ts +62 -0
- package/dist/generators/template-doc-data.d.ts.map +1 -0
- package/dist/generators/template-doc-data.js +16 -0
- package/dist/generators/template-doc-data.js.map +1 -0
- package/dist/generators/template-payload-tree.d.ts +15 -0
- package/dist/generators/template-payload-tree.d.ts.map +1 -0
- package/dist/generators/template-payload-tree.js +61 -0
- package/dist/generators/template-payload-tree.js.map +1 -0
- package/dist/generators/template-source-annotate.d.ts +74 -0
- package/dist/generators/template-source-annotate.d.ts.map +1 -0
- package/dist/generators/template-source-annotate.js +184 -0
- package/dist/generators/template-source-annotate.js.map +1 -0
- package/dist/generators/template-source-render.d.ts +24 -0
- package/dist/generators/template-source-render.d.ts.map +1 -0
- package/dist/generators/template-source-render.js +175 -0
- package/dist/generators/template-source-render.js.map +1 -0
- package/dist/generators/trace-helper-file.d.ts +9 -0
- package/dist/generators/trace-helper-file.d.ts.map +1 -0
- package/dist/generators/trace-helper-file.js +196 -0
- package/dist/generators/trace-helper-file.js.map +1 -0
- package/dist/index.d.ts +29 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -2
- package/dist/index.js.map +1 -1
- package/dist/metaobjects-config.d.ts +75 -2
- package/dist/metaobjects-config.d.ts.map +1 -1
- package/dist/metaobjects-config.js +43 -0
- package/dist/metaobjects-config.js.map +1 -1
- package/dist/naming.d.ts +19 -0
- package/dist/naming.d.ts.map +1 -1
- package/dist/naming.js +41 -0
- package/dist/naming.js.map +1 -1
- package/dist/payload-codegen.d.ts.map +1 -1
- package/dist/payload-codegen.js +12 -4
- package/dist/payload-codegen.js.map +1 -1
- package/dist/projection/extract-view-spec.d.ts.map +1 -1
- package/dist/projection/extract-view-spec.js +51 -25
- package/dist/projection/extract-view-spec.js.map +1 -1
- package/dist/relation-resolver.d.ts +16 -0
- package/dist/relation-resolver.d.ts.map +1 -1
- package/dist/relation-resolver.js +82 -1
- package/dist/relation-resolver.js.map +1 -1
- package/dist/render-context.d.ts +4 -0
- package/dist/render-context.d.ts.map +1 -1
- package/dist/render-context.js.map +1 -1
- package/dist/render-engine/embedded-templates.generated.d.ts +2 -0
- package/dist/render-engine/embedded-templates.generated.d.ts.map +1 -0
- package/dist/render-engine/embedded-templates.generated.js +15 -0
- package/dist/render-engine/embedded-templates.generated.js.map +1 -0
- package/dist/render-engine/framework-provider.d.ts.map +1 -1
- package/dist/render-engine/framework-provider.js +26 -13
- package/dist/render-engine/framework-provider.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +17 -0
- package/dist/runner.js.map +1 -1
- package/dist/templates/docs-file.d.ts +2 -6
- package/dist/templates/docs-file.d.ts.map +1 -1
- package/dist/templates/docs-file.js +2 -5
- package/dist/templates/docs-file.js.map +1 -1
- package/dist/templates/drizzle-schema.d.ts.map +1 -1
- package/dist/templates/drizzle-schema.js +30 -2
- package/dist/templates/drizzle-schema.js.map +1 -1
- package/dist/templates/entity-constants.d.ts +7 -0
- package/dist/templates/entity-constants.d.ts.map +1 -1
- package/dist/templates/entity-constants.js +1 -1
- package/dist/templates/entity-constants.js.map +1 -1
- package/dist/templates/entity-file.d.ts.map +1 -1
- package/dist/templates/entity-file.js +16 -5
- package/dist/templates/entity-file.js.map +1 -1
- package/dist/templates/enums-file.d.ts +11 -0
- package/dist/templates/enums-file.d.ts.map +1 -0
- package/dist/templates/enums-file.js +44 -0
- package/dist/templates/enums-file.js.map +1 -0
- package/dist/templates/extract-delegate-emitter.d.ts.map +1 -1
- package/dist/templates/extract-delegate-emitter.js +5 -7
- package/dist/templates/extract-delegate-emitter.js.map +1 -1
- package/dist/templates/extract-schema-emitter.d.ts.map +1 -1
- package/dist/templates/extract-schema-emitter.js +5 -1
- package/dist/templates/extract-schema-emitter.js.map +1 -1
- package/dist/templates/extractor.d.ts.map +1 -1
- package/dist/templates/extractor.js +56 -39
- package/dist/templates/extractor.js.map +1 -1
- package/dist/templates/field-meta.d.ts.map +1 -1
- package/dist/templates/field-meta.js +1 -5
- package/dist/templates/field-meta.js.map +1 -1
- package/dist/templates/filter-allowlist.d.ts +7 -2
- package/dist/templates/filter-allowlist.d.ts.map +1 -1
- package/dist/templates/filter-allowlist.js +17 -9
- package/dist/templates/filter-allowlist.js.map +1 -1
- package/dist/templates/filter-type.d.ts +7 -1
- package/dist/templates/filter-type.d.ts.map +1 -1
- package/dist/templates/filter-type.js +9 -5
- package/dist/templates/filter-type.js.map +1 -1
- package/dist/templates/find-templates.d.ts +4 -0
- package/dist/templates/find-templates.d.ts.map +1 -0
- package/dist/templates/find-templates.js +15 -0
- package/dist/templates/find-templates.js.map +1 -0
- package/dist/templates/fr010-field-mapping.d.ts +2 -0
- package/dist/templates/fr010-field-mapping.d.ts.map +1 -1
- package/dist/templates/fr010-field-mapping.js +10 -6
- package/dist/templates/fr010-field-mapping.js.map +1 -1
- package/dist/templates/inferred-types.d.ts +44 -7
- package/dist/templates/inferred-types.d.ts.map +1 -1
- package/dist/templates/inferred-types.js +107 -16
- package/dist/templates/inferred-types.js.map +1 -1
- package/dist/templates/mermaid-er.d.ts +35 -2
- package/dist/templates/mermaid-er.d.ts.map +1 -1
- package/dist/templates/mermaid-er.js +174 -7
- package/dist/templates/mermaid-er.js.map +1 -1
- package/dist/templates/output-parser.d.ts.map +1 -1
- package/dist/templates/output-parser.js +30 -79
- package/dist/templates/output-parser.js.map +1 -1
- package/dist/templates/output-prompt.d.ts.map +1 -1
- package/dist/templates/output-prompt.js +2 -2
- package/dist/templates/output-prompt.js.map +1 -1
- package/dist/templates/queries-file.d.ts.map +1 -1
- package/dist/templates/queries-file.js +112 -4
- package/dist/templates/queries-file.js.map +1 -1
- package/dist/templates/queries.d.ts +5 -0
- package/dist/templates/queries.d.ts.map +1 -1
- package/dist/templates/queries.js +7 -7
- package/dist/templates/queries.js.map +1 -1
- package/dist/templates/recover-schema-emitter.d.ts +8 -0
- package/dist/templates/recover-schema-emitter.d.ts.map +1 -0
- package/dist/templates/recover-schema-emitter.js +64 -0
- package/dist/templates/recover-schema-emitter.js.map +1 -0
- package/dist/templates/relations-block.js +10 -0
- package/dist/templates/relations-block.js.map +1 -1
- package/dist/templates/render-helper.d.ts.map +1 -1
- package/dist/templates/render-helper.js +4 -4
- package/dist/templates/render-helper.js.map +1 -1
- package/dist/templates/routes-file.d.ts.map +1 -1
- package/dist/templates/routes-file.js +183 -6
- package/dist/templates/routes-file.js.map +1 -1
- package/dist/templates/tph-discriminator.d.ts +56 -0
- package/dist/templates/tph-discriminator.d.ts.map +1 -0
- package/dist/templates/tph-discriminator.js +180 -0
- package/dist/templates/tph-discriminator.js.map +1 -0
- package/dist/templates/value-object-file.d.ts +2 -1
- package/dist/templates/value-object-file.d.ts.map +1 -1
- package/dist/templates/value-object-file.js +32 -4
- package/dist/templates/value-object-file.js.map +1 -1
- package/dist/templates/zod-validators.d.ts +64 -1
- package/dist/templates/zod-validators.d.ts.map +1 -1
- package/dist/templates/zod-validators.js +181 -8
- package/dist/templates/zod-validators.js.map +1 -1
- package/package.json +103 -34
- package/src/column-mapper.ts +25 -8
- package/src/constants.ts +18 -0
- package/src/docs-paths.ts +128 -0
- package/src/enum-import.ts +43 -0
- package/src/enum-shared.ts +95 -0
- package/src/generator-registry.ts +204 -0
- package/src/generator.ts +6 -0
- package/src/generators/api-doc-render.ts +572 -0
- package/src/generators/api-docs-file.ts +146 -0
- package/src/generators/api-field-shape.ts +114 -0
- package/src/generators/api-label.ts +7 -0
- package/src/generators/api-model.ts +1067 -0
- package/src/generators/docs-data-builder.ts +479 -185
- package/src/generators/docs-data.ts +139 -28
- package/src/generators/docs-file.ts +205 -39
- package/src/generators/entity-file.ts +31 -15
- package/src/generators/extractor-file.ts +2 -1
- package/src/generators/field-anchor.ts +24 -0
- package/src/generators/index.ts +8 -1
- package/src/generators/mermaid-er.ts +14 -0
- package/src/generators/output-parser-file.ts +3 -4
- package/src/generators/output-prompt-file.ts +2 -1
- package/src/generators/prompt-render-file.ts +3 -4
- package/src/generators/queries-file.ts +9 -3
- package/src/generators/render-helper-file.ts +2 -1
- package/src/generators/routes-file-hono.ts +5 -1
- package/src/generators/routes-file.ts +7 -1
- package/src/generators/template-doc-builder.ts +306 -0
- package/src/generators/template-doc-data.ts +85 -0
- package/src/generators/template-payload-tree.ts +71 -0
- package/src/generators/template-source-annotate.ts +290 -0
- package/src/generators/template-source-render.ts +203 -0
- package/src/generators/trace-helper-file.ts +301 -0
- package/src/index.ts +55 -4
- package/src/metaobjects-config.ts +117 -2
- package/src/naming.ts +48 -0
- package/src/payload-codegen.ts +14 -3
- package/src/projection/extract-view-spec.ts +49 -30
- package/src/relation-resolver.ts +103 -1
- package/src/render-context.ts +4 -0
- package/src/render-engine/embedded-templates.generated.ts +14 -0
- package/src/render-engine/framework-provider.ts +25 -11
- package/src/runner.ts +21 -0
- package/src/templates/docs-file.ts +2 -9
- package/src/templates/drizzle-schema.ts +31 -1
- package/src/templates/entity-constants.ts +1 -1
- package/src/templates/entity-file.ts +16 -5
- package/src/templates/enums-file.ts +50 -0
- package/src/templates/extract-delegate-emitter.ts +5 -6
- package/src/templates/extractor.ts +68 -38
- package/src/templates/field-meta.ts +0 -6
- package/src/templates/filter-allowlist.ts +17 -10
- package/src/templates/filter-type.ts +8 -6
- package/src/templates/find-templates.ts +15 -0
- package/src/templates/fr010-field-mapping.ts +10 -8
- package/src/templates/inferred-types.ts +108 -18
- package/src/templates/mermaid-er.ts +176 -8
- package/src/templates/output-parser.ts +30 -79
- package/src/templates/output-prompt.ts +2 -1
- package/src/templates/queries-file.ts +132 -3
- package/src/templates/queries.ts +15 -7
- package/src/templates/relations-block.ts +17 -0
- package/src/templates/render-helper.ts +4 -3
- package/src/templates/routes-file.ts +233 -6
- package/src/templates/tph-discriminator.ts +232 -0
- package/src/templates/value-object-file.ts +38 -4
- package/src/templates/zod-validators.ts +204 -7
- package/templates/api/agent-api.md.mustache +30 -0
- package/templates/api/entity-api.md.mustache +69 -0
- package/templates/api/index.md.mustache +21 -0
- package/templates/docs/entity-page.md.mustache +33 -21
- package/templates/docs/template-page.md.mustache +56 -0
- package/src/templates/extract-schema-emitter.ts +0 -111
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// Annotated-template IR (linked-template-source-docs, Task 1).
|
|
2
|
+
//
|
|
3
|
+
// annotateTemplate parses a Mustache template into an ordered TplToken[] where
|
|
4
|
+
// every {{variable}} / {{#section}} is resolved to the payload field it
|
|
5
|
+
// references plus that field's doc link. It tokenizes with the SAME parser
|
|
6
|
+
// verify walks (`parseTemplate` from @metaobjectsdev/render) and resolves each
|
|
7
|
+
// path with verify's EXPORTED `resolveTemplateVariable` — so the annotator and
|
|
8
|
+
// the build-time drift gate share ONE resolution and can never disagree (a
|
|
9
|
+
// later conformance gate asserts exactly that).
|
|
10
|
+
//
|
|
11
|
+
// Reuse, not reimplementation:
|
|
12
|
+
// • parseTemplate — the render verify engine's Mustache.parse.
|
|
13
|
+
// • resolveTemplateVariable — verify's context-stack walk (sections push the
|
|
14
|
+
// nested subtree; dotted paths descend). Generic
|
|
15
|
+
// over the node, so an ENRICHED tree (carrying
|
|
16
|
+
// owner/type/required) resolves identically.
|
|
17
|
+
//
|
|
18
|
+
// The annotator is a pure function over an enriched payload tree + the source —
|
|
19
|
+
// no metadata import, no I/O — so it is unit-testable and golden-pinnable.
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
parseTemplate,
|
|
23
|
+
resolveTemplateVariable,
|
|
24
|
+
type PayloadField,
|
|
25
|
+
type ResolveStack,
|
|
26
|
+
} from "@metaobjectsdev/render";
|
|
27
|
+
import { fieldAnchorSlug } from "./field-anchor.js";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* An enriched payload-field node. Structurally a verify `PayloadField` (so the
|
|
31
|
+
* shared resolver walks it), plus the per-field doc metadata the annotator needs
|
|
32
|
+
* to emit a `ResolvedField` + link: `owner` (the VO that declares the field),
|
|
33
|
+
* `type`, and `required`. Container fields (object / array-of-object) carry
|
|
34
|
+
* `fields` whose nodes are owned by the nested VO.
|
|
35
|
+
*/
|
|
36
|
+
export interface AnnotatePayloadField extends PayloadField {
|
|
37
|
+
owner: string;
|
|
38
|
+
type: string;
|
|
39
|
+
required: boolean;
|
|
40
|
+
fields?: AnnotatePayloadField[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The field a `{{variable}}` / `{{#section}}` resolves to. */
|
|
44
|
+
export interface ResolvedField {
|
|
45
|
+
owner: string;
|
|
46
|
+
name: string;
|
|
47
|
+
type: string;
|
|
48
|
+
required: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** One ordered token of the annotated template. `raw` is the verbatim source
|
|
52
|
+
* span of the tag (text tokens carry `text`), so the source round-trips. */
|
|
53
|
+
export type TplToken =
|
|
54
|
+
| { kind: "text"; text: string }
|
|
55
|
+
| {
|
|
56
|
+
kind: "var" | "unescaped";
|
|
57
|
+
raw: string;
|
|
58
|
+
path: string;
|
|
59
|
+
field?: ResolvedField;
|
|
60
|
+
href?: string;
|
|
61
|
+
valid: boolean;
|
|
62
|
+
}
|
|
63
|
+
| {
|
|
64
|
+
kind: "section" | "inverted" | "close";
|
|
65
|
+
raw: string;
|
|
66
|
+
path: string;
|
|
67
|
+
field?: ResolvedField;
|
|
68
|
+
href?: string;
|
|
69
|
+
}
|
|
70
|
+
| { kind: "partial"; raw: string; ref: string; href?: string }
|
|
71
|
+
| { kind: "comment"; raw: string };
|
|
72
|
+
|
|
73
|
+
export interface AnnotateOptions {
|
|
74
|
+
/** The root payload VO's short name (owner of the root-context fields). Used
|
|
75
|
+
* only for diagnostics / callers; per-field owner comes off the tree node. */
|
|
76
|
+
ownerVoName: string;
|
|
77
|
+
/**
|
|
78
|
+
* Resolve a partial `{{>ref}}` to a doc-page href, if the ref names a
|
|
79
|
+
* documented template. Returns the href or undefined (highlight-only).
|
|
80
|
+
* Optional — when absent, partials are captured ref-only (no href).
|
|
81
|
+
*/
|
|
82
|
+
resolvePartialHref?: (ref: string) => string | undefined;
|
|
83
|
+
/**
|
|
84
|
+
* Override how a resolved field's doc-page href is built (owner page + the
|
|
85
|
+
* shared `#field-<name>` fragment). Optional — when absent, the flat default
|
|
86
|
+
* `./<owner>.md#field-<name>` is used (byte-identical to today). A caller with
|
|
87
|
+
* the output layout + page placement in scope injects a layout-aware resolver
|
|
88
|
+
* (the SAME `docPageHref(layout, …)` the Payload cross-link uses) so the link
|
|
89
|
+
* resolves under package layout too.
|
|
90
|
+
*/
|
|
91
|
+
fieldHref?: (owner: string, name: string) => string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// A Mustache parse token: [type, value, start, end, subTokens?, closeStart?, ...].
|
|
95
|
+
type Token = readonly unknown[];
|
|
96
|
+
|
|
97
|
+
/** The doc-page href for a resolved field: `./<OwnerVO>.md#field-<name>`. The
|
|
98
|
+
* anchor slug comes from the SHARED `fieldAnchorSlug()` (prefixed — avoids
|
|
99
|
+
* colliding with other page anchors), the SAME helper the entity page uses to
|
|
100
|
+
* emit its per-field `<a id="field-<name>">` anchor, so the link and the anchor
|
|
101
|
+
* can never drift. */
|
|
102
|
+
function fieldHref(owner: string, name: string): string {
|
|
103
|
+
return `./${owner}.md#${fieldAnchorSlug(name)}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function toResolvedField(f: AnnotatePayloadField): ResolvedField {
|
|
107
|
+
return { owner: f.owner, name: f.name, type: f.type, required: f.required };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Parse `source` into an annotated IR, resolving each variable/section against
|
|
112
|
+
* the enriched `payload` tree using verify's shared resolution.
|
|
113
|
+
*/
|
|
114
|
+
export function annotateTemplate(
|
|
115
|
+
source: string,
|
|
116
|
+
payload: AnnotatePayloadField[],
|
|
117
|
+
opts: AnnotateOptions,
|
|
118
|
+
): TplToken[] {
|
|
119
|
+
const root = payload;
|
|
120
|
+
const out: TplToken[] = [];
|
|
121
|
+
let cursor = 0;
|
|
122
|
+
|
|
123
|
+
// How a resolved field's href is built: the injected layout-aware resolver
|
|
124
|
+
// when provided, else the flat default (byte-identical to today's output).
|
|
125
|
+
const buildFieldHref = opts.fieldHref ?? fieldHref;
|
|
126
|
+
|
|
127
|
+
// Emit verbatim source between `cursor` and `to` as a text token, advancing
|
|
128
|
+
// the cursor. This recovers literal text AND any span Mustache trimmed as
|
|
129
|
+
// standalone (e.g. the newline after a standalone partial/section), so the
|
|
130
|
+
// concatenated tokens reproduce the source byte-for-byte.
|
|
131
|
+
function emitTextUpTo(to: number): void {
|
|
132
|
+
if (to > cursor) {
|
|
133
|
+
out.push({ kind: "text", text: source.slice(cursor, to) });
|
|
134
|
+
cursor = to;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function resolveAt(
|
|
139
|
+
stack: ResolveStack<AnnotatePayloadField>,
|
|
140
|
+
path: string,
|
|
141
|
+
): { field: ResolvedField; href: string } | undefined {
|
|
142
|
+
const hit = resolveTemplateVariable(stack, path);
|
|
143
|
+
if (!hit) return undefined;
|
|
144
|
+
const field = toResolvedField(hit);
|
|
145
|
+
return { field, href: buildFieldHref(field.owner, field.name) };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Build a variable token ({{x}} or {{&x}}/{{{x}}}). The implicit iterator
|
|
149
|
+
// `.` is always valid against the current context; any other path resolves
|
|
150
|
+
// against the enriched tree (carrying field + href when found).
|
|
151
|
+
function makeVarToken(
|
|
152
|
+
kind: "var" | "unescaped",
|
|
153
|
+
raw: string,
|
|
154
|
+
value: string,
|
|
155
|
+
stack: ResolveStack<AnnotatePayloadField>,
|
|
156
|
+
): TplToken {
|
|
157
|
+
if (value === ".") return { kind, raw, path: value, valid: true };
|
|
158
|
+
const r = resolveAt(stack, value);
|
|
159
|
+
return r
|
|
160
|
+
? { kind, raw, path: value, field: r.field, href: r.href, valid: true }
|
|
161
|
+
: { kind, raw, path: value, valid: false };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function walk(
|
|
165
|
+
tokens: Token[],
|
|
166
|
+
stack: ResolveStack<AnnotatePayloadField>,
|
|
167
|
+
): void {
|
|
168
|
+
for (const tok of tokens) {
|
|
169
|
+
const type = tok[0] as string;
|
|
170
|
+
const value = tok[1] as string;
|
|
171
|
+
const start = tok[2] as number;
|
|
172
|
+
const end = tok[3] as number;
|
|
173
|
+
|
|
174
|
+
// Recover any source between the previous token and this tag (literal
|
|
175
|
+
// text, or trimmed standalone whitespace).
|
|
176
|
+
emitTextUpTo(start);
|
|
177
|
+
|
|
178
|
+
switch (type) {
|
|
179
|
+
case "text": {
|
|
180
|
+
out.push({ kind: "text", text: source.slice(start, end) });
|
|
181
|
+
cursor = end;
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case "name": {
|
|
185
|
+
// {{x}} — escaped variable.
|
|
186
|
+
const raw = source.slice(start, end);
|
|
187
|
+
cursor = end;
|
|
188
|
+
out.push(makeVarToken("var", raw, value, stack));
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case "&":
|
|
192
|
+
case "{": {
|
|
193
|
+
// {{&x}} / {{{x}}} — unescaped variable (mustache.js emits "&" for both).
|
|
194
|
+
const raw = source.slice(start, end);
|
|
195
|
+
cursor = end;
|
|
196
|
+
out.push(makeVarToken("unescaped", raw, value, stack));
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case "#":
|
|
200
|
+
case "^": {
|
|
201
|
+
// {{#x}}…{{/x}} (section) / {{^x}}…{{/x}} (inverted). `end` is the end
|
|
202
|
+
// of the OPENING tag; subTokens at [4]; [5] is where the closing tag
|
|
203
|
+
// begins. Emit the open tag, recurse the body in the pushed context,
|
|
204
|
+
// then emit the close tag.
|
|
205
|
+
const sub = Array.isArray(tok[4]) ? (tok[4] as Token[]) : [];
|
|
206
|
+
const closeStart = tok[5] as number;
|
|
207
|
+
const openRaw = source.slice(start, end);
|
|
208
|
+
cursor = end;
|
|
209
|
+
|
|
210
|
+
const isImplicit = value === ".";
|
|
211
|
+
const r = isImplicit ? undefined : resolveAt(stack, value);
|
|
212
|
+
const kind = type === "#" ? "section" : "inverted";
|
|
213
|
+
out.push(
|
|
214
|
+
r
|
|
215
|
+
? {
|
|
216
|
+
kind,
|
|
217
|
+
raw: openRaw,
|
|
218
|
+
path: value,
|
|
219
|
+
field: r.field,
|
|
220
|
+
href: r.href,
|
|
221
|
+
}
|
|
222
|
+
: { kind, raw: openRaw, path: value },
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
// `#` over a container pushes its element fields; `^` (and `#` over a
|
|
226
|
+
// scalar conditional) keep the current context — EXACTLY verify's rule.
|
|
227
|
+
const hit = isImplicit
|
|
228
|
+
? undefined
|
|
229
|
+
: resolveTemplateVariable(stack, value);
|
|
230
|
+
const nested = type === "#" ? hit?.fields : undefined;
|
|
231
|
+
const childStack: ResolveStack<AnnotatePayloadField> =
|
|
232
|
+
nested !== undefined ? [...stack, nested] : stack;
|
|
233
|
+
walk(sub, childStack);
|
|
234
|
+
|
|
235
|
+
// The closing tag `{{/value}}` runs from closeStart to the end of the
|
|
236
|
+
// section. Recover any body remainder Mustache trimmed, then emit close.
|
|
237
|
+
emitTextUpTo(closeStart);
|
|
238
|
+
// The close tag's exact end isn't in the token; derive it from the
|
|
239
|
+
// literal `{{/…}}` form starting at closeStart so the raw is verbatim.
|
|
240
|
+
const closeRaw = readCloseTag(source, closeStart, value);
|
|
241
|
+
out.push({ kind: "close", raw: closeRaw, path: value });
|
|
242
|
+
cursor = closeStart + closeRaw.length;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
case ">": {
|
|
246
|
+
// {{>ref}} — partial.
|
|
247
|
+
const raw = source.slice(start, end);
|
|
248
|
+
cursor = end;
|
|
249
|
+
const href = opts.resolvePartialHref?.(value);
|
|
250
|
+
out.push(
|
|
251
|
+
href !== undefined
|
|
252
|
+
? { kind: "partial", raw, ref: value, href }
|
|
253
|
+
: { kind: "partial", raw, ref: value },
|
|
254
|
+
);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
case "!": {
|
|
258
|
+
// {{! comment }}.
|
|
259
|
+
out.push({ kind: "comment", raw: source.slice(start, end) });
|
|
260
|
+
cursor = end;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
default: {
|
|
264
|
+
// set-delimiter (=) or any other: preserve verbatim as text.
|
|
265
|
+
if (Number.isFinite(end) && end > start) {
|
|
266
|
+
out.push({ kind: "text", text: source.slice(start, end) });
|
|
267
|
+
cursor = end;
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
walk(parseTemplate(source) as Token[], [root]);
|
|
276
|
+
// Any trailing source after the last token (e.g. a standalone-trimmed final
|
|
277
|
+
// newline) is recovered as text.
|
|
278
|
+
emitTextUpTo(source.length);
|
|
279
|
+
|
|
280
|
+
return out;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Read the verbatim closing tag `{{/name}}` (with any inner whitespace the
|
|
284
|
+
// author wrote) starting at `from`. Mustache only gives the close-tag start, so
|
|
285
|
+
// we locate the terminating `}}` from there to recover the exact source span.
|
|
286
|
+
function readCloseTag(source: string, from: number, _name: string): string {
|
|
287
|
+
const close = source.indexOf("}}", from);
|
|
288
|
+
if (close === -1) return source.slice(from);
|
|
289
|
+
return source.slice(from, close + 2);
|
|
290
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// Template-source renderers (linked-template-source-docs, Task 3).
|
|
2
|
+
//
|
|
3
|
+
// ONE annotated IR (the TplToken[] from annotateTemplate), THREE doc forms — no
|
|
4
|
+
// re-derivation, no second parse. All three are pure functions over the tokens:
|
|
5
|
+
//
|
|
6
|
+
// • renderSourceBlock — the verbatim template inside a ```mustache fence.
|
|
7
|
+
// The annotator round-trips the source via text/raw,
|
|
8
|
+
// so the fenced body equals the original byte-for-byte
|
|
9
|
+
// and any viewer/site highlighter colors it.
|
|
10
|
+
// • renderVariablesTable — a Markdown table of the UNIQUE referenced variables
|
|
11
|
+
// (vars + sections that resolve to a field), each a
|
|
12
|
+
// Markdown-native `[owner.name](href)` link (or flagged
|
|
13
|
+
// "not on payload" when unresolved). Agent-clean.
|
|
14
|
+
// • renderRichLinkedHtml — a collapsed <details> whose <pre> reproduces the
|
|
15
|
+
// template with each token in a SELF-CONTAINED
|
|
16
|
+
// inline-styled <span> (color per kind) and each
|
|
17
|
+
// resolved variable/section wrapped in a clickable
|
|
18
|
+
// <a href>. Inline styles only (no external CSS) so it
|
|
19
|
+
// renders identically on GitHub and a static site.
|
|
20
|
+
//
|
|
21
|
+
// Color (block + rich view), links (table + rich view), agent-cleanliness (block
|
|
22
|
+
// + table) — all from the same source of truth.
|
|
23
|
+
|
|
24
|
+
import type { TplToken } from "./template-source-annotate.js";
|
|
25
|
+
|
|
26
|
+
// ── 1. Source block ────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Reconstruct the verbatim template source from the tokens and wrap it in a
|
|
30
|
+
* ```mustache fenced block. Concatenating `text` (text tokens) / `raw` (tag
|
|
31
|
+
* tokens) round-trips the original source byte-for-byte (the annotator pins
|
|
32
|
+
* this), so the fence body is the clean, highlightable source.
|
|
33
|
+
*/
|
|
34
|
+
export function renderSourceBlock(tokens: TplToken[]): string {
|
|
35
|
+
const source = reconstructSource(tokens);
|
|
36
|
+
return "```mustache\n" + source + "\n```";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Concatenate the verbatim span of every token (text or tag raw). */
|
|
40
|
+
function reconstructSource(tokens: TplToken[]): string {
|
|
41
|
+
let out = "";
|
|
42
|
+
for (const t of tokens) out += t.kind === "text" ? t.text : t.raw;
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── 2. Variables table ─────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
interface VarRow {
|
|
49
|
+
path: string;
|
|
50
|
+
owner: string | undefined;
|
|
51
|
+
name: string | undefined;
|
|
52
|
+
type: string | undefined;
|
|
53
|
+
required: boolean | undefined;
|
|
54
|
+
href: string | undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Collect the unique referenced variables (vars/unescaped + sections/inverted
|
|
58
|
+
* that carry a path), deduped by path, in first-seen order. Implicit-iterator
|
|
59
|
+
* `.` tokens and close tokens are not "variables" and are skipped. */
|
|
60
|
+
function collectVarRows(tokens: TplToken[]): VarRow[] {
|
|
61
|
+
const seen = new Set<string>();
|
|
62
|
+
const rows: VarRow[] = [];
|
|
63
|
+
for (const t of tokens) {
|
|
64
|
+
const isRef =
|
|
65
|
+
t.kind === "var" ||
|
|
66
|
+
t.kind === "unescaped" ||
|
|
67
|
+
t.kind === "section" ||
|
|
68
|
+
t.kind === "inverted";
|
|
69
|
+
if (!isRef) continue;
|
|
70
|
+
if (t.path === "." || seen.has(t.path)) continue;
|
|
71
|
+
seen.add(t.path);
|
|
72
|
+
rows.push({
|
|
73
|
+
path: t.path,
|
|
74
|
+
owner: t.field?.owner,
|
|
75
|
+
name: t.field?.name,
|
|
76
|
+
type: t.field?.type,
|
|
77
|
+
required: t.field?.required,
|
|
78
|
+
href: t.href,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return rows;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Markdown-escape a table cell whose text may contain a `|`. */
|
|
85
|
+
function mdCell(text: string): string {
|
|
86
|
+
return text.replace(/\|/g, "\\|");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A Markdown table of the unique referenced variables. Resolved variables link
|
|
91
|
+
* to their field doc (`[owner.name](href)`); unresolved ones are em-dashed and
|
|
92
|
+
* carry a "not on payload" note in the Type cell. Returns "" when there are no
|
|
93
|
+
* variables (keeps the page clean).
|
|
94
|
+
*/
|
|
95
|
+
export function renderVariablesTable(tokens: TplToken[]): string {
|
|
96
|
+
const rows = collectVarRows(tokens);
|
|
97
|
+
if (rows.length === 0) return "";
|
|
98
|
+
|
|
99
|
+
const lines: string[] = [
|
|
100
|
+
"| Variable | Field | Type | Required |",
|
|
101
|
+
"| --- | --- | --- | --- |",
|
|
102
|
+
];
|
|
103
|
+
for (const r of rows) {
|
|
104
|
+
const variable = `\`{{${mdCell(r.path)}}}\``;
|
|
105
|
+
if (r.href && r.owner && r.name) {
|
|
106
|
+
const field = `[${r.owner}.${r.name}](${r.href})`;
|
|
107
|
+
const type = r.type ? mdCell(r.type) : "—";
|
|
108
|
+
const required = r.required ? "yes" : "no";
|
|
109
|
+
lines.push(`| ${variable} | ${field} | ${type} | ${required} |`);
|
|
110
|
+
} else {
|
|
111
|
+
// Unresolved: no field/type/required. Keep the 4-column shape; flag it
|
|
112
|
+
// "not on payload" in the Field cell, em-dash the rest.
|
|
113
|
+
lines.push(`| ${variable} | — (not on payload) | — | — |`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return lines.join("\n");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── 3. Rich linked HTML ────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
// Per-kind inline color (self-contained — no external CSS, no classes). Chosen
|
|
122
|
+
// for legibility on both GitHub's light/dark Markdown and a generic docs site.
|
|
123
|
+
const STYLE_VAR = "color:#0969da"; // blue — escaped/unescaped variable
|
|
124
|
+
const STYLE_SECTION = "color:#8250df"; // purple — section / inverted / close
|
|
125
|
+
const STYLE_PARTIAL = "color:#1a7f7a"; // teal — partial reference
|
|
126
|
+
const STYLE_COMMENT = "color:#6e7781;font-style:italic"; // gray italic — comment
|
|
127
|
+
|
|
128
|
+
/** HTML-escape literal text so `<`, `>`, `&`, `"` in the template don't break
|
|
129
|
+
* the surrounding HTML. Quotes are escaped too so token raw is safe in any
|
|
130
|
+
* attribute-adjacent position. */
|
|
131
|
+
function escapeHtml(s: string): string {
|
|
132
|
+
return s
|
|
133
|
+
.replace(/&/g, "&")
|
|
134
|
+
.replace(/</g, "<")
|
|
135
|
+
.replace(/>/g, ">")
|
|
136
|
+
.replace(/"/g, """);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** An inline-styled span for a token kind, escaping the inner verbatim text. */
|
|
140
|
+
function styledSpan(style: string, inner: string): string {
|
|
141
|
+
return `<span style="${style}">${inner}</span>`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A styled span for a token kind, wrapped in a clickable `<a href>` when the
|
|
145
|
+
* token resolved to a link (else the bare span). The raw text is HTML-escaped. */
|
|
146
|
+
function linkSpan(
|
|
147
|
+
style: string,
|
|
148
|
+
raw: string,
|
|
149
|
+
href: string | undefined,
|
|
150
|
+
): string {
|
|
151
|
+
const span = styledSpan(style, escapeHtml(raw));
|
|
152
|
+
return href ? `<a href="${escapeHtml(href)}">${span}</a>` : span;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The collapsed <details> "Linked view": a <pre> reproducing the template where
|
|
157
|
+
* each token is an inline-styled <span> (color by kind) and each resolved
|
|
158
|
+
* variable/section is additionally wrapped in a clickable <a href>. Whitespace
|
|
159
|
+
* and newlines are preserved inside the <pre>. Collapsed by default (no `open`)
|
|
160
|
+
* so the plain/agent view stays clean; a human expands it for the clickable vars.
|
|
161
|
+
*/
|
|
162
|
+
export function renderRichLinkedHtml(tokens: TplToken[]): string {
|
|
163
|
+
let pre = "";
|
|
164
|
+
for (const t of tokens) {
|
|
165
|
+
switch (t.kind) {
|
|
166
|
+
case "text": {
|
|
167
|
+
// Default color; escape literal text verbatim (preserves newlines).
|
|
168
|
+
pre += escapeHtml(t.text);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case "var":
|
|
172
|
+
case "unescaped": {
|
|
173
|
+
pre += linkSpan(STYLE_VAR, t.raw, t.href);
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case "section":
|
|
177
|
+
case "inverted":
|
|
178
|
+
case "close": {
|
|
179
|
+
// Only open tags (section/inverted) carry an href; close tags don't.
|
|
180
|
+
const href = "href" in t ? t.href : undefined;
|
|
181
|
+
pre += linkSpan(STYLE_SECTION, t.raw, href);
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case "partial": {
|
|
185
|
+
pre += linkSpan(STYLE_PARTIAL, t.raw, t.href);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case "comment": {
|
|
189
|
+
pre += styledSpan(STYLE_COMMENT, escapeHtml(t.raw));
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
"<details>\n" +
|
|
197
|
+
"<summary>Linked view</summary>\n\n" +
|
|
198
|
+
'<pre style="white-space:pre-wrap;word-break:break-word">' +
|
|
199
|
+
pre +
|
|
200
|
+
"</pre>\n\n" +
|
|
201
|
+
"</details>"
|
|
202
|
+
);
|
|
203
|
+
}
|