|
| 1 | +# Publishes GH release for new versions, with compiled app attached |
| 2 | +# - Triggered when a git tag matching X.Y.0 (major or minor) is pushed |
| 3 | +# - Or if manually dispatched with any valid and existing tag specified |
| 4 | +# - Check out the source at the requested tag |
| 5 | +# - Run a clean production build (yarn build β dist/) |
| 6 | +# - Bundle dist/ + server.js + package.json + yarn.lock into tar.gz and zip |
| 7 | +# - Create a DRAFT GH release with auto-gen changelog and bundled artifacts attached |
| 8 | + |
| 9 | +name: π Release |
| 10 | + |
| 11 | +on: |
| 12 | + push: |
| 13 | + tags: |
| 14 | + - '*.*.0' |
| 15 | + workflow_dispatch: |
| 16 | + inputs: |
| 17 | + tag: |
| 18 | + description: 'Existing git tag to release (e.g. 2.1.0)' |
| 19 | + required: true |
| 20 | + type: string |
| 21 | + |
| 22 | +permissions: |
| 23 | + contents: write |
| 24 | + |
| 25 | +concurrency: |
| 26 | + group: release-${{ inputs.tag || github.ref_name }} |
| 27 | + cancel-in-progress: false |
| 28 | + |
| 29 | +jobs: |
| 30 | + release: |
| 31 | + name: π Build & Publish Release |
| 32 | + runs-on: ubuntu-latest |
| 33 | + steps: |
| 34 | + - name: Resolve tag π·οΈ |
| 35 | + id: tag |
| 36 | + env: |
| 37 | + INPUT_TAG: ${{ inputs.tag }} |
| 38 | + EVENT_NAME: ${{ github.event_name }} |
| 39 | + REF: ${{ github.ref }} |
| 40 | + run: | |
| 41 | + if [ "$EVENT_NAME" = "workflow_dispatch" ]; then |
| 42 | + TAG="$INPUT_TAG" |
| 43 | + else |
| 44 | + TAG="${REF#refs/tags/}" |
| 45 | + fi |
| 46 | + if ! echo "$TAG" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then |
| 47 | + echo "::error::Invalid tag '$TAG'. Expected semver (e.g. 2.1.0)." |
| 48 | + exit 1 |
| 49 | + fi |
| 50 | + echo "tag=$TAG" >> "$GITHUB_OUTPUT" |
| 51 | + echo "Releasing tag: $TAG" |
| 52 | +
|
| 53 | + - name: Checkout source at tag ποΈ |
| 54 | + uses: actions/checkout@v6 |
| 55 | + with: |
| 56 | + ref: refs/tags/${{ steps.tag.outputs.tag }} |
| 57 | + fetch-depth: 0 |
| 58 | + |
| 59 | + - name: Setup Node.js π§ |
| 60 | + uses: actions/setup-node@v6 |
| 61 | + with: |
| 62 | + node-version: '22' |
| 63 | + cache: 'yarn' |
| 64 | + |
| 65 | + - name: Install dependencies π¦ |
| 66 | + run: yarn install --frozen-lockfile |
| 67 | + |
| 68 | + - name: Build for production ποΈ |
| 69 | + run: yarn build |
| 70 | + |
| 71 | + - name: Verify build output β
|
| 72 | + run: | |
| 73 | + if [ ! -d "dist/client" ]; then |
| 74 | + echo "::error::Build failed: dist/client directory not created" |
| 75 | + exit 1 |
| 76 | + fi |
| 77 | + if [ ! -f "dist/server/entry.mjs" ]; then |
| 78 | + echo "::error::Build failed: SSR entry not found" |
| 79 | + exit 1 |
| 80 | + fi |
| 81 | + echo "β
Build successful" |
| 82 | +
|
| 83 | + - name: Package release artifacts ποΈ |
| 84 | + id: package |
| 85 | + env: |
| 86 | + TAG: ${{ steps.tag.outputs.tag }} |
| 87 | + run: | |
| 88 | + STAGING="web-check-${TAG}" |
| 89 | + rm -rf "$STAGING" |
| 90 | + mkdir -p "$STAGING" |
| 91 | + cp -r dist api public "$STAGING/" |
| 92 | + cp server.js package.json yarn.lock "$STAGING/" |
| 93 | + [ -f LICENSE ] && cp LICENSE "$STAGING/" || true |
| 94 | + [ -f README.md ] && cp README.md "$STAGING/" || true |
| 95 | +
|
| 96 | + TARBALL="${STAGING}.tar.gz" |
| 97 | + ZIPFILE="${STAGING}.zip" |
| 98 | + tar -czf "$TARBALL" "$STAGING" |
| 99 | + zip -qr "$ZIPFILE" "$STAGING" |
| 100 | +
|
| 101 | + ls -lh "$TARBALL" "$ZIPFILE" |
| 102 | + echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT" |
| 103 | + echo "zipfile=$ZIPFILE" >> "$GITHUB_OUTPUT" |
| 104 | +
|
| 105 | + - name: Create draft release π |
| 106 | + id: release |
| 107 | + uses: softprops/action-gh-release@v3 |
| 108 | + with: |
| 109 | + tag_name: ${{ steps.tag.outputs.tag }} |
| 110 | + name: Web-Check v${{ steps.tag.outputs.tag }} |
| 111 | + draft: true |
| 112 | + prerelease: false |
| 113 | + generate_release_notes: true |
| 114 | + fail_on_unmatched_files: true |
| 115 | + files: | |
| 116 | + ${{ steps.package.outputs.tarball }} |
| 117 | + ${{ steps.package.outputs.zipfile }} |
| 118 | + token: ${{ secrets.BOT_TOKEN || secrets.GITHUB_TOKEN }} |
| 119 | + |
| 120 | + - name: Job summary π |
| 121 | + if: always() |
| 122 | + env: |
| 123 | + TAG: ${{ steps.tag.outputs.tag }} |
| 124 | + REPO_URL: ${{ github.server_url }}/${{ github.repository }} |
| 125 | + RELEASE_URL: ${{ steps.release.outputs.url }} |
| 126 | + RELEASE_OUTCOME: ${{ steps.release.outcome }} |
| 127 | + run: | |
| 128 | + { |
| 129 | + echo "## π Release" |
| 130 | + echo "" |
| 131 | + echo "| Step | Result |" |
| 132 | + echo "|------|--------|" |
| 133 | + if [ -n "$TAG" ]; then |
| 134 | + echo "| Tag | [\`${TAG}\`](${REPO_URL}/releases/tag/${TAG}) |" |
| 135 | + else |
| 136 | + echo "| Tag | β Could not resolve |" |
| 137 | + fi |
| 138 | + if [ "$RELEASE_OUTCOME" = "success" ]; then |
| 139 | + if [ -n "$RELEASE_URL" ]; then |
| 140 | + echo "| Draft release | β
[View draft](${RELEASE_URL}) |" |
| 141 | + else |
| 142 | + echo "| Draft release | β
[View releases](${REPO_URL}/releases) |" |
| 143 | + fi |
| 144 | + echo "| Artifacts | \`web-check-${TAG}.tar.gz\`, \`web-check-${TAG}.zip\` |" |
| 145 | + else |
| 146 | + echo "| Draft release | β Failed (outcome: ${RELEASE_OUTCOME:-unknown}) |" |
| 147 | + fi |
| 148 | + } >> "$GITHUB_STEP_SUMMARY" |
0 commit comments