@alloy-js/core 0.19.0-dev.12 → 0.19.0-dev.3

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 (95) hide show
  1. package/dist/src/components/index.d.ts +0 -4
  2. package/dist/src/components/index.d.ts.map +1 -1
  3. package/dist/src/components/index.js +0 -4
  4. package/dist/src/components/stc/index.d.ts +0 -4
  5. package/dist/src/components/stc/index.d.ts.map +1 -1
  6. package/dist/src/components/stc/index.js +0 -4
  7. package/dist/src/context/source-directory.d.ts +3 -3
  8. package/dist/src/context/source-directory.d.ts.map +1 -1
  9. package/dist/src/context/source-file.d.ts +0 -4
  10. package/dist/src/context/source-file.d.ts.map +1 -1
  11. package/dist/src/debug.d.ts.map +1 -1
  12. package/dist/src/debug.js +1 -4
  13. package/dist/src/index.browser.d.ts +1 -1
  14. package/dist/src/index.browser.d.ts.map +1 -1
  15. package/dist/src/index.browser.js +2 -2
  16. package/dist/src/render.d.ts +2 -10
  17. package/dist/src/render.d.ts.map +1 -1
  18. package/dist/src/render.js +1 -20
  19. package/dist/src/scheduler.d.ts +0 -6
  20. package/dist/src/scheduler.d.ts.map +1 -1
  21. package/dist/src/scheduler.js +0 -36
  22. package/dist/src/write-output.browser.d.ts +2 -0
  23. package/dist/src/write-output.browser.d.ts.map +1 -0
  24. package/dist/src/write-output.browser.js +4 -0
  25. package/dist/src/write-output.d.ts +1 -1
  26. package/dist/src/write-output.d.ts.map +1 -1
  27. package/dist/src/write-output.js +21 -40
  28. package/dist/test/components/source-file.test.d.ts.map +1 -1
  29. package/dist/test/rendering/formatting.test.d.ts.map +1 -1
  30. package/dist/testing/extend-expect.js +54 -60
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +3 -3
  33. package/src/components/index.tsx +0 -4
  34. package/src/components/stc/index.ts +0 -4
  35. package/src/context/source-directory.ts +3 -5
  36. package/src/context/source-file.ts +0 -5
  37. package/src/debug.ts +1 -4
  38. package/src/index.browser.ts +1 -1
  39. package/src/render.ts +5 -44
  40. package/src/scheduler.ts +0 -39
  41. package/src/write-output.browser.ts +4 -0
  42. package/src/write-output.ts +19 -49
  43. package/temp/api.json +423 -1886
  44. package/test/components/source-file.test.tsx +2 -5
  45. package/test/rendering/formatting.test.tsx +3 -9
  46. package/testing/extend-expect.ts +58 -74
  47. package/testing/vitest.d.ts +0 -4
  48. package/dist/src/components/AppendFile.d.ts +0 -90
  49. package/dist/src/components/AppendFile.d.ts.map +0 -1
  50. package/dist/src/components/AppendFile.js +0 -226
  51. package/dist/src/components/CopyFile.d.ts +0 -12
  52. package/dist/src/components/CopyFile.d.ts.map +0 -1
  53. package/dist/src/components/CopyFile.js +0 -15
  54. package/dist/src/components/TemplateFile.d.ts +0 -84
  55. package/dist/src/components/TemplateFile.d.ts.map +0 -1
  56. package/dist/src/components/TemplateFile.js +0 -133
  57. package/dist/src/components/UpdateFile.d.ts +0 -34
  58. package/dist/src/components/UpdateFile.d.ts.map +0 -1
  59. package/dist/src/components/UpdateFile.js +0 -66
  60. package/dist/src/host/alloy-host.browser.d.ts +0 -11
  61. package/dist/src/host/alloy-host.browser.d.ts.map +0 -1
  62. package/dist/src/host/alloy-host.browser.js +0 -31
  63. package/dist/src/host/alloy-host.d.ts +0 -11
  64. package/dist/src/host/alloy-host.d.ts.map +0 -1
  65. package/dist/src/host/alloy-host.js +0 -143
  66. package/dist/src/host/interface.d.ts +0 -144
  67. package/dist/src/host/interface.d.ts.map +0 -1
  68. package/dist/src/host/interface.js +0 -1
  69. package/dist/src/resource.d.ts +0 -80
  70. package/dist/src/resource.d.ts.map +0 -1
  71. package/dist/src/resource.js +0 -118
  72. package/dist/test/components/append-file.test.d.ts +0 -2
  73. package/dist/test/components/append-file.test.d.ts.map +0 -1
  74. package/dist/test/components/append-file.test.js +0 -281
  75. package/dist/test/components/copy-file.test.d.ts +0 -2
  76. package/dist/test/components/copy-file.test.d.ts.map +0 -1
  77. package/dist/test/components/copy-file.test.js +0 -94
  78. package/dist/test/components/template-file.test.d.ts +0 -2
  79. package/dist/test/components/template-file.test.d.ts.map +0 -1
  80. package/dist/test/components/template-file.test.js +0 -133
  81. package/dist/test/components/update-file.test.d.ts +0 -2
  82. package/dist/test/components/update-file.test.d.ts.map +0 -1
  83. package/dist/test/components/update-file.test.js +0 -169
  84. package/src/components/AppendFile.tsx +0 -294
  85. package/src/components/CopyFile.tsx +0 -29
  86. package/src/components/TemplateFile.tsx +0 -193
  87. package/src/components/UpdateFile.tsx +0 -86
  88. package/src/host/alloy-host.browser.ts +0 -56
  89. package/src/host/alloy-host.ts +0 -160
  90. package/src/host/interface.ts +0 -153
  91. package/src/resource.ts +0 -152
  92. package/test/components/append-file.test.tsx +0 -275
  93. package/test/components/copy-file.test.tsx +0 -98
  94. package/test/components/template-file.test.tsx +0 -127
  95. package/test/components/update-file.test.tsx +0 -214
@@ -1,169 +0,0 @@
1
- import { createComponent as _$createComponent } from "@alloy-js/core/jsx-runtime";
2
- import { existsSync, unlinkSync, writeFileSync } from "fs";
3
- import { tmpdir } from "os";
4
- import { join } from "pathe";
5
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
6
- import { UpdateFile } from "../../src/components/UpdateFile.js";
7
- import { render } from "../../src/render.js";
8
- import "../../testing/extend-expect.js";
9
- import { d } from "../../testing/render.js";
10
- describe("UpdateFile", () => {
11
- let testFilePath;
12
- beforeEach(() => {
13
- testFilePath = join(tmpdir(), "test-file.txt");
14
- });
15
- afterEach(() => {
16
- if (existsSync(testFilePath)) {
17
- unlinkSync(testFilePath);
18
- }
19
- });
20
- describe("when file exists", () => {
21
- it("should read existing file and transform content", async () => {
22
- const existingContent = "Hello World";
23
- writeFileSync(testFilePath, existingContent, "utf-8");
24
- const result = _$createComponent(UpdateFile, {
25
- path: testFilePath,
26
- children: currentContents => {
27
- return `${currentContents?.toUpperCase()}`;
28
- }
29
- });
30
- await expect(result).toRenderToAsync("HELLO WORLD");
31
- });
32
- it("should handle JSON file updates", async () => {
33
- const existingConfig = {
34
- version: "1.0.0",
35
- name: "test"
36
- };
37
- writeFileSync(testFilePath, JSON.stringify(existingConfig), "utf-8");
38
- const result = _$createComponent(UpdateFile, {
39
- path: testFilePath,
40
- children: currentContents => {
41
- const config = JSON.parse(currentContents);
42
- config.version = "2.0.0";
43
- config.newProperty = "added";
44
- return JSON.stringify(config, null, 2);
45
- }
46
- });
47
- await expect(result).toRenderToAsync(d`
48
- {
49
- "version": "2.0.0",
50
- "name": "test",
51
- "newProperty": "added"
52
- }
53
- `);
54
- });
55
- it("should handle multiline content transformation", async () => {
56
- const existingContent = d`
57
- line 1
58
- line 2
59
- line 3
60
- `;
61
- writeFileSync(testFilePath, existingContent, "utf-8");
62
- const result = _$createComponent(UpdateFile, {
63
- path: testFilePath,
64
- children: currentContents => {
65
- const lines = currentContents.split("\n");
66
- const modifiedLines = lines.map(line => `// ${line}`);
67
- return modifiedLines.join("\n");
68
- }
69
- });
70
- await expect(result).toRenderToAsync(d`
71
- // line 1
72
- // line 2
73
- // line 3
74
- `);
75
- });
76
- it("should handle empty existing file", async () => {
77
- writeFileSync(testFilePath, "", "utf-8");
78
- const result = _$createComponent(UpdateFile, {
79
- path: testFilePath,
80
- children: currentContents => {
81
- return `Content was: "${currentContents || "empty"}"`;
82
- }
83
- });
84
- await expect(result).toRenderToAsync('Content was: "empty"');
85
- });
86
- });
87
- describe("when file does not exist", () => {
88
- it("should use defaultContent when file does not exist", async () => {
89
- const result = _$createComponent(UpdateFile, {
90
- path: "non-existent.txt",
91
- defaultContent: "default content",
92
- children: currentContents => {
93
- if (currentContents === null) {
94
- return `File did not exist`;
95
- }
96
- return `File existed with: ${currentContents}`;
97
- }
98
- });
99
- await expect(result).toRenderToAsync("File did not exist");
100
- });
101
- it("should use defaultContentPath when file does not exist", async () => {
102
- const defaultFilePath = join(tmpdir(), `default-content-${Date.now()}.txt`);
103
- const defaultContent = "This is default content from file";
104
- writeFileSync(defaultFilePath, defaultContent, "utf-8");
105
- try {
106
- const result = _$createComponent(UpdateFile, {
107
- path: "non-existent.txt",
108
- defaultContentPath: defaultFilePath,
109
- children: currentContents => {
110
- return `Default: ${currentContents}`;
111
- }
112
- });
113
- await expect(result).toRenderToAsync("Default: This is default content from file");
114
- } finally {
115
- if (existsSync(defaultFilePath)) {
116
- unlinkSync(defaultFilePath);
117
- }
118
- }
119
- });
120
- it("should pass null to children when no default content provided", async () => {
121
- const result = _$createComponent(UpdateFile, {
122
- path: "non-existent.txt",
123
- children: currentContents => {
124
- if (currentContents === null) {
125
- return `Creating new file`;
126
- }
127
- return `Updating existing file: ${currentContents}`;
128
- }
129
- });
130
- await expect(result).toRenderToAsync("Creating new file");
131
- });
132
- it("should create JSON file from scratch when it doesn't exist", async () => {
133
- const result = _$createComponent(UpdateFile, {
134
- path: "new-config.json",
135
- children: currentContents => {
136
- const config = currentContents ? JSON.parse(currentContents) : {};
137
- config.name = "new-project";
138
- config.version = "1.0.0";
139
- config.dependencies = {};
140
- return JSON.stringify(config, null, 2);
141
- }
142
- });
143
- await expect(result).toRenderToAsync(d`
144
- {
145
- "name": "new-project",
146
- "version": "1.0.0",
147
- "dependencies": {}
148
- }
149
- `);
150
- });
151
- });
152
- describe("error handling", () => {
153
- it("should throw error when defaultContentPath file does not exist", async () => {
154
- expect(() => render(_$createComponent(UpdateFile, {
155
- path: "non-existent.txt",
156
- defaultContentPath: "/path/that/does/not/exist.txt",
157
- children: currentContents => currentContents
158
- }))).toThrow();
159
- });
160
- it("should handle file system errors gracefully", async () => {
161
- // Try to read a directory as a file (should cause an error)
162
- const dirPath = join(tmpdir(), `test-dir-${Date.now()}`);
163
- expect(() => render(_$createComponent(UpdateFile, {
164
- path: dirPath,
165
- children: currentContents => currentContents
166
- }))).toThrow();
167
- });
168
- });
169
- });
@@ -1,294 +0,0 @@
1
- import { computed } from "@vue/reactivity";
2
- import { join } from "pathe";
3
- import { useContext } from "../context.js";
4
- import { SourceDirectoryContext } from "../context/source-directory.js";
5
- import { createFileResource } from "../resource.js";
6
- import { Children, isComponentCreator } from "../runtime/component.js";
7
- import { childrenArray } from "../utils.jsx";
8
- import { SourceFile } from "./SourceFile.jsx";
9
-
10
- // Regular expression for finding all sigils at once
11
- const SIGIL_REGEX = /^(\s*)(.*alloy-(.+)-(start|end).*)$/gm;
12
-
13
- interface SigilInfo {
14
- id: string;
15
- start: number | null;
16
- end: number | null;
17
- }
18
-
19
- export interface AppendFileProps {
20
- /**
21
- * The path to the file to read and append content to.
22
- */
23
- path: string;
24
-
25
- /**
26
- * List of region IDs to append to. Defaults to ["append"] if not specified.
27
- * Each region corresponds to an AppendRegion child component.
28
- */
29
- regions?: string[];
30
-
31
- /**
32
- * AppendRegion children components that define content to append.
33
- */
34
- children?: Children;
35
- }
36
-
37
- /**
38
- * A component that reads a file and returns content with new content appended
39
- * at the end or within specific regions marked by alloy-\{region name\}-start/alloy-\{region name\}-end sigils.
40
- *
41
- * The component can append content in two ways:
42
- * 1. **Simple append**: Content is appended to the end of the file
43
- * 2. **Region-based append**: Content is appended before the end sigil on its own line
44
- *
45
- * Region sigils are line-based - any line containing "alloy-\{region name\}-start" or "alloy-\{region name\}-end"
46
- * is considered a sigil.
47
- *
48
- * @example
49
- * Simple append to end of file:
50
- * ```tsx
51
- * <AppendFile path="output.txt">
52
- * <AppendRegion id="append">New content to add</AppendRegion>
53
- * </AppendFile>
54
- *
55
- * // Returns:
56
- * // Original file content
57
- * // New content to add
58
- * ```
59
- *
60
- * @example
61
- * Append to specific regions:
62
- * ```tsx
63
- * // File content before:
64
- * // Header content
65
- * // <!-- alloy-main-start -->
66
- * // <!-- alloy-main-end -->
67
- * // Footer content
68
- *
69
- * <AppendFile path="template.html" regions={["main"]}>
70
- * <AppendRegion id="main">New main content</AppendRegion>
71
- * </AppendFile>
72
- *
73
- * // Returns:
74
- * // Header content
75
- * // <!-- alloy-main-start -->
76
- * // New main content
77
- * // <!-- alloy-main-end -->
78
- * // Footer content
79
- * ```
80
- */
81
- export function AppendFile(props: AppendFileProps): Children {
82
- const regions = props.regions || ["append"];
83
-
84
- // Get all children and filter for AppendRegion components
85
- const children = childrenArray(() => props.children);
86
- const appendRegions: Record<string, Children> = {};
87
-
88
- // Check if we have any AppendRegion components
89
- let hasAppendRegions = false;
90
- for (const child of children) {
91
- if (isComponentCreator(child, AppendRegion)) {
92
- hasAppendRegions = true;
93
- const regionProps = child.props as AppendRegionProps;
94
- let content: Children;
95
-
96
- if ("children" in regionProps && regionProps.children !== undefined) {
97
- content = regionProps.children;
98
- } else if ("content" in regionProps) {
99
- content = regionProps.content;
100
- } else {
101
- throw new Error(
102
- `AppendRegion "${regionProps.id}" must have either children or content`,
103
- );
104
- }
105
-
106
- appendRegions[regionProps.id] = content;
107
- }
108
- }
109
-
110
- // If no AppendRegion components found, treat all children as content for the default "append" region
111
- if (!hasAppendRegions && children.length > 0) {
112
- appendRegions["append"] = children;
113
- }
114
-
115
- // Validate that all requested regions have corresponding AppendRegion children
116
- for (const regionId of regions) {
117
- if (!(regionId in appendRegions)) {
118
- throw new Error(
119
- `Region "${regionId}" specified but no corresponding AppendRegion child found`,
120
- );
121
- }
122
- }
123
-
124
- // Read existing file content or start with empty string
125
- const parentDirectory = useContext(SourceDirectoryContext)!;
126
- const fullPath = join(
127
- parentDirectory ? parentDirectory.path : "",
128
- props.path,
129
- );
130
-
131
- const currentContents = createFileResource(fullPath);
132
- const newFileContent = computed(() => {
133
- if (currentContents.loading) {
134
- return;
135
- }
136
-
137
- const fileContent = currentContents.error ? "" : currentContents.data!;
138
-
139
- // Find all sigils in the file
140
- const sigilInfo: Record<string, SigilInfo> = {};
141
- const endSigils: Array<{
142
- regionId: string;
143
- index: number;
144
- indent: string;
145
- line: string;
146
- }> = [];
147
-
148
- // Reset regex and find all sigils
149
- SIGIL_REGEX.lastIndex = 0;
150
- let match;
151
- while ((match = SIGIL_REGEX.exec(fileContent)) !== null) {
152
- const indent = match[1];
153
- const fullLine = match[0];
154
- const regionId = match[3];
155
- const sigilType = match[4];
156
- const index = match.index!;
157
-
158
- // Initialize sigil info for this region if not exists
159
- if (!sigilInfo[regionId]) {
160
- sigilInfo[regionId] = { id: regionId, start: null, end: null };
161
- }
162
-
163
- if (sigilType === "start") {
164
- sigilInfo[regionId].start = index;
165
- } else if (sigilType === "end") {
166
- sigilInfo[regionId].end = index;
167
-
168
- // If this is a region we care about, track it for processing
169
- if (regions.includes(regionId)) {
170
- endSigils.push({
171
- regionId,
172
- index,
173
- indent,
174
- line: fullLine,
175
- });
176
- }
177
- }
178
- }
179
-
180
- // Validate regions - check for unclosed regions
181
- for (const regionId of regions) {
182
- const info = sigilInfo[regionId];
183
- if (info && info.start !== null && info.end === null) {
184
- throw new Error(
185
- `Region "${regionId}" has start sigil but no corresponding end sigil`,
186
- );
187
- }
188
- }
189
-
190
- // Check if we have any sigils to process
191
- if (endSigils.length === 0) {
192
- // No sigils found, append all regions to the end
193
- const result: Children[] = [fileContent];
194
- for (const regionId of regions) {
195
- if (fileContent && !fileContent.endsWith("\n")) {
196
- result.push("\n");
197
- }
198
- result.push(appendRegions[regionId]);
199
- }
200
- return (
201
- <SourceFile path={props.path} filetype="text/plain">
202
- {result}
203
- </SourceFile>
204
- );
205
- }
206
-
207
- // Sort end sigils by their position in the file
208
- endSigils.sort((a, b) => a.index - b.index);
209
-
210
- // Process content with sigils
211
- const result: Children[] = [];
212
- let lastIndex = 0;
213
-
214
- // Process each end sigil in order
215
- for (const { regionId, index, indent, line } of endSigils) {
216
- // Add content before this sigil
217
- if (index > lastIndex) {
218
- const beforeContent = fileContent.substring(lastIndex, index);
219
- if (beforeContent) {
220
- result.push(beforeContent);
221
- }
222
- }
223
-
224
- // Add the new content with proper indentation
225
- result.push(indent);
226
- result.push(appendRegions[regionId]);
227
- result.push("\n");
228
-
229
- // Add the sigil line
230
- result.push(line);
231
-
232
- // Update last processed index
233
- lastIndex = index + line.length;
234
- }
235
-
236
- // Add any remaining content after the last sigil
237
- if (lastIndex < fileContent.length) {
238
- const remainingPart = fileContent.substring(lastIndex);
239
- if (remainingPart) {
240
- result.push(remainingPart);
241
- }
242
- }
243
- return result;
244
- });
245
-
246
- return (
247
- <SourceFile path={props.path} filetype="text/plain">
248
- {newFileContent}
249
- </SourceFile>
250
- );
251
- }
252
-
253
- export interface AppendRegionPropsWithChildren {
254
- /**
255
- * The ID of the region.
256
- */
257
- id: string;
258
-
259
- /**
260
- * The content to append to the region.
261
- */
262
- children: Children;
263
- }
264
-
265
- export interface AppendRegionPropsWithContent {
266
- /**
267
- * The ID of the region.
268
- */
269
- id: string;
270
-
271
- /**
272
- * The content to append to the region.
273
- */
274
- content: Children;
275
- }
276
-
277
- export interface AppendRegionPropsBase {
278
- /**
279
- * The ID of the region.
280
- */
281
- id: string;
282
- }
283
-
284
- export type AppendRegionProps =
285
- | AppendRegionPropsWithChildren
286
- | AppendRegionPropsWithContent
287
- | AppendRegionPropsBase;
288
-
289
- export function AppendRegion(props: AppendRegionProps) {
290
- /**
291
- * This component does nothing except hold props which are retrieved by
292
- * the `AppendFile` component.
293
- */
294
- }
@@ -1,29 +0,0 @@
1
- import { join } from "pathe";
2
- import { useContext } from "../context.js";
3
- import { SourceDirectoryContext } from "../context/source-directory.js";
4
- import { CopyFileContext } from "../context/source-file.js";
5
- import { getContext } from "../reactivity.js";
6
-
7
- export interface CopyFileProps {
8
- /**
9
- * The path to write the copy to, relative to the containing directory.
10
- */
11
- path: string;
12
-
13
- /**
14
- * The path to the file to copy.
15
- */
16
- src: string;
17
- }
18
-
19
- export function CopyFile(props: CopyFileProps) {
20
- const parentDirectory = useContext(SourceDirectoryContext)!;
21
- const context: CopyFileContext = {
22
- path: join(parentDirectory ? parentDirectory.path : "", props.path),
23
- sourcePath: props.src,
24
- };
25
- parentDirectory?.addContent(context);
26
- const nodeContext = getContext()!;
27
- nodeContext.meta ??= {};
28
- nodeContext.meta.copyFile = context;
29
- }
@@ -1,193 +0,0 @@
1
- import { computed } from "@vue/reactivity";
2
- import { createFileResource } from "../resource.js";
3
- import { Children, isComponentCreator } from "../runtime/component.js";
4
- import { childrenArray } from "../utils.jsx";
5
- import { SourceFile } from "./SourceFile.jsx";
6
-
7
- export interface TemplateFileProps {
8
- /**
9
- * The path to write the compiled template to.
10
- */
11
- path: string;
12
-
13
- /**
14
- * The file path of the template.
15
- */
16
- src: string;
17
-
18
- /**
19
- * Template variable children components.
20
- */
21
- children?: Children;
22
- }
23
-
24
- /**
25
- * A component that reads a template file and replaces variable placeholders
26
- * with actual values.
27
- *
28
- * Template files can contain variable placeholders in the format
29
- * `{{ variable_name }}` which will be replaced with values from `TemplateVariable`
30
- * children components. Whitespace around variable names is ignored, so
31
- * `{{ name }}`, `{{name}}`, and `{{ name }}` are all equivalent.
32
- *
33
- * @example
34
- * Basic usage with template variables:
35
- * ```tsx
36
- * // Template file content (greeting.txt):
37
- * // "Hello {{ name }}! You are {{ age }} years old."
38
- *
39
- * <TemplateFile src="greeting.txt" path="output.txt">
40
- * <TemplateVariable name="name" value="John" />
41
- * <TemplateVariable name="age" value="25" />
42
- * </TemplateFile>
43
- * ```
44
- *
45
- * @example
46
- * Using children instead of value prop:
47
- * ```tsx
48
- * // Template file content (welcome.txt):
49
- * // "Welcome {{ greeting }}!"
50
- *
51
- * <TemplateFile src="welcome.txt" path="output.txt">
52
- * <TemplateVariable name="greeting">Hello World</TemplateVariable>
53
- * </TemplateFile>
54
- * ```
55
- *
56
- * @example
57
- * Complex template with multiple variables:
58
- * ```tsx
59
- * // Template file content (profile.txt):
60
- * // "Name: {{ name }}\nAge: {{ age }}\nLocation: {{ location }}"
61
- *
62
- * <TemplateFile src="profile.txt" path="profile-output.txt">
63
- * <TemplateVariable name="name" value="Alice" />
64
- * <TemplateVariable name="age">30</TemplateVariable>
65
- * <TemplateVariable name="location" value="New York" />
66
- * </TemplateFile>
67
- * ```
68
- */
69
- export function TemplateFile(props: TemplateFileProps): Children {
70
- // Get all children and filter for TemplateVariable components
71
- const children = childrenArray(() => props.children);
72
- const templateVariables: Record<string, Children> = {};
73
-
74
- // Extract variable values from TemplateVariable children
75
- for (const child of children) {
76
- if (!isComponentCreator(child, TemplateVariable)) {
77
- continue;
78
- }
79
-
80
- const variableProps = child.props as TemplateVariableProps;
81
- let value: Children;
82
-
83
- if ("children" in variableProps && variableProps.children !== undefined) {
84
- value = variableProps.children;
85
- } else if ("value" in variableProps) {
86
- value = variableProps.value;
87
- } else {
88
- throw new Error(
89
- `TemplateVariable "${variableProps.name}" must have either children or value`,
90
- );
91
- }
92
-
93
- templateVariables[variableProps.name] = value;
94
- }
95
-
96
- const templateResource = createFileResource(props.src);
97
- const fileContent = computed(() => {
98
- if (templateResource.loading) {
99
- return;
100
- }
101
-
102
- if (templateResource.error) {
103
- throw new Error(
104
- `Failed to read template file "${props.src}": ${templateResource.error}`,
105
- );
106
- }
107
-
108
- const templateContent = templateResource.data!;
109
-
110
- // Parse template and replace variables
111
- const result: Children[] = [];
112
- let lastIndex = 0;
113
-
114
- // Match {{ var_name }} patterns
115
- const variableRegex = /\{\{\s*(\w+)\s*\}\}/g;
116
- let match: RegExpExecArray | null;
117
-
118
- while ((match = variableRegex.exec(templateContent)) !== null) {
119
- const [fullMatch, variableName] = match;
120
- const matchStart = match.index;
121
-
122
- // Add content before the variable
123
- if (matchStart > lastIndex) {
124
- const beforeContent = templateContent.slice(lastIndex, matchStart);
125
- if (beforeContent) {
126
- result.push(beforeContent);
127
- }
128
- }
129
-
130
- // Add the variable value
131
- if (variableName in templateVariables) {
132
- result.push(templateVariables[variableName]);
133
- } else {
134
- throw new Error(
135
- `Template variable "${variableName}" not found in TemplateVariable children`,
136
- );
137
- }
138
-
139
- lastIndex = matchStart + fullMatch.length;
140
- }
141
-
142
- // Add remaining content after the last variable
143
- if (lastIndex < templateContent.length) {
144
- const remainingContent = templateContent.slice(lastIndex);
145
- if (remainingContent) {
146
- result.push(remainingContent);
147
- }
148
- }
149
-
150
- return result;
151
- });
152
-
153
- return (
154
- <SourceFile path={props.path} filetype="text/plain">
155
- {fileContent}
156
- </SourceFile>
157
- );
158
- }
159
-
160
- export interface TemplateVariablePropsWithChildren {
161
- /**
162
- * The name of the variable.
163
- */
164
- name: string;
165
-
166
- /**
167
- * The value of the variable.
168
- */
169
- children: Children;
170
- }
171
-
172
- export interface TemplateVariablePropsWithValue {
173
- /**
174
- * The name of the variable.
175
- */
176
- name: string;
177
-
178
- /**
179
- * The value of the variable.
180
- */
181
- value: string;
182
- }
183
-
184
- export type TemplateVariableProps =
185
- | TemplateVariablePropsWithChildren
186
- | TemplateVariablePropsWithValue;
187
-
188
- export function TemplateVariable(props: TemplateVariableProps) {
189
- /**
190
- * This component does nothing except hold props which are retrieved by
191
- * the `TemplateFile` component.
192
- */
193
- }