wrangler 0.0.0-e2b7352 → 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 (86) hide show
  1. package/README.md +24 -13
  2. package/bin/wrangler.js +3 -0
  3. package/miniflare-dist/index.mjs +4828 -21
  4. package/package.json +29 -22
  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 +386 -81
  14. package/src/__tests__/dev.test.tsx +242 -21
  15. package/src/__tests__/guess-worker-format.test.ts +18 -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 +25 -759
  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 +130 -56
  29. package/src/__tests__/logger.test.ts +115 -0
  30. package/src/__tests__/pages.test.ts +290 -43
  31. package/src/__tests__/publish.test.ts +1699 -333
  32. package/src/__tests__/r2.test.ts +24 -8
  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 +97 -45
  36. package/src/__tests__/user.test.ts +131 -0
  37. package/src/__tests__/whoami.test.tsx +14 -9
  38. package/src/bundle.ts +15 -1
  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 +20 -4
  43. package/src/config/environment.ts +26 -3
  44. package/src/config/index.ts +2 -1
  45. package/src/config/validation-helpers.ts +26 -5
  46. package/src/config/validation.ts +233 -41
  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 +60 -34
  52. package/src/dev/local.tsx +101 -21
  53. package/src/dev/remote.tsx +23 -13
  54. package/src/dev/use-esbuild.ts +19 -5
  55. package/src/dialogs.tsx +16 -6
  56. package/src/entry.ts +38 -11
  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 +529 -303
  61. package/src/inspect.ts +17 -11
  62. package/src/logger.ts +66 -0
  63. package/src/miniflare-cli/index.ts +4 -0
  64. package/src/miniflare-cli/request-context.ts +40 -0
  65. package/src/module-collection.ts +7 -7
  66. package/src/open-in-browser.ts +5 -4
  67. package/src/package-manager.ts +9 -8
  68. package/src/pages.tsx +860 -55
  69. package/src/parse.ts +1 -1
  70. package/src/proxy.ts +25 -11
  71. package/src/publish.ts +260 -141
  72. package/src/sites.tsx +27 -14
  73. package/src/tail/index.ts +1 -1
  74. package/src/tail/printing.ts +13 -8
  75. package/src/update-check.ts +19 -0
  76. package/src/user.tsx +104 -38
  77. package/src/whoami.tsx +2 -1
  78. package/src/worker.ts +15 -6
  79. package/templates/gitignore +171 -0
  80. package/templates/static-asset-facade.js +1 -1
  81. package/wrangler-dist/cli.js +20948 -50169
  82. package/miniflare-dist/index.mjs.map +0 -7
  83. package/src/__tests__/logout.test.ts +0 -52
  84. package/src/__tests__/sentry.test.ts +0 -146
  85. package/src/reporting.ts +0 -131
  86. package/wrangler-dist/cli.js.map +0 -7
package/src/parse.ts CHANGED
@@ -75,7 +75,7 @@ type TomlError = Error & {
75
75
  /**
76
76
  * A wrapper around `TOML.parse` that throws a `ParseError`.
77
77
  */
78
- export function parseTOML(input: string, file?: string): object {
78
+ export function parseTOML(input: string, file?: string): TOML.JsonMap | never {
79
79
  try {
80
80
  return TOML.parse(input);
81
81
  } catch (err) {
package/src/proxy.ts CHANGED
@@ -5,7 +5,7 @@ import WebSocket from "faye-websocket";
5
5
  import { useEffect, useRef, useState } from "react";
6
6
  import serveStatic from "serve-static";
7
7
  import { getHttpsOptions } from "./https-options";
8
- import { reportError } from "./reporting";
8
+ import { logger } from "./logger";
9
9
  import type { CfPreviewToken } from "./create-worker-preview";
10
10
  import type {
11
11
  IncomingHttpHeaders,
@@ -75,7 +75,7 @@ export function usePreviewServer({
75
75
  ip,
76
76
  }: {
77
77
  previewToken: CfPreviewToken | undefined;
78
- publicRoot: undefined | string;
78
+ publicRoot: string | undefined;
79
79
  localProtocol: "https" | "http";
80
80
  localPort: number;
81
81
  ip: string;
@@ -92,8 +92,7 @@ export function usePreviewServer({
92
92
  createProxyServer(localProtocol)
93
93
  .then((proxy) => setProxyServer(proxy))
94
94
  .catch(async (err) => {
95
- console.error("Failed to create proxy server.", err);
96
- await reportError(err);
95
+ logger.error("Failed to create proxy server:", err);
97
96
  });
98
97
  }
99
98
  }, [proxyServer, localProtocol]);
@@ -289,21 +288,29 @@ export function usePreviewServer({
289
288
  // Start/stop the server whenever the
290
289
  // containing component is mounted/unmounted.
291
290
  useEffect(() => {
291
+ const abortController = new AbortController();
292
292
  if (proxyServer === undefined) {
293
293
  return;
294
294
  }
295
295
 
296
- waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 })
296
+ waitForPortToBeAvailable(port, {
297
+ retryPeriod: 200,
298
+ timeout: 2000,
299
+ abortSignal: abortController.signal,
300
+ })
297
301
  .then(() => {
298
302
  proxyServer.listen(port, ip);
299
- console.log(`⬣ Listening at ${localProtocol}://${ip}:${port}`);
303
+ logger.log(`⬣ Listening at ${localProtocol}://${ip}:${port}`);
300
304
  })
301
305
  .catch((err) => {
302
- console.error(`⬣ Failed to start server: ${err}`);
306
+ if ((err as { code: string }).code !== "ABORT_ERR") {
307
+ logger.error(`Failed to start server: ${err}`);
308
+ }
303
309
  });
304
310
 
305
311
  return () => {
306
312
  proxyServer.close();
313
+ abortController.abort();
307
314
  };
308
315
  }, [port, ip, proxyServer, localProtocol]);
309
316
  }
@@ -344,7 +351,7 @@ async function createProxyServer(
344
351
  return server
345
352
  .on("request", function (req, res) {
346
353
  // log all requests
347
- console.log(
354
+ logger.log(
348
355
  new Date().toLocaleTimeString(),
349
356
  req.method,
350
357
  req.url,
@@ -353,7 +360,7 @@ async function createProxyServer(
353
360
  })
354
361
  .on("upgrade", (req) => {
355
362
  // log all websocket connections
356
- console.log(
363
+ logger.log(
357
364
  new Date().toLocaleTimeString(),
358
365
  req.method,
359
366
  req.url,
@@ -363,7 +370,7 @@ async function createProxyServer(
363
370
  })
364
371
  .on("error", (err) => {
365
372
  // log all connection errors
366
- console.error(new Date().toLocaleTimeString(), err);
373
+ logger.error(new Date().toLocaleTimeString(), err);
367
374
  });
368
375
  }
369
376
 
@@ -398,9 +405,16 @@ function createStreamHandler(
398
405
  */
399
406
  export async function waitForPortToBeAvailable(
400
407
  port: number,
401
- options: { retryPeriod: number; timeout: number }
408
+ options: { retryPeriod: number; timeout: number; abortSignal: AbortSignal }
402
409
  ): Promise<void> {
403
410
  return new Promise((resolve, reject) => {
411
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
412
+ (options.abortSignal as any).addEventListener("abort", () => {
413
+ const abortError = new Error("waitForPortToBeAvailable() aborted");
414
+ (abortError as Error & { code: string }).code = "ABORT_ERR";
415
+ doReject(abortError);
416
+ });
417
+
404
418
  const timeout = setTimeout(() => {
405
419
  doReject(new Error(`Timed out waiting for port ${port}`));
406
420
  }, options.timeout);
package/src/publish.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import assert from "node:assert";
2
- import { readFileSync } from "node:fs";
2
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { URLSearchParams } from "node:url";
5
5
  import tmp from "tmp-promise";
6
6
  import { bundleWorker } from "./bundle";
7
7
  import { fetchResult } from "./cfetch";
8
8
  import { createWorkerUploadForm } from "./create-worker-upload-form";
9
+ import { logger } from "./logger";
9
10
  import { syncAssets } from "./sites";
10
11
  import type { Config } from "./config";
11
12
  import type { Entry } from "./entry";
@@ -22,13 +23,17 @@ type Props = {
22
23
  compatibilityDate: string | undefined;
23
24
  compatibilityFlags: string[] | undefined;
24
25
  assetPaths: AssetPaths | undefined;
25
- triggers: (string | number)[] | undefined;
26
- routes: (string | number)[] | undefined;
26
+ triggers: string[] | undefined;
27
+ routes: string[] | undefined;
27
28
  legacyEnv: boolean | undefined;
28
- jsxFactory: undefined | string;
29
- jsxFragment: undefined | string;
30
- tsconfig: undefined | string;
29
+ jsxFactory: string | undefined;
30
+ jsxFragment: string | undefined;
31
+ tsconfig: string | undefined;
31
32
  experimentalPublic: boolean;
33
+ minify: boolean | undefined;
34
+ nodeCompat: boolean | undefined;
35
+ outDir: string | undefined;
36
+ dryRun: boolean | undefined;
32
37
  };
33
38
 
34
39
  function sleep(ms: number) {
@@ -41,7 +46,7 @@ export default async function publish(props: Props): Promise<void> {
41
46
 
42
47
  assert(
43
48
  props.compatibilityDate || config.compatibility_date,
44
- "A compatibility_date is required when publishing. Add one to your wrangler.toml file, or pass it in your terminal as --compatibility_date. See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information."
49
+ "A compatibility_date is required when publishing. Add one to your wrangler.toml file, or pass it in your terminal as --compatibility-date. See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information."
45
50
  );
46
51
 
47
52
  const triggers = props.triggers || config.triggers?.crons;
@@ -54,6 +59,15 @@ export default async function publish(props: Props): Promise<void> {
54
59
  const jsxFactory = props.jsxFactory || config.jsx_factory;
55
60
  const jsxFragment = props.jsxFragment || config.jsx_fragment;
56
61
 
62
+ const minify = props.minify ?? config.minify;
63
+
64
+ const nodeCompat = props.nodeCompat ?? config.node_compat;
65
+ if (nodeCompat) {
66
+ logger.warn(
67
+ "Enabling node.js compatibility mode for builtins and globals. This is experimental and has serious tradeoffs. Please see https://github.com/ionic-team/rollup-plugin-node-polyfills/ for more details."
68
+ );
69
+ }
70
+
57
71
  const scriptName = props.name;
58
72
  assert(
59
73
  scriptName,
@@ -65,62 +79,138 @@ export default async function publish(props: Props): Promise<void> {
65
79
  "A [site] definition requires a `bucket` field with a path to the site's public directory."
66
80
  );
67
81
 
68
- const destination = await tmp.dir({ unsafeCleanup: true });
69
- try {
70
- const envName = props.env ?? "production";
82
+ if (props.outDir) {
83
+ // we're using a custom output directory,
84
+ // so let's first ensure it exists
85
+ mkdirSync(props.outDir, { recursive: true });
86
+ // add a README
87
+ const readmePath = path.join(props.outDir, "README.md");
88
+ writeFileSync(
89
+ readmePath,
90
+ `This folder contains the built output assets for the worker "${scriptName}" generated at ${new Date().toISOString()}.`
91
+ );
92
+ }
71
93
 
72
- const { format } = props.entry;
94
+ const destination = props.outDir ?? (await tmp.dir({ unsafeCleanup: true }));
95
+ const envName = props.env ?? "production";
73
96
 
74
- if (props.experimentalPublic && format === "service-worker") {
75
- // TODO: check config too
76
- throw new Error(
77
- "You cannot publish in the service worker format with a public directory."
78
- );
79
- }
97
+ const start = Date.now();
98
+ const notProd = !props.legacyEnv && props.env;
99
+ const workerName = notProd ? `${scriptName} (${envName})` : scriptName;
100
+ const workerUrl = notProd
101
+ ? `/accounts/${accountId}/workers/services/${scriptName}/environments/${envName}`
102
+ : `/accounts/${accountId}/workers/scripts/${scriptName}`;
80
103
 
81
- if (config.wasm_modules && format === "modules") {
82
- throw new Error(
83
- "You cannot configure [wasm_modules] with an ES module worker. Instead, import the .wasm module directly in your code"
84
- );
85
- }
104
+ let available_on_subdomain; // we'll set this later
86
105
 
87
- if (config.text_blobs && format === "modules") {
88
- throw new Error(
89
- "You cannot configure [text_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[build.upload.rules]` in your wrangler.toml"
90
- );
91
- }
106
+ const { format } = props.entry;
107
+
108
+ if (props.experimentalPublic && format === "service-worker") {
109
+ throw new Error(
110
+ "You cannot publish in the service-worker format with a public directory."
111
+ );
112
+ }
113
+
114
+ if (config.wasm_modules && format === "modules") {
115
+ throw new Error(
116
+ "You cannot configure [wasm_modules] with an ES module worker. Instead, import the .wasm module directly in your code"
117
+ );
118
+ }
119
+
120
+ if (config.text_blobs && format === "modules") {
121
+ throw new Error(
122
+ "You cannot configure [text_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[rules]` in your wrangler.toml"
123
+ );
124
+ }
92
125
 
126
+ if (config.data_blobs && format === "modules") {
127
+ throw new Error(
128
+ "You cannot configure [data_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[rules]` in your wrangler.toml"
129
+ );
130
+ }
131
+ try {
93
132
  const { modules, resolvedEntryPointPath, bundleType } = await bundleWorker(
94
133
  props.entry,
95
- destination.path,
134
+ typeof destination === "string" ? destination : destination.path,
96
135
  {
97
136
  serveAssetsFromWorker: props.experimentalPublic,
98
137
  jsxFactory,
99
138
  jsxFragment,
100
139
  rules: props.rules,
101
140
  tsconfig: props.tsconfig ?? config.tsconfig,
141
+ minify,
142
+ nodeCompat,
102
143
  }
103
144
  );
104
145
 
146
+ // Some validation of durable objects + migrations
147
+ if (config.durable_objects.bindings.length > 0) {
148
+ // intrinsic [durable_objects] implies [migrations]
149
+ const exportedDurableObjects = config.durable_objects.bindings.filter(
150
+ (binding) => !binding.script_name
151
+ );
152
+ if (exportedDurableObjects.length > 0 && config.migrations.length === 0) {
153
+ logger.warn(
154
+ `In wrangler.toml, you have configured [durable_objects] exported by this Worker (${exportedDurableObjects.map(
155
+ (durable) => durable.class_name
156
+ )}), but no [migrations] for them. This may not work as expected until you add a [migrations] section to your wrangler.toml. Refer to https://developers.cloudflare.com/workers/learning/using-durable-objects/#durable-object-migrations-in-wranglertoml for more details.`
157
+ );
158
+ }
159
+ }
160
+
105
161
  const content = readFileSync(resolvedEntryPointPath, {
106
162
  encoding: "utf-8",
107
163
  });
108
164
 
109
165
  // if config.migrations
110
- // get current migration tag
111
166
  let migrations;
112
167
  if (config.migrations.length > 0) {
113
- const scripts = await fetchResult<
114
- { id: string; migration_tag: string }[]
115
- >(`/accounts/${accountId}/workers/scripts`);
116
- const script = scripts.find(({ id }) => id === scriptName);
168
+ // get current migration tag
169
+ type ScriptData = { id: string; migration_tag?: string };
170
+ let script: ScriptData | undefined;
171
+ if (!props.legacyEnv) {
172
+ try {
173
+ if (props.env) {
174
+ const scriptData = await fetchResult<{
175
+ script: ScriptData;
176
+ }>(
177
+ `/accounts/${accountId}/workers/services/${scriptName}/environments/${props.env}`
178
+ );
179
+ script = scriptData.script;
180
+ } else {
181
+ const scriptData = await fetchResult<{
182
+ default_environment: {
183
+ script: ScriptData;
184
+ };
185
+ }>(`/accounts/${accountId}/workers/services/${scriptName}`);
186
+ script = scriptData.default_environment.script;
187
+ }
188
+ } catch (err) {
189
+ if (
190
+ ![
191
+ 10090, // corresponds to workers.api.error.service_not_found, so the script wasn't previously published at all
192
+ 10092, // workers.api.error.environment_not_found, so the script wasn't published to this environment yet
193
+ ].includes((err as { code: number }).code)
194
+ ) {
195
+ throw err;
196
+ }
197
+ // else it's a 404, no script found, and we can proceed
198
+ }
199
+ } else {
200
+ const scripts = await fetchResult<ScriptData[]>(
201
+ `/accounts/${accountId}/workers/scripts`
202
+ );
203
+ script = scripts.find(({ id }) => id === scriptName);
204
+ }
205
+
117
206
  if (script?.migration_tag) {
118
207
  // was already published once
208
+ const scriptMigrationTag = script.migration_tag;
119
209
  const foundIndex = config.migrations.findIndex(
120
- (migration) => migration.tag === script.migration_tag
210
+ (migration) => migration.tag === scriptMigrationTag
121
211
  );
122
212
  if (foundIndex === -1) {
123
- console.warn(
213
+ logger.warn(
124
214
  `The published script ${scriptName} has a migration tag "${script.migration_tag}, which was not found in wrangler.toml. You may have already deleted it. Applying all available migrations to the script...`
125
215
  );
126
216
  migrations = {
@@ -129,15 +219,21 @@ export default async function publish(props: Props): Promise<void> {
129
219
  steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
130
220
  };
131
221
  } else {
132
- migrations = {
133
- old_tag: script.migration_tag,
134
- new_tag: config.migrations[config.migrations.length - 1].tag,
135
- steps: config.migrations
136
- .slice(foundIndex + 1)
137
- .map(({ tag: _tag, ...rest }) => rest),
138
- };
222
+ if (foundIndex !== config.migrations.length - 1) {
223
+ // there are new migrations to send up
224
+ migrations = {
225
+ old_tag: script.migration_tag,
226
+ new_tag: config.migrations[config.migrations.length - 1].tag,
227
+ steps: config.migrations
228
+ .slice(foundIndex + 1)
229
+ .map(({ tag: _tag, ...rest }) => rest),
230
+ };
231
+ }
232
+ // else, we're up to date, no migrations to send
139
233
  }
140
234
  } else {
235
+ // first time publishing durable objects to this script,
236
+ // so we send all the migrations
141
237
  migrations = {
142
238
  new_tag: config.migrations[config.migrations.length - 1].tag,
143
239
  steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
@@ -153,7 +249,8 @@ export default async function publish(props: Props): Promise<void> {
153
249
  // concept of service environments for kv namespaces yet).
154
250
  scriptName + (!props.legacyEnv && props.env ? `-${props.env}` : ""),
155
251
  props.assetPaths,
156
- false
252
+ false,
253
+ props.dryRun
157
254
  );
158
255
 
159
256
  const bindings: CfWorkerInit["bindings"] = {
@@ -171,6 +268,7 @@ export default async function publish(props: Props): Promise<void> {
171
268
  __STATIC_CONTENT_MANIFEST: "__STATIC_CONTENT_MANIFEST",
172
269
  }),
173
270
  },
271
+ data_blobs: config.data_blobs,
174
272
  durable_objects: config.durable_objects,
175
273
  r2_buckets: config.r2_buckets,
176
274
  unsafe: config.unsafe?.bindings,
@@ -200,57 +298,68 @@ export default async function publish(props: Props): Promise<void> {
200
298
  usage_model: config.usage_model,
201
299
  };
202
300
 
203
- const start = Date.now();
204
- const notProd = !props.legacyEnv && props.env;
205
- const workerName = notProd ? `${scriptName} (${envName})` : scriptName;
206
- const workerUrl = notProd
207
- ? `/accounts/${accountId}/workers/services/${scriptName}/environments/${envName}`
208
- : `/accounts/${accountId}/workers/scripts/${scriptName}`;
301
+ if (!props.dryRun) {
302
+ // Upload the script so it has time to propagate.
303
+ // We can also now tell whether available_on_subdomain is set
304
+ available_on_subdomain = (
305
+ await fetchResult<{ available_on_subdomain: boolean }>(
306
+ workerUrl,
307
+ {
308
+ method: "PUT",
309
+ body: createWorkerUploadForm(worker),
310
+ },
311
+ new URLSearchParams({ include_subdomain_availability: "true" })
312
+ )
313
+ ).available_on_subdomain;
314
+ }
315
+ } finally {
316
+ if (typeof destination !== "string") {
317
+ // this means we're using a temp dir,
318
+ // so let's clean up before we proceed
319
+ await destination.cleanup();
320
+ }
321
+ }
209
322
 
210
- // Upload the script so it has time to propagate.
211
- const { available_on_subdomain } = await fetchResult(
212
- workerUrl,
213
- {
214
- method: "PUT",
215
- body: createWorkerUploadForm(worker),
216
- },
217
- new URLSearchParams({ available_on_subdomain: "true" })
218
- );
323
+ if (props.dryRun) {
324
+ logger.log(`--dry-run: exiting now.`);
325
+ return;
326
+ }
219
327
 
220
- const uploadMs = Date.now() - start;
221
- const deployments: Promise<string[]>[] = [];
222
-
223
- if (deployToWorkersDev) {
224
- // Deploy to a subdomain of `workers.dev`
225
- const userSubdomain = await getSubdomain(accountId);
226
- const scriptURL =
227
- props.legacyEnv || !props.env
228
- ? `${scriptName}.${userSubdomain}.workers.dev`
229
- : `${envName}.${scriptName}.${userSubdomain}.workers.dev`;
230
- if (!available_on_subdomain) {
231
- // Enable the `workers.dev` subdomain.
232
- deployments.push(
233
- fetchResult(`${workerUrl}/subdomain`, {
234
- method: "POST",
235
- body: JSON.stringify({ enabled: true }),
236
- headers: {
237
- "Content-Type": "application/json",
238
- },
328
+ const uploadMs = Date.now() - start;
329
+ const deployments: Promise<string[]>[] = [];
330
+
331
+ if (deployToWorkersDev) {
332
+ // Deploy to a subdomain of `workers.dev`
333
+ const userSubdomain = await getSubdomain(accountId);
334
+ const scriptURL =
335
+ props.legacyEnv || !props.env
336
+ ? `${scriptName}.${userSubdomain}.workers.dev`
337
+ : `${envName}.${scriptName}.${userSubdomain}.workers.dev`;
338
+ if (!available_on_subdomain) {
339
+ // Enable the `workers.dev` subdomain.
340
+ deployments.push(
341
+ fetchResult(`${workerUrl}/subdomain`, {
342
+ method: "POST",
343
+ body: JSON.stringify({ enabled: true }),
344
+ headers: {
345
+ "Content-Type": "application/json",
346
+ },
347
+ })
348
+ .then(() => [scriptURL])
349
+ // Add a delay when the subdomain is first created.
350
+ // This is to prevent an issue where a negative cache-hit
351
+ // causes the subdomain to be unavailable for 30 seconds.
352
+ // This is a temporary measure until we fix this on the edge.
353
+ .then(async (url) => {
354
+ await sleep(3000);
355
+ return url;
239
356
  })
240
- .then(() => [scriptURL])
241
- // Add a delay when the subdomain is first created.
242
- // This is to prevent an issue where a negative cache-hit
243
- // causes the subdomain to be unavailable for 30 seconds.
244
- // This is a temporary measure until we fix this on the edge.
245
- .then(async (url) => {
246
- await sleep(3000);
247
- return url;
248
- })
249
- );
250
- } else {
251
- deployments.push(Promise.resolve([scriptURL]));
252
- }
357
+ );
253
358
  } else {
359
+ deployments.push(Promise.resolve([scriptURL]));
360
+ }
361
+ } else {
362
+ if (available_on_subdomain) {
254
363
  // Disable the workers.dev deployment
255
364
  await fetchResult(`${workerUrl}/subdomain`, {
256
365
  method: "POST",
@@ -260,63 +369,73 @@ export default async function publish(props: Props): Promise<void> {
260
369
  },
261
370
  });
262
371
  }
372
+ }
263
373
 
264
- console.log("Uploaded", workerName, formatTime(uploadMs));
374
+ logger.log("Uploaded", workerName, formatTime(uploadMs));
265
375
 
266
- // Update routing table for the script.
267
- if (routes.length > 0) {
268
- deployments.push(
269
- fetchResult(`${workerUrl}/routes`, {
270
- // TODO: PATCH will not delete previous routes on this script,
271
- // whereas PUT will. We need to decide on the default behaviour
272
- // and how to configure it.
273
- method: "PUT",
274
- body: JSON.stringify(routes.map((pattern) => ({ pattern }))),
275
- headers: {
276
- "Content-Type": "application/json",
277
- },
278
- }).then(() => {
279
- if (routes.length > 10) {
280
- return routes
281
- .slice(0, 9)
282
- .map(String)
283
- .concat([`...and ${routes.length - 10} more routes`]);
284
- }
285
- return routes.map(String);
286
- })
287
- );
288
- }
376
+ // Update routing table for the script.
377
+ if (routes.length > 0) {
378
+ deployments.push(
379
+ fetchResult(`${workerUrl}/routes`, {
380
+ // Note: PUT will delete previous routes on this script.
381
+ method: "PUT",
382
+ body: JSON.stringify(
383
+ routes.map((route) =>
384
+ typeof route !== "object" ? { pattern: route } : route
385
+ )
386
+ ),
387
+ headers: {
388
+ "Content-Type": "application/json",
389
+ },
390
+ }).then(() => {
391
+ if (routes.length > 10) {
392
+ return routes
393
+ .slice(0, 9)
394
+ .map((route) =>
395
+ typeof route === "string"
396
+ ? route
397
+ : "zone_id" in route
398
+ ? `${route.pattern} (zone id: ${route.zone_id})`
399
+ : `${route.pattern} (zone name: ${route.zone_name})`
400
+ )
401
+ .concat([`...and ${routes.length - 10} more routes`]);
402
+ }
403
+ return routes.map((route) =>
404
+ typeof route === "string"
405
+ ? route
406
+ : "zone_id" in route
407
+ ? `${route.pattern} (zone id: ${route.zone_id})`
408
+ : `${route.pattern} (zone name: ${route.zone_name})`
409
+ );
410
+ })
411
+ );
412
+ }
289
413
 
290
- // Configure any schedules for the script.
291
- // TODO: rename this to `schedules`?
292
- if (triggers && triggers.length) {
293
- deployments.push(
294
- fetchResult(`${workerUrl}/schedules`, {
295
- // TODO: Unlike routes, this endpoint does not support PATCH.
296
- // So technically, this will override any previous schedules.
297
- // We should change the endpoint to support PATCH.
298
- method: "PUT",
299
- body: JSON.stringify(triggers.map((cron) => ({ cron }))),
300
- headers: {
301
- "Content-Type": "application/json",
302
- },
303
- }).then(() => triggers.map(String))
304
- );
305
- }
414
+ // Configure any schedules for the script.
415
+ // TODO: rename this to `schedules`?
416
+ if (triggers && triggers.length) {
417
+ deployments.push(
418
+ fetchResult(`${workerUrl}/schedules`, {
419
+ // Note: PUT will override previous schedules on this script.
420
+ method: "PUT",
421
+ body: JSON.stringify(triggers.map((cron) => ({ cron }))),
422
+ headers: {
423
+ "Content-Type": "application/json",
424
+ },
425
+ }).then(() => triggers.map((trigger) => `schedule: ${trigger}`))
426
+ );
427
+ }
306
428
 
307
- const targets = await Promise.all(deployments);
308
- const deployMs = Date.now() - start - uploadMs;
429
+ const targets = await Promise.all(deployments);
430
+ const deployMs = Date.now() - start - uploadMs;
309
431
 
310
- if (deployments.length > 0) {
311
- console.log("Published", workerName, formatTime(deployMs));
312
- for (const target of targets.flat()) {
313
- console.log(" ", target);
314
- }
315
- } else {
316
- console.log("No publish targets for", workerName, formatTime(deployMs));
432
+ if (deployments.length > 0) {
433
+ logger.log("Published", workerName, formatTime(deployMs));
434
+ for (const target of targets.flat()) {
435
+ logger.log(" ", target);
317
436
  }
318
- } finally {
319
- await destination.cleanup();
437
+ } else {
438
+ logger.log("No publish targets for", workerName, formatTime(deployMs));
320
439
  }
321
440
  }
322
441