watchwhere 0.2.2 → 0.2.3
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/i18n.ts +11 -4
- package/src/index.ts +26 -1
- package/src/update-check.ts +73 -0
package/package.json
CHANGED
package/src/i18n.ts
CHANGED
|
@@ -127,6 +127,9 @@ interface Catalog {
|
|
|
127
127
|
// non-TTY
|
|
128
128
|
ttyRequired: (cmd: string) => string;
|
|
129
129
|
ambiguousQuery: (n: number) => string;
|
|
130
|
+
|
|
131
|
+
// update notifier
|
|
132
|
+
updateAvailable: (version: string) => string;
|
|
130
133
|
}
|
|
131
134
|
|
|
132
135
|
const en: Catalog = {
|
|
@@ -228,8 +231,10 @@ const en: Catalog = {
|
|
|
228
231
|
relativeHours: (n) => `${n} hr ago`,
|
|
229
232
|
relativeDays: (n) => `${n} day${n === 1 ? "" : "s"} ago`,
|
|
230
233
|
|
|
231
|
-
ttyRequired: (cmd) => `\`ww ${cmd}\` is interactive
|
|
232
|
-
ambiguousQuery: (n) => `${n} matches
|
|
234
|
+
ttyRequired: (cmd) => `\`ww ${cmd}\` is interactive. run it in a terminal, not a pipe.`,
|
|
235
|
+
ambiguousQuery: (n) => `${n} matches, query is ambiguous. refine, or run interactively.`,
|
|
236
|
+
|
|
237
|
+
updateAvailable: (version) => `new version available: ${version} (bun install -g watchwhere)`,
|
|
233
238
|
};
|
|
234
239
|
|
|
235
240
|
const tr: Catalog = {
|
|
@@ -334,8 +339,10 @@ const tr: Catalog = {
|
|
|
334
339
|
relativeHours: (n) => `${n} saat önce`,
|
|
335
340
|
relativeDays: (n) => `${n} gün önce`,
|
|
336
341
|
|
|
337
|
-
ttyRequired: (cmd) => `\`ww ${cmd}\` interaktif
|
|
338
|
-
ambiguousQuery: (n) => `${n} eşleşme var
|
|
342
|
+
ttyRequired: (cmd) => `\`ww ${cmd}\` interaktif. pipe'da değil, terminalde çalıştır.`,
|
|
343
|
+
ambiguousQuery: (n) => `${n} eşleşme var, sorgu belirsiz. daralt veya interaktif çalıştır.`,
|
|
344
|
+
|
|
345
|
+
updateAvailable: (version) => `yeni sürüm var: ${version} (bun install -g watchwhere)`,
|
|
339
346
|
};
|
|
340
347
|
|
|
341
348
|
const CATALOGS: Record<Locale, Catalog> = { en, tr };
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { runSubs } from "./commands/subs.ts";
|
|
|
10
10
|
import { runLang } from "./commands/lang.ts";
|
|
11
11
|
import { runRegion } from "./commands/region.ts";
|
|
12
12
|
import { TmdbError } from "./tmdb.ts";
|
|
13
|
+
import { checkForUpdate } from "./update-check.ts";
|
|
13
14
|
|
|
14
15
|
function usage(m: ReturnType<typeof t>): string {
|
|
15
16
|
return [
|
|
@@ -125,7 +126,31 @@ async function main(): Promise<void> {
|
|
|
125
126
|
await runSearch(args.join(" "), cfg);
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
|
|
129
|
+
async function maybeNotifyUpdate(): Promise<void> {
|
|
130
|
+
// skip on quick lookups, only show after real work
|
|
131
|
+
const first = process.argv[2];
|
|
132
|
+
if (
|
|
133
|
+
!first ||
|
|
134
|
+
first === "--version" ||
|
|
135
|
+
first === "-v" ||
|
|
136
|
+
first === "--help" ||
|
|
137
|
+
first === "-h"
|
|
138
|
+
) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const newer = await checkForUpdate(pkg.version);
|
|
143
|
+
if (!newer) return;
|
|
144
|
+
const cfg = await loadConfig().catch(() => null);
|
|
145
|
+
const m = t(resolveLocale(cfg?.language));
|
|
146
|
+
console.log();
|
|
147
|
+
console.log(c.dim(` ${c.yellow("›")} ${m.updateAvailable(newer)}`));
|
|
148
|
+
} catch {
|
|
149
|
+
// best-effort, never block exit
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
main().then(maybeNotifyUpdate).catch(async (err: unknown) => {
|
|
129
154
|
const cfg = await loadConfig().catch(() => null);
|
|
130
155
|
const m = t(resolveLocale(cfg?.language));
|
|
131
156
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const CACHE_DIR = join(homedir(), ".watchwhere", "cache");
|
|
6
|
+
const CACHE_FILE = join(CACHE_DIR, "update.json");
|
|
7
|
+
const TTL_MS = 24 * 60 * 60 * 1000;
|
|
8
|
+
const REGISTRY_URL = "https://registry.npmjs.org/watchwhere/latest";
|
|
9
|
+
const FETCH_TIMEOUT_MS = 3000;
|
|
10
|
+
|
|
11
|
+
interface UpdateCache {
|
|
12
|
+
checkedAt: string;
|
|
13
|
+
latest: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseCache(raw: unknown): UpdateCache | null {
|
|
17
|
+
if (typeof raw !== "object" || raw === null) return null;
|
|
18
|
+
const v = raw as { checkedAt?: unknown; latest?: unknown };
|
|
19
|
+
if (typeof v.checkedAt !== "string" || typeof v.latest !== "string") return null;
|
|
20
|
+
return { checkedAt: v.checkedAt, latest: v.latest };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function readCache(): Promise<UpdateCache | null> {
|
|
24
|
+
try {
|
|
25
|
+
const raw = await readFile(CACHE_FILE, "utf8");
|
|
26
|
+
const parsed = parseCache(JSON.parse(raw) as unknown);
|
|
27
|
+
if (!parsed) return null;
|
|
28
|
+
const age = Date.now() - Date.parse(parsed.checkedAt);
|
|
29
|
+
if (Number.isNaN(age) || age > TTL_MS || age < 0) return null;
|
|
30
|
+
return parsed;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function writeCache(latest: string): Promise<void> {
|
|
37
|
+
await mkdir(CACHE_DIR, { recursive: true, mode: 0o700 });
|
|
38
|
+
const entry: UpdateCache = { checkedAt: new Date().toISOString(), latest };
|
|
39
|
+
await writeFile(CACHE_FILE, JSON.stringify(entry), "utf8");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isNewer(latest: string, current: string): boolean {
|
|
43
|
+
const a = latest.split(".").map((n) => Number.parseInt(n, 10));
|
|
44
|
+
const b = current.split(".").map((n) => Number.parseInt(n, 10));
|
|
45
|
+
for (let i = 0; i < 3; i++) {
|
|
46
|
+
const ai = a[i] ?? 0;
|
|
47
|
+
const bi = b[i] ?? 0;
|
|
48
|
+
if (Number.isNaN(ai) || Number.isNaN(bi)) return false;
|
|
49
|
+
if (ai > bi) return true;
|
|
50
|
+
if (ai < bi) return false;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function checkForUpdate(currentVersion: string): Promise<string | null> {
|
|
56
|
+
try {
|
|
57
|
+
const cached = await readCache();
|
|
58
|
+
if (cached) {
|
|
59
|
+
return isNewer(cached.latest, currentVersion) ? cached.latest : null;
|
|
60
|
+
}
|
|
61
|
+
const res = await fetch(REGISTRY_URL, {
|
|
62
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
63
|
+
headers: { Accept: "application/json" },
|
|
64
|
+
});
|
|
65
|
+
if (!res.ok) return null;
|
|
66
|
+
const data = (await res.json()) as { version?: string };
|
|
67
|
+
if (typeof data.version !== "string") return null;
|
|
68
|
+
writeCache(data.version).catch(() => {});
|
|
69
|
+
return isNewer(data.version, currentVersion) ? data.version : null;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|