wrangler 0.0.0-e7cf0caa → 0.0.0-e87ad068

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 (85) hide show
  1. package/miniflare-dist/index.mjs +167 -93
  2. package/package.json +6 -6
  3. package/src/__tests__/configuration.test.ts +218 -0
  4. package/src/__tests__/constellation.test.ts +371 -0
  5. package/src/__tests__/d1/migrate.test.ts +100 -0
  6. package/src/__tests__/index.test.ts +4 -2
  7. package/src/__tests__/init.test.ts +109 -3
  8. package/src/__tests__/mtls-certificates.test.ts +2 -1
  9. package/src/__tests__/pages/functions-build.test.ts +77 -0
  10. package/src/__tests__/pages-deployment-tail.test.ts +2 -0
  11. package/src/__tests__/publish.test.ts +646 -187
  12. package/src/__tests__/tail.test.ts +49 -7
  13. package/src/__tests__/tsconfig.tsbuildinfo +1 -1
  14. package/src/__tests__/unstableDev.test.ts +61 -0
  15. package/src/__tests__/user.test.ts +1 -1
  16. package/src/api/dev.ts +12 -0
  17. package/src/api/pages/create-worker-bundle-contents.ts +4 -0
  18. package/src/api/pages/publish.tsx +28 -12
  19. package/src/bundle.ts +58 -14
  20. package/src/cfetch/internal.ts +19 -14
  21. package/src/config/environment.ts +41 -0
  22. package/src/config/index.ts +21 -0
  23. package/src/config/validation.ts +182 -0
  24. package/src/constellation/createProject.tsx +51 -0
  25. package/src/constellation/deleteProject.ts +51 -0
  26. package/src/constellation/deleteProjectModel.ts +76 -0
  27. package/src/constellation/index.ts +75 -0
  28. package/src/constellation/listCatalog.tsx +35 -0
  29. package/src/constellation/listModel.tsx +49 -0
  30. package/src/constellation/listProject.tsx +28 -0
  31. package/src/constellation/listRuntime.tsx +28 -0
  32. package/src/constellation/options.ts +17 -0
  33. package/src/constellation/types.ts +17 -0
  34. package/src/constellation/uploadModel.tsx +84 -0
  35. package/src/constellation/utils.ts +90 -0
  36. package/src/create-worker-upload-form.ts +25 -0
  37. package/src/d1/backups.tsx +4 -4
  38. package/src/d1/constants.ts +3 -0
  39. package/src/d1/create.tsx +77 -43
  40. package/src/d1/delete.ts +1 -1
  41. package/src/d1/execute.tsx +27 -13
  42. package/src/d1/list.tsx +13 -11
  43. package/src/d1/migrations/apply.tsx +29 -6
  44. package/src/d1/migrations/helpers.ts +8 -9
  45. package/src/d1/types.ts +7 -0
  46. package/src/dev/dev.tsx +9 -2
  47. package/src/dev/local.tsx +52 -20
  48. package/src/dev/remote.tsx +4 -0
  49. package/src/dev/start-server.ts +64 -40
  50. package/src/dev/use-esbuild.ts +70 -43
  51. package/src/dev-registry.ts +11 -0
  52. package/src/dev.tsx +23 -6
  53. package/src/docs/helpers.ts +50 -0
  54. package/src/docs/index.ts +25 -68
  55. package/src/entry.ts +8 -1
  56. package/src/environment-variables/factory.ts +2 -1
  57. package/src/generate/index.ts +5 -0
  58. package/src/index.ts +12 -1
  59. package/src/init.ts +52 -10
  60. package/src/inspect.ts +107 -6
  61. package/src/kv/helpers.ts +7 -4
  62. package/src/logger.ts +5 -1
  63. package/src/miniflare-cli/index.ts +2 -2
  64. package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -1
  65. package/src/module-collection.ts +1 -1
  66. package/src/pages/build.ts +30 -17
  67. package/src/pages/dev.ts +51 -8
  68. package/src/pages/functions/buildPlugin.ts +1 -0
  69. package/src/pages/functions/buildWorker.ts +92 -2
  70. package/src/pages/functions/tsconfig.tsbuildinfo +1 -1
  71. package/src/publish/publish.ts +9 -1
  72. package/src/secret/index.ts +4 -0
  73. package/src/sites.ts +164 -52
  74. package/src/tail/createTail.ts +9 -0
  75. package/src/tail/printing.ts +12 -0
  76. package/src/traverse-module-graph.ts +2 -1
  77. package/src/type-generation.ts +6 -0
  78. package/src/user/user.ts +1 -0
  79. package/src/utils/render.ts +1 -1
  80. package/src/worker.ts +26 -0
  81. package/templates/__tests__/tsconfig.tsbuildinfo +1 -1
  82. package/templates/pages-dev-util.ts +4 -1
  83. package/templates/tsconfig.tsbuildinfo +1 -1
  84. package/wrangler-dist/cli.d.ts +78 -2
  85. package/wrangler-dist/cli.js +65057 -63883
package/src/dev.tsx CHANGED
@@ -28,10 +28,10 @@ import {
28
28
  printWranglerBanner,
29
29
  } from "./index";
30
30
  import type { Config, Environment } from "./config";
31
- import type { Route } from "./config/environment";
31
+ import type { Route, Rule } from "./config/environment";
32
32
  import type { LoggerLevel } from "./logger";
33
33
  import type { EnablePagesAssetsServiceBindingOptions } from "./miniflare-cli/types";
34
- import type { CfWorkerInit } from "./worker";
34
+ import type { CfWorkerInit, CfModule } from "./worker";
35
35
  import type {
36
36
  CommonYargsArgv,
37
37
  StrictYargsOptionsToInterface,
@@ -334,6 +334,11 @@ export type AdditionalDevProps = {
334
334
  preview_bucket_name?: string;
335
335
  }[];
336
336
  d1Databases?: Environment["d1_databases"];
337
+ processEntrypoint?: boolean;
338
+ additionalModules?: CfModule[];
339
+ moduleRoot?: string;
340
+ rules?: Rule[];
341
+ constellation?: Environment["constellation"];
337
342
  };
338
343
 
339
344
  type StartDevOptions = DevArguments &
@@ -397,6 +402,8 @@ export async function startDev(args: StartDevOptions) {
397
402
  getInspectorPort,
398
403
  cliDefines,
399
404
  localPersistencePath,
405
+ processEntrypoint,
406
+ additionalModules,
400
407
  } = await validateDevServerSettings(args, config);
401
408
 
402
409
  await metrics.sendMetricsEvent(
@@ -424,7 +431,9 @@ export async function startDev(args: StartDevOptions) {
424
431
  zone={zoneId}
425
432
  host={host}
426
433
  routes={routes}
427
- rules={getRules(configParam)}
434
+ processEntrypoint={processEntrypoint}
435
+ additionalModules={additionalModules}
436
+ rules={args.rules ?? getRules(configParam)}
428
437
  legacyEnv={isLegacyEnv(configParam)}
429
438
  minify={args.minify ?? configParam.minify}
430
439
  legacyNodeCompat={legacyNodeCompat}
@@ -533,6 +542,8 @@ export async function startApiDev(args: StartDevOptions) {
533
542
  getInspectorPort,
534
543
  cliDefines,
535
544
  localPersistencePath,
545
+ processEntrypoint,
546
+ additionalModules,
536
547
  } = await validateDevServerSettings(args, config);
537
548
 
538
549
  await metrics.sendMetricsEvent(
@@ -560,7 +571,9 @@ export async function startApiDev(args: StartDevOptions) {
560
571
  zone: zoneId,
561
572
  host: host,
562
573
  routes: routes,
563
- rules: getRules(configParam),
574
+ processEntrypoint,
575
+ additionalModules,
576
+ rules: args.rules ?? getRules(configParam),
564
577
  legacyEnv: isLegacyEnv(configParam),
565
578
  minify: args.minify ?? configParam.minify,
566
579
  legacyNodeCompat,
@@ -661,7 +674,7 @@ async function getZoneIdHostAndRoutes(args: StartDevOptions, config: Config) {
661
674
  args.local = true;
662
675
  }
663
676
 
664
- if (!args.local) {
677
+ if (!args.local && !args.experimentalLocal) {
665
678
  if (host) {
666
679
  zoneId = await getZoneIdFromHost(host);
667
680
  }
@@ -687,7 +700,7 @@ async function validateDevServerSettings(
687
700
  config: Config
688
701
  ) {
689
702
  const entry = await getEntry(
690
- { assets: args.assets, script: args.script },
703
+ { assets: args.assets, script: args.script, moduleRoot: args.moduleRoot },
691
704
  config,
692
705
  "dev"
693
706
  );
@@ -787,6 +800,8 @@ async function validateDevServerSettings(
787
800
  routes,
788
801
  cliDefines,
789
802
  localPersistencePath,
803
+ processEntrypoint: !!args.processEntrypoint,
804
+ additionalModules: args.additionalModules ?? [],
790
805
  };
791
806
  }
792
807
 
@@ -860,6 +875,7 @@ function getBindings(
860
875
  },
861
876
  wasm_modules: configParam.wasm_modules,
862
877
  text_blobs: configParam.text_blobs,
878
+ browser: configParam.browser,
863
879
  data_blobs: configParam.data_blobs,
864
880
  durable_objects: {
865
881
  bindings: [
@@ -923,6 +939,7 @@ function getBindings(
923
939
  }),
924
940
  ...(args.d1Databases || []),
925
941
  ]),
942
+ constellation: configParam.constellation,
926
943
  };
927
944
 
928
945
  return bindings;
@@ -0,0 +1,50 @@
1
+ import assert from "node:assert";
2
+ import { fetch } from "undici";
3
+ import { logger } from "../logger";
4
+
5
+ // The ALGOLIA_APP_ID and ALGOLIA_PUBLIC_KEY are provided at esbuild time as a `define` for production and beta releases.
6
+ // Otherwise it is left undefined, which disables search.
7
+ declare const ALGOLIA_APP_ID: string;
8
+ declare const ALGOLIA_PUBLIC_KEY: string;
9
+
10
+ export async function runSearch(searchTerm: string) {
11
+ const id = ALGOLIA_APP_ID;
12
+ const index = "developers-cloudflare2";
13
+ const key = ALGOLIA_PUBLIC_KEY;
14
+ const params = new URLSearchParams({
15
+ query: searchTerm,
16
+ hitsPerPage: "1",
17
+ getRankingInfo: "0",
18
+ });
19
+
20
+ assert(id, "Missing Algolia App ID");
21
+ assert(key, "Missing Algolia Key");
22
+
23
+ const searchResp = await fetch(
24
+ `https://${id}-dsn.algolia.net/1/indexes/${index}/query`,
25
+ {
26
+ method: "POST",
27
+ body: JSON.stringify({
28
+ params: params.toString(),
29
+ }),
30
+ headers: {
31
+ "X-Algolia-API-Key": key,
32
+ "X-Algolia-Application-Id": id,
33
+ },
34
+ }
35
+ );
36
+ if (!searchResp.ok) {
37
+ logger.error(`Could not search the docs. Please try again later.`);
38
+ return;
39
+ }
40
+ const searchData = (await searchResp.json()) as { hits: { url: string }[] };
41
+ logger.debug("searchData: ", searchData);
42
+ if (searchData.hits[0]) {
43
+ return searchData.hits[0].url;
44
+ } else {
45
+ logger.error(
46
+ `Could not find docs for: ${searchTerm}. Please try again with another search term.`
47
+ );
48
+ return;
49
+ }
50
+ }
package/src/docs/index.ts CHANGED
@@ -3,89 +3,46 @@ import { readConfig } from "../config";
3
3
  import { logger } from "../logger";
4
4
  import * as metrics from "../metrics";
5
5
  import openInBrowser from "../open-in-browser";
6
-
6
+ import { runSearch } from "./helpers";
7
7
  import type {
8
8
  CommonYargsArgv,
9
9
  StrictYargsOptionsToInterface,
10
10
  } from "../yargs-types";
11
11
 
12
- const argToUrlHash = {
13
- d1: "d1",
14
- docs: "docs",
15
- init: "init",
16
- generate: "generate",
17
- dev: "dev",
18
- publish: "publish",
19
- delete: "delete",
20
- "kv:namespace": "kvnamespace",
21
- "kv:key": "kvkey",
22
- "kv:bulk": "kvbulk",
23
- "r2 bucket": "r2-bucket",
24
- "r2 object": "r2-object",
25
- secret: "secret",
26
- "secret:bulk": "secretbulk",
27
- tail: "tail",
28
- pages: "pages",
29
- login: "login",
30
- logout: "logout",
31
- whoami: "whoami",
32
- types: "types",
33
- deployments: "deployments",
34
- };
35
-
36
12
  export function docsOptions(yargs: CommonYargsArgv) {
37
- return yargs.positional("command", {
38
- describe: "Enter the wrangler command you want to know more about",
39
- type: "string",
40
- // requiresArg: true,
41
- choices: [
42
- "docs",
43
- "init",
44
- "generate",
45
- "dev",
46
- "publish",
47
- "delete",
48
- "tail",
49
- "secret",
50
- "secret:bulk",
51
- "kv:namespace",
52
- "kv:key",
53
- "kv:bulk",
54
- "pages",
55
- // "queues", //TODO: Undocumented
56
- "r2 object",
57
- "r2 bucket",
58
- // "dispatch-namespace", // TODO: Undocumented - Workers for Platforms
59
- "d1",
60
- // "pubsub", //TODO: Undocumented
61
- "login",
62
- "logout",
63
- "whoami",
64
- "types",
65
- "deployments",
66
- "api",
67
- ],
68
- });
69
- }
70
-
71
- function isValidParam(k: string): k is keyof typeof argToUrlHash {
72
- return k in argToUrlHash;
13
+ return yargs
14
+ .positional("command", {
15
+ describe: "Enter the wrangler command you want to know more about",
16
+ type: "string",
17
+ array: true,
18
+ })
19
+ .option("yes", {
20
+ alias: "y",
21
+ type: "boolean",
22
+ description: "Takes you to the docs, even if search fails",
23
+ });
73
24
  }
74
25
 
75
26
  export async function docsHandler(
76
27
  args: StrictYargsOptionsToInterface<typeof docsOptions>
77
28
  ) {
29
+ //if no command is provided, open the docs homepage
30
+ //or, if a command IS provided, but we can't find anything, open the docs homepage
78
31
  let urlToOpen =
79
- "https://developers.cloudflare.com/workers/wrangler/commands/";
32
+ args.yes || !args.command || args.command.length === 0
33
+ ? "https://developers.cloudflare.com/workers/wrangler/commands/"
34
+ : "";
35
+
36
+ if (args.command && args.command.length > 0) {
37
+ const searchTerm = args.command.join(" ");
38
+ const searchResult = await runSearch(searchTerm);
80
39
 
81
- if (args.command === "api") {
82
- //if api, take them to the API docs
83
- urlToOpen = "https://developers.cloudflare.com/workers/wrangler/api/";
84
- } else if (args.command && isValidParam(args.command)) {
85
- //otherwise, they get the wrangler commands page
86
- urlToOpen += `#${argToUrlHash[args.command]}`;
40
+ urlToOpen = searchResult ?? urlToOpen;
87
41
  }
88
42
 
43
+ if (!urlToOpen) {
44
+ return;
45
+ }
89
46
  await printWranglerBanner();
90
47
 
91
48
  logger.log(`Opening a link in your default browser: ${urlToOpen}`);
package/src/entry.ts CHANGED
@@ -25,6 +25,11 @@ export type Entry = {
25
25
  format: CfScriptFormat;
26
26
  /** The directory that contains all of a `--no-bundle` worker's modules. Usually `${directory}/src`. Defaults to path.dirname(file) */
27
27
  moduleRoot: string;
28
+
29
+ /**
30
+ * A worker's name
31
+ */
32
+ name?: string | undefined;
28
33
  };
29
34
 
30
35
  /**
@@ -35,6 +40,7 @@ export async function getEntry(
35
40
  script?: string;
36
41
  format?: CfScriptFormat | undefined;
37
42
  assets?: string | undefined;
43
+ moduleRoot?: string;
38
44
  },
39
45
  config: Config,
40
46
  command: "dev" | "publish" | "types"
@@ -113,7 +119,8 @@ export async function getEntry(
113
119
  file,
114
120
  directory,
115
121
  format,
116
- moduleRoot: config.base_dir ?? path.dirname(file),
122
+ moduleRoot: args.moduleRoot ?? config.base_dir ?? path.dirname(file),
123
+ name: config.name ?? "worker",
117
124
  };
118
125
  }
119
126
 
@@ -14,7 +14,8 @@ type VariableNames =
14
14
  | "WRANGLER_AUTH_URL"
15
15
  | "WRANGLER_TOKEN_URL"
16
16
  | "WRANGLER_REVOKE_URL"
17
- | "WRANGLER_CF_AUTHORIZATION_TOKEN";
17
+ | "WRANGLER_CF_AUTHORIZATION_TOKEN"
18
+ | "NO_CONSTELLATION_WARNING";
18
19
 
19
20
  type DeprecatedNames =
20
21
  | "CF_ACCOUNT_ID"
@@ -59,6 +59,11 @@ export async function generateHandler(args: GenerateArgs) {
59
59
  // print down here cuz `init` prints it own its own
60
60
  await printWranglerBanner();
61
61
 
62
+ logger.warn(
63
+ `The \`generate\` command is no longer supported and will be removed in a future version.`
64
+ // TODO: recommend replacement commands
65
+ );
66
+
62
67
  if (args.type) {
63
68
  let message = "The --type option is no longer supported.";
64
69
  if (args.type === "webpack") {
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import makeCLI from "yargs";
7
7
  import { version as wranglerVersion } from "../package.json";
8
8
  import { isBuildFailure } from "./bundle";
9
9
  import { loadDotEnv, readConfig } from "./config";
10
+ import { constellation } from "./constellation";
10
11
  import { d1 } from "./d1";
11
12
  import { deleteHandler, deleteOptions } from "./delete";
12
13
  import {
@@ -271,7 +272,7 @@ export function createCLIParser(argv: string[]) {
271
272
 
272
273
  // docs
273
274
  wrangler.command(
274
- "docs [command]",
275
+ "docs [command..]",
275
276
  "📚 Open wrangler's docs in your browser",
276
277
  docsOptions,
277
278
  docsHandler
@@ -431,6 +432,15 @@ export function createCLIParser(argv: string[]) {
431
432
  return d1(d1Yargs.command(subHelp));
432
433
  });
433
434
 
435
+ // ai
436
+ wrangler.command(
437
+ "constellation",
438
+ "🤖 Interact with Constellation models",
439
+ (aiYargs) => {
440
+ return constellation(aiYargs.command(subHelp));
441
+ }
442
+ );
443
+
434
444
  // pubsub
435
445
  wrangler.command(
436
446
  "pubsub",
@@ -569,6 +579,7 @@ export function createCLIParser(argv: string[]) {
569
579
  unsafe: config.unsafe,
570
580
  rules: config.rules,
571
581
  queues: config.queues,
582
+ constellation: config.constellation,
572
583
  };
573
584
 
574
585
  await generateTypes(configBindings, config);
package/src/init.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import { writeFile, mkdir } from "node:fs/promises";
3
- import path from "node:path";
3
+ import path, { dirname } from "node:path";
4
4
  import TOML from "@iarna/toml";
5
5
  import { findUp } from "find-up";
6
6
  import { version as wranglerVersion } from "../package.json";
@@ -18,7 +18,7 @@ import { requireAuth } from "./user";
18
18
  import { CommandLineArgsError, printWranglerBanner } from "./index";
19
19
 
20
20
  import type { RawConfig } from "./config";
21
- import type { Route, SimpleRoute } from "./config/environment";
21
+ import type { Route, SimpleRoute, TailConsumer } from "./config/environment";
22
22
  import type {
23
23
  WorkerMetadata,
24
24
  WorkerMetadataBinding,
@@ -29,6 +29,7 @@ import type {
29
29
  CommonYargsArgv,
30
30
  StrictYargsOptionsToInterface,
31
31
  } from "./yargs-types";
32
+ import type { ReadableStream } from "stream/web";
32
33
 
33
34
  export function initOptions(yargs: CommonYargsArgv) {
34
35
  return yargs
@@ -80,6 +81,8 @@ export type ServiceMetadataRes = {
80
81
  usage_model: "bundled" | "unbound";
81
82
  compatibility_date: string;
82
83
  last_deployed_from?: "wrangler" | "dash" | "api";
84
+ placement_mode?: "smart";
85
+ tail_consumers?: TailConsumer[];
83
86
  };
84
87
  };
85
88
  created_on: string;
@@ -485,10 +488,20 @@ export async function initHandler(args: InitArgs) {
485
488
  `/accounts/${accountId}/workers/services/${fromDashScriptName}/environments/${defaultEnvironment}/content`
486
489
  );
487
490
 
488
- await writeFile(
489
- path.join(creationDirectory, "./src/index.ts"),
490
- dashScript
491
- );
491
+ // writeFile in small batches (of 10) to not exhaust system file descriptors
492
+ for (const files of createBatches(dashScript, 10)) {
493
+ await Promise.all(
494
+ files.map(async (file) => {
495
+ const filepath = path
496
+ .join(creationDirectory, `./src/${file.name}`)
497
+ .replace(/\.js$/, ".ts"); // change javascript extension to typescript extension
498
+ const directory = dirname(filepath);
499
+
500
+ await mkdir(directory, { recursive: true });
501
+ await writeFile(filepath, file.stream() as ReadableStream);
502
+ })
503
+ );
504
+ }
492
505
 
493
506
  await writePackageJsonScriptsAndUpdateWranglerToml({
494
507
  isWritingScripts: shouldWritePackageJsonScripts,
@@ -591,10 +604,21 @@ export async function initHandler(args: InitArgs) {
591
604
  `/accounts/${accountId}/workers/services/${fromDashScriptName}/environments/${defaultEnvironment}/content`
592
605
  );
593
606
 
594
- await writeFile(
595
- path.join(creationDirectory, "./src/index.js"),
596
- dashScript
597
- );
607
+ // writeFile in small batches (of 10) to not exhaust system file descriptors
608
+ for (const files of createBatches(dashScript, 10)) {
609
+ await Promise.all(
610
+ files.map(async (file) => {
611
+ const filepath = path.join(
612
+ creationDirectory,
613
+ `./src/${file.name}`
614
+ );
615
+ const directory = dirname(filepath);
616
+
617
+ await mkdir(directory, { recursive: true });
618
+ await writeFile(filepath, file.stream() as ReadableStream);
619
+ })
620
+ );
621
+ }
598
622
 
599
623
  await writePackageJsonScriptsAndUpdateWranglerToml({
600
624
  isWritingScripts: shouldWritePackageJsonScripts,
@@ -868,6 +892,10 @@ async function getWorkerConfig(
868
892
  new Date().toISOString().substring(0, 10),
869
893
  ...routeOrRoutesToConfig,
870
894
  usage_model: serviceEnvMetadata.script.usage_model,
895
+ placement:
896
+ serviceEnvMetadata.script.placement_mode === "smart"
897
+ ? { mode: "smart" }
898
+ : undefined,
871
899
  ...(durableObjectClassNames.length
872
900
  ? {
873
901
  migrations: [
@@ -888,6 +916,7 @@ async function getWorkerConfig(
888
916
  return { ...envObj, [environment]: {} };
889
917
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
890
918
  }, {} as RawConfig["env"]),
919
+ tail_consumers: serviceEnvMetadata.script.tail_consumers,
891
920
  ...mappedBindings,
892
921
  };
893
922
  }
@@ -942,6 +971,13 @@ export function mapBindings(bindings: WorkerMetadataBinding[]): RawConfig {
942
971
  };
943
972
  }
944
973
  break;
974
+ case "browser":
975
+ {
976
+ configObj.browser = {
977
+ binding: binding.name,
978
+ };
979
+ }
980
+ break;
945
981
  case "r2_bucket":
946
982
  {
947
983
  configObj.r2_buckets = [
@@ -1030,3 +1066,9 @@ export function mapBindings(bindings: WorkerMetadataBinding[]): RawConfig {
1030
1066
  }, {} as RawConfig)
1031
1067
  );
1032
1068
  }
1069
+
1070
+ function* createBatches<T>(array: T[], size: number): IterableIterator<T[]> {
1071
+ for (let i = 0; i < array.length; i += size) {
1072
+ yield array.slice(i, i + size);
1073
+ }
1074
+ }
package/src/inspect.ts CHANGED
@@ -1,9 +1,10 @@
1
+ import { readFileSync } from "fs";
1
2
  import { readFile } from "fs/promises";
2
3
  import assert from "node:assert";
3
4
  import { createServer } from "node:http";
4
5
  import os from "node:os";
5
6
  import { URL } from "node:url";
6
-
7
+ import path from "path";
7
8
  import open from "open";
8
9
  import { useEffect, useRef, useState } from "react";
9
10
  import { SourceMapConsumer } from "source-map";
@@ -40,6 +41,13 @@ import type { MessageEvent } from "ws";
40
41
  * - handle more methods from console
41
42
  */
42
43
 
44
+ // Information about Wrangler's bundling process that needs passsed through
45
+ // for DevTools sourcemap transformation
46
+ export interface SourceMapMetadata {
47
+ tmpDir: string;
48
+ entryDirectory: string;
49
+ }
50
+
43
51
  interface InspectorProps {
44
52
  /**
45
53
  * The port that the local proxy server should listen on.
@@ -58,9 +66,13 @@ interface InspectorProps {
58
66
  /**
59
67
  * Sourcemap path, so that stacktraces can be interpretted
60
68
  */
61
- sourceMapPath?: string | undefined;
69
+ sourceMapPath: string | undefined;
70
+
71
+ sourceMapMetadata: SourceMapMetadata | undefined;
62
72
 
63
73
  host?: string;
74
+
75
+ name?: string;
64
76
  }
65
77
 
66
78
  export default function useInspector(props: InspectorProps) {
@@ -493,6 +505,65 @@ export default function useInspector(props: InspectorProps) {
493
505
 
494
506
  /** Send a message from the local websocket to the remote websocket */
495
507
  function sendMessageToRemoteWebSocket(event: MessageEvent) {
508
+ try {
509
+ // Intercept Network.loadNetworkResource to load sourcemaps
510
+ const message = JSON.parse(event.data as string);
511
+ if (
512
+ message.method === "Network.loadNetworkResource" &&
513
+ props.sourceMapPath !== undefined &&
514
+ props.sourceMapMetadata !== undefined
515
+ ) {
516
+ // Read the generated source map from esbuild
517
+ const sourceMap = JSON.parse(
518
+ readFileSync(props.sourceMapPath, "utf-8")
519
+ );
520
+
521
+ // The source root is a temporary directory (`tmpDir`), and so shouldn't be user-visible
522
+ // It provides no useful info to the user
523
+ sourceMap.sourceRoot = "";
524
+
525
+ const tmpDir = props.sourceMapMetadata.tmpDir;
526
+
527
+ // See https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.mt2g20loc2ct
528
+ // The above link documents the x_google_ignoreList property, which is intended to mark code that shouldn't be visible in DevTools
529
+ // Here we use it to indicate specifically Wrangler-injected code (facades & middleware)
530
+ sourceMap.x_google_ignoreList = sourceMap.sources
531
+ // Filter anything in the generated tmpDir, and anything from Wrangler's templates
532
+ // This should cover facades and middleware, but intentionally doesn't include all non-user code e.g. node_modules
533
+ .map((s: string, idx: number) =>
534
+ s.includes(tmpDir) || s.includes("wrangler/templates")
535
+ ? idx
536
+ : null
537
+ )
538
+ .filter((i: number | null) => i !== null);
539
+
540
+ const entryDirectory = props.sourceMapMetadata.entryDirectory;
541
+
542
+ sourceMap.sources = sourceMap.sources.map(
543
+ (s: string) =>
544
+ // These are never loaded by Wrangler or DevTools. However, the presence of a scheme is required for DevTools to show the path as folders in the Sources view
545
+ // The scheme is intentially not the same as for the sourceMappingURL
546
+ // Without this difference in scheme, DevTools will not strip prefix `../` path elements from top level folders (../node_modules -> node_modules, for instance)
547
+ `worker://${props.name}/${path.relative(entryDirectory, s)}`
548
+ );
549
+
550
+ sendMessageToLocalWebSocket({
551
+ data: JSON.stringify({
552
+ id: message.id,
553
+ result: {
554
+ resource: {
555
+ success: true,
556
+ text: JSON.stringify(sourceMap),
557
+ },
558
+ },
559
+ }),
560
+ });
561
+ return;
562
+ }
563
+ } catch (e) {
564
+ logger.debug(e);
565
+ // Ignore errors, fallthrough to the remote inspector
566
+ }
496
567
  try {
497
568
  assert(
498
569
  remoteWebSocket,
@@ -517,11 +588,28 @@ export default function useInspector(props: InspectorProps) {
517
588
  }
518
589
 
519
590
  /** Send a message from the local websocket to the remote websocket */
520
- function sendMessageToLocalWebSocket(event: MessageEvent) {
591
+ function sendMessageToLocalWebSocket(event: Pick<MessageEvent, "data">) {
521
592
  assert(
522
593
  localWebSocket,
523
594
  "Trying to send a message to an undefined `localWebSocket`"
524
595
  );
596
+ try {
597
+ // Intercept Debugger.scriptParsed responses to inject URL schemes
598
+ const message = JSON.parse(event.data as string);
599
+ if (message.method === "Debugger.scriptParsed") {
600
+ // Add the worker:// scheme conditionally, since some module types already have schemes (e.g. wasm)
601
+ message.params.url = new URL(
602
+ message.params.url,
603
+ `worker://${props.name}`
604
+ ).href;
605
+ localWebSocket.send(JSON.stringify(message));
606
+ return;
607
+ }
608
+ } catch (e) {
609
+ logger.debug(e);
610
+ // Ignore errors, fallthrough to the local websocket
611
+ }
612
+
525
613
  localWebSocket.send(event.data);
526
614
  }
527
615
 
@@ -555,7 +643,13 @@ export default function useInspector(props: InspectorProps) {
555
643
  );
556
644
  }
557
645
  };
558
- }, [localWebSocket, remoteWebSocket]);
646
+ }, [
647
+ localWebSocket,
648
+ remoteWebSocket,
649
+ props.name,
650
+ props.sourceMapMetadata,
651
+ props.sourceMapPath,
652
+ ]);
559
653
  }
560
654
 
561
655
  // Credit: https://stackoverflow.com/a/2117523
@@ -744,8 +838,15 @@ function logConsoleMessage(evt: Protocol.Runtime.ConsoleAPICalledEvent): void {
744
838
  /**
745
839
  * Opens the chrome debugger
746
840
  */
747
- export const openInspector = async (inspectorPort: number) => {
748
- const url = `https://devtools.devprod.cloudflare.dev/js_app?theme=systemPreferred&ws=localhost:${inspectorPort}/ws`;
841
+ export const openInspector = async (
842
+ inspectorPort: number,
843
+ worker: string | undefined
844
+ ) => {
845
+ const query = new URLSearchParams();
846
+ query.set("theme", "systemPreferred");
847
+ query.set("ws", `localhost:${inspectorPort}/ws`);
848
+ if (worker) query.set("domain", worker);
849
+ const url = `https://devtools.devprod.cloudflare.dev/js_app?${query.toString()}`;
749
850
  const errorMessage =
750
851
  "Failed to open inspector.\nInspector depends on having a Chromium-based browser installed, maybe you need to install one?";
751
852