diff --git a/.github/workflows/sync-client-library-release-notes.yml b/.github/workflows/sync-client-library-release-notes.yml index c6f6609879..59068163d1 100644 --- a/.github/workflows/sync-client-library-release-notes.yml +++ b/.github/workflows/sync-client-library-release-notes.yml @@ -28,12 +28,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout docs-v2 - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: path: docs-v2 - name: Checkout influxdb3-python - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: InfluxCommunity/influxdb3-python path: sources/influxdb3-python @@ -41,7 +41,7 @@ jobs: CHANGELOG.md - name: Checkout influxdb3-go - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: InfluxCommunity/influxdb3-go path: sources/influxdb3-go @@ -49,7 +49,7 @@ jobs: CHANGELOG.md - name: Checkout influxdb3-js - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: InfluxCommunity/influxdb3-js path: sources/influxdb3-js @@ -57,7 +57,7 @@ jobs: CHANGELOG.md - name: Checkout influxdb3-csharp - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: InfluxCommunity/influxdb3-csharp path: sources/influxdb3-csharp @@ -65,7 +65,7 @@ jobs: CHANGELOG.md - name: Checkout influxdb3-java - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: InfluxCommunity/influxdb3-java path: sources/influxdb3-java @@ -73,9 +73,9 @@ jobs: CHANGELOG.md - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: '22' - name: Run sync id: sync @@ -99,28 +99,55 @@ jobs: --docs-root "$GITHUB_WORKSPACE/docs-v2" fi + - name: Compose PR body + id: body + env: + NEEDS_ATTENTION: ${{ steps.sync.outputs.needs_attention }} + SUMMARY: ${{ steps.sync.outputs.summary }} + run: | + BODY_FILE="$RUNNER_TEMP/pr-body.md" + { + if [ "$NEEDS_ATTENTION" = "true" ]; then + echo '> [!Warning]' + echo '> One or more client libraries were skipped or failed to parse.' + echo '> Investigate upstream CHANGELOG changes (rename, format change)' + echo '> before merging — assigned to @Copilot for triage.' + echo + fi + echo 'Automated sync from the v3 client library CHANGELOGs.' + echo + echo '## Sync results' + echo + printf '%s\n' "$SUMMARY" + echo + echo '## Sources' + echo + echo '- https://github.com/InfluxCommunity/influxdb3-python/blob/main/CHANGELOG.md' + echo '- https://github.com/InfluxCommunity/influxdb3-go/blob/main/CHANGELOG.md' + echo '- https://github.com/InfluxCommunity/influxdb3-js/blob/main/CHANGELOG.md' + echo '- https://github.com/InfluxCommunity/influxdb3-csharp/blob/main/CHANGELOG.md' + echo '- https://github.com/InfluxCommunity/influxdb3-java/blob/main/CHANGELOG.md' + echo + echo 'Changed files include body-only release notes under' + echo '`content/shared/influxdb-client-libraries-reference/v3/release-notes/`' + echo 'and `latest_version` / `latest_release_date` frontmatter updates' + echo 'on the per-product stubs under' + echo '`content/influxdb3/*/reference/client-libraries/v3/*/release-notes.md`.' + } > "$BODY_FILE" + echo "path=$BODY_FILE" >> "$GITHUB_OUTPUT" + - name: Create or update pull request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v8 with: path: docs-v2 branch: sync/client-library-release-notes commit-message: 'sync(client-libs): update release notes' title: 'sync(client-libs): update release notes' - body: | - Automated sync from the v3 client library CHANGELOGs. - - Sources: - - https://github.com/InfluxCommunity/influxdb3-python/blob/main/CHANGELOG.md - - https://github.com/InfluxCommunity/influxdb3-go/blob/main/CHANGELOG.md - - https://github.com/InfluxCommunity/influxdb3-js/blob/main/CHANGELOG.md - - https://github.com/InfluxCommunity/influxdb3-csharp/blob/main/CHANGELOG.md - - https://github.com/InfluxCommunity/influxdb3-java/blob/main/CHANGELOG.md - - Changed files include body-only release notes under - `content/shared/influxdb-client-libraries-reference/v3/release-notes/` - and `latest_version` / `latest_release_date` frontmatter updates - on the per-product stubs under - `content/influxdb3/*/reference/client-libraries/v3/*/release-notes.md`. + body-path: ${{ steps.body.outputs.path }} + # Assign the Copilot coding agent only when a client was skipped or + # failed to parse, so it can investigate upstream CHANGELOG drift. + # Requires Copilot coding agent to be enabled on the repository. + assignees: ${{ steps.sync.outputs.needs_attention == 'true' && 'Copilot' || '' }} labels: | sync client-libraries diff --git a/helper-scripts/client-libraries/sync-release-notes.js b/helper-scripts/client-libraries/sync-release-notes.js index 07a938d664..db0cffe53c 100644 --- a/helper-scripts/client-libraries/sync-release-notes.js +++ b/helper-scripts/client-libraries/sync-release-notes.js @@ -2,7 +2,14 @@ // Reads a client repo's CHANGELOG.md, runs the transform, and writes the // shared source file. Designed to run from the docs-v2 repo root. -import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'; +import { + readFileSync, + writeFileSync, + appendFileSync, + existsSync, + mkdirSync, +} from 'node:fs'; +import { randomBytes } from 'node:crypto'; import { dirname, join, resolve } from 'node:path'; import { parseArgs } from 'node:util'; import { CLIENTS, getClient } from './clients.js'; @@ -139,6 +146,98 @@ function syncOne(client, sourcePath, docsRoot) { }; } +// `warning` and `error` indicate likely upstream drift (renamed CHANGELOG, +// changed heading format) and must fail the job. `skipped` means the source +// file is genuinely absent — surfaced but non-fatal so onboarding a new client +// before its first CHANGELOG ships doesn't break the nightly. +const FATAL_STATUSES = new Set(['warning', 'error']); +const ATTENTION_STATUSES = new Set(['skipped', 'warning', 'error']); + +/** + * Escape special characters in GitHub Actions workflow command properties + * (title, etc.). Colons and commas delimit command metadata fields. + */ +function escapeWorkflowCommandProperty(s) { + return String(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A') + .replace(/:/g, '%3A') + .replace(/,/g, '%2C'); +} + +/** + * Escape special characters in GitHub Actions workflow command message bodies. + */ +function escapeWorkflowCommandData(s) { + return String(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); +} + +/** + * Sanitize a value for use inside a GitHub Flavored Markdown table cell. + * Pipe characters would break the column structure; newlines would break the row. + */ +function sanitizeTableCell(s) { + return String(s ?? '') + .replace(/\|/g, '\\|') + .replace(/\r?\n/g, ' '); +} + +function formatSummary(results) { + const rows = results.map((r) => { + const detail = + r.status === 'updated' + ? `latest: \`${sanitizeTableCell(r.latestVersion)}\`` + : r.status === 'unchanged' + ? `latest: \`${sanitizeTableCell(r.latestVersion)}\`` + : sanitizeTableCell(r.reason ?? ''); + return `| \`${r.client}\` | ${r.status} | ${detail} |`; + }); + return [ + '| Client | Status | Detail |', + '| --- | --- | --- |', + ...rows, + ].join('\n'); +} + +function emitAnnotations(results) { + for (const r of results) { + if (r.status === 'error' || r.status === 'warning') { + console.log( + `::error title=${escapeWorkflowCommandProperty(`Client release-notes sync (${r.client})`)}::${escapeWorkflowCommandData(r.reason)}` + ); + } else if (r.status === 'skipped') { + console.log( + `::warning title=${escapeWorkflowCommandProperty(`Client release-notes sync (${r.client})`)}::${escapeWorkflowCommandData(r.reason)}` + ); + } + } +} + +function writeStepOutputs(outputs) { + const file = process.env.GITHUB_OUTPUT; + if (!file) return; + const lines = []; + for (const [key, value] of Object.entries(outputs)) { + if (typeof value === 'string' && value.includes('\n')) { + const delim = `EOF_${randomBytes(8).toString('hex')}`; + lines.push(`${key}<<${delim}`, value, delim); + } else { + lines.push(`${key}=${value}`); + } + } + appendFileSync(file, lines.join('\n') + '\n'); +} + +function writeStepSummary(markdown) { + const file = process.env.GITHUB_STEP_SUMMARY; + if (!file) return; + appendFileSync(file, markdown + '\n'); +} + function main() { const args = parseCliArgs(); const results = []; @@ -164,8 +263,18 @@ function main() { console.log(JSON.stringify(results, null, 2)); - const hadError = results.some((r) => r.status === 'error'); - process.exit(hadError ? 1 : 0); + const summary = formatSummary(results); + const needsAttention = results.some((r) => ATTENTION_STATUSES.has(r.status)); + + emitAnnotations(results); + writeStepSummary(`## Client library release-notes sync\n\n${summary}`); + writeStepOutputs({ + needs_attention: needsAttention ? 'true' : 'false', + summary, + }); + + const hadFatal = results.some((r) => FATAL_STATUSES.has(r.status)); + process.exit(hadFatal ? 1 : 0); } main();