wrangler 0.0.0-e95bea2 → 0.0.0-ea8e701
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 +3 -3
- package/bin/wrangler.js +10 -1
- package/miniflare-dist/index.mjs +79 -0
- package/miniflare-dist/index.mjs.map +7 -0
- package/package.json +7 -6
- package/pages/functions/filepath-routing.test.ts +125 -25
- package/pages/functions/filepath-routing.ts +6 -3
- package/src/__tests__/configuration.test.ts +826 -223
- package/src/__tests__/dev.test.tsx +145 -9
- package/src/__tests__/guess-worker-format.test.ts +26 -1
- package/src/__tests__/helpers/mock-account-id.ts +4 -0
- package/src/__tests__/helpers/mock-kv.ts +9 -16
- package/src/__tests__/helpers/mock-oauth-flow.ts +178 -0
- package/src/__tests__/helpers/run-in-tmp.ts +3 -0
- package/src/__tests__/index.test.ts +9 -13
- package/src/__tests__/jest.setup.ts +5 -0
- package/src/__tests__/kv.test.ts +43 -71
- package/src/__tests__/pages.test.ts +48 -10
- package/src/__tests__/publish.test.ts +1456 -327
- package/src/__tests__/r2.test.ts +4 -4
- package/src/__tests__/tail.test.ts +78 -4
- package/src/__tests__/user.test.ts +83 -0
- package/src/__tests__/whoami.test.tsx +16 -7
- package/src/bundle.ts +15 -2
- package/src/cfetch/internal.ts +9 -5
- package/src/config/README.md +6 -6
- package/src/config/config.ts +17 -78
- package/src/config/environment.ts +67 -1
- package/src/config/index.ts +13 -3
- package/src/config/validation-helpers.ts +177 -11
- package/src/config/validation.ts +305 -100
- package/src/dev/dev.tsx +76 -41
- package/src/dev/local.tsx +105 -114
- package/src/dev/remote.tsx +2 -0
- package/src/dev/use-esbuild.ts +4 -0
- package/src/entry.ts +103 -6
- package/src/index.tsx +251 -189
- package/src/intl-polyfill.d.ts +139 -0
- package/src/{kv/index.tsx → kv.ts} +8 -28
- package/src/miniflare-cli/README.md +30 -0
- package/src/miniflare-cli/enum-keys.ts +17 -0
- package/src/miniflare-cli/index.ts +37 -0
- package/src/module-collection.ts +58 -53
- package/src/pages.tsx +26 -10
- package/src/proxy.ts +5 -3
- package/src/publish.ts +17 -21
- package/src/sites.tsx +67 -25
- package/src/tail/index.ts +1 -1
- package/src/tail/printing.ts +2 -2
- package/src/user.tsx +161 -81
- package/wrangler-dist/cli.js +3418 -1437
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/dev2.test.tsx +0 -152
- package/src/__tests__/helpers/mock-user.ts +0 -27
- package/src/__tests__/logout.test.ts +0 -50
package/src/sites.tsx
CHANGED
|
@@ -7,29 +7,27 @@ import {
|
|
|
7
7
|
listNamespaceKeys,
|
|
8
8
|
listNamespaces,
|
|
9
9
|
putBulkKeyValue,
|
|
10
|
+
deleteBulkKeyValue,
|
|
10
11
|
} from "./kv";
|
|
11
12
|
import type { Config } from "./config";
|
|
12
13
|
import type { KeyValue } from "./kv";
|
|
13
14
|
import type { XXHashAPI } from "xxhash-wasm";
|
|
14
15
|
|
|
15
16
|
/** Paths to always ignore. */
|
|
16
|
-
const ALWAYS_IGNORE = ["node_modules"];
|
|
17
|
-
const HIDDEN_FILES_TO_INCLUDE = [
|
|
17
|
+
const ALWAYS_IGNORE = new Set(["node_modules"]);
|
|
18
|
+
const HIDDEN_FILES_TO_INCLUDE = new Set([
|
|
18
19
|
".well-known", // See https://datatracker.ietf.org/doc/html/rfc8615
|
|
19
|
-
];
|
|
20
|
+
]);
|
|
20
21
|
|
|
21
22
|
async function* getFilesInFolder(dirPath: string): AsyncIterable<string> {
|
|
22
23
|
const files = await readdir(dirPath, { withFileTypes: true });
|
|
23
24
|
for (const file of files) {
|
|
24
25
|
// Skip files that we never want to process.
|
|
25
|
-
if (ALWAYS_IGNORE.
|
|
26
|
+
if (ALWAYS_IGNORE.has(file.name)) {
|
|
26
27
|
continue;
|
|
27
28
|
}
|
|
28
29
|
// Skip hidden files (starting with .) except for some special ones
|
|
29
|
-
if (
|
|
30
|
-
file.name.startsWith(".") &&
|
|
31
|
-
!HIDDEN_FILES_TO_INCLUDE.some((p) => file.name === p)
|
|
32
|
-
) {
|
|
30
|
+
if (file.name.startsWith(".") && !HIDDEN_FILES_TO_INCLUDE.has(file.name)) {
|
|
33
31
|
continue;
|
|
34
32
|
}
|
|
35
33
|
// TODO: follow symlinks??
|
|
@@ -129,32 +127,36 @@ export async function syncAssets(
|
|
|
129
127
|
const keys = new Set(result.map((x) => x.name));
|
|
130
128
|
|
|
131
129
|
const manifest: Record<string, string> = {};
|
|
132
|
-
const
|
|
130
|
+
const toUpload: KeyValue[] = [];
|
|
133
131
|
|
|
134
132
|
const include = createPatternMatcher(siteAssets.includePatterns, false);
|
|
135
133
|
const exclude = createPatternMatcher(siteAssets.excludePatterns, true);
|
|
136
134
|
const hasher = await xxhash();
|
|
137
135
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
const assetDirectory = path.join(
|
|
137
|
+
siteAssets.baseDirectory,
|
|
138
|
+
siteAssets.assetDirectory
|
|
139
|
+
);
|
|
140
|
+
for await (const absAssetFile of getFilesInFolder(assetDirectory)) {
|
|
141
|
+
const assetFile = path.relative(siteAssets.baseDirectory, absAssetFile);
|
|
142
|
+
if (!include(assetFile)) {
|
|
141
143
|
continue;
|
|
142
144
|
}
|
|
143
|
-
if (exclude(
|
|
145
|
+
if (exclude(assetFile)) {
|
|
144
146
|
continue;
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
await validateAssetSize(
|
|
148
|
-
console.log(`reading ${
|
|
149
|
-
const content = await readFile(
|
|
149
|
+
await validateAssetSize(absAssetFile, assetFile);
|
|
150
|
+
console.log(`reading ${assetFile}...`);
|
|
151
|
+
const content = await readFile(absAssetFile, "base64");
|
|
150
152
|
|
|
151
|
-
const assetKey =
|
|
153
|
+
const assetKey = hashAsset(hasher, assetFile, content);
|
|
152
154
|
validateAssetKey(assetKey);
|
|
153
155
|
|
|
154
156
|
// now put each of the files into kv
|
|
155
157
|
if (!keys.has(assetKey)) {
|
|
156
158
|
console.log(`uploading as ${assetKey}...`);
|
|
157
|
-
|
|
159
|
+
toUpload.push({
|
|
158
160
|
key: assetKey,
|
|
159
161
|
value: content,
|
|
160
162
|
base64: true,
|
|
@@ -162,9 +164,26 @@ export async function syncAssets(
|
|
|
162
164
|
} else {
|
|
163
165
|
console.log(`skipping - already uploaded`);
|
|
164
166
|
}
|
|
165
|
-
|
|
167
|
+
|
|
168
|
+
// remove the key from the set so we know what we've already uploaded
|
|
169
|
+
keys.delete(assetKey);
|
|
170
|
+
manifest[path.relative(siteAssets.assetDirectory, absAssetFile)] = assetKey;
|
|
166
171
|
}
|
|
167
|
-
|
|
172
|
+
|
|
173
|
+
// keys now contains all the files we're deleting
|
|
174
|
+
for (const key of keys) {
|
|
175
|
+
console.log(`deleting ${key} from the asset store...`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
await Promise.all([
|
|
179
|
+
// upload all the new assets
|
|
180
|
+
putBulkKeyValue(accountId, namespace, toUpload, () => {}),
|
|
181
|
+
// delete all the unused assets
|
|
182
|
+
deleteBulkKeyValue(accountId, namespace, Array.from(keys), () => {}),
|
|
183
|
+
]);
|
|
184
|
+
|
|
185
|
+
console.log("↗️ Done syncing assets");
|
|
186
|
+
|
|
168
187
|
return { manifest, namespace };
|
|
169
188
|
}
|
|
170
189
|
|
|
@@ -180,11 +199,14 @@ function createPatternMatcher(
|
|
|
180
199
|
}
|
|
181
200
|
}
|
|
182
201
|
|
|
183
|
-
async function validateAssetSize(
|
|
184
|
-
|
|
202
|
+
async function validateAssetSize(
|
|
203
|
+
absFilePath: string,
|
|
204
|
+
relativeFilePath: string
|
|
205
|
+
) {
|
|
206
|
+
const { size } = await stat(absFilePath);
|
|
185
207
|
if (size > 25 * 1024 * 1024) {
|
|
186
208
|
throw new Error(
|
|
187
|
-
`File ${
|
|
209
|
+
`File ${relativeFilePath} is too big, it should be under 25 MiB. See https://developers.cloudflare.com/workers/platform/limits#kv-limits`
|
|
188
210
|
);
|
|
189
211
|
}
|
|
190
212
|
}
|
|
@@ -210,8 +232,23 @@ function urlSafe(filePath: string): string {
|
|
|
210
232
|
* Information about the assets that should be uploaded
|
|
211
233
|
*/
|
|
212
234
|
export interface AssetPaths {
|
|
235
|
+
/**
|
|
236
|
+
* Absolute path to the root of the project.
|
|
237
|
+
*
|
|
238
|
+
* This is the directory containing wrangler.toml or cwd if no config.
|
|
239
|
+
*/
|
|
213
240
|
baseDirectory: string;
|
|
241
|
+
/**
|
|
242
|
+
* The path to the assets directory, relative to the `baseDirectory`.
|
|
243
|
+
*/
|
|
244
|
+
assetDirectory: string;
|
|
245
|
+
/**
|
|
246
|
+
* An array of patterns that match files that should be uploaded.
|
|
247
|
+
*/
|
|
214
248
|
includePatterns: string[];
|
|
249
|
+
/**
|
|
250
|
+
* An array of patterns that match files that should not be uploaded.
|
|
251
|
+
*/
|
|
215
252
|
excludePatterns: string[];
|
|
216
253
|
}
|
|
217
254
|
|
|
@@ -224,13 +261,18 @@ export interface AssetPaths {
|
|
|
224
261
|
*/
|
|
225
262
|
export function getAssetPaths(
|
|
226
263
|
config: Config,
|
|
227
|
-
|
|
264
|
+
assetDirectory = config.site?.bucket,
|
|
228
265
|
includePatterns = config.site?.include ?? [],
|
|
229
266
|
excludePatterns = config.site?.exclude ?? []
|
|
230
267
|
): undefined | AssetPaths {
|
|
231
|
-
|
|
268
|
+
const baseDirectory = path.resolve(
|
|
269
|
+
path.dirname(config.configPath ?? "wrangler.toml")
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
return assetDirectory
|
|
232
273
|
? {
|
|
233
274
|
baseDirectory,
|
|
275
|
+
assetDirectory,
|
|
234
276
|
includePatterns,
|
|
235
277
|
excludePatterns,
|
|
236
278
|
}
|
package/src/tail/index.ts
CHANGED
package/src/tail/printing.ts
CHANGED
|
@@ -24,13 +24,13 @@ export function prettyPrintLogs(data: WebSocket.RawData): void {
|
|
|
24
24
|
|
|
25
25
|
if (eventMessage.logs.length > 0) {
|
|
26
26
|
eventMessage.logs.forEach(({ level, message }) => {
|
|
27
|
-
console.log(` (${level})
|
|
27
|
+
console.log(` (${level})`, ...message);
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
if (eventMessage.exceptions.length > 0) {
|
|
32
32
|
eventMessage.exceptions.forEach(({ name, message }) => {
|
|
33
|
-
console.error(` ${name}
|
|
33
|
+
console.error(` ${name}:`, message);
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
}
|
package/src/user.tsx
CHANGED
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
|
|
208
208
|
import assert from "node:assert";
|
|
209
209
|
import { webcrypto as crypto } from "node:crypto";
|
|
210
|
-
import {
|
|
210
|
+
import { writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
211
211
|
import http from "node:http";
|
|
212
212
|
import os from "node:os";
|
|
213
213
|
import path from "node:path";
|
|
@@ -222,6 +222,7 @@ import { fetch } from "undici";
|
|
|
222
222
|
import { getCloudflareApiBaseUrl } from "./cfetch";
|
|
223
223
|
import { getEnvironmentVariableFactory } from "./environment-variables";
|
|
224
224
|
import openInBrowser from "./open-in-browser";
|
|
225
|
+
import { readFileSync } from "./parse";
|
|
225
226
|
import type { Item as SelectInputItem } from "ink-select-input/build/SelectInput";
|
|
226
227
|
import type { ParsedUrlQuery } from "node:querystring";
|
|
227
228
|
import type { Response } from "undici";
|
|
@@ -251,17 +252,45 @@ interface PKCECodes {
|
|
|
251
252
|
codeVerifier: string;
|
|
252
253
|
}
|
|
253
254
|
|
|
254
|
-
|
|
255
|
-
|
|
255
|
+
/**
|
|
256
|
+
* The module level state of the authentication flow.
|
|
257
|
+
*/
|
|
258
|
+
interface State extends AuthTokens {
|
|
256
259
|
authorizationCode?: string;
|
|
257
260
|
codeChallenge?: string;
|
|
258
261
|
codeVerifier?: string;
|
|
259
262
|
hasAuthCodeBeenExchangedForAccessToken?: boolean;
|
|
260
|
-
refreshToken?: RefreshToken; // persist
|
|
261
263
|
stateQueryParam?: string;
|
|
262
264
|
scopes?: Scope[];
|
|
263
265
|
}
|
|
264
266
|
|
|
267
|
+
/**
|
|
268
|
+
* The tokens related to authentication.
|
|
269
|
+
*/
|
|
270
|
+
interface AuthTokens {
|
|
271
|
+
accessToken?: AccessToken;
|
|
272
|
+
refreshToken?: RefreshToken;
|
|
273
|
+
/** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
|
|
274
|
+
apiToken?: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The path to the config file that holds user authentication data,
|
|
279
|
+
* relative to the user's home directory.
|
|
280
|
+
*/
|
|
281
|
+
export const USER_AUTH_CONFIG_FILE = ".wrangler/config/default.toml";
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* The data that may be read from the `USER_CONFIG_FILE`.
|
|
285
|
+
*/
|
|
286
|
+
export interface UserAuthConfig {
|
|
287
|
+
oauth_token?: string;
|
|
288
|
+
refresh_token?: string;
|
|
289
|
+
expiration_time?: string;
|
|
290
|
+
/** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
|
|
291
|
+
api_token?: string;
|
|
292
|
+
}
|
|
293
|
+
|
|
265
294
|
interface RefreshToken {
|
|
266
295
|
value: string;
|
|
267
296
|
}
|
|
@@ -311,60 +340,74 @@ const TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
|
|
|
311
340
|
const CALLBACK_URL = "http://localhost:8976/oauth/callback";
|
|
312
341
|
const REVOKE_URL = "https://dash.cloudflare.com/oauth2/revoke";
|
|
313
342
|
|
|
314
|
-
|
|
315
|
-
let initialised = false;
|
|
343
|
+
let LocalState: State = getAuthTokens();
|
|
316
344
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
345
|
+
/**
|
|
346
|
+
* Compute the current auth tokens.
|
|
347
|
+
*/
|
|
348
|
+
function getAuthTokens(config?: UserAuthConfig): AuthTokens {
|
|
321
349
|
// get refreshToken/accessToken from fs if exists
|
|
322
350
|
try {
|
|
323
351
|
// if the environment variable is available, use that
|
|
324
352
|
const apiTokenFromEnv = getCloudflareAPITokenFromEnv();
|
|
325
353
|
if (apiTokenFromEnv) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
354
|
+
return {
|
|
355
|
+
accessToken: {
|
|
356
|
+
value: apiTokenFromEnv,
|
|
357
|
+
expiry: "3021-12-31T23:59:59+00:00",
|
|
358
|
+
},
|
|
329
359
|
};
|
|
330
|
-
initialised = true;
|
|
331
|
-
return;
|
|
332
360
|
}
|
|
333
361
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
);
|
|
339
|
-
const { oauth_token, refresh_token, expiration_time } = toml as {
|
|
340
|
-
oauth_token: string;
|
|
341
|
-
refresh_token: string;
|
|
342
|
-
expiration_time: string;
|
|
343
|
-
};
|
|
362
|
+
// otherwise try loading from the user auth config file.
|
|
363
|
+
const { oauth_token, refresh_token, expiration_time, api_token } =
|
|
364
|
+
config || readAuthConfigFile();
|
|
365
|
+
|
|
344
366
|
if (oauth_token) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
367
|
+
return {
|
|
368
|
+
accessToken: {
|
|
369
|
+
value: oauth_token,
|
|
370
|
+
// If there is no `expiration_time` field then set it to an old date, to cause it to expire immediately.
|
|
371
|
+
expiry: expiration_time ?? "2000-01-01:00:00:00+00:00",
|
|
372
|
+
},
|
|
373
|
+
refreshToken: { value: refresh_token ?? "" },
|
|
374
|
+
};
|
|
375
|
+
} else if (api_token) {
|
|
376
|
+
return { apiToken: api_token };
|
|
377
|
+
} else {
|
|
378
|
+
return {};
|
|
349
379
|
}
|
|
350
|
-
} catch
|
|
351
|
-
|
|
352
|
-
// console.error(err);
|
|
380
|
+
} catch {
|
|
381
|
+
return {};
|
|
353
382
|
}
|
|
354
|
-
initialised = true;
|
|
355
383
|
}
|
|
356
384
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
385
|
+
/**
|
|
386
|
+
* Run the initialisation of the auth state, in the case that something changed.
|
|
387
|
+
*
|
|
388
|
+
* This runs automatically whenever `writeAuthConfigFile` is run, so generally
|
|
389
|
+
* you won't need to call it yourself.
|
|
390
|
+
*/
|
|
391
|
+
export function reinitialiseAuthTokens(): void;
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Reinitialise auth state from an in-memory config, skipping
|
|
395
|
+
* over the part where we write a file and then read it back into memory
|
|
396
|
+
*/
|
|
397
|
+
export function reinitialiseAuthTokens(config: UserAuthConfig): void;
|
|
398
|
+
|
|
399
|
+
export function reinitialiseAuthTokens(config?: UserAuthConfig): void {
|
|
400
|
+
LocalState = getAuthTokens(config);
|
|
364
401
|
}
|
|
365
402
|
|
|
366
403
|
export function getAPIToken(): string | undefined {
|
|
367
|
-
|
|
404
|
+
if (LocalState.apiToken) {
|
|
405
|
+
console.warn(
|
|
406
|
+
"It looks like you have used Wrangler 1's `config` command to login with an API token.\n" +
|
|
407
|
+
"This is no longer supported in the current version of Wrangler.\n" +
|
|
408
|
+
"If you wish to authenticate via an API token then please set the `CLOUDFLARE_API_TOKEN` environment variable."
|
|
409
|
+
);
|
|
410
|
+
}
|
|
368
411
|
return LocalState.accessToken?.value;
|
|
369
412
|
}
|
|
370
413
|
|
|
@@ -578,8 +621,8 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
|
|
|
578
621
|
`?response_type=code&` +
|
|
579
622
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
580
623
|
`redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
|
|
581
|
-
//
|
|
582
|
-
`scope=${encodeURIComponent(scopes
|
|
624
|
+
// we add offline_access manually for every request
|
|
625
|
+
`scope=${encodeURIComponent([...scopes, "offline_access"].join(" "))}&` +
|
|
583
626
|
`state=${stateQueryParam}&` +
|
|
584
627
|
`code_challenge=${encodeURIComponent(codeChallenge)}&` +
|
|
585
628
|
`code_challenge_method=S256`
|
|
@@ -800,19 +843,28 @@ function generateRandomState(lengthOfState: number): string {
|
|
|
800
843
|
.join("");
|
|
801
844
|
}
|
|
802
845
|
|
|
803
|
-
|
|
804
|
-
|
|
846
|
+
/**
|
|
847
|
+
* Writes a a wrangler config file (auth credentials) to disk,
|
|
848
|
+
* and updates the user auth state with the new credentials.
|
|
849
|
+
*/
|
|
850
|
+
export function writeAuthConfigFile(config: UserAuthConfig) {
|
|
851
|
+
mkdirSync(path.join(os.homedir(), ".wrangler/config/"), {
|
|
805
852
|
recursive: true,
|
|
806
853
|
});
|
|
807
|
-
|
|
808
|
-
path.join(os.homedir(),
|
|
809
|
-
|
|
810
|
-
oauth_token = "${tokenData.token?.value || ""}"
|
|
811
|
-
refresh_token = "${tokenData.refreshToken?.value}"
|
|
812
|
-
expiration_time = "${tokenData.token?.expiry}"
|
|
813
|
-
`,
|
|
854
|
+
writeFileSync(
|
|
855
|
+
path.join(os.homedir(), USER_AUTH_CONFIG_FILE),
|
|
856
|
+
TOML.stringify(config as TOML.JsonMap),
|
|
814
857
|
{ encoding: "utf-8" }
|
|
815
858
|
);
|
|
859
|
+
|
|
860
|
+
reinitialiseAuthTokens();
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
export function readAuthConfigFile(): UserAuthConfig {
|
|
864
|
+
const toml = TOML.parse(
|
|
865
|
+
readFileSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE))
|
|
866
|
+
);
|
|
867
|
+
return toml;
|
|
816
868
|
}
|
|
817
869
|
|
|
818
870
|
type LoginProps = {
|
|
@@ -824,20 +876,29 @@ export async function loginOrRefreshIfRequired(
|
|
|
824
876
|
): Promise<boolean> {
|
|
825
877
|
// TODO: if there already is a token, then try refreshing
|
|
826
878
|
// TODO: ask permission before opening browser
|
|
827
|
-
if (!
|
|
879
|
+
if (!getAPIToken()) {
|
|
828
880
|
// Not logged in.
|
|
829
881
|
// If we are not interactive, we cannot ask the user to login
|
|
830
882
|
return isInteractive && (await login());
|
|
831
883
|
} else if (isAccessTokenExpired()) {
|
|
832
|
-
|
|
884
|
+
// We're logged in, but the refresh token seems to have expired,
|
|
885
|
+
// so let's try to refresh it
|
|
886
|
+
const didRefresh = await refreshToken();
|
|
887
|
+
if (didRefresh) {
|
|
888
|
+
// The token was refreshed, so we're done here
|
|
889
|
+
return true;
|
|
890
|
+
} else {
|
|
891
|
+
// If the refresh token isn't valid, then we ask the user to login again
|
|
892
|
+
return isInteractive && (await login());
|
|
893
|
+
}
|
|
833
894
|
} else {
|
|
834
895
|
return true;
|
|
835
896
|
}
|
|
836
897
|
}
|
|
837
898
|
|
|
838
899
|
export async function login(props?: LoginProps): Promise<boolean> {
|
|
900
|
+
console.log("Attempting to login via OAuth...");
|
|
839
901
|
const urlToOpen = await getAuthURL(props?.scopes);
|
|
840
|
-
await openInBrowser(urlToOpen);
|
|
841
902
|
let server: http.Server;
|
|
842
903
|
let loginTimeoutHandle: NodeJS.Timeout;
|
|
843
904
|
const timerPromise = new Promise<boolean>((resolve) => {
|
|
@@ -894,8 +955,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
894
955
|
finish(false, new ErrorNoAuthCode());
|
|
895
956
|
return;
|
|
896
957
|
} else {
|
|
897
|
-
const
|
|
898
|
-
|
|
958
|
+
const exchange = await exchangeAuthCodeForAccessToken();
|
|
959
|
+
writeAuthConfigFile({
|
|
960
|
+
oauth_token: exchange.token?.value ?? "",
|
|
961
|
+
expiration_time: exchange.token?.expiry,
|
|
962
|
+
refresh_token: exchange.refreshToken?.value,
|
|
963
|
+
});
|
|
899
964
|
res.writeHead(307, {
|
|
900
965
|
Location:
|
|
901
966
|
"https://welcome.developers.workers.dev/wrangler-oauth-consent-granted",
|
|
@@ -903,9 +968,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
903
968
|
res.end(() => {
|
|
904
969
|
finish(true);
|
|
905
970
|
});
|
|
906
|
-
console.log(
|
|
907
|
-
`Successfully configured. You can find your configuration file at: ${os.homedir()}/.wrangler/config/default.toml`
|
|
908
|
-
);
|
|
971
|
+
console.log(`Successfully logged in.`);
|
|
909
972
|
|
|
910
973
|
return;
|
|
911
974
|
}
|
|
@@ -916,24 +979,30 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
916
979
|
server.listen(8976);
|
|
917
980
|
});
|
|
918
981
|
|
|
982
|
+
await openInBrowser(urlToOpen);
|
|
983
|
+
|
|
919
984
|
return Promise.race([timerPromise, loginPromise]);
|
|
920
985
|
}
|
|
921
986
|
|
|
922
987
|
/**
|
|
923
988
|
* Checks to see if the access token has expired.
|
|
924
989
|
*/
|
|
925
|
-
|
|
926
|
-
throwIfNotInitialised();
|
|
990
|
+
function isAccessTokenExpired(): boolean {
|
|
927
991
|
const { accessToken } = LocalState;
|
|
928
992
|
return Boolean(accessToken && new Date() >= new Date(accessToken.expiry));
|
|
929
993
|
}
|
|
930
994
|
|
|
931
|
-
|
|
932
|
-
throwIfNotInitialised();
|
|
995
|
+
async function refreshToken(): Promise<boolean> {
|
|
933
996
|
// refresh
|
|
934
997
|
try {
|
|
935
|
-
const
|
|
936
|
-
|
|
998
|
+
const {
|
|
999
|
+
token: { value: oauth_token, expiry: expiration_time } = {
|
|
1000
|
+
value: "",
|
|
1001
|
+
expiry: "",
|
|
1002
|
+
},
|
|
1003
|
+
refreshToken: { value: refresh_token } = {},
|
|
1004
|
+
} = await exchangeRefreshTokenForAccessToken();
|
|
1005
|
+
writeAuthConfigFile({ oauth_token, expiration_time, refresh_token });
|
|
937
1006
|
return true;
|
|
938
1007
|
} catch (err) {
|
|
939
1008
|
console.error(err);
|
|
@@ -942,10 +1011,28 @@ export async function refreshToken(): Promise<boolean> {
|
|
|
942
1011
|
}
|
|
943
1012
|
|
|
944
1013
|
export async function logout(): Promise<void> {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1014
|
+
if (!LocalState.accessToken) {
|
|
1015
|
+
if (!LocalState.refreshToken) {
|
|
1016
|
+
console.log("Not logged in, exiting...");
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const body =
|
|
1021
|
+
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
1022
|
+
`token_type_hint=refresh_token&` +
|
|
1023
|
+
`token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
|
|
1024
|
+
|
|
1025
|
+
const response = await fetch(REVOKE_URL, {
|
|
1026
|
+
method: "POST",
|
|
1027
|
+
body,
|
|
1028
|
+
headers: {
|
|
1029
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1030
|
+
},
|
|
1031
|
+
});
|
|
1032
|
+
await response.text(); // blank text? would be nice if it was something meaningful
|
|
1033
|
+
console.log(
|
|
1034
|
+
"💁 Wrangler is configured with an OAuth token. The token has been successfully revoked"
|
|
1035
|
+
);
|
|
949
1036
|
}
|
|
950
1037
|
const body =
|
|
951
1038
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
@@ -960,19 +1047,12 @@ export async function logout(): Promise<void> {
|
|
|
960
1047
|
},
|
|
961
1048
|
});
|
|
962
1049
|
await response.text(); // blank text? would be nice if it was something meaningful
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
);
|
|
966
|
-
// delete the file
|
|
967
|
-
await rm(path.join(os.homedir(), ".wrangler/config/default.toml"));
|
|
968
|
-
console.log(
|
|
969
|
-
`Removing ${os.homedir()}/.wrangler/config/default.toml.. success!`
|
|
970
|
-
);
|
|
1050
|
+
rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
|
|
1051
|
+
console.log(`Successfully logged out.`);
|
|
971
1052
|
}
|
|
972
1053
|
|
|
973
|
-
export function listScopes(): void {
|
|
974
|
-
|
|
975
|
-
console.log("💁 Available scopes:");
|
|
1054
|
+
export function listScopes(message = "💁 Available scopes:"): void {
|
|
1055
|
+
console.log(message);
|
|
976
1056
|
const data = ScopeKeys.map((scope: Scope) => ({
|
|
977
1057
|
Scope: scope,
|
|
978
1058
|
Description: Scopes[scope],
|