@stim-cli/cache 1.20.1 → 1.21.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/README.md +23 -5
- package/dist/index.d.mts +31 -4
- package/dist/index.mjs +118 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@ front of it.
|
|
|
6
6
|
|
|
7
7
|
The local filesystem is always the first tier. A project can add one optional
|
|
8
8
|
second tier by pointing `cache.provider` at a module that implements this
|
|
9
|
-
contract.
|
|
10
|
-
|
|
9
|
+
contract. `@stim-cli/cache-s3` provides an S3-compatible second tier, including
|
|
10
|
+
Cloudflare R2.
|
|
11
11
|
|
|
12
12
|
If Stim is not installed globally, replace `stim` with `npx stim`.
|
|
13
13
|
|
|
@@ -30,6 +30,12 @@ provider, and lower-precedence layers that name the same provider resolved from
|
|
|
30
30
|
the same directory; options written for another provider are ignored. Keep secrets out of
|
|
31
31
|
committed settings; read them from the environment or from machine settings.
|
|
32
32
|
|
|
33
|
+
## S3-compatible storage
|
|
34
|
+
|
|
35
|
+
Install the separate [`@stim-cli/cache-s3`](../cache-s3/README.md) provider and
|
|
36
|
+
select it through `cache.provider`. The AWS SDK and archive dependencies belong
|
|
37
|
+
to that opt-in package; `@stim-cli/cache` has no runtime dependencies.
|
|
38
|
+
|
|
33
39
|
## Writing a provider
|
|
34
40
|
|
|
35
41
|
```js
|
|
@@ -53,10 +59,22 @@ export async function createCacheProvider({ projectRoot, options }) {
|
|
|
53
59
|
}
|
|
54
60
|
```
|
|
55
61
|
|
|
56
|
-
A provider implements one or
|
|
62
|
+
A provider implements one or more capabilities. It owns transport,
|
|
57
63
|
serialization, archive format, authentication, and remote retention. Stim owns
|
|
58
64
|
fingerprints, cache keys, and local artifact paths.
|
|
59
65
|
|
|
66
|
+
The optional `files` capability stores opaque complete files with
|
|
67
|
+
`store({ key, sourcePath, overwrite, signal })` and downloads them with
|
|
68
|
+
`resolve({ key, destinationDir, signal })`. A hit returns a path inside
|
|
69
|
+
`destinationDir`; a miss returns `null` and leaves it empty. The CLI consumes
|
|
70
|
+
the `metro` and `builds` adapters. Native Gradle APK caching consumes `files`
|
|
71
|
+
directly to share the APK together with its verification receipt; a files-only
|
|
72
|
+
provider can support this flow. An optional
|
|
73
|
+
`stat({ key, signal })` returns `{ size }` or `null` without downloading;
|
|
74
|
+
consumers still verify downloaded bytes. `createFilesystemFileCache(directory)`
|
|
75
|
+
implements the same contract in an owned local directory without dependencies.
|
|
76
|
+
It publishes atomically and materializes downloads in the requested scratch directory.
|
|
77
|
+
|
|
60
78
|
`metro.get` returns the stored value or `null`.
|
|
61
79
|
|
|
62
80
|
`builds.resolve` returns an existing path to the artifact, or `null` for a
|
|
@@ -112,8 +130,8 @@ const results = await runCacheProviderContract({
|
|
|
112
130
|
```
|
|
113
131
|
|
|
114
132
|
`cacheProviderContractChecks()` returns the same checks as individual cases for
|
|
115
|
-
a test runner that reports each one separately. Both helpers
|
|
116
|
-
|
|
133
|
+
a test runner that reports each one separately. Both helpers check advertised
|
|
134
|
+
Metro and build capabilities, bound every check with a deadline, and
|
|
117
135
|
verify that a call settles once its `AbortSignal` aborts.
|
|
118
136
|
|
|
119
137
|
## Budgets
|
package/dist/index.d.mts
CHANGED
|
@@ -76,7 +76,28 @@ interface BuildCacheCapability {
|
|
|
76
76
|
resolve(input: BuildResolveInput): string | null | Promise<string | null>;
|
|
77
77
|
store(input: BuildStoreInput): string | null | void | Promise<string | null | void>;
|
|
78
78
|
}
|
|
79
|
+
interface FileCacheResolveInput {
|
|
80
|
+
key: string;
|
|
81
|
+
destinationDir: string;
|
|
82
|
+
signal: AbortSignal;
|
|
83
|
+
}
|
|
84
|
+
interface FileCacheStoreInput {
|
|
85
|
+
key: string;
|
|
86
|
+
sourcePath: string;
|
|
87
|
+
overwrite: boolean;
|
|
88
|
+
signal: AbortSignal;
|
|
89
|
+
}
|
|
90
|
+
/** Opaque, complete files. Downloads are materialized inside destinationDir; a miss leaves it empty. */
|
|
91
|
+
interface FileCacheCapability {
|
|
92
|
+
resolve(input: FileCacheResolveInput): Promise<string | null>;
|
|
93
|
+
store(input: FileCacheStoreInput): Promise<void>;
|
|
94
|
+
/** Checks presence without downloading; the bytes still need verification when fetched. */
|
|
95
|
+
stat?(input: Pick<FileCacheResolveInput, "key" | "signal">): Promise<{
|
|
96
|
+
size: number;
|
|
97
|
+
} | null>;
|
|
98
|
+
}
|
|
79
99
|
interface CacheProvider {
|
|
100
|
+
files?: FileCacheCapability;
|
|
80
101
|
metro?: MetroCacheCapability;
|
|
81
102
|
builds?: BuildCacheCapability;
|
|
82
103
|
}
|
|
@@ -108,7 +129,7 @@ declare function loadCacheProvider({ projectRoot, config, timeoutMs }: {
|
|
|
108
129
|
config?: CacheProviderConfig | null;
|
|
109
130
|
timeoutMs?: number;
|
|
110
131
|
}): Promise<LoadCacheProviderResult>;
|
|
111
|
-
declare function callWithTimeout<T>(call: (signal: AbortSignal) => T | Promise<T>, timeoutMs: number): Promise<ProviderCallResult<T>>;
|
|
132
|
+
declare function callWithTimeout<T>(call: (signal: AbortSignal) => T | Promise<T>, timeoutMs: number, signal?: AbortSignal): Promise<ProviderCallResult<T>>;
|
|
112
133
|
declare function createWarnOnce(emit: (message: string) => void): WarnOnce;
|
|
113
134
|
declare function cacheProviderEnv(config: CacheProviderConfig | null): string;
|
|
114
135
|
/**
|
|
@@ -196,6 +217,7 @@ interface ResolveTieredBuildOptions {
|
|
|
196
217
|
skipRead?: boolean;
|
|
197
218
|
warn?: WarnOnce;
|
|
198
219
|
timeoutMs?: number;
|
|
220
|
+
signal?: AbortSignal;
|
|
199
221
|
}
|
|
200
222
|
interface StoreTieredBuildOptions {
|
|
201
223
|
local: BuildCacheCapability;
|
|
@@ -205,14 +227,15 @@ interface StoreTieredBuildOptions {
|
|
|
205
227
|
overwrite: boolean;
|
|
206
228
|
warn?: WarnOnce;
|
|
207
229
|
timeoutMs?: number;
|
|
230
|
+
signal?: AbortSignal;
|
|
208
231
|
}
|
|
209
232
|
interface TieredBuildStoreResult {
|
|
210
233
|
localPath: string | null;
|
|
211
234
|
providerUpload: Promise<ProviderCallResult<void>> | null;
|
|
212
235
|
providerName: string | null;
|
|
213
236
|
}
|
|
214
|
-
declare function resolveTieredBuild({ local, loadProvider, target, destinationDir, ensureDestination, skipRead, warn, timeoutMs }: ResolveTieredBuildOptions): Promise<TieredBuildResolution | null>;
|
|
215
|
-
declare function storeTieredBuild({ local, loadProvider, target, sourcePath, overwrite, warn, timeoutMs }: StoreTieredBuildOptions): Promise<TieredBuildStoreResult>;
|
|
237
|
+
declare function resolveTieredBuild({ local, loadProvider, target, destinationDir, ensureDestination, skipRead, warn, timeoutMs, signal }: ResolveTieredBuildOptions): Promise<TieredBuildResolution | null>;
|
|
238
|
+
declare function storeTieredBuild({ local, loadProvider, target, sourcePath, overwrite, warn, timeoutMs, signal }: StoreTieredBuildOptions): Promise<TieredBuildStoreResult>;
|
|
216
239
|
//#endregion
|
|
217
240
|
//#region contract.d.ts
|
|
218
241
|
interface CacheContractCheck {
|
|
@@ -249,4 +272,8 @@ declare const CACHE_CONTRACT_CHECK_TIMEOUT_MS = 3e4;
|
|
|
249
272
|
declare function cacheProviderContractChecks(options: CacheContractOptions): CacheContractCheck[];
|
|
250
273
|
declare function runCacheProviderContract(options: CacheContractOptions | CacheContractModuleOptions): Promise<CacheContractResult[]>;
|
|
251
274
|
//#endregion
|
|
252
|
-
|
|
275
|
+
//#region files.d.ts
|
|
276
|
+
/** Opaque immutable files in a caller-owned directory, with atomic publication and cancellable downloads. */
|
|
277
|
+
declare function createFilesystemFileCache(directory: string): FileCacheCapability & Required<Pick<FileCacheCapability, "stat">>;
|
|
278
|
+
//#endregion
|
|
279
|
+
export { BUILD_RESOLVE_TIMEOUT_ENV, BUILD_RESOLVE_TIMEOUT_MS, BUILD_UPLOAD_TIMEOUT_ENV, BUILD_UPLOAD_TIMEOUT_MS, BuildCacheCapability, BuildCacheContext, BuildCacheTarget, BuildResolveInput, BuildStoreInput, CACHE_CONTRACT_CHECK_TIMEOUT_MS, CACHE_PROVIDER_API_VERSION, CACHE_PROVIDER_ENV, CACHE_PROVIDER_ENV_NONE, CacheContractCheck, CacheContractModuleOptions, CacheContractOptions, CacheContractResult, CacheProvider, CacheProviderConfig, CacheProviderModule, FileCacheCapability, FileCacheResolveInput, FileCacheStoreInput, LoadBuildProvider, LoadCacheProviderResult, METRO_READ_CONCURRENCY, METRO_READ_FAILURE_LIMIT, METRO_READ_TIMEOUT_ENV, METRO_READ_TIMEOUT_MS, METRO_UPLOAD_CONCURRENCY, METRO_UPLOAD_MAX_BYTES, METRO_UPLOAD_MAX_ITEMS, METRO_WRITE_TIMEOUT_ENV, METRO_WRITE_TIMEOUT_MS, MetroCacheCapability, MetroCacheContext, MetroCacheGetInput, MetroCacheSetInput, MetroCacheStore, MetroTierLimits, MetroTierTimeouts, PROVIDER_LOAD_TIMEOUT_ENV, PROVIDER_LOAD_TIMEOUT_MS, ProviderCallResult, ResolveTieredBuildOptions, StoreTieredBuildOptions, TieredBuildResolution, TieredBuildStoreResult, TieredMetroStore, TieredMetroStoreOptions, WarnOnce, buildResolveTimeoutMs, buildUploadTimeoutMs, cacheProviderConfigFromEnv, cacheProviderContractChecks, cacheProviderEnv, cacheProviderEnvIsSet, callWithTimeout, createFilesystemFileCache, createTieredMetroStore, createWarnOnce, loadCacheProvider, metroCapabilityFromStore, resolveTieredBuild, runCacheProviderContract, storeTieredBuild, timeoutFromEnv };
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { join, relative, sep } from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { randomBytes, randomUUID } from "node:crypto";
|
|
4
|
+
import { createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
6
|
+
import { link, lstat, mkdir, rename, rm } from "node:fs/promises";
|
|
7
|
+
import { pipeline } from "node:stream/promises";
|
|
6
8
|
//#region provider.ts
|
|
7
9
|
const CACHE_PROVIDER_API_VERSION = 1;
|
|
8
10
|
const CACHE_PROVIDER_ENV = "STIM_CACHE_PROVIDER_CONFIG";
|
|
@@ -31,11 +33,13 @@ function moduleCandidate(namespace) {
|
|
|
31
33
|
return "default" in namespace ? namespace.default : namespace;
|
|
32
34
|
}
|
|
33
35
|
function capabilityError(provider) {
|
|
36
|
+
const files = provider.files;
|
|
37
|
+
if (files !== void 0 && (typeof files?.resolve !== "function" || typeof files?.store !== "function")) return "the files capability must implement resolve() and store()";
|
|
34
38
|
const metro = provider.metro;
|
|
35
39
|
if (metro !== void 0 && (typeof metro?.get !== "function" || typeof metro?.set !== "function")) return "the metro capability must implement get() and set()";
|
|
36
40
|
const builds = provider.builds;
|
|
37
41
|
if (builds !== void 0 && (typeof builds?.resolve !== "function" || typeof builds?.store !== "function")) return "the builds capability must implement resolve() and store()";
|
|
38
|
-
if (metro === void 0 && builds === void 0) return "the provider advertises
|
|
42
|
+
if (metro === void 0 && builds === void 0 && files === void 0) return "the provider advertises no files, metro or builds capability";
|
|
39
43
|
return null;
|
|
40
44
|
}
|
|
41
45
|
async function loadCacheProvider({ projectRoot, config, timeoutMs = timeoutFromEnv(PROVIDER_LOAD_TIMEOUT_ENV, PROVIDER_LOAD_TIMEOUT_MS) }) {
|
|
@@ -107,8 +111,11 @@ async function importCacheProvider({ projectRoot, config, reference }) {
|
|
|
107
111
|
provider
|
|
108
112
|
};
|
|
109
113
|
}
|
|
110
|
-
async function callWithTimeout(call, timeoutMs) {
|
|
114
|
+
async function callWithTimeout(call, timeoutMs, signal) {
|
|
115
|
+
signal?.throwIfAborted();
|
|
111
116
|
const controller = new AbortController();
|
|
117
|
+
const abort = () => controller.abort(signal?.reason);
|
|
118
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
112
119
|
let timer = null;
|
|
113
120
|
const expired = new Promise((resolve) => {
|
|
114
121
|
timer = setTimeout(() => {
|
|
@@ -117,9 +124,14 @@ async function callWithTimeout(call, timeoutMs) {
|
|
|
117
124
|
}, timeoutMs);
|
|
118
125
|
});
|
|
119
126
|
try {
|
|
120
|
-
const outcome = await Promise.race([Promise.resolve().then(() =>
|
|
127
|
+
const outcome = await Promise.race([Promise.resolve().then(() => {
|
|
128
|
+
signal?.throwIfAborted();
|
|
129
|
+
return call(controller.signal);
|
|
130
|
+
}).then((value) => ({ value }), (error) => ({ failed: reason(error) })), expired]);
|
|
131
|
+
signal?.throwIfAborted();
|
|
121
132
|
return outcome === "timeout" ? { timedOut: true } : outcome;
|
|
122
133
|
} finally {
|
|
134
|
+
signal?.removeEventListener("abort", abort);
|
|
123
135
|
if (timer) clearTimeout(timer);
|
|
124
136
|
}
|
|
125
137
|
}
|
|
@@ -386,9 +398,10 @@ function buildResolveTimeoutMs(env) {
|
|
|
386
398
|
function buildUploadTimeoutMs(env) {
|
|
387
399
|
return timeoutFromEnv(BUILD_UPLOAD_TIMEOUT_ENV, BUILD_UPLOAD_TIMEOUT_MS, env);
|
|
388
400
|
}
|
|
389
|
-
async function providerTier(loadProvider, warn) {
|
|
401
|
+
async function providerTier(loadProvider, warn, signal) {
|
|
390
402
|
if (!loadProvider) return null;
|
|
391
403
|
const loaded = await loadProvider();
|
|
404
|
+
signal?.throwIfAborted();
|
|
392
405
|
if (loaded?.unavailable) {
|
|
393
406
|
warn("provider-load", `provider not usable (${loaded.name ?? "the cache provider"}): ${loaded.unavailable}; using local cache`);
|
|
394
407
|
return null;
|
|
@@ -404,26 +417,29 @@ function neverAborted$1() {
|
|
|
404
417
|
return new AbortController().signal;
|
|
405
418
|
}
|
|
406
419
|
function ignore() {}
|
|
407
|
-
async function resolveTieredBuild({ local, loadProvider = null, target, destinationDir, ensureDestination, skipRead = false, warn = ignore, timeoutMs = buildResolveTimeoutMs() }) {
|
|
420
|
+
async function resolveTieredBuild({ local, loadProvider = null, target, destinationDir, ensureDestination, skipRead = false, warn = ignore, timeoutMs = buildResolveTimeoutMs(), signal = neverAborted$1() }) {
|
|
421
|
+
signal.throwIfAborted();
|
|
408
422
|
if (skipRead) return null;
|
|
409
423
|
const hit = await local.resolve({
|
|
410
424
|
...target,
|
|
411
425
|
destinationDir,
|
|
412
|
-
signal
|
|
426
|
+
signal
|
|
413
427
|
});
|
|
428
|
+
signal.throwIfAborted();
|
|
414
429
|
if (hit) return {
|
|
415
430
|
path: hit,
|
|
416
431
|
tier: "local"
|
|
417
432
|
};
|
|
418
|
-
const tier = await providerTier(loadProvider, warn);
|
|
433
|
+
const tier = await providerTier(loadProvider, warn, signal);
|
|
434
|
+
signal.throwIfAborted();
|
|
419
435
|
if (!tier) return null;
|
|
420
436
|
const { capability: provider, name: label } = tier;
|
|
421
437
|
ensureDestination?.(destinationDir);
|
|
422
|
-
const outcome = await callWithTimeout((
|
|
438
|
+
const outcome = await callWithTimeout((childSignal) => provider.resolve({
|
|
423
439
|
...target,
|
|
424
440
|
destinationDir,
|
|
425
|
-
signal
|
|
426
|
-
}), timeoutMs);
|
|
441
|
+
signal: childSignal
|
|
442
|
+
}), timeoutMs, signal);
|
|
427
443
|
if (outcome.timedOut) {
|
|
428
444
|
warn("provider-resolve", `${label} did not answer within ${timeoutMs}ms; building instead`);
|
|
429
445
|
return null;
|
|
@@ -444,11 +460,13 @@ async function resolveTieredBuild({ local, loadProvider = null, target, destinat
|
|
|
444
460
|
...target,
|
|
445
461
|
sourcePath: path,
|
|
446
462
|
overwrite: false,
|
|
447
|
-
signal
|
|
463
|
+
signal
|
|
448
464
|
}) || null;
|
|
449
465
|
} catch (error) {
|
|
466
|
+
signal.throwIfAborted();
|
|
450
467
|
warn("provider-backfill", `a ${label} hit could not be stored locally: ${String(error?.message || error)}`);
|
|
451
468
|
}
|
|
469
|
+
signal.throwIfAborted();
|
|
452
470
|
return {
|
|
453
471
|
path: stored || path,
|
|
454
472
|
tier: "provider",
|
|
@@ -456,29 +474,34 @@ async function resolveTieredBuild({ local, loadProvider = null, target, destinat
|
|
|
456
474
|
storedLocally: Boolean(stored)
|
|
457
475
|
};
|
|
458
476
|
}
|
|
459
|
-
async function storeTieredBuild({ local, loadProvider = null, target, sourcePath, overwrite, warn = ignore, timeoutMs = buildUploadTimeoutMs() }) {
|
|
477
|
+
async function storeTieredBuild({ local, loadProvider = null, target, sourcePath, overwrite, warn = ignore, timeoutMs = buildUploadTimeoutMs(), signal = neverAborted$1() }) {
|
|
478
|
+
signal.throwIfAborted();
|
|
460
479
|
const localPath = await local.store({
|
|
461
480
|
...target,
|
|
462
481
|
sourcePath,
|
|
463
482
|
overwrite,
|
|
464
|
-
signal
|
|
483
|
+
signal
|
|
465
484
|
}) || null;
|
|
466
|
-
|
|
485
|
+
signal.throwIfAborted();
|
|
486
|
+
const tier = await providerTier(loadProvider, warn, signal);
|
|
487
|
+
signal.throwIfAborted();
|
|
467
488
|
if (!tier) return {
|
|
468
489
|
localPath,
|
|
469
490
|
providerUpload: null,
|
|
470
491
|
providerName: null
|
|
471
492
|
};
|
|
493
|
+
const providerUpload = callWithTimeout(async (childSignal) => {
|
|
494
|
+
await tier.capability.store({
|
|
495
|
+
...target,
|
|
496
|
+
sourcePath,
|
|
497
|
+
overwrite,
|
|
498
|
+
signal: childSignal
|
|
499
|
+
});
|
|
500
|
+
}, timeoutMs, signal);
|
|
501
|
+
providerUpload.catch(() => {});
|
|
472
502
|
return {
|
|
473
503
|
localPath,
|
|
474
|
-
providerUpload
|
|
475
|
-
await tier.capability.store({
|
|
476
|
-
...target,
|
|
477
|
-
sourcePath,
|
|
478
|
-
overwrite,
|
|
479
|
-
signal
|
|
480
|
-
});
|
|
481
|
-
}, timeoutMs),
|
|
504
|
+
providerUpload,
|
|
482
505
|
providerName: tier.name
|
|
483
506
|
};
|
|
484
507
|
}
|
|
@@ -886,4 +909,74 @@ async function withDeadline(work, timeoutMs) {
|
|
|
886
909
|
}
|
|
887
910
|
}
|
|
888
911
|
//#endregion
|
|
889
|
-
|
|
912
|
+
//#region files.ts
|
|
913
|
+
/** Opaque immutable files in a caller-owned directory, with atomic publication and cancellable downloads. */
|
|
914
|
+
function createFilesystemFileCache(directory) {
|
|
915
|
+
const entry = (key) => join(directory, createHash("sha256").update(key).digest("hex"));
|
|
916
|
+
const present = async ({ key, signal }) => {
|
|
917
|
+
signal.throwIfAborted();
|
|
918
|
+
try {
|
|
919
|
+
const info = await lstat(entry(key));
|
|
920
|
+
signal.throwIfAborted();
|
|
921
|
+
return info.isFile() ? { size: info.size } : null;
|
|
922
|
+
} catch (error) {
|
|
923
|
+
if ((error instanceof Error && "code" in error ? error.code : void 0) === "ENOENT") return null;
|
|
924
|
+
throw error;
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
return {
|
|
928
|
+
stat: present,
|
|
929
|
+
async resolve({ key, destinationDir, signal }) {
|
|
930
|
+
if (!await present({
|
|
931
|
+
key,
|
|
932
|
+
signal
|
|
933
|
+
})) return null;
|
|
934
|
+
const temporary = join(destinationDir, `${randomUUID()}.partial`);
|
|
935
|
+
const complete = join(destinationDir, `${randomUUID()}.cache`);
|
|
936
|
+
try {
|
|
937
|
+
await mkdir(destinationDir, {
|
|
938
|
+
recursive: true,
|
|
939
|
+
mode: 448
|
|
940
|
+
});
|
|
941
|
+
await pipeline(createReadStream(entry(key)), createWriteStream(temporary, {
|
|
942
|
+
flags: "wx",
|
|
943
|
+
mode: 384
|
|
944
|
+
}), { signal });
|
|
945
|
+
signal.throwIfAborted();
|
|
946
|
+
await rename(temporary, complete);
|
|
947
|
+
return complete;
|
|
948
|
+
} catch (error) {
|
|
949
|
+
signal.throwIfAborted();
|
|
950
|
+
if ((error instanceof Error && "code" in error ? error.code : void 0) === "ENOENT") return null;
|
|
951
|
+
throw error;
|
|
952
|
+
} finally {
|
|
953
|
+
await rm(temporary, { force: true });
|
|
954
|
+
}
|
|
955
|
+
},
|
|
956
|
+
async store({ key, sourcePath, overwrite, signal }) {
|
|
957
|
+
signal.throwIfAborted();
|
|
958
|
+
await mkdir(directory, {
|
|
959
|
+
recursive: true,
|
|
960
|
+
mode: 448
|
|
961
|
+
});
|
|
962
|
+
const temporary = join(directory, `${randomUUID()}.partial`);
|
|
963
|
+
try {
|
|
964
|
+
await pipeline(createReadStream(sourcePath), createWriteStream(temporary, {
|
|
965
|
+
flags: "wx",
|
|
966
|
+
mode: 384
|
|
967
|
+
}), { signal });
|
|
968
|
+
signal.throwIfAborted();
|
|
969
|
+
if (overwrite) await rename(temporary, entry(key));
|
|
970
|
+
else try {
|
|
971
|
+
await link(temporary, entry(key));
|
|
972
|
+
} catch (error) {
|
|
973
|
+
if ((error instanceof Error && "code" in error ? error.code : void 0) !== "EEXIST") throw error;
|
|
974
|
+
}
|
|
975
|
+
} finally {
|
|
976
|
+
await rm(temporary, { force: true });
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
//#endregion
|
|
982
|
+
export { BUILD_RESOLVE_TIMEOUT_ENV, BUILD_RESOLVE_TIMEOUT_MS, BUILD_UPLOAD_TIMEOUT_ENV, BUILD_UPLOAD_TIMEOUT_MS, CACHE_CONTRACT_CHECK_TIMEOUT_MS, CACHE_PROVIDER_API_VERSION, CACHE_PROVIDER_ENV, CACHE_PROVIDER_ENV_NONE, METRO_READ_CONCURRENCY, METRO_READ_FAILURE_LIMIT, METRO_READ_TIMEOUT_ENV, METRO_READ_TIMEOUT_MS, METRO_UPLOAD_CONCURRENCY, METRO_UPLOAD_MAX_BYTES, METRO_UPLOAD_MAX_ITEMS, METRO_WRITE_TIMEOUT_ENV, METRO_WRITE_TIMEOUT_MS, PROVIDER_LOAD_TIMEOUT_ENV, PROVIDER_LOAD_TIMEOUT_MS, buildResolveTimeoutMs, buildUploadTimeoutMs, cacheProviderConfigFromEnv, cacheProviderContractChecks, cacheProviderEnv, cacheProviderEnvIsSet, callWithTimeout, createFilesystemFileCache, createTieredMetroStore, createWarnOnce, loadCacheProvider, metroCapabilityFromStore, resolveTieredBuild, runCacheProviderContract, storeTieredBuild, timeoutFromEnv };
|