Skip to content

Commit d7be655

Browse files
wsochamatt-jb
andauthored
Sync BUIE changelog entries to box-mintlify (#744)
* Sync BUIE changelog entries to box-mintlify * Remove isSdkRelease property --------- Co-authored-by: Mateusz Jarzębowski-Bownik <85575391+matt-jb@users.noreply.github.com>
1 parent bd04078 commit d7be655

7 files changed

Lines changed: 97 additions & 26 deletions

File tree

.github/workflows/sync-mintlify.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,15 @@ jobs:
7373
ENTRY_COUNT="$(jq '.entries | length' "$OUTPUT_JSON")"
7474
echo "entry_count=${ENTRY_COUNT}" >> "$GITHUB_OUTPUT"
7575
if [ "$ENTRY_COUNT" -eq 0 ]; then
76-
echo "No eligible SDK changes detected, skipping commit and PR creation."
77-
echo "sync_target=no eligible SDK changes" >> "$GITHUB_OUTPUT"
76+
echo "No eligible changelog entries detected, skipping commit and PR creation."
77+
echo "sync_target=no eligible changelog entries" >> "$GITHUB_OUTPUT"
7878
echo "pr_url=" >> "$GITHUB_OUTPUT"
7979
exit 0
8080
fi
8181
8282
BRANCH_SUFFIX="$(jq -r '.branchSuffix' "$OUTPUT_JSON")"
8383
PR_TITLE="$(jq -r '.prTitle' "$OUTPUT_JSON")"
84-
SYNC_TARGET="$(jq -r 'if (.entries | length) == 1 then "\(.entries[0].repoDisplayName) \(.entries[0].version)" else "\(.entries | length) SDK releases" end' "$OUTPUT_JSON")"
84+
SYNC_TARGET="$(jq -r 'if (.entries | length) == 1 then "\(.entries[0].repoDisplayName) \(.entries[0].version)" else "\(.entries | length) release changelog entries" end' "$OUTPUT_JSON")"
8585
SUMMARY_LINES="$(jq -r '.entries[] | "- \(.repoDisplayName) \(.version) ([Release](\(.releaseUrl)))"' "$OUTPUT_JSON")"
8686
FILE_LINES="$(jq -r '.entries[] | "- `\(.filePath)` (new or updated snippet)"' "$OUTPUT_JSON")"
8787

code/src/MintlifySync/changelog.js

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const fs = require('fs-extra')
55
const yaml = require('js-yaml')
66

77
const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n*/
8-
const SDK_RELEASE_HEADING_REGEX = /^#\s+(.+?)\s+`([^`]+)`\s+released\s*$/m
8+
const RELEASE_HEADING_REGEX = /^#\s+(.+?)\s+`([^`]+)`\s+released\s*$/m
99

1010
function normalizeContentPaths(contentPaths) {
1111
if (Array.isArray(contentPaths)) {
@@ -59,7 +59,7 @@ async function loadEligibleChangelogEntries({ repoPath = process.cwd(), contentP
5959

6060
const content = await fs.readFile(absolutePath, 'utf8')
6161
const entry = parseChangelogEntry({ content, contentPath })
62-
if (entry.isSdkRelease) {
62+
if (entry.isMintlifySyncEligible) {
6363
entries.push(entry)
6464
}
6565
}
@@ -79,7 +79,7 @@ function parseChangelogEntry({ content, contentPath = '' } = {}) {
7979

8080
const frontmatter = yaml.load(frontmatterMatch[1]) || {}
8181
const markdown = content.slice(frontmatterMatch[0].length)
82-
const headingMatch = markdown.match(SDK_RELEASE_HEADING_REGEX)
82+
const headingMatch = markdown.match(RELEASE_HEADING_REGEX)
8383
const repoDisplayName = headingMatch ? headingMatch[1].trim() : ''
8484
const version = headingMatch ? headingMatch[2].trim() : ''
8585
const body = headingMatch
@@ -98,25 +98,36 @@ function parseChangelogEntry({ content, contentPath = '' } = {}) {
9898
repoDisplayName,
9999
version
100100
}
101-
102101
return {
103102
...entry,
104-
isSdkRelease: isSdkReleaseEntry(entry)
103+
isMintlifySyncEligible: isMintlifySyncEligibleEntry(entry)
105104
}
106105
}
107106

108-
function isSdkReleaseEntry(entry = {}) {
109-
const labels = Array.isArray(entry.labels) ? entry.labels.map((label) => label.toLowerCase()) : []
107+
function isMintlifySyncEligibleEntry(entry = {}) {
108+
const labels = normalizeLabels(entry.labels)
109+
110+
return (
111+
(labels.includes('sdks') || labels.includes('ui-elements')) &&
112+
hasRequiredReleaseFields(entry)
113+
)
114+
}
110115

116+
function hasRequiredReleaseFields(entry = {}) {
111117
return (
112-
labels.includes('sdks') &&
113118
typeof entry.repoDisplayName === 'string' &&
114119
entry.repoDisplayName.startsWith('Box ') &&
115120
Boolean(entry.version) &&
116121
Boolean(entry.releaseSourceUrl)
117122
)
118123
}
119124

125+
function normalizeLabels(labels = []) {
126+
return Array.isArray(labels)
127+
? labels.map((label) => String(label).trim().toLowerCase()).filter(Boolean)
128+
: []
129+
}
130+
120131
function sortEntriesForInsertion(entries = []) {
121132
return [...entries].sort((left, right) => {
122133
const leftKey = `${left.appliedAt || ''}:${left.contentPath || ''}`
@@ -142,6 +153,7 @@ function isZeroSha(value) {
142153
}
143154

144155
module.exports = {
156+
isMintlifySyncEligibleEntry,
145157
loadEligibleChangelogEntries,
146158
normalizeContentPaths,
147159
parseChangelogEntry,

code/src/MintlifySync/converter.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ function mapLabelsToTags({ labels, repoDisplayName }) {
6767
if (lower === 'ios' || (lower === 'swift' && String(repoDisplayName).toLowerCase().includes('ios'))) {
6868
return 'iOS'
6969
}
70+
if (lower === 'ui-elements') {
71+
return 'UI Elements'
72+
}
7073
return lower.charAt(0).toUpperCase() + lower.slice(1)
7174
})
7275
}

code/src/MintlifySync/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ async function runMintlifySync() {
3232
repoPath: changelogRepoPath
3333
})
3434

35-
console.log(`[MintlifySync] Eligible SDK entries: ${changelogEntries.length}`)
35+
console.log(`[MintlifySync] Eligible release entries: ${changelogEntries.length}`)
3636
if (changelogEntries.length === 0) {
3737
const output = buildWorkflowOutput([])
3838
console.log(`[MintlifySync] Writing workflow output: ${workflowOutputPath}`)
3939
await fs.outputFile(workflowOutputPath, JSON.stringify(output, null, 2))
40-
console.log('[MintlifySync] No eligible SDK changelog entries found.')
40+
console.log('[MintlifySync] No eligible changelog entries found.')
4141
return output
4242
}
4343

@@ -100,7 +100,7 @@ function buildWorkflowOutput(entries = []) {
100100

101101
const prTitle = normalizedEntries.length === 1
102102
? `Add changelog: ${normalizedEntries[0].repoDisplayName} ${normalizedEntries[0].version}`
103-
: `Add changelog entries: ${normalizedEntries.length} SDK releases`
103+
: `Add changelog entries: ${normalizedEntries.length} releases`
104104

105105
return {
106106
branchSuffix: normalizedEntries.length === 1

code/tests/mintlifySync/changelog.test.js

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,47 @@ async function readChangelog(contentPath) {
1414
}
1515

1616
describe('Mintlify changelog parsing', () => {
17-
test('parses real merged SDK changelog files', async () => {
17+
test('parses real merged release changelog files', async () => {
1818
const cases = [
1919
{
2020
contentPath: 'content/2026/04-01-box-java-sdk-v1070-released.md',
21+
appliedAt: '2026-04-01',
2122
repoDisplayName: 'Box Java SDK',
2223
version: 'v10.7.0',
23-
labels: ['sdks', 'java']
24+
labels: ['sdks', 'java'],
25+
isMintlifySyncEligible: true
2426
},
2527
{
2628
contentPath: 'content/2026/04-01-box-java-sdk-v560-released.md',
29+
appliedAt: '2026-04-01',
2730
repoDisplayName: 'Box Java SDK',
2831
version: 'v5.6.0',
29-
labels: ['sdks', 'java']
32+
labels: ['sdks', 'java'],
33+
isMintlifySyncEligible: true
3034
},
3135
{
3236
contentPath: 'content/2026/04-01-box-ios-sdk-1060-released.md',
37+
appliedAt: '2026-04-01',
3338
repoDisplayName: 'Box iOS SDK',
3439
version: '10.6.0',
35-
labels: ['sdks', 'swift']
40+
labels: ['sdks', 'swift'],
41+
isMintlifySyncEligible: true
3642
},
3743
{
3844
contentPath: 'content/2026/04-01-box-windows-sdk-v1080-released.md',
45+
appliedAt: '2026-04-01',
3946
repoDisplayName: 'Box Windows SDK',
4047
version: 'v10.8.0',
41-
labels: ['sdks', 'dotnet']
48+
labels: ['sdks', 'dotnet'],
49+
isMintlifySyncEligible: true
50+
},
51+
{
52+
contentPath: 'content/2026/01-05-box-ui-elements-v2600-released.md',
53+
appliedAt: '2026-01-05',
54+
repoDisplayName: 'Box UI Elements',
55+
version: 'v26.0.0',
56+
labels: ['frontend', 'ui-elements'],
57+
isMintlifySyncEligible: true
4258
}
4359
]
4460

@@ -49,27 +65,29 @@ describe('Mintlify changelog parsing', () => {
4965
contentPath: testCase.contentPath
5066
})
5167

52-
expect(entry.isSdkRelease).toBeTruthy()
53-
expect(entry.appliedAt).toBe('2026-04-01')
68+
expect(entry.isMintlifySyncEligible).toBe(testCase.isMintlifySyncEligible)
69+
expect(entry.appliedAt).toBe(testCase.appliedAt)
5470
expect(entry.repoDisplayName).toBe(testCase.repoDisplayName)
5571
expect(entry.version).toBe(testCase.version)
5672
expect(entry.labels).toEqual(testCase.labels)
5773
expect(entry.releaseSourceUrl).toContain('/releases/tag/')
58-
expect(entry.body.startsWith('###')).toBeTruthy()
74+
expect(entry.body).toContain('###')
5975
}
6076
})
6177

62-
test('skips non-SDK changelog posts even when they are in the candidate list', async () => {
78+
test('loads eligible release entries and skips non-release posts', async () => {
6379
const entries = await loadEligibleChangelogEntries({
6480
repoPath: REPO_ROOT,
6581
contentPaths: [
82+
'content/2026/01-05-box-ui-elements-v2600-released.md',
6683
'content/2026/04-01-box-java-sdk-v1070-released.md',
6784
'content/2026/04-02-new-ai-models.md',
6885
'content/2026/04-01-box-ios-sdk-1060-released.md'
6986
]
7087
})
7188

7289
expect(entries.map((entry) => entry.contentPath)).toEqual([
90+
'content/2026/01-05-box-ui-elements-v2600-released.md',
7391
'content/2026/04-01-box-ios-sdk-1060-released.md',
7492
'content/2026/04-01-box-java-sdk-v1070-released.md'
7593
])

code/tests/mintlifySync/converter.test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,16 @@ describe('convertReleaseToMintlifyEntry', () => {
116116
'snippets/changelog/2026/04-01-box-windows-sdk-v1080-released.mdx'
117117
)
118118
})
119+
120+
test('converts real Box UI Elements changelog entries', async () => {
121+
const entry = await readParsedEntry('content/2026/01-05-box-ui-elements-v2600-released.md')
122+
const result = convertReleaseToMintlifyEntry(entry)
123+
124+
expect(result.componentName).toBe('BoxUiElementsV2600Released_2026_01_05')
125+
expect(result.filePath).toBe(
126+
'snippets/changelog/2026/01-05-box-ui-elements-v2600-released.mdx'
127+
)
128+
expect(result.mdxContent).toContain('tags={["Frontend","UI Elements"]}')
129+
expect(result.mdxContent).toContain('## Box UI Elements `v26.0.0` released')
130+
})
119131
})

code/tests/mintlifySync/index.test.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,33 @@ describe('runMintlifySync', () => {
6060
expect(await fs.readJson(outputPath)).toEqual(output)
6161
})
6262

63-
test('handles multiple SDK changelog files in a single run and ignores non-SDK posts', async () => {
63+
test('writes Mintlify files for a real Box UI Elements changelog file', async () => {
64+
const outputPath = path.join(tempDir, 'workflow-output-buie.json')
65+
process.env.CHANGELOG_REPO_PATH = REPO_ROOT
66+
process.env.CONTENT_PATHS = 'content/2026/01-05-box-ui-elements-v2600-released.md'
67+
process.env.MINTLIFY_REPO_PATH = tempDir
68+
process.env.MINTLIFY_SYNC_OUTPUT_PATH = outputPath
69+
70+
const output = await runMintlifySync()
71+
const snippetPath = path.join(
72+
tempDir,
73+
'snippets/changelog/2026/01-05-box-ui-elements-v2600-released.mdx'
74+
)
75+
const indexContent = await fs.readFile(path.join(tempDir, 'changelog/index.mdx'), 'utf8')
76+
const snippetContent = await fs.readFile(snippetPath, 'utf8')
77+
78+
expect(output.entries).toHaveLength(1)
79+
expect(output.prTitle).toBe('Add changelog: Box UI Elements v26.0.0')
80+
expect(snippetContent).toContain('## Box UI Elements `v26.0.0` released')
81+
expect(snippetContent).toContain('tags={["Frontend","UI Elements"]}')
82+
expect(indexContent).toContain(
83+
'import BoxUiElementsV2600Released_2026_01_05 from "/snippets/changelog/2026/01-05-box-ui-elements-v2600-released.mdx";'
84+
)
85+
expect(indexContent).toContain('<BoxUiElementsV2600Released_2026_01_05 />')
86+
expect(await fs.readJson(outputPath)).toEqual(output)
87+
})
88+
89+
test('handles multiple release changelog files in a single run and ignores non-release posts', async () => {
6490
process.env.CHANGELOG_REPO_PATH = REPO_ROOT
6591
process.env.CONTENT_PATHS = [
6692
'content/2026/04-02-new-ai-models.md',
@@ -74,7 +100,7 @@ describe('runMintlifySync', () => {
74100
const indexContent = await fs.readFile(path.join(tempDir, 'changelog/index.mdx'), 'utf8')
75101

76102
expect(output.entries).toHaveLength(2)
77-
expect(output.prTitle).toBe('Add changelog entries: 2 SDK releases')
103+
expect(output.prTitle).toBe('Add changelog entries: 2 releases')
78104
expect(output.filePaths).toEqual([
79105
'snippets/changelog/2026/04-01-box-ios-sdk-v1060-released.mdx',
80106
'snippets/changelog/2026/04-01-box-windows-sdk-v1080-released.mdx'
@@ -84,7 +110,7 @@ describe('runMintlifySync', () => {
84110
expect(indexContent).not.toContain('new-ai-models')
85111
})
86112

87-
test('returns a no-op workflow output when there are no eligible SDK entries', async () => {
113+
test('returns a no-op workflow output when there are no eligible release entries', async () => {
88114
process.env.CHANGELOG_REPO_PATH = REPO_ROOT
89115
process.env.CONTENT_PATHS = 'content/2026/04-02-new-ai-models.md'
90116
process.env.MINTLIFY_REPO_PATH = tempDir

0 commit comments

Comments
 (0)