From 6623a8b65ad13a30845d828ff4b6716b976498db Mon Sep 17 00:00:00 2001 From: "Randolph W. Aarseth II" Date: Wed, 18 Dec 2024 19:54:32 -0700 Subject: [PATCH] used changelog.js from ci_cd to automate verison generation --- .github/workflows/build-release.yaml | 116 +++++++++----- scripts/changelog.js | 230 +++++++++++++++++++++++++++ 2 files changed, 305 insertions(+), 41 deletions(-) create mode 100644 scripts/changelog.js diff --git a/.github/workflows/build-release.yaml b/.github/workflows/build-release.yaml index 4564907..e40795f 100644 --- a/.github/workflows/build-release.yaml +++ b/.github/workflows/build-release.yaml @@ -8,7 +8,74 @@ on: workflow_dispatch: jobs: + get-latest-sha: + name: 🔍 Get Latest SHA & Base Version + runs-on: ubuntu-latest + outputs: + version_string: ${{ steps.new_version.outputs.version_string }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: blazium-engine/vscode-anko-highlighter + ref: master + fetch-depth: 2 + + - name: Get Latest Commit SHA + id: get_sha + run: echo "sha=$(git rev-parse HEAD)" >> $ENV:GITHUB_OUTPUT + + - name: Generate Changelog.json + id: new_version + env: + GITHUB_TOKEN: ${{ github.token }} + GITHUB_OWNER: blazium-engine + GITHUB_REPO: vscode-anko-highlighter + BASE_BRANCH: 0acc2206c2a57f104d3295ec4e5b5ce7b7d9d4dd + CURRENT_BRANCH: ${{ steps.get_sha.outputs.sha }} + MAJOR_VERSION: 0 + MINOR_VERSION: 0 + PATCH_VERSION: 2 + run: | + # Ensure the working directory is correct + Set-Location -Path "${{ github.workspace }}" + + # Trigger the changelog generation script + node ./scripts/changelog.js $(Get-Location) + + # Validate if changelog.json exists + if (-Not (Test-Path -Path "changelog.json")) { + Write-Error "changelog.json not found." + exit 1 + } + + # Parse changelog.json to extract version information + $changelog = Get-Content -Raw -Path "changelog.json" | ConvertFrom-Json + $major = $changelog.version.major + $minor = $changelog.version.minor + $patch = $changelog.version.patch + + # Combine version variables into a single variable + $version = "$major.$minor.$patch" + + # Set outputs + echo "version=$version" >> $ENV:GITHUB_OUTPUT + + # Create a debug summary + $summary = @( + "# Version Information", + "- Major: $major", + "- Minor: $minor", + "- Patch: $patch", + "- Full Version: $version" + ) -join "`n" + echo $summary >> $ENV:GITHUB_STEP_SUMMARY + + + build: + name: Building VSIX Package + needs: get-latest-sha runs-on: windows-latest outputs: version: ${{ steps.validate.outputs.newVersion }} @@ -27,54 +94,21 @@ jobs: - name: Install dependencies run: | cd .\anko\ - npm install - - - name: Determine Version Bump - id: version - run: | - $comment = "${{ github.event.head_commit.message }}" - if ($comment -match "#major") { - echo "bumpType=major" >> $ENV:GITHUB_OUTPUT - } elseif ($comment -match "#minor") { - echo "bumpType=minor" >> $ENV:GITHUB_OUTPUT - } else { - echo "bumpType=patch" >> $ENV:GITHUB_OUTPUT - } - - - name: Execute Update Script - id: update - run: | - $DebugInfo = @() - $DebugInfo += "Executing update.ps1 with VersionType=${{ steps.version.outputs.bumpType }}" - $result = pwsh ./scripts/update.ps1 ${{ steps.version.outputs.bumpType }} - echo "version=$result" >> $ENV:GITHUB_OUTPUT - echo "$DebugInfo" >> $ENV:GITHUB_STEP_SUMMARY - shell: pwsh - - - name: Validate Version Output - id: validate - run: | - $version = "${{ steps.update.outputs.version }}" - echo "Validating version: $version" >> $ENV:GITHUB_STEP_SUMMARY - if ($version -notmatch "^\d+\.\d+\.\d+$") { - Write-Error "Invalid version format: $version" - exit 1 - } - echo "newVersion=$version" >> $ENV:GITHUB_OUTPUT + npm Install - name: Build VSIX file run: | cd .\anko\ - vsce package --allow-missing-repository --out ..\dist\blazium-anko-extension-${{ steps.validate.outputs.newVersion }}.vsix + vsce package --allow-missing-repository --out ..\dist\blazium-anko-extension-${{ needs.get-latest-sha.outputs.version_string }}.vsix - name: Upload artifact uses: actions/upload-artifact@v3 with: name: blazium-anko-extension-vsix - path: dist/blazium-anko-extension-${{ steps.validate.outputs.newVersion }}.vsix + path: dist/blazium-anko-extension-${{ needs.get-latest-sha.outputs.version_string }}.vsix release: - needs: build + needs: [ build, get-latest-sha ] runs-on: windows-latest permissions: contents: write # Grants write permissions for content operations @@ -91,8 +125,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: ${{ needs.build.outputs.version }} - release_name: Blazium Anko Extension v${{ needs.build.outputs.version }} + tag_name: ${{ needs.get-latest-sha.outputs.version_string }} + release_name: Blazium Anko Extension v${{ needs.get-latest-sha.outputs.version_string }} draft: false prerelease: false @@ -102,8 +136,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.createRelease.outputs.upload_url }} - asset_path: ${{ github.workspace }}/dist/blazium-anko-extension-${{ needs.build.outputs.version }}.vsix - asset_name: blazium-anko-extension-${{ needs.build.outputs.version }}.vsix + asset_path: ${{ github.workspace }}/dist/blazium-anko-extension-${{ needs.get-latest-sha.outputs.version_string }}.vsix + asset_name: blazium-anko-extension-${{ needs.get-latest-sha.outputs.version_string }}.vsix asset_content_type: application/octet-stream diff --git a/scripts/changelog.js b/scripts/changelog.js new file mode 100644 index 0000000..1abd656 --- /dev/null +++ b/scripts/changelog.js @@ -0,0 +1,230 @@ +const https = require('https'); +const fs = require('fs'); +const path = require('path'); + +// Load environment variables from .env file +const token = process.env.GITHUB_TOKEN; +const owner = process.env.GITHUB_OWNER; // The owner of the repository +const repo = process.env.GITHUB_REPO; // The repository name +const baseBranch = process.env.BASE_BRANCH; // The base branch +const currentBranch = process.env.CURRENT_BRANCH; // The current branch + + +var version = { + major: process.env.MAJOR_VERSION || 0, + minor: process.env.MINOR_VERSION || 1, + patch: process.env.PATCH_VERSION || 0 +} + + +// API base URL for the repository +const apiBaseUrl = `https://api.github.com/repos/${owner}/${repo}`; + +function httpsGet(url, headers = {}) { + return new Promise((resolve, reject) => { + const options = { + method: 'GET', + headers: { + 'Authorization': `token ${token}`, + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'blazium-engine/blazium ci/cd v0.0.1 prototype', + ...headers, + }, + }; + + const req = https.request(url, options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + resolve(JSON.parse(data)); + } else { + reject(new Error(`HTTP ${res.statusCode}: ${data}`)); + } + }); + }); + + req.on('error', (err) => { + reject(err); + }); + + req.end(); + }); +} + +// Function to compare two branches and get commits +async function compareBranches(baseBranch, currentBranch) { + const url = `${apiBaseUrl}/compare/${baseBranch}...${currentBranch}`; + try { + const data = await httpsGet(url); + return data; // Returns the comparison data (commits, files, etc.) + } catch (error) { + console.error(`Error comparing branches ${baseBranch} and ${currentBranch}:`, error.message); + return null; + } +} + +// Function to get the files changed in a specific commit +async function getCommitFiles(commitSha) { + const url = `${apiBaseUrl}/commits/${commitSha}`; + try { + const data = await httpsGet(url); + return data.files; // Returns the list of files changed in the commit + } catch (error) { + console.error(`Error fetching files for commit ${commitSha}:`, error.message); + return []; + } +} +// Function to generate a changelog and collect stats from the comparison data +async function generateChangelog(baseBranch, currentBranch, outputDir = __dirname) { + const comparisonData = await compareBranches(baseBranch, currentBranch); + + if (!comparisonData) { + return; + } + + const commits = comparisonData.commits; + const filesChanged = new Set(); // To store unique file paths + let totalPRs = 0; + const changelog = []; + const contributorStats = {}; + let firstChangeDate = null; + let lastChangeDate = null; + + // Loop through commits to gather stats and changelog + for (const commit of commits) { + const commitSha = commit.sha; + const commitMessage = commit.commit.message; + const regex = /#(skip)/ig; + const matches = commitMessage.match(regex); + + if (matches) { + continue; + } + const commitDate = new Date(commit.commit.committer.date); + const commitUser = commit.committer.login; // The username of the person who made the commit + const prNumber = extractPRNumberFromCommitMessage(commitMessage); + const semVerLabel = getSemVerLabel(commitMessage); + + // Get files changed in the commit + const commitFiles = await getCommitFiles(commitSha); + commitFiles.forEach(file => filesChanged.add(file.filename)); + + // Update first and last change dates + if (!firstChangeDate || commitDate < firstChangeDate) { + firstChangeDate = commitDate; + } + if (!lastChangeDate || commitDate > lastChangeDate) { + lastChangeDate = commitDate; + } + + // Add user contribution count + if (!contributorStats[commitUser]) { + contributorStats[commitUser] = { + count: 0, + commits: [] + }; + } + contributorStats[commitUser].count += 1; + contributorStats[commitUser].commits.push(commitSha); + + // Add to changelog + changelog.push({ + sha: commitSha, + message: commitMessage, + date: commitDate, + user: commitUser, + pr: prNumber ? `PR #${prNumber}` : null, + label: semVerLabel + }); + + // Count PRs + if (prNumber) { + totalPRs++; + } + + if (semVerLabel === "major") { + version.major++; + version.minor = 0; + version.patch = 0; + } else if (semVerLabel === "minor") { + version.minor++; + version.patch = 0; + } else if (!prNumber) { + version.patch++; + } + } + + // Calculate time differences + const timeSinceFirstChange = firstChangeDate ? (Date.now() - firstChangeDate.getTime()) : null; + const timeSinceLastChange = lastChangeDate ? (Date.now() - lastChangeDate.getTime()) : null; + + // Prepare a list of unique users and their contribution counts + const uniqueContributors = Object.keys(contributorStats).map(user => ({ + username: user, + contributions: contributorStats[user].count + })); + + // Prepare the result object + const result = { + baseBranch, + version, + currentBranch, + totalCommits: commits.length, + totalPRs, + totalFilesChanged: filesChanged.size, + timeSinceFirstChange: timeSinceFirstChange ? `${Math.floor(timeSinceFirstChange / (1000 * 60 * 60 * 24))} days` : null, + timeSinceLastChange: timeSinceLastChange ? `${Math.floor(timeSinceLastChange / (1000 * 60 * 60 * 24))} days` : null, + totalContributors: uniqueContributors.length, + uniqueContributors, + changelog + }; + + // Save the result + saveToFile(result, `changelog_${baseBranch}_to_${currentBranch}.json`, outputDir); + saveToFile(result, `changelog.json`, outputDir); +} + +function saveToFile(data, fileName, outputDir) { + const filePath = path.join(outputDir, fileName); + fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8'); + console.log(`Changelog and stats exported to ${filePath}`); +} + +// Function to extract PR numbers from a commit message (e.g., "Merge pull request #XYZ") +function extractPRNumberFromCommitMessage(commitMessage) { + const prRegex = /Merge pull request #(\d+)/; + const match = commitMessage.match(prRegex); + return match ? parseInt(match[1], 10) : null; +} + +function getSemVerLabel(message) { + const regex = /#(major|minor|patch)/ig; + const matches = message.match(regex); + + if (matches) { + const uniqueMatches = [...new Set(matches.map(match => match.toLowerCase()))]; + + // If only one valid label is present, return it (without the '#'). + if (uniqueMatches.length === 1) { + return uniqueMatches[0].replace('#', ''); + } + + // If there are multiple valid labels, take the last one. + return uniqueMatches[uniqueMatches.length - 1].replace('#', ''); + } + + // Default to 'patch' if no valid labels are found. + return 'patch'; +} + +// Main function +(async function main() { + const args = process.argv.slice(2); + const outputDir = args[0] || __dirname; + await generateChangelog(baseBranch, currentBranch, outputDir); +})();