chore: version packages #78
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: Release | |
| on: | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| concurrency: ${{ github.workflow }}-${{ github.ref }} | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| id-token: write | |
| packages: write | |
| jobs: | |
| release: | |
| name: Release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22.x" | |
| - name: Upgrade npm for OIDC support | |
| run: npm install -g npm@latest | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v1 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| run: bun install | |
| - name: Type check | |
| run: bun run typecheck | |
| - name: Lint | |
| run: bun run lint | |
| - name: Test | |
| run: bun test 2>/dev/null || true | |
| - name: Build | |
| run: bun run build | |
| - name: Create Release Pull Request or Publish | |
| id: changesets | |
| uses: changesets/action@v1 | |
| with: | |
| version: bun run changeset:version | |
| publish: echo "skip" | |
| commit: "chore: version packages" | |
| title: "chore: version packages" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Publish to npm with OIDC | |
| if: steps.changesets.outputs.hasChangesets == 'false' | |
| run: | | |
| # Check npm version (need 11.5.1+ for OIDC) | |
| echo "npm version: $(npm --version)" | |
| # Remove any existing .npmrc that might interfere with OIDC | |
| rm -f ~/.npmrc | |
| rm -f .npmrc | |
| # Check if package version is already published | |
| PACKAGE_NAME=$(node -p "require('./package.json').name") | |
| LOCAL_VERSION=$(node -p "require('./package.json').version") | |
| # Get published version (returns empty if not found) | |
| PUBLISHED_VERSION=$(npm view "$PACKAGE_NAME" version 2>/dev/null || echo "") | |
| if [ "$LOCAL_VERSION" != "$PUBLISHED_VERSION" ]; then | |
| echo "Publishing $PACKAGE_NAME@$LOCAL_VERSION (current published: ${PUBLISHED_VERSION:-none})" | |
| npm publish --access public | |
| else | |
| echo "Version $LOCAL_VERSION is already published, skipping" | |
| fi | |
| - name: Publish to GitHub Packages with OIDC | |
| if: steps.changesets.outputs.hasChangesets == 'false' | |
| run: | | |
| # Get version info and save original package name | |
| LOCAL_VERSION=$(node -p "require('./package.json').version") | |
| SCOPED_PACKAGE_NAME='@mynameistito/github-archiver' | |
| echo "Attempting to publish $SCOPED_PACKAGE_NAME@$LOCAL_VERSION to GitHub Packages" | |
| # Temporarily modify package.json with scoped name | |
| node -e " | |
| const fs = require('fs'); | |
| const pkg = require('./package.json'); | |
| global.originalName = pkg.name; | |
| pkg.name = '@mynameistito/github-archiver'; | |
| fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| console.log('Changed package name to scoped for GitHub Packages'); | |
| " | |
| # Publish to GitHub Packages registry | |
| npm publish --registry https://npm.pkg.github.com --access public || true | |
| # Restore original package name | |
| node -e " | |
| const fs = require('fs'); | |
| const pkg = require('./package.json'); | |
| pkg.name = 'github-archiver'; | |
| fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| console.log('Restored original package name'); | |
| " | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create version tag | |
| if: steps.changesets.outputs.hasChangesets == 'false' | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| TAG="v${VERSION}" | |
| echo "Creating tag ${TAG}" | |
| git config --local user.name "github-actions[bot]" | |
| git config --local user.email "github-actions[bot]@users.noreply.github.com" | |
| # Check if tag already exists locally | |
| if git rev-parse "${TAG}" >/dev/null 2>&1; then | |
| echo "ℹ️ Tag ${TAG} already exists locally, skipping creation" | |
| else | |
| git tag "${TAG}" | |
| git push origin "${TAG}" | |
| echo "✅ Created and pushed tag ${TAG}" | |
| fi | |
| - name: Update latest tag | |
| if: steps.changesets.outputs.hasChangesets == 'false' | |
| run: | | |
| git config --local user.name "github-actions[bot]" | |
| git config --local user.email "github-actions[bot]@users.noreply.github.com" | |
| git tag -f latest | |
| git push origin -f latest | |
| - name: Extract release notes and create GitHub Release | |
| if: steps.changesets.outputs.hasChangesets == 'false' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| // Read package.json to get version | |
| const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); | |
| const version = packageJson.version; | |
| const tag = `v${version}`; | |
| // Read CHANGELOG.md | |
| const changelog = fs.readFileSync('./CHANGELOG.md', 'utf8'); | |
| const lines = changelog.split('\n'); | |
| // Find the section for this version by looking for "## " (level 2 header) with the version number | |
| // This handles all formats: ## [1.1.2], ## 1.1.2, ## [1.1.2](link) (date), etc. | |
| let startIdx = -1; | |
| for (let i = 0; i < lines.length; i++) { | |
| const line = lines[i]; | |
| // Check if line is a level 2 header (## but not ###) and contains the version number | |
| if (line.startsWith('## ') && line.includes(version)) { | |
| startIdx = i; | |
| break; | |
| } | |
| } | |
| let releaseBody = 'No release notes available'; | |
| if (startIdx !== -1) { | |
| // Find the next level 2 header (## but not ###) - marks end of this version's section | |
| let endIdx = lines.length; | |
| for (let i = startIdx + 1; i < lines.length; i++) { | |
| if (lines[i].startsWith('## ') && !lines[i].startsWith('### ')) { | |
| endIdx = i; | |
| break; | |
| } | |
| } | |
| // Extract content between headers and trim whitespace | |
| const contentLines = lines.slice(startIdx + 1, endIdx); | |
| releaseBody = contentLines.join('\n').trim(); | |
| } | |
| // Create GitHub Release | |
| try { | |
| await github.rest.repos.createRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| tag_name: tag, | |
| name: `Release ${version}`, | |
| body: releaseBody, | |
| draft: false, | |
| prerelease: false | |
| }); | |
| console.log(`✅ Created GitHub Release for ${tag}`); | |
| } catch (error) { | |
| // Release might already exist, that's okay | |
| if (error.status === 422) { | |
| console.log(`ℹ️ Release for ${tag} already exists`); | |
| } else { | |
| throw error; | |
| } | |
| } |