@@ -109,6 +109,50 @@ function addUpperBound(oldRange, breakingMin) {
109109 return `>=${ oldMin } <${ breakingMin } ` ;
110110}
111111
112+ /**
113+ * Normalize a semver range to explicit bounded format
114+ * Converts ^ and ~ ranges to >=min <max format for consistency
115+ * @param {string } range - Semver range like "^2.0.0" or "~1.5.0"
116+ * @returns {string } - Normalized range like ">=2.0.0 <3.0.0"
117+ */
118+ function normalizeRange ( range ) {
119+ if ( ! range ) return range ;
120+
121+ // Already in explicit format
122+ if ( range . startsWith ( '>=' ) ) return range ;
123+
124+ const min = semver . minVersion ( range ) ;
125+ if ( ! min ) return range ;
126+
127+ // For ^ (caret): allows changes that do not modify the left-most non-zero digit
128+ // ^1.2.3 := >=1.2.3 <2.0.0
129+ // ^0.2.3 := >=0.2.3 <0.3.0
130+ // ^0.0.3 := >=0.0.3 <0.0.4
131+ if ( range . startsWith ( '^' ) ) {
132+ const { major, minor, patch } = min ;
133+ let maxVersion ;
134+ if ( major !== 0 ) {
135+ maxVersion = `${ major + 1 } .0.0` ;
136+ } else if ( minor !== 0 ) {
137+ maxVersion = `0.${ minor + 1 } .0` ;
138+ } else {
139+ maxVersion = `0.0.${ patch + 1 } ` ;
140+ }
141+ return `>=${ min . version } <${ maxVersion } ` ;
142+ }
143+
144+ // For ~ (tilde): allows patch-level changes
145+ // ~1.2.3 := >=1.2.3 <1.3.0
146+ // ~0.2.3 := >=0.2.3 <0.3.0
147+ if ( range . startsWith ( '~' ) ) {
148+ const { major, minor } = min ;
149+ return `>=${ min . version } <${ major } .${ minor + 1 } .0` ;
150+ }
151+
152+ // Fallback: just use >=min
153+ return `>=${ min . version } ` ;
154+ }
155+
112156// ============================================================================
113157// File Helpers
114158// ============================================================================
@@ -141,17 +185,19 @@ function writeJson(filePath, data) {
141185// ============================================================================
142186
143187/**
144- * Extract peer deps from package.json, excluding common ones
188+ * Extract peer deps from package.json, excluding common ones and normalizing ranges
145189 */
146190function extractPeerDeps ( packageJson , excludeCommon = true ) {
147191 if ( ! packageJson ?. peerDependencies ) return { } ;
148192
149- const peerDeps = { ... packageJson . peerDependencies } ;
193+ const peerDeps = { } ;
150194
151- if ( excludeCommon ) {
152- for ( const dep of CONFIG . commonPeerDeps ) {
153- delete peerDeps [ dep ] ;
195+ for ( const [ dep , range ] of Object . entries ( packageJson . peerDependencies ) ) {
196+ if ( excludeCommon && CONFIG . commonPeerDeps . includes ( dep ) ) {
197+ continue ;
154198 }
199+ // Normalize all ranges to explicit bounded format
200+ peerDeps [ dep ] = normalizeRange ( range ) ;
155201 }
156202
157203 return peerDeps ;
@@ -336,6 +382,11 @@ function main() {
336382 throw new Error ( 'Core package (angular-three) not found in dist. Run build first.' ) ;
337383 }
338384
385+ // Deep clone existing data BEFORE processing (since processing mutates in place)
386+ const existingDataSnapshot = existingMatrix
387+ ? JSON . stringify ( { combined : existingMatrix . combined , packages : existingMatrix . packages } )
388+ : null ;
389+
339390 // Process matrices
340391 console . log ( '\nProcessing combined matrix:' ) ;
341392 const combined = processCombinedMatrix ( existingMatrix , corePackage ) ;
@@ -350,11 +401,7 @@ function main() {
350401 } ;
351402
352403 // Check if there are actual changes (compare without metadata fields)
353- const existingData = existingMatrix
354- ? { combined : existingMatrix . combined , packages : existingMatrix . packages }
355- : null ;
356-
357- const hasChanges = JSON . stringify ( outputData ) !== JSON . stringify ( existingData ) ;
404+ const hasChanges = JSON . stringify ( outputData ) !== existingDataSnapshot ;
358405
359406 // Build final output with metadata
360407 const output = {
0 commit comments