@anvil-works/anvil-cli 0.5.10 → 0.5.12
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 +14 -0
- package/dist/cli.js +265 -77
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -172,6 +172,7 @@ Useful options:
|
|
|
172
172
|
|
|
173
173
|
```bash
|
|
174
174
|
anvil login
|
|
175
|
+
anvil login anvil.company.com
|
|
175
176
|
anvil logout
|
|
176
177
|
anvil config list
|
|
177
178
|
anvil config get <key>
|
|
@@ -260,6 +261,19 @@ If you want to change the default server permanently:
|
|
|
260
261
|
anvil config set anvilUrl https://anvil.company.com
|
|
261
262
|
```
|
|
262
263
|
|
|
264
|
+
When you run `anvil login`, the CLI now supports both local and headless flows:
|
|
265
|
+
|
|
266
|
+
- press `Enter` to open the default browser
|
|
267
|
+
- or copy the printed device-login URL and code to continue in a browser of your choice
|
|
268
|
+
|
|
269
|
+
For `anvil login`, prefer the positional server argument in docs and examples:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
anvil login anvil.company.com
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`--url` is also supported if needed.
|
|
276
|
+
|
|
263
277
|
## Troubleshooting
|
|
264
278
|
|
|
265
279
|
### `anvil` command not found
|
package/dist/cli.js
CHANGED
|
@@ -50998,6 +50998,16 @@ var __webpack_exports__ = {};
|
|
|
50998
50998
|
const selectedUrl = await logger_logger.select("Multiple logged-in Anvil URLs found. Which one would you like to use?", choices, decision.urls[0]);
|
|
50999
50999
|
return selectedUrl;
|
|
51000
51000
|
}
|
|
51001
|
+
const defaultConfigureWatchGitAuthDeps = {
|
|
51002
|
+
configureCredentialHelperForUrl: configureCredentialHelperForUrl,
|
|
51003
|
+
setAppAuthBinding: setAppAuthBinding
|
|
51004
|
+
};
|
|
51005
|
+
const defaultSyncStartDeps = {
|
|
51006
|
+
pushToAnvil,
|
|
51007
|
+
recreateSessionAndValidate,
|
|
51008
|
+
recheckSyncStatus,
|
|
51009
|
+
startWatchingWithEventHandlers
|
|
51010
|
+
};
|
|
51001
51011
|
function resolveWatchOpenPath(repoPath) {
|
|
51002
51012
|
return external_path_default().resolve(repoPath);
|
|
51003
51013
|
}
|
|
@@ -51007,6 +51017,17 @@ var __webpack_exports__ = {};
|
|
|
51007
51017
|
await openPathInEditorOrDefault(targetPath, preferredEditorCommand, deps);
|
|
51008
51018
|
logger_logger.info(chalk_source.gray(`Opened ${targetPath}`));
|
|
51009
51019
|
}
|
|
51020
|
+
async function configureWatchGitAuth(options, deps = defaultConfigureWatchGitAuthDeps) {
|
|
51021
|
+
await deps.configureCredentialHelperForUrl(options.repoPath, options.anvilUrl);
|
|
51022
|
+
await deps.setAppAuthBinding(options.repoPath, options.appId, {
|
|
51023
|
+
url: options.anvilUrl,
|
|
51024
|
+
username: options.username
|
|
51025
|
+
});
|
|
51026
|
+
}
|
|
51027
|
+
function isStaleRemotePushError(message) {
|
|
51028
|
+
const normalized = message.toLowerCase();
|
|
51029
|
+
return normalized.includes("fetch first") || normalized.includes("non-fast-forward") || normalized.includes("failed to push some refs") || normalized.includes("remote contains work that you do") || normalized.includes("[rejected]");
|
|
51030
|
+
}
|
|
51010
51031
|
async function selectAppId(candidates) {
|
|
51011
51032
|
if (0 === candidates.length) {
|
|
51012
51033
|
logger_logger.warn("Could not auto-detect app ID from repository.");
|
|
@@ -51093,11 +51114,19 @@ var __webpack_exports__ = {};
|
|
|
51093
51114
|
logger_logger.progress("push", "Pushing to Anvil...");
|
|
51094
51115
|
await git.push(pushUrl, refSpec, options.force ?? false);
|
|
51095
51116
|
logger_logger.progressEnd("push", "Pushed to Anvil");
|
|
51096
|
-
return
|
|
51117
|
+
return {
|
|
51118
|
+
success: true,
|
|
51119
|
+
staleRemote: false
|
|
51120
|
+
};
|
|
51097
51121
|
} catch (e) {
|
|
51122
|
+
const errorMessage = errors_getErrorMessage(e);
|
|
51098
51123
|
logger_logger.progressEnd("push", "Push failed");
|
|
51099
|
-
logger_logger.error(`Failed to push: ${
|
|
51100
|
-
return
|
|
51124
|
+
logger_logger.error(`Failed to push: ${errorMessage}`);
|
|
51125
|
+
return {
|
|
51126
|
+
success: false,
|
|
51127
|
+
staleRemote: isStaleRemotePushError(errorMessage),
|
|
51128
|
+
errorMessage
|
|
51129
|
+
};
|
|
51101
51130
|
}
|
|
51102
51131
|
}
|
|
51103
51132
|
async function fetchAndRebaseFromAnvil(options) {
|
|
@@ -51223,7 +51252,7 @@ var __webpack_exports__ = {};
|
|
|
51223
51252
|
return false;
|
|
51224
51253
|
}
|
|
51225
51254
|
}
|
|
51226
|
-
async function checkSyncStatusAndStart(session, options) {
|
|
51255
|
+
async function checkSyncStatusAndStart(session, options, deps = defaultSyncStartDeps) {
|
|
51227
51256
|
const syncStatus = session.syncStatus;
|
|
51228
51257
|
const branchName = session.getBranchName() || "master";
|
|
51229
51258
|
const hasUncommitted = session.hasUncommittedChanges;
|
|
@@ -51236,8 +51265,8 @@ var __webpack_exports__ = {};
|
|
|
51236
51265
|
else {
|
|
51237
51266
|
shouldPush = await logger_logger.confirm("Would you like to push this branch to Anvil?", true);
|
|
51238
51267
|
if (shouldPush) {
|
|
51239
|
-
const changed = await recheckSyncStatus(stateCategory, branchName, options);
|
|
51240
|
-
if (changed) return await checkSyncStatusAndStart(changed, options);
|
|
51268
|
+
const changed = await deps.recheckSyncStatus(stateCategory, branchName, options);
|
|
51269
|
+
if (changed) return await checkSyncStatusAndStart(changed, options, deps);
|
|
51241
51270
|
}
|
|
51242
51271
|
}
|
|
51243
51272
|
if (!shouldPush) {
|
|
@@ -51246,15 +51275,21 @@ var __webpack_exports__ = {};
|
|
|
51246
51275
|
return false;
|
|
51247
51276
|
}
|
|
51248
51277
|
session.cleanup();
|
|
51249
|
-
const pushed = await pushToAnvil({
|
|
51278
|
+
const pushed = await deps.pushToAnvil({
|
|
51250
51279
|
repoPath: options.repoPath,
|
|
51251
51280
|
appId: options.appId,
|
|
51252
51281
|
anvilUrl: options.anvilUrl,
|
|
51253
51282
|
branchName,
|
|
51254
51283
|
username: options.username
|
|
51255
51284
|
});
|
|
51256
|
-
if (!pushed)
|
|
51257
|
-
|
|
51285
|
+
if (!pushed.success) {
|
|
51286
|
+
if (pushed.staleRemote) {
|
|
51287
|
+
logger_logger.warn("Branch appeared on Anvil while pushing. Re-checking sync status...");
|
|
51288
|
+
return await deps.recreateSessionAndValidate(options);
|
|
51289
|
+
}
|
|
51290
|
+
return false;
|
|
51291
|
+
}
|
|
51292
|
+
return await deps.recreateSessionAndValidate(options);
|
|
51258
51293
|
}
|
|
51259
51294
|
if (syncStatus?.behind || syncStatus?.ahead) if (syncStatus.ahead && !syncStatus.behind) {
|
|
51260
51295
|
logger_logger.warn(`Your local repository is ${syncStatus.ahead} commit(s) ahead of Anvil.`);
|
|
@@ -51274,8 +51309,8 @@ var __webpack_exports__ = {};
|
|
|
51274
51309
|
], "push");
|
|
51275
51310
|
shouldPush = "push" === action;
|
|
51276
51311
|
if (shouldPush) {
|
|
51277
|
-
const changed = await recheckSyncStatus(stateCategory, branchName, options);
|
|
51278
|
-
if (changed) return await checkSyncStatusAndStart(changed, options);
|
|
51312
|
+
const changed = await deps.recheckSyncStatus(stateCategory, branchName, options);
|
|
51313
|
+
if (changed) return await checkSyncStatusAndStart(changed, options, deps);
|
|
51279
51314
|
}
|
|
51280
51315
|
}
|
|
51281
51316
|
if (!shouldPush) {
|
|
@@ -51284,15 +51319,15 @@ var __webpack_exports__ = {};
|
|
|
51284
51319
|
return false;
|
|
51285
51320
|
}
|
|
51286
51321
|
session.cleanup();
|
|
51287
|
-
const pushed = await pushToAnvil({
|
|
51322
|
+
const pushed = await deps.pushToAnvil({
|
|
51288
51323
|
repoPath: options.repoPath,
|
|
51289
51324
|
appId: options.appId,
|
|
51290
51325
|
anvilUrl: options.anvilUrl,
|
|
51291
51326
|
branchName,
|
|
51292
51327
|
username: options.username
|
|
51293
51328
|
});
|
|
51294
|
-
if (!pushed) return false;
|
|
51295
|
-
return await recreateSessionAndValidate(options);
|
|
51329
|
+
if (!pushed.success) return false;
|
|
51330
|
+
return await deps.recreateSessionAndValidate(options);
|
|
51296
51331
|
} else if (syncStatus.diverged) {
|
|
51297
51332
|
logger_logger.warn("Your local repository has diverged from Anvil.");
|
|
51298
51333
|
logger_logger.info(chalk_source.gray(` You are ${syncStatus.ahead} commit(s) ahead and ${syncStatus.behind} commit(s) behind.`));
|
|
@@ -51315,7 +51350,7 @@ var __webpack_exports__ = {};
|
|
|
51315
51350
|
return false;
|
|
51316
51351
|
}
|
|
51317
51352
|
if (!result.success) return false;
|
|
51318
|
-
return await recreateSessionAndValidate(options);
|
|
51353
|
+
return await deps.recreateSessionAndValidate(options);
|
|
51319
51354
|
}
|
|
51320
51355
|
const action = await logger_logger.select("How would you like to resolve this?", [
|
|
51321
51356
|
{
|
|
@@ -51339,8 +51374,8 @@ var __webpack_exports__ = {};
|
|
|
51339
51374
|
logger_logger.warn("Watch cancelled.");
|
|
51340
51375
|
return false;
|
|
51341
51376
|
}
|
|
51342
|
-
const changed = await recheckSyncStatus(stateCategory, branchName, options);
|
|
51343
|
-
if (changed) return await checkSyncStatusAndStart(changed, options);
|
|
51377
|
+
const changed = await deps.recheckSyncStatus(stateCategory, branchName, options);
|
|
51378
|
+
if (changed) return await checkSyncStatusAndStart(changed, options, deps);
|
|
51344
51379
|
if ("rebase" === action) {
|
|
51345
51380
|
const result = await fetchAndRebaseFromAnvil({
|
|
51346
51381
|
repoPath: options.repoPath,
|
|
@@ -51357,7 +51392,7 @@ var __webpack_exports__ = {};
|
|
|
51357
51392
|
return false;
|
|
51358
51393
|
}
|
|
51359
51394
|
if (!result.success) return false;
|
|
51360
|
-
return await recreateSessionAndValidate(options);
|
|
51395
|
+
return await deps.recreateSessionAndValidate(options);
|
|
51361
51396
|
}
|
|
51362
51397
|
if ("reset" === action) {
|
|
51363
51398
|
const resetWarning = hasUncommitted ? `This will discard ${syncStatus.ahead} local commit(s) and your uncommitted changes. Are you sure?` : `This will discard ${syncStatus.ahead} local commit(s). Are you sure?`;
|
|
@@ -51374,7 +51409,7 @@ var __webpack_exports__ = {};
|
|
|
51374
51409
|
username: options.username
|
|
51375
51410
|
});
|
|
51376
51411
|
if (!reset) return false;
|
|
51377
|
-
return await recreateSessionAndValidate(options);
|
|
51412
|
+
return await deps.recreateSessionAndValidate(options);
|
|
51378
51413
|
}
|
|
51379
51414
|
if ("push" === action) {
|
|
51380
51415
|
const confirmed = await logger_logger.confirm("This will overwrite Anvil's version. Are you sure?", false);
|
|
@@ -51382,7 +51417,7 @@ var __webpack_exports__ = {};
|
|
|
51382
51417
|
logger_logger.warn("Watch cancelled.");
|
|
51383
51418
|
return false;
|
|
51384
51419
|
}
|
|
51385
|
-
const pushed = await pushToAnvil({
|
|
51420
|
+
const pushed = await deps.pushToAnvil({
|
|
51386
51421
|
repoPath: options.repoPath,
|
|
51387
51422
|
appId: options.appId,
|
|
51388
51423
|
anvilUrl: options.anvilUrl,
|
|
@@ -51390,8 +51425,8 @@ var __webpack_exports__ = {};
|
|
|
51390
51425
|
username: options.username,
|
|
51391
51426
|
force: true
|
|
51392
51427
|
});
|
|
51393
|
-
if (!pushed) return false;
|
|
51394
|
-
return await recreateSessionAndValidate(options);
|
|
51428
|
+
if (!pushed.success) return false;
|
|
51429
|
+
return await deps.recreateSessionAndValidate(options);
|
|
51395
51430
|
}
|
|
51396
51431
|
} else {
|
|
51397
51432
|
logger_logger.warn(`Your local repository is ${syncStatus.behind} commit(s) behind Anvil.`);
|
|
@@ -51402,8 +51437,8 @@ var __webpack_exports__ = {};
|
|
|
51402
51437
|
else {
|
|
51403
51438
|
shouldSync = await logger_logger.confirm("Would you like to sync to the latest version from Anvil now?", true);
|
|
51404
51439
|
if (shouldSync) {
|
|
51405
|
-
const changed = await recheckSyncStatus(stateCategory, branchName, options);
|
|
51406
|
-
if (changed) return await checkSyncStatusAndStart(changed, options);
|
|
51440
|
+
const changed = await deps.recheckSyncStatus(stateCategory, branchName, options);
|
|
51441
|
+
if (changed) return await checkSyncStatusAndStart(changed, options, deps);
|
|
51407
51442
|
}
|
|
51408
51443
|
}
|
|
51409
51444
|
if (!shouldSync) {
|
|
@@ -51428,9 +51463,9 @@ var __webpack_exports__ = {};
|
|
|
51428
51463
|
logger_logger.error(`Failed to sync: ${errors_getErrorMessage(e)}`);
|
|
51429
51464
|
process.exit(1);
|
|
51430
51465
|
}
|
|
51431
|
-
return await recreateSessionAndValidate(options);
|
|
51466
|
+
return await deps.recreateSessionAndValidate(options);
|
|
51432
51467
|
}
|
|
51433
|
-
await startWatchingWithEventHandlers(session, options);
|
|
51468
|
+
await deps.startWatchingWithEventHandlers(session, options);
|
|
51434
51469
|
return true;
|
|
51435
51470
|
}
|
|
51436
51471
|
async function startWatchingWithEventHandlers(session, options) {
|
|
@@ -51639,6 +51674,12 @@ var __webpack_exports__ = {};
|
|
|
51639
51674
|
process.exit(1);
|
|
51640
51675
|
}
|
|
51641
51676
|
logger_logger.verbose(chalk_source.green("✓ Authentication tokens found"));
|
|
51677
|
+
await configureWatchGitAuth({
|
|
51678
|
+
repoPath,
|
|
51679
|
+
appId: finalAppId,
|
|
51680
|
+
anvilUrl,
|
|
51681
|
+
username
|
|
51682
|
+
});
|
|
51642
51683
|
logger_logger.progress("validate", `Validating app ID: ${finalAppId}`);
|
|
51643
51684
|
const appIdValidation = await validateAppId(finalAppId, anvilUrl, username);
|
|
51644
51685
|
logger_logger.progressEnd("validate");
|
|
@@ -51849,34 +51890,134 @@ var __webpack_exports__ = {};
|
|
|
51849
51890
|
</body>
|
|
51850
51891
|
</html>`;
|
|
51851
51892
|
}
|
|
51893
|
+
const CLIENT_ID = "anvil-sync";
|
|
51894
|
+
const SCOPES = "apps:read apps:write user:read";
|
|
51852
51895
|
function oauth_login_base64url(buf) {
|
|
51853
51896
|
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
51854
51897
|
}
|
|
51855
|
-
|
|
51898
|
+
function sleep(ms) {
|
|
51899
|
+
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
51900
|
+
}
|
|
51901
|
+
function createBrowserLaunchPrompt(authUrl) {
|
|
51902
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return {
|
|
51903
|
+
waitForOpen: Promise.resolve(false),
|
|
51904
|
+
close: ()=>{}
|
|
51905
|
+
};
|
|
51856
51906
|
const rl = external_readline_namespaceObject.createInterface({
|
|
51857
51907
|
input: process.stdin,
|
|
51858
51908
|
output: process.stdout
|
|
51859
51909
|
});
|
|
51860
|
-
|
|
51861
|
-
|
|
51862
|
-
|
|
51863
|
-
|
|
51864
|
-
|
|
51865
|
-
oauthPromise.then((r)=>({
|
|
51866
|
-
oauth: r
|
|
51867
|
-
}))
|
|
51868
|
-
]);
|
|
51869
|
-
if ("enter" === result) {
|
|
51910
|
+
let closed = false;
|
|
51911
|
+
let resolveWait = null;
|
|
51912
|
+
const close = (opened = false)=>{
|
|
51913
|
+
if (closed) return;
|
|
51914
|
+
closed = true;
|
|
51870
51915
|
rl.close();
|
|
51871
|
-
|
|
51872
|
-
|
|
51873
|
-
|
|
51916
|
+
resolveWait?.(opened);
|
|
51917
|
+
resolveWait = null;
|
|
51918
|
+
};
|
|
51919
|
+
const waitForOpen = new Promise((resolve)=>{
|
|
51920
|
+
resolveWait = resolve;
|
|
51921
|
+
rl.on("SIGINT", ()=>{
|
|
51922
|
+
close();
|
|
51923
|
+
process.kill(process.pid, "SIGINT");
|
|
51924
|
+
});
|
|
51925
|
+
rl.question("", async ()=>{
|
|
51926
|
+
try {
|
|
51927
|
+
await node_modules_open(authUrl);
|
|
51928
|
+
logger_logger.info(chalk_source.dim("Opened your browser to continue login."));
|
|
51929
|
+
close(true);
|
|
51930
|
+
} catch {
|
|
51931
|
+
logger_logger.warn("Could not open a browser automatically.");
|
|
51932
|
+
close(false);
|
|
51933
|
+
}
|
|
51934
|
+
});
|
|
51935
|
+
});
|
|
51936
|
+
return {
|
|
51937
|
+
waitForOpen,
|
|
51938
|
+
close
|
|
51939
|
+
};
|
|
51940
|
+
}
|
|
51941
|
+
async function parseOAuthError(response) {
|
|
51942
|
+
const contentType = response.headers.get("content-type") || "";
|
|
51943
|
+
if (contentType.includes("application/json")) {
|
|
51944
|
+
const data = await response.json();
|
|
51945
|
+
return data.error || `HTTP ${response.status}`;
|
|
51946
|
+
}
|
|
51947
|
+
const text = (await response.text()).trim();
|
|
51948
|
+
if (!text) return `HTTP ${response.status}`;
|
|
51949
|
+
try {
|
|
51950
|
+
const parsed = JSON.parse(text);
|
|
51951
|
+
return parsed.error || text;
|
|
51952
|
+
} catch {
|
|
51953
|
+
return text;
|
|
51874
51954
|
}
|
|
51875
|
-
rl.close();
|
|
51876
|
-
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
51877
|
-
return result.oauth;
|
|
51878
51955
|
}
|
|
51879
|
-
async function
|
|
51956
|
+
async function exchangeAuthorizationCodeForTokens(anvilUrl, redirectUri, code, codeVerifier) {
|
|
51957
|
+
const tokenResponse = await fetch(`${anvilUrl}/oauth/token`, {
|
|
51958
|
+
method: "POST",
|
|
51959
|
+
headers: {
|
|
51960
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
51961
|
+
},
|
|
51962
|
+
body: new URLSearchParams({
|
|
51963
|
+
grant_type: "authorization_code",
|
|
51964
|
+
code,
|
|
51965
|
+
redirect_uri: redirectUri,
|
|
51966
|
+
client_id: CLIENT_ID,
|
|
51967
|
+
code_verifier: codeVerifier
|
|
51968
|
+
})
|
|
51969
|
+
});
|
|
51970
|
+
if (!tokenResponse.ok) throw new Error(`Failed to exchange authorization code for token. ${await parseOAuthError(tokenResponse)}`);
|
|
51971
|
+
return await tokenResponse.json();
|
|
51972
|
+
}
|
|
51973
|
+
async function requestDeviceAuthorization(anvilUrl) {
|
|
51974
|
+
const response = await fetch(`${anvilUrl}/oauth/device_authorization`, {
|
|
51975
|
+
method: "POST",
|
|
51976
|
+
headers: {
|
|
51977
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
51978
|
+
},
|
|
51979
|
+
body: new URLSearchParams({
|
|
51980
|
+
client_id: CLIENT_ID,
|
|
51981
|
+
scope: SCOPES
|
|
51982
|
+
})
|
|
51983
|
+
});
|
|
51984
|
+
if (!response.ok) throw new Error(`Failed to start device login. ${await parseOAuthError(response)}`);
|
|
51985
|
+
return await response.json();
|
|
51986
|
+
}
|
|
51987
|
+
async function pollDeviceAuthorization(anvilUrl, deviceAuth, options) {
|
|
51988
|
+
const expiresAt = Date.now() + 1000 * deviceAuth.expires_in;
|
|
51989
|
+
let intervalMs = 1000 * Math.max(deviceAuth.interval ?? 5, 1);
|
|
51990
|
+
while(Date.now() < expiresAt){
|
|
51991
|
+
if (options?.isCancelled?.()) throw new Error("cancelled");
|
|
51992
|
+
const response = await fetch(`${anvilUrl}/oauth/token`, {
|
|
51993
|
+
method: "POST",
|
|
51994
|
+
headers: {
|
|
51995
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
51996
|
+
},
|
|
51997
|
+
body: new URLSearchParams({
|
|
51998
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
51999
|
+
device_code: deviceAuth.device_code,
|
|
52000
|
+
client_id: CLIENT_ID
|
|
52001
|
+
})
|
|
52002
|
+
});
|
|
52003
|
+
if (response.ok) return await response.json();
|
|
52004
|
+
const error = await parseOAuthError(response);
|
|
52005
|
+
if ("authorization_pending" === error) {
|
|
52006
|
+
await sleep(intervalMs);
|
|
52007
|
+
continue;
|
|
52008
|
+
}
|
|
52009
|
+
if ("slow_down" === error) {
|
|
52010
|
+
intervalMs += 1000;
|
|
52011
|
+
await sleep(intervalMs);
|
|
52012
|
+
continue;
|
|
52013
|
+
}
|
|
52014
|
+
if ("access_denied" === error) throw new Error("Device login was denied.");
|
|
52015
|
+
if ("expired_token" === error || "invalid_grant" === error) break;
|
|
52016
|
+
throw new Error(`Device login failed. ${error}`);
|
|
52017
|
+
}
|
|
52018
|
+
throw new Error("Device login expired before it was approved.");
|
|
52019
|
+
}
|
|
52020
|
+
async function createPkceLoginFlow(anvilUrl) {
|
|
51880
52021
|
const codeVerifier = external_crypto_.randomBytes(48).toString("hex");
|
|
51881
52022
|
const codeChallenge = oauth_login_base64url(external_crypto_.createHash("sha256").update(codeVerifier, "ascii").digest());
|
|
51882
52023
|
const state = external_crypto_.randomBytes(16).toString("hex");
|
|
@@ -51886,10 +52027,9 @@ var __webpack_exports__ = {};
|
|
|
51886
52027
|
if (!address || "string" == typeof address) throw new Error("No address");
|
|
51887
52028
|
const port = address.port;
|
|
51888
52029
|
const redirectUri = `http://127.0.0.1:${port}/oauth-callback`;
|
|
51889
|
-
const SCOPES = "apps:read apps:write user:read";
|
|
51890
52030
|
const authUrl = new URL(`${anvilUrl}/oauth/authorize`);
|
|
51891
52031
|
authUrl.searchParams.set("response_type", "code");
|
|
51892
|
-
authUrl.searchParams.set("client_id",
|
|
52032
|
+
authUrl.searchParams.set("client_id", CLIENT_ID);
|
|
51893
52033
|
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
51894
52034
|
authUrl.searchParams.set("scope", SCOPES);
|
|
51895
52035
|
authUrl.searchParams.set("state", state);
|
|
@@ -51923,37 +52063,84 @@ var __webpack_exports__ = {};
|
|
|
51923
52063
|
server.close();
|
|
51924
52064
|
});
|
|
51925
52065
|
});
|
|
51926
|
-
|
|
52066
|
+
let closed = false;
|
|
52067
|
+
server.on("close", ()=>{
|
|
52068
|
+
closed = true;
|
|
52069
|
+
});
|
|
52070
|
+
const close = async ()=>{
|
|
52071
|
+
if (closed) return;
|
|
52072
|
+
closed = true;
|
|
52073
|
+
server.closeAllConnections();
|
|
52074
|
+
if (!server.listening) return;
|
|
52075
|
+
await new Promise((resolve)=>server.close(()=>resolve()));
|
|
52076
|
+
};
|
|
52077
|
+
return {
|
|
52078
|
+
authUrl,
|
|
52079
|
+
close,
|
|
52080
|
+
waitForLogin: (async ()=>{
|
|
52081
|
+
const { code, error, recvState } = await codePromise;
|
|
52082
|
+
if (recvState !== state) throw new Error("Invalid state received from OAuth callback");
|
|
52083
|
+
if (error) throw new Error(`Error received from OAuth callback: ${error}`);
|
|
52084
|
+
const tokenData = await exchangeAuthorizationCodeForTokens(anvilUrl, redirectUri, code, codeVerifier);
|
|
52085
|
+
return login(anvilUrl, {
|
|
52086
|
+
access_token: tokenData.access_token,
|
|
52087
|
+
refresh_token: tokenData.refresh_token,
|
|
52088
|
+
expires_in: tokenData.expires_in,
|
|
52089
|
+
scope: tokenData.scope
|
|
52090
|
+
});
|
|
52091
|
+
})()
|
|
52092
|
+
};
|
|
52093
|
+
}
|
|
52094
|
+
function raceLoginAttempts(attempts) {
|
|
52095
|
+
return new Promise((resolve, reject)=>{
|
|
52096
|
+
let remaining = attempts.length;
|
|
52097
|
+
let lastError = null;
|
|
52098
|
+
for (const attempt of attempts)attempt.then(resolve).catch((error)=>{
|
|
52099
|
+
if ("cancelled" === error.message) return;
|
|
52100
|
+
remaining -= 1;
|
|
52101
|
+
lastError = error;
|
|
52102
|
+
if (0 === remaining && lastError) reject(lastError);
|
|
52103
|
+
});
|
|
52104
|
+
});
|
|
52105
|
+
}
|
|
52106
|
+
async function runInteractiveLoginFlow(anvilUrl) {
|
|
52107
|
+
const pkceFlow = await createPkceLoginFlow(anvilUrl);
|
|
52108
|
+
const deviceAuth = await requestDeviceAuthorization(anvilUrl);
|
|
52109
|
+
const browserPrompt = createBrowserLaunchPrompt(pkceFlow.authUrl.toString());
|
|
52110
|
+
let settled = false;
|
|
52111
|
+
let spinnerStarted = false;
|
|
52112
|
+
logger_logger.info(chalk_source.dim(`Logging in to ${anvilUrl}`));
|
|
51927
52113
|
console.log();
|
|
51928
|
-
logger_logger.info(chalk_source.
|
|
52114
|
+
logger_logger.info(chalk_source.dim("Visit:"), `${deviceAuth.verification_uri_complete || deviceAuth.verification_uri}`);
|
|
52115
|
+
logger_logger.info(chalk_source.dim("Device code:"), `${deviceAuth.user_code}`);
|
|
51929
52116
|
console.log();
|
|
51930
|
-
|
|
51931
|
-
|
|
51932
|
-
|
|
51933
|
-
|
|
51934
|
-
|
|
51935
|
-
|
|
51936
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
51937
|
-
},
|
|
51938
|
-
body: new URLSearchParams({
|
|
51939
|
-
grant_type: "authorization_code",
|
|
51940
|
-
code: code,
|
|
51941
|
-
redirect_uri: redirectUri,
|
|
51942
|
-
client_id: "anvil-sync",
|
|
51943
|
-
code_verifier: codeVerifier
|
|
51944
|
-
})
|
|
52117
|
+
logger_logger.info("OR Press ENTER to open a browser...");
|
|
52118
|
+
browserPrompt.waitForOpen.then((opened)=>{
|
|
52119
|
+
if (opened && !settled) {
|
|
52120
|
+
spinnerStarted = true;
|
|
52121
|
+
logger_logger.progress("login", "Waiting for login to complete...");
|
|
52122
|
+
}
|
|
51945
52123
|
});
|
|
51946
|
-
|
|
51947
|
-
const
|
|
51948
|
-
|
|
52124
|
+
try {
|
|
52125
|
+
const result = await raceLoginAttempts([
|
|
52126
|
+
pkceFlow.waitForLogin,
|
|
52127
|
+
pollDeviceAuthorization(anvilUrl, deviceAuth, {
|
|
52128
|
+
isCancelled: ()=>settled
|
|
52129
|
+
}).then(async (tokenData)=>login(anvilUrl, {
|
|
52130
|
+
access_token: tokenData.access_token,
|
|
52131
|
+
refresh_token: tokenData.refresh_token,
|
|
52132
|
+
expires_in: tokenData.expires_in,
|
|
52133
|
+
scope: tokenData.scope
|
|
52134
|
+
}))
|
|
52135
|
+
]);
|
|
52136
|
+
settled = true;
|
|
52137
|
+
return result;
|
|
52138
|
+
} finally{
|
|
52139
|
+
settled = true;
|
|
52140
|
+
browserPrompt.close();
|
|
52141
|
+
if (spinnerStarted) logger_logger.progressEnd("login");
|
|
52142
|
+
await pkceFlow.close();
|
|
51949
52143
|
}
|
|
51950
|
-
const tokenData = await tokenResponse.json();
|
|
51951
|
-
return login(anvilUrl, {
|
|
51952
|
-
access_token: tokenData.access_token,
|
|
51953
|
-
refresh_token: tokenData.refresh_token,
|
|
51954
|
-
expires_in: tokenData.expires_in,
|
|
51955
|
-
scope: tokenData.scope
|
|
51956
|
-
});
|
|
51957
52144
|
}
|
|
51958
52145
|
const CHECKOUT_ERROR_VALUE = "__ERROR__";
|
|
51959
52146
|
function isAbortLikeError(error) {
|
|
@@ -52706,10 +52893,11 @@ var __webpack_exports__ = {};
|
|
|
52706
52893
|
});
|
|
52707
52894
|
}
|
|
52708
52895
|
function registerLoginCommand(program) {
|
|
52709
|
-
const loginCommand = program.command("login [anvil-server-url]").description("Log in to Anvil using OAuth").alias("l").action(async (anvilUrl)=>{
|
|
52896
|
+
const loginCommand = program.command("login [anvil-server-url]").description("Log in to Anvil using OAuth").alias("l").option("-u, --url <ANVIL_URL>", "Specify Anvil server URL").action(async (anvilUrl, options)=>{
|
|
52710
52897
|
try {
|
|
52711
|
-
|
|
52712
|
-
|
|
52898
|
+
const requestedUrl = anvilUrl || options.url;
|
|
52899
|
+
if (requestedUrl) {
|
|
52900
|
+
anvilUrl = normalizeAnvilUrl(requestedUrl.trim());
|
|
52713
52901
|
setConfig("anvilUrl", anvilUrl);
|
|
52714
52902
|
} else anvilUrl = resolveAnvilUrl();
|
|
52715
52903
|
const result = await runInteractiveLoginFlow(anvilUrl);
|
|
@@ -52719,7 +52907,7 @@ var __webpack_exports__ = {};
|
|
|
52719
52907
|
process.exit(1);
|
|
52720
52908
|
}
|
|
52721
52909
|
});
|
|
52722
|
-
loginCommand.addHelpText("after", "\n" + chalk_source.bold("Examples:") + "\n anvil login Log in to default Anvil server\n anvil login anvil.works Log in to anvil.works\n anvil login localhost Log in to local Anvil server\n");
|
|
52910
|
+
loginCommand.addHelpText("after", "\n" + chalk_source.bold("Examples:") + "\n anvil login Log in to default Anvil server\n anvil login anvil.works Log in to anvil.works\n anvil login --url localhost Log in to localhost\n anvil login localhost Log in to local Anvil server\n");
|
|
52723
52911
|
}
|
|
52724
52912
|
function getTotalLoggedInAccounts(urls) {
|
|
52725
52913
|
return urls.reduce((total, url)=>total + auth_getAccountsForUrl(url).length, 0);
|