wrangler 0.0.0-e14e589 → 0.0.0-e2a26a0

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 (61) hide show
  1. package/README.md +51 -0
  2. package/bin/wrangler.js +2 -2
  3. package/package.json +49 -30
  4. package/pages/functions/buildWorker.ts +62 -0
  5. package/pages/functions/filepath-routing.test.ts +47 -0
  6. package/pages/functions/filepath-routing.ts +231 -0
  7. package/pages/functions/identifiers.ts +78 -0
  8. package/pages/functions/routes.ts +160 -0
  9. package/pages/functions/template-worker.ts +136 -0
  10. package/src/__tests__/dev.test.tsx +71 -0
  11. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  12. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  13. package/src/__tests__/helpers/mock-account-id.ts +30 -0
  14. package/src/__tests__/helpers/mock-bin.ts +33 -0
  15. package/src/__tests__/helpers/mock-cfetch.ts +207 -0
  16. package/src/__tests__/helpers/mock-console.ts +56 -0
  17. package/src/__tests__/helpers/mock-dialogs.ts +65 -0
  18. package/src/__tests__/helpers/mock-kv.ts +40 -0
  19. package/src/__tests__/helpers/mock-user.ts +27 -0
  20. package/src/__tests__/helpers/run-in-tmp.ts +29 -0
  21. package/src/__tests__/helpers/run-wrangler.ts +14 -0
  22. package/src/__tests__/index.test.ts +509 -119
  23. package/src/__tests__/jest.setup.ts +37 -0
  24. package/src/__tests__/kv.test.ts +1139 -0
  25. package/src/__tests__/logout.test.ts +50 -0
  26. package/src/__tests__/package-manager.test.ts +206 -0
  27. package/src/__tests__/publish.test.ts +1222 -0
  28. package/src/__tests__/secret.test.ts +210 -0
  29. package/src/__tests__/whoami.test.tsx +86 -0
  30. package/src/api/form_data.ts +85 -76
  31. package/src/api/preview.ts +29 -24
  32. package/src/api/worker.ts +50 -35
  33. package/src/cfetch/index.ts +102 -0
  34. package/src/cfetch/internal.ts +101 -0
  35. package/src/cli.ts +6 -7
  36. package/src/config.ts +630 -85
  37. package/src/dev.tsx +446 -282
  38. package/src/dialogs.tsx +6 -14
  39. package/src/index.tsx +773 -426
  40. package/src/inspect.ts +596 -0
  41. package/src/kv.tsx +118 -45
  42. package/src/module-collection.ts +63 -0
  43. package/src/package-manager.ts +120 -0
  44. package/src/pages.tsx +917 -211
  45. package/src/paths.ts +26 -0
  46. package/src/proxy.ts +370 -0
  47. package/src/publish.ts +220 -88
  48. package/src/sites.tsx +180 -55
  49. package/src/tail.tsx +6 -4
  50. package/src/user.tsx +77 -87
  51. package/src/whoami.tsx +69 -0
  52. package/templates/new-worker.js +15 -0
  53. package/templates/new-worker.ts +15 -0
  54. package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
  55. package/wrangler-dist/cli.js +125330 -557
  56. package/wrangler-dist/cli.js.map +3 -3
  57. package/src/__tests__/fixtures/init/.gitkeep +0 -0
  58. package/src/__tests__/mock-cfetch.js +0 -46
  59. package/src/api/inspect.ts +0 -441
  60. package/src/cfetch.ts +0 -72
  61. package/src/util/fetch.ts +0 -74
package/src/publish.ts CHANGED
@@ -1,27 +1,36 @@
1
- import type { CfWorkerInit } from "./api/worker";
2
- import { toFormData } from "./api/form_data";
3
- import esbuild from "esbuild";
4
- import tmp from "tmp-promise";
5
- import type { Config } from "./config";
6
- import path from "path";
7
- import { readFile } from "fs/promises";
8
- import cfetch from "./cfetch";
9
1
  import assert from "node:assert";
2
+ import { readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { URLSearchParams } from "node:url";
5
+ import * as esbuild from "esbuild";
6
+ import { execaCommand } from "execa";
7
+ import tmp from "tmp-promise";
8
+ import { toFormData } from "./api/form_data";
9
+ import { fetchResult } from "./cfetch";
10
+ import makeModuleCollector from "./module-collection";
10
11
  import { syncAssets } from "./sites";
12
+ import type { CfWorkerInit } from "./api/worker";
13
+ import type { Config } from "./config";
14
+ import type { AssetPaths } from "./sites";
15
+ import type { Metafile } from "esbuild";
11
16
 
12
- type CfScriptFormat = void | "modules" | "service-worker";
17
+ type CfScriptFormat = undefined | "modules" | "service-worker";
13
18
 
14
19
  type Props = {
15
20
  config: Config;
16
- format?: CfScriptFormat;
17
- script?: string;
18
- name?: string;
19
- env?: string;
20
- public?: string;
21
- site?: string;
22
- triggers?: (string | number)[];
23
- routes?: (string | number)[];
24
- legacyEnv?: boolean;
21
+ format: CfScriptFormat | undefined;
22
+ script: string | undefined;
23
+ name: string | undefined;
24
+ env: string | undefined;
25
+ compatibilityDate: string | undefined;
26
+ compatibilityFlags: string[] | undefined;
27
+ assetPaths: AssetPaths | undefined;
28
+ triggers: (string | number)[] | undefined;
29
+ routes: (string | number)[] | undefined;
30
+ legacyEnv: boolean | undefined;
31
+ jsxFactory: undefined | string;
32
+ jsxFragment: undefined | string;
33
+ experimentalPublic: boolean;
25
34
  };
26
35
 
27
36
  function sleep(ms: number) {
@@ -29,10 +38,10 @@ function sleep(ms: number) {
29
38
  }
30
39
 
31
40
  export default async function publish(props: Props): Promise<void> {
32
- if (props.public && props.format === "service-worker") {
41
+ if (props.experimentalPublic && props.format === "service-worker") {
33
42
  // TODO: check config too
34
43
  throw new Error(
35
- "You cannot use the service worker format with a public directory."
44
+ "You cannot publish in the service worker format with a public directory."
36
45
  );
37
46
  }
38
47
  // TODO: warn if git/hg has uncommitted changes
@@ -44,65 +53,133 @@ export default async function publish(props: Props): Promise<void> {
44
53
  __path__,
45
54
  } = config;
46
55
 
56
+ const envRootObj =
57
+ props.env && config.env ? config.env[props.env] || {} : config;
58
+
59
+ assert(
60
+ envRootObj.compatibility_date || props.compatibilityDate,
61
+ "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."
62
+ );
63
+
64
+ if (accountId === undefined) {
65
+ throw new Error("No account_id provided.");
66
+ }
67
+
47
68
  const triggers = props.triggers || config.triggers?.crons;
48
69
  const routes = props.routes || config.routes;
49
70
 
71
+ const jsxFactory = props.jsxFactory || config.jsx_factory;
72
+ const jsxFragment = props.jsxFragment || config.jsx_fragment;
73
+
50
74
  assert(config.account_id, "missing account id");
51
75
 
76
+ let scriptName = props.name || config.name;
77
+ assert(
78
+ scriptName,
79
+ '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>"`'
80
+ );
81
+
82
+ if (config.site?.["entry-point"]) {
83
+ console.warn(
84
+ "Deprecation notice: The `site.entry-point` config field is no longer used.\n" +
85
+ "The entry-point should be specified via the command line (e.g. `wrangler publish path/to/script`) or the `build.upload.main` config field.\n" +
86
+ "Please remove the `site.entry-point` field from the `wrangler.toml` file."
87
+ );
88
+ }
89
+
90
+ assert(
91
+ !config.site || config.site.bucket,
92
+ "A [site] definition requires a `bucket` field with a path to the site's public directory."
93
+ );
94
+
52
95
  let file: string;
53
96
  if (props.script) {
54
- file = props.script;
55
- assert(props.name, "name is required when using script");
97
+ // If the script name comes from the command line it is relative to the current working directory.
98
+ file = path.resolve(props.script);
56
99
  } else {
57
- assert(build?.upload?.main, "missing main file");
58
- assert(config.name, "missing name");
59
- file = path.join(path.dirname(__path__), build.upload.main);
100
+ // If the script name comes from the config, then it is relative to the wrangler.toml file.
101
+ if (build?.upload?.main === undefined) {
102
+ throw new Error(
103
+ "Missing entry-point: The entry-point should be specified via the command line (e.g. `wrangler publish path/to/script`) or the `build.upload.main` config field."
104
+ );
105
+ }
106
+ file = path.resolve(path.dirname(__path__), build.upload.main);
60
107
  }
61
108
 
62
- let scriptName = props.script ? props.name : config.name;
63
109
  if (props.legacyEnv) {
64
110
  scriptName += props.env ? `-${props.env}` : "";
65
111
  }
66
112
  const envName = props.env ?? "production";
67
113
 
68
114
  const destination = await tmp.dir({ unsafeCleanup: true });
115
+ if (props.config.build?.command) {
116
+ // TODO: add a deprecation message here?
117
+ console.log("running:", props.config.build.command);
118
+ await execaCommand(props.config.build.command, {
119
+ shell: true,
120
+ stdout: "inherit",
121
+ stderr: "inherit",
122
+ ...(props.config.build?.cwd && { cwd: props.config.build.cwd }),
123
+ });
124
+ }
69
125
 
126
+ const moduleCollector = makeModuleCollector();
70
127
  const result = await esbuild.build({
71
- ...(props.public
128
+ ...(props.experimentalPublic
72
129
  ? {
73
130
  stdin: {
74
131
  contents: (
75
132
  await readFile(
76
- path.join(__dirname, "../static-asset-facade.js"),
133
+ path.join(__dirname, "../templates/static-asset-facade.js"),
77
134
  "utf8"
78
135
  )
79
- ).replace("__ENTRY_POINT__", path.join(process.cwd(), file)),
136
+ ).replace("__ENTRY_POINT__", file),
80
137
  sourcefile: "static-asset-facade.js",
81
138
  resolveDir: path.dirname(file),
82
139
  },
140
+ nodePaths: [path.join(__dirname, "../vendor")],
83
141
  }
84
142
  : { entryPoints: [file] }),
85
143
  bundle: true,
86
- nodePaths: props.public ? [path.join(__dirname, "../vendor")] : undefined,
87
144
  outdir: destination.path,
88
145
  external: ["__STATIC_CONTENT_MANIFEST"],
89
146
  format: "esm",
90
147
  sourcemap: true,
91
148
  metafile: true,
149
+ conditions: ["worker", "browser"],
150
+ loader: {
151
+ ".js": "jsx",
152
+ ".html": "text",
153
+ ".pem": "text",
154
+ ".txt": "text",
155
+ },
156
+ plugins: [moduleCollector.plugin],
157
+ ...(jsxFactory && { jsxFactory }),
158
+ ...(jsxFragment && { jsxFragment }),
92
159
  });
93
160
 
94
- const chunks = Object.entries(result.metafile.outputs).find(
95
- ([_path, { entryPoint }]) =>
96
- entryPoint ===
97
- (props.public
98
- ? path.join(path.dirname(file), "static-asset-facade.js")
99
- : file)
161
+ // result.metafile is defined because of the `metafile: true` option above.
162
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
163
+ const metafile = result.metafile!;
164
+ const entryPoints = Object.entries(metafile.outputs).filter(
165
+ ([_path, output]) => output.entryPoint !== undefined
100
166
  );
101
-
167
+ assert(
168
+ entryPoints.length > 0,
169
+ `Cannot find entry-point "${file}" in generated bundle.` +
170
+ listEntryPoints(entryPoints)
171
+ );
172
+ assert(
173
+ entryPoints.length < 2,
174
+ "More than one entry-point found for generated bundle." +
175
+ listEntryPoints(entryPoints)
176
+ );
177
+ const entryPointExports = entryPoints[0][1].exports;
178
+ const resolvedEntryPointPath = entryPoints[0][0];
102
179
  const { format } = props;
103
180
  const bundle = {
104
- type: chunks[1].exports.length > 0 ? "esm" : "commonjs",
105
- exports: chunks[1].exports,
181
+ type: entryPointExports.length > 0 ? "esm" : "commonjs",
182
+ exports: entryPointExports,
106
183
  };
107
184
 
108
185
  // TODO: instead of bundling the facade with the worker, we should just bundle the worker and expose it as a module.
@@ -119,54 +196,101 @@ export default async function publish(props: Props): Promise<void> {
119
196
  return;
120
197
  }
121
198
 
122
- const content = await readFile(chunks[0], { encoding: "utf-8" });
123
- destination.cleanup();
124
- const assets =
125
- props.public || props.site || props.config.site?.bucket // TODO: allow both
126
- ? await syncAssets(
127
- accountId,
128
- scriptName,
129
- props.public || props.site || props.config.site?.bucket,
130
- false
131
- )
132
- : { manifest: undefined, namespace: undefined };
199
+ let content = await readFile(resolvedEntryPointPath, { encoding: "utf-8" });
200
+ await destination.cleanup();
201
+
202
+ // if config.migrations
203
+ // get current migration tag
204
+ let migrations;
205
+ if (config.migrations !== undefined) {
206
+ const scripts = await fetchResult<{ id: string; migration_tag: string }[]>(
207
+ `/accounts/${accountId}/workers/scripts`
208
+ );
209
+ const script = scripts.find(({ id }) => id === scriptName);
210
+ if (script?.migration_tag) {
211
+ // was already published once
212
+ const foundIndex = config.migrations.findIndex(
213
+ (migration) => migration.tag === script.migration_tag
214
+ );
215
+ if (foundIndex === -1) {
216
+ console.warn(
217
+ `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...`
218
+ );
219
+ migrations = {
220
+ old_tag: script.migration_tag,
221
+ new_tag: config.migrations[config.migrations.length - 1].tag,
222
+ steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
223
+ };
224
+ } else {
225
+ migrations = {
226
+ old_tag: script.migration_tag,
227
+ new_tag: config.migrations[config.migrations.length - 1].tag,
228
+ steps: config.migrations
229
+ .slice(foundIndex + 1)
230
+ .map(({ tag: _tag, ...rest }) => rest),
231
+ };
232
+ }
233
+ } else {
234
+ migrations = {
235
+ new_tag: config.migrations[config.migrations.length - 1].tag,
236
+ steps: config.migrations.map(({ tag: _tag, ...rest }) => rest),
237
+ };
238
+ }
239
+ }
240
+
241
+ const assets = await syncAssets(
242
+ accountId,
243
+ scriptName,
244
+ props.assetPaths,
245
+ false,
246
+ props.env
247
+ );
133
248
 
134
- const envRootObj = props.env ? config[`env.${props.env}`] : config;
249
+ const bindings: CfWorkerInit["bindings"] = {
250
+ kv_namespaces: (envRootObj.kv_namespaces || []).concat(
251
+ assets.namespace
252
+ ? { binding: "__STATIC_CONTENT", id: assets.namespace }
253
+ : []
254
+ ),
255
+ vars: envRootObj.vars,
256
+ durable_objects: envRootObj.durable_objects,
257
+ services: envRootObj.experimental_services,
258
+ };
259
+
260
+ const workerType = bundle.type === "esm" ? "esm" : "commonjs";
261
+ if (workerType !== "esm" && assets.manifest) {
262
+ content = `const __STATIC_CONTENT_MANIFEST = ${JSON.stringify(
263
+ assets.manifest
264
+ )};\n${content}`;
265
+ }
135
266
 
136
267
  const worker: CfWorkerInit = {
268
+ name: scriptName,
137
269
  main: {
138
- name: scriptName,
270
+ name: path.basename(resolvedEntryPointPath),
139
271
  content: content,
140
- type:
141
- (bundle.type === "esm" ? "modules" : "service-worker") === "modules"
142
- ? "esm"
143
- : "commonjs",
144
- },
145
- variables: {
146
- ...(envRootObj?.vars || {}),
147
- ...(envRootObj?.kv_namespaces || []).reduce(
148
- (obj, { binding, preview_id, id }) => {
149
- return { ...obj, [binding]: { namespaceId: id } };
150
- },
151
- {}
152
- ),
153
- ...(assets.namespace
154
- ? { __STATIC_CONTENT: { namespaceId: assets.namespace } }
155
- : {}),
272
+ type: workerType,
156
273
  },
157
- modules: assets.manifest
158
- ? [].concat({
159
- name: "__STATIC_CONTENT_MANIFEST",
160
- content: JSON.stringify(assets.manifest),
161
- type: "text",
162
- })
163
- : [],
274
+ bindings,
275
+ ...(migrations && { migrations }),
276
+ modules: moduleCollector.modules.concat(
277
+ assets.manifest && workerType === "esm"
278
+ ? {
279
+ name: "__STATIC_CONTENT_MANIFEST",
280
+ content: JSON.stringify(assets.manifest),
281
+ type: "text",
282
+ }
283
+ : []
284
+ ),
285
+ compatibility_date: config.compatibility_date,
286
+ compatibility_flags: config.compatibility_flags,
287
+ usage_model: config.usage_model,
164
288
  };
165
289
 
166
290
  const start = Date.now();
167
- const formatTime = (duration) => {
291
+ function formatTime(duration: number) {
168
292
  return `(${(duration / 1000).toFixed(2)} sec)`;
169
- };
293
+ }
170
294
 
171
295
  const notProd = !props.legacyEnv && props.env;
172
296
  const workerName = notProd ? `${scriptName} (${envName})` : scriptName;
@@ -174,14 +298,14 @@ export default async function publish(props: Props): Promise<void> {
174
298
  ? `/accounts/${accountId}/workers/services/${scriptName}/environments/${envName}`
175
299
  : `/accounts/${accountId}/workers/scripts/${scriptName}`;
176
300
 
177
- // Upload the script so it has time to propogate.
178
- const { available_on_subdomain } = await cfetch(
179
- `${workerUrl}?available_on_subdomain=true`,
301
+ // Upload the script so it has time to propagate.
302
+ const { available_on_subdomain } = await fetchResult(
303
+ workerUrl,
180
304
  {
181
305
  method: "PUT",
182
- // @ts-expect-error: TODO: fix this type error!
183
306
  body: toFormData(worker),
184
- }
307
+ },
308
+ new URLSearchParams({ available_on_subdomains: "true" })
185
309
  );
186
310
 
187
311
  const uploadMs = Date.now() - start;
@@ -189,7 +313,7 @@ export default async function publish(props: Props): Promise<void> {
189
313
  const deployments: Promise<string[]>[] = [];
190
314
 
191
315
  const userSubdomain = (
192
- await cfetch<{ subdomain: string }>(
316
+ await fetchResult<{ subdomain: string }>(
193
317
  `/accounts/${accountId}/workers/subdomain`
194
318
  )
195
319
  ).subdomain;
@@ -203,7 +327,7 @@ export default async function publish(props: Props): Promise<void> {
203
327
  // TODO: Make this configurable.
204
328
  if (!available_on_subdomain) {
205
329
  deployments.push(
206
- cfetch(`${workerUrl}/subdomain`, {
330
+ fetchResult(`${workerUrl}/subdomain`, {
207
331
  method: "POST",
208
332
  body: JSON.stringify({ enabled: true }),
209
333
  headers: {
@@ -215,8 +339,8 @@ export default async function publish(props: Props): Promise<void> {
215
339
  // This is to prevent an issue where a negative cache-hit
216
340
  // causes the subdomain to be unavailable for 30 seconds.
217
341
  // This is a temporary measure until we fix this on the edge.
218
- .then((url) => {
219
- sleep(3000);
342
+ .then(async (url) => {
343
+ await sleep(3000);
220
344
  return url;
221
345
  })
222
346
  );
@@ -227,7 +351,7 @@ export default async function publish(props: Props): Promise<void> {
227
351
  // Update routing table for the script.
228
352
  if (routes && routes.length) {
229
353
  deployments.push(
230
- cfetch(`${workerUrl}/routes`, {
354
+ fetchResult(`${workerUrl}/routes`, {
231
355
  // TODO: PATCH will not delete previous routes on this script,
232
356
  // whereas PUT will. We need to decide on the default behaviour
233
357
  // and how to configure it.
@@ -252,7 +376,7 @@ export default async function publish(props: Props): Promise<void> {
252
376
  // TODO: rename this to `schedules`?
253
377
  if (triggers && triggers.length) {
254
378
  deployments.push(
255
- cfetch(`${workerUrl}/schedules`, {
379
+ fetchResult(`${workerUrl}/schedules`, {
256
380
  // TODO: Unlike routes, this endpoint does not support PATCH.
257
381
  // So technically, this will override any previous schedules.
258
382
  // We should change the endpoint to support PATCH.
@@ -276,3 +400,11 @@ export default async function publish(props: Props): Promise<void> {
276
400
  console.log(" ", target);
277
401
  }
278
402
  }
403
+
404
+ function listEntryPoints(
405
+ outputs: [string, ValueOf<Metafile["outputs"]>][]
406
+ ): string {
407
+ return outputs.map(([_input, output]) => output.entryPoint).join("\n");
408
+ }
409
+
410
+ type ValueOf<T> = T[keyof T];