@clubedge/create-clubedge-app 0.42.1 → 0.43.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 CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to `create-clubedge-app` are documented here.
4
4
 
5
+ ## 0.43.1
6
+
7
+ - **`try` shows Railway's reason when Railway refuses a VM** (#144):
8
+ - Before, it said Railway may have changed its free VM and told you to update the CLI, which no update fixes.
9
+ - It now prints Railway's own explanation, then Railway's limits, the usual cause: 3 free VMs per network per day, and no anonymous VMs while Railway is busy.
10
+ - Still on Starter `v0.27.1`.
11
+
12
+ ## 0.43.0
13
+
14
+ - **Upgrades keep each app's own version** (#134, by @ahmed-husssain):
15
+ - An app whose version the project changed in `apps/*/package.json` keeps it, without a conflict on that line.
16
+ - An app whose version the project never touched takes the new Starter's.
17
+ - This completes 0.41.0's version ownership, which covered the root `package.json`.
18
+ - **`add`, `remove`, and `upgrade` that change nothing exit before downloading the Starter** (#133, by @ahmed-husssain; issue #132):
19
+ - The cases: `upgrade` on the pinned Starter's commit or with `--ref` set to the recorded commit, `add` with options the project already uses, and `remove` of modules recorded as `none`.
20
+ - These commands now work offline. They no longer refuse a project with uncommitted changes, since they change nothing.
21
+ - `upgrade --ref <branch>` still resolves the branch, which may have moved. `remove` with an unknown module still fails with `Unknown module`.
22
+ - Still on Starter `v0.27.1`.
23
+
5
24
  ## 0.42.1
6
25
 
7
26
  - Default to Starter `v0.27.1` (#140): new projects carry the Clubedge Starter brand, and their mobile app shows it.
package/README.md CHANGED
@@ -381,7 +381,7 @@ Projects created with `--infra neon` also have `.github/workflows/preview.yml`,
381
381
  Each CLI release is tested against one specific Starter release:
382
382
 
383
383
  ```text
384
- create-clubedge-app v0.42.1
384
+ create-clubedge-app v0.43.1
385
385
  |
386
386
  v
387
387
  Clubedge Starter v0.27.1
@@ -392,7 +392,7 @@ The current relationship is:
392
392
 
393
393
  | CLI release | Default Starter release | Meaning |
394
394
  | --- | --- | --- |
395
- | `create-clubedge-app@0.42.1` | `clubedge-starter@v0.27.1` | The CLI's required, tested default |
395
+ | `create-clubedge-app@0.43.1` | `clubedge-starter@v0.27.1` | The CLI's required, tested default |
396
396
 
397
397
  This means the same CLI version always scaffolds the same Starter revision by default. The CLI does not silently follow Starter `main`.
398
398
 
@@ -509,6 +509,7 @@ Each phase ends with a Starter release and a CLI release pinned to it. The full
509
509
  | 0.40.0 | v0.26.0 | Starter v0.26.0: every module on React + Vite with FastAPI (pg-boss excepted) |
510
510
  | 0.41.0 | v0.26.0 | A conflicted package.json no longer breaks later commands (`--detailed-exit-code`); doctor finds conflict markers; projects own their version and README |
511
511
  | 0.42.0 | v0.27.0 | Starter v0.27.0: mobile apps with Capacitor (`--shell capacitor`, template schema 5); client addresses per platform; Docker Compose behind Caddy, with images on GHCR |
512
+ | 0.43.0 | v0.27.1 | Upgrades keep each app's own version; add, remove, and upgrade that change nothing skip the Starter download |
512
513
 
513
514
  ## Community
514
515
 
@@ -88,10 +88,13 @@ export function customizeLegacySource(source, { displayName }) {
88
88
  .replaceAll(">Clubedge<", `>${displayName}<`)
89
89
  .replaceAll("Starter workspace", "Application workspace");
90
90
  }
91
- /** Gives a selected stack's app the package name generated projects use. */
92
- export function customizeAppManifest(source, appPackage) {
91
+ /** Gives a selected stack's app the package name generated projects use and preserves its version. */
92
+ export function customizeAppManifest(source, appPackage, version) {
93
93
  const manifest = JSON.parse(source);
94
- manifest.name = appPackage;
94
+ if (appPackage)
95
+ manifest.name = appPackage;
96
+ if (version !== undefined)
97
+ manifest.version = version;
95
98
  return json(manifest);
96
99
  }
97
100
  /**
@@ -31,11 +31,12 @@ export function hostKeyFingerprint(key) {
31
31
  }
32
32
  /**
33
33
  * Finds Railway's JSON status line in a command's stderr. Returns null when there is none, and
34
- * throws when there is one without the fields this CLI needs.
34
+ * throws when there is one without the fields this CLI needs, with Railway's reason when it
35
+ * refused the VM.
35
36
  */
36
37
  export function parseRailwayStatus(stderr) {
37
- for (const line of stderr.split(/\r?\n/)) {
38
- const trimmed = line.trim();
38
+ const lines = stderr.split(/\r?\n/).map((line) => line.trim());
39
+ for (const trimmed of lines) {
39
40
  if (!trimmed.startsWith("{"))
40
41
  continue;
41
42
  let value;
@@ -47,6 +48,8 @@ export function parseRailwayStatus(stderr) {
47
48
  }
48
49
  if (typeof value.status !== "string")
49
50
  continue;
51
+ if (value.status === "error")
52
+ throw new Error(railwayRefusal(value, lines));
50
53
  const previewUrl = value.preview_url;
51
54
  const claimUrl = value.human_claim_url;
52
55
  const expiresAt = new Date(String(value.build_expires_at));
@@ -57,6 +60,19 @@ export function parseRailwayStatus(stderr) {
57
60
  }
58
61
  return null;
59
62
  }
63
+ /**
64
+ * Why Railway refused a VM, in its own words: a message in its status line, else the text it
65
+ * wrote around that line, else the line itself. Its limits come after, since they are the usual
66
+ * cause.
67
+ */
68
+ function railwayRefusal(value, lines) {
69
+ const said = ["message", "error", "reason", "description", "detail"]
70
+ .map((key) => value[key])
71
+ .find((text) => typeof text === "string" && text.trim() !== "");
72
+ const around = lines.filter((line) => line && !line.startsWith("{")).join(" ");
73
+ const reason = (said?.trim() || around || `its answer was ${JSON.stringify(value)}`).slice(0, 500).replace(/\.$/, "");
74
+ return `Railway did not start a VM: ${reason}. Railway allows 3 free VMs per network per day and turns anonymous VMs off while it is busy, so try again later or from another network; see ${railwayFreeVmUrl}.`;
75
+ }
60
76
  /** Where the CLI keeps its own SSH key for Railway, apart from the user's ~/.ssh. */
61
77
  export function railwayKeyPath(env = process.env, platform = process.platform) {
62
78
  const configHome = env.XDG_CONFIG_HOME || (platform === "win32" && env.APPDATA ? env.APPDATA : join(homedir(), ".config"));
@@ -3,6 +3,7 @@
3
3
  // 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
+ import { customizeAppManifest } from "./customize.js";
6
7
  import { updateEnvFile } from "./env.js";
7
8
  import { INCOMING_DIR, planMerge, sameContent } from "./merge.js";
8
9
  import { readProjectFiles, renderProject } from "./project.js";
@@ -35,6 +36,26 @@ export async function planUpdate(project, base, target) {
35
36
  baseFiles.delete("README.md");
36
37
  targetFiles.delete("README.md");
37
38
  }
39
+ // Workspace apps take the new Starter's version unless the project customized their version.
40
+ for (const [path, targetContent] of targetFiles) {
41
+ if (!path.startsWith("apps/") || !path.endsWith("/package.json"))
42
+ continue;
43
+ const baseContent = baseFiles.get(path);
44
+ const currentContent = current.get(path);
45
+ if (!baseContent || !currentContent)
46
+ continue;
47
+ try {
48
+ const basePkg = JSON.parse(baseContent.toString("utf8"));
49
+ const currentPkg = JSON.parse(currentContent.toString("utf8"));
50
+ if (typeof currentPkg.version === "string" && currentPkg.version !== basePkg.version) {
51
+ baseFiles.set(path, Buffer.from(customizeAppManifest(baseContent.toString("utf8"), undefined, currentPkg.version)));
52
+ targetFiles.set(path, Buffer.from(customizeAppManifest(targetContent.toString("utf8"), undefined, currentPkg.version)));
53
+ }
54
+ }
55
+ catch {
56
+ // Leave unparseable manifests untouched.
57
+ }
58
+ }
38
59
  const actions = planMerge(baseFiles, targetFiles, current);
39
60
  if (customReadme && targetReadme && !sameContent(currentReadme, targetReadme)) {
40
61
  actions.push({
package/dist/modify.js CHANGED
@@ -366,8 +366,27 @@ export async function changeProject(args) {
366
366
  throw new Error("Not a project change.");
367
367
  const interactive = Boolean(process.stdin.isTTY) && !args.yes;
368
368
  const project = await readProject(process.cwd());
369
- await assertSafeToChange(project, args, interactive);
370
369
  const upgrading = command.name === "upgrade";
370
+ if (upgrading &&
371
+ !args.templateDir &&
372
+ project.starterCommit &&
373
+ (args.ref ? args.ref === project.starterCommit : project.starterCommit === starterPin.starterCommit)) {
374
+ outro(`The project is already on Starter ${project.starterRef}.`);
375
+ return "unchanged";
376
+ }
377
+ if (command.name === "add" &&
378
+ command.changes.length > 0 &&
379
+ command.changes.every((c) => c.option && project.modules[c.module] === c.option)) {
380
+ outro("The project already uses that selection; nothing to change.");
381
+ return "unchanged";
382
+ }
383
+ if (command.name === "remove" &&
384
+ command.modules.length > 0 &&
385
+ command.modules.every((m) => project.modules[m] === "none")) {
386
+ outro("The project already uses that selection; nothing to change.");
387
+ return "unchanged";
388
+ }
389
+ await assertSafeToChange(project, args, interactive);
371
390
  const progress = spinner();
372
391
  progress.start(upgrading ? "Preparing both Starter revisions" : `Preparing Starter ${project.starterRef}`);
373
392
  const sources = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/create-clubedge-app",
3
- "version": "0.42.1",
3
+ "version": "0.43.1",
4
4
  "description": "Create React apps from Clubedge Starter with a CLI or local MCP server.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.9.0",