wrangler 0.0.0-e043479 → 0.0.0-e09abbc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +33 -14
  3. package/bin/wrangler.js +160 -28
  4. package/kv-asset-handler.js +1 -0
  5. package/miniflare-dist/index.mjs +5354 -0
  6. package/package.json +166 -136
  7. package/src/__tests__/config-cache-without-cache-dir.test.ts +38 -0
  8. package/src/__tests__/config-cache.test.ts +42 -0
  9. package/src/__tests__/configuration.test.ts +3942 -2178
  10. package/src/__tests__/dev.test.tsx +1191 -235
  11. package/src/__tests__/guess-worker-format.test.ts +76 -57
  12. package/src/__tests__/helpers/cmd-shim.d.ts +6 -6
  13. package/src/__tests__/helpers/mock-account-id.ts +26 -22
  14. package/src/__tests__/helpers/mock-bin.ts +20 -20
  15. package/src/__tests__/helpers/mock-cfetch.ts +96 -92
  16. package/src/__tests__/helpers/mock-console.ts +59 -32
  17. package/src/__tests__/helpers/mock-dialogs.ts +121 -33
  18. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  19. package/src/__tests__/helpers/mock-istty.ts +74 -0
  20. package/src/__tests__/helpers/mock-kv.ts +28 -35
  21. package/src/__tests__/helpers/mock-oauth-flow.ts +250 -0
  22. package/src/__tests__/helpers/mock-process.ts +39 -0
  23. package/src/__tests__/helpers/mock-stdin.ts +82 -77
  24. package/src/__tests__/helpers/mock-web-socket.ts +21 -21
  25. package/src/__tests__/helpers/run-in-tmp.ts +29 -20
  26. package/src/__tests__/helpers/run-wrangler.ts +8 -8
  27. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  28. package/src/__tests__/helpers/write-wrangler-toml.ts +9 -9
  29. package/src/__tests__/https-options.test.ts +163 -0
  30. package/src/__tests__/index.test.ts +246 -850
  31. package/src/__tests__/init.test.ts +1992 -0
  32. package/src/__tests__/jest.setup.ts +85 -34
  33. package/src/__tests__/kv.test.ts +1402 -1139
  34. package/src/__tests__/logger.test.ts +141 -0
  35. package/src/__tests__/metrics.test.ts +445 -0
  36. package/src/__tests__/package-manager.test.ts +312 -165
  37. package/src/__tests__/pages.test.ts +1261 -0
  38. package/src/__tests__/parse.test.ts +253 -245
  39. package/src/__tests__/publish.test.ts +6611 -2079
  40. package/src/__tests__/pubsub.test.ts +367 -0
  41. package/src/__tests__/r2.test.ts +184 -149
  42. package/src/__tests__/route.test.ts +45 -0
  43. package/src/__tests__/secret.test.ts +474 -407
  44. package/src/__tests__/tail.test.ts +565 -431
  45. package/src/__tests__/test-old-node-version.js +31 -0
  46. package/src/__tests__/user.test.ts +143 -0
  47. package/src/__tests__/whoami.test.tsx +219 -75
  48. package/src/__tests__/worker-namespace.test.ts +327 -0
  49. package/src/abort.d.ts +3 -0
  50. package/src/api/dev.ts +86 -0
  51. package/src/api/index.ts +1 -0
  52. package/src/bundle-reporter.tsx +29 -0
  53. package/src/bundle.ts +196 -93
  54. package/src/cfetch/index.ts +83 -66
  55. package/src/cfetch/internal.ts +100 -84
  56. package/src/cli.ts +22 -14
  57. package/src/config/README.md +7 -7
  58. package/src/config/config.ts +223 -242
  59. package/src/config/diagnostics.ts +61 -61
  60. package/src/config/environment.ts +404 -256
  61. package/src/config/index.ts +218 -32
  62. package/src/config/validation-helpers.ts +436 -222
  63. package/src/config/validation.ts +1642 -907
  64. package/src/config-cache.ts +84 -0
  65. package/src/create-worker-preview.ts +255 -0
  66. package/src/create-worker-upload-form.ts +263 -0
  67. package/src/dev/dev-vars.ts +36 -0
  68. package/src/dev/dev.tsx +452 -0
  69. package/src/dev/local.tsx +373 -0
  70. package/src/dev/remote.tsx +386 -0
  71. package/src/dev/use-esbuild.ts +150 -0
  72. package/src/dev.tsx +576 -943
  73. package/src/dialogs.tsx +115 -57
  74. package/src/durable.ts +102 -0
  75. package/src/entry.ts +308 -37
  76. package/src/environment-variables.ts +25 -23
  77. package/src/errors.ts +11 -0
  78. package/src/faye-websocket.d.ts +6 -0
  79. package/src/generate.ts +33 -0
  80. package/src/git-client.ts +42 -0
  81. package/src/global-wrangler-config-path.ts +26 -0
  82. package/src/https-options.ts +127 -0
  83. package/src/index.tsx +1910 -2241
  84. package/src/init.ts +549 -0
  85. package/src/inspect.ts +603 -550
  86. package/src/intl-polyfill.d.ts +139 -0
  87. package/src/is-interactive.ts +12 -0
  88. package/src/kv.ts +430 -0
  89. package/src/logger.ts +73 -0
  90. package/src/metrics/index.ts +4 -0
  91. package/src/metrics/is-ci.ts +14 -0
  92. package/src/metrics/metrics-config.ts +239 -0
  93. package/src/metrics/metrics-dispatcher.ts +95 -0
  94. package/src/metrics/send-event.ts +92 -0
  95. package/src/miniflare-cli/README.md +30 -0
  96. package/src/miniflare-cli/assets.ts +543 -0
  97. package/src/miniflare-cli/enum-keys.ts +17 -0
  98. package/src/miniflare-cli/index.ts +85 -0
  99. package/src/miniflare-cli/request-context.ts +40 -0
  100. package/src/module-collection.ts +234 -146
  101. package/src/open-in-browser.ts +10 -6
  102. package/src/package-manager.ts +183 -90
  103. package/src/pages/build.tsx +204 -0
  104. package/src/pages/constants.ts +7 -0
  105. package/src/pages/deployments.tsx +106 -0
  106. package/src/pages/dev.tsx +401 -0
  107. package/src/pages/functions/buildPlugin.ts +105 -0
  108. package/src/pages/functions/buildWorker.ts +151 -0
  109. package/src/pages/functions/filepath-routing.test.ts +234 -0
  110. package/src/pages/functions/filepath-routing.ts +189 -0
  111. package/src/pages/functions/identifiers.ts +78 -0
  112. package/src/pages/functions/routes.ts +151 -0
  113. package/src/pages/index.tsx +84 -0
  114. package/src/pages/projects.tsx +163 -0
  115. package/src/pages/publish.tsx +338 -0
  116. package/src/pages/types.ts +40 -0
  117. package/src/pages/upload.tsx +396 -0
  118. package/src/pages/utils.ts +12 -0
  119. package/src/parse.ts +224 -121
  120. package/src/paths.ts +6 -6
  121. package/src/preview.ts +31 -0
  122. package/src/proxy.ts +413 -313
  123. package/src/publish.ts +773 -343
  124. package/src/pubsub/index.ts +286 -0
  125. package/src/pubsub/pubsub-commands.tsx +623 -0
  126. package/src/r2.ts +19 -19
  127. package/src/selfsigned.d.ts +29 -0
  128. package/src/sites.tsx +285 -151
  129. package/src/tail/filters.ts +108 -108
  130. package/src/tail/index.ts +217 -217
  131. package/src/tail/printing.ts +51 -46
  132. package/src/update-check.ts +19 -0
  133. package/src/user/choose-account.tsx +69 -0
  134. package/src/user/env-vars.ts +46 -0
  135. package/src/user/generate-auth-url.ts +33 -0
  136. package/src/user/generate-random-state.ts +16 -0
  137. package/src/user/index.ts +3 -0
  138. package/src/user/user.tsx +1171 -0
  139. package/src/whoami.tsx +63 -41
  140. package/src/worker-namespace.ts +206 -0
  141. package/src/worker.ts +191 -0
  142. package/src/zones.ts +76 -0
  143. package/templates/checked-fetch.js +17 -0
  144. package/templates/gitignore +171 -0
  145. package/templates/new-worker-scheduled.js +17 -0
  146. package/templates/new-worker-scheduled.ts +32 -0
  147. package/templates/new-worker.js +3 -3
  148. package/templates/new-worker.ts +18 -3
  149. package/templates/no-op-worker.js +10 -0
  150. package/templates/pages-shim.ts +9 -0
  151. package/templates/pages-template-plugin.ts +155 -0
  152. package/templates/pages-template-worker.ts +161 -0
  153. package/templates/static-asset-facade.js +38 -36
  154. package/templates/tsconfig.json +95 -95
  155. package/wrangler-dist/cli.d.ts +327 -0
  156. package/wrangler-dist/cli.js +94947 -111199
  157. package/pages/functions/buildWorker.ts +0 -62
  158. package/pages/functions/filepath-routing.test.ts +0 -123
  159. package/pages/functions/filepath-routing.ts +0 -178
  160. package/pages/functions/identifiers.ts +0 -78
  161. package/pages/functions/routes.ts +0 -151
  162. package/pages/functions/template-worker.ts +0 -130
  163. package/src/__tests__/helpers/faye-websocket.d.ts +0 -6
  164. package/src/__tests__/helpers/mock-user.ts +0 -27
  165. package/src/__tests__/logout.test.ts +0 -50
  166. package/src/__tests__/sentry.test.ts +0 -146
  167. package/src/api/form_data.ts +0 -217
  168. package/src/api/preview.ts +0 -147
  169. package/src/api/worker.ts +0 -186
  170. package/src/guess-worker-format.ts +0 -68
  171. package/src/kv.tsx +0 -339
  172. package/src/pages.tsx +0 -1045
  173. package/src/reporting.ts +0 -115
  174. package/src/user.tsx +0 -1063
  175. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  176. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  177. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  178. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  179. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  180. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  181. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  182. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  183. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  184. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  185. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  186. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  187. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  188. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  189. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  190. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  191. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  192. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  193. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  194. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  195. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  196. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  197. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  198. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  199. package/vendor/wrangler-mime/LICENSE +0 -21
  200. package/vendor/wrangler-mime/Mime.js +0 -97
  201. package/vendor/wrangler-mime/README.md +0 -187
  202. package/vendor/wrangler-mime/cli.js +0 -46
  203. package/vendor/wrangler-mime/index.js +0 -4
  204. package/vendor/wrangler-mime/lite.js +0 -4
  205. package/vendor/wrangler-mime/package.json +0 -52
  206. package/vendor/wrangler-mime/types/other.js +0 -1
  207. package/vendor/wrangler-mime/types/standard.js +0 -1
  208. package/wrangler-dist/cli.js.map +0 -7
@@ -1,27 +1,32 @@
1
- import type { Config, RawConfig } from "./config";
1
+ import type { RawConfig } from "./config";
2
2
  import type { Diagnostics } from "./diagnostics";
3
3
  import type { Environment, RawEnvironment } from "./environment";
4
4
 
5
5
  /**
6
6
  * Mark a field as deprecated.
7
7
  *
8
- * This function will add a diagnostics warning if the deprecated field is found in the `rawEnv`.
8
+ * This function will add a diagnostics warning if the deprecated field is found in the `rawEnv` (or an error if it's also a breaking deprecation)
9
9
  * The `fieldPath` is a dot separated property path, e.g. `"build.upload.format"`.
10
10
  */
11
11
  export function deprecated<T extends object>(
12
- diagnostics: Diagnostics,
13
- config: T,
14
- fieldPath: DeepKeyOf<T>,
15
- message: string,
16
- remove: boolean
12
+ diagnostics: Diagnostics,
13
+ config: T,
14
+ fieldPath: DeepKeyOf<T>,
15
+ message: string,
16
+ remove: boolean,
17
+ title = "Deprecation",
18
+ type: "warning" | "error" = "warning"
17
19
  ): void {
18
- const result = unwindPropertyPath(config, fieldPath);
19
- if (result !== undefined && result.field in result.container) {
20
- diagnostics.warnings.push(`DEPRECATION: "${fieldPath}":\n${message}`);
21
- if (remove) {
22
- delete (result.container as Record<string, unknown>)[result.field];
23
- }
24
- }
20
+ const BOLD = "\x1b[1m";
21
+ const NORMAL = "\x1b[0m";
22
+ const diagnosticMessage = `${BOLD}${title}${NORMAL}: "${fieldPath}":\n${message}`;
23
+ const result = unwindPropertyPath(config, fieldPath);
24
+ if (result !== undefined && result.field in result.container) {
25
+ diagnostics[`${type}s`].push(diagnosticMessage);
26
+ if (remove) {
27
+ delete (result.container as Record<string, unknown>)[result.field];
28
+ }
29
+ }
25
30
  }
26
31
 
27
32
  /**
@@ -31,16 +36,16 @@ export function deprecated<T extends object>(
31
36
  * The `fieldPath` is a dot separated property path, e.g. `"build.upload.format"`.
32
37
  */
33
38
  export function experimental<T extends object>(
34
- diagnostics: Diagnostics,
35
- config: T,
36
- fieldPath: DeepKeyOf<T>
39
+ diagnostics: Diagnostics,
40
+ config: T,
41
+ fieldPath: DeepKeyOf<T>
37
42
  ): void {
38
- const result = unwindPropertyPath(config, fieldPath);
39
- if (result !== undefined && result.field in result.container) {
40
- diagnostics.warnings.push(
41
- `"${fieldPath}" fields are experimental and may change or break at any time.`
42
- );
43
- }
43
+ const result = unwindPropertyPath(config, fieldPath);
44
+ if (result !== undefined && result.field in result.container) {
45
+ diagnostics.warnings.push(
46
+ `"${fieldPath}" fields are experimental and may change or break at any time.`
47
+ );
48
+ }
44
49
  }
45
50
 
46
51
  /**
@@ -50,15 +55,83 @@ export function experimental<T extends object>(
50
55
  * and then the `defaultValue`.
51
56
  */
52
57
  export function inheritable<K extends keyof Environment>(
53
- diagnostics: Diagnostics,
54
- config: Config | undefined,
55
- rawEnv: RawEnvironment,
56
- field: K,
57
- validate: ValidatorFn,
58
- defaultValue: Environment[K]
58
+ diagnostics: Diagnostics,
59
+ topLevelEnv: Environment | undefined,
60
+ rawEnv: RawEnvironment,
61
+ field: K,
62
+ validate: ValidatorFn,
63
+ defaultValue: Environment[K],
64
+ transformFn: TransformFn<Environment[K]> = (v) => v
59
65
  ): Environment[K] {
60
- validate(diagnostics, field, rawEnv[field], config);
61
- return (rawEnv[field] as Environment[K]) ?? config?.[field] ?? defaultValue;
66
+ validate(diagnostics, field, rawEnv[field], topLevelEnv);
67
+ return (
68
+ (rawEnv[field] as Environment[K]) ??
69
+ transformFn(topLevelEnv?.[field]) ??
70
+ defaultValue
71
+ );
72
+ }
73
+
74
+ /**
75
+ * Get an inheritable environment field, but only if we are in legacy environments
76
+ */
77
+ export function inheritableInLegacyEnvironments<K extends keyof Environment>(
78
+ diagnostics: Diagnostics,
79
+ isLegacyEnv: boolean | undefined,
80
+ topLevelEnv: Environment | undefined,
81
+ rawEnv: RawEnvironment,
82
+ field: K,
83
+ validate: ValidatorFn,
84
+ transformFn: TransformFn<Environment[K]> = (v) => v,
85
+ defaultValue: Environment[K]
86
+ ): Environment[K] {
87
+ return topLevelEnv === undefined || isLegacyEnv === true
88
+ ? inheritable(
89
+ diagnostics,
90
+ topLevelEnv,
91
+ rawEnv,
92
+ field,
93
+ validate,
94
+ defaultValue,
95
+ transformFn
96
+ )
97
+ : notAllowedInNamedServiceEnvironment(
98
+ diagnostics,
99
+ topLevelEnv,
100
+ rawEnv,
101
+ field
102
+ );
103
+ }
104
+
105
+ /**
106
+ * Type of function that is used to transform an inheritable environment field.
107
+ */
108
+ type TransformFn<T> = (fieldValue: T | undefined) => T | undefined;
109
+
110
+ /**
111
+ * Transform an environment field by appending current environment name to it.
112
+ */
113
+ export const appendEnvName =
114
+ (envName: string): TransformFn<string | undefined> =>
115
+ (fieldValue) =>
116
+ fieldValue ? `${fieldValue}-${envName}` : undefined;
117
+
118
+ /**
119
+ * Log an error if this named environment is trying to override the value in the top-level
120
+ * environment, which is not allow for this field.
121
+ */
122
+ function notAllowedInNamedServiceEnvironment<K extends keyof Environment>(
123
+ diagnostics: Diagnostics,
124
+ topLevelEnv: Environment,
125
+ rawEnv: RawEnvironment,
126
+ field: K
127
+ ): Environment[K] {
128
+ if (field in rawEnv) {
129
+ diagnostics.errors.push(
130
+ `The "${field}" field is not allowed in named service environments.\n` +
131
+ `Please remove the field from this environment.`
132
+ );
133
+ }
134
+ return topLevelEnv[field];
62
135
  }
63
136
 
64
137
  /**
@@ -68,39 +141,39 @@ export function inheritable<K extends keyof Environment>(
68
141
  * then log a warning and return the `defaultValue`.
69
142
  */
70
143
  export function notInheritable<K extends keyof Environment>(
71
- diagnostics: Diagnostics,
72
- config: Config | undefined,
73
- rawConfig: RawConfig | undefined,
74
- rawEnv: RawEnvironment,
75
- envName: string,
76
- field: K,
77
- validate: ValidatorFn,
78
- defaultValue: Environment[K]
144
+ diagnostics: Diagnostics,
145
+ topLevelEnv: Environment | undefined,
146
+ rawConfig: RawConfig | undefined,
147
+ rawEnv: RawEnvironment,
148
+ envName: string,
149
+ field: K,
150
+ validate: ValidatorFn,
151
+ defaultValue: Environment[K]
79
152
  ): Environment[K] {
80
- if (rawEnv[field] !== undefined) {
81
- validate(diagnostics, field, rawEnv[field], config);
82
- } else {
83
- if (rawConfig?.[field] !== undefined) {
84
- diagnostics.warnings.push(
85
- `"${field}" exists at the top level, but not on "env.${envName}".\n` +
86
- `This is not what you probably want, since "${field}" is not inherited by environments.\n` +
87
- `Please add "${field}" to "env.${envName}".`
88
- );
89
- }
90
- }
91
- return (rawEnv[field] as Environment[K]) ?? defaultValue;
153
+ if (rawEnv[field] !== undefined) {
154
+ validate(diagnostics, field, rawEnv[field], topLevelEnv);
155
+ } else {
156
+ if (rawConfig?.[field] !== undefined) {
157
+ diagnostics.warnings.push(
158
+ `"${field}" exists at the top level, but not on "env.${envName}".\n` +
159
+ `This is not what you probably want, since "${field}" is not inherited by environments.\n` +
160
+ `Please add "${field}" to "env.${envName}".`
161
+ );
162
+ }
163
+ }
164
+ return (rawEnv[field] as Environment[K]) ?? defaultValue;
92
165
  }
93
166
 
94
167
  // Idea taken from https://stackoverflow.com/a/66661477
95
168
  type DeepKeyOf<T> = (
96
- T extends object
97
- ? {
98
- [K in Exclude<keyof T, symbol>]: `${K}${DotPrefix<DeepKeyOf<T[K]>>}`;
99
- }[Exclude<keyof T, symbol>]
100
- : ""
169
+ T extends object
170
+ ? {
171
+ [K in Exclude<keyof T, symbol>]: `${K}${DotPrefix<DeepKeyOf<T[K]>>}`;
172
+ }[Exclude<keyof T, symbol>]
173
+ : ""
101
174
  ) extends infer D
102
- ? Extract<D, string>
103
- : never;
175
+ ? Extract<D, string>
176
+ : never;
104
177
 
105
178
  type DotPrefix<T extends string> = T extends "" ? "" : `.${T}`;
106
179
 
@@ -115,256 +188,397 @@ type DotPrefix<T extends string> = T extends "" ? "" : `.${T}`;
115
188
  * ```
116
189
  */
117
190
  function unwindPropertyPath<T extends object>(
118
- root: T,
119
- path: DeepKeyOf<T>
191
+ root: T,
192
+ path: DeepKeyOf<T>
120
193
  ): { container: object; field: string } | undefined {
121
- let container: object = root;
122
- const parts = (path as string).split(".");
123
- for (let i = 0; i < parts.length - 1; i++) {
124
- if (!hasProperty(container, parts[i])) {
125
- return;
126
- }
127
- container = container[parts[i]];
128
- }
129
- return { container, field: parts[parts.length - 1] };
194
+ let container: object = root;
195
+ const parts = (path as string).split(".");
196
+ for (let i = 0; i < parts.length - 1; i++) {
197
+ if (!hasProperty(container, parts[i])) {
198
+ return;
199
+ }
200
+ container = container[parts[i]];
201
+ }
202
+ return { container, field: parts[parts.length - 1] };
130
203
  }
131
204
 
132
205
  /**
133
206
  * The type of a function that can be used to validate a configuration field.
134
207
  */
135
208
  export type ValidatorFn = (
136
- diagnostics: Diagnostics,
137
- field: string,
138
- value: unknown,
139
- config: Config | undefined
209
+ diagnostics: Diagnostics,
210
+ field: string,
211
+ value: unknown,
212
+ topLevelEnv: Environment | undefined
140
213
  ) => boolean;
141
214
 
142
215
  /**
143
216
  * Validate that the field is a string.
144
217
  */
145
218
  export const isString: ValidatorFn = (diagnostics, field, value) => {
146
- if (value !== undefined && typeof value !== "string") {
147
- diagnostics.errors.push(
148
- `Expected "${field}" field to be a string but got ${JSON.stringify(
149
- value
150
- )}.`
151
- );
152
- return false;
153
- }
154
- return true;
219
+ if (value !== undefined && typeof value !== "string") {
220
+ diagnostics.errors.push(
221
+ `Expected "${field}" to be of type string but got ${JSON.stringify(
222
+ value
223
+ )}.`
224
+ );
225
+ return false;
226
+ }
227
+ return true;
228
+ };
229
+
230
+ /**
231
+ * Validate that the `name` field is compliant with EWC constraints.
232
+ */
233
+ export const isValidName: ValidatorFn = (diagnostics, field, value) => {
234
+ if (
235
+ (typeof value === "string" && /^$|^[a-z0-9_ ][a-z0-9-_ ]*$/.test(value)) ||
236
+ value === undefined
237
+ ) {
238
+ return true;
239
+ } else {
240
+ diagnostics.errors.push(
241
+ `Expected "${field}" to be of type string, alphanumeric and lowercase with dashes only but got ${JSON.stringify(
242
+ value
243
+ )}.`
244
+ );
245
+ return false;
246
+ }
155
247
  };
156
248
 
157
249
  /**
158
250
  * Validate that the field is an array of strings.
159
251
  */
160
252
  export const isStringArray: ValidatorFn = (diagnostics, field, value) => {
161
- if (
162
- value !== undefined &&
163
- (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
164
- ) {
165
- diagnostics.errors.push(
166
- `Expected "${field}" field to be an array of strings but got ${JSON.stringify(
167
- value
168
- )}.`
169
- );
170
- return false;
171
- }
172
- return true;
253
+ if (
254
+ value !== undefined &&
255
+ (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
256
+ ) {
257
+ diagnostics.errors.push(
258
+ `Expected "${field}" to be of type string array but got ${JSON.stringify(
259
+ value
260
+ )}.`
261
+ );
262
+ return false;
263
+ }
264
+ return true;
173
265
  };
174
266
 
175
267
  /**
176
268
  * Validate that the field is an object containing the given properties.
177
269
  */
178
270
  export const isObjectWith =
179
- (...properties: string[]): ValidatorFn =>
180
- (diagnostics, field, value) => {
181
- if (
182
- value !== undefined &&
183
- (typeof value !== "object" ||
184
- value === null ||
185
- !properties.every((prop) => prop in value))
186
- ) {
187
- diagnostics.errors.push(
188
- `Expected "${field}" field to be an object containing properties ${properties} but got ${JSON.stringify(
189
- value
190
- )}.`
191
- );
192
- return false;
193
- }
194
- return true;
195
- };
271
+ (...properties: string[]): ValidatorFn =>
272
+ (diagnostics, field, value) => {
273
+ if (
274
+ value !== undefined &&
275
+ (typeof value !== "object" ||
276
+ value === null ||
277
+ !properties.every((prop) => prop in value))
278
+ ) {
279
+ diagnostics.errors.push(
280
+ `Expected "${field}" to be of type object, containing only properties ${properties}, but got ${JSON.stringify(
281
+ value
282
+ )}.`
283
+ );
284
+ return false;
285
+ }
286
+ // it's an object with the field as desired,
287
+ // but let's also check for unexpected fields
288
+ if (value !== undefined) {
289
+ const restFields = Object.keys(value).filter(
290
+ (key) => !properties.includes(key)
291
+ );
292
+ validateAdditionalProperties(diagnostics, field, restFields, []);
293
+ }
294
+
295
+ return true;
296
+ };
196
297
 
197
298
  /**
198
299
  * Validate that the field value is one of the given choices.
199
300
  */
200
301
  export const isOneOf =
201
- (...choices: unknown[]): ValidatorFn =>
202
- (diagnostics, field, value) => {
203
- if (value !== undefined && !choices.some((choice) => value === choice)) {
204
- diagnostics.errors.push(
205
- `Expected "${field}" field to be on of ${choices} but got ${JSON.stringify(
206
- value
207
- )}.`
208
- );
209
- return false;
210
- }
211
- return true;
212
- };
302
+ (...choices: unknown[]): ValidatorFn =>
303
+ (diagnostics, field, value) => {
304
+ if (value !== undefined && !choices.some((choice) => value === choice)) {
305
+ diagnostics.errors.push(
306
+ `Expected "${field}" field to be one of ${JSON.stringify(
307
+ choices
308
+ )} but got ${JSON.stringify(value)}.`
309
+ );
310
+ return false;
311
+ }
312
+ return true;
313
+ };
314
+
315
+ /**
316
+ * Aggregate multiple validator functions
317
+ */
318
+ export const all = (...validations: ValidatorFn[]): ValidatorFn => {
319
+ return (diagnostics, field, value, config) => {
320
+ let passedValidations = true;
321
+
322
+ for (const validate of validations) {
323
+ if (!validate(diagnostics, field, value, config)) {
324
+ passedValidations = false;
325
+ }
326
+ }
327
+
328
+ return passedValidations;
329
+ };
330
+ };
331
+
332
+ /**
333
+ * Check that the field is mutually exclusive with a list of other fields.
334
+ *
335
+ * @param container the container of the fields to check against.
336
+ * @param fields the names of the fields to check against.
337
+ */
338
+ export const isMutuallyExclusiveWith = <T extends RawEnvironment | RawConfig>(
339
+ container: T,
340
+ ...fields: (keyof T)[]
341
+ ): ValidatorFn => {
342
+ return (diagnostics, field, value) => {
343
+ if (value === undefined) {
344
+ return true;
345
+ }
346
+
347
+ for (const exclusiveWith of fields) {
348
+ if (container[exclusiveWith] !== undefined) {
349
+ diagnostics.errors.push(
350
+ `Expected exactly one of the following fields ${JSON.stringify([
351
+ field,
352
+ ...fields,
353
+ ])}.`
354
+ );
355
+ return false;
356
+ }
357
+ }
358
+
359
+ return true;
360
+ };
361
+ };
213
362
 
214
363
  /**
215
364
  * Validate that the field is a boolean.
216
365
  */
217
366
  export const isBoolean: ValidatorFn = (diagnostics, field, value) => {
218
- if (value !== undefined && typeof value !== "boolean") {
219
- diagnostics.errors.push(
220
- `Expected "${field}" field to be a boolean but got ${JSON.stringify(
221
- value
222
- )}.`
223
- );
224
- return false;
225
- }
226
- return true;
367
+ if (value !== undefined && typeof value !== "boolean") {
368
+ diagnostics.errors.push(
369
+ `Expected "${field}" to be of type boolean but got ${JSON.stringify(
370
+ value
371
+ )}.`
372
+ );
373
+ return false;
374
+ }
375
+ return true;
227
376
  };
228
377
 
229
378
  /**
230
379
  * Validate that the required field exists and has the expected type.
231
380
  */
232
381
  export const validateRequiredProperty = (
233
- diagnostics: Diagnostics,
234
- container: string,
235
- key: string,
236
- value: unknown,
237
- type: string
382
+ diagnostics: Diagnostics,
383
+ container: string,
384
+ key: string,
385
+ value: unknown,
386
+ type: string,
387
+ choices?: unknown[]
238
388
  ): boolean => {
239
- if (container) {
240
- container += ".";
241
- }
242
- if (value === undefined) {
243
- diagnostics.errors.push(`ERROR: "${container}${key}" is a required field.`);
244
- return false;
245
- } else if (typeof value !== type) {
246
- diagnostics.errors.push(
247
- `ERROR: "${container}${key}" should be of type ${type} but got ${JSON.stringify(
248
- value
249
- )}.`
250
- );
251
- return false;
252
- }
253
- return true;
389
+ if (container) {
390
+ container += ".";
391
+ }
392
+ if (value === undefined) {
393
+ diagnostics.errors.push(`"${container}${key}" is a required field.`);
394
+ return false;
395
+ } else if (typeof value !== type) {
396
+ diagnostics.errors.push(
397
+ `Expected "${container}${key}" to be of type ${type} but got ${JSON.stringify(
398
+ value
399
+ )}.`
400
+ );
401
+ return false;
402
+ } else if (choices) {
403
+ if (
404
+ !isOneOf(...choices)(diagnostics, `${container}${key}`, value, undefined)
405
+ ) {
406
+ return false;
407
+ }
408
+ }
409
+ return true;
254
410
  };
255
411
 
256
412
  /**
257
413
  * Validate that, if the optional field exists, then it has the expected type.
258
414
  */
259
415
  export const validateOptionalProperty = (
260
- diagnostics: Diagnostics,
261
- container: string,
262
- key: string,
263
- value: unknown,
264
- type: string
416
+ diagnostics: Diagnostics,
417
+ container: string,
418
+ key: string,
419
+ value: unknown,
420
+ type: string,
421
+ choices?: unknown[]
265
422
  ): boolean => {
266
- if (value !== undefined) {
267
- return validateRequiredProperty(diagnostics, container, key, value, type);
268
- }
269
- return true;
423
+ if (value !== undefined) {
424
+ return validateRequiredProperty(
425
+ diagnostics,
426
+ container,
427
+ key,
428
+ value,
429
+ type,
430
+ choices
431
+ );
432
+ }
433
+ return true;
270
434
  };
271
435
 
272
436
  /**
273
437
  * Validate that the field is an array of elements of the given type.
274
438
  */
275
439
  export const validateTypedArray = (
276
- diagnostics: Diagnostics,
277
- container: string,
278
- value: unknown,
279
- type: string
440
+ diagnostics: Diagnostics,
441
+ container: string,
442
+ value: unknown,
443
+ type: string
280
444
  ): boolean => {
281
- let isValid = true;
282
- if (!Array.isArray(value)) {
283
- diagnostics.errors.push(
284
- `"${container}" should be an array of ${type}s but got ${JSON.stringify(
285
- value
286
- )}`
287
- );
288
- isValid = false;
289
- } else {
290
- for (let i = 0; i < value.length; i++) {
291
- isValid =
292
- validateRequiredProperty(
293
- diagnostics,
294
- container,
295
- `[${i}]`,
296
- value[i],
297
- type
298
- ) && isValid;
299
- }
300
- }
301
- return isValid;
445
+ let isValid = true;
446
+ if (!Array.isArray(value)) {
447
+ diagnostics.errors.push(
448
+ `Expected "${container}" to be an array of ${type}s but got ${JSON.stringify(
449
+ value
450
+ )}`
451
+ );
452
+ isValid = false;
453
+ } else {
454
+ for (let i = 0; i < value.length; i++) {
455
+ isValid =
456
+ validateRequiredProperty(
457
+ diagnostics,
458
+ container,
459
+ `[${i}]`,
460
+ value[i],
461
+ type
462
+ ) && isValid;
463
+ }
464
+ }
465
+ return isValid;
302
466
  };
303
467
 
304
468
  /**
305
469
  * Validate that, if the optional field exists, it is an array of elements of the given type.
306
470
  */
307
471
  export const validateOptionalTypedArray = (
308
- diagnostics: Diagnostics,
309
- container: string,
310
- value: unknown,
311
- type: string
472
+ diagnostics: Diagnostics,
473
+ container: string,
474
+ value: unknown,
475
+ type: string
312
476
  ) => {
313
- if (value !== undefined) {
314
- return validateTypedArray(diagnostics, container, value, type);
315
- }
316
- return true;
477
+ if (value !== undefined) {
478
+ return validateTypedArray(diagnostics, container, value, type);
479
+ }
480
+ return true;
317
481
  };
318
482
 
319
483
  /**
320
484
  * Test to see if `obj` has the required property `prop` of type `type`.
321
485
  */
322
486
  export const isRequiredProperty = <T extends object>(
323
- obj: object,
324
- prop: keyof T,
325
- type: string,
326
- choices?: unknown[]
487
+ obj: object,
488
+ prop: keyof T,
489
+ type: string,
490
+ choices?: unknown[]
327
491
  ): obj is T =>
328
- hasProperty<T>(obj, prop) &&
329
- typeof obj[prop] === type &&
330
- (choices === undefined || choices.includes(obj[prop]));
492
+ hasProperty<T>(obj, prop) &&
493
+ typeof obj[prop] === type &&
494
+ (choices === undefined || choices.includes(obj[prop]));
331
495
 
332
496
  /**
333
497
  * Test to see if `obj` has the optional property `prop` of type `type`.
334
498
  */
335
499
  export const isOptionalProperty = <T extends object>(
336
- obj: object,
337
- prop: keyof T,
338
- type: string
500
+ obj: object,
501
+ prop: keyof T,
502
+ type: string
339
503
  ): obj is T => !hasProperty<T>(obj, prop) || typeof obj[prop] === type;
340
504
 
341
505
  /**
342
506
  * Test to see if `obj` has the property `prop`.
343
507
  */
344
508
  export const hasProperty = <T extends object>(
345
- obj: object,
346
- property: keyof T
509
+ obj: object,
510
+ property: keyof T
347
511
  ): obj is T => property in obj;
348
512
 
349
513
  /**
350
514
  * Add warning messages about any properties in the given field that are not expected to be there.
351
515
  */
352
516
  export const validateAdditionalProperties = (
353
- diagnostics: Diagnostics,
354
- fieldPath: string,
355
- restProps: Iterable<string>,
356
- knownProps: Iterable<string>
517
+ diagnostics: Diagnostics,
518
+ fieldPath: string,
519
+ restProps: Iterable<string>,
520
+ knownProps: Iterable<string>
357
521
  ): boolean => {
358
- const restPropSet = new Set(restProps);
359
- for (const knownProp of knownProps) {
360
- restPropSet.delete(knownProp);
361
- }
362
- if (restPropSet.size > 0) {
363
- const fields = Array.from(restPropSet.keys()).map((field) => `"${field}"`);
364
- diagnostics.warnings.push(
365
- `Unexpected fields found in ${fieldPath} field: ${fields}`
366
- );
367
- return false;
368
- }
369
- return true;
522
+ const restPropSet = new Set(restProps);
523
+ for (const knownProp of knownProps) {
524
+ restPropSet.delete(knownProp);
525
+ }
526
+ if (restPropSet.size > 0) {
527
+ const fields = Array.from(restPropSet.keys()).map((field) => `"${field}"`);
528
+ diagnostics.warnings.push(
529
+ `Unexpected fields found in ${fieldPath} field: ${fields}`
530
+ );
531
+ return false;
532
+ }
533
+ return true;
534
+ };
535
+
536
+ /**
537
+ * Get the names of the bindings collection in `value`.
538
+ *
539
+ * Will return an empty array if it doesn't understand the value
540
+ * passed in, so another form of validation should be
541
+ * performed externally.
542
+ */
543
+ export const getBindingNames = (value: unknown): string[] => {
544
+ if (typeof value !== "object" || value === null) {
545
+ return [];
546
+ }
547
+
548
+ if (isBindingList(value)) {
549
+ return value.bindings.map(({ name }) => name);
550
+ } else if (isNamespaceList(value)) {
551
+ return value.map(({ binding }) => binding);
552
+ } else if (isRecord(value)) {
553
+ return Object.keys(value);
554
+ } else {
555
+ return [];
556
+ }
370
557
  };
558
+
559
+ const isBindingList = (
560
+ value: unknown
561
+ ): value is {
562
+ bindings: {
563
+ name: string;
564
+ }[];
565
+ } =>
566
+ isRecord(value) &&
567
+ "bindings" in value &&
568
+ Array.isArray(value.bindings) &&
569
+ value.bindings.every(
570
+ (binding) =>
571
+ isRecord(binding) && "name" in binding && typeof binding.name === "string"
572
+ );
573
+
574
+ const isNamespaceList = (value: unknown): value is { binding: string }[] =>
575
+ Array.isArray(value) &&
576
+ value.every(
577
+ (entry) =>
578
+ isRecord(entry) && "binding" in entry && typeof entry.binding === "string"
579
+ );
580
+
581
+ const isRecord = (
582
+ value: unknown
583
+ ): value is Record<string | number | symbol, unknown> =>
584
+ typeof value === "object" && value !== null && !Array.isArray(value);