@riventa/cli 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-BMS2NG3R.js +1282 -0
- package/dist/index.js +336 -38
- package/dist/repl-OYJ3XUWI.js +134 -0
- package/dist/repl.d.ts +3 -0
- package/dist/repl.js +1351 -0
- package/package.json +2 -2
package/dist/repl.js
ADDED
|
@@ -0,0 +1,1351 @@
|
|
|
1
|
+
// src/repl.ts
|
|
2
|
+
import { createInterface } from "readline";
|
|
3
|
+
import chalk16 from "chalk";
|
|
4
|
+
|
|
5
|
+
// src/utils/api.ts
|
|
6
|
+
import Conf from "conf";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
var config = new Conf({ projectName: "riventa-cli" });
|
|
9
|
+
function getApiKey() {
|
|
10
|
+
return config.get("apiKey");
|
|
11
|
+
}
|
|
12
|
+
function getProjectId() {
|
|
13
|
+
return config.get("projectId");
|
|
14
|
+
}
|
|
15
|
+
function getBaseUrl() {
|
|
16
|
+
return config.get("baseUrl") || "https://riventa.dev/api";
|
|
17
|
+
}
|
|
18
|
+
function setConfig(key, value) {
|
|
19
|
+
config.set(key, value);
|
|
20
|
+
}
|
|
21
|
+
function getConfig(key) {
|
|
22
|
+
return config.get(key);
|
|
23
|
+
}
|
|
24
|
+
function getAllConfig() {
|
|
25
|
+
return config.store;
|
|
26
|
+
}
|
|
27
|
+
function requireAuth() {
|
|
28
|
+
const apiKey = getApiKey();
|
|
29
|
+
if (!apiKey) {
|
|
30
|
+
console.log(chalk.red("\n Not authenticated. Run `riventa login` first.\n"));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
return apiKey;
|
|
34
|
+
}
|
|
35
|
+
function requireProject() {
|
|
36
|
+
const projectId = getProjectId();
|
|
37
|
+
if (!projectId) {
|
|
38
|
+
console.log(chalk.red("\n No project configured. Run `riventa init` first.\n"));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
return projectId;
|
|
42
|
+
}
|
|
43
|
+
async function apiRequest(endpoint, options = {}) {
|
|
44
|
+
const apiKey = requireAuth();
|
|
45
|
+
const url = `${getBaseUrl()}${endpoint}`;
|
|
46
|
+
const response = await fetch(url, {
|
|
47
|
+
...options,
|
|
48
|
+
headers: {
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
51
|
+
...options.headers
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const error = await response.json().catch(() => ({ message: "Unknown error" }));
|
|
56
|
+
if (response.status === 401) {
|
|
57
|
+
console.log(chalk.red("\n Authentication failed. Run `riventa login` to re-authenticate.\n"));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
throw new Error(error.error || error.message || `HTTP ${response.status}`);
|
|
61
|
+
}
|
|
62
|
+
return response.json();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/banner.ts
|
|
66
|
+
import chalk2 from "chalk";
|
|
67
|
+
function printBanner(version, email) {
|
|
68
|
+
console.log();
|
|
69
|
+
console.log(
|
|
70
|
+
chalk2.bold.white("RIVENTA") + chalk2.bold.hex("#2563EB")(".DEV")
|
|
71
|
+
);
|
|
72
|
+
console.log(
|
|
73
|
+
chalk2.gray(`Riventa CLI v${version} \xB7 AI-powered DevOps`)
|
|
74
|
+
);
|
|
75
|
+
if (email) {
|
|
76
|
+
console.log(chalk2.gray(`Logged in as `) + chalk2.white(email));
|
|
77
|
+
}
|
|
78
|
+
console.log();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/commands/login.ts
|
|
82
|
+
import chalk3 from "chalk";
|
|
83
|
+
import inquirer from "inquirer";
|
|
84
|
+
import ora from "ora";
|
|
85
|
+
|
|
86
|
+
// src/utils/browser.ts
|
|
87
|
+
import open from "open";
|
|
88
|
+
import { exec } from "child_process";
|
|
89
|
+
import { readFileSync, existsSync } from "fs";
|
|
90
|
+
function openUrl(url) {
|
|
91
|
+
const isWSL = existsSync("/proc/version") && readFileSync("/proc/version", "utf-8").toLowerCase().includes("microsoft");
|
|
92
|
+
if (isWSL) {
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
exec(`cmd.exe /c start "" "${url.replace(/&/g, "^&")}"`, (err) => {
|
|
95
|
+
if (err) reject(err);
|
|
96
|
+
else resolve();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return open(url).then(() => {
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/commands/login.ts
|
|
105
|
+
async function loginCommand() {
|
|
106
|
+
const existingKey = getApiKey();
|
|
107
|
+
if (existingKey) {
|
|
108
|
+
const user = getConfig("user");
|
|
109
|
+
if (user?.email) {
|
|
110
|
+
console.log(chalk3.gray(`
|
|
111
|
+
Already logged in as ${chalk3.white(user.email)}`));
|
|
112
|
+
const { reauth } = await inquirer.prompt([
|
|
113
|
+
{
|
|
114
|
+
type: "confirm",
|
|
115
|
+
name: "reauth",
|
|
116
|
+
message: "Do you want to re-authenticate?",
|
|
117
|
+
default: false
|
|
118
|
+
}
|
|
119
|
+
]);
|
|
120
|
+
if (!reauth) return;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
console.log();
|
|
124
|
+
console.log(chalk3.bold(" Welcome to Riventa.Dev CLI"));
|
|
125
|
+
console.log(chalk3.gray(" Connect your terminal to your Riventa account.\n"));
|
|
126
|
+
const { method } = await inquirer.prompt([
|
|
127
|
+
{
|
|
128
|
+
type: "list",
|
|
129
|
+
name: "method",
|
|
130
|
+
message: "How would you like to authenticate?",
|
|
131
|
+
choices: [
|
|
132
|
+
{
|
|
133
|
+
name: `${chalk3.cyan("Browser")} \u2014 sign in via browser (recommended)`,
|
|
134
|
+
value: "browser"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: `${chalk3.cyan("API Key")} \u2014 paste an existing API key`,
|
|
138
|
+
value: "manual"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
]);
|
|
143
|
+
if (method === "browser") {
|
|
144
|
+
await deviceFlowLogin();
|
|
145
|
+
} else {
|
|
146
|
+
await manualLogin();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function deviceFlowLogin() {
|
|
150
|
+
const baseUrl = getBaseUrl();
|
|
151
|
+
const spinner = ora("Requesting authorization...").start();
|
|
152
|
+
try {
|
|
153
|
+
const response = await fetch(`${baseUrl}/v1/auth/device`, {
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers: { "Content-Type": "application/json" }
|
|
156
|
+
});
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
throw new Error("Failed to create device code");
|
|
159
|
+
}
|
|
160
|
+
const result = await response.json();
|
|
161
|
+
const { deviceCode, userCode, verificationUrl } = result.data;
|
|
162
|
+
spinner.stop();
|
|
163
|
+
console.log();
|
|
164
|
+
console.log(chalk3.bold(" Your device code:"));
|
|
165
|
+
console.log();
|
|
166
|
+
console.log(chalk3.cyan(` ${userCode.slice(0, 4)} - ${userCode.slice(4)}`));
|
|
167
|
+
console.log();
|
|
168
|
+
console.log(chalk3.gray(` Opening ${verificationUrl}`));
|
|
169
|
+
console.log(chalk3.gray(" Enter the code above to authorize this device.\n"));
|
|
170
|
+
try {
|
|
171
|
+
await openUrl(verificationUrl);
|
|
172
|
+
} catch {
|
|
173
|
+
console.log(chalk3.yellow(` Could not open browser automatically.`));
|
|
174
|
+
console.log(chalk3.yellow(` Please visit: ${chalk3.white(verificationUrl)}
|
|
175
|
+
`));
|
|
176
|
+
}
|
|
177
|
+
const pollSpinner = ora("Waiting for authorization...").start();
|
|
178
|
+
const maxAttempts = 60;
|
|
179
|
+
let attempts = 0;
|
|
180
|
+
while (attempts < maxAttempts) {
|
|
181
|
+
await sleep(3e3);
|
|
182
|
+
attempts++;
|
|
183
|
+
try {
|
|
184
|
+
const pollResponse = await fetch(`${baseUrl}/v1/auth/device?code=${deviceCode}`);
|
|
185
|
+
const pollResult = await pollResponse.json();
|
|
186
|
+
if (pollResult.data?.status === "authorized") {
|
|
187
|
+
pollSpinner.succeed(chalk3.green("Authorized!"));
|
|
188
|
+
const { apiKey, user } = pollResult.data;
|
|
189
|
+
setConfig("apiKey", apiKey);
|
|
190
|
+
setConfig("user", user);
|
|
191
|
+
console.log();
|
|
192
|
+
console.log(chalk3.gray(" Logged in as: ") + chalk3.white(user.email));
|
|
193
|
+
if (user.name) console.log(chalk3.gray(" Name: ") + chalk3.white(user.name));
|
|
194
|
+
console.log();
|
|
195
|
+
console.log(chalk3.gray(" Get started:"));
|
|
196
|
+
console.log(chalk3.gray(" riventa init \u2014 initialize a project"));
|
|
197
|
+
console.log(chalk3.gray(" riventa projects \u2014 list your projects"));
|
|
198
|
+
console.log(chalk3.gray(" riventa review \u2014 run AI code review"));
|
|
199
|
+
console.log(chalk3.gray(" riventa --help \u2014 see all commands"));
|
|
200
|
+
console.log();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (pollResult.data?.status !== "pending") {
|
|
204
|
+
pollSpinner.fail(chalk3.red("Authorization failed"));
|
|
205
|
+
console.log(chalk3.red("\n The code may have expired. Run `riventa login` to try again.\n"));
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
pollSpinner.fail(chalk3.red("Authorization timed out"));
|
|
212
|
+
console.log(chalk3.red("\n The code expired. Run `riventa login` to try again.\n"));
|
|
213
|
+
process.exit(1);
|
|
214
|
+
} catch {
|
|
215
|
+
spinner.fail(chalk3.red("Failed to start authorization"));
|
|
216
|
+
console.log(chalk3.red("\n Could not reach Riventa servers. Check your connection.\n"));
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function manualLogin() {
|
|
221
|
+
const answer = await inquirer.prompt([
|
|
222
|
+
{
|
|
223
|
+
type: "password",
|
|
224
|
+
name: "apiKey",
|
|
225
|
+
message: "Enter your API key (starts with riv_):",
|
|
226
|
+
mask: "*",
|
|
227
|
+
validate: (input) => {
|
|
228
|
+
if (!input.startsWith("riv_")) return 'API key must start with "riv_"';
|
|
229
|
+
if (input.length < 20) return "API key is too short";
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
]);
|
|
234
|
+
await verifyAndSaveKey(answer.apiKey);
|
|
235
|
+
}
|
|
236
|
+
async function verifyAndSaveKey(apiKey) {
|
|
237
|
+
const spinner = ora("Verifying API key...").start();
|
|
238
|
+
try {
|
|
239
|
+
const baseUrl = getBaseUrl();
|
|
240
|
+
const response = await fetch(`${baseUrl}/v1/auth/verify`, {
|
|
241
|
+
headers: {
|
|
242
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
243
|
+
"Content-Type": "application/json"
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
if (!response.ok) {
|
|
247
|
+
throw new Error("Invalid API key");
|
|
248
|
+
}
|
|
249
|
+
const result = await response.json();
|
|
250
|
+
const data = result.data || result;
|
|
251
|
+
setConfig("apiKey", apiKey);
|
|
252
|
+
setConfig("user", data.user || data);
|
|
253
|
+
spinner.succeed(chalk3.green("Authenticated successfully!"));
|
|
254
|
+
const email = data.user?.email || data.email;
|
|
255
|
+
const name = data.user?.name || data.name;
|
|
256
|
+
console.log();
|
|
257
|
+
console.log(chalk3.gray(" Logged in as: ") + chalk3.white(email || "unknown"));
|
|
258
|
+
if (name) console.log(chalk3.gray(" Name: ") + chalk3.white(name));
|
|
259
|
+
console.log();
|
|
260
|
+
console.log(chalk3.gray(" Get started:"));
|
|
261
|
+
console.log(chalk3.gray(" riventa init \u2014 initialize a project"));
|
|
262
|
+
console.log(chalk3.gray(" riventa projects \u2014 list your projects"));
|
|
263
|
+
console.log(chalk3.gray(" riventa review \u2014 run AI code review"));
|
|
264
|
+
console.log(chalk3.gray(" riventa --help \u2014 see all commands"));
|
|
265
|
+
console.log();
|
|
266
|
+
} catch {
|
|
267
|
+
spinner.fail(chalk3.red("Authentication failed"));
|
|
268
|
+
console.log(chalk3.red("\n Invalid or expired API key. Please try again.\n"));
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function sleep(ms) {
|
|
273
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/commands/init.ts
|
|
277
|
+
import chalk4 from "chalk";
|
|
278
|
+
import inquirer2 from "inquirer";
|
|
279
|
+
import fs from "fs";
|
|
280
|
+
import path from "path";
|
|
281
|
+
import Conf2 from "conf";
|
|
282
|
+
import ora2 from "ora";
|
|
283
|
+
var config2 = new Conf2({ projectName: "riventa-cli" });
|
|
284
|
+
function analyzeLocalProject() {
|
|
285
|
+
const analysis = {
|
|
286
|
+
name: path.basename(process.cwd())
|
|
287
|
+
};
|
|
288
|
+
const pkgPath = path.join(process.cwd(), "package.json");
|
|
289
|
+
if (fs.existsSync(pkgPath)) {
|
|
290
|
+
try {
|
|
291
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
292
|
+
const allDeps = {
|
|
293
|
+
...pkg.dependencies,
|
|
294
|
+
...pkg.devDependencies
|
|
295
|
+
};
|
|
296
|
+
const depNames = Object.keys(allDeps);
|
|
297
|
+
analysis.dependencies = depNames;
|
|
298
|
+
analysis.scripts = pkg.scripts || {};
|
|
299
|
+
if (depNames.includes("next")) analysis.framework = "nextjs";
|
|
300
|
+
else if (depNames.includes("nuxt")) analysis.framework = "nuxt";
|
|
301
|
+
else if (depNames.includes("vue")) analysis.framework = "vue";
|
|
302
|
+
else if (depNames.includes("react")) analysis.framework = "react";
|
|
303
|
+
else if (depNames.includes("@angular/core")) analysis.framework = "angular";
|
|
304
|
+
else if (depNames.includes("svelte")) analysis.framework = "svelte";
|
|
305
|
+
else if (depNames.includes("express") || depNames.includes("fastify") || depNames.includes("koa")) {
|
|
306
|
+
analysis.framework = "nodejs";
|
|
307
|
+
}
|
|
308
|
+
} catch {
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (fs.existsSync(path.join(process.cwd(), "requirements.txt")) || fs.existsSync(path.join(process.cwd(), "pyproject.toml"))) {
|
|
312
|
+
analysis.framework = analysis.framework || "python";
|
|
313
|
+
}
|
|
314
|
+
if (fs.existsSync(path.join(process.cwd(), "go.mod"))) {
|
|
315
|
+
analysis.framework = analysis.framework || "go";
|
|
316
|
+
}
|
|
317
|
+
analysis.hasDockerfile = fs.existsSync(path.join(process.cwd(), "Dockerfile"));
|
|
318
|
+
analysis.hasTsConfig = fs.existsSync(path.join(process.cwd(), "tsconfig.json"));
|
|
319
|
+
analysis.hasGithubActions = fs.existsSync(path.join(process.cwd(), ".github"));
|
|
320
|
+
return analysis;
|
|
321
|
+
}
|
|
322
|
+
function getStaticTemplate(projectId, environment, autoReview, autoRollback) {
|
|
323
|
+
return `# Riventa.Dev Configuration
|
|
324
|
+
# Generated by riventa init
|
|
325
|
+
|
|
326
|
+
version: 1
|
|
327
|
+
|
|
328
|
+
project:
|
|
329
|
+
id: ${projectId}
|
|
330
|
+
|
|
331
|
+
# Code review settings
|
|
332
|
+
review:
|
|
333
|
+
enabled: ${autoReview}
|
|
334
|
+
auto_comment: true
|
|
335
|
+
severity_threshold: warning
|
|
336
|
+
ignore_patterns:
|
|
337
|
+
- "*.test.js"
|
|
338
|
+
- "*.spec.ts"
|
|
339
|
+
- "dist/**"
|
|
340
|
+
|
|
341
|
+
# Deployment settings
|
|
342
|
+
deploy:
|
|
343
|
+
environment: ${environment}
|
|
344
|
+
auto_rollback: ${autoRollback}
|
|
345
|
+
health_check_url: /api/health
|
|
346
|
+
rollback_threshold: 5 # Error rate percentage
|
|
347
|
+
|
|
348
|
+
# Notifications
|
|
349
|
+
notifications:
|
|
350
|
+
slack:
|
|
351
|
+
enabled: false
|
|
352
|
+
channel: "#deployments"
|
|
353
|
+
email:
|
|
354
|
+
enabled: false
|
|
355
|
+
`;
|
|
356
|
+
}
|
|
357
|
+
async function initCommand() {
|
|
358
|
+
console.log(chalk4.cyan("\n\u{1F680} Initialize Riventa.Dev\n"));
|
|
359
|
+
if (fs.existsSync(".riventa.yml")) {
|
|
360
|
+
const { overwrite } = await inquirer2.prompt([
|
|
361
|
+
{
|
|
362
|
+
type: "confirm",
|
|
363
|
+
name: "overwrite",
|
|
364
|
+
message: ".riventa.yml already exists. Overwrite?",
|
|
365
|
+
default: false
|
|
366
|
+
}
|
|
367
|
+
]);
|
|
368
|
+
if (!overwrite) {
|
|
369
|
+
console.log(chalk4.yellow("\n Initialization cancelled.\n"));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const analysisSpinner = ora2("Analyzing project...").start();
|
|
374
|
+
const projectAnalysis = analyzeLocalProject();
|
|
375
|
+
analysisSpinner.succeed(`Detected: ${projectAnalysis.framework || "generic"} project`);
|
|
376
|
+
if (projectAnalysis.framework) {
|
|
377
|
+
console.log(chalk4.gray(` Framework: ${projectAnalysis.framework}`));
|
|
378
|
+
}
|
|
379
|
+
if (projectAnalysis.hasTsConfig) {
|
|
380
|
+
console.log(chalk4.gray(" TypeScript: yes"));
|
|
381
|
+
}
|
|
382
|
+
if (projectAnalysis.hasDockerfile) {
|
|
383
|
+
console.log(chalk4.gray(" Docker: yes"));
|
|
384
|
+
}
|
|
385
|
+
console.log("");
|
|
386
|
+
const spinner = ora2("Fetching your projects...").start();
|
|
387
|
+
try {
|
|
388
|
+
const { projects } = await apiRequest("/v1/projects");
|
|
389
|
+
spinner.stop();
|
|
390
|
+
let projectId;
|
|
391
|
+
if (projects.length > 0) {
|
|
392
|
+
const { choice } = await inquirer2.prompt([
|
|
393
|
+
{
|
|
394
|
+
type: "list",
|
|
395
|
+
name: "choice",
|
|
396
|
+
message: "Select a project or create new:",
|
|
397
|
+
choices: [
|
|
398
|
+
...projects.map((p) => ({ name: p.name, value: p.id })),
|
|
399
|
+
{ name: "+ Create new project", value: "new" }
|
|
400
|
+
]
|
|
401
|
+
}
|
|
402
|
+
]);
|
|
403
|
+
if (choice === "new") {
|
|
404
|
+
projectId = await createNewProject();
|
|
405
|
+
} else {
|
|
406
|
+
projectId = choice;
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
projectId = await createNewProject();
|
|
410
|
+
}
|
|
411
|
+
const aiSpinner = ora2("Riv AI generating configuration...").start();
|
|
412
|
+
let configContent = null;
|
|
413
|
+
try {
|
|
414
|
+
const baseUrl = getBaseUrl();
|
|
415
|
+
const response = await apiRequest("/v1/projects/init-config", {
|
|
416
|
+
method: "POST",
|
|
417
|
+
body: JSON.stringify({
|
|
418
|
+
mode: "create",
|
|
419
|
+
projectAnalysis,
|
|
420
|
+
preferences: {
|
|
421
|
+
includeTests: true,
|
|
422
|
+
includeLinting: true,
|
|
423
|
+
includeDocker: projectAnalysis.hasDockerfile
|
|
424
|
+
}
|
|
425
|
+
})
|
|
426
|
+
});
|
|
427
|
+
if (response.success && response.data?.config) {
|
|
428
|
+
configContent = response.data.config;
|
|
429
|
+
aiSpinner.succeed("Riv AI generated configuration");
|
|
430
|
+
console.log(chalk4.cyan("\n --- Generated Config Preview ---"));
|
|
431
|
+
const previewLines = configContent.split("\n").slice(0, 20);
|
|
432
|
+
previewLines.forEach((line) => {
|
|
433
|
+
console.log(chalk4.gray(` ${line}`));
|
|
434
|
+
});
|
|
435
|
+
if (configContent.split("\n").length > 20) {
|
|
436
|
+
console.log(chalk4.gray(" ..."));
|
|
437
|
+
}
|
|
438
|
+
console.log(chalk4.cyan(" --- End Preview ---\n"));
|
|
439
|
+
if (response.data.explanation) {
|
|
440
|
+
console.log(chalk4.gray(` ${response.data.explanation}
|
|
441
|
+
`));
|
|
442
|
+
}
|
|
443
|
+
const { confirm } = await inquirer2.prompt([
|
|
444
|
+
{
|
|
445
|
+
type: "confirm",
|
|
446
|
+
name: "confirm",
|
|
447
|
+
message: "Apply this configuration?",
|
|
448
|
+
default: true
|
|
449
|
+
}
|
|
450
|
+
]);
|
|
451
|
+
if (!confirm) {
|
|
452
|
+
configContent = null;
|
|
453
|
+
}
|
|
454
|
+
} else {
|
|
455
|
+
aiSpinner.warn("AI generation unavailable, using default template");
|
|
456
|
+
configContent = null;
|
|
457
|
+
}
|
|
458
|
+
} catch {
|
|
459
|
+
aiSpinner.warn("AI generation unavailable, using default template");
|
|
460
|
+
configContent = null;
|
|
461
|
+
}
|
|
462
|
+
if (!configContent) {
|
|
463
|
+
const answers = await inquirer2.prompt([
|
|
464
|
+
{
|
|
465
|
+
type: "list",
|
|
466
|
+
name: "environment",
|
|
467
|
+
message: "Default deployment environment:",
|
|
468
|
+
choices: ["production", "staging", "development"],
|
|
469
|
+
default: "production"
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
type: "confirm",
|
|
473
|
+
name: "autoReview",
|
|
474
|
+
message: "Enable automatic code review on commits?",
|
|
475
|
+
default: true
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
type: "confirm",
|
|
479
|
+
name: "autoRollback",
|
|
480
|
+
message: "Enable automatic rollback on failures?",
|
|
481
|
+
default: true
|
|
482
|
+
}
|
|
483
|
+
]);
|
|
484
|
+
configContent = getStaticTemplate(projectId, answers.environment, answers.autoReview, answers.autoRollback);
|
|
485
|
+
console.log(chalk4.yellow("\n Tip: Run `riventa config update` to enhance your config with AI.\n"));
|
|
486
|
+
}
|
|
487
|
+
fs.writeFileSync(".riventa.yml", configContent);
|
|
488
|
+
config2.set("projectId", projectId);
|
|
489
|
+
console.log(chalk4.green("\n\u2713 Riventa initialized successfully!"));
|
|
490
|
+
console.log(chalk4.gray("\n Created .riventa.yml configuration file."));
|
|
491
|
+
console.log(chalk4.gray(" Run `riventa deploy` to deploy your project.\n"));
|
|
492
|
+
} catch (error) {
|
|
493
|
+
spinner.fail(chalk4.red("Failed to initialize"));
|
|
494
|
+
console.error(error);
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
async function createNewProject() {
|
|
499
|
+
const { name, repoUrl } = await inquirer2.prompt([
|
|
500
|
+
{
|
|
501
|
+
type: "input",
|
|
502
|
+
name: "name",
|
|
503
|
+
message: "Project name:",
|
|
504
|
+
default: path.basename(process.cwd())
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
type: "input",
|
|
508
|
+
name: "repoUrl",
|
|
509
|
+
message: "Repository URL (optional):"
|
|
510
|
+
}
|
|
511
|
+
]);
|
|
512
|
+
const spinner = ora2("Creating project...").start();
|
|
513
|
+
const { project } = await apiRequest("/v1/projects", {
|
|
514
|
+
method: "POST",
|
|
515
|
+
body: JSON.stringify({ name, repoUrl })
|
|
516
|
+
});
|
|
517
|
+
spinner.succeed(`Created project: ${project.name}`);
|
|
518
|
+
return project.id;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/commands/deploy.ts
|
|
522
|
+
import chalk5 from "chalk";
|
|
523
|
+
import ora3 from "ora";
|
|
524
|
+
async function deployCommand(options) {
|
|
525
|
+
const projectId = getProjectId();
|
|
526
|
+
if (!projectId) {
|
|
527
|
+
console.log(chalk5.red("\n\u2717 No project configured. Run `riventa init` first.\n"));
|
|
528
|
+
process.exit(1);
|
|
529
|
+
}
|
|
530
|
+
console.log(chalk5.cyan(`
|
|
531
|
+
\u{1F680} Deploying to ${options.environment}...
|
|
532
|
+
`));
|
|
533
|
+
const spinner = ora3("Creating deployment...").start();
|
|
534
|
+
try {
|
|
535
|
+
const { deployment } = await apiRequest("/v1/deployments", {
|
|
536
|
+
method: "POST",
|
|
537
|
+
body: JSON.stringify({
|
|
538
|
+
projectId,
|
|
539
|
+
environment: options.environment,
|
|
540
|
+
branch: options.branch || "main"
|
|
541
|
+
})
|
|
542
|
+
});
|
|
543
|
+
spinner.succeed(`Deployment created: ${deployment.id}`);
|
|
544
|
+
console.log(chalk5.gray(`
|
|
545
|
+
Status: ${deployment.status}`));
|
|
546
|
+
console.log(chalk5.gray(` Environment: ${deployment.environment}`));
|
|
547
|
+
console.log(chalk5.gray(` Branch: ${deployment.branch}`));
|
|
548
|
+
if (deployment.url) {
|
|
549
|
+
console.log(chalk5.green(`
|
|
550
|
+
URL: ${deployment.url}
|
|
551
|
+
`));
|
|
552
|
+
}
|
|
553
|
+
console.log(chalk5.gray(" Run `riventa status` to check deployment progress.\n"));
|
|
554
|
+
} catch (error) {
|
|
555
|
+
spinner.fail(chalk5.red("Deployment failed"));
|
|
556
|
+
console.log(chalk5.red(`
|
|
557
|
+
${error.message}
|
|
558
|
+
`));
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// src/commands/review.ts
|
|
564
|
+
import chalk6 from "chalk";
|
|
565
|
+
import ora4 from "ora";
|
|
566
|
+
import fs2 from "fs";
|
|
567
|
+
import { execSync } from "child_process";
|
|
568
|
+
async function reviewCommand(options) {
|
|
569
|
+
const projectId = getProjectId();
|
|
570
|
+
if (!projectId) {
|
|
571
|
+
console.log(chalk6.red("\n\u2717 No project configured. Run `riventa init` first.\n"));
|
|
572
|
+
process.exit(1);
|
|
573
|
+
}
|
|
574
|
+
console.log(chalk6.cyan("\n\u{1F50D} Running AI Code Review...\n"));
|
|
575
|
+
let code;
|
|
576
|
+
let context;
|
|
577
|
+
if (options.file) {
|
|
578
|
+
if (!fs2.existsSync(options.file)) {
|
|
579
|
+
console.log(chalk6.red(`
|
|
580
|
+
\u2717 File not found: ${options.file}
|
|
581
|
+
`));
|
|
582
|
+
process.exit(1);
|
|
583
|
+
}
|
|
584
|
+
code = fs2.readFileSync(options.file, "utf-8");
|
|
585
|
+
context = `File: ${options.file}`;
|
|
586
|
+
} else if (options.pr) {
|
|
587
|
+
const spinner2 = ora4("Fetching PR diff...").start();
|
|
588
|
+
try {
|
|
589
|
+
const { diff } = await apiRequest(`/v1/projects/${projectId}/prs/${options.pr}/diff`);
|
|
590
|
+
code = diff;
|
|
591
|
+
context = `PR #${options.pr}`;
|
|
592
|
+
spinner2.stop();
|
|
593
|
+
} catch (error) {
|
|
594
|
+
spinner2.fail("Failed to fetch PR");
|
|
595
|
+
process.exit(1);
|
|
596
|
+
}
|
|
597
|
+
} else {
|
|
598
|
+
console.log(chalk6.yellow(" No file or PR specified. Reviewing staged changes...\n"));
|
|
599
|
+
try {
|
|
600
|
+
code = execSync("git diff --staged", { encoding: "utf-8" });
|
|
601
|
+
if (!code) {
|
|
602
|
+
code = execSync("git diff HEAD~1", { encoding: "utf-8" });
|
|
603
|
+
}
|
|
604
|
+
context = "Staged changes";
|
|
605
|
+
} catch {
|
|
606
|
+
console.log(chalk6.red("\n\u2717 No changes to review. Stage some files or specify --file or --pr.\n"));
|
|
607
|
+
process.exit(1);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
const spinner = ora4("Analyzing code with AI...").start();
|
|
611
|
+
try {
|
|
612
|
+
const { review } = await apiRequest("/v1/reviews", {
|
|
613
|
+
method: "POST",
|
|
614
|
+
body: JSON.stringify({
|
|
615
|
+
projectId,
|
|
616
|
+
code,
|
|
617
|
+
context
|
|
618
|
+
})
|
|
619
|
+
});
|
|
620
|
+
spinner.succeed("Review complete!\n");
|
|
621
|
+
console.log(chalk6.bold(` Score: ${getScoreEmoji(review.score)} ${review.score}/100
|
|
622
|
+
`));
|
|
623
|
+
console.log(chalk6.bold(" Summary:"));
|
|
624
|
+
console.log(chalk6.gray(` ${review.summary}
|
|
625
|
+
`));
|
|
626
|
+
if (review.issues?.length > 0) {
|
|
627
|
+
console.log(chalk6.bold(` Issues (${review.issues.length}):`));
|
|
628
|
+
review.issues.forEach((issue) => {
|
|
629
|
+
const icon = issue.severity === "error" ? "\u{1F534}" : issue.severity === "warning" ? "\u{1F7E1}" : "\u{1F535}";
|
|
630
|
+
console.log(chalk6.gray(` ${icon} ${issue.message}`));
|
|
631
|
+
if (issue.line) {
|
|
632
|
+
console.log(chalk6.gray(` Line ${issue.line}`));
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
console.log("");
|
|
636
|
+
}
|
|
637
|
+
if (review.suggestions?.length > 0) {
|
|
638
|
+
console.log(chalk6.bold(" Suggestions:"));
|
|
639
|
+
review.suggestions.forEach((suggestion, i) => {
|
|
640
|
+
console.log(chalk6.gray(` ${i + 1}. ${suggestion}`));
|
|
641
|
+
});
|
|
642
|
+
console.log("");
|
|
643
|
+
}
|
|
644
|
+
} catch (error) {
|
|
645
|
+
spinner.fail(chalk6.red("Review failed"));
|
|
646
|
+
console.log(chalk6.red(`
|
|
647
|
+
${error.message}
|
|
648
|
+
`));
|
|
649
|
+
process.exit(1);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function getScoreEmoji(score) {
|
|
653
|
+
if (score >= 90) return "\u{1F7E2}";
|
|
654
|
+
if (score >= 70) return "\u{1F7E1}";
|
|
655
|
+
if (score >= 50) return "\u{1F7E0}";
|
|
656
|
+
return "\u{1F534}";
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/commands/status.ts
|
|
660
|
+
import chalk7 from "chalk";
|
|
661
|
+
import ora5 from "ora";
|
|
662
|
+
async function statusCommand() {
|
|
663
|
+
const projectId = getProjectId();
|
|
664
|
+
if (!projectId) {
|
|
665
|
+
console.log(chalk7.red("\n\u2717 No project configured. Run `riventa init` first.\n"));
|
|
666
|
+
process.exit(1);
|
|
667
|
+
}
|
|
668
|
+
console.log(chalk7.cyan("\n\u{1F4CA} Project Status\n"));
|
|
669
|
+
const spinner = ora5("Fetching status...").start();
|
|
670
|
+
try {
|
|
671
|
+
const [projectData, deploymentsData] = await Promise.all([
|
|
672
|
+
apiRequest(`/v1/projects/${projectId}`),
|
|
673
|
+
apiRequest(`/v1/projects/${projectId}/deployments?limit=5`)
|
|
674
|
+
]);
|
|
675
|
+
spinner.stop();
|
|
676
|
+
const { project } = projectData;
|
|
677
|
+
const { deployments } = deploymentsData;
|
|
678
|
+
console.log(chalk7.bold(" Project:"));
|
|
679
|
+
console.log(chalk7.gray(` Name: ${project.name}`));
|
|
680
|
+
console.log(chalk7.gray(` ID: ${project.id}`));
|
|
681
|
+
if (project.repoUrl) {
|
|
682
|
+
console.log(chalk7.gray(` Repository: ${project.repoUrl}`));
|
|
683
|
+
}
|
|
684
|
+
console.log("");
|
|
685
|
+
console.log(chalk7.bold(" Recent Deployments:"));
|
|
686
|
+
if (deployments.length === 0) {
|
|
687
|
+
console.log(chalk7.gray(" No deployments yet."));
|
|
688
|
+
} else {
|
|
689
|
+
deployments.forEach((d) => {
|
|
690
|
+
const statusIcon = getStatusIcon(d.status);
|
|
691
|
+
const date = new Date(d.createdAt).toLocaleString();
|
|
692
|
+
console.log(chalk7.gray(` ${statusIcon} ${d.environment} - ${d.branch} (${date})`));
|
|
693
|
+
if (d.url) {
|
|
694
|
+
console.log(chalk7.gray(` URL: ${d.url}`));
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
console.log("");
|
|
699
|
+
const activeDeployment = deployments.find((d) => d.status === "active");
|
|
700
|
+
if (activeDeployment) {
|
|
701
|
+
console.log(chalk7.bold(" Active Deployment:"));
|
|
702
|
+
console.log(chalk7.green(` ${activeDeployment.environment} - ${activeDeployment.branch}`));
|
|
703
|
+
if (activeDeployment.url) {
|
|
704
|
+
console.log(chalk7.green(` URL: ${activeDeployment.url}`));
|
|
705
|
+
}
|
|
706
|
+
console.log("");
|
|
707
|
+
}
|
|
708
|
+
} catch (error) {
|
|
709
|
+
spinner.fail(chalk7.red("Failed to fetch status"));
|
|
710
|
+
console.log(chalk7.red(`
|
|
711
|
+
${error.message}
|
|
712
|
+
`));
|
|
713
|
+
process.exit(1);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
function getStatusIcon(status) {
|
|
717
|
+
switch (status) {
|
|
718
|
+
case "active":
|
|
719
|
+
case "success":
|
|
720
|
+
return "\u{1F7E2}";
|
|
721
|
+
case "building":
|
|
722
|
+
case "deploying":
|
|
723
|
+
return "\u{1F7E1}";
|
|
724
|
+
case "failed":
|
|
725
|
+
case "error":
|
|
726
|
+
return "\u{1F534}";
|
|
727
|
+
case "cancelled":
|
|
728
|
+
return "\u26AA";
|
|
729
|
+
default:
|
|
730
|
+
return "\u{1F535}";
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// src/commands/logs.ts
|
|
735
|
+
import chalk8 from "chalk";
|
|
736
|
+
import ora6 from "ora";
|
|
737
|
+
async function logsCommand(options) {
|
|
738
|
+
const projectId = getProjectId();
|
|
739
|
+
if (!projectId) {
|
|
740
|
+
console.log(chalk8.red("\n\u2717 No project configured. Run `riventa init` first.\n"));
|
|
741
|
+
process.exit(1);
|
|
742
|
+
}
|
|
743
|
+
console.log(chalk8.cyan("\n\u{1F4DD} Deployment Logs\n"));
|
|
744
|
+
const spinner = ora6("Fetching logs...").start();
|
|
745
|
+
try {
|
|
746
|
+
let deploymentId = options.deployment;
|
|
747
|
+
if (!deploymentId) {
|
|
748
|
+
const { deployments } = await apiRequest(`/v1/projects/${projectId}/deployments?limit=1`);
|
|
749
|
+
if (deployments.length === 0) {
|
|
750
|
+
spinner.fail("No deployments found");
|
|
751
|
+
process.exit(1);
|
|
752
|
+
}
|
|
753
|
+
deploymentId = deployments[0].id;
|
|
754
|
+
}
|
|
755
|
+
const { logs, deployment } = await apiRequest(`/v1/deployments/${deploymentId}/logs`);
|
|
756
|
+
spinner.stop();
|
|
757
|
+
console.log(chalk8.bold(` Deployment: ${deployment.id}`));
|
|
758
|
+
console.log(chalk8.gray(` Environment: ${deployment.environment}`));
|
|
759
|
+
console.log(chalk8.gray(` Status: ${deployment.status}`));
|
|
760
|
+
console.log(chalk8.gray(` Created: ${new Date(deployment.createdAt).toLocaleString()}`));
|
|
761
|
+
console.log("");
|
|
762
|
+
console.log(chalk8.bold(" Logs:"));
|
|
763
|
+
console.log(chalk8.gray(" " + "-".repeat(60)));
|
|
764
|
+
if (logs.length === 0) {
|
|
765
|
+
console.log(chalk8.gray(" No logs available yet."));
|
|
766
|
+
} else {
|
|
767
|
+
logs.forEach((log) => {
|
|
768
|
+
const timestamp = new Date(log.timestamp).toLocaleTimeString();
|
|
769
|
+
const levelColor = log.level === "error" ? chalk8.red : log.level === "warn" ? chalk8.yellow : chalk8.gray;
|
|
770
|
+
console.log(` ${chalk8.dim(timestamp)} ${levelColor(`[${log.level.toUpperCase()}]`)} ${log.message}`);
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
console.log(chalk8.gray(" " + "-".repeat(60)));
|
|
774
|
+
console.log("");
|
|
775
|
+
if (options.follow && ["building", "deploying"].includes(deployment.status) && deploymentId) {
|
|
776
|
+
console.log(chalk8.yellow(" Following logs... (Ctrl+C to exit)\n"));
|
|
777
|
+
await followLogs(deploymentId);
|
|
778
|
+
}
|
|
779
|
+
} catch (error) {
|
|
780
|
+
spinner.fail(chalk8.red("Failed to fetch logs"));
|
|
781
|
+
console.log(chalk8.red(`
|
|
782
|
+
${error.message}
|
|
783
|
+
`));
|
|
784
|
+
process.exit(1);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
async function followLogs(deploymentId) {
|
|
788
|
+
let lastLogId = null;
|
|
789
|
+
const poll = async () => {
|
|
790
|
+
try {
|
|
791
|
+
const params = lastLogId ? `?after=${lastLogId}` : "";
|
|
792
|
+
const { logs, deployment } = await apiRequest(`/v1/deployments/${deploymentId}/logs${params}`);
|
|
793
|
+
if (logs.length > 0) {
|
|
794
|
+
logs.forEach((log) => {
|
|
795
|
+
const timestamp = new Date(log.timestamp).toLocaleTimeString();
|
|
796
|
+
const levelColor = log.level === "error" ? chalk8.red : log.level === "warn" ? chalk8.yellow : chalk8.gray;
|
|
797
|
+
console.log(` ${chalk8.dim(timestamp)} ${levelColor(`[${log.level.toUpperCase()}]`)} ${log.message}`);
|
|
798
|
+
});
|
|
799
|
+
lastLogId = logs[logs.length - 1].id;
|
|
800
|
+
}
|
|
801
|
+
if (["building", "deploying"].includes(deployment.status)) {
|
|
802
|
+
setTimeout(poll, 2e3);
|
|
803
|
+
} else {
|
|
804
|
+
console.log("");
|
|
805
|
+
console.log(chalk8.bold(` Deployment ${deployment.status === "active" ? "succeeded" : "failed"}!`));
|
|
806
|
+
if (deployment.url) {
|
|
807
|
+
console.log(chalk8.green(` URL: ${deployment.url}`));
|
|
808
|
+
}
|
|
809
|
+
console.log("");
|
|
810
|
+
}
|
|
811
|
+
} catch (error) {
|
|
812
|
+
console.log(chalk8.red(" Error polling logs. Retrying..."));
|
|
813
|
+
setTimeout(poll, 5e3);
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
await poll();
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// src/commands/rollback.ts
|
|
820
|
+
import chalk9 from "chalk";
|
|
821
|
+
import inquirer3 from "inquirer";
|
|
822
|
+
import ora7 from "ora";
|
|
823
|
+
async function rollbackCommand(options) {
|
|
824
|
+
const projectId = getProjectId();
|
|
825
|
+
if (!projectId) {
|
|
826
|
+
console.log(chalk9.red("\n\u2717 No project configured. Run `riventa init` first.\n"));
|
|
827
|
+
process.exit(1);
|
|
828
|
+
}
|
|
829
|
+
console.log(chalk9.cyan("\n\u23EA Rollback Deployment\n"));
|
|
830
|
+
let targetDeploymentId = options.deployment;
|
|
831
|
+
if (!targetDeploymentId) {
|
|
832
|
+
const spinner2 = ora7("Fetching deployments...").start();
|
|
833
|
+
try {
|
|
834
|
+
const { deployments } = await apiRequest(`/v1/projects/${projectId}/deployments?limit=10`);
|
|
835
|
+
spinner2.stop();
|
|
836
|
+
if (deployments.length < 2) {
|
|
837
|
+
console.log(chalk9.yellow(" No previous deployments to rollback to.\n"));
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
const successfulDeployments = deployments.filter(
|
|
841
|
+
(d) => d.status === "active" || d.status === "success"
|
|
842
|
+
);
|
|
843
|
+
if (successfulDeployments.length < 2) {
|
|
844
|
+
console.log(chalk9.yellow(" No previous successful deployments to rollback to.\n"));
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
const previousDeployments = successfulDeployments.slice(1);
|
|
848
|
+
const { selected } = await inquirer3.prompt([
|
|
849
|
+
{
|
|
850
|
+
type: "list",
|
|
851
|
+
name: "selected",
|
|
852
|
+
message: "Select a deployment to rollback to:",
|
|
853
|
+
choices: previousDeployments.map((d) => ({
|
|
854
|
+
name: `${d.environment} - ${d.branch} (${new Date(d.createdAt).toLocaleDateString()})`,
|
|
855
|
+
value: d.id
|
|
856
|
+
}))
|
|
857
|
+
}
|
|
858
|
+
]);
|
|
859
|
+
targetDeploymentId = selected;
|
|
860
|
+
} catch (error) {
|
|
861
|
+
spinner2.fail(chalk9.red("Failed to fetch deployments"));
|
|
862
|
+
console.log(chalk9.red(`
|
|
863
|
+
${error.message}
|
|
864
|
+
`));
|
|
865
|
+
process.exit(1);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
const { confirmed } = await inquirer3.prompt([
|
|
869
|
+
{
|
|
870
|
+
type: "confirm",
|
|
871
|
+
name: "confirmed",
|
|
872
|
+
message: `Are you sure you want to rollback to deployment ${targetDeploymentId}?`,
|
|
873
|
+
default: false
|
|
874
|
+
}
|
|
875
|
+
]);
|
|
876
|
+
if (!confirmed) {
|
|
877
|
+
console.log(chalk9.yellow("\n Rollback cancelled.\n"));
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
const spinner = ora7("Rolling back...").start();
|
|
881
|
+
try {
|
|
882
|
+
const { deployment } = await apiRequest(`/v1/deployments/${targetDeploymentId}/rollback`, {
|
|
883
|
+
method: "POST"
|
|
884
|
+
});
|
|
885
|
+
spinner.succeed(chalk9.green("Rollback initiated!"));
|
|
886
|
+
console.log(chalk9.gray(`
|
|
887
|
+
New deployment ID: ${deployment.id}`));
|
|
888
|
+
console.log(chalk9.gray(` Status: ${deployment.status}`));
|
|
889
|
+
if (deployment.url) {
|
|
890
|
+
console.log(chalk9.green(`
|
|
891
|
+
URL: ${deployment.url}
|
|
892
|
+
`));
|
|
893
|
+
}
|
|
894
|
+
console.log(chalk9.gray(" Run `riventa status` to check rollback progress.\n"));
|
|
895
|
+
} catch (error) {
|
|
896
|
+
spinner.fail(chalk9.red("Rollback failed"));
|
|
897
|
+
console.log(chalk9.red(`
|
|
898
|
+
${error.message}
|
|
899
|
+
`));
|
|
900
|
+
process.exit(1);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// src/commands/config.ts
|
|
905
|
+
import chalk10 from "chalk";
|
|
906
|
+
import inquirer4 from "inquirer";
|
|
907
|
+
async function configCommand(options) {
|
|
908
|
+
console.log(chalk10.cyan("\n\u2699\uFE0F CLI Configuration\n"));
|
|
909
|
+
if (options.get) {
|
|
910
|
+
const value = getConfig(options.get);
|
|
911
|
+
if (value !== void 0) {
|
|
912
|
+
console.log(chalk10.gray(` ${options.get}: ${formatValue(value)}`));
|
|
913
|
+
} else {
|
|
914
|
+
console.log(chalk10.yellow(` Config key '${options.get}' not found.`));
|
|
915
|
+
}
|
|
916
|
+
console.log("");
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
if (options.set) {
|
|
920
|
+
const [key, ...valueParts] = options.set.split("=");
|
|
921
|
+
const value = valueParts.join("=");
|
|
922
|
+
if (!key || value === void 0) {
|
|
923
|
+
console.log(chalk10.red(" Invalid format. Use --set key=value"));
|
|
924
|
+
console.log("");
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
let parsedValue = value;
|
|
928
|
+
if (value === "true") parsedValue = true;
|
|
929
|
+
else if (value === "false") parsedValue = false;
|
|
930
|
+
else if (!isNaN(Number(value)) && value !== "") parsedValue = Number(value);
|
|
931
|
+
setConfig(key, parsedValue);
|
|
932
|
+
console.log(chalk10.green(` \u2713 Set ${key} = ${formatValue(parsedValue)}`));
|
|
933
|
+
console.log("");
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (options.list) {
|
|
937
|
+
const config3 = getAllConfig();
|
|
938
|
+
const keys = Object.keys(config3);
|
|
939
|
+
if (keys.length === 0) {
|
|
940
|
+
console.log(chalk10.gray(" No configuration set."));
|
|
941
|
+
} else {
|
|
942
|
+
console.log(chalk10.bold(" Current Configuration:"));
|
|
943
|
+
console.log("");
|
|
944
|
+
keys.forEach((key) => {
|
|
945
|
+
const value = config3[key];
|
|
946
|
+
if (key === "apiKey") {
|
|
947
|
+
console.log(chalk10.gray(` ${key}: ********`));
|
|
948
|
+
} else {
|
|
949
|
+
console.log(chalk10.gray(` ${key}: ${formatValue(value)}`));
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
console.log("");
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
console.log(chalk10.bold(" Usage:"));
|
|
957
|
+
console.log(chalk10.gray(" riventa config --list List all configuration"));
|
|
958
|
+
console.log(chalk10.gray(" riventa config --get <key> Get a specific config value"));
|
|
959
|
+
console.log(chalk10.gray(" riventa config --set <k=v> Set a config value"));
|
|
960
|
+
console.log("");
|
|
961
|
+
console.log(chalk10.bold(" Available Keys:"));
|
|
962
|
+
console.log(chalk10.gray(" apiKey Your Riventa API key"));
|
|
963
|
+
console.log(chalk10.gray(" baseUrl API base URL (default: https://api.riventa.dev)"));
|
|
964
|
+
console.log(chalk10.gray(" projectId Current project ID"));
|
|
965
|
+
console.log(chalk10.gray(" user Authenticated user info"));
|
|
966
|
+
console.log(chalk10.gray(" organization Organization info"));
|
|
967
|
+
console.log("");
|
|
968
|
+
}
|
|
969
|
+
function formatValue(value) {
|
|
970
|
+
if (typeof value === "object") {
|
|
971
|
+
return JSON.stringify(value);
|
|
972
|
+
}
|
|
973
|
+
return String(value);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/commands/projects.ts
|
|
977
|
+
import chalk11 from "chalk";
|
|
978
|
+
import ora8 from "ora";
|
|
979
|
+
import inquirer5 from "inquirer";
|
|
980
|
+
async function projectsCommand() {
|
|
981
|
+
console.log(chalk11.cyan("\n\u{1F4C1} Your Projects\n"));
|
|
982
|
+
const spinner = ora8("Fetching projects...").start();
|
|
983
|
+
try {
|
|
984
|
+
const { projects } = await apiRequest("/v1/projects");
|
|
985
|
+
spinner.stop();
|
|
986
|
+
if (projects.length === 0) {
|
|
987
|
+
console.log(chalk11.gray(" No projects found."));
|
|
988
|
+
console.log(chalk11.gray(" Run `riventa init` to create a project.\n"));
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
const currentProjectId = getProjectId();
|
|
992
|
+
console.log(chalk11.bold(" Projects:"));
|
|
993
|
+
console.log("");
|
|
994
|
+
projects.forEach((project) => {
|
|
995
|
+
const isActive = project.id === currentProjectId;
|
|
996
|
+
const marker = isActive ? chalk11.green("\u2713") : " ";
|
|
997
|
+
const name = isActive ? chalk11.green(project.name) : project.name;
|
|
998
|
+
console.log(` ${marker} ${name}`);
|
|
999
|
+
console.log(chalk11.gray(` ID: ${project.id}`));
|
|
1000
|
+
if (project.repoUrl) {
|
|
1001
|
+
console.log(chalk11.gray(` Repo: ${project.repoUrl}`));
|
|
1002
|
+
}
|
|
1003
|
+
if (project.lastDeployment) {
|
|
1004
|
+
const date = new Date(project.lastDeployment.createdAt).toLocaleDateString();
|
|
1005
|
+
console.log(chalk11.gray(` Last deploy: ${project.lastDeployment.environment} (${date})`));
|
|
1006
|
+
}
|
|
1007
|
+
console.log("");
|
|
1008
|
+
});
|
|
1009
|
+
const { switchProject } = await inquirer5.prompt([
|
|
1010
|
+
{
|
|
1011
|
+
type: "confirm",
|
|
1012
|
+
name: "switchProject",
|
|
1013
|
+
message: "Switch to a different project?",
|
|
1014
|
+
default: false
|
|
1015
|
+
}
|
|
1016
|
+
]);
|
|
1017
|
+
if (switchProject) {
|
|
1018
|
+
const { selectedProject } = await inquirer5.prompt([
|
|
1019
|
+
{
|
|
1020
|
+
type: "list",
|
|
1021
|
+
name: "selectedProject",
|
|
1022
|
+
message: "Select a project:",
|
|
1023
|
+
choices: projects.map((p) => ({
|
|
1024
|
+
name: `${p.name}${p.id === currentProjectId ? " (current)" : ""}`,
|
|
1025
|
+
value: p.id
|
|
1026
|
+
}))
|
|
1027
|
+
}
|
|
1028
|
+
]);
|
|
1029
|
+
setConfig("projectId", selectedProject);
|
|
1030
|
+
const selectedName = projects.find((p) => p.id === selectedProject)?.name;
|
|
1031
|
+
console.log(chalk11.green(`
|
|
1032
|
+
\u2713 Switched to project: ${selectedName}
|
|
1033
|
+
`));
|
|
1034
|
+
}
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
spinner.fail(chalk11.red("Failed to fetch projects"));
|
|
1037
|
+
console.log(chalk11.red(`
|
|
1038
|
+
${error.message}
|
|
1039
|
+
`));
|
|
1040
|
+
process.exit(1);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// src/commands/whoami.ts
|
|
1045
|
+
import chalk12 from "chalk";
|
|
1046
|
+
import ora9 from "ora";
|
|
1047
|
+
async function whoamiCommand() {
|
|
1048
|
+
const apiKey = getApiKey();
|
|
1049
|
+
if (!apiKey) {
|
|
1050
|
+
console.log(chalk12.red("\n Not authenticated. Run `riventa login` first.\n"));
|
|
1051
|
+
process.exit(1);
|
|
1052
|
+
}
|
|
1053
|
+
const spinner = ora9("Fetching account info...").start();
|
|
1054
|
+
try {
|
|
1055
|
+
const result = await apiRequest("/v1/auth/verify");
|
|
1056
|
+
const data = result.data || result;
|
|
1057
|
+
const user = data.user || data;
|
|
1058
|
+
spinner.stop();
|
|
1059
|
+
console.log();
|
|
1060
|
+
console.log(chalk12.bold(" Account"));
|
|
1061
|
+
console.log(chalk12.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1062
|
+
console.log(chalk12.gray(" Email: ") + chalk12.white(user.email || "unknown"));
|
|
1063
|
+
if (user.name) console.log(chalk12.gray(" Name: ") + chalk12.white(user.name));
|
|
1064
|
+
if (user.plan) console.log(chalk12.gray(" Plan: ") + chalk12.cyan(user.plan));
|
|
1065
|
+
console.log(chalk12.gray(" API Key: ") + chalk12.white(apiKey.slice(0, 12) + "..."));
|
|
1066
|
+
console.log();
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
spinner.fail(chalk12.red("Failed to fetch account info"));
|
|
1069
|
+
console.log(chalk12.red(` ${error.message}
|
|
1070
|
+
`));
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// src/commands/security.ts
|
|
1075
|
+
import chalk13 from "chalk";
|
|
1076
|
+
import ora10 from "ora";
|
|
1077
|
+
async function securityCommand(options) {
|
|
1078
|
+
const projectId = requireProject();
|
|
1079
|
+
if (options.scan) {
|
|
1080
|
+
await runScan(projectId);
|
|
1081
|
+
} else if (options.score) {
|
|
1082
|
+
await showScore(projectId);
|
|
1083
|
+
} else {
|
|
1084
|
+
await showScore(projectId);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
async function runScan(projectId) {
|
|
1088
|
+
const spinner = ora10("Running security scan...").start();
|
|
1089
|
+
try {
|
|
1090
|
+
const result = await apiRequest("/v1/security/scan", {
|
|
1091
|
+
method: "POST",
|
|
1092
|
+
body: JSON.stringify({
|
|
1093
|
+
projectId,
|
|
1094
|
+
scanType: "comprehensive"
|
|
1095
|
+
})
|
|
1096
|
+
});
|
|
1097
|
+
const scan = result.data || result;
|
|
1098
|
+
spinner.succeed("Security scan complete");
|
|
1099
|
+
console.log();
|
|
1100
|
+
if (scan.score !== void 0) {
|
|
1101
|
+
const scoreColor = scan.score >= 80 ? chalk13.green : scan.score >= 60 ? chalk13.yellow : chalk13.red;
|
|
1102
|
+
console.log(chalk13.gray(" Score: ") + scoreColor(scan.score + "/100"));
|
|
1103
|
+
}
|
|
1104
|
+
const vulns = scan.vulnerabilities || [];
|
|
1105
|
+
if (vulns.length === 0) {
|
|
1106
|
+
console.log(chalk13.green("\n No vulnerabilities found!\n"));
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
console.log(chalk13.gray(`
|
|
1110
|
+
Found ${vulns.length} vulnerabilities:
|
|
1111
|
+
`));
|
|
1112
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
1113
|
+
const sorted = [...vulns].sort(
|
|
1114
|
+
(a, b) => (severityOrder[a.severity] || 4) - (severityOrder[b.severity] || 4)
|
|
1115
|
+
);
|
|
1116
|
+
for (const vuln of sorted) {
|
|
1117
|
+
const icon = getSeverityIcon(vuln.severity);
|
|
1118
|
+
console.log(` ${icon} ${chalk13.bold(vuln.title)}`);
|
|
1119
|
+
if (vuln.description) console.log(chalk13.gray(` ${vuln.description}`));
|
|
1120
|
+
if (vuln.package) console.log(chalk13.gray(` Package: ${vuln.package}`));
|
|
1121
|
+
if (vuln.fixAvailable) console.log(chalk13.green(" Fix available"));
|
|
1122
|
+
console.log();
|
|
1123
|
+
}
|
|
1124
|
+
} catch (error) {
|
|
1125
|
+
spinner.fail(chalk13.red("Security scan failed"));
|
|
1126
|
+
console.log(chalk13.red(` ${error.message}
|
|
1127
|
+
`));
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
async function showScore(projectId) {
|
|
1131
|
+
const spinner = ora10("Fetching security score...").start();
|
|
1132
|
+
try {
|
|
1133
|
+
const result = await apiRequest(`/v1/security/score?projectId=${projectId}`);
|
|
1134
|
+
const score = result.data || result;
|
|
1135
|
+
spinner.stop();
|
|
1136
|
+
console.log();
|
|
1137
|
+
const overall = score.overall ?? score.score ?? 0;
|
|
1138
|
+
const color = overall >= 80 ? chalk13.green : overall >= 60 ? chalk13.yellow : chalk13.red;
|
|
1139
|
+
console.log(chalk13.bold(" Security Score"));
|
|
1140
|
+
console.log(chalk13.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1141
|
+
console.log(chalk13.gray(" Overall: ") + color(`${overall}/100`));
|
|
1142
|
+
if (score.categories) {
|
|
1143
|
+
console.log();
|
|
1144
|
+
for (const [category, value] of Object.entries(score.categories)) {
|
|
1145
|
+
const catColor = value >= 80 ? chalk13.green : value >= 60 ? chalk13.yellow : chalk13.red;
|
|
1146
|
+
console.log(chalk13.gray(` ${category}: `) + catColor(`${value}`));
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
console.log();
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
spinner.fail(chalk13.red("Failed to fetch security score"));
|
|
1152
|
+
console.log(chalk13.red(` ${error.message}
|
|
1153
|
+
`));
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
function getSeverityIcon(severity) {
|
|
1157
|
+
switch (severity) {
|
|
1158
|
+
case "critical":
|
|
1159
|
+
return chalk13.red.bold("CRIT");
|
|
1160
|
+
case "high":
|
|
1161
|
+
return chalk13.red("HIGH");
|
|
1162
|
+
case "medium":
|
|
1163
|
+
return chalk13.yellow("MED ");
|
|
1164
|
+
case "low":
|
|
1165
|
+
return chalk13.gray("LOW ");
|
|
1166
|
+
default:
|
|
1167
|
+
return chalk13.gray("INFO");
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// src/commands/health.ts
|
|
1172
|
+
import chalk14 from "chalk";
|
|
1173
|
+
import ora11 from "ora";
|
|
1174
|
+
async function healthCommand() {
|
|
1175
|
+
const spinner = ora11("Checking API status...").start();
|
|
1176
|
+
try {
|
|
1177
|
+
const baseUrl = getBaseUrl();
|
|
1178
|
+
const response = await fetch(`${baseUrl}/v1/health`);
|
|
1179
|
+
if (!response.ok) {
|
|
1180
|
+
throw new Error(`HTTP ${response.status}`);
|
|
1181
|
+
}
|
|
1182
|
+
const result = await response.json();
|
|
1183
|
+
const data = result.data || result;
|
|
1184
|
+
spinner.succeed(chalk14.green("API is operational"));
|
|
1185
|
+
console.log();
|
|
1186
|
+
console.log(chalk14.gray(" Status: ") + chalk14.green(data.status || "operational"));
|
|
1187
|
+
console.log(chalk14.gray(" Version: ") + chalk14.white(data.version || "unknown"));
|
|
1188
|
+
console.log(chalk14.gray(" Environment: ") + chalk14.white(data.environment || "unknown"));
|
|
1189
|
+
console.log(chalk14.gray(" Endpoint: ") + chalk14.white(baseUrl));
|
|
1190
|
+
console.log();
|
|
1191
|
+
} catch (error) {
|
|
1192
|
+
spinner.fail(chalk14.red("API is unreachable"));
|
|
1193
|
+
console.log(chalk14.red(` ${error.message}`));
|
|
1194
|
+
console.log(chalk14.gray(` Endpoint: ${getBaseUrl()}
|
|
1195
|
+
`));
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// src/commands/dashboard.ts
|
|
1200
|
+
import chalk15 from "chalk";
|
|
1201
|
+
var BASE = "https://app.riventa.dev";
|
|
1202
|
+
var pages = {
|
|
1203
|
+
home: "/dashboard",
|
|
1204
|
+
reviews: "/ci-cd?tab=reviews",
|
|
1205
|
+
deployments: "/deployments",
|
|
1206
|
+
analytics: "/analytics",
|
|
1207
|
+
security: "/code-quality?tab=security",
|
|
1208
|
+
settings: "/settings",
|
|
1209
|
+
"api-keys": "/settings?tab=api-keys",
|
|
1210
|
+
flags: "/flags",
|
|
1211
|
+
monitoring: "/monitoring"
|
|
1212
|
+
};
|
|
1213
|
+
async function dashboardCommand(options) {
|
|
1214
|
+
const page = options.page || "home";
|
|
1215
|
+
const path2 = pages[page];
|
|
1216
|
+
if (!path2) {
|
|
1217
|
+
console.log(chalk15.red(`
|
|
1218
|
+
Unknown page: ${page}`));
|
|
1219
|
+
console.log(chalk15.gray(" Available pages: ") + chalk15.white(Object.keys(pages).join(", ")));
|
|
1220
|
+
console.log();
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
const url = `${BASE}${path2}`;
|
|
1224
|
+
console.log(chalk15.gray(`
|
|
1225
|
+
Opening ${url}
|
|
1226
|
+
`));
|
|
1227
|
+
try {
|
|
1228
|
+
await openUrl(url);
|
|
1229
|
+
} catch {
|
|
1230
|
+
console.log(chalk15.yellow(` Could not open browser. Visit: ${chalk15.white(url)}
|
|
1231
|
+
`));
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// src/repl.ts
|
|
1236
|
+
var VERSION = "1.2.0";
|
|
1237
|
+
var commands = {
|
|
1238
|
+
login: { description: "Sign in via browser", action: loginCommand },
|
|
1239
|
+
logout: { description: "Log out and clear credentials", action: async () => {
|
|
1240
|
+
const Conf3 = (await import("conf")).default;
|
|
1241
|
+
const config3 = new Conf3({ projectName: "riventa-cli" });
|
|
1242
|
+
config3.clear();
|
|
1243
|
+
console.log(chalk16.green("\n Logged out successfully. Credentials cleared.\n"));
|
|
1244
|
+
} },
|
|
1245
|
+
whoami: { description: "Show current authenticated user", action: whoamiCommand },
|
|
1246
|
+
init: { description: "Initialize Riventa in current directory", action: initCommand },
|
|
1247
|
+
projects: { description: "List and switch between projects", action: projectsCommand },
|
|
1248
|
+
deploy: { description: "Deploy current branch", action: deployCommand },
|
|
1249
|
+
status: { description: "View project and deployment status", action: statusCommand },
|
|
1250
|
+
logs: { description: "View deployment logs", action: logsCommand },
|
|
1251
|
+
rollback: { description: "Rollback to a previous deployment", action: rollbackCommand },
|
|
1252
|
+
review: { description: "Run AI code review on changes", action: reviewCommand },
|
|
1253
|
+
security: { description: "Security scanning and score", action: securityCommand },
|
|
1254
|
+
config: { description: "Manage CLI configuration", action: configCommand },
|
|
1255
|
+
health: { description: "Check API connectivity and status", action: healthCommand },
|
|
1256
|
+
dashboard: { description: "Open dashboard in browser", action: dashboardCommand },
|
|
1257
|
+
open: { description: "Open dashboard in browser", action: () => dashboardCommand({}) }
|
|
1258
|
+
};
|
|
1259
|
+
function showHelp() {
|
|
1260
|
+
console.log();
|
|
1261
|
+
console.log(chalk16.bold(" Available commands:"));
|
|
1262
|
+
console.log();
|
|
1263
|
+
const maxLen = Math.max(...Object.keys(commands).map((k) => k.length));
|
|
1264
|
+
for (const [name, cmd] of Object.entries(commands)) {
|
|
1265
|
+
console.log(chalk16.cyan(` ${name.padEnd(maxLen + 2)}`) + chalk16.gray(cmd.description));
|
|
1266
|
+
}
|
|
1267
|
+
console.log();
|
|
1268
|
+
console.log(chalk16.gray(" help") + chalk16.gray("".padEnd(maxLen - 2)) + chalk16.gray("Show this help"));
|
|
1269
|
+
console.log(chalk16.gray(" clear") + chalk16.gray("".padEnd(maxLen - 3)) + chalk16.gray("Clear the screen"));
|
|
1270
|
+
console.log(chalk16.gray(" exit") + chalk16.gray("".padEnd(maxLen - 2)) + chalk16.gray("Exit the CLI"));
|
|
1271
|
+
console.log();
|
|
1272
|
+
}
|
|
1273
|
+
async function startRepl() {
|
|
1274
|
+
const user = getConfig("user");
|
|
1275
|
+
printBanner(VERSION, user?.email);
|
|
1276
|
+
const cwd = process.cwd().split("/").pop() || "~";
|
|
1277
|
+
const rl = createInterface({
|
|
1278
|
+
input: process.stdin,
|
|
1279
|
+
output: process.stdout,
|
|
1280
|
+
prompt: chalk16.hex("#2563EB")("riventa") + chalk16.gray(`:${cwd}`) + chalk16.white("> "),
|
|
1281
|
+
completer: (line) => {
|
|
1282
|
+
const completions = [...Object.keys(commands), "help", "clear", "exit"];
|
|
1283
|
+
const hits = completions.filter((c) => c.startsWith(line.trim()));
|
|
1284
|
+
return [hits.length ? hits : completions, line];
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
rl.prompt();
|
|
1288
|
+
rl.on("line", async (line) => {
|
|
1289
|
+
const input = line.trim();
|
|
1290
|
+
const [cmd, ...args] = input.split(/\s+/);
|
|
1291
|
+
if (!cmd) {
|
|
1292
|
+
rl.prompt();
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
if (cmd === "exit" || cmd === "quit") {
|
|
1296
|
+
console.log(chalk16.gray("\n Goodbye!\n"));
|
|
1297
|
+
process.exit(0);
|
|
1298
|
+
}
|
|
1299
|
+
if (cmd === "help") {
|
|
1300
|
+
showHelp();
|
|
1301
|
+
rl.prompt();
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
if (cmd === "clear") {
|
|
1305
|
+
console.clear();
|
|
1306
|
+
const u = getConfig("user");
|
|
1307
|
+
printBanner(VERSION, u?.email);
|
|
1308
|
+
rl.prompt();
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
if (cmd === "version") {
|
|
1312
|
+
console.log(chalk16.gray(`
|
|
1313
|
+
v${VERSION}
|
|
1314
|
+
`));
|
|
1315
|
+
rl.prompt();
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
const command = commands[cmd];
|
|
1319
|
+
if (!command) {
|
|
1320
|
+
console.log(chalk16.red(`
|
|
1321
|
+
Unknown command: ${cmd}`));
|
|
1322
|
+
console.log(chalk16.gray(" Type ") + chalk16.cyan("help") + chalk16.gray(" to see available commands.\n"));
|
|
1323
|
+
rl.prompt();
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
try {
|
|
1327
|
+
const originalExit = process.exit;
|
|
1328
|
+
process.exit = (() => {
|
|
1329
|
+
throw new Error("__REPL_EXIT__");
|
|
1330
|
+
});
|
|
1331
|
+
await command.action({ ...Object.fromEntries(args.map((a, i) => [i, a])) });
|
|
1332
|
+
process.exit = originalExit;
|
|
1333
|
+
} catch (err) {
|
|
1334
|
+
if (err?.message !== "__REPL_EXIT__") {
|
|
1335
|
+
console.log(chalk16.red(`
|
|
1336
|
+
Error: ${err.message}
|
|
1337
|
+
`));
|
|
1338
|
+
}
|
|
1339
|
+
process.exit = (await import("process")).exit;
|
|
1340
|
+
}
|
|
1341
|
+
console.log();
|
|
1342
|
+
rl.prompt();
|
|
1343
|
+
});
|
|
1344
|
+
rl.on("close", () => {
|
|
1345
|
+
console.log(chalk16.gray("\n Goodbye!\n"));
|
|
1346
|
+
process.exit(0);
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
export {
|
|
1350
|
+
startRepl
|
|
1351
|
+
};
|