feat(docs-ui): add automated changelog sync from @backstage/ui
Adds automated synchronization of component changelogs from @backstage/ui package to the documentation site, eliminating manual changelog maintenance. The sync script parses CHANGELOG.md using remark/unified, extracts component-specific entries via "Affected components:" tags, and generates per-version TypeScript files with preserved markdown formatting. Includes GitHub API integration to automatically fetch PR numbers for changelog entries. Key features: - Version comparison to sync only new releases - Per-version file structure (changelogs/v0.2.0.ts, etc.) - Markdown preservation via AST position slicing - Component name mapping (PascalCase → kebab-case) - Duplicate detection for idempotent syncing - GitHub authentication via gh CLI or GITHUB_TOKEN - Dry-run mode with preview Usage: ```bash yarn sync:changelog # Sync new entries yarn sync:changelog:dry-run # Preview changes ``` Technical changes: - Added scripts/sync-changelog.mjs (991 lines) - Refactored changelog.ts to import per-version files - Extracted types to separate types.ts file - Added dependencies: @octokit/rest, unified (remark-parse via @mdx-js/mdx) - Added documentation in README.md Signed-off-by: Johan Persson <johanopersson@gmail.com>
This commit is contained in:
@@ -13,3 +13,28 @@ yarn start
|
||||
## Deployment
|
||||
|
||||
Deployments are done automatically when a PR is merged into the `master` branch. We host the website using Github pages.
|
||||
|
||||
## Maintaining Component Changelogs
|
||||
|
||||
After a `@backstage/ui` release, sync the component changelogs to keep documentation up-to-date:
|
||||
|
||||
```bash
|
||||
yarn sync:changelog
|
||||
```
|
||||
|
||||
This script:
|
||||
|
||||
- Parses `packages/ui/CHANGELOG.md` for new versions
|
||||
- Extracts entries tagged with "Affected components: ..."
|
||||
- Updates `src/utils/changelog.ts` with new entries
|
||||
- Handles both component-specific and general package changes
|
||||
|
||||
After running, review the changes in `src/utils/changelog.ts` and commit them.
|
||||
|
||||
**Preview changes before writing:**
|
||||
|
||||
```bash
|
||||
yarn sync:changelog:dry-run
|
||||
```
|
||||
|
||||
Running this gives you a summary of what would be written, without actually adding or changing any files.
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"lint": "next lint",
|
||||
"prestart": "yarn sync:css",
|
||||
"start": "next dev",
|
||||
"sync:changelog": "node scripts/sync-changelog.mjs",
|
||||
"sync:changelog:dry-run": "node scripts/sync-changelog.mjs --dry-run",
|
||||
"sync:css": "node scripts/sync-css.js",
|
||||
"sync:css:watch": "node scripts/sync-css.js --watch"
|
||||
},
|
||||
@@ -39,6 +41,7 @@
|
||||
"storybook": "^8.6.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/rest": "^22.0.1",
|
||||
"@shikijs/transformers": "^3.13.0",
|
||||
"@types/mdx": "^2.0.13",
|
||||
"@types/node": "^20",
|
||||
@@ -48,6 +51,7 @@
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "15.3.4",
|
||||
"lightningcss": "^1.28.2",
|
||||
"typescript": "^5"
|
||||
"typescript": "^5",
|
||||
"unified": "^11.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,991 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { unified } from 'unified';
|
||||
import remarkParse from 'remark-parse';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
/**
|
||||
* Compare two semantic versions.
|
||||
* Returns: 1 if versionA > versionB, -1 if versionA < versionB, 0 if equal
|
||||
* Handles pre-release versions (e.g., "0.9.0-next.2")
|
||||
*/
|
||||
function compareVersions(versionA, versionB) {
|
||||
// Strip 'v' prefix if present
|
||||
versionA = versionA.replace(/^v/, '');
|
||||
versionB = versionB.replace(/^v/, '');
|
||||
|
||||
// Split into [major.minor.patch, prerelease]
|
||||
const [versionABase, versionAPre] = versionA.split('-');
|
||||
const [versionBBase, versionBPre] = versionB.split('-');
|
||||
|
||||
// Compare base versions
|
||||
const versionAParts = versionABase.split('.').map(Number);
|
||||
const versionBParts = versionBBase.split('.').map(Number);
|
||||
|
||||
for (
|
||||
let i = 0;
|
||||
i < Math.max(versionAParts.length, versionBParts.length);
|
||||
i++
|
||||
) {
|
||||
const versionAPart = versionAParts[i] || 0;
|
||||
const versionBPart = versionBParts[i] || 0;
|
||||
|
||||
if (versionAPart > versionBPart) return 1;
|
||||
if (versionAPart < versionBPart) return -1;
|
||||
}
|
||||
|
||||
// If base versions equal, compare pre-release
|
||||
if (!versionAPre && !versionBPre) return 0;
|
||||
if (!versionAPre) return 1; // versionA is release, versionB is pre-release
|
||||
if (!versionBPre) return -1; // versionA is pre-release, versionB is release
|
||||
|
||||
// Both have pre-release, compare them
|
||||
return versionAPre.localeCompare(versionBPre);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if versionA is newer than versionB
|
||||
*/
|
||||
function isNewerVersion(versionA, versionB) {
|
||||
return compareVersions(versionA, versionB) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize version to base version (remove pre-release suffix)
|
||||
* "0.9.0-next.3" → "0.9.0"
|
||||
* "0.9.0" → "0.9.0"
|
||||
*/
|
||||
function normalizeVersion(version) {
|
||||
return version.split('-')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the existing changelog.ts and find the highest version number
|
||||
* by scanning the changelogs/ directory for version files
|
||||
*/
|
||||
function getLastSyncedVersion(changelogPath) {
|
||||
try {
|
||||
// Scan changelogs/ directory for latest version file
|
||||
const changelogsDir = path.join(path.dirname(changelogPath), 'changelogs');
|
||||
|
||||
if (!fs.existsSync(changelogsDir)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read all version files (v0.1.0.ts, v0.2.0.ts, etc.)
|
||||
const files = fs
|
||||
.readdirSync(changelogsDir)
|
||||
.filter(f => f.match(/^v\d+\.\d+\.\d+\.ts$/))
|
||||
.map(f => f.replace(/^v/, '').replace(/\.ts$/, ''));
|
||||
|
||||
if (files.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find highest version
|
||||
let highest = files[0];
|
||||
for (const version of files) {
|
||||
if (compareVersions(version, highest) > 0) {
|
||||
highest = version;
|
||||
}
|
||||
}
|
||||
|
||||
return highest;
|
||||
} catch (error) {
|
||||
console.error(`Error reading changelog file: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert PascalCase component name to kebab-case slug
|
||||
* Avatar -> avatar
|
||||
* ButtonIcon -> button-icon
|
||||
* SearchField -> searchfield (special case per existing data)
|
||||
*/
|
||||
function pascalToKebab(str) {
|
||||
// Special cases observed in changelog.ts
|
||||
const specialCases = {
|
||||
SearchField: 'searchfield',
|
||||
TextField: 'textfield',
|
||||
DataTable: 'datatable',
|
||||
ScrollArea: 'scrollarea',
|
||||
};
|
||||
|
||||
if (specialCases[str]) {
|
||||
return specialCases[str];
|
||||
}
|
||||
|
||||
// General conversion: insert hyphen before uppercase letters
|
||||
return str
|
||||
.replace(/([A-Z])/g, '-$1')
|
||||
.toLowerCase()
|
||||
.replace(/^-/, ''); // Remove leading hyphen
|
||||
}
|
||||
|
||||
/**
|
||||
* Map component name to valid Component type slug
|
||||
* Returns null if component not recognized
|
||||
*/
|
||||
function mapComponentName(name, validComponents) {
|
||||
const kebab = pascalToKebab(name.trim());
|
||||
const kebabLower = kebab.toLowerCase();
|
||||
|
||||
// Check if it's a valid component
|
||||
const valid = validComponents.find(c => c.toLowerCase() === kebabLower);
|
||||
|
||||
return valid || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get valid component names from types.ts Component type
|
||||
*/
|
||||
function getValidComponents(changelogPath) {
|
||||
// Read from types.ts instead of changelog.ts
|
||||
const typesPath = changelogPath.replace('changelog.ts', 'types.ts');
|
||||
const content = fs.readFileSync(typesPath, 'utf-8');
|
||||
|
||||
// Extract Component type union
|
||||
const typeMatch = content.match(/export type Component =([^;]+);/s);
|
||||
if (!typeMatch) {
|
||||
throw new Error('Could not find Component type definition');
|
||||
}
|
||||
|
||||
// Extract string literals from the union
|
||||
const components = typeMatch[1]
|
||||
.match(/['"]([^'"]+)['"]/g)
|
||||
.map(s => s.replace(/['"]/g, ''));
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse CHANGELOG.md and extract entries newer than sinceVersion
|
||||
*/
|
||||
async function parseChangelogMd(changelogPath, sinceVersion, validComponents) {
|
||||
const content = fs.readFileSync(changelogPath, 'utf-8');
|
||||
const tree = unified().use(remarkParse).parse(content);
|
||||
|
||||
const entries = [];
|
||||
let currentVersion = null;
|
||||
let currentSection = null; // 'Minor Changes', 'Patch Changes', 'Major Changes'
|
||||
let processedCount = 0;
|
||||
|
||||
// Walk through the markdown AST
|
||||
async function walk(node, depth = 0) {
|
||||
// Version headers (## 0.9.0)
|
||||
if (node.type === 'heading' && node.depth === 2) {
|
||||
const versionText = extractText(node).trim();
|
||||
// Validate that this is actually a version number (X.Y.Z or X.Y.Z-pre.N)
|
||||
// Skip headings that are just markdown content within changelog entries
|
||||
if (/^\d+\.\d+\.\d+/.test(versionText)) {
|
||||
currentVersion = versionText;
|
||||
currentSection = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Section headers (### Minor Changes)
|
||||
if (node.type === 'heading' && node.depth === 3) {
|
||||
const sectionText = extractText(node);
|
||||
currentSection = sectionText.trim();
|
||||
return;
|
||||
}
|
||||
|
||||
// List items (- 539cf26: description)
|
||||
if (node.type === 'listItem' && currentVersion && currentSection) {
|
||||
// Only process if version is newer than sinceVersion
|
||||
if (!sinceVersion || isNewerVersion(currentVersion, sinceVersion)) {
|
||||
const entry = await parseListItem(
|
||||
node,
|
||||
currentVersion,
|
||||
currentSection,
|
||||
validComponents,
|
||||
content,
|
||||
);
|
||||
if (entry) {
|
||||
entries.push(entry);
|
||||
processedCount++;
|
||||
// Progress logging every 50 entries
|
||||
if (processedCount % 50 === 0) {
|
||||
process.stdout.write(`\r Processed ${processedCount} entries...`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse into children
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
await walk(child, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await walk(tree);
|
||||
|
||||
// Clear progress line
|
||||
if (processedCount > 0) {
|
||||
process.stdout.write(`\r Processed ${processedCount} entries - done!\n`);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract plain text from a markdown node
|
||||
*/
|
||||
function extractText(node) {
|
||||
if (node.type === 'text') {
|
||||
return node.value;
|
||||
}
|
||||
if (node.children) {
|
||||
return node.children.map(extractText).join('');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract raw markdown content from a node using position offsets
|
||||
* This preserves all formatting: code blocks, lists, bold/italic, etc.
|
||||
*/
|
||||
function extractMarkdown(node, sourceText) {
|
||||
if (!node.position) {
|
||||
return extractText(node); // Fallback to text extraction
|
||||
}
|
||||
|
||||
const start = node.position.start.offset;
|
||||
const end = node.position.end.offset;
|
||||
|
||||
if (start === undefined || end === undefined) {
|
||||
return extractText(node);
|
||||
}
|
||||
|
||||
return sourceText.slice(start, end).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a list item to extract changelog entry
|
||||
* NOTE: PR numbers are NOT fetched here - they're fetched later only for new entries
|
||||
*/
|
||||
async function parseListItem(
|
||||
node,
|
||||
version,
|
||||
section,
|
||||
validComponents,
|
||||
sourceText,
|
||||
) {
|
||||
// Extract the full text content for SHA parsing
|
||||
const fullText = extractText(node);
|
||||
|
||||
// Parse commit SHA and description
|
||||
// Format: "- 539cf26: description..."
|
||||
const match = fullText.match(/^-?\s*([a-f0-9]+):\s*(.+)/s);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const commitSha = match[1];
|
||||
|
||||
// Extract full markdown content from the list item
|
||||
let fullMarkdown = extractMarkdown(node, sourceText);
|
||||
|
||||
// Remove the commit SHA prefix from markdown
|
||||
let description = fullMarkdown.replace(/^-?\s*[a-f0-9]+:\s*/, '').trim();
|
||||
|
||||
// Auto-detect components from "Affected components:" line
|
||||
let components = [];
|
||||
const componentMatch = description.match(/Affected components?:\s*([^\n]+)/i);
|
||||
if (componentMatch) {
|
||||
const componentNames = componentMatch[1]
|
||||
.split(',')
|
||||
.map(name => name.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
components = componentNames
|
||||
.map(name => mapComponentName(name, validComponents))
|
||||
.filter(Boolean);
|
||||
|
||||
// Optionally strip "Affected components:" line from description
|
||||
description = description
|
||||
.replace(/\n*Affected components?:[ \t]*[^\n]+/i, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
const prs = []; // Will be populated later by fetchPRNumbers()
|
||||
|
||||
// Infer type from section and description
|
||||
const type = inferChangeType(section, description, version);
|
||||
|
||||
return {
|
||||
version: normalizeVersion(version),
|
||||
section,
|
||||
commitSha,
|
||||
description,
|
||||
components,
|
||||
prs,
|
||||
type,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer change type from section and description
|
||||
*/
|
||||
function inferChangeType(section, description, version) {
|
||||
const isPre1 = version.startsWith('0.');
|
||||
|
||||
// Check description keywords first
|
||||
if (description.match(/^(New|Add(ed)?)\s/i)) {
|
||||
return 'new';
|
||||
}
|
||||
if (description.includes('BREAKING')) {
|
||||
return 'breaking';
|
||||
}
|
||||
|
||||
// Infer from section
|
||||
if (section === 'Minor Changes') {
|
||||
return isPre1 ? 'breaking' : undefined;
|
||||
} else if (section === 'Major Changes') {
|
||||
return 'breaking';
|
||||
} else if (section === 'Patch Changes') {
|
||||
return 'fix';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate new changelog entries and update changelog.ts
|
||||
*/
|
||||
/**
|
||||
* Fetch PR numbers for entries that need them
|
||||
* Only fetches for entries with empty prs array (new entries)
|
||||
*/
|
||||
async function fetchPRNumbers(entries, dryRun = false) {
|
||||
const entriesNeedingPRs = entries.filter(
|
||||
e => e.prs.length === 0 && e.commitSha,
|
||||
);
|
||||
|
||||
if (entriesNeedingPRs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`\n🔍 Fetching PR numbers for ${entriesNeedingPRs.length} new entries...`,
|
||||
);
|
||||
|
||||
let fetchedCount = 0;
|
||||
const startTime = Date.now();
|
||||
|
||||
for (const entry of entriesNeedingPRs) {
|
||||
const prNumber = await findPRNumber(entry.commitSha);
|
||||
if (prNumber) {
|
||||
entry.prs.push(prNumber);
|
||||
}
|
||||
|
||||
fetchedCount++;
|
||||
// Progress every 10 lookups
|
||||
if (fetchedCount % 10 === 0 || fetchedCount === entriesNeedingPRs.length) {
|
||||
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
const rate = ((fetchedCount / (Date.now() - startTime)) * 1000).toFixed(
|
||||
1,
|
||||
);
|
||||
process.stdout.write(
|
||||
`\r Fetched ${fetchedCount}/${entriesNeedingPRs.length} PR numbers (${elapsed}s, ~${rate} req/s)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||
const foundCount = entriesNeedingPRs.filter(e => e.prs.length > 0).length;
|
||||
console.log(
|
||||
`\n ✓ Found ${foundCount}/${entriesNeedingPRs.length} PR numbers in ${totalTime}s`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate per-version changelog files in changelogs/ directory
|
||||
*/
|
||||
async function generateVersionFiles(entries, changelogsDir, dryRun = false) {
|
||||
// Group entries by normalized version
|
||||
const byVersion = {};
|
||||
entries.forEach(entry => {
|
||||
const version = entry.version;
|
||||
if (!byVersion[version]) {
|
||||
byVersion[version] = [];
|
||||
}
|
||||
byVersion[version].push(entry);
|
||||
});
|
||||
|
||||
const versionFiles = [];
|
||||
const skippedVersions = [];
|
||||
const generatedVersions = [];
|
||||
|
||||
for (const [version, versionEntries] of Object.entries(byVersion)) {
|
||||
const fileName = `v${version}.ts`;
|
||||
const filePath = path.join(changelogsDir, fileName);
|
||||
const varName = `changelog_${version.replace(/\./g, '_')}`;
|
||||
|
||||
// Check if file already exists - skip if it does
|
||||
if (fs.existsSync(filePath)) {
|
||||
skippedVersions.push(version);
|
||||
// Still add to versionFiles array so main changelog.ts can import it
|
||||
versionFiles.push({
|
||||
version,
|
||||
fileName,
|
||||
filePath,
|
||||
varName,
|
||||
content: null, // No content needed for existing files
|
||||
skipped: true,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Generate TypeScript code for this version
|
||||
const entryObjects = versionEntries
|
||||
.map(entry => {
|
||||
const componentsStr = `[${entry.components
|
||||
.map(c => `'${c}'`)
|
||||
.join(', ')}]`;
|
||||
const prsStr = `[${entry.prs.map(pr => `'${pr}'`).join(', ')}]`;
|
||||
const typeStr = entry.type ? `type: '${entry.type}',` : '';
|
||||
const shaStr = entry.commitSha
|
||||
? `commitSha: '${entry.commitSha}',`
|
||||
: '';
|
||||
|
||||
// Escape description for template literal
|
||||
const descEscaped = entry.description
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/`/g, '\\`')
|
||||
.replace(/\${/g, '\\${');
|
||||
|
||||
return ` {
|
||||
components: ${componentsStr},
|
||||
version: '${entry.version}',
|
||||
prs: ${prsStr},
|
||||
description: \`${descEscaped}\`,
|
||||
${typeStr}
|
||||
${shaStr}
|
||||
}`;
|
||||
})
|
||||
.join(',\n');
|
||||
|
||||
const fileContent = `import type { ChangelogProps } from '../types';
|
||||
|
||||
export const ${varName}: ChangelogProps[] = [
|
||||
${entryObjects}
|
||||
];
|
||||
`;
|
||||
|
||||
generatedVersions.push(version);
|
||||
versionFiles.push({
|
||||
version,
|
||||
fileName,
|
||||
filePath,
|
||||
varName,
|
||||
content: fileContent,
|
||||
skipped: false,
|
||||
});
|
||||
|
||||
if (!dryRun) {
|
||||
fs.writeFileSync(filePath, fileContent, 'utf-8');
|
||||
}
|
||||
}
|
||||
|
||||
// Log summary
|
||||
if (skippedVersions.length > 0) {
|
||||
console.log(
|
||||
`\n⏭️ Skipped ${
|
||||
skippedVersions.length
|
||||
} existing version files: ${skippedVersions
|
||||
.sort((a, b) => compareVersions(a, b))
|
||||
.join(', ')}`,
|
||||
);
|
||||
}
|
||||
if (generatedVersions.length > 0) {
|
||||
console.log(
|
||||
`\n✨ ${dryRun ? 'Would generate' : 'Generated'} ${
|
||||
generatedVersions.length
|
||||
} new version files: ${generatedVersions
|
||||
.sort((a, b) => compareVersions(a, b))
|
||||
.join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
return versionFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate main changelog.ts that imports and spreads all version files
|
||||
*/
|
||||
async function generateMainChangelog(
|
||||
versionFiles,
|
||||
changelogPath,
|
||||
dryRun = false,
|
||||
) {
|
||||
// Get the changelogs directory path
|
||||
const changelogsDir = path.join(path.dirname(changelogPath), 'changelogs');
|
||||
|
||||
// Read all version files from the changelogs directory
|
||||
const allVersionFiles = new Map();
|
||||
|
||||
// First, add all files from the directory (if it exists)
|
||||
if (fs.existsSync(changelogsDir)) {
|
||||
const files = fs.readdirSync(changelogsDir);
|
||||
files.forEach(file => {
|
||||
if (file.match(/^v\d+\.\d+\.\d+\.ts$/)) {
|
||||
const version = file.replace(/^v/, '').replace(/\.ts$/, '');
|
||||
const varName = `changelog_${version.replace(/\./g, '_')}`;
|
||||
allVersionFiles.set(version, {
|
||||
version,
|
||||
fileName: file,
|
||||
varName,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Then, add/update with files from this run (in case new ones were generated)
|
||||
versionFiles.forEach(vf => {
|
||||
allVersionFiles.set(vf.version, {
|
||||
version: vf.version,
|
||||
fileName: vf.fileName,
|
||||
varName: vf.varName,
|
||||
});
|
||||
});
|
||||
|
||||
// Sort versions in descending order
|
||||
const sortedVersions = Array.from(allVersionFiles.values()).sort((a, b) =>
|
||||
compareVersions(b.version, a.version),
|
||||
);
|
||||
|
||||
const imports = sortedVersions
|
||||
.map(
|
||||
v =>
|
||||
`import { ${v.varName} } from './changelogs/${v.fileName.replace(
|
||||
'.ts',
|
||||
'',
|
||||
)}';`,
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
const spreads = sortedVersions.map(v => ` ...${v.varName},`).join('\n');
|
||||
|
||||
const content = `export * from './types';
|
||||
${imports}
|
||||
|
||||
export const changelog = [
|
||||
${spreads}
|
||||
];
|
||||
`;
|
||||
|
||||
console.log(
|
||||
`\n📝 Main changelog.ts will import all ${sortedVersions.length} version files`,
|
||||
);
|
||||
|
||||
if (!dryRun) {
|
||||
fs.writeFileSync(changelogPath, content, 'utf-8');
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an entry already exists in changelog.ts
|
||||
*/
|
||||
function isDuplicate(entry, existingContent) {
|
||||
// Simple check: look for version + description substring
|
||||
const descStart = entry.description.substring(0, 50);
|
||||
const pattern = `version: '${entry.version}'`;
|
||||
|
||||
const versionIndex = existingContent.indexOf(pattern);
|
||||
if (versionIndex === -1) return false;
|
||||
|
||||
// Check if description appears near this version
|
||||
const contextWindow = existingContent.substring(
|
||||
Math.max(0, versionIndex - 200),
|
||||
versionIndex + 500,
|
||||
);
|
||||
|
||||
return contextWindow.includes(descStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect GitHub authentication method
|
||||
* Priority: gh CLI → GITHUB_TOKEN env → unauthenticated
|
||||
*/
|
||||
function getGitHubAuth() {
|
||||
// 1. Try gh CLI (preferred - uses keyring auth)
|
||||
try {
|
||||
// Check specifically for github.com authentication
|
||||
const output = execSync('gh auth status -h github.com 2>&1', {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
if (output.includes('Logged in to github.com')) {
|
||||
console.log('✓ Using gh CLI authentication');
|
||||
return { method: 'gh-cli' };
|
||||
}
|
||||
} catch {
|
||||
// gh not installed or not authenticated to github.com
|
||||
}
|
||||
|
||||
// 2. Try GITHUB_TOKEN env var
|
||||
if (process.env.GITHUB_TOKEN) {
|
||||
console.log('✓ Using GITHUB_TOKEN authentication');
|
||||
return { method: 'token', token: process.env.GITHUB_TOKEN };
|
||||
}
|
||||
|
||||
// 3. Fallback to unauthenticated (60 req/hour limit)
|
||||
console.warn('⚠️ Using unauthenticated GitHub API (60 requests/hour)');
|
||||
console.warn(' For higher limits: authenticate gh CLI or set GITHUB_TOKEN');
|
||||
return { method: 'unauthenticated' };
|
||||
}
|
||||
|
||||
// Initialize auth and Octokit
|
||||
const githubAuth = getGitHubAuth();
|
||||
const octokit =
|
||||
githubAuth.method === 'token'
|
||||
? new Octokit({ auth: githubAuth.token })
|
||||
: new Octokit();
|
||||
|
||||
// Cache for PR lookups
|
||||
const prCache = new Map();
|
||||
|
||||
/**
|
||||
* Expand a truncated SHA to full SHA using local git repository
|
||||
* Falls back to GitHub API if git rev-parse fails
|
||||
*/
|
||||
async function expandCommitSha(shortSha) {
|
||||
try {
|
||||
// First try local git repository (fastest, works for all commits)
|
||||
// We need to run this from the backstage monorepo root
|
||||
const repoRoot = path.join(__dirname, '../../');
|
||||
|
||||
try {
|
||||
const fullSha = execSync(`git rev-parse ${shortSha}`, {
|
||||
encoding: 'utf-8',
|
||||
cwd: repoRoot,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
|
||||
if (fullSha && fullSha.length === 40) {
|
||||
return fullSha;
|
||||
}
|
||||
} catch (gitError) {
|
||||
// Handle ambiguous SHA - parse git's error output to find commit candidates
|
||||
if (gitError.stderr && gitError.stderr.includes('ambiguous')) {
|
||||
const stderr = gitError.stderr.toString();
|
||||
// Extract commit SHAs from git's hint output
|
||||
// Format: "hint: 83c100e6accfb commit 2025-10-22 - message"
|
||||
const commitMatch = stderr.match(/hint:\s+([a-f0-9]+)\s+commit\s+/);
|
||||
if (commitMatch && commitMatch[1]) {
|
||||
// Found the commit SHA from the ambiguous candidates
|
||||
const candidateSha = commitMatch[1];
|
||||
// Verify this is a valid commit
|
||||
try {
|
||||
const fullSha = execSync(`git rev-parse ${candidateSha}`, {
|
||||
encoding: 'utf-8',
|
||||
cwd: repoRoot,
|
||||
}).trim();
|
||||
if (fullSha && fullSha.length === 40) {
|
||||
return fullSha;
|
||||
}
|
||||
} catch {
|
||||
// Candidate didn't work, continue to GitHub API
|
||||
}
|
||||
}
|
||||
}
|
||||
// Commit not in local repo, try GitHub API
|
||||
}
|
||||
} catch (error) {
|
||||
// Error setting up git command, try GitHub API
|
||||
}
|
||||
|
||||
try {
|
||||
// Fallback to GitHub API
|
||||
if (githubAuth.method === 'gh-cli') {
|
||||
const result = execSync(
|
||||
`gh api repos/backstage/backstage/commits/${shortSha} --jq '.sha' 2>/dev/null`,
|
||||
{ encoding: 'utf-8' },
|
||||
).trim();
|
||||
return result || null;
|
||||
} else {
|
||||
// Use Octokit
|
||||
const { data: commit } = await octokit.rest.repos.getCommit({
|
||||
owner: 'backstage',
|
||||
repo: 'backstage',
|
||||
ref: shortSha,
|
||||
});
|
||||
return commit.sha;
|
||||
}
|
||||
} catch (error) {
|
||||
// Commit not found or other error - return null silently
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find PR number for a commit SHA using GitHub API
|
||||
*/
|
||||
async function findPRNumber(commitSha) {
|
||||
// Check cache first
|
||||
if (prCache.has(commitSha)) {
|
||||
return prCache.get(commitSha);
|
||||
}
|
||||
|
||||
try {
|
||||
// If SHA is truncated (7 chars), expand it first
|
||||
let fullSha = commitSha;
|
||||
if (commitSha.length === 7) {
|
||||
fullSha = await expandCommitSha(commitSha);
|
||||
if (!fullSha) {
|
||||
// Commit not found
|
||||
console.warn(`⚠️ Commit not found: ${commitSha}`);
|
||||
prCache.set(commitSha, null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let prNumber = null;
|
||||
|
||||
// Use gh CLI if available (faster, uses existing auth)
|
||||
if (githubAuth.method === 'gh-cli') {
|
||||
try {
|
||||
const result = execSync(
|
||||
`gh api repos/backstage/backstage/commits/${fullSha}/pulls --jq '.[0].number' 2>/dev/null`,
|
||||
{ encoding: 'utf-8' },
|
||||
).trim();
|
||||
prNumber = result || null;
|
||||
} catch (ghError) {
|
||||
// Handle gh CLI errors gracefully - commit exists but has no PR
|
||||
prNumber = null;
|
||||
}
|
||||
} else {
|
||||
// Otherwise use Octokit (token or unauthenticated)
|
||||
const { data: prs } =
|
||||
await octokit.rest.repos.listPullRequestsAssociatedWithCommit({
|
||||
owner: 'backstage',
|
||||
repo: 'backstage',
|
||||
commit_sha: fullSha,
|
||||
});
|
||||
prNumber = prs.length > 0 ? prs[0].number.toString() : null;
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
prCache.set(commitSha, prNumber);
|
||||
return prNumber;
|
||||
} catch (error) {
|
||||
if (error.message?.includes('rate limit')) {
|
||||
console.error('⚠️ GitHub API rate limit exceeded');
|
||||
console.error(
|
||||
' Authenticate gh CLI or set GITHUB_TOKEN for higher limits',
|
||||
);
|
||||
} else {
|
||||
console.warn(`⚠️ Error finding PR for ${commitSha}: ${error.message}`);
|
||||
}
|
||||
prCache.set(commitSha, null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main sync function
|
||||
*/
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const dryRun = args.includes('--dry-run');
|
||||
|
||||
const changelogTsPath = path.join(__dirname, '../src/utils/changelog.ts');
|
||||
const changelogMdPath = path.join(
|
||||
__dirname,
|
||||
'../../packages/ui/CHANGELOG.md',
|
||||
);
|
||||
|
||||
// Validate files exist
|
||||
if (!fs.existsSync(changelogTsPath)) {
|
||||
throw new Error(`changelog.ts not found at: ${changelogTsPath}`);
|
||||
}
|
||||
if (!fs.existsSync(changelogMdPath)) {
|
||||
throw new Error(`CHANGELOG.md not found at: ${changelogMdPath}`);
|
||||
}
|
||||
|
||||
console.log('📋 Syncing UI component changelogs...\n');
|
||||
|
||||
// Get last synced version
|
||||
const lastVersion = getLastSyncedVersion(changelogTsPath);
|
||||
console.log(`Last synced version: ${lastVersion || '(none)'}`);
|
||||
|
||||
// Get valid components
|
||||
const validComponents = getValidComponents(changelogTsPath);
|
||||
console.log(`Valid components: ${validComponents.length}`);
|
||||
|
||||
// Parse CHANGELOG.md
|
||||
console.log('\n📖 Parsing CHANGELOG.md...');
|
||||
const allEntries = await parseChangelogMd(
|
||||
changelogMdPath,
|
||||
lastVersion,
|
||||
validComponents,
|
||||
);
|
||||
console.log(`Found ${allEntries.length} total entries since ${lastVersion}`);
|
||||
|
||||
// Read existing changelog content for duplicate detection
|
||||
const existingContent = fs.readFileSync(changelogTsPath, 'utf-8');
|
||||
|
||||
// Filter to only new, non-duplicate entries
|
||||
const relevantEntries = allEntries.filter(e => {
|
||||
const hasComponents = e.components.length > 0 || e.components.length === 0;
|
||||
const notDuplicate = !isDuplicate(e, existingContent);
|
||||
return hasComponents && notDuplicate;
|
||||
});
|
||||
|
||||
const duplicatesCount = allEntries.length - relevantEntries.length;
|
||||
if (duplicatesCount > 0) {
|
||||
console.log(`Skipped ${duplicatesCount} duplicate entries`);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Relevant entries (with or without components): ${relevantEntries.length}`,
|
||||
);
|
||||
|
||||
if (relevantEntries.length === 0) {
|
||||
console.log('\n✅ No new entries to sync');
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch PR numbers for new entries (lazy fetch - only for entries that will be written)
|
||||
await fetchPRNumbers(relevantEntries, dryRun);
|
||||
|
||||
// Show summary
|
||||
console.log('\n📝 New entries by component:');
|
||||
const byComponent = {};
|
||||
relevantEntries.forEach(entry => {
|
||||
if (entry.components.length === 0) {
|
||||
byComponent['(general)'] = (byComponent['(general)'] || 0) + 1;
|
||||
} else {
|
||||
entry.components.forEach(comp => {
|
||||
byComponent[comp] = (byComponent[comp] || 0) + 1;
|
||||
});
|
||||
}
|
||||
});
|
||||
Object.entries(byComponent).forEach(([comp, count]) => {
|
||||
console.log(` - ${comp}: ${count} ${count === 1 ? 'entry' : 'entries'}`);
|
||||
});
|
||||
|
||||
// Warn about unknown components
|
||||
const unknownComponents = [];
|
||||
allEntries.forEach(entry => {
|
||||
const fullText = entry.description;
|
||||
const componentMatch = fullText.match(
|
||||
/Affected components?:[ \t]*([^\n]+)/i,
|
||||
);
|
||||
if (componentMatch) {
|
||||
const names = componentMatch[1].split(',').map(n => n.trim());
|
||||
names.forEach(name => {
|
||||
if (!mapComponentName(name, validComponents)) {
|
||||
unknownComponents.push(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (unknownComponents.length > 0) {
|
||||
console.log('\n⚠️ Unknown components (skipped):');
|
||||
[...new Set(unknownComponents)].forEach(name => {
|
||||
console.log(` - ${name}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Create changelogs directory if it doesn't exist
|
||||
const changelogsDir = path.join(__dirname, '../src/utils/changelogs');
|
||||
if (!fs.existsSync(changelogsDir) && !dryRun) {
|
||||
fs.mkdirSync(changelogsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Generate version files
|
||||
console.log(
|
||||
`\n${
|
||||
dryRun ? '🔍 Dry run - would generate' : '✍️ Generating'
|
||||
} version files...`,
|
||||
);
|
||||
|
||||
const versionFiles = await generateVersionFiles(
|
||||
relevantEntries,
|
||||
changelogsDir,
|
||||
dryRun,
|
||||
);
|
||||
|
||||
// Generate main changelog.ts
|
||||
const mainContent = await generateMainChangelog(
|
||||
versionFiles,
|
||||
changelogTsPath,
|
||||
dryRun,
|
||||
);
|
||||
|
||||
if (!dryRun) {
|
||||
console.log('\n✅ Changelog sync complete!');
|
||||
const newFiles = versionFiles.filter(vf => !vf.skipped).length;
|
||||
const skippedFiles = versionFiles.filter(vf => vf.skipped).length;
|
||||
if (newFiles > 0) {
|
||||
console.log(
|
||||
` Generated ${newFiles} new version file${newFiles === 1 ? '' : 's'}`,
|
||||
);
|
||||
}
|
||||
if (skippedFiles > 0) {
|
||||
console.log(
|
||||
` Preserved ${skippedFiles} existing version file${
|
||||
skippedFiles === 1 ? '' : 's'
|
||||
}`,
|
||||
);
|
||||
}
|
||||
console.log(` Updated ${changelogTsPath}`);
|
||||
} else {
|
||||
console.log('\n📄 Sample version file content:\n');
|
||||
// Find first non-skipped version file for sample
|
||||
const sampleFile = versionFiles.find(vf => !vf.skipped && vf.content);
|
||||
if (sampleFile) {
|
||||
const sample = sampleFile.content.split('\n').slice(0, 30).join('\n');
|
||||
console.log(sample);
|
||||
console.log('...\n');
|
||||
} else {
|
||||
console.log('(No new version files to generate)\n');
|
||||
}
|
||||
|
||||
console.log('\n📄 Main changelog.ts content:\n');
|
||||
console.log(mainContent);
|
||||
|
||||
const newFiles = versionFiles.filter(vf => !vf.skipped).length;
|
||||
const skippedFiles = versionFiles.filter(vf => vf.skipped).length;
|
||||
|
||||
console.log('\n✅ Dry run complete - no files were modified');
|
||||
if (newFiles > 0) {
|
||||
console.log(
|
||||
` Run without --dry-run to generate ${newFiles} new version file${
|
||||
newFiles === 1 ? '' : 's'
|
||||
}`,
|
||||
);
|
||||
}
|
||||
if (skippedFiles > 0) {
|
||||
console.log(
|
||||
` ${skippedFiles} existing version file${
|
||||
skippedFiles === 1 ? '' : 's'
|
||||
} will be preserved`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run main if executed directly
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main().catch(error => {
|
||||
console.error('❌ Error:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -52,14 +52,15 @@ export function Changelog() {
|
||||
${bumpEntries
|
||||
.map(e => {
|
||||
const prs =
|
||||
e.prs.length > 0 &&
|
||||
e.prs
|
||||
.map(
|
||||
pr =>
|
||||
`[#${pr}](https://github.com/backstage/backstage/pull/${pr})`,
|
||||
)
|
||||
.join(', ');
|
||||
return `- ${e.description} ${prs}`;
|
||||
e.prs.length > 0
|
||||
? e.prs
|
||||
.map(
|
||||
pr =>
|
||||
`[#${pr}](https://github.com/backstage/backstage/pull/${pr})`,
|
||||
)
|
||||
.join(', ')
|
||||
: '';
|
||||
return `- ${e.description}${prs ? ` ${prs}` : ''}`;
|
||||
})
|
||||
.join('\n')}`;
|
||||
})
|
||||
|
||||
@@ -17,14 +17,17 @@ export const ChangelogComponent = ({ component }: { component: Component }) => {
|
||||
${componentChangelog
|
||||
?.map(change => {
|
||||
const prs =
|
||||
change.prs.length > 0 &&
|
||||
change.prs
|
||||
.map(
|
||||
pr =>
|
||||
`[#${pr}](https://github.com/backstage/backstage/pull/${pr})`,
|
||||
)
|
||||
.join(', ');
|
||||
return `- \`${change.version}\` - ${change.description} ${prs}`;
|
||||
change.prs.length > 0
|
||||
? change.prs
|
||||
.map(
|
||||
pr =>
|
||||
`[#${pr}](https://github.com/backstage/backstage/pull/${pr})`,
|
||||
)
|
||||
.join(', ')
|
||||
: '';
|
||||
return `- \`${change.version}\` - ${change.description}${
|
||||
prs ? ` ${prs}` : ''
|
||||
}`;
|
||||
})
|
||||
.join('\n')}`}
|
||||
/>
|
||||
|
||||
+12
-536
@@ -1,538 +1,14 @@
|
||||
export type Component =
|
||||
| 'avatar'
|
||||
| 'box'
|
||||
| 'button'
|
||||
| 'button-link'
|
||||
| 'heading'
|
||||
| 'text'
|
||||
| 'button-icon'
|
||||
| 'icon'
|
||||
| 'tabs'
|
||||
| 'menu'
|
||||
| 'textfield'
|
||||
| 'datatable'
|
||||
| 'select'
|
||||
| 'collapsible'
|
||||
| 'accordion'
|
||||
| 'checkbox'
|
||||
| 'container'
|
||||
| 'link'
|
||||
| 'tooltip'
|
||||
| 'scrollarea'
|
||||
| 'flex'
|
||||
| 'switch'
|
||||
| 'grid'
|
||||
| 'searchfield'
|
||||
| 'radio-group'
|
||||
| 'card'
|
||||
| 'skeleton'
|
||||
| 'header'
|
||||
| 'passwordfield';
|
||||
export * from './types';
|
||||
import { changelog_0_6_0 } from './changelogs/v0.6.0';
|
||||
import { changelog_0_5_0 } from './changelogs/v0.5.0';
|
||||
import { changelog_0_4_0 } from './changelogs/v0.4.0';
|
||||
import { changelog_0_3_0 } from './changelogs/v0.3.0';
|
||||
import { changelog_0_2_0 } from './changelogs/v0.2.0';
|
||||
|
||||
export type Version = `${number}.${number}.${number}`;
|
||||
|
||||
export interface ChangelogProps {
|
||||
components: Component[];
|
||||
description: string;
|
||||
version: Version;
|
||||
prs: string[];
|
||||
type?: 'breaking' | 'new' | 'fix';
|
||||
}
|
||||
|
||||
export const changelog: ChangelogProps[] = [
|
||||
{
|
||||
components: ['tooltip'],
|
||||
version: '0.6.0',
|
||||
description: 'New `Tooltip` component.',
|
||||
prs: ['30461'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['card'],
|
||||
version: '0.6.0',
|
||||
description: 'New `Card` component.',
|
||||
prs: ['30467'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['header'],
|
||||
version: '0.6.0',
|
||||
description: 'New `Header` component.',
|
||||
prs: ['30410', '30459', '30493'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['button', 'button-link', 'button-icon'],
|
||||
version: '0.6.0',
|
||||
description: 'Improve `Button`, `ButtonIcon` and `ButtonLink` styling.',
|
||||
prs: ['30448'],
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
components: ['text', 'heading'],
|
||||
version: '0.6.0',
|
||||
description:
|
||||
'Update return types for `Heading` & `Text` components for React 19.',
|
||||
prs: ['30440'],
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
components: ['button', 'button-link', 'button-icon'],
|
||||
version: '0.6.0',
|
||||
description:
|
||||
'New `tertiary` variant to `Button`, `ButtonIcon` and `ButtonLink`.',
|
||||
prs: ['30453'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['skeleton'],
|
||||
version: '0.6.0',
|
||||
description: 'New `Skeleton` component.',
|
||||
prs: ['30466'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['button-icon'],
|
||||
version: '0.6.0',
|
||||
description: 'Rename `IconButton` to `ButtonIcon`.',
|
||||
prs: ['30297'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['button-link'],
|
||||
version: '0.6.0',
|
||||
description:
|
||||
'New `ButtonLink`, which replaces the previous render prop pattern on `Button` and `IconButton`.',
|
||||
prs: ['30297'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['button', 'button-link', 'button-icon'],
|
||||
version: '0.6.0',
|
||||
description: 'Remove the `render` prop from all button-related components.',
|
||||
prs: ['30297'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.6.0',
|
||||
description: 'We are consolidating all css files into a single styles.css.',
|
||||
prs: ['30325'],
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
components: ['searchfield'],
|
||||
version: '0.6.0',
|
||||
description: 'New `SearchField` component.',
|
||||
prs: ['30357'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['radio-group'],
|
||||
version: '0.6.0',
|
||||
description: 'New `RadioGroup` + `Radio` component.',
|
||||
prs: ['30327'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.6.0',
|
||||
description: 'Added placeholder prop back to `TextField` component.',
|
||||
prs: ['30286'],
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.5.0',
|
||||
description: '`TextField` now has multiple label sizes.',
|
||||
prs: ['30249'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.5.0',
|
||||
description:
|
||||
'`TextField` can now hide label and description while keeping them available for screen readers.',
|
||||
prs: ['30249'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.5.0',
|
||||
description:
|
||||
'We are removing the `render` prop in favour of the `as` prop on `Heading` and `Text`.',
|
||||
prs: ['30291'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.5.0',
|
||||
description: 'Move `TextField` component to use react Aria under the hood.',
|
||||
prs: ['30286'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['button', 'button-link'],
|
||||
version: '0.5.0',
|
||||
description:
|
||||
'Added a render prop to the `Button` component to use it as a link.',
|
||||
prs: ['30165'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['heading'],
|
||||
version: '0.5.0',
|
||||
description: 'Fix styling for the title4 prop on the Heading component.',
|
||||
prs: ['30167'],
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
components: ['flex'],
|
||||
version: '0.5.0',
|
||||
description:
|
||||
'Add `min-width: 0;` by default on Flex components to support truncated text.',
|
||||
prs: ['30168'],
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
components: ['button', 'button-link', 'button-icon'],
|
||||
version: '0.5.0',
|
||||
description:
|
||||
'`Button`, `ButtonLink`, `ButtonIcon` now default to size `small` instead of `medium`',
|
||||
prs: ['30085', '30097'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['grid'],
|
||||
version: '0.5.0',
|
||||
description: 'Rename Grid component to `<Grid.Root />`',
|
||||
prs: ['30013'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['flex', 'container', 'grid', 'box'],
|
||||
version: '0.5.0',
|
||||
description: 'Fixes spacing props on layout components',
|
||||
prs: ['30013'],
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
components: ['switch'],
|
||||
version: '0.5.0',
|
||||
description: 'New `Switch` component',
|
||||
prs: ['30251'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['tabs'],
|
||||
version: '0.4.0',
|
||||
description: 'New `Tabs` component',
|
||||
prs: ['29996'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['text', 'heading'],
|
||||
version: '0.4.0',
|
||||
description: 'Add `truncate` prop to `Text` and `Heading`',
|
||||
prs: ['29988'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['menu'],
|
||||
version: '0.4.0',
|
||||
description: 'Add combobox option to `Menu`',
|
||||
prs: ['29986'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.4.0',
|
||||
description: 'Add icon prop on `TextField`',
|
||||
prs: ['29820'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['button', 'button-icon'],
|
||||
version: '0.4.0',
|
||||
description: 'Improve icon props on `Button` and `IconButton`',
|
||||
prs: ['29667'],
|
||||
},
|
||||
{
|
||||
components: ['text', 'heading'],
|
||||
version: '0.4.0',
|
||||
description:
|
||||
'Improve the way we treat custom render on `Text` and `Heading`',
|
||||
prs: ['29989'],
|
||||
},
|
||||
{
|
||||
components: ['menu'],
|
||||
version: '0.4.0',
|
||||
description: 'Improve `Menu` styles',
|
||||
prs: ['29986'],
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.4.0',
|
||||
description: 'Improve `TextField` styles',
|
||||
prs: ['29974'],
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.4.0',
|
||||
description: 'Improve clear button on `TextField`',
|
||||
prs: ['29878'],
|
||||
},
|
||||
{
|
||||
components: ['flex'],
|
||||
version: '0.4.0',
|
||||
description: 'Fix spacing props on all layout components',
|
||||
prs: ['30013'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.4.0',
|
||||
description: 'Fix - Pin Base UI version',
|
||||
prs: ['29782'],
|
||||
},
|
||||
{
|
||||
components: ['select'],
|
||||
version: '0.4.0',
|
||||
description: 'Fix - Clicking `Select` label moves focus to trigger',
|
||||
prs: ['29755'],
|
||||
},
|
||||
{
|
||||
components: ['datatable'],
|
||||
version: '0.4.0',
|
||||
description: 'Fix `DataTable.Pagination` count issue',
|
||||
prs: ['29688'],
|
||||
},
|
||||
{
|
||||
components: ['datatable'],
|
||||
version: '0.3.0',
|
||||
description: 'Add `DataTable` component',
|
||||
prs: ['29484', '29603'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['select'],
|
||||
version: '0.3.0',
|
||||
description: 'Add `Select` component',
|
||||
prs: ['29440'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['avatar'],
|
||||
version: '0.3.0',
|
||||
description: 'Add `Avatar` component',
|
||||
prs: ['29594'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['collapsible'],
|
||||
version: '0.3.0',
|
||||
description: 'Add `Collapsible` component',
|
||||
prs: ['29617'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.3.0',
|
||||
description: 'Add `TextField` component instead of `Field` + `Input`',
|
||||
prs: ['29364'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['datatable'],
|
||||
version: '0.3.0',
|
||||
description: 'Add `TableCellProfile`',
|
||||
prs: ['29600'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description: 'Add breakpoint hooks - `up()` and `down()`',
|
||||
prs: ['29564'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description: 'Add gray scale css tokens',
|
||||
prs: ['29543'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description:
|
||||
'Update CSS styling API using `[data-___]` instead of class names for props',
|
||||
prs: ['29560'],
|
||||
},
|
||||
{
|
||||
components: ['checkbox'],
|
||||
version: '0.3.0',
|
||||
description: 'Update `Checkbox` dark mode',
|
||||
prs: ['29544'],
|
||||
},
|
||||
{
|
||||
components: ['container'],
|
||||
version: '0.3.0',
|
||||
description: 'Update `Container` styles',
|
||||
prs: ['29475'],
|
||||
},
|
||||
{
|
||||
components: ['menu'],
|
||||
version: '0.3.0',
|
||||
description: 'Update `Menu` styles',
|
||||
prs: ['29351'],
|
||||
},
|
||||
{
|
||||
components: ['select'],
|
||||
version: '0.3.0',
|
||||
description: 'Fix `Select` styles on small sizes + with long option names',
|
||||
prs: ['29545'],
|
||||
},
|
||||
{
|
||||
components: ['link'],
|
||||
version: '0.3.0',
|
||||
description: 'Fix render prop on `Link`',
|
||||
prs: ['29247'],
|
||||
},
|
||||
{
|
||||
components: ['textfield', 'select'],
|
||||
version: '0.3.0',
|
||||
description: 'Remove `Field` from `TextField` + `Select`',
|
||||
prs: ['29482'],
|
||||
},
|
||||
{
|
||||
components: ['text', 'heading'],
|
||||
version: '0.3.0',
|
||||
description: 'Update `textDecoration` to `none` on `Text` / `Heading`',
|
||||
prs: ['29357'],
|
||||
},
|
||||
{
|
||||
components: ['text', 'heading'],
|
||||
version: '0.3.0',
|
||||
description: 'Update `textDecoration` to `none` on `Text` / `Heading`',
|
||||
prs: ['29357'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description: 'Docs - Use stories from Storybook for all examples in Nextjs',
|
||||
prs: ['29306'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description: 'Docs - Add release page (this one 🤗)',
|
||||
prs: ['29461'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description: 'Docs - Add docs for Menu, Link',
|
||||
prs: ['29576'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description: 'Fix CSS watch mode',
|
||||
prs: ['29352'],
|
||||
},
|
||||
{
|
||||
components: ['menu'],
|
||||
version: '0.2.0',
|
||||
description: 'New `Menu` component',
|
||||
prs: ['29151'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['button-icon'],
|
||||
version: '0.2.0',
|
||||
description: 'New `IconButton` component',
|
||||
prs: ['29239'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['scrollarea'],
|
||||
version: '0.2.0',
|
||||
description: 'New `ScrollArea` component',
|
||||
prs: ['29240'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['button', 'checkbox'],
|
||||
version: '0.2.0',
|
||||
description: 'Improve `Button` & `Checkbox` styles',
|
||||
prs: ['29127', '28789'],
|
||||
},
|
||||
{
|
||||
components: ['text'],
|
||||
version: '0.2.0',
|
||||
description: 'Improve `Text` styles',
|
||||
prs: ['29200'],
|
||||
},
|
||||
{
|
||||
components: ['icon'],
|
||||
version: '0.2.0',
|
||||
description: 'Renamed `CanonProvider` to `IconProvider`',
|
||||
prs: ['29002'],
|
||||
},
|
||||
{
|
||||
components: ['icon'],
|
||||
version: '0.2.0',
|
||||
description: 'Added about 40+ new icons',
|
||||
prs: ['29264'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.2.0',
|
||||
description: 'Simplified styling into a unique styles.css file',
|
||||
prs: ['29199'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.2.0',
|
||||
description: 'Added global styles to Backstage',
|
||||
prs: ['29137'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.2.0',
|
||||
description: 'Update global CSS tokens',
|
||||
prs: ['28804'],
|
||||
},
|
||||
{
|
||||
components: ['flex'],
|
||||
version: '0.2.0',
|
||||
description: 'Merge Stack + Inline into Flex',
|
||||
prs: ['28634'],
|
||||
},
|
||||
{
|
||||
components: ['button'],
|
||||
version: '0.2.0',
|
||||
description: 'Improve `Button` types',
|
||||
prs: ['29205'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.2.0',
|
||||
description: 'Move font weight and family back to each components',
|
||||
prs: ['28972'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.2.0',
|
||||
description: 'Fix custom values in spacing props',
|
||||
prs: ['28770'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.2.0',
|
||||
description: 'Multiple updates on the docs site',
|
||||
prs: ['28760'],
|
||||
},
|
||||
export const changelog = [
|
||||
...changelog_0_6_0,
|
||||
...changelog_0_5_0,
|
||||
...changelog_0_4_0,
|
||||
...changelog_0_3_0,
|
||||
...changelog_0_2_0,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import type { ChangelogProps } from '../types';
|
||||
|
||||
export const changelog_0_2_0: ChangelogProps[] = [
|
||||
{
|
||||
components: ['menu'],
|
||||
version: '0.2.0',
|
||||
description: 'New `Menu` component',
|
||||
prs: ['29151'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['button-icon'],
|
||||
version: '0.2.0',
|
||||
description: 'New `IconButton` component',
|
||||
prs: ['29239'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['scrollarea'],
|
||||
version: '0.2.0',
|
||||
description: 'New `ScrollArea` component',
|
||||
prs: ['29240'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['button', 'checkbox'],
|
||||
version: '0.2.0',
|
||||
description: 'Improve `Button` & `Checkbox` styles',
|
||||
prs: ['29127', '28789'],
|
||||
},
|
||||
{
|
||||
components: ['text'],
|
||||
version: '0.2.0',
|
||||
description: 'Improve `Text` styles',
|
||||
prs: ['29200'],
|
||||
},
|
||||
{
|
||||
components: ['icon'],
|
||||
version: '0.2.0',
|
||||
description: 'Renamed `CanonProvider` to `IconProvider`',
|
||||
prs: ['29002'],
|
||||
},
|
||||
{
|
||||
components: ['icon'],
|
||||
version: '0.2.0',
|
||||
description: 'Added about 40+ new icons',
|
||||
prs: ['29264'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.2.0',
|
||||
description: 'Simplified styling into a unique styles.css file',
|
||||
prs: ['29199'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.2.0',
|
||||
description: 'Added global styles to Backstage',
|
||||
prs: ['29137'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.2.0',
|
||||
description: 'Update global CSS tokens',
|
||||
prs: ['28804'],
|
||||
},
|
||||
{
|
||||
components: ['flex'],
|
||||
version: '0.2.0',
|
||||
description: 'Merge Stack + Inline into Flex',
|
||||
prs: ['28634'],
|
||||
},
|
||||
{
|
||||
components: ['button'],
|
||||
version: '0.2.0',
|
||||
description: 'Improve `Button` types',
|
||||
prs: ['29205'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.2.0',
|
||||
description: 'Move font weight and family back to each components',
|
||||
prs: ['28972'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.2.0',
|
||||
description: 'Fix custom values in spacing props',
|
||||
prs: ['28770'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.2.0',
|
||||
description: 'Multiple updates on the docs site',
|
||||
prs: ['28760'],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,139 @@
|
||||
import type { ChangelogProps } from '../types';
|
||||
|
||||
export const changelog_0_3_0: ChangelogProps[] = [
|
||||
{
|
||||
components: ['datatable'],
|
||||
version: '0.3.0',
|
||||
description: 'Add `DataTable` component',
|
||||
prs: ['29484', '29603'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['select'],
|
||||
version: '0.3.0',
|
||||
description: 'Add `Select` component',
|
||||
prs: ['29440'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['avatar'],
|
||||
version: '0.3.0',
|
||||
description: 'Add `Avatar` component',
|
||||
prs: ['29594'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['collapsible'],
|
||||
version: '0.3.0',
|
||||
description: 'Add `Collapsible` component',
|
||||
prs: ['29617'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.3.0',
|
||||
description: 'Add `TextField` component instead of `Field` + `Input`',
|
||||
prs: ['29364'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['datatable'],
|
||||
version: '0.3.0',
|
||||
description: 'Add `TableCellProfile`',
|
||||
prs: ['29600'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description: 'Add breakpoint hooks - `up()` and `down()`',
|
||||
prs: ['29564'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description: 'Add gray scale css tokens',
|
||||
prs: ['29543'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description:
|
||||
'Update CSS styling API using `[data-___]` instead of class names for props',
|
||||
prs: ['29560'],
|
||||
},
|
||||
{
|
||||
components: ['checkbox'],
|
||||
version: '0.3.0',
|
||||
description: 'Update `Checkbox` dark mode',
|
||||
prs: ['29544'],
|
||||
},
|
||||
{
|
||||
components: ['container'],
|
||||
version: '0.3.0',
|
||||
description: 'Update `Container` styles',
|
||||
prs: ['29475'],
|
||||
},
|
||||
{
|
||||
components: ['menu'],
|
||||
version: '0.3.0',
|
||||
description: 'Update `Menu` styles',
|
||||
prs: ['29351'],
|
||||
},
|
||||
{
|
||||
components: ['select'],
|
||||
version: '0.3.0',
|
||||
description: 'Fix `Select` styles on small sizes + with long option names',
|
||||
prs: ['29545'],
|
||||
},
|
||||
{
|
||||
components: ['link'],
|
||||
version: '0.3.0',
|
||||
description: 'Fix render prop on `Link`',
|
||||
prs: ['29247'],
|
||||
},
|
||||
{
|
||||
components: ['textfield', 'select'],
|
||||
version: '0.3.0',
|
||||
description: 'Remove `Field` from `TextField` + `Select`',
|
||||
prs: ['29482'],
|
||||
},
|
||||
{
|
||||
components: ['text', 'heading'],
|
||||
version: '0.3.0',
|
||||
description: 'Update `textDecoration` to `none` on `Text` / `Heading`',
|
||||
prs: ['29357'],
|
||||
},
|
||||
{
|
||||
components: ['text', 'heading'],
|
||||
version: '0.3.0',
|
||||
description: 'Update `textDecoration` to `none` on `Text` / `Heading`',
|
||||
prs: ['29357'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description: 'Docs - Use stories from Storybook for all examples in Nextjs',
|
||||
prs: ['29306'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description: 'Docs - Add release page (this one 🤗)',
|
||||
prs: ['29461'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description: 'Docs - Add docs for Menu, Link',
|
||||
prs: ['29576'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.3.0',
|
||||
description: 'Fix CSS watch mode',
|
||||
prs: ['29352'],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { ChangelogProps } from '../types';
|
||||
|
||||
export const changelog_0_4_0: ChangelogProps[] = [
|
||||
{
|
||||
components: ['tabs'],
|
||||
version: '0.4.0',
|
||||
description: 'New `Tabs` component',
|
||||
prs: ['29996'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['text', 'heading'],
|
||||
version: '0.4.0',
|
||||
description: 'Add `truncate` prop to `Text` and `Heading`',
|
||||
prs: ['29988'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['menu'],
|
||||
version: '0.4.0',
|
||||
description: 'Add combobox option to `Menu`',
|
||||
prs: ['29986'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.4.0',
|
||||
description: 'Add icon prop on `TextField`',
|
||||
prs: ['29820'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['button', 'button-icon'],
|
||||
version: '0.4.0',
|
||||
description: 'Improve icon props on `Button` and `IconButton`',
|
||||
prs: ['29667'],
|
||||
},
|
||||
{
|
||||
components: ['text', 'heading'],
|
||||
version: '0.4.0',
|
||||
description:
|
||||
'Improve the way we treat custom render on `Text` and `Heading`',
|
||||
prs: ['29989'],
|
||||
},
|
||||
{
|
||||
components: ['menu'],
|
||||
version: '0.4.0',
|
||||
description: 'Improve `Menu` styles',
|
||||
prs: ['29986'],
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.4.0',
|
||||
description: 'Improve `TextField` styles',
|
||||
prs: ['29974'],
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.4.0',
|
||||
description: 'Improve clear button on `TextField`',
|
||||
prs: ['29878'],
|
||||
},
|
||||
{
|
||||
components: ['flex'],
|
||||
version: '0.4.0',
|
||||
description: 'Fix spacing props on all layout components',
|
||||
prs: ['30013'],
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.4.0',
|
||||
description: 'Fix - Pin Base UI version',
|
||||
prs: ['29782'],
|
||||
},
|
||||
{
|
||||
components: ['select'],
|
||||
version: '0.4.0',
|
||||
description: 'Fix - Clicking `Select` label moves focus to trigger',
|
||||
prs: ['29755'],
|
||||
},
|
||||
{
|
||||
components: ['datatable'],
|
||||
version: '0.4.0',
|
||||
description: 'Fix `DataTable.Pagination` count issue',
|
||||
prs: ['29688'],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { ChangelogProps } from '../types';
|
||||
|
||||
export const changelog_0_5_0: ChangelogProps[] = [
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.5.0',
|
||||
description: '`TextField` now has multiple label sizes.',
|
||||
prs: ['30249'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.5.0',
|
||||
description:
|
||||
'`TextField` can now hide label and description while keeping them available for screen readers.',
|
||||
prs: ['30249'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.5.0',
|
||||
description:
|
||||
'We are removing the `render` prop in favour of the `as` prop on `Heading` and `Text`.',
|
||||
prs: ['30291'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.5.0',
|
||||
description: 'Move `TextField` component to use react Aria under the hood.',
|
||||
prs: ['30286'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['button', 'button-link'],
|
||||
version: '0.5.0',
|
||||
description:
|
||||
'Added a render prop to the `Button` component to use it as a link.',
|
||||
prs: ['30165'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['heading'],
|
||||
version: '0.5.0',
|
||||
description: 'Fix styling for the title4 prop on the Heading component.',
|
||||
prs: ['30167'],
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
components: ['flex'],
|
||||
version: '0.5.0',
|
||||
description:
|
||||
'Add `min-width: 0;` by default on Flex components to support truncated text.',
|
||||
prs: ['30168'],
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
components: ['button', 'button-link', 'button-icon'],
|
||||
version: '0.5.0',
|
||||
description:
|
||||
'`Button`, `ButtonLink`, `ButtonIcon` now default to size `small` instead of `medium`',
|
||||
prs: ['30085', '30097'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['grid'],
|
||||
version: '0.5.0',
|
||||
description: 'Rename Grid component to `<Grid.Root />`',
|
||||
prs: ['30013'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['flex', 'container', 'grid', 'box'],
|
||||
version: '0.5.0',
|
||||
description: 'Fixes spacing props on layout components',
|
||||
prs: ['30013'],
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
components: ['switch'],
|
||||
version: '0.5.0',
|
||||
description: 'New `Switch` component',
|
||||
prs: ['30251'],
|
||||
type: 'new',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,105 @@
|
||||
import type { ChangelogProps } from '../types';
|
||||
|
||||
export const changelog_0_6_0: ChangelogProps[] = [
|
||||
{
|
||||
components: ['tooltip'],
|
||||
version: '0.6.0',
|
||||
description: 'New `Tooltip` component.',
|
||||
prs: ['30461'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['card'],
|
||||
version: '0.6.0',
|
||||
description: 'New `Card` component.',
|
||||
prs: ['30467'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['header'],
|
||||
version: '0.6.0',
|
||||
description: 'New `Header` component.',
|
||||
prs: ['30410', '30459', '30493'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['button', 'button-link', 'button-icon'],
|
||||
version: '0.6.0',
|
||||
description: 'Improve `Button`, `ButtonIcon` and `ButtonLink` styling.',
|
||||
prs: ['30448'],
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
components: ['text', 'heading'],
|
||||
version: '0.6.0',
|
||||
description:
|
||||
'Update return types for `Heading` & `Text` components for React 19.',
|
||||
prs: ['30440'],
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
components: ['button', 'button-link', 'button-icon'],
|
||||
version: '0.6.0',
|
||||
description:
|
||||
'New `tertiary` variant to `Button`, `ButtonIcon` and `ButtonLink`.',
|
||||
prs: ['30453'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['skeleton'],
|
||||
version: '0.6.0',
|
||||
description: 'New `Skeleton` component.',
|
||||
prs: ['30466'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['button-icon'],
|
||||
version: '0.6.0',
|
||||
description: 'Rename `IconButton` to `ButtonIcon`.',
|
||||
prs: ['30297'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: ['button-link'],
|
||||
version: '0.6.0',
|
||||
description:
|
||||
'New `ButtonLink`, which replaces the previous render prop pattern on `Button` and `IconButton`.',
|
||||
prs: ['30297'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['button', 'button-link', 'button-icon'],
|
||||
version: '0.6.0',
|
||||
description: 'Remove the `render` prop from all button-related components.',
|
||||
prs: ['30297'],
|
||||
type: 'breaking',
|
||||
},
|
||||
{
|
||||
components: [],
|
||||
version: '0.6.0',
|
||||
description: 'We are consolidating all css files into a single styles.css.',
|
||||
prs: ['30325'],
|
||||
type: 'fix',
|
||||
},
|
||||
{
|
||||
components: ['searchfield'],
|
||||
version: '0.6.0',
|
||||
description: 'New `SearchField` component.',
|
||||
prs: ['30357'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['radio-group'],
|
||||
version: '0.6.0',
|
||||
description: 'New `RadioGroup` + `Radio` component.',
|
||||
prs: ['30327'],
|
||||
type: 'new',
|
||||
},
|
||||
{
|
||||
components: ['textfield'],
|
||||
version: '0.6.0',
|
||||
description: 'Added placeholder prop back to `TextField` component.',
|
||||
prs: ['30286'],
|
||||
type: 'fix',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,46 @@
|
||||
export type Component =
|
||||
| 'avatar'
|
||||
| 'box'
|
||||
| 'button'
|
||||
| 'button-link'
|
||||
| 'heading'
|
||||
| 'text'
|
||||
| 'button-icon'
|
||||
| 'icon'
|
||||
| 'tabs'
|
||||
| 'menu'
|
||||
| 'textfield'
|
||||
| 'datatable'
|
||||
| 'select'
|
||||
| 'collapsible'
|
||||
| 'accordion'
|
||||
| 'checkbox'
|
||||
| 'container'
|
||||
| 'link'
|
||||
| 'tooltip'
|
||||
| 'scrollarea'
|
||||
| 'flex'
|
||||
| 'switch'
|
||||
| 'grid'
|
||||
| 'searchfield'
|
||||
| 'radio-group'
|
||||
| 'card'
|
||||
| 'skeleton'
|
||||
| 'header'
|
||||
| 'header-page'
|
||||
| 'password-field'
|
||||
| 'table'
|
||||
| 'visually-hidden'
|
||||
| 'dialog'
|
||||
| 'tag-group';
|
||||
|
||||
export type Version = `${number}.${number}.${number}`;
|
||||
|
||||
export interface ChangelogProps {
|
||||
components: Component[];
|
||||
description: string;
|
||||
version: Version;
|
||||
prs: string[];
|
||||
type?: 'breaking' | 'new' | 'fix';
|
||||
commitSha?: string;
|
||||
}
|
||||
+148
-1
@@ -999,6 +999,130 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/auth-token@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "@octokit/auth-token@npm:6.0.0"
|
||||
checksum: 10/a30f5c4c984964b57193de5b6f67169f74e4779fedbe716157dd3558dd9de3ca5c105cae521b7bd8ce1ae180773a2ef01afe2306ad5a329f4fd291eba2b7c7d1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/core@npm:^7.0.6":
|
||||
version: 7.0.6
|
||||
resolution: "@octokit/core@npm:7.0.6"
|
||||
dependencies:
|
||||
"@octokit/auth-token": "npm:^6.0.0"
|
||||
"@octokit/graphql": "npm:^9.0.3"
|
||||
"@octokit/request": "npm:^10.0.6"
|
||||
"@octokit/request-error": "npm:^7.0.2"
|
||||
"@octokit/types": "npm:^16.0.0"
|
||||
before-after-hook: "npm:^4.0.0"
|
||||
universal-user-agent: "npm:^7.0.0"
|
||||
checksum: 10/852d41fc3150d2a891156427dd0575c77889f1c7a109894ee541594e3fd47c0d4e0a93fee22966c507dfd6158b522e42846c2ac46b9d896078194c95fa81f4ae
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/endpoint@npm:^11.0.2":
|
||||
version: 11.0.2
|
||||
resolution: "@octokit/endpoint@npm:11.0.2"
|
||||
dependencies:
|
||||
"@octokit/types": "npm:^16.0.0"
|
||||
universal-user-agent: "npm:^7.0.2"
|
||||
checksum: 10/0d088747baf94eafbba69da23ba840b40cd3f5d0bfbc51c692ff9d9d78de6d81f06366e6e30df8c1783355be826c27d38ab9ab0708396af8f430b06cfa29db35
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/graphql@npm:^9.0.3":
|
||||
version: 9.0.3
|
||||
resolution: "@octokit/graphql@npm:9.0.3"
|
||||
dependencies:
|
||||
"@octokit/request": "npm:^10.0.6"
|
||||
"@octokit/types": "npm:^16.0.0"
|
||||
universal-user-agent: "npm:^7.0.0"
|
||||
checksum: 10/7b16f281f8571dce55280b3986fbb8d15465a7236164a5f6497ded7597ff9ee95d5796924555b979903fe8c6706fe6be1b3e140d807297f85ac8edeadc28f9fe
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/openapi-types@npm:^27.0.0":
|
||||
version: 27.0.0
|
||||
resolution: "@octokit/openapi-types@npm:27.0.0"
|
||||
checksum: 10/5cd2cdf4e41fdf522e15e3d53f3ece8380d98dda9173a6fc905828fb2c33e8733d5f5d2a757ae3a572525f4749748e66cb40e7939372132988d8eb4ba978d54f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/plugin-paginate-rest@npm:^14.0.0":
|
||||
version: 14.0.0
|
||||
resolution: "@octokit/plugin-paginate-rest@npm:14.0.0"
|
||||
dependencies:
|
||||
"@octokit/types": "npm:^16.0.0"
|
||||
peerDependencies:
|
||||
"@octokit/core": ">=6"
|
||||
checksum: 10/57ddd857528dad9c02431bc6254c2374c06057872cf9656a4a88b162ebe1c2bc9f34fbec360f2ccff72c940f29b120758ce14e8135bd027223d381eb1b8b6579
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/plugin-request-log@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "@octokit/plugin-request-log@npm:6.0.0"
|
||||
peerDependencies:
|
||||
"@octokit/core": ">=6"
|
||||
checksum: 10/8a79973b1429bfead9113c4117f418aaef5ff368795daded3415ba14623d97d5fc08d1e822dbd566ecc9f041119e1a48a11853a9c48d9eb1caa62baa79c17f83
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods@npm:^17.0.0":
|
||||
version: 17.0.0
|
||||
resolution: "@octokit/plugin-rest-endpoint-methods@npm:17.0.0"
|
||||
dependencies:
|
||||
"@octokit/types": "npm:^16.0.0"
|
||||
peerDependencies:
|
||||
"@octokit/core": ">=6"
|
||||
checksum: 10/e9d9ad4d9755cc7fb82fdcbfa870ddea8a432180f0f76c8469095557fd1e26f8caea8cae58401209be17c4f3d8cc48c0e16a3643e37e48f4d23c39e058bf2c55
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/request-error@npm:^7.0.2":
|
||||
version: 7.0.2
|
||||
resolution: "@octokit/request-error@npm:7.0.2"
|
||||
dependencies:
|
||||
"@octokit/types": "npm:^16.0.0"
|
||||
checksum: 10/8edfaca9f5271115b090f470ebfe1006841ee152dd52cb39786e026236e2c13545aa3cf3187b7b1de717167696a42f91d8fa2f870bf9be0832fb2b11d11a741d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/request@npm:^10.0.6":
|
||||
version: 10.0.6
|
||||
resolution: "@octokit/request@npm:10.0.6"
|
||||
dependencies:
|
||||
"@octokit/endpoint": "npm:^11.0.2"
|
||||
"@octokit/request-error": "npm:^7.0.2"
|
||||
"@octokit/types": "npm:^16.0.0"
|
||||
fast-content-type-parse: "npm:^3.0.0"
|
||||
universal-user-agent: "npm:^7.0.2"
|
||||
checksum: 10/b8c5cd43d3225c8c3b803e889ff251808ae180c953ca92642522843123dafcd84d919b20ca2969b6e4080ee3c2197a99afda15b5649ee51d0715f8249a5c9ca4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/rest@npm:^22.0.1":
|
||||
version: 22.0.1
|
||||
resolution: "@octokit/rest@npm:22.0.1"
|
||||
dependencies:
|
||||
"@octokit/core": "npm:^7.0.6"
|
||||
"@octokit/plugin-paginate-rest": "npm:^14.0.0"
|
||||
"@octokit/plugin-request-log": "npm:^6.0.0"
|
||||
"@octokit/plugin-rest-endpoint-methods": "npm:^17.0.0"
|
||||
checksum: 10/ec2e94cfa8766716faeb3ca18527d9af746482d35aaa6e4265a30cb669ae3f31f4ebb6235edebe5ae62bc2cec2b8e88902584f698df2e7cabac3a15fd27da665
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@octokit/types@npm:^16.0.0":
|
||||
version: 16.0.0
|
||||
resolution: "@octokit/types@npm:16.0.0"
|
||||
dependencies:
|
||||
"@octokit/openapi-types": "npm:^27.0.0"
|
||||
checksum: 10/03d5cfc29556a9b53eae8beb1bf15c0b704dc722db2c51b53f093f3c3ee6c1d8e20b682be8117a3a17034b458be7746d1b22aaefb959ceb5152ad7589b39e2c9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@pkgjs/parseargs@npm:^0.11.0":
|
||||
version: 0.11.0
|
||||
resolution: "@pkgjs/parseargs@npm:0.11.0"
|
||||
@@ -1946,6 +2070,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"before-after-hook@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "before-after-hook@npm:4.0.0"
|
||||
checksum: 10/9fd52bc0c3cca0fb115e04dacbeeaacff38fa23e1af725d62392254c31ef433b15da60efcba61552e44d64e26f25ea259f72dba005115924389e88d2fd56e19f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"better-opn@npm:^3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "better-opn@npm:3.0.2"
|
||||
@@ -2385,6 +2516,7 @@ __metadata:
|
||||
"@mdx-js/loader": "npm:^3.1.0"
|
||||
"@mdx-js/react": "npm:^3.1.0"
|
||||
"@next/mdx": "npm:15.3.4"
|
||||
"@octokit/rest": "npm:^22.0.1"
|
||||
"@remixicon/react": "npm:^4.6.0"
|
||||
"@shikijs/transformers": "npm:^3.13.0"
|
||||
"@storybook/react": "npm:^8.6.12"
|
||||
@@ -2409,6 +2541,7 @@ __metadata:
|
||||
shiki: "npm:^3.13.0"
|
||||
storybook: "npm:^8.6.12"
|
||||
typescript: "npm:^5"
|
||||
unified: "npm:^11.0.4"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -3178,6 +3311,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-content-type-parse@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "fast-content-type-parse@npm:3.0.0"
|
||||
checksum: 10/8616a8aa6c9b4f8f4f3c90eaa4e7bfc2240cfa6f41f0eef5b5aa2b2c8b38bd9ad435f1488b6d817ffd725c54651e2777b882ae9dd59366e71e7896f1ec11d473
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
|
||||
version: 3.1.3
|
||||
resolution: "fast-deep-equal@npm:3.1.3"
|
||||
@@ -6763,7 +6903,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unified@npm:^11.0.0":
|
||||
"unified@npm:^11.0.0, unified@npm:^11.0.4":
|
||||
version: 11.0.5
|
||||
resolution: "unified@npm:11.0.5"
|
||||
dependencies:
|
||||
@@ -6864,6 +7004,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2":
|
||||
version: 7.0.3
|
||||
resolution: "universal-user-agent@npm:7.0.3"
|
||||
checksum: 10/c497e85f8b11eb8fa4dce584d7a39cc98710164959f494cafc3c269b51abb20fff269951838efd7424d15f6b3d001507f3cb8b52bb5676fdb642019dfd17e63e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unrs-resolver@npm:^1.6.2":
|
||||
version: 1.11.1
|
||||
resolution: "unrs-resolver@npm:1.11.1"
|
||||
|
||||
Reference in New Issue
Block a user