pi-updater 0.2.9 → 0.3.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/CHANGELOG.md +7 -0
- package/README.md +3 -1
- package/index.ts +104 -24
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 - 2026-03-23
|
|
4
|
+
|
|
5
|
+
- Auto-restart pi after a successful update. Asks to restart, then seamlessly relaunches on the current session.
|
|
6
|
+
- Falls back to manual restart message in non-interactive modes or if restart fails.
|
|
7
|
+
- Cross-platform: uses `shell: true` on Windows to handle `.cmd` shims.
|
|
8
|
+
- `/update --test` to simulate the full update flow without a real install.
|
|
9
|
+
|
|
3
10
|
## 0.2.9 - 2026-03-16
|
|
4
11
|
|
|
5
12
|
- Keep startup checks cache-first and non-blocking.
|
package/README.md
CHANGED
|
@@ -12,10 +12,12 @@ A lightweight, Codex-style auto-updater for pi with fast, cache-first startup ch
|
|
|
12
12
|
## What it does
|
|
13
13
|
|
|
14
14
|
**On startup:** if a newer version is available, shows a prompt:
|
|
15
|
-
- **Update now** — install with npm, then restart pi
|
|
15
|
+
- **Update now** — install with npm, then auto-restart pi on the current session
|
|
16
16
|
- **Skip** — dismiss until next session
|
|
17
17
|
- **Skip this version** — don't ask again until a newer version appears
|
|
18
18
|
|
|
19
|
+
After a successful update, pi-updater asks whether to restart immediately. If confirmed, pi relaunches seamlessly on the current session. In non-interactive modes or if auto-restart fails, it falls back to a manual restart message.
|
|
20
|
+
|
|
19
21
|
**In the background (once per run):** performs one live npm check and can show the prompt in the same session when a new release is detected.
|
|
20
22
|
|
|
21
23
|
**`/update`:** manually check for updates (always fetches fresh from npm, unless `PI_OFFLINE` is set).
|
package/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
ExtensionContext,
|
|
4
4
|
} from "@mariozechner/pi-coding-agent";
|
|
5
5
|
import { VERSION, BorderedLoader } from "@mariozechner/pi-coding-agent";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
6
7
|
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
7
8
|
import { join, dirname } from "node:path";
|
|
8
9
|
import { homedir } from "node:os";
|
|
@@ -127,6 +128,40 @@ export default function (pi: ExtensionAPI) {
|
|
|
127
128
|
const promptedVersions = new Set<string>();
|
|
128
129
|
let liveCheckStarted = false;
|
|
129
130
|
|
|
131
|
+
async function findPiBinary(): Promise<string> {
|
|
132
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
133
|
+
const result = await pi.exec(cmd, ["pi"]);
|
|
134
|
+
if (result.code === 0 && result.stdout?.trim()) {
|
|
135
|
+
return result.stdout.trim().split(/\r?\n/)[0];
|
|
136
|
+
}
|
|
137
|
+
return "pi";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function canAutoRestart(ctx: ExtensionContext): boolean {
|
|
141
|
+
return ctx.hasUI && !!process.stdin.isTTY && !!process.stdout.isTTY;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function restartPi(ctx: ExtensionContext): Promise<boolean> {
|
|
145
|
+
const piBinary = await findPiBinary();
|
|
146
|
+
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
147
|
+
const restartArgs = sessionFile ? ["--session", sessionFile] : ["-c"];
|
|
148
|
+
|
|
149
|
+
return ctx.ui.custom<boolean>((tui, _theme, _kb, done) => {
|
|
150
|
+
tui.stop();
|
|
151
|
+
const result = spawnSync(piBinary, restartArgs, {
|
|
152
|
+
cwd: ctx.cwd,
|
|
153
|
+
env: process.env,
|
|
154
|
+
stdio: "inherit",
|
|
155
|
+
shell: process.platform === "win32",
|
|
156
|
+
windowsHide: false,
|
|
157
|
+
});
|
|
158
|
+
tui.start();
|
|
159
|
+
tui.requestRender(true);
|
|
160
|
+
done(!result.error && (result.status === null || result.status === 0));
|
|
161
|
+
return { render: () => [], invalidate: () => {} };
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
130
165
|
async function doInstall(
|
|
131
166
|
ctx: ExtensionContext,
|
|
132
167
|
latest: string,
|
|
@@ -136,37 +171,50 @@ export default function (pi: ExtensionAPI) {
|
|
|
136
171
|
const loader = new BorderedLoader(tui, theme, `Installing ${latest}...`);
|
|
137
172
|
loader.onAbort = () => done(false);
|
|
138
173
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
);
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
return true;
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
run()
|
|
158
|
-
.then(done)
|
|
174
|
+
pi.exec(cmd.program, cmd.args, { timeout: 120_000 })
|
|
175
|
+
.then((result) => {
|
|
176
|
+
if (result.code !== 0) {
|
|
177
|
+
ctx.ui.notify(
|
|
178
|
+
`Update failed (exit ${result.code}): ${result.stderr || result.stdout}`,
|
|
179
|
+
"error",
|
|
180
|
+
);
|
|
181
|
+
done(false);
|
|
182
|
+
} else {
|
|
183
|
+
done(true);
|
|
184
|
+
}
|
|
185
|
+
})
|
|
159
186
|
.catch(() => done(false));
|
|
187
|
+
|
|
160
188
|
return loader;
|
|
161
189
|
});
|
|
162
190
|
|
|
163
191
|
if (!success) return;
|
|
164
192
|
|
|
165
|
-
|
|
193
|
+
if (!canAutoRestart(ctx)) {
|
|
194
|
+
ctx.ui.notify(
|
|
195
|
+
`Updated to ${latest}! Please restart pi.\nTip: run \`pi -c\` to continue this session.`,
|
|
196
|
+
"info",
|
|
197
|
+
);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const restart = await ctx.ui.confirm(
|
|
166
202
|
`Updated to ${latest}!`,
|
|
167
|
-
"
|
|
203
|
+
"Restart now?",
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
if (!restart) return;
|
|
207
|
+
|
|
208
|
+
const ok = await restartPi(ctx);
|
|
209
|
+
if (ok) {
|
|
210
|
+
ctx.shutdown();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
ctx.ui.notify(
|
|
215
|
+
`Updated to ${latest}! Auto-restart failed. Please restart pi manually.\nTip: run \`pi -c\` to continue this session.`,
|
|
216
|
+
"error",
|
|
168
217
|
);
|
|
169
|
-
if (ok) ctx.shutdown();
|
|
170
218
|
}
|
|
171
219
|
|
|
172
220
|
async function showUpdatePrompt(ctx: ExtensionContext, latest: string) {
|
|
@@ -234,7 +282,39 @@ export default function (pi: ExtensionAPI) {
|
|
|
234
282
|
|
|
235
283
|
pi.registerCommand("update", {
|
|
236
284
|
description: "Check for pi updates and install",
|
|
237
|
-
handler: async (
|
|
285
|
+
handler: async (rawArgs, ctx) => {
|
|
286
|
+
// /update --test — simulate the full UI flow without a real install
|
|
287
|
+
if (rawArgs?.trim() === "--test") {
|
|
288
|
+
const fakeLatest = "99.0.0";
|
|
289
|
+
const cmd = getInstallCommand(fakeLatest);
|
|
290
|
+
const choice = await ctx.ui.select(`Update ${VERSION} → ${fakeLatest}`, [
|
|
291
|
+
`Update now (${fmtCmd(cmd)})`,
|
|
292
|
+
"Skip",
|
|
293
|
+
"Skip this version",
|
|
294
|
+
]);
|
|
295
|
+
if (!choice || choice === "Skip" || choice === "Skip this version") return;
|
|
296
|
+
|
|
297
|
+
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
|
298
|
+
const loader = new BorderedLoader(tui, theme, `Installing ${fakeLatest}...`);
|
|
299
|
+
loader.onAbort = () => done();
|
|
300
|
+
setTimeout(() => done(), 1500);
|
|
301
|
+
return loader;
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (!canAutoRestart(ctx)) {
|
|
305
|
+
ctx.ui.notify(`Updated to ${fakeLatest}! Please restart pi.`, "info");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const restart = await ctx.ui.confirm(`Updated to ${fakeLatest}!`, "Restart now?");
|
|
310
|
+
if (!restart) return;
|
|
311
|
+
|
|
312
|
+
const ok = await restartPi(ctx);
|
|
313
|
+
if (ok) { ctx.shutdown(); return; }
|
|
314
|
+
ctx.ui.notify("Test restart failed.", "error");
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
238
318
|
if (isOffline()) {
|
|
239
319
|
ctx.ui.notify(
|
|
240
320
|
"PI_OFFLINE is set. Disable it to check for updates.",
|