macroclaw 0.14.0 → 0.15.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/package.json +1 -1
- package/src/cli.test.ts +20 -4
- package/src/cli.ts +13 -2
- package/src/system-service.test.ts +43 -8
- package/src/system-service.ts +22 -16
package/package.json
CHANGED
package/src/cli.test.ts
CHANGED
|
@@ -115,7 +115,8 @@ function mockService(overrides?: Record<string, unknown>): SystemServiceManager
|
|
|
115
115
|
uninstall: mock(() => {}),
|
|
116
116
|
start: mock(() => ""),
|
|
117
117
|
stop: mock(() => {}),
|
|
118
|
-
update: mock(() => ""),
|
|
118
|
+
update: mock(() => ({ previousVersion: "0.6.0", currentVersion: "0.7.0" })),
|
|
119
|
+
isRunning: false,
|
|
119
120
|
status: mock(() => ({ installed: false, running: false, platform: "systemd" as const })),
|
|
120
121
|
logs: mock(() => "journalctl -u macroclaw -n 50 --no-pager"),
|
|
121
122
|
...overrides,
|
|
@@ -151,11 +152,26 @@ describe("Cli.service", () => {
|
|
|
151
152
|
expect(stop).toHaveBeenCalled();
|
|
152
153
|
});
|
|
153
154
|
|
|
154
|
-
it("runs update action", () => {
|
|
155
|
-
const
|
|
156
|
-
const
|
|
155
|
+
it("runs update action — stops and starts when running", () => {
|
|
156
|
+
const stop = mock(() => {});
|
|
157
|
+
const start = mock(() => "tail -f /logs");
|
|
158
|
+
const update = mock(() => ({ previousVersion: "0.6.0", currentVersion: "0.7.0" }));
|
|
159
|
+
const cli = new Cli({ systemService: mockService({ stop, start, update, isRunning: true }) });
|
|
157
160
|
cli.service("update");
|
|
161
|
+
expect(stop).toHaveBeenCalled();
|
|
158
162
|
expect(update).toHaveBeenCalled();
|
|
163
|
+
expect(start).toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("runs update action — skips stop but still starts when not running", () => {
|
|
167
|
+
const stop = mock(() => {});
|
|
168
|
+
const start = mock(() => "tail -f /logs");
|
|
169
|
+
const update = mock(() => ({ previousVersion: "0.6.0", currentVersion: "0.7.0" }));
|
|
170
|
+
const cli = new Cli({ systemService: mockService({ stop, start, update, isRunning: false }) });
|
|
171
|
+
cli.service("update");
|
|
172
|
+
expect(stop).not.toHaveBeenCalled();
|
|
173
|
+
expect(update).toHaveBeenCalled();
|
|
174
|
+
expect(start).toHaveBeenCalled();
|
|
159
175
|
});
|
|
160
176
|
|
|
161
177
|
it("runs status action", () => {
|
package/src/cli.ts
CHANGED
|
@@ -55,8 +55,19 @@ export class Cli {
|
|
|
55
55
|
console.log("Service stopped.");
|
|
56
56
|
break;
|
|
57
57
|
case "update": {
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
if (this.#serviceManager.isRunning) {
|
|
59
|
+
this.#serviceManager.stop();
|
|
60
|
+
console.log("Service stopped.");
|
|
61
|
+
}
|
|
62
|
+
const result = this.#serviceManager.update();
|
|
63
|
+
if (result.previousVersion === result.currentVersion) {
|
|
64
|
+
console.log(`macroclaw v${result.currentVersion} (already up to date)`);
|
|
65
|
+
} else {
|
|
66
|
+
console.log(`Updated macroclaw v${result.previousVersion} → v${result.currentVersion}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const logCmd = this.#serviceManager.start();
|
|
70
|
+
console.log(`Service started. Check logs:\n ${logCmd}`);
|
|
60
71
|
break;
|
|
61
72
|
}
|
|
62
73
|
case "status": {
|
|
@@ -600,23 +600,58 @@ describe("update", () => {
|
|
|
600
600
|
);
|
|
601
601
|
});
|
|
602
602
|
|
|
603
|
-
it("
|
|
603
|
+
it("runs bun install without stop/start", () => {
|
|
604
604
|
const tmpHome = `/tmp/macroclaw-test-updateld-${Date.now()}`;
|
|
605
605
|
mkdirSync(join(tmpHome, "Library/LaunchAgents"), { recursive: true });
|
|
606
606
|
writeFileSync(join(tmpHome, "Library/LaunchAgents/com.macroclaw.plist"), "test");
|
|
607
607
|
|
|
608
608
|
mockExecSync.mockImplementation((cmd: string) => {
|
|
609
|
-
if (cmd
|
|
609
|
+
if (cmd === "bun pm ls -g") return "macroclaw@0.6.0\n";
|
|
610
610
|
return "";
|
|
611
611
|
});
|
|
612
612
|
const mgr = createManager({ platform: "darwin", home: tmpHome });
|
|
613
|
-
mgr.update();
|
|
614
|
-
expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining("launchctl unload"), expect.anything());
|
|
613
|
+
const result = mgr.update();
|
|
615
614
|
expect(mockExecSync).toHaveBeenCalledWith("bun install -g macroclaw@latest", expect.anything());
|
|
616
|
-
expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining("launchctl
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
615
|
+
expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining("launchctl"), expect.anything());
|
|
616
|
+
expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining("systemctl"), expect.anything());
|
|
617
|
+
expect(result.previousVersion).toBe("0.6.0");
|
|
618
|
+
expect(result.currentVersion).toBe("0.6.0");
|
|
619
|
+
rmSync(tmpHome, { recursive: true });
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
it("returns different versions when update changes version", () => {
|
|
623
|
+
const tmpHome = `/tmp/macroclaw-test-updatever-${Date.now()}`;
|
|
624
|
+
mkdirSync(join(tmpHome, "Library/LaunchAgents"), { recursive: true });
|
|
625
|
+
writeFileSync(join(tmpHome, "Library/LaunchAgents/com.macroclaw.plist"), "test");
|
|
626
|
+
|
|
627
|
+
let installCalled = false;
|
|
628
|
+
mockExecSync.mockImplementation((cmd: string) => {
|
|
629
|
+
if (cmd.startsWith("launchctl list ")) return LAUNCHD_STOPPED;
|
|
630
|
+
if (cmd === "bun install -g macroclaw@latest") { installCalled = true; return ""; }
|
|
631
|
+
if (cmd === "bun pm ls -g") return installCalled ? "macroclaw@0.7.0\n" : "macroclaw@0.6.0\n";
|
|
632
|
+
return "";
|
|
633
|
+
});
|
|
634
|
+
const mgr = createManager({ platform: "darwin", home: tmpHome });
|
|
635
|
+
const result = mgr.update();
|
|
636
|
+
expect(result.previousVersion).toBe("0.6.0");
|
|
637
|
+
expect(result.currentVersion).toBe("0.7.0");
|
|
638
|
+
rmSync(tmpHome, { recursive: true });
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
it("returns unknown when version query fails", () => {
|
|
642
|
+
const tmpHome = `/tmp/macroclaw-test-updateunk-${Date.now()}`;
|
|
643
|
+
mkdirSync(join(tmpHome, "Library/LaunchAgents"), { recursive: true });
|
|
644
|
+
writeFileSync(join(tmpHome, "Library/LaunchAgents/com.macroclaw.plist"), "test");
|
|
645
|
+
|
|
646
|
+
mockExecSync.mockImplementation((cmd: string) => {
|
|
647
|
+
if (cmd.startsWith("launchctl list ")) return LAUNCHD_STOPPED;
|
|
648
|
+
if (cmd === "bun pm ls -g") throw new Error("command not found");
|
|
649
|
+
return "";
|
|
650
|
+
});
|
|
651
|
+
const mgr = createManager({ platform: "darwin", home: tmpHome });
|
|
652
|
+
const result = mgr.update();
|
|
653
|
+
expect(result.previousVersion).toBe("unknown");
|
|
654
|
+
expect(result.currentVersion).toBe("unknown");
|
|
620
655
|
rmSync(tmpHome, { recursive: true });
|
|
621
656
|
});
|
|
622
657
|
});
|
package/src/system-service.ts
CHANGED
|
@@ -24,6 +24,11 @@ export interface ServiceStatus {
|
|
|
24
24
|
uptime?: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export interface UpdateResult {
|
|
28
|
+
previousVersion: string;
|
|
29
|
+
currentVersion: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
export class SystemServiceManager {
|
|
28
33
|
readonly #platform: Platform;
|
|
29
34
|
readonly #home: string;
|
|
@@ -179,25 +184,15 @@ export class SystemServiceManager {
|
|
|
179
184
|
log.debug("Service stopped");
|
|
180
185
|
}
|
|
181
186
|
|
|
182
|
-
update():
|
|
187
|
+
update(): UpdateResult {
|
|
183
188
|
this.#requireInstalled();
|
|
184
189
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
this.#exec("bun install -g macroclaw@latest");
|
|
190
|
-
this.#exec(`launchctl load ${this.serviceFilePath}`);
|
|
191
|
-
} else {
|
|
192
|
-
if (this.isRunning) {
|
|
193
|
-
this.#sudo("systemctl stop macroclaw");
|
|
194
|
-
}
|
|
195
|
-
this.#exec("bun install -g macroclaw@latest");
|
|
196
|
-
this.#sudo("systemctl start macroclaw");
|
|
197
|
-
}
|
|
190
|
+
const previousVersion = this.#getInstalledVersion();
|
|
191
|
+
this.#exec("bun install -g macroclaw@latest");
|
|
192
|
+
const currentVersion = this.#getInstalledVersion();
|
|
198
193
|
|
|
199
|
-
log.debug(
|
|
200
|
-
return
|
|
194
|
+
log.debug({ previousVersion, currentVersion }, "Service updated");
|
|
195
|
+
return { previousVersion, currentVersion };
|
|
201
196
|
}
|
|
202
197
|
|
|
203
198
|
status(): ServiceStatus {
|
|
@@ -261,6 +256,17 @@ export class SystemServiceManager {
|
|
|
261
256
|
return binPath;
|
|
262
257
|
}
|
|
263
258
|
|
|
259
|
+
|
|
260
|
+
#getInstalledVersion(): string {
|
|
261
|
+
try {
|
|
262
|
+
const output = this.#exec("bun pm ls -g");
|
|
263
|
+
const match = /macroclaw@(\S+)/.exec(output);
|
|
264
|
+
return match?.[1] ?? "unknown";
|
|
265
|
+
} catch {
|
|
266
|
+
return "unknown";
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
264
270
|
#requireInstalled(): void {
|
|
265
271
|
if (!this.isInstalled) {
|
|
266
272
|
throw new Error("Service not installed. Run `macroclaw service install` first.");
|