Full operations for a blog site on AWS — S3 + CloudFront hosting, with the site
built inside a Lambda MicroVM. One CLI, no CloudFormation, no
Terraform, no CDK.
Documentation → blogwright.iamstan.dev
Works with any static site that builds via pnpm build — an Astro blog at the
repo root, or a SvelteKit/Vite SPA in a monorepo subdirectory. The CLI talks to
AWS by SigV4-signing raw HTTP requests and models the infrastructure as a
reconcilable dependency graph. Deploys are incremental: only changed files are
re-uploaded and only the changed CloudFront paths are invalidated.
Requires Node ≥ 22 and AWS credentials in the ambient provider chain.
pnpm add -D blogwright
pnpm exec blogwright init # first-run wizard — writes config/production.jsonc
pnpm exec blogwright bootstrap # create the infrastructure; prints DNS records to add
pnpm exec blogwright deploy # build in a MicroVM and publish (`bw` works too)(No TTY? Write the config by hand — only region and siteName are required:
echo '{ "region": "us-east-1", "siteName": "example" }' > config/production.jsonc.)
The quickstart walks every step, including custom domains and checking on the result.
blogwright init # first-run wizard: config/production.jsonc
blogwright bootstrap --domain example.com # create infra; prints ACM validation CNAMEs
blogwright deploy # zip + build in a MicroVM + publish
blogwright status staging # planned graph vs. live state
blogwright history # deployment history
blogwright logs <hash> # CloudWatch build logs for a hash
blogwright rollback <hash> # re-deploy an earlier build
blogwright delete # empty site/ only
blogwright destroy --yes # tear everything down
blogwright preview … # per-PR preview stack
blogwright pds … # standard.site (AT Protocol) publishingEnvironment defaults to production; pass another as the positional [env] or
--env. Full flags, positional layouts, and output contracts are in the
CLI reference.
Everything lives at blogwright.iamstan.dev:
- Deploying — the deploy
lifecycle: source zip, MicroVM build, ETag-diff sync, selective invalidation,
--refresh. - Configuration reference —
every key in
config/<env>.jsonc, with defaults. - CI deploys with GitHub OIDC — keyless deploys from GitHub Actions.
- PR previews — every PR at
https://<id>.<preview-domain>from one shared stack. - Publishing to standard.site — mirroring posts to an AT Protocol PDS, with OAuth handled by the CLI.
- Beyond Astro — monorepo
SPAs (
paths.app/paths.dist,spa,sourceInclude). - Architecture — the resource graph, bucket layout, builder MicroVM, and SigV4 transport.
Every page is also available as plain Markdown — append .md to any URL, or
start from blogwright.iamstan.dev/llms.txt.
| Package | What it is |
|---|---|
blogwright |
The CLI (blogwright / bw bins): graph engine, resource nodes, commands, dispatch — plus the blogwright/rkey subpath export (a re-export of blogwright-pds/rkey) |
blogwright-core |
SigV4 transport + per-service HTTP clients, config, S3 state store, shared ports (filesystem, terminal) |
blogwright-pds |
standard.site (AT Protocol) publishing: OAuth client, secret store, PDS record sync, URL-derived rkeys |
blogwright-build-agent |
HTTP build server baked into the builder MicroVM image (not published — its bundle ships inside the CLI package) |
pnpm install
pnpm build # core -> build-agent -> cli (and the docs site)
pnpm test # unit tests (transport mocks) — no cloud needed
# Integration tests against the floci emulator:
docker run -d --name floci -p 4566:4566 -v /var/run/docker.sock:/var/run/docker.sock floci/floci:latest
FLOCI=1 AWS_ENDPOINT_URL=http://localhost:4566 pnpm testThe lambda-microvms control plane is not emulated by floci, so the MicroVM
client and deploy orchestration are covered by transport-level mocks rather than
integration tests.
Contributor guidelines — coding style, error handling, version control (jj), and
the definition of done — live in DEVELOPMENT.md. The docs site
sources live in docs/; PRs that touch them get a preview at
pr-<n>.preview.blogwright.iamstan.dev.
Versioning is driven by changesets and releases by tags, with staged npm
publishing — no npm token anywhere (see .github/workflows/version-pr.yml
and release.yml):
- Every user-facing change ships with a changeset:
pnpm changeset, pick the impact, write a one-liner. The three publishable packages are version-fixed, so any bump moves them together. - CI maintains a "Release: version packages" PR that folds pending changesets into a version bump + per-package CHANGELOGs. Merge it when ready to release.
- Tag the merge:
git tag v<x>.<y>.<z> && git push origin v<x>.<y>.<z>(tags are the one place plain git is used — jj does not author tags; the version is previewed in the PR). - CI validates versions match the tag, builds, runs the full gate set plus
publintandarethetypeswrong, then stages all three packages to npm via OIDC trusted publishing with provenance, and cuts a GitHub Release from the changesets CHANGELOG entry. - Nothing is live yet: approve the staged packages (
npm stage approve, or the staged-packages UI on npmjs.com). Re-running a tag is idempotent — already-published packages are skipped.
One-time setup on npmjs.com: each package (blogwright-core, blogwright-pds,
blogwright) needs a Trusted Publisher pointing at this repository, the
release.yml workflow, and the publish environment. The repository needs a
publish GitHub environment (add a required reviewer there for an extra
approval gate if wanted).