diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 00000000..c55a0f4e --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,14 @@ +self-hosted-runner: + labels: [] + +# Configuration variables - null means disable check +config-variables: null + +# Ignore shellcheck rules that are too noisy for CI scripts +# SC2086: Double quote to prevent globbing - often intentional in CI +# SC2129: Consider using grouped redirects - stylistic preference +paths: + .github/workflows/**/*.yml: + ignore: + - 'SC2086:.+' + - 'SC2129:.+' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58c303cc..19b9c9a9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,6 +69,31 @@ jobs: - name: Run Linters run: just lint-frontend + # ============================================================ + # JOB 0b1: Static Analysis (typos, TOML, Dockerfiles, workflows, scripts) + # ============================================================ + static-analysis: + name: Static Analysis + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install tools + uses: taiki-e/install-action@v2 + with: + tool: typos-cli,taplo-cli,hadolint,actionlint + + - name: Run static analysis + run: | + set -euox pipefail + + typos + taplo fmt --check + hadolint --config .hadolint.yaml service/Dockerfile service/Dockerfile.dev web/Dockerfile web/Dockerfile.dev dockerfiles/Dockerfile.postgres + actionlint + find . -name "*.sh" -not -path "./.git/*" -exec shellcheck {} + + # ============================================================ # JOB 0b2: Build Storybook # ============================================================ @@ -448,6 +473,7 @@ jobs: - scan-images - lint - lint-web + - static-analysis - storybook-build - codegen-check - sqlx-check diff --git a/.hadolint.yaml b/.hadolint.yaml new file mode 100644 index 00000000..c79a7750 --- /dev/null +++ b/.hadolint.yaml @@ -0,0 +1,19 @@ +# Hadolint configuration +# https://github.com/hadolint/hadolint + +# Treat warnings as errors in CI +failure-threshold: warning + +ignored: + # Allow unpinned apt packages - we use base image version pinning instead + - DL3008 + # Allow cd in RUN commands - sometimes clearer than multiple WORKDIR switches + - DL3003 + # Allow unquoted variables for intentional word splitting (e.g., package lists) + - SC2086 + # Allow missing yarn cache clean in dev containers (not production) + - DL3060 + +trustedRegistries: + - docker.io + - ghcr.io diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 00000000..b2922c29 --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1,11 @@ +# ShellCheck configuration +# https://www.shellcheck.net/ + +# Only warn on actual issues, not style suggestions +# Severity levels: error, warning, info, style +severity=warning + +# Common exclusions for GitHub Actions workflows: +# SC2086 - Double quote to prevent globbing (often intentional in CI) +# SC2129 - Consider using { cmd1; cmd2; } >> file (style preference) +disable=SC2086,SC2129 diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 00000000..2d5405c4 --- /dev/null +++ b/_typos.toml @@ -0,0 +1,15 @@ +# typos configuration +# https://github.com/crate-ci/typos + +[default] +extend-ignore-re = [ + # Ignore hex strings and hashes + "[a-fA-F0-9]{32,}", +] + +[files] +extend-exclude = ["*.lock", "*.snap", ".sqlx/", "web/src/wasm/", "web/src/api/generated/"] + +# Known false positives can be added here: +# [default.extend-words] +# ot = "ot" diff --git a/crates/tc-crypto/Cargo.toml b/crates/tc-crypto/Cargo.toml index b0e56530..37f91a99 100644 --- a/crates/tc-crypto/Cargo.toml +++ b/crates/tc-crypto/Cargo.toml @@ -13,15 +13,15 @@ crate-type = ["cdylib", "rlib"] wasm-bindgen = "0.2" # Cryptography -sha2 = "0.10" base64 = "0.22" +sha2 = "0.10" # Error handling thiserror = "2" [dev-dependencies] -wasm-bindgen-test = "0.3" proptest = "1.4" +wasm-bindgen-test = "0.3" [lints] workspace = true diff --git a/justfile b/justfile index b914b440..e18acc2f 100644 --- a/justfile +++ b/justfile @@ -320,6 +320,38 @@ audit-unused: lint: lint-backend lint-frontend @echo "✓ All linting passed" +# ============================================================================= +# Static Analysis Tools +# ============================================================================= + +# Run all static analysis checks +lint-static: lint-typos lint-toml lint-dockerfiles lint-workflows lint-scripts + @echo "✓ All static analysis passed" + +# Check for typos in code and docs (requires typos: cargo install typos-cli) +lint-typos: + typos + +# Check TOML formatting (requires taplo: cargo install taplo-cli) +lint-toml: + taplo fmt --check + +# Fix TOML formatting +fmt-toml: + taplo fmt + +# Lint Dockerfiles (requires hadolint: brew install hadolint) +lint-dockerfiles: + hadolint service/Dockerfile service/Dockerfile.dev web/Dockerfile web/Dockerfile.dev dockerfiles/Dockerfile.postgres + +# Lint GitHub Actions workflows (requires actionlint: brew install actionlint) +lint-workflows: + actionlint + +# Lint shell scripts (requires shellcheck: brew install shellcheck) +lint-scripts: + shellcheck web/bin/*.sh web/scripts/*.sh service/bin/*.sh + # Fix all formatting (backend + frontend) fmt: fmt-backend fmt-frontend @echo "✓ All formatting applied" @@ -433,6 +465,13 @@ setup: @echo " - Docker: $(docker --version 2>/dev/null || echo "NOT INSTALLED")" @echo " - kubectl: $(kubectl version --client 2>/dev/null | head -1 || echo "NOT INSTALLED")" @echo "" + @echo "Static analysis tools (optional, for lint-static):" + @echo " - typos: $(typos --version 2>/dev/null || echo "NOT INSTALLED - cargo install typos-cli")" + @echo " - taplo: $(taplo --version 2>/dev/null || echo "NOT INSTALLED - cargo install taplo-cli")" + @echo " - hadolint: $(hadolint --version 2>/dev/null || echo "NOT INSTALLED - brew install hadolint")" + @echo " - actionlint: $(actionlint --version 2>/dev/null || echo "NOT INSTALLED - brew install actionlint")" + @echo " - shellcheck: $(shellcheck --version 2>/dev/null | head -2 | tail -1 || echo "NOT INSTALLED - brew install shellcheck")" + @echo "" @echo "For local development (no cluster needed):" @echo " just node-use # Switch to correct Node version (requires nvm)" @echo " just lint # Lint all code" diff --git a/service/Cargo.toml b/service/Cargo.toml index 78f47b69..4747c4a8 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -27,22 +27,40 @@ utoipa = { version = "5", features = ["axum_extras", "chrono", "uuid"] } utoipa-swagger-ui = { version = "9", features = ["axum"] } # Database -sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "postgres", "macros", "uuid", "chrono"] } -sqlx-core = { version = "0.8", default-features = false, features = ["_rt-tokio", "_tls-rustls-ring-webpki", "chrono", "uuid", "json", "migrate"] } -sqlx-postgres = { version = "0.8", default-features = false, features = ["chrono", "uuid", "json", "migrate"] } +sqlx = { version = "0.8", default-features = false, features = [ + "runtime-tokio", + "postgres", + "macros", + "uuid", + "chrono", +] } +sqlx-core = { version = "0.8", default-features = false, features = [ + "_rt-tokio", + "_tls-rustls-ring-webpki", + "chrono", + "uuid", + "json", + "migrate", +] } +sqlx-postgres = { version = "0.8", default-features = false, features = [ + "chrono", + "uuid", + "json", + "migrate", +] } # Configuration figment = { version = "0.10", features = ["yaml", "env"] } # Utilities +anyhow = "1.0" +async-trait = "0.1" chrono = { version = "0.4", features = ["serde"] } -uuid = { version = "1.16", features = ["v4", "serde"] } serde = { version = "1.0", features = ["derive"] } +serde-aux = "4.7.0" serde_json = "1.0" -async-trait = "0.1" thiserror = "2.0" -anyhow = "1.0" -serde-aux = "4.7.0" +uuid = { version = "1.16", features = ["v4", "serde"] } # Cryptography tc-crypto = { path = "../crates/tc-crypto", version = "0.1.0" } @@ -50,11 +68,11 @@ tc-crypto = { path = "../crates/tc-crypto", version = "0.1.0" } [dev-dependencies] hyper = "1.6" insta = { version = "1.41", features = ["json"] } -tower = { version = "0.5", features = ["util"] } -testcontainers = "0.26" -tokio = { version = "1", features = ["sync", "rt-multi-thread"] } once_cell = "1.19" tc-test-macros = { path = "../crates/test-macros", version = "0.1.0" } +testcontainers = "0.26" +tokio = { version = "1", features = ["sync", "rt-multi-thread"] } +tower = { version = "0.5", features = ["util"] } # Enable test-utils feature for integration tests tinycongress-api = { path = ".", version = "0.1.0", features = ["test-utils"] } diff --git a/service/Dockerfile b/service/Dockerfile index afef5f62..026c3286 100644 --- a/service/Dockerfile +++ b/service/Dockerfile @@ -37,7 +37,7 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry,id=cargo-registry \ FROM debian:bookworm-slim RUN apt-get update \ - && apt-get install -y ca-certificates libpq5 libssl3 \ + && apt-get install --no-install-recommends -y ca-certificates libpq5 libssl3 \ && rm -rf /var/lib/apt/lists/* COPY --from=builder /usr/src/app/target/release/tinycongress-api /usr/local/bin/ diff --git a/service/deny.toml b/service/deny.toml index 36aad60e..8e970c46 100644 --- a/service/deny.toml +++ b/service/deny.toml @@ -18,9 +18,9 @@ unmaintained = "workspace" yanked = "deny" # Ignore specific advisories (add with justification if needed) ignore = [ - # tokio-tar vulnerability in testcontainers (dev dependency only, used in tests) - # Waiting for testcontainers to update: https://rustsec.org/advisories/RUSTSEC-2025-0111 - "RUSTSEC-2025-0111", + # tokio-tar vulnerability in testcontainers (dev dependency only, used in tests) + # Waiting for testcontainers to update: https://rustsec.org/advisories/RUSTSEC-2025-0111 + "RUSTSEC-2025-0111", ] # ============================================================================= @@ -29,19 +29,19 @@ ignore = [ [licenses] # Allow common permissive licenses (SPDX identifiers) allow = [ - "MIT", - "Apache-2.0", - "Apache-2.0 WITH LLVM-exception", - "BSD-2-Clause", - "BSD-3-Clause", - "ISC", - "MPL-2.0", - "Zlib", - "Unicode-3.0", - "CC0-1.0", - "BSL-1.0", - # CDLA-Permissive-2.0 is used by webpki-roots (root certificates data) - "CDLA-Permissive-2.0", + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "MPL-2.0", + "Zlib", + "Unicode-3.0", + "CC0-1.0", + "BSL-1.0", + # CDLA-Permissive-2.0 is used by webpki-roots (root certificates data) + "CDLA-Permissive-2.0", ] # High confidence required for license detection confidence-threshold = 0.8 diff --git a/taplo.toml b/taplo.toml new file mode 100644 index 00000000..61ce5858 --- /dev/null +++ b/taplo.toml @@ -0,0 +1,25 @@ +# Taplo TOML formatter configuration +# https://taplo.tamasfe.dev/configuration/ + +[formatting] +# Don't vertically align = signs - keeps diffs minimal when adding entries +align_entries = false +array_auto_collapse = true +array_auto_expand = true +array_trailing_comma = true +column_width = 100 +compact_arrays = true +# Don't compact inline tables - keeps them readable on separate lines +compact_inline_tables = false +# Don't indent top-level entries - standard TOML style +indent_entries = false +# Don't indent table headers - keeps file structure flat and clear +indent_tables = false +inline_table_expand = true +# Don't reorder keys globally - preserves intentional grouping (e.g., comments above entries) +reorder_keys = false +trailing_newline = true + +[[rule]] +keys = ["dependencies", "dev-dependencies", "build-dependencies"] +formatting = { reorder_keys = true }