• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

AJGranowski / preceding-tag-action / 25288371112

03 May 2026 07:20PM UTC coverage: 92.634% (-0.8%) from 93.454%
25288371112

push

github

web-flow
Bump the vitest group (#84)

#skip-auto-release

152 of 169 branches covered (89.94%)

Branch coverage included in aggregate %.

263 of 279 relevant lines covered (94.27%)

11.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.78
/src/fetchPrecedingTag.ts
1
import type { GitHubAPI } from "./GitHubAPI";
2
import type { GitRef } from "./types/GitRef";
3
import type { Tag } from "./types/Tag";
4

5
interface Options {
6
    filter?: (string: string) => boolean;
7
    includeRef?: boolean;
8
}
9

10
interface TagDifference {
11
    tags: Tag[];
12
    commitDifference: number;
13
}
14

15
/**
16
 * This function finds the most recent tag that is reachable from a commit.
17
 * Functions similarly to git-describe using GitHub endpoints instead of a local git database.
18
 * If multiple tags are the same distance away, this function returns the most recent tag (by committer date, then author date).
19
 * If no tags are reachable from this commit, this function returns null.
20
 *
21
 * Will reject if the API is unavailable, or if the reference does not exist.
22
 */
23
async function fetchPrecedingTag(githubAPI: GitHubAPI, ref: GitRef, options?: Options): Promise<Tag | null> {
24
    const optionsWithDefaults = {
65✔
25
        filter: (string: string): boolean => string.length > 0,
×
26
        includeRef: false,
27
        ...options
28
    } satisfies Required<Options>;
29

30
    const sha = await githubAPI.fetchCommitSHA(ref);
65✔
31
    const allTags = await githubAPI.fetchAllTags(optionsWithDefaults.filter);
65✔
32
    const tagDistances = await Promise.all(allTags.map(async (tag) => {
65✔
33
        return {
130✔
34
            tags: [tag],
35
            commitDifference: await githubAPI.fetchCommitDifference(tag.sha, sha)
36
        };
37
    }));
38

39
    const precedingTag = tagDistances
65✔
40
        .filter((x) => {
41
            if (isNaN(x.commitDifference)) {
130✔
42
                return false;
3✔
43
            }
44

45
            if (optionsWithDefaults.includeRef) {
127✔
46
                return x.commitDifference >= 0;
3✔
47
            }
48

49
            return x.commitDifference > 0;
124✔
50
        })
51
        .reduce((prev: TagDifference | null, next: TagDifference) => {
52
            if (prev == null) {
123✔
53
                return next;
62✔
54
            }
55

56
            const nextMinusPrev = Math.abs(next.commitDifference) - Math.abs(prev.commitDifference);
61✔
57
            if (nextMinusPrev < 0) {
61✔
58
                return next;
1✔
59
            } else if (nextMinusPrev > 0) {
60✔
60
                return prev;
1✔
61
            }
62

63
            return {
59✔
64
                tags: prev.tags.concat(next.tags),
65
                commitDifference: prev.commitDifference
66
            };
67
        }, null);
68

69
    if (precedingTag == null || precedingTag.tags.length === 0) {
65✔
70
        return null;
3✔
71
    } else if (precedingTag.tags.length === 1) {
62✔
72
        return precedingTag.tags[0];
3✔
73
    }
74

75
    const commitDates = await Promise.all(precedingTag.tags.map(async (tag) => {
59✔
76
        return {
118✔
77
            tag: tag,
78
            commitDate: await githubAPI.fetchCommitDate(tag.sha)
79
        };
80
    }));
81

82
    return commitDates.reduce((prev, next) => {
59✔
83
        let compareNextPrev = nullableDateComparator(next.commitDate.committer, prev.commitDate.committer);
59✔
84
        if (compareNextPrev > 0) {
59✔
85
            return next;
27✔
86
        } else if (compareNextPrev < 0) {
32✔
87
            return prev;
27✔
88
        }
89

90
        compareNextPrev = nullableDateComparator(next.commitDate.author, prev.commitDate.author);
5✔
91

92
        if (compareNextPrev > 0) {
5✔
93
            return next;
2✔
94
        } else if (compareNextPrev < 0) {
3✔
95
            return prev;
2✔
96
        }
97

98
        return prev;
1✔
99
    }).tag;
100
}
101

102
/**
103
 * Simple date comparator, but asserts null is less than any date.
104
 */
105
const nullableDateComparator = (a: string | null | undefined, b: string | null | undefined): number => {
2✔
106
    if (a != null && b != null) {
64✔
107
        return (new Date(a)).getTime() - (new Date(b)).getTime();
20✔
108
    } else if (a == null && b != null) {
44✔
109
        return -1;
19✔
110
    } else if (a != null && b == null) {
25✔
111
        return 1;
19✔
112
    }
113

114
    return 0;
6✔
115
};
116

117
export { fetchPrecedingTag };
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc