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

feat: add runtime type generation to wrangler types command #6295

Merged
merged 12 commits into from
Jul 23, 2024
5 changes: 5 additions & 0 deletions .changeset/smart-dolls-join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

Introduce an experimental flag for `wrangler types` to dynamically generate runtime types according to the user's project configuration.
6 changes: 0 additions & 6 deletions fixtures/worker-app/worker-configuration.d.ts

This file was deleted.

133 changes: 133 additions & 0 deletions packages/wrangler/e2e/types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { existsSync } from "node:fs";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { dedent } from "../src/utils/dedent";
import { WranglerE2ETestHelper } from "./helpers/e2e-wrangler-test";

const seed = {
"wrangler.toml": dedent`
name = "test-worker"
main = "src/index.ts"
compatibility_date = "2023-01-01"
compatibility_flags = ["nodejs_compat"]
`,
"src/index.ts": dedent`
export default {
fetch(request) {
return new Response("Hello World!")
}
}
`,
"package.json": dedent`
{
"name": "test-worker",
"version": "0.0.0",
"private": true
}
`,
};

describe("types", () => {
it("should not generate runtime types without flag", async () => {
const helper = new WranglerE2ETestHelper();
await helper.seed(seed);
const output = await helper.run(`wrangler types`);

expect(output.stdout).not.toContain(`Generating runtime types...`);
});

it("should generate runtime types at the default path", async () => {
const helper = new WranglerE2ETestHelper();
await helper.seed(seed);
const output = await helper.run(`wrangler types --x-include-runtime`);

const fileExists = existsSync(
path.join(helper.tmpPath, "./.wrangler/types/runtime.d.ts")
);

expect(fileExists).toEqual(true);
expect(output.stdout).toContain(`Generating runtime types...`);
expect(output.stdout).toContain(`Generating project types...`);
expect(output.stdout).toContain(
`✨ Runtime types written to ./.wrangler/types/runtime.d.ts`
);
expect(output.stdout).toContain(
`"types": ["./.wrangler/types/runtime.d.ts"]`
);
expect(output.stdout).toContain(
`📣 It looks like you have some Node.js compatibility turned on in your project. You might want to consider adding Node.js typings with "npm i --save-dev @types/node@20.8.3". Please see the docs for more details: https://developers.cloudflare.com/workers/languages/typescript/#transitive-loading-of-typesnode-overrides-cloudflareworkers-types`
);
expect(output.stdout).toContain(
`Remember to run 'wrangler types --x-include-runtime' again if you change 'compatibility_date' or 'compatibility_flags' in your wrangler.toml.`
);
});

it("should generate runtime types at the provided path", async () => {
const helper = new WranglerE2ETestHelper();
await helper.seed(seed);
const output = await helper.run(
`wrangler types --x-include-runtime="./types.d.ts"`
);

const fileExists = existsSync(path.join(helper.tmpPath, "./types.d.ts"));

expect(fileExists).toEqual(true);
expect(output.stdout).toContain(`✨ Runtime types written to ./types.d.ts`);
expect(output.stdout).toContain(`"types": ["./types.d.ts"]`);
});

it("should generate types", async () => {
const helper = new WranglerE2ETestHelper();
await helper.seed(seed);
await helper.run(`wrangler types --x-include-runtime="./types.d.ts"`);

const file = (
await readFile(path.join(helper.tmpPath, "./types.d.ts"))
).toString();

expect(file).contains('declare module "cloudflare:workers"');
});

it("should recommend to uninstall @cloudflare/workers-types", async () => {
const helper = new WranglerE2ETestHelper();
await helper.seed({
...seed,
"tsconfig.json": dedent`
{
"compilerOptions": {
"types": ["@cloudflare/workers-types"]
}
}
`,
});
const output = await helper.run(
`wrangler types --x-include-runtime="./types.d.ts"`
);

expect(output.stdout).toContain(
`📣 You can now uninstall "@cloudflare/workers-types".`
);
});

it("should not recommend to install @types/node if 'node' exists in types array", async () => {
const helper = new WranglerE2ETestHelper();
await helper.seed({
...seed,
"tsconfig.json": dedent`
{
"compilerOptions": {
"types": ["node"]
}
}
`,
});
const output = await helper.run(
`wrangler types --x-include-runtime="./types.d.ts"`
);

expect(output.stdout).not.toContain(
`📣 It looks like you have some Node.js compatibility turned on in your project. You might want to consider adding Node.js typings with "npm i --save-dev @types/node@20.8.3". Please see the docs for more details: https://developers.cloudflare.com/workers/languages/typescript/#transitive-loading-of-typesnode-overrides-cloudflareworkers-types`
);
});
});
1 change: 1 addition & 0 deletions packages/wrangler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"selfsigned": "^2.0.1",
"source-map": "^0.6.1",
"unenv": "npm:unenv-nightly@1.10.0-1717606461.a117952",
"workerd": "1.20240718.0",
"xxhash-wasm": "^1.0.1"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions packages/wrangler/scripts/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const EXTERNAL_DEPENDENCIES = [
// unenv must be external because it contains unenv/runtime code which needs to be resolved
// and read when we are bundling the worker application
"unenv",
"workerd/worker.mjs",
penalosa marked this conversation as resolved.
Show resolved Hide resolved
];

const pathToPackageJson = path.resolve(__dirname, "..", "package.json");
Expand Down
36 changes: 28 additions & 8 deletions packages/wrangler/src/__tests__/type-generation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,14 +274,20 @@ describe("generateTypes()", () => {
await runWrangler("types -c my-wrangler-config-b.toml");

expect(std.out).toMatchInlineSnapshot(`
"interface Env {
"Generating project types...

interface Env {
var: \\"from wrangler toml\\";
}

Generating project types...

interface Env {
var: \\"from my-wrangler-config-a\\";
}

Generating project types...

interface Env {
var: \\"from my-wrangler-config-b\\";
}
Expand Down Expand Up @@ -313,7 +319,9 @@ describe("generateTypes()", () => {

await runWrangler("types");
expect(std.out).toMatchInlineSnapshot(`
"interface Env {
"Generating project types...

interface Env {
TEST_KV_NAMESPACE: KVNamespace;
SOMETHING: \\"asdasdfasdf\\";
ANOTHER: \\"thing\\";
Expand Down Expand Up @@ -413,7 +421,9 @@ describe("generateTypes()", () => {
/interface Env \{\s*\}/
);
expect(std.out).toMatchInlineSnapshot(`
"interface Env {
"Generating project types...

interface Env {
}
"
`);
Expand Down Expand Up @@ -467,7 +477,9 @@ describe("generateTypes()", () => {

await runWrangler("types");
expect(std.out).toMatchInlineSnapshot(`
"export {};
"Generating project types...

export {};
declare global {
const testing_unsafe: any;
}
Expand All @@ -486,7 +498,9 @@ describe("generateTypes()", () => {

await runWrangler("types");
expect(std.out).toMatchInlineSnapshot(`
"interface Env {
"Generating project types...

interface Env {
SOMETHING: \\"asdasdfasdf\\";
ANOTHER: \\"thing\\";
\\"some-other-var\\": \\"some-other-value\\";
Expand Down Expand Up @@ -520,7 +534,9 @@ describe("generateTypes()", () => {
await runWrangler("types");

expect(std.out).toMatchInlineSnapshot(`
"interface Env {
"Generating project types...

interface Env {
myTomlVarA: \\"A from wrangler toml\\";
myTomlVarB: \\"B from wrangler toml\\";
SECRET_A: string;
Expand Down Expand Up @@ -553,7 +569,9 @@ describe("generateTypes()", () => {
await runWrangler("types");

expect(std.out).toMatchInlineSnapshot(`
"interface Env {
"Generating project types...

interface Env {
MY_VARIABLE_A: string;
MY_VARIABLE_B: string;
}
Expand All @@ -574,7 +592,9 @@ describe("generateTypes()", () => {

await runWrangler("types --env-interface CloudflareEnv");
expect(std.out).toMatchInlineSnapshot(`
"interface CloudflareEnv {
"Generating project types...

interface CloudflareEnv {
SOMETHING: \\"asdasdfasdf\\";
ANOTHER: \\"thing\\";
\\"some-other-var\\": \\"some-other-value\\";
Expand Down
45 changes: 33 additions & 12 deletions packages/wrangler/src/deployment-bundle/node-compat.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { UserError } from "../errors";
import { logger } from "../logger";
import type { Config } from "../config";

/**
* Wrangler can provide Node.js compatibility in a number of different modes:
Expand Down Expand Up @@ -42,9 +43,11 @@ export function validateNodeCompat({
);
}

const nodejsCompatV2 = compatibilityFlags.includes(
"experimental:nodejs_compat_v2"
);
const { mode, nodejsCompat, nodejsCompatV2 } = getNodeCompatMode({
compatibility_flags: compatibilityFlags,
node_compat: legacyNodeCompat,
});

const nodejsCompatV2NotExperimental =
compatibilityFlags.includes("nodejs_compat_v2");

Expand All @@ -55,7 +58,6 @@ export function validateNodeCompat({
] = "nodejs_compat_v2";
}

const nodejsCompat = compatibilityFlags.includes("nodejs_compat");
if (nodejsCompat && nodejsCompatV2) {
throw new UserError(
"The `nodejs_compat` and `nodejs_compat_v2` compatibility flags cannot be used in together. Please select just one."
Expand Down Expand Up @@ -92,14 +94,33 @@ export function validateNodeCompat({
);
}

return mode;
}

export function getNodeCompatMode({
compatibility_flags,
node_compat,
}: Pick<Config, "compatibility_flags" | "node_compat">) {
const nodejsCompat = compatibility_flags.includes("nodejs_compat");
const nodejsCompatV2 = compatibility_flags.includes(
"experimental:nodejs_compat_v2"
);

let mode: NodeJSCompatMode;
if (nodejsCompatV2) {
return "v2";
}
if (nodejsCompat) {
return "v1";
mode = "v2";
} else if (nodejsCompat) {
mode = "v1";
} else if (node_compat) {
mode = "legacy";
} else {
mode = null;
}
if (legacyNodeCompat) {
return "legacy";
}
return null;

return {
legacy: node_compat === true,
mode,
nodejsCompat,
nodejsCompatV2,
};
}
Loading
Loading