@@ -39,19 +39,27 @@ export interface AssetMatcher {
3939}
4040
4141/**
42- * Normalizes a glob pattern to match `tinyglobby` semantics by stripping trailing slashes,
43- * expanding directory patterns with `/**`, and normalizing POSIX segments.
42+ * Normalizes an ignore glob pattern for `picomatch` to match `tinyglobby` directory-exclusion
43+ * semantics by stripping trailing slashes, normalizing POSIX segments, and expanding patterns
44+ * with `/**` (returning both `normalized` and `${normalized}/**` for single-wildcard suffixes
45+ * because `picomatch` does not make trailing `/**` optional after `*`).
4446 *
45- * @param pattern The raw glob pattern string.
46- * @returns The normalized POSIX glob pattern .
47+ * @param pattern The raw ignore glob pattern string.
48+ * @returns The normalized POSIX glob patterns .
4749 */
48- function normalizeGlobPattern ( pattern : string ) : string {
49- let result = pattern . endsWith ( '/' ) ? pattern . slice ( 0 , - 1 ) : pattern ;
50- if ( pattern . endsWith ( '/' ) || ! result . endsWith ( '*' ) ) {
51- result += '/**' ;
50+ function normalizeGlobPattern ( pattern : string ) : string [ ] {
51+ const trimmed = pattern . endsWith ( '/' ) ? pattern . slice ( 0 , - 1 ) : pattern ;
52+ const normalized = path . posix . normalize ( trimmed ) ;
53+
54+ if ( ! normalized . endsWith ( '*' ) ) {
55+ return [ `${ normalized } /**` ] ;
56+ }
57+
58+ if ( normalized . endsWith ( '/**' ) ) {
59+ return [ normalized ] ;
5260 }
5361
54- return path . posix . normalize ( result ) ;
62+ return [ normalized , ` ${ normalized } /**` ] ;
5563}
5664
5765/**
@@ -90,7 +98,7 @@ export function createAssetMatchers(assets: NormalizedAssetsPattern[]): readonly
9098 const { glob : rawGlob , ignore : rawIgnore , input } = asset ;
9199 const ignore = [ ...DEFAULT_ASSET_IGNORE , ...( rawIgnore ?? [ ] ) ]
92100 . filter ( ( pattern ) => pattern . length > 0 )
93- . map ( normalizeGlobPattern ) ;
101+ . flatMap ( normalizeGlobPattern ) ;
94102 const isMatch = picomatch ( rawGlob ? path . posix . normalize ( rawGlob ) : [ ] , {
95103 dot : true ,
96104 windows : false ,
@@ -186,77 +194,48 @@ export async function collectAssetsToEmit(
186194 }
187195
188196 if ( matchers . length > 1 ) {
189- // Ensure lower-priority modified assets do not overwrite existing higher-priority assets on disk .
197+ // Ensure lower-priority modified assets do not overwrite existing higher-priority assets.
190198 for ( const [ outputPath , { file, matcherIndex } ] of filesToEmit ) {
191199 for ( let laterIndex = matchers . length - 1 ; laterIndex > matcherIndex ; laterIndex -- ) {
192- const laterMatcher = matchers [ laterIndex ] ;
193- const candidateFile = getCandidateSourceForOutput ( laterMatcher , outputPath ) ;
194- if ( ! candidateFile || candidateFile === file . source ) {
195- continue ;
196- }
197-
198- if ( isMatchingAssetFile ( candidateFile , laterMatcher . asset . followSymlinks ) ) {
199- if ( emittedAssets . get ( candidateFile ) ?. has ( outputPath ) ) {
200- filesToEmit . delete ( outputPath ) ;
201- } else {
202- filesToEmit . set ( outputPath , {
203- file : createDiskOutputFile ( candidateFile , outputPath ) ,
204- matcherIndex : laterIndex ,
205- } ) ;
206- }
200+ const candidateFile = getCandidateSourceForOutput ( matchers [ laterIndex ] , outputPath ) ;
201+ if (
202+ candidateFile &&
203+ candidateFile !== file . source &&
204+ ! filesToDelete . has ( candidateFile ) &&
205+ emittedAssets . get ( candidateFile ) ?. has ( outputPath )
206+ ) {
207+ filesToEmit . delete ( outputPath ) ;
207208 break ;
208- } else if ( emittedAssets . get ( candidateFile ) ?. has ( outputPath ) ) {
209- filesToDelete . add ( candidateFile ) ;
210209 }
211210 }
212211 }
213212
214213 // Restore lower-priority fallback assets when a higher-priority overriding asset is deleted.
215214 for ( const deletedFile of filesToDelete ) {
216- for (
217- let deletedMatcherIndex = 0 ;
218- deletedMatcherIndex < matchers . length ;
219- deletedMatcherIndex ++
220- ) {
221- const { asset, prefix, isMatch } = matchers [ deletedMatcherIndex ] ;
222- if ( ! deletedFile . startsWith ( prefix ) ) {
223- continue ;
224- }
225-
226- const relative = deletedFile . slice ( prefix . length ) ;
227- if ( ! isMatch ( relative ) ) {
228- continue ;
229- }
230-
231- const outputPath = path . posix . join ( asset . output , relative ) ;
232- if ( filesToEmit . has ( outputPath ) ) {
233- continue ;
234- }
235-
236- for ( let candidateIndex = matchers . length - 1 ; candidateIndex >= 0 ; candidateIndex -- ) {
237- if ( candidateIndex === deletedMatcherIndex ) {
238- continue ;
239- }
215+ const deletedOutputs = emittedAssets . get ( deletedFile ) ;
216+ if ( ! deletedOutputs ) {
217+ continue ;
218+ }
240219
220+ for ( const outputPath of deletedOutputs ) {
221+ const minCandidateIndex = ( filesToEmit . get ( outputPath ) ?. matcherIndex ?? - 1 ) + 1 ;
222+ for (
223+ let candidateIndex = matchers . length - 1 ;
224+ candidateIndex >= minCandidateIndex ;
225+ candidateIndex --
226+ ) {
241227 const candidateMatcher = matchers [ candidateIndex ] ;
242228 const candidateFile = getCandidateSourceForOutput ( candidateMatcher , outputPath ) ;
243229 if ( ! candidateFile || filesToDelete . has ( candidateFile ) ) {
244230 continue ;
245231 }
246232
247233 if ( isMatchingAssetFile ( candidateFile , candidateMatcher . asset . followSymlinks ) ) {
248- if (
249- candidateIndex < deletedMatcherIndex ||
250- ! emittedAssets . get ( candidateFile ) ?. has ( outputPath )
251- ) {
252- filesToEmit . set ( outputPath , {
253- file : createDiskOutputFile ( candidateFile , outputPath ) ,
254- matcherIndex : candidateIndex ,
255- } ) ;
256- }
234+ filesToEmit . set ( outputPath , {
235+ file : createDiskOutputFile ( candidateFile , outputPath ) ,
236+ matcherIndex : candidateIndex ,
237+ } ) ;
257238 break ;
258- } else if ( emittedAssets . get ( candidateFile ) ?. has ( outputPath ) ) {
259- filesToDelete . add ( candidateFile ) ;
260239 }
261240 }
262241 }
0 commit comments