@@ -8,7 +8,9 @@ import type { ISerializedResolveContext } from '@rushstack/webpack-workspace-res
88
99import type { IDependencyEntry , IResolverContext } from './types' ;
1010
11- const MAX_LENGTH_WITHOUT_HASH : number = 120 - 26 - 1 ;
11+ const PNPM8_MAX_LENGTH_WITHOUT_HASH : number = 120 - 26 - 1 ;
12+ // pnpm 10 uses SHA-256 hex (32 chars) + underscore separator
13+ const PNPM10_MAX_LENGTH_WITHOUT_HASH : number = 120 - 32 - 1 ;
1214const BASE32 : string [ ] = 'abcdefghijklmnopqrstuvwxyz234567' . split ( '' ) ;
1315
1416// https://github.com/swansontec/rfc4648.js/blob/ead9c9b4b68e5d4a529f32925da02c02984e772c/src/codec.ts#L82-L118
@@ -42,14 +44,32 @@ export function createBase32Hash(input: string): string {
4244 return out ;
4345}
4446
47+ /**
48+ * Creates a short SHA-256 hex hash, matching pnpm 10's createShortHash.
49+ */
50+ export function createShortSha256Hash ( input : string ) : string {
51+ return createHash ( 'sha256' ) . update ( input ) . digest ( 'hex' ) . substring ( 0 , 32 ) ;
52+ }
53+
4554// https://github.com/pnpm/pnpm/blob/f394cfccda7bc519ceee8c33fc9b68a0f4235532/packages/dependency-path/src/index.ts#L167-L189
46- export function depPathToFilename ( depPath : string ) : string {
55+ export function depPathToFilename ( depPath : string , usePnpm10Hashing ?: boolean ) : string {
4756 let filename : string = depPathToFilenameUnescaped ( depPath ) . replace ( / [ \\ / : * ? " < > | ] / g, '+' ) ;
48- if ( filename . includes ( '(' ) ) {
49- filename = filename . replace ( / ( \) \( ) | \( / g, '_' ) . replace ( / \) $ / , '' ) ;
50- }
51- if ( filename . length > 120 || ( filename !== filename . toLowerCase ( ) && ! filename . startsWith ( 'file+' ) ) ) {
52- return `${ filename . substring ( 0 , MAX_LENGTH_WITHOUT_HASH ) } _${ createBase32Hash ( filename ) } ` ;
57+ if ( usePnpm10Hashing ) {
58+ // pnpm 10 also replaces `#` and handles parentheses differently
59+ filename = filename . replace ( / # / g, '+' ) ;
60+ if ( filename . includes ( '(' ) ) {
61+ filename = filename . replace ( / \) $ / , '' ) . replace ( / \) \( | \( | \) / g, '_' ) ;
62+ }
63+ if ( filename . length > 120 || ( filename !== filename . toLowerCase ( ) && ! filename . startsWith ( 'file+' ) ) ) {
64+ return `${ filename . substring ( 0 , PNPM10_MAX_LENGTH_WITHOUT_HASH ) } _${ createShortSha256Hash ( filename ) } ` ;
65+ }
66+ } else {
67+ if ( filename . includes ( '(' ) ) {
68+ filename = filename . replace ( / ( \) \( ) | \( / g, '_' ) . replace ( / \) $ / , '' ) ;
69+ }
70+ if ( filename . length > 120 || ( filename !== filename . toLowerCase ( ) && ! filename . startsWith ( 'file+' ) ) ) {
71+ return `${ filename . substring ( 0 , PNPM8_MAX_LENGTH_WITHOUT_HASH ) } _${ createBase32Hash ( filename ) } ` ;
72+ }
5373 }
5474 return filename ;
5575}
@@ -66,7 +86,8 @@ export function resolveDependencyKey(
6686 lockfileFolder : string ,
6787 key : string ,
6888 specifier : string ,
69- context : IResolverContext
89+ context : IResolverContext ,
90+ isV9Lockfile ?: boolean
7091) : string {
7192 if ( specifier . startsWith ( '/' ) ) {
7293 return getDescriptionFileRootFromKey ( lockfileFolder , specifier ) ;
@@ -79,7 +100,16 @@ export function resolveDependencyKey(
79100 } else if ( specifier . startsWith ( 'file:' ) ) {
80101 return getDescriptionFileRootFromKey ( lockfileFolder , specifier , key ) ;
81102 } else {
82- return getDescriptionFileRootFromKey ( lockfileFolder , `/${ key } @${ specifier } ` ) ;
103+ // In v9 lockfiles, aliased dependency values use the full package key format
104+ // (e.g., 'string-width@4.2.3' or '@types/events@3.0.0') instead of bare versions.
105+ // A bare version starts with a digit; a full key starts with a letter or @.
106+ if ( / ^ [ a - z A - Z @ ] / . test ( specifier ) ) {
107+ return getDescriptionFileRootFromKey ( lockfileFolder , specifier ) ;
108+ }
109+ // Construct the full dependency key from package name and version specifier.
110+ // v6 keys use '/' prefix; v9 keys don't.
111+ const fullKey : string = isV9Lockfile ? `${ key } @${ specifier } ` : `/${ key } @${ specifier } ` ;
112+ return getDescriptionFileRootFromKey ( lockfileFolder , fullKey ) ;
83113 }
84114}
85115
@@ -91,26 +121,40 @@ export function resolveDependencyKey(
91121 * @returns The physical path to the dependency
92122 */
93123export function getDescriptionFileRootFromKey ( lockfileFolder : string , key : string , name ?: string ) : string {
124+ // Detect lockfile version: v6 keys start with '/', v9 keys don't
125+ const isV9Key : boolean = ! key . startsWith ( '/' ) && ! key . startsWith ( 'file:' ) ;
126+
94127 if ( ! key . startsWith ( 'file:' ) ) {
95- name = key . slice ( 1 , key . indexOf ( '@' , 2 ) ) ;
128+ if ( key . startsWith ( '/' ) ) {
129+ // v6 format: /name@version or /@scope/name@version
130+ name = key . slice ( 1 , key . indexOf ( '@' , 2 ) ) ;
131+ } else if ( ! name ) {
132+ // v9 format: name@version or @scope/name@version
133+ const searchStart : number = key . startsWith ( '@' ) ? key . indexOf ( '/' ) + 1 : 0 ;
134+ const versionIndex : number = key . indexOf ( '@' , searchStart ) ;
135+ if ( versionIndex !== - 1 ) {
136+ name = key . slice ( 0 , versionIndex ) ;
137+ }
138+ }
96139 }
97140 if ( ! name ) {
98141 throw new Error ( `Missing package name for ${ key } ` ) ;
99142 }
100143
101- const originFolder : string = `${ lockfileFolder } /node_modules/.pnpm/${ depPathToFilename ( key ) } /node_modules` ;
144+ const originFolder : string = `${ lockfileFolder } /node_modules/.pnpm/${ depPathToFilename ( key , isV9Key ) } /node_modules` ;
102145 const descriptionFileRoot : string = `${ originFolder } /${ name } ` ;
103146 return descriptionFileRoot ;
104147}
105148
106149export function resolveDependencies (
107150 lockfileFolder : string ,
108151 collection : Record < string , IDependencyEntry > ,
109- context : IResolverContext
152+ context : IResolverContext ,
153+ isV9Lockfile ?: boolean
110154) : void {
111155 for ( const [ key , value ] of Object . entries ( collection ) ) {
112156 const version : string = typeof value === 'string' ? value : value . version ;
113- const resolved : string = resolveDependencyKey ( lockfileFolder , key , version , context ) ;
157+ const resolved : string = resolveDependencyKey ( lockfileFolder , key , version , context , isV9Lockfile ) ;
114158
115159 context . deps . set ( key , resolved ) ;
116160 }
0 commit comments