@@ -5,6 +5,7 @@ import fsExtra from 'fs-extra';
55import os from 'os' ;
66import path from 'path' ;
77import { promisify } from 'util' ;
8+ import yaml from 'yaml' ;
89import AdminForth , { AdminForthConfigMenuItem } from '../index.js' ;
910import { ADMIN_FORTH_ABSOLUTE_PATH , getComponentNameFromPath , transformObject , deepMerge , md5hash , slugifyString } from './utils.js' ;
1011import { ICodeInjector } from '../types/Back.js' ;
@@ -142,54 +143,37 @@ class CodeInjector implements ICodeInjector {
142143
143144 }
144145
145- async runNpmShell ( { command, cwd, envOverrides = { } } : {
146+ async runPnpmShell ( { command, cwd, envOverrides = { } } : {
146147 command : string ,
147148 cwd : string ,
148149 envOverrides ?: { [ key : string ] : string }
149150 } ) {
150- const nodeBinary = process . execPath ; // Path to the Node.js binary running this script
151- // On Windows, npm is npm.cmd, on Unix systems it's npm
152- const npmExecutable = process . platform === 'win32' ? 'npm.cmd' : 'npm' ;
153- const npmPath = path . join ( path . dirname ( nodeBinary ) , npmExecutable ) ; // Path to the npm executable
151+ const pnpmExecutable = process . platform === 'win32' ? 'pnpm.cmd' : 'pnpm' ;
154152 const env = {
155153 VITE_ADMINFORTH_PUBLIC_PATH : this . adminforth . config . baseUrl ,
156154 FORCE_COLOR : '1' ,
157155 ...process . env ,
158156 ...envOverrides ,
159157 } ;
160158
161- afLogger . trace ( `⚙️ exec: npm ${ command } ` ) ;
162- afLogger . trace ( `🪲 npm ${ command } cwd: ${ cwd } ` ) ;
163- afLogger . trace ( `npm ${ command } done in` ) ;
164-
165- // On Windows, execute npm.cmd directly; on Unix, use node + npm
166- let execCommand : string ;
167- if ( process . platform === 'win32' ) {
168- // Quote path if it contains spaces
169- const quotedNpmPath = npmPath . includes ( ' ' ) ? `"${ npmPath } "` : npmPath ;
170- execCommand = `${ quotedNpmPath } ${ command } ` ;
171- } else {
172- // Quote paths that contain spaces (for Unix systems)
173- const quotedNodeBinary = nodeBinary . includes ( ' ' ) ? `"${ nodeBinary } "` : nodeBinary ;
174- const quotedNpmPath = npmPath . includes ( ' ' ) ? `"${ npmPath } "` : npmPath ;
175- execCommand = `${ quotedNodeBinary } ${ quotedNpmPath } ${ command } ` ;
176- }
177-
159+ afLogger . trace ( `⚙️ exec: pnpm ${ command } ` ) ;
160+ afLogger . trace ( `🪲 pnpm ${ command } cwd: ${ cwd } ` ) ;
161+
162+ const execCommand = `${ pnpmExecutable } ${ command } ` ;
178163 const execOptions : any = {
179164 cwd,
180165 env,
181166 } ;
182-
183- // On Windows, use shell to execute .cmd files
167+
184168 if ( process . platform === 'win32' ) {
185169 execOptions . shell = true ;
186170 }
187-
188- const { stdout : out , stderr : err } = await execAsync ( execCommand , execOptions ) ;
189- afLogger . trace ( `npm ${ command } done in` ) ;
171+
172+ const { stderr : err } = await execAsync ( execCommand , execOptions ) ;
173+ afLogger . trace ( `pnpm ${ command } done in` ) ;
190174
191175 if ( err ) {
192- afLogger . trace ( `🪲npm ${ command } errors/warnings: ${ err } ` ) ;
176+ afLogger . trace ( `🪲pnpm ${ command } errors/warnings: ${ err } ` ) ;
193177 }
194178 }
195179
@@ -207,7 +191,7 @@ class CodeInjector implements ICodeInjector {
207191 return path . join ( this . getSpaDir ( ) , 'dist' ) ;
208192 }
209193
210- async packagesFromNpm ( dir : string ) : Promise < [ string , string [ ] ] > {
194+ async packagesFromPnpm ( dir : string ) : Promise < [ string , string [ ] ] > {
211195 const usersPackagePath = path . join ( dir , 'package.json' ) ;
212196 let packageContent : { dependencies : any , devDependencies : any } = null ;
213197 let lockHash : string = '' ;
@@ -218,28 +202,43 @@ class CodeInjector implements ICodeInjector {
218202 // user package.json does not exist, user does not have custom components
219203 }
220204 if ( packageContent ) {
221- const lockPath = path . join ( dir , 'package -lock.json ' ) ;
222- let lock = null ;
205+ const lockPath = path . join ( dir , 'pnpm -lock.yaml ' ) ;
206+ let lock : any = null ;
223207 try {
224- lock = JSON . parse ( await fs . promises . readFile ( lockPath , 'utf-8' ) ) ;
208+ lock = yaml . parse ( await fs . promises . readFile ( lockPath , 'utf-8' ) ) ;
225209 } catch ( e ) {
226- throw new Error ( `Custom package -lock.json does not exist in ${ dir } , but package.json does.
227- We can't determine version of packages without package -lock.json . Please run npm install in ${ dir } ` ) ;
210+ throw new Error ( `Custom pnpm -lock.yaml does not exist in ${ dir } , but package.json does.
211+ We can't determine version of packages without pnpm -lock.yaml . Please run pnpm install in ${ dir } ` ) ;
228212 }
229213 lockHash = hashify ( lock ) ;
214+ const importer = lock ?. importers ?. [ '.' ] ;
215+ if ( ! importer ) {
216+ throw new Error ( `pnpm-lock.yaml in ${ dir } does not contain importer ".". Please run pnpm install in ${ dir } ` ) ;
217+ }
218+
219+ const importerDeps = {
220+ ...( importer . dependencies || { } ) ,
221+ ...( importer . devDependencies || { } ) ,
222+ ...( importer . optionalDependencies || { } ) ,
223+ } ;
230224
231225 packages = [
232- ...Object . keys ( packageContent . dependencies || [ ] ) ,
233- ...Object . keys ( packageContent . devDependencies || [ ] )
226+ ...Object . keys ( packageContent . dependencies || { } ) ,
227+ ...Object . keys ( packageContent . devDependencies || { } )
234228 ] . reduce (
235229 ( acc , packageName ) => {
236- const pack = lock . packages [ `node_modules/${ packageName } ` ] ;
237- if ( ! pack ) {
238- throw new Error ( `Package ${ packageName } is not in package-lock.json but is in package.json. Please run 'npm install' in ${ dir } ` ) ;
230+ const depInfo = importerDeps [ packageName ] ;
231+ const raw = typeof depInfo === 'string'
232+ ? depInfo
233+ : ( depInfo ?. specifier || depInfo ?. version ) ;
234+
235+ if ( ! raw ) {
236+ throw new Error ( `Package ${ packageName } is not in pnpm-lock.yaml but is in package.json. Please run 'pnpm install' in ${ dir } ` ) ;
239237 }
240- const version = pack . version ;
241238
242- acc . push ( `${ packageName } @${ version } ` ) ;
239+ const cleaned = raw . includes ( '(' ) ? raw . split ( '(' ) [ 0 ] : raw ;
240+
241+ acc . push ( `${ packageName } @${ cleaned } ` ) ;
243242 return acc ;
244243 } , [ ]
245244 ) ;
@@ -653,17 +652,17 @@ class CodeInjector implements ICodeInjector {
653652
654653
655654 /* hash checking */
656- const spaPackageLockPath = path . join ( this . spaTmpPath ( ) , 'package -lock.json ' ) ;
657- const spaPackageLock = JSON . parse ( await fs . promises . readFile ( spaPackageLockPath , 'utf-8' ) ) ;
658- const spaLockHash = hashify ( spaPackageLock ) ;
655+ const spaPnpmLockPath = path . join ( this . spaTmpPath ( ) , 'pnpm -lock.yaml ' ) ;
656+ const spaPnpmLock = yaml . parse ( await fs . promises . readFile ( spaPnpmLockPath , 'utf-8' ) ) ;
657+ const spaLockHash = hashify ( spaPnpmLock ) ;
659658
660659 /* customPackageLock */
661660 let usersLockHash : string = '' ;
662661 let usersPackages : string [ ] = [ ] ;
663662
664663
665664 if ( this . adminforth . config . customization ?. customComponentsDir ) {
666- [ usersLockHash , usersPackages ] = await this . packagesFromNpm ( this . adminforth . config . customization . customComponentsDir ) ;
665+ [ usersLockHash , usersPackages ] = await this . packagesFromPnpm ( this . adminforth . config . customization . customComponentsDir ) ;
667666 }
668667
669668 const pluginPackages : {
@@ -675,7 +674,7 @@ class CodeInjector implements ICodeInjector {
675674 // for every installed plugin generate packages
676675 for ( const plugin of this . adminforth . activatedPlugins ) {
677676 afLogger . trace ( `🔧 Checking packages for plugin, ${ plugin . constructor . name } , ${ plugin . customFolderPath } ` ) ;
678- const [ lockHash , packages ] = await this . packagesFromNpm ( plugin . customFolderPath ) ;
677+ const [ lockHash , packages ] = await this . packagesFromPnpm ( plugin . customFolderPath ) ;
679678 if ( packages . length ) {
680679 pluginPackages . push ( {
681680 pluginName : plugin . constructor . name ,
@@ -696,18 +695,18 @@ class CodeInjector implements ICodeInjector {
696695 const existingHash = await fs . promises . readFile ( hashPath , 'utf-8' ) ;
697696 await this . checkIconNames ( icons ) ;
698697 if ( existingHash === fullHash ) {
699- afLogger . trace ( `🪲Hashes match, skipping npm ci/ install, from file: ${ existingHash } , actual: ${ fullHash } ` ) ;
698+ afLogger . trace ( `🪲Hashes match, skipping pnpm install, from file: ${ existingHash } , actual: ${ fullHash } ` ) ;
700699 return ;
701700 } else {
702- afLogger . trace ( `🪲 Hashes do not match: from file: ${ existingHash } actual: ${ fullHash } , proceeding with npm ci/ install` ) ;
701+ afLogger . trace ( `🪲 Hashes do not match: from file: ${ existingHash } actual: ${ fullHash } , proceeding with pnpm install` ) ;
703702 }
704703 } catch ( e ) {
705704 // ignore
706- afLogger . trace ( `🪲Hash file does not exist, proceeding with npm ci/ install, ${ e } ` ) ;
705+ afLogger . trace ( `🪲Hash file does not exist, proceeding with pnpm install, ${ e } ` ) ;
707706 }
708707
709- await this . runNpmShell ( { command : 'ci ' , cwd : this . spaTmpPath ( ) , envOverrides : {
710- NODE_ENV : 'development' // othewrwise it will not install devDependencies which we still need, e.g for extract
708+ await this . runPnpmShell ( { command : 'install --frozen-lockfile ' , cwd : this . spaTmpPath ( ) , envOverrides : {
709+ NODE_ENV : 'development' // otherwise it will not install devDependencies which we still need, e.g for extract
711710 } } ) ;
712711
713712 const allPacks = [
@@ -726,11 +725,11 @@ class CodeInjector implements ICodeInjector {
726725 const allPacksUnique = Array . from ( new Set ( allPacksFiltered ) ) ;
727726
728727 if ( allPacks . length ) {
729- const npmInstallCommand = `install ${ allPacksUnique . join ( ' ' ) } ` ;
730- await this . runNpmShell ( {
731- command : npmInstallCommand , cwd : this . spaTmpPath ( ) ,
728+ const pnpmInstallCommand = `install ${ allPacksUnique . join ( ' ' ) } ` ;
729+ await this . runPnpmShell ( {
730+ command : pnpmInstallCommand , cwd : this . spaTmpPath ( ) ,
732731 envOverrides : {
733- NODE_ENV : 'development' // othewrwise it will not install devDependencies which we still need, e.g for extract
732+ NODE_ENV : 'development' // otherwise it will not install devDependencies which we still need, e.g for extract
734733 }
735734 } ) ;
736735 }
@@ -938,7 +937,7 @@ class CodeInjector implements ICodeInjector {
938937 }
939938
940939 if ( ! skipExtract ) {
941- await this . runNpmShell ( { command : 'run i18n:extract' , cwd} ) ;
940+ await this . runPnpmShell ( { command : 'run i18n:extract' , cwd} ) ;
942941
943942 // create serveDir if not exists
944943 await fs . promises . mkdir ( serveDir , { recursive : true } ) ;
@@ -956,7 +955,7 @@ class CodeInjector implements ICodeInjector {
956955 if ( ! skipBuild ) {
957956
958957 // TODO probably add option to build with tsh check (plain 'build')
959- await this . runNpmShell ( { command : 'run build-only' , cwd} ) ;
958+ await this . runPnpmShell ( { command : 'run build-only' , cwd} ) ;
960959
961960 // coy dist to serveDir
962961 await fsExtra . copy ( path . join ( cwd , 'dist' ) , serveDir , { recursive : true } ) ;
@@ -969,7 +968,7 @@ class CodeInjector implements ICodeInjector {
969968 } else {
970969
971970 const command = 'run dev' ;
972- afLogger . info ( `⚙️ spawn: npm ${ command } ...` ) ;
971+ afLogger . info ( `⚙️ spawn: pnpm ${ command } ...` ) ;
973972 if ( process . env . VITE_ADMINFORTH_PUBLIC_PATH ) {
974973 afLogger . info ( `⚠️ Your VITE_ADMINFORTH_PUBLIC_PATH: ${ process . env . VITE_ADMINFORTH_PUBLIC_PATH } has no effect` ) ;
975974 }
@@ -979,14 +978,12 @@ class CodeInjector implements ICodeInjector {
979978 ...process . env ,
980979 } ;
981980
982- const nodeBinary = process . execPath ;
983- const npmPath = path . join ( path . dirname ( nodeBinary ) , 'npm' ) ;
984-
981+ const pnpmExecutable = process . platform === 'win32' ? 'pnpm.cmd' : 'pnpm' ;
985982 let devServer ;
986983 if ( process . platform === 'win32' ) {
987- devServer = spawn ( 'npm' , command . split ( ' ' ) , { cwd, env, shell : true } ) ;
984+ devServer = spawn ( pnpmExecutable , command . split ( ' ' ) , { cwd, env, shell : true } ) ;
988985 } else {
989- devServer = spawn ( ` ${ nodeBinary } ` , [ ` ${ npmPath } ` , ... command . split ( ' ' ) ] , { cwd, env } ) ;
986+ devServer = spawn ( pnpmExecutable , command . split ( ' ' ) , { cwd, env } ) ;
990987 }
991988 devServer . stdout . on ( 'data' , ( data ) => {
992989 if ( data . includes ( '➜' ) ) {
0 commit comments