alchemy 0.43.5 → 0.44.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.mjs +430 -343
- package/bin/alchemy.ts +7 -21
- package/bin/commands/create.ts +79 -23
- package/bin/services/template-manager.ts +9 -4
- package/bin/services/vibe-rules.ts +47 -0
- package/bin/types.ts +25 -5
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +8 -11
- package/lib/alchemy.js.map +1 -1
- package/lib/apply.js +2 -0
- package/lib/apply.js.map +1 -1
- package/lib/build-date.d.ts +1 -1
- package/lib/build-date.js +1 -1
- package/lib/cloudflare/assets.d.ts +1 -1
- package/lib/cloudflare/assets.d.ts.map +1 -1
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +9 -6
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker-dev.js +4 -1
- package/lib/cloudflare/bundle/bundle-worker-dev.js.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker.js +4 -1
- package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
- package/lib/cloudflare/bundle/external.d.ts +1 -0
- package/lib/cloudflare/bundle/external.d.ts.map +1 -1
- package/lib/cloudflare/bundle/external.js +1 -1
- package/lib/cloudflare/bundle/external.js.map +1 -1
- package/lib/cloudflare/bundle/nodejs-import-warning-plugin.d.ts +10 -0
- package/lib/cloudflare/bundle/nodejs-import-warning-plugin.d.ts.map +1 -0
- package/lib/cloudflare/bundle/nodejs-import-warning-plugin.js +67 -0
- package/lib/cloudflare/bundle/nodejs-import-warning-plugin.js.map +1 -0
- package/lib/cloudflare/container.d.ts +27 -2
- package/lib/cloudflare/container.d.ts.map +1 -1
- package/lib/cloudflare/container.js +82 -24
- package/lib/cloudflare/container.js.map +1 -1
- package/lib/cloudflare/do-state-store/store.d.ts.map +1 -1
- package/lib/cloudflare/do-state-store/store.js +5 -1
- package/lib/cloudflare/do-state-store/store.js.map +1 -1
- package/lib/cloudflare/redirect-rule.js +1 -1
- package/lib/cloudflare/redirect-rule.js.map +1 -1
- package/lib/cloudflare/website.d.ts +17 -0
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +1 -0
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker/miniflare.d.ts +4 -1
- package/lib/cloudflare/worker/miniflare.d.ts.map +1 -1
- package/lib/cloudflare/worker/miniflare.js +22 -2
- package/lib/cloudflare/worker/miniflare.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +12 -0
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +24 -0
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +20 -2
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/context.d.ts +6 -1
- package/lib/context.d.ts.map +1 -1
- package/lib/context.js +2 -1
- package/lib/context.js.map +1 -1
- package/lib/destroy.d.ts.map +1 -1
- package/lib/destroy.js +1 -0
- package/lib/destroy.js.map +1 -1
- package/lib/docker/api.d.ts +11 -0
- package/lib/docker/api.d.ts.map +1 -1
- package/lib/docker/api.js +41 -1
- package/lib/docker/api.js.map +1 -1
- package/lib/docker/image.d.ts +1 -1
- package/lib/docker/image.d.ts.map +1 -1
- package/lib/docker/image.js +11 -5
- package/lib/docker/image.js.map +1 -1
- package/lib/esbuild/bundle.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/llms.cloudflare.txt +379 -0
- package/lib/llms.d.ts +10 -0
- package/lib/llms.d.ts.map +1 -0
- package/lib/llms.js +12 -0
- package/lib/llms.js.map +1 -0
- package/lib/planetscale/index.d.ts +3 -2
- package/lib/planetscale/index.d.ts.map +1 -1
- package/lib/planetscale/index.js +3 -2
- package/lib/planetscale/index.js.map +1 -1
- package/lib/planetscale/password.d.ts +233 -0
- package/lib/planetscale/password.d.ts.map +1 -0
- package/lib/planetscale/password.js +238 -0
- package/lib/planetscale/password.js.map +1 -0
- package/lib/scope.d.ts +6 -5
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +18 -7
- package/lib/scope.js.map +1 -1
- package/lib/test/bun.d.ts.map +1 -1
- package/lib/test/bun.js +1 -0
- package/lib/test/bun.js.map +1 -1
- package/lib/test/vitest.d.ts.map +1 -1
- package/lib/test/vitest.js +1 -0
- package/lib/test/vitest.js.map +1 -1
- package/lib/util/nanoid.d.ts +3 -0
- package/lib/util/nanoid.d.ts.map +1 -0
- package/lib/util/nanoid.js +13 -0
- package/lib/util/nanoid.js.map +1 -0
- package/lib/util/safe-fetch.d.ts.map +1 -1
- package/lib/util/safe-fetch.js +0 -1
- package/lib/util/safe-fetch.js.map +1 -1
- package/lib/util/telemetry/client.d.ts.map +1 -1
- package/lib/util/telemetry/client.js +13 -3
- package/lib/util/telemetry/client.js.map +1 -1
- package/lib/util/telemetry/constants.d.ts +2 -1
- package/lib/util/telemetry/constants.d.ts.map +1 -1
- package/lib/util/telemetry/constants.js +3 -2
- package/lib/util/telemetry/constants.js.map +1 -1
- package/package.json +10 -2
- package/src/alchemy.ts +8 -13
- package/src/apply.ts +2 -0
- package/src/build-date.ts +1 -1
- package/src/cloudflare/bucket.ts +22 -14
- package/src/cloudflare/bundle/bundle-worker-dev.ts +4 -1
- package/src/cloudflare/bundle/bundle-worker.ts +4 -1
- package/src/cloudflare/bundle/external.ts +1 -1
- package/src/cloudflare/bundle/nodejs-import-warning-plugin.ts +94 -0
- package/src/cloudflare/container.ts +113 -28
- package/src/cloudflare/do-state-store/store.ts +5 -1
- package/src/cloudflare/redirect-rule.ts +1 -1
- package/src/cloudflare/website.ts +21 -1
- package/src/cloudflare/worker/miniflare.ts +33 -8
- package/src/cloudflare/worker-metadata.ts +15 -0
- package/src/cloudflare/wrangler.json.ts +61 -5
- package/src/context.ts +7 -0
- package/src/destroy.ts +1 -0
- package/src/docker/api.ts +57 -1
- package/src/docker/image.ts +15 -6
- package/src/index.ts +1 -0
- package/src/llms.cloudflare.txt +379 -0
- package/src/llms.ts +25 -0
- package/src/planetscale/index.ts +3 -2
- package/src/planetscale/password.ts +366 -0
- package/src/scope.ts +25 -11
- package/src/test/bun.ts +1 -0
- package/src/test/vitest.ts +1 -0
- package/src/util/nanoid.ts +13 -0
- package/src/util/safe-fetch.ts +0 -1
- package/src/util/telemetry/client.ts +17 -3
- package/src/util/telemetry/constants.ts +5 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/util/telemetry/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,MAAM,GAAG,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;AAEzC,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;AAEvC,MAAM,CAAC,MAAM,kBAAkB,GAC7B,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;AAEzE,wCAAwC;AACxC,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/util/telemetry/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,MAAM,GAAG,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;AAEzC,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;AAEvC,MAAM,CAAC,MAAM,kBAAkB,GAC7B,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;AAEzE,wCAAwC;AACxC,MAAM,CAAC,MAAM,uBAAuB,GAClC,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,wBAAwB,CAAC;AAClE,MAAM,CAAC,MAAM,kBAAkB,GAC7B,OAAO,CAAC,GAAG,CAAC,kBAAkB;IAC9B,iDAAiD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alchemy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"module": "./lib/index.js",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"build:workers": "bun ./scripts/build-workers.ts",
|
|
17
17
|
"build": "rm -rf ./*.tsbuildinfo && rm -rf ./lib && tsc -b && bun run build:cli && bun run build:workers",
|
|
18
18
|
"docs:gen": "rm -rf ./docs && typedoc",
|
|
19
|
-
"postbuild": "cpx 'src/**/*/types.d.ts' lib",
|
|
19
|
+
"postbuild": "cpx 'src/**/*/types.d.ts' lib && cpx 'src/**/*.txt' lib",
|
|
20
20
|
"publish:npm": "cp ../README.md . && bun run build && bun run build:cli && npm publish && rm README.md"
|
|
21
21
|
},
|
|
22
22
|
"bin": {
|
|
@@ -36,6 +36,10 @@
|
|
|
36
36
|
"bun": "./src/index.ts",
|
|
37
37
|
"import": "./lib/index.js"
|
|
38
38
|
},
|
|
39
|
+
"./llms": {
|
|
40
|
+
"bun": "./src/llms.ts",
|
|
41
|
+
"import": "./lib/llms.js"
|
|
42
|
+
},
|
|
39
43
|
"./ai": {
|
|
40
44
|
"bun": "./src/ai/index.ts",
|
|
41
45
|
"import": "./lib/ai/index.js"
|
|
@@ -84,6 +88,10 @@
|
|
|
84
88
|
"bun": "./src/os/index.ts",
|
|
85
89
|
"import": "./lib/os/index.js"
|
|
86
90
|
},
|
|
91
|
+
"./planetscale": {
|
|
92
|
+
"bun": "./src/planetscale/index.ts",
|
|
93
|
+
"import": "./lib/planetscale/index.js"
|
|
94
|
+
},
|
|
87
95
|
"./sentry": {
|
|
88
96
|
"bun": "./src/sentry/index.ts",
|
|
89
97
|
"import": "./lib/sentry/index.js"
|
package/src/alchemy.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
ResourceSeq,
|
|
14
14
|
} from "./resource.ts";
|
|
15
15
|
import { isRuntime } from "./runtime/global.ts";
|
|
16
|
-
import { Scope } from "./scope.ts";
|
|
16
|
+
import { DEFAULT_STAGE, Scope } from "./scope.ts";
|
|
17
17
|
import { secret } from "./secret.ts";
|
|
18
18
|
import type { StateStoreType } from "./state.ts";
|
|
19
19
|
import type { LoggerApi } from "./util/cli.ts";
|
|
@@ -52,6 +52,7 @@ function parseCliArgs(): Partial<AlchemyOptions> {
|
|
|
52
52
|
if (stageIndex !== -1 && stageIndex + 1 < args.length) {
|
|
53
53
|
options.stage = args[stageIndex + 1];
|
|
54
54
|
}
|
|
55
|
+
options.stage ??= process.env.STAGE;
|
|
55
56
|
|
|
56
57
|
// Get password from environment variables
|
|
57
58
|
if (process.env.ALCHEMY_PASSWORD) {
|
|
@@ -189,25 +190,18 @@ async function _alchemy(
|
|
|
189
190
|
});
|
|
190
191
|
const root = new Scope({
|
|
191
192
|
...mergedOptions,
|
|
192
|
-
|
|
193
|
+
parent: undefined,
|
|
194
|
+
scopeName: appName,
|
|
193
195
|
phase,
|
|
194
196
|
password: mergedOptions?.password ?? process.env.ALCHEMY_PASSWORD,
|
|
195
197
|
telemetryClient,
|
|
196
198
|
});
|
|
199
|
+
const stageName = mergedOptions?.stage ?? DEFAULT_STAGE;
|
|
197
200
|
const stage = new Scope({
|
|
198
201
|
...mergedOptions,
|
|
199
|
-
scopeName:
|
|
200
|
-
mergedOptions?.stage ??
|
|
201
|
-
process.env.ALCHEMY_STAGE ??
|
|
202
|
-
process.env.USER ??
|
|
203
|
-
process.env.USERNAME,
|
|
204
202
|
parent: root,
|
|
205
|
-
|
|
206
|
-
stage:
|
|
207
|
-
mergedOptions?.stage ??
|
|
208
|
-
process.env.ALCHEMY_STAGE ??
|
|
209
|
-
process.env.USER ??
|
|
210
|
-
process.env.USERNAME,
|
|
203
|
+
scopeName: stageName,
|
|
204
|
+
stage: stageName,
|
|
211
205
|
});
|
|
212
206
|
try {
|
|
213
207
|
Scope.storage.enterWith(root);
|
|
@@ -455,6 +449,7 @@ async function run<T>(
|
|
|
455
449
|
});
|
|
456
450
|
const _scope = new Scope({
|
|
457
451
|
...options,
|
|
452
|
+
parent: options?.parent,
|
|
458
453
|
scopeName: id,
|
|
459
454
|
telemetryClient,
|
|
460
455
|
});
|
package/src/apply.ts
CHANGED
|
@@ -164,6 +164,7 @@ async function _apply<Out extends Resource>(
|
|
|
164
164
|
seq: resource[ResourceSeq],
|
|
165
165
|
props: state.oldProps,
|
|
166
166
|
state,
|
|
167
|
+
isReplacement: false,
|
|
167
168
|
replace: () => {
|
|
168
169
|
if (phase === "create") {
|
|
169
170
|
throw new Error(
|
|
@@ -216,6 +217,7 @@ async function _apply<Out extends Resource>(
|
|
|
216
217
|
seq: resource[ResourceSeq],
|
|
217
218
|
props: state.props,
|
|
218
219
|
state,
|
|
220
|
+
isReplacement: true,
|
|
219
221
|
replace: () => {
|
|
220
222
|
throw new Error(
|
|
221
223
|
`Resource ${resource[ResourceKind]} ${resource[ResourceFQN]} cannot be replaced in create phase.`,
|
package/src/build-date.ts
CHANGED
package/src/cloudflare/bucket.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { Resource, ResourceKind } from "../resource.ts";
|
|
|
4
4
|
import { bind } from "../runtime/bind.ts";
|
|
5
5
|
import type { Secret } from "../secret.ts";
|
|
6
6
|
import { logger } from "../util/logger.ts";
|
|
7
|
+
import { withExponentialBackoff } from "../util/retry.ts";
|
|
7
8
|
import { CloudflareApiError, handleApiError } from "./api-error.ts";
|
|
8
9
|
import { type CloudflareApi, createCloudflareApi } from "./api.ts";
|
|
9
10
|
import type { Bound } from "./bound.ts";
|
|
@@ -600,22 +601,29 @@ export async function updatePublicAccess(
|
|
|
600
601
|
): Promise<void> {
|
|
601
602
|
const headers = withJurisdiction({}, jurisdiction);
|
|
602
603
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
604
|
+
await withExponentialBackoff(
|
|
605
|
+
async () => {
|
|
606
|
+
const response = await api.put(
|
|
607
|
+
`/accounts/${api.accountId}/r2/buckets/${bucketName}/domains/managed`,
|
|
608
|
+
{
|
|
609
|
+
enabled: allowPublicAccess,
|
|
610
|
+
},
|
|
611
|
+
{ headers },
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
if (!response.ok) {
|
|
615
|
+
await handleApiError(
|
|
616
|
+
response,
|
|
617
|
+
"updating public access for",
|
|
618
|
+
"R2 bucket",
|
|
619
|
+
bucketName,
|
|
620
|
+
);
|
|
621
|
+
}
|
|
607
622
|
},
|
|
608
|
-
|
|
623
|
+
(err) => err.status === 404,
|
|
624
|
+
10,
|
|
625
|
+
1000,
|
|
609
626
|
);
|
|
610
|
-
|
|
611
|
-
if (!response.ok) {
|
|
612
|
-
await handleApiError(
|
|
613
|
-
response,
|
|
614
|
-
"updating public access for",
|
|
615
|
-
"R2 bucket",
|
|
616
|
-
bucketName,
|
|
617
|
-
);
|
|
618
|
-
}
|
|
619
627
|
}
|
|
620
628
|
|
|
621
629
|
/**
|
|
@@ -5,6 +5,7 @@ import { createAliasPlugin } from "./alias-plugin.ts";
|
|
|
5
5
|
import { external, external_als } from "./external.ts";
|
|
6
6
|
import { getNodeJSCompatMode } from "./nodejs-compat-mode.ts";
|
|
7
7
|
import { nodeJsCompatPlugin } from "./nodejs-compat.ts";
|
|
8
|
+
import { nodeJsImportWarningPlugin } from "./nodejs-import-warning-plugin.ts";
|
|
8
9
|
import { wasmPlugin } from "./wasm-plugin.ts";
|
|
9
10
|
|
|
10
11
|
interface DevWorkerContext {
|
|
@@ -71,7 +72,9 @@ export async function createWorkerDevContext<B extends Bindings>(
|
|
|
71
72
|
plugins: [
|
|
72
73
|
wasmPlugin,
|
|
73
74
|
...(props.bundle?.plugins ?? []),
|
|
74
|
-
|
|
75
|
+
nodeJsCompatMode === "v2"
|
|
76
|
+
? await nodeJsCompatPlugin()
|
|
77
|
+
: nodeJsImportWarningPlugin(nodeJsCompatMode),
|
|
75
78
|
...(props.bundle?.alias
|
|
76
79
|
? [
|
|
77
80
|
createAliasPlugin({
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { external, external_als } from "./external.ts";
|
|
14
14
|
import { getNodeJSCompatMode } from "./nodejs-compat-mode.ts";
|
|
15
15
|
import { nodeJsCompatPlugin } from "./nodejs-compat.ts";
|
|
16
|
+
import { nodeJsImportWarningPlugin } from "./nodejs-import-warning-plugin.ts";
|
|
16
17
|
import { wasmPlugin } from "./wasm-plugin.ts";
|
|
17
18
|
|
|
18
19
|
export type NoBundleResult = {
|
|
@@ -108,7 +109,9 @@ export async function bundleWorkerScript<B extends Bindings>(
|
|
|
108
109
|
plugins: [
|
|
109
110
|
wasmPlugin,
|
|
110
111
|
...(props.bundle?.plugins ?? []),
|
|
111
|
-
|
|
112
|
+
nodeJsCompatMode === "v2"
|
|
113
|
+
? await nodeJsCompatPlugin()
|
|
114
|
+
: nodeJsImportWarningPlugin(nodeJsCompatMode),
|
|
112
115
|
...(props.bundle?.alias
|
|
113
116
|
? [
|
|
114
117
|
createAliasPlugin({
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Plugin, PluginBuild } from "esbuild";
|
|
2
|
+
import kleur from "kleur";
|
|
3
|
+
import module from "node:module";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { logger } from "../../util/logger.ts";
|
|
6
|
+
|
|
7
|
+
const NODE_REGEX = new RegExp(
|
|
8
|
+
`^(node:)?(${module.builtinModules.filter((m) => m !== "async_hooks").join("|")})$`,
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ESBuild plugin to detect node:* imports and warn about missing compatibility flags.
|
|
13
|
+
*
|
|
14
|
+
* This plugin warns when:
|
|
15
|
+
* - Any node:* import is found without nodejs_compat flag
|
|
16
|
+
* - node:async_hooks is imported without nodejs_compat or nodejs_als flags
|
|
17
|
+
*/
|
|
18
|
+
export function nodeJsImportWarningPlugin(mode: "als" | null): Plugin {
|
|
19
|
+
return {
|
|
20
|
+
name: "nodejs-import-warning",
|
|
21
|
+
setup(build: PluginBuild) {
|
|
22
|
+
const packages = new Set<string>();
|
|
23
|
+
const nodeImporters = new Set<string>();
|
|
24
|
+
const alsImporters = new Set<string>();
|
|
25
|
+
|
|
26
|
+
build.onStart(() => {
|
|
27
|
+
packages.clear();
|
|
28
|
+
nodeImporters.clear();
|
|
29
|
+
alsImporters.clear();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
build.onResolve(
|
|
33
|
+
{
|
|
34
|
+
filter: NODE_REGEX,
|
|
35
|
+
},
|
|
36
|
+
(args) => {
|
|
37
|
+
packages.add(args.path);
|
|
38
|
+
nodeImporters.add(args.importer);
|
|
39
|
+
return null;
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
build.onResolve(
|
|
44
|
+
{
|
|
45
|
+
filter: /^(node:)?async_hooks$/,
|
|
46
|
+
},
|
|
47
|
+
(args) => {
|
|
48
|
+
alsImporters.add(args.importer);
|
|
49
|
+
return null;
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
build.onEnd(() => {
|
|
54
|
+
if (packages.size > 0) {
|
|
55
|
+
const formattedImports = Array.from(packages)
|
|
56
|
+
.sort()
|
|
57
|
+
.map((imp) => kleur.yellow(imp))
|
|
58
|
+
.join(", ");
|
|
59
|
+
|
|
60
|
+
logger.warn(
|
|
61
|
+
[
|
|
62
|
+
`Detected Node.js imports (${formattedImports}) but ${kleur.red("nodejs_compat")} compatibility flag is not set. `,
|
|
63
|
+
`Add ${kleur.blue("nodejs_compat")} to your compatibility flags and ensure compatibilityDate >= 2024-09-23. Imported from:`,
|
|
64
|
+
formatImporters(
|
|
65
|
+
nodeImporters,
|
|
66
|
+
build.initialOptions.absWorkingDir ?? process.cwd(),
|
|
67
|
+
),
|
|
68
|
+
].join("\n"),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (alsImporters.size > 0 && !mode) {
|
|
73
|
+
logger.warn(
|
|
74
|
+
[
|
|
75
|
+
`Detected Node.js imports but ${kleur.red("nodejs_als")} compatibility flag is not set. `,
|
|
76
|
+
`Add ${kleur.blue("nodejs_als")} to your compatibility flags. Imported from:`,
|
|
77
|
+
formatImporters(
|
|
78
|
+
alsImporters,
|
|
79
|
+
build.initialOptions.absWorkingDir ?? process.cwd(),
|
|
80
|
+
),
|
|
81
|
+
].join("\n"),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function formatImporters(importers: Set<string>, cwd: string) {
|
|
90
|
+
return Array.from(importers)
|
|
91
|
+
.sort()
|
|
92
|
+
.map((imp) => `- ${kleur.yellow(path.relative(cwd, imp))}`)
|
|
93
|
+
.join("\n");
|
|
94
|
+
}
|
|
@@ -96,6 +96,7 @@ export async function Container<T>(
|
|
|
96
96
|
const image = await Image(id, {
|
|
97
97
|
name: `${api.accountId}/${name}`,
|
|
98
98
|
tag,
|
|
99
|
+
build: props.build,
|
|
99
100
|
registry: {
|
|
100
101
|
server: "registry.cloudflare.com",
|
|
101
102
|
username: credentials.username || credentials.user!,
|
|
@@ -148,6 +149,15 @@ export interface ContainerApplicationProps extends CloudflareApiOptions {
|
|
|
148
149
|
namespaceId: string;
|
|
149
150
|
};
|
|
150
151
|
rollout?: ContainerApplicationRollout;
|
|
152
|
+
/**
|
|
153
|
+
* Whether to adopt an existing container application with the same name.
|
|
154
|
+
*
|
|
155
|
+
* If `true`, the resource will attempt to adopt an existing container application
|
|
156
|
+
* instead of failing when one already exists with the same name.
|
|
157
|
+
*
|
|
158
|
+
* @default false
|
|
159
|
+
*/
|
|
160
|
+
adopt?: boolean;
|
|
151
161
|
}
|
|
152
162
|
|
|
153
163
|
export type SchedulingPolicy =
|
|
@@ -155,7 +165,8 @@ export type SchedulingPolicy =
|
|
|
155
165
|
| "gpu"
|
|
156
166
|
| "regional"
|
|
157
167
|
| "fill_metals"
|
|
158
|
-
| "default"
|
|
168
|
+
| "default"
|
|
169
|
+
| (string & {});
|
|
159
170
|
|
|
160
171
|
export interface ContainerApplication
|
|
161
172
|
extends Resource<"cloudflare::ContainerApplication"> {
|
|
@@ -232,6 +243,21 @@ export interface ContainerApplication
|
|
|
232
243
|
* })
|
|
233
244
|
* }
|
|
234
245
|
* });
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* // Adopt an existing container application
|
|
249
|
+
* const existingApp = await ContainerApplication("existing-app", {
|
|
250
|
+
* name: "existing-app",
|
|
251
|
+
* adopt: true, // Will adopt instead of failing if already exists
|
|
252
|
+
* image: await Image("updated-app", {
|
|
253
|
+
* name: "updated-app",
|
|
254
|
+
* build: {
|
|
255
|
+
* context: "./docker/updated"
|
|
256
|
+
* }
|
|
257
|
+
* }),
|
|
258
|
+
* instances: 2,
|
|
259
|
+
* maxInstances: 5
|
|
260
|
+
* });
|
|
235
261
|
*/
|
|
236
262
|
export const ContainerApplication = Resource(
|
|
237
263
|
"cloudflare::ContainerApplication",
|
|
@@ -287,29 +313,78 @@ export const ContainerApplication = Resource(
|
|
|
287
313
|
name: application.name,
|
|
288
314
|
});
|
|
289
315
|
} else {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
316
|
+
let application: ContainerApplicationData;
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
application = await createContainerApplication(api, {
|
|
320
|
+
name: props.name,
|
|
321
|
+
scheduling_policy: props.schedulingPolicy ?? "default",
|
|
322
|
+
instances: props.instances ?? 1,
|
|
323
|
+
max_instances: props.maxInstances ?? 1,
|
|
324
|
+
durable_objects: props.durableObjects
|
|
325
|
+
? {
|
|
326
|
+
namespace_id: props.durableObjects.namespaceId,
|
|
327
|
+
}
|
|
328
|
+
: undefined,
|
|
329
|
+
constraints: {
|
|
330
|
+
tier: 1,
|
|
331
|
+
},
|
|
332
|
+
configuration: {
|
|
333
|
+
image: imageReference,
|
|
334
|
+
instance_type: props.instanceType ?? "dev",
|
|
335
|
+
observability: {
|
|
336
|
+
logs: {
|
|
337
|
+
enabled: true,
|
|
338
|
+
},
|
|
309
339
|
},
|
|
310
340
|
},
|
|
311
|
-
}
|
|
312
|
-
})
|
|
341
|
+
});
|
|
342
|
+
} catch (error) {
|
|
343
|
+
// Check if this is an "already exists" error and adopt is enabled
|
|
344
|
+
if (
|
|
345
|
+
props.adopt &&
|
|
346
|
+
error instanceof Error &&
|
|
347
|
+
error.message.includes("already exists")
|
|
348
|
+
) {
|
|
349
|
+
// Find the existing container application
|
|
350
|
+
const existingApplication = await findContainerApplicationByName(
|
|
351
|
+
api,
|
|
352
|
+
props.name,
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
if (!existingApplication) {
|
|
356
|
+
throw new Error(
|
|
357
|
+
`Failed to find existing container application '${props.name}' for adoption`,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Update the existing application with new properties
|
|
362
|
+
application = await updateContainerApplication(
|
|
363
|
+
api,
|
|
364
|
+
existingApplication.id,
|
|
365
|
+
{
|
|
366
|
+
instances: props.instances ?? existingApplication.instances,
|
|
367
|
+
max_instances:
|
|
368
|
+
props.maxInstances ?? existingApplication.max_instances,
|
|
369
|
+
scheduling_policy:
|
|
370
|
+
props.schedulingPolicy ?? existingApplication.scheduling_policy,
|
|
371
|
+
configuration,
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
// Create a rollout for the updated configuration
|
|
376
|
+
await createContainerApplicationRollout(api, application.id, {
|
|
377
|
+
description: "Update configuration on adoption",
|
|
378
|
+
strategy: "rolling",
|
|
379
|
+
kind: props.rollout?.kind ?? "full_auto",
|
|
380
|
+
step_percentage: props.rollout?.stepPercentage ?? 25,
|
|
381
|
+
target_configuration: configuration,
|
|
382
|
+
});
|
|
383
|
+
} else {
|
|
384
|
+
// Re-throw the error if adopt is false or different error
|
|
385
|
+
throw error;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
313
388
|
|
|
314
389
|
return this({
|
|
315
390
|
id: application.id,
|
|
@@ -321,7 +396,7 @@ export const ContainerApplication = Resource(
|
|
|
321
396
|
|
|
322
397
|
export interface ContainerApplicationData {
|
|
323
398
|
name: string;
|
|
324
|
-
scheduling_policy:
|
|
399
|
+
scheduling_policy: SchedulingPolicy;
|
|
325
400
|
instances: number;
|
|
326
401
|
max_instances: number;
|
|
327
402
|
constraints: {
|
|
@@ -377,6 +452,14 @@ export async function listContainerApplications(
|
|
|
377
452
|
);
|
|
378
453
|
}
|
|
379
454
|
|
|
455
|
+
export async function findContainerApplicationByName(
|
|
456
|
+
api: CloudflareApi,
|
|
457
|
+
name: string,
|
|
458
|
+
): Promise<ContainerApplicationData | undefined> {
|
|
459
|
+
const applications = await listContainerApplications(api);
|
|
460
|
+
return applications.find((app) => app.name === name);
|
|
461
|
+
}
|
|
462
|
+
|
|
380
463
|
export interface CreateContainerApplicationBody {
|
|
381
464
|
name: string;
|
|
382
465
|
max_instances: number;
|
|
@@ -492,12 +575,14 @@ export async function deleteContainerApplication(
|
|
|
492
575
|
`/accounts/${api.accountId}/containers/applications/${applicationId}`,
|
|
493
576
|
);
|
|
494
577
|
const result = (await response.json()) as any;
|
|
495
|
-
|
|
496
|
-
|
|
578
|
+
// Treat missing applications as already-deleted so that destroy() is idempotent
|
|
579
|
+
if (response.ok || response.status === 404) {
|
|
580
|
+
return result?.result;
|
|
497
581
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
582
|
+
const errorMessages = Array.isArray(result?.errors)
|
|
583
|
+
? result.errors.map((e: { message: string }) => e.message).join(", ")
|
|
584
|
+
: (result?.error ?? "Unknown error");
|
|
585
|
+
throw Error(`Failed to delete container application: ${errorMessages}`);
|
|
501
586
|
}
|
|
502
587
|
|
|
503
588
|
interface CreateRolloutApplicationRequest {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { alchemy } from "../../alchemy.ts";
|
|
2
2
|
import { ResourceScope } from "../../resource.ts";
|
|
3
3
|
import type { Scope } from "../../scope.ts";
|
|
4
|
+
import { serialize } from "../../serde.ts";
|
|
4
5
|
import type { State, StateStore } from "../../state.ts";
|
|
5
6
|
import { deserializeState } from "../../state.ts";
|
|
6
7
|
import { createCloudflareApi, type CloudflareApiOptions } from "../api.ts";
|
|
@@ -153,7 +154,10 @@ export class DOStateStore implements StateStore {
|
|
|
153
154
|
|
|
154
155
|
async set(key: string, value: State): Promise<void> {
|
|
155
156
|
const client = await this.getClient();
|
|
156
|
-
await client.rpc("set", {
|
|
157
|
+
await client.rpc("set", {
|
|
158
|
+
key: this.serializeKey(key),
|
|
159
|
+
value: await serialize(this.scope, value),
|
|
160
|
+
});
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
async delete(key: string): Promise<void> {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type AssetsConfig,
|
|
11
11
|
type WorkerProps,
|
|
12
12
|
} from "./worker.ts";
|
|
13
|
-
import { WranglerJson } from "./wrangler.json.ts";
|
|
13
|
+
import { WranglerJson, type WranglerJsonSpec } from "./wrangler.json.ts";
|
|
14
14
|
|
|
15
15
|
export interface WebsiteProps<B extends Bindings>
|
|
16
16
|
extends Omit<WorkerProps<B>, "name" | "assets" | "entrypoint"> {
|
|
@@ -102,6 +102,25 @@ export interface WebsiteProps<B extends Bindings>
|
|
|
102
102
|
command: string;
|
|
103
103
|
url: string;
|
|
104
104
|
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Transform hooks to modify generated configuration files
|
|
108
|
+
*/
|
|
109
|
+
transform?: {
|
|
110
|
+
/**
|
|
111
|
+
* Hook to modify the wrangler.json object before it's written
|
|
112
|
+
*
|
|
113
|
+
* This function receives the generated wrangler.json spec and should return
|
|
114
|
+
* a modified version. It's applied as the final transformation before the
|
|
115
|
+
* file is written to disk.
|
|
116
|
+
*
|
|
117
|
+
* @param spec - The generated wrangler.json specification
|
|
118
|
+
* @returns The modified wrangler.json specification
|
|
119
|
+
*/
|
|
120
|
+
wrangler?: (
|
|
121
|
+
spec: WranglerJsonSpec,
|
|
122
|
+
) => WranglerJsonSpec | Promise<WranglerJsonSpec>;
|
|
123
|
+
};
|
|
105
124
|
}
|
|
106
125
|
|
|
107
126
|
export type Website<B extends Bindings> = B extends { ASSETS: any }
|
|
@@ -193,6 +212,7 @@ export default {
|
|
|
193
212
|
// path must be relative to the wrangler.jsonc file
|
|
194
213
|
directory: path.relative(wranglerDir, assetsDirPath),
|
|
195
214
|
},
|
|
215
|
+
transform: props.transform,
|
|
196
216
|
});
|
|
197
217
|
}
|
|
198
218
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import {
|
|
2
|
+
MiniflareCoreError,
|
|
3
|
+
type Miniflare,
|
|
4
|
+
type MiniflareOptions,
|
|
5
|
+
type RemoteProxyConnectionString,
|
|
6
|
+
type WorkerOptions,
|
|
6
7
|
} from "miniflare";
|
|
7
8
|
import path from "node:path";
|
|
8
9
|
import { findOpenPort } from "../../util/find-open-port.ts";
|
|
@@ -66,7 +67,9 @@ class MiniflareServer {
|
|
|
66
67
|
}),
|
|
67
68
|
);
|
|
68
69
|
if (this.miniflare) {
|
|
69
|
-
await
|
|
70
|
+
await withErrorRewrite(
|
|
71
|
+
this.miniflare.setOptions(await this.miniflareOptions()),
|
|
72
|
+
);
|
|
70
73
|
} else {
|
|
71
74
|
const { Miniflare } = await import("miniflare").catch(() => {
|
|
72
75
|
throw new Error(
|
|
@@ -81,9 +84,8 @@ class MiniflareServer {
|
|
|
81
84
|
process.exit(0);
|
|
82
85
|
}
|
|
83
86
|
});
|
|
84
|
-
|
|
85
87
|
this.miniflare = new Miniflare(await this.miniflareOptions());
|
|
86
|
-
await this.miniflare.ready;
|
|
88
|
+
await withErrorRewrite(this.miniflare.ready);
|
|
87
89
|
}
|
|
88
90
|
const existing = this.servers.get(worker.name);
|
|
89
91
|
if (existing) {
|
|
@@ -191,6 +193,29 @@ class MiniflareServer {
|
|
|
191
193
|
}
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
export class ExternalDependencyError extends Error {
|
|
197
|
+
constructor() {
|
|
198
|
+
super(
|
|
199
|
+
'Miniflare detected an external dependency that could not be resolved. This typically occurs when the "nodejs_compat" or "nodejs_als" compatibility flag is not enabled.',
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function withErrorRewrite<T>(promise: Promise<T>) {
|
|
205
|
+
try {
|
|
206
|
+
return await promise;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (
|
|
209
|
+
error instanceof MiniflareCoreError &&
|
|
210
|
+
error.code === "ERR_MODULE_STRING_SCRIPT"
|
|
211
|
+
) {
|
|
212
|
+
throw new ExternalDependencyError();
|
|
213
|
+
} else {
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
194
219
|
declare global {
|
|
195
220
|
var _ALCHEMY_MINIFLARE_SERVER: MiniflareServer | undefined;
|
|
196
221
|
}
|
|
@@ -351,6 +351,21 @@ export async function prepareWorkerMetadata(
|
|
|
351
351
|
|
|
352
352
|
const bindings = (props.bindings ?? {}) as Bindings;
|
|
353
353
|
|
|
354
|
+
// Validate that all Container and DurableObjectNamespace bindings have unique IDs
|
|
355
|
+
const bindingIds = new Map<string, string>();
|
|
356
|
+
for (const [bindingName, binding] of Object.entries(bindings)) {
|
|
357
|
+
if (typeof binding === "object" && binding !== null && "id" in binding) {
|
|
358
|
+
if (isDurableObjectNamespace(binding) || isContainer(binding)) {
|
|
359
|
+
if (bindingIds.has(binding.id)) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
`Duplicate binding ID '${binding.id}' found for bindings '${bindingIds.get(binding.id)}' and '${bindingName}'. Container and DurableObjectNamespace bindings must have unique IDs.`,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
bindingIds.set(binding.id, bindingName);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
354
369
|
// Convert bindings to the format expected by the API
|
|
355
370
|
for (const [bindingName, binding] of Object.entries(bindings)) {
|
|
356
371
|
// Create a copy of the binding to avoid modifying the original
|