@@ -6,8 +6,8 @@ import type {Manifest, DependencyRequestPatterns} from '../../types.js';
66import type Config from '../../config.js' ;
77import type { RegistryNames } from '../../registries/index.js' ;
88import normalizeManifest from '../../util/normalize-manifest/index.js' ;
9- import { registryNames } from '../../registries/index.js' ;
109import { MessageError } from '../../errors.js' ;
10+ import InstallationIntegrityChecker from '../../integrity-checker.js' ;
1111import Lockfile from '../../lockfile/wrapper.js' ;
1212import lockStringify from '../../lockfile/stringify.js' ;
1313import PackageFetcher from '../../package-fetcher.js' ;
@@ -20,9 +20,7 @@ import {registries} from '../../registries/index.js';
2020import { clean } from './clean.js' ;
2121import * as constants from '../../constants.js' ;
2222import * as fs from '../../util/fs.js' ;
23- import * as crypto from '../../util/crypto.js' ;
2423import map from '../../util/map.js' ;
25- import { sortAlpha } from '../../util/misc.js' ;
2624
2725const invariant = require ( 'invariant' ) ;
2826const semver = require ( 'semver' ) ;
@@ -41,13 +39,6 @@ export type InstallCwdRequest = {
4139 manifest : Object ,
4240} ;
4341
44- export type IntegrityMatch = {
45- actual : string ,
46- expected : string ,
47- loc : string ,
48- matches : boolean ,
49- } ;
50-
5142type Flags = {
5243 // install
5344 har : boolean ,
@@ -179,6 +170,7 @@ export class Install {
179170
180171 this . resolver = new PackageResolver ( config , lockfile ) ;
181172 this . fetcher = new PackageFetcher ( config , this . resolver ) ;
173+ this . integrityChecker = new InstallationIntegrityChecker ( config ) ;
182174 this . compatibility = new PackageCompatibility ( config , this . resolver , this . flags . ignoreEngines ) ;
183175 this . linker = new PackageLinker ( config , this . resolver ) ;
184176 this . scripts = new PackageInstallScripts ( config , this . resolver , this . flags . force ) ;
@@ -197,6 +189,7 @@ export class Install {
197189 compatibility: PackageCompatibility ;
198190 fetcher: PackageFetcher ;
199191 rootPatternsToOrigin: { [ pattern : string ] : string } ;
192+ integrityChecker: InstallationIntegrityChecker ;
200193
201194 /**
202195 * Create a list of dependency requests from the current directories manifests.
@@ -301,22 +294,23 @@ export class Install {
301294 async bailout (
302295 patterns: Array < string > ,
303296 ) : Promise < boolean > {
304- if ( this . flags . frozenLockfile && ! this . lockFileInSync ( patterns ) ) {
305- throw new MessageError ( this . reporter . lang ( 'frozenLockfileError' ) ) ;
306- }
307297 if ( this . flags . skipIntegrityCheck || this . flags . force ) {
308298 return false ;
309299 }
300+ const lockSource = lockStringify ( this . lockfile . getLockfile ( this . resolver . patterns ) ) ;
301+ const match = await this . integrityChecker . check ( patterns , this . lockfile , lockSource , this . flags ) ;
302+ if ( this . flags . frozenLockfile && match . missingPatterns . length > 0 ) {
303+ throw new MessageError ( this . reporter . lang ( 'frozenLockfileError' ) ) ;
304+ }
310305
311- const match = await this . matchesIntegrityHash ( patterns ) ;
312306 const haveLockfile = await fs . exists ( path . join ( this . config . cwd , constants . LOCKFILE_FILENAME ) ) ;
313307
314- if ( match . matches && haveLockfile ) {
308+ if ( match . integrityHashMatches && haveLockfile ) {
315309 this . reporter . success ( this . reporter . lang ( 'upToDate' ) ) ;
316310 return true ;
317311 }
318312
319- if ( ! patterns . length && ! match . expected ) {
313+ if ( ! patterns . length && ! match . integrityFileMissing ) {
320314 this . reporter . success ( this . reporter . lang ( 'nothingToInstall' ) ) ;
321315 await this . createEmptyManifestFolders ( ) ;
322316 await this . saveLockfileAndIntegrity ( patterns ) ;
@@ -398,8 +392,7 @@ export class Install {
398392
399393 steps . push ( async ( curr : number , total : number ) => {
400394 // remove integrity hash to make this operation atomic
401- const loc = await this . getIntegrityHashLocation ( ) ;
402- await fs . unlink ( loc ) ;
395+ await this . integrityChecker . removeIntegrityFile ( ) ;
403396 this . reporter . step ( curr , total , this . reporter . lang ( 'linkingDependencies' ) , emoji . get ( 'link' ) ) ;
404397 await this . linker . init ( patterns , this . flags . linkDuplicates ) ;
405398 } ) ;
@@ -556,41 +549,26 @@ export class Install {
556549 return flattenedPatterns ;
557550 }
558551
559- /**
560- * Check if the loaded lockfile has all the included patterns
561- */
562-
563- lockFileInSync ( patterns : Array < string > ) : boolean {
564- let inSync = true ;
565- for ( const pattern of patterns ) {
566- if ( ! this . lockfile . getLocked ( pattern ) ) {
567- inSync = false ;
568- break ;
569- }
570- }
571- return inSync ;
572- }
573-
574552 /**
575553 * Save updated integrity and lockfiles.
576554 */
577555
578556 async saveLockfileAndIntegrity ( patterns : Array < string > ) : Promise < void > {
579- // stringify current lockfile
580- const lockSource = lockStringify ( this . lockfile . getLockfile ( this . resolver . patterns ) ) ;
581-
582- // write integrity hash
583- await this . writeIntegrityHash ( lockSource , patterns ) ;
584-
585557 // --no-lockfile or --pure-lockfile flag
586558 if ( this . flags . lockfile === false || this . flags . pureLockfile ) {
587559 return ;
588560 }
589561
590- const inSync = this . lockFileInSync ( patterns ) ;
562+ // stringify current lockfile
563+ const lockSource = lockStringify ( this . lockfile . getLockfile ( this . resolver . patterns ) ) ;
591564
592- // remove is followed by install with force on which we rewrite lockfile
593- if ( inSync && patterns . length && ! this . flags . force ) {
565+ // write integrity hash
566+ await this . integrityChecker . save ( patterns , lockSource , this . flags , this . resolver . usedRegistries ) ;
567+
568+ const lockFileHasAllPatterns = patterns . filter ( ( p ) => ! this . lockfile . getLocked ( p ) ) . length === 0 ;
569+
570+ // remove command is followed by install with force, lockfile will be rewritten in any case then
571+ if ( lockFileHasAllPatterns && patterns . length && ! this . flags . force ) {
594572 return;
595573 }
596574
@@ -607,111 +585,6 @@ export class Install {
607585 this . reporter . success ( this . reporter . lang ( 'savedLockfile' ) ) ;
608586 }
609587
610- /**
611- * Check if the integrity hash of this installation matches one on disk.
612- */
613-
614- async matchesIntegrityHash ( patterns : Array < string > ) : Promise < IntegrityMatch > {
615- const loc = await this . getIntegrityHashLocation ( ) ;
616- if ( ! await fs . exists ( loc ) ) {
617- return {
618- actual : '' ,
619- expected : '' ,
620- loc,
621- matches : false ,
622- } ;
623- }
624-
625- const lockSource = lockStringify ( this . lockfile . getLockfile ( this . resolver . patterns ) ) ;
626- const actual = this . generateIntegrityHash ( lockSource , patterns ) ;
627- const expected = ( await fs . readFile ( loc ) ) . trim ( ) ;
628-
629- return {
630- actual ,
631- expected ,
632- loc ,
633- matches : actual === expected ,
634- } ;
635- }
636-
637- /**
638- * Get the location of an existing integrity hash. If none exists then return the location where we should
639- * write a new one.
640- */
641-
642- async getIntegrityHashLocation ( ) : Promise < string > {
643- // build up possible folders
644- const possibleFolders = [ ] ;
645- if ( this . config . modulesFolder ) {
646- possibleFolders . push ( this . config . modulesFolder ) ;
647- }
648-
649- // get a list of registry names to check existence in
650- let checkRegistryNames = this . resolver . usedRegistries ;
651- if ( ! checkRegistryNames . length ) {
652- // we haven't used any registries yet
653- checkRegistryNames = registryNames ;
654- }
655-
656- // ensure we only write to a registry folder that was used
657- for ( const name of checkRegistryNames ) {
658- const loc = path . join ( this . config . cwd , this . config . registries [ name ] . folder ) ;
659- possibleFolders . push ( loc ) ;
660- }
661-
662- // if we already have an integrity hash in one of these folders then use it's location otherwise use the
663- // first folder
664- const possibles = possibleFolders . map ( ( folder ) : string => path . join ( folder , constants . INTEGRITY_FILENAME ) ) ;
665- let loc = possibles [ 0 ] ;
666- for ( const possibleLoc of possibles ) {
667- if ( await fs . exists ( possibleLoc ) ) {
668- loc = possibleLoc ;
669- break ;
670- }
671- }
672- return loc ;
673- }
674- /**
675- * Write the integrity hash of the current install to disk.
676- */
677-
678- async writeIntegrityHash ( lockSource : string , patterns : Array < string > ) : Promise < void > {
679- const loc = await this . getIntegrityHashLocation ( ) ;
680- invariant ( loc , 'expected integrity hash location' ) ;
681- await fs . mkdirp ( path . dirname ( loc ) ) ;
682- await fs . writeFile ( loc , this . generateIntegrityHash ( lockSource , patterns ) ) ;
683- }
684-
685- /**
686- * Generate integrity hash of input lockfile.
687- */
688-
689- generateIntegrityHash ( lockfile : string , patterns : Array < string > ) : string {
690- const opts = [ lockfile ] ;
691-
692- opts . push ( `patterns:${ patterns . sort ( sortAlpha ) . join ( ',' ) } ` ) ;
693-
694- if ( this . flags . flat ) {
695- opts . push ( 'flat' ) ;
696- }
697-
698- if ( this . config . production ) {
699- opts . push ( 'production' ) ;
700- }
701-
702- const linkedModules = this . config . linkedModules ;
703- if ( linkedModules . length ) {
704- opts . push ( `linked:${ linkedModules . join ( ',' ) } ` ) ;
705- }
706-
707- const mirror = this . config . getOfflineMirrorPath ( ) ;
708- if ( mirror != null ) {
709- opts . push ( `mirror:${ mirror } ` ) ;
710- }
711-
712- return crypto . hash ( opts . join ( '-' ) , 'sha256' ) ;
713- }
714-
715588 /**
716589 * Load the dependency graph of the current install. Only does package resolving and wont write to the cwd.
717590 */
0 commit comments