Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
5 changes: 5 additions & 0 deletions .changeset/slimy-eels-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-cloudflare": minor
---

use `wrangler types` to generate types instead of using the `@cloudflare/workers-types` package
60 changes: 27 additions & 33 deletions packages/create-cloudflare/e2e-tests/frameworks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ describe.concurrent(

Object.entries(frameworkTests).forEach(([frameworkKey, testConfig]) => {
const frameworkConfig = getFrameworkConfig(frameworkKey);

frameworkConfig.workersTypes ??= "generated";
frameworkConfig.typesPath ??= "worker-configuration.d.ts";
frameworkConfig.envInterfaceName ??= "Env";
test({ experimental }).runIf(
shouldRunTest(frameworkConfig.id, testConfig),
)(
Expand Down Expand Up @@ -176,7 +178,11 @@ describe.concurrent(
project.path,
logStream,
);
await verifyBuildCfTypesScript(testConfig, project.path, logStream);
await verifyBuildCfTypesScript(
frameworkConfig,
project.path,
logStream,
);
await verifyBuildScript(testConfig, project.path, logStream);
} catch (e) {
console.error("ERROR", e);
Expand Down Expand Up @@ -353,56 +359,44 @@ const verifyPreviewScript = async (
};

const verifyBuildCfTypesScript = async (
{ verifyBuildCfTypes }: FrameworkTestConfig,
{ workersTypes, typesPath, envInterfaceName }: TemplateConfig,
projectPath: string,
logStream: Writable,
) => {
if (!verifyBuildCfTypes) {
if (workersTypes === "none") {
return;
}

const { outputFile, envInterfaceName } = verifyBuildCfTypes;

const outputFileContentPre = readFile(join(projectPath, outputFile));
const outputFileContentPreLines = outputFileContentPre.split("\n");

// the file contains the "Generated by Wrangler" comment without a timestamp
expect(outputFileContentPreLines).toContain("// Generated by Wrangler");

// the file contains the env interface
// the file still contains the env interface
const hasEnvInterfacePre = outputFileContentPreLines.some(
(line) =>
// old type gen - some framework templates pin older versions of wrangler
line === `interface ${envInterfaceName} {` ||
// new after importable env change
line === `interface ${envInterfaceName} extends Cloudflare.Env {}`,
);
expect(hasEnvInterfacePre).toBe(true);

// Run the `cf-typegen` script to generate types for bindings in fixture
const buildTypesProc = spawnWithLogging(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think its useful here to test wrangler types works, so instead this now tests the immediate output of c3 is as expected.

[pm, "run", "cf-typegen"],
{ cwd: projectPath },
logStream,
);
await waitForExit(buildTypesProc);

const outputFileContentPost = readFile(join(projectPath, outputFile));
const outputFileContentPostLines = outputFileContentPost.split("\n");
await waitForExit(buildTypesProc);

// the file doesn't contain the "Generated by Wrangler" comment anymore
expect(outputFileContentPostLines).not.toContain("// Generated by Wrangler");
const outputFileContent = readFile(
join(projectPath, typesPath ?? "worker-configuration.d.ts"),
).split("\n");

// the file still contains the env interface
const hasEnvInterfacePost = outputFileContentPostLines.some(
// the file contains the env interface
const hasEnvInterfacePre = outputFileContent.some(
(line) =>
// old type gen - some framework templates pin older versions of wrangler
line === `interface ${envInterfaceName} {` ||
line === `interface ${envInterfaceName ?? "Env"} {` ||
// new after importable env change
line === `interface ${envInterfaceName} extends Cloudflare.Env {}`,
line ===
`interface ${envInterfaceName ?? "Env"} extends Cloudflare.Env {}`,
);
expect(hasEnvInterfacePost).toBe(true);
expect(hasEnvInterfacePre).toBe(true);

// if the types were installed, only the Env types will be present
if (workersTypes === "generated") {
expect(outputFileContent[2]).match(
/\/\/ Runtime types generated with workerd@1\.\d{8}\.\d \d{4}-\d{2}-\d{2} ([a-z_]+,?)*/,
);
}
};

const verifyBuildScript = async (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ export default function getFrameworkTestConfig(pm: string) {
expectedText: "Hello from Cloudflare",
previewArgs: ["--host=127.0.0.1"],
},
verifyBuildCfTypes: {
outputFile: "worker-configuration.d.ts",
envInterfaceName: "Env",
},
flags: ["--no-install", "--no-git-init"],
},
"astro:pages": {
Expand Down Expand Up @@ -142,10 +138,6 @@ export default function getFrameworkTestConfig(pm: string) {
route: "/api/v1/test",
expectedText: "C3_TEST",
},
verifyBuildCfTypes: {
outputFile: "worker-configuration.d.ts",
envInterfaceName: "Env",
},
verifyBuild: {
outputDir: "./dist/analog/public",
script: "build",
Expand Down Expand Up @@ -283,10 +275,6 @@ export default function getFrameworkTestConfig(pm: string) {
route: "/",
expectedText: "Welcome to Qwik",
},
verifyBuildCfTypes: {
outputFile: "worker-configuration.d.ts",
envInterfaceName: "Env",
},
},
"qwik:workers": {
argv: ["--platform", "workers"],
Expand All @@ -308,10 +296,6 @@ export default function getFrameworkTestConfig(pm: string) {
route: "/",
expectedText: "Welcome to Qwik",
},
verifyBuildCfTypes: {
outputFile: "worker-configuration.d.ts",
envInterfaceName: "Env",
},
},
"remix:pages": {
argv: ["--platform", "pages"],
Expand All @@ -327,10 +311,6 @@ export default function getFrameworkTestConfig(pm: string) {
route: "/test",
expectedText: "C3_TEST",
},
verifyBuildCfTypes: {
outputFile: "worker-configuration.d.ts",
envInterfaceName: "Env",
},
verifyBuild: {
outputDir: "./build/client",
script: "build",
Expand All @@ -353,10 +333,6 @@ export default function getFrameworkTestConfig(pm: string) {
route: "/test",
expectedText: "C3_TEST",
},
verifyBuildCfTypes: {
outputFile: "worker-configuration.d.ts",
envInterfaceName: "Env",
},
flags: ["--typescript", "--no-install", "--no-git-init"],
},
"next:pages": {
Expand All @@ -370,10 +346,6 @@ export default function getFrameworkTestConfig(pm: string) {
},
],
testCommitMessage: true,
verifyBuildCfTypes: {
outputFile: "env.d.ts",
envInterfaceName: "CloudflareEnv",
},
verifyDeploy: {
route: "/",
expectedText: "Create Next App",
Expand Down Expand Up @@ -410,10 +382,6 @@ export default function getFrameworkTestConfig(pm: string) {
"@/*",
"--src-dir",
],
verifyBuildCfTypes: {
outputFile: "cloudflare-env.d.ts",
envInterfaceName: "CloudflareEnv",
},
verifyPreview: {
previewArgs: ["--"],
route: "/test",
Expand Down Expand Up @@ -451,10 +419,6 @@ export default function getFrameworkTestConfig(pm: string) {
route: "/test",
expectedText: "C3_TEST",
},
verifyBuildCfTypes: {
outputFile: "worker-configuration.d.ts",
envInterfaceName: "Env",
},
verifyBuild: {
outputDir: "./dist",
script: "build",
Expand Down Expand Up @@ -482,10 +446,6 @@ export default function getFrameworkTestConfig(pm: string) {
route: "/test",
expectedText: "C3_TEST",
},
verifyBuildCfTypes: {
outputFile: "worker-configuration.d.ts",
envInterfaceName: "Env",
},
},
"react:pages": {
argv: ["--platform", "pages"],
Expand Down
57 changes: 50 additions & 7 deletions packages/create-cloudflare/src/__tests__/workers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { mockSpinner } from "helpers/__tests__/mocks";
import { getLatestTypesEntrypoint } from "helpers/compatDate";
import { readFile, writeFile } from "helpers/files";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { addWorkersTypesToTsConfig } from "../workers";
import { updateTsConfig } from "../workers";
import { createTestContext } from "./helpers";
import type { C3Context } from "types";

Expand All @@ -18,12 +18,13 @@ beforeEach(() => {

const mockCompatDate = "2024-01-17";

describe("addWorkersTypesToTsConfig", () => {
describe("updateTsConfig", () => {
let ctx: C3Context;

beforeEach(() => {
ctx = createTestContext();
ctx.args.ts = true;
ctx.template.workersTypes = "generated";

vi.mocked(existsSync).mockImplementation(() => true);
vi.mocked(getLatestTypesEntrypoint).mockReturnValue(mockCompatDate);
Expand All @@ -34,8 +35,10 @@ describe("addWorkersTypesToTsConfig", () => {
);
});

test("happy path", async () => {
await addWorkersTypesToTsConfig(ctx);
test("installing workers types", async () => {
ctx.template.workersTypes = "installed";

await updateTsConfig(ctx);

expect(writeFile).toHaveBeenCalled();

Expand All @@ -46,26 +49,66 @@ describe("addWorkersTypesToTsConfig", () => {

test("tsconfig.json not found", async () => {
vi.mocked(existsSync).mockImplementation(() => false);
await addWorkersTypesToTsConfig(ctx);
await updateTsConfig(ctx);
expect(writeFile).not.toHaveBeenCalled();
});

test("latest entrypoint not found", async () => {
ctx.template.workersTypes = "installed";

vi.mocked(getLatestTypesEntrypoint).mockReturnValue(null);
await addWorkersTypesToTsConfig(ctx);
await updateTsConfig(ctx);

expect(writeFile).not.toHaveBeenCalled();
});

test("don't clobber existing entrypoints", async () => {
ctx.template.workersTypes = "installed";
vi.mocked(readFile).mockImplementation(
() =>
`{ "compilerOptions": { "types" : ["@cloudflare/workers-types/2021-03-20"]} }`,
);
await addWorkersTypesToTsConfig(ctx);
await updateTsConfig(ctx);

expect(vi.mocked(writeFile).mock.calls[0][1]).toContain(
`"@cloudflare/workers-types/2021-03-20"`,
);
});

test("will remove workers-types when generating types, if generated types include runtime types", async () => {
vi.mocked(readFile).mockImplementation((path) => {
console.log("path", path);
if (path.includes("tsconfig.json")) {
return `{ "compilerOptions": { "types" : ["@cloudflare/workers-types/2021-03-20"]} }`;
} else {
return "// Runtime types generated with workerd";
}
});
await updateTsConfig(ctx);
expect(vi.mocked(writeFile).mock.calls[0][1]).not.toContain(
`"@cloudflare/workers-types/2021-03-20"`,
);
});

test("will NOT remove workers-types when generating types, if generated types don't include runtime types", async () => {
vi.mocked(readFile).mockImplementation((path) => {
if (path.includes("tsconfig.json")) {
return `{ "compilerOptions": { "types" : ["@cloudflare/workers-types/2021-03-20"]} }`;
} else {
return "no runtime types here";
}
});
await updateTsConfig(ctx);

expect(vi.mocked(writeFile).mock.calls[0][1]).toContain(
`"@cloudflare/workers-types/2021-03-20"`,
);
});

test("will add generated types file", async () => {
await updateTsConfig(ctx);
expect(vi.mocked(writeFile).mock.calls[0][1]).toContain(
`./worker-configuration.d.ts`,
);
});
});
8 changes: 6 additions & 2 deletions packages/create-cloudflare/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
updatePackageScripts,
} from "./templates";
import { validateProjectDirectory } from "./validators";
import { installWorkersTypes } from "./workers";
import { generateWorkersTypes, installWorkersTypes } from "./workers";
import { updateWranglerConfig } from "./wrangler/config";
import type { C3Args, C3Context } from "types";

Expand Down Expand Up @@ -154,7 +154,6 @@ const configure = async (ctx: C3Context) => {
startSection("Configuring your application for Cloudflare", "Step 2 of 3");

await installWrangler();
await installWorkersTypes(ctx);

// Note: This _must_ be called before the configure phase since
// pre-existing workers assume its presence in their configure phase
Expand All @@ -168,6 +167,11 @@ const configure = async (ctx: C3Context) => {
addWranglerToGitIgnore(ctx);

await updatePackageScripts(ctx);
if (ctx.template.workersTypes === "installed") {
await installWorkersTypes(ctx);
} else if (ctx.template.workersTypes === "generated") {
await generateWorkersTypes(ctx);
}

await offerGit(ctx);
await gitCommit(ctx);
Expand Down
12 changes: 12 additions & 0 deletions packages/create-cloudflare/src/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,18 @@ export type TemplateConfig = {
/** The key of the package.json "scripts" entry for previewing the project. Defaults to undefined (there might not be such script) */
previewScript?: string;

/** The file path to the generated types file. Defaults to worker-configuration.d.ts*/
typesPath?: string;
/** The name of the Env type generated by wrangler types. Defaults to `Env`*/
envInterfaceName?: string;

/** The file path of the template. This is used internally and isn't a user facing config value.*/
path?: string;

bindings?: Record<string, unknown>;

/** Default false. To accomodate SSG frameworks etc. */
workersTypes?: "generated" | "installed" | "none";
};

type CopyFiles = (StaticFileMap | VariantInfo) & {
Expand Down Expand Up @@ -605,6 +613,10 @@ export const createContext = async (
const directory = dirname(path);
const originalCWD = process.cwd();

// set default to generate types
template.workersTypes ??= "generated";
template.typesPath ??= "worker-configuration.d.ts";
template.envInterfaceName ??= "Env";
return {
project: { name, path },
// We need to maintain a reference to the original args
Expand Down
Loading
Loading