yaver-cli 1.95.4 → 1.95.6
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/agent-runtime.js +131 -19
package/package.json
CHANGED
package/src/agent-runtime.js
CHANGED
|
@@ -4,20 +4,23 @@ const path = require('path');
|
|
|
4
4
|
const { spawn } = require('child_process');
|
|
5
5
|
const { pipeline } = require('stream/promises');
|
|
6
6
|
const https = require('https');
|
|
7
|
+
const semver = require('semver');
|
|
7
8
|
|
|
8
9
|
const PACKAGE = require('../package.json');
|
|
9
10
|
|
|
10
11
|
const DEFAULT_REPO = process.env.YAVER_AGENT_REPO || 'kivanccakmak/yaver.io';
|
|
11
|
-
const
|
|
12
|
+
const WINDOWS_REPO = process.env.YAVER_WINDOWS_AGENT_REPO || 'kivanccakmak/yaver-cli';
|
|
12
13
|
const CACHE_ROOT = process.env.YAVER_AGENT_CACHE_DIR || path.join(os.homedir(), '.yaver', 'bin');
|
|
14
|
+
let resolvedAgentVersionPromise = null;
|
|
15
|
+
const resolvedAssetPromiseByKey = new Map();
|
|
13
16
|
|
|
14
17
|
async function ensureAgentBinary({ quiet = false } = {}) {
|
|
15
|
-
const asset = resolveAsset();
|
|
18
|
+
const asset = await resolveAsset();
|
|
16
19
|
const localAgentPath = resolveLocalAgentBinary(asset);
|
|
17
20
|
if (localAgentPath) {
|
|
18
21
|
return localAgentPath;
|
|
19
22
|
}
|
|
20
|
-
const installDir = path.join(CACHE_ROOT,
|
|
23
|
+
const installDir = path.join(CACHE_ROOT, asset.version, asset.cacheKey);
|
|
21
24
|
const binaryPath = path.join(installDir, asset.binaryName);
|
|
22
25
|
|
|
23
26
|
if (fs.existsSync(binaryPath)) {
|
|
@@ -31,7 +34,7 @@ async function ensureAgentBinary({ quiet = false } = {}) {
|
|
|
31
34
|
const tmpPath = path.join(installDir, asset.downloadName);
|
|
32
35
|
|
|
33
36
|
if (!quiet) {
|
|
34
|
-
console.error(`Installing Yaver agent ${
|
|
37
|
+
console.error(`Installing Yaver agent ${asset.version} for ${asset.cacheKey}...`);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
await downloadToFile(asset.url, tmpPath);
|
|
@@ -39,6 +42,12 @@ async function ensureAgentBinary({ quiet = false } = {}) {
|
|
|
39
42
|
if (asset.archiveType === 'tar.gz') {
|
|
40
43
|
await extractTarball(tmpPath, installDir);
|
|
41
44
|
fs.rmSync(tmpPath, { force: true });
|
|
45
|
+
if (!fs.existsSync(binaryPath) && asset.extractedBinaryName) {
|
|
46
|
+
const extractedPath = path.join(installDir, asset.extractedBinaryName);
|
|
47
|
+
if (fs.existsSync(extractedPath)) {
|
|
48
|
+
fs.renameSync(extractedPath, binaryPath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
42
51
|
} else {
|
|
43
52
|
fs.renameSync(tmpPath, binaryPath);
|
|
44
53
|
}
|
|
@@ -82,14 +91,10 @@ async function runAgentCommand(args, options = {}) {
|
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
function resolveAgentInfo() {
|
|
85
|
-
const asset = resolveAsset();
|
|
86
94
|
return {
|
|
87
|
-
version:
|
|
95
|
+
version: process.env.YAVER_AGENT_VERSION || PACKAGE.version,
|
|
88
96
|
repo: DEFAULT_REPO,
|
|
89
|
-
|
|
90
|
-
binaryName: asset.binaryName,
|
|
91
|
-
cacheDir: path.join(CACHE_ROOT, AGENT_VERSION, asset.cacheKey),
|
|
92
|
-
downloadUrl: asset.url,
|
|
97
|
+
note: 'Run a real agent command once to resolve the current published agent release.',
|
|
93
98
|
};
|
|
94
99
|
}
|
|
95
100
|
|
|
@@ -109,7 +114,7 @@ function resolveLocalAgentCommand(agentArgs) {
|
|
|
109
114
|
return { command: process.env.YAVER_AGENT_BIN, args: agentArgs };
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
const asset =
|
|
117
|
+
const asset = resolveLocalAsset();
|
|
113
118
|
const localBinary = resolveLocalAgentBinary(asset);
|
|
114
119
|
if (localBinary) {
|
|
115
120
|
return { command: localBinary, args: agentArgs };
|
|
@@ -128,7 +133,7 @@ function resolveLocalAgentCommand(agentArgs) {
|
|
|
128
133
|
return null;
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
function
|
|
136
|
+
function resolveLocalAsset() {
|
|
132
137
|
const platform = process.platform;
|
|
133
138
|
const arch = process.arch;
|
|
134
139
|
const goArch = arch === 'x64' ? 'amd64' : arch === 'arm64' ? 'arm64' : null;
|
|
@@ -143,7 +148,8 @@ function resolveAsset() {
|
|
|
143
148
|
binaryName: 'yaver.exe',
|
|
144
149
|
downloadName: `yaver-windows-${goArch}.exe`,
|
|
145
150
|
archiveType: 'exe',
|
|
146
|
-
url:
|
|
151
|
+
url: '',
|
|
152
|
+
version: process.env.YAVER_AGENT_VERSION || PACKAGE.version,
|
|
147
153
|
};
|
|
148
154
|
}
|
|
149
155
|
|
|
@@ -151,17 +157,111 @@ function resolveAsset() {
|
|
|
151
157
|
return {
|
|
152
158
|
cacheKey: `${platform}-${goArch}`,
|
|
153
159
|
binaryName: 'yaver',
|
|
160
|
+
extractedBinaryName: `yaver-${platform}-${goArch}`,
|
|
154
161
|
downloadName: `yaver-${platform}-${goArch}.tar.gz`,
|
|
155
162
|
archiveType: 'tar.gz',
|
|
156
|
-
url:
|
|
163
|
+
url: '',
|
|
164
|
+
version: process.env.YAVER_AGENT_VERSION || PACKAGE.version,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
throw new Error(`unsupported platform for npm bootstrap: ${platform}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function resolveAsset() {
|
|
172
|
+
const platform = process.platform;
|
|
173
|
+
const arch = process.arch;
|
|
174
|
+
const goArch = arch === 'x64' ? 'amd64' : arch === 'arm64' ? 'arm64' : null;
|
|
175
|
+
|
|
176
|
+
if (!goArch) {
|
|
177
|
+
throw new Error(`unsupported architecture for npm bootstrap: ${arch}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const cacheKey = `${platform}-${goArch}`;
|
|
181
|
+
if (!resolvedAssetPromiseByKey.has(cacheKey)) {
|
|
182
|
+
resolvedAssetPromiseByKey.set(cacheKey, fetchRemoteAsset(platform, goArch));
|
|
183
|
+
}
|
|
184
|
+
return resolvedAssetPromiseByKey.get(cacheKey);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function fetchRemoteAsset(platform, goArch) {
|
|
188
|
+
if (platform === 'win32') {
|
|
189
|
+
const release = await fetchLatestRelease(WINDOWS_REPO);
|
|
190
|
+
const asset = findReleaseAsset(release, [`yaver-windows-${goArch}.exe`]);
|
|
191
|
+
if (!asset) {
|
|
192
|
+
throw new Error(`could not find Windows agent asset for ${goArch} in ${WINDOWS_REPO}`);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
cacheKey: `${platform}-${goArch}`,
|
|
196
|
+
binaryName: 'yaver.exe',
|
|
197
|
+
downloadName: asset.name,
|
|
198
|
+
archiveType: 'exe',
|
|
199
|
+
url: asset.browser_download_url,
|
|
200
|
+
version: release.tag_name.replace(/^v/, ''),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (platform === 'darwin' || platform === 'linux') {
|
|
205
|
+
const release = await fetchLatestRelease(DEFAULT_REPO);
|
|
206
|
+
const version = release.tag_name.replace(/^v/, '');
|
|
207
|
+
const asset = findReleaseAsset(release, [
|
|
208
|
+
`yaver-v${version}-${platform}-${goArch}.tar.gz`,
|
|
209
|
+
`yaver-${platform}-${goArch}.tar.gz`,
|
|
210
|
+
]);
|
|
211
|
+
if (!asset) {
|
|
212
|
+
throw new Error(`could not find ${platform}/${goArch} agent tarball in ${DEFAULT_REPO}`);
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
cacheKey: `${platform}-${goArch}`,
|
|
216
|
+
binaryName: 'yaver',
|
|
217
|
+
extractedBinaryName: `yaver-${platform}-${goArch}`,
|
|
218
|
+
downloadName: asset.name,
|
|
219
|
+
archiveType: 'tar.gz',
|
|
220
|
+
url: asset.browser_download_url,
|
|
221
|
+
version,
|
|
157
222
|
};
|
|
158
223
|
}
|
|
159
224
|
|
|
160
225
|
throw new Error(`unsupported platform for npm bootstrap: ${platform}`);
|
|
161
226
|
}
|
|
162
227
|
|
|
163
|
-
function
|
|
164
|
-
|
|
228
|
+
async function fetchLatestRelease(repo) {
|
|
229
|
+
const response = await request(`https://api.github.com/repos/${repo}/releases?per_page=20`, {
|
|
230
|
+
headers: {
|
|
231
|
+
'User-Agent': `yaver-cli/${PACKAGE.version}`,
|
|
232
|
+
Accept: 'application/vnd.github+json',
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
const body = await readResponseBody(response);
|
|
236
|
+
if (response.statusCode && response.statusCode >= 400) {
|
|
237
|
+
throw new Error(`failed to resolve latest release (${response.statusCode}) from ${repo}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let releases;
|
|
241
|
+
try {
|
|
242
|
+
releases = JSON.parse(body);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
throw new Error(`invalid release metadata from ${repo}: ${error.message}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const latest = Array.isArray(releases)
|
|
248
|
+
? releases.find((release) => !release.draft && !release.prerelease && semver.valid(String(release.tag_name || '').replace(/^v/, '')))
|
|
249
|
+
: null;
|
|
250
|
+
|
|
251
|
+
if (!latest || !latest.tag_name) {
|
|
252
|
+
throw new Error(`could not find a published semver release in ${repo}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return latest;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function findReleaseAsset(release, names) {
|
|
259
|
+
const byName = new Map((release.assets || []).map((asset) => [asset.name, asset]));
|
|
260
|
+
for (const name of names) {
|
|
261
|
+
const asset = byName.get(name);
|
|
262
|
+
if (asset) return asset;
|
|
263
|
+
}
|
|
264
|
+
return null;
|
|
165
265
|
}
|
|
166
266
|
|
|
167
267
|
async function downloadToFile(url, destPath) {
|
|
@@ -172,11 +272,11 @@ async function downloadToFile(url, destPath) {
|
|
|
172
272
|
await pipeline(response, fs.createWriteStream(destPath));
|
|
173
273
|
}
|
|
174
274
|
|
|
175
|
-
function request(url) {
|
|
275
|
+
function request(url, options = {}) {
|
|
176
276
|
return new Promise((resolve, reject) => {
|
|
177
|
-
https.get(url, (response) => {
|
|
277
|
+
https.get(url, options, (response) => {
|
|
178
278
|
if (response.statusCode && response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
179
|
-
resolve(request(response.headers.location));
|
|
279
|
+
resolve(request(response.headers.location, options));
|
|
180
280
|
return;
|
|
181
281
|
}
|
|
182
282
|
resolve(response);
|
|
@@ -184,6 +284,18 @@ function request(url) {
|
|
|
184
284
|
});
|
|
185
285
|
}
|
|
186
286
|
|
|
287
|
+
function readResponseBody(response) {
|
|
288
|
+
return new Promise((resolve, reject) => {
|
|
289
|
+
let body = '';
|
|
290
|
+
response.setEncoding('utf8');
|
|
291
|
+
response.on('data', (chunk) => {
|
|
292
|
+
body += chunk;
|
|
293
|
+
});
|
|
294
|
+
response.on('end', () => resolve(body));
|
|
295
|
+
response.on('error', reject);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
187
299
|
async function extractTarball(archivePath, destDir) {
|
|
188
300
|
const child = spawn('tar', ['-xzf', archivePath, '-C', destDir], {
|
|
189
301
|
stdio: ['ignore', 'pipe', 'pipe'],
|