@rebasepro/cli 0.10.1-canary.d8d45b2 → 0.10.1-canary.ed78a2c
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/bundle.d.ts +56 -0
- package/dist/commands/cloud/bundle-deploy.d.ts +25 -0
- package/dist/commands/cloud/deploy.d.ts +48 -0
- package/dist/commands/generate_sdk.d.ts +13 -0
- package/dist/fold-static.d.ts +46 -0
- package/dist/index.es.js +351 -7
- package/dist/index.es.js.map +1 -1
- package/package.json +7 -7
- package/templates/overlays/baas/backend/src/index.ts +7 -0
- package/templates/overlays/baas/backend/src/storage.ts +71 -0
- package/templates/template/.env.example +5 -0
- package/templates/template/README.md +8 -3
- package/templates/template/backend/src/index.ts +7 -0
- package/templates/template/config/admin.d.ts +9 -0
- package/templates/template/config/collections/authors.ts +12 -10
- package/templates/template/config/collections/posts.ts +17 -13
- package/templates/template/config/collections/presets/ecommerce/categories.ts +8 -6
- package/templates/template/config/collections/presets/ecommerce/orders.ts +33 -21
- package/templates/template/config/collections/presets/ecommerce/products.ts +27 -18
- package/templates/template/config/collections/tags.ts +6 -4
- package/templates/template/config/collections/users.ts +35 -30
- package/templates/template/config/frontend-assets.d.ts +17 -0
- package/templates/template/config/index.ts +6 -0
- package/templates/template/config/package.json +26 -25
- package/templates/template/config/storage.ts +88 -0
package/dist/index.es.js
CHANGED
|
@@ -1695,6 +1695,29 @@ function mayUseAmbientKey(target, cwd) {
|
|
|
1695
1695
|
return false;
|
|
1696
1696
|
}
|
|
1697
1697
|
}
|
|
1698
|
+
/**
|
|
1699
|
+
* The base URL to show in the printed usage example.
|
|
1700
|
+
*
|
|
1701
|
+
* `rebase dev` binds a port derived from the project path, not 3001, and writes
|
|
1702
|
+
* the one it actually got to `.rebase/state.json`. Printing a hardcoded
|
|
1703
|
+
* `localhost:3001` sent people to a port nothing was listening on — or, with
|
|
1704
|
+
* several projects on one machine, to a different project's backend. Prefer the
|
|
1705
|
+
* port this project last ran on; fall back to the literal only when the project
|
|
1706
|
+
* has never been started.
|
|
1707
|
+
*/
|
|
1708
|
+
function resolveExampleBaseUrl(cwd) {
|
|
1709
|
+
const projectRoot = findProjectRoot(cwd) ?? cwd;
|
|
1710
|
+
try {
|
|
1711
|
+
const state = JSON.parse(fs.readFileSync(path.join(projectRoot, ".rebase", "state.json"), "utf-8"));
|
|
1712
|
+
if (typeof state.baseUrl === "string" && state.baseUrl) return state.baseUrl;
|
|
1713
|
+
if (typeof state.port === "number") return `http://localhost:${state.port}`;
|
|
1714
|
+
} catch {}
|
|
1715
|
+
return "http://localhost:3001";
|
|
1716
|
+
}
|
|
1717
|
+
/** Whether a slug can be written as `rebase.data.<slug>` rather than a lookup. */
|
|
1718
|
+
function isIdentifierLike(slug) {
|
|
1719
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(slug);
|
|
1720
|
+
}
|
|
1698
1721
|
/** Resolve `--from` into a base URL, following the link file when asked. */
|
|
1699
1722
|
function resolveSchemaSource(from, cwd) {
|
|
1700
1723
|
if (from !== "link") {
|
|
@@ -1790,16 +1813,20 @@ export const GENERATED_AT = ${JSON.stringify((/* @__PURE__ */ new Date()).toISOS
|
|
|
1790
1813
|
console.log("");
|
|
1791
1814
|
console.log(chalk.green.bold(" ✓ SDK generated successfully!"));
|
|
1792
1815
|
console.log("");
|
|
1816
|
+
const typesImport = `./${path.relative(cwd, path.join(resolvedOutput, "database.types"))}`;
|
|
1817
|
+
const exampleSlug = collections[0]?.slug || "my_collection";
|
|
1793
1818
|
console.log(chalk.gray(" Usage:"));
|
|
1794
1819
|
console.log(chalk.gray(" import { createRebaseClient } from '@rebasepro/client';"));
|
|
1795
|
-
console.log(chalk.gray(` import type
|
|
1820
|
+
console.log(chalk.gray(` import { collectionsDictionary, type Database } from '${typesImport}';`));
|
|
1796
1821
|
console.log("");
|
|
1797
1822
|
console.log(chalk.gray(" const rebase = createRebaseClient<Database>({"));
|
|
1798
|
-
console.log(chalk.gray(
|
|
1823
|
+
console.log(chalk.gray(` baseUrl: '${resolveExampleBaseUrl(cwd)}',`));
|
|
1824
|
+
console.log(chalk.gray(" collections: collectionsDictionary,"));
|
|
1799
1825
|
console.log(chalk.gray(" // token: 'your-jwt-token',"));
|
|
1800
1826
|
console.log(chalk.gray(" });"));
|
|
1801
1827
|
console.log("");
|
|
1802
|
-
console.log(chalk.gray(` const { data } = await rebase.collection('${
|
|
1828
|
+
console.log(chalk.gray(` const { data } = await rebase.data.collection('${exampleSlug}').find();`));
|
|
1829
|
+
if (isIdentifierLike(exampleSlug)) console.log(chalk.gray(` // …or in property style: rebase.data.${exampleSlug}.find()`));
|
|
1803
1830
|
console.log("");
|
|
1804
1831
|
}
|
|
1805
1832
|
//#endregion
|
|
@@ -3288,6 +3315,30 @@ async function regenerateSchema(projectRoot, configDir, options) {
|
|
|
3288
3315
|
}
|
|
3289
3316
|
}
|
|
3290
3317
|
/**
|
|
3318
|
+
* A hand-written server entrypoint that a bundle does not use.
|
|
3319
|
+
*
|
|
3320
|
+
* `rebase dev` runs `backend/src/index.ts` whenever a project has one, so for
|
|
3321
|
+
* the whole of local development that file *is* the server and every route
|
|
3322
|
+
* written in it works. A bundle has no entrypoint of its own: the runtime boots
|
|
3323
|
+
* the bundle and mounts what the manifest points at — the config package,
|
|
3324
|
+
* functions, crons and the schema. The file is not compiled, not shipped, and
|
|
3325
|
+
* never imported.
|
|
3326
|
+
*
|
|
3327
|
+
* Nothing said so. A project with custom routes in its entrypoint built clean,
|
|
3328
|
+
* deployed green, and answered 404 on every one of them, with the file still
|
|
3329
|
+
* sitting in the repository looking exactly like the server.
|
|
3330
|
+
*
|
|
3331
|
+
* A project that means to keep its own entrypoint declares the app as
|
|
3332
|
+
* `"type": "custom"`, which builds the repository's Dockerfile instead — which
|
|
3333
|
+
* is what {@link synthesizeManifest} already infers for a manifest-less repo
|
|
3334
|
+
* carrying one. The warning names that route rather than implying the file is
|
|
3335
|
+
* a mistake.
|
|
3336
|
+
*/
|
|
3337
|
+
function findUnusedServerEntry(projectRoot, functionsDir) {
|
|
3338
|
+
const found = [path.join("backend", "src", "index.ts"), path.join(path.dirname(functionsDir), "src", "index.ts")].find((candidate) => fs.existsSync(path.join(projectRoot, candidate)));
|
|
3339
|
+
return found ? found.split(path.sep).join("/") : void 0;
|
|
3340
|
+
}
|
|
3341
|
+
/**
|
|
3291
3342
|
* Compile and assemble a bundle.
|
|
3292
3343
|
*/
|
|
3293
3344
|
async function buildBundle(options) {
|
|
@@ -3304,6 +3355,19 @@ async function buildBundle(options) {
|
|
|
3304
3355
|
if (fs.existsSync(path.join(projectRoot, paths.schema))) includes.push(paths.schema);
|
|
3305
3356
|
if (includes.length === 0) throw new Error(`Nothing to build for app "${appName}". Expected a config directory at "${paths.config}" or functions at "${paths.functions}".`);
|
|
3306
3357
|
if (paths.mode === "cms" && options.skipSchema !== true) await regenerateSchema(projectRoot, paths.config, options);
|
|
3358
|
+
const unusedEntry = findUnusedServerEntry(projectRoot, paths.functions);
|
|
3359
|
+
if (unusedEntry) {
|
|
3360
|
+
const parts = [
|
|
3361
|
+
...paths.mode === "cms" ? [`${paths.config}/`] : [],
|
|
3362
|
+
`${paths.functions}/`,
|
|
3363
|
+
"the schema"
|
|
3364
|
+
];
|
|
3365
|
+
const compiled = `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`;
|
|
3366
|
+
console.log(chalk.yellow(` ⚠ ${unusedEntry} is not the bundle's entry point — it is not compiled or shipped.`));
|
|
3367
|
+
console.log(chalk.dim(` The runtime boots the bundle itself and mounts ${compiled}.`));
|
|
3368
|
+
console.log(chalk.dim(` Routes defined there will not exist once deployed: move them to ${paths.functions}/,`));
|
|
3369
|
+
console.log(chalk.dim(` or declare this app as "type": "custom" in rebase.json to keep your own entrypoint.`));
|
|
3370
|
+
}
|
|
3307
3371
|
log(options, chalk.dim(` compiling ${includes.length} source group(s) → ${path.relative(projectRoot, outDir)}/`));
|
|
3308
3372
|
cleanOutDir(projectRoot, outDir);
|
|
3309
3373
|
const tsconfigPath = await writeBundleTsconfig(projectRoot, outDir, includes, options.skipTypeCheck === true);
|
|
@@ -3395,6 +3459,59 @@ async function buildBundle(options) {
|
|
|
3395
3459
|
* manifest — no compilation, no dependency closure (a static bundle installs
|
|
3396
3460
|
* nothing at boot).
|
|
3397
3461
|
*/
|
|
3462
|
+
/**
|
|
3463
|
+
* Fold a built static app into a backend bundle, so one runtime serves both.
|
|
3464
|
+
*
|
|
3465
|
+
* ## Why this exists
|
|
3466
|
+
*
|
|
3467
|
+
* A managed tenant runs one pod, and `bootFromBundle` on the backend path already
|
|
3468
|
+
* knows how to serve a SPA — it looks for `entry.static` and mounts `serveSPA`
|
|
3469
|
+
* last, behind `REBASE_SERVE_STATIC`. What was missing was anything putting the
|
|
3470
|
+
* assets there.
|
|
3471
|
+
*
|
|
3472
|
+
* The consequence was not subtle. A project whose custom image served its website
|
|
3473
|
+
* at `/` and its API at `/api` — the shape the scaffolded template produces — lost
|
|
3474
|
+
* the website the moment it moved to the managed runtime: the API answered
|
|
3475
|
+
* perfectly and every page 404'd. Managed could not be a drop-in replacement for
|
|
3476
|
+
* custom while the frontend simply vanished.
|
|
3477
|
+
*
|
|
3478
|
+
* Folding restores parity with the container it replaces, which is the only
|
|
3479
|
+
* honest baseline. It is deliberately the FIRST implementation and not the last:
|
|
3480
|
+
* a static app on its own bucket behind a CDN is better for cache behaviour and
|
|
3481
|
+
* lets the frontend deploy independently. But that needs infrastructure that does
|
|
3482
|
+
* not exist yet, and "your site is gone" is not an acceptable state to leave a
|
|
3483
|
+
* project in while it gets built.
|
|
3484
|
+
*
|
|
3485
|
+
* The trade it makes, stated plainly: frontend and backend now deploy together
|
|
3486
|
+
* and the bundle carries the built assets. For a project that was shipping both
|
|
3487
|
+
* in one image already, that is exactly what it had.
|
|
3488
|
+
*/
|
|
3489
|
+
function foldStaticIntoBundle(options) {
|
|
3490
|
+
const { bundleDir, assetsDir } = options;
|
|
3491
|
+
const manifestPath = path.join(bundleDir, "manifest.json");
|
|
3492
|
+
if (!fs.existsSync(manifestPath)) throw new Error(`No manifest at ${manifestPath} — build the backend bundle first.`);
|
|
3493
|
+
if (!fs.existsSync(assetsDir)) throw new Error(`No built assets at ${assetsDir}.`);
|
|
3494
|
+
const staticOut = path.join(bundleDir, "static");
|
|
3495
|
+
fs.rmSync(staticOut, {
|
|
3496
|
+
recursive: true,
|
|
3497
|
+
force: true
|
|
3498
|
+
});
|
|
3499
|
+
fs.mkdirSync(staticOut, { recursive: true });
|
|
3500
|
+
fs.cpSync(assetsDir, staticOut, { recursive: true });
|
|
3501
|
+
let fileCount = 0;
|
|
3502
|
+
const count = (dir) => {
|
|
3503
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) if (entry.isDirectory()) count(path.join(dir, entry.name));
|
|
3504
|
+
else fileCount++;
|
|
3505
|
+
};
|
|
3506
|
+
count(staticOut);
|
|
3507
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
3508
|
+
manifest.entry = {
|
|
3509
|
+
...manifest.entry,
|
|
3510
|
+
static: "static"
|
|
3511
|
+
};
|
|
3512
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
3513
|
+
return { fileCount };
|
|
3514
|
+
}
|
|
3398
3515
|
function buildStaticBundle(options) {
|
|
3399
3516
|
const { projectRoot, appName, assetsDir, outDir, runtimeRange } = options;
|
|
3400
3517
|
cleanOutDir(projectRoot, outDir);
|
|
@@ -3517,6 +3634,76 @@ function resolveCliVersion() {
|
|
|
3517
3634
|
return "unknown";
|
|
3518
3635
|
}
|
|
3519
3636
|
//#endregion
|
|
3637
|
+
//#region src/fold-static.ts
|
|
3638
|
+
/**
|
|
3639
|
+
* Folding a project's frontend into its backend bundle.
|
|
3640
|
+
*
|
|
3641
|
+
* Shared by `rebase build` and `rebase cloud deploy` deliberately. It lived in
|
|
3642
|
+
* the build *command* first, and `deploy` rebuilds the bundle itself — so a
|
|
3643
|
+
* deploy silently produced a bundle without the frontend, packed 164 KB where
|
|
3644
|
+
* 39 MB was expected, and the site 404'd on the managed runtime exactly as if
|
|
3645
|
+
* folding had never been written. Two callers building the same artefact must
|
|
3646
|
+
* share the step that completes it.
|
|
3647
|
+
*
|
|
3648
|
+
* Why fold at all: `bootFromBundle` already serves a SPA from `entry.static`
|
|
3649
|
+
* behind `REBASE_SERVE_STATIC` (default on). A managed tenant runs one pod, so
|
|
3650
|
+
* putting the built site in the bundle gives it the shape a custom container
|
|
3651
|
+
* already had — site at `/`, API at `/api` — which is the only honest baseline
|
|
3652
|
+
* for calling the managed runtime a drop-in replacement.
|
|
3653
|
+
*/
|
|
3654
|
+
/**
|
|
3655
|
+
* Which static app, if any, should be served by the backend.
|
|
3656
|
+
*
|
|
3657
|
+
* Exactly one `static` app is folded. With several, folding would have to choose,
|
|
3658
|
+
* and silently picking one of two websites is worse than doing nothing — so it
|
|
3659
|
+
* declines and names what it saw. Pure, so the decision is testable without a
|
|
3660
|
+
* filesystem.
|
|
3661
|
+
*/
|
|
3662
|
+
function selectFoldableApp(manifest) {
|
|
3663
|
+
const statics = Object.entries(manifest.apps ?? {}).filter(([, app]) => app?.type === "static").map(([name, app]) => ({
|
|
3664
|
+
name,
|
|
3665
|
+
build: app?.build,
|
|
3666
|
+
output: app?.output
|
|
3667
|
+
}));
|
|
3668
|
+
if (statics.length === 0) return {};
|
|
3669
|
+
if (statics.length > 1) return { reason: `${statics.length} static apps (${statics.map((s) => s.name).join(", ")}) — none folded in. Pick one to serve from the backend, or host them separately.` };
|
|
3670
|
+
const only = statics[0];
|
|
3671
|
+
if (!only.output) return { reason: `"${only.name}" declares no output directory — not folded in.` };
|
|
3672
|
+
return { app: only };
|
|
3673
|
+
}
|
|
3674
|
+
/**
|
|
3675
|
+
* Build the project's frontend and fold it into the backend bundle.
|
|
3676
|
+
*
|
|
3677
|
+
* Throws rather than exiting, so the caller decides whether a missing frontend
|
|
3678
|
+
* should fail its command — a `build` may reasonably want to stop, and so should
|
|
3679
|
+
* a deploy, but that is not this function's call to make.
|
|
3680
|
+
*/
|
|
3681
|
+
async function foldFrontendIntoBundle(options) {
|
|
3682
|
+
const { projectRoot, manifest, bundleDir, skipBuild } = options;
|
|
3683
|
+
const log = options.log ?? ((m) => console.log(m));
|
|
3684
|
+
const { app, reason } = selectFoldableApp(manifest);
|
|
3685
|
+
if (reason) {
|
|
3686
|
+
log(chalk.yellow(` ⚠ ${reason}`));
|
|
3687
|
+
return null;
|
|
3688
|
+
}
|
|
3689
|
+
if (!app) return null;
|
|
3690
|
+
if (app.build && !skipBuild) await execa(app.build, {
|
|
3691
|
+
cwd: projectRoot,
|
|
3692
|
+
stdio: "inherit",
|
|
3693
|
+
shell: true
|
|
3694
|
+
});
|
|
3695
|
+
const assetsDir = path.join(projectRoot, app.output);
|
|
3696
|
+
if (!fs.existsSync(assetsDir)) throw new Error(`"${app.name}" declared output "${app.output}" does not exist after building — the bundle would ship without a frontend.`);
|
|
3697
|
+
const { fileCount } = foldStaticIntoBundle({
|
|
3698
|
+
bundleDir,
|
|
3699
|
+
assetsDir
|
|
3700
|
+
});
|
|
3701
|
+
return {
|
|
3702
|
+
appName: app.name,
|
|
3703
|
+
fileCount
|
|
3704
|
+
};
|
|
3705
|
+
}
|
|
3706
|
+
//#endregion
|
|
3520
3707
|
//#region src/commands/build.ts
|
|
3521
3708
|
/**
|
|
3522
3709
|
* CLI command: rebase build [app...]
|
|
@@ -3557,6 +3744,8 @@ async function buildCommand(rawArgs = []) {
|
|
|
3557
3744
|
"--out": String,
|
|
3558
3745
|
"--skip-type-check": Boolean,
|
|
3559
3746
|
"--skip-schema": Boolean,
|
|
3747
|
+
"--no-static": Boolean,
|
|
3748
|
+
"--skip-static-build": Boolean,
|
|
3560
3749
|
"--legacy": Boolean,
|
|
3561
3750
|
"--help": Boolean,
|
|
3562
3751
|
"-h": "--help"
|
|
@@ -3627,6 +3816,19 @@ async function buildCommand(rawArgs = []) {
|
|
|
3627
3816
|
console.log(chalk.yellow(` ⚠ native dependencies detected: ${names}`));
|
|
3628
3817
|
console.log(chalk.dim(" These cannot run on the managed runtime. See `rebase doctor`."));
|
|
3629
3818
|
}
|
|
3819
|
+
if (!args["--no-static"]) {
|
|
3820
|
+
const folded = await foldFrontendIntoBundle({
|
|
3821
|
+
projectRoot,
|
|
3822
|
+
manifest,
|
|
3823
|
+
bundleDir: result.outDir,
|
|
3824
|
+
skipBuild: args["--skip-static-build"] === true,
|
|
3825
|
+
log: (m) => console.log(m)
|
|
3826
|
+
}).catch((err) => {
|
|
3827
|
+
console.error(chalk.red(` ✗ ${err instanceof Error ? err.message : String(err)}`));
|
|
3828
|
+
process.exit(1);
|
|
3829
|
+
});
|
|
3830
|
+
if (folded) console.log(chalk.green(` ✓ ${folded.appName} folded in`) + chalk.dim(` (${folded.fileCount} file(s) → served at /)`));
|
|
3831
|
+
}
|
|
3630
3832
|
} else if (app.type === "static" || app.type === "admin") await buildAssetApp(projectRoot, name, app, manifest.runtime, args["--out"]);
|
|
3631
3833
|
else if (app.type === "custom") console.log(chalk.dim(" custom container — built at deploy time from its Dockerfile"));
|
|
3632
3834
|
console.log("");
|
|
@@ -5124,9 +5326,28 @@ function bundleDeployBody(input) {
|
|
|
5124
5326
|
app: input.app ?? input.manifest.app ?? "backend",
|
|
5125
5327
|
client: "cli",
|
|
5126
5328
|
frameworkVersion: input.manifest.runtime?.builtAgainst,
|
|
5329
|
+
...input.declaredApps?.length ? { declaredApps: input.declaredApps } : {},
|
|
5127
5330
|
...input.message ? { message: input.message } : {}
|
|
5128
5331
|
};
|
|
5129
5332
|
}
|
|
5333
|
+
/**
|
|
5334
|
+
* The apps a project manifest declares.
|
|
5335
|
+
*
|
|
5336
|
+
* A deploy only ever ships ONE app's bundle, so the trigger alone could never
|
|
5337
|
+
* tell the platform that the repository also contains a web frontend and an
|
|
5338
|
+
* admin panel — and the Apps page, whose whole job is to show the set, listed a
|
|
5339
|
+
* single entry called "backend". Sending the declared set fixes that without
|
|
5340
|
+
* pretending the others are deployed: the platform registers them, and their
|
|
5341
|
+
* status says what is actually true.
|
|
5342
|
+
*/
|
|
5343
|
+
function declaredAppsFrom(manifest) {
|
|
5344
|
+
const apps = manifest?.apps;
|
|
5345
|
+
if (!apps || typeof apps !== "object") return [];
|
|
5346
|
+
return Object.entries(apps).filter(([name]) => name.trim().length > 0).map(([name, value]) => ({
|
|
5347
|
+
name,
|
|
5348
|
+
type: String(value?.type ?? "custom")
|
|
5349
|
+
}));
|
|
5350
|
+
}
|
|
5130
5351
|
/** Upload a bundle archive; returns the control-plane bundle id. */
|
|
5131
5352
|
async function uploadBundle(url, token, projectId, tarPath) {
|
|
5132
5353
|
const bytes = fs.readFileSync(tarPath);
|
|
@@ -5154,6 +5375,12 @@ async function uploadBundle(url, token, projectId, tarPath) {
|
|
|
5154
5375
|
* `deploy` triggers the control-plane `deploy` function, then tails the build
|
|
5155
5376
|
* logs from the deployment record until it succeeds or fails. `logs` shows the
|
|
5156
5377
|
* latest build log, or runtime logs with `--runtime`.
|
|
5378
|
+
*
|
|
5379
|
+
* There are three deploys behind the one verb, and which one runs depends on the
|
|
5380
|
+
* flags: `--bundle` builds and uploads a managed bundle, `--source .` uploads
|
|
5381
|
+
* this directory as a build context, and the bare form uploads nothing and asks
|
|
5382
|
+
* the control plane to rebuild what it already holds. That last one is the
|
|
5383
|
+
* dangerous one — see `planBareDeploy`.
|
|
5157
5384
|
*/
|
|
5158
5385
|
var POLL_INTERVAL_MS = 1500;
|
|
5159
5386
|
var POLL_TIMEOUT_MS = 900 * 1e3;
|
|
@@ -5276,8 +5503,20 @@ async function deployBundle(opts) {
|
|
|
5276
5503
|
appName: backend.name,
|
|
5277
5504
|
app: backend.app,
|
|
5278
5505
|
runtimeRange: loaded.manifest.runtime,
|
|
5506
|
+
skipTypeCheck: opts.skipTypeCheck,
|
|
5279
5507
|
log: (m) => console.log(chalk.gray(m))
|
|
5280
5508
|
})).outDir;
|
|
5509
|
+
try {
|
|
5510
|
+
const folded = await foldFrontendIntoBundle({
|
|
5511
|
+
projectRoot,
|
|
5512
|
+
manifest: loaded.manifest,
|
|
5513
|
+
bundleDir,
|
|
5514
|
+
log: (m) => console.log(m)
|
|
5515
|
+
});
|
|
5516
|
+
if (folded) console.log(chalk.gray(` folded ${folded.appName} in (${folded.fileCount} file(s), served at /)`));
|
|
5517
|
+
} catch (err) {
|
|
5518
|
+
fail(err instanceof Error ? err.message : String(err), "Fix the frontend build, or pass --no-static to deploy the API alone.");
|
|
5519
|
+
}
|
|
5281
5520
|
}
|
|
5282
5521
|
const manifest = readBundleManifest(bundleDir);
|
|
5283
5522
|
if (manifest.hooks?.native) {
|
|
@@ -5301,11 +5540,16 @@ async function deployBundle(opts) {
|
|
|
5301
5540
|
}
|
|
5302
5541
|
console.log("");
|
|
5303
5542
|
console.log(` 🚀 Triggering managed deployment for ${chalk.bold(projectRef)} (schema ${manifest.schemaVersion})...`);
|
|
5543
|
+
let declaredApps = [];
|
|
5544
|
+
try {
|
|
5545
|
+
declaredApps = declaredAppsFrom(loadManifest(process.cwd()).manifest);
|
|
5546
|
+
} catch {}
|
|
5304
5547
|
const body = bundleDeployBody({
|
|
5305
5548
|
projectId,
|
|
5306
5549
|
bundleId,
|
|
5307
5550
|
manifest,
|
|
5308
|
-
message: opts.message
|
|
5551
|
+
message: opts.message,
|
|
5552
|
+
declaredApps
|
|
5309
5553
|
});
|
|
5310
5554
|
try {
|
|
5311
5555
|
const res = await client.functions.invoke("deploy", body);
|
|
@@ -5323,6 +5567,89 @@ async function deployBundle(opts) {
|
|
|
5323
5567
|
reportError(e, "Managed deploy failed to start");
|
|
5324
5568
|
}
|
|
5325
5569
|
}
|
|
5570
|
+
function pick(row, ...keys) {
|
|
5571
|
+
for (const key of keys) {
|
|
5572
|
+
const raw = row?.[key];
|
|
5573
|
+
if (typeof raw === "string" && raw.trim() !== "") return raw.trim();
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5576
|
+
/** Rough age of a timestamp, for "…uploaded 6d ago". Undefined if unreadable. */
|
|
5577
|
+
function timeAgo(value, now) {
|
|
5578
|
+
if (value === void 0) return void 0;
|
|
5579
|
+
const then = value instanceof Date ? value.getTime() : new Date(value).getTime();
|
|
5580
|
+
if (Number.isNaN(then)) return void 0;
|
|
5581
|
+
const ms = now.getTime() - then;
|
|
5582
|
+
if (ms < 0) return void 0;
|
|
5583
|
+
const minutes = Math.floor(ms / 6e4);
|
|
5584
|
+
if (minutes < 1) return "just now";
|
|
5585
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
5586
|
+
const hours = Math.floor(minutes / 60);
|
|
5587
|
+
if (hours < 24) return `${hours}h ago`;
|
|
5588
|
+
return `${Math.floor(hours / 24)}d ago`;
|
|
5589
|
+
}
|
|
5590
|
+
/**
|
|
5591
|
+
* Whether this project runs on the managed runtime.
|
|
5592
|
+
*
|
|
5593
|
+
* `runtimeMode` on the project row is the authority — the control plane writes
|
|
5594
|
+
* it. The bundle-id fallback covers a control plane that does not return the
|
|
5595
|
+
* field: a successful deploy that served a bundle only happens on the managed
|
|
5596
|
+
* path.
|
|
5597
|
+
*/
|
|
5598
|
+
function isManagedProject(project, latest) {
|
|
5599
|
+
if (pick(project, "runtimeMode", "runtime_mode") === "managed") return true;
|
|
5600
|
+
return latest?.status === "success" && pick(latest, "bundleId", "bundle_id") !== void 0;
|
|
5601
|
+
}
|
|
5602
|
+
/** What a `deploy` with nothing attached will build, in the words to print. */
|
|
5603
|
+
function planBareDeploy(project, latest, now) {
|
|
5604
|
+
const projectRow = project;
|
|
5605
|
+
const deploymentRow = latest;
|
|
5606
|
+
const managed = isManagedProject(project, latest);
|
|
5607
|
+
const repo = pick(projectRow, "gitRepoUrl", "git_repo_url");
|
|
5608
|
+
if (repo) {
|
|
5609
|
+
const branch = pick(projectRow, "gitBranch", "git_branch");
|
|
5610
|
+
return {
|
|
5611
|
+
managed,
|
|
5612
|
+
source: "git",
|
|
5613
|
+
lines: [`Building from git: ${repo}${branch ? ` (${branch})` : ""}.`]
|
|
5614
|
+
};
|
|
5615
|
+
}
|
|
5616
|
+
if (pick(deploymentRow, "sourceRef", "source_ref")) {
|
|
5617
|
+
const age = timeAgo(latest?.createdAt ?? latest?.created_at, now);
|
|
5618
|
+
return {
|
|
5619
|
+
managed,
|
|
5620
|
+
source: "snapshot",
|
|
5621
|
+
lines: [`Rebuilding the stored source archive${latest?.id !== void 0 ? ` from deployment ${latest.id}` : ""}${age ? `, uploaded ${age}` : ""}.`, "This directory is NOT uploaded — pass `--source .` to build what is on disk."]
|
|
5622
|
+
};
|
|
5623
|
+
}
|
|
5624
|
+
return {
|
|
5625
|
+
managed,
|
|
5626
|
+
source: "none",
|
|
5627
|
+
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."]
|
|
5628
|
+
};
|
|
5629
|
+
}
|
|
5630
|
+
/** The one sentence that says a source build undoes `runtimeMode: managed`. */
|
|
5631
|
+
function ejectWarning(projectRef) {
|
|
5632
|
+
return `⚠ ${projectRef} runs on the managed runtime — a source build ejects it to a custom container.`;
|
|
5633
|
+
}
|
|
5634
|
+
/**
|
|
5635
|
+
* Read the two rows the preflight needs.
|
|
5636
|
+
*
|
|
5637
|
+
* Best effort by construction: a preflight that cannot read is a preflight that
|
|
5638
|
+
* says nothing, never a deploy that fails. The managed refusal rides on the same
|
|
5639
|
+
* read, so an unreadable project falls through to the old behaviour rather than
|
|
5640
|
+
* blocking a deploy on a lookup.
|
|
5641
|
+
*/
|
|
5642
|
+
async function readDeployContext(client, projectId) {
|
|
5643
|
+
try {
|
|
5644
|
+
const [project, latest] = await Promise.all([client.data.collection("projects").findById(projectId), latestDeployment(client, projectId)]);
|
|
5645
|
+
return {
|
|
5646
|
+
project,
|
|
5647
|
+
latest
|
|
5648
|
+
};
|
|
5649
|
+
} catch {
|
|
5650
|
+
return {};
|
|
5651
|
+
}
|
|
5652
|
+
}
|
|
5326
5653
|
async function deployCommand(rawArgs, projectRef) {
|
|
5327
5654
|
const args = arg({
|
|
5328
5655
|
"--no-follow": Boolean,
|
|
@@ -5330,6 +5657,8 @@ async function deployCommand(rawArgs, projectRef) {
|
|
|
5330
5657
|
"--message": String,
|
|
5331
5658
|
"--bundle": Boolean,
|
|
5332
5659
|
"--bundle-dir": String,
|
|
5660
|
+
"--skip-type-check": Boolean,
|
|
5661
|
+
"--force": Boolean,
|
|
5333
5662
|
"-m": "--message"
|
|
5334
5663
|
}, {
|
|
5335
5664
|
argv: rawArgs.slice(2),
|
|
@@ -5345,10 +5674,25 @@ async function deployCommand(rawArgs, projectRef) {
|
|
|
5345
5674
|
projectId,
|
|
5346
5675
|
projectRef,
|
|
5347
5676
|
bundleDir: args["--bundle-dir"],
|
|
5348
|
-
message: args["--message"]
|
|
5677
|
+
message: args["--message"],
|
|
5678
|
+
skipTypeCheck: args["--skip-type-check"] === true
|
|
5349
5679
|
});
|
|
5350
5680
|
return;
|
|
5351
5681
|
}
|
|
5682
|
+
const { project, latest } = await readDeployContext(client, projectId);
|
|
5683
|
+
const plan = planBareDeploy(project, latest, /* @__PURE__ */ new Date());
|
|
5684
|
+
if (!args["--source"]) {
|
|
5685
|
+
if (plan.managed && args["--force"] !== true) fail(`${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.`, "Redeploy it with `rebase cloud deploy --bundle`. To eject on purpose, pass `--source .` to build this directory, or `--force` to build what the control plane holds.", "managed_project");
|
|
5686
|
+
if (!isJsonMode()) {
|
|
5687
|
+
console.log("");
|
|
5688
|
+
if (plan.managed) console.log(chalk.yellow(` ${ejectWarning(projectRef)}`));
|
|
5689
|
+
for (const line of plan.lines) console.log(chalk.gray(` ${line}`));
|
|
5690
|
+
}
|
|
5691
|
+
} else if (plan.managed && !isJsonMode()) {
|
|
5692
|
+
console.log("");
|
|
5693
|
+
console.log(chalk.yellow(` ${ejectWarning(projectRef)}`));
|
|
5694
|
+
console.log(chalk.gray(" Use `rebase cloud deploy --bundle` to stay on managed."));
|
|
5695
|
+
}
|
|
5352
5696
|
let source;
|
|
5353
5697
|
if (args["--source"]) {
|
|
5354
5698
|
const tarPath = await createSourceTarball(args["--source"]);
|
|
@@ -8560,7 +8904,7 @@ ${chalk.green.bold("Projects")}
|
|
|
8560
8904
|
${chalk.blue.bold("projects delete")} ${chalk.gray("[id]")} Delete a project
|
|
8561
8905
|
|
|
8562
8906
|
${chalk.green.bold("Deploy & observe")}
|
|
8563
|
-
${chalk.blue.bold("deploy")} ${chalk.gray("[--source .]
|
|
8907
|
+
${chalk.blue.bold("deploy")} ${chalk.gray("[--bundle|--source .]")} Deploy the linked project + stream build logs
|
|
8564
8908
|
${chalk.blue.bold("logs")} ${chalk.gray("[--runtime] [-f]")} Show build (or runtime) logs
|
|
8565
8909
|
${chalk.blue.bold("deployments list")} ${chalk.gray("[--limit N|--all]")} Deployment history ${chalk.gray("(status, duration, trigger)")}
|
|
8566
8910
|
${chalk.blue.bold("rollback")} ${chalk.gray("[id] [-y]")} Roll back to a successful deploy
|
|
@@ -8966,6 +9310,6 @@ ${chalk.gray("Documentation: https://rebase.pro/docs")}
|
|
|
8966
9310
|
`);
|
|
8967
9311
|
}
|
|
8968
9312
|
//#endregion
|
|
8969
|
-
export { CURRENT_RUNTIME_RANGE, DEFAULT_BUNDLE_DIR, DEFAULT_CONFIG_DIR, DEFAULT_CRONS_DIR, DEFAULT_FUNCTIONS_DIR, DEFAULT_SCHEMA_FILE, MANIFEST_FILENAME, ManifestError, appsCommand, assessManagedCompatibility, authCommand, buildBundle, buildCommand, buildInitQuestions, buildStaticBundle, buildableApps, cloudCommand, collectDeclaredDependencies, configureEnvFile, createRebaseApp, dbCommand, detectNativeDependencies, detectPackageManager, detectStorageAuthorize, devCommand, doctorCommand, entry, findBackendApp, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, formatCdTarget, generateSdkCommand, getActiveBackendPlugin, getPMCommands, isPnpmAvailable, loadManifest, manifestExists, manifestPath, normalizeEsmSpecifiers, pnpmAvailabilityFromProbe, printInitHelp, requireBackendDir, requireProjectRoot, resetPnpmAvailabilityCache, resolveBackendPaths, resolveLocalBin, resolvePluginCliScript, resolveTsx, schemaCommand, startCommand, synthesizeManifest, validateManifest, validateProjectName, validateTsxInstallation, writeManifest };
|
|
9313
|
+
export { CURRENT_RUNTIME_RANGE, DEFAULT_BUNDLE_DIR, DEFAULT_CONFIG_DIR, DEFAULT_CRONS_DIR, DEFAULT_FUNCTIONS_DIR, DEFAULT_SCHEMA_FILE, MANIFEST_FILENAME, ManifestError, appsCommand, assessManagedCompatibility, authCommand, buildBundle, buildCommand, buildInitQuestions, buildStaticBundle, buildableApps, cloudCommand, collectDeclaredDependencies, configureEnvFile, createRebaseApp, dbCommand, detectNativeDependencies, detectPackageManager, detectStorageAuthorize, devCommand, doctorCommand, 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 };
|
|
8970
9314
|
|
|
8971
9315
|
//# sourceMappingURL=index.es.js.map
|