From 0ac80ae88c6da6d87a547e07af7171f4535a2e38 Mon Sep 17 00:00:00 2001 From: Abigail Cho Date: Wed, 1 Jul 2026 10:18:58 -0400 Subject: [PATCH 1/2] add workflow to update customer documentation changelog --- .github/workflows/cd.yaml | 231 +++++++++++++++++++++++++++++++++++++- 1 file changed, 230 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 022a50c7..b96021d6 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -73,4 +73,233 @@ jobs: GPG_KEY_NAME: ${{ vars.GPG_KEY_NAME }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} OSSRH_JIRA_USERNAME: ${{ secrets.OSSRH_JIRA_USERNAME }} - OSSRH_JIRA_PASSWORD: ${{ secrets.OSSRH_JIRA_PASSWORD }} \ No newline at end of file + OSSRH_JIRA_PASSWORD: ${{ secrets.OSSRH_JIRA_PASSWORD }} + + update_docs_changelog: + runs-on: ubuntu-latest + name: Update changelog in customer documentation + needs: + - extract_version + env: + SOURCE_FILE: CHANGELOG.md + DOCS_REPO_PATH: docs + CHANGELOG_DEST_DIR: fern/docs/pages/applications/Server-Side Integration/java/java-changelog + steps: + - name: Checkout source repository + uses: actions/checkout@v6 + with: + fetch-depth: 2 + + - name: Generate token + id: generate-token + uses: actions/create-github-app-token@v2 + with: + # App ID and private key PX docs app + app-id: ${{ vars.HS_DOCS_UPDATER_APP_ID }} + private-key: ${{ secrets.HS_DOCS_UPDATER_APP_PRIVATE_KEY }} + repositories: public-docs + + - name: Clone public-docs + uses: actions/checkout@v6 + with: + repository: PerimeterX/public-docs + path: ${{ env.DOCS_REPO_PATH }} + token: ${{ steps.generate-token.outputs.token }} + ref: main + + - name: Check if CHANGELOG.md was modified + id: check_changelog + run: | + BEFORE_SHA="${{ github.event.before }}" + AFTER_SHA="${{ github.event.after }}" + + if [[ "$BEFORE_SHA" == "0000000000000000000000000000000000000000" ]] || [[ -z "$BEFORE_SHA" ]]; then + echo "New branch or missing before SHA, checking if CHANGELOG.md exists" + if [ -f "CHANGELOG.md" ]; then + echo "changelog_modified=true" >> "$GITHUB_OUTPUT" + else + echo "changelog_modified=false" >> "$GITHUB_OUTPUT" + fi + else + if git diff --name-only "$BEFORE_SHA" "$AFTER_SHA" | grep -q "^CHANGELOG.md$"; then + echo "changelog_modified=true" >> "$GITHUB_OUTPUT" + else + echo "changelog_modified=false" >> "$GITHUB_OUTPUT" + fi + fi + + - name: Extract and create changelog entry + if: steps.check_changelog.outputs.changelog_modified == 'true' + uses: actions/github-script@v8 + with: + script: | + const fs = require('fs'); + const path = require('path'); + + const version = '${{ needs.extract_version.outputs.version }}'; + + // Read the CHANGELOG.md file + const changelogContent = fs.readFileSync('${{ env.SOURCE_FILE }}', 'utf8'); + + // Parse all changelog entries + const lines = changelogContent.split('\n'); + const entries = []; + let currentEntry = null; + + // Expected format: ## [vVERSION](url) (YYYY-MM-DD) + // e.g., ## [v6.17.1](https://github.com/...) (2026-06-29) + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const match = line.match(/^## \[([^\]]+)\]\([^)]*\) \((\d{4}-\d{2}-\d{2})\)/); + + if (match) { + // Save previous entry if exists + if (currentEntry) { + entries.push(currentEntry); + } + + // Start new entry + currentEntry = { + version: match[1], + date: match[2], + content: [] + }; + } else if (currentEntry) { + // Add line to current entry content + currentEntry.content.push(line); + } + } + + if (currentEntry) { + entries.push(currentEntry); + } + + if (entries.length === 0) { + core.setFailed('No changelog entries found'); + return; + } + + core.info(`Found ${entries.length} changelog entries`); + + const normalizeVersion = (v) => v.replace(/^v/i, ''); + + // Find the entry matching the extracted version + const matchingEntry = entries.find( + entry => normalizeVersion(entry.version) === normalizeVersion(version) + ); + + if (!matchingEntry) { + core.setFailed(`No changelog entry found for version ${version}`); + return; + } + + core.info(`Processing changelog entry for version ${version} dated ${matchingEntry.date}`); + + // Get the content + const content = matchingEntry.content.join('\n').trim(); + + const versionHeader = `Version ${version}`; + + // Create the .mdx file content + const mdxContent = `## ${versionHeader}\n\n${content}\n`; + + // Write the file with DATE.mdx format + const versionEntry = `## ${versionHeader}\n\n${content}\n`; + + const filename = `${matchingEntry.date}.mdx`; + const filepath = path.join(process.env.DOCS_REPO_PATH, process.env.CHANGELOG_DEST_DIR, filename); + + // Ensure directory exists before writing file + const dirPath = path.dirname(filepath); + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + core.info(`Created directory: ${dirPath}`); + } + + let finalContent; + if (fs.existsSync(filepath)) { + // File exists - check if this version is already in the file + const existingContent = fs.readFileSync(filepath, 'utf8'); + + // Check if this exact version header already exists in the file + const versionHeaderRegex = new RegExp(`^## ${versionHeader.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm'); + if (versionHeaderRegex.test(existingContent)) { + core.info(`Version ${version} already exists in ${filename}, skipping...`); + return; + } + + // Prepend new version to existing content (newer versions first) + finalContent = versionEntry + '\n' + existingContent; + core.info(`Prepending version ${version} to existing file ${filename}`); + } else { + // New file - just use the version entry + finalContent = versionEntry; + core.info(`Creating new file ${filename} with version ${version}`); + } + + fs.writeFileSync(filepath, finalContent); + + core.info(`Created/updated changelog file: ${filepath}`); + core.info(`Version: ${version}, Date: ${matchingEntry.date}`); + + - name: Commit and push changes to public-docs + id: commit_push_changes + run: | + cd ${{ env.DOCS_REPO_PATH }} + + if git status --porcelain | grep -q .; then + echo "Differences found. Committing and pushing..." + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Add changelog directory if it exists + if [ -d "${{ env.CHANGELOG_DEST_DIR }}" ]; then + git add "${{ env.CHANGELOG_DEST_DIR }}" + fi + + BRANCH_NAME="sync/changelog-update-${{ github.sha }}" + git checkout -b $BRANCH_NAME + + git commit -m "Docs Sync: Automated changelog update from source commit ${{ github.sha }}" + + REMOTE_URL="https://x-access-token:${{ steps.generate-token.outputs.token }}@github.com/PerimeterX/public-docs.git" + git push origin $BRANCH_NAME + + echo "pushed_branch=${BRANCH_NAME}" >> "$GITHUB_OUTPUT" + else + echo "No differences found after updates. No commit necessary." + fi + + - name: Create PR in public-docs + # Only run if a new branch was successfully pushed + if: steps.commit_push_changes.outputs.pushed_branch != '' + uses: actions/github-script@v8 + with: + github-token: ${{ steps.generate-token.outputs.token }} + script: | + const branchName = '${{ steps.commit_push_changes.outputs.pushed_branch }}'; + const sourceRepo = process.env.GITHUB_REPOSITORY; + const sourceSha = process.env.GITHUB_SHA; + + try { + const response = await github.rest.pulls.create({ + owner: 'PerimeterX', + repo: 'public-docs', + title: 'Automated changelog update', + head: branchName, + base: 'main', + body: `Automated changelog update. + + Source Commit: \`${sourceRepo}@${sourceSha}\` + Source File: \`${process.env.SOURCE_FILE}\` + Changelog Directory: \`${{ env.CHANGELOG_DEST_DIR }}\` + + Please review the changes and merge.`, + }); + console.log(`Pull Request created: ${response.data.html_url}`); + } + catch (error) { + console.error('Failed to create PR:', error.message); + core.setFailed(`PR creation failed: ${error.message}`); + } \ No newline at end of file From bea5cb2529154acaf3efdb6c38a5e77dbd7cac92 Mon Sep 17 00:00:00 2001 From: Abigail Cho Date: Wed, 1 Jul 2026 11:18:04 -0400 Subject: [PATCH 2/2] optimizations --- .github/workflows/cd.yaml | 132 +++++++++++++------------------------- 1 file changed, 45 insertions(+), 87 deletions(-) diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index b96021d6..f07de38c 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -90,34 +90,22 @@ jobs: with: fetch-depth: 2 - - name: Generate token - id: generate-token - uses: actions/create-github-app-token@v2 - with: - # App ID and private key PX docs app - app-id: ${{ vars.HS_DOCS_UPDATER_APP_ID }} - private-key: ${{ secrets.HS_DOCS_UPDATER_APP_PRIVATE_KEY }} - repositories: public-docs - - - name: Clone public-docs - uses: actions/checkout@v6 - with: - repository: PerimeterX/public-docs - path: ${{ env.DOCS_REPO_PATH }} - token: ${{ steps.generate-token.outputs.token }} - ref: main - - name: Check if CHANGELOG.md was modified id: check_changelog run: | BEFORE_SHA="${{ github.event.before }}" AFTER_SHA="${{ github.event.after }}" - + if [[ "$BEFORE_SHA" == "0000000000000000000000000000000000000000" ]] || [[ -z "$BEFORE_SHA" ]]; then - echo "New branch or missing before SHA, checking if CHANGELOG.md exists" - if [ -f "CHANGELOG.md" ]; then - echo "changelog_modified=true" >> "$GITHUB_OUTPUT" + echo "New branch or missing before SHA, checking parent commit diff" + if git rev-parse HEAD~1 >/dev/null 2>&1; then + if git diff --name-only HEAD~1 HEAD | grep -q "^CHANGELOG.md$"; then + echo "changelog_modified=true" >> "$GITHUB_OUTPUT" + else + echo "changelog_modified=false" >> "$GITHUB_OUTPUT" + fi else + echo "No parent commit available, skipping changelog sync" echo "changelog_modified=false" >> "$GITHUB_OUTPUT" fi else @@ -128,6 +116,25 @@ jobs: fi fi + - name: Generate token + if: steps.check_changelog.outputs.changelog_modified == 'true' + id: generate-token + uses: actions/create-github-app-token@v2 + with: + # App ID and private key PX docs app + app-id: ${{ vars.HS_DOCS_UPDATER_APP_ID }} + private-key: ${{ secrets.HS_DOCS_UPDATER_APP_PRIVATE_KEY }} + repositories: public-docs + + - name: Clone public-docs + if: steps.check_changelog.outputs.changelog_modified == 'true' + uses: actions/checkout@v6 + with: + repository: PerimeterX/public-docs + path: ${{ env.DOCS_REPO_PATH }} + token: ${{ steps.generate-token.outputs.token }} + ref: main + - name: Extract and create changelog entry if: steps.check_changelog.outputs.changelog_modified == 'true' uses: actions/github-script@v8 @@ -138,83 +145,33 @@ jobs: const version = '${{ needs.extract_version.outputs.version }}'; - // Read the CHANGELOG.md file const changelogContent = fs.readFileSync('${{ env.SOURCE_FILE }}', 'utf8'); - // Parse all changelog entries - const lines = changelogContent.split('\n'); - const entries = []; - let currentEntry = null; - // Expected format: ## [vVERSION](url) (YYYY-MM-DD) - // e.g., ## [v6.17.1](https://github.com/...) (2026-06-29) - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const match = line.match(/^## \[([^\]]+)\]\([^)]*\) \((\d{4}-\d{2}-\d{2})\)/); - - if (match) { - // Save previous entry if exists - if (currentEntry) { - entries.push(currentEntry); - } - - // Start new entry - currentEntry = { - version: match[1], - date: match[2], - content: [] - }; - } else if (currentEntry) { - // Add line to current entry content - currentEntry.content.push(line); - } - } - - if (currentEntry) { - entries.push(currentEntry); - } - - if (entries.length === 0) { - core.setFailed('No changelog entries found'); - return; - } - - core.info(`Found ${entries.length} changelog entries`); - - const normalizeVersion = (v) => v.replace(/^v/i, ''); - - // Find the entry matching the extracted version - const matchingEntry = entries.find( - entry => normalizeVersion(entry.version) === normalizeVersion(version) + // e.g., ## [v6.17.1](https://github.com/PerimeterX/perimeterx-java-sdk/compare/v6.17.1...HEAD) (2026-06-29) + const escapedVersion = version.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + // Do not use the multiline flag here: with /m, $ matches every line end and + // the lazy capture stops after the first blank line, omitting entry content. + const entryMatch = changelogContent.match( + new RegExp(`(?:^|\\n)## \\[v?${escapedVersion}\\]\\([^)]*\\) \\((\\d{4}-\\d{2}-\\d{2})\\)\\n([\\s\\S]*?)(?=\\n## \\[|$)`) ); - if (!matchingEntry) { + if (!entryMatch) { core.setFailed(`No changelog entry found for version ${version}`); return; } - core.info(`Processing changelog entry for version ${version} dated ${matchingEntry.date}`); - - // Get the content - const content = matchingEntry.content.join('\n').trim(); + const date = entryMatch[1]; + const content = entryMatch[2].trim(); - const versionHeader = `Version ${version}`; - - // Create the .mdx file content - const mdxContent = `## ${versionHeader}\n\n${content}\n`; + core.info(`Processing changelog entry for version ${version} dated ${date}`); - // Write the file with DATE.mdx format - const versionEntry = `## ${versionHeader}\n\n${content}\n`; + const versionEntry = `## Version ${version}\n\n${content}\n`; - const filename = `${matchingEntry.date}.mdx`; + const filename = `${date}.mdx`; const filepath = path.join(process.env.DOCS_REPO_PATH, process.env.CHANGELOG_DEST_DIR, filename); - // Ensure directory exists before writing file - const dirPath = path.dirname(filepath); - if (!fs.existsSync(dirPath)) { - fs.mkdirSync(dirPath, { recursive: true }); - core.info(`Created directory: ${dirPath}`); - } + fs.mkdirSync(path.dirname(filepath), { recursive: true }); let finalContent; if (fs.existsSync(filepath)) { @@ -222,7 +179,7 @@ jobs: const existingContent = fs.readFileSync(filepath, 'utf8'); // Check if this exact version header already exists in the file - const versionHeaderRegex = new RegExp(`^## ${versionHeader.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'm'); + const versionHeaderRegex = new RegExp(`^## Version ${escapedVersion}$`, 'm'); if (versionHeaderRegex.test(existingContent)) { core.info(`Version ${version} already exists in ${filename}, skipping...`); return; @@ -240,9 +197,10 @@ jobs: fs.writeFileSync(filepath, finalContent); core.info(`Created/updated changelog file: ${filepath}`); - core.info(`Version: ${version}, Date: ${matchingEntry.date}`); + core.info(`Version: ${version}, Date: ${date}`); - - name: Commit and push changes to public-docs + - name: Commit and push changes to public-docs repo + if: steps.check_changelog.outputs.changelog_modified == 'true' id: commit_push_changes run: | cd ${{ env.DOCS_REPO_PATH }}