@anvil-works/anvil-cli 0.4.3 → 0.5.1

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/dist/cli.js CHANGED
@@ -42459,8 +42459,31 @@ var __webpack_exports__ = {};
42459
42459
  }
42460
42460
  const configDefaults = {
42461
42461
  devMode: false,
42462
- verbose: false
42462
+ verbose: false,
42463
+ preferredEditor: ""
42464
+ };
42465
+ const preferredEditors = [
42466
+ "vscode",
42467
+ "cursor",
42468
+ "nvim",
42469
+ "zed",
42470
+ "windsurf",
42471
+ "pycharm"
42472
+ ];
42473
+ const preferredEditorCommandByValue = {
42474
+ vscode: "code",
42475
+ cursor: "cursor",
42476
+ nvim: "nvim",
42477
+ zed: "zed",
42478
+ windsurf: "windsurf",
42479
+ pycharm: "charm"
42463
42480
  };
42481
+ const settableConfigKeys = [
42482
+ "anvilUrl",
42483
+ "devMode",
42484
+ "verbose",
42485
+ "preferredEditor"
42486
+ ];
42464
42487
  function isTestMode() {
42465
42488
  return "test" === process.env.NODE_ENV || !!process.env.RSTEST;
42466
42489
  }
@@ -42532,6 +42555,23 @@ var __webpack_exports__ = {};
42532
42555
  }
42533
42556
  return value;
42534
42557
  }
42558
+ function parseConfigSetValue(key, value) {
42559
+ if (!settableConfigKeys.includes(key)) throw new Error(`✖ Error: unknown config key '${key}'. Allowed keys: ${settableConfigKeys.join(", ")}`);
42560
+ if ("preferredEditor" === key) {
42561
+ let normalized = value.trim().toLowerCase();
42562
+ if ("code" === normalized) normalized = "vscode";
42563
+ if (!preferredEditors.includes(normalized)) throw new Error(`✖ Error: preferredEditor must be one of: ${preferredEditors.join(", ")} (alias: code -> vscode)`);
42564
+ return normalized;
42565
+ }
42566
+ if ("anvilUrl" === key) return value.trim().replace(/\/+$/, "");
42567
+ return coerceConfigValue(key, value);
42568
+ }
42569
+ function getPreferredEditorCommand(preferredEditorValue) {
42570
+ const normalized = preferredEditorValue.trim().toLowerCase();
42571
+ if ("code" === normalized) return "code";
42572
+ if (preferredEditors.includes(normalized)) return preferredEditorCommandByValue[normalized];
42573
+ return preferredEditorValue;
42574
+ }
42535
42575
  function getAllConfig() {
42536
42576
  return config_config.store;
42537
42577
  }
@@ -47584,6 +47624,7 @@ var __webpack_exports__ = {};
47584
47624
  });
47585
47625
  this.ws.on("error", (error)=>{
47586
47626
  logger_logger.debug(`[WebSocket ${this.sessionId}]`, `Error: ${error.message}`);
47627
+ if (this.isClosing) return;
47587
47628
  if (error.message.includes("502")) logger_logger.error(chalk_source.red("WebSocket connection failed (502 Bad Gateway) - likely a temporary server issue"));
47588
47629
  else logger_logger.error(chalk_source.red(`WebSocket error: ${error.message}`));
47589
47630
  this.emit("error", {
@@ -47684,15 +47725,20 @@ var __webpack_exports__ = {};
47684
47725
  }
47685
47726
  teardownSocket() {
47686
47727
  this.stopHeartbeat();
47687
- if (!this.ws) return;
47728
+ const socket = this.ws;
47729
+ this.ws = null;
47730
+ if (!socket) return;
47688
47731
  try {
47689
47732
  logger_logger.debug(`[WebSocket ${this.sessionId}]`, "Closing WebSocket");
47690
- this.ws.removeAllListeners();
47691
- if (this.ws.readyState === ws_wrapper.OPEN || this.ws.readyState === ws_wrapper.CONNECTING) this.ws.terminate();
47733
+ if (socket.readyState === ws_wrapper.CONNECTING) {
47734
+ socket.once("error", ()=>{});
47735
+ socket.terminate();
47736
+ return;
47737
+ }
47738
+ if (socket.readyState === ws_wrapper.OPEN) return void socket.close();
47692
47739
  } catch (e) {
47693
47740
  logger_logger.debug(`[WebSocket ${this.sessionId}]`, "Error closing WebSocket (ignoring):", e);
47694
47741
  }
47695
- this.ws = null;
47696
47742
  }
47697
47743
  }
47698
47744
  async function detectRemoteChanges(gitService, oldCommitId, newCommitId) {
@@ -49956,6 +50002,121 @@ var __webpack_exports__ = {};
49956
50002
  session.hasUncommittedChanges = hasUncommittedChanges;
49957
50003
  return session;
49958
50004
  }
50005
+ function getUrlConfigKey(appId) {
50006
+ return `anvil.auth.${appId}.url`;
50007
+ }
50008
+ function getUsernameConfigKey(appId) {
50009
+ return `anvil.auth.${appId}.username`;
50010
+ }
50011
+ async function getLocalConfigValue(repoPath, key) {
50012
+ try {
50013
+ const value = (await esm_default(repoPath).raw([
50014
+ "config",
50015
+ "--local",
50016
+ "--get",
50017
+ key
50018
+ ])).trim();
50019
+ return value || void 0;
50020
+ } catch {
50021
+ return;
50022
+ }
50023
+ }
50024
+ async function getAppAuthBinding(repoPath, appId) {
50025
+ const [url, username] = await Promise.all([
50026
+ getLocalConfigValue(repoPath, getUrlConfigKey(appId)),
50027
+ getLocalConfigValue(repoPath, getUsernameConfigKey(appId))
50028
+ ]);
50029
+ return {
50030
+ url: url ? normalizeAnvilUrl(url) : void 0,
50031
+ username: username || void 0
50032
+ };
50033
+ }
50034
+ async function setAppAuthBinding(repoPath, appId, binding) {
50035
+ const git = esm_default(repoPath);
50036
+ if (binding.url) await git.raw([
50037
+ "config",
50038
+ "--local",
50039
+ getUrlConfigKey(appId),
50040
+ normalizeAnvilUrl(binding.url)
50041
+ ]);
50042
+ if (binding.username) await git.raw([
50043
+ "config",
50044
+ "--local",
50045
+ getUsernameConfigKey(appId),
50046
+ binding.username
50047
+ ]);
50048
+ }
50049
+ function getCleanGitRemoteUrl(appId, anvilUrl) {
50050
+ const normalized = normalizeAnvilUrl(anvilUrl);
50051
+ const url = new URL(normalized);
50052
+ return `${url.protocol}//${url.host}/git/${appId}.git`;
50053
+ }
50054
+ async function configureCredentialHelperForUrl(repoPath, anvilUrl) {
50055
+ const normalized = normalizeAnvilUrl(anvilUrl);
50056
+ const url = new URL(normalized);
50057
+ const scope = `${url.protocol}//${url.host}`;
50058
+ const git = esm_default(repoPath);
50059
+ try {
50060
+ await git.raw([
50061
+ "config",
50062
+ "--local",
50063
+ "--unset-all",
50064
+ `credential.${scope}.helper`
50065
+ ]);
50066
+ } catch {}
50067
+ await git.raw([
50068
+ "config",
50069
+ "--local",
50070
+ "--add",
50071
+ `credential.${scope}.helper`,
50072
+ ""
50073
+ ]);
50074
+ await git.raw([
50075
+ "config",
50076
+ "--local",
50077
+ "--add",
50078
+ `credential.${scope}.helper`,
50079
+ "!anvil git-credential"
50080
+ ]);
50081
+ await git.raw([
50082
+ "config",
50083
+ "--local",
50084
+ `credential.${scope}.useHttpPath`,
50085
+ "true"
50086
+ ]);
50087
+ await git.raw([
50088
+ "config",
50089
+ "--local",
50090
+ `credential.${scope}.username`,
50091
+ "git"
50092
+ ]);
50093
+ }
50094
+ async function hardenCheckoutGitAuth(options) {
50095
+ const { repoPath, appId, anvilUrl, username } = options;
50096
+ const remoteName = options.remoteName || "origin";
50097
+ const cleanRemoteUrl = getCleanGitRemoteUrl(appId, anvilUrl);
50098
+ const git = esm_default(repoPath);
50099
+ await git.raw([
50100
+ "remote",
50101
+ "set-url",
50102
+ remoteName,
50103
+ cleanRemoteUrl
50104
+ ]);
50105
+ await configureCredentialHelperForUrl(repoPath, anvilUrl);
50106
+ await setAppAuthBinding(repoPath, appId, {
50107
+ url: anvilUrl,
50108
+ username
50109
+ });
50110
+ return {
50111
+ cleanRemoteUrl
50112
+ };
50113
+ }
50114
+ function parseAppIdFromGitPath(pathValue) {
50115
+ if (!pathValue) return;
50116
+ const normalizedPath = pathValue.startsWith("/") ? pathValue : `/${pathValue}`;
50117
+ const match = normalizedPath.match(/\/git\/([A-Z0-9]+)\.git(?:$|\/)/);
50118
+ return match ? match[1] : void 0;
50119
+ }
49959
50120
  function decideFallbackUrl(explicitUrl, availableUrls = getAvailableAnvilUrls()) {
49960
50121
  if (explicitUrl) return {
49961
50122
  source: "explicit",
@@ -50546,14 +50707,24 @@ var __webpack_exports__ = {};
50546
50707
  let anvilUrl;
50547
50708
  let username = explicitUsername;
50548
50709
  let fallbackUrl;
50710
+ let shouldPersistUsernameBinding = false;
50549
50711
  if (explicitAppId) {
50550
50712
  finalAppId = explicitAppId;
50713
+ const binding = await getAppAuthBinding(repoPath, explicitAppId);
50714
+ if (binding.url && !explicitUrl) {
50715
+ anvilUrl = normalizeAnvilUrl(binding.url);
50716
+ logger_logger.verbose(chalk_source.cyan("Resolved URL from binding for app ID: ") + chalk_source.bold(anvilUrl));
50717
+ }
50718
+ if (binding.username && !explicitUsername) {
50719
+ username = binding.username;
50720
+ logger_logger.verbose(chalk_source.cyan("Resolved username from binding for app ID: ") + chalk_source.bold(username));
50721
+ }
50551
50722
  const remoteInfo = lookupRemoteInfoForAppId(explicitAppId, detectedFromAllRemotes);
50552
- if (remoteInfo.detectedUrl && !explicitUrl) {
50723
+ if (remoteInfo.detectedUrl && !explicitUrl && !anvilUrl) {
50553
50724
  anvilUrl = normalizeAnvilUrl(remoteInfo.detectedUrl);
50554
50725
  logger_logger.verbose(chalk_source.cyan("Resolved URL from remote for app ID: ") + chalk_source.bold(anvilUrl));
50555
50726
  }
50556
- if (remoteInfo.detectedUsername && !explicitUsername) {
50727
+ if (remoteInfo.detectedUsername && !explicitUsername && !username) {
50557
50728
  username = remoteInfo.detectedUsername;
50558
50729
  logger_logger.verbose(chalk_source.cyan("Resolved username from remote for app ID: ") + chalk_source.bold(username));
50559
50730
  }
@@ -50608,6 +50779,16 @@ var __webpack_exports__ = {};
50608
50779
  if (selected.detectedUsername && !username) username = selected.detectedUsername;
50609
50780
  }
50610
50781
  }
50782
+ const binding = await getAppAuthBinding(repoPath, finalAppId);
50783
+ if (binding.url && !explicitUrl) {
50784
+ anvilUrl = normalizeAnvilUrl(binding.url);
50785
+ logger_logger.verbose(chalk_source.cyan("Using app binding URL: ") + chalk_source.bold(anvilUrl));
50786
+ }
50787
+ if (binding.username && !explicitUsername) {
50788
+ username = binding.username;
50789
+ logger_logger.verbose(chalk_source.cyan("Using app binding username: ") + chalk_source.bold(username));
50790
+ }
50791
+ shouldPersistUsernameBinding = !binding.username && !explicitUsername;
50611
50792
  if (explicitUrl) anvilUrl = normalizeAnvilUrl(explicitUrl);
50612
50793
  else if (!anvilUrl) if (fallbackUrl) anvilUrl = fallbackUrl;
50613
50794
  else {
@@ -50627,6 +50808,13 @@ var __webpack_exports__ = {};
50627
50808
  process.exit(0);
50628
50809
  }
50629
50810
  username = resolvedUsername;
50811
+ if (shouldPersistUsernameBinding && username && auth_getAccountsForUrl(anvilUrl).length > 1) {
50812
+ await setAppAuthBinding(repoPath, finalAppId, {
50813
+ url: anvilUrl,
50814
+ username
50815
+ });
50816
+ logger_logger.verbose(chalk_source.cyan("Saved app account binding for future non-interactive use."));
50817
+ }
50630
50818
  if (username) logger_logger.verbose(chalk_source.cyan("Using account: ") + chalk_source.bold(username));
50631
50819
  if (!hasTokensForUrl(anvilUrl, username)) {
50632
50820
  if (username) logger_logger.error(`Not logged in to ${anvilUrl} as ${username}`);
@@ -50685,7 +50873,6 @@ var __webpack_exports__ = {};
50685
50873
  });
50686
50874
  return watchCommand;
50687
50875
  }
50688
- var external_http_ = __webpack_require__("http");
50689
50876
  const external_node_url_namespaceObject = require("node:url");
50690
50877
  var external_node_child_process_ = __webpack_require__("node:child_process");
50691
50878
  let isDockerCached;
@@ -51148,6 +51335,7 @@ var __webpack_exports__ = {};
51148
51335
  defineLazyProperty(open_apps, 'browser', ()=>'browser');
51149
51336
  defineLazyProperty(open_apps, 'browserPrivate', ()=>'browserPrivate');
51150
51337
  const node_modules_open = open_open;
51338
+ var external_http_ = __webpack_require__("http");
51151
51339
  const successPage = `<!DOCTYPE html>
51152
51340
  <html lang="en">
51153
51341
  <head>
@@ -51305,6 +51493,9 @@ var __webpack_exports__ = {};
51305
51493
  </body>
51306
51494
  </html>`;
51307
51495
  }
51496
+ function oauth_login_base64url(buf) {
51497
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
51498
+ }
51308
51499
  async function waitForOAuthWithPrompt(authUrl, oauthPromise) {
51309
51500
  const rl = external_readline_namespaceObject.createInterface({
51310
51501
  input: process.stdin,
@@ -51329,8 +51520,382 @@ var __webpack_exports__ = {};
51329
51520
  process.stdout.write("\r" + " ".repeat(60) + "\r");
51330
51521
  return result.oauth;
51331
51522
  }
51332
- function login_base64url(buf) {
51333
- return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
51523
+ async function runInteractiveLoginFlow(anvilUrl) {
51524
+ const codeVerifier = external_crypto_.randomBytes(48).toString("hex");
51525
+ const codeChallenge = oauth_login_base64url(external_crypto_.createHash("sha256").update(codeVerifier, "ascii").digest());
51526
+ const state = external_crypto_.randomBytes(16).toString("hex");
51527
+ const server = external_http_.createServer();
51528
+ await new Promise((resolve)=>server.listen(0, "127.0.0.1", resolve));
51529
+ const address = server.address();
51530
+ if (!address || "string" == typeof address) throw new Error("No address");
51531
+ const port = address.port;
51532
+ const redirectUri = `http://127.0.0.1:${port}/oauth-callback`;
51533
+ const SCOPES = "apps:read apps:write user:read";
51534
+ const authUrl = new URL(`${anvilUrl}/oauth/authorize`);
51535
+ authUrl.searchParams.set("response_type", "code");
51536
+ authUrl.searchParams.set("client_id", "anvil-sync");
51537
+ authUrl.searchParams.set("redirect_uri", redirectUri);
51538
+ authUrl.searchParams.set("scope", SCOPES);
51539
+ authUrl.searchParams.set("state", state);
51540
+ authUrl.searchParams.set("code_challenge", codeChallenge);
51541
+ authUrl.searchParams.set("code_challenge_method", "S256");
51542
+ const codePromise = new Promise((resolve, reject)=>{
51543
+ server.on("request", (req, res)=>{
51544
+ if (!req.url) return;
51545
+ const url = new URL(req.url, `http://127.0.0.1:${port}`);
51546
+ if ("/oauth-callback" !== url.pathname) return;
51547
+ const code = url.searchParams.get("code") || void 0;
51548
+ const error = url.searchParams.get("error") || void 0;
51549
+ const recvState = url.searchParams.get("state");
51550
+ if (!recvState || !code && !error) {
51551
+ res.statusCode = 400;
51552
+ res.end("Missing code, error or state");
51553
+ reject(new Error("Missing code/state/error"));
51554
+ server.close();
51555
+ return;
51556
+ }
51557
+ res.statusCode = 200;
51558
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
51559
+ if (code) res.end(successPage);
51560
+ else res.end(errorPage(error || "unknown"));
51561
+ resolve({
51562
+ code,
51563
+ error,
51564
+ recvState
51565
+ });
51566
+ server.closeAllConnections();
51567
+ server.close();
51568
+ });
51569
+ });
51570
+ logger_logger.info(chalk_source.blue("Link to your Anvil account to continue."));
51571
+ console.log();
51572
+ logger_logger.info(chalk_source.gray(` ${authUrl}`));
51573
+ console.log();
51574
+ const { code, error, recvState } = await waitForOAuthWithPrompt(authUrl, codePromise);
51575
+ if (recvState !== state) throw new Error("Invalid state received from OAuth callback");
51576
+ if (error) throw new Error(`Error received from OAuth callback: ${error}`);
51577
+ const tokenResponse = await fetch(`${anvilUrl}/oauth/token`, {
51578
+ method: "POST",
51579
+ headers: {
51580
+ "Content-Type": "application/x-www-form-urlencoded"
51581
+ },
51582
+ body: new URLSearchParams({
51583
+ grant_type: "authorization_code",
51584
+ code: code,
51585
+ redirect_uri: redirectUri,
51586
+ client_id: "anvil-sync",
51587
+ code_verifier: codeVerifier
51588
+ })
51589
+ });
51590
+ if (!tokenResponse.ok) {
51591
+ const errorText = await tokenResponse.text();
51592
+ throw new Error(`Failed to exchange authorization code for token. ${errorText}`);
51593
+ }
51594
+ const tokenData = await tokenResponse.json();
51595
+ return login(anvilUrl, {
51596
+ access_token: tokenData.access_token,
51597
+ refresh_token: tokenData.refresh_token,
51598
+ expires_in: tokenData.expires_in,
51599
+ scope: tokenData.scope
51600
+ });
51601
+ }
51602
+ const defaultCheckoutDeps = {
51603
+ getValidAuthToken: auth_getValidAuthToken,
51604
+ validateAppId: validateAppId,
51605
+ runInteractiveLoginFlow: runInteractiveLoginFlow,
51606
+ clone: async (repoUrl, destinationPath, options)=>{
51607
+ const cloneArgs = [];
51608
+ if (options?.branch) cloneArgs.push("--branch", options.branch);
51609
+ if ("number" == typeof options?.depth) cloneArgs.push("--depth", String(options.depth));
51610
+ if (options?.singleBranch) cloneArgs.push("--single-branch");
51611
+ if (options?.origin) cloneArgs.push("--origin", options.origin);
51612
+ if (options?.quiet) cloneArgs.push("--quiet");
51613
+ if (options?.verbose) cloneArgs.push("--verbose");
51614
+ await esm_default().clone(repoUrl, destinationPath, cloneArgs);
51615
+ },
51616
+ hardenCheckoutGitAuth: hardenCheckoutGitAuth
51617
+ };
51618
+ function parseCheckoutInput(input) {
51619
+ const trimmed = input.trim();
51620
+ if (!trimmed) throw new Error("Input is required.");
51621
+ if (/^[A-Z0-9]+$/.test(trimmed)) return {
51622
+ appId: trimmed
51623
+ };
51624
+ const asUrl = /^https?:\/\//.test(trimmed) ? trimmed : normalizeAnvilUrl(trimmed);
51625
+ let parsed;
51626
+ try {
51627
+ parsed = new URL(asUrl);
51628
+ } catch {
51629
+ throw new Error("Input must be an editor URL, /git URL, or bare app ID.");
51630
+ }
51631
+ const gitMatch = parsed.pathname.match(/\/git\/([A-Z0-9]+)\.git(?:$|\/)/);
51632
+ if (gitMatch) return {
51633
+ appId: gitMatch[1],
51634
+ detectedUrl: `${parsed.protocol}//${parsed.host}`
51635
+ };
51636
+ const appsMatch = parsed.pathname.match(/\/apps\/([A-Z0-9]+)(?:\/|$)/);
51637
+ if (appsMatch) return {
51638
+ appId: appsMatch[1],
51639
+ detectedUrl: `${parsed.protocol}//${parsed.host}`
51640
+ };
51641
+ throw new Error("Could not extract app ID. Expected URL path containing /apps/<APPID> or /git/<APPID>.git.");
51642
+ }
51643
+ function sanitizeDirectoryName(name) {
51644
+ return name.trim().replace(/\s+/g, "_").replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
51645
+ }
51646
+ function formatPathForDisplay(destinationPath) {
51647
+ const relative = external_path_default().relative(process.cwd(), destinationPath);
51648
+ if ("" === relative) return ".";
51649
+ if (relative.startsWith("..")) return destinationPath;
51650
+ return relative.startsWith(".") ? relative : `./${relative}`;
51651
+ }
51652
+ function getDefaultDestinationDirectory(appId, appName) {
51653
+ if (appName) {
51654
+ const sanitized = sanitizeDirectoryName(appName);
51655
+ if (sanitized) return sanitized;
51656
+ }
51657
+ return appId;
51658
+ }
51659
+ async function resolveCheckoutUrl(explicitUrl, parsedUrl) {
51660
+ if (explicitUrl) return normalizeAnvilUrl(explicitUrl);
51661
+ if (parsedUrl) return normalizeAnvilUrl(parsedUrl);
51662
+ const decision = decideFallbackUrl(void 0);
51663
+ if ("available-multiple" !== decision.source) return decision.url;
51664
+ const choices = decision.urls.map((url)=>({
51665
+ name: url,
51666
+ value: url
51667
+ }));
51668
+ choices.push({
51669
+ name: "Cancel",
51670
+ value: null
51671
+ });
51672
+ return logger_logger.select("Multiple logged-in Anvil URLs found. Which one would you like to use?", choices, decision.urls[0]);
51673
+ }
51674
+ async function isPathInsideGitRepo(targetPath) {
51675
+ const resolved = external_path_default().resolve(targetPath);
51676
+ let current = resolved;
51677
+ while(!external_fs_.existsSync(current)){
51678
+ const parent = external_path_default().dirname(current);
51679
+ if (parent === current) return false;
51680
+ current = parent;
51681
+ }
51682
+ try {
51683
+ const output = (await esm_default(current).raw([
51684
+ "rev-parse",
51685
+ "--is-inside-work-tree"
51686
+ ])).trim();
51687
+ return "true" === output;
51688
+ } catch {
51689
+ return false;
51690
+ }
51691
+ }
51692
+ async function isDirectoryNonEmpty(destinationPath) {
51693
+ if (!external_fs_.existsSync(destinationPath)) return false;
51694
+ const stats = await external_fs_.promises.stat(destinationPath);
51695
+ if (!stats.isDirectory()) return true;
51696
+ const entries = await external_fs_.promises.readdir(destinationPath);
51697
+ return entries.length > 0;
51698
+ }
51699
+ async function validateCheckoutDestination(destinationPath, force = false) {
51700
+ const nonEmpty = await isDirectoryNonEmpty(destinationPath);
51701
+ if (nonEmpty && !force) throw new Error(`Destination '${destinationPath}' already exists and is not empty.`);
51702
+ const insideGit = await isPathInsideGitRepo(destinationPath);
51703
+ if (insideGit && !force) throw new Error(`Destination '${destinationPath}' is inside an existing Git repository.`);
51704
+ if (force && nonEmpty) logger_logger.warn(`--force set: destination '${destinationPath}' is non-empty and clone may still fail.`);
51705
+ if (force && insideGit) logger_logger.warn("--force set: cloning inside an existing Git repository.");
51706
+ }
51707
+ async function ensureCheckoutAuthToken(anvilUrl, username, deps = defaultCheckoutDeps) {
51708
+ try {
51709
+ return await deps.getValidAuthToken(anvilUrl, username);
51710
+ } catch (e) {
51711
+ const interactive = process.stdin.isTTY && process.stdout.isTTY;
51712
+ if (!interactive) throw errors_createAuthError.required(`Not logged in to ${anvilUrl}. Run 'anvil login ${anvilUrl}' and retry.`);
51713
+ const shouldLogin = await logger_logger.confirm(`Not logged in to ${anvilUrl}. Log in now?`, true);
51714
+ if (!shouldLogin) throw errors_createAuthError.required(`Not logged in to ${anvilUrl}. Run 'anvil login ${anvilUrl}' and retry.`);
51715
+ await deps.runInteractiveLoginFlow(anvilUrl);
51716
+ return deps.getValidAuthToken(anvilUrl, username);
51717
+ }
51718
+ }
51719
+ async function resolveCheckoutUsername(anvilUrl, explicitUsername, getAccounts = auth_getAccountsForUrl) {
51720
+ if (explicitUsername) return explicitUsername;
51721
+ const accounts = getAccounts(anvilUrl);
51722
+ if (0 === accounts.length) return;
51723
+ if (1 === accounts.length) {
51724
+ logger_logger.verbose(chalk_source.cyan("Auto-selected account: ") + chalk_source.bold(accounts[0]));
51725
+ return accounts[0];
51726
+ }
51727
+ const interactive = process.stdin.isTTY && process.stdout.isTTY;
51728
+ if (!interactive) throw new Error(`Multiple accounts found for ${anvilUrl}. Use --user <USERNAME> to choose one.`);
51729
+ const choices = accounts.map((acct)=>({
51730
+ name: acct,
51731
+ value: acct
51732
+ }));
51733
+ choices.push({
51734
+ name: "Cancel",
51735
+ value: null
51736
+ });
51737
+ return logger_logger.select(`Multiple accounts found for ${anvilUrl}. Which account should be used for checkout?`, choices, accounts[0]);
51738
+ }
51739
+ async function executeCheckout(options, deps = defaultCheckoutDeps) {
51740
+ const parsed = parseCheckoutInput(options.input);
51741
+ const anvilUrl = await resolveCheckoutUrl(options.url, parsed.detectedUrl);
51742
+ if (!anvilUrl) return void logger_logger.info("Checkout cancelled.");
51743
+ const resolvedUsername = await resolveCheckoutUsername(anvilUrl, options.user);
51744
+ if (null === resolvedUsername) return void logger_logger.info("Checkout cancelled.");
51745
+ const authToken = await ensureCheckoutAuthToken(anvilUrl, resolvedUsername, deps);
51746
+ let checkoutUsername = resolvedUsername;
51747
+ if (!checkoutUsername) {
51748
+ const inferredUsername = await resolveCheckoutUsername(anvilUrl, void 0);
51749
+ if (null === inferredUsername) return void logger_logger.info("Checkout cancelled.");
51750
+ if (!inferredUsername) throw new Error(`Could not determine account for ${anvilUrl}. Use --user <USERNAME> so checkout can bind repository credentials.`);
51751
+ checkoutUsername = inferredUsername;
51752
+ }
51753
+ const validation = await deps.validateAppId(parsed.appId, anvilUrl, checkoutUsername);
51754
+ if (!validation.valid) throw new Error(validation.error || `App '${parsed.appId}' is not accessible on ${anvilUrl}.`);
51755
+ const destinationDir = options.directory || getDefaultDestinationDirectory(parsed.appId, validation.app_name);
51756
+ const destinationPath = external_path_default().resolve(process.cwd(), destinationDir);
51757
+ const destinationDisplay = formatPathForDisplay(destinationPath);
51758
+ await validateCheckoutDestination(destinationPath, options.force);
51759
+ const cloneUrl = getGitPushUrl(parsed.appId, authToken, anvilUrl);
51760
+ logger_logger.progress("checkout", `Checking out app ${parsed.appId} from ${anvilUrl}`);
51761
+ logger_logger.info(chalk_source.gray(` Destination directory: ${destinationDisplay}`));
51762
+ await deps.clone(cloneUrl, destinationPath, {
51763
+ branch: options.branch,
51764
+ depth: options.depth,
51765
+ singleBranch: options.singleBranch,
51766
+ origin: options.origin,
51767
+ quiet: options.quiet,
51768
+ verbose: options.verbose
51769
+ });
51770
+ const remoteName = options.origin || "origin";
51771
+ try {
51772
+ await deps.hardenCheckoutGitAuth({
51773
+ repoPath: destinationPath,
51774
+ appId: parsed.appId,
51775
+ anvilUrl,
51776
+ username: checkoutUsername,
51777
+ remoteName
51778
+ });
51779
+ } catch (e) {
51780
+ throw new Error(`Checkout clone succeeded, but failed to configure repository credentials: ${errors_getErrorMessage(e)}. The repository exists at ${destinationDisplay}, but Git auth bridge setup is incomplete.`);
51781
+ }
51782
+ logger_logger.progressEnd("checkout", `Checked out ${parsed.appId} into ${destinationDisplay}`);
51783
+ const preferredEditor = String(getConfig("preferredEditor") || "").trim();
51784
+ const preferredEditorCommand = preferredEditor ? getPreferredEditorCommand(preferredEditor) : "";
51785
+ if (options.open) if (preferredEditorCommand) await openInPreferredEditor(preferredEditorCommand, destinationPath);
51786
+ else {
51787
+ await node_modules_open(destinationPath);
51788
+ logger_logger.info(chalk_source.gray(`Opened ${destinationDisplay}`));
51789
+ }
51790
+ if (preferredEditorCommand && !options.open) logger_logger.info(chalk_source.cyan(`Next: ${preferredEditorCommand} ${destinationDisplay}`));
51791
+ }
51792
+ async function openInPreferredEditor(editorCommand, destinationPath) {
51793
+ await new Promise((resolve, reject)=>{
51794
+ const child = (0, external_child_process_.spawn)(editorCommand, [
51795
+ destinationPath
51796
+ ], {
51797
+ shell: true,
51798
+ stdio: "inherit"
51799
+ });
51800
+ child.on("error", reject);
51801
+ child.on("exit", (code)=>{
51802
+ if (0 === code || null === code) resolve();
51803
+ else reject(new Error(`Editor command '${editorCommand}' exited with code ${code}`));
51804
+ });
51805
+ });
51806
+ }
51807
+ function registerCheckoutCommand(program) {
51808
+ const checkoutCommand = program.command("checkout <input> [directory]").description("Check out an Anvil app locally from editor URL, git URL, or app ID").alias("co").option("-O, --open", "Open destination after checkout").option("-b, --branch <BRANCH>", "Checkout a specific branch").option("--depth <N>", "Create a shallow clone with history truncated to N commits", (value)=>parseInt(value, 10)).option("--single-branch", "Clone only one branch").option("--origin <NAME>", "Use a custom remote name instead of origin").option("-q, --quiet", "Suppress git clone progress output").option("--verbose", "Enable verbose git clone output").option("-u, --url <ANVIL_URL>", "Specify Anvil server URL").option("-U, --user <USERNAME>", "Specify which user account to use").option("-f, --force", "Override safety checks for destination path").action(async (input, directory, options)=>{
51809
+ try {
51810
+ if ("number" == typeof options?.depth && (!Number.isFinite(options.depth) || options.depth <= 0)) throw new Error("--depth must be a positive integer");
51811
+ await executeCheckout({
51812
+ input,
51813
+ directory,
51814
+ open: options?.open,
51815
+ branch: options?.branch,
51816
+ depth: options?.depth,
51817
+ singleBranch: options?.singleBranch,
51818
+ origin: options?.origin,
51819
+ quiet: options?.quiet,
51820
+ verbose: options?.verbose,
51821
+ url: options?.url,
51822
+ user: options?.user,
51823
+ force: options?.force
51824
+ }, defaultCheckoutDeps);
51825
+ } catch (e) {
51826
+ logger_logger.error("Error: " + errors_getErrorMessage(e));
51827
+ process.exit(1);
51828
+ }
51829
+ });
51830
+ checkoutCommand.addHelpText("after", "\n" + chalk_source.bold("Examples:") + "\n anvil checkout http://localhost:3000/build/apps/APPID/code/assets/theme.css\n anvil checkout https://anvil.works/git/APPID.git\n anvil checkout APPID --url anvil.works\n anvil checkout APPID --branch master --depth 1 --single-branch\n anvil checkout APPID -O # open editor/file browser after checkout\n anvil checkout APPID my-local-folder --force\n");
51831
+ }
51832
+ const defaultDeps = {
51833
+ getValidAuthToken: auth_getValidAuthToken,
51834
+ getAccountsForUrl: auth_getAccountsForUrl,
51835
+ getAppAuthBinding: getAppAuthBinding,
51836
+ cwd: ()=>process.cwd()
51837
+ };
51838
+ function parseGitCredentialRequest(rawInput) {
51839
+ const out = {};
51840
+ const lines = rawInput.split(/\r?\n/);
51841
+ for (const line of lines){
51842
+ if (!line) continue;
51843
+ const eqIdx = line.indexOf("=");
51844
+ if (eqIdx <= 0) continue;
51845
+ const key = line.slice(0, eqIdx);
51846
+ const value = line.slice(eqIdx + 1);
51847
+ if ("protocol" === key || "host" === key || "path" === key || "username" === key) out[key] = value;
51848
+ }
51849
+ return out;
51850
+ }
51851
+ async function buildGitCredentialResponse(operation, request, deps = defaultDeps) {
51852
+ if ("get" !== operation) return null;
51853
+ if (!request.protocol || !request.host) return null;
51854
+ const appId = parseAppIdFromGitPath(request.path);
51855
+ if (!appId) return null;
51856
+ const requestUrl = normalizeAnvilUrl(`${request.protocol}://${request.host}`);
51857
+ const binding = await deps.getAppAuthBinding(deps.cwd(), appId);
51858
+ const anvilUrl = binding.url ? normalizeAnvilUrl(binding.url) : requestUrl;
51859
+ let username = binding.username;
51860
+ if (!username) {
51861
+ const accounts = deps.getAccountsForUrl(anvilUrl);
51862
+ if (1 === accounts.length) username = accounts[0];
51863
+ else if (accounts.length > 1) throw new Error(`Multiple accounts found for ${anvilUrl}; bind one with anvil checkout --user.`);
51864
+ else throw new Error(`No account found for ${anvilUrl}. Run 'anvil login ${anvilUrl}'.`);
51865
+ }
51866
+ const token = await deps.getValidAuthToken(anvilUrl, username);
51867
+ return {
51868
+ username: "git",
51869
+ password: token
51870
+ };
51871
+ }
51872
+ async function readStdin() {
51873
+ const chunks = [];
51874
+ for await (const chunk of process.stdin)chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
51875
+ return Buffer.concat(chunks).toString("utf8");
51876
+ }
51877
+ function writeGitCredentialResponse(response) {
51878
+ if (!response) return;
51879
+ const lines = [];
51880
+ for (const [k, v] of Object.entries(response))lines.push(`${k}=${v}`);
51881
+ process.stdout.write(lines.join("\n") + "\n\n");
51882
+ }
51883
+ async function executeGitCredentialOperation(operation, deps = defaultDeps) {
51884
+ const raw = await readStdin();
51885
+ const request = parseGitCredentialRequest(raw);
51886
+ const response = await buildGitCredentialResponse(operation, request, deps);
51887
+ writeGitCredentialResponse(response);
51888
+ }
51889
+ function registerGitCredentialCommand(program) {
51890
+ program.command("git-credential <operation>", {
51891
+ hidden: true
51892
+ }).description("Internal helper command for Git HTTPS authentication").action(async (operation)=>{
51893
+ try {
51894
+ await executeGitCredentialOperation(operation);
51895
+ } catch {
51896
+ process.exit(1);
51897
+ }
51898
+ });
51334
51899
  }
51335
51900
  function registerLoginCommand(program) {
51336
51901
  const loginCommand = program.command("login [anvil-server-url]").description("Log in to Anvil using OAuth").alias("l").action(async (anvilUrl)=>{
@@ -51339,93 +51904,8 @@ var __webpack_exports__ = {};
51339
51904
  anvilUrl = normalizeAnvilUrl(anvilUrl.trim());
51340
51905
  setConfig("anvilUrl", anvilUrl);
51341
51906
  } else anvilUrl = resolveAnvilUrl();
51342
- const codeVerifier = external_crypto_.randomBytes(48).toString("hex");
51343
- const codeChallenge = login_base64url(external_crypto_.createHash("sha256").update(codeVerifier, "ascii").digest());
51344
- const state = external_crypto_.randomBytes(16).toString("hex");
51345
- const server = external_http_.createServer();
51346
- await new Promise((resolve)=>server.listen(0, "127.0.0.1", resolve));
51347
- const address = server.address();
51348
- if (!address || "string" == typeof address) throw new Error("No address");
51349
- const port = address.port;
51350
- const redirectUri = `http://127.0.0.1:${port}/oauth-callback`;
51351
- const SCOPES = "apps:read apps:write user:read";
51352
- const authUrl = new URL(`${anvilUrl}/oauth/authorize`);
51353
- authUrl.searchParams.set("response_type", "code");
51354
- authUrl.searchParams.set("client_id", "anvil-sync");
51355
- authUrl.searchParams.set("redirect_uri", redirectUri);
51356
- authUrl.searchParams.set("scope", SCOPES);
51357
- authUrl.searchParams.set("state", state);
51358
- authUrl.searchParams.set("code_challenge", codeChallenge);
51359
- authUrl.searchParams.set("code_challenge_method", "S256");
51360
- const codePromise = new Promise((resolve, reject)=>{
51361
- server.on("request", (req, res)=>{
51362
- if (!req.url) return;
51363
- const url = new URL(req.url, `http://127.0.0.1:${port}`);
51364
- if ("/oauth-callback" !== url.pathname) return;
51365
- const code = url.searchParams.get("code") || void 0;
51366
- const error = url.searchParams.get("error") || void 0;
51367
- const recvState = url.searchParams.get("state");
51368
- if (!recvState || !code && !error) {
51369
- res.statusCode = 400;
51370
- res.end("Missing code, error or state");
51371
- reject(new Error("Missing code/state/error"));
51372
- server.close();
51373
- return;
51374
- }
51375
- res.statusCode = 200;
51376
- res.setHeader("Content-Type", "text/html; charset=utf-8");
51377
- if (code) res.end(successPage);
51378
- else res.end(errorPage(error || "unknown"));
51379
- resolve({
51380
- code,
51381
- error,
51382
- recvState
51383
- });
51384
- server.closeAllConnections();
51385
- server.close();
51386
- });
51387
- });
51388
- logger_logger.info(chalk_source.blue("Link to your Anvil account to continue."));
51389
- console.log();
51390
- logger_logger.info(chalk_source.gray(` ${authUrl}`));
51391
- console.log();
51392
- const { code, error, recvState } = await waitForOAuthWithPrompt(authUrl, codePromise);
51393
- if (recvState !== state) {
51394
- logger_logger.error("Invalid state received from OAuth callback");
51395
- process.exit(1);
51396
- }
51397
- if (error) {
51398
- logger_logger.error(`Error received from OAuth callback: ${error}`);
51399
- process.exit(1);
51400
- }
51401
- const tokenResponse = await fetch(`${anvilUrl}/oauth/token`, {
51402
- method: "POST",
51403
- headers: {
51404
- "Content-Type": "application/x-www-form-urlencoded"
51405
- },
51406
- body: new URLSearchParams({
51407
- grant_type: "authorization_code",
51408
- code: code,
51409
- redirect_uri: redirectUri,
51410
- client_id: "anvil-sync",
51411
- code_verifier: codeVerifier
51412
- })
51413
- });
51414
- if (tokenResponse.ok) {
51415
- const tokenData = await tokenResponse.json();
51416
- const result = await login(anvilUrl, {
51417
- access_token: tokenData.access_token,
51418
- refresh_token: tokenData.refresh_token,
51419
- expires_in: tokenData.expires_in,
51420
- scope: tokenData.scope
51421
- });
51422
- logger_logger.success("Logged in as " + chalk_source.bold(result.email));
51423
- } else {
51424
- logger_logger.error("Failed to exchange authorization code for token.");
51425
- const errorText = await tokenResponse.text();
51426
- logger_logger.error(` Server response: ${errorText}`);
51427
- process.exit(1);
51428
- }
51907
+ const result = await runInteractiveLoginFlow(anvilUrl);
51908
+ logger_logger.success("Logged in as " + chalk_source.bold(result.email));
51429
51909
  } catch (e) {
51430
51910
  logger_logger.error("Error: " + e.message);
51431
51911
  process.exit(1);
@@ -51529,6 +52009,14 @@ var __webpack_exports__ = {};
51529
52009
  });
51530
52010
  logoutCommand.addHelpText("after", "\n" + chalk_source.bold("Examples:") + "\n anvil logout Logout from all accounts (or prompt if multiple)\n anvil logout anvil.works Logout from anvil.works\n anvil logout -u anvil.works Logout from anvil.works (using option)\n anvil logout -u anvil.works -U user@example.com Logout specific user\n");
51531
52011
  }
52012
+ const defaultConfigResetDeps = {
52013
+ logout: logout,
52014
+ resetConfig: resetConfig
52015
+ };
52016
+ async function performConfigReset(deps = defaultConfigResetDeps) {
52017
+ await deps.logout();
52018
+ deps.resetConfig();
52019
+ }
51532
52020
  function registerConfigCommand(program) {
51533
52021
  const configCommand = program.command("config").description("Manage configuration").alias("c");
51534
52022
  configCommand.command("get <key>").description("Get a configuration value").action(async (key)=>{
@@ -51547,8 +52035,7 @@ var __webpack_exports__ = {};
51547
52035
  });
51548
52036
  configCommand.command("set <key> <value>").description("Set a configuration value").action(async (key, value)=>{
51549
52037
  try {
51550
- let typedValue = coerceConfigValue(key, value);
51551
- if ("anvilUrl" === key && "string" == typeof typedValue) typedValue = typedValue.trim().replace(/\/+$/, "");
52038
+ const typedValue = parseConfigSetValue(key, value);
51552
52039
  setConfig(key, typedValue);
51553
52040
  logger_logger.success(`Set ${key} = ${typedValue}`);
51554
52041
  } catch (error) {
@@ -51608,7 +52095,7 @@ var __webpack_exports__ = {};
51608
52095
  });
51609
52096
  configCommand.command("reset").description("Reset configuration to defaults").action(async ()=>{
51610
52097
  try {
51611
- resetConfig();
52098
+ await performConfigReset();
51612
52099
  logger_logger.success("Configuration reset to defaults.");
51613
52100
  } catch (e) {
51614
52101
  logger_logger.error("Error: " + e.message);
@@ -51624,6 +52111,136 @@ var __webpack_exports__ = {};
51624
52111
  console.log();
51625
52112
  });
51626
52113
  }
52114
+ const onboard_defaultDeps = {
52115
+ getLatestVersion: getLatestVersion,
52116
+ getAccountsForUrl: auth_getAccountsForUrl,
52117
+ runInteractiveLoginFlow: runInteractiveLoginFlow,
52118
+ executeCheckout: executeCheckout
52119
+ };
52120
+ const DEFAULT_ANVIL_SERVER_URL_PROMPT = "Default Anvil server URL";
52121
+ async function getOnboardVersionStatus(currentVersion, latestVersionGetter = getLatestVersion) {
52122
+ const latestVersion = await latestVersionGetter();
52123
+ if (!latestVersion) return {
52124
+ currentVersion,
52125
+ latestVersion: null,
52126
+ isOutdated: null
52127
+ };
52128
+ if (!semver_default().valid(currentVersion) || !semver_default().valid(latestVersion)) return {
52129
+ currentVersion,
52130
+ latestVersion,
52131
+ isOutdated: null
52132
+ };
52133
+ return {
52134
+ currentVersion,
52135
+ latestVersion,
52136
+ isOutdated: semver_default().lt(currentVersion, latestVersion)
52137
+ };
52138
+ }
52139
+ function formatLoggedInAccounts(accounts) {
52140
+ if (0 === accounts.length) return "no account";
52141
+ if (1 === accounts.length) return accounts[0];
52142
+ return `${accounts.length} accounts`;
52143
+ }
52144
+ function isInteractiveSession() {
52145
+ return !!(process.stdin.isTTY && process.stdout.isTTY);
52146
+ }
52147
+ async function runOnboardFlow(version, deps = onboard_defaultDeps) {
52148
+ if (!isInteractiveSession()) throw new Error("`anvil onboard` is interactive and requires a TTY terminal.");
52149
+ console.log();
52150
+ logger_logger.info(chalk_source.cyan.bold("Anvil Onboarding"));
52151
+ console.log();
52152
+ logger_logger.progress("onboard-version", "Checking CLI version...");
52153
+ const versionStatus = await getOnboardVersionStatus(version, deps.getLatestVersion);
52154
+ logger_logger.progressEnd("onboard-version");
52155
+ logger_logger.info(chalk_source.cyan("CLI version: ") + chalk_source.bold(versionStatus.currentVersion));
52156
+ if (versionStatus.latestVersion) {
52157
+ logger_logger.info(chalk_source.cyan("Latest version: ") + chalk_source.bold(versionStatus.latestVersion));
52158
+ if (versionStatus.isOutdated) {
52159
+ logger_logger.warn("Your anvil-cli is out of date.");
52160
+ logger_logger.info(chalk_source.gray("Run `anvil update` to see update commands."));
52161
+ const continueOnOldVersion = await logger_logger.confirm("Continue onboarding anyway?", true);
52162
+ if (!continueOnOldVersion) return void logger_logger.info("Onboarding cancelled.");
52163
+ }
52164
+ } else logger_logger.warn("Could not determine latest published version.");
52165
+ console.log();
52166
+ const currentUrl = resolveAnvilUrl();
52167
+ logger_logger.info(chalk_source.gray("Leave this blank unless you're using a dedicated enterprise/on-prem Anvil installation."));
52168
+ const urlAnswer = await logger_logger.prompt([
52169
+ {
52170
+ type: "input",
52171
+ name: "anvilUrl",
52172
+ message: DEFAULT_ANVIL_SERVER_URL_PROMPT,
52173
+ default: currentUrl
52174
+ }
52175
+ ]);
52176
+ const configuredAnvilUrl = parseConfigSetValue("anvilUrl", urlAnswer.anvilUrl);
52177
+ setConfig("anvilUrl", configuredAnvilUrl);
52178
+ logger_logger.success(`Set default Anvil server URL (anvilUrl) = ${configuredAnvilUrl}`);
52179
+ const currentEditor = String(getConfig("preferredEditor") || "").trim().toLowerCase();
52180
+ const editorChoices = [
52181
+ {
52182
+ name: "None",
52183
+ value: ""
52184
+ },
52185
+ ...preferredEditors.map((editor)=>({
52186
+ name: editor,
52187
+ value: editor
52188
+ }))
52189
+ ];
52190
+ const selectedEditor = await logger_logger.select("Preferred editor", editorChoices, editorChoices.some((choice)=>choice.value === currentEditor) ? currentEditor : "");
52191
+ setConfig("preferredEditor", selectedEditor);
52192
+ if (selectedEditor) logger_logger.success(`Set preferredEditor = ${selectedEditor}`);
52193
+ else logger_logger.info("Left preferredEditor unset.");
52194
+ const currentVerbose = !!getConfig("verbose");
52195
+ const enableVerbose = await logger_logger.confirm("Enable verbose logging?", currentVerbose);
52196
+ setConfig("verbose", enableVerbose);
52197
+ logger_logger.success(`Set verbose = ${enableVerbose}`);
52198
+ console.log();
52199
+ const existingAccounts = deps.getAccountsForUrl(configuredAnvilUrl);
52200
+ if (existingAccounts.length > 0) logger_logger.success(`Already logged in to ${configuredAnvilUrl} as ${formatLoggedInAccounts(existingAccounts)}.`);
52201
+ else {
52202
+ const shouldLogin = await logger_logger.confirm(`You're not logged in to ${configuredAnvilUrl}. Log in now?`, true);
52203
+ if (shouldLogin) {
52204
+ const loginResult = await deps.runInteractiveLoginFlow(configuredAnvilUrl);
52205
+ logger_logger.success("Logged in as " + chalk_source.bold(loginResult.email));
52206
+ } else logger_logger.warn("Skipping login.");
52207
+ }
52208
+ console.log();
52209
+ const shouldCheckout = await logger_logger.confirm("Check out an app now?", true);
52210
+ if (shouldCheckout) {
52211
+ const checkoutAnswer = await logger_logger.prompt([
52212
+ {
52213
+ type: "input",
52214
+ name: "input",
52215
+ message: "App ID or app URL to checkout"
52216
+ }
52217
+ ]);
52218
+ const checkoutInput = checkoutAnswer.input.trim();
52219
+ if (checkoutInput) {
52220
+ const preferredEditorCommand = getPreferredEditorCommand(selectedEditor);
52221
+ let openAfterCheckout = false;
52222
+ if (preferredEditorCommand) openAfterCheckout = await logger_logger.confirm(`Open checked out app in ${preferredEditorCommand} after checkout?`, true);
52223
+ else logger_logger.info("No preferred editor is configured, so open-in-editor is skipped.");
52224
+ await deps.executeCheckout({
52225
+ input: checkoutInput,
52226
+ url: configuredAnvilUrl,
52227
+ open: openAfterCheckout
52228
+ });
52229
+ } else logger_logger.info("No app provided; skipping checkout.");
52230
+ } else logger_logger.info("Skipping checkout.");
52231
+ console.log();
52232
+ logger_logger.success("Onboarding complete.");
52233
+ }
52234
+ function registerOnboardCommand(program, version) {
52235
+ program.command("onboard").description("Guided setup for configuration, login, and optional app checkout").action(async ()=>{
52236
+ try {
52237
+ await runOnboardFlow(version, onboard_defaultDeps);
52238
+ } catch (e) {
52239
+ logger_logger.error("Error: " + errors_getErrorMessage(e));
52240
+ process.exit(1);
52241
+ }
52242
+ });
52243
+ }
51627
52244
  const packageJson = JSON.parse((0, external_fs_.readFileSync)((0, external_path_namespaceObject.join)(__dirname, "../package.json"), "utf-8"));
51628
52245
  const VERSION = packageJson.version;
51629
52246
  setLogger(new CLILogger({
@@ -51670,9 +52287,12 @@ var __webpack_exports__ = {};
51670
52287
  console.log();
51671
52288
  console.log(chalk_source.cyan("To update, run one of the following commands:"));
51672
52289
  console.log();
51673
- console.log(chalk_source.gray(" npm: ") + chalk_source.white("npm update -g @anvil-works/anvil-cli"));
51674
- console.log(chalk_source.gray(" pnpm: ") + chalk_source.white("pnpm update -g @anvil-works/anvil-cli"));
51675
- console.log(chalk_source.gray(" yarn: ") + chalk_source.white("yarn global upgrade @anvil-works/anvil-cli"));
52290
+ if ("win32" === process.platform) {
52291
+ console.log(chalk_source.gray(" PowerShell: ") + chalk_source.white("irm https://anvil.works/install/anvil-cli/install.ps1 | iex"));
52292
+ console.log(chalk_source.gray(" CMD: ") + chalk_source.white("curl -fsSL https://anvil.works/install/anvil-cli/install.cmd -o install.cmd && install.cmd && del install.cmd"));
52293
+ } else console.log(chalk_source.gray(" macOS/Linux: ") + chalk_source.white("curl -fsSL https://anvil.works/install/anvil-cli/install.sh | sh"));
52294
+ console.log(chalk_source.gray(" npm: ") + chalk_source.white("npm update -g @anvil-works/anvil-cli"));
52295
+ console.log(chalk_source.gray(" pnpm: ") + chalk_source.white("pnpm update -g @anvil-works/anvil-cli"));
51676
52296
  console.log();
51677
52297
  } catch (e) {
51678
52298
  logger_logger.error("Error: " + e.message);
@@ -51687,14 +52307,17 @@ var __webpack_exports__ = {};
51687
52307
  });
51688
52308
  if (!opts.json) {
51689
52309
  const commandName = actionCommand?.name();
51690
- if ("update" !== commandName) checkVersionAndWarn();
52310
+ if ("update" !== commandName && "git-credential" !== commandName) checkVersionAndWarn();
51691
52311
  }
51692
52312
  });
51693
52313
  const cli_watchCommand = registerWatchCommand(cli_program);
52314
+ registerCheckoutCommand(cli_program);
52315
+ registerGitCredentialCommand(cli_program);
51694
52316
  registerLoginCommand(cli_program);
51695
52317
  registerLogoutCommand(cli_program);
51696
52318
  registerConfigCommand(cli_program);
51697
52319
  registerVersionCommand(cli_program, VERSION);
52320
+ registerOnboardCommand(cli_program, VERSION);
51698
52321
  cli_program.command("update").description("Update anvil to the latest version").alias("u").action(async ()=>{
51699
52322
  await handleUpdateCommand();
51700
52323
  });