1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions: {}

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest
    permissions:
      contents: read # checkout
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false
      - uses: ruby/setup-ruby@bec3f19a76460dbe12f60def7d1a77585f07516c # v1
        with:
          bundler-cache: true
      - run: bundle exec rake

  zizmor:
    name: Audit workflows
    runs-on: ubuntu-latest
    permissions:
      contents: read # checkout
      security-events: write # upload SARIF to code scanning
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false
      - uses: zizmorcore/zizmor-action@cc914d7f3750a2d13d75c7f184a1060aa0e9d482 # v0.6.4

  build:
    name: Build
    needs: test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    permissions:
      contents: write # create GitHub release
      packages: write # push container image to GHCR
    outputs:
      version: ${{ steps.meta.outputs.version }}
      image: ghcr.io/${{ github.repository }}:${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false
      - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
      - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - id: short-sha
        run: echo "sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
      - uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
        id: meta
        with:
          images: ghcr.io/${{ github.repository }}
          flavor: latest=true
          tags: |
            type=raw,value={{date 'YYYYMMDD-HHmm'}}-${{ steps.short-sha.outputs.sha }}
      - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          build-args: |
            VERSION=${{ steps.meta.outputs.version }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
      - env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          STEPS_META_OUTPUTS_VERSION: ${{ steps.meta.outputs.version }}
        # --target names the commit explicitly: persist-credentials is
        # false, so the tag cannot ride the leftover git credentials.
        run: gh release create "${STEPS_META_OUTPUTS_VERSION}" --target "$GITHUB_SHA" --generate-notes

  # The release again, on Ruby HEAD with ZJIT on, tagged
  # <release>-ruby-head-zjit and ruby-head-zjit. A job of its own so a
  # HEAD that will not build or install the bundle holds up neither the
  # release nor the deploy.
  build-head:
    name: Build on Ruby HEAD with ZJIT
    needs: build
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    permissions:
      contents: read # checkout
      packages: write # push container image to GHCR
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false
      - uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
      - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
        id: meta
        with:
          images: ghcr.io/${{ github.repository }}
          flavor: latest=false
          tags: |
            type=raw,value=${{ needs.build.outputs.version }}-ruby-head-zjit
            type=raw,value=ruby-head-zjit
      - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
        with:
          context: .
          # Pulled fresh each time: HEAD is the point, so a cached base
          # would be yesterday's Ruby.
          pull: true
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          build-args: |
            RUBY_IMAGE=rubylang/ruby:master-noble
            RUBYOPT=--zjit
            VERSION=${{ needs.build.outputs.version }}-ruby-head-zjit
          cache-from: type=gha,scope=ruby-head-zjit
          cache-to: type=gha,scope=ruby-head-zjit,mode=max

  deploy:
    name: Deploy main preview
    needs: build
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    permissions:
      contents: read # checkout, for fly.toml
    environment:
      name: demo
      url: https://pro-tacts.fly.dev
    # One deploy at a time, and a newer main is the one worth having.
    concurrency:
      group: fly-deploy
      cancel-in-progress: true
    env:
      FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
      FLY_APP: pro-tacts
      FLY_ORG: personal
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
        with:
          persist-credentials: false

      - uses: superfly/flyctl-actions/setup-flyctl@ed8efb33836e8b2096c7fd3ba1c8afe303ebbff1 # 1.6

      # Created here rather than by hand, so the app is something this
      # repository can lose and get back. `status` is how the review-app
      # action asks the same question.
      - name: Create the app if it is not there yet
        run: flyctl status --app "$FLY_APP" >/dev/null 2>&1 || flyctl apps create "$FLY_APP" --org "$FLY_ORG"

      # The image the build job just pushed, rather than a second build
      # of the same commit: what the demo runs is then the release.
      - name: Deploy the released image
        env:
          NEEDS_BUILD_OUTPUTS_IMAGE: ${{ needs.build.outputs.image }}
        run: flyctl deploy --app "$FLY_APP" --config fly.toml --image "$NEEDS_BUILD_OUTPUTS_IMAGE"