ardent-cli 0.0.9 → 0.0.11
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 +565 -56
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import { Command as
|
|
4
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
5
|
+
import { Command as Command7 } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/branch/index.ts
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
|
|
10
|
+
// src/lib/api.ts
|
|
11
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
12
|
+
|
|
10
13
|
// src/lib/config.ts
|
|
11
14
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
12
15
|
import { homedir } from "os";
|
|
@@ -104,9 +107,8 @@ async function bootstrapCache() {
|
|
|
104
107
|
"Content-Type": "application/json",
|
|
105
108
|
"Authorization": `Bearer ${token}`
|
|
106
109
|
};
|
|
107
|
-
const [connectorsRes,
|
|
110
|
+
const [connectorsRes, meRes] = await Promise.all([
|
|
108
111
|
fetch(`${apiUrl}/v1/cli/connectors`, { headers }),
|
|
109
|
-
fetch(`${apiUrl}/v1/cli/branches`, { headers }),
|
|
110
112
|
fetch(`${apiUrl}/v1/cli/me`, { headers })
|
|
111
113
|
]);
|
|
112
114
|
let connectorCount = 0;
|
|
@@ -117,20 +119,38 @@ async function bootstrapCache() {
|
|
|
117
119
|
setCacheEntry("connectors", connectors);
|
|
118
120
|
connectorCount = connectors.length;
|
|
119
121
|
}
|
|
120
|
-
if (branchesRes.ok) {
|
|
121
|
-
const data = await branchesRes.json();
|
|
122
|
-
const branches = data.branches || [];
|
|
123
|
-
setCacheEntry("branches", branches);
|
|
124
|
-
branchCount = branches.length;
|
|
125
|
-
}
|
|
126
122
|
if (meRes.ok) {
|
|
127
123
|
const user = await meRes.json();
|
|
128
124
|
setConfig("user", user);
|
|
129
125
|
}
|
|
126
|
+
const currentConnectorId = getConfig("currentConnectorId");
|
|
127
|
+
if (currentConnectorId) {
|
|
128
|
+
const branchesRes = await fetch(
|
|
129
|
+
`${apiUrl}/v1/cli/branches?connector_id=${currentConnectorId}`,
|
|
130
|
+
{ headers }
|
|
131
|
+
);
|
|
132
|
+
if (branchesRes.ok) {
|
|
133
|
+
const data = await branchesRes.json();
|
|
134
|
+
const branches = data.branches || [];
|
|
135
|
+
setCacheEntry("branches", branches);
|
|
136
|
+
branchCount = branches.length;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
130
139
|
return { connectors: connectorCount, branches: branchCount };
|
|
131
140
|
}
|
|
132
141
|
|
|
133
142
|
// src/lib/api.ts
|
|
143
|
+
function getCliVersion() {
|
|
144
|
+
try {
|
|
145
|
+
const packageUrl = new URL("../package.json", import.meta.url);
|
|
146
|
+
const raw = readFileSync2(packageUrl, "utf-8");
|
|
147
|
+
const parsed = JSON.parse(raw);
|
|
148
|
+
return parsed.version ?? "unknown";
|
|
149
|
+
} catch {
|
|
150
|
+
return "unknown";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
var CLI_VERSION = getCliVersion();
|
|
134
154
|
function parseApiError(status, text) {
|
|
135
155
|
try {
|
|
136
156
|
const json = JSON.parse(text);
|
|
@@ -139,6 +159,20 @@ function parseApiError(status, text) {
|
|
|
139
159
|
}
|
|
140
160
|
return `API error ${status}: ${text}`;
|
|
141
161
|
}
|
|
162
|
+
function handleUpgradeRequired(text) {
|
|
163
|
+
try {
|
|
164
|
+
const json = JSON.parse(text);
|
|
165
|
+
if (json.detail) {
|
|
166
|
+
console.error(`
|
|
167
|
+
\u2717 ${json.detail}
|
|
168
|
+
`);
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
console.error("\n\u2717 Your CLI is outdated. Please update:\n");
|
|
172
|
+
}
|
|
173
|
+
console.error(" npm install -g ardent-cli@latest\n");
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
142
176
|
var ApiClient = class {
|
|
143
177
|
getHeaders() {
|
|
144
178
|
const token = getToken();
|
|
@@ -150,20 +184,28 @@ var ApiClient = class {
|
|
|
150
184
|
}
|
|
151
185
|
return {
|
|
152
186
|
"Content-Type": "application/json",
|
|
153
|
-
"Authorization": `Bearer ${token}
|
|
187
|
+
"Authorization": `Bearer ${token}`,
|
|
188
|
+
"X-CLI-Version": CLI_VERSION
|
|
154
189
|
};
|
|
155
190
|
}
|
|
191
|
+
async handleResponse(response) {
|
|
192
|
+
if (response.status === 426) {
|
|
193
|
+
const text = await response.text();
|
|
194
|
+
handleUpgradeRequired(text);
|
|
195
|
+
}
|
|
196
|
+
if (!response.ok) {
|
|
197
|
+
const text = await response.text();
|
|
198
|
+
throw new Error(parseApiError(response.status, text));
|
|
199
|
+
}
|
|
200
|
+
return response.json();
|
|
201
|
+
}
|
|
156
202
|
async get(path) {
|
|
157
203
|
const url = `${getApiUrl()}${path}`;
|
|
158
204
|
const response = await fetch(url, {
|
|
159
205
|
method: "GET",
|
|
160
206
|
headers: this.getHeaders()
|
|
161
207
|
});
|
|
162
|
-
|
|
163
|
-
const text = await response.text();
|
|
164
|
-
throw new Error(parseApiError(response.status, text));
|
|
165
|
-
}
|
|
166
|
-
return response.json();
|
|
208
|
+
return this.handleResponse(response);
|
|
167
209
|
}
|
|
168
210
|
async post(path, body) {
|
|
169
211
|
const url = `${getApiUrl()}${path}`;
|
|
@@ -172,11 +214,7 @@ var ApiClient = class {
|
|
|
172
214
|
headers: this.getHeaders(),
|
|
173
215
|
body: JSON.stringify(body)
|
|
174
216
|
});
|
|
175
|
-
|
|
176
|
-
const text = await response.text();
|
|
177
|
-
throw new Error(parseApiError(response.status, text));
|
|
178
|
-
}
|
|
179
|
-
return response.json();
|
|
217
|
+
return this.handleResponse(response);
|
|
180
218
|
}
|
|
181
219
|
async delete(path, body) {
|
|
182
220
|
const url = `${getApiUrl()}${path}`;
|
|
@@ -185,11 +223,7 @@ var ApiClient = class {
|
|
|
185
223
|
headers: this.getHeaders(),
|
|
186
224
|
body: body ? JSON.stringify(body) : void 0
|
|
187
225
|
});
|
|
188
|
-
|
|
189
|
-
const text = await response.text();
|
|
190
|
-
throw new Error(parseApiError(response.status, text));
|
|
191
|
-
}
|
|
192
|
-
return response.json();
|
|
226
|
+
return this.handleResponse(response);
|
|
193
227
|
}
|
|
194
228
|
async patch(path, body) {
|
|
195
229
|
const url = `${getApiUrl()}${path}`;
|
|
@@ -198,11 +232,7 @@ var ApiClient = class {
|
|
|
198
232
|
headers: this.getHeaders(),
|
|
199
233
|
body: JSON.stringify(body)
|
|
200
234
|
});
|
|
201
|
-
|
|
202
|
-
const text = await response.text();
|
|
203
|
-
throw new Error(parseApiError(response.status, text));
|
|
204
|
-
}
|
|
205
|
-
return response.json();
|
|
235
|
+
return this.handleResponse(response);
|
|
206
236
|
}
|
|
207
237
|
};
|
|
208
238
|
var api = new ApiClient();
|
|
@@ -224,27 +254,86 @@ function isPermissionError(err) {
|
|
|
224
254
|
return false;
|
|
225
255
|
}
|
|
226
256
|
|
|
257
|
+
// src/lib/telemetry.ts
|
|
258
|
+
import { randomUUID } from "crypto";
|
|
259
|
+
function getAnonymousId() {
|
|
260
|
+
let anonymousId = getConfig("anonymousId");
|
|
261
|
+
if (!anonymousId) {
|
|
262
|
+
anonymousId = randomUUID();
|
|
263
|
+
setConfig("anonymousId", anonymousId);
|
|
264
|
+
}
|
|
265
|
+
return anonymousId;
|
|
266
|
+
}
|
|
267
|
+
function trackEvent(event, properties = {}) {
|
|
268
|
+
const distinctId = getConfig("userId") ?? getAnonymousId();
|
|
269
|
+
fetch(`${getApiUrl()}/v1/posthog/event`, {
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers: { "Content-Type": "application/json" },
|
|
272
|
+
body: JSON.stringify({
|
|
273
|
+
events: [{
|
|
274
|
+
event,
|
|
275
|
+
properties: {
|
|
276
|
+
...properties,
|
|
277
|
+
distinct_id: distinctId,
|
|
278
|
+
source: "cli"
|
|
279
|
+
}
|
|
280
|
+
}]
|
|
281
|
+
})
|
|
282
|
+
}).catch(() => {
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function identifyUser(userId, personProperties = {}) {
|
|
286
|
+
const anonymousId = getAnonymousId();
|
|
287
|
+
setConfig("userId", userId);
|
|
288
|
+
fetch(`${getApiUrl()}/v1/posthog/event`, {
|
|
289
|
+
method: "POST",
|
|
290
|
+
headers: { "Content-Type": "application/json" },
|
|
291
|
+
body: JSON.stringify({
|
|
292
|
+
events: [{
|
|
293
|
+
event: "$identify",
|
|
294
|
+
properties: {
|
|
295
|
+
distinct_id: userId,
|
|
296
|
+
$anon_distinct_id: anonymousId,
|
|
297
|
+
source: "cli",
|
|
298
|
+
$set: personProperties
|
|
299
|
+
}
|
|
300
|
+
}]
|
|
301
|
+
})
|
|
302
|
+
}).catch(() => {
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
227
306
|
// src/commands/branch/create.ts
|
|
228
307
|
async function createAction(name, options) {
|
|
229
308
|
try {
|
|
230
309
|
const startTime = performance.now();
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
310
|
+
const connectorId = getConfig("currentConnectorId");
|
|
311
|
+
if (!connectorId) {
|
|
312
|
+
console.error("\u2717 No connector selected. Run 'ardent connector switch' first.");
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
234
315
|
await api.post("/v1/branch/create", {
|
|
235
|
-
|
|
236
|
-
service_type: options.service
|
|
316
|
+
connector_id: connectorId,
|
|
317
|
+
service_type: options.service,
|
|
318
|
+
name
|
|
237
319
|
});
|
|
238
|
-
const
|
|
239
|
-
const
|
|
320
|
+
const response = await api.get(`/v1/cli/branches?connector_id=${connectorId}`);
|
|
321
|
+
const apiBranches = response.branches || [];
|
|
322
|
+
let apiBranch;
|
|
323
|
+
for (const branch2 of apiBranches) {
|
|
324
|
+
if (branch2.name === name) {
|
|
325
|
+
apiBranch = branch2;
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
240
329
|
if (!apiBranch) {
|
|
241
330
|
console.error("\u2717 Branch created but could not fetch details");
|
|
242
331
|
process.exit(1);
|
|
243
332
|
}
|
|
244
333
|
const branch = {
|
|
245
334
|
id: apiBranch.id,
|
|
246
|
-
|
|
247
|
-
name,
|
|
335
|
+
connector_id: apiBranch.connector_id,
|
|
336
|
+
name: apiBranch.name,
|
|
248
337
|
service_type: apiBranch.service_type,
|
|
249
338
|
branch_url: apiBranch.branch_url || "",
|
|
250
339
|
status: apiBranch.status,
|
|
@@ -256,6 +345,7 @@ async function createAction(name, options) {
|
|
|
256
345
|
setCacheEntry("branches", cachedBranches);
|
|
257
346
|
setCurrentBranch(name);
|
|
258
347
|
const elapsed = ((performance.now() - startTime) / 1e3).toFixed(1);
|
|
348
|
+
trackEvent("CLI: branch create succeeded", { service_type: options.service, duration_seconds: parseFloat(elapsed) });
|
|
259
349
|
console.log(`\u2713 Branch '${name}' created and checked out in ${elapsed}s`);
|
|
260
350
|
if (branch.branch_url) {
|
|
261
351
|
console.log(`
|
|
@@ -263,9 +353,11 @@ ${branch.branch_url}`);
|
|
|
263
353
|
}
|
|
264
354
|
} catch (err) {
|
|
265
355
|
if (isNetworkError(err)) {
|
|
356
|
+
trackEvent("CLI: branch create failed", { reason: "offline" });
|
|
266
357
|
console.error("\u2717 Cannot create branch while offline");
|
|
267
358
|
process.exit(1);
|
|
268
359
|
}
|
|
360
|
+
trackEvent("CLI: branch create failed", { reason: "api_error" });
|
|
269
361
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
270
362
|
process.exit(1);
|
|
271
363
|
}
|
|
@@ -277,7 +369,12 @@ async function listAction() {
|
|
|
277
369
|
let fromCache = false;
|
|
278
370
|
let cacheTime = "";
|
|
279
371
|
try {
|
|
280
|
-
const
|
|
372
|
+
const currentConnectorId = getConfig("currentConnectorId");
|
|
373
|
+
if (!currentConnectorId) {
|
|
374
|
+
console.error("\u2717 No connector selected. Run 'ardent connector switch' first.");
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
const result = await api.get(`/v1/cli/branches?connector_id=${currentConnectorId}`);
|
|
281
378
|
if (!result.branches) {
|
|
282
379
|
throw new Error("API returned invalid response: missing branches array");
|
|
283
380
|
}
|
|
@@ -290,11 +387,14 @@ async function listAction() {
|
|
|
290
387
|
branches = cached.data;
|
|
291
388
|
fromCache = true;
|
|
292
389
|
cacheTime = formatCacheTime(cached.updated_at);
|
|
390
|
+
trackEvent("CLI: branch list served from cache");
|
|
293
391
|
} else {
|
|
392
|
+
trackEvent("CLI: branch list failed", { reason: "offline_no_cache" });
|
|
294
393
|
console.error("\u2717 Offline and no cached data available");
|
|
295
394
|
process.exit(1);
|
|
296
395
|
}
|
|
297
396
|
} else {
|
|
397
|
+
trackEvent("CLI: branch list failed", { reason: "api_error" });
|
|
298
398
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
299
399
|
process.exit(1);
|
|
300
400
|
}
|
|
@@ -303,6 +403,7 @@ async function listAction() {
|
|
|
303
403
|
console.log(`\u26A0 Offline - showing cached data from ${cacheTime}
|
|
304
404
|
`);
|
|
305
405
|
}
|
|
406
|
+
trackEvent("CLI: branch list succeeded", { branch_count: branches.length, from_cache: fromCache });
|
|
306
407
|
if (branches.length === 0) {
|
|
307
408
|
console.log("No branches found");
|
|
308
409
|
console.log(" Create one with: ardent branch create <name>");
|
|
@@ -357,6 +458,7 @@ function infoAction(name) {
|
|
|
357
458
|
console.log(`
|
|
358
459
|
URL: ${branch.branch_url}`);
|
|
359
460
|
}
|
|
461
|
+
trackEvent("CLI: branch info");
|
|
360
462
|
}
|
|
361
463
|
|
|
362
464
|
// src/commands/branch/delete.ts
|
|
@@ -369,18 +471,21 @@ async function deleteAction(name) {
|
|
|
369
471
|
process.exit(1);
|
|
370
472
|
}
|
|
371
473
|
try {
|
|
372
|
-
await api.delete(`/v1/cli/
|
|
474
|
+
await api.delete(`/v1/cli/branches/${branch.id}`);
|
|
373
475
|
const updatedBranches = cached.data.filter((cachedBranch) => cachedBranch.id !== branch.id);
|
|
374
476
|
setCacheEntry("branches", updatedBranches);
|
|
375
477
|
if (getCurrentBranch() === name) {
|
|
376
478
|
clearCurrentBranch();
|
|
377
479
|
}
|
|
480
|
+
trackEvent("CLI: branch delete succeeded");
|
|
378
481
|
console.log("\u2713 Branch deleted");
|
|
379
482
|
} catch (err) {
|
|
380
483
|
if (isNetworkError(err)) {
|
|
484
|
+
trackEvent("CLI: branch delete failed", { reason: "offline" });
|
|
381
485
|
console.error("\u2717 Cannot delete branch while offline");
|
|
382
486
|
process.exit(1);
|
|
383
487
|
}
|
|
488
|
+
trackEvent("CLI: branch delete failed", { reason: "api_error" });
|
|
384
489
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
385
490
|
process.exit(1);
|
|
386
491
|
}
|
|
@@ -427,6 +532,7 @@ async function switchAction(name) {
|
|
|
427
532
|
console.log(`
|
|
428
533
|
${branch.branch_url}`);
|
|
429
534
|
}
|
|
535
|
+
trackEvent("CLI: branch switch");
|
|
430
536
|
}
|
|
431
537
|
|
|
432
538
|
// src/commands/branch/diff.ts
|
|
@@ -476,7 +582,7 @@ async function diffAction(options) {
|
|
|
476
582
|
return;
|
|
477
583
|
}
|
|
478
584
|
try {
|
|
479
|
-
const stateRaw = await api.get(`/v1/branches/${branch.
|
|
585
|
+
const stateRaw = await api.get(`/v1/branches/${branch.id}/state`);
|
|
480
586
|
if (!stateRaw || Object.keys(stateRaw).length === 0) {
|
|
481
587
|
console.log("No changes captured");
|
|
482
588
|
return;
|
|
@@ -632,8 +738,14 @@ async function diffAction(options) {
|
|
|
632
738
|
if ((!ddl || ddl.length === 0) && (!diff || diff.length === 0)) {
|
|
633
739
|
console.log("No changes");
|
|
634
740
|
}
|
|
741
|
+
trackEvent("CLI: branch diff succeeded", {
|
|
742
|
+
sql_mode: Boolean(options.sql),
|
|
743
|
+
ddl_count: ddl?.length ?? 0,
|
|
744
|
+
data_change_count: diff?.length ?? 0
|
|
745
|
+
});
|
|
635
746
|
}
|
|
636
747
|
} catch (err) {
|
|
748
|
+
trackEvent("CLI: branch diff failed", { reason: "api_error" });
|
|
637
749
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
638
750
|
process.exit(1);
|
|
639
751
|
}
|
|
@@ -800,12 +912,20 @@ async function createAction2(type, url, options) {
|
|
|
800
912
|
}
|
|
801
913
|
try {
|
|
802
914
|
const connectorName = options.name || (isByoc ? "my-neon-connection" : "my-postgresql-connection");
|
|
915
|
+
const currentProjectId = getConfig("currentProjectId");
|
|
916
|
+
if (!currentProjectId) {
|
|
917
|
+
console.error("\u2717 No current project set. Switch to a project first:");
|
|
918
|
+
console.error(" ardent project list");
|
|
919
|
+
console.error(" ardent project switch <name>");
|
|
920
|
+
process.exit(1);
|
|
921
|
+
}
|
|
803
922
|
let createPayload;
|
|
804
923
|
if (isByoc) {
|
|
805
924
|
console.log("Creating connector (BYOC Neon)...");
|
|
806
925
|
createPayload = {
|
|
807
926
|
name: connectorName,
|
|
808
927
|
service_name: "postgresql",
|
|
928
|
+
project_id: currentProjectId,
|
|
809
929
|
byoc: options.byoc,
|
|
810
930
|
neon_api_key: options.apiKey,
|
|
811
931
|
neon_project_id: options.projectId,
|
|
@@ -822,6 +942,7 @@ async function createAction2(type, url, options) {
|
|
|
822
942
|
createPayload = {
|
|
823
943
|
name: connectorName,
|
|
824
944
|
service_name: "postgresql",
|
|
945
|
+
project_id: currentProjectId,
|
|
825
946
|
connection_details: {
|
|
826
947
|
host: parsed.host,
|
|
827
948
|
port: parsed.port,
|
|
@@ -865,11 +986,15 @@ async function createAction2(type, url, options) {
|
|
|
865
986
|
const cachedConnectors = cached?.data || [];
|
|
866
987
|
cachedConnectors.push(newConnector);
|
|
867
988
|
setCacheEntry("connectors", cachedConnectors);
|
|
989
|
+
setConfig("currentConnectorId", connectorId);
|
|
990
|
+
setConfig("currentConnectorName", connectorName);
|
|
991
|
+
trackEvent("CLI: connector create succeeded", { db_type: type, byoc: isByoc });
|
|
868
992
|
console.log("\u2713 Connector created and ready");
|
|
869
993
|
console.log(` ID: ${connectorId}`);
|
|
870
994
|
showNextStep();
|
|
871
995
|
} catch (err) {
|
|
872
996
|
if (isPermissionError(err)) {
|
|
997
|
+
trackEvent("CLI: connector create failed", { reason: "permission_denied" });
|
|
873
998
|
console.error("\u2717 You don't have permission to create connectors.");
|
|
874
999
|
console.error("");
|
|
875
1000
|
console.error(" Ask your organization admin to either:");
|
|
@@ -877,6 +1002,7 @@ async function createAction2(type, url, options) {
|
|
|
877
1002
|
console.error(" \u2022 Upgrade your role to Admin");
|
|
878
1003
|
process.exit(1);
|
|
879
1004
|
}
|
|
1005
|
+
trackEvent("CLI: connector create failed", { reason: "api_error" });
|
|
880
1006
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
881
1007
|
process.exit(1);
|
|
882
1008
|
}
|
|
@@ -888,7 +1014,9 @@ async function listAction2() {
|
|
|
888
1014
|
let fromCache = false;
|
|
889
1015
|
let cacheTime = "";
|
|
890
1016
|
try {
|
|
891
|
-
const
|
|
1017
|
+
const currentProjectId = getConfig("currentProjectId");
|
|
1018
|
+
const projectFilter = currentProjectId ? `?project_id=${currentProjectId}` : "";
|
|
1019
|
+
const result = await api.get(`/v1/cli/connectors${projectFilter}`);
|
|
892
1020
|
if (!result.connectors) {
|
|
893
1021
|
throw new Error("API returned invalid response: missing connectors array");
|
|
894
1022
|
}
|
|
@@ -902,14 +1030,17 @@ async function listAction2() {
|
|
|
902
1030
|
fromCache = true;
|
|
903
1031
|
cacheTime = formatCacheTime(cached.updated_at);
|
|
904
1032
|
} else {
|
|
1033
|
+
trackEvent("CLI: connector list failed", { reason: "offline_no_cache" });
|
|
905
1034
|
console.error("\u2717 Offline and no cached data available");
|
|
906
1035
|
process.exit(1);
|
|
907
1036
|
}
|
|
908
1037
|
} else {
|
|
1038
|
+
trackEvent("CLI: connector list failed", { reason: "api_error" });
|
|
909
1039
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
910
1040
|
process.exit(1);
|
|
911
1041
|
}
|
|
912
1042
|
}
|
|
1043
|
+
trackEvent("CLI: connector list succeeded", { connector_count: connectors.length, from_cache: fromCache });
|
|
913
1044
|
if (fromCache) {
|
|
914
1045
|
console.log(`\u26A0 Offline - showing cached data from ${cacheTime}
|
|
915
1046
|
`);
|
|
@@ -919,11 +1050,21 @@ async function listAction2() {
|
|
|
919
1050
|
console.log(" Create one with: ardent connector create postgresql <url>");
|
|
920
1051
|
return;
|
|
921
1052
|
}
|
|
1053
|
+
const currentConnectorId = getConfig("currentConnectorId");
|
|
1054
|
+
const green3 = "\x1B[32m";
|
|
1055
|
+
const dim3 = "\x1B[2m";
|
|
1056
|
+
const reset3 = "\x1B[0m";
|
|
922
1057
|
console.log("Connectors:\n");
|
|
923
1058
|
for (const connector of connectors) {
|
|
1059
|
+
const isCurrent = connector.id === currentConnectorId;
|
|
924
1060
|
const icon = connector.status === "healthy" ? "\u25CF" : "\u25CB";
|
|
925
|
-
|
|
926
|
-
|
|
1061
|
+
if (isCurrent) {
|
|
1062
|
+
console.log(`${green3}* ${icon} ${connector.name}${reset3}`);
|
|
1063
|
+
console.log(`${green3} ${connector.service_name}${reset3}`);
|
|
1064
|
+
} else {
|
|
1065
|
+
console.log(` ${icon} ${connector.name}`);
|
|
1066
|
+
console.log(`${dim3} ${connector.service_name}${reset3}`);
|
|
1067
|
+
}
|
|
927
1068
|
console.log();
|
|
928
1069
|
}
|
|
929
1070
|
}
|
|
@@ -960,13 +1101,16 @@ async function deleteAction2(name) {
|
|
|
960
1101
|
const updatedConnectors = currentCache.data.filter((c) => c.id !== connector.id);
|
|
961
1102
|
setCacheEntry("connectors", updatedConnectors);
|
|
962
1103
|
}
|
|
1104
|
+
trackEvent("CLI: connector delete succeeded");
|
|
963
1105
|
console.log("\u2713 Connector deleted");
|
|
964
1106
|
} catch (err) {
|
|
965
1107
|
if (isNetworkError(err)) {
|
|
1108
|
+
trackEvent("CLI: connector delete failed", { reason: "offline" });
|
|
966
1109
|
console.error("\u2717 Cannot delete connector while offline");
|
|
967
1110
|
process.exit(1);
|
|
968
1111
|
}
|
|
969
1112
|
if (isPermissionError(err)) {
|
|
1113
|
+
trackEvent("CLI: connector delete failed", { reason: "permission_denied" });
|
|
970
1114
|
console.error("\u2717 You don't have permission to delete connectors.");
|
|
971
1115
|
console.error("");
|
|
972
1116
|
console.error(" Ask your organization admin to either:");
|
|
@@ -974,15 +1118,70 @@ async function deleteAction2(name) {
|
|
|
974
1118
|
console.error(" \u2022 Upgrade your role to Admin");
|
|
975
1119
|
process.exit(1);
|
|
976
1120
|
}
|
|
1121
|
+
trackEvent("CLI: connector delete failed", { reason: "api_error" });
|
|
977
1122
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
978
1123
|
process.exit(1);
|
|
979
1124
|
}
|
|
980
1125
|
}
|
|
981
1126
|
|
|
1127
|
+
// src/commands/connector/switch.ts
|
|
1128
|
+
async function switchAction2(name) {
|
|
1129
|
+
const cached = getCacheEntry("connectors");
|
|
1130
|
+
let connector = cached?.data.find(
|
|
1131
|
+
(cachedConnector) => cachedConnector.name === name
|
|
1132
|
+
);
|
|
1133
|
+
if (!connector) {
|
|
1134
|
+
try {
|
|
1135
|
+
const result = await api.get("/v1/cli/connectors");
|
|
1136
|
+
if (!result.connectors) {
|
|
1137
|
+
throw new Error("API returned invalid response: missing connectors array");
|
|
1138
|
+
}
|
|
1139
|
+
setCacheEntry("connectors", result.connectors);
|
|
1140
|
+
connector = result.connectors.find(
|
|
1141
|
+
(remoteConnector) => remoteConnector.name === name
|
|
1142
|
+
);
|
|
1143
|
+
} catch (err) {
|
|
1144
|
+
if (isNetworkError(err)) {
|
|
1145
|
+
if (!connector) {
|
|
1146
|
+
console.error(`\u2717 Connector "${name}" not found in cache`);
|
|
1147
|
+
console.log(" Run 'ardent connector list' when online to refresh");
|
|
1148
|
+
process.exit(1);
|
|
1149
|
+
}
|
|
1150
|
+
} else {
|
|
1151
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1152
|
+
process.exit(1);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
if (!connector) {
|
|
1157
|
+
console.error(`\u2717 Connector "${name}" not found`);
|
|
1158
|
+
console.log(" Run: ardent connector list");
|
|
1159
|
+
process.exit(1);
|
|
1160
|
+
}
|
|
1161
|
+
const previousName = getConfig("currentConnectorName");
|
|
1162
|
+
setConfig("currentConnectorId", connector.id);
|
|
1163
|
+
setConfig("currentConnectorName", connector.name);
|
|
1164
|
+
clearCurrentBranch();
|
|
1165
|
+
if (previousName && previousName !== name) {
|
|
1166
|
+
console.log(`Switched from '${previousName}' to '${name}'`);
|
|
1167
|
+
} else {
|
|
1168
|
+
console.log(`Switched to connector '${name}'`);
|
|
1169
|
+
}
|
|
1170
|
+
try {
|
|
1171
|
+
const branchResult = await api.get(
|
|
1172
|
+
`/v1/cli/branches?connector_id=${connector.id}`
|
|
1173
|
+
);
|
|
1174
|
+
setCacheEntry("branches", branchResult.branches || []);
|
|
1175
|
+
} catch {
|
|
1176
|
+
}
|
|
1177
|
+
trackEvent("CLI: connector switch");
|
|
1178
|
+
}
|
|
1179
|
+
|
|
982
1180
|
// src/commands/connector/index.ts
|
|
983
1181
|
var connectorCommand = new Command2("connector").description("Manage database connectors");
|
|
984
1182
|
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);
|
|
985
1183
|
connectorCommand.command("list").description("List your connectors").action(listAction2);
|
|
1184
|
+
connectorCommand.command("switch <name>").description("Switch to a different connector").action(switchAction2);
|
|
986
1185
|
connectorCommand.command("delete <name>").description("Delete a connector by name").action(deleteAction2);
|
|
987
1186
|
|
|
988
1187
|
// src/commands/invite/index.ts
|
|
@@ -1001,15 +1200,18 @@ async function sendAction(email, role) {
|
|
|
1001
1200
|
email,
|
|
1002
1201
|
role: role.toLowerCase()
|
|
1003
1202
|
});
|
|
1203
|
+
trackEvent("CLI: invite send succeeded", { role: result.role });
|
|
1004
1204
|
console.log(`\u2713 Invite sent to ${result.email}`);
|
|
1005
1205
|
console.log(` Role: ${result.role}`);
|
|
1006
1206
|
console.log(` Org: ${result.org_name}`);
|
|
1007
1207
|
} catch (err) {
|
|
1008
1208
|
if (isNetworkError(err)) {
|
|
1209
|
+
trackEvent("CLI: invite send failed", { reason: "offline" });
|
|
1009
1210
|
console.error("\u2717 Cannot send invite while offline");
|
|
1010
1211
|
process.exit(1);
|
|
1011
1212
|
}
|
|
1012
1213
|
if (isPermissionError(err)) {
|
|
1214
|
+
trackEvent("CLI: invite send failed", { reason: "permission_denied" });
|
|
1013
1215
|
console.error("\u2717 You don't have permission to invite users.");
|
|
1014
1216
|
console.error("");
|
|
1015
1217
|
console.error(" Ask your organization admin to either:");
|
|
@@ -1017,6 +1219,7 @@ async function sendAction(email, role) {
|
|
|
1017
1219
|
console.error(" \u2022 Upgrade your role to Admin");
|
|
1018
1220
|
process.exit(1);
|
|
1019
1221
|
}
|
|
1222
|
+
trackEvent("CLI: invite send failed", { reason: "api_error" });
|
|
1020
1223
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1021
1224
|
process.exit(1);
|
|
1022
1225
|
}
|
|
@@ -1030,6 +1233,7 @@ async function listAction3() {
|
|
|
1030
1233
|
throw new Error("API returned invalid response: missing invites array");
|
|
1031
1234
|
}
|
|
1032
1235
|
const invites = result.invites;
|
|
1236
|
+
trackEvent("CLI: invite list succeeded", { invite_count: invites.length });
|
|
1033
1237
|
if (invites.length === 0) {
|
|
1034
1238
|
console.log("No pending invites");
|
|
1035
1239
|
return;
|
|
@@ -1043,9 +1247,11 @@ async function listAction3() {
|
|
|
1043
1247
|
}
|
|
1044
1248
|
} catch (err) {
|
|
1045
1249
|
if (isNetworkError(err)) {
|
|
1250
|
+
trackEvent("CLI: invite list failed", { reason: "offline" });
|
|
1046
1251
|
console.error("\u2717 Cannot list invites while offline");
|
|
1047
1252
|
process.exit(1);
|
|
1048
1253
|
}
|
|
1254
|
+
trackEvent("CLI: invite list failed", { reason: "api_error" });
|
|
1049
1255
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1050
1256
|
process.exit(1);
|
|
1051
1257
|
}
|
|
@@ -1055,12 +1261,15 @@ async function listAction3() {
|
|
|
1055
1261
|
async function deleteAction3(email) {
|
|
1056
1262
|
try {
|
|
1057
1263
|
await api.delete("/v1/cli/invites", { email });
|
|
1264
|
+
trackEvent("CLI: invite delete succeeded");
|
|
1058
1265
|
console.log(`\u2713 Invite for ${email} deleted`);
|
|
1059
1266
|
} catch (err) {
|
|
1060
1267
|
if (isNetworkError(err)) {
|
|
1268
|
+
trackEvent("CLI: invite delete failed", { reason: "offline" });
|
|
1061
1269
|
console.error("\u2717 Cannot delete invite while offline");
|
|
1062
1270
|
process.exit(1);
|
|
1063
1271
|
}
|
|
1272
|
+
trackEvent("CLI: invite delete failed", { reason: "api_error" });
|
|
1064
1273
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1065
1274
|
process.exit(1);
|
|
1066
1275
|
}
|
|
@@ -1084,6 +1293,7 @@ import { Command as Command4 } from "commander";
|
|
|
1084
1293
|
async function membersAction() {
|
|
1085
1294
|
try {
|
|
1086
1295
|
const result = await api.get("/v1/cli/members");
|
|
1296
|
+
trackEvent("CLI: org members succeeded", { member_count: result.members?.length ?? 0 });
|
|
1087
1297
|
if (!result.members || result.members.length === 0) {
|
|
1088
1298
|
console.log("No members found");
|
|
1089
1299
|
return;
|
|
@@ -1097,9 +1307,11 @@ async function membersAction() {
|
|
|
1097
1307
|
}
|
|
1098
1308
|
} catch (err) {
|
|
1099
1309
|
if (isNetworkError(err)) {
|
|
1310
|
+
trackEvent("CLI: org members failed", { reason: "offline" });
|
|
1100
1311
|
console.error("\u2717 Cannot list members while offline");
|
|
1101
1312
|
process.exit(1);
|
|
1102
1313
|
}
|
|
1314
|
+
trackEvent("CLI: org members failed", { reason: "api_error" });
|
|
1103
1315
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1104
1316
|
process.exit(1);
|
|
1105
1317
|
}
|
|
@@ -1115,18 +1327,22 @@ async function setRoleAction(email, role) {
|
|
|
1115
1327
|
}
|
|
1116
1328
|
try {
|
|
1117
1329
|
const result = await api.patch("/v1/cli/members/role", { email, role });
|
|
1330
|
+
trackEvent("CLI: org set-role succeeded", { role: result.new_role });
|
|
1118
1331
|
console.log(`\u2713 Updated ${result.email} to ${result.new_role}`);
|
|
1119
1332
|
} catch (err) {
|
|
1120
1333
|
if (isNetworkError(err)) {
|
|
1334
|
+
trackEvent("CLI: org set-role failed", { reason: "offline" });
|
|
1121
1335
|
console.error("\u2717 Cannot update role while offline");
|
|
1122
1336
|
process.exit(1);
|
|
1123
1337
|
}
|
|
1124
1338
|
if (isPermissionError(err)) {
|
|
1339
|
+
trackEvent("CLI: org set-role failed", { reason: "permission_denied" });
|
|
1125
1340
|
console.error("\u2717 You don't have permission to update member roles.");
|
|
1126
1341
|
console.error("");
|
|
1127
1342
|
console.error(" Only org admins and owners can change roles.");
|
|
1128
1343
|
process.exit(1);
|
|
1129
1344
|
}
|
|
1345
|
+
trackEvent("CLI: org set-role failed", { reason: "api_error" });
|
|
1130
1346
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1131
1347
|
process.exit(1);
|
|
1132
1348
|
}
|
|
@@ -1136,18 +1352,22 @@ async function setRoleAction(email, role) {
|
|
|
1136
1352
|
async function removeAction(email) {
|
|
1137
1353
|
try {
|
|
1138
1354
|
await api.delete("/v1/cli/members", { email });
|
|
1355
|
+
trackEvent("CLI: org remove member succeeded");
|
|
1139
1356
|
console.log(`\u2713 Removed ${email} from organization`);
|
|
1140
1357
|
} catch (err) {
|
|
1141
1358
|
if (isNetworkError(err)) {
|
|
1359
|
+
trackEvent("CLI: org remove member failed", { reason: "offline" });
|
|
1142
1360
|
console.error("\u2717 Cannot remove member while offline");
|
|
1143
1361
|
process.exit(1);
|
|
1144
1362
|
}
|
|
1145
1363
|
if (isPermissionError(err)) {
|
|
1364
|
+
trackEvent("CLI: org remove member failed", { reason: "permission_denied" });
|
|
1146
1365
|
console.error("\u2717 You don't have permission to remove members.");
|
|
1147
1366
|
console.error("");
|
|
1148
1367
|
console.error(" Only org admins and owners can remove members.");
|
|
1149
1368
|
process.exit(1);
|
|
1150
1369
|
}
|
|
1370
|
+
trackEvent("CLI: org remove member failed", { reason: "api_error" });
|
|
1151
1371
|
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1152
1372
|
process.exit(1);
|
|
1153
1373
|
}
|
|
@@ -1159,16 +1379,203 @@ orgCommand.command("members").description("List organization members").action(me
|
|
|
1159
1379
|
orgCommand.command("set-role <email> <role>").description("Update a member's role (owner/admin/member/viewer)").action(setRoleAction);
|
|
1160
1380
|
orgCommand.command("remove <email>").description("Remove a member from the organization").action(removeAction);
|
|
1161
1381
|
|
|
1162
|
-
// src/commands/
|
|
1382
|
+
// src/commands/project/index.ts
|
|
1163
1383
|
import { Command as Command5 } from "commander";
|
|
1164
1384
|
|
|
1385
|
+
// src/commands/project/create.ts
|
|
1386
|
+
async function createAction3(name) {
|
|
1387
|
+
const user = getConfig("user");
|
|
1388
|
+
if (!user?.org_id) {
|
|
1389
|
+
console.error("\u2717 No organization found. Run: ardent login");
|
|
1390
|
+
process.exit(1);
|
|
1391
|
+
}
|
|
1392
|
+
try {
|
|
1393
|
+
const project = await api.post("/v1/projects", {
|
|
1394
|
+
name,
|
|
1395
|
+
org_id: user.org_id
|
|
1396
|
+
});
|
|
1397
|
+
const cached = getCacheEntry("projects");
|
|
1398
|
+
const cachedProjects = cached?.data || [];
|
|
1399
|
+
cachedProjects.push(project);
|
|
1400
|
+
setCacheEntry("projects", cachedProjects);
|
|
1401
|
+
setConfig("currentProjectId", project.id);
|
|
1402
|
+
setConfig("currentProjectName", project.name);
|
|
1403
|
+
trackEvent("CLI: project create succeeded");
|
|
1404
|
+
console.log(`\u2713 Project '${name}' created`);
|
|
1405
|
+
console.log(` ID: ${project.id}`);
|
|
1406
|
+
} catch (err) {
|
|
1407
|
+
if (isNetworkError(err)) {
|
|
1408
|
+
trackEvent("CLI: project create failed", { reason: "offline" });
|
|
1409
|
+
console.error("\u2717 Cannot create project while offline");
|
|
1410
|
+
process.exit(1);
|
|
1411
|
+
}
|
|
1412
|
+
if (isPermissionError(err)) {
|
|
1413
|
+
trackEvent("CLI: project create failed", { reason: "permission_denied" });
|
|
1414
|
+
console.error("\u2717 You don't have permission to create projects.");
|
|
1415
|
+
process.exit(1);
|
|
1416
|
+
}
|
|
1417
|
+
trackEvent("CLI: project create failed", { reason: "api_error" });
|
|
1418
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1419
|
+
process.exit(1);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// src/commands/project/list.ts
|
|
1424
|
+
async function listAction4() {
|
|
1425
|
+
let projects = [];
|
|
1426
|
+
let fromCache = false;
|
|
1427
|
+
let cacheTime = "";
|
|
1428
|
+
try {
|
|
1429
|
+
const user = getConfig("user");
|
|
1430
|
+
if (!user?.org_id) {
|
|
1431
|
+
console.error("\u2717 No organization found. Run: ardent login");
|
|
1432
|
+
process.exit(1);
|
|
1433
|
+
}
|
|
1434
|
+
const result = await api.get(
|
|
1435
|
+
`/v1/projects?org_id=${user.org_id}`
|
|
1436
|
+
);
|
|
1437
|
+
if (!result.projects) {
|
|
1438
|
+
throw new Error("API returned invalid response: missing projects array");
|
|
1439
|
+
}
|
|
1440
|
+
projects = result.projects;
|
|
1441
|
+
setCacheEntry("projects", projects);
|
|
1442
|
+
} catch (err) {
|
|
1443
|
+
if (isNetworkError(err)) {
|
|
1444
|
+
const cached = getCacheEntry("projects");
|
|
1445
|
+
if (cached) {
|
|
1446
|
+
projects = cached.data;
|
|
1447
|
+
fromCache = true;
|
|
1448
|
+
cacheTime = formatCacheTime(cached.updated_at);
|
|
1449
|
+
} else {
|
|
1450
|
+
trackEvent("CLI: project list failed", { reason: "offline_no_cache" });
|
|
1451
|
+
console.error("\u2717 Offline and no cached data available");
|
|
1452
|
+
process.exit(1);
|
|
1453
|
+
}
|
|
1454
|
+
} else {
|
|
1455
|
+
trackEvent("CLI: project list failed", { reason: "api_error" });
|
|
1456
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1457
|
+
process.exit(1);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
trackEvent("CLI: project list succeeded", {
|
|
1461
|
+
project_count: projects.length,
|
|
1462
|
+
from_cache: fromCache
|
|
1463
|
+
});
|
|
1464
|
+
if (fromCache) {
|
|
1465
|
+
console.log(`\u26A0 Offline - showing cached data from ${cacheTime}
|
|
1466
|
+
`);
|
|
1467
|
+
}
|
|
1468
|
+
if (projects.length === 0) {
|
|
1469
|
+
console.log("No projects found");
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
const currentProjectId = getConfig("currentProjectId");
|
|
1473
|
+
const green3 = "\x1B[32m";
|
|
1474
|
+
const dim3 = "\x1B[2m";
|
|
1475
|
+
const reset3 = "\x1B[0m";
|
|
1476
|
+
console.log("Projects:\n");
|
|
1477
|
+
for (const project of projects) {
|
|
1478
|
+
const isCurrent = project.id === currentProjectId;
|
|
1479
|
+
if (isCurrent) {
|
|
1480
|
+
console.log(`${green3}* ${project.name}${reset3}`);
|
|
1481
|
+
console.log(`${green3} ${project.id}${reset3}`);
|
|
1482
|
+
} else {
|
|
1483
|
+
console.log(` ${project.name}`);
|
|
1484
|
+
console.log(`${dim3} ${project.id}${reset3}`);
|
|
1485
|
+
}
|
|
1486
|
+
console.log();
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
// src/commands/project/switch.ts
|
|
1491
|
+
async function switchAction3(name) {
|
|
1492
|
+
let projects = [];
|
|
1493
|
+
const cached = getCacheEntry("projects");
|
|
1494
|
+
let project = cached?.data.find(
|
|
1495
|
+
(cachedProject) => cachedProject.name === name
|
|
1496
|
+
);
|
|
1497
|
+
if (!project) {
|
|
1498
|
+
try {
|
|
1499
|
+
const user = getConfig("user");
|
|
1500
|
+
if (!user?.org_id) {
|
|
1501
|
+
console.error("\u2717 No organization found. Run: ardent login");
|
|
1502
|
+
process.exit(1);
|
|
1503
|
+
}
|
|
1504
|
+
const result = await api.get(
|
|
1505
|
+
`/v1/projects?org_id=${user.org_id}`
|
|
1506
|
+
);
|
|
1507
|
+
if (!result.projects) {
|
|
1508
|
+
throw new Error(
|
|
1509
|
+
"API returned invalid response: missing projects array"
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
projects = result.projects;
|
|
1513
|
+
setCacheEntry("projects", projects);
|
|
1514
|
+
project = projects.find(
|
|
1515
|
+
(remoteProject) => remoteProject.name === name
|
|
1516
|
+
);
|
|
1517
|
+
} catch (err) {
|
|
1518
|
+
if (isNetworkError(err)) {
|
|
1519
|
+
if (!project) {
|
|
1520
|
+
console.error(`\u2717 Project "${name}" not found in cache`);
|
|
1521
|
+
console.log(" Run 'ardent project list' when online to refresh");
|
|
1522
|
+
process.exit(1);
|
|
1523
|
+
}
|
|
1524
|
+
} else {
|
|
1525
|
+
console.error("\u2717 Failed:", err instanceof Error ? err.message : err);
|
|
1526
|
+
process.exit(1);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
if (!project) {
|
|
1531
|
+
console.error(`\u2717 Project "${name}" not found`);
|
|
1532
|
+
console.log(" Run: ardent project list");
|
|
1533
|
+
process.exit(1);
|
|
1534
|
+
}
|
|
1535
|
+
const previousId = getConfig("currentProjectId");
|
|
1536
|
+
const previousName = getConfig("currentProjectName");
|
|
1537
|
+
setConfig("currentProjectId", project.id);
|
|
1538
|
+
setConfig("currentProjectName", project.name);
|
|
1539
|
+
setConfig("currentConnectorId", void 0);
|
|
1540
|
+
setConfig("currentConnectorName", void 0);
|
|
1541
|
+
clearCurrentBranch();
|
|
1542
|
+
if (previousName && previousName !== name) {
|
|
1543
|
+
console.log(`Switched from '${previousName}' to '${name}'`);
|
|
1544
|
+
} else {
|
|
1545
|
+
console.log(`Switched to project '${name}'`);
|
|
1546
|
+
}
|
|
1547
|
+
trackEvent("CLI: project switch");
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
// src/commands/project/index.ts
|
|
1551
|
+
var projectCommand = new Command5("project").description(
|
|
1552
|
+
"Manage projects"
|
|
1553
|
+
);
|
|
1554
|
+
projectCommand.command("create <name>").description("Create a new project").action(createAction3);
|
|
1555
|
+
projectCommand.command("list").description("List your projects").action(listAction4);
|
|
1556
|
+
projectCommand.command("switch <name>").description("Switch to a different project").action(switchAction3);
|
|
1557
|
+
|
|
1558
|
+
// src/commands/auth/index.ts
|
|
1559
|
+
import { Command as Command6 } from "commander";
|
|
1560
|
+
|
|
1165
1561
|
// src/commands/auth/login.ts
|
|
1166
1562
|
async function loginAction(options) {
|
|
1167
1563
|
if (options.token) {
|
|
1168
1564
|
setConfig("token", options.token);
|
|
1169
1565
|
console.log("\u2713 Logged in successfully");
|
|
1566
|
+
trackEvent("CLI: login succeeded", { method: "token" });
|
|
1170
1567
|
try {
|
|
1171
1568
|
await bootstrapCache();
|
|
1569
|
+
const user = getConfig("user");
|
|
1570
|
+
if (user) {
|
|
1571
|
+
const tokenPersonProperties = {};
|
|
1572
|
+
if (user.email) tokenPersonProperties.email = user.email;
|
|
1573
|
+
if (user.full_name) tokenPersonProperties.$name = user.full_name;
|
|
1574
|
+
if (user.org_name) tokenPersonProperties.org_name = user.org_name;
|
|
1575
|
+
if (user.user_id) {
|
|
1576
|
+
identifyUser(user.user_id, tokenPersonProperties);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1172
1579
|
} catch {
|
|
1173
1580
|
}
|
|
1174
1581
|
showNextStep();
|
|
@@ -1182,6 +1589,7 @@ async function loginAction(options) {
|
|
|
1182
1589
|
});
|
|
1183
1590
|
if (!initResponse.ok) {
|
|
1184
1591
|
const error = await initResponse.text();
|
|
1592
|
+
trackEvent("CLI: login failed", { method: "browser", reason: "init_failed" });
|
|
1185
1593
|
console.error("\u2717 Failed to initialize auth:", error);
|
|
1186
1594
|
process.exit(1);
|
|
1187
1595
|
}
|
|
@@ -1198,6 +1606,7 @@ async function loginAction(options) {
|
|
|
1198
1606
|
`${getApiUrl()}/v1/cli/auth/poll?session=${session_id}`
|
|
1199
1607
|
);
|
|
1200
1608
|
if (!pollResponse.ok) {
|
|
1609
|
+
trackEvent("CLI: login failed", { method: "browser", reason: "poll_failed" });
|
|
1201
1610
|
console.error("\u2717 Poll failed");
|
|
1202
1611
|
process.exit(1);
|
|
1203
1612
|
}
|
|
@@ -1205,26 +1614,39 @@ async function loginAction(options) {
|
|
|
1205
1614
|
if (result.status === "completed") {
|
|
1206
1615
|
setConfig("token", result.token);
|
|
1207
1616
|
console.log("\n\u2713 Logged in successfully");
|
|
1617
|
+
trackEvent("CLI: login succeeded", { method: "browser" });
|
|
1208
1618
|
try {
|
|
1209
1619
|
await bootstrapCache();
|
|
1210
1620
|
} catch {
|
|
1211
1621
|
}
|
|
1622
|
+
const user = getConfig("user");
|
|
1623
|
+
const personProperties = {};
|
|
1624
|
+
if (user?.email) personProperties.email = user.email;
|
|
1625
|
+
if (user?.full_name) personProperties.$name = user.full_name;
|
|
1626
|
+
if (user?.org_name) personProperties.org_name = user.org_name;
|
|
1627
|
+
if (result.user_id) {
|
|
1628
|
+
identifyUser(result.user_id, personProperties);
|
|
1629
|
+
}
|
|
1212
1630
|
showNextStep();
|
|
1213
1631
|
return;
|
|
1214
1632
|
}
|
|
1215
1633
|
if (result.status === "expired") {
|
|
1634
|
+
trackEvent("CLI: login failed", { method: "browser", reason: "expired" });
|
|
1216
1635
|
console.error("\n\u2717 Session expired. Please try again.");
|
|
1217
1636
|
process.exit(1);
|
|
1218
1637
|
}
|
|
1219
1638
|
if (result.status === "error") {
|
|
1639
|
+
trackEvent("CLI: login failed", { method: "browser", reason: "error" });
|
|
1220
1640
|
console.error("\n\u2717 Error:", result.message);
|
|
1221
1641
|
process.exit(1);
|
|
1222
1642
|
}
|
|
1223
1643
|
process.stdout.write(".");
|
|
1224
1644
|
}
|
|
1645
|
+
trackEvent("CLI: login failed", { method: "browser", reason: "timeout" });
|
|
1225
1646
|
console.error("\n\u2717 Timed out. Please try again.");
|
|
1226
1647
|
process.exit(1);
|
|
1227
1648
|
} catch (error) {
|
|
1649
|
+
trackEvent("CLI: login failed", { method: "browser", reason: "network_error" });
|
|
1228
1650
|
console.error("\u2717 Login failed:", error instanceof Error ? error.message : error);
|
|
1229
1651
|
process.exit(1);
|
|
1230
1652
|
}
|
|
@@ -1232,6 +1654,7 @@ async function loginAction(options) {
|
|
|
1232
1654
|
|
|
1233
1655
|
// src/commands/auth/logout.ts
|
|
1234
1656
|
function logoutAction() {
|
|
1657
|
+
trackEvent("CLI: logout");
|
|
1235
1658
|
clearConfig();
|
|
1236
1659
|
console.log("\u2713 Logged out");
|
|
1237
1660
|
}
|
|
@@ -1240,6 +1663,7 @@ function logoutAction() {
|
|
|
1240
1663
|
function statusAction() {
|
|
1241
1664
|
const token = getConfig("token");
|
|
1242
1665
|
if (!token) {
|
|
1666
|
+
trackEvent("CLI: auth status", { authenticated: false });
|
|
1243
1667
|
console.log("\u2717 Not authenticated");
|
|
1244
1668
|
console.log(" Run: ardent login");
|
|
1245
1669
|
return;
|
|
@@ -1259,12 +1683,88 @@ function statusAction() {
|
|
|
1259
1683
|
console.log(` Token: ${token.slice(0, 8)}...${token.slice(-4)}`);
|
|
1260
1684
|
console.log(" Run: ardent login to refresh profile info");
|
|
1261
1685
|
}
|
|
1686
|
+
trackEvent("CLI: auth status", { authenticated: true });
|
|
1262
1687
|
}
|
|
1263
1688
|
|
|
1264
1689
|
// src/commands/auth/index.ts
|
|
1265
|
-
var loginCommand = new
|
|
1266
|
-
var logoutCommand = new
|
|
1267
|
-
var statusCommand = new
|
|
1690
|
+
var loginCommand = new Command6("login").description("Login to Ardent").option("-t, --token <token>", "API token (skip browser login)").action(loginAction);
|
|
1691
|
+
var logoutCommand = new Command6("logout").description("Logout from Ardent").action(logoutAction);
|
|
1692
|
+
var statusCommand = new Command6("status").description("Show status").action(statusAction);
|
|
1693
|
+
|
|
1694
|
+
// src/lib/update-check.ts
|
|
1695
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1696
|
+
import { join as join2 } from "path";
|
|
1697
|
+
import { homedir as homedir2 } from "os";
|
|
1698
|
+
var UPDATE_CHECK_FILE = join2(homedir2(), ".ardent", "update-check.json");
|
|
1699
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1700
|
+
var PACKAGE_NAME = "ardent-cli";
|
|
1701
|
+
function loadCache() {
|
|
1702
|
+
try {
|
|
1703
|
+
if (existsSync2(UPDATE_CHECK_FILE)) {
|
|
1704
|
+
return JSON.parse(readFileSync3(UPDATE_CHECK_FILE, "utf-8"));
|
|
1705
|
+
}
|
|
1706
|
+
} catch {
|
|
1707
|
+
}
|
|
1708
|
+
return null;
|
|
1709
|
+
}
|
|
1710
|
+
function saveCache(latestVersion) {
|
|
1711
|
+
try {
|
|
1712
|
+
const data = {
|
|
1713
|
+
latest_version: latestVersion,
|
|
1714
|
+
checked_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1715
|
+
};
|
|
1716
|
+
writeFileSync2(UPDATE_CHECK_FILE, JSON.stringify(data));
|
|
1717
|
+
} catch {
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
function isNewerVersion(current, latest) {
|
|
1721
|
+
const currentParts = current.split(".").map(Number);
|
|
1722
|
+
const latestParts = latest.split(".").map(Number);
|
|
1723
|
+
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
|
|
1724
|
+
const currentPart = currentParts[i] || 0;
|
|
1725
|
+
const latestPart = latestParts[i] || 0;
|
|
1726
|
+
if (latestPart > currentPart) return true;
|
|
1727
|
+
if (latestPart < currentPart) return false;
|
|
1728
|
+
}
|
|
1729
|
+
return false;
|
|
1730
|
+
}
|
|
1731
|
+
async function checkForUpdate(currentVersion) {
|
|
1732
|
+
try {
|
|
1733
|
+
const cache = loadCache();
|
|
1734
|
+
if (cache) {
|
|
1735
|
+
const elapsed = Date.now() - new Date(cache.checked_at).getTime();
|
|
1736
|
+
if (elapsed < CHECK_INTERVAL_MS) {
|
|
1737
|
+
if (isNewerVersion(currentVersion, cache.latest_version)) {
|
|
1738
|
+
printUpdateNotice(currentVersion, cache.latest_version);
|
|
1739
|
+
}
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
const controller = new AbortController();
|
|
1744
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
1745
|
+
const response = await fetch(
|
|
1746
|
+
`https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
|
|
1747
|
+
{ signal: controller.signal }
|
|
1748
|
+
);
|
|
1749
|
+
clearTimeout(timeout);
|
|
1750
|
+
if (!response.ok) return;
|
|
1751
|
+
const data = await response.json();
|
|
1752
|
+
const latestVersion = data.version;
|
|
1753
|
+
saveCache(latestVersion);
|
|
1754
|
+
if (isNewerVersion(currentVersion, latestVersion)) {
|
|
1755
|
+
printUpdateNotice(currentVersion, latestVersion);
|
|
1756
|
+
}
|
|
1757
|
+
} catch {
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
function printUpdateNotice(current, latest) {
|
|
1761
|
+
const yellow2 = "\x1B[33m";
|
|
1762
|
+
const green3 = "\x1B[32m";
|
|
1763
|
+
const reset3 = "\x1B[0m";
|
|
1764
|
+
console.error(
|
|
1765
|
+
`${yellow2}Update available: ${current} \u2192 ${latest}${reset3} \u2014 run ${green3}npm install -g ardent-cli@latest${reset3}`
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1268
1768
|
|
|
1269
1769
|
// src/index.ts
|
|
1270
1770
|
var HELP_TEXT = `
|
|
@@ -1276,9 +1776,15 @@ AUTHENTICATION
|
|
|
1276
1776
|
logout Clear stored credentials
|
|
1277
1777
|
status Check authentication status
|
|
1278
1778
|
|
|
1779
|
+
PROJECTS
|
|
1780
|
+
project create Create a new project
|
|
1781
|
+
project list List your projects (* = current)
|
|
1782
|
+
project switch Switch to a different project
|
|
1783
|
+
|
|
1279
1784
|
CONNECTORS
|
|
1280
1785
|
connector create Connect a database (postgresql, snowflake, etc.)
|
|
1281
|
-
connector list List your connectors
|
|
1786
|
+
connector list List your connectors (* = current)
|
|
1787
|
+
connector switch Switch to a different connector
|
|
1282
1788
|
connector delete Delete a connector
|
|
1283
1789
|
|
|
1284
1790
|
BRANCHES
|
|
@@ -1308,24 +1814,25 @@ EXAMPLES
|
|
|
1308
1814
|
ardent branch switch my-feature
|
|
1309
1815
|
ardent org members
|
|
1310
1816
|
`;
|
|
1311
|
-
var
|
|
1312
|
-
var program = new
|
|
1313
|
-
function
|
|
1817
|
+
var CLI_VERSION2 = getCliVersion2();
|
|
1818
|
+
var program = new Command7();
|
|
1819
|
+
function getCliVersion2() {
|
|
1314
1820
|
try {
|
|
1315
1821
|
const packageUrl = new URL("../package.json", import.meta.url);
|
|
1316
|
-
const raw =
|
|
1822
|
+
const raw = readFileSync4(packageUrl, "utf-8");
|
|
1317
1823
|
const parsed = JSON.parse(raw);
|
|
1318
1824
|
return parsed.version ?? "unknown";
|
|
1319
1825
|
} catch {
|
|
1320
1826
|
return "unknown";
|
|
1321
1827
|
}
|
|
1322
1828
|
}
|
|
1323
|
-
program.name("ardent").description("CLI for Ardent database branching").version(
|
|
1829
|
+
program.name("ardent").description("CLI for Ardent database branching").version(CLI_VERSION2).configureHelp({
|
|
1324
1830
|
formatHelp: () => BANNER + HELP_TEXT
|
|
1325
1831
|
});
|
|
1326
1832
|
program.addCommand(loginCommand);
|
|
1327
1833
|
program.addCommand(logoutCommand);
|
|
1328
1834
|
program.addCommand(statusCommand);
|
|
1835
|
+
program.addCommand(projectCommand);
|
|
1329
1836
|
program.addCommand(connectorCommand);
|
|
1330
1837
|
program.addCommand(branchCommand);
|
|
1331
1838
|
program.addCommand(inviteCommand);
|
|
@@ -1352,4 +1859,6 @@ if (args.length === 0) {
|
|
|
1352
1859
|
}
|
|
1353
1860
|
program.help();
|
|
1354
1861
|
}
|
|
1862
|
+
getAnonymousId();
|
|
1863
|
+
checkForUpdate(CLI_VERSION2);
|
|
1355
1864
|
program.parse();
|