@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.
- package/dist/WatchSession.d.ts +2 -0
- package/dist/WatchSession.d.ts.map +1 -1
- package/dist/api.d.ts +5 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/cli.js +1583 -1068
- package/dist/commands/configure.d.ts.map +1 -1
- package/dist/commands/db.d.ts +3 -0
- package/dist/commands/db.d.ts.map +1 -0
- package/dist/commands/deps.d.ts.map +1 -1
- package/dist/commands/env.d.ts +8 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/index.d.ts +3 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/modelToken.d.ts +33 -0
- package/dist/commands/modelToken.d.ts.map +1 -0
- package/dist/commands/repl.d.ts +3 -0
- package/dist/commands/repl.d.ts.map +1 -0
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/errors.d.ts +11 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.js +1257 -988
- package/dist/program.d.ts.map +1 -1
- package/dist/services/anvil-api.d.ts.map +1 -1
- package/dist/services/auth.d.ts +42 -3
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/db.d.ts +7 -0
- package/dist/services/db.d.ts.map +1 -0
- package/dist/services/deps.d.ts.map +1 -1
- package/dist/services/environment.d.ts +16 -0
- package/dist/services/environment.d.ts.map +1 -0
- package/dist/services/git-auth.d.ts +35 -0
- package/dist/services/git-auth.d.ts.map +1 -1
- package/dist/services/git.d.ts +1 -0
- package/dist/services/git.d.ts.map +1 -1
- package/dist/services/http.d.ts +2 -0
- package/dist/services/http.d.ts.map +1 -0
- package/dist/services/repl.d.ts +18 -0
- package/dist/services/repl.d.ts.map +1 -0
- package/dist/validatePython.d.ts.map +1 -1
- package/dist/watch/SaveProcessor.d.ts +2 -0
- package/dist/watch/SaveProcessor.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/commands/tables.d.ts +0 -7
- package/dist/commands/tables.d.ts.map +0 -1
- package/dist/services/tables.d.ts +0 -27
- package/dist/services/tables.d.ts.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -13384,9 +13384,10 @@ var __webpack_exports__ = {};
|
|
|
13384
13384
|
type: "auth_invalid",
|
|
13385
13385
|
message
|
|
13386
13386
|
}),
|
|
13387
|
-
refreshFailed: (message)=>({
|
|
13387
|
+
refreshFailed: (message, status)=>({
|
|
13388
13388
|
type: "token_refresh_failed",
|
|
13389
|
-
message
|
|
13389
|
+
message,
|
|
13390
|
+
status
|
|
13390
13391
|
})
|
|
13391
13392
|
};
|
|
13392
13393
|
const createAppError = {
|
|
@@ -13600,6 +13601,19 @@ var __webpack_exports__ = {};
|
|
|
13600
13601
|
return "An unknown error occurred";
|
|
13601
13602
|
}
|
|
13602
13603
|
}
|
|
13604
|
+
function getErrorCauseMessage(error) {
|
|
13605
|
+
if ("object" != typeof error || null === error || !("cause" in error)) return null;
|
|
13606
|
+
const cause = error.cause;
|
|
13607
|
+
if (null == cause) return null;
|
|
13608
|
+
const message = errors_getErrorMessage(cause);
|
|
13609
|
+
const code = "object" == typeof cause && null !== cause && "code" in cause && "string" == typeof cause.code ? cause.code : null;
|
|
13610
|
+
if (code && !message.includes(code)) return `${message} (${code})`;
|
|
13611
|
+
return message;
|
|
13612
|
+
}
|
|
13613
|
+
function logErrorCause(error, ui) {
|
|
13614
|
+
const causeMessage = getErrorCauseMessage(error);
|
|
13615
|
+
if (causeMessage) ui.verbose(`Cause: ${causeMessage}`);
|
|
13616
|
+
}
|
|
13603
13617
|
const external_module_namespaceObject = require("module");
|
|
13604
13618
|
const requireFromHere = (0, external_module_namespaceObject.createRequire)(__filename);
|
|
13605
13619
|
const GIT_CREDENTIAL_HELPER_FILENAME = "anvil-credential-helper.cjs";
|
|
@@ -13820,6 +13834,76 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
13820
13834
|
function getAuthFilePath(gitDir) {
|
|
13821
13835
|
return external_path_default().join(gitDir, "anvil-auth.json");
|
|
13822
13836
|
}
|
|
13837
|
+
const AUTH_FILE_LOCK_DEFAULTS = {
|
|
13838
|
+
staleMs: 30000,
|
|
13839
|
+
timeoutMs: 60000,
|
|
13840
|
+
retryDelayMs: 100,
|
|
13841
|
+
heartbeatMs: 5000
|
|
13842
|
+
};
|
|
13843
|
+
function git_auth_delay(ms) {
|
|
13844
|
+
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
13845
|
+
}
|
|
13846
|
+
async function acquireAuthFileLock(lockPath, options) {
|
|
13847
|
+
const deadline = Date.now() + options.timeoutMs;
|
|
13848
|
+
while(true){
|
|
13849
|
+
try {
|
|
13850
|
+
const handle = await external_fs_.promises.open(lockPath, "wx", 384);
|
|
13851
|
+
try {
|
|
13852
|
+
await handle.writeFile(JSON.stringify({
|
|
13853
|
+
pid: process.pid,
|
|
13854
|
+
acquiredAt: new Date().toISOString()
|
|
13855
|
+
}));
|
|
13856
|
+
} finally{
|
|
13857
|
+
await handle.close();
|
|
13858
|
+
}
|
|
13859
|
+
return;
|
|
13860
|
+
} catch (e) {
|
|
13861
|
+
if ("EEXIST" !== e.code) throw e;
|
|
13862
|
+
}
|
|
13863
|
+
try {
|
|
13864
|
+
const lockStat = await external_fs_.promises.stat(lockPath);
|
|
13865
|
+
if (Date.now() - lockStat.mtimeMs > options.staleMs) {
|
|
13866
|
+
await external_fs_.promises.rm(lockPath, {
|
|
13867
|
+
force: true
|
|
13868
|
+
});
|
|
13869
|
+
continue;
|
|
13870
|
+
}
|
|
13871
|
+
} catch {
|
|
13872
|
+
continue;
|
|
13873
|
+
}
|
|
13874
|
+
if (Date.now() >= deadline) throw new Error(`Timed out waiting for the Anvil auth file lock at ${lockPath}`);
|
|
13875
|
+
await git_auth_delay(options.retryDelayMs);
|
|
13876
|
+
}
|
|
13877
|
+
}
|
|
13878
|
+
async function withRepoAuthFileLock(repoPath, fn, options) {
|
|
13879
|
+
const gitDir = await getRepositoryGitDir(repoPath);
|
|
13880
|
+
const lockPath = `${getAuthFilePath(gitDir)}.lock`;
|
|
13881
|
+
const lockOptions = {
|
|
13882
|
+
...AUTH_FILE_LOCK_DEFAULTS,
|
|
13883
|
+
...options
|
|
13884
|
+
};
|
|
13885
|
+
await external_fs_.promises.mkdir(external_path_default().dirname(lockPath), {
|
|
13886
|
+
recursive: true
|
|
13887
|
+
});
|
|
13888
|
+
await acquireAuthFileLock(lockPath, lockOptions);
|
|
13889
|
+
const heartbeat = startAuthFileLockHeartbeat(lockPath, lockOptions.heartbeatMs);
|
|
13890
|
+
try {
|
|
13891
|
+
return await fn();
|
|
13892
|
+
} finally{
|
|
13893
|
+
clearInterval(heartbeat);
|
|
13894
|
+
await external_fs_.promises.rm(lockPath, {
|
|
13895
|
+
force: true
|
|
13896
|
+
});
|
|
13897
|
+
}
|
|
13898
|
+
}
|
|
13899
|
+
function startAuthFileLockHeartbeat(lockPath, heartbeatMs) {
|
|
13900
|
+
const heartbeat = setInterval(()=>{
|
|
13901
|
+
const now = new Date();
|
|
13902
|
+
external_fs_.promises.utimes(lockPath, now, now).catch(()=>{});
|
|
13903
|
+
}, heartbeatMs);
|
|
13904
|
+
heartbeat.unref();
|
|
13905
|
+
return heartbeat;
|
|
13906
|
+
}
|
|
13823
13907
|
function decodeKey(b64key) {
|
|
13824
13908
|
const key = Buffer.from(b64key, "base64");
|
|
13825
13909
|
if (32 !== key.length) throw new Error("Encryption key must be a base64-encoded 32-byte key");
|
|
@@ -13864,10 +13948,19 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
13864
13948
|
recursive: true
|
|
13865
13949
|
});
|
|
13866
13950
|
const authJson = JSON.stringify(auth, null, 2);
|
|
13867
|
-
|
|
13868
|
-
|
|
13869
|
-
|
|
13870
|
-
|
|
13951
|
+
const tempFilePath = `${authFilePath}.tmp-${external_node_crypto_default().randomBytes(6).toString("hex")}`;
|
|
13952
|
+
try {
|
|
13953
|
+
await external_fs_.promises.writeFile(tempFilePath, encrypt(authJson, encryptionKey), {
|
|
13954
|
+
mode: 384
|
|
13955
|
+
});
|
|
13956
|
+
await external_fs_.promises.chmod(tempFilePath, 448);
|
|
13957
|
+
await external_fs_.promises.rename(tempFilePath, authFilePath);
|
|
13958
|
+
} catch (e) {
|
|
13959
|
+
await external_fs_.promises.rm(tempFilePath, {
|
|
13960
|
+
force: true
|
|
13961
|
+
});
|
|
13962
|
+
throw e;
|
|
13963
|
+
}
|
|
13871
13964
|
await git.raw([
|
|
13872
13965
|
"config",
|
|
13873
13966
|
"--local",
|
|
@@ -13876,8 +13969,9 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
13876
13969
|
]);
|
|
13877
13970
|
return authFilePath;
|
|
13878
13971
|
}
|
|
13879
|
-
const
|
|
13972
|
+
const git_auth_readAuthFromRepo = async (repoPath)=>{
|
|
13880
13973
|
const git = esm_default(repoPath);
|
|
13974
|
+
if (!await git.checkIsRepo()) return;
|
|
13881
13975
|
const gitDir = await getRepositoryGitDir(repoPath, git);
|
|
13882
13976
|
const authFilePath = getAuthFilePath(gitDir);
|
|
13883
13977
|
if (!external_fs_default().existsSync(authFilePath)) return;
|
|
@@ -13889,14 +13983,20 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
13889
13983
|
return resolveAnvilUrl();
|
|
13890
13984
|
}
|
|
13891
13985
|
const ANVIL_SYNC_CLIENT_ID = "anvil-sync";
|
|
13986
|
+
const REFRESH_REQUEST_TIMEOUT_MS = 12000;
|
|
13892
13987
|
let inMemoryAuth = null;
|
|
13893
13988
|
function authMatches(auth, anvilUrl, username) {
|
|
13894
13989
|
return auth.anvilUrl === normalizeAnvilUrl(anvilUrl) && (!username || auth.username === username);
|
|
13895
13990
|
}
|
|
13896
13991
|
let repoContext = null;
|
|
13897
|
-
function
|
|
13992
|
+
function auth_setRepoContext(repoPath) {
|
|
13898
13993
|
repoContext = repoPath;
|
|
13899
13994
|
}
|
|
13995
|
+
async function resolveAuthAnvilUrl() {
|
|
13996
|
+
if (inMemoryAuth) return inMemoryAuth.anvilUrl;
|
|
13997
|
+
const repoAuth = repoContext ? await git_auth_readAuthFromRepo(repoContext) : void 0;
|
|
13998
|
+
return repoAuth?.anvilUrl ?? resolveAnvilUrl();
|
|
13999
|
+
}
|
|
13900
14000
|
const inFlightRefreshes = new Map();
|
|
13901
14001
|
async function verifyAuth(authToken, anvilUrl = getDefaultAnvilUrl()) {
|
|
13902
14002
|
try {
|
|
@@ -13917,7 +14017,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
13917
14017
|
throw errors_createAuthError.invalid(`Network error: ${e.message}`);
|
|
13918
14018
|
}
|
|
13919
14019
|
}
|
|
13920
|
-
async function refreshAccessToken(refreshToken, anvilUrl = getDefaultAnvilUrl(), clientId = ANVIL_SYNC_CLIENT_ID) {
|
|
14020
|
+
async function refreshAccessToken(refreshToken, anvilUrl = getDefaultAnvilUrl(), clientId = ANVIL_SYNC_CLIENT_ID, options) {
|
|
13921
14021
|
try {
|
|
13922
14022
|
const tokenResponse = await fetch(`${anvilUrl}/oauth/token`, {
|
|
13923
14023
|
method: "POST",
|
|
@@ -13928,11 +14028,12 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
13928
14028
|
grant_type: "refresh_token",
|
|
13929
14029
|
refresh_token: refreshToken,
|
|
13930
14030
|
client_id: clientId
|
|
13931
|
-
})
|
|
14031
|
+
}),
|
|
14032
|
+
signal: AbortSignal.timeout(options?.timeoutMs ?? REFRESH_REQUEST_TIMEOUT_MS)
|
|
13932
14033
|
});
|
|
13933
14034
|
if (!tokenResponse.ok) {
|
|
13934
14035
|
const errorText = await tokenResponse.text();
|
|
13935
|
-
throw errors_createAuthError.refreshFailed(`Failed to refresh token: ${tokenResponse.status} ${errorText}
|
|
14036
|
+
throw errors_createAuthError.refreshFailed(`Failed to refresh token: ${tokenResponse.status} ${errorText}`, tokenResponse.status);
|
|
13936
14037
|
}
|
|
13937
14038
|
const tokenData = await tokenResponse.json();
|
|
13938
14039
|
return tokenData;
|
|
@@ -13944,22 +14045,21 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
13944
14045
|
async function hasTokensForUrl(anvilUrl, username) {
|
|
13945
14046
|
if (inMemoryAuth) return authMatches(inMemoryAuth, anvilUrl, username);
|
|
13946
14047
|
{
|
|
13947
|
-
const repoAuth = repoContext ? await
|
|
14048
|
+
const repoAuth = repoContext ? await git_auth_readAuthFromRepo(repoContext) : void 0;
|
|
13948
14049
|
if (repoAuth && authMatches(repoAuth, anvilUrl, username)) return true;
|
|
13949
14050
|
return hasTokens(anvilUrl, username);
|
|
13950
14051
|
}
|
|
13951
14052
|
}
|
|
13952
|
-
async function auth_getValidAuthToken(anvilUrl = getDefaultAnvilUrl(), username) {
|
|
14053
|
+
async function auth_getValidAuthToken(anvilUrl = getDefaultAnvilUrl(), username, options) {
|
|
13953
14054
|
const normalized = normalizeAnvilUrl(anvilUrl);
|
|
13954
14055
|
const source = await resolveTokenSource(normalized, username);
|
|
13955
14056
|
const tokens = getSourceTokens(source);
|
|
13956
14057
|
if (!tokens.authToken && !tokens.refreshToken) throw errors_createAuthError.required("Not logged in. Please log in first.");
|
|
13957
|
-
|
|
13958
|
-
if (tokens.refreshToken && isExpired) {
|
|
14058
|
+
if (tokens.refreshToken && isAccessTokenExpired(tokens)) {
|
|
13959
14059
|
const refreshKey = getRefreshKey(normalized, username, source, tokens.refreshToken);
|
|
13960
14060
|
const existingRefresh = inFlightRefreshes.get(refreshKey);
|
|
13961
14061
|
if (existingRefresh) return existingRefresh;
|
|
13962
|
-
const refreshPromise = refreshAndStoreAccessToken(normalized, username, source, tokens.refreshToken).finally(()=>{
|
|
14062
|
+
const refreshPromise = refreshAndStoreAccessToken(normalized, username, source, tokens.refreshToken, options).finally(()=>{
|
|
13963
14063
|
inFlightRefreshes.delete(refreshKey);
|
|
13964
14064
|
});
|
|
13965
14065
|
inFlightRefreshes.set(refreshKey, refreshPromise);
|
|
@@ -13973,7 +14073,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
13973
14073
|
type: "memory",
|
|
13974
14074
|
auth: inMemoryAuth
|
|
13975
14075
|
};
|
|
13976
|
-
const repoAuth = repoContext ? await
|
|
14076
|
+
const repoAuth = repoContext ? await git_auth_readAuthFromRepo(repoContext) : void 0;
|
|
13977
14077
|
if (repoAuth && authMatches(repoAuth, normalizedUrl, username)) return {
|
|
13978
14078
|
type: "repo",
|
|
13979
14079
|
auth: repoAuth,
|
|
@@ -13989,6 +14089,9 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
13989
14089
|
function getSourceTokens(source) {
|
|
13990
14090
|
return "store" === source.type ? source.tokens : source.auth.tokens;
|
|
13991
14091
|
}
|
|
14092
|
+
function isAccessTokenExpired(tokens) {
|
|
14093
|
+
return null !== tokens.authTokenExpiresAt && tokens.authTokenExpiresAt <= Math.floor(Date.now() / 1000) + 60;
|
|
14094
|
+
}
|
|
13992
14095
|
function getStoreAccountUsername(normalizedUrl, username, refreshToken) {
|
|
13993
14096
|
if (username) return username;
|
|
13994
14097
|
const urlTokens = getUrlTokens(normalizedUrl);
|
|
@@ -14007,11 +14110,43 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
14007
14110
|
return `store:${normalizedUrl}:${source.accountUsername ?? username ?? refreshToken}`;
|
|
14008
14111
|
}
|
|
14009
14112
|
}
|
|
14010
|
-
async function refreshAndStoreAccessToken(normalizedUrl, username, source, refreshToken) {
|
|
14113
|
+
async function refreshAndStoreAccessToken(normalizedUrl, username, source, refreshToken, options) {
|
|
14114
|
+
if ("repo" === source.type) return refreshRepoAuthTokens(source.repoPath, {
|
|
14115
|
+
anvilUrl: normalizedUrl,
|
|
14116
|
+
username,
|
|
14117
|
+
auth: source.auth,
|
|
14118
|
+
refreshToken,
|
|
14119
|
+
lock: options?.lock,
|
|
14120
|
+
requestTimeoutMs: options?.requestTimeoutMs
|
|
14121
|
+
});
|
|
14122
|
+
return performTokenRefresh(normalizedUrl, username, source, refreshToken, options?.requestTimeoutMs);
|
|
14123
|
+
}
|
|
14124
|
+
async function refreshRepoAuthTokens(repoPath, options) {
|
|
14125
|
+
const normalizedUrl = normalizeAnvilUrl(options.anvilUrl);
|
|
14126
|
+
return withRepoAuthFileLock(repoPath, async ()=>{
|
|
14127
|
+
let auth = options.auth;
|
|
14128
|
+
let refreshToken = options.refreshToken;
|
|
14129
|
+
const current = await git_auth_readAuthFromRepo(repoPath);
|
|
14130
|
+
if (current && authMatches(current, normalizedUrl, options.username)) {
|
|
14131
|
+
if (current.tokens.authToken && !isAccessTokenExpired(current.tokens)) return current.tokens.authToken;
|
|
14132
|
+
auth = current;
|
|
14133
|
+
refreshToken = current.tokens.refreshToken ?? refreshToken;
|
|
14134
|
+
}
|
|
14135
|
+
if (!auth || !refreshToken) throw errors_createAuthError.required("No repo auth tokens available to refresh.");
|
|
14136
|
+
return performTokenRefresh(normalizedUrl, options.username, {
|
|
14137
|
+
type: "repo",
|
|
14138
|
+
auth,
|
|
14139
|
+
repoPath
|
|
14140
|
+
}, refreshToken, options.requestTimeoutMs);
|
|
14141
|
+
}, options.lock);
|
|
14142
|
+
}
|
|
14143
|
+
async function performTokenRefresh(normalizedUrl, username, source, refreshToken, requestTimeoutMs) {
|
|
14011
14144
|
try {
|
|
14012
14145
|
const tokens = getSourceTokens(source);
|
|
14013
14146
|
const clientId = tokens.clientId ?? ANVIL_SYNC_CLIENT_ID;
|
|
14014
|
-
const tokenData = await refreshAccessToken(refreshToken, normalizedUrl, clientId
|
|
14147
|
+
const tokenData = await refreshAccessToken(refreshToken, normalizedUrl, clientId, {
|
|
14148
|
+
timeoutMs: requestTimeoutMs
|
|
14149
|
+
});
|
|
14015
14150
|
const newTokens = {
|
|
14016
14151
|
authToken: tokenData.access_token,
|
|
14017
14152
|
refreshToken: tokenData.refresh_token,
|
|
@@ -14033,12 +14168,28 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
14033
14168
|
}
|
|
14034
14169
|
return tokenData.access_token;
|
|
14035
14170
|
} catch (e) {
|
|
14171
|
+
if ("repo" === source.type && isRefreshTokenRejection(e)) await clearRepoAuthTokens(source.repoPath, source.auth).catch(()=>{});
|
|
14036
14172
|
await clearTokensForAccount(normalizedUrl, username, refreshToken);
|
|
14037
14173
|
throw e;
|
|
14038
14174
|
}
|
|
14039
14175
|
}
|
|
14176
|
+
function isRefreshTokenRejection(e) {
|
|
14177
|
+
const error = e;
|
|
14178
|
+
return error?.type === "token_refresh_failed" && (400 === error.status || 401 === error.status);
|
|
14179
|
+
}
|
|
14180
|
+
async function clearRepoAuthTokens(repoPath, auth) {
|
|
14181
|
+
const encryptionKey = process.env["ANVIL_AUTH_FILE_ENCRYPTION_KEY"];
|
|
14182
|
+
if (!encryptionKey) return;
|
|
14183
|
+
auth.tokens = {
|
|
14184
|
+
authToken: null,
|
|
14185
|
+
refreshToken: null,
|
|
14186
|
+
authTokenExpiresAt: null,
|
|
14187
|
+
clientId: auth.tokens.clientId
|
|
14188
|
+
};
|
|
14189
|
+
await writeAuthToFile(repoPath, auth, encryptionKey);
|
|
14190
|
+
}
|
|
14040
14191
|
async function clearTokensForAccount(url, username, refreshToken) {
|
|
14041
|
-
if (inMemoryAuth || repoContext && await
|
|
14192
|
+
if (inMemoryAuth || repoContext && await git_auth_readAuthFromRepo(repoContext)) return;
|
|
14042
14193
|
{
|
|
14043
14194
|
const normalized = normalizeAnvilUrl(url);
|
|
14044
14195
|
if (username) deleteAccountTokens(normalized, username);
|
|
@@ -14068,7 +14219,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
14068
14219
|
loggedOut: false,
|
|
14069
14220
|
message: "Cannot log out when using in-memory auth tokens"
|
|
14070
14221
|
};
|
|
14071
|
-
if (repoContext && await
|
|
14222
|
+
if (repoContext && await git_auth_readAuthFromRepo(repoContext)) return {
|
|
14072
14223
|
loggedOut: false,
|
|
14073
14224
|
message: "Cannot log out when using auth tokens stored in the local git config."
|
|
14074
14225
|
};
|
|
@@ -14099,7 +14250,7 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
14099
14250
|
inMemoryAuth.username
|
|
14100
14251
|
] : [];
|
|
14101
14252
|
{
|
|
14102
|
-
const repoAuth = repoContext ? await
|
|
14253
|
+
const repoAuth = repoContext ? await git_auth_readAuthFromRepo(repoContext) : void 0;
|
|
14103
14254
|
if (repoAuth && authMatches(repoAuth, url)) return [
|
|
14104
14255
|
repoAuth.username
|
|
14105
14256
|
];
|
|
@@ -14111,1030 +14262,1062 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
14111
14262
|
inMemoryAuth.anvilUrl
|
|
14112
14263
|
];
|
|
14113
14264
|
{
|
|
14114
|
-
const repoAuth = repoContext ? await
|
|
14265
|
+
const repoAuth = repoContext ? await git_auth_readAuthFromRepo(repoContext) : void 0;
|
|
14115
14266
|
if (repoAuth) return [
|
|
14116
14267
|
repoAuth.anvilUrl
|
|
14117
14268
|
];
|
|
14118
14269
|
return getAvailableUrls();
|
|
14119
14270
|
}
|
|
14120
14271
|
}
|
|
14121
|
-
|
|
14122
|
-
|
|
14272
|
+
const promises_namespaceObject = require("timers/promises");
|
|
14273
|
+
const WANT_NOT_VALID_RE = /\bwant\s+[0-9a-f]{7,40}\s+not valid\b/i;
|
|
14274
|
+
const FETCH_RETRY_DELAY_MS = 200;
|
|
14275
|
+
function isWantNotValidError(error) {
|
|
14276
|
+
return WANT_NOT_VALID_RE.test(error.message || "");
|
|
14123
14277
|
}
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
|
|
14133
|
-
|
|
14134
|
-
|
|
14135
|
-
|
|
14136
|
-
|
|
14137
|
-
const data = await resp.json();
|
|
14138
|
-
if (resp.ok) return {
|
|
14139
|
-
valid: data.valid ?? false,
|
|
14140
|
-
app_name: data.app_name
|
|
14141
|
-
};
|
|
14142
|
-
if (401 === resp.status) throw errors_createAuthError.invalid("Authentication failed");
|
|
14143
|
-
if (403 === resp.status) throw createAppError.accessDenied(appId);
|
|
14144
|
-
if (404 === resp.status) throw createAppError.notFound(appId);
|
|
14145
|
-
logger_logger.debug(`validate-app-id failed: ${resp.status} ${resp.statusText}`);
|
|
14146
|
-
return {
|
|
14147
|
-
valid: false,
|
|
14148
|
-
error: data.error || `Server error: ${resp.status}`
|
|
14149
|
-
};
|
|
14150
|
-
} catch (error) {
|
|
14151
|
-
if (error.type) throw error;
|
|
14152
|
-
throw errors_createNetworkError.network(error.message);
|
|
14278
|
+
class GitService extends Emitter {
|
|
14279
|
+
git;
|
|
14280
|
+
repoPath;
|
|
14281
|
+
static INDEX_LOCK_RETRY_DELAYS_MS = [
|
|
14282
|
+
25,
|
|
14283
|
+
50,
|
|
14284
|
+
100,
|
|
14285
|
+
200
|
|
14286
|
+
];
|
|
14287
|
+
constructor(repoPath){
|
|
14288
|
+
super();
|
|
14289
|
+
this.repoPath = external_path_default().resolve(repoPath);
|
|
14290
|
+
this.git = esm_default(this.repoPath);
|
|
14153
14291
|
}
|
|
14154
|
-
|
|
14155
|
-
|
|
14156
|
-
const anvilUrl = options.anvilUrl ?? anvil_api_getDefaultAnvilUrl();
|
|
14157
|
-
const authToken = await auth_getValidAuthToken(anvilUrl, options.username);
|
|
14158
|
-
const params = new URLSearchParams();
|
|
14159
|
-
if ("number" == typeof options.limit) params.set("limit", String(options.limit));
|
|
14160
|
-
if (options.cursor) params.set("cursor", options.cursor);
|
|
14161
|
-
if (options.q && options.q.trim()) params.set("q", options.q.trim());
|
|
14162
|
-
const query = params.toString();
|
|
14163
|
-
const url = `${anvilUrl}/ide/api/_/apps${query ? `?${query}` : ""}`;
|
|
14164
|
-
try {
|
|
14165
|
-
const resp = await fetch(url, {
|
|
14166
|
-
method: "GET",
|
|
14167
|
-
headers: {
|
|
14168
|
-
Authorization: `Bearer ${authToken}`
|
|
14169
|
-
},
|
|
14170
|
-
signal: options.signal
|
|
14171
|
-
});
|
|
14172
|
-
if (!resp.ok) {
|
|
14173
|
-
if (401 === resp.status) throw errors_createAuthError.invalid("Authentication failed");
|
|
14174
|
-
throw errors_createNetworkError.server(resp.status, resp.statusText);
|
|
14175
|
-
}
|
|
14176
|
-
const data = await resp.json();
|
|
14177
|
-
return {
|
|
14178
|
-
apps: Array.isArray(data.apps) ? data.apps : [],
|
|
14179
|
-
next_cursor: "string" == typeof data.next_cursor ? data.next_cursor : null
|
|
14180
|
-
};
|
|
14181
|
-
} catch (error) {
|
|
14182
|
-
if (error.type) throw error;
|
|
14183
|
-
throw errors_createNetworkError.network(error.message);
|
|
14292
|
+
getGit() {
|
|
14293
|
+
return this.git;
|
|
14184
14294
|
}
|
|
14185
|
-
|
|
14186
|
-
|
|
14187
|
-
const url = new URL(gitUrl);
|
|
14188
|
-
const encodedToken = encodeURIComponent(authToken);
|
|
14189
|
-
return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git?no_freeze=true&q=`;
|
|
14190
|
-
}
|
|
14191
|
-
function getGitPushUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
|
|
14192
|
-
const url = new URL(anvilUrl);
|
|
14193
|
-
const encodedToken = encodeURIComponent(authToken);
|
|
14194
|
-
return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git`;
|
|
14195
|
-
}
|
|
14196
|
-
function getWebSocketUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
|
|
14197
|
-
return anvilUrl.replace(/^http/, "ws") + `/ide/api/_/apps/${appId}/ws?access_token=${authToken}`;
|
|
14198
|
-
}
|
|
14199
|
-
async function getLatestVersion() {
|
|
14200
|
-
try {
|
|
14201
|
-
const response = await fetch("https://registry.npmjs.org/@anvil-works/anvil-cli/latest");
|
|
14202
|
-
if (!response.ok) return null;
|
|
14203
|
-
const data = await response.json();
|
|
14204
|
-
return data.version;
|
|
14205
|
-
} catch (e) {
|
|
14206
|
-
return null;
|
|
14295
|
+
getRepoPath() {
|
|
14296
|
+
return this.repoPath;
|
|
14207
14297
|
}
|
|
14208
|
-
|
|
14209
|
-
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
|
|
14213
|
-
|
|
14298
|
+
async getGitDir() {
|
|
14299
|
+
try {
|
|
14300
|
+
const gitDir = (await this.git.revparse([
|
|
14301
|
+
"--git-dir"
|
|
14302
|
+
])).trim();
|
|
14303
|
+
if (!external_path_default().isAbsolute(gitDir)) return external_path_default().resolve(this.repoPath, gitDir);
|
|
14304
|
+
return gitDir;
|
|
14305
|
+
} catch (e) {
|
|
14306
|
+
throw errors_createGitError.commandFailed("rev-parse --git-dir", e.message);
|
|
14307
|
+
}
|
|
14214
14308
|
}
|
|
14215
|
-
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
14226
|
-
}
|
|
14227
|
-
function lookupRemoteInfoForAppId(appId, detectedRemotes) {
|
|
14228
|
-
const matches = detectedRemotes.filter((c)=>c.appId === appId);
|
|
14229
|
-
if (0 === matches.length) return {};
|
|
14230
|
-
const withUsername = matches.find((c)=>c.detectedUsername);
|
|
14231
|
-
if (withUsername) return {
|
|
14232
|
-
detectedUrl: withUsername.detectedUrl,
|
|
14233
|
-
detectedUsername: withUsername.detectedUsername
|
|
14234
|
-
};
|
|
14235
|
-
return {
|
|
14236
|
-
detectedUrl: matches[0].detectedUrl,
|
|
14237
|
-
detectedUsername: matches[0].detectedUsername
|
|
14238
|
-
};
|
|
14239
|
-
}
|
|
14240
|
-
async function detectAppIdsFromAllRemotes(repoPath) {
|
|
14241
|
-
const git = esm_default(repoPath);
|
|
14242
|
-
const out = [];
|
|
14243
|
-
try {
|
|
14244
|
-
const remotes = await git.getRemotes(true);
|
|
14245
|
-
for (const remote of remotes){
|
|
14246
|
-
const httpMatch = remote.refs.fetch?.match(/(?:http|https):\/\/(?:[^@]+@)?([^:\/]+)(?::\d+)?\/git\/([A-Z0-9]+)\.git/);
|
|
14247
|
-
if (httpMatch) {
|
|
14248
|
-
const [, host, detectedAppId] = httpMatch;
|
|
14249
|
-
out.push({
|
|
14250
|
-
appId: detectedAppId,
|
|
14251
|
-
source: "remote",
|
|
14252
|
-
description: `Git remote '${remote.name}'`,
|
|
14253
|
-
detectedUrl: normalizeAnvilUrl(host)
|
|
14254
|
-
});
|
|
14255
|
-
continue;
|
|
14256
|
-
}
|
|
14257
|
-
const sshMatch = remote.refs.fetch?.match(/ssh:\/\/([^@]+)@([^:]+):(\d+)\/(?:git\/)?([A-Z0-9]+)\.git/);
|
|
14258
|
-
if (sshMatch) {
|
|
14259
|
-
const [, usernamePart, host, , detectedAppId] = sshMatch;
|
|
14260
|
-
const detectedUsername = usernamePart.includes("%") ? decodeURIComponent(usernamePart) : usernamePart;
|
|
14261
|
-
const finalUsername = "git" !== detectedUsername ? detectedUsername : void 0;
|
|
14262
|
-
out.push({
|
|
14263
|
-
appId: detectedAppId,
|
|
14264
|
-
source: "remote",
|
|
14265
|
-
description: `Git remote '${remote.name}' (SSH)`,
|
|
14266
|
-
detectedUrl: normalizeAnvilUrl(host),
|
|
14267
|
-
detectedUsername: finalUsername
|
|
14268
|
-
});
|
|
14269
|
-
}
|
|
14309
|
+
async getCurrentBranch() {
|
|
14310
|
+
try {
|
|
14311
|
+
const branchRef = await this.git.revparse([
|
|
14312
|
+
"--abbrev-ref",
|
|
14313
|
+
"HEAD"
|
|
14314
|
+
]);
|
|
14315
|
+
if ("HEAD" === branchRef) throw createSyncError.detachedHead();
|
|
14316
|
+
return branchRef;
|
|
14317
|
+
} catch (e) {
|
|
14318
|
+
if ("detached_head" === e.type) throw e;
|
|
14319
|
+
throw createSyncError.detachedHead();
|
|
14270
14320
|
}
|
|
14271
|
-
}
|
|
14272
|
-
|
|
14273
|
-
|
|
14274
|
-
|
|
14275
|
-
|
|
14276
|
-
|
|
14277
|
-
|
|
14278
|
-
|
|
14279
|
-
|
|
14280
|
-
const authToken = await auth_getValidAuthToken(anvilUrl, username);
|
|
14281
|
-
const branchRef = await git.revparse([
|
|
14282
|
-
"--abbrev-ref",
|
|
14283
|
-
"HEAD"
|
|
14284
|
-
]);
|
|
14285
|
-
const currentBranch = "HEAD" === branchRef ? "master" : branchRef;
|
|
14286
|
-
const commitId = (await git.revparse([
|
|
14287
|
-
"HEAD"
|
|
14288
|
-
]))?.trim();
|
|
14289
|
-
if (!commitId) return out;
|
|
14290
|
-
const resp = await fetch(`${anvilUrl}/ide/api/_/apps/lookup-by-commit`, {
|
|
14291
|
-
method: "POST",
|
|
14292
|
-
headers: {
|
|
14293
|
-
Authorization: `Bearer ${authToken}`,
|
|
14294
|
-
"Content-Type": "application/json"
|
|
14295
|
-
},
|
|
14296
|
-
body: JSON.stringify({
|
|
14297
|
-
commit_id: commitId,
|
|
14298
|
-
branch_name: currentBranch
|
|
14299
|
-
})
|
|
14300
|
-
});
|
|
14301
|
-
logger_logger.debug(`Reverse lookup by commit ${commitId} on branch ${currentBranch} returned ${resp.status}`);
|
|
14302
|
-
if (!resp.ok) {
|
|
14303
|
-
const errorBody = await resp.text();
|
|
14304
|
-
logger_logger.debug(`lookup-by-commit error: ${errorBody}`);
|
|
14321
|
+
}
|
|
14322
|
+
async getCommitId() {
|
|
14323
|
+
try {
|
|
14324
|
+
const commitId = (await this.git.revparse([
|
|
14325
|
+
"HEAD"
|
|
14326
|
+
])).trim();
|
|
14327
|
+
return commitId;
|
|
14328
|
+
} catch (e) {
|
|
14329
|
+
throw errors_createGitError.commandFailed("revparse", e.message);
|
|
14305
14330
|
}
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
const
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
|
|
14313
|
-
|
|
14314
|
-
|
|
14331
|
+
}
|
|
14332
|
+
async getCommitInfo() {
|
|
14333
|
+
try {
|
|
14334
|
+
const hash = (await this.git.revparse([
|
|
14335
|
+
"HEAD"
|
|
14336
|
+
])).trim();
|
|
14337
|
+
const message = (await this.git.raw([
|
|
14338
|
+
"log",
|
|
14339
|
+
"-1",
|
|
14340
|
+
"--format=%s"
|
|
14341
|
+
])).trim();
|
|
14342
|
+
return {
|
|
14343
|
+
hash,
|
|
14344
|
+
shortHash: hash.substring(0, 8),
|
|
14345
|
+
message
|
|
14346
|
+
};
|
|
14347
|
+
} catch (e) {
|
|
14348
|
+
throw errors_createGitError.commandFailed("log", e.message);
|
|
14315
14349
|
}
|
|
14316
|
-
} catch (_e) {}
|
|
14317
|
-
return out;
|
|
14318
|
-
}
|
|
14319
|
-
const promises_namespaceObject = require("fs/promises");
|
|
14320
|
-
var promises_default = /*#__PURE__*/ __webpack_require__.n(promises_namespaceObject);
|
|
14321
|
-
function normalizeLineEndings(content) {
|
|
14322
|
-
return content.replace(/\r\n/g, "\n");
|
|
14323
|
-
}
|
|
14324
|
-
function pythonifyName(name) {
|
|
14325
|
-
return name.replace(/[^A-z0-9]/g, "_").replace(/^[0-9]/, "_$&");
|
|
14326
|
-
}
|
|
14327
|
-
function extractPythonName(relativePath, skipParts = 1) {
|
|
14328
|
-
const parts = relativePath.slice(0, -3).split("/");
|
|
14329
|
-
const isPackage = relativePath.endsWith("__init__.py");
|
|
14330
|
-
const relevantParts = isPackage ? parts.slice(skipParts, -1) : parts.slice(skipParts);
|
|
14331
|
-
return relevantParts.map(pythonifyName).join(".");
|
|
14332
|
-
}
|
|
14333
|
-
function deepEqual(a, b) {
|
|
14334
|
-
if (a === b) return true;
|
|
14335
|
-
if (null == a || null == b) return false;
|
|
14336
|
-
if (typeof a !== typeof b) return false;
|
|
14337
|
-
if ("object" != typeof a) return a === b;
|
|
14338
|
-
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
14339
|
-
if (Array.isArray(a)) {
|
|
14340
|
-
if (a.length !== b.length) return false;
|
|
14341
|
-
return a.every((item, index)=>deepEqual(item, b[index]));
|
|
14342
14350
|
}
|
|
14343
|
-
|
|
14344
|
-
|
|
14345
|
-
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
|
|
14350
|
-
|
|
14351
|
-
|
|
14352
|
-
|
|
14353
|
-
|
|
14354
|
-
|
|
14355
|
-
|
|
14356
|
-
|
|
14357
|
-
|
|
14358
|
-
|
|
14351
|
+
async getStatus() {
|
|
14352
|
+
try {
|
|
14353
|
+
const status = await this.git.status();
|
|
14354
|
+
const staged = status.files.filter((file)=>{
|
|
14355
|
+
const indexStatus = file.index.trim();
|
|
14356
|
+
return indexStatus.length > 0 && "?" !== indexStatus;
|
|
14357
|
+
}).map((file)=>file.path);
|
|
14358
|
+
return {
|
|
14359
|
+
isClean: status.isClean(),
|
|
14360
|
+
modified: status.modified,
|
|
14361
|
+
notAdded: status.not_added,
|
|
14362
|
+
created: status.created,
|
|
14363
|
+
deleted: status.deleted,
|
|
14364
|
+
staged,
|
|
14365
|
+
renamed: status.renamed || []
|
|
14366
|
+
};
|
|
14367
|
+
} catch (e) {
|
|
14368
|
+
throw errors_createGitError.commandFailed("status", e.message);
|
|
14369
|
+
}
|
|
14359
14370
|
}
|
|
14360
|
-
|
|
14361
|
-
|
|
14362
|
-
|
|
14363
|
-
|
|
14364
|
-
|
|
14365
|
-
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
|
|
14371
|
-
|
|
14372
|
-
|
|
14373
|
-
return true;
|
|
14374
|
-
} catch (error) {
|
|
14375
|
-
if ("ENOENT" !== error.code) throw error;
|
|
14376
|
-
await external_fs_.promises.writeFile(rootGitignorePath, `${ANVIL_GITIGNORE_ENTRY}\n`, "utf8");
|
|
14377
|
-
return true;
|
|
14371
|
+
async discardModeOnlyChanges(paths) {
|
|
14372
|
+
const discarded = [];
|
|
14373
|
+
for (const relativePath of paths)try {
|
|
14374
|
+
const headMode = await this.getHeadFileMode(relativePath);
|
|
14375
|
+
if ("100644" !== headMode && "100755" !== headMode) continue;
|
|
14376
|
+
const contentUnchanged = await this.hasSameContentAsHead(relativePath);
|
|
14377
|
+
if (!contentUnchanged) continue;
|
|
14378
|
+
await this.restoreExecutableBit(relativePath, "100755" === headMode);
|
|
14379
|
+
discarded.push(relativePath);
|
|
14380
|
+
} catch (e) {
|
|
14381
|
+
throw errors_createGitError.commandFailed("discard mode-only changes", e.message);
|
|
14382
|
+
}
|
|
14383
|
+
return discarded;
|
|
14378
14384
|
}
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14385
|
-
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
|
|
14389
|
-
|
|
14390
|
-
|
|
14391
|
-
|
|
14392
|
-
|
|
14393
|
-
|
|
14394
|
-
|
|
14395
|
-
|
|
14396
|
-
|
|
14397
|
-
|
|
14398
|
-
|
|
14399
|
-
if (commit) body.commit = commit;
|
|
14400
|
-
const resp = await fetch(`${anvilUrl}/ide/api/_/apps/${appId}/resolved-dependencies`, {
|
|
14401
|
-
method: "POST",
|
|
14402
|
-
headers: {
|
|
14403
|
-
Authorization: `Bearer ${token}`,
|
|
14404
|
-
"Content-Type": "application/json"
|
|
14405
|
-
},
|
|
14406
|
-
body: JSON.stringify(body)
|
|
14407
|
-
});
|
|
14408
|
-
if (!resp.ok) {
|
|
14409
|
-
const errorBody = await resp.text();
|
|
14410
|
-
throw new Error(formatFetchError("Failed to fetch dependencies", resp.status, errorBody));
|
|
14385
|
+
async discardStagedModeOnlyChanges(paths) {
|
|
14386
|
+
const discarded = [];
|
|
14387
|
+
for (const relativePath of paths)try {
|
|
14388
|
+
const headMode = await this.getHeadFileMode(relativePath);
|
|
14389
|
+
if ("100644" !== headMode && "100755" !== headMode) continue;
|
|
14390
|
+
const indexContentUnchanged = await this.hasSameIndexContentAsHead(relativePath);
|
|
14391
|
+
if (!indexContentUnchanged) continue;
|
|
14392
|
+
await this.git.raw([
|
|
14393
|
+
"reset",
|
|
14394
|
+
"-q",
|
|
14395
|
+
"HEAD",
|
|
14396
|
+
"--",
|
|
14397
|
+
relativePath
|
|
14398
|
+
]);
|
|
14399
|
+
await this.restoreExecutableBit(relativePath, "100755" === headMode);
|
|
14400
|
+
discarded.push(relativePath);
|
|
14401
|
+
} catch (e) {
|
|
14402
|
+
throw errors_createGitError.commandFailed("discard staged mode-only changes", e.message);
|
|
14403
|
+
}
|
|
14404
|
+
return discarded;
|
|
14411
14405
|
}
|
|
14412
|
-
|
|
14413
|
-
|
|
14414
|
-
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
});
|
|
14422
|
-
if (!response.resolved_dependencies.length) {
|
|
14423
|
-
await clearDepsCache(projectRoot);
|
|
14424
|
-
return {
|
|
14425
|
-
response,
|
|
14426
|
-
addedGitignoreEntry: false
|
|
14427
|
-
};
|
|
14406
|
+
async getHeadFileMode(relativePath) {
|
|
14407
|
+
const output = await this.git.raw([
|
|
14408
|
+
"ls-tree",
|
|
14409
|
+
"HEAD",
|
|
14410
|
+
"--",
|
|
14411
|
+
relativePath
|
|
14412
|
+
]);
|
|
14413
|
+
const match = output.match(/^(\d{6})\s/);
|
|
14414
|
+
return match?.[1] ?? null;
|
|
14428
14415
|
}
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
return external_path_default().join(projectRoot, ANVIL_DEPS_DIR);
|
|
14440
|
-
}
|
|
14441
|
-
function depsManifestPath(projectRoot) {
|
|
14442
|
-
return external_path_default().join(depsDir(projectRoot), ANVIL_DEPS_MANIFEST);
|
|
14443
|
-
}
|
|
14444
|
-
function isResolvedDependencyManifestEntry(value) {
|
|
14445
|
-
if (!value || "object" != typeof value) return false;
|
|
14446
|
-
const entry = value;
|
|
14447
|
-
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);
|
|
14448
|
-
}
|
|
14449
|
-
function isCachedDependencyManifestEntry(value) {
|
|
14450
|
-
if (!isResolvedDependencyManifestEntry(value)) return false;
|
|
14451
|
-
const entry = value;
|
|
14452
|
-
return "string" == typeof entry.package_name && entry.package_name.length > 0;
|
|
14453
|
-
}
|
|
14454
|
-
function isDepsCacheManifest(value) {
|
|
14455
|
-
if (!value || "object" != typeof value) return false;
|
|
14456
|
-
const manifest = value;
|
|
14457
|
-
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);
|
|
14458
|
-
}
|
|
14459
|
-
async function readDepsCacheManifest(projectRoot) {
|
|
14460
|
-
try {
|
|
14461
|
-
const raw = await promises_default().readFile(depsManifestPath(projectRoot), "utf-8");
|
|
14462
|
-
const parsed = JSON.parse(raw);
|
|
14463
|
-
return isDepsCacheManifest(parsed) ? parsed : void 0;
|
|
14464
|
-
} catch {
|
|
14465
|
-
return;
|
|
14416
|
+
async hasSameContentAsHead(relativePath) {
|
|
14417
|
+
const headHash = (await this.git.revparse([
|
|
14418
|
+
`HEAD:${relativePath}`
|
|
14419
|
+
])).trim();
|
|
14420
|
+
const worktreeHash = (await this.git.raw([
|
|
14421
|
+
"hash-object",
|
|
14422
|
+
"--",
|
|
14423
|
+
relativePath
|
|
14424
|
+
])).trim();
|
|
14425
|
+
return headHash === worktreeHash;
|
|
14466
14426
|
}
|
|
14467
|
-
|
|
14468
|
-
|
|
14469
|
-
|
|
14470
|
-
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
|
|
14475
|
-
}
|
|
14476
|
-
function getDependencyWatchSubscriptionsFromManifest(manifest) {
|
|
14477
|
-
if (!manifest) return [];
|
|
14478
|
-
const cachedByAppId = new Map(manifest.cached_dependencies.map((dep)=>[
|
|
14479
|
-
dep.app_id,
|
|
14480
|
-
dep
|
|
14481
|
-
]));
|
|
14482
|
-
return manifest.resolved_dependencies.flatMap((dep)=>{
|
|
14483
|
-
if (!dep.branch) return [];
|
|
14484
|
-
return [
|
|
14485
|
-
{
|
|
14486
|
-
appId: dep.app_id,
|
|
14487
|
-
branch: dep.branch,
|
|
14488
|
-
packageName: cachedByAppId.get(dep.app_id)?.package_name
|
|
14489
|
-
}
|
|
14490
|
-
];
|
|
14491
|
-
});
|
|
14492
|
-
}
|
|
14493
|
-
function normalizeAnvilYamlDependenciesFromContent(content) {
|
|
14494
|
-
const parsed = external_js_yaml_default().load(content);
|
|
14495
|
-
if (!parsed || "object" != typeof parsed || !Array.isArray(parsed.dependencies)) return [];
|
|
14496
|
-
return normalizeAnvilYamlDependenciesValue(parsed.dependencies);
|
|
14497
|
-
}
|
|
14498
|
-
function normalizeAnvilYamlDependenciesValue(value) {
|
|
14499
|
-
if (!Array.isArray(value)) return [];
|
|
14500
|
-
return value.map(normalizeForComparison);
|
|
14501
|
-
}
|
|
14502
|
-
async function readAnvilYamlDependencies(projectRoot) {
|
|
14503
|
-
try {
|
|
14504
|
-
const content = await promises_default().readFile(external_path_default().join(projectRoot, "anvil.yaml"), "utf-8");
|
|
14505
|
-
return normalizeAnvilYamlDependenciesFromContent(content);
|
|
14506
|
-
} catch {
|
|
14507
|
-
return [];
|
|
14427
|
+
async hasSameIndexContentAsHead(relativePath) {
|
|
14428
|
+
const headHash = (await this.git.revparse([
|
|
14429
|
+
`HEAD:${relativePath}`
|
|
14430
|
+
])).trim();
|
|
14431
|
+
const indexHash = (await this.git.revparse([
|
|
14432
|
+
`:${relativePath}`
|
|
14433
|
+
])).trim();
|
|
14434
|
+
return headHash === indexHash;
|
|
14508
14435
|
}
|
|
14509
|
-
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
|
|
14514
|
-
if (!manifest || !anvilYamlDependenciesEqual(sourceDependencies, manifest.source_dependencies)) return DEPENDENCY_CACHE_WARNING;
|
|
14515
|
-
if (0 === manifest.resolved_dependencies.length) return DEPENDENCY_CACHE_WARNING;
|
|
14516
|
-
const cachedByAppId = new Map(manifest.cached_dependencies.map((dependency)=>[
|
|
14517
|
-
dependency.app_id,
|
|
14518
|
-
dependency
|
|
14519
|
-
]));
|
|
14520
|
-
for (const dependency of manifest.resolved_dependencies){
|
|
14521
|
-
const cached = cachedByAppId.get(dependency.app_id);
|
|
14522
|
-
if (!cached || cached.commit_id !== dependency.commit_id) return DEPENDENCY_CACHE_WARNING;
|
|
14523
|
-
try {
|
|
14524
|
-
await promises_default().access(external_path_default().join(depsDir(projectRoot), cached.package_name, "anvil.yaml"));
|
|
14525
|
-
} catch {
|
|
14526
|
-
return DEPENDENCY_CACHE_WARNING;
|
|
14527
|
-
}
|
|
14436
|
+
async restoreExecutableBit(relativePath, executable) {
|
|
14437
|
+
const filePath = external_path_default().join(this.repoPath, relativePath);
|
|
14438
|
+
const stat = await external_fs_.promises.stat(filePath);
|
|
14439
|
+
const mode = executable ? 73 | stat.mode : -74 & stat.mode;
|
|
14440
|
+
if ((511 & stat.mode) !== (511 & mode)) await external_fs_.promises.chmod(filePath, mode);
|
|
14528
14441
|
}
|
|
14529
|
-
|
|
14530
|
-
|
|
14531
|
-
|
|
14532
|
-
|
|
14533
|
-
|
|
14534
|
-
|
|
14535
|
-
|
|
14536
|
-
|
|
14537
|
-
|
|
14538
|
-
|
|
14539
|
-
|
|
14540
|
-
|
|
14541
|
-
|
|
14542
|
-
|
|
14543
|
-
app_id: dep.app_id,
|
|
14544
|
-
commit_id: dep.commit_id,
|
|
14545
|
-
...dep.branch ? {
|
|
14546
|
-
branch: dep.branch
|
|
14547
|
-
} : {},
|
|
14548
|
-
package_name: packageName
|
|
14549
|
-
};
|
|
14550
|
-
}
|
|
14551
|
-
function safeDependencyPath(filePath, kind) {
|
|
14552
|
-
const normalizedPath = external_path_default().normalize(filePath);
|
|
14553
|
-
if (external_path_default().isAbsolute(normalizedPath) || normalizedPath.startsWith("..")) throw new Error(`Invalid dependency ${kind} path: ${filePath}`);
|
|
14554
|
-
return normalizedPath;
|
|
14555
|
-
}
|
|
14556
|
-
function dependencyPythonPath(name, kind) {
|
|
14557
|
-
const parts = name.split(".");
|
|
14558
|
-
if (0 === parts.length || parts.some((part)=>!part || "." === part || ".." === part || part.includes("/") || part.includes("\\"))) throw new Error(`Invalid dependency ${kind} name: ${name}`);
|
|
14559
|
-
return external_path_default().join(...parts);
|
|
14560
|
-
}
|
|
14561
|
-
async function writeDepToCache(projectRoot, dep) {
|
|
14562
|
-
const depDir = external_path_default().join(projectRoot, ANVIL_DEPS_DIR, dep.package_name);
|
|
14563
|
-
await promises_default().rm(depDir, {
|
|
14564
|
-
recursive: true,
|
|
14565
|
-
force: true
|
|
14566
|
-
});
|
|
14567
|
-
await promises_default().mkdir(external_path_default().join(depDir, "client_code"), {
|
|
14568
|
-
recursive: true
|
|
14569
|
-
});
|
|
14570
|
-
await promises_default().mkdir(external_path_default().join(depDir, "server_code"), {
|
|
14571
|
-
recursive: true
|
|
14572
|
-
});
|
|
14573
|
-
await promises_default().writeFile(external_path_default().join(depDir, "anvil.yaml"), external_js_yaml_default().dump({
|
|
14574
|
-
package_name: dep.package_name
|
|
14575
|
-
}));
|
|
14576
|
-
for (const mod of dep.modules ?? []){
|
|
14577
|
-
const modulePath = external_path_default().join(depDir, "client_code", dependencyPythonPath(mod.name, "module"));
|
|
14578
|
-
if (mod.is_package) {
|
|
14579
|
-
await promises_default().mkdir(modulePath, {
|
|
14580
|
-
recursive: true
|
|
14442
|
+
async hasUncommittedChanges() {
|
|
14443
|
+
const status = await this.getStatus();
|
|
14444
|
+
return !status.isClean;
|
|
14445
|
+
}
|
|
14446
|
+
async fetch(url, refSpec) {
|
|
14447
|
+
let attempt = 0;
|
|
14448
|
+
while(true)try {
|
|
14449
|
+
await this.git.fetch([
|
|
14450
|
+
url,
|
|
14451
|
+
refSpec
|
|
14452
|
+
]);
|
|
14453
|
+
if (attempt > 0) this.emit("fetch-retry-resolved", {
|
|
14454
|
+
attempts: attempt,
|
|
14455
|
+
outcome: "succeeded"
|
|
14581
14456
|
});
|
|
14582
|
-
|
|
14583
|
-
}
|
|
14584
|
-
const
|
|
14585
|
-
|
|
14586
|
-
|
|
14457
|
+
return;
|
|
14458
|
+
} catch (e) {
|
|
14459
|
+
const err = e;
|
|
14460
|
+
if (!isWantNotValidError(err)) {
|
|
14461
|
+
if (attempt > 0) this.emit("fetch-retry-resolved", {
|
|
14462
|
+
attempts: attempt,
|
|
14463
|
+
outcome: "failed"
|
|
14464
|
+
});
|
|
14465
|
+
throw errors_createGitError.fetchFailed(err.message);
|
|
14466
|
+
}
|
|
14467
|
+
attempt++;
|
|
14468
|
+
this.emit("fetch-retry", {
|
|
14469
|
+
attempt,
|
|
14470
|
+
delayMs: FETCH_RETRY_DELAY_MS,
|
|
14471
|
+
error: err.message
|
|
14587
14472
|
});
|
|
14588
|
-
|
|
14473
|
+
logger_logger.verbose(`git fetch race (attempt ${attempt}, sleeping ${FETCH_RETRY_DELAY_MS}ms): ${err.message}`);
|
|
14474
|
+
await (0, promises_namespaceObject.setTimeout)(FETCH_RETRY_DELAY_MS);
|
|
14589
14475
|
}
|
|
14590
14476
|
}
|
|
14591
|
-
|
|
14592
|
-
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
|
|
14605
|
-
|
|
14606
|
-
|
|
14477
|
+
async reset(ref, mode = "mixed") {
|
|
14478
|
+
await this.runGitCommandWithIndexLockRetry("reset", ()=>this.git.reset([
|
|
14479
|
+
`--${mode}`,
|
|
14480
|
+
ref
|
|
14481
|
+
]));
|
|
14482
|
+
}
|
|
14483
|
+
async checkout(paths) {
|
|
14484
|
+
await this.runGitCommandWithIndexLockRetry("checkout", ()=>this.git.checkout(paths));
|
|
14485
|
+
}
|
|
14486
|
+
async runGitCommandWithIndexLockRetry(command, operation) {
|
|
14487
|
+
let lastError;
|
|
14488
|
+
for(let attempt = 0; attempt <= GitService.INDEX_LOCK_RETRY_DELAYS_MS.length; attempt += 1)try {
|
|
14489
|
+
await operation();
|
|
14490
|
+
return;
|
|
14491
|
+
} catch (e) {
|
|
14492
|
+
lastError = e;
|
|
14493
|
+
if (!this.isIndexLockError(e) || attempt === GitService.INDEX_LOCK_RETRY_DELAYS_MS.length) throw errors_createGitError.commandFailed(command, e.message);
|
|
14494
|
+
await (0, promises_namespaceObject.setTimeout)(GitService.INDEX_LOCK_RETRY_DELAYS_MS[attempt]);
|
|
14607
14495
|
}
|
|
14496
|
+
throw errors_createGitError.commandFailed(command, lastError.message);
|
|
14608
14497
|
}
|
|
14609
|
-
|
|
14610
|
-
const
|
|
14611
|
-
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14621
|
-
await
|
|
14498
|
+
isIndexLockError(error) {
|
|
14499
|
+
const message = error.message ?? "";
|
|
14500
|
+
return message.includes("Unable to create") && message.includes("index.lock") && message.includes("File exists");
|
|
14501
|
+
}
|
|
14502
|
+
async stash(message) {
|
|
14503
|
+
try {
|
|
14504
|
+
const args = [
|
|
14505
|
+
"stash",
|
|
14506
|
+
"push",
|
|
14507
|
+
"--include-untracked"
|
|
14508
|
+
];
|
|
14509
|
+
if (message) args.push("-m", message);
|
|
14510
|
+
const result = await this.git.raw(args);
|
|
14511
|
+
return !result.includes("No local changes to save");
|
|
14512
|
+
} catch (e) {
|
|
14513
|
+
throw errors_createGitError.commandFailed("stash", e.message);
|
|
14622
14514
|
}
|
|
14623
14515
|
}
|
|
14624
|
-
|
|
14625
|
-
|
|
14626
|
-
|
|
14627
|
-
|
|
14628
|
-
|
|
14629
|
-
|
|
14516
|
+
async stashPop() {
|
|
14517
|
+
try {
|
|
14518
|
+
await this.git.raw([
|
|
14519
|
+
"stash",
|
|
14520
|
+
"pop"
|
|
14521
|
+
]);
|
|
14522
|
+
} catch (e) {
|
|
14523
|
+
throw errors_createGitError.commandFailed("stash pop", e.message);
|
|
14524
|
+
}
|
|
14630
14525
|
}
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
|
|
14526
|
+
async clean(files) {
|
|
14527
|
+
try {
|
|
14528
|
+
await this.git.clean(CleanOptions.FORCE, files);
|
|
14529
|
+
} catch (e) {
|
|
14530
|
+
throw errors_createGitError.commandFailed("clean", e.message);
|
|
14531
|
+
}
|
|
14637
14532
|
}
|
|
14638
|
-
|
|
14639
|
-
async function writeAllDepsToCache(projectRoot, response, options = {}) {
|
|
14640
|
-
const includeDocs = options.includeDocs ?? true;
|
|
14641
|
-
const manifest = await readDepsCacheManifest(projectRoot);
|
|
14642
|
-
const canReuseExistingCache = manifest?.includeDocs === includeDocs;
|
|
14643
|
-
if (!canReuseExistingCache) await clearDepsCache(projectRoot);
|
|
14644
|
-
const previousCached = canReuseExistingCache ? manifest.cached_dependencies : [];
|
|
14645
|
-
const resolvedAppIds = new Set(response.resolved_dependencies.map((dep)=>dep.app_id));
|
|
14646
|
-
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), {
|
|
14647
|
-
recursive: true,
|
|
14648
|
-
force: true
|
|
14649
|
-
});
|
|
14650
|
-
for (const dep of response.dependencies)await writeDepToCache(projectRoot, dep);
|
|
14651
|
-
const returnedByAppId = new Map(response.dependencies.map((dep)=>[
|
|
14652
|
-
dep.app_id,
|
|
14653
|
-
dep
|
|
14654
|
-
]));
|
|
14655
|
-
const previousByAppId = new Map(previousCached.map((dep)=>[
|
|
14656
|
-
dep.app_id,
|
|
14657
|
-
dep
|
|
14658
|
-
]));
|
|
14659
|
-
const cachedDependencies = response.resolved_dependencies.map((dep)=>{
|
|
14660
|
-
const returned = returnedByAppId.get(dep.app_id);
|
|
14661
|
-
if (returned) return cachedManifestEntry(returned, returned.package_name);
|
|
14662
|
-
const previous = previousByAppId.get(dep.app_id);
|
|
14663
|
-
if (!previous) return;
|
|
14664
|
-
return cachedManifestEntry(dep, previous.package_name);
|
|
14665
|
-
}).filter((dep)=>void 0 !== dep);
|
|
14666
|
-
const newManifest = {
|
|
14667
|
-
version: DEPS_MANIFEST_VERSION,
|
|
14668
|
-
includeDocs,
|
|
14669
|
-
source_dependencies: await readAnvilYamlDependencies(projectRoot),
|
|
14670
|
-
resolved_dependencies: response.resolved_dependencies,
|
|
14671
|
-
cached_dependencies: cachedDependencies
|
|
14672
|
-
};
|
|
14673
|
-
await promises_default().mkdir(depsDir(projectRoot), {
|
|
14674
|
-
recursive: true
|
|
14675
|
-
});
|
|
14676
|
-
await promises_default().writeFile(depsManifestPath(projectRoot), `${JSON.stringify(newManifest, null, 2)}\n`);
|
|
14677
|
-
}
|
|
14678
|
-
async function clearDepsCache(projectRoot) {
|
|
14679
|
-
await promises_default().rm(depsDir(projectRoot), {
|
|
14680
|
-
recursive: true,
|
|
14681
|
-
force: true
|
|
14682
|
-
});
|
|
14683
|
-
}
|
|
14684
|
-
async function getCachedPackages(projectRoot) {
|
|
14685
|
-
try {
|
|
14686
|
-
const entries = await promises_default().readdir(depsDir(projectRoot), {
|
|
14687
|
-
withFileTypes: true
|
|
14688
|
-
});
|
|
14689
|
-
return entries.filter((entry)=>entry.isDirectory()).map((entry)=>entry.name);
|
|
14690
|
-
} catch {
|
|
14691
|
-
return [];
|
|
14692
|
-
}
|
|
14693
|
-
}
|
|
14694
|
-
const external_timers_promises_namespaceObject = require("timers/promises");
|
|
14695
|
-
const WANT_NOT_VALID_RE = /\bwant\s+[0-9a-f]{7,40}\s+not valid\b/i;
|
|
14696
|
-
const FETCH_RETRY_DELAY_MS = 200;
|
|
14697
|
-
function isWantNotValidError(error) {
|
|
14698
|
-
return WANT_NOT_VALID_RE.test(error.message || "");
|
|
14699
|
-
}
|
|
14700
|
-
class GitService extends Emitter {
|
|
14701
|
-
git;
|
|
14702
|
-
repoPath;
|
|
14703
|
-
static INDEX_LOCK_RETRY_DELAYS_MS = [
|
|
14704
|
-
25,
|
|
14705
|
-
50,
|
|
14706
|
-
100,
|
|
14707
|
-
200
|
|
14708
|
-
];
|
|
14709
|
-
constructor(repoPath){
|
|
14710
|
-
super();
|
|
14711
|
-
this.repoPath = external_path_default().resolve(repoPath);
|
|
14712
|
-
this.git = esm_default(this.repoPath);
|
|
14713
|
-
}
|
|
14714
|
-
getGit() {
|
|
14715
|
-
return this.git;
|
|
14716
|
-
}
|
|
14717
|
-
getRepoPath() {
|
|
14718
|
-
return this.repoPath;
|
|
14719
|
-
}
|
|
14720
|
-
async getGitDir() {
|
|
14533
|
+
async diffNames(fromCommit, toCommit) {
|
|
14721
14534
|
try {
|
|
14722
|
-
const
|
|
14723
|
-
|
|
14724
|
-
|
|
14725
|
-
|
|
14726
|
-
|
|
14535
|
+
const diffResult = await this.git.diff([
|
|
14536
|
+
fromCommit,
|
|
14537
|
+
toCommit,
|
|
14538
|
+
"--name-only"
|
|
14539
|
+
]);
|
|
14540
|
+
const files = diffResult.split("\n").filter((f)=>f.trim());
|
|
14541
|
+
return files;
|
|
14727
14542
|
} catch (e) {
|
|
14728
|
-
throw errors_createGitError.commandFailed("
|
|
14543
|
+
throw errors_createGitError.commandFailed("diff", e.message);
|
|
14729
14544
|
}
|
|
14730
14545
|
}
|
|
14731
|
-
async
|
|
14546
|
+
async getAheadBehind(localRef, remoteRef) {
|
|
14732
14547
|
try {
|
|
14733
|
-
const
|
|
14734
|
-
"
|
|
14735
|
-
"
|
|
14548
|
+
const result = await this.git.raw([
|
|
14549
|
+
"rev-list",
|
|
14550
|
+
"--left-right",
|
|
14551
|
+
"--count",
|
|
14552
|
+
`${remoteRef}...${localRef}`
|
|
14736
14553
|
]);
|
|
14737
|
-
|
|
14738
|
-
return
|
|
14554
|
+
const [behind, ahead] = result.trim().split("\t").map(Number);
|
|
14555
|
+
return {
|
|
14556
|
+
ahead,
|
|
14557
|
+
behind
|
|
14558
|
+
};
|
|
14739
14559
|
} catch (e) {
|
|
14740
|
-
|
|
14741
|
-
throw createSyncError.detachedHead();
|
|
14560
|
+
throw errors_createGitError.commandFailed("rev-list", e.message);
|
|
14742
14561
|
}
|
|
14743
14562
|
}
|
|
14744
|
-
async
|
|
14563
|
+
async show(refPath) {
|
|
14745
14564
|
try {
|
|
14746
|
-
const
|
|
14747
|
-
|
|
14748
|
-
])
|
|
14749
|
-
return
|
|
14565
|
+
const content = await this.git.show([
|
|
14566
|
+
refPath
|
|
14567
|
+
]);
|
|
14568
|
+
return content;
|
|
14750
14569
|
} catch (e) {
|
|
14751
|
-
throw errors_createGitError.commandFailed("
|
|
14570
|
+
throw errors_createGitError.commandFailed("show", e.message);
|
|
14752
14571
|
}
|
|
14753
14572
|
}
|
|
14754
|
-
async
|
|
14573
|
+
async push(url, refSpec, force = false) {
|
|
14755
14574
|
try {
|
|
14756
|
-
const
|
|
14757
|
-
"
|
|
14758
|
-
|
|
14759
|
-
|
|
14760
|
-
|
|
14761
|
-
|
|
14762
|
-
"
|
|
14763
|
-
|
|
14764
|
-
|
|
14765
|
-
|
|
14766
|
-
|
|
14767
|
-
message
|
|
14768
|
-
};
|
|
14575
|
+
const args = force ? [
|
|
14576
|
+
"push",
|
|
14577
|
+
"--force",
|
|
14578
|
+
url,
|
|
14579
|
+
refSpec
|
|
14580
|
+
] : [
|
|
14581
|
+
"push",
|
|
14582
|
+
url,
|
|
14583
|
+
refSpec
|
|
14584
|
+
];
|
|
14585
|
+
await this.git.raw(args);
|
|
14769
14586
|
} catch (e) {
|
|
14770
|
-
throw errors_createGitError.commandFailed("
|
|
14587
|
+
throw errors_createGitError.commandFailed("push", e.message);
|
|
14771
14588
|
}
|
|
14772
14589
|
}
|
|
14773
|
-
async
|
|
14590
|
+
async rebase(onto) {
|
|
14774
14591
|
try {
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
}).map((file)=>file.path);
|
|
14780
|
-
return {
|
|
14781
|
-
isClean: status.isClean(),
|
|
14782
|
-
modified: status.modified,
|
|
14783
|
-
notAdded: status.not_added,
|
|
14784
|
-
created: status.created,
|
|
14785
|
-
deleted: status.deleted,
|
|
14786
|
-
staged,
|
|
14787
|
-
renamed: status.renamed || []
|
|
14788
|
-
};
|
|
14592
|
+
await this.git.raw([
|
|
14593
|
+
"rebase",
|
|
14594
|
+
onto
|
|
14595
|
+
]);
|
|
14789
14596
|
} catch (e) {
|
|
14790
|
-
throw errors_createGitError.commandFailed("
|
|
14597
|
+
throw errors_createGitError.commandFailed("rebase", e.message);
|
|
14791
14598
|
}
|
|
14792
14599
|
}
|
|
14793
|
-
async
|
|
14794
|
-
|
|
14795
|
-
|
|
14796
|
-
|
|
14797
|
-
|
|
14798
|
-
|
|
14799
|
-
if (!contentUnchanged) continue;
|
|
14800
|
-
await this.restoreExecutableBit(relativePath, "100755" === headMode);
|
|
14801
|
-
discarded.push(relativePath);
|
|
14600
|
+
async rebaseAbort() {
|
|
14601
|
+
try {
|
|
14602
|
+
await this.git.raw([
|
|
14603
|
+
"rebase",
|
|
14604
|
+
"--abort"
|
|
14605
|
+
]);
|
|
14802
14606
|
} catch (e) {
|
|
14803
|
-
throw errors_createGitError.commandFailed("
|
|
14607
|
+
throw errors_createGitError.commandFailed("rebase --abort", e.message);
|
|
14804
14608
|
}
|
|
14805
|
-
return discarded;
|
|
14806
14609
|
}
|
|
14807
|
-
async
|
|
14808
|
-
|
|
14809
|
-
|
|
14810
|
-
|
|
14811
|
-
|
|
14812
|
-
|
|
14813
|
-
|
|
14610
|
+
async isAncestor(ancestorRef, descendantRef) {
|
|
14611
|
+
try {
|
|
14612
|
+
const mergeBase = (await this.git.raw([
|
|
14613
|
+
"merge-base",
|
|
14614
|
+
ancestorRef,
|
|
14615
|
+
descendantRef
|
|
14616
|
+
])).trim();
|
|
14617
|
+
const ancestorCommit = (await this.git.revparse([
|
|
14618
|
+
ancestorRef
|
|
14619
|
+
])).trim();
|
|
14620
|
+
return mergeBase === ancestorCommit;
|
|
14621
|
+
} catch {
|
|
14622
|
+
return false;
|
|
14623
|
+
}
|
|
14624
|
+
}
|
|
14625
|
+
async mergeFastForward(ref) {
|
|
14626
|
+
await this.runGitCommandWithIndexLockRetry("merge --ff-only", ()=>this.git.raw([
|
|
14627
|
+
"merge",
|
|
14628
|
+
"--ff-only",
|
|
14629
|
+
ref
|
|
14630
|
+
]));
|
|
14631
|
+
}
|
|
14632
|
+
async deleteRef(ref) {
|
|
14633
|
+
try {
|
|
14814
14634
|
await this.git.raw([
|
|
14815
|
-
"
|
|
14816
|
-
"-
|
|
14817
|
-
|
|
14818
|
-
"--",
|
|
14819
|
-
relativePath
|
|
14635
|
+
"update-ref",
|
|
14636
|
+
"-d",
|
|
14637
|
+
ref
|
|
14820
14638
|
]);
|
|
14821
|
-
|
|
14822
|
-
|
|
14639
|
+
} catch (e) {}
|
|
14640
|
+
}
|
|
14641
|
+
async getRemotes() {
|
|
14642
|
+
try {
|
|
14643
|
+
const remotes = await this.git.getRemotes(true);
|
|
14644
|
+
return remotes.map((r)=>({
|
|
14645
|
+
name: r.name,
|
|
14646
|
+
fetchUrl: r.refs.fetch
|
|
14647
|
+
}));
|
|
14823
14648
|
} catch (e) {
|
|
14824
|
-
throw errors_createGitError.commandFailed("
|
|
14649
|
+
throw errors_createGitError.commandFailed("remote", e.message);
|
|
14825
14650
|
}
|
|
14826
|
-
return discarded;
|
|
14827
14651
|
}
|
|
14828
|
-
async
|
|
14829
|
-
const
|
|
14830
|
-
|
|
14831
|
-
"HEAD",
|
|
14832
|
-
"--",
|
|
14833
|
-
relativePath
|
|
14834
|
-
]);
|
|
14835
|
-
const match = output.match(/^(\d{6})\s/);
|
|
14836
|
-
return match?.[1] ?? null;
|
|
14652
|
+
async isGitInitialized() {
|
|
14653
|
+
const gitPath = external_path_default().join(this.repoPath, ".git");
|
|
14654
|
+
return external_fs_.existsSync(gitPath);
|
|
14837
14655
|
}
|
|
14838
|
-
async
|
|
14839
|
-
const
|
|
14840
|
-
|
|
14841
|
-
|
|
14842
|
-
|
|
14843
|
-
|
|
14844
|
-
|
|
14845
|
-
|
|
14846
|
-
|
|
14847
|
-
|
|
14656
|
+
async removeEmptyDirectories(removedFiles) {
|
|
14657
|
+
const directoriesToCheck = new Set();
|
|
14658
|
+
for (const file of removedFiles){
|
|
14659
|
+
let dir = external_path_default().dirname(file);
|
|
14660
|
+
while(dir && "." !== dir){
|
|
14661
|
+
directoriesToCheck.add(dir);
|
|
14662
|
+
const parentDir = external_path_default().dirname(dir);
|
|
14663
|
+
if (parentDir === dir) break;
|
|
14664
|
+
dir = parentDir;
|
|
14665
|
+
}
|
|
14666
|
+
}
|
|
14667
|
+
const sortedDirs = Array.from(directoriesToCheck).sort((a, b)=>b.split("/").length - a.split("/").length);
|
|
14668
|
+
for (const dir of sortedDirs)try {
|
|
14669
|
+
const dirPath = external_path_default().join(this.repoPath, dir);
|
|
14670
|
+
if (!external_fs_.existsSync(dirPath)) continue;
|
|
14671
|
+
const entries = await external_fs_.promises.readdir(dirPath);
|
|
14672
|
+
if (0 === entries.length) await external_fs_.promises.rmdir(dirPath);
|
|
14673
|
+
} catch (error) {}
|
|
14848
14674
|
}
|
|
14849
|
-
|
|
14850
|
-
|
|
14851
|
-
|
|
14852
|
-
|
|
14853
|
-
const
|
|
14854
|
-
|
|
14855
|
-
|
|
14856
|
-
|
|
14675
|
+
}
|
|
14676
|
+
async function getStagedFileChanges(git, gitService) {
|
|
14677
|
+
try {
|
|
14678
|
+
const status = await git.status();
|
|
14679
|
+
const renames = status.renamed || [];
|
|
14680
|
+
const stagedFiles = status.files.filter((f)=>" " !== f.index && "?" !== f.index);
|
|
14681
|
+
const stagedModeOnlyPaths = new Set(gitService ? await gitService.discardStagedModeOnlyChanges(stagedFiles.filter((f)=>"R" !== f.index && "D" !== f.index).map((f)=>f.path)) : []);
|
|
14682
|
+
const stagedRenames = [];
|
|
14683
|
+
for (const f of stagedFiles)if ("R" === f.index) {
|
|
14684
|
+
const rename = renames.find((r)=>r.to === f.path);
|
|
14685
|
+
if (rename) stagedRenames.push({
|
|
14686
|
+
path: rename.to,
|
|
14687
|
+
type: "rename",
|
|
14688
|
+
from: rename.from
|
|
14689
|
+
});
|
|
14690
|
+
}
|
|
14691
|
+
const stagedRenamedFromPaths = new Set(stagedRenames.map((r)=>r.from));
|
|
14692
|
+
const stagedRenamedToPaths = new Set(stagedRenames.map((r)=>r.path));
|
|
14693
|
+
const changes = [];
|
|
14694
|
+
for (const f of stagedFiles){
|
|
14695
|
+
if ("R" !== f.index) {
|
|
14696
|
+
if (!stagedModeOnlyPaths.has(f.path)) {
|
|
14697
|
+
if (!(stagedRenamedFromPaths.has(f.path) || stagedRenamedToPaths.has(f.path))) if ("D" === f.index) changes.push({
|
|
14698
|
+
path: f.path,
|
|
14699
|
+
type: "unlink"
|
|
14700
|
+
});
|
|
14701
|
+
else if ("A" === f.index || "?" === f.index) changes.push({
|
|
14702
|
+
path: f.path,
|
|
14703
|
+
type: "add"
|
|
14704
|
+
});
|
|
14705
|
+
else changes.push({
|
|
14706
|
+
path: f.path,
|
|
14707
|
+
type: "change"
|
|
14708
|
+
});
|
|
14709
|
+
}
|
|
14710
|
+
}
|
|
14711
|
+
}
|
|
14712
|
+
changes.push(...stagedRenames);
|
|
14713
|
+
return changes;
|
|
14714
|
+
} catch (e) {
|
|
14715
|
+
throw errors_createGitError.commandFailed("status", e.message);
|
|
14857
14716
|
}
|
|
14858
|
-
|
|
14859
|
-
|
|
14860
|
-
|
|
14861
|
-
|
|
14862
|
-
|
|
14717
|
+
}
|
|
14718
|
+
async function getCurrentOrFallbackBranchName(git) {
|
|
14719
|
+
const branchRef = (await git.revparse([
|
|
14720
|
+
"--abbrev-ref",
|
|
14721
|
+
"HEAD"
|
|
14722
|
+
])).trim();
|
|
14723
|
+
if ("HEAD" !== branchRef) return branchRef;
|
|
14724
|
+
const [localBranches, remoteBranches] = await Promise.all([
|
|
14725
|
+
git.raw([
|
|
14726
|
+
"branch",
|
|
14727
|
+
"--format=%(refname:short)",
|
|
14728
|
+
"--contains",
|
|
14729
|
+
"HEAD"
|
|
14730
|
+
]),
|
|
14731
|
+
git.raw([
|
|
14732
|
+
"branch",
|
|
14733
|
+
"-r",
|
|
14734
|
+
"--format=%(refname:short)",
|
|
14735
|
+
"--contains",
|
|
14736
|
+
"HEAD"
|
|
14737
|
+
])
|
|
14738
|
+
]);
|
|
14739
|
+
const branchNames = [
|
|
14740
|
+
...parseBranchList(localBranches),
|
|
14741
|
+
...parseBranchList(remoteBranches).filter((branchName)=>!branchName.endsWith("/HEAD")).map((branchName)=>branchName.replace(/^[^/]+\//, ""))
|
|
14742
|
+
];
|
|
14743
|
+
const branchSet = new Set(branchNames);
|
|
14744
|
+
if (branchSet.has("master")) return "master";
|
|
14745
|
+
if (branchSet.has("main")) return "main";
|
|
14746
|
+
return "master";
|
|
14747
|
+
}
|
|
14748
|
+
function parseBranchList(output) {
|
|
14749
|
+
return output.split(/\r?\n/).map((line)=>line.trim()).filter(Boolean);
|
|
14750
|
+
}
|
|
14751
|
+
function anvil_api_getDefaultAnvilUrl() {
|
|
14752
|
+
return resolveAnvilUrl();
|
|
14753
|
+
}
|
|
14754
|
+
async function validateAppId(appId, anvilUrl = anvil_api_getDefaultAnvilUrl(), username) {
|
|
14755
|
+
const authToken = await auth_getValidAuthToken(anvilUrl, username);
|
|
14756
|
+
try {
|
|
14757
|
+
const resp = await fetch(`${anvilUrl}/ide/api/_/apps/validate-app-id`, {
|
|
14758
|
+
method: "POST",
|
|
14759
|
+
headers: {
|
|
14760
|
+
Authorization: `Bearer ${authToken}`,
|
|
14761
|
+
"Content-Type": "application/json"
|
|
14762
|
+
},
|
|
14763
|
+
body: JSON.stringify({
|
|
14764
|
+
app_id: appId
|
|
14765
|
+
})
|
|
14766
|
+
});
|
|
14767
|
+
const data = await resp.json();
|
|
14768
|
+
if (resp.ok) return {
|
|
14769
|
+
valid: data.valid ?? false,
|
|
14770
|
+
app_name: data.app_name
|
|
14771
|
+
};
|
|
14772
|
+
if (401 === resp.status) throw errors_createAuthError.invalid("Authentication failed");
|
|
14773
|
+
if (403 === resp.status) throw createAppError.accessDenied(appId);
|
|
14774
|
+
if (404 === resp.status) throw createAppError.notFound(appId);
|
|
14775
|
+
logger_logger.debug(`validate-app-id failed: ${resp.status} ${resp.statusText}`);
|
|
14776
|
+
return {
|
|
14777
|
+
valid: false,
|
|
14778
|
+
error: data.error || `Server error: ${resp.status}`
|
|
14779
|
+
};
|
|
14780
|
+
} catch (error) {
|
|
14781
|
+
if (error.type) throw error;
|
|
14782
|
+
throw errors_createNetworkError.network(error.message);
|
|
14863
14783
|
}
|
|
14864
|
-
|
|
14865
|
-
|
|
14866
|
-
|
|
14784
|
+
}
|
|
14785
|
+
async function listAppsForCheckout(options = {}) {
|
|
14786
|
+
const anvilUrl = options.anvilUrl ?? anvil_api_getDefaultAnvilUrl();
|
|
14787
|
+
const authToken = await auth_getValidAuthToken(anvilUrl, options.username);
|
|
14788
|
+
const params = new URLSearchParams();
|
|
14789
|
+
if ("number" == typeof options.limit) params.set("limit", String(options.limit));
|
|
14790
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
14791
|
+
if (options.q && options.q.trim()) params.set("q", options.q.trim());
|
|
14792
|
+
const query = params.toString();
|
|
14793
|
+
const url = `${anvilUrl}/ide/api/_/apps${query ? `?${query}` : ""}`;
|
|
14794
|
+
try {
|
|
14795
|
+
const resp = await fetch(url, {
|
|
14796
|
+
method: "GET",
|
|
14797
|
+
headers: {
|
|
14798
|
+
Authorization: `Bearer ${authToken}`
|
|
14799
|
+
},
|
|
14800
|
+
signal: options.signal
|
|
14801
|
+
});
|
|
14802
|
+
if (!resp.ok) {
|
|
14803
|
+
if (401 === resp.status) throw errors_createAuthError.invalid("Authentication failed");
|
|
14804
|
+
throw errors_createNetworkError.server(resp.status, resp.statusText);
|
|
14805
|
+
}
|
|
14806
|
+
const data = await resp.json();
|
|
14807
|
+
return {
|
|
14808
|
+
apps: Array.isArray(data.apps) ? data.apps : [],
|
|
14809
|
+
next_cursor: "string" == typeof data.next_cursor ? data.next_cursor : null
|
|
14810
|
+
};
|
|
14811
|
+
} catch (error) {
|
|
14812
|
+
if (error.type) throw error;
|
|
14813
|
+
throw errors_createNetworkError.network(error.message);
|
|
14867
14814
|
}
|
|
14868
|
-
|
|
14869
|
-
|
|
14870
|
-
|
|
14871
|
-
|
|
14872
|
-
|
|
14873
|
-
|
|
14874
|
-
|
|
14875
|
-
|
|
14876
|
-
|
|
14877
|
-
|
|
14878
|
-
|
|
14879
|
-
|
|
14880
|
-
|
|
14881
|
-
|
|
14882
|
-
|
|
14883
|
-
|
|
14884
|
-
|
|
14885
|
-
|
|
14815
|
+
}
|
|
14816
|
+
function getGitFetchUrl(appId, authToken, gitUrl = anvil_api_getDefaultAnvilUrl()) {
|
|
14817
|
+
const url = new URL(gitUrl);
|
|
14818
|
+
const encodedToken = encodeURIComponent(authToken);
|
|
14819
|
+
return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git?no_freeze=true&q=`;
|
|
14820
|
+
}
|
|
14821
|
+
function getGitPushUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
|
|
14822
|
+
const url = new URL(anvilUrl);
|
|
14823
|
+
const encodedToken = encodeURIComponent(authToken);
|
|
14824
|
+
return `${url.protocol}//git:${encodedToken}@${url.hostname}${url.port ? ":" + url.port : ""}/git/${appId}.git`;
|
|
14825
|
+
}
|
|
14826
|
+
function getWebSocketUrl(appId, authToken, anvilUrl = anvil_api_getDefaultAnvilUrl()) {
|
|
14827
|
+
return anvilUrl.replace(/^http/, "ws") + `/ide/api/_/apps/${appId}/ws?access_token=${authToken}`;
|
|
14828
|
+
}
|
|
14829
|
+
async function getLatestVersion() {
|
|
14830
|
+
try {
|
|
14831
|
+
const response = await fetch("https://registry.npmjs.org/@anvil-works/anvil-cli/latest");
|
|
14832
|
+
if (!response.ok) return null;
|
|
14833
|
+
const data = await response.json();
|
|
14834
|
+
return data.version;
|
|
14835
|
+
} catch (e) {
|
|
14836
|
+
return null;
|
|
14837
|
+
}
|
|
14838
|
+
}
|
|
14839
|
+
function filterCandidates(candidates, explicitUrl, explicitUsername) {
|
|
14840
|
+
let filtered = candidates;
|
|
14841
|
+
if (explicitUrl) {
|
|
14842
|
+
const normalizedExplicit = normalizeAnvilUrl(explicitUrl);
|
|
14843
|
+
filtered = filtered.filter((c)=>c.detectedUrl && normalizeAnvilUrl(c.detectedUrl) === normalizedExplicit);
|
|
14844
|
+
}
|
|
14845
|
+
if (explicitUsername) filtered = filtered.filter((c)=>!c.detectedUsername || c.detectedUsername === explicitUsername);
|
|
14846
|
+
return filtered;
|
|
14847
|
+
}
|
|
14848
|
+
function formatCandidateLabel(candidate) {
|
|
14849
|
+
const parts = [
|
|
14850
|
+
candidate.appId
|
|
14851
|
+
];
|
|
14852
|
+
if (candidate.detectedUrl) if (candidate.detectedUsername) parts.push(`(${candidate.detectedUsername} on ${candidate.detectedUrl})`);
|
|
14853
|
+
else parts.push(`(${candidate.detectedUrl})`);
|
|
14854
|
+
parts.push(`- ${candidate.description}`);
|
|
14855
|
+
return parts.join(" ");
|
|
14856
|
+
}
|
|
14857
|
+
function lookupRemoteInfoForAppId(appId, detectedRemotes) {
|
|
14858
|
+
const matches = detectedRemotes.filter((c)=>c.appId === appId);
|
|
14859
|
+
if (0 === matches.length) return {};
|
|
14860
|
+
const withUsername = matches.find((c)=>c.detectedUsername);
|
|
14861
|
+
if (withUsername) return {
|
|
14862
|
+
detectedUrl: withUsername.detectedUrl,
|
|
14863
|
+
detectedUsername: withUsername.detectedUsername
|
|
14864
|
+
};
|
|
14865
|
+
return {
|
|
14866
|
+
detectedUrl: matches[0].detectedUrl,
|
|
14867
|
+
detectedUsername: matches[0].detectedUsername
|
|
14868
|
+
};
|
|
14869
|
+
}
|
|
14870
|
+
async function detectAppIdsFromAllRemotes(repoPath) {
|
|
14871
|
+
const git = esm_default(repoPath);
|
|
14872
|
+
const out = [];
|
|
14873
|
+
try {
|
|
14874
|
+
const remotes = await git.getRemotes(true);
|
|
14875
|
+
for (const remote of remotes){
|
|
14876
|
+
const httpMatch = remote.refs.fetch?.match(/(?:http|https):\/\/(?:[^@]+@)?([^:\/]+)(?::\d+)?\/git\/([A-Z0-9]+)\.git/);
|
|
14877
|
+
if (httpMatch) {
|
|
14878
|
+
const [, host, detectedAppId] = httpMatch;
|
|
14879
|
+
out.push({
|
|
14880
|
+
appId: detectedAppId,
|
|
14881
|
+
source: "remote",
|
|
14882
|
+
description: `Git remote '${remote.name}'`,
|
|
14883
|
+
detectedUrl: normalizeAnvilUrl(host)
|
|
14884
|
+
});
|
|
14885
|
+
continue;
|
|
14886
|
+
}
|
|
14887
|
+
const sshMatch = remote.refs.fetch?.match(/ssh:\/\/([^@]+)@([^:]+):(\d+)\/(?:git\/)?([A-Z0-9]+)\.git/);
|
|
14888
|
+
if (sshMatch) {
|
|
14889
|
+
const [, usernamePart, host, , detectedAppId] = sshMatch;
|
|
14890
|
+
const detectedUsername = usernamePart.includes("%") ? decodeURIComponent(usernamePart) : usernamePart;
|
|
14891
|
+
const finalUsername = "git" !== detectedUsername ? detectedUsername : void 0;
|
|
14892
|
+
out.push({
|
|
14893
|
+
appId: detectedAppId,
|
|
14894
|
+
source: "remote",
|
|
14895
|
+
description: `Git remote '${remote.name}' (SSH)`,
|
|
14896
|
+
detectedUrl: normalizeAnvilUrl(host),
|
|
14897
|
+
detectedUsername: finalUsername
|
|
14886
14898
|
});
|
|
14887
|
-
throw errors_createGitError.fetchFailed(err.message);
|
|
14888
14899
|
}
|
|
14889
|
-
attempt++;
|
|
14890
|
-
this.emit("fetch-retry", {
|
|
14891
|
-
attempt,
|
|
14892
|
-
delayMs: FETCH_RETRY_DELAY_MS,
|
|
14893
|
-
error: err.message
|
|
14894
|
-
});
|
|
14895
|
-
logger_logger.verbose(`git fetch race (attempt ${attempt}, sleeping ${FETCH_RETRY_DELAY_MS}ms): ${err.message}`);
|
|
14896
|
-
await (0, external_timers_promises_namespaceObject.setTimeout)(FETCH_RETRY_DELAY_MS);
|
|
14897
14900
|
}
|
|
14898
|
-
}
|
|
14899
|
-
|
|
14900
|
-
|
|
14901
|
-
|
|
14902
|
-
|
|
14903
|
-
|
|
14904
|
-
|
|
14905
|
-
|
|
14906
|
-
|
|
14907
|
-
|
|
14908
|
-
|
|
14909
|
-
|
|
14910
|
-
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
14914
|
-
|
|
14915
|
-
|
|
14916
|
-
|
|
14901
|
+
} catch (_e) {}
|
|
14902
|
+
return out;
|
|
14903
|
+
}
|
|
14904
|
+
async function detectAppIdsByCommitLookup(repoPath, options) {
|
|
14905
|
+
const anvilUrl = options.anvilUrl || resolveAnvilUrl();
|
|
14906
|
+
const username = options.username;
|
|
14907
|
+
const git = esm_default(repoPath);
|
|
14908
|
+
const out = [];
|
|
14909
|
+
try {
|
|
14910
|
+
const authToken = await auth_getValidAuthToken(anvilUrl, username);
|
|
14911
|
+
const commitId = (await git.revparse([
|
|
14912
|
+
"HEAD"
|
|
14913
|
+
]))?.trim();
|
|
14914
|
+
if (!commitId) return out;
|
|
14915
|
+
const branchName = await getCurrentOrFallbackBranchName(git);
|
|
14916
|
+
const resp = await fetch(`${anvilUrl}/ide/api/_/apps/lookup-by-commit`, {
|
|
14917
|
+
method: "POST",
|
|
14918
|
+
headers: {
|
|
14919
|
+
Authorization: `Bearer ${authToken}`,
|
|
14920
|
+
"Content-Type": "application/json"
|
|
14921
|
+
},
|
|
14922
|
+
body: JSON.stringify({
|
|
14923
|
+
commit_id: commitId,
|
|
14924
|
+
branch_name: branchName
|
|
14925
|
+
})
|
|
14926
|
+
});
|
|
14927
|
+
logger_logger.debug(`Reverse lookup by commit ${commitId} on branch ${branchName} returned ${resp.status}`);
|
|
14928
|
+
if (!resp.ok) {
|
|
14929
|
+
const errorBody = await resp.text();
|
|
14930
|
+
logger_logger.debug(`lookup-by-commit error: ${errorBody}`);
|
|
14917
14931
|
}
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
-
|
|
14921
|
-
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
"stash",
|
|
14928
|
-
"push",
|
|
14929
|
-
"--include-untracked"
|
|
14930
|
-
];
|
|
14931
|
-
if (message) args.push("-m", message);
|
|
14932
|
-
const result = await this.git.raw(args);
|
|
14933
|
-
return !result.includes("No local changes to save");
|
|
14934
|
-
} catch (e) {
|
|
14935
|
-
throw errors_createGitError.commandFailed("stash", e.message);
|
|
14932
|
+
if (resp.ok) {
|
|
14933
|
+
const data = await resp.json();
|
|
14934
|
+
logger_logger.debug(`data ${JSON.stringify(data)}`);
|
|
14935
|
+
const apps = data?.apps ?? [];
|
|
14936
|
+
for (const app of apps)if (app?.app_id) out.push({
|
|
14937
|
+
appId: app.app_id,
|
|
14938
|
+
source: "remote",
|
|
14939
|
+
description: `Reverse lookup by commit${app.branch ? ` on branch '${app.branch}'` : ""}`
|
|
14940
|
+
});
|
|
14936
14941
|
}
|
|
14942
|
+
} catch (_e) {}
|
|
14943
|
+
return out;
|
|
14944
|
+
}
|
|
14945
|
+
const external_fs_promises_namespaceObject = require("fs/promises");
|
|
14946
|
+
var external_fs_promises_default = /*#__PURE__*/ __webpack_require__.n(external_fs_promises_namespaceObject);
|
|
14947
|
+
function normalizeLineEndings(content) {
|
|
14948
|
+
return content.replace(/\r\n/g, "\n");
|
|
14949
|
+
}
|
|
14950
|
+
function pythonifyName(name) {
|
|
14951
|
+
return name.replace(/[^A-z0-9]/g, "_").replace(/^[0-9]/, "_$&");
|
|
14952
|
+
}
|
|
14953
|
+
function extractPythonName(relativePath, skipParts = 1) {
|
|
14954
|
+
const parts = relativePath.slice(0, -3).split("/");
|
|
14955
|
+
const isPackage = relativePath.endsWith("__init__.py");
|
|
14956
|
+
const relevantParts = isPackage ? parts.slice(skipParts, -1) : parts.slice(skipParts);
|
|
14957
|
+
return relevantParts.map(pythonifyName).join(".");
|
|
14958
|
+
}
|
|
14959
|
+
function deepEqual(a, b) {
|
|
14960
|
+
if (a === b) return true;
|
|
14961
|
+
if (null == a || null == b) return false;
|
|
14962
|
+
if (typeof a !== typeof b) return false;
|
|
14963
|
+
if ("object" != typeof a) return a === b;
|
|
14964
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
14965
|
+
if (Array.isArray(a)) {
|
|
14966
|
+
if (a.length !== b.length) return false;
|
|
14967
|
+
return a.every((item, index)=>deepEqual(item, b[index]));
|
|
14937
14968
|
}
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
14945
|
-
|
|
14946
|
-
|
|
14969
|
+
const keysA = Object.keys(a);
|
|
14970
|
+
const keysB = Object.keys(b);
|
|
14971
|
+
if (keysA.length !== keysB.length) return false;
|
|
14972
|
+
return keysA.every((key)=>deepEqual(a[key], b[key]));
|
|
14973
|
+
}
|
|
14974
|
+
function parseHtmlWithFrontmatter(htmlContent) {
|
|
14975
|
+
const frontmatterPattern = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
|
|
14976
|
+
const match = htmlContent.match(frontmatterPattern);
|
|
14977
|
+
if (match) {
|
|
14978
|
+
const yamlStr = match[1];
|
|
14979
|
+
const html = match[2];
|
|
14980
|
+
const frontmatter = yamlStr ? external_js_yaml_default().load(yamlStr) ?? {} : {};
|
|
14981
|
+
return {
|
|
14982
|
+
frontmatter,
|
|
14983
|
+
html
|
|
14984
|
+
};
|
|
14947
14985
|
}
|
|
14948
|
-
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14986
|
+
return {
|
|
14987
|
+
frontmatter: {},
|
|
14988
|
+
html: htmlContent
|
|
14989
|
+
};
|
|
14990
|
+
}
|
|
14991
|
+
function formatHttpError(prefix, status, body) {
|
|
14992
|
+
if (!body) return `${prefix}: ${status}`;
|
|
14993
|
+
try {
|
|
14994
|
+
const parsed = JSON.parse(body);
|
|
14995
|
+
if ("string" == typeof parsed.error && parsed.error.trim()) return `${prefix}: ${parsed.error} (${status})`;
|
|
14996
|
+
} catch {}
|
|
14997
|
+
return `${prefix}: ${status} ${body}`;
|
|
14998
|
+
}
|
|
14999
|
+
const ANVIL_GITIGNORE_ENTRY = "/.anvil/";
|
|
15000
|
+
async function ensureProjectGitignoreIgnoresAnvilDir(projectRoot) {
|
|
15001
|
+
const rootGitignorePath = external_path_default().join(projectRoot, ".gitignore");
|
|
15002
|
+
try {
|
|
15003
|
+
const existing = await external_fs_.promises.readFile(rootGitignorePath, "utf8");
|
|
15004
|
+
if (existing.includes(ANVIL_GITIGNORE_ENTRY)) return false;
|
|
15005
|
+
const prefix = existing.endsWith("\n") || 0 === existing.length ? "" : "\n";
|
|
15006
|
+
await external_fs_.promises.writeFile(rootGitignorePath, `${existing}${prefix}${ANVIL_GITIGNORE_ENTRY}\n`, "utf8");
|
|
15007
|
+
return true;
|
|
15008
|
+
} catch (error) {
|
|
15009
|
+
if ("ENOENT" !== error.code) throw error;
|
|
15010
|
+
await external_fs_.promises.writeFile(rootGitignorePath, `${ANVIL_GITIGNORE_ENTRY}\n`, "utf8");
|
|
15011
|
+
return true;
|
|
14954
15012
|
}
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
14962
|
-
|
|
14963
|
-
|
|
14964
|
-
|
|
14965
|
-
|
|
14966
|
-
|
|
15013
|
+
}
|
|
15014
|
+
const ANVIL_DEPS_DIR = ".anvil/deps";
|
|
15015
|
+
const ANVIL_DEPS_MANIFEST = "manifest.json";
|
|
15016
|
+
const DEPS_MANIFEST_VERSION = 2;
|
|
15017
|
+
const DEPENDENCY_CACHE_WARNING = "Dependency cache is missing or stale. Run `anvil deps fetch` before validation.";
|
|
15018
|
+
async function fetchAllDependencies(appId, anvilUrl, options = {}) {
|
|
15019
|
+
const { commit, includeDocs = true, knownDependencies = [] } = options;
|
|
15020
|
+
anvilUrl ??= await resolveAuthAnvilUrl();
|
|
15021
|
+
const token = await auth_getValidAuthToken(anvilUrl);
|
|
15022
|
+
const body = {
|
|
15023
|
+
include_docs: includeDocs,
|
|
15024
|
+
known_dependencies: knownDependencies
|
|
15025
|
+
};
|
|
15026
|
+
if (commit) body.commit = commit;
|
|
15027
|
+
const resp = await fetch(`${anvilUrl}/ide/api/_/apps/${appId}/resolved-dependencies`, {
|
|
15028
|
+
method: "POST",
|
|
15029
|
+
headers: {
|
|
15030
|
+
Authorization: `Bearer ${token}`,
|
|
15031
|
+
"Content-Type": "application/json"
|
|
15032
|
+
},
|
|
15033
|
+
body: JSON.stringify(body)
|
|
15034
|
+
});
|
|
15035
|
+
if (!resp.ok) {
|
|
15036
|
+
const errorBody = await resp.text();
|
|
15037
|
+
throw new Error(formatHttpError("Failed to fetch dependencies", resp.status, errorBody));
|
|
14967
15038
|
}
|
|
14968
|
-
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
14972
|
-
|
|
14973
|
-
|
|
14974
|
-
|
|
14975
|
-
|
|
14976
|
-
|
|
14977
|
-
|
|
14978
|
-
|
|
14979
|
-
|
|
14980
|
-
|
|
14981
|
-
|
|
14982
|
-
|
|
14983
|
-
|
|
15039
|
+
return await resp.json();
|
|
15040
|
+
}
|
|
15041
|
+
async function refreshDependencyCache(projectRoot, appId, anvilUrl, options = {}) {
|
|
15042
|
+
auth_setRepoContext(projectRoot);
|
|
15043
|
+
anvilUrl ??= await resolveAuthAnvilUrl();
|
|
15044
|
+
const includeDocs = options.includeDocs ?? true;
|
|
15045
|
+
const knownDependencies = await getKnownDependenciesForCache(projectRoot, includeDocs);
|
|
15046
|
+
const response = await fetchAllDependencies(appId, anvilUrl, {
|
|
15047
|
+
commit: options.commit,
|
|
15048
|
+
includeDocs,
|
|
15049
|
+
knownDependencies
|
|
15050
|
+
});
|
|
15051
|
+
if (!response.resolved_dependencies.length) {
|
|
15052
|
+
await clearDepsCache(projectRoot);
|
|
15053
|
+
return {
|
|
15054
|
+
response,
|
|
15055
|
+
addedGitignoreEntry: false
|
|
15056
|
+
};
|
|
14984
15057
|
}
|
|
14985
|
-
|
|
14986
|
-
|
|
14987
|
-
|
|
14988
|
-
|
|
14989
|
-
|
|
14990
|
-
|
|
14991
|
-
|
|
14992
|
-
|
|
14993
|
-
|
|
15058
|
+
const addedGitignoreEntry = await ensureProjectGitignoreIgnoresAnvilDir(projectRoot);
|
|
15059
|
+
await writeAllDepsToCache(projectRoot, response, {
|
|
15060
|
+
includeDocs
|
|
15061
|
+
});
|
|
15062
|
+
return {
|
|
15063
|
+
response,
|
|
15064
|
+
addedGitignoreEntry
|
|
15065
|
+
};
|
|
15066
|
+
}
|
|
15067
|
+
function depsDir(projectRoot) {
|
|
15068
|
+
return external_path_default().join(projectRoot, ANVIL_DEPS_DIR);
|
|
15069
|
+
}
|
|
15070
|
+
function depsManifestPath(projectRoot) {
|
|
15071
|
+
return external_path_default().join(depsDir(projectRoot), ANVIL_DEPS_MANIFEST);
|
|
15072
|
+
}
|
|
15073
|
+
function isResolvedDependencyManifestEntry(value) {
|
|
15074
|
+
if (!value || "object" != typeof value) return false;
|
|
15075
|
+
const entry = value;
|
|
15076
|
+
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);
|
|
15077
|
+
}
|
|
15078
|
+
function isCachedDependencyManifestEntry(value) {
|
|
15079
|
+
if (!isResolvedDependencyManifestEntry(value)) return false;
|
|
15080
|
+
const entry = value;
|
|
15081
|
+
return "string" == typeof entry.package_name && entry.package_name.length > 0;
|
|
15082
|
+
}
|
|
15083
|
+
function isDepsCacheManifest(value) {
|
|
15084
|
+
if (!value || "object" != typeof value) return false;
|
|
15085
|
+
const manifest = value;
|
|
15086
|
+
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);
|
|
15087
|
+
}
|
|
15088
|
+
async function readDepsCacheManifest(projectRoot) {
|
|
15089
|
+
try {
|
|
15090
|
+
const raw = await external_fs_promises_default().readFile(depsManifestPath(projectRoot), "utf-8");
|
|
15091
|
+
const parsed = JSON.parse(raw);
|
|
15092
|
+
return isDepsCacheManifest(parsed) ? parsed : void 0;
|
|
15093
|
+
} catch {
|
|
15094
|
+
return;
|
|
14994
15095
|
}
|
|
14995
|
-
|
|
14996
|
-
|
|
14997
|
-
|
|
14998
|
-
|
|
14999
|
-
|
|
15000
|
-
|
|
15001
|
-
|
|
15002
|
-
|
|
15003
|
-
|
|
15004
|
-
|
|
15005
|
-
|
|
15006
|
-
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15096
|
+
}
|
|
15097
|
+
async function getKnownDependenciesForCache(projectRoot, includeDocs) {
|
|
15098
|
+
const manifest = await readDepsCacheManifest(projectRoot);
|
|
15099
|
+
if (!manifest || manifest.includeDocs !== includeDocs) return [];
|
|
15100
|
+
return manifest.resolved_dependencies.map(({ app_id, commit_id })=>({
|
|
15101
|
+
app_id,
|
|
15102
|
+
commit_id
|
|
15103
|
+
}));
|
|
15104
|
+
}
|
|
15105
|
+
function getDependencyWatchSubscriptionsFromManifest(manifest) {
|
|
15106
|
+
if (!manifest) return [];
|
|
15107
|
+
const cachedByAppId = new Map(manifest.cached_dependencies.map((dep)=>[
|
|
15108
|
+
dep.app_id,
|
|
15109
|
+
dep
|
|
15110
|
+
]));
|
|
15111
|
+
return manifest.resolved_dependencies.flatMap((dep)=>{
|
|
15112
|
+
if (!dep.branch) return [];
|
|
15113
|
+
return [
|
|
15114
|
+
{
|
|
15115
|
+
appId: dep.app_id,
|
|
15116
|
+
branch: dep.branch,
|
|
15117
|
+
packageName: cachedByAppId.get(dep.app_id)?.package_name
|
|
15118
|
+
}
|
|
15119
|
+
];
|
|
15120
|
+
});
|
|
15121
|
+
}
|
|
15122
|
+
function normalizeAnvilYamlDependenciesFromContent(content) {
|
|
15123
|
+
const parsed = external_js_yaml_default().load(content);
|
|
15124
|
+
if (!parsed || "object" != typeof parsed || !Array.isArray(parsed.dependencies)) return [];
|
|
15125
|
+
return normalizeAnvilYamlDependenciesValue(parsed.dependencies);
|
|
15126
|
+
}
|
|
15127
|
+
function normalizeAnvilYamlDependenciesValue(value) {
|
|
15128
|
+
if (!Array.isArray(value)) return [];
|
|
15129
|
+
return value.map(normalizeForComparison);
|
|
15130
|
+
}
|
|
15131
|
+
async function readAnvilYamlDependencies(projectRoot) {
|
|
15132
|
+
try {
|
|
15133
|
+
const content = await external_fs_promises_default().readFile(external_path_default().join(projectRoot, "anvil.yaml"), "utf-8");
|
|
15134
|
+
return normalizeAnvilYamlDependenciesFromContent(content);
|
|
15135
|
+
} catch {
|
|
15136
|
+
return [];
|
|
15011
15137
|
}
|
|
15012
|
-
|
|
15138
|
+
}
|
|
15139
|
+
async function getDependencyCacheWarning(projectRoot) {
|
|
15140
|
+
const sourceDependencies = await readAnvilYamlDependencies(projectRoot);
|
|
15141
|
+
if (!Array.isArray(sourceDependencies) || 0 === sourceDependencies.length) return;
|
|
15142
|
+
const manifest = await readDepsCacheManifest(projectRoot);
|
|
15143
|
+
if (!manifest || !anvilYamlDependenciesEqual(sourceDependencies, manifest.source_dependencies)) return DEPENDENCY_CACHE_WARNING;
|
|
15144
|
+
if (0 === manifest.resolved_dependencies.length) return DEPENDENCY_CACHE_WARNING;
|
|
15145
|
+
const cachedByAppId = new Map(manifest.cached_dependencies.map((dependency)=>[
|
|
15146
|
+
dependency.app_id,
|
|
15147
|
+
dependency
|
|
15148
|
+
]));
|
|
15149
|
+
for (const dependency of manifest.resolved_dependencies){
|
|
15150
|
+
const cached = cachedByAppId.get(dependency.app_id);
|
|
15151
|
+
if (!cached || cached.commit_id !== dependency.commit_id) return DEPENDENCY_CACHE_WARNING;
|
|
15013
15152
|
try {
|
|
15014
|
-
await
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
]);
|
|
15018
|
-
} catch (e) {
|
|
15019
|
-
throw errors_createGitError.commandFailed("rebase", e.message);
|
|
15153
|
+
await external_fs_promises_default().access(external_path_default().join(depsDir(projectRoot), cached.package_name, "anvil.yaml"));
|
|
15154
|
+
} catch {
|
|
15155
|
+
return DEPENDENCY_CACHE_WARNING;
|
|
15020
15156
|
}
|
|
15021
15157
|
}
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15158
|
+
}
|
|
15159
|
+
function anvilYamlDependenciesEqual(left, right) {
|
|
15160
|
+
return deepEqual(left, right);
|
|
15161
|
+
}
|
|
15162
|
+
function normalizeForComparison(value) {
|
|
15163
|
+
if (Array.isArray(value)) return value.map(normalizeForComparison);
|
|
15164
|
+
if (value && "object" == typeof value) return Object.fromEntries(Object.entries(value).sort(([left], [right])=>left.localeCompare(right)).map(([key, entryValue])=>[
|
|
15165
|
+
key,
|
|
15166
|
+
normalizeForComparison(entryValue)
|
|
15167
|
+
]));
|
|
15168
|
+
return value;
|
|
15169
|
+
}
|
|
15170
|
+
function cachedManifestEntry(dep, packageName) {
|
|
15171
|
+
return {
|
|
15172
|
+
app_id: dep.app_id,
|
|
15173
|
+
commit_id: dep.commit_id,
|
|
15174
|
+
...dep.branch ? {
|
|
15175
|
+
branch: dep.branch
|
|
15176
|
+
} : {},
|
|
15177
|
+
package_name: packageName
|
|
15178
|
+
};
|
|
15179
|
+
}
|
|
15180
|
+
function safeDependencyPath(filePath, kind) {
|
|
15181
|
+
const normalizedPath = external_path_default().normalize(filePath);
|
|
15182
|
+
if (external_path_default().isAbsolute(normalizedPath) || normalizedPath.startsWith("..")) throw new Error(`Invalid dependency ${kind} path: ${filePath}`);
|
|
15183
|
+
return normalizedPath;
|
|
15184
|
+
}
|
|
15185
|
+
function dependencyPythonPath(name, kind) {
|
|
15186
|
+
const parts = name.split(".");
|
|
15187
|
+
if (0 === parts.length || parts.some((part)=>!part || "." === part || ".." === part || part.includes("/") || part.includes("\\"))) throw new Error(`Invalid dependency ${kind} name: ${name}`);
|
|
15188
|
+
return external_path_default().join(...parts);
|
|
15189
|
+
}
|
|
15190
|
+
async function writeDepToCache(projectRoot, dep) {
|
|
15191
|
+
const depDir = external_path_default().join(projectRoot, ANVIL_DEPS_DIR, dep.package_name);
|
|
15192
|
+
await external_fs_promises_default().rm(depDir, {
|
|
15193
|
+
recursive: true,
|
|
15194
|
+
force: true
|
|
15195
|
+
});
|
|
15196
|
+
await external_fs_promises_default().mkdir(external_path_default().join(depDir, "client_code"), {
|
|
15197
|
+
recursive: true
|
|
15198
|
+
});
|
|
15199
|
+
await external_fs_promises_default().mkdir(external_path_default().join(depDir, "server_code"), {
|
|
15200
|
+
recursive: true
|
|
15201
|
+
});
|
|
15202
|
+
await external_fs_promises_default().writeFile(external_path_default().join(depDir, "anvil.yaml"), external_js_yaml_default().dump({
|
|
15203
|
+
package_name: dep.package_name
|
|
15204
|
+
}));
|
|
15205
|
+
for (const mod of dep.modules ?? []){
|
|
15206
|
+
const modulePath = external_path_default().join(depDir, "client_code", dependencyPythonPath(mod.name, "module"));
|
|
15207
|
+
if (mod.is_package) {
|
|
15208
|
+
await external_fs_promises_default().mkdir(modulePath, {
|
|
15209
|
+
recursive: true
|
|
15210
|
+
});
|
|
15211
|
+
await external_fs_promises_default().writeFile(external_path_default().join(modulePath, "__init__.py"), mod.code);
|
|
15212
|
+
} else {
|
|
15213
|
+
const moduleFile = `${modulePath}.py`;
|
|
15214
|
+
await external_fs_promises_default().mkdir(external_path_default().dirname(moduleFile), {
|
|
15215
|
+
recursive: true
|
|
15216
|
+
});
|
|
15217
|
+
await external_fs_promises_default().writeFile(moduleFile, mod.code);
|
|
15030
15218
|
}
|
|
15031
15219
|
}
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15042
|
-
|
|
15043
|
-
|
|
15044
|
-
|
|
15220
|
+
for (const form of dep.forms ?? []){
|
|
15221
|
+
const { code, class_name, is_package, ...formTemplate } = form;
|
|
15222
|
+
const formPath = external_path_default().join(depDir, "client_code", dependencyPythonPath(class_name, "form"));
|
|
15223
|
+
if (is_package) {
|
|
15224
|
+
await external_fs_promises_default().mkdir(formPath, {
|
|
15225
|
+
recursive: true
|
|
15226
|
+
});
|
|
15227
|
+
await external_fs_promises_default().writeFile(external_path_default().join(formPath, "__init__.py"), code);
|
|
15228
|
+
await external_fs_promises_default().writeFile(external_path_default().join(formPath, "form_template.yaml"), external_js_yaml_default().dump(formTemplate));
|
|
15229
|
+
} else {
|
|
15230
|
+
const formCodeFile = `${formPath}.py`;
|
|
15231
|
+
await external_fs_promises_default().mkdir(external_path_default().dirname(formCodeFile), {
|
|
15232
|
+
recursive: true
|
|
15233
|
+
});
|
|
15234
|
+
await external_fs_promises_default().writeFile(formCodeFile, code);
|
|
15235
|
+
await external_fs_promises_default().writeFile(`${formPath}.yaml`, external_js_yaml_default().dump(formTemplate));
|
|
15045
15236
|
}
|
|
15046
15237
|
}
|
|
15047
|
-
|
|
15048
|
-
|
|
15049
|
-
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
await
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
]);
|
|
15061
|
-
} catch (e) {}
|
|
15062
|
-
}
|
|
15063
|
-
async getRemotes() {
|
|
15064
|
-
try {
|
|
15065
|
-
const remotes = await this.git.getRemotes(true);
|
|
15066
|
-
return remotes.map((r)=>({
|
|
15067
|
-
name: r.name,
|
|
15068
|
-
fetchUrl: r.refs.fetch
|
|
15069
|
-
}));
|
|
15070
|
-
} catch (e) {
|
|
15071
|
-
throw errors_createGitError.commandFailed("remote", e.message);
|
|
15238
|
+
for (const mod of dep.server_modules ?? []){
|
|
15239
|
+
const modulePath = external_path_default().join(depDir, "server_code", dependencyPythonPath(mod.name, "server module"));
|
|
15240
|
+
if (mod.is_package) {
|
|
15241
|
+
await external_fs_promises_default().mkdir(modulePath, {
|
|
15242
|
+
recursive: true
|
|
15243
|
+
});
|
|
15244
|
+
await external_fs_promises_default().writeFile(external_path_default().join(modulePath, "__init__.py"), mod.code);
|
|
15245
|
+
} else {
|
|
15246
|
+
const moduleFile = `${modulePath}.py`;
|
|
15247
|
+
await external_fs_promises_default().mkdir(external_path_default().dirname(moduleFile), {
|
|
15248
|
+
recursive: true
|
|
15249
|
+
});
|
|
15250
|
+
await external_fs_promises_default().writeFile(moduleFile, mod.code);
|
|
15072
15251
|
}
|
|
15073
15252
|
}
|
|
15074
|
-
|
|
15075
|
-
const
|
|
15076
|
-
|
|
15253
|
+
for (const asset of dep.assets ?? []){
|
|
15254
|
+
const targetPath = external_path_default().join(depDir, "theme", "assets", safeDependencyPath(asset.name, "asset"));
|
|
15255
|
+
await external_fs_promises_default().mkdir(external_path_default().dirname(targetPath), {
|
|
15256
|
+
recursive: true
|
|
15257
|
+
});
|
|
15258
|
+
await external_fs_promises_default().writeFile(targetPath, Buffer.from(asset.content, "base64"));
|
|
15077
15259
|
}
|
|
15078
|
-
|
|
15079
|
-
const
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
const parentDir = external_path_default().dirname(dir);
|
|
15085
|
-
if (parentDir === dir) break;
|
|
15086
|
-
dir = parentDir;
|
|
15087
|
-
}
|
|
15088
|
-
}
|
|
15089
|
-
const sortedDirs = Array.from(directoriesToCheck).sort((a, b)=>b.split("/").length - a.split("/").length);
|
|
15090
|
-
for (const dir of sortedDirs)try {
|
|
15091
|
-
const dirPath = external_path_default().join(this.repoPath, dir);
|
|
15092
|
-
if (!external_fs_.existsSync(dirPath)) continue;
|
|
15093
|
-
const entries = await external_fs_.promises.readdir(dirPath);
|
|
15094
|
-
if (0 === entries.length) await external_fs_.promises.rmdir(dirPath);
|
|
15095
|
-
} catch (error) {}
|
|
15260
|
+
for (const doc of dep.docs ?? []){
|
|
15261
|
+
const targetPath = external_path_default().join(depDir, safeDependencyPath(doc.path, "doc"));
|
|
15262
|
+
await external_fs_promises_default().mkdir(external_path_default().dirname(targetPath), {
|
|
15263
|
+
recursive: true
|
|
15264
|
+
});
|
|
15265
|
+
await external_fs_promises_default().writeFile(targetPath, doc.content);
|
|
15096
15266
|
}
|
|
15097
15267
|
}
|
|
15098
|
-
async function
|
|
15268
|
+
async function writeAllDepsToCache(projectRoot, response, options = {}) {
|
|
15269
|
+
const includeDocs = options.includeDocs ?? true;
|
|
15270
|
+
const manifest = await readDepsCacheManifest(projectRoot);
|
|
15271
|
+
const canReuseExistingCache = manifest?.includeDocs === includeDocs;
|
|
15272
|
+
if (!canReuseExistingCache) await clearDepsCache(projectRoot);
|
|
15273
|
+
const previousCached = canReuseExistingCache ? manifest.cached_dependencies : [];
|
|
15274
|
+
const resolvedAppIds = new Set(response.resolved_dependencies.map((dep)=>dep.app_id));
|
|
15275
|
+
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), {
|
|
15276
|
+
recursive: true,
|
|
15277
|
+
force: true
|
|
15278
|
+
});
|
|
15279
|
+
for (const dep of response.dependencies)await writeDepToCache(projectRoot, dep);
|
|
15280
|
+
const returnedByAppId = new Map(response.dependencies.map((dep)=>[
|
|
15281
|
+
dep.app_id,
|
|
15282
|
+
dep
|
|
15283
|
+
]));
|
|
15284
|
+
const previousByAppId = new Map(previousCached.map((dep)=>[
|
|
15285
|
+
dep.app_id,
|
|
15286
|
+
dep
|
|
15287
|
+
]));
|
|
15288
|
+
const cachedDependencies = response.resolved_dependencies.map((dep)=>{
|
|
15289
|
+
const returned = returnedByAppId.get(dep.app_id);
|
|
15290
|
+
if (returned) return cachedManifestEntry(returned, returned.package_name);
|
|
15291
|
+
const previous = previousByAppId.get(dep.app_id);
|
|
15292
|
+
if (!previous) return;
|
|
15293
|
+
return cachedManifestEntry(dep, previous.package_name);
|
|
15294
|
+
}).filter((dep)=>void 0 !== dep);
|
|
15295
|
+
const newManifest = {
|
|
15296
|
+
version: DEPS_MANIFEST_VERSION,
|
|
15297
|
+
includeDocs,
|
|
15298
|
+
source_dependencies: await readAnvilYamlDependencies(projectRoot),
|
|
15299
|
+
resolved_dependencies: response.resolved_dependencies,
|
|
15300
|
+
cached_dependencies: cachedDependencies
|
|
15301
|
+
};
|
|
15302
|
+
await external_fs_promises_default().mkdir(depsDir(projectRoot), {
|
|
15303
|
+
recursive: true
|
|
15304
|
+
});
|
|
15305
|
+
await external_fs_promises_default().writeFile(depsManifestPath(projectRoot), `${JSON.stringify(newManifest, null, 2)}\n`);
|
|
15306
|
+
}
|
|
15307
|
+
async function clearDepsCache(projectRoot) {
|
|
15308
|
+
await external_fs_promises_default().rm(depsDir(projectRoot), {
|
|
15309
|
+
recursive: true,
|
|
15310
|
+
force: true
|
|
15311
|
+
});
|
|
15312
|
+
}
|
|
15313
|
+
async function getCachedPackages(projectRoot) {
|
|
15099
15314
|
try {
|
|
15100
|
-
const
|
|
15101
|
-
|
|
15102
|
-
|
|
15103
|
-
|
|
15104
|
-
|
|
15105
|
-
|
|
15106
|
-
const rename = renames.find((r)=>r.to === f.path);
|
|
15107
|
-
if (rename) stagedRenames.push({
|
|
15108
|
-
path: rename.to,
|
|
15109
|
-
type: "rename",
|
|
15110
|
-
from: rename.from
|
|
15111
|
-
});
|
|
15112
|
-
}
|
|
15113
|
-
const stagedRenamedFromPaths = new Set(stagedRenames.map((r)=>r.from));
|
|
15114
|
-
const stagedRenamedToPaths = new Set(stagedRenames.map((r)=>r.path));
|
|
15115
|
-
const changes = [];
|
|
15116
|
-
for (const f of stagedFiles){
|
|
15117
|
-
if ("R" !== f.index) {
|
|
15118
|
-
if (!stagedModeOnlyPaths.has(f.path)) {
|
|
15119
|
-
if (!(stagedRenamedFromPaths.has(f.path) || stagedRenamedToPaths.has(f.path))) if ("D" === f.index) changes.push({
|
|
15120
|
-
path: f.path,
|
|
15121
|
-
type: "unlink"
|
|
15122
|
-
});
|
|
15123
|
-
else if ("A" === f.index || "?" === f.index) changes.push({
|
|
15124
|
-
path: f.path,
|
|
15125
|
-
type: "add"
|
|
15126
|
-
});
|
|
15127
|
-
else changes.push({
|
|
15128
|
-
path: f.path,
|
|
15129
|
-
type: "change"
|
|
15130
|
-
});
|
|
15131
|
-
}
|
|
15132
|
-
}
|
|
15133
|
-
}
|
|
15134
|
-
changes.push(...stagedRenames);
|
|
15135
|
-
return changes;
|
|
15136
|
-
} catch (e) {
|
|
15137
|
-
throw errors_createGitError.commandFailed("status", e.message);
|
|
15315
|
+
const entries = await external_fs_promises_default().readdir(depsDir(projectRoot), {
|
|
15316
|
+
withFileTypes: true
|
|
15317
|
+
});
|
|
15318
|
+
return entries.filter((entry)=>entry.isDirectory()).map((entry)=>entry.name);
|
|
15319
|
+
} catch {
|
|
15320
|
+
return [];
|
|
15138
15321
|
}
|
|
15139
15322
|
}
|
|
15140
15323
|
const external_chokidar_namespaceObject = require("chokidar");
|
|
@@ -16440,6 +16623,45 @@ Promise.resolve(executeGitCredentialOperation(process.argv[2] || "get", {
|
|
|
16440
16623
|
};
|
|
16441
16624
|
}
|
|
16442
16625
|
const validatePython_requireFromHere = (0, external_module_namespaceObject.createRequire)(__filename);
|
|
16626
|
+
const DEFAULT_PYRIGHT_MAX_OLD_SPACE_MB = 768;
|
|
16627
|
+
const DEFAULT_PYRIGHT_TIMEOUT_MS = 300000;
|
|
16628
|
+
function parsePositiveIntegerEnv(env, name, defaultValue) {
|
|
16629
|
+
const rawValue = env[name];
|
|
16630
|
+
if (void 0 === rawValue || "" === rawValue) return {
|
|
16631
|
+
value: defaultValue
|
|
16632
|
+
};
|
|
16633
|
+
if (!/^\d+$/.test(rawValue) || Number(rawValue) <= 0 || !Number.isSafeInteger(Number(rawValue))) return {
|
|
16634
|
+
error: `${name} must be a positive integer`
|
|
16635
|
+
};
|
|
16636
|
+
return {
|
|
16637
|
+
value: Number(rawValue)
|
|
16638
|
+
};
|
|
16639
|
+
}
|
|
16640
|
+
function pyrightChildNodeOptions(nodeOptions, maxOldSpaceMb) {
|
|
16641
|
+
const maxOldSpaceFlag = "--max[-_]old[-_]space[-_]size";
|
|
16642
|
+
const withoutEqualsForm = (nodeOptions ?? "").replace(new RegExp(`(^|\\s)${maxOldSpaceFlag}=\\S+`, "g"), "$1");
|
|
16643
|
+
const withoutSeparateForm = withoutEqualsForm.replace(new RegExp(`(^|\\s)${maxOldSpaceFlag}\\s+\\S+`, "g"), "$1");
|
|
16644
|
+
return [
|
|
16645
|
+
withoutSeparateForm.trim(),
|
|
16646
|
+
`--max-old-space-size=${maxOldSpaceMb}`
|
|
16647
|
+
].filter(Boolean).join(" ");
|
|
16648
|
+
}
|
|
16649
|
+
function semanticProcessFailure(message) {
|
|
16650
|
+
return {
|
|
16651
|
+
ok: false,
|
|
16652
|
+
message: "Python semantic validation could not be run",
|
|
16653
|
+
issues: [
|
|
16654
|
+
{
|
|
16655
|
+
path: "root",
|
|
16656
|
+
message,
|
|
16657
|
+
severity: "error"
|
|
16658
|
+
}
|
|
16659
|
+
]
|
|
16660
|
+
};
|
|
16661
|
+
}
|
|
16662
|
+
function isV8HeapOutOfMemory(stderr) {
|
|
16663
|
+
return /(?:JavaScript heap out of memory|Reached heap limit|Ineffective mark-compacts near heap limit)/i.test(stderr);
|
|
16664
|
+
}
|
|
16443
16665
|
function resolveBundledAnvilPyright() {
|
|
16444
16666
|
try {
|
|
16445
16667
|
return {
|
|
@@ -16707,8 +16929,13 @@ print(json.dumps({"issues": issues}))
|
|
|
16707
16929
|
};
|
|
16708
16930
|
}
|
|
16709
16931
|
function validatePythonWithPyright(appRoot, options = {}) {
|
|
16932
|
+
const env = options.env ?? process.env;
|
|
16933
|
+
const maxOldSpace = parsePositiveIntegerEnv(env, "ANVIL_PYRIGHT_MAX_OLD_SPACE_MB", DEFAULT_PYRIGHT_MAX_OLD_SPACE_MB);
|
|
16934
|
+
if ("error" in maxOldSpace) return semanticProcessFailure(maxOldSpace.error);
|
|
16935
|
+
const timeout = parsePositiveIntegerEnv(env, "ANVIL_PYRIGHT_TIMEOUT_MS", DEFAULT_PYRIGHT_TIMEOUT_MS);
|
|
16936
|
+
if ("error" in timeout) return semanticProcessFailure(timeout.error);
|
|
16710
16937
|
const pyright = void 0 === options.pyright ? findAnvilPyright({
|
|
16711
|
-
env
|
|
16938
|
+
env
|
|
16712
16939
|
}) : options.pyright;
|
|
16713
16940
|
if (!pyright) return {
|
|
16714
16941
|
ok: false,
|
|
@@ -16731,30 +16958,19 @@ print(json.dumps({"issues": issues}))
|
|
|
16731
16958
|
...options.files ?? []
|
|
16732
16959
|
], {
|
|
16733
16960
|
encoding: "utf8",
|
|
16734
|
-
|
|
16961
|
+
env: {
|
|
16962
|
+
...env,
|
|
16963
|
+
NODE_OPTIONS: pyrightChildNodeOptions(env.NODE_OPTIONS, maxOldSpace.value)
|
|
16964
|
+
},
|
|
16965
|
+
maxBuffer: 10485760,
|
|
16966
|
+
timeout: timeout.value
|
|
16735
16967
|
});
|
|
16736
|
-
if (result.error)
|
|
16737
|
-
|
|
16738
|
-
message
|
|
16739
|
-
|
|
16740
|
-
|
|
16741
|
-
|
|
16742
|
-
message: result.error.message,
|
|
16743
|
-
severity: "error"
|
|
16744
|
-
}
|
|
16745
|
-
]
|
|
16746
|
-
};
|
|
16747
|
-
if (0 !== result.status && 1 !== result.status) return {
|
|
16748
|
-
ok: false,
|
|
16749
|
-
message: "Python semantic validation could not be run",
|
|
16750
|
-
issues: [
|
|
16751
|
-
{
|
|
16752
|
-
path: "root",
|
|
16753
|
-
message: result.stderr.trim() || `anvil-pyright exited with status ${result.status}`,
|
|
16754
|
-
severity: "error"
|
|
16755
|
-
}
|
|
16756
|
-
]
|
|
16757
|
-
};
|
|
16968
|
+
if (result.error) {
|
|
16969
|
+
if ("ETIMEDOUT" === result.error.code) return semanticProcessFailure(`anvil-pyright exceeded the ${timeout.value} ms timeout. Increase ANVIL_PYRIGHT_TIMEOUT_MS or validate a smaller target.`);
|
|
16970
|
+
return semanticProcessFailure(result.error.message);
|
|
16971
|
+
}
|
|
16972
|
+
if (isV8HeapOutOfMemory(result.stderr)) return semanticProcessFailure(`anvil-pyright ran out of memory with a ${maxOldSpace.value} MB heap. Increase ANVIL_PYRIGHT_MAX_OLD_SPACE_MB or validate a smaller target.`);
|
|
16973
|
+
if (0 !== result.status && 1 !== result.status) return semanticProcessFailure(result.stderr.trim() || `anvil-pyright exited with status ${result.status}`);
|
|
16758
16974
|
let parsed;
|
|
16759
16975
|
try {
|
|
16760
16976
|
parsed = JSON.parse(result.stdout.trim());
|
|
@@ -17071,7 +17287,7 @@ print(json.dumps({"issues": issues}))
|
|
|
17071
17287
|
}
|
|
17072
17288
|
function validateScheduledTasks(value, path) {
|
|
17073
17289
|
const issues = [];
|
|
17074
|
-
if (
|
|
17290
|
+
if (null == value) return issues;
|
|
17075
17291
|
if (!Array.isArray(value)) {
|
|
17076
17292
|
validators_pushIssue(issues, path, "must be an array");
|
|
17077
17293
|
return issues;
|
|
@@ -18570,6 +18786,15 @@ print(json.dumps({"issues": issues}))
|
|
|
18570
18786
|
this.hasPendingChanges = true;
|
|
18571
18787
|
await this.processSaveBatchWithEvents();
|
|
18572
18788
|
}
|
|
18789
|
+
async flush() {
|
|
18790
|
+
const requireReady = ()=>{
|
|
18791
|
+
if (this.config.isPaused() || this.config.isCleanedUp() || !this.config.isSettlePeriodOver()) throw new Error("Cannot flush saves while watching is paused, closed, or changing branch");
|
|
18792
|
+
};
|
|
18793
|
+
requireReady();
|
|
18794
|
+
await this.forceSave();
|
|
18795
|
+
requireReady();
|
|
18796
|
+
if (this.hasPendingChanges) throw new Error("Saves remain pending");
|
|
18797
|
+
}
|
|
18573
18798
|
cleanup() {
|
|
18574
18799
|
if (this.hasPendingChanges) {
|
|
18575
18800
|
logger_logger.progressEnd("sync");
|
|
@@ -18666,10 +18891,7 @@ print(json.dumps({"issues": issues}))
|
|
|
18666
18891
|
status = await this.config.gitService.getStatus();
|
|
18667
18892
|
} catch (e) {
|
|
18668
18893
|
logger_logger.error(external_chalk_default().red(`Error reading git status: ${errors_getErrorMessage(e)}`));
|
|
18669
|
-
|
|
18670
|
-
skipped: true,
|
|
18671
|
-
reason: "Git status error"
|
|
18672
|
-
};
|
|
18894
|
+
throw e;
|
|
18673
18895
|
}
|
|
18674
18896
|
let allChanges;
|
|
18675
18897
|
if (this.config.stagedOnly) {
|
|
@@ -19826,6 +20048,12 @@ print(json.dumps({"issues": issues}))
|
|
|
19826
20048
|
if (!this.saveProcessor) return;
|
|
19827
20049
|
return this.saveProcessor.forceSave();
|
|
19828
20050
|
}
|
|
20051
|
+
async flushSaves() {
|
|
20052
|
+
if (!this.saveProcessor || this.isSwitchingForEnvironment) throw new Error("App synchronization is not ready");
|
|
20053
|
+
const branch = this.currentBranch;
|
|
20054
|
+
await this.saveProcessor.flush();
|
|
20055
|
+
if (this.isSwitchingForEnvironment || this.currentBranch !== branch || await this.gitService.getCurrentBranch() !== branch) throw new Error("App branch changed while flushing saves");
|
|
20056
|
+
}
|
|
19829
20057
|
async getSuccessMessage() {
|
|
19830
20058
|
try {
|
|
19831
20059
|
const info = await this.gitService.getCommitInfo();
|
|
@@ -21577,7 +21805,7 @@ print(json.dumps({"issues": issues}))
|
|
|
21577
21805
|
])).trim();
|
|
21578
21806
|
const resolvedGitCommonDir = external_path_default().isAbsolute(rawGitCommonDir) ? rawGitCommonDir : external_path_default().resolve(inputPath, rawGitCommonDir);
|
|
21579
21807
|
const gitCommonDir = external_fs_default().realpathSync(resolvedGitCommonDir);
|
|
21580
|
-
|
|
21808
|
+
auth_setRepoContext(repoPath);
|
|
21581
21809
|
const remotes = (await git.getRemotes(true)).map(detectAnvilRemote);
|
|
21582
21810
|
const anvilRemotes = remotes.filter((remote)=>remote.appId && remote.anvilUrl && "https" === remote.transport);
|
|
21583
21811
|
const findings = [];
|
|
@@ -21703,7 +21931,7 @@ print(json.dumps({"issues": issues}))
|
|
|
21703
21931
|
const repoPath = options.repoPath ?? deps.cwd();
|
|
21704
21932
|
if ("get" !== operation) return null;
|
|
21705
21933
|
if (!request.protocol || !request.host) return null;
|
|
21706
|
-
|
|
21934
|
+
auth_setRepoContext(repoPath);
|
|
21707
21935
|
const appId = parseAppIdFromGitPath(request.path);
|
|
21708
21936
|
if (!appId) return null;
|
|
21709
21937
|
const requestUrl = normalizeAnvilUrl(`${request.protocol}://${request.host}`);
|
|
@@ -21754,7 +21982,6 @@ print(json.dumps({"issues": issues}))
|
|
|
21754
21982
|
}
|
|
21755
21983
|
});
|
|
21756
21984
|
}
|
|
21757
|
-
const DEFAULT_ANVIL_URL = resolveAnvilUrl();
|
|
21758
21985
|
async function syncToLatest(repoPath, appId, options) {
|
|
21759
21986
|
const { anvilUrl, gitUrl = anvilUrl, authToken, currentBranch, username } = options;
|
|
21760
21987
|
try {
|
|
@@ -21783,9 +22010,10 @@ print(json.dumps({"issues": issues}))
|
|
|
21783
22010
|
}
|
|
21784
22011
|
}
|
|
21785
22012
|
async function watch(repoPath, appId, options = {}) {
|
|
21786
|
-
const { anvilUrl = DEFAULT_ANVIL_URL, gitUrl = anvilUrl, stagedOnly = false, username } = options;
|
|
21787
22013
|
repoPath = external_path_default().resolve(repoPath);
|
|
21788
|
-
|
|
22014
|
+
auth_setRepoContext(repoPath);
|
|
22015
|
+
const anvilUrl = options.anvilUrl ?? await resolveAuthAnvilUrl();
|
|
22016
|
+
const { gitUrl = anvilUrl, stagedOnly = false, username } = options;
|
|
21789
22017
|
const storedEnvPid = options.environment?.envPid ? void 0 : await readEnvironmentPid(repoPath);
|
|
21790
22018
|
const environment = options.environment ?? (storedEnvPid ? {
|
|
21791
22019
|
envPid: storedEnvPid
|
|
@@ -22213,7 +22441,7 @@ print(json.dumps({"issues": issues}))
|
|
|
22213
22441
|
});
|
|
22214
22442
|
logger_logger.progressEnd("verify");
|
|
22215
22443
|
const currentCategory = getSyncStateCategory(freshSession.syncStatus);
|
|
22216
|
-
const currentBranch = freshSession.getBranchName() ||
|
|
22444
|
+
const currentBranch = freshSession.getBranchName() || await getFallbackBranchName(options.repoPath);
|
|
22217
22445
|
if (currentCategory !== previousCategory || currentBranch !== previousBranch) {
|
|
22218
22446
|
logger_logger.warn("Repository status has changed. Re-evaluating...");
|
|
22219
22447
|
return freshSession;
|
|
@@ -22246,7 +22474,7 @@ print(json.dumps({"issues": issues}))
|
|
|
22246
22474
|
}
|
|
22247
22475
|
async function checkSyncStatusAndStart(session, options, deps = defaultSyncStartDeps) {
|
|
22248
22476
|
const syncStatus = session.syncStatus;
|
|
22249
|
-
const branchName = session.getBranchName() ||
|
|
22477
|
+
const branchName = session.getBranchName() || await getFallbackBranchName(options.repoPath);
|
|
22250
22478
|
const hasUncommitted = session.hasUncommittedChanges;
|
|
22251
22479
|
const stateCategory = getSyncStateCategory(syncStatus);
|
|
22252
22480
|
if (syncStatus?.branchMissing) {
|
|
@@ -22566,6 +22794,9 @@ print(json.dumps({"issues": issues}))
|
|
|
22566
22794
|
const suggestedAction = error.suggestedAction;
|
|
22567
22795
|
if ("string" == typeof suggestedAction && suggestedAction.length > 0) logger_logger.warn(suggestedAction);
|
|
22568
22796
|
}
|
|
22797
|
+
async function getFallbackBranchName(repoPath) {
|
|
22798
|
+
return getCurrentOrFallbackBranchName(esm_default(repoPath));
|
|
22799
|
+
}
|
|
22569
22800
|
async function handleWatchCommand(options) {
|
|
22570
22801
|
const invoked = process.argv[2];
|
|
22571
22802
|
if ("sync" === invoked) {
|
|
@@ -22573,7 +22804,7 @@ print(json.dumps({"issues": issues}))
|
|
|
22573
22804
|
process.exit(1);
|
|
22574
22805
|
}
|
|
22575
22806
|
const { path: repoPath = process.cwd(), appid: explicitAppId, useFirst = false, stagedOnly = false, dependencyWatch = true, autoMode = false, url: explicitUrl, user: explicitUsername, open: openAfterValidation = false } = options;
|
|
22576
|
-
|
|
22807
|
+
auth_setRepoContext(repoPath);
|
|
22577
22808
|
try {
|
|
22578
22809
|
const validationResult = await validateAnvilApp(repoPath);
|
|
22579
22810
|
if (validationResult.appName) logger_logger.info(external_chalk_default().green("Anvil app: ") + external_chalk_default().bold(validationResult.appName));
|
|
@@ -22780,6 +23011,7 @@ print(json.dumps({"issues": issues}))
|
|
|
22780
23011
|
logger_logger.success("Logged in as " + external_chalk_default().bold(result.email));
|
|
22781
23012
|
} catch (e) {
|
|
22782
23013
|
logger_logger.error("Error: " + e.message);
|
|
23014
|
+
logErrorCause(e, logger_logger);
|
|
22783
23015
|
process.exit(1);
|
|
22784
23016
|
}
|
|
22785
23017
|
});
|
|
@@ -23358,6 +23590,7 @@ print(json.dumps({"issues": issues}))
|
|
|
23358
23590
|
await runConfigureFlow(version, configure_defaultDeps);
|
|
23359
23591
|
} catch (e) {
|
|
23360
23592
|
logger_logger.error("Error: " + errors_getErrorMessage(e));
|
|
23593
|
+
logErrorCause(e, logger_logger);
|
|
23361
23594
|
process.exit(1);
|
|
23362
23595
|
}
|
|
23363
23596
|
});
|
|
@@ -23376,7 +23609,8 @@ print(json.dumps({"issues": issues}))
|
|
|
23376
23609
|
deps.command("fetch").description("Fetch dependencies into .anvil/deps/").option("--commit <commit>", "Fetch deps at specific commit").option("--exclude-docs", "Exclude dependency docs from .anvil/deps/").action(async (options)=>{
|
|
23377
23610
|
try {
|
|
23378
23611
|
const projectRoot = process.cwd();
|
|
23379
|
-
|
|
23612
|
+
auth_setRepoContext(projectRoot);
|
|
23613
|
+
const anvilUrl = await resolveAuthAnvilUrl();
|
|
23380
23614
|
const mainAppId = await resolvePrimaryAppId(projectRoot, anvilUrl);
|
|
23381
23615
|
if (!mainAppId) {
|
|
23382
23616
|
logger_logger.error("No Anvil app found in current directory");
|
|
@@ -23419,100 +23653,379 @@ print(json.dumps({"issues": issues}))
|
|
|
23419
23653
|
}
|
|
23420
23654
|
});
|
|
23421
23655
|
}
|
|
23422
|
-
|
|
23423
|
-
|
|
23424
|
-
|
|
23425
|
-
|
|
23426
|
-
|
|
23427
|
-
|
|
23428
|
-
}
|
|
23429
|
-
|
|
23656
|
+
async function applyDatabaseSchema(appId, options = {}) {
|
|
23657
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
23658
|
+
auth_setRepoContext(projectRoot);
|
|
23659
|
+
const anvilUrl = options.anvilUrl ?? await resolveAuthAnvilUrl();
|
|
23660
|
+
const appYaml = external_js_yaml_default().load(await external_fs_.promises.readFile(external_path_default().join(projectRoot, "anvil.yaml"), "utf8"));
|
|
23661
|
+
const token = await auth_getValidAuthToken(anvilUrl);
|
|
23662
|
+
const response = await fetch(`${anvilUrl}/ide/api/_/apps/${appId}/db/infer-from-env/apply-schema`, {
|
|
23663
|
+
method: "PUT",
|
|
23664
|
+
headers: {
|
|
23665
|
+
Authorization: `Bearer ${token}`,
|
|
23666
|
+
"Content-Type": "application/json"
|
|
23667
|
+
},
|
|
23668
|
+
body: JSON.stringify({
|
|
23669
|
+
schema: appYaml?.db_schema ?? {},
|
|
23670
|
+
table_id_hints: appYaml?.table_id_hints
|
|
23671
|
+
})
|
|
23672
|
+
});
|
|
23673
|
+
if (!response.ok) throw new Error(formatHttpError("Failed to apply database schema", response.status, await response.text()));
|
|
23674
|
+
return await response.json();
|
|
23675
|
+
}
|
|
23676
|
+
async function confirmSchemaApplication(force = false) {
|
|
23677
|
+
if (force) return true;
|
|
23678
|
+
assertCanPrompt("Applying database schema", "Pass --force to apply without prompting.");
|
|
23679
|
+
return logger_logger.confirm("Apply the app environment's schema to its database? This may delete tables, columns, or data.", true);
|
|
23680
|
+
}
|
|
23681
|
+
function registerDbCommand(program) {
|
|
23682
|
+
if ("1" !== process.env.ANVIL_AGENT_HOST) return;
|
|
23683
|
+
const db = program.command("db").description("Manage app databases");
|
|
23684
|
+
db.command("apply-schema").description("Apply the app schema to the database for the current environment").option("-f, --force", "Apply the schema without asking for confirmation").action(async (options)=>{
|
|
23685
|
+
try {
|
|
23686
|
+
const projectRoot = process.cwd();
|
|
23687
|
+
auth_setRepoContext(projectRoot);
|
|
23688
|
+
const anvilUrl = await resolveAuthAnvilUrl();
|
|
23689
|
+
const appId = await resolvePrimaryAppId(projectRoot, anvilUrl);
|
|
23690
|
+
if (!appId) throw new Error("No Anvil app found in current directory. Make sure you're in a directory with an Anvil app git remote.");
|
|
23691
|
+
if (!await confirmSchemaApplication(options.force)) return void logger_logger.info("Schema application cancelled.");
|
|
23692
|
+
const response = await applyDatabaseSchema(appId, {
|
|
23693
|
+
anvilUrl,
|
|
23694
|
+
projectRoot
|
|
23695
|
+
});
|
|
23696
|
+
if (getGlobalOutputConfig().jsonMode) logJsonResult(true, {
|
|
23697
|
+
data: response
|
|
23698
|
+
});
|
|
23699
|
+
else logger_logger.success("Database schema applied.");
|
|
23700
|
+
} catch (error) {
|
|
23701
|
+
const message = errors_getErrorMessage(error);
|
|
23702
|
+
if (getGlobalOutputConfig().jsonMode) logJsonResult(false, {
|
|
23703
|
+
error: message
|
|
23704
|
+
});
|
|
23705
|
+
else logger_logger.error(message);
|
|
23706
|
+
process.exit(1);
|
|
23707
|
+
}
|
|
23708
|
+
});
|
|
23430
23709
|
}
|
|
23431
|
-
async function
|
|
23710
|
+
async function ensureEnvironmentUrl(appId, envPid, options = {}) {
|
|
23711
|
+
const anvilUrl = options.anvilUrl ?? await resolveAuthAnvilUrl();
|
|
23432
23712
|
const token = await auth_getValidAuthToken(anvilUrl);
|
|
23433
|
-
const
|
|
23713
|
+
const response = await fetch(`${anvilUrl}/ide/api/_/apps/${encodeURIComponent(appId)}/environments/${encodeURIComponent(envPid)}/temporary-url`, {
|
|
23714
|
+
method: "POST",
|
|
23434
23715
|
headers: {
|
|
23435
23716
|
Authorization: `Bearer ${token}`
|
|
23436
23717
|
}
|
|
23437
23718
|
});
|
|
23438
|
-
if (!
|
|
23439
|
-
|
|
23440
|
-
throw new Error(tables_formatFetchError("Failed to fetch table mappings", resp.status, errorBody));
|
|
23441
|
-
}
|
|
23442
|
-
const databases = await resp.json();
|
|
23443
|
-
const allTables = [];
|
|
23444
|
-
for (const db of databases)for (const table of db.tables || [])allTables.push({
|
|
23445
|
-
table_id: table.id,
|
|
23446
|
-
python_name: table.python_name,
|
|
23447
|
-
name: table.name
|
|
23448
|
-
});
|
|
23719
|
+
if (!response.ok) throw new Error(formatHttpError("Failed to get environment URL", response.status, await response.text()));
|
|
23720
|
+
const { url, expires_at } = await response.json();
|
|
23449
23721
|
return {
|
|
23450
|
-
|
|
23722
|
+
url,
|
|
23723
|
+
expires_at
|
|
23451
23724
|
};
|
|
23452
23725
|
}
|
|
23453
|
-
async function
|
|
23454
|
-
const
|
|
23455
|
-
await
|
|
23456
|
-
|
|
23457
|
-
|
|
23458
|
-
|
|
23459
|
-
|
|
23460
|
-
|
|
23461
|
-
const filePath = external_path_default().join(projectRoot, TABLE_MAPPINGS_PATH);
|
|
23462
|
-
try {
|
|
23463
|
-
const content = await promises_default().readFile(filePath, "utf-8");
|
|
23464
|
-
return JSON.parse(content);
|
|
23465
|
-
} catch {
|
|
23466
|
-
return null;
|
|
23467
|
-
}
|
|
23468
|
-
}
|
|
23469
|
-
async function clearTableMappings(projectRoot) {
|
|
23470
|
-
const filePath = external_path_default().join(projectRoot, TABLE_MAPPINGS_PATH);
|
|
23471
|
-
await promises_default().rm(filePath, {
|
|
23472
|
-
force: true
|
|
23726
|
+
async function getTemporaryUplinkKey(appId, envPid, options = {}) {
|
|
23727
|
+
const anvilUrl = options.anvilUrl ?? await resolveAuthAnvilUrl();
|
|
23728
|
+
const token = await auth_getValidAuthToken(anvilUrl);
|
|
23729
|
+
const response = await fetch(`${anvilUrl}/ide/api/_/apps/${encodeURIComponent(appId)}/environments/${encodeURIComponent(envPid)}/temporary-uplink-key`, {
|
|
23730
|
+
method: "POST",
|
|
23731
|
+
headers: {
|
|
23732
|
+
Authorization: `Bearer ${token}`
|
|
23733
|
+
}
|
|
23473
23734
|
});
|
|
23735
|
+
if (!response.ok) throw new Error(formatHttpError("Failed to get temporary uplink key", response.status, await response.text()));
|
|
23736
|
+
const { key, url, expires_at } = await response.json();
|
|
23737
|
+
return {
|
|
23738
|
+
key,
|
|
23739
|
+
url,
|
|
23740
|
+
expires_at
|
|
23741
|
+
};
|
|
23742
|
+
}
|
|
23743
|
+
async function resolveEnvironmentContext() {
|
|
23744
|
+
const projectRoot = process.cwd();
|
|
23745
|
+
const envPid = await readEnvironmentPid(projectRoot);
|
|
23746
|
+
if (!envPid) throw new Error("No app environment associated with the current directory.");
|
|
23747
|
+
auth_setRepoContext(projectRoot);
|
|
23748
|
+
const anvilUrl = await resolveAuthAnvilUrl();
|
|
23749
|
+
const appId = await resolvePrimaryAppId(projectRoot, anvilUrl);
|
|
23750
|
+
if (!appId) throw new Error("No Anvil app found in current directory. Make sure you're in a directory with an Anvil app git remote.");
|
|
23751
|
+
return {
|
|
23752
|
+
envPid,
|
|
23753
|
+
anvilUrl,
|
|
23754
|
+
appId
|
|
23755
|
+
};
|
|
23474
23756
|
}
|
|
23475
|
-
function
|
|
23476
|
-
|
|
23477
|
-
|
|
23757
|
+
function registerEnvCommand(program) {
|
|
23758
|
+
if ("1" !== process.env.ANVIL_AGENT_HOST) return;
|
|
23759
|
+
const env = program.command("env").description("Manage app environments");
|
|
23760
|
+
env.command("url").description("Get a temporary private app URL for the current environment").action(async ()=>{
|
|
23478
23761
|
try {
|
|
23479
|
-
const
|
|
23480
|
-
const
|
|
23481
|
-
|
|
23482
|
-
|
|
23483
|
-
|
|
23484
|
-
|
|
23485
|
-
|
|
23486
|
-
|
|
23487
|
-
|
|
23488
|
-
|
|
23489
|
-
|
|
23490
|
-
|
|
23491
|
-
|
|
23492
|
-
|
|
23493
|
-
|
|
23494
|
-
|
|
23762
|
+
const { appId, envPid, anvilUrl } = await resolveEnvironmentContext();
|
|
23763
|
+
const { url, expires_at } = await ensureEnvironmentUrl(appId, envPid, {
|
|
23764
|
+
anvilUrl
|
|
23765
|
+
});
|
|
23766
|
+
if (getGlobalOutputConfig().jsonMode) logJsonResult(true, {
|
|
23767
|
+
data: {
|
|
23768
|
+
url,
|
|
23769
|
+
expires_at
|
|
23770
|
+
}
|
|
23771
|
+
});
|
|
23772
|
+
else logger_logger.info(`URL: ${url}\nExpires at: ${expires_at}`);
|
|
23773
|
+
} catch (error) {
|
|
23774
|
+
const message = errors_getErrorMessage(error);
|
|
23775
|
+
if (getGlobalOutputConfig().jsonMode) logJsonResult(false, {
|
|
23776
|
+
error: message
|
|
23777
|
+
});
|
|
23778
|
+
else logger_logger.error(message);
|
|
23495
23779
|
process.exit(1);
|
|
23496
23780
|
}
|
|
23497
23781
|
});
|
|
23498
|
-
|
|
23782
|
+
env.command("uplink").description("Get a temporary uplink key for the current environment").action(async ()=>{
|
|
23499
23783
|
try {
|
|
23500
|
-
const
|
|
23501
|
-
|
|
23502
|
-
|
|
23503
|
-
|
|
23504
|
-
|
|
23505
|
-
|
|
23784
|
+
const { appId, envPid, anvilUrl } = await resolveEnvironmentContext();
|
|
23785
|
+
const { key, url, expires_at } = await getTemporaryUplinkKey(appId, envPid, {
|
|
23786
|
+
anvilUrl
|
|
23787
|
+
});
|
|
23788
|
+
if (getGlobalOutputConfig().jsonMode) logJsonResult(true, {
|
|
23789
|
+
data: {
|
|
23790
|
+
key,
|
|
23791
|
+
url,
|
|
23792
|
+
expires_at
|
|
23793
|
+
}
|
|
23794
|
+
});
|
|
23795
|
+
else logger_logger.info(`Key: ${key}\nURL: ${url}\nExpires at: ${expires_at}`);
|
|
23796
|
+
} catch (error) {
|
|
23797
|
+
const message = errors_getErrorMessage(error);
|
|
23798
|
+
if (getGlobalOutputConfig().jsonMode) logJsonResult(false, {
|
|
23799
|
+
error: message
|
|
23800
|
+
});
|
|
23801
|
+
else logger_logger.error(message);
|
|
23506
23802
|
process.exit(1);
|
|
23507
23803
|
}
|
|
23508
23804
|
});
|
|
23509
|
-
|
|
23805
|
+
}
|
|
23806
|
+
const external_events_namespaceObject = require("events");
|
|
23807
|
+
function replError(error) {
|
|
23808
|
+
if ("string" == typeof error) return {
|
|
23809
|
+
message: error
|
|
23810
|
+
};
|
|
23811
|
+
if (error && "object" == typeof error) {
|
|
23812
|
+
const value = error;
|
|
23813
|
+
return {
|
|
23814
|
+
message: String(value.message ?? value["anvil/server-error"] ?? JSON.stringify(value)),
|
|
23815
|
+
..."string" == typeof value.type ? {
|
|
23816
|
+
type: value.type
|
|
23817
|
+
} : {},
|
|
23818
|
+
...Array.isArray(value.trace) ? {
|
|
23819
|
+
trace: value.trace.filter((frame)=>Array.isArray(frame) && "string" == typeof frame[0] && "number" == typeof frame[1])
|
|
23820
|
+
} : {}
|
|
23821
|
+
};
|
|
23822
|
+
}
|
|
23823
|
+
return {
|
|
23824
|
+
message: String(error)
|
|
23825
|
+
};
|
|
23826
|
+
}
|
|
23827
|
+
class ReplServerError extends Error {
|
|
23828
|
+
detail;
|
|
23829
|
+
constructor(detail){
|
|
23830
|
+
super(detail.message), this.detail = detail;
|
|
23831
|
+
}
|
|
23832
|
+
}
|
|
23833
|
+
async function runRepl(appId, envPid, options) {
|
|
23834
|
+
const interrupted = ()=>new Error("REPL interrupted.");
|
|
23835
|
+
const anvilUrl = options.anvilUrl ?? await resolveAuthAnvilUrl();
|
|
23836
|
+
const token = await auth_getValidAuthToken(anvilUrl);
|
|
23837
|
+
const url = new URL(`${anvilUrl.replace(/\/$/, "")}/ide/api/_/apps/${encodeURIComponent(appId)}/environments/${encodeURIComponent(envPid)}/ws`);
|
|
23838
|
+
url.protocol = "https:" === url.protocol ? "wss:" : "ws:";
|
|
23839
|
+
if (options.signal?.aborted) throw interrupted();
|
|
23840
|
+
const ws = new (external_ws_default())(url, {
|
|
23841
|
+
headers: {
|
|
23842
|
+
Authorization: `Bearer ${token}`
|
|
23843
|
+
},
|
|
23844
|
+
handshakeTimeout: 30000
|
|
23845
|
+
});
|
|
23846
|
+
const controller = new AbortController();
|
|
23847
|
+
let repl;
|
|
23848
|
+
let nextId = 1;
|
|
23849
|
+
let output = "";
|
|
23850
|
+
let keepalive;
|
|
23851
|
+
let pongTimeout;
|
|
23852
|
+
let closeTimeout;
|
|
23853
|
+
const startupTimeout = setTimeout(()=>{
|
|
23854
|
+
controller.abort(new Error("Timed out waiting for the REPL to start."));
|
|
23855
|
+
}, 30000);
|
|
23856
|
+
const onAbort = ()=>controller.abort(interrupted());
|
|
23857
|
+
const markResponsive = ()=>{
|
|
23858
|
+
clearTimeout(pongTimeout);
|
|
23859
|
+
pongTimeout = void 0;
|
|
23860
|
+
};
|
|
23861
|
+
options.signal?.addEventListener("abort", onAbort, {
|
|
23862
|
+
once: true
|
|
23863
|
+
});
|
|
23864
|
+
ws.on("pong", markResponsive);
|
|
23865
|
+
ws.on("unexpected-response", (request, response)=>{
|
|
23866
|
+
response.resume();
|
|
23867
|
+
controller.abort(new Error(`REPL websocket handshake failed (${response.statusCode} ${response.statusMessage}).`));
|
|
23868
|
+
request.destroy();
|
|
23869
|
+
});
|
|
23870
|
+
ws.on("error", (error)=>controller.abort(new Error(`REPL websocket connection failed: ${error.message}`)));
|
|
23871
|
+
ws.on("close", (code, reason)=>{
|
|
23872
|
+
clearTimeout(closeTimeout);
|
|
23873
|
+
controller.abort(new Error(`REPL websocket disconnected unexpectedly (code ${code}${reason.length ? `: ${reason}` : ""}).`));
|
|
23874
|
+
});
|
|
23875
|
+
const messages = (0, external_events_namespaceObject.on)(ws, "message", {
|
|
23876
|
+
signal: controller.signal
|
|
23877
|
+
});
|
|
23878
|
+
function send(message) {
|
|
23879
|
+
const id = nextId++;
|
|
23880
|
+
ws.send(JSON.stringify({
|
|
23881
|
+
...message,
|
|
23882
|
+
id
|
|
23883
|
+
}), (error)=>{
|
|
23884
|
+
if (error) controller.abort(new Error(`REPL websocket send failed: ${error.message}`));
|
|
23885
|
+
});
|
|
23886
|
+
return id;
|
|
23887
|
+
}
|
|
23888
|
+
async function readUntil(matches) {
|
|
23889
|
+
while(true){
|
|
23890
|
+
const { value, done } = await messages.next();
|
|
23891
|
+
options.signal?.throwIfAborted();
|
|
23892
|
+
if (done) throw new Error("REPL websocket message stream ended unexpectedly.");
|
|
23893
|
+
markResponsive();
|
|
23894
|
+
let message;
|
|
23895
|
+
try {
|
|
23896
|
+
message = JSON.parse(value[0].toString());
|
|
23897
|
+
if (!message || "object" != typeof message || Array.isArray(message)) throw new Error("Expected an object");
|
|
23898
|
+
} catch {
|
|
23899
|
+
throw new Error("Invalid REPL websocket message.");
|
|
23900
|
+
}
|
|
23901
|
+
if ("REPL_UPDATE" === message.event) {
|
|
23902
|
+
if (!repl || message.repl !== repl) continue;
|
|
23903
|
+
if ("string" == typeof message.output) {
|
|
23904
|
+
output += message.output;
|
|
23905
|
+
options.onOutput?.(message.output);
|
|
23906
|
+
options.signal?.throwIfAborted();
|
|
23907
|
+
}
|
|
23908
|
+
}
|
|
23909
|
+
if (message.error) throw new ReplServerError(replError(message.error));
|
|
23910
|
+
if ("REPL_UPDATE" === message.event && message.terminated) throw new Error("REPL terminated unexpectedly.");
|
|
23911
|
+
if (matches(message)) return message;
|
|
23912
|
+
}
|
|
23913
|
+
}
|
|
23914
|
+
try {
|
|
23915
|
+
await (0, external_events_namespaceObject.once)(ws, "open", {
|
|
23916
|
+
signal: controller.signal
|
|
23917
|
+
});
|
|
23918
|
+
const launchId = send({
|
|
23919
|
+
cmd: "LAUNCH_REPL"
|
|
23920
|
+
});
|
|
23921
|
+
const launched = await readUntil((message)=>message.id === launchId);
|
|
23922
|
+
if ("string" != typeof launched.repl || !launched.repl) throw new Error("Invalid REPL launch response: missing REPL ID.");
|
|
23923
|
+
repl = launched.repl;
|
|
23924
|
+
keepalive = setInterval(()=>{
|
|
23925
|
+
try {
|
|
23926
|
+
send({
|
|
23927
|
+
cmd: "REPL_KEEPALIVE",
|
|
23928
|
+
repl
|
|
23929
|
+
});
|
|
23930
|
+
pongTimeout = setTimeout(()=>controller.abort(new Error("REPL websocket heartbeat timed out.")), 10000);
|
|
23931
|
+
ws.ping();
|
|
23932
|
+
} catch (error) {
|
|
23933
|
+
controller.abort(error);
|
|
23934
|
+
}
|
|
23935
|
+
}, 20000);
|
|
23936
|
+
const isReady = (message)=>"REPL_UPDATE" === message.event && true === message.ready;
|
|
23937
|
+
await readUntil(isReady);
|
|
23938
|
+
clearTimeout(startupTimeout);
|
|
23939
|
+
send({
|
|
23940
|
+
cmd: "REPL_COMMAND",
|
|
23941
|
+
repl,
|
|
23942
|
+
command: options.code
|
|
23943
|
+
});
|
|
23944
|
+
await readUntil(isReady);
|
|
23945
|
+
return {
|
|
23946
|
+
output
|
|
23947
|
+
};
|
|
23948
|
+
} catch (error) {
|
|
23949
|
+
if (error instanceof ReplServerError) return {
|
|
23950
|
+
output,
|
|
23951
|
+
error: error.detail
|
|
23952
|
+
};
|
|
23953
|
+
const failure = controller.signal.aborted ? controller.signal.reason : error;
|
|
23954
|
+
throw failure instanceof Error ? failure : new Error(String(failure));
|
|
23955
|
+
} finally{
|
|
23956
|
+
clearTimeout(startupTimeout);
|
|
23957
|
+
clearInterval(keepalive);
|
|
23958
|
+
clearTimeout(pongTimeout);
|
|
23959
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
23960
|
+
controller.abort();
|
|
23961
|
+
await messages.return?.();
|
|
23962
|
+
if (ws.readyState === external_ws_default().OPEN) {
|
|
23963
|
+
if (repl) ws.send(JSON.stringify({
|
|
23964
|
+
id: nextId++,
|
|
23965
|
+
cmd: "TERMINATE_REPL",
|
|
23966
|
+
repl
|
|
23967
|
+
}), ()=>{});
|
|
23968
|
+
ws.close();
|
|
23969
|
+
} else if (ws.readyState === external_ws_default().CONNECTING) ws.terminate();
|
|
23970
|
+
if (ws.readyState !== external_ws_default().CLOSED) {
|
|
23971
|
+
closeTimeout = setTimeout(()=>ws.terminate(), 1000);
|
|
23972
|
+
closeTimeout.unref();
|
|
23973
|
+
}
|
|
23974
|
+
}
|
|
23975
|
+
}
|
|
23976
|
+
function writeJsonReplOutput(output) {
|
|
23977
|
+
process.stdout.write(JSON.stringify({
|
|
23978
|
+
type: "repl_output",
|
|
23979
|
+
output,
|
|
23980
|
+
timestamp: new Date().toISOString()
|
|
23981
|
+
}) + "\n");
|
|
23982
|
+
}
|
|
23983
|
+
function registerReplCommand(program) {
|
|
23984
|
+
if ("1" !== process.env.ANVIL_AGENT_HOST) return;
|
|
23985
|
+
program.command("repl <code>").description("Run Python in a fresh server REPL for the current environment; use - to read stdin").addHelpText("after", `
|
|
23986
|
+
Examples:
|
|
23987
|
+
anvil repl '1 + 2'
|
|
23988
|
+
anvil --json repl '1 + 2'
|
|
23989
|
+
anvil repl - < script.py
|
|
23990
|
+
`).action(async (code)=>{
|
|
23991
|
+
const controller = new AbortController();
|
|
23992
|
+
const onInterrupt = ()=>controller.abort();
|
|
23510
23993
|
try {
|
|
23511
|
-
await
|
|
23512
|
-
|
|
23513
|
-
|
|
23514
|
-
|
|
23515
|
-
|
|
23994
|
+
const { appId, envPid, anvilUrl } = await resolveEnvironmentContext();
|
|
23995
|
+
if ("-" === code) {
|
|
23996
|
+
const chunks = [];
|
|
23997
|
+
for await (const chunk of process.stdin)chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
23998
|
+
code = Buffer.concat(chunks).toString("utf8");
|
|
23999
|
+
}
|
|
24000
|
+
process.on("SIGINT", onInterrupt);
|
|
24001
|
+
const response = await runRepl(appId, envPid, {
|
|
24002
|
+
code,
|
|
24003
|
+
anvilUrl,
|
|
24004
|
+
signal: controller.signal,
|
|
24005
|
+
onOutput: getGlobalOutputConfig().jsonMode ? writeJsonReplOutput : (output)=>{
|
|
24006
|
+
process.stdout.write(output);
|
|
24007
|
+
}
|
|
24008
|
+
});
|
|
24009
|
+
const error = response.error;
|
|
24010
|
+
const message = error ? `${error.type ? `${error.type}: ` : ""}${error.message}` : void 0;
|
|
24011
|
+
if (getGlobalOutputConfig().jsonMode) logJsonResult(!error, {
|
|
24012
|
+
data: response,
|
|
24013
|
+
error: message
|
|
24014
|
+
});
|
|
24015
|
+
else if (error) {
|
|
24016
|
+
process.stderr.write(`${message}\n`);
|
|
24017
|
+
for (const [file, line] of error.trace ?? [])process.stderr.write(` at ${file}:${line}\n`);
|
|
24018
|
+
}
|
|
24019
|
+
if (error) process.exitCode = 1;
|
|
24020
|
+
} catch (error) {
|
|
24021
|
+
const message = errors_getErrorMessage(error);
|
|
24022
|
+
if (getGlobalOutputConfig().jsonMode) logJsonResult(false, {
|
|
24023
|
+
error: message
|
|
24024
|
+
});
|
|
24025
|
+
else logger_logger.error(message);
|
|
24026
|
+
process.exitCode = controller.signal.aborted ? 130 : 1;
|
|
24027
|
+
} finally{
|
|
24028
|
+
process.removeListener("SIGINT", onInterrupt);
|
|
23516
24029
|
}
|
|
23517
24030
|
});
|
|
23518
24031
|
}
|
|
@@ -23936,7 +24449,9 @@ print(json.dumps({"issues": issues}))
|
|
|
23936
24449
|
registerVersionCommand(program, VERSION);
|
|
23937
24450
|
registerConfigureCommand(program, VERSION);
|
|
23938
24451
|
registerDepsCommand(program);
|
|
23939
|
-
|
|
24452
|
+
registerDbCommand(program);
|
|
24453
|
+
registerEnvCommand(program);
|
|
24454
|
+
registerReplCommand(program);
|
|
23940
24455
|
program.command("update").description("Update anvil to the latest version").alias("u").action(async ()=>{
|
|
23941
24456
|
await handleUpdateCommand();
|
|
23942
24457
|
});
|