@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 CHANGED
@@ -926,7 +926,7 @@ async function applyFixes(fixes, fileContents) {
926
926
  }
927
927
 
928
928
  // src/telemetry.ts
929
- var TELEMETRY_URL = "https://cencori.com/api/v1/telemetry/scan";
929
+ var TELEMETRY_URL = "https://api.cencori.com/v1/telemetry/scan";
930
930
  var pendingTelemetry = null;
931
931
  function sendTelemetry(data) {
932
932
  pendingTelemetry = fetch(TELEMETRY_URL, {
@@ -973,10 +973,218 @@ function buildTelemetryData(result, version, hasApiKey) {
973
973
  };
974
974
  }
975
975
 
976
+ // src/changelog/index.ts
977
+ var import_child_process = require("child_process");
978
+ var CENCORI_API_URL2 = "https://api.cencori.com/v1";
979
+ function parseCommitType(message) {
980
+ const lower = message.toLowerCase();
981
+ if (lower.startsWith("feat") || lower.startsWith("feature")) return "feat";
982
+ if (lower.startsWith("fix") || lower.startsWith("bugfix")) return "fix";
983
+ if (lower.startsWith("chore")) return "chore";
984
+ if (lower.startsWith("docs")) return "docs";
985
+ if (lower.startsWith("style")) return "style";
986
+ if (lower.startsWith("refactor")) return "refactor";
987
+ if (lower.startsWith("test")) return "test";
988
+ return "other";
989
+ }
990
+ function getCommits(since = "1 week ago", cwd) {
991
+ try {
992
+ const output = (0, import_child_process.execSync)(
993
+ `git log --since="${since}" --pretty=format:"%H|%aI|%an|%s" --no-merges`,
994
+ {
995
+ cwd: cwd || process.cwd(),
996
+ encoding: "utf-8",
997
+ stdio: ["pipe", "pipe", "pipe"]
998
+ }
999
+ );
1000
+ if (!output.trim()) {
1001
+ return [];
1002
+ }
1003
+ return output.trim().split("\n").map((line) => {
1004
+ const [hash, date, author, ...messageParts] = line.split("|");
1005
+ const message = messageParts.join("|");
1006
+ return {
1007
+ hash: hash.substring(0, 7),
1008
+ date,
1009
+ author,
1010
+ message,
1011
+ type: parseCommitType(message)
1012
+ };
1013
+ });
1014
+ } catch (error) {
1015
+ return [];
1016
+ }
1017
+ }
1018
+ function groupCommitsByType(commits) {
1019
+ const groups = {
1020
+ feat: [],
1021
+ fix: [],
1022
+ docs: [],
1023
+ other: []
1024
+ };
1025
+ for (const commit of commits) {
1026
+ if (commit.type === "feat") {
1027
+ groups.feat.push(commit);
1028
+ } else if (commit.type === "fix") {
1029
+ groups.fix.push(commit);
1030
+ } else if (commit.type === "docs") {
1031
+ groups.docs.push(commit);
1032
+ } else {
1033
+ groups.other.push(commit);
1034
+ }
1035
+ }
1036
+ const entries = [];
1037
+ if (groups.feat.length > 0) {
1038
+ entries.push({
1039
+ type: "feature",
1040
+ title: "Features",
1041
+ commits: groups.feat.map((c) => c.message)
1042
+ });
1043
+ }
1044
+ if (groups.fix.length > 0) {
1045
+ entries.push({
1046
+ type: "fix",
1047
+ title: "Bug Fixes",
1048
+ commits: groups.fix.map((c) => c.message)
1049
+ });
1050
+ }
1051
+ if (groups.docs.length > 0) {
1052
+ entries.push({
1053
+ type: "docs",
1054
+ title: "Documentation",
1055
+ commits: groups.docs.map((c) => c.message)
1056
+ });
1057
+ }
1058
+ if (groups.other.length > 0) {
1059
+ entries.push({
1060
+ type: "other",
1061
+ title: "Other Changes",
1062
+ commits: groups.other.map((c) => c.message)
1063
+ });
1064
+ }
1065
+ return entries;
1066
+ }
1067
+ function generateMarkdownFree(entries, period) {
1068
+ const startDate = new Date(period.start).toLocaleDateString("en-US", {
1069
+ month: "short",
1070
+ day: "numeric",
1071
+ year: "numeric"
1072
+ });
1073
+ const endDate = new Date(period.end).toLocaleDateString("en-US", {
1074
+ month: "short",
1075
+ day: "numeric",
1076
+ year: "numeric"
1077
+ });
1078
+ let md = `## Changelog (${startDate} - ${endDate})
1079
+
1080
+ `;
1081
+ const typeEmojis = {
1082
+ feature: "\u2728",
1083
+ fix: "\u{1F41B}",
1084
+ docs: "\u{1F4DA}",
1085
+ improvement: "\u{1F527}",
1086
+ other: "\u{1F4E6}"
1087
+ };
1088
+ for (const entry of entries) {
1089
+ const emoji = typeEmojis[entry.type] || "\u{1F4E6}";
1090
+ md += `### ${emoji} ${entry.title}
1091
+
1092
+ `;
1093
+ for (const commit of entry.commits) {
1094
+ const cleanMessage = commit.replace(/^(feat|fix|chore|docs|style|refactor|test)(\(.+?\))?:\s*/i, "").trim();
1095
+ md += `- ${cleanMessage}
1096
+ `;
1097
+ }
1098
+ md += "\n";
1099
+ }
1100
+ return md;
1101
+ }
1102
+ async function generateChangelogAI(commits, apiKey, period) {
1103
+ const commitMessages = commits.map((c) => c.message).join("\n");
1104
+ try {
1105
+ const response = await fetch(`${CENCORI_API_URL2}/changelog/generate`, {
1106
+ method: "POST",
1107
+ headers: {
1108
+ "Content-Type": "application/json",
1109
+ Authorization: `Bearer ${apiKey}`
1110
+ },
1111
+ body: JSON.stringify({
1112
+ commits: commitMessages,
1113
+ period
1114
+ })
1115
+ });
1116
+ if (!response.ok) {
1117
+ throw new Error("API request failed");
1118
+ }
1119
+ const data = await response.json();
1120
+ return {
1121
+ entries: data.entries || [],
1122
+ markdown: data.markdown || ""
1123
+ };
1124
+ } catch {
1125
+ const entries = groupCommitsByType(commits);
1126
+ return {
1127
+ entries,
1128
+ markdown: generateMarkdownFree(entries, period)
1129
+ };
1130
+ }
1131
+ }
1132
+ async function generateChangelog(since = "1 week ago", apiKey, cwd) {
1133
+ const commits = getCommits(since, cwd);
1134
+ const now = /* @__PURE__ */ new Date();
1135
+ const sinceDate = /* @__PURE__ */ new Date();
1136
+ const match = since.match(/(\d+)\s*(week|day|month)s?\s*ago/i);
1137
+ if (match) {
1138
+ const amount = parseInt(match[1], 10);
1139
+ const unit = match[2].toLowerCase();
1140
+ if (unit === "week") {
1141
+ sinceDate.setDate(sinceDate.getDate() - amount * 7);
1142
+ } else if (unit === "day") {
1143
+ sinceDate.setDate(sinceDate.getDate() - amount);
1144
+ } else if (unit === "month") {
1145
+ sinceDate.setMonth(sinceDate.getMonth() - amount);
1146
+ }
1147
+ } else {
1148
+ sinceDate.setDate(sinceDate.getDate() - 7);
1149
+ }
1150
+ const period = {
1151
+ start: sinceDate.toISOString(),
1152
+ end: now.toISOString()
1153
+ };
1154
+ if (commits.length === 0) {
1155
+ return {
1156
+ period,
1157
+ entries: [],
1158
+ markdown: `## Changelog
1159
+
1160
+ No commits found in the specified period.
1161
+ `,
1162
+ commitCount: 0
1163
+ };
1164
+ }
1165
+ if (apiKey) {
1166
+ const { entries, markdown } = await generateChangelogAI(commits, apiKey, period);
1167
+ return {
1168
+ period,
1169
+ entries,
1170
+ markdown,
1171
+ commitCount: commits.length
1172
+ };
1173
+ } else {
1174
+ const entries = groupCommitsByType(commits);
1175
+ return {
1176
+ period,
1177
+ entries,
1178
+ markdown: generateMarkdownFree(entries, period),
1179
+ commitCount: commits.length
1180
+ };
1181
+ }
1182
+ }
1183
+
976
1184
  // src/cli.ts
977
1185
  var fs3 = __toESM(require("fs"));
978
1186
  var path3 = __toESM(require("path"));
979
- var VERSION = "0.3.7";
1187
+ var VERSION = "0.4.0";
980
1188
  var scoreStyles = {
981
1189
  A: { color: import_chalk.default.green },
982
1190
  B: { color: import_chalk.default.blue },
@@ -1282,6 +1490,58 @@ async function main() {
1282
1490
  process.exit(1);
1283
1491
  }
1284
1492
  });
1493
+ import_commander.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) => {
1494
+ printBanner();
1495
+ const spinner = (0, import_ora.default)({
1496
+ text: "Reading git history...",
1497
+ color: "cyan"
1498
+ }).start();
1499
+ try {
1500
+ const apiKey = getApiKey();
1501
+ const result = await generateChangelog(options.since, apiKey || void 0);
1502
+ if (result.commitCount === 0) {
1503
+ spinner.warn("No commits found in the specified period");
1504
+ console.log(import_chalk.default.yellow(`
1505
+ Try a larger time range with --since "2 weeks ago"
1506
+ `));
1507
+ process.exit(0);
1508
+ return;
1509
+ }
1510
+ spinner.succeed(`Found ${result.commitCount} commits`);
1511
+ if (options.format === "json") {
1512
+ const output = JSON.stringify(result, null, 2);
1513
+ if (options.output) {
1514
+ fs3.writeFileSync(options.output, output);
1515
+ console.log(import_chalk.default.green(`
1516
+ \u2714 Changelog saved to ${options.output}
1517
+ `));
1518
+ } else {
1519
+ console.log(output);
1520
+ }
1521
+ } else {
1522
+ if (options.output) {
1523
+ fs3.writeFileSync(options.output, result.markdown);
1524
+ console.log(import_chalk.default.green(`
1525
+ \u2714 Changelog saved to ${options.output}
1526
+ `));
1527
+ } else {
1528
+ console.log("\n" + result.markdown);
1529
+ }
1530
+ }
1531
+ if (!apiKey) {
1532
+ console.log(import_chalk.default.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"));
1533
+ console.log(import_chalk.default.cyan("\n \u{1F4A1} Want AI-enhanced changelogs?"));
1534
+ console.log(import_chalk.default.gray(" Get human-readable summaries with a Cencori API key"));
1535
+ console.log(import_chalk.default.gray(" Sign up free at https://cencori.com\n"));
1536
+ }
1537
+ process.exit(0);
1538
+ } catch (error) {
1539
+ spinner.fail("Changelog generation failed");
1540
+ console.error(import_chalk.default.red(`
1541
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1542
+ process.exit(1);
1543
+ }
1544
+ });
1285
1545
  import_commander.program.parse();
1286
1546
  }
1287
1547
  main();