@anvil-works/anvil-cli 0.8.0-canary.9 → 0.8.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +54 -0
  2. package/dist/WatchSession.d.ts +2 -0
  3. package/dist/WatchSession.d.ts.map +1 -1
  4. package/dist/api.d.ts +8 -2
  5. package/dist/api.d.ts.map +1 -1
  6. package/dist/cli.js +2799 -1249
  7. package/dist/commands/appDetection.d.ts +68 -1
  8. package/dist/commands/appDetection.d.ts.map +1 -1
  9. package/dist/commands/checkout.d.ts +1 -0
  10. package/dist/commands/checkout.d.ts.map +1 -1
  11. package/dist/commands/configure.d.ts.map +1 -1
  12. package/dist/commands/create.d.ts +52 -0
  13. package/dist/commands/create.d.ts.map +1 -0
  14. package/dist/commands/db.d.ts +3 -0
  15. package/dist/commands/db.d.ts.map +1 -0
  16. package/dist/commands/deps.d.ts.map +1 -1
  17. package/dist/commands/env.d.ts +10 -0
  18. package/dist/commands/env.d.ts.map +1 -0
  19. package/dist/commands/index.d.ts +6 -1
  20. package/dist/commands/index.d.ts.map +1 -1
  21. package/dist/commands/login.d.ts.map +1 -1
  22. package/dist/commands/modelToken.d.ts +33 -0
  23. package/dist/commands/modelToken.d.ts.map +1 -0
  24. package/dist/commands/repl.d.ts +3 -0
  25. package/dist/commands/repl.d.ts.map +1 -0
  26. package/dist/commands/secret.d.ts +3 -0
  27. package/dist/commands/secret.d.ts.map +1 -0
  28. package/dist/commands/test.d.ts +26 -0
  29. package/dist/commands/test.d.ts.map +1 -0
  30. package/dist/commands/watch.d.ts +1 -25
  31. package/dist/commands/watch.d.ts.map +1 -1
  32. package/dist/config.d.ts +3 -1
  33. package/dist/config.d.ts.map +1 -1
  34. package/dist/errors.d.ts +11 -1
  35. package/dist/errors.d.ts.map +1 -1
  36. package/dist/formTemplateValidation.d.ts.map +1 -1
  37. package/dist/index.js +1466 -994
  38. package/dist/program.d.ts.map +1 -1
  39. package/dist/services/anvil-api.d.ts +20 -0
  40. package/dist/services/anvil-api.d.ts.map +1 -1
  41. package/dist/services/auth.d.ts +42 -3
  42. package/dist/services/auth.d.ts.map +1 -1
  43. package/dist/services/db.d.ts +9 -0
  44. package/dist/services/db.d.ts.map +1 -0
  45. package/dist/services/deps.d.ts +5 -2
  46. package/dist/services/deps.d.ts.map +1 -1
  47. package/dist/services/environment.d.ts +20 -0
  48. package/dist/services/environment.d.ts.map +1 -0
  49. package/dist/services/git-auth.d.ts +35 -0
  50. package/dist/services/git-auth.d.ts.map +1 -1
  51. package/dist/services/git.d.ts +1 -0
  52. package/dist/services/git.d.ts.map +1 -1
  53. package/dist/services/http.d.ts +2 -0
  54. package/dist/services/http.d.ts.map +1 -0
  55. package/dist/services/path-open.d.ts +1 -0
  56. package/dist/services/path-open.d.ts.map +1 -1
  57. package/dist/services/repl.d.ts +20 -0
  58. package/dist/services/repl.d.ts.map +1 -0
  59. package/dist/services/secret.d.ts +43 -0
  60. package/dist/services/secret.d.ts.map +1 -0
  61. package/dist/services/server-tests.d.ts +47 -0
  62. package/dist/services/server-tests.d.ts.map +1 -0
  63. package/dist/validateFormTemplateHtml.d.ts.map +1 -1
  64. package/dist/validatePython.d.ts.map +1 -1
  65. package/dist/watch/SaveProcessor.d.ts +2 -0
  66. package/dist/watch/SaveProcessor.d.ts.map +1 -1
  67. package/package.json +2 -2
  68. package/dist/commands/tables.d.ts +0 -7
  69. package/dist/commands/tables.d.ts.map +0 -1
  70. package/dist/services/tables.d.ts +0 -27
  71. package/dist/services/tables.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -7373,18 +7373,19 @@ var __webpack_exports__ = {};
7373
7373
  AppIdWithContext: ()=>anvil_api_namespaceObject.AppIdWithContext,
7374
7374
  inspectCredentialHelperCommand: ()=>inspectCredentialHelperCommand,
7375
7375
  executeCheckout: ()=>executeCheckout,
7376
+ executeCreate: ()=>executeCreate,
7376
7377
  validateCheckoutDestination: ()=>validateCheckoutDestination,
7377
7378
  DeviceAuthorizationResponse: ()=>oauth_login_namespaceObject.DeviceAuthorizationResponse,
7378
7379
  OAuthTokenResponse: ()=>oauth_login_namespaceObject.OAuthTokenResponse,
7380
+ REFRESH_REQUEST_TIMEOUT_MS: ()=>REFRESH_REQUEST_TIMEOUT_MS,
7379
7381
  WatchSession: ()=>WatchSession_WatchSession,
7380
7382
  parseConfigSetValue: ()=>parseConfigSetValue,
7381
- requestDeviceAuthorization: ()=>requestDeviceAuthorization,
7382
7383
  getGitPushUrl: ()=>anvil_api_getGitPushUrl,
7384
+ requestDeviceAuthorization: ()=>requestDeviceAuthorization,
7383
7385
  requireEnvironmentBranch: ()=>requireEnvironmentBranch,
7384
7386
  setAuthTokensForAccount: ()=>setAccountTokens,
7385
- validateAnvilApp: ()=>validation_validateAnvilApp,
7386
7387
  ValidateAppIdResponse: ()=>anvil_api_namespaceObject.ValidateAppIdResponse,
7387
- verifyAndStoreTokens: ()=>auth_verifyAndStoreTokens,
7388
+ getModelToken: ()=>getModelToken,
7388
7389
  formatValidationPath: ()=>formatValidationPath,
7389
7390
  resolveAnvilUrl: ()=>resolveAnvilUrl,
7390
7391
  createCheckoutPickerSource: ()=>createCheckoutPickerSource,
@@ -7392,12 +7393,13 @@ var __webpack_exports__ = {};
7392
7393
  parseCheckoutInput: ()=>parseCheckoutInput,
7393
7394
  formatGitAuthDoctorReport: ()=>formatGitAuthDoctorReport,
7394
7395
  resetConfig: ()=>resetConfig,
7396
+ MODEL_TOKEN_HELPER_FILENAME: ()=>MODEL_TOKEN_HELPER_FILENAME,
7395
7397
  FormTemplateValidationResult: ()=>validators_namespaceObject.FormTemplateValidationResult,
7396
7398
  createWatchDiagnostic: ()=>createWatchDiagnostic,
7397
7399
  ValidationTarget: ()=>validators_namespaceObject.ValidationTarget,
7398
7400
  detectAppIdsByCommitLookup: ()=>anvil_api_detectAppIdsByCommitLookup,
7399
7401
  sanitizeDirectoryName: ()=>sanitizeDirectoryName,
7400
- validatePath: ()=>validatePath,
7402
+ validateAnvilApp: ()=>validation_validateAnvilApp,
7401
7403
  LoginResult: ()=>auth_namespaceObject.LoginResult,
7402
7404
  listAppsForCheckout: ()=>listAppsForCheckout,
7403
7405
  getInstalledPreferredEditors: ()=>getInstalledPreferredEditors,
@@ -7406,8 +7408,11 @@ var __webpack_exports__ = {};
7406
7408
  CheckoutExecutionDeps: ()=>checkout_namespaceObject.CheckoutExecutionDeps,
7407
7409
  validateFormTemplateHtml: ()=>validateFormTemplateHtml,
7408
7410
  ValidateBranchSyncStatusOptions: ()=>validation_namespaceObject.ValidateBranchSyncStatusOptions,
7411
+ validatePath: ()=>validatePath,
7412
+ withRepoAuthFileLock: ()=>withRepoAuthFileLock,
7409
7413
  BranchSyncStatus: ()=>validation_namespaceObject.BranchSyncStatus,
7410
7414
  getLatestVersion: ()=>getLatestVersion,
7415
+ getModelTokenHelperScript: ()=>getModelTokenHelperScript,
7411
7416
  isDirectoryNonEmpty: ()=>isDirectoryNonEmpty,
7412
7417
  syncToLatest: ()=>src_api_syncToLatest,
7413
7418
  filterCandidates: ()=>anvil_api_filterCandidates,
@@ -7416,7 +7421,7 @@ var __webpack_exports__ = {};
7416
7421
  isPathInsideGitRepo: ()=>isPathInsideGitRepo,
7417
7422
  normalizeAnvilUrl: ()=>config_normalizeAnvilUrl,
7418
7423
  ValidationIssue: ()=>validators_namespaceObject.ValidationIssue,
7419
- writeEnvironmentPid: ()=>writeEnvironmentPid,
7424
+ verifyAndStoreTokens: ()=>auth_verifyAndStoreTokens,
7420
7425
  readEnvironmentPid: ()=>environment_readEnvironmentPid,
7421
7426
  validateFormTemplate: ()=>validateFormTemplate,
7422
7427
  preferredEditors: ()=>preferredEditors,
@@ -7428,21 +7433,26 @@ var __webpack_exports__ = {};
7428
7433
  validateBranchSyncStatus: ()=>validation_validateBranchSyncStatus,
7429
7434
  checkUncommittedChanges: ()=>api_checkUncommittedChanges,
7430
7435
  hasTokensForUrl: ()=>auth_hasTokensForUrl,
7436
+ writeEnvironmentPid: ()=>writeEnvironmentPid,
7431
7437
  AnvilYamlValidationResult: ()=>validators_namespaceObject.AnvilYamlValidationResult,
7432
7438
  ValidatePathResult: ()=>validators_namespaceObject.ValidatePathResult,
7433
7439
  getSettableConfigKeys: ()=>getSettableConfigKeys,
7434
7440
  logout: ()=>logout,
7435
7441
  TokenData: ()=>auth_namespaceObject.TokenData,
7436
7442
  detectAppIdsFromAllRemotes: ()=>anvil_api_detectAppIdsFromAllRemotes,
7443
+ executeModelTokenOperation: ()=>executeModelTokenOperation,
7437
7444
  getWebSocketUrl: ()=>getWebSocketUrl,
7438
7445
  getAllConfig: ()=>getAllConfig,
7439
7446
  isCommandAvailable: ()=>config_isCommandAvailable,
7440
7447
  setInMemoryTokens: ()=>setInMemoryTokens,
7441
7448
  validateAppId: ()=>anvil_api_validateAppId,
7442
7449
  pollDeviceAuthorization: ()=>pollDeviceAuthorization,
7450
+ createAnvilApp: ()=>createAnvilApp,
7443
7451
  getValidAuthToken: ()=>auth_getValidAuthToken,
7452
+ CreatedAnvilApp: ()=>anvil_api_namespaceObject.CreatedAnvilApp,
7444
7453
  watch: ()=>src_api_watch,
7445
7454
  deleteConfig: ()=>deleteConfig,
7455
+ installModelTokenHelperScript: ()=>installModelTokenHelperScript,
7446
7456
  getGitFetchUrl: ()=>anvil_api_getGitFetchUrl,
7447
7457
  PreferredEditor: ()=>config_namespaceObject.PreferredEditor,
7448
7458
  getAvailableAnvilUrls: ()=>getAvailableAnvilUrls,
@@ -7455,8 +7465,12 @@ var __webpack_exports__ = {};
7455
7465
  CheckoutResult: ()=>checkout_namespaceObject.CheckoutResult,
7456
7466
  ParsedCheckoutInput: ()=>checkout_namespaceObject.ParsedCheckoutInput,
7457
7467
  parseCredentialHelperCommand: ()=>parseCredentialHelperCommand,
7458
- clearInMemoryTokens: ()=>clearInMemoryTokens,
7468
+ CreateCheckoutError: ()=>CreateCheckoutError,
7469
+ GetValidAuthTokenOptions: ()=>auth_namespaceObject.GetValidAuthTokenOptions,
7470
+ MODEL_TOKEN_LOCK_TIMEOUT_MS: ()=>MODEL_TOKEN_LOCK_TIMEOUT_MS,
7459
7471
  getDefaultDestinationDirectory: ()=>getDefaultDestinationDirectory,
7472
+ AUTH_FILE_LOCK_DEFAULTS: ()=>AUTH_FILE_LOCK_DEFAULTS,
7473
+ clearInMemoryTokens: ()=>clearInMemoryTokens,
7460
7474
  lookupByCommit: ()=>lookupByCommit,
7461
7475
  AppIdCandidate: ()=>anvil_api_namespaceObject.AppIdCandidate,
7462
7476
  verifyAuth: ()=>auth_verifyAuth
@@ -7473,6 +7487,7 @@ var __webpack_exports__ = {};
7473
7487
  P$: ()=>config_isCommandAvailable,
7474
7488
  n: ()=>config_isDevMode,
7475
7489
  Wj: ()=>config_isTestMode,
7490
+ Wc: ()=>config_normalizeAnvilOrigin,
7476
7491
  Vl: ()=>config_normalizeAnvilUrl,
7477
7492
  PW: ()=>parseConfigSetValue,
7478
7493
  cx: ()=>preferredEditors,
@@ -7485,12 +7500,14 @@ var __webpack_exports__ = {};
7485
7500
  __webpack_require__.r(auth_namespaceObject);
7486
7501
  __webpack_require__.d(auth_namespaceObject, {
7487
7502
  fg: ()=>ANVIL_SYNC_CLIENT_ID,
7503
+ ru: ()=>REFRESH_REQUEST_TIMEOUT_MS,
7488
7504
  Ou: ()=>clearInMemoryTokens,
7489
7505
  Fr: ()=>auth_getAccountsForUrl,
7490
7506
  u5: ()=>getAvailableAnvilUrls,
7491
7507
  _p: ()=>auth_getValidAuthToken,
7492
7508
  Fi: ()=>auth_hasTokensForUrl,
7493
7509
  ri: ()=>logout,
7510
+ UE: ()=>auth_resolveAuthAnvilUrl,
7494
7511
  f5: ()=>setInMemoryTokens,
7495
7512
  iT: ()=>auth_setRepoContext,
7496
7513
  CQ: ()=>auth_verifyAndStoreTokens,
@@ -7499,6 +7516,7 @@ var __webpack_exports__ = {};
7499
7516
  var anvil_api_namespaceObject = {};
7500
7517
  __webpack_require__.r(anvil_api_namespaceObject);
7501
7518
  __webpack_require__.d(anvil_api_namespaceObject, {
7519
+ gr: ()=>createAnvilApp,
7502
7520
  lx: ()=>anvil_api_detectAppIdsByCommitLookup,
7503
7521
  NZ: ()=>anvil_api_detectAppIdsFromAllRemotes,
7504
7522
  rZ: ()=>anvil_api_filterCandidates,
@@ -7541,6 +7559,7 @@ var __webpack_exports__ = {};
7541
7559
  A9: ()=>isDirectoryNonEmpty,
7542
7560
  oU: ()=>isPathInsideGitRepo,
7543
7561
  Qx: ()=>parseCheckoutInput,
7562
+ B6: ()=>resolveCheckoutUrlForApi,
7544
7563
  KV: ()=>sanitizeDirectoryName,
7545
7564
  Bc: ()=>validateCheckoutDestination
7546
7565
  });
@@ -13097,12 +13116,25 @@ var __webpack_exports__ = {};
13097
13116
  if (!urlInput || "string" != typeof urlInput) return urlInput;
13098
13117
  let url = urlInput.trim();
13099
13118
  url = url.replace(/\/+$/, "");
13100
- if ("localhost" === url || url.startsWith("localhost:")) return "http://localhost:3000";
13101
- if ("http://localhost" === url || url.startsWith("http://localhost:")) return "http://localhost:3000";
13102
- if ("https://localhost" === url || url.startsWith("https://localhost:")) return "http://localhost:3000";
13103
- if (/^https?:\/\//.test(url)) return url;
13119
+ if ("localhost" === url) return "http://localhost:3000";
13120
+ if (url.startsWith("localhost:")) return `http://${url}`;
13121
+ if (/^https?:\/\//i.test(url)) return url;
13104
13122
  return `https://${url}`;
13105
13123
  }
13124
+ function config_normalizeAnvilOrigin(input) {
13125
+ try {
13126
+ const raw = input.trim();
13127
+ const normalized = config_normalizeAnvilUrl(raw);
13128
+ const url = new URL(raw.includes("://") ? raw : normalized);
13129
+ if (![
13130
+ "http:",
13131
+ "https:"
13132
+ ].includes(url.protocol) || url.username || url.password || "/" !== url.pathname || url.search || url.hash) throw new Error("Invalid origin");
13133
+ return normalized;
13134
+ } catch {
13135
+ throw new Error("Server URL must be an HTTP(S) origin without credentials, a path, query, or fragment.");
13136
+ }
13137
+ }
13106
13138
  function resolveAnvilUrl() {
13107
13139
  const fromConfig = config_getConfig("anvilUrl");
13108
13140
  if ("string" == typeof fromConfig && fromConfig.trim()) return fromConfig.trim();
@@ -13365,9 +13397,10 @@ var __webpack_exports__ = {};
13365
13397
  type: "auth_invalid",
13366
13398
  message
13367
13399
  }),
13368
- refreshFailed: (message)=>({
13400
+ refreshFailed: (message, status)=>({
13369
13401
  type: "token_refresh_failed",
13370
- message
13402
+ message,
13403
+ status
13371
13404
  })
13372
13405
  };
13373
13406
  const createAppError = {
@@ -13584,6 +13617,7 @@ var __webpack_exports__ = {};
13584
13617
  const external_module_namespaceObject = require("module");
13585
13618
  const requireFromHere = (0, external_module_namespaceObject.createRequire)(__filename);
13586
13619
  const GIT_CREDENTIAL_HELPER_FILENAME = "anvil-credential-helper.cjs";
13620
+ const MODEL_TOKEN_HELPER_FILENAME = "anvil-model-token-helper.cjs";
13587
13621
  const GIT_CREDENTIAL_HELPER_LOCATOR = `$(git rev-parse --git-common-dir)/${GIT_CREDENTIAL_HELPER_FILENAME}`;
13588
13622
  function getUrlConfigKey(appId) {
13589
13623
  return `anvil.auth.${appId}.url`;
@@ -13674,6 +13708,38 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13674
13708
  });
13675
13709
  `;
13676
13710
  }
13711
+ function getModelTokenHelperScript(modulePath) {
13712
+ return `#!/usr/bin/env node
13713
+ "use strict";
13714
+
13715
+ const { execFileSync } = require("child_process");
13716
+ const { executeModelTokenOperation } = require(${JSON.stringify(modulePath)});
13717
+
13718
+ function resolveRepoPath() {
13719
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
13720
+ cwd: process.cwd(),
13721
+ encoding: "utf8",
13722
+ }).trim();
13723
+ }
13724
+
13725
+ Promise.resolve()
13726
+ .then(() => executeModelTokenOperation({ repoPath: resolveRepoPath() }))
13727
+ .catch((e) => {
13728
+ process.stderr.write("anvil model token helper failed: " + ((e && e.message) || e) + "\\n");
13729
+ process.exit(1);
13730
+ });
13731
+ `;
13732
+ }
13733
+ async function installModelTokenHelperScript(helperPath, options) {
13734
+ await external_fs_.promises.mkdir(external_path_default().dirname(helperPath), {
13735
+ recursive: true
13736
+ });
13737
+ await external_fs_.promises.writeFile(helperPath, getModelTokenHelperScript(options?.modulePath ?? getCredentialHelperModulePath()), {
13738
+ mode: 448
13739
+ });
13740
+ await external_fs_.promises.chmod(helperPath, 448);
13741
+ return helperPath;
13742
+ }
13677
13743
  function findPackageRoot(startPath) {
13678
13744
  let dir = external_fs_default().existsSync(startPath) && external_fs_default().statSync(startPath).isDirectory() ? startPath : external_path_default().dirname(startPath);
13679
13745
  while(true){
@@ -13801,6 +13867,76 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13801
13867
  function getAuthFilePath(gitDir) {
13802
13868
  return external_path_default().join(gitDir, "anvil-auth.json");
13803
13869
  }
13870
+ const AUTH_FILE_LOCK_DEFAULTS = {
13871
+ staleMs: 30000,
13872
+ timeoutMs: 60000,
13873
+ retryDelayMs: 100,
13874
+ heartbeatMs: 5000
13875
+ };
13876
+ function git_auth_delay(ms) {
13877
+ return new Promise((resolve)=>setTimeout(resolve, ms));
13878
+ }
13879
+ async function acquireAuthFileLock(lockPath, options) {
13880
+ const deadline = Date.now() + options.timeoutMs;
13881
+ while(true){
13882
+ try {
13883
+ const handle = await external_fs_.promises.open(lockPath, "wx", 384);
13884
+ try {
13885
+ await handle.writeFile(JSON.stringify({
13886
+ pid: process.pid,
13887
+ acquiredAt: new Date().toISOString()
13888
+ }));
13889
+ } finally{
13890
+ await handle.close();
13891
+ }
13892
+ return;
13893
+ } catch (e) {
13894
+ if ("EEXIST" !== e.code) throw e;
13895
+ }
13896
+ try {
13897
+ const lockStat = await external_fs_.promises.stat(lockPath);
13898
+ if (Date.now() - lockStat.mtimeMs > options.staleMs) {
13899
+ await external_fs_.promises.rm(lockPath, {
13900
+ force: true
13901
+ });
13902
+ continue;
13903
+ }
13904
+ } catch {
13905
+ continue;
13906
+ }
13907
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for the Anvil auth file lock at ${lockPath}`);
13908
+ await git_auth_delay(options.retryDelayMs);
13909
+ }
13910
+ }
13911
+ async function withRepoAuthFileLock(repoPath, fn, options) {
13912
+ const gitDir = await getRepositoryGitDir(repoPath);
13913
+ const lockPath = `${getAuthFilePath(gitDir)}.lock`;
13914
+ const lockOptions = {
13915
+ ...AUTH_FILE_LOCK_DEFAULTS,
13916
+ ...options
13917
+ };
13918
+ await external_fs_.promises.mkdir(external_path_default().dirname(lockPath), {
13919
+ recursive: true
13920
+ });
13921
+ await acquireAuthFileLock(lockPath, lockOptions);
13922
+ const heartbeat = startAuthFileLockHeartbeat(lockPath, lockOptions.heartbeatMs);
13923
+ try {
13924
+ return await fn();
13925
+ } finally{
13926
+ clearInterval(heartbeat);
13927
+ await external_fs_.promises.rm(lockPath, {
13928
+ force: true
13929
+ });
13930
+ }
13931
+ }
13932
+ function startAuthFileLockHeartbeat(lockPath, heartbeatMs) {
13933
+ const heartbeat = setInterval(()=>{
13934
+ const now = new Date();
13935
+ external_fs_.promises.utimes(lockPath, now, now).catch(()=>{});
13936
+ }, heartbeatMs);
13937
+ heartbeat.unref();
13938
+ return heartbeat;
13939
+ }
13804
13940
  function decodeKey(b64key) {
13805
13941
  const key = Buffer.from(b64key, "base64");
13806
13942
  if (32 !== key.length) throw new Error("Encryption key must be a base64-encoded 32-byte key");
@@ -13845,10 +13981,19 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13845
13981
  recursive: true
13846
13982
  });
13847
13983
  const authJson = JSON.stringify(auth, null, 2);
13848
- await external_fs_.promises.writeFile(authFilePath, encrypt(authJson, encryptionKey), {
13849
- mode: 384
13850
- });
13851
- await external_fs_.promises.chmod(authFilePath, 448);
13984
+ const tempFilePath = `${authFilePath}.tmp-${external_node_crypto_default().randomBytes(6).toString("hex")}`;
13985
+ try {
13986
+ await external_fs_.promises.writeFile(tempFilePath, encrypt(authJson, encryptionKey), {
13987
+ mode: 384
13988
+ });
13989
+ await external_fs_.promises.chmod(tempFilePath, 448);
13990
+ await external_fs_.promises.rename(tempFilePath, authFilePath);
13991
+ } catch (e) {
13992
+ await external_fs_.promises.rm(tempFilePath, {
13993
+ force: true
13994
+ });
13995
+ throw e;
13996
+ }
13852
13997
  await git.raw([
13853
13998
  "config",
13854
13999
  "--local",
@@ -13859,6 +14004,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13859
14004
  }
13860
14005
  const readAuthFromRepo = async (repoPath)=>{
13861
14006
  const git = esm_default(repoPath);
14007
+ if (!await git.checkIsRepo()) return;
13862
14008
  const gitDir = await getRepositoryGitDir(repoPath, git);
13863
14009
  const authFilePath = getAuthFilePath(gitDir);
13864
14010
  if (!external_fs_default().existsSync(authFilePath)) return;
@@ -13870,6 +14016,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13870
14016
  return resolveAnvilUrl();
13871
14017
  }
13872
14018
  const ANVIL_SYNC_CLIENT_ID = "anvil-sync";
14019
+ const REFRESH_REQUEST_TIMEOUT_MS = 12000;
13873
14020
  let inMemoryAuth = null;
13874
14021
  function setInMemoryTokens(anvilUrl, username, tokens) {
13875
14022
  inMemoryAuth = {
@@ -13888,6 +14035,11 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13888
14035
  function auth_setRepoContext(repoPath) {
13889
14036
  repoContext = repoPath;
13890
14037
  }
14038
+ async function auth_resolveAuthAnvilUrl() {
14039
+ if (inMemoryAuth) return inMemoryAuth.anvilUrl;
14040
+ const repoAuth = repoContext ? await readAuthFromRepo(repoContext) : void 0;
14041
+ return repoAuth?.anvilUrl ?? resolveAnvilUrl();
14042
+ }
13891
14043
  const inFlightRefreshes = new Map();
13892
14044
  async function auth_verifyAuth(authToken, anvilUrl = getDefaultAnvilUrl()) {
13893
14045
  try {
@@ -13908,7 +14060,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13908
14060
  throw createAuthError.invalid(`Network error: ${e.message}`);
13909
14061
  }
13910
14062
  }
13911
- async function refreshAccessToken(refreshToken, anvilUrl = getDefaultAnvilUrl(), clientId = ANVIL_SYNC_CLIENT_ID) {
14063
+ async function refreshAccessToken(refreshToken, anvilUrl = getDefaultAnvilUrl(), clientId = ANVIL_SYNC_CLIENT_ID, options) {
13912
14064
  try {
13913
14065
  const tokenResponse = await fetch(`${anvilUrl}/oauth/token`, {
13914
14066
  method: "POST",
@@ -13919,11 +14071,12 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13919
14071
  grant_type: "refresh_token",
13920
14072
  refresh_token: refreshToken,
13921
14073
  client_id: clientId
13922
- })
14074
+ }),
14075
+ signal: AbortSignal.timeout(options?.timeoutMs ?? REFRESH_REQUEST_TIMEOUT_MS)
13923
14076
  });
13924
14077
  if (!tokenResponse.ok) {
13925
14078
  const errorText = await tokenResponse.text();
13926
- throw createAuthError.refreshFailed(`Failed to refresh token: ${tokenResponse.status} ${errorText}`);
14079
+ throw createAuthError.refreshFailed(`Failed to refresh token: ${tokenResponse.status} ${errorText}`, tokenResponse.status);
13927
14080
  }
13928
14081
  const tokenData = await tokenResponse.json();
13929
14082
  return tokenData;
@@ -13940,17 +14093,16 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13940
14093
  return hasTokens(anvilUrl, username);
13941
14094
  }
13942
14095
  }
13943
- async function auth_getValidAuthToken(anvilUrl = getDefaultAnvilUrl(), username) {
14096
+ async function auth_getValidAuthToken(anvilUrl = getDefaultAnvilUrl(), username, options) {
13944
14097
  const normalized = config_normalizeAnvilUrl(anvilUrl);
13945
14098
  const source = await resolveTokenSource(normalized, username);
13946
14099
  const tokens = getSourceTokens(source);
13947
14100
  if (!tokens.authToken && !tokens.refreshToken) throw createAuthError.required("Not logged in. Please log in first.");
13948
- const isExpired = null !== tokens.authTokenExpiresAt && tokens.authTokenExpiresAt <= Math.floor(Date.now() / 1000) + 60;
13949
- if (tokens.refreshToken && isExpired) {
14101
+ if (tokens.refreshToken && isAccessTokenExpired(tokens)) {
13950
14102
  const refreshKey = getRefreshKey(normalized, username, source, tokens.refreshToken);
13951
14103
  const existingRefresh = inFlightRefreshes.get(refreshKey);
13952
14104
  if (existingRefresh) return existingRefresh;
13953
- const refreshPromise = refreshAndStoreAccessToken(normalized, username, source, tokens.refreshToken).finally(()=>{
14105
+ const refreshPromise = refreshAndStoreAccessToken(normalized, username, source, tokens.refreshToken, options).finally(()=>{
13954
14106
  inFlightRefreshes.delete(refreshKey);
13955
14107
  });
13956
14108
  inFlightRefreshes.set(refreshKey, refreshPromise);
@@ -13980,6 +14132,9 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13980
14132
  function getSourceTokens(source) {
13981
14133
  return "store" === source.type ? source.tokens : source.auth.tokens;
13982
14134
  }
14135
+ function isAccessTokenExpired(tokens) {
14136
+ return null !== tokens.authTokenExpiresAt && tokens.authTokenExpiresAt <= Math.floor(Date.now() / 1000) + 60;
14137
+ }
13983
14138
  function getStoreAccountUsername(normalizedUrl, username, refreshToken) {
13984
14139
  if (username) return username;
13985
14140
  const urlTokens = getUrlTokens(normalizedUrl);
@@ -13998,11 +14153,43 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13998
14153
  return `store:${normalizedUrl}:${source.accountUsername ?? username ?? refreshToken}`;
13999
14154
  }
14000
14155
  }
14001
- async function refreshAndStoreAccessToken(normalizedUrl, username, source, refreshToken) {
14156
+ async function refreshAndStoreAccessToken(normalizedUrl, username, source, refreshToken, options) {
14157
+ if ("repo" === source.type) return refreshRepoAuthTokens(source.repoPath, {
14158
+ anvilUrl: normalizedUrl,
14159
+ username,
14160
+ auth: source.auth,
14161
+ refreshToken,
14162
+ lock: options?.lock,
14163
+ requestTimeoutMs: options?.requestTimeoutMs
14164
+ });
14165
+ return performTokenRefresh(normalizedUrl, username, source, refreshToken, options?.requestTimeoutMs);
14166
+ }
14167
+ async function refreshRepoAuthTokens(repoPath, options) {
14168
+ const normalizedUrl = config_normalizeAnvilUrl(options.anvilUrl);
14169
+ return withRepoAuthFileLock(repoPath, async ()=>{
14170
+ let auth = options.auth;
14171
+ let refreshToken = options.refreshToken;
14172
+ const current = await readAuthFromRepo(repoPath);
14173
+ if (current && authMatches(current, normalizedUrl, options.username)) {
14174
+ if (current.tokens.authToken && !isAccessTokenExpired(current.tokens)) return current.tokens.authToken;
14175
+ auth = current;
14176
+ refreshToken = current.tokens.refreshToken ?? refreshToken;
14177
+ }
14178
+ if (!auth || !refreshToken) throw createAuthError.required("No repo auth tokens available to refresh.");
14179
+ return performTokenRefresh(normalizedUrl, options.username, {
14180
+ type: "repo",
14181
+ auth,
14182
+ repoPath
14183
+ }, refreshToken, options.requestTimeoutMs);
14184
+ }, options.lock);
14185
+ }
14186
+ async function performTokenRefresh(normalizedUrl, username, source, refreshToken, requestTimeoutMs) {
14002
14187
  try {
14003
14188
  const tokens = getSourceTokens(source);
14004
14189
  const clientId = tokens.clientId ?? ANVIL_SYNC_CLIENT_ID;
14005
- const tokenData = await refreshAccessToken(refreshToken, normalizedUrl, clientId);
14190
+ const tokenData = await refreshAccessToken(refreshToken, normalizedUrl, clientId, {
14191
+ timeoutMs: requestTimeoutMs
14192
+ });
14006
14193
  const newTokens = {
14007
14194
  authToken: tokenData.access_token,
14008
14195
  refreshToken: tokenData.refresh_token,
@@ -14024,10 +14211,26 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
14024
14211
  }
14025
14212
  return tokenData.access_token;
14026
14213
  } catch (e) {
14214
+ if ("repo" === source.type && isRefreshTokenRejection(e)) await clearRepoAuthTokens(source.repoPath, source.auth).catch(()=>{});
14027
14215
  await clearTokensForAccount(normalizedUrl, username, refreshToken);
14028
14216
  throw e;
14029
14217
  }
14030
14218
  }
14219
+ function isRefreshTokenRejection(e) {
14220
+ const error = e;
14221
+ return error?.type === "token_refresh_failed" && (400 === error.status || 401 === error.status);
14222
+ }
14223
+ async function clearRepoAuthTokens(repoPath, auth) {
14224
+ const encryptionKey = process.env["ANVIL_AUTH_FILE_ENCRYPTION_KEY"];
14225
+ if (!encryptionKey) return;
14226
+ auth.tokens = {
14227
+ authToken: null,
14228
+ refreshToken: null,
14229
+ authTokenExpiresAt: null,
14230
+ clientId: auth.tokens.clientId
14231
+ };
14232
+ await writeAuthToFile(repoPath, auth, encryptionKey);
14233
+ }
14031
14234
  async function clearTokensForAccount(url, username, refreshToken) {
14032
14235
  if (inMemoryAuth || repoContext && await readAuthFromRepo(repoContext)) return;
14033
14236
  {
@@ -14109,1042 +14312,1117 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
14109
14312
  return getAvailableUrls();
14110
14313
  }
14111
14314
  }
14112
- function anvil_api_getDefaultAnvilUrl() {
14113
- return resolveAnvilUrl();
14315
+ const promises_namespaceObject = require("timers/promises");
14316
+ const WANT_NOT_VALID_RE = /\bwant\s+[0-9a-f]{7,40}\s+not valid\b/i;
14317
+ const FETCH_RETRY_DELAY_MS = 200;
14318
+ function isWantNotValidError(error) {
14319
+ return WANT_NOT_VALID_RE.test(error.message || "");
14114
14320
  }
14115
- async function anvil_api_validateAppId(appId, anvilUrl = anvil_api_getDefaultAnvilUrl(), username) {
14116
- const authToken = await auth_getValidAuthToken(anvilUrl, username);
14117
- try {
14118
- const resp = await fetch(`${anvilUrl}/ide/api/_/apps/validate-app-id`, {
14119
- method: "POST",
14120
- headers: {
14121
- Authorization: `Bearer ${authToken}`,
14122
- "Content-Type": "application/json"
14123
- },
14124
- body: JSON.stringify({
14125
- app_id: appId
14126
- })
14127
- });
14128
- const data = await resp.json();
14129
- if (resp.ok) return {
14130
- valid: data.valid ?? false,
14131
- app_name: data.app_name
14132
- };
14133
- if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14134
- if (403 === resp.status) throw createAppError.accessDenied(appId);
14135
- if (404 === resp.status) throw createAppError.notFound(appId);
14136
- logger_logger.debug(`validate-app-id failed: ${resp.status} ${resp.statusText}`);
14137
- return {
14138
- valid: false,
14139
- error: data.error || `Server error: ${resp.status}`
14140
- };
14141
- } catch (error) {
14142
- if (error.type) throw error;
14143
- throw createNetworkError.network(error.message);
14321
+ class git_GitService extends Emitter {
14322
+ git;
14323
+ repoPath;
14324
+ static INDEX_LOCK_RETRY_DELAYS_MS = [
14325
+ 25,
14326
+ 50,
14327
+ 100,
14328
+ 200
14329
+ ];
14330
+ constructor(repoPath){
14331
+ super();
14332
+ this.repoPath = external_path_default().resolve(repoPath);
14333
+ this.git = esm_default(this.repoPath);
14144
14334
  }
14145
- }
14146
- async function lookupByCommit(commitId, branchName, anvilUrl = anvil_api_getDefaultAnvilUrl(), username) {
14147
- const authToken = await auth_getValidAuthToken(anvilUrl, username);
14148
- try {
14149
- const resp = await fetch(`${anvilUrl}/ide/api/_/apps/lookup-by-commit`, {
14150
- method: "POST",
14151
- headers: {
14152
- Authorization: `Bearer ${authToken}`,
14153
- "Content-Type": "application/json"
14154
- },
14155
- body: JSON.stringify({
14156
- commit_id: commitId,
14157
- branch_name: branchName
14158
- })
14159
- });
14160
- if (!resp.ok) {
14161
- if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14162
- throw createNetworkError.server(resp.status, resp.statusText);
14335
+ getGit() {
14336
+ return this.git;
14337
+ }
14338
+ getRepoPath() {
14339
+ return this.repoPath;
14340
+ }
14341
+ async getGitDir() {
14342
+ try {
14343
+ const gitDir = (await this.git.revparse([
14344
+ "--git-dir"
14345
+ ])).trim();
14346
+ if (!external_path_default().isAbsolute(gitDir)) return external_path_default().resolve(this.repoPath, gitDir);
14347
+ return gitDir;
14348
+ } catch (e) {
14349
+ throw errors_createGitError.commandFailed("rev-parse --git-dir", e.message);
14163
14350
  }
14164
- const data = await resp.json();
14165
- return data;
14166
- } catch (error) {
14167
- if (error.type) throw error;
14168
- throw createNetworkError.network(error.message);
14169
14351
  }
14170
- }
14171
- async function listAppsForCheckout(options = {}) {
14172
- const anvilUrl = options.anvilUrl ?? anvil_api_getDefaultAnvilUrl();
14173
- const authToken = await auth_getValidAuthToken(anvilUrl, options.username);
14174
- const params = new URLSearchParams();
14175
- if ("number" == typeof options.limit) params.set("limit", String(options.limit));
14176
- if (options.cursor) params.set("cursor", options.cursor);
14177
- if (options.q && options.q.trim()) params.set("q", options.q.trim());
14178
- const query = params.toString();
14179
- const url = `${anvilUrl}/ide/api/_/apps${query ? `?${query}` : ""}`;
14180
- try {
14181
- const resp = await fetch(url, {
14182
- method: "GET",
14183
- headers: {
14184
- Authorization: `Bearer ${authToken}`
14185
- },
14186
- signal: options.signal
14187
- });
14188
- if (!resp.ok) {
14189
- if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14190
- throw createNetworkError.server(resp.status, resp.statusText);
14352
+ async getCurrentBranch() {
14353
+ try {
14354
+ const branchRef = await this.git.revparse([
14355
+ "--abbrev-ref",
14356
+ "HEAD"
14357
+ ]);
14358
+ if ("HEAD" === branchRef) throw createSyncError.detachedHead();
14359
+ return branchRef;
14360
+ } catch (e) {
14361
+ if ("detached_head" === e.type) throw e;
14362
+ throw createSyncError.detachedHead();
14191
14363
  }
14192
- const data = await resp.json();
14193
- return {
14194
- apps: Array.isArray(data.apps) ? data.apps : [],
14195
- next_cursor: "string" == typeof data.next_cursor ? data.next_cursor : null
14196
- };
14197
- } catch (error) {
14198
- if (error.type) throw error;
14199
- throw createNetworkError.network(error.message);
14200
14364
  }
14201
- }
14202
- async function saveUpdates(appId, branchName, payload, anvilUrl = anvil_api_getDefaultAnvilUrl(), authToken) {
14203
- try {
14204
- const resp = await fetch(`${anvilUrl}/ide/api/_/apps/${appId}/update?make_fresh_version=false&branch=${branchName}`, {
14205
- method: "PUT",
14206
- headers: {
14207
- Authorization: `Bearer ${authToken}`,
14208
- "Content-Type": "application/json"
14209
- },
14210
- body: JSON.stringify(payload)
14211
- });
14212
- const data = await resp.json();
14213
- if (401 === resp.status) throw createAuthError.invalid(`Authentication failed (401): ${data.error || "unauthorized"}`);
14214
- 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)`);
14215
- return {
14216
- version: data.version,
14217
- overtaken: data.overtaken,
14218
- latest_version: data.latest_version,
14219
- error: 200 !== resp.status ? data.error || `Status ${resp.status}` : void 0
14220
- };
14221
- } catch (error) {
14222
- if (error.type) throw error;
14223
- throw createNetworkError.network(error.message);
14365
+ async getCommitId() {
14366
+ try {
14367
+ const commitId = (await this.git.revparse([
14368
+ "HEAD"
14369
+ ])).trim();
14370
+ return commitId;
14371
+ } catch (e) {
14372
+ throw errors_createGitError.commandFailed("revparse", e.message);
14373
+ }
14224
14374
  }
14225
- }
14226
- function anvil_api_getGitFetchUrl(appId, authToken, gitUrl = anvil_api_getDefaultAnvilUrl()) {
14227
- const url = new URL(gitUrl);
14228
- const encodedToken = encodeURIComponent(authToken);
14229
- return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git?no_freeze=true&q=`;
14230
- }
14231
- function anvil_api_getGitPushUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
14232
- const url = new URL(anvilUrl);
14233
- const encodedToken = encodeURIComponent(authToken);
14234
- return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git`;
14235
- }
14236
- function getWebSocketUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
14237
- return anvilUrl.replace(/^http/, "ws") + `/ide/api/_/apps/${appId}/ws?access_token=${authToken}`;
14238
- }
14239
- async function getLatestVersion() {
14240
- try {
14241
- const response = await fetch("https://registry.npmjs.org/@anvil-works/anvil-cli/latest");
14242
- if (!response.ok) return null;
14243
- const data = await response.json();
14244
- return data.version;
14245
- } catch (e) {
14246
- return null;
14247
- }
14248
- }
14249
- function anvil_api_filterCandidates(candidates, explicitUrl, explicitUsername) {
14250
- let filtered = candidates;
14251
- if (explicitUrl) {
14252
- const normalizedExplicit = config_normalizeAnvilUrl(explicitUrl);
14253
- filtered = filtered.filter((c)=>c.detectedUrl && config_normalizeAnvilUrl(c.detectedUrl) === normalizedExplicit);
14254
- }
14255
- if (explicitUsername) filtered = filtered.filter((c)=>!c.detectedUsername || c.detectedUsername === explicitUsername);
14256
- return filtered;
14257
- }
14258
- function anvil_api_formatCandidateLabel(candidate) {
14259
- const parts = [
14260
- candidate.appId
14261
- ];
14262
- if (candidate.detectedUrl) if (candidate.detectedUsername) parts.push(`(${candidate.detectedUsername} on ${candidate.detectedUrl})`);
14263
- else parts.push(`(${candidate.detectedUrl})`);
14264
- parts.push(`- ${candidate.description}`);
14265
- return parts.join(" ");
14266
- }
14267
- function anvil_api_lookupRemoteInfoForAppId(appId, detectedRemotes) {
14268
- const matches = detectedRemotes.filter((c)=>c.appId === appId);
14269
- if (0 === matches.length) return {};
14270
- const withUsername = matches.find((c)=>c.detectedUsername);
14271
- if (withUsername) return {
14272
- detectedUrl: withUsername.detectedUrl,
14273
- detectedUsername: withUsername.detectedUsername
14274
- };
14275
- return {
14276
- detectedUrl: matches[0].detectedUrl,
14277
- detectedUsername: matches[0].detectedUsername
14278
- };
14279
- }
14280
- async function anvil_api_detectAppIdsFromAllRemotes(repoPath) {
14281
- const git = esm_default(repoPath);
14282
- const out = [];
14283
- try {
14284
- const remotes = await git.getRemotes(true);
14285
- for (const remote of remotes){
14286
- const httpMatch = remote.refs.fetch?.match(/(?:http|https):\/\/(?:[^@]+@)?([^:\/]+)(?::\d+)?\/git\/([A-Z0-9]+)\.git/);
14287
- if (httpMatch) {
14288
- const [, host, detectedAppId] = httpMatch;
14289
- out.push({
14290
- appId: detectedAppId,
14291
- source: "remote",
14292
- description: `Git remote '${remote.name}'`,
14293
- detectedUrl: config_normalizeAnvilUrl(host)
14294
- });
14295
- continue;
14296
- }
14297
- const sshMatch = remote.refs.fetch?.match(/ssh:\/\/([^@]+)@([^:]+):(\d+)\/(?:git\/)?([A-Z0-9]+)\.git/);
14298
- if (sshMatch) {
14299
- const [, usernamePart, host, , detectedAppId] = sshMatch;
14300
- const detectedUsername = usernamePart.includes("%") ? decodeURIComponent(usernamePart) : usernamePart;
14301
- const finalUsername = "git" !== detectedUsername ? detectedUsername : void 0;
14302
- out.push({
14303
- appId: detectedAppId,
14304
- source: "remote",
14305
- description: `Git remote '${remote.name}' (SSH)`,
14306
- detectedUrl: config_normalizeAnvilUrl(host),
14307
- detectedUsername: finalUsername
14308
- });
14309
- }
14310
- }
14311
- } catch (_e) {}
14312
- return out;
14313
- }
14314
- async function anvil_api_detectAppIdsByCommitLookup(repoPath, options) {
14315
- const anvilUrl = options.anvilUrl || resolveAnvilUrl();
14316
- const username = options.username;
14317
- const git = esm_default(repoPath);
14318
- const out = [];
14319
- try {
14320
- const authToken = await auth_getValidAuthToken(anvilUrl, username);
14321
- const branchRef = await git.revparse([
14322
- "--abbrev-ref",
14323
- "HEAD"
14324
- ]);
14325
- const currentBranch = "HEAD" === branchRef ? "master" : branchRef;
14326
- const commitId = (await git.revparse([
14327
- "HEAD"
14328
- ]))?.trim();
14329
- if (!commitId) return out;
14330
- const resp = await fetch(`${anvilUrl}/ide/api/_/apps/lookup-by-commit`, {
14331
- method: "POST",
14332
- headers: {
14333
- Authorization: `Bearer ${authToken}`,
14334
- "Content-Type": "application/json"
14335
- },
14336
- body: JSON.stringify({
14337
- commit_id: commitId,
14338
- branch_name: currentBranch
14339
- })
14340
- });
14341
- logger_logger.debug(`Reverse lookup by commit ${commitId} on branch ${currentBranch} returned ${resp.status}`);
14342
- if (!resp.ok) {
14343
- const errorBody = await resp.text();
14344
- logger_logger.debug(`lookup-by-commit error: ${errorBody}`);
14375
+ async getCommitInfo() {
14376
+ try {
14377
+ const hash = (await this.git.revparse([
14378
+ "HEAD"
14379
+ ])).trim();
14380
+ const message = (await this.git.raw([
14381
+ "log",
14382
+ "-1",
14383
+ "--format=%s"
14384
+ ])).trim();
14385
+ return {
14386
+ hash,
14387
+ shortHash: hash.substring(0, 8),
14388
+ message
14389
+ };
14390
+ } catch (e) {
14391
+ throw errors_createGitError.commandFailed("log", e.message);
14345
14392
  }
14346
- if (resp.ok) {
14347
- const data = await resp.json();
14348
- logger_logger.debug(`data ${JSON.stringify(data)}`);
14349
- const apps = data?.apps ?? [];
14350
- for (const app of apps)if (app?.app_id) out.push({
14351
- appId: app.app_id,
14352
- source: "remote",
14353
- description: `Reverse lookup by commit${app.branch ? ` on branch '${app.branch}'` : ""}`
14354
- });
14393
+ }
14394
+ async getStatus() {
14395
+ try {
14396
+ const status = await this.git.status();
14397
+ const staged = status.files.filter((file)=>{
14398
+ const indexStatus = file.index.trim();
14399
+ return indexStatus.length > 0 && "?" !== indexStatus;
14400
+ }).map((file)=>file.path);
14401
+ return {
14402
+ isClean: status.isClean(),
14403
+ modified: status.modified,
14404
+ notAdded: status.not_added,
14405
+ created: status.created,
14406
+ deleted: status.deleted,
14407
+ staged,
14408
+ renamed: status.renamed || []
14409
+ };
14410
+ } catch (e) {
14411
+ throw errors_createGitError.commandFailed("status", e.message);
14355
14412
  }
14356
- } catch (_e) {}
14357
- return out;
14358
- }
14359
- const promises_namespaceObject = require("fs/promises");
14360
- var promises_default = /*#__PURE__*/ __webpack_require__.n(promises_namespaceObject);
14361
- function normalizeLineEndings(content) {
14362
- return content.replace(/\r\n/g, "\n");
14363
- }
14364
- function pythonifyName(name) {
14365
- return name.replace(/[^A-z0-9]/g, "_").replace(/^[0-9]/, "_$&");
14366
- }
14367
- function extractPythonName(relativePath, skipParts = 1) {
14368
- const parts = relativePath.slice(0, -3).split("/");
14369
- const isPackage = relativePath.endsWith("__init__.py");
14370
- const relevantParts = isPackage ? parts.slice(skipParts, -1) : parts.slice(skipParts);
14371
- return relevantParts.map(pythonifyName).join(".");
14372
- }
14373
- function deepEqual(a, b) {
14374
- if (a === b) return true;
14375
- if (null == a || null == b) return false;
14376
- if (typeof a !== typeof b) return false;
14377
- if ("object" != typeof a) return a === b;
14378
- if (Array.isArray(a) !== Array.isArray(b)) return false;
14379
- if (Array.isArray(a)) {
14380
- if (a.length !== b.length) return false;
14381
- return a.every((item, index)=>deepEqual(item, b[index]));
14382
14413
  }
14383
- const keysA = Object.keys(a);
14384
- const keysB = Object.keys(b);
14385
- if (keysA.length !== keysB.length) return false;
14386
- return keysA.every((key)=>deepEqual(a[key], b[key]));
14387
- }
14388
- function parseHtmlWithFrontmatter(htmlContent) {
14389
- const frontmatterPattern = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
14390
- const match = htmlContent.match(frontmatterPattern);
14391
- if (match) {
14392
- const yamlStr = match[1];
14393
- const html = match[2];
14394
- const frontmatter = yamlStr ? external_js_yaml_default().load(yamlStr) ?? {} : {};
14395
- return {
14396
- frontmatter,
14397
- html
14398
- };
14414
+ async discardModeOnlyChanges(paths) {
14415
+ const discarded = [];
14416
+ for (const relativePath of paths)try {
14417
+ const headMode = await this.getHeadFileMode(relativePath);
14418
+ if ("100644" !== headMode && "100755" !== headMode) continue;
14419
+ const contentUnchanged = await this.hasSameContentAsHead(relativePath);
14420
+ if (!contentUnchanged) continue;
14421
+ await this.restoreExecutableBit(relativePath, "100755" === headMode);
14422
+ discarded.push(relativePath);
14423
+ } catch (e) {
14424
+ throw errors_createGitError.commandFailed("discard mode-only changes", e.message);
14425
+ }
14426
+ return discarded;
14399
14427
  }
14400
- return {
14401
- frontmatter: {},
14402
- html: htmlContent
14403
- };
14404
- }
14405
- const ANVIL_GITIGNORE_ENTRY = "/.anvil/";
14406
- async function ensureProjectGitignoreIgnoresAnvilDir(projectRoot) {
14407
- const rootGitignorePath = external_path_default().join(projectRoot, ".gitignore");
14408
- try {
14409
- const existing = await external_fs_.promises.readFile(rootGitignorePath, "utf8");
14410
- if (existing.includes(ANVIL_GITIGNORE_ENTRY)) return false;
14411
- const prefix = existing.endsWith("\n") || 0 === existing.length ? "" : "\n";
14412
- await external_fs_.promises.writeFile(rootGitignorePath, `${existing}${prefix}${ANVIL_GITIGNORE_ENTRY}\n`, "utf8");
14413
- return true;
14414
- } catch (error) {
14415
- if ("ENOENT" !== error.code) throw error;
14416
- await external_fs_.promises.writeFile(rootGitignorePath, `${ANVIL_GITIGNORE_ENTRY}\n`, "utf8");
14417
- return true;
14428
+ async discardStagedModeOnlyChanges(paths) {
14429
+ const discarded = [];
14430
+ for (const relativePath of paths)try {
14431
+ const headMode = await this.getHeadFileMode(relativePath);
14432
+ if ("100644" !== headMode && "100755" !== headMode) continue;
14433
+ const indexContentUnchanged = await this.hasSameIndexContentAsHead(relativePath);
14434
+ if (!indexContentUnchanged) continue;
14435
+ await this.git.raw([
14436
+ "reset",
14437
+ "-q",
14438
+ "HEAD",
14439
+ "--",
14440
+ relativePath
14441
+ ]);
14442
+ await this.restoreExecutableBit(relativePath, "100755" === headMode);
14443
+ discarded.push(relativePath);
14444
+ } catch (e) {
14445
+ throw errors_createGitError.commandFailed("discard staged mode-only changes", e.message);
14446
+ }
14447
+ return discarded;
14418
14448
  }
14419
- }
14420
- const ANVIL_DEPS_DIR = ".anvil/deps";
14421
- const ANVIL_DEPS_MANIFEST = "manifest.json";
14422
- const DEPS_MANIFEST_VERSION = 2;
14423
- function formatFetchError(prefix, status, errorBody) {
14424
- if (!errorBody) return `${prefix}: ${status}`;
14425
- try {
14426
- const parsed = JSON.parse(errorBody);
14427
- if ("string" == typeof parsed.error && parsed.error.trim()) return `${prefix}: ${parsed.error} (${status})`;
14428
- } catch {}
14429
- return `${prefix}: ${status} ${errorBody}`;
14430
- }
14431
- async function fetchAllDependencies(appId, anvilUrl = resolveAnvilUrl(), options = {}) {
14432
- const { commit, includeDocs = true, knownDependencies = [] } = options;
14433
- const token = await auth_getValidAuthToken(anvilUrl);
14434
- const body = {
14435
- include_docs: includeDocs,
14436
- known_dependencies: knownDependencies
14437
- };
14438
- if (commit) body.commit = commit;
14439
- const resp = await fetch(`${anvilUrl}/ide/api/_/apps/${appId}/resolved-dependencies`, {
14440
- method: "POST",
14441
- headers: {
14442
- Authorization: `Bearer ${token}`,
14443
- "Content-Type": "application/json"
14444
- },
14445
- body: JSON.stringify(body)
14446
- });
14447
- if (!resp.ok) {
14448
- const errorBody = await resp.text();
14449
- throw new Error(formatFetchError("Failed to fetch dependencies", resp.status, errorBody));
14449
+ async getHeadFileMode(relativePath) {
14450
+ const output = await this.git.raw([
14451
+ "ls-tree",
14452
+ "HEAD",
14453
+ "--",
14454
+ relativePath
14455
+ ]);
14456
+ const match = output.match(/^(\d{6})\s/);
14457
+ return match?.[1] ?? null;
14450
14458
  }
14451
- return await resp.json();
14452
- }
14453
- async function refreshDependencyCache(projectRoot, appId, anvilUrl = resolveAnvilUrl(), options = {}) {
14454
- const includeDocs = options.includeDocs ?? true;
14455
- const knownDependencies = await getKnownDependenciesForCache(projectRoot, includeDocs);
14456
- const response = await fetchAllDependencies(appId, anvilUrl, {
14457
- commit: options.commit,
14458
- includeDocs,
14459
- knownDependencies
14460
- });
14461
- if (!response.resolved_dependencies.length) {
14462
- await clearDepsCache(projectRoot);
14463
- return {
14464
- response,
14465
- addedGitignoreEntry: false
14466
- };
14459
+ async hasSameContentAsHead(relativePath) {
14460
+ const headHash = (await this.git.revparse([
14461
+ `HEAD:${relativePath}`
14462
+ ])).trim();
14463
+ const worktreeHash = (await this.git.raw([
14464
+ "hash-object",
14465
+ "--",
14466
+ relativePath
14467
+ ])).trim();
14468
+ return headHash === worktreeHash;
14467
14469
  }
14468
- const addedGitignoreEntry = await ensureProjectGitignoreIgnoresAnvilDir(projectRoot);
14469
- await writeAllDepsToCache(projectRoot, response, {
14470
- includeDocs
14471
- });
14472
- return {
14473
- response,
14474
- addedGitignoreEntry
14475
- };
14476
- }
14477
- function depsDir(projectRoot) {
14478
- return external_path_default().join(projectRoot, ANVIL_DEPS_DIR);
14479
- }
14480
- function depsManifestPath(projectRoot) {
14481
- return external_path_default().join(depsDir(projectRoot), ANVIL_DEPS_MANIFEST);
14482
- }
14483
- function isResolvedDependencyManifestEntry(value) {
14484
- if (!value || "object" != typeof value) return false;
14485
- const entry = value;
14486
- 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);
14487
- }
14488
- function isCachedDependencyManifestEntry(value) {
14489
- if (!isResolvedDependencyManifestEntry(value)) return false;
14490
- const entry = value;
14491
- return "string" == typeof entry.package_name && entry.package_name.length > 0;
14492
- }
14493
- function isDepsCacheManifest(value) {
14494
- if (!value || "object" != typeof value) return false;
14495
- const manifest = value;
14496
- return manifest.version === DEPS_MANIFEST_VERSION && "boolean" == typeof manifest.includeDocs && 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);
14497
- }
14498
- async function readDepsCacheManifest(projectRoot) {
14499
- try {
14500
- const raw = await promises_default().readFile(depsManifestPath(projectRoot), "utf-8");
14501
- const parsed = JSON.parse(raw);
14502
- return isDepsCacheManifest(parsed) ? parsed : void 0;
14503
- } catch {
14504
- return;
14470
+ async hasSameIndexContentAsHead(relativePath) {
14471
+ const headHash = (await this.git.revparse([
14472
+ `HEAD:${relativePath}`
14473
+ ])).trim();
14474
+ const indexHash = (await this.git.revparse([
14475
+ `:${relativePath}`
14476
+ ])).trim();
14477
+ return headHash === indexHash;
14505
14478
  }
14506
- }
14507
- async function getKnownDependenciesForCache(projectRoot, includeDocs) {
14508
- const manifest = await readDepsCacheManifest(projectRoot);
14509
- if (!manifest || manifest.includeDocs !== includeDocs) return [];
14510
- return manifest.resolved_dependencies.map(({ app_id, commit_id })=>({
14511
- app_id,
14512
- commit_id
14513
- }));
14514
- }
14515
- function getDependencyWatchSubscriptionsFromManifest(manifest) {
14516
- if (!manifest) return [];
14517
- const cachedByAppId = new Map(manifest.cached_dependencies.map((dep)=>[
14518
- dep.app_id,
14519
- dep
14520
- ]));
14521
- return manifest.resolved_dependencies.flatMap((dep)=>{
14522
- if (!dep.branch) return [];
14523
- return [
14524
- {
14525
- appId: dep.app_id,
14526
- branch: dep.branch,
14527
- packageName: cachedByAppId.get(dep.app_id)?.package_name
14528
- }
14529
- ];
14530
- });
14531
- }
14532
- function normalizeAnvilYamlDependenciesFromContent(content) {
14533
- const parsed = external_js_yaml_default().load(content);
14534
- if (!parsed || "object" != typeof parsed || !Array.isArray(parsed.dependencies)) return [];
14535
- return normalizeAnvilYamlDependenciesValue(parsed.dependencies);
14536
- }
14537
- function normalizeAnvilYamlDependenciesValue(value) {
14538
- if (!Array.isArray(value)) return [];
14539
- return value.map(normalizeForComparison);
14540
- }
14541
- async function readAnvilYamlDependencies(projectRoot) {
14542
- try {
14543
- const content = await promises_default().readFile(external_path_default().join(projectRoot, "anvil.yaml"), "utf-8");
14544
- return normalizeAnvilYamlDependenciesFromContent(content);
14545
- } catch {
14546
- return [];
14479
+ async restoreExecutableBit(relativePath, executable) {
14480
+ const filePath = external_path_default().join(this.repoPath, relativePath);
14481
+ const stat = await external_fs_.promises.stat(filePath);
14482
+ const mode = executable ? 73 | stat.mode : -74 & stat.mode;
14483
+ if ((511 & stat.mode) !== (511 & mode)) await external_fs_.promises.chmod(filePath, mode);
14547
14484
  }
14548
- }
14549
- function anvilYamlDependenciesEqual(left, right) {
14550
- return deepEqual(left, right);
14551
- }
14552
- function normalizeForComparison(value) {
14553
- if (Array.isArray(value)) return value.map(normalizeForComparison);
14554
- if (value && "object" == typeof value) return Object.fromEntries(Object.entries(value).sort(([left], [right])=>left.localeCompare(right)).map(([key, entryValue])=>[
14555
- key,
14556
- normalizeForComparison(entryValue)
14557
- ]));
14558
- return value;
14559
- }
14560
- function cachedManifestEntry(dep, packageName) {
14561
- return {
14562
- app_id: dep.app_id,
14563
- commit_id: dep.commit_id,
14564
- ...dep.branch ? {
14565
- branch: dep.branch
14566
- } : {},
14567
- package_name: packageName
14568
- };
14569
- }
14570
- function safeDependencyPath(filePath, kind) {
14571
- const normalizedPath = external_path_default().normalize(filePath);
14572
- if (external_path_default().isAbsolute(normalizedPath) || normalizedPath.startsWith("..")) throw new Error(`Invalid dependency ${kind} path: ${filePath}`);
14573
- return normalizedPath;
14574
- }
14575
- function dependencyPythonPath(name, kind) {
14576
- const parts = name.split(".");
14577
- if (0 === parts.length || parts.some((part)=>!part || "." === part || ".." === part || part.includes("/") || part.includes("\\"))) throw new Error(`Invalid dependency ${kind} name: ${name}`);
14578
- return external_path_default().join(...parts);
14579
- }
14580
- async function writeDepToCache(projectRoot, dep) {
14581
- const depDir = external_path_default().join(projectRoot, ANVIL_DEPS_DIR, dep.package_name);
14582
- await promises_default().rm(depDir, {
14583
- recursive: true,
14584
- force: true
14585
- });
14586
- await promises_default().mkdir(external_path_default().join(depDir, "client_code"), {
14587
- recursive: true
14588
- });
14589
- await promises_default().mkdir(external_path_default().join(depDir, "server_code"), {
14590
- recursive: true
14591
- });
14592
- await promises_default().writeFile(external_path_default().join(depDir, "anvil.yaml"), external_js_yaml_default().dump({
14593
- package_name: dep.package_name
14594
- }));
14595
- for (const mod of dep.modules ?? []){
14596
- const modulePath = external_path_default().join(depDir, "client_code", dependencyPythonPath(mod.name, "module"));
14597
- if (mod.is_package) {
14598
- await promises_default().mkdir(modulePath, {
14599
- recursive: true
14600
- });
14601
- await promises_default().writeFile(external_path_default().join(modulePath, "__init__.py"), mod.code);
14602
- } else {
14603
- const moduleFile = `${modulePath}.py`;
14604
- await promises_default().mkdir(external_path_default().dirname(moduleFile), {
14605
- recursive: true
14606
- });
14607
- await promises_default().writeFile(moduleFile, mod.code);
14608
- }
14485
+ async hasUncommittedChanges() {
14486
+ const status = await this.getStatus();
14487
+ return !status.isClean;
14609
14488
  }
14610
- for (const form of dep.forms ?? []){
14611
- const { code, class_name, is_package, ...formTemplate } = form;
14612
- const formPath = external_path_default().join(depDir, "client_code", dependencyPythonPath(class_name, "form"));
14613
- if (is_package) {
14614
- await promises_default().mkdir(formPath, {
14615
- recursive: true
14489
+ async fetch(url, refSpec) {
14490
+ let attempt = 0;
14491
+ while(true)try {
14492
+ await this.git.fetch([
14493
+ url,
14494
+ refSpec
14495
+ ]);
14496
+ if (attempt > 0) this.emit("fetch-retry-resolved", {
14497
+ attempts: attempt,
14498
+ outcome: "succeeded"
14616
14499
  });
14617
- await promises_default().writeFile(external_path_default().join(formPath, "__init__.py"), code);
14618
- await promises_default().writeFile(external_path_default().join(formPath, "form_template.yaml"), external_js_yaml_default().dump(formTemplate));
14619
- } else {
14620
- const formCodeFile = `${formPath}.py`;
14621
- await promises_default().mkdir(external_path_default().dirname(formCodeFile), {
14622
- recursive: true
14500
+ return;
14501
+ } catch (e) {
14502
+ const err = e;
14503
+ if (!isWantNotValidError(err)) {
14504
+ if (attempt > 0) this.emit("fetch-retry-resolved", {
14505
+ attempts: attempt,
14506
+ outcome: "failed"
14507
+ });
14508
+ throw errors_createGitError.fetchFailed(err.message);
14509
+ }
14510
+ attempt++;
14511
+ this.emit("fetch-retry", {
14512
+ attempt,
14513
+ delayMs: FETCH_RETRY_DELAY_MS,
14514
+ error: err.message
14623
14515
  });
14624
- await promises_default().writeFile(formCodeFile, code);
14625
- await promises_default().writeFile(`${formPath}.yaml`, external_js_yaml_default().dump(formTemplate));
14516
+ logger_logger.verbose(`git fetch race (attempt ${attempt}, sleeping ${FETCH_RETRY_DELAY_MS}ms): ${err.message}`);
14517
+ await (0, promises_namespaceObject.setTimeout)(FETCH_RETRY_DELAY_MS);
14626
14518
  }
14627
14519
  }
14628
- for (const mod of dep.server_modules ?? []){
14629
- const modulePath = external_path_default().join(depDir, "server_code", dependencyPythonPath(mod.name, "server module"));
14630
- if (mod.is_package) {
14631
- await promises_default().mkdir(modulePath, {
14632
- recursive: true
14633
- });
14634
- await promises_default().writeFile(external_path_default().join(modulePath, "__init__.py"), mod.code);
14635
- } else {
14636
- const moduleFile = `${modulePath}.py`;
14637
- await promises_default().mkdir(external_path_default().dirname(moduleFile), {
14638
- recursive: true
14639
- });
14640
- await promises_default().writeFile(moduleFile, mod.code);
14520
+ async reset(ref, mode = "mixed") {
14521
+ await this.runGitCommandWithIndexLockRetry("reset", ()=>this.git.reset([
14522
+ `--${mode}`,
14523
+ ref
14524
+ ]));
14525
+ }
14526
+ async checkout(paths) {
14527
+ await this.runGitCommandWithIndexLockRetry("checkout", ()=>this.git.checkout(paths));
14528
+ }
14529
+ async runGitCommandWithIndexLockRetry(command, operation) {
14530
+ let lastError;
14531
+ for(let attempt = 0; attempt <= git_GitService.INDEX_LOCK_RETRY_DELAYS_MS.length; attempt += 1)try {
14532
+ await operation();
14533
+ return;
14534
+ } catch (e) {
14535
+ lastError = e;
14536
+ if (!this.isIndexLockError(e) || attempt === git_GitService.INDEX_LOCK_RETRY_DELAYS_MS.length) throw errors_createGitError.commandFailed(command, e.message);
14537
+ await (0, promises_namespaceObject.setTimeout)(git_GitService.INDEX_LOCK_RETRY_DELAYS_MS[attempt]);
14641
14538
  }
14539
+ throw errors_createGitError.commandFailed(command, lastError.message);
14642
14540
  }
14643
- for (const asset of dep.assets ?? []){
14644
- const targetPath = external_path_default().join(depDir, "theme", "assets", safeDependencyPath(asset.name, "asset"));
14645
- await promises_default().mkdir(external_path_default().dirname(targetPath), {
14646
- recursive: true
14647
- });
14648
- await promises_default().writeFile(targetPath, Buffer.from(asset.content, "base64"));
14541
+ isIndexLockError(error) {
14542
+ const message = error.message ?? "";
14543
+ return message.includes("Unable to create") && message.includes("index.lock") && message.includes("File exists");
14649
14544
  }
14650
- for (const doc of dep.docs ?? []){
14651
- const targetPath = external_path_default().join(depDir, safeDependencyPath(doc.path, "doc"));
14652
- await promises_default().mkdir(external_path_default().dirname(targetPath), {
14653
- recursive: true
14654
- });
14655
- await promises_default().writeFile(targetPath, doc.content);
14545
+ async stash(message) {
14546
+ try {
14547
+ const args = [
14548
+ "stash",
14549
+ "push",
14550
+ "--include-untracked"
14551
+ ];
14552
+ if (message) args.push("-m", message);
14553
+ const result = await this.git.raw(args);
14554
+ return !result.includes("No local changes to save");
14555
+ } catch (e) {
14556
+ throw errors_createGitError.commandFailed("stash", e.message);
14557
+ }
14656
14558
  }
14657
- }
14658
- async function writeAllDepsToCache(projectRoot, response, options = {}) {
14659
- const includeDocs = options.includeDocs ?? true;
14660
- const manifest = await readDepsCacheManifest(projectRoot);
14661
- const canReuseExistingCache = manifest?.includeDocs === includeDocs;
14662
- if (!canReuseExistingCache) await clearDepsCache(projectRoot);
14663
- const previousCached = canReuseExistingCache ? manifest.cached_dependencies : [];
14664
- const resolvedAppIds = new Set(response.resolved_dependencies.map((dep)=>dep.app_id));
14665
- 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), {
14666
- recursive: true,
14667
- force: true
14668
- });
14669
- for (const dep of response.dependencies)await writeDepToCache(projectRoot, dep);
14670
- const returnedByAppId = new Map(response.dependencies.map((dep)=>[
14671
- dep.app_id,
14672
- dep
14673
- ]));
14674
- const previousByAppId = new Map(previousCached.map((dep)=>[
14675
- dep.app_id,
14676
- dep
14677
- ]));
14678
- const cachedDependencies = response.resolved_dependencies.map((dep)=>{
14679
- const returned = returnedByAppId.get(dep.app_id);
14680
- if (returned) return cachedManifestEntry(returned, returned.package_name);
14681
- const previous = previousByAppId.get(dep.app_id);
14682
- if (!previous) return;
14683
- return cachedManifestEntry(dep, previous.package_name);
14684
- }).filter((dep)=>void 0 !== dep);
14685
- const newManifest = {
14686
- version: DEPS_MANIFEST_VERSION,
14687
- includeDocs,
14688
- source_dependencies: await readAnvilYamlDependencies(projectRoot),
14689
- resolved_dependencies: response.resolved_dependencies,
14690
- cached_dependencies: cachedDependencies
14691
- };
14692
- await promises_default().mkdir(depsDir(projectRoot), {
14693
- recursive: true
14694
- });
14695
- await promises_default().writeFile(depsManifestPath(projectRoot), `${JSON.stringify(newManifest, null, 2)}\n`);
14696
- }
14697
- async function clearDepsCache(projectRoot) {
14698
- await promises_default().rm(depsDir(projectRoot), {
14699
- recursive: true,
14700
- force: true
14701
- });
14702
- }
14703
- const external_timers_promises_namespaceObject = require("timers/promises");
14704
- const WANT_NOT_VALID_RE = /\bwant\s+[0-9a-f]{7,40}\s+not valid\b/i;
14705
- const FETCH_RETRY_DELAY_MS = 200;
14706
- function isWantNotValidError(error) {
14707
- return WANT_NOT_VALID_RE.test(error.message || "");
14708
- }
14709
- class git_GitService extends Emitter {
14710
- git;
14711
- repoPath;
14712
- static INDEX_LOCK_RETRY_DELAYS_MS = [
14713
- 25,
14714
- 50,
14715
- 100,
14716
- 200
14717
- ];
14718
- constructor(repoPath){
14719
- super();
14720
- this.repoPath = external_path_default().resolve(repoPath);
14721
- this.git = esm_default(this.repoPath);
14559
+ async stashPop() {
14560
+ try {
14561
+ await this.git.raw([
14562
+ "stash",
14563
+ "pop"
14564
+ ]);
14565
+ } catch (e) {
14566
+ throw errors_createGitError.commandFailed("stash pop", e.message);
14567
+ }
14722
14568
  }
14723
- getGit() {
14724
- return this.git;
14569
+ async clean(files) {
14570
+ try {
14571
+ await this.git.clean(CleanOptions.FORCE, files);
14572
+ } catch (e) {
14573
+ throw errors_createGitError.commandFailed("clean", e.message);
14574
+ }
14725
14575
  }
14726
- getRepoPath() {
14727
- return this.repoPath;
14576
+ async diffNames(fromCommit, toCommit) {
14577
+ try {
14578
+ const diffResult = await this.git.diff([
14579
+ fromCommit,
14580
+ toCommit,
14581
+ "--name-only"
14582
+ ]);
14583
+ const files = diffResult.split("\n").filter((f)=>f.trim());
14584
+ return files;
14585
+ } catch (e) {
14586
+ throw errors_createGitError.commandFailed("diff", e.message);
14587
+ }
14728
14588
  }
14729
- async getGitDir() {
14589
+ async getAheadBehind(localRef, remoteRef) {
14730
14590
  try {
14731
- const gitDir = (await this.git.revparse([
14732
- "--git-dir"
14733
- ])).trim();
14734
- if (!external_path_default().isAbsolute(gitDir)) return external_path_default().resolve(this.repoPath, gitDir);
14735
- return gitDir;
14591
+ const result = await this.git.raw([
14592
+ "rev-list",
14593
+ "--left-right",
14594
+ "--count",
14595
+ `${remoteRef}...${localRef}`
14596
+ ]);
14597
+ const [behind, ahead] = result.trim().split("\t").map(Number);
14598
+ return {
14599
+ ahead,
14600
+ behind
14601
+ };
14736
14602
  } catch (e) {
14737
- throw errors_createGitError.commandFailed("rev-parse --git-dir", e.message);
14603
+ throw errors_createGitError.commandFailed("rev-list", e.message);
14738
14604
  }
14739
14605
  }
14740
- async getCurrentBranch() {
14606
+ async show(refPath) {
14741
14607
  try {
14742
- const branchRef = await this.git.revparse([
14743
- "--abbrev-ref",
14744
- "HEAD"
14608
+ const content = await this.git.show([
14609
+ refPath
14745
14610
  ]);
14746
- if ("HEAD" === branchRef) throw createSyncError.detachedHead();
14747
- return branchRef;
14611
+ return content;
14748
14612
  } catch (e) {
14749
- if ("detached_head" === e.type) throw e;
14750
- throw createSyncError.detachedHead();
14613
+ throw errors_createGitError.commandFailed("show", e.message);
14751
14614
  }
14752
14615
  }
14753
- async getCommitId() {
14616
+ async push(url, refSpec, force = false) {
14754
14617
  try {
14755
- const commitId = (await this.git.revparse([
14756
- "HEAD"
14757
- ])).trim();
14758
- return commitId;
14618
+ const args = force ? [
14619
+ "push",
14620
+ "--force",
14621
+ url,
14622
+ refSpec
14623
+ ] : [
14624
+ "push",
14625
+ url,
14626
+ refSpec
14627
+ ];
14628
+ await this.git.raw(args);
14759
14629
  } catch (e) {
14760
- throw errors_createGitError.commandFailed("revparse", e.message);
14630
+ throw errors_createGitError.commandFailed("push", e.message);
14761
14631
  }
14762
14632
  }
14763
- async getCommitInfo() {
14633
+ async rebase(onto) {
14764
14634
  try {
14765
- const hash = (await this.git.revparse([
14766
- "HEAD"
14767
- ])).trim();
14768
- const message = (await this.git.raw([
14769
- "log",
14770
- "-1",
14771
- "--format=%s"
14772
- ])).trim();
14773
- return {
14774
- hash,
14775
- shortHash: hash.substring(0, 8),
14776
- message
14777
- };
14635
+ await this.git.raw([
14636
+ "rebase",
14637
+ onto
14638
+ ]);
14639
+ } catch (e) {
14640
+ throw errors_createGitError.commandFailed("rebase", e.message);
14641
+ }
14642
+ }
14643
+ async rebaseAbort() {
14644
+ try {
14645
+ await this.git.raw([
14646
+ "rebase",
14647
+ "--abort"
14648
+ ]);
14778
14649
  } catch (e) {
14779
- throw errors_createGitError.commandFailed("log", e.message);
14650
+ throw errors_createGitError.commandFailed("rebase --abort", e.message);
14780
14651
  }
14781
14652
  }
14782
- async getStatus() {
14653
+ async isAncestor(ancestorRef, descendantRef) {
14783
14654
  try {
14784
- const status = await this.git.status();
14785
- const staged = status.files.filter((file)=>{
14786
- const indexStatus = file.index.trim();
14787
- return indexStatus.length > 0 && "?" !== indexStatus;
14788
- }).map((file)=>file.path);
14789
- return {
14790
- isClean: status.isClean(),
14791
- modified: status.modified,
14792
- notAdded: status.not_added,
14793
- created: status.created,
14794
- deleted: status.deleted,
14795
- staged,
14796
- renamed: status.renamed || []
14797
- };
14798
- } catch (e) {
14799
- throw errors_createGitError.commandFailed("status", e.message);
14655
+ const mergeBase = (await this.git.raw([
14656
+ "merge-base",
14657
+ ancestorRef,
14658
+ descendantRef
14659
+ ])).trim();
14660
+ const ancestorCommit = (await this.git.revparse([
14661
+ ancestorRef
14662
+ ])).trim();
14663
+ return mergeBase === ancestorCommit;
14664
+ } catch {
14665
+ return false;
14800
14666
  }
14801
14667
  }
14802
- async discardModeOnlyChanges(paths) {
14803
- const discarded = [];
14804
- for (const relativePath of paths)try {
14805
- const headMode = await this.getHeadFileMode(relativePath);
14806
- if ("100644" !== headMode && "100755" !== headMode) continue;
14807
- const contentUnchanged = await this.hasSameContentAsHead(relativePath);
14808
- if (!contentUnchanged) continue;
14809
- await this.restoreExecutableBit(relativePath, "100755" === headMode);
14810
- discarded.push(relativePath);
14811
- } catch (e) {
14812
- throw errors_createGitError.commandFailed("discard mode-only changes", e.message);
14813
- }
14814
- return discarded;
14668
+ async mergeFastForward(ref) {
14669
+ await this.runGitCommandWithIndexLockRetry("merge --ff-only", ()=>this.git.raw([
14670
+ "merge",
14671
+ "--ff-only",
14672
+ ref
14673
+ ]));
14815
14674
  }
14816
- async discardStagedModeOnlyChanges(paths) {
14817
- const discarded = [];
14818
- for (const relativePath of paths)try {
14819
- const headMode = await this.getHeadFileMode(relativePath);
14820
- if ("100644" !== headMode && "100755" !== headMode) continue;
14821
- const indexContentUnchanged = await this.hasSameIndexContentAsHead(relativePath);
14822
- if (!indexContentUnchanged) continue;
14675
+ async deleteRef(ref) {
14676
+ try {
14823
14677
  await this.git.raw([
14824
- "reset",
14825
- "-q",
14826
- "HEAD",
14827
- "--",
14828
- relativePath
14678
+ "update-ref",
14679
+ "-d",
14680
+ ref
14829
14681
  ]);
14830
- await this.restoreExecutableBit(relativePath, "100755" === headMode);
14831
- discarded.push(relativePath);
14682
+ } catch (e) {}
14683
+ }
14684
+ async getRemotes() {
14685
+ try {
14686
+ const remotes = await this.git.getRemotes(true);
14687
+ return remotes.map((r)=>({
14688
+ name: r.name,
14689
+ fetchUrl: r.refs.fetch
14690
+ }));
14832
14691
  } catch (e) {
14833
- throw errors_createGitError.commandFailed("discard staged mode-only changes", e.message);
14692
+ throw errors_createGitError.commandFailed("remote", e.message);
14834
14693
  }
14835
- return discarded;
14836
14694
  }
14837
- async getHeadFileMode(relativePath) {
14838
- const output = await this.git.raw([
14839
- "ls-tree",
14840
- "HEAD",
14841
- "--",
14842
- relativePath
14843
- ]);
14844
- const match = output.match(/^(\d{6})\s/);
14845
- return match?.[1] ?? null;
14695
+ async isGitInitialized() {
14696
+ const gitPath = external_path_default().join(this.repoPath, ".git");
14697
+ return external_fs_.existsSync(gitPath);
14846
14698
  }
14847
- async hasSameContentAsHead(relativePath) {
14848
- const headHash = (await this.git.revparse([
14849
- `HEAD:${relativePath}`
14850
- ])).trim();
14851
- const worktreeHash = (await this.git.raw([
14852
- "hash-object",
14853
- "--",
14854
- relativePath
14855
- ])).trim();
14856
- return headHash === worktreeHash;
14699
+ async removeEmptyDirectories(removedFiles) {
14700
+ const directoriesToCheck = new Set();
14701
+ for (const file of removedFiles){
14702
+ let dir = external_path_default().dirname(file);
14703
+ while(dir && "." !== dir){
14704
+ directoriesToCheck.add(dir);
14705
+ const parentDir = external_path_default().dirname(dir);
14706
+ if (parentDir === dir) break;
14707
+ dir = parentDir;
14708
+ }
14709
+ }
14710
+ const sortedDirs = Array.from(directoriesToCheck).sort((a, b)=>b.split("/").length - a.split("/").length);
14711
+ for (const dir of sortedDirs)try {
14712
+ const dirPath = external_path_default().join(this.repoPath, dir);
14713
+ if (!external_fs_.existsSync(dirPath)) continue;
14714
+ const entries = await external_fs_.promises.readdir(dirPath);
14715
+ if (0 === entries.length) await external_fs_.promises.rmdir(dirPath);
14716
+ } catch (error) {}
14717
+ }
14718
+ }
14719
+ async function getStagedFileChanges(git, gitService) {
14720
+ try {
14721
+ const status = await git.status();
14722
+ const renames = status.renamed || [];
14723
+ const stagedFiles = status.files.filter((f)=>" " !== f.index && "?" !== f.index);
14724
+ const stagedModeOnlyPaths = new Set(gitService ? await gitService.discardStagedModeOnlyChanges(stagedFiles.filter((f)=>"R" !== f.index && "D" !== f.index).map((f)=>f.path)) : []);
14725
+ const stagedRenames = [];
14726
+ for (const f of stagedFiles)if ("R" === f.index) {
14727
+ const rename = renames.find((r)=>r.to === f.path);
14728
+ if (rename) stagedRenames.push({
14729
+ path: rename.to,
14730
+ type: "rename",
14731
+ from: rename.from
14732
+ });
14733
+ }
14734
+ const stagedRenamedFromPaths = new Set(stagedRenames.map((r)=>r.from));
14735
+ const stagedRenamedToPaths = new Set(stagedRenames.map((r)=>r.path));
14736
+ const changes = [];
14737
+ for (const f of stagedFiles){
14738
+ if ("R" !== f.index) {
14739
+ if (!stagedModeOnlyPaths.has(f.path)) {
14740
+ if (!(stagedRenamedFromPaths.has(f.path) || stagedRenamedToPaths.has(f.path))) if ("D" === f.index) changes.push({
14741
+ path: f.path,
14742
+ type: "unlink"
14743
+ });
14744
+ else if ("A" === f.index || "?" === f.index) changes.push({
14745
+ path: f.path,
14746
+ type: "add"
14747
+ });
14748
+ else changes.push({
14749
+ path: f.path,
14750
+ type: "change"
14751
+ });
14752
+ }
14753
+ }
14754
+ }
14755
+ changes.push(...stagedRenames);
14756
+ return changes;
14757
+ } catch (e) {
14758
+ throw errors_createGitError.commandFailed("status", e.message);
14759
+ }
14760
+ }
14761
+ async function git_getCurrentOrFallbackBranchName(git) {
14762
+ const branchRef = (await git.revparse([
14763
+ "--abbrev-ref",
14764
+ "HEAD"
14765
+ ])).trim();
14766
+ if ("HEAD" !== branchRef) return branchRef;
14767
+ const [localBranches, remoteBranches] = await Promise.all([
14768
+ git.raw([
14769
+ "branch",
14770
+ "--format=%(refname:short)",
14771
+ "--contains",
14772
+ "HEAD"
14773
+ ]),
14774
+ git.raw([
14775
+ "branch",
14776
+ "-r",
14777
+ "--format=%(refname:short)",
14778
+ "--contains",
14779
+ "HEAD"
14780
+ ])
14781
+ ]);
14782
+ const branchNames = [
14783
+ ...parseBranchList(localBranches),
14784
+ ...parseBranchList(remoteBranches).filter((branchName)=>!branchName.endsWith("/HEAD")).map((branchName)=>branchName.replace(/^[^/]+\//, ""))
14785
+ ];
14786
+ const branchSet = new Set(branchNames);
14787
+ if (branchSet.has("master")) return "master";
14788
+ if (branchSet.has("main")) return "main";
14789
+ return "master";
14790
+ }
14791
+ function parseBranchList(output) {
14792
+ return output.split(/\r?\n/).map((line)=>line.trim()).filter(Boolean);
14793
+ }
14794
+ function anvil_api_getDefaultAnvilUrl() {
14795
+ return resolveAnvilUrl();
14796
+ }
14797
+ async function createAnvilApp(options) {
14798
+ let response;
14799
+ try {
14800
+ response = await fetch(`${options.anvilUrl}/ide/api/_/apps`, {
14801
+ method: "POST",
14802
+ headers: {
14803
+ Authorization: `Bearer ${options.authToken}`,
14804
+ "Content-Type": "application/json"
14805
+ },
14806
+ body: JSON.stringify({
14807
+ name: options.name,
14808
+ tags: options.tags
14809
+ })
14810
+ });
14811
+ } catch (error) {
14812
+ throw createNetworkError.network(error.message);
14813
+ }
14814
+ const data = await response.json().catch(()=>({}));
14815
+ if (!response.ok) {
14816
+ if (401 === response.status || 403 === response.status) throw createAuthError.invalid(data.error || "Not authorized to create an app");
14817
+ throw new Error(data.error || `Failed to create app: ${response.status} ${response.statusText}`);
14818
+ }
14819
+ if ("string" != typeof data.id || !data.id) throw new Error("App creation response did not contain an app ID.");
14820
+ return {
14821
+ id: data.id,
14822
+ ..."string" == typeof data.name ? {
14823
+ name: data.name
14824
+ } : {},
14825
+ ...data.tags ? {
14826
+ tags: data.tags
14827
+ } : {}
14828
+ };
14829
+ }
14830
+ async function anvil_api_validateAppId(appId, anvilUrl = anvil_api_getDefaultAnvilUrl(), username) {
14831
+ const authToken = await auth_getValidAuthToken(anvilUrl, username);
14832
+ try {
14833
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/validate-app-id`, {
14834
+ method: "POST",
14835
+ headers: {
14836
+ Authorization: `Bearer ${authToken}`,
14837
+ "Content-Type": "application/json"
14838
+ },
14839
+ body: JSON.stringify({
14840
+ app_id: appId
14841
+ })
14842
+ });
14843
+ const data = await resp.json();
14844
+ if (resp.ok) return {
14845
+ valid: data.valid ?? false,
14846
+ app_name: data.app_name
14847
+ };
14848
+ if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14849
+ if (403 === resp.status) throw createAppError.accessDenied(appId);
14850
+ if (404 === resp.status) throw createAppError.notFound(appId);
14851
+ logger_logger.debug(`validate-app-id failed: ${resp.status} ${resp.statusText}`);
14852
+ return {
14853
+ valid: false,
14854
+ error: data.error || `Server error: ${resp.status}`
14855
+ };
14856
+ } catch (error) {
14857
+ if (error.type) throw error;
14858
+ throw createNetworkError.network(error.message);
14859
+ }
14860
+ }
14861
+ async function lookupByCommit(commitId, branchName, anvilUrl = anvil_api_getDefaultAnvilUrl(), username) {
14862
+ const authToken = await auth_getValidAuthToken(anvilUrl, username);
14863
+ try {
14864
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/lookup-by-commit`, {
14865
+ method: "POST",
14866
+ headers: {
14867
+ Authorization: `Bearer ${authToken}`,
14868
+ "Content-Type": "application/json"
14869
+ },
14870
+ body: JSON.stringify({
14871
+ commit_id: commitId,
14872
+ branch_name: branchName
14873
+ })
14874
+ });
14875
+ if (!resp.ok) {
14876
+ if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14877
+ throw createNetworkError.server(resp.status, resp.statusText);
14878
+ }
14879
+ const data = await resp.json();
14880
+ return data;
14881
+ } catch (error) {
14882
+ if (error.type) throw error;
14883
+ throw createNetworkError.network(error.message);
14884
+ }
14885
+ }
14886
+ async function listAppsForCheckout(options = {}) {
14887
+ const anvilUrl = options.anvilUrl ?? anvil_api_getDefaultAnvilUrl();
14888
+ const authToken = await auth_getValidAuthToken(anvilUrl, options.username);
14889
+ const params = new URLSearchParams();
14890
+ if ("number" == typeof options.limit) params.set("limit", String(options.limit));
14891
+ if (options.cursor) params.set("cursor", options.cursor);
14892
+ if (options.q && options.q.trim()) params.set("q", options.q.trim());
14893
+ const query = params.toString();
14894
+ const url = `${anvilUrl}/ide/api/_/apps${query ? `?${query}` : ""}`;
14895
+ try {
14896
+ const resp = await fetch(url, {
14897
+ method: "GET",
14898
+ headers: {
14899
+ Authorization: `Bearer ${authToken}`
14900
+ },
14901
+ signal: options.signal
14902
+ });
14903
+ if (!resp.ok) {
14904
+ if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14905
+ throw createNetworkError.server(resp.status, resp.statusText);
14906
+ }
14907
+ const data = await resp.json();
14908
+ return {
14909
+ apps: Array.isArray(data.apps) ? data.apps : [],
14910
+ next_cursor: "string" == typeof data.next_cursor ? data.next_cursor : null
14911
+ };
14912
+ } catch (error) {
14913
+ if (error.type) throw error;
14914
+ throw createNetworkError.network(error.message);
14857
14915
  }
14858
- async hasSameIndexContentAsHead(relativePath) {
14859
- const headHash = (await this.git.revparse([
14860
- `HEAD:${relativePath}`
14861
- ])).trim();
14862
- const indexHash = (await this.git.revparse([
14863
- `:${relativePath}`
14864
- ])).trim();
14865
- return headHash === indexHash;
14916
+ }
14917
+ async function saveUpdates(appId, branchName, payload, anvilUrl = anvil_api_getDefaultAnvilUrl(), authToken) {
14918
+ try {
14919
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/${appId}/update?make_fresh_version=false&branch=${branchName}`, {
14920
+ method: "PUT",
14921
+ headers: {
14922
+ Authorization: `Bearer ${authToken}`,
14923
+ "Content-Type": "application/json"
14924
+ },
14925
+ body: JSON.stringify(payload)
14926
+ });
14927
+ const data = await resp.json();
14928
+ if (401 === resp.status) throw createAuthError.invalid(`Authentication failed (401): ${data.error || "unauthorized"}`);
14929
+ 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)`);
14930
+ return {
14931
+ version: data.version,
14932
+ overtaken: data.overtaken,
14933
+ latest_version: data.latest_version,
14934
+ error: 200 !== resp.status ? data.error || `Status ${resp.status}` : void 0
14935
+ };
14936
+ } catch (error) {
14937
+ if (error.type) throw error;
14938
+ throw createNetworkError.network(error.message);
14866
14939
  }
14867
- async restoreExecutableBit(relativePath, executable) {
14868
- const filePath = external_path_default().join(this.repoPath, relativePath);
14869
- const stat = await external_fs_.promises.stat(filePath);
14870
- const mode = executable ? 73 | stat.mode : -74 & stat.mode;
14871
- if ((511 & stat.mode) !== (511 & mode)) await external_fs_.promises.chmod(filePath, mode);
14940
+ }
14941
+ function anvil_api_getGitFetchUrl(appId, authToken, gitUrl = anvil_api_getDefaultAnvilUrl()) {
14942
+ const url = new URL(gitUrl);
14943
+ const encodedToken = encodeURIComponent(authToken);
14944
+ return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git?no_freeze=true&q=`;
14945
+ }
14946
+ function anvil_api_getGitPushUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
14947
+ const url = new URL(anvilUrl);
14948
+ const encodedToken = encodeURIComponent(authToken);
14949
+ return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git`;
14950
+ }
14951
+ function getWebSocketUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
14952
+ return anvilUrl.replace(/^http/, "ws") + `/ide/api/_/apps/${appId}/ws?access_token=${authToken}`;
14953
+ }
14954
+ async function getLatestVersion() {
14955
+ try {
14956
+ const response = await fetch("https://registry.npmjs.org/@anvil-works/anvil-cli/latest");
14957
+ if (!response.ok) return null;
14958
+ const data = await response.json();
14959
+ return data.version;
14960
+ } catch (e) {
14961
+ return null;
14872
14962
  }
14873
- async hasUncommittedChanges() {
14874
- const status = await this.getStatus();
14875
- return !status.isClean;
14963
+ }
14964
+ function anvil_api_filterCandidates(candidates, explicitUrl, explicitUsername) {
14965
+ let filtered = candidates;
14966
+ if (explicitUrl) {
14967
+ const normalizedExplicit = config_normalizeAnvilUrl(explicitUrl);
14968
+ filtered = filtered.filter((c)=>c.detectedUrl && config_normalizeAnvilUrl(c.detectedUrl) === normalizedExplicit);
14876
14969
  }
14877
- async fetch(url, refSpec) {
14878
- let attempt = 0;
14879
- while(true)try {
14880
- await this.git.fetch([
14881
- url,
14882
- refSpec
14883
- ]);
14884
- if (attempt > 0) this.emit("fetch-retry-resolved", {
14885
- attempts: attempt,
14886
- outcome: "succeeded"
14887
- });
14888
- return;
14889
- } catch (e) {
14890
- const err = e;
14891
- if (!isWantNotValidError(err)) {
14892
- if (attempt > 0) this.emit("fetch-retry-resolved", {
14893
- attempts: attempt,
14894
- outcome: "failed"
14970
+ if (explicitUsername) filtered = filtered.filter((c)=>!c.detectedUsername || c.detectedUsername === explicitUsername);
14971
+ return filtered;
14972
+ }
14973
+ function anvil_api_formatCandidateLabel(candidate) {
14974
+ const parts = [
14975
+ candidate.appId
14976
+ ];
14977
+ if (candidate.detectedUrl) if (candidate.detectedUsername) parts.push(`(${candidate.detectedUsername} on ${candidate.detectedUrl})`);
14978
+ else parts.push(`(${candidate.detectedUrl})`);
14979
+ parts.push(`- ${candidate.description}`);
14980
+ return parts.join(" ");
14981
+ }
14982
+ function anvil_api_lookupRemoteInfoForAppId(appId, detectedRemotes) {
14983
+ const matches = detectedRemotes.filter((c)=>c.appId === appId);
14984
+ if (0 === matches.length) return {};
14985
+ const withUsername = matches.find((c)=>c.detectedUsername);
14986
+ if (withUsername) return {
14987
+ detectedUrl: withUsername.detectedUrl,
14988
+ detectedUsername: withUsername.detectedUsername
14989
+ };
14990
+ return {
14991
+ detectedUrl: matches[0].detectedUrl,
14992
+ detectedUsername: matches[0].detectedUsername
14993
+ };
14994
+ }
14995
+ async function anvil_api_detectAppIdsFromAllRemotes(repoPath) {
14996
+ const git = esm_default(repoPath);
14997
+ const out = [];
14998
+ try {
14999
+ const remotes = await git.getRemotes(true);
15000
+ for (const remote of remotes){
15001
+ const httpMatch = remote.refs.fetch?.match(/(https?):\/\/(?:[^@\/]+@)?([^\/]+)\/git\/([A-Z0-9]+)\.git/);
15002
+ if (httpMatch) {
15003
+ const [, scheme, host, detectedAppId] = httpMatch;
15004
+ const origin = new URL(`${scheme}://${host}`).origin;
15005
+ out.push({
15006
+ appId: detectedAppId,
15007
+ source: "remote",
15008
+ description: `Git remote '${remote.name}'`,
15009
+ detectedUrl: config_normalizeAnvilUrl(origin)
15010
+ });
15011
+ continue;
15012
+ }
15013
+ const sshMatch = remote.refs.fetch?.match(/ssh:\/\/([^@]+)@([^:]+):(\d+)\/(?:git\/)?([A-Z0-9]+)\.git/);
15014
+ if (sshMatch) {
15015
+ const [, usernamePart, host, , detectedAppId] = sshMatch;
15016
+ const detectedUsername = usernamePart.includes("%") ? decodeURIComponent(usernamePart) : usernamePart;
15017
+ const finalUsername = "git" !== detectedUsername ? detectedUsername : void 0;
15018
+ out.push({
15019
+ appId: detectedAppId,
15020
+ source: "remote",
15021
+ description: `Git remote '${remote.name}' (SSH)`,
15022
+ detectedUrl: config_normalizeAnvilUrl(host),
15023
+ detectedUsername: finalUsername
14895
15024
  });
14896
- throw errors_createGitError.fetchFailed(err.message);
14897
15025
  }
14898
- attempt++;
14899
- this.emit("fetch-retry", {
14900
- attempt,
14901
- delayMs: FETCH_RETRY_DELAY_MS,
14902
- error: err.message
14903
- });
14904
- logger_logger.verbose(`git fetch race (attempt ${attempt}, sleeping ${FETCH_RETRY_DELAY_MS}ms): ${err.message}`);
14905
- await (0, external_timers_promises_namespaceObject.setTimeout)(FETCH_RETRY_DELAY_MS);
14906
- }
14907
- }
14908
- async reset(ref, mode = "mixed") {
14909
- await this.runGitCommandWithIndexLockRetry("reset", ()=>this.git.reset([
14910
- `--${mode}`,
14911
- ref
14912
- ]));
14913
- }
14914
- async checkout(paths) {
14915
- await this.runGitCommandWithIndexLockRetry("checkout", ()=>this.git.checkout(paths));
14916
- }
14917
- async runGitCommandWithIndexLockRetry(command, operation) {
14918
- let lastError;
14919
- for(let attempt = 0; attempt <= git_GitService.INDEX_LOCK_RETRY_DELAYS_MS.length; attempt += 1)try {
14920
- await operation();
14921
- return;
14922
- } catch (e) {
14923
- lastError = e;
14924
- if (!this.isIndexLockError(e) || attempt === git_GitService.INDEX_LOCK_RETRY_DELAYS_MS.length) throw errors_createGitError.commandFailed(command, e.message);
14925
- await (0, external_timers_promises_namespaceObject.setTimeout)(git_GitService.INDEX_LOCK_RETRY_DELAYS_MS[attempt]);
14926
15026
  }
14927
- throw errors_createGitError.commandFailed(command, lastError.message);
14928
- }
14929
- isIndexLockError(error) {
14930
- const message = error.message ?? "";
14931
- return message.includes("Unable to create") && message.includes("index.lock") && message.includes("File exists");
14932
- }
14933
- async stash(message) {
14934
- try {
14935
- const args = [
14936
- "stash",
14937
- "push",
14938
- "--include-untracked"
14939
- ];
14940
- if (message) args.push("-m", message);
14941
- const result = await this.git.raw(args);
14942
- return !result.includes("No local changes to save");
14943
- } catch (e) {
14944
- throw errors_createGitError.commandFailed("stash", e.message);
15027
+ } catch (_e) {}
15028
+ return out;
15029
+ }
15030
+ async function anvil_api_detectAppIdsByCommitLookup(repoPath, options) {
15031
+ const anvilUrl = options.anvilUrl || resolveAnvilUrl();
15032
+ const username = options.username;
15033
+ const git = esm_default(repoPath);
15034
+ const out = [];
15035
+ try {
15036
+ const authToken = await auth_getValidAuthToken(anvilUrl, username);
15037
+ const commitId = (await git.revparse([
15038
+ "HEAD"
15039
+ ]))?.trim();
15040
+ if (!commitId) return out;
15041
+ const branchName = await git_getCurrentOrFallbackBranchName(git);
15042
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/lookup-by-commit`, {
15043
+ method: "POST",
15044
+ headers: {
15045
+ Authorization: `Bearer ${authToken}`,
15046
+ "Content-Type": "application/json"
15047
+ },
15048
+ body: JSON.stringify({
15049
+ commit_id: commitId,
15050
+ branch_name: branchName
15051
+ })
15052
+ });
15053
+ logger_logger.debug(`Reverse lookup by commit ${commitId} on branch ${branchName} returned ${resp.status}`);
15054
+ if (!resp.ok) {
15055
+ const errorBody = await resp.text();
15056
+ logger_logger.debug(`lookup-by-commit error: ${errorBody}`);
14945
15057
  }
14946
- }
14947
- async stashPop() {
14948
- try {
14949
- await this.git.raw([
14950
- "stash",
14951
- "pop"
14952
- ]);
14953
- } catch (e) {
14954
- throw errors_createGitError.commandFailed("stash pop", e.message);
15058
+ if (resp.ok) {
15059
+ const data = await resp.json();
15060
+ logger_logger.debug(`data ${JSON.stringify(data)}`);
15061
+ const apps = data?.apps ?? [];
15062
+ for (const app of apps)if (app?.app_id) out.push({
15063
+ appId: app.app_id,
15064
+ source: "remote",
15065
+ description: `Reverse lookup by commit${app.branch ? ` on branch '${app.branch}'` : ""}`
15066
+ });
14955
15067
  }
15068
+ } catch (_e) {}
15069
+ return out;
15070
+ }
15071
+ const external_fs_promises_namespaceObject = require("fs/promises");
15072
+ var external_fs_promises_default = /*#__PURE__*/ __webpack_require__.n(external_fs_promises_namespaceObject);
15073
+ function normalizeLineEndings(content) {
15074
+ return content.replace(/\r\n/g, "\n");
15075
+ }
15076
+ function pythonifyName(name) {
15077
+ return name.replace(/[^A-z0-9]/g, "_").replace(/^[0-9]/, "_$&");
15078
+ }
15079
+ function extractPythonName(relativePath, skipParts = 1) {
15080
+ const parts = relativePath.slice(0, -3).split("/");
15081
+ const isPackage = relativePath.endsWith("__init__.py");
15082
+ const relevantParts = isPackage ? parts.slice(skipParts, -1) : parts.slice(skipParts);
15083
+ return relevantParts.map(pythonifyName).join(".");
15084
+ }
15085
+ function deepEqual(a, b) {
15086
+ if (a === b) return true;
15087
+ if (null == a || null == b) return false;
15088
+ if (typeof a !== typeof b) return false;
15089
+ if ("object" != typeof a) return a === b;
15090
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
15091
+ if (Array.isArray(a)) {
15092
+ if (a.length !== b.length) return false;
15093
+ return a.every((item, index)=>deepEqual(item, b[index]));
14956
15094
  }
14957
- async clean(files) {
14958
- try {
14959
- await this.git.clean(CleanOptions.FORCE, files);
14960
- } catch (e) {
14961
- throw errors_createGitError.commandFailed("clean", e.message);
14962
- }
15095
+ const keysA = Object.keys(a);
15096
+ const keysB = Object.keys(b);
15097
+ if (keysA.length !== keysB.length) return false;
15098
+ return keysA.every((key)=>deepEqual(a[key], b[key]));
15099
+ }
15100
+ function parseHtmlWithFrontmatter(htmlContent) {
15101
+ const frontmatterPattern = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
15102
+ const match = htmlContent.match(frontmatterPattern);
15103
+ if (match) {
15104
+ const yamlStr = match[1];
15105
+ const html = match[2];
15106
+ const frontmatter = yamlStr ? external_js_yaml_default().load(yamlStr) ?? {} : {};
15107
+ return {
15108
+ frontmatter,
15109
+ html
15110
+ };
14963
15111
  }
14964
- async diffNames(fromCommit, toCommit) {
14965
- try {
14966
- const diffResult = await this.git.diff([
14967
- fromCommit,
14968
- toCommit,
14969
- "--name-only"
14970
- ]);
14971
- const files = diffResult.split("\n").filter((f)=>f.trim());
14972
- return files;
14973
- } catch (e) {
14974
- throw errors_createGitError.commandFailed("diff", e.message);
14975
- }
15112
+ return {
15113
+ frontmatter: {},
15114
+ html: htmlContent
15115
+ };
15116
+ }
15117
+ function http_formatHttpError(prefix, status, body) {
15118
+ if (!body) return `${prefix}: ${status}`;
15119
+ try {
15120
+ const parsed = JSON.parse(body);
15121
+ if ("string" == typeof parsed.error && parsed.error.trim()) return `${prefix}: ${parsed.error} (${status})`;
15122
+ } catch {}
15123
+ return `${prefix}: ${status} ${body}`;
15124
+ }
15125
+ const ANVIL_GITIGNORE_ENTRY = "/.anvil/";
15126
+ async function ensureProjectGitignoreIgnoresAnvilDir(projectRoot) {
15127
+ const rootGitignorePath = external_path_default().join(projectRoot, ".gitignore");
15128
+ try {
15129
+ const existing = await external_fs_.promises.readFile(rootGitignorePath, "utf8");
15130
+ if (existing.includes(ANVIL_GITIGNORE_ENTRY)) return false;
15131
+ const prefix = existing.endsWith("\n") || 0 === existing.length ? "" : "\n";
15132
+ await external_fs_.promises.writeFile(rootGitignorePath, `${existing}${prefix}${ANVIL_GITIGNORE_ENTRY}\n`, "utf8");
15133
+ return true;
15134
+ } catch (error) {
15135
+ if ("ENOENT" !== error.code) throw error;
15136
+ await external_fs_.promises.writeFile(rootGitignorePath, `${ANVIL_GITIGNORE_ENTRY}\n`, "utf8");
15137
+ return true;
14976
15138
  }
14977
- async getAheadBehind(localRef, remoteRef) {
14978
- try {
14979
- const result = await this.git.raw([
14980
- "rev-list",
14981
- "--left-right",
14982
- "--count",
14983
- `${remoteRef}...${localRef}`
14984
- ]);
14985
- const [behind, ahead] = result.trim().split("\t").map(Number);
14986
- return {
14987
- ahead,
14988
- behind
14989
- };
14990
- } catch (e) {
14991
- throw errors_createGitError.commandFailed("rev-list", e.message);
14992
- }
15139
+ }
15140
+ const ANVIL_DEPS_DIR = ".anvil/deps";
15141
+ const ANVIL_DEPS_MANIFEST = "manifest.json";
15142
+ const DEPS_MANIFEST_VERSION = 3;
15143
+ async function fetchAllDependencies(appId, anvilUrl, options = {}) {
15144
+ const { commit, includeDocs = true, knownDependencies = [] } = options;
15145
+ anvilUrl ??= await auth_resolveAuthAnvilUrl();
15146
+ const token = await auth_getValidAuthToken(anvilUrl, options.username);
15147
+ const body = {
15148
+ include_docs: includeDocs,
15149
+ known_dependencies: knownDependencies
15150
+ };
15151
+ if (commit) body.commit = commit;
15152
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/${appId}/resolved-dependencies`, {
15153
+ method: "POST",
15154
+ headers: {
15155
+ Authorization: `Bearer ${token}`,
15156
+ "Content-Type": "application/json"
15157
+ },
15158
+ body: JSON.stringify(body)
15159
+ });
15160
+ if (!resp.ok) {
15161
+ const errorBody = await resp.text();
15162
+ throw new Error(http_formatHttpError("Failed to fetch dependencies", resp.status, errorBody));
14993
15163
  }
14994
- async show(refPath) {
14995
- try {
14996
- const content = await this.git.show([
14997
- refPath
14998
- ]);
14999
- return content;
15000
- } catch (e) {
15001
- throw errors_createGitError.commandFailed("show", e.message);
15002
- }
15164
+ return await resp.json();
15165
+ }
15166
+ async function refreshDependencyCache(projectRoot, appId, anvilUrl, options = {}) {
15167
+ auth_setRepoContext(projectRoot);
15168
+ anvilUrl ??= await auth_resolveAuthAnvilUrl();
15169
+ const includeDocs = options.includeDocs ?? true;
15170
+ const git = new git_GitService(projectRoot);
15171
+ const commit = options.commit ?? await git.getCommitId();
15172
+ const sourceYaml = await git.show(`${commit}:anvil.yaml`).catch((error)=>{
15173
+ if (options.commit) return null;
15174
+ throw error;
15175
+ });
15176
+ const sourceDependencies = null === sourceYaml ? null : normalizeAnvilYamlDependenciesFromContent(sourceYaml);
15177
+ const knownDependencies = await getKnownDependenciesForCache(projectRoot, includeDocs);
15178
+ const response = await fetchAllDependencies(appId, anvilUrl, {
15179
+ username: options.username,
15180
+ commit,
15181
+ includeDocs,
15182
+ knownDependencies
15183
+ });
15184
+ if (!response.resolved_dependencies.length) {
15185
+ await clearDepsCache(projectRoot);
15186
+ return {
15187
+ response,
15188
+ addedGitignoreEntry: false
15189
+ };
15003
15190
  }
15004
- async push(url, refSpec, force = false) {
15005
- try {
15006
- const args = force ? [
15007
- "push",
15008
- "--force",
15009
- url,
15010
- refSpec
15011
- ] : [
15012
- "push",
15013
- url,
15014
- refSpec
15015
- ];
15016
- await this.git.raw(args);
15017
- } catch (e) {
15018
- throw errors_createGitError.commandFailed("push", e.message);
15019
- }
15191
+ const addedGitignoreEntry = await ensureProjectGitignoreIgnoresAnvilDir(projectRoot);
15192
+ await writeAllDepsToCache(projectRoot, response, {
15193
+ includeDocs,
15194
+ sourceDependencies
15195
+ });
15196
+ return {
15197
+ response,
15198
+ addedGitignoreEntry
15199
+ };
15200
+ }
15201
+ function depsDir(projectRoot) {
15202
+ return external_path_default().join(projectRoot, ANVIL_DEPS_DIR);
15203
+ }
15204
+ function depsManifestPath(projectRoot) {
15205
+ return external_path_default().join(depsDir(projectRoot), ANVIL_DEPS_MANIFEST);
15206
+ }
15207
+ function isResolvedDependencyManifestEntry(value) {
15208
+ if (!value || "object" != typeof value) return false;
15209
+ const entry = value;
15210
+ 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);
15211
+ }
15212
+ function isCachedDependencyManifestEntry(value) {
15213
+ if (!isResolvedDependencyManifestEntry(value)) return false;
15214
+ const entry = value;
15215
+ return "string" == typeof entry.package_name && entry.package_name.length > 0;
15216
+ }
15217
+ function isDepsCacheManifest(value) {
15218
+ if (!value || "object" != typeof value) return false;
15219
+ const manifest = value;
15220
+ 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);
15221
+ }
15222
+ async function readDepsCacheManifest(projectRoot) {
15223
+ try {
15224
+ const raw = await external_fs_promises_default().readFile(depsManifestPath(projectRoot), "utf-8");
15225
+ const parsed = JSON.parse(raw);
15226
+ return isDepsCacheManifest(parsed) ? parsed : void 0;
15227
+ } catch {
15228
+ return;
15020
15229
  }
15021
- async rebase(onto) {
15022
- try {
15023
- await this.git.raw([
15024
- "rebase",
15025
- onto
15026
- ]);
15027
- } catch (e) {
15028
- throw errors_createGitError.commandFailed("rebase", e.message);
15029
- }
15230
+ }
15231
+ async function getKnownDependenciesForCache(projectRoot, includeDocs) {
15232
+ const manifest = await readDepsCacheManifest(projectRoot);
15233
+ if (!manifest || manifest.includeDocs !== includeDocs) return [];
15234
+ return manifest.resolved_dependencies.map(({ app_id, commit_id })=>({
15235
+ app_id,
15236
+ commit_id
15237
+ }));
15238
+ }
15239
+ function getDependencyWatchSubscriptionsFromManifest(manifest) {
15240
+ if (!manifest) return [];
15241
+ const cachedByAppId = new Map(manifest.cached_dependencies.map((dep)=>[
15242
+ dep.app_id,
15243
+ dep
15244
+ ]));
15245
+ return manifest.resolved_dependencies.flatMap((dep)=>{
15246
+ if (!dep.branch) return [];
15247
+ return [
15248
+ {
15249
+ appId: dep.app_id,
15250
+ branch: dep.branch,
15251
+ packageName: cachedByAppId.get(dep.app_id)?.package_name
15252
+ }
15253
+ ];
15254
+ });
15255
+ }
15256
+ function normalizeAnvilYamlDependenciesFromContent(content) {
15257
+ const parsed = external_js_yaml_default().load(content);
15258
+ if (!parsed || "object" != typeof parsed || !Array.isArray(parsed.dependencies)) return [];
15259
+ return normalizeAnvilYamlDependenciesValue(parsed.dependencies);
15260
+ }
15261
+ function normalizeAnvilYamlDependenciesValue(value) {
15262
+ if (!Array.isArray(value)) return [];
15263
+ return value.map(normalizeForComparison);
15264
+ }
15265
+ async function readAnvilYamlDependencies(projectRoot) {
15266
+ try {
15267
+ const content = await external_fs_promises_default().readFile(external_path_default().join(projectRoot, "anvil.yaml"), "utf-8");
15268
+ return normalizeAnvilYamlDependenciesFromContent(content);
15269
+ } catch {
15270
+ return [];
15030
15271
  }
15031
- async rebaseAbort() {
15032
- try {
15033
- await this.git.raw([
15034
- "rebase",
15035
- "--abort"
15036
- ]);
15037
- } catch (e) {
15038
- throw errors_createGitError.commandFailed("rebase --abort", e.message);
15272
+ }
15273
+ function anvilYamlDependenciesEqual(left, right) {
15274
+ return deepEqual(left, right);
15275
+ }
15276
+ function normalizeForComparison(value) {
15277
+ if (Array.isArray(value)) return value.map(normalizeForComparison);
15278
+ if (value && "object" == typeof value) return Object.fromEntries(Object.entries(value).sort(([left], [right])=>left.localeCompare(right)).map(([key, entryValue])=>[
15279
+ key,
15280
+ normalizeForComparison(entryValue)
15281
+ ]));
15282
+ return value;
15283
+ }
15284
+ function cachedManifestEntry(dep, packageName) {
15285
+ return {
15286
+ app_id: dep.app_id,
15287
+ commit_id: dep.commit_id,
15288
+ ...dep.branch ? {
15289
+ branch: dep.branch
15290
+ } : {},
15291
+ package_name: packageName
15292
+ };
15293
+ }
15294
+ function safeDependencyPath(filePath, kind) {
15295
+ const normalizedPath = external_path_default().normalize(filePath);
15296
+ if (external_path_default().isAbsolute(normalizedPath) || normalizedPath.startsWith("..")) throw new Error(`Invalid dependency ${kind} path: ${filePath}`);
15297
+ return normalizedPath;
15298
+ }
15299
+ function dependencyPythonPath(name, kind) {
15300
+ const parts = name.split(".");
15301
+ if (0 === parts.length || parts.some((part)=>!part || "." === part || ".." === part || part.includes("/") || part.includes("\\"))) throw new Error(`Invalid dependency ${kind} name: ${name}`);
15302
+ return external_path_default().join(...parts);
15303
+ }
15304
+ async function writeDepToCache(projectRoot, dep) {
15305
+ const depDir = external_path_default().join(projectRoot, ANVIL_DEPS_DIR, dep.package_name);
15306
+ await external_fs_promises_default().rm(depDir, {
15307
+ recursive: true,
15308
+ force: true
15309
+ });
15310
+ await external_fs_promises_default().mkdir(external_path_default().join(depDir, "client_code"), {
15311
+ recursive: true
15312
+ });
15313
+ await external_fs_promises_default().mkdir(external_path_default().join(depDir, "server_code"), {
15314
+ recursive: true
15315
+ });
15316
+ await external_fs_promises_default().writeFile(external_path_default().join(depDir, "anvil.yaml"), external_js_yaml_default().dump({
15317
+ package_name: dep.package_name
15318
+ }));
15319
+ for (const mod of dep.modules ?? []){
15320
+ const modulePath = external_path_default().join(depDir, "client_code", dependencyPythonPath(mod.name, "module"));
15321
+ if (mod.is_package) {
15322
+ await external_fs_promises_default().mkdir(modulePath, {
15323
+ recursive: true
15324
+ });
15325
+ await external_fs_promises_default().writeFile(external_path_default().join(modulePath, "__init__.py"), mod.code);
15326
+ } else {
15327
+ const moduleFile = `${modulePath}.py`;
15328
+ await external_fs_promises_default().mkdir(external_path_default().dirname(moduleFile), {
15329
+ recursive: true
15330
+ });
15331
+ await external_fs_promises_default().writeFile(moduleFile, mod.code);
15039
15332
  }
15040
15333
  }
15041
- async isAncestor(ancestorRef, descendantRef) {
15042
- try {
15043
- const mergeBase = (await this.git.raw([
15044
- "merge-base",
15045
- ancestorRef,
15046
- descendantRef
15047
- ])).trim();
15048
- const ancestorCommit = (await this.git.revparse([
15049
- ancestorRef
15050
- ])).trim();
15051
- return mergeBase === ancestorCommit;
15052
- } catch {
15053
- return false;
15334
+ for (const form of dep.forms ?? []){
15335
+ const { code, class_name, is_package, ...formTemplate } = form;
15336
+ const formPath = external_path_default().join(depDir, "client_code", dependencyPythonPath(class_name, "form"));
15337
+ if (is_package) {
15338
+ await external_fs_promises_default().mkdir(formPath, {
15339
+ recursive: true
15340
+ });
15341
+ await external_fs_promises_default().writeFile(external_path_default().join(formPath, "__init__.py"), code);
15342
+ await external_fs_promises_default().writeFile(external_path_default().join(formPath, "form_template.yaml"), external_js_yaml_default().dump(formTemplate));
15343
+ } else {
15344
+ const formCodeFile = `${formPath}.py`;
15345
+ await external_fs_promises_default().mkdir(external_path_default().dirname(formCodeFile), {
15346
+ recursive: true
15347
+ });
15348
+ await external_fs_promises_default().writeFile(formCodeFile, code);
15349
+ await external_fs_promises_default().writeFile(`${formPath}.yaml`, external_js_yaml_default().dump(formTemplate));
15054
15350
  }
15055
15351
  }
15056
- async mergeFastForward(ref) {
15057
- await this.runGitCommandWithIndexLockRetry("merge --ff-only", ()=>this.git.raw([
15058
- "merge",
15059
- "--ff-only",
15060
- ref
15061
- ]));
15062
- }
15063
- async deleteRef(ref) {
15064
- try {
15065
- await this.git.raw([
15066
- "update-ref",
15067
- "-d",
15068
- ref
15069
- ]);
15070
- } catch (e) {}
15071
- }
15072
- async getRemotes() {
15073
- try {
15074
- const remotes = await this.git.getRemotes(true);
15075
- return remotes.map((r)=>({
15076
- name: r.name,
15077
- fetchUrl: r.refs.fetch
15078
- }));
15079
- } catch (e) {
15080
- throw errors_createGitError.commandFailed("remote", e.message);
15352
+ for (const mod of dep.server_modules ?? []){
15353
+ const modulePath = external_path_default().join(depDir, "server_code", dependencyPythonPath(mod.name, "server module"));
15354
+ if (mod.is_package) {
15355
+ await external_fs_promises_default().mkdir(modulePath, {
15356
+ recursive: true
15357
+ });
15358
+ await external_fs_promises_default().writeFile(external_path_default().join(modulePath, "__init__.py"), mod.code);
15359
+ } else {
15360
+ const moduleFile = `${modulePath}.py`;
15361
+ await external_fs_promises_default().mkdir(external_path_default().dirname(moduleFile), {
15362
+ recursive: true
15363
+ });
15364
+ await external_fs_promises_default().writeFile(moduleFile, mod.code);
15081
15365
  }
15082
15366
  }
15083
- async isGitInitialized() {
15084
- const gitPath = external_path_default().join(this.repoPath, ".git");
15085
- return external_fs_.existsSync(gitPath);
15367
+ for (const asset of dep.assets ?? []){
15368
+ const targetPath = external_path_default().join(depDir, "theme", "assets", safeDependencyPath(asset.name, "asset"));
15369
+ await external_fs_promises_default().mkdir(external_path_default().dirname(targetPath), {
15370
+ recursive: true
15371
+ });
15372
+ await external_fs_promises_default().writeFile(targetPath, Buffer.from(asset.content, "base64"));
15086
15373
  }
15087
- async removeEmptyDirectories(removedFiles) {
15088
- const directoriesToCheck = new Set();
15089
- for (const file of removedFiles){
15090
- let dir = external_path_default().dirname(file);
15091
- while(dir && "." !== dir){
15092
- directoriesToCheck.add(dir);
15093
- const parentDir = external_path_default().dirname(dir);
15094
- if (parentDir === dir) break;
15095
- dir = parentDir;
15096
- }
15097
- }
15098
- const sortedDirs = Array.from(directoriesToCheck).sort((a, b)=>b.split("/").length - a.split("/").length);
15099
- for (const dir of sortedDirs)try {
15100
- const dirPath = external_path_default().join(this.repoPath, dir);
15101
- if (!external_fs_.existsSync(dirPath)) continue;
15102
- const entries = await external_fs_.promises.readdir(dirPath);
15103
- if (0 === entries.length) await external_fs_.promises.rmdir(dirPath);
15104
- } catch (error) {}
15374
+ for (const doc of dep.docs ?? []){
15375
+ const targetPath = external_path_default().join(depDir, safeDependencyPath(doc.path, "doc"));
15376
+ await external_fs_promises_default().mkdir(external_path_default().dirname(targetPath), {
15377
+ recursive: true
15378
+ });
15379
+ await external_fs_promises_default().writeFile(targetPath, doc.content);
15105
15380
  }
15106
15381
  }
15107
- async function getStagedFileChanges(git, gitService) {
15108
- try {
15109
- const status = await git.status();
15110
- const renames = status.renamed || [];
15111
- const stagedFiles = status.files.filter((f)=>" " !== f.index && "?" !== f.index);
15112
- const stagedModeOnlyPaths = new Set(gitService ? await gitService.discardStagedModeOnlyChanges(stagedFiles.filter((f)=>"R" !== f.index && "D" !== f.index).map((f)=>f.path)) : []);
15113
- const stagedRenames = [];
15114
- for (const f of stagedFiles)if ("R" === f.index) {
15115
- const rename = renames.find((r)=>r.to === f.path);
15116
- if (rename) stagedRenames.push({
15117
- path: rename.to,
15118
- type: "rename",
15119
- from: rename.from
15120
- });
15121
- }
15122
- const stagedRenamedFromPaths = new Set(stagedRenames.map((r)=>r.from));
15123
- const stagedRenamedToPaths = new Set(stagedRenames.map((r)=>r.path));
15124
- const changes = [];
15125
- for (const f of stagedFiles){
15126
- if ("R" !== f.index) {
15127
- if (!stagedModeOnlyPaths.has(f.path)) {
15128
- if (!(stagedRenamedFromPaths.has(f.path) || stagedRenamedToPaths.has(f.path))) if ("D" === f.index) changes.push({
15129
- path: f.path,
15130
- type: "unlink"
15131
- });
15132
- else if ("A" === f.index || "?" === f.index) changes.push({
15133
- path: f.path,
15134
- type: "add"
15135
- });
15136
- else changes.push({
15137
- path: f.path,
15138
- type: "change"
15139
- });
15140
- }
15141
- }
15142
- }
15143
- changes.push(...stagedRenames);
15144
- return changes;
15145
- } catch (e) {
15146
- throw errors_createGitError.commandFailed("status", e.message);
15147
- }
15382
+ async function writeAllDepsToCache(projectRoot, response, options = {}) {
15383
+ const includeDocs = options.includeDocs ?? true;
15384
+ const manifest = await readDepsCacheManifest(projectRoot);
15385
+ const canReuseExistingCache = manifest?.includeDocs === includeDocs;
15386
+ if (!canReuseExistingCache) await clearDepsCache(projectRoot);
15387
+ const previousCached = canReuseExistingCache ? manifest.cached_dependencies : [];
15388
+ const resolvedAppIds = new Set(response.resolved_dependencies.map((dep)=>dep.app_id));
15389
+ 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), {
15390
+ recursive: true,
15391
+ force: true
15392
+ });
15393
+ for (const dep of response.dependencies)await writeDepToCache(projectRoot, dep);
15394
+ const returnedByAppId = new Map(response.dependencies.map((dep)=>[
15395
+ dep.app_id,
15396
+ dep
15397
+ ]));
15398
+ const previousByAppId = new Map(previousCached.map((dep)=>[
15399
+ dep.app_id,
15400
+ dep
15401
+ ]));
15402
+ const cachedDependencies = response.resolved_dependencies.map((dep)=>{
15403
+ const returned = returnedByAppId.get(dep.app_id);
15404
+ if (returned) return cachedManifestEntry(returned, returned.package_name);
15405
+ const previous = previousByAppId.get(dep.app_id);
15406
+ if (!previous) return;
15407
+ return cachedManifestEntry(dep, previous.package_name);
15408
+ }).filter((dep)=>void 0 !== dep);
15409
+ const newManifest = {
15410
+ version: DEPS_MANIFEST_VERSION,
15411
+ includeDocs,
15412
+ source_dependencies: void 0 === options.sourceDependencies ? await readAnvilYamlDependencies(projectRoot) : options.sourceDependencies,
15413
+ resolved_dependencies: response.resolved_dependencies,
15414
+ cached_dependencies: cachedDependencies
15415
+ };
15416
+ await external_fs_promises_default().mkdir(depsDir(projectRoot), {
15417
+ recursive: true
15418
+ });
15419
+ await external_fs_promises_default().writeFile(depsManifestPath(projectRoot), `${JSON.stringify(newManifest, null, 2)}\n`);
15420
+ }
15421
+ async function clearDepsCache(projectRoot) {
15422
+ await external_fs_promises_default().rm(depsDir(projectRoot), {
15423
+ recursive: true,
15424
+ force: true
15425
+ });
15148
15426
  }
15149
15427
  const external_chokidar_namespaceObject = require("chokidar");
15150
15428
  var external_chokidar_default = /*#__PURE__*/ __webpack_require__.n(external_chokidar_namespaceObject);
@@ -16208,8 +16486,10 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
16208
16486
  }
16209
16487
  if (void 0 !== value.toolbox_item && !isPlainObject(value.toolbox_item)) formTemplateValidation_pushIssue(issues, path("toolbox_item"), "must be an object");
16210
16488
  if (void 0 !== value.layout_metadata && !isPlainObject(value.layout_metadata)) formTemplateValidation_pushIssue(issues, path("layout_metadata"), "must be an object");
16489
+ if (void 0 !== value.help_text && "string" != typeof value.help_text) formTemplateValidation_pushIssue(issues, path("help_text"), "must be a string");
16211
16490
  if (void 0 !== value.item_type) if (isPlainObject(value.item_type)) {
16212
- if ("number" != typeof value.item_type.table_id) formTemplateValidation_pushIssue(issues, path("item_type.table_id"), "must be a number");
16491
+ if (void 0 !== value.item_type.table_name) validateNonEmptyString(value.item_type.table_name, path("item_type.table_name"), issues);
16492
+ else if ("number" != typeof value.item_type.table_id) formTemplateValidation_pushIssue(issues, path("item_type.table_id"), "must be a number");
16213
16493
  } else formTemplateValidation_pushIssue(issues, path("item_type"), "must be an object");
16214
16494
  return issues;
16215
16495
  }
@@ -16282,7 +16562,8 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
16282
16562
  "toolbox_item",
16283
16563
  "layout_metadata",
16284
16564
  "item_type",
16285
- "slots"
16565
+ "slots",
16566
+ "help_text"
16286
16567
  ]);
16287
16568
  const FRONTMATTER_DISALLOWED_KEYS = new Set([
16288
16569
  "container",
@@ -16912,7 +17193,7 @@ print(json.dumps({"issues": issues}))
16912
17193
  }
16913
17194
  function validateScheduledTasks(value, path) {
16914
17195
  const issues = [];
16915
- if (void 0 === value) return issues;
17196
+ if (null == value) return issues;
16916
17197
  if (!Array.isArray(value)) {
16917
17198
  validators_pushIssue(issues, path, "must be an array");
16918
17199
  return issues;
@@ -18411,6 +18692,15 @@ print(json.dumps({"issues": issues}))
18411
18692
  this.hasPendingChanges = true;
18412
18693
  await this.processSaveBatchWithEvents();
18413
18694
  }
18695
+ async flush() {
18696
+ const requireReady = ()=>{
18697
+ if (this.config.isPaused() || this.config.isCleanedUp() || !this.config.isSettlePeriodOver()) throw new Error("Cannot flush saves while watching is paused, closed, or changing branch");
18698
+ };
18699
+ requireReady();
18700
+ await this.forceSave();
18701
+ requireReady();
18702
+ if (this.hasPendingChanges) throw new Error("Saves remain pending");
18703
+ }
18414
18704
  cleanup() {
18415
18705
  if (this.hasPendingChanges) {
18416
18706
  logger_logger.progressEnd("sync");
@@ -18507,10 +18797,7 @@ print(json.dumps({"issues": issues}))
18507
18797
  status = await this.config.gitService.getStatus();
18508
18798
  } catch (e) {
18509
18799
  logger_logger.error(external_chalk_default().red(`Error reading git status: ${errors_getErrorMessage(e)}`));
18510
- return {
18511
- skipped: true,
18512
- reason: "Git status error"
18513
- };
18800
+ throw e;
18514
18801
  }
18515
18802
  let allChanges;
18516
18803
  if (this.config.stagedOnly) {
@@ -19356,6 +19643,7 @@ print(json.dumps({"issues": issues}))
19356
19643
  try {
19357
19644
  const previousSubscriptions = this.wsClient?.getDependencySubscriptions() ?? [];
19358
19645
  const { response, addedGitignoreEntry } = await refreshDependencyCache(this.repoPath, this.appId, this.anvilUrl, {
19646
+ username: this.username,
19359
19647
  includeDocs: true
19360
19648
  });
19361
19649
  if (addedGitignoreEntry) logger_logger.info("Added /.anvil/ to .gitignore");
@@ -19667,6 +19955,12 @@ print(json.dumps({"issues": issues}))
19667
19955
  if (!this.saveProcessor) return;
19668
19956
  return this.saveProcessor.forceSave();
19669
19957
  }
19958
+ async flushSaves() {
19959
+ if (!this.saveProcessor || this.isSwitchingForEnvironment) throw new Error("App synchronization is not ready");
19960
+ const branch = this.currentBranch;
19961
+ await this.saveProcessor.flush();
19962
+ if (this.isSwitchingForEnvironment || this.currentBranch !== branch || await this.gitService.getCurrentBranch() !== branch) throw new Error("App branch changed while flushing saves");
19963
+ }
19670
19964
  async getSuccessMessage() {
19671
19965
  try {
19672
19966
  const info = await this.gitService.getCommitInfo();
@@ -19927,6 +20221,18 @@ print(json.dumps({"issues": issues}))
19927
20221
  }
19928
20222
  throw new Error("Device login expired before it was approved.");
19929
20223
  }
20224
+ function quoteForPosixShell(value) {
20225
+ if ("" === value) return "''";
20226
+ return `'${value.replace(/'/g, "'\"'\"'")}'`;
20227
+ }
20228
+ function quoteForCmdShell(value) {
20229
+ const escaped = value.replace(/[%^&|<>()"!]/g, "^$&");
20230
+ return `"${escaped}"`;
20231
+ }
20232
+ function buildShellCommandLine(tokens) {
20233
+ if ("win32" === process.platform) return tokens.map(quoteForCmdShell).join(" ");
20234
+ return tokens.map(quoteForPosixShell).join(" ");
20235
+ }
19930
20236
  const CHECKOUT_ERROR_VALUE = "__ERROR__";
19931
20237
  function isAbortLikeError(error) {
19932
20238
  if (error instanceof Error && "AbortError" === error.name) return true;
@@ -20105,6 +20411,122 @@ print(json.dumps({"issues": issues}))
20105
20411
  }
20106
20412
  require("inquirer");
20107
20413
  require("ora");
20414
+ class CreateCheckoutError extends Error {
20415
+ created;
20416
+ constructor(message, created, options){
20417
+ super(message, options), this.created = created;
20418
+ this.name = "CreateCheckoutError";
20419
+ }
20420
+ }
20421
+ const create_defaultDeps = {
20422
+ getAccountsForUrl: auth_getAccountsForUrl,
20423
+ getValidAuthToken: auth_getValidAuthToken,
20424
+ createApp: createAnvilApp,
20425
+ checkout: executeCheckout
20426
+ };
20427
+ function validateCheckoutOptions(options) {
20428
+ if (false === options.checkout && (void 0 !== options.directory || void 0 !== options.gitUrl || options.open)) throw new Error("--no-checkout cannot be combined with a destination argument, --directory, --git-url, or --open.");
20429
+ }
20430
+ async function validateCreateInput(options) {
20431
+ validateCheckoutOptions(options);
20432
+ const name = options.name.trim();
20433
+ if (!name) throw new Error("App name is required.");
20434
+ const tags = void 0 === options.tags ? void 0 : [
20435
+ ...new Set(options.tags.map((tag)=>tag.trim().toLowerCase()))
20436
+ ];
20437
+ if (tags && (tags.length > 20 || tags.some((tag)=>tag.length > 50 || !/^[a-z0-9_-]+$/.test(tag)))) throw new Error("Use at most 20 unique tags, each 1–50 characters: letters, numbers, hyphens, or underscores.");
20438
+ if (false === options.checkout) return {
20439
+ name,
20440
+ tags,
20441
+ destinationPath: void 0
20442
+ };
20443
+ const directory = options.directory ?? sanitizeDirectoryName(name);
20444
+ if (!directory) throw new Error("App name cannot be used as a directory. Pass --directory <PATH>.");
20445
+ const destinationPath = external_path_default().resolve(directory);
20446
+ await validateCheckoutDestination(destinationPath);
20447
+ return {
20448
+ name,
20449
+ tags,
20450
+ destinationPath
20451
+ };
20452
+ }
20453
+ async function executeCreate(options, deps = create_defaultDeps) {
20454
+ const { name, tags, destinationPath } = await validateCreateInput(options);
20455
+ const anvilUrl = config_normalizeAnvilOrigin(options.anvilUrl ?? await resolveCheckoutUrlForApi() ?? "");
20456
+ const gitUrl = void 0 === options.gitUrl ? void 0 : config_normalizeAnvilOrigin(options.gitUrl);
20457
+ const accounts = options.user ? [] : await deps.getAccountsForUrl(anvilUrl);
20458
+ const username = options.user ?? accounts[0];
20459
+ if (!username) throw new Error(`Not logged in to ${anvilUrl}. Run 'anvil login ${anvilUrl}' and retry.`);
20460
+ if (!options.user && accounts.length > 1) throw new Error(`Multiple accounts found for ${anvilUrl}: ${accounts.join(", ")}. Use --user <USERNAME>.`);
20461
+ const authToken = await deps.getValidAuthToken(anvilUrl, username);
20462
+ const created = await deps.createApp({
20463
+ name,
20464
+ anvilUrl,
20465
+ authToken,
20466
+ ...tags ? {
20467
+ tags
20468
+ } : {}
20469
+ });
20470
+ if (void 0 === destinationPath) return {
20471
+ appId: created.id,
20472
+ appName: created.name,
20473
+ anvilUrl,
20474
+ username,
20475
+ tags: created.tags ?? tags ?? [],
20476
+ editorUrl: `${anvilUrl}/build/apps/${encodeURIComponent(created.id)}`
20477
+ };
20478
+ try {
20479
+ const checkout = await deps.checkout({
20480
+ input: created.id,
20481
+ directory: destinationPath,
20482
+ anvilUrl,
20483
+ ...gitUrl ? {
20484
+ gitUrl
20485
+ } : {},
20486
+ user: username
20487
+ });
20488
+ return {
20489
+ ...checkout,
20490
+ tags: created.tags ?? tags ?? []
20491
+ };
20492
+ } catch (error) {
20493
+ const details = {
20494
+ appId: created.id,
20495
+ appName: created.name,
20496
+ anvilUrl,
20497
+ gitUrl,
20498
+ destinationPath,
20499
+ username,
20500
+ tags: created.tags ?? tags ?? []
20501
+ };
20502
+ const retry = buildShellCommandLine([
20503
+ "anvil",
20504
+ "checkout",
20505
+ created.id,
20506
+ destinationPath,
20507
+ "--url",
20508
+ anvilUrl,
20509
+ "--user",
20510
+ username,
20511
+ ...gitUrl ? [
20512
+ "--git-url",
20513
+ gitUrl
20514
+ ] : []
20515
+ ]);
20516
+ const remainingFiles = await isDirectoryNonEmpty(destinationPath).catch(()=>true);
20517
+ const recovery = remainingFiles ? `Inspect the existing checkout at ${destinationPath} before retrying. For Git authentication problems, run ${buildShellCommandLine([
20518
+ "anvil",
20519
+ "git-auth",
20520
+ "doctor",
20521
+ "--repo",
20522
+ destinationPath
20523
+ ])}.` : `Retry checkout with: ${retry}`;
20524
+ const message = errors_getErrorMessage(error).replaceAll(authToken, "[REDACTED]").replaceAll(encodeURIComponent(authToken), "[REDACTED]");
20525
+ throw new CreateCheckoutError(`Created app ${created.id} at ${anvilUrl}, but checkout failed: ${message}. Do not rerun create. ${recovery}`, details, {
20526
+ cause: error
20527
+ });
20528
+ }
20529
+ }
20108
20530
  const defaultGitAuthDoctorDeps = {
20109
20531
  getAppAuthBinding: git_auth_getAppAuthBinding,
20110
20532
  getAccountsForUrl: auth_getAccountsForUrl,
@@ -20537,8 +20959,30 @@ print(json.dumps({"issues": issues}))
20537
20959
  const response = await buildGitCredentialResponse(operation, request, options);
20538
20960
  writeGitCredentialResponse(response);
20539
20961
  }
20540
- resolveAnvilUrl();
20541
- async function watch_decideFallbackUrl(explicitUrl, availableUrls) {
20962
+ const MODEL_TOKEN_LOCK_TIMEOUT_MS = 12000;
20963
+ const modelToken_defaultDeps = {
20964
+ getValidAuthToken: auth_getValidAuthToken,
20965
+ readAuthFromRepo: readAuthFromRepo,
20966
+ cwd: ()=>process.cwd()
20967
+ };
20968
+ async function getModelToken(options = {}) {
20969
+ const deps = options.deps ?? modelToken_defaultDeps;
20970
+ const repoPath = options.repoPath ?? deps.cwd();
20971
+ auth_setRepoContext(repoPath);
20972
+ if (!process.env["ANVIL_AUTH_FILE_ENCRYPTION_KEY"]) throw new Error("ANVIL_AUTH_FILE_ENCRYPTION_KEY is not set; cannot read repo auth tokens.");
20973
+ const auth = await deps.readAuthFromRepo(repoPath);
20974
+ if (!auth) throw new Error(`No Anvil auth tokens found for the repository at ${repoPath}.`);
20975
+ return deps.getValidAuthToken(auth.anvilUrl, auth.username, {
20976
+ lock: {
20977
+ timeoutMs: options.lockTimeoutMs ?? MODEL_TOKEN_LOCK_TIMEOUT_MS
20978
+ }
20979
+ });
20980
+ }
20981
+ async function executeModelTokenOperation(options = {}) {
20982
+ const token = await getModelToken(options);
20983
+ process.stdout.write(`${token}\n`);
20984
+ }
20985
+ async function appDetection_decideFallbackUrl(explicitUrl, availableUrls) {
20542
20986
  if (explicitUrl) return {
20543
20987
  source: "explicit",
20544
20988
  url: config_normalizeAnvilUrl(explicitUrl)
@@ -20624,7 +21068,7 @@ print(json.dumps({"issues": issues}))
20624
21068
  async function resolveCheckoutUrlForApi(explicitUrl, parsedUrl) {
20625
21069
  if (explicitUrl) return config_normalizeAnvilUrl(explicitUrl);
20626
21070
  if (parsedUrl) return config_normalizeAnvilUrl(parsedUrl);
20627
- const decision = await watch_decideFallbackUrl(void 0);
21071
+ const decision = await appDetection_decideFallbackUrl(void 0);
20628
21072
  if ("available-multiple" === decision.source) throw new Error(`Multiple logged-in Anvil URLs found: ${decision.urls.join(", ")}. Use --url <ANVIL_URL> to choose one.`);
20629
21073
  return decision.url;
20630
21074
  }
@@ -20684,7 +21128,7 @@ print(json.dumps({"issues": issues}))
20684
21128
  logger_logger.verbose(external_chalk_default().cyan("Resolved app ID: ") + external_chalk_default().bold(parsed.appId));
20685
21129
  const anvilUrl = await resolveCheckoutUrlForApi(options.anvilUrl, parsed.detectedUrl);
20686
21130
  if (!anvilUrl) throw new Error("Anvil URL is required. Use --url <ANVIL_URL> or pass an input URL containing the host.");
20687
- const gitUrl = options.gitUrl ?? anvilUrl;
21131
+ const gitUrl = void 0 === options.gitUrl ? anvilUrl : config_normalizeAnvilOrigin(options.gitUrl);
20688
21132
  const resolvedUsername = await resolveCheckoutUsernameForApi(anvilUrl, options.user);
20689
21133
  if (resolvedUsername) logger_logger.verbose(external_chalk_default().cyan("Using account: ") + external_chalk_default().bold(resolvedUsername));
20690
21134
  else logger_logger.verbose(external_chalk_default().cyan("No account preselected; resolving after auth token lookup."));
@@ -20748,7 +21192,6 @@ print(json.dumps({"issues": issues}))
20748
21192
  remoteName
20749
21193
  };
20750
21194
  }
20751
- const api_DEFAULT_ANVIL_URL = resolveAnvilUrl();
20752
21195
  async function api_checkUncommittedChanges(repoPath) {
20753
21196
  try {
20754
21197
  const git = esm_default(repoPath);
@@ -20797,9 +21240,10 @@ print(json.dumps({"issues": issues}))
20797
21240
  }
20798
21241
  }
20799
21242
  async function src_api_watch(repoPath, appId, options = {}) {
20800
- const { anvilUrl = api_DEFAULT_ANVIL_URL, gitUrl = anvilUrl, stagedOnly = false, username } = options;
20801
21243
  repoPath = external_path_default().resolve(repoPath);
20802
21244
  auth_setRepoContext(repoPath);
21245
+ const anvilUrl = options.anvilUrl ?? await auth_resolveAuthAnvilUrl();
21246
+ const { gitUrl = anvilUrl, stagedOnly = false, username } = options;
20803
21247
  const storedEnvPid = options.environment?.envPid ? void 0 : await environment_readEnvironmentPid(repoPath);
20804
21248
  const environment = options.environment ?? (storedEnvPid ? {
20805
21249
  envPid: storedEnvPid
@@ -20914,6 +21358,7 @@ print(json.dumps({"issues": issues}))
20914
21358
  }
20915
21359
  })();
20916
21360
  exports.ANVIL_SYNC_CLIENT_ID = __webpack_exports__.ANVIL_SYNC_CLIENT_ID;
21361
+ exports.AUTH_FILE_LOCK_DEFAULTS = __webpack_exports__.AUTH_FILE_LOCK_DEFAULTS;
20917
21362
  exports.AnvilYamlValidationResult = __webpack_exports__.AnvilYamlValidationResult;
20918
21363
  exports.AppIdCandidate = __webpack_exports__.AppIdCandidate;
20919
21364
  exports.AppIdWithContext = __webpack_exports__.AppIdWithContext;
@@ -20923,13 +21368,19 @@ exports.CheckoutCloneOptions = __webpack_exports__.CheckoutCloneOptions;
20923
21368
  exports.CheckoutExecutionDeps = __webpack_exports__.CheckoutExecutionDeps;
20924
21369
  exports.CheckoutOptions = __webpack_exports__.CheckoutOptions;
20925
21370
  exports.CheckoutResult = __webpack_exports__.CheckoutResult;
21371
+ exports.CreateCheckoutError = __webpack_exports__.CreateCheckoutError;
21372
+ exports.CreatedAnvilApp = __webpack_exports__.CreatedAnvilApp;
20926
21373
  exports.DeviceAuthorizationResponse = __webpack_exports__.DeviceAuthorizationResponse;
20927
21374
  exports.FormTemplateValidationResult = __webpack_exports__.FormTemplateValidationResult;
21375
+ exports.GetValidAuthTokenOptions = __webpack_exports__.GetValidAuthTokenOptions;
20928
21376
  exports.LoginResult = __webpack_exports__.LoginResult;
20929
21377
  exports.LogoutResult = __webpack_exports__.LogoutResult;
21378
+ exports.MODEL_TOKEN_HELPER_FILENAME = __webpack_exports__.MODEL_TOKEN_HELPER_FILENAME;
21379
+ exports.MODEL_TOKEN_LOCK_TIMEOUT_MS = __webpack_exports__.MODEL_TOKEN_LOCK_TIMEOUT_MS;
20930
21380
  exports.OAuthTokenResponse = __webpack_exports__.OAuthTokenResponse;
20931
21381
  exports.ParsedCheckoutInput = __webpack_exports__.ParsedCheckoutInput;
20932
21382
  exports.PreferredEditor = __webpack_exports__.PreferredEditor;
21383
+ exports.REFRESH_REQUEST_TIMEOUT_MS = __webpack_exports__.REFRESH_REQUEST_TIMEOUT_MS;
20933
21384
  exports.SettableConfigKey = __webpack_exports__.SettableConfigKey;
20934
21385
  exports.TokenData = __webpack_exports__.TokenData;
20935
21386
  exports.ValidateAppIdResponse = __webpack_exports__.ValidateAppIdResponse;
@@ -20942,6 +21393,7 @@ exports.branchFromEnvironmentVersion = __webpack_exports__.branchFromEnvironment
20942
21393
  exports.buildGitAuthDoctorReport = __webpack_exports__.buildGitAuthDoctorReport;
20943
21394
  exports.checkUncommittedChanges = __webpack_exports__.checkUncommittedChanges;
20944
21395
  exports.clearInMemoryTokens = __webpack_exports__.clearInMemoryTokens;
21396
+ exports.createAnvilApp = __webpack_exports__.createAnvilApp;
20945
21397
  exports.createCheckoutPickerSource = __webpack_exports__.createCheckoutPickerSource;
20946
21398
  exports.createWatchDiagnostic = __webpack_exports__.createWatchDiagnostic;
20947
21399
  exports.createWatchDiagnosticFromMessage = __webpack_exports__.createWatchDiagnosticFromMessage;
@@ -20949,7 +21401,9 @@ exports.deleteConfig = __webpack_exports__.deleteConfig;
20949
21401
  exports.detectAppIdsByCommitLookup = __webpack_exports__.detectAppIdsByCommitLookup;
20950
21402
  exports.detectAppIdsFromAllRemotes = __webpack_exports__.detectAppIdsFromAllRemotes;
20951
21403
  exports.executeCheckout = __webpack_exports__.executeCheckout;
21404
+ exports.executeCreate = __webpack_exports__.executeCreate;
20952
21405
  exports.executeGitCredentialOperation = __webpack_exports__.executeGitCredentialOperation;
21406
+ exports.executeModelTokenOperation = __webpack_exports__.executeModelTokenOperation;
20953
21407
  exports.filterCandidates = __webpack_exports__.filterCandidates;
20954
21408
  exports.formatCandidateLabel = __webpack_exports__.formatCandidateLabel;
20955
21409
  exports.formatGitAuthDoctorReport = __webpack_exports__.formatGitAuthDoctorReport;
@@ -20963,12 +21417,15 @@ exports.getGitFetchUrl = __webpack_exports__.getGitFetchUrl;
20963
21417
  exports.getGitPushUrl = __webpack_exports__.getGitPushUrl;
20964
21418
  exports.getInstalledPreferredEditors = __webpack_exports__.getInstalledPreferredEditors;
20965
21419
  exports.getLatestVersion = __webpack_exports__.getLatestVersion;
21420
+ exports.getModelToken = __webpack_exports__.getModelToken;
21421
+ exports.getModelTokenHelperScript = __webpack_exports__.getModelTokenHelperScript;
20966
21422
  exports.getPreferredEditorCommand = __webpack_exports__.getPreferredEditorCommand;
20967
21423
  exports.getSettableConfigKeys = __webpack_exports__.getSettableConfigKeys;
20968
21424
  exports.getValidAuthToken = __webpack_exports__.getValidAuthToken;
20969
21425
  exports.getWebSocketUrl = __webpack_exports__.getWebSocketUrl;
20970
21426
  exports.hasTokensForUrl = __webpack_exports__.hasTokensForUrl;
20971
21427
  exports.inspectCredentialHelperCommand = __webpack_exports__.inspectCredentialHelperCommand;
21428
+ exports.installModelTokenHelperScript = __webpack_exports__.installModelTokenHelperScript;
20972
21429
  exports.isCommandAvailable = __webpack_exports__.isCommandAvailable;
20973
21430
  exports.isDirectoryNonEmpty = __webpack_exports__.isDirectoryNonEmpty;
20974
21431
  exports.isPathInsideGitRepo = __webpack_exports__.isPathInsideGitRepo;
@@ -21004,9 +21461,11 @@ exports.validatePath = __webpack_exports__.validatePath;
21004
21461
  exports.verifyAndStoreTokens = __webpack_exports__.verifyAndStoreTokens;
21005
21462
  exports.verifyAuth = __webpack_exports__.verifyAuth;
21006
21463
  exports.watch = __webpack_exports__.watch;
21464
+ exports.withRepoAuthFileLock = __webpack_exports__.withRepoAuthFileLock;
21007
21465
  exports.writeEnvironmentPid = __webpack_exports__.writeEnvironmentPid;
21008
21466
  for(var __rspack_i in __webpack_exports__)if (-1 === [
21009
21467
  "ANVIL_SYNC_CLIENT_ID",
21468
+ "AUTH_FILE_LOCK_DEFAULTS",
21010
21469
  "AnvilYamlValidationResult",
21011
21470
  "AppIdCandidate",
21012
21471
  "AppIdWithContext",
@@ -21016,13 +21475,19 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
21016
21475
  "CheckoutExecutionDeps",
21017
21476
  "CheckoutOptions",
21018
21477
  "CheckoutResult",
21478
+ "CreateCheckoutError",
21479
+ "CreatedAnvilApp",
21019
21480
  "DeviceAuthorizationResponse",
21020
21481
  "FormTemplateValidationResult",
21482
+ "GetValidAuthTokenOptions",
21021
21483
  "LoginResult",
21022
21484
  "LogoutResult",
21485
+ "MODEL_TOKEN_HELPER_FILENAME",
21486
+ "MODEL_TOKEN_LOCK_TIMEOUT_MS",
21023
21487
  "OAuthTokenResponse",
21024
21488
  "ParsedCheckoutInput",
21025
21489
  "PreferredEditor",
21490
+ "REFRESH_REQUEST_TIMEOUT_MS",
21026
21491
  "SettableConfigKey",
21027
21492
  "TokenData",
21028
21493
  "ValidateAppIdResponse",
@@ -21035,6 +21500,7 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
21035
21500
  "buildGitAuthDoctorReport",
21036
21501
  "checkUncommittedChanges",
21037
21502
  "clearInMemoryTokens",
21503
+ "createAnvilApp",
21038
21504
  "createCheckoutPickerSource",
21039
21505
  "createWatchDiagnostic",
21040
21506
  "createWatchDiagnosticFromMessage",
@@ -21042,7 +21508,9 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
21042
21508
  "detectAppIdsByCommitLookup",
21043
21509
  "detectAppIdsFromAllRemotes",
21044
21510
  "executeCheckout",
21511
+ "executeCreate",
21045
21512
  "executeGitCredentialOperation",
21513
+ "executeModelTokenOperation",
21046
21514
  "filterCandidates",
21047
21515
  "formatCandidateLabel",
21048
21516
  "formatGitAuthDoctorReport",
@@ -21056,12 +21524,15 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
21056
21524
  "getGitPushUrl",
21057
21525
  "getInstalledPreferredEditors",
21058
21526
  "getLatestVersion",
21527
+ "getModelToken",
21528
+ "getModelTokenHelperScript",
21059
21529
  "getPreferredEditorCommand",
21060
21530
  "getSettableConfigKeys",
21061
21531
  "getValidAuthToken",
21062
21532
  "getWebSocketUrl",
21063
21533
  "hasTokensForUrl",
21064
21534
  "inspectCredentialHelperCommand",
21535
+ "installModelTokenHelperScript",
21065
21536
  "isCommandAvailable",
21066
21537
  "isDirectoryNonEmpty",
21067
21538
  "isPathInsideGitRepo",
@@ -21097,6 +21568,7 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
21097
21568
  "verifyAndStoreTokens",
21098
21569
  "verifyAuth",
21099
21570
  "watch",
21571
+ "withRepoAuthFileLock",
21100
21572
  "writeEnvironmentPid"
21101
21573
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
21102
21574
  Object.defineProperty(exports, '__esModule', {