wrangler 2.0.12 → 2.0.16
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.
- package/README.md +7 -1
- package/bin/wrangler.js +111 -57
- package/miniflare-dist/index.mjs +9 -2
- package/package.json +156 -154
- package/src/__tests__/config-cache-without-cache-dir.test.ts +38 -0
- package/src/__tests__/config-cache.test.ts +30 -24
- package/src/__tests__/configuration.test.ts +3935 -3476
- package/src/__tests__/dev.test.tsx +1128 -979
- package/src/__tests__/guess-worker-format.test.ts +68 -68
- package/src/__tests__/helpers/cmd-shim.d.ts +6 -6
- package/src/__tests__/helpers/faye-websocket.d.ts +4 -4
- package/src/__tests__/helpers/mock-account-id.ts +24 -24
- package/src/__tests__/helpers/mock-bin.ts +20 -20
- package/src/__tests__/helpers/mock-cfetch.ts +92 -92
- package/src/__tests__/helpers/mock-console.ts +49 -39
- package/src/__tests__/helpers/mock-dialogs.ts +94 -71
- package/src/__tests__/helpers/mock-http-server.ts +30 -30
- package/src/__tests__/helpers/mock-istty.ts +65 -18
- package/src/__tests__/helpers/mock-kv.ts +26 -26
- package/src/__tests__/helpers/mock-oauth-flow.ts +223 -228
- package/src/__tests__/helpers/mock-process.ts +39 -0
- package/src/__tests__/helpers/mock-stdin.ts +82 -77
- package/src/__tests__/helpers/mock-web-socket.ts +21 -21
- package/src/__tests__/helpers/run-in-tmp.ts +27 -27
- package/src/__tests__/helpers/run-wrangler.ts +8 -8
- package/src/__tests__/helpers/write-worker-source.ts +16 -16
- package/src/__tests__/helpers/write-wrangler-toml.ts +9 -9
- package/src/__tests__/https-options.test.ts +104 -104
- package/src/__tests__/index.test.ts +239 -234
- package/src/__tests__/init.test.ts +1605 -1250
- package/src/__tests__/jest.setup.ts +63 -33
- package/src/__tests__/kv.test.ts +1128 -1011
- package/src/__tests__/logger.test.ts +100 -74
- package/src/__tests__/package-manager.test.ts +303 -303
- package/src/__tests__/pages.test.ts +1152 -652
- package/src/__tests__/parse.test.ts +252 -252
- package/src/__tests__/publish.test.ts +6371 -5622
- package/src/__tests__/pubsub.test.ts +367 -0
- package/src/__tests__/r2.test.ts +133 -133
- package/src/__tests__/route.test.ts +18 -18
- package/src/__tests__/secret.test.ts +382 -377
- package/src/__tests__/tail.test.ts +530 -530
- package/src/__tests__/user.test.ts +123 -111
- package/src/__tests__/whoami.test.tsx +198 -117
- package/src/__tests__/worker-namespace.test.ts +327 -0
- package/src/abort.d.ts +1 -1
- package/src/api/dev.ts +49 -0
- package/src/api/index.ts +1 -0
- package/src/bundle-reporter.tsx +29 -0
- package/src/bundle.ts +157 -149
- package/src/cfetch/index.ts +80 -80
- package/src/cfetch/internal.ts +90 -83
- package/src/cli.ts +21 -7
- package/src/config/config.ts +204 -195
- package/src/config/diagnostics.ts +61 -61
- package/src/config/environment.ts +390 -357
- package/src/config/index.ts +206 -193
- package/src/config/validation-helpers.ts +366 -366
- package/src/config/validation.ts +1573 -1376
- package/src/config-cache.ts +79 -41
- package/src/create-worker-preview.ts +206 -136
- package/src/create-worker-upload-form.ts +247 -238
- package/src/dev/dev-vars.ts +13 -13
- package/src/dev/dev.tsx +329 -307
- package/src/dev/local.tsx +304 -275
- package/src/dev/remote.tsx +366 -224
- package/src/dev/use-esbuild.ts +126 -91
- package/src/dev.tsx +538 -0
- package/src/dialogs.tsx +97 -97
- package/src/durable.ts +87 -87
- package/src/entry.ts +234 -228
- package/src/environment-variables.ts +23 -23
- package/src/errors.ts +6 -6
- package/src/generate.ts +33 -0
- package/src/git-client.ts +42 -0
- package/src/https-options.ts +79 -79
- package/src/index.tsx +1775 -2763
- package/src/init.ts +549 -0
- package/src/inspect.ts +593 -593
- package/src/intl-polyfill.d.ts +123 -123
- package/src/is-interactive.ts +12 -0
- package/src/kv.ts +277 -277
- package/src/logger.ts +46 -39
- package/src/miniflare-cli/enum-keys.ts +8 -8
- package/src/miniflare-cli/index.ts +42 -31
- package/src/miniflare-cli/request-context.ts +18 -18
- package/src/module-collection.ts +212 -212
- package/src/open-in-browser.ts +4 -6
- package/src/package-manager.ts +123 -123
- package/src/pages/build.tsx +202 -0
- package/src/pages/constants.ts +7 -0
- package/src/pages/deployments.tsx +101 -0
- package/src/pages/dev.tsx +964 -0
- package/src/pages/functions/buildPlugin.ts +105 -0
- package/src/pages/functions/buildWorker.ts +151 -0
- package/{pages → src/pages}/functions/filepath-routing.test.ts +113 -113
- package/src/pages/functions/filepath-routing.ts +189 -0
- package/src/pages/functions/identifiers.ts +78 -0
- package/src/pages/functions/routes.ts +151 -0
- package/src/pages/index.tsx +84 -0
- package/src/pages/projects.tsx +157 -0
- package/src/pages/publish.tsx +335 -0
- package/src/pages/types.ts +40 -0
- package/src/pages/upload.tsx +384 -0
- package/src/pages/utils.ts +12 -0
- package/src/parse.ts +202 -138
- package/src/paths.ts +6 -6
- package/src/preview.ts +31 -0
- package/src/proxy.ts +400 -402
- package/src/publish.ts +667 -621
- package/src/pubsub/index.ts +286 -0
- package/src/pubsub/pubsub-commands.tsx +577 -0
- package/src/r2.ts +19 -19
- package/src/selfsigned.d.ts +23 -23
- package/src/sites.tsx +271 -225
- package/src/tail/filters.ts +108 -108
- package/src/tail/index.ts +217 -217
- package/src/tail/printing.ts +45 -45
- package/src/update-check.ts +11 -11
- package/src/user/choose-account.tsx +60 -0
- package/src/user/env-vars.ts +46 -0
- package/src/user/generate-auth-url.ts +33 -0
- package/src/user/generate-random-state.ts +16 -0
- package/src/user/index.ts +3 -0
- package/src/user/user.tsx +1161 -0
- package/src/whoami.tsx +61 -42
- package/src/worker-namespace.ts +190 -0
- package/src/worker.ts +110 -100
- package/src/zones.ts +39 -36
- package/templates/checked-fetch.js +17 -0
- package/templates/new-worker-scheduled.js +3 -3
- package/templates/new-worker-scheduled.ts +15 -15
- package/templates/new-worker.js +3 -3
- package/templates/new-worker.ts +15 -15
- package/templates/no-op-worker.js +10 -0
- package/templates/pages-template-plugin.ts +155 -0
- package/templates/pages-template-worker.ts +161 -0
- package/templates/static-asset-facade.js +31 -31
- package/templates/tsconfig.json +95 -95
- package/wrangler-dist/cli.js +55383 -54138
- package/pages/functions/buildPlugin.ts +0 -105
- package/pages/functions/buildWorker.ts +0 -151
- package/pages/functions/filepath-routing.ts +0 -189
- package/pages/functions/identifiers.ts +0 -78
- package/pages/functions/routes.ts +0 -156
- package/pages/functions/template-plugin.ts +0 -147
- package/pages/functions/template-worker.ts +0 -143
- package/src/pages.tsx +0 -2093
- package/src/user.tsx +0 -1214
|
@@ -9,24 +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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
diagnostics: Diagnostics,
|
|
13
|
+
config: T,
|
|
14
|
+
fieldPath: DeepKeyOf<T>,
|
|
15
|
+
message: string,
|
|
16
|
+
remove: boolean,
|
|
17
|
+
title = "Deprecation",
|
|
18
|
+
type: "warning" | "error" = "warning"
|
|
19
19
|
): void {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
+
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -36,16 +36,16 @@ export function deprecated<T extends object>(
|
|
|
36
36
|
* The `fieldPath` is a dot separated property path, e.g. `"build.upload.format"`.
|
|
37
37
|
*/
|
|
38
38
|
export function experimental<T extends object>(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
diagnostics: Diagnostics,
|
|
40
|
+
config: T,
|
|
41
|
+
fieldPath: DeepKeyOf<T>
|
|
42
42
|
): void {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -55,51 +55,51 @@ export function experimental<T extends object>(
|
|
|
55
55
|
* and then the `defaultValue`.
|
|
56
56
|
*/
|
|
57
57
|
export function inheritable<K extends keyof Environment>(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
65
65
|
): Environment[K] {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
validate(diagnostics, field, rawEnv[field], topLevelEnv);
|
|
67
|
+
return (
|
|
68
|
+
(rawEnv[field] as Environment[K]) ??
|
|
69
|
+
transformFn(topLevelEnv?.[field]) ??
|
|
70
|
+
defaultValue
|
|
71
|
+
);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* Get an inheritable environment field, but only if we are in legacy environments
|
|
76
76
|
*/
|
|
77
77
|
export function inheritableInLegacyEnvironments<K extends keyof Environment>(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
86
|
): Environment[K] {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
103
|
}
|
|
104
104
|
|
|
105
105
|
/**
|
|
@@ -111,27 +111,27 @@ type TransformFn<T> = (fieldValue: T | undefined) => T | undefined;
|
|
|
111
111
|
* Transform an environment field by appending current environment name to it.
|
|
112
112
|
*/
|
|
113
113
|
export const appendEnvName =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
(envName: string): TransformFn<string | undefined> =>
|
|
115
|
+
(fieldValue) =>
|
|
116
|
+
fieldValue ? `${fieldValue}-${envName}` : undefined;
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
119
|
* Log an error if this named environment is trying to override the value in the top-level
|
|
120
120
|
* environment, which is not allow for this field.
|
|
121
121
|
*/
|
|
122
122
|
function notAllowedInNamedServiceEnvironment<K extends keyof Environment>(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
diagnostics: Diagnostics,
|
|
124
|
+
topLevelEnv: Environment,
|
|
125
|
+
rawEnv: RawEnvironment,
|
|
126
|
+
field: K
|
|
127
127
|
): Environment[K] {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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];
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
/**
|
|
@@ -141,39 +141,39 @@ function notAllowedInNamedServiceEnvironment<K extends keyof Environment>(
|
|
|
141
141
|
* then log a warning and return the `defaultValue`.
|
|
142
142
|
*/
|
|
143
143
|
export function notInheritable<K extends keyof Environment>(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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]
|
|
152
152
|
): Environment[K] {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
// Idea taken from https://stackoverflow.com/a/66661477
|
|
168
168
|
type DeepKeyOf<T> = (
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
169
|
+
T extends object
|
|
170
|
+
? {
|
|
171
|
+
[K in Exclude<keyof T, symbol>]: `${K}${DotPrefix<DeepKeyOf<T[K]>>}`;
|
|
172
|
+
}[Exclude<keyof T, symbol>]
|
|
173
|
+
: ""
|
|
174
174
|
) extends infer D
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
? Extract<D, string>
|
|
176
|
+
: never;
|
|
177
177
|
|
|
178
178
|
type DotPrefix<T extends string> = T extends "" ? "" : `.${T}`;
|
|
179
179
|
|
|
@@ -188,145 +188,145 @@ type DotPrefix<T extends string> = T extends "" ? "" : `.${T}`;
|
|
|
188
188
|
* ```
|
|
189
189
|
*/
|
|
190
190
|
function unwindPropertyPath<T extends object>(
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
root: T,
|
|
192
|
+
path: DeepKeyOf<T>
|
|
193
193
|
): { container: object; field: string } | undefined {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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] };
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
/**
|
|
206
206
|
* The type of a function that can be used to validate a configuration field.
|
|
207
207
|
*/
|
|
208
208
|
export type ValidatorFn = (
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
diagnostics: Diagnostics,
|
|
210
|
+
field: string,
|
|
211
|
+
value: unknown,
|
|
212
|
+
topLevelEnv: Environment | undefined
|
|
213
213
|
) => boolean;
|
|
214
214
|
|
|
215
215
|
/**
|
|
216
216
|
* Validate that the field is a string.
|
|
217
217
|
*/
|
|
218
218
|
export const isString: ValidatorFn = (diagnostics, field, value) => {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
228
|
};
|
|
229
229
|
|
|
230
230
|
/**
|
|
231
231
|
* Validate that the `name` field is compliant with EWC constraints.
|
|
232
232
|
*/
|
|
233
233
|
export const isValidName: ValidatorFn = (diagnostics, field, value) => {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
+
}
|
|
247
247
|
};
|
|
248
248
|
|
|
249
249
|
/**
|
|
250
250
|
* Validate that the field is an array of strings.
|
|
251
251
|
*/
|
|
252
252
|
export const isStringArray: ValidatorFn = (diagnostics, field, value) => {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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;
|
|
265
265
|
};
|
|
266
266
|
|
|
267
267
|
/**
|
|
268
268
|
* Validate that the field is an object containing the given properties.
|
|
269
269
|
*/
|
|
270
270
|
export const isObjectWith =
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
+
};
|
|
297
297
|
|
|
298
298
|
/**
|
|
299
299
|
* Validate that the field value is one of the given choices.
|
|
300
300
|
*/
|
|
301
301
|
export const isOneOf =
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
314
|
|
|
315
315
|
/**
|
|
316
316
|
* Aggregate multiple validator functions
|
|
317
317
|
*/
|
|
318
318
|
export const all = (...validations: ValidatorFn[]): ValidatorFn => {
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
return (diagnostics, field, value, config) => {
|
|
320
|
+
let passedValidations = true;
|
|
321
321
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
322
|
+
for (const validate of validations) {
|
|
323
|
+
if (!validate(diagnostics, field, value, config)) {
|
|
324
|
+
passedValidations = false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
327
|
|
|
328
|
-
|
|
329
|
-
|
|
328
|
+
return passedValidations;
|
|
329
|
+
};
|
|
330
330
|
};
|
|
331
331
|
|
|
332
332
|
/**
|
|
@@ -336,201 +336,201 @@ export const all = (...validations: ValidatorFn[]): ValidatorFn => {
|
|
|
336
336
|
* @param fields the names of the fields to check against.
|
|
337
337
|
*/
|
|
338
338
|
export const isMutuallyExclusiveWith = <T extends RawEnvironment | RawConfig>(
|
|
339
|
-
|
|
340
|
-
|
|
339
|
+
container: T,
|
|
340
|
+
...fields: (keyof T)[]
|
|
341
341
|
): ValidatorFn => {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
361
|
};
|
|
362
362
|
|
|
363
363
|
/**
|
|
364
364
|
* Validate that the field is a boolean.
|
|
365
365
|
*/
|
|
366
366
|
export const isBoolean: ValidatorFn = (diagnostics, field, value) => {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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;
|
|
376
376
|
};
|
|
377
377
|
|
|
378
378
|
/**
|
|
379
379
|
* Validate that the required field exists and has the expected type.
|
|
380
380
|
*/
|
|
381
381
|
export const validateRequiredProperty = (
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
382
|
+
diagnostics: Diagnostics,
|
|
383
|
+
container: string,
|
|
384
|
+
key: string,
|
|
385
|
+
value: unknown,
|
|
386
|
+
type: string,
|
|
387
|
+
choices?: unknown[]
|
|
388
388
|
): boolean => {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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;
|
|
410
410
|
};
|
|
411
411
|
|
|
412
412
|
/**
|
|
413
413
|
* Validate that, if the optional field exists, then it has the expected type.
|
|
414
414
|
*/
|
|
415
415
|
export const validateOptionalProperty = (
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
416
|
+
diagnostics: Diagnostics,
|
|
417
|
+
container: string,
|
|
418
|
+
key: string,
|
|
419
|
+
value: unknown,
|
|
420
|
+
type: string,
|
|
421
|
+
choices?: unknown[]
|
|
422
422
|
): boolean => {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
423
|
+
if (value !== undefined) {
|
|
424
|
+
return validateRequiredProperty(
|
|
425
|
+
diagnostics,
|
|
426
|
+
container,
|
|
427
|
+
key,
|
|
428
|
+
value,
|
|
429
|
+
type,
|
|
430
|
+
choices
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
return true;
|
|
434
434
|
};
|
|
435
435
|
|
|
436
436
|
/**
|
|
437
437
|
* Validate that the field is an array of elements of the given type.
|
|
438
438
|
*/
|
|
439
439
|
export const validateTypedArray = (
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
440
|
+
diagnostics: Diagnostics,
|
|
441
|
+
container: string,
|
|
442
|
+
value: unknown,
|
|
443
|
+
type: string
|
|
444
444
|
): boolean => {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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;
|
|
466
466
|
};
|
|
467
467
|
|
|
468
468
|
/**
|
|
469
469
|
* Validate that, if the optional field exists, it is an array of elements of the given type.
|
|
470
470
|
*/
|
|
471
471
|
export const validateOptionalTypedArray = (
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
472
|
+
diagnostics: Diagnostics,
|
|
473
|
+
container: string,
|
|
474
|
+
value: unknown,
|
|
475
|
+
type: string
|
|
476
476
|
) => {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
477
|
+
if (value !== undefined) {
|
|
478
|
+
return validateTypedArray(diagnostics, container, value, type);
|
|
479
|
+
}
|
|
480
|
+
return true;
|
|
481
481
|
};
|
|
482
482
|
|
|
483
483
|
/**
|
|
484
484
|
* Test to see if `obj` has the required property `prop` of type `type`.
|
|
485
485
|
*/
|
|
486
486
|
export const isRequiredProperty = <T extends object>(
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
487
|
+
obj: object,
|
|
488
|
+
prop: keyof T,
|
|
489
|
+
type: string,
|
|
490
|
+
choices?: unknown[]
|
|
491
491
|
): obj is T =>
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
492
|
+
hasProperty<T>(obj, prop) &&
|
|
493
|
+
typeof obj[prop] === type &&
|
|
494
|
+
(choices === undefined || choices.includes(obj[prop]));
|
|
495
495
|
|
|
496
496
|
/**
|
|
497
497
|
* Test to see if `obj` has the optional property `prop` of type `type`.
|
|
498
498
|
*/
|
|
499
499
|
export const isOptionalProperty = <T extends object>(
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
500
|
+
obj: object,
|
|
501
|
+
prop: keyof T,
|
|
502
|
+
type: string
|
|
503
503
|
): obj is T => !hasProperty<T>(obj, prop) || typeof obj[prop] === type;
|
|
504
504
|
|
|
505
505
|
/**
|
|
506
506
|
* Test to see if `obj` has the property `prop`.
|
|
507
507
|
*/
|
|
508
508
|
export const hasProperty = <T extends object>(
|
|
509
|
-
|
|
510
|
-
|
|
509
|
+
obj: object,
|
|
510
|
+
property: keyof T
|
|
511
511
|
): obj is T => property in obj;
|
|
512
512
|
|
|
513
513
|
/**
|
|
514
514
|
* Add warning messages about any properties in the given field that are not expected to be there.
|
|
515
515
|
*/
|
|
516
516
|
export const validateAdditionalProperties = (
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
517
|
+
diagnostics: Diagnostics,
|
|
518
|
+
fieldPath: string,
|
|
519
|
+
restProps: Iterable<string>,
|
|
520
|
+
knownProps: Iterable<string>
|
|
521
521
|
): boolean => {
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
534
|
};
|
|
535
535
|
|
|
536
536
|
/**
|
|
@@ -541,44 +541,44 @@ export const validateAdditionalProperties = (
|
|
|
541
541
|
* performed externally.
|
|
542
542
|
*/
|
|
543
543
|
export const getBindingNames = (value: unknown): string[] => {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
557
|
};
|
|
558
558
|
|
|
559
559
|
const isBindingList = (
|
|
560
|
-
|
|
560
|
+
value: unknown
|
|
561
561
|
): value is {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
562
|
+
bindings: {
|
|
563
|
+
name: string;
|
|
564
|
+
}[];
|
|
565
565
|
} =>
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
573
|
|
|
574
574
|
const isNamespaceList = (value: unknown): value is { binding: string }[] =>
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
575
|
+
Array.isArray(value) &&
|
|
576
|
+
value.every(
|
|
577
|
+
(entry) =>
|
|
578
|
+
isRecord(entry) && "binding" in entry && typeof entry.binding === "string"
|
|
579
|
+
);
|
|
580
580
|
|
|
581
581
|
const isRecord = (
|
|
582
|
-
|
|
582
|
+
value: unknown
|
|
583
583
|
): value is Record<string | number | symbol, unknown> =>
|
|
584
|
-
|
|
584
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|