diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0da43d1..c8ffeb9 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,131 +2,139 @@ name: Release on: workflow_dispatch: - inputs: - version: - description: 'Version to release (e.g., 1.0.0)' - required: true - type: string concurrency: release jobs: + test-python: + name: Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12", "3.13"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + + - name: Install dependencies + working-directory: python + run: uv pip install --system -e ".[dev]" + + - name: Run tests + working-directory: python + run: pytest tests/ -v --cov=jentic.problem_details --cov-report=term-missing + + test-typescript: + name: TypeScript + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: typescript/package-lock.json + + - name: Install dependencies + working-directory: typescript + run: npm ci + + - name: Build + working-directory: typescript + run: npm run build + + - name: Run tests + working-directory: typescript + run: npm test + + - name: Type check + working-directory: typescript + run: npm run typecheck + release: name: Release packages + needs: [test-python, test-typescript] runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' environment: - name: npm-release - url: https://www.npmjs.com/org/jentic + name: release + url: https://github.com/jentic/api-problem-details/releases permissions: contents: write - id-token: write # Required for PyPI OIDC and NPM provenance + issues: write + pull-requests: write + id-token: write + attestations: write steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 + persist-credentials: false - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' - - name: Install uv - uses: astral-sh/setup-uv@v5 - - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: '20' registry-url: 'https://registry.npmjs.org' - - name: Configure Git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Update Python package version - working-directory: python - run: | - sed -i 's/^version = .*/version = "${{ inputs.version }}"/' pyproject.toml - - - name: Update TypeScript package version - working-directory: typescript - run: | - npm version ${{ inputs.version }} --no-git-tag-version - - - name: Commit version changes and create tag + - name: Install semantic-release + run: > + npm install -g + semantic-release + @semantic-release/commit-analyzer + @semantic-release/release-notes-generator + @semantic-release/github + @semantic-release/npm + @semantic-release/exec + @semantic-release/git + + - name: Run semantic-release env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git add python/pyproject.toml typescript/package.json typescript/package-lock.json - git commit -m "chore: release v${{ inputs.version }}" - git tag -a "v${{ inputs.version }}" -m "Release v${{ inputs.version }}" - git push origin main --tags - - - name: Install Python dependencies and build - working-directory: python - run: | - uv pip install --system build - python -m build + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npx semantic-release - name: Build TypeScript package working-directory: typescript run: | - npm ci - npm run build - - - name: Prepare release artifacts - run: | - mkdir -p release-assets - cp python/dist/* release-assets/ + cp ../LICENSE ../NOTICE . + npm pack + rm -f LICENSE NOTICE - echo "📦 Release artifacts:" - ls -la release-assets/ + - name: Attest npm tarball + uses: actions/attest-build-provenance@v4 + with: + subject-path: typescript/*.tgz - - name: Create GitHub Release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Publish TypeScript package to npm (OIDC) + working-directory: typescript run: | - echo "🚀 Creating GitHub release..." - VERSION="${{ inputs.version }}" + set -euo pipefail + TARBALL="$(ls -1 *.tgz | head -n 1)" + echo "Publishing $TARBALL" + npm publish "file:./$TARBALL" --provenance --access public - # Try to extract changelog for this version - CHANGELOG_CONTENT="$(sed -n '/^## v'$VERSION'/,/^## /p' CHANGELOG.md 2>/dev/null | sed '$d')" - - if [ -z "$CHANGELOG_CONTENT" ]; then - CHANGELOG_CONTENT="Release v$VERSION" - fi - - gh release create "v$VERSION" \ - --title "v$VERSION" \ - --notes "$CHANGELOG_CONTENT" \ - release-assets/* - - - name: Publish Python package to PyPI - id: pypi_publish - continue-on-error: true # TODO: Remove after Trusted Publisher is configured on PyPI + - name: Publish Python package to PyPI (OIDC) + if: hashFiles('python/dist/*.whl') != '' uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: python/dist/ - attestations: false - - - name: Publish TypeScript package to NPM - id: npm_publish - working-directory: typescript - run: npm publish --access public --provenance - - - name: Release Summary - run: | - echo "🎉 RELEASE COMPLETED" - echo "Released version: ${{ inputs.version }}" - echo "" - if [ "${{ steps.pypi_publish.outcome }}" == "success" ]; then - echo "Published to PyPI: ✓" - else - echo "Published to PyPI: ⚠️ SKIPPED (Trusted Publisher not configured yet)" - fi - echo "Published to NPM: ✓" - echo "GitHub release created: ✓" diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 0000000..77fe519 --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,47 @@ +{ + "branches": ["main"], + "tagFormat": "v${version}", + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/exec", + { + "prepareCmd": "cp LICENSE NOTICE python/ && cd python && sed -i 's/^version = .*/version = \"${nextRelease.version}\"/' pyproject.toml && pip install build && python -m build && rm -f LICENSE NOTICE" + } + ], + [ + "@semantic-release/npm", + { + "npmPublish": false, + "pkgRoot": "typescript" + } + ], + [ + "@semantic-release/github", + { + "assets": [ + { + "path": "python/dist/*.tar.gz", + "label": "Python sdist" + }, + { + "path": "python/dist/*.whl", + "label": "Python wheel" + } + ] + } + ], + [ + "@semantic-release/git", + { + "assets": [ + "python/pyproject.toml", + "typescript/package.json", + "typescript/package-lock.json" + ], + "message": "chore(release): cut the v${nextRelease.version} release [ci skip]" + } + ] + ] +} diff --git a/python/pyproject.toml b/python/pyproject.toml index 3d1aac6..aaddaed 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -5,6 +5,7 @@ description = "RFC 9457 Problem Details models for Jentic APIs" readme = "README.md" requires-python = ">=3.11" license = {text = "Apache-2.0"} +license-files = ["LICENSE", "NOTICE"] authors = [ {name = "Jentic", email = "hello@jentic.com"} ] @@ -47,6 +48,11 @@ Issues = "https://github.com/jentic/api-problem-details/issues" requires = ["hatchling"] build-backend = "hatchling.build" +[tool.hatch.build] +exclude = [ + "tests/", +] + [tool.hatch.build.targets.wheel] packages = ["src/jentic"] diff --git a/typescript/package.json b/typescript/package.json index 5049481..4030f95 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -5,6 +5,11 @@ "license": "Apache-2.0", "author": "Jentic ", "homepage": "https://github.com/jentic/api-problem-details#readme", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org", + "provenance": true + }, "repository": { "type": "git", "url": "https://github.com/jentic/api-problem-details.git", @@ -32,7 +37,9 @@ }, "files": [ "dist", - "README.md" + "README.md", + "LICENSE", + "NOTICE" ], "scripts": { "build": "tsc",