wrangler 2.0.7 → 2.0.11
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/README.md +1 -1
- package/bin/wrangler.js +16 -4
- package/package.json +2 -2
- package/src/__tests__/configuration.test.ts +165 -70
- package/src/__tests__/dev.test.tsx +158 -66
- package/src/__tests__/helpers/mock-dialogs.ts +41 -1
- package/src/__tests__/init.test.ts +191 -111
- package/src/__tests__/kv.test.ts +8 -8
- package/src/__tests__/package-manager.test.ts +154 -7
- package/src/__tests__/pages.test.ts +115 -18
- package/src/__tests__/publish.test.ts +431 -140
- package/src/__tests__/secret.test.ts +4 -4
- package/src/__tests__/whoami.test.tsx +34 -0
- package/src/cfetch/index.ts +17 -2
- package/src/cfetch/internal.ts +12 -9
- package/src/config/config.ts +1 -1
- package/src/config/validation-helpers.ts +10 -1
- package/src/config/validation.ts +59 -33
- package/src/create-worker-preview.ts +15 -15
- package/src/dev/dev.tsx +4 -15
- package/src/dev/remote.tsx +26 -16
- package/src/dialogs.tsx +48 -0
- package/src/index.tsx +181 -167
- package/src/package-manager.ts +50 -3
- package/src/pages.tsx +298 -228
- package/src/publish.ts +148 -15
- package/src/sites.tsx +52 -14
- package/src/user.tsx +12 -1
- package/src/whoami.tsx +3 -2
- package/src/worker.ts +2 -1
- package/src/zones.ts +73 -0
- package/templates/new-worker-scheduled.js +17 -0
- package/templates/new-worker-scheduled.ts +32 -0
- package/wrangler-dist/cli.js +707 -407
|
@@ -148,7 +148,7 @@ describe("wrangler secret", () => {
|
|
|
148
148
|
}
|
|
149
149
|
expect(std.out).toMatchInlineSnapshot(`
|
|
150
150
|
"
|
|
151
|
-
[32mIf you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new
|
|
151
|
+
[32mIf you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose[0m"
|
|
152
152
|
`);
|
|
153
153
|
expect(std.err).toMatchInlineSnapshot(`
|
|
154
154
|
"[31mX [41;31m[[41;97mERROR[41;31m][0m [1mMissing script name[0m
|
|
@@ -204,7 +204,7 @@ describe("wrangler secret", () => {
|
|
|
204
204
|
|
|
205
205
|
expect(std.out).toMatchInlineSnapshot(`
|
|
206
206
|
"
|
|
207
|
-
[32mIf you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new
|
|
207
|
+
[32mIf you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose[0m"
|
|
208
208
|
`);
|
|
209
209
|
expect(std.warn).toMatchInlineSnapshot(`""`);
|
|
210
210
|
});
|
|
@@ -372,7 +372,7 @@ describe("wrangler secret", () => {
|
|
|
372
372
|
}
|
|
373
373
|
expect(std.out).toMatchInlineSnapshot(`
|
|
374
374
|
"
|
|
375
|
-
[32mIf you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new
|
|
375
|
+
[32mIf you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose[0m"
|
|
376
376
|
`);
|
|
377
377
|
expect(std.err).toMatchInlineSnapshot(`
|
|
378
378
|
"[31mX [41;31m[[41;97mERROR[41;31m][0m [1mMissing script name[0m
|
|
@@ -468,7 +468,7 @@ describe("wrangler secret", () => {
|
|
|
468
468
|
}
|
|
469
469
|
expect(std.out).toMatchInlineSnapshot(`
|
|
470
470
|
"
|
|
471
|
-
[32mIf you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new
|
|
471
|
+
[32mIf you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose[0m"
|
|
472
472
|
`);
|
|
473
473
|
expect(std.err).toMatchInlineSnapshot(`
|
|
474
474
|
"[31mX [41;31m[[41;97mERROR[41;31m][0m [1mMissing script name[0m
|
|
@@ -9,6 +9,8 @@ import { runInTempDir } from "./helpers/run-in-tmp";
|
|
|
9
9
|
import type { UserInfo } from "../whoami";
|
|
10
10
|
|
|
11
11
|
describe("getUserInfo()", () => {
|
|
12
|
+
const ENV_COPY = process.env;
|
|
13
|
+
|
|
12
14
|
runInTempDir({ homedir: "./home" });
|
|
13
15
|
const std = mockConsoleMethods();
|
|
14
16
|
const { setIsTTY } = useMockIsTTY();
|
|
@@ -17,6 +19,10 @@ describe("getUserInfo()", () => {
|
|
|
17
19
|
setIsTTY(true);
|
|
18
20
|
});
|
|
19
21
|
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
process.env = ENV_COPY;
|
|
24
|
+
});
|
|
25
|
+
|
|
20
26
|
it("should return undefined if there is no config file", async () => {
|
|
21
27
|
const userInfo = await getUserInfo();
|
|
22
28
|
expect(userInfo).toBeUndefined();
|
|
@@ -28,6 +34,34 @@ describe("getUserInfo()", () => {
|
|
|
28
34
|
expect(userInfo).toBeUndefined();
|
|
29
35
|
});
|
|
30
36
|
|
|
37
|
+
it("should say it's using an API token when one is set", async () => {
|
|
38
|
+
process.env = {
|
|
39
|
+
CLOUDFLARE_API_TOKEN: "123456789",
|
|
40
|
+
};
|
|
41
|
+
setMockResponse("/user", () => {
|
|
42
|
+
return { email: "user@example.com" };
|
|
43
|
+
});
|
|
44
|
+
setMockResponse("/accounts", () => {
|
|
45
|
+
return [
|
|
46
|
+
{ name: "Account One", id: "account-1" },
|
|
47
|
+
{ name: "Account Two", id: "account-2" },
|
|
48
|
+
{ name: "Account Three", id: "account-3" },
|
|
49
|
+
];
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const userInfo = await getUserInfo();
|
|
53
|
+
expect(userInfo).toEqual({
|
|
54
|
+
authType: "API",
|
|
55
|
+
apiToken: "123456789",
|
|
56
|
+
email: "user@example.com",
|
|
57
|
+
accounts: [
|
|
58
|
+
{ name: "Account One", id: "account-1" },
|
|
59
|
+
{ name: "Account Two", id: "account-2" },
|
|
60
|
+
{ name: "Account Three", id: "account-3" },
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
31
65
|
it("should return the user's email and accounts if authenticated via config token", async () => {
|
|
32
66
|
writeAuthConfigFile({ oauth_token: "some-oauth-token" });
|
|
33
67
|
|
package/src/cfetch/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { getCloudflareAPIBaseURL as getCloudflareApiBaseUrl } from "./internal";
|
|
|
10
10
|
export interface FetchError {
|
|
11
11
|
code: number;
|
|
12
12
|
message: string;
|
|
13
|
+
error_chain?: FetchError[];
|
|
13
14
|
}
|
|
14
15
|
export interface FetchResult<ResponseType = unknown> {
|
|
15
16
|
success: boolean;
|
|
@@ -85,9 +86,9 @@ function throwFetchError(
|
|
|
85
86
|
response: FetchResult<unknown>
|
|
86
87
|
): never {
|
|
87
88
|
const error = new ParseError({
|
|
88
|
-
text:
|
|
89
|
+
text: `A request to the Cloudflare API (${resource}) failed.`,
|
|
89
90
|
notes: response.errors.map((err) => ({
|
|
90
|
-
text: err
|
|
91
|
+
text: renderError(err),
|
|
91
92
|
})),
|
|
92
93
|
});
|
|
93
94
|
// add the first error code directly to this error
|
|
@@ -104,3 +105,17 @@ function hasCursor(result_info: unknown): result_info is { cursor: string } {
|
|
|
104
105
|
const cursor = (result_info as { cursor: string } | undefined)?.cursor;
|
|
105
106
|
return cursor !== undefined && cursor !== null && cursor !== "";
|
|
106
107
|
}
|
|
108
|
+
|
|
109
|
+
function renderError(err: FetchError, level = 0): string {
|
|
110
|
+
const chainedMessages =
|
|
111
|
+
err.error_chain
|
|
112
|
+
?.map(
|
|
113
|
+
(chainedError) =>
|
|
114
|
+
`\n${" ".repeat(level)}- ${renderError(chainedError, level + 1)}`
|
|
115
|
+
)
|
|
116
|
+
.join("\n") ?? "";
|
|
117
|
+
return (
|
|
118
|
+
(err.code ? `${err.message} [code: ${err.code}]` : err.message) +
|
|
119
|
+
chainedMessages
|
|
120
|
+
);
|
|
121
|
+
}
|
package/src/cfetch/internal.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
1
2
|
import { fetch, Headers } from "undici";
|
|
3
|
+
import { version as wranglerVersion } from "../../package.json";
|
|
2
4
|
import { getEnvironmentVariableFactory } from "../environment-variables";
|
|
3
5
|
import { ParseError, parseJSON } from "../parse";
|
|
4
|
-
import {
|
|
6
|
+
import { loginOrRefreshIfRequired, requireApiToken } from "../user";
|
|
5
7
|
import type { URLSearchParams } from "node:url";
|
|
6
8
|
import type { RequestInit, HeadersInit } from "undici";
|
|
7
9
|
|
|
@@ -29,10 +31,15 @@ export async function fetchInternal<ResponseType>(
|
|
|
29
31
|
queryParams?: URLSearchParams,
|
|
30
32
|
abortSignal?: AbortSignal
|
|
31
33
|
): Promise<ResponseType> {
|
|
34
|
+
assert(
|
|
35
|
+
resource.startsWith("/"),
|
|
36
|
+
`CF API fetch - resource path must start with a "/" but got "${resource}"`
|
|
37
|
+
);
|
|
32
38
|
await requireLoggedIn();
|
|
33
39
|
const apiToken = requireApiToken();
|
|
34
40
|
const headers = cloneHeaders(init.headers);
|
|
35
41
|
addAuthorizationHeaderIfUnspecified(headers, apiToken);
|
|
42
|
+
addUserAgent(headers);
|
|
36
43
|
|
|
37
44
|
const queryString = queryParams ? `?${queryParams.toString()}` : "";
|
|
38
45
|
const method = init.method ?? "GET";
|
|
@@ -88,14 +95,6 @@ async function requireLoggedIn(): Promise<void> {
|
|
|
88
95
|
}
|
|
89
96
|
}
|
|
90
97
|
|
|
91
|
-
function requireApiToken(): string {
|
|
92
|
-
const authToken = getAPIToken();
|
|
93
|
-
if (!authToken) {
|
|
94
|
-
throw new Error("No API token found.");
|
|
95
|
-
}
|
|
96
|
-
return authToken;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
98
|
function addAuthorizationHeaderIfUnspecified(
|
|
100
99
|
headers: Record<string, string>,
|
|
101
100
|
apiToken: string
|
|
@@ -105,6 +104,10 @@ function addAuthorizationHeaderIfUnspecified(
|
|
|
105
104
|
}
|
|
106
105
|
}
|
|
107
106
|
|
|
107
|
+
function addUserAgent(headers: Record<string, string>): void {
|
|
108
|
+
headers["User-Agent"] = `wrangler/${wranglerVersion}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
108
111
|
/**
|
|
109
112
|
* The implementation for fetching a kv value from the cloudflare API.
|
|
110
113
|
* We special-case this one call, because it's the only API call that
|
package/src/config/config.ts
CHANGED
|
@@ -277,12 +277,21 @@ export const isObjectWith =
|
|
|
277
277
|
!properties.every((prop) => prop in value))
|
|
278
278
|
) {
|
|
279
279
|
diagnostics.errors.push(
|
|
280
|
-
`Expected "${field}" to be of type object, containing properties ${properties}, but got ${JSON.stringify(
|
|
280
|
+
`Expected "${field}" to be of type object, containing only properties ${properties}, but got ${JSON.stringify(
|
|
281
281
|
value
|
|
282
282
|
)}.`
|
|
283
283
|
);
|
|
284
284
|
return false;
|
|
285
285
|
}
|
|
286
|
+
// it's an object with the field as desired,
|
|
287
|
+
// but let's also check for unexpected fields
|
|
288
|
+
if (value !== undefined) {
|
|
289
|
+
const restFields = Object.keys(value).filter(
|
|
290
|
+
(key) => !properties.includes(key)
|
|
291
|
+
);
|
|
292
|
+
validateAdditionalProperties(diagnostics, field, restFields, []);
|
|
293
|
+
}
|
|
294
|
+
|
|
286
295
|
return true;
|
|
287
296
|
};
|
|
288
297
|
|
package/src/config/validation.ts
CHANGED
|
@@ -94,22 +94,6 @@ export function normalizeAndValidateConfig(
|
|
|
94
94
|
"boolean"
|
|
95
95
|
);
|
|
96
96
|
|
|
97
|
-
validateOptionalProperty(
|
|
98
|
-
diagnostics,
|
|
99
|
-
"",
|
|
100
|
-
"minify",
|
|
101
|
-
rawConfig.minify,
|
|
102
|
-
"boolean"
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
validateOptionalProperty(
|
|
106
|
-
diagnostics,
|
|
107
|
-
"",
|
|
108
|
-
"node_compat",
|
|
109
|
-
rawConfig.node_compat,
|
|
110
|
-
"boolean"
|
|
111
|
-
);
|
|
112
|
-
|
|
113
97
|
// TODO: set the default to false to turn on service environments as the default
|
|
114
98
|
const isLegacyEnv =
|
|
115
99
|
(args as { "legacy-env": boolean | undefined })["legacy-env"] ??
|
|
@@ -400,29 +384,38 @@ function normalizeAndValidateMigrations(
|
|
|
400
384
|
return [];
|
|
401
385
|
} else {
|
|
402
386
|
for (let i = 0; i < rawMigrations.length; i++) {
|
|
403
|
-
const
|
|
387
|
+
const { tag, new_classes, renamed_classes, deleted_classes, ...rest } =
|
|
388
|
+
rawMigrations[i];
|
|
389
|
+
|
|
390
|
+
validateAdditionalProperties(
|
|
391
|
+
diagnostics,
|
|
392
|
+
"migrations",
|
|
393
|
+
Object.keys(rest),
|
|
394
|
+
[]
|
|
395
|
+
);
|
|
396
|
+
|
|
404
397
|
validateRequiredProperty(
|
|
405
398
|
diagnostics,
|
|
406
399
|
`migrations[${i}]`,
|
|
407
400
|
`tag`,
|
|
408
|
-
|
|
401
|
+
tag,
|
|
409
402
|
"string"
|
|
410
403
|
);
|
|
411
404
|
validateOptionalTypedArray(
|
|
412
405
|
diagnostics,
|
|
413
406
|
`migrations[${i}].new_classes`,
|
|
414
|
-
|
|
407
|
+
new_classes,
|
|
415
408
|
"string"
|
|
416
409
|
);
|
|
417
|
-
if (
|
|
418
|
-
if (!Array.isArray(
|
|
410
|
+
if (renamed_classes !== undefined) {
|
|
411
|
+
if (!Array.isArray(renamed_classes)) {
|
|
419
412
|
diagnostics.errors.push(
|
|
420
413
|
`Expected "migrations[${i}].renamed_classes" to be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
|
|
421
|
-
|
|
414
|
+
renamed_classes
|
|
422
415
|
)}.`
|
|
423
416
|
);
|
|
424
417
|
} else if (
|
|
425
|
-
|
|
418
|
+
renamed_classes.some(
|
|
426
419
|
(c) =>
|
|
427
420
|
typeof c !== "object" ||
|
|
428
421
|
!isRequiredProperty(c, "from", "string") ||
|
|
@@ -431,7 +424,7 @@ function normalizeAndValidateMigrations(
|
|
|
431
424
|
) {
|
|
432
425
|
diagnostics.errors.push(
|
|
433
426
|
`Expected "migrations[${i}].renamed_classes" to be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
|
|
434
|
-
|
|
427
|
+
renamed_classes
|
|
435
428
|
)}.`
|
|
436
429
|
);
|
|
437
430
|
}
|
|
@@ -439,7 +432,7 @@ function normalizeAndValidateMigrations(
|
|
|
439
432
|
validateOptionalTypedArray(
|
|
440
433
|
diagnostics,
|
|
441
434
|
`migrations[${i}].deleted_classes`,
|
|
442
|
-
|
|
435
|
+
deleted_classes,
|
|
443
436
|
"string"
|
|
444
437
|
);
|
|
445
438
|
}
|
|
@@ -453,13 +446,32 @@ function normalizeAndValidateMigrations(
|
|
|
453
446
|
(binding) => !binding.script_name
|
|
454
447
|
);
|
|
455
448
|
if (exportedDurableObjects.length > 0 && rawMigrations.length === 0) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
449
|
+
if (
|
|
450
|
+
!exportedDurableObjects.some(
|
|
451
|
+
(exportedDurableObject) =>
|
|
452
|
+
typeof exportedDurableObject.class_name !== "string"
|
|
453
|
+
)
|
|
454
|
+
) {
|
|
455
|
+
const durableObjectClassnames = exportedDurableObjects.map(
|
|
456
|
+
(durable) => durable.class_name
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
diagnostics.warnings.push(
|
|
460
|
+
`In wrangler.toml, you have configured [durable_objects] exported by this Worker (${durableObjectClassnames.join(
|
|
460
461
|
", "
|
|
461
|
-
)}), but no [migrations] for them. This may not work as expected until you add a [migrations] section to your wrangler.toml.
|
|
462
|
-
|
|
462
|
+
)}), but no [migrations] for them. This may not work as expected until you add a [migrations] section to your wrangler.toml. Add this configuration to your wrangler.toml:
|
|
463
|
+
|
|
464
|
+
\`\`\`
|
|
465
|
+
[[migrations]]
|
|
466
|
+
tag = "v1" # Should be unique for each entry
|
|
467
|
+
new_classes = [${durableObjectClassnames
|
|
468
|
+
.map((name) => `"${name}"`)
|
|
469
|
+
.join(", ")}]
|
|
470
|
+
\`\`\`
|
|
471
|
+
|
|
472
|
+
Refer to https://developers.cloudflare.com/workers/learning/using-durable-objects/#durable-object-migrations-in-wranglertoml for more details.`
|
|
473
|
+
);
|
|
474
|
+
}
|
|
463
475
|
}
|
|
464
476
|
}
|
|
465
477
|
|
|
@@ -928,8 +940,22 @@ function normalizeAndValidateEnvironment(
|
|
|
928
940
|
}
|
|
929
941
|
),
|
|
930
942
|
zone_id: rawEnv.zone_id,
|
|
931
|
-
minify:
|
|
932
|
-
|
|
943
|
+
minify: inheritable(
|
|
944
|
+
diagnostics,
|
|
945
|
+
topLevelEnv,
|
|
946
|
+
rawEnv,
|
|
947
|
+
"minify",
|
|
948
|
+
isBoolean,
|
|
949
|
+
undefined
|
|
950
|
+
),
|
|
951
|
+
node_compat: inheritable(
|
|
952
|
+
diagnostics,
|
|
953
|
+
topLevelEnv,
|
|
954
|
+
rawEnv,
|
|
955
|
+
"node_compat",
|
|
956
|
+
isBoolean,
|
|
957
|
+
undefined
|
|
958
|
+
),
|
|
933
959
|
};
|
|
934
960
|
|
|
935
961
|
return environment;
|
|
@@ -63,7 +63,7 @@ async function sessionToken(
|
|
|
63
63
|
): Promise<CfPreviewToken> {
|
|
64
64
|
const { accountId } = account;
|
|
65
65
|
const initUrl = ctx.zone
|
|
66
|
-
? `/zones/${ctx.zone
|
|
66
|
+
? `/zones/${ctx.zone}/workers/edge-preview`
|
|
67
67
|
: `/accounts/${accountId}/workers/subdomain/edge-preview`;
|
|
68
68
|
|
|
69
69
|
const { exchange_url } = await fetchResult<{ exchange_url: string }>(
|
|
@@ -111,7 +111,7 @@ async function createPreviewToken(
|
|
|
111
111
|
);
|
|
112
112
|
|
|
113
113
|
const { accountId } = account;
|
|
114
|
-
const scriptId = ctx.zone ? randomId() :
|
|
114
|
+
const scriptId = worker.name || (ctx.zone ? randomId() : host.split(".")[0]);
|
|
115
115
|
const url =
|
|
116
116
|
ctx.env && !ctx.legacyEnv
|
|
117
117
|
? `/accounts/${accountId}/workers/services/${scriptId}/environments/${ctx.env}/edge-preview`
|
|
@@ -139,19 +139,19 @@ async function createPreviewToken(
|
|
|
139
139
|
|
|
140
140
|
return {
|
|
141
141
|
value: preview_token,
|
|
142
|
-
host:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
142
|
+
host:
|
|
143
|
+
ctx.host ??
|
|
144
|
+
(worker.name
|
|
145
|
+
? `${
|
|
146
|
+
worker.name
|
|
147
|
+
// TODO: this should also probably have the env prefix
|
|
148
|
+
// but it doesn't appear to work yet, instead giving us the
|
|
149
|
+
// "There is nothing here yet" screen
|
|
150
|
+
// ctx.env && !ctx.legacyEnv
|
|
151
|
+
// ? `${ctx.env}.${worker.name}`
|
|
152
|
+
// : worker.name
|
|
153
|
+
}.${host.split(".").slice(1).join(".")}`
|
|
154
|
+
: host),
|
|
155
155
|
|
|
156
156
|
inspectorUrl,
|
|
157
157
|
prewarmUrl,
|
package/src/dev/dev.tsx
CHANGED
|
@@ -9,12 +9,10 @@ import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
|
|
|
9
9
|
import onExit from "signal-exit";
|
|
10
10
|
import tmp from "tmp-promise";
|
|
11
11
|
import { fetch } from "undici";
|
|
12
|
-
import { printBindings } from "../config";
|
|
13
12
|
import { runCustomBuild } from "../entry";
|
|
14
13
|
import { openInspector } from "../inspect";
|
|
15
14
|
import { logger } from "../logger";
|
|
16
15
|
import openInBrowser from "../open-in-browser";
|
|
17
|
-
import { getAPIToken } from "../user";
|
|
18
16
|
import { Local } from "./local";
|
|
19
17
|
import { Remote } from "./remote";
|
|
20
18
|
import { useEsbuild } from "./use-esbuild";
|
|
@@ -55,16 +53,11 @@ export type DevProps = {
|
|
|
55
53
|
};
|
|
56
54
|
env: string | undefined;
|
|
57
55
|
legacyEnv: boolean;
|
|
58
|
-
zone:
|
|
59
|
-
|
|
60
|
-
id: string;
|
|
61
|
-
host: string;
|
|
62
|
-
}
|
|
63
|
-
| undefined;
|
|
56
|
+
zone: string | undefined;
|
|
57
|
+
host: string | undefined;
|
|
64
58
|
};
|
|
65
59
|
|
|
66
60
|
export function DevImplementation(props: DevProps): JSX.Element {
|
|
67
|
-
const apiToken = props.initialMode === "remote" ? getAPIToken() : undefined;
|
|
68
61
|
const directory = useTmpDir();
|
|
69
62
|
|
|
70
63
|
useCustomBuild(props.entry, props.build);
|
|
@@ -106,24 +99,20 @@ export function DevImplementation(props: DevProps): JSX.Element {
|
|
|
106
99
|
nodeCompat: props.nodeCompat,
|
|
107
100
|
});
|
|
108
101
|
|
|
109
|
-
printBindings(props.bindings);
|
|
110
|
-
|
|
111
102
|
// only load the UI if we're running in a supported environment
|
|
112
103
|
const { isRawModeSupported } = useStdin();
|
|
113
104
|
return isRawModeSupported ? (
|
|
114
|
-
<InteractiveDevSession {...props} bundle={bundle}
|
|
105
|
+
<InteractiveDevSession {...props} bundle={bundle} />
|
|
115
106
|
) : (
|
|
116
107
|
<DevSession
|
|
117
108
|
{...props}
|
|
118
109
|
bundle={bundle}
|
|
119
|
-
apiToken={apiToken}
|
|
120
110
|
local={props.initialMode === "local"}
|
|
121
111
|
/>
|
|
122
112
|
);
|
|
123
113
|
}
|
|
124
114
|
|
|
125
115
|
type InteractiveDevSessionProps = DevProps & {
|
|
126
|
-
apiToken: string | undefined;
|
|
127
116
|
bundle: EsbuildBundle | undefined;
|
|
128
117
|
};
|
|
129
118
|
|
|
@@ -186,7 +175,6 @@ function DevSession(props: DevSessionProps) {
|
|
|
186
175
|
bundle={props.bundle}
|
|
187
176
|
format={props.entry.format}
|
|
188
177
|
accountId={props.accountId}
|
|
189
|
-
apiToken={props.apiToken}
|
|
190
178
|
bindings={props.bindings}
|
|
191
179
|
assetPaths={props.assetPaths}
|
|
192
180
|
public={props.public}
|
|
@@ -200,6 +188,7 @@ function DevSession(props: DevSessionProps) {
|
|
|
200
188
|
env={props.env}
|
|
201
189
|
legacyEnv={props.legacyEnv}
|
|
202
190
|
zone={props.zone}
|
|
191
|
+
host={props.host}
|
|
203
192
|
/>
|
|
204
193
|
);
|
|
205
194
|
}
|
package/src/dev/remote.tsx
CHANGED
|
@@ -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:
|
|
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:
|
|
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
|
-
|
|
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/${
|
|
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/dialogs.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { Box, Text, useInput, render } from "ink";
|
|
3
|
+
import SelectInput from "ink-select-input";
|
|
3
4
|
import TextInput from "ink-text-input";
|
|
4
5
|
import * as React from "react";
|
|
5
6
|
import { useState } from "react";
|
|
@@ -85,3 +86,50 @@ export async function prompt(
|
|
|
85
86
|
);
|
|
86
87
|
});
|
|
87
88
|
}
|
|
89
|
+
|
|
90
|
+
type SelectOption = {
|
|
91
|
+
value: string;
|
|
92
|
+
label: string;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
type SelectProps = {
|
|
96
|
+
text: string;
|
|
97
|
+
options: SelectOption[];
|
|
98
|
+
initialIndex: number;
|
|
99
|
+
onSelect: (value: string) => void;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function Select(props: SelectProps) {
|
|
103
|
+
return (
|
|
104
|
+
<Box flexDirection="column">
|
|
105
|
+
<Text>{props.text}</Text>
|
|
106
|
+
<SelectInput
|
|
107
|
+
initialIndex={props.initialIndex}
|
|
108
|
+
items={props.options}
|
|
109
|
+
onSelect={async (selected) => {
|
|
110
|
+
props.onSelect(selected.value);
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
</Box>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function select(
|
|
118
|
+
text: string,
|
|
119
|
+
options: SelectOption[],
|
|
120
|
+
initialIndex: number
|
|
121
|
+
): Promise<string> {
|
|
122
|
+
return new Promise((resolve) => {
|
|
123
|
+
const { unmount } = render(
|
|
124
|
+
<Select
|
|
125
|
+
text={text}
|
|
126
|
+
options={options}
|
|
127
|
+
initialIndex={initialIndex}
|
|
128
|
+
onSelect={(option: string) => {
|
|
129
|
+
unmount();
|
|
130
|
+
resolve(option);
|
|
131
|
+
}}
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
}
|