---
name: zc-deploy
description: Deploy zync Cloudflare Workers (zync-www -> dev.zync.is, zync-app -> app.dev.zync.is). Use when asked to deploy www/app/all, ship to dev domains, or push a Worker. Wraps scripts/deploy.sh.
---

# zc-deploy

## Quick deploy

```bash
bash .claude/skills/zc-deploy/scripts/deploy.sh [--www|--app|--all] [--dry-run] [--skip-build]
```

Default target: `--all`. Flags: `--dry-run` (build + `wrangler deploy --dry-run`, no real deploy), `--skip-build` (deploy existing dist).

## Monorepo scope

- pnpm monorepo. Deployable Workers in scope: **zync-www**, **zync-app**.
- **zync-api** exists but is OUT of scope for this skill.
- zync-app has `[[services]]` binding `API` → `zync-api`; app needs zync-api already deployed to function.

## Dev domains

Custom-domain routes in each app's `wrangler.toml`:

| Worker    | Domain              |
|-----------|---------------------|
| zync-www  | `dev.zync.is`       |
| zync-app  | `app.dev.zync.is`   |

## zync-www (Astro SSR)

- Astro SSR: `output: 'server'`, `@astrojs/cloudflare` adapter.
- Build: `pnpm run build` (inside `apps/zync-www`).
- Deploy: `pnpm exec wrangler deploy` inside `apps/zync-www`.
- Wrangler **auto-redirects** to adapter-generated `dist/server/wrangler.json` (via `.wrangler/deploy/config.json`), which injects `main: "entry.mjs"`.
- Root `apps/zync-www/wrangler.toml` intentionally has **no `main` line** — correct.
- **CRITICAL SSR guard**: `wrangler deploy --dry-run` must show `"Attaching additional modules"` listing server chunks (e.g. BaseLayout, `_token_`, server-island-manifest). That proves the SSR Worker is bundled. Exit 0 alone is NOT enough — an assets-only deploy "succeeds" while silently dropping SSR site-wide.

## zync-app (Vite SPA + Worker)

- Vite SPA + hand-written Worker.
- `apps/zync-app/wrangler.toml`: `main = "src/worker.ts"`, assets dir `./dist`, service binding `API` → `zync-api`.
- Build: `pnpm run build` (`tsc --noEmit && vite build && bundle-budget check`).
- Deploy: `pnpm exec wrangler deploy` inside `apps/zync-app` (no redirect; deploys `src/worker.ts` directly).

## CI is stale — do not copy it

`.github/workflows/main.yml` still uses `wrangler pages deploy dist` (Cloudflare Pages → `*.pages.dev`). Current `wrangler.toml` files are **Workers (Static Assets)**. For dev.zync.is deploys ALWAYS use `wrangler deploy` (Workers), never the CI Pages command.

## Auth precheck

Before deploy: `pnpm exec wrangler whoami` must show a logged-in account.

---

## Learned Rules

### eslint-root-workspace | fired:1 | 2026-06-10
Declaring `eslint@^9` only in `packages/config` → wrong; `eslint src` in app packages resolved to global eslint v6 (no flat-config support), silently ignoring the entire custom ruleset incl `no-raw-drizzle`.
Prevent: declare `eslint@^9` at ROOT `package.json` so pnpm installs it in the workspace `.bin`; custom flat-config rules only fire from the workspace root.

### pnpm-filter-real-pkg-name | fired:1 | 2026-06-10
`pnpm --filter @zync/app exec ...` matched nothing → wrong; the package `name` is `zync-app` (no `@zync/` scope). 
Prevent: verify the real name via `node -e 'console.log(require("./package.json").name)'` before using `--filter`; don't assume a scope prefix.

### kv-not-for-write-then-read | fired:1 | 2026-06-10
Using Cloudflare KV for multi-step session state (write step-1 then read it back in step-2) → wrong; KV is eventually-consistent with a 30s minimum edge cacheTtl, so the read may return stale pre-write state and multi-step flows silently break.
Prevent: for strong-consistency session/state-machine data use a Durable Object; reserve KV for read-mostly config. For free read caching use `cache.default`, not quota-bound KV.

### dont-edit-users-uncommitted-toml | fired:1 | 2026-06-10
Guess-editing `wrangler.toml` to "fix" a Pages→Workers mismatch before deploy → wrong; those uncommitted changes were the user's in-progress migration, present at session start. "push deploy" authorized deploying what's there, not rewriting their infra on a hunch.
Prevent: `wrangler deploy --dry-run` to verify SSR bundling empirically; only edit toml if dry-run proves assets-only-no-SSR, and then surface to the user — their migration, their call.

### worktree-deploy-dir | fired:1 | 2026-06-10
Running `wrangler deploy` from project root `/home/user/Projects/zync.is` while working in a worktree → wrong; deploys master code, not worktree changes. Caused repeated 403s with no code change visible.
Prevent: when working in a worktree, always `cd` to the worktree path (e.g. `.claude/worktrees/<id>/apps/zync-api`) before `wrangler deploy`. Verify deployed URL responds with worktree changes before declaring fix done.

