@cencori/scan 0.3.8 → 0.4.1

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.mjs CHANGED
@@ -950,10 +950,210 @@ 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
+ for (const entry of entries) {
1059
+ md += `### ${entry.title}
1060
+
1061
+ `;
1062
+ for (const commit of entry.commits) {
1063
+ const cleanMessage = commit.replace(/^(feat|fix|chore|docs|style|refactor|test)(\(.+?\))?:\s*/i, "").trim();
1064
+ md += `- ${cleanMessage}
1065
+ `;
1066
+ }
1067
+ md += "\n";
1068
+ }
1069
+ return md;
1070
+ }
1071
+ async function generateChangelogAI(commits, apiKey, period) {
1072
+ const commitMessages = commits.map((c) => c.message).join("\n");
1073
+ try {
1074
+ const response = await fetch(`${CENCORI_API_URL2}/changelog/generate`, {
1075
+ method: "POST",
1076
+ headers: {
1077
+ "Content-Type": "application/json",
1078
+ Authorization: `Bearer ${apiKey}`
1079
+ },
1080
+ body: JSON.stringify({
1081
+ commits: commitMessages,
1082
+ period
1083
+ })
1084
+ });
1085
+ if (!response.ok) {
1086
+ throw new Error("API request failed");
1087
+ }
1088
+ const data = await response.json();
1089
+ return {
1090
+ entries: data.entries || [],
1091
+ markdown: data.markdown || ""
1092
+ };
1093
+ } catch {
1094
+ const entries = groupCommitsByType(commits);
1095
+ return {
1096
+ entries,
1097
+ markdown: generateMarkdownFree(entries, period)
1098
+ };
1099
+ }
1100
+ }
1101
+ async function generateChangelog(since = "1 week ago", apiKey, cwd) {
1102
+ const commits = getCommits(since, cwd);
1103
+ const now = /* @__PURE__ */ new Date();
1104
+ const sinceDate = /* @__PURE__ */ new Date();
1105
+ const match = since.match(/(\d+)\s*(week|day|month)s?\s*ago/i);
1106
+ if (match) {
1107
+ const amount = parseInt(match[1], 10);
1108
+ const unit = match[2].toLowerCase();
1109
+ if (unit === "week") {
1110
+ sinceDate.setDate(sinceDate.getDate() - amount * 7);
1111
+ } else if (unit === "day") {
1112
+ sinceDate.setDate(sinceDate.getDate() - amount);
1113
+ } else if (unit === "month") {
1114
+ sinceDate.setMonth(sinceDate.getMonth() - amount);
1115
+ }
1116
+ } else {
1117
+ sinceDate.setDate(sinceDate.getDate() - 7);
1118
+ }
1119
+ const period = {
1120
+ start: sinceDate.toISOString(),
1121
+ end: now.toISOString()
1122
+ };
1123
+ if (commits.length === 0) {
1124
+ return {
1125
+ period,
1126
+ entries: [],
1127
+ markdown: `## Changelog
1128
+
1129
+ No commits found in the specified period.
1130
+ `,
1131
+ commitCount: 0
1132
+ };
1133
+ }
1134
+ if (apiKey) {
1135
+ const { entries, markdown } = await generateChangelogAI(commits, apiKey, period);
1136
+ return {
1137
+ period,
1138
+ entries,
1139
+ markdown,
1140
+ commitCount: commits.length
1141
+ };
1142
+ } else {
1143
+ const entries = groupCommitsByType(commits);
1144
+ return {
1145
+ period,
1146
+ entries,
1147
+ markdown: generateMarkdownFree(entries, period),
1148
+ commitCount: commits.length
1149
+ };
1150
+ }
1151
+ }
1152
+
953
1153
  // src/cli.ts
954
1154
  import * as fs3 from "fs";
955
1155
  import * as path3 from "path";
956
- var VERSION = "0.3.8";
1156
+ var VERSION = "0.4.1";
957
1157
  var scoreStyles = {
958
1158
  A: { color: chalk.green },
959
1159
  B: { color: chalk.blue },
@@ -1259,6 +1459,58 @@ async function main() {
1259
1459
  process.exit(1);
1260
1460
  }
1261
1461
  });
1462
+ 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) => {
1463
+ printBanner();
1464
+ const spinner = ora({
1465
+ text: "Reading git history...",
1466
+ color: "cyan"
1467
+ }).start();
1468
+ try {
1469
+ const apiKey = getApiKey();
1470
+ const result = await generateChangelog(options.since, apiKey || void 0);
1471
+ if (result.commitCount === 0) {
1472
+ spinner.warn("No commits found in the specified period");
1473
+ console.log(chalk.yellow(`
1474
+ Try a larger time range with --since "2 weeks ago"
1475
+ `));
1476
+ process.exit(0);
1477
+ return;
1478
+ }
1479
+ spinner.succeed(`Found ${result.commitCount} commits`);
1480
+ if (options.format === "json") {
1481
+ const output = JSON.stringify(result, null, 2);
1482
+ if (options.output) {
1483
+ fs3.writeFileSync(options.output, output);
1484
+ console.log(chalk.green(`
1485
+ \u2714 Changelog saved to ${options.output}
1486
+ `));
1487
+ } else {
1488
+ console.log(output);
1489
+ }
1490
+ } else {
1491
+ if (options.output) {
1492
+ fs3.writeFileSync(options.output, result.markdown);
1493
+ console.log(chalk.green(`
1494
+ \u2714 Changelog saved to ${options.output}
1495
+ `));
1496
+ } else {
1497
+ console.log("\n" + result.markdown);
1498
+ }
1499
+ }
1500
+ if (!apiKey) {
1501
+ 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"));
1502
+ console.log(chalk.cyan("\n Want AI-enhanced changelogs?"));
1503
+ console.log(chalk.gray(" Get human-readable summaries with a Cencori API key"));
1504
+ console.log(chalk.gray(" Sign up free at https://cencori.com\n"));
1505
+ }
1506
+ process.exit(0);
1507
+ } catch (error) {
1508
+ spinner.fail("Changelog generation failed");
1509
+ console.error(chalk.red(`
1510
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1511
+ process.exit(1);
1512
+ }
1513
+ });
1262
1514
  program.parse();
1263
1515
  }
1264
1516
  main();