wrangler 2.4.2 → 2.4.3

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.
@@ -0,0 +1,145 @@
1
+ import fs from "node:fs";
2
+ import path from "path";
3
+ import { confirm } from "../../dialogs";
4
+ import { logger } from "../../logger";
5
+ import { DEFAULT_MIGRATION_PATH } from "../constants";
6
+ import { executeSql } from "../execute";
7
+ import type { ConfigFields, DevConfig, Environment } from "../../config";
8
+ import type { QueryResult } from "../execute";
9
+ import type { Migration } from "../types";
10
+
11
+ export async function getMigrationsPath(
12
+ projectPath: string,
13
+ migrationsFolderPath: string,
14
+ createIfMissing: boolean
15
+ ): Promise<string> {
16
+ const dir = path.resolve(projectPath, migrationsFolderPath);
17
+ if (fs.existsSync(dir)) return dir;
18
+
19
+ const warning = `No migrations folder found.${
20
+ migrationsFolderPath === DEFAULT_MIGRATION_PATH
21
+ ? " Set `migrations_dir` in wrangler.toml to choose a different path."
22
+ : ""
23
+ }`;
24
+
25
+ if (createIfMissing && (await confirm(`${warning}\nOk to create ${dir}?`))) {
26
+ fs.mkdirSync(dir, { recursive: true });
27
+ return dir;
28
+ } else {
29
+ logger.warn(warning);
30
+ }
31
+
32
+ throw new Error(`No migrations present at ${dir}.`);
33
+ }
34
+
35
+ export async function getUnappliedMigrations(
36
+ migrationsTableName: string,
37
+ migrationsPath: string,
38
+ local: undefined | boolean,
39
+ config: ConfigFields<DevConfig> & Environment,
40
+ name: string,
41
+ persistTo: undefined | string
42
+ ): Promise<Array<string>> {
43
+ const appliedMigrations = (
44
+ await listAppliedMigrations(
45
+ migrationsTableName,
46
+ local,
47
+ config,
48
+ name,
49
+ persistTo
50
+ )
51
+ ).map((migration) => {
52
+ return migration.name;
53
+ });
54
+ const projectMigrations = getMigrationNames(migrationsPath);
55
+
56
+ const unappliedMigrations: Array<string> = [];
57
+
58
+ for (const migration of projectMigrations) {
59
+ if (!appliedMigrations.includes(migration)) {
60
+ unappliedMigrations.push(migration);
61
+ }
62
+ }
63
+
64
+ return unappliedMigrations;
65
+ }
66
+
67
+ const listAppliedMigrations = async (
68
+ migrationsTableName: string,
69
+ local: undefined | boolean,
70
+ config: ConfigFields<DevConfig> & Environment,
71
+ name: string,
72
+ persistTo: undefined | string
73
+ ): Promise<Migration[]> => {
74
+ const Query = `SELECT *
75
+ FROM ${migrationsTableName}
76
+ ORDER BY id`;
77
+
78
+ const response: QueryResult[] | null = await executeSql(
79
+ local,
80
+ config,
81
+ name,
82
+ undefined,
83
+ persistTo,
84
+ undefined,
85
+ Query
86
+ );
87
+
88
+ if (!response || response[0].results.length === 0) return [];
89
+
90
+ return response[0].results as Migration[];
91
+ };
92
+
93
+ function getMigrationNames(migrationsPath: string): Array<string> {
94
+ const migrations = [];
95
+
96
+ const dir = fs.opendirSync(migrationsPath);
97
+
98
+ let dirent;
99
+ while ((dirent = dir.readSync()) !== null) {
100
+ if (dirent.name.endsWith(".sql")) migrations.push(dirent.name);
101
+ }
102
+
103
+ dir.closeSync();
104
+
105
+ return migrations;
106
+ }
107
+
108
+ export function getNextMigrationNumber(migrationsPath: string): number {
109
+ let highestMigrationNumber = -1;
110
+
111
+ for (const migration in getMigrationNames(migrationsPath)) {
112
+ const migrationNumber = parseInt(migration.split("_")[0]);
113
+
114
+ if (migrationNumber > highestMigrationNumber) {
115
+ highestMigrationNumber = migrationNumber;
116
+ }
117
+ }
118
+
119
+ return highestMigrationNumber + 1;
120
+ }
121
+
122
+ export const initMigrationsTable = async (
123
+ migrationsTableName: string,
124
+ local: undefined | boolean,
125
+ config: ConfigFields<DevConfig> & Environment,
126
+ name: string,
127
+ persistTo: undefined | string
128
+ ) => {
129
+ return executeSql(
130
+ local,
131
+ config,
132
+ name,
133
+ undefined,
134
+ persistTo,
135
+ undefined,
136
+ `
137
+ CREATE TABLE IF NOT EXISTS ${migrationsTableName}
138
+ (
139
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
140
+ name TEXT UNIQUE,
141
+ applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
142
+ );
143
+ `
144
+ );
145
+ };
@@ -0,0 +1,3 @@
1
+ export { ApplyHandler, ApplyOptions } from "./apply";
2
+ export { CreateHandler, CreateOptions } from "./create";
3
+ export { ListHandler, ListOptions } from "./list";
@@ -0,0 +1,79 @@
1
+ import path from "path";
2
+ import { Box, render, Text } from "ink";
3
+ import Table from "ink-table";
4
+ import React from "react";
5
+ import { withConfig } from "../../config";
6
+ import { logger } from "../../logger";
7
+ import { requireAuth } from "../../user";
8
+ import { Database } from "../options";
9
+ import { d1BetaWarning, getDatabaseInfoFromConfig } from "../utils";
10
+ import {
11
+ getMigrationsPath,
12
+ getUnappliedMigrations,
13
+ initMigrationsTable,
14
+ } from "./helpers";
15
+ import type { BaseSqlExecuteArgs } from "../execute";
16
+ import type { Argv } from "yargs";
17
+
18
+ export function ListOptions(yargs: Argv): Argv<BaseSqlExecuteArgs> {
19
+ return Database(yargs);
20
+ }
21
+
22
+ export const ListHandler = withConfig<BaseSqlExecuteArgs>(
23
+ async ({ config, database, local, persistTo }): Promise<void> => {
24
+ await requireAuth({});
25
+ logger.log(d1BetaWarning);
26
+
27
+ const databaseInfo = await getDatabaseInfoFromConfig(config, database);
28
+ if (!databaseInfo) {
29
+ throw new Error(
30
+ `Can't find a DB with name/binding '${database}' in local config. Check info in wrangler.toml...`
31
+ );
32
+ }
33
+
34
+ if (!config.configPath) {
35
+ return;
36
+ }
37
+ const { migrationsTableName, migrationsFolderPath } = databaseInfo;
38
+
39
+ const migrationsPath = await getMigrationsPath(
40
+ path.dirname(config.configPath),
41
+ migrationsFolderPath,
42
+ false
43
+ );
44
+ await initMigrationsTable(
45
+ migrationsTableName,
46
+ local,
47
+ config,
48
+ database,
49
+ persistTo
50
+ );
51
+
52
+ const unappliedMigrations = (
53
+ await getUnappliedMigrations(
54
+ migrationsTableName,
55
+ migrationsPath,
56
+ local,
57
+ config,
58
+ database,
59
+ persistTo
60
+ )
61
+ ).map((migration) => {
62
+ return {
63
+ Name: migration,
64
+ };
65
+ });
66
+
67
+ if (unappliedMigrations.length === 0) {
68
+ render(<Text>✅ No migrations to apply!</Text>);
69
+ return;
70
+ }
71
+
72
+ render(
73
+ <Box flexDirection="column">
74
+ <Text>Migrations to be applied:</Text>
75
+ <Table data={unappliedMigrations} columns={["Name"]}></Table>
76
+ </Box>
77
+ );
78
+ }
79
+ );
package/src/d1/utils.ts CHANGED
@@ -45,4 +45,4 @@ export const getDatabaseByNameOrBinding = async (
45
45
 
46
46
  export const d1BetaWarning = process.env.NO_D1_WARNING
47
47
  ? ""
48
- : "🚧 'wrangler d1 <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose";
48
+ : "🚧 D1 is currently in open alpha and is not recommended for production data and traffic.\nPlease report any bugs to https://github.com/cloudflare/wrangler2/issues/new/choose.\nTo request features, visit https://community.cloudflare.com/c/developers/d1.\nTo give feedback, visit https://discord.gg/cloudflaredev";
package/src/dev/dev.tsx CHANGED
@@ -288,6 +288,7 @@ function DevSession(props: DevSessionProps) {
288
288
  // Enable the bundling to know whether we are using dev or publish
289
289
  targetConsumer: "dev",
290
290
  testScheduled: props.testScheduled ?? false,
291
+ experimentalLocal: props.experimentalLocal,
291
292
  });
292
293
 
293
294
  // TODO(queues) support remote wrangler dev
package/src/dev/local.tsx CHANGED
@@ -839,17 +839,19 @@ export async function transformMf2OptionsToMf3Options({
839
839
  const root = path.dirname(bundle.path);
840
840
 
841
841
  assert.strictEqual(bundle.type, "esm");
842
+ // Required for source mapped paths to resolve correctly
843
+ options.modulesRoot = root;
842
844
  options.modules = [
843
845
  // Entrypoint
844
846
  {
845
847
  type: "ESModule",
846
- path: path.relative(root, bundle.path),
848
+ path: bundle.path,
847
849
  contents: await readFile(bundle.path, "utf-8"),
848
850
  },
849
851
  // Misc (WebAssembly, etc, ...)
850
852
  ...bundle.modules.map((module) => ({
851
853
  type: ModuleTypeToRuleType[module.type ?? "esm"],
852
- path: module.name,
854
+ path: path.resolve(root, module.name),
853
855
  contents: module.content,
854
856
  })),
855
857
  ];
@@ -99,6 +99,8 @@ export async function startDevServer(
99
99
  services: props.bindings.services,
100
100
  firstPartyWorkerDevFacade: props.firstPartyWorker,
101
101
  testScheduled: props.testScheduled,
102
+ local: props.local,
103
+ experimentalLocal: props.experimentalLocal,
102
104
  });
103
105
 
104
106
  if (props.local) {
@@ -206,6 +208,8 @@ async function runEsbuild({
206
208
  services,
207
209
  firstPartyWorkerDevFacade,
208
210
  testScheduled,
211
+ local,
212
+ experimentalLocal,
209
213
  }: {
210
214
  entry: Entry;
211
215
  destination: string | undefined;
@@ -224,6 +228,8 @@ async function runEsbuild({
224
228
  workerDefinitions: WorkerRegistry;
225
229
  firstPartyWorkerDevFacade: boolean | undefined;
226
230
  testScheduled?: boolean;
231
+ local: boolean;
232
+ experimentalLocal: boolean | undefined;
227
233
  }): Promise<EsbuildBundle | undefined> {
228
234
  if (!destination) return;
229
235
 
@@ -262,8 +268,9 @@ async function runEsbuild({
262
268
  services,
263
269
  firstPartyWorkerDevFacade,
264
270
  targetConsumer: "dev", // We are starting a dev server
265
- local: true,
266
271
  testScheduled,
272
+ local,
273
+ experimentalLocal,
267
274
  });
268
275
 
269
276
  return {
@@ -41,6 +41,7 @@ export function useEsbuild({
41
41
  local,
42
42
  targetConsumer,
43
43
  testScheduled,
44
+ experimentalLocal,
44
45
  }: {
45
46
  entry: Entry;
46
47
  destination: string | undefined;
@@ -62,6 +63,7 @@ export function useEsbuild({
62
63
  local: boolean;
63
64
  targetConsumer: "dev" | "publish";
64
65
  testScheduled: boolean;
66
+ experimentalLocal: boolean | undefined;
65
67
  }): EsbuildBundle | undefined {
66
68
  const [bundle, setBundle] = useState<EsbuildBundle>();
67
69
  const { exit } = useApp();
@@ -134,6 +136,7 @@ export function useEsbuild({
134
136
  local,
135
137
  targetConsumer,
136
138
  testScheduled,
139
+ experimentalLocal,
137
140
  });
138
141
 
139
142
  // Capture the `stop()` method to use as the `useEffect()` destructor.
@@ -195,6 +198,7 @@ export function useEsbuild({
195
198
  local,
196
199
  targetConsumer,
197
200
  testScheduled,
201
+ experimentalLocal,
198
202
  ]);
199
203
  return bundle;
200
204
  }
package/src/index.tsx CHANGED
@@ -445,6 +445,11 @@ export function createCLIParser(argv: string[]) {
445
445
  .option("scopes-list", {
446
446
  describe: "List all the available OAuth scopes with descriptions",
447
447
  })
448
+ .option("browser", {
449
+ default: true,
450
+ type: "boolean",
451
+ describe: "Automatically open the OAuth link in a browser",
452
+ })
448
453
  .option("scopes", {
449
454
  describe: "Pick the set of applicable OAuth scopes when logging in",
450
455
  array: true,
@@ -471,10 +476,10 @@ export function createCLIParser(argv: string[]) {
471
476
  `One of ${args.scopes} is not a valid authentication scope. Run "wrangler login --list-scopes" to see the valid scopes.`
472
477
  );
473
478
  }
474
- await login({ scopes: args.scopes });
479
+ await login({ scopes: args.scopes, browser: args.browser });
475
480
  return;
476
481
  }
477
- await login();
482
+ await login({ browser: args.browser });
478
483
  const config = readConfig(args.config as ConfigPath, args);
479
484
  await metrics.sendMetricsEvent("login user", {
480
485
  sendMetrics: config.send_metrics,
@@ -101,6 +101,7 @@ export function buildPlugin({
101
101
  checkFetch: local,
102
102
  targetConsumer: local ? "dev" : "publish",
103
103
  local,
104
+ experimentalLocal: false,
104
105
  }
105
106
  );
106
107
  }
@@ -160,6 +160,7 @@ export function buildWorker({
160
160
  checkFetch: local,
161
161
  targetConsumer: local ? "dev" : "publish",
162
162
  local,
163
+ experimentalLocal: false,
163
164
  }
164
165
  );
165
166
  }
@@ -487,6 +487,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
487
487
  // This could potentially cause issues as we no longer have identical behaviour between dev and publish?
488
488
  targetConsumer: "publish",
489
489
  local: false,
490
+ experimentalLocal: false,
490
491
  }
491
492
  );
492
493
 
@@ -1,5 +1,6 @@
1
1
  import { type BuilderCallback } from "yargs";
2
2
  import { type CommonYargsOptions } from "../../../yargs-types";
3
+ import { HandleUnauthorizedError } from "../../utils";
3
4
  import { consumers } from "./consumer";
4
5
 
5
6
  import { options as createOptions, handler as createHandler } from "./create";
@@ -30,4 +31,6 @@ export const queues: BuilderCallback<CommonYargsOptions, unknown> = (yargs) => {
30
31
  await consumers(consumersYargs);
31
32
  }
32
33
  );
34
+
35
+ yargs.fail(HandleUnauthorizedError);
33
36
  };
@@ -0,0 +1,18 @@
1
+ import { logger } from "../logger";
2
+ import { type ParseError } from "../parse";
3
+ import { getAccountId } from "../user";
4
+
5
+ const isFetchError = (err: unknown): err is ParseError => err instanceof Error;
6
+
7
+ export const HandleUnauthorizedError = async (_msg: string, err: Error) => {
8
+ //@ts-expect-error non-standard property on Error
9
+ if (isFetchError(err) && err.code === 10023) {
10
+ const accountId = await getAccountId();
11
+ if (accountId) {
12
+ return logger.log(
13
+ `Queues is not currently enabled on this account. Go to https://dash.cloudflare.com/${accountId}/workers/queues to enable it.`
14
+ );
15
+ }
16
+ }
17
+ throw err;
18
+ };
package/src/user/user.tsx CHANGED
@@ -875,6 +875,7 @@ export function readAuthConfigFile(): UserAuthConfig {
875
875
 
876
876
  type LoginProps = {
877
877
  scopes?: Scope[];
878
+ browser: boolean;
878
879
  };
879
880
 
880
881
  export async function loginOrRefreshIfRequired(): Promise<boolean> {
@@ -901,7 +902,9 @@ export async function loginOrRefreshIfRequired(): Promise<boolean> {
901
902
  }
902
903
  }
903
904
 
904
- export async function login(props?: LoginProps): Promise<boolean> {
905
+ export async function login(
906
+ props: LoginProps = { browser: true }
907
+ ): Promise<boolean> {
905
908
  logger.log("Attempting to login via OAuth...");
906
909
  const urlToOpen = await getAuthURL(props?.scopes);
907
910
  let server: http.Server;
@@ -914,7 +917,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
914
917
  server.close();
915
918
  clearTimeout(loginTimeoutHandle);
916
919
  resolve(false);
917
- }, 60000); // wait for 60 seconds for the user to authorize
920
+ }, 120000); // wait for 120 seconds for the user to authorize
918
921
  });
919
922
 
920
923
  const loginPromise = new Promise<boolean>((resolve, reject) => {
@@ -986,9 +989,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
986
989
 
987
990
  server.listen(8976);
988
991
  });
989
-
990
- logger.log(`Opening a link in your default browser: ${urlToOpen}`);
991
- await openInBrowser(urlToOpen);
992
+ if (props?.browser) {
993
+ logger.log(`Opening a link in your default browser: ${urlToOpen}`);
994
+ await openInBrowser(urlToOpen);
995
+ } else {
996
+ logger.log(`Visit this link to authenticate: ${urlToOpen}`);
997
+ }
992
998
 
993
999
  return Promise.race([timerPromise, loginPromise]);
994
1000
  }
@@ -1,8 +1,15 @@
1
- import { Awaitable, Dispatcher, Middleware, __facade_invoke__ } from "./common";
2
- export { __facade_register__, __facade_registerInternal__ } from "./common";
3
-
4
- // Miniflare's `EventTarget` follows the spec and doesn't allow exceptions to
5
- // be caught by `dispatchEvent`. Instead it has a custom`ThrowingEventTarget`
1
+ import {
2
+ Awaitable,
3
+ Dispatcher,
4
+ Middleware,
5
+ __facade_invoke__,
6
+ __facade_register__,
7
+ __facade_registerInternal__,
8
+ } from "./common";
9
+ export { __facade_register__, __facade_registerInternal__ };
10
+
11
+ // Miniflare 2's `EventTarget` follows the spec and doesn't allow exceptions to
12
+ // be caught by `dispatchEvent`. Instead it has a custom `ThrowingEventTarget`
6
13
  // class that rethrows errors from event listeners in `dispatchEvent`.
7
14
  // We'd like errors to be propagated to the top-level `addEventListener`, so
8
15
  // we'd like to use `ThrowingEventTarget`. Unfortunately, `ThrowingEventTarget`
@@ -15,45 +22,52 @@ if ((globalThis as any).MINIFLARE) {
15
22
  __FACADE_EVENT_TARGET__ = new EventTarget();
16
23
  }
17
24
 
18
- declare global {
19
- var __facade_addEventListener__: (
20
- type: string,
21
- listener: EventListenerOrEventListenerObject,
22
- options?: EventTargetAddEventListenerOptions | boolean
23
- ) => void;
24
- var __facade_removeEventListener__: (
25
- type: string,
26
- listener: EventListenerOrEventListenerObject,
27
- options?: EventTargetEventListenerOptions | boolean
28
- ) => void;
29
- var __facade_dispatchEvent__: (event: Event) => void;
30
- }
31
-
32
- function __facade_isSpecialEvent__(type: string) {
25
+ function __facade_isSpecialEvent__(
26
+ type: string
27
+ ): type is "fetch" | "scheduled" {
33
28
  return type === "fetch" || type === "scheduled";
34
29
  }
35
- globalThis.__facade_addEventListener__ = function (type, listener, options) {
30
+ const __facade__originalAddEventListener__ = globalThis.addEventListener;
31
+ const __facade__originalRemoveEventListener__ = globalThis.removeEventListener;
32
+ const __facade__originalDispatchEvent__ = globalThis.dispatchEvent;
33
+
34
+ globalThis.addEventListener = function (type, listener, options) {
36
35
  if (__facade_isSpecialEvent__(type)) {
37
- __FACADE_EVENT_TARGET__.addEventListener(type, listener, options);
36
+ __FACADE_EVENT_TARGET__.addEventListener(
37
+ type,
38
+ listener as EventListenerOrEventListenerObject,
39
+ options
40
+ );
38
41
  } else {
39
- globalThis.addEventListener(type as any, listener, options);
42
+ __facade__originalAddEventListener__(type, listener, options);
40
43
  }
41
44
  };
42
- globalThis.__facade_removeEventListener__ = function (type, listener, options) {
45
+ globalThis.removeEventListener = function (type, listener, options) {
43
46
  if (__facade_isSpecialEvent__(type)) {
44
- __FACADE_EVENT_TARGET__.removeEventListener(type, listener, options);
47
+ __FACADE_EVENT_TARGET__.removeEventListener(
48
+ type,
49
+ listener as EventListenerOrEventListenerObject,
50
+ options
51
+ );
45
52
  } else {
46
- globalThis.removeEventListener(type as any, listener, options);
53
+ __facade__originalRemoveEventListener__(type, listener, options);
47
54
  }
48
55
  };
49
- globalThis.__facade_dispatchEvent__ = function (event) {
56
+ globalThis.dispatchEvent = function (event) {
50
57
  if (__facade_isSpecialEvent__(event.type)) {
51
- __FACADE_EVENT_TARGET__.dispatchEvent(event);
58
+ return __FACADE_EVENT_TARGET__.dispatchEvent(event);
52
59
  } else {
53
- globalThis.dispatchEvent(event as any);
60
+ return __facade__originalDispatchEvent__(event);
54
61
  }
55
62
  };
56
63
 
64
+ declare global {
65
+ var addMiddleware: typeof __facade_register__;
66
+ var addMiddlewareInternal: typeof __facade_registerInternal__;
67
+ }
68
+ globalThis.addMiddleware = __facade_register__;
69
+ globalThis.addMiddlewareInternal = __facade_registerInternal__;
70
+
57
71
  const __facade_waitUntil__ = Symbol("__facade_waitUntil__");
58
72
  const __facade_response__ = Symbol("__facade_response__");
59
73
  const __facade_dispatched__ = Symbol("__facade_dispatched__");
@@ -154,7 +168,7 @@ class __Facade_ScheduledEvent__ extends __Facade_ExtendableEvent__ {
154
168
  }
155
169
  }
156
170
 
157
- globalThis.addEventListener("fetch", (event) => {
171
+ __facade__originalAddEventListener__("fetch", (event) => {
158
172
  const ctx: ExecutionContext = {
159
173
  waitUntil: event.waitUntil.bind(event),
160
174
  passThroughOnException: event.passThroughOnException.bind(event),
@@ -201,7 +215,7 @@ globalThis.addEventListener("fetch", (event) => {
201
215
  );
202
216
  });
203
217
 
204
- globalThis.addEventListener("scheduled", (event) => {
218
+ __facade__originalAddEventListener__("scheduled", (event) => {
205
219
  const facadeEvent = new __Facade_ScheduledEvent__("scheduled", {
206
220
  scheduledTime: event.scheduledTime,
207
221
  cron: event.cron,
@@ -0,0 +1,20 @@
1
+ import type { Middleware } from "./common";
2
+
3
+ // See comment in `bundle.ts` for details on why this is needed
4
+ const jsonError: Middleware = async (request, env, _ctx, middlewareCtx) => {
5
+ try {
6
+ return await middlewareCtx.next(request, env);
7
+ } catch (e: any) {
8
+ const error = {
9
+ name: e?.name,
10
+ message: e?.message ?? String(e),
11
+ stack: e?.stack,
12
+ };
13
+ return Response.json(error, {
14
+ status: 500,
15
+ headers: { "MF-Experimental-Error-Stack": "true" },
16
+ });
17
+ }
18
+ };
19
+
20
+ export default jsonError;