alchemy 0.3.0 → 0.4.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/README.md +8 -3
- 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 +76 -5
- package/lib/ai/document.js +112 -56
- package/lib/ai/html-file.js +7 -24
- package/lib/ai/json-file.js +11 -32
- 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.d.ts +5 -0
- package/lib/alchemy.js +61 -3
- package/lib/apply.js +18 -11
- package/lib/aws/account-id.d.ts +4 -1
- package/lib/aws/account-id.js +1 -1
- package/lib/aws/credentials.d.ts +5 -0
- package/lib/aws/credentials.js +0 -0
- package/lib/aws/oidc/oidc-provider.js +2 -2
- package/lib/aws/role.js +35 -23
- package/lib/cloudflare/account-api-token.d.ts +168 -0
- package/lib/cloudflare/account-api-token.js +179 -0
- package/lib/cloudflare/account-id.d.ts +1 -0
- package/lib/cloudflare/account-id.js +0 -0
- package/lib/cloudflare/api.d.ts +38 -1
- package/lib/cloudflare/api.js +55 -4
- package/lib/cloudflare/bucket.d.ts +118 -3
- package/lib/cloudflare/bucket.js +269 -89
- package/lib/cloudflare/custom-domain.d.ts +75 -0
- package/lib/cloudflare/custom-domain.js +154 -0
- package/lib/cloudflare/{dns.js → dns-records.js} +14 -1
- package/lib/cloudflare/generate-asset-manifest.js +1 -1
- package/lib/cloudflare/index.d.ts +5 -1
- package/lib/cloudflare/index.js +5 -1
- package/lib/cloudflare/permission-groups.d.ts +78 -0
- package/lib/cloudflare/permission-groups.js +48 -0
- package/lib/cloudflare/r2-rest-state-store.d.ts +2 -1
- package/lib/cloudflare/r2-rest-state-store.js +3 -2
- package/lib/cloudflare/static-site-router.js +16 -7
- package/lib/cloudflare/static-site.d.ts +17 -18
- package/lib/cloudflare/static-site.js +19 -7
- package/lib/cloudflare/upload-asset-manifest.js +4 -2
- package/lib/{util/encrypt.d.ts → encrypt.d.ts} +1 -1
- package/lib/{util/encrypt.js → encrypt.js} +1 -1
- package/lib/fs/copy-file.d.ts +54 -0
- package/lib/fs/copy-file.js +63 -0
- package/lib/fs/file-system-state-store.js +1 -1
- package/lib/fs/file.d.ts +31 -0
- package/lib/fs/file.js +30 -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/{providers.d.ts → docs/providers.d.ts} +14 -4
- package/lib/internal/docs/providers.js +196 -0
- package/lib/secret.d.ts +3 -0
- package/lib/secret.js +13 -0
- package/lib/{util/serde.d.ts → serde.d.ts} +1 -1
- package/lib/{util/serde.js → serde.js} +12 -4
- package/lib/test/bun.js +3 -2
- package/lib/util/sha256.d.ts +1 -0
- package/lib/util/sha256.js +4 -0
- package/lib/web/vitepress/config.d.ts +11 -0
- package/lib/web/vitepress/config.js +32 -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 +2 -0
- package/lib/web/vitepress/index.js +2 -0
- package/lib/web/vitepress/process-front-matter-files.d.ts +18 -0
- package/lib/web/vitepress/process-front-matter-files.js +68 -0
- package/lib/web/vitepress/vitepress.d.ts +5 -4
- package/lib/web/vitepress/vitepress.js +20 -36
- package/package.json +8 -4
- 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 +171 -66
- package/src/ai/html-file.ts +11 -30
- package/src/ai/json-file.ts +13 -37
- 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 +74 -9
- package/src/apply.ts +27 -26
- package/src/aws/account-id.ts +6 -2
- package/src/aws/credentials.ts +6 -0
- package/src/aws/oidc/oidc-provider.ts +17 -17
- package/src/aws/role.ts +68 -54
- package/src/cloudflare/account-api-token.ts +365 -0
- package/src/cloudflare/account-id.ts +0 -0
- package/src/cloudflare/api.ts +88 -17
- package/src/cloudflare/bucket.ts +493 -133
- package/src/cloudflare/custom-domain.ts +318 -0
- package/src/cloudflare/{dns.ts → dns-records.ts} +36 -22
- package/src/cloudflare/generate-asset-manifest.ts +4 -4
- package/src/cloudflare/index.ts +5 -1
- package/src/cloudflare/permission-groups.ts +137 -0
- package/src/cloudflare/r2-rest-state-store.ts +14 -13
- package/src/cloudflare/static-site-router.ts +18 -8
- package/src/cloudflare/static-site.ts +27 -35
- package/src/cloudflare/upload-asset-manifest.ts +6 -4
- package/src/cloudflare/worker.ts +44 -43
- package/src/dns/import-dns.ts +4 -4
- package/src/{util/encrypt.ts → encrypt.ts} +7 -10
- package/src/fs/copy-file.ts +115 -0
- package/src/fs/file-system-state-store.ts +5 -5
- package/src/fs/file.ts +64 -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/docs/providers.ts +281 -0
- package/src/secret.ts +16 -0
- package/src/{util/serde.ts → serde.ts} +16 -10
- package/src/test/bun.ts +8 -7
- package/src/util/sha256.ts +5 -0
- package/src/web/vitepress/config.ts +50 -0
- package/src/web/vitepress/home-page.ts +54 -42
- package/src/web/vitepress/index.ts +2 -0
- package/src/web/vitepress/process-front-matter-files.ts +98 -0
- package/src/web/vitepress/vitepress.ts +34 -50
- package/lib/internal/getting-started.d.ts +0 -10
- package/lib/internal/getting-started.js +0 -94
- package/lib/internal/project.d.ts +0 -13
- package/lib/internal/project.js +0 -101
- package/lib/internal/providers.js +0 -148
- package/src/internal/getting-started.ts +0 -107
- package/src/internal/project.ts +0 -119
- package/src/internal/providers.ts +0 -209
- /package/lib/cloudflare/{dns.d.ts → dns-records.d.ts} +0 -0
package/src/ai/json-file.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { generateObject, generateText } from "ai";
|
|
2
2
|
import type { JsonSchema, Type, type } from "arktype";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
4
|
-
import path from "node:path";
|
|
5
3
|
import type { Context } from "../context";
|
|
4
|
+
import { StaticJsonFile } from "../fs/static-json-file";
|
|
6
5
|
import { Resource } from "../resource";
|
|
7
6
|
import type { Secret } from "../secret";
|
|
8
|
-
import { ignore } from "../util/ignore";
|
|
9
7
|
import { ark } from "./ark";
|
|
10
8
|
import { type ModelConfig, createModel } from "./client";
|
|
11
9
|
|
|
@@ -193,25 +191,11 @@ export const JSONFile = Resource("ai::JSONFile", async function <
|
|
|
193
191
|
>(this: Context<JSONFile<T extends Type<any, any> ? type.infer<T> : any>>, id: string, props: JSONFileProps<T>): Promise<
|
|
194
192
|
JSONFile<T extends Type<any, any> ? type.infer<T> : any>
|
|
195
193
|
> {
|
|
196
|
-
//
|
|
197
|
-
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
198
|
-
|
|
194
|
+
// Handle deletion phase
|
|
199
195
|
if (this.phase === "delete") {
|
|
200
|
-
try {
|
|
201
|
-
await fs.unlink(props.path);
|
|
202
|
-
} catch (error: any) {
|
|
203
|
-
// Ignore if file doesn't exist
|
|
204
|
-
if (error.code !== "ENOENT") {
|
|
205
|
-
throw error;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
196
|
return this.destroy();
|
|
209
197
|
}
|
|
210
198
|
|
|
211
|
-
// Determine if we should use pretty printing
|
|
212
|
-
const pretty = props.pretty !== false;
|
|
213
|
-
const indent = props.indent ?? 2;
|
|
214
|
-
|
|
215
199
|
let jsonContent: string;
|
|
216
200
|
let jsonObject: any;
|
|
217
201
|
|
|
@@ -232,9 +216,10 @@ export const JSONFile = Resource("ai::JSONFile", async function <
|
|
|
232
216
|
});
|
|
233
217
|
|
|
234
218
|
jsonObject = object;
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
219
|
+
|
|
220
|
+
// Use StaticJsonFile to create the file
|
|
221
|
+
const file = await StaticJsonFile("file", props.path, jsonObject);
|
|
222
|
+
jsonContent = file.content;
|
|
238
223
|
} else {
|
|
239
224
|
// Use fence-based extraction
|
|
240
225
|
// Use provided system prompt or default
|
|
@@ -277,31 +262,22 @@ export const JSONFile = Resource("ai::JSONFile", async function <
|
|
|
277
262
|
content = retryResult.content;
|
|
278
263
|
}
|
|
279
264
|
|
|
280
|
-
// Parse JSON
|
|
265
|
+
// Parse JSON to get the object representation
|
|
281
266
|
jsonObject = JSON.parse(content);
|
|
282
267
|
|
|
283
|
-
//
|
|
284
|
-
|
|
268
|
+
// Use StaticJsonFile to create the file
|
|
269
|
+
const file = await StaticJsonFile("file", props.path, jsonObject);
|
|
270
|
+
jsonContent = file.content;
|
|
285
271
|
}
|
|
286
272
|
|
|
287
|
-
if (this.phase === "update" && props.path !== this.props.path) {
|
|
288
|
-
await ignore("ENOENT", () => fs.unlink(this.props.path));
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Write content to file
|
|
292
|
-
await fs.writeFile(props.path, jsonContent);
|
|
293
|
-
|
|
294
|
-
// Get file stats for timestamps
|
|
295
|
-
const stats = await fs.stat(props.path);
|
|
296
|
-
|
|
297
273
|
// Return the resource
|
|
298
274
|
return this({
|
|
299
275
|
...props,
|
|
300
276
|
schema: props.schema,
|
|
301
277
|
content: jsonContent,
|
|
302
278
|
json: jsonObject,
|
|
303
|
-
createdAt:
|
|
304
|
-
updatedAt:
|
|
279
|
+
createdAt: Date.now(),
|
|
280
|
+
updatedAt: Date.now(),
|
|
305
281
|
});
|
|
306
282
|
});
|
|
307
283
|
|
|
@@ -313,7 +289,7 @@ export const JSONFile = Resource("ai::JSONFile", async function <
|
|
|
313
289
|
* @returns The extracted JSON or error message
|
|
314
290
|
*/
|
|
315
291
|
async function extractJSONContent(
|
|
316
|
-
text: string
|
|
292
|
+
text: string
|
|
317
293
|
): Promise<{ content: string; error?: string }> {
|
|
318
294
|
const jsonCodeRegex = /```json\s*([\s\S]*?)```/g;
|
|
319
295
|
const matches = Array.from(text.matchAll(jsonCodeRegex));
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { generateText } from "ai";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import prettier from "prettier";
|
|
5
2
|
import type { Context } from "../context";
|
|
3
|
+
import { StaticTypeScriptFile } from "../fs/static-typescript-file";
|
|
6
4
|
import { Resource } from "../resource";
|
|
7
5
|
import type { Secret } from "../secret";
|
|
8
|
-
import {
|
|
9
|
-
import { type ModelConfig, createModel } from "./client";
|
|
6
|
+
import { createModel, type ModelConfig } from "./client";
|
|
10
7
|
|
|
11
8
|
/**
|
|
12
9
|
* Properties for creating or updating a TypeScriptFile
|
|
@@ -65,7 +62,7 @@ export interface TypeScriptFileProps {
|
|
|
65
62
|
* Prettier configuration to use for formatting the TypeScript code
|
|
66
63
|
* If not provided, will use the default Prettier configuration
|
|
67
64
|
*/
|
|
68
|
-
prettierConfig?:
|
|
65
|
+
prettierConfig?: object;
|
|
69
66
|
}
|
|
70
67
|
|
|
71
68
|
/**
|
|
@@ -166,20 +163,11 @@ export const TypeScriptFile = Resource(
|
|
|
166
163
|
async function (
|
|
167
164
|
this: Context<TypeScriptFile>,
|
|
168
165
|
id: string,
|
|
169
|
-
props: TypeScriptFileProps
|
|
166
|
+
props: TypeScriptFileProps
|
|
170
167
|
): Promise<TypeScriptFile> {
|
|
171
|
-
//
|
|
172
|
-
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
173
|
-
|
|
168
|
+
// Handle delete phase
|
|
174
169
|
if (this.phase === "delete") {
|
|
175
|
-
|
|
176
|
-
await fs.unlink(props.path);
|
|
177
|
-
} catch (error: any) {
|
|
178
|
-
// Ignore if file doesn't exist
|
|
179
|
-
if (error.code !== "ENOENT") {
|
|
180
|
-
throw error;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
170
|
+
// StaticTypeScriptFile will handle the deletion
|
|
183
171
|
return this.destroy();
|
|
184
172
|
}
|
|
185
173
|
|
|
@@ -218,46 +206,24 @@ export const TypeScriptFile = Resource(
|
|
|
218
206
|
|
|
219
207
|
if (retryResult.error) {
|
|
220
208
|
throw new Error(
|
|
221
|
-
`Failed to generate valid TypeScript code: ${retryResult.error}
|
|
209
|
+
`Failed to generate valid TypeScript code: ${retryResult.error}`
|
|
222
210
|
);
|
|
223
211
|
}
|
|
224
212
|
|
|
225
213
|
code = retryResult.code;
|
|
226
214
|
}
|
|
227
215
|
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
// Set default parser to typescript
|
|
231
|
-
const prettierOptions: prettier.Options = {
|
|
232
|
-
parser: "typescript",
|
|
233
|
-
...props.prettierConfig,
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// Format the code
|
|
237
|
-
code = await prettier.format(code, prettierOptions);
|
|
238
|
-
} catch (error) {
|
|
239
|
-
// If Prettier formatting fails, just use the unformatted code
|
|
240
|
-
console.warn("Failed to format TypeScript code with Prettier:", error);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (this.phase === "update" && props.path !== this.props.path) {
|
|
244
|
-
await ignore("ENOENT", () => fs.unlink(this.props.path));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Write content to file
|
|
248
|
-
await fs.writeFile(props.path, code);
|
|
249
|
-
|
|
250
|
-
// Get file stats for timestamps
|
|
251
|
-
const stats = await fs.stat(props.path);
|
|
216
|
+
// Use StaticTypeScriptFile to create/update the file
|
|
217
|
+
const file = await StaticTypeScriptFile("file", props.path, code);
|
|
252
218
|
|
|
253
219
|
// Return the resource
|
|
254
220
|
return this({
|
|
255
221
|
...props,
|
|
256
|
-
content:
|
|
257
|
-
createdAt:
|
|
258
|
-
updatedAt:
|
|
222
|
+
content: file.content,
|
|
223
|
+
createdAt: Date.now(),
|
|
224
|
+
updatedAt: Date.now(),
|
|
259
225
|
});
|
|
260
|
-
}
|
|
226
|
+
}
|
|
261
227
|
);
|
|
262
228
|
|
|
263
229
|
/**
|
|
@@ -268,7 +234,7 @@ export const TypeScriptFile = Resource(
|
|
|
268
234
|
* @returns The extracted TypeScript code or error message
|
|
269
235
|
*/
|
|
270
236
|
async function extractTypeScriptCode(
|
|
271
|
-
text: string
|
|
237
|
+
text: string
|
|
272
238
|
): Promise<{ code: string; error?: string }> {
|
|
273
239
|
const tsCodeRegex = /```ts\s*([\s\S]*?)```/g;
|
|
274
240
|
const matches = Array.from(text.matchAll(tsCodeRegex));
|
package/src/ai/vue-file.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { generateText } from "ai";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
2
|
import type { Context } from "../context";
|
|
3
|
+
import { StaticVueFile } from "../fs/static-vue-file";
|
|
5
4
|
import { Resource } from "../resource";
|
|
6
5
|
import type { Secret } from "../secret";
|
|
7
|
-
import { ignore } from "../util/ignore";
|
|
8
6
|
import { type ModelConfig, createModel } from "./client";
|
|
9
7
|
|
|
10
8
|
/**
|
|
@@ -149,20 +147,10 @@ export const VueFile = Resource(
|
|
|
149
147
|
async function (
|
|
150
148
|
this: Context<VueFile>,
|
|
151
149
|
id: string,
|
|
152
|
-
props: VueFileProps
|
|
150
|
+
props: VueFileProps
|
|
153
151
|
): Promise<VueFile> {
|
|
154
|
-
//
|
|
155
|
-
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
156
|
-
|
|
152
|
+
// Handle deletion phase
|
|
157
153
|
if (this.phase === "delete") {
|
|
158
|
-
try {
|
|
159
|
-
await fs.unlink(props.path);
|
|
160
|
-
} catch (error: any) {
|
|
161
|
-
// Ignore if file doesn't exist
|
|
162
|
-
if (error.code !== "ENOENT") {
|
|
163
|
-
throw error;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
154
|
return this.destroy();
|
|
167
155
|
}
|
|
168
156
|
|
|
@@ -201,31 +189,24 @@ export const VueFile = Resource(
|
|
|
201
189
|
|
|
202
190
|
if (retryResult.error) {
|
|
203
191
|
throw new Error(
|
|
204
|
-
`Failed to generate valid Vue code: ${retryResult.error}
|
|
192
|
+
`Failed to generate valid Vue code: ${retryResult.error}`
|
|
205
193
|
);
|
|
206
194
|
}
|
|
207
195
|
|
|
208
196
|
code = retryResult.code;
|
|
209
197
|
}
|
|
210
198
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Write content to file
|
|
216
|
-
await fs.writeFile(props.path, code);
|
|
217
|
-
|
|
218
|
-
// Get file stats for timestamps
|
|
219
|
-
const stats = await fs.stat(props.path);
|
|
199
|
+
// Use StaticVueFile to create/update the file
|
|
200
|
+
const file = await StaticVueFile("file", props.path, code);
|
|
220
201
|
|
|
221
202
|
// Return the resource
|
|
222
203
|
return this({
|
|
223
204
|
...props,
|
|
224
|
-
content:
|
|
225
|
-
createdAt:
|
|
226
|
-
updatedAt:
|
|
205
|
+
content: file.content,
|
|
206
|
+
createdAt: Date.now(),
|
|
207
|
+
updatedAt: Date.now(),
|
|
227
208
|
});
|
|
228
|
-
}
|
|
209
|
+
}
|
|
229
210
|
);
|
|
230
211
|
|
|
231
212
|
/**
|
|
@@ -236,7 +217,7 @@ export const VueFile = Resource(
|
|
|
236
217
|
* @returns The extracted Vue code or error message
|
|
237
218
|
*/
|
|
238
219
|
async function extractVueCode(
|
|
239
|
-
text: string
|
|
220
|
+
text: string
|
|
240
221
|
): Promise<{ code: string; error?: string }> {
|
|
241
222
|
const vueCodeRegex = /```vue\s*([\s\S]*?)```/g;
|
|
242
223
|
const matches = Array.from(text.matchAll(vueCodeRegex));
|
package/src/ai/yaml-file.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { generateObject, generateText } from "ai";
|
|
2
2
|
import type { JsonSchema, Type, type } from "arktype";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
4
|
-
import path from "node:path";
|
|
5
3
|
import type { Context } from "../context";
|
|
4
|
+
import { StaticYamlFile } from "../fs/static-yaml-file";
|
|
6
5
|
import { Resource } from "../resource";
|
|
7
6
|
import type { Secret } from "../secret";
|
|
8
|
-
import { ignore } from "../util/ignore";
|
|
9
7
|
import { ark } from "./ark";
|
|
10
8
|
import { type ModelConfig, createModel } from "./client";
|
|
11
9
|
|
|
@@ -200,32 +198,11 @@ export const YAMLFile = Resource("ai::YAMLFile", async function <
|
|
|
200
198
|
>(this: Context<YAMLFile<T extends Type<any, any> ? type.infer<T> : any>>, id: string, props: YAMLFileProps<T>): Promise<
|
|
201
199
|
YAMLFile<T extends Type<any, any> ? type.infer<T> : any>
|
|
202
200
|
> {
|
|
203
|
-
//
|
|
204
|
-
await fs.mkdir(path.dirname(props.path), { recursive: true });
|
|
205
|
-
|
|
201
|
+
// Handle deletion phase
|
|
206
202
|
if (this.phase === "delete") {
|
|
207
|
-
try {
|
|
208
|
-
await fs.unlink(props.path);
|
|
209
|
-
} catch (error: any) {
|
|
210
|
-
// Ignore if file doesn't exist
|
|
211
|
-
if (error.code !== "ENOENT") {
|
|
212
|
-
throw error;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
203
|
return this.destroy();
|
|
216
204
|
}
|
|
217
205
|
|
|
218
|
-
// Dynamic import js-yaml to avoid dependency issues
|
|
219
|
-
// This allows the code to work even if js-yaml is not installed
|
|
220
|
-
let yaml: typeof import("yaml");
|
|
221
|
-
try {
|
|
222
|
-
yaml = await import("yaml");
|
|
223
|
-
} catch (err) {
|
|
224
|
-
throw new Error(
|
|
225
|
-
"The 'yaml' package is required for the YAMLFile resource. Please install it with: bun add yaml",
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
206
|
let yamlContent: string;
|
|
230
207
|
let yamlObject: any;
|
|
231
208
|
|
|
@@ -247,16 +224,9 @@ export const YAMLFile = Resource("ai::YAMLFile", async function <
|
|
|
247
224
|
|
|
248
225
|
yamlObject = object;
|
|
249
226
|
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
indent: 2,
|
|
254
|
-
});
|
|
255
|
-
} catch (error) {
|
|
256
|
-
throw new Error(
|
|
257
|
-
`Failed to convert object to YAML: ${(error as Error).message}`,
|
|
258
|
-
);
|
|
259
|
-
}
|
|
227
|
+
// Let StaticYamlFile handle the YAML conversion
|
|
228
|
+
const file = await StaticYamlFile("file", props.path, yamlObject);
|
|
229
|
+
yamlContent = file.content;
|
|
260
230
|
} else {
|
|
261
231
|
// Use fence-based extraction
|
|
262
232
|
// Use provided system prompt or default
|
|
@@ -301,32 +271,22 @@ export const YAMLFile = Resource("ai::YAMLFile", async function <
|
|
|
301
271
|
|
|
302
272
|
yamlContent = content;
|
|
303
273
|
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
yamlObject = yaml.parse(yamlContent);
|
|
307
|
-
} catch (error) {
|
|
308
|
-
throw new Error(`Failed to parse YAML: ${(error as Error).message}`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
274
|
+
// Create the file with the string content
|
|
275
|
+
const file = await StaticYamlFile("file", props.path, yamlContent);
|
|
311
276
|
|
|
312
|
-
|
|
313
|
-
await
|
|
277
|
+
// We need to parse the YAML to get the object representation
|
|
278
|
+
const yaml = await import("yaml");
|
|
279
|
+
yamlObject = yaml.parse(yamlContent);
|
|
314
280
|
}
|
|
315
281
|
|
|
316
|
-
// Write content to file
|
|
317
|
-
await fs.writeFile(props.path, yamlContent);
|
|
318
|
-
|
|
319
|
-
// Get file stats for timestamps
|
|
320
|
-
const stats = await fs.stat(props.path);
|
|
321
|
-
|
|
322
282
|
// Return the resource
|
|
323
283
|
return this({
|
|
324
284
|
...props,
|
|
325
285
|
schema: props.schema,
|
|
326
286
|
content: yamlContent,
|
|
327
287
|
yaml: yamlObject,
|
|
328
|
-
createdAt:
|
|
329
|
-
updatedAt:
|
|
288
|
+
createdAt: Date.now(),
|
|
289
|
+
updatedAt: Date.now(),
|
|
330
290
|
});
|
|
331
291
|
});
|
|
332
292
|
|
|
@@ -338,7 +298,7 @@ export const YAMLFile = Resource("ai::YAMLFile", async function <
|
|
|
338
298
|
* @returns The extracted YAML or error message
|
|
339
299
|
*/
|
|
340
300
|
async function extractYAMLContent(
|
|
341
|
-
text: string
|
|
301
|
+
text: string
|
|
342
302
|
): Promise<{ content: string; error?: string }> {
|
|
343
303
|
// Check for yaml or yml fence blocks
|
|
344
304
|
const yamlCodeRegex = /```(yaml|yml)\s*([\s\S]*?)```/g;
|
package/src/alchemy.ts
CHANGED
|
@@ -40,6 +40,12 @@ export interface Alchemy {
|
|
|
40
40
|
scope: typeof scope;
|
|
41
41
|
run: typeof run;
|
|
42
42
|
destroy: typeof destroy;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get an environment variable and error if it's not set.
|
|
46
|
+
*/
|
|
47
|
+
env: typeof env;
|
|
48
|
+
|
|
43
49
|
/**
|
|
44
50
|
* Creates an encrypted secret that can be safely stored in state files.
|
|
45
51
|
* Requires a password to be set either globally in the application options
|
|
@@ -88,12 +94,18 @@ function _alchemy(
|
|
|
88
94
|
| [appName: string, options?: Omit<AlchemyOptions, "appName">]
|
|
89
95
|
): any {
|
|
90
96
|
if (typeof args[0] === "string") {
|
|
91
|
-
const [appName, options] = args;
|
|
92
|
-
|
|
97
|
+
const [appName, options] = args as [string, AlchemyOptions?];
|
|
98
|
+
const root = scope(undefined, {
|
|
93
99
|
...options,
|
|
94
100
|
appName,
|
|
95
101
|
stage: options?.stage,
|
|
96
102
|
});
|
|
103
|
+
if (options?.phase === "destroy") {
|
|
104
|
+
return destroy(root).finally(() => {
|
|
105
|
+
process.exit(0);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return root;
|
|
97
109
|
} else {
|
|
98
110
|
const [template, ...values] = args;
|
|
99
111
|
const [, secondLine] = template[0].split("\n");
|
|
@@ -111,6 +123,16 @@ function _alchemy(
|
|
|
111
123
|
values.map(async function resolve(value): Promise<string> {
|
|
112
124
|
if (typeof value === "string") {
|
|
113
125
|
return indent + value;
|
|
126
|
+
} else if (value === null) {
|
|
127
|
+
return "null";
|
|
128
|
+
} else if (value === undefined) {
|
|
129
|
+
return "undefined";
|
|
130
|
+
} else if (
|
|
131
|
+
typeof value === "number" ||
|
|
132
|
+
typeof value === "boolean" ||
|
|
133
|
+
typeof value === "bigint"
|
|
134
|
+
) {
|
|
135
|
+
return value.toString();
|
|
114
136
|
} else if (value instanceof Promise) {
|
|
115
137
|
return resolve(await value);
|
|
116
138
|
} else if (isFileRef(value)) {
|
|
@@ -128,14 +150,34 @@ function _alchemy(
|
|
|
128
150
|
} else if (Array.isArray(value)) {
|
|
129
151
|
return (
|
|
130
152
|
await Promise.all(
|
|
131
|
-
value.map(async (value, i) => `${i}. ${await resolve(value)}`)
|
|
153
|
+
value.map(async (value, i) => `${i}. ${await resolve(value)}`)
|
|
154
|
+
)
|
|
155
|
+
).join("\n");
|
|
156
|
+
} else if (
|
|
157
|
+
typeof value === "object" &&
|
|
158
|
+
typeof value.path === "string"
|
|
159
|
+
) {
|
|
160
|
+
if (typeof value.content === "string") {
|
|
161
|
+
appendices[value.path] = value.content;
|
|
162
|
+
return `[${path.basename(value.path)}](${value.path})`;
|
|
163
|
+
} else {
|
|
164
|
+
appendices[value.path] = await fs.readFile(value.path, "utf-8");
|
|
165
|
+
return `[${path.basename(value.path)}](${value.path})`;
|
|
166
|
+
}
|
|
167
|
+
} else if (typeof value === "object") {
|
|
168
|
+
return (
|
|
169
|
+
await Promise.all(
|
|
170
|
+
Object.entries(value).map(async ([key, value]) => {
|
|
171
|
+
return `* ${key}: ${await resolve(value)}`;
|
|
172
|
+
})
|
|
132
173
|
)
|
|
133
174
|
).join("\n");
|
|
134
175
|
} else {
|
|
135
176
|
// TODO: support other types
|
|
136
|
-
|
|
177
|
+
console.log(value);
|
|
178
|
+
throw new Error(`Unsupported value type: ${value}`);
|
|
137
179
|
}
|
|
138
|
-
})
|
|
180
|
+
})
|
|
139
181
|
);
|
|
140
182
|
|
|
141
183
|
// Construct the string template by joining template parts with interpolated values
|
|
@@ -144,12 +186,12 @@ function _alchemy(
|
|
|
144
186
|
part
|
|
145
187
|
.split("\n")
|
|
146
188
|
.map((line) =>
|
|
147
|
-
line.startsWith(indent) ? line.slice(indent.length) : line
|
|
189
|
+
line.startsWith(indent) ? line.slice(indent.length) : line
|
|
148
190
|
)
|
|
149
|
-
.join("\n")
|
|
191
|
+
.join("\n")
|
|
150
192
|
)
|
|
151
193
|
.flatMap((part, i) =>
|
|
152
|
-
i < stringValues.length ? [part, stringValues[i] ?? ""] : [part]
|
|
194
|
+
i < stringValues.length ? [part, stringValues[i] ?? ""] : [part]
|
|
153
195
|
)
|
|
154
196
|
.join("")
|
|
155
197
|
.split("\n");
|
|
@@ -178,6 +220,7 @@ _alchemy.destroy = destroy;
|
|
|
178
220
|
_alchemy.run = run;
|
|
179
221
|
_alchemy.scope = scope;
|
|
180
222
|
_alchemy.secret = secret;
|
|
223
|
+
_alchemy.env = env;
|
|
181
224
|
|
|
182
225
|
export interface AlchemyOptions {
|
|
183
226
|
/**
|
|
@@ -240,7 +283,7 @@ export interface AlchemyOptions {
|
|
|
240
283
|
*/
|
|
241
284
|
function scope(
|
|
242
285
|
id: string | undefined,
|
|
243
|
-
options?: AlchemyOptions
|
|
286
|
+
options?: AlchemyOptions
|
|
244
287
|
// TODO: maybe we want to allow using _ = await alchemy.scope(import.meta)
|
|
245
288
|
// | [meta: ImportMeta]
|
|
246
289
|
): Scope {
|
|
@@ -300,3 +343,25 @@ async function run<T>(
|
|
|
300
343
|
await scope.finalize();
|
|
301
344
|
}
|
|
302
345
|
}
|
|
346
|
+
|
|
347
|
+
export async function env<T = string>(
|
|
348
|
+
name: string,
|
|
349
|
+
value?: T | undefined,
|
|
350
|
+
error?: string
|
|
351
|
+
): Promise<T> {
|
|
352
|
+
if (value !== undefined) {
|
|
353
|
+
return value;
|
|
354
|
+
} else if (typeof process !== undefined) {
|
|
355
|
+
// we are in a node environment
|
|
356
|
+
return process.env[name]! as T;
|
|
357
|
+
} else {
|
|
358
|
+
// we are in a browser environment
|
|
359
|
+
try {
|
|
360
|
+
const { env } = await import("cloudflare:workers");
|
|
361
|
+
if (name in env) {
|
|
362
|
+
return env[name as keyof typeof env];
|
|
363
|
+
}
|
|
364
|
+
} catch (error) {}
|
|
365
|
+
}
|
|
366
|
+
throw new Error(error ?? `Environment variable ${name} is not set`);
|
|
367
|
+
}
|
package/src/apply.ts
CHANGED
|
@@ -7,9 +7,8 @@ import {
|
|
|
7
7
|
type Resource,
|
|
8
8
|
type ResourceProps,
|
|
9
9
|
} from "./resource";
|
|
10
|
-
import {
|
|
10
|
+
import { serialize } from "./serde";
|
|
11
11
|
import type { State } from "./state";
|
|
12
|
-
import { serialize } from "./util/serde";
|
|
13
12
|
|
|
14
13
|
export interface ApplyOptions {
|
|
15
14
|
quiet?: boolean;
|
|
@@ -19,7 +18,7 @@ export interface ApplyOptions {
|
|
|
19
18
|
export async function apply<Out extends Resource>(
|
|
20
19
|
resource: PendingResource<Out>,
|
|
21
20
|
props: ResourceProps | undefined,
|
|
22
|
-
options?: ApplyOptions
|
|
21
|
+
options?: ApplyOptions
|
|
23
22
|
): Promise<Awaited<Out>> {
|
|
24
23
|
const scope = resource.Scope;
|
|
25
24
|
try {
|
|
@@ -56,29 +55,30 @@ export async function apply<Out extends Resource>(
|
|
|
56
55
|
|
|
57
56
|
// Skip update if inputs haven't changed and resource is in a stable state
|
|
58
57
|
if (state.status === "created" || state.status === "updated") {
|
|
59
|
-
const oldProps =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
69
|
-
const newProps = JSON.stringify(
|
|
70
|
-
await serialize(scope, props, {
|
|
71
|
-
encrypt: false,
|
|
72
|
-
}),
|
|
73
|
-
null,
|
|
74
|
-
2,
|
|
75
|
-
);
|
|
76
|
-
if (oldProps === newProps && alwaysUpdate !== true) {
|
|
58
|
+
const oldProps = await serialize(scope, state.props, {
|
|
59
|
+
encrypt: false,
|
|
60
|
+
});
|
|
61
|
+
const newProps = await serialize(scope, props, {
|
|
62
|
+
encrypt: false,
|
|
63
|
+
});
|
|
64
|
+
if (
|
|
65
|
+
JSON.stringify(oldProps) === JSON.stringify(newProps) &&
|
|
66
|
+
alwaysUpdate !== true
|
|
67
|
+
) {
|
|
77
68
|
if (!quiet) {
|
|
78
|
-
console.log(`Skip: "${resource.FQN}" (no changes)`);
|
|
69
|
+
// console.log(`Skip: "${resource.FQN}" (no changes)`);
|
|
79
70
|
}
|
|
80
71
|
return state.output as Awaited<Out>;
|
|
81
72
|
}
|
|
73
|
+
|
|
74
|
+
const diff = await import("diff");
|
|
75
|
+
const yaml = await import("yaml");
|
|
76
|
+
const old = yaml.stringify(oldProps);
|
|
77
|
+
const news = yaml.stringify(newProps);
|
|
78
|
+
const patch = diff.createPatch(resource.ID, old, news);
|
|
79
|
+
// console.log(patch);
|
|
80
|
+
// if (resource.ID === "what-is-alchemy") {
|
|
81
|
+
// }
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const phase = state.status === "creating" ? "create" : "update";
|
|
@@ -88,7 +88,7 @@ export async function apply<Out extends Resource>(
|
|
|
88
88
|
|
|
89
89
|
if (!quiet) {
|
|
90
90
|
console.log(
|
|
91
|
-
`${phase === "create" ? "Create" : "Update"}: "${resource.FQN}"
|
|
91
|
+
`${phase === "create" ? "Create" : "Update"}: "${resource.FQN}"`
|
|
92
92
|
);
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -108,7 +108,7 @@ export async function apply<Out extends Resource>(
|
|
|
108
108
|
replace: () => {
|
|
109
109
|
if (isReplaced) {
|
|
110
110
|
console.warn(
|
|
111
|
-
`Resource ${resource.Kind} ${resource.FQN} is already marked as REPLACE
|
|
111
|
+
`Resource ${resource.Kind} ${resource.FQN} is already marked as REPLACE`
|
|
112
112
|
);
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
@@ -117,11 +117,11 @@ export async function apply<Out extends Resource>(
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
const output = await alchemy.run(resource.ID, async () =>
|
|
120
|
-
provider.handler.bind(ctx)(resource.ID, props)
|
|
120
|
+
provider.handler.bind(ctx)(resource.ID, props)
|
|
121
121
|
);
|
|
122
122
|
if (!quiet) {
|
|
123
123
|
console.log(
|
|
124
|
-
`${phase === "create" ? "Created" : "Updated"}: "${resource.FQN}"
|
|
124
|
+
`${phase === "create" ? "Created" : "Updated"}: "${resource.FQN}"`
|
|
125
125
|
);
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -141,6 +141,7 @@ export async function apply<Out extends Resource>(
|
|
|
141
141
|
// }
|
|
142
142
|
return output as any;
|
|
143
143
|
} catch (error) {
|
|
144
|
+
console.error(new Error().stack);
|
|
144
145
|
scope.fail();
|
|
145
146
|
throw error;
|
|
146
147
|
}
|
package/src/aws/account-id.ts
CHANGED
|
@@ -2,10 +2,14 @@ import { GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
|
|
|
2
2
|
|
|
3
3
|
const sts = new STSClient({});
|
|
4
4
|
|
|
5
|
+
export type AccountId = string & {
|
|
6
|
+
readonly __brand: "AccountId";
|
|
7
|
+
};
|
|
8
|
+
|
|
5
9
|
/**
|
|
6
10
|
* Helper to get the current AWS account ID
|
|
7
11
|
*/
|
|
8
|
-
export async function
|
|
12
|
+
export async function AccountId(): Promise<AccountId> {
|
|
9
13
|
const identity = await sts.send(new GetCallerIdentityCommand({}));
|
|
10
|
-
return identity.Account
|
|
14
|
+
return identity.Account! as AccountId;
|
|
11
15
|
}
|