Repository navigation
A component can't build a frontend: the artifact is the git tree, and deploy only runs uv sync #86
Copy link
Copy link
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Problem
Found while deploying Abecra's staging (Cloudfall's customer zero) on 2026-10-19. Abecra is a Python API plus a Vite web app in one repository. Cloudfall can ship the API but not the web build:
artifact build(engine/src/cloudfall_engine/artifact.py) clones the repository at a ref and tars the source tree (orrepository.subdirectory). No build step runs, soweb/dist/is never in the artifactcloudfall_deploy(engine/ansible/roles/cloudfall_deploy/tasks/main.yml) assertsruntime.packageManager == "uv"and materializes the release withuv sync; nothing else runs on the servercomponent.schema.jsonacceptsruntime.type: node | pm2andpackageManager: npm | pnpm | yarn, and ROADMAP M4 says "native Python and Node builds", but the deploy role refuses anything that is not uv. The schema promises more than the engine does
The workaround is a side script in the fleet repository that clones the same commit, runs pnpm install && pnpm build, rsyncs dist/ to /srv/apps/<app>/web/releases/<sha>, flips its own current symlink, and only then runs cloudfall deploy. That second release tree has no digest check, no receipt, no rollback with the API release, and can drift from it (the API's current and the web's current are switched by different tools).
Proposal
A declared build step that runs on the controller before packaging, whose outputs go into the same artifact:
spec:
build:
- workingDirectory: web
command: [pnpm, install, --frozen-lockfile]
- workingDirectory: web
command: [pnpm, build]
artifact:
include: [web/dist] # build outputs kept even if gitignored- Runs inside the clean checkout
artifact buildalready makes, with a declared toolchain (nodeversion, package manager) checked likerequired_tools; a missing tool fails with a typed error before cloning - Build outputs land in the tarball, so the existing digest, receipt, symlink switch and rollback cover them unchanged
- The
ArtifactMetadatarecords the build steps and toolchain versions that produced it - Separately: until Node runtimes deploy,
component.schema.jsonshould rejectruntime.type: node | pm2and the non-uv package managers instead of failing in the role (or the docs should say they are reserved)
Alternatives
- Keep frontends outside Cloudfall (a CDN or a side script): this is what Abecra does now, and it splits one release into two unrelated rollouts
- A separate
staticcomponent kind with its own artifact: cleaner for SPAs with no backend, but two artifacts for one commit still need a joint rollback
Acceptance
- A component declaring a
pnpm buildstep:artifact build --ref <sha>produces a tarball containingweb/dist/, and its metadata lists the steps cloudfall deployof that release putsweb/dist/underreleases/<release>/;cloudfall rollbackrestores the previous release's web build with the API- A build step that exits non-zero fails
artifact buildwith a typed error and writes no artifact cloudfall config validateon a component withruntime.type: nodeeither deploys it or fails validation; it never passes validation and fails in the role
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request
Activity
shipmill-romamo commentedon Oct 20, 2026
Triage: feature.
Confirmed on main (348ed73):
engine/src/cloudfall_engine/artifact.pytars the source tree at the ref with no build step, andcloudfall_deployassertsruntime.packageManager == "uv", whilecomponent.schema.jsonacceptsruntime.type: node | pm2andnpm | pnpm | yarn. A declared controller-side build step whose outputs go into the artifact is new behaviour (a new spec section, a toolchain check, new artifact metadata), so it gets a spec with acceptance criteria first. It pairs with #90's static routes, so the two specs are written together.The schema/engine mismatch is a broken promise, but fixing it by rejecting
nodeand the non-uv package managers in the schema would refuse configs that validate today, and the right answer depends on whether Node runtimes are coming soon. The spec decides it as one of its criteria, so the reviewer settles both at once.The spec PR is being written now (
docs/specs/002-component-build-steps.md); I'll put its link and the hold line here. Merging it is the approval.shipmill-romamo commentedon Oct 20, 2026
The spec is in #98 (
docs/specs/002-component-build-steps.md). Points to check before merging it:config validatewould refuse non-uv runtimes, so the Render importer would stop writing Node components (S-002-14). The archive becomes the tracked files plusartifact.includewhen a component declares build steps. Build steps run with a reduced environment, so a build that needs a private registry token has no way to get it yet.On hold: the build waits on #98, the spec