@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/README.md +53 -9
- package/dist/cli.js +253 -1
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +253 -1
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,11 +15,11 @@ That's it. Run it in any project directory to instantly scan for security issues
|
|
|
15
15
|
|
|
16
16
|
## Features
|
|
17
17
|
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
18
|
+
- **Pattern-based scanning** - Detects 50+ types of secrets, PII, and vulnerabilities
|
|
19
|
+
- **Cencori AI auto-fix** - Automatically fixes issues with one command
|
|
20
|
+
- **Fast** - Scans thousands of files in seconds
|
|
21
|
+
- **Zero config** - Works out of the box
|
|
22
|
+
- **Security scoring** - A through F tier grading
|
|
23
23
|
|
|
24
24
|
## Installation
|
|
25
25
|
|
|
@@ -80,7 +80,7 @@ Your API key is saved to `~/.cencorirc` for future scans.
|
|
|
80
80
|
|
|
81
81
|
## What It Detects
|
|
82
82
|
|
|
83
|
-
###
|
|
83
|
+
### API Keys & Secrets
|
|
84
84
|
|
|
85
85
|
| Provider | Pattern |
|
|
86
86
|
|----------|---------|
|
|
@@ -94,21 +94,21 @@ Your API key is saved to `~/.cencorirc` for future scans.
|
|
|
94
94
|
| Firebase | `firebase-adminsdk-...` |
|
|
95
95
|
| And 20+ more... | |
|
|
96
96
|
|
|
97
|
-
###
|
|
97
|
+
### PII (Personal Identifiable Information)
|
|
98
98
|
|
|
99
99
|
- Email addresses in code
|
|
100
100
|
- Phone numbers
|
|
101
101
|
- Social Security Numbers
|
|
102
102
|
- Credit card numbers
|
|
103
103
|
|
|
104
|
-
###
|
|
104
|
+
### Exposed Routes
|
|
105
105
|
|
|
106
106
|
- Next.js API routes without authentication
|
|
107
107
|
- Express routes without auth middleware
|
|
108
108
|
- Sensitive files in `/public` folders
|
|
109
109
|
- Dashboard/admin routes without protection
|
|
110
110
|
|
|
111
|
-
###
|
|
111
|
+
### Security Vulnerabilities
|
|
112
112
|
|
|
113
113
|
- SQL injection patterns
|
|
114
114
|
- XSS vulnerabilities (innerHTML, dangerouslySetInnerHTML)
|
|
@@ -126,6 +126,50 @@ Your API key is saved to `~/.cencorirc` for future scans.
|
|
|
126
126
|
| **D-Tier** | Poor | Significant issues found |
|
|
127
127
|
| **F-Tier** | Critical | Secrets or major vulnerabilities exposed |
|
|
128
128
|
|
|
129
|
+
## Changelog Generation
|
|
130
|
+
|
|
131
|
+
Generate AI-powered changelogs from your git commit history.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Generate weekly changelog
|
|
135
|
+
npx @cencori/scan changelog
|
|
136
|
+
|
|
137
|
+
# Custom time range
|
|
138
|
+
npx @cencori/scan changelog --since="2 weeks ago"
|
|
139
|
+
|
|
140
|
+
# Output to file
|
|
141
|
+
npx @cencori/scan changelog --output=CHANGELOG.md
|
|
142
|
+
|
|
143
|
+
# JSON format
|
|
144
|
+
npx @cencori/scan changelog --format=json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Example Output
|
|
148
|
+
|
|
149
|
+
```markdown
|
|
150
|
+
## Changelog (Jan 23, 2026 - Jan 30, 2026)
|
|
151
|
+
|
|
152
|
+
### Features
|
|
153
|
+
|
|
154
|
+
- Added AI-powered changelog generation
|
|
155
|
+
- New security scanning patterns for AWS secrets
|
|
156
|
+
|
|
157
|
+
### Bug Fixes
|
|
158
|
+
|
|
159
|
+
- Fixed telemetry not sending before process exit
|
|
160
|
+
|
|
161
|
+
### Documentation
|
|
162
|
+
|
|
163
|
+
- Updated README with new examples
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Pro Tier (with API key)
|
|
167
|
+
|
|
168
|
+
Get human-readable, summarized changelogs with AI:
|
|
169
|
+
- Converts developer commit messages to user-facing language
|
|
170
|
+
- Intelligently groups related changes
|
|
171
|
+
- Highlights breaking changes automatically
|
|
172
|
+
|
|
129
173
|
## Example Output
|
|
130
174
|
|
|
131
175
|
```
|
package/dist/cli.js
CHANGED
|
@@ -973,10 +973,210 @@ 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
|
+
for (const entry of entries) {
|
|
1082
|
+
md += `### ${entry.title}
|
|
1083
|
+
|
|
1084
|
+
`;
|
|
1085
|
+
for (const commit of entry.commits) {
|
|
1086
|
+
const cleanMessage = commit.replace(/^(feat|fix|chore|docs|style|refactor|test)(\(.+?\))?:\s*/i, "").trim();
|
|
1087
|
+
md += `- ${cleanMessage}
|
|
1088
|
+
`;
|
|
1089
|
+
}
|
|
1090
|
+
md += "\n";
|
|
1091
|
+
}
|
|
1092
|
+
return md;
|
|
1093
|
+
}
|
|
1094
|
+
async function generateChangelogAI(commits, apiKey, period) {
|
|
1095
|
+
const commitMessages = commits.map((c) => c.message).join("\n");
|
|
1096
|
+
try {
|
|
1097
|
+
const response = await fetch(`${CENCORI_API_URL2}/changelog/generate`, {
|
|
1098
|
+
method: "POST",
|
|
1099
|
+
headers: {
|
|
1100
|
+
"Content-Type": "application/json",
|
|
1101
|
+
Authorization: `Bearer ${apiKey}`
|
|
1102
|
+
},
|
|
1103
|
+
body: JSON.stringify({
|
|
1104
|
+
commits: commitMessages,
|
|
1105
|
+
period
|
|
1106
|
+
})
|
|
1107
|
+
});
|
|
1108
|
+
if (!response.ok) {
|
|
1109
|
+
throw new Error("API request failed");
|
|
1110
|
+
}
|
|
1111
|
+
const data = await response.json();
|
|
1112
|
+
return {
|
|
1113
|
+
entries: data.entries || [],
|
|
1114
|
+
markdown: data.markdown || ""
|
|
1115
|
+
};
|
|
1116
|
+
} catch {
|
|
1117
|
+
const entries = groupCommitsByType(commits);
|
|
1118
|
+
return {
|
|
1119
|
+
entries,
|
|
1120
|
+
markdown: generateMarkdownFree(entries, period)
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
async function generateChangelog(since = "1 week ago", apiKey, cwd) {
|
|
1125
|
+
const commits = getCommits(since, cwd);
|
|
1126
|
+
const now = /* @__PURE__ */ new Date();
|
|
1127
|
+
const sinceDate = /* @__PURE__ */ new Date();
|
|
1128
|
+
const match = since.match(/(\d+)\s*(week|day|month)s?\s*ago/i);
|
|
1129
|
+
if (match) {
|
|
1130
|
+
const amount = parseInt(match[1], 10);
|
|
1131
|
+
const unit = match[2].toLowerCase();
|
|
1132
|
+
if (unit === "week") {
|
|
1133
|
+
sinceDate.setDate(sinceDate.getDate() - amount * 7);
|
|
1134
|
+
} else if (unit === "day") {
|
|
1135
|
+
sinceDate.setDate(sinceDate.getDate() - amount);
|
|
1136
|
+
} else if (unit === "month") {
|
|
1137
|
+
sinceDate.setMonth(sinceDate.getMonth() - amount);
|
|
1138
|
+
}
|
|
1139
|
+
} else {
|
|
1140
|
+
sinceDate.setDate(sinceDate.getDate() - 7);
|
|
1141
|
+
}
|
|
1142
|
+
const period = {
|
|
1143
|
+
start: sinceDate.toISOString(),
|
|
1144
|
+
end: now.toISOString()
|
|
1145
|
+
};
|
|
1146
|
+
if (commits.length === 0) {
|
|
1147
|
+
return {
|
|
1148
|
+
period,
|
|
1149
|
+
entries: [],
|
|
1150
|
+
markdown: `## Changelog
|
|
1151
|
+
|
|
1152
|
+
No commits found in the specified period.
|
|
1153
|
+
`,
|
|
1154
|
+
commitCount: 0
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
if (apiKey) {
|
|
1158
|
+
const { entries, markdown } = await generateChangelogAI(commits, apiKey, period);
|
|
1159
|
+
return {
|
|
1160
|
+
period,
|
|
1161
|
+
entries,
|
|
1162
|
+
markdown,
|
|
1163
|
+
commitCount: commits.length
|
|
1164
|
+
};
|
|
1165
|
+
} else {
|
|
1166
|
+
const entries = groupCommitsByType(commits);
|
|
1167
|
+
return {
|
|
1168
|
+
period,
|
|
1169
|
+
entries,
|
|
1170
|
+
markdown: generateMarkdownFree(entries, period),
|
|
1171
|
+
commitCount: commits.length
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
976
1176
|
// src/cli.ts
|
|
977
1177
|
var fs3 = __toESM(require("fs"));
|
|
978
1178
|
var path3 = __toESM(require("path"));
|
|
979
|
-
var VERSION = "0.
|
|
1179
|
+
var VERSION = "0.4.1";
|
|
980
1180
|
var scoreStyles = {
|
|
981
1181
|
A: { color: import_chalk.default.green },
|
|
982
1182
|
B: { color: import_chalk.default.blue },
|
|
@@ -1282,6 +1482,58 @@ async function main() {
|
|
|
1282
1482
|
process.exit(1);
|
|
1283
1483
|
}
|
|
1284
1484
|
});
|
|
1485
|
+
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) => {
|
|
1486
|
+
printBanner();
|
|
1487
|
+
const spinner = (0, import_ora.default)({
|
|
1488
|
+
text: "Reading git history...",
|
|
1489
|
+
color: "cyan"
|
|
1490
|
+
}).start();
|
|
1491
|
+
try {
|
|
1492
|
+
const apiKey = getApiKey();
|
|
1493
|
+
const result = await generateChangelog(options.since, apiKey || void 0);
|
|
1494
|
+
if (result.commitCount === 0) {
|
|
1495
|
+
spinner.warn("No commits found in the specified period");
|
|
1496
|
+
console.log(import_chalk.default.yellow(`
|
|
1497
|
+
Try a larger time range with --since "2 weeks ago"
|
|
1498
|
+
`));
|
|
1499
|
+
process.exit(0);
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
spinner.succeed(`Found ${result.commitCount} commits`);
|
|
1503
|
+
if (options.format === "json") {
|
|
1504
|
+
const output = JSON.stringify(result, null, 2);
|
|
1505
|
+
if (options.output) {
|
|
1506
|
+
fs3.writeFileSync(options.output, output);
|
|
1507
|
+
console.log(import_chalk.default.green(`
|
|
1508
|
+
\u2714 Changelog saved to ${options.output}
|
|
1509
|
+
`));
|
|
1510
|
+
} else {
|
|
1511
|
+
console.log(output);
|
|
1512
|
+
}
|
|
1513
|
+
} else {
|
|
1514
|
+
if (options.output) {
|
|
1515
|
+
fs3.writeFileSync(options.output, result.markdown);
|
|
1516
|
+
console.log(import_chalk.default.green(`
|
|
1517
|
+
\u2714 Changelog saved to ${options.output}
|
|
1518
|
+
`));
|
|
1519
|
+
} else {
|
|
1520
|
+
console.log("\n" + result.markdown);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
if (!apiKey) {
|
|
1524
|
+
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"));
|
|
1525
|
+
console.log(import_chalk.default.cyan("\n Want AI-enhanced changelogs?"));
|
|
1526
|
+
console.log(import_chalk.default.gray(" Get human-readable summaries with a Cencori API key"));
|
|
1527
|
+
console.log(import_chalk.default.gray(" Sign up free at https://cencori.com\n"));
|
|
1528
|
+
}
|
|
1529
|
+
process.exit(0);
|
|
1530
|
+
} catch (error) {
|
|
1531
|
+
spinner.fail("Changelog generation failed");
|
|
1532
|
+
console.error(import_chalk.default.red(`
|
|
1533
|
+
Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
1534
|
+
process.exit(1);
|
|
1535
|
+
}
|
|
1536
|
+
});
|
|
1285
1537
|
import_commander.program.parse();
|
|
1286
1538
|
}
|
|
1287
1539
|
main();
|