Skip to content

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

Merged
johnstonmatt merged 15 commits into
developfrom
FUNC-911/feat/compute/exclude-config-property
Sep 23, 2026
Merged

johnstonmatt merged 15 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
johnstonmatt and others added 3 commits September 18, 2026 08:22
A worktree or submodule checkout has `.git` as a file holding an absolute
gitdir path, which the directory-only `.git/` pattern passed over.

@kanadgupta kanadgupta left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

happy with this! a few minor asks, will officially ✅ once these are addressed

  1. regenerate the schemas
  2. fix a gitignore pattern
  3. condense a few comments

Read the full diff and the existing threads, and ran the packager and the docs generator on this head to check two things rather than assert them. Overall this is in good shape: FUNC-911 asked for a way to keep things like node_modules/ and .env out of the bundle, and that is what [compute.<name>] exclude delivers. The matcher reuses the existing pathMatch per segment instead of pulling in a glob dependency, the refusals happen before anything is packaged or uploaded, and the earlier bot findings were all addressed in follow-up commits. CI is green, so I did not re-run the suite.

One ask before merge

  • The generated schemas under apps/docs/public/cli/ were not regenerated, so the published JSON Schema still rejects exclude on compute entries. Running the docs generator on this branch adds exactly the missing property to both files and nothing else. Details inline on packages/config/src/compute.ts.

Non-blocking

  • node_modules/ in the node default list has the same directory-only problem the .git/ fix addressed: a symlinked node_modules is not matched, and the push then fails with a message telling the user to add the pattern they already have. Reproduced against this head; table and one-character suggestion inline.
  • Comment density. The matcher file was trimmed to the repo's ratio after the bot flagged it, but the same pattern is present elsewhere: new.handler.ts (the block above const exclude and the one above the Excluded row), push.handler.ts (the block above excludedSomething), and the ternary-order comment in toml-section.ts, which I marked inline as the clearest example. Most of these restate rationale that already lives in the two SIDE_EFFECTS.md files and the schema description, so a pointer or nothing would do.

Not raising the node_modules install-step question further; the earlier thread already covers both positions.

Comment thread packages/config/src/compute.ts
Comment thread apps/cli/src/shared/compute/compute-runtimes.ts Outdated
Comment thread apps/cli/src/shared/compute/toml-section.ts Outdated
@johnstonmatt
johnstonmatt added this pull request to the merge queue Sep 23, 2026
Merged via the queue into develop with commit 2c2093c Sep 23, 2026
29 checks passed
@johnstonmatt
johnstonmatt deleted the FUNC-911/feat/compute/exclude-config-property branch September 23, 2026 13:10
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.

2 participants