kolaybase-cli 0.1.0
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/LICENSE +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1913 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1913 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// node_modules/tsup/assets/esm_shims.js
|
|
13
|
+
import path from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
var init_esm_shims = __esm({
|
|
16
|
+
"node_modules/tsup/assets/esm_shims.js"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// src/lib/config.ts
|
|
22
|
+
var config_exports = {};
|
|
23
|
+
__export(config_exports, {
|
|
24
|
+
DEFAULT_API_URL: () => DEFAULT_API_URL,
|
|
25
|
+
clearUserConfig: () => clearUserConfig,
|
|
26
|
+
getAccessToken: () => getAccessToken,
|
|
27
|
+
getApiUrl: () => getApiUrl,
|
|
28
|
+
getLocalEnv: () => getLocalEnv,
|
|
29
|
+
getProjectConfig: () => getProjectConfig,
|
|
30
|
+
getProjectRoot: () => getProjectRoot,
|
|
31
|
+
getRefreshToken: () => getRefreshToken,
|
|
32
|
+
getUserConfig: () => getUserConfig,
|
|
33
|
+
isLoggedIn: () => isLoggedIn,
|
|
34
|
+
setAccessToken: () => setAccessToken,
|
|
35
|
+
setApiUrl: () => setApiUrl,
|
|
36
|
+
setLocalEnv: () => setLocalEnv,
|
|
37
|
+
setProjectConfig: () => setProjectConfig,
|
|
38
|
+
setRefreshToken: () => setRefreshToken,
|
|
39
|
+
setUserConfig: () => setUserConfig,
|
|
40
|
+
unsetLocalEnv: () => unsetLocalEnv,
|
|
41
|
+
writeEnvFile: () => writeEnvFile
|
|
42
|
+
});
|
|
43
|
+
import Conf from "conf";
|
|
44
|
+
import path2 from "path";
|
|
45
|
+
import fs from "fs/promises";
|
|
46
|
+
async function getProjectRoot() {
|
|
47
|
+
let currentDir = process.cwd();
|
|
48
|
+
while (currentDir !== path2.parse(currentDir).root) {
|
|
49
|
+
const configPath = path2.join(currentDir, ".kolaybase");
|
|
50
|
+
try {
|
|
51
|
+
await fs.access(configPath);
|
|
52
|
+
return currentDir;
|
|
53
|
+
} catch {
|
|
54
|
+
currentDir = path2.dirname(currentDir);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
async function getProjectConfig() {
|
|
60
|
+
const projectRoot = await getProjectRoot();
|
|
61
|
+
if (!projectRoot) return null;
|
|
62
|
+
const configPath = path2.join(projectRoot, ".kolaybase", "config.json");
|
|
63
|
+
try {
|
|
64
|
+
const data = await fs.readFile(configPath, "utf-8");
|
|
65
|
+
return JSON.parse(data);
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function setProjectConfig(config) {
|
|
71
|
+
const projectRoot = process.cwd();
|
|
72
|
+
const configDir = path2.join(projectRoot, ".kolaybase");
|
|
73
|
+
const configPath = path2.join(configDir, "config.json");
|
|
74
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
75
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
76
|
+
}
|
|
77
|
+
function getUserConfig() {
|
|
78
|
+
return userConfig.store;
|
|
79
|
+
}
|
|
80
|
+
function setUserConfig(config) {
|
|
81
|
+
userConfig.set(config);
|
|
82
|
+
}
|
|
83
|
+
function clearUserConfig() {
|
|
84
|
+
userConfig.clear();
|
|
85
|
+
}
|
|
86
|
+
function getApiUrl() {
|
|
87
|
+
return userConfig.get("apiUrl") || DEFAULT_API_URL;
|
|
88
|
+
}
|
|
89
|
+
function getAccessToken() {
|
|
90
|
+
return userConfig.get("accessToken");
|
|
91
|
+
}
|
|
92
|
+
function setAccessToken(token) {
|
|
93
|
+
userConfig.set("accessToken", token);
|
|
94
|
+
}
|
|
95
|
+
function getRefreshToken() {
|
|
96
|
+
return userConfig.get("refreshToken");
|
|
97
|
+
}
|
|
98
|
+
function setRefreshToken(token) {
|
|
99
|
+
userConfig.set("refreshToken", token);
|
|
100
|
+
}
|
|
101
|
+
function isLoggedIn() {
|
|
102
|
+
return !!userConfig.get("accessToken");
|
|
103
|
+
}
|
|
104
|
+
function setApiUrl(url) {
|
|
105
|
+
userConfig.set("apiUrl", url);
|
|
106
|
+
}
|
|
107
|
+
async function writeEnvFile(project) {
|
|
108
|
+
const lines = [
|
|
109
|
+
'# Kolaybase \u2014 generated by "kb init" / "kb link"',
|
|
110
|
+
"",
|
|
111
|
+
`PROJECT_ID=${project.id}`,
|
|
112
|
+
`PROJECT_SLUG=${project.slug}`,
|
|
113
|
+
"",
|
|
114
|
+
`DB_HOST=${project.dbHost}`,
|
|
115
|
+
`DB_PORT=${project.dbPort}`,
|
|
116
|
+
`DB_NAME=${project.dbName}`,
|
|
117
|
+
`DB_USER=${project.dbUser}`,
|
|
118
|
+
`DB_PASSWORD=${project.dbPassword}`,
|
|
119
|
+
`DATABASE_URL=postgresql://${project.dbUser}:${project.dbPassword}@${project.dbHost}:${project.dbPort}/${project.dbName}`,
|
|
120
|
+
"",
|
|
121
|
+
`KEYCLOAK_REALM=${project.keycloakRealm}`,
|
|
122
|
+
`ANON_KEY=${project.anonKey}`,
|
|
123
|
+
`SERVICE_KEY=${project.serviceKey}`,
|
|
124
|
+
""
|
|
125
|
+
];
|
|
126
|
+
await fs.writeFile(".env", lines.join("\n"));
|
|
127
|
+
try {
|
|
128
|
+
let gi = "";
|
|
129
|
+
try {
|
|
130
|
+
gi = await fs.readFile(".gitignore", "utf-8");
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
if (!gi.includes(".env")) {
|
|
134
|
+
await fs.writeFile(".gitignore", gi + "\n.env\n");
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function getLocalEnv() {
|
|
140
|
+
const projectRoot = await getProjectRoot();
|
|
141
|
+
if (!projectRoot) return {};
|
|
142
|
+
const envPath = path2.join(projectRoot, ".env");
|
|
143
|
+
try {
|
|
144
|
+
const content = await fs.readFile(envPath, "utf-8");
|
|
145
|
+
const env = {};
|
|
146
|
+
content.split("\n").forEach((line) => {
|
|
147
|
+
line = line.trim();
|
|
148
|
+
if (!line || line.startsWith("#")) return;
|
|
149
|
+
const [key, ...valueParts] = line.split("=");
|
|
150
|
+
if (key && valueParts.length > 0) {
|
|
151
|
+
env[key.trim()] = valueParts.join("=").trim();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return env;
|
|
155
|
+
} catch {
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function setLocalEnv(key, value) {
|
|
160
|
+
const projectRoot = await getProjectRoot();
|
|
161
|
+
if (!projectRoot) {
|
|
162
|
+
throw new Error("Not in a Kolaybase project directory");
|
|
163
|
+
}
|
|
164
|
+
const envPath = path2.join(projectRoot, ".env");
|
|
165
|
+
let content = "";
|
|
166
|
+
try {
|
|
167
|
+
content = await fs.readFile(envPath, "utf-8");
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
const lines = content.split("\n");
|
|
171
|
+
let found = false;
|
|
172
|
+
const newLines = lines.map((line) => {
|
|
173
|
+
const trimmed = line.trim();
|
|
174
|
+
if (trimmed.startsWith(`${key}=`)) {
|
|
175
|
+
found = true;
|
|
176
|
+
return `${key}=${value}`;
|
|
177
|
+
}
|
|
178
|
+
return line;
|
|
179
|
+
});
|
|
180
|
+
if (!found) {
|
|
181
|
+
newLines.push(`${key}=${value}`);
|
|
182
|
+
}
|
|
183
|
+
await fs.writeFile(envPath, newLines.join("\n"));
|
|
184
|
+
}
|
|
185
|
+
async function unsetLocalEnv(key) {
|
|
186
|
+
const projectRoot = await getProjectRoot();
|
|
187
|
+
if (!projectRoot) {
|
|
188
|
+
throw new Error("Not in a Kolaybase project directory");
|
|
189
|
+
}
|
|
190
|
+
const envPath = path2.join(projectRoot, ".env");
|
|
191
|
+
try {
|
|
192
|
+
const content = await fs.readFile(envPath, "utf-8");
|
|
193
|
+
const lines = content.split("\n").filter((line) => {
|
|
194
|
+
const trimmed = line.trim();
|
|
195
|
+
return !trimmed.startsWith(`${key}=`);
|
|
196
|
+
});
|
|
197
|
+
await fs.writeFile(envPath, lines.join("\n"));
|
|
198
|
+
} catch {
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
var userConfig, DEFAULT_API_URL;
|
|
202
|
+
var init_config = __esm({
|
|
203
|
+
"src/lib/config.ts"() {
|
|
204
|
+
"use strict";
|
|
205
|
+
init_esm_shims();
|
|
206
|
+
userConfig = new Conf({
|
|
207
|
+
projectName: "kolaybase",
|
|
208
|
+
configName: "config"
|
|
209
|
+
});
|
|
210
|
+
DEFAULT_API_URL = "https://api.kolaybase.com";
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// src/lib/api.ts
|
|
215
|
+
import axios from "axios";
|
|
216
|
+
import chalk from "chalk";
|
|
217
|
+
function handleApiError(error2) {
|
|
218
|
+
if (axios.isAxiosError(error2)) {
|
|
219
|
+
if (error2.response) {
|
|
220
|
+
const message = error2.response.data?.message || error2.response.data?.error || error2.message;
|
|
221
|
+
console.error(chalk.red(`API Error: ${message}`));
|
|
222
|
+
if (error2.response.status === 401) {
|
|
223
|
+
console.error(chalk.yellow("Please login first with: kb login"));
|
|
224
|
+
}
|
|
225
|
+
} else if (error2.request) {
|
|
226
|
+
console.error(chalk.red("Network error: Could not connect to Kolaybase API"));
|
|
227
|
+
console.error(chalk.yellow(`Make sure the API is running at: ${getApiUrl()}`));
|
|
228
|
+
} else {
|
|
229
|
+
console.error(chalk.red(`Error: ${error2.message}`));
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
console.error(chalk.red(`Error: ${error2.message || error2}`));
|
|
233
|
+
}
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
var ApiClient, apiClient;
|
|
237
|
+
var init_api = __esm({
|
|
238
|
+
"src/lib/api.ts"() {
|
|
239
|
+
"use strict";
|
|
240
|
+
init_esm_shims();
|
|
241
|
+
init_config();
|
|
242
|
+
ApiClient = class {
|
|
243
|
+
client;
|
|
244
|
+
constructor() {
|
|
245
|
+
this.client = axios.create({
|
|
246
|
+
baseURL: getApiUrl(),
|
|
247
|
+
timeout: 3e4,
|
|
248
|
+
headers: {
|
|
249
|
+
"Content-Type": "application/json"
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
this.client.interceptors.request.use((config) => {
|
|
253
|
+
const token = getAccessToken();
|
|
254
|
+
if (token) {
|
|
255
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
256
|
+
}
|
|
257
|
+
return config;
|
|
258
|
+
});
|
|
259
|
+
this.client.interceptors.response.use(
|
|
260
|
+
(response) => response,
|
|
261
|
+
async (error2) => {
|
|
262
|
+
const originalRequest = error2.config;
|
|
263
|
+
if (error2.response?.status === 401 && originalRequest && !originalRequest._retry) {
|
|
264
|
+
originalRequest._retry = true;
|
|
265
|
+
try {
|
|
266
|
+
const refreshToken = getRefreshToken();
|
|
267
|
+
if (!refreshToken) {
|
|
268
|
+
throw new Error("No refresh token available");
|
|
269
|
+
}
|
|
270
|
+
const { data } = await axios.post(`${getApiUrl()}/api/auth/refresh`, {
|
|
271
|
+
refreshToken
|
|
272
|
+
});
|
|
273
|
+
setAccessToken(data.accessToken);
|
|
274
|
+
setRefreshToken(data.refreshToken);
|
|
275
|
+
originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;
|
|
276
|
+
return this.client(originalRequest);
|
|
277
|
+
} catch (refreshError) {
|
|
278
|
+
console.error(chalk.red("Session expired. Please login again with: kb login"));
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return Promise.reject(error2);
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
// Auth endpoints
|
|
287
|
+
async login(username, password) {
|
|
288
|
+
const { data } = await this.client.post("/api/auth/login", { username, password });
|
|
289
|
+
return data;
|
|
290
|
+
}
|
|
291
|
+
async signup(userData) {
|
|
292
|
+
const { data } = await this.client.post("/api/auth/signup", userData);
|
|
293
|
+
return data;
|
|
294
|
+
}
|
|
295
|
+
// Projects endpoints
|
|
296
|
+
async getProjects(teamId) {
|
|
297
|
+
const { data } = await this.client.get("/api/projects", {
|
|
298
|
+
params: { teamId }
|
|
299
|
+
});
|
|
300
|
+
return data;
|
|
301
|
+
}
|
|
302
|
+
async getProject(projectId) {
|
|
303
|
+
const { data } = await this.client.get(`/api/projects/${projectId}`);
|
|
304
|
+
return data;
|
|
305
|
+
}
|
|
306
|
+
async createProject(projectData) {
|
|
307
|
+
const { data } = await this.client.post("/api/projects", projectData);
|
|
308
|
+
return data;
|
|
309
|
+
}
|
|
310
|
+
async deleteProject(projectId) {
|
|
311
|
+
const { data } = await this.client.delete(`/api/projects/${projectId}`);
|
|
312
|
+
return data;
|
|
313
|
+
}
|
|
314
|
+
// SQL endpoints
|
|
315
|
+
async executeSQL(projectId, query) {
|
|
316
|
+
const { data } = await this.client.post("/api/sql/execute", {
|
|
317
|
+
projectId,
|
|
318
|
+
query
|
|
319
|
+
});
|
|
320
|
+
return data;
|
|
321
|
+
}
|
|
322
|
+
// Teams endpoints
|
|
323
|
+
async getTeams() {
|
|
324
|
+
const { data } = await this.client.get("/api/teams");
|
|
325
|
+
return data;
|
|
326
|
+
}
|
|
327
|
+
async getActiveTeam() {
|
|
328
|
+
const { data } = await this.client.get("/api/teams/active");
|
|
329
|
+
return data;
|
|
330
|
+
}
|
|
331
|
+
// Project data endpoints
|
|
332
|
+
async getTables(projectId) {
|
|
333
|
+
const { data } = await this.client.get(`/api/projects/${projectId}/data/tables`);
|
|
334
|
+
return data;
|
|
335
|
+
}
|
|
336
|
+
async getTableSchema(projectId, tableName) {
|
|
337
|
+
const { data } = await this.client.get(`/api/projects/${projectId}/data/tables/${tableName}/schema`);
|
|
338
|
+
return data;
|
|
339
|
+
}
|
|
340
|
+
async getTableData(projectId, tableName, options) {
|
|
341
|
+
const { data } = await this.client.get(`/api/projects/${projectId}/data/tables/${tableName}/rows`, {
|
|
342
|
+
params: options
|
|
343
|
+
});
|
|
344
|
+
return data;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
apiClient = new ApiClient();
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// src/lib/ui.ts
|
|
352
|
+
import chalk2 from "chalk";
|
|
353
|
+
import ora from "ora";
|
|
354
|
+
import boxen from "boxen";
|
|
355
|
+
function success(message) {
|
|
356
|
+
console.log(chalk2.green("\u2713"), message);
|
|
357
|
+
}
|
|
358
|
+
function error(message) {
|
|
359
|
+
console.log(chalk2.red("\u2717"), message);
|
|
360
|
+
}
|
|
361
|
+
function warning(message) {
|
|
362
|
+
console.log(chalk2.yellow("\u26A0"), message);
|
|
363
|
+
}
|
|
364
|
+
function info(message) {
|
|
365
|
+
console.log(chalk2.blue("\u2139"), message);
|
|
366
|
+
}
|
|
367
|
+
function createSpinner(text) {
|
|
368
|
+
return ora(text).start();
|
|
369
|
+
}
|
|
370
|
+
function printHeader(text) {
|
|
371
|
+
console.log();
|
|
372
|
+
console.log(chalk2.bold.cyan(text));
|
|
373
|
+
console.log(chalk2.cyan("\u2500".repeat(text.length)));
|
|
374
|
+
}
|
|
375
|
+
function printTable(headers, rows) {
|
|
376
|
+
const columnWidths = headers.map((header, i) => {
|
|
377
|
+
const maxRowWidth = Math.max(...rows.map((row2) => (row2[i] || "").length));
|
|
378
|
+
return Math.max(header.length, maxRowWidth);
|
|
379
|
+
});
|
|
380
|
+
const headerRow = headers.map(
|
|
381
|
+
(header, i) => header.padEnd(columnWidths[i])
|
|
382
|
+
).join(" ");
|
|
383
|
+
console.log(chalk2.bold(headerRow));
|
|
384
|
+
console.log(chalk2.gray("\u2500".repeat(headerRow.length)));
|
|
385
|
+
rows.forEach((row2) => {
|
|
386
|
+
const rowStr = row2.map(
|
|
387
|
+
(cell, i) => (cell || "").padEnd(columnWidths[i])
|
|
388
|
+
).join(" ");
|
|
389
|
+
console.log(rowStr);
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
function printKeyValue(data) {
|
|
393
|
+
const maxKeyLength = Math.max(...Object.keys(data).map((k) => k.length));
|
|
394
|
+
Object.entries(data).forEach(([key, value]) => {
|
|
395
|
+
const paddedKey = chalk2.bold(key.padEnd(maxKeyLength));
|
|
396
|
+
const displayValue = value ?? chalk2.gray("(not set)");
|
|
397
|
+
console.log(`${paddedKey} ${displayValue}`);
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
function printLogo() {
|
|
401
|
+
const logo = `
|
|
402
|
+
${chalk2.cyan("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510")}
|
|
403
|
+
${chalk2.cyan("\u2502")} ${chalk2.bold.cyan("K O L A Y B A S E")} ${chalk2.cyan("\u2502")}
|
|
404
|
+
${chalk2.cyan("\u2502")} ${chalk2.gray("Backend-as-a-Service Platform")} ${chalk2.cyan("\u2502")}
|
|
405
|
+
${chalk2.cyan("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518")}
|
|
406
|
+
`;
|
|
407
|
+
console.log(logo);
|
|
408
|
+
}
|
|
409
|
+
var init_ui = __esm({
|
|
410
|
+
"src/lib/ui.ts"() {
|
|
411
|
+
"use strict";
|
|
412
|
+
init_esm_shims();
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// src/commands/db.ts
|
|
417
|
+
var db_exports = {};
|
|
418
|
+
__export(db_exports, {
|
|
419
|
+
dbCommand: () => dbCommand,
|
|
420
|
+
dbDiff: () => dbDiff,
|
|
421
|
+
dbDump: () => dbDump,
|
|
422
|
+
dbPull: () => dbPull,
|
|
423
|
+
dbPush: () => dbPush,
|
|
424
|
+
dbReset: () => dbReset,
|
|
425
|
+
dbSeed: () => dbSeed
|
|
426
|
+
});
|
|
427
|
+
import inquirer5 from "inquirer";
|
|
428
|
+
import chalk9 from "chalk";
|
|
429
|
+
import { Pool } from "pg";
|
|
430
|
+
import fs3 from "fs/promises";
|
|
431
|
+
import path4 from "path";
|
|
432
|
+
async function dbCommand() {
|
|
433
|
+
console.log(chalk9.bold.cyan("Database Management\n"));
|
|
434
|
+
info("Use db subcommands: push, pull, reset, seed, diff");
|
|
435
|
+
}
|
|
436
|
+
async function dbPush() {
|
|
437
|
+
console.log(chalk9.bold.cyan("Push Database Schema\n"));
|
|
438
|
+
const config = await getProjectConfig();
|
|
439
|
+
if (!config) {
|
|
440
|
+
error("Not in a Kolaybase project. Run: kb init");
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
const spinner = createSpinner("Analyzing schema changes...");
|
|
444
|
+
try {
|
|
445
|
+
const env = await getLocalEnv();
|
|
446
|
+
const schemaPath = await findSchemaFile();
|
|
447
|
+
if (!schemaPath) {
|
|
448
|
+
spinner.fail("No schema file found");
|
|
449
|
+
error("Could not find schema.prisma, schema.sql, or migrations directory");
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
spinner.text = "Pushing schema to database...";
|
|
453
|
+
if (schemaPath.endsWith(".prisma")) {
|
|
454
|
+
const { execa } = await import("execa");
|
|
455
|
+
await execa("npx", ["prisma", "db", "push"], {
|
|
456
|
+
stdio: "inherit",
|
|
457
|
+
env: {
|
|
458
|
+
...process.env,
|
|
459
|
+
DATABASE_URL: env.DATABASE_URL
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
} else {
|
|
463
|
+
const sql = await fs3.readFile(schemaPath, "utf-8");
|
|
464
|
+
await apiClient.executeSQL(config.projectId, sql);
|
|
465
|
+
}
|
|
466
|
+
spinner.succeed("Schema pushed successfully");
|
|
467
|
+
success("Database is up to date");
|
|
468
|
+
} catch (err) {
|
|
469
|
+
spinner.fail("Failed to push schema");
|
|
470
|
+
handleApiError(err);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
async function dbPull() {
|
|
474
|
+
console.log(chalk9.bold.cyan("Pull Database Schema\n"));
|
|
475
|
+
const config = await getProjectConfig();
|
|
476
|
+
if (!config) {
|
|
477
|
+
error("Not in a Kolaybase project. Run: kb init");
|
|
478
|
+
process.exit(1);
|
|
479
|
+
}
|
|
480
|
+
const spinner = createSpinner("Pulling schema from database...");
|
|
481
|
+
try {
|
|
482
|
+
const env = await getLocalEnv();
|
|
483
|
+
const prismaPath = path4.join(process.cwd(), "prisma", "schema.prisma");
|
|
484
|
+
try {
|
|
485
|
+
await fs3.access(prismaPath);
|
|
486
|
+
const { execa } = await import("execa");
|
|
487
|
+
await execa("npx", ["prisma", "db", "pull"], {
|
|
488
|
+
stdio: "inherit",
|
|
489
|
+
env: {
|
|
490
|
+
...process.env,
|
|
491
|
+
DATABASE_URL: env.DATABASE_URL
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
spinner.succeed("Schema pulled successfully");
|
|
495
|
+
} catch {
|
|
496
|
+
spinner.text = "Generating SQL dump...";
|
|
497
|
+
const pool = new Pool({
|
|
498
|
+
connectionString: env.DATABASE_URL
|
|
499
|
+
});
|
|
500
|
+
const client = await pool.connect();
|
|
501
|
+
try {
|
|
502
|
+
const { rows } = await client.query(`
|
|
503
|
+
SELECT table_name
|
|
504
|
+
FROM information_schema.tables
|
|
505
|
+
WHERE table_schema = 'public'
|
|
506
|
+
ORDER BY table_name
|
|
507
|
+
`);
|
|
508
|
+
const tables = rows.map((r) => r.table_name);
|
|
509
|
+
let schemaDump = "-- Database schema dump\n\n";
|
|
510
|
+
for (const table of tables) {
|
|
511
|
+
const { rows: columns } = await client.query(`
|
|
512
|
+
SELECT column_name, data_type, is_nullable, column_default
|
|
513
|
+
FROM information_schema.columns
|
|
514
|
+
WHERE table_name = $1
|
|
515
|
+
ORDER BY ordinal_position
|
|
516
|
+
`, [table]);
|
|
517
|
+
schemaDump += `CREATE TABLE IF NOT EXISTS ${table} (
|
|
518
|
+
`;
|
|
519
|
+
schemaDump += columns.map((col) => {
|
|
520
|
+
let def = ` ${col.column_name} ${col.data_type}`;
|
|
521
|
+
if (col.is_nullable === "NO") def += " NOT NULL";
|
|
522
|
+
if (col.column_default) def += ` DEFAULT ${col.column_default}`;
|
|
523
|
+
return def;
|
|
524
|
+
}).join(",\n");
|
|
525
|
+
schemaDump += "\n);\n\n";
|
|
526
|
+
}
|
|
527
|
+
await fs3.mkdir("db", { recursive: true });
|
|
528
|
+
await fs3.writeFile("db/schema.sql", schemaDump);
|
|
529
|
+
spinner.succeed("Schema pulled successfully");
|
|
530
|
+
info("Schema saved to db/schema.sql");
|
|
531
|
+
} finally {
|
|
532
|
+
client.release();
|
|
533
|
+
await pool.end();
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
} catch (err) {
|
|
537
|
+
spinner.fail("Failed to pull schema");
|
|
538
|
+
handleApiError(err);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async function dbReset(options) {
|
|
542
|
+
console.log(chalk9.bold.cyan("Reset Database\n"));
|
|
543
|
+
const config = await getProjectConfig();
|
|
544
|
+
if (!config) {
|
|
545
|
+
error("Not in a Kolaybase project. Run: kb init");
|
|
546
|
+
process.exit(1);
|
|
547
|
+
}
|
|
548
|
+
console.log(chalk9.yellow("\u26A0 WARNING: This will delete all data in the database!"));
|
|
549
|
+
console.log();
|
|
550
|
+
if (!options.force) {
|
|
551
|
+
const answers = await inquirer5.prompt([
|
|
552
|
+
{
|
|
553
|
+
type: "confirm",
|
|
554
|
+
name: "confirm",
|
|
555
|
+
message: "Are you sure you want to reset the database?",
|
|
556
|
+
default: false
|
|
557
|
+
}
|
|
558
|
+
]);
|
|
559
|
+
if (!answers.confirm) {
|
|
560
|
+
info("Reset cancelled");
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const spinner = createSpinner("Resetting database...");
|
|
565
|
+
try {
|
|
566
|
+
const env = await getLocalEnv();
|
|
567
|
+
const pool = new Pool({
|
|
568
|
+
connectionString: env.DATABASE_URL
|
|
569
|
+
});
|
|
570
|
+
const client = await pool.connect();
|
|
571
|
+
try {
|
|
572
|
+
const { rows } = await client.query(`
|
|
573
|
+
SELECT tablename
|
|
574
|
+
FROM pg_tables
|
|
575
|
+
WHERE schemaname = 'public'
|
|
576
|
+
`);
|
|
577
|
+
for (const row2 of rows) {
|
|
578
|
+
await client.query(`DROP TABLE IF EXISTS "${row2.tablename}" CASCADE`);
|
|
579
|
+
}
|
|
580
|
+
spinner.succeed("Database reset successfully");
|
|
581
|
+
success("All tables dropped");
|
|
582
|
+
console.log();
|
|
583
|
+
info("To recreate schema, run: kb db push");
|
|
584
|
+
} finally {
|
|
585
|
+
client.release();
|
|
586
|
+
await pool.end();
|
|
587
|
+
}
|
|
588
|
+
} catch (err) {
|
|
589
|
+
spinner.fail("Failed to reset database");
|
|
590
|
+
handleApiError(err);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
async function dbSeed() {
|
|
594
|
+
console.log(chalk9.bold.cyan("Seed Database\n"));
|
|
595
|
+
const config = await getProjectConfig();
|
|
596
|
+
if (!config) {
|
|
597
|
+
error("Not in a Kolaybase project. Run: kb init");
|
|
598
|
+
process.exit(1);
|
|
599
|
+
}
|
|
600
|
+
const spinner = createSpinner("Looking for seed file...");
|
|
601
|
+
try {
|
|
602
|
+
const seedPaths = [
|
|
603
|
+
"prisma/seed.ts",
|
|
604
|
+
"prisma/seed.js",
|
|
605
|
+
"db/seed.sql",
|
|
606
|
+
"seeds/seed.sql"
|
|
607
|
+
];
|
|
608
|
+
let seedPath = null;
|
|
609
|
+
for (const p of seedPaths) {
|
|
610
|
+
try {
|
|
611
|
+
await fs3.access(p);
|
|
612
|
+
seedPath = p;
|
|
613
|
+
break;
|
|
614
|
+
} catch {
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
if (!seedPath) {
|
|
618
|
+
spinner.fail("No seed file found");
|
|
619
|
+
warning("Create a seed file at prisma/seed.ts or db/seed.sql");
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
spinner.text = "Running seed...";
|
|
623
|
+
if (seedPath.endsWith(".sql")) {
|
|
624
|
+
const sql = await fs3.readFile(seedPath, "utf-8");
|
|
625
|
+
await apiClient.executeSQL(config.projectId, sql);
|
|
626
|
+
} else {
|
|
627
|
+
const { execa } = await import("execa");
|
|
628
|
+
await execa("npx", ["prisma", "db", "seed"], {
|
|
629
|
+
stdio: "inherit"
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
spinner.succeed("Database seeded successfully");
|
|
633
|
+
} catch (err) {
|
|
634
|
+
spinner.fail("Failed to seed database");
|
|
635
|
+
handleApiError(err);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
async function dbDiff() {
|
|
639
|
+
console.log(chalk9.bold.cyan("Database Schema Diff\n"));
|
|
640
|
+
const config = await getProjectConfig();
|
|
641
|
+
if (!config) {
|
|
642
|
+
error("Not in a Kolaybase project. Run: kb init");
|
|
643
|
+
process.exit(1);
|
|
644
|
+
}
|
|
645
|
+
info("Checking for schema differences...");
|
|
646
|
+
try {
|
|
647
|
+
const { execa } = await import("execa");
|
|
648
|
+
await execa("npx", ["prisma", "migrate", "diff"], {
|
|
649
|
+
stdio: "inherit"
|
|
650
|
+
});
|
|
651
|
+
} catch (err) {
|
|
652
|
+
warning("Could not generate diff. Make sure Prisma is configured.");
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
async function dbDump(options) {
|
|
656
|
+
console.log(chalk9.bold.cyan("Dump Database Schema\n"));
|
|
657
|
+
const config = await getProjectConfig();
|
|
658
|
+
if (!config) {
|
|
659
|
+
error("Not in a Kolaybase project. Run: kb init");
|
|
660
|
+
process.exit(1);
|
|
661
|
+
}
|
|
662
|
+
const spinner = createSpinner("Dumping schema\u2026");
|
|
663
|
+
try {
|
|
664
|
+
const env = await getLocalEnv();
|
|
665
|
+
const pool = new Pool({ connectionString: env.DATABASE_URL });
|
|
666
|
+
const client = await pool.connect();
|
|
667
|
+
try {
|
|
668
|
+
const { rows: tables } = await client.query(`
|
|
669
|
+
SELECT table_name
|
|
670
|
+
FROM information_schema.tables
|
|
671
|
+
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
|
|
672
|
+
ORDER BY table_name
|
|
673
|
+
`);
|
|
674
|
+
let dump = "-- Kolaybase schema dump\n-- Generated: " + (/* @__PURE__ */ new Date()).toISOString() + "\n\n";
|
|
675
|
+
for (const t of tables) {
|
|
676
|
+
const { rows: cols } = await client.query(`
|
|
677
|
+
SELECT column_name, udt_name, is_nullable, column_default, character_maximum_length
|
|
678
|
+
FROM information_schema.columns
|
|
679
|
+
WHERE table_name = $1
|
|
680
|
+
ORDER BY ordinal_position
|
|
681
|
+
`, [t.table_name]);
|
|
682
|
+
dump += `CREATE TABLE IF NOT EXISTS "${t.table_name}" (
|
|
683
|
+
`;
|
|
684
|
+
dump += cols.map((c) => {
|
|
685
|
+
let def = ` "${c.column_name}" ${c.udt_name}`;
|
|
686
|
+
if (c.character_maximum_length) def += `(${c.character_maximum_length})`;
|
|
687
|
+
if (c.is_nullable === "NO") def += " NOT NULL";
|
|
688
|
+
if (c.column_default) def += ` DEFAULT ${c.column_default}`;
|
|
689
|
+
return def;
|
|
690
|
+
}).join(",\n");
|
|
691
|
+
dump += "\n);\n\n";
|
|
692
|
+
}
|
|
693
|
+
const outFile = options.output || "schema.sql";
|
|
694
|
+
await fs3.writeFile(outFile, dump);
|
|
695
|
+
spinner.succeed(`Schema dumped to ${chalk9.cyan(outFile)}`);
|
|
696
|
+
} finally {
|
|
697
|
+
client.release();
|
|
698
|
+
await pool.end();
|
|
699
|
+
}
|
|
700
|
+
} catch (err) {
|
|
701
|
+
spinner.fail("Failed to dump schema");
|
|
702
|
+
handleApiError(err);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
async function findSchemaFile() {
|
|
706
|
+
const paths = [
|
|
707
|
+
"prisma/schema.prisma",
|
|
708
|
+
"db/schema.sql",
|
|
709
|
+
"schema.sql",
|
|
710
|
+
"migrations"
|
|
711
|
+
];
|
|
712
|
+
for (const p of paths) {
|
|
713
|
+
try {
|
|
714
|
+
await fs3.access(p);
|
|
715
|
+
return p;
|
|
716
|
+
} catch {
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
var init_db = __esm({
|
|
722
|
+
"src/commands/db.ts"() {
|
|
723
|
+
"use strict";
|
|
724
|
+
init_esm_shims();
|
|
725
|
+
init_api();
|
|
726
|
+
init_config();
|
|
727
|
+
init_ui();
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// src/commands/inspect.ts
|
|
732
|
+
var inspect_exports = {};
|
|
733
|
+
__export(inspect_exports, {
|
|
734
|
+
inspectCommand: () => inspectCommand
|
|
735
|
+
});
|
|
736
|
+
import chalk10 from "chalk";
|
|
737
|
+
import { Pool as Pool2 } from "pg";
|
|
738
|
+
async function inspectCommand(options) {
|
|
739
|
+
const config = await getProjectConfig();
|
|
740
|
+
if (!config?.projectId) {
|
|
741
|
+
error("Not linked to a project. Run: kb link or kb init");
|
|
742
|
+
process.exit(1);
|
|
743
|
+
}
|
|
744
|
+
const env = await getLocalEnv();
|
|
745
|
+
if (!env.DATABASE_URL) {
|
|
746
|
+
error("DATABASE_URL not found in .env \u2014 run kb link to refresh credentials");
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
const pool = new Pool2({ connectionString: env.DATABASE_URL });
|
|
750
|
+
try {
|
|
751
|
+
if (options.table) {
|
|
752
|
+
await inspectTable(pool, options.table);
|
|
753
|
+
} else {
|
|
754
|
+
await inspectAll(pool);
|
|
755
|
+
}
|
|
756
|
+
} catch (err) {
|
|
757
|
+
error(`Database error: ${err.message}`);
|
|
758
|
+
process.exit(1);
|
|
759
|
+
} finally {
|
|
760
|
+
await pool.end();
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
async function inspectAll(pool) {
|
|
764
|
+
const spinner = createSpinner("Querying database\u2026");
|
|
765
|
+
const { rows } = await pool.query(`
|
|
766
|
+
SELECT
|
|
767
|
+
t.table_name,
|
|
768
|
+
pg_total_relation_size(quote_ident(t.table_name)) AS total_bytes,
|
|
769
|
+
pg_size_pretty(pg_total_relation_size(quote_ident(t.table_name))) AS size,
|
|
770
|
+
(SELECT reltuples::bigint FROM pg_class WHERE relname = t.table_name) AS est_rows
|
|
771
|
+
FROM information_schema.tables t
|
|
772
|
+
WHERE t.table_schema = 'public' AND t.table_type = 'BASE TABLE'
|
|
773
|
+
ORDER BY total_bytes DESC
|
|
774
|
+
`);
|
|
775
|
+
spinner.stop();
|
|
776
|
+
if (!rows.length) {
|
|
777
|
+
console.log(chalk10.gray(" No tables found. Push a schema first: kb db push"));
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
printHeader("Tables");
|
|
781
|
+
console.log();
|
|
782
|
+
const nameW = Math.max(10, ...rows.map((r) => r.table_name.length));
|
|
783
|
+
console.log(
|
|
784
|
+
` ${chalk10.bold("Table".padEnd(nameW))} ${chalk10.bold("Rows".padStart(10))} ${chalk10.bold("Size".padStart(10))}`
|
|
785
|
+
);
|
|
786
|
+
console.log(chalk10.gray(" " + "\u2500".repeat(nameW + 24)));
|
|
787
|
+
for (const r of rows) {
|
|
788
|
+
const rowCount = Number(r.est_rows) >= 0 ? Number(r.est_rows).toLocaleString() : "\u2014";
|
|
789
|
+
console.log(
|
|
790
|
+
` ${chalk10.cyan(r.table_name.padEnd(nameW))} ${rowCount.padStart(10)} ${String(r.size).padStart(10)}`
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
console.log();
|
|
794
|
+
console.log(chalk10.gray(` ${rows.length} table(s)`));
|
|
795
|
+
console.log(chalk10.gray(" Inspect a table: kb inspect --table <name>"));
|
|
796
|
+
}
|
|
797
|
+
async function inspectTable(pool, tableName) {
|
|
798
|
+
const spinner = createSpinner(`Inspecting ${tableName}\u2026`);
|
|
799
|
+
const { rows: columns } = await pool.query(`
|
|
800
|
+
SELECT
|
|
801
|
+
c.column_name,
|
|
802
|
+
c.data_type,
|
|
803
|
+
c.udt_name,
|
|
804
|
+
c.is_nullable,
|
|
805
|
+
c.column_default,
|
|
806
|
+
c.character_maximum_length
|
|
807
|
+
FROM information_schema.columns c
|
|
808
|
+
WHERE c.table_schema = 'public' AND c.table_name = $1
|
|
809
|
+
ORDER BY c.ordinal_position
|
|
810
|
+
`, [tableName]);
|
|
811
|
+
if (!columns.length) {
|
|
812
|
+
spinner.fail(`Table "${tableName}" not found`);
|
|
813
|
+
process.exit(1);
|
|
814
|
+
}
|
|
815
|
+
const { rows: indexes } = await pool.query(`
|
|
816
|
+
SELECT indexname, indexdef
|
|
817
|
+
FROM pg_indexes
|
|
818
|
+
WHERE schemaname = 'public' AND tablename = $1
|
|
819
|
+
`, [tableName]);
|
|
820
|
+
const { rows: fkeys } = await pool.query(`
|
|
821
|
+
SELECT
|
|
822
|
+
kcu.column_name,
|
|
823
|
+
ccu.table_name AS foreign_table,
|
|
824
|
+
ccu.column_name AS foreign_column
|
|
825
|
+
FROM information_schema.table_constraints tc
|
|
826
|
+
JOIN information_schema.key_column_usage kcu
|
|
827
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
828
|
+
JOIN information_schema.constraint_column_usage ccu
|
|
829
|
+
ON tc.constraint_name = ccu.constraint_name
|
|
830
|
+
WHERE tc.table_name = $1 AND tc.constraint_type = 'FOREIGN KEY'
|
|
831
|
+
`, [tableName]);
|
|
832
|
+
const { rows: meta } = await pool.query(`
|
|
833
|
+
SELECT
|
|
834
|
+
pg_size_pretty(pg_total_relation_size(quote_ident($1))) AS size,
|
|
835
|
+
(SELECT reltuples::bigint FROM pg_class WHERE relname = $1) AS est_rows
|
|
836
|
+
`, [tableName]);
|
|
837
|
+
spinner.stop();
|
|
838
|
+
printHeader(`Table: ${tableName}`);
|
|
839
|
+
console.log();
|
|
840
|
+
if (meta[0]) {
|
|
841
|
+
console.log(` ${chalk10.gray("Rows")} ${Number(meta[0].est_rows).toLocaleString()}`);
|
|
842
|
+
console.log(` ${chalk10.gray("Size")} ${meta[0].size}`);
|
|
843
|
+
console.log();
|
|
844
|
+
}
|
|
845
|
+
console.log(chalk10.bold(" Columns"));
|
|
846
|
+
const nameW = Math.max(6, ...columns.map((c) => c.column_name.length));
|
|
847
|
+
const typeW = Math.max(4, ...columns.map((c) => formatType(c).length));
|
|
848
|
+
console.log(
|
|
849
|
+
` ${chalk10.gray("Name".padEnd(nameW))} ${chalk10.gray("Type".padEnd(typeW))} ${chalk10.gray("Nullable")} ${chalk10.gray("Default")}`
|
|
850
|
+
);
|
|
851
|
+
console.log(chalk10.gray(" " + "\u2500".repeat(nameW + typeW + 24)));
|
|
852
|
+
for (const col of columns) {
|
|
853
|
+
const nullable = col.is_nullable === "YES" ? chalk10.yellow("YES") : chalk10.gray("NO ");
|
|
854
|
+
const def = col.column_default ? chalk10.gray(truncate2(col.column_default, 30)) : "";
|
|
855
|
+
const fk = fkeys.find((f) => f.column_name === col.column_name);
|
|
856
|
+
const fkLabel = fk ? chalk10.blue(` \u2192 ${fk.foreign_table}.${fk.foreign_column}`) : "";
|
|
857
|
+
console.log(
|
|
858
|
+
` ${chalk10.cyan(col.column_name.padEnd(nameW))} ${formatType(col).padEnd(typeW)} ${nullable} ${def}${fkLabel}`
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
if (indexes.length) {
|
|
862
|
+
console.log();
|
|
863
|
+
console.log(chalk10.bold(" Indexes"));
|
|
864
|
+
for (const idx of indexes) {
|
|
865
|
+
console.log(` ${chalk10.gray("\u2022")} ${idx.indexname}`);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
console.log();
|
|
869
|
+
}
|
|
870
|
+
function formatType(col) {
|
|
871
|
+
let t = col.udt_name || col.data_type;
|
|
872
|
+
if (col.character_maximum_length) t += `(${col.character_maximum_length})`;
|
|
873
|
+
return t;
|
|
874
|
+
}
|
|
875
|
+
function truncate2(s, max) {
|
|
876
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
877
|
+
}
|
|
878
|
+
var init_inspect = __esm({
|
|
879
|
+
"src/commands/inspect.ts"() {
|
|
880
|
+
"use strict";
|
|
881
|
+
init_esm_shims();
|
|
882
|
+
init_config();
|
|
883
|
+
init_ui();
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
// src/commands/gen.ts
|
|
888
|
+
var gen_exports = {};
|
|
889
|
+
__export(gen_exports, {
|
|
890
|
+
genClient: () => genClient,
|
|
891
|
+
genTypes: () => genTypes
|
|
892
|
+
});
|
|
893
|
+
import chalk11 from "chalk";
|
|
894
|
+
import fs4 from "fs/promises";
|
|
895
|
+
import path5 from "path";
|
|
896
|
+
import { Pool as Pool3 } from "pg";
|
|
897
|
+
async function genTypes(options) {
|
|
898
|
+
console.log(chalk11.bold.cyan("Generate TypeScript Types\n"));
|
|
899
|
+
const config = await getProjectConfig();
|
|
900
|
+
if (!config) {
|
|
901
|
+
error("Not in a Kolaybase project. Run: kb init");
|
|
902
|
+
process.exit(1);
|
|
903
|
+
}
|
|
904
|
+
const spinner = createSpinner("Generating types from database schema...");
|
|
905
|
+
try {
|
|
906
|
+
const env = await getLocalEnv();
|
|
907
|
+
const pool = new Pool3({
|
|
908
|
+
connectionString: env.DATABASE_URL
|
|
909
|
+
});
|
|
910
|
+
const client = await pool.connect();
|
|
911
|
+
try {
|
|
912
|
+
const { rows: tables } = await client.query(`
|
|
913
|
+
SELECT table_name
|
|
914
|
+
FROM information_schema.tables
|
|
915
|
+
WHERE table_schema = 'public'
|
|
916
|
+
ORDER BY table_name
|
|
917
|
+
`);
|
|
918
|
+
let typesContent = `// Generated by Kolaybase CLI
|
|
919
|
+
// Do not edit manually
|
|
920
|
+
|
|
921
|
+
export interface Database {
|
|
922
|
+
${tables.map((t) => ` ${t.table_name}: ${toPascalCase(t.table_name)};`).join("\n")}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
`;
|
|
926
|
+
for (const table of tables) {
|
|
927
|
+
const { rows: columns } = await client.query(`
|
|
928
|
+
SELECT
|
|
929
|
+
column_name,
|
|
930
|
+
data_type,
|
|
931
|
+
is_nullable,
|
|
932
|
+
column_default
|
|
933
|
+
FROM information_schema.columns
|
|
934
|
+
WHERE table_name = $1
|
|
935
|
+
ORDER BY ordinal_position
|
|
936
|
+
`, [table.table_name]);
|
|
937
|
+
const typeName = toPascalCase(table.table_name);
|
|
938
|
+
typesContent += `export interface ${typeName} {
|
|
939
|
+
`;
|
|
940
|
+
columns.forEach((col) => {
|
|
941
|
+
const tsType = pgTypeToTs(col.data_type);
|
|
942
|
+
const optional = col.is_nullable === "YES" || col.column_default ? "?" : "";
|
|
943
|
+
typesContent += ` ${col.column_name}${optional}: ${tsType};
|
|
944
|
+
`;
|
|
945
|
+
});
|
|
946
|
+
typesContent += `}
|
|
947
|
+
|
|
948
|
+
`;
|
|
949
|
+
}
|
|
950
|
+
const outputDir = options.output || "types";
|
|
951
|
+
await fs4.mkdir(outputDir, { recursive: true });
|
|
952
|
+
const outputPath = path5.join(outputDir, "database.ts");
|
|
953
|
+
await fs4.writeFile(outputPath, typesContent);
|
|
954
|
+
spinner.succeed("Types generated successfully");
|
|
955
|
+
success(`Written to ${chalk11.cyan(outputPath)}`);
|
|
956
|
+
} finally {
|
|
957
|
+
client.release();
|
|
958
|
+
await pool.end();
|
|
959
|
+
}
|
|
960
|
+
} catch (err) {
|
|
961
|
+
spinner.fail("Failed to generate types");
|
|
962
|
+
handleApiError(err);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
async function genClient(options) {
|
|
966
|
+
console.log(chalk11.bold.cyan("Generate API Client\n"));
|
|
967
|
+
const config = await getProjectConfig();
|
|
968
|
+
if (!config) {
|
|
969
|
+
error("Not in a Kolaybase project. Run: kb init");
|
|
970
|
+
process.exit(1);
|
|
971
|
+
}
|
|
972
|
+
const spinner = createSpinner("Generating API client...");
|
|
973
|
+
try {
|
|
974
|
+
const lang = options.lang || "typescript";
|
|
975
|
+
const outputDir = options.output || "lib";
|
|
976
|
+
if (lang === "typescript") {
|
|
977
|
+
await generateTsClient(config, outputDir);
|
|
978
|
+
} else if (lang === "javascript") {
|
|
979
|
+
await generateJsClient(config, outputDir);
|
|
980
|
+
} else if (lang === "python") {
|
|
981
|
+
await generatePyClient(config, outputDir);
|
|
982
|
+
} else {
|
|
983
|
+
throw new Error(`Unsupported language: ${lang}`);
|
|
984
|
+
}
|
|
985
|
+
spinner.succeed("Client generated successfully");
|
|
986
|
+
success(`Written to ${chalk11.cyan(outputDir)}`);
|
|
987
|
+
} catch (err) {
|
|
988
|
+
spinner.fail("Failed to generate client");
|
|
989
|
+
handleApiError(err);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
async function generateTsClient(config, outputDir) {
|
|
993
|
+
const clientContent = `// Generated by Kolaybase CLI
|
|
994
|
+
import axios, { AxiosInstance } from 'axios';
|
|
995
|
+
|
|
996
|
+
export interface KolaybaseConfig {
|
|
997
|
+
url?: string;
|
|
998
|
+
anonKey?: string;
|
|
999
|
+
serviceKey?: string;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
export class KolaybaseClient {
|
|
1003
|
+
private client: AxiosInstance;
|
|
1004
|
+
private projectId: string;
|
|
1005
|
+
|
|
1006
|
+
constructor(config: KolaybaseConfig = {}) {
|
|
1007
|
+
this.projectId = '${config.projectId}';
|
|
1008
|
+
|
|
1009
|
+
this.client = axios.create({
|
|
1010
|
+
baseURL: config.url || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000',
|
|
1011
|
+
headers: {
|
|
1012
|
+
'Content-Type': 'application/json',
|
|
1013
|
+
'Authorization': \`Bearer \${config.anonKey || process.env.ANON_KEY}\`,
|
|
1014
|
+
},
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// Execute raw SQL
|
|
1019
|
+
async sql<T = any>(query: string): Promise<T[]> {
|
|
1020
|
+
const { data } = await this.client.post('/sql/execute', {
|
|
1021
|
+
projectId: this.projectId,
|
|
1022
|
+
query,
|
|
1023
|
+
});
|
|
1024
|
+
return data.rows;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// Table operations
|
|
1028
|
+
table<T = any>(tableName: string) {
|
|
1029
|
+
return {
|
|
1030
|
+
select: async (columns = '*', options?: {
|
|
1031
|
+
where?: Record<string, any>;
|
|
1032
|
+
limit?: number;
|
|
1033
|
+
offset?: number;
|
|
1034
|
+
orderBy?: string;
|
|
1035
|
+
}): Promise<T[]> => {
|
|
1036
|
+
let query = \`SELECT \${columns} FROM \${tableName}\`;
|
|
1037
|
+
|
|
1038
|
+
if (options?.where) {
|
|
1039
|
+
const conditions = Object.entries(options.where)
|
|
1040
|
+
.map(([k, v]) => \`\${k} = '\${v}'\`)
|
|
1041
|
+
.join(' AND ');
|
|
1042
|
+
query += \` WHERE \${conditions}\`;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
if (options?.orderBy) {
|
|
1046
|
+
query += \` ORDER BY \${options.orderBy}\`;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
if (options?.limit) {
|
|
1050
|
+
query += \` LIMIT \${options.limit}\`;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
if (options?.offset) {
|
|
1054
|
+
query += \` OFFSET \${options.offset}\`;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
return this.sql<T>(query);
|
|
1058
|
+
},
|
|
1059
|
+
|
|
1060
|
+
insert: async (data: Partial<T> | Partial<T>[]): Promise<T[]> => {
|
|
1061
|
+
const rows = Array.isArray(data) ? data : [data];
|
|
1062
|
+
const keys = Object.keys(rows[0]);
|
|
1063
|
+
const values = rows.map(row =>
|
|
1064
|
+
\`(\${keys.map(k => \`'\${(row as any)[k]}'\`).join(', ')})\`
|
|
1065
|
+
).join(', ');
|
|
1066
|
+
|
|
1067
|
+
const query = \`
|
|
1068
|
+
INSERT INTO \${tableName} (\${keys.join(', ')})
|
|
1069
|
+
VALUES \${values}
|
|
1070
|
+
RETURNING *
|
|
1071
|
+
\`;
|
|
1072
|
+
|
|
1073
|
+
return this.sql<T>(query);
|
|
1074
|
+
},
|
|
1075
|
+
|
|
1076
|
+
update: async (data: Partial<T>, where: Record<string, any>): Promise<T[]> => {
|
|
1077
|
+
const sets = Object.entries(data)
|
|
1078
|
+
.map(([k, v]) => \`\${k} = '\${v}'\`)
|
|
1079
|
+
.join(', ');
|
|
1080
|
+
|
|
1081
|
+
const conditions = Object.entries(where)
|
|
1082
|
+
.map(([k, v]) => \`\${k} = '\${v}'\`)
|
|
1083
|
+
.join(' AND ');
|
|
1084
|
+
|
|
1085
|
+
const query = \`
|
|
1086
|
+
UPDATE \${tableName}
|
|
1087
|
+
SET \${sets}
|
|
1088
|
+
WHERE \${conditions}
|
|
1089
|
+
RETURNING *
|
|
1090
|
+
\`;
|
|
1091
|
+
|
|
1092
|
+
return this.sql<T>(query);
|
|
1093
|
+
},
|
|
1094
|
+
|
|
1095
|
+
delete: async (where: Record<string, any>): Promise<T[]> => {
|
|
1096
|
+
const conditions = Object.entries(where)
|
|
1097
|
+
.map(([k, v]) => \`\${k} = '\${v}'\`)
|
|
1098
|
+
.join(' AND ');
|
|
1099
|
+
|
|
1100
|
+
const query = \`DELETE FROM \${tableName} WHERE \${conditions} RETURNING *\`;
|
|
1101
|
+
return this.sql<T>(query);
|
|
1102
|
+
},
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
export function createClient(config?: KolaybaseConfig): KolaybaseClient {
|
|
1108
|
+
return new KolaybaseClient(config);
|
|
1109
|
+
}
|
|
1110
|
+
`;
|
|
1111
|
+
await fs4.mkdir(outputDir, { recursive: true });
|
|
1112
|
+
await fs4.writeFile(path5.join(outputDir, "kolaybase.ts"), clientContent);
|
|
1113
|
+
}
|
|
1114
|
+
async function generateJsClient(config, outputDir) {
|
|
1115
|
+
const clientContent = `// Generated by Kolaybase CLI
|
|
1116
|
+
const axios = require('axios');
|
|
1117
|
+
|
|
1118
|
+
class KolaybaseClient {
|
|
1119
|
+
constructor(config = {}) {
|
|
1120
|
+
this.projectId = '${config.projectId}';
|
|
1121
|
+
|
|
1122
|
+
this.client = axios.create({
|
|
1123
|
+
baseURL: config.url || process.env.API_URL || 'http://localhost:4000',
|
|
1124
|
+
headers: {
|
|
1125
|
+
'Content-Type': 'application/json',
|
|
1126
|
+
'Authorization': \`Bearer \${config.anonKey || process.env.ANON_KEY}\`,
|
|
1127
|
+
},
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
async sql(query) {
|
|
1132
|
+
const { data } = await this.client.post('/sql/execute', {
|
|
1133
|
+
projectId: this.projectId,
|
|
1134
|
+
query,
|
|
1135
|
+
});
|
|
1136
|
+
return data.rows;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
table(tableName) {
|
|
1140
|
+
return {
|
|
1141
|
+
select: async (columns = '*', options = {}) => {
|
|
1142
|
+
let query = \`SELECT \${columns} FROM \${tableName}\`;
|
|
1143
|
+
|
|
1144
|
+
if (options.where) {
|
|
1145
|
+
const conditions = Object.entries(options.where)
|
|
1146
|
+
.map(([k, v]) => \`\${k} = '\${v}'\`)
|
|
1147
|
+
.join(' AND ');
|
|
1148
|
+
query += \` WHERE \${conditions}\`;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
return this.sql(query);
|
|
1152
|
+
},
|
|
1153
|
+
|
|
1154
|
+
insert: async (data) => {
|
|
1155
|
+
const rows = Array.isArray(data) ? data : [data];
|
|
1156
|
+
const keys = Object.keys(rows[0]);
|
|
1157
|
+
const values = rows.map(row =>
|
|
1158
|
+
\`(\${keys.map(k => \`'\${row[k]}'\`).join(', ')})\`
|
|
1159
|
+
).join(', ');
|
|
1160
|
+
|
|
1161
|
+
const query = \`INSERT INTO \${tableName} (\${keys.join(', ')}) VALUES \${values} RETURNING *\`;
|
|
1162
|
+
return this.sql(query);
|
|
1163
|
+
},
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function createClient(config) {
|
|
1169
|
+
return new KolaybaseClient(config);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
module.exports = { KolaybaseClient, createClient };
|
|
1173
|
+
`;
|
|
1174
|
+
await fs4.mkdir(outputDir, { recursive: true });
|
|
1175
|
+
await fs4.writeFile(path5.join(outputDir, "kolaybase.js"), clientContent);
|
|
1176
|
+
}
|
|
1177
|
+
async function generatePyClient(config, outputDir) {
|
|
1178
|
+
const clientContent = `# Generated by Kolaybase CLI
|
|
1179
|
+
import os
|
|
1180
|
+
import requests
|
|
1181
|
+
from typing import List, Dict, Any, Optional
|
|
1182
|
+
|
|
1183
|
+
class KolaybaseClient:
|
|
1184
|
+
def __init__(self, config: Optional[Dict[str, str]] = None):
|
|
1185
|
+
config = config or {}
|
|
1186
|
+
self.project_id = '${config.projectId}'
|
|
1187
|
+
self.base_url = config.get('url', os.getenv('API_URL', 'http://localhost:4000'))
|
|
1188
|
+
self.anon_key = config.get('anon_key', os.getenv('ANON_KEY'))
|
|
1189
|
+
|
|
1190
|
+
self.session = requests.Session()
|
|
1191
|
+
self.session.headers.update({
|
|
1192
|
+
'Content-Type': 'application/json',
|
|
1193
|
+
'Authorization': f'Bearer {self.anon_key}'
|
|
1194
|
+
})
|
|
1195
|
+
|
|
1196
|
+
def sql(self, query: str) -> List[Dict[str, Any]]:
|
|
1197
|
+
response = self.session.post(
|
|
1198
|
+
f'{self.base_url}/sql/execute',
|
|
1199
|
+
json={'projectId': self.project_id, 'query': query}
|
|
1200
|
+
)
|
|
1201
|
+
response.raise_for_status()
|
|
1202
|
+
return response.json()['rows']
|
|
1203
|
+
|
|
1204
|
+
def table(self, table_name: str):
|
|
1205
|
+
class TableOperations:
|
|
1206
|
+
def __init__(self, client, table):
|
|
1207
|
+
self.client = client
|
|
1208
|
+
self.table = table
|
|
1209
|
+
|
|
1210
|
+
def select(self, columns='*', where=None, limit=None):
|
|
1211
|
+
query = f'SELECT {columns} FROM {self.table}'
|
|
1212
|
+
|
|
1213
|
+
if where:
|
|
1214
|
+
conditions = ' AND '.join([f"{k} = '{v}'" for k, v in where.items()])
|
|
1215
|
+
query += f' WHERE {conditions}'
|
|
1216
|
+
|
|
1217
|
+
if limit:
|
|
1218
|
+
query += f' LIMIT {limit}'
|
|
1219
|
+
|
|
1220
|
+
return self.client.sql(query)
|
|
1221
|
+
|
|
1222
|
+
return TableOperations(self, table_name)
|
|
1223
|
+
|
|
1224
|
+
def create_client(config: Optional[Dict[str, str]] = None) -> KolaybaseClient:
|
|
1225
|
+
return KolaybaseClient(config)
|
|
1226
|
+
`;
|
|
1227
|
+
await fs4.mkdir(outputDir, { recursive: true });
|
|
1228
|
+
await fs4.writeFile(path5.join(outputDir, "kolaybase.py"), clientContent);
|
|
1229
|
+
}
|
|
1230
|
+
function toPascalCase(str) {
|
|
1231
|
+
return str.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
1232
|
+
}
|
|
1233
|
+
function pgTypeToTs(pgType) {
|
|
1234
|
+
const typeMap = {
|
|
1235
|
+
"integer": "number",
|
|
1236
|
+
"bigint": "number",
|
|
1237
|
+
"smallint": "number",
|
|
1238
|
+
"decimal": "number",
|
|
1239
|
+
"numeric": "number",
|
|
1240
|
+
"real": "number",
|
|
1241
|
+
"double precision": "number",
|
|
1242
|
+
"serial": "number",
|
|
1243
|
+
"bigserial": "number",
|
|
1244
|
+
"character varying": "string",
|
|
1245
|
+
"varchar": "string",
|
|
1246
|
+
"character": "string",
|
|
1247
|
+
"char": "string",
|
|
1248
|
+
"text": "string",
|
|
1249
|
+
"boolean": "boolean",
|
|
1250
|
+
"date": "string",
|
|
1251
|
+
"timestamp": "string",
|
|
1252
|
+
"timestamp without time zone": "string",
|
|
1253
|
+
"timestamp with time zone": "string",
|
|
1254
|
+
"time": "string",
|
|
1255
|
+
"json": "any",
|
|
1256
|
+
"jsonb": "any",
|
|
1257
|
+
"uuid": "string",
|
|
1258
|
+
"bytea": "Buffer"
|
|
1259
|
+
};
|
|
1260
|
+
return typeMap[pgType.toLowerCase()] || "any";
|
|
1261
|
+
}
|
|
1262
|
+
var init_gen = __esm({
|
|
1263
|
+
"src/commands/gen.ts"() {
|
|
1264
|
+
"use strict";
|
|
1265
|
+
init_esm_shims();
|
|
1266
|
+
init_api();
|
|
1267
|
+
init_config();
|
|
1268
|
+
init_ui();
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
// src/commands/secrets.ts
|
|
1273
|
+
var secrets_exports = {};
|
|
1274
|
+
__export(secrets_exports, {
|
|
1275
|
+
listSecrets: () => listSecrets,
|
|
1276
|
+
setSecret: () => setSecret,
|
|
1277
|
+
unsetSecret: () => unsetSecret
|
|
1278
|
+
});
|
|
1279
|
+
import chalk12 from "chalk";
|
|
1280
|
+
async function listSecrets() {
|
|
1281
|
+
console.log(chalk12.bold.cyan("Environment Secrets\n"));
|
|
1282
|
+
const config = await getProjectConfig();
|
|
1283
|
+
if (!config) {
|
|
1284
|
+
error("Not in a Kolaybase project. Run: kb init");
|
|
1285
|
+
process.exit(1);
|
|
1286
|
+
}
|
|
1287
|
+
try {
|
|
1288
|
+
const env = await getLocalEnv();
|
|
1289
|
+
if (Object.keys(env).length === 0) {
|
|
1290
|
+
info("No secrets configured");
|
|
1291
|
+
console.log();
|
|
1292
|
+
console.log(chalk12.gray("Add a secret with:"), chalk12.cyan("kb secrets set KEY VALUE"));
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const masked = Object.entries(env).reduce((acc, [key, value]) => {
|
|
1296
|
+
const sensitive = ["PASSWORD", "SECRET", "KEY", "TOKEN"].some(
|
|
1297
|
+
(s) => key.toUpperCase().includes(s)
|
|
1298
|
+
);
|
|
1299
|
+
acc[key] = sensitive ? maskValue(value) : value;
|
|
1300
|
+
return acc;
|
|
1301
|
+
}, {});
|
|
1302
|
+
printKeyValue(masked);
|
|
1303
|
+
console.log();
|
|
1304
|
+
console.log(chalk12.gray(`Total: ${Object.keys(env).length} secret(s)`));
|
|
1305
|
+
} catch (err) {
|
|
1306
|
+
error(`Failed to list secrets: ${err.message}`);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
async function setSecret(key, value) {
|
|
1310
|
+
console.log(chalk12.bold.cyan("Set Secret\n"));
|
|
1311
|
+
const config = await getProjectConfig();
|
|
1312
|
+
if (!config) {
|
|
1313
|
+
error("Not in a Kolaybase project. Run: kb init");
|
|
1314
|
+
process.exit(1);
|
|
1315
|
+
}
|
|
1316
|
+
try {
|
|
1317
|
+
await setLocalEnv(key, value);
|
|
1318
|
+
success(`Secret ${chalk12.cyan(key)} set successfully`);
|
|
1319
|
+
console.log();
|
|
1320
|
+
info("Secret saved to .env file");
|
|
1321
|
+
} catch (err) {
|
|
1322
|
+
error(`Failed to set secret: ${err.message}`);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
async function unsetSecret(key) {
|
|
1326
|
+
console.log(chalk12.bold.cyan("Remove Secret\n"));
|
|
1327
|
+
const config = await getProjectConfig();
|
|
1328
|
+
if (!config) {
|
|
1329
|
+
error("Not in a Kolaybase project. Run: kb init");
|
|
1330
|
+
process.exit(1);
|
|
1331
|
+
}
|
|
1332
|
+
try {
|
|
1333
|
+
await unsetLocalEnv(key);
|
|
1334
|
+
success(`Secret ${chalk12.cyan(key)} removed successfully`);
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
error(`Failed to remove secret: ${err.message}`);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
function maskValue(value) {
|
|
1340
|
+
if (value.length <= 8) {
|
|
1341
|
+
return "*".repeat(value.length);
|
|
1342
|
+
}
|
|
1343
|
+
return value.substring(0, 4) + "*".repeat(value.length - 8) + value.substring(value.length - 4);
|
|
1344
|
+
}
|
|
1345
|
+
var init_secrets = __esm({
|
|
1346
|
+
"src/commands/secrets.ts"() {
|
|
1347
|
+
"use strict";
|
|
1348
|
+
init_esm_shims();
|
|
1349
|
+
init_config();
|
|
1350
|
+
init_ui();
|
|
1351
|
+
}
|
|
1352
|
+
});
|
|
1353
|
+
|
|
1354
|
+
// src/index.ts
|
|
1355
|
+
init_esm_shims();
|
|
1356
|
+
import { Command } from "commander";
|
|
1357
|
+
|
|
1358
|
+
// src/commands/login.ts
|
|
1359
|
+
init_esm_shims();
|
|
1360
|
+
init_api();
|
|
1361
|
+
init_config();
|
|
1362
|
+
init_ui();
|
|
1363
|
+
import inquirer from "inquirer";
|
|
1364
|
+
import chalk3 from "chalk";
|
|
1365
|
+
async function loginCommand(options) {
|
|
1366
|
+
printLogo();
|
|
1367
|
+
if (options.apiUrl) {
|
|
1368
|
+
setApiUrl(options.apiUrl);
|
|
1369
|
+
}
|
|
1370
|
+
const answers = await inquirer.prompt([
|
|
1371
|
+
{
|
|
1372
|
+
type: "input",
|
|
1373
|
+
name: "username",
|
|
1374
|
+
message: "Username:",
|
|
1375
|
+
validate: (v) => v.length > 0 || "Required"
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
type: "password",
|
|
1379
|
+
name: "password",
|
|
1380
|
+
message: "Password:",
|
|
1381
|
+
mask: "*",
|
|
1382
|
+
validate: (v) => v.length > 0 || "Required"
|
|
1383
|
+
}
|
|
1384
|
+
]);
|
|
1385
|
+
const spinner = createSpinner("Authenticating\u2026");
|
|
1386
|
+
try {
|
|
1387
|
+
const data = await apiClient.login(answers.username, answers.password);
|
|
1388
|
+
setAccessToken(data.accessToken);
|
|
1389
|
+
setRefreshToken(data.refreshToken);
|
|
1390
|
+
setUserConfig({ username: answers.username });
|
|
1391
|
+
spinner.succeed("Logged in");
|
|
1392
|
+
console.log();
|
|
1393
|
+
success(`Welcome, ${chalk3.cyan(answers.username)}`);
|
|
1394
|
+
console.log(chalk3.gray(" Run kb init to create a project or kb link to connect to one"));
|
|
1395
|
+
} catch (err) {
|
|
1396
|
+
spinner.fail("Authentication failed");
|
|
1397
|
+
handleApiError(err);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// src/commands/init.ts
|
|
1402
|
+
init_esm_shims();
|
|
1403
|
+
init_api();
|
|
1404
|
+
init_config();
|
|
1405
|
+
init_ui();
|
|
1406
|
+
import inquirer2 from "inquirer";
|
|
1407
|
+
import chalk4 from "chalk";
|
|
1408
|
+
import path3 from "path";
|
|
1409
|
+
async function initCommand(options) {
|
|
1410
|
+
if (!isLoggedIn()) {
|
|
1411
|
+
error("Not logged in. Run: kb login");
|
|
1412
|
+
process.exit(1);
|
|
1413
|
+
}
|
|
1414
|
+
const existingConfig = await getProjectConfig();
|
|
1415
|
+
if (existingConfig?.projectId) {
|
|
1416
|
+
warning(`This directory is already linked to project "${existingConfig.projectName}"`);
|
|
1417
|
+
console.log(chalk4.gray(` ID: ${existingConfig.projectId}`));
|
|
1418
|
+
console.log();
|
|
1419
|
+
console.log(chalk4.gray(" To link to a different project run: kb link"));
|
|
1420
|
+
console.log(chalk4.gray(" To unlink first: kb unlink"));
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
const spinner = createSpinner("Loading teams\u2026");
|
|
1424
|
+
try {
|
|
1425
|
+
const teams = await apiClient.getTeams();
|
|
1426
|
+
spinner.stop();
|
|
1427
|
+
if (!teams?.length) {
|
|
1428
|
+
error("No teams found. Create an account first via the Admin UI.");
|
|
1429
|
+
process.exit(1);
|
|
1430
|
+
}
|
|
1431
|
+
const answers = await inquirer2.prompt([
|
|
1432
|
+
{
|
|
1433
|
+
type: "list",
|
|
1434
|
+
name: "teamId",
|
|
1435
|
+
message: "Team:",
|
|
1436
|
+
choices: teams.map((t) => ({
|
|
1437
|
+
name: t.personalForUserId ? `${t.name} ${chalk4.gray("(personal)")}` : t.name,
|
|
1438
|
+
value: t.id
|
|
1439
|
+
})),
|
|
1440
|
+
when: teams.length > 1
|
|
1441
|
+
},
|
|
1442
|
+
{
|
|
1443
|
+
type: "input",
|
|
1444
|
+
name: "name",
|
|
1445
|
+
message: "Project name:",
|
|
1446
|
+
default: options.name || path3.basename(process.cwd()),
|
|
1447
|
+
validate: (v) => v.trim().length > 0 || "Required"
|
|
1448
|
+
},
|
|
1449
|
+
{
|
|
1450
|
+
type: "input",
|
|
1451
|
+
name: "description",
|
|
1452
|
+
message: "Description (optional):"
|
|
1453
|
+
}
|
|
1454
|
+
]);
|
|
1455
|
+
const teamId = answers.teamId || teams[0].id;
|
|
1456
|
+
const createSpinnerInstance = createSpinner("Creating project\u2026");
|
|
1457
|
+
const project = await apiClient.createProject({
|
|
1458
|
+
name: answers.name,
|
|
1459
|
+
description: answers.description || void 0,
|
|
1460
|
+
teamId
|
|
1461
|
+
});
|
|
1462
|
+
createSpinnerInstance.succeed("Project created");
|
|
1463
|
+
await setProjectConfig({
|
|
1464
|
+
projectId: project.id,
|
|
1465
|
+
projectName: project.name,
|
|
1466
|
+
projectSlug: project.slug,
|
|
1467
|
+
teamId: project.teamId,
|
|
1468
|
+
linkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1469
|
+
});
|
|
1470
|
+
await writeEnvFile(project);
|
|
1471
|
+
console.log();
|
|
1472
|
+
printHeader("Project ready");
|
|
1473
|
+
console.log();
|
|
1474
|
+
console.log(` ${chalk4.gray("Name")} ${project.name}`);
|
|
1475
|
+
console.log(` ${chalk4.gray("ID")} ${project.id}`);
|
|
1476
|
+
console.log(` ${chalk4.gray("Database")} ${project.dbName}`);
|
|
1477
|
+
console.log(` ${chalk4.gray("Realm")} ${project.keycloakRealm}`);
|
|
1478
|
+
console.log();
|
|
1479
|
+
success("Configuration saved to .kolaybase/config.json");
|
|
1480
|
+
success("Credentials saved to .env");
|
|
1481
|
+
console.log();
|
|
1482
|
+
console.log(chalk4.gray(" Next steps:"));
|
|
1483
|
+
console.log(chalk4.gray(" kb status \u2014 view full connection details"));
|
|
1484
|
+
console.log(chalk4.gray(" kb inspect \u2014 list database tables"));
|
|
1485
|
+
console.log(chalk4.gray(" kb gen types \u2014 generate TypeScript types"));
|
|
1486
|
+
console.log(chalk4.gray(" kb db push \u2014 push a local schema"));
|
|
1487
|
+
} catch (err) {
|
|
1488
|
+
handleApiError(err);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
// src/commands/projects.ts
|
|
1493
|
+
init_esm_shims();
|
|
1494
|
+
init_api();
|
|
1495
|
+
init_config();
|
|
1496
|
+
init_ui();
|
|
1497
|
+
import inquirer3 from "inquirer";
|
|
1498
|
+
import chalk5 from "chalk";
|
|
1499
|
+
async function projectsCommand() {
|
|
1500
|
+
if (!isLoggedIn()) {
|
|
1501
|
+
error("You must be logged in to view projects");
|
|
1502
|
+
console.log(chalk5.gray("Run: kb login"));
|
|
1503
|
+
process.exit(1);
|
|
1504
|
+
}
|
|
1505
|
+
const spinner = createSpinner("Loading projects...");
|
|
1506
|
+
try {
|
|
1507
|
+
const teams = await apiClient.getTeams();
|
|
1508
|
+
const team = teams[0];
|
|
1509
|
+
const projects = await apiClient.getProjects(team.id);
|
|
1510
|
+
spinner.stop();
|
|
1511
|
+
if (!projects || projects.length === 0) {
|
|
1512
|
+
info("No projects found");
|
|
1513
|
+
console.log();
|
|
1514
|
+
console.log(chalk5.gray("Create your first project with:"), chalk5.cyan("kb init"));
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
printHeader("Your Projects");
|
|
1518
|
+
console.log();
|
|
1519
|
+
const rows = projects.map((project) => [
|
|
1520
|
+
chalk5.cyan(project.name),
|
|
1521
|
+
project.slug,
|
|
1522
|
+
project.status,
|
|
1523
|
+
new Date(project.createdAt).toLocaleDateString(),
|
|
1524
|
+
project.id
|
|
1525
|
+
]);
|
|
1526
|
+
printTable(
|
|
1527
|
+
["Name", "Slug", "Status", "Created", "ID"],
|
|
1528
|
+
rows
|
|
1529
|
+
);
|
|
1530
|
+
console.log();
|
|
1531
|
+
console.log(chalk5.gray(`Total: ${projects.length} project(s)`));
|
|
1532
|
+
} catch (err) {
|
|
1533
|
+
spinner.fail("Failed to load projects");
|
|
1534
|
+
handleApiError(err);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
async function createProject(options) {
|
|
1538
|
+
if (!isLoggedIn()) {
|
|
1539
|
+
error("You must be logged in to create a project");
|
|
1540
|
+
console.log(chalk5.gray("Run: kb login"));
|
|
1541
|
+
process.exit(1);
|
|
1542
|
+
}
|
|
1543
|
+
try {
|
|
1544
|
+
const teams = await apiClient.getTeams();
|
|
1545
|
+
const team = teams[0];
|
|
1546
|
+
let name = options.name;
|
|
1547
|
+
let description = options.description;
|
|
1548
|
+
if (!name) {
|
|
1549
|
+
const answers = await inquirer3.prompt([
|
|
1550
|
+
{
|
|
1551
|
+
type: "input",
|
|
1552
|
+
name: "name",
|
|
1553
|
+
message: "Project name:",
|
|
1554
|
+
validate: (input) => input.length > 0 || "Project name is required"
|
|
1555
|
+
},
|
|
1556
|
+
{
|
|
1557
|
+
type: "input",
|
|
1558
|
+
name: "description",
|
|
1559
|
+
message: "Description (optional):"
|
|
1560
|
+
}
|
|
1561
|
+
]);
|
|
1562
|
+
name = answers.name;
|
|
1563
|
+
description = answers.description;
|
|
1564
|
+
}
|
|
1565
|
+
const spinner = createSpinner("Creating project...");
|
|
1566
|
+
const project = await apiClient.createProject({
|
|
1567
|
+
name,
|
|
1568
|
+
description,
|
|
1569
|
+
teamId: team.id
|
|
1570
|
+
});
|
|
1571
|
+
spinner.succeed("Project created successfully");
|
|
1572
|
+
console.log();
|
|
1573
|
+
console.log(chalk5.gray("Name:"), chalk5.cyan(project.name));
|
|
1574
|
+
console.log(chalk5.gray("ID:"), project.id);
|
|
1575
|
+
console.log(chalk5.gray("Slug:"), project.slug);
|
|
1576
|
+
console.log(chalk5.gray("Database:"), project.dbName);
|
|
1577
|
+
console.log();
|
|
1578
|
+
console.log(chalk5.gray("To start working with this project:"));
|
|
1579
|
+
console.log(chalk5.cyan(" kb init --link"));
|
|
1580
|
+
} catch (err) {
|
|
1581
|
+
handleApiError(err);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
async function deleteProject(projectId) {
|
|
1585
|
+
if (!isLoggedIn()) {
|
|
1586
|
+
error("You must be logged in to delete a project");
|
|
1587
|
+
console.log(chalk5.gray("Run: kb login"));
|
|
1588
|
+
process.exit(1);
|
|
1589
|
+
}
|
|
1590
|
+
try {
|
|
1591
|
+
const spinner = createSpinner("Loading project...");
|
|
1592
|
+
const project = await apiClient.getProject(projectId);
|
|
1593
|
+
spinner.stop();
|
|
1594
|
+
console.log();
|
|
1595
|
+
console.log(chalk5.yellow("\u26A0 WARNING: This action cannot be undone!"));
|
|
1596
|
+
console.log();
|
|
1597
|
+
console.log(chalk5.gray("Project:"), chalk5.cyan(project.name));
|
|
1598
|
+
console.log(chalk5.gray("Database:"), project.dbName);
|
|
1599
|
+
console.log();
|
|
1600
|
+
const answers = await inquirer3.prompt([
|
|
1601
|
+
{
|
|
1602
|
+
type: "confirm",
|
|
1603
|
+
name: "confirm",
|
|
1604
|
+
message: "Are you sure you want to delete this project?",
|
|
1605
|
+
default: false
|
|
1606
|
+
},
|
|
1607
|
+
{
|
|
1608
|
+
type: "input",
|
|
1609
|
+
name: "confirmName",
|
|
1610
|
+
message: `Type the project name "${project.name}" to confirm:`,
|
|
1611
|
+
when: (answers2) => answers2.confirm,
|
|
1612
|
+
validate: (input) => input === project.name || `You must type "${project.name}" exactly`
|
|
1613
|
+
}
|
|
1614
|
+
]);
|
|
1615
|
+
if (!answers.confirm) {
|
|
1616
|
+
info("Deletion cancelled");
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
const deleteSpinner = createSpinner("Deleting project...");
|
|
1620
|
+
await apiClient.deleteProject(projectId);
|
|
1621
|
+
deleteSpinner.succeed("Project deleted successfully");
|
|
1622
|
+
} catch (err) {
|
|
1623
|
+
handleApiError(err);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
// src/commands/status.ts
|
|
1628
|
+
init_esm_shims();
|
|
1629
|
+
init_api();
|
|
1630
|
+
init_config();
|
|
1631
|
+
init_ui();
|
|
1632
|
+
import chalk6 from "chalk";
|
|
1633
|
+
async function statusCommand(options) {
|
|
1634
|
+
const config = await getProjectConfig();
|
|
1635
|
+
if (!config?.projectId) {
|
|
1636
|
+
error("Not linked to a project. Run: kb link or kb init");
|
|
1637
|
+
process.exit(1);
|
|
1638
|
+
}
|
|
1639
|
+
if (!isLoggedIn()) {
|
|
1640
|
+
error("Not logged in. Run: kb login");
|
|
1641
|
+
process.exit(1);
|
|
1642
|
+
}
|
|
1643
|
+
const spinner = createSpinner("Fetching project details\u2026");
|
|
1644
|
+
try {
|
|
1645
|
+
const project = await apiClient.getProject(config.projectId);
|
|
1646
|
+
spinner.stop();
|
|
1647
|
+
const mask = (val) => options.showKeys ? val : val.slice(0, 6) + "\u2022".repeat(Math.max(0, val.length - 10)) + val.slice(-4);
|
|
1648
|
+
printHeader(`Project: ${project.name}`);
|
|
1649
|
+
console.log();
|
|
1650
|
+
section("General");
|
|
1651
|
+
row("Project ID", project.id);
|
|
1652
|
+
row("Name", project.name);
|
|
1653
|
+
row("Slug", project.slug);
|
|
1654
|
+
row("Status", statusBadge(project.status));
|
|
1655
|
+
row("Created", new Date(project.createdAt).toLocaleString());
|
|
1656
|
+
console.log();
|
|
1657
|
+
section("Database");
|
|
1658
|
+
row("Host", project.dbHost);
|
|
1659
|
+
row("Port", String(project.dbPort));
|
|
1660
|
+
row("Database", project.dbName);
|
|
1661
|
+
row("User", project.dbUser);
|
|
1662
|
+
row("Password", mask(project.dbPassword));
|
|
1663
|
+
console.log();
|
|
1664
|
+
const connStr = `postgresql://${project.dbUser}:${options.showKeys ? project.dbPassword : "\u2022\u2022\u2022\u2022\u2022\u2022"}@${project.dbHost}:${project.dbPort}/${project.dbName}`;
|
|
1665
|
+
row("Connection string", chalk6.cyan(connStr));
|
|
1666
|
+
console.log();
|
|
1667
|
+
section("Authentication (Keycloak)");
|
|
1668
|
+
row("Realm", project.keycloakRealm);
|
|
1669
|
+
row("Anon Key", mask(project.anonKey));
|
|
1670
|
+
row("Service Key", mask(project.serviceKey));
|
|
1671
|
+
console.log();
|
|
1672
|
+
if (!options.showKeys) {
|
|
1673
|
+
console.log(chalk6.gray(" Tip: use kb status --show-keys to reveal secrets"));
|
|
1674
|
+
}
|
|
1675
|
+
console.log();
|
|
1676
|
+
} catch (err) {
|
|
1677
|
+
spinner.fail("Could not fetch project");
|
|
1678
|
+
handleApiError(err);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
function section(title) {
|
|
1682
|
+
console.log(chalk6.bold(` ${title}`));
|
|
1683
|
+
}
|
|
1684
|
+
function row(label, value) {
|
|
1685
|
+
console.log(` ${chalk6.gray(label.padEnd(20))} ${value}`);
|
|
1686
|
+
}
|
|
1687
|
+
function statusBadge(status) {
|
|
1688
|
+
switch (status) {
|
|
1689
|
+
case "ACTIVE":
|
|
1690
|
+
return chalk6.green("\u25CF ACTIVE");
|
|
1691
|
+
case "PAUSED":
|
|
1692
|
+
return chalk6.yellow("\u25CF PAUSED");
|
|
1693
|
+
case "DELETED":
|
|
1694
|
+
return chalk6.red("\u25CF DELETED");
|
|
1695
|
+
default:
|
|
1696
|
+
return status;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// src/commands/link.ts
|
|
1701
|
+
init_esm_shims();
|
|
1702
|
+
init_api();
|
|
1703
|
+
init_config();
|
|
1704
|
+
init_ui();
|
|
1705
|
+
import inquirer4 from "inquirer";
|
|
1706
|
+
import chalk7 from "chalk";
|
|
1707
|
+
import fs2 from "fs/promises";
|
|
1708
|
+
async function linkCommand(options) {
|
|
1709
|
+
if (!isLoggedIn()) {
|
|
1710
|
+
error("Not logged in. Run: kb login");
|
|
1711
|
+
process.exit(1);
|
|
1712
|
+
}
|
|
1713
|
+
try {
|
|
1714
|
+
let projectId = options.projectId;
|
|
1715
|
+
if (!projectId) {
|
|
1716
|
+
const spinner2 = createSpinner("Loading projects\u2026");
|
|
1717
|
+
const teams = await apiClient.getTeams();
|
|
1718
|
+
let allProjects = [];
|
|
1719
|
+
for (const team of teams) {
|
|
1720
|
+
const projects = await apiClient.getProjects(team.id);
|
|
1721
|
+
allProjects.push(
|
|
1722
|
+
...projects.map((p) => ({ ...p, teamName: team.name }))
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
spinner2.stop();
|
|
1726
|
+
if (!allProjects.length) {
|
|
1727
|
+
error("No projects found. Create one first: kb init");
|
|
1728
|
+
process.exit(1);
|
|
1729
|
+
}
|
|
1730
|
+
const { selected } = await inquirer4.prompt([
|
|
1731
|
+
{
|
|
1732
|
+
type: "list",
|
|
1733
|
+
name: "selected",
|
|
1734
|
+
message: "Select a project to link:",
|
|
1735
|
+
choices: allProjects.map((p) => ({
|
|
1736
|
+
name: `${p.name} ${chalk7.gray(p.slug)} ${chalk7.gray("\u2014 " + p.teamName)}`,
|
|
1737
|
+
value: p.id
|
|
1738
|
+
}))
|
|
1739
|
+
}
|
|
1740
|
+
]);
|
|
1741
|
+
projectId = selected;
|
|
1742
|
+
}
|
|
1743
|
+
const spinner = createSpinner("Linking\u2026");
|
|
1744
|
+
const project = await apiClient.getProject(projectId);
|
|
1745
|
+
await setProjectConfig({
|
|
1746
|
+
projectId: project.id,
|
|
1747
|
+
projectName: project.name,
|
|
1748
|
+
projectSlug: project.slug,
|
|
1749
|
+
teamId: project.teamId,
|
|
1750
|
+
linkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1751
|
+
});
|
|
1752
|
+
await writeEnvFile(project);
|
|
1753
|
+
spinner.succeed(`Linked to ${chalk7.cyan(project.name)}`);
|
|
1754
|
+
console.log();
|
|
1755
|
+
console.log(` ${chalk7.gray("Database")} ${project.dbName}`);
|
|
1756
|
+
console.log(` ${chalk7.gray("Realm")} ${project.keycloakRealm}`);
|
|
1757
|
+
console.log();
|
|
1758
|
+
success("Credentials saved to .env");
|
|
1759
|
+
console.log(chalk7.gray(" Run kb status to see full connection details"));
|
|
1760
|
+
} catch (err) {
|
|
1761
|
+
handleApiError(err);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
async function unlinkProject() {
|
|
1765
|
+
try {
|
|
1766
|
+
await fs2.rm(".kolaybase", { recursive: true, force: true });
|
|
1767
|
+
success("Project unlinked");
|
|
1768
|
+
} catch (err) {
|
|
1769
|
+
error(`Failed to unlink: ${err.message}`);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
// src/commands/logs.ts
|
|
1774
|
+
init_esm_shims();
|
|
1775
|
+
init_api();
|
|
1776
|
+
init_config();
|
|
1777
|
+
init_ui();
|
|
1778
|
+
import chalk8 from "chalk";
|
|
1779
|
+
async function logsCommand(options) {
|
|
1780
|
+
const config = await getProjectConfig();
|
|
1781
|
+
if (!config?.projectId) {
|
|
1782
|
+
error("Not linked to a project. Run: kb link or kb init");
|
|
1783
|
+
process.exit(1);
|
|
1784
|
+
}
|
|
1785
|
+
if (!isLoggedIn()) {
|
|
1786
|
+
error("Not logged in. Run: kb login");
|
|
1787
|
+
process.exit(1);
|
|
1788
|
+
}
|
|
1789
|
+
const limit = parseInt(options.tail || "50", 10);
|
|
1790
|
+
const spinner = createSpinner("Fetching SQL audit logs\u2026");
|
|
1791
|
+
try {
|
|
1792
|
+
const result = await apiClient.executeSQL(
|
|
1793
|
+
config.projectId,
|
|
1794
|
+
`SELECT 1`
|
|
1795
|
+
// quick connectivity test
|
|
1796
|
+
);
|
|
1797
|
+
} catch {
|
|
1798
|
+
}
|
|
1799
|
+
try {
|
|
1800
|
+
const { Pool: Pool4 } = await import("pg");
|
|
1801
|
+
const { getLocalEnv: getLocalEnv2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
1802
|
+
const env = await getLocalEnv2();
|
|
1803
|
+
const host = env.DB_HOST || "localhost";
|
|
1804
|
+
const port = env.DB_PORT || "5432";
|
|
1805
|
+
const platformUrl = `postgresql://kolaybase:kolaybase_secret@${host}:${port}/kolaybase`;
|
|
1806
|
+
const pool = new Pool4({ connectionString: platformUrl });
|
|
1807
|
+
const client = await pool.connect();
|
|
1808
|
+
try {
|
|
1809
|
+
const { rows } = await client.query(
|
|
1810
|
+
`SELECT created_at, user_id, query, row_count, duration, error
|
|
1811
|
+
FROM sql_audit_logs
|
|
1812
|
+
WHERE project_id = $1
|
|
1813
|
+
ORDER BY created_at DESC
|
|
1814
|
+
LIMIT $2`,
|
|
1815
|
+
[config.projectId, limit]
|
|
1816
|
+
);
|
|
1817
|
+
spinner.stop();
|
|
1818
|
+
if (!rows.length) {
|
|
1819
|
+
console.log(chalk8.gray(" No SQL logs yet for this project."));
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
printHeader("SQL Audit Logs");
|
|
1823
|
+
console.log();
|
|
1824
|
+
rows.reverse().forEach((r) => {
|
|
1825
|
+
const ts = new Date(r.created_at).toLocaleString();
|
|
1826
|
+
const dur = r.duration != null ? `${r.duration}ms` : "";
|
|
1827
|
+
const badge = r.error ? chalk8.red("ERR") : chalk8.green("OK ");
|
|
1828
|
+
console.log(` ${chalk8.gray(ts)} ${badge} ${chalk8.gray(dur)}`);
|
|
1829
|
+
console.log(` ${chalk8.cyan(truncate(r.query, 100))}`);
|
|
1830
|
+
if (r.error) {
|
|
1831
|
+
console.log(` ${chalk8.red(r.error)}`);
|
|
1832
|
+
} else if (r.row_count != null) {
|
|
1833
|
+
console.log(chalk8.gray(` ${r.row_count} row(s)`));
|
|
1834
|
+
}
|
|
1835
|
+
console.log();
|
|
1836
|
+
});
|
|
1837
|
+
console.log(chalk8.gray(` Showing ${rows.length} entries`));
|
|
1838
|
+
} finally {
|
|
1839
|
+
client.release();
|
|
1840
|
+
await pool.end();
|
|
1841
|
+
}
|
|
1842
|
+
} catch (err) {
|
|
1843
|
+
spinner.fail("Could not fetch logs");
|
|
1844
|
+
console.log(chalk8.gray(` ${err.message}`));
|
|
1845
|
+
console.log(chalk8.gray(" Make sure the platform database is accessible."));
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
function truncate(s, max) {
|
|
1849
|
+
const oneLine = s.replace(/\s+/g, " ").trim();
|
|
1850
|
+
return oneLine.length > max ? oneLine.slice(0, max - 1) + "\u2026" : oneLine;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// src/index.ts
|
|
1854
|
+
var program = new Command();
|
|
1855
|
+
program.name("kb").description("Kolaybase CLI \u2014 manage your Kolaybase projects").version("0.1.0");
|
|
1856
|
+
program.command("login").description("Authenticate with the Kolaybase platform").option("--api-url <url>", "Platform API URL (default: http://localhost:4000)").action(loginCommand);
|
|
1857
|
+
program.command("init").description("Create a new Kolaybase project and link it to the current directory").option("-n, --name <name>", "Project name").action(initCommand);
|
|
1858
|
+
program.command("link").description("Link current directory to an existing remote project").option("--project-id <id>", "Project ID to link directly").action(linkCommand);
|
|
1859
|
+
program.command("unlink").description("Remove the project link from the current directory").action(unlinkProject);
|
|
1860
|
+
program.command("status").description("Show credentials, keys, and connection info for the linked project").option("--show-keys", "Reveal secret values instead of masking them").action(statusCommand);
|
|
1861
|
+
program.command("projects").alias("list").description("List all projects in your team").action(projectsCommand);
|
|
1862
|
+
program.command("projects:create").description("Create a new project without linking").option("-n, --name <name>", "Project name").option("-d, --description <description>", "Project description").action(createProject);
|
|
1863
|
+
program.command("projects:delete <projectId>").description("Delete a remote project (requires confirmation)").action(deleteProject);
|
|
1864
|
+
var db = program.command("db").description("Manage the project database");
|
|
1865
|
+
db.command("push").description("Push local schema to the project database").action(async () => {
|
|
1866
|
+
const { dbPush: dbPush2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
1867
|
+
await dbPush2();
|
|
1868
|
+
});
|
|
1869
|
+
db.command("pull").description("Introspect the remote database and save schema locally").action(async () => {
|
|
1870
|
+
const { dbPull: dbPull2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
1871
|
+
await dbPull2();
|
|
1872
|
+
});
|
|
1873
|
+
db.command("reset").description("Drop all tables in the project database").option("-f, --force", "Skip confirmation prompt").action(async (options) => {
|
|
1874
|
+
const { dbReset: dbReset2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
1875
|
+
await dbReset2(options);
|
|
1876
|
+
});
|
|
1877
|
+
db.command("seed").description("Run the seed file against the project database").action(async () => {
|
|
1878
|
+
const { dbSeed: dbSeed2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
1879
|
+
await dbSeed2();
|
|
1880
|
+
});
|
|
1881
|
+
db.command("dump").description("Dump the full database schema to SQL").option("-o, --output <file>", "Output file", "schema.sql").action(async (options) => {
|
|
1882
|
+
const { dbDump: dbDump2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
1883
|
+
await dbDump2(options);
|
|
1884
|
+
});
|
|
1885
|
+
program.command("inspect").description("Show database tables, columns, sizes, and row counts").option("-t, --table <name>", "Inspect a specific table").action(async (options) => {
|
|
1886
|
+
const { inspectCommand: inspectCommand2 } = await Promise.resolve().then(() => (init_inspect(), inspect_exports));
|
|
1887
|
+
await inspectCommand2(options);
|
|
1888
|
+
});
|
|
1889
|
+
var gen = program.command("gen").description("Generate code from the project database");
|
|
1890
|
+
gen.command("types").description("Generate TypeScript types from the database schema").option("-o, --output <path>", "Output directory", "./types").action(async (options) => {
|
|
1891
|
+
const { genTypes: genTypes2 } = await Promise.resolve().then(() => (init_gen(), gen_exports));
|
|
1892
|
+
await genTypes2(options);
|
|
1893
|
+
});
|
|
1894
|
+
gen.command("client").description("Generate a ready-to-use API client").option("-l, --lang <language>", "Language: typescript | javascript | python", "typescript").option("-o, --output <path>", "Output directory", "./lib").action(async (options) => {
|
|
1895
|
+
const { genClient: genClient2 } = await Promise.resolve().then(() => (init_gen(), gen_exports));
|
|
1896
|
+
await genClient2(options);
|
|
1897
|
+
});
|
|
1898
|
+
program.command("logs").description("Show SQL audit logs for the linked project").option("-n, --tail <lines>", "Number of recent entries", "50").action(logsCommand);
|
|
1899
|
+
var secrets = program.command("secrets").description("Manage local .env variables for the linked project");
|
|
1900
|
+
secrets.command("list").description("List all variables (sensitive values masked)").action(async () => {
|
|
1901
|
+
const { listSecrets: listSecrets2 } = await Promise.resolve().then(() => (init_secrets(), secrets_exports));
|
|
1902
|
+
await listSecrets2();
|
|
1903
|
+
});
|
|
1904
|
+
secrets.command("set <key> <value>").description("Set or update a variable").action(async (key, value) => {
|
|
1905
|
+
const { setSecret: setSecret2 } = await Promise.resolve().then(() => (init_secrets(), secrets_exports));
|
|
1906
|
+
await setSecret2(key, value);
|
|
1907
|
+
});
|
|
1908
|
+
secrets.command("unset <key>").description("Remove a variable").action(async (key) => {
|
|
1909
|
+
const { unsetSecret: unsetSecret2 } = await Promise.resolve().then(() => (init_secrets(), secrets_exports));
|
|
1910
|
+
await unsetSecret2(key);
|
|
1911
|
+
});
|
|
1912
|
+
program.parse();
|
|
1913
|
+
//# sourceMappingURL=index.js.map
|