Skip to content

Commit c71745b

Browse files
committed
add update check
1 parent fdf37a7 commit c71745b

4 files changed

Lines changed: 90 additions & 2 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "polar-cli",
3-
"version": "1.1.1",
3+
"version": "1.2.1",
44
"description": "",
55
"bin": "bin/cli.js",
66
"type": "module",

src/cli.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { update } from "./commands/update";
77
import * as Migration from "./services/migration/migrate";
88
import * as OAuth from "./services/oauth";
99
import * as Polar from "./services/polar";
10+
import { showUpdateNotice, checkForUpdateInBackground } from "./services/update-check";
1011
import { VERSION } from "./version";
1112

1213
const mainCommand = Command.make("polar").pipe(
@@ -30,4 +31,7 @@ const services = Layer.mergeAll(
3031
BunContext.layer
3132
);
3233

34+
showUpdateNotice();
35+
checkForUpdateInBackground();
36+
3337
cli(process.argv).pipe(Effect.provide(services), BunRuntime.runMain);

src/services/update-check.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { existsSync, readFileSync, mkdirSync } from "fs";
2+
import { writeFile } from "fs/promises";
3+
import { homedir } from "os";
4+
import { join } from "path";
5+
import { VERSION } from "../version";
6+
7+
const REPO = "polarsource/cli";
8+
const STATE_DIR = join(homedir(), ".polar");
9+
const STATE_FILE = join(STATE_DIR, "update-check.json");
10+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
11+
12+
interface UpdateCheckState {
13+
lastChecked: string;
14+
latestVersion: string;
15+
}
16+
17+
export function showUpdateNotice(): void {
18+
try {
19+
if (!existsSync(STATE_FILE)) return;
20+
21+
const raw = readFileSync(STATE_FILE, "utf-8");
22+
const state: UpdateCheckState = JSON.parse(raw);
23+
24+
if (!state.latestVersion || state.latestVersion === VERSION) return;
25+
26+
const dim = "\x1b[2m";
27+
const cyan = "\x1b[36m";
28+
const bold = "\x1b[1m";
29+
const reset = "\x1b[0m";
30+
31+
process.stderr.write(
32+
`\n ${dim}Update available:${reset} ${dim}${VERSION}${reset} ${dim}${reset} ${bold}${cyan}${state.latestVersion}${reset}\n` +
33+
` ${dim}Run${reset} ${cyan}polar update${reset} ${dim}to update${reset}\n\n`,
34+
);
35+
} catch {
36+
// Silently ignore any errors
37+
}
38+
}
39+
40+
export function checkForUpdateInBackground(): void {
41+
try {
42+
let shouldCheck = true;
43+
44+
if (existsSync(STATE_FILE)) {
45+
try {
46+
const raw = readFileSync(STATE_FILE, "utf-8");
47+
const state: UpdateCheckState = JSON.parse(raw);
48+
const lastChecked = new Date(state.lastChecked).getTime();
49+
if (Date.now() - lastChecked < CHECK_INTERVAL_MS) {
50+
shouldCheck = false;
51+
}
52+
} catch {
53+
// Corrupt file — re-check
54+
}
55+
}
56+
57+
if (!shouldCheck) return;
58+
59+
fetch(`https://api.github.com/repos/${REPO}/releases/latest`)
60+
.then((res) => {
61+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
62+
return res.json();
63+
})
64+
.then((data: { tag_name?: string }) => {
65+
if (!data.tag_name) return;
66+
67+
if (!existsSync(STATE_DIR)) {
68+
mkdirSync(STATE_DIR, { recursive: true });
69+
}
70+
71+
const state: UpdateCheckState = {
72+
lastChecked: new Date().toISOString(),
73+
latestVersion: data.tag_name,
74+
};
75+
76+
return writeFile(STATE_FILE, JSON.stringify(state, null, 2));
77+
})
78+
.catch(() => {
79+
// Silently ignore all errors
80+
});
81+
} catch {
82+
// Silently ignore any errors
83+
}
84+
}

src/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const VERSION = "v1.1.1";
1+
export const VERSION = "v1.2.0";

0 commit comments

Comments
 (0)