create-nextblock 0.15.0 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/bin/create-nextblock.js +23 -2
  2. package/docker-template/.dockerignore +2 -1
  3. package/package.json +1 -1
  4. package/scripts/sync-template.js +97 -0
  5. package/templates/nextblock-template/.dockerignore +2 -1
  6. package/templates/nextblock-template/README.md +57 -34
  7. package/templates/nextblock-template/app/api/cron/reset-sandbox/sandboxResetSql.ts +525 -1
  8. package/templates/nextblock-template/app/cms/settings/site-scripts/components/SiteScriptManager.tsx +13 -5
  9. package/templates/nextblock-template/docs/04-DATABASE-AND-AUTH.md +2 -1
  10. package/templates/nextblock-template/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md +26 -11
  11. package/templates/nextblock-template/docs/11-SELF-HOSTED-DOCKER.md +9 -0
  12. package/templates/nextblock-template/docs/12-VERCEL-DEPLOYMENT.md +8 -0
  13. package/templates/nextblock-template/docs/13-STAYING-UP-TO-DATE.md +372 -151
  14. package/templates/nextblock-template/docs/README.md +2 -0
  15. package/templates/nextblock-template/gitignore +3 -0
  16. package/templates/nextblock-template/lib/onboarding/status.ts +11 -5
  17. package/templates/nextblock-template/lib/setup/migrations-bundle.ts +35 -0
  18. package/templates/nextblock-template/lib/updates/check-upstream.ts +167 -46
  19. package/templates/nextblock-template/package.json +6 -1
  20. package/templates/nextblock-template/tools/build-migrate.mjs +102 -209
  21. package/templates/nextblock-template/tools/lib/migrate-core.mjs +569 -0
  22. package/templates/nextblock-template/tools/update.mjs +1285 -0
  23. package/templates/nextblock-template/tsconfig.tsbuildinfo +0 -1
@@ -1,151 +1,372 @@
1
- # 13 · Staying Up to Date (Automated Upstream Updates)
2
-
3
- NextBlock keeps your instance in sync with the upstream project
4
- (`nextblock-cms/nextblock`) with **as little manual work as possible**. How updates
5
- arrive depends on how you deployed, and the system auto-detects which path applies:
6
-
7
- | Install type | Track | How updates arrive |
8
- | :--- | :--- | :--- |
9
- | Vercel 1-click / GitHub fork (git-backed) | **A** | A daily GitHub Action merges upstream and pushes to your deploy branch (→ Vercel CD). |
10
- | `npm create nextblock` / local clone / Docker image (standalone) | **B** | The CMS checks GitHub Releases and shows a "download the new version" banner. |
11
-
12
- Both tracks surface their status in the CMS through a dashboard banner backed by the
13
- `system_alerts` table (migration `00000000000036`). Reads are ADMIN-only (RLS).
14
-
15
- ---
16
-
17
- ## Track A Git-backed installs (Vercel 1-click, GitHub forks)
18
-
19
- The workflow lives at [`.github/workflows/nextblock-sync.yml`](../.github/workflows/nextblock-sync.yml).
20
- It runs **daily at 00:00 UTC** and on demand (**Actions → NextBlock Upstream Sync → Run
21
- workflow**). Each run:
22
-
23
- 1. Merges the upstream release branch into your deploy branch.
24
- 2. **Clean merge** → commits and pushes to your branch, which triggers a normal Vercel
25
- deployment. Any open conflict issue is auto-closed.
26
- 3. **Conflict** → aborts the merge and opens (or updates) a GitHub Issue labeled
27
- `nextblock-sync-conflict`. The CMS mirrors that issue into an **amber banner** on the
28
- dashboard with a link to resolve it. Once you resolve and **close the issue**, the
29
- banner clears automatically.
30
-
31
- ### One-click install (Connect GitHub)
32
-
33
- Vercel's 1-click deploy creates your repo through an integration whose token lacks the
34
- GitHub **`workflow`** scope, so GitHub **strips `.github/workflows/`** from the copy your
35
- new repo won't have the sync workflow even though the template ships it. To fix that with no
36
- token to create, the dashboard onboarding step shows a **Connect GitHub** button:
37
-
38
- 1. Click **Connect GitHub** a short code appears.
39
- 2. Click **Authorize on GitHub**, enter the code, approve.
40
- 3. NextBlock installs `.github/workflows/nextblock-sync.yml` into your repo for you, and the
41
- step turns green.
42
-
43
- This uses GitHub's **device flow** — no token to create, no per-site callback, nothing to
44
- configure (the public client id ships with NextBlock). The authorization requests the
45
- `repo` + `workflow` scopes because GitHub requires them to write a workflow file; NextBlock
46
- uses the grant once to install the file and does **not** store it. Revoke it anytime at
47
- GitHub **Settings Applications**.
48
-
49
- ### Do you need to enable GitHub Actions?
50
-
51
- It depends on how the repository was created:
52
-
53
- - **Vercel 1-click deploy** creates a **new repository you own** (a copy, *not* a GitHub
54
- fork). GitHub **enables Actions by default** on repos you own — **there's nothing to turn
55
- on**. The sync workflow runs automatically once it lands on your repo's **default branch**.
56
- - **A manual GitHub _fork_** (the "Fork" button) has Actions **disabled** by default. Enable
57
- them once: your repo **Actions** tab **"I understand my workflows, go ahead and enable
58
- them."**
59
-
60
- > **Seeing GitHub's "Get started with Actions / choose a workflow" page?** That only means
61
- > your Actions tab is **empty** — `.github/workflows/nextblock-sync.yml` isn't on your
62
- > **default branch** yet (scheduled workflows only run from the default branch). Once it is,
63
- > the tab shows **NextBlock Upstream Sync** with a **Run workflow** button. There is no
64
- > separate "enable" button on an owned repo because Actions are already on.
65
-
66
- The dashboard onboarding step ("Automatic updates (GitHub Actions)") links to **Settings →
67
- Actions**where you can confirm Actions are allowed and completes itself once GitHub
68
- reports the sync workflow as active.
69
-
70
- ### No GitHub secrets required (public forks)
71
-
72
- The conflict signal uses the **`GITHUB_TOKEN` that GitHub provides to every workflow
73
- automatically** you do **not** add any Supabase secret to GitHub. The app writes the
74
- dashboard alert itself using the Supabase key it already has, and reads your repo's
75
- conflict issues over the public GitHub API.
76
-
77
- > **We recommend forking to a _public_ repository** — it's fully zero-config.
78
-
79
- ### Private forks
80
-
81
- If your fork is **private**, the public GitHub API can't read its issues, so add **one**
82
- environment variable to your deployment (Vercel project Settings Environment
83
- Variables, or your `.env`):
84
-
85
- | Variable | Value |
86
- | :--- | :--- |
87
- | `NEXTBLOCK_GITHUB_TOKEN` | A GitHub token with **read access to issues** on your fork (a fine-grained PAT scoped to the repo, or a classic token with `repo`). |
88
-
89
- With that set, the dashboard conflict banner works on private forks too. (The workflow
90
- itself still needs no extra secret `GITHUB_TOKEN` covers it either way.)
91
-
92
- > **⚠️ Vercel Hobby (free) plan + a private repo blocks auto-deploys.** On Hobby, Vercel only
93
- > deploys **private**-repo commits authored by the project owner and rejects automated
94
- > (bot/collaborator) commits so the auto-merge push won't deploy (*"Hobby Plan does not
95
- > support collaboration for private repositories"*). Either **make the repo public**
96
- > (recommended it also makes the conflict banner tokenless) or upgrade to **Vercel Pro**.
97
- > Public repos have no such restriction on Hobby.
98
-
99
- ### How the dashboard stays current (no cron)
100
-
101
- The CMS refreshes update/conflict status **in the background after a dashboard page
102
- loads** (throttled to ~6 hours), so it works on Vercel's Hobby plan without consuming a
103
- cron slot. Admins can also force a check immediately:
104
-
105
- ```
106
- POST /api/cms/check-updates # admin-only; returns the version + conflict status
107
- ```
108
-
109
- ---
110
-
111
- ## Track B — Standalone installs (npm create / local / Docker)
112
-
113
- These installs aren't wired to a GitHub Action, so NextBlock checks the **GitHub Releases
114
- API** and, when a newer release exists, records a `runtime_update_available` alert — an
115
- **indigo banner** on the dashboard with a direct **download link** to the release tarball.
116
- Updating is manual by design: download the archive, replace your files, and update
117
- dependencies (`npm install`). The same admin check endpoint above triggers a check on
118
- demand.
119
-
120
- ---
121
-
122
- ## Schema stays in step with deploys (build-time migrations)
123
-
124
- So a new version's code never runs against an old schema, a build-time hook
125
- ([`apps/nextblock/tools/build-migrate.mjs`](../apps/nextblock/tools/build-migrate.mjs))
126
- applies pending, forward-only migrations **before** `next build`:
127
-
128
- - **Vercel:** runs automatically when `VERCEL_ENV=production`; **preview/development
129
- builds are skipped** so they never touch live data.
130
- - **Standalone / local / Docker:** gated on `NEXTBLOCK_BUILD_MIGRATE=1`, which the
131
- `/setup` wizard and the create/Docker setup scripts write into your env automatically.
132
-
133
- It is **non-destructive and never breaks the build** if the database is unreachable it
134
- logs a warning and continues. Migrations are tracked in `supabase_migrations.schema_migrations`,
135
- identically to the Supabase CLI.
136
-
137
- > **Edge case:** if your project's migration history is empty/inconsistent, the hook skips
138
- > rather than risk misapplying. Run `npm run db:migrate:repair-history` then
139
- > `npm run db:migrate` once to reconcile (see [docs/04](./04-DATABASE-AND-AUTH.md)).
140
-
141
- ---
142
-
143
- ## Quick reference
144
-
145
- | You want… | Do this |
146
- | :--- | :--- |
147
- | Fully hands-off updates | Fork **public**, deploy on Vercel, **enable Actions** once. |
148
- | Conflict banners on a **private** fork | Also set `NEXTBLOCK_GITHUB_TOKEN`. |
149
- | To update a **standalone** install | Watch for the dashboard banner → download → replace → `npm install`. |
150
- | To force an update check now | Dashboard (admin)it polls in the background; or `POST /api/cms/check-updates`. |
151
- | To resolve a sync conflict | Open the linked GitHub issue, merge upstream locally, fix, push, close the issue. |
1
+ # 13 · Staying Up to Date (Updates & Upstream Sync)
2
+
3
+ Every NextBlock install however it was created understands one command:
4
+
5
+ ```bash
6
+ npm run update
7
+ ```
8
+
9
+ It detects which kind of install it is running inside, updates the **code** from the right
10
+ source, installs the matching **dependencies**, and then applies any pending **database
11
+ migrations**. One step, in that order, so the schema never lags behind the code.
12
+
13
+ ```bash
14
+ npm run update # code + dependencies + schema
15
+ npm run update -- --check # report what would change; write nothing
16
+ npm run update -- --yes # never prompt (implied by CI=true)
17
+ npm run update -- --force # run even when already on the latest version
18
+ npm run update -- --skip-db # code + dependencies only
19
+ npm run update -- --db-only # apply pending migrations only
20
+ ```
21
+
22
+ Implementation: [`apps/nextblock/tools/update.mjs`](../apps/nextblock/tools/update.mjs)
23
+ (synced into the standalone template as `tools/update.mjs`), on top of the shared engine
24
+ [`apps/nextblock/tools/lib/migrate-core.mjs`](../apps/nextblock/tools/lib/migrate-core.mjs).
25
+
26
+ ---
27
+
28
+ ## The four installs and their code channel
29
+
30
+ | # | Install | Layout | Code channel `npm run update` uses | Also updates itself? |
31
+ | :-- | :--- | :--- | :--- | :--- |
32
+ | 1 | Vercel 1-click / GitHub fork | Nx monorepo | `git merge upstream/master` | **Yes** — daily GitHub Action |
33
+ | 2 | `npm create nextblock` Docker | flat app | `create-nextblock@latest` on npm | No |
34
+ | 3 | `npm create nextblock` managed cloud | flat app | `create-nextblock@latest` on npm | No |
35
+ | 4 | `git clone` the monorepo | Nx monorepo | `git pull --ff-only origin` | No |
36
+
37
+ > **The Action is about layout, not hosting.** `nextblock-sync.yml` merges the **monorepo**
38
+ > into your repository, so it only works where your repository *is* the monorepo (rows 1
39
+ > and 4). Pushing a `npm create nextblock` project to GitHub and deploying it on Vercel does
40
+ > **not** make it eligible — its tree is `app/`, `components/`, `lib/` at the root, and
41
+ > merging `apps/`, `libs/` and `nx.json` into that would wreck it. Docker is orthogonal: it
42
+ > is how you *run* a project, not what shape its repository is.
43
+ >
44
+ > This is enforced, not just documented. `isMonorepoInstall()` in
45
+ > `apps/nextblock/lib/updates/check-upstream.ts` reads the `nextblock.install` marker in the
46
+ > bundled `package.json` (`"monorepo"` in `apps/nextblock`, overwritten to `"standalone"` by
47
+ > the scaffolder), so it works on a serverless filesystem where neither `nx.json` nor
48
+ > `.github/` is traced into the function. Both the update-track classification and the
49
+ > dashboard's **Connect GitHub** step key off it. They previously keyed off
50
+ > `process.env.VERCEL === '1'`, which meant a standalone app on Vercel was offered a
51
+ > workflow that would have merged the monorepo into it — *and* had its update banner
52
+ > suppressed, leaving it silently frozen with nothing to update it.
53
+
54
+ Detection is structural, not configured: a workspace with `nx.json` **and**
55
+ `libs/db/src/supabase/migrations` is the monorepo; anything else is a standalone project.
56
+ Within the monorepo, an `origin` pointing at `nextblock-cms/nextblock` is a contributor
57
+ clone (fast-forward pull); any other origin is a fork (merge from an `upstream` remote,
58
+ added automatically if missing).
59
+
60
+ ### Why standalone installs update from npm, not from a GitHub release
61
+
62
+ A standalone project's layout is `app/`, `components/`, `lib/` at the root. A GitHub source
63
+ archive of this repository is the **monorepo** layout (`apps/`, `libs/`, `tools/`) it
64
+ cannot be unpacked over a flat project at all, and the project has no remote to pull from
65
+ (`npm create nextblock` runs `git init`, with no initial commit and no remote). The
66
+ published `create-nextblock` package, by contrast, ships the complete standalone template
67
+ under `templates/nextblock-template/` the exact artifact the project was scaffolded from
68
+ and is versioned in lockstep with the app by `tools/scripts/release-cli.js`.
69
+
70
+ ### Standalone updates are a real 3-way merge, not an overwrite
71
+
72
+ `npm run update` gives a standalone project the same experience as `git pull`, without an
73
+ upstream to pull from. It downloads the template for the version you are **on**
74
+ (`package.json` `nextblock.version`) and the template for the **new** version, then walks
75
+ every framework file and picks the cheapest correct action:
76
+
77
+ | Situation | What happens |
78
+ | :--- | :--- |
79
+ | You don't have the file | Upstream added it — copied in. |
80
+ | Upstream didn't change it | Left completely alone, edits and all. |
81
+ | You never edited it | Replaced with the new version. |
82
+ | **Both** changed it | Real 3-way merge via `git merge-file`; conflicts get markers. |
83
+
84
+ So a file you never touched updates silently; a file you **customised keeps your edit**; and
85
+ only a change that genuinely overlaps yours conflicts — with ordinary
86
+ `<<<<<<< your version` / `>>>>>>> NextBlock <version>` markers. The updater lists the
87
+ conflicted files by name. Resolve them as you would any conflict, discard one file's merge
88
+ with `git checkout -- <file>`, or undo everything with `git reset --hard HEAD`.
89
+
90
+ Nothing is committed or staged for you: the result is plain working-tree changes you review
91
+ with `git status` and `git diff`, then commit yourself.
92
+
93
+ > **Why `git merge-file` and not `git apply --3way`?** `--3way` implies `--index`, which
94
+ > drags in three couplings this has no need of: it *stages* its result (so `git diff` shows
95
+ > you nothing), it requires every patched path to be tracked (a single framework path your
96
+ > project happens to `.gitignore` aborts the entire update with
97
+ > `does not exist in index`), and it requires the worktree to match the index (so a dev
98
+ > server regenerating `next-env.d.ts` mid-run aborts it too). `git merge-file` is plain
99
+ > file-in/file-out and touches no git state at all. Binary files are never merged
100
+ > textually — yours is kept and the updater says so.
101
+
102
+ Requirements: a git repository, at least one commit, and a clean working tree. Those are for
103
+ *reviewability*, not for the merge itself they are what make `git diff` and
104
+ `git reset --hard HEAD` mean something. Cleanliness is re-checked immediately before the
105
+ merge, because staging the base runs a network download and the confirmation prompt can wait
106
+ on a human.
107
+
108
+ **Framework-owned paths** (what the merge covers): `app/`, `components/`, `context/`,
109
+ `hooks/`, `lib/`, `types/`, `tools/`, `scripts/`, `docker/`, `docs/`, `proxy.ts`,
110
+ `index.d.ts`, `next-env.d.ts`, `postcss.config.js`, `eslint.config.mjs`, `Dockerfile`,
111
+ `docker-compose.yml`, `.dockerignore`, `AGENTS.md`, `CLAUDE.md`.
112
+
113
+ **Never touched**: `.env*`, `public/`, `README.md`, `.gitignore`, `.npmrc`, and the four
114
+ files the scaffolder generates per project `next.config.js`, `tailwind.config.js`,
115
+ `tsconfig.json`, and `package.json` (which is **merged**, never replaced).
116
+
117
+ **It never deletes.** Files you added yourself survive; a file removed upstream is left in
118
+ place rather than pruned (`--diff-filter=ACMR`).
119
+
120
+ #### The fallback
121
+
122
+ If there is no git repository, no commits yet, or the tree is dirty, there is nothing to
123
+ merge *against*, so the updater copies the files instead and puts a copy of anything it
124
+ replaced under `.nextblock-backup/<timestamp>/` (self-ignoring, via a nested `.gitignore`).
125
+ This is the degraded path, not the normal one — committing your work first is what gets you
126
+ the merge. A file that cannot be merged (a binary asset, or a `git merge-file` failure) is
127
+ never clobbered: your version is kept and the updater names it in the output.
128
+
129
+ `package.json` merge rules: third-party ranges (`next`, `react`, `tailwindcss`, …) are taken
130
+ verbatim from the new template that is the dependency update. `@nextblock-cms/*` entries
131
+ already present are left alone (the scaffolder writes floating ranges, so `npm install`
132
+ picks up new libs on its own); new ones are added as `latest`. Your `name`, `version`,
133
+ `overrides` and any scripts or dependencies you added are preserved, and direct
134
+ dependencies are re-aligned to their `overrides` spec afterwards so `npm install` cannot
135
+ fail with `EOVERRIDE`.
136
+
137
+ ### The version stamp
138
+
139
+ `package.json` carries `nextblock.version` the NextBlock release the project is on,
140
+ written by the scaffolder and re-written by each successful update. The project's own
141
+ `version` field belongs to you; the moment you bump it for your own site, comparing a
142
+ release against it would be meaningless. Both `npm run update` and the dashboard's update
143
+ check read the stamp, falling back to `version` only for projects created before it existed.
144
+
145
+ ---
146
+
147
+ ## Track A Git-backed installs update themselves
148
+
149
+ The workflow lives at [`.github/workflows/nextblock-sync.yml`](../.github/workflows/nextblock-sync.yml).
150
+ It runs **daily at 00:00 UTC** and on demand (**ActionsNextBlock Upstream Sync Run
151
+ workflow**). Each run:
152
+
153
+ 1. Merges the upstream release branch into your deploy branch.
154
+ 2. **Clean merge** → commits and pushes to your branch, which triggers a normal Vercel
155
+ deployment. Any open conflict issue is auto-closed.
156
+ 3. **Conflict** → aborts the merge and opens (or updates) a GitHub Issue carrying the hidden
157
+ marker `<!-- nextblock-sync-conflict -->`. The CMS mirrors that issue into an **amber
158
+ banner** on the dashboard with a link to resolve it. Once you resolve and **close the
159
+ issue**, the banner clears automatically.
160
+
161
+ Running `npm run update` on a local clone of that fork performs the same merge immediately,
162
+ rather than waiting for midnight.
163
+
164
+ ### One-click install (Connect GitHub)
165
+
166
+ Vercel's 1-click deploy creates your repo through an integration whose token lacks the
167
+ GitHub **`workflow`** scope, so GitHub **strips `.github/workflows/`** from the copy — your
168
+ new repo won't have the sync workflow even though the template ships it. To fix that with no
169
+ token to create, the dashboard onboarding step shows a **Connect GitHub** button:
170
+
171
+ 1. Click **Connect GitHub** — a short code appears.
172
+ 2. Click **Authorize on GitHub**, enter the code, approve.
173
+ 3. NextBlock installs `.github/workflows/nextblock-sync.yml` into your repo for you, and the
174
+ step turns green.
175
+
176
+ This uses GitHub's **device flow** — no token to create, no per-site callback, nothing to
177
+ configure (the public client id ships with NextBlock). The authorization requests the
178
+ `repo` + `workflow` scopes because GitHub requires them to write a workflow file; NextBlock
179
+ uses the grant once to install the file and does **not** store it. Revoke it anytime at
180
+ GitHub → **Settings → Applications**.
181
+
182
+ ### Do you need to enable GitHub Actions?
183
+
184
+ It depends on how the repository was created:
185
+
186
+ - **Vercel 1-click deploy** creates a **new repository you own** (a copy, *not* a GitHub
187
+ fork). GitHub **enables Actions by default** on repos you own — **there's nothing to turn
188
+ on**. The sync workflow runs automatically once it lands on your repo's **default branch**.
189
+ - **A manual GitHub _fork_** (the "Fork" button) has Actions **disabled** by default. Enable
190
+ them once: your repo → **Actions** tab → **"I understand my workflows, go ahead and enable
191
+ them."**
192
+
193
+ > **Seeing GitHub's "Get started with Actions / choose a workflow" page?** That only means
194
+ > your Actions tab is **empty** — `.github/workflows/nextblock-sync.yml` isn't on your
195
+ > **default branch** yet (scheduled workflows only run from the default branch). Once it is,
196
+ > the tab shows **NextBlock Upstream Sync** with a **Run workflow** button. There is no
197
+ > separate "enable" button on an owned repo because Actions are already on.
198
+
199
+ ### No GitHub secrets required (public forks)
200
+
201
+ The conflict signal uses the **`GITHUB_TOKEN` that GitHub provides to every workflow
202
+ automatically** — you do **not** add any Supabase secret to GitHub. The app writes the
203
+ dashboard alert itself using the Supabase key it already has, and reads your repo's
204
+ conflict issues over the public GitHub API.
205
+
206
+ > **We recommend forking to a _public_ repository** — it's fully zero-config.
207
+
208
+ ### Private forks
209
+
210
+ If your fork is **private**, the public GitHub API can't read its issues, so add **one**
211
+ environment variable to your deployment (Vercel project → Settings → Environment
212
+ Variables, or your `.env`):
213
+
214
+ | Variable | Value |
215
+ | :--- | :--- |
216
+ | `NEXTBLOCK_GITHUB_TOKEN` | A GitHub token with **read access to issues** on your fork (a fine-grained PAT scoped to the repo, or a classic token with `repo`). |
217
+
218
+ With that set, the dashboard conflict banner works on private forks too. (The workflow
219
+ itself still needs no extra secret — `GITHUB_TOKEN` covers it either way.)
220
+
221
+ > **⚠️ Vercel Hobby (free) plan + a private repo blocks auto-deploys.** On Hobby, Vercel only
222
+ > deploys **private**-repo commits authored by the project owner and rejects automated
223
+ > (bot/collaborator) commits — so the auto-merge push won't deploy (*"Hobby Plan does not
224
+ > support collaboration for private repositories"*). Either **make the repo public**
225
+ > (recommended — it also makes the conflict banner tokenless) or upgrade to **Vercel Pro**.
226
+ > Public repos have no such restriction on Hobby.
227
+
228
+ ---
229
+
230
+ ## Track B — the "update available" banner
231
+
232
+ Standalone installs aren't wired to a GitHub Action, so the CMS polls for a newer release
233
+ and records a `runtime_update_available` alert — an **indigo banner** on the dashboard
234
+ naming the version you're on, the version available, and the command to run.
235
+
236
+ The version signal is the **npm registry** (`create-nextblock`'s `latest` dist-tag), with
237
+ GitHub Releases as a fallback and as the source of release-notes/archive links. npm is
238
+ authoritative because releases are cut by hand and have in practice sat several minor
239
+ versions behind the real version — which silently disabled this check entirely. Whichever
240
+ source is ahead wins.
241
+
242
+ Dismissing the banner is respected: the dedupe key is the *version*, not the unresolved
243
+ state, so a dismissed alert is not re-inserted on the next poll, while a genuinely newer
244
+ version still raises a fresh one.
245
+
246
+ ### How the dashboard stays current (no cron)
247
+
248
+ The CMS refreshes update/conflict status **in the background after a dashboard page
249
+ loads** (throttled to ~6 hours), so it works on Vercel's Hobby plan without consuming a
250
+ cron slot. Admins can also force a check immediately:
251
+
252
+ ```
253
+ POST /api/cms/check-updates # admin-only; returns the version + conflict status
254
+ ```
255
+
256
+ ---
257
+
258
+ ## Where the migration SQL lives (and why that used to bite)
259
+
260
+ The `.sql` files in `libs/db/src/supabase/migrations` are the source of truth, and they
261
+ reach a running install through **four** carriers:
262
+
263
+ | Carrier | Produced by | Read by |
264
+ | :--- | :--- | :--- |
265
+ | `libs/db/src/supabase/migrations/*.sql` | hand-authored | the monorepo: `npm run update`, `npm run db:migrate`, the build hook |
266
+ | inside the `@nextblock-cms/db` npm package | `tools/scripts/copy-db-supabase.cjs` | `npm run update` on a standalone project |
267
+ | `<project>/supabase/migrations` | the CLI, at scaffold time | the build hook, and the Docker `migrate` service |
268
+ | `apps/nextblock/lib/setup/migrations-bundle.ts` | `npm run generate:migrations-bundle` | the `/setup` wizard on serverless hosts |
269
+
270
+ `<project>/supabase/migrations` is materialized **once**, when the project is created.
271
+ Nothing used to refresh it, so upgrading `@nextblock-cms/db` moved the generated TypeScript
272
+ types forward while leaving the schema behind — the app would then fail at runtime with
273
+ `column "…" does not exist`. Two changes close that gap:
274
+
275
+ - `npm run update` re-copies the package's migrations into `<project>/supabase/migrations`
276
+ **after** `npm install` and **before** applying anything.
277
+ - `migrate-core.collectMigrations()` **unions** all reachable directories (monorepo →
278
+ project → `node_modules/@nextblock-cms/db`), deduping by version, so even a user who only
279
+ ran `npm install` gets the new SQL.
280
+
281
+ ### Build-time migrations
282
+
283
+ A build-time hook ([`apps/nextblock/tools/build-migrate.mjs`](../apps/nextblock/tools/build-migrate.mjs))
284
+ applies pending, forward-only migrations **before** `next build`, so a new version's code
285
+ never runs against an old schema:
286
+
287
+ - **Vercel:** runs automatically when `VERCEL_ENV=production`; **preview/development
288
+ builds are skipped** so they never touch live data.
289
+ - **Standalone / local / Docker:** gated on `NEXTBLOCK_BUILD_MIGRATE=1`, which the
290
+ `/setup` wizard and the create/Docker setup scripts write into your env automatically.
291
+
292
+ It is **non-destructive and never breaks the build** — if the database is unreachable it
293
+ logs a warning and continues. That guarantee is why it loads `migrate-core.mjs` through a
294
+ *guarded dynamic import*: an unresolvable top-level import is a hard ESM error no `try`
295
+ could contain.
296
+
297
+ ### Conflicts hold the schema back
298
+
299
+ If a standalone merge left conflicts, `npm run update` finishes the code and dependency
300
+ work — merged files, `npm install`, the refreshed migration SQL, the version stamp — and
301
+ then **stops before migrating**. The schema never moves ahead of code the developer has not
302
+ finished deciding on, and it stays untouched if they abandon the update entirely.
303
+
304
+ Resolving is the only extra step; there is no separate resume command. Re-running
305
+ `npm run update` sees the code is already current, skips straight to the schema step, and
306
+ applies the migrations. Before it does, it re-checks with
307
+ `git grep -lE '^<<<<<<< your version$'` and refuses while any marker remains — the label is
308
+ the one `mergeTemplates()` passes to `git merge-file`, so it cannot be confused with a
309
+ conflict from the developer's own git work, and it is anchored to the start of a line so
310
+ source that merely mentions the string does not trip it.
311
+
312
+ `git reset --hard HEAD` walks away from the whole thing; the database was never touched.
313
+
314
+ ### The contract every applier honours
315
+
316
+ Whether SQL is applied by `npm run update`, the build hook, the `/setup` wizard or the
317
+ Supabase CLI, the rules are the same:
318
+
319
+ - **Forward-only.** Applied versions are skipped by version number, so re-running is safe.
320
+ - **One transaction per file.** Each migration and its history row commit together; a
321
+ failure rolls back and leaves the database exactly as it was.
322
+ - **Tracked in `supabase_migrations.schema_migrations`**, identically to the Supabase CLI.
323
+ - **Two backends**, preferred in order: the Supabase Management API (when
324
+ `SUPABASE_ACCESS_TOKEN` + a project ref are available — robust on IPv4 build networks),
325
+ otherwise a direct Postgres connection via `POSTGRES_URL` / `DATABASE_URL`.
326
+
327
+ > **Edge case:** if your project's migration history is empty/inconsistent, the hook skips
328
+ > rather than risk misapplying. Run `npm run db:migrate:repair-history` then
329
+ > `npm run db:migrate` once to reconcile (see [docs/04](./04-DATABASE-AND-AUTH.md)).
330
+
331
+ ---
332
+
333
+ ## Adding a migration (maintainers)
334
+
335
+ Migrations are **append-only**. List the folder and take the number after the highest file
336
+ on disk — do not trust a number hardcoded in any doc:
337
+
338
+ ```bash
339
+ ls libs/db/src/supabase/migrations | tail -1
340
+ ```
341
+
342
+ After adding one, regenerate everything derived from the folder:
343
+
344
+ ```bash
345
+ npm run db:migrate:check # read-only preview of the pending list
346
+ npm run db:migrate # apply
347
+ npm run db:types # regenerate Supabase TypeScript types
348
+ npm run generate:migrations-bundle # refresh the /setup wizard's embedded copy
349
+ npm run generate:sandbox # refresh the sandbox reset payload
350
+ npm run sync:create-nextblock # propagate into the standalone template
351
+ ```
352
+
353
+ `generate:migrations-bundle` and `generate:sandbox` are easy to forget and fail silently —
354
+ the bundle sat three migrations behind for a while, which would have left a serverless
355
+ `/setup` applying an incomplete schema. Treat the block above as one unit of work.
356
+
357
+ ---
358
+
359
+ ## Quick reference
360
+
361
+ | You want… | Do this |
362
+ | :--- | :--- |
363
+ | To update **any** install | `npm run update` |
364
+ | To see what would change first | `npm run update -- --check` |
365
+ | Fully hands-off updates | Fork **public**, deploy on Vercel, **enable Actions** once. |
366
+ | To update a **Docker** install | `npm run update` then `npm run docker:up` |
367
+ | To apply only pending migrations | `npm run update -- --db-only` |
368
+ | Conflict banners on a **private** fork | Also set `NEXTBLOCK_GITHUB_TOKEN`. |
369
+ | To force an update check now | Dashboard (admin) → it polls in the background; or `POST /api/cms/check-updates`. |
370
+ | To resolve a sync conflict | Open the linked GitHub issue, merge upstream locally, fix, push, close the issue. |
371
+ | To undo an update | Standalone: `git reset --hard HEAD` (or restore from `.nextblock-backup/` if the fallback ran). Git-backed: `git revert`. |
372
+ | To keep your edits to a framework file | Commit before updating — the merge preserves them and conflicts only on real overlaps. |
@@ -18,6 +18,7 @@ library surfaces rather than historical planning notes.
18
18
  - Custom blocks (data-driven CRUD): [10-CUSTOM-BLOCKS.md](./10-CUSTOM-BLOCKS.md)
19
19
  - Self-hosted local Docker stack: [11-SELF-HOSTED-DOCKER.md](./11-SELF-HOSTED-DOCKER.md)
20
20
  - One-click cloud deploy (Deploy to Vercel): [12-VERCEL-DEPLOYMENT.md](./12-VERCEL-DEPLOYMENT.md)
21
+ - Updating an install (`npm run update`, upstream sync): [13-STAYING-UP-TO-DATE.md](./13-STAYING-UP-TO-DATE.md)
21
22
 
22
23
  ## Audience Guide
23
24
 
@@ -30,6 +31,7 @@ library surfaces rather than historical planning notes.
30
31
  - Publishing the libraries / scaffold CLI: read `06`.
31
32
  - Running everything locally without cloud accounts: read `11`.
32
33
  - One-click cloud deploy and the browser setup wizard: read `12`.
34
+ - Updating an existing install, whichever way it was created: read `13`.
33
35
  - AI agents: start with this index, then move directly to the subsystem file that
34
36
  matches the task. Treat `apps/nextblock`, `libs/*`, and
35
37
  `libs/db/src/supabase/migrations` as the final authority if a doc and code ever
@@ -26,3 +26,6 @@ pnpm-debug.log*
26
26
 
27
27
  supabase/.temp
28
28
  supabase/.branches
29
+
30
+ # Local safety copies written by `npm run update` when it replaces a framework file.
31
+ .nextblock-backup/
@@ -5,9 +5,9 @@ import { createClient } from '@nextblock-cms/db/server';
5
5
  import { getStoreConfigStatus } from '@nextblock-cms/ecommerce/server';
6
6
  import { getEmailPublicSettings } from '../config/email-settings';
7
7
  import { getPrivacySettings } from '../privacy/settings';
8
- import { detectChannel } from '../setup/env-status';
9
8
  import { getSystemConfiguration } from '../setup/system-config';
10
9
  import { selfActionsSettingsUrl } from '../updates/repo-identity';
10
+ import { isMonorepoInstall } from '../updates/check-upstream';
11
11
  import { isGithubConnectAvailable } from '../updates/github-device';
12
12
 
13
13
  export type OnboardingStep = {
@@ -178,10 +178,16 @@ export async function getOnboardingStatus(opts: {
178
178
  });
179
179
  }
180
180
 
181
- // Git-backed (Vercel 1-click / fork) installs: remind the operator to enable GitHub
182
- // Actions so the upstream sync workflow can run. "done" flips once the background poll
183
- // (maybeRefreshUpstreamStatus) has seen the workflow run at least once.
184
- if (detectChannel() === 'vercel') {
181
+ // Monorepo-shaped installs (Vercel 1-click, GitHub fork, clone): remind the operator to
182
+ // enable GitHub Actions so the upstream sync workflow can run. "done" flips once the
183
+ // background poll (maybeRefreshUpstreamStatus) has seen the workflow registered.
184
+ //
185
+ // Gated on the LAYOUT, not on the host. This step's Connect GitHub button installs a
186
+ // workflow that merges the NextBlock monorepo into the repository; offering it to a
187
+ // flattened standalone project — which the previous "is this Vercel?" gate did for every
188
+ // standalone app deployed on Vercel — would merge apps/ + libs/ + nx.json into a tree
189
+ // that has none of them. Standalone installs update with `npm run update` instead.
190
+ if (isMonorepoInstall()) {
185
191
  let actionsActive = false;
186
192
  try {
187
193
  const config = await getSystemConfiguration();