@rebasepro/cli 0.25.0 → 0.25.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.
Files changed (125) hide show
  1. package/dist/{agent-setup-B5AKHOgt.js → agent-setup-BYb2Tbdz.js} +2 -2
  2. package/dist/{agent-setup-B5AKHOgt.js.map → agent-setup-BYb2Tbdz.js.map} +1 -1
  3. package/dist/{api-keys-Tm7zvAPe.js → api-keys-CSMbsmAn.js} +3 -4
  4. package/dist/{api-keys-Tm7zvAPe.js.map → api-keys-CSMbsmAn.js.map} +1 -1
  5. package/dist/{apps-BsU_ILbP.js → apps-iBXNZhVN.js} +6 -7
  6. package/dist/{apps-BsU_ILbP.js.map → apps-iBXNZhVN.js.map} +1 -1
  7. package/dist/{atlas-dev-database-OWtqnG7W.js → atlas-dev-database-BusbHP6n.js} +4 -4
  8. package/dist/{atlas-dev-database-OWtqnG7W.js.map → atlas-dev-database-BusbHP6n.js.map} +1 -1
  9. package/dist/{auth-Vl2hFKXP.js → auth-DRsTWPky.js} +6 -7
  10. package/dist/{auth-Vl2hFKXP.js.map → auth-DRsTWPky.js.map} +1 -1
  11. package/dist/{branch-pointer-9DnwkFJJ.js → branch-pointer-Bn_FjNld.js} +1 -1
  12. package/dist/{build-BBgT8EJZ.js → build-et7Rccxi.js} +6 -7
  13. package/dist/{build-BBgT8EJZ.js.map → build-et7Rccxi.js.map} +1 -1
  14. package/dist/{bundle-DtCCCr4w.js → bundle-DlqkDYSe.js} +3 -3
  15. package/dist/{bundle-DtCCCr4w.js.map → bundle-DlqkDYSe.js.map} +1 -1
  16. package/dist/{cloud-BpH0_bmV.js → cloud-B-ZIjHrA.js} +10 -11
  17. package/dist/{cloud-BpH0_bmV.js.map → cloud-B-ZIjHrA.js.map} +1 -1
  18. package/dist/commands/dev.d.ts +12 -0
  19. package/dist/commands/generate_sdk.d.ts +4 -2
  20. package/dist/commands/init.d.ts +20 -0
  21. package/dist/commands/start.d.ts +10 -0
  22. package/dist/commands/upgrade.d.ts +12 -0
  23. package/dist/{consent-ClRHmcxf.js → consent-BaB-eQKG.js} +2 -2
  24. package/dist/{consent-ClRHmcxf.js.map → consent-BaB-eQKG.js.map} +1 -1
  25. package/dist/{console-CEGm0XNc.js → console-BlD9kjFW.js} +4 -4
  26. package/dist/{console-CEGm0XNc.js.map → console-BlD9kjFW.js.map} +1 -1
  27. package/dist/{context-CyflUrKg.js → context-DHiiiJ5C.js} +3 -4
  28. package/dist/context-DHiiiJ5C.js.map +1 -0
  29. package/dist/{daemon-CknkDjd6.js → daemon-DDMRbz4T.js} +160 -5
  30. package/dist/daemon-DDMRbz4T.js.map +1 -0
  31. package/dist/{daemon-entry-CbLRrgzy.js → daemon-entry-zOC1revO.js} +6 -3
  32. package/dist/daemon-entry-zOC1revO.js.map +1 -0
  33. package/dist/{db-CRNpA5mH.js → db-9P6MnmdC.js} +90 -50
  34. package/dist/db-9P6MnmdC.js.map +1 -0
  35. package/dist/{derive-CsoFGAnK.js → derive-MTdClnkc.js} +2 -2
  36. package/dist/{derive-CsoFGAnK.js.map → derive-MTdClnkc.js.map} +1 -1
  37. package/dist/{dev-C0pbMmhS.js → dev-BDN_OKXA.js} +183 -23
  38. package/dist/dev-BDN_OKXA.js.map +1 -0
  39. package/dist/dev-db/daemon.d.ts +41 -0
  40. package/dist/dev-db/pull.d.ts +33 -5
  41. package/dist/dev-db/state.d.ts +9 -0
  42. package/dist/{dev-preflight-unHpiYO1.js → dev-preflight-FT9GbJVj.js} +73 -27
  43. package/dist/dev-preflight-FT9GbJVj.js.map +1 -0
  44. package/dist/{dev-server-B1UYifZJ.js → dev-server-CIZwAfZa.js} +14 -3
  45. package/dist/dev-server-CIZwAfZa.js.map +1 -0
  46. package/dist/{doctor-CATRdWm6.js → doctor-D8YXz7df.js} +6 -6
  47. package/dist/doctor-D8YXz7df.js.map +1 -0
  48. package/dist/{eject-DuasTr-F.js → eject-izG2KjMo.js} +3 -4
  49. package/dist/{eject-DuasTr-F.js.map → eject-izG2KjMo.js.map} +1 -1
  50. package/dist/{fold-static-hDHtDfTq.js → fold-static-CybSay_W.js} +3 -3
  51. package/dist/{fold-static-hDHtDfTq.js.map → fold-static-CybSay_W.js.map} +1 -1
  52. package/dist/{generate_sdk-Cvg5jjJF.js → generate_sdk-B4R65oUV.js} +21 -11
  53. package/dist/generate_sdk-B4R65oUV.js.map +1 -0
  54. package/dist/index.es.js +36 -37
  55. package/dist/index.es.js.map +1 -1
  56. package/dist/{init-BWAwHPtz.js → init-DYaeppEN.js} +50 -62
  57. package/dist/init-DYaeppEN.js.map +1 -0
  58. package/dist/invoked-command-CG_rIa_o.js +55 -0
  59. package/dist/invoked-command-CG_rIa_o.js.map +1 -0
  60. package/dist/last-dev-database-B_75fxZC.js +2 -0
  61. package/dist/{manifest-DRp0UVTj.js → manifest-nvPdLlJY.js} +2 -2
  62. package/dist/{manifest-DRp0UVTj.js.map → manifest-nvPdLlJY.js.map} +1 -1
  63. package/dist/{normalize-imports-BoBVh5tU.js → normalize-imports-BYw8ad87.js} +3 -3
  64. package/dist/{normalize-imports-BoBVh5tU.js.map → normalize-imports-BYw8ad87.js.map} +1 -1
  65. package/dist/{prepare-DdnU70z8.js → prepare-1rVNd2PF.js} +5 -4
  66. package/dist/{prepare-DdnU70z8.js.map → prepare-1rVNd2PF.js.map} +1 -1
  67. package/dist/project-g4K6kV1t.js +701 -0
  68. package/dist/project-g4K6kV1t.js.map +1 -0
  69. package/dist/{pull-C3PXduzl.js → pull-DDMpsZ5r.js} +131 -24
  70. package/dist/pull-DDMpsZ5r.js.map +1 -0
  71. package/dist/refusal-ReeeGP8a.js +26 -0
  72. package/dist/refusal-ReeeGP8a.js.map +1 -0
  73. package/dist/resolve-BQUAC3Wi.js +2 -0
  74. package/dist/{resources-BdNRDEAy.js → resources-CovP8AZ-.js} +4 -5
  75. package/dist/{resources-BdNRDEAy.js.map → resources-CovP8AZ-.js.map} +1 -1
  76. package/dist/{schema-B0-p6T1O.js → schema-nuxvvc50.js} +5 -6
  77. package/dist/{schema-B0-p6T1O.js.map → schema-nuxvvc50.js.map} +1 -1
  78. package/dist/{skills-DfnTeyRz.js → skills-DeUIsZcT.js} +3 -4
  79. package/dist/{skills-DfnTeyRz.js.map → skills-DeUIsZcT.js.map} +1 -1
  80. package/dist/{start-BmEZkS0n.js → start-RzZqDroW.js} +20 -7
  81. package/dist/start-RzZqDroW.js.map +1 -0
  82. package/dist/{state-CCB-FAQ9.js → state-CYdBvnwA.js} +3 -2
  83. package/dist/state-CYdBvnwA.js.map +1 -0
  84. package/dist/{status-BJ26yZOo.js → status-D9Zm88NZ.js} +8 -9
  85. package/dist/{status-BJ26yZOo.js.map → status-D9Zm88NZ.js.map} +1 -1
  86. package/dist/{storage-DKPUBTWG.js → storage-BB3umRfw.js} +3 -4
  87. package/dist/{storage-DKPUBTWG.js.map → storage-BB3umRfw.js.map} +1 -1
  88. package/dist/{telemetry-EMFRVRQi.js → telemetry-HfU5uAh1.js} +2 -2
  89. package/dist/{telemetry-EMFRVRQi.js.map → telemetry-HfU5uAh1.js.map} +1 -1
  90. package/dist/{telemetry-m9czTNGR.js → telemetry-TseNLk6X.js} +4 -4
  91. package/dist/{telemetry-m9czTNGR.js.map → telemetry-TseNLk6X.js.map} +1 -1
  92. package/dist/{upgrade-DjihWuZL.js → upgrade-apiPKKE_.js} +26 -4
  93. package/dist/{upgrade-DjihWuZL.js.map → upgrade-apiPKKE_.js.map} +1 -1
  94. package/dist/utils/args.d.ts +32 -3
  95. package/dist/utils/boot-retry.d.ts +41 -0
  96. package/dist/utils/dev-preflight.d.ts +34 -1
  97. package/dist/utils/dev-server.d.ts +11 -0
  98. package/dist/utils/project.d.ts +5 -1
  99. package/dist/utils/unknown-command.d.ts +4 -0
  100. package/package.json +7 -7
  101. package/templates/resource-graphs/blank.json +25 -0
  102. package/templates/resource-graphs/blog.json +28 -0
  103. package/templates/resource-graphs/ecommerce.json +28 -0
  104. package/templates/resource-graphs/headless.json +22 -0
  105. package/templates/template/.env.example +5 -4
  106. package/templates/template/frontend/src/App.tsx +10 -9
  107. package/dist/args-BX0YD14a.js +0 -266
  108. package/dist/args-BX0YD14a.js.map +0 -1
  109. package/dist/context-CyflUrKg.js.map +0 -1
  110. package/dist/daemon-CknkDjd6.js.map +0 -1
  111. package/dist/daemon-entry-CbLRrgzy.js.map +0 -1
  112. package/dist/db-CRNpA5mH.js.map +0 -1
  113. package/dist/dev-C0pbMmhS.js.map +0 -1
  114. package/dist/dev-preflight-unHpiYO1.js.map +0 -1
  115. package/dist/dev-server-B1UYifZJ.js.map +0 -1
  116. package/dist/doctor-CATRdWm6.js.map +0 -1
  117. package/dist/generate_sdk-Cvg5jjJF.js.map +0 -1
  118. package/dist/init-BWAwHPtz.js.map +0 -1
  119. package/dist/last-dev-database-c_dKz1S3.js +0 -2
  120. package/dist/project-Wp2zoyeN.js +0 -349
  121. package/dist/project-Wp2zoyeN.js.map +0 -1
  122. package/dist/pull-C3PXduzl.js.map +0 -1
  123. package/dist/resolve-CNQjsatH.js +0 -2
  124. package/dist/start-BmEZkS0n.js.map +0 -1
  125. package/dist/state-CCB-FAQ9.js.map +0 -1
@@ -0,0 +1,701 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { n as getPMCommands, t as detectPackageManager } from "./package-manager-DSEOwTqM.js";
3
+ import chalk from "chalk";
4
+ import arg from "arg";
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { pathToFileURL } from "url";
8
+ import { createRequire } from "module";
9
+ import { execSync } from "child_process";
10
+ import dotenv from "dotenv";
11
+ //#region src/utils/redact.ts
12
+ /**
13
+ * Connection-string passwords, replaced before a line is printed.
14
+ *
15
+ * A refusal that repeats what was typed repeats a password with it, and these
16
+ * lines land in terminals, CI logs and issues. `user:***@` is the form every
17
+ * other message uses: the driver's `redactConnectionPasswords` in
18
+ * `@rebasepro/server-postgres`, which its entry does not export, and this
19
+ * mirrors. Covers the user-info password and a `password=` parameter.
20
+ */
21
+ function redactConnectionPasswords(text) {
22
+ return text.replace(/(\b[a-z][a-z0-9+.-]*:\/\/[^\s/?#@'"]*):[^\s/?#'"]*@/gi, "$1:***@").replace(/(\bpassword\s*=\s*)('[^']*'|[^\s&'"]+)/gi, "$1***");
23
+ }
24
+ //#endregion
25
+ //#region src/utils/unknown-command.ts
26
+ /**
27
+ * What every command family says when it does not recognise a subcommand.
28
+ *
29
+ * Six families each did it differently, and the differences were not stylistic:
30
+ *
31
+ * rebase statsu "Unknown command: statsu" + the whole top-level help
32
+ * rebase auth foo "Unknown auth command: foo" + the whole auth help
33
+ * rebase apps foo "Unknown subcommand: foo" — which command's?
34
+ * rebase skills foo "Unknown skills subcommand: foo" + the whole help
35
+ * rebase telemetry x the help, silently, with exit code 1 and no error line
36
+ * rebase cloud debug x both a JSON error *and* the human one, in JSON mode
37
+ *
38
+ * Dumping a screen of help after an error is the wrong shape: the one line that
39
+ * says what happened scrolls off the top, and in a CI log it is buried in
40
+ * forty. None of them offered the correction, which for a typo is the entire
41
+ * answer — `statsu` is one transposition from `status`.
42
+ *
43
+ * So: one line, the near-miss when there is one, and where to read the rest.
44
+ */
45
+ /**
46
+ * Damerau–Levenshtein distance, capped by the caller.
47
+ *
48
+ * The transposition is the reason this is not plain Levenshtein: `statsu` for
49
+ * `status` and `craete` for `create` are the two most common typing errors
50
+ * there are, and both are distance 2 without it — the same as two unrelated
51
+ * edits, which is too far to suggest anything at all.
52
+ */
53
+ function editDistance(a, b) {
54
+ const rows = a.length + 1;
55
+ const cols = b.length + 1;
56
+ const d = Array.from({ length: rows }, () => new Array(cols).fill(0));
57
+ for (let i = 0; i < rows; i++) d[i][0] = i;
58
+ for (let j = 0; j < cols; j++) d[0][j] = j;
59
+ for (let i = 1; i < rows; i++) for (let j = 1; j < cols; j++) {
60
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
61
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
62
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
63
+ }
64
+ return d[a.length][b.length];
65
+ }
66
+ /**
67
+ * The one command the user probably meant, or nothing.
68
+ *
69
+ * Tight on purpose. A suggestion that is wrong is worse than none: it sends
70
+ * someone to read the help for a command that was never the answer, and on a
71
+ * family like `db` — where `push` and `pull` are one edit apart — a loose
72
+ * threshold would confidently propose the destructive neighbour of what they
73
+ * typed. So: one edit for a short word, two for a long one, and a prefix match
74
+ * always counts (`rebase cloud dep` for `deploy`).
75
+ */
76
+ function suggestCommand(typed, known) {
77
+ const word = typed.toLowerCase();
78
+ if (!word) return void 0;
79
+ if (word.length >= 3) {
80
+ const prefixed = known.filter((name) => name.toLowerCase().startsWith(word));
81
+ if (prefixed.length === 1) return prefixed[0];
82
+ }
83
+ const budget = word.length <= 4 ? 1 : 2;
84
+ let best;
85
+ for (const candidate of known) {
86
+ const distance = editDistance(word, candidate.toLowerCase());
87
+ if (distance > budget) continue;
88
+ if (!best || distance < best.distance) best = {
89
+ name: candidate,
90
+ distance
91
+ };
92
+ }
93
+ return best?.name;
94
+ }
95
+ /**
96
+ * The family command a word names exactly, as `<family> <command>`, or nothing.
97
+ *
98
+ * `rebase deploy` is `rebase cloud deploy`: the word is a whole command one
99
+ * level down. Exact only — a near miss there is a guess about both the family
100
+ * and the command.
101
+ */
102
+ function familyCommand(typed, families) {
103
+ const word = typed.toLowerCase();
104
+ for (const [family, commands] of Object.entries(families)) {
105
+ const match = commands.find((name) => name.toLowerCase() === word);
106
+ if (match) return `${family} ${match}`;
107
+ }
108
+ }
109
+ /**
110
+ * The line itself, without printing it — so callers that must answer in JSON
111
+ * (the cloud family, whose `fail()` writes a machine-readable envelope) use the
112
+ * same words as the ones that print.
113
+ *
114
+ * `family` is empty for the top level: `rebase --help`, not `rebase --help`.
115
+ * `families` are the command families below this level, by name, whose
116
+ * commands a word may be one of.
117
+ *
118
+ * The typed word is repeated with any connection-string password masked: a URL
119
+ * pasted where the command goes (`rebase db "postgres://u:pw@h/db"`) is a
120
+ * word like any other here, and this line lands in terminals and CI logs.
121
+ */
122
+ function unknownCommandMessage(typed, known, family = "", families = {}) {
123
+ const what = family ? `${family} command` : "command";
124
+ const suggestion = typed ? suggestCommand(typed, known) ?? familyCommand(typed, families) : void 0;
125
+ const help = family ? `rebase ${family} --help` : "rebase --help";
126
+ const shown = redactConnectionPasswords(typed ?? "");
127
+ return suggestion ? `Unknown ${what} "${shown}" — did you mean \`${suggestion}\`? Run \`${help}\` for the rest.` : `Unknown ${what} "${shown}". Run \`${help}\` for the ones there are.`;
128
+ }
129
+ /**
130
+ * Print the line to stderr and exit 1.
131
+ *
132
+ * stderr, not stdout: a mistyped command is a failure, and the families with a
133
+ * `--json` mode guarantee that stdout holds only their envelope.
134
+ */
135
+ function unknownCommand(typed, known, family = "", families = {}) {
136
+ console.error(chalk.red(`✗ ${unknownCommandMessage(typed, known, family, families)}`));
137
+ process.exit(1);
138
+ }
139
+ //#endregion
140
+ //#region src/utils/args.ts
141
+ /**
142
+ * Argument parsing for the commands that take positional arguments.
143
+ *
144
+ * Every command in the small group used to parse its own line the same way:
145
+ * `arg(spec, { argv: rawArgs.slice(4), permissive: true })`, then read `_[0]`
146
+ * and `_[1]`. Both halves of that are wrong in a way that costs data.
147
+ *
148
+ * - **`permissive: true` turns an unknown flag into a positional.** `arg`
149
+ * pushes an undeclared flag into `_` as a bare token, so `_[1]` is whatever
150
+ * came second on the line, flag or not. `rebase auth reset-password
151
+ * bob@example.com --debug` set Bob's password to the literal `--debug` — and
152
+ * `--debug` is what `bin/rebase.js` prints after *every* failure as the thing
153
+ * to re-run with, so the single most likely next keystroke after a failed
154
+ * reset was the one that reset the account to a two-word string.
155
+ * - **`slice(4)` assumes the command words are at fixed indices.** They are
156
+ * not: a flag before the command shifts everything, so `rebase --debug auth
157
+ * reset-password bob@example.com NewPass1!` read the email as
158
+ * `reset-password` and the password as `bob@example.com`.
159
+ *
160
+ * So: parse the *whole* line — `rawArgs` is `process.argv` — against a spec
161
+ * strictly, with no permissive mode. `arg` then consumes every flag wherever it
162
+ * appears and rejects the ones nobody declared, which leaves `_` holding the
163
+ * command words followed by the real positionals, in order and at a known
164
+ * offset. An unrecognised flag becomes an error naming the command's help,
165
+ * which is the only safe answer: the alternative is guessing that it was meant
166
+ * as a value.
167
+ *
168
+ * `commands/cloud/index.ts` resolves its positionals against its own spec for
169
+ * the same reason; this is that idea for the commands whose positionals are
170
+ * credentials rather than resource names.
171
+ */
172
+ /**
173
+ * Flags accepted on top of whatever a command declares.
174
+ *
175
+ * `--debug` is read by `bin/rebase.js` off `process.argv` and never by a
176
+ * command, but it has to be *declared* somewhere or strict parsing rejects the
177
+ * exact flag the CLI tells people to add. `--help`/`-h` are answered by each
178
+ * command's dispatcher before any work happens.
179
+ */
180
+ var GLOBAL_COMMAND_FLAGS = {
181
+ "--debug": Boolean,
182
+ "--help": Boolean,
183
+ "-h": "--help"
184
+ };
185
+ /**
186
+ * The declared long flag an undeclared one was probably meant to be: one edit
187
+ * or one swap away (`--form` → `--from`), or an abbreviation of exactly one
188
+ * (`--anon` → `--anonymize`).
189
+ */
190
+ function closestDeclaredFlag(typed, declared) {
191
+ if (!typed.startsWith("--")) return void 0;
192
+ const near = suggestCommand(typed.slice(2), declared.filter((flag) => flag.startsWith("--")).map((flag) => flag.slice(2)));
193
+ return near ? `--${near}` : void 0;
194
+ }
195
+ /** Past this many, a refusal names the help page rather than listing every flag. */
196
+ var LISTED_FLAGS_MAX = 8;
197
+ /**
198
+ * The sentence an undeclared flag is refused with — the one `db backup`,
199
+ * `db restore` and `db branch` say, so one CLI answers a typo one way:
200
+ *
201
+ * `rebase db pull` has no option --anonymise — did you mean --anonymize?
202
+ * Nothing was run. See `rebase db pull --help`.
203
+ *
204
+ * With nothing close it lists what the command takes, when that list is short
205
+ * enough to read.
206
+ *
207
+ * `typed` is the argument as `arg` refused it, and only its flag part is
208
+ * repeated: a value given inline (`--fro=postgres://u:pw@h/db`) is not. One
209
+ * argument holding a flag and its value, quoted together, is named as that,
210
+ * and what it quotes is password-masked: `"--fro postgres://u:pw@h/db"` was
211
+ * refused with the whole URL, password included, in the middle of a garbled
212
+ * sentence.
213
+ *
214
+ * Exported for the commands that refuse a flag outside `parseCommandArgs`.
215
+ */
216
+ function unknownOptionMessage(command, typed, spec) {
217
+ const declared = Object.keys({
218
+ ...GLOBAL_COMMAND_FLAGS,
219
+ ...spec
220
+ });
221
+ const name = typed.split(/[=\s]/)[0];
222
+ const near = closestDeclaredFlag(name, declared);
223
+ const tail = ` Nothing was run. See \`rebase ${command} --help\`.`;
224
+ if (/\s/.test(typed.trim())) {
225
+ const flag = declared.includes(name) ? name : near;
226
+ const how = flag ? ` Write them as two arguments, ${flag} <value>, or as ${flag}=<value>.` : " Write a flag and its value as two arguments.";
227
+ return `\`rebase ${command}\` has no option "${redactConnectionPasswords(typed)}": that is a flag and a value in one argument, quoted together.${how}${tail}`;
228
+ }
229
+ const own = Object.keys(spec).filter((flag) => flag.startsWith("--") && !(flag in GLOBAL_COMMAND_FLAGS));
230
+ return `\`rebase ${command}\` has no option ${name}${near ? ` — did you mean ${near}?` : own.length === 0 ? " — it takes no options." : own.length <= LISTED_FLAGS_MAX ? ` — it takes ${own.join(", ")}.` : "."}${tail}`;
231
+ }
232
+ /** What a flag resolves to, through its alias (`"-y": "--yes"`). */
233
+ function handlerOf(spec, flag) {
234
+ const declared = spec[flag];
235
+ return typeof declared === "string" ? spec[declared] : declared;
236
+ }
237
+ /** A flag that takes no value: on when given, off when left out. */
238
+ function isSwitch(handler) {
239
+ return handler === Boolean || Array.isArray(handler) && handler[0] === Boolean;
240
+ }
241
+ /** The values a switch reads as on, and as off, after its `=`. */
242
+ var SWITCH_ON = /^(?:true|1)$/i;
243
+ var SWITCH_OFF = /^(?:false|0)$/i;
244
+ /**
245
+ * A line with every `--switch=value` resolved: `=true` or `=1` is the switch
246
+ * alone, `=false` or `=0` drops it, and any other value is refused.
247
+ *
248
+ * `arg` drops the value of a flag that takes none and turns the flag on, so
249
+ * `--anonymize=false` anonymized and `--yes=false` answered yes. `=true` kept
250
+ * working by that same accident, and scripts write it, so the two readings a
251
+ * value can have are honoured and nothing else is guessed at. Past `--` every
252
+ * word is a positional, so nothing there is read.
253
+ *
254
+ * @throws UsageError naming the switch and the values it takes.
255
+ */
256
+ function resolveSwitchValues(spec, argv, command) {
257
+ const resolved = [];
258
+ for (let index = 0; index < argv.length; index += 1) {
259
+ const word = argv[index];
260
+ if (word === "--") {
261
+ resolved.push(...argv.slice(index));
262
+ break;
263
+ }
264
+ const flag = word.startsWith("--") && word.includes("=") ? word.slice(0, word.indexOf("=")) : null;
265
+ if (flag === null || !isSwitch(handlerOf(spec, flag))) {
266
+ resolved.push(word);
267
+ continue;
268
+ }
269
+ const value = word.slice(flag.length + 1);
270
+ if (SWITCH_ON.test(value)) resolved.push(flag);
271
+ else if (!SWITCH_OFF.test(value)) throw new UsageError(switchValueMessage(command, flag, word));
272
+ }
273
+ return resolved;
274
+ }
275
+ /**
276
+ * Whether a switch is on in a line read by hand: given alone, or as `=true`
277
+ * or `=1`, before any `--`. The reading {@link resolveSwitchValues} gives the
278
+ * parsed flags, for the code that looks at `rawArgs` itself.
279
+ */
280
+ function switchOn(rawArgs, flag) {
281
+ for (const word of rawArgs) {
282
+ if (word === "--") return false;
283
+ if (word === flag) return true;
284
+ if (word.startsWith(`${flag}=`) && SWITCH_ON.test(word.slice(flag.length + 1))) return true;
285
+ }
286
+ return false;
287
+ }
288
+ /** The sentence a switch given a value it cannot read is refused with. */
289
+ function switchValueMessage(command, flag, typed) {
290
+ return `\`rebase ${command}\` cannot read ${redactConnectionPasswords(typed)}: ${flag} is a switch, and after its = it takes true or 1 to turn it on, false or 0 to turn it off. Nothing was run. See \`rebase ${command} --help\`.`;
291
+ }
292
+ /** Did the line ask for help? Answered before dispatch, never by a handler. */
293
+ function wantsHelp(rawArgs) {
294
+ return rawArgs.includes("--help") || rawArgs.includes("-h");
295
+ }
296
+ /**
297
+ * The command line was wrong — nothing ran, and there is no stack to want.
298
+ *
299
+ * `bin/rebase.js` ends every failure with "Re-run with --debug for the stack
300
+ * trace.", which is right when something broke inside a command and absurd for
301
+ * a typo: the stack points at `arg` and this file, neither of which the person
302
+ * who mistyped `--ouput` has any interest in. Worse, `--debug` is a flag, so
303
+ * the suggested next command is *another* command line — and the one thing we
304
+ * have just established is that the command line is what went wrong.
305
+ *
306
+ * Marked with a property rather than left to `instanceof`, because the reader
307
+ * is `bin/rebase.js`: it imports the bundle, so the class object it would test
308
+ * against is not the one this file compiled to.
309
+ */
310
+ var UsageError = class extends Error {
311
+ isUsageError = true;
312
+ constructor(message) {
313
+ super(message);
314
+ this.name = "UsageError";
315
+ }
316
+ };
317
+ /**
318
+ * Resolve a command's flags and positionals from the full `process.argv`.
319
+ *
320
+ * `commandWords` is how many words name the command itself — 2 for
321
+ * `auth reset-password`, 1 for `start` — and is applied to the *parsed*
322
+ * positionals rather than to `argv`, so a flag placed before the command no
323
+ * longer shifts them.
324
+ *
325
+ * `command` names the command in error messages, e.g. `auth reset-password`.
326
+ *
327
+ * Throws on an unknown flag, on a switch given a value other than true,
328
+ * false, 1 or 0 (`--yes=maybe`), on a positional that looks like a flag, and
329
+ * on more positionals than the command takes. `bin/rebase.js` turns each into
330
+ * a one-line `✗ …` and exit 1.
331
+ */
332
+ function parseCommandArgs({ spec, rawArgs, commandWords, command, maxPositionals }) {
333
+ let parsed;
334
+ const merged = {
335
+ ...GLOBAL_COMMAND_FLAGS,
336
+ ...spec
337
+ };
338
+ const argv = resolveSwitchValues(merged, rawArgs.slice(2), command);
339
+ try {
340
+ parsed = arg(merged, { argv });
341
+ } catch (err) {
342
+ if (err instanceof Error && err.code === "ARG_UNKNOWN_OPTION") {
343
+ const named = /option: ([\s\S]+)$/.exec(err.message)?.[1] ?? "";
344
+ throw new UsageError(unknownOptionMessage(command, rawArgs.slice(2).find((word) => word === named || word.startsWith(`${named}=`)) ?? named, spec));
345
+ }
346
+ throw err;
347
+ }
348
+ const positionals = parsed._.slice(commandWords);
349
+ for (const value of positionals) if (value.startsWith("-")) throw new UsageError(`\`${redactConnectionPasswords(value)}\` looks like an option, not a value — pass it with an explicit flag (\`rebase ${command} --help\`).`);
350
+ if (maxPositionals !== void 0 && positionals.length > maxPositionals) throw new UsageError(`rebase ${command} takes ${maxPositionals} argument${maxPositionals === 1 ? "" : "s"}, got ${positionals.length}: ${redactConnectionPasswords(positionals.join(" "))}`);
351
+ return {
352
+ flags: parsed,
353
+ positionals,
354
+ help: Boolean(parsed["--help"])
355
+ };
356
+ }
357
+ //#endregion
358
+ //#region src/utils/project.ts
359
+ /**
360
+ * Project discovery utilities for the Rebase CLI.
361
+ *
362
+ * These helpers locate the project root, backend directory, .env file,
363
+ * and local binaries — used by all CLI command modules.
364
+ */
365
+ var project_exports = /* @__PURE__ */ __exportAll({
366
+ MANIFEST_FILENAME: () => MANIFEST_FILENAME,
367
+ dependenciesNotInstalled: () => dependenciesNotInstalled,
368
+ exitDependenciesNotInstalled: () => exitDependenciesNotInstalled,
369
+ failAsJson: () => failAsJson,
370
+ findBackendDir: () => findBackendDir,
371
+ findEnvFile: () => findEnvFile,
372
+ findFrontendDir: () => findFrontendDir,
373
+ findProjectRoot: () => findProjectRoot,
374
+ getActiveBackendPlugin: () => getActiveBackendPlugin,
375
+ readEnvFile: () => readEnvFile,
376
+ requireBackendDir: () => requireBackendDir,
377
+ requireProjectRoot: () => requireProjectRoot,
378
+ resolveLocalBin: () => resolveLocalBin,
379
+ resolvePluginCliScript: () => resolvePluginCliScript,
380
+ resolveTsx: () => resolveTsx,
381
+ resolveTsxLoader: () => resolveTsxLoader,
382
+ validateTsxInstallation: () => validateTsxInstallation,
383
+ wantsJsonOutput: () => wantsJsonOutput
384
+ });
385
+ /** The authored project manifest. Its presence alone marks a project root. */
386
+ var MANIFEST_FILENAME = "rebase.json";
387
+ /**
388
+ * Walk up from `startDir` to find the Rebase project root.
389
+ *
390
+ * A directory is the root when it holds a `rebase.json`, or when it holds a
391
+ * `package.json` that either lists `backend` as a workspace or sits beside both
392
+ * `backend/` and `config/`.
393
+ *
394
+ * `rebase.json` is checked first and needs no `package.json` beside it, because
395
+ * the conventions below all describe a repository that *contains the backend*.
396
+ * A repository holding only a frontend — the normal shape once a project's apps
397
+ * live in separate repositories — matches none of them, so without this the
398
+ * tooling could not run there at all.
399
+ */
400
+ function findProjectRoot(startDir = process.cwd()) {
401
+ let dir = path.resolve(startDir);
402
+ const root = path.parse(dir).root;
403
+ while (dir !== root) {
404
+ if (fs.existsSync(path.join(dir, "rebase.json"))) return dir;
405
+ const pkgPath = path.join(dir, "package.json");
406
+ if (fs.existsSync(pkgPath)) {
407
+ try {
408
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
409
+ if (pkg.workspaces && Array.isArray(pkg.workspaces)) {
410
+ if (pkg.workspaces.some((w) => w === "backend")) return dir;
411
+ }
412
+ } catch {}
413
+ if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "config"))) return dir;
414
+ }
415
+ dir = path.dirname(dir);
416
+ }
417
+ return null;
418
+ }
419
+ /**
420
+ * Locate the backend directory within the project root.
421
+ */
422
+ function findBackendDir(projectRoot) {
423
+ const backendDir = path.join(projectRoot, "backend");
424
+ return fs.existsSync(backendDir) ? backendDir : null;
425
+ }
426
+ /**
427
+ * Detect the active backend plugin (e.g. @rebasepro/server-postgres) from the backend's package.json.
428
+ */
429
+ function getActiveBackendPlugin(backendDir) {
430
+ const pkgPath = path.join(backendDir, "package.json");
431
+ if (!fs.existsSync(pkgPath)) return null;
432
+ try {
433
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
434
+ const deps = {
435
+ ...pkg.dependencies,
436
+ ...pkg.devDependencies
437
+ };
438
+ const candidates = Object.keys(deps).filter((dep) => dep.startsWith("@rebasepro/server-") && dep !== "@rebasepro/server");
439
+ if (candidates.length === 0) return null;
440
+ if (candidates.includes("@rebasepro/server-postgres")) return "@rebasepro/server-postgres";
441
+ for (const candidate of candidates) if (resolvePluginCliScript(backendDir, candidate)) return candidate;
442
+ return candidates[0];
443
+ } catch {}
444
+ return null;
445
+ }
446
+ /**
447
+ * Resolve the active plugin's CLI script.
448
+ */
449
+ function resolvePluginCliScript(backendDir, pluginName) {
450
+ const candidates = [];
451
+ let dir = path.resolve(backendDir);
452
+ const fsRoot = path.parse(dir).root;
453
+ while (dir !== fsRoot) {
454
+ candidates.push(path.join(dir, "node_modules", pluginName, "src", "cli.ts"), path.join(dir, "node_modules", pluginName, "dist", "cli.js"));
455
+ dir = path.dirname(dir);
456
+ }
457
+ candidates.push(path.resolve(backendDir, "..", "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"), path.resolve(backendDir, "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"), path.resolve(backendDir, "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"));
458
+ for (const candidate of candidates) if (fs.existsSync(candidate)) return candidate;
459
+ return null;
460
+ }
461
+ /**
462
+ * Locate the frontend directory within the project root.
463
+ */
464
+ function findFrontendDir(projectRoot) {
465
+ const frontendDir = path.join(projectRoot, "frontend");
466
+ return fs.existsSync(frontendDir) ? frontendDir : null;
467
+ }
468
+ /**
469
+ * Find the .env file. Checks the project root first, then backend.
470
+ */
471
+ function findEnvFile(projectRoot) {
472
+ const candidates = [path.join(projectRoot, ".env"), path.join(projectRoot, "backend", ".env")];
473
+ for (const candidate of candidates) if (fs.existsSync(candidate)) return candidate;
474
+ return null;
475
+ }
476
+ /**
477
+ * Read the project's `.env` into a plain object.
478
+ *
479
+ * One reader, because there were four: `dotenv` in `start`, a hand-rolled
480
+ * `indexOf("=")` loop in `api-keys`, a single-key regex in `auth`, and its own
481
+ * splitting in `cloud env`. `dotenv` is a declared dependency of this package,
482
+ * so the other three existed for no reason and disagreed with the correct one
483
+ * on the two things people actually write in a `.env`:
484
+ *
485
+ * - `export KEY=value`, which the hand-rolled parser keyed as
486
+ * `export KEY` — so the command reported the key as unset while it was
487
+ * right there in the file;
488
+ * - `KEY=value # comment`, whose comment travelled into the value and then
489
+ * into an `Authorization` header, coming back as a 401 with nothing
490
+ * pointing at the cause.
491
+ *
492
+ * Returns `{}` when the project has no `.env`, so callers can treat "absent"
493
+ * and "empty" alike.
494
+ */
495
+ function readEnvFile(projectRoot) {
496
+ const envFile = findEnvFile(projectRoot);
497
+ if (!envFile || !fs.existsSync(envFile)) return {};
498
+ try {
499
+ return dotenv.parse(fs.readFileSync(envFile, "utf-8"));
500
+ } catch {
501
+ return {};
502
+ }
503
+ }
504
+ /**
505
+ * Resolve a binary from the project's node_modules/.bin.
506
+ * Checks backend, root, parent monorepo root, then falls back to PATH.
507
+ */
508
+ function resolveLocalBin(projectRoot, binName) {
509
+ const candidates = [path.join(projectRoot, "backend", "node_modules", ".bin", binName), path.join(projectRoot, "node_modules", ".bin", binName)];
510
+ let parent = path.dirname(projectRoot);
511
+ const rootDir = path.parse(parent).root;
512
+ while (parent !== rootDir) {
513
+ candidates.push(path.join(parent, "node_modules", ".bin", binName));
514
+ parent = path.dirname(parent);
515
+ }
516
+ for (const candidate of candidates) if (fs.existsSync(candidate)) return candidate;
517
+ try {
518
+ const globalPath = execSync(`which ${binName}`, { encoding: "utf-8" }).trim();
519
+ if (globalPath && fs.existsSync(globalPath)) return globalPath;
520
+ } catch {}
521
+ return null;
522
+ }
523
+ /**
524
+ * Resolve the tsx binary. Checks backend node_modules first, then root.
525
+ */
526
+ function resolveTsx(projectRoot) {
527
+ return resolveLocalBin(projectRoot, "tsx");
528
+ }
529
+ /**
530
+ * tsx's loader, as the URL `node --import` takes, or null when none resolves.
531
+ *
532
+ * For running a TypeScript entry in one Node process. The `tsx` binary is a
533
+ * relay — pnpm's `.bin/tsx` shell shim, then tsx's CLI in one Node, which
534
+ * spawns a second Node with this same loader to run the script — so spawning
535
+ * through it costs a shell and a Node start (~50–100 ms) on every hop, and
536
+ * `rebase dev` pays a hop on every start and every collection save.
537
+ *
538
+ * Resolved the way Node resolves the package, from the project first — the
539
+ * backend, where a scaffold declares tsx, then the root and everything above it
540
+ * — so the tsx used is the one {@link resolveTsx} would have found; then from
541
+ * this package, as the repository's own tests resolve `tsx/cli`. A URL rather
542
+ * than a path: a Windows path is not a valid `--import` specifier.
543
+ */
544
+ function resolveTsxLoader(projectRoot) {
545
+ const bases = [
546
+ path.join(projectRoot, "backend", "noop.js"),
547
+ path.join(projectRoot, "noop.js"),
548
+ import.meta.url
549
+ ];
550
+ for (const base of bases) try {
551
+ return pathToFileURL(createRequire(base).resolve("tsx")).href;
552
+ } catch {}
553
+ return null;
554
+ }
555
+ /**
556
+ * Validate that a resolved tsx binary actually has an intact installation.
557
+ *
558
+ * `resolveLocalBin` only checks whether `node_modules/.bin/tsx` (a symlink)
559
+ * exists. If the pnpm content-addressable store was cleaned or a previous
560
+ * install was interrupted, the symlink can exist while critical files inside
561
+ * the tsx package (e.g. `dist/preflight.cjs`) are missing — causing a
562
+ * confusing MODULE_NOT_FOUND error at runtime.
563
+ *
564
+ * This function follows the symlink, walks up to find the tsx package root
565
+ * (`package.json` with `name: "tsx"`), and verifies that `dist/preflight.cjs`
566
+ * is present. Returns `null` when the installation looks healthy, or an
567
+ * error description string when it appears corrupted.
568
+ */
569
+ function validateTsxInstallation(tsxBinPath) {
570
+ try {
571
+ const realPath = fs.realpathSync(tsxBinPath);
572
+ let dir = path.dirname(realPath);
573
+ const fsRoot = path.parse(dir).root;
574
+ for (let depth = 0; depth < 10 && dir !== fsRoot; depth++) {
575
+ const pkgPath = path.join(dir, "package.json");
576
+ if (fs.existsSync(pkgPath)) try {
577
+ if (JSON.parse(fs.readFileSync(pkgPath, "utf-8")).name === "tsx") {
578
+ const preflightPath = path.join(dir, "dist", "preflight.cjs");
579
+ if (!fs.existsSync(preflightPath)) return `tsx package at ${dir} is missing dist/preflight.cjs`;
580
+ return null;
581
+ }
582
+ } catch {}
583
+ dir = path.dirname(dir);
584
+ }
585
+ return null;
586
+ } catch (err) {
587
+ return `tsx binary symlink is broken: ${err instanceof Error ? err.message : String(err)}`;
588
+ }
589
+ }
590
+ /**
591
+ * The one sentence a command says when the project's dependencies are missing.
592
+ *
593
+ * Six commands each had their own wording for the same state, and none of them
594
+ * named the remedy:
595
+ *
596
+ * ✗ Could not find CLI entry point for @rebasepro/server-postgres.
597
+ * ✗ Could not find tsx binary.
598
+ * ✗ Could not find tsx binary for backend.
599
+ *
600
+ * All three mean "you have not installed yet" — `getActiveBackendPlugin` has
601
+ * already read the driver out of `backend/package.json` by the time the first
602
+ * one fires, so the package is declared and simply not on disk, and `tsx` is a
603
+ * devDependency of every scaffold. But they read as *Rebase* being broken, and
604
+ * they name an internal path or a binary the developer never asked for rather
605
+ * than the command that fixes it. That is the whole of the failure someone sees
606
+ * on a fresh clone, where `node_modules/` is the one thing a checkout does not
607
+ * carry.
608
+ *
609
+ * So: one sentence, naming the package manager this project uses and the
610
+ * directory to run it in — the working directory is usually neither.
611
+ */
612
+ function dependenciesNotInstalled(projectRoot) {
613
+ return `Dependencies are not installed — run \`${getPMCommands(detectPackageManager(projectRoot)).install.join(" ")}\` in ${projectRoot}`;
614
+ }
615
+ /**
616
+ * Print {@link dependenciesNotInstalled} and exit 1.
617
+ *
618
+ * For the five commands that report and stop. `db.ts` throws instead, because
619
+ * its caller adds the `✗` and the exit itself.
620
+ */
621
+ function exitDependenciesNotInstalled(projectRoot) {
622
+ console.error(chalk.red(`✗ ${dependenciesNotInstalled(projectRoot)}`));
623
+ process.exit(1);
624
+ }
625
+ /**
626
+ * The refusal envelope, for a caller that asked for JSON.
627
+ *
628
+ * Same shape as the cloud family's `fail()` — `{ error: { message, code, hint } }`
629
+ * — because there is one CLI and a caller should not have to know which half of
630
+ * it answered. `code` is what a caller branches on and is never absent; the
631
+ * cloud family's reasoning about that applies here unchanged. `issues` carries
632
+ * the per-problem detail a human reader gets as a bullet list, because a
633
+ * refusal that says "2 problem(s)" and does not say which two is not
634
+ * machine-readable in any useful sense.
635
+ *
636
+ * On **stdout**, like every other `--json` result: the contract those commands
637
+ * make is that stdout holds one JSON value, and a caller that pipes stdout to a
638
+ * parser must get a parseable refusal rather than an empty stream and a
639
+ * human sentence it never sees. That contract used to hold for exactly one
640
+ * failure of each command — the one that goes through `requireProjectRoot` —
641
+ * while every other exit of the *same* command wrote human text to stderr and
642
+ * left stdout empty. So `rebase status --json` was parseable outside a project
643
+ * and unparseable inside a broken one, which is the case a caller actually has
644
+ * to handle.
645
+ *
646
+ * The flag is read off `process.argv` because the failure happens inside a
647
+ * helper the command calls before it has parsed anything. Coarse — a literal
648
+ * `--json` as some other flag's value would count — and worth it: the failure
649
+ * mode of being coarse is a JSON error where a human one was wanted, and the
650
+ * failure mode of not doing it is an unparseable stream.
651
+ */
652
+ function failAsJson(message, code, hint, issues) {
653
+ const error = {
654
+ message,
655
+ code
656
+ };
657
+ if (hint !== void 0) error.hint = hint;
658
+ if (issues !== void 0) error.issues = issues;
659
+ console.log(JSON.stringify({ error }, null, 2));
660
+ process.exit(1);
661
+ }
662
+ /**
663
+ * Did the command line ask for machine-readable output? Read as the parser
664
+ * reads a switch: `--json`, `--json=true` and `--json=1` ask for it,
665
+ * `--json=false` and `--json=0` do not.
666
+ */
667
+ function wantsJsonOutput(argv = process.argv) {
668
+ return switchOn(argv, "--json");
669
+ }
670
+ /**
671
+ * Require the project root or exit with a helpful error.
672
+ */
673
+ function requireProjectRoot() {
674
+ const root = findProjectRoot();
675
+ if (!root) {
676
+ if (wantsJsonOutput()) failAsJson("Could not find a Rebase project root.", "no_project_root", `Looked in this directory and every parent for a ${MANIFEST_FILENAME}, a package.json with a "backend" workspace, or a backend/ next to a config/.`);
677
+ console.error(chalk.red("✗ Could not find a Rebase project root."));
678
+ console.error(chalk.gray(` Looked in this directory and every parent for a ${MANIFEST_FILENAME},`));
679
+ console.error(chalk.gray(" a package.json with a \"backend\" workspace, or a backend/ next to a config/."));
680
+ console.error(chalk.gray(" Run this from inside a project, or create one with `rebase init`."));
681
+ process.exit(1);
682
+ }
683
+ return root;
684
+ }
685
+ /**
686
+ * Require the backend directory or exit with a helpful error.
687
+ */
688
+ function requireBackendDir(projectRoot) {
689
+ const backendDir = findBackendDir(projectRoot);
690
+ if (!backendDir) {
691
+ if (wantsJsonOutput()) failAsJson("Could not find a backend/ directory.", "no_backend_dir", `Expected at: ${path.join(projectRoot, "backend")}`);
692
+ console.error(chalk.red("✗ Could not find a backend/ directory."));
693
+ console.error(chalk.gray(` Expected at: ${path.join(projectRoot, "backend")}`));
694
+ process.exit(1);
695
+ }
696
+ return backendDir;
697
+ }
698
+ //#endregion
699
+ export { wantsHelp as C, redactConnectionPasswords as D, unknownCommandMessage as E, switchOn as S, unknownCommand as T, resolveTsxLoader as _, findBackendDir as a, parseCommandArgs as b, findProjectRoot as c, readEnvFile as d, requireBackendDir as f, resolveTsx as g, resolvePluginCliScript as h, failAsJson as i, getActiveBackendPlugin as l, resolveLocalBin as m, dependenciesNotInstalled as n, findEnvFile as o, requireProjectRoot as p, exitDependenciesNotInstalled as r, findFrontendDir as s, MANIFEST_FILENAME as t, project_exports as u, validateTsxInstallation as v, suggestCommand as w, resolveSwitchValues as x, UsageError as y };
700
+
701
+ //# sourceMappingURL=project-g4K6kV1t.js.map