Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
},
"devDependencies": {
"@eslint/js": "^9.38.0",
"@jest/globals": "^29.7.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.5.2",
"@types/yargs": "^17.0.33",
Expand Down
115 changes: 110 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/constants/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
export const ANY_H2_HEADING_RE = /^##\s.*$/m;
export const H3_SUBHEADER_CAPTURE_RE = /^###\s+(.+?)\s*$/;
export const RELEASE_HEADER_CAPTURE_RE = /^##\s*\[([^\]]+)\](.*)$/;
export const BULLET_PREFIX_RE = /^[*-]\s+/;
25 changes: 20 additions & 5 deletions src/utils/category-tune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
scoreCategories,
SCORE_THRESHOLDS,
} from '@/utils/category-score.js';
import { isDependencyUpdateTitle } from '@/utils/dependency-update.js';
import { isBucketName } from '@/utils/is.js';
import type { BucketName } from '@/types/changelog.js';

Expand Down Expand Up @@ -182,9 +183,23 @@ export function tuneCategoriesByTitle(
if (!adjusted[SECTION_CHANGED]) adjusted[SECTION_CHANGED] = [];
if (!adjusted[SECTION_ADDED]) adjusted[SECTION_ADDED] = [];

const allKnownTitles = Array.from(knownTitles);

// Rule: Dependency-only updates should remain in Chore to avoid noise in Changed.
const dependencyUpdates: string[] = [];
for (const title of allKnownTitles) {
if (isDependencyUpdateTitle(title)) dependencyUpdates.push(title);
}
if (dependencyUpdates.length)
moveTitlesToCategory(adjusted, dependencyUpdates, SECTION_CHORE);
const dependencyUpdateSet = new Set(dependencyUpdates);
const nonDependencyTitles = allKnownTitles.filter(
(title) => !dependencyUpdateSet.has(title),
);

// Collect titles that should be moved.
const toMove: string[] = [];
for (const title of knownTitles) {
for (const title of nonDependencyTitles) {
if (isImplicitFixTitle(title)) toMove.push(title);
}

Expand All @@ -196,7 +211,7 @@ export function tuneCategoriesByTitle(
// Examples: fix: msg, fix!: msg, fix(scope): msg, fix(scope)!: msg
const FIX_PREFIX_RE = /^fix(?:!:|(?:\([^)]*\))?!?:)/i;
const conventionalFixes: string[] = [];
for (const title of knownTitles) {
for (const title of nonDependencyTitles) {
if (FIX_PREFIX_RE.test(title)) conventionalFixes.push(title);
}
if (conventionalFixes.length)
Expand All @@ -217,7 +232,7 @@ export function tuneCategoriesByTitle(
};

const toChanged: string[] = [];
for (const title of knownTitles) {
for (const title of nonDependencyTitles) {
if (!isRefactorLike(title) && !isChangeLike(title)) continue;
const current = findCategory(title);
if (current === SECTION_FIXED) continue; // don't override explicit/implicit fixes
Expand All @@ -234,7 +249,7 @@ export function tuneCategoriesByTitle(
// Examples: feat: msg, feat!: msg, feat(scope): msg, feat(scope)!: msg
const FEAT_PREFIX_RE = /^feat(?:!:|(?:\([^)]*\))?!?:)/i;
const toAdded: string[] = [];
for (const title of knownTitles) {
for (const title of nonDependencyTitles) {
if (FEAT_PREFIX_RE.test(title)) toAdded.push(title);
}
if (toAdded.length) moveTitlesToCategory(adjusted, toAdded, SECTION_ADDED);
Expand All @@ -245,7 +260,7 @@ export function tuneCategoriesByTitle(
SECTION_DOCS,
SECTION_TEST,
]);
for (const title of knownTitles) {
for (const title of nonDependencyTitles) {
const current = findCategory(title);
if (!current || !WEAK_BUCKETS.has(current)) continue;
const scores = scoreCategories(title);
Expand Down
39 changes: 39 additions & 0 deletions src/utils/dependency-update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { CONVENTIONAL_PREFIX_RE } from '@/constants/conventional.js';
import { BULLET_PREFIX_RE } from '@/constants/markdown.js';
import { BUMP_OR_UPGRADE_RE, VERSION_FROM_TO_RE } from '@/constants/scoring.js';

const CONVENTIONAL_SCOPE_RE = /^[a-z]+(?:\(([^)]+)\))?!?:/i;
const DEP_SCOPE_RE = /\bdeps(?:-dev|-prod)?\b|\bdependencies?\b/i;
const DEP_BOT_RE = /\brenovate\b|\bdependabot\b|\bdeps?bot\b/i;
const DEP_ACTION_RE = /\b(bump|upgrade|update|pin|refresh|lockfile)\b/i;
const DEP_VERSION_RE = /\bto\s+v?\d+(?:\.\d+){0,3}\b/i;

/**
* Detect whether a title represents a dependency-only update.
* @param rawTitle PR title or changelog bullet text to inspect.
* @returns True when the title looks like a dependency update.
*/
export function isDependencyUpdateTitle(rawTitle: string): boolean {
if (!rawTitle) return false;
const trimmedTitle = rawTitle.replace(BULLET_PREFIX_RE, '').trim();
if (!trimmedTitle) return false;

const scopeMatch = trimmedTitle.match(CONVENTIONAL_SCOPE_RE);
const scope = scopeMatch?.[1];
if (scope && DEP_SCOPE_RE.test(scope)) return true;

const lower = trimmedTitle.toLowerCase();
if (DEP_BOT_RE.test(lower)) return true;

const core = lower.replace(CONVENTIONAL_PREFIX_RE, '').trim();
const hasDepPlural = /\bdeps\b|\bdependencies\b/.test(core);
const hasDepSingular = /\bdependency\b/.test(core);
const hasAction = DEP_ACTION_RE.test(core) || BUMP_OR_UPGRADE_RE.test(core);
const hasVersionHint =
VERSION_FROM_TO_RE.test(core) || DEP_VERSION_RE.test(core);

if (hasDepPlural && hasAction) return true;
if (hasDepSingular && hasAction && hasVersionHint) return true;

return false;
}
2 changes: 1 addition & 1 deletion src/utils/release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import {
normalizeTitle,
} from '@/utils/title-normalize.js';
import { FULL_CHANGELOG_RE } from '@/constants/release.js';
import { BULLET_PREFIX_RE } from '@/constants/markdown.js';

const H2_HEADING_RE = /^##\s+(.*)$/;
const BULLET_PREFIX_RE = /^[*-]\s+/;
const PR_URL_RE = /https?:\/\/\S+\/pull\/(\d+)/; // captures PR number
const PR_REF_RE = /\(#?(\d+)\)|#(\d+)/; // (#123) or #123
const AUTHOR_RE = /@([A-Za-z0-9_-]+)/;
Expand Down
Loading