@codluv/versionguard 0.6.0 → 0.8.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/changelog.d.ts +21 -2
- package/dist/changelog.d.ts.map +1 -1
- package/dist/chunks/{index-DeZAx4Le.js → index-Cipg9sxE.js} +158 -12
- package/dist/chunks/index-Cipg9sxE.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +107 -86
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -8
- package/dist/sync.d.ts +26 -1
- package/dist/sync.d.ts.map +1 -1
- package/dist/tag/index.d.ts.map +1 -1
- package/dist/types.d.ts +72 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/dist/chunks/index-DeZAx4Le.js.map +0 -1
package/dist/changelog.d.ts
CHANGED
|
@@ -19,6 +19,18 @@ export interface ChangelogValidationResult {
|
|
|
19
19
|
*/
|
|
20
20
|
hasEntryForVersion: boolean;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Options for changelog structure enforcement.
|
|
24
|
+
*
|
|
25
|
+
* @public
|
|
26
|
+
* @since 0.7.0
|
|
27
|
+
*/
|
|
28
|
+
export interface ChangelogStructureOptions {
|
|
29
|
+
/** Validate section headers against an allowed list. */
|
|
30
|
+
enforceStructure?: boolean;
|
|
31
|
+
/** Allowed section names. Defaults to Keep a Changelog standard sections. */
|
|
32
|
+
sections?: string[];
|
|
33
|
+
}
|
|
22
34
|
/**
|
|
23
35
|
* Validates a changelog file for release readiness.
|
|
24
36
|
*
|
|
@@ -28,19 +40,26 @@ export interface ChangelogValidationResult {
|
|
|
28
40
|
* The validator checks for a top-level changelog heading, an `[Unreleased]`
|
|
29
41
|
* section, and optionally a dated entry for the requested version.
|
|
30
42
|
*
|
|
43
|
+
* When `structure.enforceStructure` is `true`, section headers (`### Name`)
|
|
44
|
+
* are validated against the allowed list and empty sections produce warnings.
|
|
45
|
+
*
|
|
31
46
|
* @param changelogPath - Path to the changelog file.
|
|
32
47
|
* @param version - Version that must be present in the changelog.
|
|
33
48
|
* @param strict - Whether to require compare links and dated release headings.
|
|
34
49
|
* @param requireEntry - Whether the requested version must already have an entry.
|
|
50
|
+
* @param structure - Optional structure enforcement options.
|
|
35
51
|
* @returns The result of validating the changelog file.
|
|
36
52
|
* @example
|
|
37
53
|
* ```ts
|
|
38
54
|
* import { validateChangelog } from 'versionguard';
|
|
39
55
|
*
|
|
40
|
-
* const result = validateChangelog('CHANGELOG.md', '1.2.0', true, true
|
|
56
|
+
* const result = validateChangelog('CHANGELOG.md', '1.2.0', true, true, {
|
|
57
|
+
* enforceStructure: true,
|
|
58
|
+
* sections: ['Added', 'Changed', 'Fixed'],
|
|
59
|
+
* });
|
|
41
60
|
* ```
|
|
42
61
|
*/
|
|
43
|
-
export declare function validateChangelog(changelogPath: string, version: string, strict?: boolean, requireEntry?: boolean): ChangelogValidationResult;
|
|
62
|
+
export declare function validateChangelog(changelogPath: string, version: string, strict?: boolean, requireEntry?: boolean, structure?: ChangelogStructureOptions): ChangelogValidationResult;
|
|
44
63
|
/**
|
|
45
64
|
* Gets the most recent released version from a changelog.
|
|
46
65
|
*
|
package/dist/changelog.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../src/changelog.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC;;OAEG;IACH,KAAK,EAAE,OAAO,CAAC;IACf;;OAEG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;OAEG;IACH,kBAAkB,EAAE,OAAO,CAAC;CAC7B;
|
|
1
|
+
{"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../src/changelog.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC;;OAEG;IACH,KAAK,EAAE,OAAO,CAAC;IACf;;OAEG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;OAEG;IACH,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAcD;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC,wDAAwD;IACxD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,iBAAiB,CAC/B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,OAAc,EACtB,YAAY,GAAE,OAAc,EAC5B,SAAS,CAAC,EAAE,yBAAyB,GACpC,yBAAyB,CAuD3B;AAiCD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQrE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAA8C,GACnD,IAAI,CAmBN;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAMjE;AASD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,MAAM,EACrB,IAAI,GAAE,MAA8C,GACnD,OAAO,CAoET"}
|
|
@@ -323,7 +323,15 @@ const calver = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
|
|
|
323
323
|
validate: validate$2
|
|
324
324
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
325
325
|
const CHANGELOG_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
|
|
326
|
-
|
|
326
|
+
const KEEP_A_CHANGELOG_SECTIONS = [
|
|
327
|
+
"Added",
|
|
328
|
+
"Changed",
|
|
329
|
+
"Deprecated",
|
|
330
|
+
"Removed",
|
|
331
|
+
"Fixed",
|
|
332
|
+
"Security"
|
|
333
|
+
];
|
|
334
|
+
function validateChangelog(changelogPath, version, strict = true, requireEntry = true, structure) {
|
|
327
335
|
if (!fs.existsSync(changelogPath)) {
|
|
328
336
|
return {
|
|
329
337
|
valid: !requireEntry,
|
|
@@ -360,12 +368,36 @@ function validateChangelog(changelogPath, version, strict = true, requireEntry =
|
|
|
360
368
|
}
|
|
361
369
|
}
|
|
362
370
|
}
|
|
371
|
+
if (structure?.enforceStructure) {
|
|
372
|
+
const allowed = structure.sections ?? KEEP_A_CHANGELOG_SECTIONS;
|
|
373
|
+
const sectionErrors = validateSections(content, allowed);
|
|
374
|
+
errors.push(...sectionErrors);
|
|
375
|
+
}
|
|
363
376
|
return {
|
|
364
377
|
valid: errors.length === 0,
|
|
365
378
|
errors,
|
|
366
379
|
hasEntryForVersion
|
|
367
380
|
};
|
|
368
381
|
}
|
|
382
|
+
function validateSections(content, allowed) {
|
|
383
|
+
const errors = [];
|
|
384
|
+
const lines = content.split("\n");
|
|
385
|
+
for (let i = 0; i < lines.length; i++) {
|
|
386
|
+
const sectionMatch = lines[i].match(/^### (.+)/);
|
|
387
|
+
if (!sectionMatch) continue;
|
|
388
|
+
const sectionName = sectionMatch[1].trim();
|
|
389
|
+
if (!allowed.includes(sectionName)) {
|
|
390
|
+
errors.push(
|
|
391
|
+
`Invalid changelog section "### ${sectionName}" (line ${i + 1}). Allowed: ${allowed.join(", ")}`
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
const nextContentLine = lines.slice(i + 1).find((l) => l.trim().length > 0);
|
|
395
|
+
if (!nextContentLine || nextContentLine.startsWith("#")) {
|
|
396
|
+
errors.push(`Empty changelog section "### ${sectionName}" (line ${i + 1})`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return errors;
|
|
400
|
+
}
|
|
369
401
|
function addVersionEntry(changelogPath, version, date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) {
|
|
370
402
|
if (!fs.existsSync(changelogPath)) {
|
|
371
403
|
throw new Error(`Changelog not found: ${changelogPath}`);
|
|
@@ -1560,6 +1592,89 @@ function checkHardcodedVersions(expectedVersion, config, ignorePatterns, cwd = p
|
|
|
1560
1592
|
}
|
|
1561
1593
|
return mismatches;
|
|
1562
1594
|
}
|
|
1595
|
+
const BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1596
|
+
".png",
|
|
1597
|
+
".jpg",
|
|
1598
|
+
".jpeg",
|
|
1599
|
+
".gif",
|
|
1600
|
+
".ico",
|
|
1601
|
+
".svg",
|
|
1602
|
+
".woff",
|
|
1603
|
+
".woff2",
|
|
1604
|
+
".ttf",
|
|
1605
|
+
".eot",
|
|
1606
|
+
".otf",
|
|
1607
|
+
".zip",
|
|
1608
|
+
".tar",
|
|
1609
|
+
".gz",
|
|
1610
|
+
".bz2",
|
|
1611
|
+
".7z",
|
|
1612
|
+
".pdf",
|
|
1613
|
+
".exe",
|
|
1614
|
+
".dll",
|
|
1615
|
+
".so",
|
|
1616
|
+
".dylib",
|
|
1617
|
+
".wasm",
|
|
1618
|
+
".mp3",
|
|
1619
|
+
".mp4",
|
|
1620
|
+
".webm",
|
|
1621
|
+
".webp",
|
|
1622
|
+
".avif"
|
|
1623
|
+
]);
|
|
1624
|
+
function scanRepoForVersions(expectedVersion, scanConfig, ignorePatterns, cwd = process.cwd()) {
|
|
1625
|
+
const files = [
|
|
1626
|
+
...new Set(
|
|
1627
|
+
globSync("**/*", {
|
|
1628
|
+
cwd,
|
|
1629
|
+
absolute: true,
|
|
1630
|
+
dot: true,
|
|
1631
|
+
ignore: [
|
|
1632
|
+
...ignorePatterns,
|
|
1633
|
+
// Always skip changelogs (handled by changelog validation) and lockfiles
|
|
1634
|
+
"CHANGELOG.md",
|
|
1635
|
+
"*.lock",
|
|
1636
|
+
"package-lock.json",
|
|
1637
|
+
"yarn.lock",
|
|
1638
|
+
"pnpm-lock.yaml",
|
|
1639
|
+
// Skip VG's own config
|
|
1640
|
+
".versionguard.yml",
|
|
1641
|
+
".versionguard.yaml"
|
|
1642
|
+
]
|
|
1643
|
+
})
|
|
1644
|
+
)
|
|
1645
|
+
].sort();
|
|
1646
|
+
const allowedFiles = new Set(
|
|
1647
|
+
scanConfig.allowlist.flatMap((entry) => resolveFiles([entry.file], cwd, []))
|
|
1648
|
+
);
|
|
1649
|
+
const mismatches = [];
|
|
1650
|
+
for (const filePath of files) {
|
|
1651
|
+
if (BINARY_EXTENSIONS.has(path.extname(filePath).toLowerCase())) continue;
|
|
1652
|
+
if (allowedFiles.has(filePath)) continue;
|
|
1653
|
+
let content;
|
|
1654
|
+
try {
|
|
1655
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
1656
|
+
} catch {
|
|
1657
|
+
continue;
|
|
1658
|
+
}
|
|
1659
|
+
if (content.slice(0, 8192).includes("\0")) continue;
|
|
1660
|
+
for (const patternStr of scanConfig.patterns) {
|
|
1661
|
+
const regex = new RegExp(patternStr, "gm");
|
|
1662
|
+
let match = regex.exec(content);
|
|
1663
|
+
while (match) {
|
|
1664
|
+
const found = match[1] ?? match[0] ?? "";
|
|
1665
|
+
if (found && found !== expectedVersion && found !== "Unreleased") {
|
|
1666
|
+
mismatches.push({
|
|
1667
|
+
file: path.relative(cwd, filePath),
|
|
1668
|
+
line: getLineNumber(content, match.index),
|
|
1669
|
+
found
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
match = regex.exec(content);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
return mismatches;
|
|
1677
|
+
}
|
|
1563
1678
|
const DEFAULT_SEMVER_CONFIG = {
|
|
1564
1679
|
allowVPrefix: false,
|
|
1565
1680
|
allowBuildMetadata: true,
|
|
@@ -1775,7 +1890,21 @@ const DEFAULT_CONFIG = {
|
|
|
1775
1890
|
enabled: true,
|
|
1776
1891
|
file: "CHANGELOG.md",
|
|
1777
1892
|
strict: true,
|
|
1778
|
-
requireEntry: true
|
|
1893
|
+
requireEntry: true,
|
|
1894
|
+
enforceStructure: false,
|
|
1895
|
+
sections: ["Added", "Changed", "Deprecated", "Removed", "Fixed", "Security"]
|
|
1896
|
+
},
|
|
1897
|
+
scan: {
|
|
1898
|
+
enabled: false,
|
|
1899
|
+
patterns: [
|
|
1900
|
+
// version = "1.2.3" or version: "1.2.3" in code/config
|
|
1901
|
+
`(?:version\\s*[:=]\\s*["'])([\\d]+\\.[\\d]+\\.[\\d]+(?:-[\\w.]+)?)["']`,
|
|
1902
|
+
// Docker FROM image:1.2.3
|
|
1903
|
+
"(?:FROM\\s+\\S+:)(\\d+\\.\\d+\\.\\d+(?:-[\\w.]+)?)",
|
|
1904
|
+
// GitHub Actions uses: action@v1.2.3
|
|
1905
|
+
"(?:uses:\\s+\\S+@v?)(\\d+\\.\\d+\\.\\d+(?:-[\\w.]+)?)"
|
|
1906
|
+
],
|
|
1907
|
+
allowlist: []
|
|
1779
1908
|
},
|
|
1780
1909
|
git: {
|
|
1781
1910
|
hooks: {
|
|
@@ -2690,7 +2819,11 @@ function getTagPreflightError(config, cwd, expectedVersion, allowAutoFix = false
|
|
|
2690
2819
|
path.join(cwd, config.changelog.file),
|
|
2691
2820
|
version,
|
|
2692
2821
|
config.changelog.strict,
|
|
2693
|
-
config.changelog.requireEntry
|
|
2822
|
+
config.changelog.requireEntry,
|
|
2823
|
+
{
|
|
2824
|
+
enforceStructure: config.changelog.enforceStructure,
|
|
2825
|
+
sections: config.changelog.sections
|
|
2826
|
+
}
|
|
2694
2827
|
);
|
|
2695
2828
|
if (!changelogResult.valid) {
|
|
2696
2829
|
return changelogResult.errors[0] ?? "Changelog validation failed";
|
|
@@ -2791,6 +2924,14 @@ function validate(config, cwd = process.cwd()) {
|
|
|
2791
2924
|
);
|
|
2792
2925
|
}
|
|
2793
2926
|
}
|
|
2927
|
+
if (config.scan?.enabled) {
|
|
2928
|
+
const scanFindings = scanRepoForVersions(version, config.scan, config.ignore, cwd);
|
|
2929
|
+
for (const finding of scanFindings) {
|
|
2930
|
+
errors.push(
|
|
2931
|
+
`Stale version in ${finding.file}:${finding.line} - found "${finding.found}" but expected "${version}"`
|
|
2932
|
+
);
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2794
2935
|
let changelogValid = true;
|
|
2795
2936
|
if (config.changelog.enabled) {
|
|
2796
2937
|
const changelogPath = path.join(cwd, config.changelog.file);
|
|
@@ -2798,7 +2939,11 @@ function validate(config, cwd = process.cwd()) {
|
|
|
2798
2939
|
changelogPath,
|
|
2799
2940
|
version,
|
|
2800
2941
|
config.changelog.strict,
|
|
2801
|
-
config.changelog.requireEntry
|
|
2942
|
+
config.changelog.requireEntry,
|
|
2943
|
+
{
|
|
2944
|
+
enforceStructure: config.changelog.enforceStructure,
|
|
2945
|
+
sections: config.changelog.sections
|
|
2946
|
+
}
|
|
2802
2947
|
);
|
|
2803
2948
|
if (!changelogResult.valid) {
|
|
2804
2949
|
changelogValid = false;
|
|
@@ -2882,7 +3027,7 @@ function isWorktreeClean(cwd) {
|
|
|
2882
3027
|
}
|
|
2883
3028
|
}
|
|
2884
3029
|
export {
|
|
2885
|
-
|
|
3030
|
+
validateTagForPush as $,
|
|
2886
3031
|
checkHardcodedVersions as A,
|
|
2887
3032
|
checkHookIntegrity as B,
|
|
2888
3033
|
checkHooksPathOverride as C,
|
|
@@ -2901,16 +3046,17 @@ export {
|
|
|
2901
3046
|
initConfig as P,
|
|
2902
3047
|
resolveVersionSource as Q,
|
|
2903
3048
|
RegexVersionSource as R,
|
|
2904
|
-
|
|
3049
|
+
scanRepoForVersions as S,
|
|
2905
3050
|
TomlVersionSource as T,
|
|
2906
|
-
|
|
3051
|
+
semver as U,
|
|
2907
3052
|
VersionFileSource as V,
|
|
2908
|
-
|
|
2909
|
-
|
|
3053
|
+
suggestTagMessage as W,
|
|
3054
|
+
sync as X,
|
|
2910
3055
|
YamlVersionSource as Y,
|
|
2911
|
-
|
|
2912
|
-
|
|
3056
|
+
syncVersion as Z,
|
|
3057
|
+
validateChangelog as _,
|
|
2913
3058
|
installHooks as a,
|
|
3059
|
+
validateVersion as a0,
|
|
2914
3060
|
getPackageVersion as b,
|
|
2915
3061
|
createCkmEngine as c,
|
|
2916
3062
|
getVersionFeedback as d,
|
|
@@ -2937,4 +3083,4 @@ export {
|
|
|
2937
3083
|
canBump as y,
|
|
2938
3084
|
checkEnforceHooksPolicy as z
|
|
2939
3085
|
};
|
|
2940
|
-
//# sourceMappingURL=index-
|
|
3086
|
+
//# sourceMappingURL=index-Cipg9sxE.js.map
|