@@ -44,6 +44,18 @@ class ActionConfig {
4444 */
4545 defaultBranch = 'master'
4646
47+ /**
48+ * Maximum number of latest major versions to include (default to unlimited)
49+ * @type {number|undefined }
50+ */
51+ latestMajorVersions = undefined
52+
53+ /**
54+ * Maximum number of latest version tags per major version (default to unlimited)
55+ * @type {number|undefined }
56+ */
57+ latestVersionsPerMajor = undefined
58+
4759 /**
4860 * Tag versions
4961 * @type {{[ref: string]: TagVersion} }
@@ -73,9 +85,11 @@ exports.TagVersion = TagVersion
7385 * @param {string[] } patternStrings
7486 * @param {string } defaultBranch
7587 * @param {string[]|undefined } ignoreTags
88+ * @param {number|undefined } latestMajorVersions
89+ * @param {number|undefined } latestVersionsPerMajor
7690 * @returns {Promise }
7791 */
78- async function add ( owner , repo , patternStrings , defaultBranch , ignoreTags ) {
92+ async function add ( owner , repo , patternStrings , defaultBranch , ignoreTags , latestMajorVersions , latestVersionsPerMajor ) {
7993 assert . ok ( owner , "Arg 'owner' must not be empty" )
8094 assert . ok ( repo , "Arg 'repo' must not be empty" )
8195 assert . ok ( patternStrings , "Arg 'patternStrings' must not be null" )
@@ -94,6 +108,12 @@ async function add(owner, repo, patternStrings, defaultBranch, ignoreTags) {
94108 if ( ignoreTags && ignoreTags . length > 0 ) {
95109 config . ignoreTags = ignoreTags
96110 }
111+ if ( latestMajorVersions && latestMajorVersions > 0 ) {
112+ config . latestMajorVersions = latestMajorVersions
113+ }
114+ if ( latestVersionsPerMajor && latestVersionsPerMajor > 0 ) {
115+ config . latestVersionsPerMajor = latestVersionsPerMajor
116+ }
97117 config . defaultBranch = defaultBranch
98118
99119 const tempDir = path . join ( paths . temp , `${ owner } _${ repo } ` )
@@ -130,6 +150,9 @@ async function add(owner, repo, patternStrings, defaultBranch, ignoreTags) {
130150 config . tags [ tag ] = tagVersion
131151 }
132152
153+ // Prune old tags based on version limits
154+ pruneOldTags ( config )
155+
133156 // Write config
134157 await exec . exec ( 'mkdir' , [ '-p' , path . dirname ( file ) ] )
135158 await fs . promises . writeFile ( file , JSON . stringify ( config , null , ' ' ) )
@@ -141,6 +164,75 @@ async function add(owner, repo, patternStrings, defaultBranch, ignoreTags) {
141164}
142165exports . add = add
143166
167+ /**
168+ * Prunes old tags from the config based on latestMajorVersions and latestVersionsPerMajor.
169+ * Modifies config.tags in place.
170+ * @param {ActionConfig } config
171+ */
172+ function pruneOldTags ( config ) {
173+ const maxMajors = config . latestMajorVersions || 0
174+ const maxPerMajor = config . latestVersionsPerMajor || 0
175+
176+ if ( ! maxMajors && ! maxPerMajor ) {
177+ return
178+ }
179+
180+ const tagNames = Object . keys ( config . tags )
181+ const versionTags = [ ]
182+ const keepTags = new Set ( )
183+
184+ for ( const tag of tagNames ) {
185+ const match = tag . match ( / ^ v ( \d + ) (?: \. ( \d + ) ) ? (?: \. ( \d + ) ) ? $ / )
186+ if ( ! match ) {
187+ // Always keep non-version tags
188+ keepTags . add ( tag )
189+ continue
190+ }
191+
192+ const major = parseInt ( match [ 1 ] , 10 )
193+ const minor = match [ 2 ] !== undefined ? parseInt ( match [ 2 ] , 10 ) : - 1
194+ const patch = match [ 3 ] !== undefined ? parseInt ( match [ 3 ] , 10 ) : - 1
195+ versionTags . push ( { tag, major, minor, patch, isMajorOnly : minor === - 1 } )
196+ }
197+
198+ // Distinct major versions sorted descending (newest first)
199+ const majorVersions = [ ...new Set ( versionTags . map ( v => v . major ) ) ] . sort ( ( a , b ) => b - a )
200+ const allowedMajors = new Set (
201+ maxMajors > 0 ? majorVersions . slice ( 0 , maxMajors ) : majorVersions
202+ )
203+
204+ for ( const major of allowedMajors ) {
205+ const tagsForMajor = versionTags . filter ( v => v . major === major )
206+
207+ // Always keep major-only pointers (e.g. "v4")
208+ for ( const v of tagsForMajor . filter ( v => v . isMajorOnly ) ) {
209+ keepTags . add ( v . tag )
210+ }
211+
212+ // Sort non-major-only tags by version descending (latest first)
213+ const sorted = tagsForMajor
214+ . filter ( v => ! v . isMajorOnly )
215+ . sort ( ( a , b ) => {
216+ if ( a . minor !== b . minor ) return b . minor - a . minor
217+ return b . patch - a . patch
218+ } )
219+
220+ const kept = maxPerMajor > 0 ? sorted . slice ( 0 , maxPerMajor ) : sorted
221+ for ( const v of kept ) {
222+ keepTags . add ( v . tag )
223+ }
224+ }
225+
226+ // Remove pruned tags
227+ for ( const tag of tagNames ) {
228+ if ( ! keepTags . has ( tag ) ) {
229+ console . log ( `Pruning tag '${ tag } ' from config (version limit)` )
230+ delete config . tags [ tag ]
231+ }
232+ }
233+ }
234+ exports . pruneOldTags = pruneOldTags
235+
144236/**
145237 * Returns the action config file path
146238 * @param {string } owner
0 commit comments