@rebasepro/cli 0.10.1-canary.ff9ccd6 → 0.10.1-canary.g4db1bb1

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 CHANGED
@@ -80,6 +80,27 @@ export declare function normalizeEsmSpecifiers(outDir: string): {
80
80
  rewritten: number;
81
81
  unresolved: string[];
82
82
  };
83
+ /**
84
+ * A hand-written server entrypoint that a bundle does not use.
85
+ *
86
+ * `rebase dev` runs `backend/src/index.ts` whenever a project has one, so for
87
+ * the whole of local development that file *is* the server and every route
88
+ * written in it works. A bundle has no entrypoint of its own: the runtime boots
89
+ * the bundle and mounts what the manifest points at — the config package,
90
+ * functions, crons and the schema. The file is not compiled, not shipped, and
91
+ * never imported.
92
+ *
93
+ * Nothing said so. A project with custom routes in its entrypoint built clean,
94
+ * deployed green, and answered 404 on every one of them, with the file still
95
+ * sitting in the repository looking exactly like the server.
96
+ *
97
+ * A project that means to keep its own entrypoint declares the app as
98
+ * `"type": "custom"`, which builds the repository's Dockerfile instead — which
99
+ * is what {@link synthesizeManifest} already infers for a manifest-less repo
100
+ * carrying one. The warning names that route rather than implying the file is
101
+ * a mistake.
102
+ */
103
+ export declare function findUnusedServerEntry(projectRoot: string, functionsDir: string): string | undefined;
83
104
  /**
84
105
  * Compile and assemble a bundle.
85
106
  */
@@ -1,2 +1,50 @@
1
+ /** A project row, reduced to what says how it deploys (camel or snake columns). */
2
+ export interface DeployProjectRow {
3
+ runtimeMode?: string;
4
+ runtime_mode?: string;
5
+ gitRepoUrl?: string;
6
+ git_repo_url?: string;
7
+ gitBranch?: string;
8
+ git_branch?: string;
9
+ }
10
+ /** A deployment row, reduced to what says what it was built from. */
11
+ export interface DeploySourceRow {
12
+ id?: string | number;
13
+ status?: string;
14
+ createdAt?: string | Date;
15
+ created_at?: string | Date;
16
+ sourceRef?: string;
17
+ source_ref?: string;
18
+ bundleId?: string;
19
+ bundle_id?: string;
20
+ }
21
+ export interface BareDeployPlan {
22
+ /**
23
+ * Whether the project runs the platform runtime — in which case any source
24
+ * build here ejects it back onto a container image.
25
+ */
26
+ managed: boolean;
27
+ /**
28
+ * `git` — the control plane will clone the configured repository.
29
+ * `snapshot` — it will rebuild the newest uploaded source archive.
30
+ * `none` — it holds neither, and will refuse.
31
+ */
32
+ source: "git" | "snapshot" | "none";
33
+ /** Lines describing the build, printed before it is triggered. */
34
+ lines: string[];
35
+ }
36
+ /** Rough age of a timestamp, for "…uploaded 6d ago". Undefined if unreadable. */
37
+ export declare function timeAgo(value: string | Date | undefined, now: Date): string | undefined;
38
+ /**
39
+ * Whether this project runs on the managed runtime.
40
+ *
41
+ * `runtimeMode` on the project row is the authority — the control plane writes
42
+ * it. The bundle-id fallback covers a control plane that does not return the
43
+ * field: a successful deploy that served a bundle only happens on the managed
44
+ * path.
45
+ */
46
+ export declare function isManagedProject(project: DeployProjectRow | undefined, latest: DeploySourceRow | undefined): boolean;
47
+ /** What a `deploy` with nothing attached will build, in the words to print. */
48
+ export declare function planBareDeploy(project: DeployProjectRow | undefined, latest: DeploySourceRow | undefined, now: Date): BareDeployPlan;
1
49
  export declare function deployCommand(rawArgs: string[], projectRef: string): Promise<void>;
2
50
  export declare function logsCommand(rawArgs: string[], projectRef: string): Promise<void>;
@@ -23,6 +23,19 @@ interface GenerateSDKArgs {
23
23
  token?: string;
24
24
  help?: boolean;
25
25
  }
26
+ /**
27
+ * The base URL to show in the printed usage example.
28
+ *
29
+ * `rebase dev` binds a port derived from the project path, not 3001, and writes
30
+ * the one it actually got to `.rebase/state.json`. Printing a hardcoded
31
+ * `localhost:3001` sent people to a port nothing was listening on — or, with
32
+ * several projects on one machine, to a different project's backend. Prefer the
33
+ * port this project last ran on; fall back to the literal only when the project
34
+ * has never been started.
35
+ */
36
+ export declare function resolveExampleBaseUrl(cwd: string): string;
37
+ /** Whether a slug can be written as `rebase.data.<slug>` rather than a lookup. */
38
+ export declare function isIdentifierLike(slug: string): boolean;
26
39
  /**
27
40
  * Main entry point for the generate-sdk command.
28
41
  */
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 { Database } from './${path.relative(cwd, path.join(resolvedOutput, "database.types"))}';`));
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(" baseUrl: 'http://localhost:3001',"));
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('${collections[0]?.slug || "my_collection"}').find();`));
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);
@@ -5311,6 +5375,12 @@ async function uploadBundle(url, token, projectId, tarPath) {
5311
5375
  * `deploy` triggers the control-plane `deploy` function, then tails the build
5312
5376
  * logs from the deployment record until it succeeds or fails. `logs` shows the
5313
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`.
5314
5384
  */
5315
5385
  var POLL_INTERVAL_MS = 1500;
5316
5386
  var POLL_TIMEOUT_MS = 900 * 1e3;
@@ -5433,6 +5503,7 @@ async function deployBundle(opts) {
5433
5503
  appName: backend.name,
5434
5504
  app: backend.app,
5435
5505
  runtimeRange: loaded.manifest.runtime,
5506
+ skipTypeCheck: opts.skipTypeCheck,
5436
5507
  log: (m) => console.log(chalk.gray(m))
5437
5508
  })).outDir;
5438
5509
  try {
@@ -5496,6 +5567,89 @@ async function deployBundle(opts) {
5496
5567
  reportError(e, "Managed deploy failed to start");
5497
5568
  }
5498
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
+ }
5499
5653
  async function deployCommand(rawArgs, projectRef) {
5500
5654
  const args = arg({
5501
5655
  "--no-follow": Boolean,
@@ -5503,6 +5657,8 @@ async function deployCommand(rawArgs, projectRef) {
5503
5657
  "--message": String,
5504
5658
  "--bundle": Boolean,
5505
5659
  "--bundle-dir": String,
5660
+ "--skip-type-check": Boolean,
5661
+ "--force": Boolean,
5506
5662
  "-m": "--message"
5507
5663
  }, {
5508
5664
  argv: rawArgs.slice(2),
@@ -5518,10 +5674,25 @@ async function deployCommand(rawArgs, projectRef) {
5518
5674
  projectId,
5519
5675
  projectRef,
5520
5676
  bundleDir: args["--bundle-dir"],
5521
- message: args["--message"]
5677
+ message: args["--message"],
5678
+ skipTypeCheck: args["--skip-type-check"] === true
5522
5679
  });
5523
5680
  return;
5524
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
+ }
5525
5696
  let source;
5526
5697
  if (args["--source"]) {
5527
5698
  const tarPath = await createSourceTarball(args["--source"]);
@@ -8733,7 +8904,7 @@ ${chalk.green.bold("Projects")}
8733
8904
  ${chalk.blue.bold("projects delete")} ${chalk.gray("[id]")} Delete a project
8734
8905
 
8735
8906
  ${chalk.green.bold("Deploy & observe")}
8736
- ${chalk.blue.bold("deploy")} ${chalk.gray("[--source .] [-m msg]")} Deploy the linked project + stream build logs
8907
+ ${chalk.blue.bold("deploy")} ${chalk.gray("[--bundle|--source .]")} Deploy the linked project + stream build logs
8737
8908
  ${chalk.blue.bold("logs")} ${chalk.gray("[--runtime] [-f]")} Show build (or runtime) logs
8738
8909
  ${chalk.blue.bold("deployments list")} ${chalk.gray("[--limit N|--all]")} Deployment history ${chalk.gray("(status, duration, trigger)")}
8739
8910
  ${chalk.blue.bold("rollback")} ${chalk.gray("[id] [-y]")} Roll back to a successful deploy
@@ -9139,6 +9310,6 @@ ${chalk.gray("Documentation: https://rebase.pro/docs")}
9139
9310
  `);
9140
9311
  }
9141
9312
  //#endregion
9142
- 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, foldStaticIntoBundle, 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 };
9143
9314
 
9144
9315
  //# sourceMappingURL=index.es.js.map