@batadata/cli 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/powdb.js +207 -3
- package/dist/commands/projects.d.ts +5 -0
- package/dist/commands/projects.js +141 -20
- package/package.json +1 -1
package/dist/commands/powdb.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { api, apiError } from "../api.js";
|
|
4
4
|
import { requireToken, isJsonMode, loadConfig } from "../config.js";
|
|
@@ -652,6 +652,191 @@ function renderPullSummary(o) {
|
|
|
652
652
|
log(` ${colors.dim("Load it:")} ${colors.cyan(o.loadCommand)}`);
|
|
653
653
|
log();
|
|
654
654
|
}
|
|
655
|
+
/** `--project <id>` / `--project=<id>` only (the positional arg is the query). */
|
|
656
|
+
function powdbProjectFlag(args) {
|
|
657
|
+
for (let i = 0; i < args.length; i++) {
|
|
658
|
+
if (args[i] === "--project" && args[i + 1])
|
|
659
|
+
return args[i + 1];
|
|
660
|
+
if (args[i].startsWith("--project="))
|
|
661
|
+
return args[i].slice("--project=".length);
|
|
662
|
+
}
|
|
663
|
+
return undefined;
|
|
664
|
+
}
|
|
665
|
+
function resolvePowdbProject(args) {
|
|
666
|
+
const explicit = powdbProjectFlag(args);
|
|
667
|
+
if (explicit)
|
|
668
|
+
return explicit;
|
|
669
|
+
const { projectId } = resolveProjectId(undefined);
|
|
670
|
+
if (!projectId) {
|
|
671
|
+
emitError("MISSING_ARG", "No project specified.", "Pass --project <id>, run `bata link <project>`, or set a default project.");
|
|
672
|
+
}
|
|
673
|
+
return projectId;
|
|
674
|
+
}
|
|
675
|
+
async function powdbQuery(args) {
|
|
676
|
+
const token = requireToken();
|
|
677
|
+
const jsonMode = isJsonMode();
|
|
678
|
+
const query = args.find((a) => !a.startsWith("-") && a !== powdbProjectFlag(args));
|
|
679
|
+
if (!query) {
|
|
680
|
+
emitError("MISSING_ARG", "Usage: bata powdb query \"<PowQL>\" [--project <id>]", "");
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
const projectId = resolvePowdbProject(args);
|
|
684
|
+
const res = await api.post(`/v1/powdb/${encodeURIComponent(projectId)}/query`, { query }, token);
|
|
685
|
+
if (!res.ok) {
|
|
686
|
+
const code = res.status === 503 ? "COMPUTE_STARTING"
|
|
687
|
+
: res.status === 401 || res.status === 403 ? "INVALID_KEY"
|
|
688
|
+
: res.status === 404 ? "NOT_FOUND"
|
|
689
|
+
: res.status >= 500 || res.status === 0 ? "API_UNAVAILABLE"
|
|
690
|
+
: "CLI_ERROR";
|
|
691
|
+
emitError(code, apiError(res, "PowQL query failed"), code === "COMPUTE_STARTING" ? "The database is starting — retry in a few seconds." : "");
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
const data = res.data;
|
|
695
|
+
if (jsonMode) {
|
|
696
|
+
json(data);
|
|
697
|
+
if ("error" in data && data.error)
|
|
698
|
+
process.exitCode = 1;
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if ("error" in data && data.error) {
|
|
702
|
+
// Query executed, PowQL rejected it — surface verbatim, non-zero exit.
|
|
703
|
+
emitError("QUERY_ERROR", data.error, "");
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
log();
|
|
707
|
+
if ("kind" in data) {
|
|
708
|
+
switch (data.kind) {
|
|
709
|
+
case "rows":
|
|
710
|
+
if (data.rows.length === 0)
|
|
711
|
+
log(` ${colors.dim("(0 rows)")}`);
|
|
712
|
+
else {
|
|
713
|
+
table(data.columns, data.rows);
|
|
714
|
+
log(` ${colors.dim(`(${data.rows.length} row${data.rows.length === 1 ? "" : "s"})`)}`);
|
|
715
|
+
}
|
|
716
|
+
break;
|
|
717
|
+
case "scalar":
|
|
718
|
+
log(` ${data.value}`);
|
|
719
|
+
break;
|
|
720
|
+
case "ok":
|
|
721
|
+
log(` ${colors.green(">")} ${data.affected} row${data.affected === 1 ? "" : "s"} affected`);
|
|
722
|
+
break;
|
|
723
|
+
case "message":
|
|
724
|
+
log(` ${data.message}`);
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
if (data.duration_ms !== undefined)
|
|
728
|
+
log(` ${colors.dim(`${data.duration_ms}ms`)}`);
|
|
729
|
+
}
|
|
730
|
+
log();
|
|
731
|
+
}
|
|
732
|
+
function truncateStatement(statement) {
|
|
733
|
+
const flat = statement.replace(/\s+/g, " ").trim();
|
|
734
|
+
return flat.length > 60 ? `${flat.slice(0, 57)}...` : flat;
|
|
735
|
+
}
|
|
736
|
+
async function powdbExec(args) {
|
|
737
|
+
const token = requireToken();
|
|
738
|
+
const jsonMode = isJsonMode();
|
|
739
|
+
const transactional = args.includes("--transactional");
|
|
740
|
+
const continueOnError = args.includes("--continue-on-error");
|
|
741
|
+
if (transactional && continueOnError) {
|
|
742
|
+
emitError("INVALID_ARG", "--transactional and --continue-on-error are mutually exclusive.", "Transactional scripts stop (and roll back) on the first failure by definition.");
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
let filePath;
|
|
746
|
+
for (let i = 0; i < args.length; i++) {
|
|
747
|
+
if (args[i] === "--file" && args[i + 1])
|
|
748
|
+
filePath = args[i + 1];
|
|
749
|
+
if (args[i].startsWith("--file="))
|
|
750
|
+
filePath = args[i].slice("--file=".length);
|
|
751
|
+
}
|
|
752
|
+
let script;
|
|
753
|
+
if (filePath) {
|
|
754
|
+
try {
|
|
755
|
+
// `--file -` reads stdin (fd 0) — the pipe-friendly agent path.
|
|
756
|
+
script = readFileSync(filePath === "-" ? 0 : filePath, "utf8");
|
|
757
|
+
}
|
|
758
|
+
catch (err) {
|
|
759
|
+
emitError("FILE_ERROR", `Could not read ${filePath}: ${err instanceof Error ? err.message : String(err)}`, "");
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
else {
|
|
764
|
+
script = args.find((a) => !a.startsWith("-") && a !== powdbProjectFlag(args) && a !== filePath);
|
|
765
|
+
}
|
|
766
|
+
if (!script || script.trim().length === 0) {
|
|
767
|
+
emitError("MISSING_ARG", 'Usage: bata powdb exec "<PowQL script>" | --file <path|-> [--transactional | --continue-on-error] [--project <id>]', "Statements are ;-separated (string/#-comment aware) and run pipelined on one connection.");
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const projectId = resolvePowdbProject(args);
|
|
771
|
+
const res = await api.post(`/v1/powdb/${encodeURIComponent(projectId)}/exec`, {
|
|
772
|
+
script,
|
|
773
|
+
...(transactional ? { transactional: true } : {}),
|
|
774
|
+
...(continueOnError ? { continue_on_error: true } : {}),
|
|
775
|
+
}, token);
|
|
776
|
+
if (!res.ok) {
|
|
777
|
+
const code = res.status === 503 ? "COMPUTE_STARTING"
|
|
778
|
+
: res.status === 401 || res.status === 403 ? "INVALID_KEY"
|
|
779
|
+
: res.status === 404 ? "NOT_FOUND"
|
|
780
|
+
: res.status >= 500 || res.status === 0 ? "API_UNAVAILABLE"
|
|
781
|
+
: "CLI_ERROR";
|
|
782
|
+
emitError(code, apiError(res, "PowQL script failed"), code === "COMPUTE_STARTING" ? "The database is starting — retry in a few seconds." : "");
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const data = res.data;
|
|
786
|
+
if (jsonMode) {
|
|
787
|
+
json(data);
|
|
788
|
+
if (("error" in data && data.error) || ("failed_count" in data && data.failed_count > 0)) {
|
|
789
|
+
process.exitCode = 1;
|
|
790
|
+
}
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if ("error" in data && data.error) {
|
|
794
|
+
const where = data.statement_index !== undefined
|
|
795
|
+
? ` (statement ${data.statement_index + 1}${data.rolled_back ? ", rolled back — nothing persisted" : ""})`
|
|
796
|
+
: "";
|
|
797
|
+
emitError("QUERY_ERROR", `${data.error}${where}`, data.statement ?? "");
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
log();
|
|
801
|
+
if ("outcomes" in data) {
|
|
802
|
+
for (const o of data.outcomes) {
|
|
803
|
+
if (o.ok)
|
|
804
|
+
log(` ${colors.green(">")} ok ${colors.dim(truncateStatement(o.statement))}`);
|
|
805
|
+
else
|
|
806
|
+
log(` ${colors.red("x")} ${o.error} ${colors.dim(truncateStatement(o.statement))}`);
|
|
807
|
+
}
|
|
808
|
+
log(` ${colors.dim(`${data.statement_count} statement${data.statement_count === 1 ? "" : "s"}, ${data.failed_count} failed, ${data.duration_ms}ms`)}`);
|
|
809
|
+
if (data.failed_count > 0)
|
|
810
|
+
process.exitCode = 1;
|
|
811
|
+
}
|
|
812
|
+
else if ("results" in data) {
|
|
813
|
+
log(` ${colors.green(">")} ${data.statement_count} statement${data.statement_count === 1 ? "" : "s"} executed${transactional ? " (transactional)" : ""} ${colors.dim(`${data.duration_ms}ms`)}`);
|
|
814
|
+
}
|
|
815
|
+
log();
|
|
816
|
+
}
|
|
817
|
+
async function powdbLifecycle(action, args) {
|
|
818
|
+
const token = requireToken();
|
|
819
|
+
const jsonMode = isJsonMode();
|
|
820
|
+
const positional = args.find((a) => !a.startsWith("-"));
|
|
821
|
+
const projectId = powdbProjectFlag(args) ?? positional ?? resolvePowdbProject(args);
|
|
822
|
+
const res = action === "status"
|
|
823
|
+
? await api.get(`/v1/powdb/${encodeURIComponent(projectId)}`, token)
|
|
824
|
+
: await api.post(`/v1/powdb/${encodeURIComponent(projectId)}/${action}`, {}, token);
|
|
825
|
+
if (!res.ok) {
|
|
826
|
+
emitError(res.status === 404 ? "NOT_FOUND"
|
|
827
|
+
: res.status === 401 || res.status === 403 ? "INVALID_KEY"
|
|
828
|
+
: res.status >= 500 || res.status === 0 ? "API_UNAVAILABLE"
|
|
829
|
+
: "CLI_ERROR", apiError(res, `PowDB ${action} failed`), "");
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
if (jsonMode) {
|
|
833
|
+
json(res.data);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
log();
|
|
837
|
+
log(` ${colors.green(">")} ${colors.cyan(projectId)} ${colors.dim("engine=powdb state=")}${res.data.state}`);
|
|
838
|
+
log();
|
|
839
|
+
}
|
|
655
840
|
// ─── Help + dispatch ──────────────────────────────────────────────────────────
|
|
656
841
|
function powdbHelp() {
|
|
657
842
|
log();
|
|
@@ -662,7 +847,16 @@ function powdbHelp() {
|
|
|
662
847
|
log(` ${colors.dim("BataDB branching / metering / server insights do NOT apply to it.")}`);
|
|
663
848
|
log();
|
|
664
849
|
log(` ${colors.dim("Commands:")}`);
|
|
665
|
-
log(` ${colors.cyan("pull")} Pull a branch's schema + data into a local PowDB-loadable PowQL script`);
|
|
850
|
+
log(` ${colors.cyan("pull")} Pull a Postgres branch's schema + data into a local PowDB-loadable PowQL script`);
|
|
851
|
+
log();
|
|
852
|
+
log(` ${colors.dim("Hosted PowDB projects (create one with: bata projects create --engine powdb):")}`);
|
|
853
|
+
log(` ${colors.cyan("query")} Run a PowQL statement: bata powdb query "Note" [--project <id>]`);
|
|
854
|
+
log(` ${colors.cyan("exec")} Run a multi-statement script, pipelined (bulk load / seed / migrate):`);
|
|
855
|
+
log(` ${colors.dim(' bata powdb exec --file seed.powql [--transactional | --continue-on-error]')}`);
|
|
856
|
+
log(` ${colors.dim(' bata powdb pull --out - | bata powdb exec --file - (pipe-friendly)')}`);
|
|
857
|
+
log(` ${colors.cyan("status")} Show the server state (running | parked) [--project <id>]`);
|
|
858
|
+
log(` ${colors.cyan("park")} Scale the server to zero (WAL is durable) [--project <id>]`);
|
|
859
|
+
log(` ${colors.cyan("wake")} Start a parked server (~tens of ms) [--project <id>]`);
|
|
666
860
|
log();
|
|
667
861
|
log(` ${colors.dim("Options (pull):")}`);
|
|
668
862
|
log(` ${colors.dim("--project <id> override the linked/default project")}`);
|
|
@@ -688,10 +882,20 @@ export async function handlePowdb(args) {
|
|
|
688
882
|
switch (sub) {
|
|
689
883
|
case "pull":
|
|
690
884
|
return powdbPull(args.slice(1));
|
|
885
|
+
case "query":
|
|
886
|
+
return powdbQuery(args.slice(1));
|
|
887
|
+
case "exec":
|
|
888
|
+
return powdbExec(args.slice(1));
|
|
889
|
+
case "status":
|
|
890
|
+
return powdbLifecycle("status", args.slice(1));
|
|
891
|
+
case "park":
|
|
892
|
+
return powdbLifecycle("park", args.slice(1));
|
|
893
|
+
case "wake":
|
|
894
|
+
return powdbLifecycle("wake", args.slice(1));
|
|
691
895
|
case undefined:
|
|
692
896
|
powdbHelp();
|
|
693
897
|
return;
|
|
694
898
|
default:
|
|
695
|
-
emitError("INVALID_FLAG", `Unknown subcommand: powdb ${sub}`, "Available: pull. Run `bata powdb --help`.");
|
|
899
|
+
emitError("INVALID_FLAG", `Unknown subcommand: powdb ${sub}`, "Available: pull, query, exec, status, park, wake. Run `bata powdb --help`.");
|
|
696
900
|
}
|
|
697
901
|
}
|
|
@@ -23,6 +23,11 @@ export declare function parseCreateComputeArgs(args: string[]): {
|
|
|
23
23
|
} | {
|
|
24
24
|
error: string;
|
|
25
25
|
};
|
|
26
|
+
/** Parse `--engine postgres|powdb` (default: postgres). PowDB is a different
|
|
27
|
+
* ENGINE, not a different Postgres — the compute tier/size flags don't apply. */
|
|
28
|
+
export declare function parseEngineArg(args: string[]): "postgres" | "powdb" | {
|
|
29
|
+
error: string;
|
|
30
|
+
};
|
|
26
31
|
export declare function create(args?: string[]): Promise<void>;
|
|
27
32
|
export declare function info(projectId?: string): Promise<void>;
|
|
28
33
|
export declare function deleteProject(projectId?: string): Promise<void>;
|
|
@@ -160,29 +160,122 @@ export function parseCreateComputeArgs(args) {
|
|
|
160
160
|
}
|
|
161
161
|
return { tier, sizeCu };
|
|
162
162
|
}
|
|
163
|
+
/** `--flag <value>` / `--flag=<value>` reader. Distinguishes "absent"
|
|
164
|
+
* (undefined) from "present but missing its value" ({ error }) so a headless
|
|
165
|
+
* caller's typo fails loudly instead of silently changing behavior. */
|
|
166
|
+
function readFlagValue(args, flag) {
|
|
167
|
+
for (let i = 0; i < args.length; i++) {
|
|
168
|
+
if (args[i] === flag) {
|
|
169
|
+
const next = args[i + 1];
|
|
170
|
+
if (next === undefined || next.startsWith("-")) {
|
|
171
|
+
return { error: `${flag} requires a value.` };
|
|
172
|
+
}
|
|
173
|
+
return next;
|
|
174
|
+
}
|
|
175
|
+
if (args[i].startsWith(`${flag}=`)) {
|
|
176
|
+
const v = args[i].slice(flag.length + 1);
|
|
177
|
+
if (!v)
|
|
178
|
+
return { error: `${flag} requires a value.` };
|
|
179
|
+
return v;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
/** Parse `--engine postgres|powdb` (default: postgres). PowDB is a different
|
|
185
|
+
* ENGINE, not a different Postgres — the compute tier/size flags don't apply. */
|
|
186
|
+
export function parseEngineArg(args) {
|
|
187
|
+
let raw;
|
|
188
|
+
for (let i = 0; i < args.length; i++) {
|
|
189
|
+
if (args[i] === "--engine") {
|
|
190
|
+
const next = args[i + 1];
|
|
191
|
+
if (next === undefined || next.startsWith("-")) {
|
|
192
|
+
return { error: "--engine requires a value (postgres|powdb)." };
|
|
193
|
+
}
|
|
194
|
+
raw = next;
|
|
195
|
+
i++;
|
|
196
|
+
}
|
|
197
|
+
else if (args[i].startsWith("--engine="))
|
|
198
|
+
raw = args[i].slice("--engine=".length);
|
|
199
|
+
}
|
|
200
|
+
if (raw === undefined)
|
|
201
|
+
return "postgres";
|
|
202
|
+
const normalized = raw.toLowerCase();
|
|
203
|
+
if (normalized === "postgres" || normalized === "powdb")
|
|
204
|
+
return normalized;
|
|
205
|
+
return { error: `Invalid --engine "${raw}". Use "postgres" (default) or "powdb".` };
|
|
206
|
+
}
|
|
163
207
|
export async function create(args = []) {
|
|
164
208
|
const token = requireToken();
|
|
165
209
|
const config = loadConfig();
|
|
210
|
+
const engine = parseEngineArg(args);
|
|
211
|
+
if (typeof engine !== "string") {
|
|
212
|
+
emitError("INVALID_FLAG", engine.error, "");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (engine === "powdb" && args.some((a) => a === "--tier" || a === "--size" || a.startsWith("--tier=") || a.startsWith("--size="))) {
|
|
216
|
+
emitError("INVALID_FLAG", "--tier/--size do not apply to --engine powdb (PowDB has one serverless shape in this phase).", "");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
166
219
|
const compute = parseCreateComputeArgs(args);
|
|
167
220
|
if ("error" in compute) {
|
|
168
221
|
emitError("INVALID_FLAG", compute.error, "");
|
|
169
222
|
return;
|
|
170
223
|
}
|
|
171
|
-
|
|
172
|
-
|
|
224
|
+
const jsonMode = isJsonMode();
|
|
225
|
+
if (!jsonMode)
|
|
226
|
+
heading(engine === "powdb" ? "Create a new PowDB project" : "Create a new project");
|
|
227
|
+
// Agent-native path: --name (+ optional --region) skips the prompts entirely.
|
|
228
|
+
// Piped stdin cannot drive TWO sequential prompts — each prompt() opens a
|
|
229
|
+
// fresh readline that swallows the remaining buffered input, so the second
|
|
230
|
+
// question never resolves and Node exits silently on the unsettled await.
|
|
231
|
+
// Flags are the contract for headless use; prompts are for humans on a TTY.
|
|
232
|
+
const nameFlag = readFlagValue(args, "--name");
|
|
233
|
+
if (typeof nameFlag === "object") {
|
|
234
|
+
emitError("INVALID_FLAG", nameFlag.error, "");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const regionFlag = readFlagValue(args, "--region");
|
|
238
|
+
if (typeof regionFlag === "object") {
|
|
239
|
+
emitError("INVALID_FLAG", regionFlag.error, "");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (jsonMode && nameFlag === undefined) {
|
|
243
|
+
// JSON mode is the machine contract — prompts would pollute stdout and a
|
|
244
|
+
// headless caller can't answer them anyway.
|
|
245
|
+
emitError("MISSING_ARG", "--json requires --name <name>.", "");
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const name = nameFlag ?? (await prompt("Project name"));
|
|
173
249
|
if (!name) {
|
|
174
|
-
emitError("MISSING_ARG", "Project name is required.", "");
|
|
250
|
+
emitError("MISSING_ARG", "Project name is required.", "Pass --name <name> for headless use.");
|
|
251
|
+
}
|
|
252
|
+
let region;
|
|
253
|
+
if (regionFlag !== undefined) {
|
|
254
|
+
if (!REGIONS.some((r) => r.value === regionFlag)) {
|
|
255
|
+
emitError("INVALID_FLAG", `Invalid --region "${regionFlag}". Use one of: ${REGIONS.map((r) => r.value).join(", ")}.`, "");
|
|
256
|
+
}
|
|
257
|
+
region = regionFlag;
|
|
258
|
+
}
|
|
259
|
+
else if (nameFlag !== undefined) {
|
|
260
|
+
// --name without --region: default to the first region rather than opening
|
|
261
|
+
// a prompt a headless caller can't answer.
|
|
262
|
+
region = REGIONS[0].value;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
region = await select("Select a region", REGIONS);
|
|
175
266
|
}
|
|
176
|
-
const region = await select("Select a region", REGIONS);
|
|
177
267
|
const s = spinner("Creating project");
|
|
178
268
|
const teamId = await resolveTeamId(token);
|
|
179
|
-
const body =
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
269
|
+
const body = engine === "powdb"
|
|
270
|
+
? // PowDB: no compute picker — the server rejects pg-only options.
|
|
271
|
+
{ name, region, engine: "powdb" }
|
|
272
|
+
: {
|
|
273
|
+
name,
|
|
274
|
+
region,
|
|
275
|
+
// One friendly step: the compute is born on the chosen tier + size (no
|
|
276
|
+
// follow-up `bata compute set` needed). Defaults to serverless / 1 CU.
|
|
277
|
+
compute: { tier: compute.tier, size_cu: compute.sizeCu },
|
|
278
|
+
};
|
|
186
279
|
if (teamId) {
|
|
187
280
|
body.team_id = teamId;
|
|
188
281
|
}
|
|
@@ -197,19 +290,47 @@ export async function create(args = []) {
|
|
|
197
290
|
const project = res.data;
|
|
198
291
|
// Set as default project
|
|
199
292
|
saveConfig({ defaultProject: project.id });
|
|
293
|
+
if (jsonMode) {
|
|
294
|
+
json({
|
|
295
|
+
id: project.id,
|
|
296
|
+
name: project.name,
|
|
297
|
+
region: project.region,
|
|
298
|
+
engine,
|
|
299
|
+
status: project.status,
|
|
300
|
+
...(engine === "powdb"
|
|
301
|
+
? { pricing: "not metered yet" }
|
|
302
|
+
: { compute: { tier: compute.tier, size_cu: compute.sizeCu } }),
|
|
303
|
+
default_project_saved: true,
|
|
304
|
+
});
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
200
307
|
const tierLabel = compute.tier === "always_on" ? "Always-on (dedicated)" : "Serverless";
|
|
201
308
|
log();
|
|
202
309
|
success(`Project ${colors.cyan(project.name)} created`);
|
|
203
310
|
log();
|
|
204
|
-
|
|
205
|
-
[
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
311
|
+
if (engine === "powdb") {
|
|
312
|
+
kvList([
|
|
313
|
+
["ID", colors.dim(project.id)],
|
|
314
|
+
["Region", project.region],
|
|
315
|
+
["Engine", "PowDB (serverless — parks to zero when idle)"],
|
|
316
|
+
// Honest: PowDB compute isn't in the usage meter yet — say so, never $0.
|
|
317
|
+
["Pricing", colors.dim("not metered yet")],
|
|
318
|
+
["Status", statusBadge(project.status)],
|
|
319
|
+
]);
|
|
320
|
+
log();
|
|
321
|
+
log(` ${colors.dim("Set as default project. Run")} ${colors.cyan('bata powdb query "<PowQL>"')} ${colors.dim("to query it.")}`);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
kvList([
|
|
325
|
+
["ID", colors.dim(project.id)],
|
|
326
|
+
["Region", project.region],
|
|
327
|
+
["Compute", `${tierLabel} · ${compute.sizeCu} CU`],
|
|
328
|
+
["Pricing", colors.dim(computePriceSummary(compute.tier, compute.sizeCu))],
|
|
329
|
+
["Status", statusBadge(project.status)],
|
|
330
|
+
]);
|
|
331
|
+
log();
|
|
332
|
+
log(` ${colors.dim("Set as default project. Run")} ${colors.cyan("bata db connect")} ${colors.dim("to connect.")}`);
|
|
333
|
+
}
|
|
213
334
|
log();
|
|
214
335
|
}
|
|
215
336
|
export async function info(projectId) {
|