diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2fc82a7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,93 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +env: + # This CLI calls user-scoped endpoints only. These steps are a tripwire, + # not the access control: a literal reference to the platform-admin API in + # the source or the built binary fails the build. + ADMIN_API_PATTERN: "/api/admin|platform-admins|flagship" + +jobs: + checks: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: No admin API references in the source + run: | + # git grep exits 0 on a match, 1 on none, and anything else on error. + # This file holds the pattern, so it is the one path excluded. + rc=0 + git grep --text -niE "$ADMIN_API_PATTERN" -- ':!.github/workflows/ci.yml' || rc=$? + if [ "$rc" -eq 0 ]; then + echo "::error::admin API reference found in the source" + exit 1 + elif [ "$rc" -ne 1 ]; then + echo "::error::git grep failed with exit code $rc" + exit "$rc" + fi + + - name: Dependencies come from crates.io only + run: | + # cargo-deny misses both of these. A committed Cargo config can + # replace the crates.io source, and a path dependency that declares + # a version is not a wildcard. + configs=$(git ls-files -- ':(glob)**/.cargo/config' ':(glob)**/.cargo/config.toml') + if [ -n "$configs" ]; then + echo "::error::committed Cargo config found: $configs" + exit 1 + fi + cargo metadata --locked --format-version 1 \ + | jq -e '([.packages[] | select(.source == null) | .id] - .workspace_members) == []' > /dev/null \ + || { echo "::error::path dependency outside the workspace"; exit 1; } + + - uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # v2.1.1 + + - name: Format + run: cargo fmt --all -- --check + + - name: Clippy + run: cargo clippy --locked --all-targets -- -D warnings + + test: + name: test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Test + run: cargo test --locked + + - name: Build release binary + run: cargo build --locked --release + + - name: No admin API references in the binary + run: | + # grep exits 0 on a match, 1 on none, and 2 on error (a missing binary included). + rc=0 + grep -aoiE "$ADMIN_API_PATTERN" target/release/cachekit-cli || rc=$? + if [ "$rc" -eq 0 ]; then + echo "::error::admin API reference found in the release binary" + exit 1 + elif [ "$rc" -ne 1 ]; then + echo "::error::grep failed with exit code $rc" + exit "$rc" + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f1343ed --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cachekit-cli" +version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a6c558c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "cachekit-cli" +version = "0.0.0" +edition = "2021" +description = "Command-line client for CacheKit" +license = "MIT" +repository = "https://github.com/cachekit-io/cachekit-cli" + +[lints.rust] +unsafe_code = "forbid" diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..4e811e8 --- /dev/null +++ b/deny.toml @@ -0,0 +1,12 @@ +# Dependencies come from crates.io and nowhere else. CI adds the two checks +# cargo-deny cannot make: committed Cargo configs and versioned path deps. + +[licenses] +allow = ["MIT"] + +[bans] +wildcards = "deny" + +[sources] +unknown-registry = "deny" +unknown-git = "deny" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f0f2d5b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("cachekit-cli {}", env!("CARGO_PKG_VERSION")); +} diff --git a/tests/cli.rs b/tests/cli.rs new file mode 100644 index 0000000..bfcef62 --- /dev/null +++ b/tests/cli.rs @@ -0,0 +1,14 @@ +use std::process::Command; + +#[test] +fn binary_runs() { + let out = Command::new(env!("CARGO_BIN_EXE_cachekit-cli")) + .output() + .expect("failed to spawn the binary"); + assert!( + out.status.success(), + "exit {:?}, stderr: {}", + out.status, + String::from_utf8_lossy(&out.stderr) + ); +}