alchemy 0.11.3 → 0.12.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/lib/alchemy.d.ts +1 -1
- package/lib/alchemy.js +5 -24
- package/lib/cloudflare/index.d.ts +3 -1
- package/lib/cloudflare/index.js +3 -1
- package/lib/cloudflare/tanstack-start.d.ts +12 -0
- package/lib/cloudflare/tanstack-start.js +11 -0
- package/lib/cloudflare/vite.d.ts +12 -0
- package/lib/cloudflare/vite.js +18 -0
- package/lib/cloudflare/{vite-site.d.ts → website.d.ts} +14 -10
- package/lib/cloudflare/website.js +72 -0
- package/lib/cloudflare/worker.d.ts +42 -0
- package/lib/cloudflare/worker.js +51 -8
- package/lib/env.d.ts +5 -0
- package/lib/env.js +24 -0
- package/lib/internal/docs/providers.js +2 -2
- package/lib/secret.d.ts +5 -1
- package/lib/secret.js +6 -3
- package/package.json +5 -2
- package/src/alchemy.ts +6 -26
- package/src/cloudflare/index.ts +3 -1
- package/src/cloudflare/tanstack-start.ts +26 -0
- package/src/cloudflare/vite.ts +32 -0
- package/src/cloudflare/website.ts +140 -0
- package/src/cloudflare/worker.ts +118 -15
- package/src/env.ts +31 -0
- package/src/internal/docs/providers.ts +2 -2
- package/src/secret.ts +13 -2
- package/src/web/vitepress/process-front-matter-files.ts +1 -0
- package/lib/cloudflare/vite-site.js +0 -57
- package/src/cloudflare/vite-site.ts +0 -113
package/lib/alchemy.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { destroy } from "./destroy";
|
|
2
|
+
import { env } from "./env";
|
|
2
3
|
import { Scope } from "./scope";
|
|
3
4
|
import { secret } from "./secret";
|
|
4
5
|
import type { StateStoreType } from "./state";
|
|
@@ -143,5 +144,4 @@ declare function run<T>(...args: [id: string, fn: (this: Scope, scope: Scope) =>
|
|
|
143
144
|
options: RunOptions,
|
|
144
145
|
fn: (this: Scope, scope: Scope) => Promise<T>
|
|
145
146
|
]): Promise<T>;
|
|
146
|
-
export declare function env<T = string>(name: string, value?: T | undefined, error?: string): Promise<T>;
|
|
147
147
|
export {};
|
package/lib/alchemy.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { destroy, DestroyedSignal } from "./destroy";
|
|
4
|
+
import { env } from "./env";
|
|
4
5
|
import { Scope } from "./scope";
|
|
5
6
|
import { secret } from "./secret";
|
|
6
7
|
export const alchemy = _alchemy;
|
|
8
|
+
_alchemy.destroy = destroy;
|
|
9
|
+
_alchemy.run = run;
|
|
10
|
+
_alchemy.secret = secret;
|
|
11
|
+
_alchemy.env = env;
|
|
7
12
|
/**
|
|
8
13
|
* Implementation of the alchemy function that handles both application scoping
|
|
9
14
|
* and template string interpolation.
|
|
@@ -119,10 +124,6 @@ async function _alchemy(...args) {
|
|
|
119
124
|
].join("\n");
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
|
-
_alchemy.destroy = destroy;
|
|
123
|
-
_alchemy.run = run;
|
|
124
|
-
_alchemy.secret = secret;
|
|
125
|
-
_alchemy.env = env;
|
|
126
127
|
/**
|
|
127
128
|
* Run a function in a new scope asynchronously.
|
|
128
129
|
* Useful for isolating secret handling with a specific password.
|
|
@@ -183,23 +184,3 @@ async function run(...args) {
|
|
|
183
184
|
await _scope.finalize();
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
|
-
export async function env(name, value, error) {
|
|
187
|
-
if (value !== undefined) {
|
|
188
|
-
return value;
|
|
189
|
-
}
|
|
190
|
-
else if (typeof process !== undefined) {
|
|
191
|
-
// we are in a node environment
|
|
192
|
-
return process.env[name];
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
// we are in a browser environment
|
|
196
|
-
try {
|
|
197
|
-
const { env } = await import("cloudflare:workers");
|
|
198
|
-
if (name in env) {
|
|
199
|
-
return env[name];
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
catch (error) { }
|
|
203
|
-
}
|
|
204
|
-
throw new Error(error ?? `Environment variable ${name} is not set`);
|
|
205
|
-
}
|
|
@@ -13,9 +13,11 @@ export * from "./permission-groups";
|
|
|
13
13
|
export * from "./pipeline";
|
|
14
14
|
export * from "./queue";
|
|
15
15
|
export * from "./r2-rest-state-store";
|
|
16
|
+
export * from "./tanstack-start";
|
|
16
17
|
export * from "./vectorize-index";
|
|
17
18
|
export * from "./vectorize-metadata-index";
|
|
18
|
-
export * from "./vite
|
|
19
|
+
export * from "./vite";
|
|
20
|
+
export * from "./website";
|
|
19
21
|
export * from "./worker";
|
|
20
22
|
export { Workflow } from "./workflow";
|
|
21
23
|
export * from "./wrangler.json";
|
package/lib/cloudflare/index.js
CHANGED
|
@@ -13,9 +13,11 @@ export * from "./permission-groups";
|
|
|
13
13
|
export * from "./pipeline";
|
|
14
14
|
export * from "./queue";
|
|
15
15
|
export * from "./r2-rest-state-store";
|
|
16
|
+
export * from "./tanstack-start";
|
|
16
17
|
export * from "./vectorize-index";
|
|
17
18
|
export * from "./vectorize-metadata-index";
|
|
18
|
-
export * from "./vite
|
|
19
|
+
export * from "./vite";
|
|
20
|
+
export * from "./website";
|
|
19
21
|
export * from "./worker";
|
|
20
22
|
export { Workflow } from "./workflow";
|
|
21
23
|
export * from "./wrangler.json";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Assets } from "./assets";
|
|
2
|
+
import type { Bindings } from "./bindings";
|
|
3
|
+
import { type WebsiteProps } from "./website";
|
|
4
|
+
import type { Worker } from "./worker";
|
|
5
|
+
export interface TanStackStartProps<B extends Bindings> extends WebsiteProps<B> {
|
|
6
|
+
}
|
|
7
|
+
export type TanStackStart<B extends Bindings> = B extends {
|
|
8
|
+
ASSETS: any;
|
|
9
|
+
} ? never : Worker<B & {
|
|
10
|
+
ASSETS: Assets;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function TanStackStart<B extends Bindings>(id: string, props?: Partial<TanStackStartProps<B>>): Promise<TanStackStart<B>>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Website } from "./website";
|
|
2
|
+
export async function TanStackStart(id, props) {
|
|
3
|
+
return Website(id, {
|
|
4
|
+
...props,
|
|
5
|
+
command: props?.command ?? "bun run build",
|
|
6
|
+
wrangler: props?.wrangler ?? false,
|
|
7
|
+
entrypoint: props?.entrypoint ?? ".output/server/index.mjs",
|
|
8
|
+
compatibilityFlags: ["nodejs_compat", ...(props?.compatibilityFlags ?? [])],
|
|
9
|
+
assets: props?.assets ?? ".output/public",
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Assets } from "./assets";
|
|
2
|
+
import type { Bindings } from "./bindings";
|
|
3
|
+
import { type WebsiteProps } from "./website";
|
|
4
|
+
import type { Worker } from "./worker";
|
|
5
|
+
export interface ViteProps<B extends Bindings> extends WebsiteProps<B> {
|
|
6
|
+
}
|
|
7
|
+
export type Vite<B extends Bindings> = B extends {
|
|
8
|
+
ASSETS: any;
|
|
9
|
+
} ? never : Worker<B & {
|
|
10
|
+
ASSETS: Assets;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function Vite<B extends Bindings>(id: string, props: ViteProps<B>): Promise<Vite<B>>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Website } from "./website";
|
|
4
|
+
export async function Vite(id, props) {
|
|
5
|
+
return Website(id, {
|
|
6
|
+
...props,
|
|
7
|
+
assets: props.assets ??
|
|
8
|
+
(await (async () => {
|
|
9
|
+
try {
|
|
10
|
+
await fs.access(path.join("dist", "client", "index.html"));
|
|
11
|
+
return path.join(".", "dist", "client");
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return path.join(".", "dist");
|
|
15
|
+
}
|
|
16
|
+
})()),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Assets } from "./assets";
|
|
2
2
|
import type { Bindings } from "./bindings";
|
|
3
|
-
import { Worker } from "./worker";
|
|
4
|
-
export interface
|
|
3
|
+
import { Worker, type AssetsConfig, type WorkerProps } from "./worker";
|
|
4
|
+
export interface WebsiteProps<B extends Bindings> extends Omit<WorkerProps<B>, "name" | "assets"> {
|
|
5
5
|
/**
|
|
6
6
|
* The command to run to build the site
|
|
7
7
|
*/
|
|
@@ -23,19 +23,23 @@ export interface ViteSiteProps<B extends Bindings> {
|
|
|
23
23
|
*
|
|
24
24
|
* @default "./dist"
|
|
25
25
|
*/
|
|
26
|
-
assets?: string
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*/
|
|
30
|
-
bindings?: B;
|
|
26
|
+
assets?: string | ({
|
|
27
|
+
dist?: string;
|
|
28
|
+
} & AssetsConfig);
|
|
31
29
|
/**
|
|
32
30
|
* @default process.cwd()
|
|
33
31
|
*/
|
|
34
32
|
cwd?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Write a wrangler.jsonc file
|
|
35
|
+
*
|
|
36
|
+
* @default - no wrangler.jsonc file is written
|
|
37
|
+
*/
|
|
38
|
+
wrangler?: boolean | string;
|
|
35
39
|
}
|
|
36
|
-
export type
|
|
40
|
+
export type Website<B extends Bindings> = B extends {
|
|
37
41
|
ASSETS: any;
|
|
38
42
|
} ? never : Worker<B & {
|
|
39
43
|
ASSETS: Assets;
|
|
40
|
-
}
|
|
41
|
-
export declare function
|
|
44
|
+
}>;
|
|
45
|
+
export declare function Website<B extends Bindings>(id: string, props: WebsiteProps<B>): Promise<Website<B>>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { alchemy } from "../alchemy";
|
|
4
|
+
import { Exec } from "../os";
|
|
5
|
+
import { Assets } from "./assets";
|
|
6
|
+
import { Worker } from "./worker";
|
|
7
|
+
import { WranglerJson } from "./wrangler.json";
|
|
8
|
+
export async function Website(id, props) {
|
|
9
|
+
if (props.bindings?.ASSETS) {
|
|
10
|
+
throw new Error("ASSETS binding is reserved for internal use");
|
|
11
|
+
}
|
|
12
|
+
return alchemy.run(id, async () => {
|
|
13
|
+
// building the site requires a wrangler.jsonc file to start
|
|
14
|
+
// - so initialize an empty one if it doesn't exist
|
|
15
|
+
const cwd = path.resolve(props.cwd || process.cwd());
|
|
16
|
+
const fileName = typeof props.wrangler === "boolean" ? "wrangler.jsonc" : props.wrangler;
|
|
17
|
+
const wranglerPath = fileName && path.relative(cwd, path.join(cwd, fileName));
|
|
18
|
+
if (props.wrangler) {
|
|
19
|
+
try {
|
|
20
|
+
await fs.access(wranglerPath);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
await fs.writeFile(wranglerPath, JSON.stringify({
|
|
24
|
+
name: id,
|
|
25
|
+
main: props.main,
|
|
26
|
+
compatibility_date: new Date().toISOString().split("T")[0],
|
|
27
|
+
}, null, 2));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
await Exec("build", {
|
|
31
|
+
command: props.command,
|
|
32
|
+
});
|
|
33
|
+
const dist = typeof props.assets === "string"
|
|
34
|
+
? props.assets
|
|
35
|
+
: (props.assets?.dist ?? "dist");
|
|
36
|
+
const staticAssets = await Assets("assets", {
|
|
37
|
+
path: dist,
|
|
38
|
+
});
|
|
39
|
+
const worker = await Worker("worker", {
|
|
40
|
+
...props,
|
|
41
|
+
name: props.name ?? id,
|
|
42
|
+
entrypoint: props.main,
|
|
43
|
+
assets: {
|
|
44
|
+
html_handling: "auto-trailing-slash",
|
|
45
|
+
not_found_handling: "single-page-application",
|
|
46
|
+
run_worker_first: false,
|
|
47
|
+
...(typeof props.assets === "string" ? {} : props.assets),
|
|
48
|
+
},
|
|
49
|
+
script: props.main
|
|
50
|
+
? undefined
|
|
51
|
+
: `
|
|
52
|
+
export default {
|
|
53
|
+
async fetch(request, env) {
|
|
54
|
+
return new Response("Not Found", { status: 404 });
|
|
55
|
+
},
|
|
56
|
+
};`,
|
|
57
|
+
url: true,
|
|
58
|
+
adopt: true,
|
|
59
|
+
bindings: {
|
|
60
|
+
...props.bindings,
|
|
61
|
+
ASSETS: staticAssets,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
if (props.wrangler) {
|
|
65
|
+
await WranglerJson("wrangler.jsonc", {
|
|
66
|
+
path: wranglerPath,
|
|
67
|
+
worker,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return worker;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -5,6 +5,40 @@ import { type CloudflareApiOptions } from "./api";
|
|
|
5
5
|
import { type Bindings } from "./bindings";
|
|
6
6
|
import type { Bound } from "./bound";
|
|
7
7
|
import type { SingleStepMigration } from "./worker-migration";
|
|
8
|
+
/**
|
|
9
|
+
* Configuration options for static assets
|
|
10
|
+
*/
|
|
11
|
+
export interface AssetsConfig {
|
|
12
|
+
/**
|
|
13
|
+
* The contents of a _headers file (used to attach custom headers on asset responses)
|
|
14
|
+
*/
|
|
15
|
+
_headers?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The contents of a _redirects file (used to apply redirects or proxy paths ahead of asset serving)
|
|
18
|
+
*/
|
|
19
|
+
_redirects?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Determines the redirects and rewrites of requests for HTML content
|
|
22
|
+
* @default "auto-trailing-slash"
|
|
23
|
+
*/
|
|
24
|
+
html_handling?: "auto-trailing-slash" | "force-trailing-slash" | "drop-trailing-slash" | "none";
|
|
25
|
+
/**
|
|
26
|
+
* Determines the response when a request does not match a static asset, and there is no Worker script
|
|
27
|
+
*/
|
|
28
|
+
not_found_handling?: "none" | "404-page" | "single-page-application";
|
|
29
|
+
/**
|
|
30
|
+
* When true, requests will always invoke the Worker script.
|
|
31
|
+
* Otherwise, attempt to serve an asset matching the request, falling back to the Worker script.
|
|
32
|
+
*/
|
|
33
|
+
run_worker_first?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* When true and the incoming request matches an asset, that will be served instead of invoking the Worker script.
|
|
36
|
+
* When false, requests will always invoke the Worker script.
|
|
37
|
+
* @default true
|
|
38
|
+
* @deprecated
|
|
39
|
+
*/
|
|
40
|
+
serve_directly?: boolean;
|
|
41
|
+
}
|
|
8
42
|
/**
|
|
9
43
|
* Properties for creating or updating a Worker
|
|
10
44
|
*/
|
|
@@ -83,6 +117,10 @@ export interface WorkerProps<B extends Bindings = Bindings> extends CloudflareAp
|
|
|
83
117
|
* The compatibility flags for the worker
|
|
84
118
|
*/
|
|
85
119
|
compatibilityFlags?: string[];
|
|
120
|
+
/**
|
|
121
|
+
* Configuration for static assets
|
|
122
|
+
*/
|
|
123
|
+
assets?: AssetsConfig;
|
|
86
124
|
}
|
|
87
125
|
/**
|
|
88
126
|
* Output returned after Worker creation/update
|
|
@@ -110,6 +148,10 @@ export interface Worker<B extends Bindings = Bindings> extends Resource<"cloudfl
|
|
|
110
148
|
* The bindings that were created
|
|
111
149
|
*/
|
|
112
150
|
bindings: B | undefined;
|
|
151
|
+
/**
|
|
152
|
+
* Configuration for static assets
|
|
153
|
+
*/
|
|
154
|
+
assets?: AssetsConfig;
|
|
113
155
|
Env: {
|
|
114
156
|
[bindingName in keyof B]: Bound<B[bindingName]>;
|
|
115
157
|
};
|
package/lib/cloudflare/worker.js
CHANGED
|
@@ -132,7 +132,7 @@ export const Worker = Resource("cloudflare::Worker", {
|
|
|
132
132
|
// In the future, we might want to support multiple asset bindings
|
|
133
133
|
const assetBinding = assetsBindings[0];
|
|
134
134
|
// Upload the assets and get the completion token
|
|
135
|
-
assetUploadResult = await uploadAssets(api, workerName, assetBinding.assets);
|
|
135
|
+
assetUploadResult = await uploadAssets(api, workerName, assetBinding.assets, props.assets);
|
|
136
136
|
}
|
|
137
137
|
// Prepare metadata with bindings
|
|
138
138
|
const scriptMetadata = await prepareWorkerMetadata(this, oldBindings, props, assetUploadResult);
|
|
@@ -165,6 +165,8 @@ export const Worker = Resource("cloudflare::Worker", {
|
|
|
165
165
|
createdAt: now,
|
|
166
166
|
updatedAt: now,
|
|
167
167
|
url: workerUrl,
|
|
168
|
+
// Include assets configuration in the output
|
|
169
|
+
assets: props.assets,
|
|
168
170
|
// phantom property
|
|
169
171
|
Env: undefined,
|
|
170
172
|
});
|
|
@@ -236,6 +238,35 @@ class NotFoundError extends Error {
|
|
|
236
238
|
this.name = "NotFoundError";
|
|
237
239
|
}
|
|
238
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Creates asset configuration object from provided config or defaults
|
|
243
|
+
*/
|
|
244
|
+
function createAssetConfig(config) {
|
|
245
|
+
const assetConfig = {
|
|
246
|
+
html_handling: "auto-trailing-slash",
|
|
247
|
+
};
|
|
248
|
+
if (config) {
|
|
249
|
+
if (config._headers !== undefined) {
|
|
250
|
+
assetConfig._headers = config._headers;
|
|
251
|
+
}
|
|
252
|
+
if (config._redirects !== undefined) {
|
|
253
|
+
assetConfig._redirects = config._redirects;
|
|
254
|
+
}
|
|
255
|
+
if (config.html_handling !== undefined) {
|
|
256
|
+
assetConfig.html_handling = config.html_handling;
|
|
257
|
+
}
|
|
258
|
+
if (config.not_found_handling !== undefined) {
|
|
259
|
+
assetConfig.not_found_handling = config.not_found_handling;
|
|
260
|
+
}
|
|
261
|
+
if (config.run_worker_first !== undefined) {
|
|
262
|
+
assetConfig.run_worker_first = config.run_worker_first;
|
|
263
|
+
}
|
|
264
|
+
if (config.serve_directly !== undefined) {
|
|
265
|
+
assetConfig.serve_directly = config.serve_directly;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return assetConfig;
|
|
269
|
+
}
|
|
239
270
|
async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult) {
|
|
240
271
|
// Prepare metadata with bindings
|
|
241
272
|
const meta = {
|
|
@@ -260,8 +291,16 @@ async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult)
|
|
|
260
291
|
meta.assets = {
|
|
261
292
|
jwt: assetUploadResult.completionToken,
|
|
262
293
|
};
|
|
294
|
+
// Initialize config from assetUploadResult if it exists
|
|
263
295
|
if (assetUploadResult.assetConfig) {
|
|
264
|
-
meta.assets.config =
|
|
296
|
+
meta.assets.config = {
|
|
297
|
+
...assetUploadResult.assetConfig,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
// If there's no config from assetUploadResult but we have props.assets,
|
|
301
|
+
// we need to create the config ourselves (this handles the case when no assets were uploaded)
|
|
302
|
+
if (!meta.assets.config && props.assets) {
|
|
303
|
+
meta.assets.config = createAssetConfig(props.assets);
|
|
265
304
|
}
|
|
266
305
|
}
|
|
267
306
|
const bindings = (props.bindings ?? {});
|
|
@@ -538,9 +577,12 @@ async function getWorkerBindings(api, workerName, environment = "production") {
|
|
|
538
577
|
* @param api CloudflareApi instance
|
|
539
578
|
* @param workerName Name of the worker
|
|
540
579
|
* @param assets Assets resource containing files to upload
|
|
580
|
+
* @param assetConfig Configuration for the assets
|
|
541
581
|
* @returns Completion token for the assets upload
|
|
542
582
|
*/
|
|
543
|
-
async function uploadAssets(api, workerName, assets) {
|
|
583
|
+
async function uploadAssets(api, workerName, assets, assetConfig) {
|
|
584
|
+
// Process the assets configuration once at the beginning
|
|
585
|
+
const processedConfig = createAssetConfig(assetConfig);
|
|
544
586
|
// Generate the file manifest
|
|
545
587
|
const fileMetadata = {};
|
|
546
588
|
// Process each file in the assets
|
|
@@ -561,7 +603,10 @@ async function uploadAssets(api, workerName, assets) {
|
|
|
561
603
|
const sessionData = (await uploadSessionResponse.json());
|
|
562
604
|
// If there are no buckets, assets are already uploaded or empty
|
|
563
605
|
if (!sessionData.result.buckets || sessionData.result.buckets.length === 0) {
|
|
564
|
-
return {
|
|
606
|
+
return {
|
|
607
|
+
completionToken: sessionData.result.jwt,
|
|
608
|
+
assetConfig: processedConfig,
|
|
609
|
+
};
|
|
565
610
|
}
|
|
566
611
|
// Upload the files in batches as specified by the API
|
|
567
612
|
let completionToken = sessionData.result.jwt;
|
|
@@ -607,12 +652,10 @@ async function uploadAssets(api, workerName, assets) {
|
|
|
607
652
|
completionToken = uploadData.result.jwt;
|
|
608
653
|
}
|
|
609
654
|
}
|
|
610
|
-
// Return the final completion token
|
|
655
|
+
// Return the final completion token with asset configuration
|
|
611
656
|
return {
|
|
612
657
|
completionToken,
|
|
613
|
-
assetConfig:
|
|
614
|
-
html_handling: "auto-trailing-slash",
|
|
615
|
-
},
|
|
658
|
+
assetConfig: processedConfig,
|
|
616
659
|
};
|
|
617
660
|
}
|
|
618
661
|
/**
|
package/lib/env.d.ts
ADDED
package/lib/env.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const env = new Proxy(_env, {
|
|
2
|
+
get: (_, name) => _env(name),
|
|
3
|
+
apply: (_, __, args) => _env(...args),
|
|
4
|
+
});
|
|
5
|
+
async function _env(name, value, error) {
|
|
6
|
+
if (value !== undefined) {
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
else if (typeof process !== undefined) {
|
|
10
|
+
// we are in a node environment
|
|
11
|
+
return process.env[name];
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
// we are in a browser environment
|
|
15
|
+
try {
|
|
16
|
+
const { env } = await import("cloudflare:workers");
|
|
17
|
+
if (name in env) {
|
|
18
|
+
return env[name];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch (error) { }
|
|
22
|
+
}
|
|
23
|
+
throw new Error(error ?? `Environment variable ${name} is not set`);
|
|
24
|
+
}
|
|
@@ -49,7 +49,7 @@ async function generateProviderDocs({ provider, outDir, parallel, }) {
|
|
|
49
49
|
reasoningEffort: "high",
|
|
50
50
|
},
|
|
51
51
|
},
|
|
52
|
-
freeze:
|
|
52
|
+
freeze: false,
|
|
53
53
|
temperature: 0.1,
|
|
54
54
|
schema: type({
|
|
55
55
|
groups: type({
|
|
@@ -96,7 +96,7 @@ async function generateProviderDocs({ provider, outDir, parallel, }) {
|
|
|
96
96
|
return Document(`docs/${providerName}/${g.identifier}`, {
|
|
97
97
|
title: g.identifier,
|
|
98
98
|
path: path.join(providerDocsDir, `${g.filename.replace(".ts", "").replace(".md", "")}.md`),
|
|
99
|
-
freeze:
|
|
99
|
+
freeze: false,
|
|
100
100
|
model: {
|
|
101
101
|
id: "claude-3-5-sonnet-latest",
|
|
102
102
|
provider: "anthropic",
|
package/lib/secret.d.ts
CHANGED
|
@@ -73,5 +73,9 @@ export declare function isSecret(binding: any): binding is Secret;
|
|
|
73
73
|
*/
|
|
74
74
|
export declare function secret<S extends string | undefined>(unencrypted: S): Secret;
|
|
75
75
|
export declare namespace secret {
|
|
76
|
-
|
|
76
|
+
interface Env {
|
|
77
|
+
[key: string]: Promise<Secret>;
|
|
78
|
+
(name: string, value?: string, error?: string): Promise<Secret>;
|
|
79
|
+
}
|
|
80
|
+
const env: Env;
|
|
77
81
|
}
|
package/lib/secret.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { alchemy } from "./alchemy";
|
|
1
2
|
/**
|
|
2
3
|
* Internal wrapper for sensitive values like API keys and credentials.
|
|
3
4
|
* When stored in alchemy state files, the value is automatically encrypted
|
|
@@ -83,8 +84,11 @@ export function secret(unencrypted) {
|
|
|
83
84
|
return new Secret(unencrypted);
|
|
84
85
|
}
|
|
85
86
|
(function (secret) {
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
secret.env = new Proxy(_env, {
|
|
88
|
+
get: (_, name) => _env(name),
|
|
89
|
+
apply: (_, __, args) => _env(...args),
|
|
90
|
+
});
|
|
91
|
+
async function _env(name, value, error) {
|
|
88
92
|
const result = await alchemy.env(name, value, error);
|
|
89
93
|
if (typeof result === "string") {
|
|
90
94
|
return secret(result);
|
|
@@ -93,5 +97,4 @@ export function secret(unencrypted) {
|
|
|
93
97
|
throw new Error(`Secret environment variable ${name} is not a string`);
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
|
-
secret.env = env;
|
|
97
100
|
})(secret || (secret = {}));
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alchemy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"module": "./lib/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"publish:npm": "cp ../README.md . && rm -rf ./*.tsbuildinfo && rm -rf ./lib && tsc -b && npm publish && rm README.md",
|
|
8
8
|
"docs:gen": "rm -rf ./docs && typedoc"
|
|
9
9
|
},
|
|
10
|
-
"files": [
|
|
10
|
+
"files": [
|
|
11
|
+
"lib",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
11
14
|
"exports": {
|
|
12
15
|
".": "./lib/index.js",
|
|
13
16
|
"./ai": "./lib/ai/index.js",
|
package/src/alchemy.ts
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import { destroy, DestroyedSignal } from "./destroy";
|
|
5
|
+
import { env } from "./env";
|
|
5
6
|
import type { PendingResource } from "./resource";
|
|
6
7
|
import { Scope } from "./scope";
|
|
7
8
|
import { secret } from "./secret";
|
|
@@ -81,6 +82,11 @@ export interface Alchemy {
|
|
|
81
82
|
(template: TemplateStringsArray, ...values: any[]): Promise<string>;
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
_alchemy.destroy = destroy;
|
|
86
|
+
_alchemy.run = run;
|
|
87
|
+
_alchemy.secret = secret;
|
|
88
|
+
_alchemy.env = env;
|
|
89
|
+
|
|
84
90
|
/**
|
|
85
91
|
* Implementation of the alchemy function that handles both application scoping
|
|
86
92
|
* and template string interpolation.
|
|
@@ -214,10 +220,6 @@ async function _alchemy(
|
|
|
214
220
|
].join("\n");
|
|
215
221
|
}
|
|
216
222
|
}
|
|
217
|
-
_alchemy.destroy = destroy;
|
|
218
|
-
_alchemy.run = run;
|
|
219
|
-
_alchemy.secret = secret;
|
|
220
|
-
_alchemy.env = env;
|
|
221
223
|
|
|
222
224
|
export interface AlchemyOptions {
|
|
223
225
|
/**
|
|
@@ -349,25 +351,3 @@ async function run<T>(
|
|
|
349
351
|
await _scope.finalize();
|
|
350
352
|
}
|
|
351
353
|
}
|
|
352
|
-
|
|
353
|
-
export async function env<T = string>(
|
|
354
|
-
name: string,
|
|
355
|
-
value?: T | undefined,
|
|
356
|
-
error?: string
|
|
357
|
-
): Promise<T> {
|
|
358
|
-
if (value !== undefined) {
|
|
359
|
-
return value;
|
|
360
|
-
} else if (typeof process !== undefined) {
|
|
361
|
-
// we are in a node environment
|
|
362
|
-
return process.env[name]! as T;
|
|
363
|
-
} else {
|
|
364
|
-
// we are in a browser environment
|
|
365
|
-
try {
|
|
366
|
-
const { env } = await import("cloudflare:workers");
|
|
367
|
-
if (name in env) {
|
|
368
|
-
return env[name as keyof typeof env];
|
|
369
|
-
}
|
|
370
|
-
} catch (error) {}
|
|
371
|
-
}
|
|
372
|
-
throw new Error(error ?? `Environment variable ${name} is not set`);
|
|
373
|
-
}
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -13,9 +13,11 @@ export * from "./permission-groups";
|
|
|
13
13
|
export * from "./pipeline";
|
|
14
14
|
export * from "./queue";
|
|
15
15
|
export * from "./r2-rest-state-store";
|
|
16
|
+
export * from "./tanstack-start";
|
|
16
17
|
export * from "./vectorize-index";
|
|
17
18
|
export * from "./vectorize-metadata-index";
|
|
18
|
-
export * from "./vite
|
|
19
|
+
export * from "./vite";
|
|
20
|
+
export * from "./website";
|
|
19
21
|
export * from "./worker";
|
|
20
22
|
export { Workflow } from "./workflow";
|
|
21
23
|
export * from "./wrangler.json";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Assets } from "./assets";
|
|
2
|
+
import type { Bindings } from "./bindings";
|
|
3
|
+
import { Website, type WebsiteProps } from "./website";
|
|
4
|
+
import type { Worker } from "./worker";
|
|
5
|
+
|
|
6
|
+
export interface TanStackStartProps<B extends Bindings>
|
|
7
|
+
extends WebsiteProps<B> {}
|
|
8
|
+
|
|
9
|
+
// don't allow the ASSETS to be overriden
|
|
10
|
+
export type TanStackStart<B extends Bindings> = B extends { ASSETS: any }
|
|
11
|
+
? never
|
|
12
|
+
: Worker<B & { ASSETS: Assets }>;
|
|
13
|
+
|
|
14
|
+
export async function TanStackStart<B extends Bindings>(
|
|
15
|
+
id: string,
|
|
16
|
+
props?: Partial<TanStackStartProps<B>>
|
|
17
|
+
): Promise<TanStackStart<B>> {
|
|
18
|
+
return Website(id, {
|
|
19
|
+
...props,
|
|
20
|
+
command: props?.command ?? "bun run build",
|
|
21
|
+
wrangler: props?.wrangler ?? false,
|
|
22
|
+
entrypoint: props?.entrypoint ?? ".output/server/index.mjs",
|
|
23
|
+
compatibilityFlags: ["nodejs_compat", ...(props?.compatibilityFlags ?? [])],
|
|
24
|
+
assets: props?.assets ?? ".output/public",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { Assets } from "./assets";
|
|
4
|
+
import type { Bindings } from "./bindings";
|
|
5
|
+
import { Website, type WebsiteProps } from "./website";
|
|
6
|
+
import type { Worker } from "./worker";
|
|
7
|
+
|
|
8
|
+
export interface ViteProps<B extends Bindings> extends WebsiteProps<B> {}
|
|
9
|
+
|
|
10
|
+
// don't allow the ASSETS to be overriden
|
|
11
|
+
export type Vite<B extends Bindings> = B extends { ASSETS: any }
|
|
12
|
+
? never
|
|
13
|
+
: Worker<B & { ASSETS: Assets }>;
|
|
14
|
+
|
|
15
|
+
export async function Vite<B extends Bindings>(
|
|
16
|
+
id: string,
|
|
17
|
+
props: ViteProps<B>
|
|
18
|
+
): Promise<Vite<B>> {
|
|
19
|
+
return Website(id, {
|
|
20
|
+
...props,
|
|
21
|
+
assets:
|
|
22
|
+
props.assets ??
|
|
23
|
+
(await (async () => {
|
|
24
|
+
try {
|
|
25
|
+
await fs.access(path.join("dist", "client", "index.html"));
|
|
26
|
+
return path.join(".", "dist", "client");
|
|
27
|
+
} catch {
|
|
28
|
+
return path.join(".", "dist");
|
|
29
|
+
}
|
|
30
|
+
})()),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { alchemy } from "../alchemy";
|
|
4
|
+
import { Exec } from "../os";
|
|
5
|
+
import { Assets } from "./assets";
|
|
6
|
+
import type { Bindings } from "./bindings";
|
|
7
|
+
import { Worker, type AssetsConfig, type WorkerProps } from "./worker";
|
|
8
|
+
import { WranglerJson } from "./wrangler.json";
|
|
9
|
+
|
|
10
|
+
export interface WebsiteProps<B extends Bindings>
|
|
11
|
+
extends Omit<WorkerProps<B>, "name" | "assets"> {
|
|
12
|
+
/**
|
|
13
|
+
* The command to run to build the site
|
|
14
|
+
*/
|
|
15
|
+
command: string;
|
|
16
|
+
/**
|
|
17
|
+
* The name of the worker
|
|
18
|
+
*
|
|
19
|
+
* @default id
|
|
20
|
+
*/
|
|
21
|
+
name?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The entrypoint to your server
|
|
24
|
+
*
|
|
25
|
+
* @default - a simple server that serves static assets is generated
|
|
26
|
+
*/
|
|
27
|
+
main?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The directory containing your static assets
|
|
30
|
+
*
|
|
31
|
+
* @default "./dist"
|
|
32
|
+
*/
|
|
33
|
+
assets?:
|
|
34
|
+
| string
|
|
35
|
+
| ({
|
|
36
|
+
dist?: string;
|
|
37
|
+
} & AssetsConfig);
|
|
38
|
+
/**
|
|
39
|
+
* @default process.cwd()
|
|
40
|
+
*/
|
|
41
|
+
cwd?: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Write a wrangler.jsonc file
|
|
45
|
+
*
|
|
46
|
+
* @default - no wrangler.jsonc file is written
|
|
47
|
+
*/
|
|
48
|
+
wrangler?: boolean | string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type Website<B extends Bindings> = B extends { ASSETS: any }
|
|
52
|
+
? never
|
|
53
|
+
: Worker<B & { ASSETS: Assets }>;
|
|
54
|
+
|
|
55
|
+
export async function Website<B extends Bindings>(
|
|
56
|
+
id: string,
|
|
57
|
+
props: WebsiteProps<B>
|
|
58
|
+
): Promise<Website<B>> {
|
|
59
|
+
if (props.bindings?.ASSETS) {
|
|
60
|
+
throw new Error("ASSETS binding is reserved for internal use");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return alchemy.run(id, async () => {
|
|
64
|
+
// building the site requires a wrangler.jsonc file to start
|
|
65
|
+
// - so initialize an empty one if it doesn't exist
|
|
66
|
+
|
|
67
|
+
const cwd = path.resolve(props.cwd || process.cwd());
|
|
68
|
+
const fileName =
|
|
69
|
+
typeof props.wrangler === "boolean" ? "wrangler.jsonc" : props.wrangler;
|
|
70
|
+
const wranglerPath =
|
|
71
|
+
fileName && path.relative(cwd, path.join(cwd, fileName));
|
|
72
|
+
|
|
73
|
+
if (props.wrangler) {
|
|
74
|
+
try {
|
|
75
|
+
await fs.access(wranglerPath!);
|
|
76
|
+
} catch {
|
|
77
|
+
await fs.writeFile(
|
|
78
|
+
wranglerPath!,
|
|
79
|
+
JSON.stringify(
|
|
80
|
+
{
|
|
81
|
+
name: id,
|
|
82
|
+
main: props.main,
|
|
83
|
+
compatibility_date: new Date().toISOString().split("T")[0],
|
|
84
|
+
},
|
|
85
|
+
null,
|
|
86
|
+
2
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await Exec("build", {
|
|
93
|
+
command: props.command,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const dist =
|
|
97
|
+
typeof props.assets === "string"
|
|
98
|
+
? props.assets
|
|
99
|
+
: (props.assets?.dist ?? "dist");
|
|
100
|
+
|
|
101
|
+
const staticAssets = await Assets("assets", {
|
|
102
|
+
path: dist,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const worker = await Worker("worker", {
|
|
106
|
+
...props,
|
|
107
|
+
name: props.name ?? id,
|
|
108
|
+
entrypoint: props.main,
|
|
109
|
+
assets: {
|
|
110
|
+
html_handling: "auto-trailing-slash",
|
|
111
|
+
not_found_handling: "single-page-application",
|
|
112
|
+
run_worker_first: false,
|
|
113
|
+
...(typeof props.assets === "string" ? {} : props.assets),
|
|
114
|
+
},
|
|
115
|
+
script: props.main
|
|
116
|
+
? undefined
|
|
117
|
+
: `
|
|
118
|
+
export default {
|
|
119
|
+
async fetch(request, env) {
|
|
120
|
+
return new Response("Not Found", { status: 404 });
|
|
121
|
+
},
|
|
122
|
+
};`,
|
|
123
|
+
url: true,
|
|
124
|
+
adopt: true,
|
|
125
|
+
bindings: {
|
|
126
|
+
...props.bindings,
|
|
127
|
+
ASSETS: staticAssets,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if (props.wrangler) {
|
|
132
|
+
await WranglerJson("wrangler.jsonc", {
|
|
133
|
+
path: wranglerPath,
|
|
134
|
+
worker,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return worker as Website<B>;
|
|
139
|
+
});
|
|
140
|
+
}
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -19,6 +19,50 @@ import type { WorkerScriptMetadata } from "./worker-metadata";
|
|
|
19
19
|
import type { SingleStepMigration } from "./worker-migration";
|
|
20
20
|
import { upsertWorkflow, type Workflow } from "./workflow";
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Configuration options for static assets
|
|
24
|
+
*/
|
|
25
|
+
export interface AssetsConfig {
|
|
26
|
+
/**
|
|
27
|
+
* The contents of a _headers file (used to attach custom headers on asset responses)
|
|
28
|
+
*/
|
|
29
|
+
_headers?: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The contents of a _redirects file (used to apply redirects or proxy paths ahead of asset serving)
|
|
33
|
+
*/
|
|
34
|
+
_redirects?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Determines the redirects and rewrites of requests for HTML content
|
|
38
|
+
* @default "auto-trailing-slash"
|
|
39
|
+
*/
|
|
40
|
+
html_handling?:
|
|
41
|
+
| "auto-trailing-slash"
|
|
42
|
+
| "force-trailing-slash"
|
|
43
|
+
| "drop-trailing-slash"
|
|
44
|
+
| "none";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Determines the response when a request does not match a static asset, and there is no Worker script
|
|
48
|
+
*/
|
|
49
|
+
not_found_handling?: "none" | "404-page" | "single-page-application";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* When true, requests will always invoke the Worker script.
|
|
53
|
+
* Otherwise, attempt to serve an asset matching the request, falling back to the Worker script.
|
|
54
|
+
*/
|
|
55
|
+
run_worker_first?: boolean;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* When true and the incoming request matches an asset, that will be served instead of invoking the Worker script.
|
|
59
|
+
* When false, requests will always invoke the Worker script.
|
|
60
|
+
* @default true
|
|
61
|
+
* @deprecated
|
|
62
|
+
*/
|
|
63
|
+
serve_directly?: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
22
66
|
/**
|
|
23
67
|
* Properties for creating or updating a Worker
|
|
24
68
|
*/
|
|
@@ -110,6 +154,11 @@ export interface WorkerProps<B extends Bindings = Bindings>
|
|
|
110
154
|
* The compatibility flags for the worker
|
|
111
155
|
*/
|
|
112
156
|
compatibilityFlags?: string[];
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Configuration for static assets
|
|
160
|
+
*/
|
|
161
|
+
assets?: AssetsConfig;
|
|
113
162
|
}
|
|
114
163
|
|
|
115
164
|
/**
|
|
@@ -146,6 +195,11 @@ export interface Worker<B extends Bindings = Bindings>
|
|
|
146
195
|
*/
|
|
147
196
|
bindings: B | undefined;
|
|
148
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Configuration for static assets
|
|
200
|
+
*/
|
|
201
|
+
assets?: AssetsConfig;
|
|
202
|
+
|
|
149
203
|
// phantom property (for typeof myWorker.Env)
|
|
150
204
|
Env: {
|
|
151
205
|
[bindingName in keyof B]: Bound<B[bindingName]>;
|
|
@@ -291,7 +345,8 @@ export const Worker = Resource(
|
|
|
291
345
|
assetUploadResult = await uploadAssets(
|
|
292
346
|
api,
|
|
293
347
|
workerName,
|
|
294
|
-
assetBinding.assets
|
|
348
|
+
assetBinding.assets,
|
|
349
|
+
props.assets
|
|
295
350
|
);
|
|
296
351
|
}
|
|
297
352
|
|
|
@@ -342,6 +397,8 @@ export const Worker = Resource(
|
|
|
342
397
|
createdAt: now,
|
|
343
398
|
updatedAt: now,
|
|
344
399
|
url: workerUrl,
|
|
400
|
+
// Include assets configuration in the output
|
|
401
|
+
assets: props.assets,
|
|
345
402
|
// phantom property
|
|
346
403
|
Env: undefined!,
|
|
347
404
|
});
|
|
@@ -480,19 +537,50 @@ interface WorkerMetadata {
|
|
|
480
537
|
assets?: {
|
|
481
538
|
jwt?: string;
|
|
482
539
|
keep_assets?: boolean;
|
|
483
|
-
config?:
|
|
484
|
-
html_handling?: "auto-trailing-slash" | "none";
|
|
485
|
-
not_found_handling?: "none" | "fall-through";
|
|
486
|
-
};
|
|
540
|
+
config?: AssetsConfig;
|
|
487
541
|
};
|
|
488
542
|
}
|
|
489
543
|
|
|
490
544
|
interface AssetUploadResult {
|
|
491
545
|
completionToken: string;
|
|
492
|
-
assetConfig?:
|
|
493
|
-
|
|
494
|
-
|
|
546
|
+
assetConfig?: AssetsConfig;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Creates asset configuration object from provided config or defaults
|
|
551
|
+
*/
|
|
552
|
+
function createAssetConfig(config?: AssetsConfig): AssetsConfig {
|
|
553
|
+
const assetConfig: AssetsConfig = {
|
|
554
|
+
html_handling: "auto-trailing-slash",
|
|
495
555
|
};
|
|
556
|
+
|
|
557
|
+
if (config) {
|
|
558
|
+
if (config._headers !== undefined) {
|
|
559
|
+
assetConfig._headers = config._headers;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (config._redirects !== undefined) {
|
|
563
|
+
assetConfig._redirects = config._redirects;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (config.html_handling !== undefined) {
|
|
567
|
+
assetConfig.html_handling = config.html_handling;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (config.not_found_handling !== undefined) {
|
|
571
|
+
assetConfig.not_found_handling = config.not_found_handling;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (config.run_worker_first !== undefined) {
|
|
575
|
+
assetConfig.run_worker_first = config.run_worker_first;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (config.serve_directly !== undefined) {
|
|
579
|
+
assetConfig.serve_directly = config.serve_directly;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return assetConfig;
|
|
496
584
|
}
|
|
497
585
|
|
|
498
586
|
async function prepareWorkerMetadata<B extends Bindings>(
|
|
@@ -526,8 +614,17 @@ async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
526
614
|
jwt: assetUploadResult.completionToken,
|
|
527
615
|
};
|
|
528
616
|
|
|
617
|
+
// Initialize config from assetUploadResult if it exists
|
|
529
618
|
if (assetUploadResult.assetConfig) {
|
|
530
|
-
meta.assets.config =
|
|
619
|
+
meta.assets.config = {
|
|
620
|
+
...assetUploadResult.assetConfig,
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// If there's no config from assetUploadResult but we have props.assets,
|
|
625
|
+
// we need to create the config ourselves (this handles the case when no assets were uploaded)
|
|
626
|
+
if (!meta.assets.config && props.assets) {
|
|
627
|
+
meta.assets.config = createAssetConfig(props.assets);
|
|
531
628
|
}
|
|
532
629
|
}
|
|
533
630
|
|
|
@@ -910,13 +1007,18 @@ interface UploadResponse {
|
|
|
910
1007
|
* @param api CloudflareApi instance
|
|
911
1008
|
* @param workerName Name of the worker
|
|
912
1009
|
* @param assets Assets resource containing files to upload
|
|
1010
|
+
* @param assetConfig Configuration for the assets
|
|
913
1011
|
* @returns Completion token for the assets upload
|
|
914
1012
|
*/
|
|
915
1013
|
async function uploadAssets(
|
|
916
1014
|
api: CloudflareApi,
|
|
917
1015
|
workerName: string,
|
|
918
|
-
assets: Assets
|
|
1016
|
+
assets: Assets,
|
|
1017
|
+
assetConfig?: WorkerProps["assets"]
|
|
919
1018
|
): Promise<AssetUploadResult> {
|
|
1019
|
+
// Process the assets configuration once at the beginning
|
|
1020
|
+
const processedConfig = createAssetConfig(assetConfig);
|
|
1021
|
+
|
|
920
1022
|
// Generate the file manifest
|
|
921
1023
|
const fileMetadata: Record<string, FileMetadata> = {};
|
|
922
1024
|
|
|
@@ -949,7 +1051,10 @@ async function uploadAssets(
|
|
|
949
1051
|
|
|
950
1052
|
// If there are no buckets, assets are already uploaded or empty
|
|
951
1053
|
if (!sessionData.result.buckets || sessionData.result.buckets.length === 0) {
|
|
952
|
-
return {
|
|
1054
|
+
return {
|
|
1055
|
+
completionToken: sessionData.result.jwt,
|
|
1056
|
+
assetConfig: processedConfig,
|
|
1057
|
+
};
|
|
953
1058
|
}
|
|
954
1059
|
|
|
955
1060
|
// Upload the files in batches as specified by the API
|
|
@@ -1013,12 +1118,10 @@ async function uploadAssets(
|
|
|
1013
1118
|
}
|
|
1014
1119
|
}
|
|
1015
1120
|
|
|
1016
|
-
// Return the final completion token
|
|
1121
|
+
// Return the final completion token with asset configuration
|
|
1017
1122
|
return {
|
|
1018
1123
|
completionToken,
|
|
1019
|
-
assetConfig:
|
|
1020
|
-
html_handling: "auto-trailing-slash",
|
|
1021
|
-
},
|
|
1124
|
+
assetConfig: processedConfig,
|
|
1022
1125
|
};
|
|
1023
1126
|
}
|
|
1024
1127
|
|
package/src/env.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface Env {
|
|
2
|
+
[key: string]: Promise<string>;
|
|
3
|
+
<T = string>(name: string, value?: T | undefined, error?: string): Promise<T>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const env = new Proxy(_env, {
|
|
7
|
+
get: (_, name: string) => _env(name),
|
|
8
|
+
apply: (_, __, args: [string, any?, string?]) => _env(...args),
|
|
9
|
+
}) as Env;
|
|
10
|
+
|
|
11
|
+
async function _env<T = string>(
|
|
12
|
+
name: string,
|
|
13
|
+
value?: T | undefined,
|
|
14
|
+
error?: string
|
|
15
|
+
): Promise<T> {
|
|
16
|
+
if (value !== undefined) {
|
|
17
|
+
return value;
|
|
18
|
+
} else if (typeof process !== undefined) {
|
|
19
|
+
// we are in a node environment
|
|
20
|
+
return process.env[name]! as T;
|
|
21
|
+
} else {
|
|
22
|
+
// we are in a browser environment
|
|
23
|
+
try {
|
|
24
|
+
const { env } = await import("cloudflare:workers");
|
|
25
|
+
if (name in env) {
|
|
26
|
+
return env[name as keyof typeof env];
|
|
27
|
+
}
|
|
28
|
+
} catch (error) {}
|
|
29
|
+
}
|
|
30
|
+
throw new Error(error ?? `Environment variable ${name} is not set`);
|
|
31
|
+
}
|
|
@@ -118,7 +118,7 @@ async function generateProviderDocs({
|
|
|
118
118
|
reasoningEffort: "high",
|
|
119
119
|
},
|
|
120
120
|
},
|
|
121
|
-
freeze:
|
|
121
|
+
freeze: false,
|
|
122
122
|
temperature: 0.1,
|
|
123
123
|
schema: type({
|
|
124
124
|
groups: type({
|
|
@@ -178,7 +178,7 @@ async function generateProviderDocs({
|
|
|
178
178
|
providerDocsDir,
|
|
179
179
|
`${g.filename.replace(".ts", "").replace(".md", "")}.md`
|
|
180
180
|
),
|
|
181
|
-
freeze:
|
|
181
|
+
freeze: false,
|
|
182
182
|
model: {
|
|
183
183
|
id: "claude-3-5-sonnet-latest",
|
|
184
184
|
provider: "anthropic",
|
package/src/secret.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { alchemy } from "./alchemy";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Internal wrapper for sensitive values like API keys and credentials.
|
|
3
5
|
* When stored in alchemy state files, the value is automatically encrypted
|
|
@@ -85,12 +87,21 @@ export function secret<S extends string | undefined>(unencrypted: S): Secret {
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
export namespace secret {
|
|
88
|
-
export
|
|
90
|
+
export interface Env {
|
|
91
|
+
[key: string]: Promise<Secret>;
|
|
92
|
+
(name: string, value?: string, error?: string): Promise<Secret>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const env = new Proxy(_env, {
|
|
96
|
+
get: (_, name: string) => _env(name),
|
|
97
|
+
apply: (_, __, args: [string, any?, string?]) => _env(...args),
|
|
98
|
+
}) as Env;
|
|
99
|
+
|
|
100
|
+
async function _env(
|
|
89
101
|
name: string,
|
|
90
102
|
value?: string,
|
|
91
103
|
error?: string
|
|
92
104
|
): Promise<Secret> {
|
|
93
|
-
const alchemy = await import("./alchemy");
|
|
94
105
|
const result = await alchemy.env(name, value, error);
|
|
95
106
|
if (typeof result === "string") {
|
|
96
107
|
return secret(result);
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { alchemy } from "../alchemy";
|
|
4
|
-
import { Exec } from "../os";
|
|
5
|
-
import { Assets } from "./assets";
|
|
6
|
-
import { Worker } from "./worker";
|
|
7
|
-
import { WranglerJson } from "./wrangler.json";
|
|
8
|
-
export async function ViteSite(id, props) {
|
|
9
|
-
if (props.bindings?.ASSETS) {
|
|
10
|
-
throw new Error("ASSETS binding is reserved for internal use");
|
|
11
|
-
}
|
|
12
|
-
// @ts-ignore - we know the types are correct
|
|
13
|
-
return await alchemy.run(id, async () => {
|
|
14
|
-
// Create minimal wrangler.jsonc if it doesn't exist
|
|
15
|
-
// `building the site requires a wrangler.jsonc file to start - so initialize an empty one if it doesn't exist`
|
|
16
|
-
const cwd = props.cwd || process.cwd();
|
|
17
|
-
const wranglerPath = path.join(cwd, "wrangler.jsonc");
|
|
18
|
-
try {
|
|
19
|
-
await fs.access(wranglerPath);
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
await fs.writeFile(wranglerPath, JSON.stringify({
|
|
23
|
-
name: id,
|
|
24
|
-
main: props.main,
|
|
25
|
-
compatibility_date: new Date().toISOString().split("T")[0],
|
|
26
|
-
}, null, 2));
|
|
27
|
-
}
|
|
28
|
-
await Exec("build", {
|
|
29
|
-
command: props.command,
|
|
30
|
-
});
|
|
31
|
-
const staticAssets = await Assets("assets", {
|
|
32
|
-
path: props.assets ?? "./dist",
|
|
33
|
-
});
|
|
34
|
-
const worker = await Worker("worker", {
|
|
35
|
-
name: props.name ?? id,
|
|
36
|
-
entrypoint: props.main,
|
|
37
|
-
script: props.main
|
|
38
|
-
? undefined
|
|
39
|
-
: `
|
|
40
|
-
export default {
|
|
41
|
-
async fetch(request, env) {
|
|
42
|
-
return env.ASSETS.fetch(request);
|
|
43
|
-
},
|
|
44
|
-
};`,
|
|
45
|
-
url: true,
|
|
46
|
-
adopt: true,
|
|
47
|
-
bindings: {
|
|
48
|
-
...props.bindings,
|
|
49
|
-
ASSETS: staticAssets,
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
await WranglerJson("wrangler.jsonc", {
|
|
53
|
-
worker,
|
|
54
|
-
});
|
|
55
|
-
return worker;
|
|
56
|
-
});
|
|
57
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { alchemy } from "../alchemy";
|
|
4
|
-
import { Exec } from "../os";
|
|
5
|
-
import { Assets } from "./assets";
|
|
6
|
-
import type { Bindings } from "./bindings";
|
|
7
|
-
import { Worker } from "./worker";
|
|
8
|
-
import { WranglerJson } from "./wrangler.json";
|
|
9
|
-
|
|
10
|
-
export interface ViteSiteProps<B extends Bindings> {
|
|
11
|
-
/**
|
|
12
|
-
* The command to run to build the site
|
|
13
|
-
*/
|
|
14
|
-
command: string;
|
|
15
|
-
/**
|
|
16
|
-
* The name of the worker
|
|
17
|
-
*
|
|
18
|
-
* @default id
|
|
19
|
-
*/
|
|
20
|
-
name?: string;
|
|
21
|
-
/**
|
|
22
|
-
* The entrypoint to your server
|
|
23
|
-
*
|
|
24
|
-
* @default - a simple server that serves static assets is generated
|
|
25
|
-
*/
|
|
26
|
-
main?: string;
|
|
27
|
-
/**
|
|
28
|
-
* The directory containing your static assets
|
|
29
|
-
*
|
|
30
|
-
* @default "./dist"
|
|
31
|
-
*/
|
|
32
|
-
assets?: string;
|
|
33
|
-
/**
|
|
34
|
-
* The bindings to pass to the worker
|
|
35
|
-
*/
|
|
36
|
-
bindings?: B;
|
|
37
|
-
/**
|
|
38
|
-
* @default process.cwd()
|
|
39
|
-
*/
|
|
40
|
-
cwd?: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type ViteSite<B extends Bindings> = Promise<
|
|
44
|
-
// don't allow the ASSETS to be overriden
|
|
45
|
-
B extends { ASSETS: any } ? never : Worker<B & { ASSETS: Assets }>
|
|
46
|
-
>;
|
|
47
|
-
|
|
48
|
-
export async function ViteSite<B extends Bindings>(
|
|
49
|
-
id: string,
|
|
50
|
-
props: ViteSiteProps<B>
|
|
51
|
-
): ViteSite<B> {
|
|
52
|
-
if (props.bindings?.ASSETS) {
|
|
53
|
-
throw new Error("ASSETS binding is reserved for internal use");
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// @ts-ignore - we know the types are correct
|
|
57
|
-
return await alchemy.run(id, async () => {
|
|
58
|
-
// Create minimal wrangler.jsonc if it doesn't exist
|
|
59
|
-
|
|
60
|
-
// `building the site requires a wrangler.jsonc file to start - so initialize an empty one if it doesn't exist`
|
|
61
|
-
const cwd = props.cwd || process.cwd();
|
|
62
|
-
const wranglerPath = path.join(cwd, "wrangler.jsonc");
|
|
63
|
-
try {
|
|
64
|
-
await fs.access(wranglerPath);
|
|
65
|
-
} catch {
|
|
66
|
-
await fs.writeFile(
|
|
67
|
-
wranglerPath,
|
|
68
|
-
JSON.stringify(
|
|
69
|
-
{
|
|
70
|
-
name: id,
|
|
71
|
-
main: props.main,
|
|
72
|
-
compatibility_date: new Date().toISOString().split("T")[0],
|
|
73
|
-
},
|
|
74
|
-
null,
|
|
75
|
-
2
|
|
76
|
-
)
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
await Exec("build", {
|
|
81
|
-
command: props.command,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
const staticAssets = await Assets("assets", {
|
|
85
|
-
path: props.assets ?? "./dist",
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const worker = await Worker("worker", {
|
|
89
|
-
name: props.name ?? id,
|
|
90
|
-
entrypoint: props.main,
|
|
91
|
-
script: props.main
|
|
92
|
-
? undefined
|
|
93
|
-
: `
|
|
94
|
-
export default {
|
|
95
|
-
async fetch(request, env) {
|
|
96
|
-
return env.ASSETS.fetch(request);
|
|
97
|
-
},
|
|
98
|
-
};`,
|
|
99
|
-
url: true,
|
|
100
|
-
adopt: true,
|
|
101
|
-
bindings: {
|
|
102
|
-
...props.bindings,
|
|
103
|
-
ASSETS: staticAssets,
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
await WranglerJson("wrangler.jsonc", {
|
|
108
|
-
worker,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
return worker;
|
|
112
|
-
});
|
|
113
|
-
}
|