@anvil-works/anvil-cli 0.3.10 → 0.3.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/dist/cli.js CHANGED
@@ -44259,7 +44259,10 @@ var __webpack_exports__ = {};
44259
44259
  const handlers = this.listeners.get(event);
44260
44260
  if (handlers) handlers.forEach((handler)=>{
44261
44261
  try {
44262
- handler(data);
44262
+ const result = handler(data);
44263
+ if (result && "function" == typeof result.catch) result.catch((error)=>{
44264
+ this.onError(String(event), error);
44265
+ });
44263
44266
  } catch (error) {
44264
44267
  this.onError(String(event), error);
44265
44268
  }
@@ -44334,7 +44337,7 @@ var __webpack_exports__ = {};
44334
44337
  files
44335
44338
  })
44336
44339
  };
44337
- const createGitError = {
44340
+ const errors_createGitError = {
44338
44341
  notInitialized: (path)=>({
44339
44342
  type: "git_not_initialized",
44340
44343
  path
@@ -44520,7 +44523,7 @@ var __webpack_exports__ = {};
44520
44523
  if (!external_path_default().isAbsolute(gitDir)) return external_path_default().resolve(this.repoPath, gitDir);
44521
44524
  return gitDir;
44522
44525
  } catch (e) {
44523
- throw createGitError.commandFailed("rev-parse --git-dir", e.message);
44526
+ throw errors_createGitError.commandFailed("rev-parse --git-dir", e.message);
44524
44527
  }
44525
44528
  }
44526
44529
  async getCurrentBranch() {
@@ -44543,7 +44546,7 @@ var __webpack_exports__ = {};
44543
44546
  ])).trim();
44544
44547
  return commitId;
44545
44548
  } catch (e) {
44546
- throw createGitError.commandFailed("revparse", e.message);
44549
+ throw errors_createGitError.commandFailed("revparse", e.message);
44547
44550
  }
44548
44551
  }
44549
44552
  async getCommitInfo() {
@@ -44562,7 +44565,7 @@ var __webpack_exports__ = {};
44562
44565
  message
44563
44566
  };
44564
44567
  } catch (e) {
44565
- throw createGitError.commandFailed("log", e.message);
44568
+ throw errors_createGitError.commandFailed("log", e.message);
44566
44569
  }
44567
44570
  }
44568
44571
  async getStatus() {
@@ -44582,7 +44585,7 @@ var __webpack_exports__ = {};
44582
44585
  renamed: status.renamed || []
44583
44586
  };
44584
44587
  } catch (e) {
44585
- throw createGitError.commandFailed("status", e.message);
44588
+ throw errors_createGitError.commandFailed("status", e.message);
44586
44589
  }
44587
44590
  }
44588
44591
  async hasUncommittedChanges() {
@@ -44596,7 +44599,7 @@ var __webpack_exports__ = {};
44596
44599
  refSpec
44597
44600
  ]);
44598
44601
  } catch (e) {
44599
- throw createGitError.fetchFailed(e.message);
44602
+ throw errors_createGitError.fetchFailed(e.message);
44600
44603
  }
44601
44604
  }
44602
44605
  async reset(ref, mode = "mixed") {
@@ -44606,21 +44609,45 @@ var __webpack_exports__ = {};
44606
44609
  ref
44607
44610
  ]);
44608
44611
  } catch (e) {
44609
- throw createGitError.commandFailed("reset", e.message);
44612
+ throw errors_createGitError.commandFailed("reset", e.message);
44610
44613
  }
44611
44614
  }
44612
44615
  async checkout(paths) {
44613
44616
  try {
44614
44617
  await this.git.checkout(paths);
44615
44618
  } catch (e) {
44616
- throw createGitError.commandFailed("checkout", e.message);
44619
+ throw errors_createGitError.commandFailed("checkout", e.message);
44620
+ }
44621
+ }
44622
+ async stash(message) {
44623
+ try {
44624
+ const args = [
44625
+ "stash",
44626
+ "push",
44627
+ "--include-untracked"
44628
+ ];
44629
+ if (message) args.push("-m", message);
44630
+ const result = await this.git.raw(args);
44631
+ return !result.includes("No local changes to save");
44632
+ } catch (e) {
44633
+ throw errors_createGitError.commandFailed("stash", e.message);
44634
+ }
44635
+ }
44636
+ async stashPop() {
44637
+ try {
44638
+ await this.git.raw([
44639
+ "stash",
44640
+ "pop"
44641
+ ]);
44642
+ } catch (e) {
44643
+ throw errors_createGitError.commandFailed("stash pop", e.message);
44617
44644
  }
44618
44645
  }
44619
44646
  async clean(files) {
44620
44647
  try {
44621
44648
  await this.git.clean(CleanOptions.FORCE, files);
44622
44649
  } catch (e) {
44623
- throw createGitError.commandFailed("clean", e.message);
44650
+ throw errors_createGitError.commandFailed("clean", e.message);
44624
44651
  }
44625
44652
  }
44626
44653
  async diffNames(fromCommit, toCommit) {
@@ -44633,7 +44660,7 @@ var __webpack_exports__ = {};
44633
44660
  const files = diffResult.split("\n").filter((f)=>f.trim());
44634
44661
  return files;
44635
44662
  } catch (e) {
44636
- throw createGitError.commandFailed("diff", e.message);
44663
+ throw errors_createGitError.commandFailed("diff", e.message);
44637
44664
  }
44638
44665
  }
44639
44666
  async getAheadBehind(localRef, remoteRef) {
@@ -44650,7 +44677,7 @@ var __webpack_exports__ = {};
44650
44677
  behind
44651
44678
  };
44652
44679
  } catch (e) {
44653
- throw createGitError.commandFailed("rev-list", e.message);
44680
+ throw errors_createGitError.commandFailed("rev-list", e.message);
44654
44681
  }
44655
44682
  }
44656
44683
  async show(refPath) {
@@ -44660,7 +44687,44 @@ var __webpack_exports__ = {};
44660
44687
  ]);
44661
44688
  return content;
44662
44689
  } catch (e) {
44663
- throw createGitError.commandFailed("show", e.message);
44690
+ throw errors_createGitError.commandFailed("show", e.message);
44691
+ }
44692
+ }
44693
+ async push(url, refSpec, force = false) {
44694
+ try {
44695
+ const args = force ? [
44696
+ "push",
44697
+ "--force",
44698
+ url,
44699
+ refSpec
44700
+ ] : [
44701
+ "push",
44702
+ url,
44703
+ refSpec
44704
+ ];
44705
+ await this.git.raw(args);
44706
+ } catch (e) {
44707
+ throw errors_createGitError.commandFailed("push", e.message);
44708
+ }
44709
+ }
44710
+ async rebase(onto) {
44711
+ try {
44712
+ await this.git.raw([
44713
+ "rebase",
44714
+ onto
44715
+ ]);
44716
+ } catch (e) {
44717
+ throw errors_createGitError.commandFailed("rebase", e.message);
44718
+ }
44719
+ }
44720
+ async rebaseAbort() {
44721
+ try {
44722
+ await this.git.raw([
44723
+ "rebase",
44724
+ "--abort"
44725
+ ]);
44726
+ } catch (e) {
44727
+ throw errors_createGitError.commandFailed("rebase --abort", e.message);
44664
44728
  }
44665
44729
  }
44666
44730
  async deleteRef(ref) {
@@ -44680,7 +44744,7 @@ var __webpack_exports__ = {};
44680
44744
  fetchUrl: r.refs.fetch
44681
44745
  }));
44682
44746
  } catch (e) {
44683
- throw createGitError.commandFailed("remote", e.message);
44747
+ throw errors_createGitError.commandFailed("remote", e.message);
44684
44748
  }
44685
44749
  }
44686
44750
  async isGitInitialized() {
@@ -44741,7 +44805,7 @@ var __webpack_exports__ = {};
44741
44805
  changes.push(...stagedRenames);
44742
44806
  return changes;
44743
44807
  } catch (e) {
44744
- throw createGitError.commandFailed("status", e.message);
44808
+ throw errors_createGitError.commandFailed("status", e.message);
44745
44809
  }
44746
44810
  }
44747
44811
  function normalizeLineEndings(content) {
@@ -46521,6 +46585,11 @@ var __webpack_exports__ = {};
46521
46585
  const encodedToken = encodeURIComponent(authToken);
46522
46586
  return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git?no_freeze=true&q=`;
46523
46587
  }
46588
+ function getGitPushUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
46589
+ const url = new URL(anvilUrl);
46590
+ const encodedToken = encodeURIComponent(authToken);
46591
+ return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git`;
46592
+ }
46524
46593
  function getWebSocketUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
46525
46594
  return anvilUrl.replace(/^http/, "ws") + `/ide/api/_/apps/${appId}/ws?access_token=${authToken}`;
46526
46595
  }
@@ -46725,7 +46794,13 @@ var __webpack_exports__ = {};
46725
46794
  if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
46726
46795
  this.reconnectAttempts++;
46727
46796
  logger_logger.verbose(chalk_source.gray(`WebSocket disconnected, reconnecting in ${this.RECONNECT_DELAY / 1000}s (attempt ${this.reconnectAttempts}/${this.MAX_RECONNECT_ATTEMPTS})...`));
46728
- this.reconnectTimer = setTimeout(()=>this.connect(), this.RECONNECT_DELAY);
46797
+ this.reconnectTimer = setTimeout(()=>{
46798
+ this.connect().catch((error)=>{
46799
+ this.emit("error", {
46800
+ error: error instanceof Error ? error : new Error(String(error))
46801
+ });
46802
+ });
46803
+ }, this.RECONNECT_DELAY);
46729
46804
  } else logger_logger.warn(chalk_source.yellow(` WebSocket reconnection failed after ${this.MAX_RECONNECT_ATTEMPTS} attempts. Real-time sync disabled.`));
46730
46805
  });
46731
46806
  this.ws.on("error", (error)=>{
@@ -48268,8 +48343,11 @@ var __webpack_exports__ = {};
48268
48343
  }
48269
48344
  async handleConflict(json_resp, originalFilePaths, retryCount) {
48270
48345
  if (retryCount >= this.MAX_RETRIES) {
48271
- logger_logger.error(`Save failed after ${this.MAX_RETRIES} retries - version conflict`);
48272
- logger_logger.warn("Please resolve conflicts manually or restart anvil sync");
48346
+ logger_logger.error(`Save failed after ${this.MAX_RETRIES} retries due to version conflicts.`);
48347
+ logger_logger.warn("This usually means someone else is editing the app at the same time.");
48348
+ this.emit("max-retries-exceeded", {
48349
+ retries: this.MAX_RETRIES
48350
+ });
48273
48351
  throw createSaveError.maxRetriesExceeded(this.MAX_RETRIES);
48274
48352
  }
48275
48353
  let conflictReason = "Unknown conflict";
@@ -48604,6 +48682,7 @@ var __webpack_exports__ = {};
48604
48682
  this.saveProcessor.on("save-complete", (data)=>this.emit("save-complete", data));
48605
48683
  this.saveProcessor.on("validation-failed", (data)=>this.emit("validation-failed", data));
48606
48684
  this.saveProcessor.on("sync-conflict", (data)=>this.emit("sync-conflict", data));
48685
+ this.saveProcessor.on("max-retries-exceeded", (data)=>this.emit("max-retries-exceeded", data));
48607
48686
  }
48608
48687
  async connectWebSocket() {
48609
48688
  this.wsClient = new WebSocketClient({
@@ -48676,7 +48755,11 @@ var __webpack_exports__ = {};
48676
48755
  this.saveProcessor?.queueSave();
48677
48756
  });
48678
48757
  this.fileWatcher.on("file-change", async ({ event, path: filePath, relativePath })=>{
48679
- await this.handleFileChange(event, filePath, relativePath);
48758
+ try {
48759
+ await this.handleFileChange(event, filePath, relativePath);
48760
+ } catch (error) {
48761
+ logger_logger.error(`Error handling file change for ${relativePath}: ${error.message}`);
48762
+ }
48680
48763
  });
48681
48764
  await this.fileWatcher.start(this.currentBranch);
48682
48765
  await new Promise(()=>{});
@@ -48830,7 +48913,7 @@ var __webpack_exports__ = {};
48830
48913
  logger_logger.verbose(chalk_source.green(`✔ Branch '${branchName}' is up-to-date with Anvil.`));
48831
48914
  return null;
48832
48915
  } catch (e) {
48833
- throw createGitError.fetchFailed(e.message);
48916
+ throw errors_createGitError.fetchFailed(e.message);
48834
48917
  }
48835
48918
  }
48836
48919
  async function validateViaAnvilServer(git, branchName, appId, anvilUrl, username) {
@@ -48842,7 +48925,7 @@ var __webpack_exports__ = {};
48842
48925
  "HEAD"
48843
48926
  ])).trim();
48844
48927
  } catch (e) {
48845
- throw createGitError.commandFailed("revparse", e.message);
48928
+ throw errors_createGitError.commandFailed("revparse", e.message);
48846
48929
  }
48847
48930
  try {
48848
48931
  const resp = await fetch(`${anvilUrl}/ide/api/_/apps/lookup-by-commit`, {
@@ -48866,7 +48949,12 @@ var __webpack_exports__ = {};
48866
48949
  }
48867
48950
  const allBranches = data.apps?.flatMap((app)=>app.branches || []) || [];
48868
48951
  const branchExistsOnAnvil = allBranches.some((b)=>b === branchName);
48869
- if (!branchExistsOnAnvil) throw createValidationError.invalidApp(branchName, `Branch '${branchName}' doesn't exist on Anvil yet. Push to Anvil first: git push anvil ${branchName}`);
48952
+ if (!branchExistsOnAnvil) return {
48953
+ behind: 0,
48954
+ ahead: 0,
48955
+ diverged: false,
48956
+ branchMissing: true
48957
+ };
48870
48958
  throw createValidationError.invalidApp(appId, `Anvil server doesn't recognize commit ${commitId.substring(0, 8)} on branch '${branchName}'. Please pull the latest changes from Anvil before starting sync.`);
48871
48959
  } catch (e) {
48872
48960
  if (e.type) throw e;
@@ -48874,26 +48962,6 @@ var __webpack_exports__ = {};
48874
48962
  }
48875
48963
  }
48876
48964
  const DEFAULT_ANVIL_URL = resolveAnvilUrl();
48877
- async function checkUncommittedChanges(repoPath) {
48878
- try {
48879
- const git = esm_default(repoPath);
48880
- const status = await git.status();
48881
- const staged = status.files.filter((file)=>{
48882
- const indexStatus = file.index.trim();
48883
- return indexStatus.length > 0 && "?" !== indexStatus;
48884
- }).map((file)=>file.path);
48885
- return {
48886
- hasChanges: !status.isClean(),
48887
- modified: status.modified,
48888
- notAdded: status.not_added,
48889
- created: status.created,
48890
- deleted: status.deleted,
48891
- staged
48892
- };
48893
- } catch (e) {
48894
- throw createGitError.commandFailed("status", e.message);
48895
- }
48896
- }
48897
48965
  async function syncToLatest(repoPath, appId, anvilUrl, authToken, currentBranch, username) {
48898
48966
  try {
48899
48967
  const git = esm_default(repoPath);
@@ -48915,7 +48983,7 @@ var __webpack_exports__ = {};
48915
48983
  return newCommitId;
48916
48984
  } catch (e) {
48917
48985
  if (isAnvilError(e)) throw e;
48918
- throw createGitError.commandFailed("sync", e instanceof Error ? e.message : String(e));
48986
+ throw errors_createGitError.commandFailed("sync", e instanceof Error ? e.message : String(e));
48919
48987
  }
48920
48988
  }
48921
48989
  async function api_watch(repoPath, appId, anvilUrl = DEFAULT_ANVIL_URL, stagedOnly = false, username) {
@@ -48929,11 +48997,11 @@ var __webpack_exports__ = {};
48929
48997
  "--abbrev-ref",
48930
48998
  "HEAD"
48931
48999
  ]);
48932
- if ("HEAD" === branchRef) throw createGitError.commandFailed("revparse", "Cannot sync from detached HEAD state. Please checkout a branch first.");
49000
+ if ("HEAD" === branchRef) throw errors_createGitError.commandFailed("revparse", "Cannot sync from detached HEAD state. Please checkout a branch first.");
48933
49001
  currentBranch = branchRef;
48934
49002
  } catch (e) {
48935
49003
  if ("git_command_failed" === e.type) throw e;
48936
- throw createGitError.commandFailed("revparse", e.message);
49004
+ throw errors_createGitError.commandFailed("revparse", e.message);
48937
49005
  }
48938
49006
  let commitId;
48939
49007
  try {
@@ -48941,7 +49009,7 @@ var __webpack_exports__ = {};
48941
49009
  "HEAD"
48942
49010
  ])).trim();
48943
49011
  } catch (e) {
48944
- throw createGitError.commandFailed("revparse", e.message);
49012
+ throw errors_createGitError.commandFailed("revparse", e.message);
48945
49013
  }
48946
49014
  logger_logger.verbose(chalk_source.cyan("Current branch: ") + chalk_source.bold(currentBranch));
48947
49015
  logger_logger.verbose(chalk_source.cyan("Current commit ID: ") + chalk_source.gray(commitId));
@@ -48951,7 +49019,7 @@ var __webpack_exports__ = {};
48951
49019
  const status = await git.status();
48952
49020
  hasUncommittedChanges = status.files.length > 0;
48953
49021
  } catch (e) {
48954
- throw createGitError.commandFailed("status", e.message);
49022
+ throw errors_createGitError.commandFailed("status", e.message);
48955
49023
  }
48956
49024
  logger_logger.verbose(chalk_source.blue("Has uncommitted changes: ") + chalk_source.bold(hasUncommittedChanges));
48957
49025
  const session = new WatchSession(repoPath, appId, {
@@ -48982,7 +49050,7 @@ var __webpack_exports__ = {};
48982
49050
  try {
48983
49051
  await session.initialize();
48984
49052
  } catch (e) {
48985
- throw createGitError.commandFailed("initialize", e.message);
49053
+ throw errors_createGitError.commandFailed("initialize", e.message);
48986
49054
  }
48987
49055
  if (null !== syncStatus) session.syncStatus = syncStatus;
48988
49056
  session.hasUncommittedChanges = hasUncommittedChanges;
@@ -49078,30 +49146,328 @@ var __webpack_exports__ = {};
49078
49146
  return null;
49079
49147
  }
49080
49148
  }
49149
+ async function pushToAnvil(options) {
49150
+ try {
49151
+ const authToken = await auth_getValidAuthToken(options.anvilUrl, options.username);
49152
+ const pushUrl = getGitPushUrl(options.appId, authToken, options.anvilUrl);
49153
+ const git = new GitService(options.repoPath);
49154
+ const refSpec = `${options.branchName}:${options.branchName}`;
49155
+ logger_logger.progress("push", "Pushing to Anvil...");
49156
+ await git.push(pushUrl, refSpec, options.force ?? false);
49157
+ logger_logger.progressEnd("push", "Pushed to Anvil");
49158
+ return true;
49159
+ } catch (e) {
49160
+ logger_logger.progressEnd("push", "Push failed");
49161
+ logger_logger.error(`Failed to push: ${errors_getErrorMessage(e)}`);
49162
+ return false;
49163
+ }
49164
+ }
49165
+ async function fetchAndRebaseFromAnvil(options) {
49166
+ const git = new GitService(options.repoPath);
49167
+ const tempRef = `anvil-rebase-temp-${Date.now()}`;
49168
+ let didStash = false;
49169
+ try {
49170
+ const authToken = await auth_getValidAuthToken(options.anvilUrl, options.username);
49171
+ const fetchUrl = getGitFetchUrl(options.appId, authToken, options.anvilUrl);
49172
+ const hasChanges = await git.hasUncommittedChanges();
49173
+ if (hasChanges) {
49174
+ logger_logger.progress("rebase", "Stashing uncommitted changes...");
49175
+ didStash = await git.stash("anvil-sync: stash before rebase");
49176
+ }
49177
+ logger_logger.progressUpdate("rebase", "Fetching from Anvil...");
49178
+ await git.fetch(fetchUrl, `+${options.branchName}:${tempRef}`);
49179
+ logger_logger.progressUpdate("rebase", "Rebasing...");
49180
+ await git.rebase(tempRef);
49181
+ logger_logger.progressUpdate("rebase", "Pushing rebased changes...");
49182
+ const pushUrl = getGitPushUrl(options.appId, authToken, options.anvilUrl);
49183
+ await git.push(pushUrl, `${options.branchName}:${options.branchName}`, true);
49184
+ logger_logger.progressEnd("rebase", "Rebased and pushed to Anvil");
49185
+ await git.deleteRef(`refs/heads/${tempRef}`);
49186
+ if (didStash) try {
49187
+ await git.stashPop();
49188
+ logger_logger.verbose(chalk_source.green("Restored uncommitted changes"));
49189
+ } catch (e) {
49190
+ logger_logger.warn("Uncommitted changes were stashed but could not be restored automatically.");
49191
+ logger_logger.info(chalk_source.gray(" Run: git stash pop"));
49192
+ }
49193
+ return {
49194
+ success: true,
49195
+ conflicted: false
49196
+ };
49197
+ } catch (e) {
49198
+ const msg = errors_getErrorMessage(e);
49199
+ if (msg.includes("rebase") && (msg.includes("conflict") || msg.includes("CONFLICT") || msg.includes("could not apply"))) {
49200
+ try {
49201
+ await git.rebaseAbort();
49202
+ } catch {}
49203
+ await git.deleteRef(`refs/heads/${tempRef}`);
49204
+ if (didStash) try {
49205
+ await git.stashPop();
49206
+ } catch {
49207
+ logger_logger.warn("Your uncommitted changes are in the stash. Run: git stash pop");
49208
+ }
49209
+ logger_logger.progressEnd("rebase", "Rebase failed - conflicts");
49210
+ return {
49211
+ success: false,
49212
+ conflicted: true
49213
+ };
49214
+ }
49215
+ await git.deleteRef(`refs/heads/${tempRef}`);
49216
+ if (didStash) try {
49217
+ await git.stashPop();
49218
+ } catch {
49219
+ logger_logger.warn("Your uncommitted changes are in the stash. Run: git stash pop");
49220
+ }
49221
+ logger_logger.progressEnd("rebase", "Rebase failed");
49222
+ logger_logger.error(`Failed to rebase: ${msg}`);
49223
+ return {
49224
+ success: false,
49225
+ conflicted: false
49226
+ };
49227
+ }
49228
+ }
49229
+ async function fetchAndHardResetFromAnvil(options) {
49230
+ const git = new GitService(options.repoPath);
49231
+ const tempRef = `anvil-reset-temp-${Date.now()}`;
49232
+ try {
49233
+ const authToken = await auth_getValidAuthToken(options.anvilUrl, options.username);
49234
+ const fetchUrl = getGitFetchUrl(options.appId, authToken, options.anvilUrl);
49235
+ logger_logger.progress("reset", "Fetching from Anvil...");
49236
+ await git.fetch(fetchUrl, `+${options.branchName}:${tempRef}`);
49237
+ logger_logger.progressUpdate("reset", "Resetting to Anvil's version...");
49238
+ await git.reset(tempRef, "hard");
49239
+ logger_logger.progressEnd("reset", "Reset to Anvil's version");
49240
+ await git.deleteRef(`refs/heads/${tempRef}`);
49241
+ return true;
49242
+ } catch (e) {
49243
+ await git.deleteRef(`refs/heads/${tempRef}`);
49244
+ logger_logger.progressEnd("reset", "Reset failed");
49245
+ logger_logger.error(`Failed to reset: ${errors_getErrorMessage(e)}`);
49246
+ return false;
49247
+ }
49248
+ }
49249
+ function getSyncStateCategory(syncStatus) {
49250
+ if (syncStatus?.branchMissing) return "branch-missing";
49251
+ if (syncStatus?.diverged) return "diverged";
49252
+ if (syncStatus?.ahead && !syncStatus?.behind) return "ahead";
49253
+ if (syncStatus?.behind && !syncStatus?.ahead) return "behind";
49254
+ if (syncStatus?.ahead && syncStatus?.behind) return "diverged";
49255
+ return "in-sync";
49256
+ }
49257
+ async function recheckSyncStatus(previousCategory, previousBranch, options) {
49258
+ try {
49259
+ logger_logger.progress("verify", "Verifying repository status...");
49260
+ const freshSession = await api_watch(options.repoPath, options.appId, options.anvilUrl, options.stagedOnly, options.username);
49261
+ logger_logger.progressEnd("verify");
49262
+ const currentCategory = getSyncStateCategory(freshSession.syncStatus);
49263
+ const currentBranch = freshSession.getBranchName() || "master";
49264
+ if (currentCategory !== previousCategory || currentBranch !== previousBranch) {
49265
+ logger_logger.warn("Repository status has changed. Re-evaluating...");
49266
+ return freshSession;
49267
+ }
49268
+ freshSession.cleanup();
49269
+ return null;
49270
+ } catch (e) {
49271
+ logger_logger.progressEnd("verify");
49272
+ logger_logger.verbose(chalk_source.gray(`Could not re-verify status: ${errors_getErrorMessage(e)}`));
49273
+ return null;
49274
+ }
49275
+ }
49276
+ async function recreateSessionAndValidate(options) {
49277
+ logger_logger.progress("init", "Restarting watch session...");
49278
+ try {
49279
+ const newSession = await api_watch(options.repoPath, options.appId, options.anvilUrl, options.stagedOnly, options.username);
49280
+ logger_logger.progressEnd("init");
49281
+ return await checkSyncStatusAndStart(newSession, options);
49282
+ } catch (e) {
49283
+ logger_logger.progressEnd("init", "Failed");
49284
+ logger_logger.error(`Failed to restart: ${errors_getErrorMessage(e)}`);
49285
+ return false;
49286
+ }
49287
+ }
49081
49288
  async function checkSyncStatusAndStart(session, options) {
49082
49289
  const syncStatus = session.syncStatus;
49290
+ const branchName = session.getBranchName() || "master";
49291
+ const hasUncommitted = session.hasUncommittedChanges;
49292
+ const stateCategory = getSyncStateCategory(syncStatus);
49293
+ if (syncStatus?.branchMissing) {
49294
+ logger_logger.warn(`Branch '${branchName}' doesn't exist on Anvil yet.`);
49295
+ if (hasUncommitted) logger_logger.info(chalk_source.gray(" You also have uncommitted changes."));
49296
+ let shouldPush = options.autoMode;
49297
+ if (options.autoMode) logger_logger.info(chalk_source.cyan("→ Auto-pushing new branch to Anvil..."));
49298
+ else {
49299
+ shouldPush = await logger_logger.confirm("Would you like to push this branch to Anvil?", true);
49300
+ if (shouldPush) {
49301
+ const changed = await recheckSyncStatus(stateCategory, branchName, options);
49302
+ if (changed) return await checkSyncStatusAndStart(changed, options);
49303
+ }
49304
+ }
49305
+ if (!shouldPush) {
49306
+ logger_logger.warn("Watch cancelled. Push your branch to Anvil, then try again.");
49307
+ session.cleanup();
49308
+ return false;
49309
+ }
49310
+ session.cleanup();
49311
+ const pushed = await pushToAnvil({
49312
+ repoPath: options.repoPath,
49313
+ appId: options.appId,
49314
+ anvilUrl: options.anvilUrl,
49315
+ branchName,
49316
+ username: options.username
49317
+ });
49318
+ if (!pushed) return false;
49319
+ return await recreateSessionAndValidate(options);
49320
+ }
49083
49321
  if (syncStatus?.behind || syncStatus?.ahead) if (syncStatus.ahead && !syncStatus.behind) {
49084
- logger_logger.error(`Your local repository is ${syncStatus.ahead} commit(s) ahead of Anvil.`);
49085
- logger_logger.verbose(chalk_source.gray(" You have local commits that haven't been pushed to Anvil yet."));
49086
- logger_logger.warn("Please push your changes to Anvil before continuing:");
49087
- logger_logger.verbose(chalk_source.gray(" git push anvil <branch-name>"));
49322
+ logger_logger.warn(`Your local repository is ${syncStatus.ahead} commit(s) ahead of Anvil.`);
49323
+ if (hasUncommitted) logger_logger.info(chalk_source.gray(" You also have uncommitted changes."));
49324
+ let shouldPush = options.autoMode;
49325
+ if (options.autoMode) logger_logger.info(chalk_source.cyan(" Auto-pushing to Anvil..."));
49326
+ else {
49327
+ const action = await logger_logger.select("What would you like to do?", [
49328
+ {
49329
+ name: "Push your changes to Anvil (recommended)",
49330
+ value: "push"
49331
+ },
49332
+ {
49333
+ name: "Exit and handle manually",
49334
+ value: "exit"
49335
+ }
49336
+ ], "push");
49337
+ shouldPush = "push" === action;
49338
+ if (shouldPush) {
49339
+ const changed = await recheckSyncStatus(stateCategory, branchName, options);
49340
+ if (changed) return await checkSyncStatusAndStart(changed, options);
49341
+ }
49342
+ }
49343
+ if (!shouldPush) {
49344
+ logger_logger.warn("Watch cancelled.");
49345
+ session.cleanup();
49346
+ return false;
49347
+ }
49088
49348
  session.cleanup();
49089
- return false;
49349
+ const pushed = await pushToAnvil({
49350
+ repoPath: options.repoPath,
49351
+ appId: options.appId,
49352
+ anvilUrl: options.anvilUrl,
49353
+ branchName,
49354
+ username: options.username
49355
+ });
49356
+ if (!pushed) return false;
49357
+ return await recreateSessionAndValidate(options);
49090
49358
  } else if (syncStatus.diverged) {
49091
- logger_logger.error("Your local repository has diverged from Anvil.");
49092
- logger_logger.verbose(chalk_source.gray(` You are ${syncStatus.ahead} commit(s) ahead and ${syncStatus.behind} commit(s) behind.`));
49093
- logger_logger.warn("Please resolve the divergence before continuing:");
49094
- logger_logger.verbose(chalk_source.gray(" Option 1: Pull from Anvil and rebase your changes (git pull --rebase)"));
49095
- logger_logger.verbose(chalk_source.gray(" Option 2: Reset to Anvil's version (you'll lose local commits)"));
49096
- logger_logger.verbose(chalk_source.gray(" Option 3: Push your changes to Anvil (if you want to keep them)"));
49359
+ logger_logger.warn("Your local repository has diverged from Anvil.");
49360
+ logger_logger.info(chalk_source.gray(` You are ${syncStatus.ahead} commit(s) ahead and ${syncStatus.behind} commit(s) behind.`));
49361
+ if (hasUncommitted) logger_logger.info(chalk_source.gray(" You also have uncommitted changes."));
49097
49362
  session.cleanup();
49098
- return false;
49363
+ if (options.autoMode) {
49364
+ logger_logger.info(chalk_source.cyan("→ Auto-rebasing onto Anvil's version..."));
49365
+ const result = await fetchAndRebaseFromAnvil({
49366
+ repoPath: options.repoPath,
49367
+ appId: options.appId,
49368
+ anvilUrl: options.anvilUrl,
49369
+ branchName,
49370
+ username: options.username
49371
+ });
49372
+ if (result.conflicted) {
49373
+ logger_logger.error("Rebase failed due to conflicts. Please resolve manually:");
49374
+ logger_logger.info(chalk_source.gray(" 1. Run: git fetch anvil && git rebase anvil/" + branchName));
49375
+ logger_logger.info(chalk_source.gray(" 2. Resolve conflicts, then: git rebase --continue"));
49376
+ logger_logger.info(chalk_source.gray(" 3. Push: git push anvil " + branchName));
49377
+ return false;
49378
+ }
49379
+ if (!result.success) return false;
49380
+ return await recreateSessionAndValidate(options);
49381
+ }
49382
+ const action = await logger_logger.select("How would you like to resolve this?", [
49383
+ {
49384
+ name: "Pull from Anvil and rebase your changes on top (recommended)",
49385
+ value: "rebase"
49386
+ },
49387
+ {
49388
+ name: "Discard your local commits and use Anvil's version",
49389
+ value: "reset"
49390
+ },
49391
+ {
49392
+ name: "Push your version to Anvil (overwrites remote)",
49393
+ value: "push"
49394
+ },
49395
+ {
49396
+ name: "Exit and handle manually",
49397
+ value: "exit"
49398
+ }
49399
+ ], "rebase");
49400
+ if ("exit" === action) {
49401
+ logger_logger.warn("Watch cancelled.");
49402
+ return false;
49403
+ }
49404
+ const changed = await recheckSyncStatus(stateCategory, branchName, options);
49405
+ if (changed) return await checkSyncStatusAndStart(changed, options);
49406
+ if ("rebase" === action) {
49407
+ const result = await fetchAndRebaseFromAnvil({
49408
+ repoPath: options.repoPath,
49409
+ appId: options.appId,
49410
+ anvilUrl: options.anvilUrl,
49411
+ branchName,
49412
+ username: options.username
49413
+ });
49414
+ if (result.conflicted) {
49415
+ logger_logger.error("Rebase failed due to conflicts. Please resolve manually:");
49416
+ logger_logger.info(chalk_source.gray(" 1. Run: git fetch anvil && git rebase anvil/" + branchName));
49417
+ logger_logger.info(chalk_source.gray(" 2. Resolve conflicts, then: git rebase --continue"));
49418
+ logger_logger.info(chalk_source.gray(" 3. Push: git push anvil " + branchName));
49419
+ return false;
49420
+ }
49421
+ if (!result.success) return false;
49422
+ return await recreateSessionAndValidate(options);
49423
+ }
49424
+ if ("reset" === action) {
49425
+ 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?`;
49426
+ const confirmed = await logger_logger.confirm(resetWarning, false);
49427
+ if (!confirmed) {
49428
+ logger_logger.warn("Watch cancelled.");
49429
+ return false;
49430
+ }
49431
+ const reset = await fetchAndHardResetFromAnvil({
49432
+ repoPath: options.repoPath,
49433
+ appId: options.appId,
49434
+ anvilUrl: options.anvilUrl,
49435
+ branchName,
49436
+ username: options.username
49437
+ });
49438
+ if (!reset) return false;
49439
+ return await recreateSessionAndValidate(options);
49440
+ }
49441
+ if ("push" === action) {
49442
+ const confirmed = await logger_logger.confirm("This will overwrite Anvil's version. Are you sure?", false);
49443
+ if (!confirmed) {
49444
+ logger_logger.warn("Watch cancelled.");
49445
+ return false;
49446
+ }
49447
+ const pushed = await pushToAnvil({
49448
+ repoPath: options.repoPath,
49449
+ appId: options.appId,
49450
+ anvilUrl: options.anvilUrl,
49451
+ branchName,
49452
+ username: options.username,
49453
+ force: true
49454
+ });
49455
+ if (!pushed) return false;
49456
+ return await recreateSessionAndValidate(options);
49457
+ }
49099
49458
  } else {
49100
49459
  logger_logger.warn(`Your local repository is ${syncStatus.behind} commit(s) behind Anvil.`);
49101
49460
  logger_logger.verbose(chalk_source.gray(" You need to sync to the latest version before watching."));
49461
+ if (hasUncommitted) logger_logger.info(chalk_source.gray(" You also have uncommitted changes."));
49102
49462
  let shouldSync = options.autoMode;
49103
49463
  if (options.autoMode) logger_logger.info(chalk_source.cyan("→ Auto-syncing to latest version..."));
49104
- else shouldSync = await logger_logger.confirm("Would you like to sync to the latest version from Anvil now?", true);
49464
+ else {
49465
+ shouldSync = await logger_logger.confirm("Would you like to sync to the latest version from Anvil now?", true);
49466
+ if (shouldSync) {
49467
+ const changed = await recheckSyncStatus(stateCategory, branchName, options);
49468
+ if (changed) return await checkSyncStatusAndStart(changed, options);
49469
+ }
49470
+ }
49105
49471
  if (!shouldSync) {
49106
49472
  logger_logger.warn("Watch cancelled. Please sync manually before watching.");
49107
49473
  session.cleanup();
@@ -49117,23 +49483,14 @@ var __webpack_exports__ = {};
49117
49483
  }
49118
49484
  logger_logger.progress("sync-latest", "Syncing to latest version from Anvil...");
49119
49485
  try {
49120
- const newCommitId = await syncToLatest(options.repoPath, options.appId, options.anvilUrl, authToken, session.getBranchName() || "master", options.username);
49486
+ const newCommitId = await syncToLatest(options.repoPath, options.appId, options.anvilUrl, authToken, branchName, options.username);
49121
49487
  logger_logger.progressEnd("sync-latest", `Synced to latest version: ${newCommitId.substring(0, 8)}`);
49122
49488
  } catch (e) {
49123
49489
  logger_logger.progressEnd("sync-latest", "Failed");
49124
49490
  logger_logger.error(`Failed to sync: ${errors_getErrorMessage(e)}`);
49125
49491
  process.exit(1);
49126
49492
  }
49127
- logger_logger.progress("init", "Starting watch...");
49128
- try {
49129
- const newSession = await api_watch(options.repoPath, options.appId, options.anvilUrl, options.stagedOnly, options.username);
49130
- logger_logger.progressEnd("init");
49131
- return await checkSyncStatusAndStart(newSession, options);
49132
- } catch (e) {
49133
- logger_logger.progressEnd("init", "Failed");
49134
- logger_logger.error(`Failed to start watch: ${errors_getErrorMessage(e)}`);
49135
- process.exit(1);
49136
- }
49493
+ return await recreateSessionAndValidate(options);
49137
49494
  }
49138
49495
  await startWatchingWithEventHandlers(session, options);
49139
49496
  return true;
@@ -49141,64 +49498,30 @@ var __webpack_exports__ = {};
49141
49498
  async function startWatchingWithEventHandlers(session, options) {
49142
49499
  const { autoMode, repoPath, appId, anvilUrl, stagedOnly } = options;
49143
49500
  session.on("branch-changed", async ({ oldBranch, newBranch })=>{
49144
- session.cleanup();
49145
- logger_logger.warn(`Branch changed: ${chalk_source.bold(oldBranch)} → ${chalk_source.bold(newBranch)}`);
49146
- if (autoMode) {
49147
- logger_logger.info(chalk_source.cyan("→ Auto-restarting watch on new branch..."));
49501
+ try {
49502
+ session.cleanup();
49503
+ logger_logger.warn(`Branch changed: ${chalk_source.bold(oldBranch)} → ${chalk_source.bold(newBranch)}`);
49504
+ logger_logger.info(chalk_source.cyan("→ Restarting watch on new branch..."));
49148
49505
  const git = esm_default(repoPath);
49149
49506
  const actualBranch = (await git.revparse([
49150
49507
  "--abbrev-ref",
49151
49508
  "HEAD"
49152
49509
  ])).trim();
49153
49510
  if (actualBranch !== newBranch) logger_logger.verbose(chalk_source.yellow(`Branch changed again to ${chalk_source.bold(actualBranch)}, using that instead`));
49154
- logger_logger.verbose(chalk_source.blue(`Restarting watch on branch: ${chalk_source.bold(actualBranch)}`));
49155
49511
  logger_logger.progress("init", "Initializing watch session...");
49156
49512
  try {
49157
49513
  const newSession = await api_watch(repoPath, appId, anvilUrl, stagedOnly, options.username);
49158
49514
  logger_logger.progressEnd("init");
49159
49515
  const started = await checkSyncStatusAndStart(newSession, options);
49160
- if (!started) process.exit(1);
49516
+ if (!started) process.exit(0);
49161
49517
  } catch (e) {
49162
49518
  logger_logger.progressEnd("init", "Failed");
49163
49519
  logger_logger.error("Failed to restart watch: " + errors_getErrorMessage(e));
49164
49520
  process.exit(1);
49165
49521
  }
49166
- } else {
49167
- logger_logger.verbose(chalk_source.gray(" Watch session needs to be restarted to validate the new branch."));
49168
- const action = await logger_logger.select("What would you like to do?", [
49169
- {
49170
- name: "Restart watch on new branch (recommended)",
49171
- value: "restart"
49172
- },
49173
- {
49174
- name: "Exit",
49175
- value: "exit"
49176
- }
49177
- ], "restart");
49178
- if ("restart" === action) {
49179
- logger_logger.info(chalk_source.cyan("→ Restarting watch..."));
49180
- const git = esm_default(repoPath);
49181
- const actualBranch = (await git.revparse([
49182
- "--abbrev-ref",
49183
- "HEAD"
49184
- ])).trim();
49185
- if (actualBranch !== newBranch) logger_logger.verbose(chalk_source.yellow(`Branch changed to ${chalk_source.bold(actualBranch)} while prompting`));
49186
- logger_logger.verbose(chalk_source.blue(`Starting watch on branch: ${chalk_source.bold(actualBranch)}`));
49187
- logger_logger.progress("init", "Initializing watch session...");
49188
- try {
49189
- const newSession = await api_watch(repoPath, appId, anvilUrl, stagedOnly, options.username);
49190
- logger_logger.progressEnd("init");
49191
- const started = await checkSyncStatusAndStart(newSession, options);
49192
- if (!started) process.exit(1);
49193
- } catch (e) {
49194
- logger_logger.progressEnd("init", "Failed");
49195
- logger_logger.error("Failed to restart watch: " + errors_getErrorMessage(e));
49196
- process.exit(1);
49197
- }
49198
- } else {
49199
- logger_logger.info("Exiting...");
49200
- process.exit(0);
49201
- }
49522
+ } catch (error) {
49523
+ logger_logger.error(`Error handling branch change: ${error.message}`);
49524
+ process.exit(1);
49202
49525
  }
49203
49526
  });
49204
49527
  session.on("validation-failed", ({ reason, currentBranch })=>{
@@ -49208,8 +49531,31 @@ var __webpack_exports__ = {};
49208
49531
  session.cleanup();
49209
49532
  process.exit(1);
49210
49533
  });
49534
+ session.on("max-retries-exceeded", async ()=>{
49535
+ if (autoMode) {
49536
+ logger_logger.info(chalk_source.cyan("→ Auto-restarting watch session..."));
49537
+ session.cleanup();
49538
+ const restarted = await recreateSessionAndValidate(options);
49539
+ if (!restarted) process.exit(1);
49540
+ } else {
49541
+ const shouldRestart = await logger_logger.confirm("Would you like to restart the watch session?", true);
49542
+ if (shouldRestart) {
49543
+ session.cleanup();
49544
+ const restarted = await recreateSessionAndValidate(options);
49545
+ if (!restarted) process.exit(1);
49546
+ }
49547
+ }
49548
+ });
49211
49549
  if (session.hasUncommittedChanges) {
49212
- logger_logger.progress("save", "You have uncommitted changes. Saving to Anvil...");
49550
+ if (!autoMode) {
49551
+ const shouldContinue = await logger_logger.confirm("Continue? Your uncommitted changes will be synced to Anvil.", true);
49552
+ if (!shouldContinue) {
49553
+ logger_logger.warn("Watch cancelled. Commit, stash, or discard your changes, then try again.");
49554
+ session.cleanup();
49555
+ process.exit(0);
49556
+ }
49557
+ }
49558
+ logger_logger.progress("save", "Saving uncommitted changes to Anvil...");
49213
49559
  try {
49214
49560
  await session.forceSave();
49215
49561
  logger_logger.progressEnd("save");
@@ -49220,7 +49566,12 @@ var __webpack_exports__ = {};
49220
49566
  }
49221
49567
  await session.startWatching();
49222
49568
  }
49223
- async function handleSyncCommand(options) {
49569
+ async function handleWatchCommand(options) {
49570
+ const invoked = process.argv[2];
49571
+ if ("sync" === invoked) {
49572
+ logger_logger.error("'sync' has been renamed to 'watch'. Please use 'anvil watch' instead.");
49573
+ process.exit(1);
49574
+ }
49224
49575
  const { path: repoPath = process.cwd(), appid: explicitAppId, useFirst = false, stagedOnly = false, autoMode = false, url: explicitUrl, user: explicitUsername } = options;
49225
49576
  try {
49226
49577
  const validationResult = await validateAnvilApp(repoPath);
@@ -49285,7 +49636,7 @@ var __webpack_exports__ = {};
49285
49636
  const detectedAppIds = detectedFromAllRemotes.filter((c)=>c.detectedUrl && normalizeAnvilUrl(c.detectedUrl) === detectedUrl).map((c)=>c.appId);
49286
49637
  logger_logger.warn(chalk_source.yellow("Git remotes point to ") + chalk_source.bold(detectedUrl) + chalk_source.yellow(", but you specified: ") + chalk_source.bold(normalizedSelected));
49287
49638
  if (detectedAppIds.length > 0) logger_logger.info(chalk_source.gray(" Detected app IDs on remote URL: ") + chalk_source.bold(detectedAppIds.join(", ")));
49288
- logger_logger.info(chalk_source.gray(" To use the remote URL instead, run: ") + chalk_source.cyan(`anvil sync --url ${detectedUrl}`));
49639
+ logger_logger.info(chalk_source.gray(" To use the remote URL instead, run: ") + chalk_source.cyan(`anvil watch --url ${detectedUrl}`));
49289
49640
  }
49290
49641
  const candidatesForSelectedUrl = detectedFromAllRemotes.filter((c)=>c.detectedUrl && normalizeAnvilUrl(c.detectedUrl) === normalizedSelected);
49291
49642
  if (candidatesForSelectedUrl.length > 0) for (const candidate of candidatesForSelectedUrl){
@@ -49370,31 +49721,6 @@ var __webpack_exports__ = {};
49370
49721
  }
49371
49722
  logger_logger.verbose(chalk_source.green(`✔ App ID validated successfully`));
49372
49723
  if (appIdValidation.app_name) logger_logger.verbose(chalk_source.gray(` App name: ${appIdValidation.app_name}`));
49373
- const changeStatus = await checkUncommittedChanges(repoPath);
49374
- if (changeStatus.hasChanges) {
49375
- const skipPromptReasons = [];
49376
- if (autoMode) skipPromptReasons.push("Auto mode enabled - continuing without confirmation.");
49377
- if (stagedOnly && 0 === changeStatus.staged.length) skipPromptReasons.push("Staged-only mode with no staged changes - continuing without confirmation.");
49378
- logger_logger.warn("Repository has uncommitted changes");
49379
- if (changeStatus.modified.length > 0) logger_logger.verbose(chalk_source.gray(` Modified: ${changeStatus.modified.join(", ")}`));
49380
- if (changeStatus.created.length > 0 || changeStatus.notAdded.length > 0) {
49381
- const newFiles = [
49382
- ...changeStatus.created,
49383
- ...changeStatus.notAdded
49384
- ];
49385
- logger_logger.verbose(chalk_source.gray(` New files: ${newFiles.join(", ")}`));
49386
- }
49387
- if (changeStatus.deleted.length > 0) logger_logger.verbose(chalk_source.gray(` Deleted: ${changeStatus.deleted.join(", ")}`));
49388
- if (changeStatus.staged.length > 0) logger_logger.verbose(chalk_source.gray(` Staged: ${changeStatus.staged.join(", ")}`));
49389
- if (skipPromptReasons.length > 0) skipPromptReasons.forEach((reason)=>logger_logger.verbose(chalk_source.gray(` ${reason}`)));
49390
- else {
49391
- const shouldContinue = await logger_logger.confirm("Do you want to continue watching anyway?", true);
49392
- if (!shouldContinue) {
49393
- logger_logger.warn("Watch cancelled. Please commit or stash your changes first.");
49394
- process.exit(0);
49395
- }
49396
- }
49397
- }
49398
49724
  if (stagedOnly) logger_logger.info(chalk_source.yellow("▸ Staged-only mode: Only staged changes will be synced"));
49399
49725
  if (autoMode) logger_logger.info(chalk_source.cyan("▸ Auto mode: Will automatically restart on branch changes and sync when behind"));
49400
49726
  logger_logger.progress("init", "Initializing watch session...");
@@ -49415,10 +49741,10 @@ var __webpack_exports__ = {};
49415
49741
  process.exit(1);
49416
49742
  }
49417
49743
  }
49418
- function registerSyncCommand(program) {
49419
- const syncCommand = program.command("sync [path]").description("Watch for file changes and sync to Anvil").alias("watch").alias("w").option("-A, --appid <APP_ID>", "Specify app ID directly").option("-f, --first", "Auto-select first detected app ID without confirmation").option("-s, --staged-only", "Only sync staged changes (use git add to stage files)").option("-a, --auto", "Auto mode: restart on branch changes and sync when behind").option("-V, --verbose", "Show detailed output").option("-u, --url <ANVIL_URL>", "Specify Anvil server URL (e.g., anvil.works, localhost)").option("-U, --user <USERNAME>", "Specify which user account to use").action(async (path, options)=>{
49744
+ function registerWatchCommand(program) {
49745
+ const watchCommand = program.command("watch [path]").description("Watch for file changes and sync to Anvil").alias("sync").alias("w").option("-A, --appid <APP_ID>", "Specify app ID directly").option("-f, --first", "Auto-select first detected app ID without confirmation").option("-s, --staged-only", "Only sync staged changes (use git add to stage files)").option("-a, --auto", "Auto mode: restart on branch changes and sync when behind").option("-V, --verbose", "Show detailed output").option("-u, --url <ANVIL_URL>", "Specify Anvil server URL (e.g., anvil.works, localhost)").option("-U, --user <USERNAME>", "Specify which user account to use").action(async (path, options)=>{
49420
49746
  if (void 0 !== options.verbose && logger_logger instanceof CLILogger) logger_logger.setVerbose(options.verbose);
49421
- await handleSyncCommand({
49747
+ await handleWatchCommand({
49422
49748
  path,
49423
49749
  appid: options.appid,
49424
49750
  useFirst: options.first,
@@ -49429,7 +49755,7 @@ var __webpack_exports__ = {};
49429
49755
  user: options.user
49430
49756
  });
49431
49757
  });
49432
- return syncCommand;
49758
+ return watchCommand;
49433
49759
  }
49434
49760
  var external_http_ = __webpack_require__("http");
49435
49761
  const external_node_url_namespaceObject = require("node:url");
@@ -50180,84 +50506,89 @@ var __webpack_exports__ = {};
50180
50506
  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");
50181
50507
  }
50182
50508
  function registerLogoutCommand(program) {
50183
- const logoutCommand = program.command("logout [anvil-server-url]").description("Logout from Anvil (optionally specify URL to logout from specific installation)").option("-u, --url <ANVIL_URL>", "Specify Anvil server URL to logout from").option("-U, --user <USERNAME>", "Specify which user account to logout from").alias("lo").action(async (anvilUrl, options)=>{
50184
- const urlToLogout = options?.url || anvilUrl;
50185
- const usernameToLogout = options?.user;
50186
- if (urlToLogout) {
50187
- const normalized = normalizeAnvilUrl(urlToLogout);
50188
- const result = await logout(normalized, usernameToLogout);
50189
- if (result.loggedOut) if (usernameToLogout) logger_logger.success(`Logged out from ${normalized} as ${usernameToLogout}`);
50190
- else logger_logger.success(`Logged out from ${normalized}`);
50191
- else logger_logger.info(result.message || `Not logged in to ${normalized}. No action needed.`);
50192
- } else {
50193
- const availableUrls = getAvailableAnvilUrls();
50194
- if (0 === availableUrls.length) logger_logger.warn("No logged-in accounts found.");
50195
- else if (1 === availableUrls.length) {
50196
- const result = await logout();
50197
- if (result.loggedOut) logger_logger.success("Logged out.");
50509
+ const logoutCommand = program.command("logout [anvil-server-url]").description("Log out from Anvil (optionally specify URL to logout from specific installation)").option("-u, --url <ANVIL_URL>", "Specify Anvil server URL to logout from").option("-U, --user <USERNAME>", "Specify which user account to logout from").alias("lo").action(async (anvilUrl, options)=>{
50510
+ try {
50511
+ const urlToLogout = options?.url || anvilUrl;
50512
+ const usernameToLogout = options?.user;
50513
+ if (urlToLogout) {
50514
+ const normalized = normalizeAnvilUrl(urlToLogout);
50515
+ const result = await logout(normalized, usernameToLogout);
50516
+ if (result.loggedOut) if (usernameToLogout) logger_logger.success(`Logged out from ${normalized} as ${usernameToLogout}`);
50517
+ else logger_logger.success(`Logged out from ${normalized}`);
50518
+ else logger_logger.info(result.message || `Not logged in to ${normalized}. No action needed.`);
50198
50519
  } else {
50199
- const choices = [
50200
- {
50201
- name: "Logout from one account",
50202
- value: "one"
50203
- },
50204
- {
50205
- name: "Logout from all accounts",
50206
- value: "all"
50207
- },
50208
- {
50209
- name: "Cancel",
50210
- value: "cancel"
50211
- }
50212
- ];
50213
- const action = await logger_logger.select(`You have ${availableUrls.length} logged-in accounts. What would you like to do?`, choices, "one");
50214
- if ("cancel" === action) return void logger_logger.info("Logout cancelled.");
50215
- if ("all" === action) {
50520
+ const availableUrls = getAvailableAnvilUrls();
50521
+ if (0 === availableUrls.length) logger_logger.warn("No logged-in accounts found.");
50522
+ else if (1 === availableUrls.length) {
50216
50523
  const result = await logout();
50217
- if (result.loggedOut) logger_logger.success("Logged out from all accounts.");
50524
+ if (result.loggedOut) logger_logger.success("Logged out.");
50218
50525
  } else {
50219
- const urlChoices = availableUrls.map((url)=>({
50220
- name: url,
50221
- value: url
50222
- }));
50223
- urlChoices.push({
50224
- name: "Cancel",
50225
- value: null
50226
- });
50227
- const selectedUrl = await logger_logger.select("Which URL would you like to logout from?", urlChoices, availableUrls[0]);
50228
- if (null === selectedUrl) return void logger_logger.info("Logout cancelled.");
50229
- const accounts = auth_getAccountsForUrl(selectedUrl);
50230
- if (accounts.length > 1) {
50231
- const accountChoices = accounts.map((acc)=>({
50232
- name: acc,
50233
- value: acc
50234
- }));
50235
- accountChoices.push({
50236
- name: "All accounts for this URL",
50526
+ const choices = [
50527
+ {
50528
+ name: "Logout from one account",
50529
+ value: "one"
50530
+ },
50531
+ {
50532
+ name: "Logout from all accounts",
50237
50533
  value: "all"
50238
- });
50239
- accountChoices.push({
50534
+ },
50535
+ {
50536
+ name: "Cancel",
50537
+ value: "cancel"
50538
+ }
50539
+ ];
50540
+ const action = await logger_logger.select(`You have ${availableUrls.length} logged-in accounts. What would you like to do?`, choices, "one");
50541
+ if ("cancel" === action) return void logger_logger.info("Logout cancelled.");
50542
+ if ("all" === action) {
50543
+ const result = await logout();
50544
+ if (result.loggedOut) logger_logger.success("Logged out from all accounts.");
50545
+ } else {
50546
+ const urlChoices = availableUrls.map((url)=>({
50547
+ name: url,
50548
+ value: url
50549
+ }));
50550
+ urlChoices.push({
50240
50551
  name: "Cancel",
50241
50552
  value: null
50242
50553
  });
50243
- const selectedAccount = await logger_logger.select(`Which account would you like to logout from ${selectedUrl}?`, accountChoices, accounts[0]);
50244
- if (null === selectedAccount) return void logger_logger.info("Logout cancelled.");
50245
- if ("all" === selectedAccount) {
50554
+ const selectedUrl = await logger_logger.select("Which URL would you like to logout from?", urlChoices, availableUrls[0]);
50555
+ if (null === selectedUrl) return void logger_logger.info("Logout cancelled.");
50556
+ const accounts = auth_getAccountsForUrl(selectedUrl);
50557
+ if (accounts.length > 1) {
50558
+ const accountChoices = accounts.map((acc)=>({
50559
+ name: acc,
50560
+ value: acc
50561
+ }));
50562
+ accountChoices.push({
50563
+ name: "All accounts for this URL",
50564
+ value: "all"
50565
+ });
50566
+ accountChoices.push({
50567
+ name: "Cancel",
50568
+ value: null
50569
+ });
50570
+ const selectedAccount = await logger_logger.select(`Which account would you like to logout from ${selectedUrl}?`, accountChoices, accounts[0]);
50571
+ if (null === selectedAccount) return void logger_logger.info("Logout cancelled.");
50572
+ if ("all" === selectedAccount) {
50573
+ const result = await logout(selectedUrl);
50574
+ if (result.loggedOut) logger_logger.success(`Logged out from all accounts on ${selectedUrl}.`);
50575
+ else logger_logger.info(result.message || `Not logged in to ${selectedUrl}. No action needed.`);
50576
+ } else {
50577
+ const result = await logout(selectedUrl, selectedAccount);
50578
+ if (result.loggedOut) logger_logger.success(`Logged out from ${selectedUrl} as ${selectedAccount}.`);
50579
+ else logger_logger.info(result.message || `Not logged in to ${selectedUrl} as ${selectedAccount}. No action needed.`);
50580
+ }
50581
+ } else {
50246
50582
  const result = await logout(selectedUrl);
50247
- if (result.loggedOut) logger_logger.success(`Logged out from all accounts on ${selectedUrl}.`);
50583
+ if (result.loggedOut) logger_logger.success(`Logged out from ${selectedUrl}.`);
50248
50584
  else logger_logger.info(result.message || `Not logged in to ${selectedUrl}. No action needed.`);
50249
- } else {
50250
- const result = await logout(selectedUrl, selectedAccount);
50251
- if (result.loggedOut) logger_logger.success(`Logged out from ${selectedUrl} as ${selectedAccount}.`);
50252
- else logger_logger.info(result.message || `Not logged in to ${selectedUrl} as ${selectedAccount}. No action needed.`);
50253
50585
  }
50254
- } else {
50255
- const result = await logout(selectedUrl);
50256
- if (result.loggedOut) logger_logger.success(`Logged out from ${selectedUrl}.`);
50257
- else logger_logger.info(result.message || `Not logged in to ${selectedUrl}. No action needed.`);
50258
50586
  }
50259
50587
  }
50260
50588
  }
50589
+ } catch (error) {
50590
+ logger_logger.error(`Logout failed: ${error.message}`);
50591
+ process.exit(1);
50261
50592
  }
50262
50593
  });
50263
50594
  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");
@@ -50358,14 +50689,10 @@ var __webpack_exports__ = {};
50358
50689
  setLogger(new CLILogger({
50359
50690
  verbose: false
50360
50691
  }));
50361
- function showBetaBanner() {
50362
- console.log();
50363
- console.log(chalk_source.yellow("┌────────────────────────────────────────────────────────┐"));
50364
- console.log(chalk_source.yellow("│") + chalk_source.bgYellow.black.bold(" BETA ") + chalk_source.yellow(" This tool is in beta. Please report issues at: │"));
50365
- console.log(chalk_source.yellow("│") + chalk_source.gray(" https://github.com/anvil-works/anvil-cli/issues") + chalk_source.yellow(" │"));
50366
- console.log(chalk_source.yellow("└────────────────────────────────────────────────────────┘"));
50367
- console.log();
50368
- }
50692
+ process.on("unhandledRejection", (reason)=>{
50693
+ logger_logger.error(`Unexpected error: ${reason}`);
50694
+ process.exit(1);
50695
+ });
50369
50696
  async function checkVersionAndWarn() {
50370
50697
  getLatestVersion().then((latestVersion)=>{
50371
50698
  if (latestVersion && semver_default().valid(VERSION) && semver_default().valid(latestVersion) && semver_default().lt(VERSION, latestVersion)) {
@@ -50413,18 +50740,17 @@ var __webpack_exports__ = {};
50413
50740
  }
50414
50741
  }
50415
50742
  const cli_program = new Command();
50416
- cli_program.name("anvil").description("CLI tool for developing Anvil apps locally").version(VERSION, "-v, --version", "output the version number").option("--json", "Output in JSON format (NDJSON) for scripting/LLM consumption").hook("preAction", async (thisCommand, actionCommand)=>{
50743
+ cli_program.name("anvil").description("CLI tool for developing Anvil apps locally").version(VERSION, "-v, --version", "Output the version number").option("--json", "Output in JSON format (NDJSON) for scripting/LLM consumption").helpOption('-h, --help', 'Display help for anvil command').hook("preAction", async (thisCommand, actionCommand)=>{
50417
50744
  const opts = thisCommand.opts();
50418
50745
  if (opts.json) setGlobalOutputConfig({
50419
50746
  jsonMode: true
50420
50747
  });
50421
50748
  if (!opts.json) {
50422
- showBetaBanner();
50423
50749
  const commandName = actionCommand?.name();
50424
50750
  if ("update" !== commandName) checkVersionAndWarn();
50425
50751
  }
50426
50752
  });
50427
- const cli_syncCommand = registerSyncCommand(cli_program);
50753
+ const cli_watchCommand = registerWatchCommand(cli_program);
50428
50754
  registerLoginCommand(cli_program);
50429
50755
  registerLogoutCommand(cli_program);
50430
50756
  registerConfigCommand(cli_program);
@@ -50432,13 +50758,13 @@ var __webpack_exports__ = {};
50432
50758
  cli_program.command("update").description("Update anvil to the latest version").alias("u").action(async ()=>{
50433
50759
  await handleUpdateCommand();
50434
50760
  });
50435
- if (cli_syncCommand) {
50436
- const syncOptions = cli_syncCommand.options.map((opt)=>{
50761
+ if (cli_watchCommand) {
50762
+ const watchOptions = cli_watchCommand.options.map((opt)=>{
50437
50763
  const flags = opt.flags;
50438
50764
  const description = opt.description || "";
50439
50765
  return ` ${flags.padEnd(30)} ${description}`;
50440
50766
  }).join("\n");
50441
- if (syncOptions) cli_program.addHelpText("after", "\n" + chalk_source.bold("Sync Command Options:") + "\n" + chalk_source.gray(" (These options apply to the 'sync' command)") + "\n" + syncOptions + "\n");
50767
+ if (watchOptions) cli_program.addHelpText("after", "\n" + chalk_source.bold("Watch Command Options:") + "\n" + chalk_source.gray(" (These options apply to the 'watch' command)") + "\n" + watchOptions + "\n");
50442
50768
  }
50443
50769
  cli_program.parse();
50444
50770
  })();