feat: static-site deploys through the deployments API (s3 arm, env-gated) - #585
Merged
Conversation
Contributor
🚀 Package Preview Available!Install this PR's preview build with npm: npm i @base44-preview/cli@0.1.7-pr.585.ca3aebePrefer not to change any import paths? Install using npm alias so your code still imports npm i "base44@npm:@base44-preview/cli@0.1.7-pr.585.ca3aebe"Or add it to your {
"dependencies": {
"base44": "npm:@base44-preview/cli@0.1.7-pr.585.ca3aebe"
}
}
Preview published to npm registry — try new features instantly! |
netanelgilad
force-pushed
the
fullstack/static-workers-lane
branch
from
August 3, 2026 06:23
299efba to
d4f34d4
Compare
netanelgilad
force-pushed
the
fullstack/static-workers-lane
branch
2 times, most recently
from
August 3, 2026 13:55
0f8f74c to
4480729
Compare
…ted)
Adds the deployments core (commit-addressed create/finalize, asset
manifest hashing, presigned uploads) and routes site.outputDirectory
through it when BASE44_STATIC_DEPLOYMENTS is set: POST deployments with
{git_hash, asset_manifest} and no worker config, PUT each requested
file directly to its presigned URL echoing the signed content_type
(the URL also signs content_length), finalize with the index.html
bytes as the completion sentinel. asset_uploads: null means nothing
is owed — re-deploying a commit is idempotent.
The create response is a type-discriminated ADT so the worker (cf)
arm can slot in next to s3 without protocol changes. Gate off keeps
the legacy tar.gz upload byte-identical.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DvhQfqxACcq25XAQRpoSh9
netanelgilad
force-pushed
the
fullstack/static-workers-lane
branch
from
August 3, 2026 17:17
9eab252 to
3461f00
Compare
netanelgilad
enabled auto-merge (squash)
August 4, 2026 05:35
DolevEpshtein
approved these changes
Aug 4, 2026
netanelgilad
added a commit
that referenced
this pull request
Aug 4, 2026
Main landed PR #585 (static-site deploys) in a reworked form after review: the code moved to `core/site/`, the `.assetsignore` walk switched to globby, uploads switched to p-map + ky retry, and `--concurrency` was added. This branch had built the full-stack (cf) arm on top of #585's original layout in `core/deployments/`. Resolution: keep this branch's structure and CLI contract, adopt main's review improvements into it. Structure — kept `core/deployments/`; the cf arm deploys Workers, which is not site-specific. Reverted main's duplicate `core/site/{manifest,static-site, upload}.ts` and the deployment additions to `core/site/{api,schema}.ts`; `core/site/deploy-app.ts` remains the bridge from the site module. Adopted from main: - globby-based `.assetsignore` via `ignoreFiles`, replacing the hand-rolled matcher — gains real gitignore semantics including negation. Kept the MIME table and `AssetFile.contentType`, which the cf arm's multipart parts need. - p-map for upload concurrency (both arms) and ky's built-in retry for presigned PUTs, so a 403 from an expired URL fails fast. - `--concurrency <n>` (default 3, max 50) and `isGitCommitHash()` in `core/utils/git.ts`; `GIT_HASH_PATTERN` dropped from the deployments schema. - Main's manifest suite (negation, brace/extglob, anchoring, dotfiles). - v0.1.8, globby ^16.2.2, p-map ^7.0.6. Contract kept from this branch — main gated `--git-hash`/`--concurrency` registration on BASE44_STATIC_DEPLOYMENTS. That cannot hold here: full-stack deploys are ungated and need `--git-hash`, which now defaults to the checkout's HEAD. Both flags are therefore always registered, on `deploy` and `site deploy` alike, from a shared `addDeploymentOptions()`. The env gate now decides only whether a static output takes the deployments API or the legacy tar.gz path. Main's flag-hiding tests were dropped as no longer applicable; its `--git-hash`/`--concurrency` validation tests were ported. One behavior change: a malformed `--git-hash` is now rejected by the option's argParser ("Expected a git commit hash") before the action runs, rather than later by resolveGitHash. typecheck, lint, and knip clean; 713 tests pass. The suite has a pre-existing "Body is unusable" flake under parallel load that reproduces on pristine main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
netanelgilad
added a commit
that referenced
this pull request
Aug 4, 2026
PR #585 pulled the spinner wiring and result shaping out of `deployAction` into `deployToDeploymentsApi` / `deployTarball`. This branch had undone that: the action ended in an if/else chain over a result union, and the progress wiring had moved off to its own cli/commands/site/run-app-deploy.ts. Back to main's shape, with the full-stack flow folded in as a third helper. `deployAction` now reads as plan → confirm → build → dispatch, and each transport owns its own labels, progress and result: - deployFullStackApp — Workers, the cf arm - deployToDeploymentsApi — deployments-API static, the s3 arm - deployTarball — legacy tar.gz (unchanged from main) `runDeployTask` holds the spinner/progress wiring the two deployments-API helpers share, and `deploymentResult` the outro + --json document. run-app-deploy.ts is deleted. That let core shed an orchestrator it no longer needs: `deployAppSite()` and the AppDeployResult union are gone, and core/site/deploy-app.ts is just the planner now — `planAppDeploy()` returns the plan (with outputDir where there is one) and the command calls deployFullStack / deployStaticSite / deploySite itself. Core still decides which transport applies; the CLI no longer round-trips through a second dispatch to find out what it already asked for. The command plans twice, deliberately: once before the build for the prompt and the no-config error, once after for the transport it acts on, since a full-stack artifact is itself a build output. typecheck, lint, and knip clean; full suite green (715 tests, 71 files). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
Description
Adds an env-gated lane for deploying a site's built output through the new deployments API, alongside the existing tar.gz upload. The output directory is hashed into a content-addressed asset manifest, the deployment is created at the commit's address (
git_hash), only the assets the server still owes arePUTdirectly to presigned S3 URLs, andindex.htmlis sent as the finalize sentinel. The lane is reachable only frombase44 site deploy --git-hash <commit>and only whenBASE44_STATIC_DEPLOYMENTS=1; with the gate off, behavior is unchanged from the legacy path.Related Issue
None
Type of Change
Changes Made
Core (
src/core/site/)manifest.ts— walks the output directory withglobby(dotfiles included, symlinks not followed), honors.assetsignorevia globby'signoreFilesfor real gitignore semantics (anchoring, directory patterns, negation), and hashes each file as the first 32 hex chars ofsha256(utf8(app_id) || bytes). The app-id salt means a tenant can only collide with its own files. Caps: 25 MiB per file (stat before read), 100,000 files.static-site.ts— the flow: build manifest → fail early if there is no/index.html→createDeployment()→ upload owed assets → finalize with the index.html bytes.upload.ts— presignedPUTs via plainky(no app client, no auth headers — the URL is the credential), echoing the server-signedContent-Typeverbatim, 3 attempts with exponential backoff, concurrency viap-map(default 3, max 50).api.ts/schema.ts—POST deploymentsandPOST deployments/{id}/finalizewith Zod snake_case→camelCase transforms;asset_uploadsis modeled as a discriminated ADT ({type: "s3", ...}ornull) so a worker arm can slot in later without protocol changes.utils/git.ts—isGitCommitHash(), matching the server's^[a-fA-F0-9]{7,64}$.CLI (
site deploy)--git-hash <hash>and--concurrency <n>are registered only whenBASE44_STATIC_DEPLOYMENTSis1/true, so with the gate off the flags are absent from--helpand rejected as unknown options. Both validate in CommanderargParsers, before the action runs.options.gitHash: present →deployStaticSite(), absent → the legacydeploySite()tar.gz upload. Each branch owns its spinner labels and result shape;--jsonemits{deploymentId, gitHash}.base44 deploy(unified) is untouched — it still ships the site through the legacy tar.gz step, gate on or off.Deps / repo
p-map, bumpsglobbyto^16.2.2(forignoreFiles), both indevDependenciesper the bundled-distribution rule..gitattributespins test fixtures to LF so content-addressed hashes don't shift on Windows checkouts.Docs
docs/deployments.mdtopic guide (contract, hashing, gate, rules), linked fromdocs/AGENTS.md; updates todocs/resources.md(two site transports) anddocs/testing.md(deployment mocks).Testing
npm test)New coverage:
tests/cli/static_site_deployments.spec.ts(11 cases) — gate off hides and rejects--git-hash/--concurrency; gate off and gate-on-without-commit both keep the legacy tar.gz upload; full happy path asserting the create body (git_hash, manifest keys, noconfig), presigned bodies byte-for-byte with the signed Content-Type and no Authorization header, and a single-fieldindex.htmlmultipart finalize;asset_uploads: nullsends no PUTs but still finalizes;--jsonshape; invalid--git-hashand out-of-range--concurrency.tests/core/site-manifest.spec.ts(12 cases) — hash formula and app-id salting, manifest key/shape,.assetsignoreglobs/negation/anchoring/literal braces, dotfiles, always-ignored names, the 25 MiB limit, and hash dedupe.TestAPIServergainsmockDeploymentCreate,mockPresignedUpload,mockDeploymentFinalizewith request capture.Checklist
docs/(AGENTS.md) if I made architectural changesAdditional Notes
The lane is experimental and off by default: nothing about it surfaces until
BASE44_STATIC_DEPLOYMENTSis set. A deployment addresses a build, not production — there is no--prod, promote, rollback, or list surface; publishing stays with the platform. Deployment ids are derived fromgit_hash, so re-deploying a commit is idempotent. Adopting the lane inbase44 deployis a deliberate follow-up.🤖 Generated by Claude | 2026-08-04 05:36 UTC | ca3aebe