@batadata/cli 0.1.6 → 0.1.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/api.d.ts +26 -0
- package/dist/api.js +57 -15
- package/dist/commands/projects.d.ts +8 -0
- package/dist/commands/projects.js +12 -2
- package/dist/commands/status.js +3 -2
- package/dist/config.d.ts +8 -0
- package/dist/config.js +15 -0
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -14,6 +14,32 @@ export declare function request<T = unknown>(method: string, path: string, optio
|
|
|
14
14
|
* generic placeholder so agents see what actually went wrong.
|
|
15
15
|
*/
|
|
16
16
|
export declare function apiError(res: ApiResponse, fallback?: string): string;
|
|
17
|
+
export interface ResolvedTeam {
|
|
18
|
+
teamId: string | undefined;
|
|
19
|
+
/** Set when a configured `defaultTeam` existed but the active credential
|
|
20
|
+
* isn't a member of it, so the CLI fell back to the key's first team. */
|
|
21
|
+
mismatchNote?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Pure team-resolution policy — no fs/network/process access, so it's
|
|
25
|
+
* unit-testable without mocking modules.
|
|
26
|
+
*
|
|
27
|
+
* - Happy path: the credential IS the stored login (tokenSource === "stored")
|
|
28
|
+
* and it has a saved `defaultTeam`. That team was saved BY this login, so
|
|
29
|
+
* it's trustworthy by construction — return it with zero requests.
|
|
30
|
+
* - Otherwise (a `--api-key` flag or `BATA_API_KEY` env may belong to a
|
|
31
|
+
* different account/key than whatever last ran `bata login` on this
|
|
32
|
+
* machine) — don't trust `defaultTeam` blindly. Fetch the key's actual
|
|
33
|
+
* teams and only use `defaultTeam` if the key is really a member; else
|
|
34
|
+
* fall back to the key's first team and report the mismatch.
|
|
35
|
+
*/
|
|
36
|
+
export declare function pickTeamId(params: {
|
|
37
|
+
tokenSource: "flag" | "env" | "stored" | "none";
|
|
38
|
+
savedDefaultTeam: string | undefined;
|
|
39
|
+
fetchTeams: () => Promise<Array<{
|
|
40
|
+
id: string;
|
|
41
|
+
}> | undefined>;
|
|
42
|
+
}): Promise<ResolvedTeam>;
|
|
17
43
|
export declare function resolveTeamId(token: string): Promise<string | undefined>;
|
|
18
44
|
/**
|
|
19
45
|
* List endpoints return `{ data: [...], pagination }` (not a bare array).
|
package/dist/api.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as https from "node:https";
|
|
2
2
|
import * as http from "node:http";
|
|
3
3
|
import { URL } from "node:url";
|
|
4
|
-
import { getApiUrl, loadConfig } from "./config.js";
|
|
4
|
+
import { getApiUrl, loadConfig, getTokenSource, isJsonMode } from "./config.js";
|
|
5
|
+
import { colors } from "./utils/logger.js";
|
|
5
6
|
export async function request(method, path, options = {}) {
|
|
6
7
|
const baseUrl = getApiUrl();
|
|
7
8
|
const url = new URL(path, baseUrl);
|
|
@@ -71,24 +72,65 @@ export function apiError(res, fallback = "Request failed") {
|
|
|
71
72
|
return `${fallback} (HTTP ${res.status || "?"})`;
|
|
72
73
|
}
|
|
73
74
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
75
|
+
* Pure team-resolution policy — no fs/network/process access, so it's
|
|
76
|
+
* unit-testable without mocking modules.
|
|
77
|
+
*
|
|
78
|
+
* - Happy path: the credential IS the stored login (tokenSource === "stored")
|
|
79
|
+
* and it has a saved `defaultTeam`. That team was saved BY this login, so
|
|
80
|
+
* it's trustworthy by construction — return it with zero requests.
|
|
81
|
+
* - Otherwise (a `--api-key` flag or `BATA_API_KEY` env may belong to a
|
|
82
|
+
* different account/key than whatever last ran `bata login` on this
|
|
83
|
+
* machine) — don't trust `defaultTeam` blindly. Fetch the key's actual
|
|
84
|
+
* teams and only use `defaultTeam` if the key is really a member; else
|
|
85
|
+
* fall back to the key's first team and report the mismatch.
|
|
86
|
+
*/
|
|
87
|
+
export async function pickTeamId(params) {
|
|
88
|
+
const { tokenSource, savedDefaultTeam, fetchTeams } = params;
|
|
89
|
+
if (tokenSource === "stored" && savedDefaultTeam) {
|
|
90
|
+
return { teamId: savedDefaultTeam };
|
|
91
|
+
}
|
|
92
|
+
const teams = await fetchTeams();
|
|
93
|
+
if (!teams)
|
|
94
|
+
return { teamId: undefined };
|
|
95
|
+
if (savedDefaultTeam && teams.some((t) => t.id === savedDefaultTeam)) {
|
|
96
|
+
return { teamId: savedDefaultTeam };
|
|
97
|
+
}
|
|
98
|
+
const fallback = teams[0]?.id;
|
|
99
|
+
if (savedDefaultTeam && fallback) {
|
|
100
|
+
return {
|
|
101
|
+
teamId: fallback,
|
|
102
|
+
mismatchNote: `Configured default team "${savedDefaultTeam}" isn't accessible with this ` +
|
|
103
|
+
`API key — using "${fallback}" instead.`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return { teamId: fallback };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve the team id to operate on. Cached for the process lifetime (both
|
|
110
|
+
* the id and whether the mismatch note was already printed), since it never
|
|
111
|
+
* changes mid-run. Returns undefined only if the credential has no teams.
|
|
78
112
|
*/
|
|
79
113
|
let _cachedTeamId;
|
|
114
|
+
let _resolved = false;
|
|
80
115
|
export async function resolveTeamId(token) {
|
|
81
|
-
|
|
82
|
-
if (saved)
|
|
83
|
-
return saved;
|
|
84
|
-
if (_cachedTeamId)
|
|
116
|
+
if (_resolved)
|
|
85
117
|
return _cachedTeamId;
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
118
|
+
const result = await pickTeamId({
|
|
119
|
+
tokenSource: getTokenSource(),
|
|
120
|
+
savedDefaultTeam: loadConfig().defaultTeam,
|
|
121
|
+
fetchTeams: async () => {
|
|
122
|
+
const res = await request("GET", "/v1/teams", { token });
|
|
123
|
+
if (!res.ok)
|
|
124
|
+
return undefined;
|
|
125
|
+
const data = res.data;
|
|
126
|
+
return Array.isArray(data) ? data : data?.teams ?? [];
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
_cachedTeamId = result.teamId;
|
|
130
|
+
_resolved = true;
|
|
131
|
+
if (result.mismatchNote && !isJsonMode()) {
|
|
132
|
+
console.error(colors.dim(` ${result.mismatchNote}`));
|
|
133
|
+
}
|
|
92
134
|
return _cachedTeamId;
|
|
93
135
|
}
|
|
94
136
|
/**
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Only surface `defaultProject` if it's actually one of the caller's real
|
|
3
|
+
* projects — a saved default from another team/key/stale config must never
|
|
4
|
+
* be echoed back as if it were live data. Returns null otherwise.
|
|
5
|
+
*/
|
|
6
|
+
export declare function validDefaultProject(projects: Array<{
|
|
7
|
+
id: string;
|
|
8
|
+
}>, defaultProject: string | undefined): string | null;
|
|
1
9
|
export declare function list(): Promise<void>;
|
|
2
10
|
export declare function create(): Promise<void>;
|
|
3
11
|
export declare function info(projectId?: string): Promise<void>;
|
|
@@ -19,6 +19,16 @@ function formatDate(iso) {
|
|
|
19
19
|
const d = new Date(iso);
|
|
20
20
|
return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Only surface `defaultProject` if it's actually one of the caller's real
|
|
24
|
+
* projects — a saved default from another team/key/stale config must never
|
|
25
|
+
* be echoed back as if it were live data. Returns null otherwise.
|
|
26
|
+
*/
|
|
27
|
+
export function validDefaultProject(projects, defaultProject) {
|
|
28
|
+
if (!defaultProject)
|
|
29
|
+
return null;
|
|
30
|
+
return projects.some((p) => p.id === defaultProject) ? defaultProject : null;
|
|
31
|
+
}
|
|
22
32
|
function statusBadge(status) {
|
|
23
33
|
switch (status?.toLowerCase()) {
|
|
24
34
|
case "active":
|
|
@@ -55,6 +65,7 @@ export async function list() {
|
|
|
55
65
|
}
|
|
56
66
|
s?.stop();
|
|
57
67
|
const projects = asList(res.data);
|
|
68
|
+
const defaultProj = validDefaultProject(projects, config.defaultProject);
|
|
58
69
|
if (jsonMode) {
|
|
59
70
|
json({
|
|
60
71
|
projects: projects.map((p) => ({
|
|
@@ -64,7 +75,7 @@ export async function list() {
|
|
|
64
75
|
status: p.status,
|
|
65
76
|
created_at: projectCreatedAt(p),
|
|
66
77
|
})),
|
|
67
|
-
default_project:
|
|
78
|
+
default_project: defaultProj,
|
|
68
79
|
count: projects.length,
|
|
69
80
|
});
|
|
70
81
|
return;
|
|
@@ -75,7 +86,6 @@ export async function list() {
|
|
|
75
86
|
log();
|
|
76
87
|
return;
|
|
77
88
|
}
|
|
78
|
-
const defaultProj = config.defaultProject;
|
|
79
89
|
table(["NAME", "REGION", "STATUS", "CREATED"], projects.map((p) => [
|
|
80
90
|
p.id === defaultProj ? `${p.name} ${colors.cyan("*")}` : p.name,
|
|
81
91
|
p.region || "-",
|
package/dist/commands/status.js
CHANGED
|
@@ -8,6 +8,7 @@ import { api, apiError, resolveTeamId } from "../api.js";
|
|
|
8
8
|
import { requireToken, loadConfig, isJsonMode } from "../config.js";
|
|
9
9
|
import { colors, log, json, spinner, table, heading } from "../utils/logger.js";
|
|
10
10
|
import { emitError } from "../utils/errors.js";
|
|
11
|
+
import { validDefaultProject } from "./projects.js";
|
|
11
12
|
function statusBadge(status) {
|
|
12
13
|
switch (status?.toLowerCase()) {
|
|
13
14
|
case "active":
|
|
@@ -55,6 +56,7 @@ export async function status() {
|
|
|
55
56
|
}
|
|
56
57
|
s?.stop();
|
|
57
58
|
const projects = Array.isArray(res.data) ? res.data : [];
|
|
59
|
+
const defaultProj = validDefaultProject(projects, config.defaultProject);
|
|
58
60
|
if (jsonMode) {
|
|
59
61
|
json({
|
|
60
62
|
projects: projects.map((p) => ({
|
|
@@ -66,7 +68,7 @@ export async function status() {
|
|
|
66
68
|
branch_count: p.branches?.length ?? 0,
|
|
67
69
|
compute_status: p.computes?.[0]?.status ?? p.status ?? null,
|
|
68
70
|
})),
|
|
69
|
-
default_project:
|
|
71
|
+
default_project: defaultProj,
|
|
70
72
|
count: projects.length,
|
|
71
73
|
});
|
|
72
74
|
return;
|
|
@@ -77,7 +79,6 @@ export async function status() {
|
|
|
77
79
|
log();
|
|
78
80
|
return;
|
|
79
81
|
}
|
|
80
|
-
const defaultProj = config.defaultProject;
|
|
81
82
|
// Fetch branch/compute details for each project
|
|
82
83
|
const rows = [];
|
|
83
84
|
for (const p of projects) {
|
package/dist/config.d.ts
CHANGED
|
@@ -30,6 +30,14 @@ export declare function clearConfig(): void;
|
|
|
30
30
|
* Returns undefined if none is available.
|
|
31
31
|
*/
|
|
32
32
|
export declare function getToken(): string | undefined;
|
|
33
|
+
export type TokenSource = "flag" | "env" | "stored" | "none";
|
|
34
|
+
/**
|
|
35
|
+
* Where the active credential came from, in the same priority order as
|
|
36
|
+
* getToken(). Callers use this to decide whether it's safe to trust
|
|
37
|
+
* config saved by a *different* credential (e.g. a `defaultTeam` written by
|
|
38
|
+
* a previous `bata login`) — it only is when the token IS that stored login.
|
|
39
|
+
*/
|
|
40
|
+
export declare function getTokenSource(): TokenSource;
|
|
33
41
|
/**
|
|
34
42
|
* Like getToken() but exits with a clear, agent-friendly error if no
|
|
35
43
|
* credential can be found anywhere.
|
package/dist/config.js
CHANGED
|
@@ -68,6 +68,21 @@ export function getToken() {
|
|
|
68
68
|
return process.env.BATA_API_KEY;
|
|
69
69
|
return loadConfig().token;
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Where the active credential came from, in the same priority order as
|
|
73
|
+
* getToken(). Callers use this to decide whether it's safe to trust
|
|
74
|
+
* config saved by a *different* credential (e.g. a `defaultTeam` written by
|
|
75
|
+
* a previous `bata login`) — it only is when the token IS that stored login.
|
|
76
|
+
*/
|
|
77
|
+
export function getTokenSource() {
|
|
78
|
+
if (runtime.apiKey)
|
|
79
|
+
return "flag";
|
|
80
|
+
if (process.env.BATA_API_KEY)
|
|
81
|
+
return "env";
|
|
82
|
+
if (loadConfig().token)
|
|
83
|
+
return "stored";
|
|
84
|
+
return "none";
|
|
85
|
+
}
|
|
71
86
|
/**
|
|
72
87
|
* Like getToken() but exits with a clear, agent-friendly error if no
|
|
73
88
|
* credential can be found anywhere.
|