• 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

90.0
/src/OctokitPluginRequestCache.ts
1
import * as actionsCache from "@actions/cache";
2
import { hash } from "crypto";
3
import { join as pathJoin } from "path";
4
import { mkdir } from "fs/promises";
5
import type { Octokit } from "@octokit/core";
6
import type { RequestParameters } from "@octokit/types";
7

8
import { ETagRequestCacheDB } from "./ETagRequestCacheDB";
9

10
interface OctokitPluginRequestCacheOptions {
11
    actionsCache?: typeof actionsCache;
12
    enable?: boolean;
13
    requestCache?: ETagRequestCacheDB
14
}
15

16
interface OctokitPluginRequestCacheReturn {
17
    loadCache: (primaryKey: string, restoreKey: string) => Promise<void>;
18
    saveCache: (primaryKey: string) => Promise<void>;
19
}
20

21
const CACHE_DIR = pathJoin("/", "tmp", ".cache", "preceding-tag-action");
2✔
22

23
function mapObject(obj: Record<string, unknown>, callback: (key: string, value: unknown) => unknown): Record<string, unknown> {
24
    return Object.entries(obj).reduce((result, [key, value]: [string, any]) => {
9✔
25
        const mappedValue = callback(key, value);
24✔
26
        if (mappedValue !== undefined) {
24✔
27
            result[key] = mappedValue;
15✔
28
        }
29

30
        return result;
24✔
31
    }, {} as Record<string, unknown>);
32
}
33

34
function hashRequestParameters(requestParameters: RequestParameters): string {
35
    const headerMapper = (key: string, value: unknown): unknown => {
3✔
36
        if (key !== "accept") {
6✔
37
            return undefined;
3✔
38
        }
39

40
        return value;
3✔
41
    };
42

43
    const mediaTypeMapper = (key: string, value: unknown): unknown => {
3✔
44
        if (key !== "format") {
6✔
45
            return undefined;
3✔
46
        }
47

48
        return value;
3✔
49
    };
50

51
    const objectToHash = mapObject(requestParameters, (key, value) => {
3✔
52
        if (key === "request") {
12✔
53
            return undefined;
3✔
54
        }
55

56
        if (key === "headers") {
9✔
57
            return mapObject(value as any, headerMapper);
3✔
58
        }
59

60
        if (key === "mediaType") {
6✔
61
            return mapObject(value as any, mediaTypeMapper);
3✔
62
        }
63

64
        return value;
3✔
65
    });
66

67
    return hash("sha256", JSON.stringify(objectToHash));
3✔
68
}
69

70
export function requestCache(octokit: Octokit, options: OctokitPluginRequestCacheOptions | any): OctokitPluginRequestCacheReturn {
71
    const optionsWithDefaults = {
5✔
72
        actionsCache: actionsCache,
73
        enable: true,
74
        requestCache: new ETagRequestCacheDB(),
75
        ...options as OctokitPluginRequestCacheOptions
76
    } satisfies Required<OctokitPluginRequestCacheOptions>;
77

78
    // Early return if disabled
79
    if (!optionsWithDefaults.enable) {
5✔
80
        return {
1✔
81
            loadCache: (): Promise<void> => Promise.resolve(),
×
82
            saveCache: (): Promise<void> => Promise.resolve()
×
83
        };
84
    }
85

86
    octokit.hook.before("request", async (options) => {
4✔
87
        if (!(await optionsWithDefaults.requestCache.isOpen())) {
4!
88
            return;
×
89
        }
90

91
        let cacheControl = options.headers["cache-control"];
4✔
92
        cacheControl = cacheControl == null ? "" : cacheControl.toString();
4✔
93
        if (cacheControl.includes("no-cache")) {
4✔
94
            return;
2✔
95
        }
96

97
        const requestHash = hashRequestParameters(options);
2✔
98
        const eTag = await optionsWithDefaults.requestCache.matchETag(requestHash);
2✔
99
        if (eTag != null) {
2✔
100
            options.headers["If-None-Match"] = eTag;
1✔
101
        }
102
    });
103

104
    octokit.hook.after("request", async (response, options) => {
4✔
105
        if (!(await optionsWithDefaults.requestCache.isOpen())) {
2!
106
            return;
×
107
        }
108

109
        let cacheControl = options.headers["cache-control"];
2✔
110
        cacheControl = cacheControl == null ? "" : cacheControl.toString();
2✔
111
        if (cacheControl.includes("no-store")) {
2✔
112
            return;
1✔
113
        }
114

115
        if (response.headers.etag != null) {
1!
116
            const etagMatcher = response.headers.etag.match(/("[^"]+")/);
1✔
117
            if (etagMatcher != null && etagMatcher[1] != null) {
1!
118
                const etag = etagMatcher[1];
1✔
119
                const requestHash = hashRequestParameters(options);
1✔
120
                optionsWithDefaults.requestCache.put(requestHash, etag, response, Date.now());
1✔
121
            }
122
        }
123
    });
124

125
    octokit.hook.error("request", async (error: any) => {
4✔
126
        if (!(await optionsWithDefaults.requestCache.isOpen())) {
2!
127
            return;
×
128
        }
129

130
        if (error.status === 304 && error.response != null && error.response.headers != null && error.response.headers.etag != null) {
2!
131
            const cachedResponse = await optionsWithDefaults.requestCache.matchResponse(error.response.headers.etag);
2✔
132
            if (cachedResponse != null) {
2✔
133
                return cachedResponse.response;
1✔
134
            }
135
        }
136

137
        throw error;
1✔
138
    });
139

140
    return {
4✔
141
        async loadCache(primaryKey: string, restoreKey: string): Promise<void> {
142
            await mkdir(CACHE_DIR, {recursive: true});
2✔
143
            await optionsWithDefaults.actionsCache.restoreCache([pathJoin(CACHE_DIR, "*")], primaryKey, [restoreKey]);
2✔
144
            await optionsWithDefaults.requestCache.open();
2✔
145
        },
146
        async saveCache(primaryKey: string): Promise<void> {
147
            await optionsWithDefaults.requestCache.close();
2✔
148
            await optionsWithDefaults.actionsCache.saveCache([pathJoin(CACHE_DIR, "*")], primaryKey);
2✔
149
        }
150
    };
151
}
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