ardent-cli 0.0.2 → 0.0.5
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/index.js +642 -236
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import {
|
|
4
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
5
|
+
import { Command as Command6 } from "commander";
|
|
5
6
|
|
|
6
|
-
// src/commands/branch.ts
|
|
7
|
+
// src/commands/branch/index.ts
|
|
7
8
|
import { Command } from "commander";
|
|
8
9
|
|
|
9
10
|
// src/lib/config.ts
|
|
@@ -95,13 +96,43 @@ function clearCurrentBranch() {
|
|
|
95
96
|
delete config.currentBranch;
|
|
96
97
|
saveConfig(config);
|
|
97
98
|
}
|
|
99
|
+
async function bootstrapCache() {
|
|
100
|
+
const token = getToken();
|
|
101
|
+
if (!token) throw new Error("Not authenticated");
|
|
102
|
+
const apiUrl = getApiUrl();
|
|
103
|
+
const headers = {
|
|
104
|
+
"Content-Type": "application/json",
|
|
105
|
+
"Authorization": `Bearer ${token}`
|
|
106
|
+
};
|
|
107
|
+
const [connectorsRes, branchesRes] = await Promise.all([
|
|
108
|
+
fetch(`${apiUrl}/v1/cli/connectors`, { headers }),
|
|
109
|
+
fetch(`${apiUrl}/v1/cli/branches`, { headers })
|
|
110
|
+
]);
|
|
111
|
+
let connectorCount = 0;
|
|
112
|
+
let branchCount = 0;
|
|
113
|
+
if (connectorsRes.ok) {
|
|
114
|
+
const data = await connectorsRes.json();
|
|
115
|
+
const connectors = data.connectors || [];
|
|
116
|
+
setCacheEntry("connectors", connectors);
|
|
117
|
+
connectorCount = connectors.length;
|
|
118
|
+
}
|
|
119
|
+
if (branchesRes.ok) {
|
|
120
|
+
const data = await branchesRes.json();
|
|
121
|
+
const branches = data.branches || [];
|
|
122
|
+
setCacheEntry("branches", branches);
|
|
123
|
+
branchCount = branches.length;
|
|
124
|
+
}
|
|
125
|
+
return { connectors: connectorCount, branches: branchCount };
|
|
126
|
+
}
|
|
98
127
|
|
|
99
128
|
// src/lib/api.ts
|
|
100
129
|
var ApiClient = class {
|
|
101
130
|
getHeaders() {
|
|
102
131
|
const token = getToken();
|
|
103
132
|
if (!token) {
|
|
104
|
-
|
|
133
|
+
const green3 = "\x1B[32m";
|
|
134
|
+
const reset3 = "\x1B[0m";
|
|
135
|
+
console.error(`\u2717 Not authenticated. Run: ${green3}ardent login${reset3}`);
|
|
105
136
|
process.exit(1);
|
|
106
137
|
}
|
|
107
138
|
return {
|
|
@@ -147,6 +178,19 @@ var ApiClient = class {
|
|
|
147
178
|
}
|
|
148
179
|
return response.json();
|
|
149
180
|
}
|
|
181
|
+
async patch(path, body) {
|
|
182
|
+
const url = `${getApiUrl()}${path}`;
|
|
183
|
+
const response = await fetch(url, {
|
|
184
|
+
method: "PATCH",
|
|
185
|
+
headers: this.getHeaders(),
|
|
186
|
+
body: JSON.stringify(body)
|
|
187
|
+
});
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
const text = await response.text();
|
|
190
|
+
throw new Error(`API error ${response.status}: ${text}`);
|
|
191
|
+
}
|
|
192
|
+
return response.json();
|
|
193
|
+
}
|
|
150
194
|
};
|
|
151
195
|
var api = new ApiClient();
|
|
152
196
|
function isNetworkError(err) {
|
|
@@ -159,10 +203,15 @@ function isNetworkError(err) {
|
|
|
159
203
|
}
|
|
160
204
|
return false;
|
|
161
205
|
}
|
|
206
|
+
function isPermissionError(err) {
|
|
207
|
+
if (err instanceof Error) {
|
|
208
|
+
return err.message.includes("API error 403") || err.message.includes("API error 401");
|
|
209
|
+
}
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
162
212
|
|
|
163
|
-
// src/commands/branch.ts
|
|
164
|
-
|
|
165
|
-
branchCommand.command("create <name>").description("Create a new database branch (e.g., ardent branch create my-feature)").option("-s, --service <type>", "Service type", "postgres").action(async (name, options) => {
|
|
213
|
+
// src/commands/branch/create.ts
|
|
214
|
+
async function createAction(name, options) {
|
|
166
215
|
try {
|
|
167
216
|
const job = await api.post("/v1/cli/jobs/create", {
|
|
168
217
|
name
|
|
@@ -204,8 +253,10 @@ ${branch.branch_url}`);
|
|
|
204
253
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
205
254
|
process.exit(1);
|
|
206
255
|
}
|
|
207
|
-
}
|
|
208
|
-
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/commands/branch/list.ts
|
|
259
|
+
async function listAction() {
|
|
209
260
|
let branches = [];
|
|
210
261
|
let fromCache = false;
|
|
211
262
|
let cacheTime = "";
|
|
@@ -242,32 +293,34 @@ branchCommand.command("list").description("List your branches").action(async ()
|
|
|
242
293
|
return;
|
|
243
294
|
}
|
|
244
295
|
const current = getCurrentBranch();
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
const
|
|
296
|
+
const green3 = "\x1B[32m";
|
|
297
|
+
const dim3 = "\x1B[2m";
|
|
298
|
+
const reset3 = "\x1B[0m";
|
|
248
299
|
console.log("Branches:\n");
|
|
249
300
|
for (const branch of branches) {
|
|
250
301
|
const isCurrent = branch.name === current;
|
|
251
302
|
const icon = branch.status === "active" || branch.status === "active_degraded" ? "\u25CF" : "\u25CB";
|
|
252
303
|
if (isCurrent) {
|
|
253
|
-
console.log(`${
|
|
304
|
+
console.log(`${green3}* ${icon} ${branch.name}${reset3}`);
|
|
254
305
|
if (branch.branch_url) {
|
|
255
|
-
console.log(`${
|
|
306
|
+
console.log(`${green3} ${branch.branch_url}${reset3}`);
|
|
256
307
|
}
|
|
257
308
|
} else {
|
|
258
309
|
console.log(` ${icon} ${branch.name}`);
|
|
259
310
|
if (branch.branch_url) {
|
|
260
|
-
console.log(`${
|
|
311
|
+
console.log(`${dim3} ${branch.branch_url}${reset3}`);
|
|
261
312
|
}
|
|
262
313
|
}
|
|
263
314
|
console.log();
|
|
264
315
|
}
|
|
265
|
-
}
|
|
266
|
-
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/commands/branch/info.ts
|
|
319
|
+
function infoAction(name) {
|
|
267
320
|
const branchName = name || getCurrentBranch();
|
|
268
321
|
if (!branchName) {
|
|
269
322
|
console.error("\u2717 No branch specified and no current branch set");
|
|
270
|
-
console.log(" Run: ardent switch <name> or specify a branch name");
|
|
323
|
+
console.log(" Run: ardent branch switch <name> or specify a branch name");
|
|
271
324
|
return;
|
|
272
325
|
}
|
|
273
326
|
const cached = getCacheEntry("branches");
|
|
@@ -288,8 +341,10 @@ branchCommand.command("info [name]").description("Show branch details (defaults
|
|
|
288
341
|
console.log(`
|
|
289
342
|
URL: ${branch.branch_url}`);
|
|
290
343
|
}
|
|
291
|
-
}
|
|
292
|
-
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/commands/branch/delete.ts
|
|
347
|
+
async function deleteAction(name) {
|
|
293
348
|
const cached = getCacheEntry("branches");
|
|
294
349
|
const branch = cached?.data.find((cachedBranch) => cachedBranch.name === name);
|
|
295
350
|
if (!branch) {
|
|
@@ -313,118 +368,52 @@ branchCommand.command("delete <name>").description("Delete a branch (e.g., arden
|
|
|
313
368
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
314
369
|
process.exit(1);
|
|
315
370
|
}
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
// src/commands/connector.ts
|
|
319
|
-
import { Command as Command2 } from "commander";
|
|
320
|
-
function parsePostgresUrl(url) {
|
|
321
|
-
const atIndex = url.lastIndexOf("@");
|
|
322
|
-
const credentialsPart = atIndex > 0 ? url.substring(0, atIndex) : url;
|
|
323
|
-
const hostPart = atIndex > 0 ? url.substring(atIndex) : "";
|
|
324
|
-
const cleanedUrl = credentialsPart.replace(/\\!/g, "!") + hostPart;
|
|
325
|
-
const parsed = new URL(cleanedUrl);
|
|
326
|
-
const host = parsed.hostname;
|
|
327
|
-
const port = parsed.port || "5432";
|
|
328
|
-
const username = decodeURIComponent(parsed.username);
|
|
329
|
-
const password = parsed.password ? decodeURIComponent(parsed.password) : "";
|
|
330
|
-
if (!host) throw new Error("Host required in connection URL");
|
|
331
|
-
if (!username) throw new Error("Username required in connection URL");
|
|
332
|
-
return { host, port, username, password };
|
|
333
371
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
name: connectorName,
|
|
359
|
-
service_name: "postgresql",
|
|
360
|
-
connection_details: connectionDetails
|
|
361
|
-
});
|
|
362
|
-
const connectorId = created.id;
|
|
363
|
-
console.log("Discovering schema...");
|
|
364
|
-
await api.post(`/v1/connectors/${connectorId}/discover`, {});
|
|
365
|
-
console.log("Setting selection...");
|
|
366
|
-
await api.post(`/v1/connectors/${connectorId}/selection`, {
|
|
367
|
-
selected_paths: ["*"]
|
|
368
|
-
});
|
|
369
|
-
const connector = await api.get(`/v1/connectors/${connectorId}`);
|
|
370
|
-
if (connector.branching_engine_status === "configuration_verified") {
|
|
371
|
-
console.log("Setting up branching engine...");
|
|
372
|
-
await api.post(`/v1/connectors/${connectorId}/engine-setup`, {});
|
|
372
|
+
|
|
373
|
+
// src/commands/branch/switch.ts
|
|
374
|
+
async function switchAction(name) {
|
|
375
|
+
let cached = getCacheEntry("branches");
|
|
376
|
+
let branch = cached?.data.find((cachedBranch) => cachedBranch.name === name);
|
|
377
|
+
if (!branch) {
|
|
378
|
+
try {
|
|
379
|
+
const result = await api.get("/v1/cli/branches");
|
|
380
|
+
if (!result.branches) {
|
|
381
|
+
throw new Error("API returned invalid response: missing branches array");
|
|
382
|
+
}
|
|
383
|
+
setCacheEntry("branches", result.branches);
|
|
384
|
+
branch = result.branches.find((remoteBranch) => remoteBranch.name === name);
|
|
385
|
+
} catch (err) {
|
|
386
|
+
if (isNetworkError(err)) {
|
|
387
|
+
if (!branch) {
|
|
388
|
+
console.error(`\u2717 Branch "${name}" not found in cache`);
|
|
389
|
+
console.log(" Run 'ardent branch list' when online to refresh");
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
373
396
|
}
|
|
374
|
-
console.log("\u2713 Connector created and ready");
|
|
375
|
-
console.log(` ID: ${connectorId}`);
|
|
376
|
-
} catch (err) {
|
|
377
|
-
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
378
|
-
process.exit(1);
|
|
379
397
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const result = await api.get("/v1/cli/connectors");
|
|
384
|
-
if (!result.connectors) {
|
|
385
|
-
throw new Error("API returned invalid response: missing connectors array");
|
|
386
|
-
}
|
|
387
|
-
const connectors = result.connectors;
|
|
388
|
-
if (connectors.length === 0) {
|
|
389
|
-
console.log("No connectors found");
|
|
390
|
-
console.log(" Create one with: ardent connector create postgresql <url>");
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
console.log("Connectors:\n");
|
|
394
|
-
for (const connector of connectors) {
|
|
395
|
-
const icon = connector.status === "healthy" ? "\u25CF" : "\u25CB";
|
|
396
|
-
console.log(` ${icon} ${connector.name} (${connector.service_name})`);
|
|
397
|
-
console.log(` ID: ${connector.id}`);
|
|
398
|
-
console.log();
|
|
399
|
-
}
|
|
400
|
-
} catch (err) {
|
|
401
|
-
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
398
|
+
if (!branch) {
|
|
399
|
+
console.error(`\u2717 Branch "${name}" not found`);
|
|
400
|
+
console.log(" Run: ardent branch list");
|
|
402
401
|
process.exit(1);
|
|
403
402
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
411
|
-
const connector = result.connectors.find((existingConnector) => existingConnector.name === name);
|
|
412
|
-
if (!connector) {
|
|
413
|
-
console.error(`\u2717 Connector "${name}" not found`);
|
|
414
|
-
console.log(" Run: ardent connector list");
|
|
415
|
-
process.exit(1);
|
|
416
|
-
}
|
|
417
|
-
console.log("Deleting connector...");
|
|
418
|
-
await api.delete(`/v1/connectors/${connector.id}`);
|
|
419
|
-
console.log("\u2713 Connector deleted");
|
|
420
|
-
} catch (err) {
|
|
421
|
-
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
422
|
-
process.exit(1);
|
|
403
|
+
const previous = getCurrentBranch();
|
|
404
|
+
setCurrentBranch(name);
|
|
405
|
+
if (previous && previous !== name) {
|
|
406
|
+
console.log(`Switched from '${previous}' to '${name}'`);
|
|
407
|
+
} else {
|
|
408
|
+
console.log(`Switched to branch '${name}'`);
|
|
423
409
|
}
|
|
424
|
-
|
|
410
|
+
if (branch.branch_url) {
|
|
411
|
+
console.log(`
|
|
412
|
+
${branch.branch_url}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
425
415
|
|
|
426
|
-
// src/commands/diff.ts
|
|
427
|
-
import { Command as Command3 } from "commander";
|
|
416
|
+
// src/commands/branch/diff.ts
|
|
428
417
|
var green = "\x1B[32m";
|
|
429
418
|
var red = "\x1B[31m";
|
|
430
419
|
var yellow = "\x1B[33m";
|
|
@@ -438,7 +427,18 @@ function truncate(str, maxLen) {
|
|
|
438
427
|
if (str.length <= maxLen) return str;
|
|
439
428
|
return str.slice(0, maxLen - 2) + "..";
|
|
440
429
|
}
|
|
441
|
-
|
|
430
|
+
function parseDiffRow(raw) {
|
|
431
|
+
return {
|
|
432
|
+
schema_name: raw.schema_name,
|
|
433
|
+
table_name: raw.table_name,
|
|
434
|
+
primary_key: raw.primary_key,
|
|
435
|
+
first_op: raw.first_op,
|
|
436
|
+
last_op: raw.last_op,
|
|
437
|
+
first_data: raw.original_values ? JSON.parse(raw.original_values) : null,
|
|
438
|
+
last_data: raw.final_values ? JSON.parse(raw.final_values) : null
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
async function diffAction(options) {
|
|
442
442
|
let branch;
|
|
443
443
|
if (options.branch) {
|
|
444
444
|
branch = getBranchByName(options.branch);
|
|
@@ -455,18 +455,20 @@ var diffCommand = new Command3("diff").description("Show CDC changes on current
|
|
|
455
455
|
console.log(" Run: ardent branch list");
|
|
456
456
|
} else {
|
|
457
457
|
console.error("\u2717 No current branch set");
|
|
458
|
-
console.log(" Run: ardent switch <name> or ardent branch create <name>");
|
|
458
|
+
console.log(" Run: ardent branch switch <name> or ardent branch create <name>");
|
|
459
459
|
}
|
|
460
460
|
return;
|
|
461
461
|
}
|
|
462
462
|
try {
|
|
463
|
-
const
|
|
464
|
-
if (!
|
|
463
|
+
const stateRaw = await api.get(`/v1/branches/${branch.job_id}/state`);
|
|
464
|
+
if (!stateRaw || Object.keys(stateRaw).length === 0) {
|
|
465
465
|
console.log("No changes captured");
|
|
466
466
|
return;
|
|
467
467
|
}
|
|
468
|
-
for (const [_branchId,
|
|
469
|
-
const
|
|
468
|
+
for (const [_branchId, branchStateRaw] of Object.entries(stateRaw)) {
|
|
469
|
+
const ddl = branchStateRaw.ddl;
|
|
470
|
+
const replay_sql = branchStateRaw.replay_sql;
|
|
471
|
+
const diff = (branchStateRaw.diff || []).map(parseDiffRow);
|
|
470
472
|
if (options.sql) {
|
|
471
473
|
if (replay_sql) {
|
|
472
474
|
console.log(replay_sql);
|
|
@@ -619,13 +621,320 @@ var diffCommand = new Command3("diff").description("Show CDC changes on current
|
|
|
619
621
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
620
622
|
process.exit(1);
|
|
621
623
|
}
|
|
622
|
-
}
|
|
624
|
+
}
|
|
623
625
|
|
|
624
|
-
// src/commands/
|
|
625
|
-
|
|
626
|
-
|
|
626
|
+
// src/commands/branch/index.ts
|
|
627
|
+
var branchCommand = new Command("branch").description("Manage database branches");
|
|
628
|
+
branchCommand.command("create <name>").description("Create a new database branch").option("-s, --service <type>", "Service type", "postgres").action(createAction);
|
|
629
|
+
branchCommand.command("list").description("List your branches").action(listAction);
|
|
630
|
+
branchCommand.command("info [name]").description("Show branch details (defaults to current branch)").action(infoAction);
|
|
631
|
+
branchCommand.command("delete <name>").description("Delete a branch").action(deleteAction);
|
|
632
|
+
branchCommand.command("switch <name>").description("Switch to a different branch").action(switchAction);
|
|
633
|
+
branchCommand.command("diff").description("Show all changes since branch creation").option("-b, --branch <name>", "Branch name (defaults to current)").option("--sql", "Output as replay SQL").action(diffAction);
|
|
634
|
+
|
|
635
|
+
// src/commands/connector/index.ts
|
|
636
|
+
import { Command as Command2 } from "commander";
|
|
637
|
+
|
|
638
|
+
// src/lib/onboarding.ts
|
|
639
|
+
var BANNER = `
|
|
640
|
+
\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
641
|
+
\u2502 \u2502
|
|
642
|
+
\u2502 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2502
|
|
643
|
+
\u2502 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D \u2502
|
|
644
|
+
\u2502 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2502
|
|
645
|
+
\u2502 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2502
|
|
646
|
+
\u2502 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2502
|
|
647
|
+
\u2502 \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u2502
|
|
648
|
+
\u2502 \u2502
|
|
649
|
+
\u2502 Git for your data infrastructure \u2502
|
|
650
|
+
\u2502 \u2502
|
|
651
|
+
\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
652
|
+
`;
|
|
653
|
+
function getOnboardingState() {
|
|
654
|
+
const token = getToken();
|
|
655
|
+
if (!token) {
|
|
656
|
+
return { state: "not_logged_in", connectorCount: 0, branchCount: 0, currentBranch: void 0 };
|
|
657
|
+
}
|
|
658
|
+
const connectors = getCacheEntry("connectors");
|
|
659
|
+
const connectorCount = connectors?.data?.length || 0;
|
|
660
|
+
if (connectorCount === 0) {
|
|
661
|
+
return { state: "no_connectors", connectorCount: 0, branchCount: 0, currentBranch: void 0 };
|
|
662
|
+
}
|
|
663
|
+
const branches = getCacheEntry("branches");
|
|
664
|
+
const branchCount = branches?.data?.length || 0;
|
|
665
|
+
const currentBranch = getCurrentBranch();
|
|
666
|
+
if (branchCount === 0) {
|
|
667
|
+
return { state: "no_branches", connectorCount, branchCount: 0, currentBranch: void 0 };
|
|
668
|
+
}
|
|
669
|
+
return { state: "ready", connectorCount, branchCount, currentBranch };
|
|
670
|
+
}
|
|
671
|
+
var dim2 = "\x1B[2m";
|
|
672
|
+
var reset2 = "\x1B[0m";
|
|
673
|
+
var green2 = "\x1B[32m";
|
|
674
|
+
function showOnboarding() {
|
|
675
|
+
const { state, connectorCount, branchCount, currentBranch } = getOnboardingState();
|
|
676
|
+
switch (state) {
|
|
677
|
+
case "not_logged_in":
|
|
678
|
+
console.log(BANNER);
|
|
679
|
+
console.log(`To get started, log in with your GitHub account:
|
|
680
|
+
|
|
681
|
+
${green2}ardent login${reset2}
|
|
682
|
+
|
|
683
|
+
Run ${dim2}ardent --help${reset2} to see all commands.
|
|
684
|
+
`);
|
|
685
|
+
break;
|
|
686
|
+
case "no_connectors":
|
|
687
|
+
console.log(`
|
|
688
|
+
${green2}\u2713${reset2} Logged in
|
|
689
|
+
|
|
690
|
+
Next step \u2014 connect your first database:
|
|
691
|
+
|
|
692
|
+
${green2}ardent connector create <type> <connection-url>${reset2}
|
|
693
|
+
|
|
694
|
+
Example:
|
|
695
|
+
${dim2}ardent connector create postgresql postgresql://user:password@host:5432/database${reset2}
|
|
696
|
+
|
|
697
|
+
Run ${dim2}ardent --help${reset2} to see all commands.
|
|
698
|
+
`);
|
|
699
|
+
break;
|
|
700
|
+
case "no_branches":
|
|
701
|
+
console.log(`
|
|
702
|
+
${green2}\u2713${reset2} Logged in ${green2}\u2713${reset2} ${connectorCount} connector${connectorCount !== 1 ? "s" : ""}
|
|
703
|
+
|
|
704
|
+
You're all set! Create your first branch:
|
|
705
|
+
|
|
706
|
+
${green2}ardent branch create <name>${reset2}
|
|
707
|
+
|
|
708
|
+
Example:
|
|
709
|
+
${dim2}ardent branch create my-feature${reset2}
|
|
710
|
+
|
|
711
|
+
Run ${dim2}ardent --help${reset2} to see all commands.
|
|
712
|
+
`);
|
|
713
|
+
return true;
|
|
714
|
+
case "ready":
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
719
|
+
function showNextStep() {
|
|
720
|
+
const { state } = getOnboardingState();
|
|
721
|
+
switch (state) {
|
|
722
|
+
case "no_connectors":
|
|
723
|
+
console.log(`
|
|
724
|
+
Next step \u2014 connect your first database:
|
|
725
|
+
|
|
726
|
+
${green2}ardent connector create <type> <connection-url>${reset2}
|
|
727
|
+
|
|
728
|
+
Example:
|
|
729
|
+
${dim2}ardent connector create postgresql postgresql://user:password@host:5432/database${reset2}
|
|
730
|
+
`);
|
|
731
|
+
break;
|
|
732
|
+
case "no_branches":
|
|
733
|
+
console.log(`
|
|
734
|
+
Next step \u2014 create your first branch:
|
|
735
|
+
|
|
736
|
+
${green2}ardent branch create <name>${reset2}
|
|
737
|
+
|
|
738
|
+
Example:
|
|
739
|
+
${dim2}ardent branch create my-feature${reset2}
|
|
740
|
+
`);
|
|
741
|
+
break;
|
|
742
|
+
case "ready":
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// src/commands/connector/create.ts
|
|
748
|
+
function parsePostgresUrl(url) {
|
|
749
|
+
const atIndex = url.lastIndexOf("@");
|
|
750
|
+
const credentialsPart = atIndex > 0 ? url.substring(0, atIndex) : url;
|
|
751
|
+
const hostPart = atIndex > 0 ? url.substring(atIndex) : "";
|
|
752
|
+
const cleanedUrl = credentialsPart.replace(/\\!/g, "!") + hostPart;
|
|
753
|
+
const parsed = new URL(cleanedUrl);
|
|
754
|
+
const host = parsed.hostname;
|
|
755
|
+
const port = parsed.port || "5432";
|
|
756
|
+
const username = decodeURIComponent(parsed.username);
|
|
757
|
+
const password = parsed.password ? decodeURIComponent(parsed.password) : "";
|
|
758
|
+
if (!host) throw new Error("Host required in connection URL");
|
|
759
|
+
if (!username) throw new Error("Username required in connection URL");
|
|
760
|
+
return { host, port, username, password };
|
|
761
|
+
}
|
|
762
|
+
async function createAction2(type, url, options) {
|
|
763
|
+
const supportedTypes = ["postgresql"];
|
|
764
|
+
if (!supportedTypes.includes(type.toLowerCase())) {
|
|
765
|
+
console.error(`\u2717 Unsupported type: ${type}`);
|
|
766
|
+
console.error(` Supported: ${supportedTypes.join(", ")}`);
|
|
767
|
+
process.exit(1);
|
|
768
|
+
}
|
|
769
|
+
try {
|
|
770
|
+
const parsed = parsePostgresUrl(url);
|
|
771
|
+
if (!parsed.password) {
|
|
772
|
+
console.error("\u2717 Password required in connection URL");
|
|
773
|
+
console.error(" Example: postgresql://user:password@host:5432/db");
|
|
774
|
+
process.exit(1);
|
|
775
|
+
}
|
|
776
|
+
const connectionDetails = {
|
|
777
|
+
host: parsed.host,
|
|
778
|
+
port: parsed.port,
|
|
779
|
+
username: parsed.username,
|
|
780
|
+
password: parsed.password
|
|
781
|
+
};
|
|
782
|
+
const connectorName = options.name || "my-postgresql-connection";
|
|
783
|
+
console.log("Creating connector...");
|
|
784
|
+
const created = await api.post("/v1/connectors", {
|
|
785
|
+
name: connectorName,
|
|
786
|
+
service_name: "postgresql",
|
|
787
|
+
connection_details: connectionDetails
|
|
788
|
+
});
|
|
789
|
+
const connectorId = created.id;
|
|
790
|
+
console.log("Discovering schema...");
|
|
791
|
+
await api.post(`/v1/connectors/${connectorId}/discover`, {});
|
|
792
|
+
console.log("Setting selection...");
|
|
793
|
+
await api.post(`/v1/connectors/${connectorId}/selection`, {
|
|
794
|
+
selected_paths: ["*"]
|
|
795
|
+
});
|
|
796
|
+
const connector = await api.get(`/v1/connectors/${connectorId}`);
|
|
797
|
+
if (connector.branching_engine_status === "configuration_verified") {
|
|
798
|
+
console.log("Setting up branching engine...");
|
|
799
|
+
await api.post(`/v1/connectors/${connectorId}/engine-setup`, {});
|
|
800
|
+
}
|
|
801
|
+
const newConnector = {
|
|
802
|
+
id: connectorId,
|
|
803
|
+
name: connectorName,
|
|
804
|
+
service_name: "postgresql",
|
|
805
|
+
status: connector.connection_status === "connected" ? "healthy" : "unknown"
|
|
806
|
+
};
|
|
807
|
+
const cached = getCacheEntry("connectors");
|
|
808
|
+
const cachedConnectors = cached?.data || [];
|
|
809
|
+
cachedConnectors.push(newConnector);
|
|
810
|
+
setCacheEntry("connectors", cachedConnectors);
|
|
811
|
+
console.log("\u2713 Connector created and ready");
|
|
812
|
+
console.log(` ID: ${connectorId}`);
|
|
813
|
+
showNextStep();
|
|
814
|
+
} catch (err) {
|
|
815
|
+
if (isPermissionError(err)) {
|
|
816
|
+
console.error("\u2717 You don't have permission to create connectors.");
|
|
817
|
+
console.error("");
|
|
818
|
+
console.error(" Ask your organization admin to either:");
|
|
819
|
+
console.error(" \u2022 Create the connector for you, or");
|
|
820
|
+
console.error(" \u2022 Upgrade your role to Admin");
|
|
821
|
+
process.exit(1);
|
|
822
|
+
}
|
|
823
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
824
|
+
process.exit(1);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// src/commands/connector/list.ts
|
|
829
|
+
async function listAction2() {
|
|
830
|
+
let connectors = [];
|
|
831
|
+
let fromCache = false;
|
|
832
|
+
let cacheTime = "";
|
|
833
|
+
try {
|
|
834
|
+
const result = await api.get("/v1/cli/connectors");
|
|
835
|
+
if (!result.connectors) {
|
|
836
|
+
throw new Error("API returned invalid response: missing connectors array");
|
|
837
|
+
}
|
|
838
|
+
connectors = result.connectors;
|
|
839
|
+
setCacheEntry("connectors", connectors);
|
|
840
|
+
} catch (err) {
|
|
841
|
+
if (isNetworkError(err)) {
|
|
842
|
+
const cached = getCacheEntry("connectors");
|
|
843
|
+
if (cached) {
|
|
844
|
+
connectors = cached.data;
|
|
845
|
+
fromCache = true;
|
|
846
|
+
cacheTime = formatCacheTime(cached.updated_at);
|
|
847
|
+
} else {
|
|
848
|
+
console.error("\u2717 Offline and no cached data available");
|
|
849
|
+
process.exit(1);
|
|
850
|
+
}
|
|
851
|
+
} else {
|
|
852
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
853
|
+
process.exit(1);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (fromCache) {
|
|
857
|
+
console.log(`\u26A0 Offline - showing cached data from ${cacheTime}
|
|
858
|
+
`);
|
|
859
|
+
}
|
|
860
|
+
if (connectors.length === 0) {
|
|
861
|
+
console.log("No connectors found");
|
|
862
|
+
console.log(" Create one with: ardent connector create postgresql <url>");
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
console.log("Connectors:\n");
|
|
866
|
+
for (const connector of connectors) {
|
|
867
|
+
const icon = connector.status === "healthy" ? "\u25CF" : "\u25CB";
|
|
868
|
+
console.log(` ${icon} ${connector.service_name}`);
|
|
869
|
+
console.log(` Name: ${connector.name}`);
|
|
870
|
+
console.log();
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/commands/connector/delete.ts
|
|
875
|
+
async function deleteAction2(name) {
|
|
876
|
+
const cached = getCacheEntry("connectors");
|
|
877
|
+
let connector = cached?.data.find((c) => c.name === name);
|
|
878
|
+
if (!connector) {
|
|
879
|
+
try {
|
|
880
|
+
const result = await api.get("/v1/cli/connectors");
|
|
881
|
+
if (result.connectors) {
|
|
882
|
+
setCacheEntry("connectors", result.connectors);
|
|
883
|
+
connector = result.connectors.find((c) => c.name === name);
|
|
884
|
+
}
|
|
885
|
+
} catch (err) {
|
|
886
|
+
if (isNetworkError(err)) {
|
|
887
|
+
console.error("\u2717 Connector not found in cache and offline");
|
|
888
|
+
process.exit(1);
|
|
889
|
+
}
|
|
890
|
+
throw err;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
if (!connector) {
|
|
894
|
+
console.error(`\u2717 Connector "${name}" not found`);
|
|
895
|
+
console.log(" Run: ardent connector list");
|
|
896
|
+
process.exit(1);
|
|
897
|
+
}
|
|
898
|
+
try {
|
|
899
|
+
console.log("Deleting connector...");
|
|
900
|
+
await api.delete(`/v1/connectors/${connector.id}`);
|
|
901
|
+
const currentCache = getCacheEntry("connectors");
|
|
902
|
+
if (currentCache?.data) {
|
|
903
|
+
const updatedConnectors = currentCache.data.filter((c) => c.id !== connector.id);
|
|
904
|
+
setCacheEntry("connectors", updatedConnectors);
|
|
905
|
+
}
|
|
906
|
+
console.log("\u2713 Connector deleted");
|
|
907
|
+
} catch (err) {
|
|
908
|
+
if (isNetworkError(err)) {
|
|
909
|
+
console.error("\u2717 Cannot delete connector while offline");
|
|
910
|
+
process.exit(1);
|
|
911
|
+
}
|
|
912
|
+
if (isPermissionError(err)) {
|
|
913
|
+
console.error("\u2717 You don't have permission to delete connectors.");
|
|
914
|
+
console.error("");
|
|
915
|
+
console.error(" Ask your organization admin to either:");
|
|
916
|
+
console.error(" \u2022 Delete the connector for you, or");
|
|
917
|
+
console.error(" \u2022 Upgrade your role to Admin");
|
|
918
|
+
process.exit(1);
|
|
919
|
+
}
|
|
920
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
921
|
+
process.exit(1);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// src/commands/connector/index.ts
|
|
926
|
+
var connectorCommand = new Command2("connector").description("Manage database connectors");
|
|
927
|
+
connectorCommand.command("create <type> <url>").description("Create a new connector").option("-n, --name <name>", "Connector name").action(createAction2);
|
|
928
|
+
connectorCommand.command("list").description("List your connectors").action(listAction2);
|
|
929
|
+
connectorCommand.command("delete <name>").description("Delete a connector by name").action(deleteAction2);
|
|
930
|
+
|
|
931
|
+
// src/commands/invite/index.ts
|
|
932
|
+
import { Command as Command3 } from "commander";
|
|
933
|
+
|
|
934
|
+
// src/commands/invite/send.ts
|
|
935
|
+
async function sendAction(email, role) {
|
|
627
936
|
const validRoles = ["owner", "admin", "member", "viewer"];
|
|
628
|
-
if (!validRoles.includes(role)) {
|
|
937
|
+
if (!validRoles.includes(role.toLowerCase())) {
|
|
629
938
|
console.error(`\u2717 Invalid role: ${role}`);
|
|
630
939
|
console.error(` Valid roles: ${validRoles.join(", ")}`);
|
|
631
940
|
process.exit(1);
|
|
@@ -633,30 +942,31 @@ async function sendInvite(email, role) {
|
|
|
633
942
|
try {
|
|
634
943
|
const result = await api.post("/v1/cli/invite", {
|
|
635
944
|
email,
|
|
636
|
-
role
|
|
945
|
+
role: role.toLowerCase()
|
|
637
946
|
});
|
|
638
|
-
console.log(`\u2713
|
|
947
|
+
console.log(`\u2713 Invite sent to ${result.email}`);
|
|
639
948
|
console.log(` Role: ${result.role}`);
|
|
640
949
|
console.log(` Org: ${result.org_name}`);
|
|
641
|
-
console.log();
|
|
642
|
-
console.log("They'll receive an email with a link to join.");
|
|
643
950
|
} catch (err) {
|
|
644
951
|
if (isNetworkError(err)) {
|
|
645
952
|
console.error("\u2717 Cannot send invite while offline");
|
|
646
953
|
process.exit(1);
|
|
647
954
|
}
|
|
955
|
+
if (isPermissionError(err)) {
|
|
956
|
+
console.error("\u2717 You don't have permission to invite users.");
|
|
957
|
+
console.error("");
|
|
958
|
+
console.error(" Ask your organization admin to either:");
|
|
959
|
+
console.error(" \u2022 Send the invite for you, or");
|
|
960
|
+
console.error(" \u2022 Upgrade your role to Admin");
|
|
961
|
+
process.exit(1);
|
|
962
|
+
}
|
|
648
963
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
649
964
|
process.exit(1);
|
|
650
965
|
}
|
|
651
966
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
await sendInvite(email, role);
|
|
658
|
-
});
|
|
659
|
-
inviteCommand.command("list").description("List pending invites").action(async () => {
|
|
967
|
+
|
|
968
|
+
// src/commands/invite/list.ts
|
|
969
|
+
async function listAction3() {
|
|
660
970
|
try {
|
|
661
971
|
const result = await api.get("/v1/cli/invites");
|
|
662
972
|
if (!result.invites) {
|
|
@@ -682,8 +992,10 @@ inviteCommand.command("list").description("List pending invites").action(async (
|
|
|
682
992
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
683
993
|
process.exit(1);
|
|
684
994
|
}
|
|
685
|
-
}
|
|
686
|
-
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// src/commands/invite/delete.ts
|
|
998
|
+
async function deleteAction3(email) {
|
|
687
999
|
try {
|
|
688
1000
|
await api.delete("/v1/cli/invites", { email });
|
|
689
1001
|
console.log(`\u2713 Invite for ${email} deleted`);
|
|
@@ -695,58 +1007,114 @@ inviteCommand.command("delete <email>").description("Revoke a pending invite").a
|
|
|
695
1007
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
696
1008
|
process.exit(1);
|
|
697
1009
|
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// src/commands/invite/index.ts
|
|
1013
|
+
var inviteCommand = new Command3("invite").description("Invite a user to your organization").argument("[email]", "Email address to invite").argument("[role]", "Role to assign (owner/admin/member/viewer)", "member").action(async (email, role) => {
|
|
1014
|
+
if (!email) {
|
|
1015
|
+
inviteCommand.help();
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
await sendAction(email, role);
|
|
698
1019
|
});
|
|
1020
|
+
inviteCommand.command("list").description("List pending invites").action(listAction3);
|
|
1021
|
+
inviteCommand.command("delete <email>").description("Revoke a pending invite").action(deleteAction3);
|
|
699
1022
|
|
|
700
|
-
// src/commands/
|
|
701
|
-
import { Command as
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
process.exit(1);
|
|
723
|
-
}
|
|
1023
|
+
// src/commands/org/index.ts
|
|
1024
|
+
import { Command as Command4 } from "commander";
|
|
1025
|
+
|
|
1026
|
+
// src/commands/org/members.ts
|
|
1027
|
+
async function membersAction() {
|
|
1028
|
+
try {
|
|
1029
|
+
const result = await api.get("/v1/cli/members");
|
|
1030
|
+
if (!result.members || result.members.length === 0) {
|
|
1031
|
+
console.log("No members found");
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
console.log("Organization members:\n");
|
|
1035
|
+
for (const member of result.members) {
|
|
1036
|
+
const name = member.name ? ` (${member.name})` : "";
|
|
1037
|
+
console.log(` ${member.email}${name}`);
|
|
1038
|
+
console.log(` Role: ${member.role}`);
|
|
1039
|
+
console.log();
|
|
1040
|
+
}
|
|
1041
|
+
} catch (err) {
|
|
1042
|
+
if (isNetworkError(err)) {
|
|
1043
|
+
console.error("\u2717 Cannot list members while offline");
|
|
1044
|
+
process.exit(1);
|
|
724
1045
|
}
|
|
1046
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1047
|
+
process.exit(1);
|
|
725
1048
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// src/commands/org/set-role.ts
|
|
1052
|
+
async function setRoleAction(email, role) {
|
|
1053
|
+
const validRoles = ["owner", "admin", "member", "viewer"];
|
|
1054
|
+
if (!validRoles.includes(role)) {
|
|
1055
|
+
console.error(`\u2717 Invalid role: ${role}`);
|
|
1056
|
+
console.error(` Valid roles: ${validRoles.join(", ")}`);
|
|
729
1057
|
process.exit(1);
|
|
730
1058
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
1059
|
+
try {
|
|
1060
|
+
const result = await api.patch("/v1/cli/members/role", { email, role });
|
|
1061
|
+
console.log(`\u2713 Updated ${result.email} to ${result.new_role}`);
|
|
1062
|
+
} catch (err) {
|
|
1063
|
+
if (isNetworkError(err)) {
|
|
1064
|
+
console.error("\u2717 Cannot update role while offline");
|
|
1065
|
+
process.exit(1);
|
|
1066
|
+
}
|
|
1067
|
+
if (isPermissionError(err)) {
|
|
1068
|
+
console.error("\u2717 You don't have permission to update member roles.");
|
|
1069
|
+
console.error("");
|
|
1070
|
+
console.error(" Only org admins and owners can change roles.");
|
|
1071
|
+
process.exit(1);
|
|
1072
|
+
}
|
|
1073
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1074
|
+
process.exit(1);
|
|
737
1075
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// src/commands/org/remove.ts
|
|
1079
|
+
async function removeAction(email) {
|
|
1080
|
+
try {
|
|
1081
|
+
await api.delete("/v1/cli/members", { email });
|
|
1082
|
+
console.log(`\u2713 Removed ${email} from organization`);
|
|
1083
|
+
} catch (err) {
|
|
1084
|
+
if (isNetworkError(err)) {
|
|
1085
|
+
console.error("\u2717 Cannot remove member while offline");
|
|
1086
|
+
process.exit(1);
|
|
1087
|
+
}
|
|
1088
|
+
if (isPermissionError(err)) {
|
|
1089
|
+
console.error("\u2717 You don't have permission to remove members.");
|
|
1090
|
+
console.error("");
|
|
1091
|
+
console.error(" Only org admins and owners can remove members.");
|
|
1092
|
+
process.exit(1);
|
|
1093
|
+
}
|
|
1094
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1095
|
+
process.exit(1);
|
|
741
1096
|
}
|
|
742
|
-
}
|
|
1097
|
+
}
|
|
743
1098
|
|
|
744
|
-
// src/commands/
|
|
745
|
-
|
|
746
|
-
|
|
1099
|
+
// src/commands/org/index.ts
|
|
1100
|
+
var orgCommand = new Command4("org").description("Manage organization members");
|
|
1101
|
+
orgCommand.command("members").description("List organization members").action(membersAction);
|
|
1102
|
+
orgCommand.command("set-role <email> <role>").description("Update a member's role (owner/admin/member/viewer)").action(setRoleAction);
|
|
1103
|
+
orgCommand.command("remove <email>").description("Remove a member from the organization").action(removeAction);
|
|
1104
|
+
|
|
1105
|
+
// src/commands/auth/index.ts
|
|
1106
|
+
import { Command as Command5 } from "commander";
|
|
1107
|
+
|
|
1108
|
+
// src/commands/auth/login.ts
|
|
1109
|
+
async function loginAction(options) {
|
|
747
1110
|
if (options.token) {
|
|
748
1111
|
setConfig("token", options.token);
|
|
749
1112
|
console.log("\u2713 Logged in successfully");
|
|
1113
|
+
try {
|
|
1114
|
+
await bootstrapCache();
|
|
1115
|
+
} catch {
|
|
1116
|
+
}
|
|
1117
|
+
showNextStep();
|
|
750
1118
|
return;
|
|
751
1119
|
}
|
|
752
1120
|
console.log("Opening browser for authentication...");
|
|
@@ -780,6 +1148,11 @@ var loginCommand = new Command6("login").description("Login to Ardent").option("
|
|
|
780
1148
|
if (result.status === "completed") {
|
|
781
1149
|
setConfig("token", result.token);
|
|
782
1150
|
console.log("\n\u2713 Logged in successfully");
|
|
1151
|
+
try {
|
|
1152
|
+
await bootstrapCache();
|
|
1153
|
+
} catch {
|
|
1154
|
+
}
|
|
1155
|
+
showNextStep();
|
|
783
1156
|
return;
|
|
784
1157
|
}
|
|
785
1158
|
if (result.status === "expired") {
|
|
@@ -798,12 +1171,16 @@ var loginCommand = new Command6("login").description("Login to Ardent").option("
|
|
|
798
1171
|
console.error("\u2717 Login failed:", error instanceof Error ? error.message : error);
|
|
799
1172
|
process.exit(1);
|
|
800
1173
|
}
|
|
801
|
-
}
|
|
802
|
-
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// src/commands/auth/logout.ts
|
|
1177
|
+
function logoutAction() {
|
|
803
1178
|
clearConfig();
|
|
804
1179
|
console.log("\u2713 Logged out");
|
|
805
|
-
}
|
|
806
|
-
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// src/commands/auth/status.ts
|
|
1183
|
+
function statusAction() {
|
|
807
1184
|
const token = getConfig("token");
|
|
808
1185
|
if (token) {
|
|
809
1186
|
console.log("\u2713 Authenticated");
|
|
@@ -812,60 +1189,68 @@ var statusCommand = new Command6("status").description("Show status").action(()
|
|
|
812
1189
|
console.log("\u2717 Not authenticated");
|
|
813
1190
|
console.log(" Run: ardent login");
|
|
814
1191
|
}
|
|
815
|
-
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// src/commands/auth/index.ts
|
|
1195
|
+
var loginCommand = new Command5("login").description("Login to Ardent").option("-t, --token <token>", "API token (skip browser login)").action(loginAction);
|
|
1196
|
+
var logoutCommand = new Command5("logout").description("Logout from Ardent").action(logoutAction);
|
|
1197
|
+
var statusCommand = new Command5("status").description("Show status").action(statusAction);
|
|
816
1198
|
|
|
817
1199
|
// src/index.ts
|
|
818
|
-
var BANNER = `
|
|
819
|
-
\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
820
|
-
\u2502 \u2502
|
|
821
|
-
\u2502 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2502
|
|
822
|
-
\u2502 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D \u2502
|
|
823
|
-
\u2502 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2502
|
|
824
|
-
\u2502 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2502
|
|
825
|
-
\u2502 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2502
|
|
826
|
-
\u2502 \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u2502
|
|
827
|
-
\u2502 \u2502
|
|
828
|
-
\u2502 Git for your data infrastructure \u2502
|
|
829
|
-
\u2502 \u2502
|
|
830
|
-
\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
831
|
-
`;
|
|
832
1200
|
var HELP_TEXT = `
|
|
833
1201
|
USAGE
|
|
834
1202
|
ardent <command> [options]
|
|
835
1203
|
|
|
836
|
-
|
|
837
|
-
login
|
|
838
|
-
logout
|
|
839
|
-
status
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
connector
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1204
|
+
AUTHENTICATION
|
|
1205
|
+
login Login via browser (GitHub OAuth)
|
|
1206
|
+
logout Clear stored credentials
|
|
1207
|
+
status Check authentication status
|
|
1208
|
+
|
|
1209
|
+
CONNECTORS
|
|
1210
|
+
connector create Connect a database (postgresql, snowflake, etc.)
|
|
1211
|
+
connector list List your connectors
|
|
1212
|
+
connector delete Delete a connector
|
|
1213
|
+
|
|
1214
|
+
BRANCHES
|
|
1215
|
+
branch create Create a new database branch (auto-switches to it)
|
|
1216
|
+
branch list List your branches (* = current)
|
|
1217
|
+
branch info Show branch details (URL, status, etc.)
|
|
1218
|
+
branch delete Delete a branch
|
|
1219
|
+
branch switch Switch to a different branch
|
|
1220
|
+
branch diff Show all changes since branch creation
|
|
1221
|
+
|
|
1222
|
+
TEAM
|
|
1223
|
+
invite <email> Invite a user to your organization
|
|
1224
|
+
invite list List pending invites
|
|
1225
|
+
invite delete Revoke a pending invite
|
|
1226
|
+
org members List organization members
|
|
1227
|
+
org set-role Update a member's role
|
|
1228
|
+
org remove Remove a member from the organization
|
|
852
1229
|
|
|
853
1230
|
OPTIONS
|
|
854
|
-
--help
|
|
855
|
-
--version
|
|
1231
|
+
--help Show help for any command
|
|
1232
|
+
--version Show CLI version
|
|
856
1233
|
|
|
857
1234
|
EXAMPLES
|
|
858
1235
|
ardent login
|
|
859
|
-
ardent connector create postgresql postgresql://user:
|
|
1236
|
+
ardent connector create postgresql postgresql://user:password@your-db-host.com:5432/database
|
|
860
1237
|
ardent branch create my-feature
|
|
861
|
-
ardent branch
|
|
862
|
-
ardent
|
|
863
|
-
ardent invite teammate@company.com admin
|
|
864
|
-
|
|
865
|
-
For more help, visit https://docs.tryardent.com/cli
|
|
1238
|
+
ardent branch switch my-feature
|
|
1239
|
+
ardent org members
|
|
866
1240
|
`;
|
|
867
|
-
var
|
|
868
|
-
program
|
|
1241
|
+
var CLI_VERSION = getCliVersion();
|
|
1242
|
+
var program = new Command6();
|
|
1243
|
+
function getCliVersion() {
|
|
1244
|
+
try {
|
|
1245
|
+
const packageUrl = new URL("../package.json", import.meta.url);
|
|
1246
|
+
const raw = readFileSync2(packageUrl, "utf-8");
|
|
1247
|
+
const parsed = JSON.parse(raw);
|
|
1248
|
+
return parsed.version ?? "unknown";
|
|
1249
|
+
} catch {
|
|
1250
|
+
return "unknown";
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
program.name("ardent").description("CLI for Ardent database branching").version(CLI_VERSION).configureHelp({
|
|
869
1254
|
formatHelp: () => BANNER + HELP_TEXT
|
|
870
1255
|
});
|
|
871
1256
|
program.addCommand(loginCommand);
|
|
@@ -873,7 +1258,28 @@ program.addCommand(logoutCommand);
|
|
|
873
1258
|
program.addCommand(statusCommand);
|
|
874
1259
|
program.addCommand(connectorCommand);
|
|
875
1260
|
program.addCommand(branchCommand);
|
|
876
|
-
program.addCommand(switchCommand);
|
|
877
|
-
program.addCommand(diffCommand);
|
|
878
1261
|
program.addCommand(inviteCommand);
|
|
1262
|
+
program.addCommand(orgCommand);
|
|
1263
|
+
program.on("command:*", (operands) => {
|
|
1264
|
+
const unknown = operands[0];
|
|
1265
|
+
console.error(`Unknown command: ${unknown}
|
|
1266
|
+
`);
|
|
1267
|
+
if (unknown === "switch") {
|
|
1268
|
+
console.error(` Did you mean: ardent branch switch <name>
|
|
1269
|
+
`);
|
|
1270
|
+
} else if (unknown === "diff") {
|
|
1271
|
+
console.error(` Did you mean: ardent branch diff
|
|
1272
|
+
`);
|
|
1273
|
+
}
|
|
1274
|
+
console.error(`Run ardent --help for available commands.`);
|
|
1275
|
+
process.exit(1);
|
|
1276
|
+
});
|
|
1277
|
+
var args = process.argv.slice(2);
|
|
1278
|
+
if (args.length === 0) {
|
|
1279
|
+
const showedOnboarding = showOnboarding();
|
|
1280
|
+
if (showedOnboarding) {
|
|
1281
|
+
process.exit(0);
|
|
1282
|
+
}
|
|
1283
|
+
program.help();
|
|
1284
|
+
}
|
|
879
1285
|
program.parse();
|