wrangler 0.0.0-e88a54e → 0.0.0-e95e5a0
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 +25 -14
- package/bin/wrangler.js +13 -1
- package/miniflare-dist/index.mjs +4828 -21
- package/package.json +29 -24
- package/pages/functions/buildPlugin.ts +92 -0
- package/pages/functions/buildWorker.ts +80 -4
- package/pages/functions/filepath-routing.test.ts +11 -0
- package/pages/functions/filepath-routing.ts +8 -0
- package/pages/functions/routes.ts +6 -1
- package/pages/functions/template-plugin.ts +146 -0
- package/pages/functions/template-worker.ts +21 -8
- package/src/__tests__/config-cache.test.ts +36 -0
- package/src/__tests__/configuration.test.ts +530 -201
- package/src/__tests__/dev.test.tsx +284 -18
- package/src/__tests__/guess-worker-format.test.ts +28 -1
- package/src/__tests__/helpers/mock-cfetch.ts +5 -1
- package/src/__tests__/helpers/mock-console.ts +20 -3
- package/src/__tests__/helpers/mock-dialogs.ts +26 -1
- package/src/__tests__/helpers/mock-http-server.ts +46 -0
- package/src/__tests__/helpers/mock-istty.ts +27 -0
- package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
- package/src/__tests__/helpers/run-in-tmp.ts +7 -0
- package/src/__tests__/helpers/write-worker-source.ts +31 -0
- package/src/__tests__/https-options.test.ts +5 -2
- package/src/__tests__/index.test.ts +27 -769
- package/src/__tests__/init.test.ts +1516 -0
- package/src/__tests__/jest.setup.ts +4 -4
- package/src/__tests__/kv.test.ts +133 -61
- package/src/__tests__/logger.test.ts +115 -0
- package/src/__tests__/pages.test.ts +311 -26
- package/src/__tests__/publish.test.ts +2789 -618
- package/src/__tests__/r2.test.ts +26 -10
- package/src/__tests__/route.test.ts +4 -4
- package/src/__tests__/secret.test.ts +55 -34
- package/src/__tests__/tail.test.ts +146 -20
- package/src/__tests__/user.test.ts +131 -0
- package/src/__tests__/whoami.test.tsx +14 -9
- package/src/bundle.ts +30 -3
- package/src/cfetch/internal.ts +0 -2
- package/src/cli.ts +5 -11
- package/src/config/README.md +1 -1
- package/src/config/config.ts +20 -4
- package/src/config/environment.ts +32 -4
- package/src/config/index.ts +4 -3
- package/src/config/validation-helpers.ts +96 -7
- package/src/config/validation.ts +388 -54
- package/src/config-cache.ts +45 -0
- package/src/create-worker-preview.ts +22 -13
- package/src/create-worker-upload-form.ts +32 -9
- package/src/dev/dev-vars.ts +36 -0
- package/src/dev/dev.tsx +132 -78
- package/src/dev/local.tsx +101 -21
- package/src/dev/remote.tsx +23 -13
- package/src/dev/use-esbuild.ts +23 -5
- package/src/dialogs.tsx +16 -6
- package/src/entry.ts +83 -24
- package/src/environment-variables.ts +3 -1
- package/src/errors.ts +11 -0
- package/src/https-options.ts +3 -2
- package/src/index.tsx +689 -394
- package/src/inspect.ts +17 -11
- package/src/intl-polyfill.d.ts +139 -0
- package/src/logger.ts +66 -0
- package/src/miniflare-cli/index.ts +4 -0
- package/src/miniflare-cli/request-context.ts +40 -0
- package/src/module-collection.ts +7 -7
- package/src/open-in-browser.ts +5 -4
- package/src/package-manager.ts +9 -8
- package/src/pages.tsx +875 -56
- package/src/parse.ts +1 -1
- package/src/proxy.ts +28 -19
- package/src/publish.ts +263 -141
- package/src/sites.tsx +27 -14
- package/src/tail/index.ts +2 -2
- package/src/tail/printing.ts +13 -8
- package/src/update-check.ts +19 -0
- package/src/user.tsx +116 -41
- package/src/whoami.tsx +2 -1
- package/src/worker.ts +15 -6
- package/templates/gitignore +171 -0
- package/templates/static-asset-facade.js +1 -1
- package/wrangler-dist/cli.js +21162 -50227
- package/miniflare-dist/index.mjs.map +0 -7
- package/src/__tests__/dev2.test.tsx +0 -154
- package/src/__tests__/logout.test.ts +0 -52
- package/src/__tests__/sentry.test.ts +0 -146
- package/src/reporting.ts +0 -131
- package/wrangler-dist/cli.js.map +0 -7
package/src/sites.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
putBulkKeyValue,
|
|
10
10
|
deleteBulkKeyValue,
|
|
11
11
|
} from "./kv";
|
|
12
|
+
import { logger } from "./logger";
|
|
12
13
|
import type { Config } from "./config";
|
|
13
14
|
import type { KeyValue } from "./kv";
|
|
14
15
|
import type { XXHashAPI } from "xxhash-wasm";
|
|
@@ -82,7 +83,7 @@ async function createKVNamespaceIfNotAlreadyExisting(
|
|
|
82
83
|
|
|
83
84
|
// else we make the namespace
|
|
84
85
|
const id = await createNamespace(accountId, title);
|
|
85
|
-
|
|
86
|
+
logger.log(`🌀 Created namespace for Workers Site "${title}"`);
|
|
86
87
|
|
|
87
88
|
return {
|
|
88
89
|
created: true,
|
|
@@ -105,7 +106,8 @@ export async function syncAssets(
|
|
|
105
106
|
accountId: string,
|
|
106
107
|
scriptName: string,
|
|
107
108
|
siteAssets: AssetPaths | undefined,
|
|
108
|
-
preview: boolean
|
|
109
|
+
preview: boolean,
|
|
110
|
+
dryRun: boolean | undefined
|
|
109
111
|
): Promise<{
|
|
110
112
|
manifest: { [filePath: string]: string } | undefined;
|
|
111
113
|
namespace: string | undefined;
|
|
@@ -114,17 +116,23 @@ export async function syncAssets(
|
|
|
114
116
|
return { manifest: undefined, namespace: undefined };
|
|
115
117
|
}
|
|
116
118
|
|
|
119
|
+
if (dryRun) {
|
|
120
|
+
logger.log("(Note: doing a dry run, not uploading or deleting anything.)");
|
|
121
|
+
return { manifest: undefined, namespace: undefined };
|
|
122
|
+
}
|
|
123
|
+
|
|
117
124
|
const title = `__${scriptName}-workers_sites_assets${
|
|
118
125
|
preview ? "_preview" : ""
|
|
119
126
|
}`;
|
|
127
|
+
|
|
120
128
|
const { id: namespace } = await createKVNamespaceIfNotAlreadyExisting(
|
|
121
129
|
title,
|
|
122
130
|
accountId
|
|
123
131
|
);
|
|
124
132
|
|
|
125
133
|
// let's get all the keys in this namespace
|
|
126
|
-
const
|
|
127
|
-
const
|
|
134
|
+
const namespaceKeysResponse = await listNamespaceKeys(accountId, namespace);
|
|
135
|
+
const namespaceKeys = new Set(namespaceKeysResponse.map((x) => x.name));
|
|
128
136
|
|
|
129
137
|
const manifest: Record<string, string> = {};
|
|
130
138
|
const toUpload: KeyValue[] = [];
|
|
@@ -147,42 +155,47 @@ export async function syncAssets(
|
|
|
147
155
|
}
|
|
148
156
|
|
|
149
157
|
await validateAssetSize(absAssetFile, assetFile);
|
|
150
|
-
|
|
158
|
+
logger.log(`Reading ${assetFile}...`);
|
|
151
159
|
const content = await readFile(absAssetFile, "base64");
|
|
152
160
|
|
|
153
161
|
const assetKey = hashAsset(hasher, assetFile, content);
|
|
154
162
|
validateAssetKey(assetKey);
|
|
155
163
|
|
|
156
164
|
// now put each of the files into kv
|
|
157
|
-
if (!
|
|
158
|
-
|
|
165
|
+
if (!namespaceKeys.has(assetKey)) {
|
|
166
|
+
logger.log(`Uploading as ${assetKey}...`);
|
|
159
167
|
toUpload.push({
|
|
160
168
|
key: assetKey,
|
|
161
169
|
value: content,
|
|
162
170
|
base64: true,
|
|
163
171
|
});
|
|
164
172
|
} else {
|
|
165
|
-
|
|
173
|
+
logger.log(`Skipping - already uploaded.`);
|
|
166
174
|
}
|
|
167
175
|
|
|
168
176
|
// remove the key from the set so we know what we've already uploaded
|
|
169
|
-
|
|
177
|
+
namespaceKeys.delete(assetKey);
|
|
170
178
|
manifest[path.relative(siteAssets.assetDirectory, absAssetFile)] = assetKey;
|
|
171
179
|
}
|
|
172
180
|
|
|
173
181
|
// keys now contains all the files we're deleting
|
|
174
|
-
for (const key of
|
|
175
|
-
|
|
182
|
+
for (const key of namespaceKeys) {
|
|
183
|
+
logger.log(`Deleting ${key} from the asset store...`);
|
|
176
184
|
}
|
|
177
185
|
|
|
178
186
|
await Promise.all([
|
|
179
187
|
// upload all the new assets
|
|
180
188
|
putBulkKeyValue(accountId, namespace, toUpload, () => {}),
|
|
181
189
|
// delete all the unused assets
|
|
182
|
-
deleteBulkKeyValue(
|
|
190
|
+
deleteBulkKeyValue(
|
|
191
|
+
accountId,
|
|
192
|
+
namespace,
|
|
193
|
+
Array.from(namespaceKeys),
|
|
194
|
+
() => {}
|
|
195
|
+
),
|
|
183
196
|
]);
|
|
184
197
|
|
|
185
|
-
|
|
198
|
+
logger.log("↗️ Done syncing assets");
|
|
186
199
|
|
|
187
200
|
return { manifest, namespace };
|
|
188
201
|
}
|
|
@@ -264,7 +277,7 @@ export function getAssetPaths(
|
|
|
264
277
|
assetDirectory = config.site?.bucket,
|
|
265
278
|
includePatterns = config.site?.include ?? [],
|
|
266
279
|
excludePatterns = config.site?.exclude ?? []
|
|
267
|
-
):
|
|
280
|
+
): AssetPaths | undefined {
|
|
268
281
|
const baseDirectory = path.resolve(
|
|
269
282
|
path.dirname(config.configPath ?? "wrangler.toml")
|
|
270
283
|
);
|
package/src/tail/index.ts
CHANGED
|
@@ -161,7 +161,7 @@ export type TailEventMessage = {
|
|
|
161
161
|
* Any logs sent out by the worker
|
|
162
162
|
*/
|
|
163
163
|
logs: {
|
|
164
|
-
message: unknown;
|
|
164
|
+
message: unknown[];
|
|
165
165
|
level: string; // TODO: make this a union of possible values
|
|
166
166
|
timestamp: number;
|
|
167
167
|
}[];
|
|
@@ -179,7 +179,7 @@ export type TailEventMessage = {
|
|
|
179
179
|
* Until workers-types exposes individual types for export, we'll have
|
|
180
180
|
* to just re-define these types ourselves.
|
|
181
181
|
*/
|
|
182
|
-
event: RequestEvent | ScheduledEvent;
|
|
182
|
+
event: RequestEvent | ScheduledEvent | undefined | null;
|
|
183
183
|
};
|
|
184
184
|
|
|
185
185
|
/**
|
package/src/tail/printing.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logger } from "../logger";
|
|
1
2
|
import type { RequestEvent, ScheduledEvent, TailEventMessage } from ".";
|
|
2
3
|
import type { Outcome } from "./filters";
|
|
3
4
|
import type WebSocket from "ws";
|
|
@@ -12,25 +13,29 @@ export function prettyPrintLogs(data: WebSocket.RawData): void {
|
|
|
12
13
|
).toLocaleString();
|
|
13
14
|
const outcome = prettifyOutcome(eventMessage.outcome);
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
logger.log(`"${cronPattern}" @ ${datetime} - ${outcome}`);
|
|
16
17
|
} else {
|
|
17
|
-
const requestMethod = eventMessage.event
|
|
18
|
-
const url = eventMessage.event
|
|
18
|
+
const requestMethod = eventMessage.event?.request.method.toUpperCase();
|
|
19
|
+
const url = eventMessage.event?.request.url;
|
|
19
20
|
const outcome = prettifyOutcome(eventMessage.outcome);
|
|
20
21
|
const datetime = new Date(eventMessage.eventTimestamp).toLocaleString();
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
logger.log(
|
|
24
|
+
url
|
|
25
|
+
? `${requestMethod} ${url} - ${outcome} @ ${datetime}`
|
|
26
|
+
: `[missing request] - ${outcome} @ ${datetime}`
|
|
27
|
+
);
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
if (eventMessage.logs.length > 0) {
|
|
26
31
|
eventMessage.logs.forEach(({ level, message }) => {
|
|
27
|
-
|
|
32
|
+
logger.log(` (${level})`, ...message);
|
|
28
33
|
});
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
if (eventMessage.exceptions.length > 0) {
|
|
32
37
|
eventMessage.exceptions.forEach(({ name, message }) => {
|
|
33
|
-
|
|
38
|
+
logger.error(` ${name}:`, message);
|
|
34
39
|
});
|
|
35
40
|
}
|
|
36
41
|
}
|
|
@@ -40,9 +45,9 @@ export function jsonPrintLogs(data: WebSocket.RawData): void {
|
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
function isScheduledEvent(
|
|
43
|
-
event: RequestEvent | ScheduledEvent
|
|
48
|
+
event: RequestEvent | ScheduledEvent | undefined | null
|
|
44
49
|
): event is ScheduledEvent {
|
|
45
|
-
return "cron" in event;
|
|
50
|
+
return Boolean(event && "cron" in event);
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
function prettifyOutcome(outcome: Outcome): string {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import checkForUpdate from "update-check";
|
|
3
|
+
import pkg from "../package.json";
|
|
4
|
+
|
|
5
|
+
export async function updateCheck(): Promise<string> {
|
|
6
|
+
let update = null;
|
|
7
|
+
try {
|
|
8
|
+
// default cache for update check is 1 day
|
|
9
|
+
update = await checkForUpdate(pkg, {
|
|
10
|
+
distTag: pkg.version.startsWith("0.0.0") ? "beta" : "latest",
|
|
11
|
+
});
|
|
12
|
+
} catch (err) {
|
|
13
|
+
// ignore error
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (update) return `(update available ${chalk.green(update.latest)})`;
|
|
17
|
+
|
|
18
|
+
return "";
|
|
19
|
+
}
|
package/src/user.tsx
CHANGED
|
@@ -220,9 +220,11 @@ import Table from "ink-table";
|
|
|
220
220
|
import React from "react";
|
|
221
221
|
import { fetch } from "undici";
|
|
222
222
|
import { getCloudflareApiBaseUrl } from "./cfetch";
|
|
223
|
+
import { purgeConfigCaches } from "./config-cache";
|
|
223
224
|
import { getEnvironmentVariableFactory } from "./environment-variables";
|
|
225
|
+
import { logger } from "./logger";
|
|
224
226
|
import openInBrowser from "./open-in-browser";
|
|
225
|
-
import { readFileSync } from "./parse";
|
|
227
|
+
import { parseTOML, readFileSync } from "./parse";
|
|
226
228
|
import type { Item as SelectInputItem } from "ink-select-input/build/SelectInput";
|
|
227
229
|
import type { ParsedUrlQuery } from "node:querystring";
|
|
228
230
|
import type { Response } from "undici";
|
|
@@ -345,7 +347,7 @@ let LocalState: State = getAuthTokens();
|
|
|
345
347
|
/**
|
|
346
348
|
* Compute the current auth tokens.
|
|
347
349
|
*/
|
|
348
|
-
function getAuthTokens(): AuthTokens {
|
|
350
|
+
function getAuthTokens(config?: UserAuthConfig): AuthTokens {
|
|
349
351
|
// get refreshToken/accessToken from fs if exists
|
|
350
352
|
try {
|
|
351
353
|
// if the environment variable is available, use that
|
|
@@ -361,7 +363,7 @@ function getAuthTokens(): AuthTokens {
|
|
|
361
363
|
|
|
362
364
|
// otherwise try loading from the user auth config file.
|
|
363
365
|
const { oauth_token, refresh_token, expiration_time, api_token } =
|
|
364
|
-
readAuthConfigFile();
|
|
366
|
+
config || readAuthConfigFile();
|
|
365
367
|
|
|
366
368
|
if (oauth_token) {
|
|
367
369
|
return {
|
|
@@ -384,20 +386,44 @@ function getAuthTokens(): AuthTokens {
|
|
|
384
386
|
|
|
385
387
|
/**
|
|
386
388
|
* Run the initialisation of the auth state, in the case that something changed.
|
|
389
|
+
*
|
|
390
|
+
* This runs automatically whenever `writeAuthConfigFile` is run, so generally
|
|
391
|
+
* you won't need to call it yourself.
|
|
387
392
|
*/
|
|
388
|
-
export function reinitialiseAuthTokens(): void
|
|
389
|
-
|
|
393
|
+
export function reinitialiseAuthTokens(): void;
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Reinitialise auth state from an in-memory config, skipping
|
|
397
|
+
* over the part where we write a file and then read it back into memory
|
|
398
|
+
*/
|
|
399
|
+
export function reinitialiseAuthTokens(config: UserAuthConfig): void;
|
|
400
|
+
|
|
401
|
+
export function reinitialiseAuthTokens(config?: UserAuthConfig): void {
|
|
402
|
+
LocalState = getAuthTokens(config);
|
|
390
403
|
}
|
|
391
404
|
|
|
392
405
|
export function getAPIToken(): string | undefined {
|
|
393
406
|
if (LocalState.apiToken) {
|
|
394
|
-
|
|
407
|
+
logger.warn(
|
|
395
408
|
"It looks like you have used Wrangler 1's `config` command to login with an API token.\n" +
|
|
396
409
|
"This is no longer supported in the current version of Wrangler.\n" +
|
|
397
410
|
"If you wish to authenticate via an API token then please set the `CLOUDFLARE_API_TOKEN` environment variable."
|
|
398
411
|
);
|
|
399
412
|
}
|
|
400
|
-
|
|
413
|
+
|
|
414
|
+
const localAPIToken = getCloudflareAPITokenFromEnv();
|
|
415
|
+
|
|
416
|
+
if (
|
|
417
|
+
!process.stdout.isTTY &&
|
|
418
|
+
!localAPIToken &&
|
|
419
|
+
!LocalState.accessToken?.value
|
|
420
|
+
) {
|
|
421
|
+
throw new Error(
|
|
422
|
+
"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."
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return localAPIToken ?? LocalState.accessToken?.value;
|
|
401
427
|
}
|
|
402
428
|
|
|
403
429
|
interface AccessContext {
|
|
@@ -584,8 +610,8 @@ function isReturningFromAuthServer(query: ParsedUrlQuery): boolean {
|
|
|
584
610
|
|
|
585
611
|
const stateQueryParam = query.state;
|
|
586
612
|
if (stateQueryParam !== state.stateQueryParam) {
|
|
587
|
-
|
|
588
|
-
"
|
|
613
|
+
logger.warn(
|
|
614
|
+
"Received query string parameter doesn't match the one sent! Possible malicious activity somewhere."
|
|
589
615
|
);
|
|
590
616
|
throw new ErrorInvalidReturnedStateParam();
|
|
591
617
|
}
|
|
@@ -610,8 +636,8 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
|
|
|
610
636
|
`?response_type=code&` +
|
|
611
637
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
612
638
|
`redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
|
|
613
|
-
//
|
|
614
|
-
`scope=${encodeURIComponent(scopes
|
|
639
|
+
// we add offline_access manually for every request
|
|
640
|
+
`scope=${encodeURIComponent([...scopes, "offline_access"].join(" "))}&` +
|
|
615
641
|
`state=${stateQueryParam}&` +
|
|
616
642
|
`code_challenge=${encodeURIComponent(codeChallenge)}&` +
|
|
617
643
|
`code_challenge_method=S256`
|
|
@@ -634,7 +660,7 @@ type TokenResponse =
|
|
|
634
660
|
*/
|
|
635
661
|
async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
636
662
|
if (!LocalState.refreshToken) {
|
|
637
|
-
|
|
663
|
+
logger.warn("No refresh token is present.");
|
|
638
664
|
}
|
|
639
665
|
|
|
640
666
|
const body =
|
|
@@ -649,8 +675,27 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
649
675
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
650
676
|
},
|
|
651
677
|
});
|
|
678
|
+
|
|
652
679
|
if (response.status >= 400) {
|
|
653
|
-
|
|
680
|
+
let tokenExchangeResErr = undefined;
|
|
681
|
+
|
|
682
|
+
try {
|
|
683
|
+
tokenExchangeResErr = await response.text();
|
|
684
|
+
tokenExchangeResErr = JSON.parse(tokenExchangeResErr);
|
|
685
|
+
} catch (e) {
|
|
686
|
+
// If it can't parse to JSON ignore the error
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (tokenExchangeResErr !== undefined) {
|
|
690
|
+
// We will throw the parsed error if it parsed correctly, otherwise we throw an unknown error.
|
|
691
|
+
throw typeof tokenExchangeResErr === "string"
|
|
692
|
+
? new Error(tokenExchangeResErr)
|
|
693
|
+
: tokenExchangeResErr;
|
|
694
|
+
} else {
|
|
695
|
+
throw new ErrorUnknown(
|
|
696
|
+
"Failed to parse Error from exchangeRefreshTokenForAccessToken"
|
|
697
|
+
);
|
|
698
|
+
}
|
|
654
699
|
} else {
|
|
655
700
|
try {
|
|
656
701
|
const json = (await response.json()) as TokenResponse;
|
|
@@ -687,18 +732,6 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
687
732
|
};
|
|
688
733
|
return accessContext;
|
|
689
734
|
} catch (error) {
|
|
690
|
-
switch (error) {
|
|
691
|
-
case "invalid_grant":
|
|
692
|
-
console.warn(
|
|
693
|
-
"Expired! Auth code or refresh token needs to be renewed."
|
|
694
|
-
);
|
|
695
|
-
// alert("Redirecting to auth server to obtain a new auth grant code.");
|
|
696
|
-
// TODO: return refreshAuthCodeOrRefreshToken();
|
|
697
|
-
break;
|
|
698
|
-
default:
|
|
699
|
-
console.error(error);
|
|
700
|
-
break;
|
|
701
|
-
}
|
|
702
735
|
if (typeof error === "string") {
|
|
703
736
|
throw toErrorClass(error);
|
|
704
737
|
} else {
|
|
@@ -715,9 +748,9 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
715
748
|
const { authorizationCode, codeVerifier = "" } = LocalState;
|
|
716
749
|
|
|
717
750
|
if (!codeVerifier) {
|
|
718
|
-
|
|
751
|
+
logger.warn("No code verifier is being sent.");
|
|
719
752
|
} else if (!authorizationCode) {
|
|
720
|
-
|
|
753
|
+
logger.warn("No authorization grant code is being passed.");
|
|
721
754
|
}
|
|
722
755
|
|
|
723
756
|
const body =
|
|
@@ -738,7 +771,7 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
738
771
|
const { error } = (await response.json()) as { error: string };
|
|
739
772
|
// .catch((_) => ({ error: "invalid_json" }));
|
|
740
773
|
if (error === "invalid_grant") {
|
|
741
|
-
|
|
774
|
+
logger.log("Expired! Auth code or refresh token needs to be renewed.");
|
|
742
775
|
// alert("Redirecting to auth server to obtain a new auth grant code.");
|
|
743
776
|
// TODO: return refreshAuthCodeOrRefreshToken();
|
|
744
777
|
}
|
|
@@ -832,6 +865,10 @@ function generateRandomState(lengthOfState: number): string {
|
|
|
832
865
|
.join("");
|
|
833
866
|
}
|
|
834
867
|
|
|
868
|
+
/**
|
|
869
|
+
* Writes a a wrangler config file (auth credentials) to disk,
|
|
870
|
+
* and updates the user auth state with the new credentials.
|
|
871
|
+
*/
|
|
835
872
|
export function writeAuthConfigFile(config: UserAuthConfig) {
|
|
836
873
|
mkdirSync(path.join(os.homedir(), ".wrangler/config/"), {
|
|
837
874
|
recursive: true,
|
|
@@ -841,10 +878,12 @@ export function writeAuthConfigFile(config: UserAuthConfig) {
|
|
|
841
878
|
TOML.stringify(config as TOML.JsonMap),
|
|
842
879
|
{ encoding: "utf-8" }
|
|
843
880
|
);
|
|
881
|
+
|
|
882
|
+
reinitialiseAuthTokens();
|
|
844
883
|
}
|
|
845
884
|
|
|
846
|
-
function readAuthConfigFile(): UserAuthConfig {
|
|
847
|
-
const toml =
|
|
885
|
+
export function readAuthConfigFile(): UserAuthConfig {
|
|
886
|
+
const toml = parseTOML(
|
|
848
887
|
readFileSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE))
|
|
849
888
|
);
|
|
850
889
|
return toml;
|
|
@@ -864,21 +903,29 @@ export async function loginOrRefreshIfRequired(
|
|
|
864
903
|
// If we are not interactive, we cannot ask the user to login
|
|
865
904
|
return isInteractive && (await login());
|
|
866
905
|
} else if (isAccessTokenExpired()) {
|
|
867
|
-
|
|
906
|
+
// We're logged in, but the refresh token seems to have expired,
|
|
907
|
+
// so let's try to refresh it
|
|
908
|
+
const didRefresh = await refreshToken();
|
|
909
|
+
if (didRefresh) {
|
|
910
|
+
// The token was refreshed, so we're done here
|
|
911
|
+
return true;
|
|
912
|
+
} else {
|
|
913
|
+
// If the refresh token isn't valid, then we ask the user to login again
|
|
914
|
+
return isInteractive && (await login());
|
|
915
|
+
}
|
|
868
916
|
} else {
|
|
869
917
|
return true;
|
|
870
918
|
}
|
|
871
919
|
}
|
|
872
920
|
|
|
873
921
|
export async function login(props?: LoginProps): Promise<boolean> {
|
|
874
|
-
|
|
922
|
+
logger.log("Attempting to login via OAuth...");
|
|
875
923
|
const urlToOpen = await getAuthURL(props?.scopes);
|
|
876
|
-
await openInBrowser(urlToOpen);
|
|
877
924
|
let server: http.Server;
|
|
878
925
|
let loginTimeoutHandle: NodeJS.Timeout;
|
|
879
926
|
const timerPromise = new Promise<boolean>((resolve) => {
|
|
880
927
|
loginTimeoutHandle = setTimeout(() => {
|
|
881
|
-
|
|
928
|
+
logger.error(
|
|
882
929
|
"Timed out waiting for authorization code, please try again."
|
|
883
930
|
);
|
|
884
931
|
server.close();
|
|
@@ -914,7 +961,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
914
961
|
res.end(() => {
|
|
915
962
|
finish(false);
|
|
916
963
|
});
|
|
917
|
-
|
|
964
|
+
logger.error(
|
|
918
965
|
"Error: Consent denied. You must grant consent to Wrangler in order to login.\n" +
|
|
919
966
|
"If you don't want to do this consider passing an API token via the `CLOUDFLARE_API_TOKEN` environment variable"
|
|
920
967
|
);
|
|
@@ -943,7 +990,9 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
943
990
|
res.end(() => {
|
|
944
991
|
finish(true);
|
|
945
992
|
});
|
|
946
|
-
|
|
993
|
+
logger.log(`Successfully logged in.`);
|
|
994
|
+
|
|
995
|
+
purgeConfigCaches();
|
|
947
996
|
|
|
948
997
|
return;
|
|
949
998
|
}
|
|
@@ -954,6 +1003,8 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
954
1003
|
server.listen(8976);
|
|
955
1004
|
});
|
|
956
1005
|
|
|
1006
|
+
await openInBrowser(urlToOpen);
|
|
1007
|
+
|
|
957
1008
|
return Promise.race([timerPromise, loginPromise]);
|
|
958
1009
|
}
|
|
959
1010
|
|
|
@@ -978,7 +1029,6 @@ async function refreshToken(): Promise<boolean> {
|
|
|
978
1029
|
writeAuthConfigFile({ oauth_token, expiration_time, refresh_token });
|
|
979
1030
|
return true;
|
|
980
1031
|
} catch (err) {
|
|
981
|
-
console.error(err);
|
|
982
1032
|
return false;
|
|
983
1033
|
}
|
|
984
1034
|
}
|
|
@@ -986,7 +1036,7 @@ async function refreshToken(): Promise<boolean> {
|
|
|
986
1036
|
export async function logout(): Promise<void> {
|
|
987
1037
|
if (!LocalState.accessToken) {
|
|
988
1038
|
if (!LocalState.refreshToken) {
|
|
989
|
-
|
|
1039
|
+
logger.log("Not logged in, exiting...");
|
|
990
1040
|
return;
|
|
991
1041
|
}
|
|
992
1042
|
|
|
@@ -1003,7 +1053,7 @@ export async function logout(): Promise<void> {
|
|
|
1003
1053
|
},
|
|
1004
1054
|
});
|
|
1005
1055
|
await response.text(); // blank text? would be nice if it was something meaningful
|
|
1006
|
-
|
|
1056
|
+
logger.log(
|
|
1007
1057
|
"💁 Wrangler is configured with an OAuth token. The token has been successfully revoked"
|
|
1008
1058
|
);
|
|
1009
1059
|
}
|
|
@@ -1021,11 +1071,11 @@ export async function logout(): Promise<void> {
|
|
|
1021
1071
|
});
|
|
1022
1072
|
await response.text(); // blank text? would be nice if it was something meaningful
|
|
1023
1073
|
rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
|
|
1024
|
-
|
|
1074
|
+
logger.log(`Successfully logged out.`);
|
|
1025
1075
|
}
|
|
1026
1076
|
|
|
1027
1077
|
export function listScopes(message = "💁 Available scopes:"): void {
|
|
1028
|
-
|
|
1078
|
+
logger.log(message);
|
|
1029
1079
|
const data = ScopeKeys.map((scope: Scope) => ({
|
|
1030
1080
|
Scope: scope,
|
|
1031
1081
|
Description: Scopes[scope],
|
|
@@ -1090,6 +1140,11 @@ export async function getAccountId(
|
|
|
1090
1140
|
.join("\n")
|
|
1091
1141
|
);
|
|
1092
1142
|
}
|
|
1143
|
+
} else {
|
|
1144
|
+
if (!isInteractive)
|
|
1145
|
+
throw new Error(
|
|
1146
|
+
`Failed to automatically retrieve account IDs for the logged in user. In a non-interactive environment, it is mandatory to specify an account ID, either by assigning its value to CLOUDFLARE_ACCOUNT_ID, or as \`account_id\` in your \`wrangler.toml\` file.`
|
|
1147
|
+
);
|
|
1093
1148
|
}
|
|
1094
1149
|
return accountId;
|
|
1095
1150
|
}
|
|
@@ -1116,3 +1171,23 @@ export function ChooseAccount(props: {
|
|
|
1116
1171
|
</>
|
|
1117
1172
|
);
|
|
1118
1173
|
}
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* Ensure that a user is logged in, and a valid account_id is available.
|
|
1177
|
+
*/
|
|
1178
|
+
export async function requireAuth(config: {
|
|
1179
|
+
account_id?: string;
|
|
1180
|
+
}): Promise<string> {
|
|
1181
|
+
const isInteractive = process.stdin.isTTY;
|
|
1182
|
+
const loggedIn = await loginOrRefreshIfRequired(isInteractive);
|
|
1183
|
+
if (!loggedIn) {
|
|
1184
|
+
// didn't login, let's just quit
|
|
1185
|
+
throw new Error("Did not login, quitting...");
|
|
1186
|
+
}
|
|
1187
|
+
const accountId = config.account_id || (await getAccountId(isInteractive));
|
|
1188
|
+
if (!accountId) {
|
|
1189
|
+
throw new Error("No account id found, quitting...");
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
return accountId;
|
|
1193
|
+
}
|
package/src/whoami.tsx
CHANGED
|
@@ -2,10 +2,11 @@ import { Text, render } from "ink";
|
|
|
2
2
|
import Table from "ink-table";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { fetchListResult, fetchResult } from "./cfetch";
|
|
5
|
+
import { logger } from "./logger";
|
|
5
6
|
import { getAPIToken } from "./user";
|
|
6
7
|
|
|
7
8
|
export async function whoami() {
|
|
8
|
-
|
|
9
|
+
logger.log("Getting User settings...");
|
|
9
10
|
const user = await getUserInfo();
|
|
10
11
|
render(<WhoAmI user={user}></WhoAmI>);
|
|
11
12
|
}
|
package/src/worker.ts
CHANGED
|
@@ -75,7 +75,7 @@ interface CfKvNamespace {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
* A binding to a wasm module (in service
|
|
78
|
+
* A binding to a wasm module (in service-worker format)
|
|
79
79
|
*/
|
|
80
80
|
|
|
81
81
|
interface CfWasmModuleBindings {
|
|
@@ -83,13 +83,21 @@ interface CfWasmModuleBindings {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
|
-
* A binding to a text blob (in service
|
|
86
|
+
* A binding to a text blob (in service-worker format)
|
|
87
87
|
*/
|
|
88
88
|
|
|
89
89
|
interface CfTextBlobBindings {
|
|
90
90
|
[key: string]: string;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* A binding to a data blob (in service-worker format)
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
interface CfDataBlobBindings {
|
|
98
|
+
[key: string]: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
93
101
|
/**
|
|
94
102
|
* A Durable Object.
|
|
95
103
|
*/
|
|
@@ -137,7 +145,7 @@ export interface CfWorkerInit {
|
|
|
137
145
|
/**
|
|
138
146
|
* The list of additional modules.
|
|
139
147
|
*/
|
|
140
|
-
modules:
|
|
148
|
+
modules: CfModule[] | undefined;
|
|
141
149
|
/**
|
|
142
150
|
* All the bindings
|
|
143
151
|
*/
|
|
@@ -146,14 +154,15 @@ export interface CfWorkerInit {
|
|
|
146
154
|
kv_namespaces: CfKvNamespace[] | undefined;
|
|
147
155
|
wasm_modules: CfWasmModuleBindings | undefined;
|
|
148
156
|
text_blobs: CfTextBlobBindings | undefined;
|
|
157
|
+
data_blobs: CfDataBlobBindings | undefined;
|
|
149
158
|
durable_objects: { bindings: CfDurableObject[] } | undefined;
|
|
150
159
|
r2_buckets: CfR2Bucket[] | undefined;
|
|
151
160
|
unsafe: CfUnsafeBinding[] | undefined;
|
|
152
161
|
};
|
|
153
|
-
migrations:
|
|
162
|
+
migrations: CfDurableObjectMigrations | undefined;
|
|
154
163
|
compatibility_date: string | undefined;
|
|
155
|
-
compatibility_flags:
|
|
156
|
-
usage_model:
|
|
164
|
+
compatibility_flags: string[] | undefined;
|
|
165
|
+
usage_model: "bundled" | "unbound" | undefined;
|
|
157
166
|
}
|
|
158
167
|
|
|
159
168
|
export interface CfWorkerContext {
|