@rebasepro/cli 0.12.1-canary.g389e9b2 → 0.12.1-canary.g4e7bcbf

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.
@@ -84,6 +84,39 @@ export interface ProjectLink {
84
84
  export declare function readLink(cwd?: string): ProjectLink | null;
85
85
  export declare function writeLink(link: ProjectLink, cwd?: string): void;
86
86
  export declare function removeLink(cwd?: string): boolean;
87
+ /**
88
+ * Flags that may appear anywhere on a `rebase cloud` line, including *before*
89
+ * the resource group.
90
+ *
91
+ * They have to be declared wherever positionals are resolved, because `arg`'s
92
+ * `permissive: true` does not merely tolerate an undeclared flag — it pushes it
93
+ * into `_` alongside the positionals, and for a flag that takes a value it
94
+ * pushes the value in too. So `cloud --project acme storage create` parsed
95
+ * without this spec yields `_` of `["--project", "acme", "storage", "create"]`,
96
+ * and the group reads as `"acme"`: a real project name, in the group position,
97
+ * dispatching to nothing. Skipping tokens that start with `-` does not save you
98
+ * there — the damage is the orphaned value, which looks exactly like a
99
+ * positional.
100
+ *
101
+ * Only genuinely global flags belong here. Group-specific ones (`--bucket`,
102
+ * `--region`, …) are declared by the handler that owns them and always follow
103
+ * the group, so they cannot shift the group or action.
104
+ *
105
+ * `-p` is `--project` in eighteen places and `--password` in `login`. That
106
+ * ambiguity does not matter to the one caller that reads this spec: it resolves
107
+ * positionals and never looks at a flag's value, so all it needs to know is
108
+ * that `-p` takes one. Anything that wants the value must keep declaring it
109
+ * itself, with the meaning its own command gives it.
110
+ */
111
+ export declare const GLOBAL_CLOUD_FLAGS: {
112
+ readonly "--json": BooleanConstructor;
113
+ readonly "--yes": BooleanConstructor;
114
+ readonly "--help": BooleanConstructor;
115
+ readonly "--project": StringConstructor;
116
+ readonly "-p": "--project";
117
+ readonly "-y": "--yes";
118
+ readonly "-h": "--help";
119
+ };
87
120
  /**
88
121
  * The raw project reference to operate on: explicit `--project` flag wins,
89
122
  * otherwise the linked project. Exits with guidance when neither is present.
@@ -134,6 +167,26 @@ export declare function printJson(value: unknown): void;
134
167
  * call is what guarantees a command can never print a table AND a JSON blob.
135
168
  */
136
169
  export declare function emit(human: () => void, json: unknown): void;
170
+ /**
171
+ * Print a warning (+ optional hint) — in every output mode, always to stderr.
172
+ *
173
+ * `emit` is for a command's *result*, and JSON mode legitimately replaces the
174
+ * human rendering of one. A warning is not a result: it says the command is
175
+ * about to do something the caller may not have meant, and that is exactly as
176
+ * true when the output is piped. Gating one on `!isJsonMode()` deleted it
177
+ * precisely where nobody was watching the terminal — a `--source` deploy ejected
178
+ * a live project off the managed runtime and said so only to a TTY that wasn't
179
+ * there.
180
+ *
181
+ * stdout carries the JSON value and nothing else, so warnings go to stderr:
182
+ * a machine parser reading stdout cannot be corrupted by one. Only the
183
+ * *formatting* may depend on the mode — colour and indentation for a terminal,
184
+ * plain ASCII otherwise. Whether a warning is emitted at all may not.
185
+ *
186
+ * Anything a caller might branch on belongs in the JSON payload as well; stderr
187
+ * is for whoever reads the transcript afterwards.
188
+ */
189
+ export declare function warn(message: string, hint?: string): void;
137
190
  /** Print an error (+ optional hint) and exit non-zero. Never returns. */
138
191
  export declare function fail(message: string, hint?: string, code?: string): never;
139
192
  /**
@@ -46,5 +46,69 @@ export declare function timeAgo(value: string | Date | undefined, now: Date): st
46
46
  export declare function isManagedProject(project: DeployProjectRow | undefined, latest: DeploySourceRow | undefined): boolean;
47
47
  /** What a `deploy` with nothing attached will build, in the words to print. */
48
48
  export declare function planBareDeploy(project: DeployProjectRow | undefined, latest: DeploySourceRow | undefined, now: Date): BareDeployPlan;
49
+ /**
50
+ * A warning attached to a deploy: printed for the human, carried in the JSON.
51
+ *
52
+ * `code` is the stable half — the message is prose and will be reworded, so it
53
+ * is the code that CI or an agent branches on.
54
+ */
55
+ export interface DeployWarning {
56
+ code: string;
57
+ message: string;
58
+ hint?: string;
59
+ }
60
+ /** `code` of the warning below, and the field name it sets in the payload. */
61
+ export declare const EJECTS_MANAGED_RUNTIME = "ejects_managed_runtime";
62
+ /** The one sentence that says a source build undoes `runtimeMode: managed`. */
63
+ export declare function ejectWarning(projectRef: string): DeployWarning;
64
+ /** How a container-image deploy was asked for — the input to both rules below. */
65
+ export interface EjectContext {
66
+ /** The project currently runs on the managed runtime. */
67
+ managed: boolean;
68
+ /** `--source` was passed: build this directory. */
69
+ source: boolean;
70
+ /** `--force` was passed: eject on purpose. */
71
+ force: boolean;
72
+ }
73
+ /**
74
+ * Why a container-image deploy of a managed project is refused — or `undefined`
75
+ * to let it through.
76
+ *
77
+ * Every path below this point builds a container image, and a successful one
78
+ * sets `runtimeMode: "custom"` server-side. So the question is never "which flag
79
+ * was used" but "did the caller ask to leave the managed runtime", and only
80
+ * `--force` answers it.
81
+ *
82
+ * `--source` used to be read as answering it too, on the theory that uploading a
83
+ * build context is self-evidently a deliberate eject. It is not: `--source`
84
+ * picks *which source* gets built — this directory, rather than the stale
85
+ * archive the control plane is holding — and the eject is a side effect of the
86
+ * answer. That is exactly how a live project got flipped to `custom` by someone
87
+ * whose actual intent was "deploy what I have here", and it is the same
88
+ * ignorance the bare form is refused for. Same ignorance, same refusal.
89
+ */
90
+ export declare function ejectRefusal(opts: EjectContext, projectRef: string): {
91
+ message: string;
92
+ hint: string;
93
+ code: string;
94
+ } | undefined;
95
+ /**
96
+ * Which warnings a container-image deploy has earned.
97
+ *
98
+ * Pure, and separate from the printing, because the printing is what went
99
+ * wrong: the eject warning used to be written inline behind `!isJsonMode()`, so
100
+ * the fact that a deploy ejects a managed project existed only as a side effect
101
+ * of a TTY being attached. Deciding here, emitting once at the call site, means
102
+ * the decision cannot be output-mode-dependent again.
103
+ *
104
+ * The condition is just `managed`: anything reaching this point is a container
105
+ * image build that `ejectRefusal` has already let through, and on a managed
106
+ * project that is an eject however it was spelled. A caller who passed `--force`
107
+ * knows — the warning is for the transcript and the payload, which is what
108
+ * anyone reviewing the deploy afterwards actually reads.
109
+ */
110
+ export declare function deployWarnings(opts: EjectContext, projectRef: string): DeployWarning[];
111
+ /** The warning half of a deploy's JSON payload — merged into whatever it emits. */
112
+ export declare function warningPayload(warnings: DeployWarning[]): Record<string, unknown>;
49
113
  export declare function deployCommand(rawArgs: string[], projectRef: string): Promise<void>;
50
114
  export declare function logsCommand(rawArgs: string[], projectRef: string): Promise<void>;
@@ -1 +1,24 @@
1
+ /**
2
+ * Positional tokens after `rebase cloud` (group, action, …).
3
+ *
4
+ * Two things stop a flag being mistaken for the group. `GLOBAL_CLOUD_FLAGS` is
5
+ * declared so `arg` *consumes* the flags that may precede it — critically
6
+ * together with their values, which is the half that filtering cannot do. The
7
+ * leading-`-` skip then covers a flag nobody declared, so an unrecognised
8
+ * boolean shifts nothing.
9
+ *
10
+ * Only leading tokens are skipped: past the group and action, an undeclared
11
+ * flag and its value are somebody else's positionals and none of our business.
12
+ * A flag this file has never heard of, that takes a value, placed before the
13
+ * group, is the one shape still unresolvable here — there is no way to know
14
+ * whether the token after it is its value or the group, and guessing either way
15
+ * is worse than the handler reporting an unknown group.
16
+ *
17
+ * Exported so its tests can drive the real thing. The dispatch test used to
18
+ * re-implement it locally as `slice(3).filter(a => !a.startsWith("-"))` — which
19
+ * filtered flags, while this function did not — so the test asserted the
20
+ * behaviour we wanted against a copy that had it, and stayed green for as long
21
+ * as the real dispatcher was broken.
22
+ */
23
+ export declare function positionals(rawArgs: string[]): string[];
1
24
  export declare function cloudCommand(subcommand: string | undefined, rawArgs: string[]): Promise<void>;
@@ -1 +1,17 @@
1
+ /** Well-known filename the backend writes its actual port to. */
2
+ export declare const DEV_PORT_FILENAME = ".rebase-dev-port";
3
+ /**
4
+ * Compute a deterministic port from the project root path.
5
+ * Range: 3001–3999 (avoids privileged ports and common services).
6
+ * Two different project directories will almost always get different ports.
7
+ */
8
+ export declare function getProjectPort(projectRoot: string): number;
9
+ /**
10
+ * Resolve the best starting port for this project:
11
+ * 1. Explicit --port flag (highest priority)
12
+ * 2. PORT env var
13
+ * 3. Previously used port from .rebase-dev-port (port affinity across restarts)
14
+ * 4. Deterministic hash from project path (unique per project)
15
+ */
16
+ export declare function resolveStartPort(projectRoot: string, explicitPort?: number): number;
1
17
  export declare function devCommand(rawArgs: string[]): Promise<void>;
package/dist/index.es.js CHANGED
@@ -603,6 +603,39 @@ function removeLink(cwd = process.cwd()) {
603
603
  }
604
604
  return false;
605
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
+ };
606
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;
607
640
  /**
608
641
  * The raw project reference to operate on: explicit `--project` flag wins,
@@ -708,6 +741,35 @@ function emit(human, json) {
708
741
  if (JSON_MODE) printJson(json);
709
742
  else human();
710
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
+ }
711
773
  /** Print an error (+ optional hint) and exit non-zero. Never returns. */
712
774
  function fail(message, hint, code) {
713
775
  if (JSON_MODE) {
@@ -1569,6 +1631,8 @@ async function configureEnvFile(targetDirectory, databaseUrl) {
1569
1631
  });
1570
1632
  envContent = envContent.replace(/^JWT_SECRET=.*$/m, `JWT_SECRET=${jwtSecret}`);
1571
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}`);
1572
1636
  const runtimeVersion = readCliVersion();
1573
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`;
1574
1638
  if (databaseUrl) {
@@ -6238,12 +6302,24 @@ function resolveFrameworkVersion(sourceDir) {
6238
6302
  dir = parent;
6239
6303
  }
6240
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
+ }
6241
6317
  /** Upload a build-context tarball; returns the opaque `source` ref for deploy. */
6242
6318
  async function uploadSource(url, token, projectId, tarPath) {
6243
6319
  const bytes = fs.readFileSync(tarPath);
6244
6320
  const sizeMb = (bytes.length / 1024 / 1024).toFixed(1);
6245
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.");
6246
- console.log(chalk.gray(` Uploading source (${sizeMb} MB)...`));
6322
+ progress(chalk.gray(` Uploading source (${sizeMb} MB)...`));
6247
6323
  const res = await fetch(`${url}/api/functions/deploy/upload?projectId=${encodeURIComponent(projectId)}`, {
6248
6324
  method: "POST",
6249
6325
  headers: {
@@ -6275,7 +6351,7 @@ async function deployBundle(opts) {
6275
6351
  const loaded = loadManifest(projectRoot);
6276
6352
  const backend = findBackendApp(loaded.manifest);
6277
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.");
6278
- console.log(chalk.gray(" Building bundle..."));
6354
+ progress(chalk.gray(" Building bundle..."));
6279
6355
  bundleDir = (await buildBundle({
6280
6356
  projectRoot,
6281
6357
  appName: backend.name,
@@ -6283,16 +6359,16 @@ async function deployBundle(opts) {
6283
6359
  runtimeRange: loaded.manifest.rebase,
6284
6360
  storage: loaded.manifest.storage,
6285
6361
  skipTypeCheck: opts.skipTypeCheck,
6286
- log: (m) => console.log(chalk.gray(m))
6362
+ log: (m) => progress(chalk.gray(m))
6287
6363
  })).outDir;
6288
6364
  try {
6289
6365
  const folded = await foldFrontendIntoBundle({
6290
6366
  projectRoot,
6291
6367
  manifest: loaded.manifest,
6292
6368
  bundleDir,
6293
- log: (m) => console.log(m)
6369
+ log: (m) => progress(m)
6294
6370
  });
6295
- for (const outcome of folded) console.log(chalk.gray(` folded ${outcome.appName} in (${outcome.fileCount} file(s), served at ${outcome.path})`));
6371
+ for (const outcome of folded) progress(chalk.gray(` folded ${outcome.appName} in (${outcome.fileCount} file(s), served at ${outcome.path})`));
6296
6372
  } catch (err) {
6297
6373
  fail(err instanceof Error ? err.message : String(err), "Fix the frontend build, or pass --no-static to deploy the API alone.");
6298
6374
  }
@@ -6309,7 +6385,7 @@ async function deployBundle(opts) {
6309
6385
  try {
6310
6386
  await packBundle(bundleDir, tarPath);
6311
6387
  const sizeMb = (fs.statSync(tarPath).size / 1024 / 1024).toFixed(1);
6312
- console.log(chalk.gray(` Uploading bundle (${sizeMb} MB)...`));
6388
+ progress(chalk.gray(` Uploading bundle (${sizeMb} MB)...`));
6313
6389
  bundleId = await uploadBundle(url, token, projectId, tarPath);
6314
6390
  } catch (e) {
6315
6391
  fail(e instanceof Error ? e.message : String(e));
@@ -6317,8 +6393,10 @@ async function deployBundle(opts) {
6317
6393
  } finally {
6318
6394
  fs.rmSync(tarPath, { force: true });
6319
6395
  }
6320
- console.log("");
6321
- console.log(` 🚀 Triggering managed deployment for ${chalk.bold(projectRef)} (schema ${manifest.schemaVersion})...`);
6396
+ if (!isJsonMode()) {
6397
+ console.log("");
6398
+ console.log(` 🚀 Triggering managed deployment for ${chalk.bold(projectRef)} (schema ${manifest.schemaVersion})...`);
6399
+ }
6322
6400
  let declaredApps = [];
6323
6401
  try {
6324
6402
  declaredApps = declaredAppsFrom(loadManifest(process.cwd()).manifest);
@@ -6406,9 +6484,75 @@ function planBareDeploy(project, latest, now) {
6406
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."]
6407
6485
  };
6408
6486
  }
6487
+ /** `code` of the warning below, and the field name it sets in the payload. */
6488
+ var EJECTS_MANAGED_RUNTIME = "ejects_managed_runtime";
6409
6489
  /** The one sentence that says a source build undoes `runtimeMode: managed`. */
6410
6490
  function ejectWarning(projectRef) {
6411
- return `⚠ ${projectRef} runs on the managed runtime — a source build ejects it to a custom container.`;
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
+ };
6412
6556
  }
6413
6557
  /**
6414
6558
  * Read the two rows the preflight needs.
@@ -6479,17 +6623,18 @@ async function deployCommand(rawArgs, projectRef) {
6479
6623
  }
6480
6624
  const { project, latest } = await readDeployContext(client, projectId);
6481
6625
  const plan = planBareDeploy(project, latest, /* @__PURE__ */ new Date());
6482
- if (!args["--source"]) {
6483
- 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");
6484
- if (!isJsonMode()) {
6485
- console.log("");
6486
- if (plan.managed) console.log(chalk.yellow(` ${ejectWarning(projectRef)}`));
6487
- for (const line of plan.lines) console.log(chalk.gray(` ${line}`));
6488
- }
6489
- } else if (plan.managed && !isJsonMode()) {
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()) {
6490
6636
  console.log("");
6491
- console.log(chalk.yellow(` ${ejectWarning(projectRef)}`));
6492
- 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}`));
6493
6638
  }
6494
6639
  let source;
6495
6640
  if (args["--source"]) {
@@ -6502,8 +6647,10 @@ async function deployCommand(rawArgs, projectRef) {
6502
6647
  fs.rmSync(tarPath, { force: true });
6503
6648
  }
6504
6649
  }
6505
- console.log("");
6506
- console.log(` 🚀 Triggering deployment for project ${chalk.bold(projectRef)}${source ? " from uploaded source" : ""}...`);
6650
+ if (!isJsonMode()) {
6651
+ console.log("");
6652
+ console.log(` 🚀 Triggering deployment for project ${chalk.bold(projectRef)}${source ? " from uploaded source" : ""}...`);
6653
+ }
6507
6654
  const body = { projectId };
6508
6655
  if (source) body.source = source;
6509
6656
  if (args["--message"]) body.message = args["--message"];
@@ -6531,7 +6678,8 @@ async function deployCommand(rawArgs, projectRef) {
6531
6678
  deploymentId,
6532
6679
  deduplicated,
6533
6680
  frameworkVersion: frameworkVersion ?? null,
6534
- following: false
6681
+ following: false,
6682
+ ...warningPayload(warnings)
6535
6683
  });
6536
6684
  return;
6537
6685
  }
@@ -6545,7 +6693,8 @@ async function deployCommand(rawArgs, projectRef) {
6545
6693
  deduplicated,
6546
6694
  frameworkVersion: frameworkVersion ?? null,
6547
6695
  following: true,
6548
- status
6696
+ status,
6697
+ ...warningPayload(warnings)
6549
6698
  });
6550
6699
  }
6551
6700
  /**
@@ -9159,6 +9308,7 @@ function describeDatabaseState(db) {
9159
9308
  * So both are printed, rather than leaving anyone to infer one from a Docker tag.
9160
9309
  */
9161
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)")}`;
9162
9312
  if (project.runtimeMode !== "managed") return `custom ${chalk.gray("· your own image")}`;
9163
9313
  const version = project.runtimeVersion ?? "unknown";
9164
9314
  const framework = project.runtimeFrameworkVersion;
@@ -9557,17 +9707,41 @@ async function billingCommand(rawArgs) {
9557
9707
  * dispatched from here. Individual groups live in sibling modules; this file
9558
9708
  * only routes and prints help.
9559
9709
  */
9560
- /** Positional tokens after `rebase cloud` (group, action, …). */
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
+ */
9561
9732
  function positionals(rawArgs) {
9562
- return arg({}, {
9733
+ const rest = arg(GLOBAL_CLOUD_FLAGS, {
9563
9734
  argv: rawArgs.slice(3),
9564
9735
  permissive: true
9565
9736
  })._;
9737
+ let i = 0;
9738
+ while (i < rest.length && rest[i].startsWith("-")) i++;
9739
+ return rest.slice(i);
9566
9740
  }
9567
9741
  async function cloudCommand(subcommand, rawArgs) {
9568
9742
  initOutputMode(rawArgs);
9569
9743
  const pos = positionals(rawArgs);
9570
- const group = subcommand && subcommand !== "--help" ? subcommand : pos[0];
9744
+ const group = pos[0] ?? (subcommand !== "--help" ? subcommand : void 0);
9571
9745
  const action = pos[1];
9572
9746
  if (!group || subcommand === "--help") {
9573
9747
  printCloudHelp();
@@ -9989,8 +10163,9 @@ async function entry(args) {
9989
10163
  console.log(getVersion());
9990
10164
  return;
9991
10165
  }
9992
- const command = parsedArgs._[0];
9993
- const subcommand = parsedArgs._[1];
10166
+ const words = parsedArgs._.filter((a) => !a.startsWith("-"));
10167
+ const command = words[0];
10168
+ const subcommand = words[1];
9994
10169
  if (!command || parsedArgs["--help"] && ![
9995
10170
  "init",
9996
10171
  "schema",
@@ -10142,6 +10317,6 @@ ${chalk.gray("Documentation: https://rebase.pro/docs")}
10142
10317
  `);
10143
10318
  }
10144
10319
  //#endregion
10145
- 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, detectFrameworkDepDrift, 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, resolveCliVersion, 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 };
10146
10321
 
10147
10322
  //# sourceMappingURL=index.es.js.map