wrangler 0.0.0-e29a241 → 0.0.0-e2f9bb2

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 (88) hide show
  1. package/README.md +25 -14
  2. package/bin/wrangler.js +13 -1
  3. package/miniflare-dist/index.mjs +4828 -21
  4. package/package.json +29 -24
  5. package/pages/functions/buildPlugin.ts +92 -0
  6. package/pages/functions/buildWorker.ts +80 -4
  7. package/pages/functions/filepath-routing.test.ts +11 -0
  8. package/pages/functions/filepath-routing.ts +8 -0
  9. package/pages/functions/routes.ts +6 -1
  10. package/pages/functions/template-plugin.ts +146 -0
  11. package/pages/functions/template-worker.ts +21 -8
  12. package/src/__tests__/config-cache.test.ts +36 -0
  13. package/src/__tests__/configuration.test.ts +692 -321
  14. package/src/__tests__/dev.test.tsx +284 -18
  15. package/src/__tests__/guess-worker-format.test.ts +28 -1
  16. package/src/__tests__/helpers/mock-cfetch.ts +5 -1
  17. package/src/__tests__/helpers/mock-console.ts +20 -3
  18. package/src/__tests__/helpers/mock-dialogs.ts +24 -0
  19. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  20. package/src/__tests__/helpers/mock-istty.ts +27 -0
  21. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  22. package/src/__tests__/helpers/run-in-tmp.ts +7 -0
  23. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  24. package/src/__tests__/https-options.test.ts +5 -2
  25. package/src/__tests__/index.test.ts +27 -769
  26. package/src/__tests__/init.test.ts +1042 -0
  27. package/src/__tests__/jest.setup.ts +4 -4
  28. package/src/__tests__/kv.test.ts +133 -61
  29. package/src/__tests__/logger.test.ts +115 -0
  30. package/src/__tests__/pages.test.ts +311 -26
  31. package/src/__tests__/publish.test.ts +2789 -618
  32. package/src/__tests__/r2.test.ts +26 -10
  33. package/src/__tests__/route.test.ts +4 -4
  34. package/src/__tests__/secret.test.ts +55 -34
  35. package/src/__tests__/tail.test.ts +146 -20
  36. package/src/__tests__/user.test.ts +131 -0
  37. package/src/__tests__/whoami.test.tsx +14 -9
  38. package/src/bundle.ts +30 -3
  39. package/src/cfetch/internal.ts +0 -2
  40. package/src/cli.ts +5 -11
  41. package/src/config/README.md +1 -1
  42. package/src/config/config.ts +13 -53
  43. package/src/config/environment.ts +88 -4
  44. package/src/config/index.ts +11 -4
  45. package/src/config/validation-helpers.ts +96 -7
  46. package/src/config/validation.ts +407 -79
  47. package/src/config-cache.ts +45 -0
  48. package/src/create-worker-preview.ts +22 -13
  49. package/src/create-worker-upload-form.ts +32 -9
  50. package/src/dev/dev-vars.ts +36 -0
  51. package/src/dev/dev.tsx +132 -78
  52. package/src/dev/local.tsx +115 -29
  53. package/src/dev/remote.tsx +23 -13
  54. package/src/dev/use-esbuild.ts +23 -5
  55. package/src/dialogs.tsx +16 -6
  56. package/src/entry.ts +83 -24
  57. package/src/environment-variables.ts +3 -1
  58. package/src/errors.ts +11 -0
  59. package/src/https-options.ts +3 -2
  60. package/src/index.tsx +619 -377
  61. package/src/inspect.ts +17 -11
  62. package/src/intl-polyfill.d.ts +139 -0
  63. package/src/logger.ts +66 -0
  64. package/src/miniflare-cli/index.ts +4 -0
  65. package/src/miniflare-cli/request-context.ts +40 -0
  66. package/src/module-collection.ts +7 -7
  67. package/src/open-in-browser.ts +5 -4
  68. package/src/package-manager.ts +9 -8
  69. package/src/pages.tsx +883 -62
  70. package/src/parse.ts +1 -1
  71. package/src/proxy.ts +25 -11
  72. package/src/publish.ts +263 -141
  73. package/src/sites.tsx +27 -14
  74. package/src/tail/index.ts +2 -2
  75. package/src/tail/printing.ts +13 -8
  76. package/src/update-check.ts +19 -0
  77. package/src/user.tsx +116 -41
  78. package/src/whoami.tsx +2 -1
  79. package/src/worker.ts +15 -6
  80. package/templates/gitignore +171 -0
  81. package/templates/static-asset-facade.js +1 -1
  82. package/wrangler-dist/cli.js +21167 -50229
  83. package/miniflare-dist/index.mjs.map +0 -7
  84. package/src/__tests__/dev2.test.tsx +0 -154
  85. package/src/__tests__/logout.test.ts +0 -52
  86. package/src/__tests__/sentry.test.ts +0 -146
  87. package/src/reporting.ts +0 -131
  88. package/wrangler-dist/cli.js.map +0 -7
@@ -1,15 +1,21 @@
1
1
  import { render } from "ink-testing-library";
2
2
  import React from "react";
3
- import { reinitialiseAuthTokens, writeAuthConfigFile } from "../user";
3
+ import { writeAuthConfigFile } from "../user";
4
4
  import { getUserInfo, WhoAmI } from "../whoami";
5
5
  import { setMockResponse } from "./helpers/mock-cfetch";
6
6
  import { mockConsoleMethods } from "./helpers/mock-console";
7
+ import { useMockIsTTY } from "./helpers/mock-istty";
7
8
  import { runInTempDir } from "./helpers/run-in-tmp";
8
9
  import type { UserInfo } from "../whoami";
9
10
 
10
11
  describe("getUserInfo()", () => {
11
12
  runInTempDir({ homedir: "./home" });
12
13
  const std = mockConsoleMethods();
14
+ const { setIsTTY } = useMockIsTTY();
15
+
16
+ beforeEach(() => {
17
+ setIsTTY(true);
18
+ });
13
19
 
14
20
  it("should return undefined if there is no config file", async () => {
15
21
  const userInfo = await getUserInfo();
@@ -18,16 +24,12 @@ describe("getUserInfo()", () => {
18
24
 
19
25
  it("should return undefined if there is an empty config file", async () => {
20
26
  writeAuthConfigFile({});
21
- // Now that we have changed the auth tokens in the wrangler.toml we must reinitialize the user auth state.
22
- reinitialiseAuthTokens();
23
27
  const userInfo = await getUserInfo();
24
28
  expect(userInfo).toBeUndefined();
25
29
  });
26
30
 
27
31
  it("should return the user's email and accounts if authenticated via config token", async () => {
28
32
  writeAuthConfigFile({ oauth_token: "some-oauth-token" });
29
- // Now that we have changed the auth tokens in the wrangler.toml we must reinitialize the user auth state.
30
- reinitialiseAuthTokens();
31
33
 
32
34
  setMockResponse("/user", () => {
33
35
  return { email: "user@example.com" };
@@ -56,12 +58,15 @@ describe("getUserInfo()", () => {
56
58
 
57
59
  it("should display a warning message if the config file contains a legacy api_token field", async () => {
58
60
  writeAuthConfigFile({ api_token: "API_TOKEN" });
59
- await reinitialiseAuthTokens();
60
61
  await getUserInfo();
61
62
  expect(std.warn).toMatchInlineSnapshot(`
62
- "It looks like you have used Wrangler 1's \`config\` command to login with an API token.
63
- This is no longer supported in the current version of Wrangler.
64
- If you wish to authenticate via an API token then please set the \`CLOUDFLARE_API_TOKEN\` environment variable."
63
+ "▲ [WARNING] It looks like you have used Wrangler 1's \`config\` command to login with an API token.
64
+
65
+ This is no longer supported in the current version of Wrangler.
66
+ If you wish to authenticate via an API token then please set the \`CLOUDFLARE_API_TOKEN\`
67
+ environment variable.
68
+
69
+ "
65
70
  `);
66
71
  });
67
72
  });
package/src/bundle.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import assert from "node:assert";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
+ import NodeGlobalsPolyfills from "@esbuild-plugins/node-globals-polyfill";
5
+ import NodeModulesPolyfills from "@esbuild-plugins/node-modules-polyfill";
4
6
  import * as esbuild from "esbuild";
5
7
  import createModuleCollector from "./module-collection";
6
8
  import type { Config } from "./config";
@@ -26,10 +28,21 @@ export async function bundleWorker(
26
28
  jsxFragment: string | undefined;
27
29
  rules: Config["rules"];
28
30
  watch?: esbuild.WatchMode;
31
+ tsconfig: string | undefined;
32
+ minify: boolean | undefined;
33
+ nodeCompat: boolean | undefined;
29
34
  }
30
35
  ): Promise<BundleResult> {
31
- const { serveAssetsFromWorker, jsxFactory, jsxFragment, rules, watch } =
32
- options;
36
+ const {
37
+ serveAssetsFromWorker,
38
+ jsxFactory,
39
+ jsxFragment,
40
+ rules,
41
+ watch,
42
+ tsconfig,
43
+ minify,
44
+ nodeCompat,
45
+ } = options;
33
46
  const entryDirectory = path.dirname(entry.file);
34
47
  const moduleCollector = createModuleCollector({
35
48
  wrangler1xlegacyModuleReferences: {
@@ -54,17 +67,31 @@ export async function bundleWorker(
54
67
  outdir: destination,
55
68
  external: ["__STATIC_CONTENT_MANIFEST"],
56
69
  format: "esm",
70
+ target: "es2020",
57
71
  sourcemap: true,
72
+ minify,
58
73
  metafile: true,
59
74
  conditions: ["worker", "browser"],
75
+ ...(process.env.NODE_ENV && {
76
+ define: {
77
+ "process.env.NODE_ENV": `"${process.env.NODE_ENV}"`,
78
+ ...(nodeCompat ? { global: "globalThis" } : {}),
79
+ },
80
+ }),
60
81
  loader: {
61
82
  ".js": "jsx",
62
83
  ".mjs": "jsx",
63
84
  ".cjs": "jsx",
64
85
  },
65
- plugins: [moduleCollector.plugin],
86
+ plugins: [
87
+ moduleCollector.plugin,
88
+ ...(nodeCompat
89
+ ? [NodeGlobalsPolyfills({ buffer: true }), NodeModulesPolyfills()]
90
+ : []),
91
+ ],
66
92
  ...(jsxFactory && { jsxFactory }),
67
93
  ...(jsxFragment && { jsxFragment }),
94
+ ...(tsconfig && { tsconfig }),
68
95
  watch,
69
96
  });
70
97
 
@@ -1,7 +1,6 @@
1
1
  import { fetch, Headers } from "undici";
2
2
  import { getEnvironmentVariableFactory } from "../environment-variables";
3
3
  import { ParseError, parseJSON } from "../parse";
4
- import { reportError } from "../reporting";
5
4
  import { getAPIToken, loginOrRefreshIfRequired } from "../user";
6
5
  import type { URLSearchParams } from "node:url";
7
6
  import type { RequestInit, HeadersInit } from "undici";
@@ -48,7 +47,6 @@ export async function fetchInternal<ResponseType>(
48
47
  try {
49
48
  return parseJSON(jsonText) as ResponseType;
50
49
  } catch (err) {
51
- await reportError(err as Error);
52
50
  throw new ParseError({
53
51
  text: "Received a malformed response from the API",
54
52
  notes: [
package/src/cli.ts CHANGED
@@ -1,19 +1,13 @@
1
+ import "dotenv/config"; // Grab locally specified env params from a `.env` file.
1
2
  import process from "process";
2
3
  import { hideBin } from "yargs/helpers";
3
- import { reportError, initReporting } from "./reporting";
4
+ import { FatalError } from "./errors";
4
5
  import { main } from ".";
5
6
 
6
- try {
7
- initReporting();
8
- } catch (error) {}
9
-
10
- process.on("uncaughtExceptionMonitor", async (err, origin) => {
11
- await reportError(err, origin);
12
- });
13
-
14
- main(hideBin(process.argv)).catch(() => {
7
+ main(hideBin(process.argv)).catch((e) => {
15
8
  // The logging of any error that was thrown from `main()` is handled in the `yargs.fail()` handler.
16
9
  // Here we just want to ensure that the process exits with a non-zero code.
17
10
  // We don't want to do this inside the `main()` function, since that would kill the process when running our tests.
18
- process.exit(1);
11
+ const exitCode = (e instanceof FatalError && e.code) || 1;
12
+ process.exit(exitCode);
19
13
  });
@@ -48,7 +48,7 @@ This function will return:
48
48
  - a `Diagnostics` object, which contains any errors or warnings from the validation process
49
49
 
50
50
  The field values may have been parsed directly from the `RawConfig`, inherited into a named environment from the top-level environment, or given a default value.
51
- Generally, if there are any warnings they should be presented to the user via `console.warn()` messages,
51
+ Generally, if there are any warnings they should be presented to the user via `logger.warn()` messages,
52
52
  and if there are any errors then an `Error` should be thrown describing these errors.
53
53
 
54
54
  The `Diagnostics` object is hierarchical: each `Diagnostics` instance can contain a collection of child `Diagnostics` instance.
@@ -137,26 +137,15 @@ export interface ConfigFields<Dev extends RawDevConfig> {
137
137
  | undefined;
138
138
 
139
139
  /**
140
- * Configures a custom build step to be run by Wrangler when building your Worker.
141
- *
142
- * Refer to the [custom builds documentation](https://developers.cloudflare.com/workers/cli-wrangler/configuration#build)
143
- * for more details.
144
- *
145
- * @default {}
140
+ * A list of data files that your worker should be bound to. This is
141
+ * the "legacy" way of binding to a data file. ES module workers should
142
+ * do proper module imports.
146
143
  */
147
- build: {
148
- /** The command used to build your Worker. On Linux and macOS, the command is executed in the `sh` shell and the `cmd` shell for Windows. The `&&` and `||` shell operators may be used. */
149
- command?: string;
150
- /** The directory in which the command is executed. */
151
- cwd?: string;
152
- /** The directory to watch for changes while using wrangler dev, defaults to the current working directory */
153
- watch_dir?: string;
154
- /**
155
- * Deprecated field previously used to configure the build and upload of the script.
156
- * @deprecated
157
- */
158
- upload?: DeprecatedUpload;
159
- };
144
+ data_blobs:
145
+ | {
146
+ [key: string]: string;
147
+ }
148
+ | undefined;
160
149
  }
161
150
 
162
151
  export interface DevConfig {
@@ -164,7 +153,6 @@ export interface DevConfig {
164
153
  * IP address for the local dev server to listen on,
165
154
  *
166
155
  * @default `localhost`
167
- * @todo this needs to be implemented
168
156
  */
169
157
  ip: string;
170
158
 
@@ -179,7 +167,6 @@ export interface DevConfig {
179
167
  * Protocol that local wrangler dev server listens to requests on.
180
168
  *
181
169
  * @default `http`
182
- * @todo this needs to be implemented
183
170
  */
184
171
  local_protocol: "http" | "https";
185
172
 
@@ -190,13 +177,14 @@ export interface DevConfig {
190
177
  * See https://github.com/cloudflare/wrangler2/issues/583
191
178
  *
192
179
  * @default `https`
180
+ * @todo this needs to be implemented https://github.com/cloudflare/wrangler2/issues/583
193
181
  */
194
182
  upstream_protocol: "https" | "http";
195
183
 
196
184
  /**
197
- * Host to forward requests to, defaults to the zone of project
185
+ * Host to forward requests to, defaults to the host of the first route of project
198
186
  */
199
- host: string | undefined;
187
+ host: Config["route"];
200
188
  }
201
189
 
202
190
  export type RawDevConfig = Partial<DevConfig>;
@@ -219,40 +207,12 @@ export interface DeprecatedConfigFields {
219
207
  * @breaking
220
208
  */
221
209
  webpack_config?: string;
222
- }
223
-
224
- /**
225
- * Deprecated upload configuration.
226
- */
227
- export interface DeprecatedUpload {
228
- /**
229
- * The format of the Worker script.
230
- *
231
- * @deprecated We infer the format automatically now.
232
- */
233
- format?: "modules" | "service-worker";
234
210
 
235
211
  /**
236
- * The directory you wish to upload your worker from,
237
- * relative to the wrangler.toml file.
238
- *
239
- * Defaults to the directory containing the wrangler.toml file.
240
- *
212
+ * Configuration only used by a standalone use of the miniflare binary.
241
213
  * @deprecated
242
214
  */
243
- dir?: string;
244
-
245
- /**
246
- * The path to the Worker script, relative to `upload.dir`.
247
- *
248
- * @deprecated This will be replaced by a command line argument.
249
- */
250
- main?: string;
251
-
252
- /**
253
- * @deprecated This is now defined at the top level `rules` field.
254
- */
255
- rules?: Environment["rules"];
215
+ miniflare?: unknown;
256
216
  }
257
217
 
258
218
  interface EnvironmentMap {
@@ -64,7 +64,7 @@ interface EnvironmentInheritable {
64
64
  * @breaking
65
65
  * @inheritable
66
66
  */
67
- workers_dev: boolean;
67
+ workers_dev: boolean | undefined;
68
68
 
69
69
  /**
70
70
  * A list of routes that your worker should be published to.
@@ -74,7 +74,13 @@ interface EnvironmentInheritable {
74
74
  *
75
75
  * @inheritable
76
76
  */
77
- routes: string[] | undefined;
77
+ routes:
78
+ | (
79
+ | string
80
+ | { pattern: string; zone_id: string }
81
+ | { pattern: string; zone_name: string }
82
+ )[]
83
+ | undefined;
78
84
 
79
85
  /**
80
86
  * A route that your worker should be published to. Literally
@@ -85,7 +91,18 @@ interface EnvironmentInheritable {
85
91
  *
86
92
  * @inheritable
87
93
  */
88
- route: string | undefined;
94
+ route:
95
+ | (
96
+ | string
97
+ | { pattern: string; zone_id: string }
98
+ | { pattern: string; zone_name: string }
99
+ )
100
+ | undefined;
101
+
102
+ /**
103
+ * Path to a custom tsconfig
104
+ */
105
+ tsconfig: string | undefined;
89
106
 
90
107
  /**
91
108
  * The function to use to replace jsx syntax.
@@ -126,7 +143,7 @@ interface EnvironmentInheritable {
126
143
  *
127
144
  * @inheritable
128
145
  */
129
- usage_model: undefined | "bundled" | "unbound";
146
+ usage_model: "bundled" | "unbound" | undefined;
130
147
 
131
148
  /**
132
149
  * An ordered list of rules that define which modules to import,
@@ -138,6 +155,39 @@ interface EnvironmentInheritable {
138
155
  */
139
156
  rules: Rule[];
140
157
 
158
+ /**
159
+ * Configures a custom build step to be run by Wrangler when building your Worker.
160
+ *
161
+ * Refer to the [custom builds documentation](https://developers.cloudflare.com/workers/cli-wrangler/configuration#build)
162
+ * for more details.
163
+ *
164
+ * @default {}
165
+ */
166
+ build: {
167
+ /** The command used to build your Worker. On Linux and macOS, the command is executed in the `sh` shell and the `cmd` shell for Windows. The `&&` and `||` shell operators may be used. */
168
+ command?: string;
169
+ /** The directory in which the command is executed. */
170
+ cwd?: string;
171
+ /** The directory to watch for changes while using wrangler dev, defaults to the current working directory */
172
+ watch_dir?: string;
173
+ /**
174
+ * Deprecated field previously used to configure the build and upload of the script.
175
+ * @deprecated
176
+ */
177
+ upload?: DeprecatedUpload;
178
+ };
179
+
180
+ /**
181
+ * Minify the script before uploading.
182
+ * @inheritable
183
+ */
184
+ minify: boolean | undefined;
185
+
186
+ /**
187
+ * Add polyfills for node builtin modules and globals
188
+ */
189
+ node_compat: boolean | undefined;
190
+
141
191
  /**
142
192
  * TODO: remove this as it has been deprecated.
143
193
  *
@@ -282,6 +332,40 @@ interface EnvironmentDeprecated {
282
332
  }[];
283
333
  }
284
334
 
335
+ /**
336
+ * Deprecated upload configuration.
337
+ */
338
+ export interface DeprecatedUpload {
339
+ /**
340
+ * The format of the Worker script.
341
+ *
342
+ * @deprecated We infer the format automatically now.
343
+ */
344
+ format?: "modules" | "service-worker";
345
+
346
+ /**
347
+ * The directory you wish to upload your worker from,
348
+ * relative to the wrangler.toml file.
349
+ *
350
+ * Defaults to the directory containing the wrangler.toml file.
351
+ *
352
+ * @deprecated
353
+ */
354
+ dir?: string;
355
+
356
+ /**
357
+ * The path to the Worker script, relative to `upload.dir`.
358
+ *
359
+ * @deprecated This will be replaced by a command line argument.
360
+ */
361
+ main?: string;
362
+
363
+ /**
364
+ * @deprecated This is now defined at the top level `rules` field.
365
+ */
366
+ rules?: Environment["rules"];
367
+ }
368
+
285
369
  /**
286
370
  * The raw environment configuration that we read from the config file.
287
371
  *
@@ -1,9 +1,16 @@
1
1
  import { findUpSync } from "find-up";
2
+ import { logger } from "../logger";
2
3
  import { parseTOML, readFileSync } from "../parse";
3
4
  import { normalizeAndValidateConfig } from "./validation";
4
5
  import type { Config, RawConfig } from "./config";
5
6
 
6
- export type { Config, RawConfig } from "./config";
7
+ export type {
8
+ Config,
9
+ RawConfig,
10
+ ConfigFields,
11
+ DevConfig,
12
+ RawDevConfig,
13
+ } from "./config";
7
14
  export type {
8
15
  Environment,
9
16
  RawEnvironment,
@@ -15,7 +22,7 @@ export type {
15
22
  */
16
23
  export function readConfig(
17
24
  configPath: string | undefined,
18
- envName: string | undefined
25
+ args: unknown
19
26
  ): Config {
20
27
  let rawConfig: RawConfig = {};
21
28
  if (!configPath) {
@@ -31,11 +38,11 @@ export function readConfig(
31
38
  const { config, diagnostics } = normalizeAndValidateConfig(
32
39
  rawConfig,
33
40
  configPath,
34
- envName
41
+ args
35
42
  );
36
43
 
37
44
  if (diagnostics.hasWarnings()) {
38
- console.warn(diagnostics.renderWarnings());
45
+ logger.warn(diagnostics.renderWarnings());
39
46
  }
40
47
  if (diagnostics.hasErrors()) {
41
48
  throw new Error(diagnostics.renderErrors());
@@ -14,13 +14,15 @@ export function deprecated<T extends object>(
14
14
  fieldPath: DeepKeyOf<T>,
15
15
  message: string,
16
16
  remove: boolean,
17
- breaking = false
17
+ title = "Deprecation",
18
+ type: "warning" | "error" = "warning"
18
19
  ): void {
20
+ const BOLD = "\x1b[1m";
21
+ const NORMAL = "\x1b[0m";
22
+ const diagnosticMessage = `${BOLD}${title}${NORMAL}: "${fieldPath}":\n${message}`;
19
23
  const result = unwindPropertyPath(config, fieldPath);
20
24
  if (result !== undefined && result.field in result.container) {
21
- (breaking ? diagnostics.errors : diagnostics.warnings).push(
22
- `DEPRECATION: "${fieldPath}":\n${message}`
23
- );
25
+ diagnostics[`${type}s`].push(diagnosticMessage);
24
26
  if (remove) {
25
27
  delete (result.container as Record<string, unknown>)[result.field];
26
28
  }
@@ -58,11 +60,14 @@ export function inheritable<K extends keyof Environment>(
58
60
  rawEnv: RawEnvironment,
59
61
  field: K,
60
62
  validate: ValidatorFn,
61
- defaultValue: Environment[K]
63
+ defaultValue: Environment[K],
64
+ transformFn: TransformFn<Environment[K]> = (v) => v
62
65
  ): Environment[K] {
63
66
  validate(diagnostics, field, rawEnv[field], topLevelEnv);
64
67
  return (
65
- (rawEnv[field] as Environment[K]) ?? topLevelEnv?.[field] ?? defaultValue
68
+ (rawEnv[field] as Environment[K]) ??
69
+ transformFn(topLevelEnv?.[field]) ??
70
+ defaultValue
66
71
  );
67
72
  }
68
73
 
@@ -76,6 +81,7 @@ export function inheritableInLegacyEnvironments<K extends keyof Environment>(
76
81
  rawEnv: RawEnvironment,
77
82
  field: K,
78
83
  validate: ValidatorFn,
84
+ transformFn: TransformFn<Environment[K]> = (v) => v,
79
85
  defaultValue: Environment[K]
80
86
  ): Environment[K] {
81
87
  return topLevelEnv === undefined || isLegacyEnv === true
@@ -85,7 +91,8 @@ export function inheritableInLegacyEnvironments<K extends keyof Environment>(
85
91
  rawEnv,
86
92
  field,
87
93
  validate,
88
- defaultValue
94
+ defaultValue,
95
+ transformFn
89
96
  )
90
97
  : notAllowedInNamedServiceEnvironment(
91
98
  diagnostics,
@@ -95,6 +102,19 @@ export function inheritableInLegacyEnvironments<K extends keyof Environment>(
95
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.
@@ -207,6 +227,25 @@ export const isString: ValidatorFn = (diagnostics, field, value) => {
207
227
  return true;
208
228
  };
209
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
+ }
247
+ };
248
+
210
249
  /**
211
250
  * Validate that the field is an array of strings.
212
251
  */
@@ -484,3 +523,53 @@ export const validateAdditionalProperties = (
484
523
  }
485
524
  return true;
486
525
  };
526
+
527
+ /**
528
+ * Get the names of the bindings collection in `value`.
529
+ *
530
+ * Will return an empty array if it doesn't understand the value
531
+ * passed in, so another form of validation should be
532
+ * performed externally.
533
+ */
534
+ export const getBindingNames = (value: unknown): string[] => {
535
+ if (typeof value !== "object" || value === null) {
536
+ return [];
537
+ }
538
+
539
+ if (isBindingList(value)) {
540
+ return value.bindings.map(({ name }) => name);
541
+ } else if (isNamespaceList(value)) {
542
+ return value.map(({ binding }) => binding);
543
+ } else if (isRecord(value)) {
544
+ return Object.keys(value);
545
+ } else {
546
+ return [];
547
+ }
548
+ };
549
+
550
+ const isBindingList = (
551
+ value: unknown
552
+ ): value is {
553
+ bindings: {
554
+ name: string;
555
+ }[];
556
+ } =>
557
+ isRecord(value) &&
558
+ "bindings" in value &&
559
+ Array.isArray(value.bindings) &&
560
+ value.bindings.every(
561
+ (binding) =>
562
+ isRecord(binding) && "name" in binding && typeof binding.name === "string"
563
+ );
564
+
565
+ const isNamespaceList = (value: unknown): value is { binding: string }[] =>
566
+ Array.isArray(value) &&
567
+ value.every(
568
+ (entry) =>
569
+ isRecord(entry) && "binding" in entry && typeof entry.binding === "string"
570
+ );
571
+
572
+ const isRecord = (
573
+ value: unknown
574
+ ): value is Record<string | number | symbol, unknown> =>
575
+ typeof value === "object" && value !== null && !Array.isArray(value);