name: Release on: push: branches: - main concurrency: group: release-main cancel-in-progress: false permissions: contents: write pull-requests: read jobs: publish: name: Publish stable release runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v7 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 24 cache: npm registry-url: https://registry.npmjs.org - name: Install dependencies run: npm ci - name: Resolve release bump from PR labels id: bump uses: actions/github-script@v9 with: script: | const { data: pulls } = await github.rest.repos.listPullRequestsAssociatedWithCommit({ owner: context.repo.owner, repo: context.repo.repo, commit_sha: context.sha, }); const mergedPr = pulls.find((pr) => pr.merged_at); const labels = mergedPr?.labels?.map((label) => label.name.toLowerCase()) ?? []; // Label names map to npm semver bumps: // - minor label -> patch bump (1.0.0 -> 1.0.1) // - patch label -> minor bump (1.0.0 -> 1.1.0) // - major label -> major bump (1.0.0 -> 2.0.0) // - no label -> same as minor label let bump = "patch"; if (labels.includes("major")) { bump = "major"; } else if (labels.includes("patch")) { bump = "minor"; } else if (labels.includes("minor")) { bump = "patch"; } const bumpSource = !mergedPr ? "no merged PR found (default: minor label)" : labels.length === 0 ? `PR #${mergedPr.number} has no labels (default: minor label)` : `PR #${mergedPr.number} labels: ${labels.join(", ")}`; core.info(`Release bump: ${bump} (${bumpSource})`); core.setOutput("bump", bump); core.setOutput("source", bumpSource); - name: Resolve version id: version run: | BUMP="${{ steps.bump.outputs.bump }}" PKG_VERSION=$(node -p "require('./package.json').version") if npm view "@thiagopi/ts-utils-kit@${PKG_VERSION}" version >/dev/null 2>&1; then NPM_LATEST=$(npm view "@thiagopi/ts-utils-kit" version) echo "Version ${PKG_VERSION} is already on npm. Bumping from ${NPM_LATEST} (${BUMP})." npm version "${NPM_LATEST}" --no-git-tag-version --allow-same-version npm version "${BUMP}" --no-git-tag-version fi VERSION=$(node -p "require('./package.json').version") TAG="v${VERSION}" while npm view "@thiagopi/ts-utils-kit@${VERSION}" version >/dev/null 2>&1 || git rev-parse "${TAG}" >/dev/null 2>&1; do echo "Version ${VERSION} is already published or tagged. Bumping patch." npm version patch --no-git-tag-version VERSION=$(node -p "require('./package.json').version") TAG="v${VERSION}" done echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "tag=${TAG}" >> "$GITHUB_OUTPUT" env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Test run: npm test - name: Build run: npm run build - name: Publish to npm run: npm publish --tag latest --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Add install instructions to job summary run: | { echo "## Release published" echo "" echo "Version bump: \`${{ steps.bump.outputs.bump }}\` (${{ steps.bump.outputs.source }})" echo "" echo "Install this version:" echo "" echo '```bash' echo "npm install @thiagopi/ts-utils-kit@${{ steps.version.outputs.version }}" echo '```' echo "" echo "Or install the latest stable tag:" echo "" echo '```bash' echo "npm install @thiagopi/ts-utils-kit@latest" echo '```' } >> "$GITHUB_STEP_SUMMARY" - name: Create GitHub release uses: softprops/action-gh-release@v3 with: tag_name: ${{ steps.version.outputs.tag }} name: ${{ steps.version.outputs.tag }} generate_release_notes: true body: | ## Install Install this version: ```bash npm install @thiagopi/ts-utils-kit@${{ steps.version.outputs.version }} ``` Or install the latest stable tag: ```bash npm install @thiagopi/ts-utils-kit@latest ```