@laurence79/wireit 0.14.13-shared-cache.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +1062 -0
- package/bin/wireit.js +9 -0
- package/lib/analyzer.js +1600 -0
- package/lib/caching/cache.js +7 -0
- package/lib/caching/github-actions-cache.js +832 -0
- package/lib/caching/local-cache.js +78 -0
- package/lib/caching/shared-cache.js +256 -0
- package/lib/cli-options.js +495 -0
- package/lib/cli.js +177 -0
- package/lib/config.js +18 -0
- package/lib/error.js +160 -0
- package/lib/event.js +7 -0
- package/lib/execution/base.js +108 -0
- package/lib/execution/no-command.js +32 -0
- package/lib/execution/service.js +1017 -0
- package/lib/execution/standard.js +683 -0
- package/lib/executor.js +249 -0
- package/lib/fingerprint.js +164 -0
- package/lib/ide.js +583 -0
- package/lib/language-server.js +135 -0
- package/lib/logging/combination-logger.js +41 -0
- package/lib/logging/debug-logger.js +43 -0
- package/lib/logging/logger.js +38 -0
- package/lib/logging/metrics-logger.js +108 -0
- package/lib/logging/quiet/run-tracker.js +597 -0
- package/lib/logging/quiet/stack-map.js +41 -0
- package/lib/logging/quiet/writeover-line.js +197 -0
- package/lib/logging/quiet-logger.js +78 -0
- package/lib/logging/simple-logger.js +296 -0
- package/lib/logging/watch-logger.js +81 -0
- package/lib/script-child-process.js +270 -0
- package/lib/util/ast.js +71 -0
- package/lib/util/async-cache.js +24 -0
- package/lib/util/copy.js +120 -0
- package/lib/util/deferred.js +35 -0
- package/lib/util/delete.js +120 -0
- package/lib/util/dispose.js +16 -0
- package/lib/util/fs.js +258 -0
- package/lib/util/glob.js +255 -0
- package/lib/util/line-monitor.js +69 -0
- package/lib/util/manifest.js +31 -0
- package/lib/util/optimize-mkdirs.js +55 -0
- package/lib/util/package-json-reader.js +61 -0
- package/lib/util/package-json.js +179 -0
- package/lib/util/script-data-dir.js +19 -0
- package/lib/util/shuffle.js +16 -0
- package/lib/util/unreachable.js +12 -0
- package/lib/util/windows.js +87 -0
- package/lib/util/worker-pool.js +61 -0
- package/lib/watcher.js +396 -0
- package/package.json +470 -0
- package/schema.json +132 -0
- package/wireit.svg +1 -0
|
@@ -0,0 +1,832 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
7
|
+
if (value !== null && value !== void 0) {
|
|
8
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
9
|
+
var dispose, inner;
|
|
10
|
+
if (async) {
|
|
11
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
12
|
+
dispose = value[Symbol.asyncDispose];
|
|
13
|
+
}
|
|
14
|
+
if (dispose === void 0) {
|
|
15
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
16
|
+
dispose = value[Symbol.dispose];
|
|
17
|
+
if (async) inner = dispose;
|
|
18
|
+
}
|
|
19
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
20
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
21
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
22
|
+
}
|
|
23
|
+
else if (async) {
|
|
24
|
+
env.stack.push({ async: true });
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
29
|
+
return function (env) {
|
|
30
|
+
function fail(e) {
|
|
31
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
32
|
+
env.hasError = true;
|
|
33
|
+
}
|
|
34
|
+
var r, s = 0;
|
|
35
|
+
function next() {
|
|
36
|
+
while (r = env.stack.pop()) {
|
|
37
|
+
try {
|
|
38
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
39
|
+
if (r.dispose) {
|
|
40
|
+
var result = r.dispose.call(r.value);
|
|
41
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
42
|
+
}
|
|
43
|
+
else s |= 1;
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
fail(e);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
50
|
+
if (env.hasError) throw env.error;
|
|
51
|
+
}
|
|
52
|
+
return next();
|
|
53
|
+
};
|
|
54
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
55
|
+
var e = new Error(message);
|
|
56
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
57
|
+
});
|
|
58
|
+
import * as pathlib from 'path';
|
|
59
|
+
import * as unbudgetedFs from 'fs/promises';
|
|
60
|
+
import * as fs from '../util/fs.js';
|
|
61
|
+
import * as https from 'https';
|
|
62
|
+
import { createHash } from 'crypto';
|
|
63
|
+
import { scriptReferenceToString } from '../config.js';
|
|
64
|
+
import { getScriptDataDir } from '../util/script-data-dir.js';
|
|
65
|
+
import '../util/dispose.js';
|
|
66
|
+
import { fileBudget } from '../util/fs.js';
|
|
67
|
+
import { execFile } from 'child_process';
|
|
68
|
+
import '../util/dispose.js';
|
|
69
|
+
import { inspect } from 'util';
|
|
70
|
+
/**
|
|
71
|
+
* Caches script output to the GitHub Actions cache service.
|
|
72
|
+
*/
|
|
73
|
+
export class GitHubActionsCache {
|
|
74
|
+
#baseUrl;
|
|
75
|
+
#authToken;
|
|
76
|
+
#logger;
|
|
77
|
+
/**
|
|
78
|
+
* Once we've hit a rate limit or service availability error, simply stop
|
|
79
|
+
* hitting the cache for the remainder of this Wireit process. Caching is not
|
|
80
|
+
* critical, it's just an optimization.
|
|
81
|
+
*
|
|
82
|
+
* TODO(aomarks) We could be a little smarter and do retries, but this at
|
|
83
|
+
* least should stop builds breaking in the short-term.
|
|
84
|
+
*/
|
|
85
|
+
#serviceIsDown = false;
|
|
86
|
+
constructor(logger, baseUrl, authToken) {
|
|
87
|
+
this.#baseUrl = baseUrl;
|
|
88
|
+
this.#authToken = authToken;
|
|
89
|
+
this.#logger = logger;
|
|
90
|
+
}
|
|
91
|
+
static async create(logger) {
|
|
92
|
+
const custodianPort = process.env['WIREIT_CACHE_GITHUB_CUSTODIAN_PORT'];
|
|
93
|
+
if (custodianPort === undefined) {
|
|
94
|
+
if (process.env['ACTIONS_RUNTIME_TOKEN'] !== undefined ||
|
|
95
|
+
process.env['ACTIONS_CACHE_URL'] !== undefined ||
|
|
96
|
+
process.env['ACTIONS_RESULTS_URL'] !== undefined) {
|
|
97
|
+
console.warn('⚠️ Please upgrade to google/wireit@setup-github-cache/v2. ' +
|
|
98
|
+
'In the future, Wireit caching for this project will stop working.\n');
|
|
99
|
+
return GitHubActionsCache.#deprecatedCreate(logger);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
error: {
|
|
104
|
+
type: 'failure',
|
|
105
|
+
reason: 'invalid-usage',
|
|
106
|
+
message: 'The WIREIT_CACHE_GITHUB_CUSTODIAN_PORT environment variable was ' +
|
|
107
|
+
'not set, but is required when WIREIT_CACHE=github. Use the ' +
|
|
108
|
+
'google/wireit@setup-github-cache/v2 action to automatically set ' +
|
|
109
|
+
'this environment variable.',
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const custodianUrl = `http://localhost:${custodianPort}`;
|
|
114
|
+
let result;
|
|
115
|
+
try {
|
|
116
|
+
const response = await fetch(custodianUrl);
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
throw new Error(`HTTP status ${response.status}`);
|
|
119
|
+
}
|
|
120
|
+
result = (await response.json());
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
error: {
|
|
126
|
+
type: 'failure',
|
|
127
|
+
reason: 'unknown-error-thrown',
|
|
128
|
+
error: new Error(`Error communicating with cache token mediator service: ` +
|
|
129
|
+
inspect(error)),
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (!result.caching.github.ACTIONS_RESULTS_URL) {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
error: {
|
|
137
|
+
type: 'failure',
|
|
138
|
+
reason: 'invalid-usage',
|
|
139
|
+
message: 'No ACTIONS_RESULTS_URL was returned by the custodian.' +
|
|
140
|
+
' Ensure you are using at least version 2.0.3.',
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
ok: true,
|
|
146
|
+
value: new GitHubActionsCache(logger, result.caching.github.ACTIONS_RESULTS_URL, result.caching.github.ACTIONS_RUNTIME_TOKEN),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
static #deprecatedCreate(logger) {
|
|
150
|
+
// The ACTIONS_RESULTS_URL and ACTIONS_RUNTIME_TOKEN environment variables are
|
|
151
|
+
// automatically provided to GitHub Actions re-usable workflows. However,
|
|
152
|
+
// they are _not_ provided to regular "run" scripts. For this reason, we
|
|
153
|
+
// re-export those variables so that all "run" scripts can access them using
|
|
154
|
+
// the "google/wireit@setup-github-actions-caching/v1" re-usable workflow.
|
|
155
|
+
//
|
|
156
|
+
// https://github.com/actions/toolkit/blob/500d0b42fee2552ae9eeb5933091fe2fbf14e72d/packages/cache/src/internal/cacheHttpClient.ts#L38
|
|
157
|
+
const baseUrl = process.env['ACTIONS_RESULTS_URL'];
|
|
158
|
+
if (!baseUrl) {
|
|
159
|
+
return {
|
|
160
|
+
ok: false,
|
|
161
|
+
error: {
|
|
162
|
+
type: 'failure',
|
|
163
|
+
reason: 'invalid-usage',
|
|
164
|
+
message: 'The ACTIONS_RESULTS_URL variable was not set, but is required when ' +
|
|
165
|
+
'WIREIT_CACHE=github. Use the google/wireit@setup-github-cache/v1 ' +
|
|
166
|
+
'action to automatically set environment variables.',
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (!baseUrl.endsWith('/')) {
|
|
171
|
+
// Internally, the @actions/cache library expects the URL to end with a
|
|
172
|
+
// slash. While we could be more lenient, we want to match the behavior of
|
|
173
|
+
// any other calls happening inside that library which we don't control.
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
error: {
|
|
177
|
+
type: 'failure',
|
|
178
|
+
reason: 'invalid-usage',
|
|
179
|
+
message: `The ACTIONS_RESULTS_URL must end in a forward-slash, got ${JSON.stringify(baseUrl)}.`,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// https://github.com/actions/toolkit/blob/500d0b42fee2552ae9eeb5933091fe2fbf14e72d/packages/cache/src/internal/cacheHttpClient.ts#L63
|
|
184
|
+
const authToken = process.env['ACTIONS_RUNTIME_TOKEN'];
|
|
185
|
+
if (!authToken) {
|
|
186
|
+
return {
|
|
187
|
+
ok: false,
|
|
188
|
+
error: {
|
|
189
|
+
type: 'failure',
|
|
190
|
+
reason: 'invalid-usage',
|
|
191
|
+
message: 'The ACTIONS_RUNTIME_TOKEN variable was not set, but is required when ' +
|
|
192
|
+
'WIREIT_CACHE=github. Use the google/wireit@setup-github-cache/v1 ' +
|
|
193
|
+
'action to automatically set environment variables.',
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
ok: true,
|
|
199
|
+
value: new GitHubActionsCache(logger, baseUrl, authToken),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
async get(script, fingerprint) {
|
|
203
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
204
|
+
try {
|
|
205
|
+
if (this.#serviceIsDown) {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
const version = this.#computeVersion(fingerprint);
|
|
209
|
+
const key = this.#computeCacheKey(script);
|
|
210
|
+
const url = new URL(
|
|
211
|
+
// See https://github.com/actions/toolkit/blob/930c89072712a3aac52d74b23338f00bb0cfcb24/packages/cache/src/generated/results/api/v1/cache.twirp-client.ts#L91
|
|
212
|
+
'twirp/github.actions.results.api.v1.CacheService/GetCacheEntryDownloadURL', this.#baseUrl);
|
|
213
|
+
// See https://github.com/actions/toolkit/blob/930c89072712a3aac52d74b23338f00bb0cfcb24/packages/cache/src/cache.ts#L246
|
|
214
|
+
// and https://github.com/actions/toolkit/blob/930c89072712a3aac52d74b23338f00bb0cfcb24/packages/cache/src/generated/results/api/v1/cache.ts#L101C1-L126C2
|
|
215
|
+
const requestBody = { key, version };
|
|
216
|
+
const bodyBuffer = Buffer.from(JSON.stringify(requestBody), 'utf8');
|
|
217
|
+
const requestResult = __addDisposableResource(env_1, this.#request(url, {
|
|
218
|
+
method: 'POST',
|
|
219
|
+
headers: {
|
|
220
|
+
'Content-Type': 'application/json',
|
|
221
|
+
'Content-Length': bodyBuffer.length,
|
|
222
|
+
},
|
|
223
|
+
}), false);
|
|
224
|
+
const { req, resPromise } = requestResult;
|
|
225
|
+
req.end(bodyBuffer);
|
|
226
|
+
const result = await resPromise;
|
|
227
|
+
if (!this.#maybeHandleServiceDown(result, script)) {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
const response = result.value;
|
|
231
|
+
if (isOk(response)) {
|
|
232
|
+
const { signed_download_url: archiveLocation } = JSON.parse(await readBody(response));
|
|
233
|
+
if (!archiveLocation) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
return new GitHubActionsCacheHit(script, archiveLocation, this.#logger);
|
|
237
|
+
}
|
|
238
|
+
throw new Error(`GitHub Cache check HTTP ${String(response.statusCode)} error: ` +
|
|
239
|
+
(await readBody(response)));
|
|
240
|
+
}
|
|
241
|
+
catch (e_1) {
|
|
242
|
+
env_1.error = e_1;
|
|
243
|
+
env_1.hasError = true;
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
__disposeResources(env_1);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
async set(script, fingerprint, absFiles) {
|
|
250
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
251
|
+
try {
|
|
252
|
+
if (this.#serviceIsDown) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
const tempDir = __addDisposableResource(env_2, await makeTempDir(script), true);
|
|
256
|
+
const tarballPath = await this.#makeTarball(absFiles.map((file) => file.path), tempDir.path);
|
|
257
|
+
return await this.#reserveUploadAndCommitTarball(script, fingerprint, tarballPath);
|
|
258
|
+
}
|
|
259
|
+
catch (e_2) {
|
|
260
|
+
env_2.error = e_2;
|
|
261
|
+
env_2.hasError = true;
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
const result_1 = __disposeResources(env_2);
|
|
265
|
+
if (result_1)
|
|
266
|
+
await result_1;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* @returns True if we reserved, uploaded, and committed the tarball. False if
|
|
271
|
+
* we gave up due to a rate limit error.
|
|
272
|
+
* @throws If an unexpected HTTP error occured.
|
|
273
|
+
*/
|
|
274
|
+
async #reserveUploadAndCommitTarball(script, fingerprint, tarballPath) {
|
|
275
|
+
const tarballStats = await fs.stat(tarballPath);
|
|
276
|
+
const tarballBytes = tarballStats.size;
|
|
277
|
+
// Reference:
|
|
278
|
+
// https://github.com/actions/toolkit/blob/f8a69bc473af4a204d0c03de61d5c9d1300dfb17/packages/cache/src/cache.ts#L174
|
|
279
|
+
const GB = 1024 * 1024 * 1024;
|
|
280
|
+
const maxBytes = 10 * GB;
|
|
281
|
+
if (tarballBytes > maxBytes) {
|
|
282
|
+
this.#logger.log({
|
|
283
|
+
script,
|
|
284
|
+
type: 'info',
|
|
285
|
+
detail: 'cache-info',
|
|
286
|
+
message: `Output was too big to be cached: ` +
|
|
287
|
+
`${Math.round(tarballBytes / GB)}GB > ` +
|
|
288
|
+
`${Math.round(maxBytes / GB)}GB.`,
|
|
289
|
+
});
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
const key = this.#computeCacheKey(script);
|
|
293
|
+
const version = this.#computeVersion(fingerprint);
|
|
294
|
+
const blobUrl = await this.#createCacheEntry(script, key, version);
|
|
295
|
+
// It's likely that we'll occasionally fail to reserve an entry and get
|
|
296
|
+
// undefined here, especially when running multiple GitHub Action jobs in
|
|
297
|
+
// parallel with the same scripts, because there is a window of time between
|
|
298
|
+
// calling "get" and "set" on the cache in which another worker could have
|
|
299
|
+
// reserved the entry before us. Non fatal, just don't save.
|
|
300
|
+
if (blobUrl === undefined) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
if (!(await this.#upload(script, blobUrl, tarballPath, tarballBytes))) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
if (!(await this.#finalize(script, key, version, tarballBytes))) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* @returns True if we uploaded, false if we gave up due to a rate limit error.
|
|
313
|
+
* @throws If an unexpected HTTP error occured.
|
|
314
|
+
*/
|
|
315
|
+
async #upload(script, blobUrl, tarballPath, tarballBytes) {
|
|
316
|
+
// Reference:
|
|
317
|
+
// https://github.com/actions/toolkit/blob/500d0b42fee2552ae9eeb5933091fe2fbf14e72d/packages/cache/src/options.ts#L59
|
|
318
|
+
const maxChunkSize = 32 * 1024 * 1024;
|
|
319
|
+
// TODO: update to TypeScript 5.2 and use the new `using` syntax for the
|
|
320
|
+
// budget object.
|
|
321
|
+
const reservation = await fileBudget.reserve();
|
|
322
|
+
const tarballHandle = await unbudgetedFs.open(tarballPath, 'r');
|
|
323
|
+
let offset = 0;
|
|
324
|
+
try {
|
|
325
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
326
|
+
try {
|
|
327
|
+
// See https://github.com/actions/toolkit/blob/930c89072712a3aac52d74b23338f00bb0cfcb24/packages/cache/src/internal/uploadUtils.ts#L132
|
|
328
|
+
// TODO(aomarks) Chunks could be uploaded in parallel.
|
|
329
|
+
const blockIds = [];
|
|
330
|
+
let blockIdx = 0;
|
|
331
|
+
while (offset < tarballBytes) {
|
|
332
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
333
|
+
try {
|
|
334
|
+
const chunkSize = Math.min(tarballBytes - offset, maxChunkSize);
|
|
335
|
+
const start = offset;
|
|
336
|
+
const end = offset + chunkSize - 1;
|
|
337
|
+
offset += maxChunkSize;
|
|
338
|
+
const tarballChunkStream = await fs.createReadStream(tarballPath, {
|
|
339
|
+
fd: tarballHandle.fd,
|
|
340
|
+
start,
|
|
341
|
+
end,
|
|
342
|
+
autoClose: false,
|
|
343
|
+
});
|
|
344
|
+
const opts = {
|
|
345
|
+
method: 'PUT',
|
|
346
|
+
headers: {
|
|
347
|
+
'content-type': 'application/octet-stream',
|
|
348
|
+
'content-length': `${chunkSize}`,
|
|
349
|
+
'x-ms-blob-type': 'BlockBlob',
|
|
350
|
+
authorization: undefined,
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
const putBlockUrl = new URL(blobUrl);
|
|
354
|
+
putBlockUrl.searchParams.set('comp', 'block');
|
|
355
|
+
// All block IDs must be the same length within a blob.
|
|
356
|
+
const blockId = Buffer.from(blockIdx.toString(16).padStart(4, '0')).toString('base64');
|
|
357
|
+
blockIdx++;
|
|
358
|
+
blockIds.push(blockId);
|
|
359
|
+
putBlockUrl.searchParams.set('blockid', blockId);
|
|
360
|
+
const requestResult = __addDisposableResource(env_4, this.#request(putBlockUrl, opts), false);
|
|
361
|
+
const { req, resPromise } = requestResult;
|
|
362
|
+
tarballChunkStream.pipe(req);
|
|
363
|
+
tarballChunkStream.on('close', () => {
|
|
364
|
+
req.end();
|
|
365
|
+
});
|
|
366
|
+
const result = await resPromise;
|
|
367
|
+
if (!this.#maybeHandleServiceDown(result, script)) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
const response = result.value;
|
|
371
|
+
if (!isOk(response)) {
|
|
372
|
+
throw new Error(`GitHub Cache upload HTTP ${String(response.statusCode)} error: ${await readBody(response)}\nopts: ${JSON.stringify(opts)}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
catch (e_3) {
|
|
376
|
+
env_4.error = e_3;
|
|
377
|
+
env_4.hasError = true;
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
__disposeResources(env_4);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const putBlockListUrl = new URL(blobUrl);
|
|
384
|
+
putBlockListUrl.searchParams.set('comp', 'blocklist');
|
|
385
|
+
const doneXmlBody = Buffer.from(`<?xml version="1.0" encoding="utf-8"?>
|
|
386
|
+
<BlockList>
|
|
387
|
+
${blockIds.map((blockId) => ` <Uncommitted>${blockId}</Uncommitted>`).join('\n')}
|
|
388
|
+
</BlockList>
|
|
389
|
+
`, 'utf8');
|
|
390
|
+
const requestResult = __addDisposableResource(env_3, this.#request(putBlockListUrl, {
|
|
391
|
+
method: 'PUT',
|
|
392
|
+
headers: {
|
|
393
|
+
'content-type': 'text/plain; charset=UTF-8',
|
|
394
|
+
'content-length': `${doneXmlBody.length}`,
|
|
395
|
+
authorization: undefined,
|
|
396
|
+
},
|
|
397
|
+
}), false);
|
|
398
|
+
requestResult.req.end(doneXmlBody);
|
|
399
|
+
const r = await requestResult.resPromise;
|
|
400
|
+
if (!this.#maybeHandleServiceDown(r, script)) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
if (!isOk(r.value)) {
|
|
404
|
+
throw new Error(`GitHub Cache finalize HTTP ${String(r.value.statusCode)} error: ${await readBody(r.value)}`);
|
|
405
|
+
}
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
catch (e_4) {
|
|
409
|
+
env_3.error = e_4;
|
|
410
|
+
env_3.hasError = true;
|
|
411
|
+
}
|
|
412
|
+
finally {
|
|
413
|
+
__disposeResources(env_3);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
finally {
|
|
417
|
+
await tarballHandle.close();
|
|
418
|
+
reservation[Symbol.dispose]();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* @returns True if we committed, false if we gave up due to a rate limit error.
|
|
423
|
+
* @throws If an unexpected HTTP error occured.
|
|
424
|
+
*/
|
|
425
|
+
async #finalize(script, key, version, tarballBytes) {
|
|
426
|
+
const env_5 = { stack: [], error: void 0, hasError: false };
|
|
427
|
+
try {
|
|
428
|
+
const url = new URL(
|
|
429
|
+
// See
|
|
430
|
+
// https://github.com/actions/toolkit/blob/930c89072712a3aac52d74b23338f00bb0cfcb24/packages/cache/src/generated/results/api/v1/cache.twirp-client.ts#L132
|
|
431
|
+
`twirp/github.actions.results.api.v1.CacheService/FinalizeCacheEntryUpload`, this.#baseUrl);
|
|
432
|
+
// See
|
|
433
|
+
// https://github.com/actions/toolkit/blob/930c89072712a3aac52d74b23338f00bb0cfcb24/packages/cache/src/cache.ts#L555
|
|
434
|
+
// and https://github.com/actions/toolkit/blob/930c89072712a3aac52d74b23338f00bb0cfcb24/packages/cache/src/generated/results/api/v1/cache.ts#L57
|
|
435
|
+
const body = {
|
|
436
|
+
key,
|
|
437
|
+
version,
|
|
438
|
+
sizeBytes: tarballBytes,
|
|
439
|
+
};
|
|
440
|
+
const bodyBuffer = Buffer.from(JSON.stringify(body), 'utf8');
|
|
441
|
+
const requestResult = __addDisposableResource(env_5, this.#request(url, {
|
|
442
|
+
method: 'POST',
|
|
443
|
+
headers: {
|
|
444
|
+
'content-type': 'application/json',
|
|
445
|
+
'content-length': bodyBuffer.length,
|
|
446
|
+
},
|
|
447
|
+
}), false);
|
|
448
|
+
const { req, resPromise } = requestResult;
|
|
449
|
+
req.end(bodyBuffer);
|
|
450
|
+
const result = await resPromise;
|
|
451
|
+
if (!this.#maybeHandleServiceDown(result, script)) {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
const response = result.value;
|
|
455
|
+
if (!isOk(response)) {
|
|
456
|
+
throw new Error(`GitHub Cache commit HTTP ${String(response.statusCode)} error: ${await readBody(response)}`);
|
|
457
|
+
}
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
catch (e_5) {
|
|
461
|
+
env_5.error = e_5;
|
|
462
|
+
env_5.hasError = true;
|
|
463
|
+
}
|
|
464
|
+
finally {
|
|
465
|
+
__disposeResources(env_5);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
#request(url, options) {
|
|
469
|
+
return request(url, {
|
|
470
|
+
...options,
|
|
471
|
+
headers: {
|
|
472
|
+
accept: 'application/json',
|
|
473
|
+
// https://github.com/actions/toolkit/blob/500d0b42fee2552ae9eeb5933091fe2fbf14e72d/packages/http-client/src/auth.ts#L46
|
|
474
|
+
authorization: `Bearer ${this.#authToken}`,
|
|
475
|
+
...options?.headers,
|
|
476
|
+
},
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* If we received a network or HTTP error from the given HTTP request, set the
|
|
481
|
+
* global flag to indicate that GitHub Actions Caching is down (it's a class
|
|
482
|
+
* property, but this class should be a global singleton), log an error
|
|
483
|
+
* (possibly asynchronously), and return false. If the request was OK, just
|
|
484
|
+
* return true.
|
|
485
|
+
*/
|
|
486
|
+
#maybeHandleServiceDown(res, script) {
|
|
487
|
+
const status = res.ok ? res.value.statusCode : null;
|
|
488
|
+
if (res.ok && status != null && status >= 200 && status <= 299) {
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
if (this.#serviceIsDown) {
|
|
492
|
+
// Reduce noise; just whatever the first error was is OK.
|
|
493
|
+
//
|
|
494
|
+
// Note that this cache can be accessed concurrently, so even though we
|
|
495
|
+
// stop making HTTP requests after setting this flag, there could be >1
|
|
496
|
+
// pending requests out at the same time before the first error is
|
|
497
|
+
// detected.
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
this.#serviceIsDown = true;
|
|
501
|
+
if (!res.ok) {
|
|
502
|
+
this.#logger.log({
|
|
503
|
+
script,
|
|
504
|
+
type: 'info',
|
|
505
|
+
detail: 'cache-info',
|
|
506
|
+
message: `Network error from GitHub Actions cache service.` +
|
|
507
|
+
` GitHub Actions caching has been temporarily disabled.` +
|
|
508
|
+
` Detail:\n\n${res.error}`,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
void (async () => {
|
|
513
|
+
const body = await readBody(res.value).catch(() => '');
|
|
514
|
+
if (this.#serviceIsDown) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const message = status === 429
|
|
518
|
+
? `Hit GitHub Actions cache service rate limit`
|
|
519
|
+
: status === 503
|
|
520
|
+
? `GitHub Actions cache service is temporarily unavailable`
|
|
521
|
+
: `Unexpected HTTP ${status} error from GitHub Actions cache service`;
|
|
522
|
+
this.#logger.log({
|
|
523
|
+
script,
|
|
524
|
+
type: 'info',
|
|
525
|
+
detail: 'cache-info',
|
|
526
|
+
message: `${message}.` +
|
|
527
|
+
` GitHub Actions caching has been temporarily disabled.` +
|
|
528
|
+
` Detail:\n\nHTTP ${status}: ${body}`,
|
|
529
|
+
});
|
|
530
|
+
})();
|
|
531
|
+
}
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
#computeCacheKey(script) {
|
|
535
|
+
return `wireit-${createHash('sha256')
|
|
536
|
+
.update(scriptReferenceToString(script))
|
|
537
|
+
.digest('hex')}`;
|
|
538
|
+
}
|
|
539
|
+
#computeVersion(fingerprint) {
|
|
540
|
+
const parts = [
|
|
541
|
+
fingerprint.string,
|
|
542
|
+
'gzip', // e.g. zstd, gzip
|
|
543
|
+
// The ImageOS environment variable tells us which operating system
|
|
544
|
+
// version is being used for the worker VM (e.g. "ubuntu20",
|
|
545
|
+
// "macos11"). We already include process.platform in the fingerprint,
|
|
546
|
+
// but this is more specific.
|
|
547
|
+
//
|
|
548
|
+
// There is also an ImageVersion variable (e.g. "20220405.4") which we
|
|
549
|
+
// could consider including, but it probably changes frequently and is
|
|
550
|
+
// unlikely to affect output, so we prefer the higher cache hit rate.
|
|
551
|
+
process.env.ImageOS ?? '',
|
|
552
|
+
// Versioning salt:
|
|
553
|
+
// - <omitted>: Initial version.
|
|
554
|
+
// - 2: Removed empty directories manifest.
|
|
555
|
+
'2',
|
|
556
|
+
];
|
|
557
|
+
return createHash('sha256')
|
|
558
|
+
.update(parts.join('\x1E'))
|
|
559
|
+
.digest('hex');
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Create a tarball file in a local temp directory containing the given paths.
|
|
563
|
+
*
|
|
564
|
+
* @returns The full path to the tarball file on disk.
|
|
565
|
+
*/
|
|
566
|
+
async #makeTarball(paths, tempDir) {
|
|
567
|
+
// Create a manifest file so that we can pass a large number of files to
|
|
568
|
+
// tar.
|
|
569
|
+
const manifestPath = pathlib.join(tempDir, 'manifest.txt');
|
|
570
|
+
await fs.writeFile(manifestPath, paths.join('\n'), 'utf8');
|
|
571
|
+
const tarballPath = pathlib.join(tempDir, 'cache.tgz');
|
|
572
|
+
await new Promise((resolve, reject) => {
|
|
573
|
+
execFile('tar', [
|
|
574
|
+
// Use the newer standardized tar format.
|
|
575
|
+
'--posix',
|
|
576
|
+
// Use gzip compression.
|
|
577
|
+
//
|
|
578
|
+
// TODO(aomarks) zstd is faster and has better performance, but it's
|
|
579
|
+
// availability is unreliable, and appears to have a bug on Windows
|
|
580
|
+
// (https://github.com/actions/cache/issues/301). Investigate and
|
|
581
|
+
// enable if easy.
|
|
582
|
+
'--gzip',
|
|
583
|
+
'--create',
|
|
584
|
+
'--file',
|
|
585
|
+
tarballPath,
|
|
586
|
+
// Use absolute paths (note we use the short form because the long
|
|
587
|
+
// form is --absolute-names on GNU tar, but --absolute-paths on BSD
|
|
588
|
+
// tar).
|
|
589
|
+
'-P',
|
|
590
|
+
// We have a complete list of files and directories, so we don't need
|
|
591
|
+
// or want tar to automatically expand directories. This also allows
|
|
592
|
+
// us to create empty directories, even if they aren't actually empty
|
|
593
|
+
// on disk.
|
|
594
|
+
'--no-recursion',
|
|
595
|
+
'--files-from',
|
|
596
|
+
manifestPath,
|
|
597
|
+
], (error) => {
|
|
598
|
+
if (error != null) {
|
|
599
|
+
reject(new Error(`tar error`, { cause: error }));
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
resolve();
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
return tarballPath;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Reserve a cache entry.
|
|
610
|
+
*
|
|
611
|
+
* @returns A numeric cache id the cache entry was reserved for us, or
|
|
612
|
+
* undefined if the cache entry was already reserved, or a rate limit error
|
|
613
|
+
* occured.
|
|
614
|
+
*/
|
|
615
|
+
async #createCacheEntry(script, key, version) {
|
|
616
|
+
const env_6 = { stack: [], error: void 0, hasError: false };
|
|
617
|
+
try {
|
|
618
|
+
const url = new URL(
|
|
619
|
+
// See https://github.com/actions/toolkit/blob/930c89072712a3aac52d74b23338f00bb0cfcb24/packages/cache/src/generated/results/api/v1/cache.twirp-client.ts#L117
|
|
620
|
+
'twirp/github.actions.results.api.v1.CacheService/CreateCacheEntry', this.#baseUrl);
|
|
621
|
+
// See
|
|
622
|
+
// https://github.com/actions/toolkit/blob/930c89072712a3aac52d74b23338f00bb0cfcb24/packages/cache/src/cache.ts#L527
|
|
623
|
+
// and https://github.com/actions/toolkit/blob/930c89072712a3aac52d74b23338f00bb0cfcb24/packages/cache/src/generated/results/api/v1/cache.ts#L19
|
|
624
|
+
const body = {
|
|
625
|
+
key,
|
|
626
|
+
version,
|
|
627
|
+
};
|
|
628
|
+
const bodyBuffer = Buffer.from(JSON.stringify(body), 'utf8');
|
|
629
|
+
const requestResult = __addDisposableResource(env_6, this.#request(url, {
|
|
630
|
+
method: 'POST',
|
|
631
|
+
headers: {
|
|
632
|
+
'content-type': 'application/json',
|
|
633
|
+
'content-length': bodyBuffer.length,
|
|
634
|
+
},
|
|
635
|
+
}), false);
|
|
636
|
+
const { req, resPromise } = requestResult;
|
|
637
|
+
req.end(bodyBuffer);
|
|
638
|
+
const result = await resPromise;
|
|
639
|
+
if (result.ok && result.value.statusCode === /* Conflict */ 409) {
|
|
640
|
+
// We must have been racing with another concurrent process running the
|
|
641
|
+
// same script.
|
|
642
|
+
return undefined;
|
|
643
|
+
}
|
|
644
|
+
if (!this.#maybeHandleServiceDown(result, script)) {
|
|
645
|
+
return undefined;
|
|
646
|
+
}
|
|
647
|
+
const response = result.value;
|
|
648
|
+
if (isOk(response)) {
|
|
649
|
+
const resData = JSON.parse(await readBody(response));
|
|
650
|
+
return resData.signed_upload_url;
|
|
651
|
+
}
|
|
652
|
+
throw new Error(`GitHub Cache reserve HTTP ${String(response.statusCode)} error: ${await readBody(response)}`);
|
|
653
|
+
}
|
|
654
|
+
catch (e_6) {
|
|
655
|
+
env_6.error = e_6;
|
|
656
|
+
env_6.hasError = true;
|
|
657
|
+
}
|
|
658
|
+
finally {
|
|
659
|
+
__disposeResources(env_6);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
class GitHubActionsCacheHit {
|
|
664
|
+
#script;
|
|
665
|
+
#url;
|
|
666
|
+
#logger;
|
|
667
|
+
#applied = false;
|
|
668
|
+
constructor(script, location, logger) {
|
|
669
|
+
this.#script = script;
|
|
670
|
+
this.#url = location;
|
|
671
|
+
this.#logger = logger;
|
|
672
|
+
}
|
|
673
|
+
async apply() {
|
|
674
|
+
const env_7 = { stack: [], error: void 0, hasError: false };
|
|
675
|
+
try {
|
|
676
|
+
if (this.#applied) {
|
|
677
|
+
throw new Error('GitHubActionsCacheHit.apply was called more than once');
|
|
678
|
+
}
|
|
679
|
+
this.#applied = true;
|
|
680
|
+
const tempDir = __addDisposableResource(env_7, await makeTempDir(this.#script), true);
|
|
681
|
+
const tarballPath = pathlib.join(tempDir.path, 'cache.tgz');
|
|
682
|
+
try {
|
|
683
|
+
await this.#download(tarballPath);
|
|
684
|
+
}
|
|
685
|
+
catch (e) {
|
|
686
|
+
this.#logger.log({
|
|
687
|
+
type: 'info',
|
|
688
|
+
detail: 'cache-info',
|
|
689
|
+
script: this.#script,
|
|
690
|
+
message: `Failed to download GitHub Actions cache tarball: ${e?.message ?? String(e)}`,
|
|
691
|
+
});
|
|
692
|
+
// This is fine, it's as though there was nothing to restore from
|
|
693
|
+
// the cache.
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
await this.#extract(tarballPath);
|
|
697
|
+
}
|
|
698
|
+
catch (e_7) {
|
|
699
|
+
env_7.error = e_7;
|
|
700
|
+
env_7.hasError = true;
|
|
701
|
+
}
|
|
702
|
+
finally {
|
|
703
|
+
const result_2 = __disposeResources(env_7);
|
|
704
|
+
if (result_2)
|
|
705
|
+
await result_2;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
async #download(tarballPath) {
|
|
709
|
+
const env_8 = { stack: [], error: void 0, hasError: false };
|
|
710
|
+
try {
|
|
711
|
+
const requestResult = __addDisposableResource(env_8, request(this.#url), false);
|
|
712
|
+
const { req, resPromise } = requestResult;
|
|
713
|
+
req.end();
|
|
714
|
+
const result = await resPromise;
|
|
715
|
+
if (!result.ok) {
|
|
716
|
+
throw new Error(`GitHub Cache download TCP error`);
|
|
717
|
+
}
|
|
718
|
+
const response = result.value;
|
|
719
|
+
if (!isOk(response)) {
|
|
720
|
+
throw new Error(`GitHub Cache download HTTP ${String(response.statusCode)} error`);
|
|
721
|
+
}
|
|
722
|
+
const writeTarballStream = await fs.createWriteStream(tarballPath);
|
|
723
|
+
await new Promise((resolve, reject) => {
|
|
724
|
+
writeTarballStream.on('error', (error) => reject(error));
|
|
725
|
+
response.on('error', (error) => reject(error));
|
|
726
|
+
response.pipe(writeTarballStream);
|
|
727
|
+
writeTarballStream.on('close', () => {
|
|
728
|
+
resolve();
|
|
729
|
+
});
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
catch (e_8) {
|
|
733
|
+
env_8.error = e_8;
|
|
734
|
+
env_8.hasError = true;
|
|
735
|
+
}
|
|
736
|
+
finally {
|
|
737
|
+
__disposeResources(env_8);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
#extract(tarballPath) {
|
|
741
|
+
return new Promise((resolve, reject) => {
|
|
742
|
+
execFile('tar', ['--extract', '--file', tarballPath, '--gzip', '-P'], (error) => {
|
|
743
|
+
if (error != null) {
|
|
744
|
+
reject(new Error(`tar error`, { cause: error }));
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
resolve();
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
function request(url, options) {
|
|
754
|
+
const opts = {
|
|
755
|
+
...options,
|
|
756
|
+
headers: {
|
|
757
|
+
// https://github.com/actions/toolkit/blob/500d0b42fee2552ae9eeb5933091fe2fbf14e72d/packages/cache/src/internal/cacheHttpClient.ts#L67
|
|
758
|
+
'user-agent': 'actions/cache',
|
|
759
|
+
...options?.headers,
|
|
760
|
+
},
|
|
761
|
+
};
|
|
762
|
+
for (const [key, val] of Object.entries(opts.headers)) {
|
|
763
|
+
if (!val) {
|
|
764
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
765
|
+
delete opts.headers[key];
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
let req;
|
|
769
|
+
const resPromise = new Promise((resolve) => {
|
|
770
|
+
req = https.request(url, opts, (value) => {
|
|
771
|
+
resolve({ ok: true, value });
|
|
772
|
+
});
|
|
773
|
+
req.on('error', (error) => {
|
|
774
|
+
resolve({ ok: false, error });
|
|
775
|
+
});
|
|
776
|
+
req.on('socket', (socket) => {
|
|
777
|
+
socket.on('error', () => {
|
|
778
|
+
resolve({ ok: false, error: new Error('socket error') });
|
|
779
|
+
});
|
|
780
|
+
socket.on('close', (hadError) => {
|
|
781
|
+
if (hadError) {
|
|
782
|
+
resolve({ ok: false, error: new Error('socket closed with error') });
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
return {
|
|
788
|
+
req,
|
|
789
|
+
resPromise,
|
|
790
|
+
[Symbol.dispose]() {
|
|
791
|
+
req.destroy();
|
|
792
|
+
req.socket?.destroy();
|
|
793
|
+
},
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
function isOk(res) {
|
|
797
|
+
return (res.statusCode !== undefined &&
|
|
798
|
+
res.statusCode >= 200 &&
|
|
799
|
+
res.statusCode < 300);
|
|
800
|
+
}
|
|
801
|
+
function readBody(res) {
|
|
802
|
+
const chunks = [];
|
|
803
|
+
res.on('data', (chunk) => {
|
|
804
|
+
chunks.push(chunk);
|
|
805
|
+
});
|
|
806
|
+
return new Promise((resolve, reject) => {
|
|
807
|
+
res.on('error', (error) => {
|
|
808
|
+
reject(error);
|
|
809
|
+
});
|
|
810
|
+
res.socket.on('error', () => {
|
|
811
|
+
reject(new Error('socket error'));
|
|
812
|
+
});
|
|
813
|
+
res.socket.on('close', (hadError) => {
|
|
814
|
+
if (hadError) {
|
|
815
|
+
reject(new Error('socket closed with error'));
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
res.on('end', () => {
|
|
819
|
+
resolve(Buffer.concat(chunks).toString());
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
async function makeTempDir(script) {
|
|
824
|
+
const path = await fs.mkdtemp(pathlib.join(getScriptDataDir(script), 'temp'));
|
|
825
|
+
return {
|
|
826
|
+
path,
|
|
827
|
+
async [Symbol.asyncDispose]() {
|
|
828
|
+
await fs.rm(path, { recursive: true });
|
|
829
|
+
},
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
//# sourceMappingURL=github-actions-cache.js.map
|