@expo/build-tools 1.0.243 → 1.0.245

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.
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createRestoreBuildCacheFunction = createRestoreBuildCacheFunction;
7
+ exports.restoreCcacheAsync = restoreCcacheAsync;
8
+ exports.cacheStatsAsync = cacheStatsAsync;
9
+ const steps_1 = require("@expo/steps");
10
+ const eas_build_job_1 = require("@expo/eas-build-job");
11
+ const results_1 = require("@expo/results");
12
+ const nullthrows_1 = __importDefault(require("nullthrows"));
13
+ const cacheKey_1 = require("../../utils/cacheKey");
14
+ const turtleFetch_1 = require("../../utils/turtleFetch");
15
+ const restoreCache_1 = require("./restoreCache");
16
+ function createRestoreBuildCacheFunction() {
17
+ return new steps_1.BuildFunction({
18
+ namespace: 'eas',
19
+ id: 'restore_build_cache',
20
+ name: 'Restore Cache',
21
+ inputProviders: [
22
+ steps_1.BuildStepInput.createProvider({
23
+ id: 'platform',
24
+ required: false,
25
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
26
+ }),
27
+ ],
28
+ fn: async (stepCtx, { env, inputs }) => {
29
+ var _a;
30
+ const { logger } = stepCtx;
31
+ const workingDirectory = stepCtx.workingDirectory;
32
+ const platform = (_a = inputs.platform.value) !== null && _a !== void 0 ? _a : stepCtx.global.staticContext.job.platform;
33
+ if (!platform || ![eas_build_job_1.Platform.ANDROID, eas_build_job_1.Platform.IOS].includes(platform)) {
34
+ throw new Error(`Unsupported platform: ${platform}. Platform must be "${eas_build_job_1.Platform.ANDROID}" or "${eas_build_job_1.Platform.IOS}"`);
35
+ }
36
+ await restoreCcacheAsync({
37
+ logger,
38
+ workingDirectory,
39
+ platform,
40
+ env,
41
+ secrets: stepCtx.global.staticContext.job.secrets,
42
+ });
43
+ },
44
+ });
45
+ }
46
+ async function restoreCcacheAsync({ logger, workingDirectory, platform, env, secrets, }) {
47
+ var _a;
48
+ const enabled = env.EAS_RESTORE_CACHE === '1' || (env.EAS_USE_CACHE === '1' && env.EAS_RESTORE_CACHE !== '0');
49
+ if (!enabled) {
50
+ return;
51
+ }
52
+ const robotAccessToken = (0, nullthrows_1.default)(secrets === null || secrets === void 0 ? void 0 : secrets.robotAccessToken, 'Robot access token is required for cache operations');
53
+ const expoApiServerURL = (0, nullthrows_1.default)(env.__API_SERVER_URL, '__API_SERVER_URL is not set');
54
+ const cachePath = (0, cacheKey_1.getCcachePath)(env);
55
+ try {
56
+ const cacheKey = await (0, cacheKey_1.generateDefaultBuildCacheKeyAsync)(workingDirectory, platform);
57
+ logger.info(`Restoring cache key: ${cacheKey}`);
58
+ const jobId = (0, nullthrows_1.default)(env.EAS_BUILD_ID, 'EAS_BUILD_ID is not set');
59
+ const { archivePath, matchedKey } = await (0, restoreCache_1.downloadCacheAsync)({
60
+ logger,
61
+ jobId,
62
+ expoApiServerURL,
63
+ robotAccessToken,
64
+ paths: [cachePath],
65
+ key: cacheKey,
66
+ keyPrefixes: [cacheKey_1.CACHE_KEY_PREFIX_BY_PLATFORM[platform]],
67
+ platform,
68
+ });
69
+ await (0, restoreCache_1.decompressCacheAsync)({
70
+ archivePath,
71
+ workingDirectory,
72
+ verbose: env.EXPO_DEBUG === '1',
73
+ logger,
74
+ });
75
+ logger.info(`Cache restored successfully ${matchedKey === cacheKey ? '(direct hit)' : '(prefix match)'}`);
76
+ // Zero ccache stats for accurate tracking
77
+ await (0, results_1.asyncResult)((0, steps_1.spawnAsync)('ccache', ['--zero-stats'], {
78
+ env,
79
+ logger,
80
+ stdio: 'pipe',
81
+ }));
82
+ }
83
+ catch (err) {
84
+ if (err instanceof turtleFetch_1.TurtleFetchError && ((_a = err.response) === null || _a === void 0 ? void 0 : _a.status) === 404) {
85
+ try {
86
+ logger.info('No cache found for this key. Downloading public cache...');
87
+ const { archivePath } = await (0, restoreCache_1.downloadPublicCacheAsync)({
88
+ logger,
89
+ expoApiServerURL,
90
+ robotAccessToken,
91
+ paths: [cachePath],
92
+ platform,
93
+ });
94
+ await (0, restoreCache_1.decompressCacheAsync)({
95
+ archivePath,
96
+ workingDirectory,
97
+ verbose: env.EXPO_DEBUG === '1',
98
+ logger,
99
+ });
100
+ }
101
+ catch (err) {
102
+ logger.warn({ err }, 'Failed to download public cache');
103
+ }
104
+ }
105
+ else {
106
+ logger.warn({ err }, 'Failed to restore cache');
107
+ }
108
+ }
109
+ }
110
+ async function cacheStatsAsync({ logger, env, }) {
111
+ const enabled = env.EAS_RESTORE_CACHE === '1' || (env.EAS_USE_CACHE === '1' && env.EAS_RESTORE_CACHE !== '0');
112
+ if (!enabled) {
113
+ return;
114
+ }
115
+ logger.info('Cache stats:');
116
+ await (0, results_1.asyncResult)((0, steps_1.spawnAsync)('ccache', ['--show-stats', '-v'], {
117
+ env,
118
+ logger,
119
+ stdio: 'pipe',
120
+ }));
121
+ }
122
+ //# sourceMappingURL=restoreBuildCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restoreBuildCache.js","sourceRoot":"","sources":["../../../src/steps/functions/restoreBuildCache.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BuildFunction,\n BuildStepInput,\n BuildStepInputValueTypeName,\n spawnAsync,\n} from '@expo/steps';\nimport { Platform } from '@expo/eas-build-job';\nimport { bunyan } from '@expo/logger';\nimport { asyncResult } from '@expo/results';\nimport nullthrows from 'nullthrows';\n\nimport {\n CACHE_KEY_PREFIX_BY_PLATFORM,\n generateDefaultBuildCacheKeyAsync,\n getCcachePath,\n} from '../../utils/cacheKey';\nimport { TurtleFetchError } from '../../utils/turtleFetch';\n\nimport { downloadCacheAsync, decompressCacheAsync, downloadPublicCacheAsync } from './restoreCache';\n\nexport function createRestoreBuildCacheFunction(): BuildFunction {\n return new BuildFunction({\n namespace: 'eas',\n id: 'restore_build_cache',\n name: 'Restore Cache',\n inputProviders: [\n BuildStepInput.createProvider({\n id: 'platform',\n required: false,\n allowedValueTypeName: BuildStepInputValueTypeName.STRING,\n }),\n ],\n fn: async (stepCtx, { env, inputs }) => {\n const { logger } = stepCtx;\n const workingDirectory = stepCtx.workingDirectory;\n const platform =\n (inputs.platform.value as Platform | undefined) ??\n stepCtx.global.staticContext.job.platform;\n if (!platform || ![Platform.ANDROID, Platform.IOS].includes(platform)) {\n throw new Error(\n `Unsupported platform: ${platform}. Platform must be \"${Platform.ANDROID}\" or \"${Platform.IOS}\"`\n );\n }\n\n await restoreCcacheAsync({\n logger,\n workingDirectory,\n platform,\n env,\n secrets: stepCtx.global.staticContext.job.secrets,\n });\n },\n });\n}\n\nexport async function restoreCcacheAsync({\n logger,\n workingDirectory,\n platform,\n env,\n secrets,\n}: {\n logger: bunyan;\n workingDirectory: string;\n platform: Platform;\n env: Record<string, string | undefined>;\n secrets?: { robotAccessToken?: string };\n}): Promise<void> {\n const enabled =\n env.EAS_RESTORE_CACHE === '1' || (env.EAS_USE_CACHE === '1' && env.EAS_RESTORE_CACHE !== '0');\n\n if (!enabled) {\n return;\n }\n const robotAccessToken = nullthrows(\n secrets?.robotAccessToken,\n 'Robot access token is required for cache operations'\n );\n const expoApiServerURL = nullthrows(env.__API_SERVER_URL, '__API_SERVER_URL is not set');\n const cachePath = getCcachePath(env);\n try {\n const cacheKey = await generateDefaultBuildCacheKeyAsync(workingDirectory, platform);\n logger.info(`Restoring cache key: ${cacheKey}`);\n\n const jobId = nullthrows(env.EAS_BUILD_ID, 'EAS_BUILD_ID is not set');\n const { archivePath, matchedKey } = await downloadCacheAsync({\n logger,\n jobId,\n expoApiServerURL,\n robotAccessToken,\n paths: [cachePath],\n key: cacheKey,\n keyPrefixes: [CACHE_KEY_PREFIX_BY_PLATFORM[platform]],\n platform,\n });\n\n await decompressCacheAsync({\n archivePath,\n workingDirectory,\n verbose: env.EXPO_DEBUG === '1',\n logger,\n });\n\n logger.info(\n `Cache restored successfully ${matchedKey === cacheKey ? '(direct hit)' : '(prefix match)'}`\n );\n\n // Zero ccache stats for accurate tracking\n await asyncResult(\n spawnAsync('ccache', ['--zero-stats'], {\n env,\n logger,\n stdio: 'pipe',\n })\n );\n } catch (err: unknown) {\n if (err instanceof TurtleFetchError && err.response?.status === 404) {\n try {\n logger.info('No cache found for this key. Downloading public cache...');\n const { archivePath } = await downloadPublicCacheAsync({\n logger,\n expoApiServerURL,\n robotAccessToken,\n paths: [cachePath],\n platform,\n });\n await decompressCacheAsync({\n archivePath,\n workingDirectory,\n verbose: env.EXPO_DEBUG === '1',\n logger,\n });\n } catch (err: unknown) {\n logger.warn({ err }, 'Failed to download public cache');\n }\n } else {\n logger.warn({ err }, 'Failed to restore cache');\n }\n }\n}\n\nexport async function cacheStatsAsync({\n logger,\n env,\n}: {\n logger: bunyan;\n env: Record<string, string | undefined>;\n}): Promise<void> {\n const enabled =\n env.EAS_RESTORE_CACHE === '1' || (env.EAS_USE_CACHE === '1' && env.EAS_RESTORE_CACHE !== '0');\n\n if (!enabled) {\n return;\n }\n logger.info('Cache stats:');\n await asyncResult(\n spawnAsync('ccache', ['--show-stats', '-v'], {\n env,\n logger,\n stdio: 'pipe',\n })\n );\n}\n"]}
@@ -15,6 +15,16 @@ export declare function downloadCacheAsync({ logger, jobId, expoApiServerURL, ro
15
15
  archivePath: string;
16
16
  matchedKey: string;
17
17
  }>;
18
+ export declare function downloadPublicCacheAsync({ logger, expoApiServerURL, robotAccessToken, paths, platform, }: {
19
+ logger: bunyan;
20
+ expoApiServerURL: string;
21
+ robotAccessToken: string;
22
+ paths: string[];
23
+ platform: Platform;
24
+ }): Promise<{
25
+ archivePath: string;
26
+ matchedKey: string;
27
+ }>;
18
28
  export declare function decompressCacheAsync({ archivePath, workingDirectory, verbose, logger, }: {
19
29
  archivePath: string;
20
30
  workingDirectory: string;
@@ -28,6 +28,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.createRestoreCacheFunction = createRestoreCacheFunction;
30
30
  exports.downloadCacheAsync = downloadCacheAsync;
31
+ exports.downloadPublicCacheAsync = downloadPublicCacheAsync;
31
32
  exports.decompressCacheAsync = decompressCacheAsync;
32
33
  const fs_1 = __importDefault(require("fs"));
33
34
  const os_1 = __importDefault(require("os"));
@@ -44,6 +45,7 @@ const retryOnDNSFailure_1 = require("../../utils/retryOnDNSFailure");
44
45
  const artifacts_1 = require("../../utils/artifacts");
45
46
  const cache_1 = require("../utils/cache");
46
47
  const turtleFetch_1 = require("../../utils/turtleFetch");
48
+ const cacheKey_1 = require("../../utils/cacheKey");
47
49
  const streamPipeline = (0, util_1.promisify)(stream_1.default.pipeline);
48
50
  function createRestoreCacheFunction() {
49
51
  return new steps_1.BuildFunction({
@@ -171,6 +173,49 @@ async function downloadCacheAsync({ logger, jobId, expoApiServerURL, robotAccess
171
173
  throw err;
172
174
  }
173
175
  }
176
+ async function downloadPublicCacheAsync({ logger, expoApiServerURL, robotAccessToken, paths, platform, }) {
177
+ const routerURL = 'v2/public-turtle-caches/download';
178
+ const key = cacheKey_1.PUBLIC_CACHE_KEY_PREFIX_BY_PLATFORM[platform];
179
+ try {
180
+ const response = await (0, turtleFetch_1.turtleFetch)(new URL(routerURL, expoApiServerURL).toString(), 'POST', {
181
+ json: {
182
+ key,
183
+ version: (0, cache_1.getCacheVersion)(paths),
184
+ keyPrefixes: [key],
185
+ },
186
+ headers: {
187
+ Authorization: `Bearer ${robotAccessToken}`,
188
+ 'Content-Type': 'application/json',
189
+ },
190
+ retries: 2,
191
+ shouldThrowOnNotOk: true,
192
+ });
193
+ const result = await (0, results_1.asyncResult)(response.json());
194
+ if (!result.ok) {
195
+ throw new Error(`Unexpected response from server (${response.status}): ${result.reason}`);
196
+ }
197
+ const { matchedKey, downloadUrl } = result.value.data;
198
+ logger.info(`Matched public cache key: ${matchedKey}. Downloading...`);
199
+ const downloadDestinationDirectory = await fs_1.default.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'restore-cache-'));
200
+ const downloadResponse = await (0, retryOnDNSFailure_1.retryOnDNSFailure)(node_fetch_1.default)(downloadUrl);
201
+ if (!downloadResponse.ok) {
202
+ throw new Error(`Unexpected response from cache server (${downloadResponse.status}): ${downloadResponse.statusText}`);
203
+ }
204
+ const archiveFilename = path_1.default
205
+ .basename(new URL(downloadUrl).pathname)
206
+ .replace(/([^a-z0-9.-]+)/gi, '_');
207
+ const archivePath = path_1.default.join(downloadDestinationDirectory, archiveFilename);
208
+ await streamPipeline(downloadResponse.body, fs_1.default.createWriteStream(archivePath));
209
+ return { archivePath, matchedKey };
210
+ }
211
+ catch (err) {
212
+ if (err instanceof turtleFetch_1.TurtleFetchError && err.response.status !== 404) {
213
+ const textResult = await (0, results_1.asyncResult)(err.response.text());
214
+ throw new Error(`Unexpected response from server (${err.response.status}): ${textResult.value}`);
215
+ }
216
+ throw err;
217
+ }
218
+ }
174
219
  async function decompressCacheAsync({ archivePath, workingDirectory, verbose, logger, }) {
175
220
  if (verbose) {
176
221
  logger.info(`Extracting cache to ${workingDirectory}:`);
@@ -1 +1 @@
1
- {"version":3,"file":"restoreCache.js","sourceRoot":"","sources":["../../../src/steps/functions/restoreCache.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport stream from 'stream';\nimport { promisify } from 'util';\n\nimport * as tar from 'tar';\nimport { bunyan } from '@expo/logger';\nimport {\n BuildFunction,\n BuildStepInput,\n BuildStepInputValueTypeName,\n BuildStepOutput,\n} from '@expo/steps';\nimport z from 'zod';\nimport nullthrows from 'nullthrows';\nimport fetch from 'node-fetch';\nimport { asyncResult } from '@expo/results';\nimport { Platform } from '@expo/eas-build-job';\n\nimport { retryOnDNSFailure } from '../../utils/retryOnDNSFailure';\nimport { formatBytes } from '../../utils/artifacts';\nimport { getCacheVersion } from '../utils/cache';\nimport { turtleFetch, TurtleFetchError } from '../../utils/turtleFetch';\n\nconst streamPipeline = promisify(stream.pipeline);\n\nexport function createRestoreCacheFunction(): BuildFunction {\n return new BuildFunction({\n namespace: 'eas',\n id: 'restore_cache',\n name: 'Restore Cache',\n inputProviders: [\n BuildStepInput.createProvider({\n id: 'path',\n required: false,\n allowedValueTypeName: BuildStepInputValueTypeName.STRING,\n }),\n BuildStepInput.createProvider({\n id: 'key',\n required: true,\n allowedValueTypeName: BuildStepInputValueTypeName.STRING,\n }),\n BuildStepInput.createProvider({\n id: 'restore_keys',\n required: false,\n allowedValueTypeName: BuildStepInputValueTypeName.STRING,\n }),\n ],\n outputProviders: [\n BuildStepOutput.createProvider({\n id: 'cache_hit',\n required: false,\n }),\n ],\n fn: async (stepsCtx, { env, inputs, outputs }) => {\n const { logger } = stepsCtx;\n\n try {\n if (stepsCtx.global.staticContext.job.platform) {\n logger.error('Caches are not supported in build jobs yet.');\n return;\n }\n\n const paths = z\n .array(z.string())\n .parse(((inputs.path.value ?? '') as string).split(/[\\r\\n]+/))\n .filter((path) => path.length > 0);\n const key = z.string().parse(inputs.key.value);\n const restoreKeys = z\n .array(z.string())\n .parse(((inputs.restore_keys.value ?? '') as string).split(/[\\r\\n]+/))\n .filter((key) => key !== '');\n\n const jobId = nullthrows(env.EAS_BUILD_ID, 'EAS_BUILD_ID is not set');\n const robotAccessToken = nullthrows(\n stepsCtx.global.staticContext.job.secrets?.robotAccessToken,\n 'robotAccessToken is not set'\n );\n\n const { archivePath, matchedKey } = await downloadCacheAsync({\n logger,\n jobId,\n expoApiServerURL: stepsCtx.global.staticContext.expoApiServerURL,\n robotAccessToken,\n paths,\n key,\n keyPrefixes: restoreKeys,\n platform: stepsCtx.global.staticContext.job.platform,\n });\n\n const { size } = await fs.promises.stat(archivePath);\n logger.info(`Downloaded cache archive from ${archivePath} (${formatBytes(size)}).`);\n\n await decompressCacheAsync({\n archivePath,\n workingDirectory: stepsCtx.workingDirectory,\n verbose: true,\n logger,\n });\n\n outputs.cache_hit.set(`${matchedKey === key}`);\n } catch (error) {\n logger.error({ err: error }, 'Failed to restore cache');\n }\n },\n });\n}\n\nexport async function downloadCacheAsync({\n logger,\n jobId,\n expoApiServerURL,\n robotAccessToken,\n paths,\n key,\n keyPrefixes,\n platform,\n}: {\n logger: bunyan;\n jobId: string;\n expoApiServerURL: string;\n robotAccessToken: string;\n paths: string[];\n key: string;\n keyPrefixes: string[];\n platform: Platform | undefined;\n}): Promise<{ archivePath: string; matchedKey: string }> {\n const routerURL = platform ? 'v2/turtle-builds/caches/download' : 'v2/turtle-caches/download';\n\n try {\n const response = await turtleFetch(new URL(routerURL, expoApiServerURL).toString(), 'POST', {\n json: platform\n ? {\n buildId: jobId,\n key,\n version: getCacheVersion(paths),\n keyPrefixes,\n }\n : {\n jobRunId: jobId,\n key,\n version: getCacheVersion(paths),\n keyPrefixes,\n },\n headers: {\n Authorization: `Bearer ${robotAccessToken}`,\n 'Content-Type': 'application/json',\n },\n // It's ok to retry POST caches/download, because we're only retrying signing a download URL.\n retries: 2,\n shouldThrowOnNotOk: true,\n });\n\n const result = await asyncResult(response.json());\n if (!result.ok) {\n throw new Error(`Unexpected response from server (${response.status}): ${result.reason}`);\n }\n\n const { matchedKey, downloadUrl } = result.value.data;\n\n logger.info(`Matched cache key: ${matchedKey}. Downloading...`);\n\n const downloadDestinationDirectory = await fs.promises.mkdtemp(\n path.join(os.tmpdir(), 'restore-cache-')\n );\n\n const downloadResponse = await retryOnDNSFailure(fetch)(downloadUrl);\n if (!downloadResponse.ok) {\n throw new Error(\n `Unexpected response from cache server (${downloadResponse.status}): ${downloadResponse.statusText}`\n );\n }\n\n // URL may contain percent-encoded characters, e.g. my%20file.apk\n // this replaces all non-alphanumeric characters (excluding dot) with underscore\n const archiveFilename = path\n .basename(new URL(downloadUrl).pathname)\n .replace(/([^a-z0-9.-]+)/gi, '_');\n const archivePath = path.join(downloadDestinationDirectory, archiveFilename);\n\n await streamPipeline(downloadResponse.body, fs.createWriteStream(archivePath));\n\n return { archivePath, matchedKey };\n } catch (err: any) {\n if (err instanceof TurtleFetchError && err.response.status !== 404) {\n const textResult = await asyncResult(err.response.text());\n throw new Error(\n `Unexpected response from server (${err.response.status}): ${textResult.value}`\n );\n }\n throw err;\n }\n}\n\nexport async function decompressCacheAsync({\n archivePath,\n workingDirectory,\n verbose,\n logger,\n}: {\n archivePath: string;\n workingDirectory: string;\n verbose: boolean;\n logger: bunyan;\n}): Promise<void> {\n if (verbose) {\n logger.info(`Extracting cache to ${workingDirectory}:`);\n }\n\n // First, extract everything to the working directory\n const fileHandle = await fs.promises.open(archivePath, 'r');\n const extractedFiles: string[] = [];\n\n await streamPipeline(\n fileHandle.createReadStream(),\n tar.extract({\n cwd: workingDirectory,\n onwarn: (code, message, data) => {\n logger.warn({ code, data }, message);\n },\n preservePaths: true,\n onReadEntry: (entry) => {\n extractedFiles.push(entry.path);\n if (verbose) {\n logger.info(`- ${entry.path}`);\n }\n },\n })\n );\n\n // Handle absolute paths that were prefixed with __absolute__\n for (const extractedPath of extractedFiles) {\n if (extractedPath.startsWith('__absolute__/')) {\n const originalAbsolutePath = extractedPath.slice('__absolute__'.length);\n const currentPath = path.join(workingDirectory, extractedPath);\n\n try {\n // Ensure the target directory exists\n await fs.promises.mkdir(path.dirname(originalAbsolutePath), { recursive: true });\n\n // Move the file to its original absolute location\n await fs.promises.rename(currentPath, originalAbsolutePath);\n\n if (verbose) {\n logger.info(`Moved ${extractedPath} to ${originalAbsolutePath}`);\n }\n } catch (error) {\n logger.warn(`Failed to restore absolute path ${originalAbsolutePath}: ${error}`);\n }\n }\n }\n\n // Clean up any remaining __absolute__ directories\n const absoluteDir = path.join(workingDirectory, '__absolute__');\n if (\n await fs.promises\n .access(absoluteDir)\n .then(() => true)\n .catch(() => false)\n ) {\n await fs.promises.rm(absoluteDir, { recursive: true, force: true });\n }\n}\n"]}
1
+ {"version":3,"file":"restoreCache.js","sourceRoot":"","sources":["../../../src/steps/functions/restoreCache.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs';\nimport os from 'os';\nimport path from 'path';\nimport stream from 'stream';\nimport { promisify } from 'util';\n\nimport * as tar from 'tar';\nimport { bunyan } from '@expo/logger';\nimport {\n BuildFunction,\n BuildStepInput,\n BuildStepInputValueTypeName,\n BuildStepOutput,\n} from '@expo/steps';\nimport z from 'zod';\nimport nullthrows from 'nullthrows';\nimport fetch from 'node-fetch';\nimport { asyncResult } from '@expo/results';\nimport { Platform } from '@expo/eas-build-job';\n\nimport { retryOnDNSFailure } from '../../utils/retryOnDNSFailure';\nimport { formatBytes } from '../../utils/artifacts';\nimport { getCacheVersion } from '../utils/cache';\nimport { turtleFetch, TurtleFetchError } from '../../utils/turtleFetch';\nimport { PUBLIC_CACHE_KEY_PREFIX_BY_PLATFORM } from '../../utils/cacheKey';\n\nconst streamPipeline = promisify(stream.pipeline);\n\nexport function createRestoreCacheFunction(): BuildFunction {\n return new BuildFunction({\n namespace: 'eas',\n id: 'restore_cache',\n name: 'Restore Cache',\n inputProviders: [\n BuildStepInput.createProvider({\n id: 'path',\n required: false,\n allowedValueTypeName: BuildStepInputValueTypeName.STRING,\n }),\n BuildStepInput.createProvider({\n id: 'key',\n required: true,\n allowedValueTypeName: BuildStepInputValueTypeName.STRING,\n }),\n BuildStepInput.createProvider({\n id: 'restore_keys',\n required: false,\n allowedValueTypeName: BuildStepInputValueTypeName.STRING,\n }),\n ],\n outputProviders: [\n BuildStepOutput.createProvider({\n id: 'cache_hit',\n required: false,\n }),\n ],\n fn: async (stepsCtx, { env, inputs, outputs }) => {\n const { logger } = stepsCtx;\n\n try {\n if (stepsCtx.global.staticContext.job.platform) {\n logger.error('Caches are not supported in build jobs yet.');\n return;\n }\n\n const paths = z\n .array(z.string())\n .parse(((inputs.path.value ?? '') as string).split(/[\\r\\n]+/))\n .filter((path) => path.length > 0);\n const key = z.string().parse(inputs.key.value);\n const restoreKeys = z\n .array(z.string())\n .parse(((inputs.restore_keys.value ?? '') as string).split(/[\\r\\n]+/))\n .filter((key) => key !== '');\n\n const jobId = nullthrows(env.EAS_BUILD_ID, 'EAS_BUILD_ID is not set');\n const robotAccessToken = nullthrows(\n stepsCtx.global.staticContext.job.secrets?.robotAccessToken,\n 'robotAccessToken is not set'\n );\n\n const { archivePath, matchedKey } = await downloadCacheAsync({\n logger,\n jobId,\n expoApiServerURL: stepsCtx.global.staticContext.expoApiServerURL,\n robotAccessToken,\n paths,\n key,\n keyPrefixes: restoreKeys,\n platform: stepsCtx.global.staticContext.job.platform,\n });\n\n const { size } = await fs.promises.stat(archivePath);\n logger.info(`Downloaded cache archive from ${archivePath} (${formatBytes(size)}).`);\n\n await decompressCacheAsync({\n archivePath,\n workingDirectory: stepsCtx.workingDirectory,\n verbose: true,\n logger,\n });\n\n outputs.cache_hit.set(`${matchedKey === key}`);\n } catch (error) {\n logger.error({ err: error }, 'Failed to restore cache');\n }\n },\n });\n}\n\nexport async function downloadCacheAsync({\n logger,\n jobId,\n expoApiServerURL,\n robotAccessToken,\n paths,\n key,\n keyPrefixes,\n platform,\n}: {\n logger: bunyan;\n jobId: string;\n expoApiServerURL: string;\n robotAccessToken: string;\n paths: string[];\n key: string;\n keyPrefixes: string[];\n platform: Platform | undefined;\n}): Promise<{ archivePath: string; matchedKey: string }> {\n const routerURL = platform ? 'v2/turtle-builds/caches/download' : 'v2/turtle-caches/download';\n\n try {\n const response = await turtleFetch(new URL(routerURL, expoApiServerURL).toString(), 'POST', {\n json: platform\n ? {\n buildId: jobId,\n key,\n version: getCacheVersion(paths),\n keyPrefixes,\n }\n : {\n jobRunId: jobId,\n key,\n version: getCacheVersion(paths),\n keyPrefixes,\n },\n headers: {\n Authorization: `Bearer ${robotAccessToken}`,\n 'Content-Type': 'application/json',\n },\n // It's ok to retry POST caches/download, because we're only retrying signing a download URL.\n retries: 2,\n shouldThrowOnNotOk: true,\n });\n\n const result = await asyncResult(response.json());\n if (!result.ok) {\n throw new Error(`Unexpected response from server (${response.status}): ${result.reason}`);\n }\n\n const { matchedKey, downloadUrl } = result.value.data;\n\n logger.info(`Matched cache key: ${matchedKey}. Downloading...`);\n\n const downloadDestinationDirectory = await fs.promises.mkdtemp(\n path.join(os.tmpdir(), 'restore-cache-')\n );\n\n const downloadResponse = await retryOnDNSFailure(fetch)(downloadUrl);\n if (!downloadResponse.ok) {\n throw new Error(\n `Unexpected response from cache server (${downloadResponse.status}): ${downloadResponse.statusText}`\n );\n }\n\n // URL may contain percent-encoded characters, e.g. my%20file.apk\n // this replaces all non-alphanumeric characters (excluding dot) with underscore\n const archiveFilename = path\n .basename(new URL(downloadUrl).pathname)\n .replace(/([^a-z0-9.-]+)/gi, '_');\n const archivePath = path.join(downloadDestinationDirectory, archiveFilename);\n\n await streamPipeline(downloadResponse.body, fs.createWriteStream(archivePath));\n\n return { archivePath, matchedKey };\n } catch (err: any) {\n if (err instanceof TurtleFetchError && err.response.status !== 404) {\n const textResult = await asyncResult(err.response.text());\n throw new Error(\n `Unexpected response from server (${err.response.status}): ${textResult.value}`\n );\n }\n throw err;\n }\n}\n\nexport async function downloadPublicCacheAsync({\n logger,\n expoApiServerURL,\n robotAccessToken,\n paths,\n platform,\n}: {\n logger: bunyan;\n expoApiServerURL: string;\n robotAccessToken: string;\n paths: string[];\n platform: Platform;\n}): Promise<{ archivePath: string; matchedKey: string }> {\n const routerURL = 'v2/public-turtle-caches/download';\n const key = PUBLIC_CACHE_KEY_PREFIX_BY_PLATFORM[platform];\n\n try {\n const response = await turtleFetch(new URL(routerURL, expoApiServerURL).toString(), 'POST', {\n json: {\n key,\n version: getCacheVersion(paths),\n keyPrefixes: [key],\n },\n headers: {\n Authorization: `Bearer ${robotAccessToken}`,\n 'Content-Type': 'application/json',\n },\n retries: 2,\n shouldThrowOnNotOk: true,\n });\n\n const result = await asyncResult(response.json());\n if (!result.ok) {\n throw new Error(`Unexpected response from server (${response.status}): ${result.reason}`);\n }\n\n const { matchedKey, downloadUrl } = result.value.data;\n\n logger.info(`Matched public cache key: ${matchedKey}. Downloading...`);\n\n const downloadDestinationDirectory = await fs.promises.mkdtemp(\n path.join(os.tmpdir(), 'restore-cache-')\n );\n\n const downloadResponse = await retryOnDNSFailure(fetch)(downloadUrl);\n if (!downloadResponse.ok) {\n throw new Error(\n `Unexpected response from cache server (${downloadResponse.status}): ${downloadResponse.statusText}`\n );\n }\n\n const archiveFilename = path\n .basename(new URL(downloadUrl).pathname)\n .replace(/([^a-z0-9.-]+)/gi, '_');\n const archivePath = path.join(downloadDestinationDirectory, archiveFilename);\n\n await streamPipeline(downloadResponse.body, fs.createWriteStream(archivePath));\n\n return { archivePath, matchedKey };\n } catch (err: any) {\n if (err instanceof TurtleFetchError && err.response.status !== 404) {\n const textResult = await asyncResult(err.response.text());\n throw new Error(\n `Unexpected response from server (${err.response.status}): ${textResult.value}`\n );\n }\n throw err;\n }\n}\n\nexport async function decompressCacheAsync({\n archivePath,\n workingDirectory,\n verbose,\n logger,\n}: {\n archivePath: string;\n workingDirectory: string;\n verbose: boolean;\n logger: bunyan;\n}): Promise<void> {\n if (verbose) {\n logger.info(`Extracting cache to ${workingDirectory}:`);\n }\n\n // First, extract everything to the working directory\n const fileHandle = await fs.promises.open(archivePath, 'r');\n const extractedFiles: string[] = [];\n\n await streamPipeline(\n fileHandle.createReadStream(),\n tar.extract({\n cwd: workingDirectory,\n onwarn: (code, message, data) => {\n logger.warn({ code, data }, message);\n },\n preservePaths: true,\n onReadEntry: (entry) => {\n extractedFiles.push(entry.path);\n if (verbose) {\n logger.info(`- ${entry.path}`);\n }\n },\n })\n );\n\n // Handle absolute paths that were prefixed with __absolute__\n for (const extractedPath of extractedFiles) {\n if (extractedPath.startsWith('__absolute__/')) {\n const originalAbsolutePath = extractedPath.slice('__absolute__'.length);\n const currentPath = path.join(workingDirectory, extractedPath);\n\n try {\n // Ensure the target directory exists\n await fs.promises.mkdir(path.dirname(originalAbsolutePath), { recursive: true });\n\n // Move the file to its original absolute location\n await fs.promises.rename(currentPath, originalAbsolutePath);\n\n if (verbose) {\n logger.info(`Moved ${extractedPath} to ${originalAbsolutePath}`);\n }\n } catch (error) {\n logger.warn(`Failed to restore absolute path ${originalAbsolutePath}: ${error}`);\n }\n }\n }\n\n // Clean up any remaining __absolute__ directories\n const absoluteDir = path.join(workingDirectory, '__absolute__');\n if (\n await fs.promises\n .access(absoluteDir)\n .then(() => true)\n .catch(() => false)\n ) {\n await fs.promises.rm(absoluteDir, { recursive: true, force: true });\n }\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import { BuildFunction } from '@expo/steps';
2
+ import { Platform } from '@expo/eas-build-job';
3
+ import { bunyan } from '@expo/logger';
4
+ export declare function createSaveBuildCacheFunction(evictUsedBefore: Date): BuildFunction;
5
+ export declare function saveCcacheAsync({ logger, workingDirectory, platform, evictUsedBefore, env, secrets, }: {
6
+ logger: bunyan;
7
+ workingDirectory: string;
8
+ platform: Platform;
9
+ evictUsedBefore: Date;
10
+ env: Record<string, string | undefined>;
11
+ secrets?: {
12
+ robotAccessToken?: string;
13
+ };
14
+ }): Promise<void>;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createSaveBuildCacheFunction = createSaveBuildCacheFunction;
7
+ exports.saveCcacheAsync = saveCcacheAsync;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const steps_1 = require("@expo/steps");
10
+ const eas_build_job_1 = require("@expo/eas-build-job");
11
+ const results_1 = require("@expo/results");
12
+ const nullthrows_1 = __importDefault(require("nullthrows"));
13
+ const cacheKey_1 = require("../../utils/cacheKey");
14
+ const saveCache_1 = require("./saveCache");
15
+ function createSaveBuildCacheFunction(evictUsedBefore) {
16
+ return new steps_1.BuildFunction({
17
+ namespace: 'eas',
18
+ id: 'save_build_cache',
19
+ name: 'Save Cache',
20
+ inputProviders: [
21
+ steps_1.BuildStepInput.createProvider({
22
+ id: 'platform',
23
+ required: false,
24
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
25
+ }),
26
+ ],
27
+ fn: async (stepCtx, { env, inputs }) => {
28
+ var _a;
29
+ const { logger } = stepCtx;
30
+ const workingDirectory = stepCtx.workingDirectory;
31
+ const platform = (_a = inputs.platform.value) !== null && _a !== void 0 ? _a : stepCtx.global.staticContext.job.platform;
32
+ if (!platform || ![eas_build_job_1.Platform.ANDROID, eas_build_job_1.Platform.IOS].includes(platform)) {
33
+ throw new Error(`Unsupported platform: ${platform}. Platform must be "${eas_build_job_1.Platform.ANDROID}" or "${eas_build_job_1.Platform.IOS}"`);
34
+ }
35
+ await saveCcacheAsync({
36
+ logger,
37
+ workingDirectory,
38
+ platform,
39
+ evictUsedBefore,
40
+ env,
41
+ secrets: stepCtx.global.staticContext.job.secrets,
42
+ });
43
+ },
44
+ });
45
+ }
46
+ async function saveCcacheAsync({ logger, workingDirectory, platform, evictUsedBefore, env, secrets, }) {
47
+ const enabled = env.EAS_SAVE_CACHE === '1' || (env.EAS_USE_CACHE === '1' && env.EAS_SAVE_CACHE !== '0');
48
+ if (!enabled) {
49
+ return;
50
+ }
51
+ try {
52
+ const cacheKey = await (0, cacheKey_1.generateDefaultBuildCacheKeyAsync)(workingDirectory, platform);
53
+ logger.info(`Saving cache key: ${cacheKey}`);
54
+ const jobId = (0, nullthrows_1.default)(env.EAS_BUILD_ID, 'EAS_BUILD_ID is not set');
55
+ const robotAccessToken = (0, nullthrows_1.default)(secrets === null || secrets === void 0 ? void 0 : secrets.robotAccessToken, 'Robot access token is required for cache operations');
56
+ const expoApiServerURL = (0, nullthrows_1.default)(env.__API_SERVER_URL, '__API_SERVER_URL is not set');
57
+ const cachePath = (0, cacheKey_1.getCcachePath)(env);
58
+ // Cache size can blow up over time over many builds, so evict stale files
59
+ // and only upload what was used within this build's time window
60
+ const evictWindow = Math.floor((Date.now() - evictUsedBefore.getTime()) / 1000);
61
+ logger.info('Pruning cache...');
62
+ await (0, results_1.asyncResult)((0, steps_1.spawnAsync)('ccache', ['--evict-older-than', evictWindow + 's'], {
63
+ env,
64
+ logger,
65
+ stdio: 'pipe',
66
+ }));
67
+ logger.info('Preparing cache archive...');
68
+ const { archivePath } = await (0, saveCache_1.compressCacheAsync)({
69
+ paths: [cachePath],
70
+ workingDirectory,
71
+ verbose: env.EXPO_DEBUG === '1',
72
+ logger,
73
+ });
74
+ const { size } = await fs_1.default.promises.stat(archivePath);
75
+ await (0, saveCache_1.uploadCacheAsync)({
76
+ logger,
77
+ jobId,
78
+ expoApiServerURL,
79
+ robotAccessToken,
80
+ archivePath,
81
+ key: cacheKey,
82
+ paths: [cachePath],
83
+ size,
84
+ platform,
85
+ });
86
+ }
87
+ catch (err) {
88
+ logger.error({ err }, 'Failed to save cache');
89
+ }
90
+ }
91
+ //# sourceMappingURL=saveBuildCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"saveBuildCache.js","sourceRoot":"","sources":["../../../src/steps/functions/saveBuildCache.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'fs';\n\nimport {\n BuildFunction,\n BuildStepInput,\n BuildStepInputValueTypeName,\n spawnAsync,\n} from '@expo/steps';\nimport { Platform } from '@expo/eas-build-job';\nimport { bunyan } from '@expo/logger';\nimport { asyncResult } from '@expo/results';\nimport nullthrows from 'nullthrows';\n\nimport { generateDefaultBuildCacheKeyAsync, getCcachePath } from '../../utils/cacheKey';\n\nimport { compressCacheAsync, uploadCacheAsync } from './saveCache';\n\nexport function createSaveBuildCacheFunction(evictUsedBefore: Date): BuildFunction {\n return new BuildFunction({\n namespace: 'eas',\n id: 'save_build_cache',\n name: 'Save Cache',\n inputProviders: [\n BuildStepInput.createProvider({\n id: 'platform',\n required: false,\n allowedValueTypeName: BuildStepInputValueTypeName.STRING,\n }),\n ],\n fn: async (stepCtx, { env, inputs }) => {\n const { logger } = stepCtx;\n const workingDirectory = stepCtx.workingDirectory;\n const platform =\n (inputs.platform.value as Platform | undefined) ??\n stepCtx.global.staticContext.job.platform;\n if (!platform || ![Platform.ANDROID, Platform.IOS].includes(platform)) {\n throw new Error(\n `Unsupported platform: ${platform}. Platform must be \"${Platform.ANDROID}\" or \"${Platform.IOS}\"`\n );\n }\n\n await saveCcacheAsync({\n logger,\n workingDirectory,\n platform,\n evictUsedBefore,\n env,\n secrets: stepCtx.global.staticContext.job.secrets,\n });\n },\n });\n}\n\nexport async function saveCcacheAsync({\n logger,\n workingDirectory,\n platform,\n evictUsedBefore,\n env,\n secrets,\n}: {\n logger: bunyan;\n workingDirectory: string;\n platform: Platform;\n evictUsedBefore: Date;\n env: Record<string, string | undefined>;\n secrets?: { robotAccessToken?: string };\n}): Promise<void> {\n const enabled =\n env.EAS_SAVE_CACHE === '1' || (env.EAS_USE_CACHE === '1' && env.EAS_SAVE_CACHE !== '0');\n\n if (!enabled) {\n return;\n }\n\n try {\n const cacheKey = await generateDefaultBuildCacheKeyAsync(workingDirectory, platform);\n logger.info(`Saving cache key: ${cacheKey}`);\n\n const jobId = nullthrows(env.EAS_BUILD_ID, 'EAS_BUILD_ID is not set');\n const robotAccessToken = nullthrows(\n secrets?.robotAccessToken,\n 'Robot access token is required for cache operations'\n );\n const expoApiServerURL = nullthrows(env.__API_SERVER_URL, '__API_SERVER_URL is not set');\n const cachePath = getCcachePath(env);\n\n // Cache size can blow up over time over many builds, so evict stale files\n // and only upload what was used within this build's time window\n const evictWindow = Math.floor((Date.now() - evictUsedBefore.getTime()) / 1000);\n logger.info('Pruning cache...');\n await asyncResult(\n spawnAsync('ccache', ['--evict-older-than', evictWindow + 's'], {\n env,\n logger,\n stdio: 'pipe',\n })\n );\n\n logger.info('Preparing cache archive...');\n\n const { archivePath } = await compressCacheAsync({\n paths: [cachePath],\n workingDirectory,\n verbose: env.EXPO_DEBUG === '1',\n logger,\n });\n\n const { size } = await fs.promises.stat(archivePath);\n\n await uploadCacheAsync({\n logger,\n jobId,\n expoApiServerURL,\n robotAccessToken,\n archivePath,\n key: cacheKey,\n paths: [cachePath],\n size,\n platform,\n });\n } catch (err) {\n logger.error({ err }, 'Failed to save cache');\n }\n}\n"]}
@@ -13,6 +13,17 @@ export declare function uploadCacheAsync({ logger, jobId, expoApiServerURL, robo
13
13
  size: number;
14
14
  platform: Platform | undefined;
15
15
  }): Promise<void>;
16
+ export declare function uploadPublicCacheAsync({ logger, jobId, expoApiServerURL, robotAccessToken, paths, key, archivePath, size, }: {
17
+ logger: bunyan;
18
+ jobId: string;
19
+ expoApiServerURL: string;
20
+ robotAccessToken: string;
21
+ paths: string[];
22
+ key: string;
23
+ archivePath: string;
24
+ size: number;
25
+ platform: Platform | undefined;
26
+ }): Promise<void>;
16
27
  export declare function compressCacheAsync({ paths, workingDirectory, verbose, logger, }: {
17
28
  paths: string[];
18
29
  workingDirectory: string;
@@ -28,6 +28,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.createSaveCacheFunction = createSaveCacheFunction;
30
30
  exports.uploadCacheAsync = uploadCacheAsync;
31
+ exports.uploadPublicCacheAsync = uploadPublicCacheAsync;
31
32
  exports.compressCacheAsync = compressCacheAsync;
32
33
  const fs_1 = __importDefault(require("fs"));
33
34
  const os_1 = __importDefault(require("os"));
@@ -77,17 +78,32 @@ function createSaveCacheFunction() {
77
78
  logger,
78
79
  });
79
80
  const { size } = await fs_1.default.promises.stat(archivePath);
80
- await uploadCacheAsync({
81
- logger,
82
- jobId,
83
- expoApiServerURL: stepsCtx.global.staticContext.expoApiServerURL,
84
- robotAccessToken,
85
- archivePath,
86
- key,
87
- paths,
88
- size,
89
- platform: stepsCtx.global.staticContext.job.platform,
90
- });
81
+ if (env.EAS_PUBLIC_CACHE === '1') {
82
+ await uploadPublicCacheAsync({
83
+ logger,
84
+ jobId,
85
+ expoApiServerURL: stepsCtx.global.staticContext.expoApiServerURL,
86
+ robotAccessToken,
87
+ archivePath,
88
+ key,
89
+ paths,
90
+ size,
91
+ platform: stepsCtx.global.staticContext.job.platform,
92
+ });
93
+ }
94
+ else {
95
+ await uploadCacheAsync({
96
+ logger,
97
+ jobId,
98
+ expoApiServerURL: stepsCtx.global.staticContext.expoApiServerURL,
99
+ robotAccessToken,
100
+ archivePath,
101
+ key,
102
+ paths,
103
+ size,
104
+ platform: stepsCtx.global.staticContext.job.platform,
105
+ });
106
+ }
91
107
  }
92
108
  catch (error) {
93
109
  logger.error({ err: error }, 'Failed to create cache');
@@ -144,6 +160,45 @@ async function uploadCacheAsync({ logger, jobId, expoApiServerURL, robotAccessTo
144
160
  }
145
161
  logger.info(`Uploaded cache archive to ${archivePath} (${(0, artifacts_1.formatBytes)(size)}).`);
146
162
  }
163
+ async function uploadPublicCacheAsync({ logger, jobId, expoApiServerURL, robotAccessToken, paths, key, archivePath, size, }) {
164
+ const routerPath = 'v2/public-turtle-caches/upload-sessions';
165
+ const response = await (0, retryOnDNSFailure_1.retryOnDNSFailure)(node_fetch_1.default)(new URL(routerPath, expoApiServerURL), {
166
+ method: 'POST',
167
+ body: JSON.stringify({
168
+ jobRunId: jobId,
169
+ key,
170
+ version: (0, cache_1.getCacheVersion)(paths),
171
+ size,
172
+ }),
173
+ headers: {
174
+ Authorization: `Bearer ${robotAccessToken}`,
175
+ 'Content-Type': 'application/json',
176
+ },
177
+ });
178
+ if (!response.ok) {
179
+ if (response.status === 409) {
180
+ logger.info(`Cache ${key} already exists, skipping upload`);
181
+ return;
182
+ }
183
+ const textResult = await (0, results_1.asyncResult)(response.text());
184
+ throw new Error(`Unexpected response from server (${response.status}): ${textResult.value}`);
185
+ }
186
+ const result = await (0, results_1.asyncResult)(response.json());
187
+ if (!result.ok) {
188
+ throw new Error(`Unexpected response from server (${response.status}): ${result.reason}`);
189
+ }
190
+ const { url, headers } = result.value.data;
191
+ logger.info(`Uploading public cache...`);
192
+ const uploadResponse = await (0, retryOnDNSFailure_1.retryOnDNSFailure)(node_fetch_1.default)(new URL(url), {
193
+ method: 'PUT',
194
+ headers,
195
+ body: fs_1.default.createReadStream(archivePath),
196
+ });
197
+ if (!uploadResponse.ok) {
198
+ throw new Error(`Unexpected response from cache server (${uploadResponse.status}): ${uploadResponse.statusText}`);
199
+ }
200
+ logger.info(`Uploaded cache archive to ${archivePath} (${(0, artifacts_1.formatBytes)(size)}).`);
201
+ }
147
202
  async function compressCacheAsync({ paths, workingDirectory, verbose, logger, }) {
148
203
  const archiveDestinationDirectory = await fs_1.default.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'save-cache-'));
149
204
  // Process and normalize all paths