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

AJGranowski / preceding-tag-action / 25260058069

02 May 2026 07:32PM UTC coverage: 93.324% (-0.4%) from 93.711%
25260058069

Pull #77

github

AJGranowski
Create cache directory in loadCache
Pull Request #77: Add request cache

172 of 183 branches covered (93.99%)

Branch coverage included in aggregate %.

165 of 178 new or added lines in 4 files covered. (92.7%)

4 existing lines in 2 files now uncovered.

485 of 521 relevant lines covered (93.09%)

12.17 hits per line

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

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

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

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

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

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

23
function mapObject(obj: Record<string, unknown>, callback: (key: string, value: unknown) => unknown): Record<string, unknown> {
9✔
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
        }
15✔
29

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

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

40
        return value;
3✔
41
    };
6✔
42

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

48
        return value;
3✔
49
    };
6✔
50

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

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

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

64
        return value;
3✔
65
    });
3✔
66

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

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

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

86
    octokit.hook.before("request", async (options) => {
4✔
87
        if (!(await optionsWithDefaults.requestCache.isOpen())) {
4!
NEW
88
            return;
×
NEW
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
        }
2✔
96

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

104
    octokit.hook.after("request", async (response, options) => {
4✔
105
        if (!(await optionsWithDefaults.requestCache.isOpen())) {
2!
NEW
106
            return;
×
NEW
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
        }
1✔
114

115
        if (response.headers.etag != null) {
1✔
116
            const requestHash = hashRequestParameters(options);
1✔
117
            optionsWithDefaults.requestCache.put(requestHash, response.headers.etag, response, Date.now());
1✔
118
        }
1✔
119
    });
4✔
120

121
    octokit.hook.error("request", async (error: any) => {
4✔
122
        if (!(await optionsWithDefaults.requestCache.isOpen())) {
2!
NEW
123
            return;
×
NEW
124
        }
×
125

126
        if (error.status === 304 && error.response != null && error.response.headers != null && error.response.headers.etag != null) {
2✔
127
            const cachedResponse = await optionsWithDefaults.requestCache.matchResponse(error.response.headers.etag);
2✔
128
            if (cachedResponse != null) {
2✔
129
                return cachedResponse.response;
1✔
130
            }
1✔
131
        }
2✔
132

133
        throw error;
1✔
134
    });
4✔
135

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