ardent-cli 0.0.10 → 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 +437 -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();
|
|
@@ -277,23 +307,33 @@ function identifyUser(userId, personProperties = {}) {
|
|
|
277
307
|
async function createAction(name, options) {
|
|
278
308
|
try {
|
|
279
309
|
const startTime = performance.now();
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
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
|
+
}
|
|
283
315
|
await api.post("/v1/branch/create", {
|
|
284
|
-
|
|
285
|
-
service_type: options.service
|
|
316
|
+
connector_id: connectorId,
|
|
317
|
+
service_type: options.service,
|
|
318
|
+
name
|
|
286
319
|
});
|
|
287
|
-
const
|
|
288
|
-
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
|
+
}
|
|
289
329
|
if (!apiBranch) {
|
|
290
330
|
console.error("\u2717 Branch created but could not fetch details");
|
|
291
331
|
process.exit(1);
|
|
292
332
|
}
|
|
293
333
|
const branch = {
|
|
294
334
|
id: apiBranch.id,
|
|
295
|
-
|
|
296
|
-
name,
|
|
335
|
+
connector_id: apiBranch.connector_id,
|
|
336
|
+
name: apiBranch.name,
|
|
297
337
|
service_type: apiBranch.service_type,
|
|
298
338
|
branch_url: apiBranch.branch_url || "",
|
|
299
339
|
status: apiBranch.status,
|
|
@@ -329,7 +369,12 @@ async function listAction() {
|
|
|
329
369
|
let fromCache = false;
|
|
330
370
|
let cacheTime = "";
|
|
331
371
|
try {
|
|
332
|
-
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}`);
|
|
333
378
|
if (!result.branches) {
|
|
334
379
|
throw new Error("API returned invalid response: missing branches array");
|
|
335
380
|
}
|
|
@@ -426,7 +471,7 @@ async function deleteAction(name) {
|
|
|
426
471
|
process.exit(1);
|
|
427
472
|
}
|
|
428
473
|
try {
|
|
429
|
-
await api.delete(`/v1/cli/
|
|
474
|
+
await api.delete(`/v1/cli/branches/${branch.id}`);
|
|
430
475
|
const updatedBranches = cached.data.filter((cachedBranch) => cachedBranch.id !== branch.id);
|
|
431
476
|
setCacheEntry("branches", updatedBranches);
|
|
432
477
|
if (getCurrentBranch() === name) {
|
|
@@ -537,7 +582,7 @@ async function diffAction(options) {
|
|
|
537
582
|
return;
|
|
538
583
|
}
|
|
539
584
|
try {
|
|
540
|
-
const stateRaw = await api.get(`/v1/branches/${branch.
|
|
585
|
+
const stateRaw = await api.get(`/v1/branches/${branch.id}/state`);
|
|
541
586
|
if (!stateRaw || Object.keys(stateRaw).length === 0) {
|
|
542
587
|
console.log("No changes captured");
|
|
543
588
|
return;
|
|
@@ -867,12 +912,20 @@ async function createAction2(type, url, options) {
|
|
|
867
912
|
}
|
|
868
913
|
try {
|
|
869
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
|
+
}
|
|
870
922
|
let createPayload;
|
|
871
923
|
if (isByoc) {
|
|
872
924
|
console.log("Creating connector (BYOC Neon)...");
|
|
873
925
|
createPayload = {
|
|
874
926
|
name: connectorName,
|
|
875
927
|
service_name: "postgresql",
|
|
928
|
+
project_id: currentProjectId,
|
|
876
929
|
byoc: options.byoc,
|
|
877
930
|
neon_api_key: options.apiKey,
|
|
878
931
|
neon_project_id: options.projectId,
|
|
@@ -889,6 +942,7 @@ async function createAction2(type, url, options) {
|
|
|
889
942
|
createPayload = {
|
|
890
943
|
name: connectorName,
|
|
891
944
|
service_name: "postgresql",
|
|
945
|
+
project_id: currentProjectId,
|
|
892
946
|
connection_details: {
|
|
893
947
|
host: parsed.host,
|
|
894
948
|
port: parsed.port,
|
|
@@ -932,6 +986,8 @@ async function createAction2(type, url, options) {
|
|
|
932
986
|
const cachedConnectors = cached?.data || [];
|
|
933
987
|
cachedConnectors.push(newConnector);
|
|
934
988
|
setCacheEntry("connectors", cachedConnectors);
|
|
989
|
+
setConfig("currentConnectorId", connectorId);
|
|
990
|
+
setConfig("currentConnectorName", connectorName);
|
|
935
991
|
trackEvent("CLI: connector create succeeded", { db_type: type, byoc: isByoc });
|
|
936
992
|
console.log("\u2713 Connector created and ready");
|
|
937
993
|
console.log(` ID: ${connectorId}`);
|
|
@@ -958,7 +1014,9 @@ async function listAction2() {
|
|
|
958
1014
|
let fromCache = false;
|
|
959
1015
|
let cacheTime = "";
|
|
960
1016
|
try {
|
|
961
|
-
const
|
|
1017
|
+
const currentProjectId = getConfig("currentProjectId");
|
|
1018
|
+
const projectFilter = currentProjectId ? `?project_id=${currentProjectId}` : "";
|
|
1019
|
+
const result = await api.get(`/v1/cli/connectors${projectFilter}`);
|
|
962
1020
|
if (!result.connectors) {
|
|
963
1021
|
throw new Error("API returned invalid response: missing connectors array");
|
|
964
1022
|
}
|
|
@@ -992,11 +1050,21 @@ async function listAction2() {
|
|
|
992
1050
|
console.log(" Create one with: ardent connector create postgresql <url>");
|
|
993
1051
|
return;
|
|
994
1052
|
}
|
|
1053
|
+
const currentConnectorId = getConfig("currentConnectorId");
|
|
1054
|
+
const green3 = "\x1B[32m";
|
|
1055
|
+
const dim3 = "\x1B[2m";
|
|
1056
|
+
const reset3 = "\x1B[0m";
|
|
995
1057
|
console.log("Connectors:\n");
|
|
996
1058
|
for (const connector of connectors) {
|
|
1059
|
+
const isCurrent = connector.id === currentConnectorId;
|
|
997
1060
|
const icon = connector.status === "healthy" ? "\u25CF" : "\u25CB";
|
|
998
|
-
|
|
999
|
-
|
|
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
|
+
}
|
|
1000
1068
|
console.log();
|
|
1001
1069
|
}
|
|
1002
1070
|
}
|
|
@@ -1056,10 +1124,64 @@ async function deleteAction2(name) {
|
|
|
1056
1124
|
}
|
|
1057
1125
|
}
|
|
1058
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
|
+
|
|
1059
1180
|
// src/commands/connector/index.ts
|
|
1060
1181
|
var connectorCommand = new Command2("connector").description("Manage database connectors");
|
|
1061
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);
|
|
1062
1183
|
connectorCommand.command("list").description("List your connectors").action(listAction2);
|
|
1184
|
+
connectorCommand.command("switch <name>").description("Switch to a different connector").action(switchAction2);
|
|
1063
1185
|
connectorCommand.command("delete <name>").description("Delete a connector by name").action(deleteAction2);
|
|
1064
1186
|
|
|
1065
1187
|
// src/commands/invite/index.ts
|
|
@@ -1257,9 +1379,185 @@ orgCommand.command("members").description("List organization members").action(me
|
|
|
1257
1379
|
orgCommand.command("set-role <email> <role>").description("Update a member's role (owner/admin/member/viewer)").action(setRoleAction);
|
|
1258
1380
|
orgCommand.command("remove <email>").description("Remove a member from the organization").action(removeAction);
|
|
1259
1381
|
|
|
1260
|
-
// src/commands/
|
|
1382
|
+
// src/commands/project/index.ts
|
|
1261
1383
|
import { Command as Command5 } from "commander";
|
|
1262
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
|
+
|
|
1263
1561
|
// src/commands/auth/login.ts
|
|
1264
1562
|
async function loginAction(options) {
|
|
1265
1563
|
if (options.token) {
|
|
@@ -1389,9 +1687,84 @@ function statusAction() {
|
|
|
1389
1687
|
}
|
|
1390
1688
|
|
|
1391
1689
|
// src/commands/auth/index.ts
|
|
1392
|
-
var loginCommand = new
|
|
1393
|
-
var logoutCommand = new
|
|
1394
|
-
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
|
+
}
|
|
1395
1768
|
|
|
1396
1769
|
// src/index.ts
|
|
1397
1770
|
var HELP_TEXT = `
|
|
@@ -1403,9 +1776,15 @@ AUTHENTICATION
|
|
|
1403
1776
|
logout Clear stored credentials
|
|
1404
1777
|
status Check authentication status
|
|
1405
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
|
+
|
|
1406
1784
|
CONNECTORS
|
|
1407
1785
|
connector create Connect a database (postgresql, snowflake, etc.)
|
|
1408
|
-
connector list List your connectors
|
|
1786
|
+
connector list List your connectors (* = current)
|
|
1787
|
+
connector switch Switch to a different connector
|
|
1409
1788
|
connector delete Delete a connector
|
|
1410
1789
|
|
|
1411
1790
|
BRANCHES
|
|
@@ -1435,24 +1814,25 @@ EXAMPLES
|
|
|
1435
1814
|
ardent branch switch my-feature
|
|
1436
1815
|
ardent org members
|
|
1437
1816
|
`;
|
|
1438
|
-
var
|
|
1439
|
-
var program = new
|
|
1440
|
-
function
|
|
1817
|
+
var CLI_VERSION2 = getCliVersion2();
|
|
1818
|
+
var program = new Command7();
|
|
1819
|
+
function getCliVersion2() {
|
|
1441
1820
|
try {
|
|
1442
1821
|
const packageUrl = new URL("../package.json", import.meta.url);
|
|
1443
|
-
const raw =
|
|
1822
|
+
const raw = readFileSync4(packageUrl, "utf-8");
|
|
1444
1823
|
const parsed = JSON.parse(raw);
|
|
1445
1824
|
return parsed.version ?? "unknown";
|
|
1446
1825
|
} catch {
|
|
1447
1826
|
return "unknown";
|
|
1448
1827
|
}
|
|
1449
1828
|
}
|
|
1450
|
-
program.name("ardent").description("CLI for Ardent database branching").version(
|
|
1829
|
+
program.name("ardent").description("CLI for Ardent database branching").version(CLI_VERSION2).configureHelp({
|
|
1451
1830
|
formatHelp: () => BANNER + HELP_TEXT
|
|
1452
1831
|
});
|
|
1453
1832
|
program.addCommand(loginCommand);
|
|
1454
1833
|
program.addCommand(logoutCommand);
|
|
1455
1834
|
program.addCommand(statusCommand);
|
|
1835
|
+
program.addCommand(projectCommand);
|
|
1456
1836
|
program.addCommand(connectorCommand);
|
|
1457
1837
|
program.addCommand(branchCommand);
|
|
1458
1838
|
program.addCommand(inviteCommand);
|
|
@@ -1480,4 +1860,5 @@ if (args.length === 0) {
|
|
|
1480
1860
|
program.help();
|
|
1481
1861
|
}
|
|
1482
1862
|
getAnonymousId();
|
|
1863
|
+
checkForUpdate(CLI_VERSION2);
|
|
1483
1864
|
program.parse();
|