create-meith 0.30.1 → 0.32.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-meith",
3
- "version": "0.30.1",
3
+ "version": "0.32.0",
4
4
  "description": "Scaffold a Meith board, plugin or theme — npx create-meith <name> writes a deployable board workspace; --plugin and --theme write extension workspaces built on the published kits.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/bin.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { run } from './cli'
3
3
 
4
- const result = await run(process.argv.slice(2), '0.30.1')
4
+ const result = await run(process.argv.slice(2), '0.32.0')
5
5
  for (const line of result.lines) {
6
6
  if (result.code === 0) console.log(line)
7
7
  else console.error(line)
package/src/cli.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  scaffoldExtension,
11
11
  validateExtensionName,
12
12
  } from './scaffold-extension'
13
+ import { type RunUpdateOptions, runUpdate } from './update'
13
14
 
14
15
  const execFileAsync = promisify(execFile)
15
16
 
@@ -36,7 +37,11 @@ async function initGit(target: string): Promise<boolean> {
36
37
  }
37
38
  }
38
39
 
39
- export async function run(argv: readonly string[], version: string): Promise<CliResult> {
40
+ export async function run(
41
+ argv: readonly string[],
42
+ version: string,
43
+ updateOptions: RunUpdateOptions = {},
44
+ ): Promise<CliResult> {
40
45
  const positional: string[] = []
41
46
  for (let i = 0; i < argv.length; i++) {
42
47
  const arg = argv[i] as string
@@ -52,22 +57,32 @@ export async function run(argv: readonly string[], version: string): Promise<Cli
52
57
  return {
53
58
  code: 0,
54
59
  lines: [
55
- 'create-meith — scaffold a forum project, a plugin or a theme.',
60
+ 'create-meith — scaffold a forum project, a plugin or a theme, or update one.',
56
61
  '',
57
62
  ' npx create-meith <name> [--repo <url>] [--no-git]',
58
63
  ' npx create-meith --plugin <name> [--repo <url>] [--no-git]',
59
64
  ' npx create-meith --theme <name> [--repo <url>] [--no-git]',
65
+ ' npx create-meith@latest update',
60
66
  '',
61
67
  'The first form writes a deployable board into ./<name>, then tells you',
62
68
  'what to run. --plugin and --theme write an extension workspace instead:',
63
69
  'source and a passing test copied from the meith repository’s worked',
64
70
  'examples, plus a README and a marketplace listing.json.',
65
71
  '',
72
+ '`update`, run inside a board, moves it to this release: every @meith/*',
73
+ 'pin and next together in package.json, and the deploy files the scaffold',
74
+ 'owns rewritten to the new shape — files you have edited are left alone',
75
+ 'and named. `update` is a reserved word, so a board cannot take it as a name.',
76
+ '',
66
77
  '--no-git skips initializing a git repository in the new directory.',
67
78
  ],
68
79
  }
69
80
  }
70
81
 
82
+ if (name === 'update') {
83
+ return await runUpdate(version, updateOptions)
84
+ }
85
+
71
86
  const wantsPlugin = argv.includes('--plugin')
72
87
  const wantsTheme = argv.includes('--theme')
73
88
  if (wantsPlugin && wantsTheme) {
@@ -5,7 +5,7 @@ export const PLUGIN_TEMPLATES: readonly ExtensionTemplate[] = [
5
5
  {
6
6
  path: 'src/plugin.tsx',
7
7
  contents:
8
- "import { definePlugin } from '@meith/plugin-kit'\n\nexport const __MEITH_EXTENSION_CAMEL__Plugin = definePlugin({\n key: '__MEITH_EXTENSION_KEY__',\n name: '__MEITH_EXTENSION_TITLE__',\n version: '0.1.0',\n description:\n 'A plugin scaffolded by create-meith: a footer link, a greeting ' +\n 'on the board index, one setting, one migration, one task and an admin page.',\n apiVersion: '0',\n\n settings: [\n {\n key: 'greeting',\n label: 'Greeting',\n default: 'A greeting from the __MEITH_EXTENSION_TITLE__ plugin!',\n description: 'Shown on the status page under Admin → Plugins → __MEITH_EXTENSION_TITLE__.',\n },\n ],\n\n migrations: [\n {\n id: '0001_create_wave_table',\n statements: [\n `create table if not exists plugin___MEITH_EXTENSION_SNAKE___wave (\n id integer generated by default as identity primary key,\n waved_at timestamptz not null default now()\n )`,\n ],\n },\n ],\n\n tasks: [\n {\n id: 'wave',\n intervalSeconds: 3600,\n run: (context) => {\n context.logger.info('__MEITH_EXTENSION_KEY__ plugin: waving', {\n greeting: context.settings.greeting,\n })\n },\n },\n ],\n\n adminPages: [\n {\n path: 'status',\n title: '__MEITH_EXTENSION_TITLE__ plugin',\n render: (context) => (\n <div className=\"flex flex-col gap-2 text-sm\">\n <p>{String(context.settings.greeting)}</p>\n <p className=\"text-muted-foreground\">\n This page is rendered by the plugin itself. Edit the greeting on the plugin&rsquo;s\n settings screen and reload to see the resolved value arrive in{' '}\n <code className=\"text-xs\">context.settings</code>.\n </p>\n </div>\n ),\n },\n ],\n\n contributions: [\n {\n region: 'index.footer',\n render: (context) => (\n <p className=\"text-xs text-muted-foreground\" data-plugin=\"__MEITH_EXTENSION_KEY__\">\n {context.viewer.isGuest\n ? 'Greetings, guest — this line comes from the __MEITH_EXTENSION_TITLE__ plugin.'\n : 'Greetings, member — this line comes from the __MEITH_EXTENSION_TITLE__ plugin.'}\n </p>\n ),\n },\n ],\n\n hooks: {\n 'view.footer': (footer) => ({\n ...footer,\n links: [\n ...footer.links,\n {\n label: '__MEITH_EXTENSION_TITLE__ plugin',\n href: '__MEITH_EXTENSION_REPOSITORY__',\n },\n ],\n }),\n\n 'post.created': () => {},\n },\n})\n",
8
+ "import { definePlugin } from '@meith/plugin-kit'\n\nexport const __MEITH_EXTENSION_CAMEL__Plugin = definePlugin({\n key: '__MEITH_EXTENSION_KEY__',\n name: '__MEITH_EXTENSION_TITLE__',\n version: '0.1.0',\n description:\n 'A plugin scaffolded by create-meith: a footer link, a greeting ' +\n 'on the board index, one setting, one migration, one task and an admin page.',\n apiVersion: '0',\n\n settings: [\n {\n key: 'greeting',\n label: 'Greeting',\n default: 'A greeting from the __MEITH_EXTENSION_TITLE__ plugin!',\n description: 'Shown on the status page under Admin → Plugins → __MEITH_EXTENSION_TITLE__.',\n },\n ],\n\n migrations: [\n {\n id: '0001_create_wave_table',\n statements: [\n `create table if not exists plugin___MEITH_EXTENSION_SNAKE___wave (\n id integer generated by default as identity primary key,\n waved_at timestamptz not null default now()\n )`,\n ],\n },\n ],\n\n tasks: [\n {\n id: 'wave',\n intervalSeconds: 3600,\n run: (context) => {\n context.logger.info('__MEITH_EXTENSION_KEY__ plugin: waving', {\n greeting: context.settings.greeting,\n })\n },\n },\n ],\n\n adminPages: [\n {\n path: 'status',\n title: '__MEITH_EXTENSION_TITLE__ plugin',\n render: (context) => (\n <div className=\"flex flex-col gap-2 text-sm\">\n <p>{String(context.settings.greeting)}</p>\n <p className=\"text-muted-foreground\">\n This page is rendered by the plugin itself. Edit the greeting on the plugin&rsquo;s\n settings screen and reload to see the resolved value arrive in{' '}\n <code className=\"text-xs\">context.settings</code>.\n </p>\n </div>\n ),\n },\n ],\n\n contributions: [\n {\n region: 'index.footer',\n render: (context) => {\n const key = context.viewer.isGuest ? '__MEITH_EXTENSION_KEY__.footer.guest' : '__MEITH_EXTENSION_KEY__.footer.member'\n const fallback = context.viewer.isGuest\n ? 'Greetings, guest — this line comes from the __MEITH_EXTENSION_TITLE__ plugin.'\n : 'Greetings, member — this line comes from the __MEITH_EXTENSION_TITLE__ plugin.'\n return (\n <p className=\"text-xs text-muted-foreground\" data-plugin=\"__MEITH_EXTENSION_KEY__\">\n {context.t.has(key) ? context.t.t(key) : fallback}\n </p>\n )\n },\n },\n ],\n\n hooks: {\n 'view.footer': (footer) => ({\n ...footer,\n links: [\n ...footer.links,\n {\n label: '__MEITH_EXTENSION_TITLE__ plugin',\n href: '__MEITH_EXTENSION_REPOSITORY__',\n },\n ],\n }),\n\n 'post.created': () => {},\n },\n})\n",
9
9
  },
10
10
  {
11
11
  path: 'src/plugin.test.ts',
package/src/index.ts CHANGED
@@ -26,3 +26,22 @@ export {
26
26
  scaffoldTheme,
27
27
  validateExtensionName,
28
28
  } from './scaffold-extension'
29
+ export {
30
+ compareExactVersions,
31
+ fetchPreviousTree,
32
+ mergeManifest,
33
+ normalizeActionPins,
34
+ parseExactVersion,
35
+ planUpdate,
36
+ RELEASE_NOTES_URL,
37
+ type RunUpdateOptions,
38
+ runUpdate,
39
+ substituteBoardName,
40
+ TEMPLATE_BOARD_NAME,
41
+ TEMPLATE_REPOSITORIES,
42
+ templateTarballUrl,
43
+ type UpdateInputs,
44
+ type UpdatePlan,
45
+ type UpdateResult,
46
+ unpackTemplateTarball,
47
+ } from './update'
package/src/scaffold.ts CHANGED
@@ -149,6 +149,19 @@ const ENV_SMTP_MAIL_BLOCK = `# Mail. Leave these alone and the installer asks fo
149
149
  # MAIL_SMTP_PASSWORD=
150
150
  # MAIL_FROM=noreply@yourdomain.com`
151
151
 
152
+ const ENV_BACKUP_BLOCK = `# The off-site backup destination: an S3-compatible bucket \`meith backup\`
153
+ # ships every bundle to, pruned there to the newest --keep (7 unless set).
154
+ # All four required together — a partial set fails the backup, never the board.
155
+ # BACKUP_S3_ENDPOINT is for anything S3-compatible (R2, MinIO, Spaces;
156
+ # BACKUP_S3_REGION=auto for R2); BACKUP_S3_PREFIX shares one bucket between
157
+ # boards. Use a bucket of its own, never the bucket the uploads live in.
158
+ # BACKUP_S3_BUCKET=
159
+ # BACKUP_S3_REGION=
160
+ # BACKUP_S3_ACCESS_KEY_ID=
161
+ # BACKUP_S3_SECRET_ACCESS_KEY=
162
+ # BACKUP_S3_ENDPOINT=
163
+ # BACKUP_S3_PREFIX=`
164
+
152
165
  function selfHostEnvExample(name: string): string {
153
166
  return `# ${name} — environment.
154
167
  #
@@ -179,6 +192,8 @@ APP_URL=
179
192
 
180
193
  ${ENV_SMTP_MAIL_BLOCK}
181
194
 
195
+ ${ENV_BACKUP_BLOCK}
196
+
182
197
  `
183
198
  }
184
199
 
@@ -350,7 +365,6 @@ function envExample(name: string, target: ScaffoldTarget): string {
350
365
 
351
366
  const SELF_HOST_DEPLOY_KIT = [
352
367
  '.dockerignore',
353
- '.github/dependabot.yml',
354
368
  '.github/workflows/build.yml',
355
369
  'Dockerfile',
356
370
  'Dockerfile.prebuilt',
@@ -547,9 +561,10 @@ export function installedPluginDefinitions() {
547
561
  files.set(
548
562
  '.github/dependabot.yml',
549
563
  `# Keeps this board's GitHub Actions current: one weekly pull request that
550
- # bumps the actions pinned in .github/workflows. Upgrading Meith itself is
551
- # separate and deliberate — see README.md, "Upgrading" — because the board's
552
- # \`next\` pin has to move together with \`@meith/web\` rather than on its own.
564
+ # bumps the actions pinned in .github/workflows. Meith itself is updated
565
+ # separately, by .github/workflows/update.yml — see README.md, "Upgrading" —
566
+ # because the board's \`next\` pin has to move together with \`@meith/web\`
567
+ # rather than on its own.
553
568
  version: 2
554
569
  updates:
555
570
  - package-ecosystem: github-actions
@@ -562,6 +577,83 @@ updates:
562
577
  `,
563
578
  )
564
579
 
580
+ files.set(
581
+ '.github/workflows/update.yml',
582
+ `# Opens a pull request whenever a new Meith release is out — see README.md,
583
+ # "Upgrading". Once a week, and on the Run workflow button, it runs the same
584
+ # updater you can run by hand, \`npx create-meith@latest update\`: every
585
+ # @meith/* pin and next moved together in package.json, and the deploy files
586
+ # the scaffold owns rewritten to the new release's shape. A file you have
587
+ # edited yourself is never rewritten; the run's log names any it left alone.
588
+ #
589
+ # One-time setup: under Settings → Actions → General, enable "Allow GitHub
590
+ # Actions to create and approve pull requests" — without it the last step
591
+ # fails, saying GitHub Actions is not permitted to create pull requests.
592
+ #
593
+ # Merging is still an upgrade, not a formality: read the release notes the
594
+ # pull request links, take a backup first, and once the new version serves,
595
+ # run \`meith upgrade\` against it. Migrations are forward-only — the backup
596
+ # is the rollback.
597
+ name: Meith update
598
+
599
+ on:
600
+ schedule:
601
+ - cron: '30 4 * * 1'
602
+ workflow_dispatch:
603
+
604
+ permissions:
605
+ contents: write
606
+ pull-requests: write
607
+
608
+ jobs:
609
+ update:
610
+ name: Update Meith
611
+ runs-on: ubuntu-latest
612
+ steps:
613
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
614
+
615
+ - name: Run the updater
616
+ run: npx --yes create-meith@latest update
617
+
618
+ - name: Open a pull request
619
+ env:
620
+ GH_TOKEN: \${{ github.token }}
621
+ run: |
622
+ if [ -z "$(git status --porcelain)" ]; then
623
+ echo "Already up to date."
624
+ exit 0
625
+ fi
626
+ VERSION=$(node -p "require('./package.json').dependencies['@meith/web']")
627
+ BODY="$RUNNER_TEMP/meith-update-body.md"
628
+ {
629
+ echo "Updates this board to Meith $VERSION — every @meith/* package and"
630
+ echo "next moved together, and the scaffold-owned deploy files rewritten"
631
+ echo "to this release's shape. Files with local edits were left alone;"
632
+ echo "this workflow run's log names any it skipped."
633
+ echo
634
+ echo "Before merging and deploying:"
635
+ echo
636
+ echo "1. Read the release notes — their Migrations line says what this"
637
+ echo " release does to the schema:"
638
+ echo " https://github.com/meith-dev/meith/releases/tag/v$VERSION"
639
+ echo "2. Take a backup. Migrations are forward-only; the backup is the"
640
+ echo " rollback."
641
+ echo
642
+ echo "After the new version deploys, run 'meith upgrade' once against the"
643
+ echo "board — the admin panel shows a notice until it has run. See"
644
+ echo "README.md, \\"Upgrading\\"."
645
+ } > "$BODY"
646
+ git config user.name "github-actions[bot]"
647
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
648
+ git checkout -B meith-update
649
+ git add -A
650
+ git commit -m "Update Meith to $VERSION"
651
+ git push -f origin meith-update
652
+ gh pr create --title "Update Meith to $VERSION" --body-file "$BODY" \\
653
+ || gh pr edit meith-update --title "Update Meith to $VERSION" --body-file "$BODY"
654
+ `,
655
+ )
656
+
565
657
  files.set(
566
658
  'Dockerfile',
567
659
  `# syntax=docker/dockerfile:1.7-labs
@@ -980,8 +1072,24 @@ services:
980
1072
  - MAIL_SMTP_USERNAME=\${MAIL_SMTP_USERNAME:-}
981
1073
  - MAIL_SMTP_PASSWORD=\${MAIL_SMTP_PASSWORD:-}
982
1074
  - MAIL_FROM=\${MAIL_FROM:-}
1075
+ # The off-site backup destination, unset until the four required values
1076
+ # are filled in on the Coolify resource. Coolify only hands a compose
1077
+ # service the variables the file names, so a Scheduled Task running
1078
+ # \`meith backup\` in this container would never see them without these
1079
+ # lines — see the meith repository's
1080
+ # docs/getting-started/deployment/coolify.md, "Set up backups".
1081
+ - BACKUP_S3_BUCKET=\${BACKUP_S3_BUCKET:-}
1082
+ - BACKUP_S3_REGION=\${BACKUP_S3_REGION:-}
1083
+ - BACKUP_S3_ACCESS_KEY_ID=\${BACKUP_S3_ACCESS_KEY_ID:-}
1084
+ - BACKUP_S3_SECRET_ACCESS_KEY=\${BACKUP_S3_SECRET_ACCESS_KEY:-}
1085
+ - BACKUP_S3_ENDPOINT=\${BACKUP_S3_ENDPOINT:-}
1086
+ - BACKUP_S3_PREFIX=\${BACKUP_S3_PREFIX:-}
983
1087
  volumes:
984
1088
  - uploads:/app/.uploads
1089
+ # Where \`meith backup --dir /backups\` writes its ring of bundles. A
1090
+ # named volume rather than the container filesystem, so the ring survives
1091
+ # every redeploy.
1092
+ - backups:/backups
985
1093
  depends_on:
986
1094
  postgres:
987
1095
  condition: service_healthy
@@ -1017,6 +1125,7 @@ services:
1017
1125
  volumes:
1018
1126
  pgdata:
1019
1127
  uploads:
1128
+ backups:
1020
1129
  `,
1021
1130
  )
1022
1131
 
@@ -1113,8 +1222,24 @@ services:
1113
1222
  - MAIL_SMTP_USERNAME=\${MAIL_SMTP_USERNAME:-}
1114
1223
  - MAIL_SMTP_PASSWORD=\${MAIL_SMTP_PASSWORD:-}
1115
1224
  - MAIL_FROM=\${MAIL_FROM:-}
1225
+ # The off-site backup destination, unset until the four required values
1226
+ # are filled in on the Coolify resource. Coolify only hands a compose
1227
+ # service the variables the file names, so a Scheduled Task running
1228
+ # \`meith backup\` in this container would never see them without these
1229
+ # lines — see the meith repository's
1230
+ # docs/getting-started/deployment/coolify.md, "Set up backups".
1231
+ - BACKUP_S3_BUCKET=\${BACKUP_S3_BUCKET:-}
1232
+ - BACKUP_S3_REGION=\${BACKUP_S3_REGION:-}
1233
+ - BACKUP_S3_ACCESS_KEY_ID=\${BACKUP_S3_ACCESS_KEY_ID:-}
1234
+ - BACKUP_S3_SECRET_ACCESS_KEY=\${BACKUP_S3_SECRET_ACCESS_KEY:-}
1235
+ - BACKUP_S3_ENDPOINT=\${BACKUP_S3_ENDPOINT:-}
1236
+ - BACKUP_S3_PREFIX=\${BACKUP_S3_PREFIX:-}
1116
1237
  volumes:
1117
1238
  - uploads:/app/.uploads
1239
+ # Where \`meith backup --dir /backups\` writes its ring of bundles. A
1240
+ # named volume rather than the container filesystem, so the ring survives
1241
+ # every redeploy.
1242
+ - backups:/backups
1118
1243
  depends_on:
1119
1244
  postgres:
1120
1245
  condition: service_healthy
@@ -1150,6 +1275,7 @@ services:
1150
1275
  volumes:
1151
1276
  pgdata:
1152
1277
  uploads:
1278
+ backups:
1153
1279
  `,
1154
1280
  )
1155
1281
 
@@ -1336,27 +1462,52 @@ for the full guide.
1336
1462
 
1337
1463
  ## Upgrading
1338
1464
 
1465
+ \`.github/workflows/update.yml\` does this for you: once a week — and
1466
+ whenever you press **Run workflow** on the Actions tab — it checks for a new
1467
+ Meith release and opens a pull request that moves every \`@meith/*\` package
1468
+ and \`next\` together, and rewrites the deploy files this scaffold owns
1469
+ (\`Dockerfile\`, the compose files, the workflows) to the new release's
1470
+ shape. A file you have edited yourself is never rewritten; the run's log
1471
+ names any it left for you. One-time setup: under
1472
+ **Settings → Actions → General**, enable **Allow GitHub Actions to create
1473
+ and approve pull requests**, or the workflow cannot open one.
1474
+
1475
+ Merging that pull request is still an upgrade, not a formality: read the
1476
+ release notes it links, take a backup first, and press **Redeploy** in
1477
+ Coolify after the merge — pushing alone does not rebuild. Once the new
1478
+ version serves, run \`npm run meith -- upgrade\` against it for the plugin
1479
+ migrations.
1480
+
1481
+ The same update, by hand and without waiting for the schedule:
1482
+
1483
+ \`\`\`sh
1484
+ npx create-meith@latest update
1485
+ git commit -am "Update Meith"
1486
+ git push
1487
+ \`\`\`
1488
+
1489
+ Under the hood, the version move is these two commands, plus the deploy-file
1490
+ rewrite neither of them can do:
1491
+
1339
1492
  \`\`\`sh
1340
1493
  npm install --save-exact @meith/web@latest @meith/cli@latest @meith/theme-default@latest
1341
1494
  npm install --save-exact next@$(node -p "require('./node_modules/@meith/web/package.json').dependencies.next")
1342
- git commit -am "Upgrade Meith and the Next.js version it builds with"
1343
- git push
1344
1495
  \`\`\`
1345
1496
 
1346
1497
  The second command is not optional. This board pins \`next\` itself, and
1347
- nothing bumps it for you: upgrading only the \`@meith/*\` packages leaves the
1348
- board's own pin on the old Next while \`@meith/web\` depends on the new one,
1349
- which npm resolves by installing both — the build then runs on one version
1350
- while everything reading \`package.json\` sees the other. Reading the version
1351
- out of the freshly installed \`@meith/web\` is what keeps the two the same
1352
- without anybody having to know the number.
1353
-
1354
- This upgrade is deliberate and manual for that reason: \`next\` and
1355
- \`@meith/web\` move together or not at all. What *is* kept current for you is
1356
- this repository's own GitHub Actions — \`.github/dependabot.yml\` opens a
1357
- weekly pull request bumping the actions pinned in
1358
- \`.github/workflows/build.yml\`, which is a safe, independent update the two
1359
- commands above never touch.
1498
+ the npm commands alone never bump it: upgrading only the \`@meith/*\` packages
1499
+ leaves the board's own pin on the old Next while \`@meith/web\` depends on the
1500
+ new one, which npm resolves by installing both — the build then runs on one
1501
+ version while everything reading \`package.json\` sees the other. Reading the
1502
+ version out of the freshly installed \`@meith/web\` is what keeps the two the
1503
+ same without anybody having to know the number.
1504
+
1505
+ \`next\` and \`@meith/web\` move together or not at all, which is why one
1506
+ updater owns the whole move and no dependency bot bumps either on its own.
1507
+ What Dependabot *does* keep current is this repository's own GitHub Actions —
1508
+ \`.github/dependabot.yml\` opens a weekly pull request bumping the actions
1509
+ pinned under \`.github/workflows\`, a safe, independent update the updater
1510
+ leaves to it.
1360
1511
 
1361
1512
  On the quick-start path there is no version to keep in sync by hand:
1362
1513
  \`Dockerfile\` runs \`npm install\` straight from this \`package.json\` on every
@@ -1607,20 +1758,38 @@ each retry into another attempt against whatever it is failing against.
1607
1758
 
1608
1759
  ## Upgrading
1609
1760
 
1761
+ \`.github/workflows/update.yml\` does this for you: once a week — and
1762
+ whenever you press **Run workflow** on the Actions tab — it checks for a new
1763
+ Meith release and opens a pull request that moves every \`@meith/*\` package
1764
+ and \`next\` together. A file you have edited yourself is never rewritten; the
1765
+ run's log names any it left for you. One-time setup: under
1766
+ **Settings → Actions → General**, enable **Allow GitHub Actions to create and
1767
+ approve pull requests**, or the workflow cannot open one. Read the release
1768
+ notes the pull request links and take a backup before merging; Vercel
1769
+ rebuilds on the merge, and the build command applies the new migrations
1770
+ before it builds.
1771
+
1772
+ The same update, by hand and without waiting for the schedule:
1773
+
1774
+ \`\`\`sh
1775
+ npx create-meith@latest update
1776
+ git commit -am "Update Meith"
1777
+ git push
1778
+ \`\`\`
1779
+
1780
+ Under the hood, the version move is these two commands:
1781
+
1610
1782
  \`\`\`sh
1611
1783
  npm install --save-exact @meith/web@latest @meith/cli@latest @meith/theme-default@latest
1612
1784
  npm install --save-exact next@$(node -p "require('./node_modules/@meith/web/package.json').dependencies.next")
1613
- git commit -am "Upgrade Meith and the Next.js version it builds with"
1614
- git push
1615
1785
  \`\`\`
1616
1786
 
1617
- Vercel rebuilds on the push, and the build command applies the new migrations
1618
- before it builds. \`--save-exact\` matters and \`.npmrc\` already sets it for
1619
- everything else installed here.
1787
+ \`--save-exact\` matters and \`.npmrc\` already sets it for everything else
1788
+ installed here.
1620
1789
 
1621
1790
  The second command is not optional. This board pins \`next\` itself — Vercel
1622
- reads that pin to pick its Next.js builder — and nothing bumps it for you.
1623
- Upgrading only the \`@meith/*\` packages leaves two versions of Next
1791
+ reads that pin to pick its Next.js builder — and the npm commands alone never
1792
+ bump it. Upgrading only the \`@meith/*\` packages leaves two versions of Next
1624
1793
  installed, the board built with one and the platform configured for the
1625
1794
  other. Reading the version out of the freshly installed \`@meith/web\` keeps
1626
1795
  them the same without anybody having to know the number.