@athsra/cli 1.1.7 → 1.2.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/index.ts +10 -1
- package/src/lib/update-check.ts +95 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@athsra/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "athsra CLI — E2EE secret manager on Cloudflare edge. Doppler-style dev UX + zero-knowledge encryption + soft-delete + version history. MIT.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { serviceTokenCmd } from './commands/service-token.ts';
|
|
|
29
29
|
import { setCmd } from './commands/set.ts';
|
|
30
30
|
import { unsetCmd } from './commands/unset.ts';
|
|
31
31
|
import { versionsCmd } from './commands/versions.ts';
|
|
32
|
+
import { notifyIfOutdated } from './lib/update-check.ts';
|
|
32
33
|
|
|
33
34
|
// package.json 단일 출처 — 하드코딩 desync 방지(1.1.3 이후 bump 누락으로 --version 이 stale 했던 버그 근본 수정).
|
|
34
35
|
const VERSION = pkg.version;
|
|
@@ -186,7 +187,15 @@ function levenshtein(a: string, b: string): number {
|
|
|
186
187
|
return prev[n] ?? 0;
|
|
187
188
|
}
|
|
188
189
|
|
|
189
|
-
|
|
190
|
+
// 대화형 명령(login/doctor)에서 stale 글로벌 경고 — 비차단(캐시 기반)·opt-out(ATHSRA_NO_UPDATE_CHECK/CI).
|
|
191
|
+
// login 은 ecosystem 2026-06-21 피드백의 버그 진입점: stale CLI(v1.0.0)가 이미 수정된 SSO 버그를 재현.
|
|
192
|
+
const nudge =
|
|
193
|
+
cmd === 'login' || cmd === 'doctor'
|
|
194
|
+
? notifyIfOutdated(VERSION).catch(() => {})
|
|
195
|
+
: Promise.resolve();
|
|
196
|
+
|
|
197
|
+
nudge
|
|
198
|
+
.then(() => handler(args.slice(1)))
|
|
190
199
|
.then((code) => process.exit(code))
|
|
191
200
|
.catch((err: Error) => {
|
|
192
201
|
console.error(`error: ${err.message}`);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 버전 staleness nudge — 글로벌 설치가 npm latest 보다 낡으면 경고.
|
|
3
|
+
*
|
|
4
|
+
* 동기: stale 글로벌(v1.0.0)이 이미 수정된 SSO 버그(discovery `/sso` prefix · WSL xdg-open 크래시)를
|
|
5
|
+
* 재현해 agent-driven 로그인을 막던 것 (ecosystem 2026-06-21 피드백). canon agent-auth-ux 가 athsra 를
|
|
6
|
+
* agent-driven 인증 레퍼런스로 규정하므로, stale 설치가 깨진 인증을 조용히 서빙하지 않게 신호한다.
|
|
7
|
+
*
|
|
8
|
+
* 비차단: 경고는 캐시 기반(무네트워크·즉시), 갱신은 1일 1회 ≤2s timeout — 대화형 명령(login/doctor)에서만 호출.
|
|
9
|
+
* 우회: ATHSRA_NO_UPDATE_CHECK / CI. 어떤 실패(네트워크·파싱·fs)에도 조용히 no-op.
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { configDir, ensureConfigDir } from './config.ts';
|
|
14
|
+
|
|
15
|
+
const REGISTRY = 'https://registry.npmjs.org/@athsra/cli/latest';
|
|
16
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
17
|
+
const TIMEOUT_MS = 2000;
|
|
18
|
+
|
|
19
|
+
interface UpdateCache {
|
|
20
|
+
latest?: string;
|
|
21
|
+
checkedAt?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function cacheFile(): string {
|
|
25
|
+
return join(configDir(), 'update-check.json');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function readCache(): UpdateCache {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(readFileSync(cacheFile(), 'utf8')) as UpdateCache;
|
|
31
|
+
} catch {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function writeCache(c: UpdateCache): void {
|
|
37
|
+
try {
|
|
38
|
+
ensureConfigDir();
|
|
39
|
+
writeFileSync(cacheFile(), JSON.stringify(c));
|
|
40
|
+
} catch {
|
|
41
|
+
/* 캐시 못 써도 무해 — 다음 실행에 재시도 */
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** semver x.y.z 비교 — a 가 b 보다 높으면 true. 비표준/pre-release 는 보수적으로 false. */
|
|
46
|
+
export function isNewer(a: string, b: string): boolean {
|
|
47
|
+
const pa = a.split('.').map((n) => Number.parseInt(n, 10));
|
|
48
|
+
const pb = b.split('.').map((n) => Number.parseInt(n, 10));
|
|
49
|
+
for (let i = 0; i < 3; i++) {
|
|
50
|
+
const x = pa[i] ?? 0;
|
|
51
|
+
const y = pb[i] ?? 0;
|
|
52
|
+
if (Number.isNaN(x) || Number.isNaN(y)) return false;
|
|
53
|
+
if (x !== y) return x > y;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function fetchLatest(): Promise<string | null> {
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch(REGISTRY, {
|
|
61
|
+
headers: { accept: 'application/json' },
|
|
62
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
63
|
+
});
|
|
64
|
+
if (!res.ok) return null;
|
|
65
|
+
const doc: unknown = await res.json();
|
|
66
|
+
if (doc && typeof doc === 'object' && 'version' in doc) {
|
|
67
|
+
const v = (doc as { version: unknown }).version;
|
|
68
|
+
return typeof v === 'string' ? v : null;
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
/* offline / timeout / parse → null (조용) */
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 현재 버전이 npm latest 보다 낡으면 stderr 경고. 대화형 명령(login/doctor)에서 호출.
|
|
78
|
+
* 캐시된 latest 로 즉시 판단 + 1일 경과 시 갱신(≤2s). 실패·opt-out 시 조용히 no-op.
|
|
79
|
+
*/
|
|
80
|
+
export async function notifyIfOutdated(current: string): Promise<void> {
|
|
81
|
+
if (process.env.ATHSRA_NO_UPDATE_CHECK || process.env.CI) return;
|
|
82
|
+
let cache = readCache();
|
|
83
|
+
if (!cache.checkedAt || Date.now() - cache.checkedAt > DAY_MS) {
|
|
84
|
+
const latest = await fetchLatest();
|
|
85
|
+
// 갱신 실패 시에도 checkedAt 만 올려 매 실행 네트워크 재시도 방지 (직전 캐시 latest 유지).
|
|
86
|
+
cache = latest ? { latest, checkedAt: Date.now() } : { ...cache, checkedAt: Date.now() };
|
|
87
|
+
writeCache(cache);
|
|
88
|
+
}
|
|
89
|
+
if (cache.latest && isNewer(cache.latest, current)) {
|
|
90
|
+
process.stderr.write(
|
|
91
|
+
`\n ⚠ @athsra/cli ${cache.latest} 사용 가능 (현재 ${current}). ` +
|
|
92
|
+
`업그레이드: npm i -g @athsra/cli@latest (bun: bun add -g @athsra/cli@latest)\n\n`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|