Skip to content

feat(cli): support exclude patterns for compute build context - #6658

Open
johnstonmatt wants to merge 9 commits into
developfrom
FUNC-911/feat/compute/exclude-config-property
Open

johnstonmatt wants to merge 9 commits into
developfrom
FUNC-911/feat/compute/exclude-config-property

Conversation

@johnstonmatt

@johnstonmatt johnstonmatt commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Adds a [compute.<name>] exclude list so a compute's build context can leave out secrets and generated trees instead of uploading them for the platform to ignore, and gives each runtime scaffolded by compute new a sensible default list.

  • Add an exclude: string[] field to the compute entry schema in packages/config. The schema checks only that the key holds a list of strings; pattern syntax is validated by push, which can then refuse a single bad pattern by name instead of failing the whole config load.
  • Teach push to compile the recorded patterns with .gitignore semantics (name-anywhere, /-anchoring, trailing-/ for directories, ** spanning, glob syntax per segment) and skip matched paths — including excluded symlinks, checked before the existing escaping-link validation — while pruning excluded directories entirely rather than descending into them.
  • Refuse re-inclusion (!...) and malformed/empty patterns up front, before any packaging or upload, and report the packaged line's excluded count; a source excluded down to nothing now fails naming the offending patterns instead of reporting an empty directory.
  • Let a runtime declare default exclude patterns via COMPUTE_RUNTIME_EXCLUSIONS, written into config.toml by compute new (extending the TOML section writer to render string-list values) rather than applied invisibly at push time, so the list stays visible and editable.
  • Populate each runtime's actual default list: every runtime excludes env files and VCS metadata, node also drops node_modules/ and *.log, deno drops *.log, and dockerfile adds nothing further since the user's own Dockerfile decides what it copies.

`[compute.<name>]` gains an `exclude` list of patterns naming paths to leave
out of the build context a deploy uploads, so a project can keep secrets and
generated trees out of the image.

Patterns are carried verbatim rather than validated here: unlike `instances`, a
pattern this layer cannot read is never silently dropped, so the CLI is free to
refuse a specific one by name instead of failing the whole config load.
`push` reads the recorded patterns and leaves whatever they match out of the
`.tar.gz` build context it uploads, so a secret or a generated tree no longer
has to reach the platform to be ignored by the build.

Patterns are read the way `.gitignore` reads them, because that is the
vocabulary the paths people want gone are already written in: bare names match
at any depth, a `/` anchors to the source directory, a trailing `/` matches
directories only, `**` spans them, and one segment is matched by the glob
matcher the seed globber already uses. An excluded directory is not descended
into, so nothing beneath it is read at all.

Re-inclusion (`!`) is refused rather than read as a literal filename: pruning
at the directory means a pattern re-admitting something beneath it could never
be reached, and a setting that silently does nothing is worse than one that
isn't offered. Empty patterns, empty path segments and malformed character
classes are refused the same way, before anything is packaged or uploaded,
beside the runtime and exposure checks.

A matched symlink is excluded before it is vetted for escaping the context, so
excluding a hoisted `node_modules` link is now an answer to
`ComputeSourceEscapingLinkError` rather than something that failure pre-empts.

The packaged line carries an excluded count when patterns are configured, and a
source whose every file is excluded fails naming the patterns instead of
reporting the empty-directory case.
Adds the mechanism only: `COMPUTE_RUNTIME_EXCLUSIONS` is keyed by runtime the
way the description and marker-file tables already are, `compute new` writes
whatever a runtime declares into `[compute.<name>] exclude`, and the TOML
section writer learns to render a list of strings as an array.

Every runtime declares an empty list here, so no scaffold changes behavior yet
and no `exclude` key is written — what each runtime should leave out depends on
how it resolves dependencies, and is decided per runtime rather than alongside
the plumbing.

Recording the list in `config.toml` rather than applying it silently at push
time keeps it visible, editable, and the same on every machine. A built-in
default nobody could see would be a second, invisible source of truth for what
ships, and `push` stays free of defaults of its own.
Fills in the per-runtime lists the previous commit left empty, so a scaffolded
compute starts with the patterns its runtime actually wants.

Every runtime keeps environment files and version-control metadata out, since
both are secrets or noise in an image whose context is uploaded to the
platform. Beyond that the lists diverge by how each runtime resolves what it
depends on. `node` drops the locally installed tree's contents with
`node_modules/*` while keeping the directory, so the runtime still finds the
resolution root it expects and the build resolves dependencies itself rather
than inheriting one machine's platform-specific binaries. `deno` caches remote
dependencies outside the project and so has no installed tree to drop. And
`dockerfile` assumes nothing further about a context the user's own `Dockerfile`
decides how to copy.

This is the commit that changes what a scaffolded compute deploys, and the one
to revert if the build does not resolve dependencies.
@johnstonmatt
johnstonmatt requested a review from a team as a code owner September 17, 2026 05:58

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI Review

All 10 deduplicated findings are confirmed. The main issue is a likely runtime-breaking Node default that excludes dependencies despite the existing no-install deployment contract. The remaining findings cover misleading empty-package errors, matcher correctness/performance, stale generated schema output, type-safety, and documentation/convention violations.

Findings

Severity Location Category Sources Claim
🟠 MAJOR apps/cli/src/shared/compute/compute-runtimes.ts:66 correctness claude+codex The Node scaffold excludes installed dependencies even though the existing catalog-runtime deployment contract has no server-side install step.
🟡 MINOR apps/cli/src/commands/experimental/compute/push/push.handler.ts:351 error-handling claude+codex A configured exclusion list misdiagnoses any zero-file package as having every file excluded, even when no pattern matched.
🟡 MINOR apps/cli/src/shared/compute/toml-section.ts:79 type-safety claude The production as-cast used to render string values violates the repository's explicit type-safety convention and masks incomplete narrowing.
🟡 MINOR apps/cli/src/shared/compute/compute-exclude.ts:65 correctness claude+codex A terminal /** matches and removes its parent directory, contrary to the documented gitignore-style behavior.
🟡 MINOR apps/cli/src/shared/compute/compute-exclude.ts:65 performance codex Globstar matching repeatedly evaluates the same suffix states, producing combinatorial work for valid patterns with multiple globstars.
🟡 MINOR apps/docs/public/cli/config.schema.json:2383 generated-artifacts claude The published generated config schema omits the new compute exclude property, causing schema validation and editor tooling to reject it.
⚪ NIT apps/cli/src/shared/compute/compute-exclude.ts:115 correctness claude Repeated trailing slashes unexpectedly change an unanchored directory pattern into an anchored one.
⚪ NIT apps/cli/src/shared/compute/compute-runtimes.ts:66 documentation claude The runtime-default explanation does not account for *.log being present only in the Node and Deno lists.
⚪ NIT apps/cli/src/shared/compute/compute-exclude.ts:135 error-handling codex Malformed glob syntax unrelated to character classes is incorrectly reported as a malformed character class.
⚪ NIT apps/cli/src/shared/compute/compute-exclude.ts:5 maintainability codex The new matcher file violates the repository's comment-density policy and duplicates semantics maintained in command documentation.

Findings outside the diff

  • 🟡 MINOR apps/docs/public/cli/config.schema.json:2383 — The published generated config schema omits the new compute exclude property, causing schema validation and editor tooling to reject it.

Stats

Claude findings: 7 · Codex findings: 6 · Confirmed: 10 · Refuted: 0 · Uncertain: 0


Models: claude-opus-5 + gpt-5.6-sol · Trigger: auto · Workflow run

This review runs once per PR. A maintainer can request another with a /ai-review comment.

Comment thread apps/cli/src/shared/compute/compute-runtimes.ts Outdated
Comment thread apps/cli/src/commands/experimental/compute/push/push.handler.ts
Comment thread apps/cli/src/shared/compute/toml-section.ts Outdated
Comment thread apps/cli/src/shared/compute/compute-exclude.ts
Comment thread apps/cli/src/shared/compute/compute-exclude.ts Outdated
Comment thread apps/cli/src/shared/compute/compute-runtimes.ts Outdated
Comment thread apps/cli/src/shared/compute/compute-exclude.ts
Comment thread apps/cli/src/shared/compute/compute-exclude.ts Outdated
Comment thread apps/cli/src/shared/compute/compute-exclude.ts
The refusal for a symlink leaving the build context still told users to install
the compute's dependencies inside its own directory. That was the third and
last place carrying the old no-server-install contract, and the only one that
was a user-facing string rather than a comment, so it survived the earlier
sweep and now contradicted both the `node` scaffold's own defaults and this
error's rationale one file over.

Excluding the link is the cheaper recovery and the one `exclude` exists to
offer, so the suggestion names it first. Pinned by the escaping-link cases,
which asserted only the error type before and so let the guidance drift while
staying green.
Comment thread apps/cli/src/shared/compute/compute-exclude.ts Outdated
Review follow-ups on the `exclude` matcher, all behaviour-visible:

A trailing `**` now has to consume a segment. `cache/**` matching `cache`
itself meant the walk pruned the directory the pattern was written to empty,
taking it out of the archive rather than emptying it; a spanner anywhere else
may still span nothing, so `build/**/cache` keeps matching `build/cache`.

Adjacent spanners collapse to one. `**/**` spans exactly what `**` spans, so
leaving the repeats in let a pattern retry the same suffixes once per spanner —
combinatorial work on a matcher that runs for every entry in the walk.

Anchoring is decided after trailing separators come off, so `dist//` is the
same unanchored directory pattern as `dist/` rather than silently becoming a
root-only one.

A malformed pattern no longer claims to be a malformed character class.
`pathMatch` returns one verdict for every bad operator, a trailing escape
included, so the message names the offending segment instead of guessing.

`push` picks the empty-source message from what was actually excluded rather
than from whether patterns were configured: a tree of nothing but empty
directories packages to zero files whatever `exclude` says, and blaming a
pattern that matched nothing sent the user to edit a line doing its job.

Also drops the `as string` in the TOML section writer by narrowing on what each
branch is rather than what it isn't, explains why only the catalog runtimes
exclude `*.log`, and moves the matcher's prose into the command's
SIDE_EFFECTS.md, which is the doc of record for the semantics.
The scaffolded pattern dropped the directory's contents while keeping the
directory itself; exclude the directory outright.
Comment thread apps/cli/src/shared/compute/compute-runtimes.ts Outdated
A worktree or submodule checkout has `.git` as a file holding an absolute
gitdir path, which the directory-only `.git/` pattern passed over.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant