wrangler 0.0.0-e043479 → 0.0.0-e0a0509

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 (96) hide show
  1. package/README.md +27 -14
  2. package/bin/wrangler.js +13 -1
  3. package/miniflare-dist/index.mjs +4886 -0
  4. package/package.json +38 -25
  5. package/pages/functions/buildPlugin.ts +92 -0
  6. package/pages/functions/buildWorker.ts +80 -4
  7. package/pages/functions/filepath-routing.test.ts +136 -25
  8. package/pages/functions/filepath-routing.ts +14 -3
  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 +1294 -355
  14. package/src/__tests__/dev.test.tsx +592 -12
  15. package/src/__tests__/guess-worker-format.test.ts +37 -18
  16. package/src/__tests__/helpers/mock-account-id.ts +4 -0
  17. package/src/__tests__/helpers/mock-cfetch.ts +8 -4
  18. package/src/__tests__/helpers/mock-console.ts +20 -3
  19. package/src/__tests__/helpers/mock-dialogs.ts +26 -1
  20. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  21. package/src/__tests__/helpers/mock-istty.ts +27 -0
  22. package/src/__tests__/helpers/mock-kv.ts +9 -16
  23. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  24. package/src/__tests__/helpers/run-in-tmp.ts +10 -0
  25. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  26. package/src/__tests__/https-options.test.ts +130 -0
  27. package/src/__tests__/index.test.ts +129 -753
  28. package/src/__tests__/init.test.ts +1516 -0
  29. package/src/__tests__/jest.setup.ts +13 -5
  30. package/src/__tests__/kv.test.ts +208 -174
  31. package/src/__tests__/logger.test.ts +115 -0
  32. package/src/__tests__/pages.test.ts +327 -0
  33. package/src/__tests__/parse.test.ts +4 -0
  34. package/src/__tests__/publish.test.ts +3313 -505
  35. package/src/__tests__/r2.test.ts +38 -26
  36. package/src/__tests__/route.test.ts +45 -0
  37. package/src/__tests__/secret.test.ts +55 -34
  38. package/src/__tests__/tail.test.ts +146 -20
  39. package/src/__tests__/user.test.ts +131 -0
  40. package/src/__tests__/whoami.test.tsx +26 -7
  41. package/src/bundle.ts +43 -7
  42. package/src/cfetch/internal.ts +9 -5
  43. package/src/cli.ts +5 -11
  44. package/src/config/README.md +7 -7
  45. package/src/config/config.ts +69 -105
  46. package/src/config/environment.ts +93 -4
  47. package/src/config/index.ts +15 -4
  48. package/src/config/validation-helpers.ts +228 -23
  49. package/src/config/validation.ts +559 -154
  50. package/src/config-cache.ts +45 -0
  51. package/src/{api/preview.ts → create-worker-preview.ts} +41 -13
  52. package/src/{api/form_data.ts → create-worker-upload-form.ts} +33 -10
  53. package/src/dev/dev-vars.ts +36 -0
  54. package/src/dev/dev.tsx +418 -0
  55. package/src/dev/local.tsx +312 -0
  56. package/src/dev/remote.tsx +213 -0
  57. package/src/dev/use-esbuild.ts +118 -0
  58. package/src/dialogs.tsx +16 -6
  59. package/src/entry.ts +222 -12
  60. package/src/environment-variables.ts +3 -1
  61. package/src/errors.ts +11 -0
  62. package/src/https-options.ts +123 -0
  63. package/src/index.tsx +878 -482
  64. package/src/inspect.ts +25 -11
  65. package/src/intl-polyfill.d.ts +139 -0
  66. package/src/{kv.tsx → kv.ts} +7 -27
  67. package/src/logger.ts +66 -0
  68. package/src/miniflare-cli/README.md +30 -0
  69. package/src/miniflare-cli/enum-keys.ts +17 -0
  70. package/src/miniflare-cli/index.ts +41 -0
  71. package/src/miniflare-cli/request-context.ts +40 -0
  72. package/src/module-collection.ts +94 -6
  73. package/src/open-in-browser.ts +18 -5
  74. package/src/package-manager.ts +9 -8
  75. package/src/pages.tsx +892 -62
  76. package/src/parse.ts +4 -2
  77. package/src/proxy.ts +128 -55
  78. package/src/publish.ts +305 -199
  79. package/src/selfsigned.d.ts +29 -0
  80. package/src/sites.tsx +88 -33
  81. package/src/tail/index.ts +2 -2
  82. package/src/tail/printing.ts +13 -8
  83. package/src/update-check.ts +19 -0
  84. package/src/user.tsx +244 -104
  85. package/src/whoami.tsx +2 -1
  86. package/src/{api/worker.ts → worker.ts} +15 -29
  87. package/templates/gitignore +171 -0
  88. package/templates/static-asset-facade.js +1 -1
  89. package/wrangler-dist/cli.js +23568 -50489
  90. package/src/__tests__/helpers/mock-user.ts +0 -27
  91. package/src/__tests__/logout.test.ts +0 -50
  92. package/src/__tests__/sentry.test.ts +0 -146
  93. package/src/dev.tsx +0 -960
  94. package/src/guess-worker-format.ts +0 -68
  95. package/src/reporting.ts +0 -115
  96. package/wrangler-dist/cli.js.map +0 -7
package/src/publish.ts CHANGED
@@ -1,36 +1,39 @@
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
- import { execaCommand } from "execa";
6
5
  import tmp from "tmp-promise";
7
- import { toFormData } from "./api/form_data";
8
6
  import { bundleWorker } from "./bundle";
9
7
  import { fetchResult } from "./cfetch";
10
- import { fileExists } from "./entry";
11
- import guessWorkerFormat from "./guess-worker-format";
8
+ import { createWorkerUploadForm } from "./create-worker-upload-form";
9
+ import { logger } from "./logger";
12
10
  import { syncAssets } from "./sites";
13
- import type { CfScriptFormat, CfWorkerInit } from "./api/worker";
14
11
  import type { Config } from "./config";
12
+ import type { Entry } from "./entry";
15
13
  import type { AssetPaths } from "./sites";
14
+ import type { CfWorkerInit } from "./worker";
16
15
 
17
16
  type Props = {
18
17
  config: Config;
19
18
  accountId: string;
20
- format: CfScriptFormat | undefined;
21
- entry: { file: string; directory: string };
19
+ entry: Entry;
22
20
  rules: Config["rules"];
23
21
  name: string | undefined;
24
22
  env: string | undefined;
25
23
  compatibilityDate: string | undefined;
26
24
  compatibilityFlags: string[] | undefined;
27
25
  assetPaths: AssetPaths | undefined;
28
- triggers: (string | number)[] | undefined;
29
- routes: (string | number)[] | undefined;
26
+ triggers: string[] | undefined;
27
+ routes: string[] | undefined;
30
28
  legacyEnv: boolean | undefined;
31
- jsxFactory: undefined | string;
32
- jsxFragment: undefined | string;
29
+ jsxFactory: string | undefined;
30
+ jsxFragment: string | undefined;
31
+ tsconfig: string | undefined;
33
32
  experimentalPublic: boolean;
33
+ minify: boolean | undefined;
34
+ nodeCompat: boolean | undefined;
35
+ outDir: string | undefined;
36
+ dryRun: boolean | undefined;
34
37
  };
35
38
 
36
39
  function sleep(ms: number) {
@@ -41,25 +44,29 @@ export default async function publish(props: Props): Promise<void> {
41
44
  // TODO: warn if git/hg has uncommitted changes
42
45
  const { config, accountId } = props;
43
46
 
44
- // TODO: should we automatically fallback to top level config if there is no matching environment??
45
- const envRootObj = (props.env && config.env[props.env]) || config;
46
-
47
47
  assert(
48
- envRootObj.compatibility_date || props.compatibilityDate,
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."
48
+ props.compatibilityDate || config.compatibility_date,
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."
50
50
  );
51
51
 
52
- const triggers = props.triggers || envRootObj.triggers?.crons;
52
+ const triggers = props.triggers || config.triggers?.crons;
53
53
  const routes =
54
- props.routes ??
55
- envRootObj.routes ??
56
- (envRootObj.route ? [envRootObj.route] : []) ??
57
- [];
54
+ props.routes ?? config.routes ?? (config.route ? [config.route] : []) ?? [];
55
+
56
+ // deployToWorkersDev defaults to true only if there aren't any routes defined
57
+ const deployToWorkersDev = config.workers_dev ?? routes.length === 0;
58
58
 
59
- const { workers_dev: deployToWorkersDev } = config;
59
+ const jsxFactory = props.jsxFactory || config.jsx_factory;
60
+ const jsxFragment = props.jsxFragment || config.jsx_fragment;
60
61
 
61
- const jsxFactory = props.jsxFactory || envRootObj.jsx_factory;
62
- const jsxFragment = props.jsxFragment || envRootObj.jsx_fragment;
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
+ }
63
70
 
64
71
  const scriptName = props.name;
65
72
  assert(
@@ -67,96 +74,143 @@ export default async function publish(props: Props): Promise<void> {
67
74
  'You need to provide a name when publishing a worker. Either pass it as a cli arg with `--name <name>` or in your config file as `name = "<name>"`'
68
75
  );
69
76
 
70
- if (config.site?.["entry-point"]) {
71
- console.warn(
72
- "Deprecation notice: The `site.entry-point` config field is no longer used.\n" +
73
- "The entry-point should be specified via the command line (e.g. `wrangler publish path/to/script`) or the `main` config field.\n" +
74
- "Please remove the `site.entry-point` field from the `wrangler.toml` file."
75
- );
76
- }
77
-
78
77
  assert(
79
78
  !config.site || config.site.bucket,
80
79
  "A [site] definition requires a `bucket` field with a path to the site's public directory."
81
80
  );
82
81
 
83
- const destination = await tmp.dir({ unsafeCleanup: true });
84
- try {
85
- const envName = props.env ?? "production";
86
-
87
- if (config.build.command) {
88
- // TODO: add a deprecation message here?
89
- console.log("running:", config.build.command);
90
- await execaCommand(config.build.command, {
91
- shell: true,
92
- stdout: "inherit",
93
- stderr: "inherit",
94
- timeout: 1000 * 30,
95
- ...(config.build.cwd && { cwd: config.build.cwd }),
96
- });
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
+ }
97
93
 
98
- if (fileExists(props.entry.file) === false) {
99
- throw new Error(
100
- `Could not resolve "${path.relative(
101
- process.cwd(),
102
- props.entry.file
103
- )}".`
104
- );
105
- }
106
- }
94
+ const destination = props.outDir ?? (await tmp.dir({ unsafeCleanup: true }));
95
+ const envName = props.env ?? "production";
107
96
 
108
- const format = await guessWorkerFormat(props.entry, props.format);
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}`;
109
103
 
110
- if (props.experimentalPublic && format === "service-worker") {
111
- // TODO: check config too
112
- throw new Error(
113
- "You cannot publish in the service worker format with a public directory."
114
- );
115
- }
104
+ let available_on_subdomain; // we'll set this later
116
105
 
117
- if (config.wasm_modules && format === "modules") {
118
- throw new Error(
119
- "You cannot configure [wasm_modules] with an ES module worker. Instead, import the .wasm module directly in your code"
120
- );
121
- }
106
+ const { format } = props.entry;
122
107
 
123
- if (config.text_blobs && format === "modules") {
124
- throw new Error(
125
- "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"
126
- );
127
- }
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
+ }
128
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 {
129
132
  const { modules, resolvedEntryPointPath, bundleType } = await bundleWorker(
130
133
  props.entry,
131
- destination.path,
134
+ typeof destination === "string" ? destination : destination.path,
132
135
  {
133
136
  serveAssetsFromWorker: props.experimentalPublic,
134
137
  jsxFactory,
135
138
  jsxFragment,
136
- format,
137
139
  rules: props.rules,
140
+ tsconfig: props.tsconfig ?? config.tsconfig,
141
+ minify,
142
+ nodeCompat,
138
143
  }
139
144
  );
140
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
+
141
161
  const content = readFileSync(resolvedEntryPointPath, {
142
162
  encoding: "utf-8",
143
163
  });
144
164
 
145
165
  // if config.migrations
146
- // get current migration tag
147
166
  let migrations;
148
167
  if (config.migrations.length > 0) {
149
- const scripts = await fetchResult<
150
- { id: string; migration_tag: string }[]
151
- >(`/accounts/${accountId}/workers/scripts`);
152
- 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
+
153
206
  if (script?.migration_tag) {
154
207
  // was already published once
208
+ const scriptMigrationTag = script.migration_tag;
155
209
  const foundIndex = config.migrations.findIndex(
156
- (migration) => migration.tag === script.migration_tag
210
+ (migration) => migration.tag === scriptMigrationTag
157
211
  );
158
212
  if (foundIndex === -1) {
159
- console.warn(
213
+ logger.warn(
160
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...`
161
215
  );
162
216
  migrations = {
@@ -165,15 +219,21 @@ export default async function publish(props: Props): Promise<void> {
165
219
  steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
166
220
  };
167
221
  } else {
168
- migrations = {
169
- old_tag: script.migration_tag,
170
- new_tag: config.migrations[config.migrations.length - 1].tag,
171
- steps: config.migrations
172
- .slice(foundIndex + 1)
173
- .map(({ tag: _tag, ...rest }) => rest),
174
- };
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
175
233
  }
176
234
  } else {
235
+ // first time publishing durable objects to this script,
236
+ // so we send all the migrations
177
237
  migrations = {
178
238
  new_tag: config.migrations[config.migrations.length - 1].tag,
179
239
  steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
@@ -189,16 +249,17 @@ export default async function publish(props: Props): Promise<void> {
189
249
  // concept of service environments for kv namespaces yet).
190
250
  scriptName + (!props.legacyEnv && props.env ? `-${props.env}` : ""),
191
251
  props.assetPaths,
192
- false
252
+ false,
253
+ props.dryRun
193
254
  );
194
255
 
195
256
  const bindings: CfWorkerInit["bindings"] = {
196
- kv_namespaces: (envRootObj.kv_namespaces || []).concat(
257
+ kv_namespaces: (config.kv_namespaces || []).concat(
197
258
  assets.namespace
198
259
  ? { binding: "__STATIC_CONTENT", id: assets.namespace }
199
260
  : []
200
261
  ),
201
- vars: envRootObj.vars,
262
+ vars: config.vars,
202
263
  wasm_modules: config.wasm_modules,
203
264
  text_blobs: {
204
265
  ...config.text_blobs,
@@ -207,9 +268,10 @@ export default async function publish(props: Props): Promise<void> {
207
268
  __STATIC_CONTENT_MANIFEST: "__STATIC_CONTENT_MANIFEST",
208
269
  }),
209
270
  },
210
- durable_objects: envRootObj.durable_objects,
211
- r2_buckets: envRootObj.r2_buckets,
212
- unsafe: envRootObj.unsafe?.bindings,
271
+ data_blobs: config.data_blobs,
272
+ durable_objects: config.durable_objects,
273
+ r2_buckets: config.r2_buckets,
274
+ unsafe: config.unsafe?.bindings,
213
275
  };
214
276
 
215
277
  if (assets.manifest) {
@@ -230,67 +292,74 @@ export default async function publish(props: Props): Promise<void> {
230
292
  bindings,
231
293
  migrations,
232
294
  modules,
233
- compatibility_date: config.compatibility_date,
234
- compatibility_flags: config.compatibility_flags,
295
+ compatibility_date: props.compatibilityDate ?? config.compatibility_date,
296
+ compatibility_flags:
297
+ props.compatibilityFlags ?? config.compatibility_flags,
235
298
  usage_model: config.usage_model,
236
299
  };
237
300
 
238
- const start = Date.now();
239
- const notProd = !props.legacyEnv && props.env;
240
- const workerName = notProd ? `${scriptName} (${envName})` : scriptName;
241
- const workerUrl = notProd
242
- ? `/accounts/${accountId}/workers/services/${scriptName}/environments/${envName}`
243
- : `/accounts/${accountId}/workers/scripts/${scriptName}`;
244
-
245
- // Upload the script so it has time to propagate.
246
- const { available_on_subdomain } = await fetchResult(
247
- workerUrl,
248
- {
249
- method: "PUT",
250
- body: toFormData(worker),
251
- },
252
- new URLSearchParams({ available_on_subdomain: "true" })
253
- );
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
+ }
254
322
 
255
- const uploadMs = Date.now() - start;
256
- console.log("Uploaded", workerName, formatTime(uploadMs));
257
- const deployments: Promise<string[]>[] = [];
323
+ if (props.dryRun) {
324
+ logger.log(`--dry-run: exiting now.`);
325
+ return;
326
+ }
258
327
 
259
- if (deployToWorkersDev) {
260
- // Deploy to a subdomain of `workers.dev`
261
- const userSubdomain = (
262
- await fetchResult<{ subdomain: string }>(
263
- `/accounts/${accountId}/workers/subdomain`
264
- )
265
- ).subdomain;
266
- const scriptURL =
267
- props.legacyEnv || !props.env
268
- ? `${scriptName}.${userSubdomain}.workers.dev`
269
- : `${envName}.${scriptName}.${userSubdomain}.workers.dev`;
270
- if (!available_on_subdomain) {
271
- // Enable the `workers.dev` subdomain.
272
- deployments.push(
273
- fetchResult(`${workerUrl}/subdomain`, {
274
- method: "POST",
275
- body: JSON.stringify({ enabled: true }),
276
- headers: {
277
- "Content-Type": "application/json",
278
- },
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;
279
356
  })
280
- .then(() => [scriptURL])
281
- // Add a delay when the subdomain is first created.
282
- // This is to prevent an issue where a negative cache-hit
283
- // causes the subdomain to be unavailable for 30 seconds.
284
- // This is a temporary measure until we fix this on the edge.
285
- .then(async (url) => {
286
- await sleep(3000);
287
- return url;
288
- })
289
- );
290
- } else {
291
- deployments.push(Promise.resolve([scriptURL]));
292
- }
357
+ );
293
358
  } else {
359
+ deployments.push(Promise.resolve([scriptURL]));
360
+ }
361
+ } else {
362
+ if (available_on_subdomain) {
294
363
  // Disable the workers.dev deployment
295
364
  await fetchResult(`${workerUrl}/subdomain`, {
296
365
  method: "POST",
@@ -300,64 +369,101 @@ export default async function publish(props: Props): Promise<void> {
300
369
  },
301
370
  });
302
371
  }
372
+ }
303
373
 
304
- // Update routing table for the script.
305
- if (routes.length > 0) {
306
- deployments.push(
307
- fetchResult(`${workerUrl}/routes`, {
308
- // TODO: PATCH will not delete previous routes on this script,
309
- // whereas PUT will. We need to decide on the default behaviour
310
- // and how to configure it.
311
- method: "PUT",
312
- body: JSON.stringify(routes.map((pattern) => ({ pattern }))),
313
- headers: {
314
- "Content-Type": "application/json",
315
- },
316
- }).then(() => {
317
- if (routes.length > 10) {
318
- return routes
319
- .slice(0, 9)
320
- .map(String)
321
- .concat([`...and ${routes.length - 10} more routes`]);
322
- }
323
- return routes.map(String);
324
- })
325
- );
326
- }
374
+ logger.log("Uploaded", workerName, formatTime(uploadMs));
327
375
 
328
- // Configure any schedules for the script.
329
- // TODO: rename this to `schedules`?
330
- if (triggers && triggers.length) {
331
- deployments.push(
332
- fetchResult(`${workerUrl}/schedules`, {
333
- // TODO: Unlike routes, this endpoint does not support PATCH.
334
- // So technically, this will override any previous schedules.
335
- // We should change the endpoint to support PATCH.
336
- method: "PUT",
337
- body: JSON.stringify(triggers.map((cron) => ({ cron }))),
338
- headers: {
339
- "Content-Type": "application/json",
340
- },
341
- }).then(() => triggers.map(String))
342
- );
343
- }
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
+ }
413
+
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
+ }
344
428
 
345
- const targets = await Promise.all(deployments);
346
- const deployMs = Date.now() - start - uploadMs;
429
+ const targets = await Promise.all(deployments);
430
+ const deployMs = Date.now() - start - uploadMs;
347
431
 
348
- if (deployments.length > 0) {
349
- console.log("Published", workerName, formatTime(deployMs));
350
- for (const target of targets.flat()) {
351
- console.log(" ", target);
352
- }
353
- } else {
354
- 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);
355
436
  }
356
- } finally {
357
- await destination.cleanup();
437
+ } else {
438
+ logger.log("No publish targets for", workerName, formatTime(deployMs));
358
439
  }
359
440
  }
360
441
 
361
442
  function formatTime(duration: number) {
362
443
  return `(${(duration / 1000).toFixed(2)} sec)`;
363
444
  }
445
+
446
+ async function getSubdomain(accountId: string): Promise<string> {
447
+ try {
448
+ const { subdomain } = await fetchResult(
449
+ `/accounts/${accountId}/workers/subdomain`
450
+ );
451
+ return subdomain;
452
+ } catch (e) {
453
+ const error = e as { code?: number };
454
+ if (typeof error === "object" && !!error && error.code === 10007) {
455
+ // 10007 error code: not found
456
+ // https://api.cloudflare.com/#worker-subdomain-get-subdomain
457
+
458
+ const errorMessage =
459
+ "Error: You need to register a workers.dev subdomain before publishing to workers.dev";
460
+ const solutionMessage =
461
+ "You can either publish your worker to one or more routes by specifying them in wrangler.toml, or register a workers.dev subdomain here:";
462
+ const onboardingLink = `https://dash.cloudflare.com/${accountId}/workers/onboarding`;
463
+
464
+ throw new Error(`${errorMessage}\n${solutionMessage}\n${onboardingLink}`);
465
+ } else {
466
+ throw e;
467
+ }
468
+ }
469
+ }
@@ -0,0 +1,29 @@
1
+ declare module "selfsigned" {
2
+ import type { pki } from "node-forge";
3
+
4
+ export type Attributes = pki.CertificateField[];
5
+
6
+ export interface Options {
7
+ days?: number;
8
+ keySize?: number;
9
+ extensions?: unknown[];
10
+ algorithm?: string;
11
+ pkcs7?: boolean;
12
+ clientCertificate?: boolean;
13
+ clientCertificateCN?: string;
14
+ }
15
+
16
+ export interface Certificate {
17
+ private: string;
18
+ public: string;
19
+ cert: string;
20
+ fingerprint: string;
21
+ }
22
+
23
+ export function generate(attrs?: Attributes, options?: Options): Certificate;
24
+ export function generate(
25
+ attrs: Attributes,
26
+ options: Options,
27
+ callback: (err: Error, certificate: Certificate) => void
28
+ ): void;
29
+ }