diff --git a/.changeset/angry-keys-dance.md b/.changeset/angry-keys-dance.md new file mode 100644 index 000000000000..78f466b4f9e5 --- /dev/null +++ b/.changeset/angry-keys-dance.md @@ -0,0 +1,12 @@ +--- +"wrangler": minor +--- + +Added new [[pipelines]] bindings. This creates a new binding that allows sending events to +the specified pipeline. + +Example: + +[[pipelines]] +binding = "MY_PIPELINE" +pipeline = "my-pipeline" diff --git a/.prettierignore b/.prettierignore index b489d5f785ec..f9587c3f48de 100644 --- a/.prettierignore +++ b/.prettierignore @@ -24,6 +24,8 @@ packages/create-cloudflare/templates/**/*.* # but still exclude the worker-configuration.d.ts file, since it's generated !packages/create-cloudflare/templates/hello-world/**/*.* packages/create-cloudflare/templates/hello-world/**/worker-configuration.d.ts +# dist-functions are generated in the fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self folder +dist-functions vscode.d.ts vscode.*.d.ts diff --git a/packages/wrangler/src/__tests__/configuration.test.ts b/packages/wrangler/src/__tests__/configuration.test.ts index eabf60d81110..c55200e843b3 100644 --- a/packages/wrangler/src/__tests__/configuration.test.ts +++ b/packages/wrangler/src/__tests__/configuration.test.ts @@ -130,6 +130,7 @@ describe("normalizeAndValidateConfig()", () => { upload_source_maps: undefined, placement: undefined, tail_consumers: undefined, + pipelines: [], }); expect(diagnostics.hasErrors()).toBe(false); expect(diagnostics.hasWarnings()).toBe(false); @@ -3181,6 +3182,114 @@ describe("normalizeAndValidateConfig()", () => { }); }); + describe("[pipelines]", () => { + it("should error if pipelines is an object", () => { + const { diagnostics } = normalizeAndValidateConfig( + // @ts-expect-error purposely using an invalid value + { pipelines: {} }, + 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( + // @ts-expect-error purposely using an invalid value + { pipelines: "BAD" }, + 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( + // @ts-expect-error purposely using an invalid value + { pipelines: 999 }, + 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( + // @ts-expect-error purposely using an invalid value + { pipelines: null }, + 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 must have a string \\"binding\\" field but got {}. + - \\"pipelines[0]\\" bindings must have a string \\"pipeline\\" field but got {}. + - \\"pipelines[2]\\" bindings must have a string \\"binding\\" field but got {\\"binding\\":2000,\\"project\\":2111}. + - \\"pipelines[2]\\" bindings must have a string \\"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 0c835981a0c6..82b8b03a5e89 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -10615,6 +10615,43 @@ 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) + Deployed test-name triggers (TIMINGS) + https://test-name.test-sub-domain.workers.dev + Current Version ID: Galaxy-Class" + `); + }); + }); + describe("--keep-vars", () => { it("should send keepVars when keep-vars is passed in", async () => { vi.stubEnv("CLOUDFLARE_API_TOKEN", "hunter2"); diff --git a/packages/wrangler/src/__tests__/type-generation.test.ts b/packages/wrangler/src/__tests__/type-generation.test.ts index df1f416f997c..88fc1cdbfd00 100644 --- a/packages/wrangler/src/__tests__/type-generation.test.ts +++ b/packages/wrangler/src/__tests__/type-generation.test.ts @@ -217,6 +217,7 @@ const bindingsConfigMock: Omit< }, { type: "CompiledWasm", globs: ["**/*.wasm"], fallthrough: true }, ], + pipelines: [], }; describe("generateTypes()", () => { 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 6354ceea24ab..824788e2aa64 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, experimental_assets: undefined, diff --git a/packages/wrangler/src/api/startDevWorker/types.ts b/packages/wrangler/src/api/startDevWorker/types.ts index 14bce75d059e..f5c7a3178415 100644 --- a/packages/wrangler/src/api/startDevWorker/types.ts +++ b/packages/wrangler/src/api/startDevWorker/types.ts @@ -16,6 +16,7 @@ import type { CfLogfwdrBinding, CfModule, CfMTlsCertificate, + CfPipeline, CfQueue, CfR2Bucket, CfScriptFormat, @@ -261,6 +262,7 @@ export type Binding = | ({ type: "analytics_engine" } & Omit) | ({ type: "dispatch_namespace" } & Omit) | ({ type: "mtls_certificate" } & Omit) + | ({ type: "pipeline" } & Omit) | ({ type: "logfwdr" } & Omit) | { type: `unsafe_${string}` } | { type: "assets" }; diff --git a/packages/wrangler/src/api/startDevWorker/utils.ts b/packages/wrangler/src/api/startDevWorker/utils.ts index b237ede5814f..383bf71b4f85 100644 --- a/packages/wrangler/src/api/startDevWorker/utils.ts +++ b/packages/wrangler/src/api/startDevWorker/utils.ts @@ -244,6 +244,12 @@ export function convertCfWorkerInitBindingstoBindings( output[info["binding"]] = { type: "assets" }; break; } + case "pipelines": { + for (const { binding, ...x } of info) { + output[binding] = { type: "pipeline", ...x }; + } + break; + } default: { assertNever(type); } @@ -282,6 +288,7 @@ export async function convertBindingsToCfWorkerInitBindings( logfwdr: undefined, unsafe: undefined, experimental_assets: undefined, + pipelines: undefined, }; const fetchers: Record = {}; @@ -354,6 +361,9 @@ export async function convertBindingsToCfWorkerInitBindings( } else if (binding.type === "mtls_certificate") { bindings.mtls_certificates ??= []; bindings.mtls_certificates.push({ ...binding, binding: name }); + } else if (binding.type === "pipeline") { + bindings.pipelines ??= []; + bindings.pipelines.push({ ...binding, binding: name }); } else if (binding.type === "logfwdr") { bindings.logfwdr ??= { bindings: [] }; bindings.logfwdr.bindings.push({ ...binding, name: name }); 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 f04a9d3b37a1..73d36d468437 100644 --- a/packages/wrangler/src/config/environment.ts +++ b/packages/wrangler/src/config/environment.ts @@ -754,6 +754,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 6d1c400d5141..6110ffa6e644 100644 --- a/packages/wrangler/src/config/index.ts +++ b/packages/wrangler/src/config/index.ts @@ -15,15 +15,15 @@ import type { NormalizeAndValidateConfigArgs } from "./validation"; export type { Config, - RawConfig, ConfigFields, DevConfig, + RawConfig, RawDevConfig, } from "./config"; export type { + ConfigModuleRuleType, Environment, RawEnvironment, - ConfigModuleRuleType, } from "./environment"; type ReadConfigCommandArgs = NormalizeAndValidateConfigArgs & { @@ -232,6 +232,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) { @@ -443,6 +444,16 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) { }); } + if (pipelines?.length) { + 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 bb12664b51e2..ccb385d49c1b 100644 --- a/packages/wrangler/src/config/validation.ts +++ b/packages/wrangler/src/config/validation.ts @@ -1456,6 +1456,16 @@ function normalizeAndValidateEnvironment( validateAIBinding(envName), undefined ), + pipelines: notInheritable( + diagnostics, + topLevelEnv, + rawConfig, + rawEnv, + envName, + "pipelines", + validateBindingArray(envName, validatePipelineBinding), + [] + ), version_metadata: notInheritable( diagnostics, topLevelEnv, @@ -2213,6 +2223,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => { "service", "logfwdr", "mtls_certificate", + "pipeline", ]; if (safeBindings.includes(value.type)) { @@ -3115,6 +3126,40 @@ 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; + // Pipeline bindings must have a binding and a pipeline. + if (!isRequiredProperty(value, "binding", "string")) { + diagnostics.errors.push( + `"${field}" bindings must 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 string "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 d3785f249e87..dd1013bc8ef8 100644 --- a/packages/wrangler/src/deploy/deploy.ts +++ b/packages/wrangler/src/deploy/deploy.ts @@ -692,6 +692,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, experimental_assets: config.experimental_assets?.binding ? { binding: config.experimental_assets.binding } @@ -875,6 +876,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m logger.log("Worker Startup Time:", result.startup_time_ms, "ms"); } bindingsPrinted = true; + printBindings({ ...withoutStaticAssets, vars: maskedVars }); versionId = parseNonHyphenedUuid(result.deployment_id); 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 08b4886cd458..c3a48dc42f37 100644 --- a/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts +++ b/packages/wrangler/src/deployment-bundle/create-worker-upload-form.ts @@ -110,6 +110,7 @@ export type WorkerMetadataBinding = }; } | { type: "mtls_certificate"; name: string; certificate_id: string } + | { type: "pipelines"; name: string; id: string } | { type: "logfwdr"; name: string; @@ -316,6 +317,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 13b6426608df..fc8679847c7b 100644 --- a/packages/wrangler/src/deployment-bundle/worker.ts +++ b/packages/wrangler/src/deployment-bundle/worker.ts @@ -226,6 +226,11 @@ export interface CfExperimentalAssetBinding { binding: string; } +export interface CfPipeline { + binding: string; + pipeline: string; +} + export interface CfUnsafeBinding { name: string; type: string; @@ -327,6 +332,7 @@ export interface CfWorkerInit { dispatch_namespaces: CfDispatchNamespace[] | undefined; mtls_certificates: CfMTlsCertificate[] | undefined; logfwdr: CfLogfwdr | undefined; + pipelines: CfPipeline[] | undefined; unsafe: CfUnsafe | undefined; experimental_assets: CfExperimentalAssetBinding | undefined; }; diff --git a/packages/wrangler/src/dev.tsx b/packages/wrangler/src/dev.tsx index d545f6e7bc42..8bb0b0a2ce9c 100644 --- a/packages/wrangler/src/dev.tsx +++ b/packages/wrangler/src/dev.tsx @@ -726,6 +726,7 @@ export async function startDev(args: StartDevOptions) { analytics_engine_datasets: undefined, dispatch_namespaces: undefined, mtls_certificates: undefined, + pipelines: undefined, logfwdr: undefined, unsafe: undefined, experimental_assets: undefined, @@ -1600,6 +1601,7 @@ export function getBindings( capnp: configParam.unsafe.capnp, }, mtls_certificates: configParam.mtls_certificates, + pipelines: configParam.pipelines, send_email: configParam.send_email, experimental_assets: configParam.experimental_assets?.binding ? { binding: configParam.experimental_assets?.binding } diff --git a/packages/wrangler/src/secret/index.ts b/packages/wrangler/src/secret/index.ts index 0a2c62f620c6..23579f036093 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: [] }, experimental_assets: undefined, unsafe: { diff --git a/packages/wrangler/src/versions/upload.ts b/packages/wrangler/src/versions/upload.ts index 3b6f11acdf0e..cf7bb5e455d3 100644 --- a/packages/wrangler/src/versions/upload.ts +++ b/packages/wrangler/src/versions/upload.ts @@ -388,6 +388,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, experimental_assets: config.experimental_assets?.binding ? { binding: config.experimental_assets?.binding }