@clubedge/create-clubedge-app 0.45.0 → 0.46.1
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/CHANGELOG.md +22 -0
- package/README.md +25 -11
- package/dist/cli/args.js +1 -1
- package/dist/cli/ui.js +3 -2
- package/dist/core/env.js +17 -1
- package/dist/core/manifest-fields.js +12 -0
- package/dist/core/plan.js +11 -1
- package/dist/core/update.js +24 -7
- package/dist/index.js +12 -8
- package/dist/modify.js +192 -39
- package/package.json +3 -3
- package/skills/clubedge-starter/SKILL.md +3 -0
- package/template/.github/workflows/expo-release.yml +65 -0
- package/template/.github/workflows/expo-update.yml +56 -0
- package/template/.github/workflows/shell-release.yml +145 -0
- package/template/README.md +12 -0
- package/template/SETUP.md +121 -5
- package/template/apps/api/.env.example +24 -0
- package/template/apps/api/clubedge.stack.json +12 -0
- package/template/apps/api/package.json +4 -1
- package/template/apps/api/src/app.ts +10 -0
- package/template/apps/api/src/env/production.test.ts +18 -0
- package/template/apps/api/src/env/production.ts +28 -0
- package/template/apps/api/src/routes/push.test.ts +160 -0
- package/template/apps/api/src/routes/push.ts +146 -0
- package/template/apps/api/src/server/index.ts +2 -0
- package/template/apps/api/src/server/push.ts +40 -0
- package/template/apps/api/src/server/variants/push.native.ts +78 -0
- package/template/apps/api-fastapi/.env.example +24 -0
- package/template/apps/api-fastapi/Dockerfile +1 -0
- package/template/apps/api-fastapi/clubedge.stack.json +24 -0
- package/template/apps/api-fastapi/package.json +1 -1
- package/template/apps/api-fastapi/pyproject.toml +3 -1
- package/template/apps/api-fastapi/src/clubedge_api/app.py +18 -0
- package/template/apps/api-fastapi/src/clubedge_api/production_env.py +27 -0
- package/template/apps/api-fastapi/src/clubedge_api/push.py +141 -0
- package/template/apps/api-fastapi/src/clubedge_api/push_expo.py +67 -0
- package/template/apps/api-fastapi/src/clubedge_api/push_native.py +316 -0
- package/template/apps/api-fastapi/src/clubedge_api/push_service.py +21 -0
- package/template/apps/api-fastapi/src/clubedge_api/routes/push.py +163 -0
- package/template/apps/api-fastapi/src/clubedge_api/server.py +5 -0
- package/template/apps/api-fastapi/src/clubedge_api/variants/push_service_native.py +19 -0
- package/template/apps/api-fastapi/tests/test_production_env.py +11 -0
- package/template/apps/api-fastapi/tests/test_push.py +289 -0
- package/template/apps/api-fastapi/tests/test_push_expo.py +90 -0
- package/template/apps/api-fastapi/tests/test_push_native.py +202 -0
- package/template/apps/api-fastapi/uv.lock +197 -1
- package/template/apps/capacitor/.env.example +13 -0
- package/template/apps/capacitor/_gitignore +2 -0
- package/template/apps/capacitor/capacitor.config.ts +19 -0
- package/template/apps/capacitor/clubedge.stack.json +9 -1
- package/template/apps/capacitor/e2e/README.md +23 -0
- package/template/apps/capacitor/e2e/account.yaml +116 -0
- package/template/apps/capacitor/package.json +5 -2
- package/template/apps/capacitor/scripts/build.mjs +17 -6
- package/template/apps/capacitor/scripts/push.mjs +80 -0
- package/template/apps/capacitor/scripts/release.mjs +133 -0
- package/template/apps/capacitor/scripts/version.mjs +129 -0
- package/template/apps/capacitor/version.json +3 -0
- package/template/apps/expo/.env.example +7 -0
- package/template/apps/expo/app.config.ts +93 -33
- package/template/apps/expo/app.json +5 -0
- package/template/apps/expo/clubedge.stack.json +33 -2
- package/template/apps/expo/e2e/README.md +26 -0
- package/template/apps/expo/e2e/account.yaml +111 -0
- package/template/apps/expo/eas.json +33 -0
- package/template/apps/expo/package.json +9 -6
- package/template/apps/expo/scripts/set-version.mjs +66 -0
- package/template/apps/expo/src/app/(app)/index.tsx +2 -0
- package/template/apps/expo/src/app/(app)/settings/index.tsx +4 -2
- package/template/apps/expo/src/components/ota-banner.tsx +69 -0
- package/template/apps/expo/src/components/push-settings.tsx +56 -0
- package/template/apps/expo/src/components/sign-out-sheet.tsx +3 -0
- package/template/apps/expo/src/components/update-gate.tsx +5 -1
- package/template/apps/expo/src/lib/api.ts +2 -1
- package/template/apps/expo/src/lib/ota.ts +30 -0
- package/template/apps/expo/src/lib/push-token.ts +20 -0
- package/template/apps/expo/src/lib/push.ts +161 -0
- package/template/apps/expo/src/lib/send.ts +27 -0
- package/template/apps/expo/src/variants/api.auth.ts +6 -11
- package/template/apps/expo/src/variants/app-layout.auth.tsx +2 -0
- package/template/apps/expo/src/variants/push-token.native.ts +14 -0
- package/template/apps/expo/variants/eas.updates.json +36 -0
- package/template/apps/start/clubedge.stack.json +3 -0
- package/template/apps/start/package.json +3 -3
- package/template/apps/vite/clubedge.stack.json +5 -0
- package/template/apps/vite/package.json +3 -3
- package/template/apps/vite/server.mjs +3 -0
- package/template/apps/vite/src/components/dashboard/variants/account-button.supabase.tsx +7 -0
- package/template/apps/vite/src/main.tsx +5 -2
- package/template/apps/vite/src/routes/settings.tsx +19 -4
- package/template/apps/web/clubedge.stack.json +3 -0
- package/template/apps/web/package.json +3 -3
- package/template/clubedge/modules/ota.json +15 -0
- package/template/clubedge/modules/push.json +36 -0
- package/template/clubedge.template.json +19 -2
- package/template/package.json +1 -1
- package/template/packages/auth/package.json +1 -1
- package/template/packages/auth-appwrite/package.json +1 -1
- package/template/packages/auth-better-auth/package.json +1 -1
- package/template/packages/auth-firebase/package.json +1 -1
- package/template/packages/auth-supabase/package.json +1 -1
- package/template/packages/cache/package.json +1 -1
- package/template/packages/cache-redis/package.json +1 -1
- package/template/packages/contract/openapi.yaml +149 -0
- package/template/packages/contract/package.json +1 -1
- package/template/packages/contract/src/app-update.test.ts +21 -0
- package/template/packages/contract/src/app-update.ts +4 -2
- package/template/packages/contract/src/conformance.ts +43 -0
- package/template/packages/contract/src/index.ts +1 -1
- package/template/packages/contract/src/schema.ts +179 -0
- package/template/packages/core/package.json +1 -1
- package/template/packages/db/package.json +1 -1
- package/template/packages/email/package.json +1 -1
- package/template/packages/email-resend/package.json +1 -1
- package/template/packages/email-smtp/package.json +1 -1
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/jobs-bullmq/package.json +1 -1
- package/template/packages/jobs-inngest/package.json +1 -1
- package/template/packages/jobs-pg-boss/package.json +1 -1
- package/template/packages/local-db/package.json +1 -1
- package/template/packages/mobile/package.json +9 -6
- package/template/packages/mobile/src/push-settings.tsx +95 -0
- package/template/packages/mobile/src/push.test.ts +153 -0
- package/template/packages/mobile/src/push.ts +126 -0
- package/template/packages/observability/package.json +1 -1
- package/template/packages/observability-otel/package.json +1 -1
- package/template/packages/observability-sentry/package.json +1 -1
- package/template/packages/payments/package.json +1 -1
- package/template/packages/payments-stripe/package.json +1 -1
- package/template/packages/push/drizzle/0000_initial.sql +11 -0
- package/template/packages/push/drizzle/meta/0000_snapshot.json +96 -0
- package/template/packages/push/drizzle/meta/_journal.json +13 -0
- package/template/packages/push/drizzle.config.ts +12 -0
- package/template/packages/push/package.json +35 -0
- package/template/packages/push/scripts/migrate.ts +14 -0
- package/template/packages/push/src/devices.test.ts +133 -0
- package/template/packages/push/src/devices.ts +78 -0
- package/template/packages/push/src/index.ts +12 -0
- package/template/packages/push/src/schema.ts +29 -0
- package/template/packages/push/src/types.ts +59 -0
- package/template/packages/push/tsconfig.json +4 -0
- package/template/packages/push-expo/package.json +21 -0
- package/template/packages/push-expo/src/index.test.ts +82 -0
- package/template/packages/push-expo/src/index.ts +70 -0
- package/template/packages/push-expo/tsconfig.json +4 -0
- package/template/packages/push-native/package.json +21 -0
- package/template/packages/push-native/src/index.test.ts +245 -0
- package/template/packages/push-native/src/index.ts +291 -0
- package/template/packages/push-native/tsconfig.json +4 -0
- package/template/packages/storage/package.json +1 -1
- package/template/packages/storage-s3/package.json +1 -1
- package/template/packages/storage-supabase/package.json +1 -1
- package/template/packages/ui/package.json +2 -2
- package/template/pnpm-lock.yaml +512 -362
- package/template.lock.json +63 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `create-clubedge-app` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.46.1
|
|
6
|
+
|
|
7
|
+
- **Starter v0.30.1** by default (#167). Its template schema is still 6. The [Starter's release notes](https://github.com/Clubedge/clubedge-starter/releases/tag/v0.30.1) cover:
|
|
8
|
+
- the native app (`--mobile expo`) reaching its API again: since v0.29.0, each of its calls to the API threw after the API had answered, so the account never loaded, and saving a name or deleting the account failed;
|
|
9
|
+
- getting the fix to installed apps: a new build in the stores, then `APP_MINIMUM_VERSION` on the API, which sends older installs to the update screen, or an over-the-air update with `--ota eas`;
|
|
10
|
+
- end-to-end tests of both mobile apps on an Android emulator;
|
|
11
|
+
- Expo SDK 58's latest patch versions.
|
|
12
|
+
|
|
13
|
+
## 0.46.0
|
|
14
|
+
|
|
15
|
+
- **Add and remove the apps installed on devices** (#161), so a project created without them can take them later, and drop them again, without starting over: `pnpm clubedge add mobile expo` for the native app, `pnpm clubedge add shell capacitor` for the shell that packages the website, and `pnpm clubedge remove mobile` or `pnpm clubedge remove shell`. The shell needs Starter v0.27.0 or later, and the native app v0.28.0.
|
|
16
|
+
- Adding an app brings its folder, its CI job and the Starter's other sections for it, its dependencies, and its local env file. `package.json` records it, so later changes and upgrades keep it.
|
|
17
|
+
- The app must fit the project, and the CLI says which project can have it when this one can't. When the project's option for a module is one the app lacks, such as a sign-in the native app doesn't have yet, switch it in the same command: `pnpm clubedge add mobile expo auth better-auth`.
|
|
18
|
+
- Removing an app deletes the Starter's files and lists what was never the Starter's, such as the shell's `android/` and `ios/` projects, to delete once you no longer need them.
|
|
19
|
+
- The framework and the API stay as the project was created.
|
|
20
|
+
- When a change keeps a variable in a local env file, the comments above it that you never edited now follow the example's, so a later change that removes the variable removes them too.
|
|
21
|
+
- **Module options can name the registry packages only they need** (#162), in a new `dependencies` field. A project without the option leaves them out, such as the notification plugins in projects without push. Stack packs can use it too ([stack packs](https://github.com/Clubedge/create-clubedge-app/blob/main/docs/stack-packs.md)).
|
|
22
|
+
- **Starter v0.30.0** by default (#163). Its template schema is still 6. The [Starter's release notes](https://github.com/Clubedge/clubedge-starter/releases/tag/v0.30.0) cover:
|
|
23
|
+
- store releases for both mobile apps, signed, from your computer or a workflow, with build numbers made from each app's version;
|
|
24
|
+
- push notifications for both apps (`--push expo` or `--push native`);
|
|
25
|
+
- over-the-air updates for the native app (`--ota eas`).
|
|
26
|
+
|
|
5
27
|
## 0.45.0
|
|
6
28
|
|
|
7
29
|
- **Starter v0.29.0** by default (#159). Its template schema is still 6. The [Starter's release notes](https://github.com/Clubedge/clubedge-starter/releases/tag/v0.29.0) cover:
|
package/README.md
CHANGED
|
@@ -165,6 +165,8 @@ pnpm dlx @clubedge/create-clubedge-app my-app --framework vite --api hono --mobi
|
|
|
165
165
|
|
|
166
166
|
A shell packages the frontend's static files, so it needs `--framework vite`, and the frontend still needs its API (`--api hono` or `--api fastapi`). A native app calls the API through the website, as the shell does, so it needs one too. Next.js and TanStack Start render their pages on their own server and have no separate API for an app to call: the CLI refuses `--shell` and `--mobile` with them, and interactive runs only ask about a mobile app after a frontend and its API. A shell and a native app run no server, so `deploy` deploys the frontend and the API as above and leaves them out. The native app's Starter lists the sign-in options it supports, so the CLI refuses an `--auth` option the app does not have yet.
|
|
167
167
|
|
|
168
|
+
A project created without them can add them later, and drop them again: `pnpm clubedge add mobile expo`, `pnpm clubedge add shell capacitor`, or `pnpm clubedge remove mobile` (since CLI 0.46.0, see [Change an existing project](#change-an-existing-project)).
|
|
169
|
+
|
|
168
170
|
#### Without a website
|
|
169
171
|
|
|
170
172
|
A project can also be only an API and its native app: `--framework none` leaves the web app out (since CLI 0.44.0, with Starter v0.28.0).
|
|
@@ -241,7 +243,7 @@ pnpm dlx @clubedge/create-clubedge-app my-app --yes --no-git --framework tanstac
|
|
|
241
243
|
pnpm dlx @clubedge/create-clubedge-app my-app --ref main
|
|
242
244
|
|
|
243
245
|
# Reproduce a specific Starter release or commit
|
|
244
|
-
pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.
|
|
246
|
+
pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.30.1
|
|
245
247
|
pnpm dlx @clubedge/create-clubedge-app my-app --ref <commit-sha>
|
|
246
248
|
|
|
247
249
|
# Test local Starter changes before releasing them
|
|
@@ -250,7 +252,7 @@ pnpm dlx @clubedge/create-clubedge-app my-app --template-dir ../clubedge-starter
|
|
|
250
252
|
|
|
251
253
|
## Change an existing project
|
|
252
254
|
|
|
253
|
-
Inside a project created by the CLI, `add` and `remove` change modules, and `upgrade` moves the project to a newer Starter release. Projects from Starter v0.8.0 on have a `clubedge` script, so `pnpm clubedge add cache redis` works too. Without a module or option, `add` and `remove` ask for them, with a list where you can pick several modules at once:
|
|
255
|
+
Inside a project created by the CLI, `add` and `remove` change modules and the apps installed on devices, and `upgrade` moves the project to a newer Starter release. Projects from Starter v0.8.0 on have a `clubedge` script, so `pnpm clubedge add cache redis` works too. Without a module or option, `add` and `remove` ask for them, with a list where you can pick several modules and apps at once:
|
|
254
256
|
|
|
255
257
|
```sh
|
|
256
258
|
pnpm clubedge add # choose a module and option from a list
|
|
@@ -258,9 +260,11 @@ pnpm dlx @clubedge/create-clubedge-app add cache redis # or switch: add aut
|
|
|
258
260
|
pnpm dlx @clubedge/create-clubedge-app remove storage
|
|
259
261
|
pnpm clubedge add email resend payments stripe # several modules as one change
|
|
260
262
|
pnpm clubedge remove storage payments
|
|
263
|
+
pnpm clubedge add mobile expo # the native app; add shell capacitor packages the website
|
|
264
|
+
pnpm clubedge remove mobile
|
|
261
265
|
pnpm dlx @clubedge/create-clubedge-app add infra local --dry-run
|
|
262
266
|
pnpm dlx @clubedge/create-clubedge-app@latest upgrade # to the newest CLI's Starter release
|
|
263
|
-
pnpm dlx @clubedge/create-clubedge-app upgrade --ref v0.
|
|
267
|
+
pnpm dlx @clubedge/create-clubedge-app upgrade --ref v0.30.1 # or a specific tag, branch, or commit
|
|
264
268
|
```
|
|
265
269
|
|
|
266
270
|
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.
|
|
@@ -274,6 +278,15 @@ The CLI renders the project twice, from the Starter commit and modules recorded
|
|
|
274
278
|
|
|
275
279
|
Several modules in one command are applied as one change, and their requirements are checked together, so `add auth supabase storage supabase` works even though Supabase Storage needs Supabase Auth. When lines conflict, the CLI lists each file with its number of conflict blocks. Before an upgrade, it lists the Starter releases it crosses with the sections of their release notes, such as `v0.13.0 Observability, Tests, Also`.
|
|
276
280
|
|
|
281
|
+
### Apps installed on devices
|
|
282
|
+
|
|
283
|
+
Since CLI 0.46.0, `add` and `remove` also take the apps that run on phones rather than on a server: the native app (`add mobile expo`) and the shell that packages the website (`add shell capacitor`), with Starter v0.27.0 or later for the shell and v0.28.0 for the native app. The project's framework and API stay as they were created, since its deployment and data are built around them.
|
|
284
|
+
|
|
285
|
+
- Adding an app brings its folder, its CI job and the rest of the Starter's sections for it, its dependencies in `pnpm-lock.yaml`, and its local env file, made from its example as a new project's is. `package.json` records it, so later changes and upgrades keep it.
|
|
286
|
+
- The app must fit the project: the native app calls an API, so it needs `--framework vite` or `none` with an API, and the shell packages the Vite frontend. The CLI says which project can have the app when this one cannot.
|
|
287
|
+
- An app implements the modules it uses, such as the native app's sign-in. When the project's option is one it lacks, switch it in the same command: `add mobile expo auth better-auth`.
|
|
288
|
+
- Removing an app deletes the Starter's files and leaves what was never the Starter's, such as its env file, its `node_modules`, and the shell's `android/` and `ios/` projects. The CLI lists them, to delete once you no longer need them.
|
|
289
|
+
|
|
277
290
|
The project must be a Git repository without uncommitted changes, so you can review the result with `git diff` and undo it. Pass `--force` to skip this check. Add `--no-install` to skip `pnpm install`. Afterward, run `pnpm db:migrate` and `pnpm check:services` if the change added tables or services.
|
|
278
291
|
|
|
279
292
|
pnpm cannot read a `package.json` with conflict markers, so a change that leaves conflicts in one skips `pnpm install` and says so: resolve them, then run `pnpm install`. Until then, `pnpm clubedge` does not start either, and the CLI's own commands (`pnpm dlx @clubedge/create-clubedge-app status`, for example) stop with a message that names the file and the command that compares it with the Starter's version in `.clubedge/incoming/`. When the install fails for another reason, the CLI still lists what is left to resolve, then exits with an error.
|
|
@@ -400,27 +413,27 @@ Projects created with `--infra neon` also have `.github/workflows/preview.yml`,
|
|
|
400
413
|
Each CLI release is tested against one specific Starter release:
|
|
401
414
|
|
|
402
415
|
```text
|
|
403
|
-
create-clubedge-app v0.
|
|
416
|
+
create-clubedge-app v0.46.1
|
|
404
417
|
|
|
|
405
418
|
v
|
|
406
|
-
Clubedge Starter v0.
|
|
407
|
-
commit
|
|
419
|
+
Clubedge Starter v0.30.1
|
|
420
|
+
commit 6d04531ffb2cd9f0e76b6e2b3b542f733b66aed6
|
|
408
421
|
```
|
|
409
422
|
|
|
410
423
|
The current relationship is:
|
|
411
424
|
|
|
412
425
|
| CLI release | Default Starter release | Meaning |
|
|
413
426
|
| --- | --- | --- |
|
|
414
|
-
| `create-clubedge-app@0.
|
|
427
|
+
| `create-clubedge-app@0.46.1` | `clubedge-starter@v0.30.1` | The CLI's required, tested default |
|
|
415
428
|
|
|
416
429
|
This means the same CLI version always scaffolds the same Starter revision by default. The CLI does not silently follow Starter `main`.
|
|
417
430
|
|
|
418
|
-
The `v0.
|
|
419
|
-
`
|
|
431
|
+
The `v0.30.1` tag is expected to resolve to commit
|
|
432
|
+
`6d04531ffb2cd9f0e76b6e2b3b542f733b66aed6`. The CLI verifies this before
|
|
420
433
|
scaffolding with the default reference and fails rather than silently using a
|
|
421
434
|
moved tag.
|
|
422
435
|
|
|
423
|
-
When the Starter changes, its maintainers first publish a new versioned release, such as `v0.
|
|
436
|
+
When the Starter changes, its maintainers first publish a new versioned release, such as `v0.30.1`. 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.
|
|
424
437
|
|
|
425
438
|
Every generated project records its CLI version, Starter repository, Starter ref, Starter commit, framework, and modules in `package.json` and `README.md`.
|
|
426
439
|
|
|
@@ -475,7 +488,7 @@ CI packs the CLI with `npm pack`, scaffolds clean projects from the packed tarba
|
|
|
475
488
|
1. Make and validate changes in `clubedge-starter`.
|
|
476
489
|
2. Run lint, typecheck, unit tests, browser tests, and production build.
|
|
477
490
|
3. Update the Starter package versions.
|
|
478
|
-
4. Commit `main`, create a release tag such as `v0.
|
|
491
|
+
4. Commit `main`, create a release tag such as `v0.30.1`, and publish the Starter release.
|
|
479
492
|
|
|
480
493
|
### CLI maintainers
|
|
481
494
|
|
|
@@ -531,6 +544,7 @@ Each phase ends with a Starter release and a CLI release pinned to it. The full
|
|
|
531
544
|
| 0.43.0 | v0.27.1 | Upgrades keep each app's own version; add, remove, and upgrade that change nothing skip the Starter download |
|
|
532
545
|
| 0.44.0 | v0.28.0 | Starter v0.28.0: the native app with Expo (`--mobile expo`, template schema 6); projects without a website (`--framework none`), with template rules; audit exceptions per stack |
|
|
533
546
|
| 0.45.0 | v0.29.0 | Starter v0.29.0: an update policy for installed apps, a new design for the native app, and clean splash screens; `package.json` merged by key |
|
|
547
|
+
| 0.46.0 | v0.30.0 | Add and remove the mobile apps in an existing project; Starter v0.30.0: store releases, push notifications, and over-the-air updates; module options' `dependencies` |
|
|
534
548
|
|
|
535
549
|
## Community
|
|
536
550
|
|
package/dist/cli/args.js
CHANGED
|
@@ -153,7 +153,7 @@ function parseCommand(positionals) {
|
|
|
153
153
|
const module = rest[0];
|
|
154
154
|
if (name === "add") {
|
|
155
155
|
if (rest.length > 1 && rest.length % 2 === 1) {
|
|
156
|
-
throw new UsageError("Usage: add [module option]..., for example: add cache redis,
|
|
156
|
+
throw new UsageError("Usage: add [module option]..., for example: add cache redis, add email resend payments stripe, or add mobile expo");
|
|
157
157
|
}
|
|
158
158
|
const changes = rest.length === 1
|
|
159
159
|
? [{ module: rest[0] }]
|
package/dist/cli/ui.js
CHANGED
|
@@ -101,8 +101,9 @@ Usage:
|
|
|
101
101
|
upgrade, status, doctor, try, deploy, or neon. Without a module or option, add and remove ask
|
|
102
102
|
for them.
|
|
103
103
|
|
|
104
|
-
add and remove change modules of an existing project,
|
|
105
|
-
|
|
104
|
+
add and remove change modules of an existing project, and its apps installed on
|
|
105
|
+
devices (add mobile expo, add shell capacitor, remove mobile), several at once as
|
|
106
|
+
one change, and upgrade moves it to a newer Starter (this CLI's release unless --ref
|
|
106
107
|
is given) and lists the Starter releases it crosses. The change is merged into
|
|
107
108
|
your code; lines you changed that it also changes get conflict markers. Commit
|
|
108
109
|
first so you can review the result with git diff.
|
package/dist/core/env.js
CHANGED
|
@@ -49,12 +49,28 @@ export function updateEnvFile(local, baseExample, targetExample) {
|
|
|
49
49
|
const start = generated ? at - comments.length : at;
|
|
50
50
|
lines.splice(start, at - start + 1, ...(generated ? replacement : replacement.slice(-1)));
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Gives a variable whose value stays the target's comments, if the developer kept the base's:
|
|
54
|
+
* another change of the selection then still finds them as generated, and removes them with it.
|
|
55
|
+
*/
|
|
56
|
+
function refreshComments(key) {
|
|
57
|
+
const at = find(lines, key);
|
|
58
|
+
const comments = commentsAbove(lines, at);
|
|
59
|
+
const baseComments = commentsAbove(base, find(base, key));
|
|
60
|
+
const targetComments = commentsAbove(target, find(target, key));
|
|
61
|
+
if (sameLines(comments, baseComments) && !sameLines(baseComments, targetComments)) {
|
|
62
|
+
lines.splice(at - comments.length, comments.length, ...targetComments);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
52
65
|
for (const [key, before] of baseValues) {
|
|
53
66
|
if (!mine.has(key))
|
|
54
67
|
continue;
|
|
55
68
|
const after = targetValues.get(key);
|
|
56
|
-
if (after === before || mine.get(key) === after)
|
|
69
|
+
if (after === before || mine.get(key) === after) {
|
|
70
|
+
if (after !== undefined)
|
|
71
|
+
refreshComments(key);
|
|
57
72
|
continue;
|
|
73
|
+
}
|
|
58
74
|
const generated = mine.get(key) === before;
|
|
59
75
|
if (after === undefined) {
|
|
60
76
|
(generated ? result.removed : result.unused).push(key);
|
|
@@ -89,6 +89,17 @@ export function parseRequires(file, value, prefix) {
|
|
|
89
89
|
}
|
|
90
90
|
return requires;
|
|
91
91
|
}
|
|
92
|
+
/** An npm package name, scoped or not, such as expo-notifications or @capacitor/push-notifications. */
|
|
93
|
+
const PACKAGE_NAME = /^(@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
|
|
94
|
+
/** `dependencies`: packages from the registry, by name. */
|
|
95
|
+
function parseDependencies(file, value, prefix) {
|
|
96
|
+
if (value === undefined)
|
|
97
|
+
return [];
|
|
98
|
+
if (!isStringArray(value) || !value.every((name) => PACKAGE_NAME.test(name))) {
|
|
99
|
+
throw new ManifestError(`${file}: ${prefix}dependencies must be an array of package names.`);
|
|
100
|
+
}
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
92
103
|
/** A module's name, default, and options; `prefix` locates it in its file. */
|
|
93
104
|
export function parseModule(file, entry, prefix) {
|
|
94
105
|
const { fail, requireString } = fields(file, prefix);
|
|
@@ -111,6 +122,7 @@ export function parseModule(file, entry, prefix) {
|
|
|
111
122
|
options[optionId] = {
|
|
112
123
|
name: read.requireString(values, "name"),
|
|
113
124
|
packages: read.optionalStringRecord(values, "packages"),
|
|
125
|
+
dependencies: parseDependencies(file, values.dependencies, optionPrefix),
|
|
114
126
|
files: read.optionalStringArray(values, "files"),
|
|
115
127
|
replace: read.optionalStringRecord(values, "replace"),
|
|
116
128
|
requires: parseRequires(file, values.requires, optionPrefix),
|
package/dist/core/plan.js
CHANGED
|
@@ -238,6 +238,8 @@ function resolveModuleFiles(manifest, selection, stacks) {
|
|
|
238
238
|
const keptPaths = new Set();
|
|
239
239
|
const usedPackages = new Map();
|
|
240
240
|
const offeredPackages = new Map();
|
|
241
|
+
const usedDependencies = new Set();
|
|
242
|
+
const offeredDependencies = new Set();
|
|
241
243
|
const variantSources = new Set();
|
|
242
244
|
/** Variant source path to the default file it replaces. */
|
|
243
245
|
const replacements = new Map();
|
|
@@ -254,6 +256,11 @@ function resolveModuleFiles(manifest, selection, stacks) {
|
|
|
254
256
|
if (selected)
|
|
255
257
|
usedPackages.set(name, directory);
|
|
256
258
|
}
|
|
259
|
+
for (const name of option.dependencies) {
|
|
260
|
+
offeredDependencies.add(name);
|
|
261
|
+
if (selected)
|
|
262
|
+
usedDependencies.add(name);
|
|
263
|
+
}
|
|
257
264
|
for (const [target, variant] of Object.entries(option.replace)) {
|
|
258
265
|
variantSources.add(variant);
|
|
259
266
|
if (selected)
|
|
@@ -303,9 +310,12 @@ function resolveModuleFiles(manifest, selection, stacks) {
|
|
|
303
310
|
if (Object.keys(rename).length)
|
|
304
311
|
scripts.rename = rename;
|
|
305
312
|
const removedPackages = [...offeredPackages].filter(([name]) => !usedPackages.has(name));
|
|
313
|
+
// Registry packages only left-out options list go the way of left-out workspace packages:
|
|
314
|
+
// out of each app's package.json and the lockfile's importers.
|
|
315
|
+
const removedDependencies = [...offeredDependencies].filter((name) => !usedDependencies.has(name));
|
|
306
316
|
return {
|
|
307
317
|
scripts,
|
|
308
|
-
removedPackageNames: removedPackages.map(([name]) => name),
|
|
318
|
+
removedPackageNames: [...removedPackages.map(([name]) => name), ...removedDependencies],
|
|
309
319
|
removedPackageDirectories: removedPackages.map(([, directory]) => directory),
|
|
310
320
|
replacedTargets: new Set(replacements.values()),
|
|
311
321
|
replacements,
|
package/dist/core/update.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Changes an existing project from one generated state (base) to another (target): different
|
|
2
|
-
// modules for add and remove, or a newer Starter for upgrade. The project's own edits
|
|
3
|
-
// through a three-way merge, see merge.ts.
|
|
2
|
+
// modules or apps for add and remove, or a newer Starter for upgrade. The project's own edits
|
|
3
|
+
// survive through a three-way merge, see merge.ts.
|
|
4
4
|
import { mkdir, readdir, rm, rmdir, writeFile } from "node:fs/promises";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { customizeAppManifest } from "./customize.js";
|
|
@@ -14,15 +14,17 @@ export async function planUpdate(project, base, target) {
|
|
|
14
14
|
source: side.source,
|
|
15
15
|
manifest: side.manifest,
|
|
16
16
|
identity: project.identity,
|
|
17
|
-
stacks: project.stacks,
|
|
17
|
+
stacks: side.stacks ?? project.stacks,
|
|
18
18
|
modules: side.modules,
|
|
19
19
|
cliVersion: side.cliVersion,
|
|
20
20
|
});
|
|
21
21
|
const before = await render(base);
|
|
22
22
|
const after = await render(target);
|
|
23
|
-
// The lockfile and the local env files are handled on their own below.
|
|
24
|
-
|
|
25
|
-
const
|
|
23
|
+
// The lockfile and the local env files are handled on their own below. The env file of an app
|
|
24
|
+
// the change removes is left as it is: it is the developer's, and Git ignores it.
|
|
25
|
+
const envFiles = after.envFiles;
|
|
26
|
+
const removedEnvFiles = before.envFiles.filter(({ to }) => !after.envFiles.some((file) => file.to === to));
|
|
27
|
+
const special = [LOCKFILE, ...[...envFiles, ...removedEnvFiles].map(({ to }) => to)];
|
|
26
28
|
const strip = (files) => new Map([...files].filter(([path]) => !special.includes(path)));
|
|
27
29
|
const baseFiles = strip(before.files);
|
|
28
30
|
const targetFiles = strip(after.files);
|
|
@@ -80,7 +82,14 @@ export async function planUpdate(project, base, target) {
|
|
|
80
82
|
const text = (files) => files.get(envFile.from)?.toString("utf8") ?? "";
|
|
81
83
|
const local = current.get(envFile.to)?.toString("utf8");
|
|
82
84
|
const update = local === undefined ? null : updateEnvFile(local, text(before.files), text(after.files));
|
|
83
|
-
|
|
85
|
+
const added = !before.envFiles.some(({ to }) => to === envFile.to);
|
|
86
|
+
const example = after.files.get(envFile.from);
|
|
87
|
+
return {
|
|
88
|
+
path: envFile.to,
|
|
89
|
+
update,
|
|
90
|
+
changed: update !== null && update.text !== local,
|
|
91
|
+
...(local === undefined && added && example ? { create: example } : {}),
|
|
92
|
+
};
|
|
84
93
|
});
|
|
85
94
|
return { project, actions, lockfile, envs };
|
|
86
95
|
}
|
|
@@ -137,5 +146,13 @@ export async function applyUpdate(plan) {
|
|
|
137
146
|
for (const env of plan.envs) {
|
|
138
147
|
if (env.update && env.changed)
|
|
139
148
|
await writeFile(join(root, env.path), env.update.text);
|
|
149
|
+
if (env.create) {
|
|
150
|
+
await mkdir(dirname(join(root, env.path)), { recursive: true });
|
|
151
|
+
// An env file written since the plan was made is never replaced.
|
|
152
|
+
await writeFile(join(root, env.path), env.create, { flag: "wx" }).catch((error) => {
|
|
153
|
+
if (error.code !== "EEXIST")
|
|
154
|
+
throw error;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
140
157
|
}
|
|
141
158
|
}
|
package/dist/index.js
CHANGED
|
@@ -77,13 +77,12 @@ async function chooseStacks(manifest, requested, interactive) {
|
|
|
77
77
|
* offered. Returns null when a prompt is cancelled.
|
|
78
78
|
*/
|
|
79
79
|
async function chooseModules(manifest, stacks, requested, interactive) {
|
|
80
|
-
|
|
80
|
+
// Modules with a choice for these apps: a module only a mobile app uses has one for a website.
|
|
81
|
+
const unasked = Object.keys(manifest.modules).filter((id) => requested[id] === undefined && moduleChoices(manifest, stacks, id, requested).length > 1);
|
|
81
82
|
if (!interactive || unasked.length === 0)
|
|
82
83
|
return requested;
|
|
83
84
|
const customize = await confirm({
|
|
84
|
-
message: `Customize the included services (${
|
|
85
|
-
.map((module) => module.name.toLowerCase())
|
|
86
|
-
.join(", ")})?`,
|
|
85
|
+
message: `Customize the included services (${unasked.map((id) => manifest.modules[id].name.toLowerCase()).join(", ")})?`,
|
|
87
86
|
initialValue: false,
|
|
88
87
|
});
|
|
89
88
|
if (isCancel(customize))
|
|
@@ -101,8 +100,13 @@ async function chooseModules(manifest, stacks, requested, interactive) {
|
|
|
101
100
|
// Modules answered later are checked when the plan is created.
|
|
102
101
|
return value === undefined || allowed.includes(value);
|
|
103
102
|
}));
|
|
104
|
-
|
|
103
|
+
// With one option left, there is nothing to ask. It is still the answer: later modules'
|
|
104
|
+
// requirements check it, and the plan takes it rather than the default.
|
|
105
|
+
if (available.length < 2) {
|
|
106
|
+
if (available[0] !== undefined)
|
|
107
|
+
chosen[id] = available[0][0];
|
|
105
108
|
continue;
|
|
109
|
+
}
|
|
106
110
|
const value = await select({
|
|
107
111
|
message: module.name,
|
|
108
112
|
initialValue: available.some(([optionId]) => optionId === module.default)
|
|
@@ -259,7 +263,7 @@ async function createProject(args) {
|
|
|
259
263
|
const directory = relative(process.cwd(), targetDirectory) || ".";
|
|
260
264
|
const scripts = JSON.parse(await readFile(join(targetDirectory, "package.json"), "utf8")).scripts ?? {};
|
|
261
265
|
const later = scripts.clubedge
|
|
262
|
-
? `Change modules later with \`pnpm clubedge add\` or \`pnpm clubedge remove\`, and get Starter updates with \`pnpm clubedge upgrade\`.\n\n`
|
|
266
|
+
? `Change modules and mobile apps later with \`pnpm clubedge add\` or \`pnpm clubedge remove\`, and get Starter updates with \`pnpm clubedge upgrade\`.\n\n`
|
|
263
267
|
: "";
|
|
264
268
|
outro(`Next steps:\n cd ${directory}\n${migrate ? ` ${pnpmCommand} ${migrate}\n` : ""} ${pnpmCommand} dev\n\n` +
|
|
265
269
|
`See SETUP.md for database and provider configuration.\n\n` +
|
|
@@ -318,8 +322,8 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
318
322
|
}
|
|
319
323
|
else {
|
|
320
324
|
const intros = {
|
|
321
|
-
add: "Add a module to this project",
|
|
322
|
-
remove: "Remove a module from this project",
|
|
325
|
+
add: "Add a module or an app to this project",
|
|
326
|
+
remove: "Remove a module or an app from this project",
|
|
323
327
|
upgrade: "Upgrade this project to a newer Starter",
|
|
324
328
|
};
|
|
325
329
|
intro(intros[args.command.name]);
|