@@ -42,7 +42,10 @@ const path = require('path')
4242const colors = require ( 'chalk' )
4343const Messenger = require ( '@codedungeon/messenger' )
4444
45+ // Import shared release management utilities
4546const { program } = require ( 'commander' )
47+ const releaseManagement = require ( '../src/commands/support/release-management' )
48+
4649const { getFolderFromCommandLine, runShellCommand, getPluginFileContents, fileExists, getCopyTargetPath } = require ( './shared' )
4750
4851// Command line options
@@ -141,137 +144,8 @@ function getRelativeTime(publishedAt) {
141144 }
142145}
143146
144- /**
145- * Check if a version is a pre-release version (alpha, beta, rc, etc.)
146- * @param {string } version - Version string
147- * @returns {boolean } - True if pre-release
148- */
149- function isPreRelease ( version ) {
150- return / - ( a l p h a | b e t a | r c | p r e | d e v | s n a p s h o t ) / i. test ( version )
151- }
152-
153- /**
154- * Get the base version from a pre-release version (removes pre-release identifier)
155- * @param {string } version - Version string (e.g., "2.1.0-alpha.1")
156- * @returns {string } - Base version (e.g., "2.1.0")
157- */
158- function getBaseVersion ( version ) {
159- return version . replace ( / - .* $ / , '' )
160- }
161-
162- /**
163- * Check if a pre-release version has an obsolete stable counterpart
164- * @param {string } preReleaseVersion - The pre-release version to check
165- * @param {Array<{name: string, tag: string, version: string, publishedAt: string}> } allReleases - All releases
166- * @param {number } monthsThreshold - Number of months after which stable version makes pre-release obsolete
167- * @returns {boolean } - True if the pre-release is obsolete
168- */
169- function isPreReleaseObsolete ( preReleaseVersion , allReleases , monthsThreshold = 3 ) {
170- const baseVersion = getBaseVersion ( preReleaseVersion )
171- const now = new Date ( )
172- const thresholdDate = new Date ( now . getTime ( ) - monthsThreshold * 30 * 24 * 60 * 60 * 1000 )
173-
174- // Find the stable version of the same base version
175- const stableVersion = allReleases . find ( ( release ) => ! isPreRelease ( release . version ) && getBaseVersion ( release . version ) === baseVersion )
176-
177- if ( ! stableVersion ) {
178- return false // No stable version found, keep the pre-release
179- }
180-
181- // Check if the stable version was published more than the threshold ago
182- const stablePublishedDate = new Date ( stableVersion . publishedAt )
183- return stablePublishedDate < thresholdDate
184- }
185-
186- /**
187- * Compare two version strings for sorting (semantic versioning)
188- * @param {string } a - First version
189- * @param {string } b - Second version
190- * @returns {number } - Comparison result
191- */
192- function compareVersions ( a , b ) {
193- // Remove pre-release identifiers for comparison
194- const cleanA = a . replace ( / - .* $ / , '' )
195- const cleanB = b . replace ( / - .* $ / , '' )
196-
197- const partsA = cleanA . split ( '.' ) . map ( Number )
198- const partsB = cleanB . split ( '.' ) . map ( Number )
199-
200- const maxLength = Math . max ( partsA . length , partsB . length )
201-
202- for ( let i = 0 ; i < maxLength ; i ++ ) {
203- const partA = partsA [ i ] || 0
204- const partB = partsB [ i ] || 0
205-
206- if ( partA !== partB ) {
207- return partB - partA // Descending order (newest first)
208- }
209- }
210-
211- // If versions are equal, put pre-release after stable
212- const aIsPre = isPreRelease ( a )
213- const bIsPre = isPreRelease ( b )
214-
215- if ( aIsPre && ! bIsPre ) return 1
216- if ( ! aIsPre && bIsPre ) return - 1
217-
218- return 0
219- }
220-
221- /**
222- * Identify releases that should be pruned based on heuristics
223- * @param {Array<{name: string, tag: string, version: string, publishedAt: string}> } releases - Array of releases
224- * @returns {Array<{name: string, tag: string, version: string, publishedAt: string}> } - Releases to prune
225- */
226- function identifyReleasesToPrune ( releases ) {
227- if ( releases . length <= 3 ) {
228- return [ ] // Keep at least 3 releases minimum
229- }
230-
231- const now = new Date ( )
232- const sixMonthsAgo = new Date ( now . getTime ( ) - 6 * 30 * 24 * 60 * 60 * 1000 )
233- const twoYearsAgo = new Date ( now . getTime ( ) - 2 * 365 * 24 * 60 * 60 * 1000 )
234-
235- // Sort releases by version (newest first)
236- const sortedReleases = [ ...releases ] . sort ( ( a , b ) => compareVersions ( a . version , b . version ) )
237-
238- const toPrune = [ ]
239- const stableReleases = sortedReleases . filter ( ( r ) => ! isPreRelease ( r . version ) )
240- const preReleaseReleases = sortedReleases . filter ( ( r ) => isPreRelease ( r . version ) )
241-
242- // Keep the latest stable release
243- const latestStable = stableReleases [ 0 ]
244-
245- // Keep the latest 2-3 pre-release versions if they're recent
246- const recentPreReleases = preReleaseReleases . filter ( ( r ) => new Date ( r . publishedAt ) >= sixMonthsAgo ) . slice ( 0 , 3 )
247-
248- // Identify releases to prune
249- for ( const release of releases ) {
250- const isRecent = new Date ( release . publishedAt ) >= sixMonthsAgo
251- const isOld = new Date ( release . publishedAt ) < twoYearsAgo
252- const isLatestStable = release === latestStable
253- const isRecentPreRelease = recentPreReleases . includes ( release )
254- const isObsoletePreRelease = isPreRelease ( release . version ) && isPreReleaseObsolete ( release . version , releases )
255-
256- // Prune if:
257- // 1. It's old (2+ years) AND not the latest stable
258- // 2. It's a pre-release that's obsolete (stable version published 3+ months ago)
259- // 3. It's a pre-release that's not recent and we have more than 5 pre-releases
260- // 4. It's not recent and not the latest stable and we have more than 5 total releases
261-
262- if ( isOld && ! isLatestStable ) {
263- toPrune . push ( release )
264- } else if ( isObsoletePreRelease ) {
265- toPrune . push ( release )
266- } else if ( isPreRelease ( release . version ) && ! isRecentPreRelease && preReleaseReleases . length > 5 ) {
267- toPrune . push ( release )
268- } else if ( ! isRecent && ! isLatestStable && releases . length > 5 ) {
269- toPrune . push ( release )
270- }
271- }
272-
273- return toPrune
274- }
147+ // Use shared utility functions
148+ const { isPreRelease, getBaseVersion, isPreReleaseObsolete, compareVersions, identifyReleasesToPrune, generatePruneCommands } = releaseManagement
275149
276150/**
277151 * Identify releases to prune for duplicate version scenario (more lenient)
@@ -309,25 +183,6 @@ function identifyReleasesToPruneForDuplicate(releases) {
309183 return toPrune
310184}
311185
312- /**
313- * Generate prune commands for identified releases
314- * @param {Array<{name: string, tag: string, version: string, publishedAt: string}> } releasesToPrune - Releases to prune
315- * @returns {string } - Commands to run for pruning
316- */
317- function generatePruneCommands ( releasesToPrune ) {
318- if ( releasesToPrune . length === 0 ) {
319- return 'No releases recommended for pruning.'
320- }
321-
322- const commands = releasesToPrune . map ( ( release ) => `gh release delete "${ release . tag } " -y` )
323-
324- if ( releasesToPrune . length === 1 ) {
325- return `Recommended prune command:\n${ commands [ 0 ] } `
326- }
327-
328- return `Recommended prune commands:\n${ commands . join ( '\n' ) } \n\nTo prune all at once:\n${ commands . join ( ' && ' ) } `
329- }
330-
331186/**
332187 * Get all existing releases for a specific plugin
333188 * @param {string } pluginName - The plugin name to search for
0 commit comments