Files
ECC/.github/workflows/reusable-release.yml
T
1db5c8ab4a fix(install): harden ECC installer lifecycle
Make Antigravity 2.0 installs native and safely migrate legacy state. Ensure doctor, repair, status projection, repeat installs, legacy Codex sync, and uninstall converge without losing user files. Exclude Python bytecode and harden repo-scan bootstrap guidance.

Gate publishing and pull-request merges on one exact packed artifact completing install, repeat, drift, repair, status, and uninstall across Linux, macOS, and Windows.

Co-authored-by: lorencifernando-coder <lorenci.fernando@gmail.com>

Co-authored-by: Suliman Abdulrazzaq <suliman9000a@gmail.com>

Co-authored-by: Wu Shuwen <mikewushuwen@outlook.com>
2026-08-13 16:42:51 -04:00

230 lines
9.4 KiB
YAML

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 }}
package_sha256: ${{ steps.pack.outputs.package_sha256 }}
release_commit: ${{ steps.source.outputs.release_commit }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
ref: refs/tags/${{ inputs.tag }}
persist-credentials: false
- name: Pin release source
id: source
run: |
RELEASE_COMMIT=$(git rev-parse HEAD)
echo "release_commit=${RELEASE_COMMIT}" >> "$GITHUB_OUTPUT"
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.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 <<EOF
## ECC ${TAG_VERSION}
### What This Release Focuses On
- Harness reliability and cross-platform compatibility
- Eval-driven quality improvements
- Better workflow and operator ergonomics
### Package Notes
- npm package: \`ecc-universal\`
- Claude marketplace/plugin identifier: \`ecc@ecc\`
EOF
- name: Pack npm artifact
id: pack
run: |
npm pack --json > npm-pack.json
node -e "const crypto = require('crypto'); const fs = require('fs'); const data = JSON.parse(fs.readFileSync('npm-pack.json', 'utf8')); const file = data[0]?.filename; if (!/^ecc-universal-[0-9A-Za-z.+-]+\.tgz$/.test(file || '')) throw new Error('Unexpected packed filename'); const archives = fs.readdirSync('.').filter(name => name.endsWith('.tgz')); if (archives.length !== 1 || archives[0] !== file) throw new Error('Expected exactly one packed archive'); const digest = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'); fs.appendFileSync(process.env.GITHUB_OUTPUT, 'package_file=' + file + '\npackage_sha256=' + digest + '\n')"
- 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
- name: Verify existing npm artifact matches candidate
if: steps.npm_publish_state.outputs.already_published == 'true'
env:
ECC_RELEASE_PACKAGE: ${{ steps.pack.outputs.package_file }}
run: |
PACKAGE_NAME=$(node -p "require('./package.json').name")
PACKAGE_VERSION=$(node -p "require('./package.json').version")
REGISTRY_INTEGRITY=$(npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" dist.integrity)
ECC_REGISTRY_INTEGRITY="$REGISTRY_INTEGRITY" node -e "const crypto = require('crypto'); const fs = require('fs'); const expected = process.env.ECC_REGISTRY_INTEGRITY; if (!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(expected || '')) throw new Error('Invalid registry integrity'); const actual = 'sha512-' + crypto.createHash('sha512').update(fs.readFileSync(process.env.ECC_RELEASE_PACKAGE)).digest('base64'); if (actual !== expected) throw new Error('Existing npm artifact does not match tested candidate')"
lifecycle:
name: Packed Lifecycle (${{ matrix.os }})
needs: verify
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout lifecycle test
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ needs.verify.outputs.release_commit }}
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '20.x'
- name: Download exact packed artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ecc-release-artifacts
path: release-artifacts
- name: Verify packed install lifecycle
env:
ECC_RELEASE_PACKAGE: release-artifacts/${{ needs.verify.outputs.package_file }}
ECC_RELEASE_SHA256: ${{ needs.verify.outputs.package_sha256 }}
run: node tests/ci/packed-artifact-lifecycle.js
publish:
name: Publish Release
runs-on: ubuntu-latest
needs: [verify, lifecycle]
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@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- name: Verify artifact before publish
env:
ECC_RELEASE_PACKAGE: ${{ needs.verify.outputs.package_file }}
ECC_RELEASE_SHA256: ${{ needs.verify.outputs.package_sha256 }}
run: node -e "const crypto = require('crypto'); const fs = require('fs'); const file = process.env.ECC_RELEASE_PACKAGE; const expected = process.env.ECC_RELEASE_SHA256; if (!/^ecc-universal-[0-9A-Za-z.+-]+\.tgz$/.test(file || '')) throw new Error('Unexpected packed filename'); if (!/^[a-f0-9]{64}$/.test(expected || '')) throw new Error('Invalid packed SHA-256'); const archives = fs.readdirSync('.').filter(name => name.endsWith('.tgz')); if (archives.length !== 1 || archives[0] !== file) throw new Error('Expected exactly one downloaded archive'); const actual = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'); if (actual !== expected) throw new Error('Downloaded publish artifact SHA-256 mismatch')"
- name: Create GitHub Release
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
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 }}
ECC_RELEASE_PACKAGE: ${{ needs.verify.outputs.package_file }}
NPM_DIST_TAG: ${{ needs.verify.outputs.dist_tag }}
run: npm publish "./${ECC_RELEASE_PACKAGE}" --access public --provenance --tag "${NPM_DIST_TAG}"