@microwiseai/snapshot 0.3.46 → 0.3.57
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/dist/commands/access.js +1 -1
- package/dist/commands/access.js.map +1 -1
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +37 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +335 -112
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/setup.d.ts +5 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +52 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/access-sync.d.ts.map +1 -1
- package/dist/lib/access-sync.js +2 -0
- package/dist/lib/access-sync.js.map +1 -1
- package/dist/lib/config.d.ts +15 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +96 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/gitlab.js +3 -3
- package/dist/lib/gitlab.js.map +1 -1
- package/dist/lib/prerequisites.d.ts +33 -0
- package/dist/lib/prerequisites.d.ts.map +1 -0
- package/dist/lib/prerequisites.js +109 -0
- package/dist/lib/prerequisites.js.map +1 -0
- package/dist/lib/session.d.ts +6 -8
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/lib/session.js +10 -12
- package/dist/lib/session.js.map +1 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/node_modules/@microwiseai/snapshot-dedup/dist/dedup.d.ts +108 -0
- package/node_modules/@microwiseai/snapshot-dedup/dist/dedup.d.ts.map +1 -0
- package/node_modules/@microwiseai/snapshot-dedup/dist/dedup.js +196 -0
- package/node_modules/@microwiseai/snapshot-dedup/dist/dedup.js.map +1 -0
- package/node_modules/@microwiseai/snapshot-dedup/dist/index.d.ts +21 -0
- package/node_modules/@microwiseai/snapshot-dedup/dist/index.d.ts.map +1 -0
- package/node_modules/@microwiseai/snapshot-dedup/dist/index.js +27 -0
- package/node_modules/@microwiseai/snapshot-dedup/dist/index.js.map +1 -0
- package/node_modules/@microwiseai/snapshot-dedup/package.json +41 -0
- package/node_modules/@microwiseai/snapshot-parallel/dist/index.d.ts +9 -0
- package/node_modules/@microwiseai/snapshot-parallel/dist/index.d.ts.map +1 -0
- package/node_modules/@microwiseai/snapshot-parallel/dist/index.js +8 -0
- package/node_modules/@microwiseai/snapshot-parallel/dist/index.js.map +1 -0
- package/node_modules/@microwiseai/snapshot-parallel/dist/parallel-installer.d.ts +86 -0
- package/node_modules/@microwiseai/snapshot-parallel/dist/parallel-installer.d.ts.map +1 -0
- package/node_modules/@microwiseai/snapshot-parallel/dist/parallel-installer.js +159 -0
- package/node_modules/@microwiseai/snapshot-parallel/dist/parallel-installer.js.map +1 -0
- package/node_modules/@microwiseai/snapshot-parallel/package.json +41 -0
- package/package.json +7 -3
package/dist/commands/install.js
CHANGED
|
@@ -5,7 +5,7 @@ import { homedir, platform, arch } from 'os';
|
|
|
5
5
|
import { join } from 'path';
|
|
6
6
|
import { fetchSnapshotFromRepo, snapshotNameToRepoName } from '../lib/gitlab.js';
|
|
7
7
|
import { saveCurrentSnapshot } from '../lib/snapshot.js';
|
|
8
|
-
import { getLicenseKey, REGISTRY_PROXY_URL, VENDOR_NAME, PACKAGE_NAME, getIstCredentials, LICENSE_API_URL, verifyLicenseKey, PUBLIC_PACKAGES } from '../lib/config.js';
|
|
8
|
+
import { getLicenseKey, getProxyAuthToken, exchangeSessionToken, REGISTRY_PROXY_URL, VENDOR_NAME, PACKAGE_NAME, getIstCredentials, LICENSE_API_URL, verifyLicenseKey, PUBLIC_PACKAGES } from '../lib/config.js';
|
|
9
9
|
import { resolveSnapshotDependencies } from '../lib/package-resolver.js';
|
|
10
10
|
// transitive-resolver removed — npm handles dependency resolution automatically
|
|
11
11
|
import { installSkitPackage } from '../lib/skit-adapter.js';
|
|
@@ -79,7 +79,7 @@ const DEFAULT_INSTALLERS = {
|
|
|
79
79
|
/**
|
|
80
80
|
* Configure .npmrc for registry-proxy with session token
|
|
81
81
|
*/
|
|
82
|
-
function configureNpmrcForProxy(scopes
|
|
82
|
+
function configureNpmrcForProxy(scopes) {
|
|
83
83
|
const npmrcPath = join(homedir(), '.npmrc');
|
|
84
84
|
let originalContent = null;
|
|
85
85
|
// Backup existing .npmrc
|
|
@@ -105,8 +105,8 @@ function configureNpmrcForProxy(scopes, licenseKey) {
|
|
|
105
105
|
for (const scope of scopes) {
|
|
106
106
|
lines.push(`${scope}:registry=${REGISTRY_PROXY_URL}/`);
|
|
107
107
|
}
|
|
108
|
-
//
|
|
109
|
-
lines.push(`//${proxyHost}/:_authToken
|
|
108
|
+
// 환경변수 참조 — 토큰 직접 안 넣음 (npm이 ${ENV_VAR} 구문을 해석)
|
|
109
|
+
lines.push(`//${proxyHost}/:_authToken=\${IST_SESSION_TOKEN}`);
|
|
110
110
|
writeFileSync(npmrcPath, lines.join('\n') + '\n');
|
|
111
111
|
return { path: npmrcPath, content: originalContent };
|
|
112
112
|
}
|
|
@@ -324,33 +324,73 @@ async function installPackagesByType(packages, installerName, installers, snapsh
|
|
|
324
324
|
return result;
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
|
-
// Install packages
|
|
327
|
+
// Install packages
|
|
328
328
|
const isNpmInstaller = installerName === 'npm-proxy' || installerConfig.command.includes('npm install');
|
|
329
329
|
const entries = recordToEntries(packages);
|
|
330
|
-
|
|
331
|
-
//
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const success = runCommand(command);
|
|
338
|
-
return { success, error: success ? undefined : `Command failed: ${command}` };
|
|
339
|
-
}, {
|
|
340
|
-
concurrency: 4,
|
|
341
|
-
onProgress: (event) => {
|
|
342
|
-
const { result: r, current, total: t } = event;
|
|
343
|
-
const progress = `[${current}/${t}]`;
|
|
344
|
-
if (r.success) {
|
|
345
|
-
console.log(`${indent}${chalk.blue(progress)} ${r.spec}...${chalk.green(' ✓')}`);
|
|
330
|
+
if (isNpmInstaller) {
|
|
331
|
+
// Batch install: collect all not-yet-installed packages and run a single npm install -g
|
|
332
|
+
const toInstall = [];
|
|
333
|
+
for (const entry of entries) {
|
|
334
|
+
if (isNpmGlobalInstalled(entry.name, entry.version)) {
|
|
335
|
+
console.log(`${indent}${chalk.gray('skip')} ${entry.name}@${entry.version}${chalk.green(' (already installed)')}`);
|
|
336
|
+
result.installed.push(`${entry.name}@${entry.version}`);
|
|
346
337
|
}
|
|
347
338
|
else {
|
|
348
|
-
|
|
339
|
+
toInstall.push(entry);
|
|
349
340
|
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
341
|
+
}
|
|
342
|
+
if (toInstall.length > 0) {
|
|
343
|
+
const specs = toInstall.map(e => `${e.name}@${e.version}`);
|
|
344
|
+
const batchCommand = installerConfig.command.replace('{pkg}', specs.join(' '));
|
|
345
|
+
console.log(chalk.gray(`${indent} Running batch: npm install -g (${specs.length} packages)`));
|
|
346
|
+
const success = runCommand(batchCommand);
|
|
347
|
+
if (success) {
|
|
348
|
+
for (const spec of specs) {
|
|
349
|
+
console.log(`${indent}${chalk.blue('batch')} ${spec}...${chalk.green(' ✓')}`);
|
|
350
|
+
}
|
|
351
|
+
result.installed.push(...specs);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
console.log(chalk.red(`${indent} Batch install failed, falling back to individual installs...`));
|
|
355
|
+
for (const entry of toInstall) {
|
|
356
|
+
const pkgSpec = `${entry.name}@${entry.version}`;
|
|
357
|
+
const command = installerConfig.command.replace('{pkg}', pkgSpec);
|
|
358
|
+
const ok = runCommand(command);
|
|
359
|
+
if (ok) {
|
|
360
|
+
console.log(`${indent}${chalk.blue('fallback')} ${pkgSpec}...${chalk.green(' ✓')}`);
|
|
361
|
+
result.installed.push(pkgSpec);
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
console.log(`${indent}${chalk.blue('fallback')} ${pkgSpec}...${chalk.red(' ✗')}`);
|
|
365
|
+
result.failed.push(pkgSpec);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
// Non-npm installer: use parallel install as before
|
|
373
|
+
const parallelResult = await parallelInstall(entries, async (entry) => {
|
|
374
|
+
const pkgSpec = `${entry.name}@${entry.version}`;
|
|
375
|
+
const command = installerConfig.command.replace('{pkg}', pkgSpec);
|
|
376
|
+
const success = runCommand(command);
|
|
377
|
+
return { success, error: success ? undefined : `Command failed: ${command}` };
|
|
378
|
+
}, {
|
|
379
|
+
concurrency: 4,
|
|
380
|
+
onProgress: (event) => {
|
|
381
|
+
const { result: r, current, total: t } = event;
|
|
382
|
+
const progress = `[${current}/${t}]`;
|
|
383
|
+
if (r.success) {
|
|
384
|
+
console.log(`${indent}${chalk.blue(progress)} ${r.spec}...${chalk.green(' ✓')}`);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
console.log(`${indent}${chalk.blue(progress)} ${r.spec}...${chalk.red(' ✗')}`);
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
result.installed.push(...parallelResult.installed);
|
|
392
|
+
result.failed.push(...parallelResult.failed);
|
|
393
|
+
}
|
|
354
394
|
return result;
|
|
355
395
|
}
|
|
356
396
|
export async function installSnapshot(snapshot, depth = 0, parentInstallers = {}, options = {}) {
|
|
@@ -539,29 +579,74 @@ export async function installSnapshot(snapshot, depth = 0, parentInstallers = {}
|
|
|
539
579
|
continue;
|
|
540
580
|
}
|
|
541
581
|
}
|
|
542
|
-
// Install packages
|
|
582
|
+
// Install packages
|
|
543
583
|
const installEntries = recordToEntries(packages);
|
|
544
584
|
const installCfg = installerConfig; // capture for closure
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
const { result: r, current, total: t } = event;
|
|
554
|
-
const progress = `[${current}/${t}]`;
|
|
555
|
-
if (r.success) {
|
|
556
|
-
console.log(`${indent}${chalk.blue(progress)} ${r.spec}...${chalk.green(' ✓')}`);
|
|
585
|
+
const isNpmType = installerName === 'npm-proxy' || installCfg.command.includes('npm install');
|
|
586
|
+
if (isNpmType) {
|
|
587
|
+
// Batch install: collect all not-yet-installed packages and run a single npm install -g
|
|
588
|
+
const toInstall = [];
|
|
589
|
+
for (const entry of installEntries) {
|
|
590
|
+
if (isNpmGlobalInstalled(entry.name, entry.version)) {
|
|
591
|
+
console.log(`${indent}${chalk.gray('skip')} ${entry.name}@${entry.version}${chalk.green(' (already installed)')}`);
|
|
592
|
+
result.installed.push(`${entry.name}@${entry.version}`);
|
|
557
593
|
}
|
|
558
594
|
else {
|
|
559
|
-
|
|
595
|
+
toInstall.push(entry);
|
|
560
596
|
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
597
|
+
}
|
|
598
|
+
if (toInstall.length > 0) {
|
|
599
|
+
const specs = toInstall.map(e => `${e.name}@${e.version}`);
|
|
600
|
+
const batchCommand = installCfg.command.replace('{pkg}', specs.join(' '));
|
|
601
|
+
console.log(chalk.gray(`${indent} Running batch: npm install -g (${specs.length} packages)`));
|
|
602
|
+
const success = runCommand(batchCommand);
|
|
603
|
+
if (success) {
|
|
604
|
+
for (const spec of specs) {
|
|
605
|
+
console.log(`${indent}${chalk.blue('batch')} ${spec}...${chalk.green(' ✓')}`);
|
|
606
|
+
}
|
|
607
|
+
result.installed.push(...specs);
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
console.log(chalk.red(`${indent} Batch install failed, falling back to individual installs...`));
|
|
611
|
+
for (const entry of toInstall) {
|
|
612
|
+
const pkgSpec = `${entry.name}@${entry.version}`;
|
|
613
|
+
const command = installCfg.command.replace('{pkg}', pkgSpec);
|
|
614
|
+
const ok = runCommand(command);
|
|
615
|
+
if (ok) {
|
|
616
|
+
console.log(`${indent}${chalk.blue('fallback')} ${pkgSpec}...${chalk.green(' ✓')}`);
|
|
617
|
+
result.installed.push(pkgSpec);
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
console.log(`${indent}${chalk.blue('fallback')} ${pkgSpec}...${chalk.red(' ✗')}`);
|
|
621
|
+
result.failed.push(pkgSpec);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
// Non-npm installer: use parallel install as before
|
|
629
|
+
const parallelResult = await parallelInstall(installEntries, async (entry) => {
|
|
630
|
+
const pkgSpec = `${entry.name}@${entry.version}`;
|
|
631
|
+
const command = installCfg.command.replace('{pkg}', pkgSpec);
|
|
632
|
+
const success = runCommand(command);
|
|
633
|
+
return { success, error: success ? undefined : `Command failed: ${command}` };
|
|
634
|
+
}, {
|
|
635
|
+
concurrency: 4,
|
|
636
|
+
onProgress: (event) => {
|
|
637
|
+
const { result: r, current, total: t } = event;
|
|
638
|
+
const progress = `[${current}/${t}]`;
|
|
639
|
+
if (r.success) {
|
|
640
|
+
console.log(`${indent}${chalk.blue(progress)} ${r.spec}...${chalk.green(' ✓')}`);
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
console.log(`${indent}${chalk.blue(progress)} ${r.spec}...${chalk.red(' ✗')}`);
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
result.installed.push(...parallelResult.installed);
|
|
648
|
+
result.failed.push(...parallelResult.failed);
|
|
649
|
+
}
|
|
565
650
|
}
|
|
566
651
|
}
|
|
567
652
|
// Legacy: packages (use glpkg) - parallel
|
|
@@ -619,6 +704,55 @@ export async function installSnapshot(snapshot, depth = 0, parentInstallers = {}
|
|
|
619
704
|
}
|
|
620
705
|
return { result, installers };
|
|
621
706
|
}
|
|
707
|
+
/**
|
|
708
|
+
* Recursively collect all npm-type packages from snapshot and its extends chain.
|
|
709
|
+
* npm-type = installer is 'npm-proxy' OR command contains 'npm install'.
|
|
710
|
+
* When useProxy=true, glpkg/gitlab-install are also treated as npm (effectiveInstallers override).
|
|
711
|
+
* Skips skit packages. Returns Record<name, version>.
|
|
712
|
+
*/
|
|
713
|
+
async function collectAllNpmPackages(snapshot, useProxy = true) {
|
|
714
|
+
const npmPackages = {};
|
|
715
|
+
// Helper: is this installer name npm-type?
|
|
716
|
+
const isNpmInstaller = (installerName) => {
|
|
717
|
+
if (installerName === 'skit')
|
|
718
|
+
return false;
|
|
719
|
+
if (installerName === 'npm-proxy')
|
|
720
|
+
return true;
|
|
721
|
+
if (useProxy && (installerName === 'glpkg' || installerName === 'gitlab-install'))
|
|
722
|
+
return true;
|
|
723
|
+
const cfg = snapshot.installers?.[installerName] || DEFAULT_INSTALLERS[installerName];
|
|
724
|
+
return cfg ? cfg.command.includes('npm install') : false;
|
|
725
|
+
};
|
|
726
|
+
// Collect from snapshot.install
|
|
727
|
+
if (snapshot.install) {
|
|
728
|
+
for (const [installerName, packages] of Object.entries(snapshot.install)) {
|
|
729
|
+
if (isNpmInstaller(installerName)) {
|
|
730
|
+
Object.assign(npmPackages, packages);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
// Legacy: snapshot.packages uses glpkg → npm when useProxy
|
|
735
|
+
if (useProxy && snapshot.packages) {
|
|
736
|
+
Object.assign(npmPackages, snapshot.packages);
|
|
737
|
+
}
|
|
738
|
+
// Recurse into extends chain
|
|
739
|
+
if (snapshot.extends) {
|
|
740
|
+
try {
|
|
741
|
+
const baseSnapshot = await fetchSnapshotFromRepo(snapshot.extends);
|
|
742
|
+
const baseNpmPackages = await collectAllNpmPackages(baseSnapshot, useProxy);
|
|
743
|
+
// Base packages: don't override current snapshot's versions
|
|
744
|
+
for (const [name, version] of Object.entries(baseNpmPackages)) {
|
|
745
|
+
if (!(name in npmPackages)) {
|
|
746
|
+
npmPackages[name] = version;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
catch {
|
|
751
|
+
// If base snapshot fetch fails, continue with what we have.
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return npmPackages;
|
|
755
|
+
}
|
|
622
756
|
/**
|
|
623
757
|
* Recursively collect all packages from snapshot and its extends chain.
|
|
624
758
|
* This ensures .npmrc is configured with ALL scopes before installation begins,
|
|
@@ -658,51 +792,6 @@ export async function installCommand(name) {
|
|
|
658
792
|
║ Snapshot Installer ║
|
|
659
793
|
╚══════════════════════════════════════════════════════════════╝
|
|
660
794
|
`));
|
|
661
|
-
// ========== IST Login Check ==========
|
|
662
|
-
// Public packages can be installed without login (defined in config.ts)
|
|
663
|
-
// Strip version from name for comparison (e.g., @ist/minimal@0.6.9 -> @ist/minimal)
|
|
664
|
-
const packageNameOnly = name.includes('@', 1) ? name.substring(0, name.lastIndexOf('@')) : name;
|
|
665
|
-
const isPublicPackage = PUBLIC_PACKAGES.includes(packageNameOnly);
|
|
666
|
-
const istCredentials = getIstCredentials();
|
|
667
|
-
if (istCredentials?.userId) {
|
|
668
|
-
console.log(chalk.green('✓') + ' IST login verified');
|
|
669
|
-
}
|
|
670
|
-
else if (isPublicPackage) {
|
|
671
|
-
console.log(chalk.yellow('⚠') + ' No IST login - installing public package');
|
|
672
|
-
}
|
|
673
|
-
else {
|
|
674
|
-
console.log(chalk.red('⛔ IST login required for non-public packages'));
|
|
675
|
-
console.log(chalk.gray(' Run: ist auth login'));
|
|
676
|
-
console.log(chalk.gray(' Or install @ist/minimal first (no login required)'));
|
|
677
|
-
process.exit(1);
|
|
678
|
-
}
|
|
679
|
-
// ======================================
|
|
680
|
-
// ========== License Check ==========
|
|
681
|
-
// Use local license key storage (from 'snapshot license activate')
|
|
682
|
-
const storedLicenseKey = getLicenseKey();
|
|
683
|
-
let licenseValid = false;
|
|
684
|
-
if (storedLicenseKey) {
|
|
685
|
-
try {
|
|
686
|
-
const verifyResult = await verifyLicenseKey(storedLicenseKey);
|
|
687
|
-
if (verifyResult.valid) {
|
|
688
|
-
licenseValid = true;
|
|
689
|
-
markLicenseChecked(PACKAGE_NAME);
|
|
690
|
-
console.log(chalk.green('✓') + ' License verified');
|
|
691
|
-
}
|
|
692
|
-
else {
|
|
693
|
-
console.log(chalk.yellow('⚠') + ' License invalid - trying anonymous access...');
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
catch {
|
|
697
|
-
console.log(chalk.yellow('⚠') + ' License check failed - trying anonymous access...');
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
else {
|
|
701
|
-
// No license key - try anonymous access (public packages will work via registry-proxy)
|
|
702
|
-
console.log(chalk.yellow('⚠') + ' No license - trying anonymous access...');
|
|
703
|
-
console.log(chalk.gray(' (Public packages will install without license)'));
|
|
704
|
-
}
|
|
705
|
-
// ====================================
|
|
706
795
|
// Validate name
|
|
707
796
|
let repoName;
|
|
708
797
|
try {
|
|
@@ -719,16 +808,32 @@ export async function installCommand(name) {
|
|
|
719
808
|
}
|
|
720
809
|
console.log(chalk.gray(`Snapshot: ${name}`));
|
|
721
810
|
console.log(chalk.gray(`Repo: ${repoName}`));
|
|
722
|
-
// Check auth:
|
|
811
|
+
// Check auth: exchange fresh session token
|
|
723
812
|
const licenseKey = getLicenseKey();
|
|
724
|
-
|
|
813
|
+
let sessionToken = null;
|
|
725
814
|
if (licenseKey) {
|
|
726
|
-
|
|
815
|
+
sessionToken = licenseKey;
|
|
816
|
+
console.log(chalk.green('✓') + ' Using license key');
|
|
727
817
|
}
|
|
728
818
|
else {
|
|
729
|
-
|
|
819
|
+
// Fresh token 교환 시도
|
|
820
|
+
sessionToken = await exchangeSessionToken();
|
|
821
|
+
if (sessionToken) {
|
|
822
|
+
console.log(chalk.green('✓') + ' Session token acquired');
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
// 기존 토큰 폴백
|
|
826
|
+
sessionToken = getProxyAuthToken();
|
|
827
|
+
if (sessionToken) {
|
|
828
|
+
console.log(chalk.green('✓') + ' Using existing session token');
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
console.log(chalk.yellow('⚠') + ' No auth token - trying anonymous access');
|
|
832
|
+
}
|
|
833
|
+
}
|
|
730
834
|
}
|
|
731
|
-
|
|
835
|
+
const useProxy = true;
|
|
836
|
+
// Fetch snapshot first — access check depends on snapshot.json content
|
|
732
837
|
console.log(chalk.gray('Fetching snapshot...'));
|
|
733
838
|
let snapshot;
|
|
734
839
|
try {
|
|
@@ -744,15 +849,64 @@ export async function installCommand(name) {
|
|
|
744
849
|
if (snapshot.description) {
|
|
745
850
|
console.log(chalk.gray(` ${snapshot.description}`));
|
|
746
851
|
}
|
|
747
|
-
// ==========
|
|
748
|
-
//
|
|
749
|
-
//
|
|
750
|
-
//
|
|
751
|
-
//
|
|
752
|
-
//
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
852
|
+
// ========== Access Check (based on snapshot.json access field) ==========
|
|
853
|
+
// Determine access type from snapshot.json:
|
|
854
|
+
// - access: "public" → no login/license required
|
|
855
|
+
// - access: "authenticated" → IST login required, no license needed
|
|
856
|
+
// - access: { type: "licensed", productId } → license required
|
|
857
|
+
// - access field absent → fallback to PUBLIC_PACKAGES for backward compat
|
|
858
|
+
const packageNameOnly = name.includes('@', 1) ? name.substring(0, name.lastIndexOf('@')) : name;
|
|
859
|
+
const accessField = snapshot.access;
|
|
860
|
+
const isPublicAccess = accessField === 'public'
|
|
861
|
+
|| (!accessField && PUBLIC_PACKAGES.includes(packageNameOnly)); // fallback for old snapshots without access field
|
|
862
|
+
const isAuthenticatedAccess = accessField === 'authenticated';
|
|
863
|
+
const isLicensedAccess = !!accessField && typeof accessField === 'object' && accessField.type === 'licensed';
|
|
864
|
+
// --- IST Login Check ---
|
|
865
|
+
const istCredentials = getIstCredentials();
|
|
866
|
+
if (istCredentials?.userId) {
|
|
867
|
+
console.log(chalk.green('✓') + ' IST login verified');
|
|
868
|
+
}
|
|
869
|
+
else if (isPublicAccess) {
|
|
870
|
+
console.log(chalk.yellow('⚠') + ' No IST login - installing public package');
|
|
871
|
+
}
|
|
872
|
+
else {
|
|
873
|
+
console.log(chalk.red('⛔ IST login required for non-public packages'));
|
|
874
|
+
console.log(chalk.gray(' Run: ist auth login'));
|
|
875
|
+
console.log(chalk.gray(' Or install a public snapshot first (no login required)'));
|
|
876
|
+
process.exit(1);
|
|
877
|
+
}
|
|
878
|
+
// --- License Check ---
|
|
879
|
+
const storedLicenseKey = getLicenseKey();
|
|
880
|
+
let licenseValid = false;
|
|
881
|
+
if (isPublicAccess || isAuthenticatedAccess) {
|
|
882
|
+
// Public and authenticated snapshots — skip license check entirely
|
|
883
|
+
const label = isAuthenticatedAccess ? 'Authenticated' : 'Public';
|
|
884
|
+
console.log(chalk.green('✓') + ` ${label} snapshot - no license required`);
|
|
885
|
+
}
|
|
886
|
+
else if (storedLicenseKey) {
|
|
887
|
+
try {
|
|
888
|
+
const verifyResult = await verifyLicenseKey(storedLicenseKey);
|
|
889
|
+
if (verifyResult.valid) {
|
|
890
|
+
licenseValid = true;
|
|
891
|
+
markLicenseChecked(PACKAGE_NAME);
|
|
892
|
+
console.log(chalk.green('✓') + ' License verified');
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
console.log(chalk.yellow('⚠') + ' License invalid - trying anonymous access...');
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
catch {
|
|
899
|
+
console.log(chalk.yellow('⚠') + ' License check failed - trying anonymous access...');
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
// No license key - try anonymous access
|
|
904
|
+
console.log(chalk.yellow('⚠') + ' No license - trying anonymous access...');
|
|
905
|
+
console.log(chalk.gray(' (Public packages will install without license)'));
|
|
906
|
+
}
|
|
907
|
+
// --- Licensed Snapshot Gate ---
|
|
908
|
+
if (isLicensedAccess) {
|
|
909
|
+
const productId = accessField.productId;
|
|
756
910
|
if (!licenseValid) {
|
|
757
911
|
console.log('');
|
|
758
912
|
console.log(chalk.red('⛔ This snapshot requires a license'));
|
|
@@ -762,11 +916,10 @@ export async function installCommand(name) {
|
|
|
762
916
|
}
|
|
763
917
|
console.log(chalk.green('✓') + ` License verified for ${productId}`);
|
|
764
918
|
}
|
|
765
|
-
//
|
|
766
|
-
// ============================================
|
|
919
|
+
// =======================================================================
|
|
767
920
|
// ========== Authorize Session with Registry Proxy ==========
|
|
768
921
|
let sessionId;
|
|
769
|
-
if (licenseKey) {
|
|
922
|
+
if (licenseKey || sessionToken) {
|
|
770
923
|
// Collect all packages for session authorization
|
|
771
924
|
const sessionPackages = [];
|
|
772
925
|
// From snapshot.install
|
|
@@ -785,17 +938,20 @@ export async function installCommand(name) {
|
|
|
785
938
|
}
|
|
786
939
|
// Transitive deps removed — npm handles dependency resolution automatically
|
|
787
940
|
if (sessionPackages.length > 0) {
|
|
941
|
+
// Determine auth token: licenseKey takes priority, fallback to sessionToken
|
|
942
|
+
const authToken = licenseKey || sessionToken;
|
|
788
943
|
console.log(chalk.gray(`Authorizing session for ${sessionPackages.length} packages...`));
|
|
789
944
|
console.log(chalk.gray(` Packages: ${sessionPackages.join(', ')}`));
|
|
945
|
+
console.log(chalk.gray(` Auth type: ${licenseKey ? 'license key' : 'session token'}`));
|
|
790
946
|
try {
|
|
791
|
-
sessionId = await authorizeSession(
|
|
947
|
+
sessionId = await authorizeSession(authToken, name, sessionPackages);
|
|
792
948
|
console.log(chalk.green('✓') + ' Session authorized: ' + sessionId);
|
|
793
949
|
}
|
|
794
950
|
catch (error) {
|
|
795
951
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
796
|
-
// Session authorization failure is not fatal - fallback to
|
|
952
|
+
// Session authorization failure is not fatal - fallback to token auth
|
|
797
953
|
console.log(chalk.yellow('⚠') + ` Session authorization failed: ${errMsg}`);
|
|
798
|
-
console.log(chalk.gray(' Falling back to
|
|
954
|
+
console.log(chalk.gray(' Falling back to token authentication...'));
|
|
799
955
|
}
|
|
800
956
|
}
|
|
801
957
|
}
|
|
@@ -807,12 +963,14 @@ export async function installCommand(name) {
|
|
|
807
963
|
if (useProxy) {
|
|
808
964
|
const scopes = extractScopes(allPackages);
|
|
809
965
|
if (scopes.length > 0) {
|
|
810
|
-
if (
|
|
966
|
+
if (sessionToken) {
|
|
967
|
+
// 환경변수에 설정 (npm이 .npmrc의 ${IST_SESSION_TOKEN}을 해석)
|
|
968
|
+
process.env.IST_SESSION_TOKEN = sessionToken;
|
|
811
969
|
console.log(chalk.gray(`Configuring registry-proxy for scopes: ${scopes.join(', ')}`));
|
|
812
|
-
npmrcBackup = configureNpmrcForProxy(scopes
|
|
970
|
+
npmrcBackup = configureNpmrcForProxy(scopes);
|
|
813
971
|
}
|
|
814
972
|
else {
|
|
815
|
-
//
|
|
973
|
+
// No auth token at all — anonymous mode (public packages only)
|
|
816
974
|
console.log(chalk.gray(`Configuring registry-proxy (anonymous) for scopes: ${scopes.join(', ')}`));
|
|
817
975
|
npmrcBackup = configureNpmrcForProxyAnonymous(scopes);
|
|
818
976
|
}
|
|
@@ -825,7 +983,58 @@ export async function installCommand(name) {
|
|
|
825
983
|
effectiveInstallers['glpkg'] = DEFAULT_INSTALLERS['npm-proxy'];
|
|
826
984
|
effectiveInstallers['gitlab-install'] = DEFAULT_INSTALLERS['npm-proxy'];
|
|
827
985
|
}
|
|
986
|
+
// ========== Pre-install: batch all npm packages from extends chain ==========
|
|
987
|
+
// Collect all npm-type packages across the entire extends chain and install
|
|
988
|
+
// them in a single `npm install -g` call, instead of one per snapshot layer.
|
|
989
|
+
const allNpmPackages = await collectAllNpmPackages(snapshot, useProxy);
|
|
990
|
+
const npmEntries = Object.entries(allNpmPackages);
|
|
991
|
+
if (npmEntries.length > 0) {
|
|
992
|
+
console.log('');
|
|
993
|
+
console.log(chalk.cyan(`► Pre-installing ${npmEntries.length} npm packages from entire extends chain...`));
|
|
994
|
+
// Filter out already-installed packages
|
|
995
|
+
const toInstall = [];
|
|
996
|
+
for (const [name, version] of npmEntries) {
|
|
997
|
+
if (isNpmGlobalInstalled(name, version)) {
|
|
998
|
+
console.log(` ${chalk.gray('skip')} ${name}@${version}${chalk.green(' (already installed)')}`);
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
toInstall.push({ name, version });
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
if (toInstall.length > 0) {
|
|
1005
|
+
const specs = toInstall.map(e => `${e.name}@${e.version}`);
|
|
1006
|
+
const npmCommand = DEFAULT_INSTALLERS['npm-proxy'].command.replace('{pkg}', specs.join(' '));
|
|
1007
|
+
console.log(chalk.gray(` Running single batch: npm install -g (${specs.length} packages)`));
|
|
1008
|
+
const batchSuccess = runCommand(npmCommand);
|
|
1009
|
+
if (batchSuccess) {
|
|
1010
|
+
for (const spec of specs) {
|
|
1011
|
+
console.log(` ${chalk.blue('batch')} ${spec}...${chalk.green(' ✓')}`);
|
|
1012
|
+
}
|
|
1013
|
+
console.log(chalk.green(`✓ Pre-installed ${specs.length} npm packages in one batch`));
|
|
1014
|
+
}
|
|
1015
|
+
else {
|
|
1016
|
+
// Batch failed - fall back to individual installs
|
|
1017
|
+
console.log(chalk.yellow(' Batch install failed, falling back to individual installs...'));
|
|
1018
|
+
for (const entry of toInstall) {
|
|
1019
|
+
const pkgSpec = `${entry.name}@${entry.version}`;
|
|
1020
|
+
const cmd = DEFAULT_INSTALLERS['npm-proxy'].command.replace('{pkg}', pkgSpec);
|
|
1021
|
+
const ok = runCommand(cmd);
|
|
1022
|
+
if (ok) {
|
|
1023
|
+
console.log(` ${chalk.blue('fallback')} ${pkgSpec}...${chalk.green(' ✓')}`);
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
console.log(` ${chalk.blue('fallback')} ${pkgSpec}...${chalk.red(' ✗')}`);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
else {
|
|
1032
|
+
console.log(chalk.green('✓ All npm packages already installed'));
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
// =============================================================================
|
|
828
1036
|
// Install (handles extends recursively)
|
|
1037
|
+
// installSnapshot's internal isNpmGlobalInstalled checks will skip already-installed packages
|
|
829
1038
|
let installError = null;
|
|
830
1039
|
let result = { installed: [], failed: [] };
|
|
831
1040
|
try {
|
|
@@ -847,6 +1056,8 @@ export async function installCommand(name) {
|
|
|
847
1056
|
restoreNpmrc(npmrcBackup);
|
|
848
1057
|
console.log(chalk.gray('Restored .npmrc'));
|
|
849
1058
|
}
|
|
1059
|
+
// 환경변수 정리
|
|
1060
|
+
delete process.env.IST_SESSION_TOKEN;
|
|
850
1061
|
}
|
|
851
1062
|
if (installError) {
|
|
852
1063
|
console.log(chalk.red(`Error during installation: ${installError.message}`));
|
|
@@ -879,6 +1090,18 @@ export async function installCommand(name) {
|
|
|
879
1090
|
console.log('');
|
|
880
1091
|
console.log(chalk.green('✓') + ' Snapshot info saved');
|
|
881
1092
|
}
|
|
1093
|
+
// Post-install: run doctor to check prerequisites
|
|
1094
|
+
console.log('');
|
|
1095
|
+
const { checkPrerequisites, MINIMAL_PREREQUISITES } = await import('../lib/prerequisites.js');
|
|
1096
|
+
const prereqResults = checkPrerequisites(MINIMAL_PREREQUISITES);
|
|
1097
|
+
const missingPrereqs = prereqResults.filter(r => !r.installed);
|
|
1098
|
+
if (missingPrereqs.length > 0) {
|
|
1099
|
+
console.log(chalk.yellow(`\n⚠ ${missingPrereqs.length} prerequisite(s) not satisfied:`));
|
|
1100
|
+
for (const r of missingPrereqs) {
|
|
1101
|
+
console.log(` ${chalk.red('❌')} ${r.name} — required by ${r.requiredBy.join(', ')}`);
|
|
1102
|
+
}
|
|
1103
|
+
console.log(chalk.gray(`\nRun 'snapshot setup' to install, or 'snapshot doctor' for details.`));
|
|
1104
|
+
}
|
|
882
1105
|
// Send telemetry (fire and forget)
|
|
883
1106
|
sendInstallTelemetry({
|
|
884
1107
|
package: snapshot.name,
|