@relevate/katachi 0.1.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.
Files changed (92) hide show
  1. package/CONTRIBUTING.md +60 -0
  2. package/LICENSE +21 -0
  3. package/README.md +194 -0
  4. package/bin/katachi.mjs +30 -0
  5. package/dist/api/index.d.ts +54 -0
  6. package/dist/api/index.js +45 -0
  7. package/dist/api/jsx.d.ts +26 -0
  8. package/dist/cli/index.d.ts +1 -0
  9. package/dist/cli/index.js +77 -0
  10. package/dist/core/ast.d.ts +115 -0
  11. package/dist/core/ast.js +51 -0
  12. package/dist/core/build.d.ts +15 -0
  13. package/dist/core/build.js +107 -0
  14. package/dist/core/compiler.d.ts +9 -0
  15. package/dist/core/compiler.js +9 -0
  16. package/dist/core/example-fixtures.d.ts +5 -0
  17. package/dist/core/example-fixtures.js +54 -0
  18. package/dist/core/parser.d.ts +5 -0
  19. package/dist/core/parser.js +637 -0
  20. package/dist/core/types.d.ts +65 -0
  21. package/dist/core/types.js +1 -0
  22. package/dist/core/verify.d.ts +25 -0
  23. package/dist/core/verify.js +270 -0
  24. package/dist/index.d.ts +7 -0
  25. package/dist/index.js +5 -0
  26. package/dist/targets/askama.d.ts +11 -0
  27. package/dist/targets/askama.js +122 -0
  28. package/dist/targets/index.d.ts +5 -0
  29. package/dist/targets/index.js +60 -0
  30. package/dist/targets/react.d.ts +4 -0
  31. package/dist/targets/react.js +28 -0
  32. package/dist/targets/shared.d.ts +36 -0
  33. package/dist/targets/shared.js +278 -0
  34. package/dist/targets/static-jsx.d.ts +4 -0
  35. package/dist/targets/static-jsx.js +28 -0
  36. package/dist/verify-examples.d.ts +1 -0
  37. package/dist/verify-examples.js +14 -0
  38. package/docs/architecture.md +122 -0
  39. package/docs/getting-started.md +154 -0
  40. package/docs/syntax.md +236 -0
  41. package/docs/targets.md +53 -0
  42. package/examples/basic/README.md +67 -0
  43. package/examples/basic/components/badge-chip.html +3 -0
  44. package/examples/basic/components/comparison-table.html +24 -0
  45. package/examples/basic/components/glyph.html +6 -0
  46. package/examples/basic/components/hover-note.html +6 -0
  47. package/examples/basic/components/media-frame.html +15 -0
  48. package/examples/basic/components/notice-panel.html +24 -0
  49. package/examples/basic/components/resource-tile.html +24 -0
  50. package/examples/basic/components/stack-shell.html +3 -0
  51. package/examples/basic/dist/askama/badge-chip.rs +18 -0
  52. package/examples/basic/dist/askama/comparison-table.rs +47 -0
  53. package/examples/basic/dist/askama/glyph.rs +21 -0
  54. package/examples/basic/dist/askama/hover-note.rs +19 -0
  55. package/examples/basic/dist/askama/includes/badge-chip.html +5 -0
  56. package/examples/basic/dist/askama/includes/comparison-table.html +34 -0
  57. package/examples/basic/dist/askama/includes/glyph.html +6 -0
  58. package/examples/basic/dist/askama/includes/hover-note.html +6 -0
  59. package/examples/basic/dist/askama/includes/media-frame.html +23 -0
  60. package/examples/basic/dist/askama/includes/notice-panel.html +34 -0
  61. package/examples/basic/dist/askama/includes/resource-tile.html +42 -0
  62. package/examples/basic/dist/askama/includes/stack-shell.html +5 -0
  63. package/examples/basic/dist/askama/media-frame.rs +37 -0
  64. package/examples/basic/dist/askama/notice-panel.rs +49 -0
  65. package/examples/basic/dist/askama/resource-tile.rs +59 -0
  66. package/examples/basic/dist/askama/stack-shell.rs +17 -0
  67. package/examples/basic/dist/jsx-static/badge-chip.tsx +18 -0
  68. package/examples/basic/dist/jsx-static/comparison-table.tsx +53 -0
  69. package/examples/basic/dist/jsx-static/glyph.tsx +21 -0
  70. package/examples/basic/dist/jsx-static/hover-note.tsx +19 -0
  71. package/examples/basic/dist/jsx-static/media-frame.tsx +41 -0
  72. package/examples/basic/dist/jsx-static/notice-panel.tsx +53 -0
  73. package/examples/basic/dist/jsx-static/resource-tile.tsx +63 -0
  74. package/examples/basic/dist/jsx-static/stack-shell.tsx +17 -0
  75. package/examples/basic/dist/react/badge-chip.tsx +18 -0
  76. package/examples/basic/dist/react/comparison-table.tsx +53 -0
  77. package/examples/basic/dist/react/glyph.tsx +21 -0
  78. package/examples/basic/dist/react/hover-note.tsx +19 -0
  79. package/examples/basic/dist/react/media-frame.tsx +41 -0
  80. package/examples/basic/dist/react/notice-panel.tsx +53 -0
  81. package/examples/basic/dist/react/resource-tile.tsx +63 -0
  82. package/examples/basic/dist/react/stack-shell.tsx +17 -0
  83. package/examples/basic/src/templates/badge-chip.template.tsx +18 -0
  84. package/examples/basic/src/templates/comparison-table.template.tsx +35 -0
  85. package/examples/basic/src/templates/glyph.template.tsx +17 -0
  86. package/examples/basic/src/templates/hover-note.template.tsx +17 -0
  87. package/examples/basic/src/templates/media-frame.template.tsx +25 -0
  88. package/examples/basic/src/templates/notice-panel.template.tsx +40 -0
  89. package/examples/basic/src/templates/resource-tile.template.tsx +51 -0
  90. package/examples/basic/src/templates/stack-shell.template.tsx +13 -0
  91. package/examples/basic/tsconfig.json +10 -0
  92. package/package.json +69 -0
@@ -0,0 +1,60 @@
1
+ # Contributing
2
+
3
+ Thanks for taking an interest in Katachi.
4
+
5
+ ## Before you start
6
+
7
+ Katachi is still early. The current priority is keeping the compiler small, understandable, and honest about its supported surface.
8
+
9
+ Please open an issue or discussion before starting large feature work, especially for:
10
+
11
+ - new targets
12
+ - new authoring syntax
13
+ - parser rewrites
14
+ - runtime behavior changes
15
+
16
+ ## Local setup
17
+
18
+ ```bash
19
+ pnpm install
20
+ pnpm typecheck
21
+ pnpm test
22
+ pnpm build
23
+ pnpm verify:examples
24
+ ```
25
+
26
+ ## Contribution guidelines
27
+
28
+ - Keep changes focused.
29
+ - Prefer extending the typed compiler model over adding special cases.
30
+ - Add tests for parser, emitter, or build behavior when you change them.
31
+ - Update docs when the supported authoring surface changes.
32
+ - Do not broaden the supported TSX subset without documenting it.
33
+
34
+ ## Project shape
35
+
36
+ The most important internal layers are:
37
+
38
+ - `src/core/parser.ts`
39
+ - `src/core/ast.ts`
40
+ - `src/core/types.ts`
41
+ - `src/targets/*`
42
+ - `tests/*.test.ts`
43
+
44
+ If you add a new target:
45
+
46
+ 1. create a target module in `src/targets/`
47
+ 2. register it in `src/targets/index.ts`
48
+ 3. add tests for the generated output
49
+
50
+ ## Scope
51
+
52
+ Katachi is intentionally constrained.
53
+
54
+ Non-goals include:
55
+
56
+ - full React semantics
57
+ - arbitrary JavaScript execution in templates
58
+ - full Askama parity in the authoring language
59
+
60
+ If a proposal pushes Katachi toward being a general-purpose framework compiler, it should be justified very carefully.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Relevate
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,194 @@
1
+ # Katachi
2
+
3
+ Katachi lets you author template-like components once in restricted TSX and
4
+ compile them to multiple outputs.
5
+
6
+ Today it can emit:
7
+
8
+ - React TSX components
9
+ - static-oriented TSX components
10
+ - Askama Rust wrapper files
11
+ - Askama include partials
12
+
13
+ Katachi is still early, but it is already usable if you need one component
14
+ source that can target both React-style environments and Askama.
15
+
16
+ ## Getting Started
17
+
18
+ Try it once without installing it:
19
+
20
+ ```bash
21
+ pnpm dlx @relevate/katachi build
22
+ ```
23
+
24
+ or:
25
+
26
+ ```bash
27
+ npx @relevate/katachi build
28
+ ```
29
+
30
+ If you want Katachi in your project, install it:
31
+
32
+ ```bash
33
+ pnpm add -D @relevate/katachi
34
+ ```
35
+
36
+ or:
37
+
38
+ ```bash
39
+ npm install --save-dev @relevate/katachi
40
+ ```
41
+
42
+ Then add Katachi's JSX typing layer to `tsconfig.json`:
43
+
44
+ ```json
45
+ {
46
+ "compilerOptions": {
47
+ "jsx": "preserve",
48
+ "types": ["node", "@relevate/katachi/jsx"]
49
+ }
50
+ }
51
+ ```
52
+
53
+ If you already use a `types` array, append `@relevate/katachi/jsx` instead of
54
+ replacing your existing entries.
55
+
56
+ By default, Katachi reads templates from:
57
+
58
+ ```txt
59
+ src/templates/**/*.template.tsx
60
+ ```
61
+
62
+ Build from your project root:
63
+
64
+ ```bash
65
+ pnpm exec katachi build
66
+ ```
67
+
68
+ or without installing it first:
69
+
70
+ ```bash
71
+ pnpm dlx @relevate/katachi build
72
+ ```
73
+
74
+ By default, Katachi writes:
75
+
76
+ - `dist/react`
77
+ - `dist/jsx-static`
78
+ - `dist/askama`
79
+ - `dist/askama/includes`
80
+
81
+ If you want custom paths:
82
+
83
+ ```bash
84
+ pnpm exec katachi build --templates ./katachi/templates --dist ./generated
85
+ ```
86
+
87
+ ## First Template
88
+
89
+ Start with a normal `.template.tsx` file:
90
+
91
+ ```tsx
92
+ import { If, type TemplateNode } from "@relevate/katachi";
93
+
94
+ export type Props = {
95
+ tone: "calm" | "urgent";
96
+ title: string;
97
+ children?: TemplateNode;
98
+ };
99
+
100
+ export default function NoticePanel({ tone, title, children }: Props) {
101
+ return (
102
+ <aside
103
+ className={[
104
+ "rounded-3xl border px-5 py-4",
105
+ tone == "calm" && "border-sky-200 bg-sky-50/80",
106
+ tone == "urgent" && "border-rose-200 bg-rose-50/80",
107
+ ]}
108
+ >
109
+ <h3>{title}</h3>
110
+ <If test={tone == "urgent"}>
111
+ <p>Action recommended</p>
112
+ </If>
113
+ {children}
114
+ </aside>
115
+ );
116
+ }
117
+ ```
118
+
119
+ Build it with:
120
+
121
+ ```bash
122
+ pnpm exec katachi build
123
+ ```
124
+
125
+ That generates target-specific files under `dist/`.
126
+
127
+ ## What Katachi Generates
128
+
129
+ By default, build output goes to:
130
+
131
+ - `dist/react/**/*.tsx`
132
+ - `dist/jsx-static/**/*.tsx`
133
+ - `dist/askama/**/*.rs`
134
+ - `dist/askama/includes/**/*.html`
135
+
136
+ Nested templates preserve their relative directory layout.
137
+
138
+ ## What Katachi Supports Today
139
+
140
+ - template authoring in `src/templates/**/*.template.tsx`
141
+ - imports between templates
142
+ - dynamic `class` and `className` arrays
143
+ - `If`
144
+ - `For`
145
+ - `safe(...)`
146
+ - nested components
147
+ - React output
148
+ - static-oriented TSX output
149
+ - Askama output
150
+
151
+ ## Why Katachi Exists
152
+
153
+ At Relevate, our docs system is built in Rust and renders components with
154
+ Askama. We also wanted a live editor that could use the same component
155
+ structure and styling without maintaining a separate hand-written React
156
+ component library.
157
+
158
+ Katachi exists to make that possible: one authoring format, multiple outputs.
159
+
160
+ ## What Katachi Is Not
161
+
162
+ - not a full React compiler
163
+ - not a full Askama replacement
164
+ - not arbitrary JavaScript execution in templates
165
+ - not a general-purpose frontend framework
166
+
167
+ ## Documentation
168
+
169
+ - [Getting started](./docs/getting-started.md)
170
+ - [Template syntax](./docs/syntax.md)
171
+ - [Target outputs](./docs/targets.md)
172
+ - [Architecture](./docs/architecture.md)
173
+ - [Consumer example](./examples/basic/README.md)
174
+
175
+ ## Current Limitations
176
+
177
+ - The TSX input is not arbitrary React.
178
+ - Generated React output is valid React, but the input syntax is compiler-owned.
179
+ - The repository-level smoke tests build the public example project under `examples/basic`.
180
+
181
+ ## Contributing
182
+
183
+ If you are working on Katachi itself rather than using it in another project:
184
+
185
+ - `pnpm build`
186
+ - `pnpm verify:examples`
187
+ - `pnpm test`
188
+ - `pnpm typecheck`
189
+
190
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) and [docs/architecture.md](./docs/architecture.md).
191
+
192
+ ## License
193
+
194
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync } from "node:fs";
4
+ import { spawnSync } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+ import { dirname, resolve } from "node:path";
7
+
8
+ const binDir = dirname(fileURLToPath(import.meta.url));
9
+ const packageRoot = resolve(binDir, "..");
10
+ const builtCliPath = resolve(packageRoot, "dist/cli/index.js");
11
+ const sourceCliPath = resolve(packageRoot, "src/cli/index.ts");
12
+
13
+ const hasBuiltCli = existsSync(builtCliPath);
14
+
15
+ const result = spawnSync(
16
+ process.execPath,
17
+ hasBuiltCli
18
+ ? [builtCliPath, ...process.argv.slice(2)]
19
+ : ["--import", "tsx/esm", sourceCliPath, ...process.argv.slice(2)],
20
+ {
21
+ cwd: process.cwd(),
22
+ stdio: "inherit",
23
+ },
24
+ );
25
+
26
+ if (typeof result.status === "number") {
27
+ process.exit(result.status);
28
+ }
29
+
30
+ process.exit(1);
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Public API helpers exposed to Katachi template files.
3
+ *
4
+ * These exports exist primarily for editor support and package consumers. The
5
+ * Katachi compiler parses template source text directly, so these helpers are
6
+ * not expected to execute in normal builds.
7
+ */
8
+ export type ClassValue = string | number | boolean | null | undefined | ClassValue[];
9
+ export type TemplateNode = string | number | boolean | null | undefined | TemplateNode[];
10
+ export type IfProps = {
11
+ test: unknown;
12
+ children?: TemplateNode;
13
+ };
14
+ export type ForProps<T = unknown> = {
15
+ each: readonly T[] | T[] | null | undefined;
16
+ as: string;
17
+ index?: string;
18
+ children?: TemplateNode;
19
+ };
20
+ /**
21
+ * Placeholder runtime export for template files. The compiler reads source
22
+ * templates directly and never evaluates this function during normal use.
23
+ */
24
+ export declare function If(_props: IfProps): TemplateNode;
25
+ /**
26
+ * Placeholder runtime export for template files. The compiler reads source
27
+ * templates directly and never evaluates this function during normal use.
28
+ */
29
+ export declare function For<T>(_props: ForProps<T>): TemplateNode;
30
+ /**
31
+ * Marks a printed value as safe in Katachi templates. This is a no-op at the
32
+ * API layer because escaping is handled by target emitters.
33
+ */
34
+ export declare function safe<T>(value: T): T;
35
+ /**
36
+ * Portable length helper for Katachi templates.
37
+ */
38
+ export declare function len(value: {
39
+ length: number;
40
+ } | string | readonly unknown[] | null | undefined): number;
41
+ /**
42
+ * Portable emptiness helper for Katachi templates.
43
+ */
44
+ export declare function isEmpty(value: {
45
+ length: number;
46
+ } | string | readonly unknown[] | null | undefined): boolean;
47
+ /**
48
+ * Portable presence helper for Katachi templates.
49
+ */
50
+ export declare function isSome<T>(value: T | null | undefined): value is T;
51
+ /**
52
+ * Portable absence helper for Katachi templates.
53
+ */
54
+ export declare function isNone<T>(value: T | null | undefined): value is null | undefined;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Placeholder runtime export for template files. The compiler reads source
3
+ * templates directly and never evaluates this function during normal use.
4
+ */
5
+ export function If(_props) {
6
+ return null;
7
+ }
8
+ /**
9
+ * Placeholder runtime export for template files. The compiler reads source
10
+ * templates directly and never evaluates this function during normal use.
11
+ */
12
+ export function For(_props) {
13
+ return null;
14
+ }
15
+ /**
16
+ * Marks a printed value as safe in Katachi templates. This is a no-op at the
17
+ * API layer because escaping is handled by target emitters.
18
+ */
19
+ export function safe(value) {
20
+ return value;
21
+ }
22
+ /**
23
+ * Portable length helper for Katachi templates.
24
+ */
25
+ export function len(value) {
26
+ return value?.length ?? 0;
27
+ }
28
+ /**
29
+ * Portable emptiness helper for Katachi templates.
30
+ */
31
+ export function isEmpty(value) {
32
+ return (value?.length ?? 0) === 0;
33
+ }
34
+ /**
35
+ * Portable presence helper for Katachi templates.
36
+ */
37
+ export function isSome(value) {
38
+ return value != null;
39
+ }
40
+ /**
41
+ * Portable absence helper for Katachi templates.
42
+ */
43
+ export function isNone(value) {
44
+ return value == null;
45
+ }
@@ -0,0 +1,26 @@
1
+ import type { ClassValue, TemplateNode } from "./index.js";
2
+
3
+ declare global {
4
+ namespace JSX {
5
+ /**
6
+ * Authoring templates do not produce real JSX runtime elements. The parser
7
+ * reads the source text and lowers it into Katachi AST nodes instead.
8
+ */
9
+ type Element = TemplateNode;
10
+
11
+ interface ElementChildrenAttribute {
12
+ children: {};
13
+ }
14
+
15
+ interface IntrinsicElements {
16
+ [elemName: string]: {
17
+ children?: TemplateNode;
18
+ class?: ClassValue;
19
+ className?: ClassValue;
20
+ [attrName: string]: unknown;
21
+ };
22
+ }
23
+ }
24
+ }
25
+
26
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,77 @@
1
+ import { resolve } from "node:path";
2
+ import { buildProject } from "../core/build.js";
3
+ import { verifyAskamaFixtures } from "../core/verify.js";
4
+ import { basicExampleRoot, createExampleFixtures, exampleFixtures } from "../core/example-fixtures.js";
5
+ function printHelp() {
6
+ console.log(`Katachi
7
+
8
+ Usage:
9
+ katachi build [--project <dir>] [--templates <dir>] [--dist <dir>]
10
+ katachi verify:examples
11
+ katachi help
12
+
13
+ Defaults:
14
+ --project current working directory
15
+ --templates <project>/src/templates
16
+ --dist <project>/dist`);
17
+ }
18
+ function parseArgs(argv) {
19
+ const [commandArg, ...rest] = argv;
20
+ const command = (commandArg ?? "build");
21
+ if (!["build", "verify:askama", "verify:examples", "help"].includes(command)) {
22
+ throw new Error(`Unknown command: ${command}`);
23
+ }
24
+ const options = { command };
25
+ for (let index = 0; index < rest.length; index += 1) {
26
+ const current = rest[index];
27
+ const next = rest[index + 1];
28
+ if (current === "--project" && next) {
29
+ options.projectRoot = resolve(next);
30
+ index += 1;
31
+ continue;
32
+ }
33
+ if (current === "--templates" && next) {
34
+ options.templatesDir = resolve(next);
35
+ index += 1;
36
+ continue;
37
+ }
38
+ if (current === "--dist" && next) {
39
+ options.distDir = resolve(next);
40
+ index += 1;
41
+ continue;
42
+ }
43
+ throw new Error(`Unknown or incomplete option: ${current}`);
44
+ }
45
+ return options;
46
+ }
47
+ function run() {
48
+ const options = parseArgs(process.argv.slice(2));
49
+ if (options.command === "help") {
50
+ printHelp();
51
+ return;
52
+ }
53
+ if (options.command === "build") {
54
+ buildProject({
55
+ projectRoot: options.projectRoot,
56
+ templatesDir: options.templatesDir,
57
+ distDir: options.distDir,
58
+ });
59
+ return;
60
+ }
61
+ if (options.command === "verify:examples" || options.command === "verify:askama") {
62
+ const projectRoot = options.projectRoot ?? basicExampleRoot;
63
+ const distDir = options.distDir ?? resolve(projectRoot, "dist");
64
+ buildProject({
65
+ projectRoot,
66
+ templatesDir: options.templatesDir,
67
+ distDir,
68
+ });
69
+ verifyAskamaFixtures({
70
+ fixtures: projectRoot === basicExampleRoot && distDir === resolve(basicExampleRoot, "dist")
71
+ ? exampleFixtures
72
+ : createExampleFixtures(projectRoot, distDir),
73
+ });
74
+ return;
75
+ }
76
+ }
77
+ run();
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Canonical portable AST used by Katachi after parsing authoring input.
3
+ *
4
+ * Targets emit from this model instead of parsing templates themselves.
5
+ */
6
+ export type Expr = {
7
+ kind: "var";
8
+ name: string;
9
+ } | {
10
+ kind: "string";
11
+ value: string;
12
+ } | {
13
+ kind: "bool";
14
+ value: boolean;
15
+ } | {
16
+ kind: "number";
17
+ value: number;
18
+ } | {
19
+ kind: "intrinsic";
20
+ name: "len" | "isEmpty" | "isSome" | "isNone";
21
+ args: Expr[];
22
+ } | {
23
+ kind: "raw";
24
+ source: string;
25
+ } | {
26
+ kind: "eq";
27
+ left: Expr;
28
+ right: Expr;
29
+ } | {
30
+ kind: "neq";
31
+ left: Expr;
32
+ right: Expr;
33
+ } | {
34
+ kind: "and";
35
+ left: Expr;
36
+ right: Expr;
37
+ } | {
38
+ kind: "or";
39
+ left: Expr;
40
+ right: Expr;
41
+ } | {
42
+ kind: "not";
43
+ expr: Expr;
44
+ };
45
+ export type ClassItem = {
46
+ kind: "static";
47
+ value: string;
48
+ } | {
49
+ kind: "when";
50
+ test: Expr;
51
+ value: string;
52
+ };
53
+ export type AttrValue = {
54
+ kind: "text";
55
+ value: string;
56
+ } | {
57
+ kind: "expr";
58
+ expr: Expr;
59
+ } | {
60
+ kind: "classList";
61
+ items: ClassItem[];
62
+ };
63
+ export type Node = {
64
+ kind: "text";
65
+ value: string;
66
+ } | {
67
+ kind: "slot";
68
+ name: string;
69
+ } | {
70
+ kind: "print";
71
+ expr: Expr;
72
+ safe?: boolean;
73
+ } | {
74
+ kind: "if";
75
+ test: Expr;
76
+ then: Node[];
77
+ else?: Node[];
78
+ } | {
79
+ kind: "for";
80
+ item: string;
81
+ each: Expr;
82
+ children: Node[];
83
+ indexName?: string | null;
84
+ } | {
85
+ kind: "element";
86
+ tag: string;
87
+ attrs?: Record<string, AttrValue>;
88
+ children?: Node[];
89
+ } | {
90
+ kind: "component";
91
+ name: string;
92
+ props?: Record<string, AttrValue>;
93
+ children?: Node[];
94
+ };
95
+ export declare const v: (name: string) => Expr;
96
+ export declare const s: (value: string) => Expr;
97
+ export declare const b: (value: boolean) => Expr;
98
+ export declare const n: (value: number) => Expr;
99
+ export declare const intrinsic: (name: "len" | "isEmpty" | "isSome" | "isNone", ...args: Expr[]) => Expr;
100
+ export declare const raw: (source: string) => Expr;
101
+ export declare const eq: (left: Expr, right: Expr) => Expr;
102
+ export declare const neq: (left: Expr, right: Expr) => Expr;
103
+ export declare const and: (left: Expr, right: Expr) => Expr;
104
+ export declare const or: (left: Expr, right: Expr) => Expr;
105
+ export declare const not: (expr: Expr) => Expr;
106
+ export declare const textAttr: (value: string) => AttrValue;
107
+ export declare const exprAttr: (expr: Expr) => AttrValue;
108
+ export declare const classList: (...items: ClassItem[]) => AttrValue;
109
+ export declare const textNode: (value: string) => Node;
110
+ export declare const slotNode: (name: string) => Node;
111
+ export declare const printNode: (expr: Expr, safe?: boolean) => Node;
112
+ export declare const ifNode: (test: Expr, thenNodes: Node[], elseNodes?: Node[]) => Node;
113
+ export declare const forNode: (item: string, each: Expr, children?: Node[], indexName?: string | null) => Node;
114
+ export declare const elementNode: (tag: string, attrs?: Record<string, AttrValue>, children?: Node[]) => Node;
115
+ export declare const componentNode: (name: string, props?: Record<string, AttrValue>, children?: Node[]) => Node;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Canonical portable AST used by Katachi after parsing authoring input.
3
+ *
4
+ * Targets emit from this model instead of parsing templates themselves.
5
+ */
6
+ export const v = (name) => ({ kind: "var", name });
7
+ export const s = (value) => ({ kind: "string", value });
8
+ export const b = (value) => ({ kind: "bool", value });
9
+ export const n = (value) => ({ kind: "number", value });
10
+ export const intrinsic = (name, ...args) => ({
11
+ kind: "intrinsic",
12
+ name,
13
+ args,
14
+ });
15
+ export const raw = (source) => ({ kind: "raw", source });
16
+ export const eq = (left, right) => ({ kind: "eq", left, right });
17
+ export const neq = (left, right) => ({ kind: "neq", left, right });
18
+ export const and = (left, right) => ({ kind: "and", left, right });
19
+ export const or = (left, right) => ({ kind: "or", left, right });
20
+ export const not = (expr) => ({ kind: "not", expr });
21
+ export const textAttr = (value) => ({ kind: "text", value });
22
+ export const exprAttr = (expr) => ({ kind: "expr", expr });
23
+ export const classList = (...items) => ({ kind: "classList", items });
24
+ export const textNode = (value) => ({ kind: "text", value });
25
+ export const slotNode = (name) => ({ kind: "slot", name });
26
+ export const printNode = (expr, safe = false) => ({ kind: "print", expr, safe });
27
+ export const ifNode = (test, thenNodes, elseNodes = []) => ({
28
+ kind: "if",
29
+ test,
30
+ then: thenNodes,
31
+ else: elseNodes,
32
+ });
33
+ export const forNode = (item, each, children = [], indexName = null) => ({
34
+ kind: "for",
35
+ item,
36
+ each,
37
+ children,
38
+ indexName,
39
+ });
40
+ export const elementNode = (tag, attrs = {}, children = []) => ({
41
+ kind: "element",
42
+ tag,
43
+ attrs,
44
+ children,
45
+ });
46
+ export const componentNode = (name, props = {}, children = []) => ({
47
+ kind: "component",
48
+ name,
49
+ props,
50
+ children,
51
+ });
@@ -0,0 +1,15 @@
1
+ import type { BuildTemplate } from "./types.js";
2
+ export interface BuildProjectOptions {
3
+ projectRoot?: string;
4
+ distDir?: string;
5
+ templatesDir?: string;
6
+ logger?: Pick<Console, "log">;
7
+ }
8
+ export interface BuildProjectResult {
9
+ templates: BuildTemplate[];
10
+ writtenFiles: string[];
11
+ }
12
+ /**
13
+ * Builds the current project and writes all configured outputs to `dist/`.
14
+ */
15
+ export declare function buildProject(options?: BuildProjectOptions): BuildProjectResult;