@anvil-works/anvil-cli 0.5.5 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +151 -12
- package/dist/index.js +144 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -47629,12 +47629,14 @@ var __webpack_exports__ = {};
|
|
|
47629
47629
|
editSession;
|
|
47630
47630
|
username;
|
|
47631
47631
|
reconnectAttempts = 0;
|
|
47632
|
+
connectAttempts = 0;
|
|
47632
47633
|
reconnectTimer = null;
|
|
47633
47634
|
heartbeatTimer = null;
|
|
47634
47635
|
pongTimeoutTimer = null;
|
|
47635
47636
|
reconnectDelayMs;
|
|
47636
47637
|
isClosing = false;
|
|
47637
47638
|
sessionId;
|
|
47639
|
+
lastDisconnectSummary = null;
|
|
47638
47640
|
RECONNECT_DELAY_BASE_MS = 5000;
|
|
47639
47641
|
RECONNECT_DELAY_MAX_MS = 60000;
|
|
47640
47642
|
HEARTBEAT_INTERVAL_MS = 30000;
|
|
@@ -47652,6 +47654,7 @@ var __webpack_exports__ = {};
|
|
|
47652
47654
|
}
|
|
47653
47655
|
async connect() {
|
|
47654
47656
|
this.isClosing = false;
|
|
47657
|
+
this.connectAttempts++;
|
|
47655
47658
|
if (this.ws?.readyState === ws_wrapper.OPEN) return void logger_logger.debug(`[WebSocket ${this.sessionId}]`, "WebSocket already open, skipping connection");
|
|
47656
47659
|
if (this.ws) {
|
|
47657
47660
|
logger_logger.debug(`[WebSocket ${this.sessionId}]`, "Closing existing WebSocket before reconnecting");
|
|
@@ -47660,6 +47663,7 @@ var __webpack_exports__ = {};
|
|
|
47660
47663
|
this.authToken = await auth_getValidAuthToken(this.anvilUrl, this.username);
|
|
47661
47664
|
const wsUrl = getWebSocketUrl(this.appId, this.authToken, this.anvilUrl);
|
|
47662
47665
|
logger_logger.debug(`[WebSocket ${this.sessionId}]`, `Connecting to: ${wsUrl.replace(this.authToken, "[TOKEN]")}`);
|
|
47666
|
+
logger_logger.verbose(chalk_source.gray(`WebSocket connecting to ${this.getConnectionTarget(wsUrl)} (app ${this.appId}, branch ${this.currentBranch}, session ${this.sessionId}, connect attempt ${this.connectAttempts})`));
|
|
47663
47667
|
this.ws = new ws_wrapper(wsUrl);
|
|
47664
47668
|
this.ws.on("open", ()=>{
|
|
47665
47669
|
logger_logger.verbose(chalk_source.green("🔌 Connected to Anvil WebSocket"));
|
|
@@ -47676,6 +47680,21 @@ var __webpack_exports__ = {};
|
|
|
47676
47680
|
this.ws?.send(JSON.stringify(subscribeMsg));
|
|
47677
47681
|
this.emit("connected", void 0);
|
|
47678
47682
|
});
|
|
47683
|
+
this.ws.on("unexpected-response", (request, response)=>{
|
|
47684
|
+
const summary = this.summarizeUnexpectedResponse(response, wsUrl);
|
|
47685
|
+
this.lastDisconnectSummary = summary;
|
|
47686
|
+
logger_logger.error(chalk_source.red(summary));
|
|
47687
|
+
response.resume();
|
|
47688
|
+
request.destroy();
|
|
47689
|
+
if (this.isClosing) return;
|
|
47690
|
+
this.stopHeartbeat();
|
|
47691
|
+
this.ws = null;
|
|
47692
|
+
this.emit("disconnected", {
|
|
47693
|
+
code: 1006,
|
|
47694
|
+
reason: summary
|
|
47695
|
+
});
|
|
47696
|
+
this.scheduleReconnect();
|
|
47697
|
+
});
|
|
47679
47698
|
this.ws.on("message", (data)=>{
|
|
47680
47699
|
this.markSocketResponsive();
|
|
47681
47700
|
try {
|
|
@@ -47700,6 +47719,7 @@ var __webpack_exports__ = {};
|
|
|
47700
47719
|
return;
|
|
47701
47720
|
}
|
|
47702
47721
|
if (this.isClosing) return;
|
|
47722
|
+
this.lastDisconnectSummary = this.lastDisconnectSummary ?? this.formatCloseSummary(code, reason.toString());
|
|
47703
47723
|
this.emit("disconnected", {
|
|
47704
47724
|
code,
|
|
47705
47725
|
reason: reason.toString()
|
|
@@ -47709,8 +47729,13 @@ var __webpack_exports__ = {};
|
|
|
47709
47729
|
this.ws.on("error", (error)=>{
|
|
47710
47730
|
logger_logger.debug(`[WebSocket ${this.sessionId}]`, `Error: ${error.message}`);
|
|
47711
47731
|
if (this.isClosing) return;
|
|
47712
|
-
if (error.message.includes("
|
|
47713
|
-
|
|
47732
|
+
if (error.message.includes("Unexpected server response")) return;
|
|
47733
|
+
if (error.message.includes("502")) logger_logger.error(chalk_source.red(`WebSocket connection failed (502 Bad Gateway) - the server could not open the live update connection (${this.getConnectionContext()})`));
|
|
47734
|
+
else {
|
|
47735
|
+
const details = this.getConnectionContext();
|
|
47736
|
+
logger_logger.error(chalk_source.red(`WebSocket error: ${error.message} (${details})`));
|
|
47737
|
+
}
|
|
47738
|
+
this.lastDisconnectSummary = `WebSocket error: ${error.message}`;
|
|
47714
47739
|
this.emit("error", {
|
|
47715
47740
|
error
|
|
47716
47741
|
});
|
|
@@ -47765,7 +47790,7 @@ var __webpack_exports__ = {};
|
|
|
47765
47790
|
if (this.reconnectTimer || this.isClosing) return;
|
|
47766
47791
|
this.reconnectAttempts++;
|
|
47767
47792
|
const delayMs = this.reconnectDelayMs;
|
|
47768
|
-
logger_logger.verbose(chalk_source.gray(`WebSocket disconnected, reconnecting in ${Math.round(delayMs / 1000)}s (attempt ${this.
|
|
47793
|
+
logger_logger.verbose(chalk_source.gray(`WebSocket disconnected (${this.lastDisconnectSummary ?? "unknown cause"}), reconnecting in ${Math.round(delayMs / 1000)}s (${this.getConnectionContext()}, next connect attempt ${this.connectAttempts + 1})...`));
|
|
47769
47794
|
this.reconnectTimer = setTimeout(()=>{
|
|
47770
47795
|
this.reconnectTimer = null;
|
|
47771
47796
|
this.connect().catch((error)=>{
|
|
@@ -47774,8 +47799,47 @@ var __webpack_exports__ = {};
|
|
|
47774
47799
|
});
|
|
47775
47800
|
});
|
|
47776
47801
|
}, delayMs);
|
|
47802
|
+
this.lastDisconnectSummary = null;
|
|
47777
47803
|
this.reconnectDelayMs = Math.min(Math.round(1.5 * this.reconnectDelayMs), this.RECONNECT_DELAY_MAX_MS);
|
|
47778
47804
|
}
|
|
47805
|
+
getConnectionContext() {
|
|
47806
|
+
return `app ${this.appId}, branch ${this.currentBranch}, session ${this.sessionId}`;
|
|
47807
|
+
}
|
|
47808
|
+
getConnectionTarget(wsUrl) {
|
|
47809
|
+
const url = new URL(wsUrl);
|
|
47810
|
+
return `${url.origin}${url.pathname}`;
|
|
47811
|
+
}
|
|
47812
|
+
formatCloseSummary(code, reason) {
|
|
47813
|
+
const suffix = reason ? `, reason=${reason}` : "";
|
|
47814
|
+
return `close code=${code}${suffix}`;
|
|
47815
|
+
}
|
|
47816
|
+
summarizeUnexpectedResponse(response, wsUrl) {
|
|
47817
|
+
const statusCode = response.statusCode ?? "unknown";
|
|
47818
|
+
const statusMessage = response.statusMessage ?? "Unknown";
|
|
47819
|
+
const details = [
|
|
47820
|
+
`target ${this.getConnectionTarget(wsUrl)}`,
|
|
47821
|
+
this.getConnectionContext(),
|
|
47822
|
+
this.getHandshakeMetadata(response)
|
|
47823
|
+
].filter(Boolean).join(", ");
|
|
47824
|
+
return `WebSocket handshake failed (${statusCode} ${statusMessage}; ${details})`;
|
|
47825
|
+
}
|
|
47826
|
+
getHandshakeMetadata(response) {
|
|
47827
|
+
const headers = response.headers;
|
|
47828
|
+
const detailParts = [];
|
|
47829
|
+
const requestId = this.getHeaderValue(headers["x-request-id"]) || this.getHeaderValue(headers["x-amzn-requestid"]) || this.getHeaderValue(headers["cf-ray"]);
|
|
47830
|
+
const retryAfter = this.getHeaderValue(headers["retry-after"]);
|
|
47831
|
+
const server = this.getHeaderValue(headers["server"]);
|
|
47832
|
+
const via = this.getHeaderValue(headers["via"]);
|
|
47833
|
+
if (requestId) detailParts.push(`request-id ${requestId}`);
|
|
47834
|
+
if (retryAfter) detailParts.push(`retry-after ${retryAfter}`);
|
|
47835
|
+
if (server) detailParts.push(`server ${server}`);
|
|
47836
|
+
if (via) detailParts.push(`via ${via}`);
|
|
47837
|
+
return detailParts.join(", ");
|
|
47838
|
+
}
|
|
47839
|
+
getHeaderValue(header) {
|
|
47840
|
+
if (Array.isArray(header)) return header[0] ?? null;
|
|
47841
|
+
return header ?? null;
|
|
47842
|
+
}
|
|
47779
47843
|
startHeartbeat() {
|
|
47780
47844
|
this.stopHeartbeat();
|
|
47781
47845
|
this.heartbeatTimer = setInterval(()=>{
|
|
@@ -47917,9 +47981,9 @@ var __webpack_exports__ = {};
|
|
|
47917
47981
|
result.localOnlyChanges.forEach((c)=>logger_logger.verbose(chalk_source.gray(` ${c.path}`)));
|
|
47918
47982
|
}
|
|
47919
47983
|
}
|
|
47920
|
-
async function deleteFilesRemovedOnAnvil(gitService, unstagedFiles) {
|
|
47984
|
+
async function deleteFilesRemovedOnAnvil(gitService, unstagedFiles, remotelyChangedFiles) {
|
|
47921
47985
|
const filesToRemove = [];
|
|
47922
|
-
for (const file of unstagedFiles)try {
|
|
47986
|
+
for (const file of unstagedFiles)if (remotelyChangedFiles.has(file)) try {
|
|
47923
47987
|
await gitService.show(`HEAD:${file}`);
|
|
47924
47988
|
} catch (e) {
|
|
47925
47989
|
filesToRemove.push(file);
|
|
@@ -48023,6 +48087,16 @@ var __webpack_exports__ = {};
|
|
|
48023
48087
|
function normalizeClientCodePath(relativePath) {
|
|
48024
48088
|
return relativePath.replace(/\\/g, "/");
|
|
48025
48089
|
}
|
|
48090
|
+
function isServerRequirementsPath(relativePath) {
|
|
48091
|
+
return "server_code/requirements.txt" === relativePath.replace(/\\/g, "/");
|
|
48092
|
+
}
|
|
48093
|
+
async function readServerRequirements(repoPath) {
|
|
48094
|
+
try {
|
|
48095
|
+
return await external_fs_.promises.readFile(external_path_default().join(repoPath, "server_code", "requirements.txt"), "utf8");
|
|
48096
|
+
} catch (error) {
|
|
48097
|
+
return;
|
|
48098
|
+
}
|
|
48099
|
+
}
|
|
48026
48100
|
function detectFormTemplate(relativePath) {
|
|
48027
48101
|
const normalized = normalizeClientCodePath(relativePath);
|
|
48028
48102
|
if (!normalized.startsWith("client_code/")) return null;
|
|
@@ -48486,6 +48560,44 @@ var __webpack_exports__ = {};
|
|
|
48486
48560
|
type: "ignore",
|
|
48487
48561
|
reason: "anvil.yaml handled specially (multiple save paths)"
|
|
48488
48562
|
};
|
|
48563
|
+
if (isServerRequirementsPath(relativePath)) {
|
|
48564
|
+
if ("unlink" === changeType) {
|
|
48565
|
+
const anvilYamlContent = await readFileContent(repoPath, "anvil.yaml", stagedOnly);
|
|
48566
|
+
const anvilConfig = jsYaml.load(anvilYamlContent) ?? {};
|
|
48567
|
+
const runtimeOptions = {
|
|
48568
|
+
...anvilConfig.runtime_options ?? {}
|
|
48569
|
+
};
|
|
48570
|
+
const serverSpec = {
|
|
48571
|
+
...runtimeOptions.server_spec ?? {}
|
|
48572
|
+
};
|
|
48573
|
+
delete serverSpec.requirements;
|
|
48574
|
+
if (Object.keys(serverSpec).length > 0) return {
|
|
48575
|
+
type: "save",
|
|
48576
|
+
savePath: [
|
|
48577
|
+
"runtime_options",
|
|
48578
|
+
"server_spec"
|
|
48579
|
+
],
|
|
48580
|
+
content: serverSpec
|
|
48581
|
+
};
|
|
48582
|
+
delete runtimeOptions.server_spec;
|
|
48583
|
+
return {
|
|
48584
|
+
type: "save",
|
|
48585
|
+
savePath: [
|
|
48586
|
+
"runtime_options"
|
|
48587
|
+
],
|
|
48588
|
+
content: runtimeOptions
|
|
48589
|
+
};
|
|
48590
|
+
}
|
|
48591
|
+
return {
|
|
48592
|
+
type: "save",
|
|
48593
|
+
savePath: [
|
|
48594
|
+
"runtime_options",
|
|
48595
|
+
"server_spec",
|
|
48596
|
+
"requirements"
|
|
48597
|
+
],
|
|
48598
|
+
content: await readFileContent(repoPath, relativePath, stagedOnly)
|
|
48599
|
+
};
|
|
48600
|
+
}
|
|
48489
48601
|
if ("unlink" === changeType) return await handleFileDeletion(repoPath, relativePath, editorYaml);
|
|
48490
48602
|
for (const route of ROUTES)if (route.matches(relativePath)) return await route.handle(repoPath, relativePath, changeType, editorYaml, stagedOnly);
|
|
48491
48603
|
return {
|
|
@@ -48892,6 +49004,14 @@ var __webpack_exports__ = {};
|
|
|
48892
49004
|
throw new Error(`Failed to parse anvil.yaml: ${error.message}. File may be in an invalid state.`);
|
|
48893
49005
|
}
|
|
48894
49006
|
if (!parsedYaml || "object" != typeof parsedYaml) throw new Error("anvil.yaml is not a valid YAML object");
|
|
49007
|
+
const currentRequirements = await readServerRequirements(repoPath);
|
|
49008
|
+
if (void 0 !== currentRequirements) parsedYaml.runtime_options = {
|
|
49009
|
+
...parsedYaml.runtime_options ?? {},
|
|
49010
|
+
server_spec: {
|
|
49011
|
+
...parsedYaml.runtime_options?.server_spec ?? {},
|
|
49012
|
+
requirements: currentRequirements
|
|
49013
|
+
}
|
|
49014
|
+
};
|
|
48895
49015
|
if (!validateAnvilYaml(yamlContent)) throw new Error("anvil.yaml validation failed - see errors above");
|
|
48896
49016
|
const changes = [];
|
|
48897
49017
|
for (const [key, value] of Object.entries(parsedYaml))if (!previousYaml || !deepEqual(previousYaml[key], value)) changes.push({
|
|
@@ -49135,7 +49255,19 @@ var __webpack_exports__ = {};
|
|
|
49135
49255
|
async getHeadAnvilYaml() {
|
|
49136
49256
|
try {
|
|
49137
49257
|
const content = await this.config.gitService.show("HEAD:anvil.yaml");
|
|
49138
|
-
|
|
49258
|
+
const parsedYaml = js_yaml_load(content);
|
|
49259
|
+
if (!parsedYaml || "object" != typeof parsedYaml) return null;
|
|
49260
|
+
try {
|
|
49261
|
+
const requirements = await this.config.gitService.show("HEAD:server_code/requirements.txt");
|
|
49262
|
+
parsedYaml.runtime_options = {
|
|
49263
|
+
...parsedYaml.runtime_options ?? {},
|
|
49264
|
+
server_spec: {
|
|
49265
|
+
...parsedYaml.runtime_options?.server_spec ?? {},
|
|
49266
|
+
requirements
|
|
49267
|
+
}
|
|
49268
|
+
};
|
|
49269
|
+
} catch (e) {}
|
|
49270
|
+
return parsedYaml;
|
|
49139
49271
|
} catch (e) {
|
|
49140
49272
|
return null;
|
|
49141
49273
|
}
|
|
@@ -49429,21 +49561,24 @@ var __webpack_exports__ = {};
|
|
|
49429
49561
|
const httpUrl = getGitFetchUrl(this.config.appId, this.config.getAuthToken(), this.config.anvilUrl);
|
|
49430
49562
|
const currentBranch = this.config.getCurrentBranch();
|
|
49431
49563
|
const tempRef = `anvil-sync-temp-${Date.now()}`;
|
|
49564
|
+
const oldCommitId = this.config.getCommitId();
|
|
49432
49565
|
await this.config.gitService.fetch(httpUrl, `+${currentBranch}:${tempRef}`);
|
|
49433
49566
|
await this.config.gitService.reset(tempRef, "mixed");
|
|
49434
49567
|
await this.config.gitService.deleteRef(`refs/heads/${tempRef}`);
|
|
49568
|
+
const newCommitId = await this.config.gitService.getCommitId();
|
|
49435
49569
|
await this.config.gitService.checkout([
|
|
49436
49570
|
".anvil_editor.yaml",
|
|
49437
49571
|
"anvil.yaml"
|
|
49438
49572
|
]);
|
|
49439
49573
|
await this.config.editorYaml.reload();
|
|
49440
|
-
await this.deleteFilesRemovedOnAnvilLocally();
|
|
49574
|
+
await this.deleteFilesRemovedOnAnvilLocally(oldCommitId, newCommitId);
|
|
49441
49575
|
await this.discardFormattingOnlyYamlChanges();
|
|
49442
49576
|
}
|
|
49443
|
-
async deleteFilesRemovedOnAnvilLocally() {
|
|
49577
|
+
async deleteFilesRemovedOnAnvilLocally(oldCommitId, newCommitId) {
|
|
49444
49578
|
try {
|
|
49445
49579
|
const status = await this.config.gitService.getStatus();
|
|
49446
|
-
await
|
|
49580
|
+
const remotelyChangedFiles = await detectRemoteChanges(this.config.gitService, oldCommitId, newCommitId);
|
|
49581
|
+
await deleteFilesRemovedOnAnvil(this.config.gitService, status.notAdded, remotelyChangedFiles);
|
|
49447
49582
|
} catch (e) {
|
|
49448
49583
|
logger_logger.verbose(chalk_source.gray(` Failed to check git status: ${errors_getErrorMessage(e)}`));
|
|
49449
49584
|
}
|
|
@@ -50807,7 +50942,7 @@ var __webpack_exports__ = {};
|
|
|
50807
50942
|
async function confirmReverseLookupWithResolvedUser(anvilUrl, username) {
|
|
50808
50943
|
const resolvedUsername = await resolveUsernameForUrl(anvilUrl, username, `Multiple accounts found for ${anvilUrl}. Which account should be used for app lookup?`);
|
|
50809
50944
|
if (null === resolvedUsername) return null;
|
|
50810
|
-
const shouldContinue = await logger_logger.confirm(`Search ${anvilUrl} ${resolvedUsername ? `for ${resolvedUsername}` : ""} for matching app IDs?
|
|
50945
|
+
const shouldContinue = await logger_logger.confirm(`Search ${anvilUrl} ${resolvedUsername ? `for ${resolvedUsername}` : ""} for matching app IDs? This is slower than detecting the app ID from local git remotes.`, true);
|
|
50811
50946
|
return {
|
|
50812
50947
|
username: resolvedUsername,
|
|
50813
50948
|
shouldContinue
|
|
@@ -50864,6 +50999,11 @@ var __webpack_exports__ = {};
|
|
|
50864
50999
|
};
|
|
50865
51000
|
return null;
|
|
50866
51001
|
}
|
|
51002
|
+
if (1 === candidates.length) {
|
|
51003
|
+
const [candidate] = candidates;
|
|
51004
|
+
logger_logger.success("Auto-selected detected app ID: " + chalk_source.bold(candidate.appId));
|
|
51005
|
+
return candidate;
|
|
51006
|
+
}
|
|
50867
51007
|
const choices = candidates.map((candidate, index)=>({
|
|
50868
51008
|
name: formatCandidateLabel(candidate),
|
|
50869
51009
|
value: index
|
|
@@ -50873,12 +51013,11 @@ var __webpack_exports__ = {};
|
|
|
50873
51013
|
value: null
|
|
50874
51014
|
});
|
|
50875
51015
|
try {
|
|
50876
|
-
const promptText = 1 === candidates.length ? "Confirm the detected app ID:" : "Select an app ID:";
|
|
50877
51016
|
const answer = await logger_logger.prompt([
|
|
50878
51017
|
{
|
|
50879
51018
|
type: "list",
|
|
50880
51019
|
name: "appId",
|
|
50881
|
-
message:
|
|
51020
|
+
message: "Select an app ID:",
|
|
50882
51021
|
choices: choices,
|
|
50883
51022
|
pageSize: 10
|
|
50884
51023
|
}
|
package/dist/index.js
CHANGED
|
@@ -22643,12 +22643,14 @@ var __webpack_exports__ = {};
|
|
|
22643
22643
|
editSession;
|
|
22644
22644
|
username;
|
|
22645
22645
|
reconnectAttempts = 0;
|
|
22646
|
+
connectAttempts = 0;
|
|
22646
22647
|
reconnectTimer = null;
|
|
22647
22648
|
heartbeatTimer = null;
|
|
22648
22649
|
pongTimeoutTimer = null;
|
|
22649
22650
|
reconnectDelayMs;
|
|
22650
22651
|
isClosing = false;
|
|
22651
22652
|
sessionId;
|
|
22653
|
+
lastDisconnectSummary = null;
|
|
22652
22654
|
RECONNECT_DELAY_BASE_MS = 5000;
|
|
22653
22655
|
RECONNECT_DELAY_MAX_MS = 60000;
|
|
22654
22656
|
HEARTBEAT_INTERVAL_MS = 30000;
|
|
@@ -22666,6 +22668,7 @@ var __webpack_exports__ = {};
|
|
|
22666
22668
|
}
|
|
22667
22669
|
async connect() {
|
|
22668
22670
|
this.isClosing = false;
|
|
22671
|
+
this.connectAttempts++;
|
|
22669
22672
|
if (this.ws?.readyState === ws_wrapper.OPEN) return void logger_logger.debug(`[WebSocket ${this.sessionId}]`, "WebSocket already open, skipping connection");
|
|
22670
22673
|
if (this.ws) {
|
|
22671
22674
|
logger_logger.debug(`[WebSocket ${this.sessionId}]`, "Closing existing WebSocket before reconnecting");
|
|
@@ -22674,6 +22677,7 @@ var __webpack_exports__ = {};
|
|
|
22674
22677
|
this.authToken = await auth_getValidAuthToken(this.anvilUrl, this.username);
|
|
22675
22678
|
const wsUrl = getWebSocketUrl(this.appId, this.authToken, this.anvilUrl);
|
|
22676
22679
|
logger_logger.debug(`[WebSocket ${this.sessionId}]`, `Connecting to: ${wsUrl.replace(this.authToken, "[TOKEN]")}`);
|
|
22680
|
+
logger_logger.verbose(chalk_source.gray(`WebSocket connecting to ${this.getConnectionTarget(wsUrl)} (app ${this.appId}, branch ${this.currentBranch}, session ${this.sessionId}, connect attempt ${this.connectAttempts})`));
|
|
22677
22681
|
this.ws = new ws_wrapper(wsUrl);
|
|
22678
22682
|
this.ws.on("open", ()=>{
|
|
22679
22683
|
logger_logger.verbose(chalk_source.green("🔌 Connected to Anvil WebSocket"));
|
|
@@ -22690,6 +22694,21 @@ var __webpack_exports__ = {};
|
|
|
22690
22694
|
this.ws?.send(JSON.stringify(subscribeMsg));
|
|
22691
22695
|
this.emit("connected", void 0);
|
|
22692
22696
|
});
|
|
22697
|
+
this.ws.on("unexpected-response", (request, response)=>{
|
|
22698
|
+
const summary = this.summarizeUnexpectedResponse(response, wsUrl);
|
|
22699
|
+
this.lastDisconnectSummary = summary;
|
|
22700
|
+
logger_logger.error(chalk_source.red(summary));
|
|
22701
|
+
response.resume();
|
|
22702
|
+
request.destroy();
|
|
22703
|
+
if (this.isClosing) return;
|
|
22704
|
+
this.stopHeartbeat();
|
|
22705
|
+
this.ws = null;
|
|
22706
|
+
this.emit("disconnected", {
|
|
22707
|
+
code: 1006,
|
|
22708
|
+
reason: summary
|
|
22709
|
+
});
|
|
22710
|
+
this.scheduleReconnect();
|
|
22711
|
+
});
|
|
22693
22712
|
this.ws.on("message", (data)=>{
|
|
22694
22713
|
this.markSocketResponsive();
|
|
22695
22714
|
try {
|
|
@@ -22714,6 +22733,7 @@ var __webpack_exports__ = {};
|
|
|
22714
22733
|
return;
|
|
22715
22734
|
}
|
|
22716
22735
|
if (this.isClosing) return;
|
|
22736
|
+
this.lastDisconnectSummary = this.lastDisconnectSummary ?? this.formatCloseSummary(code, reason.toString());
|
|
22717
22737
|
this.emit("disconnected", {
|
|
22718
22738
|
code,
|
|
22719
22739
|
reason: reason.toString()
|
|
@@ -22723,8 +22743,13 @@ var __webpack_exports__ = {};
|
|
|
22723
22743
|
this.ws.on("error", (error)=>{
|
|
22724
22744
|
logger_logger.debug(`[WebSocket ${this.sessionId}]`, `Error: ${error.message}`);
|
|
22725
22745
|
if (this.isClosing) return;
|
|
22726
|
-
if (error.message.includes("
|
|
22727
|
-
|
|
22746
|
+
if (error.message.includes("Unexpected server response")) return;
|
|
22747
|
+
if (error.message.includes("502")) logger_logger.error(chalk_source.red(`WebSocket connection failed (502 Bad Gateway) - the server could not open the live update connection (${this.getConnectionContext()})`));
|
|
22748
|
+
else {
|
|
22749
|
+
const details = this.getConnectionContext();
|
|
22750
|
+
logger_logger.error(chalk_source.red(`WebSocket error: ${error.message} (${details})`));
|
|
22751
|
+
}
|
|
22752
|
+
this.lastDisconnectSummary = `WebSocket error: ${error.message}`;
|
|
22728
22753
|
this.emit("error", {
|
|
22729
22754
|
error
|
|
22730
22755
|
});
|
|
@@ -22779,7 +22804,7 @@ var __webpack_exports__ = {};
|
|
|
22779
22804
|
if (this.reconnectTimer || this.isClosing) return;
|
|
22780
22805
|
this.reconnectAttempts++;
|
|
22781
22806
|
const delayMs = this.reconnectDelayMs;
|
|
22782
|
-
logger_logger.verbose(chalk_source.gray(`WebSocket disconnected, reconnecting in ${Math.round(delayMs / 1000)}s (attempt ${this.
|
|
22807
|
+
logger_logger.verbose(chalk_source.gray(`WebSocket disconnected (${this.lastDisconnectSummary ?? "unknown cause"}), reconnecting in ${Math.round(delayMs / 1000)}s (${this.getConnectionContext()}, next connect attempt ${this.connectAttempts + 1})...`));
|
|
22783
22808
|
this.reconnectTimer = setTimeout(()=>{
|
|
22784
22809
|
this.reconnectTimer = null;
|
|
22785
22810
|
this.connect().catch((error)=>{
|
|
@@ -22788,8 +22813,47 @@ var __webpack_exports__ = {};
|
|
|
22788
22813
|
});
|
|
22789
22814
|
});
|
|
22790
22815
|
}, delayMs);
|
|
22816
|
+
this.lastDisconnectSummary = null;
|
|
22791
22817
|
this.reconnectDelayMs = Math.min(Math.round(1.5 * this.reconnectDelayMs), this.RECONNECT_DELAY_MAX_MS);
|
|
22792
22818
|
}
|
|
22819
|
+
getConnectionContext() {
|
|
22820
|
+
return `app ${this.appId}, branch ${this.currentBranch}, session ${this.sessionId}`;
|
|
22821
|
+
}
|
|
22822
|
+
getConnectionTarget(wsUrl) {
|
|
22823
|
+
const url = new URL(wsUrl);
|
|
22824
|
+
return `${url.origin}${url.pathname}`;
|
|
22825
|
+
}
|
|
22826
|
+
formatCloseSummary(code, reason) {
|
|
22827
|
+
const suffix = reason ? `, reason=${reason}` : "";
|
|
22828
|
+
return `close code=${code}${suffix}`;
|
|
22829
|
+
}
|
|
22830
|
+
summarizeUnexpectedResponse(response, wsUrl) {
|
|
22831
|
+
const statusCode = response.statusCode ?? "unknown";
|
|
22832
|
+
const statusMessage = response.statusMessage ?? "Unknown";
|
|
22833
|
+
const details = [
|
|
22834
|
+
`target ${this.getConnectionTarget(wsUrl)}`,
|
|
22835
|
+
this.getConnectionContext(),
|
|
22836
|
+
this.getHandshakeMetadata(response)
|
|
22837
|
+
].filter(Boolean).join(", ");
|
|
22838
|
+
return `WebSocket handshake failed (${statusCode} ${statusMessage}; ${details})`;
|
|
22839
|
+
}
|
|
22840
|
+
getHandshakeMetadata(response) {
|
|
22841
|
+
const headers = response.headers;
|
|
22842
|
+
const detailParts = [];
|
|
22843
|
+
const requestId = this.getHeaderValue(headers["x-request-id"]) || this.getHeaderValue(headers["x-amzn-requestid"]) || this.getHeaderValue(headers["cf-ray"]);
|
|
22844
|
+
const retryAfter = this.getHeaderValue(headers["retry-after"]);
|
|
22845
|
+
const server = this.getHeaderValue(headers["server"]);
|
|
22846
|
+
const via = this.getHeaderValue(headers["via"]);
|
|
22847
|
+
if (requestId) detailParts.push(`request-id ${requestId}`);
|
|
22848
|
+
if (retryAfter) detailParts.push(`retry-after ${retryAfter}`);
|
|
22849
|
+
if (server) detailParts.push(`server ${server}`);
|
|
22850
|
+
if (via) detailParts.push(`via ${via}`);
|
|
22851
|
+
return detailParts.join(", ");
|
|
22852
|
+
}
|
|
22853
|
+
getHeaderValue(header) {
|
|
22854
|
+
if (Array.isArray(header)) return header[0] ?? null;
|
|
22855
|
+
return header ?? null;
|
|
22856
|
+
}
|
|
22793
22857
|
startHeartbeat() {
|
|
22794
22858
|
this.stopHeartbeat();
|
|
22795
22859
|
this.heartbeatTimer = setInterval(()=>{
|
|
@@ -22931,9 +22995,9 @@ var __webpack_exports__ = {};
|
|
|
22931
22995
|
result.localOnlyChanges.forEach((c)=>logger_logger.verbose(chalk_source.gray(` ${c.path}`)));
|
|
22932
22996
|
}
|
|
22933
22997
|
}
|
|
22934
|
-
async function deleteFilesRemovedOnAnvil(gitService, unstagedFiles) {
|
|
22998
|
+
async function deleteFilesRemovedOnAnvil(gitService, unstagedFiles, remotelyChangedFiles) {
|
|
22935
22999
|
const filesToRemove = [];
|
|
22936
|
-
for (const file of unstagedFiles)try {
|
|
23000
|
+
for (const file of unstagedFiles)if (remotelyChangedFiles.has(file)) try {
|
|
22937
23001
|
await gitService.show(`HEAD:${file}`);
|
|
22938
23002
|
} catch (e) {
|
|
22939
23003
|
filesToRemove.push(file);
|
|
@@ -23536,6 +23600,16 @@ var __webpack_exports__ = {};
|
|
|
23536
23600
|
function normalizeClientCodePath(relativePath) {
|
|
23537
23601
|
return relativePath.replace(/\\/g, "/");
|
|
23538
23602
|
}
|
|
23603
|
+
function isServerRequirementsPath(relativePath) {
|
|
23604
|
+
return "server_code/requirements.txt" === relativePath.replace(/\\/g, "/");
|
|
23605
|
+
}
|
|
23606
|
+
async function readServerRequirements(repoPath) {
|
|
23607
|
+
try {
|
|
23608
|
+
return await external_fs_.promises.readFile(external_path_default().join(repoPath, "server_code", "requirements.txt"), "utf8");
|
|
23609
|
+
} catch (error) {
|
|
23610
|
+
return;
|
|
23611
|
+
}
|
|
23612
|
+
}
|
|
23539
23613
|
function detectFormTemplate(relativePath) {
|
|
23540
23614
|
const normalized = normalizeClientCodePath(relativePath);
|
|
23541
23615
|
if (!normalized.startsWith("client_code/")) return null;
|
|
@@ -23999,6 +24073,44 @@ var __webpack_exports__ = {};
|
|
|
23999
24073
|
type: "ignore",
|
|
24000
24074
|
reason: "anvil.yaml handled specially (multiple save paths)"
|
|
24001
24075
|
};
|
|
24076
|
+
if (isServerRequirementsPath(relativePath)) {
|
|
24077
|
+
if ("unlink" === changeType) {
|
|
24078
|
+
const anvilYamlContent = await readFileContent(repoPath, "anvil.yaml", stagedOnly);
|
|
24079
|
+
const anvilConfig = jsYaml.load(anvilYamlContent) ?? {};
|
|
24080
|
+
const runtimeOptions = {
|
|
24081
|
+
...anvilConfig.runtime_options ?? {}
|
|
24082
|
+
};
|
|
24083
|
+
const serverSpec = {
|
|
24084
|
+
...runtimeOptions.server_spec ?? {}
|
|
24085
|
+
};
|
|
24086
|
+
delete serverSpec.requirements;
|
|
24087
|
+
if (Object.keys(serverSpec).length > 0) return {
|
|
24088
|
+
type: "save",
|
|
24089
|
+
savePath: [
|
|
24090
|
+
"runtime_options",
|
|
24091
|
+
"server_spec"
|
|
24092
|
+
],
|
|
24093
|
+
content: serverSpec
|
|
24094
|
+
};
|
|
24095
|
+
delete runtimeOptions.server_spec;
|
|
24096
|
+
return {
|
|
24097
|
+
type: "save",
|
|
24098
|
+
savePath: [
|
|
24099
|
+
"runtime_options"
|
|
24100
|
+
],
|
|
24101
|
+
content: runtimeOptions
|
|
24102
|
+
};
|
|
24103
|
+
}
|
|
24104
|
+
return {
|
|
24105
|
+
type: "save",
|
|
24106
|
+
savePath: [
|
|
24107
|
+
"runtime_options",
|
|
24108
|
+
"server_spec",
|
|
24109
|
+
"requirements"
|
|
24110
|
+
],
|
|
24111
|
+
content: await readFileContent(repoPath, relativePath, stagedOnly)
|
|
24112
|
+
};
|
|
24113
|
+
}
|
|
24002
24114
|
if ("unlink" === changeType) return await handleFileDeletion(repoPath, relativePath, editorYaml);
|
|
24003
24115
|
for (const route of ROUTES)if (route.matches(relativePath)) return await route.handle(repoPath, relativePath, changeType, editorYaml, stagedOnly);
|
|
24004
24116
|
return {
|
|
@@ -24405,6 +24517,14 @@ var __webpack_exports__ = {};
|
|
|
24405
24517
|
throw new Error(`Failed to parse anvil.yaml: ${error.message}. File may be in an invalid state.`);
|
|
24406
24518
|
}
|
|
24407
24519
|
if (!parsedYaml || "object" != typeof parsedYaml) throw new Error("anvil.yaml is not a valid YAML object");
|
|
24520
|
+
const currentRequirements = await readServerRequirements(repoPath);
|
|
24521
|
+
if (void 0 !== currentRequirements) parsedYaml.runtime_options = {
|
|
24522
|
+
...parsedYaml.runtime_options ?? {},
|
|
24523
|
+
server_spec: {
|
|
24524
|
+
...parsedYaml.runtime_options?.server_spec ?? {},
|
|
24525
|
+
requirements: currentRequirements
|
|
24526
|
+
}
|
|
24527
|
+
};
|
|
24408
24528
|
if (!validateAnvilYaml(yamlContent)) throw new Error("anvil.yaml validation failed - see errors above");
|
|
24409
24529
|
const changes = [];
|
|
24410
24530
|
for (const [key, value] of Object.entries(parsedYaml))if (!previousYaml || !deepEqual(previousYaml[key], value)) changes.push({
|
|
@@ -24648,7 +24768,19 @@ var __webpack_exports__ = {};
|
|
|
24648
24768
|
async getHeadAnvilYaml() {
|
|
24649
24769
|
try {
|
|
24650
24770
|
const content = await this.config.gitService.show("HEAD:anvil.yaml");
|
|
24651
|
-
|
|
24771
|
+
const parsedYaml = load(content);
|
|
24772
|
+
if (!parsedYaml || "object" != typeof parsedYaml) return null;
|
|
24773
|
+
try {
|
|
24774
|
+
const requirements = await this.config.gitService.show("HEAD:server_code/requirements.txt");
|
|
24775
|
+
parsedYaml.runtime_options = {
|
|
24776
|
+
...parsedYaml.runtime_options ?? {},
|
|
24777
|
+
server_spec: {
|
|
24778
|
+
...parsedYaml.runtime_options?.server_spec ?? {},
|
|
24779
|
+
requirements
|
|
24780
|
+
}
|
|
24781
|
+
};
|
|
24782
|
+
} catch (e) {}
|
|
24783
|
+
return parsedYaml;
|
|
24652
24784
|
} catch (e) {
|
|
24653
24785
|
return null;
|
|
24654
24786
|
}
|
|
@@ -24942,21 +25074,24 @@ var __webpack_exports__ = {};
|
|
|
24942
25074
|
const httpUrl = getGitFetchUrl(this.config.appId, this.config.getAuthToken(), this.config.anvilUrl);
|
|
24943
25075
|
const currentBranch = this.config.getCurrentBranch();
|
|
24944
25076
|
const tempRef = `anvil-sync-temp-${Date.now()}`;
|
|
25077
|
+
const oldCommitId = this.config.getCommitId();
|
|
24945
25078
|
await this.config.gitService.fetch(httpUrl, `+${currentBranch}:${tempRef}`);
|
|
24946
25079
|
await this.config.gitService.reset(tempRef, "mixed");
|
|
24947
25080
|
await this.config.gitService.deleteRef(`refs/heads/${tempRef}`);
|
|
25081
|
+
const newCommitId = await this.config.gitService.getCommitId();
|
|
24948
25082
|
await this.config.gitService.checkout([
|
|
24949
25083
|
".anvil_editor.yaml",
|
|
24950
25084
|
"anvil.yaml"
|
|
24951
25085
|
]);
|
|
24952
25086
|
await this.config.editorYaml.reload();
|
|
24953
|
-
await this.deleteFilesRemovedOnAnvilLocally();
|
|
25087
|
+
await this.deleteFilesRemovedOnAnvilLocally(oldCommitId, newCommitId);
|
|
24954
25088
|
await this.discardFormattingOnlyYamlChanges();
|
|
24955
25089
|
}
|
|
24956
|
-
async deleteFilesRemovedOnAnvilLocally() {
|
|
25090
|
+
async deleteFilesRemovedOnAnvilLocally(oldCommitId, newCommitId) {
|
|
24957
25091
|
try {
|
|
24958
25092
|
const status = await this.config.gitService.getStatus();
|
|
24959
|
-
await
|
|
25093
|
+
const remotelyChangedFiles = await detectRemoteChanges(this.config.gitService, oldCommitId, newCommitId);
|
|
25094
|
+
await deleteFilesRemovedOnAnvil(this.config.gitService, status.notAdded, remotelyChangedFiles);
|
|
24960
25095
|
} catch (e) {
|
|
24961
25096
|
logger_logger.verbose(chalk_source.gray(` Failed to check git status: ${errors_getErrorMessage(e)}`));
|
|
24962
25097
|
}
|