wrangler 2.0.19 → 2.0.23

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.
package/src/dev.tsx CHANGED
@@ -22,10 +22,12 @@ import {
22
22
  getDevCompatibilityDate,
23
23
  getRules,
24
24
  isLegacyEnv,
25
+ DEFAULT_INSPECTOR_PORT,
25
26
  } from "./index";
26
27
 
27
28
  import type { Config } from "./config";
28
29
  import type { Route } from "./config/environment";
30
+ import type { EnablePagesAssetsServiceBindingOptions } from "./miniflare-cli";
29
31
  import type { CfWorkerInit } from "./worker";
30
32
  import type { RequestInit } from "undici";
31
33
  import type { Argv, ArgumentsCamelCase } from "yargs";
@@ -63,8 +65,10 @@ interface DevArgs {
63
65
  minify?: boolean;
64
66
  "node-compat"?: boolean;
65
67
  "experimental-enable-local-persistence"?: boolean;
68
+ "live-reload"?: boolean;
66
69
  onReady?: () => void;
67
70
  logLevel?: "none" | "error" | "log" | "warn" | "debug";
71
+ logPrefix?: string;
68
72
  showInteractiveDevSession?: boolean;
69
73
  }
70
74
 
@@ -225,6 +229,12 @@ export function devOptions(yargs: Argv): Argv<DevArgs> {
225
229
  describe: "Enable persistence for this session (only for local mode)",
226
230
  type: "boolean",
227
231
  })
232
+ .option("live-reload", {
233
+ // TODO: Add back in once we have remote `--live-reload`
234
+ hidden: true,
235
+ // describe: "Auto reload HTML pages when change is detected",
236
+ type: "boolean",
237
+ })
228
238
  .option("inspect", {
229
239
  describe: "Enable dev tools",
230
240
  type: "boolean",
@@ -250,7 +260,28 @@ export async function devHandler(args: ArgumentsCamelCase<DevArgs>) {
250
260
  }
251
261
  }
252
262
 
253
- export async function startDev(args: ArgumentsCamelCase<DevArgs>) {
263
+ type StartDevOptions = ArgumentsCamelCase<DevArgs> & {
264
+ // These options can be passed in directly when called with the `wrangler.dev()` API.
265
+ // They aren't exposed as CLI arguments.
266
+ vars?: {
267
+ [key: string]: unknown;
268
+ };
269
+ kv?: {
270
+ binding: string;
271
+ id: string;
272
+ preview_id?: string;
273
+ }[];
274
+ durableObjects?: {
275
+ name: string;
276
+ class_name: string;
277
+ script_name?: string | undefined;
278
+ environment?: string | undefined;
279
+ }[];
280
+ forceLocal?: boolean;
281
+ enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
282
+ };
283
+
284
+ export async function startDev(args: StartDevOptions) {
254
285
  let watcher: ReturnType<typeof watch> | undefined;
255
286
  let rerender: (node: React.ReactNode) => void | undefined;
256
287
  try {
@@ -265,7 +296,7 @@ export async function startDev(args: ArgumentsCamelCase<DevArgs>) {
265
296
  ((args.script &&
266
297
  findWranglerToml(path.dirname(args.script))) as ConfigPath);
267
298
  let config = readConfig(configPath, args);
268
- metrics.sendMetricsEvent(
299
+ await metrics.sendMetricsEvent(
269
300
  "run dev",
270
301
  { local: args.local },
271
302
  { sendMetrics: config.send_metrics, offline: args.local }
@@ -351,6 +382,10 @@ export async function startDev(args: ArgumentsCamelCase<DevArgs>) {
351
382
  const routes: Route[] | undefined =
352
383
  args.routes || (config.route && [config.route]) || config.routes;
353
384
 
385
+ if (args.forceLocal) {
386
+ args.local = true;
387
+ }
388
+
354
389
  if (!args.local) {
355
390
  if (host) {
356
391
  zoneId = await getZoneIdFromHost(host);
@@ -370,7 +405,7 @@ export async function startDev(args: ArgumentsCamelCase<DevArgs>) {
370
405
  }
371
406
  }
372
407
 
373
- const nodeCompat = args["node-compat"] ?? config.node_compat;
408
+ const nodeCompat = args.nodeCompat ?? config.node_compat;
374
409
  if (nodeCompat) {
375
410
  logger.warn(
376
411
  "Enabling node.js compatibility mode for built-ins and globals. This is experimental and has serious tradeoffs. Please see https://github.com/ionic-team/rollup-plugin-node-polyfills/ for more details."
@@ -382,33 +417,44 @@ export async function startDev(args: ArgumentsCamelCase<DevArgs>) {
382
417
  configParam: Config
383
418
  ): Promise<CfWorkerInit["bindings"]> {
384
419
  return {
385
- kv_namespaces: configParam.kv_namespaces?.map(
386
- ({ binding, preview_id, id: _id }) => {
387
- // In `dev`, we make folks use a separate kv namespace called
388
- // `preview_id` instead of `id` so that they don't
389
- // break production data. So here we check that a `preview_id`
390
- // has actually been configured.
391
- // This whole block of code will be obsoleted in the future
392
- // when we have copy-on-write for previews on edge workers.
393
- if (!preview_id) {
394
- // TODO: This error has to be a _lot_ better, ideally just asking
395
- // to create a preview namespace for the user automatically
396
- throw new Error(
397
- `In development, you should use a separate kv namespace than the one you'd use in production. Please create a new kv namespace with "wrangler kv:namespace create <name> --preview" and add its id as preview_id to the kv_namespace "${binding}" in your wrangler.toml`
398
- ); // Ugh, I really don't like this message very much
420
+ kv_namespaces: [
421
+ ...(configParam.kv_namespaces || []).map(
422
+ ({ binding, preview_id, id: _id }) => {
423
+ // In `dev`, we make folks use a separate kv namespace called
424
+ // `preview_id` instead of `id` so that they don't
425
+ // break production data. So here we check that a `preview_id`
426
+ // has actually been configured.
427
+ // This whole block of code will be obsoleted in the future
428
+ // when we have copy-on-write for previews on edge workers.
429
+ if (!preview_id) {
430
+ // TODO: This error has to be a _lot_ better, ideally just asking
431
+ // to create a preview namespace for the user automatically
432
+ throw new Error(
433
+ `In development, you should use a separate kv namespace than the one you'd use in production. Please create a new kv namespace with "wrangler kv:namespace create <name> --preview" and add its id as preview_id to the kv_namespace "${binding}" in your wrangler.toml`
434
+ ); // Ugh, I really don't like this message very much
435
+ }
436
+ return {
437
+ binding,
438
+ id: preview_id,
439
+ };
399
440
  }
400
- return {
401
- binding,
402
- id: preview_id,
403
- };
404
- }
405
- ),
441
+ ),
442
+ ...(args.kv || []),
443
+ ],
406
444
  // Use a copy of combinedVars since we're modifying it later
407
- vars: getVarsForDev(configParam),
445
+ vars: {
446
+ ...getVarsForDev(configParam),
447
+ ...args.vars,
448
+ },
408
449
  wasm_modules: configParam.wasm_modules,
409
450
  text_blobs: configParam.text_blobs,
410
451
  data_blobs: configParam.data_blobs,
411
- durable_objects: configParam.durable_objects,
452
+ durable_objects: {
453
+ bindings: [
454
+ ...(configParam.durable_objects || { bindings: [] }).bindings,
455
+ ...(args.durableObjects || []),
456
+ ],
457
+ },
412
458
  r2_buckets: configParam.r2_buckets?.map(
413
459
  ({ binding, preview_bucket_name, bucket_name: _bucket_name }) => {
414
460
  // same idea as kv namespace preview id,
@@ -431,7 +477,7 @@ export async function startDev(args: ArgumentsCamelCase<DevArgs>) {
431
477
  }
432
478
 
433
479
  const getLocalPort = memoizeGetPort(DEFAULT_LOCAL_PORT);
434
- const getInspectorPort = memoizeGetPort(9229);
480
+ const getInspectorPort = memoizeGetPort(DEFAULT_INSPECTOR_PORT);
435
481
 
436
482
  // eslint-disable-next-line no-inner-declarations
437
483
  async function getDevReactElement(configParam: Config) {
@@ -483,16 +529,21 @@ export async function startDev(args: ArgumentsCamelCase<DevArgs>) {
483
529
  jsxFragment={args["jsx-fragment"] || config.jsx_fragment}
484
530
  tsconfig={args.tsconfig ?? config.tsconfig}
485
531
  upstreamProtocol={upstreamProtocol}
486
- localProtocol={args["local-protocol"] || config.dev.local_protocol}
532
+ localProtocol={args.localProtocol || config.dev.local_protocol}
487
533
  localUpstream={args["local-upstream"] || host}
488
534
  enableLocalPersistence={
489
- args["experimental-enable-local-persistence"] || false
535
+ args.experimentalEnableLocalPersistence || false
490
536
  }
537
+ liveReload={args.liveReload || false}
491
538
  accountId={config.account_id || getAccountFromCache()?.id}
492
539
  assetPaths={assetPaths}
493
540
  port={args.port || config.dev.port || (await getLocalPort())}
494
541
  ip={args.ip || config.dev.ip}
495
- inspectorPort={args["inspector-port"] ?? (await getInspectorPort())}
542
+ inspectorPort={
543
+ args["inspector-port"] ||
544
+ config.dev.inspector_port ||
545
+ (await getInspectorPort())
546
+ }
496
547
  isWorkersSite={Boolean(args.site || config.site)}
497
548
  compatibilityDate={getDevCompatibilityDate(
498
549
  config,
@@ -505,9 +556,12 @@ export async function startDev(args: ArgumentsCamelCase<DevArgs>) {
505
556
  bindings={bindings}
506
557
  crons={config.triggers.crons}
507
558
  logLevel={args.logLevel}
559
+ logPrefix={args.logPrefix}
508
560
  onReady={args.onReady}
509
- inspect={args.inspect}
561
+ inspect={args.inspect ?? true}
510
562
  showInteractiveDevSession={args.showInteractiveDevSession}
563
+ forceLocal={args.forceLocal}
564
+ enablePagesAssetsServiceBinding={args.enablePagesAssetsServiceBinding}
511
565
  />
512
566
  );
513
567
  }
package/src/index.tsx CHANGED
@@ -76,6 +76,7 @@ export type ConfigPath = string | undefined;
76
76
  const resetColor = "\x1b[0m";
77
77
  const fgGreenColor = "\x1b[32m";
78
78
  export const DEFAULT_LOCAL_PORT = 8787;
79
+ export const DEFAULT_INSPECTOR_PORT = 9229;
79
80
 
80
81
  const proxy =
81
82
  process.env.https_proxy ||
@@ -258,11 +259,22 @@ function createCLIParser(argv: string[]) {
258
259
  ["*"],
259
260
  false,
260
261
  () => {},
261
- (args) => {
262
+ async (args) => {
262
263
  if (args._.length > 0) {
263
264
  throw new CommandLineArgsError(`Unknown command: ${args._}.`);
264
265
  } else {
265
- wrangler.showHelp("log");
266
+ // args.v will exist and be true in the case that no command is called, and the -v
267
+ // option is present. This is to allow for running asynchronous printWranglerBanner
268
+ // in the version command.
269
+ if (args.v) {
270
+ if (process.stdout.isTTY) {
271
+ await printWranglerBanner();
272
+ } else {
273
+ logger.log(wranglerVersion);
274
+ }
275
+ } else {
276
+ wrangler.showHelp("log");
277
+ }
266
278
  }
267
279
  }
268
280
  );
@@ -501,7 +513,7 @@ function createCLIParser(argv: string[]) {
501
513
  (args.config as ConfigPath) ||
502
514
  (args.script && findWranglerToml(path.dirname(args.script)));
503
515
  const config = readConfig(configPath, args);
504
- metrics.sendMetricsEvent("deploy worker script", {
516
+ await metrics.sendMetricsEvent("deploy worker script", {
505
517
  sendMetrics: config.send_metrics,
506
518
  });
507
519
  const entry = await getEntry(args, config, "publish");
@@ -644,7 +656,7 @@ function createCLIParser(argv: string[]) {
644
656
  await printWranglerBanner();
645
657
  }
646
658
  const config = readConfig(args.config as ConfigPath, args);
647
- metrics.sendMetricsEvent("begin log stream", {
659
+ await metrics.sendMetricsEvent("begin log stream", {
648
660
  sendMetrics: config.send_metrics,
649
661
  });
650
662
 
@@ -692,7 +704,7 @@ function createCLIParser(argv: string[]) {
692
704
  onExit(async () => {
693
705
  tail.terminate();
694
706
  await deleteTail();
695
- metrics.sendMetricsEvent("end log stream", {
707
+ await metrics.sendMetricsEvent("end log stream", {
696
708
  sendMetrics: config.send_metrics,
697
709
  });
698
710
  });
@@ -711,7 +723,7 @@ function createCLIParser(argv: string[]) {
711
723
  await setTimeout(100);
712
724
  break;
713
725
  case tail.CLOSED:
714
- metrics.sendMetricsEvent("end log stream", {
726
+ await metrics.sendMetricsEvent("end log stream", {
715
727
  sendMetrics: config.send_metrics,
716
728
  });
717
729
  throw new Error(
@@ -727,7 +739,7 @@ function createCLIParser(argv: string[]) {
727
739
  tail.on("close", async () => {
728
740
  tail.terminate();
729
741
  await deleteTail();
730
- metrics.sendMetricsEvent("end log stream", {
742
+ await metrics.sendMetricsEvent("end log stream", {
731
743
  sendMetrics: config.send_metrics,
732
744
  });
733
745
  });
@@ -960,7 +972,7 @@ function createCLIParser(argv: string[]) {
960
972
 
961
973
  try {
962
974
  await submitSecret();
963
- metrics.sendMetricsEvent("create encrypted variable", {
975
+ await metrics.sendMetricsEvent("create encrypted variable", {
964
976
  sendMetrics: config.send_metrics,
965
977
  });
966
978
  } catch (e) {
@@ -1035,7 +1047,7 @@ function createCLIParser(argv: string[]) {
1035
1047
  : `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1036
1048
 
1037
1049
  await fetchResult(`${url}/${args.key}`, { method: "DELETE" });
1038
- metrics.sendMetricsEvent("delete encrypted variable", {
1050
+ await metrics.sendMetricsEvent("delete encrypted variable", {
1039
1051
  sendMetrics: config.send_metrics,
1040
1052
  });
1041
1053
  logger.log(`✨ Success! Deleted secret ${args.key}`);
@@ -1078,7 +1090,7 @@ function createCLIParser(argv: string[]) {
1078
1090
  : `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1079
1091
 
1080
1092
  logger.log(JSON.stringify(await fetchResult(url), null, " "));
1081
- metrics.sendMetricsEvent("list encrypted variables", {
1093
+ await metrics.sendMetricsEvent("list encrypted variables", {
1082
1094
  sendMetrics: config.send_metrics,
1083
1095
  });
1084
1096
  }
@@ -1142,7 +1154,7 @@ function createCLIParser(argv: string[]) {
1142
1154
 
1143
1155
  logger.log(`🌀 Creating namespace with title "${title}"`);
1144
1156
  const namespaceId = await createKVNamespace(accountId, title);
1145
- metrics.sendMetricsEvent("create kv namespace", {
1157
+ await metrics.sendMetricsEvent("create kv namespace", {
1146
1158
  sendMetrics: config.send_metrics,
1147
1159
  });
1148
1160
 
@@ -1173,7 +1185,7 @@ function createCLIParser(argv: string[]) {
1173
1185
  logger.log(
1174
1186
  JSON.stringify(await listKVNamespaces(accountId), null, " ")
1175
1187
  );
1176
- metrics.sendMetricsEvent("list kv namespaces", {
1188
+ await metrics.sendMetricsEvent("list kv namespaces", {
1177
1189
  sendMetrics: config.send_metrics,
1178
1190
  });
1179
1191
  }
@@ -1220,8 +1232,10 @@ function createCLIParser(argv: string[]) {
1220
1232
 
1221
1233
  const accountId = await requireAuth(config);
1222
1234
 
1235
+ logger.log(`Deleting KV namespace ${id}.`);
1223
1236
  await deleteKVNamespace(accountId, id);
1224
- metrics.sendMetricsEvent("delete kv namespace", {
1237
+ logger.log(`Deleted KV namespace ${id}.`);
1238
+ await metrics.sendMetricsEvent("delete kv namespace", {
1225
1239
  sendMetrics: config.send_metrics,
1226
1240
  });
1227
1241
 
@@ -1347,7 +1361,7 @@ function createCLIParser(argv: string[]) {
1347
1361
  expiration_ttl: ttl,
1348
1362
  metadata: metadata as KeyValue["metadata"],
1349
1363
  });
1350
- metrics.sendMetricsEvent("write kv key-value", {
1364
+ await metrics.sendMetricsEvent("write kv key-value", {
1351
1365
  sendMetrics: config.send_metrics,
1352
1366
  });
1353
1367
  }
@@ -1399,7 +1413,7 @@ function createCLIParser(argv: string[]) {
1399
1413
  prefix
1400
1414
  );
1401
1415
  logger.log(JSON.stringify(results, undefined, 2));
1402
- metrics.sendMetricsEvent("list kv keys", {
1416
+ await metrics.sendMetricsEvent("list kv keys", {
1403
1417
  sendMetrics: config.send_metrics,
1404
1418
  });
1405
1419
  }
@@ -1462,7 +1476,7 @@ function createCLIParser(argv: string[]) {
1462
1476
  } else {
1463
1477
  process.stdout.write(bufferKVValue);
1464
1478
  }
1465
- metrics.sendMetricsEvent("read kv value", {
1479
+ await metrics.sendMetricsEvent("read kv value", {
1466
1480
  sendMetrics: config.send_metrics,
1467
1481
  });
1468
1482
  }
@@ -1511,7 +1525,7 @@ function createCLIParser(argv: string[]) {
1511
1525
  const accountId = await requireAuth(config);
1512
1526
 
1513
1527
  await deleteKVKeyValue(accountId, namespaceId, key);
1514
- metrics.sendMetricsEvent("delete kv key-value", {
1528
+ await metrics.sendMetricsEvent("delete kv key-value", {
1515
1529
  sendMetrics: config.send_metrics,
1516
1530
  });
1517
1531
  }
@@ -1618,7 +1632,7 @@ function createCLIParser(argv: string[]) {
1618
1632
 
1619
1633
  const accountId = await requireAuth(config);
1620
1634
  await putKVBulkKeyValue(accountId, namespaceId, content);
1621
- metrics.sendMetricsEvent("write kv key-values (bulk)", {
1635
+ await metrics.sendMetricsEvent("write kv key-values (bulk)", {
1622
1636
  sendMetrics: config.send_metrics,
1623
1637
  });
1624
1638
 
@@ -1711,7 +1725,7 @@ function createCLIParser(argv: string[]) {
1711
1725
  const accountId = await requireAuth(config);
1712
1726
 
1713
1727
  await deleteKVBulkKeyValue(accountId, namespaceId, content);
1714
- metrics.sendMetricsEvent("delete kv key-values (bulk)", {
1728
+ await metrics.sendMetricsEvent("delete kv key-values (bulk)", {
1715
1729
  sendMetrics: config.send_metrics,
1716
1730
  });
1717
1731
 
@@ -1753,7 +1767,7 @@ function createCLIParser(argv: string[]) {
1753
1767
  logger.log(`Creating bucket ${args.name}.`);
1754
1768
  await createR2Bucket(accountId, args.name);
1755
1769
  logger.log(`Created bucket ${args.name}.`);
1756
- metrics.sendMetricsEvent("create r2 bucket", {
1770
+ await metrics.sendMetricsEvent("create r2 bucket", {
1757
1771
  sendMetrics: config.send_metrics,
1758
1772
  });
1759
1773
  }
@@ -1765,7 +1779,7 @@ function createCLIParser(argv: string[]) {
1765
1779
  const accountId = await requireAuth(config);
1766
1780
 
1767
1781
  logger.log(JSON.stringify(await listR2Buckets(accountId), null, 2));
1768
- metrics.sendMetricsEvent("list r2 buckets", {
1782
+ await metrics.sendMetricsEvent("list r2 buckets", {
1769
1783
  sendMetrics: config.send_metrics,
1770
1784
  });
1771
1785
  });
@@ -1790,7 +1804,7 @@ function createCLIParser(argv: string[]) {
1790
1804
  logger.log(`Deleting bucket ${args.name}.`);
1791
1805
  await deleteR2Bucket(accountId, args.name);
1792
1806
  logger.log(`Deleted bucket ${args.name}.`);
1793
- metrics.sendMetricsEvent("delete r2 bucket", {
1807
+ await metrics.sendMetricsEvent("delete r2 bucket", {
1794
1808
  sendMetrics: config.send_metrics,
1795
1809
  });
1796
1810
  }
@@ -1862,7 +1876,7 @@ function createCLIParser(argv: string[]) {
1862
1876
  }
1863
1877
  await login();
1864
1878
  const config = readConfig(args.config as ConfigPath, args);
1865
- metrics.sendMetricsEvent("login user", {
1879
+ await metrics.sendMetricsEvent("login user", {
1866
1880
  sendMetrics: config.send_metrics,
1867
1881
  });
1868
1882
 
@@ -1882,7 +1896,7 @@ function createCLIParser(argv: string[]) {
1882
1896
  await printWranglerBanner();
1883
1897
  await logout();
1884
1898
  const config = readConfig(undefined, {});
1885
- metrics.sendMetricsEvent("logout user", {
1899
+ await metrics.sendMetricsEvent("logout user", {
1886
1900
  sendMetrics: config.send_metrics,
1887
1901
  });
1888
1902
  }
@@ -1897,12 +1911,35 @@ function createCLIParser(argv: string[]) {
1897
1911
  await printWranglerBanner();
1898
1912
  await whoami();
1899
1913
  const config = readConfig(undefined, {});
1900
- metrics.sendMetricsEvent("view accounts", {
1914
+ await metrics.sendMetricsEvent("view accounts", {
1901
1915
  sendMetrics: config.send_metrics,
1902
1916
  });
1903
1917
  }
1904
1918
  );
1905
1919
 
1920
+ // This set to false to allow overwrite of default behaviour
1921
+ wrangler.version(false);
1922
+
1923
+ // version
1924
+ wrangler.command(
1925
+ "version",
1926
+ false,
1927
+ () => {},
1928
+ async () => {
1929
+ if (process.stdout.isTTY) {
1930
+ await printWranglerBanner();
1931
+ } else {
1932
+ logger.log(wranglerVersion);
1933
+ }
1934
+ }
1935
+ );
1936
+
1937
+ wrangler.option("v", {
1938
+ describe: "Show version number",
1939
+ alias: "version",
1940
+ type: "boolean",
1941
+ });
1942
+
1906
1943
  wrangler.option("config", {
1907
1944
  alias: "c",
1908
1945
  describe: "Path to .toml configuration file",
@@ -1912,7 +1949,7 @@ function createCLIParser(argv: string[]) {
1912
1949
 
1913
1950
  wrangler.group(["config", "help", "version"], "Flags:");
1914
1951
  wrangler.help().alias("h", "help");
1915
- wrangler.version(wranglerVersion).alias("v", "version");
1952
+
1916
1953
  wrangler.exitProcess(false);
1917
1954
 
1918
1955
  return wrangler;
@@ -0,0 +1,14 @@
1
+ import isCI from "is-ci";
2
+
3
+ /**
4
+ * Use this object to find out if we are currently running in a continuous integration environment.
5
+ *
6
+ * The isCI constant imported above cannot be easily mocked for testing.
7
+ * By wrapping this up in a method on an object, it results in clean and testable code.
8
+ */
9
+ export const CI = {
10
+ /** Is Wrangler currently running in a CI? */
11
+ isCI() {
12
+ return isCI;
13
+ },
14
+ };
@@ -1,13 +1,15 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { readFileSync, mkdirSync, writeFileSync } from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { fetchResult } from "../cfetch";
6
6
  import { getConfigCache, saveToConfigCache } from "../config-cache";
7
7
  import { confirm } from "../dialogs";
8
+ import { getEnvironmentVariableFactory } from "../environment-variables";
8
9
  import isInteractive from "../is-interactive";
9
10
  import { logger } from "../logger";
10
11
  import { getAPIToken } from "../user";
12
+ import { CI } from "./is-ci";
11
13
 
12
14
  /**
13
15
  * The date that the metrics being gathered was last updated in a way that would require
@@ -21,6 +23,10 @@ import { getAPIToken } from "../user";
21
23
  export const CURRENT_METRICS_DATE = new Date(2022, 6, 4);
22
24
  export const USER_ID_CACHE_PATH = "user-id.json";
23
25
 
26
+ export const getWranglerSendMetricsFromEnv = getEnvironmentVariableFactory({
27
+ variableName: "WRANGLER_SEND_METRICS",
28
+ });
29
+
24
30
  export interface MetricsConfigOptions {
25
31
  /**
26
32
  * Defines whether to send metrics to Cloudflare:
@@ -66,10 +72,21 @@ export async function getMetricsConfig({
66
72
  sendMetrics,
67
73
  offline = false,
68
74
  }: MetricsConfigOptions): Promise<MetricsConfig> {
69
- const config = await readMetricsConfig();
70
- const deviceId = await getDeviceId(config);
75
+ const config = readMetricsConfig();
76
+ const deviceId = getDeviceId(config);
71
77
  const userId = await getUserId(offline);
72
78
 
79
+ // If the WRANGLER_SEND_METRICS environment variable has been set use that
80
+ // and ignore everything else.
81
+ const sendMetricsEnv = getWranglerSendMetricsFromEnv();
82
+ if (sendMetricsEnv !== undefined) {
83
+ return {
84
+ enabled: sendMetricsEnv.toLowerCase() === "true",
85
+ deviceId,
86
+ userId,
87
+ };
88
+ }
89
+
73
90
  // If the project is explicitly set the `send_metrics` options in `wrangler.toml`
74
91
  // then use that and ignore any user preference.
75
92
  if (sendMetrics !== undefined) {
@@ -89,8 +106,8 @@ export async function getMetricsConfig({
89
106
  }
90
107
 
91
108
  // We couldn't get the metrics permission from the project-level nor the user-level config.
92
- // If we are not interactive then just bail out.
93
- if (!isInteractive()) {
109
+ // If we are not interactive or in a CI build then just bail out.
110
+ if (!isInteractive() || CI.isCI()) {
94
111
  return { enabled: false, deviceId, userId };
95
112
  }
96
113
 
@@ -107,7 +124,7 @@ export async function getMetricsConfig({
107
124
  " - to disable sending metrics for a project: `send_metrics = false`\n" +
108
125
  " - to enable sending metrics for a project: `send_metrics = true`"
109
126
  );
110
- await writeMetricsConfig({
127
+ writeMetricsConfig({
111
128
  permission: {
112
129
  enabled,
113
130
  date: CURRENT_METRICS_DATE,
@@ -120,9 +137,9 @@ export async function getMetricsConfig({
120
137
  /**
121
138
  * Stringify and write the given info to the metrics config file.
122
139
  */
123
- export async function writeMetricsConfig(config: MetricsConfigFile) {
124
- await mkdir(path.dirname(getMetricsConfigPath()), { recursive: true });
125
- await writeFile(
140
+ export function writeMetricsConfig(config: MetricsConfigFile) {
141
+ mkdirSync(path.dirname(getMetricsConfigPath()), { recursive: true });
142
+ writeFileSync(
126
143
  getMetricsConfigPath(),
127
144
  JSON.stringify(
128
145
  config,
@@ -135,11 +152,11 @@ export async function writeMetricsConfig(config: MetricsConfigFile) {
135
152
  /**
136
153
  * Read and parse the metrics config file.
137
154
  */
138
- export async function readMetricsConfig(): Promise<MetricsConfigFile> {
155
+ export function readMetricsConfig(): MetricsConfigFile {
139
156
  try {
140
- return JSON.parse(
141
- await readFile(getMetricsConfigPath(), "utf8"),
142
- (key, value) => (key === "date" ? new Date(value) : value)
157
+ const config = readFileSync(getMetricsConfigPath(), "utf8");
158
+ return JSON.parse(config, (key, value) =>
159
+ key === "date" ? new Date(value) : value
143
160
  );
144
161
  } catch {
145
162
  return {};
@@ -172,12 +189,12 @@ export interface MetricsConfigFile {
172
189
  *
173
190
  * Once created this ID is stored in the metrics config file.
174
191
  */
175
- async function getDeviceId(config: MetricsConfigFile) {
192
+ function getDeviceId(config: MetricsConfigFile) {
176
193
  // Get or create the deviceId.
177
194
  const deviceId = config.deviceId ?? randomUUID();
178
195
  if (config.deviceId === undefined) {
179
196
  // We had to create a new deviceID so store it now.
180
- await writeMetricsConfig({ ...config, deviceId });
197
+ writeMetricsConfig({ ...config, deviceId });
181
198
  }
182
199
  return deviceId;
183
200
  }
@@ -47,6 +47,8 @@ export async function getMetricsDispatcher(options: MetricsConfigOptions) {
47
47
  }
48
48
 
49
49
  // Lazily get the config for this dispatcher only when an event is being dispatched.
50
+ // We must await this since it might trigger user interaction that would break other UI
51
+ // in Wrangler if it was allowed to run in parallel.
50
52
  const metricsConfig = await getMetricsConfig(options);
51
53
  if (!metricsConfig.enabled) {
52
54
  logger.debug(
@@ -57,36 +59,36 @@ export async function getMetricsDispatcher(options: MetricsConfigOptions) {
57
59
  return;
58
60
  }
59
61
 
60
- try {
61
- logger.debug(`Metrics dispatcher: Posting data ${JSON.stringify(event)}`);
62
- const body = JSON.stringify({
63
- deviceId: metricsConfig.deviceId,
64
- userId: metricsConfig.userId,
65
- event: event.name,
66
- properties: {
67
- category: "Workers",
68
- wranglerVersion,
69
- ...event.properties,
70
- },
71
- });
62
+ logger.debug(`Metrics dispatcher: Posting data ${JSON.stringify(event)}`);
63
+ const body = JSON.stringify({
64
+ deviceId: metricsConfig.deviceId,
65
+ userId: metricsConfig.userId,
66
+ event: event.name,
67
+ properties: {
68
+ category: "Workers",
69
+ wranglerVersion,
70
+ ...event.properties,
71
+ },
72
+ });
72
73
 
73
- await fetch(`${SPARROW_URL}/api/v1/${event.type}`, {
74
- method: "POST",
75
- headers: {
76
- Accept: "*/*",
77
- "Content-Type": "application/json",
78
- "Sparrow-Source-Key": SPARROW_SOURCE_KEY,
79
- },
80
- mode: "cors",
81
- keepalive: true,
82
- body,
83
- });
84
- } catch (e) {
74
+ // Do not await this fetch call.
75
+ // Just fire-and-forget, otherwise we might slow down the rest of Wrangler.
76
+ fetch(`${SPARROW_URL}/api/v1/${event.type}`, {
77
+ method: "POST",
78
+ headers: {
79
+ Accept: "*/*",
80
+ "Content-Type": "application/json",
81
+ "Sparrow-Source-Key": SPARROW_SOURCE_KEY,
82
+ },
83
+ mode: "cors",
84
+ keepalive: true,
85
+ body,
86
+ }).catch((e) => {
85
87
  logger.debug(
86
88
  "Metrics dispatcher: Failed to send request:",
87
89
  (e as Error).message
88
90
  );
89
- }
91
+ });
90
92
  }
91
93
  }
92
94