@anvil-works/anvil-cli 0.5.13 → 0.5.14
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/CLILogger.d.ts +54 -0
- package/dist/CLILogger.d.ts.map +1 -0
- package/dist/EditorYaml.d.ts +57 -0
- package/dist/EditorYaml.d.ts.map +1 -0
- package/dist/Emitter.d.ts +32 -0
- package/dist/Emitter.d.ts.map +1 -0
- package/dist/SavePathRouter.d.ts +56 -0
- package/dist/SavePathRouter.d.ts.map +1 -0
- package/dist/WatchSession.d.ts +112 -0
- package/dist/WatchSession.d.ts.map +1 -0
- package/dist/api.d.ts +35 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2092 -2092
- package/dist/commands/checkout-picker.d.ts +25 -0
- package/dist/commands/checkout-picker.d.ts.map +1 -0
- package/dist/commands/checkout.d.ts +85 -0
- package/dist/commands/checkout.d.ts.map +1 -0
- package/dist/commands/config.d.ts +30 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/configure.d.ts +13 -0
- package/dist/commands/configure.d.ts.map +1 -0
- package/dist/commands/gitCredential.d.ts +21 -0
- package/dist/commands/gitCredential.d.ts.map +1 -0
- package/dist/commands/index.d.ts +13 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/logout.d.ts +5 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/version.d.ts +10 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/watch.d.ts +99 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/config.d.ts +56 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/errors.d.ts +151 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/globalConfig.d.ts +23 -0
- package/dist/globalConfig.d.ts.map +1 -0
- package/dist/index.js +41657 -17604
- package/dist/logger.d.ts +67 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/oauthHtml.d.ts +3 -0
- package/dist/oauthHtml.d.ts.map +1 -0
- package/dist/program.d.ts +5 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/services/anvil-api.d.ts +124 -0
- package/dist/services/anvil-api.d.ts.map +1 -0
- package/dist/services/auth.d.ts +69 -0
- package/dist/services/auth.d.ts.map +1 -0
- package/dist/services/git-auth.d.ts +19 -0
- package/dist/services/git-auth.d.ts.map +1 -0
- package/dist/services/git.d.ts +174 -0
- package/dist/services/git.d.ts.map +1 -0
- package/dist/services/index.d.ts +9 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/keychain.d.ts +9 -0
- package/dist/services/keychain.d.ts.map +1 -0
- package/dist/services/oauth-login.d.ts +46 -0
- package/dist/services/oauth-login.d.ts.map +1 -0
- package/dist/services/path-open.d.ts +8 -0
- package/dist/services/path-open.d.ts.map +1 -0
- package/dist/services/token-store.d.ts +37 -0
- package/dist/services/token-store.d.ts.map +1 -0
- package/dist/services/validation.d.ts +23 -0
- package/dist/services/validation.d.ts.map +1 -0
- package/dist/utils.d.ts +34 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/validators.d.ts +9 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/watch/ConflictResolver.d.ts +54 -0
- package/dist/watch/ConflictResolver.d.ts.map +1 -0
- package/dist/watch/FileWatcher.d.ts +68 -0
- package/dist/watch/FileWatcher.d.ts.map +1 -0
- package/dist/watch/SaveProcessor.d.ts +97 -0
- package/dist/watch/SaveProcessor.d.ts.map +1 -0
- package/dist/watch/SyncManager.d.ts +49 -0
- package/dist/watch/SyncManager.d.ts.map +1 -0
- package/dist/watch/WebSocketClient.d.ts +98 -0
- package/dist/watch/WebSocketClient.d.ts.map +1 -0
- package/dist/watch/index.d.ts +33 -0
- package/dist/watch/index.d.ts.map +1 -0
- package/package.json +15 -2
package/dist/cli.js
CHANGED
|
@@ -50156,217 +50156,7 @@ var __webpack_exports__ = {};
|
|
|
50156
50156
|
throw errors_createNetworkError.network(e.message);
|
|
50157
50157
|
}
|
|
50158
50158
|
}
|
|
50159
|
-
|
|
50160
|
-
async function syncToLatest(repoPath, appId, anvilUrl, authToken, currentBranch, username) {
|
|
50161
|
-
try {
|
|
50162
|
-
const git = esm_default(repoPath);
|
|
50163
|
-
const commitId = (await git.revparse([
|
|
50164
|
-
"HEAD"
|
|
50165
|
-
])).trim();
|
|
50166
|
-
const session = new WatchSession(repoPath, appId, {
|
|
50167
|
-
anvilUrl,
|
|
50168
|
-
authToken,
|
|
50169
|
-
currentBranch,
|
|
50170
|
-
commitId,
|
|
50171
|
-
username
|
|
50172
|
-
});
|
|
50173
|
-
await session.initialize();
|
|
50174
|
-
await session.syncRemoteChanges();
|
|
50175
|
-
const newCommitId = (await git.revparse([
|
|
50176
|
-
"HEAD"
|
|
50177
|
-
])).trim();
|
|
50178
|
-
return newCommitId;
|
|
50179
|
-
} catch (e) {
|
|
50180
|
-
if (isAnvilError(e)) throw e;
|
|
50181
|
-
throw errors_createGitError.commandFailed("sync", e instanceof Error ? e.message : String(e));
|
|
50182
|
-
}
|
|
50183
|
-
}
|
|
50184
|
-
async function api_watch(repoPath, appId, anvilUrl = DEFAULT_ANVIL_URL, stagedOnly = false, username) {
|
|
50185
|
-
repoPath = external_path_default().resolve(repoPath);
|
|
50186
|
-
const authToken = await auth_getValidAuthToken(anvilUrl, username);
|
|
50187
|
-
await verifyAuth(authToken, anvilUrl);
|
|
50188
|
-
const git = esm_default(repoPath);
|
|
50189
|
-
let currentBranch;
|
|
50190
|
-
try {
|
|
50191
|
-
const branchRef = await git.revparse([
|
|
50192
|
-
"--abbrev-ref",
|
|
50193
|
-
"HEAD"
|
|
50194
|
-
]);
|
|
50195
|
-
if ("HEAD" === branchRef) throw errors_createGitError.commandFailed("revparse", "Cannot sync from detached HEAD state. Please checkout a branch first.");
|
|
50196
|
-
currentBranch = branchRef;
|
|
50197
|
-
} catch (e) {
|
|
50198
|
-
if ("git_command_failed" === e.type) throw e;
|
|
50199
|
-
throw errors_createGitError.commandFailed("revparse", e.message);
|
|
50200
|
-
}
|
|
50201
|
-
let commitId;
|
|
50202
|
-
try {
|
|
50203
|
-
commitId = (await git.revparse([
|
|
50204
|
-
"HEAD"
|
|
50205
|
-
])).trim();
|
|
50206
|
-
} catch (e) {
|
|
50207
|
-
throw errors_createGitError.commandFailed("revparse", e.message);
|
|
50208
|
-
}
|
|
50209
|
-
logger_logger.verbose(chalk_source.cyan("Current branch: ") + chalk_source.bold(currentBranch));
|
|
50210
|
-
logger_logger.verbose(chalk_source.cyan("Current commit ID: ") + chalk_source.gray(commitId));
|
|
50211
|
-
const syncStatus = await validateBranchSyncStatus(git, currentBranch, appId, anvilUrl, username);
|
|
50212
|
-
let hasUncommittedChanges = false;
|
|
50213
|
-
try {
|
|
50214
|
-
const status = await git.status();
|
|
50215
|
-
hasUncommittedChanges = status.files.length > 0;
|
|
50216
|
-
} catch (e) {
|
|
50217
|
-
throw errors_createGitError.commandFailed("status", e.message);
|
|
50218
|
-
}
|
|
50219
|
-
logger_logger.verbose(chalk_source.blue("Has uncommitted changes: ") + chalk_source.bold(hasUncommittedChanges));
|
|
50220
|
-
const session = new WatchSession(repoPath, appId, {
|
|
50221
|
-
anvilUrl,
|
|
50222
|
-
authToken,
|
|
50223
|
-
currentBranch,
|
|
50224
|
-
commitId,
|
|
50225
|
-
stagedOnly,
|
|
50226
|
-
username
|
|
50227
|
-
});
|
|
50228
|
-
session.on("branch-changed", (data)=>{
|
|
50229
|
-
logger_logger.debug("Event: branch-changed", data);
|
|
50230
|
-
});
|
|
50231
|
-
session.on("sync-conflict", (data)=>{
|
|
50232
|
-
logger_logger.debug("Event: sync-conflict", data);
|
|
50233
|
-
});
|
|
50234
|
-
session.on("remote-update", (data)=>{
|
|
50235
|
-
logger_logger.debug("Event: remote-update", data);
|
|
50236
|
-
});
|
|
50237
|
-
session.on("validation-failed", (data)=>{
|
|
50238
|
-
logger_logger.debug("Event: validation-failed", data);
|
|
50239
|
-
});
|
|
50240
|
-
session.on("save-complete", (data)=>{
|
|
50241
|
-
logger_logger.debug("Event: save-complete", data);
|
|
50242
|
-
});
|
|
50243
|
-
session.on("save-started", (data)=>{
|
|
50244
|
-
logger_logger.debug("Event: save-started", data);
|
|
50245
|
-
});
|
|
50246
|
-
try {
|
|
50247
|
-
await session.initialize();
|
|
50248
|
-
} catch (e) {
|
|
50249
|
-
throw errors_createGitError.commandFailed("initialize", e.message);
|
|
50250
|
-
}
|
|
50251
|
-
if (null !== syncStatus) session.syncStatus = syncStatus;
|
|
50252
|
-
session.hasUncommittedChanges = hasUncommittedChanges;
|
|
50253
|
-
return session;
|
|
50254
|
-
}
|
|
50255
|
-
function getUrlConfigKey(appId) {
|
|
50256
|
-
return `anvil.auth.${appId}.url`;
|
|
50257
|
-
}
|
|
50258
|
-
function getUsernameConfigKey(appId) {
|
|
50259
|
-
return `anvil.auth.${appId}.username`;
|
|
50260
|
-
}
|
|
50261
|
-
async function getLocalConfigValue(repoPath, key) {
|
|
50262
|
-
try {
|
|
50263
|
-
const value = (await esm_default(repoPath).raw([
|
|
50264
|
-
"config",
|
|
50265
|
-
"--local",
|
|
50266
|
-
"--get",
|
|
50267
|
-
key
|
|
50268
|
-
])).trim();
|
|
50269
|
-
return value || void 0;
|
|
50270
|
-
} catch {
|
|
50271
|
-
return;
|
|
50272
|
-
}
|
|
50273
|
-
}
|
|
50274
|
-
async function getAppAuthBinding(repoPath, appId) {
|
|
50275
|
-
const [url, username] = await Promise.all([
|
|
50276
|
-
getLocalConfigValue(repoPath, getUrlConfigKey(appId)),
|
|
50277
|
-
getLocalConfigValue(repoPath, getUsernameConfigKey(appId))
|
|
50278
|
-
]);
|
|
50279
|
-
return {
|
|
50280
|
-
url: url ? normalizeAnvilUrl(url) : void 0,
|
|
50281
|
-
username: username || void 0
|
|
50282
|
-
};
|
|
50283
|
-
}
|
|
50284
|
-
async function setAppAuthBinding(repoPath, appId, binding) {
|
|
50285
|
-
const git = esm_default(repoPath);
|
|
50286
|
-
if (binding.url) await git.raw([
|
|
50287
|
-
"config",
|
|
50288
|
-
"--local",
|
|
50289
|
-
getUrlConfigKey(appId),
|
|
50290
|
-
normalizeAnvilUrl(binding.url)
|
|
50291
|
-
]);
|
|
50292
|
-
if (binding.username) await git.raw([
|
|
50293
|
-
"config",
|
|
50294
|
-
"--local",
|
|
50295
|
-
getUsernameConfigKey(appId),
|
|
50296
|
-
binding.username
|
|
50297
|
-
]);
|
|
50298
|
-
}
|
|
50299
|
-
function getCleanGitRemoteUrl(appId, anvilUrl) {
|
|
50300
|
-
const normalized = normalizeAnvilUrl(anvilUrl);
|
|
50301
|
-
const url = new URL(normalized);
|
|
50302
|
-
return `${url.protocol}//${url.host}/git/${appId}.git`;
|
|
50303
|
-
}
|
|
50304
|
-
async function configureCredentialHelperForUrl(repoPath, anvilUrl) {
|
|
50305
|
-
const normalized = normalizeAnvilUrl(anvilUrl);
|
|
50306
|
-
const url = new URL(normalized);
|
|
50307
|
-
const scope = `${url.protocol}//${url.host}`;
|
|
50308
|
-
const git = esm_default(repoPath);
|
|
50309
|
-
try {
|
|
50310
|
-
await git.raw([
|
|
50311
|
-
"config",
|
|
50312
|
-
"--local",
|
|
50313
|
-
"--unset-all",
|
|
50314
|
-
`credential.${scope}.helper`
|
|
50315
|
-
]);
|
|
50316
|
-
} catch {}
|
|
50317
|
-
await git.raw([
|
|
50318
|
-
"config",
|
|
50319
|
-
"--local",
|
|
50320
|
-
"--add",
|
|
50321
|
-
`credential.${scope}.helper`,
|
|
50322
|
-
""
|
|
50323
|
-
]);
|
|
50324
|
-
await git.raw([
|
|
50325
|
-
"config",
|
|
50326
|
-
"--local",
|
|
50327
|
-
"--add",
|
|
50328
|
-
`credential.${scope}.helper`,
|
|
50329
|
-
"!anvil git-credential"
|
|
50330
|
-
]);
|
|
50331
|
-
await git.raw([
|
|
50332
|
-
"config",
|
|
50333
|
-
"--local",
|
|
50334
|
-
`credential.${scope}.useHttpPath`,
|
|
50335
|
-
"true"
|
|
50336
|
-
]);
|
|
50337
|
-
await git.raw([
|
|
50338
|
-
"config",
|
|
50339
|
-
"--local",
|
|
50340
|
-
`credential.${scope}.username`,
|
|
50341
|
-
"git"
|
|
50342
|
-
]);
|
|
50343
|
-
}
|
|
50344
|
-
async function hardenCheckoutGitAuth(options) {
|
|
50345
|
-
const { repoPath, appId, anvilUrl, username } = options;
|
|
50346
|
-
const remoteName = options.remoteName || "origin";
|
|
50347
|
-
const cleanRemoteUrl = getCleanGitRemoteUrl(appId, anvilUrl);
|
|
50348
|
-
const git = esm_default(repoPath);
|
|
50349
|
-
await git.raw([
|
|
50350
|
-
"remote",
|
|
50351
|
-
"set-url",
|
|
50352
|
-
remoteName,
|
|
50353
|
-
cleanRemoteUrl
|
|
50354
|
-
]);
|
|
50355
|
-
await configureCredentialHelperForUrl(repoPath, anvilUrl);
|
|
50356
|
-
await setAppAuthBinding(repoPath, appId, {
|
|
50357
|
-
url: anvilUrl,
|
|
50358
|
-
username
|
|
50359
|
-
});
|
|
50360
|
-
return {
|
|
50361
|
-
cleanRemoteUrl
|
|
50362
|
-
};
|
|
50363
|
-
}
|
|
50364
|
-
function parseAppIdFromGitPath(pathValue) {
|
|
50365
|
-
if (!pathValue) return;
|
|
50366
|
-
const normalizedPath = pathValue.startsWith("/") ? pathValue : `/${pathValue}`;
|
|
50367
|
-
const match = normalizedPath.match(/\/git\/([A-Z0-9]+)\.git(?:$|\/)/);
|
|
50368
|
-
return match ? match[1] : void 0;
|
|
50369
|
-
}
|
|
50159
|
+
var external_http_ = __webpack_require__("http");
|
|
50370
50160
|
const external_node_url_namespaceObject = require("node:url");
|
|
50371
50161
|
var external_node_child_process_ = __webpack_require__("node:child_process");
|
|
50372
50162
|
let isDockerCached;
|
|
@@ -50829,2024 +50619,2234 @@ var __webpack_exports__ = {};
|
|
|
50829
50619
|
defineLazyProperty(open_apps, 'browser', ()=>'browser');
|
|
50830
50620
|
defineLazyProperty(open_apps, 'browserPrivate', ()=>'browserPrivate');
|
|
50831
50621
|
const node_modules_open = open_open;
|
|
50832
|
-
const
|
|
50833
|
-
|
|
50834
|
-
|
|
50835
|
-
|
|
50836
|
-
|
|
50837
|
-
|
|
50838
|
-
|
|
50839
|
-
|
|
50840
|
-
|
|
50841
|
-
|
|
50842
|
-
|
|
50843
|
-
|
|
50844
|
-
|
|
50845
|
-
|
|
50846
|
-
|
|
50622
|
+
const successPage = `<!DOCTYPE html>
|
|
50623
|
+
<html lang="en">
|
|
50624
|
+
<head>
|
|
50625
|
+
<meta charset="utf-8" />
|
|
50626
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
50627
|
+
<title>Anvil Sync • Login Complete</title>
|
|
50628
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
50629
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
50630
|
+
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
50631
|
+
<style>
|
|
50632
|
+
* { box-sizing: border-box; }
|
|
50633
|
+
html, body { height: 100%; margin: 0; }
|
|
50634
|
+
body {
|
|
50635
|
+
font-family: Poppins, -apple-system, system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
50636
|
+
color: #555555;
|
|
50637
|
+
background: #f7fdff;
|
|
50638
|
+
display: flex;
|
|
50639
|
+
align-items: center;
|
|
50640
|
+
justify-content: center;
|
|
50641
|
+
padding: 20px;
|
|
50847
50642
|
}
|
|
50848
|
-
|
|
50849
|
-
|
|
50850
|
-
|
|
50851
|
-
|
|
50852
|
-
|
|
50853
|
-
|
|
50854
|
-
|
|
50855
|
-
let escaping = false;
|
|
50856
|
-
for(let i = 0; i < input.length; i += 1){
|
|
50857
|
-
const ch = input[i];
|
|
50858
|
-
if (escaping) {
|
|
50859
|
-
current += ch;
|
|
50860
|
-
escaping = false;
|
|
50861
|
-
continue;
|
|
50862
|
-
}
|
|
50863
|
-
if ("\\" === ch) {
|
|
50864
|
-
escaping = true;
|
|
50865
|
-
continue;
|
|
50866
|
-
}
|
|
50867
|
-
if (quote) {
|
|
50868
|
-
if (ch === quote) quote = null;
|
|
50869
|
-
else current += ch;
|
|
50870
|
-
continue;
|
|
50871
|
-
}
|
|
50872
|
-
if ('"' === ch || "'" === ch) {
|
|
50873
|
-
quote = ch;
|
|
50874
|
-
continue;
|
|
50875
|
-
}
|
|
50876
|
-
if (/\s/.test(ch)) {
|
|
50877
|
-
if (current) {
|
|
50878
|
-
tokens.push(current);
|
|
50879
|
-
current = "";
|
|
50880
|
-
}
|
|
50881
|
-
continue;
|
|
50882
|
-
}
|
|
50883
|
-
current += ch;
|
|
50643
|
+
.card {
|
|
50644
|
+
width: min(480px, 100%);
|
|
50645
|
+
background: #ffffff;
|
|
50646
|
+
border-radius: 12px;
|
|
50647
|
+
padding: 48px 40px;
|
|
50648
|
+
text-align: center;
|
|
50649
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
50884
50650
|
}
|
|
50885
|
-
|
|
50886
|
-
|
|
50887
|
-
|
|
50888
|
-
|
|
50889
|
-
|
|
50890
|
-
|
|
50891
|
-
|
|
50892
|
-
|
|
50893
|
-
|
|
50894
|
-
|
|
50895
|
-
|
|
50896
|
-
|
|
50897
|
-
|
|
50898
|
-
|
|
50899
|
-
|
|
50900
|
-
|
|
50651
|
+
.icon {
|
|
50652
|
+
display: inline-flex;
|
|
50653
|
+
align-items: center;
|
|
50654
|
+
justify-content: center;
|
|
50655
|
+
width: 56px;
|
|
50656
|
+
height: 56px;
|
|
50657
|
+
margin: 0 auto 24px;
|
|
50658
|
+
border-radius: 50%;
|
|
50659
|
+
background: #1bb0ee;
|
|
50660
|
+
color: white;
|
|
50661
|
+
}
|
|
50662
|
+
.icon svg {
|
|
50663
|
+
width: 28px;
|
|
50664
|
+
height: 28px;
|
|
50665
|
+
}
|
|
50666
|
+
h1 {
|
|
50667
|
+
font-size: 24px;
|
|
50668
|
+
font-weight: 600;
|
|
50669
|
+
line-height: 1.3;
|
|
50670
|
+
margin: 0 0 12px;
|
|
50671
|
+
color: #1a1a1a;
|
|
50672
|
+
}
|
|
50673
|
+
p {
|
|
50674
|
+
color: #555555;
|
|
50675
|
+
font-size: 15px;
|
|
50676
|
+
line-height: 1.5;
|
|
50677
|
+
margin: 0;
|
|
50678
|
+
}
|
|
50679
|
+
</style>
|
|
50680
|
+
</head>
|
|
50681
|
+
<body>
|
|
50682
|
+
<main class="card" role="status" aria-live="polite">
|
|
50683
|
+
<div class="icon" aria-hidden="true">
|
|
50684
|
+
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
50685
|
+
<path d="M20 7L9 18l-5-5" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
50686
|
+
</svg>
|
|
50687
|
+
</div>
|
|
50688
|
+
<h1>Anvil login complete</h1>
|
|
50689
|
+
<p>Close this window and return to your terminal to continue.</p>
|
|
50690
|
+
</main>
|
|
50691
|
+
</body>
|
|
50692
|
+
</html>`;
|
|
50693
|
+
function errorPage(errorMsg) {
|
|
50694
|
+
return `<!DOCTYPE html>
|
|
50695
|
+
<html lang="en">
|
|
50696
|
+
<head>
|
|
50697
|
+
<meta charset="utf-8" />
|
|
50698
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
50699
|
+
<title>Anvil Sync • Login Error</title>
|
|
50700
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
50701
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
50702
|
+
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
50703
|
+
<style>
|
|
50704
|
+
* { box-sizing: border-box; }
|
|
50705
|
+
html, body { height: 100%; margin: 0; }
|
|
50706
|
+
body {
|
|
50707
|
+
font-family: Poppins, -apple-system, system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
50708
|
+
color: #555555;
|
|
50709
|
+
background: #f7fdff;
|
|
50710
|
+
display: flex;
|
|
50711
|
+
align-items: center;
|
|
50712
|
+
justify-content: center;
|
|
50713
|
+
padding: 20px;
|
|
50714
|
+
}
|
|
50715
|
+
.card {
|
|
50716
|
+
width: min(480px, 100%);
|
|
50717
|
+
background: #ffffff;
|
|
50718
|
+
border-radius: 12px;
|
|
50719
|
+
padding: 48px 40px;
|
|
50720
|
+
text-align: center;
|
|
50721
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
50722
|
+
}
|
|
50723
|
+
.icon {
|
|
50724
|
+
display: inline-flex;
|
|
50725
|
+
align-items: center;
|
|
50726
|
+
justify-content: center;
|
|
50727
|
+
width: 56px;
|
|
50728
|
+
height: 56px;
|
|
50729
|
+
margin: 0 auto 24px;
|
|
50730
|
+
border-radius: 50%;
|
|
50731
|
+
background: #ef4444;
|
|
50732
|
+
color: white;
|
|
50733
|
+
}
|
|
50734
|
+
.icon svg {
|
|
50735
|
+
width: 28px;
|
|
50736
|
+
height: 28px;
|
|
50737
|
+
}
|
|
50738
|
+
h1 {
|
|
50739
|
+
font-size: 24px;
|
|
50740
|
+
font-weight: 600;
|
|
50741
|
+
line-height: 1.3;
|
|
50742
|
+
margin: 0 0 16px;
|
|
50743
|
+
color: #1a1a1a;
|
|
50744
|
+
}
|
|
50745
|
+
p {
|
|
50746
|
+
color: #555555;
|
|
50747
|
+
font-size: 15px;
|
|
50748
|
+
line-height: 1.5;
|
|
50749
|
+
margin: 16px 0 0;
|
|
50750
|
+
}
|
|
50751
|
+
.details {
|
|
50752
|
+
color: #666666;
|
|
50753
|
+
font-size: 13px;
|
|
50754
|
+
word-break: break-word;
|
|
50755
|
+
font-weight: 500;
|
|
50756
|
+
font-family: ui-monospace, "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace;
|
|
50757
|
+
margin: 16px 0 0;
|
|
50758
|
+
padding: 12px;
|
|
50759
|
+
background: #f8f9fa;
|
|
50760
|
+
border-radius: 6px;
|
|
50761
|
+
text-align: left;
|
|
50762
|
+
}
|
|
50763
|
+
</style>
|
|
50764
|
+
</head>
|
|
50765
|
+
<body>
|
|
50766
|
+
<main class="card" role="status" aria-live="polite">
|
|
50767
|
+
<div class="icon" aria-hidden="true">
|
|
50768
|
+
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
50769
|
+
<path d="M15 9l-6 6M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
50770
|
+
</svg>
|
|
50771
|
+
</div>
|
|
50772
|
+
<h1>Anvil login failed</h1>
|
|
50773
|
+
<div id="details" class="details">${errorMsg}</div>
|
|
50774
|
+
<p>Close this window and return to your terminal to retry.</p>
|
|
50775
|
+
</main>
|
|
50776
|
+
</body>
|
|
50777
|
+
</html>`;
|
|
50901
50778
|
}
|
|
50902
|
-
|
|
50903
|
-
|
|
50904
|
-
|
|
50905
|
-
return
|
|
50779
|
+
const CLIENT_ID = "anvil-sync";
|
|
50780
|
+
const SCOPES = "apps:read apps:write user:read";
|
|
50781
|
+
function oauth_login_base64url(buf) {
|
|
50782
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
50906
50783
|
}
|
|
50907
|
-
|
|
50908
|
-
|
|
50909
|
-
const tokens = parseCommandTokens(preferredEditorCommand);
|
|
50910
|
-
if (0 === tokens.length) throw new Error("empty command");
|
|
50911
|
-
await deps.spawnShellCommand(buildShellCommandLine([
|
|
50912
|
-
...tokens,
|
|
50913
|
-
targetPath
|
|
50914
|
-
]));
|
|
50915
|
-
return;
|
|
50916
|
-
} catch (error) {
|
|
50917
|
-
logger_logger.warn(`Failed to open ${formatFallbackEditorLabel(preferredEditorCommand)} automatically: ${errors_getErrorMessage(error)}.`);
|
|
50918
|
-
}
|
|
50919
|
-
else logger_logger.info(`Open ${formatFallbackEditorLabel(preferredEditorCommand)} to edit your app.`);
|
|
50920
|
-
await deps.openSystem(targetPath);
|
|
50784
|
+
function sleep(ms) {
|
|
50785
|
+
return new Promise((resolve)=>setTimeout(resolve, ms));
|
|
50921
50786
|
}
|
|
50922
|
-
function
|
|
50923
|
-
if (
|
|
50924
|
-
|
|
50925
|
-
|
|
50926
|
-
};
|
|
50927
|
-
if (availableUrls.length > 1) return {
|
|
50928
|
-
source: "available-multiple",
|
|
50929
|
-
urls: availableUrls
|
|
50930
|
-
};
|
|
50931
|
-
if (1 === availableUrls.length) return {
|
|
50932
|
-
source: "available-single",
|
|
50933
|
-
url: availableUrls[0]
|
|
50787
|
+
function createBrowserLaunchPrompt(authUrl) {
|
|
50788
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return {
|
|
50789
|
+
waitForOpen: Promise.resolve(false),
|
|
50790
|
+
close: ()=>{}
|
|
50934
50791
|
};
|
|
50935
|
-
const
|
|
50936
|
-
|
|
50937
|
-
|
|
50938
|
-
|
|
50792
|
+
const rl = external_readline_namespaceObject.createInterface({
|
|
50793
|
+
input: process.stdin,
|
|
50794
|
+
output: process.stdout
|
|
50795
|
+
});
|
|
50796
|
+
let closed = false;
|
|
50797
|
+
let resolveWait = null;
|
|
50798
|
+
const close = (opened = false)=>{
|
|
50799
|
+
if (closed) return;
|
|
50800
|
+
closed = true;
|
|
50801
|
+
rl.close();
|
|
50802
|
+
resolveWait?.(opened);
|
|
50803
|
+
resolveWait = null;
|
|
50939
50804
|
};
|
|
50805
|
+
const waitForOpen = new Promise((resolve)=>{
|
|
50806
|
+
resolveWait = resolve;
|
|
50807
|
+
rl.on("SIGINT", ()=>{
|
|
50808
|
+
close();
|
|
50809
|
+
process.kill(process.pid, "SIGINT");
|
|
50810
|
+
});
|
|
50811
|
+
rl.question("", async ()=>{
|
|
50812
|
+
try {
|
|
50813
|
+
await node_modules_open(authUrl);
|
|
50814
|
+
logger_logger.info(chalk_source.dim("Opened your browser to continue login."));
|
|
50815
|
+
close(true);
|
|
50816
|
+
} catch {
|
|
50817
|
+
logger_logger.warn("Could not open a browser automatically.");
|
|
50818
|
+
close(false);
|
|
50819
|
+
}
|
|
50820
|
+
});
|
|
50821
|
+
});
|
|
50940
50822
|
return {
|
|
50941
|
-
|
|
50942
|
-
|
|
50823
|
+
waitForOpen,
|
|
50824
|
+
close
|
|
50943
50825
|
};
|
|
50944
50826
|
}
|
|
50945
|
-
function
|
|
50946
|
-
|
|
50947
|
-
|
|
50948
|
-
|
|
50949
|
-
|
|
50950
|
-
|
|
50951
|
-
|
|
50952
|
-
}
|
|
50953
|
-
|
|
50954
|
-
|
|
50955
|
-
|
|
50956
|
-
|
|
50957
|
-
|
|
50958
|
-
};
|
|
50959
|
-
}
|
|
50960
|
-
async function resolveUsernameForUrl(anvilUrl, explicitUsername, promptMessage = "Multiple accounts found. Which account owns this app?") {
|
|
50961
|
-
if (explicitUsername) return explicitUsername;
|
|
50962
|
-
const decision = decideUsernameForUrl(auth_getAccountsForUrl(anvilUrl));
|
|
50963
|
-
if ("none" === decision.source) return;
|
|
50964
|
-
if ("single" === decision.source) {
|
|
50965
|
-
logger_logger.verbose(chalk_source.cyan("Auto-selected account: ") + chalk_source.bold(decision.username));
|
|
50966
|
-
return decision.username;
|
|
50827
|
+
async function parseOAuthError(response) {
|
|
50828
|
+
const contentType = response.headers.get("content-type") || "";
|
|
50829
|
+
if (contentType.includes("application/json")) {
|
|
50830
|
+
const data = await response.json();
|
|
50831
|
+
return data.error || `HTTP ${response.status}`;
|
|
50832
|
+
}
|
|
50833
|
+
const text = (await response.text()).trim();
|
|
50834
|
+
if (!text) return `HTTP ${response.status}`;
|
|
50835
|
+
try {
|
|
50836
|
+
const parsed = JSON.parse(text);
|
|
50837
|
+
return parsed.error || text;
|
|
50838
|
+
} catch {
|
|
50839
|
+
return text;
|
|
50967
50840
|
}
|
|
50968
|
-
const choices = decision.usernames.map((acct)=>({
|
|
50969
|
-
name: acct,
|
|
50970
|
-
value: acct
|
|
50971
|
-
}));
|
|
50972
|
-
choices.push({
|
|
50973
|
-
name: "Cancel",
|
|
50974
|
-
value: null
|
|
50975
|
-
});
|
|
50976
|
-
return logger_logger.select(promptMessage, choices, decision.usernames[0]);
|
|
50977
|
-
}
|
|
50978
|
-
async function confirmReverseLookupWithResolvedUser(anvilUrl, username) {
|
|
50979
|
-
const resolvedUsername = await resolveUsernameForUrl(anvilUrl, username, `Multiple accounts found for ${anvilUrl}. Which account should be used for app lookup?`);
|
|
50980
|
-
if (null === resolvedUsername) return null;
|
|
50981
|
-
const shouldContinue = await logger_logger.confirm(`Search ${anvilUrl} ${resolvedUsername ? `for ${resolvedUsername}` : ""} for matching app IDs? This is slower than detecting the app ID from local git remotes.`, true);
|
|
50982
|
-
return {
|
|
50983
|
-
username: resolvedUsername,
|
|
50984
|
-
shouldContinue
|
|
50985
|
-
};
|
|
50986
50841
|
}
|
|
50987
|
-
async function
|
|
50988
|
-
const
|
|
50989
|
-
|
|
50990
|
-
|
|
50991
|
-
|
|
50992
|
-
|
|
50993
|
-
|
|
50994
|
-
|
|
50995
|
-
|
|
50996
|
-
|
|
50842
|
+
async function exchangeAuthorizationCodeForTokens(anvilUrl, redirectUri, code, codeVerifier) {
|
|
50843
|
+
const tokenResponse = await fetch(`${anvilUrl}/oauth/token`, {
|
|
50844
|
+
method: "POST",
|
|
50845
|
+
headers: {
|
|
50846
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
50847
|
+
},
|
|
50848
|
+
body: new URLSearchParams({
|
|
50849
|
+
grant_type: "authorization_code",
|
|
50850
|
+
code,
|
|
50851
|
+
redirect_uri: redirectUri,
|
|
50852
|
+
client_id: CLIENT_ID,
|
|
50853
|
+
code_verifier: codeVerifier
|
|
50854
|
+
})
|
|
50997
50855
|
});
|
|
50998
|
-
|
|
50999
|
-
return
|
|
51000
|
-
}
|
|
51001
|
-
const defaultConfigureWatchGitAuthDeps = {
|
|
51002
|
-
configureCredentialHelperForUrl: configureCredentialHelperForUrl,
|
|
51003
|
-
setAppAuthBinding: setAppAuthBinding
|
|
51004
|
-
};
|
|
51005
|
-
const defaultSyncStartDeps = {
|
|
51006
|
-
pushToAnvil,
|
|
51007
|
-
recreateSessionAndValidate,
|
|
51008
|
-
recheckSyncStatus,
|
|
51009
|
-
startWatchingWithEventHandlers
|
|
51010
|
-
};
|
|
51011
|
-
function resolveWatchOpenPath(repoPath) {
|
|
51012
|
-
return external_path_default().resolve(repoPath);
|
|
51013
|
-
}
|
|
51014
|
-
async function openWatchPath(targetPath, deps) {
|
|
51015
|
-
const preferredEditor = String(getConfig("preferredEditor") || "").trim();
|
|
51016
|
-
const preferredEditorCommand = preferredEditor ? getPreferredEditorCommand(preferredEditor) : "";
|
|
51017
|
-
await openPathInEditorOrDefault(targetPath, preferredEditorCommand, deps);
|
|
51018
|
-
logger_logger.info(chalk_source.gray(`Opened ${targetPath}`));
|
|
50856
|
+
if (!tokenResponse.ok) throw new Error(`Failed to exchange authorization code for token. ${await parseOAuthError(tokenResponse)}`);
|
|
50857
|
+
return await tokenResponse.json();
|
|
51019
50858
|
}
|
|
51020
|
-
async function
|
|
51021
|
-
await
|
|
51022
|
-
|
|
51023
|
-
|
|
51024
|
-
|
|
50859
|
+
async function requestDeviceAuthorization(anvilUrl) {
|
|
50860
|
+
const response = await fetch(`${anvilUrl}/oauth/device_authorization`, {
|
|
50861
|
+
method: "POST",
|
|
50862
|
+
headers: {
|
|
50863
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
50864
|
+
},
|
|
50865
|
+
body: new URLSearchParams({
|
|
50866
|
+
client_id: CLIENT_ID,
|
|
50867
|
+
scope: SCOPES
|
|
50868
|
+
})
|
|
51025
50869
|
});
|
|
50870
|
+
if (!response.ok) throw new Error(`Failed to start device login. ${await parseOAuthError(response)}`);
|
|
50871
|
+
return await response.json();
|
|
51026
50872
|
}
|
|
51027
|
-
function
|
|
51028
|
-
const
|
|
51029
|
-
|
|
51030
|
-
|
|
51031
|
-
|
|
51032
|
-
|
|
51033
|
-
|
|
51034
|
-
|
|
51035
|
-
|
|
51036
|
-
|
|
51037
|
-
|
|
51038
|
-
|
|
51039
|
-
|
|
51040
|
-
|
|
51041
|
-
|
|
51042
|
-
resolve(null);
|
|
51043
|
-
};
|
|
51044
|
-
rl.on("SIGINT", handleInterrupt);
|
|
51045
|
-
rl.question(chalk_source.cyan("Please enter app ID manually (or press Enter to exit): "), (answer)=>{
|
|
51046
|
-
rl.removeListener("SIGINT", handleInterrupt);
|
|
51047
|
-
rl.close();
|
|
51048
|
-
const trimmed = answer.trim();
|
|
51049
|
-
trimmed ? resolve(trimmed) : resolve(null);
|
|
51050
|
-
});
|
|
50873
|
+
async function pollDeviceAuthorization(anvilUrl, deviceAuth, options) {
|
|
50874
|
+
const expiresAt = Date.now() + 1000 * deviceAuth.expires_in;
|
|
50875
|
+
let intervalMs = 1000 * Math.max(deviceAuth.interval ?? 5, 1);
|
|
50876
|
+
while(Date.now() < expiresAt){
|
|
50877
|
+
if (options?.isCancelled?.()) throw new Error("cancelled");
|
|
50878
|
+
const response = await fetch(`${anvilUrl}/oauth/token`, {
|
|
50879
|
+
method: "POST",
|
|
50880
|
+
headers: {
|
|
50881
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
50882
|
+
},
|
|
50883
|
+
body: new URLSearchParams({
|
|
50884
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
50885
|
+
device_code: deviceAuth.device_code,
|
|
50886
|
+
client_id: CLIENT_ID
|
|
50887
|
+
})
|
|
51051
50888
|
});
|
|
51052
|
-
if (
|
|
51053
|
-
|
|
51054
|
-
|
|
51055
|
-
|
|
51056
|
-
|
|
51057
|
-
return null;
|
|
51058
|
-
}
|
|
51059
|
-
if (1 === candidates.length) {
|
|
51060
|
-
const [candidate] = candidates;
|
|
51061
|
-
logger_logger.success("Auto-selected detected app ID: " + chalk_source.bold(candidate.appId));
|
|
51062
|
-
return candidate;
|
|
51063
|
-
}
|
|
51064
|
-
const choices = candidates.map((candidate, index)=>({
|
|
51065
|
-
name: formatCandidateLabel(candidate),
|
|
51066
|
-
value: index
|
|
51067
|
-
}));
|
|
51068
|
-
choices.push({
|
|
51069
|
-
name: "Cancel",
|
|
51070
|
-
value: null
|
|
51071
|
-
});
|
|
51072
|
-
try {
|
|
51073
|
-
const answer = await logger_logger.prompt([
|
|
51074
|
-
{
|
|
51075
|
-
type: "list",
|
|
51076
|
-
name: "appId",
|
|
51077
|
-
message: "Select an app ID:",
|
|
51078
|
-
choices: choices,
|
|
51079
|
-
pageSize: 10
|
|
51080
|
-
}
|
|
51081
|
-
]);
|
|
51082
|
-
if (null === answer.appId) {
|
|
51083
|
-
const rl = external_readline_namespaceObject.createInterface({
|
|
51084
|
-
input: process.stdin,
|
|
51085
|
-
output: process.stdout
|
|
51086
|
-
});
|
|
51087
|
-
const manualAppId = await new Promise((resolve)=>{
|
|
51088
|
-
rl.question(chalk_source.cyan("Please enter app ID manually (or press Enter to exit): "), (answer)=>{
|
|
51089
|
-
rl.close();
|
|
51090
|
-
const trimmed = answer.trim();
|
|
51091
|
-
trimmed ? resolve(trimmed) : resolve(null);
|
|
51092
|
-
});
|
|
51093
|
-
});
|
|
51094
|
-
if (manualAppId) return {
|
|
51095
|
-
appId: manualAppId,
|
|
51096
|
-
source: "config",
|
|
51097
|
-
description: "Manual entry"
|
|
51098
|
-
};
|
|
51099
|
-
return null;
|
|
50889
|
+
if (response.ok) return await response.json();
|
|
50890
|
+
const error = await parseOAuthError(response);
|
|
50891
|
+
if ("authorization_pending" === error) {
|
|
50892
|
+
await sleep(intervalMs);
|
|
50893
|
+
continue;
|
|
51100
50894
|
}
|
|
51101
|
-
|
|
51102
|
-
|
|
51103
|
-
|
|
51104
|
-
|
|
51105
|
-
|
|
51106
|
-
|
|
51107
|
-
|
|
51108
|
-
|
|
51109
|
-
try {
|
|
51110
|
-
const authToken = await auth_getValidAuthToken(options.anvilUrl, options.username);
|
|
51111
|
-
const pushUrl = getGitPushUrl(options.appId, authToken, options.anvilUrl);
|
|
51112
|
-
const git = new GitService(options.repoPath);
|
|
51113
|
-
const refSpec = `${options.branchName}:${options.branchName}`;
|
|
51114
|
-
logger_logger.progress("push", "Pushing to Anvil...");
|
|
51115
|
-
await git.push(pushUrl, refSpec, options.force ?? false);
|
|
51116
|
-
logger_logger.progressEnd("push", "Pushed to Anvil");
|
|
51117
|
-
return {
|
|
51118
|
-
success: true,
|
|
51119
|
-
staleRemote: false
|
|
51120
|
-
};
|
|
51121
|
-
} catch (e) {
|
|
51122
|
-
const errorMessage = errors_getErrorMessage(e);
|
|
51123
|
-
logger_logger.progressEnd("push", "Push failed");
|
|
51124
|
-
logger_logger.error(`Failed to push: ${errorMessage}`);
|
|
51125
|
-
return {
|
|
51126
|
-
success: false,
|
|
51127
|
-
staleRemote: isStaleRemotePushError(errorMessage),
|
|
51128
|
-
errorMessage
|
|
51129
|
-
};
|
|
50895
|
+
if ("slow_down" === error) {
|
|
50896
|
+
intervalMs += 1000;
|
|
50897
|
+
await sleep(intervalMs);
|
|
50898
|
+
continue;
|
|
50899
|
+
}
|
|
50900
|
+
if ("access_denied" === error) throw new Error("Device login was denied.");
|
|
50901
|
+
if ("expired_token" === error || "invalid_grant" === error) break;
|
|
50902
|
+
throw new Error(`Device login failed. ${error}`);
|
|
51130
50903
|
}
|
|
50904
|
+
throw new Error("Device login expired before it was approved.");
|
|
51131
50905
|
}
|
|
51132
|
-
async function
|
|
51133
|
-
const
|
|
51134
|
-
const
|
|
51135
|
-
|
|
51136
|
-
|
|
51137
|
-
|
|
51138
|
-
|
|
51139
|
-
|
|
51140
|
-
|
|
51141
|
-
|
|
51142
|
-
|
|
51143
|
-
|
|
51144
|
-
|
|
51145
|
-
|
|
51146
|
-
|
|
51147
|
-
|
|
51148
|
-
|
|
51149
|
-
|
|
51150
|
-
|
|
51151
|
-
|
|
51152
|
-
|
|
51153
|
-
|
|
51154
|
-
|
|
51155
|
-
|
|
51156
|
-
|
|
51157
|
-
|
|
51158
|
-
|
|
51159
|
-
|
|
51160
|
-
|
|
51161
|
-
|
|
51162
|
-
|
|
51163
|
-
|
|
51164
|
-
} catch (e) {
|
|
51165
|
-
const msg = errors_getErrorMessage(e);
|
|
51166
|
-
if (msg.includes("rebase") && (msg.includes("conflict") || msg.includes("CONFLICT") || msg.includes("could not apply"))) {
|
|
51167
|
-
try {
|
|
51168
|
-
await git.rebaseAbort();
|
|
51169
|
-
} catch {}
|
|
51170
|
-
await git.deleteRef(`refs/heads/${tempRef}`);
|
|
51171
|
-
if (didStash) try {
|
|
51172
|
-
await git.stashPop();
|
|
51173
|
-
} catch {
|
|
51174
|
-
logger_logger.warn("Your uncommitted changes are in the stash. Run: git stash pop");
|
|
50906
|
+
async function createPkceLoginFlow(anvilUrl) {
|
|
50907
|
+
const codeVerifier = external_crypto_.randomBytes(48).toString("hex");
|
|
50908
|
+
const codeChallenge = oauth_login_base64url(external_crypto_.createHash("sha256").update(codeVerifier, "ascii").digest());
|
|
50909
|
+
const state = external_crypto_.randomBytes(16).toString("hex");
|
|
50910
|
+
const server = external_http_.createServer();
|
|
50911
|
+
await new Promise((resolve)=>server.listen(0, "127.0.0.1", resolve));
|
|
50912
|
+
const address = server.address();
|
|
50913
|
+
if (!address || "string" == typeof address) throw new Error("No address");
|
|
50914
|
+
const port = address.port;
|
|
50915
|
+
const redirectUri = `http://127.0.0.1:${port}/oauth-callback`;
|
|
50916
|
+
const authUrl = new URL(`${anvilUrl}/oauth/authorize`);
|
|
50917
|
+
authUrl.searchParams.set("response_type", "code");
|
|
50918
|
+
authUrl.searchParams.set("client_id", CLIENT_ID);
|
|
50919
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
50920
|
+
authUrl.searchParams.set("scope", SCOPES);
|
|
50921
|
+
authUrl.searchParams.set("state", state);
|
|
50922
|
+
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
50923
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
50924
|
+
const codePromise = new Promise((resolve, reject)=>{
|
|
50925
|
+
server.on("request", (req, res)=>{
|
|
50926
|
+
if (!req.url) return;
|
|
50927
|
+
const url = new URL(req.url, `http://127.0.0.1:${port}`);
|
|
50928
|
+
if ("/oauth-callback" !== url.pathname) return;
|
|
50929
|
+
const code = url.searchParams.get("code") || void 0;
|
|
50930
|
+
const error = url.searchParams.get("error") || void 0;
|
|
50931
|
+
const recvState = url.searchParams.get("state");
|
|
50932
|
+
if (!recvState || !code && !error) {
|
|
50933
|
+
res.statusCode = 400;
|
|
50934
|
+
res.end("Missing code, error or state");
|
|
50935
|
+
reject(new Error("Missing code/state/error"));
|
|
50936
|
+
server.close();
|
|
50937
|
+
return;
|
|
51175
50938
|
}
|
|
51176
|
-
|
|
51177
|
-
|
|
51178
|
-
|
|
51179
|
-
|
|
51180
|
-
|
|
51181
|
-
|
|
51182
|
-
|
|
51183
|
-
|
|
51184
|
-
|
|
51185
|
-
|
|
51186
|
-
|
|
51187
|
-
}
|
|
51188
|
-
|
|
51189
|
-
|
|
51190
|
-
|
|
51191
|
-
|
|
51192
|
-
|
|
51193
|
-
|
|
51194
|
-
|
|
50939
|
+
res.statusCode = 200;
|
|
50940
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
50941
|
+
if (code) res.end(successPage);
|
|
50942
|
+
else res.end(errorPage(error || "unknown"));
|
|
50943
|
+
resolve({
|
|
50944
|
+
code,
|
|
50945
|
+
error,
|
|
50946
|
+
recvState
|
|
50947
|
+
});
|
|
50948
|
+
server.closeAllConnections();
|
|
50949
|
+
server.close();
|
|
50950
|
+
});
|
|
50951
|
+
});
|
|
50952
|
+
let closed = false;
|
|
50953
|
+
server.on("close", ()=>{
|
|
50954
|
+
closed = true;
|
|
50955
|
+
});
|
|
50956
|
+
const close = async ()=>{
|
|
50957
|
+
if (closed) return;
|
|
50958
|
+
closed = true;
|
|
50959
|
+
server.closeAllConnections();
|
|
50960
|
+
if (!server.listening) return;
|
|
50961
|
+
await new Promise((resolve)=>server.close(()=>resolve()));
|
|
50962
|
+
};
|
|
50963
|
+
return {
|
|
50964
|
+
authUrl,
|
|
50965
|
+
close,
|
|
50966
|
+
waitForLogin: (async ()=>{
|
|
50967
|
+
const { code, error, recvState } = await codePromise;
|
|
50968
|
+
if (recvState !== state) throw new Error("Invalid state received from OAuth callback");
|
|
50969
|
+
if (error) throw new Error(`Error received from OAuth callback: ${error}`);
|
|
50970
|
+
const tokenData = await exchangeAuthorizationCodeForTokens(anvilUrl, redirectUri, code, codeVerifier);
|
|
50971
|
+
return login(anvilUrl, {
|
|
50972
|
+
access_token: tokenData.access_token,
|
|
50973
|
+
refresh_token: tokenData.refresh_token,
|
|
50974
|
+
expires_in: tokenData.expires_in,
|
|
50975
|
+
scope: tokenData.scope
|
|
50976
|
+
});
|
|
50977
|
+
})()
|
|
50978
|
+
};
|
|
51195
50979
|
}
|
|
51196
|
-
|
|
51197
|
-
|
|
51198
|
-
|
|
50980
|
+
function raceLoginAttempts(attempts) {
|
|
50981
|
+
return new Promise((resolve, reject)=>{
|
|
50982
|
+
let remaining = attempts.length;
|
|
50983
|
+
let lastError = null;
|
|
50984
|
+
for (const attempt of attempts)attempt.then(resolve).catch((error)=>{
|
|
50985
|
+
if ("cancelled" === error.message) return;
|
|
50986
|
+
remaining -= 1;
|
|
50987
|
+
lastError = error;
|
|
50988
|
+
if (0 === remaining && lastError) reject(lastError);
|
|
50989
|
+
});
|
|
50990
|
+
});
|
|
50991
|
+
}
|
|
50992
|
+
async function runInteractiveLoginFlow(anvilUrl) {
|
|
50993
|
+
const pkceFlow = await createPkceLoginFlow(anvilUrl);
|
|
50994
|
+
const deviceAuth = await requestDeviceAuthorization(anvilUrl);
|
|
50995
|
+
const browserPrompt = createBrowserLaunchPrompt(pkceFlow.authUrl.toString());
|
|
50996
|
+
let settled = false;
|
|
50997
|
+
let spinnerStarted = false;
|
|
50998
|
+
logger_logger.info(chalk_source.dim(`Logging in to ${anvilUrl}`));
|
|
50999
|
+
console.log();
|
|
51000
|
+
logger_logger.info(chalk_source.dim("Visit:"), `${deviceAuth.verification_uri_complete || deviceAuth.verification_uri}`);
|
|
51001
|
+
logger_logger.info(chalk_source.dim("Device code:"), `${deviceAuth.user_code}`);
|
|
51002
|
+
console.log();
|
|
51003
|
+
logger_logger.info("OR Press ENTER to open a browser...");
|
|
51004
|
+
browserPrompt.waitForOpen.then((opened)=>{
|
|
51005
|
+
if (opened && !settled) {
|
|
51006
|
+
spinnerStarted = true;
|
|
51007
|
+
logger_logger.progress("login", "Waiting for login to complete...");
|
|
51008
|
+
}
|
|
51009
|
+
});
|
|
51199
51010
|
try {
|
|
51200
|
-
const
|
|
51201
|
-
|
|
51202
|
-
|
|
51203
|
-
|
|
51204
|
-
|
|
51205
|
-
|
|
51206
|
-
|
|
51207
|
-
|
|
51208
|
-
|
|
51209
|
-
|
|
51210
|
-
|
|
51211
|
-
|
|
51212
|
-
|
|
51213
|
-
|
|
51011
|
+
const result = await raceLoginAttempts([
|
|
51012
|
+
pkceFlow.waitForLogin,
|
|
51013
|
+
pollDeviceAuthorization(anvilUrl, deviceAuth, {
|
|
51014
|
+
isCancelled: ()=>settled
|
|
51015
|
+
}).then(async (tokenData)=>login(anvilUrl, {
|
|
51016
|
+
access_token: tokenData.access_token,
|
|
51017
|
+
refresh_token: tokenData.refresh_token,
|
|
51018
|
+
expires_in: tokenData.expires_in,
|
|
51019
|
+
scope: tokenData.scope
|
|
51020
|
+
}))
|
|
51021
|
+
]);
|
|
51022
|
+
settled = true;
|
|
51023
|
+
return result;
|
|
51024
|
+
} finally{
|
|
51025
|
+
settled = true;
|
|
51026
|
+
browserPrompt.close();
|
|
51027
|
+
if (spinnerStarted) logger_logger.progressEnd("login");
|
|
51028
|
+
await pkceFlow.close();
|
|
51214
51029
|
}
|
|
51215
51030
|
}
|
|
51216
|
-
function
|
|
51217
|
-
|
|
51218
|
-
if (syncStatus?.diverged) return "diverged";
|
|
51219
|
-
if (syncStatus?.ahead && !syncStatus?.behind) return "ahead";
|
|
51220
|
-
if (syncStatus?.behind && !syncStatus?.ahead) return "behind";
|
|
51221
|
-
if (syncStatus?.ahead && syncStatus?.behind) return "diverged";
|
|
51222
|
-
return "in-sync";
|
|
51031
|
+
function getUrlConfigKey(appId) {
|
|
51032
|
+
return `anvil.auth.${appId}.url`;
|
|
51223
51033
|
}
|
|
51224
|
-
|
|
51225
|
-
|
|
51226
|
-
logger_logger.progress("verify", "Verifying repository status...");
|
|
51227
|
-
const freshSession = await api_watch(options.repoPath, options.appId, options.anvilUrl, options.stagedOnly, options.username);
|
|
51228
|
-
logger_logger.progressEnd("verify");
|
|
51229
|
-
const currentCategory = getSyncStateCategory(freshSession.syncStatus);
|
|
51230
|
-
const currentBranch = freshSession.getBranchName() || "master";
|
|
51231
|
-
if (currentCategory !== previousCategory || currentBranch !== previousBranch) {
|
|
51232
|
-
logger_logger.warn("Repository status has changed. Re-evaluating...");
|
|
51233
|
-
return freshSession;
|
|
51234
|
-
}
|
|
51235
|
-
freshSession.cleanup();
|
|
51236
|
-
return null;
|
|
51237
|
-
} catch (e) {
|
|
51238
|
-
logger_logger.progressEnd("verify");
|
|
51239
|
-
logger_logger.verbose(chalk_source.gray(`Could not re-verify status: ${errors_getErrorMessage(e)}`));
|
|
51240
|
-
return null;
|
|
51241
|
-
}
|
|
51034
|
+
function getUsernameConfigKey(appId) {
|
|
51035
|
+
return `anvil.auth.${appId}.username`;
|
|
51242
51036
|
}
|
|
51243
|
-
async function
|
|
51244
|
-
logger_logger.progress("init", "Restarting watch session...");
|
|
51037
|
+
async function getLocalConfigValue(repoPath, key) {
|
|
51245
51038
|
try {
|
|
51246
|
-
const
|
|
51247
|
-
|
|
51248
|
-
|
|
51249
|
-
|
|
51250
|
-
|
|
51251
|
-
|
|
51252
|
-
return
|
|
51039
|
+
const value = (await esm_default(repoPath).raw([
|
|
51040
|
+
"config",
|
|
51041
|
+
"--local",
|
|
51042
|
+
"--get",
|
|
51043
|
+
key
|
|
51044
|
+
])).trim();
|
|
51045
|
+
return value || void 0;
|
|
51046
|
+
} catch {
|
|
51047
|
+
return;
|
|
51253
51048
|
}
|
|
51254
51049
|
}
|
|
51255
|
-
async function
|
|
51256
|
-
const
|
|
51257
|
-
|
|
51258
|
-
|
|
51259
|
-
|
|
51260
|
-
|
|
51261
|
-
|
|
51262
|
-
|
|
51263
|
-
|
|
51264
|
-
|
|
51265
|
-
|
|
51266
|
-
|
|
51267
|
-
|
|
51268
|
-
|
|
51269
|
-
|
|
51270
|
-
|
|
51271
|
-
|
|
51272
|
-
|
|
51273
|
-
|
|
51274
|
-
|
|
51275
|
-
|
|
51276
|
-
|
|
51277
|
-
|
|
51278
|
-
|
|
51279
|
-
|
|
51280
|
-
|
|
51281
|
-
|
|
51282
|
-
|
|
51283
|
-
|
|
51050
|
+
async function getAppAuthBinding(repoPath, appId) {
|
|
51051
|
+
const [url, username] = await Promise.all([
|
|
51052
|
+
getLocalConfigValue(repoPath, getUrlConfigKey(appId)),
|
|
51053
|
+
getLocalConfigValue(repoPath, getUsernameConfigKey(appId))
|
|
51054
|
+
]);
|
|
51055
|
+
return {
|
|
51056
|
+
url: url ? normalizeAnvilUrl(url) : void 0,
|
|
51057
|
+
username: username || void 0
|
|
51058
|
+
};
|
|
51059
|
+
}
|
|
51060
|
+
async function setAppAuthBinding(repoPath, appId, binding) {
|
|
51061
|
+
const git = esm_default(repoPath);
|
|
51062
|
+
if (binding.url) await git.raw([
|
|
51063
|
+
"config",
|
|
51064
|
+
"--local",
|
|
51065
|
+
getUrlConfigKey(appId),
|
|
51066
|
+
normalizeAnvilUrl(binding.url)
|
|
51067
|
+
]);
|
|
51068
|
+
if (binding.username) await git.raw([
|
|
51069
|
+
"config",
|
|
51070
|
+
"--local",
|
|
51071
|
+
getUsernameConfigKey(appId),
|
|
51072
|
+
binding.username
|
|
51073
|
+
]);
|
|
51074
|
+
}
|
|
51075
|
+
function getCleanGitRemoteUrl(appId, anvilUrl) {
|
|
51076
|
+
const normalized = normalizeAnvilUrl(anvilUrl);
|
|
51077
|
+
const url = new URL(normalized);
|
|
51078
|
+
return `${url.protocol}//${url.host}/git/${appId}.git`;
|
|
51079
|
+
}
|
|
51080
|
+
async function configureCredentialHelperForUrl(repoPath, anvilUrl) {
|
|
51081
|
+
const normalized = normalizeAnvilUrl(anvilUrl);
|
|
51082
|
+
const url = new URL(normalized);
|
|
51083
|
+
const scope = `${url.protocol}//${url.host}`;
|
|
51084
|
+
const git = esm_default(repoPath);
|
|
51085
|
+
try {
|
|
51086
|
+
await git.raw([
|
|
51087
|
+
"config",
|
|
51088
|
+
"--local",
|
|
51089
|
+
"--unset-all",
|
|
51090
|
+
`credential.${scope}.helper`
|
|
51091
|
+
]);
|
|
51092
|
+
} catch {}
|
|
51093
|
+
await git.raw([
|
|
51094
|
+
"config",
|
|
51095
|
+
"--local",
|
|
51096
|
+
"--add",
|
|
51097
|
+
`credential.${scope}.helper`,
|
|
51098
|
+
""
|
|
51099
|
+
]);
|
|
51100
|
+
await git.raw([
|
|
51101
|
+
"config",
|
|
51102
|
+
"--local",
|
|
51103
|
+
"--add",
|
|
51104
|
+
`credential.${scope}.helper`,
|
|
51105
|
+
"!anvil git-credential"
|
|
51106
|
+
]);
|
|
51107
|
+
await git.raw([
|
|
51108
|
+
"config",
|
|
51109
|
+
"--local",
|
|
51110
|
+
`credential.${scope}.useHttpPath`,
|
|
51111
|
+
"true"
|
|
51112
|
+
]);
|
|
51113
|
+
await git.raw([
|
|
51114
|
+
"config",
|
|
51115
|
+
"--local",
|
|
51116
|
+
`credential.${scope}.username`,
|
|
51117
|
+
"git"
|
|
51118
|
+
]);
|
|
51119
|
+
}
|
|
51120
|
+
async function hardenCheckoutGitAuth(options) {
|
|
51121
|
+
const { repoPath, appId, anvilUrl, username } = options;
|
|
51122
|
+
const remoteName = options.remoteName || "origin";
|
|
51123
|
+
const cleanRemoteUrl = getCleanGitRemoteUrl(appId, anvilUrl);
|
|
51124
|
+
const git = esm_default(repoPath);
|
|
51125
|
+
await git.raw([
|
|
51126
|
+
"remote",
|
|
51127
|
+
"set-url",
|
|
51128
|
+
remoteName,
|
|
51129
|
+
cleanRemoteUrl
|
|
51130
|
+
]);
|
|
51131
|
+
await configureCredentialHelperForUrl(repoPath, anvilUrl);
|
|
51132
|
+
await setAppAuthBinding(repoPath, appId, {
|
|
51133
|
+
url: anvilUrl,
|
|
51134
|
+
username
|
|
51135
|
+
});
|
|
51136
|
+
return {
|
|
51137
|
+
cleanRemoteUrl
|
|
51138
|
+
};
|
|
51139
|
+
}
|
|
51140
|
+
function parseAppIdFromGitPath(pathValue) {
|
|
51141
|
+
if (!pathValue) return;
|
|
51142
|
+
const normalizedPath = pathValue.startsWith("/") ? pathValue : `/${pathValue}`;
|
|
51143
|
+
const match = normalizedPath.match(/\/git\/([A-Z0-9]+)\.git(?:$|\/)/);
|
|
51144
|
+
return match ? match[1] : void 0;
|
|
51145
|
+
}
|
|
51146
|
+
const defaultDeps = {
|
|
51147
|
+
openSystem: async (pathToOpen)=>node_modules_open(pathToOpen),
|
|
51148
|
+
isCommandAvailable: isCommandAvailable,
|
|
51149
|
+
spawnShellCommand: async (commandLine)=>{
|
|
51150
|
+
await new Promise((resolve, reject)=>{
|
|
51151
|
+
const child = (0, external_child_process_.spawn)(commandLine, {
|
|
51152
|
+
shell: true,
|
|
51153
|
+
stdio: "inherit"
|
|
51154
|
+
});
|
|
51155
|
+
child.on("error", reject);
|
|
51156
|
+
child.on("exit", (code)=>{
|
|
51157
|
+
if (0 === code || null === code) resolve();
|
|
51158
|
+
else reject(new Error(`Editor command exited with code ${code}`));
|
|
51159
|
+
});
|
|
51284
51160
|
});
|
|
51285
|
-
if (!pushed.success) {
|
|
51286
|
-
if (pushed.staleRemote) {
|
|
51287
|
-
logger_logger.warn("Branch appeared on Anvil while pushing. Re-checking sync status...");
|
|
51288
|
-
return await deps.recreateSessionAndValidate(options);
|
|
51289
|
-
}
|
|
51290
|
-
return false;
|
|
51291
|
-
}
|
|
51292
|
-
return await deps.recreateSessionAndValidate(options);
|
|
51293
51161
|
}
|
|
51294
|
-
|
|
51295
|
-
|
|
51296
|
-
|
|
51297
|
-
|
|
51298
|
-
|
|
51299
|
-
|
|
51300
|
-
|
|
51301
|
-
|
|
51302
|
-
|
|
51303
|
-
|
|
51304
|
-
|
|
51305
|
-
|
|
51306
|
-
|
|
51307
|
-
|
|
51308
|
-
}
|
|
51309
|
-
], "push");
|
|
51310
|
-
shouldPush = "push" === action;
|
|
51311
|
-
if (shouldPush) {
|
|
51312
|
-
const changed = await deps.recheckSyncStatus(stateCategory, branchName, options);
|
|
51313
|
-
if (changed) return await checkSyncStatusAndStart(changed, options, deps);
|
|
51314
|
-
}
|
|
51162
|
+
};
|
|
51163
|
+
function parseCommandTokens(command) {
|
|
51164
|
+
const input = command.trim();
|
|
51165
|
+
if (!input) return [];
|
|
51166
|
+
const tokens = [];
|
|
51167
|
+
let current = "";
|
|
51168
|
+
let quote = null;
|
|
51169
|
+
let escaping = false;
|
|
51170
|
+
for(let i = 0; i < input.length; i += 1){
|
|
51171
|
+
const ch = input[i];
|
|
51172
|
+
if (escaping) {
|
|
51173
|
+
current += ch;
|
|
51174
|
+
escaping = false;
|
|
51175
|
+
continue;
|
|
51315
51176
|
}
|
|
51316
|
-
if (
|
|
51317
|
-
|
|
51318
|
-
|
|
51319
|
-
return false;
|
|
51177
|
+
if ("\\" === ch) {
|
|
51178
|
+
escaping = true;
|
|
51179
|
+
continue;
|
|
51320
51180
|
}
|
|
51321
|
-
|
|
51322
|
-
|
|
51323
|
-
|
|
51324
|
-
|
|
51325
|
-
anvilUrl: options.anvilUrl,
|
|
51326
|
-
branchName,
|
|
51327
|
-
username: options.username
|
|
51328
|
-
});
|
|
51329
|
-
if (!pushed.success) return false;
|
|
51330
|
-
return await deps.recreateSessionAndValidate(options);
|
|
51331
|
-
} else if (syncStatus.diverged) {
|
|
51332
|
-
logger_logger.warn("Your local repository has diverged from Anvil.");
|
|
51333
|
-
logger_logger.info(chalk_source.gray(` You are ${syncStatus.ahead} commit(s) ahead and ${syncStatus.behind} commit(s) behind.`));
|
|
51334
|
-
if (hasUncommitted) logger_logger.info(chalk_source.gray(" You also have uncommitted changes."));
|
|
51335
|
-
session.cleanup();
|
|
51336
|
-
if (options.autoMode) {
|
|
51337
|
-
logger_logger.info(chalk_source.cyan("→ Auto-rebasing onto Anvil's version..."));
|
|
51338
|
-
const result = await fetchAndRebaseFromAnvil({
|
|
51339
|
-
repoPath: options.repoPath,
|
|
51340
|
-
appId: options.appId,
|
|
51341
|
-
anvilUrl: options.anvilUrl,
|
|
51342
|
-
branchName,
|
|
51343
|
-
username: options.username
|
|
51344
|
-
});
|
|
51345
|
-
if (result.conflicted) {
|
|
51346
|
-
logger_logger.error("Rebase failed due to conflicts. Please resolve manually:");
|
|
51347
|
-
logger_logger.info(chalk_source.gray(" 1. Run: git fetch anvil && git rebase anvil/" + branchName));
|
|
51348
|
-
logger_logger.info(chalk_source.gray(" 2. Resolve conflicts, then: git rebase --continue"));
|
|
51349
|
-
logger_logger.info(chalk_source.gray(" 3. Push: git push anvil " + branchName));
|
|
51350
|
-
return false;
|
|
51351
|
-
}
|
|
51352
|
-
if (!result.success) return false;
|
|
51353
|
-
return await deps.recreateSessionAndValidate(options);
|
|
51181
|
+
if (quote) {
|
|
51182
|
+
if (ch === quote) quote = null;
|
|
51183
|
+
else current += ch;
|
|
51184
|
+
continue;
|
|
51354
51185
|
}
|
|
51355
|
-
|
|
51356
|
-
|
|
51357
|
-
|
|
51358
|
-
value: "rebase"
|
|
51359
|
-
},
|
|
51360
|
-
{
|
|
51361
|
-
name: "Discard your local commits and use Anvil's version",
|
|
51362
|
-
value: "reset"
|
|
51363
|
-
},
|
|
51364
|
-
{
|
|
51365
|
-
name: "Push your version to Anvil (overwrites remote)",
|
|
51366
|
-
value: "push"
|
|
51367
|
-
},
|
|
51368
|
-
{
|
|
51369
|
-
name: "Exit and handle manually",
|
|
51370
|
-
value: "exit"
|
|
51371
|
-
}
|
|
51372
|
-
], "rebase");
|
|
51373
|
-
if ("exit" === action) {
|
|
51374
|
-
logger_logger.warn("Watch cancelled.");
|
|
51375
|
-
return false;
|
|
51186
|
+
if ('"' === ch || "'" === ch) {
|
|
51187
|
+
quote = ch;
|
|
51188
|
+
continue;
|
|
51376
51189
|
}
|
|
51377
|
-
|
|
51378
|
-
|
|
51379
|
-
|
|
51380
|
-
|
|
51381
|
-
repoPath: options.repoPath,
|
|
51382
|
-
appId: options.appId,
|
|
51383
|
-
anvilUrl: options.anvilUrl,
|
|
51384
|
-
branchName,
|
|
51385
|
-
username: options.username
|
|
51386
|
-
});
|
|
51387
|
-
if (result.conflicted) {
|
|
51388
|
-
logger_logger.error("Rebase failed due to conflicts. Please resolve manually:");
|
|
51389
|
-
logger_logger.info(chalk_source.gray(" 1. Run: git fetch anvil && git rebase anvil/" + branchName));
|
|
51390
|
-
logger_logger.info(chalk_source.gray(" 2. Resolve conflicts, then: git rebase --continue"));
|
|
51391
|
-
logger_logger.info(chalk_source.gray(" 3. Push: git push anvil " + branchName));
|
|
51392
|
-
return false;
|
|
51190
|
+
if (/\s/.test(ch)) {
|
|
51191
|
+
if (current) {
|
|
51192
|
+
tokens.push(current);
|
|
51193
|
+
current = "";
|
|
51393
51194
|
}
|
|
51394
|
-
|
|
51395
|
-
return await deps.recreateSessionAndValidate(options);
|
|
51195
|
+
continue;
|
|
51396
51196
|
}
|
|
51397
|
-
|
|
51398
|
-
|
|
51399
|
-
|
|
51400
|
-
|
|
51401
|
-
|
|
51402
|
-
|
|
51197
|
+
current += ch;
|
|
51198
|
+
}
|
|
51199
|
+
if (escaping) current += "\\";
|
|
51200
|
+
if (quote) throw new Error(`Unterminated quote in command: ${command}`);
|
|
51201
|
+
if (current) tokens.push(current);
|
|
51202
|
+
return tokens;
|
|
51203
|
+
}
|
|
51204
|
+
function quoteForPosixShell(value) {
|
|
51205
|
+
if ("" === value) return "''";
|
|
51206
|
+
return `'${value.replace(/'/g, "'\"'\"'")}'`;
|
|
51207
|
+
}
|
|
51208
|
+
function quoteForCmdShell(value) {
|
|
51209
|
+
const escaped = value.replace(/[%^&|<>()"!]/g, "^$&");
|
|
51210
|
+
return `"${escaped}"`;
|
|
51211
|
+
}
|
|
51212
|
+
function buildShellCommandLine(tokens) {
|
|
51213
|
+
if ("win32" === process.platform) return tokens.map(quoteForCmdShell).join(" ");
|
|
51214
|
+
return tokens.map(quoteForPosixShell).join(" ");
|
|
51215
|
+
}
|
|
51216
|
+
function formatFallbackEditorLabel(preferredEditorCommand) {
|
|
51217
|
+
const trimmed = preferredEditorCommand.trim();
|
|
51218
|
+
if (!trimmed) return "your editor";
|
|
51219
|
+
return trimmed;
|
|
51220
|
+
}
|
|
51221
|
+
async function openPathInEditorOrDefault(targetPath, preferredEditorCommand, deps = defaultDeps) {
|
|
51222
|
+
if (preferredEditorCommand) if (deps.isCommandAvailable(preferredEditorCommand)) try {
|
|
51223
|
+
const tokens = parseCommandTokens(preferredEditorCommand);
|
|
51224
|
+
if (0 === tokens.length) throw new Error("empty command");
|
|
51225
|
+
await deps.spawnShellCommand(buildShellCommandLine([
|
|
51226
|
+
...tokens,
|
|
51227
|
+
targetPath
|
|
51228
|
+
]));
|
|
51229
|
+
return;
|
|
51230
|
+
} catch (error) {
|
|
51231
|
+
logger_logger.warn(`Failed to open ${formatFallbackEditorLabel(preferredEditorCommand)} automatically: ${errors_getErrorMessage(error)}.`);
|
|
51232
|
+
}
|
|
51233
|
+
else logger_logger.info(`Open ${formatFallbackEditorLabel(preferredEditorCommand)} to edit your app.`);
|
|
51234
|
+
await deps.openSystem(targetPath);
|
|
51235
|
+
}
|
|
51236
|
+
const CHECKOUT_ERROR_VALUE = "__ERROR__";
|
|
51237
|
+
function isAbortLikeError(error) {
|
|
51238
|
+
if (error instanceof Error && "AbortError" === error.name) return true;
|
|
51239
|
+
if ("object" == typeof error && null !== error) {
|
|
51240
|
+
const maybeType = error.type;
|
|
51241
|
+
const maybeMessage = error.message;
|
|
51242
|
+
if ("network_error" === maybeType && "string" == typeof maybeMessage && maybeMessage.toLowerCase().includes("abort")) return true;
|
|
51243
|
+
}
|
|
51244
|
+
const message = errors_getErrorMessage(error).toLowerCase();
|
|
51245
|
+
return "aborted" === message || message.includes("aborted");
|
|
51246
|
+
}
|
|
51247
|
+
function normalizePickerQuery(term) {
|
|
51248
|
+
return (term || "").trim();
|
|
51249
|
+
}
|
|
51250
|
+
function parseNonNegativeMs(raw) {
|
|
51251
|
+
if (null == raw) return null;
|
|
51252
|
+
const parsed = Number(raw);
|
|
51253
|
+
if (!Number.isFinite(parsed) || parsed < 0) return null;
|
|
51254
|
+
return parsed;
|
|
51255
|
+
}
|
|
51256
|
+
function getCheckoutPickerDelayMs() {
|
|
51257
|
+
const fromEnv = parseNonNegativeMs(process.env.ANVIL_CHECKOUT_PICKER_DELAY_MS);
|
|
51258
|
+
if (null != fromEnv) return fromEnv;
|
|
51259
|
+
return 0;
|
|
51260
|
+
}
|
|
51261
|
+
function getCheckoutPickerPaginationDelayMs() {
|
|
51262
|
+
return getCheckoutPickerDelayMs();
|
|
51263
|
+
}
|
|
51264
|
+
function getCheckoutPickerMaxRows() {
|
|
51265
|
+
const raw = process.env.ANVIL_CHECKOUT_PICKER_MAX_ROWS;
|
|
51266
|
+
const fromEnv = null == raw ? NaN : Number(raw);
|
|
51267
|
+
if (null != raw && Number.isFinite(fromEnv) && fromEnv >= 4) return Math.floor(fromEnv);
|
|
51268
|
+
return 12;
|
|
51269
|
+
}
|
|
51270
|
+
function formatLastEdited(lastEdited) {
|
|
51271
|
+
if (!lastEdited) return;
|
|
51272
|
+
return chalk_source.gray(`Last edited: ${lastEdited}`);
|
|
51273
|
+
}
|
|
51274
|
+
function formatAppPickerLabel(app) {
|
|
51275
|
+
const appName = app.app_name || "Unnamed App";
|
|
51276
|
+
return `${chalk_source.cyan(appName)} ${chalk_source.gray(`(${app.app_id})`)}`;
|
|
51277
|
+
}
|
|
51278
|
+
async function waitForDebounce(ms, signal) {
|
|
51279
|
+
if (ms <= 0) return;
|
|
51280
|
+
if (signal.aborted) throw new Error("aborted");
|
|
51281
|
+
await new Promise((resolve, reject)=>{
|
|
51282
|
+
const timer = setTimeout(()=>{
|
|
51283
|
+
cleanup();
|
|
51284
|
+
resolve();
|
|
51285
|
+
}, ms);
|
|
51286
|
+
const onAbort = ()=>{
|
|
51287
|
+
cleanup();
|
|
51288
|
+
reject(new Error("aborted"));
|
|
51289
|
+
};
|
|
51290
|
+
const cleanup = ()=>{
|
|
51291
|
+
clearTimeout(timer);
|
|
51292
|
+
signal.removeEventListener("abort", onAbort);
|
|
51293
|
+
};
|
|
51294
|
+
signal.addEventListener("abort", onAbort, {
|
|
51295
|
+
once: true
|
|
51296
|
+
});
|
|
51297
|
+
});
|
|
51298
|
+
}
|
|
51299
|
+
function createCheckoutPickerSource(anvilUrl, username, deps, options = {}) {
|
|
51300
|
+
const pageSize = options.pageSize ?? 20;
|
|
51301
|
+
const debounceMs = options.debounceMs ?? 200;
|
|
51302
|
+
const staleTimeMs = options.staleTimeMs ?? 30000;
|
|
51303
|
+
const maxAutoPagesPerCall = options.maxAutoPagesPerCall ?? 8;
|
|
51304
|
+
const initialQuery = normalizePickerQuery(options.initialQuery);
|
|
51305
|
+
const simulatedDelayMs = getCheckoutPickerDelayMs();
|
|
51306
|
+
const paginationDelayMs = getCheckoutPickerPaginationDelayMs();
|
|
51307
|
+
const cache = new Map();
|
|
51308
|
+
const getState = (query)=>{
|
|
51309
|
+
const existing = cache.get(query);
|
|
51310
|
+
if (existing) return existing;
|
|
51311
|
+
const created = {
|
|
51312
|
+
items: [],
|
|
51313
|
+
nextCursor: null,
|
|
51314
|
+
exhausted: false,
|
|
51315
|
+
lastError: null,
|
|
51316
|
+
updatedAt: 0,
|
|
51317
|
+
inFlight: null
|
|
51318
|
+
};
|
|
51319
|
+
cache.set(query, created);
|
|
51320
|
+
return created;
|
|
51321
|
+
};
|
|
51322
|
+
const fetchPage = async (query, signal, options = {})=>{
|
|
51323
|
+
const state = getState(query);
|
|
51324
|
+
if (state.inFlight) return void await state.inFlight;
|
|
51325
|
+
state.inFlight = (async ()=>{
|
|
51326
|
+
try {
|
|
51327
|
+
if (simulatedDelayMs > 0) await new Promise((resolve)=>setTimeout(resolve, simulatedDelayMs));
|
|
51328
|
+
if (options.cursor && paginationDelayMs > 0) await new Promise((resolve)=>setTimeout(resolve, paginationDelayMs));
|
|
51329
|
+
const response = await deps.listAppsForCheckout({
|
|
51330
|
+
anvilUrl,
|
|
51331
|
+
username,
|
|
51332
|
+
limit: pageSize,
|
|
51333
|
+
cursor: options.cursor,
|
|
51334
|
+
q: query || void 0,
|
|
51335
|
+
signal
|
|
51336
|
+
});
|
|
51337
|
+
if (options.reset) state.items = [
|
|
51338
|
+
...response.apps
|
|
51339
|
+
];
|
|
51340
|
+
else state.items.push(...response.apps);
|
|
51341
|
+
state.nextCursor = response.next_cursor;
|
|
51342
|
+
state.exhausted = !response.next_cursor;
|
|
51343
|
+
state.lastError = null;
|
|
51344
|
+
state.updatedAt = Date.now();
|
|
51345
|
+
} catch (e) {
|
|
51346
|
+
if (!signal.aborted && !isAbortLikeError(e)) {
|
|
51347
|
+
const message = errors_getErrorMessage(e);
|
|
51348
|
+
state.lastError = message;
|
|
51349
|
+
state.updatedAt = Date.now();
|
|
51350
|
+
}
|
|
51351
|
+
} finally{
|
|
51352
|
+
state.inFlight = null;
|
|
51403
51353
|
}
|
|
51404
|
-
|
|
51405
|
-
|
|
51406
|
-
|
|
51407
|
-
|
|
51408
|
-
|
|
51409
|
-
|
|
51354
|
+
})();
|
|
51355
|
+
await state.inFlight;
|
|
51356
|
+
};
|
|
51357
|
+
const ensureLoaded = async (query, signal)=>{
|
|
51358
|
+
const state = getState(query);
|
|
51359
|
+
const isStale = Date.now() - state.updatedAt > staleTimeMs;
|
|
51360
|
+
if (0 === state.items.length) {
|
|
51361
|
+
await waitForDebounce(debounceMs, signal);
|
|
51362
|
+
await fetchPage(query, signal, {
|
|
51363
|
+
cursor: void 0,
|
|
51364
|
+
reset: false
|
|
51410
51365
|
});
|
|
51411
|
-
|
|
51412
|
-
|
|
51413
|
-
|
|
51414
|
-
|
|
51415
|
-
|
|
51416
|
-
|
|
51417
|
-
|
|
51418
|
-
|
|
51419
|
-
|
|
51420
|
-
|
|
51421
|
-
|
|
51422
|
-
appId: options.appId,
|
|
51423
|
-
anvilUrl: options.anvilUrl,
|
|
51424
|
-
branchName,
|
|
51425
|
-
username: options.username,
|
|
51426
|
-
force: true
|
|
51366
|
+
} else if (state.lastError) {
|
|
51367
|
+
await waitForDebounce(debounceMs, signal);
|
|
51368
|
+
await fetchPage(query, signal, {
|
|
51369
|
+
cursor: void 0,
|
|
51370
|
+
reset: true
|
|
51371
|
+
});
|
|
51372
|
+
} else if (isStale) {
|
|
51373
|
+
await waitForDebounce(debounceMs, signal);
|
|
51374
|
+
await fetchPage(query, signal, {
|
|
51375
|
+
cursor: void 0,
|
|
51376
|
+
reset: true
|
|
51427
51377
|
});
|
|
51428
|
-
if (!pushed.success) return false;
|
|
51429
|
-
return await deps.recreateSessionAndValidate(options);
|
|
51430
51378
|
}
|
|
51431
|
-
|
|
51432
|
-
|
|
51433
|
-
|
|
51434
|
-
|
|
51435
|
-
|
|
51436
|
-
|
|
51437
|
-
|
|
51438
|
-
|
|
51439
|
-
|
|
51440
|
-
const changed = await deps.recheckSyncStatus(stateCategory, branchName, options);
|
|
51441
|
-
if (changed) return await checkSyncStatusAndStart(changed, options, deps);
|
|
51442
|
-
}
|
|
51379
|
+
let pagesLoaded = 0;
|
|
51380
|
+
while(state.nextCursor && pagesLoaded < maxAutoPagesPerCall){
|
|
51381
|
+
if (signal.aborted) throw new Error("aborted");
|
|
51382
|
+
const cursor = state.nextCursor;
|
|
51383
|
+
await fetchPage(query, signal, {
|
|
51384
|
+
cursor,
|
|
51385
|
+
reset: false
|
|
51386
|
+
});
|
|
51387
|
+
pagesLoaded += 1;
|
|
51443
51388
|
}
|
|
51444
|
-
|
|
51445
|
-
|
|
51446
|
-
|
|
51447
|
-
|
|
51389
|
+
};
|
|
51390
|
+
return {
|
|
51391
|
+
source: async (term, { signal })=>{
|
|
51392
|
+
const query = normalizePickerQuery(term) || initialQuery;
|
|
51393
|
+
if (signal.aborted) return [];
|
|
51394
|
+
await ensureLoaded(query, signal);
|
|
51395
|
+
const state = getState(query);
|
|
51396
|
+
if (state.lastError) return [
|
|
51397
|
+
{
|
|
51398
|
+
name: `${chalk_source.yellow("Fetch failed")} ${chalk_source.gray(`(${state.lastError})`)}`,
|
|
51399
|
+
value: CHECKOUT_ERROR_VALUE,
|
|
51400
|
+
disabled: true
|
|
51401
|
+
}
|
|
51402
|
+
];
|
|
51403
|
+
const summary = state.nextCursor ? chalk_source.gray(`Loaded ${state.items.length} apps • more available`) : chalk_source.gray(`Loaded ${state.items.length} apps`);
|
|
51404
|
+
const choices = state.items.map((app)=>({
|
|
51405
|
+
name: formatAppPickerLabel(app),
|
|
51406
|
+
value: app.app_id,
|
|
51407
|
+
description: formatLastEdited(app.last_edited)
|
|
51408
|
+
}));
|
|
51409
|
+
choices.unshift({
|
|
51410
|
+
name: summary,
|
|
51411
|
+
value: CHECKOUT_ERROR_VALUE,
|
|
51412
|
+
disabled: true
|
|
51413
|
+
});
|
|
51414
|
+
return choices;
|
|
51448
51415
|
}
|
|
51449
|
-
|
|
51450
|
-
|
|
51451
|
-
|
|
51452
|
-
|
|
51453
|
-
|
|
51454
|
-
|
|
51455
|
-
|
|
51416
|
+
};
|
|
51417
|
+
}
|
|
51418
|
+
const INLINE_SPINNER_FRAMES = [
|
|
51419
|
+
"⠋",
|
|
51420
|
+
"⠙",
|
|
51421
|
+
"⠹",
|
|
51422
|
+
"⠸",
|
|
51423
|
+
"⠼",
|
|
51424
|
+
"⠴",
|
|
51425
|
+
"⠦",
|
|
51426
|
+
"⠧",
|
|
51427
|
+
"⠇",
|
|
51428
|
+
"⠏"
|
|
51429
|
+
];
|
|
51430
|
+
async function runCustomCheckoutPicker(anvilUrl, username, deps, initialQuery) {
|
|
51431
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) return null;
|
|
51432
|
+
const pageSize = 20;
|
|
51433
|
+
const staleTimeMs = 30000;
|
|
51434
|
+
const debounceMs = 180;
|
|
51435
|
+
const simulatedDelayMs = getCheckoutPickerDelayMs();
|
|
51436
|
+
const paginationDelayMs = getCheckoutPickerPaginationDelayMs();
|
|
51437
|
+
const maxVisibleRows = getCheckoutPickerMaxRows();
|
|
51438
|
+
const cache = new Map();
|
|
51439
|
+
let query = normalizePickerQuery(initialQuery);
|
|
51440
|
+
let selectedIndex = 0;
|
|
51441
|
+
let scrollOffset = 0;
|
|
51442
|
+
let loading = false;
|
|
51443
|
+
let loadingMore = false;
|
|
51444
|
+
let done = false;
|
|
51445
|
+
let pendingTimer = null;
|
|
51446
|
+
let loadingRenderTimer = null;
|
|
51447
|
+
let activeController = null;
|
|
51448
|
+
let resolveResult = null;
|
|
51449
|
+
const ensureState = (key)=>{
|
|
51450
|
+
const existing = cache.get(key);
|
|
51451
|
+
if (existing) return existing;
|
|
51452
|
+
const created = {
|
|
51453
|
+
items: [],
|
|
51454
|
+
nextCursor: null,
|
|
51455
|
+
lastError: null,
|
|
51456
|
+
updatedAt: 0
|
|
51457
|
+
};
|
|
51458
|
+
cache.set(key, created);
|
|
51459
|
+
return created;
|
|
51460
|
+
};
|
|
51461
|
+
const currentState = ()=>ensureState(query);
|
|
51462
|
+
const clearScreen = ()=>{
|
|
51463
|
+
process.stdout.write("\x1b[2J\x1b[H");
|
|
51464
|
+
};
|
|
51465
|
+
const visibleRows = ()=>{
|
|
51466
|
+
const rows = process.stdout.rows || 24;
|
|
51467
|
+
return Math.min(maxVisibleRows, Math.max(6, rows - 9));
|
|
51468
|
+
};
|
|
51469
|
+
const syncViewport = ()=>{
|
|
51470
|
+
const items = currentState().items;
|
|
51471
|
+
if (selectedIndex >= items.length) selectedIndex = Math.max(0, items.length - 1);
|
|
51472
|
+
const height = visibleRows();
|
|
51473
|
+
if (selectedIndex < scrollOffset) scrollOffset = selectedIndex;
|
|
51474
|
+
else if (selectedIndex >= scrollOffset + height) scrollOffset = Math.max(0, selectedIndex - height + 1);
|
|
51475
|
+
};
|
|
51476
|
+
const render = ()=>{
|
|
51477
|
+
clearScreen();
|
|
51478
|
+
const state = currentState();
|
|
51479
|
+
const items = state.items;
|
|
51480
|
+
const activeQuery = query || chalk_source.gray("(all apps)");
|
|
51481
|
+
const spinnerFrame = INLINE_SPINNER_FRAMES[Math.floor(Date.now() / 120) % INLINE_SPINNER_FRAMES.length];
|
|
51482
|
+
const status = loading ? loadingMore ? chalk_source.yellow(`${spinnerFrame} Loading more apps... (${items.length} loaded)`) : chalk_source.yellow(`${spinnerFrame} Loading apps...`) : state.nextCursor ? chalk_source.gray(`Loaded ${items.length} apps • more available`) : chalk_source.gray(`Loaded ${items.length} apps`);
|
|
51483
|
+
process.stdout.write(`${chalk_source.bold("Search apps by name or app ID")}\n`);
|
|
51484
|
+
process.stdout.write(`${chalk_source.cyan("Query:")} ${activeQuery}\n`);
|
|
51485
|
+
process.stdout.write(`${status}\n`);
|
|
51486
|
+
if (state.lastError) process.stdout.write(`${chalk_source.red(`Fetch failed: ${state.lastError}`)}\n`);
|
|
51487
|
+
const height = visibleRows();
|
|
51488
|
+
const start = scrollOffset;
|
|
51489
|
+
const end = Math.min(items.length, start + height);
|
|
51490
|
+
if (0 !== items.length || loading || state.lastError) for(let i = start; i < end; i += 1){
|
|
51491
|
+
const item = items[i];
|
|
51492
|
+
const label = formatAppPickerLabel(item);
|
|
51493
|
+
const line = i === selectedIndex ? `${chalk_source.green("❯")} ${label}` : ` ${label}`;
|
|
51494
|
+
process.stdout.write(`${line}\n`);
|
|
51456
51495
|
}
|
|
51457
|
-
|
|
51496
|
+
else process.stdout.write(`${chalk_source.gray("No matches. Keep typing.")}\n`);
|
|
51497
|
+
process.stdout.write(`\n${chalk_source.gray("↑/↓ navigate • type to search • enter select • esc/ctrl+c cancel")}\n`);
|
|
51498
|
+
};
|
|
51499
|
+
const fetchPage = async (key, cursor)=>{
|
|
51500
|
+
const state = ensureState(key);
|
|
51501
|
+
const controller = new AbortController();
|
|
51502
|
+
if (!cursor && activeController) activeController.abort();
|
|
51503
|
+
activeController = controller;
|
|
51504
|
+
loading = true;
|
|
51505
|
+
loadingMore = !!cursor;
|
|
51506
|
+
startLoadingAnimation();
|
|
51507
|
+
render();
|
|
51458
51508
|
try {
|
|
51459
|
-
|
|
51460
|
-
|
|
51509
|
+
if (simulatedDelayMs > 0) await new Promise((resolve)=>setTimeout(resolve, simulatedDelayMs));
|
|
51510
|
+
if (cursor && paginationDelayMs > 0) await new Promise((resolve)=>setTimeout(resolve, paginationDelayMs));
|
|
51511
|
+
const response = await deps.listAppsForCheckout({
|
|
51512
|
+
anvilUrl,
|
|
51513
|
+
username,
|
|
51514
|
+
limit: pageSize,
|
|
51515
|
+
cursor,
|
|
51516
|
+
q: key || void 0,
|
|
51517
|
+
signal: controller.signal
|
|
51518
|
+
});
|
|
51519
|
+
const merged = cursor ? state.items.concat(response.apps) : response.apps;
|
|
51520
|
+
cache.set(key, {
|
|
51521
|
+
items: merged,
|
|
51522
|
+
nextCursor: response.next_cursor,
|
|
51523
|
+
lastError: null,
|
|
51524
|
+
updatedAt: Date.now()
|
|
51525
|
+
});
|
|
51526
|
+
if (key === query) syncViewport();
|
|
51461
51527
|
} catch (e) {
|
|
51462
|
-
|
|
51463
|
-
|
|
51464
|
-
|
|
51465
|
-
|
|
51466
|
-
|
|
51467
|
-
|
|
51468
|
-
|
|
51469
|
-
return true;
|
|
51470
|
-
}
|
|
51471
|
-
async function startWatchingWithEventHandlers(session, options) {
|
|
51472
|
-
const { autoMode, repoPath, appId, anvilUrl, stagedOnly } = options;
|
|
51473
|
-
session.on("branch-changed", async ({ oldBranch, newBranch })=>{
|
|
51474
|
-
try {
|
|
51475
|
-
session.cleanup();
|
|
51476
|
-
logger_logger.warn(`Branch changed: ${chalk_source.bold(oldBranch)} → ${chalk_source.bold(newBranch)}`);
|
|
51477
|
-
logger_logger.info(chalk_source.cyan("→ Restarting watch on new branch..."));
|
|
51478
|
-
const git = esm_default(repoPath);
|
|
51479
|
-
const actualBranch = (await git.revparse([
|
|
51480
|
-
"--abbrev-ref",
|
|
51481
|
-
"HEAD"
|
|
51482
|
-
])).trim();
|
|
51483
|
-
if (actualBranch !== newBranch) logger_logger.verbose(chalk_source.yellow(`Branch changed again to ${chalk_source.bold(actualBranch)}, using that instead`));
|
|
51484
|
-
logger_logger.progress("init", "Initializing watch session...");
|
|
51485
|
-
try {
|
|
51486
|
-
const newSession = await api_watch(repoPath, appId, anvilUrl, stagedOnly, options.username);
|
|
51487
|
-
logger_logger.progressEnd("init");
|
|
51488
|
-
const started = await checkSyncStatusAndStart(newSession, options);
|
|
51489
|
-
if (!started) process.exit(0);
|
|
51490
|
-
} catch (e) {
|
|
51491
|
-
logger_logger.progressEnd("init", "Failed");
|
|
51492
|
-
logger_logger.error("Failed to restart watch: " + errors_getErrorMessage(e));
|
|
51493
|
-
process.exit(1);
|
|
51494
|
-
}
|
|
51495
|
-
} catch (error) {
|
|
51496
|
-
logger_logger.error(`Error handling branch change: ${error.message}`);
|
|
51497
|
-
process.exit(1);
|
|
51498
|
-
}
|
|
51499
|
-
});
|
|
51500
|
-
session.on("validation-failed", ({ reason, currentBranch })=>{
|
|
51501
|
-
logger_logger.error(chalk_source.red(`Validation failed: ${reason}`));
|
|
51502
|
-
logger_logger.verbose(chalk_source.yellow(` Current branch: ${currentBranch}`));
|
|
51503
|
-
logger_logger.verbose(chalk_source.yellow(" Please restart anvil to re-validate the branch."));
|
|
51504
|
-
session.cleanup();
|
|
51505
|
-
process.exit(1);
|
|
51506
|
-
});
|
|
51507
|
-
session.on("max-retries-exceeded", async ()=>{
|
|
51508
|
-
if (autoMode) {
|
|
51509
|
-
logger_logger.info(chalk_source.cyan("→ Auto-restarting watch session..."));
|
|
51510
|
-
session.cleanup();
|
|
51511
|
-
const restarted = await recreateSessionAndValidate(options);
|
|
51512
|
-
if (!restarted) process.exit(1);
|
|
51513
|
-
} else {
|
|
51514
|
-
const shouldRestart = await logger_logger.confirm("Would you like to restart the watch session?", true);
|
|
51515
|
-
if (shouldRestart) {
|
|
51516
|
-
session.cleanup();
|
|
51517
|
-
const restarted = await recreateSessionAndValidate(options);
|
|
51518
|
-
if (!restarted) process.exit(1);
|
|
51528
|
+
if (!controller.signal.aborted && !isAbortLikeError(e)) {
|
|
51529
|
+
const message = errors_getErrorMessage(e);
|
|
51530
|
+
cache.set(key, {
|
|
51531
|
+
...state,
|
|
51532
|
+
lastError: message,
|
|
51533
|
+
updatedAt: Date.now()
|
|
51534
|
+
});
|
|
51519
51535
|
}
|
|
51536
|
+
} finally{
|
|
51537
|
+
if (activeController === controller) activeController = null;
|
|
51538
|
+
loading = false;
|
|
51539
|
+
loadingMore = false;
|
|
51540
|
+
stopLoadingAnimation();
|
|
51541
|
+
render();
|
|
51520
51542
|
}
|
|
51521
|
-
}
|
|
51522
|
-
|
|
51523
|
-
|
|
51524
|
-
|
|
51525
|
-
|
|
51526
|
-
|
|
51527
|
-
|
|
51528
|
-
|
|
51529
|
-
|
|
51543
|
+
};
|
|
51544
|
+
const ensureFresh = async (key)=>{
|
|
51545
|
+
const state = ensureState(key);
|
|
51546
|
+
const stale = Date.now() - state.updatedAt > staleTimeMs;
|
|
51547
|
+
if (0 === state.items.length || stale || state.lastError) await fetchPage(key);
|
|
51548
|
+
else render();
|
|
51549
|
+
};
|
|
51550
|
+
const maybeLoadMore = async ()=>{
|
|
51551
|
+
const state = currentState();
|
|
51552
|
+
if (!state.nextCursor || loading) return;
|
|
51553
|
+
if (selectedIndex >= Math.max(0, state.items.length - 2)) await fetchPage(query, state.nextCursor || void 0);
|
|
51554
|
+
};
|
|
51555
|
+
const scheduleQueryFetch = ()=>{
|
|
51556
|
+
if (pendingTimer) clearTimeout(pendingTimer);
|
|
51557
|
+
pendingTimer = setTimeout(()=>{
|
|
51558
|
+
pendingTimer = null;
|
|
51559
|
+
ensureFresh(query);
|
|
51560
|
+
}, debounceMs);
|
|
51561
|
+
};
|
|
51562
|
+
const startLoadingAnimation = ()=>{
|
|
51563
|
+
if (loadingRenderTimer) return;
|
|
51564
|
+
loadingRenderTimer = setInterval(()=>{
|
|
51565
|
+
if (!done && loading) render();
|
|
51566
|
+
}, 120);
|
|
51567
|
+
};
|
|
51568
|
+
const stopLoadingAnimation = ()=>{
|
|
51569
|
+
if (!loadingRenderTimer) return;
|
|
51570
|
+
clearInterval(loadingRenderTimer);
|
|
51571
|
+
loadingRenderTimer = null;
|
|
51572
|
+
};
|
|
51573
|
+
const finish = (value)=>{
|
|
51574
|
+
done = true;
|
|
51575
|
+
if (pendingTimer) {
|
|
51576
|
+
clearTimeout(pendingTimer);
|
|
51577
|
+
pendingTimer = null;
|
|
51530
51578
|
}
|
|
51531
|
-
|
|
51532
|
-
|
|
51533
|
-
|
|
51534
|
-
logger_logger.progressEnd("save");
|
|
51535
|
-
} catch (e) {
|
|
51536
|
-
logger_logger.progressEnd("save", "Failed");
|
|
51537
|
-
logger_logger.error(`Failed to save: ${errors_getErrorMessage(e)}`);
|
|
51579
|
+
if (activeController) {
|
|
51580
|
+
activeController.abort();
|
|
51581
|
+
activeController = null;
|
|
51538
51582
|
}
|
|
51539
|
-
|
|
51540
|
-
|
|
51541
|
-
|
|
51542
|
-
|
|
51543
|
-
|
|
51544
|
-
|
|
51545
|
-
|
|
51546
|
-
|
|
51547
|
-
|
|
51548
|
-
|
|
51549
|
-
|
|
51550
|
-
|
|
51551
|
-
|
|
51552
|
-
|
|
51553
|
-
|
|
51554
|
-
|
|
51555
|
-
if (filteredCandidates.length !== detectedFromAllRemotes.length) logger_logger.verbose(chalk_source.cyan(`After filtering: ${filteredCandidates.length} candidate(s)`));
|
|
51556
|
-
let finalAppId;
|
|
51557
|
-
let anvilUrl;
|
|
51558
|
-
let username = explicitUsername;
|
|
51559
|
-
let fallbackUrl;
|
|
51560
|
-
let shouldPersistUsernameBinding = false;
|
|
51561
|
-
if (explicitAppId) {
|
|
51562
|
-
finalAppId = explicitAppId;
|
|
51563
|
-
const binding = await getAppAuthBinding(repoPath, explicitAppId);
|
|
51564
|
-
if (binding.url && !explicitUrl) {
|
|
51565
|
-
anvilUrl = normalizeAnvilUrl(binding.url);
|
|
51566
|
-
logger_logger.verbose(chalk_source.cyan("Resolved URL from binding for app ID: ") + chalk_source.bold(anvilUrl));
|
|
51567
|
-
}
|
|
51568
|
-
if (binding.username && !explicitUsername) {
|
|
51569
|
-
username = binding.username;
|
|
51570
|
-
logger_logger.verbose(chalk_source.cyan("Resolved username from binding for app ID: ") + chalk_source.bold(username));
|
|
51571
|
-
}
|
|
51572
|
-
const remoteInfo = lookupRemoteInfoForAppId(explicitAppId, detectedFromAllRemotes);
|
|
51573
|
-
if (remoteInfo.detectedUrl && !explicitUrl && !anvilUrl) {
|
|
51574
|
-
anvilUrl = normalizeAnvilUrl(remoteInfo.detectedUrl);
|
|
51575
|
-
logger_logger.verbose(chalk_source.cyan("Resolved URL from remote for app ID: ") + chalk_source.bold(anvilUrl));
|
|
51576
|
-
}
|
|
51577
|
-
if (remoteInfo.detectedUsername && !explicitUsername && !username) {
|
|
51578
|
-
username = remoteInfo.detectedUsername;
|
|
51579
|
-
logger_logger.verbose(chalk_source.cyan("Resolved username from remote for app ID: ") + chalk_source.bold(username));
|
|
51580
|
-
}
|
|
51581
|
-
} else {
|
|
51582
|
-
logger_logger.verbose(chalk_source.cyan("No app ID provided, attempting auto-detection..."));
|
|
51583
|
-
if (0 === filteredCandidates.length) {
|
|
51584
|
-
logger_logger.verbose(chalk_source.gray("No app IDs found in git remotes."));
|
|
51585
|
-
const resolvedFallbackUrl = await resolveUrlForFallback(explicitUrl);
|
|
51586
|
-
if (null === resolvedFallbackUrl) {
|
|
51587
|
-
logger_logger.warn("Operation cancelled.");
|
|
51588
|
-
process.exit(0);
|
|
51589
|
-
}
|
|
51590
|
-
fallbackUrl = normalizeAnvilUrl(resolvedFallbackUrl);
|
|
51591
|
-
const lookupDecision = await confirmReverseLookupWithResolvedUser(fallbackUrl, username);
|
|
51592
|
-
if (null === lookupDecision) {
|
|
51593
|
-
logger_logger.warn("Operation cancelled.");
|
|
51594
|
-
process.exit(0);
|
|
51595
|
-
}
|
|
51596
|
-
username = lookupDecision.username;
|
|
51597
|
-
if (lookupDecision.shouldContinue) {
|
|
51598
|
-
logger_logger.progress("detect", `Searching ${fallbackUrl} ${username ? `for ${username}` : ""} for matching app IDs...`);
|
|
51599
|
-
const reverseLookupCandidates = await detectAppIdsByCommitLookup(repoPath, {
|
|
51600
|
-
anvilUrl: fallbackUrl,
|
|
51601
|
-
username,
|
|
51602
|
-
includeRemotes: false
|
|
51603
|
-
});
|
|
51604
|
-
logger_logger.progressEnd("detect");
|
|
51605
|
-
for (const c of reverseLookupCandidates)filteredCandidates.push({
|
|
51606
|
-
...c,
|
|
51607
|
-
detectedUrl: fallbackUrl
|
|
51608
|
-
});
|
|
51609
|
-
}
|
|
51610
|
-
}
|
|
51611
|
-
if (filteredCandidates.length > 0) {
|
|
51612
|
-
for (const c of filteredCandidates)logger_logger.verbose(chalk_source.gray(` Found: ${formatCandidateLabel(c)}`));
|
|
51613
|
-
if (filteredCandidates.length > 1) logger_logger.verbose(chalk_source.yellow(`Found ${filteredCandidates.length} potential app IDs`));
|
|
51614
|
-
}
|
|
51615
|
-
if (useFirst && filteredCandidates.length > 0) {
|
|
51616
|
-
const selected = filteredCandidates[0];
|
|
51617
|
-
finalAppId = selected.appId;
|
|
51618
|
-
if (selected.detectedUrl) anvilUrl = normalizeAnvilUrl(selected.detectedUrl);
|
|
51619
|
-
if (selected.detectedUsername && !username) username = selected.detectedUsername;
|
|
51620
|
-
logger_logger.success("Auto-selected first app ID: " + chalk_source.bold(finalAppId));
|
|
51621
|
-
} else {
|
|
51622
|
-
const selected = await selectAppId(filteredCandidates);
|
|
51623
|
-
if (!selected) {
|
|
51624
|
-
logger_logger.error("No app ID provided. Cannot continue without an app ID.");
|
|
51625
|
-
process.exit(1);
|
|
51626
|
-
}
|
|
51627
|
-
finalAppId = selected.appId;
|
|
51628
|
-
if (selected.detectedUrl) anvilUrl = normalizeAnvilUrl(selected.detectedUrl);
|
|
51629
|
-
if (selected.detectedUsername && !username) username = selected.detectedUsername;
|
|
51630
|
-
}
|
|
51631
|
-
}
|
|
51632
|
-
const binding = await getAppAuthBinding(repoPath, finalAppId);
|
|
51633
|
-
if (binding.url && !explicitUrl) {
|
|
51634
|
-
anvilUrl = normalizeAnvilUrl(binding.url);
|
|
51635
|
-
logger_logger.verbose(chalk_source.cyan("Using app binding URL: ") + chalk_source.bold(anvilUrl));
|
|
51636
|
-
}
|
|
51637
|
-
if (binding.username && !explicitUsername) {
|
|
51638
|
-
username = binding.username;
|
|
51639
|
-
logger_logger.verbose(chalk_source.cyan("Using app binding username: ") + chalk_source.bold(username));
|
|
51583
|
+
stopLoadingAnimation();
|
|
51584
|
+
process.stdin.removeListener("keypress", onKeypress);
|
|
51585
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) process.stdin.setRawMode(false);
|
|
51586
|
+
process.stdin.pause();
|
|
51587
|
+
clearScreen();
|
|
51588
|
+
if (resolveResult) resolveResult(value);
|
|
51589
|
+
};
|
|
51590
|
+
const onKeypress = (_str, key)=>{
|
|
51591
|
+
if (done) return;
|
|
51592
|
+
if (key.ctrl && "c" === key.name) return void finish(null);
|
|
51593
|
+
if ("escape" === key.name) return void finish(null);
|
|
51594
|
+
if ("return" === key.name) {
|
|
51595
|
+
const state = currentState();
|
|
51596
|
+
const selected = state.items[selectedIndex];
|
|
51597
|
+
finish(selected ? selected.app_id : null);
|
|
51598
|
+
return;
|
|
51640
51599
|
}
|
|
51641
|
-
|
|
51642
|
-
|
|
51643
|
-
|
|
51644
|
-
|
|
51645
|
-
|
|
51646
|
-
if (null === resolvedFallbackUrl) {
|
|
51647
|
-
logger_logger.warn("Operation cancelled.");
|
|
51648
|
-
process.exit(0);
|
|
51600
|
+
if ("up" === key.name) {
|
|
51601
|
+
if (selectedIndex > 0) {
|
|
51602
|
+
selectedIndex -= 1;
|
|
51603
|
+
syncViewport();
|
|
51604
|
+
render();
|
|
51649
51605
|
}
|
|
51650
|
-
|
|
51651
|
-
}
|
|
51652
|
-
anvilUrl = normalizeAnvilUrl(anvilUrl);
|
|
51653
|
-
logger_logger.verbose(chalk_source.green("Using app ID: ") + chalk_source.bold(finalAppId));
|
|
51654
|
-
logger_logger.verbose(chalk_source.cyan("Using Anvil URL: ") + chalk_source.bold(anvilUrl));
|
|
51655
|
-
const resolvedUsername = await resolveUsernameForUrl(anvilUrl, username);
|
|
51656
|
-
if (null === resolvedUsername) {
|
|
51657
|
-
logger_logger.warn("Operation cancelled.");
|
|
51658
|
-
process.exit(0);
|
|
51606
|
+
return;
|
|
51659
51607
|
}
|
|
51660
|
-
|
|
51661
|
-
|
|
51662
|
-
|
|
51663
|
-
|
|
51664
|
-
|
|
51665
|
-
|
|
51666
|
-
|
|
51608
|
+
if ("down" === key.name) {
|
|
51609
|
+
const maxIndex = Math.max(0, currentState().items.length - 1);
|
|
51610
|
+
if (selectedIndex < maxIndex) {
|
|
51611
|
+
selectedIndex += 1;
|
|
51612
|
+
syncViewport();
|
|
51613
|
+
render();
|
|
51614
|
+
maybeLoadMore();
|
|
51615
|
+
}
|
|
51616
|
+
return;
|
|
51667
51617
|
}
|
|
51668
|
-
if (
|
|
51669
|
-
|
|
51670
|
-
|
|
51671
|
-
|
|
51672
|
-
|
|
51673
|
-
|
|
51674
|
-
|
|
51618
|
+
if ("backspace" === key.name || "delete" === key.name) {
|
|
51619
|
+
if (query.length > 0) {
|
|
51620
|
+
query = query.slice(0, -1);
|
|
51621
|
+
selectedIndex = 0;
|
|
51622
|
+
scrollOffset = 0;
|
|
51623
|
+
render();
|
|
51624
|
+
scheduleQueryFetch();
|
|
51625
|
+
}
|
|
51626
|
+
return;
|
|
51675
51627
|
}
|
|
51676
|
-
|
|
51677
|
-
|
|
51678
|
-
|
|
51679
|
-
|
|
51680
|
-
|
|
51681
|
-
|
|
51682
|
-
});
|
|
51683
|
-
logger_logger.progress("validate", `Validating app ID: ${finalAppId}`);
|
|
51684
|
-
const appIdValidation = await validateAppId(finalAppId, anvilUrl, username);
|
|
51685
|
-
logger_logger.progressEnd("validate");
|
|
51686
|
-
if (!appIdValidation.valid) {
|
|
51687
|
-
logger_logger.error(`App ID validation failed: ${appIdValidation.error}`);
|
|
51688
|
-
logger_logger.verbose(chalk_source.yellow("This could mean:"));
|
|
51689
|
-
logger_logger.verbose(chalk_source.yellow(" • The app ID doesn't exist on the server"));
|
|
51690
|
-
logger_logger.verbose(chalk_source.yellow(" • You don't have access to this app"));
|
|
51691
|
-
logger_logger.verbose(chalk_source.yellow(" • There's a network or authentication issue"));
|
|
51692
|
-
process.exit(1);
|
|
51628
|
+
if (!key.ctrl && !key.meta && key.sequence && 1 === key.sequence.length && key.sequence >= " ") {
|
|
51629
|
+
query += key.sequence;
|
|
51630
|
+
selectedIndex = 0;
|
|
51631
|
+
scrollOffset = 0;
|
|
51632
|
+
render();
|
|
51633
|
+
scheduleQueryFetch();
|
|
51693
51634
|
}
|
|
51694
|
-
|
|
51695
|
-
|
|
51696
|
-
|
|
51697
|
-
|
|
51698
|
-
|
|
51699
|
-
|
|
51700
|
-
|
|
51701
|
-
|
|
51702
|
-
|
|
51703
|
-
|
|
51704
|
-
autoMode,
|
|
51705
|
-
repoPath,
|
|
51706
|
-
appId: finalAppId,
|
|
51707
|
-
anvilUrl,
|
|
51708
|
-
stagedOnly,
|
|
51709
|
-
username
|
|
51635
|
+
};
|
|
51636
|
+
logger_logger.pause();
|
|
51637
|
+
try {
|
|
51638
|
+
external_readline_default().emitKeypressEvents(process.stdin);
|
|
51639
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) process.stdin.setRawMode(true);
|
|
51640
|
+
process.stdin.resume();
|
|
51641
|
+
process.stdin.on("keypress", onKeypress);
|
|
51642
|
+
await ensureFresh(query);
|
|
51643
|
+
return await new Promise((resolve)=>{
|
|
51644
|
+
resolveResult = resolve;
|
|
51710
51645
|
});
|
|
51711
|
-
|
|
51712
|
-
|
|
51713
|
-
logger_logger.error("Error: " + errors_getErrorMessage(e));
|
|
51714
|
-
process.exit(1);
|
|
51646
|
+
} finally{
|
|
51647
|
+
logger_logger.resume();
|
|
51715
51648
|
}
|
|
51716
51649
|
}
|
|
51717
|
-
function
|
|
51718
|
-
|
|
51719
|
-
|
|
51720
|
-
|
|
51721
|
-
await handleWatchCommand({
|
|
51722
|
-
path,
|
|
51723
|
-
appid: options.appid,
|
|
51724
|
-
useFirst: options.first,
|
|
51725
|
-
stagedOnly: options.stagedOnly,
|
|
51726
|
-
autoMode: options.auto,
|
|
51727
|
-
verbose: globalOptions.verbose,
|
|
51728
|
-
open: options.open,
|
|
51729
|
-
url: options.url,
|
|
51730
|
-
user: options.user
|
|
51731
|
-
});
|
|
51650
|
+
async function selectAppForCheckout(anvilUrl, username, deps, initialQuery) {
|
|
51651
|
+
if (!isTestMode() && "1" !== process.env.ANVIL_CHECKOUT_LEGACY_PICKER) return runCustomCheckoutPicker(anvilUrl, username, deps, initialQuery);
|
|
51652
|
+
const picker = createCheckoutPickerSource(anvilUrl, username, deps, {
|
|
51653
|
+
initialQuery
|
|
51732
51654
|
});
|
|
51733
|
-
|
|
51655
|
+
try {
|
|
51656
|
+
const answer = await logger_logger.prompt([
|
|
51657
|
+
{
|
|
51658
|
+
type: "search",
|
|
51659
|
+
name: "value",
|
|
51660
|
+
message: "Search apps by name or app ID",
|
|
51661
|
+
source: picker.source,
|
|
51662
|
+
pageSize: 12,
|
|
51663
|
+
loop: false,
|
|
51664
|
+
instructions: {
|
|
51665
|
+
navigation: "↑↓ navigate • type to filter",
|
|
51666
|
+
pager: "enter select • ctrl+c cancel"
|
|
51667
|
+
}
|
|
51668
|
+
}
|
|
51669
|
+
]);
|
|
51670
|
+
const selected = String(answer.value || "");
|
|
51671
|
+
if (!selected || selected === CHECKOUT_ERROR_VALUE) return null;
|
|
51672
|
+
return selected;
|
|
51673
|
+
} catch (e) {
|
|
51674
|
+
const err = e;
|
|
51675
|
+
if ("ExitPromptError" === err.name || String(err.message || "").includes("User force closed")) return null;
|
|
51676
|
+
throw e;
|
|
51677
|
+
}
|
|
51734
51678
|
}
|
|
51735
|
-
|
|
51736
|
-
|
|
51737
|
-
|
|
51738
|
-
|
|
51739
|
-
|
|
51740
|
-
|
|
51741
|
-
|
|
51742
|
-
|
|
51743
|
-
|
|
51744
|
-
|
|
51745
|
-
|
|
51746
|
-
|
|
51747
|
-
|
|
51748
|
-
|
|
51749
|
-
|
|
51750
|
-
|
|
51751
|
-
|
|
51752
|
-
|
|
51753
|
-
|
|
51754
|
-
|
|
51755
|
-
|
|
51756
|
-
|
|
51757
|
-
.
|
|
51758
|
-
|
|
51759
|
-
|
|
51760
|
-
|
|
51761
|
-
|
|
51762
|
-
|
|
51763
|
-
|
|
51764
|
-
|
|
51765
|
-
|
|
51766
|
-
|
|
51767
|
-
align-items: center;
|
|
51768
|
-
justify-content: center;
|
|
51769
|
-
width: 56px;
|
|
51770
|
-
height: 56px;
|
|
51771
|
-
margin: 0 auto 24px;
|
|
51772
|
-
border-radius: 50%;
|
|
51773
|
-
background: #1bb0ee;
|
|
51774
|
-
color: white;
|
|
51775
|
-
}
|
|
51776
|
-
.icon svg {
|
|
51777
|
-
width: 28px;
|
|
51778
|
-
height: 28px;
|
|
51779
|
-
}
|
|
51780
|
-
h1 {
|
|
51781
|
-
font-size: 24px;
|
|
51782
|
-
font-weight: 600;
|
|
51783
|
-
line-height: 1.3;
|
|
51784
|
-
margin: 0 0 12px;
|
|
51785
|
-
color: #1a1a1a;
|
|
51786
|
-
}
|
|
51787
|
-
p {
|
|
51788
|
-
color: #555555;
|
|
51789
|
-
font-size: 15px;
|
|
51790
|
-
line-height: 1.5;
|
|
51791
|
-
margin: 0;
|
|
51792
|
-
}
|
|
51793
|
-
</style>
|
|
51794
|
-
</head>
|
|
51795
|
-
<body>
|
|
51796
|
-
<main class="card" role="status" aria-live="polite">
|
|
51797
|
-
<div class="icon" aria-hidden="true">
|
|
51798
|
-
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
51799
|
-
<path d="M20 7L9 18l-5-5" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
51800
|
-
</svg>
|
|
51801
|
-
</div>
|
|
51802
|
-
<h1>Anvil login complete</h1>
|
|
51803
|
-
<p>Close this window and return to your terminal to continue.</p>
|
|
51804
|
-
</main>
|
|
51805
|
-
</body>
|
|
51806
|
-
</html>`;
|
|
51807
|
-
function errorPage(errorMsg) {
|
|
51808
|
-
return `<!DOCTYPE html>
|
|
51809
|
-
<html lang="en">
|
|
51810
|
-
<head>
|
|
51811
|
-
<meta charset="utf-8" />
|
|
51812
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
51813
|
-
<title>Anvil Sync • Login Error</title>
|
|
51814
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
51815
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
51816
|
-
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
51817
|
-
<style>
|
|
51818
|
-
* { box-sizing: border-box; }
|
|
51819
|
-
html, body { height: 100%; margin: 0; }
|
|
51820
|
-
body {
|
|
51821
|
-
font-family: Poppins, -apple-system, system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
51822
|
-
color: #555555;
|
|
51823
|
-
background: #f7fdff;
|
|
51824
|
-
display: flex;
|
|
51825
|
-
align-items: center;
|
|
51826
|
-
justify-content: center;
|
|
51827
|
-
padding: 20px;
|
|
51679
|
+
const defaultCheckoutDeps = {
|
|
51680
|
+
getValidAuthToken: auth_getValidAuthToken,
|
|
51681
|
+
validateAppId: validateAppId,
|
|
51682
|
+
listAppsForCheckout: listAppsForCheckout,
|
|
51683
|
+
runInteractiveLoginFlow: runInteractiveLoginFlow,
|
|
51684
|
+
clone: async (repoUrl, destinationPath, options)=>{
|
|
51685
|
+
const cloneArgs = [];
|
|
51686
|
+
if (options?.branch) cloneArgs.push("--branch", options.branch);
|
|
51687
|
+
if ("number" == typeof options?.depth) cloneArgs.push("--depth", String(options.depth));
|
|
51688
|
+
if (options?.singleBranch) cloneArgs.push("--single-branch");
|
|
51689
|
+
if (options?.origin) cloneArgs.push("--origin", options.origin);
|
|
51690
|
+
if (options?.quiet) cloneArgs.push("--quiet");
|
|
51691
|
+
if (options?.verbose) cloneArgs.push("--verbose");
|
|
51692
|
+
await esm_default().clone(repoUrl, destinationPath, cloneArgs);
|
|
51693
|
+
},
|
|
51694
|
+
hardenCheckoutGitAuth: hardenCheckoutGitAuth
|
|
51695
|
+
};
|
|
51696
|
+
function isInteractiveSession() {
|
|
51697
|
+
return !!(process.stdin.isTTY && process.stdout.isTTY);
|
|
51698
|
+
}
|
|
51699
|
+
function parseCheckoutInput(input) {
|
|
51700
|
+
const trimmed = input.trim();
|
|
51701
|
+
if (!trimmed) throw new Error("Input is required.");
|
|
51702
|
+
if (/^[A-Z0-9]+$/.test(trimmed)) return {
|
|
51703
|
+
appId: trimmed
|
|
51704
|
+
};
|
|
51705
|
+
const asUrl = /^https?:\/\//.test(trimmed) ? trimmed : normalizeAnvilUrl(trimmed);
|
|
51706
|
+
let parsed;
|
|
51707
|
+
try {
|
|
51708
|
+
parsed = new URL(asUrl);
|
|
51709
|
+
} catch {
|
|
51710
|
+
throw new Error("Input must be an editor URL, /git URL, or bare app ID.");
|
|
51828
51711
|
}
|
|
51829
|
-
.
|
|
51830
|
-
|
|
51831
|
-
|
|
51832
|
-
|
|
51833
|
-
|
|
51834
|
-
|
|
51835
|
-
|
|
51712
|
+
const gitMatch = parsed.pathname.match(/\/git\/([A-Z0-9]+)\.git(?:$|\/)/);
|
|
51713
|
+
if (gitMatch) return {
|
|
51714
|
+
appId: gitMatch[1],
|
|
51715
|
+
detectedUrl: `${parsed.protocol}//${parsed.host}`
|
|
51716
|
+
};
|
|
51717
|
+
const appsMatch = parsed.pathname.match(/\/apps\/([A-Z0-9]+)(?:\/|$)/);
|
|
51718
|
+
if (appsMatch) return {
|
|
51719
|
+
appId: appsMatch[1],
|
|
51720
|
+
detectedUrl: `${parsed.protocol}//${parsed.host}`
|
|
51721
|
+
};
|
|
51722
|
+
throw new Error("Could not extract app ID. Expected URL path containing /apps/<APPID> or /git/<APPID>.git.");
|
|
51723
|
+
}
|
|
51724
|
+
function sanitizeDirectoryName(name) {
|
|
51725
|
+
return name.trim().replace(/\s+/g, "_").replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
51726
|
+
}
|
|
51727
|
+
function formatPathForDisplay(destinationPath) {
|
|
51728
|
+
const relative = external_path_default().relative(process.cwd(), destinationPath);
|
|
51729
|
+
if ("" === relative) return ".";
|
|
51730
|
+
if (relative.startsWith("..")) return destinationPath;
|
|
51731
|
+
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
51732
|
+
}
|
|
51733
|
+
function getDefaultDestinationDirectory(appId, appName) {
|
|
51734
|
+
if (appName) {
|
|
51735
|
+
const sanitized = sanitizeDirectoryName(appName);
|
|
51736
|
+
if (sanitized) return sanitized;
|
|
51836
51737
|
}
|
|
51837
|
-
|
|
51838
|
-
|
|
51839
|
-
|
|
51840
|
-
|
|
51841
|
-
|
|
51842
|
-
|
|
51843
|
-
margin: 0 auto 24px;
|
|
51844
|
-
border-radius: 50%;
|
|
51845
|
-
background: #ef4444;
|
|
51846
|
-
color: white;
|
|
51738
|
+
return appId;
|
|
51739
|
+
}
|
|
51740
|
+
async function resolveCheckoutUrl(explicitUrl, parsedUrl) {
|
|
51741
|
+
if (explicitUrl) {
|
|
51742
|
+
logger_logger.verbose(chalk_source.cyan("Using Anvil URL from --url: ") + chalk_source.bold(normalizeAnvilUrl(explicitUrl)));
|
|
51743
|
+
return normalizeAnvilUrl(explicitUrl);
|
|
51847
51744
|
}
|
|
51848
|
-
|
|
51849
|
-
|
|
51850
|
-
|
|
51745
|
+
if (parsedUrl) {
|
|
51746
|
+
logger_logger.verbose(chalk_source.cyan("Using Anvil URL from checkout input: ") + chalk_source.bold(normalizeAnvilUrl(parsedUrl)));
|
|
51747
|
+
return normalizeAnvilUrl(parsedUrl);
|
|
51851
51748
|
}
|
|
51852
|
-
|
|
51853
|
-
|
|
51854
|
-
|
|
51855
|
-
|
|
51856
|
-
margin: 0 0 16px;
|
|
51857
|
-
color: #1a1a1a;
|
|
51749
|
+
const decision = decideFallbackUrl(void 0);
|
|
51750
|
+
if ("available-multiple" !== decision.source) {
|
|
51751
|
+
if (decision.url) logger_logger.verbose(chalk_source.cyan("Using configured Anvil URL: ") + chalk_source.bold(decision.url));
|
|
51752
|
+
return decision.url;
|
|
51858
51753
|
}
|
|
51859
|
-
|
|
51860
|
-
|
|
51861
|
-
|
|
51862
|
-
|
|
51863
|
-
|
|
51754
|
+
const choices = decision.urls.map((url)=>({
|
|
51755
|
+
name: url,
|
|
51756
|
+
value: url
|
|
51757
|
+
}));
|
|
51758
|
+
choices.push({
|
|
51759
|
+
name: "Cancel",
|
|
51760
|
+
value: null
|
|
51761
|
+
});
|
|
51762
|
+
return logger_logger.select("Multiple logged-in Anvil URLs found. Which one would you like to use?", choices, decision.urls[0]);
|
|
51763
|
+
}
|
|
51764
|
+
async function isPathInsideGitRepo(targetPath) {
|
|
51765
|
+
const resolved = external_path_default().resolve(targetPath);
|
|
51766
|
+
let current = resolved;
|
|
51767
|
+
while(!external_fs_.existsSync(current)){
|
|
51768
|
+
const parent = external_path_default().dirname(current);
|
|
51769
|
+
if (parent === current) return false;
|
|
51770
|
+
current = parent;
|
|
51864
51771
|
}
|
|
51865
|
-
|
|
51866
|
-
|
|
51867
|
-
|
|
51868
|
-
|
|
51869
|
-
|
|
51870
|
-
|
|
51871
|
-
|
|
51872
|
-
|
|
51873
|
-
background: #f8f9fa;
|
|
51874
|
-
border-radius: 6px;
|
|
51875
|
-
text-align: left;
|
|
51772
|
+
try {
|
|
51773
|
+
const output = (await esm_default(current).raw([
|
|
51774
|
+
"rev-parse",
|
|
51775
|
+
"--is-inside-work-tree"
|
|
51776
|
+
])).trim();
|
|
51777
|
+
return "true" === output;
|
|
51778
|
+
} catch {
|
|
51779
|
+
return false;
|
|
51876
51780
|
}
|
|
51877
|
-
</style>
|
|
51878
|
-
</head>
|
|
51879
|
-
<body>
|
|
51880
|
-
<main class="card" role="status" aria-live="polite">
|
|
51881
|
-
<div class="icon" aria-hidden="true">
|
|
51882
|
-
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
51883
|
-
<path d="M15 9l-6 6M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
51884
|
-
</svg>
|
|
51885
|
-
</div>
|
|
51886
|
-
<h1>Anvil login failed</h1>
|
|
51887
|
-
<div id="details" class="details">${errorMsg}</div>
|
|
51888
|
-
<p>Close this window and return to your terminal to retry.</p>
|
|
51889
|
-
</main>
|
|
51890
|
-
</body>
|
|
51891
|
-
</html>`;
|
|
51892
51781
|
}
|
|
51893
|
-
|
|
51894
|
-
|
|
51895
|
-
|
|
51896
|
-
|
|
51782
|
+
async function isDirectoryNonEmpty(destinationPath) {
|
|
51783
|
+
if (!external_fs_.existsSync(destinationPath)) return false;
|
|
51784
|
+
const stats = await external_fs_.promises.stat(destinationPath);
|
|
51785
|
+
if (!stats.isDirectory()) return true;
|
|
51786
|
+
const entries = await external_fs_.promises.readdir(destinationPath);
|
|
51787
|
+
return entries.length > 0;
|
|
51897
51788
|
}
|
|
51898
|
-
function
|
|
51899
|
-
|
|
51789
|
+
async function validateCheckoutDestination(destinationPath, force = false) {
|
|
51790
|
+
const nonEmpty = await isDirectoryNonEmpty(destinationPath);
|
|
51791
|
+
if (nonEmpty && !force) throw new Error(`Destination '${destinationPath}' already exists and is not empty.`);
|
|
51792
|
+
const insideGit = await isPathInsideGitRepo(destinationPath);
|
|
51793
|
+
if (insideGit && !force) throw new Error(`Destination '${destinationPath}' is inside an existing Git repository.`);
|
|
51794
|
+
if (force && nonEmpty) logger_logger.warn(`--force set: destination '${destinationPath}' is non-empty and clone may still fail.`);
|
|
51795
|
+
if (force && insideGit) logger_logger.warn("--force set: cloning inside an existing Git repository.");
|
|
51900
51796
|
}
|
|
51901
|
-
function
|
|
51902
|
-
|
|
51903
|
-
|
|
51904
|
-
|
|
51905
|
-
}
|
|
51906
|
-
|
|
51907
|
-
|
|
51908
|
-
|
|
51909
|
-
|
|
51910
|
-
|
|
51911
|
-
|
|
51912
|
-
|
|
51913
|
-
|
|
51914
|
-
|
|
51915
|
-
|
|
51916
|
-
|
|
51917
|
-
|
|
51918
|
-
|
|
51919
|
-
|
|
51920
|
-
|
|
51921
|
-
|
|
51922
|
-
|
|
51923
|
-
|
|
51924
|
-
|
|
51925
|
-
|
|
51926
|
-
|
|
51927
|
-
|
|
51928
|
-
|
|
51929
|
-
|
|
51930
|
-
|
|
51931
|
-
logger_logger.warn("Could not open a browser automatically.");
|
|
51932
|
-
close(false);
|
|
51933
|
-
}
|
|
51934
|
-
});
|
|
51797
|
+
async function ensureCheckoutAuthToken(anvilUrl, username, deps = defaultCheckoutDeps) {
|
|
51798
|
+
try {
|
|
51799
|
+
logger_logger.verbose(chalk_source.cyan("Checking auth token for: ") + chalk_source.bold(username ? `${username} @ ${anvilUrl}` : anvilUrl));
|
|
51800
|
+
return await deps.getValidAuthToken(anvilUrl, username);
|
|
51801
|
+
} catch (e) {
|
|
51802
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
51803
|
+
if (!interactive) throw errors_createAuthError.required(`Not logged in to ${anvilUrl}. Run 'anvil login ${anvilUrl}' and retry.`);
|
|
51804
|
+
const shouldLogin = await logger_logger.confirm(`Not logged in to ${anvilUrl}. Log in now?`, true);
|
|
51805
|
+
if (!shouldLogin) throw errors_createAuthError.required(`Not logged in to ${anvilUrl}. Run 'anvil login ${anvilUrl}' and retry.`);
|
|
51806
|
+
await deps.runInteractiveLoginFlow(anvilUrl);
|
|
51807
|
+
return deps.getValidAuthToken(anvilUrl, username);
|
|
51808
|
+
}
|
|
51809
|
+
}
|
|
51810
|
+
async function resolveCheckoutUsername(anvilUrl, explicitUsername, getAccounts = auth_getAccountsForUrl) {
|
|
51811
|
+
if (explicitUsername) return explicitUsername;
|
|
51812
|
+
const accounts = getAccounts(anvilUrl);
|
|
51813
|
+
if (0 === accounts.length) return;
|
|
51814
|
+
if (1 === accounts.length) {
|
|
51815
|
+
logger_logger.verbose(chalk_source.cyan("Auto-selected account: ") + chalk_source.bold(accounts[0]));
|
|
51816
|
+
return accounts[0];
|
|
51817
|
+
}
|
|
51818
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
51819
|
+
if (!interactive) throw new Error(`Multiple accounts found for ${anvilUrl}. Use --user <USERNAME> to choose one.`);
|
|
51820
|
+
const choices = accounts.map((acct)=>({
|
|
51821
|
+
name: acct,
|
|
51822
|
+
value: acct
|
|
51823
|
+
}));
|
|
51824
|
+
choices.push({
|
|
51825
|
+
name: "Cancel",
|
|
51826
|
+
value: null
|
|
51935
51827
|
});
|
|
51936
|
-
return {
|
|
51937
|
-
waitForOpen,
|
|
51938
|
-
close
|
|
51939
|
-
};
|
|
51828
|
+
return logger_logger.select(`Multiple accounts found for ${anvilUrl}. Which account should be used for checkout?`, choices, accounts[0]);
|
|
51940
51829
|
}
|
|
51941
|
-
async function
|
|
51942
|
-
|
|
51943
|
-
|
|
51944
|
-
|
|
51945
|
-
|
|
51830
|
+
async function executeCheckout(options, deps = defaultCheckoutDeps) {
|
|
51831
|
+
let checkoutInput = options.input?.trim();
|
|
51832
|
+
let preselectedAnvilUrl;
|
|
51833
|
+
let preselectedUsername;
|
|
51834
|
+
if (!checkoutInput) {
|
|
51835
|
+
if (!isInteractiveSession()) throw new Error("`anvil checkout` without arguments requires an interactive TTY. Pass APP_ID or URL explicitly in non-interactive mode.");
|
|
51836
|
+
const selectedUrl = await resolveCheckoutUrl(options.url);
|
|
51837
|
+
if (!selectedUrl) return void logger_logger.info("Checkout cancelled.");
|
|
51838
|
+
preselectedAnvilUrl = selectedUrl;
|
|
51839
|
+
const selectedUsername = await resolveCheckoutUsername(selectedUrl, options.user);
|
|
51840
|
+
if (null === selectedUsername) return void logger_logger.info("Checkout cancelled.");
|
|
51841
|
+
preselectedUsername = selectedUsername || void 0;
|
|
51842
|
+
const selectedInput = await selectAppForCheckout(selectedUrl, preselectedUsername, deps, options.query);
|
|
51843
|
+
if (!selectedInput) return void logger_logger.info("Checkout cancelled.");
|
|
51844
|
+
checkoutInput = selectedInput;
|
|
51946
51845
|
}
|
|
51947
|
-
|
|
51948
|
-
|
|
51846
|
+
logger_logger.verbose(chalk_source.cyan("Checkout input: ") + chalk_source.bold(checkoutInput));
|
|
51847
|
+
const parsed = parseCheckoutInput(checkoutInput);
|
|
51848
|
+
logger_logger.verbose(chalk_source.cyan("Resolved app ID: ") + chalk_source.bold(parsed.appId));
|
|
51849
|
+
const anvilUrl = preselectedAnvilUrl || await resolveCheckoutUrl(options.url, parsed.detectedUrl);
|
|
51850
|
+
if (!anvilUrl) return void logger_logger.info("Checkout cancelled.");
|
|
51851
|
+
const resolvedUsername = preselectedUsername || await resolveCheckoutUsername(anvilUrl, options.user);
|
|
51852
|
+
if (null === resolvedUsername) return void logger_logger.info("Checkout cancelled.");
|
|
51853
|
+
if (resolvedUsername) logger_logger.verbose(chalk_source.cyan("Using account: ") + chalk_source.bold(resolvedUsername));
|
|
51854
|
+
else logger_logger.verbose(chalk_source.cyan("No account preselected; resolving after auth token lookup."));
|
|
51855
|
+
const authToken = await ensureCheckoutAuthToken(anvilUrl, resolvedUsername, deps);
|
|
51856
|
+
let checkoutUsername = resolvedUsername;
|
|
51857
|
+
if (!checkoutUsername) {
|
|
51858
|
+
const inferredUsername = await resolveCheckoutUsername(anvilUrl, void 0);
|
|
51859
|
+
if (null === inferredUsername) return void logger_logger.info("Checkout cancelled.");
|
|
51860
|
+
if (!inferredUsername) throw new Error(`Could not determine account for ${anvilUrl}. Use --user <USERNAME> so checkout can bind repository credentials.`);
|
|
51861
|
+
checkoutUsername = inferredUsername;
|
|
51862
|
+
}
|
|
51863
|
+
logger_logger.verbose(chalk_source.cyan("Checkout account: ") + chalk_source.bold(checkoutUsername));
|
|
51864
|
+
logger_logger.verbose(chalk_source.blue(`Validating app ID ${parsed.appId} with Anvil server...`));
|
|
51865
|
+
const validation = await deps.validateAppId(parsed.appId, anvilUrl, checkoutUsername);
|
|
51866
|
+
if (!validation.valid) throw new Error(validation.error || `App '${parsed.appId}' is not accessible on ${anvilUrl}.`);
|
|
51867
|
+
logger_logger.verbose(chalk_source.green(`✔ App ID validated successfully`));
|
|
51868
|
+
if (validation.app_name) logger_logger.verbose(chalk_source.gray(` App name: ${validation.app_name}`));
|
|
51869
|
+
const destinationDir = options.directory || getDefaultDestinationDirectory(parsed.appId, validation.app_name);
|
|
51870
|
+
const destinationPath = external_path_default().resolve(process.cwd(), destinationDir);
|
|
51871
|
+
const destinationDisplay = formatPathForDisplay(destinationPath);
|
|
51872
|
+
logger_logger.verbose(chalk_source.cyan("Resolved destination: ") + chalk_source.bold(destinationDisplay));
|
|
51873
|
+
await validateCheckoutDestination(destinationPath, options.force);
|
|
51874
|
+
const cloneUrl = getGitPushUrl(parsed.appId, authToken, anvilUrl);
|
|
51875
|
+
logger_logger.verbose(chalk_source.cyan("Clone source: ") + chalk_source.bold(`${anvilUrl}/git/${parsed.appId}.git`));
|
|
51876
|
+
if (options.branch) logger_logger.verbose(chalk_source.cyan("Requested branch: ") + chalk_source.bold(options.branch));
|
|
51877
|
+
if ("number" == typeof options.depth) logger_logger.verbose(chalk_source.cyan("Clone depth: ") + chalk_source.bold(String(options.depth)));
|
|
51878
|
+
if (options.singleBranch) logger_logger.verbose(chalk_source.cyan("Single-branch clone enabled"));
|
|
51879
|
+
logger_logger.progress("checkout", `Checking out app ${parsed.appId} from ${anvilUrl}`);
|
|
51880
|
+
logger_logger.info(chalk_source.gray(` Destination directory: ${destinationDisplay}`));
|
|
51881
|
+
await deps.clone(cloneUrl, destinationPath, {
|
|
51882
|
+
branch: options.branch,
|
|
51883
|
+
depth: options.depth,
|
|
51884
|
+
singleBranch: options.singleBranch,
|
|
51885
|
+
origin: options.origin,
|
|
51886
|
+
quiet: options.quiet,
|
|
51887
|
+
verbose: options.verbose
|
|
51888
|
+
});
|
|
51889
|
+
const remoteName = options.origin || "origin";
|
|
51949
51890
|
try {
|
|
51950
|
-
|
|
51951
|
-
|
|
51952
|
-
|
|
51953
|
-
|
|
51891
|
+
await deps.hardenCheckoutGitAuth({
|
|
51892
|
+
repoPath: destinationPath,
|
|
51893
|
+
appId: parsed.appId,
|
|
51894
|
+
anvilUrl,
|
|
51895
|
+
username: checkoutUsername,
|
|
51896
|
+
remoteName
|
|
51897
|
+
});
|
|
51898
|
+
logger_logger.verbose(chalk_source.cyan("Configured Git auth bridge for remote: ") + chalk_source.bold(remoteName));
|
|
51899
|
+
} catch (e) {
|
|
51900
|
+
throw new Error(`Checkout clone succeeded, but failed to configure repository credentials: ${errors_getErrorMessage(e)}. The repository exists at ${destinationDisplay}, but Git auth bridge setup is incomplete.`);
|
|
51901
|
+
}
|
|
51902
|
+
logger_logger.progressEnd("checkout", `Checked out ${parsed.appId} into ${destinationDisplay}`);
|
|
51903
|
+
const preferredEditor = String(getConfig("preferredEditor") || "").trim();
|
|
51904
|
+
const preferredEditorCommand = preferredEditor ? getPreferredEditorCommand(preferredEditor) : "";
|
|
51905
|
+
if (options.open) {
|
|
51906
|
+
await checkout_openPathInEditorOrDefault(destinationPath, preferredEditorCommand);
|
|
51907
|
+
logger_logger.info(chalk_source.gray(`Opened ${destinationDisplay}`));
|
|
51954
51908
|
}
|
|
51909
|
+
if (preferredEditorCommand && !options.open) if (isCommandAvailable(preferredEditorCommand)) logger_logger.info(chalk_source.cyan(`Next: ${preferredEditorCommand} ${destinationDisplay}`));
|
|
51910
|
+
else logger_logger.warn(`Preferred editor command '${preferredEditorCommand}' was not found in PATH. Run 'anvil configure' to choose an installed editor.`);
|
|
51955
51911
|
}
|
|
51956
|
-
async function
|
|
51957
|
-
|
|
51958
|
-
method: "POST",
|
|
51959
|
-
headers: {
|
|
51960
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
51961
|
-
},
|
|
51962
|
-
body: new URLSearchParams({
|
|
51963
|
-
grant_type: "authorization_code",
|
|
51964
|
-
code,
|
|
51965
|
-
redirect_uri: redirectUri,
|
|
51966
|
-
client_id: CLIENT_ID,
|
|
51967
|
-
code_verifier: codeVerifier
|
|
51968
|
-
})
|
|
51969
|
-
});
|
|
51970
|
-
if (!tokenResponse.ok) throw new Error(`Failed to exchange authorization code for token. ${await parseOAuthError(tokenResponse)}`);
|
|
51971
|
-
return await tokenResponse.json();
|
|
51912
|
+
async function checkout_openPathInEditorOrDefault(destinationPath, preferredEditorCommand, deps) {
|
|
51913
|
+
await openPathInEditorOrDefault(destinationPath, preferredEditorCommand, deps);
|
|
51972
51914
|
}
|
|
51973
|
-
|
|
51974
|
-
const
|
|
51975
|
-
|
|
51976
|
-
|
|
51977
|
-
"
|
|
51978
|
-
|
|
51979
|
-
|
|
51980
|
-
|
|
51981
|
-
|
|
51982
|
-
|
|
51915
|
+
function registerCheckoutCommand(program) {
|
|
51916
|
+
const checkoutCommand = program.command("checkout [input] [directory]").description("Check out an Anvil app locally from editor URL, git URL, app ID, or interactive selection").alias("co").option("-O, --open", "Open destination after checkout").option("-b, --branch <BRANCH>", "Checkout a specific branch").option("--depth <N>", "Create a shallow clone with history truncated to N commits", (value)=>parseInt(value, 10)).option("--single-branch", "Clone only one branch").option("--origin <NAME>", "Use a custom remote name instead of origin").option("--quiet", "Suppress git clone progress output").option("--verbose", "Enable verbose git clone output").option("-u, --url <ANVIL_URL>", "Specify Anvil server URL").option("-U, --user <USERNAME>", "Specify which user account to use").option("-f, --force", "Override safety checks for destination path").option("-Q, --query <QUERY>", "Initial search query for interactive checkout picker").action(async (input, directory, options, command)=>{
|
|
51917
|
+
try {
|
|
51918
|
+
const globalOptions = command?.optsWithGlobals() || options || {};
|
|
51919
|
+
if ("number" == typeof options?.depth && (!Number.isFinite(options.depth) || options.depth <= 0)) throw new Error("--depth must be a positive integer");
|
|
51920
|
+
await executeCheckout({
|
|
51921
|
+
input,
|
|
51922
|
+
directory,
|
|
51923
|
+
open: options?.open,
|
|
51924
|
+
branch: options?.branch,
|
|
51925
|
+
depth: options?.depth,
|
|
51926
|
+
singleBranch: options?.singleBranch,
|
|
51927
|
+
origin: options?.origin,
|
|
51928
|
+
quiet: options?.quiet,
|
|
51929
|
+
verbose: globalOptions.verbose,
|
|
51930
|
+
url: options?.url,
|
|
51931
|
+
user: options?.user,
|
|
51932
|
+
force: options?.force,
|
|
51933
|
+
query: options?.query
|
|
51934
|
+
}, defaultCheckoutDeps);
|
|
51935
|
+
} catch (e) {
|
|
51936
|
+
logger_logger.error("Error: " + errors_getErrorMessage(e));
|
|
51937
|
+
process.exit(1);
|
|
51938
|
+
}
|
|
51983
51939
|
});
|
|
51984
|
-
|
|
51985
|
-
return await response.json();
|
|
51940
|
+
checkoutCommand.addHelpText("after", "\n" + chalk_source.bold("Examples:") + '\n anvil checkout http://localhost:3000/build/apps/APPID/code/assets/theme.css\n anvil checkout https://anvil.works/git/APPID.git\n anvil checkout APPID --url anvil.works\n anvil checkout APPID --branch master --depth 1 --single-branch\n anvil checkout APPID -O # open editor/file browser after checkout\n anvil checkout APPID my-local-folder --force\n anvil checkout # interactive search/select app list\n anvil checkout -Q "dashboard" # preseed picker search\n');
|
|
51986
51941
|
}
|
|
51987
|
-
|
|
51988
|
-
|
|
51989
|
-
|
|
51990
|
-
|
|
51991
|
-
|
|
51992
|
-
|
|
51993
|
-
|
|
51994
|
-
|
|
51995
|
-
|
|
51996
|
-
|
|
51997
|
-
|
|
51998
|
-
|
|
51999
|
-
|
|
52000
|
-
client_id: CLIENT_ID
|
|
52001
|
-
})
|
|
51942
|
+
const DEFAULT_ANVIL_URL = resolveAnvilUrl();
|
|
51943
|
+
async function syncToLatest(repoPath, appId, anvilUrl, authToken, currentBranch, username) {
|
|
51944
|
+
try {
|
|
51945
|
+
const git = esm_default(repoPath);
|
|
51946
|
+
const commitId = (await git.revparse([
|
|
51947
|
+
"HEAD"
|
|
51948
|
+
])).trim();
|
|
51949
|
+
const session = new WatchSession(repoPath, appId, {
|
|
51950
|
+
anvilUrl,
|
|
51951
|
+
authToken,
|
|
51952
|
+
currentBranch,
|
|
51953
|
+
commitId,
|
|
51954
|
+
username
|
|
52002
51955
|
});
|
|
52003
|
-
|
|
52004
|
-
|
|
52005
|
-
|
|
52006
|
-
|
|
52007
|
-
|
|
52008
|
-
|
|
52009
|
-
|
|
52010
|
-
|
|
52011
|
-
|
|
52012
|
-
continue;
|
|
52013
|
-
}
|
|
52014
|
-
if ("access_denied" === error) throw new Error("Device login was denied.");
|
|
52015
|
-
if ("expired_token" === error || "invalid_grant" === error) break;
|
|
52016
|
-
throw new Error(`Device login failed. ${error}`);
|
|
51956
|
+
await session.initialize();
|
|
51957
|
+
await session.syncRemoteChanges();
|
|
51958
|
+
const newCommitId = (await git.revparse([
|
|
51959
|
+
"HEAD"
|
|
51960
|
+
])).trim();
|
|
51961
|
+
return newCommitId;
|
|
51962
|
+
} catch (e) {
|
|
51963
|
+
if (isAnvilError(e)) throw e;
|
|
51964
|
+
throw errors_createGitError.commandFailed("sync", e instanceof Error ? e.message : String(e));
|
|
52017
51965
|
}
|
|
52018
|
-
throw new Error("Device login expired before it was approved.");
|
|
52019
51966
|
}
|
|
52020
|
-
async function
|
|
52021
|
-
|
|
52022
|
-
const
|
|
52023
|
-
|
|
52024
|
-
const
|
|
52025
|
-
|
|
52026
|
-
|
|
52027
|
-
|
|
52028
|
-
|
|
52029
|
-
|
|
52030
|
-
|
|
52031
|
-
|
|
52032
|
-
|
|
52033
|
-
|
|
52034
|
-
|
|
52035
|
-
|
|
52036
|
-
|
|
52037
|
-
|
|
52038
|
-
|
|
52039
|
-
|
|
52040
|
-
|
|
52041
|
-
|
|
52042
|
-
|
|
52043
|
-
|
|
52044
|
-
|
|
52045
|
-
|
|
52046
|
-
|
|
52047
|
-
|
|
52048
|
-
|
|
52049
|
-
|
|
52050
|
-
|
|
52051
|
-
|
|
52052
|
-
|
|
52053
|
-
|
|
52054
|
-
|
|
52055
|
-
|
|
52056
|
-
|
|
52057
|
-
|
|
52058
|
-
|
|
52059
|
-
|
|
52060
|
-
|
|
52061
|
-
|
|
52062
|
-
|
|
52063
|
-
server.close();
|
|
52064
|
-
});
|
|
51967
|
+
async function api_watch(repoPath, appId, anvilUrl = DEFAULT_ANVIL_URL, stagedOnly = false, username) {
|
|
51968
|
+
repoPath = external_path_default().resolve(repoPath);
|
|
51969
|
+
const authToken = await auth_getValidAuthToken(anvilUrl, username);
|
|
51970
|
+
await verifyAuth(authToken, anvilUrl);
|
|
51971
|
+
const git = esm_default(repoPath);
|
|
51972
|
+
let currentBranch;
|
|
51973
|
+
try {
|
|
51974
|
+
const branchRef = await git.revparse([
|
|
51975
|
+
"--abbrev-ref",
|
|
51976
|
+
"HEAD"
|
|
51977
|
+
]);
|
|
51978
|
+
if ("HEAD" === branchRef) throw errors_createGitError.commandFailed("revparse", "Cannot sync from detached HEAD state. Please checkout a branch first.");
|
|
51979
|
+
currentBranch = branchRef;
|
|
51980
|
+
} catch (e) {
|
|
51981
|
+
if ("git_command_failed" === e.type) throw e;
|
|
51982
|
+
throw errors_createGitError.commandFailed("revparse", e.message);
|
|
51983
|
+
}
|
|
51984
|
+
let commitId;
|
|
51985
|
+
try {
|
|
51986
|
+
commitId = (await git.revparse([
|
|
51987
|
+
"HEAD"
|
|
51988
|
+
])).trim();
|
|
51989
|
+
} catch (e) {
|
|
51990
|
+
throw errors_createGitError.commandFailed("revparse", e.message);
|
|
51991
|
+
}
|
|
51992
|
+
logger_logger.verbose(chalk_source.cyan("Current branch: ") + chalk_source.bold(currentBranch));
|
|
51993
|
+
logger_logger.verbose(chalk_source.cyan("Current commit ID: ") + chalk_source.gray(commitId));
|
|
51994
|
+
const syncStatus = await validateBranchSyncStatus(git, currentBranch, appId, anvilUrl, username);
|
|
51995
|
+
let hasUncommittedChanges = false;
|
|
51996
|
+
try {
|
|
51997
|
+
const status = await git.status();
|
|
51998
|
+
hasUncommittedChanges = status.files.length > 0;
|
|
51999
|
+
} catch (e) {
|
|
52000
|
+
throw errors_createGitError.commandFailed("status", e.message);
|
|
52001
|
+
}
|
|
52002
|
+
logger_logger.verbose(chalk_source.blue("Has uncommitted changes: ") + chalk_source.bold(hasUncommittedChanges));
|
|
52003
|
+
const session = new WatchSession(repoPath, appId, {
|
|
52004
|
+
anvilUrl,
|
|
52005
|
+
authToken,
|
|
52006
|
+
currentBranch,
|
|
52007
|
+
commitId,
|
|
52008
|
+
stagedOnly,
|
|
52009
|
+
username
|
|
52065
52010
|
});
|
|
52066
|
-
|
|
52067
|
-
|
|
52068
|
-
closed = true;
|
|
52011
|
+
session.on("branch-changed", (data)=>{
|
|
52012
|
+
logger_logger.debug("Event: branch-changed", data);
|
|
52069
52013
|
});
|
|
52070
|
-
|
|
52071
|
-
|
|
52072
|
-
closed = true;
|
|
52073
|
-
server.closeAllConnections();
|
|
52074
|
-
if (!server.listening) return;
|
|
52075
|
-
await new Promise((resolve)=>server.close(()=>resolve()));
|
|
52076
|
-
};
|
|
52077
|
-
return {
|
|
52078
|
-
authUrl,
|
|
52079
|
-
close,
|
|
52080
|
-
waitForLogin: (async ()=>{
|
|
52081
|
-
const { code, error, recvState } = await codePromise;
|
|
52082
|
-
if (recvState !== state) throw new Error("Invalid state received from OAuth callback");
|
|
52083
|
-
if (error) throw new Error(`Error received from OAuth callback: ${error}`);
|
|
52084
|
-
const tokenData = await exchangeAuthorizationCodeForTokens(anvilUrl, redirectUri, code, codeVerifier);
|
|
52085
|
-
return login(anvilUrl, {
|
|
52086
|
-
access_token: tokenData.access_token,
|
|
52087
|
-
refresh_token: tokenData.refresh_token,
|
|
52088
|
-
expires_in: tokenData.expires_in,
|
|
52089
|
-
scope: tokenData.scope
|
|
52090
|
-
});
|
|
52091
|
-
})()
|
|
52092
|
-
};
|
|
52093
|
-
}
|
|
52094
|
-
function raceLoginAttempts(attempts) {
|
|
52095
|
-
return new Promise((resolve, reject)=>{
|
|
52096
|
-
let remaining = attempts.length;
|
|
52097
|
-
let lastError = null;
|
|
52098
|
-
for (const attempt of attempts)attempt.then(resolve).catch((error)=>{
|
|
52099
|
-
if ("cancelled" === error.message) return;
|
|
52100
|
-
remaining -= 1;
|
|
52101
|
-
lastError = error;
|
|
52102
|
-
if (0 === remaining && lastError) reject(lastError);
|
|
52103
|
-
});
|
|
52014
|
+
session.on("sync-conflict", (data)=>{
|
|
52015
|
+
logger_logger.debug("Event: sync-conflict", data);
|
|
52104
52016
|
});
|
|
52105
|
-
|
|
52106
|
-
|
|
52107
|
-
|
|
52108
|
-
|
|
52109
|
-
|
|
52110
|
-
|
|
52111
|
-
|
|
52112
|
-
|
|
52113
|
-
|
|
52114
|
-
|
|
52115
|
-
|
|
52116
|
-
console.log();
|
|
52117
|
-
logger_logger.info("OR Press ENTER to open a browser...");
|
|
52118
|
-
browserPrompt.waitForOpen.then((opened)=>{
|
|
52119
|
-
if (opened && !settled) {
|
|
52120
|
-
spinnerStarted = true;
|
|
52121
|
-
logger_logger.progress("login", "Waiting for login to complete...");
|
|
52122
|
-
}
|
|
52017
|
+
session.on("remote-update", (data)=>{
|
|
52018
|
+
logger_logger.debug("Event: remote-update", data);
|
|
52019
|
+
});
|
|
52020
|
+
session.on("validation-failed", (data)=>{
|
|
52021
|
+
logger_logger.debug("Event: validation-failed", data);
|
|
52022
|
+
});
|
|
52023
|
+
session.on("save-complete", (data)=>{
|
|
52024
|
+
logger_logger.debug("Event: save-complete", data);
|
|
52025
|
+
});
|
|
52026
|
+
session.on("save-started", (data)=>{
|
|
52027
|
+
logger_logger.debug("Event: save-started", data);
|
|
52123
52028
|
});
|
|
52124
52029
|
try {
|
|
52125
|
-
|
|
52126
|
-
|
|
52127
|
-
|
|
52128
|
-
isCancelled: ()=>settled
|
|
52129
|
-
}).then(async (tokenData)=>login(anvilUrl, {
|
|
52130
|
-
access_token: tokenData.access_token,
|
|
52131
|
-
refresh_token: tokenData.refresh_token,
|
|
52132
|
-
expires_in: tokenData.expires_in,
|
|
52133
|
-
scope: tokenData.scope
|
|
52134
|
-
}))
|
|
52135
|
-
]);
|
|
52136
|
-
settled = true;
|
|
52137
|
-
return result;
|
|
52138
|
-
} finally{
|
|
52139
|
-
settled = true;
|
|
52140
|
-
browserPrompt.close();
|
|
52141
|
-
if (spinnerStarted) logger_logger.progressEnd("login");
|
|
52142
|
-
await pkceFlow.close();
|
|
52143
|
-
}
|
|
52144
|
-
}
|
|
52145
|
-
const CHECKOUT_ERROR_VALUE = "__ERROR__";
|
|
52146
|
-
function isAbortLikeError(error) {
|
|
52147
|
-
if (error instanceof Error && "AbortError" === error.name) return true;
|
|
52148
|
-
if ("object" == typeof error && null !== error) {
|
|
52149
|
-
const maybeType = error.type;
|
|
52150
|
-
const maybeMessage = error.message;
|
|
52151
|
-
if ("network_error" === maybeType && "string" == typeof maybeMessage && maybeMessage.toLowerCase().includes("abort")) return true;
|
|
52030
|
+
await session.initialize();
|
|
52031
|
+
} catch (e) {
|
|
52032
|
+
throw errors_createGitError.commandFailed("initialize", e.message);
|
|
52152
52033
|
}
|
|
52153
|
-
|
|
52154
|
-
|
|
52034
|
+
if (null !== syncStatus) session.syncStatus = syncStatus;
|
|
52035
|
+
session.hasUncommittedChanges = hasUncommittedChanges;
|
|
52036
|
+
return session;
|
|
52155
52037
|
}
|
|
52156
|
-
function
|
|
52157
|
-
|
|
52038
|
+
function decideFallbackUrl(explicitUrl, availableUrls = getAvailableAnvilUrls()) {
|
|
52039
|
+
if (explicitUrl) return {
|
|
52040
|
+
source: "explicit",
|
|
52041
|
+
url: normalizeAnvilUrl(explicitUrl)
|
|
52042
|
+
};
|
|
52043
|
+
if (availableUrls.length > 1) return {
|
|
52044
|
+
source: "available-multiple",
|
|
52045
|
+
urls: availableUrls
|
|
52046
|
+
};
|
|
52047
|
+
if (1 === availableUrls.length) return {
|
|
52048
|
+
source: "available-single",
|
|
52049
|
+
url: availableUrls[0]
|
|
52050
|
+
};
|
|
52051
|
+
const fromConfig = getConfig("anvilUrl");
|
|
52052
|
+
if ("string" == typeof fromConfig && fromConfig.trim()) return {
|
|
52053
|
+
source: "config",
|
|
52054
|
+
url: normalizeAnvilUrl(fromConfig.trim())
|
|
52055
|
+
};
|
|
52056
|
+
return {
|
|
52057
|
+
source: "default",
|
|
52058
|
+
url: isDevMode() ? "http://localhost:3000" : "https://anvil.works"
|
|
52059
|
+
};
|
|
52158
52060
|
}
|
|
52159
|
-
function
|
|
52160
|
-
if (
|
|
52161
|
-
|
|
52162
|
-
|
|
52163
|
-
return
|
|
52061
|
+
function decideUsernameForUrl(accounts) {
|
|
52062
|
+
if (0 === accounts.length) return {
|
|
52063
|
+
source: "none"
|
|
52064
|
+
};
|
|
52065
|
+
if (1 === accounts.length) return {
|
|
52066
|
+
source: "single",
|
|
52067
|
+
username: accounts[0]
|
|
52068
|
+
};
|
|
52069
|
+
return {
|
|
52070
|
+
source: "multiple",
|
|
52071
|
+
usernames: [
|
|
52072
|
+
...accounts
|
|
52073
|
+
]
|
|
52074
|
+
};
|
|
52164
52075
|
}
|
|
52165
|
-
function
|
|
52166
|
-
|
|
52167
|
-
|
|
52168
|
-
return
|
|
52076
|
+
async function resolveUsernameForUrl(anvilUrl, explicitUsername, promptMessage = "Multiple accounts found. Which account owns this app?") {
|
|
52077
|
+
if (explicitUsername) return explicitUsername;
|
|
52078
|
+
const decision = decideUsernameForUrl(auth_getAccountsForUrl(anvilUrl));
|
|
52079
|
+
if ("none" === decision.source) return;
|
|
52080
|
+
if ("single" === decision.source) {
|
|
52081
|
+
logger_logger.verbose(chalk_source.cyan("Auto-selected account: ") + chalk_source.bold(decision.username));
|
|
52082
|
+
return decision.username;
|
|
52083
|
+
}
|
|
52084
|
+
const choices = decision.usernames.map((acct)=>({
|
|
52085
|
+
name: acct,
|
|
52086
|
+
value: acct
|
|
52087
|
+
}));
|
|
52088
|
+
choices.push({
|
|
52089
|
+
name: "Cancel",
|
|
52090
|
+
value: null
|
|
52091
|
+
});
|
|
52092
|
+
return logger_logger.select(promptMessage, choices, decision.usernames[0]);
|
|
52169
52093
|
}
|
|
52170
|
-
function
|
|
52171
|
-
|
|
52094
|
+
async function confirmReverseLookupWithResolvedUser(anvilUrl, username) {
|
|
52095
|
+
const resolvedUsername = await resolveUsernameForUrl(anvilUrl, username, `Multiple accounts found for ${anvilUrl}. Which account should be used for app lookup?`);
|
|
52096
|
+
if (null === resolvedUsername) return null;
|
|
52097
|
+
const shouldContinue = await logger_logger.confirm(`Search ${anvilUrl} ${resolvedUsername ? `for ${resolvedUsername}` : ""} for matching app IDs? This is slower than detecting the app ID from local git remotes.`, true);
|
|
52098
|
+
return {
|
|
52099
|
+
username: resolvedUsername,
|
|
52100
|
+
shouldContinue
|
|
52101
|
+
};
|
|
52172
52102
|
}
|
|
52173
|
-
function
|
|
52174
|
-
const
|
|
52175
|
-
|
|
52176
|
-
|
|
52177
|
-
|
|
52103
|
+
async function resolveUrlForFallback(explicitUrl) {
|
|
52104
|
+
const decision = decideFallbackUrl(explicitUrl);
|
|
52105
|
+
if ("available-multiple" !== decision.source) return decision.url;
|
|
52106
|
+
const choices = decision.urls.map((url)=>({
|
|
52107
|
+
name: url,
|
|
52108
|
+
value: url
|
|
52109
|
+
}));
|
|
52110
|
+
choices.push({
|
|
52111
|
+
name: "Cancel",
|
|
52112
|
+
value: null
|
|
52113
|
+
});
|
|
52114
|
+
const selectedUrl = await logger_logger.select("Multiple logged-in Anvil URLs found. Which one would you like to use?", choices, decision.urls[0]);
|
|
52115
|
+
return selectedUrl;
|
|
52178
52116
|
}
|
|
52179
|
-
|
|
52180
|
-
|
|
52181
|
-
|
|
52117
|
+
const defaultConfigureWatchGitAuthDeps = {
|
|
52118
|
+
configureCredentialHelperForUrl: configureCredentialHelperForUrl,
|
|
52119
|
+
setAppAuthBinding: setAppAuthBinding
|
|
52120
|
+
};
|
|
52121
|
+
const defaultSyncStartDeps = {
|
|
52122
|
+
pushToAnvil,
|
|
52123
|
+
recreateSessionAndValidate,
|
|
52124
|
+
recheckSyncStatus,
|
|
52125
|
+
startWatchingWithEventHandlers
|
|
52126
|
+
};
|
|
52127
|
+
function resolveWatchOpenPath(repoPath) {
|
|
52128
|
+
return external_path_default().resolve(repoPath);
|
|
52182
52129
|
}
|
|
52183
|
-
function
|
|
52184
|
-
const
|
|
52185
|
-
|
|
52130
|
+
async function openWatchPath(targetPath, deps) {
|
|
52131
|
+
const preferredEditor = String(getConfig("preferredEditor") || "").trim();
|
|
52132
|
+
const preferredEditorCommand = preferredEditor ? getPreferredEditorCommand(preferredEditor) : "";
|
|
52133
|
+
await openPathInEditorOrDefault(targetPath, preferredEditorCommand, deps);
|
|
52134
|
+
logger_logger.info(chalk_source.gray(`Opened ${targetPath}`));
|
|
52186
52135
|
}
|
|
52187
|
-
async function
|
|
52188
|
-
|
|
52189
|
-
|
|
52190
|
-
|
|
52191
|
-
|
|
52192
|
-
cleanup();
|
|
52193
|
-
resolve();
|
|
52194
|
-
}, ms);
|
|
52195
|
-
const onAbort = ()=>{
|
|
52196
|
-
cleanup();
|
|
52197
|
-
reject(new Error("aborted"));
|
|
52198
|
-
};
|
|
52199
|
-
const cleanup = ()=>{
|
|
52200
|
-
clearTimeout(timer);
|
|
52201
|
-
signal.removeEventListener("abort", onAbort);
|
|
52202
|
-
};
|
|
52203
|
-
signal.addEventListener("abort", onAbort, {
|
|
52204
|
-
once: true
|
|
52205
|
-
});
|
|
52136
|
+
async function configureWatchGitAuth(options, deps = defaultConfigureWatchGitAuthDeps) {
|
|
52137
|
+
await deps.configureCredentialHelperForUrl(options.repoPath, options.anvilUrl);
|
|
52138
|
+
await deps.setAppAuthBinding(options.repoPath, options.appId, {
|
|
52139
|
+
url: options.anvilUrl,
|
|
52140
|
+
username: options.username
|
|
52206
52141
|
});
|
|
52207
52142
|
}
|
|
52208
|
-
function
|
|
52209
|
-
const
|
|
52210
|
-
|
|
52211
|
-
|
|
52212
|
-
|
|
52213
|
-
|
|
52214
|
-
|
|
52215
|
-
|
|
52216
|
-
|
|
52217
|
-
|
|
52218
|
-
|
|
52219
|
-
|
|
52220
|
-
|
|
52221
|
-
|
|
52222
|
-
|
|
52223
|
-
|
|
52224
|
-
|
|
52225
|
-
|
|
52226
|
-
|
|
52143
|
+
function isStaleRemotePushError(message) {
|
|
52144
|
+
const normalized = message.toLowerCase();
|
|
52145
|
+
return normalized.includes("fetch first") || normalized.includes("non-fast-forward") || normalized.includes("failed to push some refs") || normalized.includes("remote contains work that you do") || normalized.includes("[rejected]");
|
|
52146
|
+
}
|
|
52147
|
+
async function selectAppId(candidates) {
|
|
52148
|
+
if (0 === candidates.length) {
|
|
52149
|
+
logger_logger.warn("Could not auto-detect app ID from repository.");
|
|
52150
|
+
const rl = external_readline_namespaceObject.createInterface({
|
|
52151
|
+
input: process.stdin,
|
|
52152
|
+
output: process.stdout
|
|
52153
|
+
});
|
|
52154
|
+
const manualAppId = await new Promise((resolve)=>{
|
|
52155
|
+
const handleInterrupt = ()=>{
|
|
52156
|
+
rl.close();
|
|
52157
|
+
logger_logger.warn("Operation cancelled by user.");
|
|
52158
|
+
resolve(null);
|
|
52159
|
+
};
|
|
52160
|
+
rl.on("SIGINT", handleInterrupt);
|
|
52161
|
+
rl.question(chalk_source.cyan("Please enter app ID manually (or press Enter to exit): "), (answer)=>{
|
|
52162
|
+
rl.removeListener("SIGINT", handleInterrupt);
|
|
52163
|
+
rl.close();
|
|
52164
|
+
const trimmed = answer.trim();
|
|
52165
|
+
trimmed ? resolve(trimmed) : resolve(null);
|
|
52166
|
+
});
|
|
52167
|
+
});
|
|
52168
|
+
if (manualAppId) return {
|
|
52169
|
+
appId: manualAppId,
|
|
52170
|
+
source: "config",
|
|
52171
|
+
description: "Manual entry"
|
|
52227
52172
|
};
|
|
52228
|
-
|
|
52229
|
-
|
|
52230
|
-
|
|
52231
|
-
|
|
52232
|
-
|
|
52233
|
-
|
|
52234
|
-
|
|
52235
|
-
|
|
52236
|
-
|
|
52237
|
-
|
|
52238
|
-
|
|
52239
|
-
|
|
52240
|
-
|
|
52241
|
-
|
|
52242
|
-
|
|
52243
|
-
|
|
52244
|
-
|
|
52245
|
-
|
|
52246
|
-
|
|
52247
|
-
|
|
52248
|
-
|
|
52249
|
-
|
|
52250
|
-
|
|
52251
|
-
state.exhausted = !response.next_cursor;
|
|
52252
|
-
state.lastError = null;
|
|
52253
|
-
state.updatedAt = Date.now();
|
|
52254
|
-
} catch (e) {
|
|
52255
|
-
if (!signal.aborted && !isAbortLikeError(e)) {
|
|
52256
|
-
const message = errors_getErrorMessage(e);
|
|
52257
|
-
state.lastError = message;
|
|
52258
|
-
state.updatedAt = Date.now();
|
|
52259
|
-
}
|
|
52260
|
-
} finally{
|
|
52261
|
-
state.inFlight = null;
|
|
52173
|
+
return null;
|
|
52174
|
+
}
|
|
52175
|
+
if (1 === candidates.length) {
|
|
52176
|
+
const [candidate] = candidates;
|
|
52177
|
+
logger_logger.success("Auto-selected detected app ID: " + chalk_source.bold(candidate.appId));
|
|
52178
|
+
return candidate;
|
|
52179
|
+
}
|
|
52180
|
+
const choices = candidates.map((candidate, index)=>({
|
|
52181
|
+
name: formatCandidateLabel(candidate),
|
|
52182
|
+
value: index
|
|
52183
|
+
}));
|
|
52184
|
+
choices.push({
|
|
52185
|
+
name: "Cancel",
|
|
52186
|
+
value: null
|
|
52187
|
+
});
|
|
52188
|
+
try {
|
|
52189
|
+
const answer = await logger_logger.prompt([
|
|
52190
|
+
{
|
|
52191
|
+
type: "list",
|
|
52192
|
+
name: "appId",
|
|
52193
|
+
message: "Select an app ID:",
|
|
52194
|
+
choices: choices,
|
|
52195
|
+
pageSize: 10
|
|
52262
52196
|
}
|
|
52263
|
-
|
|
52264
|
-
|
|
52265
|
-
|
|
52266
|
-
|
|
52267
|
-
|
|
52268
|
-
const isStale = Date.now() - state.updatedAt > staleTimeMs;
|
|
52269
|
-
if (0 === state.items.length) {
|
|
52270
|
-
await waitForDebounce(debounceMs, signal);
|
|
52271
|
-
await fetchPage(query, signal, {
|
|
52272
|
-
cursor: void 0,
|
|
52273
|
-
reset: false
|
|
52274
|
-
});
|
|
52275
|
-
} else if (state.lastError) {
|
|
52276
|
-
await waitForDebounce(debounceMs, signal);
|
|
52277
|
-
await fetchPage(query, signal, {
|
|
52278
|
-
cursor: void 0,
|
|
52279
|
-
reset: true
|
|
52280
|
-
});
|
|
52281
|
-
} else if (isStale) {
|
|
52282
|
-
await waitForDebounce(debounceMs, signal);
|
|
52283
|
-
await fetchPage(query, signal, {
|
|
52284
|
-
cursor: void 0,
|
|
52285
|
-
reset: true
|
|
52197
|
+
]);
|
|
52198
|
+
if (null === answer.appId) {
|
|
52199
|
+
const rl = external_readline_namespaceObject.createInterface({
|
|
52200
|
+
input: process.stdin,
|
|
52201
|
+
output: process.stdout
|
|
52286
52202
|
});
|
|
52287
|
-
|
|
52288
|
-
|
|
52289
|
-
|
|
52290
|
-
|
|
52291
|
-
|
|
52292
|
-
|
|
52293
|
-
cursor,
|
|
52294
|
-
reset: false
|
|
52203
|
+
const manualAppId = await new Promise((resolve)=>{
|
|
52204
|
+
rl.question(chalk_source.cyan("Please enter app ID manually (or press Enter to exit): "), (answer)=>{
|
|
52205
|
+
rl.close();
|
|
52206
|
+
const trimmed = answer.trim();
|
|
52207
|
+
trimmed ? resolve(trimmed) : resolve(null);
|
|
52208
|
+
});
|
|
52295
52209
|
});
|
|
52296
|
-
|
|
52210
|
+
if (manualAppId) return {
|
|
52211
|
+
appId: manualAppId,
|
|
52212
|
+
source: "config",
|
|
52213
|
+
description: "Manual entry"
|
|
52214
|
+
};
|
|
52215
|
+
return null;
|
|
52297
52216
|
}
|
|
52298
|
-
|
|
52299
|
-
|
|
52300
|
-
|
|
52301
|
-
|
|
52302
|
-
|
|
52303
|
-
|
|
52304
|
-
const state = getState(query);
|
|
52305
|
-
if (state.lastError) return [
|
|
52306
|
-
{
|
|
52307
|
-
name: `${chalk_source.yellow("Fetch failed")} ${chalk_source.gray(`(${state.lastError})`)}`,
|
|
52308
|
-
value: CHECKOUT_ERROR_VALUE,
|
|
52309
|
-
disabled: true
|
|
52310
|
-
}
|
|
52311
|
-
];
|
|
52312
|
-
const summary = state.nextCursor ? chalk_source.gray(`Loaded ${state.items.length} apps • more available`) : chalk_source.gray(`Loaded ${state.items.length} apps`);
|
|
52313
|
-
const choices = state.items.map((app)=>({
|
|
52314
|
-
name: formatAppPickerLabel(app),
|
|
52315
|
-
value: app.app_id,
|
|
52316
|
-
description: formatLastEdited(app.last_edited)
|
|
52317
|
-
}));
|
|
52318
|
-
choices.unshift({
|
|
52319
|
-
name: summary,
|
|
52320
|
-
value: CHECKOUT_ERROR_VALUE,
|
|
52321
|
-
disabled: true
|
|
52322
|
-
});
|
|
52323
|
-
return choices;
|
|
52324
|
-
}
|
|
52325
|
-
};
|
|
52217
|
+
return candidates[answer.appId];
|
|
52218
|
+
} catch (error) {
|
|
52219
|
+
const errorObj = error;
|
|
52220
|
+
if ("ExitPromptError" === errorObj.name || errorObj.message.includes("User force closed")) logger_logger.warn("Operation cancelled by user.");
|
|
52221
|
+
return null;
|
|
52222
|
+
}
|
|
52326
52223
|
}
|
|
52327
|
-
|
|
52328
|
-
|
|
52329
|
-
|
|
52330
|
-
|
|
52331
|
-
|
|
52332
|
-
|
|
52333
|
-
|
|
52334
|
-
|
|
52335
|
-
|
|
52336
|
-
|
|
52337
|
-
|
|
52338
|
-
|
|
52339
|
-
async function runCustomCheckoutPicker(anvilUrl, username, deps, initialQuery) {
|
|
52340
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) return null;
|
|
52341
|
-
const pageSize = 20;
|
|
52342
|
-
const staleTimeMs = 30000;
|
|
52343
|
-
const debounceMs = 180;
|
|
52344
|
-
const simulatedDelayMs = getCheckoutPickerDelayMs();
|
|
52345
|
-
const paginationDelayMs = getCheckoutPickerPaginationDelayMs();
|
|
52346
|
-
const maxVisibleRows = getCheckoutPickerMaxRows();
|
|
52347
|
-
const cache = new Map();
|
|
52348
|
-
let query = normalizePickerQuery(initialQuery);
|
|
52349
|
-
let selectedIndex = 0;
|
|
52350
|
-
let scrollOffset = 0;
|
|
52351
|
-
let loading = false;
|
|
52352
|
-
let loadingMore = false;
|
|
52353
|
-
let done = false;
|
|
52354
|
-
let pendingTimer = null;
|
|
52355
|
-
let loadingRenderTimer = null;
|
|
52356
|
-
let activeController = null;
|
|
52357
|
-
let resolveResult = null;
|
|
52358
|
-
const ensureState = (key)=>{
|
|
52359
|
-
const existing = cache.get(key);
|
|
52360
|
-
if (existing) return existing;
|
|
52361
|
-
const created = {
|
|
52362
|
-
items: [],
|
|
52363
|
-
nextCursor: null,
|
|
52364
|
-
lastError: null,
|
|
52365
|
-
updatedAt: 0
|
|
52224
|
+
async function pushToAnvil(options) {
|
|
52225
|
+
try {
|
|
52226
|
+
const authToken = await auth_getValidAuthToken(options.anvilUrl, options.username);
|
|
52227
|
+
const pushUrl = getGitPushUrl(options.appId, authToken, options.anvilUrl);
|
|
52228
|
+
const git = new GitService(options.repoPath);
|
|
52229
|
+
const refSpec = `${options.branchName}:${options.branchName}`;
|
|
52230
|
+
logger_logger.progress("push", "Pushing to Anvil...");
|
|
52231
|
+
await git.push(pushUrl, refSpec, options.force ?? false);
|
|
52232
|
+
logger_logger.progressEnd("push", "Pushed to Anvil");
|
|
52233
|
+
return {
|
|
52234
|
+
success: true,
|
|
52235
|
+
staleRemote: false
|
|
52366
52236
|
};
|
|
52367
|
-
|
|
52368
|
-
|
|
52369
|
-
|
|
52370
|
-
|
|
52371
|
-
|
|
52372
|
-
|
|
52373
|
-
|
|
52374
|
-
|
|
52375
|
-
|
|
52376
|
-
|
|
52377
|
-
|
|
52378
|
-
|
|
52379
|
-
|
|
52380
|
-
|
|
52381
|
-
|
|
52382
|
-
|
|
52383
|
-
|
|
52384
|
-
|
|
52385
|
-
|
|
52386
|
-
|
|
52387
|
-
|
|
52388
|
-
|
|
52389
|
-
const activeQuery = query || chalk_source.gray("(all apps)");
|
|
52390
|
-
const spinnerFrame = INLINE_SPINNER_FRAMES[Math.floor(Date.now() / 120) % INLINE_SPINNER_FRAMES.length];
|
|
52391
|
-
const status = loading ? loadingMore ? chalk_source.yellow(`${spinnerFrame} Loading more apps... (${items.length} loaded)`) : chalk_source.yellow(`${spinnerFrame} Loading apps...`) : state.nextCursor ? chalk_source.gray(`Loaded ${items.length} apps • more available`) : chalk_source.gray(`Loaded ${items.length} apps`);
|
|
52392
|
-
process.stdout.write(`${chalk_source.bold("Search apps by name or app ID")}\n`);
|
|
52393
|
-
process.stdout.write(`${chalk_source.cyan("Query:")} ${activeQuery}\n`);
|
|
52394
|
-
process.stdout.write(`${status}\n`);
|
|
52395
|
-
if (state.lastError) process.stdout.write(`${chalk_source.red(`Fetch failed: ${state.lastError}`)}\n`);
|
|
52396
|
-
const height = visibleRows();
|
|
52397
|
-
const start = scrollOffset;
|
|
52398
|
-
const end = Math.min(items.length, start + height);
|
|
52399
|
-
if (0 !== items.length || loading || state.lastError) for(let i = start; i < end; i += 1){
|
|
52400
|
-
const item = items[i];
|
|
52401
|
-
const label = formatAppPickerLabel(item);
|
|
52402
|
-
const line = i === selectedIndex ? `${chalk_source.green("❯")} ${label}` : ` ${label}`;
|
|
52403
|
-
process.stdout.write(`${line}\n`);
|
|
52237
|
+
} catch (e) {
|
|
52238
|
+
const errorMessage = errors_getErrorMessage(e);
|
|
52239
|
+
logger_logger.progressEnd("push", "Push failed");
|
|
52240
|
+
logger_logger.error(`Failed to push: ${errorMessage}`);
|
|
52241
|
+
return {
|
|
52242
|
+
success: false,
|
|
52243
|
+
staleRemote: isStaleRemotePushError(errorMessage),
|
|
52244
|
+
errorMessage
|
|
52245
|
+
};
|
|
52246
|
+
}
|
|
52247
|
+
}
|
|
52248
|
+
async function fetchAndRebaseFromAnvil(options) {
|
|
52249
|
+
const git = new GitService(options.repoPath);
|
|
52250
|
+
const tempRef = `anvil-rebase-temp-${Date.now()}`;
|
|
52251
|
+
let didStash = false;
|
|
52252
|
+
try {
|
|
52253
|
+
const authToken = await auth_getValidAuthToken(options.anvilUrl, options.username);
|
|
52254
|
+
const fetchUrl = getGitFetchUrl(options.appId, authToken, options.anvilUrl);
|
|
52255
|
+
const hasChanges = await git.hasUncommittedChanges();
|
|
52256
|
+
if (hasChanges) {
|
|
52257
|
+
logger_logger.progress("rebase", "Stashing uncommitted changes...");
|
|
52258
|
+
didStash = await git.stash("anvil-sync: stash before rebase");
|
|
52404
52259
|
}
|
|
52405
|
-
|
|
52406
|
-
|
|
52407
|
-
|
|
52408
|
-
|
|
52409
|
-
|
|
52410
|
-
const
|
|
52411
|
-
|
|
52412
|
-
|
|
52413
|
-
|
|
52414
|
-
|
|
52415
|
-
|
|
52416
|
-
|
|
52417
|
-
try {
|
|
52418
|
-
if (simulatedDelayMs > 0) await new Promise((resolve)=>setTimeout(resolve, simulatedDelayMs));
|
|
52419
|
-
if (cursor && paginationDelayMs > 0) await new Promise((resolve)=>setTimeout(resolve, paginationDelayMs));
|
|
52420
|
-
const response = await deps.listAppsForCheckout({
|
|
52421
|
-
anvilUrl,
|
|
52422
|
-
username,
|
|
52423
|
-
limit: pageSize,
|
|
52424
|
-
cursor,
|
|
52425
|
-
q: key || void 0,
|
|
52426
|
-
signal: controller.signal
|
|
52427
|
-
});
|
|
52428
|
-
const merged = cursor ? state.items.concat(response.apps) : response.apps;
|
|
52429
|
-
cache.set(key, {
|
|
52430
|
-
items: merged,
|
|
52431
|
-
nextCursor: response.next_cursor,
|
|
52432
|
-
lastError: null,
|
|
52433
|
-
updatedAt: Date.now()
|
|
52434
|
-
});
|
|
52435
|
-
if (key === query) syncViewport();
|
|
52260
|
+
logger_logger.progressUpdate("rebase", "Fetching from Anvil...");
|
|
52261
|
+
await git.fetch(fetchUrl, `+${options.branchName}:${tempRef}`);
|
|
52262
|
+
logger_logger.progressUpdate("rebase", "Rebasing...");
|
|
52263
|
+
await git.rebase(tempRef);
|
|
52264
|
+
logger_logger.progressUpdate("rebase", "Pushing rebased changes...");
|
|
52265
|
+
const pushUrl = getGitPushUrl(options.appId, authToken, options.anvilUrl);
|
|
52266
|
+
await git.push(pushUrl, `${options.branchName}:${options.branchName}`, true);
|
|
52267
|
+
logger_logger.progressEnd("rebase", "Rebased and pushed to Anvil");
|
|
52268
|
+
await git.deleteRef(`refs/heads/${tempRef}`);
|
|
52269
|
+
if (didStash) try {
|
|
52270
|
+
await git.stashPop();
|
|
52271
|
+
logger_logger.verbose(chalk_source.green("Restored uncommitted changes"));
|
|
52436
52272
|
} catch (e) {
|
|
52437
|
-
|
|
52438
|
-
|
|
52439
|
-
|
|
52440
|
-
|
|
52441
|
-
|
|
52442
|
-
|
|
52443
|
-
|
|
52273
|
+
logger_logger.warn("Uncommitted changes were stashed but could not be restored automatically.");
|
|
52274
|
+
logger_logger.info(chalk_source.gray(" Run: git stash pop"));
|
|
52275
|
+
}
|
|
52276
|
+
return {
|
|
52277
|
+
success: true,
|
|
52278
|
+
conflicted: false
|
|
52279
|
+
};
|
|
52280
|
+
} catch (e) {
|
|
52281
|
+
const msg = errors_getErrorMessage(e);
|
|
52282
|
+
if (msg.includes("rebase") && (msg.includes("conflict") || msg.includes("CONFLICT") || msg.includes("could not apply"))) {
|
|
52283
|
+
try {
|
|
52284
|
+
await git.rebaseAbort();
|
|
52285
|
+
} catch {}
|
|
52286
|
+
await git.deleteRef(`refs/heads/${tempRef}`);
|
|
52287
|
+
if (didStash) try {
|
|
52288
|
+
await git.stashPop();
|
|
52289
|
+
} catch {
|
|
52290
|
+
logger_logger.warn("Your uncommitted changes are in the stash. Run: git stash pop");
|
|
52444
52291
|
}
|
|
52445
|
-
|
|
52446
|
-
|
|
52447
|
-
|
|
52448
|
-
|
|
52449
|
-
|
|
52450
|
-
render();
|
|
52292
|
+
logger_logger.progressEnd("rebase", "Rebase failed - conflicts");
|
|
52293
|
+
return {
|
|
52294
|
+
success: false,
|
|
52295
|
+
conflicted: true
|
|
52296
|
+
};
|
|
52451
52297
|
}
|
|
52452
|
-
|
|
52453
|
-
|
|
52454
|
-
|
|
52455
|
-
|
|
52456
|
-
|
|
52457
|
-
else render();
|
|
52458
|
-
};
|
|
52459
|
-
const maybeLoadMore = async ()=>{
|
|
52460
|
-
const state = currentState();
|
|
52461
|
-
if (!state.nextCursor || loading) return;
|
|
52462
|
-
if (selectedIndex >= Math.max(0, state.items.length - 2)) await fetchPage(query, state.nextCursor || void 0);
|
|
52463
|
-
};
|
|
52464
|
-
const scheduleQueryFetch = ()=>{
|
|
52465
|
-
if (pendingTimer) clearTimeout(pendingTimer);
|
|
52466
|
-
pendingTimer = setTimeout(()=>{
|
|
52467
|
-
pendingTimer = null;
|
|
52468
|
-
ensureFresh(query);
|
|
52469
|
-
}, debounceMs);
|
|
52470
|
-
};
|
|
52471
|
-
const startLoadingAnimation = ()=>{
|
|
52472
|
-
if (loadingRenderTimer) return;
|
|
52473
|
-
loadingRenderTimer = setInterval(()=>{
|
|
52474
|
-
if (!done && loading) render();
|
|
52475
|
-
}, 120);
|
|
52476
|
-
};
|
|
52477
|
-
const stopLoadingAnimation = ()=>{
|
|
52478
|
-
if (!loadingRenderTimer) return;
|
|
52479
|
-
clearInterval(loadingRenderTimer);
|
|
52480
|
-
loadingRenderTimer = null;
|
|
52481
|
-
};
|
|
52482
|
-
const finish = (value)=>{
|
|
52483
|
-
done = true;
|
|
52484
|
-
if (pendingTimer) {
|
|
52485
|
-
clearTimeout(pendingTimer);
|
|
52486
|
-
pendingTimer = null;
|
|
52298
|
+
await git.deleteRef(`refs/heads/${tempRef}`);
|
|
52299
|
+
if (didStash) try {
|
|
52300
|
+
await git.stashPop();
|
|
52301
|
+
} catch {
|
|
52302
|
+
logger_logger.warn("Your uncommitted changes are in the stash. Run: git stash pop");
|
|
52487
52303
|
}
|
|
52488
|
-
|
|
52489
|
-
|
|
52490
|
-
|
|
52491
|
-
|
|
52492
|
-
|
|
52493
|
-
|
|
52494
|
-
if (process.stdin.isTTY && process.stdin.setRawMode) process.stdin.setRawMode(false);
|
|
52495
|
-
process.stdin.pause();
|
|
52496
|
-
clearScreen();
|
|
52497
|
-
if (resolveResult) resolveResult(value);
|
|
52498
|
-
};
|
|
52499
|
-
const onKeypress = (_str, key)=>{
|
|
52500
|
-
if (done) return;
|
|
52501
|
-
if (key.ctrl && "c" === key.name) return void finish(null);
|
|
52502
|
-
if ("escape" === key.name) return void finish(null);
|
|
52503
|
-
if ("return" === key.name) {
|
|
52504
|
-
const state = currentState();
|
|
52505
|
-
const selected = state.items[selectedIndex];
|
|
52506
|
-
finish(selected ? selected.app_id : null);
|
|
52507
|
-
return;
|
|
52508
|
-
}
|
|
52509
|
-
if ("up" === key.name) {
|
|
52510
|
-
if (selectedIndex > 0) {
|
|
52511
|
-
selectedIndex -= 1;
|
|
52512
|
-
syncViewport();
|
|
52513
|
-
render();
|
|
52514
|
-
}
|
|
52515
|
-
return;
|
|
52516
|
-
}
|
|
52517
|
-
if ("down" === key.name) {
|
|
52518
|
-
const maxIndex = Math.max(0, currentState().items.length - 1);
|
|
52519
|
-
if (selectedIndex < maxIndex) {
|
|
52520
|
-
selectedIndex += 1;
|
|
52521
|
-
syncViewport();
|
|
52522
|
-
render();
|
|
52523
|
-
maybeLoadMore();
|
|
52524
|
-
}
|
|
52525
|
-
return;
|
|
52526
|
-
}
|
|
52527
|
-
if ("backspace" === key.name || "delete" === key.name) {
|
|
52528
|
-
if (query.length > 0) {
|
|
52529
|
-
query = query.slice(0, -1);
|
|
52530
|
-
selectedIndex = 0;
|
|
52531
|
-
scrollOffset = 0;
|
|
52532
|
-
render();
|
|
52533
|
-
scheduleQueryFetch();
|
|
52534
|
-
}
|
|
52535
|
-
return;
|
|
52536
|
-
}
|
|
52537
|
-
if (!key.ctrl && !key.meta && key.sequence && 1 === key.sequence.length && key.sequence >= " ") {
|
|
52538
|
-
query += key.sequence;
|
|
52539
|
-
selectedIndex = 0;
|
|
52540
|
-
scrollOffset = 0;
|
|
52541
|
-
render();
|
|
52542
|
-
scheduleQueryFetch();
|
|
52543
|
-
}
|
|
52544
|
-
};
|
|
52545
|
-
logger_logger.pause();
|
|
52546
|
-
try {
|
|
52547
|
-
external_readline_default().emitKeypressEvents(process.stdin);
|
|
52548
|
-
if (process.stdin.isTTY && process.stdin.setRawMode) process.stdin.setRawMode(true);
|
|
52549
|
-
process.stdin.resume();
|
|
52550
|
-
process.stdin.on("keypress", onKeypress);
|
|
52551
|
-
await ensureFresh(query);
|
|
52552
|
-
return await new Promise((resolve)=>{
|
|
52553
|
-
resolveResult = resolve;
|
|
52554
|
-
});
|
|
52555
|
-
} finally{
|
|
52556
|
-
logger_logger.resume();
|
|
52304
|
+
logger_logger.progressEnd("rebase", "Rebase failed");
|
|
52305
|
+
logger_logger.error(`Failed to rebase: ${msg}`);
|
|
52306
|
+
return {
|
|
52307
|
+
success: false,
|
|
52308
|
+
conflicted: false
|
|
52309
|
+
};
|
|
52557
52310
|
}
|
|
52558
52311
|
}
|
|
52559
|
-
async function
|
|
52560
|
-
|
|
52561
|
-
const
|
|
52562
|
-
initialQuery
|
|
52563
|
-
});
|
|
52312
|
+
async function fetchAndHardResetFromAnvil(options) {
|
|
52313
|
+
const git = new GitService(options.repoPath);
|
|
52314
|
+
const tempRef = `anvil-reset-temp-${Date.now()}`;
|
|
52564
52315
|
try {
|
|
52565
|
-
const
|
|
52566
|
-
|
|
52567
|
-
|
|
52568
|
-
|
|
52569
|
-
|
|
52570
|
-
|
|
52571
|
-
|
|
52572
|
-
|
|
52573
|
-
|
|
52574
|
-
navigation: "↑↓ navigate • type to filter",
|
|
52575
|
-
pager: "enter select • ctrl+c cancel"
|
|
52576
|
-
}
|
|
52577
|
-
}
|
|
52578
|
-
]);
|
|
52579
|
-
const selected = String(answer.value || "");
|
|
52580
|
-
if (!selected || selected === CHECKOUT_ERROR_VALUE) return null;
|
|
52581
|
-
return selected;
|
|
52316
|
+
const authToken = await auth_getValidAuthToken(options.anvilUrl, options.username);
|
|
52317
|
+
const fetchUrl = getGitFetchUrl(options.appId, authToken, options.anvilUrl);
|
|
52318
|
+
logger_logger.progress("reset", "Fetching from Anvil...");
|
|
52319
|
+
await git.fetch(fetchUrl, `+${options.branchName}:${tempRef}`);
|
|
52320
|
+
logger_logger.progressUpdate("reset", "Resetting to Anvil's version...");
|
|
52321
|
+
await git.reset(tempRef, "hard");
|
|
52322
|
+
logger_logger.progressEnd("reset", "Reset to Anvil's version");
|
|
52323
|
+
await git.deleteRef(`refs/heads/${tempRef}`);
|
|
52324
|
+
return true;
|
|
52582
52325
|
} catch (e) {
|
|
52583
|
-
|
|
52584
|
-
|
|
52585
|
-
|
|
52326
|
+
await git.deleteRef(`refs/heads/${tempRef}`);
|
|
52327
|
+
logger_logger.progressEnd("reset", "Reset failed");
|
|
52328
|
+
logger_logger.error(`Failed to reset: ${errors_getErrorMessage(e)}`);
|
|
52329
|
+
return false;
|
|
52586
52330
|
}
|
|
52587
52331
|
}
|
|
52588
|
-
|
|
52589
|
-
|
|
52590
|
-
|
|
52591
|
-
|
|
52592
|
-
|
|
52593
|
-
|
|
52594
|
-
|
|
52595
|
-
if (options?.branch) cloneArgs.push("--branch", options.branch);
|
|
52596
|
-
if ("number" == typeof options?.depth) cloneArgs.push("--depth", String(options.depth));
|
|
52597
|
-
if (options?.singleBranch) cloneArgs.push("--single-branch");
|
|
52598
|
-
if (options?.origin) cloneArgs.push("--origin", options.origin);
|
|
52599
|
-
if (options?.quiet) cloneArgs.push("--quiet");
|
|
52600
|
-
if (options?.verbose) cloneArgs.push("--verbose");
|
|
52601
|
-
await esm_default().clone(repoUrl, destinationPath, cloneArgs);
|
|
52602
|
-
},
|
|
52603
|
-
hardenCheckoutGitAuth: hardenCheckoutGitAuth
|
|
52604
|
-
};
|
|
52605
|
-
function isInteractiveSession() {
|
|
52606
|
-
return !!(process.stdin.isTTY && process.stdout.isTTY);
|
|
52332
|
+
function getSyncStateCategory(syncStatus) {
|
|
52333
|
+
if (syncStatus?.branchMissing) return "branch-missing";
|
|
52334
|
+
if (syncStatus?.diverged) return "diverged";
|
|
52335
|
+
if (syncStatus?.ahead && !syncStatus?.behind) return "ahead";
|
|
52336
|
+
if (syncStatus?.behind && !syncStatus?.ahead) return "behind";
|
|
52337
|
+
if (syncStatus?.ahead && syncStatus?.behind) return "diverged";
|
|
52338
|
+
return "in-sync";
|
|
52607
52339
|
}
|
|
52608
|
-
function
|
|
52609
|
-
const trimmed = input.trim();
|
|
52610
|
-
if (!trimmed) throw new Error("Input is required.");
|
|
52611
|
-
if (/^[A-Z0-9]+$/.test(trimmed)) return {
|
|
52612
|
-
appId: trimmed
|
|
52613
|
-
};
|
|
52614
|
-
const asUrl = /^https?:\/\//.test(trimmed) ? trimmed : normalizeAnvilUrl(trimmed);
|
|
52615
|
-
let parsed;
|
|
52340
|
+
async function recheckSyncStatus(previousCategory, previousBranch, options) {
|
|
52616
52341
|
try {
|
|
52617
|
-
|
|
52618
|
-
|
|
52619
|
-
|
|
52620
|
-
|
|
52621
|
-
|
|
52622
|
-
|
|
52623
|
-
|
|
52624
|
-
|
|
52625
|
-
|
|
52626
|
-
|
|
52627
|
-
|
|
52628
|
-
|
|
52629
|
-
|
|
52630
|
-
|
|
52631
|
-
|
|
52632
|
-
}
|
|
52633
|
-
function sanitizeDirectoryName(name) {
|
|
52634
|
-
return name.trim().replace(/\s+/g, "_").replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
52635
|
-
}
|
|
52636
|
-
function formatPathForDisplay(destinationPath) {
|
|
52637
|
-
const relative = external_path_default().relative(process.cwd(), destinationPath);
|
|
52638
|
-
if ("" === relative) return ".";
|
|
52639
|
-
if (relative.startsWith("..")) return destinationPath;
|
|
52640
|
-
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
52641
|
-
}
|
|
52642
|
-
function getDefaultDestinationDirectory(appId, appName) {
|
|
52643
|
-
if (appName) {
|
|
52644
|
-
const sanitized = sanitizeDirectoryName(appName);
|
|
52645
|
-
if (sanitized) return sanitized;
|
|
52646
|
-
}
|
|
52647
|
-
return appId;
|
|
52648
|
-
}
|
|
52649
|
-
async function resolveCheckoutUrl(explicitUrl, parsedUrl) {
|
|
52650
|
-
if (explicitUrl) {
|
|
52651
|
-
logger_logger.verbose(chalk_source.cyan("Using Anvil URL from --url: ") + chalk_source.bold(normalizeAnvilUrl(explicitUrl)));
|
|
52652
|
-
return normalizeAnvilUrl(explicitUrl);
|
|
52653
|
-
}
|
|
52654
|
-
if (parsedUrl) {
|
|
52655
|
-
logger_logger.verbose(chalk_source.cyan("Using Anvil URL from checkout input: ") + chalk_source.bold(normalizeAnvilUrl(parsedUrl)));
|
|
52656
|
-
return normalizeAnvilUrl(parsedUrl);
|
|
52657
|
-
}
|
|
52658
|
-
const decision = decideFallbackUrl(void 0);
|
|
52659
|
-
if ("available-multiple" !== decision.source) {
|
|
52660
|
-
if (decision.url) logger_logger.verbose(chalk_source.cyan("Using configured Anvil URL: ") + chalk_source.bold(decision.url));
|
|
52661
|
-
return decision.url;
|
|
52342
|
+
logger_logger.progress("verify", "Verifying repository status...");
|
|
52343
|
+
const freshSession = await api_watch(options.repoPath, options.appId, options.anvilUrl, options.stagedOnly, options.username);
|
|
52344
|
+
logger_logger.progressEnd("verify");
|
|
52345
|
+
const currentCategory = getSyncStateCategory(freshSession.syncStatus);
|
|
52346
|
+
const currentBranch = freshSession.getBranchName() || "master";
|
|
52347
|
+
if (currentCategory !== previousCategory || currentBranch !== previousBranch) {
|
|
52348
|
+
logger_logger.warn("Repository status has changed. Re-evaluating...");
|
|
52349
|
+
return freshSession;
|
|
52350
|
+
}
|
|
52351
|
+
freshSession.cleanup();
|
|
52352
|
+
return null;
|
|
52353
|
+
} catch (e) {
|
|
52354
|
+
logger_logger.progressEnd("verify");
|
|
52355
|
+
logger_logger.verbose(chalk_source.gray(`Could not re-verify status: ${errors_getErrorMessage(e)}`));
|
|
52356
|
+
return null;
|
|
52662
52357
|
}
|
|
52663
|
-
const choices = decision.urls.map((url)=>({
|
|
52664
|
-
name: url,
|
|
52665
|
-
value: url
|
|
52666
|
-
}));
|
|
52667
|
-
choices.push({
|
|
52668
|
-
name: "Cancel",
|
|
52669
|
-
value: null
|
|
52670
|
-
});
|
|
52671
|
-
return logger_logger.select("Multiple logged-in Anvil URLs found. Which one would you like to use?", choices, decision.urls[0]);
|
|
52672
52358
|
}
|
|
52673
|
-
async function
|
|
52674
|
-
|
|
52675
|
-
let current = resolved;
|
|
52676
|
-
while(!external_fs_.existsSync(current)){
|
|
52677
|
-
const parent = external_path_default().dirname(current);
|
|
52678
|
-
if (parent === current) return false;
|
|
52679
|
-
current = parent;
|
|
52680
|
-
}
|
|
52359
|
+
async function recreateSessionAndValidate(options) {
|
|
52360
|
+
logger_logger.progress("init", "Restarting watch session...");
|
|
52681
52361
|
try {
|
|
52682
|
-
const
|
|
52683
|
-
|
|
52684
|
-
|
|
52685
|
-
|
|
52686
|
-
|
|
52687
|
-
|
|
52362
|
+
const newSession = await api_watch(options.repoPath, options.appId, options.anvilUrl, options.stagedOnly, options.username);
|
|
52363
|
+
logger_logger.progressEnd("init");
|
|
52364
|
+
return await checkSyncStatusAndStart(newSession, options);
|
|
52365
|
+
} catch (e) {
|
|
52366
|
+
logger_logger.progressEnd("init", "Failed");
|
|
52367
|
+
logger_logger.error(`Failed to restart: ${errors_getErrorMessage(e)}`);
|
|
52688
52368
|
return false;
|
|
52689
52369
|
}
|
|
52690
52370
|
}
|
|
52691
|
-
async function
|
|
52692
|
-
|
|
52693
|
-
const
|
|
52694
|
-
|
|
52695
|
-
const
|
|
52696
|
-
|
|
52697
|
-
|
|
52698
|
-
|
|
52699
|
-
|
|
52700
|
-
|
|
52701
|
-
|
|
52702
|
-
|
|
52703
|
-
|
|
52704
|
-
|
|
52705
|
-
|
|
52706
|
-
|
|
52707
|
-
|
|
52708
|
-
|
|
52709
|
-
|
|
52710
|
-
|
|
52711
|
-
|
|
52712
|
-
|
|
52713
|
-
|
|
52714
|
-
|
|
52715
|
-
|
|
52716
|
-
|
|
52371
|
+
async function checkSyncStatusAndStart(session, options, deps = defaultSyncStartDeps) {
|
|
52372
|
+
const syncStatus = session.syncStatus;
|
|
52373
|
+
const branchName = session.getBranchName() || "master";
|
|
52374
|
+
const hasUncommitted = session.hasUncommittedChanges;
|
|
52375
|
+
const stateCategory = getSyncStateCategory(syncStatus);
|
|
52376
|
+
if (syncStatus?.branchMissing) {
|
|
52377
|
+
logger_logger.warn(`Branch '${branchName}' doesn't exist on Anvil yet.`);
|
|
52378
|
+
if (hasUncommitted) logger_logger.info(chalk_source.gray(" You also have uncommitted changes."));
|
|
52379
|
+
let shouldPush = options.autoMode;
|
|
52380
|
+
if (options.autoMode) logger_logger.info(chalk_source.cyan("→ Auto-pushing new branch to Anvil..."));
|
|
52381
|
+
else {
|
|
52382
|
+
shouldPush = await logger_logger.confirm("Would you like to push this branch to Anvil?", true);
|
|
52383
|
+
if (shouldPush) {
|
|
52384
|
+
const changed = await deps.recheckSyncStatus(stateCategory, branchName, options);
|
|
52385
|
+
if (changed) return await checkSyncStatusAndStart(changed, options, deps);
|
|
52386
|
+
}
|
|
52387
|
+
}
|
|
52388
|
+
if (!shouldPush) {
|
|
52389
|
+
logger_logger.warn("Watch cancelled. Push your branch to Anvil, then try again.");
|
|
52390
|
+
session.cleanup();
|
|
52391
|
+
return false;
|
|
52392
|
+
}
|
|
52393
|
+
session.cleanup();
|
|
52394
|
+
const pushed = await deps.pushToAnvil({
|
|
52395
|
+
repoPath: options.repoPath,
|
|
52396
|
+
appId: options.appId,
|
|
52397
|
+
anvilUrl: options.anvilUrl,
|
|
52398
|
+
branchName,
|
|
52399
|
+
username: options.username
|
|
52400
|
+
});
|
|
52401
|
+
if (!pushed.success) {
|
|
52402
|
+
if (pushed.staleRemote) {
|
|
52403
|
+
logger_logger.warn("Branch appeared on Anvil while pushing. Re-checking sync status...");
|
|
52404
|
+
return await deps.recreateSessionAndValidate(options);
|
|
52405
|
+
}
|
|
52406
|
+
return false;
|
|
52407
|
+
}
|
|
52408
|
+
return await deps.recreateSessionAndValidate(options);
|
|
52717
52409
|
}
|
|
52718
|
-
|
|
52719
|
-
|
|
52720
|
-
|
|
52721
|
-
|
|
52722
|
-
|
|
52723
|
-
|
|
52724
|
-
|
|
52725
|
-
|
|
52410
|
+
if (syncStatus?.behind || syncStatus?.ahead) if (syncStatus.ahead && !syncStatus.behind) {
|
|
52411
|
+
logger_logger.warn(`Your local repository is ${syncStatus.ahead} commit(s) ahead of Anvil.`);
|
|
52412
|
+
if (hasUncommitted) logger_logger.info(chalk_source.gray(" You also have uncommitted changes."));
|
|
52413
|
+
let shouldPush = options.autoMode;
|
|
52414
|
+
if (options.autoMode) logger_logger.info(chalk_source.cyan("→ Auto-pushing to Anvil..."));
|
|
52415
|
+
else {
|
|
52416
|
+
const action = await logger_logger.select("What would you like to do?", [
|
|
52417
|
+
{
|
|
52418
|
+
name: "Push your changes to Anvil (recommended)",
|
|
52419
|
+
value: "push"
|
|
52420
|
+
},
|
|
52421
|
+
{
|
|
52422
|
+
name: "Exit and handle manually",
|
|
52423
|
+
value: "exit"
|
|
52424
|
+
}
|
|
52425
|
+
], "push");
|
|
52426
|
+
shouldPush = "push" === action;
|
|
52427
|
+
if (shouldPush) {
|
|
52428
|
+
const changed = await deps.recheckSyncStatus(stateCategory, branchName, options);
|
|
52429
|
+
if (changed) return await checkSyncStatusAndStart(changed, options, deps);
|
|
52430
|
+
}
|
|
52431
|
+
}
|
|
52432
|
+
if (!shouldPush) {
|
|
52433
|
+
logger_logger.warn("Watch cancelled.");
|
|
52434
|
+
session.cleanup();
|
|
52435
|
+
return false;
|
|
52436
|
+
}
|
|
52437
|
+
session.cleanup();
|
|
52438
|
+
const pushed = await deps.pushToAnvil({
|
|
52439
|
+
repoPath: options.repoPath,
|
|
52440
|
+
appId: options.appId,
|
|
52441
|
+
anvilUrl: options.anvilUrl,
|
|
52442
|
+
branchName,
|
|
52443
|
+
username: options.username
|
|
52444
|
+
});
|
|
52445
|
+
if (!pushed.success) return false;
|
|
52446
|
+
return await deps.recreateSessionAndValidate(options);
|
|
52447
|
+
} else if (syncStatus.diverged) {
|
|
52448
|
+
logger_logger.warn("Your local repository has diverged from Anvil.");
|
|
52449
|
+
logger_logger.info(chalk_source.gray(` You are ${syncStatus.ahead} commit(s) ahead and ${syncStatus.behind} commit(s) behind.`));
|
|
52450
|
+
if (hasUncommitted) logger_logger.info(chalk_source.gray(" You also have uncommitted changes."));
|
|
52451
|
+
session.cleanup();
|
|
52452
|
+
if (options.autoMode) {
|
|
52453
|
+
logger_logger.info(chalk_source.cyan("→ Auto-rebasing onto Anvil's version..."));
|
|
52454
|
+
const result = await fetchAndRebaseFromAnvil({
|
|
52455
|
+
repoPath: options.repoPath,
|
|
52456
|
+
appId: options.appId,
|
|
52457
|
+
anvilUrl: options.anvilUrl,
|
|
52458
|
+
branchName,
|
|
52459
|
+
username: options.username
|
|
52460
|
+
});
|
|
52461
|
+
if (result.conflicted) {
|
|
52462
|
+
logger_logger.error("Rebase failed due to conflicts. Please resolve manually:");
|
|
52463
|
+
logger_logger.info(chalk_source.gray(" 1. Run: git fetch anvil && git rebase anvil/" + branchName));
|
|
52464
|
+
logger_logger.info(chalk_source.gray(" 2. Resolve conflicts, then: git rebase --continue"));
|
|
52465
|
+
logger_logger.info(chalk_source.gray(" 3. Push: git push anvil " + branchName));
|
|
52466
|
+
return false;
|
|
52467
|
+
}
|
|
52468
|
+
if (!result.success) return false;
|
|
52469
|
+
return await deps.recreateSessionAndValidate(options);
|
|
52470
|
+
}
|
|
52471
|
+
const action = await logger_logger.select("How would you like to resolve this?", [
|
|
52472
|
+
{
|
|
52473
|
+
name: "Pull from Anvil and rebase your changes on top (recommended)",
|
|
52474
|
+
value: "rebase"
|
|
52475
|
+
},
|
|
52476
|
+
{
|
|
52477
|
+
name: "Discard your local commits and use Anvil's version",
|
|
52478
|
+
value: "reset"
|
|
52479
|
+
},
|
|
52480
|
+
{
|
|
52481
|
+
name: "Push your version to Anvil (overwrites remote)",
|
|
52482
|
+
value: "push"
|
|
52483
|
+
},
|
|
52484
|
+
{
|
|
52485
|
+
name: "Exit and handle manually",
|
|
52486
|
+
value: "exit"
|
|
52487
|
+
}
|
|
52488
|
+
], "rebase");
|
|
52489
|
+
if ("exit" === action) {
|
|
52490
|
+
logger_logger.warn("Watch cancelled.");
|
|
52491
|
+
return false;
|
|
52492
|
+
}
|
|
52493
|
+
const changed = await deps.recheckSyncStatus(stateCategory, branchName, options);
|
|
52494
|
+
if (changed) return await checkSyncStatusAndStart(changed, options, deps);
|
|
52495
|
+
if ("rebase" === action) {
|
|
52496
|
+
const result = await fetchAndRebaseFromAnvil({
|
|
52497
|
+
repoPath: options.repoPath,
|
|
52498
|
+
appId: options.appId,
|
|
52499
|
+
anvilUrl: options.anvilUrl,
|
|
52500
|
+
branchName,
|
|
52501
|
+
username: options.username
|
|
52502
|
+
});
|
|
52503
|
+
if (result.conflicted) {
|
|
52504
|
+
logger_logger.error("Rebase failed due to conflicts. Please resolve manually:");
|
|
52505
|
+
logger_logger.info(chalk_source.gray(" 1. Run: git fetch anvil && git rebase anvil/" + branchName));
|
|
52506
|
+
logger_logger.info(chalk_source.gray(" 2. Resolve conflicts, then: git rebase --continue"));
|
|
52507
|
+
logger_logger.info(chalk_source.gray(" 3. Push: git push anvil " + branchName));
|
|
52508
|
+
return false;
|
|
52509
|
+
}
|
|
52510
|
+
if (!result.success) return false;
|
|
52511
|
+
return await deps.recreateSessionAndValidate(options);
|
|
52512
|
+
}
|
|
52513
|
+
if ("reset" === action) {
|
|
52514
|
+
const resetWarning = hasUncommitted ? `This will discard ${syncStatus.ahead} local commit(s) and your uncommitted changes. Are you sure?` : `This will discard ${syncStatus.ahead} local commit(s). Are you sure?`;
|
|
52515
|
+
const confirmed = await logger_logger.confirm(resetWarning, false);
|
|
52516
|
+
if (!confirmed) {
|
|
52517
|
+
logger_logger.warn("Watch cancelled.");
|
|
52518
|
+
return false;
|
|
52519
|
+
}
|
|
52520
|
+
const reset = await fetchAndHardResetFromAnvil({
|
|
52521
|
+
repoPath: options.repoPath,
|
|
52522
|
+
appId: options.appId,
|
|
52523
|
+
anvilUrl: options.anvilUrl,
|
|
52524
|
+
branchName,
|
|
52525
|
+
username: options.username
|
|
52526
|
+
});
|
|
52527
|
+
if (!reset) return false;
|
|
52528
|
+
return await deps.recreateSessionAndValidate(options);
|
|
52529
|
+
}
|
|
52530
|
+
if ("push" === action) {
|
|
52531
|
+
const confirmed = await logger_logger.confirm("This will overwrite Anvil's version. Are you sure?", false);
|
|
52532
|
+
if (!confirmed) {
|
|
52533
|
+
logger_logger.warn("Watch cancelled.");
|
|
52534
|
+
return false;
|
|
52535
|
+
}
|
|
52536
|
+
const pushed = await deps.pushToAnvil({
|
|
52537
|
+
repoPath: options.repoPath,
|
|
52538
|
+
appId: options.appId,
|
|
52539
|
+
anvilUrl: options.anvilUrl,
|
|
52540
|
+
branchName,
|
|
52541
|
+
username: options.username,
|
|
52542
|
+
force: true
|
|
52543
|
+
});
|
|
52544
|
+
if (!pushed.success) return false;
|
|
52545
|
+
return await deps.recreateSessionAndValidate(options);
|
|
52546
|
+
}
|
|
52547
|
+
} else {
|
|
52548
|
+
logger_logger.warn(`Your local repository is ${syncStatus.behind} commit(s) behind Anvil.`);
|
|
52549
|
+
logger_logger.verbose(chalk_source.gray(" You need to sync to the latest version before watching."));
|
|
52550
|
+
if (hasUncommitted) logger_logger.info(chalk_source.gray(" You also have uncommitted changes."));
|
|
52551
|
+
let shouldSync = options.autoMode;
|
|
52552
|
+
if (options.autoMode) logger_logger.info(chalk_source.cyan("→ Auto-syncing to latest version..."));
|
|
52553
|
+
else {
|
|
52554
|
+
shouldSync = await logger_logger.confirm("Would you like to sync to the latest version from Anvil now?", true);
|
|
52555
|
+
if (shouldSync) {
|
|
52556
|
+
const changed = await deps.recheckSyncStatus(stateCategory, branchName, options);
|
|
52557
|
+
if (changed) return await checkSyncStatusAndStart(changed, options, deps);
|
|
52558
|
+
}
|
|
52559
|
+
}
|
|
52560
|
+
if (!shouldSync) {
|
|
52561
|
+
logger_logger.warn("Watch cancelled. Please sync manually before watching.");
|
|
52562
|
+
session.cleanup();
|
|
52563
|
+
return false;
|
|
52564
|
+
}
|
|
52565
|
+
session.cleanup();
|
|
52566
|
+
let authToken;
|
|
52567
|
+
try {
|
|
52568
|
+
authToken = await auth_getValidAuthToken(options.anvilUrl, options.username);
|
|
52569
|
+
} catch (e) {
|
|
52570
|
+
logger_logger.error("Not logged in. Please log in first.");
|
|
52571
|
+
process.exit(1);
|
|
52572
|
+
}
|
|
52573
|
+
logger_logger.progress("sync-latest", "Syncing to latest version from Anvil...");
|
|
52574
|
+
try {
|
|
52575
|
+
const newCommitId = await syncToLatest(options.repoPath, options.appId, options.anvilUrl, authToken, branchName, options.username);
|
|
52576
|
+
logger_logger.progressEnd("sync-latest", `Synced to latest version: ${newCommitId.substring(0, 8)}`);
|
|
52577
|
+
} catch (e) {
|
|
52578
|
+
logger_logger.progressEnd("sync-latest", "Failed");
|
|
52579
|
+
logger_logger.error(`Failed to sync: ${errors_getErrorMessage(e)}`);
|
|
52580
|
+
process.exit(1);
|
|
52581
|
+
}
|
|
52582
|
+
return await deps.recreateSessionAndValidate(options);
|
|
52726
52583
|
}
|
|
52727
|
-
|
|
52728
|
-
|
|
52729
|
-
const choices = accounts.map((acct)=>({
|
|
52730
|
-
name: acct,
|
|
52731
|
-
value: acct
|
|
52732
|
-
}));
|
|
52733
|
-
choices.push({
|
|
52734
|
-
name: "Cancel",
|
|
52735
|
-
value: null
|
|
52736
|
-
});
|
|
52737
|
-
return logger_logger.select(`Multiple accounts found for ${anvilUrl}. Which account should be used for checkout?`, choices, accounts[0]);
|
|
52584
|
+
await deps.startWatchingWithEventHandlers(session, options);
|
|
52585
|
+
return true;
|
|
52738
52586
|
}
|
|
52739
|
-
async function
|
|
52740
|
-
|
|
52741
|
-
|
|
52742
|
-
|
|
52743
|
-
|
|
52744
|
-
|
|
52745
|
-
|
|
52746
|
-
|
|
52747
|
-
|
|
52748
|
-
|
|
52749
|
-
|
|
52750
|
-
|
|
52751
|
-
|
|
52752
|
-
|
|
52753
|
-
|
|
52754
|
-
|
|
52755
|
-
|
|
52756
|
-
|
|
52757
|
-
|
|
52758
|
-
|
|
52759
|
-
|
|
52760
|
-
|
|
52761
|
-
|
|
52762
|
-
|
|
52763
|
-
|
|
52764
|
-
|
|
52765
|
-
|
|
52766
|
-
|
|
52767
|
-
|
|
52768
|
-
|
|
52769
|
-
|
|
52770
|
-
|
|
52771
|
-
|
|
52772
|
-
|
|
52773
|
-
|
|
52774
|
-
|
|
52775
|
-
|
|
52776
|
-
|
|
52777
|
-
|
|
52778
|
-
|
|
52779
|
-
|
|
52780
|
-
|
|
52781
|
-
|
|
52782
|
-
|
|
52783
|
-
|
|
52784
|
-
|
|
52785
|
-
|
|
52786
|
-
|
|
52787
|
-
|
|
52788
|
-
|
|
52789
|
-
logger_logger.info(chalk_source.gray(` Destination directory: ${destinationDisplay}`));
|
|
52790
|
-
await deps.clone(cloneUrl, destinationPath, {
|
|
52791
|
-
branch: options.branch,
|
|
52792
|
-
depth: options.depth,
|
|
52793
|
-
singleBranch: options.singleBranch,
|
|
52794
|
-
origin: options.origin,
|
|
52795
|
-
quiet: options.quiet,
|
|
52796
|
-
verbose: options.verbose
|
|
52587
|
+
async function startWatchingWithEventHandlers(session, options) {
|
|
52588
|
+
const { autoMode, repoPath, appId, anvilUrl, stagedOnly } = options;
|
|
52589
|
+
session.on("branch-changed", async ({ oldBranch, newBranch })=>{
|
|
52590
|
+
try {
|
|
52591
|
+
session.cleanup();
|
|
52592
|
+
logger_logger.warn(`Branch changed: ${chalk_source.bold(oldBranch)} → ${chalk_source.bold(newBranch)}`);
|
|
52593
|
+
logger_logger.info(chalk_source.cyan("→ Restarting watch on new branch..."));
|
|
52594
|
+
const git = esm_default(repoPath);
|
|
52595
|
+
const actualBranch = (await git.revparse([
|
|
52596
|
+
"--abbrev-ref",
|
|
52597
|
+
"HEAD"
|
|
52598
|
+
])).trim();
|
|
52599
|
+
if (actualBranch !== newBranch) logger_logger.verbose(chalk_source.yellow(`Branch changed again to ${chalk_source.bold(actualBranch)}, using that instead`));
|
|
52600
|
+
logger_logger.progress("init", "Initializing watch session...");
|
|
52601
|
+
try {
|
|
52602
|
+
const newSession = await api_watch(repoPath, appId, anvilUrl, stagedOnly, options.username);
|
|
52603
|
+
logger_logger.progressEnd("init");
|
|
52604
|
+
const started = await checkSyncStatusAndStart(newSession, options);
|
|
52605
|
+
if (!started) process.exit(0);
|
|
52606
|
+
} catch (e) {
|
|
52607
|
+
logger_logger.progressEnd("init", "Failed");
|
|
52608
|
+
logger_logger.error("Failed to restart watch: " + errors_getErrorMessage(e));
|
|
52609
|
+
process.exit(1);
|
|
52610
|
+
}
|
|
52611
|
+
} catch (error) {
|
|
52612
|
+
logger_logger.error(`Error handling branch change: ${error.message}`);
|
|
52613
|
+
process.exit(1);
|
|
52614
|
+
}
|
|
52615
|
+
});
|
|
52616
|
+
session.on("validation-failed", ({ reason, currentBranch })=>{
|
|
52617
|
+
logger_logger.error(chalk_source.red(`Validation failed: ${reason}`));
|
|
52618
|
+
logger_logger.verbose(chalk_source.yellow(` Current branch: ${currentBranch}`));
|
|
52619
|
+
logger_logger.verbose(chalk_source.yellow(" Please restart anvil to re-validate the branch."));
|
|
52620
|
+
session.cleanup();
|
|
52621
|
+
process.exit(1);
|
|
52622
|
+
});
|
|
52623
|
+
session.on("max-retries-exceeded", async ()=>{
|
|
52624
|
+
if (autoMode) {
|
|
52625
|
+
logger_logger.info(chalk_source.cyan("→ Auto-restarting watch session..."));
|
|
52626
|
+
session.cleanup();
|
|
52627
|
+
const restarted = await recreateSessionAndValidate(options);
|
|
52628
|
+
if (!restarted) process.exit(1);
|
|
52629
|
+
} else {
|
|
52630
|
+
const shouldRestart = await logger_logger.confirm("Would you like to restart the watch session?", true);
|
|
52631
|
+
if (shouldRestart) {
|
|
52632
|
+
session.cleanup();
|
|
52633
|
+
const restarted = await recreateSessionAndValidate(options);
|
|
52634
|
+
if (!restarted) process.exit(1);
|
|
52635
|
+
}
|
|
52636
|
+
}
|
|
52797
52637
|
});
|
|
52798
|
-
|
|
52638
|
+
if (session.hasUncommittedChanges) {
|
|
52639
|
+
if (!autoMode) {
|
|
52640
|
+
const shouldContinue = await logger_logger.confirm("Continue? Your uncommitted changes will be synced to Anvil.", true);
|
|
52641
|
+
if (!shouldContinue) {
|
|
52642
|
+
logger_logger.warn("Watch cancelled. Commit, stash, or discard your changes, then try again.");
|
|
52643
|
+
session.cleanup();
|
|
52644
|
+
process.exit(0);
|
|
52645
|
+
}
|
|
52646
|
+
}
|
|
52647
|
+
logger_logger.progress("save", "Saving uncommitted changes to Anvil...");
|
|
52648
|
+
try {
|
|
52649
|
+
await session.forceSave();
|
|
52650
|
+
logger_logger.progressEnd("save");
|
|
52651
|
+
} catch (e) {
|
|
52652
|
+
logger_logger.progressEnd("save", "Failed");
|
|
52653
|
+
logger_logger.error(`Failed to save: ${errors_getErrorMessage(e)}`);
|
|
52654
|
+
}
|
|
52655
|
+
}
|
|
52656
|
+
await session.startWatching();
|
|
52657
|
+
}
|
|
52658
|
+
async function handleWatchCommand(options) {
|
|
52659
|
+
const invoked = process.argv[2];
|
|
52660
|
+
if ("sync" === invoked) {
|
|
52661
|
+
logger_logger.error("'sync' has been renamed to 'watch'. Please use 'anvil watch' instead.");
|
|
52662
|
+
process.exit(1);
|
|
52663
|
+
}
|
|
52664
|
+
const { path: repoPath = process.cwd(), appid: explicitAppId, useFirst = false, stagedOnly = false, autoMode = false, url: explicitUrl, user: explicitUsername, open: openAfterValidation = false } = options;
|
|
52799
52665
|
try {
|
|
52800
|
-
await
|
|
52801
|
-
|
|
52802
|
-
|
|
52666
|
+
const validationResult = await validateAnvilApp(repoPath);
|
|
52667
|
+
if (validationResult.appName) logger_logger.info(chalk_source.green("Anvil app: ") + chalk_source.bold(validationResult.appName));
|
|
52668
|
+
const detectedFromAllRemotes = await detectAppIdsFromAllRemotes(repoPath);
|
|
52669
|
+
let filteredCandidates = filterCandidates(detectedFromAllRemotes, explicitUrl, explicitUsername);
|
|
52670
|
+
logger_logger.verbose(chalk_source.cyan(`Detected ${detectedFromAllRemotes.length} app ID(s) from git remotes`));
|
|
52671
|
+
if (filteredCandidates.length !== detectedFromAllRemotes.length) logger_logger.verbose(chalk_source.cyan(`After filtering: ${filteredCandidates.length} candidate(s)`));
|
|
52672
|
+
let finalAppId;
|
|
52673
|
+
let anvilUrl;
|
|
52674
|
+
let username = explicitUsername;
|
|
52675
|
+
let fallbackUrl;
|
|
52676
|
+
let shouldPersistUsernameBinding = false;
|
|
52677
|
+
if (explicitAppId) {
|
|
52678
|
+
finalAppId = explicitAppId;
|
|
52679
|
+
const binding = await getAppAuthBinding(repoPath, explicitAppId);
|
|
52680
|
+
if (binding.url && !explicitUrl) {
|
|
52681
|
+
anvilUrl = normalizeAnvilUrl(binding.url);
|
|
52682
|
+
logger_logger.verbose(chalk_source.cyan("Resolved URL from binding for app ID: ") + chalk_source.bold(anvilUrl));
|
|
52683
|
+
}
|
|
52684
|
+
if (binding.username && !explicitUsername) {
|
|
52685
|
+
username = binding.username;
|
|
52686
|
+
logger_logger.verbose(chalk_source.cyan("Resolved username from binding for app ID: ") + chalk_source.bold(username));
|
|
52687
|
+
}
|
|
52688
|
+
const remoteInfo = lookupRemoteInfoForAppId(explicitAppId, detectedFromAllRemotes);
|
|
52689
|
+
if (remoteInfo.detectedUrl && !explicitUrl && !anvilUrl) {
|
|
52690
|
+
anvilUrl = normalizeAnvilUrl(remoteInfo.detectedUrl);
|
|
52691
|
+
logger_logger.verbose(chalk_source.cyan("Resolved URL from remote for app ID: ") + chalk_source.bold(anvilUrl));
|
|
52692
|
+
}
|
|
52693
|
+
if (remoteInfo.detectedUsername && !explicitUsername && !username) {
|
|
52694
|
+
username = remoteInfo.detectedUsername;
|
|
52695
|
+
logger_logger.verbose(chalk_source.cyan("Resolved username from remote for app ID: ") + chalk_source.bold(username));
|
|
52696
|
+
}
|
|
52697
|
+
} else {
|
|
52698
|
+
logger_logger.verbose(chalk_source.cyan("No app ID provided, attempting auto-detection..."));
|
|
52699
|
+
if (0 === filteredCandidates.length) {
|
|
52700
|
+
logger_logger.verbose(chalk_source.gray("No app IDs found in git remotes."));
|
|
52701
|
+
const resolvedFallbackUrl = await resolveUrlForFallback(explicitUrl);
|
|
52702
|
+
if (null === resolvedFallbackUrl) {
|
|
52703
|
+
logger_logger.warn("Operation cancelled.");
|
|
52704
|
+
process.exit(0);
|
|
52705
|
+
}
|
|
52706
|
+
fallbackUrl = normalizeAnvilUrl(resolvedFallbackUrl);
|
|
52707
|
+
const lookupDecision = await confirmReverseLookupWithResolvedUser(fallbackUrl, username);
|
|
52708
|
+
if (null === lookupDecision) {
|
|
52709
|
+
logger_logger.warn("Operation cancelled.");
|
|
52710
|
+
process.exit(0);
|
|
52711
|
+
}
|
|
52712
|
+
username = lookupDecision.username;
|
|
52713
|
+
if (lookupDecision.shouldContinue) {
|
|
52714
|
+
logger_logger.progress("detect", `Searching ${fallbackUrl} ${username ? `for ${username}` : ""} for matching app IDs...`);
|
|
52715
|
+
const reverseLookupCandidates = await detectAppIdsByCommitLookup(repoPath, {
|
|
52716
|
+
anvilUrl: fallbackUrl,
|
|
52717
|
+
username,
|
|
52718
|
+
includeRemotes: false
|
|
52719
|
+
});
|
|
52720
|
+
logger_logger.progressEnd("detect");
|
|
52721
|
+
for (const c of reverseLookupCandidates)filteredCandidates.push({
|
|
52722
|
+
...c,
|
|
52723
|
+
detectedUrl: fallbackUrl
|
|
52724
|
+
});
|
|
52725
|
+
}
|
|
52726
|
+
}
|
|
52727
|
+
if (filteredCandidates.length > 0) {
|
|
52728
|
+
for (const c of filteredCandidates)logger_logger.verbose(chalk_source.gray(` Found: ${formatCandidateLabel(c)}`));
|
|
52729
|
+
if (filteredCandidates.length > 1) logger_logger.verbose(chalk_source.yellow(`Found ${filteredCandidates.length} potential app IDs`));
|
|
52730
|
+
}
|
|
52731
|
+
if (useFirst && filteredCandidates.length > 0) {
|
|
52732
|
+
const selected = filteredCandidates[0];
|
|
52733
|
+
finalAppId = selected.appId;
|
|
52734
|
+
if (selected.detectedUrl) anvilUrl = normalizeAnvilUrl(selected.detectedUrl);
|
|
52735
|
+
if (selected.detectedUsername && !username) username = selected.detectedUsername;
|
|
52736
|
+
logger_logger.success("Auto-selected first app ID: " + chalk_source.bold(finalAppId));
|
|
52737
|
+
} else {
|
|
52738
|
+
const selected = await selectAppId(filteredCandidates);
|
|
52739
|
+
if (!selected) {
|
|
52740
|
+
logger_logger.error("No app ID provided. Cannot continue without an app ID.");
|
|
52741
|
+
process.exit(1);
|
|
52742
|
+
}
|
|
52743
|
+
finalAppId = selected.appId;
|
|
52744
|
+
if (selected.detectedUrl) anvilUrl = normalizeAnvilUrl(selected.detectedUrl);
|
|
52745
|
+
if (selected.detectedUsername && !username) username = selected.detectedUsername;
|
|
52746
|
+
}
|
|
52747
|
+
}
|
|
52748
|
+
const binding = await getAppAuthBinding(repoPath, finalAppId);
|
|
52749
|
+
if (binding.url && !explicitUrl) {
|
|
52750
|
+
anvilUrl = normalizeAnvilUrl(binding.url);
|
|
52751
|
+
logger_logger.verbose(chalk_source.cyan("Using app binding URL: ") + chalk_source.bold(anvilUrl));
|
|
52752
|
+
}
|
|
52753
|
+
if (binding.username && !explicitUsername) {
|
|
52754
|
+
username = binding.username;
|
|
52755
|
+
logger_logger.verbose(chalk_source.cyan("Using app binding username: ") + chalk_source.bold(username));
|
|
52756
|
+
}
|
|
52757
|
+
shouldPersistUsernameBinding = !binding.username && !explicitUsername;
|
|
52758
|
+
if (explicitUrl) anvilUrl = normalizeAnvilUrl(explicitUrl);
|
|
52759
|
+
else if (!anvilUrl) if (fallbackUrl) anvilUrl = fallbackUrl;
|
|
52760
|
+
else {
|
|
52761
|
+
const resolvedFallbackUrl = await resolveUrlForFallback();
|
|
52762
|
+
if (null === resolvedFallbackUrl) {
|
|
52763
|
+
logger_logger.warn("Operation cancelled.");
|
|
52764
|
+
process.exit(0);
|
|
52765
|
+
}
|
|
52766
|
+
anvilUrl = normalizeAnvilUrl(resolvedFallbackUrl);
|
|
52767
|
+
}
|
|
52768
|
+
anvilUrl = normalizeAnvilUrl(anvilUrl);
|
|
52769
|
+
logger_logger.verbose(chalk_source.green("Using app ID: ") + chalk_source.bold(finalAppId));
|
|
52770
|
+
logger_logger.verbose(chalk_source.cyan("Using Anvil URL: ") + chalk_source.bold(anvilUrl));
|
|
52771
|
+
const resolvedUsername = await resolveUsernameForUrl(anvilUrl, username);
|
|
52772
|
+
if (null === resolvedUsername) {
|
|
52773
|
+
logger_logger.warn("Operation cancelled.");
|
|
52774
|
+
process.exit(0);
|
|
52775
|
+
}
|
|
52776
|
+
username = resolvedUsername;
|
|
52777
|
+
if (shouldPersistUsernameBinding && username && auth_getAccountsForUrl(anvilUrl).length > 1) {
|
|
52778
|
+
await setAppAuthBinding(repoPath, finalAppId, {
|
|
52779
|
+
url: anvilUrl,
|
|
52780
|
+
username
|
|
52781
|
+
});
|
|
52782
|
+
logger_logger.verbose(chalk_source.cyan("Saved app account binding for future non-interactive use."));
|
|
52783
|
+
}
|
|
52784
|
+
if (username) logger_logger.verbose(chalk_source.cyan("Using account: ") + chalk_source.bold(username));
|
|
52785
|
+
if (!hasTokensForUrl(anvilUrl, username)) {
|
|
52786
|
+
if (username) logger_logger.error(`Not logged in to ${anvilUrl} as ${username}`);
|
|
52787
|
+
else logger_logger.error(`Not logged in to ${anvilUrl}`);
|
|
52788
|
+
logger_logger.verbose(chalk_source.yellow("Please log in first:"));
|
|
52789
|
+
logger_logger.info(chalk_source.cyan(` anvil login ${anvilUrl.replace(/^https?:\/\//, "")}`));
|
|
52790
|
+
process.exit(1);
|
|
52791
|
+
}
|
|
52792
|
+
logger_logger.verbose(chalk_source.green("✓ Authentication tokens found"));
|
|
52793
|
+
await configureWatchGitAuth({
|
|
52794
|
+
repoPath,
|
|
52795
|
+
appId: finalAppId,
|
|
52803
52796
|
anvilUrl,
|
|
52804
|
-
username
|
|
52805
|
-
remoteName
|
|
52797
|
+
username
|
|
52806
52798
|
});
|
|
52807
|
-
logger_logger.
|
|
52799
|
+
logger_logger.progress("validate", `Validating app ID: ${finalAppId}`);
|
|
52800
|
+
const appIdValidation = await validateAppId(finalAppId, anvilUrl, username);
|
|
52801
|
+
logger_logger.progressEnd("validate");
|
|
52802
|
+
if (!appIdValidation.valid) {
|
|
52803
|
+
logger_logger.error(`App ID validation failed: ${appIdValidation.error}`);
|
|
52804
|
+
logger_logger.verbose(chalk_source.yellow("This could mean:"));
|
|
52805
|
+
logger_logger.verbose(chalk_source.yellow(" • The app ID doesn't exist on the server"));
|
|
52806
|
+
logger_logger.verbose(chalk_source.yellow(" • You don't have access to this app"));
|
|
52807
|
+
logger_logger.verbose(chalk_source.yellow(" • There's a network or authentication issue"));
|
|
52808
|
+
process.exit(1);
|
|
52809
|
+
}
|
|
52810
|
+
logger_logger.verbose(chalk_source.green(`✔ App ID validated successfully`));
|
|
52811
|
+
if (appIdValidation.app_name) logger_logger.verbose(chalk_source.gray(` App name: ${appIdValidation.app_name}`));
|
|
52812
|
+
if (stagedOnly) logger_logger.info(chalk_source.yellow("▸ Staged-only mode: Only staged changes will be synced"));
|
|
52813
|
+
if (autoMode) logger_logger.info(chalk_source.cyan("▸ Auto mode: Will automatically restart on branch changes and sync when behind"));
|
|
52814
|
+
logger_logger.progress("init", "Initializing watch session...");
|
|
52815
|
+
const session = await api_watch(repoPath, finalAppId, anvilUrl, stagedOnly, username);
|
|
52816
|
+
logger_logger.progressEnd("init");
|
|
52817
|
+
if (openAfterValidation) await openWatchPath(resolveWatchOpenPath(repoPath));
|
|
52818
|
+
logger_logger.verbose(chalk_source.blue("Watching for file changes..."));
|
|
52819
|
+
const started = await checkSyncStatusAndStart(session, {
|
|
52820
|
+
autoMode,
|
|
52821
|
+
repoPath,
|
|
52822
|
+
appId: finalAppId,
|
|
52823
|
+
anvilUrl,
|
|
52824
|
+
stagedOnly,
|
|
52825
|
+
username
|
|
52826
|
+
});
|
|
52827
|
+
if (!started) process.exit(1);
|
|
52808
52828
|
} catch (e) {
|
|
52809
|
-
|
|
52810
|
-
|
|
52811
|
-
logger_logger.progressEnd("checkout", `Checked out ${parsed.appId} into ${destinationDisplay}`);
|
|
52812
|
-
const preferredEditor = String(getConfig("preferredEditor") || "").trim();
|
|
52813
|
-
const preferredEditorCommand = preferredEditor ? getPreferredEditorCommand(preferredEditor) : "";
|
|
52814
|
-
if (options.open) {
|
|
52815
|
-
await checkout_openPathInEditorOrDefault(destinationPath, preferredEditorCommand);
|
|
52816
|
-
logger_logger.info(chalk_source.gray(`Opened ${destinationDisplay}`));
|
|
52829
|
+
logger_logger.error("Error: " + errors_getErrorMessage(e));
|
|
52830
|
+
process.exit(1);
|
|
52817
52831
|
}
|
|
52818
|
-
if (preferredEditorCommand && !options.open) if (isCommandAvailable(preferredEditorCommand)) logger_logger.info(chalk_source.cyan(`Next: ${preferredEditorCommand} ${destinationDisplay}`));
|
|
52819
|
-
else logger_logger.warn(`Preferred editor command '${preferredEditorCommand}' was not found in PATH. Run 'anvil configure' to choose an installed editor.`);
|
|
52820
52832
|
}
|
|
52821
|
-
|
|
52822
|
-
|
|
52823
|
-
|
|
52824
|
-
|
|
52825
|
-
|
|
52826
|
-
|
|
52827
|
-
|
|
52828
|
-
|
|
52829
|
-
|
|
52830
|
-
|
|
52831
|
-
|
|
52832
|
-
|
|
52833
|
-
|
|
52834
|
-
|
|
52835
|
-
|
|
52836
|
-
origin: options?.origin,
|
|
52837
|
-
quiet: options?.quiet,
|
|
52838
|
-
verbose: globalOptions.verbose,
|
|
52839
|
-
url: options?.url,
|
|
52840
|
-
user: options?.user,
|
|
52841
|
-
force: options?.force,
|
|
52842
|
-
query: options?.query
|
|
52843
|
-
}, defaultCheckoutDeps);
|
|
52844
|
-
} catch (e) {
|
|
52845
|
-
logger_logger.error("Error: " + errors_getErrorMessage(e));
|
|
52846
|
-
process.exit(1);
|
|
52847
|
-
}
|
|
52833
|
+
function registerWatchCommand(program) {
|
|
52834
|
+
const watchCommand = program.command("watch [path]").description("Watch for file changes and sync to Anvil").alias("sync").alias("w").option("-A, --appid <APP_ID>", "Specify app ID directly").option("-f, --first", "Auto-select first detected app ID without confirmation").option("-s, --staged-only", "Only sync staged changes (use git add to stage files)").option("-a, --auto", "Auto mode: restart on branch changes and sync when behind").option("-O, --open", "Open watched path in preferred editor (or default app)").option("-u, --url <ANVIL_URL>", "Specify Anvil server URL (e.g., anvil.works, localhost)").option("-U, --user <USERNAME>", "Specify which user account to use").action(async (path, options, command)=>{
|
|
52835
|
+
const globalOptions = command.optsWithGlobals();
|
|
52836
|
+
if (void 0 !== globalOptions.verbose && logger_logger instanceof CLILogger) logger_logger.setVerbose(globalOptions.verbose);
|
|
52837
|
+
await handleWatchCommand({
|
|
52838
|
+
path,
|
|
52839
|
+
appid: options.appid,
|
|
52840
|
+
useFirst: options.first,
|
|
52841
|
+
stagedOnly: options.stagedOnly,
|
|
52842
|
+
autoMode: options.auto,
|
|
52843
|
+
verbose: globalOptions.verbose,
|
|
52844
|
+
open: options.open,
|
|
52845
|
+
url: options.url,
|
|
52846
|
+
user: options.user
|
|
52847
|
+
});
|
|
52848
52848
|
});
|
|
52849
|
-
|
|
52849
|
+
return watchCommand;
|
|
52850
52850
|
}
|
|
52851
52851
|
const gitCredential_defaultDeps = {
|
|
52852
52852
|
getValidAuthToken: auth_getValidAuthToken,
|