|
1 | 1 | import * as fs from "node:fs"; |
2 | 2 | import { http, HttpResponse } from "msw"; |
3 | 3 | import { afterAll, beforeAll, beforeEach, describe, it, vi } from "vitest"; |
| 4 | +import { experimental_generateTypes } from "../api"; |
4 | 5 | import { |
5 | 6 | constructTSModuleGlob, |
6 | 7 | constructTypeKey, |
@@ -544,7 +545,7 @@ const bindingsConfigMock: Omit< |
544 | 545 | vpc_networks: [], |
545 | 546 | }; |
546 | 547 |
|
547 | | -describe("generate types", () => { |
| 548 | +describe("generate types - CLI", () => { |
548 | 549 | let spy: MockInstance; |
549 | 550 | const std = mockConsoleMethods(); |
550 | 551 | const originalColumns = process.stdout.columns; |
@@ -3549,6 +3550,289 @@ describe("generate types", () => { |
3549 | 3550 | }); |
3550 | 3551 | }); |
3551 | 3552 |
|
| 3553 | +describe("generate types - API", () => { |
| 3554 | + runInTempDir(); |
| 3555 | + |
| 3556 | + beforeEach(() => { |
| 3557 | + for (const configPath of [ |
| 3558 | + "./wrangler.jsonc", |
| 3559 | + "./wrangler.json", |
| 3560 | + "./wrangler.toml", |
| 3561 | + ]) { |
| 3562 | + if (fs.existsSync(configPath)) { |
| 3563 | + fs.unlinkSync(configPath); |
| 3564 | + } |
| 3565 | + } |
| 3566 | + |
| 3567 | + vi.spyOn(generateRuntime, "generateRuntimeTypes").mockImplementation( |
| 3568 | + async () => ({ |
| 3569 | + runtimeHeader: "// Runtime types generated with workerd@", |
| 3570 | + runtimeTypes: "<runtime types go here>", |
| 3571 | + }) |
| 3572 | + ); |
| 3573 | + }); |
| 3574 | + |
| 3575 | + it("returns combined and split `env` output", async ({ expect }) => { |
| 3576 | + fs.writeFileSync( |
| 3577 | + "./wrangler.jsonc", |
| 3578 | + JSON.stringify({ |
| 3579 | + compatibility_date: "2026-01-01", |
| 3580 | + vars: { |
| 3581 | + myVar: "hello", |
| 3582 | + }, |
| 3583 | + }), |
| 3584 | + "utf-8" |
| 3585 | + ); |
| 3586 | + |
| 3587 | + const generated = await experimental_generateTypes({ |
| 3588 | + includeRuntime: false, |
| 3589 | + }); |
| 3590 | + |
| 3591 | + expect(generated.path).toBe("worker-configuration.d.ts"); |
| 3592 | + expect(generated.runtime).toBeNull(); |
| 3593 | + expect(generated.env).toContain('myVar: "hello";'); |
| 3594 | + expect(generated.content).toContain("/* eslint-disable */"); |
| 3595 | + expect(generated.content).toContain( |
| 3596 | + "interface Env extends Cloudflare.Env {}" |
| 3597 | + ); |
| 3598 | + expect(fs.existsSync("./worker-configuration.d.ts")).toBe(false); |
| 3599 | + }); |
| 3600 | + |
| 3601 | + it("returns `runtime` output when `env` types are excluded", async ({ |
| 3602 | + expect, |
| 3603 | + }) => { |
| 3604 | + fs.writeFileSync( |
| 3605 | + "./wrangler.jsonc", |
| 3606 | + JSON.stringify({ |
| 3607 | + compatibility_date: "2026-01-01", |
| 3608 | + }), |
| 3609 | + "utf-8" |
| 3610 | + ); |
| 3611 | + |
| 3612 | + const result = await experimental_generateTypes({ |
| 3613 | + includeEnv: false, |
| 3614 | + includeRuntime: true, |
| 3615 | + }); |
| 3616 | + |
| 3617 | + expect(result.env).toBeNull(); |
| 3618 | + expect(result.runtime).toBe("<runtime types go here>"); |
| 3619 | + expect(result.content).toContain("// Begin runtime types"); |
| 3620 | + expect(result.content).toContain("<runtime types go here>"); |
| 3621 | + }); |
| 3622 | + |
| 3623 | + it("supports `strictVars=false` and custom env interface/path", async ({ |
| 3624 | + expect, |
| 3625 | + }) => { |
| 3626 | + fs.writeFileSync( |
| 3627 | + "./wrangler.jsonc", |
| 3628 | + JSON.stringify({ |
| 3629 | + compatibility_date: "2026-01-01", |
| 3630 | + vars: { |
| 3631 | + myVar: "hello", |
| 3632 | + }, |
| 3633 | + }), |
| 3634 | + "utf-8" |
| 3635 | + ); |
| 3636 | + |
| 3637 | + const result = await experimental_generateTypes({ |
| 3638 | + envInterface: "CustomEnv", |
| 3639 | + includeRuntime: false, |
| 3640 | + path: "./types/custom-worker.d.ts", |
| 3641 | + strictVars: false, |
| 3642 | + }); |
| 3643 | + |
| 3644 | + expect(result.path).toBe("./types/custom-worker.d.ts"); |
| 3645 | + expect(result.content).toContain( |
| 3646 | + "interface CustomEnv extends Cloudflare.Env {}" |
| 3647 | + ); |
| 3648 | + expect(result.env).toContain("myVar: string;"); |
| 3649 | + expect(fs.existsSync("./types/custom-worker.d.ts")).toBe(false); |
| 3650 | + }); |
| 3651 | + |
| 3652 | + it("builds `env` header command from API options, not process args", async ({ |
| 3653 | + expect, |
| 3654 | + }) => { |
| 3655 | + fs.writeFileSync( |
| 3656 | + "./wrangler.jsonc", |
| 3657 | + JSON.stringify({ |
| 3658 | + compatibility_date: "2026-01-01", |
| 3659 | + vars: { |
| 3660 | + myVar: "hello", |
| 3661 | + }, |
| 3662 | + }), |
| 3663 | + "utf-8" |
| 3664 | + ); |
| 3665 | + |
| 3666 | + const previousArgv = process.argv; |
| 3667 | + process.argv = ["node", "vitest", "run"]; |
| 3668 | + |
| 3669 | + try { |
| 3670 | + const result = await experimental_generateTypes({ |
| 3671 | + envInterface: "CustomEnv", |
| 3672 | + includeRuntime: false, |
| 3673 | + path: "./types/custom-worker.d.ts", |
| 3674 | + strictVars: false, |
| 3675 | + }); |
| 3676 | + |
| 3677 | + expect(result.content).toMatch( |
| 3678 | + /\/\/ Generated by Wrangler by running `wrangler types --include-runtime=false --strict-vars=false --env-interface=CustomEnv \.\/types\/custom-worker\.d\.ts` \(hash: [a-f0-9]{32}\)/ |
| 3679 | + ); |
| 3680 | + } finally { |
| 3681 | + process.argv = previousArgv; |
| 3682 | + } |
| 3683 | + }); |
| 3684 | + |
| 3685 | + it("supports multi-config service resolution", async ({ expect }) => { |
| 3686 | + fs.mkdirSync("./primary", { recursive: true }); |
| 3687 | + fs.mkdirSync("./secondary", { recursive: true }); |
| 3688 | + |
| 3689 | + // Primary worker |
| 3690 | + fs.writeFileSync( |
| 3691 | + "./primary/wrangler.jsonc", |
| 3692 | + JSON.stringify({ |
| 3693 | + compatibility_date: "2026-01-01", |
| 3694 | + main: "./index.ts", |
| 3695 | + name: "primary", |
| 3696 | + services: [ |
| 3697 | + { |
| 3698 | + binding: "SECONDARY", |
| 3699 | + service: "secondary", |
| 3700 | + }, |
| 3701 | + ], |
| 3702 | + }), |
| 3703 | + "utf-8" |
| 3704 | + ); |
| 3705 | + fs.writeFileSync( |
| 3706 | + "./primary/index.ts", |
| 3707 | + "export default { async fetch() { return new Response('ok'); } };", |
| 3708 | + "utf-8" |
| 3709 | + ); |
| 3710 | + |
| 3711 | + // Secondary worker |
| 3712 | + fs.writeFileSync( |
| 3713 | + "./secondary/wrangler.jsonc", |
| 3714 | + JSON.stringify({ |
| 3715 | + compatibility_date: "2026-01-01", |
| 3716 | + main: "./worker.ts", |
| 3717 | + name: "secondary", |
| 3718 | + }), |
| 3719 | + "utf-8" |
| 3720 | + ); |
| 3721 | + fs.writeFileSync( |
| 3722 | + "./secondary/worker.ts", |
| 3723 | + "export default { async fetch() { return new Response('ok'); } };", |
| 3724 | + "utf-8" |
| 3725 | + ); |
| 3726 | + |
| 3727 | + const result = await experimental_generateTypes({ |
| 3728 | + config: ["./primary/wrangler.jsonc", "./secondary/wrangler.jsonc"], |
| 3729 | + includeRuntime: false, |
| 3730 | + path: "./primary/worker-configuration.d.ts", |
| 3731 | + }); |
| 3732 | + |
| 3733 | + expect(result.env).toContain( |
| 3734 | + 'SECONDARY: Service<typeof import("../secondary/worker").default>;' |
| 3735 | + ); |
| 3736 | + }); |
| 3737 | + |
| 3738 | + it("validates API options", async ({ expect }) => { |
| 3739 | + await expect(experimental_generateTypes({})).rejects.toThrow( |
| 3740 | + "No config file detected. This command requires a Wrangler configuration file." |
| 3741 | + ); |
| 3742 | + |
| 3743 | + await expect( |
| 3744 | + experimental_generateTypes({ |
| 3745 | + includeEnv: false, |
| 3746 | + includeRuntime: false, |
| 3747 | + }) |
| 3748 | + ).rejects.toThrow( |
| 3749 | + "You cannot run this command without including either Env or Runtime types" |
| 3750 | + ); |
| 3751 | + |
| 3752 | + await expect( |
| 3753 | + experimental_generateTypes({ |
| 3754 | + path: "worker-configuration.txt", |
| 3755 | + }) |
| 3756 | + ).rejects.toThrow( |
| 3757 | + "The provided output path 'worker-configuration.txt' does not point to a declaration file - please use the '.d.ts' extension" |
| 3758 | + ); |
| 3759 | + |
| 3760 | + await expect( |
| 3761 | + experimental_generateTypes({ |
| 3762 | + envInterface: "123Bad", |
| 3763 | + }) |
| 3764 | + ).rejects.toThrow( |
| 3765 | + /The provided env-interface value .* does not satisfy the validation regex/ |
| 3766 | + ); |
| 3767 | + }); |
| 3768 | + |
| 3769 | + it("matches CLI output for `env` only generation", async ({ expect }) => { |
| 3770 | + fs.writeFileSync( |
| 3771 | + "./wrangler.jsonc", |
| 3772 | + JSON.stringify({ |
| 3773 | + compatibility_date: "2026-01-01", |
| 3774 | + vars: { |
| 3775 | + myVar: "hello", |
| 3776 | + }, |
| 3777 | + }), |
| 3778 | + "utf-8" |
| 3779 | + ); |
| 3780 | + |
| 3781 | + // Manual process arguments controlling required to control codegen header output |
| 3782 | + const previousArgv = process.argv; |
| 3783 | + process.argv = ["node", "wrangler", "types", "--include-runtime=false"]; |
| 3784 | + |
| 3785 | + try { |
| 3786 | + await runWrangler("types --include-runtime=false"); |
| 3787 | + |
| 3788 | + const cliOutput = fs.readFileSync("./worker-configuration.d.ts", "utf-8"); |
| 3789 | + |
| 3790 | + process.argv = ["node", "wrangler", "types", "--include-runtime=false"]; |
| 3791 | + const apiOutput = await experimental_generateTypes({ |
| 3792 | + includeRuntime: false, |
| 3793 | + }); |
| 3794 | + |
| 3795 | + expect(apiOutput.content).toBe(cliOutput); |
| 3796 | + } finally { |
| 3797 | + process.argv = previousArgv; |
| 3798 | + } |
| 3799 | + }); |
| 3800 | + |
| 3801 | + it("matches CLI output for `runtime` + `env` generation", async ({ |
| 3802 | + expect, |
| 3803 | + }) => { |
| 3804 | + fs.writeFileSync( |
| 3805 | + "./wrangler.jsonc", |
| 3806 | + JSON.stringify({ |
| 3807 | + compatibility_date: "2026-01-01", |
| 3808 | + vars: { |
| 3809 | + myVar: "hello", |
| 3810 | + }, |
| 3811 | + }), |
| 3812 | + "utf-8" |
| 3813 | + ); |
| 3814 | + |
| 3815 | + // Manual process arguments controlling required to control codegen header output |
| 3816 | + const previousArgv = process.argv; |
| 3817 | + process.argv = ["node", "wrangler", "types", "custom-types.d.ts"]; |
| 3818 | + |
| 3819 | + try { |
| 3820 | + await runWrangler("types custom-types.d.ts"); |
| 3821 | + |
| 3822 | + const cliOutput = fs.readFileSync("./custom-types.d.ts", "utf-8"); |
| 3823 | + |
| 3824 | + process.argv = ["node", "wrangler", "types", "custom-types.d.ts"]; |
| 3825 | + const apiOutput = await experimental_generateTypes({ |
| 3826 | + path: "custom-types.d.ts", |
| 3827 | + }); |
| 3828 | + |
| 3829 | + expect(apiOutput.content).toBe(cliOutput); |
| 3830 | + } finally { |
| 3831 | + process.argv = previousArgv; |
| 3832 | + } |
| 3833 | + }); |
| 3834 | +}); |
| 3835 | + |
3552 | 3836 | describe("pipeline schema type generation", () => { |
3553 | 3837 | const std = mockConsoleMethods(); |
3554 | 3838 | const originalColumns = process.stdout.columns; |
|
0 commit comments