Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions .github/actions/fetch-release-secrets/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Fetch Release Secrets
description: Fetches release environment variables from AWS SSM Parameter Store and Secrets Manager

inputs:
aws-region:
description: 'AWS region where secrets are stored'
required: false
default: 'us-west-2'
role-to-assume:
description: 'IAM role ARN to assume for secrets access'
required: true
parameter-path:
description: 'SSM parameter path prefix'
required: false
default: '/patchwave-analysis/ci/release/env-vars'
certificate-secret-id:
description: 'Secrets Manager secret ID for the signing certificate'
required: false
default: 'patchwave-analysis/ci/release/env-vars/MACOS_SIGN_P12'

runs:
using: 'composite'
steps:
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1
with:
role-to-assume: ${{ inputs.role-to-assume }}
aws-region: ${{ inputs.aws-region }}

- name: Fetch MACOS_SIGN_P12 from Secrets Manager
shell: bash
env:
CERTIFICATE_SECRET_ID: ${{ inputs.certificate-secret-id }}
run: |
set -euo pipefail

value=$(aws secretsmanager get-secret-value \
--secret-id "$CERTIFICATE_SECRET_ID" \
--query 'SecretString' \
--output text)

echo "::add-mask::$value"

{
echo "MACOS_SIGN_P12<<EOF_MACOS_SIGN_P12"
echo "$value"
echo "EOF_MACOS_SIGN_P12"
} >> "$GITHUB_ENV"

echo "Exported: MACOS_SIGN_P12 (from Secrets Manager)"

- name: Fetch secrets from SSM
shell: bash
env:
PARAMETER_PATH: ${{ inputs.parameter-path }}
run: |
set -euo pipefail

PARAM_PATH="$PARAMETER_PATH"

PARAMS=$(aws ssm get-parameters-by-path \
--path "$PARAM_PATH" \
--recursive \
--with-decryption \
--query 'Parameters[*].[Name,Value]' \
--output text)

if [ -z "$PARAMS" ]; then
echo "::error::No parameters found under path: $PARAM_PATH"
exit 1
fi

while IFS=$'\t' read -r name value; do
env_var="${name##*/}"

echo "::add-mask::$value"

{
echo "${env_var}<<EOF_${env_var}"
echo "$value"
echo "EOF_${env_var}"
} >> "$GITHUB_ENV"

echo "Exported: $env_var"
done <<< "$PARAMS"
35 changes: 35 additions & 0 deletions .github/workflows/lint-pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Lint PR title

on:
pull_request:
types: [opened, edited, reopened, synchronize]

permissions: {}

jobs:
lint:
name: Conventional commit
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
pull-requests: read
steps:
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# Keep in sync with release-please-config.json changelog-sections.
types: |
feat
fix
perf
deps
revert
docs
chore
style
refactor
test
build
ci
requireScope: false
34 changes: 34 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Release PR

on:
push:
branches: [main]

permissions: {}

jobs:
release-please:
name: release-please
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
steps:
# release-please runs with an app token (not GITHUB_TOKEN) so the tag it
# pushes on release triggers the tag-gated release.yml workflow.
- name: Generate ContextBridge app token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ secrets.CB_PR_AUTOMATION_APP_ID }}
private-key: ${{ secrets.CB_PR_AUTOMATION_APP_PRIVATE_KEY }}
owner: contextbridge
repositories: patchwave-analysis
permission-contents: write
permission-pull-requests: write

- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
with:
token: ${{ steps.app-token.outputs.token }}
target-branch: main
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
44 changes: 44 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Release

on:
push:
tags: ['v*']

permissions: {}

jobs:
test:
name: Tests
uses: ./.github/workflows/test.yml
permissions:
contents: read

release:
name: GoReleaser
needs: test
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write # create/update the GitHub Release and upload artifacts
id-token: write # OIDC: assume the AWS release role for signing secrets
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false

- uses: ./.github/actions/bootstrap

- name: Fetch release secrets
uses: ./.github/actions/fetch-release-secrets
with:
role-to-assume: ${{ secrets.RELEASE_ROLE_ARN }}

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7.2.1
with:
version: '~> v2'
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_CURRENT_TAG: ${{ github.ref_name }}
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ jobs:
run: bun run lint
- name: Typecheck
run: bun run typecheck
- name: Shellcheck and test analyze.sh
run: |
shellcheck scripts/analyze.sh scripts/analyze.test.sh
sh scripts/analyze.test.sh

test:
name: Test
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/zizmor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: zizmor

on:
pull_request:
push:
branches: [main]

permissions: {}

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

jobs:
zizmor:
name: zizmor
runs-on: ubuntu-24.04
permissions:
contents: read
actions: read # zizmor reads workflow run metadata for online audits
security-events: write # upload SARIF to GitHub code scanning
steps:
# paths-filter needs a git checkout — it runs `git branch --show-current`
# before any API fallback, and fails outside a working tree.
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

# Audit only when something zizmor cares about changed. Keeps the job a
# required check that always reports a status, while skipping the
# API-heavy audit (and SARIF upload) on unrelated PRs.
- name: Detect workflow changes
id: changes
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
filters: |
github:
- '.github/**'
- 'zizmor.yml'

- name: Install uv
if: steps.changes.outputs.github == 'true'
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0

- name: Run zizmor
if: steps.changes.outputs.github == 'true'
run: uvx zizmor --format=sarif .github/ > zizmor.sarif
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload zizmor SARIF to code scanning
if: steps.changes.outputs.github == 'true'
uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
with:
sarif_file: zizmor.sarif
category: zizmor
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ node_modules
# output
out
dist
dist-release
*.tgz

# dev-only fixture for the report web app
Expand Down
64 changes: 64 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
version: 2
project_name: patchwave-analysis

# The before hook builds the embedded web report into dist/report-web/, so
# goreleaser can't also own dist/ — it requires its own output dir to be empty
# after hooks run. Keep goreleaser's artifacts separate.
dist: dist-release

before:
hooks:
- bun install --frozen-lockfile
# The CLI embeds dist/report-web/index.html via a `with { type: 'text' }`
# import, so the web report must be built before the binary is compiled.
# `release --clean` wipes dist/ before these hooks run, so the order holds.
- bun run build:report-web

builds:
- id: patchwave-analysis
builder: bun
main: ./src/index.ts
binary: patchwave-analysis
targets:
- bun-darwin-arm64
- bun-darwin-x64
- bun-linux-x64
flags:
- --compile

# Version-less archive names keep the GitHub `releases/latest/download/<asset>`
# URLs stable across releases, which is what scripts/analyze.sh fetches.
archives:
- formats: [tar.gz]
name_template: 'patchwave-analysis_{{ .Os }}_{{ .Arch }}'

checksum:
name_template: checksums.txt

notarize:
macos:
- enabled: '{{ isEnvSet "MACOS_SIGN_P12" }}'
ids:
- patchwave-analysis
sign:
certificate: '{{ .Env.MACOS_SIGN_P12 }}'
password: '{{ .Env.MACOS_SIGN_PASSWORD }}'
entitlements: ./entitlements.plist
notarize:
issuer_id: '{{ .Env.MACOS_NOTARY_ISSUER_ID }}'
key_id: '{{ .Env.MACOS_NOTARY_KEY_ID }}'
key: '{{ .Env.MACOS_NOTARY_KEY }}'
wait: true
timeout: 20m

# release-please owns the GitHub Release and its changelog body; goreleaser only
# attaches artifacts to the release release-please already created for the tag.
changelog:
disable: true

release:
draft: false
prerelease: auto
mode: keep-existing
extra_files:
- glob: ./scripts/analyze.sh
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.0.1"
}
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,28 @@ Given an org or user, the report covers:

## Run it

You need a GitHub token with `repo` and `read:org` scopes. For CVE metrics, add `security_events`.
You need a GitHub token with `repo` and `read:org` scopes (add `security_events` for CVE metrics). The CLI resolves it from `GITHUB_TOKEN`, then `GH_TOKEN`, then `gh auth token` — so if you're signed in with the `gh` CLI there's nothing to set.

### With Bun installed
### One-off run (recommended)

```sh
bunx patchwave-analysis@latest <your-org>
bash -c "$(curl -fsSL https://patchwave.ai/analyze.sh)"
```

### With the gh CLI installed
This downloads the signed binary for your platform from the latest release, verifies its checksum, runs the interactive session, and cleans up after itself — nothing is installed. The report is written to your current directory. Pin a specific release with `PW_VERSION`:

```sh
bunx patchwave-analysis@latest <your-org>
# auth is auto-resolved via `gh auth token`
PW_VERSION=v0.1.0 bash -c "$(curl -fsSL https://patchwave.ai/analyze.sh)"
```

Token resolution order: `GITHUB_TOKEN` env var, then `GH_TOKEN` env var, then `gh auth token`.
### Download the binary yourself

Grab the archive for your platform from the [latest release](https://github.com/contextbridge/patchwave-analysis/releases/latest), then:

```sh
tar -xzf patchwave-analysis_darwin_arm64.tar.gz
./patchwave-analysis
```

### From source

Expand All @@ -41,7 +47,7 @@ git clone https://github.com/contextbridge/patchwave-analysis
cd patchwave-analysis
bun install
bun run build:report-web
bun run src/index.ts <your-org>
bun run src/index.ts
```

For local report UI development:
Expand Down
Loading
Loading