# Bean & Bark: every PR gets a URL

Tunde's first storefront PR is up: the wholesale discount fix. Nadia wants to *see it* — not a diff, not a promise, the actual checkout UI showing correct totals — and she's at the roastery with a phone. What a reviewer needs is a URL. What nobody needs is another permanent environment. A static-site preview publishes under the existing dev CloudFront distribution and carries an expiry date.

**You'll learn**

- Enable previews once in the manifest — and read what each default means
- Publish a PR beneath the shared static-site distribution: `kanject aws preview publish <name>`
- Run the lifecycle: `preview ls`, `url`, `extend --by`, `rm` / `rm --expired`
- Explain the TTL lifecycle and the provider-owned boundary that keeps real stages out of preview cleanup

> **Thursday, 2:05pm — the ticket:** **Nadia**: *"Tunde's discount fix — can I poke at it before you merge? last pricing change cost us a friday 😅 I'm at the roastery, phone only. also please don't make another 'temporary' stage, we still have three of those from last year 🙏"*

## Enable it once

Previews are off by default. Turning them on is a manifest block, committed like everything else:

```json
"preview": {
  "enabled": true,
  "basedOn": "dev",
  "ttlDays": 7,
  "stackName": "preview-{name}-{service}",
  "apiStage": "preview-{name}",
  "shareUrl": true
}
```

Read it as policy, not just config. `basedOn: "dev"` selects the deployed static-site stack whose bucket and CloudFront distribution host previews. `ttlDays: 7` gives every preview an expiry date. The stack and API-stage templates remain the naming policy for stack-backed preview inventory.

## Publish the storefront preview

```bash
# Publish the storefront beneath the shared dev distribution
kanject aws preview publish pr-12
```

The CLI discovers the dev site's CloudFormation outputs, builds with a preview-scoped base path, uploads beneath `/previews/pr-12/`, updates routing, records the expiry, and invalidates the relevant CloudFront paths. Dev assets do not move. If a matching backend preview exists, its endpoint is written to public runtime config; otherwise the CLI warns and still publishes the front end.

## The lifecycle

```bash
kanject aws preview ls                # what's alive, and when it expires
kanject aws preview url pr-12         # resolves the target-native live URL
kanject aws preview extend pr-12 --by 7d
kanject aws preview rm pr-12          # done reviewing? tear it down
kanject aws preview rm --expired      # or let CI sweep the stragglers
```

Every lifecycle command reads live provider state. `preview url` first resolves the human name against the inventory, then asks the target for its public URL; static-site previews use their ledger record and shared CloudFront distribution. That means CI needs AWS credentials and read access when it posts the link on a pull request.

> **Real stages stay outside the preview lifecycle:** Stack-backed previews must carry `kanject:preview=true` or `extend` refuses with **KANCLI204**. Static-site previews must have an active ledger record. Names alone never make a deployment eligible for extension or cleanup.

## Nobody remembers to clean up — so nobody has to

The PR merges Friday morning; the preview is now garbage. Maybe you `rm` it in the merge ritual — but the honest answer is that reviewers forget, which is why every preview carries its TTL. A scheduled `preview rm --expired` in CI sweeps everything past its date: the "three temporary stages from last year" problem, structurally deleted. Need more time on a genuinely long review? That's what `extend --by 7d` is for — a deliberate, logged decision to keep one preview alive, instead of immortality by default.

> **🎯 Reviewed from a phone, gone by Friday:** Count what this chapter didn't create: no standing environment, no shared password doc, no cleanup ticket. A branch became a URL, a founder became a reviewer, and the preview assets are scheduled for removal. **Every deploy artifact is either alive on purpose, or scheduled to disappear.**

**Recap**

- Previews are enabled by a committed manifest block: `basedOn` chooses the base deployment and `ttlDays` gives every preview an expiry.
- `preview publish <name>` builds and uploads a static site beneath the base stage's shared CloudFront distribution; dev assets never move.
- `preview url` resolves the link from live provider inventory; `extend --by` deliberately buys time; tags and ledger records keep real stages outside the lifecycle.
- Cleanup is structural, not remembered: `preview rm --expired` sweeps expired preview records and their provider resources.

**Before the merge — check yourself**

**1. A CI job runs `kanject aws preview url pr-12` to comment the link on the PR. What AWS permissions does that step need?**

- AWS credentials with access to read the live preview inventory ✓ — `preview url` resolves the name through the provider and asks the target for its URL. Static-site previews read their ledger and shared CloudFront deployment.
- None — the URL is computed only from the manifest — The command deliberately verifies live provider state, so a stale or missing preview does not produce a plausible but dead URL.
- Lambda invoke, to verify the endpoint responds — `url` prints where the preview lives; it makes no claim the code behind it is healthy. Checking that is the reviewer's job — that's the point of the preview.

**2. Someone runs `kanject aws preview extend pr-12 --by 7d`. What changes?**

- Its recorded expiry moves to seven days from now; the site is not rebuilt ✓ — Static-site previews update their ledger record. Stack-backed previews update only their expiry tag. Neither path redeploys the application.
- The dev stage gets a seven-day TTL — The human preview name resolves only within provider-owned preview inventory; base stages stay outside that lifecycle.
- The current expiry has seven days added to it — `--by` is measured from now, not from the existing expiry.

**Try it yourself**

**1. Wire the weekly sweep**

Write the one command a scheduled CI job should run so forgotten previews never outlive their TTL — and say what keeps that job from ever deleting dev.

_Hint:_ One flag on `preview rm`; the safety boundary is provider-owned preview inventory, not a name pattern.

_Solution:_ A scheduled job running `kanject aws preview rm --expired` removes every preview past its recorded expiry. It can't touch dev: stack-backed inventory requires the preview tag and static-site inventory requires a preview ledger record.

```bash
kanject aws preview rm --expired
```

**2. Predict the static preview path**

Tunde opens PR 31. Which command publishes it, where does it live beneath the shared distribution, and when is it eligible for cleanup?

_Hint:_ The preview name is both the publish argument and the path segment; `ttlDays` controls eligibility.

_Solution:_ `kanject aws preview publish pr-31` uploads beneath `/previews/pr-31/`. Its ledger expiry is seven days from publication, after which the next `preview rm --expired` may remove it.

> **The end — and the other side of the story:** This started with a spreadsheet and a DM. Now Bean & Bark has a service with a ledger, secrets that never touched git, a rehearsed rollback, one released Pricing SDK, a cross-platform desktop candidate path, and PRs that review themselves out of existence. The API writes orders into DynamoDB—and *reading* that table well is its own craft, with its own Bean & Bark series: [learn DynamoDB by doing](https://www.kanject.com/docs/dynostudio-bean-bark/). For day-to-day lookup, the [CLI overview](https://www.kanject.com/docs/cli/) maps the neutral references.

---
_Source: https://www.kanject.com/docs/cli-bean-bark-previews/ · Kanject Docs_
