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 frompackage.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:
- Keep your version of every file both sides changed in
drizzle/meta:git checkout --ours -- drizzle/meta/_journal.json, and the same for any snapshotgit statusshows as "both added". - Find the release's new migration files — the
.sqlfiles indrizzle/and the snapshots indrizzle/meta/that only the release added (git statuslists them as new files; the release notes name the migrations). Read the.sqlones, then remove all of them withgit rm. - Make sure
src/holds the merged schema — the featureschema.tsfiles are ordinary source, merged like everything else. - 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. - If a removed file contained data changes (an
UPDATEorINSERTbackfill),db:generatecannot see those. Create an empty migration withbunx 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.