Publish packages to GitHub Release #9
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: Publish packages to GitHub Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Release tag (e.g. v1.2.3)" | |
| required: true | |
| type: string | |
| title: | |
| description: "Release title (optional). Defaults to the tag." | |
| required: false | |
| type: string | |
| prerelease: | |
| description: "Mark as prerelease?" | |
| required: false | |
| default: false | |
| type: boolean | |
| permissions: | |
| contents: write | |
| jobs: | |
| release: | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # Optional but recommended: ensure tag doesn't already exist | |
| - name: Check if tag already exists | |
| id: tagcheck | |
| run: | | |
| set -euo pipefail | |
| TAG="${{ inputs.tag }}" | |
| if git rev-parse "$TAG" >/dev/null 2>&1; then | |
| echo "Tag $TAG already exists locally." | |
| fi | |
| if git ls-remote --tags origin "$TAG" | grep -q "$TAG"; then | |
| echo "Tag $TAG already exists on origin. Refusing to overwrite." | |
| exit 1 | |
| fi | |
| # Create and push the tag pointing to the current commit | |
| - name: Create and push tag | |
| run: | | |
| set -euo pipefail | |
| TAG="${{ inputs.tag }}" | |
| git tag "$TAG" | |
| git push origin "$TAG" | |
| - name: Reclaim disk space | |
| uses: AdityaGarg8/remove-unwanted-software@v5 | |
| with: | |
| remove-dotnet: true | |
| remove-haskell: true | |
| remove-codeql: true | |
| remove-docker-images: true | |
| - name: Install pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: latest | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "24.10.0" | |
| cache: "pnpm" | |
| - name: Install dependencies | |
| run: pnpm install | |
| - name: Build packages | |
| run: pnpm nx run-many -t build --projects="packages/*" | |
| - name: Patch package.json monorepo deps to tarball URLs | |
| env: | |
| OLD_VER: "workspace:*" | |
| TAG: "1.0.0-alpha.20" | |
| BASE_URL: "https://github.com/nachooya/react-native-harness/releases/download" | |
| SCOPE: "@react-native-harness/" | |
| run: | | |
| set -euo pipefail | |
| node <<'NODE' | |
| const fs = require("fs"); | |
| const path = require("path"); | |
| const OLD_VER = process.env.OLD_VER; | |
| const TAG = process.env.TAG; | |
| const BASE_URL = process.env.BASE_URL; | |
| const SCOPE = process.env.SCOPE; | |
| // Recursively find all package.json files (excluding node_modules/dist) | |
| function findPackageJson(dir, out = []) { | |
| for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { | |
| if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") continue; | |
| const full = path.join(dir, entry.name); | |
| if (entry.isDirectory()) findPackageJson(full, out); | |
| else if (entry.isFile() && entry.name === "package.json") out.push(full); | |
| } | |
| return out; | |
| } | |
| function tarballUrl(pkgName) { | |
| // pkgName is like "@react-native-harness/runtime" | |
| const monorepoPackage = pkgName.startsWith(SCOPE) | |
| ? pkgName.slice(SCOPE.length) | |
| : pkgName; | |
| return `${BASE_URL}/${TAG}/react-native-harness-${monorepoPackage}-${TAG}.tgz`; | |
| } | |
| const files = findPackageJson(process.cwd()); | |
| let totalChanges = 0; | |
| for (const file of files) { | |
| const raw = fs.readFileSync(file, "utf8"); | |
| const pkg = JSON.parse(raw); | |
| let changed = false; | |
| // fields to patch | |
| const depFields = [ | |
| "dependencies", | |
| "devDependencies", | |
| "peerDependencies", | |
| "optionalDependencies", | |
| ]; | |
| for (const field of depFields) { | |
| if (!pkg[field]) continue; | |
| for (const [name, ver] of Object.entries(pkg[field])) { | |
| if (ver === OLD_VER && name.startsWith(SCOPE)) { | |
| pkg[field][name] = tarballUrl(name); | |
| changed = true; | |
| } | |
| } | |
| } | |
| if (changed) { | |
| fs.writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n"); | |
| console.log("Patched:", file); | |
| totalChanges++; | |
| } | |
| } | |
| console.log(`✅ Patched ${totalChanges} package.json files`); | |
| NODE | |
| - name: Create package tarballs | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| for pkg in ./packages/*; do | |
| if [ -f "$pkg/package.json" ]; then | |
| echo "Packing $pkg" | |
| (cd "$pkg" && pnpm pack --pack-destination ../../dist) | |
| fi | |
| done | |
| # Optional: generate checksums | |
| - name: Generate checksums | |
| run: | | |
| set -euo pipefail | |
| sha256sum dist/*.tgz > dist/checksums.txt | |
| - name: Create GitHub Release and upload assets | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ inputs.tag }} | |
| name: ${{ inputs.title || inputs.tag }} | |
| prerelease: ${{ inputs.prerelease }} | |
| generate_release_notes: true | |
| files: | | |
| dist/*.tgz | |
| dist/checksums.txt |