xkat-cli 2.7.1 → 2.8.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 +46 -0
- package/README.md +14 -6
- package/bin/checksums.json +5 -5
- package/bin/xkat-cli.js +182 -17
- package/package.json +1 -1
- package/scripts/install.js +79 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.7.3
|
|
4
|
+
|
|
5
|
+
- **패키지를 올려도 옛 바이너리를 계속 쓰던 문제.** 바로 아래 npm 12 대응으로
|
|
6
|
+
바이너리를 `~/.xkat/` 에 받게 되면서 생긴 구멍이다. 다운로드 주소는 버전별인데
|
|
7
|
+
받아 둔 파일에는 버전이 적혀 있지 않았고, npm 12 에서는 postinstall 이 갱신해
|
|
8
|
+
주던 패키지 안 `binaries/` 가 아예 없다. 그래서 `npm i -g xkat-cli@latest` 로
|
|
9
|
+
래퍼만 새것이 되고 **실행은 예전 바이너리로 계속됐다.**
|
|
10
|
+
|
|
11
|
+
받을 때 `~/.xkat/xkat-cli.version` 에 버전을 같이 적는다. 래퍼는 자기 버전과
|
|
12
|
+
다르면 새로 받는다. 소스 트리 빌드나 손으로 놓은 바이너리는 건드리지 않는다.
|
|
13
|
+
|
|
14
|
+
- **새 버전이 나온 걸 알려준다.** 지금은 배포가 잦아서, 몇 주 전 CLI 를 쓰는
|
|
15
|
+
학습자가 이미 고쳐진 버그를 만나고도 알 길이 없었다. 하루에 한 번, 터미널일
|
|
16
|
+
때만, 1.5초 안에 대답이 없으면 포기하고, 실패는 전부 조용히 넘어간다. 오프라인
|
|
17
|
+
학습자에게는 아무것도 보이지 않아야 하고 이 확인 때문에 명령이 느려지면 안 된다.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
⬆️ A newer xkat-cli is available: 2.7.3 → 2.8.0
|
|
21
|
+
Update with: npm install -g xkat-cli@latest
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`XKAT_NO_UPDATE_CHECK=1` 로 끌 수 있다.
|
|
25
|
+
|
|
26
|
+
- **첫 실행이 오래 걸린다는 것을 웹이 말해준다.** 아래 변경으로 다운로드가 설치
|
|
27
|
+
시점에서 `xkat-cli up` 안으로 옮겨졌다. 환경 설정 모달 1단계는 "연결 대기" 만
|
|
28
|
+
보여주고 있어서, 10~30초 동안 먹통과 구분되지 않았다.
|
|
29
|
+
|
|
30
|
+
- **npm 12 에서 설치해도 바이너리가 오지 않던 문제.** npm 12 는 의존성의
|
|
31
|
+
lifecycle 스크립트를 기본으로 끈다. 이 패키지는 `postinstall` 에서 바이너리를
|
|
32
|
+
내려받으므로, `npm i -g xkat-cli` 를 해도 래퍼만 깔리고 실행하면
|
|
33
|
+
"binary not found" 가 뜬다. 안내대로 재설치해도 같은 결과라 빠져나갈 수 없다.
|
|
34
|
+
실제 npm 12.0.2 로 재현했다:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
npm warn install-scripts 1 package had install scripts blocked because they
|
|
38
|
+
are not covered by allowScripts: xkat-cli@2.7.2 (postinstall: …)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
래퍼가 바이너리를 못 찾으면 **첫 실행 때 직접 내려받는다.** 받는 곳은
|
|
42
|
+
`~/.xkat/` 다 — 전역 설치는 사용자가 쓸 수 없는 위치에 있을 수 있지만 홈은
|
|
43
|
+
언제나 쓸 수 있고, 래퍼가 이미 그 경로를 후보로 보고 있었다.
|
|
44
|
+
|
|
45
|
+
검증은 그대로다. 해시를 확인한 뒤에야 임시 파일을 최종 이름으로 옮기므로,
|
|
46
|
+
중단된 다운로드가 쓸 수 있는 바이너리로 남지 않는다. `postinstall` 도 그대로
|
|
47
|
+
두어, 스크립트를 실행하는 패키지 매니저에서는 첫 실행이 즉시 시작된다.
|
|
48
|
+
|
|
3
49
|
## 2.7.1
|
|
4
50
|
|
|
5
51
|
- **"가상 머신이 돌고 있나" 를 두 곳이 다르게 답하던 문제.** 에이전트가
|
package/README.md
CHANGED
|
@@ -165,12 +165,20 @@ xkat-cli includes the following safeguards:
|
|
|
165
165
|
Requests from a browser are already constrained by the Origin allowlist.
|
|
166
166
|
5. **Audit log**: Connection attempts are recorded locally with a timestamp in
|
|
167
167
|
`~/.xkat/agent.log`; every host-shell grant is logged with its origin.
|
|
168
|
-
6. **Binary
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
168
|
+
6. **Binary integrity check**: The platform-specific Rust binary is downloaded
|
|
169
|
+
over HTTPS and verified (SHA-256) against the checksum manifest published
|
|
170
|
+
with this package, to prevent MITM, tampering, or supply-chain injection.
|
|
171
|
+
The download is written to a temporary file and renamed into place only
|
|
172
|
+
after it verifies, so an interrupted download is never left behind as a
|
|
173
|
+
usable binary. A missing manifest entry or a hash mismatch **fails** — an
|
|
174
|
+
unverified binary is never used.
|
|
175
|
+
|
|
176
|
+
The fetch happens at install time when the package manager runs install
|
|
177
|
+
scripts, and **on first run otherwise**. npm 12 turned dependency lifecycle
|
|
178
|
+
scripts off by default; without the first-run path, `npm i -g xkat-cli`
|
|
179
|
+
under npm 12 would leave the wrapper with no binary and re-installing would
|
|
180
|
+
change nothing. First-run downloads go to `~/.xkat/`, which the user can
|
|
181
|
+
always write to — a global install may not be.
|
|
174
182
|
|
|
175
183
|
## Consumers
|
|
176
184
|
|
package/bin/checksums.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"xkat-cli-macos-aarch64": "
|
|
3
|
-
"xkat-cli-macos-x64": "
|
|
4
|
-
"xkat-cli-linux-x64": "
|
|
5
|
-
"xkat-cli-linux-aarch64": "
|
|
6
|
-
"xkat-cli-win-x64.exe": "
|
|
2
|
+
"xkat-cli-macos-aarch64": "29d698aff99fe704c0c154223fdcb583efbbd99e692df2617567257af25c3135",
|
|
3
|
+
"xkat-cli-macos-x64": "323ba511978017a118337da7ba1dd085501d775563b2aa6b95b0ac92a3f40f30",
|
|
4
|
+
"xkat-cli-linux-x64": "4a02ccc48638279bc1118ef37ab3d169e5559f886e153143492ab0f8f6fe3380",
|
|
5
|
+
"xkat-cli-linux-aarch64": "cd52e4a1009107bbf4addfe7032419e56fa736017e6a010ac85350e4ec7af18d",
|
|
6
|
+
"xkat-cli-win-x64.exe": "5660b96abecc0e9f67608ef1e7d4fbaa1dd11b901333b08b18e557c77b63c325"
|
|
7
7
|
}
|
package/bin/xkat-cli.js
CHANGED
|
@@ -53,29 +53,194 @@ const binaryPath = candidatePaths.find((p) => {
|
|
|
53
53
|
return stats.size > 0;
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* True when the binary we found is one *we* fetched for an older version.
|
|
58
|
+
*
|
|
59
|
+
* The download URL is versioned, so a wrapper updated to 2.8.0 alongside a
|
|
60
|
+
* 2.7.x binary in `~/.xkat/` would keep running the old one — and under npm 12
|
|
61
|
+
* the package's own `binaries/` directory (which the postinstall would have
|
|
62
|
+
* refreshed) does not exist at all, so the stale copy always wins. Only the
|
|
63
|
+
* fetched cache is checked; a source-tree build or a hand-placed binary is the
|
|
64
|
+
* developer's business.
|
|
65
|
+
*/
|
|
66
|
+
function isStaleFetchedBinary(found) {
|
|
67
|
+
try {
|
|
68
|
+
const { userBinaryPath, readCachedVersion, VERSION } = require('../scripts/install.js');
|
|
69
|
+
if (path.resolve(found) !== path.resolve(userBinaryPath())) return false;
|
|
70
|
+
return readCachedVersion(found) !== VERSION;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
56
76
|
const args = process.argv.slice(2);
|
|
57
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Tells the learner when a newer xkat-cli has been published.
|
|
80
|
+
*
|
|
81
|
+
* Releases are frequent right now, and a learner running a month-old CLI hits
|
|
82
|
+
* bugs that were fixed weeks ago with no way to know. The check is deliberately
|
|
83
|
+
* timid: it is skipped unless stdout is a terminal, it runs at most once a day,
|
|
84
|
+
* it gives up after 1.5 seconds, and every failure is silent. It must never be
|
|
85
|
+
* the reason a command is slow or fails — an offline learner sees nothing.
|
|
86
|
+
*/
|
|
87
|
+
const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
88
|
+
|
|
89
|
+
function updateCheckStampPath() {
|
|
90
|
+
return path.join(os.homedir(), '.xkat', 'update-check');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function dueForUpdateCheck() {
|
|
94
|
+
if (!process.stdout.isTTY) return false;
|
|
95
|
+
if (process.env.XKAT_NO_UPDATE_CHECK) return false;
|
|
96
|
+
try {
|
|
97
|
+
const age = Date.now() - fs.statSync(updateCheckStampPath()).mtimeMs;
|
|
98
|
+
return age > UPDATE_CHECK_INTERVAL_MS;
|
|
99
|
+
} catch {
|
|
100
|
+
return true; // never checked
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function markUpdateChecked() {
|
|
105
|
+
try {
|
|
106
|
+
fs.mkdirSync(path.dirname(updateCheckStampPath()), { recursive: true });
|
|
107
|
+
fs.writeFileSync(updateCheckStampPath(), new Date().toISOString());
|
|
108
|
+
} catch {}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Compares dotted numeric versions. Returns true when `latest` is newer. */
|
|
112
|
+
function isNewer(latest, current) {
|
|
113
|
+
const toParts = (v) => String(v).split('.').map((n) => parseInt(n, 10) || 0);
|
|
114
|
+
const a = toParts(latest);
|
|
115
|
+
const b = toParts(current);
|
|
116
|
+
for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
|
|
117
|
+
if ((a[i] || 0) > (b[i] || 0)) return true;
|
|
118
|
+
if ((a[i] || 0) < (b[i] || 0)) return false;
|
|
119
|
+
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function fetchLatestVersion() {
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
let settled = false;
|
|
126
|
+
const done = (v) => { if (!settled) { settled = true; resolve(v); } };
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const https = require('https');
|
|
130
|
+
// The `/latest` document is ~2KB. The whole packument is ~17KB and grows
|
|
131
|
+
// with every release, and the abbreviated Accept header it wants is a 406
|
|
132
|
+
// on this endpoint.
|
|
133
|
+
const req = https.get(
|
|
134
|
+
'https://registry.npmjs.org/xkat-cli/latest',
|
|
135
|
+
{ headers: { Accept: 'application/json' }, timeout: 1500 },
|
|
136
|
+
(res) => {
|
|
137
|
+
if (res.statusCode !== 200) { res.resume(); return done(null); }
|
|
138
|
+
let body = '';
|
|
139
|
+
res.setEncoding('utf8');
|
|
140
|
+
res.on('data', (c) => { body += c; });
|
|
141
|
+
res.on('end', () => {
|
|
142
|
+
try { done(JSON.parse(body).version || null); } catch { done(null); }
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
req.on('timeout', () => { req.destroy(); done(null); });
|
|
147
|
+
req.on('error', () => done(null));
|
|
148
|
+
} catch {
|
|
149
|
+
done(null);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function noticeIfOutdated() {
|
|
155
|
+
if (!dueForUpdateCheck()) return;
|
|
156
|
+
markUpdateChecked();
|
|
157
|
+
|
|
158
|
+
let current;
|
|
159
|
+
try {
|
|
160
|
+
current = require('../package.json').version;
|
|
161
|
+
} catch {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const latest = await fetchLatestVersion();
|
|
166
|
+
if (!latest || !isNewer(latest, current)) return;
|
|
167
|
+
|
|
168
|
+
console.log('');
|
|
169
|
+
console.log(`⬆️ A newer xkat-cli is available: ${current} → ${latest}`);
|
|
170
|
+
console.log(' Update with: npm install -g xkat-cli@latest');
|
|
171
|
+
console.log(' (or run once without installing: npx -y xkat-cli@latest up)');
|
|
172
|
+
console.log('');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Fetches the binary on first run when the install script never ran.
|
|
177
|
+
*
|
|
178
|
+
* npm 12 turned dependency lifecycle scripts off by default, so `npm i -g
|
|
179
|
+
* xkat-cli` installs this wrapper and nothing else — and the old advice
|
|
180
|
+
* ("reinstall to fetch it") led nowhere because re-installing did the same.
|
|
181
|
+
*
|
|
182
|
+
* The download goes to `~/.xkat/`, not into the package directory: a global
|
|
183
|
+
* install may live somewhere the user cannot write, and that path is already
|
|
184
|
+
* one of the places this wrapper looks.
|
|
185
|
+
*/
|
|
186
|
+
async function fetchBinary(reason) {
|
|
187
|
+
let ensureBinary, userBinaryPath;
|
|
188
|
+
try {
|
|
189
|
+
({ ensureBinary, userBinaryPath } = require('../scripts/install.js'));
|
|
190
|
+
} catch (e) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log(`📥 xkat-cli: ${reason} — fetching the binary for this platform...`);
|
|
195
|
+
try {
|
|
196
|
+
return await ensureBinary({ destPath: userBinaryPath(), quiet: true });
|
|
197
|
+
} catch (e) {
|
|
198
|
+
console.error(`❌ xkat-cli: could not fetch the binary (${e.message})`);
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
58
203
|
// Every subcommand — `down` included — is handled by the Rust binary. This
|
|
59
204
|
// wrapper used to reimplement shutdown by killing everything on ports
|
|
60
205
|
// 38000..38050 plus every process matching "xkat-cli", which included itself
|
|
61
206
|
// and any unrelated process with that string in its command line.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
console.error(' Reinstall to fetch it: npm install -g xkat-cli');
|
|
65
|
-
console.error(' Or build from source: cd apps/xkat-agent && cargo build --release');
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
207
|
+
(async () => {
|
|
208
|
+
await noticeIfOutdated();
|
|
68
209
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
stdio: 'inherit',
|
|
72
|
-
env: process.env,
|
|
73
|
-
});
|
|
210
|
+
let resolved = binaryPath;
|
|
211
|
+
let reason = 'first run';
|
|
74
212
|
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
213
|
+
if (resolved && isStaleFetchedBinary(resolved)) {
|
|
214
|
+
reason = 'this is a new version';
|
|
215
|
+
resolved = null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!resolved) {
|
|
219
|
+
resolved = await fetchBinary(reason);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!resolved) {
|
|
223
|
+
console.error('❌ xkat-cli binary not found.');
|
|
224
|
+
console.error(' Check your network and try again, or build from source:');
|
|
225
|
+
console.error(' cd apps/xkat-agent && cargo build --release');
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Forward to the Rust binary.
|
|
230
|
+
//
|
|
231
|
+
// The cached binary is not re-verified here. The download is atomic — hashed,
|
|
232
|
+
// then renamed into place — so a file that exists is a file that passed the
|
|
233
|
+
// check. Hashing 14MB on every invocation would buy nothing.
|
|
234
|
+
const result = spawnSync(resolved, args, {
|
|
235
|
+
stdio: 'inherit',
|
|
236
|
+
env: process.env,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
if (result.error) {
|
|
240
|
+
console.error(`❌ Could not run xkat-cli: ${result.error.message}`);
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
79
243
|
|
|
80
|
-
// A signalled child reports status === null; treat that as a failure, not success.
|
|
81
|
-
process.exit(result.status ?? (result.signal ? 1 : 0));
|
|
244
|
+
// A signalled child reports status === null; treat that as a failure, not success.
|
|
245
|
+
process.exit(result.status ?? (result.signal ? 1 : 0));
|
|
246
|
+
})();
|
package/package.json
CHANGED
package/scripts/install.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* install.js —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* install.js — fetches the prebuilt Rust binary for this platform.
|
|
3
|
+
*
|
|
4
|
+
* Two callers:
|
|
5
|
+
* 1. `postinstall`, when the package manager still runs install scripts.
|
|
6
|
+
* 2. `bin/xkat-cli.js` on first run, when it does not. npm 12 turned
|
|
7
|
+
* dependency lifecycle scripts off by default, so a learner who installs
|
|
8
|
+
* with it gets the wrapper and no binary — and re-installing changes
|
|
9
|
+
* nothing. The wrapper therefore fetches it on demand.
|
|
10
|
+
*
|
|
11
|
+
* Both paths verify the download against the checksum manifest published with
|
|
12
|
+
* this package; an unverified binary is never used.
|
|
6
13
|
*/
|
|
7
14
|
process.removeAllListeners('warning');
|
|
8
15
|
process.emitWarning = () => {};
|
|
@@ -61,11 +68,53 @@ function fail(message, hint) {
|
|
|
61
68
|
process.exitCode = 1;
|
|
62
69
|
}
|
|
63
70
|
|
|
64
|
-
|
|
71
|
+
/** Where a first-run fetch puts the binary: always writable by the user. */
|
|
72
|
+
function userBinaryPath() {
|
|
73
|
+
return path.join(
|
|
74
|
+
os.homedir(),
|
|
75
|
+
'.xkat',
|
|
76
|
+
os.platform() === 'win32' ? 'xkat-cli.exe' : 'xkat-cli'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Which version the cached binary is.
|
|
82
|
+
*
|
|
83
|
+
* The file itself carries no version we can read cheaply, and the download URL
|
|
84
|
+
* is versioned — so a wrapper updated to 2.8.0 would otherwise keep running a
|
|
85
|
+
* 2.7.x binary fetched earlier, forever. The marker is written next to the
|
|
86
|
+
* binary when it is fetched.
|
|
87
|
+
*/
|
|
88
|
+
function cachedVersionPath(binaryPath) {
|
|
89
|
+
return `${binaryPath}.version`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readCachedVersion(binaryPath) {
|
|
93
|
+
try {
|
|
94
|
+
return fs.readFileSync(cachedVersionPath(binaryPath), 'utf8').trim();
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Ensures a verified binary exists at `destPath` and returns it.
|
|
102
|
+
*
|
|
103
|
+
* @param {object} [opts]
|
|
104
|
+
* @param {string} [opts.destPath] Where to put it. Defaults to the package's
|
|
105
|
+
* own `binaries/` directory — right for postinstall, wrong for first-run
|
|
106
|
+
* fetch, because a global install may sit in a root-owned directory.
|
|
107
|
+
* @param {boolean} [opts.quiet] Suppress the "already present" chatter.
|
|
108
|
+
* @returns {Promise<string|null>} Path to the binary, or null when there is
|
|
109
|
+
* nothing to fetch (source checkout, unsupported platform).
|
|
110
|
+
*/
|
|
111
|
+
async function ensureBinary(opts = {}) {
|
|
65
112
|
if (isSourceCheckout()) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
113
|
+
if (!opts.quiet) {
|
|
114
|
+
console.log('🛠️ Agent source tree detected. Skipping binary download.');
|
|
115
|
+
console.log(" (Use 'cargo build --release' for development)");
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
69
118
|
}
|
|
70
119
|
|
|
71
120
|
const target = getPlatformTarget();
|
|
@@ -73,7 +122,7 @@ async function main() {
|
|
|
73
122
|
// Not a failure: the package still installs, the user builds from source.
|
|
74
123
|
console.log(`⚠️ xkat-cli: unsupported platform (${os.platform()}-${os.arch()})`);
|
|
75
124
|
console.log(' Build manually: cd apps/xkat-agent && cargo build --release');
|
|
76
|
-
return;
|
|
125
|
+
return null;
|
|
77
126
|
}
|
|
78
127
|
|
|
79
128
|
// Integrity verification is a documented guarantee, so a missing or
|
|
@@ -95,14 +144,17 @@ async function main() {
|
|
|
95
144
|
);
|
|
96
145
|
}
|
|
97
146
|
|
|
98
|
-
const
|
|
99
|
-
const
|
|
147
|
+
const destPath = opts.destPath || path.join(__dirname, '..', 'binaries', target);
|
|
148
|
+
const binDir = path.dirname(destPath);
|
|
100
149
|
|
|
101
150
|
if (fs.existsSync(destPath)) {
|
|
102
151
|
try {
|
|
103
152
|
if ((await calculateSha256(destPath)) === expectedHash) {
|
|
104
|
-
console.log(`✅ xkat-cli binary already present and verified: ${target}`);
|
|
105
|
-
|
|
153
|
+
if (!opts.quiet) console.log(`✅ xkat-cli binary already present and verified: ${target}`);
|
|
154
|
+
try {
|
|
155
|
+
fs.writeFileSync(cachedVersionPath(destPath), VERSION);
|
|
156
|
+
} catch {}
|
|
157
|
+
return destPath;
|
|
106
158
|
}
|
|
107
159
|
console.log('⚠️ Hash mismatch for existing binary. Re-downloading...');
|
|
108
160
|
} catch (err) {
|
|
@@ -130,15 +182,22 @@ async function main() {
|
|
|
130
182
|
fs.chmodSync(partPath, 0o755);
|
|
131
183
|
}
|
|
132
184
|
fs.renameSync(partPath, destPath);
|
|
185
|
+
try {
|
|
186
|
+
fs.writeFileSync(cachedVersionPath(destPath), VERSION);
|
|
187
|
+
} catch {
|
|
188
|
+
// 기록에 실패해도 바이너리는 쓸 수 있다. 다음 실행에서 한 번 더 받을 뿐이다.
|
|
189
|
+
}
|
|
133
190
|
|
|
134
191
|
console.log('✅ Integrity verified: SHA-256 match');
|
|
135
|
-
console.log('✅ Installed! Run: xkat-cli up');
|
|
192
|
+
if (!opts.quiet) console.log('✅ Installed! Run: xkat-cli up');
|
|
193
|
+
return destPath;
|
|
136
194
|
} catch (err) {
|
|
137
195
|
fs.rmSync(partPath, { force: true });
|
|
138
196
|
fail(
|
|
139
197
|
`binary download failed: ${err.message}`,
|
|
140
198
|
'Retry the install, or build locally: cd apps/xkat-agent && cargo build --release'
|
|
141
199
|
);
|
|
200
|
+
return null;
|
|
142
201
|
}
|
|
143
202
|
}
|
|
144
203
|
|
|
@@ -191,4 +250,9 @@ function downloadFile(urlStr, dest, redirectsLeft = MAX_REDIRECTS) {
|
|
|
191
250
|
});
|
|
192
251
|
}
|
|
193
252
|
|
|
194
|
-
|
|
253
|
+
module.exports = { ensureBinary, userBinaryPath, getPlatformTarget, readCachedVersion, VERSION };
|
|
254
|
+
|
|
255
|
+
// postinstall 로 실행됐을 때만 스스로 돈다. 래퍼가 require 할 때는 돌지 않는다.
|
|
256
|
+
if (require.main === module) {
|
|
257
|
+
ensureBinary();
|
|
258
|
+
}
|