Actions for Forgejo runners because apparently they just cannot handle GitHub ones and I hate finding them
  • TypeScript 91.8%
  • JavaScript 3.6%
  • Shell 2.8%
  • Python 1.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
forgejo-bot 68c7026fc5 chore: rebuild action bundles
[skip ci]
2026-09-13 23:17:16 +00:00
.forgejo/workflows detect-markers: declarative custom-markers input + custom JSON output 2026-08-10 09:31:52 -07:00
ai-review chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
appstore-metadata-sync chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
appstore-testflight chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
cargo-audit Swap to Public org 2026-03-03 13:20:35 -08:00
cargo-get chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
cargo-publish-workspace chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
check-appwrite-storage chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
check-orphan-versions chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
ci-step-status chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
create-release-tag create-release-tag: shared idempotent release-tag action 2026-09-01 16:15:19 -07:00
detect-changes chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
detect-markers detect-markers: declarative custom-markers input + custom JSON output 2026-08-10 09:31:52 -07:00
dispatch-forgejo-workflow chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
download-artifact docs(download-artifact): record that --user is NOT a credential leak, because it looks like one 2026-08-14 09:24:17 -07:00
download-file chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
download-resumable feat(download-resumable): composite curl download with stall detection + byte-offset resume 2026-06-12 16:55:43 -07:00
e2e-image-smoke fix(e2e-image-smoke): pull+run the immutable tag not repo@digest (ZLayer daemon 422); retry manifest GET 2026-09-03 11:37:19 -07:00
examples Remove internal actions relocated to Zataverse/org-actions 2026-09-05 15:36:26 -07:00
github_format fix: fixed /tmp staging paths are shared by every job on the host 2026-08-04 14:17:34 -07:00
golangci-lint chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
google-play-metadata-sync chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
google-play-staged-rollout chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
helm-unittest chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
install-git-bash chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
intellitester chore: sync intellitester action.yml from Intellitester@c00669666ef0082f6f34f48379194a5d566f45c8 2026-05-11 18:55:46 +00:00
mint-appwrite-token mint-appwrite-token: mint per-file storage tokens via Appwrite Tokens API 2026-05-29 00:26:20 -07:00
prune-appwrite-storage chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
pypi-publish-workspace Fix python urls 2026-04-23 10:43:33 -07:00
release-continue release-continue: retry git push + coordinator dispatch on transient DNS/network 2026-07-24 23:47:43 -07:00
release-orchestrator feat(release-orchestrator): carry the [purge] decision in release state 2026-08-01 11:22:10 -07:00
release-service chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
resolve-version chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
retry retry: generic composite action to wrap a command with exponential-backoff retry 2026-09-04 14:58:00 -07:00
s3-cache chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-android-sdk chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-binaryen chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-bun chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-ccache chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-container-runtime Add setup-container-runtime (composite) and verify-artifact (node20) actions 2026-05-15 17:38:17 -07:00
setup-cuda-toolkit chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-deno chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-docker chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-dotnet chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-fastlane chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-flutter chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-go chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-gradle chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-helm chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-java chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-kind chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-kotlin chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-kubectl chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-libclang chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-msvc chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-ninja chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-node chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-php fix: fixed /tmp staging paths are shared by every job on the host 2026-08-04 14:17:34 -07:00
setup-pnpm chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-podman chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-python chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-ruby chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-rust chore: rebuild action bundles 2026-09-13 23:17:16 +00:00
setup-sccache chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-shell chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-swift chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-system-deps chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-tauri-cli chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-terraform chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-uniffi-bindgen-go Fix bc ai is dum 2026-05-20 23:02:07 -07:00
setup-wasi-sdk chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-zgit fix(setup-zgit): repair invalid YAML in the registries block (column-0 heredoc terminated the run: scalar) 2026-08-24 01:43:01 -04:00
setup-zig chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
setup-ztus chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
shared fix(platform): bun run typecheck has been red on main since the tar test landed 2026-07-31 02:48:25 -04:00
sonarqube chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
stamp-versions feat(stamp-versions): workspace-wide multi-ecosystem version stamper 2026-08-26 14:08:15 -07:00
submit-appstore-review chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
tauri-build-android chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
tauri-build-ios chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
trivy chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
tus-upload chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
upload-appstore chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
upload-appwrite-storage chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
upload-artifact up/download-artifact: --no-same-owner on tar extract (rootless remapped-root safe) 2026-08-04 10:01:45 -07:00
upload-firebase-app-distribution chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
upload-google-play chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
verify-artifact chore: rebuild action bundles 2026-09-05 22:39:17 +00:00
windows-sign-binaries windows signing: install Azure CLI via the MSI only, and validate az before trusting it 2026-08-17 11:29:45 -07:00
windows-trusted-signing windows signing: install Azure CLI via the MSI only, and validate az before trusting it 2026-08-17 11:29:45 -07:00
wrangler-deploy wrangler-deploy: composite action wrapping wrangler CLI 2026-05-31 18:44:46 -07:00
zgit-workspace zgit-workspace: treat a zero-member Project as a checkout-only success (thin members consume all deps from the registry) instead of a malformed-manifest hard error 2026-09-01 15:08:52 -07:00
zlayer-macos-daemon-pkg zlayer-macos-daemon-pkg: notarize via App Store Connect API key (asc-key-id/issuer/p8); sign every Mach-O before pkgbuild 2026-09-04 19:35:51 -07:00
.gitattributes Add action to build actions lmao 2026-04-14 14:41:24 -07:00
.gitignore Add action to build actions lmao 2026-04-14 14:41:24 -07:00
bun.lock Remove internal actions relocated to Zataverse/org-actions 2026-09-05 15:36:26 -07:00
LICENSE Initial commit 2025-11-30 21:31:00 +00:00
package.json Remove internal actions relocated to Zataverse/org-actions 2026-09-05 15:36:26 -07:00
README.md Remove internal actions relocated to Zataverse/org-actions 2026-09-05 15:36:26 -07:00
tsconfig.base.json Add action to build actions lmao 2026-04-14 14:41:24 -07:00

actions

Actions for Forgejo runners because apparently they just cannot handle GitHub ones and I hate finding them

Language Setup Actions

setup-go

Install Go from official releases.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-go@main
  with:
    go-version: '1.23'    # optional, default: 1.23
    cache: true           # optional, default: true

setup-node

Install Node.js with package manager support.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-node@main
  with:
    node-version: '22'           # optional, default: 22
    package-manager: pnpm        # optional: npm, yarn, pnpm (default: npm)
    cache: true                  # optional, default: true

setup-python

Install Python via uv (fast Python package manager).

- uses: https://forge.blackleafdigital.com/Public/actions/setup-python@main
  with:
    python-version: '3.12'    # optional, default: 3.12
    uv-version: latest        # optional, default: latest
    cache: true               # optional, default: true

setup-rust

Install Rust toolchain via rustup with optional sccache integration.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-rust@main
  with:
    toolchain: stable              # optional, default: stable
    components: rustfmt, clippy    # optional
    targets: x86_64-unknown-linux-musl  # optional

With sccache + S3

- uses: https://forge.blackleafdigital.com/Public/actions/setup-rust@main
  with:
    components: rustfmt, clippy
    sccache: true
    sccache-s3-bucket: ${{ secrets.S3_BUCKET }}
    sccache-s3-endpoint: https://${{ secrets.S3_ENDPOINT }}
    sccache-s3-region: ${{ secrets.S3_REGION }}
    sccache-s3-access-key-id: ${{ secrets.S3_ACCESS_KEY }}
    sccache-s3-secret-access-key: ${{ secrets.S3_ACCESS_SECRET }}
    sccache-key-prefix: sccache/myproject

setup-bun

Install Bun JavaScript runtime.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-bun@main
  with:
    bun-version: latest    # optional, default: latest

setup-deno

Install Deno JavaScript/TypeScript runtime.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-deno@main
  with:
    deno-version: latest    # optional, default: latest

setup-java

Install Java/JDK via Eclipse Temurin (Adoptium).

- uses: https://forge.blackleafdigital.com/Public/actions/setup-java@main
  with:
    java-version: '21'        # optional, default: 21
    distribution: temurin     # optional, default: temurin

setup-dotnet

Install .NET SDK via official Microsoft installer.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-dotnet@main
  with:
    dotnet-version: '8.0'    # optional, default: 8.0

setup-zig

Install Zig compiler.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-zig@main
  with:
    zig-version: '0.13.0'    # optional, default: 0.13.0 (or 'master')

setup-swift

Install Swift toolchain.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-swift@main
  with:
    swift-version: '5.10'    # optional, default: 5.10

Tool Setup Actions

setup-kind

Install Kind (Kubernetes in Docker) with optional cluster creation.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-kind@main
  with:
    kind-version: v0.24.0     # optional, default: v0.24.0
    cluster-name: kind        # optional, default: kind
    wait: '120s'              # optional, default: 120s
    create-cluster: true      # optional, default: true

setup-kubectl

Install kubectl CLI.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-kubectl@main
  with:
    kubectl-version: stable    # optional, default: stable (latest)

setup-helm

Install Helm package manager for Kubernetes.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-helm@main
  with:
    helm-version: latest    # optional, default: latest

setup-terraform

Install Terraform from HashiCorp.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-terraform@main
  with:
    terraform-version: latest    # optional, default: latest

setup-docker

Install Docker (Linux only, macOS requires Docker Desktop).

- uses: https://forge.blackleafdigital.com/Public/actions/setup-docker@main
  with:
    docker-version: latest    # optional, default: latest

setup-podman

Install Podman container engine.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-podman@main
  with:
    podman-version: latest    # optional, default: latest (system package)

setup-sccache

Standalone sccache setup with optional S3 backend.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-sccache@main
  with:
    version: v0.8.1                # optional
    bucket: ${{ secrets.S3_BUCKET }}
    endpoint: https://${{ secrets.S3_ENDPOINT }}
    region: ${{ secrets.S3_REGION }}
    access-key: ${{ secrets.S3_ACCESS_KEY }}
    secret-key: ${{ secrets.S3_ACCESS_SECRET }}
    key-prefix: sccache/myproject

setup-zgit

Download the prebuilt zgit CLI from a zGit release and add it to PATH. Resolves latest (or a pinned v tag) via the Forgejo releases API and selects the bare binary for the runner OS+arch (x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, aarch64-apple-darwin, x86_64-pc-windows-msvc.exe). zGit is a private repo, so a Forgejo token is required for both the release lookup and the asset download.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-zgit@main
  with:
    version: latest                              # optional, default: latest (or a tag like v0.0.12)
    forgejo-url: https://forge.blackleafdigital.com   # optional
    repo: BlackLeafDigital/zGit                  # optional
    token: ${{ secrets.FORGEJO_TOKEN }}          # required: zGit is private
    registries: '[{"name":"zreg","index":"sparse+https://registry.blackleafdigital.com/cargo/","token":"${{ secrets.ZREGISTRY_TOKEN }}"}]'  # optional; only the source-build fallback needs it
# outputs: cli-path, resolved-tag

When no prebuilt asset exists for the runner (currently macOS-arm64 and Windows-arm64, whose release legs aren't attached), this action builds zgit from source (cargo build -p zgit-cli), which pulls zparse-grammars from the private zreg cargo registry and 401s without a token. Pass registries — a JSON array of {"name","index","token"}, the same shape setup-rust accepts — to authenticate that build. Only each entry's token is load-bearing (zGit's own .cargo/config.toml already declares the indexes + the cargo:token provider); index is accepted for setup-rust parity. This makes the source-build order-independent of any setup-rust step. Leave it empty on platforms that have a prebuilt binary. Parsing needs jq.

zgit-workspace

Put a zGit Project's members on disk in one step: installs zgit (via setup-zgit), hard-verifies the super-repo is at the run commit, then zgit project sync --frozen. Replaces the hand-rolled materialize/verify shell scripts every zGit-Project repo used to duplicate.

- uses: actions/checkout@v4
- uses: https://forge.blackleafdigital.com/Public/actions/zgit-workspace@main
  with:
    token: ${{ secrets.FORGEJO_TOKEN }}          # required: member repos are private
    sha: ${{ github.sha }}                       # optional, default: github.sha
    working-directory: .                         # optional, default: . (dir holding .zproject.toml)
    version: latest                              # optional, default: latest
    forgejo-url: https://forge.blackleafdigital.com   # optional
    repo: BlackLeafDigital/zGit                  # optional
    heal-global-git-config: 'true'               # optional, default: true
    registries: '[{"name":"zreg","index":"sparse+https://registry.blackleafdigital.com/cargo/","token":"${{ secrets.ZREGISTRY_TOKEN }}"}]'  # optional; passed through to setup-zgit's source-build
# outputs: member-count, declared-count, sync-json, zgit-version

Fails the build (never warns) when: the token is empty, git rev-parse HEAD is not sha — the stale-warm-workspace failure mode that once shipped a binary built from a 19-commit-old tree — or fewer members land than .zproject.toml declares.

--frozen clones absent members so a cold runner works, never pulls present ones so a warm runner does no network I/O, and treats any lock/pin drift as a hard error. The token is passed through git's GIT_CONFIG_COUNT env protocol — never in a URL, in argv, or in a persisted config file — and is scoped to the forge host only. registries is forwarded verbatim to the setup-zgit step so the source-build fallback authenticates the private cargo registry (see setup-zgit above).

Submodule pin enforcement. After sync, a final step verifies every submodule-backed member is at its committed gitlink — the 160000 SHA the super-repo's tree records (git rev-parse HEAD:<path>), git's authoritative pin — re-fetching from the authoritative remote and hard-checking-out the exact commit if a warm/stale clone trails it. It enforces the gitlink, not the .zproject.lock submodule rows: the gitlink is updated automatically on every meta-repo commit and cannot drift, whereas a lock's submodule rows are a derived mirror that could lag and drag a build backward. (A member with no initialized .git is refused, not enforced, so bare submodule dirs can't leak git ops into the super-repo.)

Windows: every run: step here is shell: bash. On a Windows host executor with no bash on PATH, run https://forge.blackleafdigital.com/Public/actions/install-git-bash@main first — otherwise every step of this action fails to launch.

Security & Quality Actions

cargo-get

Extract metadata (version, name, description, authors, etc.) from Cargo.toml without needing Rust installed. Handles workspace inheritance ({ workspace = true } fields).

- uses: https://forge.blackleafdigital.com/Public/actions/cargo-get@main
  with:
    path: .                    # optional, default: .  (dir or Cargo.toml path)
    workspace-member: mycrate  # optional: specific workspace member to query

Outputs include package_name, package_version, package_version_{major,minor,patch,pre,build}, package_edition, package_rust_version, package_authors, package_description, package_license, package_publish, workspace_members, is_workspace, and more.

cargo-publish-workspace

Idempotent, rate-limit-aware publisher for Rust workspaces. Skips crates already live at the target version on crates.io and retries each crate with per-crate exponential backoff (up to 5 minutes) on HTTP 429. Publishes in topological dependency order.

- uses: https://forge.blackleafdigital.com/Public/actions/cargo-publish-workspace@main
  with:
    version: ${{ needs.version.outputs.version }}
    registry: crates-io                                   # optional, default: crates-io
    exclude: blazen-py,blazen-node                        # optional: comma-separated skip list
    cargo-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
    max-backoff-seconds: '300'                            # optional, default: 300
    max-attempts-per-crate: '5'                           # optional, default: 5

Requires setup-rust to have run earlier in the job. Outputs published, skipped, total (comma-separated lists + count).

cargo-audit

Run security audit for Rust dependencies.

- uses: https://forge.blackleafdigital.com/Public/actions/cargo-audit@main
  with:
    deny: unmaintained  # optional: fail on unmaintained crates

trivy

Run Trivy security scanner for vulnerabilities in code, containers, and dependencies.

- uses: https://forge.blackleafdigital.com/Public/actions/trivy@main
  with:
    scan-type: fs                    # optional: fs, image, repo (default: fs)
    severity: CRITICAL,HIGH          # optional (default: CRITICAL,HIGH)
    exit-code: '1'                   # optional: fail on findings (default: 1)
    ignore-unfixed: false            # optional: ignore unpatched vulns
    format: table                    # optional: table, json, sarif

Scan Docker image

- uses: https://forge.blackleafdigital.com/Public/actions/trivy@main
  with:
    scan-type: image
    image-ref: myapp:latest
    severity: CRITICAL

sonarqube

Run SonarQube code quality analysis.

- uses: https://forge.blackleafdigital.com/Public/actions/sonarqube@main
  with:
    host-url: ${{ secrets.SONAR_HOST_URL }}
    token: ${{ secrets.SONAR_TOKEN }}
    project-key: my-project
    project-name: My Project         # optional (defaults to project-key)
    sources: src                     # optional (default: .)
    exclusions: '**/*.test.ts'       # optional: patterns to exclude

ai-review

AI-powered code review using Z.AI (GLM) or other OpenAI-compatible providers.

- uses: https://forge.blackleafdigital.com/Public/actions/ai-review@main
  with:
    api-key: ${{ secrets.ZAI_API_KEY }}
    forgejo-token: ${{ secrets.FORGEJO_TOKEN }}
    forgejo-url: https://forge.blackleafdigital.com
    model: glm-4.6                   # optional (default: glm-4.6)
    base-url: https://api.z.ai/v1    # optional (default: Z.AI)
    provider: openai                 # optional: openai, anthropic, ollama

With OpenAI

- uses: https://forge.blackleafdigital.com/Public/actions/ai-review@main
  with:
    api-key: ${{ secrets.OPENAI_API_KEY }}
    forgejo-token: ${{ secrets.FORGEJO_TOKEN }}
    forgejo-url: https://forge.blackleafdigital.com
    base-url: https://api.openai.com/v1
    model: gpt-4o

Mobile App Release

End-to-end Tauri 2.x mobile release pipeline — toolchain setup, signed builds, store uploads, TestFlight / staged rollouts, metadata sync, and App Review submission. See examples/release-mobile.yml for the canonical workflow that chains these actions together (parallel iOS + Android jobs with ci-step-status gating, zrelay-notify on success/failure, and a Forgejo release at the end).

setup-fastlane

Install fastlane (and bundler) onto the runner. Requires Ruby on PATH (run setup-ruby first). Detects a Gemfile and falls back to gem install fastlane when none is present.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-ruby@main
  with:
    ruby-version: '3.3'
- uses: https://forge.blackleafdigital.com/Public/actions/setup-fastlane@main
  with:
    fastlane-version: latest    # optional, default: latest
    bundler-version: latest     # optional, default: latest
    gemfile-dir: .              # optional, default: .

setup-tauri-cli

Install the Tauri 2.x CLI via cargo-binstall (preferred, prebuilt) or cargo install, with optional iOS / Android Rust target installation.

- uses: https://forge.blackleafdigital.com/Public/actions/setup-tauri-cli@main
  with:
    tauri-version: latest    # optional, default: latest
    target: ios              # optional: desktop | ios | android | all
    use-prebuilt: true       # optional, default: true (prefer cargo-binstall)

tauri-build-ios

Build a Tauri 2.x iOS app and produce a signed IPA ready for App Store / TestFlight / ad-hoc distribution. Outputs ipa-path, bundle-version, and bundle-short-version.

- uses: https://forge.blackleafdigital.com/Public/actions/tauri-build-ios@main
  with:
    bundle-id: com.blackleaf.imaige
    code-sign-identity: ${{ secrets.IOS_CODE_SIGN_IDENTITY }}
    provisioning-profile-name: ${{ secrets.IOS_PROVISIONING_PROFILE_NAME }}
    team-id: ${{ secrets.IOS_TEAM_ID }}
    target: aarch64-apple-ios    # optional, default: aarch64-apple-ios
    export-method: app-store     # optional: app-store | ad-hoc | enterprise | development

tauri-build-android

Build a signed Android AAB (and optional APK) from a Tauri 2.x project using the Tauri CLI + Gradle. Decodes a base64 keystore on the fly. Outputs aab-path, apk-path, version-code, version-name.

- uses: https://forge.blackleafdigital.com/Public/actions/tauri-build-android@main
  with:
    package-name: com.blackleaf.imaige
    keystore-base64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
    keystore-password: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
    key-alias: ${{ secrets.ANDROID_KEY_ALIAS }}
    key-password: ${{ secrets.ANDROID_KEY_PASSWORD }}
    targets: aarch64-linux-android    # optional, CSV of Android Rust targets
    build-apk: false                  # optional: also produce a signed APK

upload-appstore

Upload an IPA to App Store Connect (TestFlight) using API-key authentication. With wait-for-processing: true it polls App Store Connect until the build reaches VALID (or fails) and exposes build-id + processing-state for downstream TestFlight / review steps. Always emits bundle-version and bundle-short-version parsed from the IPA.

- uses: https://forge.blackleafdigital.com/Public/actions/upload-appstore@main
  with:
    ipa-path: ${{ steps.build-ios.outputs.ipa-path }}
    api-key-id: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
    issuer-id: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
    api-private-key: ${{ secrets.APP_STORE_CONNECT_API_PRIVATE_KEY }}
    wait-for-processing: 'true'    # optional, default: false
    timeout-minutes: '60'          # optional, default: 60
    poll-interval-seconds: '30'    # optional, default: 30

appstore-testflight

Attach a previously-uploaded build to TestFlight beta groups, set what-to-test localizations, toggle auto-notify, and optionally submit for external beta review.

- uses: https://forge.blackleafdigital.com/Public/actions/appstore-testflight@main
  with:
    api-key-id: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
    issuer-id: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
    api-private-key: ${{ secrets.APP_STORE_CONNECT_API_PRIVATE_KEY }}
    bundle-id: com.blackleaf.imaige
    build-version: ${{ steps.build-ios.outputs.bundle-version }}
    beta-groups: 'Internal QA,External Beta'
    submit-for-beta-review: auto    # optional: true | false | auto

appstore-metadata-sync

Sync App Store Connect metadata, screenshots, and app preview videos via fastlane deliver. Requires setup-fastlane to have run earlier in the job. Reads the standard fastlane/metadata + fastlane/screenshots directory layout.

- uses: https://forge.blackleafdigital.com/Public/actions/appstore-metadata-sync@main
  with:
    api-key-id: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
    issuer-id: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
    api-private-key: ${{ secrets.APP_STORE_CONNECT_API_PRIVATE_KEY }}
    app-identifier: com.blackleaf.imaige
    version-string: 1.2.3            # optional: bump to / target this version
    skip-screenshots: false          # optional, default: false
    skip-metadata: false             # optional, default: false

submit-appstore-review

Submit an App Store Connect version for review via the App Store Connect API. Supports auto-release on approval, phased rollout, and per-locale "what's new" text.

- uses: https://forge.blackleafdigital.com/Public/actions/submit-appstore-review@main
  with:
    api-key-id: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
    issuer-id: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
    api-private-key: ${{ secrets.APP_STORE_CONNECT_API_PRIVATE_KEY }}
    bundle-id: com.blackleaf.imaige
    version-string: 1.2.3
    build-version: ${{ steps.build-ios.outputs.bundle-version }}
    auto-release: 'true'    # optional, default: true (AFTER_APPROVAL)
    phased-release: 'false' # optional, default: false (7-day phased rollout)

upload-google-play

Upload an AAB to Google Play via the Developer API using a service account. Supports release-track selection, staged rollout with a user-fraction, per-locale release notes (JSON), and optional ProGuard/R8 mapping file upload after the bundle upload.

- uses: https://forge.blackleafdigital.com/Public/actions/upload-google-play@main
  with:
    aab-path: ${{ steps.build-android.outputs.aab-path }}
    package-name: com.blackleaf.imaige
    service-account-json: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
    track: internal               # optional: internal | alpha | beta | production
    user-fraction: '0.10'         # optional: start a staged rollout at 10%
    release-notes-json: '[{"language":"en-US","text":"Bug fixes."}]'  # optional
    mapping-file: app/build/outputs/mapping/release/mapping.txt        # optional

google-play-staged-rollout

Adjust an existing Google Play release's staged-rollout userFraction and status (set / halt / resume / complete) without re-uploading the bundle. Useful for promoting an internal upload to a wider rollout or pausing a rollout that's misbehaving.

- uses: https://forge.blackleafdigital.com/Public/actions/google-play-staged-rollout@main
  with:
    service-account-json: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
    package-name: com.blackleaf.imaige
    track: production
    action: set            # set | halt | resume | complete
    user-fraction: '0.10'  # required for set / resume

google-play-metadata-sync

Sync per-locale Google Play store listings (title, short / full description, video URL) and image assets (icon, feature graphic, phone / tablet / TV / wear screenshots) via the Play Developer API v3. Reads the fastlane/metadata/android/<locale>/... directory layout.

- uses: https://forge.blackleafdigital.com/Public/actions/google-play-metadata-sync@main
  with:
    service-account-json: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
    package-name: com.blackleaf.imaige
    listings-dir: ./fastlane/metadata/android    # optional, default shown
    image-types: 'phoneScreenshots,featureGraphic,icon'  # optional

CI / Release Orchestration

detect-markers

Parse commit-message markers into job-gating outputs. Single source of truth for the house marker convention — replaces the inline contains(...)/bash that every repo used to duplicate.

- id: m
  uses: https://forge.blackleafdigital.com/Public/actions/detect-markers@main
  with:
    message: ${{ github.event.head_commit.message }}   # push event
    # message: ${{ github.event.workflow_run.head_commit.message }}  # workflow_run
    # default-bump: patch                               # optional
    # subject-only: true                                # optional — see below

⚠️ The body arms the marker — and subject-only: is the fix

Every marker test is a substring/regex match over the whole string handed to the action. When message: is empty the action falls back to git log -1, and that fallback defaults to --pretty=%B — subject and body. So a commit whose body merely mentions a marker as prose arms it.

This is not theoretical. A Blazen commit that documented [release], [purge] and [v0.8.0] in its body parsed as version=0.8.0 purge=true; only the [skip] in its subject held release at false. That repo's release chain walks itself to a crates.io publish, which is permanent and un-replaceable.

    subject-only: true    # fallback reads `--pretty=%s` (subject) not `%B`
  • Default is false — today's exact behaviour, unchanged for every existing caller. This input is purely opt-in.
  • It affects only the git log fallback. A non-empty message: is always parsed verbatim, whatever subject-only is set to — so a caller that passes ${{ github.event.head_commit.message }} (the full message, body included) gets no protection from this input; it must pass a subject instead, or switch to message: '' + subject-only: true.
  • Truthy spellings: true/yes/on/1, any case. Anything else is false, so a typo fails open to today's behaviour, never accidentally-on.
  • The step log names the source it parsed (input / %s / %B), so a run is self-evidencing.

The message: '' trap. The fallback is keyed on the value being empty, not on the input being absent — so an explicitly-passed message: '' re-opens it. That is the documented ZLayer auto-tag.yaml idiom (it is how a dispatched workflow re-derives markers off the checked-out sha), and it is deliberately preserved. If you use it, pair it with subject-only: true.

Regardless of any of the above, the standing rule holds: never write a bracketed marker literally in a commit message you do not intend to fire — not even in the body. Break it up ([re + lease]) or spell it out.

Marker Output Meaning
[release] (or [release minor] / [release:major]) release=true opt into the publish/release chain
[np] / [no-publish] no-publish=true → release=false suppress publishing
[skip] / [skip-checks] / [skip-ci] / [skip ci] / [ci skip] skip=true → release=false skip CI/checks
[fast] fast=true skip test/lint/audit jobs; release still fires
[purge] purge=true force fresh rebuild / invalidate cached artifacts
[major] / [minor] / [patch] bump=<x> bump type (default patch)
[vX.Y.Z] (opt. -pre) version=X.Y.Z[-pre] explicit version override
push event + release dry-run=false live publish; otherwise true

Outputs: release, no-publish, skip, fast, purge, bump, version, dry-run.

Canonical marker-driven release pattern (push-triggered ci.yml → dispatch release.yml, since Forgejo's on: workflow_run hook fires unreliably):

  dispatch-release:
    runs-on: ubuntu-latest
    needs: [lint, test]            # gate on green CI
    if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - id: m
        uses: https://forge.blackleafdigital.com/Public/actions/detect-markers@main
        with:
          message: ${{ github.event.head_commit.message }}
      - if: steps.m.outputs.release == 'true'
        uses: https://forge.blackleafdigital.com/Public/actions/dispatch-forgejo-workflow@main
        with:
          workflow: release.yml
          ref: main
          token: ${{ secrets.FORGEJO_ACTIONS_TOKEN }}
          inputs-json: >-
            {"version":"${{ steps.m.outputs.version }}","bump":"${{ steps.m.outputs.bump }}","dry_run":"false"}

dispatch-forgejo-workflow

Fire a Forgejo workflow_dispatch via the REST API (reliable replacement for the flaky on: workflow_run cross-workflow trigger). Optionally polls for completion.

- uses: https://forge.blackleafdigital.com/Public/actions/dispatch-forgejo-workflow@main
  with:
    workflow: release.yml          # required — workflow filename
    ref: main                      # optional, default: main
    inputs-json: '{"dry_run":"false"}'   # optional JSON of inputs
    token: ${{ secrets.FORGEJO_ACTIONS_TOKEN }}   # required
    wait: 'false'                  # optional — poll until run completes