alchemy 0.3.0 → 0.3.1
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/lib/ai/approve.d.ts +99 -0
- package/lib/ai/approve.js +76 -0
- package/lib/ai/astro-file.js +7 -24
- package/lib/ai/client.d.ts +7 -0
- package/lib/ai/client.js +45 -0
- package/lib/ai/css-file.js +7 -24
- package/lib/ai/data.d.ts +26 -21
- package/lib/ai/data.js +33 -22
- package/lib/ai/document.d.ts +64 -4
- package/lib/ai/document.js +102 -55
- package/lib/ai/html-file.js +7 -24
- package/lib/ai/index.d.ts +2 -0
- package/lib/ai/index.js +2 -0
- package/lib/ai/json-file.js +11 -32
- package/lib/ai/review.d.ts +122 -0
- package/lib/ai/review.js +101 -0
- package/lib/ai/typescript-file.d.ts +1 -2
- package/lib/ai/typescript-file.js +8 -39
- package/lib/ai/vue-file.js +7 -24
- package/lib/ai/yaml-file.js +12 -48
- package/lib/alchemy.js +17 -2
- package/lib/apply.js +1 -1
- package/lib/cloudflare/generate-asset-manifest.js +1 -1
- package/lib/cloudflare/static-site-router.js +16 -7
- package/lib/cloudflare/static-site.js +3 -0
- package/lib/cloudflare/upload-asset-manifest.js +4 -2
- package/lib/fs/copy-file.d.ts +54 -0
- package/lib/fs/copy-file.js +63 -0
- package/lib/fs/file.d.ts +13 -0
- package/lib/fs/file.js +24 -5
- package/lib/fs/index.d.ts +6 -0
- package/lib/fs/index.js +6 -0
- package/lib/fs/static-astro-file.d.ts +34 -0
- package/lib/fs/static-astro-file.js +39 -0
- package/lib/fs/static-css-file.d.ts +24 -0
- package/lib/fs/static-css-file.js +29 -0
- package/lib/fs/static-html-file.d.ts +31 -0
- package/lib/fs/static-html-file.js +36 -0
- package/lib/fs/static-json-file.d.ts +2 -2
- package/lib/fs/static-json-file.js +3 -2
- package/lib/fs/static-text-file.d.ts +1 -1
- package/lib/fs/static-text-file.js +3 -2
- package/lib/fs/static-typescript-file.d.ts +2 -2
- package/lib/fs/static-typescript-file.js +3 -2
- package/lib/fs/static-vue-file.d.ts +28 -0
- package/lib/fs/static-vue-file.js +33 -0
- package/lib/fs/static-yaml-file.d.ts +11 -8
- package/lib/fs/static-yaml-file.js +3 -2
- package/lib/internal/getting-started.d.ts +15 -4
- package/lib/internal/getting-started.js +34 -41
- package/lib/internal/index.d.ts +3 -0
- package/lib/internal/index.js +3 -0
- package/lib/internal/providers.d.ts +7 -3
- package/lib/internal/providers.js +42 -18
- package/lib/internal/tutorial.d.ts +104 -0
- package/lib/internal/tutorial.js +251 -0
- package/lib/web/vitepress/config.d.ts +10 -0
- package/lib/web/vitepress/config.js +31 -0
- package/lib/web/vitepress/home-page.d.ts +5 -3
- package/lib/web/vitepress/home-page.js +48 -35
- package/lib/web/vitepress/index.d.ts +1 -0
- package/lib/web/vitepress/index.js +1 -0
- package/lib/web/vitepress/vitepress.d.ts +5 -4
- package/lib/web/vitepress/vitepress.js +17 -34
- package/package.json +1 -1
- package/src/ai/approve.ts +163 -0
- package/src/ai/astro-file.ts +11 -30
- package/src/ai/client.ts +59 -0
- package/src/ai/css-file.ts +11 -30
- package/src/ai/data.ts +50 -25
- package/src/ai/document.ts +146 -65
- package/src/ai/html-file.ts +11 -30
- package/src/ai/index.ts +2 -0
- package/src/ai/json-file.ts +13 -37
- package/src/ai/review.ts +213 -0
- package/src/ai/typescript-file.ts +14 -48
- package/src/ai/vue-file.ts +11 -30
- package/src/ai/yaml-file.ts +13 -53
- package/src/alchemy.ts +18 -7
- package/src/apply.ts +8 -8
- package/src/cloudflare/generate-asset-manifest.ts +4 -4
- package/src/cloudflare/static-site-router.ts +18 -8
- package/src/cloudflare/static-site.ts +11 -7
- package/src/cloudflare/upload-asset-manifest.ts +6 -4
- package/src/cloudflare/worker.ts +44 -43
- package/src/fs/copy-file.ts +115 -0
- package/src/fs/file.ts +35 -9
- package/src/fs/index.ts +6 -0
- package/src/fs/static-astro-file.ts +45 -0
- package/src/fs/static-css-file.ts +35 -0
- package/src/fs/static-html-file.ts +42 -0
- package/src/fs/static-json-file.ts +4 -3
- package/src/fs/static-text-file.ts +5 -2
- package/src/fs/static-typescript-file.ts +4 -3
- package/src/fs/static-vue-file.ts +39 -0
- package/src/fs/static-yaml-file.ts +13 -9
- package/src/internal/getting-started.ts +51 -43
- package/src/internal/index.ts +3 -0
- package/src/internal/providers.ts +59 -27
- package/src/internal/tutorial.ts +392 -0
- package/src/web/vitepress/config.ts +48 -0
- package/src/web/vitepress/home-page.ts +54 -42
- package/src/web/vitepress/index.ts +1 -0
- package/src/web/vitepress/vitepress.ts +31 -48
- package/lib/internal/project.d.ts +0 -13
- package/lib/internal/project.js +0 -101
- package/src/internal/project.ts +0 -119
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { generateText } from "ai";
|
|
2
|
-
import
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import prettier from "prettier";
|
|
2
|
+
import { StaticTypeScriptFile } from "../fs/static-typescript-file";
|
|
5
3
|
import { Resource } from "../resource";
|
|
6
|
-
import { ignore } from "../util/ignore";
|
|
7
4
|
import { createModel } from "./client";
|
|
8
5
|
/**
|
|
9
6
|
* Default system prompt for TypeScript file generation
|
|
@@ -75,18 +72,9 @@ const DEFAULT_TS_SYSTEM_PROMPT = "You are a TypeScript code generator. Create Ty
|
|
|
75
72
|
* });
|
|
76
73
|
*/
|
|
77
74
|
export const TypeScriptFile = Resource("ai::TypeScriptFile", async function (id, props) {
|
|
78
|
-
//
|
|
79
|
-
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
75
|
+
// Handle delete phase
|
|
80
76
|
if (this.phase === "delete") {
|
|
81
|
-
|
|
82
|
-
await fs.unlink(props.path);
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
// Ignore if file doesn't exist
|
|
86
|
-
if (error.code !== "ENOENT") {
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
77
|
+
// StaticTypeScriptFile will handle the deletion
|
|
90
78
|
return this.destroy();
|
|
91
79
|
}
|
|
92
80
|
// Use provided system prompt or default
|
|
@@ -121,33 +109,14 @@ export const TypeScriptFile = Resource("ai::TypeScriptFile", async function (id,
|
|
|
121
109
|
}
|
|
122
110
|
code = retryResult.code;
|
|
123
111
|
}
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
// Set default parser to typescript
|
|
127
|
-
const prettierOptions = {
|
|
128
|
-
parser: "typescript",
|
|
129
|
-
...props.prettierConfig,
|
|
130
|
-
};
|
|
131
|
-
// Format the code
|
|
132
|
-
code = await prettier.format(code, prettierOptions);
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
135
|
-
// If Prettier formatting fails, just use the unformatted code
|
|
136
|
-
console.warn("Failed to format TypeScript code with Prettier:", error);
|
|
137
|
-
}
|
|
138
|
-
if (this.phase === "update" && props.path !== this.props.path) {
|
|
139
|
-
await ignore("ENOENT", () => fs.unlink(this.props.path));
|
|
140
|
-
}
|
|
141
|
-
// Write content to file
|
|
142
|
-
await fs.writeFile(props.path, code);
|
|
143
|
-
// Get file stats for timestamps
|
|
144
|
-
const stats = await fs.stat(props.path);
|
|
112
|
+
// Use StaticTypeScriptFile to create/update the file
|
|
113
|
+
const file = await StaticTypeScriptFile("file", props.path, code);
|
|
145
114
|
// Return the resource
|
|
146
115
|
return this({
|
|
147
116
|
...props,
|
|
148
|
-
content:
|
|
149
|
-
createdAt:
|
|
150
|
-
updatedAt:
|
|
117
|
+
content: file.content,
|
|
118
|
+
createdAt: Date.now(),
|
|
119
|
+
updatedAt: Date.now(),
|
|
151
120
|
});
|
|
152
121
|
});
|
|
153
122
|
/**
|
package/lib/ai/vue-file.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { generateText } from "ai";
|
|
2
|
-
import
|
|
3
|
-
import path from "node:path";
|
|
2
|
+
import { StaticVueFile } from "../fs/static-vue-file";
|
|
4
3
|
import { Resource } from "../resource";
|
|
5
|
-
import { ignore } from "../util/ignore";
|
|
6
4
|
import { createModel } from "./client";
|
|
7
5
|
/**
|
|
8
6
|
* Default system prompt for Vue file generation
|
|
@@ -66,18 +64,8 @@ const DEFAULT_VUE_SYSTEM_PROMPT = "You are a Vue component generator. Create a s
|
|
|
66
64
|
* });
|
|
67
65
|
*/
|
|
68
66
|
export const VueFile = Resource("ai::VueFile", async function (id, props) {
|
|
69
|
-
//
|
|
70
|
-
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
67
|
+
// Handle deletion phase
|
|
71
68
|
if (this.phase === "delete") {
|
|
72
|
-
try {
|
|
73
|
-
await fs.unlink(props.path);
|
|
74
|
-
}
|
|
75
|
-
catch (error) {
|
|
76
|
-
// Ignore if file doesn't exist
|
|
77
|
-
if (error.code !== "ENOENT") {
|
|
78
|
-
throw error;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
69
|
return this.destroy();
|
|
82
70
|
}
|
|
83
71
|
// Use provided system prompt or default
|
|
@@ -112,19 +100,14 @@ export const VueFile = Resource("ai::VueFile", async function (id, props) {
|
|
|
112
100
|
}
|
|
113
101
|
code = retryResult.code;
|
|
114
102
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
// Write content to file
|
|
119
|
-
await fs.writeFile(props.path, code);
|
|
120
|
-
// Get file stats for timestamps
|
|
121
|
-
const stats = await fs.stat(props.path);
|
|
103
|
+
// Use StaticVueFile to create/update the file
|
|
104
|
+
const file = await StaticVueFile("file", props.path, code);
|
|
122
105
|
// Return the resource
|
|
123
106
|
return this({
|
|
124
107
|
...props,
|
|
125
|
-
content:
|
|
126
|
-
createdAt:
|
|
127
|
-
updatedAt:
|
|
108
|
+
content: file.content,
|
|
109
|
+
createdAt: Date.now(),
|
|
110
|
+
updatedAt: Date.now(),
|
|
128
111
|
});
|
|
129
112
|
});
|
|
130
113
|
/**
|
package/lib/ai/yaml-file.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { generateObject, generateText } from "ai";
|
|
2
|
-
import
|
|
3
|
-
import path from "node:path";
|
|
2
|
+
import { StaticYamlFile } from "../fs/static-yaml-file";
|
|
4
3
|
import { Resource } from "../resource";
|
|
5
|
-
import { ignore } from "../util/ignore";
|
|
6
4
|
import { ark } from "./ark";
|
|
7
5
|
import { createModel } from "./client";
|
|
8
6
|
/**
|
|
@@ -96,29 +94,10 @@ const DEFAULT_YAML_SYSTEM_PROMPT = "You are a YAML generator. Create valid YAML
|
|
|
96
94
|
* });
|
|
97
95
|
*/
|
|
98
96
|
export const YAMLFile = Resource("ai::YAMLFile", async function (id, props) {
|
|
99
|
-
//
|
|
100
|
-
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
97
|
+
// Handle deletion phase
|
|
101
98
|
if (this.phase === "delete") {
|
|
102
|
-
try {
|
|
103
|
-
await fs.unlink(props.path);
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
// Ignore if file doesn't exist
|
|
107
|
-
if (error.code !== "ENOENT") {
|
|
108
|
-
throw error;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
99
|
return this.destroy();
|
|
112
100
|
}
|
|
113
|
-
// Dynamic import js-yaml to avoid dependency issues
|
|
114
|
-
// This allows the code to work even if js-yaml is not installed
|
|
115
|
-
let yaml;
|
|
116
|
-
try {
|
|
117
|
-
yaml = await import("yaml");
|
|
118
|
-
}
|
|
119
|
-
catch (err) {
|
|
120
|
-
throw new Error("The 'yaml' package is required for the YAMLFile resource. Please install it with: bun add yaml");
|
|
121
|
-
}
|
|
122
101
|
let yamlContent;
|
|
123
102
|
let yamlObject;
|
|
124
103
|
// Check if schema is provided
|
|
@@ -136,15 +115,9 @@ export const YAMLFile = Resource("ai::YAMLFile", async function (id, props) {
|
|
|
136
115
|
: { temperature: props.temperature }),
|
|
137
116
|
});
|
|
138
117
|
yamlObject = object;
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
indent: 2,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
throw new Error(`Failed to convert object to YAML: ${error.message}`);
|
|
147
|
-
}
|
|
118
|
+
// Let StaticYamlFile handle the YAML conversion
|
|
119
|
+
const file = await StaticYamlFile("file", props.path, yamlObject);
|
|
120
|
+
yamlContent = file.content;
|
|
148
121
|
}
|
|
149
122
|
else {
|
|
150
123
|
// Use fence-based extraction
|
|
@@ -181,29 +154,20 @@ export const YAMLFile = Resource("ai::YAMLFile", async function (id, props) {
|
|
|
181
154
|
content = retryResult.content;
|
|
182
155
|
}
|
|
183
156
|
yamlContent = content;
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
throw new Error(`Failed to parse YAML: ${error.message}`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (this.phase === "update" && props.path !== this.props.path) {
|
|
193
|
-
await ignore("ENOENT", () => fs.unlink(this.props.path));
|
|
157
|
+
// Create the file with the string content
|
|
158
|
+
const file = await StaticYamlFile("file", props.path, yamlContent);
|
|
159
|
+
// We need to parse the YAML to get the object representation
|
|
160
|
+
const yaml = await import("yaml");
|
|
161
|
+
yamlObject = yaml.parse(yamlContent);
|
|
194
162
|
}
|
|
195
|
-
// Write content to file
|
|
196
|
-
await fs.writeFile(props.path, yamlContent);
|
|
197
|
-
// Get file stats for timestamps
|
|
198
|
-
const stats = await fs.stat(props.path);
|
|
199
163
|
// Return the resource
|
|
200
164
|
return this({
|
|
201
165
|
...props,
|
|
202
166
|
schema: props.schema,
|
|
203
167
|
content: yamlContent,
|
|
204
168
|
yaml: yamlObject,
|
|
205
|
-
createdAt:
|
|
206
|
-
updatedAt:
|
|
169
|
+
createdAt: Date.now(),
|
|
170
|
+
updatedAt: Date.now(),
|
|
207
171
|
});
|
|
208
172
|
});
|
|
209
173
|
/**
|
package/lib/alchemy.js
CHANGED
|
@@ -33,6 +33,17 @@ function _alchemy(...args) {
|
|
|
33
33
|
if (typeof value === "string") {
|
|
34
34
|
return indent + value;
|
|
35
35
|
}
|
|
36
|
+
else if (value === null) {
|
|
37
|
+
return "null";
|
|
38
|
+
}
|
|
39
|
+
else if (value === undefined) {
|
|
40
|
+
return "undefined";
|
|
41
|
+
}
|
|
42
|
+
else if (typeof value === "number" ||
|
|
43
|
+
typeof value === "boolean" ||
|
|
44
|
+
typeof value === "bigint") {
|
|
45
|
+
return value.toString();
|
|
46
|
+
}
|
|
36
47
|
else if (value instanceof Promise) {
|
|
37
48
|
return resolve(await value);
|
|
38
49
|
}
|
|
@@ -55,7 +66,8 @@ function _alchemy(...args) {
|
|
|
55
66
|
}
|
|
56
67
|
else {
|
|
57
68
|
// TODO: support other types
|
|
58
|
-
|
|
69
|
+
console.log(value);
|
|
70
|
+
throw new Error(`Unsupported value type: ${value}`);
|
|
59
71
|
}
|
|
60
72
|
}));
|
|
61
73
|
// Construct the string template by joining template parts with interpolated values
|
|
@@ -104,7 +116,10 @@ _alchemy.secret = secret;
|
|
|
104
116
|
* apiKey: alchemy.secret(process.env.API_KEY)
|
|
105
117
|
* });
|
|
106
118
|
*/
|
|
107
|
-
function scope(id, options
|
|
119
|
+
function scope(id, options
|
|
120
|
+
// TODO: maybe we want to allow using _ = await alchemy.scope(import.meta)
|
|
121
|
+
// | [meta: ImportMeta]
|
|
122
|
+
) {
|
|
108
123
|
const scope = new Scope({
|
|
109
124
|
...options,
|
|
110
125
|
appName: options?.appName,
|
package/lib/apply.js
CHANGED
|
@@ -46,7 +46,7 @@ export async function apply(resource, props, options) {
|
|
|
46
46
|
}), null, 2);
|
|
47
47
|
if (oldProps === newProps && alwaysUpdate !== true) {
|
|
48
48
|
if (!quiet) {
|
|
49
|
-
console.log(`Skip: "${resource.FQN}" (no changes)`);
|
|
49
|
+
// console.log(`Skip: "${resource.FQN}" (no changes)`);
|
|
50
50
|
}
|
|
51
51
|
return state.output;
|
|
52
52
|
}
|
|
@@ -9,7 +9,7 @@ export async function generateAssetManifest(dir, fileOptions = [
|
|
|
9
9
|
cacheControl: "max-age=0,no-cache,no-store,must-revalidate",
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
|
-
files: ["**/*.js", "**/*.css"],
|
|
12
|
+
files: ["**/*.js", "**/*.css", "**/*.png", "**/*.webp"],
|
|
13
13
|
cacheControl: "max-age=31536000,public,immutable",
|
|
14
14
|
},
|
|
15
15
|
]) {
|
|
@@ -35,34 +35,44 @@ app.notFound(async (ctx) => {
|
|
|
35
35
|
}
|
|
36
36
|
// Fetch from KV
|
|
37
37
|
{
|
|
38
|
-
const object = await env.ASSETS.getWithMetadata(filePath
|
|
38
|
+
const object = await env.ASSETS.getWithMetadata(filePath, {
|
|
39
|
+
type: "arrayBuffer",
|
|
40
|
+
});
|
|
39
41
|
if (object.value) {
|
|
40
42
|
return await respond(200, filePath, object);
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
{
|
|
44
46
|
const guess = filePath + (filePath.endsWith("/") ? "" : "/") + "index.html";
|
|
45
|
-
const object = await env.ASSETS.getWithMetadata(guess
|
|
47
|
+
const object = await env.ASSETS.getWithMetadata(guess, {
|
|
48
|
+
type: "arrayBuffer",
|
|
49
|
+
});
|
|
46
50
|
if (object.value) {
|
|
47
51
|
return await respond(200, guess, object);
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
{
|
|
51
55
|
const guess = filePath + ".html";
|
|
52
|
-
const object = await env.ASSETS.getWithMetadata(guess
|
|
56
|
+
const object = await env.ASSETS.getWithMetadata(guess, {
|
|
57
|
+
type: "arrayBuffer",
|
|
58
|
+
});
|
|
53
59
|
if (object.value) {
|
|
54
60
|
return await respond(200, guess, object);
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
63
|
// Handle error page
|
|
58
64
|
if (env.ERROR_PAGE) {
|
|
59
|
-
const object = await env.ASSETS.getWithMetadata(env.ERROR_PAGE
|
|
65
|
+
const object = await env.ASSETS.getWithMetadata(env.ERROR_PAGE, {
|
|
66
|
+
type: "arrayBuffer",
|
|
67
|
+
});
|
|
60
68
|
if (object.value) {
|
|
61
69
|
return await respond(404, env.ERROR_PAGE, object);
|
|
62
70
|
}
|
|
63
71
|
}
|
|
64
72
|
else {
|
|
65
|
-
const object = await env.ASSETS.getWithMetadata(env.INDEX_PAGE
|
|
73
|
+
const object = await env.ASSETS.getWithMetadata(env.INDEX_PAGE, {
|
|
74
|
+
type: "arrayBuffer",
|
|
75
|
+
});
|
|
66
76
|
if (object.value) {
|
|
67
77
|
return await respond(200, env.INDEX_PAGE, object);
|
|
68
78
|
}
|
|
@@ -92,8 +102,7 @@ app.notFound(async (ctx) => {
|
|
|
92
102
|
headers.set("content-type", object.metadata.contentType);
|
|
93
103
|
headers.set("cache-control", object.metadata.cacheControl);
|
|
94
104
|
}
|
|
95
|
-
//
|
|
96
|
-
// const response = new Response(base64ToArrayBuffer(object.value), {
|
|
105
|
+
// Create response with the raw data directly
|
|
97
106
|
const response = new Response(object.value, {
|
|
98
107
|
status,
|
|
99
108
|
headers,
|
|
@@ -122,6 +122,9 @@ export const StaticSite = Resource("cloudflare::StaticSite", {
|
|
|
122
122
|
}),
|
|
123
123
|
generateAssetManifest(props.dir),
|
|
124
124
|
]);
|
|
125
|
+
console.log({
|
|
126
|
+
assetManifest,
|
|
127
|
+
});
|
|
125
128
|
// Step 3: Upload assets to KV
|
|
126
129
|
await uploadAssetManifest(api, kv.namespaceId, assetManifest);
|
|
127
130
|
// Prepare the bindings for the worker
|
|
@@ -34,8 +34,10 @@ export async function uploadAssetManifest(api, namespaceId, manifest) {
|
|
|
34
34
|
// Create FormData for multipart upload
|
|
35
35
|
const formData = new FormData();
|
|
36
36
|
formData.append("metadata", JSON.stringify(metadata));
|
|
37
|
-
// Add file content as 'value'
|
|
38
|
-
const blob = new Blob([content]
|
|
37
|
+
// Add file content as 'value' - Fix for binary file corruption
|
|
38
|
+
const blob = new Blob([new Uint8Array(content)], {
|
|
39
|
+
type: item.contentType,
|
|
40
|
+
});
|
|
39
41
|
formData.append("value", blob);
|
|
40
42
|
try {
|
|
41
43
|
const response = await api.put(`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}/values/${item.key}`, formData);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Context } from "../context";
|
|
2
|
+
import { Resource } from "../resource";
|
|
3
|
+
/**
|
|
4
|
+
* Properties for creating a CopyFile resource
|
|
5
|
+
*/
|
|
6
|
+
export interface CopyFileProps {
|
|
7
|
+
/**
|
|
8
|
+
* Source path of the file to copy
|
|
9
|
+
*/
|
|
10
|
+
src: string;
|
|
11
|
+
/**
|
|
12
|
+
* Destination path where the file should be copied to
|
|
13
|
+
*/
|
|
14
|
+
dest: string;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to overwrite the destination file if it already exists
|
|
17
|
+
* @default true
|
|
18
|
+
*/
|
|
19
|
+
overwrite?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Output returned after CopyFile creation/update
|
|
23
|
+
*/
|
|
24
|
+
export interface CopyFile extends Resource<"fs::CopyFile">, CopyFileProps {
|
|
25
|
+
/**
|
|
26
|
+
* Time at which the object was created
|
|
27
|
+
*/
|
|
28
|
+
createdAt: number;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the file was successfully copied
|
|
31
|
+
*/
|
|
32
|
+
copied: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* CopyFile Resource
|
|
36
|
+
*
|
|
37
|
+
* Copies a file from a source location to a destination location.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // Copy a file to a new location
|
|
41
|
+
* const copiedFile = await CopyFile("config-copy", {
|
|
42
|
+
* src: "config.json",
|
|
43
|
+
* dest: "backup/config.json"
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // Copy a file without overwriting if destination exists
|
|
48
|
+
* const safeCopy = await CopyFile("safe-copy", {
|
|
49
|
+
* src: "data.json",
|
|
50
|
+
* dest: "backup/data.json",
|
|
51
|
+
* overwrite: false
|
|
52
|
+
* });
|
|
53
|
+
*/
|
|
54
|
+
export declare const CopyFile: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<CopyFile>, id: string, props: CopyFileProps) => Promise<CopyFile>);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { Resource } from "../resource";
|
|
3
|
+
import { ignore } from "../util/ignore";
|
|
4
|
+
/**
|
|
5
|
+
* CopyFile Resource
|
|
6
|
+
*
|
|
7
|
+
* Copies a file from a source location to a destination location.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* // Copy a file to a new location
|
|
11
|
+
* const copiedFile = await CopyFile("config-copy", {
|
|
12
|
+
* src: "config.json",
|
|
13
|
+
* dest: "backup/config.json"
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Copy a file without overwriting if destination exists
|
|
18
|
+
* const safeCopy = await CopyFile("safe-copy", {
|
|
19
|
+
* src: "data.json",
|
|
20
|
+
* dest: "backup/data.json",
|
|
21
|
+
* overwrite: false
|
|
22
|
+
* });
|
|
23
|
+
*/
|
|
24
|
+
export const CopyFile = Resource("fs::CopyFile", async function (id, props) {
|
|
25
|
+
const { src, dest, overwrite = true } = props;
|
|
26
|
+
if (this.phase === "delete") {
|
|
27
|
+
// When deleting, remove the destination file
|
|
28
|
+
await ignore("ENOENT", async () => fs.promises.unlink(dest));
|
|
29
|
+
return this.destroy();
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
try {
|
|
33
|
+
// Check if source file exists
|
|
34
|
+
await fs.promises.access(src, fs.constants.F_OK);
|
|
35
|
+
// If this is an update and the destination has changed, delete the old file
|
|
36
|
+
if (this.phase === "update" &&
|
|
37
|
+
this.output?.dest &&
|
|
38
|
+
this.output.dest !== dest) {
|
|
39
|
+
await ignore("ENOENT", async () => fs.promises.unlink(this.output.dest));
|
|
40
|
+
}
|
|
41
|
+
// Check if destination file exists
|
|
42
|
+
const destinationExists = await fs.promises
|
|
43
|
+
.access(dest, fs.constants.F_OK)
|
|
44
|
+
.then(() => true)
|
|
45
|
+
.catch(() => false);
|
|
46
|
+
// Copy file if destination doesn't exist or overwrite is true
|
|
47
|
+
if (!destinationExists || overwrite) {
|
|
48
|
+
await fs.promises.copyFile(src, dest);
|
|
49
|
+
}
|
|
50
|
+
return this({
|
|
51
|
+
src,
|
|
52
|
+
dest,
|
|
53
|
+
overwrite,
|
|
54
|
+
copied: true,
|
|
55
|
+
createdAt: Date.now(),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error(`Error copying file from ${src} to ${dest}:`, error);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
package/lib/fs/file.d.ts
CHANGED
|
@@ -79,6 +79,19 @@ export interface File extends Resource<"fs::File"> {
|
|
|
79
79
|
* path: "logs/app.log",
|
|
80
80
|
* content: "application log entry"
|
|
81
81
|
* });
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* // Update file content and path
|
|
85
|
+
* let file = await File("config.json", {
|
|
86
|
+
* path: "config.json",
|
|
87
|
+
* content: '{ "version": "1.0.0" }'
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* // Later, update the path and content (old file will be removed)
|
|
91
|
+
* file = await File("config.json", {
|
|
92
|
+
* path: "config/config.json",
|
|
93
|
+
* content: '{ "version": "1.0.1" }'
|
|
94
|
+
* });
|
|
82
95
|
*/
|
|
83
96
|
export declare const File: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<File>, id: string, props: {
|
|
84
97
|
path: string;
|
package/lib/fs/file.js
CHANGED
|
@@ -36,6 +36,19 @@ alchemy.files = async (...args) => {
|
|
|
36
36
|
* path: "logs/app.log",
|
|
37
37
|
* content: "application log entry"
|
|
38
38
|
* });
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* // Update file content and path
|
|
42
|
+
* let file = await File("config.json", {
|
|
43
|
+
* path: "config.json",
|
|
44
|
+
* content: '{ "version": "1.0.0" }'
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* // Later, update the path and content (old file will be removed)
|
|
48
|
+
* file = await File("config.json", {
|
|
49
|
+
* path: "config/config.json",
|
|
50
|
+
* content: '{ "version": "1.0.1" }'
|
|
51
|
+
* });
|
|
39
52
|
*/
|
|
40
53
|
export const File = Resource("fs::File", async function (id, props) {
|
|
41
54
|
const filePath = props?.path ?? id;
|
|
@@ -43,12 +56,18 @@ export const File = Resource("fs::File", async function (id, props) {
|
|
|
43
56
|
await ignore("ENOENT", async () => fs.promises.unlink(filePath));
|
|
44
57
|
return this.destroy();
|
|
45
58
|
}
|
|
46
|
-
else
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
else if (this.phase === "update" &&
|
|
60
|
+
this.output &&
|
|
61
|
+
this.output.path !== filePath) {
|
|
62
|
+
// If path has changed, delete the old file
|
|
63
|
+
console.log(`File: Path changed from ${this.output.path} to ${filePath}, removing old file`);
|
|
64
|
+
await ignore("ENOENT", async () => fs.promises.unlink(this.output.path));
|
|
51
65
|
}
|
|
66
|
+
// Create directory and write file
|
|
67
|
+
await fs.promises.mkdir(path.dirname(filePath), {
|
|
68
|
+
recursive: true,
|
|
69
|
+
});
|
|
70
|
+
await fs.promises.writeFile(filePath, props.content);
|
|
52
71
|
return this({
|
|
53
72
|
path: filePath,
|
|
54
73
|
content: props.content,
|
package/lib/fs/index.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
export * from "./copy-file";
|
|
1
2
|
export * from "./file";
|
|
2
3
|
export * from "./file-collection";
|
|
3
4
|
export * from "./file-ref";
|
|
5
|
+
export * from "./file-system-state-store";
|
|
4
6
|
export * from "./folder";
|
|
7
|
+
export * from "./static-astro-file";
|
|
8
|
+
export * from "./static-css-file";
|
|
9
|
+
export * from "./static-html-file";
|
|
5
10
|
export * from "./static-json-file";
|
|
6
11
|
export * from "./static-text-file";
|
|
7
12
|
export * from "./static-typescript-file";
|
|
13
|
+
export * from "./static-vue-file";
|
|
8
14
|
export * from "./static-yaml-file";
|
package/lib/fs/index.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
export * from "./copy-file";
|
|
1
2
|
export * from "./file";
|
|
2
3
|
export * from "./file-collection";
|
|
3
4
|
export * from "./file-ref";
|
|
5
|
+
export * from "./file-system-state-store";
|
|
4
6
|
export * from "./folder";
|
|
7
|
+
export * from "./static-astro-file";
|
|
8
|
+
export * from "./static-css-file";
|
|
9
|
+
export * from "./static-html-file";
|
|
5
10
|
export * from "./static-json-file";
|
|
6
11
|
export * from "./static-text-file";
|
|
7
12
|
export * from "./static-typescript-file";
|
|
13
|
+
export * from "./static-vue-file";
|
|
8
14
|
export * from "./static-yaml-file";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { File } from "./file";
|
|
2
|
+
export type StaticAstroFile = File;
|
|
3
|
+
/**
|
|
4
|
+
* Creates a static Astro component file
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* // Create an Astro component file with content
|
|
8
|
+
* const header = await StaticAstroFile("Header.astro",
|
|
9
|
+
* `---
|
|
10
|
+
* import Logo from '../components/Logo.astro';
|
|
11
|
+
* const navItems = ['Home', 'About', 'Contact'];
|
|
12
|
+
* ---
|
|
13
|
+
*
|
|
14
|
+
* <header class="header">
|
|
15
|
+
* <Logo />
|
|
16
|
+
* <nav>
|
|
17
|
+
* <ul>
|
|
18
|
+
* {navItems.map(item => (
|
|
19
|
+
* <li><a href={`/${item.toLowerCase()}`}>{item}</a></li>
|
|
20
|
+
* ))}
|
|
21
|
+
* </ul>
|
|
22
|
+
* </nav>
|
|
23
|
+
* </header>
|
|
24
|
+
*
|
|
25
|
+
* <style>
|
|
26
|
+
* .header {
|
|
27
|
+
* display: flex;
|
|
28
|
+
* justify-content: space-between;
|
|
29
|
+
* padding: 1rem;
|
|
30
|
+
* }
|
|
31
|
+
* </style>`
|
|
32
|
+
* );
|
|
33
|
+
*/
|
|
34
|
+
export declare function StaticAstroFile(id: string, ...args: [content: string] | [path: string, content: string]): Promise<StaticAstroFile>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { File } from "./file";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a static Astro component file
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* // Create an Astro component file with content
|
|
7
|
+
* const header = await StaticAstroFile("Header.astro",
|
|
8
|
+
* `---
|
|
9
|
+
* import Logo from '../components/Logo.astro';
|
|
10
|
+
* const navItems = ['Home', 'About', 'Contact'];
|
|
11
|
+
* ---
|
|
12
|
+
*
|
|
13
|
+
* <header class="header">
|
|
14
|
+
* <Logo />
|
|
15
|
+
* <nav>
|
|
16
|
+
* <ul>
|
|
17
|
+
* {navItems.map(item => (
|
|
18
|
+
* <li><a href={`/${item.toLowerCase()}`}>{item}</a></li>
|
|
19
|
+
* ))}
|
|
20
|
+
* </ul>
|
|
21
|
+
* </nav>
|
|
22
|
+
* </header>
|
|
23
|
+
*
|
|
24
|
+
* <style>
|
|
25
|
+
* .header {
|
|
26
|
+
* display: flex;
|
|
27
|
+
* justify-content: space-between;
|
|
28
|
+
* padding: 1rem;
|
|
29
|
+
* }
|
|
30
|
+
* </style>`
|
|
31
|
+
* );
|
|
32
|
+
*/
|
|
33
|
+
export function StaticAstroFile(id, ...args) {
|
|
34
|
+
const [path, content] = args.length === 1 ? [id, args[0]] : args;
|
|
35
|
+
return File(id, {
|
|
36
|
+
path,
|
|
37
|
+
content,
|
|
38
|
+
});
|
|
39
|
+
}
|