1+ import { Command } from "commander" ;
2+ import { logger , showErrorMessages } from "../core/logger" ;
3+ import { LOCAL_PACKAGES_DIR , ProjectConfigHandler , PackageSource } from "../config/project-config" ;
4+ import { GLOBAL_BLUESCRIPT_PATH } from "../config/global-config" ;
5+ import { cwd , exec } from "../core/shell" ;
6+ import * as fs from '../core/fs' ;
7+ import * as path from 'path' ;
8+
9+
10+ class InstallationHandler {
11+ private projectConfigHandler : ProjectConfigHandler ;
12+ private projectRootDir : string ;
13+ private packagesDir : string ;
14+
15+ constructor ( ) {
16+ this . projectRootDir = cwd ( ) ;
17+ this . projectConfigHandler = ProjectConfigHandler . load ( this . projectRootDir ) ;
18+ this . packagesDir = LOCAL_PACKAGES_DIR ( this . projectRootDir ) ;
19+ }
20+
21+ public async installAll ( ) {
22+ this . ensurePackageDir ( ) ;
23+ await this . processInstallQueue ( this . projectConfigHandler . getDepenencies ( ) ) ;
24+ }
25+
26+ public async installPackage ( url : string , version ?: string ) {
27+ this . ensurePackageDir ( ) ;
28+ const packageConfigHandler = await this . downloadPackage ( url , version ) ;
29+ const packageName = packageConfigHandler . getConfig ( ) . projectName ;
30+ await this . processInstallQueue ( packageConfigHandler . getDepenencies ( ) ) ;
31+ this . projectConfigHandler . addDependency ( { name : packageName , url, version} ) ;
32+ this . projectConfigHandler . save ( this . projectRootDir ) ;
33+ }
34+
35+ private async processInstallQueue ( queue : PackageSource [ ] ) {
36+ const installedPackages = new Set < string > ( ) ;
37+
38+ while ( queue . length > 0 ) {
39+ const currentPkg = queue . shift ( ) ;
40+ if ( ! currentPkg ) break ;
41+ if ( installedPackages . has ( currentPkg . name ) ) continue ;
42+
43+ const pkgConfigHandler = await this . downloadPackage ( currentPkg . url , currentPkg . version ) ;
44+ pkgConfigHandler . checkVmVersion ( this . projectConfigHandler . getConfig ( ) . vmVersion ) ;
45+ installedPackages . add ( currentPkg . name ) ;
46+ pkgConfigHandler . getDepenencies ( ) . forEach ( ( pkgDep ) => {
47+ installedPackages . add ( pkgDep . name ) ;
48+ } ) ;
49+ }
50+ }
51+
52+ private ensurePackageDir ( ) {
53+ if ( ! fs . exists ( this . packagesDir ) ) {
54+ fs . makeDir ( this . packagesDir ) ;
55+ }
56+ }
57+
58+ private async downloadPackage ( url : string , version ?: string ) : Promise < ProjectConfigHandler > {
59+ logger . log ( `Downloading from ${ url } ...` ) ;
60+ const tmpDir = path . join ( GLOBAL_BLUESCRIPT_PATH , 'tmp-package' ) ;
61+ const branchCmd = version ? `--branch ${ version } ` : '' ;
62+ const cmd = `git clone --depth 1 ${ branchCmd } ${ url } ${ tmpDir } ` ;
63+ try {
64+ await exec ( cmd , { silent : true } ) ;
65+ const gitDir = path . join ( tmpDir , '.git' ) ;
66+ if ( fs . exists ( gitDir ) ) {
67+ fs . removeDir ( gitDir ) ;
68+ }
69+ const configHandler = ProjectConfigHandler . load ( tmpDir ) ;
70+ const packageName = configHandler . getConfig ( ) . projectName ;
71+ const packageDir = path . join ( this . packagesDir , packageName ) ;
72+ if ( fs . exists ( packageDir ) ) {
73+ fs . removeDir ( packageDir ) ;
74+ }
75+ fs . moveDir ( tmpDir , packageDir ) ;
76+ return configHandler ;
77+ } catch ( error ) {
78+ if ( fs . exists ( tmpDir ) ) {
79+ fs . removeDir ( tmpDir ) ;
80+ }
81+ throw new Error ( `Failed to download package from '${ url } '.` , { cause : error } ) ;
82+ }
83+ }
84+ }
85+
86+
87+ export async function handleInstallCommand ( url : string | undefined , options : { tag ?: string } ) {
88+ try {
89+ const installationHandler = new InstallationHandler ( ) ;
90+ if ( url ) {
91+ installationHandler . installPackage ( url , options . tag ) ;
92+ } else {
93+ installationHandler . installAll ( ) ;
94+ }
95+ } catch ( error ) {
96+ const errorMessage =
97+ url ? `Failed to install ${ url } .` : `Failed to install packages.` ;
98+ logger . error ( errorMessage ) ;
99+ showErrorMessages ( error ) ;
100+ process . exit ( 1 ) ;
101+ }
102+ }
103+
104+ export function registerInstallCommand ( program : Command ) {
105+ program
106+ . command ( 'install' )
107+ . description ( 'install all dependencies, or add a new package via Git URL' )
108+ . argument ( '[git-url]' , 'git repository URL to add as a dependency' )
109+ . option ( '-t, --tag <tag>' , 'git tag or branch to checkout (e.g., v1.0.0)' )
110+ . action ( handleInstallCommand ) ;
111+ }
0 commit comments