@rebasepro/cli 0.25.0-canary.ge06257e → 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.
- package/bin/rebase.js +9 -5
- package/dist/{agent-setup-C83DXnlx.js → agent-setup-BYb2Tbdz.js} +2 -2
- package/dist/{agent-setup-C83DXnlx.js.map → agent-setup-BYb2Tbdz.js.map} +1 -1
- package/dist/{api-keys-Bc4FJKYp.js → api-keys-CSMbsmAn.js} +28 -22
- package/dist/api-keys-CSMbsmAn.js.map +1 -0
- package/dist/{apps-wG-ZTryA.js → apps-iBXNZhVN.js} +6 -7
- package/dist/{apps-wG-ZTryA.js.map → apps-iBXNZhVN.js.map} +1 -1
- package/dist/{atlas-dev-database-DeK3Sa4h.js → atlas-dev-database-BusbHP6n.js} +4 -4
- package/dist/{atlas-dev-database-DeK3Sa4h.js.map → atlas-dev-database-BusbHP6n.js.map} +1 -1
- package/dist/{auth-BL7bNQVu.js → auth-DRsTWPky.js} +77 -11
- package/dist/auth-DRsTWPky.js.map +1 -0
- package/dist/{branch-pointer-DTQNLFtb.js → branch-pointer-Bn_FjNld.js} +1 -1
- package/dist/{build-h_7Re-67.js → build-et7Rccxi.js} +6 -7
- package/dist/{build-h_7Re-67.js.map → build-et7Rccxi.js.map} +1 -1
- package/dist/{bundle-CqURVN-K.js → bundle-DlqkDYSe.js} +6 -21
- package/dist/bundle-DlqkDYSe.js.map +1 -0
- package/dist/{cloud-DVV_5GPG.js → cloud-B-ZIjHrA.js} +10 -11
- package/dist/{cloud-DVV_5GPG.js.map → cloud-B-ZIjHrA.js.map} +1 -1
- package/dist/commands/api-keys.d.ts +4 -1
- package/dist/commands/auth.d.ts +3 -2
- package/dist/commands/dev.d.ts +12 -0
- package/dist/commands/generate_sdk.d.ts +4 -2
- package/dist/commands/init.d.ts +20 -0
- package/dist/commands/start.d.ts +10 -0
- package/dist/commands/upgrade.d.ts +12 -0
- package/dist/connection-banner-DbLJzWf4.js +128 -0
- package/dist/connection-banner-DbLJzWf4.js.map +1 -0
- package/dist/{consent-ClRHmcxf.js → consent-BaB-eQKG.js} +2 -2
- package/dist/{consent-ClRHmcxf.js.map → consent-BaB-eQKG.js.map} +1 -1
- package/dist/{console-Dw3HLdUs.js → console-BlD9kjFW.js} +4 -4
- package/dist/{console-Dw3HLdUs.js.map → console-BlD9kjFW.js.map} +1 -1
- package/dist/{context-BM-oOMNX.js → context-DHiiiJ5C.js} +3 -4
- package/dist/context-DHiiiJ5C.js.map +1 -0
- package/dist/{daemon-CknkDjd6.js → daemon-DDMRbz4T.js} +160 -5
- package/dist/daemon-DDMRbz4T.js.map +1 -0
- package/dist/{daemon-entry-C6pxdily.js → daemon-entry-zOC1revO.js} +204 -5
- package/dist/daemon-entry-zOC1revO.js.map +1 -0
- package/dist/{db-Dkn9XhT-.js → db-9P6MnmdC.js} +237 -86
- package/dist/db-9P6MnmdC.js.map +1 -0
- package/dist/{derive-Dl0z2OWi.js → derive-MTdClnkc.js} +29 -14
- package/dist/{derive-Dl0z2OWi.js.map → derive-MTdClnkc.js.map} +1 -1
- package/dist/{dev-DAcZR4Pp.js → dev-BDN_OKXA.js} +183 -23
- package/dist/dev-BDN_OKXA.js.map +1 -0
- package/dist/dev-db/connection-banner.d.ts +47 -0
- package/dist/dev-db/daemon.d.ts +41 -0
- package/dist/dev-db/notification-proxy.d.ts +82 -1
- package/dist/dev-db/pull.d.ts +33 -5
- package/dist/dev-db/state.d.ts +9 -0
- package/dist/{dev-preflight-CorMg7Ub.js → dev-preflight-FT9GbJVj.js} +73 -4
- package/dist/dev-preflight-FT9GbJVj.js.map +1 -0
- package/dist/{dev-server-B1UYifZJ.js → dev-server-CIZwAfZa.js} +14 -3
- package/dist/dev-server-CIZwAfZa.js.map +1 -0
- package/dist/{doctor-Ds_lzc9p.js → doctor-D8YXz7df.js} +6 -6
- package/dist/doctor-D8YXz7df.js.map +1 -0
- package/dist/{eject-CJHkfnat.js → eject-izG2KjMo.js} +3 -4
- package/dist/{eject-CJHkfnat.js.map → eject-izG2KjMo.js.map} +1 -1
- package/dist/{fold-static-BC9CYicw.js → fold-static-CybSay_W.js} +3 -3
- package/dist/{fold-static-BC9CYicw.js.map → fold-static-CybSay_W.js.map} +1 -1
- package/dist/{generate_sdk-kfkZmG-d.js → generate_sdk-B4R65oUV.js} +22 -12
- package/dist/generate_sdk-B4R65oUV.js.map +1 -0
- package/dist/index.es.js +36 -37
- package/dist/index.es.js.map +1 -1
- package/dist/{init-CjoJoThg.js → init-DYaeppEN.js} +52 -63
- package/dist/init-DYaeppEN.js.map +1 -0
- package/dist/invoked-command-CG_rIa_o.js +55 -0
- package/dist/invoked-command-CG_rIa_o.js.map +1 -0
- package/dist/last-dev-database-B_75fxZC.js +2 -0
- package/dist/{manifest-DRp0UVTj.js → manifest-nvPdLlJY.js} +2 -2
- package/dist/{manifest-DRp0UVTj.js.map → manifest-nvPdLlJY.js.map} +1 -1
- package/dist/{normalize-imports-BHxL2d05.js → normalize-imports-BYw8ad87.js} +3 -3
- package/dist/{normalize-imports-BHxL2d05.js.map → normalize-imports-BYw8ad87.js.map} +1 -1
- package/dist/precompress.d.ts +0 -7
- package/dist/{prepare-C1iaiTC4.js → prepare-1rVNd2PF.js} +7 -6
- package/dist/prepare-1rVNd2PF.js.map +1 -0
- package/dist/project-g4K6kV1t.js +701 -0
- package/dist/project-g4K6kV1t.js.map +1 -0
- package/dist/{pull-COpNJFzY.js → pull-DDMpsZ5r.js} +143 -63
- package/dist/pull-DDMpsZ5r.js.map +1 -0
- package/dist/refusal-ReeeGP8a.js +26 -0
- package/dist/refusal-ReeeGP8a.js.map +1 -0
- package/dist/resolve-BQUAC3Wi.js +2 -0
- package/dist/resources/derive.d.ts +9 -0
- package/dist/{resources-CLm6g0br.js → resources-CovP8AZ-.js} +4 -5
- package/dist/{resources-CLm6g0br.js.map → resources-CovP8AZ-.js.map} +1 -1
- package/dist/{schema-BKdh6UoE.js → schema-nuxvvc50.js} +5 -6
- package/dist/{schema-BKdh6UoE.js.map → schema-nuxvvc50.js.map} +1 -1
- package/dist/{skills-DEhYiPeO.js → skills-DeUIsZcT.js} +3 -4
- package/dist/{skills-DEhYiPeO.js.map → skills-DeUIsZcT.js.map} +1 -1
- package/dist/{start-BsPJedPQ.js → start-RzZqDroW.js} +20 -7
- package/dist/start-RzZqDroW.js.map +1 -0
- package/dist/{state-CCB-FAQ9.js → state-CYdBvnwA.js} +3 -2
- package/dist/state-CYdBvnwA.js.map +1 -0
- package/dist/{status-D_B3tlDy.js → status-D9Zm88NZ.js} +11 -12
- package/dist/{status-D_B3tlDy.js.map → status-D9Zm88NZ.js.map} +1 -1
- package/dist/{storage-CtB8polS.js → storage-BB3umRfw.js} +3 -4
- package/dist/{storage-CtB8polS.js.map → storage-BB3umRfw.js.map} +1 -1
- package/dist/{telemetry-EMFRVRQi.js → telemetry-HfU5uAh1.js} +2 -2
- package/dist/{telemetry-EMFRVRQi.js.map → telemetry-HfU5uAh1.js.map} +1 -1
- package/dist/{telemetry-BYmPU-Ji.js → telemetry-TseNLk6X.js} +4 -4
- package/dist/{telemetry-BYmPU-Ji.js.map → telemetry-TseNLk6X.js.map} +1 -1
- package/dist/{upgrade-DbuKX9lv.js → upgrade-apiPKKE_.js} +29 -6
- package/dist/{upgrade-DbuKX9lv.js.map → upgrade-apiPKKE_.js.map} +1 -1
- package/dist/utils/args.d.ts +45 -3
- package/dist/utils/boot-retry.d.ts +41 -0
- package/dist/utils/dev-preflight.d.ts +34 -1
- package/dist/utils/dev-server.d.ts +11 -0
- package/dist/utils/project.d.ts +5 -1
- package/dist/utils/redact.d.ts +10 -0
- package/dist/utils/refusal.d.ts +18 -0
- package/dist/utils/unknown-command.d.ts +4 -0
- package/package.json +7 -7
- package/templates/overlays/baas/README.md +2 -1
- package/templates/resource-graphs/blank.json +25 -0
- package/templates/resource-graphs/blog.json +28 -0
- package/templates/resource-graphs/ecommerce.json +28 -0
- package/templates/resource-graphs/headless.json +22 -0
- package/templates/template/.env.example +11 -4
- package/templates/template/frontend/src/App.tsx +10 -9
- package/templates/template/frontend/vite.config.ts +7 -73
- package/dist/api-keys-Bc4FJKYp.js.map +0 -1
- package/dist/args-CcBphFdj.js +0 -237
- package/dist/args-CcBphFdj.js.map +0 -1
- package/dist/auth-BL7bNQVu.js.map +0 -1
- package/dist/bundle-CqURVN-K.js.map +0 -1
- package/dist/context-BM-oOMNX.js.map +0 -1
- package/dist/daemon-CknkDjd6.js.map +0 -1
- package/dist/daemon-entry-C6pxdily.js.map +0 -1
- package/dist/db-Dkn9XhT-.js.map +0 -1
- package/dist/dev-DAcZR4Pp.js.map +0 -1
- package/dist/dev-preflight-CorMg7Ub.js.map +0 -1
- package/dist/dev-server-B1UYifZJ.js.map +0 -1
- package/dist/doctor-Ds_lzc9p.js.map +0 -1
- package/dist/generate_sdk-kfkZmG-d.js.map +0 -1
- package/dist/init-CjoJoThg.js.map +0 -1
- package/dist/last-dev-database-BEBPfAMY.js +0 -2
- package/dist/prepare-C1iaiTC4.js.map +0 -1
- package/dist/project-Wp2zoyeN.js +0 -349
- package/dist/project-Wp2zoyeN.js.map +0 -1
- package/dist/pull-COpNJFzY.js.map +0 -1
- package/dist/resolve-b_p9bmdH.js +0 -2
- package/dist/start-BsPJedPQ.js.map +0 -1
- 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
|