@anvil-works/anvil-cli 0.8.0-canary.8 → 0.8.0-dev.0

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 (47) hide show
  1. package/dist/WatchSession.d.ts +2 -0
  2. package/dist/WatchSession.d.ts.map +1 -1
  3. package/dist/api.d.ts +5 -1
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/cli.js +1583 -1068
  6. package/dist/commands/configure.d.ts.map +1 -1
  7. package/dist/commands/db.d.ts +3 -0
  8. package/dist/commands/db.d.ts.map +1 -0
  9. package/dist/commands/deps.d.ts.map +1 -1
  10. package/dist/commands/env.d.ts +8 -0
  11. package/dist/commands/env.d.ts.map +1 -0
  12. package/dist/commands/index.d.ts +3 -1
  13. package/dist/commands/index.d.ts.map +1 -1
  14. package/dist/commands/login.d.ts.map +1 -1
  15. package/dist/commands/modelToken.d.ts +33 -0
  16. package/dist/commands/modelToken.d.ts.map +1 -0
  17. package/dist/commands/repl.d.ts +3 -0
  18. package/dist/commands/repl.d.ts.map +1 -0
  19. package/dist/commands/watch.d.ts.map +1 -1
  20. package/dist/errors.d.ts +11 -1
  21. package/dist/errors.d.ts.map +1 -1
  22. package/dist/index.js +1257 -988
  23. package/dist/program.d.ts.map +1 -1
  24. package/dist/services/anvil-api.d.ts.map +1 -1
  25. package/dist/services/auth.d.ts +42 -3
  26. package/dist/services/auth.d.ts.map +1 -1
  27. package/dist/services/db.d.ts +7 -0
  28. package/dist/services/db.d.ts.map +1 -0
  29. package/dist/services/deps.d.ts.map +1 -1
  30. package/dist/services/environment.d.ts +16 -0
  31. package/dist/services/environment.d.ts.map +1 -0
  32. package/dist/services/git-auth.d.ts +35 -0
  33. package/dist/services/git-auth.d.ts.map +1 -1
  34. package/dist/services/git.d.ts +1 -0
  35. package/dist/services/git.d.ts.map +1 -1
  36. package/dist/services/http.d.ts +2 -0
  37. package/dist/services/http.d.ts.map +1 -0
  38. package/dist/services/repl.d.ts +18 -0
  39. package/dist/services/repl.d.ts.map +1 -0
  40. package/dist/validatePython.d.ts.map +1 -1
  41. package/dist/watch/SaveProcessor.d.ts +2 -0
  42. package/dist/watch/SaveProcessor.d.ts.map +1 -1
  43. package/package.json +2 -2
  44. package/dist/commands/tables.d.ts +0 -7
  45. package/dist/commands/tables.d.ts.map +0 -1
  46. package/dist/services/tables.d.ts +0 -27
  47. package/dist/services/tables.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -7376,15 +7376,15 @@ var __webpack_exports__ = {};
7376
7376
  validateCheckoutDestination: ()=>validateCheckoutDestination,
7377
7377
  DeviceAuthorizationResponse: ()=>oauth_login_namespaceObject.DeviceAuthorizationResponse,
7378
7378
  OAuthTokenResponse: ()=>oauth_login_namespaceObject.OAuthTokenResponse,
7379
+ REFRESH_REQUEST_TIMEOUT_MS: ()=>REFRESH_REQUEST_TIMEOUT_MS,
7379
7380
  WatchSession: ()=>WatchSession_WatchSession,
7380
7381
  parseConfigSetValue: ()=>parseConfigSetValue,
7381
- requestDeviceAuthorization: ()=>requestDeviceAuthorization,
7382
7382
  getGitPushUrl: ()=>anvil_api_getGitPushUrl,
7383
+ requestDeviceAuthorization: ()=>requestDeviceAuthorization,
7383
7384
  requireEnvironmentBranch: ()=>requireEnvironmentBranch,
7384
7385
  setAuthTokensForAccount: ()=>setAccountTokens,
7385
- validateAnvilApp: ()=>validation_validateAnvilApp,
7386
7386
  ValidateAppIdResponse: ()=>anvil_api_namespaceObject.ValidateAppIdResponse,
7387
- verifyAndStoreTokens: ()=>auth_verifyAndStoreTokens,
7387
+ getModelToken: ()=>getModelToken,
7388
7388
  formatValidationPath: ()=>formatValidationPath,
7389
7389
  resolveAnvilUrl: ()=>resolveAnvilUrl,
7390
7390
  createCheckoutPickerSource: ()=>createCheckoutPickerSource,
@@ -7392,12 +7392,13 @@ var __webpack_exports__ = {};
7392
7392
  parseCheckoutInput: ()=>parseCheckoutInput,
7393
7393
  formatGitAuthDoctorReport: ()=>formatGitAuthDoctorReport,
7394
7394
  resetConfig: ()=>resetConfig,
7395
+ MODEL_TOKEN_HELPER_FILENAME: ()=>MODEL_TOKEN_HELPER_FILENAME,
7395
7396
  FormTemplateValidationResult: ()=>validators_namespaceObject.FormTemplateValidationResult,
7396
7397
  createWatchDiagnostic: ()=>createWatchDiagnostic,
7397
7398
  ValidationTarget: ()=>validators_namespaceObject.ValidationTarget,
7398
7399
  detectAppIdsByCommitLookup: ()=>anvil_api_detectAppIdsByCommitLookup,
7399
7400
  sanitizeDirectoryName: ()=>sanitizeDirectoryName,
7400
- validatePath: ()=>validatePath,
7401
+ validateAnvilApp: ()=>validation_validateAnvilApp,
7401
7402
  LoginResult: ()=>auth_namespaceObject.LoginResult,
7402
7403
  listAppsForCheckout: ()=>listAppsForCheckout,
7403
7404
  getInstalledPreferredEditors: ()=>getInstalledPreferredEditors,
@@ -7406,8 +7407,11 @@ var __webpack_exports__ = {};
7406
7407
  CheckoutExecutionDeps: ()=>checkout_namespaceObject.CheckoutExecutionDeps,
7407
7408
  validateFormTemplateHtml: ()=>validateFormTemplateHtml,
7408
7409
  ValidateBranchSyncStatusOptions: ()=>validation_namespaceObject.ValidateBranchSyncStatusOptions,
7410
+ validatePath: ()=>validatePath,
7411
+ withRepoAuthFileLock: ()=>withRepoAuthFileLock,
7409
7412
  BranchSyncStatus: ()=>validation_namespaceObject.BranchSyncStatus,
7410
7413
  getLatestVersion: ()=>getLatestVersion,
7414
+ getModelTokenHelperScript: ()=>getModelTokenHelperScript,
7411
7415
  isDirectoryNonEmpty: ()=>isDirectoryNonEmpty,
7412
7416
  syncToLatest: ()=>src_api_syncToLatest,
7413
7417
  filterCandidates: ()=>anvil_api_filterCandidates,
@@ -7416,7 +7420,7 @@ var __webpack_exports__ = {};
7416
7420
  isPathInsideGitRepo: ()=>isPathInsideGitRepo,
7417
7421
  normalizeAnvilUrl: ()=>config_normalizeAnvilUrl,
7418
7422
  ValidationIssue: ()=>validators_namespaceObject.ValidationIssue,
7419
- writeEnvironmentPid: ()=>writeEnvironmentPid,
7423
+ verifyAndStoreTokens: ()=>auth_verifyAndStoreTokens,
7420
7424
  readEnvironmentPid: ()=>environment_readEnvironmentPid,
7421
7425
  validateFormTemplate: ()=>validateFormTemplate,
7422
7426
  preferredEditors: ()=>preferredEditors,
@@ -7428,12 +7432,14 @@ var __webpack_exports__ = {};
7428
7432
  validateBranchSyncStatus: ()=>validation_validateBranchSyncStatus,
7429
7433
  checkUncommittedChanges: ()=>api_checkUncommittedChanges,
7430
7434
  hasTokensForUrl: ()=>auth_hasTokensForUrl,
7435
+ writeEnvironmentPid: ()=>writeEnvironmentPid,
7431
7436
  AnvilYamlValidationResult: ()=>validators_namespaceObject.AnvilYamlValidationResult,
7432
7437
  ValidatePathResult: ()=>validators_namespaceObject.ValidatePathResult,
7433
7438
  getSettableConfigKeys: ()=>getSettableConfigKeys,
7434
7439
  logout: ()=>logout,
7435
7440
  TokenData: ()=>auth_namespaceObject.TokenData,
7436
7441
  detectAppIdsFromAllRemotes: ()=>anvil_api_detectAppIdsFromAllRemotes,
7442
+ executeModelTokenOperation: ()=>executeModelTokenOperation,
7437
7443
  getWebSocketUrl: ()=>getWebSocketUrl,
7438
7444
  getAllConfig: ()=>getAllConfig,
7439
7445
  isCommandAvailable: ()=>config_isCommandAvailable,
@@ -7443,6 +7449,7 @@ var __webpack_exports__ = {};
7443
7449
  getValidAuthToken: ()=>auth_getValidAuthToken,
7444
7450
  watch: ()=>src_api_watch,
7445
7451
  deleteConfig: ()=>deleteConfig,
7452
+ installModelTokenHelperScript: ()=>installModelTokenHelperScript,
7446
7453
  getGitFetchUrl: ()=>anvil_api_getGitFetchUrl,
7447
7454
  PreferredEditor: ()=>config_namespaceObject.PreferredEditor,
7448
7455
  getAvailableAnvilUrls: ()=>getAvailableAnvilUrls,
@@ -7455,8 +7462,11 @@ var __webpack_exports__ = {};
7455
7462
  CheckoutResult: ()=>checkout_namespaceObject.CheckoutResult,
7456
7463
  ParsedCheckoutInput: ()=>checkout_namespaceObject.ParsedCheckoutInput,
7457
7464
  parseCredentialHelperCommand: ()=>parseCredentialHelperCommand,
7465
+ MODEL_TOKEN_LOCK_TIMEOUT_MS: ()=>MODEL_TOKEN_LOCK_TIMEOUT_MS,
7466
+ GetValidAuthTokenOptions: ()=>auth_namespaceObject.GetValidAuthTokenOptions,
7458
7467
  clearInMemoryTokens: ()=>clearInMemoryTokens,
7459
7468
  getDefaultDestinationDirectory: ()=>getDefaultDestinationDirectory,
7469
+ AUTH_FILE_LOCK_DEFAULTS: ()=>AUTH_FILE_LOCK_DEFAULTS,
7460
7470
  lookupByCommit: ()=>lookupByCommit,
7461
7471
  AppIdCandidate: ()=>anvil_api_namespaceObject.AppIdCandidate,
7462
7472
  verifyAuth: ()=>auth_verifyAuth
@@ -7485,12 +7495,14 @@ var __webpack_exports__ = {};
7485
7495
  __webpack_require__.r(auth_namespaceObject);
7486
7496
  __webpack_require__.d(auth_namespaceObject, {
7487
7497
  fg: ()=>ANVIL_SYNC_CLIENT_ID,
7498
+ ru: ()=>REFRESH_REQUEST_TIMEOUT_MS,
7488
7499
  Ou: ()=>clearInMemoryTokens,
7489
7500
  Fr: ()=>auth_getAccountsForUrl,
7490
7501
  u5: ()=>getAvailableAnvilUrls,
7491
7502
  _p: ()=>auth_getValidAuthToken,
7492
7503
  Fi: ()=>auth_hasTokensForUrl,
7493
7504
  ri: ()=>logout,
7505
+ UE: ()=>auth_resolveAuthAnvilUrl,
7494
7506
  f5: ()=>setInMemoryTokens,
7495
7507
  iT: ()=>auth_setRepoContext,
7496
7508
  CQ: ()=>auth_verifyAndStoreTokens,
@@ -13365,9 +13377,10 @@ var __webpack_exports__ = {};
13365
13377
  type: "auth_invalid",
13366
13378
  message
13367
13379
  }),
13368
- refreshFailed: (message)=>({
13380
+ refreshFailed: (message, status)=>({
13369
13381
  type: "token_refresh_failed",
13370
- message
13382
+ message,
13383
+ status
13371
13384
  })
13372
13385
  };
13373
13386
  const createAppError = {
@@ -13584,6 +13597,7 @@ var __webpack_exports__ = {};
13584
13597
  const external_module_namespaceObject = require("module");
13585
13598
  const requireFromHere = (0, external_module_namespaceObject.createRequire)(__filename);
13586
13599
  const GIT_CREDENTIAL_HELPER_FILENAME = "anvil-credential-helper.cjs";
13600
+ const MODEL_TOKEN_HELPER_FILENAME = "anvil-model-token-helper.cjs";
13587
13601
  const GIT_CREDENTIAL_HELPER_LOCATOR = `$(git rev-parse --git-common-dir)/${GIT_CREDENTIAL_HELPER_FILENAME}`;
13588
13602
  function getUrlConfigKey(appId) {
13589
13603
  return `anvil.auth.${appId}.url`;
@@ -13674,6 +13688,38 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13674
13688
  });
13675
13689
  `;
13676
13690
  }
13691
+ function getModelTokenHelperScript(modulePath) {
13692
+ return `#!/usr/bin/env node
13693
+ "use strict";
13694
+
13695
+ const { execFileSync } = require("child_process");
13696
+ const { executeModelTokenOperation } = require(${JSON.stringify(modulePath)});
13697
+
13698
+ function resolveRepoPath() {
13699
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
13700
+ cwd: process.cwd(),
13701
+ encoding: "utf8",
13702
+ }).trim();
13703
+ }
13704
+
13705
+ Promise.resolve()
13706
+ .then(() => executeModelTokenOperation({ repoPath: resolveRepoPath() }))
13707
+ .catch((e) => {
13708
+ process.stderr.write("anvil model token helper failed: " + ((e && e.message) || e) + "\\n");
13709
+ process.exit(1);
13710
+ });
13711
+ `;
13712
+ }
13713
+ async function installModelTokenHelperScript(helperPath, options) {
13714
+ await external_fs_.promises.mkdir(external_path_default().dirname(helperPath), {
13715
+ recursive: true
13716
+ });
13717
+ await external_fs_.promises.writeFile(helperPath, getModelTokenHelperScript(options?.modulePath ?? getCredentialHelperModulePath()), {
13718
+ mode: 448
13719
+ });
13720
+ await external_fs_.promises.chmod(helperPath, 448);
13721
+ return helperPath;
13722
+ }
13677
13723
  function findPackageRoot(startPath) {
13678
13724
  let dir = external_fs_default().existsSync(startPath) && external_fs_default().statSync(startPath).isDirectory() ? startPath : external_path_default().dirname(startPath);
13679
13725
  while(true){
@@ -13801,6 +13847,76 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13801
13847
  function getAuthFilePath(gitDir) {
13802
13848
  return external_path_default().join(gitDir, "anvil-auth.json");
13803
13849
  }
13850
+ const AUTH_FILE_LOCK_DEFAULTS = {
13851
+ staleMs: 30000,
13852
+ timeoutMs: 60000,
13853
+ retryDelayMs: 100,
13854
+ heartbeatMs: 5000
13855
+ };
13856
+ function git_auth_delay(ms) {
13857
+ return new Promise((resolve)=>setTimeout(resolve, ms));
13858
+ }
13859
+ async function acquireAuthFileLock(lockPath, options) {
13860
+ const deadline = Date.now() + options.timeoutMs;
13861
+ while(true){
13862
+ try {
13863
+ const handle = await external_fs_.promises.open(lockPath, "wx", 384);
13864
+ try {
13865
+ await handle.writeFile(JSON.stringify({
13866
+ pid: process.pid,
13867
+ acquiredAt: new Date().toISOString()
13868
+ }));
13869
+ } finally{
13870
+ await handle.close();
13871
+ }
13872
+ return;
13873
+ } catch (e) {
13874
+ if ("EEXIST" !== e.code) throw e;
13875
+ }
13876
+ try {
13877
+ const lockStat = await external_fs_.promises.stat(lockPath);
13878
+ if (Date.now() - lockStat.mtimeMs > options.staleMs) {
13879
+ await external_fs_.promises.rm(lockPath, {
13880
+ force: true
13881
+ });
13882
+ continue;
13883
+ }
13884
+ } catch {
13885
+ continue;
13886
+ }
13887
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for the Anvil auth file lock at ${lockPath}`);
13888
+ await git_auth_delay(options.retryDelayMs);
13889
+ }
13890
+ }
13891
+ async function withRepoAuthFileLock(repoPath, fn, options) {
13892
+ const gitDir = await getRepositoryGitDir(repoPath);
13893
+ const lockPath = `${getAuthFilePath(gitDir)}.lock`;
13894
+ const lockOptions = {
13895
+ ...AUTH_FILE_LOCK_DEFAULTS,
13896
+ ...options
13897
+ };
13898
+ await external_fs_.promises.mkdir(external_path_default().dirname(lockPath), {
13899
+ recursive: true
13900
+ });
13901
+ await acquireAuthFileLock(lockPath, lockOptions);
13902
+ const heartbeat = startAuthFileLockHeartbeat(lockPath, lockOptions.heartbeatMs);
13903
+ try {
13904
+ return await fn();
13905
+ } finally{
13906
+ clearInterval(heartbeat);
13907
+ await external_fs_.promises.rm(lockPath, {
13908
+ force: true
13909
+ });
13910
+ }
13911
+ }
13912
+ function startAuthFileLockHeartbeat(lockPath, heartbeatMs) {
13913
+ const heartbeat = setInterval(()=>{
13914
+ const now = new Date();
13915
+ external_fs_.promises.utimes(lockPath, now, now).catch(()=>{});
13916
+ }, heartbeatMs);
13917
+ heartbeat.unref();
13918
+ return heartbeat;
13919
+ }
13804
13920
  function decodeKey(b64key) {
13805
13921
  const key = Buffer.from(b64key, "base64");
13806
13922
  if (32 !== key.length) throw new Error("Encryption key must be a base64-encoded 32-byte key");
@@ -13845,10 +13961,19 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13845
13961
  recursive: true
13846
13962
  });
13847
13963
  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);
13964
+ const tempFilePath = `${authFilePath}.tmp-${external_node_crypto_default().randomBytes(6).toString("hex")}`;
13965
+ try {
13966
+ await external_fs_.promises.writeFile(tempFilePath, encrypt(authJson, encryptionKey), {
13967
+ mode: 384
13968
+ });
13969
+ await external_fs_.promises.chmod(tempFilePath, 448);
13970
+ await external_fs_.promises.rename(tempFilePath, authFilePath);
13971
+ } catch (e) {
13972
+ await external_fs_.promises.rm(tempFilePath, {
13973
+ force: true
13974
+ });
13975
+ throw e;
13976
+ }
13852
13977
  await git.raw([
13853
13978
  "config",
13854
13979
  "--local",
@@ -13859,6 +13984,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13859
13984
  }
13860
13985
  const readAuthFromRepo = async (repoPath)=>{
13861
13986
  const git = esm_default(repoPath);
13987
+ if (!await git.checkIsRepo()) return;
13862
13988
  const gitDir = await getRepositoryGitDir(repoPath, git);
13863
13989
  const authFilePath = getAuthFilePath(gitDir);
13864
13990
  if (!external_fs_default().existsSync(authFilePath)) return;
@@ -13870,6 +13996,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13870
13996
  return resolveAnvilUrl();
13871
13997
  }
13872
13998
  const ANVIL_SYNC_CLIENT_ID = "anvil-sync";
13999
+ const REFRESH_REQUEST_TIMEOUT_MS = 12000;
13873
14000
  let inMemoryAuth = null;
13874
14001
  function setInMemoryTokens(anvilUrl, username, tokens) {
13875
14002
  inMemoryAuth = {
@@ -13888,6 +14015,11 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13888
14015
  function auth_setRepoContext(repoPath) {
13889
14016
  repoContext = repoPath;
13890
14017
  }
14018
+ async function auth_resolveAuthAnvilUrl() {
14019
+ if (inMemoryAuth) return inMemoryAuth.anvilUrl;
14020
+ const repoAuth = repoContext ? await readAuthFromRepo(repoContext) : void 0;
14021
+ return repoAuth?.anvilUrl ?? resolveAnvilUrl();
14022
+ }
13891
14023
  const inFlightRefreshes = new Map();
13892
14024
  async function auth_verifyAuth(authToken, anvilUrl = getDefaultAnvilUrl()) {
13893
14025
  try {
@@ -13908,7 +14040,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13908
14040
  throw createAuthError.invalid(`Network error: ${e.message}`);
13909
14041
  }
13910
14042
  }
13911
- async function refreshAccessToken(refreshToken, anvilUrl = getDefaultAnvilUrl(), clientId = ANVIL_SYNC_CLIENT_ID) {
14043
+ async function refreshAccessToken(refreshToken, anvilUrl = getDefaultAnvilUrl(), clientId = ANVIL_SYNC_CLIENT_ID, options) {
13912
14044
  try {
13913
14045
  const tokenResponse = await fetch(`${anvilUrl}/oauth/token`, {
13914
14046
  method: "POST",
@@ -13919,11 +14051,12 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13919
14051
  grant_type: "refresh_token",
13920
14052
  refresh_token: refreshToken,
13921
14053
  client_id: clientId
13922
- })
14054
+ }),
14055
+ signal: AbortSignal.timeout(options?.timeoutMs ?? REFRESH_REQUEST_TIMEOUT_MS)
13923
14056
  });
13924
14057
  if (!tokenResponse.ok) {
13925
14058
  const errorText = await tokenResponse.text();
13926
- throw createAuthError.refreshFailed(`Failed to refresh token: ${tokenResponse.status} ${errorText}`);
14059
+ throw createAuthError.refreshFailed(`Failed to refresh token: ${tokenResponse.status} ${errorText}`, tokenResponse.status);
13927
14060
  }
13928
14061
  const tokenData = await tokenResponse.json();
13929
14062
  return tokenData;
@@ -13940,17 +14073,16 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13940
14073
  return hasTokens(anvilUrl, username);
13941
14074
  }
13942
14075
  }
13943
- async function auth_getValidAuthToken(anvilUrl = getDefaultAnvilUrl(), username) {
14076
+ async function auth_getValidAuthToken(anvilUrl = getDefaultAnvilUrl(), username, options) {
13944
14077
  const normalized = config_normalizeAnvilUrl(anvilUrl);
13945
14078
  const source = await resolveTokenSource(normalized, username);
13946
14079
  const tokens = getSourceTokens(source);
13947
14080
  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) {
14081
+ if (tokens.refreshToken && isAccessTokenExpired(tokens)) {
13950
14082
  const refreshKey = getRefreshKey(normalized, username, source, tokens.refreshToken);
13951
14083
  const existingRefresh = inFlightRefreshes.get(refreshKey);
13952
14084
  if (existingRefresh) return existingRefresh;
13953
- const refreshPromise = refreshAndStoreAccessToken(normalized, username, source, tokens.refreshToken).finally(()=>{
14085
+ const refreshPromise = refreshAndStoreAccessToken(normalized, username, source, tokens.refreshToken, options).finally(()=>{
13954
14086
  inFlightRefreshes.delete(refreshKey);
13955
14087
  });
13956
14088
  inFlightRefreshes.set(refreshKey, refreshPromise);
@@ -13980,6 +14112,9 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13980
14112
  function getSourceTokens(source) {
13981
14113
  return "store" === source.type ? source.tokens : source.auth.tokens;
13982
14114
  }
14115
+ function isAccessTokenExpired(tokens) {
14116
+ return null !== tokens.authTokenExpiresAt && tokens.authTokenExpiresAt <= Math.floor(Date.now() / 1000) + 60;
14117
+ }
13983
14118
  function getStoreAccountUsername(normalizedUrl, username, refreshToken) {
13984
14119
  if (username) return username;
13985
14120
  const urlTokens = getUrlTokens(normalizedUrl);
@@ -13998,11 +14133,43 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
13998
14133
  return `store:${normalizedUrl}:${source.accountUsername ?? username ?? refreshToken}`;
13999
14134
  }
14000
14135
  }
14001
- async function refreshAndStoreAccessToken(normalizedUrl, username, source, refreshToken) {
14136
+ async function refreshAndStoreAccessToken(normalizedUrl, username, source, refreshToken, options) {
14137
+ if ("repo" === source.type) return refreshRepoAuthTokens(source.repoPath, {
14138
+ anvilUrl: normalizedUrl,
14139
+ username,
14140
+ auth: source.auth,
14141
+ refreshToken,
14142
+ lock: options?.lock,
14143
+ requestTimeoutMs: options?.requestTimeoutMs
14144
+ });
14145
+ return performTokenRefresh(normalizedUrl, username, source, refreshToken, options?.requestTimeoutMs);
14146
+ }
14147
+ async function refreshRepoAuthTokens(repoPath, options) {
14148
+ const normalizedUrl = config_normalizeAnvilUrl(options.anvilUrl);
14149
+ return withRepoAuthFileLock(repoPath, async ()=>{
14150
+ let auth = options.auth;
14151
+ let refreshToken = options.refreshToken;
14152
+ const current = await readAuthFromRepo(repoPath);
14153
+ if (current && authMatches(current, normalizedUrl, options.username)) {
14154
+ if (current.tokens.authToken && !isAccessTokenExpired(current.tokens)) return current.tokens.authToken;
14155
+ auth = current;
14156
+ refreshToken = current.tokens.refreshToken ?? refreshToken;
14157
+ }
14158
+ if (!auth || !refreshToken) throw createAuthError.required("No repo auth tokens available to refresh.");
14159
+ return performTokenRefresh(normalizedUrl, options.username, {
14160
+ type: "repo",
14161
+ auth,
14162
+ repoPath
14163
+ }, refreshToken, options.requestTimeoutMs);
14164
+ }, options.lock);
14165
+ }
14166
+ async function performTokenRefresh(normalizedUrl, username, source, refreshToken, requestTimeoutMs) {
14002
14167
  try {
14003
14168
  const tokens = getSourceTokens(source);
14004
14169
  const clientId = tokens.clientId ?? ANVIL_SYNC_CLIENT_ID;
14005
- const tokenData = await refreshAccessToken(refreshToken, normalizedUrl, clientId);
14170
+ const tokenData = await refreshAccessToken(refreshToken, normalizedUrl, clientId, {
14171
+ timeoutMs: requestTimeoutMs
14172
+ });
14006
14173
  const newTokens = {
14007
14174
  authToken: tokenData.access_token,
14008
14175
  refreshToken: tokenData.refresh_token,
@@ -14024,10 +14191,26 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
14024
14191
  }
14025
14192
  return tokenData.access_token;
14026
14193
  } catch (e) {
14194
+ if ("repo" === source.type && isRefreshTokenRejection(e)) await clearRepoAuthTokens(source.repoPath, source.auth).catch(()=>{});
14027
14195
  await clearTokensForAccount(normalizedUrl, username, refreshToken);
14028
14196
  throw e;
14029
14197
  }
14030
14198
  }
14199
+ function isRefreshTokenRejection(e) {
14200
+ const error = e;
14201
+ return error?.type === "token_refresh_failed" && (400 === error.status || 401 === error.status);
14202
+ }
14203
+ async function clearRepoAuthTokens(repoPath, auth) {
14204
+ const encryptionKey = process.env["ANVIL_AUTH_FILE_ENCRYPTION_KEY"];
14205
+ if (!encryptionKey) return;
14206
+ auth.tokens = {
14207
+ authToken: null,
14208
+ refreshToken: null,
14209
+ authTokenExpiresAt: null,
14210
+ clientId: auth.tokens.clientId
14211
+ };
14212
+ await writeAuthToFile(repoPath, auth, encryptionKey);
14213
+ }
14031
14214
  async function clearTokensForAccount(url, username, refreshToken) {
14032
14215
  if (inMemoryAuth || repoContext && await readAuthFromRepo(repoContext)) return;
14033
14216
  {
@@ -14109,1042 +14292,1074 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
14109
14292
  return getAvailableUrls();
14110
14293
  }
14111
14294
  }
14112
- function anvil_api_getDefaultAnvilUrl() {
14113
- return resolveAnvilUrl();
14295
+ const promises_namespaceObject = require("timers/promises");
14296
+ const WANT_NOT_VALID_RE = /\bwant\s+[0-9a-f]{7,40}\s+not valid\b/i;
14297
+ const FETCH_RETRY_DELAY_MS = 200;
14298
+ function isWantNotValidError(error) {
14299
+ return WANT_NOT_VALID_RE.test(error.message || "");
14114
14300
  }
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);
14301
+ class git_GitService extends Emitter {
14302
+ git;
14303
+ repoPath;
14304
+ static INDEX_LOCK_RETRY_DELAYS_MS = [
14305
+ 25,
14306
+ 50,
14307
+ 100,
14308
+ 200
14309
+ ];
14310
+ constructor(repoPath){
14311
+ super();
14312
+ this.repoPath = external_path_default().resolve(repoPath);
14313
+ this.git = esm_default(this.repoPath);
14144
14314
  }
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);
14315
+ getGit() {
14316
+ return this.git;
14317
+ }
14318
+ getRepoPath() {
14319
+ return this.repoPath;
14320
+ }
14321
+ async getGitDir() {
14322
+ try {
14323
+ const gitDir = (await this.git.revparse([
14324
+ "--git-dir"
14325
+ ])).trim();
14326
+ if (!external_path_default().isAbsolute(gitDir)) return external_path_default().resolve(this.repoPath, gitDir);
14327
+ return gitDir;
14328
+ } catch (e) {
14329
+ throw errors_createGitError.commandFailed("rev-parse --git-dir", e.message);
14163
14330
  }
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
14331
  }
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);
14332
+ async getCurrentBranch() {
14333
+ try {
14334
+ const branchRef = await this.git.revparse([
14335
+ "--abbrev-ref",
14336
+ "HEAD"
14337
+ ]);
14338
+ if ("HEAD" === branchRef) throw createSyncError.detachedHead();
14339
+ return branchRef;
14340
+ } catch (e) {
14341
+ if ("detached_head" === e.type) throw e;
14342
+ throw createSyncError.detachedHead();
14191
14343
  }
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
14344
  }
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);
14345
+ async getCommitId() {
14346
+ try {
14347
+ const commitId = (await this.git.revparse([
14348
+ "HEAD"
14349
+ ])).trim();
14350
+ return commitId;
14351
+ } catch (e) {
14352
+ throw errors_createGitError.commandFailed("revparse", e.message);
14353
+ }
14224
14354
  }
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}`);
14345
- }
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
- });
14355
+ async getCommitInfo() {
14356
+ try {
14357
+ const hash = (await this.git.revparse([
14358
+ "HEAD"
14359
+ ])).trim();
14360
+ const message = (await this.git.raw([
14361
+ "log",
14362
+ "-1",
14363
+ "--format=%s"
14364
+ ])).trim();
14365
+ return {
14366
+ hash,
14367
+ shortHash: hash.substring(0, 8),
14368
+ message
14369
+ };
14370
+ } catch (e) {
14371
+ throw errors_createGitError.commandFailed("log", e.message);
14355
14372
  }
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
- }
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
- };
14399
14373
  }
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;
14418
- }
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));
14450
- }
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
- };
14467
- }
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;
14374
+ async getStatus() {
14375
+ try {
14376
+ const status = await this.git.status();
14377
+ const staged = status.files.filter((file)=>{
14378
+ const indexStatus = file.index.trim();
14379
+ return indexStatus.length > 0 && "?" !== indexStatus;
14380
+ }).map((file)=>file.path);
14381
+ return {
14382
+ isClean: status.isClean(),
14383
+ modified: status.modified,
14384
+ notAdded: status.not_added,
14385
+ created: status.created,
14386
+ deleted: status.deleted,
14387
+ staged,
14388
+ renamed: status.renamed || []
14389
+ };
14390
+ } catch (e) {
14391
+ throw errors_createGitError.commandFailed("status", e.message);
14392
+ }
14505
14393
  }
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 [];
14394
+ async discardModeOnlyChanges(paths) {
14395
+ const discarded = [];
14396
+ for (const relativePath of paths)try {
14397
+ const headMode = await this.getHeadFileMode(relativePath);
14398
+ if ("100644" !== headMode && "100755" !== headMode) continue;
14399
+ const contentUnchanged = await this.hasSameContentAsHead(relativePath);
14400
+ if (!contentUnchanged) continue;
14401
+ await this.restoreExecutableBit(relativePath, "100755" === headMode);
14402
+ discarded.push(relativePath);
14403
+ } catch (e) {
14404
+ throw errors_createGitError.commandFailed("discard mode-only changes", e.message);
14405
+ }
14406
+ return discarded;
14547
14407
  }
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);
14408
+ async discardStagedModeOnlyChanges(paths) {
14409
+ const discarded = [];
14410
+ for (const relativePath of paths)try {
14411
+ const headMode = await this.getHeadFileMode(relativePath);
14412
+ if ("100644" !== headMode && "100755" !== headMode) continue;
14413
+ const indexContentUnchanged = await this.hasSameIndexContentAsHead(relativePath);
14414
+ if (!indexContentUnchanged) continue;
14415
+ await this.git.raw([
14416
+ "reset",
14417
+ "-q",
14418
+ "HEAD",
14419
+ "--",
14420
+ relativePath
14421
+ ]);
14422
+ await this.restoreExecutableBit(relativePath, "100755" === headMode);
14423
+ discarded.push(relativePath);
14424
+ } catch (e) {
14425
+ throw errors_createGitError.commandFailed("discard staged mode-only changes", e.message);
14608
14426
  }
14427
+ return discarded;
14609
14428
  }
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
14429
+ async getHeadFileMode(relativePath) {
14430
+ const output = await this.git.raw([
14431
+ "ls-tree",
14432
+ "HEAD",
14433
+ "--",
14434
+ relativePath
14435
+ ]);
14436
+ const match = output.match(/^(\d{6})\s/);
14437
+ return match?.[1] ?? null;
14438
+ }
14439
+ async hasSameContentAsHead(relativePath) {
14440
+ const headHash = (await this.git.revparse([
14441
+ `HEAD:${relativePath}`
14442
+ ])).trim();
14443
+ const worktreeHash = (await this.git.raw([
14444
+ "hash-object",
14445
+ "--",
14446
+ relativePath
14447
+ ])).trim();
14448
+ return headHash === worktreeHash;
14449
+ }
14450
+ async hasSameIndexContentAsHead(relativePath) {
14451
+ const headHash = (await this.git.revparse([
14452
+ `HEAD:${relativePath}`
14453
+ ])).trim();
14454
+ const indexHash = (await this.git.revparse([
14455
+ `:${relativePath}`
14456
+ ])).trim();
14457
+ return headHash === indexHash;
14458
+ }
14459
+ async restoreExecutableBit(relativePath, executable) {
14460
+ const filePath = external_path_default().join(this.repoPath, relativePath);
14461
+ const stat = await external_fs_.promises.stat(filePath);
14462
+ const mode = executable ? 73 | stat.mode : -74 & stat.mode;
14463
+ if ((511 & stat.mode) !== (511 & mode)) await external_fs_.promises.chmod(filePath, mode);
14464
+ }
14465
+ async hasUncommittedChanges() {
14466
+ const status = await this.getStatus();
14467
+ return !status.isClean;
14468
+ }
14469
+ async fetch(url, refSpec) {
14470
+ let attempt = 0;
14471
+ while(true)try {
14472
+ await this.git.fetch([
14473
+ url,
14474
+ refSpec
14475
+ ]);
14476
+ if (attempt > 0) this.emit("fetch-retry-resolved", {
14477
+ attempts: attempt,
14478
+ outcome: "succeeded"
14616
14479
  });
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
14480
+ return;
14481
+ } catch (e) {
14482
+ const err = e;
14483
+ if (!isWantNotValidError(err)) {
14484
+ if (attempt > 0) this.emit("fetch-retry-resolved", {
14485
+ attempts: attempt,
14486
+ outcome: "failed"
14487
+ });
14488
+ throw errors_createGitError.fetchFailed(err.message);
14489
+ }
14490
+ attempt++;
14491
+ this.emit("fetch-retry", {
14492
+ attempt,
14493
+ delayMs: FETCH_RETRY_DELAY_MS,
14494
+ error: err.message
14623
14495
  });
14624
- await promises_default().writeFile(formCodeFile, code);
14625
- await promises_default().writeFile(`${formPath}.yaml`, external_js_yaml_default().dump(formTemplate));
14496
+ logger_logger.verbose(`git fetch race (attempt ${attempt}, sleeping ${FETCH_RETRY_DELAY_MS}ms): ${err.message}`);
14497
+ await (0, promises_namespaceObject.setTimeout)(FETCH_RETRY_DELAY_MS);
14626
14498
  }
14627
14499
  }
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);
14500
+ async reset(ref, mode = "mixed") {
14501
+ await this.runGitCommandWithIndexLockRetry("reset", ()=>this.git.reset([
14502
+ `--${mode}`,
14503
+ ref
14504
+ ]));
14505
+ }
14506
+ async checkout(paths) {
14507
+ await this.runGitCommandWithIndexLockRetry("checkout", ()=>this.git.checkout(paths));
14508
+ }
14509
+ async runGitCommandWithIndexLockRetry(command, operation) {
14510
+ let lastError;
14511
+ for(let attempt = 0; attempt <= git_GitService.INDEX_LOCK_RETRY_DELAYS_MS.length; attempt += 1)try {
14512
+ await operation();
14513
+ return;
14514
+ } catch (e) {
14515
+ lastError = e;
14516
+ if (!this.isIndexLockError(e) || attempt === git_GitService.INDEX_LOCK_RETRY_DELAYS_MS.length) throw errors_createGitError.commandFailed(command, e.message);
14517
+ await (0, promises_namespaceObject.setTimeout)(git_GitService.INDEX_LOCK_RETRY_DELAYS_MS[attempt]);
14641
14518
  }
14519
+ throw errors_createGitError.commandFailed(command, lastError.message);
14642
14520
  }
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"));
14521
+ isIndexLockError(error) {
14522
+ const message = error.message ?? "";
14523
+ return message.includes("Unable to create") && message.includes("index.lock") && message.includes("File exists");
14649
14524
  }
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);
14525
+ async stash(message) {
14526
+ try {
14527
+ const args = [
14528
+ "stash",
14529
+ "push",
14530
+ "--include-untracked"
14531
+ ];
14532
+ if (message) args.push("-m", message);
14533
+ const result = await this.git.raw(args);
14534
+ return !result.includes("No local changes to save");
14535
+ } catch (e) {
14536
+ throw errors_createGitError.commandFailed("stash", e.message);
14537
+ }
14656
14538
  }
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);
14539
+ async stashPop() {
14540
+ try {
14541
+ await this.git.raw([
14542
+ "stash",
14543
+ "pop"
14544
+ ]);
14545
+ } catch (e) {
14546
+ throw errors_createGitError.commandFailed("stash pop", e.message);
14547
+ }
14722
14548
  }
14723
- getGit() {
14724
- return this.git;
14549
+ async clean(files) {
14550
+ try {
14551
+ await this.git.clean(CleanOptions.FORCE, files);
14552
+ } catch (e) {
14553
+ throw errors_createGitError.commandFailed("clean", e.message);
14554
+ }
14725
14555
  }
14726
- getRepoPath() {
14727
- return this.repoPath;
14556
+ async diffNames(fromCommit, toCommit) {
14557
+ try {
14558
+ const diffResult = await this.git.diff([
14559
+ fromCommit,
14560
+ toCommit,
14561
+ "--name-only"
14562
+ ]);
14563
+ const files = diffResult.split("\n").filter((f)=>f.trim());
14564
+ return files;
14565
+ } catch (e) {
14566
+ throw errors_createGitError.commandFailed("diff", e.message);
14567
+ }
14728
14568
  }
14729
- async getGitDir() {
14569
+ async getAheadBehind(localRef, remoteRef) {
14730
14570
  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;
14571
+ const result = await this.git.raw([
14572
+ "rev-list",
14573
+ "--left-right",
14574
+ "--count",
14575
+ `${remoteRef}...${localRef}`
14576
+ ]);
14577
+ const [behind, ahead] = result.trim().split("\t").map(Number);
14578
+ return {
14579
+ ahead,
14580
+ behind
14581
+ };
14582
+ } catch (e) {
14583
+ throw errors_createGitError.commandFailed("rev-list", e.message);
14584
+ }
14585
+ }
14586
+ async show(refPath) {
14587
+ try {
14588
+ const content = await this.git.show([
14589
+ refPath
14590
+ ]);
14591
+ return content;
14592
+ } catch (e) {
14593
+ throw errors_createGitError.commandFailed("show", e.message);
14594
+ }
14595
+ }
14596
+ async push(url, refSpec, force = false) {
14597
+ try {
14598
+ const args = force ? [
14599
+ "push",
14600
+ "--force",
14601
+ url,
14602
+ refSpec
14603
+ ] : [
14604
+ "push",
14605
+ url,
14606
+ refSpec
14607
+ ];
14608
+ await this.git.raw(args);
14736
14609
  } catch (e) {
14737
- throw errors_createGitError.commandFailed("rev-parse --git-dir", e.message);
14610
+ throw errors_createGitError.commandFailed("push", e.message);
14738
14611
  }
14739
14612
  }
14740
- async getCurrentBranch() {
14613
+ async rebase(onto) {
14741
14614
  try {
14742
- const branchRef = await this.git.revparse([
14743
- "--abbrev-ref",
14744
- "HEAD"
14615
+ await this.git.raw([
14616
+ "rebase",
14617
+ onto
14745
14618
  ]);
14746
- if ("HEAD" === branchRef) throw createSyncError.detachedHead();
14747
- return branchRef;
14748
14619
  } catch (e) {
14749
- if ("detached_head" === e.type) throw e;
14750
- throw createSyncError.detachedHead();
14620
+ throw errors_createGitError.commandFailed("rebase", e.message);
14751
14621
  }
14752
14622
  }
14753
- async getCommitId() {
14623
+ async rebaseAbort() {
14754
14624
  try {
14755
- const commitId = (await this.git.revparse([
14756
- "HEAD"
14757
- ])).trim();
14758
- return commitId;
14625
+ await this.git.raw([
14626
+ "rebase",
14627
+ "--abort"
14628
+ ]);
14759
14629
  } catch (e) {
14760
- throw errors_createGitError.commandFailed("revparse", e.message);
14630
+ throw errors_createGitError.commandFailed("rebase --abort", e.message);
14761
14631
  }
14762
14632
  }
14763
- async getCommitInfo() {
14633
+ async isAncestor(ancestorRef, descendantRef) {
14764
14634
  try {
14765
- const hash = (await this.git.revparse([
14766
- "HEAD"
14635
+ const mergeBase = (await this.git.raw([
14636
+ "merge-base",
14637
+ ancestorRef,
14638
+ descendantRef
14767
14639
  ])).trim();
14768
- const message = (await this.git.raw([
14769
- "log",
14770
- "-1",
14771
- "--format=%s"
14640
+ const ancestorCommit = (await this.git.revparse([
14641
+ ancestorRef
14772
14642
  ])).trim();
14773
- return {
14774
- hash,
14775
- shortHash: hash.substring(0, 8),
14776
- message
14777
- };
14778
- } catch (e) {
14779
- throw errors_createGitError.commandFailed("log", e.message);
14643
+ return mergeBase === ancestorCommit;
14644
+ } catch {
14645
+ return false;
14780
14646
  }
14781
14647
  }
14782
- async getStatus() {
14648
+ async mergeFastForward(ref) {
14649
+ await this.runGitCommandWithIndexLockRetry("merge --ff-only", ()=>this.git.raw([
14650
+ "merge",
14651
+ "--ff-only",
14652
+ ref
14653
+ ]));
14654
+ }
14655
+ async deleteRef(ref) {
14783
14656
  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
- };
14657
+ await this.git.raw([
14658
+ "update-ref",
14659
+ "-d",
14660
+ ref
14661
+ ]);
14662
+ } catch (e) {}
14663
+ }
14664
+ async getRemotes() {
14665
+ try {
14666
+ const remotes = await this.git.getRemotes(true);
14667
+ return remotes.map((r)=>({
14668
+ name: r.name,
14669
+ fetchUrl: r.refs.fetch
14670
+ }));
14798
14671
  } catch (e) {
14799
- throw errors_createGitError.commandFailed("status", e.message);
14672
+ throw errors_createGitError.commandFailed("remote", e.message);
14800
14673
  }
14801
14674
  }
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);
14675
+ async isGitInitialized() {
14676
+ const gitPath = external_path_default().join(this.repoPath, ".git");
14677
+ return external_fs_.existsSync(gitPath);
14678
+ }
14679
+ async removeEmptyDirectories(removedFiles) {
14680
+ const directoriesToCheck = new Set();
14681
+ for (const file of removedFiles){
14682
+ let dir = external_path_default().dirname(file);
14683
+ while(dir && "." !== dir){
14684
+ directoriesToCheck.add(dir);
14685
+ const parentDir = external_path_default().dirname(dir);
14686
+ if (parentDir === dir) break;
14687
+ dir = parentDir;
14688
+ }
14813
14689
  }
14814
- return discarded;
14690
+ const sortedDirs = Array.from(directoriesToCheck).sort((a, b)=>b.split("/").length - a.split("/").length);
14691
+ for (const dir of sortedDirs)try {
14692
+ const dirPath = external_path_default().join(this.repoPath, dir);
14693
+ if (!external_fs_.existsSync(dirPath)) continue;
14694
+ const entries = await external_fs_.promises.readdir(dirPath);
14695
+ if (0 === entries.length) await external_fs_.promises.rmdir(dirPath);
14696
+ } catch (error) {}
14815
14697
  }
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;
14823
- await this.git.raw([
14824
- "reset",
14825
- "-q",
14826
- "HEAD",
14827
- "--",
14828
- relativePath
14829
- ]);
14830
- await this.restoreExecutableBit(relativePath, "100755" === headMode);
14831
- discarded.push(relativePath);
14832
- } catch (e) {
14833
- throw errors_createGitError.commandFailed("discard staged mode-only changes", e.message);
14698
+ }
14699
+ async function getStagedFileChanges(git, gitService) {
14700
+ try {
14701
+ const status = await git.status();
14702
+ const renames = status.renamed || [];
14703
+ const stagedFiles = status.files.filter((f)=>" " !== f.index && "?" !== f.index);
14704
+ const stagedModeOnlyPaths = new Set(gitService ? await gitService.discardStagedModeOnlyChanges(stagedFiles.filter((f)=>"R" !== f.index && "D" !== f.index).map((f)=>f.path)) : []);
14705
+ const stagedRenames = [];
14706
+ for (const f of stagedFiles)if ("R" === f.index) {
14707
+ const rename = renames.find((r)=>r.to === f.path);
14708
+ if (rename) stagedRenames.push({
14709
+ path: rename.to,
14710
+ type: "rename",
14711
+ from: rename.from
14712
+ });
14834
14713
  }
14835
- return discarded;
14714
+ const stagedRenamedFromPaths = new Set(stagedRenames.map((r)=>r.from));
14715
+ const stagedRenamedToPaths = new Set(stagedRenames.map((r)=>r.path));
14716
+ const changes = [];
14717
+ for (const f of stagedFiles){
14718
+ if ("R" !== f.index) {
14719
+ if (!stagedModeOnlyPaths.has(f.path)) {
14720
+ if (!(stagedRenamedFromPaths.has(f.path) || stagedRenamedToPaths.has(f.path))) if ("D" === f.index) changes.push({
14721
+ path: f.path,
14722
+ type: "unlink"
14723
+ });
14724
+ else if ("A" === f.index || "?" === f.index) changes.push({
14725
+ path: f.path,
14726
+ type: "add"
14727
+ });
14728
+ else changes.push({
14729
+ path: f.path,
14730
+ type: "change"
14731
+ });
14732
+ }
14733
+ }
14734
+ }
14735
+ changes.push(...stagedRenames);
14736
+ return changes;
14737
+ } catch (e) {
14738
+ throw errors_createGitError.commandFailed("status", e.message);
14836
14739
  }
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;
14740
+ }
14741
+ async function git_getCurrentOrFallbackBranchName(git) {
14742
+ const branchRef = (await git.revparse([
14743
+ "--abbrev-ref",
14744
+ "HEAD"
14745
+ ])).trim();
14746
+ if ("HEAD" !== branchRef) return branchRef;
14747
+ const [localBranches, remoteBranches] = await Promise.all([
14748
+ git.raw([
14749
+ "branch",
14750
+ "--format=%(refname:short)",
14751
+ "--contains",
14752
+ "HEAD"
14753
+ ]),
14754
+ git.raw([
14755
+ "branch",
14756
+ "-r",
14757
+ "--format=%(refname:short)",
14758
+ "--contains",
14759
+ "HEAD"
14760
+ ])
14761
+ ]);
14762
+ const branchNames = [
14763
+ ...parseBranchList(localBranches),
14764
+ ...parseBranchList(remoteBranches).filter((branchName)=>!branchName.endsWith("/HEAD")).map((branchName)=>branchName.replace(/^[^/]+\//, ""))
14765
+ ];
14766
+ const branchSet = new Set(branchNames);
14767
+ if (branchSet.has("master")) return "master";
14768
+ if (branchSet.has("main")) return "main";
14769
+ return "master";
14770
+ }
14771
+ function parseBranchList(output) {
14772
+ return output.split(/\r?\n/).map((line)=>line.trim()).filter(Boolean);
14773
+ }
14774
+ function anvil_api_getDefaultAnvilUrl() {
14775
+ return resolveAnvilUrl();
14776
+ }
14777
+ async function anvil_api_validateAppId(appId, anvilUrl = anvil_api_getDefaultAnvilUrl(), username) {
14778
+ const authToken = await auth_getValidAuthToken(anvilUrl, username);
14779
+ try {
14780
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/validate-app-id`, {
14781
+ method: "POST",
14782
+ headers: {
14783
+ Authorization: `Bearer ${authToken}`,
14784
+ "Content-Type": "application/json"
14785
+ },
14786
+ body: JSON.stringify({
14787
+ app_id: appId
14788
+ })
14789
+ });
14790
+ const data = await resp.json();
14791
+ if (resp.ok) return {
14792
+ valid: data.valid ?? false,
14793
+ app_name: data.app_name
14794
+ };
14795
+ if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14796
+ if (403 === resp.status) throw createAppError.accessDenied(appId);
14797
+ if (404 === resp.status) throw createAppError.notFound(appId);
14798
+ logger_logger.debug(`validate-app-id failed: ${resp.status} ${resp.statusText}`);
14799
+ return {
14800
+ valid: false,
14801
+ error: data.error || `Server error: ${resp.status}`
14802
+ };
14803
+ } catch (error) {
14804
+ if (error.type) throw error;
14805
+ throw createNetworkError.network(error.message);
14806
+ }
14807
+ }
14808
+ async function lookupByCommit(commitId, branchName, anvilUrl = anvil_api_getDefaultAnvilUrl(), username) {
14809
+ const authToken = await auth_getValidAuthToken(anvilUrl, username);
14810
+ try {
14811
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/lookup-by-commit`, {
14812
+ method: "POST",
14813
+ headers: {
14814
+ Authorization: `Bearer ${authToken}`,
14815
+ "Content-Type": "application/json"
14816
+ },
14817
+ body: JSON.stringify({
14818
+ commit_id: commitId,
14819
+ branch_name: branchName
14820
+ })
14821
+ });
14822
+ if (!resp.ok) {
14823
+ if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14824
+ throw createNetworkError.server(resp.status, resp.statusText);
14825
+ }
14826
+ const data = await resp.json();
14827
+ return data;
14828
+ } catch (error) {
14829
+ if (error.type) throw error;
14830
+ throw createNetworkError.network(error.message);
14846
14831
  }
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;
14832
+ }
14833
+ async function listAppsForCheckout(options = {}) {
14834
+ const anvilUrl = options.anvilUrl ?? anvil_api_getDefaultAnvilUrl();
14835
+ const authToken = await auth_getValidAuthToken(anvilUrl, options.username);
14836
+ const params = new URLSearchParams();
14837
+ if ("number" == typeof options.limit) params.set("limit", String(options.limit));
14838
+ if (options.cursor) params.set("cursor", options.cursor);
14839
+ if (options.q && options.q.trim()) params.set("q", options.q.trim());
14840
+ const query = params.toString();
14841
+ const url = `${anvilUrl}/ide/api/_/apps${query ? `?${query}` : ""}`;
14842
+ try {
14843
+ const resp = await fetch(url, {
14844
+ method: "GET",
14845
+ headers: {
14846
+ Authorization: `Bearer ${authToken}`
14847
+ },
14848
+ signal: options.signal
14849
+ });
14850
+ if (!resp.ok) {
14851
+ if (401 === resp.status) throw createAuthError.invalid("Authentication failed");
14852
+ throw createNetworkError.server(resp.status, resp.statusText);
14853
+ }
14854
+ const data = await resp.json();
14855
+ return {
14856
+ apps: Array.isArray(data.apps) ? data.apps : [],
14857
+ next_cursor: "string" == typeof data.next_cursor ? data.next_cursor : null
14858
+ };
14859
+ } catch (error) {
14860
+ if (error.type) throw error;
14861
+ throw createNetworkError.network(error.message);
14857
14862
  }
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;
14863
+ }
14864
+ async function saveUpdates(appId, branchName, payload, anvilUrl = anvil_api_getDefaultAnvilUrl(), authToken) {
14865
+ try {
14866
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/${appId}/update?make_fresh_version=false&branch=${branchName}`, {
14867
+ method: "PUT",
14868
+ headers: {
14869
+ Authorization: `Bearer ${authToken}`,
14870
+ "Content-Type": "application/json"
14871
+ },
14872
+ body: JSON.stringify(payload)
14873
+ });
14874
+ const data = await resp.json();
14875
+ if (401 === resp.status) throw createAuthError.invalid(`Authentication failed (401): ${data.error || "unauthorized"}`);
14876
+ if (403 === resp.status) throw createAuthError.invalid(`Authentication failed (403): ${data.error || "unauthorized"}\n This may indicate:\n - The OAuth token doesn't have permission to write to app ${appId}\n - The app belongs to a different user or organization\n - The OAuth token is missing required scopes (apps:write)`);
14877
+ return {
14878
+ version: data.version,
14879
+ overtaken: data.overtaken,
14880
+ latest_version: data.latest_version,
14881
+ error: 200 !== resp.status ? data.error || `Status ${resp.status}` : void 0
14882
+ };
14883
+ } catch (error) {
14884
+ if (error.type) throw error;
14885
+ throw createNetworkError.network(error.message);
14866
14886
  }
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);
14887
+ }
14888
+ function anvil_api_getGitFetchUrl(appId, authToken, gitUrl = anvil_api_getDefaultAnvilUrl()) {
14889
+ const url = new URL(gitUrl);
14890
+ const encodedToken = encodeURIComponent(authToken);
14891
+ return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git?no_freeze=true&q=`;
14892
+ }
14893
+ function anvil_api_getGitPushUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
14894
+ const url = new URL(anvilUrl);
14895
+ const encodedToken = encodeURIComponent(authToken);
14896
+ return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git`;
14897
+ }
14898
+ function getWebSocketUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
14899
+ return anvilUrl.replace(/^http/, "ws") + `/ide/api/_/apps/${appId}/ws?access_token=${authToken}`;
14900
+ }
14901
+ async function getLatestVersion() {
14902
+ try {
14903
+ const response = await fetch("https://registry.npmjs.org/@anvil-works/anvil-cli/latest");
14904
+ if (!response.ok) return null;
14905
+ const data = await response.json();
14906
+ return data.version;
14907
+ } catch (e) {
14908
+ return null;
14872
14909
  }
14873
- async hasUncommittedChanges() {
14874
- const status = await this.getStatus();
14875
- return !status.isClean;
14910
+ }
14911
+ function anvil_api_filterCandidates(candidates, explicitUrl, explicitUsername) {
14912
+ let filtered = candidates;
14913
+ if (explicitUrl) {
14914
+ const normalizedExplicit = config_normalizeAnvilUrl(explicitUrl);
14915
+ filtered = filtered.filter((c)=>c.detectedUrl && config_normalizeAnvilUrl(c.detectedUrl) === normalizedExplicit);
14876
14916
  }
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"
14917
+ if (explicitUsername) filtered = filtered.filter((c)=>!c.detectedUsername || c.detectedUsername === explicitUsername);
14918
+ return filtered;
14919
+ }
14920
+ function anvil_api_formatCandidateLabel(candidate) {
14921
+ const parts = [
14922
+ candidate.appId
14923
+ ];
14924
+ if (candidate.detectedUrl) if (candidate.detectedUsername) parts.push(`(${candidate.detectedUsername} on ${candidate.detectedUrl})`);
14925
+ else parts.push(`(${candidate.detectedUrl})`);
14926
+ parts.push(`- ${candidate.description}`);
14927
+ return parts.join(" ");
14928
+ }
14929
+ function anvil_api_lookupRemoteInfoForAppId(appId, detectedRemotes) {
14930
+ const matches = detectedRemotes.filter((c)=>c.appId === appId);
14931
+ if (0 === matches.length) return {};
14932
+ const withUsername = matches.find((c)=>c.detectedUsername);
14933
+ if (withUsername) return {
14934
+ detectedUrl: withUsername.detectedUrl,
14935
+ detectedUsername: withUsername.detectedUsername
14936
+ };
14937
+ return {
14938
+ detectedUrl: matches[0].detectedUrl,
14939
+ detectedUsername: matches[0].detectedUsername
14940
+ };
14941
+ }
14942
+ async function anvil_api_detectAppIdsFromAllRemotes(repoPath) {
14943
+ const git = esm_default(repoPath);
14944
+ const out = [];
14945
+ try {
14946
+ const remotes = await git.getRemotes(true);
14947
+ for (const remote of remotes){
14948
+ const httpMatch = remote.refs.fetch?.match(/(?:http|https):\/\/(?:[^@]+@)?([^:\/]+)(?::\d+)?\/git\/([A-Z0-9]+)\.git/);
14949
+ if (httpMatch) {
14950
+ const [, host, detectedAppId] = httpMatch;
14951
+ out.push({
14952
+ appId: detectedAppId,
14953
+ source: "remote",
14954
+ description: `Git remote '${remote.name}'`,
14955
+ detectedUrl: config_normalizeAnvilUrl(host)
14956
+ });
14957
+ continue;
14958
+ }
14959
+ const sshMatch = remote.refs.fetch?.match(/ssh:\/\/([^@]+)@([^:]+):(\d+)\/(?:git\/)?([A-Z0-9]+)\.git/);
14960
+ if (sshMatch) {
14961
+ const [, usernamePart, host, , detectedAppId] = sshMatch;
14962
+ const detectedUsername = usernamePart.includes("%") ? decodeURIComponent(usernamePart) : usernamePart;
14963
+ const finalUsername = "git" !== detectedUsername ? detectedUsername : void 0;
14964
+ out.push({
14965
+ appId: detectedAppId,
14966
+ source: "remote",
14967
+ description: `Git remote '${remote.name}' (SSH)`,
14968
+ detectedUrl: config_normalizeAnvilUrl(host),
14969
+ detectedUsername: finalUsername
14895
14970
  });
14896
- throw errors_createGitError.fetchFailed(err.message);
14897
14971
  }
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
14972
  }
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);
14973
+ } catch (_e) {}
14974
+ return out;
14975
+ }
14976
+ async function anvil_api_detectAppIdsByCommitLookup(repoPath, options) {
14977
+ const anvilUrl = options.anvilUrl || resolveAnvilUrl();
14978
+ const username = options.username;
14979
+ const git = esm_default(repoPath);
14980
+ const out = [];
14981
+ try {
14982
+ const authToken = await auth_getValidAuthToken(anvilUrl, username);
14983
+ const commitId = (await git.revparse([
14984
+ "HEAD"
14985
+ ]))?.trim();
14986
+ if (!commitId) return out;
14987
+ const branchName = await git_getCurrentOrFallbackBranchName(git);
14988
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/lookup-by-commit`, {
14989
+ method: "POST",
14990
+ headers: {
14991
+ Authorization: `Bearer ${authToken}`,
14992
+ "Content-Type": "application/json"
14993
+ },
14994
+ body: JSON.stringify({
14995
+ commit_id: commitId,
14996
+ branch_name: branchName
14997
+ })
14998
+ });
14999
+ logger_logger.debug(`Reverse lookup by commit ${commitId} on branch ${branchName} returned ${resp.status}`);
15000
+ if (!resp.ok) {
15001
+ const errorBody = await resp.text();
15002
+ logger_logger.debug(`lookup-by-commit error: ${errorBody}`);
14945
15003
  }
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);
15004
+ if (resp.ok) {
15005
+ const data = await resp.json();
15006
+ logger_logger.debug(`data ${JSON.stringify(data)}`);
15007
+ const apps = data?.apps ?? [];
15008
+ for (const app of apps)if (app?.app_id) out.push({
15009
+ appId: app.app_id,
15010
+ source: "remote",
15011
+ description: `Reverse lookup by commit${app.branch ? ` on branch '${app.branch}'` : ""}`
15012
+ });
14955
15013
  }
15014
+ } catch (_e) {}
15015
+ return out;
15016
+ }
15017
+ const external_fs_promises_namespaceObject = require("fs/promises");
15018
+ var external_fs_promises_default = /*#__PURE__*/ __webpack_require__.n(external_fs_promises_namespaceObject);
15019
+ function normalizeLineEndings(content) {
15020
+ return content.replace(/\r\n/g, "\n");
15021
+ }
15022
+ function pythonifyName(name) {
15023
+ return name.replace(/[^A-z0-9]/g, "_").replace(/^[0-9]/, "_$&");
15024
+ }
15025
+ function extractPythonName(relativePath, skipParts = 1) {
15026
+ const parts = relativePath.slice(0, -3).split("/");
15027
+ const isPackage = relativePath.endsWith("__init__.py");
15028
+ const relevantParts = isPackage ? parts.slice(skipParts, -1) : parts.slice(skipParts);
15029
+ return relevantParts.map(pythonifyName).join(".");
15030
+ }
15031
+ function deepEqual(a, b) {
15032
+ if (a === b) return true;
15033
+ if (null == a || null == b) return false;
15034
+ if (typeof a !== typeof b) return false;
15035
+ if ("object" != typeof a) return a === b;
15036
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
15037
+ if (Array.isArray(a)) {
15038
+ if (a.length !== b.length) return false;
15039
+ return a.every((item, index)=>deepEqual(item, b[index]));
14956
15040
  }
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
- }
15041
+ const keysA = Object.keys(a);
15042
+ const keysB = Object.keys(b);
15043
+ if (keysA.length !== keysB.length) return false;
15044
+ return keysA.every((key)=>deepEqual(a[key], b[key]));
15045
+ }
15046
+ function parseHtmlWithFrontmatter(htmlContent) {
15047
+ const frontmatterPattern = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
15048
+ const match = htmlContent.match(frontmatterPattern);
15049
+ if (match) {
15050
+ const yamlStr = match[1];
15051
+ const html = match[2];
15052
+ const frontmatter = yamlStr ? external_js_yaml_default().load(yamlStr) ?? {} : {};
15053
+ return {
15054
+ frontmatter,
15055
+ html
15056
+ };
14963
15057
  }
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
- }
15058
+ return {
15059
+ frontmatter: {},
15060
+ html: htmlContent
15061
+ };
15062
+ }
15063
+ function formatHttpError(prefix, status, body) {
15064
+ if (!body) return `${prefix}: ${status}`;
15065
+ try {
15066
+ const parsed = JSON.parse(body);
15067
+ if ("string" == typeof parsed.error && parsed.error.trim()) return `${prefix}: ${parsed.error} (${status})`;
15068
+ } catch {}
15069
+ return `${prefix}: ${status} ${body}`;
15070
+ }
15071
+ const ANVIL_GITIGNORE_ENTRY = "/.anvil/";
15072
+ async function ensureProjectGitignoreIgnoresAnvilDir(projectRoot) {
15073
+ const rootGitignorePath = external_path_default().join(projectRoot, ".gitignore");
15074
+ try {
15075
+ const existing = await external_fs_.promises.readFile(rootGitignorePath, "utf8");
15076
+ if (existing.includes(ANVIL_GITIGNORE_ENTRY)) return false;
15077
+ const prefix = existing.endsWith("\n") || 0 === existing.length ? "" : "\n";
15078
+ await external_fs_.promises.writeFile(rootGitignorePath, `${existing}${prefix}${ANVIL_GITIGNORE_ENTRY}\n`, "utf8");
15079
+ return true;
15080
+ } catch (error) {
15081
+ if ("ENOENT" !== error.code) throw error;
15082
+ await external_fs_.promises.writeFile(rootGitignorePath, `${ANVIL_GITIGNORE_ENTRY}\n`, "utf8");
15083
+ return true;
14976
15084
  }
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
- }
15085
+ }
15086
+ const ANVIL_DEPS_DIR = ".anvil/deps";
15087
+ const ANVIL_DEPS_MANIFEST = "manifest.json";
15088
+ const DEPS_MANIFEST_VERSION = 2;
15089
+ async function fetchAllDependencies(appId, anvilUrl, options = {}) {
15090
+ const { commit, includeDocs = true, knownDependencies = [] } = options;
15091
+ anvilUrl ??= await auth_resolveAuthAnvilUrl();
15092
+ const token = await auth_getValidAuthToken(anvilUrl);
15093
+ const body = {
15094
+ include_docs: includeDocs,
15095
+ known_dependencies: knownDependencies
15096
+ };
15097
+ if (commit) body.commit = commit;
15098
+ const resp = await fetch(`${anvilUrl}/ide/api/_/apps/${appId}/resolved-dependencies`, {
15099
+ method: "POST",
15100
+ headers: {
15101
+ Authorization: `Bearer ${token}`,
15102
+ "Content-Type": "application/json"
15103
+ },
15104
+ body: JSON.stringify(body)
15105
+ });
15106
+ if (!resp.ok) {
15107
+ const errorBody = await resp.text();
15108
+ throw new Error(formatHttpError("Failed to fetch dependencies", resp.status, errorBody));
14993
15109
  }
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
- }
15110
+ return await resp.json();
15111
+ }
15112
+ async function refreshDependencyCache(projectRoot, appId, anvilUrl, options = {}) {
15113
+ auth_setRepoContext(projectRoot);
15114
+ anvilUrl ??= await auth_resolveAuthAnvilUrl();
15115
+ const includeDocs = options.includeDocs ?? true;
15116
+ const knownDependencies = await getKnownDependenciesForCache(projectRoot, includeDocs);
15117
+ const response = await fetchAllDependencies(appId, anvilUrl, {
15118
+ commit: options.commit,
15119
+ includeDocs,
15120
+ knownDependencies
15121
+ });
15122
+ if (!response.resolved_dependencies.length) {
15123
+ await clearDepsCache(projectRoot);
15124
+ return {
15125
+ response,
15126
+ addedGitignoreEntry: false
15127
+ };
15003
15128
  }
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
- }
15129
+ const addedGitignoreEntry = await ensureProjectGitignoreIgnoresAnvilDir(projectRoot);
15130
+ await writeAllDepsToCache(projectRoot, response, {
15131
+ includeDocs
15132
+ });
15133
+ return {
15134
+ response,
15135
+ addedGitignoreEntry
15136
+ };
15137
+ }
15138
+ function depsDir(projectRoot) {
15139
+ return external_path_default().join(projectRoot, ANVIL_DEPS_DIR);
15140
+ }
15141
+ function depsManifestPath(projectRoot) {
15142
+ return external_path_default().join(depsDir(projectRoot), ANVIL_DEPS_MANIFEST);
15143
+ }
15144
+ function isResolvedDependencyManifestEntry(value) {
15145
+ if (!value || "object" != typeof value) return false;
15146
+ const entry = value;
15147
+ 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);
15148
+ }
15149
+ function isCachedDependencyManifestEntry(value) {
15150
+ if (!isResolvedDependencyManifestEntry(value)) return false;
15151
+ const entry = value;
15152
+ return "string" == typeof entry.package_name && entry.package_name.length > 0;
15153
+ }
15154
+ function isDepsCacheManifest(value) {
15155
+ if (!value || "object" != typeof value) return false;
15156
+ const manifest = value;
15157
+ 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);
15158
+ }
15159
+ async function readDepsCacheManifest(projectRoot) {
15160
+ try {
15161
+ const raw = await external_fs_promises_default().readFile(depsManifestPath(projectRoot), "utf-8");
15162
+ const parsed = JSON.parse(raw);
15163
+ return isDepsCacheManifest(parsed) ? parsed : void 0;
15164
+ } catch {
15165
+ return;
15020
15166
  }
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
- }
15167
+ }
15168
+ async function getKnownDependenciesForCache(projectRoot, includeDocs) {
15169
+ const manifest = await readDepsCacheManifest(projectRoot);
15170
+ if (!manifest || manifest.includeDocs !== includeDocs) return [];
15171
+ return manifest.resolved_dependencies.map(({ app_id, commit_id })=>({
15172
+ app_id,
15173
+ commit_id
15174
+ }));
15175
+ }
15176
+ function getDependencyWatchSubscriptionsFromManifest(manifest) {
15177
+ if (!manifest) return [];
15178
+ const cachedByAppId = new Map(manifest.cached_dependencies.map((dep)=>[
15179
+ dep.app_id,
15180
+ dep
15181
+ ]));
15182
+ return manifest.resolved_dependencies.flatMap((dep)=>{
15183
+ if (!dep.branch) return [];
15184
+ return [
15185
+ {
15186
+ appId: dep.app_id,
15187
+ branch: dep.branch,
15188
+ packageName: cachedByAppId.get(dep.app_id)?.package_name
15189
+ }
15190
+ ];
15191
+ });
15192
+ }
15193
+ function normalizeAnvilYamlDependenciesFromContent(content) {
15194
+ const parsed = external_js_yaml_default().load(content);
15195
+ if (!parsed || "object" != typeof parsed || !Array.isArray(parsed.dependencies)) return [];
15196
+ return normalizeAnvilYamlDependenciesValue(parsed.dependencies);
15197
+ }
15198
+ function normalizeAnvilYamlDependenciesValue(value) {
15199
+ if (!Array.isArray(value)) return [];
15200
+ return value.map(normalizeForComparison);
15201
+ }
15202
+ async function readAnvilYamlDependencies(projectRoot) {
15203
+ try {
15204
+ const content = await external_fs_promises_default().readFile(external_path_default().join(projectRoot, "anvil.yaml"), "utf-8");
15205
+ return normalizeAnvilYamlDependenciesFromContent(content);
15206
+ } catch {
15207
+ return [];
15030
15208
  }
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);
15209
+ }
15210
+ function anvilYamlDependenciesEqual(left, right) {
15211
+ return deepEqual(left, right);
15212
+ }
15213
+ function normalizeForComparison(value) {
15214
+ if (Array.isArray(value)) return value.map(normalizeForComparison);
15215
+ if (value && "object" == typeof value) return Object.fromEntries(Object.entries(value).sort(([left], [right])=>left.localeCompare(right)).map(([key, entryValue])=>[
15216
+ key,
15217
+ normalizeForComparison(entryValue)
15218
+ ]));
15219
+ return value;
15220
+ }
15221
+ function cachedManifestEntry(dep, packageName) {
15222
+ return {
15223
+ app_id: dep.app_id,
15224
+ commit_id: dep.commit_id,
15225
+ ...dep.branch ? {
15226
+ branch: dep.branch
15227
+ } : {},
15228
+ package_name: packageName
15229
+ };
15230
+ }
15231
+ function safeDependencyPath(filePath, kind) {
15232
+ const normalizedPath = external_path_default().normalize(filePath);
15233
+ if (external_path_default().isAbsolute(normalizedPath) || normalizedPath.startsWith("..")) throw new Error(`Invalid dependency ${kind} path: ${filePath}`);
15234
+ return normalizedPath;
15235
+ }
15236
+ function dependencyPythonPath(name, kind) {
15237
+ const parts = name.split(".");
15238
+ if (0 === parts.length || parts.some((part)=>!part || "." === part || ".." === part || part.includes("/") || part.includes("\\"))) throw new Error(`Invalid dependency ${kind} name: ${name}`);
15239
+ return external_path_default().join(...parts);
15240
+ }
15241
+ async function writeDepToCache(projectRoot, dep) {
15242
+ const depDir = external_path_default().join(projectRoot, ANVIL_DEPS_DIR, dep.package_name);
15243
+ await external_fs_promises_default().rm(depDir, {
15244
+ recursive: true,
15245
+ force: true
15246
+ });
15247
+ await external_fs_promises_default().mkdir(external_path_default().join(depDir, "client_code"), {
15248
+ recursive: true
15249
+ });
15250
+ await external_fs_promises_default().mkdir(external_path_default().join(depDir, "server_code"), {
15251
+ recursive: true
15252
+ });
15253
+ await external_fs_promises_default().writeFile(external_path_default().join(depDir, "anvil.yaml"), external_js_yaml_default().dump({
15254
+ package_name: dep.package_name
15255
+ }));
15256
+ for (const mod of dep.modules ?? []){
15257
+ const modulePath = external_path_default().join(depDir, "client_code", dependencyPythonPath(mod.name, "module"));
15258
+ if (mod.is_package) {
15259
+ await external_fs_promises_default().mkdir(modulePath, {
15260
+ recursive: true
15261
+ });
15262
+ await external_fs_promises_default().writeFile(external_path_default().join(modulePath, "__init__.py"), mod.code);
15263
+ } else {
15264
+ const moduleFile = `${modulePath}.py`;
15265
+ await external_fs_promises_default().mkdir(external_path_default().dirname(moduleFile), {
15266
+ recursive: true
15267
+ });
15268
+ await external_fs_promises_default().writeFile(moduleFile, mod.code);
15039
15269
  }
15040
15270
  }
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;
15271
+ for (const form of dep.forms ?? []){
15272
+ const { code, class_name, is_package, ...formTemplate } = form;
15273
+ const formPath = external_path_default().join(depDir, "client_code", dependencyPythonPath(class_name, "form"));
15274
+ if (is_package) {
15275
+ await external_fs_promises_default().mkdir(formPath, {
15276
+ recursive: true
15277
+ });
15278
+ await external_fs_promises_default().writeFile(external_path_default().join(formPath, "__init__.py"), code);
15279
+ await external_fs_promises_default().writeFile(external_path_default().join(formPath, "form_template.yaml"), external_js_yaml_default().dump(formTemplate));
15280
+ } else {
15281
+ const formCodeFile = `${formPath}.py`;
15282
+ await external_fs_promises_default().mkdir(external_path_default().dirname(formCodeFile), {
15283
+ recursive: true
15284
+ });
15285
+ await external_fs_promises_default().writeFile(formCodeFile, code);
15286
+ await external_fs_promises_default().writeFile(`${formPath}.yaml`, external_js_yaml_default().dump(formTemplate));
15054
15287
  }
15055
15288
  }
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);
15289
+ for (const mod of dep.server_modules ?? []){
15290
+ const modulePath = external_path_default().join(depDir, "server_code", dependencyPythonPath(mod.name, "server module"));
15291
+ if (mod.is_package) {
15292
+ await external_fs_promises_default().mkdir(modulePath, {
15293
+ recursive: true
15294
+ });
15295
+ await external_fs_promises_default().writeFile(external_path_default().join(modulePath, "__init__.py"), mod.code);
15296
+ } else {
15297
+ const moduleFile = `${modulePath}.py`;
15298
+ await external_fs_promises_default().mkdir(external_path_default().dirname(moduleFile), {
15299
+ recursive: true
15300
+ });
15301
+ await external_fs_promises_default().writeFile(moduleFile, mod.code);
15081
15302
  }
15082
15303
  }
15083
- async isGitInitialized() {
15084
- const gitPath = external_path_default().join(this.repoPath, ".git");
15085
- return external_fs_.existsSync(gitPath);
15304
+ for (const asset of dep.assets ?? []){
15305
+ const targetPath = external_path_default().join(depDir, "theme", "assets", safeDependencyPath(asset.name, "asset"));
15306
+ await external_fs_promises_default().mkdir(external_path_default().dirname(targetPath), {
15307
+ recursive: true
15308
+ });
15309
+ await external_fs_promises_default().writeFile(targetPath, Buffer.from(asset.content, "base64"));
15086
15310
  }
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) {}
15311
+ for (const doc of dep.docs ?? []){
15312
+ const targetPath = external_path_default().join(depDir, safeDependencyPath(doc.path, "doc"));
15313
+ await external_fs_promises_default().mkdir(external_path_default().dirname(targetPath), {
15314
+ recursive: true
15315
+ });
15316
+ await external_fs_promises_default().writeFile(targetPath, doc.content);
15105
15317
  }
15106
15318
  }
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
- }
15319
+ async function writeAllDepsToCache(projectRoot, response, options = {}) {
15320
+ const includeDocs = options.includeDocs ?? true;
15321
+ const manifest = await readDepsCacheManifest(projectRoot);
15322
+ const canReuseExistingCache = manifest?.includeDocs === includeDocs;
15323
+ if (!canReuseExistingCache) await clearDepsCache(projectRoot);
15324
+ const previousCached = canReuseExistingCache ? manifest.cached_dependencies : [];
15325
+ const resolvedAppIds = new Set(response.resolved_dependencies.map((dep)=>dep.app_id));
15326
+ 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), {
15327
+ recursive: true,
15328
+ force: true
15329
+ });
15330
+ for (const dep of response.dependencies)await writeDepToCache(projectRoot, dep);
15331
+ const returnedByAppId = new Map(response.dependencies.map((dep)=>[
15332
+ dep.app_id,
15333
+ dep
15334
+ ]));
15335
+ const previousByAppId = new Map(previousCached.map((dep)=>[
15336
+ dep.app_id,
15337
+ dep
15338
+ ]));
15339
+ const cachedDependencies = response.resolved_dependencies.map((dep)=>{
15340
+ const returned = returnedByAppId.get(dep.app_id);
15341
+ if (returned) return cachedManifestEntry(returned, returned.package_name);
15342
+ const previous = previousByAppId.get(dep.app_id);
15343
+ if (!previous) return;
15344
+ return cachedManifestEntry(dep, previous.package_name);
15345
+ }).filter((dep)=>void 0 !== dep);
15346
+ const newManifest = {
15347
+ version: DEPS_MANIFEST_VERSION,
15348
+ includeDocs,
15349
+ source_dependencies: await readAnvilYamlDependencies(projectRoot),
15350
+ resolved_dependencies: response.resolved_dependencies,
15351
+ cached_dependencies: cachedDependencies
15352
+ };
15353
+ await external_fs_promises_default().mkdir(depsDir(projectRoot), {
15354
+ recursive: true
15355
+ });
15356
+ await external_fs_promises_default().writeFile(depsManifestPath(projectRoot), `${JSON.stringify(newManifest, null, 2)}\n`);
15357
+ }
15358
+ async function clearDepsCache(projectRoot) {
15359
+ await external_fs_promises_default().rm(depsDir(projectRoot), {
15360
+ recursive: true,
15361
+ force: true
15362
+ });
15148
15363
  }
15149
15364
  const external_chokidar_namespaceObject = require("chokidar");
15150
15365
  var external_chokidar_default = /*#__PURE__*/ __webpack_require__.n(external_chokidar_namespaceObject);
@@ -16912,7 +17127,7 @@ print(json.dumps({"issues": issues}))
16912
17127
  }
16913
17128
  function validateScheduledTasks(value, path) {
16914
17129
  const issues = [];
16915
- if (void 0 === value) return issues;
17130
+ if (null == value) return issues;
16916
17131
  if (!Array.isArray(value)) {
16917
17132
  validators_pushIssue(issues, path, "must be an array");
16918
17133
  return issues;
@@ -18411,6 +18626,15 @@ print(json.dumps({"issues": issues}))
18411
18626
  this.hasPendingChanges = true;
18412
18627
  await this.processSaveBatchWithEvents();
18413
18628
  }
18629
+ async flush() {
18630
+ const requireReady = ()=>{
18631
+ if (this.config.isPaused() || this.config.isCleanedUp() || !this.config.isSettlePeriodOver()) throw new Error("Cannot flush saves while watching is paused, closed, or changing branch");
18632
+ };
18633
+ requireReady();
18634
+ await this.forceSave();
18635
+ requireReady();
18636
+ if (this.hasPendingChanges) throw new Error("Saves remain pending");
18637
+ }
18414
18638
  cleanup() {
18415
18639
  if (this.hasPendingChanges) {
18416
18640
  logger_logger.progressEnd("sync");
@@ -18507,10 +18731,7 @@ print(json.dumps({"issues": issues}))
18507
18731
  status = await this.config.gitService.getStatus();
18508
18732
  } catch (e) {
18509
18733
  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
- };
18734
+ throw e;
18514
18735
  }
18515
18736
  let allChanges;
18516
18737
  if (this.config.stagedOnly) {
@@ -19667,6 +19888,12 @@ print(json.dumps({"issues": issues}))
19667
19888
  if (!this.saveProcessor) return;
19668
19889
  return this.saveProcessor.forceSave();
19669
19890
  }
19891
+ async flushSaves() {
19892
+ if (!this.saveProcessor || this.isSwitchingForEnvironment) throw new Error("App synchronization is not ready");
19893
+ const branch = this.currentBranch;
19894
+ await this.saveProcessor.flush();
19895
+ if (this.isSwitchingForEnvironment || this.currentBranch !== branch || await this.gitService.getCurrentBranch() !== branch) throw new Error("App branch changed while flushing saves");
19896
+ }
19670
19897
  async getSuccessMessage() {
19671
19898
  try {
19672
19899
  const info = await this.gitService.getCommitInfo();
@@ -20537,7 +20764,29 @@ print(json.dumps({"issues": issues}))
20537
20764
  const response = await buildGitCredentialResponse(operation, request, options);
20538
20765
  writeGitCredentialResponse(response);
20539
20766
  }
20540
- resolveAnvilUrl();
20767
+ const MODEL_TOKEN_LOCK_TIMEOUT_MS = 12000;
20768
+ const modelToken_defaultDeps = {
20769
+ getValidAuthToken: auth_getValidAuthToken,
20770
+ readAuthFromRepo: readAuthFromRepo,
20771
+ cwd: ()=>process.cwd()
20772
+ };
20773
+ async function getModelToken(options = {}) {
20774
+ const deps = options.deps ?? modelToken_defaultDeps;
20775
+ const repoPath = options.repoPath ?? deps.cwd();
20776
+ auth_setRepoContext(repoPath);
20777
+ if (!process.env["ANVIL_AUTH_FILE_ENCRYPTION_KEY"]) throw new Error("ANVIL_AUTH_FILE_ENCRYPTION_KEY is not set; cannot read repo auth tokens.");
20778
+ const auth = await deps.readAuthFromRepo(repoPath);
20779
+ if (!auth) throw new Error(`No Anvil auth tokens found for the repository at ${repoPath}.`);
20780
+ return deps.getValidAuthToken(auth.anvilUrl, auth.username, {
20781
+ lock: {
20782
+ timeoutMs: options.lockTimeoutMs ?? MODEL_TOKEN_LOCK_TIMEOUT_MS
20783
+ }
20784
+ });
20785
+ }
20786
+ async function executeModelTokenOperation(options = {}) {
20787
+ const token = await getModelToken(options);
20788
+ process.stdout.write(`${token}\n`);
20789
+ }
20541
20790
  async function watch_decideFallbackUrl(explicitUrl, availableUrls) {
20542
20791
  if (explicitUrl) return {
20543
20792
  source: "explicit",
@@ -20748,7 +20997,6 @@ print(json.dumps({"issues": issues}))
20748
20997
  remoteName
20749
20998
  };
20750
20999
  }
20751
- const api_DEFAULT_ANVIL_URL = resolveAnvilUrl();
20752
21000
  async function api_checkUncommittedChanges(repoPath) {
20753
21001
  try {
20754
21002
  const git = esm_default(repoPath);
@@ -20797,9 +21045,10 @@ print(json.dumps({"issues": issues}))
20797
21045
  }
20798
21046
  }
20799
21047
  async function src_api_watch(repoPath, appId, options = {}) {
20800
- const { anvilUrl = api_DEFAULT_ANVIL_URL, gitUrl = anvilUrl, stagedOnly = false, username } = options;
20801
21048
  repoPath = external_path_default().resolve(repoPath);
20802
21049
  auth_setRepoContext(repoPath);
21050
+ const anvilUrl = options.anvilUrl ?? await auth_resolveAuthAnvilUrl();
21051
+ const { gitUrl = anvilUrl, stagedOnly = false, username } = options;
20803
21052
  const storedEnvPid = options.environment?.envPid ? void 0 : await environment_readEnvironmentPid(repoPath);
20804
21053
  const environment = options.environment ?? (storedEnvPid ? {
20805
21054
  envPid: storedEnvPid
@@ -20914,6 +21163,7 @@ print(json.dumps({"issues": issues}))
20914
21163
  }
20915
21164
  })();
20916
21165
  exports.ANVIL_SYNC_CLIENT_ID = __webpack_exports__.ANVIL_SYNC_CLIENT_ID;
21166
+ exports.AUTH_FILE_LOCK_DEFAULTS = __webpack_exports__.AUTH_FILE_LOCK_DEFAULTS;
20917
21167
  exports.AnvilYamlValidationResult = __webpack_exports__.AnvilYamlValidationResult;
20918
21168
  exports.AppIdCandidate = __webpack_exports__.AppIdCandidate;
20919
21169
  exports.AppIdWithContext = __webpack_exports__.AppIdWithContext;
@@ -20925,11 +21175,15 @@ exports.CheckoutOptions = __webpack_exports__.CheckoutOptions;
20925
21175
  exports.CheckoutResult = __webpack_exports__.CheckoutResult;
20926
21176
  exports.DeviceAuthorizationResponse = __webpack_exports__.DeviceAuthorizationResponse;
20927
21177
  exports.FormTemplateValidationResult = __webpack_exports__.FormTemplateValidationResult;
21178
+ exports.GetValidAuthTokenOptions = __webpack_exports__.GetValidAuthTokenOptions;
20928
21179
  exports.LoginResult = __webpack_exports__.LoginResult;
20929
21180
  exports.LogoutResult = __webpack_exports__.LogoutResult;
21181
+ exports.MODEL_TOKEN_HELPER_FILENAME = __webpack_exports__.MODEL_TOKEN_HELPER_FILENAME;
21182
+ exports.MODEL_TOKEN_LOCK_TIMEOUT_MS = __webpack_exports__.MODEL_TOKEN_LOCK_TIMEOUT_MS;
20930
21183
  exports.OAuthTokenResponse = __webpack_exports__.OAuthTokenResponse;
20931
21184
  exports.ParsedCheckoutInput = __webpack_exports__.ParsedCheckoutInput;
20932
21185
  exports.PreferredEditor = __webpack_exports__.PreferredEditor;
21186
+ exports.REFRESH_REQUEST_TIMEOUT_MS = __webpack_exports__.REFRESH_REQUEST_TIMEOUT_MS;
20933
21187
  exports.SettableConfigKey = __webpack_exports__.SettableConfigKey;
20934
21188
  exports.TokenData = __webpack_exports__.TokenData;
20935
21189
  exports.ValidateAppIdResponse = __webpack_exports__.ValidateAppIdResponse;
@@ -20950,6 +21204,7 @@ exports.detectAppIdsByCommitLookup = __webpack_exports__.detectAppIdsByCommitLoo
20950
21204
  exports.detectAppIdsFromAllRemotes = __webpack_exports__.detectAppIdsFromAllRemotes;
20951
21205
  exports.executeCheckout = __webpack_exports__.executeCheckout;
20952
21206
  exports.executeGitCredentialOperation = __webpack_exports__.executeGitCredentialOperation;
21207
+ exports.executeModelTokenOperation = __webpack_exports__.executeModelTokenOperation;
20953
21208
  exports.filterCandidates = __webpack_exports__.filterCandidates;
20954
21209
  exports.formatCandidateLabel = __webpack_exports__.formatCandidateLabel;
20955
21210
  exports.formatGitAuthDoctorReport = __webpack_exports__.formatGitAuthDoctorReport;
@@ -20963,12 +21218,15 @@ exports.getGitFetchUrl = __webpack_exports__.getGitFetchUrl;
20963
21218
  exports.getGitPushUrl = __webpack_exports__.getGitPushUrl;
20964
21219
  exports.getInstalledPreferredEditors = __webpack_exports__.getInstalledPreferredEditors;
20965
21220
  exports.getLatestVersion = __webpack_exports__.getLatestVersion;
21221
+ exports.getModelToken = __webpack_exports__.getModelToken;
21222
+ exports.getModelTokenHelperScript = __webpack_exports__.getModelTokenHelperScript;
20966
21223
  exports.getPreferredEditorCommand = __webpack_exports__.getPreferredEditorCommand;
20967
21224
  exports.getSettableConfigKeys = __webpack_exports__.getSettableConfigKeys;
20968
21225
  exports.getValidAuthToken = __webpack_exports__.getValidAuthToken;
20969
21226
  exports.getWebSocketUrl = __webpack_exports__.getWebSocketUrl;
20970
21227
  exports.hasTokensForUrl = __webpack_exports__.hasTokensForUrl;
20971
21228
  exports.inspectCredentialHelperCommand = __webpack_exports__.inspectCredentialHelperCommand;
21229
+ exports.installModelTokenHelperScript = __webpack_exports__.installModelTokenHelperScript;
20972
21230
  exports.isCommandAvailable = __webpack_exports__.isCommandAvailable;
20973
21231
  exports.isDirectoryNonEmpty = __webpack_exports__.isDirectoryNonEmpty;
20974
21232
  exports.isPathInsideGitRepo = __webpack_exports__.isPathInsideGitRepo;
@@ -21004,9 +21262,11 @@ exports.validatePath = __webpack_exports__.validatePath;
21004
21262
  exports.verifyAndStoreTokens = __webpack_exports__.verifyAndStoreTokens;
21005
21263
  exports.verifyAuth = __webpack_exports__.verifyAuth;
21006
21264
  exports.watch = __webpack_exports__.watch;
21265
+ exports.withRepoAuthFileLock = __webpack_exports__.withRepoAuthFileLock;
21007
21266
  exports.writeEnvironmentPid = __webpack_exports__.writeEnvironmentPid;
21008
21267
  for(var __rspack_i in __webpack_exports__)if (-1 === [
21009
21268
  "ANVIL_SYNC_CLIENT_ID",
21269
+ "AUTH_FILE_LOCK_DEFAULTS",
21010
21270
  "AnvilYamlValidationResult",
21011
21271
  "AppIdCandidate",
21012
21272
  "AppIdWithContext",
@@ -21018,11 +21278,15 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
21018
21278
  "CheckoutResult",
21019
21279
  "DeviceAuthorizationResponse",
21020
21280
  "FormTemplateValidationResult",
21281
+ "GetValidAuthTokenOptions",
21021
21282
  "LoginResult",
21022
21283
  "LogoutResult",
21284
+ "MODEL_TOKEN_HELPER_FILENAME",
21285
+ "MODEL_TOKEN_LOCK_TIMEOUT_MS",
21023
21286
  "OAuthTokenResponse",
21024
21287
  "ParsedCheckoutInput",
21025
21288
  "PreferredEditor",
21289
+ "REFRESH_REQUEST_TIMEOUT_MS",
21026
21290
  "SettableConfigKey",
21027
21291
  "TokenData",
21028
21292
  "ValidateAppIdResponse",
@@ -21043,6 +21307,7 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
21043
21307
  "detectAppIdsFromAllRemotes",
21044
21308
  "executeCheckout",
21045
21309
  "executeGitCredentialOperation",
21310
+ "executeModelTokenOperation",
21046
21311
  "filterCandidates",
21047
21312
  "formatCandidateLabel",
21048
21313
  "formatGitAuthDoctorReport",
@@ -21056,12 +21321,15 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
21056
21321
  "getGitPushUrl",
21057
21322
  "getInstalledPreferredEditors",
21058
21323
  "getLatestVersion",
21324
+ "getModelToken",
21325
+ "getModelTokenHelperScript",
21059
21326
  "getPreferredEditorCommand",
21060
21327
  "getSettableConfigKeys",
21061
21328
  "getValidAuthToken",
21062
21329
  "getWebSocketUrl",
21063
21330
  "hasTokensForUrl",
21064
21331
  "inspectCredentialHelperCommand",
21332
+ "installModelTokenHelperScript",
21065
21333
  "isCommandAvailable",
21066
21334
  "isDirectoryNonEmpty",
21067
21335
  "isPathInsideGitRepo",
@@ -21097,6 +21365,7 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
21097
21365
  "verifyAndStoreTokens",
21098
21366
  "verifyAuth",
21099
21367
  "watch",
21368
+ "withRepoAuthFileLock",
21100
21369
  "writeEnvironmentPid"
21101
21370
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
21102
21371
  Object.defineProperty(exports, '__esModule', {