@anvil-works/anvil-cli 0.4.3 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -120
- package/dist/cli.js +727 -104
- package/dist/index.js +12 -5
- package/package.json +3 -2
- package/scripts/install/install.cmd +26 -0
- package/scripts/install/install.ps1 +180 -0
- package/scripts/install/install.sh +151 -0
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,6 +50002,121 @@ var __webpack_exports__ = {};
|
|
|
49956
50002
|
session.hasUncommittedChanges = hasUncommittedChanges;
|
|
49957
50003
|
return session;
|
|
49958
50004
|
}
|
|
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
|
+
}
|
|
49959
50120
|
function decideFallbackUrl(explicitUrl, availableUrls = getAvailableAnvilUrls()) {
|
|
49960
50121
|
if (explicitUrl) return {
|
|
49961
50122
|
source: "explicit",
|
|
@@ -50546,14 +50707,24 @@ var __webpack_exports__ = {};
|
|
|
50546
50707
|
let anvilUrl;
|
|
50547
50708
|
let username = explicitUsername;
|
|
50548
50709
|
let fallbackUrl;
|
|
50710
|
+
let shouldPersistUsernameBinding = false;
|
|
50549
50711
|
if (explicitAppId) {
|
|
50550
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
|
+
}
|
|
50551
50722
|
const remoteInfo = lookupRemoteInfoForAppId(explicitAppId, detectedFromAllRemotes);
|
|
50552
|
-
if (remoteInfo.detectedUrl && !explicitUrl) {
|
|
50723
|
+
if (remoteInfo.detectedUrl && !explicitUrl && !anvilUrl) {
|
|
50553
50724
|
anvilUrl = normalizeAnvilUrl(remoteInfo.detectedUrl);
|
|
50554
50725
|
logger_logger.verbose(chalk_source.cyan("Resolved URL from remote for app ID: ") + chalk_source.bold(anvilUrl));
|
|
50555
50726
|
}
|
|
50556
|
-
if (remoteInfo.detectedUsername && !explicitUsername) {
|
|
50727
|
+
if (remoteInfo.detectedUsername && !explicitUsername && !username) {
|
|
50557
50728
|
username = remoteInfo.detectedUsername;
|
|
50558
50729
|
logger_logger.verbose(chalk_source.cyan("Resolved username from remote for app ID: ") + chalk_source.bold(username));
|
|
50559
50730
|
}
|
|
@@ -50608,6 +50779,16 @@ var __webpack_exports__ = {};
|
|
|
50608
50779
|
if (selected.detectedUsername && !username) username = selected.detectedUsername;
|
|
50609
50780
|
}
|
|
50610
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;
|
|
50611
50792
|
if (explicitUrl) anvilUrl = normalizeAnvilUrl(explicitUrl);
|
|
50612
50793
|
else if (!anvilUrl) if (fallbackUrl) anvilUrl = fallbackUrl;
|
|
50613
50794
|
else {
|
|
@@ -50627,6 +50808,13 @@ var __webpack_exports__ = {};
|
|
|
50627
50808
|
process.exit(0);
|
|
50628
50809
|
}
|
|
50629
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."));
|
|
50817
|
+
}
|
|
50630
50818
|
if (username) logger_logger.verbose(chalk_source.cyan("Using account: ") + chalk_source.bold(username));
|
|
50631
50819
|
if (!hasTokensForUrl(anvilUrl, username)) {
|
|
50632
50820
|
if (username) logger_logger.error(`Not logged in to ${anvilUrl} as ${username}`);
|
|
@@ -50685,7 +50873,6 @@ var __webpack_exports__ = {};
|
|
|
50685
50873
|
});
|
|
50686
50874
|
return watchCommand;
|
|
50687
50875
|
}
|
|
50688
|
-
var external_http_ = __webpack_require__("http");
|
|
50689
50876
|
const external_node_url_namespaceObject = require("node:url");
|
|
50690
50877
|
var external_node_child_process_ = __webpack_require__("node:child_process");
|
|
50691
50878
|
let isDockerCached;
|
|
@@ -51148,6 +51335,7 @@ var __webpack_exports__ = {};
|
|
|
51148
51335
|
defineLazyProperty(open_apps, 'browser', ()=>'browser');
|
|
51149
51336
|
defineLazyProperty(open_apps, 'browserPrivate', ()=>'browserPrivate');
|
|
51150
51337
|
const node_modules_open = open_open;
|
|
51338
|
+
var external_http_ = __webpack_require__("http");
|
|
51151
51339
|
const successPage = `<!DOCTYPE html>
|
|
51152
51340
|
<html lang="en">
|
|
51153
51341
|
<head>
|
|
@@ -51305,6 +51493,9 @@ var __webpack_exports__ = {};
|
|
|
51305
51493
|
</body>
|
|
51306
51494
|
</html>`;
|
|
51307
51495
|
}
|
|
51496
|
+
function oauth_login_base64url(buf) {
|
|
51497
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
51498
|
+
}
|
|
51308
51499
|
async function waitForOAuthWithPrompt(authUrl, oauthPromise) {
|
|
51309
51500
|
const rl = external_readline_namespaceObject.createInterface({
|
|
51310
51501
|
input: process.stdin,
|
|
@@ -51329,8 +51520,382 @@ var __webpack_exports__ = {};
|
|
|
51329
51520
|
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
51330
51521
|
return result.oauth;
|
|
51331
51522
|
}
|
|
51332
|
-
function
|
|
51333
|
-
|
|
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
|
+
});
|
|
51334
51899
|
}
|
|
51335
51900
|
function registerLoginCommand(program) {
|
|
51336
51901
|
const loginCommand = program.command("login [anvil-server-url]").description("Log in to Anvil using OAuth").alias("l").action(async (anvilUrl)=>{
|
|
@@ -51339,93 +51904,8 @@ var __webpack_exports__ = {};
|
|
|
51339
51904
|
anvilUrl = normalizeAnvilUrl(anvilUrl.trim());
|
|
51340
51905
|
setConfig("anvilUrl", anvilUrl);
|
|
51341
51906
|
} else anvilUrl = resolveAnvilUrl();
|
|
51342
|
-
const
|
|
51343
|
-
|
|
51344
|
-
const state = external_crypto_.randomBytes(16).toString("hex");
|
|
51345
|
-
const server = external_http_.createServer();
|
|
51346
|
-
await new Promise((resolve)=>server.listen(0, "127.0.0.1", resolve));
|
|
51347
|
-
const address = server.address();
|
|
51348
|
-
if (!address || "string" == typeof address) throw new Error("No address");
|
|
51349
|
-
const port = address.port;
|
|
51350
|
-
const redirectUri = `http://127.0.0.1:${port}/oauth-callback`;
|
|
51351
|
-
const SCOPES = "apps:read apps:write user:read";
|
|
51352
|
-
const authUrl = new URL(`${anvilUrl}/oauth/authorize`);
|
|
51353
|
-
authUrl.searchParams.set("response_type", "code");
|
|
51354
|
-
authUrl.searchParams.set("client_id", "anvil-sync");
|
|
51355
|
-
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
51356
|
-
authUrl.searchParams.set("scope", SCOPES);
|
|
51357
|
-
authUrl.searchParams.set("state", state);
|
|
51358
|
-
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
51359
|
-
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
51360
|
-
const codePromise = new Promise((resolve, reject)=>{
|
|
51361
|
-
server.on("request", (req, res)=>{
|
|
51362
|
-
if (!req.url) return;
|
|
51363
|
-
const url = new URL(req.url, `http://127.0.0.1:${port}`);
|
|
51364
|
-
if ("/oauth-callback" !== url.pathname) return;
|
|
51365
|
-
const code = url.searchParams.get("code") || void 0;
|
|
51366
|
-
const error = url.searchParams.get("error") || void 0;
|
|
51367
|
-
const recvState = url.searchParams.get("state");
|
|
51368
|
-
if (!recvState || !code && !error) {
|
|
51369
|
-
res.statusCode = 400;
|
|
51370
|
-
res.end("Missing code, error or state");
|
|
51371
|
-
reject(new Error("Missing code/state/error"));
|
|
51372
|
-
server.close();
|
|
51373
|
-
return;
|
|
51374
|
-
}
|
|
51375
|
-
res.statusCode = 200;
|
|
51376
|
-
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
51377
|
-
if (code) res.end(successPage);
|
|
51378
|
-
else res.end(errorPage(error || "unknown"));
|
|
51379
|
-
resolve({
|
|
51380
|
-
code,
|
|
51381
|
-
error,
|
|
51382
|
-
recvState
|
|
51383
|
-
});
|
|
51384
|
-
server.closeAllConnections();
|
|
51385
|
-
server.close();
|
|
51386
|
-
});
|
|
51387
|
-
});
|
|
51388
|
-
logger_logger.info(chalk_source.blue("Link to your Anvil account to continue."));
|
|
51389
|
-
console.log();
|
|
51390
|
-
logger_logger.info(chalk_source.gray(` ${authUrl}`));
|
|
51391
|
-
console.log();
|
|
51392
|
-
const { code, error, recvState } = await waitForOAuthWithPrompt(authUrl, codePromise);
|
|
51393
|
-
if (recvState !== state) {
|
|
51394
|
-
logger_logger.error("Invalid state received from OAuth callback");
|
|
51395
|
-
process.exit(1);
|
|
51396
|
-
}
|
|
51397
|
-
if (error) {
|
|
51398
|
-
logger_logger.error(`Error received from OAuth callback: ${error}`);
|
|
51399
|
-
process.exit(1);
|
|
51400
|
-
}
|
|
51401
|
-
const tokenResponse = await fetch(`${anvilUrl}/oauth/token`, {
|
|
51402
|
-
method: "POST",
|
|
51403
|
-
headers: {
|
|
51404
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
51405
|
-
},
|
|
51406
|
-
body: new URLSearchParams({
|
|
51407
|
-
grant_type: "authorization_code",
|
|
51408
|
-
code: code,
|
|
51409
|
-
redirect_uri: redirectUri,
|
|
51410
|
-
client_id: "anvil-sync",
|
|
51411
|
-
code_verifier: codeVerifier
|
|
51412
|
-
})
|
|
51413
|
-
});
|
|
51414
|
-
if (tokenResponse.ok) {
|
|
51415
|
-
const tokenData = await tokenResponse.json();
|
|
51416
|
-
const result = await login(anvilUrl, {
|
|
51417
|
-
access_token: tokenData.access_token,
|
|
51418
|
-
refresh_token: tokenData.refresh_token,
|
|
51419
|
-
expires_in: tokenData.expires_in,
|
|
51420
|
-
scope: tokenData.scope
|
|
51421
|
-
});
|
|
51422
|
-
logger_logger.success("Logged in as " + chalk_source.bold(result.email));
|
|
51423
|
-
} else {
|
|
51424
|
-
logger_logger.error("Failed to exchange authorization code for token.");
|
|
51425
|
-
const errorText = await tokenResponse.text();
|
|
51426
|
-
logger_logger.error(` Server response: ${errorText}`);
|
|
51427
|
-
process.exit(1);
|
|
51428
|
-
}
|
|
51907
|
+
const result = await runInteractiveLoginFlow(anvilUrl);
|
|
51908
|
+
logger_logger.success("Logged in as " + chalk_source.bold(result.email));
|
|
51429
51909
|
} catch (e) {
|
|
51430
51910
|
logger_logger.error("Error: " + e.message);
|
|
51431
51911
|
process.exit(1);
|
|
@@ -51529,6 +52009,14 @@ var __webpack_exports__ = {};
|
|
|
51529
52009
|
});
|
|
51530
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");
|
|
51531
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
|
+
}
|
|
51532
52020
|
function registerConfigCommand(program) {
|
|
51533
52021
|
const configCommand = program.command("config").description("Manage configuration").alias("c");
|
|
51534
52022
|
configCommand.command("get <key>").description("Get a configuration value").action(async (key)=>{
|
|
@@ -51547,8 +52035,7 @@ var __webpack_exports__ = {};
|
|
|
51547
52035
|
});
|
|
51548
52036
|
configCommand.command("set <key> <value>").description("Set a configuration value").action(async (key, value)=>{
|
|
51549
52037
|
try {
|
|
51550
|
-
|
|
51551
|
-
if ("anvilUrl" === key && "string" == typeof typedValue) typedValue = typedValue.trim().replace(/\/+$/, "");
|
|
52038
|
+
const typedValue = parseConfigSetValue(key, value);
|
|
51552
52039
|
setConfig(key, typedValue);
|
|
51553
52040
|
logger_logger.success(`Set ${key} = ${typedValue}`);
|
|
51554
52041
|
} catch (error) {
|
|
@@ -51608,7 +52095,7 @@ var __webpack_exports__ = {};
|
|
|
51608
52095
|
});
|
|
51609
52096
|
configCommand.command("reset").description("Reset configuration to defaults").action(async ()=>{
|
|
51610
52097
|
try {
|
|
51611
|
-
|
|
52098
|
+
await performConfigReset();
|
|
51612
52099
|
logger_logger.success("Configuration reset to defaults.");
|
|
51613
52100
|
} catch (e) {
|
|
51614
52101
|
logger_logger.error("Error: " + e.message);
|
|
@@ -51624,6 +52111,136 @@ var __webpack_exports__ = {};
|
|
|
51624
52111
|
console.log();
|
|
51625
52112
|
});
|
|
51626
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
|
+
}
|
|
51627
52244
|
const packageJson = JSON.parse((0, external_fs_.readFileSync)((0, external_path_namespaceObject.join)(__dirname, "../package.json"), "utf-8"));
|
|
51628
52245
|
const VERSION = packageJson.version;
|
|
51629
52246
|
setLogger(new CLILogger({
|
|
@@ -51670,9 +52287,12 @@ var __webpack_exports__ = {};
|
|
|
51670
52287
|
console.log();
|
|
51671
52288
|
console.log(chalk_source.cyan("To update, run one of the following commands:"));
|
|
51672
52289
|
console.log();
|
|
51673
|
-
|
|
51674
|
-
|
|
51675
|
-
|
|
52290
|
+
if ("win32" === process.platform) {
|
|
52291
|
+
console.log(chalk_source.gray(" PowerShell: ") + chalk_source.white("irm https://anvil.works/install/anvil-cli/install.ps1 | iex"));
|
|
52292
|
+
console.log(chalk_source.gray(" CMD: ") + chalk_source.white("curl -fsSL https://anvil.works/install/anvil-cli/install.cmd -o install.cmd && install.cmd && del install.cmd"));
|
|
52293
|
+
} else console.log(chalk_source.gray(" macOS/Linux: ") + chalk_source.white("curl -fsSL https://anvil.works/install/anvil-cli/install.sh | sh"));
|
|
52294
|
+
console.log(chalk_source.gray(" npm: ") + chalk_source.white("npm update -g @anvil-works/anvil-cli"));
|
|
52295
|
+
console.log(chalk_source.gray(" pnpm: ") + chalk_source.white("pnpm update -g @anvil-works/anvil-cli"));
|
|
51676
52296
|
console.log();
|
|
51677
52297
|
} catch (e) {
|
|
51678
52298
|
logger_logger.error("Error: " + e.message);
|
|
@@ -51687,14 +52307,17 @@ var __webpack_exports__ = {};
|
|
|
51687
52307
|
});
|
|
51688
52308
|
if (!opts.json) {
|
|
51689
52309
|
const commandName = actionCommand?.name();
|
|
51690
|
-
if ("update" !== commandName) checkVersionAndWarn();
|
|
52310
|
+
if ("update" !== commandName && "git-credential" !== commandName) checkVersionAndWarn();
|
|
51691
52311
|
}
|
|
51692
52312
|
});
|
|
51693
52313
|
const cli_watchCommand = registerWatchCommand(cli_program);
|
|
52314
|
+
registerCheckoutCommand(cli_program);
|
|
52315
|
+
registerGitCredentialCommand(cli_program);
|
|
51694
52316
|
registerLoginCommand(cli_program);
|
|
51695
52317
|
registerLogoutCommand(cli_program);
|
|
51696
52318
|
registerConfigCommand(cli_program);
|
|
51697
52319
|
registerVersionCommand(cli_program, VERSION);
|
|
52320
|
+
registerOnboardCommand(cli_program, VERSION);
|
|
51698
52321
|
cli_program.command("update").description("Update anvil to the latest version").alias("u").action(async ()=>{
|
|
51699
52322
|
await handleUpdateCommand();
|
|
51700
52323
|
});
|