@anvil-works/anvil-cli 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +208 -145
- package/dist/cli.js +847 -141
- package/dist/index.js +12 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -42459,8 +42459,31 @@ var __webpack_exports__ = {};
|
|
|
42459
42459
|
}
|
|
42460
42460
|
const configDefaults = {
|
|
42461
42461
|
devMode: false,
|
|
42462
|
-
verbose: false
|
|
42462
|
+
verbose: false,
|
|
42463
|
+
preferredEditor: ""
|
|
42464
|
+
};
|
|
42465
|
+
const preferredEditors = [
|
|
42466
|
+
"vscode",
|
|
42467
|
+
"cursor",
|
|
42468
|
+
"nvim",
|
|
42469
|
+
"zed",
|
|
42470
|
+
"windsurf",
|
|
42471
|
+
"pycharm"
|
|
42472
|
+
];
|
|
42473
|
+
const preferredEditorCommandByValue = {
|
|
42474
|
+
vscode: "code",
|
|
42475
|
+
cursor: "cursor",
|
|
42476
|
+
nvim: "nvim",
|
|
42477
|
+
zed: "zed",
|
|
42478
|
+
windsurf: "windsurf",
|
|
42479
|
+
pycharm: "charm"
|
|
42463
42480
|
};
|
|
42481
|
+
const settableConfigKeys = [
|
|
42482
|
+
"anvilUrl",
|
|
42483
|
+
"devMode",
|
|
42484
|
+
"verbose",
|
|
42485
|
+
"preferredEditor"
|
|
42486
|
+
];
|
|
42464
42487
|
function isTestMode() {
|
|
42465
42488
|
return "test" === process.env.NODE_ENV || !!process.env.RSTEST;
|
|
42466
42489
|
}
|
|
@@ -42532,6 +42555,23 @@ var __webpack_exports__ = {};
|
|
|
42532
42555
|
}
|
|
42533
42556
|
return value;
|
|
42534
42557
|
}
|
|
42558
|
+
function parseConfigSetValue(key, value) {
|
|
42559
|
+
if (!settableConfigKeys.includes(key)) throw new Error(`✖ Error: unknown config key '${key}'. Allowed keys: ${settableConfigKeys.join(", ")}`);
|
|
42560
|
+
if ("preferredEditor" === key) {
|
|
42561
|
+
let normalized = value.trim().toLowerCase();
|
|
42562
|
+
if ("code" === normalized) normalized = "vscode";
|
|
42563
|
+
if (!preferredEditors.includes(normalized)) throw new Error(`✖ Error: preferredEditor must be one of: ${preferredEditors.join(", ")} (alias: code -> vscode)`);
|
|
42564
|
+
return normalized;
|
|
42565
|
+
}
|
|
42566
|
+
if ("anvilUrl" === key) return value.trim().replace(/\/+$/, "");
|
|
42567
|
+
return coerceConfigValue(key, value);
|
|
42568
|
+
}
|
|
42569
|
+
function getPreferredEditorCommand(preferredEditorValue) {
|
|
42570
|
+
const normalized = preferredEditorValue.trim().toLowerCase();
|
|
42571
|
+
if ("code" === normalized) return "code";
|
|
42572
|
+
if (preferredEditors.includes(normalized)) return preferredEditorCommandByValue[normalized];
|
|
42573
|
+
return preferredEditorValue;
|
|
42574
|
+
}
|
|
42535
42575
|
function getAllConfig() {
|
|
42536
42576
|
return config_config.store;
|
|
42537
42577
|
}
|
|
@@ -47584,6 +47624,7 @@ var __webpack_exports__ = {};
|
|
|
47584
47624
|
});
|
|
47585
47625
|
this.ws.on("error", (error)=>{
|
|
47586
47626
|
logger_logger.debug(`[WebSocket ${this.sessionId}]`, `Error: ${error.message}`);
|
|
47627
|
+
if (this.isClosing) return;
|
|
47587
47628
|
if (error.message.includes("502")) logger_logger.error(chalk_source.red("WebSocket connection failed (502 Bad Gateway) - likely a temporary server issue"));
|
|
47588
47629
|
else logger_logger.error(chalk_source.red(`WebSocket error: ${error.message}`));
|
|
47589
47630
|
this.emit("error", {
|
|
@@ -47684,15 +47725,20 @@ var __webpack_exports__ = {};
|
|
|
47684
47725
|
}
|
|
47685
47726
|
teardownSocket() {
|
|
47686
47727
|
this.stopHeartbeat();
|
|
47687
|
-
|
|
47728
|
+
const socket = this.ws;
|
|
47729
|
+
this.ws = null;
|
|
47730
|
+
if (!socket) return;
|
|
47688
47731
|
try {
|
|
47689
47732
|
logger_logger.debug(`[WebSocket ${this.sessionId}]`, "Closing WebSocket");
|
|
47690
|
-
|
|
47691
|
-
|
|
47733
|
+
if (socket.readyState === ws_wrapper.CONNECTING) {
|
|
47734
|
+
socket.once("error", ()=>{});
|
|
47735
|
+
socket.terminate();
|
|
47736
|
+
return;
|
|
47737
|
+
}
|
|
47738
|
+
if (socket.readyState === ws_wrapper.OPEN) return void socket.close();
|
|
47692
47739
|
} catch (e) {
|
|
47693
47740
|
logger_logger.debug(`[WebSocket ${this.sessionId}]`, "Error closing WebSocket (ignoring):", e);
|
|
47694
47741
|
}
|
|
47695
|
-
this.ws = null;
|
|
47696
47742
|
}
|
|
47697
47743
|
}
|
|
47698
47744
|
async function detectRemoteChanges(gitService, oldCommitId, newCommitId) {
|
|
@@ -49956,13 +50002,199 @@ var __webpack_exports__ = {};
|
|
|
49956
50002
|
session.hasUncommittedChanges = hasUncommittedChanges;
|
|
49957
50003
|
return session;
|
|
49958
50004
|
}
|
|
49959
|
-
function
|
|
49960
|
-
|
|
49961
|
-
|
|
49962
|
-
|
|
50005
|
+
function getUrlConfigKey(appId) {
|
|
50006
|
+
return `anvil.auth.${appId}.url`;
|
|
50007
|
+
}
|
|
50008
|
+
function getUsernameConfigKey(appId) {
|
|
50009
|
+
return `anvil.auth.${appId}.username`;
|
|
50010
|
+
}
|
|
50011
|
+
async function getLocalConfigValue(repoPath, key) {
|
|
50012
|
+
try {
|
|
50013
|
+
const value = (await esm_default(repoPath).raw([
|
|
50014
|
+
"config",
|
|
50015
|
+
"--local",
|
|
50016
|
+
"--get",
|
|
50017
|
+
key
|
|
50018
|
+
])).trim();
|
|
50019
|
+
return value || void 0;
|
|
50020
|
+
} catch {
|
|
50021
|
+
return;
|
|
50022
|
+
}
|
|
50023
|
+
}
|
|
50024
|
+
async function getAppAuthBinding(repoPath, appId) {
|
|
50025
|
+
const [url, username] = await Promise.all([
|
|
50026
|
+
getLocalConfigValue(repoPath, getUrlConfigKey(appId)),
|
|
50027
|
+
getLocalConfigValue(repoPath, getUsernameConfigKey(appId))
|
|
50028
|
+
]);
|
|
50029
|
+
return {
|
|
50030
|
+
url: url ? normalizeAnvilUrl(url) : void 0,
|
|
50031
|
+
username: username || void 0
|
|
50032
|
+
};
|
|
50033
|
+
}
|
|
50034
|
+
async function setAppAuthBinding(repoPath, appId, binding) {
|
|
50035
|
+
const git = esm_default(repoPath);
|
|
50036
|
+
if (binding.url) await git.raw([
|
|
50037
|
+
"config",
|
|
50038
|
+
"--local",
|
|
50039
|
+
getUrlConfigKey(appId),
|
|
50040
|
+
normalizeAnvilUrl(binding.url)
|
|
50041
|
+
]);
|
|
50042
|
+
if (binding.username) await git.raw([
|
|
50043
|
+
"config",
|
|
50044
|
+
"--local",
|
|
50045
|
+
getUsernameConfigKey(appId),
|
|
50046
|
+
binding.username
|
|
50047
|
+
]);
|
|
50048
|
+
}
|
|
50049
|
+
function getCleanGitRemoteUrl(appId, anvilUrl) {
|
|
50050
|
+
const normalized = normalizeAnvilUrl(anvilUrl);
|
|
50051
|
+
const url = new URL(normalized);
|
|
50052
|
+
return `${url.protocol}//${url.host}/git/${appId}.git`;
|
|
50053
|
+
}
|
|
50054
|
+
async function configureCredentialHelperForUrl(repoPath, anvilUrl) {
|
|
50055
|
+
const normalized = normalizeAnvilUrl(anvilUrl);
|
|
50056
|
+
const url = new URL(normalized);
|
|
50057
|
+
const scope = `${url.protocol}//${url.host}`;
|
|
50058
|
+
const git = esm_default(repoPath);
|
|
50059
|
+
try {
|
|
50060
|
+
await git.raw([
|
|
50061
|
+
"config",
|
|
50062
|
+
"--local",
|
|
50063
|
+
"--unset-all",
|
|
50064
|
+
`credential.${scope}.helper`
|
|
50065
|
+
]);
|
|
50066
|
+
} catch {}
|
|
50067
|
+
await git.raw([
|
|
50068
|
+
"config",
|
|
50069
|
+
"--local",
|
|
50070
|
+
"--add",
|
|
50071
|
+
`credential.${scope}.helper`,
|
|
50072
|
+
""
|
|
50073
|
+
]);
|
|
50074
|
+
await git.raw([
|
|
50075
|
+
"config",
|
|
50076
|
+
"--local",
|
|
50077
|
+
"--add",
|
|
50078
|
+
`credential.${scope}.helper`,
|
|
50079
|
+
"!anvil git-credential"
|
|
50080
|
+
]);
|
|
50081
|
+
await git.raw([
|
|
50082
|
+
"config",
|
|
50083
|
+
"--local",
|
|
50084
|
+
`credential.${scope}.useHttpPath`,
|
|
50085
|
+
"true"
|
|
50086
|
+
]);
|
|
50087
|
+
await git.raw([
|
|
50088
|
+
"config",
|
|
50089
|
+
"--local",
|
|
50090
|
+
`credential.${scope}.username`,
|
|
50091
|
+
"git"
|
|
50092
|
+
]);
|
|
50093
|
+
}
|
|
50094
|
+
async function hardenCheckoutGitAuth(options) {
|
|
50095
|
+
const { repoPath, appId, anvilUrl, username } = options;
|
|
50096
|
+
const remoteName = options.remoteName || "origin";
|
|
50097
|
+
const cleanRemoteUrl = getCleanGitRemoteUrl(appId, anvilUrl);
|
|
50098
|
+
const git = esm_default(repoPath);
|
|
50099
|
+
await git.raw([
|
|
50100
|
+
"remote",
|
|
50101
|
+
"set-url",
|
|
50102
|
+
remoteName,
|
|
50103
|
+
cleanRemoteUrl
|
|
50104
|
+
]);
|
|
50105
|
+
await configureCredentialHelperForUrl(repoPath, anvilUrl);
|
|
50106
|
+
await setAppAuthBinding(repoPath, appId, {
|
|
50107
|
+
url: anvilUrl,
|
|
50108
|
+
username
|
|
50109
|
+
});
|
|
50110
|
+
return {
|
|
50111
|
+
cleanRemoteUrl
|
|
50112
|
+
};
|
|
50113
|
+
}
|
|
50114
|
+
function parseAppIdFromGitPath(pathValue) {
|
|
50115
|
+
if (!pathValue) return;
|
|
50116
|
+
const normalizedPath = pathValue.startsWith("/") ? pathValue : `/${pathValue}`;
|
|
50117
|
+
const match = normalizedPath.match(/\/git\/([A-Z0-9]+)\.git(?:$|\/)/);
|
|
50118
|
+
return match ? match[1] : void 0;
|
|
50119
|
+
}
|
|
50120
|
+
function decideFallbackUrl(explicitUrl, availableUrls = getAvailableAnvilUrls()) {
|
|
50121
|
+
if (explicitUrl) return {
|
|
50122
|
+
source: "explicit",
|
|
50123
|
+
url: normalizeAnvilUrl(explicitUrl)
|
|
50124
|
+
};
|
|
50125
|
+
if (availableUrls.length > 1) return {
|
|
50126
|
+
source: "available-multiple",
|
|
50127
|
+
urls: availableUrls
|
|
50128
|
+
};
|
|
50129
|
+
if (1 === availableUrls.length) return {
|
|
50130
|
+
source: "available-single",
|
|
50131
|
+
url: availableUrls[0]
|
|
50132
|
+
};
|
|
49963
50133
|
const fromConfig = getConfig("anvilUrl");
|
|
49964
|
-
if ("string" == typeof fromConfig && fromConfig.trim()) return
|
|
49965
|
-
|
|
50134
|
+
if ("string" == typeof fromConfig && fromConfig.trim()) return {
|
|
50135
|
+
source: "config",
|
|
50136
|
+
url: normalizeAnvilUrl(fromConfig.trim())
|
|
50137
|
+
};
|
|
50138
|
+
return {
|
|
50139
|
+
source: "default",
|
|
50140
|
+
url: isDevMode() ? "http://localhost:3000" : "https://anvil.works"
|
|
50141
|
+
};
|
|
50142
|
+
}
|
|
50143
|
+
function decideUsernameForUrl(accounts) {
|
|
50144
|
+
if (0 === accounts.length) return {
|
|
50145
|
+
source: "none"
|
|
50146
|
+
};
|
|
50147
|
+
if (1 === accounts.length) return {
|
|
50148
|
+
source: "single",
|
|
50149
|
+
username: accounts[0]
|
|
50150
|
+
};
|
|
50151
|
+
return {
|
|
50152
|
+
source: "multiple",
|
|
50153
|
+
usernames: [
|
|
50154
|
+
...accounts
|
|
50155
|
+
]
|
|
50156
|
+
};
|
|
50157
|
+
}
|
|
50158
|
+
async function resolveUsernameForUrl(anvilUrl, explicitUsername, promptMessage = "Multiple accounts found. Which account owns this app?") {
|
|
50159
|
+
if (explicitUsername) return explicitUsername;
|
|
50160
|
+
const decision = decideUsernameForUrl(auth_getAccountsForUrl(anvilUrl));
|
|
50161
|
+
if ("none" === decision.source) return;
|
|
50162
|
+
if ("single" === decision.source) {
|
|
50163
|
+
logger_logger.verbose(chalk_source.cyan("Auto-selected account: ") + chalk_source.bold(decision.username));
|
|
50164
|
+
return decision.username;
|
|
50165
|
+
}
|
|
50166
|
+
const choices = decision.usernames.map((acct)=>({
|
|
50167
|
+
name: acct,
|
|
50168
|
+
value: acct
|
|
50169
|
+
}));
|
|
50170
|
+
choices.push({
|
|
50171
|
+
name: "Cancel",
|
|
50172
|
+
value: null
|
|
50173
|
+
});
|
|
50174
|
+
return logger_logger.select(promptMessage, choices, decision.usernames[0]);
|
|
50175
|
+
}
|
|
50176
|
+
async function confirmReverseLookupWithResolvedUser(anvilUrl, username) {
|
|
50177
|
+
const resolvedUsername = await resolveUsernameForUrl(anvilUrl, username, `Multiple accounts found for ${anvilUrl}. Which account should be used for app lookup?`);
|
|
50178
|
+
if (null === resolvedUsername) return null;
|
|
50179
|
+
const shouldContinue = await logger_logger.confirm(`Search ${anvilUrl} ${resolvedUsername ? `for ${resolvedUsername}` : ""} for matching app IDs? (slower)`, true);
|
|
50180
|
+
return {
|
|
50181
|
+
username: resolvedUsername,
|
|
50182
|
+
shouldContinue
|
|
50183
|
+
};
|
|
50184
|
+
}
|
|
50185
|
+
async function resolveUrlForFallback(explicitUrl) {
|
|
50186
|
+
const decision = decideFallbackUrl(explicitUrl);
|
|
50187
|
+
if ("available-multiple" !== decision.source) return decision.url;
|
|
50188
|
+
const choices = decision.urls.map((url)=>({
|
|
50189
|
+
name: url,
|
|
50190
|
+
value: url
|
|
50191
|
+
}));
|
|
50192
|
+
choices.push({
|
|
50193
|
+
name: "Cancel",
|
|
50194
|
+
value: null
|
|
50195
|
+
});
|
|
50196
|
+
const selectedUrl = await logger_logger.select("Multiple logged-in Anvil URLs found. Which one would you like to use?", choices, decision.urls[0]);
|
|
50197
|
+
return selectedUrl;
|
|
49966
50198
|
}
|
|
49967
50199
|
async function selectAppId(candidates) {
|
|
49968
50200
|
if (0 === candidates.length) {
|
|
@@ -50474,14 +50706,25 @@ var __webpack_exports__ = {};
|
|
|
50474
50706
|
let finalAppId;
|
|
50475
50707
|
let anvilUrl;
|
|
50476
50708
|
let username = explicitUsername;
|
|
50709
|
+
let fallbackUrl;
|
|
50710
|
+
let shouldPersistUsernameBinding = false;
|
|
50477
50711
|
if (explicitAppId) {
|
|
50478
50712
|
finalAppId = explicitAppId;
|
|
50713
|
+
const binding = await getAppAuthBinding(repoPath, explicitAppId);
|
|
50714
|
+
if (binding.url && !explicitUrl) {
|
|
50715
|
+
anvilUrl = normalizeAnvilUrl(binding.url);
|
|
50716
|
+
logger_logger.verbose(chalk_source.cyan("Resolved URL from binding for app ID: ") + chalk_source.bold(anvilUrl));
|
|
50717
|
+
}
|
|
50718
|
+
if (binding.username && !explicitUsername) {
|
|
50719
|
+
username = binding.username;
|
|
50720
|
+
logger_logger.verbose(chalk_source.cyan("Resolved username from binding for app ID: ") + chalk_source.bold(username));
|
|
50721
|
+
}
|
|
50479
50722
|
const remoteInfo = lookupRemoteInfoForAppId(explicitAppId, detectedFromAllRemotes);
|
|
50480
|
-
if (remoteInfo.detectedUrl && !explicitUrl) {
|
|
50723
|
+
if (remoteInfo.detectedUrl && !explicitUrl && !anvilUrl) {
|
|
50481
50724
|
anvilUrl = normalizeAnvilUrl(remoteInfo.detectedUrl);
|
|
50482
50725
|
logger_logger.verbose(chalk_source.cyan("Resolved URL from remote for app ID: ") + chalk_source.bold(anvilUrl));
|
|
50483
50726
|
}
|
|
50484
|
-
if (remoteInfo.detectedUsername && !explicitUsername) {
|
|
50727
|
+
if (remoteInfo.detectedUsername && !explicitUsername && !username) {
|
|
50485
50728
|
username = remoteInfo.detectedUsername;
|
|
50486
50729
|
logger_logger.verbose(chalk_source.cyan("Resolved username from remote for app ID: ") + chalk_source.bold(username));
|
|
50487
50730
|
}
|
|
@@ -50489,13 +50732,23 @@ var __webpack_exports__ = {};
|
|
|
50489
50732
|
logger_logger.verbose(chalk_source.cyan("No app ID provided, attempting auto-detection..."));
|
|
50490
50733
|
if (0 === filteredCandidates.length) {
|
|
50491
50734
|
logger_logger.verbose(chalk_source.gray("No app IDs found in git remotes."));
|
|
50492
|
-
const
|
|
50493
|
-
|
|
50494
|
-
|
|
50495
|
-
|
|
50735
|
+
const resolvedFallbackUrl = await resolveUrlForFallback(explicitUrl);
|
|
50736
|
+
if (null === resolvedFallbackUrl) {
|
|
50737
|
+
logger_logger.warn("Operation cancelled.");
|
|
50738
|
+
process.exit(0);
|
|
50739
|
+
}
|
|
50740
|
+
fallbackUrl = normalizeAnvilUrl(resolvedFallbackUrl);
|
|
50741
|
+
const lookupDecision = await confirmReverseLookupWithResolvedUser(fallbackUrl, username);
|
|
50742
|
+
if (null === lookupDecision) {
|
|
50743
|
+
logger_logger.warn("Operation cancelled.");
|
|
50744
|
+
process.exit(0);
|
|
50745
|
+
}
|
|
50746
|
+
username = lookupDecision.username;
|
|
50747
|
+
if (lookupDecision.shouldContinue) {
|
|
50748
|
+
logger_logger.progress("detect", `Searching ${fallbackUrl} ${username ? `for ${username}` : ""} for matching app IDs...`);
|
|
50496
50749
|
const reverseLookupCandidates = await detectAppIdsByCommitLookup(repoPath, {
|
|
50497
50750
|
anvilUrl: fallbackUrl,
|
|
50498
|
-
username
|
|
50751
|
+
username,
|
|
50499
50752
|
includeRemotes: false
|
|
50500
50753
|
});
|
|
50501
50754
|
logger_logger.progressEnd("detect");
|
|
@@ -50526,32 +50779,41 @@ var __webpack_exports__ = {};
|
|
|
50526
50779
|
if (selected.detectedUsername && !username) username = selected.detectedUsername;
|
|
50527
50780
|
}
|
|
50528
50781
|
}
|
|
50782
|
+
const binding = await getAppAuthBinding(repoPath, finalAppId);
|
|
50783
|
+
if (binding.url && !explicitUrl) {
|
|
50784
|
+
anvilUrl = normalizeAnvilUrl(binding.url);
|
|
50785
|
+
logger_logger.verbose(chalk_source.cyan("Using app binding URL: ") + chalk_source.bold(anvilUrl));
|
|
50786
|
+
}
|
|
50787
|
+
if (binding.username && !explicitUsername) {
|
|
50788
|
+
username = binding.username;
|
|
50789
|
+
logger_logger.verbose(chalk_source.cyan("Using app binding username: ") + chalk_source.bold(username));
|
|
50790
|
+
}
|
|
50791
|
+
shouldPersistUsernameBinding = !binding.username && !explicitUsername;
|
|
50529
50792
|
if (explicitUrl) anvilUrl = normalizeAnvilUrl(explicitUrl);
|
|
50530
|
-
else if (!anvilUrl) anvilUrl =
|
|
50793
|
+
else if (!anvilUrl) if (fallbackUrl) anvilUrl = fallbackUrl;
|
|
50794
|
+
else {
|
|
50795
|
+
const resolvedFallbackUrl = await resolveUrlForFallback();
|
|
50796
|
+
if (null === resolvedFallbackUrl) {
|
|
50797
|
+
logger_logger.warn("Operation cancelled.");
|
|
50798
|
+
process.exit(0);
|
|
50799
|
+
}
|
|
50800
|
+
anvilUrl = normalizeAnvilUrl(resolvedFallbackUrl);
|
|
50801
|
+
}
|
|
50531
50802
|
anvilUrl = normalizeAnvilUrl(anvilUrl);
|
|
50532
50803
|
logger_logger.verbose(chalk_source.green("Using app ID: ") + chalk_source.bold(finalAppId));
|
|
50533
50804
|
logger_logger.verbose(chalk_source.cyan("Using Anvil URL: ") + chalk_source.bold(anvilUrl));
|
|
50534
|
-
|
|
50535
|
-
|
|
50536
|
-
|
|
50537
|
-
|
|
50538
|
-
|
|
50539
|
-
|
|
50540
|
-
|
|
50541
|
-
|
|
50542
|
-
|
|
50543
|
-
|
|
50544
|
-
|
|
50545
|
-
|
|
50546
|
-
value: null
|
|
50547
|
-
});
|
|
50548
|
-
const selected = await logger_logger.select("Multiple accounts found. Which account owns this app?", choices, accounts[0]);
|
|
50549
|
-
if (null === selected) {
|
|
50550
|
-
logger_logger.warn("Operation cancelled.");
|
|
50551
|
-
process.exit(0);
|
|
50552
|
-
}
|
|
50553
|
-
username = selected;
|
|
50554
|
-
}
|
|
50805
|
+
const resolvedUsername = await resolveUsernameForUrl(anvilUrl, username);
|
|
50806
|
+
if (null === resolvedUsername) {
|
|
50807
|
+
logger_logger.warn("Operation cancelled.");
|
|
50808
|
+
process.exit(0);
|
|
50809
|
+
}
|
|
50810
|
+
username = resolvedUsername;
|
|
50811
|
+
if (shouldPersistUsernameBinding && username && auth_getAccountsForUrl(anvilUrl).length > 1) {
|
|
50812
|
+
await setAppAuthBinding(repoPath, finalAppId, {
|
|
50813
|
+
url: anvilUrl,
|
|
50814
|
+
username
|
|
50815
|
+
});
|
|
50816
|
+
logger_logger.verbose(chalk_source.cyan("Saved app account binding for future non-interactive use."));
|
|
50555
50817
|
}
|
|
50556
50818
|
if (username) logger_logger.verbose(chalk_source.cyan("Using account: ") + chalk_source.bold(username));
|
|
50557
50819
|
if (!hasTokensForUrl(anvilUrl, username)) {
|
|
@@ -50611,7 +50873,6 @@ var __webpack_exports__ = {};
|
|
|
50611
50873
|
});
|
|
50612
50874
|
return watchCommand;
|
|
50613
50875
|
}
|
|
50614
|
-
var external_http_ = __webpack_require__("http");
|
|
50615
50876
|
const external_node_url_namespaceObject = require("node:url");
|
|
50616
50877
|
var external_node_child_process_ = __webpack_require__("node:child_process");
|
|
50617
50878
|
let isDockerCached;
|
|
@@ -51074,6 +51335,7 @@ var __webpack_exports__ = {};
|
|
|
51074
51335
|
defineLazyProperty(open_apps, 'browser', ()=>'browser');
|
|
51075
51336
|
defineLazyProperty(open_apps, 'browserPrivate', ()=>'browserPrivate');
|
|
51076
51337
|
const node_modules_open = open_open;
|
|
51338
|
+
var external_http_ = __webpack_require__("http");
|
|
51077
51339
|
const successPage = `<!DOCTYPE html>
|
|
51078
51340
|
<html lang="en">
|
|
51079
51341
|
<head>
|
|
@@ -51231,6 +51493,9 @@ var __webpack_exports__ = {};
|
|
|
51231
51493
|
</body>
|
|
51232
51494
|
</html>`;
|
|
51233
51495
|
}
|
|
51496
|
+
function oauth_login_base64url(buf) {
|
|
51497
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
51498
|
+
}
|
|
51234
51499
|
async function waitForOAuthWithPrompt(authUrl, oauthPromise) {
|
|
51235
51500
|
const rl = external_readline_namespaceObject.createInterface({
|
|
51236
51501
|
input: process.stdin,
|
|
@@ -51255,8 +51520,382 @@ var __webpack_exports__ = {};
|
|
|
51255
51520
|
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
51256
51521
|
return result.oauth;
|
|
51257
51522
|
}
|
|
51258
|
-
function
|
|
51259
|
-
|
|
51523
|
+
async function runInteractiveLoginFlow(anvilUrl) {
|
|
51524
|
+
const codeVerifier = external_crypto_.randomBytes(48).toString("hex");
|
|
51525
|
+
const codeChallenge = oauth_login_base64url(external_crypto_.createHash("sha256").update(codeVerifier, "ascii").digest());
|
|
51526
|
+
const state = external_crypto_.randomBytes(16).toString("hex");
|
|
51527
|
+
const server = external_http_.createServer();
|
|
51528
|
+
await new Promise((resolve)=>server.listen(0, "127.0.0.1", resolve));
|
|
51529
|
+
const address = server.address();
|
|
51530
|
+
if (!address || "string" == typeof address) throw new Error("No address");
|
|
51531
|
+
const port = address.port;
|
|
51532
|
+
const redirectUri = `http://127.0.0.1:${port}/oauth-callback`;
|
|
51533
|
+
const SCOPES = "apps:read apps:write user:read";
|
|
51534
|
+
const authUrl = new URL(`${anvilUrl}/oauth/authorize`);
|
|
51535
|
+
authUrl.searchParams.set("response_type", "code");
|
|
51536
|
+
authUrl.searchParams.set("client_id", "anvil-sync");
|
|
51537
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
51538
|
+
authUrl.searchParams.set("scope", SCOPES);
|
|
51539
|
+
authUrl.searchParams.set("state", state);
|
|
51540
|
+
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
51541
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
51542
|
+
const codePromise = new Promise((resolve, reject)=>{
|
|
51543
|
+
server.on("request", (req, res)=>{
|
|
51544
|
+
if (!req.url) return;
|
|
51545
|
+
const url = new URL(req.url, `http://127.0.0.1:${port}`);
|
|
51546
|
+
if ("/oauth-callback" !== url.pathname) return;
|
|
51547
|
+
const code = url.searchParams.get("code") || void 0;
|
|
51548
|
+
const error = url.searchParams.get("error") || void 0;
|
|
51549
|
+
const recvState = url.searchParams.get("state");
|
|
51550
|
+
if (!recvState || !code && !error) {
|
|
51551
|
+
res.statusCode = 400;
|
|
51552
|
+
res.end("Missing code, error or state");
|
|
51553
|
+
reject(new Error("Missing code/state/error"));
|
|
51554
|
+
server.close();
|
|
51555
|
+
return;
|
|
51556
|
+
}
|
|
51557
|
+
res.statusCode = 200;
|
|
51558
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
51559
|
+
if (code) res.end(successPage);
|
|
51560
|
+
else res.end(errorPage(error || "unknown"));
|
|
51561
|
+
resolve({
|
|
51562
|
+
code,
|
|
51563
|
+
error,
|
|
51564
|
+
recvState
|
|
51565
|
+
});
|
|
51566
|
+
server.closeAllConnections();
|
|
51567
|
+
server.close();
|
|
51568
|
+
});
|
|
51569
|
+
});
|
|
51570
|
+
logger_logger.info(chalk_source.blue("Link to your Anvil account to continue."));
|
|
51571
|
+
console.log();
|
|
51572
|
+
logger_logger.info(chalk_source.gray(` ${authUrl}`));
|
|
51573
|
+
console.log();
|
|
51574
|
+
const { code, error, recvState } = await waitForOAuthWithPrompt(authUrl, codePromise);
|
|
51575
|
+
if (recvState !== state) throw new Error("Invalid state received from OAuth callback");
|
|
51576
|
+
if (error) throw new Error(`Error received from OAuth callback: ${error}`);
|
|
51577
|
+
const tokenResponse = await fetch(`${anvilUrl}/oauth/token`, {
|
|
51578
|
+
method: "POST",
|
|
51579
|
+
headers: {
|
|
51580
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
51581
|
+
},
|
|
51582
|
+
body: new URLSearchParams({
|
|
51583
|
+
grant_type: "authorization_code",
|
|
51584
|
+
code: code,
|
|
51585
|
+
redirect_uri: redirectUri,
|
|
51586
|
+
client_id: "anvil-sync",
|
|
51587
|
+
code_verifier: codeVerifier
|
|
51588
|
+
})
|
|
51589
|
+
});
|
|
51590
|
+
if (!tokenResponse.ok) {
|
|
51591
|
+
const errorText = await tokenResponse.text();
|
|
51592
|
+
throw new Error(`Failed to exchange authorization code for token. ${errorText}`);
|
|
51593
|
+
}
|
|
51594
|
+
const tokenData = await tokenResponse.json();
|
|
51595
|
+
return login(anvilUrl, {
|
|
51596
|
+
access_token: tokenData.access_token,
|
|
51597
|
+
refresh_token: tokenData.refresh_token,
|
|
51598
|
+
expires_in: tokenData.expires_in,
|
|
51599
|
+
scope: tokenData.scope
|
|
51600
|
+
});
|
|
51601
|
+
}
|
|
51602
|
+
const defaultCheckoutDeps = {
|
|
51603
|
+
getValidAuthToken: auth_getValidAuthToken,
|
|
51604
|
+
validateAppId: validateAppId,
|
|
51605
|
+
runInteractiveLoginFlow: runInteractiveLoginFlow,
|
|
51606
|
+
clone: async (repoUrl, destinationPath, options)=>{
|
|
51607
|
+
const cloneArgs = [];
|
|
51608
|
+
if (options?.branch) cloneArgs.push("--branch", options.branch);
|
|
51609
|
+
if ("number" == typeof options?.depth) cloneArgs.push("--depth", String(options.depth));
|
|
51610
|
+
if (options?.singleBranch) cloneArgs.push("--single-branch");
|
|
51611
|
+
if (options?.origin) cloneArgs.push("--origin", options.origin);
|
|
51612
|
+
if (options?.quiet) cloneArgs.push("--quiet");
|
|
51613
|
+
if (options?.verbose) cloneArgs.push("--verbose");
|
|
51614
|
+
await esm_default().clone(repoUrl, destinationPath, cloneArgs);
|
|
51615
|
+
},
|
|
51616
|
+
hardenCheckoutGitAuth: hardenCheckoutGitAuth
|
|
51617
|
+
};
|
|
51618
|
+
function parseCheckoutInput(input) {
|
|
51619
|
+
const trimmed = input.trim();
|
|
51620
|
+
if (!trimmed) throw new Error("Input is required.");
|
|
51621
|
+
if (/^[A-Z0-9]+$/.test(trimmed)) return {
|
|
51622
|
+
appId: trimmed
|
|
51623
|
+
};
|
|
51624
|
+
const asUrl = /^https?:\/\//.test(trimmed) ? trimmed : normalizeAnvilUrl(trimmed);
|
|
51625
|
+
let parsed;
|
|
51626
|
+
try {
|
|
51627
|
+
parsed = new URL(asUrl);
|
|
51628
|
+
} catch {
|
|
51629
|
+
throw new Error("Input must be an editor URL, /git URL, or bare app ID.");
|
|
51630
|
+
}
|
|
51631
|
+
const gitMatch = parsed.pathname.match(/\/git\/([A-Z0-9]+)\.git(?:$|\/)/);
|
|
51632
|
+
if (gitMatch) return {
|
|
51633
|
+
appId: gitMatch[1],
|
|
51634
|
+
detectedUrl: `${parsed.protocol}//${parsed.host}`
|
|
51635
|
+
};
|
|
51636
|
+
const appsMatch = parsed.pathname.match(/\/apps\/([A-Z0-9]+)(?:\/|$)/);
|
|
51637
|
+
if (appsMatch) return {
|
|
51638
|
+
appId: appsMatch[1],
|
|
51639
|
+
detectedUrl: `${parsed.protocol}//${parsed.host}`
|
|
51640
|
+
};
|
|
51641
|
+
throw new Error("Could not extract app ID. Expected URL path containing /apps/<APPID> or /git/<APPID>.git.");
|
|
51642
|
+
}
|
|
51643
|
+
function sanitizeDirectoryName(name) {
|
|
51644
|
+
return name.trim().replace(/\s+/g, "_").replace(/[^a-zA-Z0-9._-]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
51645
|
+
}
|
|
51646
|
+
function formatPathForDisplay(destinationPath) {
|
|
51647
|
+
const relative = external_path_default().relative(process.cwd(), destinationPath);
|
|
51648
|
+
if ("" === relative) return ".";
|
|
51649
|
+
if (relative.startsWith("..")) return destinationPath;
|
|
51650
|
+
return relative.startsWith(".") ? relative : `./${relative}`;
|
|
51651
|
+
}
|
|
51652
|
+
function getDefaultDestinationDirectory(appId, appName) {
|
|
51653
|
+
if (appName) {
|
|
51654
|
+
const sanitized = sanitizeDirectoryName(appName);
|
|
51655
|
+
if (sanitized) return sanitized;
|
|
51656
|
+
}
|
|
51657
|
+
return appId;
|
|
51658
|
+
}
|
|
51659
|
+
async function resolveCheckoutUrl(explicitUrl, parsedUrl) {
|
|
51660
|
+
if (explicitUrl) return normalizeAnvilUrl(explicitUrl);
|
|
51661
|
+
if (parsedUrl) return normalizeAnvilUrl(parsedUrl);
|
|
51662
|
+
const decision = decideFallbackUrl(void 0);
|
|
51663
|
+
if ("available-multiple" !== decision.source) return decision.url;
|
|
51664
|
+
const choices = decision.urls.map((url)=>({
|
|
51665
|
+
name: url,
|
|
51666
|
+
value: url
|
|
51667
|
+
}));
|
|
51668
|
+
choices.push({
|
|
51669
|
+
name: "Cancel",
|
|
51670
|
+
value: null
|
|
51671
|
+
});
|
|
51672
|
+
return logger_logger.select("Multiple logged-in Anvil URLs found. Which one would you like to use?", choices, decision.urls[0]);
|
|
51673
|
+
}
|
|
51674
|
+
async function isPathInsideGitRepo(targetPath) {
|
|
51675
|
+
const resolved = external_path_default().resolve(targetPath);
|
|
51676
|
+
let current = resolved;
|
|
51677
|
+
while(!external_fs_.existsSync(current)){
|
|
51678
|
+
const parent = external_path_default().dirname(current);
|
|
51679
|
+
if (parent === current) return false;
|
|
51680
|
+
current = parent;
|
|
51681
|
+
}
|
|
51682
|
+
try {
|
|
51683
|
+
const output = (await esm_default(current).raw([
|
|
51684
|
+
"rev-parse",
|
|
51685
|
+
"--is-inside-work-tree"
|
|
51686
|
+
])).trim();
|
|
51687
|
+
return "true" === output;
|
|
51688
|
+
} catch {
|
|
51689
|
+
return false;
|
|
51690
|
+
}
|
|
51691
|
+
}
|
|
51692
|
+
async function isDirectoryNonEmpty(destinationPath) {
|
|
51693
|
+
if (!external_fs_.existsSync(destinationPath)) return false;
|
|
51694
|
+
const stats = await external_fs_.promises.stat(destinationPath);
|
|
51695
|
+
if (!stats.isDirectory()) return true;
|
|
51696
|
+
const entries = await external_fs_.promises.readdir(destinationPath);
|
|
51697
|
+
return entries.length > 0;
|
|
51698
|
+
}
|
|
51699
|
+
async function validateCheckoutDestination(destinationPath, force = false) {
|
|
51700
|
+
const nonEmpty = await isDirectoryNonEmpty(destinationPath);
|
|
51701
|
+
if (nonEmpty && !force) throw new Error(`Destination '${destinationPath}' already exists and is not empty.`);
|
|
51702
|
+
const insideGit = await isPathInsideGitRepo(destinationPath);
|
|
51703
|
+
if (insideGit && !force) throw new Error(`Destination '${destinationPath}' is inside an existing Git repository.`);
|
|
51704
|
+
if (force && nonEmpty) logger_logger.warn(`--force set: destination '${destinationPath}' is non-empty and clone may still fail.`);
|
|
51705
|
+
if (force && insideGit) logger_logger.warn("--force set: cloning inside an existing Git repository.");
|
|
51706
|
+
}
|
|
51707
|
+
async function ensureCheckoutAuthToken(anvilUrl, username, deps = defaultCheckoutDeps) {
|
|
51708
|
+
try {
|
|
51709
|
+
return await deps.getValidAuthToken(anvilUrl, username);
|
|
51710
|
+
} catch (e) {
|
|
51711
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
51712
|
+
if (!interactive) throw errors_createAuthError.required(`Not logged in to ${anvilUrl}. Run 'anvil login ${anvilUrl}' and retry.`);
|
|
51713
|
+
const shouldLogin = await logger_logger.confirm(`Not logged in to ${anvilUrl}. Log in now?`, true);
|
|
51714
|
+
if (!shouldLogin) throw errors_createAuthError.required(`Not logged in to ${anvilUrl}. Run 'anvil login ${anvilUrl}' and retry.`);
|
|
51715
|
+
await deps.runInteractiveLoginFlow(anvilUrl);
|
|
51716
|
+
return deps.getValidAuthToken(anvilUrl, username);
|
|
51717
|
+
}
|
|
51718
|
+
}
|
|
51719
|
+
async function resolveCheckoutUsername(anvilUrl, explicitUsername, getAccounts = auth_getAccountsForUrl) {
|
|
51720
|
+
if (explicitUsername) return explicitUsername;
|
|
51721
|
+
const accounts = getAccounts(anvilUrl);
|
|
51722
|
+
if (0 === accounts.length) return;
|
|
51723
|
+
if (1 === accounts.length) {
|
|
51724
|
+
logger_logger.verbose(chalk_source.cyan("Auto-selected account: ") + chalk_source.bold(accounts[0]));
|
|
51725
|
+
return accounts[0];
|
|
51726
|
+
}
|
|
51727
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
51728
|
+
if (!interactive) throw new Error(`Multiple accounts found for ${anvilUrl}. Use --user <USERNAME> to choose one.`);
|
|
51729
|
+
const choices = accounts.map((acct)=>({
|
|
51730
|
+
name: acct,
|
|
51731
|
+
value: acct
|
|
51732
|
+
}));
|
|
51733
|
+
choices.push({
|
|
51734
|
+
name: "Cancel",
|
|
51735
|
+
value: null
|
|
51736
|
+
});
|
|
51737
|
+
return logger_logger.select(`Multiple accounts found for ${anvilUrl}. Which account should be used for checkout?`, choices, accounts[0]);
|
|
51738
|
+
}
|
|
51739
|
+
async function executeCheckout(options, deps = defaultCheckoutDeps) {
|
|
51740
|
+
const parsed = parseCheckoutInput(options.input);
|
|
51741
|
+
const anvilUrl = await resolveCheckoutUrl(options.url, parsed.detectedUrl);
|
|
51742
|
+
if (!anvilUrl) return void logger_logger.info("Checkout cancelled.");
|
|
51743
|
+
const resolvedUsername = await resolveCheckoutUsername(anvilUrl, options.user);
|
|
51744
|
+
if (null === resolvedUsername) return void logger_logger.info("Checkout cancelled.");
|
|
51745
|
+
const authToken = await ensureCheckoutAuthToken(anvilUrl, resolvedUsername, deps);
|
|
51746
|
+
let checkoutUsername = resolvedUsername;
|
|
51747
|
+
if (!checkoutUsername) {
|
|
51748
|
+
const inferredUsername = await resolveCheckoutUsername(anvilUrl, void 0);
|
|
51749
|
+
if (null === inferredUsername) return void logger_logger.info("Checkout cancelled.");
|
|
51750
|
+
if (!inferredUsername) throw new Error(`Could not determine account for ${anvilUrl}. Use --user <USERNAME> so checkout can bind repository credentials.`);
|
|
51751
|
+
checkoutUsername = inferredUsername;
|
|
51752
|
+
}
|
|
51753
|
+
const validation = await deps.validateAppId(parsed.appId, anvilUrl, checkoutUsername);
|
|
51754
|
+
if (!validation.valid) throw new Error(validation.error || `App '${parsed.appId}' is not accessible on ${anvilUrl}.`);
|
|
51755
|
+
const destinationDir = options.directory || getDefaultDestinationDirectory(parsed.appId, validation.app_name);
|
|
51756
|
+
const destinationPath = external_path_default().resolve(process.cwd(), destinationDir);
|
|
51757
|
+
const destinationDisplay = formatPathForDisplay(destinationPath);
|
|
51758
|
+
await validateCheckoutDestination(destinationPath, options.force);
|
|
51759
|
+
const cloneUrl = getGitPushUrl(parsed.appId, authToken, anvilUrl);
|
|
51760
|
+
logger_logger.progress("checkout", `Checking out app ${parsed.appId} from ${anvilUrl}`);
|
|
51761
|
+
logger_logger.info(chalk_source.gray(` Destination directory: ${destinationDisplay}`));
|
|
51762
|
+
await deps.clone(cloneUrl, destinationPath, {
|
|
51763
|
+
branch: options.branch,
|
|
51764
|
+
depth: options.depth,
|
|
51765
|
+
singleBranch: options.singleBranch,
|
|
51766
|
+
origin: options.origin,
|
|
51767
|
+
quiet: options.quiet,
|
|
51768
|
+
verbose: options.verbose
|
|
51769
|
+
});
|
|
51770
|
+
const remoteName = options.origin || "origin";
|
|
51771
|
+
try {
|
|
51772
|
+
await deps.hardenCheckoutGitAuth({
|
|
51773
|
+
repoPath: destinationPath,
|
|
51774
|
+
appId: parsed.appId,
|
|
51775
|
+
anvilUrl,
|
|
51776
|
+
username: checkoutUsername,
|
|
51777
|
+
remoteName
|
|
51778
|
+
});
|
|
51779
|
+
} catch (e) {
|
|
51780
|
+
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.`);
|
|
51781
|
+
}
|
|
51782
|
+
logger_logger.progressEnd("checkout", `Checked out ${parsed.appId} into ${destinationDisplay}`);
|
|
51783
|
+
const preferredEditor = String(getConfig("preferredEditor") || "").trim();
|
|
51784
|
+
const preferredEditorCommand = preferredEditor ? getPreferredEditorCommand(preferredEditor) : "";
|
|
51785
|
+
if (options.open) if (preferredEditorCommand) await openInPreferredEditor(preferredEditorCommand, destinationPath);
|
|
51786
|
+
else {
|
|
51787
|
+
await node_modules_open(destinationPath);
|
|
51788
|
+
logger_logger.info(chalk_source.gray(`Opened ${destinationDisplay}`));
|
|
51789
|
+
}
|
|
51790
|
+
if (preferredEditorCommand && !options.open) logger_logger.info(chalk_source.cyan(`Next: ${preferredEditorCommand} ${destinationDisplay}`));
|
|
51791
|
+
}
|
|
51792
|
+
async function openInPreferredEditor(editorCommand, destinationPath) {
|
|
51793
|
+
await new Promise((resolve, reject)=>{
|
|
51794
|
+
const child = (0, external_child_process_.spawn)(editorCommand, [
|
|
51795
|
+
destinationPath
|
|
51796
|
+
], {
|
|
51797
|
+
shell: true,
|
|
51798
|
+
stdio: "inherit"
|
|
51799
|
+
});
|
|
51800
|
+
child.on("error", reject);
|
|
51801
|
+
child.on("exit", (code)=>{
|
|
51802
|
+
if (0 === code || null === code) resolve();
|
|
51803
|
+
else reject(new Error(`Editor command '${editorCommand}' exited with code ${code}`));
|
|
51804
|
+
});
|
|
51805
|
+
});
|
|
51806
|
+
}
|
|
51807
|
+
function registerCheckoutCommand(program) {
|
|
51808
|
+
const checkoutCommand = program.command("checkout <input> [directory]").description("Check out an Anvil app locally from editor URL, git URL, or app ID").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("-q, --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").action(async (input, directory, options)=>{
|
|
51809
|
+
try {
|
|
51810
|
+
if ("number" == typeof options?.depth && (!Number.isFinite(options.depth) || options.depth <= 0)) throw new Error("--depth must be a positive integer");
|
|
51811
|
+
await executeCheckout({
|
|
51812
|
+
input,
|
|
51813
|
+
directory,
|
|
51814
|
+
open: options?.open,
|
|
51815
|
+
branch: options?.branch,
|
|
51816
|
+
depth: options?.depth,
|
|
51817
|
+
singleBranch: options?.singleBranch,
|
|
51818
|
+
origin: options?.origin,
|
|
51819
|
+
quiet: options?.quiet,
|
|
51820
|
+
verbose: options?.verbose,
|
|
51821
|
+
url: options?.url,
|
|
51822
|
+
user: options?.user,
|
|
51823
|
+
force: options?.force
|
|
51824
|
+
}, defaultCheckoutDeps);
|
|
51825
|
+
} catch (e) {
|
|
51826
|
+
logger_logger.error("Error: " + errors_getErrorMessage(e));
|
|
51827
|
+
process.exit(1);
|
|
51828
|
+
}
|
|
51829
|
+
});
|
|
51830
|
+
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");
|
|
51831
|
+
}
|
|
51832
|
+
const defaultDeps = {
|
|
51833
|
+
getValidAuthToken: auth_getValidAuthToken,
|
|
51834
|
+
getAccountsForUrl: auth_getAccountsForUrl,
|
|
51835
|
+
getAppAuthBinding: getAppAuthBinding,
|
|
51836
|
+
cwd: ()=>process.cwd()
|
|
51837
|
+
};
|
|
51838
|
+
function parseGitCredentialRequest(rawInput) {
|
|
51839
|
+
const out = {};
|
|
51840
|
+
const lines = rawInput.split(/\r?\n/);
|
|
51841
|
+
for (const line of lines){
|
|
51842
|
+
if (!line) continue;
|
|
51843
|
+
const eqIdx = line.indexOf("=");
|
|
51844
|
+
if (eqIdx <= 0) continue;
|
|
51845
|
+
const key = line.slice(0, eqIdx);
|
|
51846
|
+
const value = line.slice(eqIdx + 1);
|
|
51847
|
+
if ("protocol" === key || "host" === key || "path" === key || "username" === key) out[key] = value;
|
|
51848
|
+
}
|
|
51849
|
+
return out;
|
|
51850
|
+
}
|
|
51851
|
+
async function buildGitCredentialResponse(operation, request, deps = defaultDeps) {
|
|
51852
|
+
if ("get" !== operation) return null;
|
|
51853
|
+
if (!request.protocol || !request.host) return null;
|
|
51854
|
+
const appId = parseAppIdFromGitPath(request.path);
|
|
51855
|
+
if (!appId) return null;
|
|
51856
|
+
const requestUrl = normalizeAnvilUrl(`${request.protocol}://${request.host}`);
|
|
51857
|
+
const binding = await deps.getAppAuthBinding(deps.cwd(), appId);
|
|
51858
|
+
const anvilUrl = binding.url ? normalizeAnvilUrl(binding.url) : requestUrl;
|
|
51859
|
+
let username = binding.username;
|
|
51860
|
+
if (!username) {
|
|
51861
|
+
const accounts = deps.getAccountsForUrl(anvilUrl);
|
|
51862
|
+
if (1 === accounts.length) username = accounts[0];
|
|
51863
|
+
else if (accounts.length > 1) throw new Error(`Multiple accounts found for ${anvilUrl}; bind one with anvil checkout --user.`);
|
|
51864
|
+
else throw new Error(`No account found for ${anvilUrl}. Run 'anvil login ${anvilUrl}'.`);
|
|
51865
|
+
}
|
|
51866
|
+
const token = await deps.getValidAuthToken(anvilUrl, username);
|
|
51867
|
+
return {
|
|
51868
|
+
username: "git",
|
|
51869
|
+
password: token
|
|
51870
|
+
};
|
|
51871
|
+
}
|
|
51872
|
+
async function readStdin() {
|
|
51873
|
+
const chunks = [];
|
|
51874
|
+
for await (const chunk of process.stdin)chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
51875
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
51876
|
+
}
|
|
51877
|
+
function writeGitCredentialResponse(response) {
|
|
51878
|
+
if (!response) return;
|
|
51879
|
+
const lines = [];
|
|
51880
|
+
for (const [k, v] of Object.entries(response))lines.push(`${k}=${v}`);
|
|
51881
|
+
process.stdout.write(lines.join("\n") + "\n\n");
|
|
51882
|
+
}
|
|
51883
|
+
async function executeGitCredentialOperation(operation, deps = defaultDeps) {
|
|
51884
|
+
const raw = await readStdin();
|
|
51885
|
+
const request = parseGitCredentialRequest(raw);
|
|
51886
|
+
const response = await buildGitCredentialResponse(operation, request, deps);
|
|
51887
|
+
writeGitCredentialResponse(response);
|
|
51888
|
+
}
|
|
51889
|
+
function registerGitCredentialCommand(program) {
|
|
51890
|
+
program.command("git-credential <operation>", {
|
|
51891
|
+
hidden: true
|
|
51892
|
+
}).description("Internal helper command for Git HTTPS authentication").action(async (operation)=>{
|
|
51893
|
+
try {
|
|
51894
|
+
await executeGitCredentialOperation(operation);
|
|
51895
|
+
} catch {
|
|
51896
|
+
process.exit(1);
|
|
51897
|
+
}
|
|
51898
|
+
});
|
|
51260
51899
|
}
|
|
51261
51900
|
function registerLoginCommand(program) {
|
|
51262
51901
|
const loginCommand = program.command("login [anvil-server-url]").description("Log in to Anvil using OAuth").alias("l").action(async (anvilUrl)=>{
|
|
@@ -51265,93 +51904,8 @@ var __webpack_exports__ = {};
|
|
|
51265
51904
|
anvilUrl = normalizeAnvilUrl(anvilUrl.trim());
|
|
51266
51905
|
setConfig("anvilUrl", anvilUrl);
|
|
51267
51906
|
} else anvilUrl = resolveAnvilUrl();
|
|
51268
|
-
const
|
|
51269
|
-
|
|
51270
|
-
const state = external_crypto_.randomBytes(16).toString("hex");
|
|
51271
|
-
const server = external_http_.createServer();
|
|
51272
|
-
await new Promise((resolve)=>server.listen(0, "127.0.0.1", resolve));
|
|
51273
|
-
const address = server.address();
|
|
51274
|
-
if (!address || "string" == typeof address) throw new Error("No address");
|
|
51275
|
-
const port = address.port;
|
|
51276
|
-
const redirectUri = `http://127.0.0.1:${port}/oauth-callback`;
|
|
51277
|
-
const SCOPES = "apps:read apps:write user:read";
|
|
51278
|
-
const authUrl = new URL(`${anvilUrl}/oauth/authorize`);
|
|
51279
|
-
authUrl.searchParams.set("response_type", "code");
|
|
51280
|
-
authUrl.searchParams.set("client_id", "anvil-sync");
|
|
51281
|
-
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
51282
|
-
authUrl.searchParams.set("scope", SCOPES);
|
|
51283
|
-
authUrl.searchParams.set("state", state);
|
|
51284
|
-
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
51285
|
-
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
51286
|
-
const codePromise = new Promise((resolve, reject)=>{
|
|
51287
|
-
server.on("request", (req, res)=>{
|
|
51288
|
-
if (!req.url) return;
|
|
51289
|
-
const url = new URL(req.url, `http://127.0.0.1:${port}`);
|
|
51290
|
-
if ("/oauth-callback" !== url.pathname) return;
|
|
51291
|
-
const code = url.searchParams.get("code") || void 0;
|
|
51292
|
-
const error = url.searchParams.get("error") || void 0;
|
|
51293
|
-
const recvState = url.searchParams.get("state");
|
|
51294
|
-
if (!recvState || !code && !error) {
|
|
51295
|
-
res.statusCode = 400;
|
|
51296
|
-
res.end("Missing code, error or state");
|
|
51297
|
-
reject(new Error("Missing code/state/error"));
|
|
51298
|
-
server.close();
|
|
51299
|
-
return;
|
|
51300
|
-
}
|
|
51301
|
-
res.statusCode = 200;
|
|
51302
|
-
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
51303
|
-
if (code) res.end(successPage);
|
|
51304
|
-
else res.end(errorPage(error || "unknown"));
|
|
51305
|
-
resolve({
|
|
51306
|
-
code,
|
|
51307
|
-
error,
|
|
51308
|
-
recvState
|
|
51309
|
-
});
|
|
51310
|
-
server.closeAllConnections();
|
|
51311
|
-
server.close();
|
|
51312
|
-
});
|
|
51313
|
-
});
|
|
51314
|
-
logger_logger.info(chalk_source.blue("Link to your Anvil account to continue."));
|
|
51315
|
-
console.log();
|
|
51316
|
-
logger_logger.info(chalk_source.gray(` ${authUrl}`));
|
|
51317
|
-
console.log();
|
|
51318
|
-
const { code, error, recvState } = await waitForOAuthWithPrompt(authUrl, codePromise);
|
|
51319
|
-
if (recvState !== state) {
|
|
51320
|
-
logger_logger.error("Invalid state received from OAuth callback");
|
|
51321
|
-
process.exit(1);
|
|
51322
|
-
}
|
|
51323
|
-
if (error) {
|
|
51324
|
-
logger_logger.error(`Error received from OAuth callback: ${error}`);
|
|
51325
|
-
process.exit(1);
|
|
51326
|
-
}
|
|
51327
|
-
const tokenResponse = await fetch(`${anvilUrl}/oauth/token`, {
|
|
51328
|
-
method: "POST",
|
|
51329
|
-
headers: {
|
|
51330
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
51331
|
-
},
|
|
51332
|
-
body: new URLSearchParams({
|
|
51333
|
-
grant_type: "authorization_code",
|
|
51334
|
-
code: code,
|
|
51335
|
-
redirect_uri: redirectUri,
|
|
51336
|
-
client_id: "anvil-sync",
|
|
51337
|
-
code_verifier: codeVerifier
|
|
51338
|
-
})
|
|
51339
|
-
});
|
|
51340
|
-
if (tokenResponse.ok) {
|
|
51341
|
-
const tokenData = await tokenResponse.json();
|
|
51342
|
-
const result = await login(anvilUrl, {
|
|
51343
|
-
access_token: tokenData.access_token,
|
|
51344
|
-
refresh_token: tokenData.refresh_token,
|
|
51345
|
-
expires_in: tokenData.expires_in,
|
|
51346
|
-
scope: tokenData.scope
|
|
51347
|
-
});
|
|
51348
|
-
logger_logger.success("Logged in as " + chalk_source.bold(result.email));
|
|
51349
|
-
} else {
|
|
51350
|
-
logger_logger.error("Failed to exchange authorization code for token.");
|
|
51351
|
-
const errorText = await tokenResponse.text();
|
|
51352
|
-
logger_logger.error(` Server response: ${errorText}`);
|
|
51353
|
-
process.exit(1);
|
|
51354
|
-
}
|
|
51907
|
+
const result = await runInteractiveLoginFlow(anvilUrl);
|
|
51908
|
+
logger_logger.success("Logged in as " + chalk_source.bold(result.email));
|
|
51355
51909
|
} catch (e) {
|
|
51356
51910
|
logger_logger.error("Error: " + e.message);
|
|
51357
51911
|
process.exit(1);
|
|
@@ -51359,6 +51913,13 @@ var __webpack_exports__ = {};
|
|
|
51359
51913
|
});
|
|
51360
51914
|
loginCommand.addHelpText("after", "\n" + chalk_source.bold("Examples:") + "\n anvil login Log in to default Anvil server\n anvil login anvil.works Log in to anvil.works\n anvil login localhost Log in to local Anvil server\n");
|
|
51361
51915
|
}
|
|
51916
|
+
function getTotalLoggedInAccounts(urls) {
|
|
51917
|
+
return urls.reduce((total, url)=>total + auth_getAccountsForUrl(url).length, 0);
|
|
51918
|
+
}
|
|
51919
|
+
function formatMultiAccountLogoutPrompt(totalAccounts, urlCount) {
|
|
51920
|
+
if (urlCount <= 1) return `You have ${totalAccounts} logged-in accounts. What would you like to do?`;
|
|
51921
|
+
return `You have ${totalAccounts} logged-in accounts across ${urlCount} URLs. What would you like to do?`;
|
|
51922
|
+
}
|
|
51362
51923
|
function registerLogoutCommand(program) {
|
|
51363
51924
|
const logoutCommand = program.command("logout [anvil-server-url]").description("Log out from Anvil (optionally specify URL to logout from specific installation)").option("-u, --url <ANVIL_URL>", "Specify Anvil server URL to logout from").option("-U, --user <USERNAME>", "Specify which user account to logout from").alias("lo").action(async (anvilUrl, options)=>{
|
|
51364
51925
|
try {
|
|
@@ -51372,8 +51933,9 @@ var __webpack_exports__ = {};
|
|
|
51372
51933
|
else logger_logger.info(result.message || `Not logged in to ${normalized}. No action needed.`);
|
|
51373
51934
|
} else {
|
|
51374
51935
|
const availableUrls = getAvailableAnvilUrls();
|
|
51375
|
-
|
|
51376
|
-
|
|
51936
|
+
const totalAccounts = getTotalLoggedInAccounts(availableUrls);
|
|
51937
|
+
if (0 === totalAccounts) logger_logger.warn("No logged-in accounts found.");
|
|
51938
|
+
else if (1 === totalAccounts) {
|
|
51377
51939
|
const result = await logout();
|
|
51378
51940
|
if (result.loggedOut) logger_logger.success("Logged out.");
|
|
51379
51941
|
} else {
|
|
@@ -51391,7 +51953,7 @@ var __webpack_exports__ = {};
|
|
|
51391
51953
|
value: "cancel"
|
|
51392
51954
|
}
|
|
51393
51955
|
];
|
|
51394
|
-
const action = await logger_logger.select(
|
|
51956
|
+
const action = await logger_logger.select(formatMultiAccountLogoutPrompt(totalAccounts, availableUrls.length), choices, "one");
|
|
51395
51957
|
if ("cancel" === action) return void logger_logger.info("Logout cancelled.");
|
|
51396
51958
|
if ("all" === action) {
|
|
51397
51959
|
const result = await logout();
|
|
@@ -51447,6 +52009,14 @@ var __webpack_exports__ = {};
|
|
|
51447
52009
|
});
|
|
51448
52010
|
logoutCommand.addHelpText("after", "\n" + chalk_source.bold("Examples:") + "\n anvil logout Logout from all accounts (or prompt if multiple)\n anvil logout anvil.works Logout from anvil.works\n anvil logout -u anvil.works Logout from anvil.works (using option)\n anvil logout -u anvil.works -U user@example.com Logout specific user\n");
|
|
51449
52011
|
}
|
|
52012
|
+
const defaultConfigResetDeps = {
|
|
52013
|
+
logout: logout,
|
|
52014
|
+
resetConfig: resetConfig
|
|
52015
|
+
};
|
|
52016
|
+
async function performConfigReset(deps = defaultConfigResetDeps) {
|
|
52017
|
+
await deps.logout();
|
|
52018
|
+
deps.resetConfig();
|
|
52019
|
+
}
|
|
51450
52020
|
function registerConfigCommand(program) {
|
|
51451
52021
|
const configCommand = program.command("config").description("Manage configuration").alias("c");
|
|
51452
52022
|
configCommand.command("get <key>").description("Get a configuration value").action(async (key)=>{
|
|
@@ -51465,8 +52035,7 @@ var __webpack_exports__ = {};
|
|
|
51465
52035
|
});
|
|
51466
52036
|
configCommand.command("set <key> <value>").description("Set a configuration value").action(async (key, value)=>{
|
|
51467
52037
|
try {
|
|
51468
|
-
|
|
51469
|
-
if ("anvilUrl" === key && "string" == typeof typedValue) typedValue = typedValue.trim().replace(/\/+$/, "");
|
|
52038
|
+
const typedValue = parseConfigSetValue(key, value);
|
|
51470
52039
|
setConfig(key, typedValue);
|
|
51471
52040
|
logger_logger.success(`Set ${key} = ${typedValue}`);
|
|
51472
52041
|
} catch (error) {
|
|
@@ -51490,17 +52059,21 @@ var __webpack_exports__ = {};
|
|
|
51490
52059
|
let totalAccounts = 0;
|
|
51491
52060
|
for (const url of urls){
|
|
51492
52061
|
const urlData = authTokens[url];
|
|
51493
|
-
if (urlData && "object" == typeof urlData && !Array.isArray(urlData))
|
|
52062
|
+
if (urlData && "object" == typeof urlData && !Array.isArray(urlData)) {
|
|
52063
|
+
const usernames = Object.keys(urlData);
|
|
52064
|
+
totalAccounts += usernames.filter((username)=>hasTokensForUrl(url, username)).length;
|
|
52065
|
+
}
|
|
51494
52066
|
}
|
|
51495
|
-
console.log(chalk_source.cyan("authTokens") + ` = ${chalk_source.gray(`${totalAccounts} account(s) across ${urls.length} URL(s)`)}`);
|
|
52067
|
+
console.log(chalk_source.cyan("authTokens") + ` = ${chalk_source.gray(`${totalAccounts} logged-in account(s) across ${urls.length} URL(s)`)}`);
|
|
51496
52068
|
for (const url of urls){
|
|
51497
52069
|
const urlData = authTokens[url];
|
|
51498
52070
|
if (urlData && "object" == typeof urlData && !Array.isArray(urlData)) {
|
|
51499
52071
|
const usernames = Object.keys(urlData).sort();
|
|
51500
52072
|
if (usernames.length > 0) for (const username of usernames){
|
|
51501
52073
|
const accountData = urlData[username];
|
|
51502
|
-
const
|
|
51503
|
-
const
|
|
52074
|
+
const hasConfigTokens = !!(accountData?.authToken || accountData?.refreshToken);
|
|
52075
|
+
const hasAnyTokens = hasTokensForUrl(url, username);
|
|
52076
|
+
const status = hasConfigTokens ? chalk_source.green("✓ logged in (config)") : hasAnyTokens ? chalk_source.green("✓ logged in (keychain)") : chalk_source.gray("(no tokens)");
|
|
51504
52077
|
console.log(chalk_source.gray(` ${url}: ${username} ${status}`));
|
|
51505
52078
|
}
|
|
51506
52079
|
}
|
|
@@ -51522,7 +52095,7 @@ var __webpack_exports__ = {};
|
|
|
51522
52095
|
});
|
|
51523
52096
|
configCommand.command("reset").description("Reset configuration to defaults").action(async ()=>{
|
|
51524
52097
|
try {
|
|
51525
|
-
|
|
52098
|
+
await performConfigReset();
|
|
51526
52099
|
logger_logger.success("Configuration reset to defaults.");
|
|
51527
52100
|
} catch (e) {
|
|
51528
52101
|
logger_logger.error("Error: " + e.message);
|
|
@@ -51538,6 +52111,136 @@ var __webpack_exports__ = {};
|
|
|
51538
52111
|
console.log();
|
|
51539
52112
|
});
|
|
51540
52113
|
}
|
|
52114
|
+
const onboard_defaultDeps = {
|
|
52115
|
+
getLatestVersion: getLatestVersion,
|
|
52116
|
+
getAccountsForUrl: auth_getAccountsForUrl,
|
|
52117
|
+
runInteractiveLoginFlow: runInteractiveLoginFlow,
|
|
52118
|
+
executeCheckout: executeCheckout
|
|
52119
|
+
};
|
|
52120
|
+
const DEFAULT_ANVIL_SERVER_URL_PROMPT = "Default Anvil server URL";
|
|
52121
|
+
async function getOnboardVersionStatus(currentVersion, latestVersionGetter = getLatestVersion) {
|
|
52122
|
+
const latestVersion = await latestVersionGetter();
|
|
52123
|
+
if (!latestVersion) return {
|
|
52124
|
+
currentVersion,
|
|
52125
|
+
latestVersion: null,
|
|
52126
|
+
isOutdated: null
|
|
52127
|
+
};
|
|
52128
|
+
if (!semver_default().valid(currentVersion) || !semver_default().valid(latestVersion)) return {
|
|
52129
|
+
currentVersion,
|
|
52130
|
+
latestVersion,
|
|
52131
|
+
isOutdated: null
|
|
52132
|
+
};
|
|
52133
|
+
return {
|
|
52134
|
+
currentVersion,
|
|
52135
|
+
latestVersion,
|
|
52136
|
+
isOutdated: semver_default().lt(currentVersion, latestVersion)
|
|
52137
|
+
};
|
|
52138
|
+
}
|
|
52139
|
+
function formatLoggedInAccounts(accounts) {
|
|
52140
|
+
if (0 === accounts.length) return "no account";
|
|
52141
|
+
if (1 === accounts.length) return accounts[0];
|
|
52142
|
+
return `${accounts.length} accounts`;
|
|
52143
|
+
}
|
|
52144
|
+
function isInteractiveSession() {
|
|
52145
|
+
return !!(process.stdin.isTTY && process.stdout.isTTY);
|
|
52146
|
+
}
|
|
52147
|
+
async function runOnboardFlow(version, deps = onboard_defaultDeps) {
|
|
52148
|
+
if (!isInteractiveSession()) throw new Error("`anvil onboard` is interactive and requires a TTY terminal.");
|
|
52149
|
+
console.log();
|
|
52150
|
+
logger_logger.info(chalk_source.cyan.bold("Anvil Onboarding"));
|
|
52151
|
+
console.log();
|
|
52152
|
+
logger_logger.progress("onboard-version", "Checking CLI version...");
|
|
52153
|
+
const versionStatus = await getOnboardVersionStatus(version, deps.getLatestVersion);
|
|
52154
|
+
logger_logger.progressEnd("onboard-version");
|
|
52155
|
+
logger_logger.info(chalk_source.cyan("CLI version: ") + chalk_source.bold(versionStatus.currentVersion));
|
|
52156
|
+
if (versionStatus.latestVersion) {
|
|
52157
|
+
logger_logger.info(chalk_source.cyan("Latest version: ") + chalk_source.bold(versionStatus.latestVersion));
|
|
52158
|
+
if (versionStatus.isOutdated) {
|
|
52159
|
+
logger_logger.warn("Your anvil-cli is out of date.");
|
|
52160
|
+
logger_logger.info(chalk_source.gray("Run `anvil update` to see update commands."));
|
|
52161
|
+
const continueOnOldVersion = await logger_logger.confirm("Continue onboarding anyway?", true);
|
|
52162
|
+
if (!continueOnOldVersion) return void logger_logger.info("Onboarding cancelled.");
|
|
52163
|
+
}
|
|
52164
|
+
} else logger_logger.warn("Could not determine latest published version.");
|
|
52165
|
+
console.log();
|
|
52166
|
+
const currentUrl = resolveAnvilUrl();
|
|
52167
|
+
logger_logger.info(chalk_source.gray("Leave this blank unless you're using a dedicated enterprise/on-prem Anvil installation."));
|
|
52168
|
+
const urlAnswer = await logger_logger.prompt([
|
|
52169
|
+
{
|
|
52170
|
+
type: "input",
|
|
52171
|
+
name: "anvilUrl",
|
|
52172
|
+
message: DEFAULT_ANVIL_SERVER_URL_PROMPT,
|
|
52173
|
+
default: currentUrl
|
|
52174
|
+
}
|
|
52175
|
+
]);
|
|
52176
|
+
const configuredAnvilUrl = parseConfigSetValue("anvilUrl", urlAnswer.anvilUrl);
|
|
52177
|
+
setConfig("anvilUrl", configuredAnvilUrl);
|
|
52178
|
+
logger_logger.success(`Set default Anvil server URL (anvilUrl) = ${configuredAnvilUrl}`);
|
|
52179
|
+
const currentEditor = String(getConfig("preferredEditor") || "").trim().toLowerCase();
|
|
52180
|
+
const editorChoices = [
|
|
52181
|
+
{
|
|
52182
|
+
name: "None",
|
|
52183
|
+
value: ""
|
|
52184
|
+
},
|
|
52185
|
+
...preferredEditors.map((editor)=>({
|
|
52186
|
+
name: editor,
|
|
52187
|
+
value: editor
|
|
52188
|
+
}))
|
|
52189
|
+
];
|
|
52190
|
+
const selectedEditor = await logger_logger.select("Preferred editor", editorChoices, editorChoices.some((choice)=>choice.value === currentEditor) ? currentEditor : "");
|
|
52191
|
+
setConfig("preferredEditor", selectedEditor);
|
|
52192
|
+
if (selectedEditor) logger_logger.success(`Set preferredEditor = ${selectedEditor}`);
|
|
52193
|
+
else logger_logger.info("Left preferredEditor unset.");
|
|
52194
|
+
const currentVerbose = !!getConfig("verbose");
|
|
52195
|
+
const enableVerbose = await logger_logger.confirm("Enable verbose logging?", currentVerbose);
|
|
52196
|
+
setConfig("verbose", enableVerbose);
|
|
52197
|
+
logger_logger.success(`Set verbose = ${enableVerbose}`);
|
|
52198
|
+
console.log();
|
|
52199
|
+
const existingAccounts = deps.getAccountsForUrl(configuredAnvilUrl);
|
|
52200
|
+
if (existingAccounts.length > 0) logger_logger.success(`Already logged in to ${configuredAnvilUrl} as ${formatLoggedInAccounts(existingAccounts)}.`);
|
|
52201
|
+
else {
|
|
52202
|
+
const shouldLogin = await logger_logger.confirm(`You're not logged in to ${configuredAnvilUrl}. Log in now?`, true);
|
|
52203
|
+
if (shouldLogin) {
|
|
52204
|
+
const loginResult = await deps.runInteractiveLoginFlow(configuredAnvilUrl);
|
|
52205
|
+
logger_logger.success("Logged in as " + chalk_source.bold(loginResult.email));
|
|
52206
|
+
} else logger_logger.warn("Skipping login.");
|
|
52207
|
+
}
|
|
52208
|
+
console.log();
|
|
52209
|
+
const shouldCheckout = await logger_logger.confirm("Check out an app now?", true);
|
|
52210
|
+
if (shouldCheckout) {
|
|
52211
|
+
const checkoutAnswer = await logger_logger.prompt([
|
|
52212
|
+
{
|
|
52213
|
+
type: "input",
|
|
52214
|
+
name: "input",
|
|
52215
|
+
message: "App ID or app URL to checkout"
|
|
52216
|
+
}
|
|
52217
|
+
]);
|
|
52218
|
+
const checkoutInput = checkoutAnswer.input.trim();
|
|
52219
|
+
if (checkoutInput) {
|
|
52220
|
+
const preferredEditorCommand = getPreferredEditorCommand(selectedEditor);
|
|
52221
|
+
let openAfterCheckout = false;
|
|
52222
|
+
if (preferredEditorCommand) openAfterCheckout = await logger_logger.confirm(`Open checked out app in ${preferredEditorCommand} after checkout?`, true);
|
|
52223
|
+
else logger_logger.info("No preferred editor is configured, so open-in-editor is skipped.");
|
|
52224
|
+
await deps.executeCheckout({
|
|
52225
|
+
input: checkoutInput,
|
|
52226
|
+
url: configuredAnvilUrl,
|
|
52227
|
+
open: openAfterCheckout
|
|
52228
|
+
});
|
|
52229
|
+
} else logger_logger.info("No app provided; skipping checkout.");
|
|
52230
|
+
} else logger_logger.info("Skipping checkout.");
|
|
52231
|
+
console.log();
|
|
52232
|
+
logger_logger.success("Onboarding complete.");
|
|
52233
|
+
}
|
|
52234
|
+
function registerOnboardCommand(program, version) {
|
|
52235
|
+
program.command("onboard").description("Guided setup for configuration, login, and optional app checkout").action(async ()=>{
|
|
52236
|
+
try {
|
|
52237
|
+
await runOnboardFlow(version, onboard_defaultDeps);
|
|
52238
|
+
} catch (e) {
|
|
52239
|
+
logger_logger.error("Error: " + errors_getErrorMessage(e));
|
|
52240
|
+
process.exit(1);
|
|
52241
|
+
}
|
|
52242
|
+
});
|
|
52243
|
+
}
|
|
51541
52244
|
const packageJson = JSON.parse((0, external_fs_.readFileSync)((0, external_path_namespaceObject.join)(__dirname, "../package.json"), "utf-8"));
|
|
51542
52245
|
const VERSION = packageJson.version;
|
|
51543
52246
|
setLogger(new CLILogger({
|
|
@@ -51601,14 +52304,17 @@ var __webpack_exports__ = {};
|
|
|
51601
52304
|
});
|
|
51602
52305
|
if (!opts.json) {
|
|
51603
52306
|
const commandName = actionCommand?.name();
|
|
51604
|
-
if ("update" !== commandName) checkVersionAndWarn();
|
|
52307
|
+
if ("update" !== commandName && "git-credential" !== commandName) checkVersionAndWarn();
|
|
51605
52308
|
}
|
|
51606
52309
|
});
|
|
51607
52310
|
const cli_watchCommand = registerWatchCommand(cli_program);
|
|
52311
|
+
registerCheckoutCommand(cli_program);
|
|
52312
|
+
registerGitCredentialCommand(cli_program);
|
|
51608
52313
|
registerLoginCommand(cli_program);
|
|
51609
52314
|
registerLogoutCommand(cli_program);
|
|
51610
52315
|
registerConfigCommand(cli_program);
|
|
51611
52316
|
registerVersionCommand(cli_program, VERSION);
|
|
52317
|
+
registerOnboardCommand(cli_program, VERSION);
|
|
51612
52318
|
cli_program.command("update").description("Update anvil to the latest version").alias("u").action(async ()=>{
|
|
51613
52319
|
await handleUpdateCommand();
|
|
51614
52320
|
});
|