@rebasepro/cli 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +2 -1
  2. package/bin/rebase.js +143 -4
  3. package/dist/{agent-setup-B5AKHOgt.js → agent-setup-D9mHRZ5R.js} +125 -3
  4. package/dist/agent-setup-D9mHRZ5R.js.map +1 -0
  5. package/dist/{api-keys-Tm7zvAPe.js → api-keys-CyJ26KEJ.js} +41 -10
  6. package/dist/api-keys-CyJ26KEJ.js.map +1 -0
  7. package/dist/{apps-BsU_ILbP.js → apps-CnLDV5b6.js} +6 -7
  8. package/dist/{apps-BsU_ILbP.js.map → apps-CnLDV5b6.js.map} +1 -1
  9. package/dist/{atlas-dev-database-OWtqnG7W.js → atlas-dev-database-BODqdCWt.js} +4 -4
  10. package/dist/{atlas-dev-database-OWtqnG7W.js.map → atlas-dev-database-BODqdCWt.js.map} +1 -1
  11. package/dist/{auth-Vl2hFKXP.js → auth-dII2jCO1.js} +10 -8
  12. package/dist/auth-dII2jCO1.js.map +1 -0
  13. package/dist/{branch-pointer-9DnwkFJJ.js → branch-pointer-BQUF6lYa.js} +1 -1
  14. package/dist/{build-BBgT8EJZ.js → build-Cza7ZQrp.js} +6 -7
  15. package/dist/{build-BBgT8EJZ.js.map → build-Cza7ZQrp.js.map} +1 -1
  16. package/dist/{bundle-DtCCCr4w.js → bundle-C6yEtMJ-.js} +53 -5
  17. package/dist/bundle-C6yEtMJ-.js.map +1 -0
  18. package/dist/bundle.d.ts +14 -0
  19. package/dist/{cloud-BpH0_bmV.js → cloud-BdnmqQaZ.js} +8528 -4342
  20. package/dist/cloud-BdnmqQaZ.js.map +1 -0
  21. package/dist/collections-dir-DlvDhime.js +69 -0
  22. package/dist/collections-dir-DlvDhime.js.map +1 -0
  23. package/dist/commands/agent-setup.d.ts +58 -0
  24. package/dist/commands/api-keys.d.ts +13 -0
  25. package/dist/commands/cloud/activity.d.ts +25 -0
  26. package/dist/commands/cloud/app-key.d.ts +18 -0
  27. package/dist/commands/cloud/auth.d.ts +1 -0
  28. package/dist/commands/cloud/billing-invoices.d.ts +39 -0
  29. package/dist/commands/cloud/claim.d.ts +7 -0
  30. package/dist/commands/cloud/context.d.ts +115 -6
  31. package/dist/commands/cloud/deploy-summary.d.ts +162 -0
  32. package/dist/commands/cloud/deploy.d.ts +64 -0
  33. package/dist/commands/cloud/env.d.ts +24 -0
  34. package/dist/commands/cloud/index.d.ts +13 -1
  35. package/dist/commands/cloud/init.d.ts +143 -0
  36. package/dist/commands/cloud/login-flow.d.ts +51 -0
  37. package/dist/commands/cloud/notifications.d.ts +28 -0
  38. package/dist/commands/cloud/orgs.d.ts +13 -0
  39. package/dist/commands/cloud/previews.d.ts +123 -0
  40. package/dist/commands/cloud/resources.d.ts +68 -14
  41. package/dist/commands/cloud/runtime-logs.d.ts +176 -0
  42. package/dist/commands/cloud/service-key.d.ts +15 -0
  43. package/dist/commands/cloud/token-capabilities.d.ts +61 -8
  44. package/dist/commands/cloud/tokens.d.ts +18 -3
  45. package/dist/commands/cloud/usage.d.ts +63 -0
  46. package/dist/commands/dev.d.ts +22 -0
  47. package/dist/commands/doctor.d.ts +1 -1
  48. package/dist/commands/generate-view.d.ts +94 -0
  49. package/dist/commands/generate_sdk.d.ts +19 -2
  50. package/dist/commands/init.d.ts +20 -0
  51. package/dist/commands/skills.d.ts +4 -3
  52. package/dist/commands/start.d.ts +10 -0
  53. package/dist/commands/storage.d.ts +4 -0
  54. package/dist/commands/upgrade.d.ts +12 -0
  55. package/dist/commands/verify.d.ts +96 -0
  56. package/dist/{consent-ClRHmcxf.js → consent-BD7F29T3.js} +2 -2
  57. package/dist/{consent-ClRHmcxf.js.map → consent-BD7F29T3.js.map} +1 -1
  58. package/dist/{console-CEGm0XNc.js → console-BXB6-8GT.js} +12 -6
  59. package/dist/console-BXB6-8GT.js.map +1 -0
  60. package/dist/{context-CyflUrKg.js → context-GUxkZm-S.js} +482 -26
  61. package/dist/context-GUxkZm-S.js.map +1 -0
  62. package/dist/{daemon-CknkDjd6.js → daemon-Cp7oNXth.js} +160 -5
  63. package/dist/daemon-Cp7oNXth.js.map +1 -0
  64. package/dist/{daemon-entry-CbLRrgzy.js → daemon-entry-zOC1revO.js} +6 -3
  65. package/dist/daemon-entry-zOC1revO.js.map +1 -0
  66. package/dist/{db-CRNpA5mH.js → db-KzlqddGD.js} +101 -54
  67. package/dist/db-KzlqddGD.js.map +1 -0
  68. package/dist/{derive-CsoFGAnK.js → derive-ae9UOoOB.js} +2 -2
  69. package/dist/{derive-CsoFGAnK.js.map → derive-ae9UOoOB.js.map} +1 -1
  70. package/dist/{dev-C0pbMmhS.js → dev-DTqVMgB3.js} +231 -44
  71. package/dist/dev-DTqVMgB3.js.map +1 -0
  72. package/dist/dev-db/daemon.d.ts +41 -0
  73. package/dist/dev-db/pull.d.ts +33 -5
  74. package/dist/dev-db/state.d.ts +9 -0
  75. package/dist/{dev-preflight-unHpiYO1.js → dev-preflight-Cxv3TiD1.js} +73 -27
  76. package/dist/dev-preflight-Cxv3TiD1.js.map +1 -0
  77. package/dist/{dev-server-B1UYifZJ.js → dev-server-OJsUGZEB.js} +14 -3
  78. package/dist/dev-server-OJsUGZEB.js.map +1 -0
  79. package/dist/{doctor-CATRdWm6.js → doctor-BRWOlqeG.js} +14 -12
  80. package/dist/doctor-BRWOlqeG.js.map +1 -0
  81. package/dist/{eject-DuasTr-F.js → eject-DABcDnPN.js} +3 -4
  82. package/dist/{eject-DuasTr-F.js.map → eject-DABcDnPN.js.map} +1 -1
  83. package/dist/{fold-static-hDHtDfTq.js → fold-static-BnAK-anQ.js} +3 -3
  84. package/dist/{fold-static-hDHtDfTq.js.map → fold-static-BnAK-anQ.js.map} +1 -1
  85. package/dist/generate-view-IFwCADvb.js +662 -0
  86. package/dist/generate-view-IFwCADvb.js.map +1 -0
  87. package/dist/{generate_sdk-Cvg5jjJF.js → generate_sdk-BvcWvoUh.js} +49 -13
  88. package/dist/generate_sdk-BvcWvoUh.js.map +1 -0
  89. package/dist/index.es.js +58 -40
  90. package/dist/index.es.js.map +1 -1
  91. package/dist/{init-BWAwHPtz.js → init-CaU6GhwI.js} +50 -62
  92. package/dist/init-CaU6GhwI.js.map +1 -0
  93. package/dist/invoked-command-CG_rIa_o.js +55 -0
  94. package/dist/invoked-command-CG_rIa_o.js.map +1 -0
  95. package/dist/last-dev-database-DpW5dfoX.js +2 -0
  96. package/dist/{manifest-DRp0UVTj.js → manifest-BtzDy7KQ.js} +36 -3
  97. package/dist/{manifest-DRp0UVTj.js.map → manifest-BtzDy7KQ.js.map} +1 -1
  98. package/dist/{normalize-imports-BoBVh5tU.js → normalize-imports-D0zXlM6g.js} +3 -3
  99. package/dist/{normalize-imports-BoBVh5tU.js.map → normalize-imports-D0zXlM6g.js.map} +1 -1
  100. package/dist/{path-flags-BFgG9V6y.js → path-flags-DNvtEi2-.js} +5 -4
  101. package/dist/path-flags-DNvtEi2-.js.map +1 -0
  102. package/dist/{prepare-DdnU70z8.js → prepare-CTw9NEkh.js} +5 -4
  103. package/dist/{prepare-DdnU70z8.js.map → prepare-CTw9NEkh.js.map} +1 -1
  104. package/dist/project-CasK56vN.js +701 -0
  105. package/dist/project-CasK56vN.js.map +1 -0
  106. package/dist/{pull-C3PXduzl.js → pull-DDMpsZ5r.js} +131 -24
  107. package/dist/pull-DDMpsZ5r.js.map +1 -0
  108. package/dist/refusal-ReeeGP8a.js +26 -0
  109. package/dist/refusal-ReeeGP8a.js.map +1 -0
  110. package/dist/resolve-D647F1LA.js +2 -0
  111. package/dist/{resources-BdNRDEAy.js → resources-CPSC5Kcu.js} +4 -5
  112. package/dist/{resources-BdNRDEAy.js.map → resources-CPSC5Kcu.js.map} +1 -1
  113. package/dist/{schema-B0-p6T1O.js → schema-BSXwZSiI.js} +9 -8
  114. package/dist/schema-BSXwZSiI.js.map +1 -0
  115. package/dist/{skills-DfnTeyRz.js → skills-BU2flMOy.js} +52 -8
  116. package/dist/skills-BU2flMOy.js.map +1 -0
  117. package/dist/{start-BmEZkS0n.js → start-mVb6upwY.js} +20 -7
  118. package/dist/start-mVb6upwY.js.map +1 -0
  119. package/dist/{state-CCB-FAQ9.js → state-CYdBvnwA.js} +3 -2
  120. package/dist/state-CYdBvnwA.js.map +1 -0
  121. package/dist/static-syntax-check.d.ts +64 -0
  122. package/dist/{status-BJ26yZOo.js → status-BREvtzJm.js} +8 -9
  123. package/dist/{status-BJ26yZOo.js.map → status-BREvtzJm.js.map} +1 -1
  124. package/dist/{storage-DKPUBTWG.js → storage-DtOXGflp.js} +159 -16
  125. package/dist/storage-DtOXGflp.js.map +1 -0
  126. package/dist/{telemetry-EMFRVRQi.js → telemetry-CgwJPVMO.js} +2 -2
  127. package/dist/{telemetry-EMFRVRQi.js.map → telemetry-CgwJPVMO.js.map} +1 -1
  128. package/dist/{telemetry-m9czTNGR.js → telemetry-DwK5RQJl.js} +4 -4
  129. package/dist/{telemetry-m9czTNGR.js.map → telemetry-DwK5RQJl.js.map} +1 -1
  130. package/dist/{upgrade-DjihWuZL.js → upgrade-B7sVwL8S.js} +26 -4
  131. package/dist/{upgrade-DjihWuZL.js.map → upgrade-B7sVwL8S.js.map} +1 -1
  132. package/dist/utils/args.d.ts +32 -3
  133. package/dist/utils/boot-retry.d.ts +41 -0
  134. package/dist/utils/collections-dir.d.ts +14 -0
  135. package/dist/utils/dev-preflight.d.ts +34 -1
  136. package/dist/utils/dev-server.d.ts +11 -0
  137. package/dist/utils/path-flags.d.ts +2 -1
  138. package/dist/utils/project.d.ts +5 -1
  139. package/dist/utils/tool-stdio.d.ts +1 -1
  140. package/dist/utils/unknown-command.d.ts +4 -0
  141. package/dist/verify-D7UsV_qe.js +730 -0
  142. package/dist/verify-D7UsV_qe.js.map +1 -0
  143. package/package.json +9 -7
  144. package/templates/eject/backend/src/index.ts +9 -1
  145. package/templates/generated-schemas/ecommerce.ts +3 -0
  146. package/templates/overlays/baas/ai-instructions.md +13 -0
  147. package/templates/resource-graphs/blank.json +25 -0
  148. package/templates/resource-graphs/blog.json +28 -0
  149. package/templates/resource-graphs/ecommerce.json +28 -0
  150. package/templates/resource-graphs/headless.json +22 -0
  151. package/templates/template/.claude/hooks/post-edit-collection.sh +99 -0
  152. package/templates/template/.claude/settings.json +17 -0
  153. package/templates/template/.env.example +15 -5
  154. package/templates/template/ai-instructions.md +21 -1
  155. package/templates/template/config/collections/presets/ecommerce/index.ts +23 -0
  156. package/templates/template/docker-compose.yml +4 -1
  157. package/templates/template/frontend/package.json +1 -0
  158. package/templates/template/frontend/src/App.tsx +10 -9
  159. package/templates/views/AppView.tsx.tmpl +138 -0
  160. package/templates/views/EntityView.tsx.tmpl +64 -0
  161. package/templates/views/index.ts.tmpl +8 -0
  162. package/dist/agent-setup-B5AKHOgt.js.map +0 -1
  163. package/dist/api-keys-Tm7zvAPe.js.map +0 -1
  164. package/dist/args-BX0YD14a.js +0 -266
  165. package/dist/args-BX0YD14a.js.map +0 -1
  166. package/dist/auth-Vl2hFKXP.js.map +0 -1
  167. package/dist/bundle-DtCCCr4w.js.map +0 -1
  168. package/dist/cloud-BpH0_bmV.js.map +0 -1
  169. package/dist/console-CEGm0XNc.js.map +0 -1
  170. package/dist/context-CyflUrKg.js.map +0 -1
  171. package/dist/daemon-CknkDjd6.js.map +0 -1
  172. package/dist/daemon-entry-CbLRrgzy.js.map +0 -1
  173. package/dist/db-CRNpA5mH.js.map +0 -1
  174. package/dist/dev-C0pbMmhS.js.map +0 -1
  175. package/dist/dev-preflight-unHpiYO1.js.map +0 -1
  176. package/dist/dev-server-B1UYifZJ.js.map +0 -1
  177. package/dist/doctor-CATRdWm6.js.map +0 -1
  178. package/dist/generate_sdk-Cvg5jjJF.js.map +0 -1
  179. package/dist/init-BWAwHPtz.js.map +0 -1
  180. package/dist/last-dev-database-c_dKz1S3.js +0 -2
  181. package/dist/path-flags-BFgG9V6y.js.map +0 -1
  182. package/dist/project-Wp2zoyeN.js +0 -349
  183. package/dist/project-Wp2zoyeN.js.map +0 -1
  184. package/dist/pull-C3PXduzl.js.map +0 -1
  185. package/dist/resolve-CNQjsatH.js +0 -2
  186. package/dist/schema-B0-p6T1O.js.map +0 -1
  187. package/dist/skills-DfnTeyRz.js.map +0 -1
  188. package/dist/start-BmEZkS0n.js.map +0 -1
  189. package/dist/state-CCB-FAQ9.js.map +0 -1
  190. package/dist/storage-DKPUBTWG.js.map +0 -1
@@ -1,14 +1,15 @@
1
- import { i as recordEvent } from "./telemetry-EMFRVRQi.js";
1
+ import { i as recordEvent } from "./telemetry-CgwJPVMO.js";
2
2
  import { n as getPMCommands, t as detectPackageManager } from "./package-manager-DSEOwTqM.js";
3
- import { a as findBackendDir, g as resolveTsx, h as resolvePluginCliScript, l as getActiveBackendPlugin, n as dependenciesNotInstalled, o as findEnvFile, p as requireProjectRoot, s as findFrontendDir, v as validateTsxInstallation } from "./project-Wp2zoyeN.js";
4
- import { n as parseCommandArgs, r as wantsHelp, t as UsageError } from "./args-BX0YD14a.js";
5
- import { d as findBackendApp, f as formatAppAddress, g as resolveBackendPaths, l as cmsMountOf, p as loadManifest, v as staticAppAddress } from "./manifest-DRp0UVTj.js";
6
- import { n as getProjectPort } from "./dev-server-B1UYifZJ.js";
7
- import { i as ensureDevDatabase, o as probeTcp } from "./dev-preflight-unHpiYO1.js";
3
+ import { C as switchOn, a as findBackendDir, b as UsageError, g as resolveTsx, h as resolvePluginCliScript, l as getActiveBackendPlugin, n as dependenciesNotInstalled, o as findEnvFile, p as requireProjectRoot, s as findFrontendDir, v as validateTsxInstallation, w as wantsHelp, x as parseCommandArgs } from "./project-CasK56vN.js";
4
+ import { d as findBackendApp, f as formatAppAddress, g as resolveBackendPaths, l as cmsMountOf, p as loadManifest, v as staticAppAddress } from "./manifest-BtzDy7KQ.js";
5
+ import { r as getProjectPort, t as DEV_BACKEND_URL_ENV } from "./dev-server-OJsUGZEB.js";
6
+ import { collectionsDirArgs, resolveCollectionsDir, resolveConfigDir } from "./collections-dir-DlvDhime.js";
7
+ import { t as Refusal } from "./refusal-ReeeGP8a.js";
8
+ import { i as ensureDevDatabase, o as probeTcp } from "./dev-preflight-Cxv3TiD1.js";
8
9
  import { t as MANAGED_IDLE_TIMEOUT_MS } from "./constraints-CtiylsGf.js";
9
- import { a as resolveCliEntry, o as resolveSpawn } from "./daemon-CknkDjd6.js";
10
- import { DEV_DATABASE_KIND_ENV, i as lastDevDatabaseNotice, managedNotices, prepareDatabaseEnv, resolveProjectDatabase, s as recordLastDevDatabase } from "./prepare-DdnU70z8.js";
11
- import { runDriverDbCommand, runDriverSchemaCommand } from "./db-CRNpA5mH.js";
10
+ import { a as resolveCliEntry, o as resolveSpawn } from "./daemon-Cp7oNXth.js";
11
+ import { DEV_DATABASE_KIND_ENV, i as lastDevDatabaseNotice, managedNotices, prepareDatabaseEnv, resolveProjectDatabase, s as recordLastDevDatabase } from "./prepare-CTw9NEkh.js";
12
+ import { runDriverDbCommand, runDriverSchemaCommand } from "./db-KzlqddGD.js";
12
13
  import { t as affectsSqlSchema } from "./collection-drift-BKj_BoMq.js";
13
14
  import chalk from "chalk";
14
15
  import fs from "fs";
@@ -20,6 +21,80 @@ import dotenv from "dotenv";
20
21
  import { DEFAULT_RESOURCE_KEY, parseEnvBoolean } from "@rebasepro/types";
21
22
  import { execa, execaCommandSync } from "execa";
22
23
  import { StringDecoder } from "string_decoder";
24
+ //#region src/utils/boot-retry.ts
25
+ /**
26
+ * A backend boot that lost its database part-way, which booting again fixes.
27
+ *
28
+ * `rebase db branch create --force` closes the database it copies: it ends
29
+ * every connection to it and refuses new ones until the copy is done. A
30
+ * backend booting at that moment failed with `Failed to start the Rebase
31
+ * runtime … terminating connection due to administrator command (57P01)`, and
32
+ * `rebase dev` then said "Fix the error above; the watcher restarts it on the
33
+ * next change", about an error nobody could fix and that the next boot would
34
+ * not have. The same holds for a database that is restarting.
35
+ *
36
+ * This reads the backend's output and says when a failed boot is worth another
37
+ * try, and after how long: a few tries, each waiting twice as long as the one
38
+ * before, and none once the backend has failed for a reason of its own.
39
+ */
40
+ /** The sentence that starts a boot failure, as `reportBootFailure` in `@rebasepro/server` writes it. */
41
+ var BOOT_FAILED = /Failed to start the Rebase runtime/;
42
+ /**
43
+ * Why a boot failed that a later boot does not, by what PostgreSQL said:
44
+ * the server ended the connection (57P01 `pg_terminate_backend`, 57P02, 57P03
45
+ * a server starting or stopping), or the database is closed to new
46
+ * connections (55000, what a forced branch create does while it copies).
47
+ * A connection the server ended while it sat idle is reported by `pg` on the
48
+ * next query, in its own words and with no code: "Client has encountered a
49
+ * connection error and is not queryable".
50
+ */
51
+ var PASSING_CAUSES = [
52
+ /terminating connection due to administrator command/i,
53
+ /Client has encountered a connection error and is not queryable/i,
54
+ /\(57P0[123]\)/,
55
+ /is not currently accepting connections/i,
56
+ /the database system is (?:starting up|shutting down|in recovery mode)/i
57
+ ];
58
+ var BootRetry = class {
59
+ maxAttempts;
60
+ firstDelayMs;
61
+ failing = false;
62
+ planned = false;
63
+ attempts = 0;
64
+ constructor(maxAttempts = 3, firstDelayMs = 1e3) {
65
+ this.maxAttempts = maxAttempts;
66
+ this.firstDelayMs = firstDelayMs;
67
+ }
68
+ /**
69
+ * Read one line of the backend's output, colour removed. Returns a plan
70
+ * the first time a failing boot names a passing cause, and null otherwise,
71
+ * including once the tries are spent.
72
+ */
73
+ line(text) {
74
+ if (BOOT_FAILED.test(text)) {
75
+ this.failing = true;
76
+ this.planned = false;
77
+ }
78
+ if (!this.failing || this.planned) return null;
79
+ if (!PASSING_CAUSES.some((cause) => cause.test(text))) return null;
80
+ this.planned = true;
81
+ if (this.attempts >= this.maxAttempts) return null;
82
+ this.attempts += 1;
83
+ return {
84
+ attempt: this.attempts,
85
+ of: this.maxAttempts,
86
+ delayMs: this.firstDelayMs * 2 ** (this.attempts - 1),
87
+ reason: text.trim()
88
+ };
89
+ }
90
+ /** The backend is serving: a later failure is a new one, with every try left. */
91
+ up() {
92
+ this.failing = false;
93
+ this.planned = false;
94
+ this.attempts = 0;
95
+ }
96
+ };
97
+ //#endregion
23
98
  //#region src/commands/dev.ts
24
99
  /**
25
100
  * CLI command: rebase dev
@@ -225,12 +300,53 @@ function devRuntimeEnv(projectRoot) {
225
300
  */
226
301
  function devWatchIncludes(projectRoot, paths) {
227
302
  const dirs = [paths.REBASE_DEV_FUNCTIONS, paths.REBASE_DEV_CRONS].filter((entry) => Boolean(entry)).map((entry) => path.resolve(projectRoot, entry));
228
- dirs.push(path.join(projectRoot, "config"));
303
+ dirs.push(path.join(projectRoot, resolveConfigDir(projectRoot)));
229
304
  return dirs;
230
305
  }
231
306
  /** Where dev watches the collections it regenerates the schema and SDK from. */
232
307
  function devCollectionsDir(projectRoot) {
233
- return path.join(projectRoot, "config", "collections");
308
+ return resolveCollectionsDir(projectRoot);
309
+ }
310
+ /**
311
+ * Call `onAppear` once, when `target` exists, and return what stops waiting.
312
+ *
313
+ * Watches the nearest directory above `target` that exists (no higher than
314
+ * `root`), and follows the path down as each part of it appears — `fs.watch`
315
+ * cannot watch a directory that is not there yet. A headless project has no
316
+ * `config/collections` until `rebase schema introspect` writes one, and dev
317
+ * watched it only when it was there at start: the backend kept serving the
318
+ * API it derived from the database, and edits to the new files did nothing,
319
+ * until dev was restarted by hand.
320
+ */
321
+ function whenDirectoryAppears(target, root, onAppear) {
322
+ let watcher = null;
323
+ let watching = null;
324
+ let done = false;
325
+ const stop = () => {
326
+ done = true;
327
+ watcher?.close();
328
+ watcher = null;
329
+ };
330
+ const arm = () => {
331
+ if (done) return;
332
+ if (fs.existsSync(target)) {
333
+ stop();
334
+ onAppear();
335
+ return;
336
+ }
337
+ let dir = path.dirname(target);
338
+ while (!fs.existsSync(dir) && dir !== root && path.dirname(dir) !== dir) dir = path.dirname(dir);
339
+ if (dir === watching || !fs.existsSync(dir)) return;
340
+ watcher?.close();
341
+ watching = dir;
342
+ try {
343
+ watcher = fs.watch(dir, () => arm());
344
+ } catch {
345
+ watcher = null;
346
+ }
347
+ };
348
+ arm();
349
+ return stop;
234
350
  }
235
351
  /**
236
352
  * The file dev writes to restart the backend, or `null` when tsx would not see it.
@@ -323,7 +439,7 @@ async function declaredAdditionalDatabases(projectRoot) {
323
439
  try {
324
440
  const backend = findBackendApp(loadManifest(projectRoot).manifest);
325
441
  if (!backend) return [];
326
- const { deriveResourceGraph, deriveOptionsFor } = await import("./derive-CsoFGAnK.js").then((n) => n.i);
442
+ const { deriveResourceGraph, deriveOptionsFor } = await import("./derive-ae9UOoOB.js").then((n) => n.i);
327
443
  const { graph } = await deriveResourceGraph({
328
444
  ...deriveOptionsFor(projectRoot, backend.app),
329
445
  cronsDir: void 0,
@@ -666,7 +782,7 @@ function readEnvVar(projectRoot, name) {
666
782
  * mode serves whatever the database already has.
667
783
  */
668
784
  function projectHasCollections(projectRoot) {
669
- const collectionsDir = path.join(projectRoot, "config", "collections");
785
+ const collectionsDir = devCollectionsDir(projectRoot);
670
786
  return fs.existsSync(collectionsDir) && fs.readdirSync(collectionsDir).some((name) => /\.(ts|js|mts|mjs)$/.test(name) && !name.startsWith("index."));
671
787
  }
672
788
  /**
@@ -763,9 +879,13 @@ function needsStaleSchemaPreflight(generation) {
763
879
  * hand, so it is captured, and on failure only the lines that say what went
764
880
  * wrong are shown — not the stack under them. Like the schema, a failure never
765
881
  * stops `dev`.
882
+ *
883
+ * `backendPort` is where the backend is, or is about to be: the README's
884
+ * example names it. The first run is before the backend binds, so it is told
885
+ * rather than left to read `.rebase/state.json`.
766
886
  */
767
- async function regenerateSdk(projectRoot) {
768
- if (!projectHasCollections(projectRoot)) return;
887
+ async function regenerateSdk(projectRoot, backendPort) {
888
+ if (!projectHasCollections(projectRoot)) return "skipped";
769
889
  const entry = resolveCliEntry();
770
890
  const { execPath, prefixArgs } = resolveSpawn(entry);
771
891
  try {
@@ -775,16 +895,43 @@ async function regenerateSdk(projectRoot) {
775
895
  "generate-sdk"
776
896
  ], {
777
897
  cwd: projectRoot,
778
- all: true
898
+ all: true,
899
+ ...backendPort !== null ? { env: { [DEV_BACKEND_URL_ENV]: `http://localhost:${backendPort}` } } : {}
779
900
  });
901
+ return "generated";
780
902
  } catch (error) {
781
903
  const cause = (typeof error === "object" && error !== null && "all" in error && typeof error.all === "string" ? error.all : "").split("\n").filter((line) => line.trim() && !/^\s+at /.test(line)).slice(-6);
782
904
  console.log(` ${chalk.yellow("⚠")} ${chalk.gray("Could not regenerate the SDK types in generated/sdk.")}`);
783
905
  if (cause.length > 0) console.log(chalk.gray(cause.map((line) => ` ${line.trim()}`).join("\n")));
784
906
  console.log(` ${chalk.gray(`Run ${chalk.cyan("rebase generate-sdk")} to see why.`)}`);
907
+ return "failed";
785
908
  }
786
909
  }
787
910
  /**
911
+ * What dev prints once a collection edit's regeneration is done.
912
+ *
913
+ * The green "regenerated" box only when neither half failed. It used to be
914
+ * printed unconditionally, right under "⚠ Could not regenerate the database
915
+ * schema … nothing was applied" — and then the backend refused to boot over
916
+ * the same collection error, so the success line was the one wrong thing on
917
+ * the screen.
918
+ */
919
+ function collectionChangeSummary(schema, sdk) {
920
+ const failed = [schema === "failed" && "the database schema", sdk === "failed" && "the SDK types"].filter((part) => Boolean(part));
921
+ if (failed.length > 0) return [
922
+ chalk.yellow(` ⚠ Not regenerated: ${failed.join(" and ")} — fix the error above and save again.`),
923
+ chalk.gray(" The backend restarts and reports the same problem until it is fixed."),
924
+ ""
925
+ ];
926
+ return [
927
+ chalk.green(" ✓ Schema and SDK types regenerated. The backend restarts and boot"),
928
+ chalk.green(" creates what is missing — a new collection, a new property."),
929
+ chalk.gray(" For what boot leaves alone — a renamed column, a narrowed type,"),
930
+ chalk.gray(` a removed field — run ${chalk.cyan("rebase db push")}.`),
931
+ ""
932
+ ];
933
+ }
934
+ /**
788
935
  * One variable's value out of `.env` text, or undefined when it has none.
789
936
  *
790
937
  * The obvious `\s*(.+?)\s*$` does not do this, because `\s` matches a newline
@@ -982,7 +1129,7 @@ async function devCommand(rawArgs) {
982
1129
  printDevHelp();
983
1130
  return;
984
1131
  }
985
- if (rawArgs.includes("--generate") || rawArgs.includes("-g")) throw new UsageError("`rebase dev --generate` is gone: dev now regenerates the schema and the SDK types on every start and every collection save. Drop the flag.");
1132
+ if (switchOn(rawArgs, "--generate") || rawArgs.includes("-g")) throw new UsageError("`rebase dev --generate` is gone: dev now regenerates the schema and the SDK types on every start and every collection save. Drop the flag.");
986
1133
  const { flags: args } = parseCommandArgs({
987
1134
  spec: DEV_FLAGS,
988
1135
  rawArgs,
@@ -1009,7 +1156,7 @@ async function devCommand(rawArgs) {
1009
1156
  const { port: startPort, source: startPortSource } = resolveStartPort(projectRoot, args["--port"]);
1010
1157
  const portIsPinned = args["--port"] !== void 0;
1011
1158
  const pinnedFrontendPort = process.env.REBASE_FRONTEND_PORT;
1012
- if (pinnedFrontendPort && !/^\d+$/.test(pinnedFrontendPort)) throw new Error(`REBASE_FRONTEND_PORT must be a number, got ${JSON.stringify(pinnedFrontendPort)}.`);
1159
+ if (pinnedFrontendPort && !/^\d+$/.test(pinnedFrontendPort)) throw new Refusal(`REBASE_FRONTEND_PORT must be a number, got ${JSON.stringify(pinnedFrontendPort)}.`);
1013
1160
  const frontendPort = pinnedFrontendPort ? Number(pinnedFrontendPort) : getProjectFrontendPort(projectRoot);
1014
1161
  if (!frontendOnly) {
1015
1162
  const refusal = await pinnedPortRefusal(startPort, portIsPinned);
@@ -1046,6 +1193,15 @@ async function devCommand(rawArgs) {
1046
1193
  * which is the failure the flag exists to produce.
1047
1194
  */
1048
1195
  let prepared = null;
1196
+ /**
1197
+ * The backend port the SDK README was last generated for: the one the
1198
+ * backend is told at boot, then each one it announces it bound.
1199
+ */
1200
+ let sdkBackendPort = startsBackend ? startPort : null;
1201
+ const regenerateSdkFor = (port) => {
1202
+ sdkBackendPort = port;
1203
+ return regenerateSdk(projectRoot, port);
1204
+ };
1049
1205
  if (!frontendOnly) {
1050
1206
  const resolved = args["--docker"] ? resolveProjectDatabase(projectRoot, {
1051
1207
  flagUrl,
@@ -1054,7 +1210,7 @@ async function devCommand(rawArgs) {
1054
1210
  }) : null;
1055
1211
  const dockerUrl = resolved?.kind === "docker" ? resolved.url ?? void 0 : void 0;
1056
1212
  ({schemaGeneration, database: prepared} = await overlapDevStartup({
1057
- generate: async () => (await Promise.all([ensureGeneratedSchema(projectRoot), regenerateSdk(projectRoot)]))[0],
1213
+ generate: async () => (await Promise.all([ensureGeneratedSchema(projectRoot), regenerateSdkFor(sdkBackendPort)]))[0],
1058
1214
  preflight: () => runDatabasePreflight({
1059
1215
  projectRoot,
1060
1216
  disabled: noDb || flagUrl !== null,
@@ -1292,16 +1448,22 @@ async function devCommand(rawArgs) {
1292
1448
  try {
1293
1449
  const activePlugin = getActiveBackendPlugin(backendDir);
1294
1450
  const pluginCli = activePlugin ? resolvePluginCliScript(backendDir, activePlugin) : null;
1295
- if (pluginCli && needsStaleSchemaPreflight(schemaGeneration)) await execa(tsxBin, [
1296
- pluginCli,
1297
- "schema",
1298
- "stale",
1299
- "--fix"
1300
- ], {
1301
- cwd: backendDir,
1302
- stdio: "inherit",
1303
- env
1304
- });
1451
+ if (pluginCli && needsStaleSchemaPreflight(schemaGeneration)) {
1452
+ const staleArgs = [
1453
+ "schema",
1454
+ "stale",
1455
+ "--fix"
1456
+ ];
1457
+ await execa(tsxBin, [
1458
+ pluginCli,
1459
+ ...staleArgs,
1460
+ ...collectionsDirArgs(projectRoot, staleArgs)
1461
+ ], {
1462
+ cwd: backendDir,
1463
+ stdio: "inherit",
1464
+ env
1465
+ });
1466
+ }
1305
1467
  } catch {}
1306
1468
  const ejectedEntry = path.join(backendDir, "src", "index.ts");
1307
1469
  const usesStockRuntime = !fs.existsSync(ejectedEntry);
@@ -1345,13 +1507,12 @@ async function devCommand(rawArgs) {
1345
1507
  }
1346
1508
  };
1347
1509
  const collectionsDir = devCollectionsDir(projectRoot);
1348
- if (fs.existsSync(collectionsDir)) {
1510
+ {
1349
1511
  let debounce = null;
1350
1512
  let sqlSchemaAffected = false;
1351
1513
  let changed = "";
1352
- fs.watch(collectionsDir, { recursive: true }, (_eventType, filename) => {
1353
- if (!filename || filename.startsWith(".") || filename.endsWith(".tmp")) return;
1354
- sqlSchemaAffected = sqlSchemaAffected || affectsSqlSchema(collectionsDir, filename);
1514
+ const collectionsChanged = (filename, affectsSchema) => {
1515
+ sqlSchemaAffected = sqlSchemaAffected || affectsSchema;
1355
1516
  changed = filename;
1356
1517
  if (debounce) clearTimeout(debounce);
1357
1518
  debounce = setTimeout(() => {
@@ -1359,7 +1520,7 @@ async function devCommand(rawArgs) {
1359
1520
  sqlSchemaAffected = false;
1360
1521
  (async () => {
1361
1522
  if (!regenerateSchema) {
1362
- await regenerateSdk(projectRoot);
1523
+ await regenerateSdkFor(resolvedBackendPort ?? startPort);
1363
1524
  restartBackend();
1364
1525
  return;
1365
1526
  }
@@ -1372,17 +1533,22 @@ async function devCommand(rawArgs) {
1372
1533
  chalk.yellow(" └──────────────────────────────────────────────────────────────┘")
1373
1534
  ].join("\n"));
1374
1535
  const schemaBefore = generatedSchemaStamp();
1375
- await Promise.all([ensureGeneratedSchema(projectRoot), regenerateSdk(projectRoot)]);
1376
- if (!(usesStockRuntime && generatedSchemaStamp() !== schemaBefore)) restartBackend();
1377
- console.log([
1378
- chalk.green(" ✓ Schema and SDK types regenerated. The backend restarts and boot"),
1379
- chalk.green(" creates what is missing — a new collection, a new property."),
1380
- chalk.gray(" For what boot leaves alone — a renamed column, a narrowed type,"),
1381
- chalk.gray(` a removed field — run ${chalk.cyan("rebase db push")}.`),
1382
- ""
1383
- ].join("\n"));
1536
+ const [schemaResult, sdkResult] = await Promise.all([ensureGeneratedSchema(projectRoot), regenerateSdkFor(resolvedBackendPort ?? startPort)]);
1537
+ if (!(usesStockRuntime && schemaBefore !== null && generatedSchemaStamp() !== schemaBefore)) restartBackend();
1538
+ console.log(collectionChangeSummary(schemaResult, sdkResult).join("\n"));
1384
1539
  })();
1385
1540
  }, 500);
1541
+ };
1542
+ const watchCollections = () => {
1543
+ fs.watch(collectionsDir, { recursive: true }, (_eventType, filename) => {
1544
+ if (!filename || filename.startsWith(".") || filename.endsWith(".tmp")) return;
1545
+ collectionsChanged(filename, affectsSqlSchema(collectionsDir, filename));
1546
+ });
1547
+ };
1548
+ if (fs.existsSync(collectionsDir)) watchCollections();
1549
+ else whenDirectoryAppears(collectionsDir, projectRoot, () => {
1550
+ watchCollections();
1551
+ collectionsChanged(`${path.basename(collectionsDir)}/`, true);
1386
1552
  });
1387
1553
  }
1388
1554
  if (!backendOnly && frontendDir && await frontendCanStartWithBackend({
@@ -1415,6 +1581,23 @@ async function devCommand(rawArgs) {
1415
1581
  * after the entry crashes, so nothing exits to contradict it.
1416
1582
  */
1417
1583
  let backendAnnounced = false;
1584
+ /**
1585
+ * A boot that lost its database part-way is booted again, a few
1586
+ * times, instead of waiting for a file change. See utils/boot-retry.ts:
1587
+ * a `rebase db branch create --force` that lands while the backend
1588
+ * boots ends its connections, and nothing in the project is wrong.
1589
+ */
1590
+ const bootRetry = new BootRetry();
1591
+ const retryPassingBootFailure = (line) => {
1592
+ if (!usableTrigger || shuttingDown) return;
1593
+ const plan = bootRetry.line(stripAnsi(line));
1594
+ if (!plan) return;
1595
+ setTimeout(() => {
1596
+ if (shuttingDown || backendAnnounced) return;
1597
+ console.log(chalk.gray(` ↻ The backend lost its database while it was starting; starting it again (${plan.attempt} of ${plan.of}).`));
1598
+ restartBackend();
1599
+ }, plan.delayMs).unref?.();
1600
+ };
1418
1601
  /** The verdict, printed once per failure rather than once per line. */
1419
1602
  let backendDownReported = false;
1420
1603
  const reportBackendDown = (headline) => {
@@ -1537,6 +1720,7 @@ async function devCommand(rawArgs) {
1537
1720
  console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
1538
1721
  noticeWatcherRestart(line);
1539
1722
  noticePortMoved(line);
1723
+ retryPassingBootFailure(line);
1540
1724
  const cleanLine = stripAnsi(line);
1541
1725
  const swaggerMatch = cleanLine.match(/Swagger UI available.*"path":"([^"]+)"/);
1542
1726
  if (swaggerMatch) swaggerPath = swaggerMatch[1];
@@ -1545,6 +1729,7 @@ async function devCommand(rawArgs) {
1545
1729
  backendAnnounced = true;
1546
1730
  backendDownReported = false;
1547
1731
  missedProbes = 0;
1732
+ bootRetry.up();
1548
1733
  if (backendOnly) process.exitCode = 0;
1549
1734
  disarmReadyDeadline();
1550
1735
  resolvedBackendPort = parseInt(serverMatch[1], 10);
@@ -1561,6 +1746,7 @@ async function devCommand(rawArgs) {
1561
1746
  fs.writeFileSync(urlFile, `http://localhost:${resolvedBackendPort}`, "utf-8");
1562
1747
  const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
1563
1748
  fs.writeFileSync(portFile, devPortFileContents(resolvedBackendPort, startPort), "utf-8");
1749
+ if (sdkBackendPort !== null && sdkBackendPort !== resolvedBackendPort) regenerateSdkFor(resolvedBackendPort);
1564
1750
  }
1565
1751
  });
1566
1752
  /** Whether we've already shown a corrupted-modules recovery hint. */
@@ -1569,6 +1755,7 @@ async function devCommand(rawArgs) {
1569
1755
  console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
1570
1756
  noticeWatcherRestart(line);
1571
1757
  noticePortMoved(line);
1758
+ retryPassingBootFailure(line);
1572
1759
  if (!corruptedModulesWarned) {
1573
1760
  const cleanLine = stripAnsi(line);
1574
1761
  if (cleanLine.includes("Cannot find module") && cleanLine.includes("node_modules/.pnpm/")) {
@@ -1690,4 +1877,4 @@ ${chalk.green.bold("Description")}
1690
1877
  //#endregion
1691
1878
  export { devCommand };
1692
1879
 
1693
- //# sourceMappingURL=dev-C0pbMmhS.js.map
1880
+ //# sourceMappingURL=dev-DTqVMgB3.js.map