Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/preview-auto-provision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": minor
---

Add auto-provisioning for KV and R2 bindings in `wrangler preview`

Preview deployments now create deterministic KV namespaces and R2 buckets for binding-only entries in the `previews` config block, then use those generated resources for the deployment without writing IDs back to the config file. `wrangler preview delete` also cleans up only those auto-provisioned preview resources, leaving explicitly configured KV namespaces and R2 buckets untouched.
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ export function createWorkerUploadForm(
binding,
service,
environment,
preview_id,
entrypoint,
props,
cross_account_grant,
Expand All @@ -478,6 +479,7 @@ export function createWorkerUploadForm(
service,
cross_account_grant,
...(environment && { environment }),
...(preview_id && { preview_id }),
...(entrypoint && { entrypoint }),
...(props && { props }),
});
Expand Down
62 changes: 34 additions & 28 deletions packages/deploy-helpers/src/deploy/helpers/print-bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -513,43 +513,49 @@ export function printBindings(

if (services.length > 0) {
output.push(
...services.map(({ binding, service, entrypoint, remote, dev }) => {
let value = service;
let mode = undefined;
...services.map(
({ binding, service, preview_id, entrypoint, remote, dev }) => {
let value = service;
let mode = undefined;

if (entrypoint) {
value += `#${entrypoint}`;
}
if (preview_id) {
value += `@${preview_id}`;
}

if (dev !== undefined && context.local) {
mode = getMode({ isSimulatedLocally: true });
} else if (remote) {
mode = getMode({ isSimulatedLocally: false });
} else if (context.local && context.registry !== null) {
const isSelfBinding = service === context.name;
if (entrypoint) {
value += `#${entrypoint}`;
}

if (isSelfBinding) {
hasConnectionStatus = true;
mode = getMode({ isSimulatedLocally: true, connected: true });
} else {
const registryDefinition = context.registry?.[service];
hasConnectionStatus = true;
if (dev !== undefined && context.local) {
mode = getMode({ isSimulatedLocally: true });
} else if (remote) {
mode = getMode({ isSimulatedLocally: false });
} else if (context.local && context.registry !== null) {
const isSelfBinding = service === context.name;

if (registryDefinition && registryDefinition.debugPortAddress) {
if (isSelfBinding) {
hasConnectionStatus = true;
mode = getMode({ isSimulatedLocally: true, connected: true });
} else {
mode = getMode({ isSimulatedLocally: true, connected: false });
const registryDefinition = context.registry?.[service];
hasConnectionStatus = true;

if (registryDefinition && registryDefinition.debugPortAddress) {
mode = getMode({ isSimulatedLocally: true, connected: true });
} else {
mode = getMode({ isSimulatedLocally: true, connected: false });
}
}
}
}

return {
name: binding,
type: getBindingTypeFriendlyName("service"),
value,
mode,
};
})
return {
name: binding,
type: getBindingTypeFriendlyName("service"),
value,
mode,
};
}
)
);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/workers-utils/src/config/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,8 @@ export interface EnvironmentNonInheritable {
* The environment of the service (e.g. production, staging, etc).
*/
environment?: string;
/** Optionally, the preview tag, slug, or display name of the service to bind to. */
preview_id?: string;
/** Optionally, the entrypoint (named export) of the service to bind to. */
entrypoint?: string;
/** Optional properties that will be made available to the service via ctx.props. */
Expand Down
8 changes: 8 additions & 0 deletions packages/workers-utils/src/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4581,6 +4581,14 @@ const validateServiceBinding: ValidatorFn = (diagnostics, field, value) => {
);
isValid = false;
}
if (!isOptionalProperty(value, "preview_id", "string")) {
diagnostics.errors.push(
`"${field}" bindings should have a string "preview_id" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}
if (!isOptionalProperty(value, "entrypoint", "string")) {
diagnostics.errors.push(
`"${field}" bindings should have a string "entrypoint" field but got ${JSON.stringify(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export function mapWorkerMetadataBindings(
binding: binding.name,
service: binding.service,
environment: binding.environment,
preview_id: binding.preview_id,
entrypoint: binding.entrypoint,
},
];
Expand Down
1 change: 1 addition & 0 deletions packages/workers-utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export type WorkerMetadataBinding =
name: string;
service: string;
environment?: string;
preview_id?: string;
entrypoint?: string;
cross_account_grant?: string;
}
Expand Down
1 change: 1 addition & 0 deletions packages/workers-utils/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ export interface CfService {
binding: string;
service: string;
environment?: string;
preview_id?: string;
entrypoint?: string;
props?: Record<string, unknown>;
remote?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,23 @@ describe("createWorkerUploadForm — bindings", () => {
entrypoint: "AuthHandler",
});
});

it("should include service bindings with preview IDs", ({ expect }) => {
const bindings: StartDevWorkerInput["bindings"] = {
AUTH: {
type: "service",
service: "auth-worker",
preview_id: "feature-preview",
},
};
const form = createWorkerUploadForm(createEsmWorker(), bindings);
expect(getBindings(form)).toContainEqual({
name: "AUTH",
type: "service",
service: "auth-worker",
preview_id: "feature-preview",
});
});
});

describe("queue bindings", () => {
Expand Down
45 changes: 45 additions & 0 deletions packages/wrangler/src/__tests__/deploy/bindings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1886,6 +1886,51 @@ describe("deploy", () => {
expect(std.warn).toMatchInlineSnapshot(`""`);
});

it("should support service bindings to Worker Previews", async ({
expect,
}) => {
writeWranglerConfig({
services: [
{
binding: "FOO",
service: "foo-service",
preview_id: "feature-preview",
},
],
});
writeWorkerSource();
mockSubDomainRequest();
mockUploadWorkerRequest({
expectedBindings: [
{
type: "service",
name: "FOO",
service: "foo-service",
preview_id: "feature-preview",
},
],
});

await runWrangler("deploy index.js");
expect(std.out).toMatchInlineSnapshot(`
"
⛅️ wrangler x.x.x
──────────────────
Total Upload: xx KiB / gzip: xx KiB
Worker Startup Time: 100 ms
Your Worker has access to the following bindings:
Binding Resource
env.FOO (foo-service@feature-preview) Worker

Uploaded test-name (TIMINGS)
Deployed test-name triggers (TIMINGS)
https://test-name.test-sub-domain.workers.dev
Current Version ID: Galaxy-Class"
`);
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`""`);
});

it("should support service bindings with props", async ({ expect }) => {
writeWranglerConfig({
services: [
Expand Down
Loading
Loading