yaver-cli 1.95.4 → 1.95.5

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/agent-runtime.js +131 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaver-cli",
3
- "version": "1.95.4",
3
+ "version": "1.95.5",
4
4
  "description": "Unified npm bootstrap for the Yaver agent and push-to-device React Native tooling",
5
5
  "bin": {
6
6
  "yaver": "bin/yaver",
@@ -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 AGENT_VERSION = process.env.YAVER_AGENT_VERSION || PACKAGE.version;
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, AGENT_VERSION, asset.cacheKey);
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 ${AGENT_VERSION} for ${asset.cacheKey}...`);
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: AGENT_VERSION,
95
+ version: process.env.YAVER_AGENT_VERSION || PACKAGE.version,
88
96
  repo: DEFAULT_REPO,
89
- asset: asset.downloadName,
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 = resolveAsset();
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 resolveAsset() {
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: releaseUrl(`yaver-windows-${goArch}.exe`),
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: releaseUrl(`yaver-${platform}-${goArch}.tar.gz`),
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 releaseUrl(assetName) {
164
- return `https://github.com/${DEFAULT_REPO}/releases/download/v${AGENT_VERSION}/${assetName}`;
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'],