Skip to content

Commit 8d25ded

Browse files
fix: setting triggers.crons:[] in Wrangler config should delete deployed cron schedules
1 parent ef579c7 commit 8d25ded

File tree

8 files changed

+125
-13
lines changed

8 files changed

+125
-13
lines changed

.changeset/shaky-planets-feel.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+
fix: setting triggers.crons:[] in Wrangler config should delete deployed cron schedules

packages/wrangler/src/__tests__/config/configuration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ describe("normalizeAndValidateConfig()", () => {
114114
ai: undefined,
115115
version_metadata: undefined,
116116
triggers: {
117-
crons: [],
117+
crons: undefined,
118118
},
119119
unsafe: {
120120
bindings: undefined,

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2069,6 +2069,43 @@ Update them to point to this script instead?`,
20692069
it.todo("should error if it's a workers.dev route");
20702070
});
20712071

2072+
describe("triggers", () => {
2073+
it("should deploy the worker with a scheduled trigger", async () => {
2074+
const crons = ["*/5 * * * *", "0 18 * * 6L"];
2075+
writeWranglerConfig({
2076+
triggers: { crons },
2077+
});
2078+
writeWorkerSource();
2079+
mockSubDomainRequest();
2080+
mockUploadWorkerRequest({ expectedType: "esm" });
2081+
mockPublishSchedulesRequest({ crons });
2082+
await runWrangler("deploy ./index");
2083+
});
2084+
2085+
it("should deploy the worker with an empty array of scheduled triggers", async () => {
2086+
const crons: string[] = [];
2087+
writeWranglerConfig({
2088+
triggers: { crons },
2089+
});
2090+
writeWorkerSource();
2091+
mockSubDomainRequest();
2092+
mockUploadWorkerRequest({ expectedType: "esm" });
2093+
mockPublishSchedulesRequest({ crons });
2094+
await runWrangler("deploy ./index");
2095+
});
2096+
2097+
it.each([{ triggers: { crons: undefined } }, { triggers: undefined }, {}])(
2098+
"should deploy the worker without updating the scheduled triggers",
2099+
async (config) => {
2100+
writeWranglerConfig(config);
2101+
writeWorkerSource();
2102+
mockSubDomainRequest();
2103+
mockUploadWorkerRequest({ expectedType: "esm" });
2104+
await runWrangler("deploy ./index");
2105+
}
2106+
);
2107+
});
2108+
20722109
describe("entry-points", () => {
20732110
it("should be able to use `index` with no extension as the entry-point (esm)", async () => {
20742111
writeWranglerConfig();
@@ -12943,6 +12980,38 @@ function mockLastDeploymentRequest() {
1294312980
msw.use(...mswSuccessDeploymentScriptMetadata);
1294412981
}
1294512982

12983+
function mockPublishSchedulesRequest({
12984+
crons = [],
12985+
env = undefined,
12986+
legacyEnv = false,
12987+
}: {
12988+
crons: Config["triggers"]["crons"];
12989+
env?: string | undefined;
12990+
legacyEnv?: boolean | undefined;
12991+
}) {
12992+
const servicesOrScripts = env && !legacyEnv ? "services" : "scripts";
12993+
const environment = env && !legacyEnv ? "/environments/:envName" : "";
12994+
12995+
msw.use(
12996+
http.put(
12997+
`*/accounts/:accountId/workers/${servicesOrScripts}/:scriptName${environment}/schedules`,
12998+
async ({ request, params }) => {
12999+
expect(params.accountId).toEqual("some-account-id");
13000+
expect(params.scriptName).toEqual(
13001+
legacyEnv && env ? `test-name-${env}` : "test-name"
13002+
);
13003+
if (!legacyEnv) {
13004+
expect(params.envName).toEqual(env);
13005+
}
13006+
const body = (await request.json()) as [{ cron: string }];
13007+
expect(body).toEqual(crons.map((cron) => ({ cron })));
13008+
return HttpResponse.json(createFetchResult(null));
13009+
},
13010+
{ once: true }
13011+
)
13012+
);
13013+
}
13014+
1294613015
function mockPublishRoutesRequest({
1294713016
routes = [],
1294813017
env = undefined,

packages/wrangler/src/config/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ export const defaultWranglerConfig: Config = {
332332
jsx_fragment: "React.Fragment",
333333
migrations: [],
334334
triggers: {
335-
crons: [],
335+
crons: undefined,
336336
},
337337
rules: [],
338338
build: { command: undefined, watch_dir: "./src", cwd: undefined },

packages/wrangler/src/config/environment.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,10 @@ interface EnvironmentInheritable {
285285
*
286286
* For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#triggers
287287
*
288-
* @default {crons:[]}
288+
* @default {crons: undefined}
289289
* @inheritable
290290
*/
291-
triggers: { crons: string[] };
291+
triggers: { crons: string[] | undefined };
292292

293293
/**
294294
* Specify limits for runtime behavior.

packages/wrangler/src/config/validation.ts

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
inheritableInLegacyEnvironments,
1818
isBoolean,
1919
isMutuallyExclusiveWith,
20-
isObjectWith,
2120
isOptionalProperty,
2221
isRequiredProperty,
2322
isString,
@@ -1122,8 +1121,8 @@ function normalizeAndValidateEnvironment(
11221121
topLevelEnv,
11231122
rawEnv,
11241123
"triggers",
1125-
isObjectWith("crons"),
1126-
{ crons: [] }
1124+
validateTriggers,
1125+
{ crons: undefined }
11271126
),
11281127
assets: normalizeAndValidateAssets(diagnostics, topLevelEnv, rawEnv),
11291128
limits: normalizeAndValidateLimits(diagnostics, topLevelEnv, rawEnv),
@@ -1509,6 +1508,44 @@ const validateAndNormalizeRules = (
15091508
);
15101509
};
15111510

1511+
const validateTriggers: ValidatorFn = (
1512+
diagnostics,
1513+
triggersFieldName,
1514+
triggersValue
1515+
) => {
1516+
if (triggersValue === undefined || triggersValue === null) {
1517+
return true;
1518+
}
1519+
1520+
if (typeof triggersValue !== "object") {
1521+
diagnostics.errors.push(
1522+
`Expected "${triggersFieldName}" to be of type object but got ${JSON.stringify(
1523+
triggersValue
1524+
)}.`
1525+
);
1526+
return false;
1527+
}
1528+
1529+
let isValid = true;
1530+
1531+
if ("crons" in triggersValue && !Array.isArray(triggersValue.crons)) {
1532+
diagnostics.errors.push(
1533+
`Expected "${triggersFieldName}.crons" to be of type array, but got ${JSON.stringify(triggersValue)}.`
1534+
);
1535+
isValid = false;
1536+
}
1537+
1538+
isValid =
1539+
validateAdditionalProperties(
1540+
diagnostics,
1541+
triggersFieldName,
1542+
Object.keys(triggersValue),
1543+
["crons"]
1544+
) && isValid;
1545+
1546+
return isValid;
1547+
};
1548+
15121549
const validateRules =
15131550
(envName: string): ValidatorFn =>
15141551
(diagnostics, field, envValue, config) => {

packages/wrangler/src/dev/miniflare.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,7 @@ export async function buildMiniflareOptions(
11381138
internalObjects: CfDurableObject[];
11391139
entrypointNames: string[];
11401140
}> {
1141-
if (config.crons.length > 0 && !config.testScheduled) {
1141+
if (config.crons?.length && !config.testScheduled) {
11421142
if (!didWarnMiniflareCronSupport) {
11431143
didWarnMiniflareCronSupport = true;
11441144
logger.warn(

packages/wrangler/src/triggers/deploy.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default async function triggersDeploy(
3838
): Promise<string[] | void> {
3939
const { config, accountId, name: scriptName } = props;
4040

41-
const triggers = props.triggers || config.triggers?.crons;
41+
const schedules = props.triggers || config.triggers?.crons;
4242
const routes =
4343
props.routes ?? config.routes ?? (config.route ? [config.route] : []) ?? [];
4444
const routesOnly: Array<Route> = [];
@@ -258,17 +258,18 @@ export default async function triggersDeploy(
258258
}
259259

260260
// Configure any schedules for the script.
261-
// TODO: rename this to `schedules`?
262-
if (triggers && triggers.length) {
261+
// If schedules is not defined then we just leave whatever is previously deployed alone.
262+
// If it is an empty array we will remove all schedules.
263+
if (schedules) {
263264
deployments.push(
264265
fetchResult(`${workerUrl}/schedules`, {
265266
// Note: PUT will override previous schedules on this script.
266267
method: "PUT",
267-
body: JSON.stringify(triggers.map((cron) => ({ cron }))),
268+
body: JSON.stringify(schedules.map((cron) => ({ cron }))),
268269
headers: {
269270
"Content-Type": "application/json",
270271
},
271-
}).then(() => triggers.map((trigger) => `schedule: ${trigger}`))
272+
}).then(() => schedules.map((trigger) => `schedule: ${trigger}`))
272273
);
273274
}
274275

0 commit comments

Comments
 (0)