alchemy 0.69.1 → 0.70.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 +3883 -3906
- package/bin/alchemy.ts +2 -0
- package/bin/commands/telemetry.ts +33 -0
- package/bin/services/execute-alchemy.ts +7 -0
- package/bin/trpc.ts +4 -9
- package/lib/alchemy.d.ts +7 -3
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +3 -15
- package/lib/alchemy.js.map +1 -1
- package/lib/apply.js +24 -9
- package/lib/apply.js.map +1 -1
- package/lib/aws/utils.d.ts +2 -1
- package/lib/aws/utils.d.ts.map +1 -1
- package/lib/aws/utils.js.map +1 -1
- package/lib/clickhouse/api/client.gen.d.ts +13 -0
- package/lib/clickhouse/api/client.gen.d.ts.map +1 -0
- package/lib/clickhouse/api/client.gen.js +7 -0
- package/lib/clickhouse/api/client.gen.js.map +1 -0
- package/lib/clickhouse/api/sdk.gen.d.ts +322 -0
- package/lib/clickhouse/api/sdk.gen.d.ts.map +1 -0
- package/lib/clickhouse/api/sdk.gen.js +683 -0
- package/lib/clickhouse/api/sdk.gen.js.map +1 -0
- package/lib/clickhouse/api/types.gen.d.ts +5030 -0
- package/lib/clickhouse/api/types.gen.d.ts.map +1 -0
- package/lib/clickhouse/api/types.gen.js +2 -0
- package/lib/clickhouse/api/types.gen.js.map +1 -0
- package/lib/clickhouse/api-key.d.ts +29 -0
- package/lib/clickhouse/api-key.d.ts.map +1 -0
- package/lib/clickhouse/api-key.js +83 -0
- package/lib/clickhouse/api-key.js.map +1 -0
- package/lib/clickhouse/api.d.ts +36 -0
- package/lib/clickhouse/api.d.ts.map +1 -0
- package/lib/clickhouse/api.js +48 -0
- package/lib/clickhouse/api.js.map +1 -0
- package/lib/clickhouse/index.d.ts +4 -0
- package/lib/clickhouse/index.d.ts.map +1 -0
- package/lib/clickhouse/index.js +4 -0
- package/lib/clickhouse/index.js.map +1 -0
- package/lib/clickhouse/organization.d.ts +16 -0
- package/lib/clickhouse/organization.d.ts.map +1 -0
- package/lib/clickhouse/organization.js +21 -0
- package/lib/clickhouse/organization.js.map +1 -0
- package/lib/clickhouse/service.d.ts +275 -0
- package/lib/clickhouse/service.d.ts.map +1 -0
- package/lib/clickhouse/service.js +274 -0
- package/lib/clickhouse/service.js.map +1 -0
- package/lib/cloudflare/account-api-token.d.ts.map +1 -1
- package/lib/cloudflare/account-api-token.js +1 -1
- package/lib/cloudflare/account-api-token.js.map +1 -1
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +2 -0
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/bun-spa/bun-spa.d.ts +13 -0
- package/lib/cloudflare/bun-spa/bun-spa.d.ts.map +1 -0
- package/lib/cloudflare/bun-spa/bun-spa.js +95 -0
- package/lib/cloudflare/bun-spa/bun-spa.js.map +1 -0
- 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 +11 -1
- package/lib/cloudflare/container.d.ts.map +1 -1
- 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/build-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.js +33 -6
- package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-controller.js +2 -0
- package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.js +36 -49
- package/lib/cloudflare/miniflare/miniflare-worker-proxy.js.map +1 -1
- package/lib/cloudflare/permission-groups.d.ts +2 -3
- package/lib/cloudflare/permission-groups.d.ts.map +1 -1
- package/lib/cloudflare/permission-groups.js +10 -15
- package/lib/cloudflare/permission-groups.js.map +1 -1
- package/lib/cloudflare/redwood/redwood.d.ts +1 -1
- package/lib/cloudflare/redwood/redwood.js +1 -1
- package/lib/cloudflare/util/filter-env-bindings.d.ts +62 -0
- package/lib/cloudflare/util/filter-env-bindings.d.ts.map +1 -0
- package/lib/cloudflare/util/filter-env-bindings.js +81 -0
- package/lib/cloudflare/util/filter-env-bindings.js.map +1 -0
- package/lib/cloudflare/website.d.ts +2 -2
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +6 -18
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts +1 -0
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +1 -0
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +11 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +8 -0
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +3 -4
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/coinbase/client.d.ts +54 -0
- package/lib/coinbase/client.d.ts.map +1 -0
- package/lib/coinbase/client.js +89 -0
- package/lib/coinbase/client.js.map +1 -0
- package/lib/coinbase/evm-account.d.ts +118 -0
- package/lib/coinbase/evm-account.d.ts.map +1 -0
- package/lib/coinbase/evm-account.js +198 -0
- package/lib/coinbase/evm-account.js.map +1 -0
- package/lib/coinbase/evm-smart-account.d.ts +122 -0
- package/lib/coinbase/evm-smart-account.d.ts.map +1 -0
- package/lib/coinbase/evm-smart-account.js +213 -0
- package/lib/coinbase/evm-smart-account.js.map +1 -0
- package/lib/coinbase/faucet.d.ts +11 -0
- package/lib/coinbase/faucet.d.ts.map +1 -0
- package/lib/coinbase/faucet.js +146 -0
- package/lib/coinbase/faucet.js.map +1 -0
- package/lib/coinbase/index.d.ts +4 -0
- package/lib/coinbase/index.d.ts.map +1 -0
- package/lib/coinbase/index.js +4 -0
- package/lib/coinbase/index.js.map +1 -0
- package/lib/coinbase/types.d.ts +28 -0
- package/lib/coinbase/types.d.ts.map +1 -0
- package/lib/coinbase/types.js +4 -0
- package/lib/{util/telemetry → coinbase}/types.js.map +1 -1
- package/lib/coinbase/utils.d.ts +13 -0
- package/lib/coinbase/utils.d.ts.map +1 -0
- package/lib/coinbase/utils.js +17 -0
- package/lib/coinbase/utils.js.map +1 -0
- package/lib/neon/api/types.gen.d.ts +7 -2
- package/lib/neon/api/types.gen.d.ts.map +1 -1
- package/lib/planetscale/api/types.gen.d.ts +20 -4
- package/lib/planetscale/api/types.gen.d.ts.map +1 -1
- package/lib/planetscale/api.d.ts.map +1 -1
- package/lib/planetscale/api.js +1 -0
- package/lib/planetscale/api.js.map +1 -1
- package/lib/planetscale/password.d.ts.map +1 -1
- package/lib/planetscale/password.js +1 -13
- package/lib/planetscale/password.js.map +1 -1
- package/lib/resource.d.ts.map +1 -1
- package/lib/resource.js +7 -3
- package/lib/resource.js.map +1 -1
- package/lib/scope.d.ts +9 -9
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +18 -37
- package/lib/scope.js.map +1 -1
- package/lib/state/instrumented-state-store.d.ts +1 -3
- package/lib/state/instrumented-state-store.d.ts.map +1 -1
- package/lib/state/instrumented-state-store.js +19 -21
- package/lib/state/instrumented-state-store.js.map +1 -1
- package/lib/test/bun.d.ts.map +1 -1
- package/lib/test/bun.js +1 -2
- package/lib/test/bun.js.map +1 -1
- package/lib/test/vitest.d.ts.map +1 -1
- package/lib/test/vitest.js +1 -2
- package/lib/test/vitest.js.map +1 -1
- package/lib/util/diff.d.ts +5 -0
- package/lib/util/diff.d.ts.map +1 -0
- package/lib/util/diff.js +14 -0
- package/lib/util/diff.js.map +1 -0
- package/lib/util/idempotent-spawn.js +1 -1
- package/lib/util/idempotent-spawn.js.map +1 -1
- package/lib/util/telemetry.d.ts +53 -0
- package/lib/util/telemetry.d.ts.map +1 -0
- package/lib/util/telemetry.js +215 -0
- package/lib/util/telemetry.js.map +1 -0
- package/package.json +19 -1
- package/src/alchemy.ts +10 -20
- package/src/apply.ts +29 -12
- package/src/aws/utils.ts +2 -1
- package/src/clickhouse/api/client.gen.ts +28 -0
- package/src/clickhouse/api/sdk.gen.ts +1286 -0
- package/src/clickhouse/api/types.gen.ts +5898 -0
- package/src/clickhouse/api-key.ts +136 -0
- package/src/clickhouse/api.ts +84 -0
- package/src/clickhouse/index.ts +3 -0
- package/src/clickhouse/organization.ts +31 -0
- package/src/clickhouse/service.ts +668 -0
- package/src/cloudflare/account-api-token.ts +1 -4
- package/src/cloudflare/api.ts +2 -0
- package/src/cloudflare/bun-spa/bun-spa.ts +136 -0
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/container.ts +20 -1
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/miniflare/build-worker-options.ts +38 -8
- package/src/cloudflare/miniflare/miniflare-controller.ts +2 -0
- package/src/cloudflare/miniflare/miniflare-worker-proxy.ts +41 -58
- package/src/cloudflare/permission-groups.ts +21 -49
- package/src/cloudflare/redwood/redwood.ts +1 -1
- package/src/cloudflare/util/filter-env-bindings.ts +96 -0
- package/src/cloudflare/website.ts +11 -24
- package/src/cloudflare/worker-metadata.ts +2 -0
- package/src/cloudflare/worker.ts +12 -1
- package/src/cloudflare/wrangler.json.ts +12 -8
- package/src/coinbase/client.ts +146 -0
- package/src/coinbase/evm-account.ts +317 -0
- package/src/coinbase/evm-smart-account.ts +339 -0
- package/src/coinbase/faucet.ts +173 -0
- package/src/coinbase/index.ts +3 -0
- package/src/coinbase/types.ts +33 -0
- package/src/coinbase/utils.ts +19 -0
- package/src/neon/api/types.gen.ts +13 -1
- package/src/planetscale/api/types.gen.ts +22 -2
- package/src/planetscale/api.ts +1 -0
- package/src/planetscale/password.ts +1 -14
- package/src/resource.ts +11 -4
- package/src/scope.ts +29 -45
- package/src/state/instrumented-state-store.ts +27 -26
- package/src/test/bun.ts +1 -2
- package/src/test/vitest.ts +1 -2
- package/src/util/diff.ts +14 -0
- package/src/util/idempotent-spawn.ts +1 -1
- package/src/util/telemetry.ts +328 -0
- package/templates/tanstack-start/package.json +2 -2
- package/workers/tunnel-proxy.js +1 -1
- package/lib/util/telemetry/client.d.ts +0 -31
- package/lib/util/telemetry/client.d.ts.map +0 -1
- package/lib/util/telemetry/client.js +0 -121
- package/lib/util/telemetry/client.js.map +0 -1
- package/lib/util/telemetry/constants.d.ts +0 -5
- package/lib/util/telemetry/constants.d.ts.map +0 -1
- package/lib/util/telemetry/constants.js +0 -8
- package/lib/util/telemetry/constants.js.map +0 -1
- package/lib/util/telemetry/context.d.ts +0 -15
- package/lib/util/telemetry/context.d.ts.map +0 -1
- package/lib/util/telemetry/context.js +0 -128
- package/lib/util/telemetry/context.js.map +0 -1
- package/lib/util/telemetry/index.d.ts +0 -3
- package/lib/util/telemetry/index.d.ts.map +0 -1
- package/lib/util/telemetry/index.js +0 -3
- package/lib/util/telemetry/index.js.map +0 -1
- package/lib/util/telemetry/types.d.ts +0 -68
- package/lib/util/telemetry/types.d.ts.map +0 -1
- package/lib/util/telemetry/types.js +0 -1
- package/src/util/telemetry/client.ts +0 -156
- package/src/util/telemetry/constants.ts +0 -13
- package/src/util/telemetry/context.ts +0 -154
- package/src/util/telemetry/index.ts +0 -2
- package/src/util/telemetry/types.ts +0 -105
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Scope } from "../../scope.ts";
|
|
4
|
+
import { exists } from "../../util/exists.ts";
|
|
5
|
+
import type { Assets } from "../assets.ts";
|
|
6
|
+
import type { Bindings } from "../bindings.ts";
|
|
7
|
+
import {
|
|
8
|
+
extractStringAndSecretBindings,
|
|
9
|
+
unencryptSecrets,
|
|
10
|
+
} from "../util/filter-env-bindings.ts";
|
|
11
|
+
import {
|
|
12
|
+
spreadBuildProps,
|
|
13
|
+
spreadDevProps,
|
|
14
|
+
Website,
|
|
15
|
+
type WebsiteProps,
|
|
16
|
+
} from "../website.ts";
|
|
17
|
+
|
|
18
|
+
export interface BunSPAProps<B extends Bindings> extends WebsiteProps<B> {
|
|
19
|
+
frontend: string;
|
|
20
|
+
outDir?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type BunSPA<B extends Bindings> = Website<B> & { apiUrl: string };
|
|
24
|
+
|
|
25
|
+
export async function BunSPA<B extends Bindings>(
|
|
26
|
+
id: string,
|
|
27
|
+
props: BunSPAProps<B>,
|
|
28
|
+
): Promise<BunSPA<B> & { apiUrl: string }> {
|
|
29
|
+
const frontendPath = path.resolve(props.frontend);
|
|
30
|
+
if (!(await exists(frontendPath))) {
|
|
31
|
+
throw new Error(`Frontend path ${frontendPath} does not exist`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const stats = await fs.stat(frontendPath);
|
|
35
|
+
if (!stats.isFile()) {
|
|
36
|
+
throw new Error(`Frontend path ${frontendPath} is not a file`);
|
|
37
|
+
}
|
|
38
|
+
const outDir = path.resolve(props.outDir ?? "dist/client");
|
|
39
|
+
|
|
40
|
+
if (props.assets) {
|
|
41
|
+
throw new Error("assets are not supported in BunSPA");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const scope = Scope.current;
|
|
45
|
+
console.log("creating website", outDir);
|
|
46
|
+
const website = await Website(id, {
|
|
47
|
+
spa: true,
|
|
48
|
+
...props,
|
|
49
|
+
bindings: {
|
|
50
|
+
...props.bindings,
|
|
51
|
+
// set NODE_ENV in worker appropriately if not already set
|
|
52
|
+
NODE_ENV:
|
|
53
|
+
(props.bindings?.NODE_ENV ?? scope.local)
|
|
54
|
+
? "development"
|
|
55
|
+
: "production",
|
|
56
|
+
} as unknown as B,
|
|
57
|
+
assets: {
|
|
58
|
+
directory: path.resolve(outDir),
|
|
59
|
+
},
|
|
60
|
+
build: spreadBuildProps(
|
|
61
|
+
props,
|
|
62
|
+
`bun build '${frontendPath}' --outdir ${outDir}`,
|
|
63
|
+
),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
let apiUrl = website.url!;
|
|
67
|
+
// in dev
|
|
68
|
+
if (scope.local) {
|
|
69
|
+
const cwd = props.cwd ?? process.cwd();
|
|
70
|
+
await validateBunfigToml(cwd);
|
|
71
|
+
const dev = spreadDevProps(
|
|
72
|
+
props,
|
|
73
|
+
`bun '${path.relative(cwd, frontendPath)}'`,
|
|
74
|
+
);
|
|
75
|
+
const secrets = props.wrangler?.secrets ?? !props.wrangler?.path;
|
|
76
|
+
const env = {
|
|
77
|
+
...(process.env ?? {}),
|
|
78
|
+
...(props.env ?? {}),
|
|
79
|
+
...extractStringAndSecretBindings(props.bindings ?? {}, secrets),
|
|
80
|
+
};
|
|
81
|
+
website.url = await scope.spawn(website.name, {
|
|
82
|
+
cmd: typeof dev === "string" ? dev : dev.command!,
|
|
83
|
+
cwd,
|
|
84
|
+
extract: (line) => {
|
|
85
|
+
const URL_REGEX =
|
|
86
|
+
/http:\/\/(localhost|0\.0\.0\.0|127\.0\.0\.1|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):\d+\/?/;
|
|
87
|
+
const match = line
|
|
88
|
+
.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "")
|
|
89
|
+
.match(URL_REGEX);
|
|
90
|
+
if (match) {
|
|
91
|
+
return match[0];
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
env: {
|
|
95
|
+
...unencryptSecrets(env ?? {}),
|
|
96
|
+
...(typeof dev === "object" ? dev.env : {}),
|
|
97
|
+
FORCE_COLOR: "1",
|
|
98
|
+
...process.env,
|
|
99
|
+
NODE_ENV: "development",
|
|
100
|
+
ALCHEMY_ROOT: Scope.current.rootDir,
|
|
101
|
+
PUBLIC_BACKEND_URL: apiUrl,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return { ...website, apiUrl } as BunSPA<B>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function validateBunfigToml(cwd: string): Promise<void> {
|
|
109
|
+
const bunfigPath = path.join(cwd, "bunfig.toml");
|
|
110
|
+
|
|
111
|
+
if (!(await exists(bunfigPath))) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
"bunfig.toml is required for BunSPA to work correctly.\n\n" +
|
|
114
|
+
`Create ${bunfigPath} with the following content:\n\n` +
|
|
115
|
+
"[serve.static]\n" +
|
|
116
|
+
`env='PUBLIC_*'\n\n` +
|
|
117
|
+
"This allows Bun to expose PUBLIC_* environment variables to the frontend during development.",
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const content = await fs.readFile(bunfigPath, "utf-8");
|
|
122
|
+
const config = Bun.TOML.parse(content) as Record<string, any>;
|
|
123
|
+
|
|
124
|
+
const hasServeStatic = config.serve?.static;
|
|
125
|
+
const hasEnvConfig = hasServeStatic && config.serve.static.env === "PUBLIC_*";
|
|
126
|
+
|
|
127
|
+
if (!hasServeStatic || !hasEnvConfig) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
"bunfig.toml is missing required configuration for BunSPA.\n\n" +
|
|
130
|
+
`Add the following section to ${bunfigPath}:\n\n` +
|
|
131
|
+
"[serve.static]\n" +
|
|
132
|
+
`env='PUBLIC_*'\n\n` +
|
|
133
|
+
"This allows Bun to expose PUBLIC_* environment variables to the frontend during development.",
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -93,9 +93,28 @@ export interface ContainerProps
|
|
|
93
93
|
* - `basic`: Basic production instances with standard resources
|
|
94
94
|
* - `standard`: Standard production instances with enhanced resources
|
|
95
95
|
*
|
|
96
|
+
* | Instance Type | vCPU | Memory (Min) | Memory (Max) |
|
|
97
|
+
* |---------------|------|--------------|--------------|
|
|
98
|
+
* | lite | 1/16 | 256 MiB | 2 GB |
|
|
99
|
+
* | basic | 1/4 | 1 GiB | 4 GB |
|
|
100
|
+
* | standard-1 | 1/2 | 4 GiB | 8 GB |
|
|
101
|
+
* | standard-2 | 1 | 6 GiB | 12 GB |
|
|
102
|
+
* | standard-3 | 2 | 8 GiB | 16 GB |
|
|
103
|
+
* | standard-4 | 4 | 12 GiB | 20 GB |
|
|
104
|
+
*
|
|
96
105
|
* @see https://developers.cloudflare.com/containers/pricing/
|
|
106
|
+
* @see https://developers.cloudflare.com/changelog/2025-10-01-new-container-instance-types/
|
|
97
107
|
*/
|
|
98
|
-
export type InstanceType =
|
|
108
|
+
export type InstanceType =
|
|
109
|
+
| "lite"
|
|
110
|
+
| "dev"
|
|
111
|
+
| "basic"
|
|
112
|
+
| "standard"
|
|
113
|
+
| "standard-1"
|
|
114
|
+
| "standard-2"
|
|
115
|
+
| "standard-3"
|
|
116
|
+
| "standard-4"
|
|
117
|
+
| (string & {});
|
|
99
118
|
|
|
100
119
|
/**
|
|
101
120
|
* Type guard to check if a binding is a Container binding.
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from "./bound.ts";
|
|
|
15
15
|
export * from "./browser-rendering.ts";
|
|
16
16
|
export * from "./bucket-object.ts";
|
|
17
17
|
export * from "./bucket.ts";
|
|
18
|
+
export * from "./bun-spa/bun-spa.ts";
|
|
18
19
|
export * from "./certificate-pack.ts";
|
|
19
20
|
export * from "./cloudflare-env-proxy.ts";
|
|
20
21
|
export * from "./compatibility-date.gen.ts";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as miniflare from "miniflare";
|
|
2
2
|
import { assertNever } from "../../util/assert-never.ts";
|
|
3
3
|
import type { HTTPServer } from "../../util/http.ts";
|
|
4
|
+
import { logger } from "../../util/logger.ts";
|
|
4
5
|
import type { CloudflareApi } from "../api.ts";
|
|
5
6
|
import type {
|
|
6
7
|
Binding,
|
|
@@ -52,7 +53,9 @@ type RemoteBinding =
|
|
|
52
53
|
| WorkerBindingService;
|
|
53
54
|
|
|
54
55
|
type BaseWorkerOptions = {
|
|
55
|
-
[K in keyof miniflare.WorkerOptions]: K extends
|
|
56
|
+
[K in keyof miniflare.WorkerOptions]: K extends
|
|
57
|
+
| "compatibilityFlags"
|
|
58
|
+
| "routes"
|
|
56
59
|
? miniflare.WorkerOptions[K]
|
|
57
60
|
: Exclude<miniflare.WorkerOptions[K], string[]>;
|
|
58
61
|
};
|
|
@@ -70,21 +73,18 @@ export const buildWorkerOptions = async (
|
|
|
70
73
|
compatibilityFlags: input.compatibilityFlags,
|
|
71
74
|
unsafeDirectSockets: [
|
|
72
75
|
// This matches the Wrangler configuration by exposing the default handler (e.g. `export default { fetch }`).
|
|
73
|
-
// However, unlike Wrangler, we set `proxy: false` to avoid the following error when connecting via a websocket:
|
|
74
|
-
// workerd/io/worker.c++:2164: info: uncaught exception; source = Uncaught (in promise); stack = TypeError: Invalid URL string.
|
|
75
76
|
{
|
|
76
77
|
entrypoint: "default",
|
|
77
|
-
proxy:
|
|
78
|
+
proxy: true,
|
|
78
79
|
},
|
|
79
80
|
],
|
|
80
81
|
containerEngine: {
|
|
81
82
|
localDocker: {
|
|
82
|
-
socketPath:
|
|
83
|
-
process.platform === "win32"
|
|
84
|
-
? "//./pipe/docker_engine"
|
|
85
|
-
: "unix:///var/run/docker.sock",
|
|
83
|
+
socketPath: await getDockerSocketPath(),
|
|
86
84
|
},
|
|
87
85
|
},
|
|
86
|
+
// This exposes the worker as a route that can be accessed by setting the MF-Route-Override header.
|
|
87
|
+
routes: [input.name],
|
|
88
88
|
};
|
|
89
89
|
for (const [key, binding] of Object.entries(input.bindings ?? {})) {
|
|
90
90
|
if (typeof binding === "string") {
|
|
@@ -478,3 +478,33 @@ const isRemoteBinding = (binding: Binding) => {
|
|
|
478
478
|
!!binding.dev.remote
|
|
479
479
|
);
|
|
480
480
|
};
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* DOCKER_HOST env is standardized
|
|
484
|
+
* docker has an option to expose on tcp://localhost:2375; so we check 2375 if the user has it enabled
|
|
485
|
+
* the pipe on windows doesn't work half the time(even though the pipe exists). This seems like a strange error on how miniflare parses the pipe
|
|
486
|
+
* @returns The Docker path
|
|
487
|
+
*/
|
|
488
|
+
async function getDockerSocketPath() {
|
|
489
|
+
if (process.env.DOCKER_HOST) {
|
|
490
|
+
return process.env.DOCKER_HOST;
|
|
491
|
+
}
|
|
492
|
+
// Check if docker is running on tcp://localhost:2375 using fetch
|
|
493
|
+
try {
|
|
494
|
+
const url = "http://localhost:2375/_ping";
|
|
495
|
+
const res = await fetch(url, { method: "GET" });
|
|
496
|
+
if (res.ok) {
|
|
497
|
+
const text = await res.text();
|
|
498
|
+
if (text.trim() === "OK") {
|
|
499
|
+
return "localhost:2375";
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
} catch {}
|
|
503
|
+
if (process.platform === "win32") {
|
|
504
|
+
logger.warn(
|
|
505
|
+
"Using the pipe on Windows is unstable. If you have issues, try setting DOCKER_HOST or enabling 'Expose daemon on tcp://localhost:2375 without TLS' in docker desktop",
|
|
506
|
+
);
|
|
507
|
+
return "//./pipe/docker_engine";
|
|
508
|
+
}
|
|
509
|
+
return "unix:///var/run/docker.sock";
|
|
510
|
+
}
|
|
@@ -104,6 +104,8 @@ export class MiniflareController {
|
|
|
104
104
|
// This is required to allow websites and other separate processes
|
|
105
105
|
// to detect Alchemy-managed Durable Objects via the Wrangler dev registry.
|
|
106
106
|
unsafeDevRegistryDurableObjectProxy: true,
|
|
107
|
+
// This exposes other handlers like `scheduled` and `email` via HTTP.
|
|
108
|
+
unsafeTriggerHandlers: true,
|
|
107
109
|
};
|
|
108
110
|
for (const worker of this.options.values()) {
|
|
109
111
|
options.workers.push(worker);
|
|
@@ -13,7 +13,7 @@ export interface MiniflareWorkerProxy {
|
|
|
13
13
|
export async function createMiniflareWorkerProxy(options: {
|
|
14
14
|
port: number;
|
|
15
15
|
transformRequest?: (request: RequestInfo) => void;
|
|
16
|
-
getWorkerName: (request:
|
|
16
|
+
getWorkerName: (request: RequestInfo) => string;
|
|
17
17
|
miniflare: miniflare.Miniflare;
|
|
18
18
|
mode: "local" | "remote";
|
|
19
19
|
}) {
|
|
@@ -45,71 +45,50 @@ export async function createMiniflareWorkerProxy(options: {
|
|
|
45
45
|
const handleFetch = async (
|
|
46
46
|
req: http.IncomingMessage,
|
|
47
47
|
): Promise<miniflare.Response> => {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
const info = parseIncomingMessage(req);
|
|
49
|
+
options.transformRequest?.(info);
|
|
50
|
+
|
|
51
|
+
const name = options.getWorkerName(info);
|
|
52
|
+
info.headers.set("MF-Route-Override", name);
|
|
51
53
|
|
|
52
|
-
// Handle scheduled events
|
|
54
|
+
// Handle scheduled events.
|
|
55
|
+
// Wrangler exposes this as /__scheduled, but Miniflare exposes it as /cdn-cgi/handler/scheduled.
|
|
53
56
|
// https://developers.cloudflare.com/workers/runtime-apis/handlers/scheduled/#background
|
|
54
57
|
// https://github.com/cloudflare/workers-sdk/blob/7d53b9ab6b370944b7934ad51ebef43160c3c775/packages/wrangler/templates/middleware/middleware-scheduled.ts#L6
|
|
55
|
-
if (url.pathname === "/__scheduled") {
|
|
56
|
-
|
|
57
|
-
scheduledTime: new Date(),
|
|
58
|
-
cron: url.searchParams.get("cron") ?? undefined,
|
|
59
|
-
});
|
|
60
|
-
return new miniflare.Response("Ran scheduled function");
|
|
58
|
+
if (info.url.pathname === "/__scheduled") {
|
|
59
|
+
info.url.pathname = "/cdn-cgi/handler/scheduled";
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
const
|
|
64
|
-
.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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 } => {
|
|
89
|
-
const info = parseIncomingMessage(req);
|
|
90
|
-
options.transformRequest?.(info);
|
|
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
|
-
};
|
|
62
|
+
const request = new miniflare.Request(info.url, {
|
|
63
|
+
method: info.method,
|
|
64
|
+
headers: info.headers,
|
|
65
|
+
body: info.body,
|
|
66
|
+
redirect: info.redirect,
|
|
67
|
+
duplex: info.duplex,
|
|
68
|
+
});
|
|
69
|
+
return await options.miniflare.dispatchFetch(request);
|
|
101
70
|
};
|
|
102
71
|
|
|
103
72
|
const createServerWebSocket = async (req: http.IncomingMessage) => {
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
const target = await options.miniflare.unsafeGetDirectURL(name);
|
|
73
|
+
// Rewrite to Miniflare entry URL
|
|
74
|
+
const target = await options.miniflare.ready;
|
|
107
75
|
const url = new URL(req.url ?? "/", target);
|
|
108
76
|
url.protocol = url.protocol.replace("http", "ws");
|
|
77
|
+
|
|
109
78
|
const protocols = req.headers["sec-websocket-protocol"]
|
|
110
79
|
?.split(",")
|
|
111
80
|
.map((p) => p.trim());
|
|
112
|
-
|
|
81
|
+
|
|
82
|
+
// Get worker name to set MF-Route-Override header
|
|
83
|
+
const info = parseIncomingMessage(req);
|
|
84
|
+
options.transformRequest?.(info);
|
|
85
|
+
const name = options.getWorkerName(info);
|
|
86
|
+
|
|
87
|
+
const server = new WebSocket(url, protocols, {
|
|
88
|
+
headers: {
|
|
89
|
+
"MF-Route-Override": name,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
113
92
|
await once(server, "open");
|
|
114
93
|
return server;
|
|
115
94
|
};
|
|
@@ -121,10 +100,8 @@ export async function createMiniflareWorkerProxy(options: {
|
|
|
121
100
|
return {
|
|
122
101
|
url,
|
|
123
102
|
close: async () => {
|
|
124
|
-
await
|
|
125
|
-
|
|
126
|
-
new Promise((resolve) => wss.close(resolve)),
|
|
127
|
-
]);
|
|
103
|
+
// If we await this while the `wss` server is open, the server will hang.
|
|
104
|
+
server.close();
|
|
128
105
|
},
|
|
129
106
|
};
|
|
130
107
|
}
|
|
@@ -172,7 +149,13 @@ const writeMiniflareResponseToNode = (
|
|
|
172
149
|
) => {
|
|
173
150
|
out.statusCode = res.status;
|
|
174
151
|
res.headers.forEach((value, key) => {
|
|
175
|
-
|
|
152
|
+
// The `transfer-encoding` header prevents Cloudflare Tunnels from working because of the following error:
|
|
153
|
+
// Request failed error="Unable to reach the origin service. The service may be down or it may not be
|
|
154
|
+
// responding to traffic from cloudflared: net/http: HTTP/1.x transport connection broken: too many
|
|
155
|
+
// transfer encodings: [\"chunked\" \"chunked\"]"
|
|
156
|
+
if (key !== "transfer-encoding") {
|
|
157
|
+
out.setHeader(key, value);
|
|
158
|
+
}
|
|
176
159
|
});
|
|
177
160
|
|
|
178
161
|
if (res.body) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Context } from "../context.ts";
|
|
2
1
|
import { Resource } from "../resource.ts";
|
|
2
|
+
import { extractCloudflareResult } from "./api-response.ts";
|
|
3
3
|
import { createCloudflareApi, type CloudflareApiOptions } from "./api.ts";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -22,16 +22,6 @@ export interface PermissionGroup {
|
|
|
22
22
|
scopes: string[];
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* Response from the Cloudflare permission groups API
|
|
27
|
-
*/
|
|
28
|
-
interface PermissionGroupsResponse {
|
|
29
|
-
result: PermissionGroup[];
|
|
30
|
-
success: boolean;
|
|
31
|
-
errors: any[];
|
|
32
|
-
messages: any[];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
25
|
export type PermissionGroupName = R2PermissionGroups | (string & {});
|
|
36
26
|
|
|
37
27
|
export type R2PermissionGroups =
|
|
@@ -86,7 +76,7 @@ export type PermissionGroups = {
|
|
|
86
76
|
*
|
|
87
77
|
* @example
|
|
88
78
|
* // Get all permission groups including those for R2
|
|
89
|
-
* const permissions = await PermissionGroups(
|
|
79
|
+
* const permissions = await PermissionGroups();
|
|
90
80
|
*
|
|
91
81
|
* // Use with AccountApiToken to create a token with proper permissions
|
|
92
82
|
* const token = await AccountApiToken("r2-token", {
|
|
@@ -106,42 +96,24 @@ export type PermissionGroups = {
|
|
|
106
96
|
* ]
|
|
107
97
|
* });
|
|
108
98
|
*/
|
|
109
|
-
export
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// Initialize API client
|
|
122
|
-
const api = await createCloudflareApi(options);
|
|
123
|
-
|
|
124
|
-
// Fetch permission groups from Cloudflare API
|
|
125
|
-
const response = await api.get(
|
|
126
|
-
`/accounts/${api.accountId}/tokens/permission_groups`,
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
if (!response.ok) {
|
|
130
|
-
throw new Error(
|
|
131
|
-
`Failed to fetch permission groups: ${response.statusText}`,
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const data = (await response.json()) as PermissionGroupsResponse;
|
|
99
|
+
export async function PermissionGroups(
|
|
100
|
+
options: CloudflareApiOptions = {},
|
|
101
|
+
): Promise<PermissionGroups> {
|
|
102
|
+
const api = await createCloudflareApi(options);
|
|
103
|
+
const result = await extractCloudflareResult<PermissionGroup[]>(
|
|
104
|
+
"fetch permission groups",
|
|
105
|
+
api.get(`/accounts/${api.accountId}/tokens/permission_groups`),
|
|
106
|
+
);
|
|
107
|
+
return Object.fromEntries(
|
|
108
|
+
result.map((group) => [group.name, group]),
|
|
109
|
+
) as PermissionGroups;
|
|
110
|
+
}
|
|
136
111
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
112
|
+
// we are deprecating the PermissionGroups resource (it is now just a function)
|
|
113
|
+
Resource("cloudflare::PermissionGroups", async function (this) {
|
|
114
|
+
if (this.phase === "delete") {
|
|
115
|
+
return this.destroy();
|
|
116
|
+
}
|
|
142
117
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
) as PermissionGroups;
|
|
146
|
-
},
|
|
147
|
-
);
|
|
118
|
+
throw new Error("Not implemented");
|
|
119
|
+
});
|
|
@@ -12,7 +12,7 @@ export type Redwood<B extends Bindings> = B extends { ASSETS: any }
|
|
|
12
12
|
: Worker<B & { ASSETS: Assets }>;
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Deploy a RedwoodJS application to Cloudflare
|
|
15
|
+
* Deploy a RedwoodJS application to Cloudflare Workers with automatically configured defaults.
|
|
16
16
|
*
|
|
17
17
|
* This resource handles the deployment of RedwoodJS applications with optimized settings for
|
|
18
18
|
* Cloudflare Workers, including proper build commands and compatibility flags.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { isSecret, type Secret } from "../../secret.ts";
|
|
2
|
+
import type { Bindings } from "../bindings.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extract string and Secret bindings from a Bindings object
|
|
6
|
+
*
|
|
7
|
+
* This utility filters a Bindings object to return only string values
|
|
8
|
+
* and optionally Secret objects. All other binding types (workers, KV, R2, etc.)
|
|
9
|
+
* are excluded from the result.
|
|
10
|
+
*
|
|
11
|
+
* @param bindings - The bindings object to filter
|
|
12
|
+
* @param includeSecrets - Whether to include Secret objects in the result
|
|
13
|
+
* @returns A record containing only string values and Secret objects (if includeSecrets is true)
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const filtered = extractStringAndSecretBindings(props.bindings, true);
|
|
18
|
+
* // Returns: { API_KEY: Secret(...), DEBUG: "true" }
|
|
19
|
+
* // Excludes: { CACHE: KVNamespace, STORAGE: R2Bucket }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function extractStringAndSecretBindings(
|
|
23
|
+
bindings: Bindings,
|
|
24
|
+
includeSecrets: boolean,
|
|
25
|
+
): Record<string, string | Secret> {
|
|
26
|
+
return Object.fromEntries(
|
|
27
|
+
Object.entries(bindings ?? {}).flatMap(([key, value]) => {
|
|
28
|
+
if (typeof value === "string" || (isSecret(value) && includeSecrets)) {
|
|
29
|
+
return [[key, value]];
|
|
30
|
+
}
|
|
31
|
+
return [];
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Unencrypt all Secret values in an environment object
|
|
38
|
+
*
|
|
39
|
+
* Takes a record that may contain Secret objects and returns a new record
|
|
40
|
+
* with all Secret values replaced by their unencrypted string values.
|
|
41
|
+
* Non-Secret values are passed through unchanged. Undefined values are filtered out.
|
|
42
|
+
*
|
|
43
|
+
* @param env - The environment object that may contain Secret values
|
|
44
|
+
* @returns A new record with all Secrets replaced by their unencrypted values
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* const env = { API_KEY: Secret("abc123"), DEBUG: "true" };
|
|
49
|
+
* const unencrypted = unencryptSecrets(env);
|
|
50
|
+
* // Returns: { API_KEY: "abc123", DEBUG: "true" }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function unencryptSecrets(
|
|
54
|
+
env: Record<string, string | Secret | undefined>,
|
|
55
|
+
): Record<string, string> {
|
|
56
|
+
return Object.fromEntries(
|
|
57
|
+
Object.entries(env ?? {}).flatMap(([key, value]) => {
|
|
58
|
+
if (isSecret(value)) {
|
|
59
|
+
return [[key, value.unencrypted]];
|
|
60
|
+
}
|
|
61
|
+
if (typeof value === "string") {
|
|
62
|
+
return [[key, value]];
|
|
63
|
+
}
|
|
64
|
+
return [];
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Filter bindings to strings and unencrypt secrets in one operation
|
|
71
|
+
*
|
|
72
|
+
* This is a convenience function that combines extractStringAndSecretBindings
|
|
73
|
+
* and unencryptSecrets. It filters bindings to strings/secrets and immediately
|
|
74
|
+
* unencrypts any Secret values.
|
|
75
|
+
*
|
|
76
|
+
* Useful for preparing environment variables for dev/build commands where
|
|
77
|
+
* secrets need to be decrypted and all resource bindings need to be excluded.
|
|
78
|
+
*
|
|
79
|
+
* @param bindings - The bindings object to filter
|
|
80
|
+
* @param includeSecrets - Whether to include (and unencrypt) Secret objects
|
|
81
|
+
* @returns A record containing only string values with all secrets unencrypted
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const env = filterStringBindings(props.bindings, true);
|
|
86
|
+
* // Input: { API_KEY: Secret("abc"), DEBUG: "true", CACHE: KVNamespace }
|
|
87
|
+
* // Returns: { API_KEY: "abc", DEBUG: "true" }
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export function filterStringBindings(
|
|
91
|
+
bindings: Bindings,
|
|
92
|
+
includeSecrets: boolean,
|
|
93
|
+
): Record<string, string> {
|
|
94
|
+
const extracted = extractStringAndSecretBindings(bindings, includeSecrets);
|
|
95
|
+
return unencryptSecrets(extracted);
|
|
96
|
+
}
|