@alloy-js/core 0.19.0-dev.3 → 0.19.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/CHANGELOG.md +22 -0
- package/dist/src/components/AppendFile.d.ts +90 -0
- package/dist/src/components/AppendFile.d.ts.map +1 -0
- package/dist/src/components/AppendFile.js +226 -0
- package/dist/src/components/CopyFile.d.ts +12 -0
- package/dist/src/components/CopyFile.d.ts.map +1 -0
- package/dist/src/components/CopyFile.js +15 -0
- package/dist/src/components/TemplateFile.d.ts +84 -0
- package/dist/src/components/TemplateFile.d.ts.map +1 -0
- package/dist/src/components/TemplateFile.js +133 -0
- package/dist/src/components/UpdateFile.d.ts +34 -0
- package/dist/src/components/UpdateFile.d.ts.map +1 -0
- package/dist/src/components/UpdateFile.js +66 -0
- package/dist/src/components/index.d.ts +4 -0
- package/dist/src/components/index.d.ts.map +1 -1
- package/dist/src/components/index.js +4 -0
- package/dist/src/components/stc/index.d.ts +4 -0
- package/dist/src/components/stc/index.d.ts.map +1 -1
- package/dist/src/components/stc/index.js +4 -0
- package/dist/src/context/source-directory.d.ts +3 -3
- package/dist/src/context/source-directory.d.ts.map +1 -1
- package/dist/src/context/source-file.d.ts +4 -0
- package/dist/src/context/source-file.d.ts.map +1 -1
- package/dist/src/debug.d.ts.map +1 -1
- package/dist/src/debug.js +4 -1
- package/dist/src/host/alloy-host.browser.d.ts +11 -0
- package/dist/src/host/alloy-host.browser.d.ts.map +1 -0
- package/dist/src/host/alloy-host.browser.js +31 -0
- package/dist/src/host/alloy-host.d.ts +11 -0
- package/dist/src/host/alloy-host.d.ts.map +1 -0
- package/dist/src/host/alloy-host.js +143 -0
- package/dist/src/host/interface.d.ts +144 -0
- package/dist/src/host/interface.d.ts.map +1 -0
- package/dist/src/host/interface.js +1 -0
- package/dist/src/index.browser.d.ts +1 -1
- package/dist/src/index.browser.d.ts.map +1 -1
- package/dist/src/index.browser.js +2 -2
- package/dist/src/render.d.ts +10 -2
- package/dist/src/render.d.ts.map +1 -1
- package/dist/src/render.js +20 -1
- package/dist/src/resource.d.ts +80 -0
- package/dist/src/resource.d.ts.map +1 -0
- package/dist/src/resource.js +118 -0
- package/dist/src/scheduler.d.ts +6 -0
- package/dist/src/scheduler.d.ts.map +1 -1
- package/dist/src/scheduler.js +36 -0
- package/dist/src/write-output.d.ts +1 -1
- package/dist/src/write-output.d.ts.map +1 -1
- package/dist/src/write-output.js +40 -21
- package/dist/test/components/append-file.test.d.ts +2 -0
- package/dist/test/components/append-file.test.d.ts.map +1 -0
- package/dist/test/components/append-file.test.js +281 -0
- package/dist/test/components/copy-file.test.d.ts +2 -0
- package/dist/test/components/copy-file.test.d.ts.map +1 -0
- package/dist/test/components/copy-file.test.js +94 -0
- package/dist/test/components/source-file.test.d.ts.map +1 -1
- package/dist/test/components/template-file.test.d.ts +2 -0
- package/dist/test/components/template-file.test.d.ts.map +1 -0
- package/dist/test/components/template-file.test.js +133 -0
- package/dist/test/components/update-file.test.d.ts +2 -0
- package/dist/test/components/update-file.test.d.ts.map +1 -0
- package/dist/test/components/update-file.test.js +169 -0
- package/dist/test/rendering/formatting.test.d.ts.map +1 -1
- package/dist/testing/extend-expect.js +60 -54
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/src/components/AppendFile.tsx +294 -0
- package/src/components/CopyFile.tsx +29 -0
- package/src/components/TemplateFile.tsx +193 -0
- package/src/components/UpdateFile.tsx +86 -0
- package/src/components/index.tsx +4 -0
- package/src/components/stc/index.ts +4 -0
- package/src/context/source-directory.ts +5 -3
- package/src/context/source-file.ts +5 -0
- package/src/debug.ts +4 -1
- package/src/host/alloy-host.browser.ts +56 -0
- package/src/host/alloy-host.ts +160 -0
- package/src/host/interface.ts +153 -0
- package/src/index.browser.ts +1 -1
- package/src/render.ts +44 -5
- package/src/resource.ts +152 -0
- package/src/scheduler.ts +39 -0
- package/src/write-output.ts +49 -19
- package/temp/api.json +2009 -546
- package/test/components/append-file.test.tsx +275 -0
- package/test/components/copy-file.test.tsx +98 -0
- package/test/components/source-file.test.tsx +5 -2
- package/test/components/template-file.test.tsx +127 -0
- package/test/components/update-file.test.tsx +214 -0
- package/test/rendering/formatting.test.tsx +9 -3
- package/testing/extend-expect.ts +74 -58
- package/testing/vitest.d.ts +4 -0
- package/dist/src/write-output.browser.d.ts +0 -2
- package/dist/src/write-output.browser.d.ts.map +0 -1
- package/dist/src/write-output.browser.js +0 -4
- package/src/write-output.browser.ts +0 -4
package/dist/src/render.js
CHANGED
|
@@ -8,7 +8,7 @@ import { effect, getContext, getElementCache, isCustomContext, root, untrack } f
|
|
|
8
8
|
import { isRefkey } from "./refkey.js";
|
|
9
9
|
import { isComponentCreator } from "./runtime/component.js";
|
|
10
10
|
import { isIntrinsicElement } from "./runtime/intrinsic.js";
|
|
11
|
-
import { flushJobs } from "./scheduler.js";
|
|
11
|
+
import { flushJobs, flushJobsAsync } from "./scheduler.js";
|
|
12
12
|
import { trace, TracePhase } from "./tracer.js";
|
|
13
13
|
const {
|
|
14
14
|
builders: {
|
|
@@ -129,6 +129,14 @@ export function isPrintHook(type) {
|
|
|
129
129
|
export function render(children, options) {
|
|
130
130
|
const tree = renderTree(children);
|
|
131
131
|
flushJobs();
|
|
132
|
+
return sourceFilesForTree(tree, options);
|
|
133
|
+
}
|
|
134
|
+
export async function renderAsync(children, options) {
|
|
135
|
+
const tree = renderTree(children);
|
|
136
|
+
await flushJobsAsync();
|
|
137
|
+
return sourceFilesForTree(tree, options);
|
|
138
|
+
}
|
|
139
|
+
export function sourceFilesForTree(tree, options) {
|
|
132
140
|
let rootDirectory = undefined;
|
|
133
141
|
|
|
134
142
|
// when passing Output, the first render tree child is the Output component.
|
|
@@ -174,6 +182,17 @@ export function render(children, options) {
|
|
|
174
182
|
})
|
|
175
183
|
};
|
|
176
184
|
currentDirectory.contents.push(sourceFile);
|
|
185
|
+
} else if (context.meta?.copyFile) {
|
|
186
|
+
if (!currentDirectory) {
|
|
187
|
+
// This shouldn't happen if you're using the Output component.
|
|
188
|
+
throw new Error("Copy file doesn't have parent directory. Make sure you have used the Output component.");
|
|
189
|
+
}
|
|
190
|
+
const sourceFile = {
|
|
191
|
+
kind: "file",
|
|
192
|
+
path: context.meta?.copyFile.path,
|
|
193
|
+
sourcePath: context.meta?.copyFile.sourcePath
|
|
194
|
+
};
|
|
195
|
+
currentDirectory.contents.push(sourceFile);
|
|
177
196
|
} else {
|
|
178
197
|
recurse(currentDirectory);
|
|
179
198
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Ref } from "@vue/reactivity";
|
|
2
|
+
/**
|
|
3
|
+
* Represents an external resource fetched asynchronously.
|
|
4
|
+
*/
|
|
5
|
+
export interface Resource<T> {
|
|
6
|
+
/**
|
|
7
|
+
* The data if it's been loaded successfully. Null when not yet loaded or
|
|
8
|
+
* there was an error.
|
|
9
|
+
*/
|
|
10
|
+
data: T | null;
|
|
11
|
+
/**
|
|
12
|
+
* Whether the resource is still being fetched.
|
|
13
|
+
*/
|
|
14
|
+
loading: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The error loading the resource, if any.
|
|
17
|
+
*/
|
|
18
|
+
error: null | Error;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create a resource that fetches data asynchronously.
|
|
22
|
+
*
|
|
23
|
+
* This function has two overloads:
|
|
24
|
+
* 1. Simple fetcher - fetches data once when the resource is created
|
|
25
|
+
* 2. Reactive fetcher - fetches data when a reactive source changes
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // Simple usage - fetches data once when created
|
|
30
|
+
* const userResource = createResource(async () => {
|
|
31
|
+
* const response = await fetch('/api/user');
|
|
32
|
+
* return response.json();
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* // Access the resource state
|
|
36
|
+
* console.log(userResource.loading); // true initially
|
|
37
|
+
* console.log(userResource.data); // null initially
|
|
38
|
+
* console.log(userResource.error); // null initially
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* // Reactive usage - fetches data when the ref changes
|
|
44
|
+
* const userId = ref(1);
|
|
45
|
+
*
|
|
46
|
+
* const userResource = createResource(userId, async (id) => {
|
|
47
|
+
* const response = await fetch(`/api/user/${id}`);
|
|
48
|
+
* return response.json();
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* // The fetcher will be called automatically when userId changes
|
|
52
|
+
* userId.value = 2; // This triggers a new fetch with id=2
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function createResource<U>(fetcher: () => Promise<U>): Resource<U>;
|
|
56
|
+
/**
|
|
57
|
+
* Create a resource that fetches data asynchronously based on a reactive source.
|
|
58
|
+
*/
|
|
59
|
+
export declare function createResource<T, U>(refSource: Ref<T> | (() => T), fetcher: (input: T) => Promise<U>): Resource<U>;
|
|
60
|
+
/**
|
|
61
|
+
* Create a resource that reads a file from the file system.
|
|
62
|
+
*
|
|
63
|
+
* This is a convenience function that creates a resource for reading file content
|
|
64
|
+
* using the AlloyHost file system API. The file is read as text when the resource
|
|
65
|
+
* is created.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* // Read a configuration file
|
|
70
|
+
* const configResource = createFileResource('./config.json');
|
|
71
|
+
*
|
|
72
|
+
* // Access the file content
|
|
73
|
+
* if (!configResource.loading && !configResource.error) {
|
|
74
|
+
* const configText = configResource.data; // string content of the file
|
|
75
|
+
* const config = JSON.parse(configText);
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function createFileResource(path: string): Resource<string>;
|
|
80
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAKvD;;GAEG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB;;;OAGG;IACH,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IAEf;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,KAAK,EAAE,IAAI,GAAG,KAAK,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC1E;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EACjC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAC7B,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GAChC,QAAQ,CAAC,CAAC,CAAC,CAAC;AA4Df;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,oBAI9C"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { isRef, reactive } from "@vue/reactivity";
|
|
2
|
+
import { AlloyHost } from "./host/alloy-host.js";
|
|
3
|
+
import { effect } from "./reactivity.js";
|
|
4
|
+
import { trackPromise } from "./scheduler.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents an external resource fetched asynchronously.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a resource that fetches data asynchronously.
|
|
12
|
+
*
|
|
13
|
+
* This function has two overloads:
|
|
14
|
+
* 1. Simple fetcher - fetches data once when the resource is created
|
|
15
|
+
* 2. Reactive fetcher - fetches data when a reactive source changes
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* // Simple usage - fetches data once when created
|
|
20
|
+
* const userResource = createResource(async () => {
|
|
21
|
+
* const response = await fetch('/api/user');
|
|
22
|
+
* return response.json();
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Access the resource state
|
|
26
|
+
* console.log(userResource.loading); // true initially
|
|
27
|
+
* console.log(userResource.data); // null initially
|
|
28
|
+
* console.log(userResource.error); // null initially
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* // Reactive usage - fetches data when the ref changes
|
|
34
|
+
* const userId = ref(1);
|
|
35
|
+
*
|
|
36
|
+
* const userResource = createResource(userId, async (id) => {
|
|
37
|
+
* const response = await fetch(`/api/user/${id}`);
|
|
38
|
+
* return response.json();
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* // The fetcher will be called automatically when userId changes
|
|
42
|
+
* userId.value = 2; // This triggers a new fetch with id=2
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Create a resource that fetches data asynchronously based on a reactive source.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
export function createResource(fetcherOrSource, maybeFetcher) {
|
|
51
|
+
let getter = null;
|
|
52
|
+
let fetcher;
|
|
53
|
+
if (arguments.length === 1) {
|
|
54
|
+
fetcher = fetcherOrSource;
|
|
55
|
+
} else {
|
|
56
|
+
getter = fetcherOrSource;
|
|
57
|
+
fetcher = maybeFetcher;
|
|
58
|
+
}
|
|
59
|
+
const resource = reactive({
|
|
60
|
+
data: null,
|
|
61
|
+
loading: true,
|
|
62
|
+
error: null
|
|
63
|
+
});
|
|
64
|
+
if (!getter) {
|
|
65
|
+
const promise = fetcher();
|
|
66
|
+
trackPromise(promise.then(result => {
|
|
67
|
+
resource.data = result;
|
|
68
|
+
resource.loading = false;
|
|
69
|
+
}).catch(error => {
|
|
70
|
+
resource.error = error;
|
|
71
|
+
resource.loading = false;
|
|
72
|
+
}));
|
|
73
|
+
} else {
|
|
74
|
+
effect(() => {
|
|
75
|
+
let input;
|
|
76
|
+
if (isRef(getter)) {
|
|
77
|
+
input = getter.value;
|
|
78
|
+
} else {
|
|
79
|
+
input = getter();
|
|
80
|
+
}
|
|
81
|
+
const promise = fetcher(input);
|
|
82
|
+
trackPromise(promise);
|
|
83
|
+
promise.then(result => {
|
|
84
|
+
resource.data = result;
|
|
85
|
+
resource.loading = false;
|
|
86
|
+
}, error => {
|
|
87
|
+
resource.error = error;
|
|
88
|
+
resource.loading = false;
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return resource;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create a resource that reads a file from the file system.
|
|
97
|
+
*
|
|
98
|
+
* This is a convenience function that creates a resource for reading file content
|
|
99
|
+
* using the AlloyHost file system API. The file is read as text when the resource
|
|
100
|
+
* is created.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* // Read a configuration file
|
|
105
|
+
* const configResource = createFileResource('./config.json');
|
|
106
|
+
*
|
|
107
|
+
* // Access the file content
|
|
108
|
+
* if (!configResource.loading && !configResource.error) {
|
|
109
|
+
* const configText = configResource.data; // string content of the file
|
|
110
|
+
* const config = JSON.parse(configText);
|
|
111
|
+
* }
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export function createFileResource(path) {
|
|
115
|
+
return createResource(() => {
|
|
116
|
+
return AlloyHost.read(path).text();
|
|
117
|
+
});
|
|
118
|
+
}
|
package/dist/src/scheduler.d.ts
CHANGED
|
@@ -4,5 +4,11 @@ export interface QueueJob {
|
|
|
4
4
|
}
|
|
5
5
|
export declare function scheduler(jobGetter: () => ReactiveEffectRunner, immediate?: boolean): () => void;
|
|
6
6
|
export declare function queueJob(job: QueueJob, immediate?: boolean): void;
|
|
7
|
+
/**
|
|
8
|
+
* Register a promise that the scheduler should wait for during flushJobs.
|
|
9
|
+
* This is used by async resources to ensure the scheduler waits for their completion.
|
|
10
|
+
*/
|
|
11
|
+
export declare function trackPromise(promise: Promise<any>): void;
|
|
7
12
|
export declare function flushJobs(): void;
|
|
13
|
+
export declare function flushJobsAsync(): Promise<void>;
|
|
8
14
|
//# sourceMappingURL=scheduler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,WAAW,QAAQ;IACvB,IAAI,GAAG,CAAC;CACT;
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,WAAW,QAAQ;IACvB,IAAI,GAAG,CAAC;CACT;AAKD,wBAAgB,SAAS,CACvB,SAAS,EAAE,MAAM,oBAAoB,EACrC,SAAS,UAAQ,cAKlB;AACD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,UAAQ,QASxD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,QAKjD;AAED,wBAAgB,SAAS,SAaxB;AAED,wBAAsB,cAAc,kBAiBnC"}
|
package/dist/src/scheduler.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const immediateQueue = new Set();
|
|
2
2
|
const queue = new Set();
|
|
3
|
+
const pendingPromises = new Set();
|
|
3
4
|
export function scheduler(jobGetter, immediate = false) {
|
|
4
5
|
return () => {
|
|
5
6
|
queueJob(jobGetter(), immediate);
|
|
@@ -15,11 +16,46 @@ export function queueJob(job, immediate = false) {
|
|
|
15
16
|
queue.add(job);
|
|
16
17
|
}
|
|
17
18
|
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Register a promise that the scheduler should wait for during flushJobs.
|
|
22
|
+
* This is used by async resources to ensure the scheduler waits for their completion.
|
|
23
|
+
*/
|
|
24
|
+
export function trackPromise(promise) {
|
|
25
|
+
pendingPromises.add(promise);
|
|
26
|
+
void promise.finally(() => {
|
|
27
|
+
pendingPromises.delete(promise);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
18
30
|
export function flushJobs() {
|
|
31
|
+
// First, run all synchronous jobs
|
|
19
32
|
let job;
|
|
20
33
|
while ((job = takeJob()) !== null) {
|
|
21
34
|
job();
|
|
22
35
|
}
|
|
36
|
+
|
|
37
|
+
// If there are no pending promises, we're done
|
|
38
|
+
if (pendingPromises.size > 0) {
|
|
39
|
+
throw new Error("Asynchronous jobs were found but render was called synchronously. Use `asyncRender` instead.");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function flushJobsAsync() {
|
|
43
|
+
// Keep running jobs until both the queues are empty and all promises are resolved
|
|
44
|
+
while (true) {
|
|
45
|
+
// First, run all synchronous jobs
|
|
46
|
+
let job;
|
|
47
|
+
while ((job = takeJob()) !== null) {
|
|
48
|
+
job();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// If there are no pending promises, we're done
|
|
52
|
+
if (pendingPromises.size === 0) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Wait for all current promises to complete
|
|
57
|
+
await Promise.allSettled(Array.from(pendingPromises));
|
|
58
|
+
}
|
|
23
59
|
}
|
|
24
60
|
function takeJob() {
|
|
25
61
|
if (immediateQueue.size > 0) {
|
|
@@ -3,5 +3,5 @@ import { OutputDirectory } from "./render.js";
|
|
|
3
3
|
* Write the output from {@link render} to the file system.
|
|
4
4
|
*
|
|
5
5
|
*/
|
|
6
|
-
export declare function writeOutput(output: OutputDirectory, basePath?: string): void
|
|
6
|
+
export declare function writeOutput(output: OutputDirectory, basePath?: string): Promise<void[]>;
|
|
7
7
|
//# sourceMappingURL=write-output.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write-output.d.ts","sourceRoot":"","sources":["../../src/write-output.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,
|
|
1
|
+
{"version":3,"file":"write-output.d.ts","sourceRoot":"","sources":["../../src/write-output.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,eAAe,EACvB,QAAQ,GAAE,MAAW,mBAoDtB"}
|
package/dist/src/write-output.js
CHANGED
|
@@ -1,33 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { dirname, relative, resolve } from "pathe";
|
|
2
|
+
import { AlloyHost } from "./host/alloy-host.js";
|
|
3
3
|
import { traverseOutput } from "./utils.js";
|
|
4
4
|
/**
|
|
5
5
|
* Write the output from {@link render} to the file system.
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
|
-
export function writeOutput(output, basePath = "") {
|
|
8
|
+
export async function writeOutput(output, basePath = "") {
|
|
9
|
+
const ops = [];
|
|
9
10
|
traverseOutput(output, {
|
|
10
11
|
visitDirectory(directory) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
console.log("create", relative(process.cwd(), path));
|
|
17
|
-
mkdirSync(path, {
|
|
18
|
-
recursive: true
|
|
19
|
-
});
|
|
20
|
-
},
|
|
21
|
-
visitFile(file) {
|
|
22
|
-
const path = resolve(basePath, file.path);
|
|
23
|
-
if (existsSync(path)) {
|
|
24
|
-
// eslint-disable-next-line no-console
|
|
25
|
-
console.log("overwrite", relative(process.cwd(), path));
|
|
26
|
-
} else {
|
|
12
|
+
ops.push((async () => {
|
|
13
|
+
const path = resolve(basePath, directory.path);
|
|
14
|
+
if (await AlloyHost.exists(path)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
27
17
|
// eslint-disable-next-line no-console
|
|
28
18
|
console.log("create", relative(process.cwd(), path));
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
await AlloyHost.mkdir(path);
|
|
20
|
+
})());
|
|
21
|
+
},
|
|
22
|
+
visitFile(file) {
|
|
23
|
+
ops.push((async () => {
|
|
24
|
+
if ("contents" in file) {
|
|
25
|
+
const path = resolve(basePath, file.path);
|
|
26
|
+
if (await AlloyHost.exists(path)) {
|
|
27
|
+
// eslint-disable-next-line no-console
|
|
28
|
+
console.log("overwrite", relative(process.cwd(), path));
|
|
29
|
+
} else {
|
|
30
|
+
// eslint-disable-next-line no-console
|
|
31
|
+
console.log("create", relative(process.cwd(), path));
|
|
32
|
+
}
|
|
33
|
+
await AlloyHost.write(path, file.contents);
|
|
34
|
+
} else {
|
|
35
|
+
// copy file
|
|
36
|
+
const source = resolve(basePath, file.sourcePath);
|
|
37
|
+
const target = resolve(basePath, file.path);
|
|
38
|
+
if (await AlloyHost.exists(target)) {
|
|
39
|
+
// eslint-disable-next-line no-console
|
|
40
|
+
console.log("copy over", relative(process.cwd(), target));
|
|
41
|
+
} else {
|
|
42
|
+
// eslint-disable-next-line no-console
|
|
43
|
+
console.log("copy", relative(process.cwd(), target));
|
|
44
|
+
}
|
|
45
|
+
await AlloyHost.mkdir(dirname(target));
|
|
46
|
+
await AlloyHost.write(target, AlloyHost.read(source).stream());
|
|
47
|
+
}
|
|
48
|
+
})());
|
|
31
49
|
}
|
|
32
50
|
});
|
|
51
|
+
return Promise.all(ops);
|
|
33
52
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"append-file.test.d.ts","sourceRoot":"","sources":["../../../test/components/append-file.test.tsx"],"names":[],"mappings":"AAMA,OAAO,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,281 @@
|
|
|
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 "path";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
6
|
+
import { AppendFile, AppendRegion } from "../../src/components/AppendFile.js";
|
|
7
|
+
import { render, renderAsync } from "../../src/render.js";
|
|
8
|
+
import "../../testing/extend-expect.js";
|
|
9
|
+
import { d } from "../../testing/render.js";
|
|
10
|
+
describe("AppendFile", () => {
|
|
11
|
+
let testFilePath;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
// Create a unique temporary file path for each test
|
|
14
|
+
testFilePath = join(tmpdir(), `test-append-file-${Date.now()}.txt`);
|
|
15
|
+
});
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
// Clean up test file after each test
|
|
18
|
+
if (existsSync(testFilePath)) {
|
|
19
|
+
unlinkSync(testFilePath);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
it("should append content to end of file when no sigils present", async () => {
|
|
23
|
+
// Create initial file content
|
|
24
|
+
writeFileSync(testFilePath, "Initial content", "utf-8");
|
|
25
|
+
const result = _$createComponent(AppendFile, {
|
|
26
|
+
path: testFilePath,
|
|
27
|
+
get children() {
|
|
28
|
+
return _$createComponent(AppendRegion, {
|
|
29
|
+
id: "append",
|
|
30
|
+
children: "New content"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
await expect(result).toRenderToAsync("Initial content\nNew content");
|
|
35
|
+
});
|
|
36
|
+
it("should append content to end of file when no sigils present with no explicit append region", async () => {
|
|
37
|
+
// Create initial file content
|
|
38
|
+
writeFileSync(testFilePath, "Initial content", "utf-8");
|
|
39
|
+
const result = _$createComponent(AppendFile, {
|
|
40
|
+
path: testFilePath,
|
|
41
|
+
children: "New content"
|
|
42
|
+
});
|
|
43
|
+
await expect(result).toRenderToAsync("Initial content\nNew content");
|
|
44
|
+
});
|
|
45
|
+
it("should append content within region sigils", async () => {
|
|
46
|
+
const initialContent = d`
|
|
47
|
+
Header content
|
|
48
|
+
<!-- alloy-main-start -->
|
|
49
|
+
<!-- alloy-main-end -->
|
|
50
|
+
Footer content
|
|
51
|
+
`;
|
|
52
|
+
writeFileSync(testFilePath, initialContent, "utf-8");
|
|
53
|
+
const result = _$createComponent(AppendFile, {
|
|
54
|
+
path: testFilePath,
|
|
55
|
+
regions: ["main"],
|
|
56
|
+
get children() {
|
|
57
|
+
return _$createComponent(AppendRegion, {
|
|
58
|
+
id: "main",
|
|
59
|
+
children: "New main content"
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
await expect(result).toRenderToAsync(d`
|
|
64
|
+
Header content
|
|
65
|
+
<!-- alloy-main-start -->
|
|
66
|
+
New main content
|
|
67
|
+
<!-- alloy-main-end -->
|
|
68
|
+
Footer content
|
|
69
|
+
`);
|
|
70
|
+
});
|
|
71
|
+
it("should handle multiple regions", async () => {
|
|
72
|
+
const initialContent = d`
|
|
73
|
+
<!-- alloy-header-start -->
|
|
74
|
+
<!-- alloy-header-end -->
|
|
75
|
+
Main content
|
|
76
|
+
<!-- alloy-footer-start -->
|
|
77
|
+
<!-- alloy-footer-end -->
|
|
78
|
+
`;
|
|
79
|
+
writeFileSync(testFilePath, initialContent, "utf-8");
|
|
80
|
+
const result = _$createComponent(AppendFile, {
|
|
81
|
+
path: testFilePath,
|
|
82
|
+
regions: ["header", "footer"],
|
|
83
|
+
get children() {
|
|
84
|
+
return [_$createComponent(AppendRegion, {
|
|
85
|
+
id: "header",
|
|
86
|
+
children: "Header text"
|
|
87
|
+
}), _$createComponent(AppendRegion, {
|
|
88
|
+
id: "footer",
|
|
89
|
+
children: "Footer text"
|
|
90
|
+
})];
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
await expect(result).toRenderToAsync(d`
|
|
94
|
+
<!-- alloy-header-start -->
|
|
95
|
+
Header text
|
|
96
|
+
<!-- alloy-header-end -->
|
|
97
|
+
Main content
|
|
98
|
+
<!-- alloy-footer-start -->
|
|
99
|
+
Footer text
|
|
100
|
+
<!-- alloy-footer-end -->
|
|
101
|
+
`);
|
|
102
|
+
});
|
|
103
|
+
it("should preserve existing content and append new content", async () => {
|
|
104
|
+
const initialContent = d`
|
|
105
|
+
Header
|
|
106
|
+
<!-- alloy-main-start -->
|
|
107
|
+
Existing content
|
|
108
|
+
<!-- alloy-main-end -->
|
|
109
|
+
Footer
|
|
110
|
+
`;
|
|
111
|
+
writeFileSync(testFilePath, initialContent, "utf-8");
|
|
112
|
+
const result = _$createComponent(AppendFile, {
|
|
113
|
+
path: testFilePath,
|
|
114
|
+
regions: ["main"],
|
|
115
|
+
get children() {
|
|
116
|
+
return _$createComponent(AppendRegion, {
|
|
117
|
+
id: "main",
|
|
118
|
+
children: "New appended content"
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
await expect(result).toRenderToAsync(d`
|
|
123
|
+
Header
|
|
124
|
+
<!-- alloy-main-start -->
|
|
125
|
+
Existing content
|
|
126
|
+
New appended content
|
|
127
|
+
<!-- alloy-main-end -->
|
|
128
|
+
Footer
|
|
129
|
+
`);
|
|
130
|
+
});
|
|
131
|
+
it("should use content prop instead of children", async () => {
|
|
132
|
+
writeFileSync(testFilePath, "Start ", "utf-8");
|
|
133
|
+
const result = _$createComponent(AppendFile, {
|
|
134
|
+
path: testFilePath,
|
|
135
|
+
get children() {
|
|
136
|
+
return _$createComponent(AppendRegion, {
|
|
137
|
+
id: "append",
|
|
138
|
+
content: "End"
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
await expect(result).toRenderToAsync("Start\nEnd");
|
|
143
|
+
});
|
|
144
|
+
it("should default to 'append' region when no regions specified", async () => {
|
|
145
|
+
writeFileSync(testFilePath, "Content", "utf-8");
|
|
146
|
+
const result = _$createComponent(AppendFile, {
|
|
147
|
+
path: testFilePath,
|
|
148
|
+
get children() {
|
|
149
|
+
return _$createComponent(AppendRegion, {
|
|
150
|
+
id: "append",
|
|
151
|
+
children: "default region"
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
await expect(result).toRenderToAsync("Content\ndefault region");
|
|
156
|
+
});
|
|
157
|
+
it("should throw error when region is missing corresponding AppendRegion", async () => {
|
|
158
|
+
writeFileSync(testFilePath, "content", "utf-8");
|
|
159
|
+
expect(() => render(_$createComponent(AppendFile, {
|
|
160
|
+
path: testFilePath,
|
|
161
|
+
regions: ["missing"],
|
|
162
|
+
get children() {
|
|
163
|
+
return _$createComponent(AppendRegion, {
|
|
164
|
+
id: "append",
|
|
165
|
+
children: "content"
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}))).toThrow('Region "missing" specified but no corresponding AppendRegion child found');
|
|
169
|
+
});
|
|
170
|
+
it("should throw error when AppendRegion has neither children nor content", async () => {
|
|
171
|
+
writeFileSync(testFilePath, "content", "utf-8");
|
|
172
|
+
expect(() => render(_$createComponent(AppendFile, {
|
|
173
|
+
path: testFilePath,
|
|
174
|
+
get children() {
|
|
175
|
+
return _$createComponent(AppendRegion, {
|
|
176
|
+
id: "append"
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}))).toThrow('AppendRegion "append" must have either children or content');
|
|
180
|
+
});
|
|
181
|
+
it("should throw error when region has missing start sigil", async () => {
|
|
182
|
+
const contentWithOnlyEnd = d`
|
|
183
|
+
Content
|
|
184
|
+
<!-- alloy-incomplete-end -->
|
|
185
|
+
`;
|
|
186
|
+
writeFileSync(testFilePath, contentWithOnlyEnd, "utf-8");
|
|
187
|
+
const result = _$createComponent(AppendFile, {
|
|
188
|
+
path: testFilePath,
|
|
189
|
+
regions: ["incomplete"],
|
|
190
|
+
get children() {
|
|
191
|
+
return _$createComponent(AppendRegion, {
|
|
192
|
+
id: "incomplete",
|
|
193
|
+
children: "content"
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Should insert before the end sigil
|
|
199
|
+
await expect(result).toRenderToAsync(d`
|
|
200
|
+
Content
|
|
201
|
+
content
|
|
202
|
+
<!-- alloy-incomplete-end -->
|
|
203
|
+
`);
|
|
204
|
+
});
|
|
205
|
+
it("should throw error when region has missing end sigil", async () => {
|
|
206
|
+
const contentWithOnlyStart = d`
|
|
207
|
+
Content
|
|
208
|
+
<!-- alloy-incomplete-start -->
|
|
209
|
+
`;
|
|
210
|
+
writeFileSync(testFilePath, contentWithOnlyStart, "utf-8");
|
|
211
|
+
await expect(async () => renderAsync(_$createComponent(AppendFile, {
|
|
212
|
+
path: testFilePath,
|
|
213
|
+
regions: ["incomplete"],
|
|
214
|
+
get children() {
|
|
215
|
+
return _$createComponent(AppendRegion, {
|
|
216
|
+
id: "incomplete",
|
|
217
|
+
children: "content"
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}))).rejects.toThrow('Region "incomplete" has start sigil but no corresponding end sigil');
|
|
221
|
+
});
|
|
222
|
+
it("should handle complex nested content", async () => {
|
|
223
|
+
const initialContent = d`
|
|
224
|
+
<!-- alloy-config-start -->
|
|
225
|
+
<!-- alloy-config-end -->
|
|
226
|
+
`;
|
|
227
|
+
writeFileSync(testFilePath, initialContent, "utf-8");
|
|
228
|
+
const jsonContent = d`
|
|
229
|
+
{
|
|
230
|
+
"newProperty": "value",
|
|
231
|
+
"nested": {
|
|
232
|
+
"key": "data"
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
`;
|
|
236
|
+
const result = _$createComponent(AppendFile, {
|
|
237
|
+
path: testFilePath,
|
|
238
|
+
regions: ["config"],
|
|
239
|
+
get children() {
|
|
240
|
+
return _$createComponent(AppendRegion, {
|
|
241
|
+
id: "config",
|
|
242
|
+
children: jsonContent
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
await expect(result).toRenderToAsync(d`
|
|
247
|
+
<!-- alloy-config-start -->
|
|
248
|
+
{
|
|
249
|
+
"newProperty": "value",
|
|
250
|
+
"nested": {
|
|
251
|
+
"key": "data"
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
<!-- alloy-config-end -->
|
|
255
|
+
`);
|
|
256
|
+
});
|
|
257
|
+
it("should preserve indentation level of the end sigil", async () => {
|
|
258
|
+
const initialContent = d`
|
|
259
|
+
base
|
|
260
|
+
<!-- alloy-indented-start -->
|
|
261
|
+
<!-- alloy-indented-end -->
|
|
262
|
+
`;
|
|
263
|
+
writeFileSync(testFilePath, initialContent, "utf-8");
|
|
264
|
+
const result = _$createComponent(AppendFile, {
|
|
265
|
+
path: testFilePath,
|
|
266
|
+
regions: ["indented"],
|
|
267
|
+
get children() {
|
|
268
|
+
return _$createComponent(AppendRegion, {
|
|
269
|
+
id: "indented",
|
|
270
|
+
children: "new content"
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
await expect(result).toRenderToAsync(d`
|
|
275
|
+
base
|
|
276
|
+
<!-- alloy-indented-start -->
|
|
277
|
+
new content
|
|
278
|
+
<!-- alloy-indented-end -->
|
|
279
|
+
`);
|
|
280
|
+
});
|
|
281
|
+
});
|