ardent-cli 0.0.3 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +678 -235
- 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,51 @@ 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
|
|
129
|
+
function parseApiError(status, text) {
|
|
130
|
+
try {
|
|
131
|
+
const json = JSON.parse(text);
|
|
132
|
+
if (json.detail) return `API error ${status}: ${json.detail}`;
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
return `API error ${status}: ${text}`;
|
|
136
|
+
}
|
|
100
137
|
var ApiClient = class {
|
|
101
138
|
getHeaders() {
|
|
102
139
|
const token = getToken();
|
|
103
140
|
if (!token) {
|
|
104
|
-
|
|
141
|
+
const green3 = "\x1B[32m";
|
|
142
|
+
const reset3 = "\x1B[0m";
|
|
143
|
+
console.error(`\u2717 Not authenticated. Run: ${green3}ardent login${reset3}`);
|
|
105
144
|
process.exit(1);
|
|
106
145
|
}
|
|
107
146
|
return {
|
|
@@ -117,7 +156,7 @@ var ApiClient = class {
|
|
|
117
156
|
});
|
|
118
157
|
if (!response.ok) {
|
|
119
158
|
const text = await response.text();
|
|
120
|
-
throw new Error(
|
|
159
|
+
throw new Error(parseApiError(response.status, text));
|
|
121
160
|
}
|
|
122
161
|
return response.json();
|
|
123
162
|
}
|
|
@@ -130,7 +169,7 @@ var ApiClient = class {
|
|
|
130
169
|
});
|
|
131
170
|
if (!response.ok) {
|
|
132
171
|
const text = await response.text();
|
|
133
|
-
throw new Error(
|
|
172
|
+
throw new Error(parseApiError(response.status, text));
|
|
134
173
|
}
|
|
135
174
|
return response.json();
|
|
136
175
|
}
|
|
@@ -143,7 +182,20 @@ var ApiClient = class {
|
|
|
143
182
|
});
|
|
144
183
|
if (!response.ok) {
|
|
145
184
|
const text = await response.text();
|
|
146
|
-
throw new Error(
|
|
185
|
+
throw new Error(parseApiError(response.status, text));
|
|
186
|
+
}
|
|
187
|
+
return response.json();
|
|
188
|
+
}
|
|
189
|
+
async patch(path, body) {
|
|
190
|
+
const url = `${getApiUrl()}${path}`;
|
|
191
|
+
const response = await fetch(url, {
|
|
192
|
+
method: "PATCH",
|
|
193
|
+
headers: this.getHeaders(),
|
|
194
|
+
body: JSON.stringify(body)
|
|
195
|
+
});
|
|
196
|
+
if (!response.ok) {
|
|
197
|
+
const text = await response.text();
|
|
198
|
+
throw new Error(parseApiError(response.status, text));
|
|
147
199
|
}
|
|
148
200
|
return response.json();
|
|
149
201
|
}
|
|
@@ -159,10 +211,16 @@ function isNetworkError(err) {
|
|
|
159
211
|
}
|
|
160
212
|
return false;
|
|
161
213
|
}
|
|
214
|
+
function isPermissionError(err) {
|
|
215
|
+
if (err instanceof Error) {
|
|
216
|
+
const msg = err.message.toLowerCase();
|
|
217
|
+
return msg.includes("api error 403") || msg.includes("api error 401") || msg.includes("not authorized") || msg.includes("forbidden");
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
162
221
|
|
|
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) => {
|
|
222
|
+
// src/commands/branch/create.ts
|
|
223
|
+
async function createAction(name, options) {
|
|
166
224
|
try {
|
|
167
225
|
const job = await api.post("/v1/cli/jobs/create", {
|
|
168
226
|
name
|
|
@@ -204,8 +262,10 @@ ${branch.branch_url}`);
|
|
|
204
262
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
205
263
|
process.exit(1);
|
|
206
264
|
}
|
|
207
|
-
}
|
|
208
|
-
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/commands/branch/list.ts
|
|
268
|
+
async function listAction() {
|
|
209
269
|
let branches = [];
|
|
210
270
|
let fromCache = false;
|
|
211
271
|
let cacheTime = "";
|
|
@@ -242,32 +302,34 @@ branchCommand.command("list").description("List your branches").action(async ()
|
|
|
242
302
|
return;
|
|
243
303
|
}
|
|
244
304
|
const current = getCurrentBranch();
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
const
|
|
305
|
+
const green3 = "\x1B[32m";
|
|
306
|
+
const dim3 = "\x1B[2m";
|
|
307
|
+
const reset3 = "\x1B[0m";
|
|
248
308
|
console.log("Branches:\n");
|
|
249
309
|
for (const branch of branches) {
|
|
250
310
|
const isCurrent = branch.name === current;
|
|
251
311
|
const icon = branch.status === "active" || branch.status === "active_degraded" ? "\u25CF" : "\u25CB";
|
|
252
312
|
if (isCurrent) {
|
|
253
|
-
console.log(`${
|
|
313
|
+
console.log(`${green3}* ${icon} ${branch.name}${reset3}`);
|
|
254
314
|
if (branch.branch_url) {
|
|
255
|
-
console.log(`${
|
|
315
|
+
console.log(`${green3} ${branch.branch_url}${reset3}`);
|
|
256
316
|
}
|
|
257
317
|
} else {
|
|
258
318
|
console.log(` ${icon} ${branch.name}`);
|
|
259
319
|
if (branch.branch_url) {
|
|
260
|
-
console.log(`${
|
|
320
|
+
console.log(`${dim3} ${branch.branch_url}${reset3}`);
|
|
261
321
|
}
|
|
262
322
|
}
|
|
263
323
|
console.log();
|
|
264
324
|
}
|
|
265
|
-
}
|
|
266
|
-
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/commands/branch/info.ts
|
|
328
|
+
function infoAction(name) {
|
|
267
329
|
const branchName = name || getCurrentBranch();
|
|
268
330
|
if (!branchName) {
|
|
269
331
|
console.error("\u2717 No branch specified and no current branch set");
|
|
270
|
-
console.log(" Run: ardent switch <name> or specify a branch name");
|
|
332
|
+
console.log(" Run: ardent branch switch <name> or specify a branch name");
|
|
271
333
|
return;
|
|
272
334
|
}
|
|
273
335
|
const cached = getCacheEntry("branches");
|
|
@@ -288,8 +350,10 @@ branchCommand.command("info [name]").description("Show branch details (defaults
|
|
|
288
350
|
console.log(`
|
|
289
351
|
URL: ${branch.branch_url}`);
|
|
290
352
|
}
|
|
291
|
-
}
|
|
292
|
-
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// src/commands/branch/delete.ts
|
|
356
|
+
async function deleteAction(name) {
|
|
293
357
|
const cached = getCacheEntry("branches");
|
|
294
358
|
const branch = cached?.data.find((cachedBranch) => cachedBranch.name === name);
|
|
295
359
|
if (!branch) {
|
|
@@ -313,118 +377,52 @@ branchCommand.command("delete <name>").description("Delete a branch (e.g., arden
|
|
|
313
377
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
314
378
|
process.exit(1);
|
|
315
379
|
}
|
|
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
380
|
}
|
|
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`, {});
|
|
381
|
+
|
|
382
|
+
// src/commands/branch/switch.ts
|
|
383
|
+
async function switchAction(name) {
|
|
384
|
+
let cached = getCacheEntry("branches");
|
|
385
|
+
let branch = cached?.data.find((cachedBranch) => cachedBranch.name === name);
|
|
386
|
+
if (!branch) {
|
|
387
|
+
try {
|
|
388
|
+
const result = await api.get("/v1/cli/branches");
|
|
389
|
+
if (!result.branches) {
|
|
390
|
+
throw new Error("API returned invalid response: missing branches array");
|
|
391
|
+
}
|
|
392
|
+
setCacheEntry("branches", result.branches);
|
|
393
|
+
branch = result.branches.find((remoteBranch) => remoteBranch.name === name);
|
|
394
|
+
} catch (err) {
|
|
395
|
+
if (isNetworkError(err)) {
|
|
396
|
+
if (!branch) {
|
|
397
|
+
console.error(`\u2717 Branch "${name}" not found in cache`);
|
|
398
|
+
console.log(" Run 'ardent branch list' when online to refresh");
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
373
405
|
}
|
|
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
406
|
}
|
|
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);
|
|
407
|
+
if (!branch) {
|
|
408
|
+
console.error(`\u2717 Branch "${name}" not found`);
|
|
409
|
+
console.log(" Run: ardent branch list");
|
|
402
410
|
process.exit(1);
|
|
403
411
|
}
|
|
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);
|
|
412
|
+
const previous = getCurrentBranch();
|
|
413
|
+
setCurrentBranch(name);
|
|
414
|
+
if (previous && previous !== name) {
|
|
415
|
+
console.log(`Switched from '${previous}' to '${name}'`);
|
|
416
|
+
} else {
|
|
417
|
+
console.log(`Switched to branch '${name}'`);
|
|
423
418
|
}
|
|
424
|
-
|
|
419
|
+
if (branch.branch_url) {
|
|
420
|
+
console.log(`
|
|
421
|
+
${branch.branch_url}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
425
424
|
|
|
426
|
-
// src/commands/diff.ts
|
|
427
|
-
import { Command as Command3 } from "commander";
|
|
425
|
+
// src/commands/branch/diff.ts
|
|
428
426
|
var green = "\x1B[32m";
|
|
429
427
|
var red = "\x1B[31m";
|
|
430
428
|
var yellow = "\x1B[33m";
|
|
@@ -449,7 +447,7 @@ function parseDiffRow(raw) {
|
|
|
449
447
|
last_data: raw.final_values ? JSON.parse(raw.final_values) : null
|
|
450
448
|
};
|
|
451
449
|
}
|
|
452
|
-
|
|
450
|
+
async function diffAction(options) {
|
|
453
451
|
let branch;
|
|
454
452
|
if (options.branch) {
|
|
455
453
|
branch = getBranchByName(options.branch);
|
|
@@ -466,7 +464,7 @@ var diffCommand = new Command3("diff").description("Show CDC changes on current
|
|
|
466
464
|
console.log(" Run: ardent branch list");
|
|
467
465
|
} else {
|
|
468
466
|
console.error("\u2717 No current branch set");
|
|
469
|
-
console.log(" Run: ardent switch <name> or ardent branch create <name>");
|
|
467
|
+
console.log(" Run: ardent branch switch <name> or ardent branch create <name>");
|
|
470
468
|
}
|
|
471
469
|
return;
|
|
472
470
|
}
|
|
@@ -632,13 +630,361 @@ var diffCommand = new Command3("diff").description("Show CDC changes on current
|
|
|
632
630
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
633
631
|
process.exit(1);
|
|
634
632
|
}
|
|
635
|
-
}
|
|
633
|
+
}
|
|
636
634
|
|
|
637
|
-
// src/commands/
|
|
638
|
-
|
|
639
|
-
|
|
635
|
+
// src/commands/branch/index.ts
|
|
636
|
+
var branchCommand = new Command("branch").description("Manage database branches");
|
|
637
|
+
branchCommand.command("create <name>").description("Create a new database branch").option("-s, --service <type>", "Service type", "postgres").action(createAction);
|
|
638
|
+
branchCommand.command("list").description("List your branches").action(listAction);
|
|
639
|
+
branchCommand.command("info [name]").description("Show branch details (defaults to current branch)").action(infoAction);
|
|
640
|
+
branchCommand.command("delete <name>").description("Delete a branch").action(deleteAction);
|
|
641
|
+
branchCommand.command("switch <name>").description("Switch to a different branch").action(switchAction);
|
|
642
|
+
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);
|
|
643
|
+
|
|
644
|
+
// src/commands/connector/index.ts
|
|
645
|
+
import { Command as Command2 } from "commander";
|
|
646
|
+
|
|
647
|
+
// src/lib/onboarding.ts
|
|
648
|
+
var BANNER = `
|
|
649
|
+
\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
|
|
650
|
+
\u2502 \u2502
|
|
651
|
+
\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
|
|
652
|
+
\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
|
|
653
|
+
\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
|
|
654
|
+
\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
|
|
655
|
+
\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
|
|
656
|
+
\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
|
|
657
|
+
\u2502 \u2502
|
|
658
|
+
\u2502 Git for your data infrastructure \u2502
|
|
659
|
+
\u2502 \u2502
|
|
660
|
+
\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
|
|
661
|
+
`;
|
|
662
|
+
function getOnboardingState() {
|
|
663
|
+
const token = getToken();
|
|
664
|
+
if (!token) {
|
|
665
|
+
return { state: "not_logged_in", connectorCount: 0, branchCount: 0, currentBranch: void 0 };
|
|
666
|
+
}
|
|
667
|
+
const connectors = getCacheEntry("connectors");
|
|
668
|
+
const connectorCount = connectors?.data?.length || 0;
|
|
669
|
+
if (connectorCount === 0) {
|
|
670
|
+
return { state: "no_connectors", connectorCount: 0, branchCount: 0, currentBranch: void 0 };
|
|
671
|
+
}
|
|
672
|
+
const branches = getCacheEntry("branches");
|
|
673
|
+
const branchCount = branches?.data?.length || 0;
|
|
674
|
+
const currentBranch = getCurrentBranch();
|
|
675
|
+
if (branchCount === 0) {
|
|
676
|
+
return { state: "no_branches", connectorCount, branchCount: 0, currentBranch: void 0 };
|
|
677
|
+
}
|
|
678
|
+
return { state: "ready", connectorCount, branchCount, currentBranch };
|
|
679
|
+
}
|
|
680
|
+
var dim2 = "\x1B[2m";
|
|
681
|
+
var reset2 = "\x1B[0m";
|
|
682
|
+
var green2 = "\x1B[32m";
|
|
683
|
+
function showOnboarding() {
|
|
684
|
+
const { state, connectorCount, branchCount, currentBranch } = getOnboardingState();
|
|
685
|
+
switch (state) {
|
|
686
|
+
case "not_logged_in":
|
|
687
|
+
console.log(BANNER);
|
|
688
|
+
console.log(`To get started, log in with your GitHub account:
|
|
689
|
+
|
|
690
|
+
${green2}ardent login${reset2}
|
|
691
|
+
|
|
692
|
+
Run ${dim2}ardent --help${reset2} to see all commands.
|
|
693
|
+
`);
|
|
694
|
+
break;
|
|
695
|
+
case "no_connectors":
|
|
696
|
+
console.log(`
|
|
697
|
+
${green2}\u2713${reset2} Logged in
|
|
698
|
+
|
|
699
|
+
Next step \u2014 connect your first database:
|
|
700
|
+
|
|
701
|
+
${green2}ardent connector create <type> <connection-url>${reset2}
|
|
702
|
+
|
|
703
|
+
Example:
|
|
704
|
+
${dim2}ardent connector create postgresql postgresql://user:password@host:5432/database${reset2}
|
|
705
|
+
|
|
706
|
+
Run ${dim2}ardent --help${reset2} to see all commands.
|
|
707
|
+
`);
|
|
708
|
+
break;
|
|
709
|
+
case "no_branches":
|
|
710
|
+
console.log(`
|
|
711
|
+
${green2}\u2713${reset2} Logged in ${green2}\u2713${reset2} ${connectorCount} connector${connectorCount !== 1 ? "s" : ""}
|
|
712
|
+
|
|
713
|
+
You're all set! Create your first branch:
|
|
714
|
+
|
|
715
|
+
${green2}ardent branch create <name>${reset2}
|
|
716
|
+
|
|
717
|
+
Example:
|
|
718
|
+
${dim2}ardent branch create my-feature${reset2}
|
|
719
|
+
|
|
720
|
+
Run ${dim2}ardent --help${reset2} to see all commands.
|
|
721
|
+
`);
|
|
722
|
+
return true;
|
|
723
|
+
case "ready":
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
return true;
|
|
727
|
+
}
|
|
728
|
+
function showNextStep() {
|
|
729
|
+
const { state } = getOnboardingState();
|
|
730
|
+
switch (state) {
|
|
731
|
+
case "no_connectors":
|
|
732
|
+
console.log(`
|
|
733
|
+
Next step \u2014 connect your first database:
|
|
734
|
+
|
|
735
|
+
${green2}ardent connector create <type> <connection-url>${reset2}
|
|
736
|
+
|
|
737
|
+
Example:
|
|
738
|
+
${dim2}ardent connector create postgresql postgresql://user:password@host:5432/database${reset2}
|
|
739
|
+
`);
|
|
740
|
+
break;
|
|
741
|
+
case "no_branches":
|
|
742
|
+
console.log(`
|
|
743
|
+
Next step \u2014 create your first branch:
|
|
744
|
+
|
|
745
|
+
${green2}ardent branch create <name>${reset2}
|
|
746
|
+
|
|
747
|
+
Example:
|
|
748
|
+
${dim2}ardent branch create my-feature${reset2}
|
|
749
|
+
`);
|
|
750
|
+
break;
|
|
751
|
+
case "ready":
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/commands/connector/create.ts
|
|
757
|
+
function parsePostgresUrl(url) {
|
|
758
|
+
const atIndex = url.lastIndexOf("@");
|
|
759
|
+
const credentialsPart = atIndex > 0 ? url.substring(0, atIndex) : url;
|
|
760
|
+
const hostPart = atIndex > 0 ? url.substring(atIndex) : "";
|
|
761
|
+
const cleanedUrl = credentialsPart.replace(/\\!/g, "!") + hostPart;
|
|
762
|
+
const parsed = new URL(cleanedUrl);
|
|
763
|
+
const host = parsed.hostname;
|
|
764
|
+
const port = parsed.port || "5432";
|
|
765
|
+
const username = decodeURIComponent(parsed.username);
|
|
766
|
+
const password = parsed.password ? decodeURIComponent(parsed.password) : "";
|
|
767
|
+
if (!host) throw new Error("Host required in connection URL");
|
|
768
|
+
if (!username) throw new Error("Username required in connection URL");
|
|
769
|
+
return { host, port, username, password };
|
|
770
|
+
}
|
|
771
|
+
async function createAction2(type, url, options) {
|
|
772
|
+
const supportedTypes = ["postgresql"];
|
|
773
|
+
if (!supportedTypes.includes(type.toLowerCase())) {
|
|
774
|
+
console.error(`\u2717 Unsupported type: ${type}`);
|
|
775
|
+
console.error(` Supported: ${supportedTypes.join(", ")}`);
|
|
776
|
+
process.exit(1);
|
|
777
|
+
}
|
|
778
|
+
const isByoc = Boolean(options.byoc);
|
|
779
|
+
if (isByoc) {
|
|
780
|
+
if (!options.apiKey || !options.projectId) {
|
|
781
|
+
console.error("\u2717 --api-key and --project-id are required when using --byoc");
|
|
782
|
+
process.exit(1);
|
|
783
|
+
}
|
|
784
|
+
if (options.byoc !== "neon") {
|
|
785
|
+
console.error(`\u2717 Unsupported BYOC provider: ${options.byoc}`);
|
|
786
|
+
console.error(" Supported: neon");
|
|
787
|
+
process.exit(1);
|
|
788
|
+
}
|
|
789
|
+
} else if (!url) {
|
|
790
|
+
console.error("\u2717 Connection URL required");
|
|
791
|
+
console.error(" Example: ardent connector create postgresql postgresql://user:pass@host:5432/db");
|
|
792
|
+
process.exit(1);
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
const connectorName = options.name || (isByoc ? "my-neon-connection" : "my-postgresql-connection");
|
|
796
|
+
let createPayload;
|
|
797
|
+
if (isByoc) {
|
|
798
|
+
console.log("Creating connector (BYOC Neon)...");
|
|
799
|
+
createPayload = {
|
|
800
|
+
name: connectorName,
|
|
801
|
+
service_name: "postgresql",
|
|
802
|
+
byoc: options.byoc,
|
|
803
|
+
neon_api_key: options.apiKey,
|
|
804
|
+
neon_project_id: options.projectId,
|
|
805
|
+
connection_details: {}
|
|
806
|
+
};
|
|
807
|
+
} else {
|
|
808
|
+
const parsed = parsePostgresUrl(url);
|
|
809
|
+
if (!parsed.password) {
|
|
810
|
+
console.error("\u2717 Password required in connection URL");
|
|
811
|
+
console.error(" Example: postgresql://user:password@host:5432/db");
|
|
812
|
+
process.exit(1);
|
|
813
|
+
}
|
|
814
|
+
console.log("Creating connector...");
|
|
815
|
+
createPayload = {
|
|
816
|
+
name: connectorName,
|
|
817
|
+
service_name: "postgresql",
|
|
818
|
+
connection_details: {
|
|
819
|
+
host: parsed.host,
|
|
820
|
+
port: parsed.port,
|
|
821
|
+
username: parsed.username,
|
|
822
|
+
password: parsed.password
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
const created = await api.post("/v1/connectors", createPayload);
|
|
827
|
+
const connectorId = created.id;
|
|
828
|
+
if (isByoc) {
|
|
829
|
+
console.log("Setting up branching engine...");
|
|
830
|
+
try {
|
|
831
|
+
await api.post(`/v1/connectors/${connectorId}/engine-setup`, {});
|
|
832
|
+
} catch (setupErr) {
|
|
833
|
+
console.error("\u2717 Engine setup failed. To retry, delete and recreate:");
|
|
834
|
+
console.error(` ardent connector delete ${connectorName}`);
|
|
835
|
+
throw setupErr;
|
|
836
|
+
}
|
|
837
|
+
} else {
|
|
838
|
+
console.log("Discovering schema...");
|
|
839
|
+
await api.post(`/v1/connectors/${connectorId}/discover`, {});
|
|
840
|
+
console.log("Setting selection...");
|
|
841
|
+
await api.post(`/v1/connectors/${connectorId}/selection`, {
|
|
842
|
+
selected_paths: ["*"]
|
|
843
|
+
});
|
|
844
|
+
const connector = await api.get(`/v1/connectors/${connectorId}`);
|
|
845
|
+
if (connector.branching_engine_status === "configuration_verified") {
|
|
846
|
+
console.log("Setting up branching engine...");
|
|
847
|
+
await api.post(`/v1/connectors/${connectorId}/engine-setup`, {});
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
const finalConnector = await api.get(`/v1/connectors/${connectorId}`);
|
|
851
|
+
const newConnector = {
|
|
852
|
+
id: connectorId,
|
|
853
|
+
name: connectorName,
|
|
854
|
+
service_name: "postgresql",
|
|
855
|
+
status: finalConnector.branching_engine_status ?? finalConnector.connection_status ?? "pending"
|
|
856
|
+
};
|
|
857
|
+
const cached = getCacheEntry("connectors");
|
|
858
|
+
const cachedConnectors = cached?.data || [];
|
|
859
|
+
cachedConnectors.push(newConnector);
|
|
860
|
+
setCacheEntry("connectors", cachedConnectors);
|
|
861
|
+
console.log("\u2713 Connector created and ready");
|
|
862
|
+
console.log(` ID: ${connectorId}`);
|
|
863
|
+
showNextStep();
|
|
864
|
+
} catch (err) {
|
|
865
|
+
if (isPermissionError(err)) {
|
|
866
|
+
console.error("\u2717 You don't have permission to create connectors.");
|
|
867
|
+
console.error("");
|
|
868
|
+
console.error(" Ask your organization admin to either:");
|
|
869
|
+
console.error(" \u2022 Create the connector for you, or");
|
|
870
|
+
console.error(" \u2022 Upgrade your role to Admin");
|
|
871
|
+
process.exit(1);
|
|
872
|
+
}
|
|
873
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
874
|
+
process.exit(1);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// src/commands/connector/list.ts
|
|
879
|
+
async function listAction2() {
|
|
880
|
+
let connectors = [];
|
|
881
|
+
let fromCache = false;
|
|
882
|
+
let cacheTime = "";
|
|
883
|
+
try {
|
|
884
|
+
const result = await api.get("/v1/cli/connectors");
|
|
885
|
+
if (!result.connectors) {
|
|
886
|
+
throw new Error("API returned invalid response: missing connectors array");
|
|
887
|
+
}
|
|
888
|
+
connectors = result.connectors;
|
|
889
|
+
setCacheEntry("connectors", connectors);
|
|
890
|
+
} catch (err) {
|
|
891
|
+
if (isNetworkError(err)) {
|
|
892
|
+
const cached = getCacheEntry("connectors");
|
|
893
|
+
if (cached) {
|
|
894
|
+
connectors = cached.data;
|
|
895
|
+
fromCache = true;
|
|
896
|
+
cacheTime = formatCacheTime(cached.updated_at);
|
|
897
|
+
} else {
|
|
898
|
+
console.error("\u2717 Offline and no cached data available");
|
|
899
|
+
process.exit(1);
|
|
900
|
+
}
|
|
901
|
+
} else {
|
|
902
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
903
|
+
process.exit(1);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
if (fromCache) {
|
|
907
|
+
console.log(`\u26A0 Offline - showing cached data from ${cacheTime}
|
|
908
|
+
`);
|
|
909
|
+
}
|
|
910
|
+
if (connectors.length === 0) {
|
|
911
|
+
console.log("No connectors found");
|
|
912
|
+
console.log(" Create one with: ardent connector create postgresql <url>");
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
console.log("Connectors:\n");
|
|
916
|
+
for (const connector of connectors) {
|
|
917
|
+
const icon = connector.status === "healthy" ? "\u25CF" : "\u25CB";
|
|
918
|
+
console.log(` ${icon} ${connector.service_name}`);
|
|
919
|
+
console.log(` Name: ${connector.name}`);
|
|
920
|
+
console.log();
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// src/commands/connector/delete.ts
|
|
925
|
+
async function deleteAction2(name) {
|
|
926
|
+
const cached = getCacheEntry("connectors");
|
|
927
|
+
let connector = cached?.data.find((c) => c.name === name);
|
|
928
|
+
if (!connector) {
|
|
929
|
+
try {
|
|
930
|
+
const result = await api.get("/v1/cli/connectors");
|
|
931
|
+
if (result.connectors) {
|
|
932
|
+
setCacheEntry("connectors", result.connectors);
|
|
933
|
+
connector = result.connectors.find((c) => c.name === name);
|
|
934
|
+
}
|
|
935
|
+
} catch (err) {
|
|
936
|
+
if (isNetworkError(err)) {
|
|
937
|
+
console.error("\u2717 Connector not found in cache and offline");
|
|
938
|
+
process.exit(1);
|
|
939
|
+
}
|
|
940
|
+
throw err;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
if (!connector) {
|
|
944
|
+
console.error(`\u2717 Connector "${name}" not found`);
|
|
945
|
+
console.log(" Run: ardent connector list");
|
|
946
|
+
process.exit(1);
|
|
947
|
+
}
|
|
948
|
+
try {
|
|
949
|
+
console.log("Deleting connector...");
|
|
950
|
+
await api.delete(`/v1/connectors/${connector.id}`);
|
|
951
|
+
const currentCache = getCacheEntry("connectors");
|
|
952
|
+
if (currentCache?.data) {
|
|
953
|
+
const updatedConnectors = currentCache.data.filter((c) => c.id !== connector.id);
|
|
954
|
+
setCacheEntry("connectors", updatedConnectors);
|
|
955
|
+
}
|
|
956
|
+
console.log("\u2713 Connector deleted");
|
|
957
|
+
} catch (err) {
|
|
958
|
+
if (isNetworkError(err)) {
|
|
959
|
+
console.error("\u2717 Cannot delete connector while offline");
|
|
960
|
+
process.exit(1);
|
|
961
|
+
}
|
|
962
|
+
if (isPermissionError(err)) {
|
|
963
|
+
console.error("\u2717 You don't have permission to delete connectors.");
|
|
964
|
+
console.error("");
|
|
965
|
+
console.error(" Ask your organization admin to either:");
|
|
966
|
+
console.error(" \u2022 Delete the connector for you, or");
|
|
967
|
+
console.error(" \u2022 Upgrade your role to Admin");
|
|
968
|
+
process.exit(1);
|
|
969
|
+
}
|
|
970
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
971
|
+
process.exit(1);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// src/commands/connector/index.ts
|
|
976
|
+
var connectorCommand = new Command2("connector").description("Manage database connectors");
|
|
977
|
+
connectorCommand.command("create <type> [url]").description("Create a new connector").option("-n, --name <name>", "Connector name").option("--byoc <provider>", "Bring your own Neon project (e.g. neon)").option("--api-key <key>", "Neon API key (required with --byoc)").option("--project-id <id>", "Neon project ID (required with --byoc)").action(createAction2);
|
|
978
|
+
connectorCommand.command("list").description("List your connectors").action(listAction2);
|
|
979
|
+
connectorCommand.command("delete <name>").description("Delete a connector by name").action(deleteAction2);
|
|
980
|
+
|
|
981
|
+
// src/commands/invite/index.ts
|
|
982
|
+
import { Command as Command3 } from "commander";
|
|
983
|
+
|
|
984
|
+
// src/commands/invite/send.ts
|
|
985
|
+
async function sendAction(email, role) {
|
|
640
986
|
const validRoles = ["owner", "admin", "member", "viewer"];
|
|
641
|
-
if (!validRoles.includes(role)) {
|
|
987
|
+
if (!validRoles.includes(role.toLowerCase())) {
|
|
642
988
|
console.error(`\u2717 Invalid role: ${role}`);
|
|
643
989
|
console.error(` Valid roles: ${validRoles.join(", ")}`);
|
|
644
990
|
process.exit(1);
|
|
@@ -646,30 +992,31 @@ async function sendInvite(email, role) {
|
|
|
646
992
|
try {
|
|
647
993
|
const result = await api.post("/v1/cli/invite", {
|
|
648
994
|
email,
|
|
649
|
-
role
|
|
995
|
+
role: role.toLowerCase()
|
|
650
996
|
});
|
|
651
|
-
console.log(`\u2713
|
|
997
|
+
console.log(`\u2713 Invite sent to ${result.email}`);
|
|
652
998
|
console.log(` Role: ${result.role}`);
|
|
653
999
|
console.log(` Org: ${result.org_name}`);
|
|
654
|
-
console.log();
|
|
655
|
-
console.log("They'll receive an email with a link to join.");
|
|
656
1000
|
} catch (err) {
|
|
657
1001
|
if (isNetworkError(err)) {
|
|
658
1002
|
console.error("\u2717 Cannot send invite while offline");
|
|
659
1003
|
process.exit(1);
|
|
660
1004
|
}
|
|
1005
|
+
if (isPermissionError(err)) {
|
|
1006
|
+
console.error("\u2717 You don't have permission to invite users.");
|
|
1007
|
+
console.error("");
|
|
1008
|
+
console.error(" Ask your organization admin to either:");
|
|
1009
|
+
console.error(" \u2022 Send the invite for you, or");
|
|
1010
|
+
console.error(" \u2022 Upgrade your role to Admin");
|
|
1011
|
+
process.exit(1);
|
|
1012
|
+
}
|
|
661
1013
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
662
1014
|
process.exit(1);
|
|
663
1015
|
}
|
|
664
1016
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
return;
|
|
669
|
-
}
|
|
670
|
-
await sendInvite(email, role);
|
|
671
|
-
});
|
|
672
|
-
inviteCommand.command("list").description("List pending invites").action(async () => {
|
|
1017
|
+
|
|
1018
|
+
// src/commands/invite/list.ts
|
|
1019
|
+
async function listAction3() {
|
|
673
1020
|
try {
|
|
674
1021
|
const result = await api.get("/v1/cli/invites");
|
|
675
1022
|
if (!result.invites) {
|
|
@@ -695,8 +1042,10 @@ inviteCommand.command("list").description("List pending invites").action(async (
|
|
|
695
1042
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
696
1043
|
process.exit(1);
|
|
697
1044
|
}
|
|
698
|
-
}
|
|
699
|
-
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// src/commands/invite/delete.ts
|
|
1048
|
+
async function deleteAction3(email) {
|
|
700
1049
|
try {
|
|
701
1050
|
await api.delete("/v1/cli/invites", { email });
|
|
702
1051
|
console.log(`\u2713 Invite for ${email} deleted`);
|
|
@@ -708,58 +1057,114 @@ inviteCommand.command("delete <email>").description("Revoke a pending invite").a
|
|
|
708
1057
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
709
1058
|
process.exit(1);
|
|
710
1059
|
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// src/commands/invite/index.ts
|
|
1063
|
+
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) => {
|
|
1064
|
+
if (!email) {
|
|
1065
|
+
inviteCommand.help();
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
await sendAction(email, role);
|
|
711
1069
|
});
|
|
1070
|
+
inviteCommand.command("list").description("List pending invites").action(listAction3);
|
|
1071
|
+
inviteCommand.command("delete <email>").description("Revoke a pending invite").action(deleteAction3);
|
|
712
1072
|
|
|
713
|
-
// src/commands/
|
|
714
|
-
import { Command as
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
process.exit(1);
|
|
736
|
-
}
|
|
1073
|
+
// src/commands/org/index.ts
|
|
1074
|
+
import { Command as Command4 } from "commander";
|
|
1075
|
+
|
|
1076
|
+
// src/commands/org/members.ts
|
|
1077
|
+
async function membersAction() {
|
|
1078
|
+
try {
|
|
1079
|
+
const result = await api.get("/v1/cli/members");
|
|
1080
|
+
if (!result.members || result.members.length === 0) {
|
|
1081
|
+
console.log("No members found");
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
console.log("Organization members:\n");
|
|
1085
|
+
for (const member of result.members) {
|
|
1086
|
+
const name = member.name ? ` (${member.name})` : "";
|
|
1087
|
+
console.log(` ${member.email}${name}`);
|
|
1088
|
+
console.log(` Role: ${member.role}`);
|
|
1089
|
+
console.log();
|
|
1090
|
+
}
|
|
1091
|
+
} catch (err) {
|
|
1092
|
+
if (isNetworkError(err)) {
|
|
1093
|
+
console.error("\u2717 Cannot list members while offline");
|
|
1094
|
+
process.exit(1);
|
|
737
1095
|
}
|
|
1096
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1097
|
+
process.exit(1);
|
|
738
1098
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// src/commands/org/set-role.ts
|
|
1102
|
+
async function setRoleAction(email, role) {
|
|
1103
|
+
const validRoles = ["owner", "admin", "member", "viewer"];
|
|
1104
|
+
if (!validRoles.includes(role)) {
|
|
1105
|
+
console.error(`\u2717 Invalid role: ${role}`);
|
|
1106
|
+
console.error(` Valid roles: ${validRoles.join(", ")}`);
|
|
742
1107
|
process.exit(1);
|
|
743
1108
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1109
|
+
try {
|
|
1110
|
+
const result = await api.patch("/v1/cli/members/role", { email, role });
|
|
1111
|
+
console.log(`\u2713 Updated ${result.email} to ${result.new_role}`);
|
|
1112
|
+
} catch (err) {
|
|
1113
|
+
if (isNetworkError(err)) {
|
|
1114
|
+
console.error("\u2717 Cannot update role while offline");
|
|
1115
|
+
process.exit(1);
|
|
1116
|
+
}
|
|
1117
|
+
if (isPermissionError(err)) {
|
|
1118
|
+
console.error("\u2717 You don't have permission to update member roles.");
|
|
1119
|
+
console.error("");
|
|
1120
|
+
console.error(" Only org admins and owners can change roles.");
|
|
1121
|
+
process.exit(1);
|
|
1122
|
+
}
|
|
1123
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1124
|
+
process.exit(1);
|
|
750
1125
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// src/commands/org/remove.ts
|
|
1129
|
+
async function removeAction(email) {
|
|
1130
|
+
try {
|
|
1131
|
+
await api.delete("/v1/cli/members", { email });
|
|
1132
|
+
console.log(`\u2713 Removed ${email} from organization`);
|
|
1133
|
+
} catch (err) {
|
|
1134
|
+
if (isNetworkError(err)) {
|
|
1135
|
+
console.error("\u2717 Cannot remove member while offline");
|
|
1136
|
+
process.exit(1);
|
|
1137
|
+
}
|
|
1138
|
+
if (isPermissionError(err)) {
|
|
1139
|
+
console.error("\u2717 You don't have permission to remove members.");
|
|
1140
|
+
console.error("");
|
|
1141
|
+
console.error(" Only org admins and owners can remove members.");
|
|
1142
|
+
process.exit(1);
|
|
1143
|
+
}
|
|
1144
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1145
|
+
process.exit(1);
|
|
754
1146
|
}
|
|
755
|
-
}
|
|
1147
|
+
}
|
|
756
1148
|
|
|
757
|
-
// src/commands/
|
|
758
|
-
|
|
759
|
-
|
|
1149
|
+
// src/commands/org/index.ts
|
|
1150
|
+
var orgCommand = new Command4("org").description("Manage organization members");
|
|
1151
|
+
orgCommand.command("members").description("List organization members").action(membersAction);
|
|
1152
|
+
orgCommand.command("set-role <email> <role>").description("Update a member's role (owner/admin/member/viewer)").action(setRoleAction);
|
|
1153
|
+
orgCommand.command("remove <email>").description("Remove a member from the organization").action(removeAction);
|
|
1154
|
+
|
|
1155
|
+
// src/commands/auth/index.ts
|
|
1156
|
+
import { Command as Command5 } from "commander";
|
|
1157
|
+
|
|
1158
|
+
// src/commands/auth/login.ts
|
|
1159
|
+
async function loginAction(options) {
|
|
760
1160
|
if (options.token) {
|
|
761
1161
|
setConfig("token", options.token);
|
|
762
1162
|
console.log("\u2713 Logged in successfully");
|
|
1163
|
+
try {
|
|
1164
|
+
await bootstrapCache();
|
|
1165
|
+
} catch {
|
|
1166
|
+
}
|
|
1167
|
+
showNextStep();
|
|
763
1168
|
return;
|
|
764
1169
|
}
|
|
765
1170
|
console.log("Opening browser for authentication...");
|
|
@@ -793,6 +1198,11 @@ var loginCommand = new Command6("login").description("Login to Ardent").option("
|
|
|
793
1198
|
if (result.status === "completed") {
|
|
794
1199
|
setConfig("token", result.token);
|
|
795
1200
|
console.log("\n\u2713 Logged in successfully");
|
|
1201
|
+
try {
|
|
1202
|
+
await bootstrapCache();
|
|
1203
|
+
} catch {
|
|
1204
|
+
}
|
|
1205
|
+
showNextStep();
|
|
796
1206
|
return;
|
|
797
1207
|
}
|
|
798
1208
|
if (result.status === "expired") {
|
|
@@ -811,12 +1221,16 @@ var loginCommand = new Command6("login").description("Login to Ardent").option("
|
|
|
811
1221
|
console.error("\u2717 Login failed:", error instanceof Error ? error.message : error);
|
|
812
1222
|
process.exit(1);
|
|
813
1223
|
}
|
|
814
|
-
}
|
|
815
|
-
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// src/commands/auth/logout.ts
|
|
1227
|
+
function logoutAction() {
|
|
816
1228
|
clearConfig();
|
|
817
1229
|
console.log("\u2713 Logged out");
|
|
818
|
-
}
|
|
819
|
-
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// src/commands/auth/status.ts
|
|
1233
|
+
function statusAction() {
|
|
820
1234
|
const token = getConfig("token");
|
|
821
1235
|
if (token) {
|
|
822
1236
|
console.log("\u2713 Authenticated");
|
|
@@ -825,60 +1239,68 @@ var statusCommand = new Command6("status").description("Show status").action(()
|
|
|
825
1239
|
console.log("\u2717 Not authenticated");
|
|
826
1240
|
console.log(" Run: ardent login");
|
|
827
1241
|
}
|
|
828
|
-
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// src/commands/auth/index.ts
|
|
1245
|
+
var loginCommand = new Command5("login").description("Login to Ardent").option("-t, --token <token>", "API token (skip browser login)").action(loginAction);
|
|
1246
|
+
var logoutCommand = new Command5("logout").description("Logout from Ardent").action(logoutAction);
|
|
1247
|
+
var statusCommand = new Command5("status").description("Show status").action(statusAction);
|
|
829
1248
|
|
|
830
1249
|
// src/index.ts
|
|
831
|
-
var BANNER = `
|
|
832
|
-
\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
|
|
833
|
-
\u2502 \u2502
|
|
834
|
-
\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
|
|
835
|
-
\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
|
|
836
|
-
\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
|
|
837
|
-
\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
|
|
838
|
-
\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
|
|
839
|
-
\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
|
|
840
|
-
\u2502 \u2502
|
|
841
|
-
\u2502 Git for your data infrastructure \u2502
|
|
842
|
-
\u2502 \u2502
|
|
843
|
-
\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
|
|
844
|
-
`;
|
|
845
1250
|
var HELP_TEXT = `
|
|
846
1251
|
USAGE
|
|
847
1252
|
ardent <command> [options]
|
|
848
1253
|
|
|
849
|
-
|
|
850
|
-
login
|
|
851
|
-
logout
|
|
852
|
-
status
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
connector
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1254
|
+
AUTHENTICATION
|
|
1255
|
+
login Login via browser (GitHub OAuth)
|
|
1256
|
+
logout Clear stored credentials
|
|
1257
|
+
status Check authentication status
|
|
1258
|
+
|
|
1259
|
+
CONNECTORS
|
|
1260
|
+
connector create Connect a database (postgresql, snowflake, etc.)
|
|
1261
|
+
connector list List your connectors
|
|
1262
|
+
connector delete Delete a connector
|
|
1263
|
+
|
|
1264
|
+
BRANCHES
|
|
1265
|
+
branch create Create a new database branch (auto-switches to it)
|
|
1266
|
+
branch list List your branches (* = current)
|
|
1267
|
+
branch info Show branch details (URL, status, etc.)
|
|
1268
|
+
branch delete Delete a branch
|
|
1269
|
+
branch switch Switch to a different branch
|
|
1270
|
+
branch diff Show all changes since branch creation
|
|
1271
|
+
|
|
1272
|
+
TEAM
|
|
1273
|
+
invite <email> Invite a user to your organization
|
|
1274
|
+
invite list List pending invites
|
|
1275
|
+
invite delete Revoke a pending invite
|
|
1276
|
+
org members List organization members
|
|
1277
|
+
org set-role Update a member's role
|
|
1278
|
+
org remove Remove a member from the organization
|
|
865
1279
|
|
|
866
1280
|
OPTIONS
|
|
867
|
-
--help
|
|
868
|
-
--version
|
|
1281
|
+
--help Show help for any command
|
|
1282
|
+
--version Show CLI version
|
|
869
1283
|
|
|
870
1284
|
EXAMPLES
|
|
871
1285
|
ardent login
|
|
872
|
-
ardent connector create postgresql postgresql://user:
|
|
1286
|
+
ardent connector create postgresql postgresql://user:password@your-db-host.com:5432/database
|
|
873
1287
|
ardent branch create my-feature
|
|
874
|
-
ardent branch
|
|
875
|
-
ardent
|
|
876
|
-
ardent invite teammate@company.com admin
|
|
877
|
-
|
|
878
|
-
For more help, visit https://docs.tryardent.com/cli
|
|
1288
|
+
ardent branch switch my-feature
|
|
1289
|
+
ardent org members
|
|
879
1290
|
`;
|
|
880
|
-
var
|
|
881
|
-
program
|
|
1291
|
+
var CLI_VERSION = getCliVersion();
|
|
1292
|
+
var program = new Command6();
|
|
1293
|
+
function getCliVersion() {
|
|
1294
|
+
try {
|
|
1295
|
+
const packageUrl = new URL("../package.json", import.meta.url);
|
|
1296
|
+
const raw = readFileSync2(packageUrl, "utf-8");
|
|
1297
|
+
const parsed = JSON.parse(raw);
|
|
1298
|
+
return parsed.version ?? "unknown";
|
|
1299
|
+
} catch {
|
|
1300
|
+
return "unknown";
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
program.name("ardent").description("CLI for Ardent database branching").version(CLI_VERSION).configureHelp({
|
|
882
1304
|
formatHelp: () => BANNER + HELP_TEXT
|
|
883
1305
|
});
|
|
884
1306
|
program.addCommand(loginCommand);
|
|
@@ -886,7 +1308,28 @@ program.addCommand(logoutCommand);
|
|
|
886
1308
|
program.addCommand(statusCommand);
|
|
887
1309
|
program.addCommand(connectorCommand);
|
|
888
1310
|
program.addCommand(branchCommand);
|
|
889
|
-
program.addCommand(switchCommand);
|
|
890
|
-
program.addCommand(diffCommand);
|
|
891
1311
|
program.addCommand(inviteCommand);
|
|
1312
|
+
program.addCommand(orgCommand);
|
|
1313
|
+
program.on("command:*", (operands) => {
|
|
1314
|
+
const unknown = operands[0];
|
|
1315
|
+
console.error(`Unknown command: ${unknown}
|
|
1316
|
+
`);
|
|
1317
|
+
if (unknown === "switch") {
|
|
1318
|
+
console.error(` Did you mean: ardent branch switch <name>
|
|
1319
|
+
`);
|
|
1320
|
+
} else if (unknown === "diff") {
|
|
1321
|
+
console.error(` Did you mean: ardent branch diff
|
|
1322
|
+
`);
|
|
1323
|
+
}
|
|
1324
|
+
console.error(`Run ardent --help for available commands.`);
|
|
1325
|
+
process.exit(1);
|
|
1326
|
+
});
|
|
1327
|
+
var args = process.argv.slice(2);
|
|
1328
|
+
if (args.length === 0) {
|
|
1329
|
+
const showedOnboarding = showOnboarding();
|
|
1330
|
+
if (showedOnboarding) {
|
|
1331
|
+
process.exit(0);
|
|
1332
|
+
}
|
|
1333
|
+
program.help();
|
|
1334
|
+
}
|
|
892
1335
|
program.parse();
|