alchemy 0.4.1 → 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.js +3 -1
- 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 +0 -1
- package/lib/cloudflare/index.js +0 -1
- package/lib/cloudflare/worker.d.ts +4 -0
- package/lib/cloudflare/worker.js +3 -1
- 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 +3 -0
- package/src/cloudflare/bound.ts +4 -1
- package/src/cloudflare/custom-domain.ts +1 -15
- package/src/cloudflare/index.ts +0 -1
- package/src/cloudflare/worker.ts +8 -1
- 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/alchemy.js
CHANGED
|
@@ -10,7 +10,7 @@ export const alchemy = _alchemy;
|
|
|
10
10
|
* Implementation of the alchemy function that handles both application scoping
|
|
11
11
|
* and template string interpolation.
|
|
12
12
|
*/
|
|
13
|
-
function _alchemy(...args) {
|
|
13
|
+
async function _alchemy(...args) {
|
|
14
14
|
if (typeof args[0] === "string") {
|
|
15
15
|
const [appName, options] = args;
|
|
16
16
|
const root = scope(undefined, {
|
|
@@ -19,9 +19,8 @@ function _alchemy(...args) {
|
|
|
19
19
|
stage: options?.stage,
|
|
20
20
|
});
|
|
21
21
|
if (options?.phase === "destroy") {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
22
|
+
await destroy(root);
|
|
23
|
+
return process.exit(0);
|
|
25
24
|
}
|
|
26
25
|
return root;
|
|
27
26
|
}
|
|
@@ -32,92 +31,90 @@ function _alchemy(...args) {
|
|
|
32
31
|
? secondLine.match(/^(\s*)/)?.[1]?.length || 0
|
|
33
32
|
: 0;
|
|
34
33
|
const indent = " ".repeat(leadingSpaces);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
34
|
+
const { isFileCollection, isFileRef } = await import("./fs");
|
|
35
|
+
const appendices = {};
|
|
36
|
+
const stringValues = await Promise.all(values.map(async function resolve(value) {
|
|
37
|
+
if (typeof value === "string") {
|
|
38
|
+
return indent + value;
|
|
39
|
+
}
|
|
40
|
+
else if (value === null) {
|
|
41
|
+
return "null";
|
|
42
|
+
}
|
|
43
|
+
else if (value === undefined) {
|
|
44
|
+
return "undefined";
|
|
45
|
+
}
|
|
46
|
+
else if (typeof value === "number" ||
|
|
47
|
+
typeof value === "boolean" ||
|
|
48
|
+
typeof value === "bigint") {
|
|
49
|
+
return value.toString();
|
|
50
|
+
}
|
|
51
|
+
else if (value instanceof Promise) {
|
|
52
|
+
return resolve(await value);
|
|
53
|
+
}
|
|
54
|
+
else if (isFileRef(value)) {
|
|
55
|
+
if (!(value.path in appendices)) {
|
|
56
|
+
appendices[value.path] = await fs.readFile(value.path, "utf-8");
|
|
55
57
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
return `[${path.basename(value.path)}](${value.path})`;
|
|
59
|
+
}
|
|
60
|
+
else if (isFileCollection(value)) {
|
|
61
|
+
return Object.entries(value.files)
|
|
62
|
+
.map(([filePath, content]) => {
|
|
63
|
+
appendices[filePath] = content;
|
|
64
|
+
return `[${path.basename(filePath)}](${filePath})`;
|
|
65
|
+
})
|
|
66
|
+
.join("\n\n");
|
|
67
|
+
}
|
|
68
|
+
else if (Array.isArray(value)) {
|
|
69
|
+
return (await Promise.all(value.map(async (value, i) => `${i}. ${await resolve(value)}`))).join("\n");
|
|
70
|
+
}
|
|
71
|
+
else if (typeof value === "object" &&
|
|
72
|
+
typeof value.path === "string") {
|
|
73
|
+
if (typeof value.content === "string") {
|
|
74
|
+
appendices[value.path] = value.content;
|
|
60
75
|
return `[${path.basename(value.path)}](${value.path})`;
|
|
61
76
|
}
|
|
62
|
-
else if (isFileCollection(value)) {
|
|
63
|
-
return Object.entries(value.files)
|
|
64
|
-
.map(([filePath, content]) => {
|
|
65
|
-
appendices[filePath] = content;
|
|
66
|
-
return `[${path.basename(filePath)}](${filePath})`;
|
|
67
|
-
})
|
|
68
|
-
.join("\n\n");
|
|
69
|
-
}
|
|
70
|
-
else if (Array.isArray(value)) {
|
|
71
|
-
return (await Promise.all(value.map(async (value, i) => `${i}. ${await resolve(value)}`))).join("\n");
|
|
72
|
-
}
|
|
73
|
-
else if (typeof value === "object" &&
|
|
74
|
-
typeof value.path === "string") {
|
|
75
|
-
if (typeof value.content === "string") {
|
|
76
|
-
appendices[value.path] = value.content;
|
|
77
|
-
return `[${path.basename(value.path)}](${value.path})`;
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
appendices[value.path] = await fs.readFile(value.path, "utf-8");
|
|
81
|
-
return `[${path.basename(value.path)}](${value.path})`;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
else if (typeof value === "object") {
|
|
85
|
-
return (await Promise.all(Object.entries(value).map(async ([key, value]) => {
|
|
86
|
-
return `* ${key}: ${await resolve(value)}`;
|
|
87
|
-
}))).join("\n");
|
|
88
|
-
}
|
|
89
77
|
else {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
throw new Error(`Unsupported value type: ${value}`);
|
|
78
|
+
appendices[value.path] = await fs.readFile(value.path, "utf-8");
|
|
79
|
+
return `[${path.basename(value.path)}](${value.path})`;
|
|
93
80
|
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
81
|
+
}
|
|
82
|
+
else if (typeof value === "object") {
|
|
83
|
+
return (await Promise.all(Object.entries(value).map(async ([key, value]) => {
|
|
84
|
+
return `* ${key}: ${await resolve(value)}`;
|
|
85
|
+
}))).join("\n");
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// TODO: support other types
|
|
89
|
+
console.log(value);
|
|
90
|
+
throw new Error(`Unsupported value type: ${value}`);
|
|
91
|
+
}
|
|
92
|
+
}));
|
|
93
|
+
// Construct the string template by joining template parts with interpolated values
|
|
94
|
+
const lines = template
|
|
95
|
+
.map((part) => part
|
|
96
|
+
.split("\n")
|
|
97
|
+
.map((line) => line.startsWith(indent) ? line.slice(indent.length) : line)
|
|
98
|
+
.join("\n"))
|
|
99
|
+
.flatMap((part, i) => i < stringValues.length ? [part, stringValues[i] ?? ""] : [part])
|
|
100
|
+
.join("")
|
|
101
|
+
.split("\n");
|
|
102
|
+
// Collect and sort appendices by file path
|
|
103
|
+
return [
|
|
104
|
+
// format the user prompt and trim the first line if it's empty
|
|
105
|
+
lines.length > 1 && lines[0].replaceAll(" ", "").length === 0
|
|
106
|
+
? lines.slice(1).join("\n")
|
|
107
|
+
: lines.join("\n"),
|
|
108
|
+
// sort appendices by path and include at the end of the prompt
|
|
109
|
+
Object.entries(appendices)
|
|
110
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
111
|
+
.map(([filePath, content]) => {
|
|
112
|
+
const extension = path.extname(filePath).slice(1);
|
|
113
|
+
const codeTag = extension ? extension : "";
|
|
114
|
+
return `// ${filePath}\n\`\`\`${codeTag}\n${content}\n\`\`\``;
|
|
115
|
+
})
|
|
116
|
+
.join("\n\n"),
|
|
117
|
+
].join("\n");
|
|
121
118
|
}
|
|
122
119
|
}
|
|
123
120
|
_alchemy.destroy = destroy;
|
package/lib/cloudflare/assets.js
CHANGED
|
@@ -21,7 +21,9 @@ import { getContentType } from "../util/content-type";
|
|
|
21
21
|
* }
|
|
22
22
|
* });
|
|
23
23
|
*/
|
|
24
|
-
export const Assets = Resource("cloudflare::Asset",
|
|
24
|
+
export const Assets = Resource("cloudflare::Asset", {
|
|
25
|
+
alwaysUpdate: true,
|
|
26
|
+
}, async function (id, props) {
|
|
25
27
|
if (this.phase === "delete") {
|
|
26
28
|
return this.destroy();
|
|
27
29
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Secret } from "../secret";
|
|
2
|
+
import type { Assets } from "./assets";
|
|
2
3
|
import type { Binding } from "./bindings";
|
|
3
4
|
import type { R2Bucket as _R2Bucket } from "./bucket";
|
|
4
5
|
import type { DurableObjectNamespace as _DurableObjectNamespace } from "./durable-object-namespace";
|
|
5
6
|
import type { KVNamespace as _KVNamespace } from "./kv-namespace";
|
|
6
7
|
import type { Worker as _Worker } from "./worker";
|
|
7
|
-
export type Bound<T extends Binding> = T extends _DurableObjectNamespace ? DurableObjectNamespace : T extends _KVNamespace ? KVNamespace : T extends _Worker ? Worker : T extends _R2Bucket ? R2Bucket : T extends Secret ? string :
|
|
8
|
+
export type Bound<T extends Binding> = T extends _DurableObjectNamespace ? DurableObjectNamespace : T extends _KVNamespace ? KVNamespace : T extends _Worker ? Worker : T extends _R2Bucket ? R2Bucket : T extends Secret ? string : T extends Assets ? Service : Service;
|
|
@@ -41,8 +41,7 @@ export interface CustomDomain extends Resource<"cloudflare::CustomDomain">, Cust
|
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* Configure custom domain for a Cloudflare Worker using the Cloudflare Custom Domains API
|
|
44
|
-
* This attaches a worker
|
|
45
|
-
* to a specific hostname within a zone.
|
|
44
|
+
* This attaches a worker to a specific hostname within a zone.
|
|
46
45
|
*
|
|
47
46
|
* @example
|
|
48
47
|
* // Bind a domain to a standard Cloudflare Worker
|
|
@@ -57,19 +56,6 @@ export interface CustomDomain extends Resource<"cloudflare::CustomDomain">, Cust
|
|
|
57
56
|
* workerName: apiWorker.name // Use the name from the Worker resource
|
|
58
57
|
* });
|
|
59
58
|
*
|
|
60
|
-
* @example
|
|
61
|
-
* // Bind a domain to a Cloudflare Static Site
|
|
62
|
-
* const mySite = await StaticSite("landing-page", {
|
|
63
|
-
* name: "my-landing-page-site", // This becomes the underlying worker name
|
|
64
|
-
* dir: "./dist/landing-page"
|
|
65
|
-
* });
|
|
66
|
-
*
|
|
67
|
-
* const siteDomain = await CustomDomain("site-domain-binding", {
|
|
68
|
-
* name: "www.example.com",
|
|
69
|
-
* zoneId: "YOUR_ZONE_ID", // Replace with actual Zone ID
|
|
70
|
-
* workerName: mySite.name // Use the name from the StaticSite resource
|
|
71
|
-
* });
|
|
72
|
-
*
|
|
73
59
|
* @see https://developers.cloudflare.com/api/resources/workers/subresources/domains/
|
|
74
60
|
*/
|
|
75
61
|
export declare const CustomDomain: (((this: any, id: string, props?: {}) => never) & (new (_: never) => never)) | ((this: Context<CustomDomain>, logicalId: string, props: CustomDomainProps) => Promise<CustomDomain>);
|
|
@@ -2,8 +2,7 @@ import { Resource } from "../resource";
|
|
|
2
2
|
import { CloudflareApi, createCloudflareApi, handleApiError } from "./api";
|
|
3
3
|
/**
|
|
4
4
|
* Configure custom domain for a Cloudflare Worker using the Cloudflare Custom Domains API
|
|
5
|
-
* This attaches a worker
|
|
6
|
-
* to a specific hostname within a zone.
|
|
5
|
+
* This attaches a worker to a specific hostname within a zone.
|
|
7
6
|
*
|
|
8
7
|
* @example
|
|
9
8
|
* // Bind a domain to a standard Cloudflare Worker
|
|
@@ -18,19 +17,6 @@ import { CloudflareApi, createCloudflareApi, handleApiError } from "./api";
|
|
|
18
17
|
* workerName: apiWorker.name // Use the name from the Worker resource
|
|
19
18
|
* });
|
|
20
19
|
*
|
|
21
|
-
* @example
|
|
22
|
-
* // Bind a domain to a Cloudflare Static Site
|
|
23
|
-
* const mySite = await StaticSite("landing-page", {
|
|
24
|
-
* name: "my-landing-page-site", // This becomes the underlying worker name
|
|
25
|
-
* dir: "./dist/landing-page"
|
|
26
|
-
* });
|
|
27
|
-
*
|
|
28
|
-
* const siteDomain = await CustomDomain("site-domain-binding", {
|
|
29
|
-
* name: "www.example.com",
|
|
30
|
-
* zoneId: "YOUR_ZONE_ID", // Replace with actual Zone ID
|
|
31
|
-
* workerName: mySite.name // Use the name from the StaticSite resource
|
|
32
|
-
* });
|
|
33
|
-
*
|
|
34
20
|
* @see https://developers.cloudflare.com/api/resources/workers/subresources/domains/
|
|
35
21
|
*/
|
|
36
22
|
export const CustomDomain = Resource("cloudflare::CustomDomain", async function (logicalId, // Changed param name from id to logicalId for clarity
|
|
@@ -9,7 +9,6 @@ export * from "./durable-object-namespace";
|
|
|
9
9
|
export * from "./kv-namespace";
|
|
10
10
|
export * from "./permission-groups";
|
|
11
11
|
export * from "./r2-rest-state-store";
|
|
12
|
-
export * from "./static-site";
|
|
13
12
|
export * from "./worker";
|
|
14
13
|
export * from "./wrangler.json";
|
|
15
14
|
export * from "./zone";
|
package/lib/cloudflare/index.js
CHANGED
|
@@ -9,7 +9,6 @@ export * from "./durable-object-namespace";
|
|
|
9
9
|
export * from "./kv-namespace";
|
|
10
10
|
export * from "./permission-groups";
|
|
11
11
|
export * from "./r2-rest-state-store";
|
|
12
|
-
export * from "./static-site";
|
|
13
12
|
export * from "./worker";
|
|
14
13
|
export * from "./wrangler.json";
|
|
15
14
|
export * from "./zone";
|
|
@@ -74,6 +74,10 @@ export interface WorkerProps<B extends Bindings = Bindings> {
|
|
|
74
74
|
* Migrations to apply to the worker
|
|
75
75
|
*/
|
|
76
76
|
migrations?: SingleStepMigration;
|
|
77
|
+
/**
|
|
78
|
+
* Whether to adopt the Worker if it already exists when creating
|
|
79
|
+
*/
|
|
80
|
+
adopt?: boolean;
|
|
77
81
|
}
|
|
78
82
|
/**
|
|
79
83
|
* Output returned after Worker creation/update
|
package/lib/cloudflare/worker.js
CHANGED
|
@@ -102,7 +102,9 @@ export const Worker = Resource("cloudflare::Worker", {
|
|
|
102
102
|
return this.destroy();
|
|
103
103
|
}
|
|
104
104
|
else if (this.phase === "create") {
|
|
105
|
-
|
|
105
|
+
if (!props.adopt) {
|
|
106
|
+
await assertWorkerDoesNotExist(this, api, workerName);
|
|
107
|
+
}
|
|
106
108
|
}
|
|
107
109
|
const oldBindings = await this.get("bindings");
|
|
108
110
|
// Get the script content - either from props.script, or by bundling
|
|
@@ -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
|
|