wrangler 2.0.29 → 2.1.2

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 (45) hide show
  1. package/miniflare-dist/index.mjs +1136 -372
  2. package/package.json +3 -2
  3. package/src/__tests__/helpers/mock-cfetch.ts +39 -19
  4. package/src/__tests__/helpers/mock-console.ts +11 -2
  5. package/src/__tests__/helpers/msw/handlers/index.ts +13 -0
  6. package/src/__tests__/helpers/msw/handlers/namespaces.ts +104 -0
  7. package/src/__tests__/helpers/msw/handlers/oauth.ts +36 -0
  8. package/src/__tests__/helpers/msw/handlers/r2.ts +80 -0
  9. package/src/__tests__/helpers/msw/handlers/user.ts +63 -0
  10. package/src/__tests__/helpers/msw/index.ts +4 -0
  11. package/src/__tests__/index.test.ts +9 -7
  12. package/src/__tests__/init.test.ts +356 -5
  13. package/src/__tests__/jest.setup.ts +16 -0
  14. package/src/__tests__/middleware.test.ts +768 -0
  15. package/src/__tests__/pages.test.ts +11 -12
  16. package/src/__tests__/publish.test.ts +516 -438
  17. package/src/__tests__/r2.test.ts +128 -93
  18. package/src/__tests__/secret.test.ts +78 -0
  19. package/src/__tests__/tail.test.ts +47 -74
  20. package/src/__tests__/whoami.test.tsx +49 -64
  21. package/src/api/dev.ts +23 -4
  22. package/src/bundle.ts +225 -1
  23. package/src/dev/dev.tsx +3 -1
  24. package/src/dev/local.tsx +2 -2
  25. package/src/dev/remote.tsx +6 -3
  26. package/src/dev/start-server.ts +11 -7
  27. package/src/dev/use-esbuild.ts +4 -0
  28. package/src/dev.tsx +6 -16
  29. package/src/dialogs.tsx +12 -0
  30. package/src/index.tsx +95 -4
  31. package/src/init.ts +286 -11
  32. package/src/miniflare-cli/assets.ts +130 -415
  33. package/src/miniflare-cli/index.ts +3 -1
  34. package/src/pages/dev.tsx +5 -1
  35. package/src/pages/hash.tsx +13 -0
  36. package/src/pages/upload.tsx +3 -18
  37. package/src/publish.ts +38 -4
  38. package/src/tail/filters.ts +1 -5
  39. package/src/tail/index.ts +6 -3
  40. package/templates/middleware/common.ts +62 -0
  41. package/templates/middleware/loader-modules.ts +84 -0
  42. package/templates/middleware/loader-sw.ts +213 -0
  43. package/templates/middleware/middleware-pretty-error.ts +40 -0
  44. package/templates/middleware/middleware-scheduled.ts +14 -0
  45. package/wrangler-dist/cli.js +65900 -65432
package/src/dev.tsx CHANGED
@@ -3,7 +3,6 @@ import { watch } from "chokidar";
3
3
  import getPort from "get-port";
4
4
  import { render } from "ink";
5
5
  import React from "react";
6
- import { fetch } from "undici";
7
6
  import { findWranglerToml, printBindings, readConfig } from "./config";
8
7
  import Dev from "./dev/dev";
9
8
  import { getVarsForDev } from "./dev/dev-vars";
@@ -30,7 +29,6 @@ import type { Config } from "./config";
30
29
  import type { Route } from "./config/environment";
31
30
  import type { EnablePagesAssetsServiceBindingOptions } from "./miniflare-cli";
32
31
  import type { CfWorkerInit } from "./worker";
33
- import type { RequestInit } from "undici";
34
32
  import type { Argv, ArgumentsCamelCase } from "yargs";
35
33
 
36
34
  interface DevArgs {
@@ -69,7 +67,7 @@ interface DevArgs {
69
67
  "node-compat"?: boolean;
70
68
  "experimental-enable-local-persistence"?: boolean;
71
69
  "live-reload"?: boolean;
72
- onReady?: () => void;
70
+ onReady?: (ip: string, port: number) => void;
73
71
  logLevel?: "none" | "error" | "log" | "warn" | "debug";
74
72
  logPrefix?: string;
75
73
  showInteractiveDevSession?: boolean;
@@ -430,12 +428,6 @@ export async function startDev(args: StartDevOptions) {
430
428
  devReactElement.unmount();
431
429
  await watcher?.close();
432
430
  },
433
- fetch: async (init?: RequestInit) => {
434
- const port = args.port || config.dev.port || (await getLocalPort());
435
- const address = args.ip || config.dev.ip || "localhost";
436
-
437
- return await fetch(`http://${address}:${port}/`, init);
438
- },
439
431
  };
440
432
  } finally {
441
433
  await watcher?.close();
@@ -506,7 +498,11 @@ export async function startApiDev(args: StartDevOptions) {
506
498
  accountId: configParam.account_id || getAccountFromCache()?.id,
507
499
  assetPaths: assetPaths,
508
500
  assetsConfig: configParam.assets,
509
- port: args.port || configParam.dev.port || (await getLocalPort()),
501
+ //port can be 0, which means to use a random port
502
+ port:
503
+ args.port === 0
504
+ ? args.port
505
+ : args.port || configParam.dev.port || (await getLocalPort()),
510
506
  ip: args.ip || configParam.dev.ip,
511
507
  inspectorPort:
512
508
  args["inspector-port"] ||
@@ -544,12 +540,6 @@ export async function startApiDev(args: StartDevOptions) {
544
540
  stop: async () => {
545
541
  await devServer.stop();
546
542
  },
547
- fetch: async (init?: RequestInit) => {
548
- const port = args.port || config.dev.port || (await getLocalPort());
549
- const address = args.ip || config.dev.ip || "localhost";
550
-
551
- return await fetch(`http://${address}:${port}/`, init);
552
- },
553
543
  };
554
544
  }
555
545
  /**
package/src/dialogs.tsx CHANGED
@@ -133,3 +133,15 @@ export function select(
133
133
  );
134
134
  });
135
135
  }
136
+
137
+ export async function fromDashMessagePrompt(
138
+ deploySource: string
139
+ ): Promise<boolean | void> {
140
+ if (deploySource === "dash") {
141
+ logger.warn(
142
+ `You are about to publish a Workers Service that was last published via the Cloudflare Dashboard.
143
+ Edits that have been made via the dashboard will be overridden by your local code and config.`
144
+ );
145
+ return await confirm("Would you like to continue?");
146
+ }
147
+ }
package/src/index.tsx CHANGED
@@ -99,6 +99,9 @@ const proxy =
99
99
 
100
100
  if (proxy) {
101
101
  setGlobalDispatcher(new ProxyAgent(proxy));
102
+ logger.log(
103
+ `Proxy environment variables detected. We'll use your proxy for fetch requests.`
104
+ );
102
105
  }
103
106
 
104
107
  export function getRules(config: Config): Config["rules"] {
@@ -741,15 +744,13 @@ function createCLIParser(argv: string[]) {
741
744
  clientIp: args.ip,
742
745
  };
743
746
 
744
- const filters = translateCLICommandToFilterMessage(
745
- cliFilters,
746
- args.debug
747
- );
747
+ const filters = translateCLICommandToFilterMessage(cliFilters);
748
748
 
749
749
  const { tail, expiration, deleteTail } = await createTail(
750
750
  accountId,
751
751
  scriptName,
752
752
  filters,
753
+ args.debug,
753
754
  !isLegacyEnv(config) ? args.env : undefined
754
755
  );
755
756
 
@@ -1162,6 +1163,96 @@ function createCLIParser(argv: string[]) {
1162
1163
  }
1163
1164
  );
1164
1165
 
1166
+ wrangler.command(
1167
+ "secret:bulk <json>",
1168
+ "🗄️ Bulk upload secrets for a Worker",
1169
+ (yargs) => {
1170
+ return yargs
1171
+ .positional("json", {
1172
+ describe: `The JSON file of key-value pairs to upload, in form {"key": value, ...}`,
1173
+ type: "string",
1174
+ demandOption: "true",
1175
+ })
1176
+ .option("name", {
1177
+ describe: "Name of the Worker",
1178
+ type: "string",
1179
+ requiresArg: true,
1180
+ })
1181
+ .option("env", {
1182
+ type: "string",
1183
+ requiresArg: true,
1184
+ describe:
1185
+ "Binds the secret to the Worker of the specific environment.",
1186
+ alias: "e",
1187
+ });
1188
+ },
1189
+ async (secretBulkArgs) => {
1190
+ await printWranglerBanner();
1191
+ const config = readConfig(
1192
+ secretBulkArgs.config as ConfigPath,
1193
+ secretBulkArgs
1194
+ );
1195
+
1196
+ const scriptName = getLegacyScriptName(secretBulkArgs, config);
1197
+ if (!scriptName) {
1198
+ throw new Error(
1199
+ "Required Worker name missing. Please specify the Worker name in wrangler.toml, or pass it as an argument with `--name <worker-name>`"
1200
+ );
1201
+ }
1202
+
1203
+ const accountId = await requireAuth(config);
1204
+
1205
+ logger.log(
1206
+ `🌀 Creating the secrets for the Worker "${scriptName}" ${
1207
+ secretBulkArgs.env && !isLegacyEnv(config)
1208
+ ? `(${secretBulkArgs.env})`
1209
+ : ""
1210
+ }`
1211
+ );
1212
+ const jsonFilePath = path.resolve(secretBulkArgs.json);
1213
+ const content = parseJSON<Record<string, string>>(
1214
+ readFileSync(jsonFilePath),
1215
+ jsonFilePath
1216
+ );
1217
+ for (const key in content) {
1218
+ if (typeof content[key] !== "string") {
1219
+ throw new Error(
1220
+ `The value for ${key} in ${jsonFilePath} is not a string.`
1221
+ );
1222
+ }
1223
+ }
1224
+
1225
+ const url =
1226
+ !secretBulkArgs.env || isLegacyEnv(config)
1227
+ ? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets`
1228
+ : `/accounts/${accountId}/workers/services/${scriptName}/environments/${secretBulkArgs.env}/secrets`;
1229
+ // Until we have a bulk route for secrets, we need to make a request for each key/value pair
1230
+ await Promise.allSettled(
1231
+ Object.entries(content).map(async ([key, value]) => {
1232
+ return fetchResult(url, {
1233
+ method: "PUT",
1234
+ headers: { "Content-Type": "application/json" },
1235
+ body: JSON.stringify({
1236
+ name: key,
1237
+ text: value,
1238
+ type: "secret_text",
1239
+ }),
1240
+ })
1241
+ .then(() => {
1242
+ logger.log(`✨ Successfully created secret for key: ${key}`);
1243
+ })
1244
+ .catch((e) => {
1245
+ logger.error(
1246
+ `🚨 Error uploading secret for key: ${key}:
1247
+ ${e.message}`
1248
+ );
1249
+ });
1250
+ })
1251
+ );
1252
+ return logger.log("✨ Finished processing secrets JSON file");
1253
+ }
1254
+ );
1255
+
1165
1256
  // kv
1166
1257
  // :namespace
1167
1258
  wrangler.command(
package/src/init.ts CHANGED
@@ -5,6 +5,7 @@ import TOML from "@iarna/toml";
5
5
  import { findUp } from "find-up";
6
6
  import { version as wranglerVersion } from "../package.json";
7
7
 
8
+ import { fetchResult } from "./cfetch";
8
9
  import { fetchDashboardScript } from "./cfetch/internal";
9
10
  import { readConfig } from "./config";
10
11
  import { confirm, select } from "./dialogs";
@@ -15,6 +16,10 @@ import { parsePackageJSON, parseTOML, readFileSync } from "./parse";
15
16
  import { getBasePath } from "./paths";
16
17
  import { requireAuth } from "./user";
17
18
  import { CommandLineArgsError, printWranglerBanner } from "./index";
19
+ import type { RawConfig } from "./config";
20
+
21
+ import type { Route, SimpleRoute } from "./config/environment";
22
+ import type { WorkerMetadata } from "./create-worker-upload-form";
18
23
  import type { ConfigPath } from "./index";
19
24
  import type { Argv, ArgumentsCamelCase } from "yargs";
20
25
 
@@ -56,6 +61,53 @@ interface InitArgs {
56
61
  yes?: boolean;
57
62
  }
58
63
 
64
+ export type ServiceMetadataRes = {
65
+ id: string;
66
+ default_environment: {
67
+ environment: string;
68
+ created_on: string;
69
+ modified_on: string;
70
+ script: {
71
+ id: string;
72
+ tag: string;
73
+ etag: string;
74
+ handlers: string[];
75
+ modified_on: string;
76
+ created_on: string;
77
+ migration_tag: string;
78
+ usage_model: "bundled" | "unbound";
79
+ compatibility_date: string;
80
+ last_deployed_from?: "wrangler" | "dash" | "api";
81
+ };
82
+ };
83
+ created_on: string;
84
+ modified_on: string;
85
+ usage_model: "bundled" | "unbound";
86
+ environments: [
87
+ {
88
+ environment: string;
89
+ created_on: string;
90
+ modified_on: string;
91
+ }
92
+ ];
93
+ };
94
+
95
+ export type RawSimpleRoute = { pattern: string };
96
+ export type RawRoutes = (RawSimpleRoute | Exclude<Route, SimpleRoute>) & {
97
+ id: string;
98
+ };
99
+ export type RoutesRes = RawRoutes[];
100
+
101
+ export type CronTriggersRes = {
102
+ schedules: [
103
+ {
104
+ cron: string;
105
+ created_on: Date;
106
+ modified_on: Date;
107
+ }
108
+ ];
109
+ };
110
+
59
111
  export async function initHandler(args: ArgumentsCamelCase<InitArgs>) {
60
112
  await printWranglerBanner();
61
113
  if (args.type) {
@@ -457,17 +509,23 @@ export async function initHandler(args: ArgumentsCamelCase<InitArgs>) {
457
509
  path.join(creationDirectory, "./src/index.ts")
458
510
  );
459
511
  if (fromDashScriptName) {
512
+ logger.warn(
513
+ `After running "wrangler init --from-dash", modifying your worker via the Cloudflare dashboard is discouraged.
514
+ Edits made via the Dashboard will not be synchronized locally and will be overridden by your local code and config when you publish.`
515
+ );
460
516
  const config = readConfig(args.config as ConfigPath, args);
461
517
  const accountId = await requireAuth(config);
462
518
  await mkdir(path.join(creationDirectory, "./src"), {
463
519
  recursive: true,
464
520
  });
465
-
521
+ const serviceMetaData = await fetchResult<ServiceMetadataRes>(
522
+ `/accounts/${accountId}/workers/services/${fromDashScriptName}`
523
+ );
524
+ const defaultEnvironment =
525
+ serviceMetaData.default_environment.environment;
526
+ // I want the default environment, assuming it's the most up to date code.
466
527
  const dashScript = await fetchDashboardScript(
467
- `/accounts/${accountId}/workers/scripts/${fromDashScriptName}`,
468
- {
469
- method: "GET",
470
- }
528
+ `/accounts/${accountId}/workers/services/${fromDashScriptName}/environments/${defaultEnvironment}/content`
471
529
  );
472
530
 
473
531
  await writeFile(
@@ -480,7 +538,11 @@ export async function initHandler(args: ArgumentsCamelCase<InitArgs>) {
480
538
  justCreatedWranglerToml,
481
539
  pathToPackageJson,
482
540
  "src/index.ts",
483
- {}
541
+
542
+ (await getWorkerConfig(accountId, fromDashScriptName, {
543
+ defaultEnvironment,
544
+ environments: serviceMetaData.environments,
545
+ })) as TOML.JsonMap
484
546
  );
485
547
  } else {
486
548
  const newWorkerType = yesFlag
@@ -523,17 +585,25 @@ export async function initHandler(args: ArgumentsCamelCase<InitArgs>) {
523
585
  );
524
586
 
525
587
  if (fromDashScriptName) {
588
+ logger.warn(
589
+ `After running "wrangler init --from-dash", modifying your worker via the Cloudflare dashboard is discouraged.
590
+ Edits made via the Dashboard will not be synchronized locally and will be overridden by your local code and config when you publish.`
591
+ );
526
592
  const config = readConfig(args.config as ConfigPath, args);
527
593
  const accountId = await requireAuth(config);
528
594
  await mkdir(path.join(creationDirectory, "./src"), {
529
595
  recursive: true,
530
596
  });
531
597
 
598
+ const serviceMetaData = await fetchResult<ServiceMetadataRes>(
599
+ `/accounts/${accountId}/workers/services/${fromDashScriptName}`
600
+ );
601
+ const defaultEnvironment =
602
+ serviceMetaData.default_environment.environment;
603
+
604
+ // I want the default environment, assuming it's the most up to date code.
532
605
  const dashScript = await fetchDashboardScript(
533
- `/accounts/${accountId}/workers/scripts/${fromDashScriptName}`,
534
- {
535
- method: "GET",
536
- }
606
+ `/accounts/${accountId}/workers/services/${fromDashScriptName}/environments/${defaultEnvironment}/content`
537
607
  );
538
608
 
539
609
  await writeFile(
@@ -546,7 +616,11 @@ export async function initHandler(args: ArgumentsCamelCase<InitArgs>) {
546
616
  justCreatedWranglerToml,
547
617
  pathToPackageJson,
548
618
  "src/index.ts",
549
- {}
619
+ //? Should we have Environment argument for `wrangler init --from-dash` - Jacob
620
+ (await getWorkerConfig(accountId, fromDashScriptName, {
621
+ defaultEnvironment,
622
+ environments: serviceMetaData.environments,
623
+ })) as TOML.JsonMap
550
624
  );
551
625
  } else {
552
626
  const newWorkerType = yesFlag
@@ -620,3 +694,204 @@ async function findPath(
620
694
  });
621
695
  }
622
696
  }
697
+
698
+ async function getWorkerConfig(
699
+ accountId: string,
700
+ fromDashScriptName: string,
701
+ {
702
+ defaultEnvironment,
703
+ environments,
704
+ }: {
705
+ defaultEnvironment: string;
706
+ environments: ServiceMetadataRes["environments"];
707
+ }
708
+ ): Promise<RawConfig> {
709
+ const [bindings, routes, serviceEnvMetadata, cronTriggers] =
710
+ await Promise.all([
711
+ fetchResult<WorkerMetadata["bindings"]>(
712
+ `/accounts/${accountId}/workers/services/${fromDashScriptName}/environments/${defaultEnvironment}/bindings`
713
+ ),
714
+ fetchResult<RoutesRes>(
715
+ `/accounts/${accountId}/workers/services/${fromDashScriptName}/environments/${defaultEnvironment}/routes`
716
+ ),
717
+ fetchResult<ServiceMetadataRes["default_environment"]>(
718
+ `/accounts/${accountId}/workers/services/${fromDashScriptName}/environments/${defaultEnvironment}`
719
+ ),
720
+ fetchResult<CronTriggersRes>(
721
+ `/accounts/${accountId}/workers/scripts/${fromDashScriptName}/schedules`
722
+ ),
723
+ ]).catch((e) => {
724
+ throw new Error(
725
+ `Error Occurred ${e}: Unable to fetch bindings, routes, or services metadata from the dashboard. Please try again later.`
726
+ );
727
+ });
728
+
729
+ const mappedBindings = bindings
730
+ .filter((binding) => (binding.type as string) !== "secret_text")
731
+ // Combine the same types into {[type]: [binding]}
732
+ .reduce((configObj, binding) => {
733
+ // Some types have different names in wrangler.toml
734
+ // I want the type safety of the binding being destructured after the case narrowing the union but type is unused
735
+
736
+ switch (binding.type) {
737
+ case "plain_text":
738
+ {
739
+ configObj.vars = {
740
+ ...(configObj.vars ?? {}),
741
+ name: binding.name,
742
+ text: binding.text,
743
+ };
744
+ }
745
+ break;
746
+ case "json":
747
+ {
748
+ configObj.vars = {
749
+ ...(configObj.vars ?? {}),
750
+ name: binding.name,
751
+ json: binding.json,
752
+ };
753
+ }
754
+ break;
755
+ case "kv_namespace":
756
+ {
757
+ configObj.kv_namespaces = [
758
+ ...(configObj.kv_namespaces ?? []),
759
+ { id: binding.namespace_id, binding: binding.name },
760
+ ];
761
+ }
762
+ break;
763
+ case "durable_object_namespace":
764
+ {
765
+ configObj.durable_objects = {
766
+ bindings: [
767
+ ...(configObj.durable_objects?.bindings ?? []),
768
+ {
769
+ name: binding.name,
770
+ class_name: binding.class_name,
771
+ script_name: binding.script_name,
772
+ environment: binding.environment,
773
+ },
774
+ ],
775
+ };
776
+ }
777
+ break;
778
+ case "r2_bucket":
779
+ {
780
+ configObj.r2_buckets = [
781
+ ...(configObj.r2_buckets ?? []),
782
+ { binding: binding.name, bucket_name: binding.bucket_name },
783
+ ];
784
+ }
785
+ break;
786
+ case "service":
787
+ {
788
+ configObj.services = [
789
+ ...(configObj.services ?? []),
790
+ {
791
+ binding: binding.name,
792
+ service: binding.service,
793
+ environment: binding.environment,
794
+ },
795
+ ];
796
+ }
797
+ break;
798
+ case "namespace":
799
+ {
800
+ configObj.dispatch_namespaces = [
801
+ ...(configObj.dispatch_namespaces ?? []),
802
+ { binding: binding.name, namespace: binding.namespace },
803
+ ];
804
+ }
805
+ break;
806
+ case "logfwdr":
807
+ {
808
+ configObj.logfwdr = {
809
+ // TODO: Messaging about adding schema file path
810
+ schema: "",
811
+ bindings: [
812
+ ...(configObj.logfwdr?.bindings ?? []),
813
+ { name: binding.name, destination: binding.destination },
814
+ ],
815
+ };
816
+ }
817
+ break;
818
+ case "wasm_module":
819
+ {
820
+ configObj.wasm_modules = {
821
+ ...(configObj.wasm_modules ?? {}),
822
+ [binding.name]: binding.part,
823
+ };
824
+ }
825
+ break;
826
+ case "text_blob":
827
+ {
828
+ configObj.text_blobs = {
829
+ ...(configObj.text_blobs ?? {}),
830
+ [binding.name]: binding.part,
831
+ };
832
+ }
833
+ break;
834
+ case "data_blob":
835
+ {
836
+ configObj.data_blobs = {
837
+ ...(configObj.data_blobs ?? {}),
838
+ [binding.name]: binding.part,
839
+ };
840
+ }
841
+ break;
842
+ default: {
843
+ // If we don't know what the type is, its an unsafe binding
844
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
845
+ if (!(binding as any)?.type) break;
846
+ configObj.unsafe = {
847
+ bindings: [...(configObj.unsafe?.bindings ?? []), binding],
848
+ };
849
+ }
850
+ }
851
+
852
+ return configObj;
853
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
854
+ }, {} as RawConfig);
855
+
856
+ const durableObjectClassNames = bindings
857
+ .filter((binding) => binding.type === "durable_object_namespace")
858
+ .map(
859
+ (durableObject) => (durableObject as { class_name: string }).class_name
860
+ );
861
+
862
+ const routeOrRoutes = routes.map((rawRoute) => {
863
+ const { id: _id, ...route } = rawRoute;
864
+ if (Object.keys(route).length === 1) {
865
+ return route.pattern;
866
+ } else {
867
+ return route as Route;
868
+ }
869
+ });
870
+ const routeOrRoutesToConfig =
871
+ routeOrRoutes.length > 1
872
+ ? { routes: routeOrRoutes }
873
+ : { route: routeOrRoutes[0] };
874
+
875
+ return {
876
+ compatibility_date: serviceEnvMetadata.script.compatibility_date,
877
+ ...routeOrRoutesToConfig,
878
+ usage_model: serviceEnvMetadata.script.usage_model,
879
+ migrations: [
880
+ {
881
+ tag: serviceEnvMetadata.script.migration_tag,
882
+ new_classes: durableObjectClassNames,
883
+ },
884
+ ],
885
+ triggers: {
886
+ crons: cronTriggers.schedules.map((scheduled) => scheduled.cron),
887
+ },
888
+ env: environments
889
+ .filter((env) => env.environment !== "production")
890
+ // `env` can have multiple Environments, with different configs.
891
+ .reduce((envObj, { environment }) => {
892
+ return { ...envObj, [environment]: {} };
893
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
894
+ }, {} as RawConfig["env"]),
895
+ ...mappedBindings,
896
+ };
897
+ }