ework-web 0.10.2 → 0.10.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-web",
3
- "version": "0.10.2",
3
+ "version": "0.10.4",
4
4
  "type": "module",
5
5
  "description": "ework-web — standalone multi-project issue tracker. Local SQLite-backed, no external API dependency. Bun + TypeScript + SSR HTML.",
6
6
  "license": "MIT",
@@ -78,14 +78,16 @@ function buildEnvBlock(env: Map<string, string>, target: DeployTarget): string {
78
78
  if (v === undefined) continue;
79
79
  lines.push(`${k}=${v}`);
80
80
  }
81
- lines.push(`WORK_DB_HOST=${target.mysqlHost}`);
81
+ const mysqlHost = String(target.mysqlHost).replace(/:\d+$/, "");
82
+ lines.push(`WORK_DB_HOST=${mysqlHost}`);
82
83
  lines.push(`DAEMON_PORT=${String(target.daemonPort ?? 3101)}`);
83
84
  lines.push(`DAEMON_HOST=0.0.0.0`);
84
85
  lines.push(`DAEMON_ENV=production`);
85
86
  return lines.join("\n");
86
87
  }
87
88
 
88
- function buildSetupScript(envBlock: string, daemonPort: number): string {
89
+ function buildSetupScript(envBlock: string, daemonPort: number, mysqlHostRaw: string, mysqlPort: string): string {
90
+ const mysqlHost = mysqlHostRaw.replace(/:\d+$/, "");
89
91
  return [
90
92
  "set -e",
91
93
  `command -v npm >/dev/null 2>&1 || { curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -; sudo apt-get install -y nodejs; }`,
@@ -95,9 +97,11 @@ function buildSetupScript(envBlock: string, daemonPort: number): string {
95
97
  `mkdir -p "$HOME/.local/lib"`,
96
98
  `npm config set prefix "$HOME/.local/lib"`,
97
99
  `grep -q '.local/lib/bin' "$HOME/.bashrc" 2>/dev/null || echo 'export PATH="$HOME/.local/lib/bin:$HOME/.bun/bin:$PATH"' >> "$HOME/.bashrc"`,
98
- `echo "[1/4] installing ework-aio..."`,
100
+ `echo "[1/5] checking MySQL connectivity to ${mysqlHost}:${mysqlPort}..."`,
101
+ `timeout 5 bash -c "echo > /dev/tcp/${mysqlHost}/${mysqlPort}" 2>/dev/null && echo "MySQL port reachable" || { echo "MySQL UNREACHABLE at ${mysqlHost}:${mysqlPort} — daemon will fail to connect"; exit 1; }`,
102
+ `echo "[2/5] installing ework-aio..."`,
99
103
  `npm install -g ework-aio`,
100
- `echo "[2/4] writing daemon config..."`,
104
+ `echo "[3/5] writing daemon config..."`,
101
105
  `mkdir -p ~/.local/share/ework-aio/ework-daemon`,
102
106
  `REMOTE_IP=$(hostname -I 2>/dev/null | awk '{print $1}')`,
103
107
  `DAEMON_ENDPOINT_LINE=""`,
@@ -106,9 +110,9 @@ function buildSetupScript(envBlock: string, daemonPort: number): string {
106
110
  envBlock,
107
111
  `EWORK_DAEMON_ENV_EOF`,
108
112
  `echo "$DAEMON_ENDPOINT_LINE"; } > ~/.local/share/ework-aio/ework-daemon/.env`,
109
- `echo "[3/4] starting daemon..."`,
113
+ `echo "[4/5] starting daemon..."`,
110
114
  `ework-aio start daemon`,
111
- `echo "[4/4] verifying daemon is alive..."`,
115
+ `echo "[5/5] verifying daemon is alive..."`,
112
116
  `sleep 2`,
113
117
  `curl -sf --max-time 3 http://127.0.0.1:${String(daemonPort)}/api/status >/dev/null 2>&1 && echo "DAEMON_ALIVE" || echo "DAEMON_WARNING: status check failed (may need a moment to boot)"`,
114
118
  `echo DAEMON_STARTED`,
@@ -132,7 +136,7 @@ export async function deployRemoteDaemon(opts: DeployOpts): Promise<DeployResult
132
136
  }
133
137
  const env = parseEnvFile(envContent);
134
138
  const envBlock = buildEnvBlock(env, opts);
135
- const script = buildSetupScript(envBlock, opts.daemonPort ?? 3101);
139
+ const script = buildSetupScript(envBlock, opts.daemonPort ?? 3101, opts.mysqlHost, String(env.get("WORK_DB_PORT") ?? "3306"));
136
140
 
137
141
  const args = [
138
142
  "ssh",
package/src/index.ts CHANGED
@@ -973,25 +973,31 @@ async function handle(req: Request, url: URL, ip: string, ctx: { authed: boolean
973
973
  if (targets.length === 0 || !mysqlHost) {
974
974
  return json({ ok: false, error: "至少需要一个目标 + mysqlHost" }, 400);
975
975
  }
976
- if (targets.length === 1) {
977
- const [single] = targets;
978
- if (!single) return json({ ok: false, error: "no target" }, 400);
979
- const result = await deployRemoteDaemon({ ...single, timeoutMs });
980
- return json(result, result.ok ? 200 : 422);
981
- }
982
976
 
983
977
  const { readable, writable } = new TransformStream();
984
978
  const writer = writable.getWriter();
985
979
  const enc = new TextEncoder();
986
980
  const send = (s: string) => writer.write(enc.encode(`data: ${JSON.stringify(s)}\n\n`));
987
981
 
988
- deployBatch(targets, timeoutMs, (label, chunk) => send(`[${label}] ${chunk}`))
989
- .then((results) => {
990
- const ok = [...results.values()].every((r) => r.ok);
991
- send(`\n${ok ? "ALL OK" : "SOME FAILED"}: ${[...results.entries()].map(([k, v]) => `${k}=${v.ok ? "✓" : "✗"}`).join(", ")}`);
992
- })
993
- .catch((e) => send(`ERROR: ${e instanceof Error ? e.message : String(e)}`))
994
- .finally(() => writer.close());
982
+ if (targets.length === 1) {
983
+ const [single] = targets;
984
+ send(`▶ 部署到 ${single!.sshHost}:${single!.daemonPort ?? 3101}(超时 ${timeoutMs / 1000}s)...\n\n`);
985
+ deployRemoteDaemon({ ...single!, timeoutMs, onOutput: (chunk) => send(chunk) })
986
+ .then((result) => {
987
+ send(`\n${result.ok ? "✓ 成功" : "✗ 失败"}: ${result.ok ? "daemon 已部署" : (result.error || "未知错误")}\n`);
988
+ if (result.output) send(result.output);
989
+ })
990
+ .catch((e) => send(`\n✗ 异常: ${e instanceof Error ? e.message : String(e)}\n`))
991
+ .finally(() => writer.close());
992
+ } else {
993
+ deployBatch(targets, timeoutMs, (label, chunk) => send(`[${label}] ${chunk}`))
994
+ .then((results) => {
995
+ const ok = [...results.values()].every((r) => r.ok);
996
+ send(`\n${ok ? "ALL OK" : "SOME FAILED"}: ${[...results.entries()].map(([k, v]) => `${k}=${v.ok ? "✓" : "✗"}`).join(", ")}`);
997
+ })
998
+ .catch((e) => send(`ERROR: ${e instanceof Error ? e.message : String(e)}`))
999
+ .finally(() => writer.close());
1000
+ }
995
1001
 
996
1002
  return new Response(readable, {
997
1003
  headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" },
@@ -247,6 +247,11 @@
247
247
  if (deployBtn) deployBtn.disabled = false;
248
248
  return;
249
249
  }
250
+ if (/^(127\.|localhost$|0\.0\.0\.0$)/.test(mysqlHost)) {
251
+ setDeployResult('✗ MySQL 主机必须是远程机器可达的地址(不能是 ' + mysqlHost + ')。请使用 LAN IP,如 192.168.x.x', 'err');
252
+ if (deployBtn) deployBtn.disabled = false;
253
+ return;
254
+ }
250
255
 
251
256
  deployResultEl.textContent = '';
252
257
  appendDeployLog('▶ 部署 ' + targets.length + ' 个目标(超时 ' + (timeoutMs / 1000) + 's/个)...\n\n');