|
|
@ -1,30 +1,119 @@ |
|
|
// Prints the notes of the current release, published as a single GitHub
|
|
|
// Prints the notes of the current release, published as a single GitHub
|
|
|
// release for all packages: the preview image of the new icons followed by the
|
|
|
// release for all packages: the preview image of the new icons, the list of the
|
|
|
// changes from the changelogs of every package for this version. An entry
|
|
|
// new icons, the icons renamed since the previous release and the changes from
|
|
|
// shared by several packages is listed once, "Updated dependencies" entries and
|
|
|
// the changelogs of every package for this version, in the format of the
|
|
|
// the thanks to the maintainers are left out.
|
|
|
// previous releases:
|
|
|
//
|
|
|
//
|
|
|
// --ref <git ref> ref the image is loaded from (default: the `v<version>` tag)
|
|
|
// ### 18 new icons:
|
|
|
// --image-only print just the image
|
|
|
//
|
|
|
|
|
|
// - `outline/gpu`
|
|
|
|
|
|
//
|
|
|
|
|
|
// Renamed icons:
|
|
|
|
|
|
//
|
|
|
|
|
|
// - `outline/foodsteps` renamed to `outline/footsteps`
|
|
|
|
|
|
//
|
|
|
|
|
|
// ### Fixes and improvements
|
|
|
|
|
|
//
|
|
|
|
|
|
// - **Webfont:** Fix the `home` glyph (#1616, thanks @contributor)
|
|
|
|
|
|
//
|
|
|
|
|
|
// An entry shared by several packages is listed once, "Updated dependencies"
|
|
|
|
|
|
// entries and the thanks to the maintainers are left out.
|
|
|
|
|
|
//
|
|
|
|
|
|
// --ref <git ref> ref the image is loaded from (default: the `v<version>` tag)
|
|
|
|
|
|
// --since <git ref> ref the renamed icons are compared to (default: the tag
|
|
|
|
|
|
// of the previous release)
|
|
|
|
|
|
// --image-only print just the image
|
|
|
|
|
|
import { execFileSync } from 'child_process'; |
|
|
import { existsSync, readFileSync } from 'fs'; |
|
|
import { existsSync, readFileSync } from 'fs'; |
|
|
import { globSync } from 'glob'; |
|
|
import { globSync } from 'glob'; |
|
|
import { dirname, resolve } from 'path'; |
|
|
import { dirname, resolve } from 'path'; |
|
|
import { GITHUB_DIR, PACKAGES_DIR, getArgvs } from './helpers.mjs'; |
|
|
import { GITHUB_DIR, HOME_DIR, PACKAGES_DIR, getArgvs } from './helpers.mjs'; |
|
|
|
|
|
|
|
|
const MAIN_PACKAGE = '@tabler/icons'; |
|
|
const MAIN_PACKAGE = '@tabler/icons'; |
|
|
const CHANGE_TYPES = ['Major', 'Minor', 'Patch']; |
|
|
|
|
|
const MAINTAINERS = ['codecalm']; |
|
|
const MAINTAINERS = ['codecalm']; |
|
|
|
|
|
|
|
|
// " Thanks [@user](url), [@other](url)!" added by @changesets/changelog-github,
|
|
|
// Changelog sections of Changesets and their headings in the release notes
|
|
|
// kept only for the contributors from outside of the team
|
|
|
const CHANGE_TYPES = { |
|
|
const removeMaintainersThanks = (entry) => |
|
|
Major: 'Breaking changes', |
|
|
entry.replace(/ Thanks ((?:\[@[^\]]+\]\([^)]+\)(?:, )?)+)!/, (match, users) => { |
|
|
Minor: 'New features', |
|
|
const contributors = users |
|
|
Patch: 'Fixes and improvements', |
|
|
.split(', ') |
|
|
}; |
|
|
.filter((user) => !MAINTAINERS.some((maintainer) => user.startsWith(`[@${maintainer}]`))); |
|
|
|
|
|
|
|
|
const PACKAGE_LABELS = { |
|
|
return contributors.length > 0 ? ` Thanks ${contributors.join(', ')}!` : ''; |
|
|
'@tabler/icons-angular': 'Angular', |
|
|
}); |
|
|
'@tabler/icons-astro': 'Astro', |
|
|
|
|
|
'@tabler/icons-eps': 'EPS', |
|
|
|
|
|
'@tabler/icons-pdf': 'PDF', |
|
|
|
|
|
'@tabler/icons-png': 'PNG', |
|
|
|
|
|
'@tabler/icons-preact': 'Preact', |
|
|
|
|
|
'@tabler/icons-react': 'React', |
|
|
|
|
|
'@tabler/icons-react-native': 'React Native', |
|
|
|
|
|
'@tabler/icons-solidjs': 'SolidJS', |
|
|
|
|
|
'@tabler/icons-sprite': 'Sprite', |
|
|
|
|
|
'@tabler/icons-svelte': 'Svelte', |
|
|
|
|
|
'@tabler/icons-svelte-runes': 'Svelte Runes', |
|
|
|
|
|
'@tabler/icons-vue': 'Vue', |
|
|
|
|
|
'@tabler/icons-webfont': 'Webfont', |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// Entry of the changeset generated for the new icons (see release-changeset.mjs)
|
|
|
|
|
|
const NEW_ICONS_REGEX = /^\d+ new icons?$/; |
|
|
|
|
|
|
|
|
|
|
|
// "- [#1](url) [`sha`](url) Thanks [@user](url)! - Summary" generated by
|
|
|
|
|
|
// @changesets/changelog-github, every part before the summary is optional
|
|
|
|
|
|
const ENTRY_REGEX = |
|
|
|
|
|
/^- (?:(?:\[#(\d+)\]\([^)]+\))? ?(?:\[`[0-9a-f]+`\]\([^)]+\))? ?(?:Thanks (.+?)!)? - )?(.*)$/; |
|
|
|
|
|
|
|
|
|
|
|
const git = (...args) => |
|
|
|
|
|
execFileSync('git', args, { |
|
|
|
|
|
cwd: HOME_DIR, |
|
|
|
|
|
encoding: 'utf-8', |
|
|
|
|
|
stdio: ['ignore', 'pipe', 'ignore'], |
|
|
|
|
|
}).trim(); |
|
|
|
|
|
|
|
|
|
|
|
// Icons renamed since the previous release as [from, to] pairs, read from git
|
|
|
|
|
|
// as renames need no changeset
|
|
|
|
|
|
const getRenamedIcons = (version, since) => { |
|
|
|
|
|
try { |
|
|
|
|
|
since ||= git( |
|
|
|
|
|
'describe', |
|
|
|
|
|
'--tags', |
|
|
|
|
|
'--abbrev=0', |
|
|
|
|
|
'--match', |
|
|
|
|
|
'v[0-9]*', |
|
|
|
|
|
'--exclude', |
|
|
|
|
|
`v${version}`, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const diff = git('diff', since, 'HEAD', '--name-status', '--diff-filter=R', '--', 'icons'); |
|
|
|
|
|
|
|
|
|
|
|
return [...diff.matchAll(/^R\d+\s+icons\/(\S+)\.svg\s+icons\/(\S+)\.svg$/gm)].map( |
|
|
|
|
|
([, from, to]) => [from, to], |
|
|
|
|
|
); |
|
|
|
|
|
} catch { |
|
|
|
|
|
// no previous release or no git history (shallow clone)
|
|
|
|
|
|
return []; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// Changes the entry to "- Summary (#1, thanks @user)"
|
|
|
|
|
|
const formatEntry = (entry) => { |
|
|
|
|
|
const [firstLine, ...continuation] = entry.split('\n'); |
|
|
|
|
|
const [, pull, users = '', summary] = firstLine.match(ENTRY_REGEX); |
|
|
|
|
|
|
|
|
|
|
|
const contributors = [...users.matchAll(/\[@([^\]]+)\]/g)] |
|
|
|
|
|
.map(([, user]) => user) |
|
|
|
|
|
.filter((user) => !MAINTAINERS.includes(user)); |
|
|
|
|
|
|
|
|
|
|
|
const refs = [ |
|
|
|
|
|
pull && `#${pull}`, |
|
|
|
|
|
contributors.length > 0 && `thanks ${contributors.map((user) => `@${user}`).join(', ')}`, |
|
|
|
|
|
].filter(Boolean); |
|
|
|
|
|
|
|
|
|
|
|
return [`- ${summary}${refs.length > 0 ? ` (${refs.join(', ')})` : ''}`, ...continuation].join( |
|
|
|
|
|
'\n', |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
const { version } = JSON.parse(readFileSync(resolve(PACKAGES_DIR, 'icons/package.json'), 'utf-8')); |
|
|
const { version } = JSON.parse(readFileSync(resolve(PACKAGES_DIR, 'icons/package.json'), 'utf-8')); |
|
|
const argvs = getArgvs(); |
|
|
const argvs = getArgvs(); |
|
|
@ -44,7 +133,7 @@ if (!argvs['image-only']) { |
|
|
|
|
|
|
|
|
// { [type]: Map<entry, packages[]> } in the order the entries first appear,
|
|
|
// { [type]: Map<entry, packages[]> } in the order the entries first appear,
|
|
|
// starting with the main package
|
|
|
// starting with the main package
|
|
|
const changes = Object.fromEntries(CHANGE_TYPES.map((type) => [type, new Map()])); |
|
|
const changes = Object.fromEntries(Object.keys(CHANGE_TYPES).map((type) => [type, new Map()])); |
|
|
|
|
|
|
|
|
const packages = globSync(resolve(PACKAGES_DIR, '*/CHANGELOG.md')) |
|
|
const packages = globSync(resolve(PACKAGES_DIR, '*/CHANGELOG.md')) |
|
|
.map((changelogPath) => ({ |
|
|
.map((changelogPath) => ({ |
|
|
@ -59,7 +148,7 @@ if (!argvs['image-only']) { |
|
|
new RegExp(`^## ${escapedVersion}\\n([\\s\\S]*?)(?=^## |(?![\\s\\S]))`, 'm'), |
|
|
new RegExp(`^## ${escapedVersion}\\n([\\s\\S]*?)(?=^## |(?![\\s\\S]))`, 'm'), |
|
|
) || []; |
|
|
) || []; |
|
|
|
|
|
|
|
|
CHANGE_TYPES.forEach((type) => { |
|
|
Object.keys(CHANGE_TYPES).forEach((type) => { |
|
|
const [, list = ''] = |
|
|
const [, list = ''] = |
|
|
section.match( |
|
|
section.match( |
|
|
new RegExp(`^### ${type} Changes\\n([\\s\\S]*?)(?=^### |(?![\\s\\S]))`, 'm'), |
|
|
new RegExp(`^### ${type} Changes\\n([\\s\\S]*?)(?=^### |(?![\\s\\S]))`, 'm'), |
|
|
@ -68,29 +157,60 @@ if (!argvs['image-only']) { |
|
|
// Top-level list items, together with their indented continuation lines
|
|
|
// Top-level list items, together with their indented continuation lines
|
|
|
list |
|
|
list |
|
|
.split(/^(?=- )/m) |
|
|
.split(/^(?=- )/m) |
|
|
.map((entry) => removeMaintainersThanks(entry.trim())) |
|
|
.map((entry) => entry.trim()) |
|
|
.filter((entry) => entry.startsWith('- ') && !entry.startsWith('- Updated dependencies')) |
|
|
.filter((entry) => entry.startsWith('- ') && !entry.startsWith('- Updated dependencies')) |
|
|
|
|
|
.map(formatEntry) |
|
|
.forEach((entry) => { |
|
|
.forEach((entry) => { |
|
|
changes[type].set(entry, [...(changes[type].get(entry) || []), name]); |
|
|
changes[type].set(entry, [...(changes[type].get(entry) || []), name]); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
CHANGE_TYPES.forEach((type) => { |
|
|
// The new icons go first, as a section of their own
|
|
|
|
|
|
Object.values(changes).forEach((entries) => { |
|
|
|
|
|
entries.forEach((names, entry) => { |
|
|
|
|
|
const [firstLine, ...icons] = entry.split('\n'); |
|
|
|
|
|
|
|
|
|
|
|
if (!NEW_ICONS_REGEX.test(firstLine.slice(2))) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
console.log(`### ${firstLine.slice(2)}:\n`); |
|
|
|
|
|
console.log( |
|
|
|
|
|
icons |
|
|
|
|
|
.map((line) => line.trim()) |
|
|
|
|
|
.filter(Boolean) |
|
|
|
|
|
.join('\n'), |
|
|
|
|
|
); |
|
|
|
|
|
console.log(''); |
|
|
|
|
|
|
|
|
|
|
|
entries.delete(entry); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const renamedIcons = getRenamedIcons(version, argvs.since); |
|
|
|
|
|
|
|
|
|
|
|
if (renamedIcons.length > 0) { |
|
|
|
|
|
console.log('Renamed icons:\n'); |
|
|
|
|
|
renamedIcons.forEach(([from, to]) => console.log(`- \`${from}\` renamed to \`${to}\``)); |
|
|
|
|
|
console.log(''); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Object.entries(CHANGE_TYPES).forEach(([type, heading]) => { |
|
|
if (changes[type].size === 0) { |
|
|
if (changes[type].size === 0) { |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
console.log(`### ${type} Changes\n`); |
|
|
console.log(`### ${heading}\n`); |
|
|
|
|
|
|
|
|
changes[type].forEach((names, entry) => { |
|
|
changes[type].forEach((names, entry) => { |
|
|
// Entries of the main package (icons) need no label
|
|
|
// Entries of the main package (icons) need no label
|
|
|
const label = names.includes(MAIN_PACKAGE) |
|
|
const label = names.includes(MAIN_PACKAGE) |
|
|
? '' |
|
|
? '' |
|
|
: `${[...names] |
|
|
: `**${names |
|
|
|
|
|
.map((name) => PACKAGE_LABELS[name] || name) |
|
|
.sort() |
|
|
.sort() |
|
|
.map((name) => `\`${name}\``) |
|
|
.join(', ')}:** `;
|
|
|
.join(', ')}: `;
|
|
|
|
|
|
|
|
|
|
|
|
console.log(entry.replace(/^- /, `- ${label}`)); |
|
|
console.log(entry.replace(/^- /, `- ${label}`)); |
|
|
}); |
|
|
}); |
|
|
|