ticketlens 0.5.0 → 0.5.1
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/bin/ticketlens.mjs
CHANGED
|
@@ -31,12 +31,27 @@ import { readCliToken, saveCliToken, deleteCliToken } from '../skills/jtb/script
|
|
|
31
31
|
import { browserLogin } from '../skills/jtb/scripts/lib/browser-login.mjs';
|
|
32
32
|
import { syncProfiles, getApiBase, getConsoleBase } from '../skills/jtb/scripts/lib/sync.mjs';
|
|
33
33
|
import { promptSecret, promptText } from '../skills/jtb/scripts/lib/prompt-helpers.mjs';
|
|
34
|
+
import { checkForUpdate, getUpdateHint } from '../skills/jtb/scripts/lib/update-check.mjs';
|
|
34
35
|
|
|
35
36
|
const args = process.argv.slice(2);
|
|
36
37
|
const { command, args: cmdArgs } = parseCommand(args);
|
|
37
38
|
|
|
38
39
|
// Fire-and-forget: silently refresh license.json at startup if >7 days since last validation
|
|
39
40
|
revalidateIfStale();
|
|
41
|
+
// Fire-and-forget: refresh the cached latest npm version once per 24h
|
|
42
|
+
checkForUpdate();
|
|
43
|
+
|
|
44
|
+
// Show a one-line update hint on stderr after the command exits (never blocks stdout)
|
|
45
|
+
process.on('exit', () => {
|
|
46
|
+
try {
|
|
47
|
+
const latest = getUpdateHint();
|
|
48
|
+
if (!latest) return;
|
|
49
|
+
const s = createStyler({ isTTY: process.stderr.isTTY });
|
|
50
|
+
process.stderr.write(`\n${s.brand('◆')} Update available ${s.dim('→')} ${s.bold(s.cyan(latest))} ${s.dim('npm install -g ticketlens')}\n`);
|
|
51
|
+
} catch {
|
|
52
|
+
// never let the update hint crash the process
|
|
53
|
+
}
|
|
54
|
+
});
|
|
40
55
|
|
|
41
56
|
switch (command) {
|
|
42
57
|
case 'fetch':
|
package/package.json
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background npm update check.
|
|
3
|
+
* Caches the latest published version in ~/.ticketlens/update-check.json with a 24h TTL.
|
|
4
|
+
* Never blocks the CLI — all failures are silently swallowed.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { DEFAULT_CONFIG_DIR, getVersion } from './config.mjs';
|
|
10
|
+
|
|
11
|
+
export const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
12
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/ticketlens/latest';
|
|
13
|
+
const CHECK_FILE = 'update-check.json';
|
|
14
|
+
|
|
15
|
+
/** Path to the update-check cache file. */
|
|
16
|
+
function checkFilePath(configDir) {
|
|
17
|
+
return join(configDir, CHECK_FILE);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns the cached latestVersion string if the cache is present and within TTL,
|
|
22
|
+
* or null otherwise.
|
|
23
|
+
*/
|
|
24
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+$/;
|
|
25
|
+
|
|
26
|
+
export function readUpdateCheck(configDir = DEFAULT_CONFIG_DIR) {
|
|
27
|
+
try {
|
|
28
|
+
const data = JSON.parse(readFileSync(checkFilePath(configDir), 'utf8'));
|
|
29
|
+
if (!data?.checkedAt || typeof data.latestVersion !== 'string') return null;
|
|
30
|
+
if (!SEMVER_RE.test(data.latestVersion)) return null;
|
|
31
|
+
if (Date.now() - new Date(data.checkedAt).getTime() > UPDATE_CHECK_TTL_MS) return null;
|
|
32
|
+
return data.latestVersion;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Writes { checkedAt, latestVersion } to the cache file. Non-fatal.
|
|
40
|
+
*/
|
|
41
|
+
export function writeUpdateCheck(configDir = DEFAULT_CONFIG_DIR, latestVersion) {
|
|
42
|
+
try {
|
|
43
|
+
mkdirSync(configDir, { recursive: true });
|
|
44
|
+
writeFileSync(
|
|
45
|
+
checkFilePath(configDir),
|
|
46
|
+
JSON.stringify({ checkedAt: new Date().toISOString(), latestVersion }),
|
|
47
|
+
{ mode: 0o644 },
|
|
48
|
+
);
|
|
49
|
+
} catch {
|
|
50
|
+
// non-fatal
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Returns true when latest is strictly greater than current.
|
|
56
|
+
* Strips leading 'v' from both before comparing.
|
|
57
|
+
*/
|
|
58
|
+
export function isNewer(latest, current) {
|
|
59
|
+
const parse = v => v.replace(/^v/, '').split('.').map(Number);
|
|
60
|
+
const [la, lb, lc] = parse(latest);
|
|
61
|
+
const [ca, cb, cc] = parse(current);
|
|
62
|
+
if (la !== ca) return la > ca;
|
|
63
|
+
if (lb !== cb) return lb > cb;
|
|
64
|
+
return lc > cc;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Fetches the latest version from the npm registry.
|
|
69
|
+
* Returns the version string, or null on any failure.
|
|
70
|
+
*/
|
|
71
|
+
export async function fetchLatestVersion({ fetcher = globalThis.fetch } = {}) {
|
|
72
|
+
try {
|
|
73
|
+
const res = await fetcher(REGISTRY_URL, { signal: AbortSignal.timeout(10_000) });
|
|
74
|
+
if (!res.ok) return null;
|
|
75
|
+
const data = await res.json();
|
|
76
|
+
return data?.version ?? null;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Fire-and-forget: checks if the cache is expired and, if so, fetches the latest
|
|
84
|
+
* version from npm and writes it to the cache. Errors are silently swallowed.
|
|
85
|
+
* Call at CLI startup; never await.
|
|
86
|
+
*/
|
|
87
|
+
export function checkForUpdate({ configDir = DEFAULT_CONFIG_DIR, fetcher = globalThis.fetch } = {}) {
|
|
88
|
+
if (readUpdateCheck(configDir) !== null) return Promise.resolve();
|
|
89
|
+
return fetchLatestVersion({ fetcher })
|
|
90
|
+
.then(v => { if (v) writeUpdateCheck(configDir, v); })
|
|
91
|
+
.catch(() => {});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns the cached latestVersion string if it is newer than the current installed
|
|
96
|
+
* version, or null if no update is available or the cache is absent/expired.
|
|
97
|
+
*/
|
|
98
|
+
export function getUpdateHint({ configDir = DEFAULT_CONFIG_DIR, currentVersion = getVersion() } = {}) {
|
|
99
|
+
const latest = readUpdateCheck(configDir);
|
|
100
|
+
if (!latest) return null;
|
|
101
|
+
return isNewer(latest, currentVersion) ? latest : null;
|
|
102
|
+
}
|