wrangler 0.0.0-e33bea9b → 0.0.0-e351afcf

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 (105) hide show
  1. package/miniflare-dist/index.mjs +167 -93
  2. package/package.json +14 -9
  3. package/src/__tests__/api-devregistry.test.ts +121 -0
  4. package/src/__tests__/api.test.ts +27 -5
  5. package/src/__tests__/configuration.test.ts +59 -12
  6. package/src/__tests__/constellation.test.ts +371 -0
  7. package/src/__tests__/d1/execute.test.ts +2 -0
  8. package/src/__tests__/d1/migrate.test.ts +102 -0
  9. package/src/__tests__/delete.test.ts +6 -0
  10. package/src/__tests__/deployments.test.ts +335 -95
  11. package/src/__tests__/dev.test.tsx +71 -56
  12. package/src/__tests__/generate.test.ts +3 -4
  13. package/src/__tests__/helpers/mock-console.ts +6 -0
  14. package/src/__tests__/helpers/msw/handlers/deployments.ts +70 -3
  15. package/src/__tests__/helpers/msw/index.ts +4 -2
  16. package/src/__tests__/helpers/worker-scripts/parent-worker.js +4 -1
  17. package/src/__tests__/index.test.ts +14 -6
  18. package/src/__tests__/init.test.ts +127 -96
  19. package/src/__tests__/jest.setup.ts +4 -0
  20. package/src/__tests__/kv.test.ts +9 -9
  21. package/src/__tests__/logger.test.ts +68 -2
  22. package/src/__tests__/middleware.scheduled.test.ts +2 -2
  23. package/src/__tests__/middleware.test.ts +2 -2
  24. package/src/__tests__/mtls-certificates.test.ts +7 -3
  25. package/src/__tests__/pages/deployment-list.test.ts +2 -0
  26. package/src/__tests__/pages/functions-build.test.ts +70 -0
  27. package/src/__tests__/pages/project-list.test.ts +2 -0
  28. package/src/__tests__/pages/project-upload.test.ts +43 -24
  29. package/src/__tests__/pages/publish.test.ts +136 -92
  30. package/src/__tests__/publish.test.ts +878 -332
  31. package/src/__tests__/pubsub.test.ts +3 -0
  32. package/src/__tests__/queues.test.ts +5 -2
  33. package/src/__tests__/traverse-module-graph.test.ts +220 -0
  34. package/src/__tests__/tsconfig.tsbuildinfo +1 -1
  35. package/src/__tests__/user.test.ts +1 -1
  36. package/src/__tests__/worker-namespace.test.ts +1 -0
  37. package/src/api/dev.ts +12 -18
  38. package/src/api/pages/create-worker-bundle-contents.ts +1 -0
  39. package/src/api/pages/publish.tsx +28 -12
  40. package/src/bundle.ts +26 -9
  41. package/src/config/environment.ts +27 -0
  42. package/src/config/index.ts +18 -0
  43. package/src/config/validation.ts +91 -1
  44. package/src/constellation/createProject.tsx +51 -0
  45. package/src/constellation/deleteProject.ts +51 -0
  46. package/src/constellation/deleteProjectModel.ts +68 -0
  47. package/src/constellation/index.ts +75 -0
  48. package/src/constellation/listCatalog.tsx +35 -0
  49. package/src/constellation/listModel.tsx +41 -0
  50. package/src/constellation/listProject.tsx +28 -0
  51. package/src/constellation/listRuntime.tsx +28 -0
  52. package/src/constellation/options.ts +17 -0
  53. package/src/constellation/types.ts +17 -0
  54. package/src/constellation/uploadModel.tsx +64 -0
  55. package/src/constellation/utils.ts +90 -0
  56. package/src/create-worker-upload-form.ts +18 -1
  57. package/src/d1/backups.tsx +19 -15
  58. package/src/d1/create.tsx +45 -39
  59. package/src/d1/delete.ts +1 -1
  60. package/src/d1/execute.tsx +23 -20
  61. package/src/d1/list.tsx +14 -12
  62. package/src/d1/migrations/apply.tsx +36 -30
  63. package/src/d1/migrations/create.tsx +15 -12
  64. package/src/d1/migrations/helpers.ts +5 -8
  65. package/src/d1/migrations/list.tsx +10 -7
  66. package/src/deployments.ts +260 -8
  67. package/src/dev/dev.tsx +2 -0
  68. package/src/dev/local.tsx +48 -20
  69. package/src/dev/start-server.ts +49 -46
  70. package/src/dev/use-esbuild.ts +52 -49
  71. package/src/dev-registry.ts +2 -1
  72. package/src/dev.tsx +11 -5
  73. package/src/docs/helpers.ts +50 -0
  74. package/src/docs/index.ts +25 -68
  75. package/src/entry.ts +19 -8
  76. package/src/environment-variables/factory.ts +2 -1
  77. package/src/generate/index.ts +2 -2
  78. package/src/index.ts +86 -23
  79. package/src/init.ts +144 -135
  80. package/src/kv/helpers.ts +7 -4
  81. package/src/logger.ts +23 -4
  82. package/src/metrics/send-event.ts +2 -1
  83. package/src/miniflare-cli/index.ts +2 -2
  84. package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -1
  85. package/src/module-collection.ts +92 -26
  86. package/src/pages/build.ts +29 -17
  87. package/src/pages/deployments.tsx +3 -5
  88. package/src/pages/dev.ts +24 -6
  89. package/src/pages/functions/buildPlugin.ts +1 -0
  90. package/src/pages/functions/buildWorker.ts +59 -8
  91. package/src/pages/functions/tsconfig.tsbuildinfo +1 -1
  92. package/src/pages/projects.tsx +2 -5
  93. package/src/pages/upload.tsx +24 -4
  94. package/src/publish/publish.ts +10 -26
  95. package/src/queues/cli/commands/consumer/add.ts +6 -0
  96. package/src/queues/client.ts +1 -0
  97. package/src/secret/index.ts +1 -0
  98. package/src/sites.ts +164 -52
  99. package/src/traverse-module-graph.ts +53 -0
  100. package/src/user/user.ts +1 -0
  101. package/src/utils/render.ts +93 -0
  102. package/src/worker.ts +10 -0
  103. package/wrangler-dist/cli.d.ts +29 -2
  104. package/wrangler-dist/cli.js +23270 -22110
  105. package/src/__tests__/api-devregistry.test.js +0 -64
@@ -60,7 +60,7 @@ describe("User", () => {
60
60
  expect(counter).toBe(1);
61
61
  expect(std.out).toMatchInlineSnapshot(`
62
62
  "Attempting to login via OAuth...
63
- Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256
63
+ Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20constellation%3Awrite%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256
64
64
  Successfully logged in."
65
65
  `);
66
66
  expect(readAuthConfigFile()).toEqual<UserAuthConfig>({
@@ -27,6 +27,7 @@ describe("dispatch-namespace", () => {
27
27
  Object {
28
28
  "debug": "",
29
29
  "err": "",
30
+ "info": "",
30
31
  "out": "wrangler dispatch-namespace
31
32
 
32
33
  📦 Interact with a dispatch namespace
package/src/api/dev.ts CHANGED
@@ -3,6 +3,7 @@ import { startApiDev, startDev } from "../dev";
3
3
  import { logger } from "../logger";
4
4
 
5
5
  import type { Environment } from "../config";
6
+ import type { Rule } from "../config/environment";
6
7
  import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli/types";
7
8
  import type { RequestInit, Response, RequestInfo } from "undici";
8
9
 
@@ -42,6 +43,9 @@ export interface UnstableDevOptions {
42
43
  bucket_name: string;
43
44
  preview_bucket_name?: string;
44
45
  }[];
46
+ processEntrypoint?: boolean;
47
+ moduleRoot?: string;
48
+ rules?: Rule[];
45
49
  logLevel?: "none" | "info" | "error" | "log" | "warn" | "debug"; // Specify logging level [choices: "debug", "info", "log", "warn", "error", "none"] [default: "log"]
46
50
  inspect?: boolean;
47
51
  local?: boolean;
@@ -143,7 +147,6 @@ export async function unstable_dev(
143
147
  enablePagesAssetsServiceBinding,
144
148
  liveReload,
145
149
  showInteractiveDevSession,
146
- forceLocal,
147
150
  onReady: (address, port) => {
148
151
  readyPort = port;
149
152
  readyAddress = address;
@@ -151,6 +154,7 @@ export async function unstable_dev(
151
154
  },
152
155
  config: options?.config,
153
156
  env: options?.env,
157
+ processEntrypoint: !!options?.processEntrypoint,
154
158
  bundle: options?.bundle,
155
159
  compatibilityDate: options?.compatibilityDate,
156
160
  compatibilityFlags: options?.compatibilityFlags,
@@ -232,6 +236,7 @@ export async function unstable_dev(
232
236
  experimentalLocal: experimentalLocal ?? false,
233
237
  experimentalLocalRemoteKv: experimentalLocalRemoteKv ?? false,
234
238
  enablePagesAssetsServiceBinding,
239
+ forceLocal,
235
240
  liveReload,
236
241
  onReady: (address, port) => {
237
242
  readyPort = port;
@@ -301,27 +306,16 @@ export async function unstable_dev(
301
306
  export function parseRequestInput(
302
307
  readyAddress: string,
303
308
  readyPort: number,
304
- input?: RequestInfo,
309
+ input: RequestInfo = "/",
305
310
  init?: RequestInit,
306
311
  protocol: "http" | "https" = "http"
307
312
  ): [RequestInfo, RequestInit | undefined] {
308
313
  if (input instanceof Request) {
309
314
  return [input, undefined];
310
- } else if (input instanceof URL) {
311
- input = `${protocol}://${readyAddress}:${readyPort}${input.pathname}`;
312
- } else if (typeof input === "string") {
313
- try {
314
- // Want to strip the URL to only get the pathname, but the user could pass in only the pathname
315
- // Will error if we try and pass "/something" into new URL("/something")
316
- input = `${protocol}://${readyAddress}:${readyPort}${
317
- new URL(input).pathname
318
- }`;
319
- } catch {
320
- input = `${protocol}://${readyAddress}:${readyPort}${input}`;
321
- }
322
- } else {
323
- input = `${protocol}://${readyAddress}:${readyPort}`;
324
315
  }
325
-
326
- return [input, init];
316
+ const url = new URL(`${input}`, `${protocol}://${readyAddress}:${readyPort}`);
317
+ url.protocol = protocol;
318
+ url.hostname = readyAddress;
319
+ url.port = readyPort.toString();
320
+ return [url, init];
327
321
  }
@@ -49,6 +49,7 @@ function createWorkerBundleFormData(workerBundle: BundleResult): FormData {
49
49
  bindings: {
50
50
  vars: undefined,
51
51
  kv_namespaces: undefined,
52
+ send_email: undefined,
52
53
  wasm_modules: undefined,
53
54
  text_blobs: undefined,
54
55
  data_blobs: undefined,
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync, lstatSync, readFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join, resolve as resolvePath } from "node:path";
4
4
  import { cwd } from "node:process";
@@ -14,6 +14,7 @@ import {
14
14
  import {
15
15
  buildRawWorker,
16
16
  checkRawWorker,
17
+ traverseAndBuildWorkerJSDirectory,
17
18
  } from "../../pages/functions/buildWorker";
18
19
  import { validateRoutes } from "../../pages/functions/routes-validation";
19
20
  import { upload } from "../../pages/upload";
@@ -65,7 +66,7 @@ interface PagesPublishOptions {
65
66
 
66
67
  /**
67
68
  * Whether to run bundling on `_worker.js` before deploying.
68
- * Default: false
69
+ * Default: true
69
70
  */
70
71
  bundle?: boolean;
71
72
 
@@ -95,9 +96,12 @@ export async function publish({
95
96
  _redirects: string | undefined,
96
97
  _routesGenerated: string | undefined,
97
98
  _routesCustom: string | undefined,
99
+ _workerJSIsDirectory = false,
98
100
  _workerJS: string | undefined;
99
101
 
100
- const workerScriptPath = resolvePath(directory, "_worker.js");
102
+ bundle = bundle ?? true;
103
+
104
+ const _workerPath = resolvePath(directory, "_worker.js");
101
105
 
102
106
  try {
103
107
  _headers = readFileSync(join(directory, "_headers"), "utf-8");
@@ -116,7 +120,10 @@ export async function publish({
116
120
  } catch {}
117
121
 
118
122
  try {
119
- _workerJS = readFileSync(workerScriptPath, "utf-8");
123
+ _workerJSIsDirectory = lstatSync(_workerPath).isDirectory();
124
+ if (!_workerJSIsDirectory) {
125
+ _workerJS = readFileSync(_workerPath, "utf-8");
126
+ }
120
127
  } catch {}
121
128
 
122
129
  // Grab the bindings from the API, we need these for shims and other such hacky inserts
@@ -240,16 +247,23 @@ export async function publish({
240
247
  * Advanced Mode
241
248
  * https://developers.cloudflare.com/pages/platform/functions/#advanced-mode
242
249
  *
243
- * When using a _worker.js file, the entire /functions directory is ignored
250
+ * When using a _worker.js file or _worker.js/ directory, the entire /functions directory is ignored
244
251
  * – this includes its routing and middleware characteristics.
245
252
  */
246
- if (_workerJS) {
253
+ if (_workerJSIsDirectory) {
254
+ workerBundle = await traverseAndBuildWorkerJSDirectory({
255
+ workerJSDirectory: _workerPath,
256
+ buildOutputDirectory: directory,
257
+ d1Databases,
258
+ nodejsCompat,
259
+ });
260
+ } else if (_workerJS) {
247
261
  if (bundle) {
248
262
  const outfile = join(tmpdir(), `./bundledWorker-${Math.random()}.mjs`);
249
263
  workerBundle = await buildRawWorker({
250
- workerScriptPath,
264
+ workerScriptPath: _workerPath,
251
265
  outfile,
252
- directory: directory ?? ".",
266
+ directory,
253
267
  local: false,
254
268
  sourcemap: true,
255
269
  watch: false,
@@ -258,17 +272,19 @@ export async function publish({
258
272
  nodejsCompat,
259
273
  });
260
274
  } else {
261
- await checkRawWorker(workerScriptPath, () => {});
262
- // TODO: Replace this with the cool new no-bundle stuff when that lands: https://github.com/cloudflare/workers-sdk/pull/2769
275
+ await checkRawWorker(_workerPath, () => {});
276
+ // TODO: Let users configure this in the future.
263
277
  workerBundle = {
264
278
  modules: [],
265
279
  dependencies: {},
266
280
  stop: undefined,
267
- resolvedEntryPointPath: workerScriptPath,
281
+ resolvedEntryPointPath: _workerPath,
268
282
  bundleType: "esm",
269
283
  };
270
284
  }
285
+ }
271
286
 
287
+ if (_workerJS || _workerJSIsDirectory) {
272
288
  const workerBundleContents = await createUploadWorkerBundleContents(
273
289
  workerBundle as BundleResult
274
290
  );
@@ -302,7 +318,7 @@ export async function publish({
302
318
  * Pages Functions
303
319
  * https://developers.cloudflare.com/pages/platform/functions/
304
320
  */
305
- if (builtFunctions && !_workerJS) {
321
+ if (builtFunctions && !_workerJS && !_workerJSIsDirectory) {
306
322
  const workerBundleContents = await createUploadWorkerBundleContents(
307
323
  workerBundle as BundleResult
308
324
  );
package/src/bundle.ts CHANGED
@@ -7,12 +7,19 @@ import NodeModulesPolyfills from "@esbuild-plugins/node-modules-polyfill";
7
7
  import * as esbuild from "esbuild";
8
8
  import tmp from "tmp-promise";
9
9
  import createModuleCollector from "./module-collection";
10
- import { getBasePath, toUrlPath } from "./paths";
10
+ import { getBasePath } from "./paths";
11
11
  import type { Config } from "./config";
12
12
  import type { DurableObjectBindings } from "./config/environment";
13
13
  import type { WorkerRegistry } from "./dev-registry";
14
14
  import type { Entry } from "./entry";
15
15
  import type { CfModule } from "./worker";
16
+
17
+ export const COMMON_ESBUILD_OPTIONS = {
18
+ // Our workerd runtime uses the same V8 version as recent Chrome, which is highly ES2022 compliant: https://kangax.github.io/compat-table/es2016plus/
19
+ target: "es2022",
20
+ loader: { ".js": "jsx", ".mjs": "jsx", ".cjs": "jsx" },
21
+ } as const;
22
+
16
23
  export type BundleResult = {
17
24
  modules: CfModule[];
18
25
  dependencies: esbuild.Metafile["outputs"][string]["inputs"];
@@ -94,6 +101,15 @@ const nodejsCompatPlugin: esbuild.Plugin = {
94
101
  },
95
102
  };
96
103
 
104
+ const cloudflareJsPlugin: esbuild.Plugin = {
105
+ name: "cloudflare javascript Plugin",
106
+ setup(pluginBuild) {
107
+ pluginBuild.onResolve({ filter: /^cloudflare:.*/ }, () => {
108
+ return { external: true };
109
+ });
110
+ },
111
+ };
112
+
97
113
  /**
98
114
  * Generate a bundle for the worker identified by the arguments passed in.
99
115
  */
@@ -101,6 +117,8 @@ export async function bundleWorker(
101
117
  entry: Entry,
102
118
  destination: string,
103
119
  options: {
120
+ // When `bundle` is set to false, we apply shims to the Worker, but won't pull in any imports
121
+ bundle?: boolean;
104
122
  serveAssetsFromWorker: boolean;
105
123
  assets?: StaticAssetsConfig;
106
124
  betaD1Shims?: string[];
@@ -133,6 +151,7 @@ export async function bundleWorker(
133
151
  }
134
152
  ): Promise<BundleResult> {
135
153
  const {
154
+ bundle = true,
136
155
  serveAssetsFromWorker,
137
156
  betaD1Shims,
138
157
  doBindings,
@@ -334,7 +353,7 @@ export async function bundleWorker(
334
353
 
335
354
  const buildOptions: esbuild.BuildOptions & { metafile: true } = {
336
355
  entryPoints: [inputEntry.file],
337
- bundle: true,
356
+ bundle,
338
357
  absWorkingDir: entry.directory,
339
358
  outdir: destination,
340
359
  entryNames: entryName || path.parse(entry.file).name,
@@ -346,10 +365,9 @@ export async function bundleWorker(
346
365
  }
347
366
  : {}),
348
367
  inject,
349
- external: ["__STATIC_CONTENT_MANIFEST"],
368
+ external: bundle ? ["__STATIC_CONTENT_MANIFEST"] : undefined,
350
369
  format: entry.format === "modules" ? "esm" : "iife",
351
- // Our workerd runtime uses the same V8 version as recent Chrome, which is highly ES2022 compliant: https://kangax.github.io/compat-table/es2016plus/
352
- target: "es2022",
370
+ target: COMMON_ESBUILD_OPTIONS.target,
353
371
  sourcemap: sourcemap ?? true, // this needs to use ?? to accept false
354
372
  // Include a reference to the output folder in the sourcemap.
355
373
  // This is omitted by default, but we need it to properly resolve source paths in error output.
@@ -368,9 +386,7 @@ export async function bundleWorker(
368
386
  },
369
387
  }),
370
388
  loader: {
371
- ".js": "jsx",
372
- ".mjs": "jsx",
373
- ".cjs": "jsx",
389
+ ...COMMON_ESBUILD_OPTIONS.loader,
374
390
  ...(loader || {}),
375
391
  },
376
392
  plugins: [
@@ -379,6 +395,7 @@ export async function bundleWorker(
379
395
  ? [NodeGlobalsPolyfills({ buffer: true }), NodeModulesPolyfills()]
380
396
  : []),
381
397
  ...(nodejsCompat ? [nodejsCompatPlugin] : []),
398
+ ...[cloudflareJsPlugin],
382
399
  ...(plugins || []),
383
400
  ],
384
401
  ...(jsxFactory && { jsxFactory }),
@@ -585,7 +602,7 @@ async function applyMiddlewareLoaderFacade(
585
602
  ...Object.fromEntries(
586
603
  middleware.map((val, index) => [
587
604
  middlewareIdentifiers[index],
588
- toUrlPath(path.resolve(getBasePath(), val.path)),
605
+ path.resolve(getBasePath(), val.path),
589
606
  ])
590
607
  ),
591
608
  }),
@@ -74,6 +74,12 @@ interface EnvironmentInheritable {
74
74
  */
75
75
  main: string | undefined;
76
76
 
77
+ /**
78
+ * The directory in which module rules should be evaluated in a `--no-bundle` worker
79
+ * This defaults to dirname(main) when left undefined
80
+ */
81
+ base_dir: string | undefined;
82
+
77
83
  /**
78
84
  * Whether we use <name>.<subdomain>.workers.dev to
79
85
  * test and deploy your worker.
@@ -339,6 +345,24 @@ interface EnvironmentNonInheritable {
339
345
  preview_id?: string;
340
346
  }[];
341
347
 
348
+ /**
349
+ * These specify bindings to send email from inside your Worker.
350
+ *
351
+ * NOTE: This field is not automatically inherited from the top level environment,
352
+ * and so must be specified in every named environment.
353
+ *
354
+ * @default `[]`
355
+ * @nonInheritable
356
+ */
357
+ send_email: {
358
+ /** The binding name used to refer to the this binding */
359
+ name: string;
360
+ /** If this binding should be restricted to a specific verified address */
361
+ destination_address?: string;
362
+ /** If this binding should be restricted to a set of verified addresses */
363
+ allowed_destination_addresses?: string[];
364
+ }[];
365
+
342
366
  /**
343
367
  * Specifies Queues that are bound to this Worker environment.
344
368
  *
@@ -374,6 +398,9 @@ interface EnvironmentNonInheritable {
374
398
 
375
399
  /** The queue to send messages that failed to be consumed. */
376
400
  dead_letter_queue?: string;
401
+
402
+ /** The maximum number of concurrent consumer Worker invocations. Leaving this unset will allow your consumer to scale to the maximum concurrency needed to keep up with the message backlog. */
403
+ max_concurrency?: number | null;
377
404
  }[];
378
405
  };
379
406
 
@@ -97,6 +97,7 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
97
97
  data_blobs,
98
98
  durable_objects,
99
99
  kv_namespaces,
100
+ send_email,
100
101
  queues,
101
102
  d1_databases,
102
103
  r2_buckets,
@@ -155,6 +156,23 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
155
156
  });
156
157
  }
157
158
 
159
+ if (send_email !== undefined && send_email.length > 0) {
160
+ output.push({
161
+ type: "Send Email",
162
+ entries: send_email.map(
163
+ ({ name, destination_address, allowed_destination_addresses }) => {
164
+ return {
165
+ key: name,
166
+ value:
167
+ destination_address ||
168
+ allowed_destination_addresses?.join(", ") ||
169
+ "unrestricted",
170
+ };
171
+ }
172
+ ),
173
+ });
174
+ }
175
+
158
176
  if (queues !== undefined && queues.length > 0) {
159
177
  output.push({
160
178
  type: "Queues",
@@ -358,6 +358,26 @@ function normalizeAndValidateMainField(
358
358
  }
359
359
  }
360
360
 
361
+ /**
362
+ * Validate the `base_dir` field and return the normalized values.
363
+ */
364
+ function normalizeAndValidateBaseDirField(
365
+ configPath: string | undefined,
366
+ rawDir: string | undefined
367
+ ): string | undefined {
368
+ const configDir = path.dirname(configPath ?? "wrangler.toml");
369
+ if (rawDir !== undefined) {
370
+ if (typeof rawDir === "string") {
371
+ const directory = path.resolve(configDir);
372
+ return path.resolve(directory, rawDir);
373
+ } else {
374
+ return rawDir;
375
+ }
376
+ } else {
377
+ return;
378
+ }
379
+ }
380
+
361
381
  /**
362
382
  * Validate the `dev` configuration and return the normalized values.
363
383
  */
@@ -1011,6 +1031,17 @@ function normalizeAndValidateEnvironment(
1011
1031
  ),
1012
1032
  deprecatedUpload
1013
1033
  ),
1034
+ base_dir: normalizeAndValidateBaseDirField(
1035
+ configPath,
1036
+ inheritable(
1037
+ diagnostics,
1038
+ topLevelEnv,
1039
+ rawEnv,
1040
+ "base_dir",
1041
+ isString,
1042
+ undefined
1043
+ )
1044
+ ),
1014
1045
  route,
1015
1046
  routes,
1016
1047
  triggers: inheritable(
@@ -1074,6 +1105,16 @@ function normalizeAndValidateEnvironment(
1074
1105
  validateBindingArray(envName, validateKVBinding),
1075
1106
  []
1076
1107
  ),
1108
+ send_email: notInheritable(
1109
+ diagnostics,
1110
+ topLevelEnv,
1111
+ rawConfig,
1112
+ rawEnv,
1113
+ envName,
1114
+ "send_email",
1115
+ validateBindingArray(envName, validateSendEmailBinding),
1116
+ []
1117
+ ),
1077
1118
  queues: notInheritable(
1078
1119
  diagnostics,
1079
1120
  topLevelEnv,
@@ -1765,6 +1806,53 @@ const validateKVBinding: ValidatorFn = (diagnostics, field, value) => {
1765
1806
  return isValid;
1766
1807
  };
1767
1808
 
1809
+ const validateSendEmailBinding: ValidatorFn = (diagnostics, field, value) => {
1810
+ if (typeof value !== "object" || value === null) {
1811
+ diagnostics.errors.push(
1812
+ `"send_email" bindings should be objects, but got ${JSON.stringify(
1813
+ value
1814
+ )}`
1815
+ );
1816
+ return false;
1817
+ }
1818
+ let isValid = true;
1819
+ // send email bindings must have a name.
1820
+ if (!isRequiredProperty(value, "name", "string")) {
1821
+ diagnostics.errors.push(
1822
+ `"${field}" bindings should have a string "name" field but got ${JSON.stringify(
1823
+ value
1824
+ )}.`
1825
+ );
1826
+ isValid = false;
1827
+ }
1828
+ if (!isOptionalProperty(value, "destination_address", "string")) {
1829
+ diagnostics.errors.push(
1830
+ `"${field}" bindings should, optionally, have a string "destination_address" field but got ${JSON.stringify(
1831
+ value
1832
+ )}.`
1833
+ );
1834
+ isValid = false;
1835
+ }
1836
+ if (!isOptionalProperty(value, "allowed_destination_addresses", "object")) {
1837
+ diagnostics.errors.push(
1838
+ `"${field}" bindings should, optionally, have a []string "allowed_destination_addresses" field but got ${JSON.stringify(
1839
+ value
1840
+ )}.`
1841
+ );
1842
+ isValid = false;
1843
+ }
1844
+ if (
1845
+ "destination_address" in value &&
1846
+ "allowed_destination_addresses" in value
1847
+ ) {
1848
+ diagnostics.errors.push(
1849
+ `"${field}" bindings should have either a "destination_address" or "allowed_destination_addresses" field, but not both.`
1850
+ );
1851
+ isValid = false;
1852
+ }
1853
+ return isValid;
1854
+ };
1855
+
1768
1856
  const validateQueueBinding: ValidatorFn = (diagnostics, field, value) => {
1769
1857
  if (typeof value !== "object" || value === null) {
1770
1858
  diagnostics.errors.push(
@@ -2215,6 +2303,7 @@ const validateConsumer: ValidatorFn = (diagnostics, field, value, _config) => {
2215
2303
  "max_batch_timeout",
2216
2304
  "max_retries",
2217
2305
  "dead_letter_queue",
2306
+ "max_concurrency",
2218
2307
  ])
2219
2308
  ) {
2220
2309
  isValid = false;
@@ -2230,12 +2319,13 @@ const validateConsumer: ValidatorFn = (diagnostics, field, value, _config) => {
2230
2319
 
2231
2320
  const options: {
2232
2321
  key: string;
2233
- type: "number" | "string";
2322
+ type: "number" | "string" | "boolean";
2234
2323
  }[] = [
2235
2324
  { key: "max_batch_size", type: "number" },
2236
2325
  { key: "max_batch_timeout", type: "number" },
2237
2326
  { key: "max_retries", type: "number" },
2238
2327
  { key: "dead_letter_queue", type: "string" },
2328
+ { key: "max_concurrency", type: "number" },
2239
2329
  ];
2240
2330
  for (const optionalOpt of options) {
2241
2331
  if (!isOptionalProperty(value, optionalOpt.key, optionalOpt.type)) {
@@ -0,0 +1,51 @@
1
+ import { fetchResult } from "../cfetch";
2
+ import { withConfig } from "../config";
3
+ import { logger } from "../logger";
4
+ import { requireAuth } from "../user";
5
+ import { takeName } from "./options";
6
+ import { constellationBetaWarning } from "./utils";
7
+ import type {
8
+ CommonYargsArgv,
9
+ StrictYargsOptionsToInterface,
10
+ } from "../yargs-types";
11
+ import type { Project } from "./types";
12
+
13
+ export function options(yargs: CommonYargsArgv) {
14
+ return takeName(yargs)
15
+ .positional("runtime", {
16
+ describe: "The name of the runtime to use",
17
+ type: "string",
18
+ demandOption: true,
19
+ })
20
+ .epilogue(constellationBetaWarning);
21
+ }
22
+
23
+ type HandlerOptions = StrictYargsOptionsToInterface<typeof options>;
24
+ export const handler = withConfig<HandlerOptions>(
25
+ async ({ name, runtime, config }): Promise<void> => {
26
+ const accountId = await requireAuth(config);
27
+
28
+ logger.log(constellationBetaWarning);
29
+
30
+ let proj: Project;
31
+ try {
32
+ proj = await fetchResult(`/accounts/${accountId}/constellation/project`, {
33
+ method: "POST",
34
+ headers: {
35
+ "Content-Type": "application/json",
36
+ },
37
+ body: JSON.stringify({
38
+ name,
39
+ runtime,
40
+ }),
41
+ });
42
+ } catch (e) {
43
+ if ((e as { code: number }).code === 7409) {
44
+ throw new Error("A project with that name already exists");
45
+ }
46
+ throw e;
47
+ }
48
+
49
+ logger.log(`✅ Successfully created Project "${proj.name}"!`);
50
+ }
51
+ );
@@ -0,0 +1,51 @@
1
+ import { fetchResult } from "../cfetch";
2
+ import { withConfig } from "../config";
3
+ import { confirm } from "../dialogs";
4
+ import { logger } from "../logger";
5
+ import { requireAuth } from "../user";
6
+ import { takeName } from "./options";
7
+ import { constellationBetaWarning, getProjectByName } from "./utils";
8
+ import type {
9
+ CommonYargsArgv,
10
+ StrictYargsOptionsToInterface,
11
+ } from "../yargs-types";
12
+
13
+ export function options(yargs: CommonYargsArgv) {
14
+ return takeName(yargs)
15
+ .option("force", {
16
+ describe: "Skip confirmation",
17
+ type: "boolean",
18
+ alias: "f",
19
+ default: false,
20
+ })
21
+ .epilogue(constellationBetaWarning);
22
+ }
23
+ type HandlerOptions = StrictYargsOptionsToInterface<typeof options>;
24
+ export const handler = withConfig<HandlerOptions>(
25
+ async ({ name, force, config }): Promise<void> => {
26
+ const accountId = await requireAuth(config);
27
+ logger.log(constellationBetaWarning);
28
+
29
+ const proj = await getProjectByName(config, accountId, name);
30
+
31
+ logger.log(`About to delete Project '${name}' (${proj.id}).`);
32
+ if (!force) {
33
+ const response = await confirm(`Ok to proceed?`);
34
+ if (!response) {
35
+ logger.log(`Not deleting.`);
36
+ return;
37
+ }
38
+
39
+ logger.log("Deleting...");
40
+ }
41
+
42
+ await fetchResult(
43
+ `/accounts/${accountId}/constellation/project/${proj.id}`,
44
+ {
45
+ method: "DELETE",
46
+ }
47
+ );
48
+
49
+ logger.log(`Deleted '${name}' successfully.`);
50
+ }
51
+ );