wrangler 2.0.8 → 2.0.9

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/dev.tsx CHANGED
@@ -13,7 +13,6 @@ import { runCustomBuild } from "../entry";
13
13
  import { openInspector } from "../inspect";
14
14
  import { logger } from "../logger";
15
15
  import openInBrowser from "../open-in-browser";
16
- import { getAPIToken } from "../user";
17
16
  import { Local } from "./local";
18
17
  import { Remote } from "./remote";
19
18
  import { useEsbuild } from "./use-esbuild";
@@ -54,16 +53,11 @@ export type DevProps = {
54
53
  };
55
54
  env: string | undefined;
56
55
  legacyEnv: boolean;
57
- zone:
58
- | {
59
- id: string;
60
- host: string;
61
- }
62
- | undefined;
56
+ zone: string | undefined;
57
+ host: string | undefined;
63
58
  };
64
59
 
65
60
  export function DevImplementation(props: DevProps): JSX.Element {
66
- const apiToken = props.initialMode === "remote" ? getAPIToken() : undefined;
67
61
  const directory = useTmpDir();
68
62
 
69
63
  useCustomBuild(props.entry, props.build);
@@ -108,19 +102,17 @@ export function DevImplementation(props: DevProps): JSX.Element {
108
102
  // only load the UI if we're running in a supported environment
109
103
  const { isRawModeSupported } = useStdin();
110
104
  return isRawModeSupported ? (
111
- <InteractiveDevSession {...props} bundle={bundle} apiToken={apiToken} />
105
+ <InteractiveDevSession {...props} bundle={bundle} />
112
106
  ) : (
113
107
  <DevSession
114
108
  {...props}
115
109
  bundle={bundle}
116
- apiToken={apiToken}
117
110
  local={props.initialMode === "local"}
118
111
  />
119
112
  );
120
113
  }
121
114
 
122
115
  type InteractiveDevSessionProps = DevProps & {
123
- apiToken: string | undefined;
124
116
  bundle: EsbuildBundle | undefined;
125
117
  };
126
118
 
@@ -183,7 +175,6 @@ function DevSession(props: DevSessionProps) {
183
175
  bundle={props.bundle}
184
176
  format={props.entry.format}
185
177
  accountId={props.accountId}
186
- apiToken={props.apiToken}
187
178
  bindings={props.bindings}
188
179
  assetPaths={props.assetPaths}
189
180
  public={props.public}
@@ -197,6 +188,7 @@ function DevSession(props: DevSessionProps) {
197
188
  env={props.env}
198
189
  legacyEnv={props.legacyEnv}
199
190
  zone={props.zone}
191
+ host={props.host}
200
192
  />
201
193
  );
202
194
  }
@@ -1,4 +1,3 @@
1
- import assert from "node:assert";
2
1
  import { readFile } from "node:fs/promises";
3
2
  import path from "node:path";
4
3
  import { useState, useEffect, useRef } from "react";
@@ -7,6 +6,7 @@ import useInspector from "../inspect";
7
6
  import { logger } from "../logger";
8
7
  import { usePreviewServer } from "../proxy";
9
8
  import { syncAssets } from "../sites";
9
+ import { requireApiToken, requireAuth } from "../user";
10
10
  import type { CfPreviewToken } from "../create-worker-preview";
11
11
  import type { AssetPaths } from "../sites";
12
12
  import type { CfModule, CfWorkerInit, CfScriptFormat } from "../worker";
@@ -23,24 +23,21 @@ export function Remote(props: {
23
23
  localProtocol: "https" | "http";
24
24
  inspectorPort: number;
25
25
  accountId: string | undefined;
26
- apiToken: string | undefined;
27
26
  bindings: CfWorkerInit["bindings"];
28
27
  compatibilityDate: string;
29
28
  compatibilityFlags: string[] | undefined;
30
29
  usageModel: "bundled" | "unbound" | undefined;
31
30
  env: string | undefined;
32
31
  legacyEnv: boolean | undefined;
33
- zone: { id: string; host: string } | undefined;
32
+ zone: string | undefined;
33
+ host: string | undefined;
34
34
  }) {
35
- assert(props.accountId, "accountId is required");
36
- assert(props.apiToken, "apiToken is required");
37
35
  const previewToken = useWorker({
38
36
  name: props.name,
39
37
  bundle: props.bundle,
40
38
  format: props.format,
41
39
  modules: props.bundle ? props.bundle.modules : [],
42
40
  accountId: props.accountId,
43
- apiToken: props.apiToken,
44
41
  bindings: props.bindings,
45
42
  assetPaths: props.assetPaths,
46
43
  port: props.port,
@@ -50,6 +47,7 @@ export function Remote(props: {
50
47
  env: props.env,
51
48
  legacyEnv: props.legacyEnv,
52
49
  zone: props.zone,
50
+ host: props.host,
53
51
  });
54
52
 
55
53
  usePreviewServer({
@@ -73,8 +71,7 @@ export function useWorker(props: {
73
71
  bundle: EsbuildBundle | undefined;
74
72
  format: CfScriptFormat | undefined;
75
73
  modules: CfModule[];
76
- accountId: string;
77
- apiToken: string;
74
+ accountId: string | undefined;
78
75
  bindings: CfWorkerInit["bindings"];
79
76
  assetPaths: AssetPaths | undefined;
80
77
  port: number;
@@ -83,7 +80,8 @@ export function useWorker(props: {
83
80
  usageModel: "bundled" | "unbound" | undefined;
84
81
  env: string | undefined;
85
82
  legacyEnv: boolean | undefined;
86
- zone: { id: string; host: string } | undefined;
83
+ zone: string | undefined;
84
+ host: string | undefined;
87
85
  }): CfPreviewToken | undefined {
88
86
  const {
89
87
  name,
@@ -91,7 +89,6 @@ export function useWorker(props: {
91
89
  format,
92
90
  modules,
93
91
  accountId,
94
- apiToken,
95
92
  bindings,
96
93
  assetPaths,
97
94
  compatibilityDate,
@@ -105,6 +102,9 @@ export function useWorker(props: {
105
102
  // something's "happened" in our system; We make a ref and
106
103
  // mark it once we log our initial message. Refs are vars!
107
104
  const startedRef = useRef(false);
105
+ // This ref holds the actual accountId being used, the `accountId` prop could be undefined
106
+ // as it is only what is retrieved from the wrangler.toml config.
107
+ const accountIdRef = useRef(accountId);
108
108
 
109
109
  useEffect(() => {
110
110
  const abortController = new AbortController();
@@ -119,8 +119,13 @@ export function useWorker(props: {
119
119
  logger.log("⎔ Detected changes, restarted server.");
120
120
  }
121
121
 
122
+ // Ensure we have an account id, even if it means logging in here.
123
+ accountIdRef.current = await requireAuth({
124
+ account_id: accountIdRef.current,
125
+ });
126
+
122
127
  const assets = await syncAssets(
123
- accountId,
128
+ accountIdRef.current,
124
129
  // When we're using the newer service environments, we wouldn't
125
130
  // have added the env name on to the script name. However, we must
126
131
  // include it in the kv namespace name regardless (since there's no
@@ -173,10 +178,15 @@ export function useWorker(props: {
173
178
  await createWorkerPreview(
174
179
  init,
175
180
  {
176
- accountId,
177
- apiToken,
181
+ accountId: accountIdRef.current,
182
+ apiToken: requireApiToken(),
183
+ },
184
+ {
185
+ env: props.env,
186
+ legacyEnv: props.legacyEnv,
187
+ zone: props.zone,
188
+ host: props.host,
178
189
  },
179
- { env: props.env, legacyEnv: props.legacyEnv, zone: props.zone },
180
190
  abortController.signal
181
191
  )
182
192
  );
@@ -193,7 +203,7 @@ export function useWorker(props: {
193
203
  "Error: You need to register a workers.dev subdomain before running the dev command in remote mode";
194
204
  const solutionMessage =
195
205
  "You can either enable local mode by pressing l, or register a workers.dev subdomain here:";
196
- const onboardingLink = `https://dash.cloudflare.com/${accountId}/workers/onboarding`;
206
+ const onboardingLink = `https://dash.cloudflare.com/${accountIdRef.current}/workers/onboarding`;
197
207
  logger.error(
198
208
  `${errorMessage}\n${solutionMessage}\n${onboardingLink}`
199
209
  );
@@ -211,7 +221,6 @@ export function useWorker(props: {
211
221
  bundle,
212
222
  format,
213
223
  accountId,
214
- apiToken,
215
224
  port,
216
225
  assetPaths,
217
226
  compatibilityDate,
@@ -222,6 +231,7 @@ export function useWorker(props: {
222
231
  props.env,
223
232
  props.legacyEnv,
224
233
  props.zone,
234
+ props.host,
225
235
  ]);
226
236
  return token;
227
237
  }
package/src/index.tsx CHANGED
@@ -66,6 +66,7 @@ import {
66
66
  requireAuth,
67
67
  } from "./user";
68
68
  import { whoami } from "./whoami";
69
+ import { getZoneIdFromHost, getZoneForRoute } from "./zones";
69
70
 
70
71
  import type { Config } from "./config";
71
72
  import type { TailCLIFilters } from "./tail";
@@ -444,7 +445,10 @@ function createCLIParser(argv: string[]) {
444
445
  yesFlag ||
445
446
  (await confirm("Would you like to use git to manage this Worker?"));
446
447
  if (shouldInitGit) {
447
- await execa("git", ["init", "--initial-branch=main"], {
448
+ await execa("git", ["init"], {
449
+ cwd: creationDirectory,
450
+ });
451
+ await execa("git", ["branch", "-m", "main"], {
448
452
  cwd: creationDirectory,
449
453
  });
450
454
  await writeFile(
@@ -1079,92 +1083,25 @@ function createCLIParser(argv: string[]) {
1079
1083
  );
1080
1084
  }
1081
1085
 
1082
- const accountId = !args.local ? await requireAuth(config) : undefined;
1083
-
1084
1086
  // TODO: if worker_dev = false and no routes, then error (only for dev)
1085
1087
 
1086
- /**
1087
- * Given something that resembles a URL,
1088
- * try to extract a host from it
1089
- */
1090
- function getHost(urlLike: string): string | undefined {
1091
- // strip leading * / *.
1092
- urlLike = urlLike.replace(/^\*(\.)?/g, "");
1093
-
1094
- if (
1095
- !(urlLike.startsWith("http://") || urlLike.startsWith("https://"))
1096
- ) {
1097
- urlLike = "http://" + urlLike;
1098
- }
1099
- return new URL(urlLike).host;
1100
- }
1101
-
1102
- /**
1103
- * Given something that resembles a host,
1104
- * try to infer a zone id from it
1105
- */
1106
- async function getZoneId(host: string): Promise<string | undefined> {
1107
- const zones = await fetchResult<{ id: string }[]>(
1108
- `/zones`,
1109
- {},
1110
- new URLSearchParams({ name: host })
1111
- );
1112
- return zones[0]?.id;
1113
- }
1114
-
1115
- // When we're given a host (in one of the above ways), we do 2 things:
1116
- // - We try to extract a host from it
1117
- // - We try to get a zone id from the host
1118
- //
1119
- // So it turns out it's particularly hard to get a 'valid' domain
1120
- // from a string, so we don't even try to validate TLDs, etc.
1121
- // Once we get something that looks like w.x.y.z-ish, we then try to
1122
- // get a zone id for it, by lopping off subdomains until we get a hit
1123
- // from the API. That's it!
1124
-
1125
- let zone: { host: string; id: string } | undefined;
1088
+ // Compute zone info from the `host` and `route` args and config;
1089
+ let host = args.host || config.dev.host;
1090
+ let zoneId: string | undefined;
1126
1091
 
1127
1092
  if (!args.local) {
1128
- const hostLike =
1129
- args.host ||
1130
- config.dev.host ||
1131
- (args.routes && args.routes[0]) ||
1132
- config.route ||
1133
- (config.routes && config.routes[0]);
1134
-
1135
- let zoneId: string | undefined =
1136
- typeof hostLike === "object" && "zone_id" in hostLike
1137
- ? hostLike.zone_id
1138
- : undefined;
1139
-
1140
- const host =
1141
- typeof hostLike === "string"
1142
- ? getHost(hostLike)
1143
- : typeof hostLike === "object"
1144
- ? "zone_name" in hostLike
1145
- ? getHost(hostLike.zone_name)
1146
- : getHost(hostLike.pattern)
1147
- : undefined;
1148
-
1149
- const hostPieces =
1150
- typeof host === "string" ? host.split(".") : undefined;
1151
-
1152
- while (!zoneId && hostPieces && hostPieces.length > 1) {
1153
- zoneId = await getZoneId(hostPieces.join("."));
1154
- hostPieces.shift();
1093
+ if (host) {
1094
+ zoneId = await getZoneIdFromHost(host);
1155
1095
  }
1156
-
1157
- if (host && !zoneId) {
1158
- throw new Error(`Could not find zone for ${hostLike}`);
1096
+ const routes = args.routes || config.route || config.routes;
1097
+ if (!zoneId && routes) {
1098
+ const firstRoute = Array.isArray(routes) ? routes[0] : routes;
1099
+ const zone = await getZoneForRoute(firstRoute);
1100
+ if (zone) {
1101
+ zoneId = zone.id;
1102
+ host = zone.host;
1103
+ }
1159
1104
  }
1160
-
1161
- zone =
1162
- typeof zoneId === "string" && typeof host === "string"
1163
- ? {
1164
- host,
1165
- id: zoneId,
1166
- }
1167
- : undefined;
1168
1105
  }
1169
1106
 
1170
1107
  const nodeCompat = args.nodeCompat ?? config.node_compat;
@@ -1243,7 +1180,8 @@ function createCLIParser(argv: string[]) {
1243
1180
  name={getScriptName(args, config)}
1244
1181
  entry={entry}
1245
1182
  env={args.env}
1246
- zone={zone}
1183
+ zone={zoneId}
1184
+ host={host}
1247
1185
  rules={getRules(config)}
1248
1186
  legacyEnv={isLegacyEnv(config)}
1249
1187
  minify={args.minify ?? config.minify}
@@ -1258,7 +1196,7 @@ function createCLIParser(argv: string[]) {
1258
1196
  enableLocalPersistence={
1259
1197
  args["experimental-enable-local-persistence"] || false
1260
1198
  }
1261
- accountId={accountId}
1199
+ accountId={config.account_id}
1262
1200
  assetPaths={getAssetPaths(
1263
1201
  config,
1264
1202
  args.site,
@@ -1672,6 +1610,7 @@ function createCLIParser(argv: string[]) {
1672
1610
  rules={getRules(config)}
1673
1611
  env={args.env}
1674
1612
  zone={undefined}
1613
+ host={undefined}
1675
1614
  legacyEnv={isLegacyEnv(config)}
1676
1615
  build={config.build || {}}
1677
1616
  minify={undefined}
package/src/pages.tsx CHANGED
@@ -1139,9 +1139,15 @@ const createDeployment: CommandModule<
1139
1139
 
1140
1140
  const files = [...fileMap.values()];
1141
1141
 
1142
- const { jwt } = await fetchResult<{ jwt: string }>(
1143
- `/accounts/${accountId}/pages/projects/${projectName}/upload-token`
1144
- );
1142
+ async function fetchJwt(): Promise<string> {
1143
+ return (
1144
+ await fetchResult<{ jwt: string }>(
1145
+ `/accounts/${accountId}/pages/projects/${projectName}/upload-token`
1146
+ )
1147
+ ).jwt;
1148
+ }
1149
+
1150
+ let jwt = await fetchJwt();
1145
1151
 
1146
1152
  const start = Date.now();
1147
1153
 
@@ -1237,6 +1243,11 @@ const createDeployment: CommandModule<
1237
1243
  await new Promise((resolve) =>
1238
1244
  setTimeout(resolve, attempts++ * 1000)
1239
1245
  );
1246
+
1247
+ if ((e as { code: number }).code === 8000013) {
1248
+ // Looks like the JWT expired, fetch another one
1249
+ jwt = await fetchJwt();
1250
+ }
1240
1251
  return doUpload();
1241
1252
  } else {
1242
1253
  throw e;
package/src/publish.ts CHANGED
@@ -4,13 +4,15 @@ import path from "node:path";
4
4
  import { URLSearchParams } from "node:url";
5
5
  import tmp from "tmp-promise";
6
6
  import { bundleWorker } from "./bundle";
7
- import { fetchResult } from "./cfetch";
7
+ import { fetchListResult, fetchResult } from "./cfetch";
8
8
  import { printBindings } from "./config";
9
9
  import { createWorkerUploadForm } from "./create-worker-upload-form";
10
10
  import { confirm } from "./dialogs";
11
11
  import { getMigrationsToUpload } from "./durable";
12
12
  import { logger } from "./logger";
13
+ import { ParseError } from "./parse";
13
14
  import { syncAssets } from "./sites";
15
+ import { getZoneForRoute } from "./zones";
14
16
  import type { Config } from "./config";
15
17
  import type {
16
18
  Route,
@@ -260,7 +262,7 @@ export default async function publish(props: Props): Promise<void> {
260
262
  const nodeCompat = props.nodeCompat ?? config.node_compat;
261
263
  if (nodeCompat) {
262
264
  logger.warn(
263
- "Enabling node.js compatibility mode for builtins and globals. This is experimental and has serious tradeoffs. Please see https://github.com/ionic-team/rollup-plugin-node-polyfills/ for more details."
265
+ "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."
264
266
  );
265
267
  }
266
268
 
@@ -291,7 +293,7 @@ export default async function publish(props: Props): Promise<void> {
291
293
  const envName = props.env ?? "production";
292
294
 
293
295
  const start = Date.now();
294
- const notProd = !props.legacyEnv && props.env;
296
+ const notProd = Boolean(!props.legacyEnv && props.env);
295
297
  const workerName = notProd ? `${scriptName} (${envName})` : scriptName;
296
298
  const workerUrl = notProd
297
299
  ? `/accounts/${accountId}/workers/services/${scriptName}/environments/${envName}`
@@ -497,18 +499,7 @@ export default async function publish(props: Props): Promise<void> {
497
499
  // Update routing table for the script.
498
500
  if (routesOnly.length > 0) {
499
501
  deployments.push(
500
- fetchResult(`${workerUrl}/routes`, {
501
- // Note: PUT will delete previous routes on this script.
502
- method: "PUT",
503
- body: JSON.stringify(
504
- routesOnly.map((route) =>
505
- typeof route !== "object" ? { pattern: route } : route
506
- )
507
- ),
508
- headers: {
509
- "Content-Type": "application/json",
510
- },
511
- }).then(() => {
502
+ publishRoutes(routesOnly, { workerUrl, scriptName, notProd }).then(() => {
512
503
  if (routesOnly.length > 10) {
513
504
  return routesOnly
514
505
  .slice(0, 9)
@@ -581,3 +572,145 @@ async function getSubdomain(accountId: string): Promise<string> {
581
572
  }
582
573
  }
583
574
  }
575
+
576
+ /**
577
+ * Associate the newly deployed Worker with the given routes.
578
+ */
579
+ async function publishRoutes(
580
+ routes: Route[],
581
+ {
582
+ workerUrl,
583
+ scriptName,
584
+ notProd,
585
+ }: { workerUrl: string; scriptName: string; notProd: boolean }
586
+ ): Promise<string[]> {
587
+ try {
588
+ return await fetchResult(`${workerUrl}/routes`, {
589
+ // Note: PUT will delete previous routes on this script.
590
+ method: "PUT",
591
+ body: JSON.stringify(
592
+ routes.map((route) =>
593
+ typeof route !== "object" ? { pattern: route } : route
594
+ )
595
+ ),
596
+ headers: {
597
+ "Content-Type": "application/json",
598
+ },
599
+ });
600
+ } catch (e) {
601
+ if (isAuthenticationError(e)) {
602
+ // An authentication error is probably due to a known issue,
603
+ // where the user is logged in via an API token that does not have "All Zones".
604
+ return await publishRoutesFallback(routes, { scriptName, notProd });
605
+ } else {
606
+ throw e;
607
+ }
608
+ }
609
+ }
610
+
611
+ /**
612
+ * Try updating routes for the Worker using a less optimal zone-based API.
613
+ *
614
+ * Compute match zones to the routes, then for each route attempt to connect it to the Worker via the zone.
615
+ */
616
+ async function publishRoutesFallback(
617
+ routes: Route[],
618
+ { scriptName, notProd }: { scriptName: string; notProd: boolean }
619
+ ) {
620
+ if (notProd) {
621
+ throw new Error(
622
+ "Service environments combined with an API token that doesn't have 'All Zones' permissions is not supported.\n" +
623
+ "Either turn off service environments by setting `legacy_env = true`, creating an API token with 'All Zones' permissions, or logging in via OAuth"
624
+ );
625
+ }
626
+ logger.warn(
627
+ "The current authentication token does not have 'All Zones' permissions.\n" +
628
+ "Falling back to using the zone-based API endpoint to update each route individually.\n" +
629
+ "Note that there is no access to routes associated with zones that the API token does not have permission for.\n" +
630
+ "Existing routes for this Worker in such zones will not be deleted."
631
+ );
632
+
633
+ const deployedRoutes: string[] = [];
634
+
635
+ // Collect the routes (and their zones) that will be deployed.
636
+ const activeZones = new Map<string, string>();
637
+ const routesToDeploy = new Map<string, string>();
638
+ for (const route of routes) {
639
+ const zone = await getZoneForRoute(route);
640
+ if (zone) {
641
+ activeZones.set(zone.id, zone.host);
642
+ routesToDeploy.set(
643
+ typeof route === "string" ? route : route.pattern,
644
+ zone.id
645
+ );
646
+ }
647
+ }
648
+
649
+ // Collect the routes that are already deployed.
650
+ const allRoutes = new Map<string, string>();
651
+ const alreadyDeployedRoutes = new Set<string>();
652
+ for (const [zone, host] of activeZones) {
653
+ try {
654
+ for (const { pattern, script } of await fetchListResult<{
655
+ pattern: string;
656
+ script: string;
657
+ }>(`/zones/${zone}/workers/routes`)) {
658
+ allRoutes.set(pattern, script);
659
+ if (script === scriptName) {
660
+ alreadyDeployedRoutes.add(pattern);
661
+ }
662
+ }
663
+ } catch (e) {
664
+ if (isAuthenticationError(e)) {
665
+ e.notes.push({
666
+ text: `This could be because the API token being used does not have permission to access the zone "${host}" (${zone}).`,
667
+ });
668
+ }
669
+ throw e;
670
+ }
671
+ }
672
+
673
+ // Deploy each route that is not already deployed.
674
+ for (const [routePattern, zoneId] of routesToDeploy.entries()) {
675
+ if (allRoutes.has(routePattern)) {
676
+ const knownScript = allRoutes.get(routePattern);
677
+ if (knownScript === scriptName) {
678
+ // This route is already associated with this worker, so no need to hit the API.
679
+ alreadyDeployedRoutes.delete(routePattern);
680
+ continue;
681
+ } else {
682
+ throw new Error(
683
+ `The route with pattern "${routePattern}" is already associated with another worker called "${knownScript}".`
684
+ );
685
+ }
686
+ }
687
+
688
+ const { pattern } = await fetchResult(`/zones/${zoneId}/workers/routes`, {
689
+ method: "POST",
690
+ body: JSON.stringify({
691
+ pattern: routePattern,
692
+ script: scriptName,
693
+ }),
694
+ headers: {
695
+ "Content-Type": "application/json",
696
+ },
697
+ });
698
+
699
+ deployedRoutes.push(pattern);
700
+ }
701
+
702
+ if (alreadyDeployedRoutes.size) {
703
+ logger.warn(
704
+ "Previously deployed routes:\n" +
705
+ "The following routes were already associated with this worker, and have not been deleted:\n" +
706
+ [...alreadyDeployedRoutes.values()].map((route) => ` - "${route}"\n`) +
707
+ "If these routes are not wanted then you can remove them in the dashboard."
708
+ );
709
+ }
710
+
711
+ return deployedRoutes;
712
+ }
713
+
714
+ function isAuthenticationError(e: unknown): e is ParseError {
715
+ return e instanceof ParseError && (e as { code?: number }).code === 10000;
716
+ }
package/src/user.tsx CHANGED
@@ -233,7 +233,7 @@ import type { Response } from "undici";
233
233
  /**
234
234
  * Try to read the API token from the environment.
235
235
  */
236
- const getCloudflareAPITokenFromEnv = getEnvironmentVariableFactory({
236
+ export const getCloudflareAPITokenFromEnv = getEnvironmentVariableFactory({
237
237
  variableName: "CLOUDFLARE_API_TOKEN",
238
238
  deprecatedName: "CF_API_TOKEN",
239
239
  });
@@ -1201,3 +1201,14 @@ export async function requireAuth(config: {
1201
1201
 
1202
1202
  return accountId;
1203
1203
  }
1204
+
1205
+ /**
1206
+ * Throw an error if there is no API token available.
1207
+ */
1208
+ export function requireApiToken(): string {
1209
+ const authToken = getAPIToken();
1210
+ if (!authToken) {
1211
+ throw new Error("No API token found.");
1212
+ }
1213
+ return authToken;
1214
+ }
package/src/whoami.tsx CHANGED
@@ -3,7 +3,7 @@ import Table from "ink-table";
3
3
  import React from "react";
4
4
  import { fetchListResult, fetchResult } from "./cfetch";
5
5
  import { logger } from "./logger";
6
- import { getAPIToken } from "./user";
6
+ import { getAPIToken, getCloudflareAPITokenFromEnv } from "./user";
7
7
 
8
8
  export async function whoami() {
9
9
  logger.log("Getting User settings...");
@@ -48,10 +48,11 @@ export interface UserInfo {
48
48
 
49
49
  export async function getUserInfo(): Promise<UserInfo | undefined> {
50
50
  const apiToken = getAPIToken();
51
+ const apiTokenFromEnv = getCloudflareAPITokenFromEnv();
51
52
  return apiToken
52
53
  ? {
53
54
  apiToken,
54
- authType: "OAuth",
55
+ authType: apiTokenFromEnv ? "API" : "OAuth",
55
56
  email: await getEmail(),
56
57
  accounts: await getAccounts(),
57
58
  }
package/src/worker.ts CHANGED
@@ -176,5 +176,6 @@ export interface CfWorkerInit {
176
176
  export interface CfWorkerContext {
177
177
  env: string | undefined;
178
178
  legacyEnv: boolean | undefined;
179
- zone: { id: string; host: string } | undefined;
179
+ zone: string | undefined;
180
+ host: string | undefined;
180
181
  }