[feat] Remove Additions section and add hyperlinks to API changelog

- Remove Additions section from changelog output (no longer needed)
- Add line number tracking in API snapshots
- Generate GitHub permalinks to referenced code in changelog
- Update workflows to pass git reference for link generation
- Breaking changes and modifications now link to source code

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
snomiao
2025-11-05 10:15:37 +00:00
parent 779f539b0e
commit 9c94a4818f
4 changed files with 87 additions and 41 deletions

View File

@@ -149,12 +149,18 @@ jobs:
- name: Compare API snapshots and generate changelog - name: Compare API snapshots and generate changelog
id: generate_changelog id: generate_changelog
run: | run: |
# Get git ref for TO version
GIT_REF=$(git rev-parse ${{ steps.validate_versions.outputs.to_tag }})
# Run the comparison script # Run the comparison script
CHANGELOG_OUTPUT=$(node scripts/compare-api-snapshots.js \ CHANGELOG_OUTPUT=$(node scripts/compare-api-snapshots.js \
.api-snapshots/from.json \ .api-snapshots/from.json \
.api-snapshots/to.json \ .api-snapshots/to.json \
${{ steps.validate_versions.outputs.from_version }} \ ${{ steps.validate_versions.outputs.from_version }} \
${{ steps.validate_versions.outputs.to_version }}) ${{ steps.validate_versions.outputs.to_version }} \
Comfy-Org \
ComfyUI_frontend \
"$GIT_REF")
# Save changelog to file for artifact # Save changelog to file for artifact
echo "$CHANGELOG_OUTPUT" > .api-snapshots/CHANGELOG-${{ steps.validate_versions.outputs.from_version }}-to-${{ steps.validate_versions.outputs.to_version }}.md echo "$CHANGELOG_OUTPUT" > .api-snapshots/CHANGELOG-${{ steps.validate_versions.outputs.from_version }}-to-${{ steps.validate_versions.outputs.to_version }}.md

View File

@@ -141,6 +141,9 @@ jobs:
# Create docs directory if it doesn't exist # Create docs directory if it doesn't exist
mkdir -p docs mkdir -p docs
# Get current git ref (commit SHA)
GIT_REF=$(git rev-parse HEAD)
# Run the comparison script # Run the comparison script
if [ -f .api-snapshots/previous.json ]; then if [ -f .api-snapshots/previous.json ]; then
node scripts/compare-api-snapshots.js \ node scripts/compare-api-snapshots.js \
@@ -148,6 +151,9 @@ jobs:
.api-snapshots/current.json \ .api-snapshots/current.json \
${{ steps.previous_version.outputs.version }} \ ${{ steps.previous_version.outputs.version }} \
${{ steps.current_version.outputs.version }} \ ${{ steps.current_version.outputs.version }} \
Comfy-Org \
ComfyUI_frontend \
"$GIT_REF" \
>> docs/API-CHANGELOG.md >> docs/API-CHANGELOG.md
else else
# First release - just document the initial API surface # First release - just document the initial API surface

View File

@@ -10,12 +10,20 @@ import * as fs from 'fs'
const args = process.argv.slice(2) const args = process.argv.slice(2)
if (args.length < 4) { if (args.length < 4) {
console.error( console.error(
'Usage: compare-api-snapshots.js <previous.json> <current.json> <previous-version> <current-version>' 'Usage: compare-api-snapshots.js <previous.json> <current.json> <previous-version> <current-version> [repo-owner] [repo-name] [git-ref]'
) )
process.exit(1) process.exit(1)
} }
const [previousPath, currentPath, previousVersion, currentVersion] = args const [
previousPath,
currentPath,
previousVersion,
currentVersion,
repoOwner = 'Comfy-Org',
repoName = 'ComfyUI_frontend',
gitRef = 'main'
] = args
if (!fs.existsSync(previousPath)) { if (!fs.existsSync(previousPath)) {
console.error(`Previous snapshot not found: ${previousPath}`) console.error(`Previous snapshot not found: ${previousPath}`)
@@ -30,6 +38,15 @@ if (!fs.existsSync(currentPath)) {
const previousApi = JSON.parse(fs.readFileSync(previousPath, 'utf-8')) const previousApi = JSON.parse(fs.readFileSync(previousPath, 'utf-8'))
const currentApi = JSON.parse(fs.readFileSync(currentPath, 'utf-8')) const currentApi = JSON.parse(fs.readFileSync(currentPath, 'utf-8'))
/**
* Generate GitHub permalink to source code
*/
function generateGitHubLink(name, line) {
if (!line) return name
// Format: https://github.com/Comfy-Org/ComfyUI_frontend/blob/main/dist/index.d.ts#L123
return `[\`${name}\`](https://github.com/${repoOwner}/${repoName}/blob/${gitRef}/dist/index.d.ts#L${line})`
}
/** /**
* Compare two API snapshots and generate changelog * Compare two API snapshots and generate changelog
*/ */
@@ -263,37 +280,40 @@ function formatChangelog(changes, prevVersion, currVersion) {
lines.push(`**${categoryToTitle(category)}**`) lines.push(`**${categoryToTitle(category)}**`)
lines.push('') lines.push('')
for (const item of items) { for (const item of items) {
lines.push(`- **Removed**: \`${item.name}\``) const displayName = item.item?.line
? generateGitHubLink(item.name, item.item.line)
: `\`${item.name}\``
lines.push(`- **Removed**: ${displayName}`)
} }
lines.push('') lines.push('')
} }
} }
// Additions // Additions - commented out as per feedback
if (changes.additions.length > 0) { // if (changes.additions.length > 0) {
lines.push('### ✨ Additions') // lines.push('### ✨ Additions')
lines.push('') // lines.push('')
//
const grouped = groupByCategory(changes.additions) // const grouped = groupByCategory(changes.additions)
for (const [category, items] of Object.entries(grouped)) { // for (const [category, items] of Object.entries(grouped)) {
lines.push(`**${categoryToTitle(category)}**`) // lines.push(`**${categoryToTitle(category)}**`)
lines.push('') // lines.push('')
for (const item of items) { // for (const item of items) {
lines.push(`- \`${item.name}\``) // lines.push(`- \`${item.name}\``)
if (item.item.members && item.item.members.length > 0) { // if (item.item.members && item.item.members.length > 0) {
const publicMembers = item.item.members.filter( // const publicMembers = item.item.members.filter(
(m) => !m.visibility || m.visibility === 'public' // (m) => !m.visibility || m.visibility === 'public'
) // )
if (publicMembers.length > 0 && publicMembers.length <= 5) { // if (publicMembers.length > 0 && publicMembers.length <= 5) {
lines.push( // lines.push(
` - Members: ${publicMembers.map((m) => `\`${m.name}\``).join(', ')}` // ` - Members: ${publicMembers.map((m) => `\`${m.name}\``).join(', ')}`
) // )
} // }
} // }
} // }
lines.push('') // lines.push('')
} // }
} // }
// Modifications // Modifications
if (changes.modifications.length > 0) { if (changes.modifications.length > 0) {
@@ -314,7 +334,13 @@ function formatChangelog(changes, prevVersion, currVersion) {
lines.push(`**${categoryToTitle(category)}**`) lines.push(`**${categoryToTitle(category)}**`)
lines.push('') lines.push('')
for (const item of items) { for (const item of items) {
lines.push(`- \`${item.name}\``) // Get the current item to access line number
const currItem =
currentApi[item.category] && currentApi[item.category][item.name]
const displayName = currItem?.line
? generateGitHubLink(item.name, currItem.line)
: `\`${item.name}\``
lines.push(`- ${displayName}`)
for (const change of item.changes) { for (const change of item.changes) {
const formatted = formatChange(change) const formatted = formatChange(change)
if (formatted) { if (formatted) {
@@ -326,11 +352,7 @@ function formatChangelog(changes, prevVersion, currVersion) {
} }
} }
if ( if (changes.breaking.length === 0 && changes.modifications.length === 0) {
changes.breaking.length === 0 &&
changes.additions.length === 0 &&
changes.modifications.length === 0
) {
lines.push('_No API changes detected._') lines.push('_No API changes detected._')
lines.push('') lines.push('')
} }

View File

@@ -38,17 +38,20 @@ function extractApiSurface(sourceFile) {
// Extract type aliases // Extract type aliases
if (ts.isTypeAliasDeclaration(node) && node.name) { if (ts.isTypeAliasDeclaration(node) && node.name) {
const name = node.name.text const name = node.name.text
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart())
api.types[name] = { api.types[name] = {
kind: 'type', kind: 'type',
name, name,
text: node.getText(sourceFile), text: node.getText(sourceFile),
exported: hasExportModifier(node) exported: hasExportModifier(node),
line: line + 1 // Convert to 1-indexed
} }
} }
// Extract interfaces // Extract interfaces
if (ts.isInterfaceDeclaration(node) && node.name) { if (ts.isInterfaceDeclaration(node) && node.name) {
const name = node.name.text const name = node.name.text
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart())
const members = [] const members = []
node.members.forEach((member) => { node.members.forEach((member) => {
@@ -83,13 +86,15 @@ function extractApiSurface(sourceFile) {
clause.types.map((type) => type.getText(sourceFile)) clause.types.map((type) => type.getText(sourceFile))
) )
.flat() .flat()
: [] : [],
line: line + 1 // Convert to 1-indexed
} }
} }
// Extract enums // Extract enums
if (ts.isEnumDeclaration(node) && node.name) { if (ts.isEnumDeclaration(node) && node.name) {
const name = node.name.text const name = node.name.text
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart())
const members = node.members.map((member) => ({ const members = node.members.map((member) => ({
name: member.name.getText(sourceFile), name: member.name.getText(sourceFile),
value: member.initializer value: member.initializer
@@ -101,13 +106,15 @@ function extractApiSurface(sourceFile) {
kind: 'enum', kind: 'enum',
name, name,
members, members,
exported: hasExportModifier(node) exported: hasExportModifier(node),
line: line + 1 // Convert to 1-indexed
} }
} }
// Extract functions // Extract functions
if (ts.isFunctionDeclaration(node) && node.name) { if (ts.isFunctionDeclaration(node) && node.name) {
const name = node.name.text const name = node.name.text
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart())
api.functions[name] = { api.functions[name] = {
kind: 'function', kind: 'function',
name, name,
@@ -117,13 +124,15 @@ function extractApiSurface(sourceFile) {
optional: !!p.questionToken optional: !!p.questionToken
})), })),
returnType: node.type ? node.type.getText(sourceFile) : 'any', returnType: node.type ? node.type.getText(sourceFile) : 'any',
exported: hasExportModifier(node) exported: hasExportModifier(node),
line: line + 1 // Convert to 1-indexed
} }
} }
// Extract classes // Extract classes
if (ts.isClassDeclaration(node) && node.name) { if (ts.isClassDeclaration(node) && node.name) {
const name = node.name.text const name = node.name.text
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart())
const members = [] const members = []
const methods = [] const methods = []
@@ -162,12 +171,14 @@ function extractApiSurface(sourceFile) {
clause.types.map((type) => type.getText(sourceFile)) clause.types.map((type) => type.getText(sourceFile))
) )
.flat() .flat()
: [] : [],
line: line + 1 // Convert to 1-indexed
} }
} }
// Extract variable declarations (constants) // Extract variable declarations (constants)
if (ts.isVariableStatement(node)) { if (ts.isVariableStatement(node)) {
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart())
node.declarationList.declarations.forEach((decl) => { node.declarationList.declarations.forEach((decl) => {
if (decl.name && ts.isIdentifier(decl.name)) { if (decl.name && ts.isIdentifier(decl.name)) {
const name = decl.name.text const name = decl.name.text
@@ -175,7 +186,8 @@ function extractApiSurface(sourceFile) {
kind: 'constant', kind: 'constant',
name, name,
type: decl.type ? decl.type.getText(sourceFile) : 'unknown', type: decl.type ? decl.type.getText(sourceFile) : 'unknown',
exported: hasExportModifier(node) exported: hasExportModifier(node),
line: line + 1 // Convert to 1-indexed
} }
} }
}) })