@rebasepro/cli 0.12.0 → 0.12.1-canary.g009ed95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/rebase.js +27 -1
- package/dist/bundle.d.ts +36 -0
- package/dist/commands/cloud/context.d.ts +53 -0
- package/dist/commands/cloud/deploy.d.ts +64 -0
- package/dist/commands/cloud/index.d.ts +23 -0
- package/dist/commands/dev.d.ts +16 -0
- package/dist/index.es.js +370 -41
- package/dist/index.es.js.map +1 -1
- package/package.json +11 -11
- package/templates/overlays/baas/backend/package.json +2 -2
- package/templates/overlays/baas/package.json +1 -2
- package/templates/template/backend/package.json +2 -2
- package/templates/template/config/collections/index.ts +9 -1
- package/templates/template/frontend/package.json +3 -3
- package/templates/template/frontend/src/App.tsx +2 -1
- package/templates/template/frontend/src/main.tsx +2 -1
- package/templates/template/frontend/vite.config.ts +0 -1
- package/templates/template/package.json +1 -2
package/dist/index.es.js
CHANGED
|
@@ -355,8 +355,9 @@ function requireProjectRoot() {
|
|
|
355
355
|
const root = findProjectRoot();
|
|
356
356
|
if (!root) {
|
|
357
357
|
console.error(chalk.red("✗ Could not find a Rebase project root."));
|
|
358
|
-
console.error(chalk.gray(
|
|
359
|
-
console.error(chalk.gray("
|
|
358
|
+
console.error(chalk.gray(` Looked in this directory and every parent for a ${MANIFEST_FILENAME},`));
|
|
359
|
+
console.error(chalk.gray(" a package.json with a \"backend\" workspace, or a backend/ next to a config/."));
|
|
360
|
+
console.error(chalk.gray(" Run this from inside a project, or create one with `rebase init`."));
|
|
360
361
|
process.exit(1);
|
|
361
362
|
}
|
|
362
363
|
return root;
|
|
@@ -532,7 +533,7 @@ async function requireClient(rawArgs) {
|
|
|
532
533
|
* the ingress and the console read — see saas/backend/src/utils/tenant-domain.ts).
|
|
533
534
|
*
|
|
534
535
|
* The CLI cannot know this value: it is per-deployment configuration (production
|
|
535
|
-
* serves tenants at `
|
|
536
|
+
* serves tenants at `rebase.website`, a dev control plane at `localhost`). It
|
|
536
537
|
* used to be hardcoded to `rebase.pro`, so `cloud projects create` congratulated
|
|
537
538
|
* the user with a URL that resolves nowhere near their app.
|
|
538
539
|
*
|
|
@@ -602,6 +603,39 @@ function removeLink(cwd = process.cwd()) {
|
|
|
602
603
|
}
|
|
603
604
|
return false;
|
|
604
605
|
}
|
|
606
|
+
/**
|
|
607
|
+
* Flags that may appear anywhere on a `rebase cloud` line, including *before*
|
|
608
|
+
* the resource group.
|
|
609
|
+
*
|
|
610
|
+
* They have to be declared wherever positionals are resolved, because `arg`'s
|
|
611
|
+
* `permissive: true` does not merely tolerate an undeclared flag — it pushes it
|
|
612
|
+
* into `_` alongside the positionals, and for a flag that takes a value it
|
|
613
|
+
* pushes the value in too. So `cloud --project acme storage create` parsed
|
|
614
|
+
* without this spec yields `_` of `["--project", "acme", "storage", "create"]`,
|
|
615
|
+
* and the group reads as `"acme"`: a real project name, in the group position,
|
|
616
|
+
* dispatching to nothing. Skipping tokens that start with `-` does not save you
|
|
617
|
+
* there — the damage is the orphaned value, which looks exactly like a
|
|
618
|
+
* positional.
|
|
619
|
+
*
|
|
620
|
+
* Only genuinely global flags belong here. Group-specific ones (`--bucket`,
|
|
621
|
+
* `--region`, …) are declared by the handler that owns them and always follow
|
|
622
|
+
* the group, so they cannot shift the group or action.
|
|
623
|
+
*
|
|
624
|
+
* `-p` is `--project` in eighteen places and `--password` in `login`. That
|
|
625
|
+
* ambiguity does not matter to the one caller that reads this spec: it resolves
|
|
626
|
+
* positionals and never looks at a flag's value, so all it needs to know is
|
|
627
|
+
* that `-p` takes one. Anything that wants the value must keep declaring it
|
|
628
|
+
* itself, with the meaning its own command gives it.
|
|
629
|
+
*/
|
|
630
|
+
var GLOBAL_CLOUD_FLAGS = {
|
|
631
|
+
"--json": Boolean,
|
|
632
|
+
"--yes": Boolean,
|
|
633
|
+
"--help": Boolean,
|
|
634
|
+
"--project": String,
|
|
635
|
+
"-p": "--project",
|
|
636
|
+
"-y": "--yes",
|
|
637
|
+
"-h": "--help"
|
|
638
|
+
};
|
|
605
639
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
606
640
|
/**
|
|
607
641
|
* The raw project reference to operate on: explicit `--project` flag wins,
|
|
@@ -707,6 +741,35 @@ function emit(human, json) {
|
|
|
707
741
|
if (JSON_MODE) printJson(json);
|
|
708
742
|
else human();
|
|
709
743
|
}
|
|
744
|
+
/**
|
|
745
|
+
* Print a warning (+ optional hint) — in every output mode, always to stderr.
|
|
746
|
+
*
|
|
747
|
+
* `emit` is for a command's *result*, and JSON mode legitimately replaces the
|
|
748
|
+
* human rendering of one. A warning is not a result: it says the command is
|
|
749
|
+
* about to do something the caller may not have meant, and that is exactly as
|
|
750
|
+
* true when the output is piped. Gating one on `!isJsonMode()` deleted it
|
|
751
|
+
* precisely where nobody was watching the terminal — a `--source` deploy ejected
|
|
752
|
+
* a live project off the managed runtime and said so only to a TTY that wasn't
|
|
753
|
+
* there.
|
|
754
|
+
*
|
|
755
|
+
* stdout carries the JSON value and nothing else, so warnings go to stderr:
|
|
756
|
+
* a machine parser reading stdout cannot be corrupted by one. Only the
|
|
757
|
+
* *formatting* may depend on the mode — colour and indentation for a terminal,
|
|
758
|
+
* plain ASCII otherwise. Whether a warning is emitted at all may not.
|
|
759
|
+
*
|
|
760
|
+
* Anything a caller might branch on belongs in the JSON payload as well; stderr
|
|
761
|
+
* is for whoever reads the transcript afterwards.
|
|
762
|
+
*/
|
|
763
|
+
function warn(message, hint) {
|
|
764
|
+
if (JSON_MODE) {
|
|
765
|
+
process.stderr.write(`warning: ${stripAnsi(message)}\n`);
|
|
766
|
+
if (hint) process.stderr.write(` ${stripAnsi(hint)}\n`);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
console.error("");
|
|
770
|
+
console.error(chalk.yellow(` ⚠ ${message}`));
|
|
771
|
+
if (hint) console.error(chalk.gray(` ${hint}`));
|
|
772
|
+
}
|
|
710
773
|
/** Print an error (+ optional hint) and exit non-zero. Never returns. */
|
|
711
774
|
function fail(message, hint, code) {
|
|
712
775
|
if (JSON_MODE) {
|
|
@@ -1461,9 +1524,10 @@ async function replacePlaceholders(options) {
|
|
|
1461
1524
|
versionToUse = stdout.trim();
|
|
1462
1525
|
} catch {
|
|
1463
1526
|
try {
|
|
1527
|
+
const tag = cliVersion.includes("canary") ? "canary" : "latest";
|
|
1464
1528
|
const { stdout } = await execa(viewBin, [
|
|
1465
1529
|
"view",
|
|
1466
|
-
`${pkgName}@${
|
|
1530
|
+
`${pkgName}@${tag}`,
|
|
1467
1531
|
"version"
|
|
1468
1532
|
]);
|
|
1469
1533
|
if (!stdout.trim()) throw new Error("Not found");
|
|
@@ -1560,13 +1624,22 @@ async function configureEnvFile(targetDirectory, databaseUrl) {
|
|
|
1560
1624
|
const dbPassword = crypto.randomBytes(16).toString("hex");
|
|
1561
1625
|
const serviceKey = crypto.randomBytes(48).toString("base64");
|
|
1562
1626
|
let envContent = fs.readFileSync(envPath, "utf-8");
|
|
1627
|
+
envContent = envContent.replace(/^(# ║ {2})(Copy this file to \.env and fill in the values)( +║)$/m, (_match, open, old, close) => {
|
|
1628
|
+
const replacement = "Generated by `rebase init` — the secrets below are already set";
|
|
1629
|
+
const width = old.length + close.length - 1;
|
|
1630
|
+
return open + replacement.slice(0, width).padEnd(width, " ") + "║";
|
|
1631
|
+
});
|
|
1563
1632
|
envContent = envContent.replace(/^JWT_SECRET=.*$/m, `JWT_SECRET=${jwtSecret}`);
|
|
1564
1633
|
envContent = envContent.replace(/^#\s*REBASE_SERVICE_KEY=.*$/m, `REBASE_SERVICE_KEY=${serviceKey}`);
|
|
1634
|
+
const composeApiPort = /^PORT=(\d+)/m.exec(envContent)?.[1] ?? "3001";
|
|
1635
|
+
envContent = envContent.replace(/^#\s*CORS_ORIGINS=.*$/m, `CORS_ORIGINS=http://localhost:${composeApiPort}`);
|
|
1565
1636
|
const runtimeVersion = readCliVersion();
|
|
1566
1637
|
envContent = /^#?\s*REBASE_VERSION=.*$/m.test(envContent) ? envContent.replace(/^#?\s*REBASE_VERSION=.*$/m, `REBASE_VERSION=${runtimeVersion}`) : `${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.\nREBASE_VERSION=${runtimeVersion}\n`;
|
|
1567
1638
|
if (databaseUrl) {
|
|
1568
1639
|
if (/[\r\n]/.test(databaseUrl)) throw new Error("Invalid DATABASE_URL: multiline values are not allowed.");
|
|
1569
|
-
|
|
1640
|
+
const { pinSearchPath } = await import("@rebasepro/server-postgres");
|
|
1641
|
+
const pinnedUrl = pinSearchPath(databaseUrl);
|
|
1642
|
+
envContent = envContent.replace(/^DATABASE_URL=.*$/m, `DATABASE_URL=${pinnedUrl}\nDATABASE_PASSWORD=${dbPassword}`);
|
|
1570
1643
|
} else {
|
|
1571
1644
|
const dbPort = await findAvailablePort(5432);
|
|
1572
1645
|
envContent = envContent.replace(/^DATABASE_URL=.*$/m, `DATABASE_URL=postgresql://rebase:${dbPassword}@localhost:${dbPort}/rebase?options=-c%20search_path=public&sslmode=disable\nDATABASE_PASSWORD=${dbPassword}`);
|
|
@@ -2084,7 +2157,7 @@ var REMOVED_APP_TYPES = {
|
|
|
2084
2157
|
mobile: "mobile apps are no longer declared in the manifest — nothing consumed this type. Remove the entry"
|
|
2085
2158
|
};
|
|
2086
2159
|
/** Reserved because they name things in URLs and CLI output. */
|
|
2087
|
-
var RESERVED_APP_NAMES = new Set([
|
|
2160
|
+
var RESERVED_APP_NAMES = /* @__PURE__ */ new Set([
|
|
2088
2161
|
"api",
|
|
2089
2162
|
"health",
|
|
2090
2163
|
"metrics",
|
|
@@ -3081,7 +3154,7 @@ ${chalk.green.bold("Description")}
|
|
|
3081
3154
|
*/
|
|
3082
3155
|
var DEFAULT_BUNDLE_DIR = "dist-bundle";
|
|
3083
3156
|
/** Packages whose presence means the bundle cannot run on a stock runtime image. */
|
|
3084
|
-
var KNOWN_NATIVE_PACKAGES = new Set([
|
|
3157
|
+
var KNOWN_NATIVE_PACKAGES = /* @__PURE__ */ new Set([
|
|
3085
3158
|
"sharp",
|
|
3086
3159
|
"canvas",
|
|
3087
3160
|
"bcrypt",
|
|
@@ -3097,7 +3170,7 @@ var KNOWN_NATIVE_PACKAGES = new Set([
|
|
|
3097
3170
|
"pg-native"
|
|
3098
3171
|
]);
|
|
3099
3172
|
/** Dependencies supplied by the runtime image itself, not by the bundle. */
|
|
3100
|
-
var RUNTIME_PROVIDED = new Set([
|
|
3173
|
+
var RUNTIME_PROVIDED = /* @__PURE__ */ new Set([
|
|
3101
3174
|
"@rebasepro/server",
|
|
3102
3175
|
"@rebasepro/types",
|
|
3103
3176
|
"@rebasepro/client",
|
|
@@ -3506,6 +3579,139 @@ function collectDeclaredDependencies(projectRoot) {
|
|
|
3506
3579
|
return declared;
|
|
3507
3580
|
}
|
|
3508
3581
|
/**
|
|
3582
|
+
* Lowest version a range could resolve to, or null if it is not a range.
|
|
3583
|
+
*
|
|
3584
|
+
* Kept deliberately tiny and local. The published range grammar here is a caret,
|
|
3585
|
+
* a tilde, an exact version or a `>=` floor, and the alternative — a semver
|
|
3586
|
+
* dependency in the CLI — buys breadth this does not need.
|
|
3587
|
+
*/
|
|
3588
|
+
function lowerBoundOf(range) {
|
|
3589
|
+
const raw = range.trim().replace(/^[\^~]/, "").replace(/^>=\s*/, "").replace(/^v/, "");
|
|
3590
|
+
if (!/^\d+(\.\d+){0,2}$/.test(raw)) return null;
|
|
3591
|
+
const [major, minor = 0, patch = 0] = raw.split(".").map(Number);
|
|
3592
|
+
return [
|
|
3593
|
+
major,
|
|
3594
|
+
minor,
|
|
3595
|
+
patch
|
|
3596
|
+
];
|
|
3597
|
+
}
|
|
3598
|
+
/** Highest version a range could resolve to (exclusive), or null. */
|
|
3599
|
+
function upperBoundOf(range) {
|
|
3600
|
+
const trimmed = range.trim();
|
|
3601
|
+
const min = lowerBoundOf(trimmed);
|
|
3602
|
+
if (!min) return null;
|
|
3603
|
+
if (trimmed.startsWith(">=")) return null;
|
|
3604
|
+
const [major, minor, patch] = min;
|
|
3605
|
+
if (trimmed.startsWith("^")) {
|
|
3606
|
+
if (major > 0) return [
|
|
3607
|
+
major + 1,
|
|
3608
|
+
0,
|
|
3609
|
+
0
|
|
3610
|
+
];
|
|
3611
|
+
if (minor > 0) return [
|
|
3612
|
+
0,
|
|
3613
|
+
minor + 1,
|
|
3614
|
+
0
|
|
3615
|
+
];
|
|
3616
|
+
return [
|
|
3617
|
+
0,
|
|
3618
|
+
0,
|
|
3619
|
+
patch + 1
|
|
3620
|
+
];
|
|
3621
|
+
}
|
|
3622
|
+
if (trimmed.startsWith("~")) return trimmed.replace(/^~v?/, "").split(".").length >= 2 ? [
|
|
3623
|
+
major,
|
|
3624
|
+
minor + 1,
|
|
3625
|
+
0
|
|
3626
|
+
] : [
|
|
3627
|
+
major + 1,
|
|
3628
|
+
0,
|
|
3629
|
+
0
|
|
3630
|
+
];
|
|
3631
|
+
const parts = trimmed.replace(/^v/, "").split(".").length;
|
|
3632
|
+
if (parts === 1) return [
|
|
3633
|
+
major + 1,
|
|
3634
|
+
0,
|
|
3635
|
+
0
|
|
3636
|
+
];
|
|
3637
|
+
if (parts === 2) return [
|
|
3638
|
+
major,
|
|
3639
|
+
minor + 1,
|
|
3640
|
+
0
|
|
3641
|
+
];
|
|
3642
|
+
return [
|
|
3643
|
+
major,
|
|
3644
|
+
minor,
|
|
3645
|
+
patch + 1
|
|
3646
|
+
];
|
|
3647
|
+
}
|
|
3648
|
+
function compareTriples(a, b) {
|
|
3649
|
+
for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1;
|
|
3650
|
+
return 0;
|
|
3651
|
+
}
|
|
3652
|
+
/**
|
|
3653
|
+
* Whether a declared range could EVER resolve at or above `target`.
|
|
3654
|
+
*
|
|
3655
|
+
* The same question the control plane asks at intake, asked here first. Only a
|
|
3656
|
+
* range whose entire span sits below the target is reported — `^0.10.0` can
|
|
3657
|
+
* never cross to 0.12 whatever npm publishes — because a false alarm on a build
|
|
3658
|
+
* that would have worked trains people to ignore the warning that matters.
|
|
3659
|
+
*/
|
|
3660
|
+
function canReach(range, target) {
|
|
3661
|
+
const ceiling = upperBoundOf(range);
|
|
3662
|
+
const floor = lowerBoundOf(target);
|
|
3663
|
+
if (!floor || !lowerBoundOf(range)) return null;
|
|
3664
|
+
if (!ceiling) return true;
|
|
3665
|
+
return compareTriples(ceiling, floor) > 0;
|
|
3666
|
+
}
|
|
3667
|
+
/**
|
|
3668
|
+
* Find `@rebasepro/*` dependencies pinned to a version older than this CLI.
|
|
3669
|
+
*
|
|
3670
|
+
* This is the only place a developer can be told. In development, every
|
|
3671
|
+
* `@rebasepro/*` resolves through pnpm's `link:`/`workspace:` overrides to the
|
|
3672
|
+
* checkout, so the version STRINGS in package.json are never exercised — the
|
|
3673
|
+
* project runs fine locally on whatever is on disk, and the declared numbers are
|
|
3674
|
+
* first honoured when the runtime npm-installs them from a bundle in the cloud.
|
|
3675
|
+
* A project scaffolded at 0.10.0 therefore keeps working on a developer's
|
|
3676
|
+
* machine indefinitely while being, in the cloud, a 0.10.0 driver.
|
|
3677
|
+
*
|
|
3678
|
+
* That matters because the image supplies only `@rebasepro/server`; the database
|
|
3679
|
+
* driver comes from these declarations and a newer runtime never updates it.
|
|
3680
|
+
* Every package.json is scanned, `dependencies` and `devDependencies` both,
|
|
3681
|
+
* because they have to be bumped together and the one that gets forgotten is the
|
|
3682
|
+
* one nobody looks at.
|
|
3683
|
+
*/
|
|
3684
|
+
function detectFrameworkDepDrift(projectRoot, cliVersion) {
|
|
3685
|
+
const found = [];
|
|
3686
|
+
for (const relative of [
|
|
3687
|
+
"package.json",
|
|
3688
|
+
"backend/package.json",
|
|
3689
|
+
"config/package.json",
|
|
3690
|
+
"frontend/package.json"
|
|
3691
|
+
]) {
|
|
3692
|
+
const file = path.join(projectRoot, relative);
|
|
3693
|
+
if (!fs.existsSync(file)) continue;
|
|
3694
|
+
try {
|
|
3695
|
+
const pkg = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
3696
|
+
for (const block of [pkg.dependencies, pkg.devDependencies]) for (const [name, range] of Object.entries(block ?? {})) {
|
|
3697
|
+
if (!name.startsWith("@rebasepro/")) continue;
|
|
3698
|
+
if (typeof range !== "string") continue;
|
|
3699
|
+
found.push({
|
|
3700
|
+
name,
|
|
3701
|
+
range,
|
|
3702
|
+
file: relative
|
|
3703
|
+
});
|
|
3704
|
+
}
|
|
3705
|
+
} catch {}
|
|
3706
|
+
}
|
|
3707
|
+
const behind = found.filter((d) => canReach(d.range, cliVersion) === false);
|
|
3708
|
+
const bounds = new Set(found.map((d) => lowerBoundOf(d.range)).filter((b) => b != null).map((b) => b.join(".")));
|
|
3709
|
+
return {
|
|
3710
|
+
behind,
|
|
3711
|
+
disagreeing: bounds.size > 1 ? [...bounds].sort() : []
|
|
3712
|
+
};
|
|
3713
|
+
}
|
|
3714
|
+
/**
|
|
3509
3715
|
* Rewrite relative import specifiers in emitted JavaScript so Node can resolve them.
|
|
3510
3716
|
*
|
|
3511
3717
|
* TypeScript deliberately does not touch specifiers: `moduleResolution: "bundler"`
|
|
@@ -4203,6 +4409,16 @@ async function buildCommand(rawArgs = []) {
|
|
|
4203
4409
|
console.log(chalk.yellow(` ⚠ native dependencies detected: ${names}`));
|
|
4204
4410
|
console.log(chalk.dim(" These cannot run on the managed runtime. See `rebase doctor`."));
|
|
4205
4411
|
}
|
|
4412
|
+
const drift = detectFrameworkDepDrift(projectRoot, resolveCliVersion());
|
|
4413
|
+
if (drift.behind.length > 0) {
|
|
4414
|
+
console.log(chalk.yellow(` ⚠ framework dependencies older than this CLI (${resolveCliVersion()}):`));
|
|
4415
|
+
for (const dep of drift.behind) console.log(chalk.dim(` ${dep.name}@${dep.range} (${dep.file})`));
|
|
4416
|
+
console.log(chalk.dim(" The image supplies the server, but your bundle supplies the database"));
|
|
4417
|
+
console.log(chalk.dim(" driver — a newer runtime does not update it. Bump these and rebuild."));
|
|
4418
|
+
} else if (drift.disagreeing.length > 0) {
|
|
4419
|
+
console.log(chalk.yellow(` ⚠ mixed @rebasepro versions declared: ${drift.disagreeing.join(", ")}`));
|
|
4420
|
+
console.log(chalk.dim(" These are published together and expect to run together; pin them alike."));
|
|
4421
|
+
}
|
|
4206
4422
|
if (!args["--no-static"]) {
|
|
4207
4423
|
const folded = await foldFrontendIntoBundle({
|
|
4208
4424
|
projectRoot,
|
|
@@ -5061,7 +5277,7 @@ function parseAgentFlags(rawArgs) {
|
|
|
5061
5277
|
return requested;
|
|
5062
5278
|
}
|
|
5063
5279
|
async function skillsInstall(rawArgs = []) {
|
|
5064
|
-
const projectDir = process.cwd();
|
|
5280
|
+
const projectDir = findProjectRoot() ?? process.cwd();
|
|
5065
5281
|
let skillsDir;
|
|
5066
5282
|
try {
|
|
5067
5283
|
skillsDir = getSkillsSourceDir();
|
|
@@ -5105,7 +5321,8 @@ async function skillsInstall(rawArgs = []) {
|
|
|
5105
5321
|
for (const agentKey of agents) {
|
|
5106
5322
|
const agent = AGENTS[agentKey];
|
|
5107
5323
|
const count = installForAgent(agentKey, skills, projectDir);
|
|
5108
|
-
|
|
5324
|
+
const shown = path.relative(process.cwd(), path.join(projectDir, agent.targetDir)) || agent.targetDir;
|
|
5325
|
+
console.log(` ${chalk.green("✓")} ${chalk.bold(agent.label)} — ${count} skills installed to ${chalk.gray(shown)}`);
|
|
5109
5326
|
}
|
|
5110
5327
|
console.log("");
|
|
5111
5328
|
console.log(chalk.gray(" Skills are project-local. Commit them to share with your team."));
|
|
@@ -6085,12 +6302,24 @@ function resolveFrameworkVersion(sourceDir) {
|
|
|
6085
6302
|
dir = parent;
|
|
6086
6303
|
}
|
|
6087
6304
|
}
|
|
6305
|
+
/**
|
|
6306
|
+
* A progress line for a human — dropped entirely in JSON mode.
|
|
6307
|
+
*
|
|
6308
|
+
* Progress is not a result. In JSON mode stdout carries the one result value
|
|
6309
|
+
* and nothing else, so every unguarded `console.log` on a deploy path was a
|
|
6310
|
+
* line printed in front of the JSON, breaking the parser meant to read it.
|
|
6311
|
+
* Warnings are the other half of this rule and go the other way: they are
|
|
6312
|
+
* `warn`, which prints in every mode, to stderr. See `warn` in `context.ts`.
|
|
6313
|
+
*/
|
|
6314
|
+
function progress(line) {
|
|
6315
|
+
if (!isJsonMode()) console.log(line);
|
|
6316
|
+
}
|
|
6088
6317
|
/** Upload a build-context tarball; returns the opaque `source` ref for deploy. */
|
|
6089
6318
|
async function uploadSource(url, token, projectId, tarPath) {
|
|
6090
6319
|
const bytes = fs.readFileSync(tarPath);
|
|
6091
6320
|
const sizeMb = (bytes.length / 1024 / 1024).toFixed(1);
|
|
6092
6321
|
if (bytes.length > MAX_SOURCE_UPLOAD_BYTES) fail(`Source context is ${sizeMb} MB — the upload cap is ${Math.round(MAX_SOURCE_UPLOAD_BYTES / 1024 / 1024)} MB.`, "Trim the build context: exclude sourcemaps (*.map), build output and large assets via .rebaseignore or .gitignore.");
|
|
6093
|
-
|
|
6322
|
+
progress(chalk.gray(` Uploading source (${sizeMb} MB)...`));
|
|
6094
6323
|
const res = await fetch(`${url}/api/functions/deploy/upload?projectId=${encodeURIComponent(projectId)}`, {
|
|
6095
6324
|
method: "POST",
|
|
6096
6325
|
headers: {
|
|
@@ -6122,7 +6351,7 @@ async function deployBundle(opts) {
|
|
|
6122
6351
|
const loaded = loadManifest(projectRoot);
|
|
6123
6352
|
const backend = findBackendApp(loaded.manifest);
|
|
6124
6353
|
if (!backend) fail("This repository declares no backend app to deploy as a bundle.", "A managed deploy runs the backend; declare one in rebase.json, or deploy from the backend's repository.");
|
|
6125
|
-
|
|
6354
|
+
progress(chalk.gray(" Building bundle..."));
|
|
6126
6355
|
bundleDir = (await buildBundle({
|
|
6127
6356
|
projectRoot,
|
|
6128
6357
|
appName: backend.name,
|
|
@@ -6130,16 +6359,16 @@ async function deployBundle(opts) {
|
|
|
6130
6359
|
runtimeRange: loaded.manifest.rebase,
|
|
6131
6360
|
storage: loaded.manifest.storage,
|
|
6132
6361
|
skipTypeCheck: opts.skipTypeCheck,
|
|
6133
|
-
log: (m) =>
|
|
6362
|
+
log: (m) => progress(chalk.gray(m))
|
|
6134
6363
|
})).outDir;
|
|
6135
6364
|
try {
|
|
6136
6365
|
const folded = await foldFrontendIntoBundle({
|
|
6137
6366
|
projectRoot,
|
|
6138
6367
|
manifest: loaded.manifest,
|
|
6139
6368
|
bundleDir,
|
|
6140
|
-
log: (m) =>
|
|
6369
|
+
log: (m) => progress(m)
|
|
6141
6370
|
});
|
|
6142
|
-
for (const outcome of folded)
|
|
6371
|
+
for (const outcome of folded) progress(chalk.gray(` folded ${outcome.appName} in (${outcome.fileCount} file(s), served at ${outcome.path})`));
|
|
6143
6372
|
} catch (err) {
|
|
6144
6373
|
fail(err instanceof Error ? err.message : String(err), "Fix the frontend build, or pass --no-static to deploy the API alone.");
|
|
6145
6374
|
}
|
|
@@ -6156,7 +6385,7 @@ async function deployBundle(opts) {
|
|
|
6156
6385
|
try {
|
|
6157
6386
|
await packBundle(bundleDir, tarPath);
|
|
6158
6387
|
const sizeMb = (fs.statSync(tarPath).size / 1024 / 1024).toFixed(1);
|
|
6159
|
-
|
|
6388
|
+
progress(chalk.gray(` Uploading bundle (${sizeMb} MB)...`));
|
|
6160
6389
|
bundleId = await uploadBundle(url, token, projectId, tarPath);
|
|
6161
6390
|
} catch (e) {
|
|
6162
6391
|
fail(e instanceof Error ? e.message : String(e));
|
|
@@ -6164,8 +6393,10 @@ async function deployBundle(opts) {
|
|
|
6164
6393
|
} finally {
|
|
6165
6394
|
fs.rmSync(tarPath, { force: true });
|
|
6166
6395
|
}
|
|
6167
|
-
|
|
6168
|
-
|
|
6396
|
+
if (!isJsonMode()) {
|
|
6397
|
+
console.log("");
|
|
6398
|
+
console.log(` 🚀 Triggering managed deployment for ${chalk.bold(projectRef)} (schema ${manifest.schemaVersion})...`);
|
|
6399
|
+
}
|
|
6169
6400
|
let declaredApps = [];
|
|
6170
6401
|
try {
|
|
6171
6402
|
declaredApps = declaredAppsFrom(loadManifest(process.cwd()).manifest);
|
|
@@ -6253,9 +6484,75 @@ function planBareDeploy(project, latest, now) {
|
|
|
6253
6484
|
lines: ["This project has no git repository configured and no stored source archive to rebuild.", "Upload this directory with `--source .`, or set a repository URL in the project settings."]
|
|
6254
6485
|
};
|
|
6255
6486
|
}
|
|
6487
|
+
/** `code` of the warning below, and the field name it sets in the payload. */
|
|
6488
|
+
var EJECTS_MANAGED_RUNTIME = "ejects_managed_runtime";
|
|
6256
6489
|
/** The one sentence that says a source build undoes `runtimeMode: managed`. */
|
|
6257
6490
|
function ejectWarning(projectRef) {
|
|
6258
|
-
return
|
|
6491
|
+
return {
|
|
6492
|
+
code: EJECTS_MANAGED_RUNTIME,
|
|
6493
|
+
message: `${projectRef} runs on the managed runtime — this build ejects it to a custom container.`,
|
|
6494
|
+
hint: "Use `rebase cloud deploy --bundle` to stay on managed."
|
|
6495
|
+
};
|
|
6496
|
+
}
|
|
6497
|
+
/**
|
|
6498
|
+
* Why a container-image deploy of a managed project is refused — or `undefined`
|
|
6499
|
+
* to let it through.
|
|
6500
|
+
*
|
|
6501
|
+
* Every path below this point builds a container image, and a successful one
|
|
6502
|
+
* sets `runtimeMode: "custom"` server-side. So the question is never "which flag
|
|
6503
|
+
* was used" but "did the caller ask to leave the managed runtime", and only
|
|
6504
|
+
* `--force` answers it.
|
|
6505
|
+
*
|
|
6506
|
+
* `--source` used to be read as answering it too, on the theory that uploading a
|
|
6507
|
+
* build context is self-evidently a deliberate eject. It is not: `--source`
|
|
6508
|
+
* picks *which source* gets built — this directory, rather than the stale
|
|
6509
|
+
* archive the control plane is holding — and the eject is a side effect of the
|
|
6510
|
+
* answer. That is exactly how a live project got flipped to `custom` by someone
|
|
6511
|
+
* whose actual intent was "deploy what I have here", and it is the same
|
|
6512
|
+
* ignorance the bare form is refused for. Same ignorance, same refusal.
|
|
6513
|
+
*/
|
|
6514
|
+
function ejectRefusal(opts, projectRef) {
|
|
6515
|
+
if (!opts.managed || opts.force) return void 0;
|
|
6516
|
+
const eject = "To eject on purpose, add `--force`.";
|
|
6517
|
+
if (opts.source) return {
|
|
6518
|
+
message: `${projectRef} runs on the managed runtime, and \`--source\` builds a container image from this directory — which ejects it from managed. Picking a build method is not the same as asking to leave the runtime.`,
|
|
6519
|
+
hint: `Deploy this directory to the managed runtime with \`rebase cloud deploy --bundle\`. ${eject}`,
|
|
6520
|
+
code: "managed_project"
|
|
6521
|
+
};
|
|
6522
|
+
return {
|
|
6523
|
+
message: `${projectRef} runs on the managed runtime, and a plain \`rebase cloud deploy\` builds a container image instead — ejecting it from managed, from source the control plane already holds rather than this directory.`,
|
|
6524
|
+
hint: `Redeploy it with \`rebase cloud deploy --bundle\`. ${eject} \`--source . --force\` builds this directory; \`--force\` alone builds what the control plane holds.`,
|
|
6525
|
+
code: "managed_project"
|
|
6526
|
+
};
|
|
6527
|
+
}
|
|
6528
|
+
/**
|
|
6529
|
+
* Which warnings a container-image deploy has earned.
|
|
6530
|
+
*
|
|
6531
|
+
* Pure, and separate from the printing, because the printing is what went
|
|
6532
|
+
* wrong: the eject warning used to be written inline behind `!isJsonMode()`, so
|
|
6533
|
+
* the fact that a deploy ejects a managed project existed only as a side effect
|
|
6534
|
+
* of a TTY being attached. Deciding here, emitting once at the call site, means
|
|
6535
|
+
* the decision cannot be output-mode-dependent again.
|
|
6536
|
+
*
|
|
6537
|
+
* The condition is just `managed`: anything reaching this point is a container
|
|
6538
|
+
* image build that `ejectRefusal` has already let through, and on a managed
|
|
6539
|
+
* project that is an eject however it was spelled. A caller who passed `--force`
|
|
6540
|
+
* knows — the warning is for the transcript and the payload, which is what
|
|
6541
|
+
* anyone reviewing the deploy afterwards actually reads.
|
|
6542
|
+
*/
|
|
6543
|
+
function deployWarnings(opts, projectRef) {
|
|
6544
|
+
return opts.managed ? [ejectWarning(projectRef)] : [];
|
|
6545
|
+
}
|
|
6546
|
+
/** The warning half of a deploy's JSON payload — merged into whatever it emits. */
|
|
6547
|
+
function warningPayload(warnings) {
|
|
6548
|
+
return {
|
|
6549
|
+
warnings: warnings.map((w) => ({
|
|
6550
|
+
code: w.code,
|
|
6551
|
+
message: w.message,
|
|
6552
|
+
hint: w.hint ?? null
|
|
6553
|
+
})),
|
|
6554
|
+
ejectsManagedRuntime: warnings.some((w) => w.code === EJECTS_MANAGED_RUNTIME)
|
|
6555
|
+
};
|
|
6259
6556
|
}
|
|
6260
6557
|
/**
|
|
6261
6558
|
* Read the two rows the preflight needs.
|
|
@@ -6326,17 +6623,18 @@ async function deployCommand(rawArgs, projectRef) {
|
|
|
6326
6623
|
}
|
|
6327
6624
|
const { project, latest } = await readDeployContext(client, projectId);
|
|
6328
6625
|
const plan = planBareDeploy(project, latest, /* @__PURE__ */ new Date());
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6626
|
+
const eject = {
|
|
6627
|
+
managed: plan.managed,
|
|
6628
|
+
source: Boolean(args["--source"]),
|
|
6629
|
+
force: args["--force"] === true
|
|
6630
|
+
};
|
|
6631
|
+
const refusal = ejectRefusal(eject, projectRef);
|
|
6632
|
+
if (refusal) fail(refusal.message, refusal.hint, refusal.code);
|
|
6633
|
+
const warnings = deployWarnings(eject, projectRef);
|
|
6634
|
+
for (const w of warnings) warn(w.message, w.hint);
|
|
6635
|
+
if (!args["--source"] && !isJsonMode()) {
|
|
6337
6636
|
console.log("");
|
|
6338
|
-
console.log(chalk.
|
|
6339
|
-
console.log(chalk.gray(" Use `rebase cloud deploy --bundle` to stay on managed."));
|
|
6637
|
+
for (const line of plan.lines) console.log(chalk.gray(` ${line}`));
|
|
6340
6638
|
}
|
|
6341
6639
|
let source;
|
|
6342
6640
|
if (args["--source"]) {
|
|
@@ -6349,8 +6647,10 @@ async function deployCommand(rawArgs, projectRef) {
|
|
|
6349
6647
|
fs.rmSync(tarPath, { force: true });
|
|
6350
6648
|
}
|
|
6351
6649
|
}
|
|
6352
|
-
|
|
6353
|
-
|
|
6650
|
+
if (!isJsonMode()) {
|
|
6651
|
+
console.log("");
|
|
6652
|
+
console.log(` 🚀 Triggering deployment for project ${chalk.bold(projectRef)}${source ? " from uploaded source" : ""}...`);
|
|
6653
|
+
}
|
|
6354
6654
|
const body = { projectId };
|
|
6355
6655
|
if (source) body.source = source;
|
|
6356
6656
|
if (args["--message"]) body.message = args["--message"];
|
|
@@ -6378,7 +6678,8 @@ async function deployCommand(rawArgs, projectRef) {
|
|
|
6378
6678
|
deploymentId,
|
|
6379
6679
|
deduplicated,
|
|
6380
6680
|
frameworkVersion: frameworkVersion ?? null,
|
|
6381
|
-
following: false
|
|
6681
|
+
following: false,
|
|
6682
|
+
...warningPayload(warnings)
|
|
6382
6683
|
});
|
|
6383
6684
|
return;
|
|
6384
6685
|
}
|
|
@@ -6392,7 +6693,8 @@ async function deployCommand(rawArgs, projectRef) {
|
|
|
6392
6693
|
deduplicated,
|
|
6393
6694
|
frameworkVersion: frameworkVersion ?? null,
|
|
6394
6695
|
following: true,
|
|
6395
|
-
status
|
|
6696
|
+
status,
|
|
6697
|
+
...warningPayload(warnings)
|
|
6396
6698
|
});
|
|
6397
6699
|
}
|
|
6398
6700
|
/**
|
|
@@ -9006,6 +9308,7 @@ function describeDatabaseState(db) {
|
|
|
9006
9308
|
* So both are printed, rather than leaving anyone to infer one from a Docker tag.
|
|
9007
9309
|
*/
|
|
9008
9310
|
function describeRuntime(project) {
|
|
9311
|
+
if (project.runtimeMode == null || project.runtimeMode.trim() === "") return `not deployed yet ${chalk.gray("· the first deploy decides (`--bundle` keeps it managed)")}`;
|
|
9009
9312
|
if (project.runtimeMode !== "managed") return `custom ${chalk.gray("· your own image")}`;
|
|
9010
9313
|
const version = project.runtimeVersion ?? "unknown";
|
|
9011
9314
|
const framework = project.runtimeFrameworkVersion;
|
|
@@ -9404,17 +9707,41 @@ async function billingCommand(rawArgs) {
|
|
|
9404
9707
|
* dispatched from here. Individual groups live in sibling modules; this file
|
|
9405
9708
|
* only routes and prints help.
|
|
9406
9709
|
*/
|
|
9407
|
-
/**
|
|
9710
|
+
/**
|
|
9711
|
+
* Positional tokens after `rebase cloud` (group, action, …).
|
|
9712
|
+
*
|
|
9713
|
+
* Two things stop a flag being mistaken for the group. `GLOBAL_CLOUD_FLAGS` is
|
|
9714
|
+
* declared so `arg` *consumes* the flags that may precede it — critically
|
|
9715
|
+
* together with their values, which is the half that filtering cannot do. The
|
|
9716
|
+
* leading-`-` skip then covers a flag nobody declared, so an unrecognised
|
|
9717
|
+
* boolean shifts nothing.
|
|
9718
|
+
*
|
|
9719
|
+
* Only leading tokens are skipped: past the group and action, an undeclared
|
|
9720
|
+
* flag and its value are somebody else's positionals and none of our business.
|
|
9721
|
+
* A flag this file has never heard of, that takes a value, placed before the
|
|
9722
|
+
* group, is the one shape still unresolvable here — there is no way to know
|
|
9723
|
+
* whether the token after it is its value or the group, and guessing either way
|
|
9724
|
+
* is worse than the handler reporting an unknown group.
|
|
9725
|
+
*
|
|
9726
|
+
* Exported so its tests can drive the real thing. The dispatch test used to
|
|
9727
|
+
* re-implement it locally as `slice(3).filter(a => !a.startsWith("-"))` — which
|
|
9728
|
+
* filtered flags, while this function did not — so the test asserted the
|
|
9729
|
+
* behaviour we wanted against a copy that had it, and stayed green for as long
|
|
9730
|
+
* as the real dispatcher was broken.
|
|
9731
|
+
*/
|
|
9408
9732
|
function positionals(rawArgs) {
|
|
9409
|
-
|
|
9733
|
+
const rest = arg(GLOBAL_CLOUD_FLAGS, {
|
|
9410
9734
|
argv: rawArgs.slice(3),
|
|
9411
9735
|
permissive: true
|
|
9412
9736
|
})._;
|
|
9737
|
+
let i = 0;
|
|
9738
|
+
while (i < rest.length && rest[i].startsWith("-")) i++;
|
|
9739
|
+
return rest.slice(i);
|
|
9413
9740
|
}
|
|
9414
9741
|
async function cloudCommand(subcommand, rawArgs) {
|
|
9415
9742
|
initOutputMode(rawArgs);
|
|
9416
9743
|
const pos = positionals(rawArgs);
|
|
9417
|
-
const group =
|
|
9744
|
+
const group = pos[0] ?? (subcommand !== "--help" ? subcommand : void 0);
|
|
9418
9745
|
const action = pos[1];
|
|
9419
9746
|
if (!group || subcommand === "--help") {
|
|
9420
9747
|
printCloudHelp();
|
|
@@ -9836,8 +10163,9 @@ async function entry(args) {
|
|
|
9836
10163
|
console.log(getVersion());
|
|
9837
10164
|
return;
|
|
9838
10165
|
}
|
|
9839
|
-
const
|
|
9840
|
-
const
|
|
10166
|
+
const words = parsedArgs._.filter((a) => !a.startsWith("-"));
|
|
10167
|
+
const command = words[0];
|
|
10168
|
+
const subcommand = words[1];
|
|
9841
10169
|
if (!command || parsedArgs["--help"] && ![
|
|
9842
10170
|
"init",
|
|
9843
10171
|
"schema",
|
|
@@ -9876,9 +10204,10 @@ async function entry(args) {
|
|
|
9876
10204
|
argv: args.slice(3),
|
|
9877
10205
|
permissive: true
|
|
9878
10206
|
});
|
|
10207
|
+
const sdkRoot = sdkArgs["--help"] ? process.cwd() : requireProjectRoot();
|
|
9879
10208
|
await generateSdkCommand({
|
|
9880
|
-
collectionsDir: sdkArgs["--collections-dir"] || "
|
|
9881
|
-
output: sdkArgs["--output"] || "
|
|
10209
|
+
collectionsDir: sdkArgs["--collections-dir"] || path.join(sdkRoot, "config/collections"),
|
|
10210
|
+
output: sdkArgs["--output"] || path.join(sdkRoot, "generated/sdk"),
|
|
9882
10211
|
from: sdkArgs["--from"],
|
|
9883
10212
|
token: sdkArgs["--token"],
|
|
9884
10213
|
help: sdkArgs["--help"],
|
|
@@ -9988,6 +10317,6 @@ ${chalk.gray("Documentation: https://rebase.pro/docs")}
|
|
|
9988
10317
|
`);
|
|
9989
10318
|
}
|
|
9990
10319
|
//#endregion
|
|
9991
|
-
export { CURRENT_RUNTIME_RANGE, DEFAULT_BUNDLE_DIR, DEFAULT_CONFIG_DIR, DEFAULT_CRONS_DIR, DEFAULT_FUNCTIONS_DIR, DEFAULT_SCHEMA_FILE, MANIFEST_FILENAME, ManifestError, TEMPLATE_PLACEHOLDER_FILES, appsCommand, assessManagedCompatibility, authCommand, buildBundle, buildCommand, buildInitQuestions, buildStaticBundle, buildableApps, cloudCommand, collectDeclaredDependencies, configureEnvFile, createRebaseApp, dbCommand, detectNativeDependencies, detectPackageManager, detectStorageAuthorize, devCommand, doctorCommand, ejectCommand, entry, findBackendApp, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, findUnusedServerEntry, foldStaticIntoBundle, formatCdTarget, generateSdkCommand, getActiveBackendPlugin, getPMCommands, isIdentifierLike, isPnpmAvailable, loadManifest, manifestExists, manifestPath, normalizeEsmSpecifiers, pnpmAvailabilityFromProbe, printInitHelp, requireBackendDir, requireProjectRoot, resetPnpmAvailabilityCache, resolveBackendPaths, resolveExampleBaseUrl, resolveLocalBin, resolvePluginCliScript, resolveTsx, schemaCommand, startCommand, synthesizeManifest, validateManifest, validateProjectName, validateTsxInstallation, writeManifest };
|
|
10320
|
+
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 };
|
|
9992
10321
|
|
|
9993
10322
|
//# sourceMappingURL=index.es.js.map
|