Skip to content

Commit 989e17e

Browse files
V3 backport of #10157 (#10226)
* feat: enforce 64-character limit for Workflow binding names locally (#10157) Fixes #10055 by implementing proper validation in validateWorkflowBinding function to match production behavior. - Implemented validateWorkflowBinding function with 64-character name limit - Added comprehensive tests covering valid/invalid names and boundary cases - Added validation for required fields (binding, name, class_name) - Added validation for optional fields (script_name, experimental_remote) - Created changeset for the feature Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: [email protected] <[email protected]> * fixup! feat: enforce 64-character limit for Workflow binding names locally (#10157) --------- Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent f480ec7 commit 989e17e

File tree

3 files changed

+186
-3
lines changed

3 files changed

+186
-3
lines changed

.changeset/rare-peaches-change.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Enforce 64-character limit for Workflow binding names locally to match production validation

packages/wrangler/src/__tests__/workflows.test.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { clearDialogs } from "./helpers/mock-dialogs";
66
import { msw } from "./helpers/msw";
77
import { runInTempDir } from "./helpers/run-in-tmp";
88
import { runWrangler } from "./helpers/run-wrangler";
9+
import { writeWorkerSource } from "./helpers/write-worker-source";
910
import { writeWranglerConfig } from "./helpers/write-wrangler-config";
1011
import type { Instance, Workflow } from "../workflows/types";
1112

@@ -433,4 +434,116 @@ describe("wrangler workflows", () => {
433434
);
434435
});
435436
});
437+
438+
describe("workflow binding validation", () => {
439+
it("should validate workflow binding with valid name", async () => {
440+
writeWorkerSource({ format: "ts" });
441+
writeWranglerConfig({
442+
main: "index.ts",
443+
workflows: [
444+
{
445+
binding: "MY_WORKFLOW",
446+
name: "valid-workflow-name",
447+
class_name: "MyWorkflow",
448+
script_name: "external-script",
449+
},
450+
],
451+
});
452+
453+
await runWrangler("deploy --dry-run");
454+
expect(std.err).toBe("");
455+
});
456+
457+
it("should reject workflow binding with name exceeding 64 characters", async () => {
458+
const longName = "a".repeat(65); // 65 characters
459+
writeWranglerConfig({
460+
workflows: [
461+
{
462+
binding: "MY_WORKFLOW",
463+
name: longName,
464+
class_name: "MyWorkflow",
465+
},
466+
],
467+
});
468+
469+
await expect(runWrangler("deploy --dry-run")).rejects.toThrow();
470+
expect(std.err).toContain("must be 64 characters or less");
471+
expect(std.err).toContain("but got 65 characters");
472+
});
473+
474+
it("should accept workflow binding with name exactly 64 characters", async () => {
475+
const maxLengthName = "a".repeat(64); // exactly 64 characters
476+
writeWorkerSource({ format: "ts" });
477+
writeWranglerConfig({
478+
main: "index.ts",
479+
workflows: [
480+
{
481+
binding: "MY_WORKFLOW",
482+
name: maxLengthName,
483+
class_name: "MyWorkflow",
484+
script_name: "external-script",
485+
},
486+
],
487+
});
488+
489+
await runWrangler("deploy --dry-run");
490+
expect(std.err).toBe("");
491+
});
492+
493+
it("should validate required fields for workflow binding", async () => {
494+
writeWranglerConfig({
495+
workflows: [
496+
{
497+
binding: "MY_WORKFLOW",
498+
} as any, // eslint-disable-line @typescript-eslint/no-explicit-any
499+
],
500+
});
501+
502+
await expect(runWrangler("deploy --dry-run")).rejects.toThrow();
503+
expect(std.err).toContain('should have a string "name" field');
504+
expect(std.err).toContain('should have a string "class_name" field');
505+
});
506+
507+
it("should validate optional fields for workflow binding", async () => {
508+
writeWorkerSource({ format: "ts" });
509+
writeWranglerConfig({
510+
main: "index.ts",
511+
workflows: [
512+
{
513+
binding: "MY_WORKFLOW",
514+
name: "my-workflow",
515+
class_name: "MyWorkflow",
516+
script_name: "external-script",
517+
},
518+
],
519+
});
520+
521+
await runWrangler("deploy --dry-run");
522+
expect(std.err).toBe("");
523+
});
524+
525+
it("should reject workflow binding with invalid field types", async () => {
526+
writeWranglerConfig({
527+
workflows: [
528+
{
529+
binding: 123, // should be string
530+
name: "my-workflow",
531+
class_name: "MyWorkflow",
532+
} as any, // eslint-disable-line @typescript-eslint/no-explicit-any
533+
],
534+
});
535+
536+
await expect(runWrangler("deploy --dry-run")).rejects.toThrow();
537+
expect(std.err).toContain('should have a string "binding" field');
538+
});
539+
540+
it("should reject workflow binding that is not an object", async () => {
541+
writeWranglerConfig({
542+
workflows: ["invalid-workflow-config"] as any, // eslint-disable-line @typescript-eslint/no-explicit-any
543+
});
544+
545+
await expect(runWrangler("deploy --dry-run")).rejects.toThrow();
546+
expect(std.err).toContain('"workflows" bindings should be objects');
547+
});
548+
});
436549
});

packages/wrangler/src/config/validation.ts

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2173,10 +2173,75 @@ const validateDurableObjectBinding: ValidatorFn = (
21732173
/**
21742174
* Check that the given field is a valid "workflow" binding object.
21752175
*/
2176-
const validateWorkflowBinding: ValidatorFn = (_diagnostics, _field, _value) => {
2177-
// TODO
2176+
const validateWorkflowBinding: ValidatorFn = (diagnostics, field, value) => {
2177+
if (typeof value !== "object" || value === null) {
2178+
diagnostics.errors.push(
2179+
`"workflows" bindings should be objects, but got ${JSON.stringify(value)}`
2180+
);
2181+
return false;
2182+
}
21782183

2179-
return true;
2184+
let isValid = true;
2185+
2186+
if (!isRequiredProperty(value, "binding", "string")) {
2187+
diagnostics.errors.push(
2188+
`"${field}" bindings should have a string "binding" field but got ${JSON.stringify(
2189+
value
2190+
)}.`
2191+
);
2192+
isValid = false;
2193+
}
2194+
2195+
if (!isRequiredProperty(value, "name", "string")) {
2196+
diagnostics.errors.push(
2197+
`"${field}" bindings should have a string "name" field but got ${JSON.stringify(
2198+
value
2199+
)}.`
2200+
);
2201+
isValid = false;
2202+
} else if (value.name.length > 64) {
2203+
diagnostics.errors.push(
2204+
`"${field}" binding "name" field must be 64 characters or less, but got ${value.name.length} characters.`
2205+
);
2206+
isValid = false;
2207+
}
2208+
2209+
if (!isRequiredProperty(value, "class_name", "string")) {
2210+
diagnostics.errors.push(
2211+
`"${field}" bindings should have a string "class_name" field but got ${JSON.stringify(
2212+
value
2213+
)}.`
2214+
);
2215+
isValid = false;
2216+
}
2217+
2218+
if (!isOptionalProperty(value, "script_name", "string")) {
2219+
diagnostics.errors.push(
2220+
`"${field}" bindings should, optionally, have a string "script_name" field but got ${JSON.stringify(
2221+
value
2222+
)}.`
2223+
);
2224+
isValid = false;
2225+
}
2226+
2227+
if (!isOptionalProperty(value, "experimental_remote", "boolean")) {
2228+
diagnostics.errors.push(
2229+
`"${field}" bindings should, optionally, have a boolean "experimental_remote" field but got ${JSON.stringify(
2230+
value
2231+
)}.`
2232+
);
2233+
isValid = false;
2234+
}
2235+
2236+
validateAdditionalProperties(diagnostics, field, Object.keys(value), [
2237+
"binding",
2238+
"name",
2239+
"class_name",
2240+
"script_name",
2241+
"experimental_remote",
2242+
]);
2243+
2244+
return isValid;
21802245
};
21812246

21822247
const validateCflogfwdrObject: (env: string) => ValidatorFn =

0 commit comments

Comments
 (0)