@tryghost/velo-cli 0.1.1 → 0.1.4
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 +61 -18
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,6 +8,9 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
import { dirname, join as join2 } from "path";
|
|
11
14
|
|
|
12
15
|
// src/commands/login.ts
|
|
13
16
|
import { createServer } from "http";
|
|
@@ -101,12 +104,26 @@ async function apiRequest(endpoint, options = {}) {
|
|
|
101
104
|
}
|
|
102
105
|
async function verifyToken(token) {
|
|
103
106
|
const url = `${API_BASE_URL}/cli/verify`;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
try {
|
|
108
|
+
const response = await fetch(url, {
|
|
109
|
+
headers: {
|
|
110
|
+
"Authorization": `Bearer ${token}`
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const contentType = response.headers.get("content-type") || "";
|
|
114
|
+
if (contentType.includes("text/html")) {
|
|
115
|
+
return {
|
|
116
|
+
valid: false,
|
|
117
|
+
error: "Authentication required (got HTML instead of JSON - endpoint may be behind Cloudflare Access)"
|
|
118
|
+
};
|
|
107
119
|
}
|
|
108
|
-
|
|
109
|
-
|
|
120
|
+
return await response.json();
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return {
|
|
123
|
+
valid: false,
|
|
124
|
+
error: error.message
|
|
125
|
+
};
|
|
126
|
+
}
|
|
110
127
|
}
|
|
111
128
|
|
|
112
129
|
// src/commands/login.ts
|
|
@@ -275,11 +292,6 @@ async function browserLogin() {
|
|
|
275
292
|
try {
|
|
276
293
|
const credentials = await credentialsPromise;
|
|
277
294
|
spinner.stop();
|
|
278
|
-
const verification = await verifyToken(credentials.token);
|
|
279
|
-
if (!verification.valid) {
|
|
280
|
-
console.log(chalk.red("Token verification failed. Please try again."));
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
295
|
saveCredentials(credentials);
|
|
284
296
|
console.log(chalk.green(`
|
|
285
297
|
\u2713 Logged in as ${chalk.bold(credentials.email)}`));
|
|
@@ -340,17 +352,46 @@ async function status(options) {
|
|
|
340
352
|
endpoint += `&repo=${encodeURIComponent(options.repo)}`;
|
|
341
353
|
}
|
|
342
354
|
const metrics = await apiRequest(endpoint);
|
|
355
|
+
const repoStats = {};
|
|
356
|
+
let totalRuns = 0;
|
|
357
|
+
let totalSuccesses = 0;
|
|
358
|
+
let totalDurationMs = 0;
|
|
359
|
+
let durationCount = 0;
|
|
360
|
+
for (const item of metrics.summary) {
|
|
361
|
+
const count = parseInt(item.count, 10);
|
|
362
|
+
totalRuns += count;
|
|
363
|
+
if (!repoStats[item.repo]) {
|
|
364
|
+
repoStats[item.repo] = { total: 0, successes: 0, failures: 0, totalDurationMs: 0, successCount: 0 };
|
|
365
|
+
}
|
|
366
|
+
repoStats[item.repo].total += count;
|
|
367
|
+
if (item.status === "success") {
|
|
368
|
+
totalSuccesses += count;
|
|
369
|
+
repoStats[item.repo].successes += count;
|
|
370
|
+
repoStats[item.repo].totalDurationMs += item.avg_duration_ms * count;
|
|
371
|
+
repoStats[item.repo].successCount += count;
|
|
372
|
+
totalDurationMs += item.avg_duration_ms * count;
|
|
373
|
+
durationCount += count;
|
|
374
|
+
} else if (item.status === "failure") {
|
|
375
|
+
repoStats[item.repo].failures += count;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const successRate = totalRuns > 0 ? totalSuccesses / totalRuns * 100 : 0;
|
|
379
|
+
const avgDurationMin = durationCount > 0 ? totalDurationMs / durationCount / 6e4 : 0;
|
|
343
380
|
console.log(chalk3.white.bold("Last " + days + " days:"));
|
|
344
|
-
console.log(` Total runs: ${chalk3.cyan(
|
|
345
|
-
console.log(` Success rate: ${formatRate(
|
|
346
|
-
console.log(` Avg duration: ${chalk3.gray(
|
|
381
|
+
console.log(` Total runs: ${chalk3.cyan(totalRuns.toLocaleString())}`);
|
|
382
|
+
console.log(` Success rate: ${formatRate(successRate)}`);
|
|
383
|
+
console.log(` Avg duration: ${chalk3.gray(avgDurationMin.toFixed(1) + " min")}`);
|
|
347
384
|
console.log();
|
|
348
|
-
const repos = Object.entries(
|
|
385
|
+
const repos = Object.entries(repoStats).map(([repo, data]) => ({
|
|
386
|
+
repo,
|
|
387
|
+
...data,
|
|
388
|
+
successRate: data.total > 0 ? data.successes / data.total * 100 : 0
|
|
389
|
+
})).sort((a, b) => b.total - a.total).slice(0, 5);
|
|
349
390
|
if (repos.length > 0) {
|
|
350
391
|
console.log(chalk3.white.bold("Top repos:"));
|
|
351
|
-
for (const
|
|
352
|
-
const shortName = repo.split("/")[1] || repo;
|
|
353
|
-
const rateStr = formatRate(data.
|
|
392
|
+
for (const data of repos) {
|
|
393
|
+
const shortName = data.repo.split("/")[1] || data.repo;
|
|
394
|
+
const rateStr = formatRate(data.successRate);
|
|
354
395
|
console.log(` ${chalk3.cyan(shortName.padEnd(20))} ${data.total.toString().padStart(5)} runs ${rateStr}`);
|
|
355
396
|
}
|
|
356
397
|
console.log();
|
|
@@ -446,8 +487,10 @@ async function api(endpoint, options) {
|
|
|
446
487
|
}
|
|
447
488
|
|
|
448
489
|
// src/index.ts
|
|
490
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
491
|
+
var pkg = JSON.parse(readFileSync2(join2(__dirname, "..", "package.json"), "utf-8"));
|
|
449
492
|
var program = new Command();
|
|
450
|
-
program.name("velo").description("CLI for Velo CI/CD metrics").version(
|
|
493
|
+
program.name("velo").description("CLI for Velo CI/CD metrics").version(pkg.version);
|
|
451
494
|
program.command("login").description("Authenticate via Ghost SSO").option("--manual", "Use manual token entry (for headless servers)").action((options) => login({ manual: options.manual }));
|
|
452
495
|
program.command("logout").description("Clear saved credentials").action(logout);
|
|
453
496
|
program.command("status").description("Show CI health overview").option("-r, --repo <repo>", "Filter by repository (e.g., TryGhost/Ghost)").option("-d, --days <days>", "Number of days to analyze (default: 7)", "7").action((options) => {
|