@shiplens/cli 1.2.9 → 1.3.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/lib/api.js CHANGED
@@ -27,9 +27,13 @@ class APIClient {
27
27
  const lib = isHttps ? https : http;
28
28
 
29
29
  const payload = body ? (typeof body === 'string' ? body : JSON.stringify(body)) : null;
30
+ let pkgVer = '1.3.0';
31
+ try {
32
+ pkgVer = require('../package.json').version || pkgVer;
33
+ } catch (e) {}
30
34
  const headers = {
31
35
  'Content-Type': 'application/json',
32
- 'User-Agent': 'Shiplens-CLI/1.2.7 (Node.js)',
36
+ 'User-Agent': `Shiplens-CLI/${pkgVer} (Node.js)`,
33
37
  };
34
38
  if (this.secret) {
35
39
  headers['Authorization'] = `Bearer ${this.secret}`;
package/lib/cli.js CHANGED
@@ -15,7 +15,11 @@ const { handleDoctor } = require('./commands/doctor');
15
15
  const { handleContext } = require('./commands/context');
16
16
  const { handleMcp } = require('./commands/mcp');
17
17
 
18
- const VERSION = '1.2.7';
18
+ let VERSION = '1.3.0';
19
+ try {
20
+ const pkg = require('../package.json');
21
+ if (pkg.version) VERSION = pkg.version;
22
+ } catch (e) {}
19
23
 
20
24
  function parseArgs(argv) {
21
25
  const flags = {};
@@ -44,13 +44,21 @@ async function handleInit(args, flags, ctx) {
44
44
  ctx.output(result);
45
45
  process.exitCode = 1;
46
46
  return;
47
+ } else if (!process.stdin.isTTY) {
48
+ console.log(`\n⚠️ Existing Shiplens project detected!`);
49
+ console.log(`📌 App ID: ${existing.app_id} (Source: ${existing.source_file})`);
50
+ console.log(`📊 Dashboard: ${ctx.client.baseURL}/dashboard/${existing.app_id}`);
51
+ console.log(`🛑 Non-interactive terminal detected (CI/Agent). Existing configuration preserved.`);
52
+ console.log(`💡 To overwrite and create a new project, run with --force:`);
53
+ console.log(` npx @shiplens/cli init --force\n`);
54
+ return;
47
55
  } else {
48
56
  console.log(`\n⚠️ Existing Shiplens project detected!`);
49
57
  console.log(`📌 App ID: ${existing.app_id} (Source: ${existing.source_file})`);
50
58
  console.log(`📊 Dashboard: ${ctx.client.baseURL}/dashboard/${existing.app_id}`);
51
59
  console.log(`\n💡 Option 1 [Recommended]: Keep existing setup and reuse App ID.`);
52
60
  console.log(`💡 Option 2: Run with --force to overwrite and create a new project:`);
53
- console.log(` npx shiplens-cli init --force\n`);
61
+ console.log(` npx @shiplens/cli init --force\n`);
54
62
 
55
63
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
56
64
  const confirmed = await new Promise((resolve) => {
@@ -124,9 +132,9 @@ async function handleInit(args, flags, ctx) {
124
132
  // 6. Install SDK dependency
125
133
  let installResult = { success: true, manager: 'skipped' };
126
134
  if (!flags['no-install'] && detected.package_manager) {
127
- installResult = installSDKDependency(wd, detected.package_manager);
135
+ installResult = await installSDKDependency(wd, detected.package_manager);
128
136
  if (!installResult.success) {
129
- const errMsg = `SDK installation failed: Unable to download @shiplens/sdk from official npm registry, Alibaba Cloud mirror (npmmirror), GitHub mirror, or Shiplens CDN mirror (after 5s x 2 retries on each tier).\n` +
137
+ const errMsg = `SDK installation failed: Unable to download @shiplens/sdk across all download sources (official npm registry, Alibaba Cloud npmmirror, GitHub mirror, and Shiplens CDN mirror).\n` +
130
138
  `Troubleshooting steps:\n` +
131
139
  ` 1. Check your local network connection or HTTP proxy / VPN settings;\n` +
132
140
  ` 2. Try running manually with Alibaba mirror: npm install @shiplens/sdk --registry=https://registry.npmmirror.com;\n` +
package/lib/injector.js CHANGED
@@ -1,10 +1,11 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const { execSync } = require('child_process');
3
+ const { execSync, spawn } = require('child_process');
4
4
 
5
5
  const FRAMEWORKS = {
6
6
  NEXT_APP: 'nextjs-app',
7
7
  NEXT_PAGES: 'nextjs-pages',
8
+ CRA: 'cra',
8
9
  VITE: 'vite',
9
10
  VUE: 'vue',
10
11
  HTML: 'html',
@@ -57,6 +58,8 @@ function detectProject(dir = process.cwd()) {
57
58
  } else {
58
59
  result.framework = FRAMEWORKS.NEXT_PAGES;
59
60
  }
61
+ } else if (deps['react-scripts'] || deps['react-app-rewired'] || deps['craco'] || deps['@craco/craco']) {
62
+ result.framework = FRAMEWORKS.CRA;
60
63
  } else if (deps['vue'] || deps['nuxt']) {
61
64
  result.framework = FRAMEWORKS.VUE;
62
65
  } else if (deps['vite'] || deps['react'] || deps['svelte']) {
@@ -271,6 +274,26 @@ function injectHTML(dir, appId) {
271
274
  return injectFallback(dir, appId);
272
275
  }
273
276
 
277
+ function injectCRA(dir, appId) {
278
+ const candidates = [
279
+ 'src/index.tsx', 'src/index.ts',
280
+ 'src/index.jsx', 'src/index.js',
281
+ ];
282
+ for (const cand of candidates) {
283
+ const p = path.join(dir, cand);
284
+ if (fs.existsSync(p)) {
285
+ let content = fs.readFileSync(p, 'utf8');
286
+ if (content.includes('@shiplens/sdk') || content.includes('initShiplens') || content.includes(appId)) {
287
+ return cand;
288
+ }
289
+ const injection = `\n// Shiplens SDK - User Analytics\nconst { initShiplens } = require('@shiplens/sdk');\ninitShiplens({ appId: '${appId}' });\n`;
290
+ fs.writeFileSync(p, content + injection, 'utf8');
291
+ return cand;
292
+ }
293
+ }
294
+ return injectFallback(dir, appId);
295
+ }
296
+
274
297
  function injectFallback(dir, appId) {
275
298
  const configFile = path.join(dir, 'shiplens.config.ts');
276
299
  const content = `import { initShiplens } from '@shiplens/sdk';\n\nexport function setupShiplens() {\n if (typeof window !== 'undefined') {\n initShiplens({\n appId: '${appId}',\n });\n }\n}\n`;
@@ -284,6 +307,8 @@ function injectSDK(dir, framework, appId) {
284
307
  return injectNextApp(dir, appId);
285
308
  case FRAMEWORKS.NEXT_PAGES:
286
309
  return injectNextPages(dir, appId);
310
+ case FRAMEWORKS.CRA:
311
+ return injectCRA(dir, appId);
287
312
  case FRAMEWORKS.VUE:
288
313
  return injectVue(dir, appId);
289
314
  case FRAMEWORKS.VITE:
@@ -292,23 +317,40 @@ function injectSDK(dir, framework, appId) {
292
317
  return injectHTML(dir, appId);
293
318
  default:
294
319
  try { return injectNextApp(dir, appId); } catch (e) {}
320
+ try { return injectCRA(dir, appId); } catch (e) {}
295
321
  try { return injectVite(dir, appId); } catch (e) {}
296
322
  try { return injectHTML(dir, appId); } catch (e) {}
297
323
  return injectFallback(dir, appId);
298
324
  }
299
325
  }
300
326
 
301
- function installSDKDependency(dir, pkgManager = 'npm') {
327
+ function killProcess(child) {
328
+ if (!child || child.killed) return;
329
+ try {
330
+ if (process.platform === 'win32' && child.pid) {
331
+ try {
332
+ execSync(`taskkill /pid ${child.pid} /T /F`, { stdio: 'ignore' });
333
+ } catch (e) {
334
+ try { child.kill('SIGKILL'); } catch (err) {}
335
+ }
336
+ } else {
337
+ try { child.kill('SIGTERM'); } catch (e) {}
338
+ setTimeout(() => {
339
+ try { child.kill('SIGKILL'); } catch (e) {}
340
+ }, 500);
341
+ }
342
+ } catch (e) {
343
+ try { child.kill(); } catch (err) {}
344
+ }
345
+ }
346
+
347
+ async function installSDKDependency(dir, pkgManager = 'npm') {
302
348
  const isWindows = process.platform === 'win32';
303
349
  const ext = isWindows ? '.cmd' : '';
304
-
305
- // 1. Define 4-Tier Download Sources with 5s timeout & 2 retries per tier
306
- // Tier 1: NPM official/default registry
307
- // Tier 2: Alibaba Cloud / Taobao npm mirror (https://registry.npmmirror.com)
308
- // Tier 3: GitHub repository mirror
309
- // Tier 4: Shiplens official CDN mirror
350
+
310
351
  const sources = [
311
352
  {
353
+ id: 'npmjs',
312
354
  name: 'npm registry',
313
355
  getCommand: (pm) => {
314
356
  if (pm === 'pnpm') return `pnpm${ext} add @shiplens/sdk`;
@@ -318,6 +360,7 @@ function installSDKDependency(dir, pkgManager = 'npm') {
318
360
  },
319
361
  },
320
362
  {
363
+ id: 'npmmirror',
321
364
  name: 'Alibaba Cloud mirror (npmmirror)',
322
365
  getCommand: (pm) => {
323
366
  const mirror = '--registry=https://registry.npmmirror.com';
@@ -328,6 +371,7 @@ function installSDKDependency(dir, pkgManager = 'npm') {
328
371
  },
329
372
  },
330
373
  {
374
+ id: 'github',
331
375
  name: 'GitHub mirror',
332
376
  getCommand: (pm) => {
333
377
  const repoUrl = 'https://github.com/Hyperlong/shiplens-sdk.git';
@@ -338,6 +382,7 @@ function installSDKDependency(dir, pkgManager = 'npm') {
338
382
  },
339
383
  },
340
384
  {
385
+ id: 'cdn',
341
386
  name: 'Shiplens CDN mirror',
342
387
  getCommand: (pm) => {
343
388
  const cdnUrl = 'https://cdn.shiplens.dev/packages/shiplens-sdk.tgz';
@@ -349,42 +394,107 @@ function installSDKDependency(dir, pkgManager = 'npm') {
349
394
  },
350
395
  ];
351
396
 
352
- let lastError = null;
353
- const RETRIES_PER_TIER = 2;
354
- const TIMEOUT_MS = 5000; // 5 seconds per attempt
355
-
356
- for (const source of sources) {
357
- const cmd = source.getCommand(pkgManager);
358
-
359
- // Attempt with primary package manager (up to 2 retries, 5s timeout each)
360
- for (let attempt = 1; attempt <= RETRIES_PER_TIER; attempt++) {
397
+ const TIER1_DELAY_MS = 20 * 1000; // 20s
398
+ const TOTAL_TIMEOUT_MS = 5 * 60 * 1000; // 5min
399
+
400
+ return new Promise((resolve) => {
401
+ let isDone = false;
402
+ const activeChildren = new Set();
403
+ let tier2Timer = null;
404
+ let totalTimer = null;
405
+ let startedCount = 0;
406
+ let finishedFailureCount = 0;
407
+
408
+ const cleanupAndFinish = (result) => {
409
+ if (isDone) return;
410
+ isDone = true;
411
+ if (tier2Timer) clearTimeout(tier2Timer);
412
+ if (totalTimer) clearTimeout(totalTimer);
413
+ for (const child of activeChildren) {
414
+ killProcess(child);
415
+ }
416
+ activeChildren.clear();
417
+ resolve(result);
418
+ };
419
+
420
+ const runSource = (source) => {
421
+ if (isDone) return;
422
+ startedCount++;
423
+ const cmd = source.getCommand(pkgManager);
424
+ let child;
361
425
  try {
362
- execSync(cmd, { cwd: dir, stdio: 'ignore', timeout: TIMEOUT_MS });
363
- return { success: true, manager: pkgManager, source: source.name, attempt };
364
- } catch (err) {
365
- lastError = err;
426
+ child = spawn(cmd, { cwd: dir, shell: true, stdio: 'ignore' });
427
+ } catch (e) {
428
+ finishedFailureCount++;
429
+ checkAllFailed();
430
+ return;
366
431
  }
367
- }
368
432
 
369
- // If primary package manager failed and isn't npm, attempt npm fallback on this source (up to 2 retries, 5s timeout each)
370
- if (pkgManager !== 'npm') {
371
- const npmCmd = source.getCommand('npm');
372
- for (let attempt = 1; attempt <= RETRIES_PER_TIER; attempt++) {
373
- try {
374
- execSync(npmCmd, { cwd: dir, stdio: 'ignore', timeout: TIMEOUT_MS });
375
- return { success: true, manager: 'npm (fallback)', source: source.name, attempt };
376
- } catch (npmErr) {
377
- lastError = npmErr;
433
+ activeChildren.add(child);
434
+
435
+ const onExit = (code) => {
436
+ activeChildren.delete(child);
437
+ if (isDone) return;
438
+ if (code === 0) {
439
+ cleanupAndFinish({
440
+ success: true,
441
+ manager: pkgManager,
442
+ source: source.name,
443
+ });
444
+ } else {
445
+ finishedFailureCount++;
446
+ if (source.id === 'npmjs' && tier2Timer) {
447
+ clearTimeout(tier2Timer);
448
+ tier2Timer = null;
449
+ startBackupSources();
450
+ }
451
+ checkAllFailed();
378
452
  }
379
- }
380
- }
381
- }
453
+ };
382
454
 
383
- return {
384
- success: false,
385
- manager: pkgManager,
386
- error: lastError ? lastError.message : 'All 4 download sources timed out or failed (5s x 2 retries each)',
387
- };
455
+ child.on('close', onExit);
456
+ child.on('error', () => onExit(1));
457
+ };
458
+
459
+ const startBackupSources = () => {
460
+ if (isDone) return;
461
+ if (tier2Timer) {
462
+ clearTimeout(tier2Timer);
463
+ tier2Timer = null;
464
+ }
465
+ for (let i = 1; i < sources.length; i++) {
466
+ runSource(sources[i]);
467
+ }
468
+ };
469
+
470
+ const checkAllFailed = () => {
471
+ if (isDone) return;
472
+ if (startedCount === sources.length && finishedFailureCount === sources.length) {
473
+ cleanupAndFinish({
474
+ success: false,
475
+ manager: pkgManager,
476
+ error: 'All 4 download sources failed or timed out',
477
+ });
478
+ }
479
+ };
480
+
481
+ // t = 0s: Start Tier 1 (npmjs)
482
+ runSource(sources[0]);
483
+
484
+ // t = 20s: If still running, keep npmjs alive and start npmmirror + GitHub + CDN
485
+ tier2Timer = setTimeout(() => {
486
+ startBackupSources();
487
+ }, TIER1_DELAY_MS);
488
+
489
+ // t = 5min: Total timeout across all sources
490
+ totalTimer = setTimeout(() => {
491
+ cleanupAndFinish({
492
+ success: false,
493
+ manager: pkgManager,
494
+ error: 'SDK installation timed out after 5 minutes across all download sources',
495
+ });
496
+ }, TOTAL_TIMEOUT_MS);
497
+ });
388
498
  }
389
499
 
390
500
  function getGitEmail() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shiplens/cli",
3
- "version": "1.2.9",
3
+ "version": "1.3.1",
4
4
  "description": "Shiplens CLI (In Development) - 15-second zero-config analytics initialization and full telemetry analysis engine",
5
5
  "main": "lib/index.js",
6
6
  "bin": {