link-assistant-router 0.18.0

Link.Assistant.Router — Claude MAX OAuth proxy and token gateway for Anthropic APIs
Documentation
#!/usr/bin/env rust-script
//! Validate release workflow publishing invariants.

use std::fs;
use std::process::exit;

fn main() {
    let workflow = fs::read_to_string(".github/workflows/release.yml")
        .expect("failed to read .github/workflows/release.yml");

    let required_snippets = [
        "auto-release:",
        "manual-release:",
        "packages: write",
        "CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}",
        "DOCKERHUB_IMAGE: konard/link-assistant-router",
        "rust-script scripts/wait-for-crate.rs",
        "docker/login-action@v4",
        "docker/setup-buildx-action@v4",
        "docker/metadata-action@v6",
        "docker/build-push-action@v7",
        "username: konard",
        "password: ${{ secrets.DOCKERHUB_TOKEN }}",
        "ghcr.io/${{ github.repository }}",
        "${{ env.DOCKERHUB_IMAGE }}",
        "type=raw,value=latest",
        "type=raw,value=${{ steps.current_version.outputs.version }}",
        "type=raw,value=${{ steps.version.outputs.new_version }}",
        "org.opencontainers.image.version=${{ steps.current_version.outputs.version }}",
        "org.opencontainers.image.version=${{ steps.version.outputs.new_version }}",
        "--crates-io-url \"https://crates.io/crates/link-assistant-router\"",
        "--docker-hub-url \"https://hub.docker.com/r/konard/link-assistant-router\"",
    ];

    let mut failures = Vec::new();
    for snippet in required_snippets {
        if !workflow.contains(snippet) {
            failures.push(format!(
                "missing required release workflow snippet: {snippet}"
            ));
        }
    }

    if count_occurrences(&workflow, "packages: write") < 2 {
        failures.push("auto and manual release jobs must both grant packages: write".to_string());
    }

    if count_occurrences(&workflow, "docker/login-action@v4") < 4 {
        failures.push(
            "auto and manual release jobs must both log in to GHCR and Docker Hub".to_string(),
        );
    }

    if count_occurrences(&workflow, "docker/build-push-action@v7") < 2 {
        failures.push("auto and manual release jobs must both publish Docker images".to_string());
    }

    if count_occurrences(
        &workflow,
        "CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}",
    ) < 3
    {
        failures.push(
            "workflow must map both CARGO_REGISTRY_TOKEN and CARGO_TOKEN secrets to Cargo's native CARGO_REGISTRY_TOKEN env var"
                .to_string(),
        );
    }

    if count_occurrences(
        &workflow,
        "--crates-io-url \"https://crates.io/crates/link-assistant-router\"",
    ) < 2
    {
        failures.push(
            "auto and manual GitHub releases must include the crates.io release badge/link"
                .to_string(),
        );
    }

    if count_occurrences(
        &workflow,
        "--docker-hub-url \"https://hub.docker.com/r/konard/link-assistant-router\"",
    ) < 2
    {
        failures.push(
            "auto and manual GitHub releases must include the Docker Hub release badge/link"
                .to_string(),
        );
    }

    if failures.is_empty() {
        println!("release workflow publishes crates, GHCR images, and Docker Hub images");
    } else {
        for failure in failures {
            eprintln!("Error: {failure}");
        }
        exit(1);
    }
}

fn count_occurrences(haystack: &str, needle: &str) -> usize {
    haystack.match_indices(needle).count()
}