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/src/alchemy.ts
CHANGED
|
@@ -22,7 +22,7 @@ export const alchemy: Alchemy = _alchemy as any;
|
|
|
22
22
|
*
|
|
23
23
|
* @example
|
|
24
24
|
* // Create an application scope with stage and secret handling
|
|
25
|
-
* const app = alchemy("github:alchemy", {
|
|
25
|
+
* const app = await alchemy("github:alchemy", {
|
|
26
26
|
* stage: "prod",
|
|
27
27
|
* phase: "up",
|
|
28
28
|
* // Required for encrypting/decrypting secrets
|
|
@@ -57,13 +57,13 @@ export interface Alchemy {
|
|
|
57
57
|
* Used to create and manage resources with proper secret handling.
|
|
58
58
|
*
|
|
59
59
|
* @example
|
|
60
|
-
* const app = alchemy("my-app", {
|
|
60
|
+
* const app = await alchemy("my-app", {
|
|
61
61
|
* stage: "prod",
|
|
62
62
|
* // Required for encrypting/decrypting secrets
|
|
63
63
|
* password: process.env.SECRET_PASSPHRASE
|
|
64
64
|
* });
|
|
65
65
|
*/
|
|
66
|
-
(...parameters: Parameters<typeof scope>):
|
|
66
|
+
(...parameters: Parameters<typeof scope>): Promise<Scope>;
|
|
67
67
|
/**
|
|
68
68
|
* Template literal tag that supports file interpolation for documentation.
|
|
69
69
|
* Automatically formats the content and appends file contents as code blocks.
|
|
@@ -88,11 +88,11 @@ export interface Alchemy {
|
|
|
88
88
|
* Implementation of the alchemy function that handles both application scoping
|
|
89
89
|
* and template string interpolation.
|
|
90
90
|
*/
|
|
91
|
-
function _alchemy(
|
|
91
|
+
async function _alchemy(
|
|
92
92
|
...args:
|
|
93
93
|
| [template: TemplateStringsArray, ...values: any[]]
|
|
94
94
|
| [appName: string, options?: Omit<AlchemyOptions, "appName">]
|
|
95
|
-
):
|
|
95
|
+
): Promise<Scope | string | never> {
|
|
96
96
|
if (typeof args[0] === "string") {
|
|
97
97
|
const [appName, options] = args as [string, AlchemyOptions?];
|
|
98
98
|
const root = scope(undefined, {
|
|
@@ -101,9 +101,8 @@ function _alchemy(
|
|
|
101
101
|
stage: options?.stage,
|
|
102
102
|
});
|
|
103
103
|
if (options?.phase === "destroy") {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
});
|
|
104
|
+
await destroy(root);
|
|
105
|
+
return process.exit(0);
|
|
107
106
|
}
|
|
108
107
|
return root;
|
|
109
108
|
} else {
|
|
@@ -114,106 +113,104 @@ function _alchemy(
|
|
|
114
113
|
: 0;
|
|
115
114
|
const indent = " ".repeat(leadingSpaces);
|
|
116
115
|
|
|
117
|
-
|
|
118
|
-
const { isFileCollection, isFileRef } = await import("./fs");
|
|
116
|
+
const { isFileCollection, isFileRef } = await import("./fs");
|
|
119
117
|
|
|
120
|
-
|
|
118
|
+
const appendices: Record<string, string> = {};
|
|
121
119
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
120
|
+
const stringValues = await Promise.all(
|
|
121
|
+
values.map(async function resolve(value): Promise<string> {
|
|
122
|
+
if (typeof value === "string") {
|
|
123
|
+
return indent + value;
|
|
124
|
+
} else if (value === null) {
|
|
125
|
+
return "null";
|
|
126
|
+
} else if (value === undefined) {
|
|
127
|
+
return "undefined";
|
|
128
|
+
} else if (
|
|
129
|
+
typeof value === "number" ||
|
|
130
|
+
typeof value === "boolean" ||
|
|
131
|
+
typeof value === "bigint"
|
|
132
|
+
) {
|
|
133
|
+
return value.toString();
|
|
134
|
+
} else if (value instanceof Promise) {
|
|
135
|
+
return resolve(await value);
|
|
136
|
+
} else if (isFileRef(value)) {
|
|
137
|
+
if (!(value.path in appendices)) {
|
|
138
|
+
appendices[value.path] = await fs.readFile(value.path, "utf-8");
|
|
139
|
+
}
|
|
140
|
+
return `[${path.basename(value.path)}](${value.path})`;
|
|
141
|
+
} else if (isFileCollection(value)) {
|
|
142
|
+
return Object.entries(value.files)
|
|
143
|
+
.map(([filePath, content]) => {
|
|
144
|
+
appendices[filePath] = content;
|
|
145
|
+
return `[${path.basename(filePath)}](${filePath})`;
|
|
146
|
+
})
|
|
147
|
+
.join("\n\n");
|
|
148
|
+
} else if (Array.isArray(value)) {
|
|
149
|
+
return (
|
|
150
|
+
await Promise.all(
|
|
151
|
+
value.map(async (value, i) => `${i}. ${await resolve(value)}`)
|
|
152
|
+
)
|
|
153
|
+
).join("\n");
|
|
154
|
+
} else if (
|
|
155
|
+
typeof value === "object" &&
|
|
156
|
+
typeof value.path === "string"
|
|
157
|
+
) {
|
|
158
|
+
if (typeof value.content === "string") {
|
|
159
|
+
appendices[value.path] = value.content;
|
|
142
160
|
return `[${path.basename(value.path)}](${value.path})`;
|
|
143
|
-
} else if (isFileCollection(value)) {
|
|
144
|
-
return Object.entries(value.files)
|
|
145
|
-
.map(([filePath, content]) => {
|
|
146
|
-
appendices[filePath] = content;
|
|
147
|
-
return `[${path.basename(filePath)}](${filePath})`;
|
|
148
|
-
})
|
|
149
|
-
.join("\n\n");
|
|
150
|
-
} else if (Array.isArray(value)) {
|
|
151
|
-
return (
|
|
152
|
-
await Promise.all(
|
|
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
|
-
})
|
|
173
|
-
)
|
|
174
|
-
).join("\n");
|
|
175
161
|
} else {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
throw new Error(`Unsupported value type: ${value}`);
|
|
162
|
+
appendices[value.path] = await fs.readFile(value.path, "utf-8");
|
|
163
|
+
return `[${path.basename(value.path)}](${value.path})`;
|
|
179
164
|
}
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
part
|
|
187
|
-
.split("\n")
|
|
188
|
-
.map((line) =>
|
|
189
|
-
line.startsWith(indent) ? line.slice(indent.length) : line
|
|
165
|
+
} else if (typeof value === "object") {
|
|
166
|
+
return (
|
|
167
|
+
await Promise.all(
|
|
168
|
+
Object.entries(value).map(async ([key, value]) => {
|
|
169
|
+
return `* ${key}: ${await resolve(value)}`;
|
|
170
|
+
})
|
|
190
171
|
)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
172
|
+
).join("\n");
|
|
173
|
+
} else {
|
|
174
|
+
// TODO: support other types
|
|
175
|
+
console.log(value);
|
|
176
|
+
throw new Error(`Unsupported value type: ${value}`);
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
);
|
|
198
180
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
181
|
+
// Construct the string template by joining template parts with interpolated values
|
|
182
|
+
const lines = template
|
|
183
|
+
.map((part) =>
|
|
184
|
+
part
|
|
185
|
+
.split("\n")
|
|
186
|
+
.map((line) =>
|
|
187
|
+
line.startsWith(indent) ? line.slice(indent.length) : line
|
|
188
|
+
)
|
|
189
|
+
.join("\n")
|
|
190
|
+
)
|
|
191
|
+
.flatMap((part, i) =>
|
|
192
|
+
i < stringValues.length ? [part, stringValues[i] ?? ""] : [part]
|
|
193
|
+
)
|
|
194
|
+
.join("")
|
|
195
|
+
.split("\n");
|
|
205
196
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
197
|
+
// Collect and sort appendices by file path
|
|
198
|
+
return [
|
|
199
|
+
// format the user prompt and trim the first line if it's empty
|
|
200
|
+
lines.length > 1 && lines[0].replaceAll(" ", "").length === 0
|
|
201
|
+
? lines.slice(1).join("\n")
|
|
202
|
+
: lines.join("\n"),
|
|
203
|
+
|
|
204
|
+
// sort appendices by path and include at the end of the prompt
|
|
205
|
+
Object.entries(appendices)
|
|
206
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
207
|
+
.map(([filePath, content]) => {
|
|
208
|
+
const extension = path.extname(filePath).slice(1);
|
|
209
|
+
const codeTag = extension ? extension : "";
|
|
210
|
+
return `// ${filePath}\n\`\`\`${codeTag}\n${content}\n\`\`\``;
|
|
211
|
+
})
|
|
212
|
+
.join("\n\n"),
|
|
213
|
+
].join("\n");
|
|
217
214
|
}
|
|
218
215
|
}
|
|
219
216
|
_alchemy.destroy = destroy;
|
package/src/cloudflare/assets.ts
CHANGED
package/src/cloudflare/bound.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
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";
|
|
@@ -15,4 +16,6 @@ export type Bound<T extends Binding> = T extends _DurableObjectNamespace
|
|
|
15
16
|
? R2Bucket
|
|
16
17
|
: T extends Secret
|
|
17
18
|
? string
|
|
18
|
-
:
|
|
19
|
+
: T extends Assets
|
|
20
|
+
? Service
|
|
21
|
+
: Service;
|
|
@@ -64,8 +64,7 @@ export interface CustomDomain
|
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Configure custom domain for a Cloudflare Worker using the Cloudflare Custom Domains API
|
|
67
|
-
* This attaches a worker
|
|
68
|
-
* to a specific hostname within a zone.
|
|
67
|
+
* This attaches a worker to a specific hostname within a zone.
|
|
69
68
|
*
|
|
70
69
|
* @example
|
|
71
70
|
* // Bind a domain to a standard Cloudflare Worker
|
|
@@ -80,19 +79,6 @@ export interface CustomDomain
|
|
|
80
79
|
* workerName: apiWorker.name // Use the name from the Worker resource
|
|
81
80
|
* });
|
|
82
81
|
*
|
|
83
|
-
* @example
|
|
84
|
-
* // Bind a domain to a Cloudflare Static Site
|
|
85
|
-
* const mySite = await StaticSite("landing-page", {
|
|
86
|
-
* name: "my-landing-page-site", // This becomes the underlying worker name
|
|
87
|
-
* dir: "./dist/landing-page"
|
|
88
|
-
* });
|
|
89
|
-
*
|
|
90
|
-
* const siteDomain = await CustomDomain("site-domain-binding", {
|
|
91
|
-
* name: "www.example.com",
|
|
92
|
-
* zoneId: "YOUR_ZONE_ID", // Replace with actual Zone ID
|
|
93
|
-
* workerName: mySite.name // Use the name from the StaticSite resource
|
|
94
|
-
* });
|
|
95
|
-
*
|
|
96
82
|
* @see https://developers.cloudflare.com/api/resources/workers/subresources/domains/
|
|
97
83
|
*/
|
|
98
84
|
export const CustomDomain = Resource(
|
package/src/cloudflare/index.ts
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";
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -101,6 +101,11 @@ export interface WorkerProps<B extends Bindings = Bindings> {
|
|
|
101
101
|
* Migrations to apply to the worker
|
|
102
102
|
*/
|
|
103
103
|
migrations?: SingleStepMigration;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Whether to adopt the Worker if it already exists when creating
|
|
107
|
+
*/
|
|
108
|
+
adopt?: boolean;
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
/**
|
|
@@ -245,7 +250,9 @@ export const Worker = Resource(
|
|
|
245
250
|
await deleteWorker(this, api, workerName);
|
|
246
251
|
return this.destroy();
|
|
247
252
|
} else if (this.phase === "create") {
|
|
248
|
-
|
|
253
|
+
if (!props.adopt) {
|
|
254
|
+
await assertWorkerDoesNotExist(this, api, workerName);
|
|
255
|
+
}
|
|
249
256
|
}
|
|
250
257
|
|
|
251
258
|
const oldBindings = await this.get<Bindings>("bindings");
|
|
@@ -124,6 +124,7 @@ async function generateProviderDocs({
|
|
|
124
124
|
reasoningEffort: "high",
|
|
125
125
|
},
|
|
126
126
|
},
|
|
127
|
+
freeze: true,
|
|
127
128
|
temperature: 0.1,
|
|
128
129
|
schema: type({
|
|
129
130
|
groups: type({
|
|
@@ -156,7 +157,7 @@ async function generateProviderDocs({
|
|
|
156
157
|
A file is considered a "Utility" if it contains utility functions that are not resources or clients.
|
|
157
158
|
A file is considered a "Types" if it contains just type definitions and maybe helpers around working with those types.
|
|
158
159
|
|
|
159
|
-
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. "
|
|
160
|
+
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.
|
|
160
161
|
|
|
161
162
|
// "Resource Name"
|
|
162
163
|
const ResourceName = Resource(...)
|
package/src/os/exec.ts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import type { Context } from "../context";
|
|
4
|
+
import { Resource } from "../resource";
|
|
5
|
+
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Properties for executing a shell command
|
|
10
|
+
*/
|
|
11
|
+
export interface ExecProps {
|
|
12
|
+
/**
|
|
13
|
+
* The command to execute (including any arguments)
|
|
14
|
+
*/
|
|
15
|
+
command: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Whether to memoize the command (only re-run if the command changes)
|
|
19
|
+
*
|
|
20
|
+
* @default false
|
|
21
|
+
*/
|
|
22
|
+
memoize?: boolean;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Working directory for the command
|
|
26
|
+
*/
|
|
27
|
+
cwd?: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Environment variables to set
|
|
31
|
+
*/
|
|
32
|
+
env?: Record<string, string>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Maximum buffer size for stdout and stderr (in bytes)
|
|
36
|
+
* @default 1024 * 1024 (1MB)
|
|
37
|
+
*/
|
|
38
|
+
maxBuffer?: number;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Whether to throw an error if the command exits with a non-zero status
|
|
42
|
+
*/
|
|
43
|
+
throwOnError?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Output returned after command execution
|
|
48
|
+
*/
|
|
49
|
+
export interface Exec extends Resource<"os::Exec">, ExecProps {
|
|
50
|
+
/**
|
|
51
|
+
* Unique identifier for this execution
|
|
52
|
+
*/
|
|
53
|
+
id: string;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Exit code of the command
|
|
57
|
+
*/
|
|
58
|
+
exitCode: number;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Standard output from the command
|
|
62
|
+
*/
|
|
63
|
+
stdout: string;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Standard error from the command
|
|
67
|
+
*/
|
|
68
|
+
stderr: string;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Time at which the command was executed
|
|
72
|
+
*/
|
|
73
|
+
executedAt: number;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Whether the command has completed execution
|
|
77
|
+
*/
|
|
78
|
+
completed: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Execute a shell command
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* // Run a simple command
|
|
86
|
+
* const result = await Exec("list-files", {
|
|
87
|
+
* command: "ls -la"
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* console.log(result.stdout);
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* // Run a command in a specific directory with custom environment
|
|
94
|
+
* const build = await Exec("build-project", {
|
|
95
|
+
* command: "npm run build",
|
|
96
|
+
* cwd: "./my-project",
|
|
97
|
+
* env: { NODE_ENV: "production" }
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* // Run a command with a larger buffer for output
|
|
102
|
+
* const logs = await Exec("get-logs", {
|
|
103
|
+
* command: "cat large-log-file.log",
|
|
104
|
+
* maxBuffer: 10 * 1024 * 1024 // 10MB
|
|
105
|
+
* });
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* // Run a memoized command that only re-executes when the command changes
|
|
109
|
+
* const memoizedCmd = await Exec("status-check", {
|
|
110
|
+
* command: "git status",
|
|
111
|
+
* memoize: true
|
|
112
|
+
* });
|
|
113
|
+
*
|
|
114
|
+
* // This won't actually run the command again if nothing has changed
|
|
115
|
+
* await Exec("status-check", {
|
|
116
|
+
* command: "git status",
|
|
117
|
+
* memoize: true
|
|
118
|
+
* });
|
|
119
|
+
*/
|
|
120
|
+
export const Exec = Resource(
|
|
121
|
+
"os::Exec",
|
|
122
|
+
{
|
|
123
|
+
alwaysUpdate: true,
|
|
124
|
+
},
|
|
125
|
+
async function (
|
|
126
|
+
this: Context<Exec>,
|
|
127
|
+
id: string,
|
|
128
|
+
props: ExecProps
|
|
129
|
+
): Promise<Exec> {
|
|
130
|
+
if (this.phase === "delete") {
|
|
131
|
+
// Nothing to actually delete for an exec command
|
|
132
|
+
return this.destroy();
|
|
133
|
+
} else if (
|
|
134
|
+
this.phase === "update" &&
|
|
135
|
+
props.memoize &&
|
|
136
|
+
this.output?.command === props.command
|
|
137
|
+
) {
|
|
138
|
+
// If memoize is enabled and the command hasn't changed, return the existing output
|
|
139
|
+
return this.output;
|
|
140
|
+
} else {
|
|
141
|
+
// Default values
|
|
142
|
+
let stdout = "";
|
|
143
|
+
let stderr = "";
|
|
144
|
+
let exitCode = 0;
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
console.log(props.command);
|
|
148
|
+
// Execute the command
|
|
149
|
+
const result = await execAsync(props.command, {
|
|
150
|
+
cwd: props.cwd || process.cwd(),
|
|
151
|
+
env: { ...process.env, ...props.env },
|
|
152
|
+
maxBuffer: props.maxBuffer || 1024 * 1024, // Default 1MB
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
stdout = result.stdout;
|
|
156
|
+
console.log(stdout);
|
|
157
|
+
stderr = result.stderr;
|
|
158
|
+
exitCode = 0; // Success
|
|
159
|
+
} catch (error: any) {
|
|
160
|
+
console.log("error", error);
|
|
161
|
+
if (props.throwOnError) {
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// If not throwing, capture the error information
|
|
166
|
+
exitCode = error.code || 1;
|
|
167
|
+
stdout = error.stdout || "";
|
|
168
|
+
stderr = error.stderr || String(error);
|
|
169
|
+
console.log(stdout);
|
|
170
|
+
console.error(stderr);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Return the execution result
|
|
174
|
+
return this({
|
|
175
|
+
id,
|
|
176
|
+
command: props.command,
|
|
177
|
+
cwd: props.cwd,
|
|
178
|
+
env: props.env,
|
|
179
|
+
maxBuffer: props.maxBuffer,
|
|
180
|
+
throwOnError: props.throwOnError,
|
|
181
|
+
memoize: props.memoize,
|
|
182
|
+
exitCode,
|
|
183
|
+
stdout,
|
|
184
|
+
stderr,
|
|
185
|
+
executedAt: Date.now(),
|
|
186
|
+
completed: true,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
);
|
package/src/os/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./exec";
|
package/src/secret.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
|
* ```
|
|
@@ -55,7 +55,7 @@ export function isSecret(binding: any): binding is Secret {
|
|
|
55
55
|
*
|
|
56
56
|
* @example
|
|
57
57
|
* // Global password for all secrets
|
|
58
|
-
* const app = alchemy("my-app", {
|
|
58
|
+
* const app = await alchemy("my-app", {
|
|
59
59
|
* password: process.env.SECRET_PASSPHRASE
|
|
60
60
|
* });
|
|
61
61
|
*
|
|
@@ -190,7 +190,7 @@ export const VitepressProject = Resource(
|
|
|
190
190
|
// import { Document } from "alchemy/docs";
|
|
191
191
|
// import path from "path";
|
|
192
192
|
|
|
193
|
-
//
|
|
193
|
+
// const app = await alchemy("alchemy.run", {
|
|
194
194
|
// stage: "prod",
|
|
195
195
|
// phase: process.argv.includes("--destroy") ? "destroy" : "up",
|
|
196
196
|
// password: process.env.SECRET_PASSPHRASE,
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import crypto from "crypto";
|
|
2
|
-
import { promises as fs } from "fs";
|
|
3
|
-
import { glob } from "glob";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import { getContentType } from "../util/content-type";
|
|
6
|
-
export async function generateAssetManifest(dir, fileOptions = [
|
|
7
|
-
{
|
|
8
|
-
files: "**",
|
|
9
|
-
cacheControl: "max-age=0,no-cache,no-store,must-revalidate",
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
files: ["**/*.js", "**/*.css", "**/*.png", "**/*.webp"],
|
|
13
|
-
cacheControl: "max-age=31536000,public,immutable",
|
|
14
|
-
},
|
|
15
|
-
]) {
|
|
16
|
-
// First, collect all file info based on fileOptions (reversed to prioritize later rules)
|
|
17
|
-
const fileInfos = [];
|
|
18
|
-
const filesProcessed = new Set();
|
|
19
|
-
// Process file options in reverse order (later rules override earlier ones)
|
|
20
|
-
// Use for...of to maintain order but allow await inside the loop
|
|
21
|
-
const reversedOptions = [...fileOptions].reverse();
|
|
22
|
-
for (const fileOption of reversedOptions) {
|
|
23
|
-
const patterns = Array.isArray(fileOption.files)
|
|
24
|
-
? fileOption.files
|
|
25
|
-
: [fileOption.files];
|
|
26
|
-
// Get all files matching the pattern
|
|
27
|
-
const files = (await Promise.all(patterns.map(async (p) => {
|
|
28
|
-
// Use glob as async function instead of globSync
|
|
29
|
-
return glob(p, {
|
|
30
|
-
cwd: path.resolve(dir),
|
|
31
|
-
nodir: true,
|
|
32
|
-
dot: true,
|
|
33
|
-
ignore: [
|
|
34
|
-
".git/**",
|
|
35
|
-
"node_modules/**",
|
|
36
|
-
...(typeof fileOption.ignore === "string"
|
|
37
|
-
? [fileOption.ignore]
|
|
38
|
-
: (fileOption.ignore ?? [])),
|
|
39
|
-
],
|
|
40
|
-
});
|
|
41
|
-
})))
|
|
42
|
-
.flat()
|
|
43
|
-
.filter((file) => !filesProcessed.has(file));
|
|
44
|
-
// Add files to processed set
|
|
45
|
-
for (const file of files) {
|
|
46
|
-
filesProcessed.add(file);
|
|
47
|
-
fileInfos.push({
|
|
48
|
-
file,
|
|
49
|
-
cacheControl: fileOption.cacheControl,
|
|
50
|
-
contentType: fileOption.contentType,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
// Process all files in parallel
|
|
55
|
-
const manifestEntries = await Promise.all(fileInfos.map(async ({ file, cacheControl, contentType }) => {
|
|
56
|
-
const source = path.resolve(dir, file);
|
|
57
|
-
const content = await fs.readFile(source);
|
|
58
|
-
const hash = crypto.createHash("sha256").update(content).digest("hex");
|
|
59
|
-
return {
|
|
60
|
-
source,
|
|
61
|
-
key: file,
|
|
62
|
-
hash,
|
|
63
|
-
cacheControl,
|
|
64
|
-
contentType: contentType ?? getContentType(file),
|
|
65
|
-
};
|
|
66
|
-
}));
|
|
67
|
-
return manifestEntries;
|
|
68
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export type Env = {
|
|
2
|
-
ASSETS: KVNamespace;
|
|
3
|
-
INDEX_PAGE: string;
|
|
4
|
-
ERROR_PAGE?: string;
|
|
5
|
-
/**
|
|
6
|
-
* Optional backend worker bound to this static site
|
|
7
|
-
* Used to handle requests that don't match any static files
|
|
8
|
-
*/
|
|
9
|
-
BACKEND_WORKER?: Service;
|
|
10
|
-
} & {
|
|
11
|
-
[key in `ROUTE_${string}`]: Service;
|
|
12
|
-
} & {
|
|
13
|
-
[key in `__ROUTE_${string}`]: string;
|
|
14
|
-
};
|
|
15
|
-
declare const _default: {
|
|
16
|
-
fetch(request: Request, env: Env): Promise<Response>;
|
|
17
|
-
};
|
|
18
|
-
export default _default;
|