wrangler 2.4.4 → 2.6.0
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/bin/wrangler.js +20 -8
- package/miniflare-dist/index.mjs +90 -41
- package/package.json +3 -3
- package/src/__tests__/configuration.test.ts +211 -0
- package/src/__tests__/delete.test.ts +81 -48
- package/src/__tests__/dev.test.tsx +25 -8
- package/src/__tests__/helpers/mock-oauth-flow.ts +5 -1
- package/src/__tests__/helpers/msw/handlers/oauth.ts +13 -18
- package/src/__tests__/init.test.ts +18 -3
- package/src/__tests__/logout.test.ts +47 -0
- package/src/__tests__/metrics.test.ts +88 -43
- package/src/__tests__/pages-deployment-tail.test.ts +165 -101
- package/src/__tests__/publish.test.ts +94 -7
- package/src/__tests__/pubsub.test.ts +208 -88
- package/src/__tests__/queues.test.ts +155 -67
- package/src/__tests__/tail.test.ts +207 -108
- package/src/__tests__/type-generation.test.ts +7 -0
- package/src/__tests__/user.test.ts +43 -69
- package/src/config/environment.ts +16 -0
- package/src/config/index.ts +31 -8
- package/src/config/validation.ts +49 -0
- package/src/create-worker-upload-form.ts +9 -0
- package/src/d1/backups.tsx +7 -2
- package/src/d1/delete.tsx +4 -4
- package/src/d1/index.ts +2 -0
- package/src/d1/migrations/apply.tsx +6 -5
- package/src/d1/migrations/helpers.ts +4 -2
- package/src/d1/migrations/list.tsx +2 -2
- package/src/d1/migrations/options.ts +18 -0
- package/src/dev/dev.tsx +46 -22
- package/src/dev/local.tsx +63 -29
- package/src/dev/start-server.ts +18 -21
- package/src/dev.tsx +33 -13
- package/src/git-client.ts +15 -4
- package/src/index.tsx +1 -0
- package/src/init.ts +8 -0
- package/src/miniflare-cli/assets.ts +55 -28
- package/src/miniflare-cli/index.ts +2 -1
- package/src/pages/dev.tsx +7 -0
- package/src/proxy.ts +5 -0
- package/src/publish/publish.ts +1 -0
- package/src/secret/index.ts +1 -0
- package/src/type-generation.ts +10 -2
- package/src/worker.ts +6 -0
- package/wrangler-dist/cli.d.ts +15 -0
- package/wrangler-dist/cli.js +2045 -636
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import fetchMock from "jest-fetch-mock";
|
|
4
|
-
import { getGlobalWranglerConfigPath } from "../global-wrangler-config-path";
|
|
1
|
+
import { rest } from "msw";
|
|
5
2
|
import { CI } from "../is-ci";
|
|
6
3
|
import {
|
|
7
4
|
loginOrRefreshIfRequired,
|
|
8
5
|
readAuthConfigFile,
|
|
9
6
|
requireAuth,
|
|
10
|
-
USER_AUTH_CONFIG_FILE,
|
|
11
7
|
writeAuthConfigFile,
|
|
12
8
|
} from "../user";
|
|
13
9
|
import { mockConsoleMethods } from "./helpers/mock-console";
|
|
14
10
|
import { useMockIsTTY } from "./helpers/mock-istty";
|
|
15
11
|
import { mockOAuthFlow } from "./helpers/mock-oauth-flow";
|
|
12
|
+
import {
|
|
13
|
+
msw,
|
|
14
|
+
mswSuccessOauthHandlers,
|
|
15
|
+
mswSuccessUserHandlers,
|
|
16
|
+
} from "./helpers/msw";
|
|
16
17
|
import { runInTempDir } from "./helpers/run-in-tmp";
|
|
17
18
|
import { runWrangler } from "./helpers/run-wrangler";
|
|
18
19
|
import type { Config } from "../config";
|
|
@@ -22,41 +23,44 @@ describe("User", () => {
|
|
|
22
23
|
let isCISpy: jest.SpyInstance;
|
|
23
24
|
runInTempDir();
|
|
24
25
|
const std = mockConsoleMethods();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
mockGrantAccessToken,
|
|
28
|
-
mockGrantAuthorization,
|
|
29
|
-
mockRevokeAuthorization,
|
|
30
|
-
mockExchangeRefreshTokenForAccessToken,
|
|
31
|
-
} = mockOAuthFlow();
|
|
32
|
-
|
|
26
|
+
// TODO: Implement these two mocks with MSW
|
|
27
|
+
const { mockOAuthServerCallback } = mockOAuthFlow();
|
|
33
28
|
const { setIsTTY } = useMockIsTTY();
|
|
34
29
|
|
|
35
30
|
beforeEach(() => {
|
|
31
|
+
msw.use(...mswSuccessOauthHandlers, ...mswSuccessUserHandlers);
|
|
36
32
|
isCISpy = jest.spyOn(CI, "isCI").mockReturnValue(false);
|
|
37
33
|
});
|
|
38
34
|
|
|
39
35
|
describe("login", () => {
|
|
40
36
|
it("should login a user when `wrangler login` is run", async () => {
|
|
41
|
-
mockOAuthServerCallback();
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
mockOAuthServerCallback("success");
|
|
38
|
+
|
|
39
|
+
let counter = 0;
|
|
40
|
+
msw.use(
|
|
41
|
+
rest.post("*/oauth2/token", async (_, response, context) => {
|
|
42
|
+
counter += 1;
|
|
43
|
+
|
|
44
|
+
return response.once(
|
|
45
|
+
context.status(200),
|
|
46
|
+
context.json({
|
|
47
|
+
access_token: "test-access-token",
|
|
48
|
+
expires_in: 100000,
|
|
49
|
+
refresh_token: "test-refresh-token",
|
|
50
|
+
scope: "account:read",
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
})
|
|
54
|
+
);
|
|
44
55
|
|
|
45
56
|
await runWrangler("login");
|
|
46
57
|
|
|
47
|
-
expect(
|
|
48
|
-
accessTokenRequest.expected.url
|
|
49
|
-
);
|
|
50
|
-
expect(accessTokenRequest.actual.method).toEqual(
|
|
51
|
-
accessTokenRequest.expected.method
|
|
52
|
-
);
|
|
53
|
-
|
|
58
|
+
expect(counter).toBe(1);
|
|
54
59
|
expect(std.out).toMatchInlineSnapshot(`
|
|
55
60
|
"Attempting to login via OAuth...
|
|
56
61
|
Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?response_type=code&client_id=54d11594-84e4-41aa-b438-e81b8fa78ee7&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20offline_access&state=MOCK_STATE_PARAM&code_challenge=MOCK_CODE_CHALLENGE&code_challenge_method=S256
|
|
57
62
|
Successfully logged in."
|
|
58
63
|
`);
|
|
59
|
-
|
|
60
64
|
expect(readAuthConfigFile()).toEqual<UserAuthConfig>({
|
|
61
65
|
api_token: undefined,
|
|
62
66
|
oauth_token: "test-access-token",
|
|
@@ -67,50 +71,25 @@ describe("User", () => {
|
|
|
67
71
|
});
|
|
68
72
|
});
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
it("should exit with a message stating the user is not logged in", async () => {
|
|
72
|
-
await runWrangler("logout");
|
|
73
|
-
expect(std.out).toMatchInlineSnapshot(`"Not logged in, exiting..."`);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("should logout user that has been properly logged in", async () => {
|
|
77
|
-
writeAuthConfigFile({
|
|
78
|
-
oauth_token: "some-oauth-tok",
|
|
79
|
-
refresh_token: "some-refresh-tok",
|
|
80
|
-
});
|
|
81
|
-
const outcome = mockRevokeAuthorization();
|
|
82
|
-
|
|
83
|
-
await runWrangler("logout");
|
|
84
|
-
|
|
85
|
-
expect(outcome.actual.url).toEqual(
|
|
86
|
-
"https://dash.cloudflare.com/oauth2/revoke"
|
|
87
|
-
);
|
|
88
|
-
expect(outcome.actual.method).toEqual("POST");
|
|
89
|
-
|
|
90
|
-
expect(std.out).toMatchInlineSnapshot(`"Successfully logged out."`);
|
|
91
|
-
|
|
92
|
-
// Make sure that we made the request to logout.
|
|
93
|
-
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
94
|
-
|
|
95
|
-
// Make sure that logout removed the config file containing the auth tokens.
|
|
96
|
-
const config = path.join(
|
|
97
|
-
getGlobalWranglerConfigPath(),
|
|
98
|
-
USER_AUTH_CONFIG_FILE
|
|
99
|
-
);
|
|
100
|
-
expect(fs.existsSync(config)).toBeFalsy();
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// TODO: Improve OAuth mocking to handle `/token` endpoints from different calls
|
|
105
|
-
it("should handle errors for failed token refresh", async () => {
|
|
74
|
+
it("should handle errors for failed token refresh in a non-interactive environment", async () => {
|
|
106
75
|
setIsTTY(false);
|
|
107
76
|
writeAuthConfigFile({
|
|
108
77
|
oauth_token: "hunter2",
|
|
109
78
|
refresh_token: "Order 66",
|
|
110
79
|
});
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
80
|
+
// TODO: Use MSW to handle `/token` endpoints from different calls
|
|
81
|
+
let counter = 0;
|
|
82
|
+
msw.use(
|
|
83
|
+
rest.post("*/oauth2/token", async (request, response, context) => {
|
|
84
|
+
counter += 1;
|
|
85
|
+
return response.once(
|
|
86
|
+
context.status(400),
|
|
87
|
+
context.body(
|
|
88
|
+
`<html> <body> This shouldn't be sent, but should be handled </body> </html>`
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
})
|
|
92
|
+
);
|
|
114
93
|
|
|
115
94
|
// Handles the requireAuth error throw from failed login that is unhandled due to directly calling it here
|
|
116
95
|
await expect(
|
|
@@ -118,20 +97,15 @@ describe("User", () => {
|
|
|
118
97
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
119
98
|
`"In a non-interactive environment, it's necessary to set a CLOUDFLARE_API_TOKEN environment variable for wrangler to work. Please go to https://developers.cloudflare.com/api/tokens/create/ for instructions on how to create an api token, and assign its value to CLOUDFLARE_API_TOKEN."`
|
|
120
99
|
);
|
|
100
|
+
expect(counter).toBe(0);
|
|
121
101
|
});
|
|
122
102
|
|
|
123
103
|
it("should confirm no error message when refresh is successful", async () => {
|
|
124
104
|
setIsTTY(false);
|
|
125
|
-
mockOAuthServerCallback();
|
|
126
105
|
writeAuthConfigFile({
|
|
127
106
|
oauth_token: "hunter2",
|
|
128
107
|
refresh_token: "Order 66",
|
|
129
108
|
});
|
|
130
|
-
mockGrantAuthorization({ respondWith: "success" });
|
|
131
|
-
|
|
132
|
-
mockExchangeRefreshTokenForAccessToken({
|
|
133
|
-
respondWith: "refreshSuccess",
|
|
134
|
-
});
|
|
135
109
|
|
|
136
110
|
// Handles the requireAuth error throw from failed login that is unhandled due to directly calling it here
|
|
137
111
|
await expect(requireAuth({} as Config)).rejects.toThrowError();
|
|
@@ -439,6 +439,22 @@ interface EnvironmentNonInheritable {
|
|
|
439
439
|
}[]
|
|
440
440
|
| undefined;
|
|
441
441
|
|
|
442
|
+
/**
|
|
443
|
+
* Specifies analytics engine datasets that are bound to this Worker environment.
|
|
444
|
+
*
|
|
445
|
+
* NOTE: This field is not automatically inherited from the top level environment,
|
|
446
|
+
* and so must be specified in every named environment.
|
|
447
|
+
*
|
|
448
|
+
* @default `[]`
|
|
449
|
+
* @nonInheritable
|
|
450
|
+
*/
|
|
451
|
+
analytics_engine_datasets: {
|
|
452
|
+
/** The binding name used to refer to the dataset in the worker. */
|
|
453
|
+
binding: string;
|
|
454
|
+
/** The name of this dataset to write to. */
|
|
455
|
+
dataset?: string;
|
|
456
|
+
}[];
|
|
457
|
+
|
|
442
458
|
/**
|
|
443
459
|
* "Unsafe" tables for features that aren't directly supported by wrangler.
|
|
444
460
|
*
|
package/src/config/index.ts
CHANGED
|
@@ -93,6 +93,7 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
|
|
|
93
93
|
r2_buckets,
|
|
94
94
|
logfwdr,
|
|
95
95
|
services,
|
|
96
|
+
analytics_engine_datasets,
|
|
96
97
|
text_blobs,
|
|
97
98
|
unsafe,
|
|
98
99
|
vars,
|
|
@@ -159,14 +160,21 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
|
|
|
159
160
|
if (d1_databases !== undefined && d1_databases.length > 0) {
|
|
160
161
|
output.push({
|
|
161
162
|
type: "D1 Databases",
|
|
162
|
-
entries: d1_databases.map(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
163
|
+
entries: d1_databases.map(
|
|
164
|
+
({ binding, database_name, database_id, preview_database_id }) => {
|
|
165
|
+
let databaseValue = `${database_id}`;
|
|
166
|
+
if (database_name) {
|
|
167
|
+
databaseValue = `${database_name} (${database_id})`;
|
|
168
|
+
}
|
|
169
|
+
if (preview_database_id) {
|
|
170
|
+
databaseValue += `, Preview: (${preview_database_id})`;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
key: removeD1BetaPrefix(binding),
|
|
174
|
+
value: databaseValue,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
),
|
|
170
178
|
});
|
|
171
179
|
}
|
|
172
180
|
|
|
@@ -211,6 +219,21 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
|
|
|
211
219
|
});
|
|
212
220
|
}
|
|
213
221
|
|
|
222
|
+
if (
|
|
223
|
+
analytics_engine_datasets !== undefined &&
|
|
224
|
+
analytics_engine_datasets.length > 0
|
|
225
|
+
) {
|
|
226
|
+
output.push({
|
|
227
|
+
type: "Analytics Engine Datasets",
|
|
228
|
+
entries: analytics_engine_datasets.map(({ binding, dataset }) => {
|
|
229
|
+
return {
|
|
230
|
+
key: binding,
|
|
231
|
+
value: dataset ?? binding,
|
|
232
|
+
};
|
|
233
|
+
}),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
214
237
|
if (text_blobs !== undefined && Object.keys(text_blobs).length > 0) {
|
|
215
238
|
output.push({
|
|
216
239
|
type: "Text Blobs",
|
package/src/config/validation.ts
CHANGED
|
@@ -1115,6 +1115,16 @@ function normalizeAndValidateEnvironment(
|
|
|
1115
1115
|
validateBindingArray(envName, validateServiceBinding),
|
|
1116
1116
|
[]
|
|
1117
1117
|
),
|
|
1118
|
+
analytics_engine_datasets: notInheritable(
|
|
1119
|
+
diagnostics,
|
|
1120
|
+
topLevelEnv,
|
|
1121
|
+
rawConfig,
|
|
1122
|
+
rawEnv,
|
|
1123
|
+
envName,
|
|
1124
|
+
"analytics_engine_datasets",
|
|
1125
|
+
validateBindingArray(envName, validateAnalyticsEngineBinding),
|
|
1126
|
+
[]
|
|
1127
|
+
),
|
|
1118
1128
|
dispatch_namespaces: notInheritable(
|
|
1119
1129
|
diagnostics,
|
|
1120
1130
|
topLevelEnv,
|
|
@@ -1834,6 +1844,7 @@ const validateBindingsHaveUniqueNames = (
|
|
|
1834
1844
|
durable_objects,
|
|
1835
1845
|
kv_namespaces,
|
|
1836
1846
|
r2_buckets,
|
|
1847
|
+
analytics_engine_datasets,
|
|
1837
1848
|
text_blobs,
|
|
1838
1849
|
unsafe,
|
|
1839
1850
|
vars,
|
|
@@ -1848,6 +1859,7 @@ const validateBindingsHaveUniqueNames = (
|
|
|
1848
1859
|
"Durable Object": getBindingNames(durable_objects),
|
|
1849
1860
|
"KV Namespace": getBindingNames(kv_namespaces),
|
|
1850
1861
|
"R2 Bucket": getBindingNames(r2_buckets),
|
|
1862
|
+
"Analytics Engine Dataset": getBindingNames(analytics_engine_datasets),
|
|
1851
1863
|
"Text Blob": getBindingNames(text_blobs),
|
|
1852
1864
|
Unsafe: getBindingNames(unsafe),
|
|
1853
1865
|
"Environment Variable": getBindingNames(vars),
|
|
@@ -1956,6 +1968,43 @@ const validateServiceBinding: ValidatorFn = (diagnostics, field, value) => {
|
|
|
1956
1968
|
return isValid;
|
|
1957
1969
|
};
|
|
1958
1970
|
|
|
1971
|
+
const validateAnalyticsEngineBinding: ValidatorFn = (
|
|
1972
|
+
diagnostics,
|
|
1973
|
+
field,
|
|
1974
|
+
value
|
|
1975
|
+
) => {
|
|
1976
|
+
if (typeof value !== "object" || value === null) {
|
|
1977
|
+
diagnostics.errors.push(
|
|
1978
|
+
`"analytics_engine" bindings should be objects, but got ${JSON.stringify(
|
|
1979
|
+
value
|
|
1980
|
+
)}`
|
|
1981
|
+
);
|
|
1982
|
+
return false;
|
|
1983
|
+
}
|
|
1984
|
+
let isValid = true;
|
|
1985
|
+
// Service bindings must have a binding and optional dataset.
|
|
1986
|
+
if (!isRequiredProperty(value, "binding", "string")) {
|
|
1987
|
+
diagnostics.errors.push(
|
|
1988
|
+
`"${field}" bindings should have a string "binding" field but got ${JSON.stringify(
|
|
1989
|
+
value
|
|
1990
|
+
)}.`
|
|
1991
|
+
);
|
|
1992
|
+
isValid = false;
|
|
1993
|
+
}
|
|
1994
|
+
if (
|
|
1995
|
+
!isOptionalProperty(value, "dataset", "string") ||
|
|
1996
|
+
(value as { dataset: string }).dataset?.length === 0
|
|
1997
|
+
) {
|
|
1998
|
+
diagnostics.errors.push(
|
|
1999
|
+
`"${field}" bindings should, optionally, have a string "dataset" field but got ${JSON.stringify(
|
|
2000
|
+
value
|
|
2001
|
+
)}.`
|
|
2002
|
+
);
|
|
2003
|
+
isValid = false;
|
|
2004
|
+
}
|
|
2005
|
+
return isValid;
|
|
2006
|
+
};
|
|
2007
|
+
|
|
1959
2008
|
const validateWorkerNamespaceBinding: ValidatorFn = (
|
|
1960
2009
|
diagnostics,
|
|
1961
2010
|
field,
|
|
@@ -43,6 +43,7 @@ type WorkerMetadataBinding =
|
|
|
43
43
|
| { type: "r2_bucket"; name: string; bucket_name: string }
|
|
44
44
|
| { type: "d1"; name: string; id: string; internalEnv?: string }
|
|
45
45
|
| { type: "service"; name: string; service: string; environment?: string }
|
|
46
|
+
| { type: "analytics_engine"; name: string; dataset?: string }
|
|
46
47
|
| { type: "namespace"; name: string; namespace: string }
|
|
47
48
|
| {
|
|
48
49
|
type: "logfwdr";
|
|
@@ -149,6 +150,14 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
|
|
|
149
150
|
});
|
|
150
151
|
});
|
|
151
152
|
|
|
153
|
+
bindings.analytics_engine_datasets?.forEach(({ binding, dataset }) => {
|
|
154
|
+
metadataBindings.push({
|
|
155
|
+
name: binding,
|
|
156
|
+
type: "analytics_engine",
|
|
157
|
+
dataset,
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
152
161
|
bindings.dispatch_namespaces?.forEach(({ binding, namespace }) => {
|
|
153
162
|
metadataBindings.push({
|
|
154
163
|
name: binding,
|
package/src/d1/backups.tsx
CHANGED
|
@@ -141,9 +141,9 @@ export const RestoreHandler = withConfig<BackupRestoreArgs>(
|
|
|
141
141
|
name
|
|
142
142
|
);
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
logger.log(`Restoring ${name} from backup ${backupId}....`);
|
|
145
145
|
await restoreBackup(accountId, db.uuid, backupId);
|
|
146
|
-
|
|
146
|
+
logger.log(`Done!`);
|
|
147
147
|
}
|
|
148
148
|
);
|
|
149
149
|
|
|
@@ -199,6 +199,11 @@ export const DownloadHandler = withConfig<BackupDownloadArgs>(
|
|
|
199
199
|
|
|
200
200
|
logger.log(`🌀 Downloading backup ${backupId} from '${name}'`);
|
|
201
201
|
const response = await getBackupResponse(accountId, db.uuid, backupId);
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Failed to download backup ${backupId} from '${name}' - got ${response.status} from the API`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
202
207
|
logger.log(`🌀 Saving to ${filename}`);
|
|
203
208
|
// TODO: stream this once we upgrade to Node18 and can use Writable.fromWeb
|
|
204
209
|
const buffer = await response.arrayBuffer();
|
package/src/d1/delete.tsx
CHANGED
|
@@ -36,21 +36,21 @@ export const Handler = withConfig<CreateArgs>(
|
|
|
36
36
|
name
|
|
37
37
|
);
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
logger.log(`About to delete DB '${name}' (${db.uuid}).`);
|
|
40
40
|
if (!skipConfirmation) {
|
|
41
41
|
const response = await confirm(`Ok to proceed?`);
|
|
42
42
|
if (!response) {
|
|
43
|
-
|
|
43
|
+
logger.log(`Not deleting.`);
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
logger.log("Deleting...");
|
|
49
49
|
|
|
50
50
|
await fetchResult(`/accounts/${accountId}/d1/database/${db.uuid}`, {
|
|
51
51
|
method: "DELETE",
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
logger.log(`Deleted '${name}' successfully.`);
|
|
55
55
|
}
|
|
56
56
|
);
|
package/src/d1/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export const d1 = (yargs: Argv<CommonYargsOptions>) => {
|
|
|
26
26
|
)
|
|
27
27
|
.command("backup", "Interact with D1 Backups", (yargs2) =>
|
|
28
28
|
yargs2
|
|
29
|
+
.demandCommand()
|
|
29
30
|
.command(
|
|
30
31
|
"list <name>",
|
|
31
32
|
"List your D1 backups",
|
|
@@ -74,6 +75,7 @@ export const d1 = (yargs: Argv<CommonYargsOptions>) => {
|
|
|
74
75
|
)
|
|
75
76
|
.command("migrations", "Interact with D1 Migrations", (yargs2) =>
|
|
76
77
|
yargs2
|
|
78
|
+
.demandCommand()
|
|
77
79
|
.command(
|
|
78
80
|
"list <database>",
|
|
79
81
|
"List your D1 migrations",
|
|
@@ -5,23 +5,25 @@ import Table from "ink-table";
|
|
|
5
5
|
import React from "react";
|
|
6
6
|
import { withConfig } from "../../config";
|
|
7
7
|
import { confirm } from "../../dialogs";
|
|
8
|
+
import { CI } from "../../is-ci";
|
|
9
|
+
import isInteractive from "../../is-interactive";
|
|
8
10
|
import { logger } from "../../logger";
|
|
9
11
|
import { requireAuth } from "../../user";
|
|
10
12
|
import { createBackup } from "../backups";
|
|
11
13
|
import { executeSql } from "../execute";
|
|
12
|
-
import { Database } from "../options";
|
|
13
14
|
import { d1BetaWarning, getDatabaseInfoFromConfig } from "../utils";
|
|
14
15
|
import {
|
|
15
16
|
getMigrationsPath,
|
|
16
17
|
getUnappliedMigrations,
|
|
17
18
|
initMigrationsTable,
|
|
18
19
|
} from "./helpers";
|
|
20
|
+
import { DatabaseWithLocal } from "./options";
|
|
19
21
|
import type { ParseError } from "../../parse";
|
|
20
22
|
import type { BaseSqlExecuteArgs } from "../execute";
|
|
21
23
|
import type { Argv } from "yargs";
|
|
22
24
|
|
|
23
25
|
export function ApplyOptions(yargs: Argv): Argv<BaseSqlExecuteArgs> {
|
|
24
|
-
return
|
|
26
|
+
return DatabaseWithLocal(yargs);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export const ApplyHandler = withConfig<BaseSqlExecuteArgs>(
|
|
@@ -88,8 +90,7 @@ export const ApplyHandler = withConfig<BaseSqlExecuteArgs>(
|
|
|
88
90
|
return;
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
if (isInteractive) {
|
|
93
|
+
if (isInteractive() && !CI.isCI()) {
|
|
93
94
|
const ok = await confirm(
|
|
94
95
|
`About to apply ${unappliedMigrations.length} migration(s)\n` +
|
|
95
96
|
"Your database may not be available to serve requests during the migration, continue?",
|
|
@@ -121,7 +122,7 @@ export const ApplyHandler = withConfig<BaseSqlExecuteArgs>(
|
|
|
121
122
|
local,
|
|
122
123
|
config,
|
|
123
124
|
database,
|
|
124
|
-
|
|
125
|
+
isInteractive() && !CI.isCI(),
|
|
125
126
|
persistTo,
|
|
126
127
|
undefined,
|
|
127
128
|
query
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { confirm } from "../../dialogs";
|
|
4
|
+
import { CI } from "../../is-ci";
|
|
5
|
+
import isInteractive from "../../is-interactive";
|
|
4
6
|
import { logger } from "../../logger";
|
|
5
7
|
import { DEFAULT_MIGRATION_PATH } from "../constants";
|
|
6
8
|
import { executeSql } from "../execute";
|
|
@@ -79,7 +81,7 @@ const listAppliedMigrations = async (
|
|
|
79
81
|
local,
|
|
80
82
|
config,
|
|
81
83
|
name,
|
|
82
|
-
|
|
84
|
+
isInteractive() && !CI.isCI(),
|
|
83
85
|
persistTo,
|
|
84
86
|
undefined,
|
|
85
87
|
Query
|
|
@@ -130,7 +132,7 @@ export const initMigrationsTable = async (
|
|
|
130
132
|
local,
|
|
131
133
|
config,
|
|
132
134
|
name,
|
|
133
|
-
|
|
135
|
+
isInteractive() && !CI.isCI(),
|
|
134
136
|
persistTo,
|
|
135
137
|
undefined,
|
|
136
138
|
`
|
|
@@ -5,18 +5,18 @@ import React from "react";
|
|
|
5
5
|
import { withConfig } from "../../config";
|
|
6
6
|
import { logger } from "../../logger";
|
|
7
7
|
import { requireAuth } from "../../user";
|
|
8
|
-
import { Database } from "../options";
|
|
9
8
|
import { d1BetaWarning, getDatabaseInfoFromConfig } from "../utils";
|
|
10
9
|
import {
|
|
11
10
|
getMigrationsPath,
|
|
12
11
|
getUnappliedMigrations,
|
|
13
12
|
initMigrationsTable,
|
|
14
13
|
} from "./helpers";
|
|
14
|
+
import { DatabaseWithLocal } from "./options";
|
|
15
15
|
import type { BaseSqlExecuteArgs } from "../execute";
|
|
16
16
|
import type { Argv } from "yargs";
|
|
17
17
|
|
|
18
18
|
export function ListOptions(yargs: Argv): Argv<BaseSqlExecuteArgs> {
|
|
19
|
-
return
|
|
19
|
+
return DatabaseWithLocal(yargs);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export const ListHandler = withConfig<BaseSqlExecuteArgs>(
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Database } from "../options";
|
|
2
|
+
import type { Argv } from "yargs";
|
|
3
|
+
|
|
4
|
+
export function DatabaseWithLocal(yargs: Argv) {
|
|
5
|
+
return Database(yargs)
|
|
6
|
+
.option("local", {
|
|
7
|
+
describe:
|
|
8
|
+
"Execute commands/files against a local DB for use with wrangler dev --local",
|
|
9
|
+
type: "boolean",
|
|
10
|
+
})
|
|
11
|
+
.option("persist-to", {
|
|
12
|
+
describe:
|
|
13
|
+
"Specify directory to use for local persistence (you must use --local with this flag)",
|
|
14
|
+
type: "string",
|
|
15
|
+
requiresArg: true,
|
|
16
|
+
})
|
|
17
|
+
.implies("persist-to", "local");
|
|
18
|
+
}
|
package/src/dev/dev.tsx
CHANGED
|
@@ -113,8 +113,8 @@ export type DevProps = {
|
|
|
113
113
|
name: string | undefined;
|
|
114
114
|
noBundle: boolean;
|
|
115
115
|
entry: Entry;
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
initialPort: number;
|
|
117
|
+
initialIp: string;
|
|
118
118
|
inspectorPort: number;
|
|
119
119
|
rules: Config["rules"];
|
|
120
120
|
accountId: string | undefined;
|
|
@@ -171,25 +171,42 @@ export function DevImplementation(props: DevProps): JSX.Element {
|
|
|
171
171
|
);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
// This is a nasty hack to allow `useHotkeys` and its "[b] open a browser" feature to read these values
|
|
175
|
+
// without triggering a re-render loop when `onReady()` updates them.
|
|
176
|
+
// The initially requested port can be different than what's actually used, if, for example, you request port 0.
|
|
177
|
+
let ip: string;
|
|
178
|
+
let port: number;
|
|
179
|
+
|
|
174
180
|
function InteractiveDevSession(props: DevProps) {
|
|
175
181
|
const toggles = useHotkeys({
|
|
176
182
|
initial: {
|
|
177
183
|
local: props.initialMode === "local",
|
|
178
184
|
tunnel: false,
|
|
179
185
|
},
|
|
180
|
-
port: props.port,
|
|
181
|
-
ip: props.ip,
|
|
182
186
|
inspectorPort: props.inspectorPort,
|
|
183
187
|
inspect: props.inspect,
|
|
184
188
|
localProtocol: props.localProtocol,
|
|
185
189
|
forceLocal: props.forceLocal,
|
|
186
190
|
});
|
|
187
191
|
|
|
192
|
+
ip = props.initialIp;
|
|
193
|
+
port = props.initialPort;
|
|
194
|
+
|
|
188
195
|
useTunnel(toggles.tunnel);
|
|
189
196
|
|
|
197
|
+
const onReady = (newIp: string, newPort: number) => {
|
|
198
|
+
if (newIp !== props.initialIp || newPort !== props.initialPort) {
|
|
199
|
+
ip = newIp;
|
|
200
|
+
port = newPort;
|
|
201
|
+
if (props.onReady) {
|
|
202
|
+
props.onReady(newIp, newPort);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
190
207
|
return (
|
|
191
208
|
<>
|
|
192
|
-
<DevSession {...props} local={toggles.local} />
|
|
209
|
+
<DevSession {...props} local={toggles.local} onReady={onReady} />
|
|
193
210
|
<Box borderStyle="round" paddingLeft={1} paddingRight={1}>
|
|
194
211
|
<Text bold={true}>[b]</Text>
|
|
195
212
|
<Text> open a browser, </Text>
|
|
@@ -301,6 +318,22 @@ function DevSession(props: DevSessionProps) {
|
|
|
301
318
|
);
|
|
302
319
|
}
|
|
303
320
|
|
|
321
|
+
const announceAndOnReady: typeof props.onReady = (finalIp, finalPort) => {
|
|
322
|
+
if (process.send) {
|
|
323
|
+
process.send(
|
|
324
|
+
JSON.stringify({
|
|
325
|
+
event: "DEV_SERVER_READY",
|
|
326
|
+
ip: finalIp,
|
|
327
|
+
port: finalPort,
|
|
328
|
+
})
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (props.onReady) {
|
|
333
|
+
props.onReady(finalIp, finalPort);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
304
337
|
return props.local ? (
|
|
305
338
|
<Local
|
|
306
339
|
name={props.name}
|
|
@@ -312,8 +345,8 @@ function DevSession(props: DevSessionProps) {
|
|
|
312
345
|
bindings={props.bindings}
|
|
313
346
|
workerDefinitions={workerDefinitions}
|
|
314
347
|
assetPaths={props.assetPaths}
|
|
315
|
-
|
|
316
|
-
|
|
348
|
+
initialPort={props.initialPort}
|
|
349
|
+
initialIp={props.initialIp}
|
|
317
350
|
rules={props.rules}
|
|
318
351
|
inspectorPort={props.inspectorPort}
|
|
319
352
|
localPersistencePath={props.localPersistencePath}
|
|
@@ -324,7 +357,7 @@ function DevSession(props: DevSessionProps) {
|
|
|
324
357
|
localUpstream={props.localUpstream}
|
|
325
358
|
logPrefix={props.logPrefix}
|
|
326
359
|
inspect={props.inspect}
|
|
327
|
-
onReady={
|
|
360
|
+
onReady={announceAndOnReady}
|
|
328
361
|
enablePagesAssetsServiceBinding={props.enablePagesAssetsServiceBinding}
|
|
329
362
|
experimentalLocal={props.experimentalLocal}
|
|
330
363
|
accountId={props.accountId}
|
|
@@ -339,8 +372,8 @@ function DevSession(props: DevSessionProps) {
|
|
|
339
372
|
bindings={props.bindings}
|
|
340
373
|
assetPaths={props.assetPaths}
|
|
341
374
|
isWorkersSite={props.isWorkersSite}
|
|
342
|
-
port={props.
|
|
343
|
-
ip={props.
|
|
375
|
+
port={props.initialPort}
|
|
376
|
+
ip={props.initialIp}
|
|
344
377
|
localProtocol={props.localProtocol}
|
|
345
378
|
inspectorPort={props.inspectorPort}
|
|
346
379
|
// TODO: @threepointone #1167
|
|
@@ -354,7 +387,7 @@ function DevSession(props: DevSessionProps) {
|
|
|
354
387
|
zone={props.zone}
|
|
355
388
|
host={props.host}
|
|
356
389
|
routes={props.routes}
|
|
357
|
-
onReady={
|
|
390
|
+
onReady={announceAndOnReady}
|
|
358
391
|
sourceMapPath={bundle?.sourceMapPath}
|
|
359
392
|
sendMetrics={props.sendMetrics}
|
|
360
393
|
/>
|
|
@@ -503,25 +536,16 @@ type useHotkeysInitialState = {
|
|
|
503
536
|
};
|
|
504
537
|
function useHotkeys(props: {
|
|
505
538
|
initial: useHotkeysInitialState;
|
|
506
|
-
port: number;
|
|
507
|
-
ip: string;
|
|
508
539
|
inspectorPort: number;
|
|
509
540
|
inspect: boolean;
|
|
510
541
|
localProtocol: "http" | "https";
|
|
511
542
|
forceLocal: boolean | undefined;
|
|
512
543
|
}) {
|
|
513
|
-
const {
|
|
514
|
-
initial,
|
|
515
|
-
port,
|
|
516
|
-
ip,
|
|
517
|
-
inspectorPort,
|
|
518
|
-
inspect,
|
|
519
|
-
localProtocol,
|
|
520
|
-
forceLocal,
|
|
521
|
-
} = props;
|
|
544
|
+
const { initial, inspectorPort, inspect, localProtocol, forceLocal } = props;
|
|
522
545
|
// UGH, we should put port in context instead
|
|
523
546
|
const [toggles, setToggles] = useState(initial);
|
|
524
547
|
const { exit } = useApp();
|
|
548
|
+
|
|
525
549
|
useInput(
|
|
526
550
|
async (
|
|
527
551
|
input,
|