@rebasepro/cli 0.23.0 → 0.24.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.
- package/README.md +19 -3
- package/bin/rebase.js +1 -1
- package/dist/{branch-pointer-dBiClJ0p.js → branch-pointer-kk9F5FBO.js} +2 -2
- package/dist/{branch-pointer-dBiClJ0p.js.map → branch-pointer-kk9F5FBO.js.map} +1 -1
- package/dist/bundle.d.ts +6 -2
- package/dist/cli.d.ts +21 -0
- package/dist/commands/__test_support__/parser-specs.d.ts +13 -0
- package/dist/commands/agent-setup.d.ts +116 -0
- package/dist/commands/api-keys.d.ts +84 -23
- package/dist/commands/apps.d.ts +17 -0
- package/dist/commands/auth.d.ts +7 -0
- package/dist/commands/build.d.ts +5 -1
- package/dist/commands/cloud/auth.d.ts +4 -3
- package/dist/commands/cloud/bundle-deploy.d.ts +45 -0
- package/dist/commands/cloud/context.d.ts +44 -5
- package/dist/commands/cloud/cron.d.ts +53 -0
- package/dist/commands/cloud/debug.d.ts +134 -0
- package/dist/commands/cloud/index.d.ts +89 -12
- package/dist/commands/cloud/token-capabilities.d.ts +76 -0
- package/dist/commands/cloud/tokens.d.ts +23 -0
- package/dist/commands/db.d.ts +15 -1
- package/dist/commands/dev.d.ts +78 -7
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/generate_sdk.d.ts +3 -2
- package/dist/commands/init.d.ts +57 -14
- package/dist/commands/schema.d.ts +6 -0
- package/dist/commands/skills.d.ts +109 -5
- package/dist/commands/start.d.ts +29 -0
- package/dist/{daemon-DR9Tk0La.js → daemon-C8p32i-n.js} +5 -3
- package/dist/daemon-C8p32i-n.js.map +1 -0
- package/dist/dev-db/daemon.d.ts +10 -0
- package/dist/dev-db/prepare.d.ts +10 -0
- package/dist/fold-static.d.ts +26 -3
- package/dist/index.es.js +12086 -9459
- package/dist/index.es.js.map +1 -1
- package/dist/manifest.d.ts +41 -1
- package/dist/package-manager-CXQevsE1.js +193 -0
- package/dist/package-manager-CXQevsE1.js.map +1 -0
- package/dist/{project-3WCbrrfW.js → project-DmaGLmH2.js} +4 -183
- package/dist/project-DmaGLmH2.js.map +1 -0
- package/dist/upgrade.d.ts +14 -2
- package/dist/utils/path-flags.d.ts +18 -0
- package/dist/version-DsgBmP22.js +58 -0
- package/dist/version-DsgBmP22.js.map +1 -0
- package/package.json +7 -7
- package/templates/eject/backend/src/index.ts +14 -1
- package/templates/overlays/baas/ai-instructions.md +1 -1
- package/templates/overlays/baas/config/storage.ts +5 -3
- package/templates/template/.cursorrules +1 -1
- package/templates/template/.env.example +10 -6
- package/templates/template/.github/copilot-instructions.md +1 -1
- package/templates/template/.mcp.json +2 -2
- package/templates/template/.windsurfrules +1 -1
- package/templates/template/AGENTS.md +1 -1
- package/templates/template/CLAUDE.md +1 -1
- package/templates/template/README.md +10 -11
- package/templates/template/ai-instructions.md +3 -3
- package/templates/template/backend/functions/hello.ts +8 -0
- package/templates/template/backend/package.json +1 -1
- package/templates/template/config/storage.ts +5 -3
- package/templates/template/docker-compose.yml +6 -6
- package/templates/template/gitignore +6 -1
- package/templates/template/package.json +4 -3
- package/templates/template/scripts/example.ts +4 -3
- package/dist/daemon-DR9Tk0La.js.map +0 -1
- package/dist/project-3WCbrrfW.js.map +0 -1
package/dist/manifest.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ManagedCompatibility, type RebaseAppConfig, type RebaseBackendAppConfig, type RebaseProjectManifest, type RebaseStaticAppConfig } from "@rebasepro/types";
|
|
1
|
+
import { type AppAddress, type ManagedCompatibility, type RebaseAppConfig, type RebaseBackendAppConfig, type RebaseProjectManifest, type RebaseStaticAppConfig } from "@rebasepro/types";
|
|
2
2
|
/** Runtime range written into new manifests. */
|
|
3
3
|
export declare const CURRENT_RUNTIME_RANGE = "^1";
|
|
4
4
|
/** Conventional locations, matching what `rebase init` scaffolds. */
|
|
@@ -22,6 +22,41 @@ export declare class ManifestError extends Error {
|
|
|
22
22
|
readonly issues: ManifestValidationIssue[];
|
|
23
23
|
constructor(message: string, issues?: ManifestValidationIssue[]);
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Where a validated static app answers: its hostname, if its `path` names one,
|
|
27
|
+
* and the path under it.
|
|
28
|
+
*
|
|
29
|
+
* The manifest keeps `path` as it was written, URL and all, because it is also
|
|
30
|
+
* what `writeManifest` puts back: storing the split pieces instead would have
|
|
31
|
+
* `rebase eject` rewrite `"https://admin.example.com"` to `"/"` and drop the
|
|
32
|
+
* hostname from a file the developer never asked it to touch. Everything that
|
|
33
|
+
* mounts, builds or prints an app reads it through here instead, so there is
|
|
34
|
+
* one reading of the field.
|
|
35
|
+
*
|
|
36
|
+
* Throws on a value `validateManifest` would have refused. Every caller holds a
|
|
37
|
+
* manifest that came through it, so reaching the throw means one did not — and
|
|
38
|
+
* guessing an address for it would build or mount the app somewhere it was
|
|
39
|
+
* never declared.
|
|
40
|
+
*/
|
|
41
|
+
export declare function staticAppAddress(name: string, app: {
|
|
42
|
+
path?: string;
|
|
43
|
+
}): AppAddress;
|
|
44
|
+
/**
|
|
45
|
+
* An address as a developer would type it: the path alone for an app on every
|
|
46
|
+
* hostname, the https URL for one on its own. The root of a hostname prints as
|
|
47
|
+
* the bare origin — `https://admin.example.com`, which is how it is declared.
|
|
48
|
+
*/
|
|
49
|
+
export declare function formatAppAddress(address: AppAddress): string;
|
|
50
|
+
/**
|
|
51
|
+
* The order static apps are mounted in, and the order a bundle lists them.
|
|
52
|
+
*
|
|
53
|
+
* Longest path first, so an app at `/` is registered after the apps beneath it
|
|
54
|
+
* and its catch-all cannot claim their URLs. At an equal path, the app bound to
|
|
55
|
+
* a hostname goes first: on that hostname it is the one that answers, and the
|
|
56
|
+
* app on every hostname answers the rest. That is the runtime's matching rule,
|
|
57
|
+
* restated as a sort so a bundle reads in the order it is served.
|
|
58
|
+
*/
|
|
59
|
+
export declare function compareMountOrder(a: AppAddress, b: AppAddress): number;
|
|
25
60
|
/**
|
|
26
61
|
* Validate a parsed manifest, collecting every problem.
|
|
27
62
|
*/
|
|
@@ -89,11 +124,16 @@ export declare function findBackendApp(manifest: RebaseProjectManifest): {
|
|
|
89
124
|
* Returns the *serving* app alongside the path, because a caller with a base URL
|
|
90
125
|
* needs to know which app answers there — and because the path alone cannot say
|
|
91
126
|
* whether the CMS is the whole of an app or one route of it.
|
|
127
|
+
*
|
|
128
|
+
* `path` is always a path. `host` is the serving app's hostname, when its `path`
|
|
129
|
+
* gives it one: the CMS is then at `https://<host><path>` and nowhere else, and
|
|
130
|
+
* a caller holding a local or project base URL must not put the path on that.
|
|
92
131
|
*/
|
|
93
132
|
export declare function cmsMountOf(manifest: RebaseProjectManifest): {
|
|
94
133
|
appName: string;
|
|
95
134
|
app: RebaseStaticAppConfig;
|
|
96
135
|
path: string;
|
|
136
|
+
host?: string;
|
|
97
137
|
} | undefined;
|
|
98
138
|
/**
|
|
99
139
|
* The app a deploy targets: the one named, or the obvious one.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { spawnSync } from "child_process";
|
|
5
|
+
//#region src/utils/package-manager.ts
|
|
6
|
+
/**
|
|
7
|
+
* Package manager detection and command abstraction.
|
|
8
|
+
*
|
|
9
|
+
* Detects whether the user is running pnpm or npm and provides
|
|
10
|
+
* a unified interface for common package-manager operations so
|
|
11
|
+
* the rest of the CLI never has to hardcode a specific PM.
|
|
12
|
+
*/
|
|
13
|
+
var package_manager_exports = /* @__PURE__ */ __exportAll({
|
|
14
|
+
detectPackageManager: () => detectPackageManager,
|
|
15
|
+
getPMCommands: () => getPMCommands,
|
|
16
|
+
isPnpmAvailable: () => isPnpmAvailable,
|
|
17
|
+
pnpmAvailabilityFromProbe: () => pnpmAvailabilityFromProbe
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* How long to wait for `pnpm --version` before giving up on the probe.
|
|
21
|
+
*
|
|
22
|
+
* `pnpm --version` is a cold Node start, and on a machine that is busy — a
|
|
23
|
+
* parallel install, a full test run — it routinely takes seconds. Measured at
|
|
24
|
+
* 630ms, 990ms and 4293ms on three consecutive runs of one developer laptop
|
|
25
|
+
* under load, so the previous 3s budget was inside the normal spread rather
|
|
26
|
+
* than safely outside it.
|
|
27
|
+
*/
|
|
28
|
+
var PNPM_PROBE_TIMEOUT_MS = 5e3;
|
|
29
|
+
/** Memoised result of the probe. pnpm cannot appear or vanish mid-process. */
|
|
30
|
+
var cachedPnpmAvailable;
|
|
31
|
+
/**
|
|
32
|
+
* Decide availability from a `spawnSync` outcome.
|
|
33
|
+
*
|
|
34
|
+
* Split out from the spawn itself so the decision is testable without starting
|
|
35
|
+
* a process — which is what made the old test load-sensitive and occasionally
|
|
36
|
+
* red for reasons that had nothing to do with the code under test.
|
|
37
|
+
*
|
|
38
|
+
* The three outcomes are distinguishable, and the old code conflated two of
|
|
39
|
+
* them by asking only `status === 0`:
|
|
40
|
+
*
|
|
41
|
+
* not installed status null, signal null, error.code ENOENT
|
|
42
|
+
* timed out status null, signal SIGTERM, error.code ETIMEDOUT
|
|
43
|
+
* broken install status non-zero, no error
|
|
44
|
+
*/
|
|
45
|
+
function pnpmAvailabilityFromProbe(res) {
|
|
46
|
+
const code = res.error?.code;
|
|
47
|
+
if (code === "ENOENT") return false;
|
|
48
|
+
if (code === "ETIMEDOUT" || res.signal) return true;
|
|
49
|
+
if (res.error) return false;
|
|
50
|
+
return res.status === 0;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Whether pnpm is runnable on this machine.
|
|
54
|
+
*
|
|
55
|
+
* Used to decide whether a fresh project can be scaffolded with pnpm. Kept
|
|
56
|
+
* cheap and non-interactive (bounded timeout, output discarded) so it never
|
|
57
|
+
* hangs detection if a corepack shim misbehaves, and memoised so that repeated
|
|
58
|
+
* detection in one CLI run costs one process rather than one per call.
|
|
59
|
+
*/
|
|
60
|
+
function isPnpmAvailable() {
|
|
61
|
+
if (cachedPnpmAvailable !== void 0) return cachedPnpmAvailable;
|
|
62
|
+
try {
|
|
63
|
+
cachedPnpmAvailable = pnpmAvailabilityFromProbe(spawnSync("pnpm", ["--version"], {
|
|
64
|
+
stdio: "ignore",
|
|
65
|
+
timeout: PNPM_PROBE_TIMEOUT_MS
|
|
66
|
+
}));
|
|
67
|
+
} catch {
|
|
68
|
+
cachedPnpmAvailable = false;
|
|
69
|
+
}
|
|
70
|
+
return cachedPnpmAvailable;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Detect the package manager for a Rebase project.
|
|
74
|
+
*
|
|
75
|
+
* Rebase recommends pnpm, so detection prefers it. Crucially, *how the CLI was
|
|
76
|
+
* invoked* (`npx` vs `pnpm dlx`, i.e. `npm_config_user_agent`) is deliberately
|
|
77
|
+
* ignored: running `npx @rebasepro/cli init` says nothing about how the user
|
|
78
|
+
* wants to manage the project they're creating, and letting it pin the scaffold
|
|
79
|
+
* to npm is what made every `npx`-invoked project an npm project.
|
|
80
|
+
*
|
|
81
|
+
* Detection order:
|
|
82
|
+
* 1. An existing lock file — an explicit choice we always respect
|
|
83
|
+
* (`pnpm-lock.yaml` wins over `package-lock.json` when both are present).
|
|
84
|
+
* 2. pnpm, whenever it is installed.
|
|
85
|
+
* 3. npm, only as a fallback when pnpm is genuinely unavailable.
|
|
86
|
+
*/
|
|
87
|
+
function detectPackageManager(targetDir) {
|
|
88
|
+
const dirs = [targetDir, process.cwd()].filter((d) => !!d);
|
|
89
|
+
for (const dir of dirs) {
|
|
90
|
+
if (fs.existsSync(path.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
91
|
+
if (fs.existsSync(path.join(dir, "package-lock.json"))) return "npm";
|
|
92
|
+
for (const [lock, name] of [
|
|
93
|
+
["yarn.lock", "yarn"],
|
|
94
|
+
["bun.lockb", "bun"],
|
|
95
|
+
["bun.lock", "bun"]
|
|
96
|
+
]) {
|
|
97
|
+
if (!fs.existsSync(path.join(dir, lock))) continue;
|
|
98
|
+
console.warn(`[rebase] Found ${lock}, but Rebase scaffolds pnpm or npm workspaces only — ${name} is not supported yet. Continuing with ${isPnpmAvailable() ? "pnpm" : "npm"}.`);
|
|
99
|
+
return isPnpmAvailable() ? "pnpm" : "npm";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (isPnpmAvailable()) return "pnpm";
|
|
103
|
+
return "npm";
|
|
104
|
+
}
|
|
105
|
+
/** Build the command helpers for a given package manager. */
|
|
106
|
+
function getPMCommands(pm) {
|
|
107
|
+
if (pm === "npm") return {
|
|
108
|
+
name: "npm",
|
|
109
|
+
install: ["npm", "install"],
|
|
110
|
+
run: (script) => [
|
|
111
|
+
"npm",
|
|
112
|
+
"run",
|
|
113
|
+
script
|
|
114
|
+
],
|
|
115
|
+
exec: (bin, args) => [
|
|
116
|
+
"npx",
|
|
117
|
+
bin,
|
|
118
|
+
...args
|
|
119
|
+
],
|
|
120
|
+
view: (pkg, field) => [
|
|
121
|
+
"npm",
|
|
122
|
+
"view",
|
|
123
|
+
pkg,
|
|
124
|
+
field
|
|
125
|
+
],
|
|
126
|
+
runAll: (script) => [
|
|
127
|
+
"npm",
|
|
128
|
+
"run",
|
|
129
|
+
script,
|
|
130
|
+
"--workspaces",
|
|
131
|
+
"--if-present"
|
|
132
|
+
],
|
|
133
|
+
runWorkspace: (workspace, script) => [
|
|
134
|
+
"npm",
|
|
135
|
+
"run",
|
|
136
|
+
script,
|
|
137
|
+
"-w",
|
|
138
|
+
workspace
|
|
139
|
+
],
|
|
140
|
+
dlx: (pkg, args) => [
|
|
141
|
+
"npx",
|
|
142
|
+
"-y",
|
|
143
|
+
pkg,
|
|
144
|
+
...args
|
|
145
|
+
],
|
|
146
|
+
workspaceProtocol: "*"
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
name: "pnpm",
|
|
150
|
+
install: ["pnpm", "install"],
|
|
151
|
+
run: (script) => [
|
|
152
|
+
"pnpm",
|
|
153
|
+
"run",
|
|
154
|
+
script
|
|
155
|
+
],
|
|
156
|
+
exec: (bin, args) => [
|
|
157
|
+
"pnpm",
|
|
158
|
+
"exec",
|
|
159
|
+
bin,
|
|
160
|
+
...args
|
|
161
|
+
],
|
|
162
|
+
view: (pkg, field) => [
|
|
163
|
+
"pnpm",
|
|
164
|
+
"view",
|
|
165
|
+
pkg,
|
|
166
|
+
field
|
|
167
|
+
],
|
|
168
|
+
runAll: (script) => [
|
|
169
|
+
"pnpm",
|
|
170
|
+
"-r",
|
|
171
|
+
"run",
|
|
172
|
+
script
|
|
173
|
+
],
|
|
174
|
+
runWorkspace: (workspace, script) => [
|
|
175
|
+
"pnpm",
|
|
176
|
+
"--filter",
|
|
177
|
+
`./${workspace}`,
|
|
178
|
+
"run",
|
|
179
|
+
script
|
|
180
|
+
],
|
|
181
|
+
dlx: (pkg, args) => [
|
|
182
|
+
"pnpm",
|
|
183
|
+
"dlx",
|
|
184
|
+
pkg,
|
|
185
|
+
...args
|
|
186
|
+
],
|
|
187
|
+
workspaceProtocol: "workspace:*"
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
export { getPMCommands as n, package_manager_exports as r, detectPackageManager as t };
|
|
192
|
+
|
|
193
|
+
//# sourceMappingURL=package-manager-CXQevsE1.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-manager-CXQevsE1.js","names":[],"sources":["../src/utils/package-manager.ts"],"sourcesContent":["/**\n * Package manager detection and command abstraction.\n *\n * Detects whether the user is running pnpm or npm and provides\n * a unified interface for common package-manager operations so\n * the rest of the CLI never has to hardcode a specific PM.\n */\nimport fs from \"fs\";\nimport path from \"path\";\nimport { spawnSync } from \"child_process\";\n\n/**\n * The two package managers the CLI can scaffold for.\n *\n * Yarn and bun are deliberately absent, and this is the note that says so\n * rather than leaving it to be discovered. A yarn or bun user gets an npm\n * project: `detectPackageManager` finds no `pnpm-lock.yaml` and no\n * `package-lock.json`, falls through to the pnpm probe, and lands on npm —\n * silently, and with a `yarn.lock` or `bun.lockb` sitting right there in the\n * directory it was asked about.\n *\n * The reason is not indifference. Every generated project is a workspace, and\n * the scaffold writes one protocol for the workspace links —\n * `workspaceProtocol` below is `\"workspace:*\"` or `\"*\"`. Yarn understands\n * `workspace:*` and bun does not; the two also disagree with pnpm about where\n * `node_modules` goes, which is what `rebase dev` walks to find the backend.\n * Supporting them means a fourth and fifth scaffold shape, each needing its own\n * end-to-end test, and neither exists.\n *\n * So the honest position is: pnpm (recommended) or npm. A yarn or bun user is\n * better served knowing they will get an npm workspace than finding out from\n * the lockfile that appears after their first install. See\n * {@link detectPackageManager}, which now says so out loud.\n */\nexport type PackageManager = \"pnpm\" | \"npm\";\n\nexport interface PMCommands {\n /** The binary name (\"pnpm\" | \"npm\"). */\n name: PackageManager;\n /** Install all dependencies — e.g. `pnpm install` / `npm install`. */\n install: string[];\n /** Run a script — e.g. `pnpm run dev` / `npm run dev`. */\n run: (script: string) => string[];\n /** Execute a local bin — e.g. `pnpm exec rebase ...` / `npx rebase ...`. */\n exec: (bin: string, args: string[]) => string[];\n /** Query the registry — e.g. `pnpm view <pkg> version` / `npm view <pkg> version`. */\n view: (pkg: string, field: string) => string[];\n /** Run all workspace scripts — e.g. `pnpm -r run build` / `npm run build --workspaces`. */\n runAll: (script: string) => string[];\n /** Run a script in a specific workspace — e.g. `pnpm --filter \"*-backend\" start` / `npm run start -w backend`. */\n runWorkspace: (workspace: string, script: string) => string[];\n /** Execute a one-off package — e.g. `pnpm dlx skills ...` / `npx -y skills ...`. */\n dlx: (pkg: string, args: string[]) => string[];\n /** The workspace dependency protocol: `\"workspace:*\"` for pnpm, `\"*\"` for npm. */\n workspaceProtocol: string;\n}\n\n/**\n * How long to wait for `pnpm --version` before giving up on the probe.\n *\n * `pnpm --version` is a cold Node start, and on a machine that is busy — a\n * parallel install, a full test run — it routinely takes seconds. Measured at\n * 630ms, 990ms and 4293ms on three consecutive runs of one developer laptop\n * under load, so the previous 3s budget was inside the normal spread rather\n * than safely outside it.\n */\nconst PNPM_PROBE_TIMEOUT_MS = 5000;\n\n/** Memoised result of the probe. pnpm cannot appear or vanish mid-process. */\nlet cachedPnpmAvailable: boolean | undefined;\n\n/**\n * Decide availability from a `spawnSync` outcome.\n *\n * Split out from the spawn itself so the decision is testable without starting\n * a process — which is what made the old test load-sensitive and occasionally\n * red for reasons that had nothing to do with the code under test.\n *\n * The three outcomes are distinguishable, and the old code conflated two of\n * them by asking only `status === 0`:\n *\n * not installed status null, signal null, error.code ENOENT\n * timed out status null, signal SIGTERM, error.code ETIMEDOUT\n * broken install status non-zero, no error\n */\nexport function pnpmAvailabilityFromProbe(res: {\n status: number | null;\n signal?: NodeJS.Signals | null;\n error?: { code?: string } | Error;\n}): boolean {\n const code = (res.error as { code?: string } | undefined)?.code;\n\n // The binary is not on PATH. Genuinely absent.\n if (code === \"ENOENT\") return false;\n\n // We killed it for taking too long. That means it WAS found and did start,\n // so pnpm is installed — it was merely slow, which on a loaded machine is\n // routine rather than exceptional. Reporting \"absent\" here is what silently\n // scaffolded npm projects for developers who had pnpm all along.\n //\n // The trade-off, stated plainly: a pnpm that hangs forever (a misbehaving\n // corepack shim) now resolves to pnpm instead of falling back to npm. That\n // is the rarer and more visible failure — the user sees their next command\n // hang — whereas the case this fixes was silent and produced a project\n // pinned to the wrong package manager. The timeout still bounds how long\n // detection itself waits, which was always its real job.\n if (code === \"ETIMEDOUT\" || res.signal) return true;\n\n // Any other spawn error: treat as unavailable rather than guess.\n if (res.error) return false;\n\n return res.status === 0;\n}\n\n/**\n * Whether pnpm is runnable on this machine.\n *\n * Used to decide whether a fresh project can be scaffolded with pnpm. Kept\n * cheap and non-interactive (bounded timeout, output discarded) so it never\n * hangs detection if a corepack shim misbehaves, and memoised so that repeated\n * detection in one CLI run costs one process rather than one per call.\n */\nexport function isPnpmAvailable(): boolean {\n if (cachedPnpmAvailable !== undefined) return cachedPnpmAvailable;\n try {\n const res = spawnSync(\"pnpm\", [\"--version\"], {\n stdio: \"ignore\",\n timeout: PNPM_PROBE_TIMEOUT_MS\n });\n cachedPnpmAvailable = pnpmAvailabilityFromProbe(res);\n } catch {\n cachedPnpmAvailable = false;\n }\n return cachedPnpmAvailable;\n}\n\n/** Forget the memoised probe. For tests; nothing in a CLI run needs it. */\nexport function resetPnpmAvailabilityCache(): void {\n cachedPnpmAvailable = undefined;\n}\n\n/**\n * Detect the package manager for a Rebase project.\n *\n * Rebase recommends pnpm, so detection prefers it. Crucially, *how the CLI was\n * invoked* (`npx` vs `pnpm dlx`, i.e. `npm_config_user_agent`) is deliberately\n * ignored: running `npx @rebasepro/cli init` says nothing about how the user\n * wants to manage the project they're creating, and letting it pin the scaffold\n * to npm is what made every `npx`-invoked project an npm project.\n *\n * Detection order:\n * 1. An existing lock file — an explicit choice we always respect\n * (`pnpm-lock.yaml` wins over `package-lock.json` when both are present).\n * 2. pnpm, whenever it is installed.\n * 3. npm, only as a fallback when pnpm is genuinely unavailable.\n */\nexport function detectPackageManager(targetDir?: string): PackageManager {\n // 1. Respect an existing project's lock file.\n const dirs = [targetDir, process.cwd()].filter((d): d is string => !!d);\n for (const dir of dirs) {\n if (fs.existsSync(path.join(dir, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (fs.existsSync(path.join(dir, \"package-lock.json\"))) return \"npm\";\n\n // A yarn or bun lockfile is a stated choice this CLI cannot honour —\n // see the note on `PackageManager`. Say so once, here, rather than\n // scaffolding an npm workspace beside their lockfile without comment.\n for (const [lock, name] of [[\"yarn.lock\", \"yarn\"], [\"bun.lockb\", \"bun\"], [\"bun.lock\", \"bun\"]] as const) {\n if (!fs.existsSync(path.join(dir, lock))) continue;\n console.warn(\n `[rebase] Found ${lock}, but Rebase scaffolds pnpm or npm workspaces only — ` +\n `${name} is not supported yet. Continuing with ` +\n `${isPnpmAvailable() ? \"pnpm\" : \"npm\"}.`\n );\n return isPnpmAvailable() ? \"pnpm\" : \"npm\";\n }\n }\n\n // 2. Prefer pnpm whenever it's installed.\n if (isPnpmAvailable()) return \"pnpm\";\n\n // 3. Fall back to npm only when pnpm is genuinely unavailable.\n return \"npm\";\n}\n\n/** Build the command helpers for a given package manager. */\nexport function getPMCommands(pm: PackageManager): PMCommands {\n if (pm === \"npm\") {\n return {\n name: \"npm\",\n install: [\"npm\", \"install\"],\n run: (script) => [\"npm\", \"run\", script],\n exec: (bin, args) => [\"npx\", bin, ...args],\n view: (pkg, field) => [\"npm\", \"view\", pkg, field],\n runAll: (script) => [\"npm\", \"run\", script, \"--workspaces\", \"--if-present\"],\n runWorkspace: (workspace, script) => [\"npm\", \"run\", script, \"-w\", workspace],\n dlx: (pkg, args) => [\"npx\", \"-y\", pkg, ...args],\n workspaceProtocol: \"*\"\n };\n }\n\n return {\n name: \"pnpm\",\n install: [\"pnpm\", \"install\"],\n run: (script) => [\"pnpm\", \"run\", script],\n exec: (bin, args) => [\"pnpm\", \"exec\", bin, ...args],\n view: (pkg, field) => [\"pnpm\", \"view\", pkg, field],\n runAll: (script) => [\"pnpm\", \"-r\", \"run\", script],\n // Filter by directory (`./backend`), not name: pnpm's `--filter` matches\n // the package *name* (e.g. `my-app-backend`), so a bare `backend` matches\n // nothing. npm's `-w` is path-based, which is why this only bites pnpm.\n runWorkspace: (workspace, script) => [\"pnpm\", \"--filter\", `./${workspace}`, \"run\", script],\n dlx: (pkg, args) => [\"pnpm\", \"dlx\", pkg, ...args],\n workspaceProtocol: \"workspace:*\"\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAkEA,IAAM,wBAAwB;;AAG9B,IAAI;;;;;;;;;;;;;;;AAgBJ,SAAgB,0BAA0B,KAI9B;CACR,MAAM,OAAQ,IAAI,OAAyC;CAG3D,IAAI,SAAS,UAAU,OAAO;CAa9B,IAAI,SAAS,eAAe,IAAI,QAAQ,OAAO;CAG/C,IAAI,IAAI,OAAO,OAAO;CAEtB,OAAO,IAAI,WAAW;AAC1B;;;;;;;;;AAUA,SAAgB,kBAA2B;CACvC,IAAI,wBAAwB,KAAA,GAAW,OAAO;CAC9C,IAAI;EAKA,sBAAsB,0BAJV,UAAU,QAAQ,CAAC,WAAW,GAAG;GACzC,OAAO;GACP,SAAS;EACb,CACgD,CAAG;CACvD,QAAQ;EACJ,sBAAsB;CAC1B;CACA,OAAO;AACX;;;;;;;;;;;;;;;;AAsBA,SAAgB,qBAAqB,WAAoC;CAErE,MAAM,OAAO,CAAC,WAAW,QAAQ,IAAI,CAAC,CAAC,CAAC,QAAQ,MAAmB,CAAC,CAAC,CAAC;CACtE,KAAK,MAAM,OAAO,MAAM;EACpB,IAAI,GAAG,WAAW,KAAK,KAAK,KAAK,gBAAgB,CAAC,GAAG,OAAO;EAC5D,IAAI,GAAG,WAAW,KAAK,KAAK,KAAK,mBAAmB,CAAC,GAAG,OAAO;EAK/D,KAAK,MAAM,CAAC,MAAM,SAAS;GAAC,CAAC,aAAa,MAAM;GAAG,CAAC,aAAa,KAAK;GAAG,CAAC,YAAY,KAAK;EAAC,GAAY;GACpG,IAAI,CAAC,GAAG,WAAW,KAAK,KAAK,KAAK,IAAI,CAAC,GAAG;GAC1C,QAAQ,KACJ,kBAAkB,KAAK,uDACpB,KAAK,yCACL,gBAAgB,IAAI,SAAS,MAAM,EAC1C;GACA,OAAO,gBAAgB,IAAI,SAAS;EACxC;CACJ;CAGA,IAAI,gBAAgB,GAAG,OAAO;CAG9B,OAAO;AACX;;AAGA,SAAgB,cAAc,IAAgC;CAC1D,IAAI,OAAO,OACP,OAAO;EACH,MAAM;EACN,SAAS,CAAC,OAAO,SAAS;EAC1B,MAAM,WAAW;GAAC;GAAO;GAAO;EAAM;EACtC,OAAO,KAAK,SAAS;GAAC;GAAO;GAAK,GAAG;EAAI;EACzC,OAAO,KAAK,UAAU;GAAC;GAAO;GAAQ;GAAK;EAAK;EAChD,SAAS,WAAW;GAAC;GAAO;GAAO;GAAQ;GAAgB;EAAc;EACzE,eAAe,WAAW,WAAW;GAAC;GAAO;GAAO;GAAQ;GAAM;EAAS;EAC3E,MAAM,KAAK,SAAS;GAAC;GAAO;GAAM;GAAK,GAAG;EAAI;EAC9C,mBAAmB;CACvB;CAGJ,OAAO;EACH,MAAM;EACN,SAAS,CAAC,QAAQ,SAAS;EAC3B,MAAM,WAAW;GAAC;GAAQ;GAAO;EAAM;EACvC,OAAO,KAAK,SAAS;GAAC;GAAQ;GAAQ;GAAK,GAAG;EAAI;EAClD,OAAO,KAAK,UAAU;GAAC;GAAQ;GAAQ;GAAK;EAAK;EACjD,SAAS,WAAW;GAAC;GAAQ;GAAM;GAAO;EAAM;EAIhD,eAAe,WAAW,WAAW;GAAC;GAAQ;GAAY,KAAK;GAAa;GAAO;EAAM;EACzF,MAAM,KAAK,SAAS;GAAC;GAAQ;GAAO;GAAK,GAAG;EAAI;EAChD,mBAAmB;CACvB;AACJ"}
|
|
@@ -1,189 +1,10 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
+
import { n as getPMCommands, t as detectPackageManager } from "./package-manager-CXQevsE1.js";
|
|
2
3
|
import chalk from "chalk";
|
|
3
4
|
import path from "path";
|
|
4
5
|
import fs from "fs";
|
|
5
|
-
import { execSync
|
|
6
|
+
import { execSync } from "child_process";
|
|
6
7
|
import dotenv from "dotenv";
|
|
7
|
-
//#region src/utils/package-manager.ts
|
|
8
|
-
/**
|
|
9
|
-
* Package manager detection and command abstraction.
|
|
10
|
-
*
|
|
11
|
-
* Detects whether the user is running pnpm or npm and provides
|
|
12
|
-
* a unified interface for common package-manager operations so
|
|
13
|
-
* the rest of the CLI never has to hardcode a specific PM.
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* How long to wait for `pnpm --version` before giving up on the probe.
|
|
17
|
-
*
|
|
18
|
-
* `pnpm --version` is a cold Node start, and on a machine that is busy — a
|
|
19
|
-
* parallel install, a full test run — it routinely takes seconds. Measured at
|
|
20
|
-
* 630ms, 990ms and 4293ms on three consecutive runs of one developer laptop
|
|
21
|
-
* under load, so the previous 3s budget was inside the normal spread rather
|
|
22
|
-
* than safely outside it.
|
|
23
|
-
*/
|
|
24
|
-
var PNPM_PROBE_TIMEOUT_MS = 5e3;
|
|
25
|
-
/** Memoised result of the probe. pnpm cannot appear or vanish mid-process. */
|
|
26
|
-
var cachedPnpmAvailable;
|
|
27
|
-
/**
|
|
28
|
-
* Decide availability from a `spawnSync` outcome.
|
|
29
|
-
*
|
|
30
|
-
* Split out from the spawn itself so the decision is testable without starting
|
|
31
|
-
* a process — which is what made the old test load-sensitive and occasionally
|
|
32
|
-
* red for reasons that had nothing to do with the code under test.
|
|
33
|
-
*
|
|
34
|
-
* The three outcomes are distinguishable, and the old code conflated two of
|
|
35
|
-
* them by asking only `status === 0`:
|
|
36
|
-
*
|
|
37
|
-
* not installed status null, signal null, error.code ENOENT
|
|
38
|
-
* timed out status null, signal SIGTERM, error.code ETIMEDOUT
|
|
39
|
-
* broken install status non-zero, no error
|
|
40
|
-
*/
|
|
41
|
-
function pnpmAvailabilityFromProbe(res) {
|
|
42
|
-
const code = res.error?.code;
|
|
43
|
-
if (code === "ENOENT") return false;
|
|
44
|
-
if (code === "ETIMEDOUT" || res.signal) return true;
|
|
45
|
-
if (res.error) return false;
|
|
46
|
-
return res.status === 0;
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Whether pnpm is runnable on this machine.
|
|
50
|
-
*
|
|
51
|
-
* Used to decide whether a fresh project can be scaffolded with pnpm. Kept
|
|
52
|
-
* cheap and non-interactive (bounded timeout, output discarded) so it never
|
|
53
|
-
* hangs detection if a corepack shim misbehaves, and memoised so that repeated
|
|
54
|
-
* detection in one CLI run costs one process rather than one per call.
|
|
55
|
-
*/
|
|
56
|
-
function isPnpmAvailable() {
|
|
57
|
-
if (cachedPnpmAvailable !== void 0) return cachedPnpmAvailable;
|
|
58
|
-
try {
|
|
59
|
-
cachedPnpmAvailable = pnpmAvailabilityFromProbe(spawnSync("pnpm", ["--version"], {
|
|
60
|
-
stdio: "ignore",
|
|
61
|
-
timeout: PNPM_PROBE_TIMEOUT_MS
|
|
62
|
-
}));
|
|
63
|
-
} catch {
|
|
64
|
-
cachedPnpmAvailable = false;
|
|
65
|
-
}
|
|
66
|
-
return cachedPnpmAvailable;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Detect the package manager for a Rebase project.
|
|
70
|
-
*
|
|
71
|
-
* Rebase recommends pnpm, so detection prefers it. Crucially, *how the CLI was
|
|
72
|
-
* invoked* (`npx` vs `pnpm dlx`, i.e. `npm_config_user_agent`) is deliberately
|
|
73
|
-
* ignored: running `npx @rebasepro/cli init` says nothing about how the user
|
|
74
|
-
* wants to manage the project they're creating, and letting it pin the scaffold
|
|
75
|
-
* to npm is what made every `npx`-invoked project an npm project.
|
|
76
|
-
*
|
|
77
|
-
* Detection order:
|
|
78
|
-
* 1. An existing lock file — an explicit choice we always respect
|
|
79
|
-
* (`pnpm-lock.yaml` wins over `package-lock.json` when both are present).
|
|
80
|
-
* 2. pnpm, whenever it is installed.
|
|
81
|
-
* 3. npm, only as a fallback when pnpm is genuinely unavailable.
|
|
82
|
-
*/
|
|
83
|
-
function detectPackageManager(targetDir) {
|
|
84
|
-
const dirs = [targetDir, process.cwd()].filter((d) => !!d);
|
|
85
|
-
for (const dir of dirs) {
|
|
86
|
-
if (fs.existsSync(path.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
87
|
-
if (fs.existsSync(path.join(dir, "package-lock.json"))) return "npm";
|
|
88
|
-
for (const [lock, name] of [
|
|
89
|
-
["yarn.lock", "yarn"],
|
|
90
|
-
["bun.lockb", "bun"],
|
|
91
|
-
["bun.lock", "bun"]
|
|
92
|
-
]) {
|
|
93
|
-
if (!fs.existsSync(path.join(dir, lock))) continue;
|
|
94
|
-
console.warn(`[rebase] Found ${lock}, but Rebase scaffolds pnpm or npm workspaces only — ${name} is not supported yet. Continuing with ${isPnpmAvailable() ? "pnpm" : "npm"}.`);
|
|
95
|
-
return isPnpmAvailable() ? "pnpm" : "npm";
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (isPnpmAvailable()) return "pnpm";
|
|
99
|
-
return "npm";
|
|
100
|
-
}
|
|
101
|
-
/** Build the command helpers for a given package manager. */
|
|
102
|
-
function getPMCommands(pm) {
|
|
103
|
-
if (pm === "npm") return {
|
|
104
|
-
name: "npm",
|
|
105
|
-
install: ["npm", "install"],
|
|
106
|
-
run: (script) => [
|
|
107
|
-
"npm",
|
|
108
|
-
"run",
|
|
109
|
-
script
|
|
110
|
-
],
|
|
111
|
-
exec: (bin, args) => [
|
|
112
|
-
"npx",
|
|
113
|
-
bin,
|
|
114
|
-
...args
|
|
115
|
-
],
|
|
116
|
-
view: (pkg, field) => [
|
|
117
|
-
"npm",
|
|
118
|
-
"view",
|
|
119
|
-
pkg,
|
|
120
|
-
field
|
|
121
|
-
],
|
|
122
|
-
runAll: (script) => [
|
|
123
|
-
"npm",
|
|
124
|
-
"run",
|
|
125
|
-
script,
|
|
126
|
-
"--workspaces",
|
|
127
|
-
"--if-present"
|
|
128
|
-
],
|
|
129
|
-
runWorkspace: (workspace, script) => [
|
|
130
|
-
"npm",
|
|
131
|
-
"run",
|
|
132
|
-
script,
|
|
133
|
-
"-w",
|
|
134
|
-
workspace
|
|
135
|
-
],
|
|
136
|
-
dlx: (pkg, args) => [
|
|
137
|
-
"npx",
|
|
138
|
-
"-y",
|
|
139
|
-
pkg,
|
|
140
|
-
...args
|
|
141
|
-
],
|
|
142
|
-
workspaceProtocol: "*"
|
|
143
|
-
};
|
|
144
|
-
return {
|
|
145
|
-
name: "pnpm",
|
|
146
|
-
install: ["pnpm", "install"],
|
|
147
|
-
run: (script) => [
|
|
148
|
-
"pnpm",
|
|
149
|
-
"run",
|
|
150
|
-
script
|
|
151
|
-
],
|
|
152
|
-
exec: (bin, args) => [
|
|
153
|
-
"pnpm",
|
|
154
|
-
"exec",
|
|
155
|
-
bin,
|
|
156
|
-
...args
|
|
157
|
-
],
|
|
158
|
-
view: (pkg, field) => [
|
|
159
|
-
"pnpm",
|
|
160
|
-
"view",
|
|
161
|
-
pkg,
|
|
162
|
-
field
|
|
163
|
-
],
|
|
164
|
-
runAll: (script) => [
|
|
165
|
-
"pnpm",
|
|
166
|
-
"-r",
|
|
167
|
-
"run",
|
|
168
|
-
script
|
|
169
|
-
],
|
|
170
|
-
runWorkspace: (workspace, script) => [
|
|
171
|
-
"pnpm",
|
|
172
|
-
"--filter",
|
|
173
|
-
`./${workspace}`,
|
|
174
|
-
"run",
|
|
175
|
-
script
|
|
176
|
-
],
|
|
177
|
-
dlx: (pkg, args) => [
|
|
178
|
-
"pnpm",
|
|
179
|
-
"dlx",
|
|
180
|
-
pkg,
|
|
181
|
-
...args
|
|
182
|
-
],
|
|
183
|
-
workspaceProtocol: "workspace:*"
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
//#endregion
|
|
187
8
|
//#region src/utils/project.ts
|
|
188
9
|
/**
|
|
189
10
|
* Project discovery utilities for the Rebase CLI.
|
|
@@ -494,6 +315,6 @@ function requireBackendDir(projectRoot) {
|
|
|
494
315
|
return backendDir;
|
|
495
316
|
}
|
|
496
317
|
//#endregion
|
|
497
|
-
export { validateTsxInstallation as _, findBackendDir as a, 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
|
|
318
|
+
export { validateTsxInstallation as _, findBackendDir as a, 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 };
|
|
498
319
|
|
|
499
|
-
//# sourceMappingURL=project-
|
|
320
|
+
//# sourceMappingURL=project-DmaGLmH2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-DmaGLmH2.js","names":[],"sources":["../src/utils/project.ts"],"sourcesContent":["/**\n * Project discovery utilities for the Rebase CLI.\n *\n * These helpers locate the project root, backend directory, .env file,\n * and local binaries — used by all CLI command modules.\n */\nimport fs from \"fs\";\nimport path from \"path\";\nimport { execSync } from \"child_process\";\nimport dotenv from \"dotenv\";\nimport chalk from \"chalk\";\nimport { detectPackageManager, getPMCommands } from \"./package-manager\";\n\n/** The authored project manifest. Its presence alone marks a project root. */\nexport const MANIFEST_FILENAME = \"rebase.json\";\n\n/**\n * Walk up from `startDir` to find the Rebase project root.\n *\n * A directory is the root when it holds a `rebase.json`, or when it holds a\n * `package.json` that either lists `backend` as a workspace or sits beside both\n * `backend/` and `config/`.\n *\n * `rebase.json` is checked first and needs no `package.json` beside it, because\n * the conventions below all describe a repository that *contains the backend*.\n * A repository holding only a frontend — the normal shape once a project's apps\n * live in separate repositories — matches none of them, so without this the\n * tooling could not run there at all.\n */\nexport function findProjectRoot(startDir: string = process.cwd()): string | null {\n let dir = path.resolve(startDir);\n const root = path.parse(dir).root;\n\n while (dir !== root) {\n if (fs.existsSync(path.join(dir, MANIFEST_FILENAME))) {\n return dir;\n }\n\n const pkgPath = path.join(dir, \"package.json\");\n\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\"));\n // Check for workspace-based project (monorepo root)\n if (pkg.workspaces && Array.isArray(pkg.workspaces)) {\n const hasBackend = pkg.workspaces.some((w: string) =>\n w === \"backend\"\n );\n if (hasBackend) return dir;\n }\n } catch {\n // ignore parse errors\n }\n\n // Check for sibling backend directory\n if (fs.existsSync(path.join(dir, \"backend\")) && fs.existsSync(path.join(dir, \"config\"))) {\n return dir;\n }\n }\n\n dir = path.dirname(dir);\n }\n\n return null;\n}\n\n/**\n * Locate the backend directory within the project root.\n */\nexport function findBackendDir(projectRoot: string): string | null {\n const backendDir = path.join(projectRoot, \"backend\");\n return fs.existsSync(backendDir) ? backendDir : null;\n}\n\n/**\n * Detect the active backend plugin (e.g. @rebasepro/server-postgres) from the backend's package.json.\n */\nexport function getActiveBackendPlugin(backendDir: string): string | null {\n const pkgPath = path.join(backendDir, \"package.json\");\n if (!fs.existsSync(pkgPath)) return null;\n\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\"));\n const deps = { ...pkg.dependencies,\n...pkg.devDependencies };\n\n // Collect all @rebasepro/server-* driver plugins (exclude server itself)\n const candidates = Object.keys(deps).filter(\n dep => dep.startsWith(\"@rebasepro/server-\") && dep !== \"@rebasepro/server\"\n );\n\n if (candidates.length === 0) return null;\n\n // Prefer server-postgres — it's the primary supported driver\n if (candidates.includes(\"@rebasepro/server-postgres\")) {\n return \"@rebasepro/server-postgres\";\n }\n\n // Fallback: return the first candidate that actually has a CLI entry point\n for (const candidate of candidates) {\n if (resolvePluginCliScript(backendDir, candidate)) {\n return candidate;\n }\n }\n\n // Last resort: return whatever we found\n return candidates[0];\n } catch {\n // Ignore parse errors\n }\n return null;\n}\n\n/**\n * Resolve the active plugin's CLI script.\n */\nexport function resolvePluginCliScript(backendDir: string, pluginName: string): string | null {\n const candidates: string[] = [];\n\n // Walk up from the backend dir: pnpm links the plugin into\n // backend/node_modules, while npm workspaces hoist it to the project (or an\n // enclosing monorepo) root.\n let dir = path.resolve(backendDir);\n const fsRoot = path.parse(dir).root;\n while (dir !== fsRoot) {\n candidates.push(\n path.join(dir, \"node_modules\", pluginName, \"src\", \"cli.ts\"),\n path.join(dir, \"node_modules\", pluginName, \"dist\", \"cli.js\")\n );\n dir = path.dirname(dir);\n }\n\n candidates.push(\n // For monorepo dev mode:\n path.resolve(backendDir, \"..\", \"..\", \"..\", \"packages\", pluginName.replace(\"@rebasepro/\", \"\"), \"src\", \"cli.ts\"),\n path.resolve(backendDir, \"..\", \"..\", \"packages\", pluginName.replace(\"@rebasepro/\", \"\"), \"src\", \"cli.ts\"),\n path.resolve(backendDir, \"..\", \"packages\", pluginName.replace(\"@rebasepro/\", \"\"), \"src\", \"cli.ts\")\n );\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) return candidate;\n }\n return null;\n}\n\n/**\n * Locate the frontend directory within the project root.\n */\nexport function findFrontendDir(projectRoot: string): string | null {\n const frontendDir = path.join(projectRoot, \"frontend\");\n return fs.existsSync(frontendDir) ? frontendDir : null;\n}\n\n/**\n * Find the .env file. Checks the project root first, then backend.\n */\nexport function findEnvFile(projectRoot: string): string | null {\n const candidates = [\n path.join(projectRoot, \".env\"),\n path.join(projectRoot, \"backend\", \".env\")\n ];\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) return candidate;\n }\n\n return null;\n}\n\n/**\n * Read the project's `.env` into a plain object.\n *\n * One reader, because there were four: `dotenv` in `start`, a hand-rolled\n * `indexOf(\"=\")` loop in `api-keys`, a single-key regex in `auth`, and its own\n * splitting in `cloud env`. `dotenv` is a declared dependency of this package,\n * so the other three existed for no reason and disagreed with the correct one\n * on the two things people actually write in a `.env`:\n *\n * - `export KEY=value`, which the hand-rolled parser keyed as\n * `export KEY` — so the command reported the key as unset while it was\n * right there in the file;\n * - `KEY=value # comment`, whose comment travelled into the value and then\n * into an `Authorization` header, coming back as a 401 with nothing\n * pointing at the cause.\n *\n * Returns `{}` when the project has no `.env`, so callers can treat \"absent\"\n * and \"empty\" alike.\n */\nexport function readEnvFile(projectRoot: string): Record<string, string> {\n const envFile = findEnvFile(projectRoot);\n if (!envFile || !fs.existsSync(envFile)) return {};\n try {\n return dotenv.parse(fs.readFileSync(envFile, \"utf-8\"));\n } catch {\n // An unreadable or malformed file is not worth failing a command over;\n // the caller reports the missing value it was looking for.\n return {};\n }\n}\n\n/**\n * Resolve a binary from the project's node_modules/.bin.\n * Checks backend, root, parent monorepo root, then falls back to PATH.\n */\nexport function resolveLocalBin(projectRoot: string, binName: string): string | null {\n const candidates = [\n path.join(projectRoot, \"backend\", \"node_modules\", \".bin\", binName),\n path.join(projectRoot, \"node_modules\", \".bin\", binName)\n ];\n\n // Also check parent directories (for monorepo setups where app/ is nested)\n let parent = path.dirname(projectRoot);\n const rootDir = path.parse(parent).root;\n while (parent !== rootDir) {\n candidates.push(path.join(parent, \"node_modules\", \".bin\", binName));\n parent = path.dirname(parent);\n }\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) return candidate;\n }\n\n // Fall back to globally installed binary via which\n try {\n const globalPath = execSync(`which ${binName}`, { encoding: \"utf-8\" }).trim();\n if (globalPath && fs.existsSync(globalPath)) return globalPath;\n } catch {\n // not found globally\n }\n\n return null;\n}\n\n/**\n * Resolve the tsx binary. Checks backend node_modules first, then root.\n */\nexport function resolveTsx(projectRoot: string): string | null {\n return resolveLocalBin(projectRoot, \"tsx\");\n}\n\n/**\n * Validate that a resolved tsx binary actually has an intact installation.\n *\n * `resolveLocalBin` only checks whether `node_modules/.bin/tsx` (a symlink)\n * exists. If the pnpm content-addressable store was cleaned or a previous\n * install was interrupted, the symlink can exist while critical files inside\n * the tsx package (e.g. `dist/preflight.cjs`) are missing — causing a\n * confusing MODULE_NOT_FOUND error at runtime.\n *\n * This function follows the symlink, walks up to find the tsx package root\n * (`package.json` with `name: \"tsx\"`), and verifies that `dist/preflight.cjs`\n * is present. Returns `null` when the installation looks healthy, or an\n * error description string when it appears corrupted.\n */\nexport function validateTsxInstallation(tsxBinPath: string): string | null {\n try {\n // Follow the symlink chain to the real tsx entry script\n const realPath = fs.realpathSync(tsxBinPath);\n\n // Walk up from the real binary to locate the tsx package root\n let dir = path.dirname(realPath);\n const fsRoot = path.parse(dir).root;\n for (let depth = 0; depth < 10 && dir !== fsRoot; depth++) {\n const pkgPath = path.join(dir, \"package.json\");\n if (fs.existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(pkgPath, \"utf-8\"));\n if (pkg.name === \"tsx\") {\n // Found the tsx package root — verify critical preload file\n const preflightPath = path.join(dir, \"dist\", \"preflight.cjs\");\n if (!fs.existsSync(preflightPath)) {\n return `tsx package at ${dir} is missing dist/preflight.cjs`;\n }\n return null; // Installation looks healthy\n }\n } catch {\n // Malformed package.json — keep walking\n }\n }\n dir = path.dirname(dir);\n }\n\n // Could not determine tsx root — don't block, assume valid\n return null;\n } catch (err) {\n // realpathSync throws if the symlink target is completely gone\n return `tsx binary symlink is broken: ${err instanceof Error ? err.message : String(err)}`;\n }\n}\n\n/**\n * The one sentence a command says when the project's dependencies are missing.\n *\n * Six commands each had their own wording for the same state, and none of them\n * named the remedy:\n *\n * ✗ Could not find CLI entry point for @rebasepro/server-postgres.\n * ✗ Could not find tsx binary.\n * ✗ Could not find tsx binary for backend.\n *\n * All three mean \"you have not installed yet\" — `getActiveBackendPlugin` has\n * already read the driver out of `backend/package.json` by the time the first\n * one fires, so the package is declared and simply not on disk, and `tsx` is a\n * devDependency of every scaffold. But they read as *Rebase* being broken, and\n * they name an internal path or a binary the developer never asked for rather\n * than the command that fixes it. That is the whole of the failure someone sees\n * on a fresh clone, where `node_modules/` is the one thing a checkout does not\n * carry.\n *\n * So: one sentence, naming the package manager this project uses and the\n * directory to run it in — the working directory is usually neither.\n */\nexport function dependenciesNotInstalled(projectRoot: string): string {\n const install = getPMCommands(detectPackageManager(projectRoot)).install.join(\" \");\n return `Dependencies are not installed — run \\`${install}\\` in ${projectRoot}`;\n}\n\n/**\n * Print {@link dependenciesNotInstalled} and exit 1.\n *\n * For the five commands that report and stop. `db.ts` throws instead, because\n * its caller adds the `✗` and the exit itself.\n */\nexport function exitDependenciesNotInstalled(projectRoot: string): never {\n console.error(chalk.red(`✗ ${dependenciesNotInstalled(projectRoot)}`));\n process.exit(1);\n}\n\n/** One problem inside a refusal, when the command found several. */\nexport interface JsonFailureIssue {\n /** Where it is — a config path, a manifest key, a file. */\n path?: string;\n message: string;\n}\n\n/**\n * The refusal envelope, for a caller that asked for JSON.\n *\n * Same shape as the cloud family's `fail()` — `{ error: { message, code, hint } }`\n * — because there is one CLI and a caller should not have to know which half of\n * it answered. `code` is what a caller branches on and is never absent; the\n * cloud family's reasoning about that applies here unchanged. `issues` carries\n * the per-problem detail a human reader gets as a bullet list, because a\n * refusal that says \"2 problem(s)\" and does not say which two is not\n * machine-readable in any useful sense.\n *\n * On **stdout**, like every other `--json` result: the contract those commands\n * make is that stdout holds one JSON value, and a caller that pipes stdout to a\n * parser must get a parseable refusal rather than an empty stream and a\n * human sentence it never sees. That contract used to hold for exactly one\n * failure of each command — the one that goes through `requireProjectRoot` —\n * while every other exit of the *same* command wrote human text to stderr and\n * left stdout empty. So `rebase status --json` was parseable outside a project\n * and unparseable inside a broken one, which is the case a caller actually has\n * to handle.\n *\n * The flag is read off `process.argv` because the failure happens inside a\n * helper the command calls before it has parsed anything. Coarse — a literal\n * `--json` as some other flag's value would count — and worth it: the failure\n * mode of being coarse is a JSON error where a human one was wanted, and the\n * failure mode of not doing it is an unparseable stream.\n */\nexport function failAsJson(\n message: string,\n code: string,\n hint?: string,\n issues?: JsonFailureIssue[]\n): never {\n const error: Record<string, unknown> = { message, code };\n if (hint !== undefined) error.hint = hint;\n if (issues !== undefined) error.issues = issues;\n console.log(JSON.stringify({ error }, null, 2));\n process.exit(1);\n}\n\n/** Did the command line ask for machine-readable output? */\nexport function wantsJsonOutput(argv: readonly string[] = process.argv): boolean {\n return argv.includes(\"--json\");\n}\n\n/**\n * Require the project root or exit with a helpful error.\n */\nexport function requireProjectRoot(): string {\n const root = findProjectRoot();\n if (!root) {\n if (wantsJsonOutput()) {\n // `rebase status --json` outside a project wrote four grey lines to\n // stderr and nothing at all to stdout, so the agent or CI step that\n // asked for JSON got an empty parse and no reason.\n failAsJson(\n \"Could not find a Rebase project root.\",\n \"no_project_root\",\n `Looked in this directory and every parent for a ${MANIFEST_FILENAME}, `\n + \"a package.json with a \\\"backend\\\" workspace, or a backend/ next to a config/.\"\n );\n }\n // Name what is actually looked for, in the order `findProjectRoot`\n // looks for it. The old wording (\"backend/, frontend/, and config/\")\n // described neither the manifest — which is the primary marker and the\n // only one `rebase init` writes — nor a `--headless` project, which has\n // no frontend at all and would read this as \"you are in the wrong\n // place\" while standing in the right one.\n console.error(chalk.red(\"✗ Could not find a Rebase project root.\"));\n console.error(chalk.gray(` Looked in this directory and every parent for a ${MANIFEST_FILENAME},`));\n console.error(chalk.gray(\" a package.json with a \\\"backend\\\" workspace, or a backend/ next to a config/.\"));\n console.error(chalk.gray(\" Run this from inside a project, or create one with `rebase init`.\"));\n process.exit(1);\n }\n return root;\n}\n\n/**\n * Require the backend directory or exit with a helpful error.\n */\nexport function requireBackendDir(projectRoot: string): string {\n const backendDir = findBackendDir(projectRoot);\n if (!backendDir) {\n if (wantsJsonOutput()) {\n failAsJson(\n \"Could not find a backend/ directory.\",\n \"no_backend_dir\",\n `Expected at: ${path.join(projectRoot, \"backend\")}`\n );\n }\n console.error(chalk.red(\"✗ Could not find a backend/ directory.\"));\n console.error(chalk.gray(` Expected at: ${path.join(projectRoot, \"backend\")}`));\n process.exit(1);\n }\n return backendDir;\n}\n"],"mappings":"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"}
|
package/dist/upgrade.d.ts
CHANGED
|
@@ -58,8 +58,20 @@ export interface ProjectFiles {
|
|
|
58
58
|
packageJsons: string[];
|
|
59
59
|
workspaceYamls: string[];
|
|
60
60
|
}
|
|
61
|
-
/**
|
|
62
|
-
export
|
|
61
|
+
/** `git -C <cwd> …`, returning stdout. Throws when git is missing or refuses. */
|
|
62
|
+
export type GitRunner = (cwd: string, args: string[]) => string;
|
|
63
|
+
/**
|
|
64
|
+
* Every `package.json` and `pnpm-workspace.yaml` under the project root that
|
|
65
|
+
* belongs to the project.
|
|
66
|
+
*
|
|
67
|
+
* In a git repository that means what git lists: a gitignored build context,
|
|
68
|
+
* scratch copy or vendored package is not the project's, and rewriting it
|
|
69
|
+
* would change files nobody committed. The walk enters only directories that
|
|
70
|
+
* hold a listed file, so an ignored tree is not read at all. Outside a
|
|
71
|
+
* repository every directory is walked. {@link skipDirectory} applies either
|
|
72
|
+
* way.
|
|
73
|
+
*/
|
|
74
|
+
export declare function discoverProjectFiles(projectRoot: string, git?: GitRunner): ProjectFiles;
|
|
63
75
|
/** Where a catalog pin sits: `catalog`, or `catalogs.<name>` for a named one. */
|
|
64
76
|
export type CatalogField = "catalog" | `catalogs.${string}`;
|
|
65
77
|
export interface ChangedPin {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The rule, as every help page and error states it. */
|
|
2
|
+
export declare const COLLECTIONS_PATH_RULE = "relative paths are resolved from where you run the command";
|
|
3
|
+
/** The `--collections` line of a help page. */
|
|
4
|
+
export declare const COLLECTIONS_FLAG_HELP = "Collections directory (default: config/collections); relative paths are resolved from where you run the command";
|
|
5
|
+
/**
|
|
6
|
+
* The refusal for a `--collections` path that does not exist, or `null`.
|
|
7
|
+
*
|
|
8
|
+
* Checked by the CLI, in the reader's terms, before the driver is spawned: the
|
|
9
|
+
* driver's own check runs in `backend/` and can only describe the path it was
|
|
10
|
+
* handed. A generator pointed at a directory that is not there writes an empty
|
|
11
|
+
* schema, and a push of an empty schema drops every table — so nothing runs.
|
|
12
|
+
*
|
|
13
|
+
* Scripts written for the old rule (`--collections ../config/collections`,
|
|
14
|
+
* run from the root) land here, and the message says exactly what to change.
|
|
15
|
+
*/
|
|
16
|
+
export declare function missingCollectionsPath(args: readonly string[], cwd: string, backendDir: string): string[] | null;
|
|
17
|
+
/** Print {@link missingCollectionsPath}'s refusal and exit 1, or return. */
|
|
18
|
+
export declare function refuseMissingCollectionsPath(args: readonly string[], cwd: string, backendDir: string): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
//#region src/utils/version.ts
|
|
5
|
+
/**
|
|
6
|
+
* This CLI's own version, and the User-Agent built from it.
|
|
7
|
+
*
|
|
8
|
+
* Four call sites used to answer "what version am I?" and each answered it
|
|
9
|
+
* differently. `cli.ts` walked one directory up from `__dirname`. `init.ts`
|
|
10
|
+
* walked up looking for a folder literally named `cli`. `bundle.ts` had
|
|
11
|
+
* `resolveCliVersion`, which walked up at most five levels for a manifest whose
|
|
12
|
+
* `name` matched — the only one of the three that was actually correct, and the
|
|
13
|
+
* one the other two did not know existed. Every HTTP request to the control
|
|
14
|
+
* plane answered it not at all, which is the expensive gap: a control plane
|
|
15
|
+
* cannot refuse a CLI that is too old for its wire format if no request ever
|
|
16
|
+
* says which CLI is calling.
|
|
17
|
+
*
|
|
18
|
+
* One answer now, and it is `bundle.ts`'s: walk up for the manifest that names
|
|
19
|
+
* this package, rather than counting directories or matching a folder name.
|
|
20
|
+
* `dist/` is one level down in a published install and two in a source
|
|
21
|
+
* checkout, and a folder called `cli` is not guaranteed to be ours. Two
|
|
22
|
+
* differences from that original — `fileURLToPath` instead of `new
|
|
23
|
+
* URL().pathname`, which is what makes it survive a path containing a space,
|
|
24
|
+
* and no five-level ceiling.
|
|
25
|
+
*/
|
|
26
|
+
function readVersion() {
|
|
27
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const root = path.parse(dir).root;
|
|
29
|
+
while (dir && dir !== root) {
|
|
30
|
+
const manifest = path.join(dir, "package.json");
|
|
31
|
+
if (fs.existsSync(manifest)) try {
|
|
32
|
+
const pkg = JSON.parse(fs.readFileSync(manifest, "utf-8"));
|
|
33
|
+
if (pkg.name === "@rebasepro/cli" && typeof pkg.version === "string") return pkg.version;
|
|
34
|
+
} catch {}
|
|
35
|
+
dir = path.dirname(dir);
|
|
36
|
+
}
|
|
37
|
+
return "unknown";
|
|
38
|
+
}
|
|
39
|
+
var cached;
|
|
40
|
+
/** The CLI's version, or `"unknown"` when it cannot be read. Memoized. */
|
|
41
|
+
function cliVersion() {
|
|
42
|
+
if (cached === void 0) cached = readVersion();
|
|
43
|
+
return cached;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* `rebase-cli/0.17.3` — sent on every control-plane request.
|
|
47
|
+
*
|
|
48
|
+
* The control plane needs it to answer `CLI_TOO_OLD` with a minimum version
|
|
49
|
+
* instead of failing somewhere further in with a shape error. A client that
|
|
50
|
+
* does not identify itself can only ever be refused generically.
|
|
51
|
+
*/
|
|
52
|
+
function cliUserAgent() {
|
|
53
|
+
return `rebase-cli/${cliVersion()}`;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { cliVersion as n, cliUserAgent as t };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=version-DsgBmP22.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-DsgBmP22.js","names":[],"sources":["../src/utils/version.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\n/**\n * This CLI's own version, and the User-Agent built from it.\n *\n * Four call sites used to answer \"what version am I?\" and each answered it\n * differently. `cli.ts` walked one directory up from `__dirname`. `init.ts`\n * walked up looking for a folder literally named `cli`. `bundle.ts` had\n * `resolveCliVersion`, which walked up at most five levels for a manifest whose\n * `name` matched — the only one of the three that was actually correct, and the\n * one the other two did not know existed. Every HTTP request to the control\n * plane answered it not at all, which is the expensive gap: a control plane\n * cannot refuse a CLI that is too old for its wire format if no request ever\n * says which CLI is calling.\n *\n * One answer now, and it is `bundle.ts`'s: walk up for the manifest that names\n * this package, rather than counting directories or matching a folder name.\n * `dist/` is one level down in a published install and two in a source\n * checkout, and a folder called `cli` is not guaranteed to be ours. Two\n * differences from that original — `fileURLToPath` instead of `new\n * URL().pathname`, which is what makes it survive a path containing a space,\n * and no five-level ceiling.\n */\nfunction readVersion(): string {\n let dir = path.dirname(fileURLToPath(import.meta.url));\n const root = path.parse(dir).root;\n while (dir && dir !== root) {\n const manifest = path.join(dir, \"package.json\");\n if (fs.existsSync(manifest)) {\n try {\n const pkg = JSON.parse(fs.readFileSync(manifest, \"utf-8\"));\n if (pkg.name === \"@rebasepro/cli\" && typeof pkg.version === \"string\") return pkg.version;\n } catch {\n // A malformed manifest on the way up is not ours to complain\n // about; keep walking.\n }\n }\n dir = path.dirname(dir);\n }\n return \"unknown\";\n}\n\nlet cached: string | undefined;\n\n/** The CLI's version, or `\"unknown\"` when it cannot be read. Memoized. */\nexport function cliVersion(): string {\n if (cached === undefined) cached = readVersion();\n return cached;\n}\n\n/**\n * `rebase-cli/0.17.3` — sent on every control-plane request.\n *\n * The control plane needs it to answer `CLI_TOO_OLD` with a minimum version\n * instead of failing somewhere further in with a shape error. A client that\n * does not identify itself can only ever be refused generically.\n */\nexport function cliUserAgent(): string {\n return `rebase-cli/${cliVersion()}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAS,cAAsB;CAC3B,IAAI,MAAM,KAAK,QAAQ,cAAc,OAAO,KAAK,GAAG,CAAC;CACrD,MAAM,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC;CAC7B,OAAO,OAAO,QAAQ,MAAM;EACxB,MAAM,WAAW,KAAK,KAAK,KAAK,cAAc;EAC9C,IAAI,GAAG,WAAW,QAAQ,GACtB,IAAI;GACA,MAAM,MAAM,KAAK,MAAM,GAAG,aAAa,UAAU,OAAO,CAAC;GACzD,IAAI,IAAI,SAAS,oBAAoB,OAAO,IAAI,YAAY,UAAU,OAAO,IAAI;EACrF,QAAQ,CAGR;EAEJ,MAAM,KAAK,QAAQ,GAAG;CAC1B;CACA,OAAO;AACX;AAEA,IAAI;;AAGJ,SAAgB,aAAqB;CACjC,IAAI,WAAW,KAAA,GAAW,SAAS,YAAY;CAC/C,OAAO;AACX;;;;;;;;AASA,SAAgB,eAAuB;CACnC,OAAO,cAAc,WAAW;AACpC"}
|