@my-life-buddies/cli 0.17.6 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/web/app.css CHANGED
@@ -542,11 +542,11 @@ body[data-hosted-preview] .connection-detail-body dl > div { display:grid; grid-
542
542
  body[data-hosted-preview] .connection-detail-body dt { color:var(--color-muted); }
543
543
  body[data-hosted-preview] .connection-detail-body dd { margin:0; text-align:left; overflow-wrap:anywhere; }
544
544
  body[data-hosted-preview] .environment-note { padding:8px 0 0; margin-top:8px; border:0; border-top:1px solid var(--color-rule); }
545
- body[data-hosted-preview] #connect-instructions { padding:16px 20px; border-top:1px solid var(--color-rule); background:var(--color-paper); max-height:240px; overflow:auto; }
545
+ body[data-hosted-preview] :is(#connect-instructions, #disconnect-result) { padding:16px 20px; border-top:1px solid var(--color-rule); background:var(--color-paper); max-height:240px; overflow:auto; }
546
546
  body[data-hosted-preview] .command-heading { display:flex; justify-content:space-between; align-items:center; gap:16px; }
547
547
  body[data-hosted-preview] .command-heading strong { font-size:13px; font-weight:600; }
548
- body[data-hosted-preview] #connect-instructions pre { padding:12px 14px; margin:12px 0 8px; white-space:pre-wrap; overflow-wrap:anywhere; border:1px solid var(--color-rule); border-radius:8px; background:var(--color-paper-raised); line-height:1.7; font-size:12px; }
549
- body[data-hosted-preview] #connect-instructions p { margin:0; color:var(--color-muted); font-size:12px; line-height:1.6; }
548
+ body[data-hosted-preview] :is(#connect-instructions, #disconnect-result) pre { padding:12px 14px; margin:12px 0 8px; white-space:pre-wrap; overflow-wrap:anywhere; border:1px solid var(--color-rule); border-radius:8px; background:var(--color-paper-raised); line-height:1.7; font-size:12px; }
549
+ body[data-hosted-preview] :is(#connect-instructions, #disconnect-result) p { margin:0; color:var(--color-muted); font-size:12px; line-height:1.6; }
550
550
  body[data-hosted-preview] .workspace { flex:1; min-height:0; grid-template-columns:minmax(350px,1fr) clamp(380px,43%,580px); overflow:hidden; }
551
551
  body[data-hosted-preview] .debugger { display:flex; flex-direction:column; min-height:0; overflow:hidden; background:var(--color-paper-raised); }
552
552
  body[data-hosted-preview] .hosted-page-heading { flex:none; padding:0 20px 20px; }
@@ -719,3 +719,6 @@ body[data-hosted-preview] .pairing-panel { top:80px; }
719
719
  .diagnostic-entry time { font-size:10px; }
720
720
  .diagnostic-source { display:none; }
721
721
  }
722
+
723
+ body[data-hosted-preview] #disconnect-message { color:var(--color-ink); font-weight:600; margin-bottom:8px; }
724
+ body[data-hosted-preview] #copy-stop-command { margin:8px 0; }
package/dist/web/app.js CHANGED
@@ -5,6 +5,7 @@
5
5
  const hostedBuddyId = hosted ? new URL(location.href).searchParams.get("buddyId") : "";
6
6
  const connectionUrl = hosted ? new URL(`../api/buddies/${encodeURIComponent(hostedBuddyId)}/preview`, location.href) : undefined;
7
7
  let connectionInstance = "", connectionReady = !hosted, connectionBusy = false, devPollBusy = false;
8
+ let disconnectBusy = false, connectionEpoch = 0;
8
9
  let connectionChecked = false, connectionFailure = "", devAction = "";
9
10
  let eventSource, eventSourceInstance = "", liveEventsActive = false, pollingInterval = 0;
10
11
  function previewApiUrl(path) {
@@ -112,7 +113,7 @@
112
113
  },
113
114
  });
114
115
  const value = await response.json().catch(() => ({}));
115
- if (hosted && (!connectionReady || instance !== connectionInstance)) throw Object.assign(new Error("本地连接已改变,请等待重新连接"), {status:409, code:"PREVIEW_CONNECTION_CHANGED"});
116
+ if (hosted && (disconnectBusy || !connectionReady || instance !== connectionInstance)) throw Object.assign(new Error("本地连接已改变,请等待重新连接"), {status:409, code:"PREVIEW_CONNECTION_CHANGED"});
116
117
  if (!response.ok) {
117
118
  const error = new Error((typeof value?.error === "string" ? value.error : value?.error?.message) || `请求失败(HTTP ${response.status})`);
118
119
  error.code = value?.error?.code || "PREVIEW_REQUEST_FAILED";
@@ -256,7 +257,7 @@
256
257
  if (!elements.pairingPanel.hidden && pairingRevision !== state.dev.revision) void togglePairing(true);
257
258
  elements.connectionState.dataset.phase = phase;
258
259
  elements.connectionLabel.textContent = phase === "disconnected" ? "未连接本地工程" : phaseCopy(phase);
259
- elements.devControl.disabled = !connectionReady || Boolean(devAction) || phase === "starting" || phase === "stopping";
260
+ elements.devControl.disabled = disconnectBusy || !connectionReady || Boolean(devAction) || phase === "starting" || phase === "stopping";
260
261
  elements.devControl.dataset.action = ["ready", "reloading", "reload_failed"].includes(phase) ? "stop" : "start";
261
262
  elements.devControl.textContent = devAction === "start" ? "正在启动…" : devAction === "stop" ? "正在停止…" : phase === "failed" ? "重试启动" : ["ready", "reloading", "reload_failed"].includes(phase) ? "停止 DEV" : phase === "starting" ? "正在启动…" : phase === "stopping" ? "正在停止…" : "启动 DEV";
262
263
  elements.environmentAgent.textContent = phase === "ready"
@@ -317,6 +318,9 @@
317
318
  $("#workbench-state-title").textContent = copy[0];
318
319
  $("#workbench-state-copy").textContent = copy[1];
319
320
  $("#connection-description").textContent = !connectionChecked ? "检查中" : phase === "disconnected" ? "等待连接" : "保存自动重载,可能中断回复;Widget 数据不自动迁移。";
321
+ $("#force-disconnect").hidden = !connectionInstance;
322
+ $("#force-disconnect").disabled = disconnectBusy;
323
+ $("#force-disconnect").textContent = disconnectBusy ? "正在断开…" : "强制断连";
320
324
  const connectButton = $("#get-connect-command");
321
325
  connectButton.className = `button ${connectionReady ? "button-secondary" : "button-primary"}`;
322
326
  connectButton.textContent = connectionReady ? "连接指令" : connectionInstance ? "重新连接" : "连接工程";
@@ -692,7 +696,7 @@
692
696
  }
693
697
 
694
698
  async function controlDev() {
695
- if (!connectionReady || state.dev.phase === "starting" || state.dev.phase === "stopping") return;
699
+ if (disconnectBusy || !connectionReady || state.dev.phase === "starting" || state.dev.phase === "stopping") return;
696
700
  if (devAction) return;
697
701
  devAction = ["ready", "reloading", "reload_failed"].includes(state.dev.phase) ? "stop" : "start";
698
702
  elements.errorBanner.hidden = true;
@@ -712,7 +716,7 @@
712
716
  }
713
717
 
714
718
  function updateSendButton() {
715
- const ready = connectionReady && !devAction && state.simulatorLoaded && state.dev.phase === "ready" && !state.resetting && !state.sending;
719
+ const ready = connectionReady && !disconnectBusy && !devAction && state.simulatorLoaded && state.dev.phase === "ready" && !state.resetting && !state.sending;
716
720
  elements.sendMessage.disabled = !ready || !elements.messageInput.value.trim();
717
721
  }
718
722
 
@@ -948,18 +952,23 @@
948
952
  }
949
953
 
950
954
  async function refreshConnection() {
951
- if (connectionBusy) return;
955
+ if (connectionBusy || disconnectBusy) return;
952
956
  connectionBusy = true;
957
+ const epoch = connectionEpoch;
953
958
  try {
954
959
  const response = await fetch(connectionUrl, { signal: AbortSignal.timeout(5000) });
955
960
  const value = await response.json();
956
961
  if (!response.ok) throw new Error(value.error || "连接状态读取失败");
962
+ if (epoch !== connectionEpoch) return;
957
963
  connectionChecked = true; connectionFailure = "";
958
964
  $("#connect-command").textContent = value.command;
959
965
  $("#copy-connect-command").disabled = false;
960
- if (!value.connected) { disconnected(); return; }
966
+ $("#stop-command").textContent = value.stopCommand || "buddy-cli dev stop";
967
+ if (value.projectRoot) $("#project-root").textContent = value.projectRoot;
968
+ if (value.machineName) $("#machine-name").textContent = value.machineName;
969
+ if (!value.connected) { connectionInstance = value.instanceId || ""; disconnected(); return; }
961
970
  const changed = connectionInstance !== value.instanceId;
962
- if (changed) closeEvents();
971
+ if (changed) { closeEvents(); $("#disconnect-result").hidden = true; }
963
972
  const newlyConnected = !connectionReady || changed;
964
973
  connectionInstance = value.instanceId;
965
974
  connectionReady = true;
@@ -982,6 +991,7 @@
982
991
  }
983
992
  if (value.liveEvents) connectEvents();
984
993
  } catch (cause) {
994
+ if (epoch !== connectionEpoch) return;
985
995
  const message = cause.message || "连接已中断,正在重试…";
986
996
  connectionChecked = true; connectionFailure = message;
987
997
  disconnected(message);
@@ -990,8 +1000,31 @@
990
1000
  finally { connectionBusy = false; }
991
1001
  }
992
1002
 
1003
+ async function forceDisconnect() {
1004
+ if (disconnectBusy || !connectionInstance) return;
1005
+ if (!window.confirm("强制断开此工程并停止 DEV?若本机无法响应,需要在对应电脑的工程目录执行停止指令。")) return;
1006
+ disconnectBusy = true; connectionEpoch++;
1007
+ const url = previewApiUrl("/api/connection/disconnect");
1008
+ closeEvents(); renderDev();
1009
+ try {
1010
+ const response = await fetch(url, { method: "POST", signal: AbortSignal.timeout(35_000) });
1011
+ const value = await response.json();
1012
+ if (!response.ok) throw new Error(value.error || "断连请求失败,请刷新状态后重试");
1013
+ connectionInstance = "";
1014
+ disconnected();
1015
+ $("#disconnect-result").hidden = false;
1016
+ $("#disconnect-message").textContent = value.localStopped
1017
+ ? "已断开后台连接,并确认当前工程的本地 DEV 已停止。"
1018
+ : "已断开后台连接,但无法确认本机 Runtime 已停止。请到原电脑的工程目录执行下面的停止指令。";
1019
+ } catch (cause) {
1020
+ $("#disconnect-result").hidden = false;
1021
+ $("#disconnect-message").textContent = "尚未确认断连和停止结果,请检查终端或执行下面的停止指令,再刷新连接状态。";
1022
+ showError(cause);
1023
+ } finally { disconnectBusy = false; renderDev(); void refreshConnection(); }
1024
+ }
1025
+
993
1026
  async function pollHostedDev() {
994
- if (!connectionReady || devPollBusy) return;
1027
+ if (!connectionReady || devPollBusy || disconnectBusy) return;
995
1028
  devPollBusy = true;
996
1029
  try {
997
1030
  const [dev, debug] = await Promise.all([api("/api/dev"), api("/api/debug")]);
@@ -1017,13 +1050,18 @@
1017
1050
  engineeringDetails.querySelector("summary").focus();
1018
1051
  }
1019
1052
  });
1053
+ $("#force-disconnect").addEventListener("click", () => void forceDisconnect());
1054
+ $("#copy-stop-command").addEventListener("click", async () => {
1055
+ try { await navigator.clipboard.writeText($("#stop-command").textContent); toast("停止指令已复制,请在原电脑的工程目录执行"); }
1056
+ catch { showError(new Error("请手动复制停止指令")); }
1057
+ });
1020
1058
  $("#get-connect-command").addEventListener("click", () => {
1021
1059
  $("#connect-instructions").hidden = !$("#connect-instructions").hidden;
1022
1060
  $("#get-connect-command").setAttribute("aria-expanded", String(!$("#connect-instructions").hidden));
1023
1061
  void refreshConnection();
1024
1062
  });
1025
1063
  $("#copy-connect-command").addEventListener("click", async () => {
1026
- try { await navigator.clipboard.writeText($("#connect-command").textContent); toast("连接指令已复制,请在当前搭子的工程目录执行"); }
1064
+ try { await navigator.clipboard.writeText($("#connect-command").textContent); toast("连接指令已复制,在工程目录执行后返回当前页面即可"); }
1027
1065
  catch { showError(new Error("请手动复制下方的完整连接指令")); }
1028
1066
  });
1029
1067
  const statusTimer = window.setInterval(() => void refreshConnection(), 2000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@my-life-buddies/cli",
3
- "version": "0.17.6",
3
+ "version": "0.18.0",
4
4
  "type": "module",
5
5
  "description": "搭搭 Developer Platform 命令行工具",
6
6
  "publishConfig": {
@@ -39,7 +39,7 @@
39
39
  "ws": "8.21.3"
40
40
  },
41
41
  "devDependencies": {
42
- "@buddy/cli-core": "0.17.6",
43
- "@buddy/preview": "0.17.6"
42
+ "@buddy/cli-core": "0.18.0",
43
+ "@buddy/preview": "0.18.0"
44
44
  }
45
45
  }