wrangler 2.6.1 → 2.7.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 +9 -1
- package/miniflare-dist/index.mjs +1 -1
- package/package.json +12 -10
- package/src/__tests__/api-dev.test.ts +65 -36
- package/src/__tests__/api-devregistry.test.js +14 -6
- package/src/__tests__/configuration.test.ts +2 -31
- package/src/__tests__/{d1.test.ts → d1/d1.test.ts} +48 -5
- package/src/__tests__/d1/splitter.test.ts +255 -0
- package/src/__tests__/delete.test.ts +5 -2
- package/src/__tests__/deployments.test.ts +20 -6
- package/src/__tests__/dev.test.tsx +52 -19
- package/src/__tests__/generate.test.ts +7 -4
- package/src/__tests__/helpers/mock-auth-domain.ts +20 -0
- package/src/__tests__/helpers/mock-cfetch.ts +2 -57
- package/src/__tests__/helpers/mock-dialogs.ts +70 -86
- package/src/__tests__/helpers/mock-oauth-flow.ts +64 -49
- package/src/__tests__/helpers/mock-process.ts +8 -13
- package/src/__tests__/helpers/msw/blob-worker.cjs +19 -0
- package/src/__tests__/helpers/msw/read-file-sync.js +61 -0
- package/src/__tests__/index.test.ts +46 -42
- package/src/__tests__/init.test.ts +782 -522
- package/src/__tests__/jest.setup.ts +20 -24
- package/src/__tests__/kv.test.ts +286 -173
- package/src/__tests__/logout.test.ts +1 -1
- package/src/__tests__/metrics.test.ts +5 -7
- package/src/__tests__/middleware.scheduled.test.ts +40 -30
- package/src/__tests__/middleware.test.ts +144 -120
- package/src/__tests__/pages.test.ts +1618 -1161
- package/src/__tests__/publish.test.ts +174 -125
- package/src/__tests__/r2.test.ts +2 -2
- package/src/__tests__/secret.test.ts +183 -126
- package/src/__tests__/tail.test.ts +6 -0
- package/src/__tests__/tsconfig-sanity.ts +12 -0
- package/src/__tests__/tsconfig.json +8 -0
- package/src/__tests__/tsconfig.tsbuildinfo +1 -0
- package/src/__tests__/whoami.test.tsx +1 -96
- package/src/api/dev.ts +78 -41
- package/src/api/index.ts +1 -1
- package/src/{bundle-reporter.tsx → bundle-reporter.ts} +0 -0
- package/src/cfetch/index.ts +0 -2
- package/src/cfetch/internal.ts +16 -18
- package/src/cli.ts +2 -2
- package/src/config/index.ts +2 -1
- package/src/config/validation.ts +1 -2
- package/src/create-worker-upload-form.ts +2 -2
- package/src/d1/{delete.tsx → delete.ts} +0 -0
- package/src/d1/execute.tsx +8 -37
- package/src/d1/migrations/apply.tsx +32 -19
- package/src/d1/migrations/{index.tsx → index.ts} +0 -0
- package/src/d1/splitter.ts +161 -0
- package/src/d1/{types.tsx → types.ts} +0 -0
- package/src/delete.ts +3 -8
- package/src/deployments.ts +6 -0
- package/src/deprecated/index.ts +2 -295
- package/src/dev/dev.tsx +2 -2
- package/src/dev/{get-local-persistence-path.tsx → get-local-persistence-path.ts} +0 -0
- package/src/dev/local.tsx +16 -4
- package/src/dev/remote.tsx +28 -1
- package/src/dev/start-server.ts +19 -11
- package/src/dev/use-esbuild.ts +1 -1
- package/src/{dev-registry.tsx → dev-registry.ts} +0 -0
- package/src/dev.tsx +35 -11
- package/src/dialogs.ts +136 -0
- package/src/dispatch-namespace.ts +1 -1
- package/src/docs/index.ts +97 -0
- package/src/environment-variables/factory.ts +88 -0
- package/src/environment-variables/misc-variables.ts +30 -0
- package/src/generate/index.ts +300 -0
- package/src/{index.tsx → index.ts} +16 -10
- package/src/init.ts +106 -60
- package/src/jest.d.ts +4 -0
- package/src/logger.ts +15 -3
- package/src/metrics/metrics-config.ts +1 -1
- package/src/metrics/send-event.ts +2 -1
- package/src/miniflare-cli/assets.ts +4 -0
- package/src/miniflare-cli/index.ts +1 -5
- package/src/miniflare-cli/tsconfig.json +9 -0
- package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -0
- package/src/miniflare-cli/types.ts +11 -0
- package/src/pages/{build.tsx → build.ts} +0 -0
- package/src/pages/{deployment-tails.tsx → deployment-tails.ts} +0 -0
- package/src/pages/{dev.tsx → dev.ts} +53 -55
- package/src/pages/functions/buildWorker.ts +1 -1
- package/src/pages/functions/tsconfig.json +8 -0
- package/src/pages/functions/tsconfig.tsbuildinfo +1 -0
- package/src/pages/{functions.tsx → functions.ts} +0 -0
- package/src/pages/{hash.tsx → hash.ts} +0 -0
- package/src/pages/{index.tsx → index.ts} +0 -0
- package/src/pages/projects.tsx +3 -5
- package/src/pages/publish.tsx +16 -5
- package/src/pages/upload.tsx +27 -6
- package/src/publish/publish.ts +9 -7
- package/src/pubsub/{pubsub-commands.tsx → pubsub-commands.ts} +1 -1
- package/src/secret/index.ts +1 -1
- package/src/{sites.tsx → sites.ts} +0 -0
- package/src/tail/index.ts +2 -3
- package/src/tsconfig-sanity.ts +16 -0
- package/src/user/access.ts +0 -1
- package/src/user/auth-variables.ts +113 -0
- package/src/user/choose-account.tsx +1 -31
- package/src/user/index.ts +0 -1
- package/src/user/{user.tsx → user.ts} +107 -73
- package/src/{whoami.tsx → whoami.ts} +37 -71
- package/templates/__tests__/tsconfig-sanity.ts +12 -0
- package/templates/__tests__/tsconfig.json +8 -0
- package/templates/__tests__/tsconfig.tsbuildinfo +1 -0
- package/templates/d1-beta-facade.js +36 -0
- package/templates/facade.d.ts +14 -0
- package/templates/first-party-worker-module-facade.ts +4 -3
- package/templates/format-dev-errors.ts +7 -6
- package/templates/init-tests/test-jest-new-worker.js +3 -5
- package/templates/init-tests/test-vitest-new-worker.js +3 -5
- package/templates/init-tests/test-vitest-new-worker.ts +25 -0
- package/templates/middleware/loader-modules.ts +0 -2
- package/templates/middleware/loader-sw.ts +6 -0
- package/templates/pages-dev-pipeline.ts +4 -1
- package/templates/pages-shim.ts +4 -1
- package/templates/pages-template-plugin.ts +12 -7
- package/templates/serve-static-assets.ts +16 -14
- package/templates/tsconfig-sanity.ts +11 -0
- package/templates/tsconfig.init.json +106 -0
- package/templates/tsconfig.json +5 -103
- package/templates/tsconfig.tsbuildinfo +1 -0
- package/wrangler-dist/cli.d.ts +58 -60
- package/wrangler-dist/cli.js +34498 -55459
- package/wrangler-dist/wasm-sync.wasm +0 -0
- package/src/__tests__/dialogs.test.tsx +0 -40
- package/src/dialogs.tsx +0 -168
- package/src/environment-variables.ts +0 -50
- package/src/user/env-vars.ts +0 -46
package/src/pages/upload.tsx
CHANGED
|
@@ -40,6 +40,10 @@ export function Options(yargs: Argv) {
|
|
|
40
40
|
type: "string",
|
|
41
41
|
description: "The name of the project you want to deploy to",
|
|
42
42
|
},
|
|
43
|
+
"skip-caching": {
|
|
44
|
+
type: "boolean",
|
|
45
|
+
description: "Skip asset caching which speeds up builds",
|
|
46
|
+
},
|
|
43
47
|
})
|
|
44
48
|
.epilogue(pagesBetaWarning);
|
|
45
49
|
}
|
|
@@ -47,6 +51,7 @@ export function Options(yargs: Argv) {
|
|
|
47
51
|
export const Handler = async ({
|
|
48
52
|
directory,
|
|
49
53
|
outputManifestPath,
|
|
54
|
+
skipCaching,
|
|
50
55
|
}: UploadArgs) => {
|
|
51
56
|
if (!directory) {
|
|
52
57
|
throw new FatalError("Must specify a directory.", 1);
|
|
@@ -59,6 +64,7 @@ export const Handler = async ({
|
|
|
59
64
|
const manifest = await upload({
|
|
60
65
|
directory,
|
|
61
66
|
jwt: process.env.CF_PAGES_UPLOAD_JWT,
|
|
67
|
+
skipCaching: skipCaching ?? false,
|
|
62
68
|
});
|
|
63
69
|
|
|
64
70
|
if (outputManifestPath) {
|
|
@@ -74,8 +80,14 @@ export const upload = async (
|
|
|
74
80
|
| {
|
|
75
81
|
directory: string;
|
|
76
82
|
jwt: string;
|
|
83
|
+
skipCaching: boolean;
|
|
84
|
+
}
|
|
85
|
+
| {
|
|
86
|
+
directory: string;
|
|
87
|
+
accountId: string;
|
|
88
|
+
projectName: string;
|
|
89
|
+
skipCaching: boolean;
|
|
77
90
|
}
|
|
78
|
-
| { directory: string; accountId: string; projectName: string }
|
|
79
91
|
) => {
|
|
80
92
|
async function fetchJwt(): Promise<string> {
|
|
81
93
|
if ("jwt" in args) {
|
|
@@ -184,7 +196,12 @@ export const upload = async (
|
|
|
184
196
|
const start = Date.now();
|
|
185
197
|
|
|
186
198
|
let attempts = 0;
|
|
187
|
-
const getMissingHashes = async (): Promise<string[]> => {
|
|
199
|
+
const getMissingHashes = async (skipCaching: boolean): Promise<string[]> => {
|
|
200
|
+
if (skipCaching) {
|
|
201
|
+
console.debug("Force skipping cache");
|
|
202
|
+
return files.map(({ hash }) => hash);
|
|
203
|
+
}
|
|
204
|
+
|
|
188
205
|
try {
|
|
189
206
|
return await fetchResult<string[]>(`/pages/assets/check-missing`, {
|
|
190
207
|
method: "POST",
|
|
@@ -207,13 +224,13 @@ export const upload = async (
|
|
|
207
224
|
// Looks like the JWT expired, fetch another one
|
|
208
225
|
jwt = await fetchJwt();
|
|
209
226
|
}
|
|
210
|
-
return getMissingHashes();
|
|
227
|
+
return getMissingHashes(skipCaching);
|
|
211
228
|
} else {
|
|
212
229
|
throw e;
|
|
213
230
|
}
|
|
214
231
|
}
|
|
215
232
|
};
|
|
216
|
-
const missingHashes = await getMissingHashes();
|
|
233
|
+
const missingHashes = await getMissingHashes(args.skipCaching);
|
|
217
234
|
|
|
218
235
|
const sortedFiles = files
|
|
219
236
|
.filter((file) => missingHashes.includes(file.hash))
|
|
@@ -283,7 +300,8 @@ export const upload = async (
|
|
|
283
300
|
);
|
|
284
301
|
|
|
285
302
|
try {
|
|
286
|
-
|
|
303
|
+
console.debug("POST /pages/assets/upload");
|
|
304
|
+
const res = await fetchResult(`/pages/assets/upload`, {
|
|
287
305
|
method: "POST",
|
|
288
306
|
headers: {
|
|
289
307
|
"Content-Type": "application/json",
|
|
@@ -291,8 +309,10 @@ export const upload = async (
|
|
|
291
309
|
},
|
|
292
310
|
body: JSON.stringify(payload),
|
|
293
311
|
});
|
|
312
|
+
console.debug("result:", res);
|
|
294
313
|
} catch (e) {
|
|
295
314
|
if (attempts < MAX_UPLOAD_ATTEMPTS) {
|
|
315
|
+
console.debug("failed:", e, "retrying...");
|
|
296
316
|
// Exponential backoff, 1 second first time, then 2 second, then 4 second etc.
|
|
297
317
|
await new Promise((resolvePromise) =>
|
|
298
318
|
setTimeout(resolvePromise, Math.pow(2, attempts++) * 1000)
|
|
@@ -304,12 +324,13 @@ export const upload = async (
|
|
|
304
324
|
}
|
|
305
325
|
return doUpload();
|
|
306
326
|
} else {
|
|
327
|
+
console.debug("failed:", e);
|
|
307
328
|
throw e;
|
|
308
329
|
}
|
|
309
330
|
}
|
|
310
331
|
};
|
|
311
332
|
|
|
312
|
-
queue.add(() =>
|
|
333
|
+
void queue.add(() =>
|
|
313
334
|
doUpload().then(
|
|
314
335
|
() => {
|
|
315
336
|
counter += bucket.files.length;
|
package/src/publish/publish.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { fetchListResult, fetchResult } from "../cfetch";
|
|
13
13
|
import { printBindings } from "../config";
|
|
14
14
|
import { createWorkerUploadForm } from "../create-worker-upload-form";
|
|
15
|
-
import { confirm
|
|
15
|
+
import { confirm } from "../dialogs";
|
|
16
16
|
import { getMigrationsToUpload } from "../durable";
|
|
17
17
|
import { logger } from "../logger";
|
|
18
18
|
import { getMetricsUsageHeaders } from "../metrics";
|
|
@@ -269,12 +269,14 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
269
269
|
};
|
|
270
270
|
};
|
|
271
271
|
|
|
272
|
-
if (
|
|
273
|
-
(
|
|
274
|
-
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
|
|
272
|
+
if (default_environment.script.last_deployed_from === "dash") {
|
|
273
|
+
logger.warn(
|
|
274
|
+
`You are about to publish a Workers Service that was last published via the Cloudflare Dashboard.\nEdits that have been made via the dashboard will be overridden by your local code and config.`
|
|
275
|
+
);
|
|
276
|
+
if (!(await confirm("Would you like to continue?"))) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
278
280
|
} catch (e) {
|
|
279
281
|
// code: 10090, message: workers.api.error.service_not_found
|
|
280
282
|
// is thrown from the above fetchResult on the first publish of a Worker
|
|
@@ -49,7 +49,7 @@ export function pubSubCommands(
|
|
|
49
49
|
namespace.description = args.description;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
logger.log(`Creating Pub/
|
|
52
|
+
logger.log(`Creating Pub/Sub Namespace ${args.name}...`);
|
|
53
53
|
await pubsub.createPubSubNamespace(accountId, namespace);
|
|
54
54
|
logger.log(`Success! Created Pub/Sub Namespace ${args.name}`);
|
|
55
55
|
await metrics.sendMetricsEvent("create pubsub namespace", {
|
package/src/secret/index.ts
CHANGED
|
@@ -58,7 +58,7 @@ export const secret = (secretYargs: Argv<CommonYargsOptions>) => {
|
|
|
58
58
|
const isInteractive = process.stdin.isTTY;
|
|
59
59
|
const secretValue = trimTrailingWhitespace(
|
|
60
60
|
isInteractive
|
|
61
|
-
? await prompt("Enter a secret value:",
|
|
61
|
+
? await prompt("Enter a secret value:", { isSecret: true })
|
|
62
62
|
: await readFromStdin()
|
|
63
63
|
);
|
|
64
64
|
|
|
File without changes
|
package/src/tail/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import onExit from "signal-exit";
|
|
|
3
3
|
|
|
4
4
|
import { fetchResult, fetchScriptContent } from "../cfetch";
|
|
5
5
|
import { readConfig } from "../config";
|
|
6
|
-
import {
|
|
6
|
+
import { confirm } from "../dialogs";
|
|
7
7
|
import {
|
|
8
8
|
isLegacyEnv,
|
|
9
9
|
printWranglerBanner,
|
|
@@ -144,8 +144,7 @@ export async function tailHandler(args: TailArgs) {
|
|
|
144
144
|
`Beginning log collection requires restarting the Durable Objects associated with ${scriptName}. Any WebSocket connections or other non-persisted state will be lost as part of this restart.`
|
|
145
145
|
);
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
if (!shouldContinue) {
|
|
147
|
+
if (!(await confirm("Would you like to continue?"))) {
|
|
149
148
|
return;
|
|
150
149
|
}
|
|
151
150
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// `@types/node` should be included
|
|
2
|
+
Buffer.from("test");
|
|
3
|
+
|
|
4
|
+
// @ts-expect-error `@types/jest` should NOT be included
|
|
5
|
+
test("test");
|
|
6
|
+
|
|
7
|
+
// @ts-expect-error `@cloudflare/workers-types` should NOT be included
|
|
8
|
+
const _handler: ExportedHandler = {};
|
|
9
|
+
// @ts-expect-error `@cloudflare/workers-types` should NOT be included
|
|
10
|
+
new HTMLRewriter();
|
|
11
|
+
|
|
12
|
+
// @ts-expect-error `fetch` should NOT be included as our minimum supported
|
|
13
|
+
// Node version is 16.13.0 which does not include `fetch` on the global scope
|
|
14
|
+
void fetch("http://localhost/");
|
|
15
|
+
|
|
16
|
+
export {};
|
package/src/user/access.ts
CHANGED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { getEnvironmentVariableFactory } from "../environment-variables/factory";
|
|
2
|
+
import { getCloudflareApiEnvironmentFromEnv } from "../environment-variables/misc-variables";
|
|
3
|
+
import { getAccessToken } from "./access";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `CLOUDFLARE_ACCOUNT_ID` overrides the account inferred from the current user.
|
|
7
|
+
*/
|
|
8
|
+
export const getCloudflareAccountIdFromEnv = getEnvironmentVariableFactory({
|
|
9
|
+
variableName: "CLOUDFLARE_ACCOUNT_ID",
|
|
10
|
+
deprecatedName: "CF_ACCOUNT_ID",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export const getCloudflareAPITokenFromEnv = getEnvironmentVariableFactory({
|
|
14
|
+
variableName: "CLOUDFLARE_API_TOKEN",
|
|
15
|
+
deprecatedName: "CF_API_TOKEN",
|
|
16
|
+
});
|
|
17
|
+
export const getCloudflareGlobalAuthKeyFromEnv = getEnvironmentVariableFactory({
|
|
18
|
+
variableName: "CLOUDFLARE_API_KEY",
|
|
19
|
+
deprecatedName: "CF_API_KEY",
|
|
20
|
+
});
|
|
21
|
+
export const getCloudflareGlobalAuthEmailFromEnv =
|
|
22
|
+
getEnvironmentVariableFactory({
|
|
23
|
+
variableName: "CLOUDFLARE_EMAIL",
|
|
24
|
+
deprecatedName: "CF_EMAIL",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* `WRANGLER_CLIENT_ID` is a UUID that is used to identify Wrangler
|
|
29
|
+
* to the Cloudflare APIs.
|
|
30
|
+
*
|
|
31
|
+
* Normally you should not need to set this explicitly.
|
|
32
|
+
* If you want to switch to the staging environment set the
|
|
33
|
+
* `WRANGLER_USE_STAGING` environment variable instead.
|
|
34
|
+
*/
|
|
35
|
+
export const getClientIdFromEnv = getEnvironmentVariableFactory({
|
|
36
|
+
variableName: "WRANGLER_CLIENT_ID",
|
|
37
|
+
defaultValue: () =>
|
|
38
|
+
getCloudflareApiEnvironmentFromEnv() === "staging"
|
|
39
|
+
? "4b2ea6cc-9421-4761-874b-ce550e0e3def"
|
|
40
|
+
: "54d11594-84e4-41aa-b438-e81b8fa78ee7",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* `WRANGLER_AUTH_DOMAIN` is the URL base domain that is used
|
|
45
|
+
* to access OAuth URLs for the Cloudflare APIs.
|
|
46
|
+
*
|
|
47
|
+
* Normally you should not need to set this explicitly.
|
|
48
|
+
* If you want to switch to the staging environment set the
|
|
49
|
+
* `WRANGLER_USE_STAGING` environment variable instead.
|
|
50
|
+
*/
|
|
51
|
+
export const getAuthDomainFromEnv = getEnvironmentVariableFactory({
|
|
52
|
+
variableName: "WRANGLER_AUTH_DOMAIN",
|
|
53
|
+
defaultValue: () =>
|
|
54
|
+
getCloudflareApiEnvironmentFromEnv() === "staging"
|
|
55
|
+
? "dash.staging.cloudflare.com"
|
|
56
|
+
: "dash.cloudflare.com",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* `WRANGLER_AUTH_URL` is the path that is used to access OAuth
|
|
61
|
+
* for the Cloudflare APIs.
|
|
62
|
+
*
|
|
63
|
+
* Normally you should not need to set this explicitly.
|
|
64
|
+
* If you want to switch to the staging environment set the
|
|
65
|
+
* `WRANGLER_USE_STAGING` environment variable instead.
|
|
66
|
+
*/
|
|
67
|
+
export const getAuthUrlFromEnv = getEnvironmentVariableFactory({
|
|
68
|
+
variableName: "WRANGLER_AUTH_URL",
|
|
69
|
+
defaultValue: () => `https://${getAuthDomainFromEnv()}/oauth2/auth`,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* `WRANGLER_TOKEN_URL` is the path that is used to exchange an OAuth
|
|
74
|
+
* token for an API token.
|
|
75
|
+
*
|
|
76
|
+
* Normally you should not need to set this explicitly.
|
|
77
|
+
* If you want to switch to the staging environment set the
|
|
78
|
+
* `WRANGLER_USE_STAGING` environment variable instead.
|
|
79
|
+
*/
|
|
80
|
+
export const getTokenUrlFromEnv = getEnvironmentVariableFactory({
|
|
81
|
+
variableName: "WRANGLER_TOKEN_URL",
|
|
82
|
+
defaultValue: () => `https://${getAuthDomainFromEnv()}/oauth2/token`,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* `WRANGLER_REVOKE_URL` is the path that is used to exchange an OAuth
|
|
87
|
+
* refresh token for a new OAuth token.
|
|
88
|
+
*
|
|
89
|
+
* Normally you should not need to set this explicitly.
|
|
90
|
+
* If you want to switch to the staging environment set the
|
|
91
|
+
* `WRANGLER_USE_STAGING` environment variable instead.
|
|
92
|
+
*/
|
|
93
|
+
export const getRevokeUrlFromEnv = getEnvironmentVariableFactory({
|
|
94
|
+
variableName: "WRANGLER_REVOKE_URL",
|
|
95
|
+
defaultValue: () => `https://${getAuthDomainFromEnv()}/oauth2/revoke`,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Set the `WRANGLER_CF_AUTHORIZATION_TOKEN` to the CF_Authorization token found at https://dash.staging.cloudflare.com/bypass-limits
|
|
100
|
+
* if you want to access the staging environment, triggered by `WRANGLER_API_ENVIRONMENT=staging`.
|
|
101
|
+
*/
|
|
102
|
+
export const getCloudflareAccessToken = async () => {
|
|
103
|
+
const env = getEnvironmentVariableFactory({
|
|
104
|
+
variableName: "WRANGLER_CF_AUTHORIZATION_TOKEN",
|
|
105
|
+
})();
|
|
106
|
+
|
|
107
|
+
// If the environment variable is defined, go ahead and use it.
|
|
108
|
+
if (env !== undefined) {
|
|
109
|
+
return env;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return getAccessToken(getAuthDomainFromEnv());
|
|
113
|
+
};
|
|
@@ -1,41 +1,11 @@
|
|
|
1
|
-
import { Text } from "ink";
|
|
2
|
-
import SelectInput from "ink-select-input";
|
|
3
|
-
import React from "react";
|
|
4
1
|
import { fetchListResult } from "../cfetch";
|
|
5
|
-
import {
|
|
6
|
-
import { getCloudflareAccountIdFromEnv } from "./env-vars";
|
|
2
|
+
import { getCloudflareAccountIdFromEnv } from "./auth-variables";
|
|
7
3
|
|
|
8
4
|
export type ChooseAccountItem = {
|
|
9
5
|
id: string;
|
|
10
6
|
name: string;
|
|
11
7
|
};
|
|
12
8
|
|
|
13
|
-
/**
|
|
14
|
-
* A component that allows the user to select from a list of available accounts.
|
|
15
|
-
*/
|
|
16
|
-
export function ChooseAccount(props: {
|
|
17
|
-
accounts: ChooseAccountItem[];
|
|
18
|
-
onSelect: (account: { name: string; id: string }) => void;
|
|
19
|
-
onError: (error: Error) => void;
|
|
20
|
-
}) {
|
|
21
|
-
return (
|
|
22
|
-
<>
|
|
23
|
-
<Text bold>Select an account from below:</Text>
|
|
24
|
-
<SelectInput
|
|
25
|
-
items={props.accounts.map((item) => ({
|
|
26
|
-
key: item.id,
|
|
27
|
-
label: item.name,
|
|
28
|
-
value: item,
|
|
29
|
-
}))}
|
|
30
|
-
onSelect={(item) => {
|
|
31
|
-
logger.log(`Using account: "${item.value.name} - ${item.value.id}"`);
|
|
32
|
-
props.onSelect({ id: item.value.id, name: item.value.name });
|
|
33
|
-
}}
|
|
34
|
-
/>
|
|
35
|
-
</>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
9
|
/**
|
|
40
10
|
* Infer a list of available accounts for the current user.
|
|
41
11
|
*/
|
package/src/user/index.ts
CHANGED
|
@@ -214,28 +214,61 @@ import url from "node:url";
|
|
|
214
214
|
import { TextEncoder } from "node:util";
|
|
215
215
|
import TOML from "@iarna/toml";
|
|
216
216
|
import { HostURL } from "@webcontainer/env";
|
|
217
|
-
import { render } from "ink";
|
|
218
|
-
import Table from "ink-table";
|
|
219
|
-
import React from "react";
|
|
220
217
|
import { fetch } from "undici";
|
|
221
218
|
import {
|
|
222
219
|
getConfigCache,
|
|
223
220
|
purgeConfigCaches,
|
|
224
221
|
saveToConfigCache,
|
|
225
222
|
} from "../config-cache";
|
|
223
|
+
import { NoDefaultValueProvided, select } from "../dialogs";
|
|
226
224
|
import { getGlobalWranglerConfigPath } from "../global-wrangler-config-path";
|
|
227
225
|
import { CI } from "../is-ci";
|
|
228
226
|
import isInteractive from "../is-interactive";
|
|
229
227
|
import { logger } from "../logger";
|
|
230
228
|
import openInBrowser from "../open-in-browser";
|
|
231
229
|
import { parseTOML, readFileSync } from "../parse";
|
|
232
|
-
import {
|
|
233
|
-
import {
|
|
230
|
+
import { domainUsesAccess } from "./access";
|
|
231
|
+
import {
|
|
232
|
+
getAuthDomainFromEnv,
|
|
233
|
+
getAuthUrlFromEnv,
|
|
234
|
+
getClientIdFromEnv,
|
|
235
|
+
getCloudflareAccessToken,
|
|
236
|
+
getCloudflareAPITokenFromEnv,
|
|
237
|
+
getCloudflareGlobalAuthEmailFromEnv,
|
|
238
|
+
getCloudflareGlobalAuthKeyFromEnv,
|
|
239
|
+
getRevokeUrlFromEnv,
|
|
240
|
+
getTokenUrlFromEnv,
|
|
241
|
+
} from "./auth-variables";
|
|
242
|
+
import { getAccountChoices } from "./choose-account";
|
|
234
243
|
import { generateAuthUrl } from "./generate-auth-url";
|
|
235
244
|
import { generateRandomState } from "./generate-random-state";
|
|
236
|
-
import type {
|
|
245
|
+
import type { ChooseAccountItem } from "./choose-account";
|
|
237
246
|
import type { ParsedUrlQuery } from "node:querystring";
|
|
238
247
|
|
|
248
|
+
export type ApiCredentials =
|
|
249
|
+
| {
|
|
250
|
+
apiToken: string;
|
|
251
|
+
}
|
|
252
|
+
| {
|
|
253
|
+
authKey: string;
|
|
254
|
+
authEmail: string;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Try to read an API token or Global Auth from the environment.
|
|
259
|
+
*/
|
|
260
|
+
export function getAuthFromEnv(): ApiCredentials | undefined {
|
|
261
|
+
const globalApiKey = getCloudflareGlobalAuthKeyFromEnv();
|
|
262
|
+
const globalApiEmail = getCloudflareGlobalAuthEmailFromEnv();
|
|
263
|
+
const apiToken = getCloudflareAPITokenFromEnv();
|
|
264
|
+
|
|
265
|
+
if (globalApiKey && globalApiEmail) {
|
|
266
|
+
return { authKey: globalApiKey, authEmail: globalApiEmail };
|
|
267
|
+
} else if (apiToken) {
|
|
268
|
+
return { apiToken };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
239
272
|
/**
|
|
240
273
|
* An implementation of rfc6749#section-4.1 and rfc7636.
|
|
241
274
|
*/
|
|
@@ -330,11 +363,6 @@ export function validateScopeKeys(
|
|
|
330
363
|
return scopes.every((scope) => scope in Scopes);
|
|
331
364
|
}
|
|
332
365
|
|
|
333
|
-
const CLIENT_ID = "54d11594-84e4-41aa-b438-e81b8fa78ee7";
|
|
334
|
-
const AUTH_URL = "https://dash.cloudflare.com/oauth2/auth";
|
|
335
|
-
const TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
|
|
336
|
-
const REVOKE_URL = "https://dash.cloudflare.com/oauth2/revoke";
|
|
337
|
-
|
|
338
366
|
/**
|
|
339
367
|
* To allow OAuth callbacks in environments such as WebContainer we need to
|
|
340
368
|
* create a host URL which only resolves `localhost` to a WebContainer
|
|
@@ -386,7 +414,7 @@ function getAuthTokens(config?: UserAuthConfig): AuthTokens | undefined {
|
|
|
386
414
|
}
|
|
387
415
|
|
|
388
416
|
/**
|
|
389
|
-
* Run the
|
|
417
|
+
* Run the initialization of the auth state, in the case that something changed.
|
|
390
418
|
*
|
|
391
419
|
* This runs automatically whenever `writeAuthConfigFile` is run, so generally
|
|
392
420
|
* you won't need to call it yourself.
|
|
@@ -625,8 +653,8 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
|
|
|
625
653
|
});
|
|
626
654
|
|
|
627
655
|
return generateAuthUrl({
|
|
628
|
-
authUrl:
|
|
629
|
-
clientId:
|
|
656
|
+
authUrl: getAuthUrlFromEnv(),
|
|
657
|
+
clientId: getClientIdFromEnv(),
|
|
630
658
|
callbackUrl: CALLBACK_URL,
|
|
631
659
|
scopes,
|
|
632
660
|
stateQueryParam,
|
|
@@ -653,19 +681,14 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
653
681
|
logger.warn("No refresh token is present.");
|
|
654
682
|
}
|
|
655
683
|
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const response = await fetch(TOKEN_URL, {
|
|
662
|
-
method: "POST",
|
|
663
|
-
body,
|
|
664
|
-
headers: {
|
|
665
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
666
|
-
},
|
|
684
|
+
const params = new URLSearchParams({
|
|
685
|
+
grant_type: "refresh_token",
|
|
686
|
+
refresh_token: LocalState.refreshToken?.value ?? "",
|
|
687
|
+
client_id: getClientIdFromEnv(),
|
|
667
688
|
});
|
|
668
689
|
|
|
690
|
+
const response = await fetchAuthToken(params);
|
|
691
|
+
|
|
669
692
|
if (response.status >= 400) {
|
|
670
693
|
let tokenExchangeResErr = undefined;
|
|
671
694
|
|
|
@@ -743,20 +766,15 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
743
766
|
logger.warn("No authorization grant code is being passed.");
|
|
744
767
|
}
|
|
745
768
|
|
|
746
|
-
const
|
|
747
|
-
`
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
const response = await fetch(TOKEN_URL, {
|
|
754
|
-
method: "POST",
|
|
755
|
-
body,
|
|
756
|
-
headers: {
|
|
757
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
758
|
-
},
|
|
769
|
+
const params = new URLSearchParams({
|
|
770
|
+
grant_type: `authorization_code`,
|
|
771
|
+
code: authorizationCode ?? "",
|
|
772
|
+
redirect_uri: CALLBACK_URL,
|
|
773
|
+
client_id: getClientIdFromEnv(),
|
|
774
|
+
code_verifier: codeVerifier,
|
|
759
775
|
});
|
|
776
|
+
|
|
777
|
+
const response = await fetchAuthToken(params);
|
|
760
778
|
if (!response.ok) {
|
|
761
779
|
const { error } = (await response.json()) as { error: string };
|
|
762
780
|
// .catch((_) => ({ error: "invalid_json" }));
|
|
@@ -1038,11 +1056,11 @@ export async function logout(): Promise<void> {
|
|
|
1038
1056
|
}
|
|
1039
1057
|
|
|
1040
1058
|
const body =
|
|
1041
|
-
`client_id=${encodeURIComponent(
|
|
1059
|
+
`client_id=${encodeURIComponent(getClientIdFromEnv())}&` +
|
|
1042
1060
|
`token_type_hint=refresh_token&` +
|
|
1043
1061
|
`token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
|
|
1044
1062
|
|
|
1045
|
-
const response = await fetch(
|
|
1063
|
+
const response = await fetch(getRevokeUrlFromEnv(), {
|
|
1046
1064
|
method: "POST",
|
|
1047
1065
|
body,
|
|
1048
1066
|
headers: {
|
|
@@ -1055,11 +1073,11 @@ export async function logout(): Promise<void> {
|
|
|
1055
1073
|
);
|
|
1056
1074
|
}
|
|
1057
1075
|
const body =
|
|
1058
|
-
`client_id=${encodeURIComponent(
|
|
1076
|
+
`client_id=${encodeURIComponent(getClientIdFromEnv())}&` +
|
|
1059
1077
|
`token_type_hint=refresh_token&` +
|
|
1060
1078
|
`token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
|
|
1061
1079
|
|
|
1062
|
-
const response = await fetch(
|
|
1080
|
+
const response = await fetch(getRevokeUrlFromEnv(), {
|
|
1063
1081
|
method: "POST",
|
|
1064
1082
|
body,
|
|
1065
1083
|
headers: {
|
|
@@ -1077,8 +1095,7 @@ export function listScopes(message = "💁 Available scopes:"): void {
|
|
|
1077
1095
|
Scope: scope,
|
|
1078
1096
|
Description: Scopes[scope],
|
|
1079
1097
|
}));
|
|
1080
|
-
|
|
1081
|
-
unmount();
|
|
1098
|
+
logger.table(data);
|
|
1082
1099
|
// TODO: maybe a good idea to show usage here
|
|
1083
1100
|
}
|
|
1084
1101
|
|
|
@@ -1097,36 +1114,32 @@ export async function getAccountId(): Promise<string | undefined> {
|
|
|
1097
1114
|
return accounts[0].id;
|
|
1098
1115
|
}
|
|
1099
1116
|
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
(
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1117
|
+
try {
|
|
1118
|
+
const accountID = await select("Select an account", {
|
|
1119
|
+
choices: accounts.map((account) => ({
|
|
1120
|
+
title: account.name,
|
|
1121
|
+
value: account.id,
|
|
1122
|
+
})),
|
|
1123
|
+
});
|
|
1124
|
+
const account = accounts.find(
|
|
1125
|
+
(a) => a.id === accountID
|
|
1126
|
+
) as ChooseAccountItem;
|
|
1127
|
+
saveAccountToCache({ id: account.id, name: account.name });
|
|
1128
|
+
return accountID;
|
|
1129
|
+
} catch (e) {
|
|
1130
|
+
// Did we try to select an account in CI or a non-interactive terminal?
|
|
1131
|
+
if (e instanceof NoDefaultValueProvided) {
|
|
1132
|
+
throw new Error(
|
|
1133
|
+
`More than one account available but unable to select one in non-interactive mode.
|
|
1134
|
+
Please set the appropriate \`account_id\` in your \`wrangler.toml\` file.
|
|
1135
|
+
Available accounts are (\`<name>\`: \`<account_id>\`):
|
|
1136
|
+
${accounts
|
|
1137
|
+
.map((account) => ` \`${account.name}\`: \`${account.id}\``)
|
|
1138
|
+
.join("\n")}`
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
throw e;
|
|
1120
1142
|
}
|
|
1121
|
-
|
|
1122
|
-
throw new Error(
|
|
1123
|
-
"More than one account available but unable to select one in non-interactive mode.\n" +
|
|
1124
|
-
`Please set the appropriate \`account_id\` in your \`wrangler.toml\` file.\n` +
|
|
1125
|
-
`Available accounts are ("<name>" - "<id>"):\n` +
|
|
1126
|
-
accounts
|
|
1127
|
-
.map((account) => ` "${account.name}" - "${account.id}")`)
|
|
1128
|
-
.join("\n")
|
|
1129
|
-
);
|
|
1130
1143
|
}
|
|
1131
1144
|
|
|
1132
1145
|
/**
|
|
@@ -1196,3 +1209,24 @@ export function getAccountFromCache():
|
|
|
1196
1209
|
export function getScopes(): Scope[] | undefined {
|
|
1197
1210
|
return LocalState.scopes;
|
|
1198
1211
|
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* Make a request to the Cloudflare OAuth endpoint to get a token.
|
|
1215
|
+
*
|
|
1216
|
+
* Note that the `body` of the POST request is form-urlencoded so
|
|
1217
|
+
* can be represented by a URLSearchParams object.
|
|
1218
|
+
*/
|
|
1219
|
+
async function fetchAuthToken(body: URLSearchParams) {
|
|
1220
|
+
const headers: Record<string, string> = {
|
|
1221
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1222
|
+
};
|
|
1223
|
+
if (await domainUsesAccess(getAuthDomainFromEnv())) {
|
|
1224
|
+
// We are trying to access the staging API so we need an "access token".
|
|
1225
|
+
headers["Cookie"] = `CF_Authorization=${await getCloudflareAccessToken()}`;
|
|
1226
|
+
}
|
|
1227
|
+
return await fetch(getTokenUrlFromEnv(), {
|
|
1228
|
+
method: "POST",
|
|
1229
|
+
body: body.toString(),
|
|
1230
|
+
headers,
|
|
1231
|
+
});
|
|
1232
|
+
}
|