Skip to content

Commit 8faf2c0

Browse files
dario-piotrowiczpenalosapetebacondarwin
authored
widen multi-env vars types in wrangler types and add --strict-vars option to the command (#5086)
* fix: widen multi-env `vars` types in `wrangler types` * add `strict-vars` option to `wrangler types` --------- Co-authored-by: Somhairle MacLeòid <[email protected]> Co-authored-by: Pete Bacon Darwin <[email protected]>
1 parent e8aaa39 commit 8faf2c0

File tree

4 files changed

+360
-115
lines changed

4 files changed

+360
-115
lines changed

.changeset/proud-forks-build.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
fix: widen multi-env `vars` types in `wrangler types`
6+
7+
Currently, the type generated for `vars` is a string literal consisting of the value of the variable in the top level environment. If multiple environments
8+
are specified this wrongly restricts the type, since the variable could contain any of the values from each of the environments.
9+
10+
For example, given a `wrangler.toml` containing the following:
11+
12+
```
13+
[vars]
14+
MY_VAR = "dev value"
15+
16+
[env.production.vars]
17+
MY_VAR = "prod value"
18+
```
19+
20+
running `wrangler types` would generate:
21+
22+
```ts
23+
interface Env {
24+
MY_VAR: "dev value";
25+
}
26+
```
27+
28+
making typescript incorrectly assume that `MY_VAR` is always going to be `"dev value"`
29+
30+
after these changes, the generated interface would instead be:
31+
32+
```ts
33+
interface Env {
34+
MY_VAR: "dev value" | "prod value";
35+
}
36+
```

.changeset/unlucky-timers-sort.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
"wrangler": minor
3+
---
4+
5+
add `--strict-vars` option to `wrangler types`
6+
7+
add a new `--strict-vars` option to `wrangler types` that developers can (by setting the
8+
flag to `false`) use to disable the default strict/literal types generation for their variables
9+
10+
opting out of strict variables can be useful when developers change often their `vars` values,
11+
even more so when multiple environments are involved
12+
13+
## Example
14+
15+
With a toml containing:
16+
17+
```toml
18+
[vars]
19+
MY_VARIABLE = "production_value"
20+
MY_NUMBERS = [1, 2, 3]
21+
22+
[env.staging.vars]
23+
MY_VARIABLE = "staging_value"
24+
MY_NUMBERS = [7, 8, 9]
25+
```
26+
27+
the `wrangler types` command would generate the following interface:
28+
29+
```
30+
interface Env {
31+
MY_VARIABLE: "production_value" | "staging_value";
32+
MY_NUMBERS: [1,2,3] | [7,8,9];
33+
}
34+
```
35+
36+
while `wrangler types --strict-vars=false` would instead generate:
37+
38+
```
39+
interface Env {
40+
MY_VARIABLE: string;
41+
MY_NUMBERS: number[];
42+
}
43+
```
44+
45+
(allowing the developer to easily change their toml variables without the
46+
risk of breaking typescript types)

packages/wrangler/src/__tests__/type-generation.test.ts

Lines changed: 134 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as fs from "fs";
22
import * as TOML from "@iarna/toml";
33
import {
44
constructTSModuleGlob,
5-
constructType,
65
constructTypeKey,
76
generateImportSpecifier,
87
isValidIdentifier,
@@ -65,43 +64,6 @@ describe("constructTSModuleGlob() should return a valid TS glob ", () => {
6564
});
6665
});
6766

68-
describe("constructType", () => {
69-
it("should return a valid type", () => {
70-
expect(constructType("valid", "string")).toBe("valid: string;");
71-
expect(constructType("valid123", "string")).toBe("valid123: string;");
72-
expect(constructType("valid_123", "string")).toBe("valid_123: string;");
73-
expect(constructType("valid_123_", "string")).toBe("valid_123_: string;");
74-
expect(constructType("_valid_123_", "string")).toBe("_valid_123_: string;");
75-
expect(constructType("_valid_123_", "string")).toBe("_valid_123_: string;");
76-
77-
expect(constructType("123invalid", "string")).toBe('"123invalid": string;');
78-
expect(constructType("invalid-123", "string")).toBe(
79-
'"invalid-123": string;'
80-
);
81-
expect(constructType("invalid 123", "string")).toBe(
82-
'"invalid 123": string;'
83-
);
84-
85-
expect(constructType("valid", 'a"', false)).toBe('valid: "a\\"";');
86-
expect(constructType("valid", "a\\", false)).toBe('valid: "a\\\\";');
87-
expect(constructType("valid", "a\\b", false)).toBe('valid: "a\\\\b";');
88-
expect(constructType("valid", 'a\\b"', false)).toBe('valid: "a\\\\b\\"";');
89-
90-
expect(constructType("valid", 1)).toBe("valid: 1;");
91-
expect(constructType("valid", 12345)).toBe("valid: 12345;");
92-
expect(constructType("valid", true)).toBe("valid: true;");
93-
expect(constructType("valid", false)).toBe("valid: false;");
94-
});
95-
});
96-
97-
describe("constructType with multiline strings", () => {
98-
it("should correctly escape newlines in string values", () => {
99-
const multilineString = "This is a\nmulti-line\nstring";
100-
const expected = `valid: "This is a\\nmulti-line\\nstring";`;
101-
expect(constructType("valid", multilineString, false)).toBe(expected);
102-
});
103-
});
104-
10567
describe("generateImportSpecifier", () => {
10668
it("should generate a relative import specifier", () => {
10769
expect(generateImportSpecifier("/app/types.ts", "/app/index.ts")).toBe(
@@ -602,6 +564,35 @@ describe("generateTypes()", () => {
602564
`);
603565
});
604566

567+
it("should allow opting out of strict-vars", async () => {
568+
fs.writeFileSync(
569+
"./wrangler.toml",
570+
TOML.stringify({
571+
vars: {
572+
varStr: "A from wrangler toml",
573+
varArrNum: [1, 2, 3],
574+
varArrMix: [1, "two", 3, true],
575+
varObj: { test: true },
576+
},
577+
} as TOML.JsonMap),
578+
"utf-8"
579+
);
580+
581+
await runWrangler("types --strict-vars=false");
582+
583+
expect(std.out).toMatchInlineSnapshot(`
584+
"Generating project types...
585+
586+
interface Env {
587+
varStr: string;
588+
varArrNum: number[];
589+
varArrMix: (boolean|number|string)[];
590+
varObj: object;
591+
}
592+
"
593+
`);
594+
});
595+
605596
it("should override vars with secrets", async () => {
606597
fs.writeFileSync(
607598
"./wrangler.toml",
@@ -634,6 +625,110 @@ describe("generateTypes()", () => {
634625
`);
635626
});
636627

628+
it("various different types of vars", async () => {
629+
fs.writeFileSync(
630+
"./wrangler.toml",
631+
TOML.stringify({
632+
vars: {
633+
"var-a": '"a\\""',
634+
"var-a-1": '"a\\\\"',
635+
"var-a-b": '"a\\\\b"',
636+
"var-a-b-": '"a\\\\b\\""',
637+
1: 1,
638+
12345: 12345,
639+
true: true,
640+
false: false,
641+
"multi\nline\nvar": "this\nis\na\nmulti\nline\nvariable!",
642+
},
643+
} as TOML.JsonMap),
644+
"utf-8"
645+
);
646+
await runWrangler("types");
647+
648+
expect(std.out).toMatchInlineSnapshot(`
649+
"Generating project types...
650+
651+
interface Env {
652+
\\"1\\": 1;
653+
\\"12345\\": 12345;
654+
\\"var-a\\": \\"/\\"a///\\"/\\"\\";
655+
\\"var-a-1\\": \\"/\\"a/////\\"\\";
656+
\\"var-a-b\\": \\"/\\"a////b/\\"\\";
657+
\\"var-a-b-\\": \\"/\\"a////b///\\"/\\"\\";
658+
true: true;
659+
false: false;
660+
\\"multi
661+
line
662+
var\\": \\"this/nis/na/nmulti/nline/nvariable!\\";
663+
}
664+
"
665+
`);
666+
});
667+
668+
describe("vars present in multiple environments", () => {
669+
beforeEach(() => {
670+
fs.writeFileSync(
671+
"./wrangler.toml",
672+
TOML.stringify({
673+
vars: {
674+
MY_VAR: "a var",
675+
MY_VAR_A: "A (dev)",
676+
MY_VAR_B: { value: "B (dev)" },
677+
MY_VAR_C: ["a", "b", "c"],
678+
},
679+
env: {
680+
production: {
681+
vars: {
682+
MY_VAR: "a var",
683+
MY_VAR_A: "A (prod)",
684+
MY_VAR_B: { value: "B (prod)" },
685+
MY_VAR_C: [1, 2, 3],
686+
},
687+
},
688+
staging: {
689+
vars: {
690+
MY_VAR_A: "A (stag)",
691+
},
692+
},
693+
},
694+
} as TOML.JsonMap),
695+
"utf-8"
696+
);
697+
});
698+
699+
it("should produce string and union types for variables (default)", async () => {
700+
await runWrangler("types");
701+
702+
expect(std.out).toMatchInlineSnapshot(`
703+
"Generating project types...
704+
705+
interface Env {
706+
MY_VAR: \\"a var\\";
707+
MY_VAR_A: \\"A (dev)\\" | \\"A (prod)\\" | \\"A (stag)\\";
708+
MY_VAR_C: [\\"a\\",\\"b\\",\\"c\\"] | [1,2,3];
709+
MY_VAR_B: {\\"value\\":\\"B (dev)\\"} | {\\"value\\":\\"B (prod)\\"};
710+
}
711+
"
712+
`);
713+
});
714+
715+
it("should produce non-strict types for variables (with --strict-vars=false)", async () => {
716+
await runWrangler("types --strict-vars=false");
717+
718+
expect(std.out).toMatchInlineSnapshot(`
719+
"Generating project types...
720+
721+
interface Env {
722+
MY_VAR: string;
723+
MY_VAR_A: string;
724+
MY_VAR_C: string[] | number[];
725+
MY_VAR_B: object;
726+
}
727+
"
728+
`);
729+
});
730+
});
731+
637732
describe("customization", () => {
638733
describe("env", () => {
639734
it("should allow the user to customize the interface name", async () => {
@@ -768,7 +863,7 @@ describe("generateTypes()", () => {
768863
});
769864
});
770865

771-
it("should allow multiple customization to be applied together", async () => {
866+
it("should allow multiple customizations to be applied together", async () => {
772867
fs.writeFileSync(
773868
"./wrangler.toml",
774869
TOML.stringify({

0 commit comments

Comments
 (0)