@shiplens/cli 1.2.9 → 1.3.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/lib/commands/init.js +11 -3
- package/lib/injector.js +149 -39
- package/package.json +1 -1
package/lib/commands/init.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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