Upgrading

Pull a new ShipKit release into your project with git — add the release repo as a remote, merge a version tag, resolve conflicts, and bring migrations along.

Your project and ShipKit share code, so a new release arrives the way any upstream change does: as a git merge. You keep your history, the template's changes land on top of it, and git tells you exactly where the two touched the same lines.

How releases are published

After purchase your GitHub account is invited as a collaborator to the ShipKit release repo. Each version there is a single commit on main, tagged v<version> (v0.5.0, v0.6.0, …), and each has release notes on the repo's Releases page listing new migrations, new or renamed env keys and breaking changes. Versions follow semver: a minor release may add migrations and optional env keys; a major one has breaking changes or a new required env key.

Read the release notes before you merge. They tell you what to expect in the conflicts and what to do after.

Add the remote once

Use the SSH or HTTPS URL of the release repo you were invited to:

git remote add shipkit <release-repo-url>
git fetch shipkit --tags
git tag -l 'v*'

If you created your project by cloning the release repo, origin already points at it. Rename it to shipkit and add your own repository as origin:

git remote rename origin shipkit
git remote add origin <your-repo-url>

To preview what a release changes before merging it:

git diff v0.5.0 v0.6.0 --stat

Merge a release

Start from a clean working tree on a branch of its own, so an upgrade that goes wrong costs nothing:

git switch -c upgrade-v0.6.0

How the first merge goes depends on how your project began.

Your project started as a clone

Your history already contains the template commit you started from, so git has a common ancestor and only shows you what changed since then:

git merge v0.6.0

Every later upgrade is the same command with the next tag.

Your project started as a copy

If you downloaded the files, or copied them into a fresh git init, your history and the template's have nothing in common, and a plain merge refuses with "refusing to merge unrelated histories". Allow it:

git merge v0.6.0 --allow-unrelated-histories

Without a common ancestor, git cannot tell your edits from the template's changes, so every file that differs on the two sides becomes a conflict. There is a better route when you know which version you copied. Record that version as already merged, without changing any of your files, and then merge the new one:

git merge v0.5.0 --allow-unrelated-histories -s ours -m "Record ShipKit v0.5.0 as merged"
git merge v0.6.0

The first command only links the histories (-s ours keeps your tree exactly as it is). The second is then a normal three-way merge that brings in only what changed between v0.5.0 and v0.6.0. After this first time, your project behaves like a clone: git merge <tag> for every upgrade.

Resolve conflicts

Conflicts cluster where you customised the template. Some patterns:

  • src/config/* (app-config.ts, plans.ts, landing.ts) — you almost always want your values plus the template's new fields. Keep yours and add what the release introduced.

  • messages/en.json, messages/zh.json — both sides added keys. Keep both sets; watch for a missing comma.

  • bun.lock — do not edit it by hand. Take the template's version, then let bun re-add your own dependencies from package.json:

    git checkout --theirs bun.lock
    bun install
    
  • A module you deleted — if the release changed files inside a feature you removed, git reports "deleted by us". Keep it deleted with git rm, then search for new [feature: <name>] blocks the release added to shared files and cut those out too. Deleting features lists what each module touches.

  • .dev.vars.example — take the template's version, then copy any new keys into your own .dev.vars.

When every file is resolved, git add them and git commit. To give up midway, git merge --abort returns you to where you started.

Migrations

Migrations live in drizzle/, numbered, with a snapshot and a journal entry in drizzle/meta/. D1 remembers which migration files it has applied by name.

If you have no migrations of your own, take the template's drizzle/ directory as it comes and apply it:

bun run db:migrate:local

If you added your own migrations, the release's new files will collide with yours: both sides claim the next number, and drizzle/meta/_journal.json conflicts. Your files are already applied to your databases, so keep them and regenerate the template's change as your next migration instead:

  1. Keep your version of every file both sides changed in drizzle/meta: git checkout --ours -- drizzle/meta/_journal.json, and the same for any snapshot git status shows as "both added".
  2. Find the release's new migration files — the .sql files in drizzle/ and the snapshots in drizzle/meta/ that only the release added (git status lists them as new files; the release notes name the migrations). Read the .sql ones, then remove all of them with git rm.
  3. Make sure src/ holds the merged schema — the feature schema.ts files are ordinary source, merged like everything else.
  4. Run bun run db:generate. It compares the merged schema with your latest snapshot and writes the template's changes as a new migration after yours.
  5. If a removed file contained data changes (an UPDATE or INSERT backfill), db:generate cannot see those. Create an empty migration with bunx drizzle-kit generate --custom --name <name> and paste the statements into it, in the order the release had them.

Then bun run db:migrate:local. See Database for how migrations are generated and applied.

Check before you ship

Install, regenerate what is generated, and run the full suite:

bun install
bun run cf-typegen          # if wrangler.jsonc changed
bun run build               # also generates src/paraglide, which typecheck needs
bun run typecheck
bun run check
bun run test
bun run e2e

Restart the dev server afterwards: env is read at startup. Click through the pages you customised — the tests cover the template's behaviour, not your changes to it.

Deploy the upgrade

Merge the branch into main, then:

bunx wrangler secret put <NEW_KEY>   # for each new key in the release notes
bun run db:migrate:remote
bun run deploy

Migrate before deploying, as with any schema change. If CI deploys for you, it refuses to deploy while remote migrations are pending. See Deploy.

A coding agent handles most of this well. Point it at this page and the release notes, and have it stop at each conflict it is unsure about rather than guess. See Working with AI agents.