Skip to content

Commit 5c3b83f

Browse files
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]>
1 parent a341101 commit 5c3b83f

File tree

3 files changed

+187
-3
lines changed

3 files changed

+187
-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: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from "./helpers/normalize";
1414
import { runInTempDir } from "./helpers/run-in-tmp";
1515
import { runWrangler } from "./helpers/run-wrangler";
16+
import { writeWorkerSource } from "./helpers/write-worker-source";
1617
import { writeWranglerConfig } from "./helpers/write-wrangler-config";
1718
import type { Instance, Workflow } from "../workflows/types";
1819

@@ -667,4 +668,117 @@ describe("wrangler workflows", () => {
667668
);
668669
});
669670
});
671+
672+
describe("workflow binding validation", () => {
673+
it("should validate workflow binding with valid name", async () => {
674+
writeWorkerSource({ format: "ts" });
675+
writeWranglerConfig({
676+
main: "index.ts",
677+
workflows: [
678+
{
679+
binding: "MY_WORKFLOW",
680+
name: "valid-workflow-name",
681+
class_name: "MyWorkflow",
682+
script_name: "external-script",
683+
},
684+
],
685+
});
686+
687+
await runWrangler("deploy --dry-run");
688+
expect(std.err).toBe("");
689+
});
690+
691+
it("should reject workflow binding with name exceeding 64 characters", async () => {
692+
const longName = "a".repeat(65); // 65 characters
693+
writeWranglerConfig({
694+
workflows: [
695+
{
696+
binding: "MY_WORKFLOW",
697+
name: longName,
698+
class_name: "MyWorkflow",
699+
},
700+
],
701+
});
702+
703+
await expect(runWrangler("deploy --dry-run")).rejects.toThrow();
704+
expect(std.err).toContain("must be 64 characters or less");
705+
expect(std.err).toContain("but got 65 characters");
706+
});
707+
708+
it("should accept workflow binding with name exactly 64 characters", async () => {
709+
const maxLengthName = "a".repeat(64); // exactly 64 characters
710+
writeWorkerSource({ format: "ts" });
711+
writeWranglerConfig({
712+
main: "index.ts",
713+
workflows: [
714+
{
715+
binding: "MY_WORKFLOW",
716+
name: maxLengthName,
717+
class_name: "MyWorkflow",
718+
script_name: "external-script",
719+
},
720+
],
721+
});
722+
723+
await runWrangler("deploy --dry-run");
724+
expect(std.err).toBe("");
725+
});
726+
727+
it("should validate required fields for workflow binding", async () => {
728+
writeWranglerConfig({
729+
workflows: [
730+
{
731+
binding: "MY_WORKFLOW",
732+
} as any, // eslint-disable-line @typescript-eslint/no-explicit-any
733+
],
734+
});
735+
736+
await expect(runWrangler("deploy --dry-run")).rejects.toThrow();
737+
expect(std.err).toContain('should have a string "name" field');
738+
expect(std.err).toContain('should have a string "class_name" field');
739+
});
740+
741+
it("should validate optional fields for workflow binding", async () => {
742+
writeWorkerSource({ format: "ts" });
743+
writeWranglerConfig({
744+
main: "index.ts",
745+
workflows: [
746+
{
747+
binding: "MY_WORKFLOW",
748+
name: "my-workflow",
749+
class_name: "MyWorkflow",
750+
script_name: "external-script",
751+
experimental_remote: true,
752+
},
753+
],
754+
});
755+
756+
await runWrangler("deploy --dry-run");
757+
expect(std.err).toBe("");
758+
});
759+
760+
it("should reject workflow binding with invalid field types", async () => {
761+
writeWranglerConfig({
762+
workflows: [
763+
{
764+
binding: 123, // should be string
765+
name: "my-workflow",
766+
class_name: "MyWorkflow",
767+
} as any, // eslint-disable-line @typescript-eslint/no-explicit-any
768+
],
769+
});
770+
771+
await expect(runWrangler("deploy --dry-run")).rejects.toThrow();
772+
expect(std.err).toContain('should have a string "binding" field');
773+
});
774+
775+
it("should reject workflow binding that is not an object", async () => {
776+
writeWranglerConfig({
777+
workflows: ["invalid-workflow-config"] as any, // eslint-disable-line @typescript-eslint/no-explicit-any
778+
});
779+
780+
await expect(runWrangler("deploy --dry-run")).rejects.toThrow();
781+
expect(std.err).toContain('"workflows" bindings should be objects');
782+
});
783+
});
670784
});

packages/wrangler/src/config/validation.ts

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2000,10 +2000,75 @@ const validateDurableObjectBinding: ValidatorFn = (
20002000
/**
20012001
* Check that the given field is a valid "workflow" binding object.
20022002
*/
2003-
const validateWorkflowBinding: ValidatorFn = (_diagnostics, _field, _value) => {
2004-
// TODO
2003+
const validateWorkflowBinding: ValidatorFn = (diagnostics, field, value) => {
2004+
if (typeof value !== "object" || value === null) {
2005+
diagnostics.errors.push(
2006+
`"workflows" bindings should be objects, but got ${JSON.stringify(value)}`
2007+
);
2008+
return false;
2009+
}
20052010

2006-
return true;
2011+
let isValid = true;
2012+
2013+
if (!isRequiredProperty(value, "binding", "string")) {
2014+
diagnostics.errors.push(
2015+
`"${field}" bindings should have a string "binding" field but got ${JSON.stringify(
2016+
value
2017+
)}.`
2018+
);
2019+
isValid = false;
2020+
}
2021+
2022+
if (!isRequiredProperty(value, "name", "string")) {
2023+
diagnostics.errors.push(
2024+
`"${field}" bindings should have a string "name" field but got ${JSON.stringify(
2025+
value
2026+
)}.`
2027+
);
2028+
isValid = false;
2029+
} else if (value.name.length > 64) {
2030+
diagnostics.errors.push(
2031+
`"${field}" binding "name" field must be 64 characters or less, but got ${value.name.length} characters.`
2032+
);
2033+
isValid = false;
2034+
}
2035+
2036+
if (!isRequiredProperty(value, "class_name", "string")) {
2037+
diagnostics.errors.push(
2038+
`"${field}" bindings should have a string "class_name" field but got ${JSON.stringify(
2039+
value
2040+
)}.`
2041+
);
2042+
isValid = false;
2043+
}
2044+
2045+
if (!isOptionalProperty(value, "script_name", "string")) {
2046+
diagnostics.errors.push(
2047+
`"${field}" bindings should, optionally, have a string "script_name" field but got ${JSON.stringify(
2048+
value
2049+
)}.`
2050+
);
2051+
isValid = false;
2052+
}
2053+
2054+
if (!isOptionalProperty(value, "experimental_remote", "boolean")) {
2055+
diagnostics.errors.push(
2056+
`"${field}" bindings should, optionally, have a boolean "experimental_remote" field but got ${JSON.stringify(
2057+
value
2058+
)}.`
2059+
);
2060+
isValid = false;
2061+
}
2062+
2063+
validateAdditionalProperties(diagnostics, field, Object.keys(value), [
2064+
"binding",
2065+
"name",
2066+
"class_name",
2067+
"script_name",
2068+
"experimental_remote",
2069+
]);
2070+
2071+
return isValid;
20072072
};
20082073

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

0 commit comments

Comments
 (0)