|
| 1 | +name: public plugin gates |
| 2 | + |
| 3 | +on: |
| 4 | + pull_request: |
| 5 | + push: |
| 6 | + branches: [main] |
| 7 | + workflow_dispatch: |
| 8 | + inputs: |
| 9 | + after_rebundle: |
| 10 | + description: Set by the follow-up dispatch that gates an auto-rebundle commit. |
| 11 | + type: boolean |
| 12 | + default: false |
| 13 | + |
| 14 | +permissions: |
| 15 | + contents: read |
| 16 | + |
| 17 | +concurrency: |
| 18 | + group: public-plugin-${{ github.workflow }}-${{ github.ref }} |
| 19 | + cancel-in-progress: ${{ github.event_name == 'pull_request' }} |
| 20 | + |
| 21 | +jobs: |
| 22 | + verify: |
| 23 | + runs-on: ubuntu-latest |
| 24 | + timeout-minutes: 20 |
| 25 | + steps: |
| 26 | + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 |
| 27 | + with: |
| 28 | + fetch-depth: 0 |
| 29 | + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 |
| 30 | + with: |
| 31 | + node-version: "22" |
| 32 | + - name: Pack current screenrig/cli main |
| 33 | + run: | |
| 34 | + repository="screenrig/cli" |
| 35 | + filename="screenrig-cli.tgz" |
| 36 | + test "$(jq -r .state components.lock.json)" = resolved |
| 37 | + test "$(jq -r .cli.repository components.lock.json)" = "${repository}" |
| 38 | + # Pack current main. Do not refetch a SHA from components.lock.json; |
| 39 | + # that file is provenance of the tarball just packed. |
| 40 | + install -d "${RUNNER_TEMP}/screenrig-cli-source" |
| 41 | + git -C "${RUNNER_TEMP}/screenrig-cli-source" init --quiet |
| 42 | + git -C "${RUNNER_TEMP}/screenrig-cli-source" fetch --depth=1 \ |
| 43 | + "https://github.com/${repository}.git" main |
| 44 | + git -C "${RUNNER_TEMP}/screenrig-cli-source" checkout --quiet --detach FETCH_HEAD |
| 45 | + commit="$(git -C "${RUNNER_TEMP}/screenrig-cli-source" rev-parse HEAD)" |
| 46 | + # Stamp the same CalVer the CLI distributing job used when this |
| 47 | + # main HEAD is tagged vYY.MM.N, so the tarball hash is stable. |
| 48 | + cli_version="$(python3 scripts/calver.py cli-stamp --repository "${repository}" --commit "${commit}")" |
| 49 | + if [ -n "${cli_version}" ]; then |
| 50 | + export SCREENRIG_VERSION="${cli_version}" |
| 51 | + git -C "${RUNNER_TEMP}/screenrig-cli-source" fetch --depth=1 \ |
| 52 | + "https://github.com/${repository}.git" \ |
| 53 | + "refs/tags/v${cli_version}:refs/tags/v${cli_version}" || true |
| 54 | + fi |
| 55 | + npm --prefix "${RUNNER_TEMP}/screenrig-cli-source" ci |
| 56 | + npm --prefix "${RUNNER_TEMP}/screenrig-cli-source" run build |
| 57 | + install -d "${RUNNER_TEMP}/screenrig-cli" |
| 58 | + bash "${RUNNER_TEMP}/screenrig-cli-source/scripts/package-release.sh" \ |
| 59 | + "${RUNNER_TEMP}/screenrig-cli/${filename}" |
| 60 | + test -f "${RUNNER_TEMP}/screenrig-cli/${filename}" |
| 61 | + printf '%s' "${commit}" > "${RUNNER_TEMP}/screenrig-cli/commit" |
| 62 | + - name: Rebuild and verify the committed plugin bundle |
| 63 | + run: | |
| 64 | + filename="screenrig-cli.tgz" |
| 65 | + artifact="${RUNNER_TEMP}/screenrig-cli/${filename}" |
| 66 | + commit="$(cat "${RUNNER_TEMP}/screenrig-cli/commit")" |
| 67 | + packed="$(sha256sum "${artifact}" | awk '{print $1}')" |
| 68 | + locked="$(jq -r .cli.artifact.sha256 components.lock.json)" |
| 69 | + if [ "${packed}" != "${locked}" ]; then |
| 70 | + if [ "${GITHUB_REF}" != "refs/heads/main" ]; then |
| 71 | + echo "Committed CLI tarball provenance differs from current screenrig/cli main" >&2 |
| 72 | + echo "packed ${packed}" >&2 |
| 73 | + echo "recorded ${locked}" >&2 |
| 74 | + exit 1 |
| 75 | + fi |
| 76 | + python3 scripts/build-plugin.py --cli-artifact "${artifact}" --write-lock --cli-commit "${commit}" |
| 77 | + install -d "${RUNNER_TEMP}/screenrig-cli-bundle-update" |
| 78 | + cp components.lock.json "${RUNNER_TEMP}/screenrig-cli-bundle-update/components.lock.json" |
| 79 | + cp -a plugins/screenrig "${RUNNER_TEMP}/screenrig-cli-bundle-update/plugins-screenrig" |
| 80 | + fi |
| 81 | + python3 scripts/build-plugin.py --check --cli-artifact "${artifact}" |
| 82 | + python3 scripts/validate-plugin.py --cli-artifact "${artifact}" --cli-source "${RUNNER_TEMP}/screenrig-cli-source" |
| 83 | + python3 scripts/check-public-repo.py |
| 84 | + python3 scripts/test.py |
| 85 | + - name: Allow the unprivileged user namespaces Chromium's sandbox needs |
| 86 | + run: | |
| 87 | + # Ubuntu 23.10+ confines unprivileged user namespaces with AppArmor, |
| 88 | + # so Playwright's Chromium finds no usable sandbox. Lift that on this |
| 89 | + # throwaway runner rather than rendering with a weakened sandbox. |
| 90 | + # https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md |
| 91 | + knob=/proc/sys/kernel/apparmor_restrict_unprivileged_userns |
| 92 | + test -e "${knob}" || exit 0 |
| 93 | + echo 0 | sudo tee "${knob}" > /dev/null |
| 94 | + - name: Verify optional dashboard runtime and repeat rendering |
| 95 | + run: | |
| 96 | + plugins/screenrig/skills/screenrig-dashboard/scripts/screenrig-dashboard setup > "${RUNNER_TEMP}/dashboard-setup.json" |
| 97 | + dashboard_runtime="$(jq -r .data.runtime "${RUNNER_TEMP}/dashboard-setup.json")" |
| 98 | + "${dashboard_runtime}/bin/python" -m playwright install-deps chromium |
| 99 | + "${dashboard_runtime}/bin/python" -B scripts/test.py --browser DashboardTests |
| 100 | + - name: Upload rebuilt marketplace CLI when it drifted |
| 101 | + if: github.ref == 'refs/heads/main' |
| 102 | + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 |
| 103 | + with: |
| 104 | + name: screenrig-cli-bundle-update |
| 105 | + path: ${{ runner.temp }}/screenrig-cli-bundle-update/ |
| 106 | + # v4 omits dotfiles unless set; that deleted .claude-plugin/plugin.json. |
| 107 | + include-hidden-files: true |
| 108 | + if-no-files-found: ignore |
| 109 | + retention-days: 1 |
| 110 | + - name: Package deterministic plugin artifact |
| 111 | + if: github.ref != 'refs/heads/main' |
| 112 | + run: >- |
| 113 | + tar --sort=name --format=ustar --mtime=@0 --owner=0 --group=0 |
| 114 | + --numeric-owner --mode=u=rwX,go=rX -C plugins/screenrig -cf - . |
| 115 | + | gzip -n -9 > "${RUNNER_TEMP}/screenrig-plugin.tar.gz" |
| 116 | + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 |
| 117 | + if: github.ref != 'refs/heads/main' |
| 118 | + with: |
| 119 | + name: screenrig-plugin |
| 120 | + path: ${{ runner.temp }}/screenrig-plugin.tar.gz |
| 121 | + if-no-files-found: error |
| 122 | + retention-days: 14 |
| 123 | + |
| 124 | + distribute: |
| 125 | + if: github.repository == 'screenrig/plugin' && github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') |
| 126 | + needs: verify |
| 127 | + runs-on: ubuntu-latest |
| 128 | + timeout-minutes: 15 |
| 129 | + permissions: |
| 130 | + contents: write |
| 131 | + actions: write |
| 132 | + steps: |
| 133 | + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 |
| 134 | + with: |
| 135 | + fetch-depth: 0 |
| 136 | + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 |
| 137 | + continue-on-error: true |
| 138 | + with: |
| 139 | + name: screenrig-cli-bundle-update |
| 140 | + path: ${{ runner.temp }}/screenrig-cli-bundle-update |
| 141 | + - name: Publish current CLI bundle when screenrig/cli main moved |
| 142 | + env: |
| 143 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 144 | + AFTER_REBUNDLE: ${{ github.event.inputs.after_rebundle }} |
| 145 | + run: | |
| 146 | + update="${RUNNER_TEMP}/screenrig-cli-bundle-update/components.lock.json" |
| 147 | + if [ ! -f "${update}" ]; then |
| 148 | + echo "Committed marketplace CLI already matches screenrig/cli main" |
| 149 | + exit 0 |
| 150 | + fi |
| 151 | + old="$(jq -r .cli.artifact.sha256 components.lock.json)" |
| 152 | + new="$(jq -r .cli.artifact.sha256 "${update}")" |
| 153 | + if [ "${old}" = "${new}" ]; then |
| 154 | + echo "Bundled CLI digest unchanged; not committing" |
| 155 | + exit 0 |
| 156 | + fi |
| 157 | + if [ "${AFTER_REBUNDLE}" = "true" ]; then |
| 158 | + # This run exists to gate the rebundle commit the previous run |
| 159 | + # pushed. Rebundling again would push a fresh ungated tip and |
| 160 | + # dispatch forever, so never commit from this generation. |
| 161 | + old_commit="$(jq -r .cli.commit components.lock.json)" |
| 162 | + new_commit="$(jq -r .cli.commit "${update}")" |
| 163 | + if [ "${old_commit}" = "${new_commit}" ]; then |
| 164 | + # Packing screenrig/cli ${old_commit} is not byte-reproducible, so |
| 165 | + # the digest alone moved while the bundled CLI stayed put. Accept |
| 166 | + # the recorded provenance and leave this gated tip on main. |
| 167 | + echo "Bundled CLI is still screenrig/cli ${old_commit}; tarball digest is not reproducible" |
| 168 | + echo "recorded ${old}" |
| 169 | + echo "packed ${new}" |
| 170 | + exit 0 |
| 171 | + fi |
| 172 | + echo "This run gates rebundle tip $(git rev-parse HEAD), yet screenrig/cli main moved again" >&2 |
| 173 | + echo "recorded ${old_commit} ${old}" >&2 |
| 174 | + echo "packed ${new_commit} ${new}" >&2 |
| 175 | + echo "Not chaining another rebundle; the next push to main bundles ${new_commit}" >&2 |
| 176 | + exit 1 |
| 177 | + fi |
| 178 | + cp "${update}" components.lock.json |
| 179 | + rm -rf plugins/screenrig |
| 180 | + cp -a "${RUNNER_TEMP}/screenrig-cli-bundle-update/plugins-screenrig" plugins/screenrig |
| 181 | + # download-artifact restores files as 644. |
| 182 | + chmod 0755 plugins/screenrig/cli/dist/bin.js \ |
| 183 | + plugins/screenrig/skills/screenrig/scripts/screenrig \ |
| 184 | + plugins/screenrig/skills/screenrig/scripts/screenrig-plugin-freshness \ |
| 185 | + plugins/screenrig/skills/screenrig-dashboard/scripts/screenrig-dashboard \ |
| 186 | + plugins/screenrig/skills/screenrig-dashboard/scripts/dashboard.py |
| 187 | + git add -- plugins/screenrig |
| 188 | + if git diff --cached --quiet; then |
| 189 | + # Packing is not byte-reproducible, and a new release tag on the |
| 190 | + # same CLI commit changes its stamped version, so the digest moves |
| 191 | + # while the bundle agents install stays identical. Keep main as is. |
| 192 | + echo "Bundled plugin unchanged; only the CLI tarball digest moved, not committing" |
| 193 | + exit 0 |
| 194 | + fi |
| 195 | + git add -- components.lock.json |
| 196 | + git config user.name "github-actions[bot]" |
| 197 | + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" |
| 198 | + git commit -m "Bundle current screenrig/cli main" |
| 199 | + git push origin "HEAD:${GITHUB_REF_NAME}" |
| 200 | + tip="$(git rev-parse HEAD)" |
| 201 | + # A GITHUB_TOKEN push never re-fires `on: push`, so this rebundle tip |
| 202 | + # would carry no check runs at all. Dispatch these gates on the new |
| 203 | + # tip, flagged as the follow-up generation so it cannot chain forever. |
| 204 | + curl --proto '=https' --tlsv1.2 --fail --silent --show-error \ |
| 205 | + --request POST \ |
| 206 | + --header "Accept: application/vnd.github+json" \ |
| 207 | + --header "Authorization: Bearer ${GITHUB_TOKEN}" \ |
| 208 | + --header "X-GitHub-Api-Version: 2022-11-28" \ |
| 209 | + --data '{"ref":"main","inputs":{"after_rebundle":"true"}}' \ |
| 210 | + "${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/actions/workflows/ci.yml/dispatches" |
| 211 | + echo "Dispatched public plugin gates for rebundle tip ${tip}" |
| 212 | + - name: Package the distributing artifact |
| 213 | + # A release commits its version to main before tagging; pack main as is. |
| 214 | + run: >- |
| 215 | + tar --sort=name --format=ustar --mtime=@0 --owner=0 --group=0 |
| 216 | + --numeric-owner --mode=u=rwX,go=rX -C plugins/screenrig -cf - . |
| 217 | + | gzip -n -9 > "${RUNNER_TEMP}/screenrig-plugin.tar.gz" |
| 218 | + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 |
| 219 | + with: |
| 220 | + name: screenrig-plugin |
| 221 | + path: ${{ runner.temp }}/screenrig-plugin.tar.gz |
| 222 | + if-no-files-found: error |
| 223 | + retention-days: 14 |
| 224 | + |
| 225 | + secrets: |
| 226 | + runs-on: ubuntu-latest |
| 227 | + timeout-minutes: 10 |
| 228 | + steps: |
| 229 | + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 |
| 230 | + with: |
| 231 | + fetch-depth: 0 |
| 232 | + - name: Install checksum-pinned Gitleaks |
| 233 | + env: |
| 234 | + GITLEAKS_VERSION: 8.30.1 |
| 235 | + GITLEAKS_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb |
| 236 | + run: | |
| 237 | + install -d "${RUNNER_TEMP}/screenrig-tools/gitleaks" |
| 238 | + archive="${RUNNER_TEMP}/screenrig-tools/gitleaks.tar.gz" |
| 239 | + curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location \ |
| 240 | + "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ |
| 241 | + --output "${archive}" |
| 242 | + printf '%s %s\n' "${GITLEAKS_SHA256}" "${archive}" | sha256sum --check --status |
| 243 | + tar --extract --gzip --file "${archive}" \ |
| 244 | + --directory "${RUNNER_TEMP}/screenrig-tools/gitleaks" gitleaks |
| 245 | + chmod 0755 "${RUNNER_TEMP}/screenrig-tools/gitleaks/gitleaks" |
| 246 | + - name: Scan complete Git history for secrets |
| 247 | + run: >- |
| 248 | + "${RUNNER_TEMP}/screenrig-tools/gitleaks/gitleaks" git |
| 249 | + --redact --no-banner . |
0 commit comments