1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Simple Cache Manager for Link Validation Results
5
+ * Uses GitHub Actions cache API or local file storage
6
+ */
7
+
8
+ const fs = require ( 'fs' ) ;
9
+ const path = require ( 'path' ) ;
10
+ const crypto = require ( 'crypto' ) ;
11
+ const process = require ( 'process' ) ;
12
+
13
+ const CACHE_VERSION = 'v1' ;
14
+ const CACHE_KEY_PREFIX = 'link-validation' ;
15
+ const LOCAL_CACHE_DIR = path . join ( process . cwd ( ) , '.cache' , 'link-validation' ) ;
16
+
17
+ /**
18
+ * Simple cache interface
19
+ */
20
+ class CacheManager {
21
+ constructor ( options = { } ) {
22
+ this . useGitHubCache =
23
+ options . useGitHubCache !== false && process . env . GITHUB_ACTIONS ;
24
+ this . localCacheDir = options . localCacheDir || LOCAL_CACHE_DIR ;
25
+
26
+ // Configurable cache TTL - default 30 days, support environment variable
27
+ this . cacheTTLDays =
28
+ options . cacheTTLDays || parseInt ( process . env . LINK_CACHE_TTL_DAYS ) || 30 ;
29
+ this . maxAge = this . cacheTTLDays * 24 * 60 * 60 * 1000 ;
30
+
31
+ if ( ! this . useGitHubCache ) {
32
+ this . ensureLocalCacheDir ( ) ;
33
+ }
34
+ }
35
+
36
+ ensureLocalCacheDir ( ) {
37
+ if ( ! fs . existsSync ( this . localCacheDir ) ) {
38
+ fs . mkdirSync ( this . localCacheDir , { recursive : true } ) ;
39
+ }
40
+ }
41
+
42
+ generateCacheKey ( filePath , fileHash ) {
43
+ const pathHash = crypto
44
+ . createHash ( 'sha256' )
45
+ . update ( filePath )
46
+ . digest ( 'hex' )
47
+ . substring ( 0 , 8 ) ;
48
+ return `${ CACHE_KEY_PREFIX } -${ CACHE_VERSION } -${ pathHash } -${ fileHash } ` ;
49
+ }
50
+
51
+ async get ( filePath , fileHash ) {
52
+ if ( this . useGitHubCache ) {
53
+ return await this . getFromGitHubCache ( filePath , fileHash ) ;
54
+ } else {
55
+ return await this . getFromLocalCache ( filePath , fileHash ) ;
56
+ }
57
+ }
58
+
59
+ async set ( filePath , fileHash , results ) {
60
+ if ( this . useGitHubCache ) {
61
+ return await this . setToGitHubCache ( filePath , fileHash , results ) ;
62
+ } else {
63
+ return await this . setToLocalCache ( filePath , fileHash , results ) ;
64
+ }
65
+ }
66
+
67
+ async getFromGitHubCache ( filePath , fileHash ) {
68
+ // TODO: This method is a placeholder for GitHub Actions cache integration
69
+ // GitHub Actions cache is handled directly in the workflow via actions/cache
70
+ // This method should either be implemented or removed in future versions
71
+ console . warn (
72
+ '[PLACEHOLDER] getFromGitHubCache: Using placeholder implementation - always returns null'
73
+ ) ;
74
+ return null ;
75
+ }
76
+
77
+ async setToGitHubCache ( filePath , fileHash , results ) {
78
+ // TODO: This method is a placeholder for GitHub Actions cache integration
79
+ // GitHub Actions cache is handled directly in the workflow via actions/cache
80
+ // This method should either be implemented or removed in future versions
81
+ console . warn (
82
+ '[PLACEHOLDER] setToGitHubCache: Using placeholder implementation - always returns true'
83
+ ) ;
84
+ return true ;
85
+ }
86
+
87
+ async getFromLocalCache ( filePath , fileHash ) {
88
+ const cacheKey = this . generateCacheKey ( filePath , fileHash ) ;
89
+ const cacheFile = path . join ( this . localCacheDir , `${ cacheKey } .json` ) ;
90
+
91
+ if ( ! fs . existsSync ( cacheFile ) ) {
92
+ return null ;
93
+ }
94
+
95
+ try {
96
+ const content = fs . readFileSync ( cacheFile , 'utf8' ) ;
97
+ const cached = JSON . parse ( content ) ;
98
+
99
+ // TTL check using configured cache duration
100
+ const age = Date . now ( ) - new Date ( cached . cachedAt ) . getTime ( ) ;
101
+
102
+ if ( age > this . maxAge ) {
103
+ fs . unlinkSync ( cacheFile ) ;
104
+ return null ;
105
+ }
106
+
107
+ return cached . results ;
108
+ } catch ( error ) {
109
+ // Clean up corrupted cache
110
+ try {
111
+ fs . unlinkSync ( cacheFile ) ;
112
+ } catch {
113
+ // Ignore cleanup errors
114
+ }
115
+ return null ;
116
+ }
117
+ }
118
+
119
+ async setToLocalCache ( filePath , fileHash , results ) {
120
+ const cacheKey = this . generateCacheKey ( filePath , fileHash ) ;
121
+ const cacheFile = path . join ( this . localCacheDir , `${ cacheKey } .json` ) ;
122
+
123
+ const cacheData = {
124
+ filePath,
125
+ fileHash,
126
+ results,
127
+ cachedAt : new Date ( ) . toISOString ( ) ,
128
+ } ;
129
+
130
+ try {
131
+ fs . writeFileSync ( cacheFile , JSON . stringify ( cacheData , null , 2 ) ) ;
132
+ return true ;
133
+ } catch ( error ) {
134
+ console . warn ( `Cache save failed: ${ error . message } ` ) ;
135
+ return false ;
136
+ }
137
+ }
138
+
139
+ async cleanup ( ) {
140
+ if ( this . useGitHubCache ) {
141
+ return { removed : 0 , note : 'GitHub Actions cache auto-managed' } ;
142
+ }
143
+
144
+ let removed = 0 ;
145
+ if ( ! fs . existsSync ( this . localCacheDir ) ) {
146
+ return { removed } ;
147
+ }
148
+
149
+ const files = fs . readdirSync ( this . localCacheDir ) ;
150
+
151
+ for ( const file of files ) {
152
+ if ( ! file . endsWith ( '.json' ) ) continue ;
153
+
154
+ const filePath = path . join ( this . localCacheDir , file ) ;
155
+ try {
156
+ const stat = fs . statSync ( filePath ) ;
157
+ if ( Date . now ( ) - stat . mtime . getTime ( ) > this . maxAge ) {
158
+ fs . unlinkSync ( filePath ) ;
159
+ removed ++ ;
160
+ }
161
+ } catch {
162
+ // Remove corrupted files
163
+ try {
164
+ fs . unlinkSync ( filePath ) ;
165
+ removed ++ ;
166
+ } catch {
167
+ // Ignore errors
168
+ }
169
+ }
170
+ }
171
+
172
+ return { removed } ;
173
+ }
174
+ }
175
+
176
+ module . exports = CacheManager ;
177
+ module . exports . CacheManager = CacheManager ;
0 commit comments