@rebasepro/cli 0.13.0 → 0.13.1-canary.g18cfeb7
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/dist/commands/auth.d.ts +31 -0
- package/dist/commands/cloud/databases.d.ts +1 -0
- package/dist/commands/cloud/debug.d.ts +1 -0
- package/dist/commands/cloud/domains.d.ts +1 -0
- package/dist/commands/cloud/env.d.ts +1 -0
- package/dist/commands/cloud/extensions.d.ts +1 -0
- package/dist/commands/cloud/orgs.d.ts +1 -0
- package/dist/commands/cloud/resources.d.ts +1 -0
- package/dist/commands/cloud/settings.d.ts +1 -0
- package/dist/commands/dev.d.ts +0 -7
- package/dist/commands/init.d.ts +22 -0
- package/dist/index.es.js +367 -76
- package/dist/index.es.js.map +1 -1
- package/dist/telemetry/payload.d.ts +1 -1
- package/dist/utils/collection-drift.d.ts +27 -0
- package/dist/utils/project.d.ts +20 -0
- package/package.json +7 -7
- package/templates/eject/docker-compose.custom.yml +4 -4
- package/templates/template/.env.example +2 -2
- package/templates/template/config/package.json +1 -0
- package/templates/template/docker-compose.yml +4 -4
package/dist/index.es.js
CHANGED
|
@@ -12,8 +12,9 @@ import crypto from "crypto";
|
|
|
12
12
|
import { execSync, spawn, spawnSync } from "child_process";
|
|
13
13
|
import os from "os";
|
|
14
14
|
import { createRebaseClient } from "@rebasepro/client";
|
|
15
|
+
import dotenv from "dotenv";
|
|
15
16
|
import { createRequire } from "module";
|
|
16
|
-
import { BUNDLE_FORMAT_VERSION, RUNTIME_CONTRACT_VERSION, computeSchemaVersion, deserializeCollections, findStorageSuffixCollision, normalizeStorageSources, storageEnvSuffix } from "@rebasepro/types";
|
|
17
|
+
import { BUNDLE_FORMAT_VERSION, DEFAULT_DATA_SOURCE_KEY, RUNTIME_CONTRACT_VERSION, computeSchemaVersion, deserializeCollections, findStorageSuffixCollision, getDataSourceCapabilities, normalizeStorageSources, storageEnvSuffix } from "@rebasepro/types";
|
|
17
18
|
import { generateSDK } from "@rebasepro/codegen";
|
|
18
19
|
//#region src/utils/package-manager.ts
|
|
19
20
|
/**
|
|
@@ -289,6 +290,34 @@ function findEnvFile(projectRoot) {
|
|
|
289
290
|
return null;
|
|
290
291
|
}
|
|
291
292
|
/**
|
|
293
|
+
* Read the project's `.env` into a plain object.
|
|
294
|
+
*
|
|
295
|
+
* One reader, because there were four: `dotenv` in `start`, a hand-rolled
|
|
296
|
+
* `indexOf("=")` loop in `api-keys`, a single-key regex in `auth`, and its own
|
|
297
|
+
* splitting in `cloud env`. `dotenv` is a declared dependency of this package,
|
|
298
|
+
* so the other three existed for no reason and disagreed with the correct one
|
|
299
|
+
* on the two things people actually write in a `.env`:
|
|
300
|
+
*
|
|
301
|
+
* - `export KEY=value`, which the hand-rolled parser keyed as
|
|
302
|
+
* `export KEY` — so the command reported the key as unset while it was
|
|
303
|
+
* right there in the file;
|
|
304
|
+
* - `KEY=value # comment`, whose comment travelled into the value and then
|
|
305
|
+
* into an `Authorization` header, coming back as a 401 with nothing
|
|
306
|
+
* pointing at the cause.
|
|
307
|
+
*
|
|
308
|
+
* Returns `{}` when the project has no `.env`, so callers can treat "absent"
|
|
309
|
+
* and "empty" alike.
|
|
310
|
+
*/
|
|
311
|
+
function readEnvFile(projectRoot) {
|
|
312
|
+
const envFile = findEnvFile(projectRoot);
|
|
313
|
+
if (!envFile || !fs.existsSync(envFile)) return {};
|
|
314
|
+
try {
|
|
315
|
+
return dotenv.parse(fs.readFileSync(envFile, "utf-8"));
|
|
316
|
+
} catch {
|
|
317
|
+
return {};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
292
321
|
* Resolve a binary from the project's node_modules/.bin.
|
|
293
322
|
* Checks backend, root, parent monorepo root, then falls back to PATH.
|
|
294
323
|
*/
|
|
@@ -1789,7 +1818,7 @@ async function createProject$1(options) {
|
|
|
1789
1818
|
console.log(` ${chalk.cyan(runDev.join(" "))}`);
|
|
1790
1819
|
}
|
|
1791
1820
|
console.log("");
|
|
1792
|
-
console.log(isBaas ? chalk.gray("This starts a headless API (Hono + PostgreSQL). There are no collection files: ") + chalk.gray("the API is derived from your database schema. Once it serves a table, docs are at /api/swagger.") : chalk.gray("This starts both the backend (Hono + PostgreSQL)") + chalk.gray(" and the frontend (Vite + React) concurrently."));
|
|
1821
|
+
console.log(isBaas ? introspected ? chalk.gray("This starts a headless API (Hono + PostgreSQL) over the collections just ") + chalk.gray("generated from your database in config/collections. Edit them to change what the ") + chalk.gray("API exposes; docs are at /api/swagger.") : chalk.gray("This starts a headless API (Hono + PostgreSQL). There are no collection files: ") + chalk.gray("the API is derived from your database schema. Once it serves a table, docs are at /api/swagger.") : chalk.gray("This starts both the backend (Hono + PostgreSQL)") + chalk.gray(" and the frontend (Vite + React) concurrently."));
|
|
1793
1822
|
console.log("");
|
|
1794
1823
|
console.log(chalk.gray("Docs: https://rebase.pro/docs"));
|
|
1795
1824
|
console.log(chalk.gray("GitHub: https://github.com/rebasepro/rebase"));
|
|
@@ -2013,6 +2042,32 @@ function readCliVersion() {
|
|
|
2013
2042
|
} catch {}
|
|
2014
2043
|
return "latest";
|
|
2015
2044
|
}
|
|
2045
|
+
/**
|
|
2046
|
+
* The runtime image tag to pin, given the version of the CLI doing the scaffolding.
|
|
2047
|
+
*
|
|
2048
|
+
* Only a stable release publishes `rebasepro/server` — a multi-arch build on
|
|
2049
|
+
* every push to main would cost minutes per commit for an image nobody pulls.
|
|
2050
|
+
* So pinning a prerelease CLI's own version writes a tag that cannot exist, and
|
|
2051
|
+
* `docker compose up` fails on `manifest unknown`, which is the same dead end
|
|
2052
|
+
* as the missing-repository bug this pinning was added to prevent.
|
|
2053
|
+
*
|
|
2054
|
+
* A prerelease therefore falls back to `latest`, which is correct rather than
|
|
2055
|
+
* merely available: a bundle's manifest declares the runtime range it needs
|
|
2056
|
+
* (`^1`), the image supplies only `@rebasepro/server`, and the framework a
|
|
2057
|
+
* bundle runs is installed from its own `deps.declared` at boot. The current
|
|
2058
|
+
* stable runtime boots a canary bundle by design.
|
|
2059
|
+
*
|
|
2060
|
+
* A floating tag is a real cost — it is what pinning exists to avoid — so say
|
|
2061
|
+
* so in the file rather than leaving a reader to discover it.
|
|
2062
|
+
*/
|
|
2063
|
+
function resolveRuntimeImageTag(cliVersion) {
|
|
2064
|
+
if (/^\d+\.\d+\.\d+-/.test(cliVersion)) return {
|
|
2065
|
+
tag: "latest",
|
|
2066
|
+
note: `# Scaffolded by a prerelease CLI (${cliVersion}), which publishes no runtime image,\n# so this floats to the newest stable runtime. Pin an exact version once you
|
|
2067
|
+
# deploy: a moving tag changes what you are running with no version changing.`
|
|
2068
|
+
};
|
|
2069
|
+
return { tag: cliVersion };
|
|
2070
|
+
}
|
|
2016
2071
|
async function configureEnvFile(targetDirectory, databaseUrl) {
|
|
2017
2072
|
const envExamplePath = path.join(targetDirectory, ".env.example");
|
|
2018
2073
|
const envPath = path.join(targetDirectory, ".env");
|
|
@@ -2031,8 +2086,9 @@ async function configureEnvFile(targetDirectory, databaseUrl) {
|
|
|
2031
2086
|
envContent = envContent.replace(/^#\s*REBASE_SERVICE_KEY=.*$/m, `REBASE_SERVICE_KEY=${serviceKey}`);
|
|
2032
2087
|
const composeApiPort = /^PORT=(\d+)/m.exec(envContent)?.[1] ?? "3001";
|
|
2033
2088
|
envContent = envContent.replace(/^#\s*CORS_ORIGINS=.*$/m, `CORS_ORIGINS=http://localhost:${composeApiPort}`);
|
|
2034
|
-
const runtimeVersion = readCliVersion();
|
|
2035
|
-
|
|
2089
|
+
const { tag: runtimeVersion, note } = resolveRuntimeImageTag(readCliVersion());
|
|
2090
|
+
const pinned = `${note ? `${note}\n` : ""}REBASE_VERSION=${runtimeVersion}`;
|
|
2091
|
+
envContent = /^#?\s*REBASE_VERSION=.*$/m.test(envContent) ? envContent.replace(/^#?\s*REBASE_VERSION=.*$/m, pinned) : `${envContent.trimEnd()}\n\n# The Rebase runtime image tag docker-compose.yml pulls.\n# Change this and restart to upgrade; your project bundle is untouched.\n${pinned}\n`;
|
|
2036
2092
|
if (databaseUrl) {
|
|
2037
2093
|
if (/[\r\n]/.test(databaseUrl)) throw new Error("Invalid DATABASE_URL: multiline values are not allowed.");
|
|
2038
2094
|
const { pinSearchPath } = await import("@rebasepro/server-postgres");
|
|
@@ -2040,7 +2096,7 @@ async function configureEnvFile(targetDirectory, databaseUrl) {
|
|
|
2040
2096
|
envContent = envContent.replace(/^DATABASE_URL=.*$/m, `DATABASE_URL=${pinnedUrl}\nDATABASE_PASSWORD=${dbPassword}`);
|
|
2041
2097
|
} else {
|
|
2042
2098
|
const dbPort = await findAvailablePort(5432);
|
|
2043
|
-
envContent = envContent.replace(/^DATABASE_URL=.*$/m, `DATABASE_URL=postgresql://
|
|
2099
|
+
envContent = envContent.replace(/^DATABASE_URL=.*$/m, `DATABASE_URL=postgresql://rebase_app:${dbPassword}@localhost:${dbPort}/rebase?options=-c%20search_path=public&sslmode=disable\nDATABASE_PASSWORD=${dbPassword}`);
|
|
2044
2100
|
const dockerComposePath = path.join(targetDirectory, "docker-compose.yml");
|
|
2045
2101
|
if (fs.existsSync(dockerComposePath)) {
|
|
2046
2102
|
let dockerComposeContent = fs.readFileSync(dockerComposePath, "utf-8");
|
|
@@ -2367,7 +2423,7 @@ async function schemaCommand(subcommand, rawArgs) {
|
|
|
2367
2423
|
return;
|
|
2368
2424
|
}
|
|
2369
2425
|
const projectRoot = requireProjectRoot();
|
|
2370
|
-
recordEvent("cli.
|
|
2426
|
+
recordEvent("cli.schema", { subcommand: subcommand ?? "none" }, { projectRoot });
|
|
2371
2427
|
const backendDir = requireBackendDir(projectRoot);
|
|
2372
2428
|
const activePlugin = getActiveBackendPlugin(backendDir);
|
|
2373
2429
|
if (!activePlugin) {
|
|
@@ -2436,7 +2492,7 @@ async function dbCommand(subcommand, rawArgs) {
|
|
|
2436
2492
|
return;
|
|
2437
2493
|
}
|
|
2438
2494
|
const projectRoot = requireProjectRoot();
|
|
2439
|
-
recordEvent("cli.
|
|
2495
|
+
recordEvent("cli.db", { subcommand: subcommand ?? "none" }, { projectRoot });
|
|
2440
2496
|
const backendDir = requireBackendDir(projectRoot);
|
|
2441
2497
|
const activePlugin = getActiveBackendPlugin(backendDir);
|
|
2442
2498
|
if (!activePlugin) {
|
|
@@ -3074,6 +3130,106 @@ function resolveBackendPaths(app, projectRoot) {
|
|
|
3074
3130
|
};
|
|
3075
3131
|
}
|
|
3076
3132
|
//#endregion
|
|
3133
|
+
//#region src/utils/collection-drift.ts
|
|
3134
|
+
/**
|
|
3135
|
+
* Which edits under `config/collections` can put the *SQL* schema out of date.
|
|
3136
|
+
*
|
|
3137
|
+
* `rebase dev` watches that directory and, on any change, either tells the
|
|
3138
|
+
* developer to run `rebase schema generate` / `rebase db push` or runs them.
|
|
3139
|
+
* The watcher is recursive and knows nothing about what it is watching, so it
|
|
3140
|
+
* said that about every file under the directory — including a
|
|
3141
|
+
* `collections/firestore/exercises.ts`, whose documents live in Firestore and
|
|
3142
|
+
* for which there is no Drizzle schema to regenerate and no database to push
|
|
3143
|
+
* to. Advice that is wrong on every edit is advice a developer learns to
|
|
3144
|
+
* ignore, which is worse than none: the same box is the only warning for the
|
|
3145
|
+
* Postgres collection next to it, where it is real.
|
|
3146
|
+
*
|
|
3147
|
+
* Two independent reasons a change cannot affect the SQL schema, both checked
|
|
3148
|
+
* here:
|
|
3149
|
+
*
|
|
3150
|
+
* 1. **The loader would never read the file.** `loadCollectionsFromDirectory`
|
|
3151
|
+
* reads the top level of the collections directory only — no recursion, no
|
|
3152
|
+
* `index`, no tests, no declarations. A file it does not read cannot change
|
|
3153
|
+
* what it returns, and the watcher must not claim otherwise.
|
|
3154
|
+
* 2. **Every collection in it is served by another engine.** A Firestore or
|
|
3155
|
+
* MongoDB collection has no table, no migration and no policies.
|
|
3156
|
+
*
|
|
3157
|
+
* The engine check reads the source text rather than importing the module: the
|
|
3158
|
+
* CLI runs as plain Node and cannot evaluate a project's TypeScript, and a
|
|
3159
|
+
* watcher must answer in the time between two keystrokes. Anything it cannot
|
|
3160
|
+
* read confidently counts as SQL-affecting — a spurious warning is a nuisance,
|
|
3161
|
+
* a suppressed one hides real drift.
|
|
3162
|
+
*/
|
|
3163
|
+
/**
|
|
3164
|
+
* Would `loadCollectionsFromDirectory` load this file?
|
|
3165
|
+
*
|
|
3166
|
+
* Mirrors that loader's own `isCollectionFile` plus its flat (non-recursive)
|
|
3167
|
+
* scan. `relativePath` is relative to the collections directory, as `fs.watch`
|
|
3168
|
+
* reports it.
|
|
3169
|
+
*/
|
|
3170
|
+
function isLoadedCollectionFile(relativePath) {
|
|
3171
|
+
const normalized = relativePath.split(path.sep).join("/");
|
|
3172
|
+
if (normalized.includes("/")) return false;
|
|
3173
|
+
const file = normalized;
|
|
3174
|
+
if (!file.endsWith(".ts") && !file.endsWith(".js")) return false;
|
|
3175
|
+
if (file.startsWith(".")) return false;
|
|
3176
|
+
if (file.includes(".test.")) return false;
|
|
3177
|
+
if (file.endsWith(".d.ts")) return false;
|
|
3178
|
+
if (file === "index.ts" || file === "index.js") return false;
|
|
3179
|
+
return true;
|
|
3180
|
+
}
|
|
3181
|
+
var ENGINE_LITERAL = /\bengine\s*:\s*["'`]([^"'`]+)["'`]/g;
|
|
3182
|
+
var DATA_SOURCE_LITERAL = /\bdataSource\s*:\s*["'`]([^"'`]+)["'`]/g;
|
|
3183
|
+
/**
|
|
3184
|
+
* Drop comments, so a `// engine: "firestore"` in a docblock cannot silence a
|
|
3185
|
+
* warning about the Postgres collection the file actually declares.
|
|
3186
|
+
*
|
|
3187
|
+
* Deliberately naive — it does not understand strings, so a `//` inside one
|
|
3188
|
+
* (a URL in a default value) eats the rest of that line. That only ever removes
|
|
3189
|
+
* text, and removing an engine literal makes this file *more* likely to warn,
|
|
3190
|
+
* which is the side to be wrong on.
|
|
3191
|
+
*/
|
|
3192
|
+
function stripComments(source) {
|
|
3193
|
+
return source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\n]*/g, " ");
|
|
3194
|
+
}
|
|
3195
|
+
function literals(source, pattern) {
|
|
3196
|
+
const found = [];
|
|
3197
|
+
for (const match of source.matchAll(pattern)) found.push(match[1]);
|
|
3198
|
+
return found;
|
|
3199
|
+
}
|
|
3200
|
+
/**
|
|
3201
|
+
* Does this collection source declare anything a SQL toolchain would own?
|
|
3202
|
+
*
|
|
3203
|
+
* The fallback order matches `resolveDataSource`: an explicit `engine` wins,
|
|
3204
|
+
* and a collection that only names a `dataSource` is resolved as if the key
|
|
3205
|
+
* were the engine — which is what that function does when no registry is
|
|
3206
|
+
* available, and the CLI has none. An engine nobody recognises counts as
|
|
3207
|
+
* relational, for the reason `isRelationalCollection` gives.
|
|
3208
|
+
*
|
|
3209
|
+
* A file declaring several collections is SQL-affecting if *any* of them is.
|
|
3210
|
+
*/
|
|
3211
|
+
function declaresRelationalCollection(rawSource) {
|
|
3212
|
+
const source = stripComments(rawSource);
|
|
3213
|
+
const engines = literals(source, ENGINE_LITERAL);
|
|
3214
|
+
const declared = engines.length > 0 ? engines : literals(source, DATA_SOURCE_LITERAL).filter((key) => key !== DEFAULT_DATA_SOURCE_KEY);
|
|
3215
|
+
if (declared.length === 0) return true;
|
|
3216
|
+
return declared.some((engine) => getDataSourceCapabilities(engine).supportsRelations);
|
|
3217
|
+
}
|
|
3218
|
+
/**
|
|
3219
|
+
* Can this edit have changed the generated SQL schema?
|
|
3220
|
+
*
|
|
3221
|
+
* Answers `true` when it cannot tell — an unreadable file is a reason to warn,
|
|
3222
|
+
* not a reason to go quiet.
|
|
3223
|
+
*/
|
|
3224
|
+
function affectsSqlSchema(collectionsDir, relativePath) {
|
|
3225
|
+
if (!isLoadedCollectionFile(relativePath)) return false;
|
|
3226
|
+
try {
|
|
3227
|
+
return declaresRelationalCollection(fs.readFileSync(path.join(collectionsDir, relativePath), "utf8"));
|
|
3228
|
+
} catch {
|
|
3229
|
+
return true;
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
//#endregion
|
|
3077
3233
|
//#region src/commands/dev.ts
|
|
3078
3234
|
/**
|
|
3079
3235
|
* CLI command: rebase dev
|
|
@@ -3167,14 +3323,33 @@ function getProjectPort(projectRoot) {
|
|
|
3167
3323
|
* 3. Previously used port from .rebase-dev-port (port affinity across restarts)
|
|
3168
3324
|
* 4. Deterministic hash from project path (unique per project)
|
|
3169
3325
|
*/
|
|
3326
|
+
/**
|
|
3327
|
+
* A TCP port, or `undefined` for anything that is not one.
|
|
3328
|
+
*
|
|
3329
|
+
* One predicate for both sources below. The port file was already checked for
|
|
3330
|
+
* range, and `PORT` — the source a human or a platform actually sets — was not,
|
|
3331
|
+
* so `PORT=oops` reached `parseInt` and was returned as `NaN`: the dev server
|
|
3332
|
+
* then bound to whatever the OS handed out and the CLI printed a URL for a port
|
|
3333
|
+
* nothing was listening on.
|
|
3334
|
+
*/
|
|
3335
|
+
function parsePort(raw) {
|
|
3336
|
+
if (raw === void 0) return void 0;
|
|
3337
|
+
const port = Number(raw.trim());
|
|
3338
|
+
if (!Number.isInteger(port) || port <= 0 || port >= 65536) return void 0;
|
|
3339
|
+
return port;
|
|
3340
|
+
}
|
|
3170
3341
|
function resolveStartPort(projectRoot, explicitPort) {
|
|
3171
3342
|
if (explicitPort) return explicitPort;
|
|
3172
|
-
if (process.env.PORT)
|
|
3343
|
+
if (process.env.PORT) {
|
|
3344
|
+
const fromEnv = parsePort(process.env.PORT);
|
|
3345
|
+
if (fromEnv !== void 0) return fromEnv;
|
|
3346
|
+
console.warn(chalk.yellow(` ⚠ Ignoring PORT="${process.env.PORT}" — not a port between 1 and 65535.`));
|
|
3347
|
+
}
|
|
3173
3348
|
try {
|
|
3174
3349
|
const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
|
|
3175
3350
|
if (fs.existsSync(portFile)) {
|
|
3176
|
-
const saved =
|
|
3177
|
-
if (saved
|
|
3351
|
+
const saved = parsePort(fs.readFileSync(portFile, "utf-8"));
|
|
3352
|
+
if (saved !== void 0) return saved;
|
|
3178
3353
|
}
|
|
3179
3354
|
} catch {}
|
|
3180
3355
|
return getProjectPort(projectRoot);
|
|
@@ -3188,7 +3363,7 @@ async function devCommand(rawArgs) {
|
|
|
3188
3363
|
"--help": Boolean,
|
|
3189
3364
|
"-b": "--backend-only",
|
|
3190
3365
|
"-f": "--frontend-only",
|
|
3191
|
-
"-
|
|
3366
|
+
"-P": "--port",
|
|
3192
3367
|
"-g": "--generate",
|
|
3193
3368
|
"-h": "--help"
|
|
3194
3369
|
}, {
|
|
@@ -3345,6 +3520,20 @@ async function devCommand(rawArgs) {
|
|
|
3345
3520
|
} catch {}
|
|
3346
3521
|
/** Whether the frontend has been launched (we only launch it once). */
|
|
3347
3522
|
let frontendLaunched = false;
|
|
3523
|
+
try {
|
|
3524
|
+
const activePlugin = getActiveBackendPlugin(backendDir);
|
|
3525
|
+
const pluginCli = activePlugin ? resolvePluginCliScript(backendDir, activePlugin) : null;
|
|
3526
|
+
if (pluginCli) await execa(tsxBin, [
|
|
3527
|
+
pluginCli,
|
|
3528
|
+
"schema",
|
|
3529
|
+
"stale",
|
|
3530
|
+
"--fix"
|
|
3531
|
+
], {
|
|
3532
|
+
cwd: backendDir,
|
|
3533
|
+
stdio: "inherit",
|
|
3534
|
+
env
|
|
3535
|
+
});
|
|
3536
|
+
} catch {}
|
|
3348
3537
|
if (shouldGenerate) {
|
|
3349
3538
|
console.log(chalk.gray(" → Ensuring schema and SDK are generated on start..."));
|
|
3350
3539
|
try {
|
|
@@ -3372,14 +3561,18 @@ async function devCommand(rawArgs) {
|
|
|
3372
3561
|
const collectionsDir = path.join(projectRoot, "config", "collections");
|
|
3373
3562
|
if (fs.existsSync(collectionsDir)) {
|
|
3374
3563
|
let watchDebounce = null;
|
|
3564
|
+
let sqlSchemaAffected = false;
|
|
3375
3565
|
fs.watch(collectionsDir, { recursive: true }, (eventType, filename) => {
|
|
3376
3566
|
if (!filename || filename.startsWith(".") || filename.endsWith(".tmp")) return;
|
|
3567
|
+
sqlSchemaAffected = sqlSchemaAffected || affectsSqlSchema(collectionsDir, filename);
|
|
3377
3568
|
if (watchDebounce) clearTimeout(watchDebounce);
|
|
3378
3569
|
watchDebounce = setTimeout(async () => {
|
|
3379
|
-
|
|
3570
|
+
const regenerateSchema = sqlSchemaAffected;
|
|
3571
|
+
sqlSchemaAffected = false;
|
|
3572
|
+
console.log(chalk.yellow(`\n 🔄 Collection change detected (${filename}). Regenerating ${regenerateSchema ? "schema & SDK" : "SDK"}...`));
|
|
3380
3573
|
try {
|
|
3381
3574
|
const activePlugin = getActiveBackendPlugin(backendDir);
|
|
3382
|
-
const pluginCli = activePlugin ? resolvePluginCliScript(backendDir, activePlugin) : null;
|
|
3575
|
+
const pluginCli = regenerateSchema && activePlugin ? resolvePluginCliScript(backendDir, activePlugin) : null;
|
|
3383
3576
|
if (pluginCli) await execa(tsxBin, [
|
|
3384
3577
|
pluginCli,
|
|
3385
3578
|
"schema",
|
|
@@ -3395,7 +3588,7 @@ async function devCommand(rawArgs) {
|
|
|
3395
3588
|
stdio: "inherit",
|
|
3396
3589
|
env
|
|
3397
3590
|
});
|
|
3398
|
-
console.log(chalk.green(
|
|
3591
|
+
console.log(chalk.green(` ✓ ${regenerateSchema ? "Schema & SDK" : "SDK"} regenerated successfully. Hono will reload.`));
|
|
3399
3592
|
} catch (err) {
|
|
3400
3593
|
console.error(chalk.red(` ✗ Failed to regenerate schema/SDK: ${err instanceof Error ? err.message : err}`));
|
|
3401
3594
|
}
|
|
@@ -3420,12 +3613,14 @@ async function devCommand(rawArgs) {
|
|
|
3420
3613
|
let driftDebounce = null;
|
|
3421
3614
|
fs.watch(collectionsDir, { recursive: true }, (_eventType, filename) => {
|
|
3422
3615
|
if (!filename || filename.startsWith(".") || filename.endsWith(".tmp")) return;
|
|
3616
|
+
if (!affectsSqlSchema(collectionsDir, filename)) return;
|
|
3423
3617
|
if (driftDebounce) clearTimeout(driftDebounce);
|
|
3424
3618
|
driftDebounce = setTimeout(() => {
|
|
3619
|
+
const shown = filename.length > 31 ? `…${filename.slice(-30)}` : filename.padEnd(31);
|
|
3425
3620
|
console.log([
|
|
3426
3621
|
"",
|
|
3427
3622
|
chalk.yellow(" ┌──────────────────────────────────────────────────────────────┐"),
|
|
3428
|
-
chalk.yellow(" │ ⚠️ Collection file changed: ") + chalk.white(
|
|
3623
|
+
chalk.yellow(" │ ⚠️ Collection file changed: ") + chalk.white(shown) + chalk.yellow("│"),
|
|
3429
3624
|
chalk.yellow(" │ │"),
|
|
3430
3625
|
chalk.yellow(" │ Your schema may be out of sync. Run: │"),
|
|
3431
3626
|
chalk.yellow(" │ ") + chalk.cyan("rebase schema generate") + chalk.yellow(" regenerate Drizzle schema │"),
|
|
@@ -4732,7 +4927,8 @@ ${chalk.bold("Examples")}
|
|
|
4732
4927
|
}
|
|
4733
4928
|
async function buildCommand(rawArgs = []) {
|
|
4734
4929
|
const args = arg({
|
|
4735
|
-
"--
|
|
4930
|
+
"--output": String,
|
|
4931
|
+
"--out": "--output",
|
|
4736
4932
|
"--skip-type-check": Boolean,
|
|
4737
4933
|
"--skip-schema": Boolean,
|
|
4738
4934
|
"--no-static": Boolean,
|
|
@@ -4800,7 +4996,7 @@ async function buildCommand(rawArgs = []) {
|
|
|
4800
4996
|
projectRoot,
|
|
4801
4997
|
appName: name,
|
|
4802
4998
|
app,
|
|
4803
|
-
outDir: args["--
|
|
4999
|
+
outDir: args["--output"],
|
|
4804
5000
|
runtimeRange: manifest.rebase,
|
|
4805
5001
|
storage: manifest.storage,
|
|
4806
5002
|
skipTypeCheck: args["--skip-type-check"],
|
|
@@ -4837,7 +5033,7 @@ async function buildCommand(rawArgs = []) {
|
|
|
4837
5033
|
});
|
|
4838
5034
|
for (const outcome of folded ?? []) console.log(chalk.green(` ✓ ${outcome.appName} folded in`) + chalk.dim(` (${outcome.fileCount} file(s) → served at ${outcome.path})`));
|
|
4839
5035
|
}
|
|
4840
|
-
} else if (app.type === "static") await buildAssetApp(projectRoot, name, app, manifest.rebase, args["--
|
|
5036
|
+
} else if (app.type === "static") await buildAssetApp(projectRoot, name, app, manifest.rebase, args["--output"]);
|
|
4841
5037
|
console.log("");
|
|
4842
5038
|
}
|
|
4843
5039
|
console.log(chalk.green("✓ Build complete."));
|
|
@@ -5286,6 +5482,48 @@ async function startWorkspaceBackend(projectRoot, env) {
|
|
|
5286
5482
|
* Subcommands:
|
|
5287
5483
|
* reset-password — Reset a user's password
|
|
5288
5484
|
*/
|
|
5485
|
+
/**
|
|
5486
|
+
* Pick the user with exactly this email out of a search response.
|
|
5487
|
+
*
|
|
5488
|
+
* `/api/admin/users?search=` is an `ILIKE '%…%'` over email **or display
|
|
5489
|
+
* name**, ordered by role count descending. This used to take row `[0]` and
|
|
5490
|
+
* reset it, then print the email it had been *given* as confirmation — so two
|
|
5491
|
+
* ordinary situations ended in a successful-looking reset of somebody else's
|
|
5492
|
+
* account:
|
|
5493
|
+
*
|
|
5494
|
+
* - a substring collision: `bob@example.com` also matches
|
|
5495
|
+
* `robert.bob@example.com`;
|
|
5496
|
+
* - a display name, which is user-controlled and accepted up to 255
|
|
5497
|
+
* characters with no constraint on its content, containing an address
|
|
5498
|
+
* belonging to someone else.
|
|
5499
|
+
*
|
|
5500
|
+
* The ordering makes it worse rather than better — `array_length(roles) DESC
|
|
5501
|
+
* NULLS LAST` puts the most privileged match first, so the account most likely
|
|
5502
|
+
* to be reset by mistake is an admin's.
|
|
5503
|
+
*
|
|
5504
|
+
* Returns `undefined` when nothing matched exactly, which the caller reports
|
|
5505
|
+
* rather than falling through to a guess. The direct-database fallback below
|
|
5506
|
+
* has always matched with `eq(usersTable.email, email)`; this is the same
|
|
5507
|
+
* definition, so the command no longer resets different accounts depending on
|
|
5508
|
+
* whether the backend happened to be running.
|
|
5509
|
+
*/
|
|
5510
|
+
function selectUserForEmail(payload, email) {
|
|
5511
|
+
const wanted = email.trim().toLowerCase();
|
|
5512
|
+
if (!wanted) return void 0;
|
|
5513
|
+
const rows = Array.isArray(payload) ? payload : payload && typeof payload === "object" && Array.isArray(payload.users) ? payload.users : [];
|
|
5514
|
+
for (const row of rows) {
|
|
5515
|
+
if (!row || typeof row !== "object") continue;
|
|
5516
|
+
const record = row;
|
|
5517
|
+
const rowEmail = typeof record.email === "string" ? record.email.trim().toLowerCase() : void 0;
|
|
5518
|
+
if (!rowEmail || rowEmail !== wanted) continue;
|
|
5519
|
+
const id = typeof record.id === "string" ? record.id : typeof record.uid === "string" ? record.uid : void 0;
|
|
5520
|
+
if (!id) continue;
|
|
5521
|
+
return {
|
|
5522
|
+
id,
|
|
5523
|
+
email: record.email
|
|
5524
|
+
};
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5289
5527
|
async function authCommand(subcommand, rawArgs) {
|
|
5290
5528
|
if (!subcommand || subcommand === "--help") {
|
|
5291
5529
|
printAuthHelp();
|
|
@@ -5306,8 +5544,7 @@ async function resetPassword(rawArgs) {
|
|
|
5306
5544
|
const args = arg({
|
|
5307
5545
|
"--email": String,
|
|
5308
5546
|
"--password": String,
|
|
5309
|
-
"-e": "--email"
|
|
5310
|
-
"-p": "--password"
|
|
5547
|
+
"-e": "--email"
|
|
5311
5548
|
}, {
|
|
5312
5549
|
argv: rawArgs.slice(4),
|
|
5313
5550
|
permissive: true
|
|
@@ -5322,12 +5559,8 @@ async function resetPassword(rawArgs) {
|
|
|
5322
5559
|
process.exit(1);
|
|
5323
5560
|
}
|
|
5324
5561
|
const projectRoot = requireProjectRoot();
|
|
5325
|
-
let envServiceKey;
|
|
5326
5562
|
const envFile = findEnvFile(projectRoot);
|
|
5327
|
-
|
|
5328
|
-
const match = fs.readFileSync(envFile, "utf8").match(/^\s*REBASE_SERVICE_KEY\s*=\s*['"]?(.*?)['"]?\s*$/m);
|
|
5329
|
-
if (match && match[1]) envServiceKey = match[1];
|
|
5330
|
-
} catch {}
|
|
5563
|
+
const envServiceKey = readEnvFile(projectRoot).REBASE_SERVICE_KEY;
|
|
5331
5564
|
let baseUrl = process.env.REBASE_BASE_URL;
|
|
5332
5565
|
let serviceKey = process.env.REBASE_SERVICE_KEY || envServiceKey;
|
|
5333
5566
|
const statePath = path.join(projectRoot, ".rebase", "state.json");
|
|
@@ -5347,7 +5580,7 @@ async function resetPassword(rawArgs) {
|
|
|
5347
5580
|
try {
|
|
5348
5581
|
const finalPass = newPassword || "NewPassword123!";
|
|
5349
5582
|
const cleanBaseUrl = baseUrl.replace(/\/+$/, "");
|
|
5350
|
-
const searchUrl = `${cleanBaseUrl}/api/admin/users?search=${encodeURIComponent(email)}&limit=
|
|
5583
|
+
const searchUrl = `${cleanBaseUrl}/api/admin/users?search=${encodeURIComponent(email)}&limit=50`;
|
|
5351
5584
|
const searchRes = await fetch(searchUrl, { headers: {
|
|
5352
5585
|
"Authorization": `Bearer ${serviceKey}`,
|
|
5353
5586
|
"Accept": "application/json"
|
|
@@ -5355,18 +5588,9 @@ async function resetPassword(rawArgs) {
|
|
|
5355
5588
|
if (!searchRes.ok) throw new Error(`Failed to list users: ${searchRes.statusText}`);
|
|
5356
5589
|
const searchData = await searchRes.json();
|
|
5357
5590
|
if (!searchData || typeof searchData !== "object") throw new Error("Invalid response format from user search API.");
|
|
5358
|
-
|
|
5359
|
-
if (
|
|
5360
|
-
|
|
5361
|
-
if (firstUser && typeof firstUser === "object" && "id" in firstUser && typeof firstUser.id === "string") userId = firstUser.id;
|
|
5362
|
-
else if (firstUser && typeof firstUser === "object" && "uid" in firstUser && typeof firstUser.uid === "string") userId = firstUser.uid;
|
|
5363
|
-
} else if ("users" in searchData && Array.isArray(searchData.users)) {
|
|
5364
|
-
const firstUser = searchData.users[0];
|
|
5365
|
-
if (firstUser && typeof firstUser === "object" && "id" in firstUser && typeof firstUser.id === "string") userId = firstUser.id;
|
|
5366
|
-
else if (firstUser && typeof firstUser === "object" && "uid" in firstUser && typeof firstUser.uid === "string") userId = firstUser.uid;
|
|
5367
|
-
}
|
|
5368
|
-
if (!userId) throw new Error(`User not found with email: ${email}`);
|
|
5369
|
-
const resetUrl = `${cleanBaseUrl}/api/admin/users/${userId}/reset-password`;
|
|
5591
|
+
const matched = selectUserForEmail(searchData, email);
|
|
5592
|
+
if (!matched) throw new Error(`No user has the email ${email}.`);
|
|
5593
|
+
const resetUrl = `${cleanBaseUrl}/api/admin/users/${matched.id}/reset-password`;
|
|
5370
5594
|
const resetRes = await fetch(resetUrl, {
|
|
5371
5595
|
method: "POST",
|
|
5372
5596
|
headers: {
|
|
@@ -5383,7 +5607,7 @@ async function resetPassword(rawArgs) {
|
|
|
5383
5607
|
console.log("API reset successful.");
|
|
5384
5608
|
console.log(chalk.bold(" 🔑 Rebase Auth — Reset Password (via API)"));
|
|
5385
5609
|
console.log("");
|
|
5386
|
-
console.log(` ${chalk.gray("Email:")} ${email}`);
|
|
5610
|
+
console.log(` ${chalk.gray("Email:")} ${matched.email}`);
|
|
5387
5611
|
console.log(` ${chalk.gray("Password:")} ${finalPass}`);
|
|
5388
5612
|
console.log("");
|
|
5389
5613
|
return;
|
|
@@ -5451,10 +5675,12 @@ async function resetPassword() {
|
|
|
5451
5675
|
if (result.length > 0) {
|
|
5452
5676
|
console.log("✅ Password reset for: " + result[0].email);
|
|
5453
5677
|
${!newPassword ? "console.log(\" New password: \" + newPassword);" : ""}
|
|
5454
|
-
|
|
5455
|
-
console.log("✗ User not found: " + email);
|
|
5678
|
+
process.exit(0);
|
|
5456
5679
|
}
|
|
5457
|
-
|
|
5680
|
+
// Nothing was updated, so nothing was reset. Exiting 0 here reported
|
|
5681
|
+
// success for a no-op, which is what a script would have believed.
|
|
5682
|
+
console.error("✗ User not found: " + email);
|
|
5683
|
+
process.exit(1);
|
|
5458
5684
|
}
|
|
5459
5685
|
|
|
5460
5686
|
resetPassword().catch(console.error);
|
|
@@ -5472,11 +5698,20 @@ resetPassword().catch(console.error);
|
|
|
5472
5698
|
stdio: "inherit",
|
|
5473
5699
|
env
|
|
5474
5700
|
});
|
|
5701
|
+
const cleanup = () => {
|
|
5702
|
+
try {
|
|
5703
|
+
fs.unlinkSync(tmpScriptPath);
|
|
5704
|
+
} catch {}
|
|
5705
|
+
};
|
|
5475
5706
|
return new Promise((resolve) => {
|
|
5707
|
+
child.on("error", (err) => {
|
|
5708
|
+
cleanup();
|
|
5709
|
+
console.error(chalk.red("✗ Could not run the reset script."));
|
|
5710
|
+
console.error(chalk.gray(` ${err.message}`));
|
|
5711
|
+
process.exit(1);
|
|
5712
|
+
});
|
|
5476
5713
|
child.on("close", (code) => {
|
|
5477
|
-
|
|
5478
|
-
fs.unlinkSync(tmpScriptPath);
|
|
5479
|
-
} catch {}
|
|
5714
|
+
cleanup();
|
|
5480
5715
|
if (code !== 0) process.exit(code ?? 1);
|
|
5481
5716
|
resolve();
|
|
5482
5717
|
});
|
|
@@ -5514,7 +5749,34 @@ ${chalk.green.bold("Examples")}
|
|
|
5514
5749
|
* Detects three-way schema drift between collection definitions,
|
|
5515
5750
|
* the generated Drizzle schema, and the live PostgreSQL database.
|
|
5516
5751
|
*/
|
|
5752
|
+
/**
|
|
5753
|
+
* `--help` is answered before the project guard, not after.
|
|
5754
|
+
*
|
|
5755
|
+
* `doctor` declared no `--help` at all, so the flag fell through to the command
|
|
5756
|
+
* body and hit `requireProjectRoot()` — and `rebase doctor --help` outside a
|
|
5757
|
+
* project answered "✗ Could not find a Rebase project root." Asking a command
|
|
5758
|
+
* what it does is the one question that cannot require being somewhere
|
|
5759
|
+
* particular to ask.
|
|
5760
|
+
*/
|
|
5761
|
+
function printDoctorHelp() {
|
|
5762
|
+
console.log(`
|
|
5763
|
+
${chalk.bold("rebase doctor")} — Detect drift between collections, schema and database
|
|
5764
|
+
|
|
5765
|
+
${chalk.green.bold("Usage")}
|
|
5766
|
+
rebase doctor
|
|
5767
|
+
|
|
5768
|
+
Compares the collections you declare, the generated Drizzle schema, and the
|
|
5769
|
+
tables that actually exist, then reports what disagrees and how to reconcile it.
|
|
5770
|
+
|
|
5771
|
+
Run from inside a Rebase project — it reads the project's collections and
|
|
5772
|
+
connects to its database.
|
|
5773
|
+
`);
|
|
5774
|
+
}
|
|
5517
5775
|
async function doctorCommand(rawArgs) {
|
|
5776
|
+
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
5777
|
+
printDoctorHelp();
|
|
5778
|
+
return;
|
|
5779
|
+
}
|
|
5518
5780
|
const projectRoot = requireProjectRoot();
|
|
5519
5781
|
const backendDir = requireBackendDir(projectRoot);
|
|
5520
5782
|
const activePlugin = getActiveBackendPlugin(backendDir);
|
|
@@ -5663,7 +5925,8 @@ async function skillsCommand(subcommand, rawArgs) {
|
|
|
5663
5925
|
}
|
|
5664
5926
|
/**
|
|
5665
5927
|
* Agents named explicitly on the command line, e.g. `--agent claude --agent cursor`
|
|
5666
|
-
* (also accepts a comma-separated list). Returns null when none were
|
|
5928
|
+
* (also accepts a comma-separated list, and `all`). Returns null when none were
|
|
5929
|
+
* given.
|
|
5667
5930
|
*/
|
|
5668
5931
|
function parseAgentFlags(rawArgs) {
|
|
5669
5932
|
const requested = [];
|
|
@@ -5673,6 +5936,7 @@ function parseAgentFlags(rawArgs) {
|
|
|
5673
5936
|
if (value && !value.startsWith("-")) requested.push(...value.split(",").map((v) => v.trim()).filter(Boolean));
|
|
5674
5937
|
}
|
|
5675
5938
|
if (requested.length === 0) return null;
|
|
5939
|
+
if (requested.includes("all")) return Object.keys(AGENTS);
|
|
5676
5940
|
const valid = Object.keys(AGENTS);
|
|
5677
5941
|
const unknown = requested.filter((a) => !valid.includes(a));
|
|
5678
5942
|
if (unknown.length > 0) {
|
|
@@ -5700,6 +5964,7 @@ async function skillsInstall(rawArgs = []) {
|
|
|
5700
5964
|
if (!process.stdin.isTTY) {
|
|
5701
5965
|
console.error(chalk.red("Cannot prompt: this is a non-interactive terminal (no TTY)."));
|
|
5702
5966
|
console.error(chalk.yellow(` Name the agents explicitly, e.g. rebase skills install --agent ${Object.keys(AGENTS)[0]}`));
|
|
5967
|
+
console.error(chalk.yellow(" Or install for every supported agent: rebase skills install --agent all"));
|
|
5703
5968
|
console.error(chalk.gray(` Available: ${Object.keys(AGENTS).join(", ")}`));
|
|
5704
5969
|
process.exit(1);
|
|
5705
5970
|
}
|
|
@@ -5747,13 +6012,16 @@ ${chalk.green.bold("Subcommands")}
|
|
|
5747
6012
|
|
|
5748
6013
|
${chalk.green.bold("Options")}
|
|
5749
6014
|
${chalk.blue("--agent, -a")} Agent(s) to install for, skipping detection and the prompt.
|
|
5750
|
-
Repeat the flag or pass a comma-separated list.
|
|
5751
|
-
|
|
6015
|
+
Repeat the flag or pass a comma-separated list, or ${chalk.bold("all")}.
|
|
6016
|
+
Required without a TTY: a scaffolded project carries a marker
|
|
6017
|
+
file for every agent, so detection cannot pick one for you.
|
|
6018
|
+
Available: ${Object.keys(AGENTS).join(", ")}, all
|
|
5752
6019
|
|
|
5753
6020
|
${chalk.green.bold("Examples")}
|
|
5754
6021
|
${chalk.cyan("rebase skills install")}
|
|
5755
6022
|
${chalk.cyan("rebase skills install --agent claude")}
|
|
5756
6023
|
${chalk.cyan("rebase skills install --agent claude,cursor")}
|
|
6024
|
+
${chalk.cyan("rebase skills install --agent all")} ${chalk.gray("# scripted / CI")}
|
|
5757
6025
|
`);
|
|
5758
6026
|
}
|
|
5759
6027
|
//#endregion
|
|
@@ -5766,25 +6034,13 @@ ${chalk.green.bold("Examples")}
|
|
|
5766
6034
|
* create — Create a new API key
|
|
5767
6035
|
* revoke — Revoke an existing API key
|
|
5768
6036
|
*/
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
5777
|
-
const idx = trimmed.indexOf("=");
|
|
5778
|
-
if (idx > 0) {
|
|
5779
|
-
const key = trimmed.slice(0, idx).trim();
|
|
5780
|
-
let value = trimmed.slice(idx + 1).trim();
|
|
5781
|
-
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
5782
|
-
env[key] = value;
|
|
5783
|
-
}
|
|
5784
|
-
}
|
|
5785
|
-
}
|
|
5786
|
-
return env;
|
|
5787
|
-
}
|
|
6037
|
+
/**
|
|
6038
|
+
* Was a hand-rolled `indexOf("=")` loop. It keyed `export KEY=value` as
|
|
6039
|
+
* `export KEY` and carried a trailing `# comment` into the value — so a key
|
|
6040
|
+
* that was present read as absent, or reached an `Authorization` header with a
|
|
6041
|
+
* comment attached and came back 401. See `readEnvFile`.
|
|
6042
|
+
*/
|
|
6043
|
+
var loadEnv = readEnvFile;
|
|
5788
6044
|
function resolveBaseUrl(env, projectRoot) {
|
|
5789
6045
|
if (env.REBASE_BASE_URL) return env.REBASE_BASE_URL;
|
|
5790
6046
|
if (projectRoot) try {
|
|
@@ -6055,7 +6311,12 @@ ${chalk.green.bold("Examples")}
|
|
|
6055
6311
|
* a documentation comment that quietly fell out of date two releases ago.
|
|
6056
6312
|
*/
|
|
6057
6313
|
async function telemetryCommand(rawArgs) {
|
|
6058
|
-
|
|
6314
|
+
const subcommand = rawArgs.slice(3).filter((a) => !a.startsWith("-"))[0];
|
|
6315
|
+
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
6316
|
+
printHelp$2();
|
|
6317
|
+
return;
|
|
6318
|
+
}
|
|
6319
|
+
switch (subcommand) {
|
|
6059
6320
|
case "status":
|
|
6060
6321
|
case void 0:
|
|
6061
6322
|
printStatus();
|
|
@@ -6154,8 +6415,7 @@ async function loginCommand(rawArgs) {
|
|
|
6154
6415
|
const args = arg({
|
|
6155
6416
|
"--email": String,
|
|
6156
6417
|
"--password": String,
|
|
6157
|
-
"-e": "--email"
|
|
6158
|
-
"-p": "--password"
|
|
6418
|
+
"-e": "--email"
|
|
6159
6419
|
}, {
|
|
6160
6420
|
argv: rawArgs.slice(3),
|
|
6161
6421
|
permissive: true
|
|
@@ -8162,7 +8422,8 @@ async function revealEnv(rawArgs) {
|
|
|
8162
8422
|
}
|
|
8163
8423
|
async function pullEnv(rawArgs) {
|
|
8164
8424
|
const args = arg({
|
|
8165
|
-
"--
|
|
8425
|
+
"--output": String,
|
|
8426
|
+
"--out": "--output",
|
|
8166
8427
|
"--yes": Boolean,
|
|
8167
8428
|
"-y": "--yes",
|
|
8168
8429
|
"--project": String,
|
|
@@ -8174,7 +8435,7 @@ async function pullEnv(rawArgs) {
|
|
|
8174
8435
|
const { client } = await requireClient(rawArgs);
|
|
8175
8436
|
const projectId = await requireProject(rawArgs, client);
|
|
8176
8437
|
displayProjectRef(rawArgs);
|
|
8177
|
-
const outPath = path.resolve(args["--
|
|
8438
|
+
const outPath = path.resolve(args["--output"] || ".env");
|
|
8178
8439
|
try {
|
|
8179
8440
|
const list = await fetchEnvVars(client, projectId);
|
|
8180
8441
|
if (fs.existsSync(outPath)) await confirmDestructive({
|
|
@@ -10293,15 +10554,43 @@ function positionals(rawArgs) {
|
|
|
10293
10554
|
while (i < rest.length && rest[i].startsWith("-")) i++;
|
|
10294
10555
|
return rest.slice(i);
|
|
10295
10556
|
}
|
|
10557
|
+
/**
|
|
10558
|
+
* The help page for each group, keyed by every alias the dispatch below accepts.
|
|
10559
|
+
*
|
|
10560
|
+
* Aliases are listed explicitly rather than normalised first, so a group that
|
|
10561
|
+
* gains one and forgets it here degrades to the index page — wrong, but a page.
|
|
10562
|
+
* `cloud-help.test.ts` asserts the two stay in step.
|
|
10563
|
+
*
|
|
10564
|
+
* A group absent from this map has no page of its own; the index lists it.
|
|
10565
|
+
*/
|
|
10566
|
+
var GROUP_HELP = {
|
|
10567
|
+
env: printEnvHelp,
|
|
10568
|
+
domains: printDomainsHelp,
|
|
10569
|
+
domain: printDomainsHelp,
|
|
10570
|
+
extensions: printExtensionsHelp,
|
|
10571
|
+
extension: printExtensionsHelp,
|
|
10572
|
+
settings: printSettingsHelp,
|
|
10573
|
+
orgs: printOrgsHelp,
|
|
10574
|
+
org: printOrgsHelp,
|
|
10575
|
+
db: printDbHelp,
|
|
10576
|
+
database: printDbHelp,
|
|
10577
|
+
debug: printDebugHelp,
|
|
10578
|
+
storage: printStorageHelp
|
|
10579
|
+
};
|
|
10296
10580
|
async function cloudCommand(subcommand, rawArgs) {
|
|
10297
10581
|
initOutputMode(rawArgs);
|
|
10298
10582
|
const pos = positionals(rawArgs);
|
|
10299
10583
|
const group = pos[0] ?? (subcommand !== "--help" ? subcommand : void 0);
|
|
10584
|
+
const wantsHelp = rawArgs.includes("--help") || rawArgs.includes("-h");
|
|
10300
10585
|
const action = pos[1];
|
|
10301
|
-
if (!group
|
|
10586
|
+
if (!group) {
|
|
10302
10587
|
printCloudHelp();
|
|
10303
10588
|
return;
|
|
10304
10589
|
}
|
|
10590
|
+
if (wantsHelp) {
|
|
10591
|
+
(GROUP_HELP[group] ?? printCloudHelp)();
|
|
10592
|
+
return;
|
|
10593
|
+
}
|
|
10305
10594
|
switch (group) {
|
|
10306
10595
|
case "login":
|
|
10307
10596
|
await loginCommand(rawArgs);
|
|
@@ -10741,7 +11030,7 @@ async function entry(args) {
|
|
|
10741
11030
|
printHelp();
|
|
10742
11031
|
return;
|
|
10743
11032
|
}
|
|
10744
|
-
const effectiveSubcommand = parsedArgs["--help"] ? "--help" : subcommand;
|
|
11033
|
+
const effectiveSubcommand = parsedArgs["--help"] && !subcommand ? "--help" : subcommand;
|
|
10745
11034
|
switch (command) {
|
|
10746
11035
|
case "init":
|
|
10747
11036
|
await createRebaseApp(args);
|
|
@@ -10860,9 +11149,11 @@ ${chalk.green.bold("API Keys")}
|
|
|
10860
11149
|
${chalk.blue.bold("api-keys list")} List all service API keys
|
|
10861
11150
|
${chalk.blue.bold("api-keys create")} Create a new scoped API key
|
|
10862
11151
|
${chalk.blue.bold("api-keys revoke")} Revoke an existing API key
|
|
10863
|
-
${chalk.blue.bold("telemetry")} Anonymous usage sharing (opt-in, off by default)
|
|
10864
11152
|
${chalk.blue.bold("api-keys")} ${chalk.gray("--help")} Show API key command help
|
|
10865
11153
|
|
|
11154
|
+
${chalk.green.bold("Usage sharing")}
|
|
11155
|
+
${chalk.blue.bold("telemetry")} Anonymous usage sharing (opt-in, off by default)
|
|
11156
|
+
|
|
10866
11157
|
${chalk.green.bold("Rebase Cloud")}
|
|
10867
11158
|
${chalk.blue.bold("cloud login")} Sign in to the hosted control plane
|
|
10868
11159
|
${chalk.blue.bold("cloud link")} Link this directory to a cloud project
|
|
@@ -10893,6 +11184,6 @@ function telemetryNotice() {
|
|
|
10893
11184
|
return chalk.gray(`Usage sharing: ${sharing ? "on" : "off"} — ${chalk.cyan("rebase telemetry")} to inspect or change\n`);
|
|
10894
11185
|
}
|
|
10895
11186
|
//#endregion
|
|
10896
|
-
export { CURRENT_RUNTIME_RANGE, DEFAULT_BUNDLE_DIR, DEFAULT_CONFIG_DIR, DEFAULT_CRONS_DIR, DEFAULT_FUNCTIONS_DIR, DEFAULT_SCHEMA_FILE, DEV_PORT_FILENAME, MANIFEST_FILENAME, ManifestError, TEMPLATE_PLACEHOLDER_FILES, appsCommand, assessManagedCompatibility, authCommand, buildBundle, buildCommand, buildInitQuestions, buildStaticBundle, buildableApps, cloudCommand, collectDeclaredDependencies, configureEnvFile, createRebaseApp, dbCommand, detectFrameworkDepDrift, detectNativeDependencies, detectPackageManager, detectStorageAuthorize, devCommand, doctorCommand, ejectCommand, entry, findBackendApp, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, findUnusedServerEntry, foldStaticIntoBundle, formatCdTarget, generateSdkCommand, getActiveBackendPlugin, getPMCommands, getProjectPort, isIdentifierLike, isPnpmAvailable, loadManifest, manifestExists, manifestPath, normalizeEsmSpecifiers, pnpmAvailabilityFromProbe, positionals, printInitHelp, requireBackendDir, requireProjectRoot, resetPnpmAvailabilityCache, resolveBackendPaths, resolveCliVersion, resolveExampleBaseUrl, resolveLocalBin, resolvePluginCliScript, resolveStartPort, resolveTsx, schemaCommand, startCommand, synthesizeManifest, validateManifest, validateProjectName, validateTsxInstallation, writeManifest };
|
|
11187
|
+
export { CURRENT_RUNTIME_RANGE, DEFAULT_BUNDLE_DIR, DEFAULT_CONFIG_DIR, DEFAULT_CRONS_DIR, DEFAULT_FUNCTIONS_DIR, DEFAULT_SCHEMA_FILE, DEV_PORT_FILENAME, MANIFEST_FILENAME, ManifestError, TEMPLATE_PLACEHOLDER_FILES, appsCommand, assessManagedCompatibility, authCommand, buildBundle, buildCommand, buildInitQuestions, buildStaticBundle, buildableApps, cloudCommand, collectDeclaredDependencies, configureEnvFile, createRebaseApp, dbCommand, detectFrameworkDepDrift, detectNativeDependencies, detectPackageManager, detectStorageAuthorize, devCommand, doctorCommand, ejectCommand, entry, findBackendApp, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, findUnusedServerEntry, foldStaticIntoBundle, formatCdTarget, generateSdkCommand, getActiveBackendPlugin, getPMCommands, getProjectPort, isIdentifierLike, isPnpmAvailable, loadManifest, manifestExists, manifestPath, normalizeEsmSpecifiers, pnpmAvailabilityFromProbe, positionals, printInitHelp, readEnvFile, requireBackendDir, requireProjectRoot, resetPnpmAvailabilityCache, resolveBackendPaths, resolveCliVersion, resolveExampleBaseUrl, resolveLocalBin, resolvePluginCliScript, resolveRuntimeImageTag, resolveStartPort, resolveTsx, schemaCommand, selectUserForEmail, startCommand, synthesizeManifest, validateManifest, validateProjectName, validateTsxInstallation, writeManifest };
|
|
10897
11188
|
|
|
10898
11189
|
//# sourceMappingURL=index.es.js.map
|