name: Reusable Release Workflow on: workflow_call: inputs: tag: description: 'Version tag (e.g., v1.0.0)' required: true type: string generate-notes: description: 'Auto-generate release notes' required: false type: boolean default: true secrets: NPM_TOKEN: required: false workflow_dispatch: inputs: tag: description: 'Version tag to release or republish (e.g., v2.0.0-rc.1)' required: true type: string generate-notes: description: 'Auto-generate release notes' required: false type: boolean default: true permissions: contents: read jobs: verify: name: Verify Release runs-on: ubuntu-latest outputs: already_published: ${{ steps.npm_publish_state.outputs.already_published }} dist_tag: ${{ steps.npm_publish_state.outputs.dist_tag }} package_file: ${{ steps.pack.outputs.package_file }} steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 ref: ${{ inputs.tag }} persist-credentials: false - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: '20.x' registry-url: 'https://registry.npmjs.org' - name: Install dependencies run: npm ci --ignore-scripts - name: Run supply-chain IOC scan run: npm run security:ioc-scan - name: Verify OpenCode package payload run: node tests/scripts/build-opencode.test.js - name: Validate version tag env: INPUT_TAG: ${{ inputs.tag }} run: | if ! [[ "$INPUT_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then echo "Invalid version tag format. Expected vX.Y.Z or vX.Y.Z-prerelease" exit 1 fi - name: Verify package version matches tag env: INPUT_TAG: ${{ inputs.tag }} run: | TAG_VERSION="${INPUT_TAG#v}" PACKAGE_VERSION=$(node -p "require('./package.json').version") if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then echo "::error::Tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)" echo "Run: ./scripts/release.sh $TAG_VERSION" exit 1 fi - name: Verify release metadata stays in sync run: node tests/plugin-manifest.test.js - name: Check npm publish state id: npm_publish_state run: | PACKAGE_NAME=$(node -p "require('./package.json').name") PACKAGE_VERSION=$(node -p "require('./package.json').version") NPM_DIST_TAG=$(node -p "require('./package.json').version.includes('-') ? 'next' : 'latest'") if npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version >/dev/null 2>&1; then echo "already_published=true" >> "$GITHUB_OUTPUT" else echo "already_published=false" >> "$GITHUB_OUTPUT" fi echo "dist_tag=${NPM_DIST_TAG}" >> "$GITHUB_OUTPUT" - name: Generate release highlights env: TAG_NAME: ${{ inputs.tag }} run: | TAG_VERSION="${TAG_NAME#v}" cat > release_body.md < npm-pack.json PACKAGE_FILE=$(node -e "const fs = require('fs'); const data = JSON.parse(fs.readFileSync('npm-pack.json', 'utf8')); console.log(data[0].filename)") echo "package_file=${PACKAGE_FILE}" >> "$GITHUB_OUTPUT" - name: Upload release artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ecc-release-artifacts path: | release_body.md ${{ steps.pack.outputs.package_file }} if-no-files-found: error publish: name: Publish Release runs-on: ubuntu-latest needs: verify permissions: contents: write id-token: write steps: - name: Download release artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: ecc-release-artifacts - name: Setup Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: '20.x' registry-url: 'https://registry.npmjs.org' - name: Create GitHub Release uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: tag_name: ${{ inputs.tag }} body_path: release_body.md generate_release_notes: ${{ inputs.generate-notes }} prerelease: ${{ contains(inputs.tag, '-') }} make_latest: ${{ contains(inputs.tag, '-') && 'false' || 'true' }} - name: Publish npm package if: needs.verify.outputs.already_published != 'true' env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: npm publish "${{ needs.verify.outputs.package_file }}" --access public --provenance --tag "${{ needs.verify.outputs.dist_tag }}"