launch-quickly
Decisions

0005 — The template is vendored, not a dependency

Three kinds of code, three upgrade policies. The interesting one is the middle: ours to improve, yours to edit.

Context

A starter kit has to answer one question honestly: when we improve the action client, does your copy improve too?

The two usual answers are both bad. Publish it as a library and customers cannot edit code they own and paid for. Ship it as a snapshot and "lifetime updates" means, as upgrades.mdx puts it, "a changelog you may read and a diff you may apply by hand."

Decision

Three categories, three policies:

Published packages (@launchquickly/* — CLI, ESLint config and plugin, codemods). Upgraded with pnpm up, like any dependency. Nobody edits these.

Vendored and managed — the action client, tenancy primitives, the error taxonomy, the application components. Vendored because the APIs are not stable enough to publish and buyers genuinely do edit them; managed because they are ours to improve. lq upgrade tracks which copies are still the ones we shipped.

Product surface — never touched. src/components/ui is deliberately here: those are shadcn's primitives, re-installable with npx shadcn add, and the first thing anyone restyles.

Consequences

This is what makes the upgrade path possible at all, and it is the commercial moat: the ESLint plugin is MIT on purpose because it is the copyable part. What is not copyable is the manifest, the generators and a tested upgrade path into code a customer has already edited.

The cost is that the boundary has to be exactly right, and getting it wrong is expensive in a way that only shows up at a customer's machine. Two examples, both found by running the upgrade against a real project rather than in CI:

A managed file may not depend on something an upgrade cannot deliver. Managed files are free to import unmanaged ones, and an upgrade delivers only the managed half — so files would land importing modules that were never coming, and the project stopped building with no warning, because nothing looked.

CI was structurally blind to it. The upgrade job synthesises a release by copying the current template and upgrades a project materialised from that same template, so every unmanaged peer already exists. A managed file needing a new unmanaged peer cannot fail there.

That second point generalises past this record: a test whose fixture is derived from the thing under test can only confirm the thing agrees with itself.

On this page