@clubedge/create-clubedge-app 0.20.0 → 0.21.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.
Files changed (34) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +16 -15
  3. package/package.json +5 -4
  4. package/template/.github/workflows/ci.yml +10 -10
  5. package/template/.github/workflows/clubedge-upgrade.yml +134 -0
  6. package/template/.github/workflows/deploy.yml +19 -19
  7. package/template/apps/start/package.json +1 -1
  8. package/template/apps/web/package.json +1 -1
  9. package/template/package.json +1 -1
  10. package/template/packages/auth/package.json +1 -1
  11. package/template/packages/auth-better-auth/package.json +1 -1
  12. package/template/packages/auth-supabase/package.json +1 -1
  13. package/template/packages/cache/package.json +1 -1
  14. package/template/packages/cache-redis/package.json +1 -1
  15. package/template/packages/core/package.json +1 -1
  16. package/template/packages/db/package.json +1 -1
  17. package/template/packages/email/package.json +1 -1
  18. package/template/packages/email-resend/package.json +1 -1
  19. package/template/packages/email-smtp/package.json +1 -1
  20. package/template/packages/jobs/package.json +1 -1
  21. package/template/packages/jobs-pg-boss/package.json +1 -1
  22. package/template/packages/local-db/package.json +1 -1
  23. package/template/packages/local-db/src/server.test.ts +3 -2
  24. package/template/packages/observability/package.json +1 -1
  25. package/template/packages/observability-otel/package.json +1 -1
  26. package/template/packages/observability-sentry/package.json +1 -1
  27. package/template/packages/payments/package.json +1 -1
  28. package/template/packages/payments/src/billing.test.ts +27 -4
  29. package/template/packages/payments-stripe/package.json +1 -1
  30. package/template/packages/storage/package.json +1 -1
  31. package/template/packages/storage-s3/package.json +1 -1
  32. package/template/packages/storage-supabase/package.json +1 -1
  33. package/template/packages/ui/package.json +1 -1
  34. package/template.lock.json +3 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to `create-clubedge-app` are documented here.
4
4
 
5
+ ## 0.21.0
6
+
7
+ - Default to Starter `v0.16.0` (#52), which adds:
8
+ - Starter upgrades as pull requests: every project gets `.github/workflows/clubedge-upgrade.yml`, which checks every Monday for a newer Starter release and opens a pull request with `pnpm clubedge upgrade`'s result, the release notes, and anything left to resolve, so the project's own CI tests the upgrade before it is merged.
9
+ - GitHub Actions on their Node 24 versions in `ci.yml` and `deploy.yml`, with Linux jobs on Ubuntu 24.04.
10
+ - Steadier tests: PGlite starts once per test file, with room for slow CI runners.
11
+ - Release tooling for maintainers (#51): `pnpm release patch|minor|major` opens the release pull request with a drafted changelog, `pnpm release tag` publishes it, and the Starter pin workflow opens a pull request when the Starter publishes a release.
12
+ - CI names the generated project's failing check, in an annotation and the job summary (#50).
13
+
5
14
  ## 0.20.0
6
15
 
7
16
  - Conflicts are easier to resolve after `add`, `remove`, and `upgrade`:
package/README.md CHANGED
@@ -127,7 +127,7 @@ pnpm dlx @clubedge/create-clubedge-app my-app --yes --no-git --framework tanstac
127
127
  pnpm dlx @clubedge/create-clubedge-app my-app --ref main
128
128
 
129
129
  # Reproduce a specific Starter release or commit
130
- pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.15.0
130
+ pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.16.0
131
131
  pnpm dlx @clubedge/create-clubedge-app my-app --ref <commit-sha>
132
132
 
133
133
  # Test local Starter changes before releasing them
@@ -146,7 +146,7 @@ pnpm clubedge add email resend payments stripe # several modules as
146
146
  pnpm clubedge remove storage payments
147
147
  pnpm dlx @clubedge/create-clubedge-app add infra local --dry-run
148
148
  pnpm dlx @clubedge/create-clubedge-app@latest upgrade # to the newest CLI's Starter release
149
- pnpm dlx @clubedge/create-clubedge-app upgrade --ref v0.15.0 # or a specific tag, branch, or commit
149
+ pnpm dlx @clubedge/create-clubedge-app upgrade --ref v0.16.0 # or a specific tag, branch, or commit
150
150
  ```
151
151
 
152
152
  The CLI renders the project twice, from the Starter commit and modules recorded in your `package.json` and from the new state, and merges the difference into your project. The new state has other modules for `add` and `remove`, and the newer Starter with the same modules for `upgrade`. Modules a newer Starter introduces start as `none` where possible, so an upgrade never adds a service on its own.
@@ -194,27 +194,27 @@ When the app is ready, the CLI opens the preview in your browser (`--no-open` tu
194
194
  Each CLI release is tested against one specific Starter release:
195
195
 
196
196
  ```text
197
- create-clubedge-app v0.20.0
197
+ create-clubedge-app v0.21.0
198
198
  |
199
199
  v
200
- Clubedge Starter v0.15.0
201
- commit 239f9e9fd575165d72f70a91592cafbd43bb7574
200
+ Clubedge Starter v0.16.0
201
+ commit e233aa68ddc8c21df6cf2b01b9e26876d2e4aeca
202
202
  ```
203
203
 
204
204
  The current relationship is:
205
205
 
206
206
  | CLI release | Default Starter release | Meaning |
207
207
  | --- | --- | --- |
208
- | `create-clubedge-app@0.20.0` | `clubedge-starter@v0.15.0` | The CLI's required, tested default |
208
+ | `create-clubedge-app@0.21.0` | `clubedge-starter@v0.16.0` | The CLI's required, tested default |
209
209
 
210
210
  This means the same CLI version always scaffolds the same Starter revision by default. The CLI does not silently follow Starter `main`.
211
211
 
212
- The `v0.15.0` tag is expected to resolve to commit
213
- `239f9e9fd575165d72f70a91592cafbd43bb7574`. The CLI verifies this before
212
+ The `v0.16.0` tag is expected to resolve to commit
213
+ `e233aa68ddc8c21df6cf2b01b9e26876d2e4aeca`. The CLI verifies this before
214
214
  scaffolding with the default reference and fails rather than silently using a
215
215
  moved tag.
216
216
 
217
- When the Starter changes, its maintainers first publish a new versioned release, such as `v0.15.0`. The CLI is then updated to use that release, all generated-project checks must pass, and a new CLI version is published. This keeps Starter and CLI releases independently versioned while preserving a tested compatibility relationship.
217
+ When the Starter changes, its maintainers first publish a new versioned release, such as `v0.16.0`. The CLI is then updated to use that release, all generated-project checks must pass, and a new CLI version is published. This keeps Starter and CLI releases independently versioned while preserving a tested compatibility relationship.
218
218
 
219
219
  Every generated project records its CLI version, Starter repository, Starter ref, Starter commit, framework, and modules in `package.json` and `README.md`.
220
220
 
@@ -269,15 +269,15 @@ CI packs the CLI with `npm pack`, scaffolds clean projects from the packed tarba
269
269
  1. Make and validate changes in `clubedge-starter`.
270
270
  2. Run lint, typecheck, unit tests, browser tests, and production build.
271
271
  3. Update the Starter package versions.
272
- 4. Commit `main`, create a release tag such as `v0.15.0`, and publish the Starter release.
272
+ 4. Commit `main`, create a release tag such as `v0.16.0`, and publish the Starter release.
273
273
 
274
274
  ### CLI maintainers
275
275
 
276
- 1. Set `clubedge.starterRef` and `clubedge.starterCommit` in [`package.json`](package.json) to the new tested Starter tag, and bump `version`.
277
- 2. Update the version relationship table and changelog.
278
- 3. Generate a clean project from that exact tag.
279
- 4. Run install, lint, typecheck, unit tests, browser tests, production build, and runtime smoke checks.
280
- 5. Publish a new CLI version only after the generated-project verification passes.
276
+ 1. When the Starter publishes a release, the [Starter pin](.github/workflows/starter-pin.yml) workflow opens a pull request that moves `clubedge.starterRef` and `clubedge.starterCommit` in [`package.json`](package.json), and the docs that name them, to the new tag (daily, or run it by hand; locally: `pnpm release pin vX.Y.Z`). Its CI generates clean projects from that exact tag and runs install, lint, typecheck, unit tests, browser tests, production build, and runtime smoke checks. Merge it once they pass.
277
+ 2. `pnpm release patch|minor|major` (from an up-to-date `main`; add `--dry-run` to preview) opens the release pull request: it bumps `version`, updates the version relationship here and in CONTRIBUTING, adds the release table rows for a minor or major release, and drafts the changelog entry from the pull requests merged since the last tag. Edit the draft in that pull request: it becomes the GitHub Release page.
278
+ 3. After it is merged, `pnpm release tag` tags `main`. The Release workflow checks and publishes it to npm, creates the GitHub Release page from the changelog entry, and smoke-tests the published package.
279
+
280
+ The pin workflow needs **Settings → Actions → General → Allow GitHub Actions to create and approve pull requests**, or a `STARTER_PIN_TOKEN` secret (a fine-grained token with contents and pull requests write), which also lets its pull request start CI by itself.
281
281
 
282
282
  The required CI job always tests the pinned default. The scheduled compatibility job tests Starter `main` early, but unreleased Starter changes must not change the stable CLI default.
283
283
 
@@ -299,6 +299,7 @@ Each phase ends with a Starter release and a CLI release pinned to it. The full
299
299
  | 0.18.0 | v0.15.0 | `try`: a new project on a free, temporary Railway VM, with a preview URL and a claim link (experimental) |
300
300
  | 0.19.0 | v0.15.0 | `pnpm clubedge try` uploads an existing project to the VM; the preview opens in the browser |
301
301
  | 0.20.0 | v0.15.0 | The Starter's version of each conflicted file in `.clubedge/incoming/`, several modules at once, sharing a `try` preview by claiming the VM, `try --status` |
302
+ | 0.21.0 | v0.16.0 | Starter v0.16.0: upgrades delivered as pull requests, Node 24 actions; release tooling for maintainers |
302
303
  | next | | Show the Starter's version of a conflicted file next to yours |
303
304
 
304
305
  ## Community
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/create-clubedge-app",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Create a Next.js or TanStack Start application from the Clubedge Starter reference implementation.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.9.0",
@@ -22,8 +22,8 @@
22
22
  },
23
23
  "clubedge": {
24
24
  "starterRepository": "Clubedge/clubedge-starter",
25
- "starterRef": "v0.15.0",
26
- "starterCommit": "239f9e9fd575165d72f70a91592cafbd43bb7574"
25
+ "starterRef": "v0.16.0",
26
+ "starterCommit": "e233aa68ddc8c21df6cf2b01b9e26876d2e4aeca"
27
27
  },
28
28
  "license": "Apache-2.0",
29
29
  "repository": {
@@ -48,7 +48,8 @@
48
48
  "test": "vitest run",
49
49
  "bundle-template": "node scripts/bundle-template.mjs",
50
50
  "start": "pnpm build && node ./bin/create.mjs",
51
- "prepack": "pnpm build"
51
+ "prepack": "pnpm build",
52
+ "release": "node scripts/release.mjs"
52
53
  },
53
54
  "dependencies": {
54
55
  "@clack/prompts": "^1.8.1",
@@ -8,13 +8,13 @@ permissions:
8
8
  contents: read
9
9
  jobs:
10
10
  verify:
11
- runs-on: ubuntu-latest
11
+ runs-on: ubuntu-24.04
12
12
  env:
13
13
  DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/postgres
14
14
  steps:
15
- - uses: actions/checkout@v4
16
- - uses: pnpm/action-setup@v4
17
- - uses: actions/setup-node@v4
15
+ - uses: actions/checkout@v7
16
+ - uses: pnpm/action-setup@v6
17
+ - uses: actions/setup-node@v7
18
18
  with:
19
19
  node-version: 22
20
20
  cache: pnpm
@@ -44,11 +44,11 @@ jobs:
44
44
  - run: pnpm audit --audit-level high
45
45
 
46
46
  e2e:
47
- runs-on: ubuntu-latest
47
+ runs-on: ubuntu-24.04
48
48
  steps:
49
- - uses: actions/checkout@v4
50
- - uses: pnpm/action-setup@v4
51
- - uses: actions/setup-node@v4
49
+ - uses: actions/checkout@v7
50
+ - uses: pnpm/action-setup@v6
51
+ - uses: actions/setup-node@v7
52
52
  with:
53
53
  node-version: 22
54
54
  cache: pnpm
@@ -57,7 +57,7 @@ jobs:
57
57
  - run: pnpm test:e2e
58
58
 
59
59
  docker:
60
- runs-on: ubuntu-latest
60
+ runs-on: ubuntu-24.04
61
61
  services:
62
62
  postgres:
63
63
  image: postgres:17
@@ -70,7 +70,7 @@ jobs:
70
70
  options: >-
71
71
  --health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 10
72
72
  steps:
73
- - uses: actions/checkout@v4
73
+ - uses: actions/checkout@v7
74
74
  - name: Build the production image
75
75
  run: docker build --tag app:ci .
76
76
  - name: Run the image's migrations twice; the second run changes nothing
@@ -0,0 +1,134 @@
1
+ name: Starter upgrade
2
+
3
+ # Checks every Monday for a newer Clubedge Starter release. When there is one, it runs
4
+ # `pnpm clubedge upgrade` and opens a pull request with the release notes and anything left for
5
+ # you to resolve, so your CI tests the upgrade before you merge it. Run it by hand from the
6
+ # Actions tab, or delete this file to turn it off.
7
+ #
8
+ # It needs Settings -> Actions -> General -> "Allow GitHub Actions to create and approve pull
9
+ # requests", or a CLUBEDGE_UPGRADE_TOKEN secret (a fine-grained token with contents and pull
10
+ # requests write), which also lets the pull request start your CI by itself.
11
+ on:
12
+ schedule:
13
+ - cron: "17 7 * * 1"
14
+ workflow_dispatch:
15
+
16
+ permissions:
17
+ contents: write
18
+ pull-requests: write
19
+ actions: write
20
+
21
+ concurrency:
22
+ group: clubedge-upgrade
23
+ cancel-in-progress: false
24
+
25
+ jobs:
26
+ upgrade:
27
+ # The Starter's own repository is the template every project comes from, not a project.
28
+ if: github.repository != 'Clubedge/clubedge-starter'
29
+ runs-on: ubuntu-24.04
30
+ steps:
31
+ - uses: actions/checkout@v7
32
+ - uses: pnpm/action-setup@v6
33
+ - uses: actions/setup-node@v7
34
+ with:
35
+ node-version: 22
36
+ - name: Upgrade to the newest Starter release
37
+ id: upgrade
38
+ shell: bash
39
+ run: |
40
+ before=$(node -p "require('./package.json').clubedge?.starterRef ?? ''")
41
+ if [ -z "$before" ]; then
42
+ echo "package.json records no Starter release: this project was not created by create-clubedge-app."
43
+ exit 1
44
+ fi
45
+ # The newest CLI upgrades to the Starter release it is pinned to.
46
+ pnpm dlx @clubedge/create-clubedge-app@latest upgrade --no-install 2>&1 | tee "$RUNNER_TEMP/upgrade.log"
47
+ after=$(node -p "require('./package.json').clubedge.starterRef")
48
+ if [ "$after" = "$before" ] && [ -z "$(git status --porcelain)" ]; then
49
+ echo "Already on the newest Starter release, $before."
50
+ exit 0
51
+ fi
52
+ branch="clubedge/upgrade-$after"
53
+ if git ls-remote --exit-code --heads origin "$branch" >/dev/null; then
54
+ echo "$branch already exists: its pull request is open, or was closed on purpose."
55
+ exit 0
56
+ fi
57
+ # The lockfile follows the upgraded package.json files; a conflict there can stop it.
58
+ install="updated"
59
+ pnpm install --no-frozen-lockfile >"$RUNNER_TEMP/install.log" 2>&1 || install="failed"
60
+ {
61
+ echo "before=$before"
62
+ echo "after=$after"
63
+ echo "branch=$branch"
64
+ echo "install=$install"
65
+ if [ -d .clubedge/incoming ]; then echo "incoming=true"; fi
66
+ } >>"$GITHUB_OUTPUT"
67
+ - name: Keep the Starter's versions of conflicted files
68
+ if: steps.upgrade.outputs.incoming == 'true'
69
+ uses: actions/upload-artifact@v7
70
+ with:
71
+ name: starter-versions
72
+ path: .clubedge/incoming/
73
+ include-hidden-files: true
74
+ - name: Open the pull request
75
+ if: steps.upgrade.outputs.branch != ''
76
+ shell: bash
77
+ env:
78
+ GH_TOKEN: ${{ secrets.CLUBEDGE_UPGRADE_TOKEN || github.token }}
79
+ OWN_TOKEN: ${{ secrets.CLUBEDGE_UPGRADE_TOKEN != '' }}
80
+ BEFORE: ${{ steps.upgrade.outputs.before }}
81
+ AFTER: ${{ steps.upgrade.outputs.after }}
82
+ BRANCH: ${{ steps.upgrade.outputs.branch }}
83
+ INSTALL: ${{ steps.upgrade.outputs.install }}
84
+ RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
85
+ run: |
86
+ git config user.name "github-actions[bot]"
87
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
88
+ git switch --create "$BRANCH"
89
+ git add --all
90
+ git commit --quiet --message "chore: upgrade to Clubedge Starter $AFTER"
91
+ git push --quiet --set-upstream origin "$BRANCH"
92
+
93
+ repository=$(node -p "require('./package.json').clubedge.starterRepository ?? 'Clubedge/clubedge-starter'")
94
+ conflicts=$(git grep -l '^<<<<<<< yours' -- . || true)
95
+ {
96
+ echo "Upgrades this project from Clubedge Starter \`$BEFORE\` to [\`$AFTER\`](https://github.com/$repository/releases/tag/$AFTER), with \`pnpm clubedge upgrade\`. Your own edits are merged in; files you never changed take the new version."
97
+ echo
98
+ if [ -n "$conflicts" ]; then
99
+ echo "### Conflicts to resolve"
100
+ echo
101
+ echo "These files have lines both you and the Starter changed, marked \`<<<<<<< yours\` ... \`>>>>>>> clubedge\`. Keep your lines, the Starter's, or a mix, then delete the markers:"
102
+ echo
103
+ printf -- '- `%s`\n' $conflicts
104
+ echo
105
+ echo "The Starter's whole new version of each is in the **starter-versions** artifact of [the run]($RUN_URL), to compare with yours."
106
+ echo
107
+ fi
108
+ if [ "$INSTALL" = "failed" ]; then
109
+ echo "### pnpm install"
110
+ echo
111
+ echo "\`pnpm install\` did not complete on the upgraded project, usually because of a conflict in a \`package.json\`. Resolve it, then run \`pnpm install\` and commit \`pnpm-lock.yaml\`."
112
+ echo
113
+ fi
114
+ echo "### Before merging"
115
+ echo
116
+ echo "- Read the [release notes](https://github.com/$repository/releases/tag/$AFTER) for anything to do by hand."
117
+ echo "- Run \`pnpm db:migrate\` for new migrations, and your tests."
118
+ echo
119
+ echo "<details><summary>Upgrade output</summary>"
120
+ echo
121
+ echo '```'
122
+ tail -n 80 "$RUNNER_TEMP/upgrade.log"
123
+ echo '```'
124
+ echo
125
+ echo "</details>"
126
+ echo
127
+ echo "Opened by the Starter upgrade workflow (\`.github/workflows/clubedge-upgrade.yml\`)."
128
+ } >"$RUNNER_TEMP/body.md"
129
+ gh pr create --base "${{ github.event.repository.default_branch }}" --head "$BRANCH" \
130
+ --title "chore: upgrade to Clubedge Starter $AFTER" --body-file "$RUNNER_TEMP/body.md"
131
+ # Pull requests opened with the default token start no workflows: start CI on the branch.
132
+ if [ "$OWN_TOKEN" != "true" ] && [ -f .github/workflows/ci.yml ]; then
133
+ gh workflow run ci.yml --ref "$BRANCH" || echo "Start CI on $BRANCH by hand."
134
+ fi
@@ -12,26 +12,26 @@ jobs:
12
12
  # Builds the production image and publishes it to this repository's GitHub Container Registry
13
13
  # as ghcr.io/<owner>/<repository>, tagged with the commit and latest. No secret is needed.
14
14
  image:
15
- runs-on: ubuntu-latest
15
+ runs-on: ubuntu-24.04
16
16
  permissions:
17
17
  contents: read
18
18
  packages: write
19
19
  steps:
20
- - uses: actions/checkout@v4
21
- - uses: docker/setup-buildx-action@v3
22
- - uses: docker/login-action@v3
20
+ - uses: actions/checkout@v7
21
+ - uses: docker/setup-buildx-action@v4
22
+ - uses: docker/login-action@v4
23
23
  with:
24
24
  registry: ghcr.io
25
25
  username: ${{ github.actor }}
26
26
  password: ${{ secrets.GITHUB_TOKEN }}
27
27
  - id: meta
28
- uses: docker/metadata-action@v5
28
+ uses: docker/metadata-action@v6
29
29
  with:
30
30
  images: ghcr.io/${{ github.repository }}
31
31
  tags: |
32
32
  type=sha
33
33
  type=raw,value=latest
34
- - uses: docker/build-push-action@v6
34
+ - uses: docker/build-push-action@v7
35
35
  with:
36
36
  context: .
37
37
  push: true
@@ -40,9 +40,9 @@ jobs:
40
40
  # clubedge:end
41
41
  # clubedge:if deploy=fly
42
42
  fly:
43
- runs-on: ubuntu-latest
43
+ runs-on: ubuntu-24.04
44
44
  steps:
45
- - uses: actions/checkout@v4
45
+ - uses: actions/checkout@v7
46
46
  - uses: superfly/flyctl-actions/setup-flyctl@master
47
47
  # Builds on Fly's builders and runs the release command (migrations) before switching.
48
48
  - run: flyctl deploy --remote-only
@@ -51,9 +51,9 @@ jobs:
51
51
  # clubedge:end
52
52
  # clubedge:if deploy=railway
53
53
  railway:
54
- runs-on: ubuntu-latest
54
+ runs-on: ubuntu-24.04
55
55
  steps:
56
- - uses: actions/checkout@v4
56
+ - uses: actions/checkout@v7
57
57
  # Uploads this checkout; Railway builds the image and runs the pre-deploy migrations.
58
58
  - run: npx --yes @railway/cli@4 up --ci --service "$RAILWAY_SERVICE"
59
59
  env:
@@ -62,7 +62,7 @@ jobs:
62
62
  # clubedge:end
63
63
  # clubedge:if deploy=render
64
64
  render:
65
- runs-on: ubuntu-latest
65
+ runs-on: ubuntu-24.04
66
66
  steps:
67
67
  # The service's deploy hook starts a deploy of its branch, with its pre-deploy migrations.
68
68
  - run: curl --fail --silent --show-error --request POST "$RENDER_DEPLOY_HOOK_URL"
@@ -71,15 +71,15 @@ jobs:
71
71
  # clubedge:end
72
72
  # clubedge:if deploy=vercel
73
73
  vercel:
74
- runs-on: ubuntu-latest
74
+ runs-on: ubuntu-24.04
75
75
  env:
76
76
  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
77
77
  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
78
78
  VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
79
79
  steps:
80
- - uses: actions/checkout@v4
81
- - uses: pnpm/action-setup@v4
82
- - uses: actions/setup-node@v4
80
+ - uses: actions/checkout@v7
81
+ - uses: pnpm/action-setup@v6
82
+ - uses: actions/setup-node@v7
83
83
  with:
84
84
  node-version: 22
85
85
  cache: pnpm
@@ -91,14 +91,14 @@ jobs:
91
91
  # clubedge:end
92
92
  # clubedge:if deploy=netlify
93
93
  netlify:
94
- runs-on: ubuntu-latest
94
+ runs-on: ubuntu-24.04
95
95
  env:
96
96
  NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
97
97
  NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
98
98
  steps:
99
- - uses: actions/checkout@v4
100
- - uses: pnpm/action-setup@v4
101
- - uses: actions/setup-node@v4
99
+ - uses: actions/checkout@v7
100
+ - uses: pnpm/action-setup@v6
101
+ - uses: actions/setup-node@v7
102
102
  with:
103
103
  node-version: 22
104
104
  cache: pnpm
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/start",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/web",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "scripts": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clubedge-starter-v1",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "packageManager": "pnpm@10.9.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/auth",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/auth-better-auth",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Better Auth adapter for @clubedge/auth, storing users and sessions in your own PostgreSQL.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/auth-supabase",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Supabase Auth adapter for @clubedge/auth.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/cache",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/cache-redis",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Redis rate limiter and cache for @clubedge/cache.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/core",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/db",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/email",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Email interface, console and in-memory providers, and message templates.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/email-resend",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Resend provider for @clubedge/email, through Resend's HTTP API.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/email-smtp",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "SMTP provider for @clubedge/email, for any SMTP server.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/jobs",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Background jobs interface: job definitions, a queue contract, and an in-memory queue.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/jobs-pg-boss",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "pg-boss provider for @clubedge/jobs: a job queue stored in the project's PostgreSQL.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/local-db",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "PostgreSQL for development without Docker: PGlite served over the PostgreSQL protocol.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -8,11 +8,12 @@ describe("serveLocalPostgres", () => {
8
8
  let server: LocalPostgresServer;
9
9
  let url: string;
10
10
 
11
+ // Starting PGlite takes seconds on a busy CI runner.
11
12
  beforeAll(async () => {
12
13
  db = await PGlite.create();
13
14
  server = await serveLocalPostgres(db, { port: 0 });
14
15
  url = `postgresql://postgres:postgres@127.0.0.1:${server.port}/postgres`;
15
- });
16
+ }, 30_000);
16
17
 
17
18
  afterAll(async () => {
18
19
  await server.close();
@@ -115,5 +116,5 @@ describe("LocalPostgresServer.close", () => {
115
116
  await client.end({ timeout: 0 });
116
117
  await db.close();
117
118
  }
118
- });
119
+ }, 30_000);
119
120
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/observability",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Observability interface: error reports and spans for @clubedge/core's telemetry hooks, and an in-memory provider for tests.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/observability-otel",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "OpenTelemetry provider for @clubedge/observability: traces and error logs exported over OTLP to any collector.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/observability-sentry",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Sentry provider for @clubedge/observability: server and browser errors, traces, and source map uploads.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/payments",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Provider-neutral payments: the PaymentProvider interface, billing logic, and subscription tables.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -2,7 +2,7 @@ import { PGlite } from "@electric-sql/pglite";
2
2
  import { drizzle } from "drizzle-orm/pglite";
3
3
  import { migrate } from "drizzle-orm/pglite/migrator";
4
4
  import { fileURLToPath } from "node:url";
5
- import { beforeEach, describe, expect, it, vi } from "vitest";
5
+ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
6
6
  import { users, type Database } from "@clubedge/db";
7
7
  import { createBilling, findSubscription, isEntitled, NoBillingAccountError } from "./billing";
8
8
  import { subscriptions } from "./schema";
@@ -10,7 +10,7 @@ import type { PaymentEvent, PaymentProvider, SubscriptionSnapshot } from "./type
10
10
 
11
11
  const folder = (path: string) => fileURLToPath(new URL(path, import.meta.url));
12
12
 
13
- /** A fresh PostgreSQL (PGlite) with the app's and the payment migrations applied. */
13
+ /** A PostgreSQL (PGlite) with the app's and the payment migrations applied. */
14
14
  async function database() {
15
15
  const client = await PGlite.create();
16
16
  const db = drizzle({ client });
@@ -20,7 +20,19 @@ async function database() {
20
20
  migrationsTable: "__drizzle_migrations_payments",
21
21
  });
22
22
  // The query builder is the same as the postgres.js driver's that the app uses.
23
- return db as unknown as Database;
23
+ return { client, db: db as unknown as Database };
24
+ }
25
+
26
+ /** Empties the app's tables; the migration records live in the drizzle schema and stay. */
27
+ async function emptyTables(client: PGlite) {
28
+ const { rows } = await client.query<{ name: string }>(
29
+ "select tablename as name from pg_tables where schemaname = 'public'",
30
+ );
31
+ if (rows.length) {
32
+ await client.exec(
33
+ `truncate ${rows.map(({ name }) => `"${name}"`).join(", ")} restart identity cascade`,
34
+ );
35
+ }
24
36
  }
25
37
 
26
38
  /** A provider whose next webhook event the test chooses. */
@@ -53,11 +65,22 @@ const snapshot = (
53
65
  const request = { body: "{}", headers: new Headers() };
54
66
 
55
67
  describe("createBilling", () => {
68
+ let client: PGlite;
56
69
  let db: Database;
57
70
  let userId: string;
58
71
 
72
+ // One database for the file: starting PGlite and migrating takes seconds on a busy CI runner,
73
+ // so each test starts from empty tables instead of a new database.
74
+ beforeAll(async () => {
75
+ ({ client, db } = await database());
76
+ }, 60_000);
77
+
78
+ afterAll(async () => {
79
+ await client.close();
80
+ });
81
+
59
82
  beforeEach(async () => {
60
- db = await database();
83
+ await emptyTables(client);
61
84
  const [user] = await db
62
85
  .insert(users)
63
86
  .values({ email: "ada@example.com" })
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/payments-stripe",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Stripe provider for @clubedge/payments: Checkout, the customer portal, and verified webhooks.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/storage",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/storage-s3",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "S3-compatible adapter for @clubedge/storage.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/storage-supabase",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Supabase Storage adapter for @clubedge/storage.",
5
5
  "private": true,
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/ui",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "repository": "Clubedge/clubedge-starter",
3
- "ref": "v0.15.0",
4
- "commit": "239f9e9fd575165d72f70a91592cafbd43bb7574",
3
+ "ref": "v0.16.0",
4
+ "commit": "e233aa68ddc8c21df6cf2b01b9e26876d2e4aeca",
5
5
  "files": [
6
6
  ".dockerignore",
7
7
  ".env.example",
@@ -11,6 +11,7 @@
11
11
  ".github/ISSUE_TEMPLATE/feature_request.yml",
12
12
  ".github/pull_request_template.md",
13
13
  ".github/workflows/ci.yml",
14
+ ".github/workflows/clubedge-upgrade.yml",
14
15
  ".github/workflows/deploy.yml",
15
16
  "_gitignore",
16
17
  ".prettierignore",