@codluv/versionguard 0.6.0 → 0.7.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.
@@ -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
  *
@@ -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;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAC/B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,OAAc,EACtB,YAAY,GAAE,OAAc,GAC3B,yBAAyB,CAgD3B;AAED;;;;;;;;;;;;;;;;;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"}
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
- function validateChangelog(changelogPath, version, strict = true, requireEntry = true) {
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}`);
@@ -1775,7 +1807,9 @@ const DEFAULT_CONFIG = {
1775
1807
  enabled: true,
1776
1808
  file: "CHANGELOG.md",
1777
1809
  strict: true,
1778
- requireEntry: true
1810
+ requireEntry: true,
1811
+ enforceStructure: false,
1812
+ sections: ["Added", "Changed", "Deprecated", "Removed", "Fixed", "Security"]
1779
1813
  },
1780
1814
  git: {
1781
1815
  hooks: {
@@ -2690,7 +2724,11 @@ function getTagPreflightError(config, cwd, expectedVersion, allowAutoFix = false
2690
2724
  path.join(cwd, config.changelog.file),
2691
2725
  version,
2692
2726
  config.changelog.strict,
2693
- config.changelog.requireEntry
2727
+ config.changelog.requireEntry,
2728
+ {
2729
+ enforceStructure: config.changelog.enforceStructure,
2730
+ sections: config.changelog.sections
2731
+ }
2694
2732
  );
2695
2733
  if (!changelogResult.valid) {
2696
2734
  return changelogResult.errors[0] ?? "Changelog validation failed";
@@ -2798,7 +2836,11 @@ function validate(config, cwd = process.cwd()) {
2798
2836
  changelogPath,
2799
2837
  version,
2800
2838
  config.changelog.strict,
2801
- config.changelog.requireEntry
2839
+ config.changelog.requireEntry,
2840
+ {
2841
+ enforceStructure: config.changelog.enforceStructure,
2842
+ sections: config.changelog.sections
2843
+ }
2802
2844
  );
2803
2845
  if (!changelogResult.valid) {
2804
2846
  changelogValid = false;
@@ -2937,4 +2979,4 @@ export {
2937
2979
  canBump as y,
2938
2980
  checkEnforceHooksPolicy as z
2939
2981
  };
2940
- //# sourceMappingURL=index-DeZAx4Le.js.map
2982
+ //# sourceMappingURL=index-DWiw8Nps.js.map