@clubedge/create-clubedge-app 0.47.0 → 0.48.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 +45 -3
- package/README.md +20 -16
- package/dist/cli/ui.js +4 -2
- package/dist/core/customize.js +6 -3
- package/dist/core/deploy.js +11 -5
- package/dist/core/execute.js +26 -10
- package/dist/core/health.js +26 -2
- package/dist/core/lockfile-peers.js +15 -2
- package/dist/core/merge.js +47 -4
- package/dist/core/package-json-merge.js +88 -10
- package/dist/core/patches.js +140 -0
- package/dist/core/project.js +3 -5
- package/dist/core/update.js +23 -3
- package/dist/inspect.js +13 -18
- package/dist/modify.js +129 -26
- package/dist/utils/fs.js +15 -10
- package/package.json +3 -3
- package/template/apps/api/package.json +1 -1
- package/template/apps/api-fastapi/package.json +1 -1
- package/template/apps/api-fastapi/pyproject.toml +1 -1
- package/template/apps/api-fastapi/src/clubedge_api/users.py +12 -7
- package/template/apps/api-fastapi/tests/test_users.py +19 -1
- package/template/apps/api-fastapi/uv.lock +1 -1
- package/template/apps/capacitor/package.json +1 -1
- package/template/apps/expo/package.json +16 -16
- package/template/apps/start/package.json +1 -1
- package/template/apps/vite/package.json +1 -1
- package/template/apps/web/package.json +1 -1
- package/template/clubedge.template.json +1 -1
- package/template/deploy/cloudflare/package.json +1 -1
- package/template/e2e/tsconfig.json +7 -0
- package/template/package.json +3 -2
- 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 +2 -2
- package/template/packages/cache-redis/src/index.test.ts +8 -0
- package/template/packages/cache-redis/src/index.ts +16 -8
- package/template/packages/contract/package.json +1 -1
- package/template/packages/core/package.json +1 -1
- package/template/packages/db/package.json +1 -1
- package/template/packages/db/src/users.test.ts +54 -1
- package/template/packages/db/src/users.ts +62 -15
- 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 +1 -1
- 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/package.json +1 -1
- package/template/packages/push-expo/package.json +1 -1
- package/template/packages/push-native/package.json +1 -1
- 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 +1 -1
- package/template/pnpm-lock.yaml +544 -571
- package/template.lock.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `create-clubedge-app` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.48.1
|
|
6
|
+
|
|
7
|
+
- **Starter v0.31.2** by default (#214). Its template schema is still 6. The [Starter's release notes](https://github.com/Clubedge/clubedge-starter/releases/tag/v0.31.2) cover:
|
|
8
|
+
- a new user's first requests at once, which get one users row instead of a 500 for all but one, in the Hono API, the FastAPI API, and the websites; a sync of the profile to an email address another user's row has still fails.
|
|
9
|
+
- **Projects from Starter releases before v0.5.0 upgrade** (#206; issue #205). They record no modules, since those releases had none, and the CLI refused them as "created before create-clubedge-app 0.5.0", though 0.48.0 had created them. An upgrade now keeps Supabase Auth, S3 storage, and the Redis cache, which those releases built in and v0.5.0 made its first modules, and a note says so; modules that came later start as `none`. `add` and `remove` on such a project say to upgrade first.
|
|
10
|
+
- **A dependency you only raised within its range, as `pnpm up` does, takes the Starter's new major or its removal** (#208; issue #207). After `pnpm up -r`, every upgrade to Starter v0.31.1 left a `package.json` conflict pnpm cannot read, such as `redis` `^5.12.2` against the Starter's `^6.3.1`, or a dependency the Starter moved to another package. A change that asks for less than your raise, such as a pin below it, still conflicts, and a range both sides raised still takes the higher one.
|
|
11
|
+
- **A `pnpm patch` the change leaves unused is named before it stops `pnpm install`** (#210; issue #209). A patch made on a version the change moves away from stops the install with `ERR_PNPM_UNUSED_PATCH`. The CLI names it, with the range that leaves it out, skips the install, and says how to keep it: keyed by the package alone, pnpm applies it to whichever version it installs.
|
|
12
|
+
- CI upgrades a project from Starter v0.4.0 and matches it against a fresh one (#206), and the tests that commit to Git have more time on Windows (#210).
|
|
13
|
+
|
|
14
|
+
## 0.48.0
|
|
15
|
+
|
|
16
|
+
- **Starter v0.31.1** by default (#202). Its template schema is still 6. The [Starter's release notes](https://github.com/Clubedge/clubedge-starter/releases/tag/v0.31.1) cover:
|
|
17
|
+
- Expo SDK 58's latest patch versions, without which the native app's CI job fails in projects generated from v0.31.0;
|
|
18
|
+
- version 6 of the `redis` client for the Redis cache, still speaking RESP2;
|
|
19
|
+
- `pnpm typecheck` checking the browser tests too.
|
|
20
|
+
- **`add`, `remove`, and `upgrade` refuse to change a project that still has conflict markers from an earlier change** (#198; issue #195). Merging into those files could leave old and new markers interleaved, or carry them forward with exit code 0. The CLI now lists each file with its number of conflict blocks and asks to resolve them first. `--force` changes the project anyway.
|
|
21
|
+
- **Conflict blocks hold only the lines that differ** (#199; issue #196), as Git writes them. Lines your version and the Starter's share inside a conflict stay outside the markers, unless what separates two blocks is three lines or fewer, or lines without a letter or digit, such as closing braces and blank lines: those blocks stay one. In two apps built alongside the Starter, 44% and 39% of the lines inside markers were shared.
|
|
22
|
+
- **A dependency both sides raised merges to the higher version** (#193; issue #191). When the project and the Starter both raised a dependency's `^` or `~` range and both ranges keep the same major (the same minor for `~` and for `^0.x`), the merge keeps the higher range, where it left a conflict in `package.json` that pnpm can't read. Other changes to the same dependency, such as exact versions or a new major, still conflict. This stopped 4 of 30 test upgrades.
|
|
23
|
+
- **`add`, `remove`, `upgrade`, and `status` report what happened, and what to run next** (#194; issue #192):
|
|
24
|
+
- `status` has a Conflicts row with the files left with conflict markers, and its next step is to resolve them.
|
|
25
|
+
- A change ends by saying to resolve any conflicts first, then to review the diff and run `pnpm typecheck` and the tests: files that merged cleanly can still break the code.
|
|
26
|
+
- `--dry-run` says how many conflicts the change would leave, in how many files, and exits 2 with `--detailed-exit-code`, as the change itself would.
|
|
27
|
+
- When GitHub can't list the Starter releases an upgrade crosses, such as past its rate limit, the upgrade says so and links the releases page, instead of leaving the list out.
|
|
28
|
+
- A module new since the project's Starter release names that release, where it named the target.
|
|
29
|
+
- A README generated without its "Generated from" section, as React + Vite projects from older releases are, is no longer called replaced.
|
|
30
|
+
- A file whose merge changes nothing is left out of the change.
|
|
31
|
+
- **"Updated N files" counts every file the change writes** (#200; issue #197), the lockfile and env files included.
|
|
32
|
+
- **A failed scaffold keeps the files that were already in the directory** (#165 and issue #153, both by @ahmed-husssain). Rolling back a failed run in an existing directory emptied it; it now removes only the files the run wrote, and the folders that leaves empty.
|
|
33
|
+
- **A lockfile with CRLF line endings is customized like one with LF** (#189; issue #186, reported by @ahmed-husssain). With CRLF, the apps and packages a project leaves out stayed in its lockfile, and no optional peer was pruned. The Starter checks the lockfile out with LF, so this concerned copies made outside Git, such as one given to `--template-dir`.
|
|
34
|
+
- **`deploy` keeps the platform's direct database URL when the database doesn't change** (#188; issue #187, reported by @ahmed-husssain). A deploy with no env file, after one that supplied a pooled Neon URL, set `DATABASE_URL_DIRECT` to nothing, and migrations and the jobs worker fell back to the pooled URL.
|
|
35
|
+
- **An empty variable with an inline comment is empty** (#190, by @ahmed-husssain). `DATABASE_URL= # leave blank to use platform Postgres` read as the comment, so `deploy` took it for a hosted database, created none on the platform, and the app failed at startup. A `#` inside a value, as in `PASSWORD=secret#123`, stays part of it.
|
|
36
|
+
- `pnpm release` drafts name who wrote each pull request and list first-time contributors (#185).
|
|
37
|
+
|
|
38
|
+
Thanks to everyone who contributed to this release: @ahmed-husssain and @yassine-ahmed.
|
|
39
|
+
|
|
5
40
|
## 0.47.0
|
|
6
41
|
|
|
7
42
|
- **Starter v0.31.0** by default (#183). Its template schema is still 6. The [Starter's release notes](https://github.com/Clubedge/clubedge-starter/releases/tag/v0.31.0) cover:
|
|
@@ -10,16 +45,23 @@ All notable changes to `create-clubedge-app` are documented here.
|
|
|
10
45
|
- Supabase sessions that the Hono API verifies itself;
|
|
11
46
|
- fixes to the APIs' CORS and to SMTP sends that never answer;
|
|
12
47
|
- browser tests in Chromium, Firefox, and WebKit.
|
|
13
|
-
- **On Windows, an argument can no longer run a second command or expand a variable** (#176). The CLI ran commands through cmd.exe as one line, where `%VAR%` is expanded inside quotes, a line break ends the command, and a quote inside an argument can leave `&` and `|` outside the quotes. No argument from outside reached these commands, so this closes the gap before one does:
|
|
48
|
+
- **On Windows, an argument can no longer run a second command or expand a variable** (#176; issue #156, reported by @ahmed-husssain). The CLI ran commands through cmd.exe as one line, where `%VAR%` is expanded inside quotes, a line break ends the command, and a quote inside an argument can leave `&` and `|` outside the quotes. No argument from outside reached these commands, so this closes the gap before one does:
|
|
14
49
|
- A program (`.exe`, `.com`) now starts directly, with no shell, so `git`, `node`, `docker`, and `uv` no longer go through cmd.exe.
|
|
15
50
|
- A batch file (`.cmd`, `.bat`), such as the npm shims for `pnpm` and `npx`, still needs cmd.exe. Each argument is quoted the way Windows programs split their command line, then escaped for both of cmd.exe's readings. An argument with a line break is refused.
|
|
16
|
-
- **Windows device names are refused as project directories** (#169, #171): `CON`, `PRN`, `AUX`, `NUL`, `COM1` to `COM9`, and `LPT1` to `LPT9`, with or without an extension, which on Windows could create a folder that can't be deleted. The CLI checks the project's own name and each parent folder it would create, in the prompt and in the MCP server's `create_project` too. Folders that already exist are left alone.
|
|
17
|
-
- **The browser opens web addresses only** (#175). Commands that open a page, such as `deploy`, `neon`, and `try`, hand the system's opener only an `http://` or `https://` address with a host, and open nothing otherwise.
|
|
51
|
+
- **Windows device names are refused as project directories** (#169, by @biggdawg320, and #171; issue #155, reported by @ahmed-husssain): `CON`, `PRN`, `AUX`, `NUL`, `COM1` to `COM9`, and `LPT1` to `LPT9`, with or without an extension, which on Windows could create a folder that can't be deleted. The CLI checks the project's own name and each parent folder it would create, in the prompt and in the MCP server's `create_project` too. Folders that already exist are left alone.
|
|
52
|
+
- **The browser opens web addresses only** (#175, by @jdotc1; issue #157, reported by @ahmed-husssain). Commands that open a page, such as `deploy`, `neon`, and `try`, hand the system's opener only an `http://` or `https://` address with a host, and open nothing otherwise.
|
|
18
53
|
- **`create_project` reports progress over MCP** (#178) while it installs dependencies. A client that sends a progress token gets a notification when each install starts, every 5 seconds while it runs, and when it ends or fails, so a client that resets its timeout on progress keeps waiting. A client that sends none gets the same result as before.
|
|
19
54
|
- **Sponsors** (#181, #182): the README has a Sponsors section. [Neon](https://neon.com) supports Clubedge Starter through its Open Source Program.
|
|
20
55
|
- CI runs generated projects' browser checks in Chromium, by name (#180), since the Starter now has three browser projects.
|
|
21
56
|
- `@modelcontextprotocol/server` 2.4.0 or newer (#173), vitest 5.1.0 (#174), and astral-sh/setup-uv 10.3.3 in CI (#172).
|
|
22
57
|
|
|
58
|
+
### New contributors
|
|
59
|
+
|
|
60
|
+
- @biggdawg320 made their first contribution in #169.
|
|
61
|
+
- @jdotc1 made their first contribution in #175.
|
|
62
|
+
|
|
63
|
+
Thanks to everyone who contributed to this release: @ahmed-husssain, @biggdawg320, @jdotc1, and @yassine-ahmed.
|
|
64
|
+
|
|
23
65
|
## 0.46.1
|
|
24
66
|
|
|
25
67
|
- **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:
|
package/README.md
CHANGED
|
@@ -224,7 +224,8 @@ Options:
|
|
|
224
224
|
(upgrade: the Starter to upgrade to)
|
|
225
225
|
--template-dir <path> Scaffold from a local Starter checkout (for Starter development)
|
|
226
226
|
--dry-run Show what would be created or changed without writing anything
|
|
227
|
-
--force add/remove: change a project with uncommitted changes
|
|
227
|
+
--force add/remove/upgrade: change a project with uncommitted changes,
|
|
228
|
+
unresolved conflicts, or without Git
|
|
228
229
|
-y, --yes Accept defaults and never prompt (default directory: my-app)
|
|
229
230
|
--no-install Skip dependency installation
|
|
230
231
|
--no-git Skip Git repository initialization
|
|
@@ -245,7 +246,7 @@ pnpm dlx @clubedge/create-clubedge-app my-app --yes --no-git --framework tanstac
|
|
|
245
246
|
pnpm dlx @clubedge/create-clubedge-app my-app --ref main
|
|
246
247
|
|
|
247
248
|
# Reproduce a specific Starter release or commit
|
|
248
|
-
pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.31.
|
|
249
|
+
pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.31.2
|
|
249
250
|
pnpm dlx @clubedge/create-clubedge-app my-app --ref <commit-sha>
|
|
250
251
|
|
|
251
252
|
# Test local Starter changes before releasing them
|
|
@@ -266,7 +267,7 @@ pnpm clubedge add mobile expo # the native app; ad
|
|
|
266
267
|
pnpm clubedge remove mobile
|
|
267
268
|
pnpm dlx @clubedge/create-clubedge-app add infra local --dry-run
|
|
268
269
|
pnpm dlx @clubedge/create-clubedge-app@latest upgrade # to the newest CLI's Starter release
|
|
269
|
-
pnpm dlx @clubedge/create-clubedge-app upgrade --ref v0.31.
|
|
270
|
+
pnpm dlx @clubedge/create-clubedge-app upgrade --ref v0.31.2 # or a specific tag, branch, or commit
|
|
270
271
|
```
|
|
271
272
|
|
|
272
273
|
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 +275,8 @@ The CLI renders the project twice, from the Starter commit and modules recorded
|
|
|
274
275
|
- Files you never changed are updated, added, or deleted.
|
|
275
276
|
- Files you changed keep your edits. Where the module change touches the same lines, the file gets conflict markers (`<<<<<<< yours` ... `>>>>>>> clubedge`) for you to resolve. The Starter's whole new version of each such file is saved in `.clubedge/incoming/` to compare with yours (`git diff --no-index <file> .clubedge/incoming/<file>`); Git ignores that folder, so delete it when you are done.
|
|
276
277
|
- `package.json` files merge by key, not by line: a script, dependency, or override you added next to the Starter's keeps both, and markers wrap only a key you both changed to different values, such as the same dependency's range. The file keeps your key order, indentation, and line endings.
|
|
278
|
+
- A dependency's range that only one side raised within itself, as `pnpm up` does, gives way to the other side's change when that removes the dependency or asks for at least the raised version: the Starter's move to a new major, or its removal of a dependency it moved to another package, over your `pnpm up -r`. A range both sides raised within itself takes the higher one.
|
|
279
|
+
- A `pnpm patch` made on a version the change moves away from would stop `pnpm install` (`ERR_PNPM_UNUSED_PATCH`). The CLI names each such patch and the range that leaves it out, and skips the install until it is fixed: keyed by the package alone, pnpm applies a patch to whichever version it installs.
|
|
277
280
|
- Your own files are never touched.
|
|
278
281
|
- Each app's local env file (`apps/web/.env.local`, and `apps/api/.env.local`, `apps/shell/.env.local`, or `apps/mobile/.env.local` when the project has them) keeps your values. Values still at their generated defaults follow the new selection, and new variables are added.
|
|
279
282
|
- `pnpm-lock.yaml` is replaced if you never changed your dependencies; otherwise `pnpm install` reconciles it.
|
|
@@ -289,11 +292,11 @@ Since CLI 0.46.0, `add` and `remove` also take the apps that run on phones rathe
|
|
|
289
292
|
- 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`.
|
|
290
293
|
- 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.
|
|
291
294
|
|
|
292
|
-
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
|
|
295
|
+
The project must be a Git repository without uncommitted changes, so you can review the result with `git diff` and undo it, and without conflict markers left by an earlier change, which the new change would merge into: the CLI names the files to resolve first. Pass `--force` to skip these checks. Add `--no-install` to skip `pnpm install`. Afterward, run `pnpm typecheck` and your tests, since files that merge without conflicts can still break the code, and `pnpm db:migrate` and `pnpm check:services` if the change added tables or services.
|
|
293
296
|
|
|
294
297
|
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.
|
|
295
298
|
|
|
296
|
-
A change that leaves conflicts exits with 0, like a clean one. For scripts that need to tell them apart, add `--detailed-exit-code`: the CLI then exits with 2 when the change was written with conflicts to resolve, 0 when it was clean or there was nothing to change, and 1 when it failed.
|
|
299
|
+
A change that leaves conflicts exits with 0, like a clean one. For scripts that need to tell them apart, add `--detailed-exit-code`: the CLI then exits with 2 when the change was written with conflicts to resolve, 0 when it was clean or there was nothing to change, and 1 when it failed. With `--dry-run`, it counts the conflicts the change would leave, and exits with 2 when there are any, so a script can check before changing anything.
|
|
297
300
|
|
|
298
301
|
## A hosted database with Neon
|
|
299
302
|
|
|
@@ -323,11 +326,11 @@ Claiming keeps the data but resets the password: copy the pooled and direct conn
|
|
|
323
326
|
Two read-only commands help you keep a project healthy:
|
|
324
327
|
|
|
325
328
|
```sh
|
|
326
|
-
pnpm clubedge status # Starter release, modules, Git state, and whether a newer Starter exists
|
|
329
|
+
pnpm clubedge status # Starter release, modules, Git state, conflicts left, and whether a newer Starter exists
|
|
327
330
|
pnpm clubedge doctor # Node.js, pnpm, dependencies, lockfile, env file, and pnpm check:services
|
|
328
331
|
```
|
|
329
332
|
|
|
330
|
-
`status` ends with the command to run next, such as `pnpm clubedge upgrade` when a newer Starter release is available; it asks npm for the newest CLI and still works offline. `doctor` checks that Node.js and pnpm meet the project's versions, that dependencies are installed, that `pnpm-lock.yaml` matches every `package.json`, and that each app's local env file has every variable in its example (`apps/web/.env.local` and `.env.example`, and `apps/api/.env.local` and `apps/api/.env.example` with a separate API), then runs `pnpm check:services` (skip it with `--no-services`). It exits with an error when a check fails, so it also works in CI.
|
|
333
|
+
`status` ends with the command to run next, such as `pnpm clubedge upgrade` when a newer Starter release is available; it asks npm for the newest CLI and still works offline. It lists the files an earlier change left with conflict markers, and a `.clubedge/incoming/` left behind, and then says to resolve the conflicts first. `doctor` checks that Node.js and pnpm meet the project's versions, that dependencies are installed, that `pnpm-lock.yaml` matches every `package.json`, and that each app's local env file has every variable in its example (`apps/web/.env.local` and `.env.example`, and `apps/api/.env.local` and `apps/api/.env.example` with a separate API), then runs `pnpm check:services` (skip it with `--no-services`). It exits with an error when a check fails, so it also works in CI.
|
|
331
334
|
|
|
332
335
|
## Try it on a Railway VM (experimental)
|
|
333
336
|
|
|
@@ -415,27 +418,27 @@ Projects created with `--infra neon` also have `.github/workflows/preview.yml`,
|
|
|
415
418
|
Each CLI release is tested against one specific Starter release:
|
|
416
419
|
|
|
417
420
|
```text
|
|
418
|
-
create-clubedge-app v0.
|
|
421
|
+
create-clubedge-app v0.48.1
|
|
419
422
|
|
|
|
420
423
|
v
|
|
421
|
-
Clubedge Starter v0.31.
|
|
422
|
-
commit
|
|
424
|
+
Clubedge Starter v0.31.2
|
|
425
|
+
commit 2635acabc29de2eb3656bc9e6741eac8bd72cd38
|
|
423
426
|
```
|
|
424
427
|
|
|
425
428
|
The current relationship is:
|
|
426
429
|
|
|
427
430
|
| CLI release | Default Starter release | Meaning |
|
|
428
431
|
| --- | --- | --- |
|
|
429
|
-
| `create-clubedge-app@0.
|
|
432
|
+
| `create-clubedge-app@0.48.1` | `clubedge-starter@v0.31.2` | The CLI's required, tested default |
|
|
430
433
|
|
|
431
434
|
This means the same CLI version always scaffolds the same Starter revision by default. The CLI does not silently follow Starter `main`.
|
|
432
435
|
|
|
433
|
-
The `v0.31.
|
|
434
|
-
`
|
|
436
|
+
The `v0.31.2` tag is expected to resolve to commit
|
|
437
|
+
`2635acabc29de2eb3656bc9e6741eac8bd72cd38`. The CLI verifies this before
|
|
435
438
|
scaffolding with the default reference and fails rather than silently using a
|
|
436
439
|
moved tag.
|
|
437
440
|
|
|
438
|
-
When the Starter changes, its maintainers first publish a new versioned release, such as `v0.31.
|
|
441
|
+
When the Starter changes, its maintainers first publish a new versioned release, such as `v0.31.2`. 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.
|
|
439
442
|
|
|
440
443
|
Every generated project records its CLI version, Starter repository, Starter ref, Starter commit, framework, and modules in `package.json` and `README.md`.
|
|
441
444
|
|
|
@@ -490,12 +493,12 @@ CI packs the CLI with `npm pack`, scaffolds clean projects from the packed tarba
|
|
|
490
493
|
1. Make and validate changes in `clubedge-starter`.
|
|
491
494
|
2. Run lint, typecheck, unit tests, browser tests, and production build.
|
|
492
495
|
3. Update the Starter package versions.
|
|
493
|
-
4. Commit `main`, create a release tag such as `v0.31.
|
|
496
|
+
4. Commit `main`, create a release tag such as `v0.31.2`, and publish the Starter release.
|
|
494
497
|
|
|
495
498
|
### CLI maintainers
|
|
496
499
|
|
|
497
500
|
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.
|
|
498
|
-
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.
|
|
501
|
+
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. The draft names who wrote each pull request that a maintainer or a bot didn't, lists the first-time contributors, and thanks everyone by name. Edit the draft in that pull request, keeping those credits and adding who reported the issues it closes: it becomes the GitHub Release page.
|
|
499
502
|
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.
|
|
500
503
|
|
|
501
504
|
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.
|
|
@@ -548,6 +551,7 @@ Each phase ends with a Starter release and a CLI release pinned to it. The full
|
|
|
548
551
|
| 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 |
|
|
549
552
|
| 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` |
|
|
550
553
|
| 0.47.0 | v0.31.0 | Starter v0.31.0: deploying to Cloudflare (`--deploy cloudflare`), downloads by byte range, and browser tests in three engines; on Windows, arguments that can't start a second command and device names refused as project directories; install progress over MCP |
|
|
554
|
+
| 0.48.0 | v0.31.1 | Starter v0.31.1: Expo SDK 58's latest patch versions; `add`, `remove`, and `upgrade` refuse to merge into unresolved conflicts, conflict blocks hold only the lines that differ, a dependency both sides raised merges to the higher version, and reports say what to run next |
|
|
551
555
|
|
|
552
556
|
## Sponsors
|
|
553
557
|
|
package/dist/cli/ui.js
CHANGED
|
@@ -131,9 +131,11 @@ ${selectionHelp(manifest)}
|
|
|
131
131
|
(upgrade: the Starter to upgrade to)
|
|
132
132
|
--template-dir <path> Scaffold from a local Starter checkout (for Starter development)
|
|
133
133
|
--dry-run Show what would be created or changed without writing anything
|
|
134
|
-
--force add/remove: change a project with uncommitted changes
|
|
134
|
+
--force add/remove/upgrade: change a project with uncommitted changes,
|
|
135
|
+
unresolved conflicts, or without Git
|
|
135
136
|
--detailed-exit-code add/remove/upgrade: exit with 2 when the change leaves conflicts to
|
|
136
|
-
resolve (0: changed cleanly or nothing to
|
|
137
|
+
resolve, or would with --dry-run (0: changed cleanly or nothing to
|
|
138
|
+
change, 1: failed)
|
|
137
139
|
--no-services doctor: skip pnpm check:services
|
|
138
140
|
--no-open try, deploy (Vercel, Netlify, Render): do not open the URL in the browser
|
|
139
141
|
--claim try: open the page that claims the VM, to share the preview
|
package/dist/core/customize.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Pure text transforms applied to template files. Keeping them free of I/O makes each one easy
|
|
2
2
|
// to test and lets --dry-run describe them without touching the disk.
|
|
3
|
-
import { pruneOptionalPeers } from "./lockfile-peers.js";
|
|
3
|
+
import { keepLineEndings, pruneOptionalPeers } from "./lockfile-peers.js";
|
|
4
4
|
import { FRAMEWORK_SLOT } from "./manifest-fields.js";
|
|
5
5
|
const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
6
6
|
/**
|
|
@@ -124,9 +124,12 @@ export function customizeAppManifest(source, appPackage, version) {
|
|
|
124
124
|
* works. Importers are keyed by workspace path at two-space indentation; package entries that
|
|
125
125
|
* are no longer used are harmless, and pnpm prunes them on the next lockfile update. Optional
|
|
126
126
|
* peer links to packages that only left-out importers installed are removed, so those packages
|
|
127
|
-
* are not installed either.
|
|
127
|
+
* are not installed either. The lockfile keeps its line endings.
|
|
128
128
|
*/
|
|
129
|
-
export function customizeLockfile(source,
|
|
129
|
+
export function customizeLockfile(source, changes) {
|
|
130
|
+
return keepLineEndings(source, (text) => customizeImporters(text, changes));
|
|
131
|
+
}
|
|
132
|
+
function customizeImporters(source, { remove, rename = [], removeDependencies = [] }) {
|
|
130
133
|
const output = [];
|
|
131
134
|
let inImporters = false;
|
|
132
135
|
let skippingImporter = false;
|
package/dist/core/deploy.js
CHANGED
|
@@ -98,7 +98,7 @@ export function parseEnvFile(text) {
|
|
|
98
98
|
const raw = match[2].trim();
|
|
99
99
|
// A quoted value keeps everything inside the quotes, and may still have a comment after them.
|
|
100
100
|
const quoted = /^(["'])(.*?)\1\s*(?:#.*)?$/.exec(raw);
|
|
101
|
-
values[match[1]] = quoted ? quoted[2] : raw.replace(
|
|
101
|
+
values[match[1]] = quoted ? quoted[2] : raw.replace(/(?:^|\s+)#.*$/, "");
|
|
102
102
|
}
|
|
103
103
|
return values;
|
|
104
104
|
}
|
|
@@ -133,10 +133,16 @@ export function planVariables(input) {
|
|
|
133
133
|
const direct = directDatabaseUrl(external.DATABASE_URL, envFile.DATABASE_URL_DIRECT);
|
|
134
134
|
if (direct)
|
|
135
135
|
set.DATABASE_URL_DIRECT = direct;
|
|
136
|
-
// A direct URL left from an earlier database would send migrations there
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
// A direct URL left from an earlier database would send migrations there. When this deploy
|
|
137
|
+
// decides the database (it sets DATABASE_URL, or the platform attaches its own), or no
|
|
138
|
+
// DATABASE_URL is left for the direct URL to belong to, point it at the database the app now
|
|
139
|
+
// uses, or blank it (blank counts as unset). A deploy that keeps the database the platform
|
|
140
|
+
// already has, such as one with no env file, leaves its direct URL as it is.
|
|
141
|
+
else if (existing.DATABASE_URL_DIRECT) {
|
|
142
|
+
const decided = set.DATABASE_URL !== undefined || attached.includes("DATABASE_URL");
|
|
143
|
+
if (decided || !existing.DATABASE_URL)
|
|
144
|
+
set.DATABASE_URL_DIRECT = set.DATABASE_URL ?? "";
|
|
145
|
+
}
|
|
140
146
|
// Optional: without a direct URL, migrations and the jobs worker use DATABASE_URL, and the
|
|
141
147
|
// client-address settings have safe defaults.
|
|
142
148
|
const optional = ["DATABASE_URL_DIRECT", ...clientAddressKeys];
|
package/dist/core/execute.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { copyTemplateFiles, emptyDirectory, pathExists } from "../utils/fs.js";
|
|
1
|
+
import { mkdir, readdir, readFile, rm, rmdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join, parse, relative, resolve } from "node:path";
|
|
3
|
+
import { copyTemplateFiles, pathExists } from "../utils/fs.js";
|
|
5
4
|
import { isReservedDeviceName } from "../utils/names.js";
|
|
6
5
|
export class TargetError extends Error {
|
|
7
6
|
name = "TargetError";
|
|
@@ -37,9 +36,26 @@ export async function assertTargetIsUsable(directory) {
|
|
|
37
36
|
throw error;
|
|
38
37
|
}
|
|
39
38
|
}
|
|
39
|
+
/** Removes files created during scaffolding, then removes any empty directories left behind. */
|
|
40
|
+
export async function cleanupWrittenFiles(target, files, folders = []) {
|
|
41
|
+
const dirs = new Set();
|
|
42
|
+
const targetPath = resolve(target);
|
|
43
|
+
for (const file of files)
|
|
44
|
+
await rm(file, { force: true }).catch(() => { });
|
|
45
|
+
const paths = [...files.map((file) => dirname(file)), ...folders.map((folder) => folder.replace(/^\\\\\?\\/, ""))];
|
|
46
|
+
for (const p of paths) {
|
|
47
|
+
for (let cur = resolve(p); cur !== targetPath && cur !== dirname(cur) && !relative(targetPath, cur).startsWith(".."); cur = dirname(cur)) {
|
|
48
|
+
dirs.add(cur);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
for (const dir of [...dirs].sort((a, b) => b.length - a.length)) {
|
|
52
|
+
await rmdir(dir).catch(() => { });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
40
55
|
/**
|
|
41
56
|
* Writes the project described by the plan. If any step fails, everything written so far is
|
|
42
|
-
* removed: a directory the CLI created is deleted, and a pre-existing
|
|
57
|
+
* removed: a directory the CLI created is deleted, and for a pre-existing directory only files
|
|
58
|
+
* created during the run are removed, preserving any pre-existing contents.
|
|
43
59
|
*/
|
|
44
60
|
export async function executePlan(plan, source) {
|
|
45
61
|
const target = plan.targetDirectory;
|
|
@@ -56,21 +72,21 @@ export async function executePlan(plan, source) {
|
|
|
56
72
|
if (!(error && typeof error === "object" && "code" in error && error.code === "EEXIST"))
|
|
57
73
|
throw error;
|
|
58
74
|
}
|
|
75
|
+
const written = [];
|
|
76
|
+
const folders = [];
|
|
59
77
|
try {
|
|
60
|
-
await copyTemplateFiles(source.root, target, plan.files);
|
|
78
|
+
await copyTemplateFiles(source.root, target, plan.files, written, folders);
|
|
61
79
|
for (const edit of plan.edits) {
|
|
62
80
|
const path = join(target, edit.path);
|
|
63
81
|
await writeFile(path, edit.apply(await readFile(path, "utf8")));
|
|
64
82
|
}
|
|
65
|
-
|
|
66
|
-
await copyFile(join(target, envFile.from), join(target, envFile.to), constants.COPYFILE_EXCL);
|
|
67
|
-
}
|
|
83
|
+
await copyTemplateFiles(target, target, plan.envFiles, written, folders);
|
|
68
84
|
}
|
|
69
85
|
catch (error) {
|
|
70
86
|
if (created)
|
|
71
87
|
await rm(target, { recursive: true, force: true });
|
|
72
88
|
else
|
|
73
|
-
await
|
|
89
|
+
await cleanupWrittenFiles(target, written, folders);
|
|
74
90
|
throw error;
|
|
75
91
|
}
|
|
76
92
|
}
|
package/dist/core/health.js
CHANGED
|
@@ -52,8 +52,14 @@ export function updateState(projectRef, latest) {
|
|
|
52
52
|
export function latestCliCommand(cli) {
|
|
53
53
|
return cli === "pnpm clubedge" ? cli : "pnpm dlx @clubedge/create-clubedge-app@latest";
|
|
54
54
|
}
|
|
55
|
-
/**
|
|
56
|
-
|
|
55
|
+
/**
|
|
56
|
+
* What to do next, given the working tree, whether a newer Starter exists, and the files an
|
|
57
|
+
* earlier change left with conflict markers, which come first.
|
|
58
|
+
*/
|
|
59
|
+
export function nextStep(git, update, cli, conflicts = []) {
|
|
60
|
+
if (conflicts.length) {
|
|
61
|
+
return "Resolve the conflicts first: keep your lines, the Starter's, or a mix in each block, then delete the markers and .clubedge/incoming.";
|
|
62
|
+
}
|
|
57
63
|
if (update.kind === "available") {
|
|
58
64
|
const upgrade = `${latestCliCommand(cli)} upgrade`;
|
|
59
65
|
return git.kind === "dirty"
|
|
@@ -210,6 +216,24 @@ export function checkDatabaseUrls(input) {
|
|
|
210
216
|
}
|
|
211
217
|
return { name, status: "ok", detail: "Neon, pooled for the app and direct for migrations and the jobs worker" };
|
|
212
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* The conflicts line of `status`: the files an earlier change left with conflict markers, and
|
|
221
|
+
* the Starter's versions it left to compare with in .clubedge/incoming.
|
|
222
|
+
*/
|
|
223
|
+
export function describeConflicts(conflicts, incomingLeft) {
|
|
224
|
+
if (!conflicts.length) {
|
|
225
|
+
return incomingLeft
|
|
226
|
+
? "none, but .clubedge/incoming still holds the Starter's versions from the last change: delete it when you are done comparing"
|
|
227
|
+
: "none";
|
|
228
|
+
}
|
|
229
|
+
const total = conflicts.reduce((sum, c) => sum + c.blocks, 0);
|
|
230
|
+
const files = conflicts
|
|
231
|
+
.slice(0, 3)
|
|
232
|
+
.map((c) => `${c.path} (${c.blocks} ${c.blocks === 1 ? "block" : "blocks"})`)
|
|
233
|
+
.join(", ");
|
|
234
|
+
return (`${total} ${total === 1 ? "conflict" : "conflicts"} in ${conflicts.length} ${conflicts.length === 1 ? "file" : "files"}: ` +
|
|
235
|
+
`${files}${conflicts.length > 3 ? ", ..." : ""}${incomingLeft ? "; the Starter's versions are in .clubedge/incoming" : ""}`);
|
|
236
|
+
}
|
|
213
237
|
export function checkConflicts(conflicts) {
|
|
214
238
|
const name = "Conflicts";
|
|
215
239
|
if (!conflicts.length)
|
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
// workspace, and records the link in lockfile keys: drizzle-orm@0.45.4(@electric-sql/pglite@0.5.8).
|
|
3
3
|
// When a left-out workspace package was the only reason the peer was installed, the link would
|
|
4
4
|
// keep installing it, so these helpers remove such links from the lockfile.
|
|
5
|
+
/**
|
|
6
|
+
* Changes a lockfile's text with LF line endings and gives it back in the original's. The Starter's
|
|
7
|
+
* .gitattributes checks pnpm-lock.yaml out with LF everywhere, but a copy made outside Git, such as
|
|
8
|
+
* one given to --template-dir, can have CRLF, and every line would then end in a stray \r.
|
|
9
|
+
*/
|
|
10
|
+
export function keepLineEndings(source, change) {
|
|
11
|
+
if (!source.includes("\r\n"))
|
|
12
|
+
return change(source);
|
|
13
|
+
return change(source.replace(/\r\n/g, "\n")).replace(/\n/g, "\r\n");
|
|
14
|
+
}
|
|
5
15
|
function unquote(text) {
|
|
6
16
|
return text.replace(/^(['"])(.*)\1$/, "$2");
|
|
7
17
|
}
|
|
@@ -18,7 +28,7 @@ function parse(source) {
|
|
|
18
28
|
let key = "";
|
|
19
29
|
let group = "";
|
|
20
30
|
let entry = "";
|
|
21
|
-
for (const line of source.split(
|
|
31
|
+
for (const line of source.split(/\r?\n/)) {
|
|
22
32
|
if (/^\S/.test(line)) {
|
|
23
33
|
section = line.replace(/:.*$/, "");
|
|
24
34
|
continue;
|
|
@@ -117,9 +127,12 @@ function removeSuffixSegments(text, peer) {
|
|
|
117
127
|
/**
|
|
118
128
|
* Drops optional-peer links to packages nothing else installs (see findUnneededOptionalPeers),
|
|
119
129
|
* so `pnpm install --frozen-lockfile` no longer installs them. Snapshot entries that become
|
|
120
|
-
* identical are merged.
|
|
130
|
+
* identical are merged. The lockfile keeps its line endings.
|
|
121
131
|
*/
|
|
122
132
|
export function pruneOptionalPeers(source) {
|
|
133
|
+
return keepLineEndings(source, pruneLines);
|
|
134
|
+
}
|
|
135
|
+
function pruneLines(source) {
|
|
123
136
|
const peers = findUnneededOptionalPeers(source);
|
|
124
137
|
if (!peers.length)
|
|
125
138
|
return source;
|
package/dist/core/merge.js
CHANGED
|
@@ -50,13 +50,51 @@ function withMarkers(regions) {
|
|
|
50
50
|
}
|
|
51
51
|
return { text: lines.join("\n"), conflict };
|
|
52
52
|
}
|
|
53
|
+
const hasLetterOrDigit = (lines) => lines.some((line) => /[\p{L}\p{N}]/u.test(line));
|
|
54
|
+
/**
|
|
55
|
+
* The regions with each conflict split where your lines and the Starter's are the same, so a
|
|
56
|
+
* block holds only what differs, as Git writes conflicts. Apps built alongside the Starter share
|
|
57
|
+
* many lines in their conflicts: 44% of those in Chess's and 39% in Chat App's.
|
|
58
|
+
*/
|
|
59
|
+
function refine(regions) {
|
|
60
|
+
return regions.flatMap((region) => "ok" in region || !region.yours.length || !region.theirs.length
|
|
61
|
+
? [region]
|
|
62
|
+
: diffComm(region.yours, region.theirs).map((part) => part.common ? { ok: part.common } : { yours: part.buffer1, theirs: part.buffer2 }));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Joins conflicts with few shared lines between them back into one block, as Git does: three
|
|
66
|
+
* lines or fewer, or lines without a letter or digit, such as closing braces and blank lines,
|
|
67
|
+
* read better inside one block than as the edges of two.
|
|
68
|
+
*/
|
|
69
|
+
function joinClose(regions) {
|
|
70
|
+
const joined = [];
|
|
71
|
+
for (const region of regions) {
|
|
72
|
+
const last = joined.at(-1);
|
|
73
|
+
// Consecutive regions of one kind are one region.
|
|
74
|
+
if (last && "ok" in last && "ok" in region) {
|
|
75
|
+
joined[joined.length - 1] = { ok: [...last.ok, ...region.ok] };
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (last && !("ok" in last) && !("ok" in region)) {
|
|
79
|
+
joined[joined.length - 1] = { yours: [...last.yours, ...region.yours], theirs: [...last.theirs, ...region.theirs] };
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const before = joined.at(-2);
|
|
83
|
+
if (!("ok" in region) && last && "ok" in last && before && !("ok" in before) && (last.ok.length <= 3 || !hasLetterOrDigit(last.ok))) {
|
|
84
|
+
joined.splice(-2, 2, { yours: [...before.yours, ...last.ok, ...region.yours], theirs: [...before.theirs, ...last.ok, ...region.theirs] });
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
joined.push(region);
|
|
88
|
+
}
|
|
89
|
+
return joined;
|
|
90
|
+
}
|
|
53
91
|
/** Merges base -> target into current. Resolves null for binary files, which cannot be merged. */
|
|
54
92
|
export function mergeText(current, base, target) {
|
|
55
93
|
if (isBinary(current) || isBinary(base) || isBinary(target))
|
|
56
94
|
return null;
|
|
57
95
|
const split = (content) => normalize(content).split("\n");
|
|
58
96
|
const regions = diff3Merge(split(current), split(base), split(target), { excludeFalseConflicts: true });
|
|
59
|
-
return withMarkers(regions.flatMap((region) => region.ok ? [{ ok: region.ok }] : region.conflict ? [{ yours: region.conflict.a, theirs: region.conflict.b }] : []));
|
|
97
|
+
return withMarkers(joinClose(refine(regions.flatMap((region) => region.ok ? [{ ok: region.ok }] : region.conflict ? [{ yours: region.conflict.a, theirs: region.conflict.b }] : []))));
|
|
60
98
|
}
|
|
61
99
|
/**
|
|
62
100
|
* Merges a package.json by key (see package-json-merge.ts), so edits to different keys never
|
|
@@ -69,6 +107,7 @@ export function mergePackageJsonText(current, base, target) {
|
|
|
69
107
|
return null;
|
|
70
108
|
return withMarkers(diffComm(merged.yours.split("\n"), merged.theirs.split("\n")).map((region) => region.common !== undefined ? { ok: region.common } : { yours: region.buffer1, theirs: region.buffer2 }));
|
|
71
109
|
}
|
|
110
|
+
/** The merge of a file both sides changed; null when the project already has the Starter's change. */
|
|
72
111
|
function merged(path, current, base, target, binaryReason) {
|
|
73
112
|
const result = (/(^|\/)package\.json$/.test(path) ? mergePackageJsonText(current, base, target) : null) ?? mergeText(current, base, target);
|
|
74
113
|
if (!result)
|
|
@@ -76,7 +115,7 @@ function merged(path, current, base, target, binaryReason) {
|
|
|
76
115
|
const content = withLineEndings(result.text, current);
|
|
77
116
|
if (result.conflict)
|
|
78
117
|
return { path, kind: "conflict", content, incoming: withLineEndings(normalize(target), current) };
|
|
79
|
-
return { path, kind: "merge", content };
|
|
118
|
+
return sameContent(content, current) ? null : { path, kind: "merge", content };
|
|
80
119
|
}
|
|
81
120
|
/**
|
|
82
121
|
* Decides what happens to every file the base or the target contains. Files only the project
|
|
@@ -95,7 +134,9 @@ export function planMerge(base, target, current) {
|
|
|
95
134
|
if (!mine)
|
|
96
135
|
actions.push({ path, kind: "create", content: after });
|
|
97
136
|
else if (!sameContent(mine, after)) {
|
|
98
|
-
|
|
137
|
+
const action = merged(path, mine, Buffer.alloc(0), after, "Your file differs from the one the new selection adds.");
|
|
138
|
+
if (action)
|
|
139
|
+
actions.push(action);
|
|
99
140
|
}
|
|
100
141
|
continue;
|
|
101
142
|
}
|
|
@@ -122,7 +163,9 @@ export function planMerge(base, target, current) {
|
|
|
122
163
|
actions.push({ path, kind: "update", content });
|
|
123
164
|
}
|
|
124
165
|
else if (!sameContent(mine, after)) {
|
|
125
|
-
|
|
166
|
+
const action = merged(path, mine, before, after, "You changed this file, and the new selection changes it too.");
|
|
167
|
+
if (action)
|
|
168
|
+
actions.push(action);
|
|
126
169
|
}
|
|
127
170
|
}
|
|
128
171
|
}
|