Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C3] fix: use a valid compatibility date for worker templates #3343

Merged
merged 3 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/small-lies-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"create-cloudflare": patch
---

fix: use a valid compatibility date for worker templates

Previously, we changed wrangler.toml to use the current date for the
compatibility_date setting in wrangler.toml when generating workers.
But this is almost always going to be too recent and results in a warning.

Now we look up the most recent compatibility date via npm on the workerd
package and use that instead.

Fixes https://github.com/cloudflare/workers-sdk/issues/2385
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"webassemblymemory",
"websockets",
"xxhash",
"workerd",
"zjcompt"
],
"cSpell.ignoreWords": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,51 @@ import { detectPackageManager } from "helpers/packages";
import { beforeEach, afterEach, describe, expect, test, vi } from "vitest";
import whichPMRuns from "which-pm-runs";
import {
getWorkerdCompatibilityDate,
installPackages,
installWrangler,
npmInstall,
runCommand,
} from "../command";

// We can change how the mock spawn works by setting these variables
let spawnResultCode = 0;
let spawnStdout: string | undefined = undefined;
let spawnStderr: string | undefined = undefined;

describe("Command Helpers", () => {
afterEach(() => {
vi.clearAllMocks();
spawnResultCode = 0;
spawnStdout = undefined;
spawnStderr = undefined;
});

beforeEach(() => {
// Mock out the child_process.spawn function
vi.mock("cross-spawn", () => {
const mockedSpawn = vi.fn().mockImplementation(() => ({
on: vi.fn().mockImplementation((event, cb) => {
if (event === "close") {
cb(0);
}
}),
}));
const mockedSpawn = vi.fn().mockImplementation(() => {
return {
on: vi.fn().mockImplementation((event, cb) => {
if (event === "close") {
cb(spawnResultCode);
}
}),
stdout: {
on(event: "data", cb: (data: string) => void) {
spawnStdout !== undefined && cb(spawnStdout);
},
},
stderr: {
on(event: "data", cb: (data: string) => void) {
spawnStderr !== undefined && cb(spawnStderr);
},
},
};
});

return { spawn: mockedSpawn };
});

vi.mock("which-pm-runs");
vi.mocked(whichPMRuns).mockReturnValue({ name: "npm", version: "8.3.1" });

Expand Down Expand Up @@ -142,4 +162,35 @@ describe("Command Helpers", () => {
expect(pm.dlx).toBe("yarn");
});
});

describe("getWorkerdCompatibilityDate()", () => {
test("normal flow", async () => {
spawnStdout = "2.20250110.5";
const date = await getWorkerdCompatibilityDate();
expectSpawnWith("npm info workerd dist-tags.latest");
expect(date).toBe("2025-01-10");
});

test("empty result", async () => {
spawnStdout = "";
const date = await getWorkerdCompatibilityDate();
expectSpawnWith("npm info workerd dist-tags.latest");
expect(date).toBe("2023-05-18");
});

test("verbose output (e.g. yarn or debug mode)", async () => {
spawnStdout =
"Debugger attached.\nyarn info v1.22.19\n2.20250110.5\n✨ Done in 0.83s.";
const date = await getWorkerdCompatibilityDate();
expectSpawnWith("npm info workerd dist-tags.latest");
expect(date).toBe("2025-01-10");
});

test("command failed", async () => {
spawnResultCode = 1;
const date = await getWorkerdCompatibilityDate();
expectSpawnWith("npm info workerd dist-tags.latest");
expect(date).toBe("2023-05-18");
});
});
});
92 changes: 75 additions & 17 deletions packages/create-cloudflare/src/helpers/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@ type Command = string | string[];

type RunOptions = {
startText?: string;
doneText?: string;
doneText?: string | ((output: string) => string);
silent?: boolean;
captureOutput?: boolean;
useSpinner?: boolean;
env?: NodeJS.ProcessEnv;
cwd?: string;
/** If defined this function is called to all you to transform the output from the command into a new string. */
transformOutput?: (output: string) => string;
/** If defined, this function is called to return a string that is used if the `transformOutput()` fails. */
fallbackOutput?: (error: unknown) => string;
};

type MultiRunOptions = RunOptions & {
Expand All @@ -35,35 +39,35 @@ type PrintOptions<T> = {
promise: Promise<T> | (() => Promise<T>);
useSpinner?: boolean;
startText: string;
doneText?: string;
doneText?: string | ((output: T) => string);
};

export const runCommand = async (
command: Command,
opts?: RunOptions
opts: RunOptions = {}
): Promise<string> => {
if (typeof command === "string") {
command = command.trim().replace(/\s+/g, ` `).split(" ");
}

return printAsyncStatus({
useSpinner: opts?.useSpinner ?? opts?.silent,
startText: opts?.startText || command.join(" "),
doneText: opts?.doneText,
useSpinner: opts.useSpinner ?? opts.silent,
startText: opts.startText || command.join(" "),
doneText: opts.doneText,
promise() {
const [executable, ...args] = command;

const squelch = opts?.silent || process.env.VITEST;
const squelch = opts.silent || process.env.VITEST;

const cmd = spawn(executable, [...args], {
// TODO: ideally inherit stderr, but npm install uses this for warnings
// stdio: [ioMode, ioMode, "inherit"],
stdio: squelch ? "pipe" : "inherit",
env: {
...process.env,
...opts?.env,
...opts.env,
},
cwd: opts?.cwd,
cwd: opts.cwd,
});

let output = ``;
Expand All @@ -79,27 +83,46 @@ export const runCommand = async (

return new Promise<string>((resolve, reject) => {
cmd.on("close", (code) => {
if (code === 0) {
resolve(stripAnsi(output));
} else {
reject(new Error(output, { cause: code }));
try {
if (code !== 0) {
throw new Error(output, { cause: code });
}

// Process any captured output
const transformOutput =
opts.transformOutput ?? ((result: string) => result);
const processedOutput = transformOutput(stripAnsi(output));

// Send the captured (and processed) output back to the caller
resolve(processedOutput);
} catch (e) {
// Something went wrong.
// Perhaps the command or the transform failed.
// If there is a fallback use the result of calling that
if (opts.fallbackOutput) {
resolve(opts.fallbackOutput(e));
} else {
reject(new Error(output, { cause: e }));
}
}
});
});
},
});
};

// run mutliple commands in sequence (not parallel)
// run multiple commands in sequence (not parallel)
export async function runCommands({ commands, ...opts }: MultiRunOptions) {
return printAsyncStatus({
useSpinner: opts.useSpinner ?? opts.silent,
startText: opts.startText,
doneText: opts.doneText,
async promise() {
const results = [];
for (const command of commands) {
await runCommand(command, { ...opts, useSpinner: false });
results.push(await runCommand(command, { ...opts, useSpinner: false }));
}
return results.join("\n");
},
});
}
Expand All @@ -121,9 +144,13 @@ export const printAsyncStatus = async <T>({
}

try {
await promise;
const output = await promise;

s?.stop(opts.doneText);
const doneText =
typeof opts.doneText === "function"
? opts.doneText(output)
: opts.doneText;
s?.stop(doneText);
} catch (err) {
s?.stop((err as Error).message);
} finally {
Expand Down Expand Up @@ -276,3 +303,34 @@ export const listAccounts = async () => {

return accounts;
};

/**
* Look up the latest release of workerd and use its date as the compatibility_date
petebacondarwin marked this conversation as resolved.
Show resolved Hide resolved
* configuration value for wrangler.toml.
*
* If the look up fails then we fall back to a well known date.
*
* The date is extracted from the version number of the workerd package tagged as `latest`.
* The format of the version is `major.yyyymmdd.patch`.
*
* @returns The latest compatibility date for workerd in the form "YYYY-MM-DD"
*/
export async function getWorkerdCompatibilityDate() {
const { npm } = detectPackageManager();
return runCommand(`${npm} info workerd dist-tags.latest`, {
silent: true,
captureOutput: true,
startText: "Retrieving current workerd compatibility date",
transformOutput: (result) => {
// The format of the workerd version is `major.yyyymmdd.patch`.
const match = result.match(/\d+\.(\d{4})(\d{2})(\d{2})\.\d+/);
if (!match) {
throw new Error("Could not find workerd date");
}
const [, year, month, date] = match;
return `${year}-${month}-${date}`;
},
fallbackOutput: () => "2023-05-18",
doneText: (output) => `${brandColor("compatibility date")} ${dim(output)}`,
});
}
16 changes: 11 additions & 5 deletions packages/create-cloudflare/src/workers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { resolve, join } from "path";
import { chdir } from "process";
import { endSection, updateStatus, startSection } from "helpers/cli";
import { brandColor, dim } from "helpers/colors";
import { npmInstall, runCommand } from "helpers/command";
import {
getWorkerdCompatibilityDate,
npmInstall,
runCommand,
} from "helpers/command";
import { confirmInput, textInput } from "helpers/interactive";
import {
chooseAccount,
Expand Down Expand Up @@ -149,12 +153,14 @@ async function updateFiles(ctx: Context) {
};

// update files
contents.packagejson.name = ctx.project.name;
if (contents.packagejson.name === "<TBD>") {
contents.packagejson.name = ctx.project.name;
}
contents.wranglertoml = contents.wranglertoml
petebacondarwin marked this conversation as resolved.
Show resolved Hide resolved
.replace(/^name = .+$/m, `name = "${ctx.project.name}"`)
.replace(/^name\s*=\s*"<TBD>"/m, `name = "${ctx.project.name}"`)
.replace(
/^compatibility_date = .+$/m,
`compatibility_date = "${new Date().toISOString().substring(0, 10)}"`
/^compatibility_date\s*=\s*"<TBD>"/m,
`compatibility_date = "${await getWorkerdCompatibilityDate()}"`
);

// write files
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
name = "cloudflare-workers-chatgpt-plugin-example"
name = "<TBD>"
main = "src/index.ts"
compatibility_date = "2023-04-07"
compatibility_date = "<TBD>"
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "<TBD>"
main = "src/worker.js"
compatibility_date = "2023-04-21"
compatibility_date = "<TBD>"

# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
# Note: Use secrets to store sensitive data.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "<TBD>"
main = "src/worker.ts"
compatibility_date = "2023-04-21"
compatibility_date = "<TBD>"

# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
# Note: Use secrets to store sensitive data.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "<TBD>"
main = "src/worker.js"
compatibility_date = "2023-04-21"
compatibility_date = "<TBD>"

# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
# Note: Use secrets to store sensitive data.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "<TBD>"
main = "src/worker.ts"
compatibility_date = "2023-04-21"
compatibility_date = "<TBD>"

# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
# Note: Use secrets to store sensitive data.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "<TBD>"
main = "src/worker.js"
compatibility_date = "2023-05-15"
compatibility_date = "<TBD>"

# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer.
# Docs: https://developers.cloudflare.com/queues/get-started
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "<TBD>"
main = "src/worker.ts"
compatibility_date = "2023-05-15"
compatibility_date = "<TBD>"

# Bind a Queue producer. Use this binding to schedule an arbitrary task that may be processed later by a Queue consumer.
# Docs: https://developers.cloudflare.com/queues/get-started
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "<TBD>"
main = "src/worker.ts"
compatibility_date = "2023-05-15"
compatibility_date = "<TBD>"

# Cron Triggers
# Docs: https://developers.cloudflare.com/workers/platform/triggers/cron-triggers/
Expand Down