Updating the Codebase
Pull new DirectoryLaunch releases into your customized project — add the upstream remote, run the pre-flight, merge a release tag, and keep your branding, config and copy intact.
You own a copy of the boilerplate. Your branding, your pages, your database — none of it lives on our servers. Updates work the way updates work in any git project: we publish a release, you merge it, you resolve the handful of places where your work and ours overlap.
Merge a release tag (v3.0.0), never upstream/main. main is whatever was pushed
most recently, including work in progress. A tag is a version we finished, tested and wrote
release notes for.
Setup — once, right after you clone
pnpm setup:upstreamThat single command does three things, all of them local to your clone:
| It sets up | Why it matters |
|---|---|
The upstream remote + tags | So you can see and merge our releases |
The ours merge driver | Your config/*.config.ts, messages/*.json and public/assets/** survive a merge untouched — no conflict, no lost branding |
git rerere | A conflict you resolve once stays resolved in every future release |
Git ships only three built-in merge behaviours (text, binary, union) — ours is not
one of them, so it has to be configured per clone. The rules live in .gitattributes in your
repo and do nothing until pnpm setup:upstream has run. If you skip it, updates still work;
you just resolve your config files by hand.
Every update
# 1. See what you're getting into. Read-only, changes nothing.
pnpm upgrade:check
# 2. Rehearse it. Performs the merge in a throwaway worktree and reports
# the conflicts, then deletes it. Your repository is never touched.
pnpm upgrade:try --build
# 3. Do it for real, on a branch
git checkout -b update-v3.0.0
git merge v3.0.0
# 4. Resolve whatever conflicts remain (see below)
# 5. Catch up dependencies, configs and database
pnpm install
pnpm fix:configs --apply # when a release says so — see below
pnpm db:status
# 6. Prove it compiles AND runs, then merge into your main
npx tsc --noEmit
pnpm build
pnpm verify:smokepnpm upgrade:try answers "how bad is this going to be?" before you create a branch.
It merges in a disposable git worktree, prints the conflicts and the number of files
affected, optionally builds the result with --build, then throws the worktree away.
Nothing in your repository changes, not even your working tree.
What pnpm upgrade:check tells you
It is the whole reason this is not scary. Before you touch anything, it prints:
- the release you are on and the newest one available
- the changelog entries between the two
- migrations this release adds that your database hasn't got
- env vars this release adds that are missing from your
.env.local - the conflict forecast — the exact list of files you have changed that this release also changes, split into "handled for you" and "you will resolve by hand"
If the forecast is empty, the merge is mechanical.
Your config inherits from ours
Every config/*.config.ts is protected on merge, so your version survives. On its own that
also meant a key we added was silently withheld — and it happened: siteConfig.legal and
siteConfig.poweredByBadge shipped in 1.18.0 and reached nobody who had cloned earlier.
Since 2.0.0 each config is wrapped in a defineX({ ... }) call and layers over
config/defaults/. Your values still win; only keys you have not set fall through to ours.
pnpm fix:configs --apply
npx tsc --noEmitThe wrapper cannot arrive on its own — your configs are exactly the files a merge keeps.
Until you run it they stop inheriting, and lib/config-guard.ts deliberately fails the
typecheck rather than letting that pass quietly. The script names anything it will not
touch; those are two lines by hand — an import, and wrapping the export in defineX({ ... }).
Your legal, FAQ and help copy inherits the same way. If you never rewrote those pages you are
running our example text, and our corrections to it now reach you. To take a document over,
copy it out of config/defaults/legal.defaults.ts (or faq.defaults.ts, help.defaults.ts)
into your own config — from then on it is yours in full and stops receiving our changes.
Ten configs held business logic as well as data — including resolveCheckoutMode, which
every buy button calls. Code in a file we can never update is a fix that can never reach you,
so it moved to lib/. If you import a helper from a config, the path changed:
getPlan → @/lib/plans, resolveCheckoutMode → @/lib/verticals/store/product,
planSeesLeadContact → @/lib/quotes, isFreemailDomain → @/lib/claims,
hasRegionTier → @/lib/local-seo/slug. UPGRADING.md in your repo has the full table.
The configs hold data only now.
Resolving conflicts
pnpm upgrade:check sorts the forecast into three groups, and each is a different kind of
work:
| Group | Paths | What to do |
|---|---|---|
| Handled for you | config/*.config.ts, messages/*.json, public/assets/** | Nothing. Your version wins automatically. |
| Yours, deliberately unprotected | app/(marketing)/**, app/(dashboard)/**, components/layout/**, app/globals.css | Keep your layout, take our logic. These carry your design and our fixes at once, so locking them to your version would strand you on the release you cloned. |
| Ours | lib/**, app/api/**, components/ui/**, middleware.ts, supabase/**, types/** | Take upstream. If you cannot, that edit wants to move somewhere it survives a merge. |
git checkout --ours config/site.config.ts # keep your branding
git checkout --theirs lib/supabase/auth-helpers.ts # take our fix
git add <file>Since 1.21.0 that map lives in ownership.json in your repo root, with a reason for every
path. .gitattributes is generated from it (pnpm gen:ownership) and pnpm check:ownership
fails CI if the two disagree — they used to, which is how seven config files spent several
releases unprotected.
After keeping your version of a config file, skim our version for new keys and copy them
across — git diff HEAD...MERGE_HEAD -- config/site.config.ts shows you what we added.
Where conflicts actually happen
Most customizations never conflict, because they aren't in code:
- Colours, typography, radius, shadows — stored in your database, edited at
/admin/design. Nothing to merge. - Card and page layouts, map defaults — same, database-backed.
- Brand name, logos, contact details, plans, feature flags, pricing cards, FAQ — in
config/, protected by the merge driver. Keys added by a release still reach you: your config holds your overrides, and anything you have not set falls back toconfig/defaults/, which is core and does update.
Conflicts come from editing page files. If you rewrote
app/(marketing)/pricing/page.tsx or your Header.tsx, those will conflict whenever we
change them — that's honest, unavoidable git, and the changelog flags those releases with
[ui] so you see it coming.
See What to customize for the full map.
Taking only part of a release
Sometimes you want the API fix and not the redesign. That is possible here because no commit mixes the two — a technical change and a visual change never share a commit.
pnpm upgrade:pick v3.0.0lists the release's commits split into core (lib/, app/api/, components/ui/ — you
never edited these) and customer-facing (marketing pages, layout, styles, config). Then:
git checkout -b core-of-v3.0.0
pnpm upgrade:pick v3.0.0 --applycherry-picks only the core commits and leaves every customer-facing one behind.
A commit you skip comes back the moment you merge that release in full. Partial upgrades are a way to take a security fix today and deal with a redesign next month — not a way to fork permanently.
Does it still run?
pnpm build proves the code compiles. It does not prove a page renders.
pnpm verify:smokeboots the built app and checks that the home page, pricing, FAQ, categories, sign-in, dashboard, admin, the projects API, the sitemap and robots.txt all answer. Routes behind a feature flag you turned off are skipped. It is the difference between "it compiles" and "it works", and it takes about a minute.
Database migrations
Run this after every merge:
pnpm db:statusIt compares the migration files in supabase/migrations/ against a ledger table in your own
Supabase project and prints exactly which ones you still owe. Paste those files into the
Supabase SQL Editor in the order shown.
Run supabase/migrations/0000_migrations_ledger.sql once, then re-run any migration you're
unsure about. Every migration file is idempotent and records itself in the ledger, so
re-running is exactly how an existing project back-fills its history. After that,
pnpm db:status always knows the truth.
Let AI do it
The repo ships an /upgrade slash command for Claude Code. It runs the pre-flight, creates
the branch, merges the tag, applies the who-wins rule file by file, lists pending migrations
and runs lint + typecheck + build:
/upgrade v3.0.0
Using Cursor or another assistant instead? Paste this:
If something goes wrong
You merged on a branch, so nothing is lost:
git merge --abort # mid-merge, undo it
git checkout main && git branch -D update-v3.0.0 # after the factYour production site is unaffected until you merge that branch into main and deploy.
Downloaded a ZIP instead of cloning?
There is no upgrade path from a ZIP — a snapshot has no history to merge against. Move to git once:
git init && git add . && git commit -m "chore: my customized DirectoryLaunch"
git remote add origin https://github.com/you/my-directory.git
git push -u origin main
pnpm setup:upstreamThe first merge after that needs --allow-unrelated-histories and will be noisy. Every
release after it behaves normally.
Do you have to update?
No. A version you deployed keeps working. Merge a release when it contains something you
want, or when it is a security release — those are PATCH bumps in
the changelog and always safe to take.