@@ -2967,3 +2967,147 @@ describe("Python PEP 440 magic version", () => {
29672967 expect ( cleaned ) . not . toContain ( "pyproject.toml" ) ;
29682968 } ) ;
29692969} ) ;
2970+
2971+ describe ( "getLatestVersionFromGitTags" , ( ) => {
2972+ // Helper to create a temporary bare git repo that can serve as a remote,
2973+ // and a clone of it, so `git ls-remote --tags origin` works.
2974+ async function createRepoWithTags ( tags : string [ ] ) : Promise < { cloneDir : string ; bareDir : string } > {
2975+ const bareDir = await fs . mkdtemp ( path . join ( require ( "os" ) . tmpdir ( ) , "test-bare-" ) ) ;
2976+ const cloneDir = await fs . mkdtemp ( path . join ( require ( "os" ) . tmpdir ( ) , "test-clone-" ) ) ;
2977+
2978+ // Create bare repo with explicit main branch
2979+ await runCommand ( [ "git" , "init" , "--bare" , "--initial-branch=main" , bareDir ] , bareDir ) ;
2980+
2981+ // Clone it
2982+ await runCommand ( [ "git" , "clone" , bareDir , cloneDir ] , cloneDir ) ;
2983+
2984+ // Configure git user for commits
2985+ await runCommand ( [ "git" , "config" , "user.email" , "test@test.com" ] , cloneDir ) ;
2986+ await runCommand ( [ "git" , "config" , "user.name" , "Test" ] , cloneDir ) ;
2987+
2988+ // Create an initial commit so we have something to tag
2989+ const filePath = path . join ( cloneDir , "README.md" ) ;
2990+ await fs . writeFile ( filePath , "# Test\n" ) ;
2991+ await runCommand ( [ "git" , "add" , "." ] , cloneDir ) ;
2992+ await runCommand ( [ "git" , "commit" , "-m" , "Initial commit" ] , cloneDir ) ;
2993+ await runCommand ( [ "git" , "push" , "-u" , "origin" , "main" ] , cloneDir ) ;
2994+
2995+ // Create tags
2996+ for ( const tag of tags ) {
2997+ await runCommand ( [ "git" , "tag" , tag ] , cloneDir ) ;
2998+ }
2999+ // Push all tags
3000+ if ( tags . length > 0 ) {
3001+ await runCommand ( [ "git" , "push" , "origin" , "--tags" ] , cloneDir ) ;
3002+ }
3003+
3004+ return { cloneDir, bareDir } ;
3005+ }
3006+
3007+ async function cleanupDirs ( ...dirs : string [ ] ) : Promise < void > {
3008+ for ( const dir of dirs ) {
3009+ await fs . rm ( dir , { recursive : true , force : true } ) ;
3010+ }
3011+ }
3012+
3013+ it ( "returns latest semver tag from repo with multiple versions" , async ( ) => {
3014+ const { cloneDir, bareDir } = await createRepoWithTags ( [ "v1.0.0" , "v1.1.0" , "v2.0.0" , "v1.5.3" ] ) ;
3015+ try {
3016+ const service = new AutoVersioningService ( { logger : mockLogger } ) ;
3017+ const result = await service . getLatestVersionFromGitTags ( cloneDir ) ;
3018+ expect ( result ) . toBe ( "v2.0.0" ) ;
3019+ } finally {
3020+ await cleanupDirs ( cloneDir , bareDir ) ;
3021+ }
3022+ } ) ;
3023+
3024+ it ( "returns undefined when repo has no tags" , async ( ) => {
3025+ const { cloneDir, bareDir } = await createRepoWithTags ( [ ] ) ;
3026+ try {
3027+ const service = new AutoVersioningService ( { logger : mockLogger } ) ;
3028+ const result = await service . getLatestVersionFromGitTags ( cloneDir ) ;
3029+ expect ( result ) . toBeUndefined ( ) ;
3030+ } finally {
3031+ await cleanupDirs ( cloneDir , bareDir ) ;
3032+ }
3033+ } ) ;
3034+
3035+ it ( "filters out non-semver tags" , async ( ) => {
3036+ const { cloneDir, bareDir } = await createRepoWithTags ( [ "latest" , "release-candidate" , "build-123" , "v1.2.3" ] ) ;
3037+ try {
3038+ const service = new AutoVersioningService ( { logger : mockLogger } ) ;
3039+ const result = await service . getLatestVersionFromGitTags ( cloneDir ) ;
3040+ expect ( result ) . toBe ( "v1.2.3" ) ;
3041+ } finally {
3042+ await cleanupDirs ( cloneDir , bareDir ) ;
3043+ }
3044+ } ) ;
3045+
3046+ it ( "returns undefined when all tags are non-semver" , async ( ) => {
3047+ const { cloneDir, bareDir } = await createRepoWithTags ( [ "latest" , "nightly" , "release-candidate" ] ) ;
3048+ try {
3049+ const service = new AutoVersioningService ( { logger : mockLogger } ) ;
3050+ const result = await service . getLatestVersionFromGitTags ( cloneDir ) ;
3051+ expect ( result ) . toBeUndefined ( ) ;
3052+ } finally {
3053+ await cleanupDirs ( cloneDir , bareDir ) ;
3054+ }
3055+ } ) ;
3056+
3057+ it ( "handles tags without v prefix" , async ( ) => {
3058+ const { cloneDir, bareDir } = await createRepoWithTags ( [ "1.0.0" , "1.1.0" , "2.0.0" ] ) ;
3059+ try {
3060+ const service = new AutoVersioningService ( { logger : mockLogger } ) ;
3061+ const result = await service . getLatestVersionFromGitTags ( cloneDir ) ;
3062+ expect ( result ) . toBe ( "2.0.0" ) ;
3063+ } finally {
3064+ await cleanupDirs ( cloneDir , bareDir ) ;
3065+ }
3066+ } ) ;
3067+
3068+ it ( "handles mixed v-prefixed and bare semver tags" , async ( ) => {
3069+ const { cloneDir, bareDir } = await createRepoWithTags ( [ "v1.0.0" , "2.0.0" , "v1.5.0" ] ) ;
3070+ try {
3071+ const service = new AutoVersioningService ( { logger : mockLogger } ) ;
3072+ const result = await service . getLatestVersionFromGitTags ( cloneDir ) ;
3073+ expect ( result ) . toBe ( "2.0.0" ) ;
3074+ } finally {
3075+ await cleanupDirs ( cloneDir , bareDir ) ;
3076+ }
3077+ } ) ;
3078+
3079+ it ( "handles pre-release versions correctly (semver ordering)" , async ( ) => {
3080+ const { cloneDir, bareDir } = await createRepoWithTags ( [ "v1.0.0-beta.1" , "v1.0.0" , "v1.0.0-alpha.1" ] ) ;
3081+ try {
3082+ const service = new AutoVersioningService ( { logger : mockLogger } ) ;
3083+ const result = await service . getLatestVersionFromGitTags ( cloneDir ) ;
3084+ // semver: v1.0.0 > v1.0.0-beta.1 > v1.0.0-alpha.1
3085+ expect ( result ) . toBe ( "v1.0.0" ) ;
3086+ } finally {
3087+ await cleanupDirs ( cloneDir , bareDir ) ;
3088+ }
3089+ } ) ;
3090+
3091+ it ( "returns undefined for non-git directory" , async ( ) => {
3092+ const tmpDir = await fs . mkdtemp ( path . join ( require ( "os" ) . tmpdir ( ) , "test-nogit-" ) ) ;
3093+ try {
3094+ const service = new AutoVersioningService ( { logger : mockLogger } ) ;
3095+ const result = await service . getLatestVersionFromGitTags ( tmpDir ) ;
3096+ // Should not throw — returns undefined gracefully
3097+ expect ( result ) . toBeUndefined ( ) ;
3098+ } finally {
3099+ await cleanupDirs ( tmpDir ) ;
3100+ }
3101+ } ) ;
3102+
3103+ it ( "handles single tag correctly" , async ( ) => {
3104+ const { cloneDir, bareDir } = await createRepoWithTags ( [ "v0.1.0" ] ) ;
3105+ try {
3106+ const service = new AutoVersioningService ( { logger : mockLogger } ) ;
3107+ const result = await service . getLatestVersionFromGitTags ( cloneDir ) ;
3108+ expect ( result ) . toBe ( "v0.1.0" ) ;
3109+ } finally {
3110+ await cleanupDirs ( cloneDir , bareDir ) ;
3111+ }
3112+ } ) ;
3113+ } ) ;
0 commit comments