@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,107 @@
1
+ import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { basename, dirname, join, relative, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { parseTemplateFile } from "./parser.js";
5
+ import { outputTargets } from "../targets/index.js";
6
+ /**
7
+ * Recursively finds all Katachi template files.
8
+ */
9
+ function collectTemplateFiles(directory) {
10
+ const entries = readdirSync(directory, { withFileTypes: true });
11
+ const files = [];
12
+ for (const entry of entries) {
13
+ const fullPath = resolve(directory, entry.name);
14
+ if (entry.isDirectory()) {
15
+ files.push(...collectTemplateFiles(fullPath));
16
+ continue;
17
+ }
18
+ if (entry.isFile() && entry.name.endsWith(".template.tsx")) {
19
+ files.push(fullPath);
20
+ }
21
+ }
22
+ return files;
23
+ }
24
+ function toRelativeModulePath(fromRelativePath, toRelativePathWithoutExtension) {
25
+ const fromDir = dirname(fromRelativePath);
26
+ const relativePath = relative(fromDir, toRelativePathWithoutExtension).replaceAll("\\", "/");
27
+ if (!relativePath || !relativePath.startsWith(".")) {
28
+ return `./${relativePath}`;
29
+ }
30
+ return relativePath;
31
+ }
32
+ function toRelativeIncludePath(fromRelativePath, importedRelativePath) {
33
+ const fromDir = dirname(fromRelativePath);
34
+ const includeTarget = join("includes", dirname(importedRelativePath), `${basename(importedRelativePath).replace(/\.template\.tsx$/, "")}.html`);
35
+ const relativePath = relative(fromDir, includeTarget).replaceAll("\\", "/");
36
+ if (!relativePath || !relativePath.startsWith(".")) {
37
+ return `./${relativePath}`;
38
+ }
39
+ return relativePath;
40
+ }
41
+ /**
42
+ * Builds the current project and writes all configured outputs to `dist/`.
43
+ */
44
+ export function buildProject(options = {}) {
45
+ const projectRoot = options.projectRoot ?? process.cwd();
46
+ const distDir = options.distDir ?? resolve(projectRoot, "dist");
47
+ const templatesDir = options.templatesDir ?? resolve(projectRoot, "src/templates");
48
+ const logger = options.logger ?? console;
49
+ const writtenFiles = [];
50
+ mkdirSync(distDir, { recursive: true });
51
+ const templateFiles = collectTemplateFiles(templatesDir);
52
+ const parsedTemplates = templateFiles.map((filePath) => {
53
+ const source = readFileSync(filePath, "utf8");
54
+ const relativePath = relative(templatesDir, filePath);
55
+ const fileName = basename(relativePath).replace(/\.template\.tsx$/, "");
56
+ return {
57
+ ...parseTemplateFile(source),
58
+ sourcePath: filePath,
59
+ relativePath,
60
+ fileName,
61
+ componentRegistry: {},
62
+ };
63
+ });
64
+ const templateByPath = new Map(parsedTemplates.map((template) => [template.sourcePath, template]));
65
+ for (const template of parsedTemplates) {
66
+ const componentRegistry = {};
67
+ for (const entry of template.imports ?? []) {
68
+ if (!entry.source.includes(".template")) {
69
+ continue;
70
+ }
71
+ const importPath = entry.source.endsWith(".tsx")
72
+ ? entry.source
73
+ : `${entry.source}.tsx`;
74
+ const resolvedPath = resolve(dirname(template.sourcePath), importPath);
75
+ const importedTemplate = templateByPath.get(resolvedPath);
76
+ if (!importedTemplate) {
77
+ throw new Error(`Could not resolve imported component ${entry.localName} from ${template.sourcePath}: ${entry.source}`);
78
+ }
79
+ componentRegistry[entry.localName] = {
80
+ reactImport: toRelativeModulePath(template.relativePath, importedTemplate.relativePath.replace(/\.template\.tsx$/, "")),
81
+ include: toRelativeIncludePath(template.relativePath, importedTemplate.relativePath),
82
+ };
83
+ }
84
+ template.componentRegistry = componentRegistry;
85
+ const templateDir = dirname(template.relativePath);
86
+ for (const target of outputTargets) {
87
+ for (const output of target.emitFiles(template)) {
88
+ const outputPath = join(distDir, target.outputSubdir, templateDir, output.fileName);
89
+ mkdirSync(dirname(outputPath), { recursive: true });
90
+ writeFileSync(outputPath, output.content, "utf8");
91
+ writtenFiles.push(outputPath);
92
+ logger.log(`wrote ${outputPath}`);
93
+ }
94
+ }
95
+ }
96
+ return {
97
+ templates: parsedTemplates,
98
+ writtenFiles,
99
+ };
100
+ }
101
+ function isMainModule(metaUrl) {
102
+ const entry = process.argv[1];
103
+ return Boolean(entry) && resolve(entry) === fileURLToPath(metaUrl);
104
+ }
105
+ if (isMainModule(import.meta.url)) {
106
+ buildProject();
107
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Compatibility barrel for existing internal imports.
3
+ *
4
+ * New code should usually import from `./ast` or `./targets/*` directly.
5
+ */
6
+ export * from "./ast.js";
7
+ export { emitReact, emitReactComponent } from "../targets/react.js";
8
+ export { emitStaticJsx, emitStaticJsxComponent } from "../targets/static-jsx.js";
9
+ export { emitAskama, emitAskamaComponent, emitAskamaPartial } from "../targets/askama.js";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Compatibility barrel for existing internal imports.
3
+ *
4
+ * New code should usually import from `./ast` or `./targets/*` directly.
5
+ */
6
+ export * from "./ast.js";
7
+ export { emitReact, emitReactComponent } from "../targets/react.js";
8
+ export { emitStaticJsx, emitStaticJsxComponent } from "../targets/static-jsx.js";
9
+ export { emitAskama, emitAskamaComponent, emitAskamaPartial } from "../targets/askama.js";
@@ -0,0 +1,5 @@
1
+ import type { Fixture } from "./verify.js";
2
+ export declare function resolveExampleRoot(exampleName?: string): string;
3
+ export declare function createExampleFixtures(exampleRoot: string, distDir?: string): Fixture[];
4
+ export declare const basicExampleRoot: string;
5
+ export declare const exampleFixtures: Fixture[];
@@ -0,0 +1,54 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { resolve } from "node:path";
3
+ const rootDir = fileURLToPath(new URL("../..", import.meta.url));
4
+ export function resolveExampleRoot(exampleName = "basic") {
5
+ return resolve(rootDir, "examples", exampleName);
6
+ }
7
+ export function createExampleFixtures(exampleRoot, distDir = resolve(exampleRoot, "dist")) {
8
+ const componentsDir = resolve(exampleRoot, "components");
9
+ const askamaIncludesDir = resolve(distDir, "askama", "includes");
10
+ return [
11
+ {
12
+ name: "examples/basic/badge-chip",
13
+ source: resolve(componentsDir, "badge-chip.html"),
14
+ generated: resolve(askamaIncludesDir, "badge-chip.html"),
15
+ },
16
+ {
17
+ name: "examples/basic/comparison-table",
18
+ source: resolve(componentsDir, "comparison-table.html"),
19
+ generated: resolve(askamaIncludesDir, "comparison-table.html"),
20
+ },
21
+ {
22
+ name: "examples/basic/glyph",
23
+ source: resolve(componentsDir, "glyph.html"),
24
+ generated: resolve(askamaIncludesDir, "glyph.html"),
25
+ },
26
+ {
27
+ name: "examples/basic/hover-note",
28
+ source: resolve(componentsDir, "hover-note.html"),
29
+ generated: resolve(askamaIncludesDir, "hover-note.html"),
30
+ },
31
+ {
32
+ name: "examples/basic/media-frame",
33
+ source: resolve(componentsDir, "media-frame.html"),
34
+ generated: resolve(askamaIncludesDir, "media-frame.html"),
35
+ },
36
+ {
37
+ name: "examples/basic/notice-panel",
38
+ source: resolve(componentsDir, "notice-panel.html"),
39
+ generated: resolve(askamaIncludesDir, "notice-panel.html"),
40
+ },
41
+ {
42
+ name: "examples/basic/resource-tile",
43
+ source: resolve(componentsDir, "resource-tile.html"),
44
+ generated: resolve(askamaIncludesDir, "resource-tile.html"),
45
+ },
46
+ {
47
+ name: "examples/basic/stack-shell",
48
+ source: resolve(componentsDir, "stack-shell.html"),
49
+ generated: resolve(askamaIncludesDir, "stack-shell.html"),
50
+ },
51
+ ];
52
+ }
53
+ export const basicExampleRoot = resolveExampleRoot();
54
+ export const exampleFixtures = createExampleFixtures(basicExampleRoot);
@@ -0,0 +1,5 @@
1
+ import type { ParsedTemplate } from "./types.js";
2
+ /**
3
+ * Parses a template module into the portable compiler representation.
4
+ */
5
+ export declare function parseTemplateFile(source: string): ParsedTemplate;