chore(deps): bump fast-uri from 3.1.2 to 3.1.4 #9
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Create Tag and Release on Merge | |
| # This workflow creates a tag and GitHub Release when a release PR is merged to main | |
| # It can also be triggered manually after a merge if the PR title was incorrect. | |
| on: | |
| pull_request: | |
| types: [closed] | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| jobs: | |
| create-tag-and-release: | |
| if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && startsWith(github.event.pull_request.title, 'release:')) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: read | |
| steps: | |
| - name: Checkout main | |
| if: github.event_name != 'workflow_dispatch' | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| ref: main | |
| - name: Checkout selected ref | |
| if: github.event_name == 'workflow_dispatch' | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Enable Corepack | |
| run: corepack enable | |
| - name: Install dependencies | |
| run: yarn install --immutable | |
| - name: Extract version from package.json | |
| id: version | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "tag=v$VERSION" >> $GITHUB_OUTPUT | |
| echo "✅ Detected version: $VERSION" | |
| - name: Check if tag already exists | |
| id: check-tag | |
| run: | | |
| TAG_NAME="${{ steps.version.outputs.tag }}" | |
| if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then | |
| echo "exists=true" >> $GITHUB_OUTPUT | |
| echo "⚠️ Tag $TAG_NAME already exists locally" | |
| else | |
| echo "exists=false" >> $GITHUB_OUTPUT | |
| echo "✅ Tag $TAG_NAME does not exist, will create" | |
| fi | |
| - name: Create Git Tag | |
| if: steps.check-tag.outputs.exists == 'false' | |
| run: | | |
| TAG_NAME="${{ steps.version.outputs.tag }}" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git tag -a "$TAG_NAME" -m "Release $TAG_NAME" | |
| git push origin "$TAG_NAME" | |
| echo "✅ Tag $TAG_NAME created and pushed" | |
| - name: Generate and Create GitHub Release | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const { execSync } = require('child_process'); | |
| const fs = require('fs'); | |
| // Get version from package.json | |
| const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); | |
| const version = packageJson.version; | |
| const tagName = `v${version}`; | |
| console.log(`📦 Creating release for tag: ${tagName}`); | |
| // Check if release already exists | |
| try { | |
| const existingRelease = await github.rest.repos.getReleaseByTag({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| tag: tagName | |
| }); | |
| console.log(`⚠️ Release ${tagName} already exists!`); | |
| console.log(`Release URL: ${existingRelease.data.html_url}`); | |
| return; | |
| } catch (error) { | |
| if (error.status !== 404) { | |
| throw error; | |
| } | |
| console.log(`✅ No existing release found for ${tagName}, proceeding...`); | |
| } | |
| // Get previous tag | |
| const { data: tags } = await github.rest.repos.listTags({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| per_page: 10, | |
| }); | |
| let previousTagName = ''; | |
| for (const tag of tags) { | |
| if (tag.name !== tagName) { | |
| previousTagName = tag.name; | |
| break; | |
| } | |
| } | |
| console.log(`Generating release notes from ${previousTagName || 'start'} to ${tagName}`); | |
| // Get commits between tags | |
| let commits = []; | |
| try { | |
| const { data: comparison } = await github.rest.repos.compareCommits({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| base: previousTagName || 'HEAD~50', | |
| head: tagName, | |
| }); | |
| commits = comparison.commits; | |
| console.log(`Found ${commits.length} commits between ${previousTagName || 'start'} and ${tagName}`); | |
| } catch (error) { | |
| console.log(`Could not compare commits: ${error.message}`); | |
| commits = []; | |
| } | |
| // Get PRs for commits | |
| const prMap = new Map(); | |
| for (const commit of commits) { | |
| try { | |
| const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| commit_sha: commit.sha, | |
| }); | |
| for (const pr of prs) { | |
| if (pr.merged_at && | |
| !prMap.has(pr.number) && | |
| !pr.title.toLowerCase().includes('release:')) { | |
| prMap.set(pr.number, pr); | |
| } | |
| } | |
| } catch (error) { | |
| console.log(`Could not get PRs for commit ${commit.sha.substring(0, 7)}:`, error.message); | |
| } | |
| } | |
| const prs = Array.from(prMap.values()); | |
| console.log(`Found ${prs.length} merged PRs between tags`); | |
| // Categorize PRs by labels | |
| const breaking = []; | |
| const features = []; | |
| const bugs = []; | |
| const improvements = []; | |
| const docs = []; | |
| const dependencies = []; | |
| const chores = []; | |
| const other = []; | |
| prs.forEach(pr => { | |
| const labels = pr.labels.map(l => l.name); | |
| const prLine = `- ${pr.title} (#${pr.number}) @${pr.user.login}`; | |
| if (labels.includes('breaking')) { | |
| breaking.push(prLine); | |
| } else if (labels.includes('type: feature')) { | |
| features.push(prLine); | |
| } else if (labels.includes('type: bug')) { | |
| bugs.push(prLine); | |
| } else if (labels.includes('type: improvement')) { | |
| improvements.push(prLine); | |
| } else if (labels.includes('type: docs')) { | |
| docs.push(prLine); | |
| } else if (labels.includes('dependencies')) { | |
| dependencies.push(prLine); | |
| } else if (labels.includes('type: chore')) { | |
| chores.push(prLine); | |
| } else { | |
| other.push(prLine); | |
| } | |
| }); | |
| // Build release notes | |
| let releaseNotes = ``; | |
| // Get CHANGELOG.md content for this version | |
| let changelogContent = ''; | |
| try { | |
| const changelogPath = './CHANGELOG.md'; | |
| if (fs.existsSync(changelogPath)) { | |
| const changelogText = fs.readFileSync(changelogPath, 'utf8'); | |
| const versionNumber = version; | |
| const escapedVersion = versionNumber.replace(/\./g, '\\.'); | |
| const versionRegex = new RegExp(`##\\s+${escapedVersion}\\s*\\n([\\s\\S]*?)(?=\\n##|$)`); | |
| const match = changelogText.match(versionRegex); | |
| if (match && match[1]) { | |
| changelogContent = match[1].trim(); | |
| console.log(`✅ Found CHANGELOG.md content for version ${versionNumber}`); | |
| } | |
| } | |
| } catch (error) { | |
| console.log(`Could not read CHANGELOG.md: ${error.message}`); | |
| } | |
| // Use CHANGELOG.md if available | |
| if (changelogContent) { | |
| releaseNotes += changelogContent + '\n\n'; | |
| releaseNotes += `---\n\n`; | |
| releaseNotes += `## Pull Requests\n\n`; | |
| } else { | |
| if (prs.length > 0) { | |
| const totalPRs = prs.length; | |
| releaseNotes += `This release includes ${totalPRs} ${totalPRs === 1 ? 'change' : 'changes'}.\n\n`; | |
| } | |
| } | |
| if (breaking.length > 0) { | |
| releaseNotes += `### Breaking Changes\n${breaking.join('\n')}\n\n`; | |
| } | |
| if (features.length > 0) { | |
| releaseNotes += `### New Features\n${features.join('\n')}\n\n`; | |
| } | |
| if (bugs.length > 0) { | |
| releaseNotes += `### Bug Fixes\n${bugs.join('\n')}\n\n`; | |
| } | |
| if (improvements.length > 0) { | |
| releaseNotes += `### Improvements\n${improvements.join('\n')}\n\n`; | |
| } | |
| if (docs.length > 0) { | |
| releaseNotes += `### Documentation\n${docs.join('\n')}\n\n`; | |
| } | |
| if (dependencies.length > 0) { | |
| releaseNotes += `### Dependencies\n${dependencies.join('\n')}\n\n`; | |
| } | |
| if (chores.length > 0) { | |
| releaseNotes += `### Chores\n${chores.join('\n')}\n\n`; | |
| } | |
| if (other.length > 0) { | |
| releaseNotes += `### Other Changes\n${other.join('\n')}\n\n`; | |
| } | |
| if (prs.length === 0) { | |
| releaseNotes += `No merged pull requests found for this release.\n\n`; | |
| } | |
| if (previousTagName) { | |
| releaseNotes += `\n---\n**Full Changelog**: https://github.com/${context.repo.owner}/${context.repo.repo}/compare/${previousTagName}...${tagName}`; | |
| } | |
| // Create GitHub Release | |
| const release = await github.rest.repos.createRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| tag_name: tagName, | |
| name: `Release ${tagName}`, | |
| body: releaseNotes, | |
| draft: false, | |
| prerelease: false, | |
| make_latest: 'true' | |
| }); | |
| console.log(`✅ Release ${tagName} created successfully!`); | |
| console.log(`Release URL: ${release.data.html_url}`); |