@@ -150,6 +150,31 @@ const resolveGitCommitHash = Effect.fn("resolveGitCommitHash")(function* (repoRo
150150 return hash . toLowerCase ( ) ;
151151} ) ;
152152
153+ const resolveGitHeadTags = Effect . fn ( "resolveGitHeadTags" ) ( function * ( repoRoot : string ) {
154+ const result = yield * spawnAndCollectOutput (
155+ ChildProcess . make ( "git" , [ "tag" , "--points-at" , "HEAD" ] , {
156+ cwd : repoRoot ,
157+ } ) ,
158+ ) . pipe (
159+ Effect . catch ( ( ) =>
160+ Effect . succeed ( {
161+ stdout : "" ,
162+ stderr : "" ,
163+ exitCode : 1 ,
164+ } ) ,
165+ ) ,
166+ ) ;
167+
168+ if ( result . exitCode !== 0 ) {
169+ return [ ] ;
170+ }
171+
172+ return result . stdout
173+ . split ( / \r ? \n / )
174+ . map ( ( tag ) => tag . trim ( ) )
175+ . filter ( ( tag ) => tag . length > 0 ) ;
176+ } ) ;
177+
153178const resolvePythonForNodeGyp = Effect . fn ( "resolvePythonForNodeGyp" ) ( function * ( ) {
154179 const fs = yield * FileSystem . FileSystem ;
155180 const path = yield * Path . Path ;
@@ -532,6 +557,82 @@ export function resolveDesktopUpdateChannel(version: string): "latest" | "nightl
532557 return / - nightly \.\d{ 8 } \.\d+ $ / . test ( version ) ? "nightly" : "latest" ;
533558}
534559
560+ const ExactSemverTagPattern =
561+ / ^ v ? ( 0 | [ 1 - 9 ] \d * ) \. ( 0 | [ 1 - 9 ] \d * ) \. ( 0 | [ 1 - 9 ] \d * ) (?: - ( [ 0 - 9 A - Z a - z - ] + (?: \. [ 0 - 9 A - Z a - z - ] + ) * ) ) ? (?: \+ [ 0 - 9 A - Z a - z - ] + (?: \. [ 0 - 9 A - Z a - z - ] + ) * ) ? $ / ;
562+
563+ interface ParsedSemver {
564+ readonly major : number ;
565+ readonly minor : number ;
566+ readonly patch : number ;
567+ readonly prerelease : ReadonlyArray < string > ;
568+ }
569+
570+ function parseExactSemver ( version : string ) : ParsedSemver {
571+ const [ , major = "0" , minor = "0" , patch = "0" , prerelease = "" ] =
572+ ExactSemverTagPattern . exec ( version ) ?? [ ] ;
573+
574+ return {
575+ major : Number ( major ) ,
576+ minor : Number ( minor ) ,
577+ patch : Number ( patch ) ,
578+ prerelease : prerelease ? prerelease . split ( "." ) : [ ] ,
579+ } ;
580+ }
581+
582+ function comparePrereleaseIdentifier ( a : string , b : string ) : number {
583+ const aNumeric = / ^ \d+ $ / . test ( a ) ;
584+ const bNumeric = / ^ \d+ $ / . test ( b ) ;
585+
586+ if ( aNumeric && bNumeric ) {
587+ return Number ( a ) - Number ( b ) ;
588+ }
589+ if ( aNumeric ) return - 1 ;
590+ if ( bNumeric ) return 1 ;
591+
592+ return a . localeCompare ( b , "en" ) ;
593+ }
594+
595+ function compareSemverPrecedence ( a : string , b : string ) : number {
596+ const left = parseExactSemver ( a ) ;
597+ const right = parseExactSemver ( b ) ;
598+
599+ const releaseDifference =
600+ left . major - right . major || left . minor - right . minor || left . patch - right . patch ;
601+ if ( releaseDifference !== 0 ) return releaseDifference ;
602+
603+ if ( left . prerelease . length === 0 && right . prerelease . length > 0 ) return 1 ;
604+ if ( left . prerelease . length > 0 && right . prerelease . length === 0 ) return - 1 ;
605+
606+ for ( const [ index , leftIdentifier ] of left . prerelease . entries ( ) ) {
607+ const rightIdentifier = right . prerelease [ index ] ;
608+ if ( rightIdentifier === undefined ) return 1 ;
609+
610+ const difference = comparePrereleaseIdentifier ( leftIdentifier , rightIdentifier ) ;
611+ if ( difference !== 0 ) return difference ;
612+ }
613+
614+ if ( right . prerelease . length > left . prerelease . length ) return - 1 ;
615+
616+ return a . localeCompare ( b , "en" ) ;
617+ }
618+
619+ export function resolveDesktopArtifactVersion (
620+ explicitVersion : string | undefined ,
621+ headTags : ReadonlyArray < string > ,
622+ fallbackVersion : string ,
623+ ) : string {
624+ if ( explicitVersion ) {
625+ return explicitVersion ;
626+ }
627+
628+ const headSemverVersions = headTags
629+ . filter ( ( tag ) => ExactSemverTagPattern . test ( tag ) )
630+ . map ( ( tag ) => tag . replace ( / ^ v / , "" ) )
631+ . toSorted ( compareSemverPrecedence ) ;
632+
633+ return headSemverVersions . at ( - 1 ) ?? fallbackVersion ;
634+ }
635+
535636export function resolveDesktopBuildIconAssets ( version : string ) : DesktopBuildIconAssets {
536637 if ( resolveDesktopUpdateChannel ( version ) === "nightly" ) {
537638 return {
@@ -710,7 +811,11 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
710811 } ) ,
711812 } ) ;
712813
713- const appVersion = options . version ?? serverPackageJson . version ;
814+ const appVersion = resolveDesktopArtifactVersion (
815+ options . version ,
816+ yield * resolveGitHeadTags ( repoRoot ) ,
817+ serverPackageJson . version ,
818+ ) ;
714819 const iconAssets = resolveDesktopBuildIconAssets ( appVersion ) ;
715820 const commitHash = yield * resolveGitCommitHash ( repoRoot ) ;
716821 const mkdir = options . keepStage ? fs . makeTempDirectory : fs . makeTempDirectoryScoped ;
0 commit comments