@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.
Files changed (3) hide show
  1. package/README.md +14 -0
  2. package/dist/cli.js +265 -77
  3. 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 true;
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: ${errors_getErrorMessage(e)}`);
51100
- return false;
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) return false;
51257
- return await recreateSessionAndValidate(options);
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
- async function waitForOAuthWithPrompt(authUrl, oauthPromise) {
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
- const enterPromise = new Promise((resolve)=>{
51861
- rl.question(chalk_source.gray("Press Enter to open browser (or visit URL manually)... "), ()=>resolve("enter"));
51862
- });
51863
- const result = await Promise.race([
51864
- enterPromise,
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
- await node_modules_open(authUrl.toString());
51872
- logger_logger.info(chalk_source.cyan("Waiting for login to complete in browser...\n"));
51873
- return await oauthPromise;
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 runInteractiveLoginFlow(anvilUrl) {
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", "anvil-sync");
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
- logger_logger.info(chalk_source.blue("Link to your Anvil account to continue."));
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.gray(` ${authUrl}`));
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
- const { code, error, recvState } = await waitForOAuthWithPrompt(authUrl, codePromise);
51931
- if (recvState !== state) throw new Error("Invalid state received from OAuth callback");
51932
- if (error) throw new Error(`Error received from OAuth callback: ${error}`);
51933
- const tokenResponse = await fetch(`${anvilUrl}/oauth/token`, {
51934
- method: "POST",
51935
- headers: {
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
- if (!tokenResponse.ok) {
51947
- const errorText = await tokenResponse.text();
51948
- throw new Error(`Failed to exchange authorization code for token. ${errorText}`);
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
- if (anvilUrl) {
52712
- anvilUrl = normalizeAnvilUrl(anvilUrl.trim());
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anvil-works/anvil-cli",
3
- "version": "0.5.10",
3
+ "version": "0.5.12",
4
4
  "description": "CLI tool for developing Anvil apps locally",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",