alchemy 0.4.0 → 0.5.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 +3 -463
- package/lib/ai/data.d.ts +5 -0
- package/lib/ai/data.js +3 -0
- package/lib/alchemy.d.ts +3 -3
- package/lib/alchemy.js +82 -85
- package/lib/cloudflare/assets.d.ts +74 -0
- package/lib/cloudflare/assets.js +75 -0
- package/lib/cloudflare/bindings.d.ts +6 -1
- package/lib/cloudflare/bindings.js +6 -0
- package/lib/cloudflare/bound.d.ts +2 -1
- package/lib/cloudflare/custom-domain.d.ts +1 -15
- package/lib/cloudflare/custom-domain.js +1 -15
- package/lib/cloudflare/index.d.ts +1 -1
- package/lib/cloudflare/index.js +1 -1
- package/lib/cloudflare/r2-rest-state-store.js +52 -22
- package/lib/cloudflare/worker.d.ts +18 -0
- package/lib/cloudflare/worker.js +153 -4
- package/lib/internal/docs/providers.js +2 -1
- package/lib/os/exec.d.ts +103 -0
- package/lib/os/exec.js +103 -0
- package/lib/os/index.d.ts +1 -0
- package/lib/os/index.js +1 -0
- package/lib/secret.d.ts +2 -2
- package/lib/secret.js +2 -2
- package/lib/web/vitepress/vitepress.js +1 -1
- package/package.json +2 -1
- package/src/ai/data.ts +10 -0
- package/src/alchemy.ts +97 -100
- package/src/cloudflare/assets.ts +158 -0
- package/src/cloudflare/bindings.ts +11 -2
- package/src/cloudflare/bound.ts +4 -1
- package/src/cloudflare/custom-domain.ts +1 -15
- package/src/cloudflare/index.ts +1 -1
- package/src/cloudflare/r2-rest-state-store.ts +78 -34
- package/src/cloudflare/worker.ts +261 -6
- package/src/internal/docs/providers.ts +2 -1
- package/src/os/exec.ts +190 -0
- package/src/os/index.ts +1 -0
- package/src/secret.ts +2 -2
- package/src/web/vitepress/vitepress.ts +1 -1
- package/lib/cloudflare/generate-asset-manifest.d.ts +0 -2
- package/lib/cloudflare/generate-asset-manifest.js +0 -68
- package/lib/cloudflare/static-site-router.d.ts +0 -18
- package/lib/cloudflare/static-site-router.js +0 -115
- package/lib/cloudflare/static-site.d.ts +0 -211
- package/lib/cloudflare/static-site.js +0 -213
- package/lib/cloudflare/upload-asset-manifest.d.ts +0 -11
- package/lib/cloudflare/upload-asset-manifest.js +0 -56
- package/src/cloudflare/generate-asset-manifest.ts +0 -93
- package/src/cloudflare/static-site-router.ts +0 -157
- package/src/cloudflare/static-site.ts +0 -416
- package/src/cloudflare/upload-asset-manifest.ts +0 -85
- package/src/web/vitepress/index.md +0 -75
package/lib/cloudflare/worker.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import * as crypto from "crypto";
|
|
1
2
|
import * as fs from "fs/promises";
|
|
2
3
|
import { Bundle } from "../esbuild/bundle";
|
|
3
4
|
import { Resource } from "../resource";
|
|
4
5
|
import { isSecret } from "../secret";
|
|
6
|
+
import { getContentType } from "../util/content-type";
|
|
5
7
|
import { withExponentialBackoff } from "../util/retry";
|
|
6
8
|
import { slugify } from "../util/slugify";
|
|
7
9
|
import { createCloudflareApi } from "./api";
|
|
8
|
-
import { isDurableObjectNamespace, } from "./bindings";
|
|
10
|
+
import { isAssets, isDurableObjectNamespace, } from "./bindings";
|
|
9
11
|
import { isKVNamespace } from "./kv-namespace";
|
|
10
12
|
/**
|
|
11
13
|
* A Cloudflare Worker is a serverless function that can be deployed to the Cloudflare network.
|
|
@@ -68,6 +70,20 @@ import { isKVNamespace } from "./kv-namespace";
|
|
|
68
70
|
* }
|
|
69
71
|
* });
|
|
70
72
|
*
|
|
73
|
+
* @example
|
|
74
|
+
* // Create a worker with static assets:
|
|
75
|
+
* const staticAssets = await Assets("static", {
|
|
76
|
+
* path: "./src/assets"
|
|
77
|
+
* });
|
|
78
|
+
*
|
|
79
|
+
* const frontendWorker = await Worker("frontend", {
|
|
80
|
+
* name: "frontend-worker",
|
|
81
|
+
* entrypoint: "./src/worker.ts",
|
|
82
|
+
* bindings: {
|
|
83
|
+
* ASSETS: staticAssets
|
|
84
|
+
* }
|
|
85
|
+
* });
|
|
86
|
+
*
|
|
71
87
|
* @see https://developers.cloudflare.com/workers/
|
|
72
88
|
*/
|
|
73
89
|
export const Worker = Resource("cloudflare::Worker", {
|
|
@@ -86,12 +102,33 @@ export const Worker = Resource("cloudflare::Worker", {
|
|
|
86
102
|
return this.destroy();
|
|
87
103
|
}
|
|
88
104
|
else if (this.phase === "create") {
|
|
89
|
-
|
|
105
|
+
if (!props.adopt) {
|
|
106
|
+
await assertWorkerDoesNotExist(this, api, workerName);
|
|
107
|
+
}
|
|
90
108
|
}
|
|
91
109
|
const oldBindings = await this.get("bindings");
|
|
92
|
-
const scriptMetadata = await prepareWorkerMetadata(this, oldBindings, props);
|
|
93
110
|
// Get the script content - either from props.script, or by bundling
|
|
94
111
|
const scriptContent = props.script ?? (await bundleWorkerScript(props));
|
|
112
|
+
// Find any assets bindings
|
|
113
|
+
const assetsBindings = [];
|
|
114
|
+
if (props.bindings) {
|
|
115
|
+
for (const [bindingName, binding] of Object.entries(props.bindings)) {
|
|
116
|
+
if (isAssets(binding)) {
|
|
117
|
+
assetsBindings.push({ name: bindingName, assets: binding });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Upload any assets and get completion tokens
|
|
122
|
+
let assetUploadResult;
|
|
123
|
+
if (assetsBindings.length > 0) {
|
|
124
|
+
// We'll use the first asset binding for now
|
|
125
|
+
// In the future, we might want to support multiple asset bindings
|
|
126
|
+
const assetBinding = assetsBindings[0];
|
|
127
|
+
// Upload the assets and get the completion token
|
|
128
|
+
assetUploadResult = await uploadAssets(api, workerName, assetBinding.assets);
|
|
129
|
+
}
|
|
130
|
+
// Prepare metadata with bindings
|
|
131
|
+
const scriptMetadata = await prepareWorkerMetadata(this, oldBindings, props, assetUploadResult);
|
|
95
132
|
// Upload the worker script
|
|
96
133
|
await putWorker(api, workerName, scriptContent, scriptMetadata);
|
|
97
134
|
// TODO: it is less than ideal that this can fail, resulting in state problem
|
|
@@ -206,7 +243,7 @@ class NotFoundError extends Error {
|
|
|
206
243
|
this.name = "NotFoundError";
|
|
207
244
|
}
|
|
208
245
|
}
|
|
209
|
-
async function prepareWorkerMetadata(ctx, oldBindings, props) {
|
|
246
|
+
async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult) {
|
|
210
247
|
// Prepare metadata with bindings
|
|
211
248
|
const meta = {
|
|
212
249
|
bindings: [],
|
|
@@ -223,6 +260,15 @@ async function prepareWorkerMetadata(ctx, oldBindings, props) {
|
|
|
223
260
|
new_sqlite_classes: props.migrations?.new_sqlite_classes ?? [],
|
|
224
261
|
},
|
|
225
262
|
};
|
|
263
|
+
// If we have asset upload results, add them to the metadata
|
|
264
|
+
if (assetUploadResult) {
|
|
265
|
+
meta.assets = {
|
|
266
|
+
jwt: assetUploadResult.completionToken,
|
|
267
|
+
};
|
|
268
|
+
if (assetUploadResult.assetConfig) {
|
|
269
|
+
meta.assets.config = assetUploadResult.assetConfig;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
226
272
|
const bindings = (props.bindings ?? {});
|
|
227
273
|
// Convert bindings to the format expected by the API
|
|
228
274
|
for (const [bindingName, binding] of Object.entries(bindings)) {
|
|
@@ -284,6 +330,12 @@ async function prepareWorkerMetadata(ctx, oldBindings, props) {
|
|
|
284
330
|
bucket_name: binding.name,
|
|
285
331
|
});
|
|
286
332
|
}
|
|
333
|
+
else if (isAssets(binding)) {
|
|
334
|
+
meta.bindings.push({
|
|
335
|
+
type: "assets",
|
|
336
|
+
name: bindingName,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
287
339
|
else if (isSecret(binding)) {
|
|
288
340
|
meta.bindings.push({
|
|
289
341
|
type: "secret_text",
|
|
@@ -468,3 +520,100 @@ async function getWorkerBindings(api, workerName, environment = "production") {
|
|
|
468
520
|
const data = await response.json();
|
|
469
521
|
return data.result;
|
|
470
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* Uploads assets to Cloudflare and returns a completion token
|
|
525
|
+
*
|
|
526
|
+
* @param api CloudflareApi instance
|
|
527
|
+
* @param workerName Name of the worker
|
|
528
|
+
* @param assets Assets resource containing files to upload
|
|
529
|
+
* @returns Completion token for the assets upload
|
|
530
|
+
*/
|
|
531
|
+
async function uploadAssets(api, workerName, assets) {
|
|
532
|
+
// Generate the file manifest
|
|
533
|
+
const fileMetadata = {};
|
|
534
|
+
// Process each file in the assets
|
|
535
|
+
for (const file of assets.files) {
|
|
536
|
+
const { hash, size } = await calculateFileMetadata(file.filePath);
|
|
537
|
+
// Use the relative path as the key, ensuring it starts with a slash
|
|
538
|
+
const key = file.path.startsWith("/") ? file.path : `/${file.path}`;
|
|
539
|
+
fileMetadata[key] = { hash, size };
|
|
540
|
+
}
|
|
541
|
+
// Start the upload session
|
|
542
|
+
const uploadSessionUrl = `/accounts/${api.accountId}/workers/scripts/${workerName}/assets-upload-session`;
|
|
543
|
+
const uploadSessionResponse = await api.post(uploadSessionUrl, JSON.stringify({ manifest: fileMetadata }), {
|
|
544
|
+
headers: { "Content-Type": "application/json" },
|
|
545
|
+
});
|
|
546
|
+
if (!uploadSessionResponse.ok) {
|
|
547
|
+
throw new Error(`Failed to start assets upload session: ${uploadSessionResponse.status} ${uploadSessionResponse.statusText}`);
|
|
548
|
+
}
|
|
549
|
+
const sessionData = (await uploadSessionResponse.json());
|
|
550
|
+
// If there are no buckets, assets are already uploaded or empty
|
|
551
|
+
if (!sessionData.result.buckets || sessionData.result.buckets.length === 0) {
|
|
552
|
+
return { completionToken: sessionData.result.jwt };
|
|
553
|
+
}
|
|
554
|
+
// Upload the files in batches as specified by the API
|
|
555
|
+
let completionToken = sessionData.result.jwt;
|
|
556
|
+
const buckets = sessionData.result.buckets;
|
|
557
|
+
// Process each bucket of files
|
|
558
|
+
for (const bucket of buckets) {
|
|
559
|
+
const formData = new FormData();
|
|
560
|
+
// Add each file in the bucket to the form
|
|
561
|
+
for (const fileHash of bucket) {
|
|
562
|
+
// Find the file with this hash
|
|
563
|
+
const file = assets.files.find((f) => {
|
|
564
|
+
const filePath = f.path.startsWith("/") ? f.path : `/${f.path}`;
|
|
565
|
+
return fileMetadata[filePath]?.hash === fileHash;
|
|
566
|
+
});
|
|
567
|
+
if (!file) {
|
|
568
|
+
throw new Error(`Could not find file with hash ${fileHash}`);
|
|
569
|
+
}
|
|
570
|
+
// Read the file content
|
|
571
|
+
const fileContent = await fs.readFile(file.filePath);
|
|
572
|
+
// Convert to base64 as required by the API when using base64=true
|
|
573
|
+
const base64Content = fileContent.toString("base64");
|
|
574
|
+
// Add the file to the form with the hash as the key and set the correct content type
|
|
575
|
+
const blob = new Blob([base64Content], {
|
|
576
|
+
type: getContentType(file.filePath),
|
|
577
|
+
});
|
|
578
|
+
formData.append(fileHash, blob, fileHash);
|
|
579
|
+
}
|
|
580
|
+
// Upload this batch of files
|
|
581
|
+
const uploadResponse = await api.post(`/accounts/${api.accountId}/workers/assets/upload?base64=true`, formData, {
|
|
582
|
+
headers: {
|
|
583
|
+
Authorization: `Bearer ${completionToken}`,
|
|
584
|
+
"Content-Type": "multipart/form-data",
|
|
585
|
+
},
|
|
586
|
+
});
|
|
587
|
+
if (!uploadResponse.ok) {
|
|
588
|
+
throw new Error(`Failed to upload asset files: ${uploadResponse.status} ${uploadResponse.statusText}`);
|
|
589
|
+
}
|
|
590
|
+
const uploadData = (await uploadResponse.json());
|
|
591
|
+
// Update the completion token for the next batch
|
|
592
|
+
if (uploadData.result.jwt) {
|
|
593
|
+
completionToken = uploadData.result.jwt;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
// Return the final completion token
|
|
597
|
+
return {
|
|
598
|
+
completionToken,
|
|
599
|
+
assetConfig: {
|
|
600
|
+
html_handling: "auto-trailing-slash",
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Calculate the SHA-256 hash and size of a file
|
|
606
|
+
*
|
|
607
|
+
* @param filePath Path to the file
|
|
608
|
+
* @returns Hash (first 32 chars of SHA-256) and size of the file
|
|
609
|
+
*/
|
|
610
|
+
async function calculateFileMetadata(filePath) {
|
|
611
|
+
const hash = crypto.createHash("sha256");
|
|
612
|
+
const fileContent = await fs.readFile(filePath);
|
|
613
|
+
hash.update(fileContent);
|
|
614
|
+
const fileHash = hash.digest("hex").substring(0, 32); // First 32 chars of hash
|
|
615
|
+
return {
|
|
616
|
+
hash: fileHash,
|
|
617
|
+
size: fileContent.length,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
@@ -55,6 +55,7 @@ async function generateProviderDocs({ provider, outDir, parallel, }) {
|
|
|
55
55
|
reasoningEffort: "high",
|
|
56
56
|
},
|
|
57
57
|
},
|
|
58
|
+
freeze: true,
|
|
58
59
|
temperature: 0.1,
|
|
59
60
|
schema: type({
|
|
60
61
|
groups: type({
|
|
@@ -81,7 +82,7 @@ async function generateProviderDocs({ provider, outDir, parallel, }) {
|
|
|
81
82
|
A file is considered a "Utility" if it contains utility functions that are not resources or clients.
|
|
82
83
|
A file is considered a "Types" if it contains just type definitions and maybe helpers around working with those types.
|
|
83
84
|
|
|
84
|
-
The title should be simply the name of the resource's const in code (with spaces added in between each word), e.g. "Bucket" or "Function", except with spaces, e.g. "
|
|
85
|
+
The title should be simply the name of the resource's const in code (with spaces added in between each word), e.g. "Bucket" or "Function", except with spaces, e.g. "StaticSite" for "const StaticSite". Maintain all other casing.
|
|
85
86
|
|
|
86
87
|
// "Resource Name"
|
|
87
88
|
const ResourceName = Resource(...)
|
package/lib/os/exec.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Context } from "../context";
|
|
2
|
+
import { Resource } from "../resource";
|
|
3
|
+
/**
|
|
4
|
+
* Properties for executing a shell command
|
|
5
|
+
*/
|
|
6
|
+
export interface ExecProps {
|
|
7
|
+
/**
|
|
8
|
+
* The command to execute (including any arguments)
|
|
9
|
+
*/
|
|
10
|
+
command: string;
|
|
11
|
+
/**
|
|
12
|
+
* Whether to memoize the command (only re-run if the command changes)
|
|
13
|
+
*
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
16
|
+
memoize?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Working directory for the command
|
|
19
|
+
*/
|
|
20
|
+
cwd?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Environment variables to set
|
|
23
|
+
*/
|
|
24
|
+
env?: Record<string, string>;
|
|
25
|
+
/**
|
|
26
|
+
* Maximum buffer size for stdout and stderr (in bytes)
|
|
27
|
+
* @default 1024 * 1024 (1MB)
|
|
28
|
+
*/
|
|
29
|
+
maxBuffer?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Whether to throw an error if the command exits with a non-zero status
|
|
32
|
+
*/
|
|
33
|
+
throwOnError?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Output returned after command execution
|
|
37
|
+
*/
|
|
38
|
+
export interface Exec extends Resource<"os::Exec">, ExecProps {
|
|
39
|
+
/**
|
|
40
|
+
* Unique identifier for this execution
|
|
41
|
+
*/
|
|
42
|
+
id: string;
|
|
43
|
+
/**
|
|
44
|
+
* Exit code of the command
|
|
45
|
+
*/
|
|
46
|
+
exitCode: number;
|
|
47
|
+
/**
|
|
48
|
+
* Standard output from the command
|
|
49
|
+
*/
|
|
50
|
+
stdout: string;
|
|
51
|
+
/**
|
|
52
|
+
* Standard error from the command
|
|
53
|
+
*/
|
|
54
|
+
stderr: string;
|
|
55
|
+
/**
|
|
56
|
+
* Time at which the command was executed
|
|
57
|
+
*/
|
|
58
|
+
executedAt: number;
|
|
59
|
+
/**
|
|
60
|
+
* Whether the command has completed execution
|
|
61
|
+
*/
|
|
62
|
+
completed: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Execute a shell command
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* // Run a simple command
|
|
69
|
+
* const result = await Exec("list-files", {
|
|
70
|
+
* command: "ls -la"
|
|
71
|
+
* });
|
|
72
|
+
*
|
|
73
|
+
* console.log(result.stdout);
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // Run a command in a specific directory with custom environment
|
|
77
|
+
* const build = await Exec("build-project", {
|
|
78
|
+
* command: "npm run build",
|
|
79
|
+
* cwd: "./my-project",
|
|
80
|
+
* env: { NODE_ENV: "production" }
|
|
81
|
+
* });
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* // Run a command with a larger buffer for output
|
|
85
|
+
* const logs = await Exec("get-logs", {
|
|
86
|
+
* command: "cat large-log-file.log",
|
|
87
|
+
* maxBuffer: 10 * 1024 * 1024 // 10MB
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* // Run a memoized command that only re-executes when the command changes
|
|
92
|
+
* const memoizedCmd = await Exec("status-check", {
|
|
93
|
+
* command: "git status",
|
|
94
|
+
* memoize: true
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* // This won't actually run the command again if nothing has changed
|
|
98
|
+
* await Exec("status-check", {
|
|
99
|
+
* command: "git status",
|
|
100
|
+
* memoize: true
|
|
101
|
+
* });
|
|
102
|
+
*/
|
|
103
|
+
export declare const Exec: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<Exec>, id: string, props: ExecProps) => Promise<Exec>);
|
package/lib/os/exec.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { Resource } from "../resource";
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
/**
|
|
6
|
+
* Execute a shell command
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Run a simple command
|
|
10
|
+
* const result = await Exec("list-files", {
|
|
11
|
+
* command: "ls -la"
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* console.log(result.stdout);
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // Run a command in a specific directory with custom environment
|
|
18
|
+
* const build = await Exec("build-project", {
|
|
19
|
+
* command: "npm run build",
|
|
20
|
+
* cwd: "./my-project",
|
|
21
|
+
* env: { NODE_ENV: "production" }
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // Run a command with a larger buffer for output
|
|
26
|
+
* const logs = await Exec("get-logs", {
|
|
27
|
+
* command: "cat large-log-file.log",
|
|
28
|
+
* maxBuffer: 10 * 1024 * 1024 // 10MB
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // Run a memoized command that only re-executes when the command changes
|
|
33
|
+
* const memoizedCmd = await Exec("status-check", {
|
|
34
|
+
* command: "git status",
|
|
35
|
+
* memoize: true
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* // This won't actually run the command again if nothing has changed
|
|
39
|
+
* await Exec("status-check", {
|
|
40
|
+
* command: "git status",
|
|
41
|
+
* memoize: true
|
|
42
|
+
* });
|
|
43
|
+
*/
|
|
44
|
+
export const Exec = Resource("os::Exec", {
|
|
45
|
+
alwaysUpdate: true,
|
|
46
|
+
}, async function (id, props) {
|
|
47
|
+
if (this.phase === "delete") {
|
|
48
|
+
// Nothing to actually delete for an exec command
|
|
49
|
+
return this.destroy();
|
|
50
|
+
}
|
|
51
|
+
else if (this.phase === "update" &&
|
|
52
|
+
props.memoize &&
|
|
53
|
+
this.output?.command === props.command) {
|
|
54
|
+
// If memoize is enabled and the command hasn't changed, return the existing output
|
|
55
|
+
return this.output;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
// Default values
|
|
59
|
+
let stdout = "";
|
|
60
|
+
let stderr = "";
|
|
61
|
+
let exitCode = 0;
|
|
62
|
+
try {
|
|
63
|
+
console.log(props.command);
|
|
64
|
+
// Execute the command
|
|
65
|
+
const result = await execAsync(props.command, {
|
|
66
|
+
cwd: props.cwd || process.cwd(),
|
|
67
|
+
env: { ...process.env, ...props.env },
|
|
68
|
+
maxBuffer: props.maxBuffer || 1024 * 1024, // Default 1MB
|
|
69
|
+
});
|
|
70
|
+
stdout = result.stdout;
|
|
71
|
+
console.log(stdout);
|
|
72
|
+
stderr = result.stderr;
|
|
73
|
+
exitCode = 0; // Success
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.log("error", error);
|
|
77
|
+
if (props.throwOnError) {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
// If not throwing, capture the error information
|
|
81
|
+
exitCode = error.code || 1;
|
|
82
|
+
stdout = error.stdout || "";
|
|
83
|
+
stderr = error.stderr || String(error);
|
|
84
|
+
console.log(stdout);
|
|
85
|
+
console.error(stderr);
|
|
86
|
+
}
|
|
87
|
+
// Return the execution result
|
|
88
|
+
return this({
|
|
89
|
+
id,
|
|
90
|
+
command: props.command,
|
|
91
|
+
cwd: props.cwd,
|
|
92
|
+
env: props.env,
|
|
93
|
+
maxBuffer: props.maxBuffer,
|
|
94
|
+
throwOnError: props.throwOnError,
|
|
95
|
+
memoize: props.memoize,
|
|
96
|
+
exitCode,
|
|
97
|
+
stdout,
|
|
98
|
+
stderr,
|
|
99
|
+
executedAt: Date.now(),
|
|
100
|
+
completed: true,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./exec";
|
package/lib/os/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./exec";
|
package/lib/secret.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 1. Globally when initializing the alchemy application:
|
|
7
7
|
* ```ts
|
|
8
|
-
* const app = alchemy("my-app", {
|
|
8
|
+
* const app = await alchemy("my-app", {
|
|
9
9
|
* password: process.env.SECRET_PASSPHRASE
|
|
10
10
|
* });
|
|
11
11
|
* ```
|
|
@@ -49,7 +49,7 @@ export declare function isSecret(binding: any): binding is Secret;
|
|
|
49
49
|
*
|
|
50
50
|
* @example
|
|
51
51
|
* // Global password for all secrets
|
|
52
|
-
* const app = alchemy("my-app", {
|
|
52
|
+
* const app = await alchemy("my-app", {
|
|
53
53
|
* password: process.env.SECRET_PASSPHRASE
|
|
54
54
|
* });
|
|
55
55
|
*
|
package/lib/secret.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 1. Globally when initializing the alchemy application:
|
|
7
7
|
* ```ts
|
|
8
|
-
* const app = alchemy("my-app", {
|
|
8
|
+
* const app = await alchemy("my-app", {
|
|
9
9
|
* password: process.env.SECRET_PASSPHRASE
|
|
10
10
|
* });
|
|
11
11
|
* ```
|
|
@@ -54,7 +54,7 @@ export function isSecret(binding) {
|
|
|
54
54
|
*
|
|
55
55
|
* @example
|
|
56
56
|
* // Global password for all secrets
|
|
57
|
-
* const app = alchemy("my-app", {
|
|
57
|
+
* const app = await alchemy("my-app", {
|
|
58
58
|
* password: process.env.SECRET_PASSPHRASE
|
|
59
59
|
* });
|
|
60
60
|
*
|
|
@@ -76,7 +76,7 @@ export const VitepressProject = Resource("project::VitepressProject", {
|
|
|
76
76
|
// import { Folder } from "alchemy/fs";
|
|
77
77
|
// import { Document } from "alchemy/docs";
|
|
78
78
|
// import path from "path";
|
|
79
|
-
//
|
|
79
|
+
// const app = await alchemy("alchemy.run", {
|
|
80
80
|
// stage: "prod",
|
|
81
81
|
// phase: process.argv.includes("--destroy") ? "destroy" : "up",
|
|
82
82
|
// password: process.env.SECRET_PASSPHRASE,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alchemy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"module": "./lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"./esbuild": "./lib/esbuild/index.js",
|
|
19
19
|
"./fs": "./lib/fs/index.js",
|
|
20
20
|
"./github": "./lib/github/index.js",
|
|
21
|
+
"./os": "./lib/os/index.js",
|
|
21
22
|
"./shadcn": "./lib/shadcn/index.js",
|
|
22
23
|
"./stripe": "./lib/stripe/index.js",
|
|
23
24
|
"./test/bun": "./lib/test/bun.js",
|
package/src/ai/data.ts
CHANGED
|
@@ -63,6 +63,12 @@ export interface DataProps<T extends Type<any, any>> {
|
|
|
63
63
|
* Model configuration
|
|
64
64
|
*/
|
|
65
65
|
model?: ModelConfig;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether to freeze the generated object
|
|
69
|
+
* @default false
|
|
70
|
+
*/
|
|
71
|
+
freeze?: boolean;
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
/**
|
|
@@ -171,6 +177,10 @@ export const Data = Resource("ai::Object", async function <
|
|
|
171
177
|
throw new Error("Either prompt or messages must be provided");
|
|
172
178
|
}
|
|
173
179
|
|
|
180
|
+
if (this.phase === "update" && props.freeze) {
|
|
181
|
+
return this(this.output);
|
|
182
|
+
}
|
|
183
|
+
|
|
174
184
|
// Create messages array if only prompt is provided
|
|
175
185
|
const messages = props.messages || [{ role: "user", content: props.prompt! }];
|
|
176
186
|
|