@anvil-works/anvil-cli 0.8.0-canary.2 → 0.8.0-canary.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +3 -1
  2. package/dist/SavePathRouter.d.ts.map +1 -1
  3. package/dist/WatchSession.d.ts +2 -0
  4. package/dist/WatchSession.d.ts.map +1 -1
  5. package/dist/api.d.ts +7 -2
  6. package/dist/api.d.ts.map +1 -1
  7. package/dist/cli.js +2255 -1086
  8. package/dist/commands/checkout-picker.d.ts +1 -2
  9. package/dist/commands/checkout-picker.d.ts.map +1 -1
  10. package/dist/commands/configure.d.ts.map +1 -1
  11. package/dist/commands/db.d.ts +3 -0
  12. package/dist/commands/db.d.ts.map +1 -0
  13. package/dist/commands/deps.d.ts.map +1 -1
  14. package/dist/commands/env.d.ts +8 -0
  15. package/dist/commands/env.d.ts.map +1 -0
  16. package/dist/commands/gitAuth.d.ts +5 -1
  17. package/dist/commands/gitAuth.d.ts.map +1 -1
  18. package/dist/commands/index.d.ts +4 -1
  19. package/dist/commands/index.d.ts.map +1 -1
  20. package/dist/commands/login.d.ts.map +1 -1
  21. package/dist/commands/modelToken.d.ts +33 -0
  22. package/dist/commands/modelToken.d.ts.map +1 -0
  23. package/dist/commands/repl.d.ts +3 -0
  24. package/dist/commands/repl.d.ts.map +1 -0
  25. package/dist/commands/secret.d.ts +3 -0
  26. package/dist/commands/secret.d.ts.map +1 -0
  27. package/dist/commands/watch.d.ts.map +1 -1
  28. package/dist/componentTypeSpec.d.ts +1 -1
  29. package/dist/componentTypeSpec.d.ts.map +1 -1
  30. package/dist/errors.d.ts +11 -1
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/formTemplateValidation.d.ts.map +1 -1
  33. package/dist/index.js +1555 -1030
  34. package/dist/program.d.ts +5 -14
  35. package/dist/program.d.ts.map +1 -1
  36. package/dist/services/anvil-api.d.ts.map +1 -1
  37. package/dist/services/auth.d.ts +42 -3
  38. package/dist/services/auth.d.ts.map +1 -1
  39. package/dist/services/db.d.ts +7 -0
  40. package/dist/services/db.d.ts.map +1 -0
  41. package/dist/services/deps.d.ts +9 -4
  42. package/dist/services/deps.d.ts.map +1 -1
  43. package/dist/services/environment.d.ts +16 -0
  44. package/dist/services/environment.d.ts.map +1 -0
  45. package/dist/services/git-auth.d.ts +38 -1
  46. package/dist/services/git-auth.d.ts.map +1 -1
  47. package/dist/services/git.d.ts +1 -0
  48. package/dist/services/git.d.ts.map +1 -1
  49. package/dist/services/http.d.ts +2 -0
  50. package/dist/services/http.d.ts.map +1 -0
  51. package/dist/services/repl.d.ts +18 -0
  52. package/dist/services/repl.d.ts.map +1 -0
  53. package/dist/services/secret.d.ts +39 -0
  54. package/dist/services/secret.d.ts.map +1 -0
  55. package/dist/validateFormTemplateHtml.d.ts +6 -2
  56. package/dist/validateFormTemplateHtml.d.ts.map +1 -1
  57. package/dist/validatePython.d.ts +29 -3
  58. package/dist/validatePython.d.ts.map +1 -1
  59. package/dist/validators.d.ts +5 -1
  60. package/dist/validators.d.ts.map +1 -1
  61. package/dist/watch/SaveProcessor.d.ts +2 -0
  62. package/dist/watch/SaveProcessor.d.ts.map +1 -1
  63. package/package.json +3 -2
  64. package/dist/commands/tables.d.ts +0 -7
  65. package/dist/commands/tables.d.ts.map +0 -1
  66. package/dist/services/tables.d.ts +0 -27
  67. package/dist/services/tables.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -7376,27 +7376,29 @@ var __webpack_exports__ = {};
7376
7376
  validateCheckoutDestination: ()=>validateCheckoutDestination,
7377
7377
  DeviceAuthorizationResponse: ()=>oauth_login_namespaceObject.DeviceAuthorizationResponse,
7378
7378
  OAuthTokenResponse: ()=>oauth_login_namespaceObject.OAuthTokenResponse,
7379
+ REFRESH_REQUEST_TIMEOUT_MS: ()=>REFRESH_REQUEST_TIMEOUT_MS,
7379
7380
  WatchSession: ()=>WatchSession_WatchSession,
7380
7381
  parseConfigSetValue: ()=>parseConfigSetValue,
7381
- requestDeviceAuthorization: ()=>requestDeviceAuthorization,
7382
7382
  getGitPushUrl: ()=>anvil_api_getGitPushUrl,
7383
+ requestDeviceAuthorization: ()=>requestDeviceAuthorization,
7383
7384
  requireEnvironmentBranch: ()=>requireEnvironmentBranch,
7384
7385
  setAuthTokensForAccount: ()=>setAccountTokens,
7385
- validateAnvilApp: ()=>validation_validateAnvilApp,
7386
7386
  ValidateAppIdResponse: ()=>anvil_api_namespaceObject.ValidateAppIdResponse,
7387
- verifyAndStoreTokens: ()=>auth_verifyAndStoreTokens,
7387
+ getModelToken: ()=>getModelToken,
7388
7388
  formatValidationPath: ()=>formatValidationPath,
7389
7389
  resolveAnvilUrl: ()=>resolveAnvilUrl,
7390
+ createCheckoutPickerSource: ()=>createCheckoutPickerSource,
7390
7391
  buildGitAuthDoctorReport: ()=>buildGitAuthDoctorReport,
7391
7392
  parseCheckoutInput: ()=>parseCheckoutInput,
7392
7393
  formatGitAuthDoctorReport: ()=>formatGitAuthDoctorReport,
7393
7394
  resetConfig: ()=>resetConfig,
7395
+ MODEL_TOKEN_HELPER_FILENAME: ()=>MODEL_TOKEN_HELPER_FILENAME,
7394
7396
  FormTemplateValidationResult: ()=>validators_namespaceObject.FormTemplateValidationResult,
7395
7397
  createWatchDiagnostic: ()=>createWatchDiagnostic,
7396
7398
  ValidationTarget: ()=>validators_namespaceObject.ValidationTarget,
7397
7399
  detectAppIdsByCommitLookup: ()=>anvil_api_detectAppIdsByCommitLookup,
7398
7400
  sanitizeDirectoryName: ()=>sanitizeDirectoryName,
7399
- validatePath: ()=>validatePath,
7401
+ validateAnvilApp: ()=>validation_validateAnvilApp,
7400
7402
  LoginResult: ()=>auth_namespaceObject.LoginResult,
7401
7403
  listAppsForCheckout: ()=>listAppsForCheckout,
7402
7404
  getInstalledPreferredEditors: ()=>getInstalledPreferredEditors,
@@ -7405,8 +7407,11 @@ var __webpack_exports__ = {};
7405
7407
  CheckoutExecutionDeps: ()=>checkout_namespaceObject.CheckoutExecutionDeps,
7406
7408
  validateFormTemplateHtml: ()=>validateFormTemplateHtml,
7407
7409
  ValidateBranchSyncStatusOptions: ()=>validation_namespaceObject.ValidateBranchSyncStatusOptions,
7410
+ validatePath: ()=>validatePath,
7411
+ withRepoAuthFileLock: ()=>withRepoAuthFileLock,
7408
7412
  BranchSyncStatus: ()=>validation_namespaceObject.BranchSyncStatus,
7409
7413
  getLatestVersion: ()=>getLatestVersion,
7414
+ getModelTokenHelperScript: ()=>getModelTokenHelperScript,
7410
7415
  isDirectoryNonEmpty: ()=>isDirectoryNonEmpty,
7411
7416
  syncToLatest: ()=>src_api_syncToLatest,
7412
7417
  filterCandidates: ()=>anvil_api_filterCandidates,
@@ -7415,7 +7420,7 @@ var __webpack_exports__ = {};
7415
7420
  isPathInsideGitRepo: ()=>isPathInsideGitRepo,
7416
7421
  normalizeAnvilUrl: ()=>config_normalizeAnvilUrl,
7417
7422
  ValidationIssue: ()=>validators_namespaceObject.ValidationIssue,
7418
- writeEnvironmentPid: ()=>writeEnvironmentPid,
7423
+ verifyAndStoreTokens: ()=>auth_verifyAndStoreTokens,
7419
7424
  readEnvironmentPid: ()=>environment_readEnvironmentPid,
7420
7425
  validateFormTemplate: ()=>validateFormTemplate,
7421
7426
  preferredEditors: ()=>preferredEditors,
@@ -7427,12 +7432,14 @@ var __webpack_exports__ = {};
7427
7432
  validateBranchSyncStatus: ()=>validation_validateBranchSyncStatus,
7428
7433
  checkUncommittedChanges: ()=>api_checkUncommittedChanges,
7429
7434
  hasTokensForUrl: ()=>auth_hasTokensForUrl,
7435
+ writeEnvironmentPid: ()=>writeEnvironmentPid,
7430
7436
  AnvilYamlValidationResult: ()=>validators_namespaceObject.AnvilYamlValidationResult,
7431
7437
  ValidatePathResult: ()=>validators_namespaceObject.ValidatePathResult,
7432
7438
  getSettableConfigKeys: ()=>getSettableConfigKeys,
7433
7439
  logout: ()=>logout,
7434
7440
  TokenData: ()=>auth_namespaceObject.TokenData,
7435
7441
  detectAppIdsFromAllRemotes: ()=>anvil_api_detectAppIdsFromAllRemotes,
7442
+ executeModelTokenOperation: ()=>executeModelTokenOperation,
7436
7443
  getWebSocketUrl: ()=>getWebSocketUrl,
7437
7444
  getAllConfig: ()=>getAllConfig,
7438
7445
  isCommandAvailable: ()=>config_isCommandAvailable,
@@ -7442,6 +7449,7 @@ var __webpack_exports__ = {};
7442
7449
  getValidAuthToken: ()=>auth_getValidAuthToken,
7443
7450
  watch: ()=>src_api_watch,
7444
7451
  deleteConfig: ()=>deleteConfig,
7452
+ installModelTokenHelperScript: ()=>installModelTokenHelperScript,
7445
7453
  getGitFetchUrl: ()=>anvil_api_getGitFetchUrl,
7446
7454
  PreferredEditor: ()=>config_namespaceObject.PreferredEditor,
7447
7455
  getAvailableAnvilUrls: ()=>getAvailableAnvilUrls,
@@ -7454,8 +7462,11 @@ var __webpack_exports__ = {};
7454
7462
  CheckoutResult: ()=>checkout_namespaceObject.CheckoutResult,
7455
7463
  ParsedCheckoutInput: ()=>checkout_namespaceObject.ParsedCheckoutInput,
7456
7464
  parseCredentialHelperCommand: ()=>parseCredentialHelperCommand,
7465
+ MODEL_TOKEN_LOCK_TIMEOUT_MS: ()=>MODEL_TOKEN_LOCK_TIMEOUT_MS,
7466
+ GetValidAuthTokenOptions: ()=>auth_namespaceObject.GetValidAuthTokenOptions,
7457
7467
  clearInMemoryTokens: ()=>clearInMemoryTokens,
7458
7468
  getDefaultDestinationDirectory: ()=>getDefaultDestinationDirectory,
7469
+ AUTH_FILE_LOCK_DEFAULTS: ()=>AUTH_FILE_LOCK_DEFAULTS,
7459
7470
  lookupByCommit: ()=>lookupByCommit,
7460
7471
  AppIdCandidate: ()=>anvil_api_namespaceObject.AppIdCandidate,
7461
7472
  verifyAuth: ()=>auth_verifyAuth
@@ -7484,12 +7495,14 @@ var __webpack_exports__ = {};
7484
7495
  __webpack_require__.r(auth_namespaceObject);
7485
7496
  __webpack_require__.d(auth_namespaceObject, {
7486
7497
  fg: ()=>ANVIL_SYNC_CLIENT_ID,
7498
+ ru: ()=>REFRESH_REQUEST_TIMEOUT_MS,
7487
7499
  Ou: ()=>clearInMemoryTokens,
7488
7500
  Fr: ()=>auth_getAccountsForUrl,
7489
7501
  u5: ()=>getAvailableAnvilUrls,
7490
7502
  _p: ()=>auth_getValidAuthToken,
7491
7503
  Fi: ()=>auth_hasTokensForUrl,
7492
7504
  ri: ()=>logout,
7505
+ UE: ()=>auth_resolveAuthAnvilUrl,
7493
7506
  f5: ()=>setInMemoryTokens,
7494
7507
  iT: ()=>auth_setRepoContext,
7495
7508
  CQ: ()=>auth_verifyAndStoreTokens,
@@ -13364,9 +13377,10 @@ var __webpack_exports__ = {};
13364
13377
  type: "auth_invalid",
13365
13378
  message
13366
13379
  }),
13367
- refreshFailed: (message)=>({
13380
+ refreshFailed: (message, status)=>({
13368
13381
  type: "token_refresh_failed",
13369
- message
13382
+ message,
13383
+ status
13370
13384
  })
13371
13385
  };
13372
13386
  const createAppError = {
@@ -13582,6 +13596,9 @@ var __webpack_exports__ = {};
13582
13596
  }
13583
13597
  const external_module_namespaceObject = require("module");
13584
13598
  const requireFromHere = (0, external_module_namespaceObject.createRequire)(__filename);
13599
+ const GIT_CREDENTIAL_HELPER_FILENAME = "anvil-credential-helper.cjs";
13600
+ const MODEL_TOKEN_HELPER_FILENAME = "anvil-model-token-helper.cjs";
13601
+ const GIT_CREDENTIAL_HELPER_LOCATOR = `$(git rev-parse --git-common-dir)/${GIT_CREDENTIAL_HELPER_FILENAME}`;
13585
13602
  function getUrlConfigKey(appId) {
13586
13603
  return `anvil.auth.${appId}.url`;
13587
13604
  }
@@ -13598,12 +13615,14 @@ var __webpack_exports__ = {};
13598
13615
  const platform = options?.platform ?? process.platform;
13599
13616
  const execPath = options?.execPath ?? process.execPath;
13600
13617
  const helperPath = options.helperPath;
13618
+ const dynamicHelperPath = `"${GIT_CREDENTIAL_HELPER_LOCATOR}"`;
13601
13619
  if ("win32" === platform) {
13602
13620
  const resolvedExecPath = external_path_default().win32.resolve(execPath);
13603
- const resolvedHelperPath = external_path_default().win32.resolve(helperPath);
13604
- return `!${shellQuoteDouble(resolvedExecPath)} ${shellQuoteDouble(resolvedHelperPath)}`;
13621
+ const helperArgument = helperPath ? shellQuoteDouble(external_path_default().win32.resolve(helperPath)) : dynamicHelperPath;
13622
+ return `!${shellQuoteDouble(resolvedExecPath)} ${helperArgument}`;
13605
13623
  }
13606
- return `!${shellQuote(external_path_default().resolve(execPath))} ${shellQuote(external_path_default().resolve(helperPath))}`;
13624
+ const helperArgument = helperPath ? shellQuote(external_path_default().resolve(helperPath)) : dynamicHelperPath;
13625
+ return `!${shellQuote(external_path_default().resolve(execPath))} ${helperArgument}`;
13607
13626
  }
13608
13627
  async function getLocalConfigValue(repoPath, key) {
13609
13628
  try {
@@ -13652,21 +13671,14 @@ var __webpack_exports__ = {};
13652
13671
  return `#!/usr/bin/env node
13653
13672
  "use strict";
13654
13673
 
13655
- const fs = require("fs");
13656
- const path = require("path");
13674
+ const { execFileSync } = require("child_process");
13657
13675
  const { executeGitCredentialOperation } = require(${JSON.stringify(modulePath)});
13658
13676
 
13659
13677
  function resolveRepoPath() {
13660
- // Git's credential-helper stdin only includes the remote URL path
13661
- // (/git/APP.git), not the local checkout path. Repo-local auth lives under
13662
- // this checkout's git dir, so derive the repo path from the generated helper
13663
- // location instead of the credential request.
13664
- const gitDir = __dirname;
13665
- const linkedWorktreeGitdir = path.join(gitDir, "gitdir");
13666
- if (fs.existsSync(linkedWorktreeGitdir)) {
13667
- return path.dirname(path.resolve(gitDir, fs.readFileSync(linkedWorktreeGitdir, "utf8").trim()));
13668
- }
13669
- return path.dirname(gitDir);
13678
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
13679
+ cwd: process.cwd(),
13680
+ encoding: "utf8",
13681
+ }).trim();
13670
13682
  }
13671
13683
 
13672
13684
  Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
@@ -13676,6 +13688,38 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13676
13688
  });
13677
13689
  `;
13678
13690
  }
13691
+ function getModelTokenHelperScript(modulePath) {
13692
+ return `#!/usr/bin/env node
13693
+ "use strict";
13694
+
13695
+ const { execFileSync } = require("child_process");
13696
+ const { executeModelTokenOperation } = require(${JSON.stringify(modulePath)});
13697
+
13698
+ function resolveRepoPath() {
13699
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
13700
+ cwd: process.cwd(),
13701
+ encoding: "utf8",
13702
+ }).trim();
13703
+ }
13704
+
13705
+ Promise.resolve()
13706
+ .then(() => executeModelTokenOperation({ repoPath: resolveRepoPath() }))
13707
+ .catch((e) => {
13708
+ process.stderr.write("anvil model token helper failed: " + ((e && e.message) || e) + "\\n");
13709
+ process.exit(1);
13710
+ });
13711
+ `;
13712
+ }
13713
+ async function installModelTokenHelperScript(helperPath, options) {
13714
+ await external_fs_.promises.mkdir(external_path_default().dirname(helperPath), {
13715
+ recursive: true
13716
+ });
13717
+ await external_fs_.promises.writeFile(helperPath, getModelTokenHelperScript(options?.modulePath ?? getCredentialHelperModulePath()), {
13718
+ mode: 448
13719
+ });
13720
+ await external_fs_.promises.chmod(helperPath, 448);
13721
+ return helperPath;
13722
+ }
13679
13723
  function findPackageRoot(startPath) {
13680
13724
  let dir = external_fs_default().existsSync(startPath) && external_fs_default().statSync(startPath).isDirectory() ? startPath : external_path_default().dirname(startPath);
13681
13725
  while(true){
@@ -13701,16 +13745,22 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13701
13745
  if (isAnvilCliPackageFile(currentModulePath) && "cli.js" !== external_path_default().basename(currentModulePath)) return currentModulePath;
13702
13746
  return (options?.resolvePackageEntry ?? (()=>requireFromHere.resolve("@anvil-works/anvil-cli")))();
13703
13747
  }
13704
- async function getRepositoryGitDir(repoPath, git = esm_default(repoPath)) {
13705
- const rawGitDir = (await git.raw([
13748
+ async function getRepositoryGitPath(repoPath, revParseArg, git = esm_default(repoPath)) {
13749
+ const rawGitPath = (await git.raw([
13706
13750
  "rev-parse",
13707
- "--git-dir"
13751
+ revParseArg
13708
13752
  ])).trim();
13709
- return external_path_default().isAbsolute(rawGitDir) ? rawGitDir : external_path_default().resolve(repoPath, rawGitDir);
13753
+ return external_path_default().isAbsolute(rawGitPath) ? rawGitPath : external_path_default().resolve(repoPath, rawGitPath);
13754
+ }
13755
+ async function getRepositoryGitDir(repoPath, git = esm_default(repoPath)) {
13756
+ return getRepositoryGitPath(repoPath, "--git-dir", git);
13757
+ }
13758
+ async function getRepositoryCommonGitDir(repoPath, git = esm_default(repoPath)) {
13759
+ return getRepositoryGitPath(repoPath, "--git-common-dir", git);
13710
13760
  }
13711
13761
  async function writeCredentialHelperScript(options) {
13712
- const gitDir = await getRepositoryGitDir(options.repoPath, options.git);
13713
- const helperPath = external_path_default().join(gitDir, "anvil-credential-helper.cjs");
13762
+ const gitCommonDir = await getRepositoryCommonGitDir(options.repoPath, options.git);
13763
+ const helperPath = external_path_default().join(gitCommonDir, GIT_CREDENTIAL_HELPER_FILENAME);
13714
13764
  await external_fs_.promises.mkdir(external_path_default().dirname(helperPath), {
13715
13765
  recursive: true
13716
13766
  });
@@ -13725,7 +13775,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13725
13775
  const url = new URL(normalized);
13726
13776
  const scope = `${url.protocol}//${url.host}`;
13727
13777
  const git = esm_default(repoPath);
13728
- const helperPath = await writeCredentialHelperScript({
13778
+ await writeCredentialHelperScript({
13729
13779
  repoPath,
13730
13780
  git,
13731
13781
  modulePath: options?.modulePath
@@ -13752,7 +13802,6 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13752
13802
  `credential.${scope}.helper`,
13753
13803
  getGitCredentialHelperCommand({
13754
13804
  execPath: options?.execPath,
13755
- helperPath,
13756
13805
  platform: options?.platform
13757
13806
  })
13758
13807
  ]);
@@ -13798,6 +13847,76 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13798
13847
  function getAuthFilePath(gitDir) {
13799
13848
  return external_path_default().join(gitDir, "anvil-auth.json");
13800
13849
  }
13850
+ const AUTH_FILE_LOCK_DEFAULTS = {
13851
+ staleMs: 30000,
13852
+ timeoutMs: 60000,
13853
+ retryDelayMs: 100,
13854
+ heartbeatMs: 5000
13855
+ };
13856
+ function git_auth_delay(ms) {
13857
+ return new Promise((resolve)=>setTimeout(resolve, ms));
13858
+ }
13859
+ async function acquireAuthFileLock(lockPath, options) {
13860
+ const deadline = Date.now() + options.timeoutMs;
13861
+ while(true){
13862
+ try {
13863
+ const handle = await external_fs_.promises.open(lockPath, "wx", 384);
13864
+ try {
13865
+ await handle.writeFile(JSON.stringify({
13866
+ pid: process.pid,
13867
+ acquiredAt: new Date().toISOString()
13868
+ }));
13869
+ } finally{
13870
+ await handle.close();
13871
+ }
13872
+ return;
13873
+ } catch (e) {
13874
+ if ("EEXIST" !== e.code) throw e;
13875
+ }
13876
+ try {
13877
+ const lockStat = await external_fs_.promises.stat(lockPath);
13878
+ if (Date.now() - lockStat.mtimeMs > options.staleMs) {
13879
+ await external_fs_.promises.rm(lockPath, {
13880
+ force: true
13881
+ });
13882
+ continue;
13883
+ }
13884
+ } catch {
13885
+ continue;
13886
+ }
13887
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for the Anvil auth file lock at ${lockPath}`);
13888
+ await git_auth_delay(options.retryDelayMs);
13889
+ }
13890
+ }
13891
+ async function withRepoAuthFileLock(repoPath, fn, options) {
13892
+ const gitDir = await getRepositoryGitDir(repoPath);
13893
+ const lockPath = `${getAuthFilePath(gitDir)}.lock`;
13894
+ const lockOptions = {
13895
+ ...AUTH_FILE_LOCK_DEFAULTS,
13896
+ ...options
13897
+ };
13898
+ await external_fs_.promises.mkdir(external_path_default().dirname(lockPath), {
13899
+ recursive: true
13900
+ });
13901
+ await acquireAuthFileLock(lockPath, lockOptions);
13902
+ const heartbeat = startAuthFileLockHeartbeat(lockPath, lockOptions.heartbeatMs);
13903
+ try {
13904
+ return await fn();
13905
+ } finally{
13906
+ clearInterval(heartbeat);
13907
+ await external_fs_.promises.rm(lockPath, {
13908
+ force: true
13909
+ });
13910
+ }
13911
+ }
13912
+ function startAuthFileLockHeartbeat(lockPath, heartbeatMs) {
13913
+ const heartbeat = setInterval(()=>{
13914
+ const now = new Date();
13915
+ external_fs_.promises.utimes(lockPath, now, now).catch(()=>{});
13916
+ }, heartbeatMs);
13917
+ heartbeat.unref();
13918
+ return heartbeat;
13919
+ }
13801
13920
  function decodeKey(b64key) {
13802
13921
  const key = Buffer.from(b64key, "base64");
13803
13922
  if (32 !== key.length) throw new Error("Encryption key must be a base64-encoded 32-byte key");
@@ -13842,10 +13961,19 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13842
13961
  recursive: true
13843
13962
  });
13844
13963
  const authJson = JSON.stringify(auth, null, 2);
13845
- await external_fs_.promises.writeFile(authFilePath, encrypt(authJson, encryptionKey), {
13846
- mode: 384
13847
- });
13848
- await external_fs_.promises.chmod(authFilePath, 448);
13964
+ const tempFilePath = `${authFilePath}.tmp-${external_node_crypto_default().randomBytes(6).toString("hex")}`;
13965
+ try {
13966
+ await external_fs_.promises.writeFile(tempFilePath, encrypt(authJson, encryptionKey), {
13967
+ mode: 384
13968
+ });
13969
+ await external_fs_.promises.chmod(tempFilePath, 448);
13970
+ await external_fs_.promises.rename(tempFilePath, authFilePath);
13971
+ } catch (e) {
13972
+ await external_fs_.promises.rm(tempFilePath, {
13973
+ force: true
13974
+ });
13975
+ throw e;
13976
+ }
13849
13977
  await git.raw([
13850
13978
  "config",
13851
13979
  "--local",
@@ -13856,6 +13984,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13856
13984
  }
13857
13985
  const readAuthFromRepo = async (repoPath)=>{
13858
13986
  const git = esm_default(repoPath);
13987
+ if (!await git.checkIsRepo()) return;
13859
13988
  const gitDir = await getRepositoryGitDir(repoPath, git);
13860
13989
  const authFilePath = getAuthFilePath(gitDir);
13861
13990
  if (!external_fs_default().existsSync(authFilePath)) return;
@@ -13867,6 +13996,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13867
13996
  return resolveAnvilUrl();
13868
13997
  }
13869
13998
  const ANVIL_SYNC_CLIENT_ID = "anvil-sync";
13999
+ const REFRESH_REQUEST_TIMEOUT_MS = 12000;
13870
14000
  let inMemoryAuth = null;
13871
14001
  function setInMemoryTokens(anvilUrl, username, tokens) {
13872
14002
  inMemoryAuth = {
@@ -13885,6 +14015,11 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13885
14015
  function auth_setRepoContext(repoPath) {
13886
14016
  repoContext = repoPath;
13887
14017
  }
14018
+ async function auth_resolveAuthAnvilUrl() {
14019
+ if (inMemoryAuth) return inMemoryAuth.anvilUrl;
14020
+ const repoAuth = repoContext ? await readAuthFromRepo(repoContext) : void 0;
14021
+ return repoAuth?.anvilUrl ?? resolveAnvilUrl();
14022
+ }
13888
14023
  const inFlightRefreshes = new Map();
13889
14024
  async function auth_verifyAuth(authToken, anvilUrl = getDefaultAnvilUrl()) {
13890
14025
  try {
@@ -13905,7 +14040,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13905
14040
  throw createAuthError.invalid(`Network error: ${e.message}`);
13906
14041
  }
13907
14042
  }
13908
- async function refreshAccessToken(refreshToken, anvilUrl = getDefaultAnvilUrl(), clientId = ANVIL_SYNC_CLIENT_ID) {
14043
+ async function refreshAccessToken(refreshToken, anvilUrl = getDefaultAnvilUrl(), clientId = ANVIL_SYNC_CLIENT_ID, options) {
13909
14044
  try {
13910
14045
  const tokenResponse = await fetch(`${anvilUrl}/oauth/token`, {
13911
14046
  method: "POST",
@@ -13916,11 +14051,12 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13916
14051
  grant_type: "refresh_token",
13917
14052
  refresh_token: refreshToken,
13918
14053
  client_id: clientId
13919
- })
14054
+ }),
14055
+ signal: AbortSignal.timeout(options?.timeoutMs ?? REFRESH_REQUEST_TIMEOUT_MS)
13920
14056
  });
13921
14057
  if (!tokenResponse.ok) {
13922
14058
  const errorText = await tokenResponse.text();
13923
- throw createAuthError.refreshFailed(`Failed to refresh token: ${tokenResponse.status} ${errorText}`);
14059
+ throw createAuthError.refreshFailed(`Failed to refresh token: ${tokenResponse.status} ${errorText}`, tokenResponse.status);
13924
14060
  }
13925
14061
  const tokenData = await tokenResponse.json();
13926
14062
  return tokenData;
@@ -13937,17 +14073,16 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13937
14073
  return hasTokens(anvilUrl, username);
13938
14074
  }
13939
14075
  }
13940
- async function auth_getValidAuthToken(anvilUrl = getDefaultAnvilUrl(), username) {
14076
+ async function auth_getValidAuthToken(anvilUrl = getDefaultAnvilUrl(), username, options) {
13941
14077
  const normalized = config_normalizeAnvilUrl(anvilUrl);
13942
14078
  const source = await resolveTokenSource(normalized, username);
13943
14079
  const tokens = getSourceTokens(source);
13944
14080
  if (!tokens.authToken && !tokens.refreshToken) throw createAuthError.required("Not logged in. Please log in first.");
13945
- const isExpired = null !== tokens.authTokenExpiresAt && tokens.authTokenExpiresAt <= Math.floor(Date.now() / 1000) + 60;
13946
- if (tokens.refreshToken && isExpired) {
14081
+ if (tokens.refreshToken && isAccessTokenExpired(tokens)) {
13947
14082
  const refreshKey = getRefreshKey(normalized, username, source, tokens.refreshToken);
13948
14083
  const existingRefresh = inFlightRefreshes.get(refreshKey);
13949
14084
  if (existingRefresh) return existingRefresh;
13950
- const refreshPromise = refreshAndStoreAccessToken(normalized, username, source, tokens.refreshToken).finally(()=>{
14085
+ const refreshPromise = refreshAndStoreAccessToken(normalized, username, source, tokens.refreshToken, options).finally(()=>{
13951
14086
  inFlightRefreshes.delete(refreshKey);
13952
14087
  });
13953
14088
  inFlightRefreshes.set(refreshKey, refreshPromise);
@@ -13977,6 +14112,9 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13977
14112
  function getSourceTokens(source) {
13978
14113
  return "store" === source.type ? source.tokens : source.auth.tokens;
13979
14114
  }
14115
+ function isAccessTokenExpired(tokens) {
14116
+ return null !== tokens.authTokenExpiresAt && tokens.authTokenExpiresAt <= Math.floor(Date.now() / 1000) + 60;
14117
+ }
13980
14118
  function getStoreAccountUsername(normalizedUrl, username, refreshToken) {
13981
14119
  if (username) return username;
13982
14120
  const urlTokens = getUrlTokens(normalizedUrl);
@@ -13995,11 +14133,43 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13995
14133
  return `store:${normalizedUrl}:${source.accountUsername ?? username ?? refreshToken}`;
13996
14134
  }
13997
14135
  }
13998
- async function refreshAndStoreAccessToken(normalizedUrl, username, source, refreshToken) {
14136
+ async function refreshAndStoreAccessToken(normalizedUrl, username, source, refreshToken, options) {
14137
+ if ("repo" === source.type) return refreshRepoAuthTokens(source.repoPath, {
14138
+ anvilUrl: normalizedUrl,
14139
+ username,
14140
+ auth: source.auth,
14141
+ refreshToken,
14142
+ lock: options?.lock,
14143
+ requestTimeoutMs: options?.requestTimeoutMs
14144
+ });
14145
+ return performTokenRefresh(normalizedUrl, username, source, refreshToken, options?.requestTimeoutMs);
14146
+ }
14147
+ async function refreshRepoAuthTokens(repoPath, options) {
14148
+ const normalizedUrl = config_normalizeAnvilUrl(options.anvilUrl);
14149
+ return withRepoAuthFileLock(repoPath, async ()=>{
14150
+ let auth = options.auth;
14151
+ let refreshToken = options.refreshToken;
14152
+ const current = await readAuthFromRepo(repoPath);
14153
+ if (current && authMatches(current, normalizedUrl, options.username)) {
14154
+ if (current.tokens.authToken && !isAccessTokenExpired(current.tokens)) return current.tokens.authToken;
14155
+ auth = current;
14156
+ refreshToken = current.tokens.refreshToken ?? refreshToken;
14157
+ }
14158
+ if (!auth || !refreshToken) throw createAuthError.required("No repo auth tokens available to refresh.");
14159
+ return performTokenRefresh(normalizedUrl, options.username, {
14160
+ type: "repo",
14161
+ auth,
14162
+ repoPath
14163
+ }, refreshToken, options.requestTimeoutMs);
14164
+ }, options.lock);
14165
+ }
14166
+ async function performTokenRefresh(normalizedUrl, username, source, refreshToken, requestTimeoutMs) {
13999
14167
  try {
14000
14168
  const tokens = getSourceTokens(source);
14001
14169
  const clientId = tokens.clientId ?? ANVIL_SYNC_CLIENT_ID;
14002
- const tokenData = await refreshAccessToken(refreshToken, normalizedUrl, clientId);
14170
+ const tokenData = await refreshAccessToken(refreshToken, normalizedUrl, clientId, {
14171
+ timeoutMs: requestTimeoutMs
14172
+ });
14003
14173
  const newTokens = {
14004
14174
  authToken: tokenData.access_token,
14005
14175
  refreshToken: tokenData.refresh_token,
@@ -14021,10 +14191,26 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
14021
14191
  }
14022
14192
  return tokenData.access_token;
14023
14193
  } catch (e) {
14194
+ if ("repo" === source.type && isRefreshTokenRejection(e)) await clearRepoAuthTokens(source.repoPath, source.auth).catch(()=>{});
14024
14195
  await clearTokensForAccount(normalizedUrl, username, refreshToken);
14025
14196
  throw e;
14026
14197
  }
14027
14198
  }
14199
+ function isRefreshTokenRejection(e) {
14200
+ const error = e;
14201
+ return error?.type === "token_refresh_failed" && (400 === error.status || 401 === error.status);
14202
+ }
14203
+ async function clearRepoAuthTokens(repoPath, auth) {
14204
+ const encryptionKey = process.env["ANVIL_AUTH_FILE_ENCRYPTION_KEY"];
14205
+ if (!encryptionKey) return;
14206
+ auth.tokens = {
14207
+ authToken: null,
14208
+ refreshToken: null,
14209
+ authTokenExpiresAt: null,
14210
+ clientId: auth.tokens.clientId
14211
+ };
14212
+ await writeAuthToFile(repoPath, auth, encryptionKey);
14213
+ }
14028
14214
  async function clearTokensForAccount(url, username, refreshToken) {
14029
14215
  if (inMemoryAuth || repoContext && await readAuthFromRepo(repoContext)) return;
14030
14216
  {
@@ -14106,630 +14292,65 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
14106
14292
  return getAvailableUrls();
14107
14293
  }
14108
14294
  }
14109
- function anvil_api_getDefaultAnvilUrl() {
14110
- return resolveAnvilUrl();
14295
+ const promises_namespaceObject = require("timers/promises");
14296
+ const WANT_NOT_VALID_RE = /\bwant\s+[0-9a-f]{7,40}\s+not valid\b/i;
14297
+ const FETCH_RETRY_DELAY_MS = 200;
14298
+ function isWantNotValidError(error) {
14299
+ return WANT_NOT_VALID_RE.test(error.message || "");
14111
14300
  }
14112
- async function anvil_api_validateAppId(appId, anvilUrl = anvil_api_getDefaultAnvilUrl(), username) {
14113
- const authToken = await auth_getValidAuthToken(anvilUrl, username);
14114
- try {
14115
- const resp = await fetch(`${anvilUrl}/ide/api/_/apps/validate-app-id`, {
14116
- method: "POST",
14117
- headers: {
14118
- Authorization: `Bearer ${authToken}`,
14119
- "Content-Type": "application/json"
14120
- },
14121
- body: JSON.stringify({
14122
- app_id: appId
14123
- })
14124
- });
14125
- const data = await resp.json();
14126
- if (resp.ok) return {
14127
- valid: data.valid ?? false,
14128
- app_name: data.app_name
14129
- };
14130
- if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14131
- if (403 === resp.status) throw createAppError.accessDenied(appId);
14132
- if (404 === resp.status) throw createAppError.notFound(appId);
14133
- logger_logger.debug(`validate-app-id failed: ${resp.status} ${resp.statusText}`);
14134
- return {
14135
- valid: false,
14136
- error: data.error || `Server error: ${resp.status}`
14137
- };
14138
- } catch (error) {
14139
- if (error.type) throw error;
14140
- throw createNetworkError.network(error.message);
14301
+ class git_GitService extends Emitter {
14302
+ git;
14303
+ repoPath;
14304
+ static INDEX_LOCK_RETRY_DELAYS_MS = [
14305
+ 25,
14306
+ 50,
14307
+ 100,
14308
+ 200
14309
+ ];
14310
+ constructor(repoPath){
14311
+ super();
14312
+ this.repoPath = external_path_default().resolve(repoPath);
14313
+ this.git = esm_default(this.repoPath);
14141
14314
  }
14142
- }
14143
- async function lookupByCommit(commitId, branchName, anvilUrl = anvil_api_getDefaultAnvilUrl(), username) {
14144
- const authToken = await auth_getValidAuthToken(anvilUrl, username);
14145
- try {
14146
- const resp = await fetch(`${anvilUrl}/ide/api/_/apps/lookup-by-commit`, {
14147
- method: "POST",
14148
- headers: {
14149
- Authorization: `Bearer ${authToken}`,
14150
- "Content-Type": "application/json"
14151
- },
14152
- body: JSON.stringify({
14153
- commit_id: commitId,
14154
- branch_name: branchName
14155
- })
14156
- });
14157
- if (!resp.ok) {
14158
- if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14159
- throw createNetworkError.server(resp.status, resp.statusText);
14315
+ getGit() {
14316
+ return this.git;
14317
+ }
14318
+ getRepoPath() {
14319
+ return this.repoPath;
14320
+ }
14321
+ async getGitDir() {
14322
+ try {
14323
+ const gitDir = (await this.git.revparse([
14324
+ "--git-dir"
14325
+ ])).trim();
14326
+ if (!external_path_default().isAbsolute(gitDir)) return external_path_default().resolve(this.repoPath, gitDir);
14327
+ return gitDir;
14328
+ } catch (e) {
14329
+ throw errors_createGitError.commandFailed("rev-parse --git-dir", e.message);
14160
14330
  }
14161
- const data = await resp.json();
14162
- return data;
14163
- } catch (error) {
14164
- if (error.type) throw error;
14165
- throw createNetworkError.network(error.message);
14166
14331
  }
14167
- }
14168
- async function listAppsForCheckout(options = {}) {
14169
- const anvilUrl = options.anvilUrl ?? anvil_api_getDefaultAnvilUrl();
14170
- const authToken = await auth_getValidAuthToken(anvilUrl, options.username);
14171
- const params = new URLSearchParams();
14172
- if ("number" == typeof options.limit) params.set("limit", String(options.limit));
14173
- if (options.cursor) params.set("cursor", options.cursor);
14174
- if (options.q && options.q.trim()) params.set("q", options.q.trim());
14175
- const query = params.toString();
14176
- const url = `${anvilUrl}/ide/api/_/apps${query ? `?${query}` : ""}`;
14177
- try {
14178
- const resp = await fetch(url, {
14179
- method: "GET",
14180
- headers: {
14181
- Authorization: `Bearer ${authToken}`
14182
- },
14183
- signal: options.signal
14184
- });
14185
- if (!resp.ok) {
14186
- if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14187
- throw createNetworkError.server(resp.status, resp.statusText);
14332
+ async getCurrentBranch() {
14333
+ try {
14334
+ const branchRef = await this.git.revparse([
14335
+ "--abbrev-ref",
14336
+ "HEAD"
14337
+ ]);
14338
+ if ("HEAD" === branchRef) throw createSyncError.detachedHead();
14339
+ return branchRef;
14340
+ } catch (e) {
14341
+ if ("detached_head" === e.type) throw e;
14342
+ throw createSyncError.detachedHead();
14188
14343
  }
14189
- const data = await resp.json();
14190
- return {
14191
- apps: Array.isArray(data.apps) ? data.apps : [],
14192
- next_cursor: "string" == typeof data.next_cursor ? data.next_cursor : null
14193
- };
14194
- } catch (error) {
14195
- if (error.type) throw error;
14196
- throw createNetworkError.network(error.message);
14197
14344
  }
14198
- }
14199
- async function saveUpdates(appId, branchName, payload, anvilUrl = anvil_api_getDefaultAnvilUrl(), authToken) {
14200
- try {
14201
- const resp = await fetch(`${anvilUrl}/ide/api/_/apps/${appId}/update?make_fresh_version=false&branch=${branchName}`, {
14202
- method: "PUT",
14203
- headers: {
14204
- Authorization: `Bearer ${authToken}`,
14205
- "Content-Type": "application/json"
14206
- },
14207
- body: JSON.stringify(payload)
14208
- });
14209
- const data = await resp.json();
14210
- if (401 === resp.status) throw createAuthError.invalid(`Authentication failed (401): ${data.error || "unauthorized"}`);
14211
- if (403 === resp.status) throw createAuthError.invalid(`Authentication failed (403): ${data.error || "unauthorized"}\n This may indicate:\n - The OAuth token doesn't have permission to write to app ${appId}\n - The app belongs to a different user or organization\n - The OAuth token is missing required scopes (apps:write)`);
14212
- return {
14213
- version: data.version,
14214
- overtaken: data.overtaken,
14215
- latest_version: data.latest_version,
14216
- error: 200 !== resp.status ? data.error || `Status ${resp.status}` : void 0
14217
- };
14218
- } catch (error) {
14219
- if (error.type) throw error;
14220
- throw createNetworkError.network(error.message);
14221
- }
14222
- }
14223
- function anvil_api_getGitFetchUrl(appId, authToken, gitUrl = anvil_api_getDefaultAnvilUrl()) {
14224
- const url = new URL(gitUrl);
14225
- const encodedToken = encodeURIComponent(authToken);
14226
- return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git?no_freeze=true&q=`;
14227
- }
14228
- function anvil_api_getGitPushUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
14229
- const url = new URL(anvilUrl);
14230
- const encodedToken = encodeURIComponent(authToken);
14231
- return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git`;
14232
- }
14233
- function getWebSocketUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
14234
- return anvilUrl.replace(/^http/, "ws") + `/ide/api/_/apps/${appId}/ws?access_token=${authToken}`;
14235
- }
14236
- async function getLatestVersion() {
14237
- try {
14238
- const response = await fetch("https://registry.npmjs.org/@anvil-works/anvil-cli/latest");
14239
- if (!response.ok) return null;
14240
- const data = await response.json();
14241
- return data.version;
14242
- } catch (e) {
14243
- return null;
14244
- }
14245
- }
14246
- function anvil_api_filterCandidates(candidates, explicitUrl, explicitUsername) {
14247
- let filtered = candidates;
14248
- if (explicitUrl) {
14249
- const normalizedExplicit = config_normalizeAnvilUrl(explicitUrl);
14250
- filtered = filtered.filter((c)=>c.detectedUrl && config_normalizeAnvilUrl(c.detectedUrl) === normalizedExplicit);
14251
- }
14252
- if (explicitUsername) filtered = filtered.filter((c)=>!c.detectedUsername || c.detectedUsername === explicitUsername);
14253
- return filtered;
14254
- }
14255
- function anvil_api_formatCandidateLabel(candidate) {
14256
- const parts = [
14257
- candidate.appId
14258
- ];
14259
- if (candidate.detectedUrl) if (candidate.detectedUsername) parts.push(`(${candidate.detectedUsername} on ${candidate.detectedUrl})`);
14260
- else parts.push(`(${candidate.detectedUrl})`);
14261
- parts.push(`- ${candidate.description}`);
14262
- return parts.join(" ");
14263
- }
14264
- function anvil_api_lookupRemoteInfoForAppId(appId, detectedRemotes) {
14265
- const matches = detectedRemotes.filter((c)=>c.appId === appId);
14266
- if (0 === matches.length) return {};
14267
- const withUsername = matches.find((c)=>c.detectedUsername);
14268
- if (withUsername) return {
14269
- detectedUrl: withUsername.detectedUrl,
14270
- detectedUsername: withUsername.detectedUsername
14271
- };
14272
- return {
14273
- detectedUrl: matches[0].detectedUrl,
14274
- detectedUsername: matches[0].detectedUsername
14275
- };
14276
- }
14277
- async function anvil_api_detectAppIdsFromAllRemotes(repoPath) {
14278
- const git = esm_default(repoPath);
14279
- const out = [];
14280
- try {
14281
- const remotes = await git.getRemotes(true);
14282
- for (const remote of remotes){
14283
- const httpMatch = remote.refs.fetch?.match(/(?:http|https):\/\/(?:[^@]+@)?([^:\/]+)(?::\d+)?\/git\/([A-Z0-9]+)\.git/);
14284
- if (httpMatch) {
14285
- const [, host, detectedAppId] = httpMatch;
14286
- out.push({
14287
- appId: detectedAppId,
14288
- source: "remote",
14289
- description: `Git remote '${remote.name}'`,
14290
- detectedUrl: config_normalizeAnvilUrl(host)
14291
- });
14292
- continue;
14293
- }
14294
- const sshMatch = remote.refs.fetch?.match(/ssh:\/\/([^@]+)@([^:]+):(\d+)\/(?:git\/)?([A-Z0-9]+)\.git/);
14295
- if (sshMatch) {
14296
- const [, usernamePart, host, , detectedAppId] = sshMatch;
14297
- const detectedUsername = usernamePart.includes("%") ? decodeURIComponent(usernamePart) : usernamePart;
14298
- const finalUsername = "git" !== detectedUsername ? detectedUsername : void 0;
14299
- out.push({
14300
- appId: detectedAppId,
14301
- source: "remote",
14302
- description: `Git remote '${remote.name}' (SSH)`,
14303
- detectedUrl: config_normalizeAnvilUrl(host),
14304
- detectedUsername: finalUsername
14305
- });
14306
- }
14307
- }
14308
- } catch (_e) {}
14309
- return out;
14310
- }
14311
- async function anvil_api_detectAppIdsByCommitLookup(repoPath, options) {
14312
- const anvilUrl = options.anvilUrl || resolveAnvilUrl();
14313
- const username = options.username;
14314
- const git = esm_default(repoPath);
14315
- const out = [];
14316
- try {
14317
- const authToken = await auth_getValidAuthToken(anvilUrl, username);
14318
- const branchRef = await git.revparse([
14319
- "--abbrev-ref",
14320
- "HEAD"
14321
- ]);
14322
- const currentBranch = "HEAD" === branchRef ? "master" : branchRef;
14323
- const commitId = (await git.revparse([
14324
- "HEAD"
14325
- ]))?.trim();
14326
- if (!commitId) return out;
14327
- const resp = await fetch(`${anvilUrl}/ide/api/_/apps/lookup-by-commit`, {
14328
- method: "POST",
14329
- headers: {
14330
- Authorization: `Bearer ${authToken}`,
14331
- "Content-Type": "application/json"
14332
- },
14333
- body: JSON.stringify({
14334
- commit_id: commitId,
14335
- branch_name: currentBranch
14336
- })
14337
- });
14338
- logger_logger.debug(`Reverse lookup by commit ${commitId} on branch ${currentBranch} returned ${resp.status}`);
14339
- if (!resp.ok) {
14340
- const errorBody = await resp.text();
14341
- logger_logger.debug(`lookup-by-commit error: ${errorBody}`);
14342
- }
14343
- if (resp.ok) {
14344
- const data = await resp.json();
14345
- logger_logger.debug(`data ${JSON.stringify(data)}`);
14346
- const apps = data?.apps ?? [];
14347
- for (const app of apps)if (app?.app_id) out.push({
14348
- appId: app.app_id,
14349
- source: "remote",
14350
- description: `Reverse lookup by commit${app.branch ? ` on branch '${app.branch}'` : ""}`
14351
- });
14352
- }
14353
- } catch (_e) {}
14354
- return out;
14355
- }
14356
- const promises_namespaceObject = require("fs/promises");
14357
- var promises_default = /*#__PURE__*/ __webpack_require__.n(promises_namespaceObject);
14358
- function normalizeLineEndings(content) {
14359
- return content.replace(/\r\n/g, "\n");
14360
- }
14361
- function pythonifyName(name) {
14362
- return name.replace(/[^A-z0-9]/g, "_").replace(/^[0-9]/, "_$&");
14363
- }
14364
- function extractPythonName(relativePath, skipParts = 1) {
14365
- const parts = relativePath.slice(0, -3).split("/");
14366
- const isPackage = relativePath.endsWith("__init__.py");
14367
- const relevantParts = isPackage ? parts.slice(skipParts, -1) : parts.slice(skipParts);
14368
- return relevantParts.map(pythonifyName).join(".");
14369
- }
14370
- function deepEqual(a, b) {
14371
- if (a === b) return true;
14372
- if (null == a || null == b) return false;
14373
- if (typeof a !== typeof b) return false;
14374
- if ("object" != typeof a) return a === b;
14375
- if (Array.isArray(a) !== Array.isArray(b)) return false;
14376
- if (Array.isArray(a)) {
14377
- if (a.length !== b.length) return false;
14378
- return a.every((item, index)=>deepEqual(item, b[index]));
14379
- }
14380
- const keysA = Object.keys(a);
14381
- const keysB = Object.keys(b);
14382
- if (keysA.length !== keysB.length) return false;
14383
- return keysA.every((key)=>deepEqual(a[key], b[key]));
14384
- }
14385
- function parseHtmlWithFrontmatter(htmlContent) {
14386
- const frontmatterPattern = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
14387
- const match = htmlContent.match(frontmatterPattern);
14388
- if (match) {
14389
- const yamlStr = match[1];
14390
- const html = match[2];
14391
- const frontmatter = yamlStr ? external_js_yaml_default().load(yamlStr) ?? {} : {};
14392
- return {
14393
- frontmatter,
14394
- html
14395
- };
14396
- }
14397
- return {
14398
- frontmatter: {},
14399
- html: htmlContent
14400
- };
14401
- }
14402
- const ANVIL_GITIGNORE_ENTRY = "/.anvil/";
14403
- async function ensureProjectGitignoreIgnoresAnvilDir(projectRoot) {
14404
- const rootGitignorePath = external_path_default().join(projectRoot, ".gitignore");
14405
- try {
14406
- const existing = await external_fs_.promises.readFile(rootGitignorePath, "utf8");
14407
- if (existing.includes(ANVIL_GITIGNORE_ENTRY)) return false;
14408
- const prefix = existing.endsWith("\n") || 0 === existing.length ? "" : "\n";
14409
- await external_fs_.promises.writeFile(rootGitignorePath, `${existing}${prefix}${ANVIL_GITIGNORE_ENTRY}\n`, "utf8");
14410
- return true;
14411
- } catch (error) {
14412
- if ("ENOENT" !== error.code) throw error;
14413
- await external_fs_.promises.writeFile(rootGitignorePath, `${ANVIL_GITIGNORE_ENTRY}\n`, "utf8");
14414
- return true;
14415
- }
14416
- }
14417
- const ANVIL_DEPS_DIR = ".anvil/deps";
14418
- const ANVIL_DEPS_MANIFEST = "manifest.json";
14419
- const DEPS_MANIFEST_VERSION = 1;
14420
- function formatFetchError(prefix, status, errorBody) {
14421
- if (!errorBody) return `${prefix}: ${status}`;
14422
- try {
14423
- const parsed = JSON.parse(errorBody);
14424
- if ("string" == typeof parsed.error && parsed.error.trim()) return `${prefix}: ${parsed.error} (${status})`;
14425
- } catch {}
14426
- return `${prefix}: ${status} ${errorBody}`;
14427
- }
14428
- async function fetchAllDependencies(appId, anvilUrl = resolveAnvilUrl(), options = {}) {
14429
- const { commit, includeDocs = true, knownDependencies = [] } = options;
14430
- const token = await auth_getValidAuthToken(anvilUrl);
14431
- const body = {
14432
- include_docs: includeDocs,
14433
- known_dependencies: knownDependencies
14434
- };
14435
- if (commit) body.commit = commit;
14436
- const resp = await fetch(`${anvilUrl}/ide/api/_/apps/${appId}/resolved-dependencies`, {
14437
- method: "POST",
14438
- headers: {
14439
- Authorization: `Bearer ${token}`,
14440
- "Content-Type": "application/json"
14441
- },
14442
- body: JSON.stringify(body)
14443
- });
14444
- if (!resp.ok) {
14445
- const errorBody = await resp.text();
14446
- throw new Error(formatFetchError("Failed to fetch dependencies", resp.status, errorBody));
14447
- }
14448
- return await resp.json();
14449
- }
14450
- async function refreshDependencyCache(projectRoot, appId, anvilUrl = resolveAnvilUrl(), options = {}) {
14451
- const includeDocs = options.includeDocs ?? true;
14452
- const knownDependencies = await getKnownDependenciesForCache(projectRoot, includeDocs);
14453
- const response = await fetchAllDependencies(appId, anvilUrl, {
14454
- commit: options.commit,
14455
- includeDocs,
14456
- knownDependencies
14457
- });
14458
- if (!response.resolved_dependencies.length) {
14459
- await clearDepsCache(projectRoot);
14460
- return {
14461
- response,
14462
- addedGitignoreEntry: false
14463
- };
14464
- }
14465
- const addedGitignoreEntry = await ensureProjectGitignoreIgnoresAnvilDir(projectRoot);
14466
- await writeAllDepsToCache(projectRoot, response, {
14467
- includeDocs
14468
- });
14469
- return {
14470
- response,
14471
- addedGitignoreEntry
14472
- };
14473
- }
14474
- function depsDir(projectRoot) {
14475
- return external_path_default().join(projectRoot, ANVIL_DEPS_DIR);
14476
- }
14477
- function depsManifestPath(projectRoot) {
14478
- return external_path_default().join(depsDir(projectRoot), ANVIL_DEPS_MANIFEST);
14479
- }
14480
- function isResolvedDependencyManifestEntry(value) {
14481
- if (!value || "object" != typeof value) return false;
14482
- const entry = value;
14483
- return "string" == typeof entry.app_id && entry.app_id.length > 0 && "string" == typeof entry.commit_id && entry.commit_id.length > 0 && (void 0 === entry.branch || "string" == typeof entry.branch);
14484
- }
14485
- function isCachedDependencyManifestEntry(value) {
14486
- if (!isResolvedDependencyManifestEntry(value)) return false;
14487
- const entry = value;
14488
- return "string" == typeof entry.package_name && entry.package_name.length > 0;
14489
- }
14490
- function isDepsCacheManifest(value) {
14491
- if (!value || "object" != typeof value) return false;
14492
- const manifest = value;
14493
- return manifest.version === DEPS_MANIFEST_VERSION && "boolean" == typeof manifest.includeDocs && Array.isArray(manifest.resolved_dependencies) && manifest.resolved_dependencies.every(isResolvedDependencyManifestEntry) && Array.isArray(manifest.cached_dependencies) && manifest.cached_dependencies.every(isCachedDependencyManifestEntry);
14494
- }
14495
- async function readDepsCacheManifest(projectRoot) {
14496
- try {
14497
- const raw = await promises_default().readFile(depsManifestPath(projectRoot), "utf-8");
14498
- const parsed = JSON.parse(raw);
14499
- return isDepsCacheManifest(parsed) ? parsed : void 0;
14500
- } catch {
14501
- return;
14502
- }
14503
- }
14504
- async function getKnownDependenciesForCache(projectRoot, includeDocs) {
14505
- const manifest = await readDepsCacheManifest(projectRoot);
14506
- if (!manifest || manifest.includeDocs !== includeDocs) return [];
14507
- return manifest.resolved_dependencies.map(({ app_id, commit_id })=>({
14508
- app_id,
14509
- commit_id
14510
- }));
14511
- }
14512
- function getDependencyWatchSubscriptionsFromManifest(manifest) {
14513
- if (!manifest) return [];
14514
- const cachedByAppId = new Map(manifest.cached_dependencies.map((dep)=>[
14515
- dep.app_id,
14516
- dep
14517
- ]));
14518
- return manifest.resolved_dependencies.flatMap((dep)=>{
14519
- if (!dep.branch) return [];
14520
- return [
14521
- {
14522
- appId: dep.app_id,
14523
- branch: dep.branch,
14524
- packageName: cachedByAppId.get(dep.app_id)?.package_name
14525
- }
14526
- ];
14527
- });
14528
- }
14529
- function normalizeAnvilYamlDependenciesFromContent(content) {
14530
- const parsed = external_js_yaml_default().load(content);
14531
- if (!parsed || "object" != typeof parsed || !Array.isArray(parsed.dependencies)) return [];
14532
- return normalizeAnvilYamlDependenciesValue(parsed.dependencies);
14533
- }
14534
- function normalizeAnvilYamlDependenciesValue(value) {
14535
- if (!Array.isArray(value)) return [];
14536
- return normalizeForComparison(value);
14537
- }
14538
- async function readAnvilYamlDependencies(projectRoot) {
14539
- try {
14540
- const content = await promises_default().readFile(external_path_default().join(projectRoot, "anvil.yaml"), "utf-8");
14541
- return normalizeAnvilYamlDependenciesFromContent(content);
14542
- } catch {
14543
- return [];
14544
- }
14545
- }
14546
- function anvilYamlDependenciesEqual(left, right) {
14547
- return deepEqual(left, right);
14548
- }
14549
- function normalizeForComparison(value) {
14550
- if (Array.isArray(value)) return value.map(normalizeForComparison);
14551
- if (value && "object" == typeof value) return Object.fromEntries(Object.entries(value).sort(([left], [right])=>left.localeCompare(right)).map(([key, entryValue])=>[
14552
- key,
14553
- normalizeForComparison(entryValue)
14554
- ]));
14555
- return value;
14556
- }
14557
- function cachedManifestEntry(dep, packageName) {
14558
- return {
14559
- app_id: dep.app_id,
14560
- commit_id: dep.commit_id,
14561
- ...dep.branch ? {
14562
- branch: dep.branch
14563
- } : {},
14564
- package_name: packageName
14565
- };
14566
- }
14567
- function safeDependencyPath(filePath, kind) {
14568
- const normalizedPath = external_path_default().normalize(filePath);
14569
- if (external_path_default().isAbsolute(normalizedPath) || normalizedPath.startsWith("..")) throw new Error(`Invalid dependency ${kind} path: ${filePath}`);
14570
- return normalizedPath;
14571
- }
14572
- async function writeDepToCache(projectRoot, dep) {
14573
- const depDir = external_path_default().join(projectRoot, ANVIL_DEPS_DIR, dep.package_name);
14574
- await promises_default().rm(depDir, {
14575
- recursive: true,
14576
- force: true
14577
- });
14578
- await promises_default().mkdir(external_path_default().join(depDir, "client_code"), {
14579
- recursive: true
14580
- });
14581
- await promises_default().mkdir(external_path_default().join(depDir, "server_code"), {
14582
- recursive: true
14583
- });
14584
- await promises_default().writeFile(external_path_default().join(depDir, "anvil.yaml"), external_js_yaml_default().dump({
14585
- package_name: dep.package_name
14586
- }));
14587
- for (const mod of dep.modules ?? [])if (mod.is_package) {
14588
- const modDir = external_path_default().join(depDir, "client_code", mod.name);
14589
- await promises_default().mkdir(modDir, {
14590
- recursive: true
14591
- });
14592
- await promises_default().writeFile(external_path_default().join(modDir, "__init__.py"), mod.code);
14593
- } else await promises_default().writeFile(external_path_default().join(depDir, "client_code", `${mod.name}.py`), mod.code);
14594
- for (const form of dep.forms ?? []){
14595
- const { code, class_name, is_package, ...formTemplate } = form;
14596
- if (is_package) {
14597
- const formDir = external_path_default().join(depDir, "client_code", class_name);
14598
- await promises_default().mkdir(formDir, {
14599
- recursive: true
14600
- });
14601
- await promises_default().writeFile(external_path_default().join(formDir, "__init__.py"), code);
14602
- await promises_default().writeFile(external_path_default().join(formDir, "form_template.yaml"), external_js_yaml_default().dump(formTemplate));
14603
- } else {
14604
- await promises_default().writeFile(external_path_default().join(depDir, "client_code", `${class_name}.py`), code);
14605
- await promises_default().writeFile(external_path_default().join(depDir, "client_code", `${class_name}.yaml`), external_js_yaml_default().dump(formTemplate));
14606
- }
14607
- }
14608
- for (const mod of dep.server_modules ?? [])if (mod.is_package) {
14609
- const modDir = external_path_default().join(depDir, "server_code", mod.name);
14610
- await promises_default().mkdir(modDir, {
14611
- recursive: true
14612
- });
14613
- await promises_default().writeFile(external_path_default().join(modDir, "__init__.py"), mod.code);
14614
- } else await promises_default().writeFile(external_path_default().join(depDir, "server_code", `${mod.name}.py`), mod.code);
14615
- for (const asset of dep.assets ?? []){
14616
- const targetPath = external_path_default().join(depDir, "theme", "assets", safeDependencyPath(asset.name, "asset"));
14617
- await promises_default().mkdir(external_path_default().dirname(targetPath), {
14618
- recursive: true
14619
- });
14620
- await promises_default().writeFile(targetPath, Buffer.from(asset.content, "base64"));
14621
- }
14622
- for (const doc of dep.docs ?? []){
14623
- const targetPath = external_path_default().join(depDir, safeDependencyPath(doc.path, "doc"));
14624
- await promises_default().mkdir(external_path_default().dirname(targetPath), {
14625
- recursive: true
14626
- });
14627
- await promises_default().writeFile(targetPath, doc.content);
14628
- }
14629
- }
14630
- async function writeAllDepsToCache(projectRoot, response, options = {}) {
14631
- const includeDocs = options.includeDocs ?? true;
14632
- const manifest = await readDepsCacheManifest(projectRoot);
14633
- const canReuseExistingCache = manifest?.includeDocs === includeDocs;
14634
- if (!canReuseExistingCache) await clearDepsCache(projectRoot);
14635
- const previousCached = canReuseExistingCache ? manifest.cached_dependencies : [];
14636
- const resolvedAppIds = new Set(response.resolved_dependencies.map((dep)=>dep.app_id));
14637
- for (const dep of previousCached)if (!resolvedAppIds.has(dep.app_id)) await promises_default().rm(external_path_default().join(projectRoot, ANVIL_DEPS_DIR, dep.package_name), {
14638
- recursive: true,
14639
- force: true
14640
- });
14641
- for (const dep of response.dependencies)await writeDepToCache(projectRoot, dep);
14642
- const returnedByAppId = new Map(response.dependencies.map((dep)=>[
14643
- dep.app_id,
14644
- dep
14645
- ]));
14646
- const previousByAppId = new Map(previousCached.map((dep)=>[
14647
- dep.app_id,
14648
- dep
14649
- ]));
14650
- const cachedDependencies = response.resolved_dependencies.map((dep)=>{
14651
- const returned = returnedByAppId.get(dep.app_id);
14652
- if (returned) return cachedManifestEntry(returned, returned.package_name);
14653
- const previous = previousByAppId.get(dep.app_id);
14654
- if (!previous) return;
14655
- return cachedManifestEntry(dep, previous.package_name);
14656
- }).filter((dep)=>void 0 !== dep);
14657
- const newManifest = {
14658
- version: DEPS_MANIFEST_VERSION,
14659
- includeDocs,
14660
- resolved_dependencies: response.resolved_dependencies,
14661
- cached_dependencies: cachedDependencies
14662
- };
14663
- await promises_default().mkdir(depsDir(projectRoot), {
14664
- recursive: true
14665
- });
14666
- await promises_default().writeFile(depsManifestPath(projectRoot), `${JSON.stringify(newManifest, null, 2)}\n`);
14667
- }
14668
- async function clearDepsCache(projectRoot) {
14669
- await promises_default().rm(depsDir(projectRoot), {
14670
- recursive: true,
14671
- force: true
14672
- });
14673
- }
14674
- const external_timers_promises_namespaceObject = require("timers/promises");
14675
- const WANT_NOT_VALID_RE = /\bwant\s+[0-9a-f]{7,40}\s+not valid\b/i;
14676
- const FETCH_RETRY_DELAY_MS = 200;
14677
- function isWantNotValidError(error) {
14678
- return WANT_NOT_VALID_RE.test(error.message || "");
14679
- }
14680
- class git_GitService extends Emitter {
14681
- git;
14682
- repoPath;
14683
- static INDEX_LOCK_RETRY_DELAYS_MS = [
14684
- 25,
14685
- 50,
14686
- 100,
14687
- 200
14688
- ];
14689
- constructor(repoPath){
14690
- super();
14691
- this.repoPath = external_path_default().resolve(repoPath);
14692
- this.git = esm_default(this.repoPath);
14693
- }
14694
- getGit() {
14695
- return this.git;
14696
- }
14697
- getRepoPath() {
14698
- return this.repoPath;
14699
- }
14700
- async getGitDir() {
14701
- try {
14702
- const gitDir = (await this.git.revparse([
14703
- "--git-dir"
14704
- ])).trim();
14705
- if (!external_path_default().isAbsolute(gitDir)) return external_path_default().resolve(this.repoPath, gitDir);
14706
- return gitDir;
14707
- } catch (e) {
14708
- throw errors_createGitError.commandFailed("rev-parse --git-dir", e.message);
14709
- }
14710
- }
14711
- async getCurrentBranch() {
14712
- try {
14713
- const branchRef = await this.git.revparse([
14714
- "--abbrev-ref",
14715
- "HEAD"
14716
- ]);
14717
- if ("HEAD" === branchRef) throw createSyncError.detachedHead();
14718
- return branchRef;
14719
- } catch (e) {
14720
- if ("detached_head" === e.type) throw e;
14721
- throw createSyncError.detachedHead();
14722
- }
14723
- }
14724
- async getCommitId() {
14725
- try {
14726
- const commitId = (await this.git.revparse([
14727
- "HEAD"
14728
- ])).trim();
14729
- return commitId;
14730
- } catch (e) {
14731
- throw errors_createGitError.commandFailed("revparse", e.message);
14732
- }
14345
+ async getCommitId() {
14346
+ try {
14347
+ const commitId = (await this.git.revparse([
14348
+ "HEAD"
14349
+ ])).trim();
14350
+ return commitId;
14351
+ } catch (e) {
14352
+ throw errors_createGitError.commandFailed("revparse", e.message);
14353
+ }
14733
14354
  }
14734
14355
  async getCommitInfo() {
14735
14356
  try {
@@ -14767,359 +14388,987 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
14767
14388
  renamed: status.renamed || []
14768
14389
  };
14769
14390
  } catch (e) {
14770
- throw errors_createGitError.commandFailed("status", e.message);
14391
+ throw errors_createGitError.commandFailed("status", e.message);
14392
+ }
14393
+ }
14394
+ async discardModeOnlyChanges(paths) {
14395
+ const discarded = [];
14396
+ for (const relativePath of paths)try {
14397
+ const headMode = await this.getHeadFileMode(relativePath);
14398
+ if ("100644" !== headMode && "100755" !== headMode) continue;
14399
+ const contentUnchanged = await this.hasSameContentAsHead(relativePath);
14400
+ if (!contentUnchanged) continue;
14401
+ await this.restoreExecutableBit(relativePath, "100755" === headMode);
14402
+ discarded.push(relativePath);
14403
+ } catch (e) {
14404
+ throw errors_createGitError.commandFailed("discard mode-only changes", e.message);
14405
+ }
14406
+ return discarded;
14407
+ }
14408
+ async discardStagedModeOnlyChanges(paths) {
14409
+ const discarded = [];
14410
+ for (const relativePath of paths)try {
14411
+ const headMode = await this.getHeadFileMode(relativePath);
14412
+ if ("100644" !== headMode && "100755" !== headMode) continue;
14413
+ const indexContentUnchanged = await this.hasSameIndexContentAsHead(relativePath);
14414
+ if (!indexContentUnchanged) continue;
14415
+ await this.git.raw([
14416
+ "reset",
14417
+ "-q",
14418
+ "HEAD",
14419
+ "--",
14420
+ relativePath
14421
+ ]);
14422
+ await this.restoreExecutableBit(relativePath, "100755" === headMode);
14423
+ discarded.push(relativePath);
14424
+ } catch (e) {
14425
+ throw errors_createGitError.commandFailed("discard staged mode-only changes", e.message);
14426
+ }
14427
+ return discarded;
14428
+ }
14429
+ async getHeadFileMode(relativePath) {
14430
+ const output = await this.git.raw([
14431
+ "ls-tree",
14432
+ "HEAD",
14433
+ "--",
14434
+ relativePath
14435
+ ]);
14436
+ const match = output.match(/^(\d{6})\s/);
14437
+ return match?.[1] ?? null;
14438
+ }
14439
+ async hasSameContentAsHead(relativePath) {
14440
+ const headHash = (await this.git.revparse([
14441
+ `HEAD:${relativePath}`
14442
+ ])).trim();
14443
+ const worktreeHash = (await this.git.raw([
14444
+ "hash-object",
14445
+ "--",
14446
+ relativePath
14447
+ ])).trim();
14448
+ return headHash === worktreeHash;
14449
+ }
14450
+ async hasSameIndexContentAsHead(relativePath) {
14451
+ const headHash = (await this.git.revparse([
14452
+ `HEAD:${relativePath}`
14453
+ ])).trim();
14454
+ const indexHash = (await this.git.revparse([
14455
+ `:${relativePath}`
14456
+ ])).trim();
14457
+ return headHash === indexHash;
14458
+ }
14459
+ async restoreExecutableBit(relativePath, executable) {
14460
+ const filePath = external_path_default().join(this.repoPath, relativePath);
14461
+ const stat = await external_fs_.promises.stat(filePath);
14462
+ const mode = executable ? 73 | stat.mode : -74 & stat.mode;
14463
+ if ((511 & stat.mode) !== (511 & mode)) await external_fs_.promises.chmod(filePath, mode);
14464
+ }
14465
+ async hasUncommittedChanges() {
14466
+ const status = await this.getStatus();
14467
+ return !status.isClean;
14468
+ }
14469
+ async fetch(url, refSpec) {
14470
+ let attempt = 0;
14471
+ while(true)try {
14472
+ await this.git.fetch([
14473
+ url,
14474
+ refSpec
14475
+ ]);
14476
+ if (attempt > 0) this.emit("fetch-retry-resolved", {
14477
+ attempts: attempt,
14478
+ outcome: "succeeded"
14479
+ });
14480
+ return;
14481
+ } catch (e) {
14482
+ const err = e;
14483
+ if (!isWantNotValidError(err)) {
14484
+ if (attempt > 0) this.emit("fetch-retry-resolved", {
14485
+ attempts: attempt,
14486
+ outcome: "failed"
14487
+ });
14488
+ throw errors_createGitError.fetchFailed(err.message);
14489
+ }
14490
+ attempt++;
14491
+ this.emit("fetch-retry", {
14492
+ attempt,
14493
+ delayMs: FETCH_RETRY_DELAY_MS,
14494
+ error: err.message
14495
+ });
14496
+ logger_logger.verbose(`git fetch race (attempt ${attempt}, sleeping ${FETCH_RETRY_DELAY_MS}ms): ${err.message}`);
14497
+ await (0, promises_namespaceObject.setTimeout)(FETCH_RETRY_DELAY_MS);
14498
+ }
14499
+ }
14500
+ async reset(ref, mode = "mixed") {
14501
+ await this.runGitCommandWithIndexLockRetry("reset", ()=>this.git.reset([
14502
+ `--${mode}`,
14503
+ ref
14504
+ ]));
14505
+ }
14506
+ async checkout(paths) {
14507
+ await this.runGitCommandWithIndexLockRetry("checkout", ()=>this.git.checkout(paths));
14508
+ }
14509
+ async runGitCommandWithIndexLockRetry(command, operation) {
14510
+ let lastError;
14511
+ for(let attempt = 0; attempt <= git_GitService.INDEX_LOCK_RETRY_DELAYS_MS.length; attempt += 1)try {
14512
+ await operation();
14513
+ return;
14514
+ } catch (e) {
14515
+ lastError = e;
14516
+ if (!this.isIndexLockError(e) || attempt === git_GitService.INDEX_LOCK_RETRY_DELAYS_MS.length) throw errors_createGitError.commandFailed(command, e.message);
14517
+ await (0, promises_namespaceObject.setTimeout)(git_GitService.INDEX_LOCK_RETRY_DELAYS_MS[attempt]);
14518
+ }
14519
+ throw errors_createGitError.commandFailed(command, lastError.message);
14520
+ }
14521
+ isIndexLockError(error) {
14522
+ const message = error.message ?? "";
14523
+ return message.includes("Unable to create") && message.includes("index.lock") && message.includes("File exists");
14524
+ }
14525
+ async stash(message) {
14526
+ try {
14527
+ const args = [
14528
+ "stash",
14529
+ "push",
14530
+ "--include-untracked"
14531
+ ];
14532
+ if (message) args.push("-m", message);
14533
+ const result = await this.git.raw(args);
14534
+ return !result.includes("No local changes to save");
14535
+ } catch (e) {
14536
+ throw errors_createGitError.commandFailed("stash", e.message);
14537
+ }
14538
+ }
14539
+ async stashPop() {
14540
+ try {
14541
+ await this.git.raw([
14542
+ "stash",
14543
+ "pop"
14544
+ ]);
14545
+ } catch (e) {
14546
+ throw errors_createGitError.commandFailed("stash pop", e.message);
14547
+ }
14548
+ }
14549
+ async clean(files) {
14550
+ try {
14551
+ await this.git.clean(CleanOptions.FORCE, files);
14552
+ } catch (e) {
14553
+ throw errors_createGitError.commandFailed("clean", e.message);
14554
+ }
14555
+ }
14556
+ async diffNames(fromCommit, toCommit) {
14557
+ try {
14558
+ const diffResult = await this.git.diff([
14559
+ fromCommit,
14560
+ toCommit,
14561
+ "--name-only"
14562
+ ]);
14563
+ const files = diffResult.split("\n").filter((f)=>f.trim());
14564
+ return files;
14565
+ } catch (e) {
14566
+ throw errors_createGitError.commandFailed("diff", e.message);
14567
+ }
14568
+ }
14569
+ async getAheadBehind(localRef, remoteRef) {
14570
+ try {
14571
+ const result = await this.git.raw([
14572
+ "rev-list",
14573
+ "--left-right",
14574
+ "--count",
14575
+ `${remoteRef}...${localRef}`
14576
+ ]);
14577
+ const [behind, ahead] = result.trim().split("\t").map(Number);
14578
+ return {
14579
+ ahead,
14580
+ behind
14581
+ };
14582
+ } catch (e) {
14583
+ throw errors_createGitError.commandFailed("rev-list", e.message);
14584
+ }
14585
+ }
14586
+ async show(refPath) {
14587
+ try {
14588
+ const content = await this.git.show([
14589
+ refPath
14590
+ ]);
14591
+ return content;
14592
+ } catch (e) {
14593
+ throw errors_createGitError.commandFailed("show", e.message);
14594
+ }
14595
+ }
14596
+ async push(url, refSpec, force = false) {
14597
+ try {
14598
+ const args = force ? [
14599
+ "push",
14600
+ "--force",
14601
+ url,
14602
+ refSpec
14603
+ ] : [
14604
+ "push",
14605
+ url,
14606
+ refSpec
14607
+ ];
14608
+ await this.git.raw(args);
14609
+ } catch (e) {
14610
+ throw errors_createGitError.commandFailed("push", e.message);
14771
14611
  }
14772
14612
  }
14773
- async discardModeOnlyChanges(paths) {
14774
- const discarded = [];
14775
- for (const relativePath of paths)try {
14776
- const headMode = await this.getHeadFileMode(relativePath);
14777
- if ("100644" !== headMode && "100755" !== headMode) continue;
14778
- const contentUnchanged = await this.hasSameContentAsHead(relativePath);
14779
- if (!contentUnchanged) continue;
14780
- await this.restoreExecutableBit(relativePath, "100755" === headMode);
14781
- discarded.push(relativePath);
14613
+ async rebase(onto) {
14614
+ try {
14615
+ await this.git.raw([
14616
+ "rebase",
14617
+ onto
14618
+ ]);
14782
14619
  } catch (e) {
14783
- throw errors_createGitError.commandFailed("discard mode-only changes", e.message);
14620
+ throw errors_createGitError.commandFailed("rebase", e.message);
14784
14621
  }
14785
- return discarded;
14786
14622
  }
14787
- async discardStagedModeOnlyChanges(paths) {
14788
- const discarded = [];
14789
- for (const relativePath of paths)try {
14790
- const headMode = await this.getHeadFileMode(relativePath);
14791
- if ("100644" !== headMode && "100755" !== headMode) continue;
14792
- const indexContentUnchanged = await this.hasSameIndexContentAsHead(relativePath);
14793
- if (!indexContentUnchanged) continue;
14623
+ async rebaseAbort() {
14624
+ try {
14794
14625
  await this.git.raw([
14795
- "reset",
14796
- "-q",
14797
- "HEAD",
14798
- "--",
14799
- relativePath
14626
+ "rebase",
14627
+ "--abort"
14800
14628
  ]);
14801
- await this.restoreExecutableBit(relativePath, "100755" === headMode);
14802
- discarded.push(relativePath);
14803
14629
  } catch (e) {
14804
- throw errors_createGitError.commandFailed("discard staged mode-only changes", e.message);
14630
+ throw errors_createGitError.commandFailed("rebase --abort", e.message);
14805
14631
  }
14806
- return discarded;
14807
14632
  }
14808
- async getHeadFileMode(relativePath) {
14809
- const output = await this.git.raw([
14810
- "ls-tree",
14811
- "HEAD",
14812
- "--",
14813
- relativePath
14814
- ]);
14815
- const match = output.match(/^(\d{6})\s/);
14816
- return match?.[1] ?? null;
14633
+ async isAncestor(ancestorRef, descendantRef) {
14634
+ try {
14635
+ const mergeBase = (await this.git.raw([
14636
+ "merge-base",
14637
+ ancestorRef,
14638
+ descendantRef
14639
+ ])).trim();
14640
+ const ancestorCommit = (await this.git.revparse([
14641
+ ancestorRef
14642
+ ])).trim();
14643
+ return mergeBase === ancestorCommit;
14644
+ } catch {
14645
+ return false;
14646
+ }
14817
14647
  }
14818
- async hasSameContentAsHead(relativePath) {
14819
- const headHash = (await this.git.revparse([
14820
- `HEAD:${relativePath}`
14821
- ])).trim();
14822
- const worktreeHash = (await this.git.raw([
14823
- "hash-object",
14824
- "--",
14825
- relativePath
14826
- ])).trim();
14827
- return headHash === worktreeHash;
14648
+ async mergeFastForward(ref) {
14649
+ await this.runGitCommandWithIndexLockRetry("merge --ff-only", ()=>this.git.raw([
14650
+ "merge",
14651
+ "--ff-only",
14652
+ ref
14653
+ ]));
14828
14654
  }
14829
- async hasSameIndexContentAsHead(relativePath) {
14830
- const headHash = (await this.git.revparse([
14831
- `HEAD:${relativePath}`
14832
- ])).trim();
14833
- const indexHash = (await this.git.revparse([
14834
- `:${relativePath}`
14835
- ])).trim();
14836
- return headHash === indexHash;
14655
+ async deleteRef(ref) {
14656
+ try {
14657
+ await this.git.raw([
14658
+ "update-ref",
14659
+ "-d",
14660
+ ref
14661
+ ]);
14662
+ } catch (e) {}
14837
14663
  }
14838
- async restoreExecutableBit(relativePath, executable) {
14839
- const filePath = external_path_default().join(this.repoPath, relativePath);
14840
- const stat = await external_fs_.promises.stat(filePath);
14841
- const mode = executable ? 73 | stat.mode : -74 & stat.mode;
14842
- if ((511 & stat.mode) !== (511 & mode)) await external_fs_.promises.chmod(filePath, mode);
14664
+ async getRemotes() {
14665
+ try {
14666
+ const remotes = await this.git.getRemotes(true);
14667
+ return remotes.map((r)=>({
14668
+ name: r.name,
14669
+ fetchUrl: r.refs.fetch
14670
+ }));
14671
+ } catch (e) {
14672
+ throw errors_createGitError.commandFailed("remote", e.message);
14673
+ }
14843
14674
  }
14844
- async hasUncommittedChanges() {
14845
- const status = await this.getStatus();
14846
- return !status.isClean;
14675
+ async isGitInitialized() {
14676
+ const gitPath = external_path_default().join(this.repoPath, ".git");
14677
+ return external_fs_.existsSync(gitPath);
14847
14678
  }
14848
- async fetch(url, refSpec) {
14849
- let attempt = 0;
14850
- while(true)try {
14851
- await this.git.fetch([
14852
- url,
14853
- refSpec
14854
- ]);
14855
- if (attempt > 0) this.emit("fetch-retry-resolved", {
14856
- attempts: attempt,
14857
- outcome: "succeeded"
14858
- });
14859
- return;
14860
- } catch (e) {
14861
- const err = e;
14862
- if (!isWantNotValidError(err)) {
14863
- if (attempt > 0) this.emit("fetch-retry-resolved", {
14864
- attempts: attempt,
14865
- outcome: "failed"
14866
- });
14867
- throw errors_createGitError.fetchFailed(err.message);
14679
+ async removeEmptyDirectories(removedFiles) {
14680
+ const directoriesToCheck = new Set();
14681
+ for (const file of removedFiles){
14682
+ let dir = external_path_default().dirname(file);
14683
+ while(dir && "." !== dir){
14684
+ directoriesToCheck.add(dir);
14685
+ const parentDir = external_path_default().dirname(dir);
14686
+ if (parentDir === dir) break;
14687
+ dir = parentDir;
14868
14688
  }
14869
- attempt++;
14870
- this.emit("fetch-retry", {
14871
- attempt,
14872
- delayMs: FETCH_RETRY_DELAY_MS,
14873
- error: err.message
14689
+ }
14690
+ const sortedDirs = Array.from(directoriesToCheck).sort((a, b)=>b.split("/").length - a.split("/").length);
14691
+ for (const dir of sortedDirs)try {
14692
+ const dirPath = external_path_default().join(this.repoPath, dir);
14693
+ if (!external_fs_.existsSync(dirPath)) continue;
14694
+ const entries = await external_fs_.promises.readdir(dirPath);
14695
+ if (0 === entries.length) await external_fs_.promises.rmdir(dirPath);
14696
+ } catch (error) {}
14697
+ }
14698
+ }
14699
+ async function getStagedFileChanges(git, gitService) {
14700
+ try {
14701
+ const status = await git.status();
14702
+ const renames = status.renamed || [];
14703
+ const stagedFiles = status.files.filter((f)=>" " !== f.index && "?" !== f.index);
14704
+ const stagedModeOnlyPaths = new Set(gitService ? await gitService.discardStagedModeOnlyChanges(stagedFiles.filter((f)=>"R" !== f.index && "D" !== f.index).map((f)=>f.path)) : []);
14705
+ const stagedRenames = [];
14706
+ for (const f of stagedFiles)if ("R" === f.index) {
14707
+ const rename = renames.find((r)=>r.to === f.path);
14708
+ if (rename) stagedRenames.push({
14709
+ path: rename.to,
14710
+ type: "rename",
14711
+ from: rename.from
14874
14712
  });
14875
- logger_logger.verbose(`git fetch race (attempt ${attempt}, sleeping ${FETCH_RETRY_DELAY_MS}ms): ${err.message}`);
14876
- await (0, external_timers_promises_namespaceObject.setTimeout)(FETCH_RETRY_DELAY_MS);
14877
14713
  }
14714
+ const stagedRenamedFromPaths = new Set(stagedRenames.map((r)=>r.from));
14715
+ const stagedRenamedToPaths = new Set(stagedRenames.map((r)=>r.path));
14716
+ const changes = [];
14717
+ for (const f of stagedFiles){
14718
+ if ("R" !== f.index) {
14719
+ if (!stagedModeOnlyPaths.has(f.path)) {
14720
+ if (!(stagedRenamedFromPaths.has(f.path) || stagedRenamedToPaths.has(f.path))) if ("D" === f.index) changes.push({
14721
+ path: f.path,
14722
+ type: "unlink"
14723
+ });
14724
+ else if ("A" === f.index || "?" === f.index) changes.push({
14725
+ path: f.path,
14726
+ type: "add"
14727
+ });
14728
+ else changes.push({
14729
+ path: f.path,
14730
+ type: "change"
14731
+ });
14732
+ }
14733
+ }
14734
+ }
14735
+ changes.push(...stagedRenames);
14736
+ return changes;
14737
+ } catch (e) {
14738
+ throw errors_createGitError.commandFailed("status", e.message);
14878
14739
  }
14879
- async reset(ref, mode = "mixed") {
14880
- await this.runGitCommandWithIndexLockRetry("reset", ()=>this.git.reset([
14881
- `--${mode}`,
14882
- ref
14883
- ]));
14740
+ }
14741
+ async function git_getCurrentOrFallbackBranchName(git) {
14742
+ const branchRef = (await git.revparse([
14743
+ "--abbrev-ref",
14744
+ "HEAD"
14745
+ ])).trim();
14746
+ if ("HEAD" !== branchRef) return branchRef;
14747
+ const [localBranches, remoteBranches] = await Promise.all([
14748
+ git.raw([
14749
+ "branch",
14750
+ "--format=%(refname:short)",
14751
+ "--contains",
14752
+ "HEAD"
14753
+ ]),
14754
+ git.raw([
14755
+ "branch",
14756
+ "-r",
14757
+ "--format=%(refname:short)",
14758
+ "--contains",
14759
+ "HEAD"
14760
+ ])
14761
+ ]);
14762
+ const branchNames = [
14763
+ ...parseBranchList(localBranches),
14764
+ ...parseBranchList(remoteBranches).filter((branchName)=>!branchName.endsWith("/HEAD")).map((branchName)=>branchName.replace(/^[^/]+\//, ""))
14765
+ ];
14766
+ const branchSet = new Set(branchNames);
14767
+ if (branchSet.has("master")) return "master";
14768
+ if (branchSet.has("main")) return "main";
14769
+ return "master";
14770
+ }
14771
+ function parseBranchList(output) {
14772
+ return output.split(/\r?\n/).map((line)=>line.trim()).filter(Boolean);
14773
+ }
14774
+ function anvil_api_getDefaultAnvilUrl() {
14775
+ return resolveAnvilUrl();
14776
+ }
14777
+ async function anvil_api_validateAppId(appId, anvilUrl = anvil_api_getDefaultAnvilUrl(), username) {
14778
+ const authToken = await auth_getValidAuthToken(anvilUrl, username);
14779
+ try {
14780
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/validate-app-id`, {
14781
+ method: "POST",
14782
+ headers: {
14783
+ Authorization: `Bearer ${authToken}`,
14784
+ "Content-Type": "application/json"
14785
+ },
14786
+ body: JSON.stringify({
14787
+ app_id: appId
14788
+ })
14789
+ });
14790
+ const data = await resp.json();
14791
+ if (resp.ok) return {
14792
+ valid: data.valid ?? false,
14793
+ app_name: data.app_name
14794
+ };
14795
+ if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14796
+ if (403 === resp.status) throw createAppError.accessDenied(appId);
14797
+ if (404 === resp.status) throw createAppError.notFound(appId);
14798
+ logger_logger.debug(`validate-app-id failed: ${resp.status} ${resp.statusText}`);
14799
+ return {
14800
+ valid: false,
14801
+ error: data.error || `Server error: ${resp.status}`
14802
+ };
14803
+ } catch (error) {
14804
+ if (error.type) throw error;
14805
+ throw createNetworkError.network(error.message);
14884
14806
  }
14885
- async checkout(paths) {
14886
- await this.runGitCommandWithIndexLockRetry("checkout", ()=>this.git.checkout(paths));
14807
+ }
14808
+ async function lookupByCommit(commitId, branchName, anvilUrl = anvil_api_getDefaultAnvilUrl(), username) {
14809
+ const authToken = await auth_getValidAuthToken(anvilUrl, username);
14810
+ try {
14811
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/lookup-by-commit`, {
14812
+ method: "POST",
14813
+ headers: {
14814
+ Authorization: `Bearer ${authToken}`,
14815
+ "Content-Type": "application/json"
14816
+ },
14817
+ body: JSON.stringify({
14818
+ commit_id: commitId,
14819
+ branch_name: branchName
14820
+ })
14821
+ });
14822
+ if (!resp.ok) {
14823
+ if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14824
+ throw createNetworkError.server(resp.status, resp.statusText);
14825
+ }
14826
+ const data = await resp.json();
14827
+ return data;
14828
+ } catch (error) {
14829
+ if (error.type) throw error;
14830
+ throw createNetworkError.network(error.message);
14887
14831
  }
14888
- async runGitCommandWithIndexLockRetry(command, operation) {
14889
- let lastError;
14890
- for(let attempt = 0; attempt <= git_GitService.INDEX_LOCK_RETRY_DELAYS_MS.length; attempt += 1)try {
14891
- await operation();
14892
- return;
14893
- } catch (e) {
14894
- lastError = e;
14895
- if (!this.isIndexLockError(e) || attempt === git_GitService.INDEX_LOCK_RETRY_DELAYS_MS.length) throw errors_createGitError.commandFailed(command, e.message);
14896
- await (0, external_timers_promises_namespaceObject.setTimeout)(git_GitService.INDEX_LOCK_RETRY_DELAYS_MS[attempt]);
14832
+ }
14833
+ async function listAppsForCheckout(options = {}) {
14834
+ const anvilUrl = options.anvilUrl ?? anvil_api_getDefaultAnvilUrl();
14835
+ const authToken = await auth_getValidAuthToken(anvilUrl, options.username);
14836
+ const params = new URLSearchParams();
14837
+ if ("number" == typeof options.limit) params.set("limit", String(options.limit));
14838
+ if (options.cursor) params.set("cursor", options.cursor);
14839
+ if (options.q && options.q.trim()) params.set("q", options.q.trim());
14840
+ const query = params.toString();
14841
+ const url = `${anvilUrl}/ide/api/_/apps${query ? `?${query}` : ""}`;
14842
+ try {
14843
+ const resp = await fetch(url, {
14844
+ method: "GET",
14845
+ headers: {
14846
+ Authorization: `Bearer ${authToken}`
14847
+ },
14848
+ signal: options.signal
14849
+ });
14850
+ if (!resp.ok) {
14851
+ if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14852
+ throw createNetworkError.server(resp.status, resp.statusText);
14897
14853
  }
14898
- throw errors_createGitError.commandFailed(command, lastError.message);
14854
+ const data = await resp.json();
14855
+ return {
14856
+ apps: Array.isArray(data.apps) ? data.apps : [],
14857
+ next_cursor: "string" == typeof data.next_cursor ? data.next_cursor : null
14858
+ };
14859
+ } catch (error) {
14860
+ if (error.type) throw error;
14861
+ throw createNetworkError.network(error.message);
14899
14862
  }
14900
- isIndexLockError(error) {
14901
- const message = error.message ?? "";
14902
- return message.includes("Unable to create") && message.includes("index.lock") && message.includes("File exists");
14863
+ }
14864
+ async function saveUpdates(appId, branchName, payload, anvilUrl = anvil_api_getDefaultAnvilUrl(), authToken) {
14865
+ try {
14866
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/${appId}/update?make_fresh_version=false&branch=${branchName}`, {
14867
+ method: "PUT",
14868
+ headers: {
14869
+ Authorization: `Bearer ${authToken}`,
14870
+ "Content-Type": "application/json"
14871
+ },
14872
+ body: JSON.stringify(payload)
14873
+ });
14874
+ const data = await resp.json();
14875
+ if (401 === resp.status) throw createAuthError.invalid(`Authentication failed (401): ${data.error || "unauthorized"}`);
14876
+ if (403 === resp.status) throw createAuthError.invalid(`Authentication failed (403): ${data.error || "unauthorized"}\n This may indicate:\n - The OAuth token doesn't have permission to write to app ${appId}\n - The app belongs to a different user or organization\n - The OAuth token is missing required scopes (apps:write)`);
14877
+ return {
14878
+ version: data.version,
14879
+ overtaken: data.overtaken,
14880
+ latest_version: data.latest_version,
14881
+ error: 200 !== resp.status ? data.error || `Status ${resp.status}` : void 0
14882
+ };
14883
+ } catch (error) {
14884
+ if (error.type) throw error;
14885
+ throw createNetworkError.network(error.message);
14903
14886
  }
14904
- async stash(message) {
14905
- try {
14906
- const args = [
14907
- "stash",
14908
- "push",
14909
- "--include-untracked"
14910
- ];
14911
- if (message) args.push("-m", message);
14912
- const result = await this.git.raw(args);
14913
- return !result.includes("No local changes to save");
14914
- } catch (e) {
14915
- throw errors_createGitError.commandFailed("stash", e.message);
14916
- }
14887
+ }
14888
+ function anvil_api_getGitFetchUrl(appId, authToken, gitUrl = anvil_api_getDefaultAnvilUrl()) {
14889
+ const url = new URL(gitUrl);
14890
+ const encodedToken = encodeURIComponent(authToken);
14891
+ return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git?no_freeze=true&q=`;
14892
+ }
14893
+ function anvil_api_getGitPushUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
14894
+ const url = new URL(anvilUrl);
14895
+ const encodedToken = encodeURIComponent(authToken);
14896
+ return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git`;
14897
+ }
14898
+ function getWebSocketUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
14899
+ return anvilUrl.replace(/^http/, "ws") + `/ide/api/_/apps/${appId}/ws?access_token=${authToken}`;
14900
+ }
14901
+ async function getLatestVersion() {
14902
+ try {
14903
+ const response = await fetch("https://registry.npmjs.org/@anvil-works/anvil-cli/latest");
14904
+ if (!response.ok) return null;
14905
+ const data = await response.json();
14906
+ return data.version;
14907
+ } catch (e) {
14908
+ return null;
14917
14909
  }
14918
- async stashPop() {
14919
- try {
14920
- await this.git.raw([
14921
- "stash",
14922
- "pop"
14923
- ]);
14924
- } catch (e) {
14925
- throw errors_createGitError.commandFailed("stash pop", e.message);
14926
- }
14910
+ }
14911
+ function anvil_api_filterCandidates(candidates, explicitUrl, explicitUsername) {
14912
+ let filtered = candidates;
14913
+ if (explicitUrl) {
14914
+ const normalizedExplicit = config_normalizeAnvilUrl(explicitUrl);
14915
+ filtered = filtered.filter((c)=>c.detectedUrl && config_normalizeAnvilUrl(c.detectedUrl) === normalizedExplicit);
14927
14916
  }
14928
- async clean(files) {
14929
- try {
14930
- await this.git.clean(CleanOptions.FORCE, files);
14931
- } catch (e) {
14932
- throw errors_createGitError.commandFailed("clean", e.message);
14917
+ if (explicitUsername) filtered = filtered.filter((c)=>!c.detectedUsername || c.detectedUsername === explicitUsername);
14918
+ return filtered;
14919
+ }
14920
+ function anvil_api_formatCandidateLabel(candidate) {
14921
+ const parts = [
14922
+ candidate.appId
14923
+ ];
14924
+ if (candidate.detectedUrl) if (candidate.detectedUsername) parts.push(`(${candidate.detectedUsername} on ${candidate.detectedUrl})`);
14925
+ else parts.push(`(${candidate.detectedUrl})`);
14926
+ parts.push(`- ${candidate.description}`);
14927
+ return parts.join(" ");
14928
+ }
14929
+ function anvil_api_lookupRemoteInfoForAppId(appId, detectedRemotes) {
14930
+ const matches = detectedRemotes.filter((c)=>c.appId === appId);
14931
+ if (0 === matches.length) return {};
14932
+ const withUsername = matches.find((c)=>c.detectedUsername);
14933
+ if (withUsername) return {
14934
+ detectedUrl: withUsername.detectedUrl,
14935
+ detectedUsername: withUsername.detectedUsername
14936
+ };
14937
+ return {
14938
+ detectedUrl: matches[0].detectedUrl,
14939
+ detectedUsername: matches[0].detectedUsername
14940
+ };
14941
+ }
14942
+ async function anvil_api_detectAppIdsFromAllRemotes(repoPath) {
14943
+ const git = esm_default(repoPath);
14944
+ const out = [];
14945
+ try {
14946
+ const remotes = await git.getRemotes(true);
14947
+ for (const remote of remotes){
14948
+ const httpMatch = remote.refs.fetch?.match(/(?:http|https):\/\/(?:[^@]+@)?([^:\/]+)(?::\d+)?\/git\/([A-Z0-9]+)\.git/);
14949
+ if (httpMatch) {
14950
+ const [, host, detectedAppId] = httpMatch;
14951
+ out.push({
14952
+ appId: detectedAppId,
14953
+ source: "remote",
14954
+ description: `Git remote '${remote.name}'`,
14955
+ detectedUrl: config_normalizeAnvilUrl(host)
14956
+ });
14957
+ continue;
14958
+ }
14959
+ const sshMatch = remote.refs.fetch?.match(/ssh:\/\/([^@]+)@([^:]+):(\d+)\/(?:git\/)?([A-Z0-9]+)\.git/);
14960
+ if (sshMatch) {
14961
+ const [, usernamePart, host, , detectedAppId] = sshMatch;
14962
+ const detectedUsername = usernamePart.includes("%") ? decodeURIComponent(usernamePart) : usernamePart;
14963
+ const finalUsername = "git" !== detectedUsername ? detectedUsername : void 0;
14964
+ out.push({
14965
+ appId: detectedAppId,
14966
+ source: "remote",
14967
+ description: `Git remote '${remote.name}' (SSH)`,
14968
+ detectedUrl: config_normalizeAnvilUrl(host),
14969
+ detectedUsername: finalUsername
14970
+ });
14971
+ }
14933
14972
  }
14934
- }
14935
- async diffNames(fromCommit, toCommit) {
14936
- try {
14937
- const diffResult = await this.git.diff([
14938
- fromCommit,
14939
- toCommit,
14940
- "--name-only"
14941
- ]);
14942
- const files = diffResult.split("\n").filter((f)=>f.trim());
14943
- return files;
14944
- } catch (e) {
14945
- throw errors_createGitError.commandFailed("diff", e.message);
14973
+ } catch (_e) {}
14974
+ return out;
14975
+ }
14976
+ async function anvil_api_detectAppIdsByCommitLookup(repoPath, options) {
14977
+ const anvilUrl = options.anvilUrl || resolveAnvilUrl();
14978
+ const username = options.username;
14979
+ const git = esm_default(repoPath);
14980
+ const out = [];
14981
+ try {
14982
+ const authToken = await auth_getValidAuthToken(anvilUrl, username);
14983
+ const commitId = (await git.revparse([
14984
+ "HEAD"
14985
+ ]))?.trim();
14986
+ if (!commitId) return out;
14987
+ const branchName = await git_getCurrentOrFallbackBranchName(git);
14988
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/lookup-by-commit`, {
14989
+ method: "POST",
14990
+ headers: {
14991
+ Authorization: `Bearer ${authToken}`,
14992
+ "Content-Type": "application/json"
14993
+ },
14994
+ body: JSON.stringify({
14995
+ commit_id: commitId,
14996
+ branch_name: branchName
14997
+ })
14998
+ });
14999
+ logger_logger.debug(`Reverse lookup by commit ${commitId} on branch ${branchName} returned ${resp.status}`);
15000
+ if (!resp.ok) {
15001
+ const errorBody = await resp.text();
15002
+ logger_logger.debug(`lookup-by-commit error: ${errorBody}`);
15003
+ }
15004
+ if (resp.ok) {
15005
+ const data = await resp.json();
15006
+ logger_logger.debug(`data ${JSON.stringify(data)}`);
15007
+ const apps = data?.apps ?? [];
15008
+ for (const app of apps)if (app?.app_id) out.push({
15009
+ appId: app.app_id,
15010
+ source: "remote",
15011
+ description: `Reverse lookup by commit${app.branch ? ` on branch '${app.branch}'` : ""}`
15012
+ });
14946
15013
  }
15014
+ } catch (_e) {}
15015
+ return out;
15016
+ }
15017
+ const external_fs_promises_namespaceObject = require("fs/promises");
15018
+ var external_fs_promises_default = /*#__PURE__*/ __webpack_require__.n(external_fs_promises_namespaceObject);
15019
+ function normalizeLineEndings(content) {
15020
+ return content.replace(/\r\n/g, "\n");
15021
+ }
15022
+ function pythonifyName(name) {
15023
+ return name.replace(/[^A-z0-9]/g, "_").replace(/^[0-9]/, "_$&");
15024
+ }
15025
+ function extractPythonName(relativePath, skipParts = 1) {
15026
+ const parts = relativePath.slice(0, -3).split("/");
15027
+ const isPackage = relativePath.endsWith("__init__.py");
15028
+ const relevantParts = isPackage ? parts.slice(skipParts, -1) : parts.slice(skipParts);
15029
+ return relevantParts.map(pythonifyName).join(".");
15030
+ }
15031
+ function deepEqual(a, b) {
15032
+ if (a === b) return true;
15033
+ if (null == a || null == b) return false;
15034
+ if (typeof a !== typeof b) return false;
15035
+ if ("object" != typeof a) return a === b;
15036
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
15037
+ if (Array.isArray(a)) {
15038
+ if (a.length !== b.length) return false;
15039
+ return a.every((item, index)=>deepEqual(item, b[index]));
14947
15040
  }
14948
- async getAheadBehind(localRef, remoteRef) {
14949
- try {
14950
- const result = await this.git.raw([
14951
- "rev-list",
14952
- "--left-right",
14953
- "--count",
14954
- `${remoteRef}...${localRef}`
14955
- ]);
14956
- const [behind, ahead] = result.trim().split("\t").map(Number);
14957
- return {
14958
- ahead,
14959
- behind
14960
- };
14961
- } catch (e) {
14962
- throw errors_createGitError.commandFailed("rev-list", e.message);
14963
- }
15041
+ const keysA = Object.keys(a);
15042
+ const keysB = Object.keys(b);
15043
+ if (keysA.length !== keysB.length) return false;
15044
+ return keysA.every((key)=>deepEqual(a[key], b[key]));
15045
+ }
15046
+ function parseHtmlWithFrontmatter(htmlContent) {
15047
+ const frontmatterPattern = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
15048
+ const match = htmlContent.match(frontmatterPattern);
15049
+ if (match) {
15050
+ const yamlStr = match[1];
15051
+ const html = match[2];
15052
+ const frontmatter = yamlStr ? external_js_yaml_default().load(yamlStr) ?? {} : {};
15053
+ return {
15054
+ frontmatter,
15055
+ html
15056
+ };
14964
15057
  }
14965
- async show(refPath) {
14966
- try {
14967
- const content = await this.git.show([
14968
- refPath
14969
- ]);
14970
- return content;
14971
- } catch (e) {
14972
- throw errors_createGitError.commandFailed("show", e.message);
14973
- }
15058
+ return {
15059
+ frontmatter: {},
15060
+ html: htmlContent
15061
+ };
15062
+ }
15063
+ function formatHttpError(prefix, status, body) {
15064
+ if (!body) return `${prefix}: ${status}`;
15065
+ try {
15066
+ const parsed = JSON.parse(body);
15067
+ if ("string" == typeof parsed.error && parsed.error.trim()) return `${prefix}: ${parsed.error} (${status})`;
15068
+ } catch {}
15069
+ return `${prefix}: ${status} ${body}`;
15070
+ }
15071
+ const ANVIL_GITIGNORE_ENTRY = "/.anvil/";
15072
+ async function ensureProjectGitignoreIgnoresAnvilDir(projectRoot) {
15073
+ const rootGitignorePath = external_path_default().join(projectRoot, ".gitignore");
15074
+ try {
15075
+ const existing = await external_fs_.promises.readFile(rootGitignorePath, "utf8");
15076
+ if (existing.includes(ANVIL_GITIGNORE_ENTRY)) return false;
15077
+ const prefix = existing.endsWith("\n") || 0 === existing.length ? "" : "\n";
15078
+ await external_fs_.promises.writeFile(rootGitignorePath, `${existing}${prefix}${ANVIL_GITIGNORE_ENTRY}\n`, "utf8");
15079
+ return true;
15080
+ } catch (error) {
15081
+ if ("ENOENT" !== error.code) throw error;
15082
+ await external_fs_.promises.writeFile(rootGitignorePath, `${ANVIL_GITIGNORE_ENTRY}\n`, "utf8");
15083
+ return true;
14974
15084
  }
14975
- async push(url, refSpec, force = false) {
14976
- try {
14977
- const args = force ? [
14978
- "push",
14979
- "--force",
14980
- url,
14981
- refSpec
14982
- ] : [
14983
- "push",
14984
- url,
14985
- refSpec
14986
- ];
14987
- await this.git.raw(args);
14988
- } catch (e) {
14989
- throw errors_createGitError.commandFailed("push", e.message);
14990
- }
15085
+ }
15086
+ const ANVIL_DEPS_DIR = ".anvil/deps";
15087
+ const ANVIL_DEPS_MANIFEST = "manifest.json";
15088
+ const DEPS_MANIFEST_VERSION = 3;
15089
+ async function fetchAllDependencies(appId, anvilUrl, options = {}) {
15090
+ const { commit, includeDocs = true, knownDependencies = [] } = options;
15091
+ anvilUrl ??= await auth_resolveAuthAnvilUrl();
15092
+ const token = await auth_getValidAuthToken(anvilUrl, options.username);
15093
+ const body = {
15094
+ include_docs: includeDocs,
15095
+ known_dependencies: knownDependencies
15096
+ };
15097
+ if (commit) body.commit = commit;
15098
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/${appId}/resolved-dependencies`, {
15099
+ method: "POST",
15100
+ headers: {
15101
+ Authorization: `Bearer ${token}`,
15102
+ "Content-Type": "application/json"
15103
+ },
15104
+ body: JSON.stringify(body)
15105
+ });
15106
+ if (!resp.ok) {
15107
+ const errorBody = await resp.text();
15108
+ throw new Error(formatHttpError("Failed to fetch dependencies", resp.status, errorBody));
14991
15109
  }
14992
- async rebase(onto) {
14993
- try {
14994
- await this.git.raw([
14995
- "rebase",
14996
- onto
14997
- ]);
14998
- } catch (e) {
14999
- throw errors_createGitError.commandFailed("rebase", e.message);
15000
- }
15110
+ return await resp.json();
15111
+ }
15112
+ async function refreshDependencyCache(projectRoot, appId, anvilUrl, options = {}) {
15113
+ auth_setRepoContext(projectRoot);
15114
+ anvilUrl ??= await auth_resolveAuthAnvilUrl();
15115
+ const includeDocs = options.includeDocs ?? true;
15116
+ const git = new git_GitService(projectRoot);
15117
+ const commit = options.commit ?? await git.getCommitId();
15118
+ const sourceYaml = await git.show(`${commit}:anvil.yaml`).catch((error)=>{
15119
+ if (options.commit) return null;
15120
+ throw error;
15121
+ });
15122
+ const sourceDependencies = null === sourceYaml ? null : normalizeAnvilYamlDependenciesFromContent(sourceYaml);
15123
+ const knownDependencies = await getKnownDependenciesForCache(projectRoot, includeDocs);
15124
+ const response = await fetchAllDependencies(appId, anvilUrl, {
15125
+ username: options.username,
15126
+ commit,
15127
+ includeDocs,
15128
+ knownDependencies
15129
+ });
15130
+ if (!response.resolved_dependencies.length) {
15131
+ await clearDepsCache(projectRoot);
15132
+ return {
15133
+ response,
15134
+ addedGitignoreEntry: false
15135
+ };
15001
15136
  }
15002
- async rebaseAbort() {
15003
- try {
15004
- await this.git.raw([
15005
- "rebase",
15006
- "--abort"
15007
- ]);
15008
- } catch (e) {
15009
- throw errors_createGitError.commandFailed("rebase --abort", e.message);
15010
- }
15137
+ const addedGitignoreEntry = await ensureProjectGitignoreIgnoresAnvilDir(projectRoot);
15138
+ await writeAllDepsToCache(projectRoot, response, {
15139
+ includeDocs,
15140
+ sourceDependencies
15141
+ });
15142
+ return {
15143
+ response,
15144
+ addedGitignoreEntry
15145
+ };
15146
+ }
15147
+ function depsDir(projectRoot) {
15148
+ return external_path_default().join(projectRoot, ANVIL_DEPS_DIR);
15149
+ }
15150
+ function depsManifestPath(projectRoot) {
15151
+ return external_path_default().join(depsDir(projectRoot), ANVIL_DEPS_MANIFEST);
15152
+ }
15153
+ function isResolvedDependencyManifestEntry(value) {
15154
+ if (!value || "object" != typeof value) return false;
15155
+ const entry = value;
15156
+ return "string" == typeof entry.app_id && entry.app_id.length > 0 && "string" == typeof entry.commit_id && entry.commit_id.length > 0 && (void 0 === entry.branch || "string" == typeof entry.branch);
15157
+ }
15158
+ function isCachedDependencyManifestEntry(value) {
15159
+ if (!isResolvedDependencyManifestEntry(value)) return false;
15160
+ const entry = value;
15161
+ return "string" == typeof entry.package_name && entry.package_name.length > 0;
15162
+ }
15163
+ function isDepsCacheManifest(value) {
15164
+ if (!value || "object" != typeof value) return false;
15165
+ const manifest = value;
15166
+ return manifest.version === DEPS_MANIFEST_VERSION && "boolean" == typeof manifest.includeDocs && (null === manifest.source_dependencies || Array.isArray(manifest.source_dependencies)) && Array.isArray(manifest.resolved_dependencies) && manifest.resolved_dependencies.every(isResolvedDependencyManifestEntry) && Array.isArray(manifest.cached_dependencies) && manifest.cached_dependencies.every(isCachedDependencyManifestEntry);
15167
+ }
15168
+ async function readDepsCacheManifest(projectRoot) {
15169
+ try {
15170
+ const raw = await external_fs_promises_default().readFile(depsManifestPath(projectRoot), "utf-8");
15171
+ const parsed = JSON.parse(raw);
15172
+ return isDepsCacheManifest(parsed) ? parsed : void 0;
15173
+ } catch {
15174
+ return;
15011
15175
  }
15012
- async isAncestor(ancestorRef, descendantRef) {
15013
- try {
15014
- const mergeBase = (await this.git.raw([
15015
- "merge-base",
15016
- ancestorRef,
15017
- descendantRef
15018
- ])).trim();
15019
- const ancestorCommit = (await this.git.revparse([
15020
- ancestorRef
15021
- ])).trim();
15022
- return mergeBase === ancestorCommit;
15023
- } catch {
15024
- return false;
15176
+ }
15177
+ async function getKnownDependenciesForCache(projectRoot, includeDocs) {
15178
+ const manifest = await readDepsCacheManifest(projectRoot);
15179
+ if (!manifest || manifest.includeDocs !== includeDocs) return [];
15180
+ return manifest.resolved_dependencies.map(({ app_id, commit_id })=>({
15181
+ app_id,
15182
+ commit_id
15183
+ }));
15184
+ }
15185
+ function getDependencyWatchSubscriptionsFromManifest(manifest) {
15186
+ if (!manifest) return [];
15187
+ const cachedByAppId = new Map(manifest.cached_dependencies.map((dep)=>[
15188
+ dep.app_id,
15189
+ dep
15190
+ ]));
15191
+ return manifest.resolved_dependencies.flatMap((dep)=>{
15192
+ if (!dep.branch) return [];
15193
+ return [
15194
+ {
15195
+ appId: dep.app_id,
15196
+ branch: dep.branch,
15197
+ packageName: cachedByAppId.get(dep.app_id)?.package_name
15198
+ }
15199
+ ];
15200
+ });
15201
+ }
15202
+ function normalizeAnvilYamlDependenciesFromContent(content) {
15203
+ const parsed = external_js_yaml_default().load(content);
15204
+ if (!parsed || "object" != typeof parsed || !Array.isArray(parsed.dependencies)) return [];
15205
+ return normalizeAnvilYamlDependenciesValue(parsed.dependencies);
15206
+ }
15207
+ function normalizeAnvilYamlDependenciesValue(value) {
15208
+ if (!Array.isArray(value)) return [];
15209
+ return value.map(normalizeForComparison);
15210
+ }
15211
+ async function readAnvilYamlDependencies(projectRoot) {
15212
+ try {
15213
+ const content = await external_fs_promises_default().readFile(external_path_default().join(projectRoot, "anvil.yaml"), "utf-8");
15214
+ return normalizeAnvilYamlDependenciesFromContent(content);
15215
+ } catch {
15216
+ return [];
15217
+ }
15218
+ }
15219
+ function anvilYamlDependenciesEqual(left, right) {
15220
+ return deepEqual(left, right);
15221
+ }
15222
+ function normalizeForComparison(value) {
15223
+ if (Array.isArray(value)) return value.map(normalizeForComparison);
15224
+ if (value && "object" == typeof value) return Object.fromEntries(Object.entries(value).sort(([left], [right])=>left.localeCompare(right)).map(([key, entryValue])=>[
15225
+ key,
15226
+ normalizeForComparison(entryValue)
15227
+ ]));
15228
+ return value;
15229
+ }
15230
+ function cachedManifestEntry(dep, packageName) {
15231
+ return {
15232
+ app_id: dep.app_id,
15233
+ commit_id: dep.commit_id,
15234
+ ...dep.branch ? {
15235
+ branch: dep.branch
15236
+ } : {},
15237
+ package_name: packageName
15238
+ };
15239
+ }
15240
+ function safeDependencyPath(filePath, kind) {
15241
+ const normalizedPath = external_path_default().normalize(filePath);
15242
+ if (external_path_default().isAbsolute(normalizedPath) || normalizedPath.startsWith("..")) throw new Error(`Invalid dependency ${kind} path: ${filePath}`);
15243
+ return normalizedPath;
15244
+ }
15245
+ function dependencyPythonPath(name, kind) {
15246
+ const parts = name.split(".");
15247
+ if (0 === parts.length || parts.some((part)=>!part || "." === part || ".." === part || part.includes("/") || part.includes("\\"))) throw new Error(`Invalid dependency ${kind} name: ${name}`);
15248
+ return external_path_default().join(...parts);
15249
+ }
15250
+ async function writeDepToCache(projectRoot, dep) {
15251
+ const depDir = external_path_default().join(projectRoot, ANVIL_DEPS_DIR, dep.package_name);
15252
+ await external_fs_promises_default().rm(depDir, {
15253
+ recursive: true,
15254
+ force: true
15255
+ });
15256
+ await external_fs_promises_default().mkdir(external_path_default().join(depDir, "client_code"), {
15257
+ recursive: true
15258
+ });
15259
+ await external_fs_promises_default().mkdir(external_path_default().join(depDir, "server_code"), {
15260
+ recursive: true
15261
+ });
15262
+ await external_fs_promises_default().writeFile(external_path_default().join(depDir, "anvil.yaml"), external_js_yaml_default().dump({
15263
+ package_name: dep.package_name
15264
+ }));
15265
+ for (const mod of dep.modules ?? []){
15266
+ const modulePath = external_path_default().join(depDir, "client_code", dependencyPythonPath(mod.name, "module"));
15267
+ if (mod.is_package) {
15268
+ await external_fs_promises_default().mkdir(modulePath, {
15269
+ recursive: true
15270
+ });
15271
+ await external_fs_promises_default().writeFile(external_path_default().join(modulePath, "__init__.py"), mod.code);
15272
+ } else {
15273
+ const moduleFile = `${modulePath}.py`;
15274
+ await external_fs_promises_default().mkdir(external_path_default().dirname(moduleFile), {
15275
+ recursive: true
15276
+ });
15277
+ await external_fs_promises_default().writeFile(moduleFile, mod.code);
15025
15278
  }
15026
15279
  }
15027
- async mergeFastForward(ref) {
15028
- try {
15029
- await this.git.raw([
15030
- "merge",
15031
- "--ff-only",
15032
- ref
15033
- ]);
15034
- } catch (e) {
15035
- throw errors_createGitError.commandFailed("merge --ff-only", e.message);
15280
+ for (const form of dep.forms ?? []){
15281
+ const { code, class_name, is_package, ...formTemplate } = form;
15282
+ const formPath = external_path_default().join(depDir, "client_code", dependencyPythonPath(class_name, "form"));
15283
+ if (is_package) {
15284
+ await external_fs_promises_default().mkdir(formPath, {
15285
+ recursive: true
15286
+ });
15287
+ await external_fs_promises_default().writeFile(external_path_default().join(formPath, "__init__.py"), code);
15288
+ await external_fs_promises_default().writeFile(external_path_default().join(formPath, "form_template.yaml"), external_js_yaml_default().dump(formTemplate));
15289
+ } else {
15290
+ const formCodeFile = `${formPath}.py`;
15291
+ await external_fs_promises_default().mkdir(external_path_default().dirname(formCodeFile), {
15292
+ recursive: true
15293
+ });
15294
+ await external_fs_promises_default().writeFile(formCodeFile, code);
15295
+ await external_fs_promises_default().writeFile(`${formPath}.yaml`, external_js_yaml_default().dump(formTemplate));
15036
15296
  }
15037
15297
  }
15038
- async deleteRef(ref) {
15039
- try {
15040
- await this.git.raw([
15041
- "update-ref",
15042
- "-d",
15043
- ref
15044
- ]);
15045
- } catch (e) {}
15046
- }
15047
- async getRemotes() {
15048
- try {
15049
- const remotes = await this.git.getRemotes(true);
15050
- return remotes.map((r)=>({
15051
- name: r.name,
15052
- fetchUrl: r.refs.fetch
15053
- }));
15054
- } catch (e) {
15055
- throw errors_createGitError.commandFailed("remote", e.message);
15298
+ for (const mod of dep.server_modules ?? []){
15299
+ const modulePath = external_path_default().join(depDir, "server_code", dependencyPythonPath(mod.name, "server module"));
15300
+ if (mod.is_package) {
15301
+ await external_fs_promises_default().mkdir(modulePath, {
15302
+ recursive: true
15303
+ });
15304
+ await external_fs_promises_default().writeFile(external_path_default().join(modulePath, "__init__.py"), mod.code);
15305
+ } else {
15306
+ const moduleFile = `${modulePath}.py`;
15307
+ await external_fs_promises_default().mkdir(external_path_default().dirname(moduleFile), {
15308
+ recursive: true
15309
+ });
15310
+ await external_fs_promises_default().writeFile(moduleFile, mod.code);
15056
15311
  }
15057
15312
  }
15058
- async isGitInitialized() {
15059
- const gitPath = external_path_default().join(this.repoPath, ".git");
15060
- return external_fs_.existsSync(gitPath);
15313
+ for (const asset of dep.assets ?? []){
15314
+ const targetPath = external_path_default().join(depDir, "theme", "assets", safeDependencyPath(asset.name, "asset"));
15315
+ await external_fs_promises_default().mkdir(external_path_default().dirname(targetPath), {
15316
+ recursive: true
15317
+ });
15318
+ await external_fs_promises_default().writeFile(targetPath, Buffer.from(asset.content, "base64"));
15061
15319
  }
15062
- async removeEmptyDirectories(removedFiles) {
15063
- const directoriesToCheck = new Set();
15064
- for (const file of removedFiles){
15065
- let dir = external_path_default().dirname(file);
15066
- while(dir && "." !== dir){
15067
- directoriesToCheck.add(dir);
15068
- const parentDir = external_path_default().dirname(dir);
15069
- if (parentDir === dir) break;
15070
- dir = parentDir;
15071
- }
15072
- }
15073
- const sortedDirs = Array.from(directoriesToCheck).sort((a, b)=>b.split("/").length - a.split("/").length);
15074
- for (const dir of sortedDirs)try {
15075
- const dirPath = external_path_default().join(this.repoPath, dir);
15076
- if (!external_fs_.existsSync(dirPath)) continue;
15077
- const entries = await external_fs_.promises.readdir(dirPath);
15078
- if (0 === entries.length) await external_fs_.promises.rmdir(dirPath);
15079
- } catch (error) {}
15320
+ for (const doc of dep.docs ?? []){
15321
+ const targetPath = external_path_default().join(depDir, safeDependencyPath(doc.path, "doc"));
15322
+ await external_fs_promises_default().mkdir(external_path_default().dirname(targetPath), {
15323
+ recursive: true
15324
+ });
15325
+ await external_fs_promises_default().writeFile(targetPath, doc.content);
15080
15326
  }
15081
15327
  }
15082
- async function getStagedFileChanges(git, gitService) {
15083
- try {
15084
- const status = await git.status();
15085
- const renames = status.renamed || [];
15086
- const stagedFiles = status.files.filter((f)=>" " !== f.index && "?" !== f.index);
15087
- const stagedModeOnlyPaths = new Set(gitService ? await gitService.discardStagedModeOnlyChanges(stagedFiles.filter((f)=>"R" !== f.index && "D" !== f.index).map((f)=>f.path)) : []);
15088
- const stagedRenames = [];
15089
- for (const f of stagedFiles)if ("R" === f.index) {
15090
- const rename = renames.find((r)=>r.to === f.path);
15091
- if (rename) stagedRenames.push({
15092
- path: rename.to,
15093
- type: "rename",
15094
- from: rename.from
15095
- });
15096
- }
15097
- const stagedRenamedFromPaths = new Set(stagedRenames.map((r)=>r.from));
15098
- const stagedRenamedToPaths = new Set(stagedRenames.map((r)=>r.path));
15099
- const changes = [];
15100
- for (const f of stagedFiles){
15101
- if ("R" !== f.index) {
15102
- if (!stagedModeOnlyPaths.has(f.path)) {
15103
- if (!(stagedRenamedFromPaths.has(f.path) || stagedRenamedToPaths.has(f.path))) if ("D" === f.index) changes.push({
15104
- path: f.path,
15105
- type: "unlink"
15106
- });
15107
- else if ("A" === f.index || "?" === f.index) changes.push({
15108
- path: f.path,
15109
- type: "add"
15110
- });
15111
- else changes.push({
15112
- path: f.path,
15113
- type: "change"
15114
- });
15115
- }
15116
- }
15117
- }
15118
- changes.push(...stagedRenames);
15119
- return changes;
15120
- } catch (e) {
15121
- throw errors_createGitError.commandFailed("status", e.message);
15122
- }
15328
+ async function writeAllDepsToCache(projectRoot, response, options = {}) {
15329
+ const includeDocs = options.includeDocs ?? true;
15330
+ const manifest = await readDepsCacheManifest(projectRoot);
15331
+ const canReuseExistingCache = manifest?.includeDocs === includeDocs;
15332
+ if (!canReuseExistingCache) await clearDepsCache(projectRoot);
15333
+ const previousCached = canReuseExistingCache ? manifest.cached_dependencies : [];
15334
+ const resolvedAppIds = new Set(response.resolved_dependencies.map((dep)=>dep.app_id));
15335
+ for (const dep of previousCached)if (!resolvedAppIds.has(dep.app_id)) await external_fs_promises_default().rm(external_path_default().join(projectRoot, ANVIL_DEPS_DIR, dep.package_name), {
15336
+ recursive: true,
15337
+ force: true
15338
+ });
15339
+ for (const dep of response.dependencies)await writeDepToCache(projectRoot, dep);
15340
+ const returnedByAppId = new Map(response.dependencies.map((dep)=>[
15341
+ dep.app_id,
15342
+ dep
15343
+ ]));
15344
+ const previousByAppId = new Map(previousCached.map((dep)=>[
15345
+ dep.app_id,
15346
+ dep
15347
+ ]));
15348
+ const cachedDependencies = response.resolved_dependencies.map((dep)=>{
15349
+ const returned = returnedByAppId.get(dep.app_id);
15350
+ if (returned) return cachedManifestEntry(returned, returned.package_name);
15351
+ const previous = previousByAppId.get(dep.app_id);
15352
+ if (!previous) return;
15353
+ return cachedManifestEntry(dep, previous.package_name);
15354
+ }).filter((dep)=>void 0 !== dep);
15355
+ const newManifest = {
15356
+ version: DEPS_MANIFEST_VERSION,
15357
+ includeDocs,
15358
+ source_dependencies: void 0 === options.sourceDependencies ? await readAnvilYamlDependencies(projectRoot) : options.sourceDependencies,
15359
+ resolved_dependencies: response.resolved_dependencies,
15360
+ cached_dependencies: cachedDependencies
15361
+ };
15362
+ await external_fs_promises_default().mkdir(depsDir(projectRoot), {
15363
+ recursive: true
15364
+ });
15365
+ await external_fs_promises_default().writeFile(depsManifestPath(projectRoot), `${JSON.stringify(newManifest, null, 2)}\n`);
15366
+ }
15367
+ async function clearDepsCache(projectRoot) {
15368
+ await external_fs_promises_default().rm(depsDir(projectRoot), {
15369
+ recursive: true,
15370
+ force: true
15371
+ });
15123
15372
  }
15124
15373
  const external_chokidar_namespaceObject = require("chokidar");
15125
15374
  var external_chokidar_default = /*#__PURE__*/ __webpack_require__.n(external_chokidar_namespaceObject);
@@ -15892,7 +16141,8 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
15892
16141
  if (PYTHON_KEYWORDS.has(value)) pushIssue(issues, path, "must be a valid Python identifier (not a keyword)");
15893
16142
  }
15894
16143
  const componentTypeSpec_IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
15895
- const DEP_ID_RE = /^dep_[A-Za-z0-9]+$/;
16144
+ const LEGACY_CUSTOM_COMPONENT_SPEC_RE = /^form:(?:([^:]+):)?(.+)$/;
16145
+ const PACKAGE_QUALIFIED_FORM_NAME_RE = /^([^.]+)\.(.+)$/;
15896
16146
  const componentTypeSpec_PYTHON_KEYWORDS = new Set([
15897
16147
  "False",
15898
16148
  "None",
@@ -15932,32 +16182,17 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
15932
16182
  "match",
15933
16183
  "case"
15934
16184
  ]);
15935
- const LEGACY_FORM_TYPE_REFERENCE_MESSAGE = "must be a valid form: reference (form:Module.Form, form:dep_xxx:Package.Form)";
16185
+ const LEGACY_FORM_TYPE_REFERENCE_MESSAGE = "must be a valid form: reference (form:Module.Form, form:dep_xxx:Package.Form, form:APP_ID:Package.Form)";
15936
16186
  const COMPONENT_TYPE_REFERENCE_MESSAGE = "must be a known Anvil component type (see componentIcons.ts), a form: reference, or a package-qualified form reference (Package.Form)";
15937
16187
  function isValidIdentifierSegment(value) {
15938
16188
  return value.length > 0 && componentTypeSpec_IDENTIFIER_RE.test(value) && !componentTypeSpec_PYTHON_KEYWORDS.has(value);
15939
16189
  }
15940
16190
  function isValidLegacyFormTypeReference(typeName) {
15941
- if (!typeName.startsWith("form:")) return false;
15942
- const rest = typeName.slice(5);
15943
- if (0 === rest.length) return false;
15944
- let classPath = rest;
15945
- if (rest.startsWith("dep_")) {
15946
- const splitIndex = rest.indexOf(":");
15947
- if (splitIndex <= 0) return false;
15948
- const depId = rest.slice(0, splitIndex);
15949
- if (!DEP_ID_RE.test(depId)) return false;
15950
- classPath = rest.slice(splitIndex + 1);
15951
- }
15952
- if (0 === classPath.length) return false;
15953
- return classPath.split(".").every((part)=>isValidIdentifierSegment(part));
16191
+ return LEGACY_CUSTOM_COMPONENT_SPEC_RE.test(typeName);
15954
16192
  }
15955
16193
  function isValidPackageQualifiedComponentType(typeName) {
15956
16194
  if (typeName.startsWith("anvil.")) return false;
15957
- if (!typeName.includes(".")) return false;
15958
- const parts = typeName.split(".");
15959
- if (parts.length < 2) return false;
15960
- return parts.every((part)=>isValidIdentifierSegment(part));
16195
+ return PACKAGE_QUALIFIED_FORM_NAME_RE.test(typeName);
15961
16196
  }
15962
16197
  function componentTypeSpec_pushIssue(issues, path, message) {
15963
16198
  issues.push({
@@ -16040,8 +16275,15 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
16040
16275
  message
16041
16276
  });
16042
16277
  }
16278
+ function validateNonEmptyString(value, path, issues) {
16279
+ if ("string" != typeof value || 0 === value.length) {
16280
+ formTemplateValidation_pushIssue(issues, path, "must be a non-empty string");
16281
+ return false;
16282
+ }
16283
+ return true;
16284
+ }
16043
16285
  function validateIdentifier(value, path, issues) {
16044
- if ("string" != typeof value || 0 === value.length) return void formTemplateValidation_pushIssue(issues, path, "must be a non-empty string");
16286
+ if (!validateNonEmptyString(value, path, issues)) return;
16045
16287
  if (!formTemplateValidation_IDENTIFIER_RE.test(value)) return void formTemplateValidation_pushIssue(issues, path, "must be a valid identifier");
16046
16288
  if (formTemplateValidation_PYTHON_KEYWORDS.has(value)) formTemplateValidation_pushIssue(issues, path, "must be a valid Python identifier (not a keyword)");
16047
16289
  }
@@ -16066,7 +16308,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
16066
16308
  const bindingPath = `${path}[${index}]`;
16067
16309
  if (!isPlainObject(binding)) return void formTemplateValidation_pushIssue(issues, bindingPath, "must be an object");
16068
16310
  if ("string" != typeof binding.property || 0 === binding.property.length) formTemplateValidation_pushIssue(issues, `${bindingPath}.property`, "must be a non-empty string");
16069
- if ("string" != typeof binding.code || 0 === binding.code.length) formTemplateValidation_pushIssue(issues, `${bindingPath}.code`, "must be a non-empty string");
16311
+ if ("string" != typeof binding.code) formTemplateValidation_pushIssue(issues, `${bindingPath}.code`, "must be a string");
16070
16312
  if (void 0 !== binding.writeback && "boolean" != typeof binding.writeback) formTemplateValidation_pushIssue(issues, `${bindingPath}.writeback`, "must be a boolean");
16071
16313
  });
16072
16314
  }
@@ -16124,7 +16366,8 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
16124
16366
  validatePropertiesObject(value.set_layout_properties, `${path}.set_layout_properties`, issues);
16125
16367
  if (isPlainObject(value.target)) {
16126
16368
  if ("container" !== value.target.type && "slot" !== value.target.type) formTemplateValidation_pushIssue(issues, `${path}.target.type`, "must be 'container' or 'slot'");
16127
- if ("container" !== value.target.type || "" !== value.target.name) validateIdentifier(value.target.name, `${path}.target.name`, issues);
16369
+ if ("slot" === value.target.type) validateNonEmptyString(value.target.name, `${path}.target.name`, issues);
16370
+ else if ("container" !== value.target.type || "" !== value.target.name) validateIdentifier(value.target.name, `${path}.target.name`, issues);
16128
16371
  } else formTemplateValidation_pushIssue(issues, `${path}.target`, "must be an object");
16129
16372
  if (void 0 !== value.template) validateComponentNode(value.template, `${path}.template`, issues, seenNames);
16130
16373
  }
@@ -16181,7 +16424,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
16181
16424
  } else {
16182
16425
  const componentNames = options.componentNames ?? new Set();
16183
16426
  for (const [slotName, slotDef] of Object.entries(value.slots)){
16184
- validateIdentifier(slotName, path(`slots.${slotName}.name`), issues);
16427
+ validateNonEmptyString(slotName, path(`slots.${slotName}.name`), issues);
16185
16428
  validateSlotDefinition(slotDef, path(`slots.${slotName}`), issues, componentNames);
16186
16429
  }
16187
16430
  }
@@ -16189,6 +16432,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
16189
16432
  }
16190
16433
  if (void 0 !== value.toolbox_item && !isPlainObject(value.toolbox_item)) formTemplateValidation_pushIssue(issues, path("toolbox_item"), "must be an object");
16191
16434
  if (void 0 !== value.layout_metadata && !isPlainObject(value.layout_metadata)) formTemplateValidation_pushIssue(issues, path("layout_metadata"), "must be an object");
16435
+ if (void 0 !== value.help_text && "string" != typeof value.help_text) formTemplateValidation_pushIssue(issues, path("help_text"), "must be a string");
16192
16436
  if (void 0 !== value.item_type) if (isPlainObject(value.item_type)) {
16193
16437
  if ("number" != typeof value.item_type.table_id) formTemplateValidation_pushIssue(issues, path("item_type.table_id"), "must be a number");
16194
16438
  } else formTemplateValidation_pushIssue(issues, path("item_type"), "must be an object");
@@ -16263,7 +16507,8 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
16263
16507
  "toolbox_item",
16264
16508
  "layout_metadata",
16265
16509
  "item_type",
16266
- "slots"
16510
+ "slots",
16511
+ "help_text"
16267
16512
  ]);
16268
16513
  const FRONTMATTER_DISALLOWED_KEYS = new Set([
16269
16514
  "container",
@@ -16382,7 +16627,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
16382
16627
  function validateParsedHtmlTemplate(parsed) {
16383
16628
  return validateFormTemplateData(parsed);
16384
16629
  }
16385
- function validateFormTemplateHtml(htmlContent, parseHtml = form_template_parser_namespaceObject.parseSerializedHtml) {
16630
+ function validateFormTemplateHtml(htmlContent, parseHtml = form_template_parser_namespaceObject.parseSerializedHtml, options = {}) {
16386
16631
  const { frontmatter, body, bodyStartOffset } = splitHtmlFormTemplateFrontmatter(htmlContent);
16387
16632
  if (null !== frontmatter) {
16388
16633
  const frontmatterIssues = validateHtmlFrontmatter(frontmatter);
@@ -16395,7 +16640,10 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
16395
16640
  let parsed;
16396
16641
  try {
16397
16642
  parsed = parseHtml(body, {
16398
- warnings: true
16643
+ warnings: true,
16644
+ ...options.formSpecPackageContext ? {
16645
+ formSpecPackageContext: options.formSpecPackageContext
16646
+ } : {}
16399
16647
  });
16400
16648
  } catch (error) {
16401
16649
  return {
@@ -16426,6 +16674,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
16426
16674
  ok: true
16427
16675
  };
16428
16676
  }
16677
+ (0, external_module_namespaceObject.createRequire)(__filename);
16429
16678
  const PYTHON_VALIDATOR_SCRIPT = String.raw`
16430
16679
  import ast
16431
16680
  import json
@@ -16889,7 +17138,7 @@ print(json.dumps({"issues": issues}))
16889
17138
  }
16890
17139
  function validateScheduledTasks(value, path) {
16891
17140
  const issues = [];
16892
- if (void 0 === value) return issues;
17141
+ if (null == value) return issues;
16893
17142
  if (!Array.isArray(value)) {
16894
17143
  validators_pushIssue(issues, path, "must be an array");
16895
17144
  return issues;
@@ -17037,15 +17286,25 @@ print(json.dumps({"issues": issues}))
17037
17286
  const existingPath = resolveExistingPath(filePath);
17038
17287
  if (!existingPath) return null;
17039
17288
  let currentDir = external_fs_.statSync(existingPath).isDirectory() ? existingPath : external_path_default().dirname(existingPath);
17040
- let appRoot = null;
17289
+ const candidates = [];
17041
17290
  while(true){
17042
- if (external_fs_.existsSync(external_path_default().join(currentDir, "anvil.yaml"))) appRoot = currentDir;
17291
+ if (external_fs_.existsSync(external_path_default().join(currentDir, "anvil.yaml"))) candidates.push(currentDir);
17043
17292
  const parentDir = external_path_default().dirname(currentDir);
17044
- if (parentDir === currentDir) return appRoot;
17293
+ if (parentDir === currentDir) break;
17045
17294
  currentDir = parentDir;
17046
17295
  }
17296
+ return candidates.find((candidate)=>!candidates.some((outerCandidate)=>{
17297
+ if (candidate === outerCandidate) return false;
17298
+ return [
17299
+ "client_code",
17300
+ "server_code"
17301
+ ].some((codeDir)=>{
17302
+ const relativePath = external_path_default().relative(external_path_default().join(outerCandidate, codeDir), candidate);
17303
+ return !external_path_default().isAbsolute(relativePath) && ".." !== relativePath && !relativePath.startsWith(`..${external_path_default().sep}`);
17304
+ });
17305
+ })) ?? null;
17047
17306
  }
17048
- function validatePath(filePath, fileContent) {
17307
+ function validatePath(filePath, fileContent, options = {}) {
17049
17308
  const target = inferValidationTarget(filePath);
17050
17309
  const existingPath = resolveExistingPath(filePath);
17051
17310
  const appRoot = findAnvilAppRoot(filePath);
@@ -17118,7 +17377,9 @@ print(json.dumps({"issues": issues}))
17118
17377
  ]
17119
17378
  };
17120
17379
  }
17121
- const result = validateFormTemplateHtml(fileContent);
17380
+ const result = validateFormTemplateHtml(fileContent, void 0, {
17381
+ formSpecPackageContext: options.formSpecPackageContext
17382
+ });
17122
17383
  return result.ok ? validPathResult(target, result.warnings) : {
17123
17384
  ...result,
17124
17385
  target
@@ -17935,7 +18196,7 @@ print(json.dumps({"issues": issues}))
17935
18196
  ]
17936
18197
  });
17937
18198
  }
17938
- async function buildFormTemplateData(relativePath, templateInfo, context) {
18199
+ async function buildFormTemplateData(relativePath, templateInfo, context, missingCodeBehavior = "error") {
17939
18200
  const { repoPath, stagedOnly = false } = context;
17940
18201
  const { kind } = templateInfo;
17941
18202
  const loadResult = await loadFormTemplateData(relativePath, templateInfo, context);
@@ -17949,15 +18210,28 @@ print(json.dumps({"issues": issues}))
17949
18210
  };
17950
18211
  const codeRelativePath = "package" === kind ? relativePath.replace(/form_template\.(yaml|html)$/i, "__init__.py") : relativePath.replace(/\.(yaml|html)$/i, ".py");
17951
18212
  const className = extractPythonName(codeRelativePath);
17952
- const codeContent = await readFileContent(repoPath, codeRelativePath, stagedOnly);
17953
- if (!codeContent) return {
17954
- formData: {},
17955
- className: "",
17956
- ignoreResult: {
17957
- type: "ignore",
17958
- reason: "package" === kind ? "Form __init__.py file not found - cannot save template without code" : "Form .py file not found - cannot save template without code"
18213
+ let codeContent;
18214
+ try {
18215
+ codeContent = await readFileContent(repoPath, codeRelativePath, stagedOnly);
18216
+ } catch (error) {
18217
+ if (stagedOnly || !isFileNotFoundError(error) || "error" === missingCodeBehavior) throw error;
18218
+ const missingCodeReason = `Form code file ${codeRelativePath} is not present yet`;
18219
+ const knownForm = context.editorYaml.hasId("forms", className);
18220
+ if ("defer" === missingCodeBehavior || knownForm) {
18221
+ const action = knownForm ? `not saving existing form ${className}` : `deferring form ${className}`;
18222
+ logger_logger.warn(external_chalk_default().yellow(`Form template ${relativePath} has no code file ${codeRelativePath} yet; ${action}.`));
18223
+ return {
18224
+ formData: {},
18225
+ className: "",
18226
+ ignoreResult: {
18227
+ type: "ignore",
18228
+ reason: missingCodeReason
18229
+ }
18230
+ };
17959
18231
  }
17960
- };
18232
+ codeContent = "";
18233
+ logger_logger.warn(external_chalk_default().yellow(`Form template ${relativePath} has no code file ${codeRelativePath} yet; creating ${className} with empty code.`));
18234
+ }
17961
18235
  const formData = (0, form_template_parser_namespaceObject.buildFormTemplateSavePayload)(loadResult.source, {
17962
18236
  className,
17963
18237
  code: codeContent,
@@ -17969,6 +18243,9 @@ print(json.dumps({"issues": issues}))
17969
18243
  ignoreResult: null
17970
18244
  };
17971
18245
  }
18246
+ function isFileNotFoundError(error) {
18247
+ return "object" == typeof error && null !== error && "ENOENT" === error.code;
18248
+ }
17972
18249
  async function handleFormTemplateChange(change, context) {
17973
18250
  const { editorYaml } = context;
17974
18251
  const { relativePath, changeType } = change;
@@ -17979,10 +18256,11 @@ print(json.dumps({"issues": issues}))
17979
18256
  };
17980
18257
  const kind = templateInfo.kind;
17981
18258
  const format = templateInfo.format;
18259
+ const missingCodeBehavior = "add" === changeType ? "create-new-form-if-unknown" : "defer";
17982
18260
  const result = await buildFormTemplateData(relativePath, {
17983
18261
  kind,
17984
18262
  format
17985
- }, context);
18263
+ }, context, missingCodeBehavior);
17986
18264
  if (result.ignoreResult) return result.ignoreResult;
17987
18265
  const { formData, className } = result;
17988
18266
  return handleIdBasedOperation({
@@ -18268,10 +18546,13 @@ print(json.dumps({"issues": issues}))
18268
18546
  code: error.type,
18269
18547
  recoverability: "requires_user_action",
18270
18548
  requiresUserAction: true,
18271
- suggestedAction: "Inspect the repository state and resolve the Git error before retrying.",
18549
+ suggestedAction: isIndexLockFailure(error) ? "Confirm no Git process is running for this repository, then remove the stale .git/index.lock file and retry." : "Inspect the repository state and resolve the Git error before retrying.",
18272
18550
  gitCommand: "git_command_failed" === error.type ? error.command : void 0
18273
18551
  };
18274
18552
  }
18553
+ function isIndexLockFailure(error) {
18554
+ return "git_command_failed" === error.type && error.message.includes("Unable to create") && error.message.includes("index.lock") && error.message.includes("File exists");
18555
+ }
18275
18556
  function networkDiagnostic(error) {
18276
18557
  return {
18277
18558
  code: error.type,
@@ -18356,6 +18637,15 @@ print(json.dumps({"issues": issues}))
18356
18637
  this.hasPendingChanges = true;
18357
18638
  await this.processSaveBatchWithEvents();
18358
18639
  }
18640
+ async flush() {
18641
+ const requireReady = ()=>{
18642
+ if (this.config.isPaused() || this.config.isCleanedUp() || !this.config.isSettlePeriodOver()) throw new Error("Cannot flush saves while watching is paused, closed, or changing branch");
18643
+ };
18644
+ requireReady();
18645
+ await this.forceSave();
18646
+ requireReady();
18647
+ if (this.hasPendingChanges) throw new Error("Saves remain pending");
18648
+ }
18359
18649
  cleanup() {
18360
18650
  if (this.hasPendingChanges) {
18361
18651
  logger_logger.progressEnd("sync");
@@ -18452,10 +18742,7 @@ print(json.dumps({"issues": issues}))
18452
18742
  status = await this.config.gitService.getStatus();
18453
18743
  } catch (e) {
18454
18744
  logger_logger.error(external_chalk_default().red(`Error reading git status: ${errors_getErrorMessage(e)}`));
18455
- return {
18456
- skipped: true,
18457
- reason: "Git status error"
18458
- };
18745
+ throw e;
18459
18746
  }
18460
18747
  let allChanges;
18461
18748
  if (this.config.stagedOnly) {
@@ -19301,6 +19588,7 @@ print(json.dumps({"issues": issues}))
19301
19588
  try {
19302
19589
  const previousSubscriptions = this.wsClient?.getDependencySubscriptions() ?? [];
19303
19590
  const { response, addedGitignoreEntry } = await refreshDependencyCache(this.repoPath, this.appId, this.anvilUrl, {
19591
+ username: this.username,
19304
19592
  includeDocs: true
19305
19593
  });
19306
19594
  if (addedGitignoreEntry) logger_logger.info("Added /.anvil/ to .gitignore");
@@ -19612,6 +19900,12 @@ print(json.dumps({"issues": issues}))
19612
19900
  if (!this.saveProcessor) return;
19613
19901
  return this.saveProcessor.forceSave();
19614
19902
  }
19903
+ async flushSaves() {
19904
+ if (!this.saveProcessor || this.isSwitchingForEnvironment) throw new Error("App synchronization is not ready");
19905
+ const branch = this.currentBranch;
19906
+ await this.saveProcessor.flush();
19907
+ if (this.isSwitchingForEnvironment || this.currentBranch !== branch || await this.gitService.getCurrentBranch() !== branch) throw new Error("App branch changed while flushing saves");
19908
+ }
19615
19909
  async getSuccessMessage() {
19616
19910
  try {
19617
19911
  const info = await this.gitService.getCommitInfo();
@@ -19872,6 +20166,182 @@ print(json.dumps({"issues": issues}))
19872
20166
  }
19873
20167
  throw new Error("Device login expired before it was approved.");
19874
20168
  }
20169
+ const CHECKOUT_ERROR_VALUE = "__ERROR__";
20170
+ function isAbortLikeError(error) {
20171
+ if (error instanceof Error && "AbortError" === error.name) return true;
20172
+ if ("object" == typeof error && null !== error) {
20173
+ const maybeType = error.type;
20174
+ const maybeMessage = error.message;
20175
+ if ("network_error" === maybeType && "string" == typeof maybeMessage && maybeMessage.toLowerCase().includes("abort")) return true;
20176
+ }
20177
+ const message = errors_getErrorMessage(error).toLowerCase();
20178
+ return "aborted" === message || message.includes("aborted");
20179
+ }
20180
+ function normalizePickerQuery(term) {
20181
+ return (term || "").trim();
20182
+ }
20183
+ function parseNonNegativeMs(raw) {
20184
+ if (null == raw) return null;
20185
+ const parsed = Number(raw);
20186
+ if (!Number.isFinite(parsed) || parsed < 0) return null;
20187
+ return parsed;
20188
+ }
20189
+ function getCheckoutPickerDelayMs() {
20190
+ const fromEnv = parseNonNegativeMs(process.env.ANVIL_CHECKOUT_PICKER_DELAY_MS);
20191
+ if (null != fromEnv) return fromEnv;
20192
+ return 0;
20193
+ }
20194
+ function getCheckoutPickerPaginationDelayMs() {
20195
+ return getCheckoutPickerDelayMs();
20196
+ }
20197
+ function formatLastEdited(lastEdited) {
20198
+ if (!lastEdited) return;
20199
+ return external_chalk_default().gray(`Last edited: ${lastEdited}`);
20200
+ }
20201
+ function formatAppPickerLabel(app) {
20202
+ const appName = app.app_name || "Unnamed App";
20203
+ return `${external_chalk_default().cyan(appName)} ${external_chalk_default().gray(`(${app.app_id})`)}`;
20204
+ }
20205
+ async function waitForDebounce(ms, signal) {
20206
+ if (ms <= 0) return;
20207
+ if (signal.aborted) throw new Error("aborted");
20208
+ await new Promise((resolve, reject)=>{
20209
+ const timer = setTimeout(()=>{
20210
+ cleanup();
20211
+ resolve();
20212
+ }, ms);
20213
+ const onAbort = ()=>{
20214
+ cleanup();
20215
+ reject(new Error("aborted"));
20216
+ };
20217
+ const cleanup = ()=>{
20218
+ clearTimeout(timer);
20219
+ signal.removeEventListener("abort", onAbort);
20220
+ };
20221
+ signal.addEventListener("abort", onAbort, {
20222
+ once: true
20223
+ });
20224
+ });
20225
+ }
20226
+ function createCheckoutPickerSource(anvilUrl, username, deps, options = {}) {
20227
+ const pageSize = options.pageSize ?? 20;
20228
+ const debounceMs = options.debounceMs ?? 200;
20229
+ const staleTimeMs = options.staleTimeMs ?? 30000;
20230
+ const maxAutoPagesPerCall = options.maxAutoPagesPerCall ?? 8;
20231
+ const initialQuery = normalizePickerQuery(options.initialQuery);
20232
+ const simulatedDelayMs = getCheckoutPickerDelayMs();
20233
+ const paginationDelayMs = getCheckoutPickerPaginationDelayMs();
20234
+ const cache = new Map();
20235
+ const getState = (query)=>{
20236
+ const existing = cache.get(query);
20237
+ if (existing) return existing;
20238
+ const created = {
20239
+ items: [],
20240
+ nextCursor: null,
20241
+ exhausted: false,
20242
+ lastError: null,
20243
+ updatedAt: 0,
20244
+ inFlight: null
20245
+ };
20246
+ cache.set(query, created);
20247
+ return created;
20248
+ };
20249
+ const fetchPage = async (query, signal, options = {})=>{
20250
+ const state = getState(query);
20251
+ if (state.inFlight) return void await state.inFlight;
20252
+ state.inFlight = (async ()=>{
20253
+ try {
20254
+ if (simulatedDelayMs > 0) await new Promise((resolve)=>setTimeout(resolve, simulatedDelayMs));
20255
+ if (options.cursor && paginationDelayMs > 0) await new Promise((resolve)=>setTimeout(resolve, paginationDelayMs));
20256
+ const response = await deps.listAppsForCheckout({
20257
+ anvilUrl,
20258
+ username,
20259
+ limit: pageSize,
20260
+ cursor: options.cursor,
20261
+ q: query || void 0,
20262
+ signal
20263
+ });
20264
+ if (options.reset) state.items = [
20265
+ ...response.apps
20266
+ ];
20267
+ else state.items.push(...response.apps);
20268
+ state.nextCursor = response.next_cursor;
20269
+ state.exhausted = !response.next_cursor;
20270
+ state.lastError = null;
20271
+ state.updatedAt = Date.now();
20272
+ } catch (e) {
20273
+ if (!signal.aborted && !isAbortLikeError(e)) {
20274
+ const message = errors_getErrorMessage(e);
20275
+ state.lastError = message;
20276
+ state.updatedAt = Date.now();
20277
+ }
20278
+ } finally{
20279
+ state.inFlight = null;
20280
+ }
20281
+ })();
20282
+ await state.inFlight;
20283
+ };
20284
+ const ensureLoaded = async (query, signal)=>{
20285
+ const state = getState(query);
20286
+ const isStale = Date.now() - state.updatedAt > staleTimeMs;
20287
+ if (0 === state.items.length) {
20288
+ await waitForDebounce(debounceMs, signal);
20289
+ await fetchPage(query, signal, {
20290
+ cursor: void 0,
20291
+ reset: false
20292
+ });
20293
+ } else if (state.lastError) {
20294
+ await waitForDebounce(debounceMs, signal);
20295
+ await fetchPage(query, signal, {
20296
+ cursor: void 0,
20297
+ reset: true
20298
+ });
20299
+ } else if (isStale) {
20300
+ await waitForDebounce(debounceMs, signal);
20301
+ await fetchPage(query, signal, {
20302
+ cursor: void 0,
20303
+ reset: true
20304
+ });
20305
+ }
20306
+ let pagesLoaded = 0;
20307
+ while(state.nextCursor && pagesLoaded < maxAutoPagesPerCall){
20308
+ if (signal.aborted) throw new Error("aborted");
20309
+ const cursor = state.nextCursor;
20310
+ await fetchPage(query, signal, {
20311
+ cursor,
20312
+ reset: false
20313
+ });
20314
+ pagesLoaded += 1;
20315
+ }
20316
+ };
20317
+ return {
20318
+ source: async (term, { signal })=>{
20319
+ const query = normalizePickerQuery(term) || initialQuery;
20320
+ if (signal.aborted) return [];
20321
+ await ensureLoaded(query, signal);
20322
+ const state = getState(query);
20323
+ if (state.lastError) return [
20324
+ {
20325
+ name: `${external_chalk_default().yellow("Fetch failed")} ${external_chalk_default().gray(`(${state.lastError})`)}`,
20326
+ value: CHECKOUT_ERROR_VALUE,
20327
+ disabled: true
20328
+ }
20329
+ ];
20330
+ const summary = state.nextCursor ? external_chalk_default().gray(`Loaded ${state.items.length} apps • more available`) : external_chalk_default().gray(`Loaded ${state.items.length} apps`);
20331
+ const choices = state.items.map((app)=>({
20332
+ name: formatAppPickerLabel(app),
20333
+ value: app.app_id,
20334
+ description: formatLastEdited(app.last_edited)
20335
+ }));
20336
+ choices.unshift({
20337
+ name: summary,
20338
+ value: CHECKOUT_ERROR_VALUE,
20339
+ disabled: true
20340
+ });
20341
+ return choices;
20342
+ }
20343
+ };
20344
+ }
19875
20345
  require("inquirer");
19876
20346
  require("ora");
19877
20347
  const defaultGitAuthDoctorDeps = {
@@ -19950,29 +20420,30 @@ print(json.dumps({"issues": issues}))
19950
20420
  return;
19951
20421
  }
19952
20422
  }
19953
- function inspectCredentialHelperCommand(raw) {
20423
+ function inspectCredentialHelperCommand(raw, options = {}) {
19954
20424
  const details = {
19955
20425
  raw
19956
20426
  };
19957
20427
  try {
19958
20428
  const parsed = parseCredentialHelperCommand(raw);
19959
20429
  details.execPath = parsed.execPath;
19960
- details.helperPath = parsed.helperPath;
20430
+ details.helperPath = parsed.helperPath === GIT_CREDENTIAL_HELPER_LOCATOR && options.gitCommonDir ? external_path_default().join(options.gitCommonDir, GIT_CREDENTIAL_HELPER_FILENAME) : parsed.helperPath;
19961
20431
  } catch (e) {
19962
20432
  details.parseError = e.message;
19963
20433
  return details;
19964
20434
  }
19965
20435
  if (details.execPath) details.execExists = external_fs_default().existsSync(details.execPath);
19966
- if (details.helperPath) {
19967
- details.helperExists = external_fs_default().existsSync(details.helperPath);
20436
+ const helperPath = details.helperPath;
20437
+ if (helperPath && helperPath !== GIT_CREDENTIAL_HELPER_LOCATOR) {
20438
+ details.helperExists = external_fs_default().existsSync(helperPath);
19968
20439
  if (details.helperExists) {
19969
20440
  try {
19970
- const stat = external_fs_default().statSync(details.helperPath);
20441
+ const stat = external_fs_default().statSync(helperPath);
19971
20442
  details.helperExecutable = (73 & stat.mode) !== 0;
19972
20443
  } catch {
19973
20444
  details.helperExecutable = false;
19974
20445
  }
19975
- details.modulePath = readHelperModulePath(details.helperPath);
20446
+ details.modulePath = readHelperModulePath(helperPath);
19976
20447
  if (details.modulePath) details.moduleExists = external_fs_default().existsSync(details.modulePath);
19977
20448
  }
19978
20449
  }
@@ -20071,7 +20542,7 @@ print(json.dumps({"issues": issues}))
20071
20542
  child.stdin.end(input);
20072
20543
  });
20073
20544
  }
20074
- async function buildAppReport(repoPath, remote, deps) {
20545
+ async function buildAppReport(repoPath, gitCommonDir, remote, deps) {
20075
20546
  const appId = remote.appId;
20076
20547
  const anvilUrl = remote.anvilUrl;
20077
20548
  const url = new URL(anvilUrl);
@@ -20079,7 +20550,9 @@ print(json.dumps({"issues": issues}))
20079
20550
  const helperConfigKey = `credential.${credentialScope}.helper`;
20080
20551
  const helperValues = await getLocalConfigValues(repoPath, helperConfigKey);
20081
20552
  const nonEmptyHelperValues = helperValues.filter((value)=>"" !== value.trim());
20082
- const helperDetails = nonEmptyHelperValues.map(inspectCredentialHelperCommand);
20553
+ const helperDetails = nonEmptyHelperValues.map((value)=>inspectCredentialHelperCommand(value, {
20554
+ gitCommonDir
20555
+ }));
20083
20556
  const findings = [];
20084
20557
  if (0 === helperValues.length) findings.push(finding("error", "missing_helper_config", `No local ${helperConfigKey} entries found.`));
20085
20558
  else if (0 === nonEmptyHelperValues.length) findings.push(finding("error", "missing_helper_command", `${helperConfigKey} only resets helpers; no Anvil helper command is configured.`));
@@ -20158,12 +20631,18 @@ print(json.dumps({"issues": issues}))
20158
20631
  "--git-dir"
20159
20632
  ])).trim();
20160
20633
  const gitDir = external_path_default().isAbsolute(rawGitDir) ? rawGitDir : external_path_default().resolve(inputPath, rawGitDir);
20634
+ const rawGitCommonDir = (await git.raw([
20635
+ "rev-parse",
20636
+ "--git-common-dir"
20637
+ ])).trim();
20638
+ const resolvedGitCommonDir = external_path_default().isAbsolute(rawGitCommonDir) ? rawGitCommonDir : external_path_default().resolve(inputPath, rawGitCommonDir);
20639
+ const gitCommonDir = external_fs_default().realpathSync(resolvedGitCommonDir);
20161
20640
  auth_setRepoContext(repoPath);
20162
20641
  const remotes = (await git.getRemotes(true)).map(detectAnvilRemote);
20163
20642
  const anvilRemotes = remotes.filter((remote)=>remote.appId && remote.anvilUrl && "https" === remote.transport);
20164
20643
  const findings = [];
20165
20644
  if (0 === anvilRemotes.length) findings.push(finding("error", "missing_anvil_https_remote", "No HTTPS Anvil git remotes were detected."));
20166
- const apps = await Promise.all(anvilRemotes.map((remote)=>buildAppReport(repoPath, remote, deps)));
20645
+ const apps = await Promise.all(anvilRemotes.map((remote)=>buildAppReport(repoPath, gitCommonDir, remote, deps)));
20167
20646
  const status = getHighestStatus([
20168
20647
  ...findings.map((item)=>item.status),
20169
20648
  ...apps.flatMap((app)=>app.findings.map((item)=>item.status))
@@ -20171,6 +20650,7 @@ print(json.dumps({"issues": issues}))
20171
20650
  return {
20172
20651
  repoPath,
20173
20652
  gitDir,
20653
+ gitCommonDir,
20174
20654
  remotes,
20175
20655
  apps,
20176
20656
  findings,
@@ -20187,6 +20667,7 @@ print(json.dumps({"issues": issues}))
20187
20667
  lines.push(external_chalk_default().bold("Git auth doctor"));
20188
20668
  lines.push(`Repository: ${report.repoPath}`);
20189
20669
  lines.push(`Git dir: ${report.gitDir}`);
20670
+ lines.push(`Git common dir: ${report.gitCommonDir}`);
20190
20671
  lines.push(`Overall: ${formatStatus(report.status)}`);
20191
20672
  lines.push("");
20192
20673
  lines.push(external_chalk_default().bold("Detected remotes:"));
@@ -20295,7 +20776,29 @@ print(json.dumps({"issues": issues}))
20295
20776
  const response = await buildGitCredentialResponse(operation, request, options);
20296
20777
  writeGitCredentialResponse(response);
20297
20778
  }
20298
- resolveAnvilUrl();
20779
+ const MODEL_TOKEN_LOCK_TIMEOUT_MS = 12000;
20780
+ const modelToken_defaultDeps = {
20781
+ getValidAuthToken: auth_getValidAuthToken,
20782
+ readAuthFromRepo: readAuthFromRepo,
20783
+ cwd: ()=>process.cwd()
20784
+ };
20785
+ async function getModelToken(options = {}) {
20786
+ const deps = options.deps ?? modelToken_defaultDeps;
20787
+ const repoPath = options.repoPath ?? deps.cwd();
20788
+ auth_setRepoContext(repoPath);
20789
+ if (!process.env["ANVIL_AUTH_FILE_ENCRYPTION_KEY"]) throw new Error("ANVIL_AUTH_FILE_ENCRYPTION_KEY is not set; cannot read repo auth tokens.");
20790
+ const auth = await deps.readAuthFromRepo(repoPath);
20791
+ if (!auth) throw new Error(`No Anvil auth tokens found for the repository at ${repoPath}.`);
20792
+ return deps.getValidAuthToken(auth.anvilUrl, auth.username, {
20793
+ lock: {
20794
+ timeoutMs: options.lockTimeoutMs ?? MODEL_TOKEN_LOCK_TIMEOUT_MS
20795
+ }
20796
+ });
20797
+ }
20798
+ async function executeModelTokenOperation(options = {}) {
20799
+ const token = await getModelToken(options);
20800
+ process.stdout.write(`${token}\n`);
20801
+ }
20299
20802
  async function watch_decideFallbackUrl(explicitUrl, availableUrls) {
20300
20803
  if (explicitUrl) return {
20301
20804
  source: "explicit",
@@ -20506,7 +21009,6 @@ print(json.dumps({"issues": issues}))
20506
21009
  remoteName
20507
21010
  };
20508
21011
  }
20509
- const api_DEFAULT_ANVIL_URL = resolveAnvilUrl();
20510
21012
  async function api_checkUncommittedChanges(repoPath) {
20511
21013
  try {
20512
21014
  const git = esm_default(repoPath);
@@ -20555,9 +21057,10 @@ print(json.dumps({"issues": issues}))
20555
21057
  }
20556
21058
  }
20557
21059
  async function src_api_watch(repoPath, appId, options = {}) {
20558
- const { anvilUrl = api_DEFAULT_ANVIL_URL, gitUrl = anvilUrl, stagedOnly = false, username } = options;
20559
21060
  repoPath = external_path_default().resolve(repoPath);
20560
21061
  auth_setRepoContext(repoPath);
21062
+ const anvilUrl = options.anvilUrl ?? await auth_resolveAuthAnvilUrl();
21063
+ const { gitUrl = anvilUrl, stagedOnly = false, username } = options;
20561
21064
  const storedEnvPid = options.environment?.envPid ? void 0 : await environment_readEnvironmentPid(repoPath);
20562
21065
  const environment = options.environment ?? (storedEnvPid ? {
20563
21066
  envPid: storedEnvPid
@@ -20672,6 +21175,7 @@ print(json.dumps({"issues": issues}))
20672
21175
  }
20673
21176
  })();
20674
21177
  exports.ANVIL_SYNC_CLIENT_ID = __webpack_exports__.ANVIL_SYNC_CLIENT_ID;
21178
+ exports.AUTH_FILE_LOCK_DEFAULTS = __webpack_exports__.AUTH_FILE_LOCK_DEFAULTS;
20675
21179
  exports.AnvilYamlValidationResult = __webpack_exports__.AnvilYamlValidationResult;
20676
21180
  exports.AppIdCandidate = __webpack_exports__.AppIdCandidate;
20677
21181
  exports.AppIdWithContext = __webpack_exports__.AppIdWithContext;
@@ -20683,11 +21187,15 @@ exports.CheckoutOptions = __webpack_exports__.CheckoutOptions;
20683
21187
  exports.CheckoutResult = __webpack_exports__.CheckoutResult;
20684
21188
  exports.DeviceAuthorizationResponse = __webpack_exports__.DeviceAuthorizationResponse;
20685
21189
  exports.FormTemplateValidationResult = __webpack_exports__.FormTemplateValidationResult;
21190
+ exports.GetValidAuthTokenOptions = __webpack_exports__.GetValidAuthTokenOptions;
20686
21191
  exports.LoginResult = __webpack_exports__.LoginResult;
20687
21192
  exports.LogoutResult = __webpack_exports__.LogoutResult;
21193
+ exports.MODEL_TOKEN_HELPER_FILENAME = __webpack_exports__.MODEL_TOKEN_HELPER_FILENAME;
21194
+ exports.MODEL_TOKEN_LOCK_TIMEOUT_MS = __webpack_exports__.MODEL_TOKEN_LOCK_TIMEOUT_MS;
20688
21195
  exports.OAuthTokenResponse = __webpack_exports__.OAuthTokenResponse;
20689
21196
  exports.ParsedCheckoutInput = __webpack_exports__.ParsedCheckoutInput;
20690
21197
  exports.PreferredEditor = __webpack_exports__.PreferredEditor;
21198
+ exports.REFRESH_REQUEST_TIMEOUT_MS = __webpack_exports__.REFRESH_REQUEST_TIMEOUT_MS;
20691
21199
  exports.SettableConfigKey = __webpack_exports__.SettableConfigKey;
20692
21200
  exports.TokenData = __webpack_exports__.TokenData;
20693
21201
  exports.ValidateAppIdResponse = __webpack_exports__.ValidateAppIdResponse;
@@ -20700,6 +21208,7 @@ exports.branchFromEnvironmentVersion = __webpack_exports__.branchFromEnvironment
20700
21208
  exports.buildGitAuthDoctorReport = __webpack_exports__.buildGitAuthDoctorReport;
20701
21209
  exports.checkUncommittedChanges = __webpack_exports__.checkUncommittedChanges;
20702
21210
  exports.clearInMemoryTokens = __webpack_exports__.clearInMemoryTokens;
21211
+ exports.createCheckoutPickerSource = __webpack_exports__.createCheckoutPickerSource;
20703
21212
  exports.createWatchDiagnostic = __webpack_exports__.createWatchDiagnostic;
20704
21213
  exports.createWatchDiagnosticFromMessage = __webpack_exports__.createWatchDiagnosticFromMessage;
20705
21214
  exports.deleteConfig = __webpack_exports__.deleteConfig;
@@ -20707,6 +21216,7 @@ exports.detectAppIdsByCommitLookup = __webpack_exports__.detectAppIdsByCommitLoo
20707
21216
  exports.detectAppIdsFromAllRemotes = __webpack_exports__.detectAppIdsFromAllRemotes;
20708
21217
  exports.executeCheckout = __webpack_exports__.executeCheckout;
20709
21218
  exports.executeGitCredentialOperation = __webpack_exports__.executeGitCredentialOperation;
21219
+ exports.executeModelTokenOperation = __webpack_exports__.executeModelTokenOperation;
20710
21220
  exports.filterCandidates = __webpack_exports__.filterCandidates;
20711
21221
  exports.formatCandidateLabel = __webpack_exports__.formatCandidateLabel;
20712
21222
  exports.formatGitAuthDoctorReport = __webpack_exports__.formatGitAuthDoctorReport;
@@ -20720,12 +21230,15 @@ exports.getGitFetchUrl = __webpack_exports__.getGitFetchUrl;
20720
21230
  exports.getGitPushUrl = __webpack_exports__.getGitPushUrl;
20721
21231
  exports.getInstalledPreferredEditors = __webpack_exports__.getInstalledPreferredEditors;
20722
21232
  exports.getLatestVersion = __webpack_exports__.getLatestVersion;
21233
+ exports.getModelToken = __webpack_exports__.getModelToken;
21234
+ exports.getModelTokenHelperScript = __webpack_exports__.getModelTokenHelperScript;
20723
21235
  exports.getPreferredEditorCommand = __webpack_exports__.getPreferredEditorCommand;
20724
21236
  exports.getSettableConfigKeys = __webpack_exports__.getSettableConfigKeys;
20725
21237
  exports.getValidAuthToken = __webpack_exports__.getValidAuthToken;
20726
21238
  exports.getWebSocketUrl = __webpack_exports__.getWebSocketUrl;
20727
21239
  exports.hasTokensForUrl = __webpack_exports__.hasTokensForUrl;
20728
21240
  exports.inspectCredentialHelperCommand = __webpack_exports__.inspectCredentialHelperCommand;
21241
+ exports.installModelTokenHelperScript = __webpack_exports__.installModelTokenHelperScript;
20729
21242
  exports.isCommandAvailable = __webpack_exports__.isCommandAvailable;
20730
21243
  exports.isDirectoryNonEmpty = __webpack_exports__.isDirectoryNonEmpty;
20731
21244
  exports.isPathInsideGitRepo = __webpack_exports__.isPathInsideGitRepo;
@@ -20761,9 +21274,11 @@ exports.validatePath = __webpack_exports__.validatePath;
20761
21274
  exports.verifyAndStoreTokens = __webpack_exports__.verifyAndStoreTokens;
20762
21275
  exports.verifyAuth = __webpack_exports__.verifyAuth;
20763
21276
  exports.watch = __webpack_exports__.watch;
21277
+ exports.withRepoAuthFileLock = __webpack_exports__.withRepoAuthFileLock;
20764
21278
  exports.writeEnvironmentPid = __webpack_exports__.writeEnvironmentPid;
20765
21279
  for(var __rspack_i in __webpack_exports__)if (-1 === [
20766
21280
  "ANVIL_SYNC_CLIENT_ID",
21281
+ "AUTH_FILE_LOCK_DEFAULTS",
20767
21282
  "AnvilYamlValidationResult",
20768
21283
  "AppIdCandidate",
20769
21284
  "AppIdWithContext",
@@ -20775,11 +21290,15 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
20775
21290
  "CheckoutResult",
20776
21291
  "DeviceAuthorizationResponse",
20777
21292
  "FormTemplateValidationResult",
21293
+ "GetValidAuthTokenOptions",
20778
21294
  "LoginResult",
20779
21295
  "LogoutResult",
21296
+ "MODEL_TOKEN_HELPER_FILENAME",
21297
+ "MODEL_TOKEN_LOCK_TIMEOUT_MS",
20780
21298
  "OAuthTokenResponse",
20781
21299
  "ParsedCheckoutInput",
20782
21300
  "PreferredEditor",
21301
+ "REFRESH_REQUEST_TIMEOUT_MS",
20783
21302
  "SettableConfigKey",
20784
21303
  "TokenData",
20785
21304
  "ValidateAppIdResponse",
@@ -20792,6 +21311,7 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
20792
21311
  "buildGitAuthDoctorReport",
20793
21312
  "checkUncommittedChanges",
20794
21313
  "clearInMemoryTokens",
21314
+ "createCheckoutPickerSource",
20795
21315
  "createWatchDiagnostic",
20796
21316
  "createWatchDiagnosticFromMessage",
20797
21317
  "deleteConfig",
@@ -20799,6 +21319,7 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
20799
21319
  "detectAppIdsFromAllRemotes",
20800
21320
  "executeCheckout",
20801
21321
  "executeGitCredentialOperation",
21322
+ "executeModelTokenOperation",
20802
21323
  "filterCandidates",
20803
21324
  "formatCandidateLabel",
20804
21325
  "formatGitAuthDoctorReport",
@@ -20812,12 +21333,15 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
20812
21333
  "getGitPushUrl",
20813
21334
  "getInstalledPreferredEditors",
20814
21335
  "getLatestVersion",
21336
+ "getModelToken",
21337
+ "getModelTokenHelperScript",
20815
21338
  "getPreferredEditorCommand",
20816
21339
  "getSettableConfigKeys",
20817
21340
  "getValidAuthToken",
20818
21341
  "getWebSocketUrl",
20819
21342
  "hasTokensForUrl",
20820
21343
  "inspectCredentialHelperCommand",
21344
+ "installModelTokenHelperScript",
20821
21345
  "isCommandAvailable",
20822
21346
  "isDirectoryNonEmpty",
20823
21347
  "isPathInsideGitRepo",
@@ -20853,6 +21377,7 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
20853
21377
  "verifyAndStoreTokens",
20854
21378
  "verifyAuth",
20855
21379
  "watch",
21380
+ "withRepoAuthFileLock",
20856
21381
  "writeEnvironmentPid"
20857
21382
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
20858
21383
  Object.defineProperty(exports, '__esModule', {