@batadata/cli 0.2.6 → 0.2.7
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/db.d.ts +5 -5
- package/dist/commands/db.js +72 -42
- package/package.json +1 -1
package/dist/commands/db.d.ts
CHANGED
|
@@ -18,18 +18,18 @@ export declare function parseBranchCreateArgs(args: string[]): {
|
|
|
18
18
|
expiresIn?: string;
|
|
19
19
|
purpose?: string;
|
|
20
20
|
};
|
|
21
|
-
export declare function connect(): Promise<void>;
|
|
21
|
+
export declare function connect(args?: string[]): Promise<void>;
|
|
22
22
|
export declare function url(args?: string[]): Promise<void>;
|
|
23
|
-
export declare function branches(): Promise<void>;
|
|
23
|
+
export declare function branches(args?: string[]): Promise<void>;
|
|
24
24
|
export declare function branchCreate(args?: string[]): Promise<void>;
|
|
25
|
-
export declare function branchDelete(
|
|
25
|
+
export declare function branchDelete(args?: string[]): Promise<void>;
|
|
26
26
|
/**
|
|
27
27
|
* `bata db branch protect <name-or-id>` / `unprotect <name-or-id>` — flip a
|
|
28
28
|
* branch's protection flag (PATCH /v1/branches/:id). A protected branch refuses
|
|
29
29
|
* delete/reset/rollback/reap — the safety rail for headless/agent ops. The ref
|
|
30
30
|
* is resolved by name OR id, like the other branch commands.
|
|
31
31
|
*/
|
|
32
|
-
export declare function branchSetProtected(
|
|
32
|
+
export declare function branchSetProtected(args: string[], wantProtected: boolean): Promise<void>;
|
|
33
33
|
/**
|
|
34
34
|
* `bata db branch checkout <name-or-id>` — pin a branch into the directory's
|
|
35
35
|
* `.batadata/project.json` so later `db query` / `db url` target it with no
|
|
@@ -37,7 +37,7 @@ export declare function branchSetProtected(ref: string | undefined, wantProtecte
|
|
|
37
37
|
* branch ref is resolved by name OR id, just like `db query --branch`.
|
|
38
38
|
*/
|
|
39
39
|
export declare function branchCheckout(args: string[]): Promise<void>;
|
|
40
|
-
export declare function studio(): Promise<void>;
|
|
40
|
+
export declare function studio(args?: string[]): Promise<void>;
|
|
41
41
|
/**
|
|
42
42
|
* Pull `--branch <ref>` / `--at <ISO-timestamp|LSN>` flags (and their `=` forms)
|
|
43
43
|
* out of the query args and return them plus the remaining args, which join into
|
package/dist/commands/db.js
CHANGED
|
@@ -6,7 +6,7 @@ import { colors, log, json, error, success, spinner, table, heading, info as log
|
|
|
6
6
|
import { prompt, confirmDestructive } from "../utils/prompts.js";
|
|
7
7
|
import { openBrowser } from "../utils/open.js";
|
|
8
8
|
import { emitError, isRetryable } from "../utils/errors.js";
|
|
9
|
-
import { resolveProjectId,
|
|
9
|
+
import { resolveProjectId, readLinkFile, findLinkFile, writeLinkFile } from "../link.js";
|
|
10
10
|
// Share the exact LSN/timestamp discriminator `bata restore` uses so `db query
|
|
11
11
|
// --at` classifies a restore point identically.
|
|
12
12
|
import { classifyRestorePoint } from "./restore.js";
|
|
@@ -140,7 +140,7 @@ async function resolveBranchRef(projectId, token, teamId, ref) {
|
|
|
140
140
|
return null;
|
|
141
141
|
return result.branches.find((b) => b.id === ref || b.name === ref) ?? null;
|
|
142
142
|
}
|
|
143
|
-
export async function connect() {
|
|
143
|
+
export async function connect(args = []) {
|
|
144
144
|
// psql is an interactive session — there's no headless equivalent. Don't spawn
|
|
145
145
|
// it in --json or non-TTY contexts; point agents at the headless surfaces.
|
|
146
146
|
if (isJsonMode() || !process.stdin.isTTY) {
|
|
@@ -148,7 +148,8 @@ export async function connect() {
|
|
|
148
148
|
}
|
|
149
149
|
const token = requireToken();
|
|
150
150
|
const config = loadConfig();
|
|
151
|
-
const projectId =
|
|
151
|
+
const { projectId: projectFlag } = parseProjectFlag(args);
|
|
152
|
+
const projectId = resolveProjectId(projectFlag).projectId;
|
|
152
153
|
if (!projectId) {
|
|
153
154
|
emitError("NO_PROJECT", "No default project.", "Run bata projects create or set one with bata projects info <id>.");
|
|
154
155
|
}
|
|
@@ -215,11 +216,15 @@ export async function url(args = []) {
|
|
|
215
216
|
"\n");
|
|
216
217
|
process.stdout.write(conn.connection_uri + "\n");
|
|
217
218
|
}
|
|
218
|
-
export async function branches() {
|
|
219
|
+
export async function branches(args = []) {
|
|
219
220
|
const token = requireToken();
|
|
220
221
|
const config = loadConfig();
|
|
221
222
|
const jsonMode = isJsonMode();
|
|
222
|
-
|
|
223
|
+
// `db branches` used to take NO args, so an explicit `--project <id>` was
|
|
224
|
+
// SILENTLY IGNORED and the default/linked project answered — the wrong-target
|
|
225
|
+
// class the 0.1.6 projects fix closed for projects info/delete.
|
|
226
|
+
const { projectId: projectFlag } = parseProjectFlag(args);
|
|
227
|
+
const projectId = resolveProjectId(projectFlag).projectId;
|
|
223
228
|
if (!projectId) {
|
|
224
229
|
emitError("NO_PROJECT", "No default project set.", "Set one with: bata projects info <id>");
|
|
225
230
|
}
|
|
@@ -278,11 +283,16 @@ export async function branchCreate(args = []) {
|
|
|
278
283
|
const jsonMode = isJsonMode();
|
|
279
284
|
const token = requireToken();
|
|
280
285
|
const config = loadConfig();
|
|
281
|
-
|
|
286
|
+
// Strip --project BEFORE the positional parse. Pre-fix, `--project <id>` was
|
|
287
|
+
// both ignored (branch landed on the default project) AND its VALUE bled into
|
|
288
|
+
// the positional name when it led the args (`db branch create --project X dev`
|
|
289
|
+
// created a branch literally named "X" — on the wrong project).
|
|
290
|
+
const { projectId: projectFlag, rest } = parseProjectFlag(args);
|
|
291
|
+
const projectId = resolveProjectId(projectFlag).projectId;
|
|
282
292
|
if (!projectId) {
|
|
283
293
|
emitError("NO_PROJECT", "No default project set.", "Set one with: bata projects info <id>");
|
|
284
294
|
}
|
|
285
|
-
const { name, expiresIn, purpose } = parseBranchCreateArgs(
|
|
295
|
+
const { name, expiresIn, purpose } = parseBranchCreateArgs(rest);
|
|
286
296
|
// A relative TTL (--expires-in 2h) becomes an absolute expires_at the server
|
|
287
297
|
// enforces. Parse it up front so a bad duration fails fast, before any call.
|
|
288
298
|
let expiresAt;
|
|
@@ -330,9 +340,11 @@ export async function branchCreate(args = []) {
|
|
|
330
340
|
purpose: res.data.purpose ?? purpose ?? null,
|
|
331
341
|
},
|
|
332
342
|
// The branch row exists immediately, but its compute may still be
|
|
333
|
-
// provisioning — poll `db branches` for status before connecting.
|
|
343
|
+
// provisioning — poll `db branches` for status before connecting. The
|
|
344
|
+
// poll command carries the resolved project explicitly so following it
|
|
345
|
+
// verbatim can never land on a different linked/default project.
|
|
334
346
|
ready: false,
|
|
335
|
-
poll:
|
|
347
|
+
poll: `bata db branches --project ${projectId} --json`,
|
|
336
348
|
});
|
|
337
349
|
return;
|
|
338
350
|
}
|
|
@@ -341,29 +353,35 @@ export async function branchCreate(args = []) {
|
|
|
341
353
|
if (expiresAt) {
|
|
342
354
|
log(` ${colors.dim("Expires")} ${formatDate(expiresAt)} ${colors.dim("(auto-deleted)")}`);
|
|
343
355
|
}
|
|
344
|
-
log(` ${colors.dim("Poll readiness with")} ${colors.cyan(
|
|
356
|
+
log(` ${colors.dim("Poll readiness with")} ${colors.cyan(`bata db branches --project ${projectId}`)}`);
|
|
345
357
|
log();
|
|
346
358
|
}
|
|
347
|
-
export async function branchDelete(
|
|
359
|
+
export async function branchDelete(args = []) {
|
|
348
360
|
const jsonMode = isJsonMode();
|
|
349
361
|
const token = requireToken();
|
|
350
362
|
const config = loadConfig();
|
|
351
|
-
|
|
363
|
+
// --project used to be silently ignored here (the delete hit the default/
|
|
364
|
+
// linked project's branch list) — same wrong-target class as `db branches`.
|
|
365
|
+
const { projectId: projectFlag, rest } = parseProjectFlag(args);
|
|
366
|
+
const projectId = resolveProjectId(projectFlag).projectId;
|
|
367
|
+
const ref = rest[0];
|
|
352
368
|
if (!projectId) {
|
|
353
369
|
emitError("NO_PROJECT", "No default project set.", "Set one with: bata projects info <id>");
|
|
354
370
|
}
|
|
355
|
-
if (!
|
|
356
|
-
emitError("MISSING_ARG", "Branch name is required.", "Usage: bata db branch delete <name>");
|
|
371
|
+
if (!ref) {
|
|
372
|
+
emitError("MISSING_ARG", "Branch name or id is required.", "Usage: bata db branch delete <name-or-id>");
|
|
357
373
|
}
|
|
358
374
|
// Skip the prompt headlessly (--yes / --json / no TTY) so agents and CI can
|
|
359
375
|
// actually delete — and never report success without deleting.
|
|
360
|
-
const ok = await confirmDestructive(`Delete branch ${colors.cyan(
|
|
376
|
+
const ok = await confirmDestructive(`Delete branch ${colors.cyan(ref)}?`);
|
|
361
377
|
if (!ok) {
|
|
362
378
|
log(" Aborted.");
|
|
363
379
|
return;
|
|
364
380
|
}
|
|
365
|
-
const s = jsonMode ? null : spinner(`Deleting branch ${
|
|
366
|
-
//
|
|
381
|
+
const s = jsonMode ? null : spinner(`Deleting branch ${ref}`);
|
|
382
|
+
// Fetch the branch list explicitly (not via resolveBranchRef, which folds an
|
|
383
|
+
// API outage into null) so a 5xx stays API_UNAVAILABLE/exit 6 — an agent must
|
|
384
|
+
// never read a retryable outage as "branch doesn't exist".
|
|
367
385
|
const query = {};
|
|
368
386
|
if (config.defaultTeam)
|
|
369
387
|
query.team_id = config.defaultTeam;
|
|
@@ -372,10 +390,12 @@ export async function branchDelete(name) {
|
|
|
372
390
|
s?.stop();
|
|
373
391
|
emitError(projRes.status >= 500 || projRes.status === 0 ? "API_UNAVAILABLE" : "CLI_ERROR", apiError(projRes, "Failed to fetch branches."), "");
|
|
374
392
|
}
|
|
375
|
-
|
|
393
|
+
// Match by name OR id, like checkout/protect/query — delete used to match
|
|
394
|
+
// by name only, so a real branch id got "not found".
|
|
395
|
+
const branch = projRes.data.branches.find((b) => b.id === ref || b.name === ref);
|
|
376
396
|
if (!branch) {
|
|
377
397
|
s?.stop();
|
|
378
|
-
emitError("BRANCH_NOT_FOUND", `Branch "${
|
|
398
|
+
emitError("BRANCH_NOT_FOUND", `Branch "${ref}" not found in this project.`, "List branches with: bata db branches --json");
|
|
379
399
|
}
|
|
380
400
|
const res = await api.del(`/v1/branches/${branch.id}`, token, query);
|
|
381
401
|
s?.stop();
|
|
@@ -383,11 +403,11 @@ export async function branchDelete(name) {
|
|
|
383
403
|
emitError(res.status >= 500 || res.status === 0 ? "API_UNAVAILABLE" : "CLI_ERROR", apiError(res, "Failed to delete branch."), "");
|
|
384
404
|
}
|
|
385
405
|
if (jsonMode) {
|
|
386
|
-
json({ branch: { id: branch.id, name, project_id: projectId }, deleted: true });
|
|
406
|
+
json({ branch: { id: branch.id, name: branch.name, project_id: projectId }, deleted: true });
|
|
387
407
|
return;
|
|
388
408
|
}
|
|
389
409
|
log();
|
|
390
|
-
log(` ${colors.green(">")} Branch ${colors.cyan(name)} deleted`);
|
|
410
|
+
log(` ${colors.green(">")} Branch ${colors.cyan(branch.name)} deleted`);
|
|
391
411
|
log();
|
|
392
412
|
}
|
|
393
413
|
/**
|
|
@@ -396,11 +416,13 @@ export async function branchDelete(name) {
|
|
|
396
416
|
* delete/reset/rollback/reap — the safety rail for headless/agent ops. The ref
|
|
397
417
|
* is resolved by name OR id, like the other branch commands.
|
|
398
418
|
*/
|
|
399
|
-
export async function branchSetProtected(
|
|
419
|
+
export async function branchSetProtected(args, wantProtected) {
|
|
400
420
|
const jsonMode = isJsonMode();
|
|
401
421
|
const token = requireToken();
|
|
402
422
|
const config = loadConfig();
|
|
403
|
-
const projectId =
|
|
423
|
+
const { projectId: projectFlag, rest } = parseProjectFlag(args);
|
|
424
|
+
const projectId = resolveProjectId(projectFlag).projectId;
|
|
425
|
+
const ref = rest[0];
|
|
404
426
|
const verb = wantProtected ? "protect" : "unprotect";
|
|
405
427
|
if (!projectId) {
|
|
406
428
|
emitError("NO_PROJECT", "No default project set.", "Set one with: bata projects info <id>");
|
|
@@ -505,8 +527,9 @@ export async function branchCheckout(args) {
|
|
|
505
527
|
log(` ${colors.dim("Link file:")} ${colors.dim(linkFile)}`);
|
|
506
528
|
log();
|
|
507
529
|
}
|
|
508
|
-
export async function studio() {
|
|
509
|
-
const projectId =
|
|
530
|
+
export async function studio(args = []) {
|
|
531
|
+
const { projectId: projectFlag } = parseProjectFlag(args);
|
|
532
|
+
const projectId = resolveProjectId(projectFlag).projectId;
|
|
510
533
|
const studioUrl = projectId
|
|
511
534
|
? `https://bench-app-one.vercel.app/studio?project=${projectId}`
|
|
512
535
|
: "https://bench-app-one.vercel.app/studio";
|
|
@@ -652,9 +675,6 @@ async function runTimeTravelQuery(opts) {
|
|
|
652
675
|
export async function query(args = []) {
|
|
653
676
|
const jsonMode = isJsonMode();
|
|
654
677
|
const { branchId: branchFlag, at, projectId: projectFlag, rest } = parseQueryFlags(args);
|
|
655
|
-
// Branch precedence: an explicit --branch wins, else the branch pinned by
|
|
656
|
-
// `bata db branch checkout` in .batadata/project.json, else the primary.
|
|
657
|
-
const branchId = resolveBranchId(branchFlag).branchId;
|
|
658
678
|
const sql = rest.join(" ").trim();
|
|
659
679
|
if (!sql) {
|
|
660
680
|
emitError("MISSING_ARG", "SQL query is required.", 'Usage: bata db query "SELECT 1"');
|
|
@@ -663,6 +683,14 @@ export async function query(args = []) {
|
|
|
663
683
|
const config = loadConfig();
|
|
664
684
|
// Project precedence: --project flag > .batadata link > config default.
|
|
665
685
|
const projectId = resolveProjectId(projectFlag).projectId;
|
|
686
|
+
// Branch precedence: an explicit --branch wins, else the branch pinned by
|
|
687
|
+
// `bata db branch checkout` in .batadata/project.json — but ONLY when the
|
|
688
|
+
// query targets the linked project. A pinned branch from project A must never
|
|
689
|
+
// resolve against an explicit `--project B` (it misses → BRANCH_NOT_FOUND, or
|
|
690
|
+
// worse, a name collision silently targets the wrong branch).
|
|
691
|
+
const link = readLinkFile()?.link;
|
|
692
|
+
const pinnedBranch = link && link.projectId === projectId ? link.branchId ?? undefined : undefined;
|
|
693
|
+
const branchId = branchFlag ?? pinnedBranch;
|
|
666
694
|
if (!projectId) {
|
|
667
695
|
emitError("NO_PROJECT", "No project for db query.", "Pass --project <id>, or run `bata link <project>` to set a default.");
|
|
668
696
|
}
|
|
@@ -786,8 +814,9 @@ function dbHelp(sub) {
|
|
|
786
814
|
log(` ${colors.bold("bata db query")} — run a SQL query against a branch`);
|
|
787
815
|
log();
|
|
788
816
|
usage('bata db query "SELECT 1"');
|
|
789
|
-
usage("bata db query <sql> [--branch <id-or-name>] [--at <timestamp|LSN>] [--json]");
|
|
817
|
+
usage("bata db query <sql> [--project <id>] [--branch <id-or-name>] [--at <timestamp|LSN>] [--json]");
|
|
790
818
|
log();
|
|
819
|
+
note("--project <id> Target a specific project (default: linked/default project)");
|
|
791
820
|
note("--branch <id-or-name> Target a specific branch by id OR name (default: the project's primary)");
|
|
792
821
|
note("--at <timestamp|LSN> Time-travel: run the query AS OF a past point (ISO-8601");
|
|
793
822
|
note(" timestamp e.g. 2026-07-04T12:00:00Z, or an LSN e.g. 0/15994B0).");
|
|
@@ -799,7 +828,7 @@ function dbHelp(sub) {
|
|
|
799
828
|
case "branches":
|
|
800
829
|
log(` ${colors.bold("bata db branches")} — list branches and their compute status`);
|
|
801
830
|
log();
|
|
802
|
-
usage("bata db branches [--json]");
|
|
831
|
+
usage("bata db branches [--project <id>] [--json]");
|
|
803
832
|
log();
|
|
804
833
|
note("STATUS shows the live compute state; a ✓ means ready to query.");
|
|
805
834
|
note("--json includes computeStatus + ready — poll these after create/cold start.");
|
|
@@ -807,12 +836,13 @@ function dbHelp(sub) {
|
|
|
807
836
|
case "branch":
|
|
808
837
|
log(` ${colors.bold("bata db branch")} — create, delete, or check out a branch`);
|
|
809
838
|
log();
|
|
810
|
-
usage("bata db branch create <name> [--expires-in <2h|30m|7d>] [--purpose <text>]");
|
|
811
|
-
usage("bata db branch delete <name> [--yes]");
|
|
839
|
+
usage("bata db branch create <name> [--project <id>] [--expires-in <2h|30m|7d>] [--purpose <text>]");
|
|
840
|
+
usage("bata db branch delete <name-or-id> [--project <id>] [--yes]");
|
|
812
841
|
usage("bata db branch checkout <name-or-id> [--project <id>]");
|
|
813
|
-
usage("bata db branch protect <name-or-id>");
|
|
814
|
-
usage("bata db branch unprotect <name-or-id>");
|
|
842
|
+
usage("bata db branch protect <name-or-id> [--project <id>]");
|
|
843
|
+
usage("bata db branch unprotect <name-or-id> [--project <id>]");
|
|
815
844
|
log();
|
|
845
|
+
note("--project Target a specific project (default: linked/default project).");
|
|
816
846
|
note("--expires-in Auto-delete the branch after this long (units: s/m/h/d/w).");
|
|
817
847
|
note("--purpose Free-text note describing why the branch exists.");
|
|
818
848
|
note("checkout pins the branch into .batadata/project.json so db query/url");
|
|
@@ -823,12 +853,12 @@ function dbHelp(sub) {
|
|
|
823
853
|
case "url":
|
|
824
854
|
log(` ${colors.bold("bata db url")} — print the connection string`);
|
|
825
855
|
log();
|
|
826
|
-
usage("bata db url [--json]");
|
|
856
|
+
usage("bata db url [--project <id>] [--json]");
|
|
827
857
|
break;
|
|
828
858
|
case "connect":
|
|
829
859
|
log(` ${colors.bold("bata db connect")} — open an interactive psql session`);
|
|
830
860
|
log();
|
|
831
|
-
usage("bata db connect");
|
|
861
|
+
usage("bata db connect [--project <id>]");
|
|
832
862
|
note("Interactive only — use `bata db query` / `bata db url` headlessly.");
|
|
833
863
|
break;
|
|
834
864
|
case "studio":
|
|
@@ -865,27 +895,27 @@ export async function handleDb(args) {
|
|
|
865
895
|
}
|
|
866
896
|
switch (sub) {
|
|
867
897
|
case "connect":
|
|
868
|
-
return connect();
|
|
898
|
+
return connect(args.slice(1));
|
|
869
899
|
case "url":
|
|
870
900
|
return url(args.slice(1));
|
|
871
901
|
case "branches":
|
|
872
|
-
return branches();
|
|
902
|
+
return branches(args.slice(1));
|
|
873
903
|
case "branch": {
|
|
874
904
|
const action = args[1];
|
|
875
905
|
if (action === "create")
|
|
876
906
|
return branchCreate(args.slice(2));
|
|
877
907
|
if (action === "delete")
|
|
878
|
-
return branchDelete(args
|
|
908
|
+
return branchDelete(args.slice(2));
|
|
879
909
|
if (action === "checkout")
|
|
880
910
|
return branchCheckout(args.slice(2));
|
|
881
911
|
if (action === "protect")
|
|
882
|
-
return branchSetProtected(args
|
|
912
|
+
return branchSetProtected(args.slice(2), true);
|
|
883
913
|
if (action === "unprotect")
|
|
884
|
-
return branchSetProtected(args
|
|
914
|
+
return branchSetProtected(args.slice(2), false);
|
|
885
915
|
emitError("INVALID_FLAG", `Unknown: db branch ${action || ""}`, "Available: create, delete, checkout, protect, unprotect");
|
|
886
916
|
}
|
|
887
917
|
case "studio":
|
|
888
|
-
return studio();
|
|
918
|
+
return studio(args.slice(1));
|
|
889
919
|
case "query":
|
|
890
920
|
return query(args.slice(1));
|
|
891
921
|
default:
|