@hienlh/ppm 0.8.36 → 0.8.37

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.37] - 2026-03-24
4
+
5
+ ### Fixed
6
+ - **Intel Mac AVX crash**: Use baseline x64 builds that work on all Intel CPUs (no AVX requirement)
7
+ - **Linux ARM64**: Add `ppm-linux-arm64` binary for Raspberry Pi / ARM servers
8
+
3
9
  ## [0.8.36] - 2026-03-24
4
10
 
5
11
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.8.36",
3
+ "version": "0.8.37",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -23,9 +23,10 @@ mkdir -p dist
23
23
 
24
24
  TARGETS=(
25
25
  "bun-darwin-arm64:ppm-darwin-arm64"
26
- "bun-darwin-x64:ppm-darwin-x64"
27
- "bun-linux-x64:ppm-linux-x64"
28
- "bun-windows-x64:ppm-windows-x64.exe"
26
+ "bun-darwin-x64-baseline:ppm-darwin-x64"
27
+ "bun-linux-x64-baseline:ppm-linux-x64"
28
+ "bun-linux-arm64:ppm-linux-arm64"
29
+ "bun-windows-x64-baseline:ppm-windows-x64.exe"
29
30
  )
30
31
 
31
32
  for entry in "${TARGETS[@]}"; do
@@ -237,6 +237,14 @@ export async function startServer(options: {
237
237
  }
238
238
  }
239
239
 
240
+ // Write preliminary status.json so child process can read shareUrl on startup
241
+ // (child reads this before parent has a chance to write PID — fixes race condition)
242
+ writeFileSync(statusFile, JSON.stringify({
243
+ port, host,
244
+ shareUrl: shareUrl ?? null,
245
+ tunnelPid: tunnelPid ?? null,
246
+ }));
247
+
240
248
  // Spawn server child process with log file
241
249
  const { openSync } = await import("node:fs");
242
250
  const logFile = resolve(ppmDir, "ppm.log");
@@ -300,8 +308,8 @@ export async function startServer(options: {
300
308
  process.exit(1);
301
309
  }
302
310
 
303
- // Write status file with both PIDs + server script path for restart
304
- const status = { pid: childPid, port, host, shareUrl, tunnelPid, serverScript: script };
311
+ // Update status file with child PID + server script path for restart
312
+ const status = { pid: childPid, port, host, shareUrl: shareUrl ?? null, tunnelPid: tunnelPid ?? null, serverScript: script };
305
313
  writeFileSync(statusFile, JSON.stringify(status));
306
314
  writeFileSync(pidFile, String(childPid));
307
315
 
@@ -1,24 +1,9 @@
1
1
  import { Hono } from "hono";
2
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
- import { resolve } from "node:path";
4
- import { homedir } from "node:os";
5
2
  import { tunnelService } from "../../services/tunnel.service.ts";
6
3
  import { configService } from "../../services/config.service.ts";
7
4
  import { getLocalIp } from "../../lib/network-utils.ts";
8
5
  import { ok, err } from "../../types/api.ts";
9
6
 
10
- /** Patch shareUrl + tunnelPid in status.json so `ppm status` reflects web-started tunnels */
11
- function patchStatusFile(shareUrl: string | null): void {
12
- const path = resolve(homedir(), ".ppm", "status.json");
13
- if (!existsSync(path)) return;
14
- try {
15
- const data = JSON.parse(readFileSync(path, "utf-8"));
16
- data.shareUrl = shareUrl;
17
- data.tunnelPid = shareUrl ? tunnelService.getTunnelPid() : null;
18
- writeFileSync(path, JSON.stringify(data));
19
- } catch { /* ignore — status.json may be absent in dev */ }
20
- }
21
-
22
7
  export const tunnelRoutes = new Hono();
23
8
 
24
9
  /** GET /api/tunnel — current tunnel status + local URL */
@@ -40,7 +25,6 @@ tunnelRoutes.post("/start", async (c) => {
40
25
  try {
41
26
  const port = configService.get("port") ?? 8080;
42
27
  const url = await tunnelService.startTunnel(port);
43
- patchStatusFile(url);
44
28
 
45
29
  // Sync tunnel URL to PPM Cloud (if linked)
46
30
  import("../../services/cloud.service.ts")
@@ -58,7 +42,6 @@ tunnelRoutes.post("/start", async (c) => {
58
42
  /** POST /api/tunnel/stop — stop tunnel */
59
43
  tunnelRoutes.post("/stop", (c) => {
60
44
  tunnelService.stopTunnel();
61
- patchStatusFile(null);
62
45
 
63
46
  // Stop cloud heartbeat
64
47
  import("../../services/cloud.service.ts")
@@ -1,7 +1,7 @@
1
1
  import type { Subprocess } from "bun";
2
2
  import { resolve } from "node:path";
3
3
  import { homedir } from "node:os";
4
- import { existsSync, unlinkSync } from "node:fs";
4
+ import { existsSync, unlinkSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import { ensureCloudflared } from "./cloudflared.service.ts";
6
6
 
7
7
  const TUNNEL_URL_REGEX = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
@@ -81,6 +81,7 @@ class TunnelService {
81
81
  });
82
82
 
83
83
  this.url = url;
84
+ this.persistToStatusFile();
84
85
  return url;
85
86
  }
86
87
 
@@ -108,6 +109,7 @@ class TunnelService {
108
109
  this.externalPid = null;
109
110
  }
110
111
  this.url = null;
112
+ this.persistToStatusFile();
111
113
  }
112
114
 
113
115
  /** Get current tunnel URL (null if not running) */
@@ -129,6 +131,18 @@ class TunnelService {
129
131
  setExternalPid(pid: number): void {
130
132
  this.externalPid = pid;
131
133
  }
134
+
135
+ /** Persist shareUrl + tunnelPid to status.json (central write point) */
136
+ private persistToStatusFile(): void {
137
+ const statusFile = resolve(homedir(), ".ppm", "status.json");
138
+ if (!existsSync(statusFile)) return;
139
+ try {
140
+ const data = JSON.parse(readFileSync(statusFile, "utf-8"));
141
+ data.shareUrl = this.url;
142
+ data.tunnelPid = this.getTunnelPid() ?? null;
143
+ writeFileSync(statusFile, JSON.stringify(data));
144
+ } catch {}
145
+ }
132
146
  }
133
147
 
134
148
  /** Singleton tunnel service */