alchemy 0.93.0 → 0.93.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 +2 -2
- package/lib/cloudflare/ai-search-namespace.d.ts.map +1 -1
- package/lib/cloudflare/ai-search-namespace.js +7 -10
- package/lib/cloudflare/ai-search-namespace.js.map +1 -1
- package/lib/cloudflare/ai-search.d.ts.map +1 -1
- package/lib/cloudflare/ai-search.js +7 -24
- package/lib/cloudflare/ai-search.js.map +1 -1
- package/lib/cloudflare/container.d.ts.map +1 -1
- package/lib/cloudflare/container.js +14 -8
- package/lib/cloudflare/container.js.map +1 -1
- package/lib/cloudflare/custom-domain.d.ts +14 -0
- package/lib/cloudflare/custom-domain.d.ts.map +1 -1
- package/lib/cloudflare/custom-domain.js +3 -0
- package/lib/cloudflare/custom-domain.js.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.js +12 -6
- package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.js +31 -13
- package/lib/cloudflare/miniflare/remote-binding-proxy.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 +15 -1
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +6 -0
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +3 -0
- package/lib/cloudflare/worker.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 +4 -44
- package/lib/docker/image.js.map +1 -1
- package/lib/docker/registry.d.ts +11 -0
- package/lib/docker/registry.d.ts.map +1 -0
- package/lib/docker/registry.js +41 -0
- package/lib/docker/registry.js.map +1 -0
- package/package.json +2 -2
- package/src/cloudflare/ai-search-namespace.ts +7 -11
- package/src/cloudflare/ai-search.ts +7 -26
- package/src/cloudflare/container.ts +15 -8
- package/src/cloudflare/custom-domain.ts +25 -1
- package/src/cloudflare/miniflare/build-worker-options.ts +19 -9
- package/src/cloudflare/miniflare/remote-binding-proxy.ts +58 -34
- package/src/cloudflare/website.ts +19 -1
- package/src/cloudflare/worker.ts +9 -0
- package/src/docker/image.ts +5 -56
- package/src/docker/registry.ts +62 -0
- package/workers/tunnel-proxy.js +1 -1
package/lib/docker/image.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/docker/image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/docker/image.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Secret as SecretValue } from "../secret.ts";
|
|
2
|
+
export interface RegistryPushCredentials {
|
|
3
|
+
server: string;
|
|
4
|
+
username: string;
|
|
5
|
+
password: string | SecretValue;
|
|
6
|
+
}
|
|
7
|
+
export declare function pushImageToRegistry(imageRef: string, registry: RegistryPushCredentials): Promise<{
|
|
8
|
+
imageRef: string;
|
|
9
|
+
repoDigest?: string;
|
|
10
|
+
}>;
|
|
11
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/docker/registry.ts"],"names":[],"mappings":"AAGA,OAAO,EAAU,KAAK,MAAM,IAAI,WAAW,EAAE,MAAM,cAAc,CAAC;AAGlE,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,WAAW,CAAC;CAChC;AAED,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAAC;IACT,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC,CA2CD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "pathe";
|
|
4
|
+
import { Secret } from "../secret.js";
|
|
5
|
+
import { DockerApi } from "./api.js";
|
|
6
|
+
export async function pushImageToRegistry(imageRef, registry) {
|
|
7
|
+
const registryHost = registry.server.replace(/\/$/, "");
|
|
8
|
+
const password = Secret.unwrap(registry.password);
|
|
9
|
+
const firstSegment = imageRef.split("/")[0];
|
|
10
|
+
const hasRegistryPrefix = firstSegment.includes(".");
|
|
11
|
+
const targetImage = hasRegistryPrefix
|
|
12
|
+
? imageRef
|
|
13
|
+
: `${registryHost}/${imageRef}`;
|
|
14
|
+
let repoDigest;
|
|
15
|
+
let api;
|
|
16
|
+
try {
|
|
17
|
+
const tempConfigDir = await fs.mkdtemp(path.join(os.tmpdir(), "docker-config-"));
|
|
18
|
+
api = new DockerApi({ configDir: tempConfigDir });
|
|
19
|
+
await api.login(registryHost, registry.username, password);
|
|
20
|
+
if (targetImage !== imageRef) {
|
|
21
|
+
await api.exec(["tag", imageRef, targetImage]);
|
|
22
|
+
}
|
|
23
|
+
const { stdout } = await api.exec(["push", targetImage]);
|
|
24
|
+
const digestMatch = /digest:\s+([a-z0-9]+:[a-f0-9]{64})/.exec(stdout);
|
|
25
|
+
if (digestMatch) {
|
|
26
|
+
const digestHash = digestMatch[1];
|
|
27
|
+
const [repoWithoutTag] = targetImage.split(":").length > 2
|
|
28
|
+
? [targetImage]
|
|
29
|
+
: [targetImage.substring(0, targetImage.lastIndexOf(":"))];
|
|
30
|
+
repoDigest = `${repoWithoutTag}@${digestHash}`;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
imageRef: targetImage,
|
|
34
|
+
repoDigest,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
await api?.logout(registryHost);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/docker/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,OAAO,CAAC;AACzB,OAAO,EAAE,MAAM,EAA8B,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAQrC,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,QAAgB,EAChB,QAAiC;IAKjC,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAElD,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,MAAM,iBAAiB,GAAG,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACrD,MAAM,WAAW,GAAG,iBAAiB;QACnC,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,GAAG,YAAY,IAAI,QAAQ,EAAE,CAAC;IAElC,IAAI,UAA8B,CAAC;IACnC,IAAI,GAA0B,CAAC;IAE/B,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,MAAM,EAAE,CAAC,OAAO,CACpC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CACzC,CAAC;QACF,GAAG,GAAG,IAAI,SAAS,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC,CAAC;QAElD,MAAM,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAE3D,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;QACjD,CAAC;QAED,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;QACzD,MAAM,WAAW,GAAG,oCAAoC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtE,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;YAClC,MAAM,CAAC,cAAc,CAAC,GACpB,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC;gBAC/B,CAAC,CAAC,CAAC,WAAW,CAAC;gBACf,CAAC,CAAC,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC/D,UAAU,GAAG,GAAG,cAAc,IAAI,UAAU,EAAE,CAAC;QACjD,CAAC;QAED,OAAO;YACL,QAAQ,EAAE,WAAW;YACrB,UAAU;SACX,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,GAAG,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;IAClC,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alchemy",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"author": "Sam Goodwin <sam@alchemy.run>",
|
|
6
6
|
"homepage": "https://alchemy.run",
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
"glob": "^10.0.0",
|
|
196
196
|
"jszip": "^3.0.0",
|
|
197
197
|
"libsodium-wrappers": "^0.8.0",
|
|
198
|
-
"miniflare": "4.
|
|
198
|
+
"miniflare": "4.20260329.0",
|
|
199
199
|
"neverthrow": "^8.2.0",
|
|
200
200
|
"open": "^10.1.2",
|
|
201
201
|
"openapi-types": "^12.1.3",
|
|
@@ -194,17 +194,13 @@ export const AiSearchNamespace = Resource(
|
|
|
194
194
|
);
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
createdAt: new Date().toISOString(),
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
197
|
+
// NOTE: AI Search is an always-remote binding (no Miniflare-native
|
|
198
|
+
// implementation). `alchemy dev` wires the worker binding via
|
|
199
|
+
// `remote-binding-proxy`, which requires the namespace to actually exist
|
|
200
|
+
// on Cloudflare at preview-token creation time — a locally mocked
|
|
201
|
+
// resource would cause the Worker deploy to fail with error 10359
|
|
202
|
+
// ("namespace … does not exist"). Follow the same pattern as Vectorize
|
|
203
|
+
// and skip the `scope.local` mock branch entirely.
|
|
208
204
|
const api = await createCloudflareApi(props);
|
|
209
205
|
|
|
210
206
|
if (this.phase === "delete") {
|
|
@@ -373,32 +373,13 @@ export const AiSearch = Resource(
|
|
|
373
373
|
// Resolve namespace: AiSearchNamespace resource → string, default to "default"
|
|
374
374
|
const namespace = resolveNamespace(props.namespace);
|
|
375
375
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
this.scope.createPhysicalName(id, "-", 32);
|
|
384
|
-
const now = new Date().toISOString();
|
|
385
|
-
// `AiSearch` is `SnakeToCamel<ApiResponse>` intersected with extras —
|
|
386
|
-
// all non-required fields are optional, so we only populate what's
|
|
387
|
-
// needed. No `as unknown as` escape hatch required.
|
|
388
|
-
const mock: AiSearch = {
|
|
389
|
-
id: name,
|
|
390
|
-
name,
|
|
391
|
-
namespace,
|
|
392
|
-
accountId: "local",
|
|
393
|
-
accountTag: "local",
|
|
394
|
-
createdAt: now,
|
|
395
|
-
internalId: name,
|
|
396
|
-
modifiedAt: now,
|
|
397
|
-
vectorizeName: "",
|
|
398
|
-
};
|
|
399
|
-
return mock;
|
|
400
|
-
}
|
|
401
|
-
|
|
376
|
+
// NOTE: AI Search is an always-remote binding (no Miniflare-native
|
|
377
|
+
// implementation). `alchemy dev` wires the worker binding via
|
|
378
|
+
// `remote-binding-proxy`, which requires the instance to actually exist
|
|
379
|
+
// on Cloudflare at preview-token creation time — a locally mocked
|
|
380
|
+
// resource would cause the Worker deploy to fail with error 10360
|
|
381
|
+
// ("instance … not found"). Follow the same pattern as Vectorize and
|
|
382
|
+
// skip the `scope.local` mock branch entirely.
|
|
402
383
|
const api = await createCloudflareApi(props);
|
|
403
384
|
|
|
404
385
|
const validateBucketSource = async (
|
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
type DockerBuildOptions,
|
|
6
6
|
type ImageProps,
|
|
7
7
|
} from "../docker/image.ts";
|
|
8
|
+
import { pushImageToRegistry } from "../docker/registry.ts";
|
|
8
9
|
import type { RemoteImage } from "../docker/remote-image.ts";
|
|
9
10
|
import { Resource } from "../resource.ts";
|
|
10
11
|
import { Scope } from "../scope.ts";
|
|
11
|
-
import { secret } from "../secret.ts";
|
|
12
12
|
import {
|
|
13
13
|
createCloudflareApi,
|
|
14
14
|
type CloudflareApi,
|
|
@@ -467,7 +467,6 @@ export async function Container<T>(
|
|
|
467
467
|
}
|
|
468
468
|
|
|
469
469
|
const api = await createCloudflareApi(props);
|
|
470
|
-
const credentials = await getContainerCredentials(api);
|
|
471
470
|
|
|
472
471
|
const image = await Image(id, {
|
|
473
472
|
name: `${api.accountId}/${name}`,
|
|
@@ -482,16 +481,24 @@ export async function Container<T>(
|
|
|
482
481
|
platform: "linux/amd64",
|
|
483
482
|
context: process.cwd(),
|
|
484
483
|
},
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
username: credentials.username || credentials.user!,
|
|
488
|
-
password: secret(credentials.password),
|
|
489
|
-
},
|
|
484
|
+
image: props.image,
|
|
485
|
+
skipPush: true,
|
|
490
486
|
} as ImageProps);
|
|
491
487
|
|
|
488
|
+
const credentials = await getContainerCredentials(api);
|
|
489
|
+
const pushedImage = await pushImageToRegistry(image.imageRef, {
|
|
490
|
+
server: "registry.cloudflare.com",
|
|
491
|
+
username: credentials.username || credentials.user!,
|
|
492
|
+
password: credentials.password,
|
|
493
|
+
});
|
|
494
|
+
|
|
492
495
|
return {
|
|
493
496
|
...output,
|
|
494
|
-
image
|
|
497
|
+
image: {
|
|
498
|
+
...image,
|
|
499
|
+
imageRef: pushedImage.imageRef,
|
|
500
|
+
repoDigest: pushedImage.repoDigest,
|
|
501
|
+
},
|
|
495
502
|
};
|
|
496
503
|
}
|
|
497
504
|
|
|
@@ -44,6 +44,21 @@ export interface CustomDomainProps extends CloudflareApiOptions {
|
|
|
44
44
|
*/
|
|
45
45
|
adopt?: boolean;
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* If true, forcibly transfer the hostname when it is currently bound to a
|
|
49
|
+
* different Worker service. Without this flag, Cloudflare rejects the
|
|
50
|
+
* upsert with HTTP 409 / error code 100116 "Hostname '<host>' already in
|
|
51
|
+
* use by other custom domain".
|
|
52
|
+
*
|
|
53
|
+
* This is destructive to the previously bound Worker (its traffic on this
|
|
54
|
+
* hostname stops immediately), so it is opt-in. Only relevant when the
|
|
55
|
+
* existing binding points at a different `workerName` or `environment`.
|
|
56
|
+
*
|
|
57
|
+
* @see https://developers.cloudflare.com/api/resources/workers/subresources/domains/methods/update/
|
|
58
|
+
* @default false
|
|
59
|
+
*/
|
|
60
|
+
overrideExistingOrigin?: boolean;
|
|
61
|
+
|
|
47
62
|
/**
|
|
48
63
|
* Whether to delete the custom domain when removed from Alchemy.
|
|
49
64
|
* If set to false, the custom domain will remain but the resource will be removed from state.
|
|
@@ -269,12 +284,21 @@ async function ensureCustomDomain(
|
|
|
269
284
|
if (!bindingExists || needsUpdate) {
|
|
270
285
|
operationPerformed = bindingExists ? "update" : "create";
|
|
271
286
|
|
|
272
|
-
const putPayload
|
|
287
|
+
const putPayload: {
|
|
288
|
+
zone_id: string;
|
|
289
|
+
hostname: string;
|
|
290
|
+
service: string;
|
|
291
|
+
environment: string;
|
|
292
|
+
override_existing_origin?: boolean;
|
|
293
|
+
} = {
|
|
273
294
|
zone_id: props.zoneId,
|
|
274
295
|
hostname: domainHostname,
|
|
275
296
|
service: props.workerName,
|
|
276
297
|
environment: environment,
|
|
277
298
|
};
|
|
299
|
+
if (props.overrideExistingOrigin !== undefined) {
|
|
300
|
+
putPayload.override_existing_origin = props.overrideExistingOrigin;
|
|
301
|
+
}
|
|
278
302
|
|
|
279
303
|
const putResponse = await api.put(
|
|
280
304
|
`/accounts/${api.accountId}/workers/domains`,
|
|
@@ -40,8 +40,6 @@ type RemoteBinding =
|
|
|
40
40
|
{
|
|
41
41
|
type:
|
|
42
42
|
| "ai"
|
|
43
|
-
| "ai_search"
|
|
44
|
-
| "ai_search_namespace"
|
|
45
43
|
| "browser"
|
|
46
44
|
| "dispatch_namespace"
|
|
47
45
|
| "mtls_certificate"
|
|
@@ -57,7 +55,13 @@ type RemoteBinding =
|
|
|
57
55
|
| Extract<
|
|
58
56
|
WorkerBindingSpec,
|
|
59
57
|
{ type: "send_email" | "service" | "vpc_service" }
|
|
60
|
-
|
|
58
|
+
>
|
|
59
|
+
// AI Search bindings reject the `raw` flag at the Cloudflare API
|
|
60
|
+
// validation layer (10333). Wrangler's upload form deliberately omits
|
|
61
|
+
// `raw` for these two binding types in
|
|
62
|
+
// packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts,
|
|
63
|
+
// so we mirror that here for the remote-binding-proxy upload.
|
|
64
|
+
| Extract<WorkerBindingSpec, { type: "ai_search" | "ai_search_namespace" }>;
|
|
61
65
|
|
|
62
66
|
type BaseWorkerOptions = {
|
|
63
67
|
[K in keyof miniflare.WorkerOptions]: K extends
|
|
@@ -96,24 +100,30 @@ export const buildWorkerOptions = async (
|
|
|
96
100
|
}
|
|
97
101
|
if (isAiSearch(binding)) {
|
|
98
102
|
// AI Search instance bindings are not supported natively by Miniflare;
|
|
99
|
-
// proxy them to the deployed instance
|
|
100
|
-
// `vectorize`, etc.). Instance
|
|
101
|
-
// default namespace on the CF side,
|
|
102
|
-
// surfaced in the remote-proxy metadata.
|
|
103
|
+
// proxy them to the deployed instance via the `remote-binding-proxy`
|
|
104
|
+
// worker (same mechanism used by `ai`, `vectorize`, etc.). Instance
|
|
105
|
+
// bindings are always scoped to the default namespace on the CF side,
|
|
106
|
+
// so the namespace need not be surfaced in the remote-proxy metadata.
|
|
107
|
+
//
|
|
108
|
+
// Note: unlike `ai`/`browser`/`vectorize`, AI Search bindings must NOT
|
|
109
|
+
// carry `raw: true` — CF's API rejects that field on `ai_search` and
|
|
110
|
+
// `ai_search_namespace` bindings (error 10333). This mirrors wrangler's
|
|
111
|
+
// create-worker-upload-form.ts which also omits `raw` for these two
|
|
112
|
+
// binding types.
|
|
103
113
|
remoteBindings.push({
|
|
104
114
|
type: "ai_search",
|
|
105
115
|
name: key,
|
|
106
116
|
instance_name: binding.name,
|
|
107
|
-
raw: true,
|
|
108
117
|
});
|
|
109
118
|
continue;
|
|
110
119
|
}
|
|
111
120
|
if (isAiSearchNamespace(binding)) {
|
|
121
|
+
// See comment above on `ai_search`: `raw: true` is rejected by CF for
|
|
122
|
+
// this binding type, so we push without it.
|
|
112
123
|
remoteBindings.push({
|
|
113
124
|
type: "ai_search_namespace",
|
|
114
125
|
name: key,
|
|
115
126
|
namespace: binding.namespace,
|
|
116
|
-
raw: true,
|
|
117
127
|
});
|
|
118
128
|
continue;
|
|
119
129
|
}
|
|
@@ -39,7 +39,12 @@ export async function createRemoteProxyWorker(input: {
|
|
|
39
39
|
bindings: WorkerBindingSpec[];
|
|
40
40
|
}): Promise<RemoteBindingProxy> {
|
|
41
41
|
const script = await getInternalWorkerBundle("remote-binding-proxy");
|
|
42
|
-
|
|
42
|
+
// We must create the preview token BEFORE probing for Cloudflare Access:
|
|
43
|
+
// a preview worker URL only returns the Access 302 when the request carries
|
|
44
|
+
// the preview token (otherwise CF responds with 404 regardless of Access
|
|
45
|
+
// configuration). Probing without the token would incorrectly report
|
|
46
|
+
// "no Access" for accounts that do gate workers.dev behind Access.
|
|
47
|
+
const [token, subdomain] = await Promise.all([
|
|
43
48
|
createWorkersPreviewToken(input.api, {
|
|
44
49
|
name: input.name,
|
|
45
50
|
metadata: {
|
|
@@ -54,14 +59,10 @@ export async function createRemoteProxyWorker(input: {
|
|
|
54
59
|
minimal_mode: true,
|
|
55
60
|
},
|
|
56
61
|
}),
|
|
57
|
-
getAccountSubdomain(input.api)
|
|
58
|
-
const host = `${input.name}.${subdomain}.workers.dev`;
|
|
59
|
-
return {
|
|
60
|
-
host,
|
|
61
|
-
accessToken: await getAccessToken(host),
|
|
62
|
-
};
|
|
63
|
-
}),
|
|
62
|
+
getAccountSubdomain(input.api),
|
|
64
63
|
]);
|
|
64
|
+
const host = `${input.name}.${subdomain}.workers.dev`;
|
|
65
|
+
const accessToken = await getAccessToken(host, token);
|
|
65
66
|
|
|
66
67
|
const baseHeaders: Record<string, string> = {
|
|
67
68
|
"cf-workers-preview-token": token,
|
|
@@ -158,7 +159,13 @@ async function createWorkersPreviewToken(
|
|
|
158
159
|
|
|
159
160
|
async function prewarm(url: string, previewToken: string) {
|
|
160
161
|
try {
|
|
161
|
-
|
|
162
|
+
// Pass the preview token to the Access probe — prewarm targets a preview
|
|
163
|
+
// worker URL, which only surfaces an Access 302 when the request carries
|
|
164
|
+
// the preview token.
|
|
165
|
+
const accessToken = await getAccessToken(
|
|
166
|
+
new URL(url).hostname,
|
|
167
|
+
previewToken,
|
|
168
|
+
);
|
|
162
169
|
await fetch(url, {
|
|
163
170
|
method: "POST",
|
|
164
171
|
headers: {
|
|
@@ -197,42 +204,59 @@ async function createWorkersPreviewSession(api: CloudflareApi) {
|
|
|
197
204
|
/**
|
|
198
205
|
* If the given domain uses Cloudflare Access, fetches the access token for the domain.
|
|
199
206
|
* Otherwise, returns undefined.
|
|
207
|
+
*
|
|
208
|
+
* For workers.dev preview URLs, pass the `cf-workers-preview-token` alongside
|
|
209
|
+
* the HEAD probe: the preview worker only surfaces its Access 302 when the
|
|
210
|
+
* request carries the preview token (a bare HEAD returns 404 regardless of
|
|
211
|
+
* Access configuration).
|
|
212
|
+
*
|
|
200
213
|
* @see https://github.com/cloudflare/workers-sdk/blob/a5eb5134f73d3983655d325a4de71c6370c57faa/packages/wrangler/src/user/access.ts#L10
|
|
201
214
|
*/
|
|
202
|
-
const getAccessToken = memoize(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
215
|
+
const getAccessToken = memoize(
|
|
216
|
+
async (hostname: string, previewToken?: string) => {
|
|
217
|
+
if (!(await domainUsesAccess(hostname, previewToken))) {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
const result = await Scope.current
|
|
221
|
+
.exec(`access-${hostname}`, `cloudflared access login ${hostname}`)
|
|
222
|
+
.catch(() => {
|
|
223
|
+
throw new Error(
|
|
224
|
+
[
|
|
225
|
+
`The \`cloudflared\` CLI is not installed, but is required to access the domain "${hostname}".`,
|
|
226
|
+
`Please install it from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation and run \`cloudflare access login ${hostname}\`.`,
|
|
227
|
+
].join("\n"),
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
const matches = result.stdout.match(/fetched your token:\n\n(.*)/m);
|
|
231
|
+
if (matches?.[1]) {
|
|
232
|
+
return matches[1];
|
|
233
|
+
}
|
|
234
|
+
const error = new Error(
|
|
235
|
+
`Failed to get access token for domain "${hostname}".`,
|
|
236
|
+
);
|
|
237
|
+
Object.assign(error, result);
|
|
238
|
+
throw error;
|
|
239
|
+
},
|
|
240
|
+
(hostname) => hostname,
|
|
241
|
+
);
|
|
226
242
|
|
|
227
243
|
/**
|
|
228
244
|
* Returns true if the domain uses Cloudflare Access.
|
|
245
|
+
*
|
|
246
|
+
* @param hostname the workers.dev preview host
|
|
247
|
+
* @param previewToken optional preview token; required when probing a
|
|
248
|
+
* preview-worker URL because CF only serves the Access 302 to requests
|
|
249
|
+
* that carry the token.
|
|
229
250
|
*/
|
|
230
|
-
async function domainUsesAccess(hostname: string) {
|
|
251
|
+
async function domainUsesAccess(hostname: string, previewToken?: string) {
|
|
231
252
|
try {
|
|
232
253
|
const response = await fetch(`https://${hostname}`, {
|
|
233
254
|
method: "HEAD",
|
|
234
255
|
redirect: "manual",
|
|
235
256
|
signal: AbortSignal.timeout(1000),
|
|
257
|
+
headers: previewToken
|
|
258
|
+
? { "cf-workers-preview-token": previewToken }
|
|
259
|
+
: undefined,
|
|
236
260
|
});
|
|
237
261
|
return !!(
|
|
238
262
|
response.status === 302 &&
|
|
@@ -10,6 +10,7 @@ import { Assets } from "./assets.ts";
|
|
|
10
10
|
import type { Bindings } from "./bindings.ts";
|
|
11
11
|
import { DEFAULT_COMPATIBILITY_DATE } from "./compatibility-date.ts";
|
|
12
12
|
import { unionCompatibilityFlags } from "./compatibility-presets.ts";
|
|
13
|
+
import { quickTunnel } from "./quick-tunnel.ts";
|
|
13
14
|
import {
|
|
14
15
|
extractStringAndSecretBindings,
|
|
15
16
|
unencryptSecrets,
|
|
@@ -78,6 +79,10 @@ export interface WebsiteProps<
|
|
|
78
79
|
* The local domain to use for the dev server
|
|
79
80
|
*/
|
|
80
81
|
domain?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Whether to use a Cloudflare Tunnel for the dev server
|
|
84
|
+
*/
|
|
85
|
+
tunnel?: boolean;
|
|
81
86
|
/**
|
|
82
87
|
* Additional environment variables to set when running the dev command
|
|
83
88
|
*/
|
|
@@ -363,6 +368,10 @@ export async function Website<
|
|
|
363
368
|
ALCHEMY_ROOT: Scope.current.rootDir,
|
|
364
369
|
},
|
|
365
370
|
});
|
|
371
|
+
if (url && ((typeof dev === "object" && dev.tunnel) || scope.tunnel)) {
|
|
372
|
+
const { tunnelUrl } = await quickTunnel(scope, url);
|
|
373
|
+
url = tunnelUrl;
|
|
374
|
+
}
|
|
366
375
|
}
|
|
367
376
|
|
|
368
377
|
return (await Worker(id, {
|
|
@@ -377,7 +386,16 @@ export async function Website<
|
|
|
377
386
|
}
|
|
378
387
|
: {}),
|
|
379
388
|
},
|
|
380
|
-
dev:
|
|
389
|
+
dev: worker.dev
|
|
390
|
+
? {
|
|
391
|
+
url: url as never,
|
|
392
|
+
port: worker.dev.port,
|
|
393
|
+
remote: worker.dev.remote,
|
|
394
|
+
tunnel: worker.dev.tunnel,
|
|
395
|
+
}
|
|
396
|
+
: url
|
|
397
|
+
? { url }
|
|
398
|
+
: undefined,
|
|
381
399
|
})) as Website<B, RPC>;
|
|
382
400
|
}
|
|
383
401
|
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -329,6 +329,12 @@ export interface BaseWorkerProps<
|
|
|
329
329
|
* @default false
|
|
330
330
|
*/
|
|
331
331
|
adopt?: boolean;
|
|
332
|
+
/**
|
|
333
|
+
* Forcibly transfer the hostname when it is currently bound to a
|
|
334
|
+
* different Worker service. See {@link CustomDomainProps.overrideExistingOrigin}.
|
|
335
|
+
* @default false
|
|
336
|
+
*/
|
|
337
|
+
overrideExistingOrigin?: boolean;
|
|
332
338
|
}
|
|
333
339
|
)[];
|
|
334
340
|
|
|
@@ -1497,6 +1503,7 @@ async function provisionResources<B extends Bindings>(
|
|
|
1497
1503
|
name: domain,
|
|
1498
1504
|
zoneId: undefined,
|
|
1499
1505
|
adopt: props.adopt,
|
|
1506
|
+
overrideExistingOrigin: undefined,
|
|
1500
1507
|
};
|
|
1501
1508
|
}
|
|
1502
1509
|
if (domain.domainName === "") {
|
|
@@ -1506,6 +1513,7 @@ async function provisionResources<B extends Bindings>(
|
|
|
1506
1513
|
name: domain.domainName,
|
|
1507
1514
|
zoneId: domain.zoneId,
|
|
1508
1515
|
adopt: domain.adopt ?? props.adopt,
|
|
1516
|
+
overrideExistingOrigin: domain.overrideExistingOrigin,
|
|
1509
1517
|
};
|
|
1510
1518
|
}),
|
|
1511
1519
|
eventSources: props.eventSources?.map((eventSource) => {
|
|
@@ -1578,6 +1586,7 @@ async function provisionResources<B extends Bindings>(
|
|
|
1578
1586
|
name: domain.name,
|
|
1579
1587
|
zoneId: domain.zoneId,
|
|
1580
1588
|
adopt: domain.adopt,
|
|
1589
|
+
overrideExistingOrigin: domain.overrideExistingOrigin,
|
|
1581
1590
|
delete: props.delete,
|
|
1582
1591
|
workerName: options.name,
|
|
1583
1592
|
dev: options.local,
|
package/src/docker/image.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
-
import os from "node:os";
|
|
3
2
|
import path from "pathe";
|
|
4
3
|
import type { Context } from "../context.ts";
|
|
5
4
|
import { Resource } from "../resource.ts";
|
|
6
5
|
import type { Secret } from "../secret.ts";
|
|
7
6
|
import { DockerApi } from "./api.ts";
|
|
7
|
+
import { pushImageToRegistry } from "./registry.ts";
|
|
8
8
|
import type { RemoteImage } from "./remote-image.ts";
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -66,9 +66,9 @@ export interface DockerBuildOptions {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export interface ImageRegistry {
|
|
69
|
+
server: string;
|
|
69
70
|
username: string;
|
|
70
71
|
password: Secret;
|
|
71
|
-
server: string;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
@@ -285,63 +285,12 @@ export const Image = Resource(
|
|
|
285
285
|
let repoDigest: string | undefined;
|
|
286
286
|
let finalImageRef = imageRef;
|
|
287
287
|
if (props.registry && !props.skipPush) {
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const registryHost = server.replace(/\/$/, "");
|
|
292
|
-
|
|
293
|
-
// Determine if the built image already includes a registry host (e.g. ghcr.io/user/repo)
|
|
294
|
-
const firstSegment = imageRef.split("/")[0];
|
|
295
|
-
const hasRegistryPrefix = firstSegment.includes(".");
|
|
296
|
-
|
|
297
|
-
// Compose the target image reference that will be pushed
|
|
298
|
-
const targetImage = hasRegistryPrefix
|
|
299
|
-
? imageRef // already fully-qualified
|
|
300
|
-
: `${registryHost}/${imageRef}`;
|
|
301
|
-
|
|
302
|
-
try {
|
|
303
|
-
// Create a temporary directory that will act as an isolated Docker config
|
|
304
|
-
// (credentials) directory. This prevents race-conditions when multiple
|
|
305
|
-
// concurrent tests perform `docker login` / `logout` by ensuring each
|
|
306
|
-
// Image operation has its own credential store.
|
|
307
|
-
const tempConfigDir = await fs.mkdtemp(
|
|
308
|
-
path.join(os.tmpdir(), "docker-config-"),
|
|
309
|
-
);
|
|
310
|
-
const api = new DockerApi({ configDir: tempConfigDir });
|
|
311
|
-
|
|
312
|
-
// Authenticate to registry using the isolated config directory
|
|
313
|
-
await api.login(registryHost, username, password.unencrypted);
|
|
314
|
-
|
|
315
|
-
// Tag local image with fully qualified name if necessary
|
|
316
|
-
if (targetImage !== imageRef) {
|
|
317
|
-
await api.exec(["tag", imageRef, targetImage]);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Push the image
|
|
321
|
-
const { stdout: pushOut } = await api.exec(["push", targetImage]);
|
|
322
|
-
|
|
323
|
-
// Attempt to extract the repo digest from push output
|
|
324
|
-
const digestMatch = /digest:\s+([a-z0-9]+:[a-f0-9]{64})/.exec(pushOut);
|
|
325
|
-
if (digestMatch) {
|
|
326
|
-
const digestHash = digestMatch[1];
|
|
327
|
-
// Strip tag (anything after last :) to build image@digest reference
|
|
328
|
-
const [repoWithoutTag] =
|
|
329
|
-
targetImage.split(":").length > 2
|
|
330
|
-
? [targetImage] // unlikely but safety
|
|
331
|
-
: [targetImage.substring(0, targetImage.lastIndexOf(":"))];
|
|
332
|
-
repoDigest = `${repoWithoutTag}@${digestHash}`;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Update the final image reference to point at the pushed image
|
|
336
|
-
finalImageRef = targetImage;
|
|
337
|
-
} finally {
|
|
338
|
-
// Clean up credentials from the isolated config
|
|
339
|
-
await api.logout(registryHost);
|
|
340
|
-
}
|
|
288
|
+
const pushedImage = await pushImageToRegistry(imageRef, props.registry);
|
|
289
|
+
finalImageRef = pushedImage.imageRef;
|
|
290
|
+
repoDigest = pushedImage.repoDigest;
|
|
341
291
|
}
|
|
342
292
|
return {
|
|
343
293
|
kind: "Image",
|
|
344
|
-
...props,
|
|
345
294
|
tag,
|
|
346
295
|
name,
|
|
347
296
|
imageRef: finalImageRef,
|