diff --git a/packages/wrangler/src/__tests__/configuration.test.ts b/packages/wrangler/src/__tests__/configuration.test.ts index 4c9972790bbb..71bdeaae4bf7 100644 --- a/packages/wrangler/src/__tests__/configuration.test.ts +++ b/packages/wrangler/src/__tests__/configuration.test.ts @@ -7,6 +7,8 @@ import type { RawDevConfig, RawEnvironment, } from "../config"; +import { pipeline } from "node:stream"; +import { pipelines } from "../pipelines"; describe("normalizeAndValidateConfig()", () => { it("should use defaults for empty configuration", () => { @@ -93,6 +95,7 @@ describe("normalizeAndValidateConfig()", () => { upload_source_maps: undefined, placement: undefined, tail_consumers: undefined, + pipelines: [], }); expect(diagnostics.hasErrors()).toBe(false); expect(diagnostics.hasWarnings()).toBe(false); @@ -3055,6 +3058,104 @@ describe("normalizeAndValidateConfig()", () => { }); }); + describe("[pipelines]", () => { + it("should error if pipelines is an object", () => { + const { diagnostics } = normalizeAndValidateConfig( + { pipelines: {} } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"pipelines\\" should be an array but got {}." + `); + }); + + it("should error if pipelines is a string", () => { + const { diagnostics } = normalizeAndValidateConfig( + { pipelines: "BAD" } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"pipelines\\" should be an array but got \\"BAD\\"." + `); + }); + + it("should error if pipelines is a number", () => { + const { diagnostics } = normalizeAndValidateConfig( + { pipelines: 999 } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"pipelines\\" should be an array but got 999." + `); + }); + + it("should error if pipelines is null", () => { + const { diagnostics } = normalizeAndValidateConfig( + { pipelines: null } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasWarnings()).toBe(false); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - The field \\"pipelines\\" should be an array but got null." + `); + }); + + it("should accept valid bindings", () => { + const { diagnostics } = normalizeAndValidateConfig( + { + pipelines: [ + { binding: "VALID", pipeline: "343cd4f1d58c42fbb5bd082592fd7143" }, + ], + } as unknown as RawConfig, + undefined, + { env: undefined } + ); + + expect(diagnostics.hasErrors()).toBe(false); + }); + + it("should error if pipelines.bindings are not valid", () => { + const { diagnostics } = normalizeAndValidateConfig( + { + pipelines: [ + {}, + { binding: "VALID", pipeline: "343cd4f1d58c42fbb5bd082592fd7143" }, + { binding: 2000, project: 2111 }, + ], + } as unknown as RawConfig, + undefined, + { env: undefined } + ); + expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - Unexpected fields found in pipelines[2] field: \\"project\\"" + `); + expect(diagnostics.hasWarnings()).toBe(true); + expect(diagnostics.renderErrors()).toMatchInlineSnapshot(` + "Processing wrangler configuration: + - \\"pipelines[0]\\" bindings should have a string \\"binding\\" field but got {}. + - \\"pipelines[0]\\" bindings must have a \\"pipeline\\" field but got {}. + - \\"pipelines[2]\\" bindings should have a string \\"binding\\" field but got {\\"binding\\":2000,\\"project\\":2111}. + - \\"pipelines[2]\\" bindings must have a \\"pipeline\\" field but got {\\"binding\\":2000,\\"project\\":2111}." + `); + }); + }); + describe("[unsafe.bindings]", () => { it("should error if unsafe is an array", () => { const { diagnostics } = normalizeAndValidateConfig( diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index f67614e814ad..a8ca6b317f58 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -10643,6 +10643,47 @@ export default{ }); }); + describe("pipelines", () => { + it("should upload pipelines bindings", async () => { + writeWranglerToml({ + pipelines: [ + { + binding: "MY_PIPELINE", + pipeline: "0123456789ABCDEF0123456789ABCDEF", + }, + ], + }); + await fs.promises.writeFile("index.js", `export default {};`); + mockSubDomainRequest(); + mockUploadWorkerRequest({ + expectedBindings: [ + { + type: "pipelines", + name: "MY_PIPELINE", + id: "0123456789ABCDEF0123456789ABCDEF", + }, + ], + }); + + await runWrangler("deploy index.js"); + expect(std.out).toMatchInlineSnapshot(` + "Total Upload: xx KiB / gzip: xx KiB + Worker Startup Time: 100 ms + Your worker has access to the following bindings: + - Pipelines: + - MY_PIPELINE: 0123456789ABCDEF0123456789ABCDEF + Uploaded test-name (TIMINGS) + Published test-name (TIMINGS) + https://test-name.test-sub-domain.workers.dev + Current Deployment ID: Galaxy-Class + Current Version ID: Galaxy-Class + + + Note: Deployment ID has been renamed to Version ID. Deployment ID is present to maintain compatibility with the previous behavior of this command. This output will change in a future version of Wrangler. To learn more visit: https://developers.cloudflare.com/workers/configuration/versions-and-deployments" + `); + }); + }); + describe("--keep-vars", () => { it("should send keepVars when keep-vars is passed in", async () => { process.env = { diff --git a/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts b/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts index bfb87c2a39be..f6b6223077ab 100644 --- a/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts +++ b/packages/wrangler/src/api/pages/create-worker-bundle-contents.ts @@ -66,6 +66,7 @@ function createWorkerBundleFormData( text_blobs: undefined, data_blobs: undefined, dispatch_namespaces: undefined, + pipelines: undefined, logfwdr: undefined, unsafe: undefined, }; diff --git a/packages/wrangler/src/config/config.ts b/packages/wrangler/src/config/config.ts index 640743c0de33..80dc47c02c73 100644 --- a/packages/wrangler/src/config/config.ts +++ b/packages/wrangler/src/config/config.ts @@ -402,4 +402,5 @@ export const defaultWranglerConfig: Config = { }, mtls_certificates: [], tail_consumers: undefined, + pipelines: [], }; diff --git a/packages/wrangler/src/config/environment.ts b/packages/wrangler/src/config/environment.ts index eedef0b28857..3d0f88f782b8 100644 --- a/packages/wrangler/src/config/environment.ts +++ b/packages/wrangler/src/config/environment.ts @@ -748,6 +748,23 @@ export interface EnvironmentNonInheritable { /** Details about the outbound Worker which will handle outbound requests from your namespace */ outbound?: DispatchNamespaceOutbound; }[]; + + /** + * Specifies list of Pipelines bound to this Worker environment + * + * NOTE: This field is not automatically inherited from the top level environment, + * and so must be specified in every named environment. + * + * @default `[]` + * @nonInheritable + */ + pipelines: { + /** The binding name used to refer to the bound service. */ + binding: string; + + /** Name of the Pipeline to bind */ + pipeline: string; + }[]; } /** diff --git a/packages/wrangler/src/config/index.ts b/packages/wrangler/src/config/index.ts index 3248ea08aa4f..8bdf1fe988ce 100644 --- a/packages/wrangler/src/config/index.ts +++ b/packages/wrangler/src/config/index.ts @@ -220,6 +220,7 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) { wasm_modules, dispatch_namespaces, mtls_certificates, + pipelines, } = bindings; if (data_blobs !== undefined && Object.keys(data_blobs).length > 0) { @@ -431,6 +432,16 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) { }); } + if (pipelines != undefined && pipelines.length>0) { + output.push({ + type: "Pipelines", + entries: pipelines.map(({ binding, pipeline }) => ({ + key: binding, + value: pipeline, + })) + }) + } + if (version_metadata !== undefined) { output.push({ type: "Worker Version Metadata", diff --git a/packages/wrangler/src/config/validation.ts b/packages/wrangler/src/config/validation.ts index 1e199d505d8a..aabfd9440c44 100644 --- a/packages/wrangler/src/config/validation.ts +++ b/packages/wrangler/src/config/validation.ts @@ -1444,6 +1444,16 @@ function normalizeAndValidateEnvironment( validateAIBinding(envName), undefined ), + pipelines: notInheritable( + diagnostics, + topLevelEnv, + rawConfig, + rawEnv, + envName, + "pipelines", + validateBindingArray(envName, validatePipelineBinding), + [] + ), version_metadata: notInheritable( diagnostics, topLevelEnv, @@ -2201,6 +2211,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => { "service", "logfwdr", "mtls_certificate", + "pipeline", ]; if (safeBindings.includes(value.type)) { @@ -3095,6 +3106,44 @@ const validateConsumer: ValidatorFn = (diagnostics, field, value, _config) => { return isValid; }; +const validatePipelineBinding: ValidatorFn = (diagnostics, field, value) => { + if (typeof value !== "object" || value === null) { + diagnostics.errors.push( + `"pipeline" bindings should be objects, but got ${JSON.stringify( + value + )}` + ); + return false; + } + let isValid = true; + // Pipelin bindings must have a binding and a pipeline. + if (!isRequiredProperty(value, "binding", "string")) { + diagnostics.errors.push( + `"${field}" bindings should have a string "binding" field but got ${JSON.stringify( + value + )}.` + ); + isValid = false; + } + if (!isRequiredProperty(value, "pipeline", "string")) { + diagnostics.errors.push( + `"${field}" bindings must have a "pipeline" field but got ${JSON.stringify( + value + )}.` + ); + isValid = false; + } + + validateAdditionalProperties(diagnostics, field, Object.keys(value), [ + "binding", + "pipeline", + ]); + + return isValid; +}; + + + function normalizeAndValidateLimits( diagnostics: Diagnostics, topLevelEnv: Environment | undefined, diff --git a/packages/wrangler/src/deploy/deploy.ts b/packages/wrangler/src/deploy/deploy.ts index 00154b704939..df618e66453b 100644 --- a/packages/wrangler/src/deploy/deploy.ts +++ b/packages/wrangler/src/deploy/deploy.ts @@ -633,6 +633,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m analytics_engine_datasets: config.analytics_engine_datasets, dispatch_namespaces: config.dispatch_namespaces, mtls_certificates: config.mtls_certificates, + pipelines: config.pipelines, logfwdr: config.logfwdr, unsafe: { bindings: config.unsafe.bindings, diff --git a/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts b/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts index e89c24062bb9..cf939d9c50e7 100644 --- a/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts +++ b/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts @@ -109,8 +109,9 @@ export type WorkerMetadataBinding = params?: { name: string }[]; }; } - | { type: "mtls_certificate"; name: string; certificate_id: string } - | { + | { type: "mtls_certificate"; name: string; certificate_id: string } + | { type: "pipelines"; name: string; id: string } + | { type: "logfwdr"; name: string; destination: string; @@ -304,6 +305,14 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData { }); }); + bindings.pipelines?.forEach(({ binding, pipeline }) => { + metadataBindings.push({ + name: binding, + type: 'pipelines', + id: pipeline, + }) + }) + bindings.logfwdr?.bindings.forEach(({ name, destination }) => { metadataBindings.push({ name: name, diff --git a/packages/wrangler/src/deployment-bundle/worker.ts b/packages/wrangler/src/deployment-bundle/worker.ts index 3e685c0e41f9..88d68cd9b5b5 100644 --- a/packages/wrangler/src/deployment-bundle/worker.ts +++ b/packages/wrangler/src/deployment-bundle/worker.ts @@ -221,6 +221,11 @@ export interface CfLogfwdrBinding { destination: string; } +export interface CfPipeline { + binding: string; + pipeline: string; +} + export interface CfUnsafeBinding { name: string; type: string; @@ -316,6 +321,7 @@ export interface CfWorkerInit { dispatch_namespaces: CfDispatchNamespace[] | undefined; mtls_certificates: CfMTlsCertificate[] | undefined; logfwdr: CfLogfwdr | undefined; + pipelines: CfPipeline[] | undefined; unsafe: CfUnsafe | undefined; }; /** diff --git a/packages/wrangler/src/dev.tsx b/packages/wrangler/src/dev.tsx index 6cc9b4cbd11e..452b18f15a58 100644 --- a/packages/wrangler/src/dev.tsx +++ b/packages/wrangler/src/dev.tsx @@ -705,6 +705,7 @@ export async function startDev(args: StartDevOptions) { analytics_engine_datasets: undefined, dispatch_namespaces: undefined, mtls_certificates: undefined, + pipelines: undefined, logfwdr: undefined, unsafe: undefined, }), diff --git a/packages/wrangler/src/secret/index.ts b/packages/wrangler/src/secret/index.ts index 80ba9b3ee097..166cb63c38a5 100644 --- a/packages/wrangler/src/secret/index.ts +++ b/packages/wrangler/src/secret/index.ts @@ -92,6 +92,7 @@ async function createDraftWorker({ data_blobs: {}, dispatch_namespaces: [], mtls_certificates: [], + pipelines: [], logfwdr: { bindings: [] }, unsafe: { bindings: undefined,