Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions docs/caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ ZUKE_REMOTE_CACHE_URL=https://cache.example.com ZUKE_REMOTE_CACHE_TOKEN=… ./zu
ZUKE_REMOTE_CACHE_DIR=/mnt/zuke-cache ./zuke ci
```

`ZUKE_REMOTE_CACHE_MAX_INFLATED_BYTES` raises the 2 GiB bound on what a fetched
artifact may decompress to, for whichever store the variables select.

That is also the precedence: a plain URL, then an S3 URL, then a directory, then
the Actions credentials. **`--no-remote-cache`** uses the local cache only for a
run (it skips both restore and upload); **`--no-cache`** still disables _both_
Expand Down Expand Up @@ -326,10 +329,13 @@ target:
the archive); when it lands under **`.git` or `.zuke`** — a restored git hook
runs on the next ordinary git command — with `.zuke/tools` the one exception,
since a provisioned toolchain is what a remote cache is for and the toolchain
re-verifies a restored binary against its recorded checksum before reusing it;
and when it falls **outside the paths the target declared with
`.outputs(...)`**. That last one is what stops an innocuous-looking
`deno.json` or lockfile riding along in an otherwise valid artifact.
re-verifies a restored binary against its recorded checksum before reusing it.
A `.git` _inside_ `.zuke/tools` is allowed too, because an SDK such as
Flutter's is a git checkout that runs `git` on itself; a store that could
write there could already replace the SDK's binaries; and when it falls
**outside the paths the target declared with `.outputs(...)`**. That last one
is what stops an innocuous-looking `deno.json` or lockfile riding along in an
otherwise valid artifact.
- **A restore never writes through a symbolic link.** The checks above are
lexical — they reason about the entry's _name_. An archive cannot carry a
link, but a workspace can already hold one at a declared output, and writing a
Expand All @@ -351,8 +357,12 @@ target:
outputs compress, so a legitimate archive inflates several times over and a
shared cap would refuse real artifacts. The wire bound defaults to 512 MiB and
is `maxArtifactBytes` on `HttpCacheStore`; the inflated bound defaults to 2
GiB. Because a tar entry costs at least its 512-byte header, bounding bytes
bounds entry count too.
GiB and is `maxInflatedBytes` on every built-in store, or
`ZUKE_REMOTE_CACHE_MAX_INFLATED_BYTES` (a byte count) for an env-selected one.
Raise it to cache a larger output such as a whole SDK; the restore holds the
inflated archive in memory, so it is also the memory ceiling. Because a tar
entry costs at least its 512-byte header, bounding bytes bounds entry count
too.
- **A refused archive is a cache miss, not a build failure.** The target
rebuilds and the refusal is reported as a warning — so whoever can write the
store cannot halt every build that reads it. That covers every refusal above,
Expand Down
53 changes: 45 additions & 8 deletions llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ async function archiveOutputs(outputs: readonly string[], host: OutputHost): Pro
Archive a target's `outputs` into a gzipped tar of their current contents,
each file with its permission bits so a binary restores executable. A
declared output that does not exist is skipped, as is anything under a
`.git` or `.zuke` directory other than `.zuke/tools`.
`.git` or `.zuke` directory, other than `.zuke/tools` and whatever it holds.

function assert(condition: unknown, message: string): asserts condition
Assert that `condition` is truthy, narrowing it for the rest of the scope.
Expand Down Expand Up @@ -337,6 +337,8 @@ function envCacheStore(readEnv: (name: string) => string | undefined): RemoteCac
on a GitHub Actions job that exposes its `ACTIONS_RUNTIME_TOKEN` and
`ACTIONS_RESULTS_URL` (the `actions-cache` input of the `zuke-build/zuke`
action), a {@link "./github_actions_cache.ts".GitHubActionsCacheStore}.
Whichever is chosen takes `ZUKE_REMOTE_CACHE_MAX_INFLATED_BYTES`, when set,
as its {@link RemoteCacheStore.maxInflatedBytes}.

function envStateStore(readEnv: (name: string) => string | undefined, host: StateHost): StateStore | undefined
Resolve a {@link StateStore} from the environment, or `undefined` when none is
Expand Down Expand Up @@ -866,7 +868,8 @@ async function restoreOutputs(artifact: Uint8Array, host: OutputHost, outputs?:

@param maxBytes
The most the artifact may decompress to before it is refused,
defaulting to 2 GiB. The bound is applied while decompressing, so a small
defaulting to 2 GiB; the executor passes the store's
{@link RemoteCacheStore.maxInflatedBytes}. The bound is applied while decompressing, so a small
archive that expands without limit is refused rather than buffered first.
It is larger than the bound a store puts on the compressed bytes because
outputs compress; both exist to stop memory exhaustion.
Expand Down Expand Up @@ -993,7 +996,10 @@ async function syncCiFiles(files: readonly CiFile[], options: CiSyncOptions): Pr
instead (so CI can fail when the committed config has drifted).

function tar(entries: TarEntry[]): Uint8Array
Create a `ustar` archive from the given entries (in order).
Create a `ustar` archive from the given entries (in order). A name or
symlink target past ustar's 100 bytes is carried by a pax extended header
(typeflag `'x'`) ahead of its member, which {@link untar} and every modern
`tar` read back.

function target(): TargetBuilder
Create a new, empty target builder.
Expand Down Expand Up @@ -1709,12 +1715,17 @@ class FileSystemBuildRegistry implements BuildRegistry
class FileSystemCacheStore implements RemoteCacheStore
A {@link RemoteCacheStore} backed by a shared or mounted directory.

constructor(dir: string)
constructor(dir: string, options: FileSystemCacheStoreOptions)
Build the store over a directory.

@param dir
The directory archives are read from and written to.

@param options
The restore bound.

readonly maxInflatedBytes?: number
The most an artifact may decompress to; see {@link RemoteCacheStore.maxInflatedBytes}.
get(key: string): Promise<Uint8Array | null>
Fetch the archived outputs stored under `key`, or `null` if there are none.
async put(key: string, artifact: Uint8Array): Promise<void>
Expand Down Expand Up @@ -1816,6 +1827,8 @@ class GitHubActionsCacheStore implements RemoteCacheStore

constructor(options: GitHubActionsCacheStoreOptions)
Build the store from the service URL, the runtime token, and the `fetch` seam.
readonly maxInflatedBytes?: number
The most an artifact may decompress to; see {@link RemoteCacheStore.maxInflatedBytes}.
async get(key: string): Promise<Uint8Array | null>
Fetch the archived outputs stored under `key`, or `null` if there are none.
async put(key: string, artifact: Uint8Array): Promise<void>
Expand Down Expand Up @@ -1878,7 +1891,9 @@ class HttpCacheStore implements RemoteCacheStore
decompresses to.

constructor(options: HttpCacheStoreOptions)
Build the store from its URL, optional token, size cap, and `fetch` seam.
Build the store from its URL, optional token, size caps, and `fetch` seam.
readonly maxInflatedBytes?: number
The most an artifact may decompress to; see {@link RemoteCacheStore.maxInflatedBytes}.
async get(key: string): Promise<Uint8Array | null>
Fetch the archived outputs stored under `key`, or `null` if there are none.
async put(key: string, artifact: Uint8Array): Promise<void>
Expand Down Expand Up @@ -2225,6 +2240,8 @@ class S3CacheStore implements RemoteCacheStore

constructor(options: S3CacheStoreOptions)
Build the store from the bucket URL, region, credentials and seams.
readonly maxInflatedBytes?: number
The most an artifact may decompress to; see {@link RemoteCacheStore.maxInflatedBytes}.
get(key: string): Promise<Uint8Array | null>
Fetch the archived outputs stored under `key`, or `null` if there are none.
put(key: string, artifact: Uint8Array): Promise<void>
Expand Down Expand Up @@ -3943,6 +3960,13 @@ interface FanOutOptions
env?: Record<string, string>
Environment variables set on every job (e.g. the remote-cache config).

interface FileSystemCacheStoreOptions
Configuration for a {@link FileSystemCacheStore}.

maxInflatedBytes?: number
The most a fetched artifact may decompress to, in bytes — see
{@link RemoteCacheStore.maxInflatedBytes}. Defaults to 2 GiB.

interface FileTasksApi
The shape of {@link FileTasks}.

Expand Down Expand Up @@ -4062,6 +4086,9 @@ interface GitHubActionsCacheStoreOptions
this store is written only by the repository's own workflows and the
outputs worth caching here are the big ones (a 1.5 GB SDK). What the bytes
decompress to is bounded separately, by `restoreOutputs`.
maxInflatedBytes?: number
The most a fetched artifact may decompress to, in bytes — see
{@link RemoteCacheStore.maxInflatedBytes}. Defaults to 2 GiB.

interface GlobOptions
Options for {@link glob}.
Expand Down Expand Up @@ -4129,6 +4156,9 @@ interface HttpCacheStoreOptions
outputs are genuinely larger; a refusal is a warned rebuild, not a build
failure. What the bytes decompress to is bounded separately, by
{@link restoreOutputs}.
maxInflatedBytes?: number
The most a fetched artifact may decompress to, in bytes — see
{@link RemoteCacheStore.maxInflatedBytes}. Defaults to 2 GiB.

interface HttpOptions
Options shared by the HTTP helpers.
Expand Down Expand Up @@ -4557,6 +4587,11 @@ interface RemoteCacheStore
point of view: the executor never fails a build because the store is
unreachable — it just rebuilds and, where it can, re-uploads.

readonly maxInflatedBytes?: number
The most an artifact from this store may decompress to, in bytes, before
{@link restoreOutputs} refuses it. Defaults to 2 GiB; raise it for a target
whose outputs are genuinely larger, such as a whole SDK. The restore holds
the inflated archive in memory, so the bound is also its memory ceiling.
get(key: string): Promise<Uint8Array | null>
Fetch the archived outputs stored under `key`, or `null` if there are none.
put(key: string, artifact: Uint8Array): Promise<void>
Expand Down Expand Up @@ -4995,6 +5030,9 @@ interface S3CacheStoreOptions
The most a fetched artifact may weigh on the wire, in bytes, before it is
refused. Defaults to 512 MiB — see
{@link "./remote_cache.ts".HttpCacheStoreOptions.maxArtifactBytes}.
maxInflatedBytes?: number
The most a fetched artifact may decompress to, in bytes — see
{@link RemoteCacheStore.maxInflatedBytes}. Defaults to 2 GiB.

interface ScheduleEntry
A scheduled trigger: a 5-field cron expression in an optional IANA timezone.
Expand Down Expand Up @@ -5123,12 +5161,11 @@ interface TarEntry
A single entry within a tar archive — a regular file or a symbolic link.

name: string
The entry's path inside the archive (≤ 100 bytes).
The entry's path inside the archive.
data: Uint8Array
The file contents (empty for a symlink entry).
linkname?: string
For a symbolic-link entry, its target (≤ 100 bytes); absent for a regular
file. Node's release tarballs, for one, ship `bin/npm`/`bin/npx` as symlinks
For a symbolic-link entry, its target; absent for a regular file. Node's release tarballs, for one, ship `bin/npm`/`bin/npx` as symlinks
into `lib/node_modules`, so extracting a runtime tree must preserve them.
mode?: number
A regular file's permission bits, written into its header: `0o755` for an
Expand Down
53 changes: 45 additions & 8 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ async function archiveOutputs(outputs: readonly string[], host: OutputHost): Pro
Archive a target's `outputs` into a gzipped tar of their current contents,
each file with its permission bits so a binary restores executable. A
declared output that does not exist is skipped, as is anything under a
`.git` or `.zuke` directory other than `.zuke/tools`.
`.git` or `.zuke` directory, other than `.zuke/tools` and whatever it holds.

function assert(condition: unknown, message: string): asserts condition
Assert that `condition` is truthy, narrowing it for the rest of the scope.
Expand Down Expand Up @@ -391,6 +391,8 @@ function envCacheStore(readEnv: (name: string) => string | undefined): RemoteCac
on a GitHub Actions job that exposes its `ACTIONS_RUNTIME_TOKEN` and
`ACTIONS_RESULTS_URL` (the `actions-cache` input of the `zuke-build/zuke`
action), a {@link "./github_actions_cache.ts".GitHubActionsCacheStore}.
Whichever is chosen takes `ZUKE_REMOTE_CACHE_MAX_INFLATED_BYTES`, when set,
as its {@link RemoteCacheStore.maxInflatedBytes}.

function envStateStore(readEnv: (name: string) => string | undefined, host: StateHost): StateStore | undefined
Resolve a {@link StateStore} from the environment, or `undefined` when none is
Expand Down Expand Up @@ -920,7 +922,8 @@ async function restoreOutputs(artifact: Uint8Array, host: OutputHost, outputs?:

@param maxBytes
The most the artifact may decompress to before it is refused,
defaulting to 2 GiB. The bound is applied while decompressing, so a small
defaulting to 2 GiB; the executor passes the store's
{@link RemoteCacheStore.maxInflatedBytes}. The bound is applied while decompressing, so a small
archive that expands without limit is refused rather than buffered first.
It is larger than the bound a store puts on the compressed bytes because
outputs compress; both exist to stop memory exhaustion.
Expand Down Expand Up @@ -1047,7 +1050,10 @@ async function syncCiFiles(files: readonly CiFile[], options: CiSyncOptions): Pr
instead (so CI can fail when the committed config has drifted).

function tar(entries: TarEntry[]): Uint8Array
Create a `ustar` archive from the given entries (in order).
Create a `ustar` archive from the given entries (in order). A name or
symlink target past ustar's 100 bytes is carried by a pax extended header
(typeflag `'x'`) ahead of its member, which {@link untar} and every modern
`tar` read back.

function target(): TargetBuilder
Create a new, empty target builder.
Expand Down Expand Up @@ -1763,12 +1769,17 @@ class FileSystemBuildRegistry implements BuildRegistry
class FileSystemCacheStore implements RemoteCacheStore
A {@link RemoteCacheStore} backed by a shared or mounted directory.

constructor(dir: string)
constructor(dir: string, options: FileSystemCacheStoreOptions)
Build the store over a directory.

@param dir
The directory archives are read from and written to.

@param options
The restore bound.

readonly maxInflatedBytes?: number
The most an artifact may decompress to; see {@link RemoteCacheStore.maxInflatedBytes}.
get(key: string): Promise<Uint8Array | null>
Fetch the archived outputs stored under `key`, or `null` if there are none.
async put(key: string, artifact: Uint8Array): Promise<void>
Expand Down Expand Up @@ -1870,6 +1881,8 @@ class GitHubActionsCacheStore implements RemoteCacheStore

constructor(options: GitHubActionsCacheStoreOptions)
Build the store from the service URL, the runtime token, and the `fetch` seam.
readonly maxInflatedBytes?: number
The most an artifact may decompress to; see {@link RemoteCacheStore.maxInflatedBytes}.
async get(key: string): Promise<Uint8Array | null>
Fetch the archived outputs stored under `key`, or `null` if there are none.
async put(key: string, artifact: Uint8Array): Promise<void>
Expand Down Expand Up @@ -1932,7 +1945,9 @@ class HttpCacheStore implements RemoteCacheStore
decompresses to.

constructor(options: HttpCacheStoreOptions)
Build the store from its URL, optional token, size cap, and `fetch` seam.
Build the store from its URL, optional token, size caps, and `fetch` seam.
readonly maxInflatedBytes?: number
The most an artifact may decompress to; see {@link RemoteCacheStore.maxInflatedBytes}.
async get(key: string): Promise<Uint8Array | null>
Fetch the archived outputs stored under `key`, or `null` if there are none.
async put(key: string, artifact: Uint8Array): Promise<void>
Expand Down Expand Up @@ -2279,6 +2294,8 @@ class S3CacheStore implements RemoteCacheStore

constructor(options: S3CacheStoreOptions)
Build the store from the bucket URL, region, credentials and seams.
readonly maxInflatedBytes?: number
The most an artifact may decompress to; see {@link RemoteCacheStore.maxInflatedBytes}.
get(key: string): Promise<Uint8Array | null>
Fetch the archived outputs stored under `key`, or `null` if there are none.
put(key: string, artifact: Uint8Array): Promise<void>
Expand Down Expand Up @@ -3997,6 +4014,13 @@ interface FanOutOptions
env?: Record<string, string>
Environment variables set on every job (e.g. the remote-cache config).

interface FileSystemCacheStoreOptions
Configuration for a {@link FileSystemCacheStore}.

maxInflatedBytes?: number
The most a fetched artifact may decompress to, in bytes — see
{@link RemoteCacheStore.maxInflatedBytes}. Defaults to 2 GiB.

interface FileTasksApi
The shape of {@link FileTasks}.

Expand Down Expand Up @@ -4116,6 +4140,9 @@ interface GitHubActionsCacheStoreOptions
this store is written only by the repository's own workflows and the
outputs worth caching here are the big ones (a 1.5 GB SDK). What the bytes
decompress to is bounded separately, by `restoreOutputs`.
maxInflatedBytes?: number
The most a fetched artifact may decompress to, in bytes — see
{@link RemoteCacheStore.maxInflatedBytes}. Defaults to 2 GiB.

interface GlobOptions
Options for {@link glob}.
Expand Down Expand Up @@ -4183,6 +4210,9 @@ interface HttpCacheStoreOptions
outputs are genuinely larger; a refusal is a warned rebuild, not a build
failure. What the bytes decompress to is bounded separately, by
{@link restoreOutputs}.
maxInflatedBytes?: number
The most a fetched artifact may decompress to, in bytes — see
{@link RemoteCacheStore.maxInflatedBytes}. Defaults to 2 GiB.

interface HttpOptions
Options shared by the HTTP helpers.
Expand Down Expand Up @@ -4611,6 +4641,11 @@ interface RemoteCacheStore
point of view: the executor never fails a build because the store is
unreachable — it just rebuilds and, where it can, re-uploads.

readonly maxInflatedBytes?: number
The most an artifact from this store may decompress to, in bytes, before
{@link restoreOutputs} refuses it. Defaults to 2 GiB; raise it for a target
whose outputs are genuinely larger, such as a whole SDK. The restore holds
the inflated archive in memory, so the bound is also its memory ceiling.
get(key: string): Promise<Uint8Array | null>
Fetch the archived outputs stored under `key`, or `null` if there are none.
put(key: string, artifact: Uint8Array): Promise<void>
Expand Down Expand Up @@ -5049,6 +5084,9 @@ interface S3CacheStoreOptions
The most a fetched artifact may weigh on the wire, in bytes, before it is
refused. Defaults to 512 MiB — see
{@link "./remote_cache.ts".HttpCacheStoreOptions.maxArtifactBytes}.
maxInflatedBytes?: number
The most a fetched artifact may decompress to, in bytes — see
{@link RemoteCacheStore.maxInflatedBytes}. Defaults to 2 GiB.

interface ScheduleEntry
A scheduled trigger: a 5-field cron expression in an optional IANA timezone.
Expand Down Expand Up @@ -5177,12 +5215,11 @@ interface TarEntry
A single entry within a tar archive — a regular file or a symbolic link.

name: string
The entry's path inside the archive (≤ 100 bytes).
The entry's path inside the archive.
data: Uint8Array
The file contents (empty for a symlink entry).
linkname?: string
For a symbolic-link entry, its target (≤ 100 bytes); absent for a regular
file. Node's release tarballs, for one, ship `bin/npm`/`bin/npx` as symlinks
For a symbolic-link entry, its target; absent for a regular file. Node's release tarballs, for one, ship `bin/npm`/`bin/npx` as symlinks
into `lib/node_modules`, so extracting a runtime tree must preserve them.
mode?: number
A regular file's permission bits, written into its header: `0o755` for an
Expand Down
1 change: 1 addition & 0 deletions packages/core/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export {
archiveOutputs,
envCacheStore,
FileSystemCacheStore,
type FileSystemCacheStoreOptions,
HttpCacheStore,
type HttpCacheStoreOptions,
type OutputHost,
Expand Down
Loading
Loading