alchemy 0.78.0 → 0.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/alchemy.js +2 -1
- package/lib/cloudflare/bindings.d.ts +2 -2
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bindings.js.map +1 -1
- package/lib/cloudflare/bound.d.ts +1 -1
- package/lib/cloudflare/bound.d.ts.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/miniflare/build-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.js +10 -0
- 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 -2
- package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
- package/lib/cloudflare/website.d.ts +4 -0
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +36 -2
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts +1 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +16 -1
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker-subdomain.d.ts +1 -0
- package/lib/cloudflare/worker-subdomain.d.ts.map +1 -1
- package/lib/cloudflare/worker-subdomain.js +10 -2
- package/lib/cloudflare/worker-subdomain.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +10 -0
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +23 -3
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/workflow.d.ts +1 -0
- package/lib/cloudflare/workflow.d.ts.map +1 -1
- package/lib/cloudflare/workflow.js +6 -0
- package/lib/cloudflare/workflow.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +1 -1
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +16 -3
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/docker/api.d.ts +14 -0
- package/lib/docker/api.d.ts.map +1 -1
- package/lib/docker/api.js +18 -2
- package/lib/docker/api.js.map +1 -1
- package/lib/docker/container.d.ts +5 -0
- package/lib/docker/container.d.ts.map +1 -1
- package/lib/docker/container.js +69 -49
- package/lib/docker/container.js.map +1 -1
- package/lib/docker/volume.d.ts +5 -0
- package/lib/docker/volume.d.ts.map +1 -1
- package/lib/docker/volume.js +43 -19
- package/lib/docker/volume.js.map +1 -1
- package/lib/neon/project.d.ts +30 -16
- package/lib/neon/project.d.ts.map +1 -1
- package/lib/neon/project.js +123 -1
- package/lib/neon/project.js.map +1 -1
- package/lib/util/camel-to-snake.d.ts +1 -1
- package/lib/util/camel-to-snake.d.ts.map +1 -1
- package/lib/util/camel-to-snake.js +26 -13
- package/lib/util/camel-to-snake.js.map +1 -1
- package/lib/util/find-open-port.d.ts +1 -0
- package/lib/util/find-open-port.d.ts.map +1 -1
- package/lib/util/find-open-port.js +15 -0
- package/lib/util/find-open-port.js.map +1 -1
- package/package.json +1 -1
- package/src/cloudflare/bindings.ts +3 -1
- package/src/cloudflare/bound.ts +19 -13
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/miniflare/build-worker-options.ts +10 -0
- package/src/cloudflare/miniflare/miniflare-controller.ts +3 -2
- package/src/cloudflare/website.ts +43 -2
- package/src/cloudflare/worker-metadata.ts +19 -1
- package/src/cloudflare/worker-subdomain.ts +14 -2
- package/src/cloudflare/worker.ts +36 -2
- package/src/cloudflare/workflow.ts +9 -0
- package/src/cloudflare/wrangler.json.ts +14 -1
- package/src/docker/api.ts +24 -2
- package/src/docker/container.ts +83 -52
- package/src/docker/volume.ts +64 -27
- package/src/neon/project.ts +157 -2
- package/src/util/camel-to-snake.ts +34 -19
- package/src/util/find-open-port.ts +18 -0
- package/workers/tunnel-proxy.js +1 -1
package/package.json
CHANGED
|
@@ -26,10 +26,10 @@ import type { SecretRef as CloudflareSecretRef } from "./secret-ref.ts";
|
|
|
26
26
|
import type { Secret as CloudflareSecret } from "./secret.ts";
|
|
27
27
|
import type { VectorizeIndex } from "./vectorize-index.ts";
|
|
28
28
|
import type { VersionMetadata } from "./version-metadata.ts";
|
|
29
|
+
import type { WorkerLoader } from "./worker-loader.ts";
|
|
29
30
|
import type { WorkerRef } from "./worker-ref.ts";
|
|
30
31
|
import type { WorkerStub } from "./worker-stub.ts";
|
|
31
32
|
import type { Worker } from "./worker.ts";
|
|
32
|
-
import type { WorkerLoader } from "./worker-loader.ts";
|
|
33
33
|
import type { Workflow } from "./workflow.ts";
|
|
34
34
|
|
|
35
35
|
export type Bindings = {
|
|
@@ -72,6 +72,8 @@ export type Binding =
|
|
|
72
72
|
| string
|
|
73
73
|
| VectorizeIndex
|
|
74
74
|
| Worker
|
|
75
|
+
| Worker.DevDomain
|
|
76
|
+
| Worker.DevUrl
|
|
75
77
|
| WorkerStub
|
|
76
78
|
| WorkerRef
|
|
77
79
|
| WorkerEntrypoint
|
package/src/cloudflare/bound.ts
CHANGED
|
@@ -88,17 +88,23 @@ export type Bound<T extends Binding> =
|
|
|
88
88
|
? ImagesBinding
|
|
89
89
|
: T extends _VersionMetadata
|
|
90
90
|
? WorkerVersionMetadata
|
|
91
|
-
: T extends
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
: T extends
|
|
92
|
+
| _Worker.DevDomain
|
|
93
|
+
| _Worker.DevUrl
|
|
94
|
+
? string
|
|
95
|
+
: T extends Self
|
|
96
|
+
? Service
|
|
97
|
+
: T extends Json<
|
|
98
|
+
infer T
|
|
97
99
|
>
|
|
98
|
-
?
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
? T
|
|
101
|
+
: T extends _Container<
|
|
102
|
+
infer Obj
|
|
103
|
+
>
|
|
104
|
+
? DurableObjectNamespace<
|
|
105
|
+
Obj &
|
|
106
|
+
Rpc.DurableObjectBranded
|
|
107
|
+
>
|
|
108
|
+
: T extends undefined
|
|
109
|
+
? undefined
|
|
110
|
+
: Service;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as miniflare from "miniflare";
|
|
2
2
|
import path from "pathe";
|
|
3
3
|
import { assertNever } from "../../util/assert-never.ts";
|
|
4
|
+
import { reservePort } from "../../util/find-open-port.ts";
|
|
4
5
|
import type { HTTPServer } from "../../util/http.ts";
|
|
5
6
|
import { logger } from "../../util/logger.ts";
|
|
6
7
|
import type { CloudflareApi } from "../api.ts";
|
|
@@ -83,6 +84,7 @@ export const buildWorkerOptions = async (
|
|
|
83
84
|
// This exposes the worker as a route that can be accessed by setting the MF-Route-Override header.
|
|
84
85
|
routes: [input.name],
|
|
85
86
|
};
|
|
87
|
+
const port = input.port ?? (await reservePort(input.name));
|
|
86
88
|
for (const [key, binding] of Object.entries(input.bindings ?? {})) {
|
|
87
89
|
if (typeof binding === "string") {
|
|
88
90
|
(options.bindings ??= {})[key] = binding;
|
|
@@ -92,6 +94,14 @@ export const buildWorkerOptions = async (
|
|
|
92
94
|
(options.serviceBindings ??= {})[key] = miniflare.kCurrentWorker;
|
|
93
95
|
continue;
|
|
94
96
|
}
|
|
97
|
+
if (binding.type === "cloudflare::Worker::DevDomain") {
|
|
98
|
+
(options.bindings ??= {})[key] = `localhost:${port}`;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (binding.type === "cloudflare::Worker::DevUrl") {
|
|
102
|
+
(options.bindings ??= {})[key] = `http://localhost:${port}`;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
95
105
|
switch (binding.type) {
|
|
96
106
|
case "ai": {
|
|
97
107
|
const existing = remoteBindings.find((b) => b.type === "ai");
|
|
@@ -2,7 +2,7 @@ import * as miniflare from "miniflare";
|
|
|
2
2
|
import assert from "node:assert";
|
|
3
3
|
import path from "pathe";
|
|
4
4
|
import { Scope } from "../../scope.ts";
|
|
5
|
-
import {
|
|
5
|
+
import { reservePort } from "../../util/find-open-port.ts";
|
|
6
6
|
import type { HTTPServer } from "../../util/http.ts";
|
|
7
7
|
import { logger } from "../../util/logger.ts";
|
|
8
8
|
import { AsyncMutex } from "../../util/mutex.ts";
|
|
@@ -37,6 +37,7 @@ export class MiniflareController {
|
|
|
37
37
|
|
|
38
38
|
async add(input: MiniflareWorkerInput) {
|
|
39
39
|
const { watch, remoteProxy } = await buildWorkerOptions(input);
|
|
40
|
+
|
|
40
41
|
if (remoteProxy) {
|
|
41
42
|
this.remoteProxies.set(input.name, remoteProxy);
|
|
42
43
|
}
|
|
@@ -54,7 +55,7 @@ export class MiniflareController {
|
|
|
54
55
|
});
|
|
55
56
|
} else {
|
|
56
57
|
const proxy = await createMiniflareWorkerProxy({
|
|
57
|
-
port: input.port ?? (await
|
|
58
|
+
port: input.port ?? (await reservePort(input.name)),
|
|
58
59
|
getWorkerName: () => input.name,
|
|
59
60
|
miniflare,
|
|
60
61
|
mode: "local",
|
|
@@ -5,6 +5,7 @@ import { Exec } from "../os/index.ts";
|
|
|
5
5
|
import { Scope } from "../scope.ts";
|
|
6
6
|
import { dedent } from "../util/dedent.ts";
|
|
7
7
|
import { logger } from "../util/logger.ts";
|
|
8
|
+
import { createCloudflareApi } from "./api.ts";
|
|
8
9
|
import { Assets } from "./assets.ts";
|
|
9
10
|
import type { Bindings } from "./bindings.ts";
|
|
10
11
|
import { DEFAULT_COMPATIBILITY_DATE } from "./compatibility-date.gen.ts";
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
extractStringAndSecretBindings,
|
|
14
15
|
unencryptSecrets,
|
|
15
16
|
} from "./util/filter-env-bindings.ts";
|
|
17
|
+
import { computeWorkerDevDomain } from "./worker-subdomain.ts";
|
|
16
18
|
import { type AssetsConfig, Worker, type WorkerProps } from "./worker.ts";
|
|
17
19
|
import { WranglerJson, type WranglerJsonSpec } from "./wrangler.json.ts";
|
|
18
20
|
|
|
@@ -70,6 +72,10 @@ export interface WebsiteProps<B extends Bindings>
|
|
|
70
72
|
* The command to run to start the dev server
|
|
71
73
|
*/
|
|
72
74
|
command?: string;
|
|
75
|
+
/**
|
|
76
|
+
* The local domain to use for the dev server
|
|
77
|
+
*/
|
|
78
|
+
domain?: string;
|
|
73
79
|
/**
|
|
74
80
|
* Additional environment variables to set when running the dev command
|
|
75
81
|
*/
|
|
@@ -225,11 +231,46 @@ export async function Website<B extends Bindings>(
|
|
|
225
231
|
};
|
|
226
232
|
})();
|
|
227
233
|
const secrets = props.wrangler?.secrets ?? !props.wrangler?.path;
|
|
234
|
+
|
|
228
235
|
const env = {
|
|
229
|
-
...
|
|
230
|
-
...
|
|
236
|
+
...process.env,
|
|
237
|
+
...props.env,
|
|
231
238
|
...extractStringAndSecretBindings(props.bindings ?? {}, secrets),
|
|
232
239
|
};
|
|
240
|
+
if (props.bindings) {
|
|
241
|
+
for (const [key, value] of Object.entries(props.bindings)) {
|
|
242
|
+
if (typeof value !== "object") continue;
|
|
243
|
+
if (
|
|
244
|
+
value.type === "cloudflare::Worker::DevDomain" ||
|
|
245
|
+
value.type === "cloudflare::Worker::DevUrl"
|
|
246
|
+
) {
|
|
247
|
+
if (Scope.current.local) {
|
|
248
|
+
const domain = typeof dev === "object" ? dev.domain : undefined;
|
|
249
|
+
if (!domain) {
|
|
250
|
+
// we can't know which port the local web server (e.g. vite dev) will use
|
|
251
|
+
// vite dev # default to 5173 or configured in vite.config.ts
|
|
252
|
+
// vite dev --port XYZ
|
|
253
|
+
// next dev # who fucking knows
|
|
254
|
+
// for now: we require the user tell us the local domain
|
|
255
|
+
throw new Error(
|
|
256
|
+
"You must set `dev.domain` when running in local development mode and a Worker.DevDomain binding",
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
env[key] =
|
|
260
|
+
value.type === "cloudflare::Worker::DevDomain"
|
|
261
|
+
? domain
|
|
262
|
+
: `http://${domain}`;
|
|
263
|
+
} else {
|
|
264
|
+
const api = await createCloudflareApi(props);
|
|
265
|
+
const domain = await computeWorkerDevDomain(api, name);
|
|
266
|
+
env[key] =
|
|
267
|
+
value.type === "cloudflare::Worker::DevDomain"
|
|
268
|
+
? domain
|
|
269
|
+
: `https://${domain}`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
233
274
|
const worker = {
|
|
234
275
|
...workerProps,
|
|
235
276
|
name,
|
|
@@ -19,7 +19,13 @@ import type {
|
|
|
19
19
|
MultiStepMigration,
|
|
20
20
|
SingleStepMigration,
|
|
21
21
|
} from "./worker-migration.ts";
|
|
22
|
-
import {
|
|
22
|
+
import { computeWorkerDevDomain } from "./worker-subdomain.ts";
|
|
23
|
+
import {
|
|
24
|
+
isWorker,
|
|
25
|
+
Worker,
|
|
26
|
+
type AssetsConfig,
|
|
27
|
+
type WorkerProps,
|
|
28
|
+
} from "./worker.ts";
|
|
23
29
|
|
|
24
30
|
/**
|
|
25
31
|
* Metadata returned by Cloudflare API for a worker script
|
|
@@ -427,6 +433,18 @@ export async function prepareWorkerMetadata(
|
|
|
427
433
|
service: props.workerName,
|
|
428
434
|
entrypoint: binding.__entrypoint__,
|
|
429
435
|
});
|
|
436
|
+
} else if (binding.type === "cloudflare::Worker::DevDomain") {
|
|
437
|
+
meta.bindings.push({
|
|
438
|
+
type: "plain_text",
|
|
439
|
+
name: bindingName,
|
|
440
|
+
text: await computeWorkerDevDomain(api, props.workerName),
|
|
441
|
+
});
|
|
442
|
+
} else if (binding.type === "cloudflare::Worker::DevUrl") {
|
|
443
|
+
meta.bindings.push({
|
|
444
|
+
type: "plain_text",
|
|
445
|
+
name: bindingName,
|
|
446
|
+
text: `https://${await computeWorkerDevDomain(api, props.workerName)}`,
|
|
447
|
+
});
|
|
430
448
|
} else if (binding.type === "d1") {
|
|
431
449
|
meta.bindings.push({
|
|
432
450
|
type: "d1",
|
|
@@ -87,7 +87,9 @@ export async function disableWorkerSubdomain(
|
|
|
87
87
|
`disable subdomain for "${scriptName}"`,
|
|
88
88
|
api.post(
|
|
89
89
|
`/accounts/${api.accountId}/workers/scripts/${scriptName}/subdomain`,
|
|
90
|
-
{
|
|
90
|
+
{
|
|
91
|
+
enabled: false,
|
|
92
|
+
},
|
|
91
93
|
),
|
|
92
94
|
).catch((error) => {
|
|
93
95
|
if (error.status === 404) {
|
|
@@ -107,7 +109,10 @@ export async function enableWorkerSubdomain(
|
|
|
107
109
|
`enable subdomain for "${scriptName}"`,
|
|
108
110
|
api.post(
|
|
109
111
|
`/accounts/${api.accountId}/workers/scripts/${scriptName}/subdomain`,
|
|
110
|
-
{
|
|
112
|
+
{
|
|
113
|
+
enabled: true,
|
|
114
|
+
previews_enabled: true,
|
|
115
|
+
},
|
|
111
116
|
),
|
|
112
117
|
),
|
|
113
118
|
(error) => error instanceof CloudflareApiError && error.status === 404,
|
|
@@ -116,6 +121,13 @@ export async function enableWorkerSubdomain(
|
|
|
116
121
|
);
|
|
117
122
|
}
|
|
118
123
|
|
|
124
|
+
export async function computeWorkerDevDomain(
|
|
125
|
+
api: CloudflareApi,
|
|
126
|
+
scriptName: string,
|
|
127
|
+
) {
|
|
128
|
+
return `${scriptName}.${await getAccountSubdomain(api)}.workers.dev`;
|
|
129
|
+
}
|
|
130
|
+
|
|
119
131
|
export async function getWorkerSubdomain(
|
|
120
132
|
api: CloudflareApi,
|
|
121
133
|
scriptName: string,
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -51,11 +51,17 @@ import {
|
|
|
51
51
|
import {
|
|
52
52
|
type WorkerScriptMetadata,
|
|
53
53
|
bumpMigrationTagVersion,
|
|
54
|
+
getWorkerSettings,
|
|
54
55
|
prepareWorkerMetadata,
|
|
55
56
|
} from "./worker-metadata.ts";
|
|
56
57
|
import { WorkerSubdomain, disableWorkerSubdomain } from "./worker-subdomain.ts";
|
|
57
58
|
import { createTail } from "./worker-tail.ts";
|
|
58
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
Workflow,
|
|
61
|
+
deleteWorkflow,
|
|
62
|
+
isWorkflow,
|
|
63
|
+
upsertWorkflow,
|
|
64
|
+
} from "./workflow.ts";
|
|
59
65
|
|
|
60
66
|
// Previous versions of `Worker` used the `Bundle` resource.
|
|
61
67
|
// This import is here to avoid errors when destroying the `Bundle` resource.
|
|
@@ -803,6 +809,17 @@ export function Worker<const B extends Bindings>(
|
|
|
803
809
|
return _Worker(id, props as WorkerProps<B>);
|
|
804
810
|
}
|
|
805
811
|
|
|
812
|
+
export namespace Worker {
|
|
813
|
+
export type DevDomain = typeof DevDomain;
|
|
814
|
+
export const DevDomain = {
|
|
815
|
+
type: "cloudflare::Worker::DevDomain" as const,
|
|
816
|
+
};
|
|
817
|
+
export type DevUrl = typeof DevUrl;
|
|
818
|
+
export const DevUrl = {
|
|
819
|
+
type: "cloudflare::Worker::DevUrl" as const,
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
806
823
|
Worker.experimentalEntrypoint = <RPC extends Rpc.WorkerEntrypointBranded>(
|
|
807
824
|
worker: Worker | WorkerRef | Self,
|
|
808
825
|
entrypoint: string,
|
|
@@ -951,7 +968,10 @@ const _Worker = Resource(
|
|
|
951
968
|
await createEmptyWorker(api, options.name, props.version);
|
|
952
969
|
}
|
|
953
970
|
} else {
|
|
954
|
-
await
|
|
971
|
+
await Promise.all([
|
|
972
|
+
deleteQueueConsumers(api, options.name),
|
|
973
|
+
deleteWorkflows(api, options.name),
|
|
974
|
+
]);
|
|
955
975
|
await deleteWorker(api, {
|
|
956
976
|
scriptName: options.name,
|
|
957
977
|
dispatchNamespace: options.dispatchNamespace,
|
|
@@ -1708,6 +1728,20 @@ async function deleteQueueConsumers(api: CloudflareApi, scriptName: string) {
|
|
|
1708
1728
|
);
|
|
1709
1729
|
}
|
|
1710
1730
|
|
|
1731
|
+
async function deleteWorkflows(api: CloudflareApi, scriptName: string) {
|
|
1732
|
+
const settings = await getWorkerSettings(api, scriptName);
|
|
1733
|
+
await Promise.all(
|
|
1734
|
+
(settings?.bindings ?? []).map(async (binding) => {
|
|
1735
|
+
if (
|
|
1736
|
+
binding.type === "workflow" &&
|
|
1737
|
+
(binding.script_name === scriptName || !binding.script_name)
|
|
1738
|
+
) {
|
|
1739
|
+
await deleteWorkflow(api, binding.workflow_name);
|
|
1740
|
+
}
|
|
1741
|
+
}),
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1711
1745
|
export async function deleteWorker(
|
|
1712
1746
|
api: CloudflareApi,
|
|
1713
1747
|
props: {
|
|
@@ -116,3 +116,12 @@ export async function upsertWorkflow(
|
|
|
116
116
|
|
|
117
117
|
return body.result;
|
|
118
118
|
}
|
|
119
|
+
|
|
120
|
+
export async function deleteWorkflow(api: CloudflareApi, name: string) {
|
|
121
|
+
const response = await api.delete(
|
|
122
|
+
`/accounts/${api.accountId}/workflows/${name}`,
|
|
123
|
+
);
|
|
124
|
+
if (!response.ok && response.status !== 404) {
|
|
125
|
+
await handleApiError(response, "delete", "workflow", name);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -14,7 +14,8 @@ import type { EventSource } from "./event-source.ts";
|
|
|
14
14
|
import { isQueueEventSource } from "./event-source.ts";
|
|
15
15
|
import { isQueue } from "./queue.ts";
|
|
16
16
|
import { unencryptSecrets } from "./util/filter-env-bindings.ts";
|
|
17
|
-
import {
|
|
17
|
+
import { getAccountSubdomain } from "./worker-subdomain.ts";
|
|
18
|
+
import { isWorker, Worker, type WorkerProps } from "./worker.ts";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Properties for wrangler.json configuration file
|
|
@@ -180,6 +181,7 @@ export async function WranglerJson(
|
|
|
180
181
|
Scope.current.local && !props.worker.dev?.remote,
|
|
181
182
|
async () =>
|
|
182
183
|
worker.accountId ?? (await createCloudflareApi(worker)).accountId,
|
|
184
|
+
async () => await getAccountSubdomain(await createCloudflareApi(worker)),
|
|
183
185
|
);
|
|
184
186
|
}
|
|
185
187
|
|
|
@@ -253,6 +255,7 @@ async function processBindings(
|
|
|
253
255
|
writeSecrets: boolean,
|
|
254
256
|
local: boolean,
|
|
255
257
|
getAccountId: () => Promise<string>,
|
|
258
|
+
getAccountSubdomain: () => Promise<string>,
|
|
256
259
|
): Promise<void> {
|
|
257
260
|
// Arrays to collect different binding types
|
|
258
261
|
const kvNamespaces: WranglerJsonConfig["kv_namespaces"] = [];
|
|
@@ -322,6 +325,16 @@ async function processBindings(
|
|
|
322
325
|
service: workerName,
|
|
323
326
|
entrypoint: binding.__entrypoint__,
|
|
324
327
|
});
|
|
328
|
+
} else if (binding.type === "cloudflare::Worker::DevDomain") {
|
|
329
|
+
const subdomain = await getAccountSubdomain();
|
|
330
|
+
// Subdomain binding
|
|
331
|
+
(spec.vars ??= {})[bindingName] =
|
|
332
|
+
`${workerName}.${subdomain}.workers.dev`;
|
|
333
|
+
} else if (binding.type === "cloudflare::Worker::DevUrl") {
|
|
334
|
+
const subdomain = await getAccountSubdomain();
|
|
335
|
+
// Subdomain binding
|
|
336
|
+
(spec.vars ??= {})[bindingName] =
|
|
337
|
+
`https://${workerName}.${subdomain}.workers.dev`;
|
|
325
338
|
} else if (binding.type === "service") {
|
|
326
339
|
// Service binding
|
|
327
340
|
services.push({
|
package/src/docker/api.ts
CHANGED
|
@@ -69,6 +69,12 @@ type VolumeInfo = {
|
|
|
69
69
|
Scope: string;
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
+
type ContainerInfo = {
|
|
73
|
+
Id: string;
|
|
74
|
+
State: { Status: "created" | "running" | "paused" | "stopped" | "exited" };
|
|
75
|
+
Created: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
72
78
|
/**
|
|
73
79
|
* Docker API client that wraps Docker CLI commands
|
|
74
80
|
*/
|
|
@@ -134,7 +140,8 @@ export class DockerApi {
|
|
|
134
140
|
} catch (error: any) {
|
|
135
141
|
const message = stderr || error.message || "Command failed";
|
|
136
142
|
if (
|
|
137
|
-
message.includes("unexpected status from HEAD request")
|
|
143
|
+
(message.includes("unexpected status from HEAD request") ||
|
|
144
|
+
message.includes("502 Bad Gateway")) &&
|
|
138
145
|
remainingAttempts > 0
|
|
139
146
|
) {
|
|
140
147
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -350,6 +357,21 @@ export class DockerApi {
|
|
|
350
357
|
return stdout;
|
|
351
358
|
}
|
|
352
359
|
|
|
360
|
+
/**
|
|
361
|
+
* Get Docker container information
|
|
362
|
+
*
|
|
363
|
+
* @param containerId Container ID or name
|
|
364
|
+
* @returns Container details in JSON format
|
|
365
|
+
*/
|
|
366
|
+
async inspectContainer(containerId: string): Promise<ContainerInfo[]> {
|
|
367
|
+
const { stdout } = await this.exec(["container", "inspect", containerId]);
|
|
368
|
+
try {
|
|
369
|
+
return JSON.parse(stdout.trim()) as ContainerInfo[];
|
|
370
|
+
} catch (_error) {
|
|
371
|
+
return [];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
353
375
|
/**
|
|
354
376
|
* Check if a container exists
|
|
355
377
|
*
|
|
@@ -358,7 +380,7 @@ export class DockerApi {
|
|
|
358
380
|
*/
|
|
359
381
|
async containerExists(containerId: string): Promise<boolean> {
|
|
360
382
|
try {
|
|
361
|
-
await this.
|
|
383
|
+
await this.inspectContainer(containerId);
|
|
362
384
|
return true;
|
|
363
385
|
} catch (_error) {
|
|
364
386
|
return false;
|
package/src/docker/container.ts
CHANGED
|
@@ -176,6 +176,12 @@ export interface ContainerProps {
|
|
|
176
176
|
* Healthcheck configuration
|
|
177
177
|
*/
|
|
178
178
|
healthcheck?: HealthcheckConfig;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Whether to adopt the container if it already exists
|
|
182
|
+
* @default false
|
|
183
|
+
*/
|
|
184
|
+
adopt?: boolean;
|
|
179
185
|
}
|
|
180
186
|
|
|
181
187
|
/**
|
|
@@ -303,71 +309,96 @@ export const Container = Resource(
|
|
|
303
309
|
|
|
304
310
|
// Return destroyed state
|
|
305
311
|
return this.destroy();
|
|
306
|
-
}
|
|
307
|
-
let containerState: NonNullable<Container["state"]> = "created";
|
|
312
|
+
}
|
|
308
313
|
|
|
309
|
-
|
|
310
|
-
// Check if container already exists (for update)
|
|
311
|
-
const containerExists = await api.containerExists(containerName);
|
|
314
|
+
let containerState: NonNullable<Container["state"]> = "created";
|
|
312
315
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
+
// Check if container already exists
|
|
317
|
+
const containerExists = await api.containerExists(containerName);
|
|
318
|
+
|
|
319
|
+
if (containerExists) {
|
|
320
|
+
if (this.phase === "update") {
|
|
321
|
+
// Remove existing container for update
|
|
322
|
+
await api.removeContainer(containerName, true);
|
|
323
|
+
} else {
|
|
324
|
+
// Create phase - check for adoption
|
|
325
|
+
if (!props.adopt) {
|
|
326
|
+
throw new Error(
|
|
327
|
+
`Container "${containerName}" already exists. Use adopt: true to adopt it.`,
|
|
328
|
+
);
|
|
316
329
|
}
|
|
317
|
-
}
|
|
318
330
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
331
|
+
// Adopt existing container
|
|
332
|
+
const containerInfos = await api.inspectContainer(containerName);
|
|
333
|
+
const containerInfo = containerInfos[0];
|
|
334
|
+
let adoptedState = containerInfo.State.Status;
|
|
335
|
+
|
|
336
|
+
// Optionally start the container if requested
|
|
337
|
+
if (props.start && containerInfo.State.Status !== "running") {
|
|
338
|
+
await api.startContainer(containerName);
|
|
339
|
+
adoptedState = "running";
|
|
325
340
|
}
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
...props,
|
|
344
|
+
id: containerInfo.Id,
|
|
345
|
+
name: containerName,
|
|
346
|
+
state: adoptedState,
|
|
347
|
+
createdAt: new Date(containerInfo.Created).getTime(),
|
|
348
|
+
};
|
|
326
349
|
}
|
|
350
|
+
}
|
|
327
351
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
`${volume.containerPath}${readOnlyFlag}`;
|
|
335
|
-
}
|
|
352
|
+
// Prepare port mappings
|
|
353
|
+
const portMappings: Record<string, string> = {};
|
|
354
|
+
if (props.ports) {
|
|
355
|
+
for (const port of props.ports) {
|
|
356
|
+
const protocol = port.protocol || "tcp";
|
|
357
|
+
portMappings[`${port.external}`] = `${port.internal}/${protocol}`;
|
|
336
358
|
}
|
|
359
|
+
}
|
|
337
360
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
// Connect to networks if specified
|
|
348
|
-
if (props.networks) {
|
|
349
|
-
for (const network of props.networks) {
|
|
350
|
-
const networkId =
|
|
351
|
-
typeof network === "string" ? network : network.name;
|
|
352
|
-
await api.connectNetwork(containerId, networkId, {
|
|
353
|
-
aliases: network.aliases,
|
|
354
|
-
});
|
|
355
|
-
}
|
|
361
|
+
// Prepare volume mappings
|
|
362
|
+
const volumeMappings: Record<string, string> = {};
|
|
363
|
+
if (props.volumes) {
|
|
364
|
+
for (const volume of props.volumes) {
|
|
365
|
+
const readOnlyFlag = volume.readOnly ? ":ro" : "";
|
|
366
|
+
volumeMappings[volume.hostPath] =
|
|
367
|
+
`${volume.containerPath}${readOnlyFlag}`;
|
|
356
368
|
}
|
|
369
|
+
}
|
|
357
370
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
371
|
+
// Create new container
|
|
372
|
+
const containerId = await api.createContainer(imageRef, containerName, {
|
|
373
|
+
ports: portMappings,
|
|
374
|
+
env: props.environment,
|
|
375
|
+
volumes: volumeMappings,
|
|
376
|
+
cmd: props.command,
|
|
377
|
+
healthcheck: props.healthcheck,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Connect to networks if specified
|
|
381
|
+
if (props.networks) {
|
|
382
|
+
for (const network of props.networks) {
|
|
383
|
+
const networkId = typeof network === "string" ? network : network.name;
|
|
384
|
+
await api.connectNetwork(containerId, networkId, {
|
|
385
|
+
aliases: network.aliases,
|
|
386
|
+
});
|
|
362
387
|
}
|
|
388
|
+
}
|
|
363
389
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
state: containerState,
|
|
369
|
-
createdAt: Date.now(),
|
|
370
|
-
};
|
|
390
|
+
// Start container if requested
|
|
391
|
+
if (props.start) {
|
|
392
|
+
await api.startContainer(containerId);
|
|
393
|
+
containerState = "running";
|
|
371
394
|
}
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
...props,
|
|
398
|
+
id: containerId,
|
|
399
|
+
name: containerName,
|
|
400
|
+
state: containerState,
|
|
401
|
+
createdAt: Date.now(),
|
|
402
|
+
};
|
|
372
403
|
},
|
|
373
404
|
);
|