wrangler 0.0.0-e88a54e → 0.0.0-e8e684c7

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