@cencori/scan 0.3.7 → 0.4.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/cli.js +262 -2
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +262 -2
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -903,7 +903,7 @@ async function applyFixes(fixes, fileContents) {
|
|
|
903
903
|
}
|
|
904
904
|
|
|
905
905
|
// src/telemetry.ts
|
|
906
|
-
var TELEMETRY_URL = "https://cencori.com/
|
|
906
|
+
var TELEMETRY_URL = "https://api.cencori.com/v1/telemetry/scan";
|
|
907
907
|
var pendingTelemetry = null;
|
|
908
908
|
function sendTelemetry(data) {
|
|
909
909
|
pendingTelemetry = fetch(TELEMETRY_URL, {
|
|
@@ -950,10 +950,218 @@ function buildTelemetryData(result, version, hasApiKey) {
|
|
|
950
950
|
};
|
|
951
951
|
}
|
|
952
952
|
|
|
953
|
+
// src/changelog/index.ts
|
|
954
|
+
import { execSync } from "child_process";
|
|
955
|
+
var CENCORI_API_URL2 = "https://api.cencori.com/v1";
|
|
956
|
+
function parseCommitType(message) {
|
|
957
|
+
const lower = message.toLowerCase();
|
|
958
|
+
if (lower.startsWith("feat") || lower.startsWith("feature")) return "feat";
|
|
959
|
+
if (lower.startsWith("fix") || lower.startsWith("bugfix")) return "fix";
|
|
960
|
+
if (lower.startsWith("chore")) return "chore";
|
|
961
|
+
if (lower.startsWith("docs")) return "docs";
|
|
962
|
+
if (lower.startsWith("style")) return "style";
|
|
963
|
+
if (lower.startsWith("refactor")) return "refactor";
|
|
964
|
+
if (lower.startsWith("test")) return "test";
|
|
965
|
+
return "other";
|
|
966
|
+
}
|
|
967
|
+
function getCommits(since = "1 week ago", cwd) {
|
|
968
|
+
try {
|
|
969
|
+
const output = execSync(
|
|
970
|
+
`git log --since="${since}" --pretty=format:"%H|%aI|%an|%s" --no-merges`,
|
|
971
|
+
{
|
|
972
|
+
cwd: cwd || process.cwd(),
|
|
973
|
+
encoding: "utf-8",
|
|
974
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
975
|
+
}
|
|
976
|
+
);
|
|
977
|
+
if (!output.trim()) {
|
|
978
|
+
return [];
|
|
979
|
+
}
|
|
980
|
+
return output.trim().split("\n").map((line) => {
|
|
981
|
+
const [hash, date, author, ...messageParts] = line.split("|");
|
|
982
|
+
const message = messageParts.join("|");
|
|
983
|
+
return {
|
|
984
|
+
hash: hash.substring(0, 7),
|
|
985
|
+
date,
|
|
986
|
+
author,
|
|
987
|
+
message,
|
|
988
|
+
type: parseCommitType(message)
|
|
989
|
+
};
|
|
990
|
+
});
|
|
991
|
+
} catch (error) {
|
|
992
|
+
return [];
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
function groupCommitsByType(commits) {
|
|
996
|
+
const groups = {
|
|
997
|
+
feat: [],
|
|
998
|
+
fix: [],
|
|
999
|
+
docs: [],
|
|
1000
|
+
other: []
|
|
1001
|
+
};
|
|
1002
|
+
for (const commit of commits) {
|
|
1003
|
+
if (commit.type === "feat") {
|
|
1004
|
+
groups.feat.push(commit);
|
|
1005
|
+
} else if (commit.type === "fix") {
|
|
1006
|
+
groups.fix.push(commit);
|
|
1007
|
+
} else if (commit.type === "docs") {
|
|
1008
|
+
groups.docs.push(commit);
|
|
1009
|
+
} else {
|
|
1010
|
+
groups.other.push(commit);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
const entries = [];
|
|
1014
|
+
if (groups.feat.length > 0) {
|
|
1015
|
+
entries.push({
|
|
1016
|
+
type: "feature",
|
|
1017
|
+
title: "Features",
|
|
1018
|
+
commits: groups.feat.map((c) => c.message)
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
if (groups.fix.length > 0) {
|
|
1022
|
+
entries.push({
|
|
1023
|
+
type: "fix",
|
|
1024
|
+
title: "Bug Fixes",
|
|
1025
|
+
commits: groups.fix.map((c) => c.message)
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
if (groups.docs.length > 0) {
|
|
1029
|
+
entries.push({
|
|
1030
|
+
type: "docs",
|
|
1031
|
+
title: "Documentation",
|
|
1032
|
+
commits: groups.docs.map((c) => c.message)
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
if (groups.other.length > 0) {
|
|
1036
|
+
entries.push({
|
|
1037
|
+
type: "other",
|
|
1038
|
+
title: "Other Changes",
|
|
1039
|
+
commits: groups.other.map((c) => c.message)
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
return entries;
|
|
1043
|
+
}
|
|
1044
|
+
function generateMarkdownFree(entries, period) {
|
|
1045
|
+
const startDate = new Date(period.start).toLocaleDateString("en-US", {
|
|
1046
|
+
month: "short",
|
|
1047
|
+
day: "numeric",
|
|
1048
|
+
year: "numeric"
|
|
1049
|
+
});
|
|
1050
|
+
const endDate = new Date(period.end).toLocaleDateString("en-US", {
|
|
1051
|
+
month: "short",
|
|
1052
|
+
day: "numeric",
|
|
1053
|
+
year: "numeric"
|
|
1054
|
+
});
|
|
1055
|
+
let md = `## Changelog (${startDate} - ${endDate})
|
|
1056
|
+
|
|
1057
|
+
`;
|
|
1058
|
+
const typeEmojis = {
|
|
1059
|
+
feature: "\u2728",
|
|
1060
|
+
fix: "\u{1F41B}",
|
|
1061
|
+
docs: "\u{1F4DA}",
|
|
1062
|
+
improvement: "\u{1F527}",
|
|
1063
|
+
other: "\u{1F4E6}"
|
|
1064
|
+
};
|
|
1065
|
+
for (const entry of entries) {
|
|
1066
|
+
const emoji = typeEmojis[entry.type] || "\u{1F4E6}";
|
|
1067
|
+
md += `### ${emoji} ${entry.title}
|
|
1068
|
+
|
|
1069
|
+
`;
|
|
1070
|
+
for (const commit of entry.commits) {
|
|
1071
|
+
const cleanMessage = commit.replace(/^(feat|fix|chore|docs|style|refactor|test)(\(.+?\))?:\s*/i, "").trim();
|
|
1072
|
+
md += `- ${cleanMessage}
|
|
1073
|
+
`;
|
|
1074
|
+
}
|
|
1075
|
+
md += "\n";
|
|
1076
|
+
}
|
|
1077
|
+
return md;
|
|
1078
|
+
}
|
|
1079
|
+
async function generateChangelogAI(commits, apiKey, period) {
|
|
1080
|
+
const commitMessages = commits.map((c) => c.message).join("\n");
|
|
1081
|
+
try {
|
|
1082
|
+
const response = await fetch(`${CENCORI_API_URL2}/changelog/generate`, {
|
|
1083
|
+
method: "POST",
|
|
1084
|
+
headers: {
|
|
1085
|
+
"Content-Type": "application/json",
|
|
1086
|
+
Authorization: `Bearer ${apiKey}`
|
|
1087
|
+
},
|
|
1088
|
+
body: JSON.stringify({
|
|
1089
|
+
commits: commitMessages,
|
|
1090
|
+
period
|
|
1091
|
+
})
|
|
1092
|
+
});
|
|
1093
|
+
if (!response.ok) {
|
|
1094
|
+
throw new Error("API request failed");
|
|
1095
|
+
}
|
|
1096
|
+
const data = await response.json();
|
|
1097
|
+
return {
|
|
1098
|
+
entries: data.entries || [],
|
|
1099
|
+
markdown: data.markdown || ""
|
|
1100
|
+
};
|
|
1101
|
+
} catch {
|
|
1102
|
+
const entries = groupCommitsByType(commits);
|
|
1103
|
+
return {
|
|
1104
|
+
entries,
|
|
1105
|
+
markdown: generateMarkdownFree(entries, period)
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
async function generateChangelog(since = "1 week ago", apiKey, cwd) {
|
|
1110
|
+
const commits = getCommits(since, cwd);
|
|
1111
|
+
const now = /* @__PURE__ */ new Date();
|
|
1112
|
+
const sinceDate = /* @__PURE__ */ new Date();
|
|
1113
|
+
const match = since.match(/(\d+)\s*(week|day|month)s?\s*ago/i);
|
|
1114
|
+
if (match) {
|
|
1115
|
+
const amount = parseInt(match[1], 10);
|
|
1116
|
+
const unit = match[2].toLowerCase();
|
|
1117
|
+
if (unit === "week") {
|
|
1118
|
+
sinceDate.setDate(sinceDate.getDate() - amount * 7);
|
|
1119
|
+
} else if (unit === "day") {
|
|
1120
|
+
sinceDate.setDate(sinceDate.getDate() - amount);
|
|
1121
|
+
} else if (unit === "month") {
|
|
1122
|
+
sinceDate.setMonth(sinceDate.getMonth() - amount);
|
|
1123
|
+
}
|
|
1124
|
+
} else {
|
|
1125
|
+
sinceDate.setDate(sinceDate.getDate() - 7);
|
|
1126
|
+
}
|
|
1127
|
+
const period = {
|
|
1128
|
+
start: sinceDate.toISOString(),
|
|
1129
|
+
end: now.toISOString()
|
|
1130
|
+
};
|
|
1131
|
+
if (commits.length === 0) {
|
|
1132
|
+
return {
|
|
1133
|
+
period,
|
|
1134
|
+
entries: [],
|
|
1135
|
+
markdown: `## Changelog
|
|
1136
|
+
|
|
1137
|
+
No commits found in the specified period.
|
|
1138
|
+
`,
|
|
1139
|
+
commitCount: 0
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
if (apiKey) {
|
|
1143
|
+
const { entries, markdown } = await generateChangelogAI(commits, apiKey, period);
|
|
1144
|
+
return {
|
|
1145
|
+
period,
|
|
1146
|
+
entries,
|
|
1147
|
+
markdown,
|
|
1148
|
+
commitCount: commits.length
|
|
1149
|
+
};
|
|
1150
|
+
} else {
|
|
1151
|
+
const entries = groupCommitsByType(commits);
|
|
1152
|
+
return {
|
|
1153
|
+
period,
|
|
1154
|
+
entries,
|
|
1155
|
+
markdown: generateMarkdownFree(entries, period),
|
|
1156
|
+
commitCount: commits.length
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
953
1161
|
// src/cli.ts
|
|
954
1162
|
import * as fs3 from "fs";
|
|
955
1163
|
import * as path3 from "path";
|
|
956
|
-
var VERSION = "0.
|
|
1164
|
+
var VERSION = "0.4.0";
|
|
957
1165
|
var scoreStyles = {
|
|
958
1166
|
A: { color: chalk.green },
|
|
959
1167
|
B: { color: chalk.blue },
|
|
@@ -1259,6 +1467,58 @@ async function main() {
|
|
|
1259
1467
|
process.exit(1);
|
|
1260
1468
|
}
|
|
1261
1469
|
});
|
|
1470
|
+
program.command("changelog").description("Generate AI-powered changelog from git commits").option("-s, --since <time>", "Time range for commits", "1 week ago").option("-o, --output <file>", "Output to file instead of stdout").option("-f, --format <format>", "Output format (markdown or json)", "markdown").action(async (options) => {
|
|
1471
|
+
printBanner();
|
|
1472
|
+
const spinner = ora({
|
|
1473
|
+
text: "Reading git history...",
|
|
1474
|
+
color: "cyan"
|
|
1475
|
+
}).start();
|
|
1476
|
+
try {
|
|
1477
|
+
const apiKey = getApiKey();
|
|
1478
|
+
const result = await generateChangelog(options.since, apiKey || void 0);
|
|
1479
|
+
if (result.commitCount === 0) {
|
|
1480
|
+
spinner.warn("No commits found in the specified period");
|
|
1481
|
+
console.log(chalk.yellow(`
|
|
1482
|
+
Try a larger time range with --since "2 weeks ago"
|
|
1483
|
+
`));
|
|
1484
|
+
process.exit(0);
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
spinner.succeed(`Found ${result.commitCount} commits`);
|
|
1488
|
+
if (options.format === "json") {
|
|
1489
|
+
const output = JSON.stringify(result, null, 2);
|
|
1490
|
+
if (options.output) {
|
|
1491
|
+
fs3.writeFileSync(options.output, output);
|
|
1492
|
+
console.log(chalk.green(`
|
|
1493
|
+
\u2714 Changelog saved to ${options.output}
|
|
1494
|
+
`));
|
|
1495
|
+
} else {
|
|
1496
|
+
console.log(output);
|
|
1497
|
+
}
|
|
1498
|
+
} else {
|
|
1499
|
+
if (options.output) {
|
|
1500
|
+
fs3.writeFileSync(options.output, result.markdown);
|
|
1501
|
+
console.log(chalk.green(`
|
|
1502
|
+
\u2714 Changelog saved to ${options.output}
|
|
1503
|
+
`));
|
|
1504
|
+
} else {
|
|
1505
|
+
console.log("\n" + result.markdown);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
if (!apiKey) {
|
|
1509
|
+
console.log(chalk.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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1510
|
+
console.log(chalk.cyan("\n \u{1F4A1} Want AI-enhanced changelogs?"));
|
|
1511
|
+
console.log(chalk.gray(" Get human-readable summaries with a Cencori API key"));
|
|
1512
|
+
console.log(chalk.gray(" Sign up free at https://cencori.com\n"));
|
|
1513
|
+
}
|
|
1514
|
+
process.exit(0);
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
spinner.fail("Changelog generation failed");
|
|
1517
|
+
console.error(chalk.red(`
|
|
1518
|
+
Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
1519
|
+
process.exit(1);
|
|
1520
|
+
}
|
|
1521
|
+
});
|
|
1262
1522
|
program.parse();
|
|
1263
1523
|
}
|
|
1264
1524
|
main();
|