alchemy 0.68.0 → 0.69.1
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/bin/alchemy.js +32 -33
- package/bin/commands/init.ts +41 -55
- package/lib/aws/control/properties.d.ts +64 -0
- package/lib/aws/control/properties.d.ts.map +1 -1
- package/lib/aws/control/properties.js +554 -39
- package/lib/aws/control/properties.js.map +1 -1
- package/lib/aws/control/resource.d.ts.map +1 -1
- package/lib/aws/control/resource.js +33 -0
- package/lib/aws/control/resource.js.map +1 -1
- package/lib/aws/control/types.d.ts +5451 -1587
- package/lib/aws/control/types.d.ts.map +1 -1
- package/lib/aws/control/types.js.map +1 -1
- package/lib/aws/control/update-types.d.ts +12913 -0
- package/lib/aws/control/update-types.d.ts.map +1 -0
- package/lib/aws/control/update-types.js +12916 -0
- package/lib/aws/control/update-types.js.map +1 -0
- package/lib/cloudflare/api.d.ts +1 -1
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +9 -10
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/astro/astro.d.ts.map +1 -1
- package/lib/cloudflare/astro/astro.js +3 -3
- package/lib/cloudflare/astro/astro.js.map +1 -1
- package/lib/cloudflare/bucket-object.d.ts +86 -0
- package/lib/cloudflare/bucket-object.d.ts.map +1 -0
- package/lib/cloudflare/bucket-object.js +67 -0
- package/lib/cloudflare/bucket-object.js.map +1 -0
- package/lib/cloudflare/bucket.d.ts +36 -6
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +154 -18
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
- package/lib/cloudflare/compatibility-date.gen.js +1 -1
- package/lib/cloudflare/container.d.ts.map +1 -1
- package/lib/cloudflare/container.js +2 -2
- package/lib/cloudflare/container.js.map +1 -1
- package/lib/cloudflare/index.d.ts +1 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +1 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +44 -12
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
- package/lib/cloudflare/next.js.map +1 -1
- package/lib/cloudflare/nuxt/nuxt.d.ts.map +1 -1
- package/lib/cloudflare/nuxt/nuxt.js +3 -3
- package/lib/cloudflare/nuxt/nuxt.js.map +1 -1
- package/lib/cloudflare/tanstack-start/plugin.d.ts +2 -15
- package/lib/cloudflare/tanstack-start/plugin.d.ts.map +1 -1
- package/lib/cloudflare/tanstack-start/plugin.js +5 -25
- package/lib/cloudflare/tanstack-start/plugin.js.map +1 -1
- package/lib/cloudflare/tanstack-start/tanstack-start.d.ts.map +1 -1
- package/lib/cloudflare/tanstack-start/tanstack-start.js +15 -4
- package/lib/cloudflare/tanstack-start/tanstack-start.js.map +1 -1
- package/lib/cloudflare/vite/vite.d.ts.map +1 -1
- package/lib/cloudflare/vite/vite.js +3 -3
- package/lib/cloudflare/vite/vite.js.map +1 -1
- package/lib/cloudflare/website.d.ts +7 -1
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +20 -1
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts +4 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +2 -0
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +6 -0
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +1 -0
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +34 -9
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +12 -1
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/docker/api.d.ts +22 -1
- package/lib/docker/api.d.ts.map +1 -1
- package/lib/docker/api.js +64 -2
- package/lib/docker/api.js.map +1 -1
- package/lib/docker/container.d.ts +88 -0
- package/lib/docker/container.d.ts.map +1 -1
- package/lib/docker/container.js +36 -0
- package/lib/docker/container.js.map +1 -1
- package/lib/llms.cloudflare.txt +2 -2
- package/lib/neon/branch.d.ts +19 -3
- package/lib/neon/branch.d.ts.map +1 -1
- package/lib/neon/branch.js +4 -4
- package/lib/neon/branch.js.map +1 -1
- package/lib/neon/project.d.ts +17 -1
- package/lib/neon/project.d.ts.map +1 -1
- package/lib/neon/project.js +4 -4
- package/lib/neon/project.js.map +1 -1
- package/lib/serde.d.ts +3 -0
- package/lib/serde.d.ts.map +1 -1
- package/lib/serde.js +40 -0
- package/lib/serde.js.map +1 -1
- package/lib/test/bun.js.map +1 -1
- package/package.json +1 -1
- package/src/aws/control/properties.ts +554 -39
- package/src/aws/control/resource.ts +58 -0
- package/src/aws/control/types.ts +85281 -84914
- package/src/aws/control/update-types.ts +12917 -0
- package/src/cloudflare/api.ts +9 -10
- package/src/cloudflare/astro/astro.ts +8 -3
- package/src/cloudflare/bucket-object.ts +127 -0
- package/src/cloudflare/bucket.ts +218 -18
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/container.ts +4 -5
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +57 -13
- package/src/cloudflare/next.ts +2 -2
- package/src/cloudflare/nuxt/nuxt.ts +8 -3
- package/src/cloudflare/tanstack-start/plugin.ts +7 -28
- package/src/cloudflare/tanstack-start/tanstack-start.ts +15 -4
- package/src/cloudflare/vite/vite.ts +8 -3
- package/src/cloudflare/website.ts +29 -2
- package/src/cloudflare/worker-metadata.ts +6 -1
- package/src/cloudflare/worker.ts +7 -0
- package/src/cloudflare/wrangler.json.ts +47 -18
- package/src/docker/api.ts +86 -2
- package/src/docker/container.ts +97 -0
- package/src/llms.cloudflare.txt +2 -2
- package/src/neon/branch.ts +6 -5
- package/src/neon/project.ts +4 -4
- package/src/serde.ts +50 -0
- package/src/test/bun.ts +1 -1
- package/templates/rwsdk/.cursor/rules/alchemy_cloudflare.mdc +2 -2
- package/templates/tanstack-start/.cta.json +13 -0
- package/templates/tanstack-start/README.md +267 -49
- package/templates/tanstack-start/_gitignore +9 -18
- package/templates/tanstack-start/alchemy.run.ts +2 -2
- package/templates/tanstack-start/package.json +24 -19
- package/templates/tanstack-start/public/favicon.ico +0 -0
- package/templates/tanstack-start/public/logo192.png +0 -0
- package/templates/tanstack-start/public/logo512.png +0 -0
- package/templates/tanstack-start/public/manifest.json +25 -0
- package/templates/tanstack-start/public/robots.txt +3 -0
- package/templates/tanstack-start/src/components/Header.tsx +21 -0
- package/templates/tanstack-start/src/logo.svg +12 -0
- package/templates/tanstack-start/src/router.tsx +7 -16
- package/templates/tanstack-start/src/routes/__root.tsx +13 -114
- package/templates/tanstack-start/src/routes/api.demo-names.ts +15 -0
- package/templates/tanstack-start/src/routes/demo.start.api-request.tsx +44 -0
- package/templates/tanstack-start/src/routes/demo.start.server-funcs.tsx +97 -0
- package/templates/tanstack-start/src/routes/index.tsx +31 -4
- package/templates/tanstack-start/src/styles.css +15 -0
- package/templates/tanstack-start/tsconfig.json +19 -22
- package/templates/tanstack-start/types/env.d.ts +2 -2
- package/templates/tanstack-start/vite.config.ts +16 -25
- package/workers/tunnel-proxy.js +1 -1
- package/templates/tanstack-start/_prettierignore +0 -4
- package/templates/tanstack-start/public/android-chrome-192x192.png +0 -0
- package/templates/tanstack-start/public/android-chrome-512x512.png +0 -0
- package/templates/tanstack-start/public/apple-touch-icon.png +0 -0
- package/templates/tanstack-start/public/favicon-16x16.png +0 -0
- package/templates/tanstack-start/public/favicon-32x32.png +0 -0
- package/templates/tanstack-start/public/favicon.png +0 -0
- package/templates/tanstack-start/public/site.webmanifest +0 -19
- package/templates/tanstack-start/src/components/DefaultCatchBoundary.tsx +0 -53
- package/templates/tanstack-start/src/components/NotFound.tsx +0 -25
- package/templates/tanstack-start/src/components/PostError.tsx +0 -5
- package/templates/tanstack-start/src/components/UserError.tsx +0 -5
- package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-a.tsx +0 -10
- package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout/route-b.tsx +0 -10
- package/templates/tanstack-start/src/routes/_pathlessLayout/_nested-layout.tsx +0 -34
- package/templates/tanstack-start/src/routes/_pathlessLayout.tsx +0 -16
- package/templates/tanstack-start/src/routes/api/users.$userId.ts +0 -28
- package/templates/tanstack-start/src/routes/api/users.ts +0 -59
- package/templates/tanstack-start/src/routes/customScript[.]js.ts +0 -10
- package/templates/tanstack-start/src/routes/deferred.tsx +0 -62
- package/templates/tanstack-start/src/routes/posts.$postId.tsx +0 -34
- package/templates/tanstack-start/src/routes/posts.index.tsx +0 -8
- package/templates/tanstack-start/src/routes/posts.tsx +0 -38
- package/templates/tanstack-start/src/routes/posts_.$postId.deep.tsx +0 -29
- package/templates/tanstack-start/src/routes/redirect.tsx +0 -9
- package/templates/tanstack-start/src/routes/users.$userId.tsx +0 -44
- package/templates/tanstack-start/src/routes/users.index.tsx +0 -18
- package/templates/tanstack-start/src/routes/users.tsx +0 -49
- package/templates/tanstack-start/src/styles/app.css +0 -12
- package/templates/tanstack-start/src/utils/loggingMiddleware.tsx +0 -41
- package/templates/tanstack-start/src/utils/posts.tsx +0 -40
- package/templates/tanstack-start/src/utils/seo.ts +0 -33
- package/templates/tanstack-start/src/utils/users.tsx +0 -5
|
@@ -31,10 +31,7 @@ export async function createMiniflareWorkerProxy(options: {
|
|
|
31
31
|
});
|
|
32
32
|
server.on("request", async (req, res) => {
|
|
33
33
|
try {
|
|
34
|
-
const
|
|
35
|
-
const name = options.getWorkerName(request);
|
|
36
|
-
const worker = await options.miniflare.getWorker(name);
|
|
37
|
-
const response = await worker.fetch(request);
|
|
34
|
+
const response = await handleFetch(req);
|
|
38
35
|
writeMiniflareResponseToNode(response, res);
|
|
39
36
|
} catch (error) {
|
|
40
37
|
console.error(error);
|
|
@@ -45,20 +42,67 @@ export async function createMiniflareWorkerProxy(options: {
|
|
|
45
42
|
}
|
|
46
43
|
});
|
|
47
44
|
|
|
48
|
-
const
|
|
45
|
+
const handleFetch = async (
|
|
46
|
+
req: http.IncomingMessage,
|
|
47
|
+
): Promise<miniflare.Response> => {
|
|
48
|
+
const { request, url } = toMiniflareRequest(req);
|
|
49
|
+
const name = options.getWorkerName(request);
|
|
50
|
+
const worker = await options.miniflare.getWorker(name);
|
|
51
|
+
|
|
52
|
+
// Handle scheduled events
|
|
53
|
+
// https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/#background
|
|
54
|
+
// https://github.com/cloudflare/workers-sdk/blob/7d53b9ab6b370944b7934ad51ebef43160c3c775/packages/wrangler/templates/middleware/middleware-scheduled.ts#L6
|
|
55
|
+
if (url.pathname === "/__scheduled") {
|
|
56
|
+
await worker.scheduled({
|
|
57
|
+
scheduledTime: new Date(),
|
|
58
|
+
cron: url.searchParams.get("cron") ?? undefined,
|
|
59
|
+
});
|
|
60
|
+
return new miniflare.Response("Ran scheduled function");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const result = await worker
|
|
64
|
+
.fetch(request)
|
|
65
|
+
.then((response) => ({ success: true, response }) as const)
|
|
66
|
+
.catch((error) => ({ success: false, error }) as const);
|
|
67
|
+
|
|
68
|
+
// If you open the `/__scheduled` page in a browser, the browser will automatically make a request to `/favicon.ico`.
|
|
69
|
+
// For scheduled Workers _without_ a fetch handler, this will result in an unhelpful error that we don't need to log.
|
|
70
|
+
// To avoid this, inject a 404 response to favicon.ico loads on the `/__scheduled` page
|
|
71
|
+
if (
|
|
72
|
+
request.headers.get("referer")?.endsWith("/__scheduled") &&
|
|
73
|
+
url.pathname === "/favicon.ico" &&
|
|
74
|
+
!result.success
|
|
75
|
+
) {
|
|
76
|
+
return new miniflare.Response(null, { status: 404 });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!result.success) {
|
|
80
|
+
throw result.error;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return result.response;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const toMiniflareRequest = (
|
|
87
|
+
req: http.IncomingMessage,
|
|
88
|
+
): { request: miniflare.Request; url: URL } => {
|
|
49
89
|
const info = parseIncomingMessage(req);
|
|
50
90
|
options.transformRequest?.(info);
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
91
|
+
return {
|
|
92
|
+
request: new miniflare.Request(info.url, {
|
|
93
|
+
method: info.method,
|
|
94
|
+
headers: info.headers,
|
|
95
|
+
body: info.body,
|
|
96
|
+
redirect: info.redirect,
|
|
97
|
+
duplex: info.duplex,
|
|
98
|
+
}),
|
|
99
|
+
url: info.url,
|
|
100
|
+
};
|
|
58
101
|
};
|
|
59
102
|
|
|
60
103
|
const createServerWebSocket = async (req: http.IncomingMessage) => {
|
|
61
|
-
const
|
|
104
|
+
const { request } = toMiniflareRequest(req);
|
|
105
|
+
const name = options.getWorkerName(request);
|
|
62
106
|
const target = await options.miniflare.unsafeGetDirectURL(name);
|
|
63
107
|
const url = new URL(req.url ?? "/", target);
|
|
64
108
|
url.protocol = url.protocol.replace("http", "ws");
|
package/src/cloudflare/next.ts
CHANGED
|
@@ -113,7 +113,7 @@ const normalizeCommand = (
|
|
|
113
113
|
command: string;
|
|
114
114
|
env: Record<string, string>;
|
|
115
115
|
},
|
|
116
|
-
)
|
|
116
|
+
) => {
|
|
117
117
|
return {
|
|
118
118
|
command:
|
|
119
119
|
typeof input === "string" ? input : (input?.command ?? defaults.command),
|
|
@@ -122,5 +122,5 @@ const normalizeCommand = (
|
|
|
122
122
|
...(typeof input === "object" ? (input?.env ?? {}) : {}),
|
|
123
123
|
},
|
|
124
124
|
memoize: typeof input === "object" ? input?.memoize : undefined,
|
|
125
|
-
};
|
|
125
|
+
} satisfies WebsiteProps<any>["build"];
|
|
126
126
|
};
|
|
@@ -2,7 +2,12 @@ import path from "node:path";
|
|
|
2
2
|
import { getPackageManagerRunner } from "../../util/detect-package-manager.ts";
|
|
3
3
|
import type { Assets } from "../assets.ts";
|
|
4
4
|
import type { Bindings } from "../bindings.ts";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
spreadBuildProps,
|
|
7
|
+
spreadDevProps,
|
|
8
|
+
Website,
|
|
9
|
+
type WebsiteProps,
|
|
10
|
+
} from "../website.ts";
|
|
6
11
|
import type { Worker } from "../worker.ts";
|
|
7
12
|
|
|
8
13
|
/**
|
|
@@ -55,8 +60,8 @@ export async function Nuxt<B extends Bindings>(
|
|
|
55
60
|
return await Website(id, {
|
|
56
61
|
...props,
|
|
57
62
|
noBundle: props?.noBundle ?? true,
|
|
58
|
-
build: props
|
|
59
|
-
dev: props
|
|
63
|
+
build: spreadBuildProps(props, `${runner} nuxt build`),
|
|
64
|
+
dev: spreadDevProps(props, `${runner} nuxt dev`),
|
|
60
65
|
compatibility: "node",
|
|
61
66
|
// Default entry point for cloudflare-module preset
|
|
62
67
|
entrypoint: props?.entrypoint ?? "./.output/server/index.mjs",
|
|
@@ -1,30 +1,9 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
1
|
+
import type { PluginConfig } from "@cloudflare/vite-plugin";
|
|
2
|
+
import alchemyVite from "../vite/plugin.ts";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* @see https://developers.cloudflare.com/workers/framework-guides/web-apps/tanstack/#using-cloudflare-bindings
|
|
10
|
-
*/
|
|
11
|
-
export default function alchemy(options: GetPlatformProxyOptions = {}) {
|
|
12
|
-
return {
|
|
13
|
-
name: "cloudflare-workers-dev-shim",
|
|
14
|
-
apply: "serve", // dev‑only
|
|
15
|
-
enforce: "pre",
|
|
16
|
-
resolveId(id: string) {
|
|
17
|
-
if (id === "cloudflare:workers") return id; // tell Vite we handled it
|
|
18
|
-
},
|
|
19
|
-
load(id: string) {
|
|
20
|
-
if (id === "cloudflare:workers") {
|
|
21
|
-
return dedent`
|
|
22
|
-
import { getCloudflareEnvProxy } from "alchemy/cloudflare";
|
|
23
|
-
export const env = await getCloudflareEnvProxy(${JSON.stringify(
|
|
24
|
-
options,
|
|
25
|
-
)});
|
|
26
|
-
`;
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
} as const;
|
|
4
|
+
export default function alchemy(options: PluginConfig = {}) {
|
|
5
|
+
return alchemyVite({
|
|
6
|
+
viteEnvironment: { name: "ssr" },
|
|
7
|
+
...options,
|
|
8
|
+
});
|
|
30
9
|
}
|
|
@@ -15,10 +15,21 @@ export async function TanStackStart<B extends Bindings>(
|
|
|
15
15
|
props?: Partial<TanStackStartProps<B>>,
|
|
16
16
|
): Promise<TanStackStart<B>> {
|
|
17
17
|
return await Vite(id, {
|
|
18
|
-
|
|
18
|
+
entrypoint: "dist/server/index.js",
|
|
19
|
+
assets: "dist/client",
|
|
20
|
+
compatibility: "node",
|
|
19
21
|
noBundle: true,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
spa: false,
|
|
23
|
+
...props,
|
|
24
|
+
wrangler: {
|
|
25
|
+
...props?.wrangler,
|
|
26
|
+
transform: async (spec) => {
|
|
27
|
+
const transformed = (await props?.wrangler?.transform?.(spec)) ?? spec;
|
|
28
|
+
if (!props?.wrangler?.main) {
|
|
29
|
+
transformed.main = "@tanstack/react-start/server-entry";
|
|
30
|
+
}
|
|
31
|
+
return transformed;
|
|
32
|
+
},
|
|
33
|
+
},
|
|
23
34
|
});
|
|
24
35
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { getPackageManagerRunner } from "../../util/detect-package-manager.ts";
|
|
2
2
|
import type { Assets } from "../assets.ts";
|
|
3
3
|
import type { Bindings } from "../bindings.ts";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
spreadBuildProps,
|
|
6
|
+
spreadDevProps,
|
|
7
|
+
Website,
|
|
8
|
+
type WebsiteProps,
|
|
9
|
+
} from "../website.ts";
|
|
5
10
|
import type { Worker } from "../worker.ts";
|
|
6
11
|
|
|
7
12
|
export interface ViteProps<B extends Bindings> extends WebsiteProps<B> {}
|
|
@@ -27,7 +32,7 @@ export async function Vite<B extends Bindings>(
|
|
|
27
32
|
props.assets?.directory ??
|
|
28
33
|
(props.entrypoint || props.script ? "dist/client" : "dist"),
|
|
29
34
|
},
|
|
30
|
-
build: props
|
|
31
|
-
dev: props
|
|
35
|
+
build: spreadBuildProps(props, `${runner} vite build`),
|
|
36
|
+
dev: spreadDevProps(props, `${runner} vite dev`),
|
|
32
37
|
});
|
|
33
38
|
}
|
|
@@ -27,7 +27,7 @@ export interface WebsiteProps<B extends Bindings>
|
|
|
27
27
|
/**
|
|
28
28
|
* The command to run to build the site
|
|
29
29
|
*/
|
|
30
|
-
command
|
|
30
|
+
command?: string;
|
|
31
31
|
/**
|
|
32
32
|
* Additional environment variables to set when running the build command
|
|
33
33
|
*/
|
|
@@ -287,7 +287,7 @@ export async function Website<B extends Bindings>(
|
|
|
287
287
|
|
|
288
288
|
const scope = Scope.current;
|
|
289
289
|
|
|
290
|
-
if (build && !scope.local) {
|
|
290
|
+
if (build?.command && !scope.local) {
|
|
291
291
|
await Exec(`${id}-build`, {
|
|
292
292
|
cwd: path.relative(process.cwd(), paths.cwd),
|
|
293
293
|
command: build.command,
|
|
@@ -334,6 +334,7 @@ export async function Website<B extends Bindings>(
|
|
|
334
334
|
// NOTE: we must set this to ensure the user does not accidentally set `NODE_ENV=production`
|
|
335
335
|
// which breaks `vite dev` (it won't, for example, re-write `process.env.TSS_APP_BASE` in the `.js` client side bundle)
|
|
336
336
|
NODE_ENV: "development",
|
|
337
|
+
ALCHEMY_ROOT: Scope.current.rootDir,
|
|
337
338
|
},
|
|
338
339
|
});
|
|
339
340
|
}
|
|
@@ -353,3 +354,29 @@ export async function Website<B extends Bindings>(
|
|
|
353
354
|
dev: url ? { url } : undefined,
|
|
354
355
|
})) as Website<B>;
|
|
355
356
|
}
|
|
357
|
+
|
|
358
|
+
export const spreadBuildProps = (
|
|
359
|
+
props: { build?: WebsiteProps<Bindings>["build"] } | undefined,
|
|
360
|
+
defaultCommand: string,
|
|
361
|
+
): WebsiteProps<Bindings>["build"] => {
|
|
362
|
+
if (typeof props?.build === "object") {
|
|
363
|
+
return {
|
|
364
|
+
...props.build,
|
|
365
|
+
command: props.build.command ?? defaultCommand,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
return props?.build ?? defaultCommand;
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
export const spreadDevProps = (
|
|
372
|
+
props: { dev?: WebsiteProps<Bindings>["dev"] } | undefined,
|
|
373
|
+
defaultCommand: string,
|
|
374
|
+
): WebsiteProps<Bindings>["dev"] => {
|
|
375
|
+
if (typeof props?.dev === "object") {
|
|
376
|
+
return {
|
|
377
|
+
...props.dev,
|
|
378
|
+
command: props.dev.command ?? defaultCommand,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
return props?.dev ?? defaultCommand;
|
|
382
|
+
};
|
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
MultiStepMigration,
|
|
19
19
|
SingleStepMigration,
|
|
20
20
|
} from "./worker-migration.ts";
|
|
21
|
-
import type
|
|
21
|
+
import { isWorker, type AssetsConfig, type WorkerProps } from "./worker.ts";
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Metadata returned by Cloudflare API for a worker script
|
|
@@ -175,6 +175,7 @@ export interface WorkerDefaultEnvironment extends WorkerEnvironment {
|
|
|
175
175
|
script: WorkerScriptInfo;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
//? (jacob): why is this not using BaseWorkerProps?
|
|
178
179
|
export interface WorkerMetadata {
|
|
179
180
|
compatibility_date: string;
|
|
180
181
|
compatibility_flags?: string[];
|
|
@@ -202,6 +203,7 @@ export interface WorkerMetadata {
|
|
|
202
203
|
limits?: {
|
|
203
204
|
cpu_ms?: number;
|
|
204
205
|
};
|
|
206
|
+
tail_consumers?: Array<Worker | { service: string }>;
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
export async function prepareWorkerMetadata(
|
|
@@ -316,6 +318,9 @@ export async function prepareWorkerMetadata(
|
|
|
316
318
|
const meta: WorkerMetadata = {
|
|
317
319
|
compatibility_date: props.compatibilityDate,
|
|
318
320
|
compatibility_flags: props.compatibilityFlags,
|
|
321
|
+
tail_consumers: props.tailConsumers?.map((consumer) =>
|
|
322
|
+
isWorker(consumer) ? { service: consumer.name } : consumer,
|
|
323
|
+
),
|
|
319
324
|
bindings: [],
|
|
320
325
|
observability: {
|
|
321
326
|
enabled: props.observability?.enabled !== false,
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -375,6 +375,11 @@ export interface BaseWorkerProps<
|
|
|
375
375
|
*/
|
|
376
376
|
cpu_ms?: number;
|
|
377
377
|
};
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Tail consumers that will receive execution logs from this worker
|
|
381
|
+
*/
|
|
382
|
+
tailConsumers?: Array<Worker | { service: string }>;
|
|
378
383
|
}
|
|
379
384
|
|
|
380
385
|
export interface InlineWorkerProps<
|
|
@@ -1018,6 +1023,7 @@ const _Worker = Resource(
|
|
|
1018
1023
|
props.crons?.map((cron) => ({ cron })) ?? [],
|
|
1019
1024
|
);
|
|
1020
1025
|
}
|
|
1026
|
+
|
|
1021
1027
|
await Promise.all(
|
|
1022
1028
|
options.workflows.map((workflow) =>
|
|
1023
1029
|
upsertWorkflow(api, {
|
|
@@ -1063,6 +1069,7 @@ const _Worker = Resource(
|
|
|
1063
1069
|
url: subdomain?.url,
|
|
1064
1070
|
assets: props.assets,
|
|
1065
1071
|
crons: props.crons,
|
|
1072
|
+
tailConsumers: props.tailConsumers,
|
|
1066
1073
|
routes,
|
|
1067
1074
|
domains,
|
|
1068
1075
|
namespace: props.namespace,
|
|
@@ -11,7 +11,7 @@ import type { DurableObjectNamespace } from "./durable-object-namespace.ts";
|
|
|
11
11
|
import type { EventSource } from "./event-source.ts";
|
|
12
12
|
import { isQueueEventSource } from "./event-source.ts";
|
|
13
13
|
import { isQueue } from "./queue.ts";
|
|
14
|
-
import type
|
|
14
|
+
import { isWorker, type Worker, type WorkerProps } from "./worker.ts";
|
|
15
15
|
|
|
16
16
|
type WranglerJsonRateLimit = Omit<WorkerBindingRateLimit, "type"> & {
|
|
17
17
|
type: "rate_limit";
|
|
@@ -182,6 +182,15 @@ export async function WranglerJson(
|
|
|
182
182
|
spec.vars = { ...worker.env };
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
if (worker.tailConsumers && worker.tailConsumers.length > 0) {
|
|
186
|
+
spec.tail_consumers = worker.tailConsumers.map((consumer) => {
|
|
187
|
+
if (isWorker(consumer)) {
|
|
188
|
+
return { service: consumer.name };
|
|
189
|
+
}
|
|
190
|
+
return { service: consumer.service };
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
185
194
|
if (worker.crons && worker.crons.length > 0) {
|
|
186
195
|
spec.triggers = { crons: worker.crons };
|
|
187
196
|
}
|
|
@@ -350,14 +359,7 @@ export interface WranglerJsonSpec {
|
|
|
350
359
|
*/
|
|
351
360
|
queues?: {
|
|
352
361
|
producers?: { queue: string; binding: string }[];
|
|
353
|
-
consumers?:
|
|
354
|
-
queue: string;
|
|
355
|
-
max_batch_size?: number;
|
|
356
|
-
max_concurrency?: number;
|
|
357
|
-
max_retries?: number;
|
|
358
|
-
max_wait_time_ms?: number;
|
|
359
|
-
retry_delay?: number;
|
|
360
|
-
}[];
|
|
362
|
+
consumers?: QueueConsumerWranglerJson[];
|
|
361
363
|
};
|
|
362
364
|
|
|
363
365
|
/**
|
|
@@ -492,6 +494,11 @@ export interface WranglerJsonSpec {
|
|
|
492
494
|
experimental_remote?: boolean;
|
|
493
495
|
}[];
|
|
494
496
|
|
|
497
|
+
/**
|
|
498
|
+
* Tail consumers that will receive execution logs from this worker
|
|
499
|
+
*/
|
|
500
|
+
tail_consumers?: Array<{ service: string }>;
|
|
501
|
+
|
|
495
502
|
/**
|
|
496
503
|
* Unsafe bindings section for experimental features
|
|
497
504
|
*/
|
|
@@ -500,6 +507,33 @@ export interface WranglerJsonSpec {
|
|
|
500
507
|
};
|
|
501
508
|
}
|
|
502
509
|
|
|
510
|
+
/**
|
|
511
|
+
* Queue consumer bindings for wrangler.json
|
|
512
|
+
*/
|
|
513
|
+
interface QueueConsumerWranglerJson {
|
|
514
|
+
queue: string;
|
|
515
|
+
/**
|
|
516
|
+
* The maximum number of messages allowed in each batch.
|
|
517
|
+
*/
|
|
518
|
+
max_batch_size?: number;
|
|
519
|
+
/**
|
|
520
|
+
* The maximum number of concurrent consumers allowed to run at once. Leaving this unset will mean that the number of invocations will scale to the currently supported maximum.
|
|
521
|
+
*/
|
|
522
|
+
max_concurrency?: number;
|
|
523
|
+
/**
|
|
524
|
+
* The maximum number of retries for a message, if it fails or `retryAll()` is invoked.
|
|
525
|
+
*/
|
|
526
|
+
max_retries?: number;
|
|
527
|
+
/**
|
|
528
|
+
* The maximum number of seconds to wait for messages to fill a batch before the batch is sent to the consumer Worker.
|
|
529
|
+
*/
|
|
530
|
+
max_batch_timeout?: number;
|
|
531
|
+
/**
|
|
532
|
+
* The number of seconds to delay retried messages for by default, before they are re-delivered to the consumer. This can be overridden on a per-message or per-batch basis when retrying messages.
|
|
533
|
+
*/
|
|
534
|
+
retry_delay?: number;
|
|
535
|
+
}
|
|
536
|
+
|
|
503
537
|
/**
|
|
504
538
|
* Process worker bindings into wrangler.json format
|
|
505
539
|
*/
|
|
@@ -554,14 +588,7 @@ function processBindings(
|
|
|
554
588
|
}[] = [];
|
|
555
589
|
const queues: {
|
|
556
590
|
producers: { queue: string; binding: string }[];
|
|
557
|
-
consumers:
|
|
558
|
-
queue: string;
|
|
559
|
-
max_batch_size?: number;
|
|
560
|
-
max_concurrency?: number;
|
|
561
|
-
max_retries?: number;
|
|
562
|
-
max_wait_time_ms?: number;
|
|
563
|
-
retry_delay?: number;
|
|
564
|
-
}[];
|
|
591
|
+
consumers: QueueConsumerWranglerJson[];
|
|
565
592
|
} = {
|
|
566
593
|
producers: [],
|
|
567
594
|
consumers: [],
|
|
@@ -604,7 +631,9 @@ function processBindings(
|
|
|
604
631
|
max_batch_size: eventSource.settings?.batchSize,
|
|
605
632
|
max_concurrency: eventSource.settings?.maxConcurrency,
|
|
606
633
|
max_retries: eventSource.settings?.maxRetries,
|
|
607
|
-
|
|
634
|
+
max_batch_timeout: eventSource.settings?.maxWaitTimeMs
|
|
635
|
+
? eventSource.settings?.maxWaitTimeMs / 1000
|
|
636
|
+
: undefined,
|
|
608
637
|
retry_delay: eventSource.settings?.retryDelay,
|
|
609
638
|
});
|
|
610
639
|
} else if (isQueue(eventSource)) {
|
package/src/docker/api.ts
CHANGED
|
@@ -3,6 +3,41 @@ import { Buffer } from "node:buffer";
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import type { Duration, HealthcheckConfig } from "./container.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Normalize a duration value to Docker CLI format
|
|
10
|
+
* Accepts either a number (seconds) or a string with unit suffix (ms|s|m|h)
|
|
11
|
+
* Returns a string in Docker CLI format
|
|
12
|
+
*
|
|
13
|
+
* The Duration type provides compile-time type safety, ensuring only valid
|
|
14
|
+
* formats are accepted: number or string matching the pattern `${number}${unit}`
|
|
15
|
+
*
|
|
16
|
+
* @param duration Duration value (number in seconds or string with unit)
|
|
17
|
+
* @returns Normalized duration string for Docker CLI
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* normalizeDuration(30) // "30s"
|
|
21
|
+
* normalizeDuration("30s") // "30s"
|
|
22
|
+
* normalizeDuration("1m") // "1m"
|
|
23
|
+
* normalizeDuration("500ms") // "500ms"
|
|
24
|
+
* normalizeDuration("1.5s") // "1.5s"
|
|
25
|
+
*/
|
|
26
|
+
export function normalizeDuration(duration: Duration): string {
|
|
27
|
+
if (typeof duration === "number") {
|
|
28
|
+
return `${duration}s`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Validate string format: must be a number followed by unit (ms|s|m|h)
|
|
32
|
+
const match = duration.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)$/);
|
|
33
|
+
if (!match) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Invalid duration format: "${duration}". Expected format: number followed by unit (ms|s|m|h), e.g., "30s", "1m", "500ms"`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return duration;
|
|
40
|
+
}
|
|
6
41
|
|
|
7
42
|
/**
|
|
8
43
|
* Options for Docker API requests
|
|
@@ -59,7 +94,10 @@ export class DockerApi {
|
|
|
59
94
|
* @param args Command arguments to pass to Docker CLI
|
|
60
95
|
* @returns Result of the command
|
|
61
96
|
*/
|
|
62
|
-
async exec(
|
|
97
|
+
async exec(
|
|
98
|
+
args: string[],
|
|
99
|
+
remainingAttempts = 3,
|
|
100
|
+
): Promise<{ stdout: string; stderr: string }> {
|
|
63
101
|
// If a custom config directory is provided, ensure all commands use it by
|
|
64
102
|
// setting the DOCKER_CONFIG env variable for the spawned process.
|
|
65
103
|
const env = this.configDir
|
|
@@ -94,8 +132,16 @@ export class DockerApi {
|
|
|
94
132
|
try {
|
|
95
133
|
await subprocess;
|
|
96
134
|
} catch (error: any) {
|
|
135
|
+
const message = stderr || error.message || "Command failed";
|
|
136
|
+
if (
|
|
137
|
+
message.includes("unexpected status from HEAD request") &&
|
|
138
|
+
remainingAttempts > 0
|
|
139
|
+
) {
|
|
140
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
141
|
+
return this.exec(args, remainingAttempts - 1);
|
|
142
|
+
}
|
|
97
143
|
// Process failed, but we still have the output
|
|
98
|
-
throw new Error(
|
|
144
|
+
throw new Error(message);
|
|
99
145
|
}
|
|
100
146
|
|
|
101
147
|
return { stdout, stderr };
|
|
@@ -177,6 +223,7 @@ export class DockerApi {
|
|
|
177
223
|
env?: Record<string, string>;
|
|
178
224
|
volumes?: Record<string, string>;
|
|
179
225
|
cmd?: string[];
|
|
226
|
+
healthcheck?: HealthcheckConfig;
|
|
180
227
|
} = {},
|
|
181
228
|
): Promise<string> {
|
|
182
229
|
const args = ["create", "--name", name];
|
|
@@ -202,6 +249,43 @@ export class DockerApi {
|
|
|
202
249
|
}
|
|
203
250
|
}
|
|
204
251
|
|
|
252
|
+
// Add healthcheck configuration
|
|
253
|
+
if (options.healthcheck) {
|
|
254
|
+
const hc = options.healthcheck;
|
|
255
|
+
|
|
256
|
+
// Format the cmd
|
|
257
|
+
const cmdStr = Array.isArray(hc.cmd) ? hc.cmd.join(" ") : hc.cmd;
|
|
258
|
+
args.push("--health-cmd", cmdStr);
|
|
259
|
+
|
|
260
|
+
// Add interval
|
|
261
|
+
if (hc.interval !== undefined) {
|
|
262
|
+
args.push("--health-interval", normalizeDuration(hc.interval));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Add timeout
|
|
266
|
+
if (hc.timeout !== undefined) {
|
|
267
|
+
args.push("--health-timeout", normalizeDuration(hc.timeout));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Add retries
|
|
271
|
+
if (hc.retries !== undefined) {
|
|
272
|
+
args.push("--health-retries", String(hc.retries));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Add start period
|
|
276
|
+
if (hc.startPeriod !== undefined) {
|
|
277
|
+
args.push("--health-start-period", normalizeDuration(hc.startPeriod));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Add start interval (API 1.44+)
|
|
281
|
+
if (hc.startInterval !== undefined) {
|
|
282
|
+
args.push(
|
|
283
|
+
"--health-start-interval",
|
|
284
|
+
normalizeDuration(hc.startInterval),
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
205
289
|
args.push(image);
|
|
206
290
|
|
|
207
291
|
// Add command if specified
|