diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 3a8380a..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,177 +0,0 @@ -name: CI - -on: - push: - branches: [ "trunk" ] - tags: [ 'v*' ] - pull_request: - branches: [ "trunk" ] - workflow_dispatch: - -env: - CARGO_TERM_COLOR: always - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - test: - name: 🛠️ Run tests - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Install Rust stable - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: npm - cache-dependency-path: slonq-js/package-lock.json - - - name: Install Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - - - name: Run tests - run: | - cargo test --verbose - cd slonq-python - uv run maturin develop - uv run pytest - cd ../slonq-js - npm install - npm ci - npm run build - npm test - - linux: - name: Build wheels on ${{ matrix.platform.runner }} ${{ matrix.platform.target }} - runs-on: ${{ matrix.platform.runner }} - needs: test - strategy: - matrix: - platform: - - runner: ubuntu-22.04 - target: x86_64 - - runner: ubuntu-22.04 - target: x86 - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Install Rust stable - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - - - name: Install Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - - - name: Build wheels - uses: PyO3/maturin-action@v1 - with: - working-directory: slonq-python - target: ${{ matrix.platform.target }} - args: --release --out dist --find-interpreter - sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} - manylinux: auto - - name: Upload wheels - uses: actions/upload-artifact@v5 - with: - name: wheels-linux-${{ matrix.platform.target }} - path: slonq-python/dist - - macos: - name: Build wheels on ${{ matrix.platform.runner }} ${{ matrix.platform.target }} - runs-on: ${{ matrix.platform.runner }} - needs: test - strategy: - matrix: - platform: - - runner: macos-15-intel - target: x86_64 - - runner: macos-latest - target: aarch64 - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Install Rust stable - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - - - name: Install Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - name: Install uv - uses: astral-sh/setup-uv@v7 - - - name: Cache Rust dependencies - uses: Swatinem/rust-cache@v2 - - - name: Build wheels - uses: PyO3/maturin-action@v1 - with: - working-directory: slonq-python - target: ${{ matrix.platform.target }} - args: --release --out dist --find-interpreter - sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} - manylinux: auto - - - name: Upload wheels - uses: actions/upload-artifact@v5 - with: - name: wheels-macos-${{ matrix.platform.target }} - path: slonq-python/dist - - release: - name: Release - runs-on: ubuntu-latest - if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} - needs: [ linux, macos ] - permissions: - # Use to sign the release artifacts - id-token: write - # Used to upload release artifacts - contents: write - # Used to generate artifact attestation - attestations: write - steps: - - uses: actions/download-artifact@v6 - - name: Generate artifact attestation - uses: actions/attest-build-provenance@v3 - with: - subject-path: 'wheels-*/*' - - name: Install uv - uses: astral-sh/setup-uv@v7 - - name: Publish to PyPI - run: uv publish 'wheels-*/*' - env: - UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml new file mode 100644 index 0000000..2e67a45 --- /dev/null +++ b/.github/workflows/javascript.yml @@ -0,0 +1,362 @@ +name: JavaScript + +on: + push: + branches: [trunk] + tags: ['v*'] + paths: + - 'slonq-js/**' + - 'slonq-core/**' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/javascript.yml' + pull_request: + branches: [trunk] + paths: + - 'slonq-js/**' + - 'slonq-core/**' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/javascript.yml' + workflow_dispatch: + inputs: + version: + description: 'Version to publish (e.g. 0.1.0). Required when releasing via dispatch (no tag).' + required: false + default: '' + +env: + CARGO_TERM_COLOR: always + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }} + +defaults: + run: + shell: bash + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '20' + cache: npm + cache-dependency-path: slonq-js/package-lock.json + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 + with: + toolchain: stable + + - name: Cargo cache + uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + + - name: npm ci + build + test + working-directory: slonq-js + # Integration tests use testcontainers + postgres:16-alpine; Docker is + # pre-installed on ubuntu-latest runners. + run: | + npm install + npm ci + npm run build + npm test + + build: + # Build matrix runs on every trigger (PR included) so we catch + # platform-specific failures before release. The `.node` artifacts are + # only uploaded on push/dispatch — see the `if:` on the upload step below. + name: Build native (${{ matrix.platform.triple }}) + runs-on: ${{ matrix.platform.runner }} + strategy: + fail-fast: false + matrix: + platform: + - triple: darwin-arm64 + runner: macos-latest + target: aarch64-apple-darwin + zigbuild: false + artifact: libslonq_js.dylib + - triple: darwin-x64 + runner: macos-15-intel + target: x86_64-apple-darwin + zigbuild: false + artifact: libslonq_js.dylib + - triple: linux-x64-gnu + runner: ubuntu-22.04 + target: x86_64-unknown-linux-gnu + zigbuild: false + artifact: libslonq_js.so + - triple: linux-arm64-gnu + runner: ubuntu-22.04 + target: aarch64-unknown-linux-gnu + zigbuild: true + artifact: libslonq_js.so + - triple: linux-x64-musl + runner: ubuntu-22.04 + target: x86_64-unknown-linux-musl + zigbuild: true + artifact: libslonq_js.so + - triple: linux-arm64-musl + runner: ubuntu-22.04 + target: aarch64-unknown-linux-musl + zigbuild: true + artifact: libslonq_js.so + - triple: win32-x64-msvc + runner: windows-latest + target: x86_64-pc-windows-msvc + zigbuild: false + artifact: slonq_js.dll + - triple: win32-arm64-msvc + runner: windows-11-arm + target: aarch64-pc-windows-msvc + zigbuild: false + artifact: slonq_js.dll + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 + with: + toolchain: stable + targets: ${{ matrix.platform.target }} + + - if: matrix.platform.zigbuild + uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # 2.2.1 + with: + version: 0.16.0 + + - if: matrix.platform.zigbuild + run: cargo install --locked cargo-zigbuild + + - name: Build (cargo) + if: ${{ !matrix.platform.zigbuild }} + run: cargo build --release -p slonq-js --target ${{ matrix.platform.target }} + + - name: Build (cargo-zigbuild) + if: matrix.platform.zigbuild + env: + # musl targets default to static-CRT linking, which forbids cdylib. + # -C target-feature=-crt-static switches musl over to dynamic libc + # so the `.so` can actually be produced. It's a no-op on the gnu + # cross targets in this matrix (gnu uses dynamic libc by default). + RUSTFLAGS: '-C target-feature=-crt-static' + run: cargo zigbuild --release -p slonq-js --target ${{ matrix.platform.target }} + + - name: Stage artifact + run: | + mkdir -p slonq-js/npm/${{ matrix.platform.triple }} + cp target/${{ matrix.platform.target }}/release/${{ matrix.platform.artifact }} \ + slonq-js/npm/${{ matrix.platform.triple }}/index.node + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} + with: + name: prebuilt-${{ matrix.platform.triple }} + path: slonq-js/npm/${{ matrix.platform.triple }} + if-no-files-found: error + + ts-build: + name: Build TypeScript surface + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '20' + cache: npm + cache-dependency-path: slonq-js/package-lock.json + + - name: npm ci + build:ts + working-directory: slonq-js + run: | + npm install + npm ci + npm run build:ts + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} + with: + name: ts-dist + path: slonq-js/dist + if-no-files-found: error + + release: + name: Release + runs-on: ubuntu-latest + # `test` is deliberately *not* in needs: it's the PR gate; the matrix builds + # are the release gate. Tag-time Docker flakes shouldn't block a release that + # already passed CI on PR. + needs: [build, ts-build] + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + permissions: + id-token: write + contents: read + attestations: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + # npm Trusted Publishing requires npm CLI >= 11.5.1, which is newer + # than what setup-node ships with Node 20 (npm 10.x). Upgrade explicitly. + # See https://docs.npmjs.com/trusted-publisher + - name: Upgrade npm for trusted publishing + run: npm install -g npm@latest + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: artifacts + + - name: Stage prebuilt subpackages + run: | + for d in artifacts/prebuilt-*; do + triple=${d#artifacts/prebuilt-} + mkdir -p slonq-js/npm/$triple + cp -f $d/index.node slonq-js/npm/$triple/index.node + done + + - name: Stage main package dist/ + run: | + rm -rf slonq-js/dist + cp -r artifacts/ts-dist slonq-js/dist + + - name: Resolve publish version + id: version + run: | + if [ "${GITHUB_REF#refs/tags/}" != "$GITHUB_REF" ]; then + version="${GITHUB_REF#refs/tags/}" + version="${version#v}" + else + version="${{ inputs.version }}" + fi + if [ -z "$version" ]; then + echo "::error::No version available. Push a tag (e.g. v0.1.0) or set inputs.version." + exit 1 + fi + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "Publishing version: $version" + + - name: Set package versions + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + for d in slonq-js/npm/*/; do + (cd "$d" && npm version --no-git-tag-version --allow-same-version "$VERSION") + done + (cd slonq-js && npm version --no-git-tag-version --allow-same-version "$VERSION") + node -e " + const fs = require('fs'); + const path = 'slonq-js/package.json'; + const pkg = JSON.parse(fs.readFileSync(path, 'utf8')); + const v = process.env.VERSION; + for (const dep of Object.keys(pkg.optionalDependencies || {})) { + pkg.optionalDependencies[dep] = v; + } + fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n'); + " + + - name: Generate artifact attestation + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + with: + subject-path: | + slonq-js/npm/*/index.node + slonq-js/dist/**/* + + # Authentication is via npm Trusted Publishing (OIDC). The id-token: write + # permission above + --provenance + a Trusted Publisher configured on + # npmjs.com (linking each `@chmodas/slonq*` package to this repo + this + # workflow file) is what authorises the publish. No NPM_TOKEN. + # + # Tolerate EPUBLISHCONFLICT (already-published version) so a re-run after + # a partial-failure release tag picks up where it left off. + - name: Publish prebuilt subpackages to npm + run: | + publish_or_skip() { + local dir="$1" + local out + if out=$(cd "$dir" && npm publish --access public --provenance --ignore-scripts 2>&1); then + echo "$out" + else + echo "$out" + if echo "$out" | grep -qiE 'EPUBLISHCONFLICT|cannot publish over the previously published versions'; then + echo "::notice::version already published; continuing" + return 0 + fi + return 1 + fi + } + for d in slonq-js/npm/*/; do + echo "Publishing $d" + publish_or_skip "$d" + done + + - name: Wait for npm registry to index subpackages + env: + VERSION: ${{ steps.version.outputs.version }} + # 60 retries × 5s = 5 minutes. Generous to handle registry propagation + # under load and avoid leaving the main package failed-to-resolve. + run: | + for d in slonq-js/npm/*/; do + name=$(node -p "require('./$d/package.json').name") + echo "Waiting for $name@$VERSION to be queryable..." + for i in $(seq 1 60); do + if npm view "$name@$VERSION" version >/dev/null 2>&1; then + echo " $name@$VERSION available." + break + fi + if [ "$i" -eq 60 ]; then + echo "::error::$name@$VERSION not visible on registry after 5 minutes." + exit 1 + fi + sleep 5 + done + done + + - name: Publish main package to npm + working-directory: slonq-js + run: | + if out=$(npm publish --access public --provenance --ignore-scripts 2>&1); then + echo "$out" + else + echo "$out" + if echo "$out" | grep -qiE 'EPUBLISHCONFLICT|cannot publish over the previously published versions'; then + echo "::notice::main package version already published; continuing" + exit 0 + fi + exit 1 + fi + + ci-passed: + name: CI passed + runs-on: ubuntu-latest + needs: [test, build, ts-build] + if: always() + steps: + - name: Verify prerequisite jobs + run: | + fail=0 + for r in "${{ needs.test.result }}" "${{ needs.build.result }}" "${{ needs.ts-build.result }}"; do + case "$r" in + success|skipped) ;; + *) echo "::error::prerequisite job result: $r"; fail=1 ;; + esac + done + exit $fail diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000..eab6fd0 --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,218 @@ +name: Python + +on: + push: + branches: [trunk] + tags: ['v*'] + paths: + - 'slonq-python/**' + - 'slonq-core/**' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/python.yml' + pull_request: + branches: [trunk] + paths: + - 'slonq-python/**' + - 'slonq-core/**' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/python.yml' + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }} + +defaults: + run: + shell: bash + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Python 3.x + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.x' + + - name: Install uv + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 + with: + toolchain: stable + + - name: Cargo cache + uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + + - name: maturin develop + pytest + working-directory: slonq-python + run: | + uv run maturin develop + uv run pytest + + build: + # Build matrix runs on every trigger, so we catch platform-specific failures before release. + # Wheel artifacts, on the other hand, are only uploaded on push/workflow_dispatch. + name: Build wheels (${{ matrix.platform.key }}) + runs-on: ${{ matrix.platform.runner }} + strategy: + fail-fast: false + matrix: + platform: + # Linux gnu (manylinux). aarch64 cross-compiles via QEMU inside maturin-action. + - key: linux-x64-gnu + runner: ubuntu-22.04 + target: x86_64 + manylinux: auto + python_arch: x64 + - key: linux-arm64-gnu + runner: ubuntu-22.04 + target: aarch64 + manylinux: auto + python_arch: x64 + # Linux musl (musllinux). Same QEMU story for aarch64. + - key: linux-x64-musl + runner: ubuntu-22.04 + target: x86_64 + manylinux: musllinux_1_2 + python_arch: x64 + - key: linux-arm64-musl + runner: ubuntu-22.04 + target: aarch64 + manylinux: musllinux_1_2 + python_arch: x64 + # macOS + - key: macos-x64 + runner: macos-15-intel + target: x86_64 + manylinux: '' + python_arch: x64 + - key: macos-arm64 + runner: macos-latest + target: aarch64 + manylinux: '' + python_arch: arm64 + # Windows. + - key: windows-x64 + runner: windows-latest + target: x64 + manylinux: '' + python_arch: x64 + - key: windows-arm64 + runner: windows-11-arm + target: aarch64 + manylinux: '' + python_arch: arm64 + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Python 3.x + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ matrix.platform.key == 'windows-arm64' && '3.13' || '3.x' }} + architecture: ${{ matrix.platform.python_arch }} + + - name: Build wheels + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 + with: + working-directory: slonq-python + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + manylinux: ${{ matrix.platform.manylinux }} + + # Only upload wheel artifacts on push/dispatch — PR runs verify the + # build but discard output to save artifact storage. + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} + with: + name: wheels-${{ matrix.platform.key }} + path: slonq-python/dist + if-no-files-found: error + + sdist: + name: Build sdist + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Build sdist + uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 + with: + working-directory: slonq-python + command: sdist + args: --out dist + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }} + with: + name: wheels-sdist + path: slonq-python/dist + if-no-files-found: error + + release: + name: Release + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + needs: [build, sdist] + permissions: + id-token: write + contents: write + attestations: write + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: artifacts + + - name: Stage wheels into one directory + run: | + mkdir -p dist + find artifacts -name '*.whl' -o -name '*.tar.gz' | xargs -I{} cp {} dist/ + + - name: Generate artifact attestation + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + with: + subject-path: 'dist/*' + + - name: Install uv + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + + - name: Publish to PyPI + env: + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + # `--check-url` lets a re-run after a partial-failure tag skip wheels that + # already exist on PyPI instead of erroring out. + run: uv publish --check-url https://pypi.org/simple/ 'dist/*' + + ci-passed: + # Aggregator job. Branch protection / Repository Rulesets should require this + # check; it gives a stable status name regardless of which prereq jobs ran. + name: CI passed + runs-on: ubuntu-latest + needs: [test, build, sdist] + if: always() + steps: + - name: Verify prerequisite jobs + run: | + fail=0 + for r in "${{ needs.test.result }}" "${{ needs.build.result }}" "${{ needs.sdist.result }}"; do + case "$r" in + success|skipped) ;; + *) echo "::error::prerequisite job result: $r"; fail=1 ;; + esac + done + exit $fail diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..1491484 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,62 @@ +name: Rust + +on: + push: + branches: [trunk] + tags: ['v*'] + paths: + - 'slonq-core/**' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/rust.yml' + pull_request: + branches: [trunk] + paths: + - 'slonq-core/**' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/rust.yml' + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }} + +defaults: + run: + shell: bash + +jobs: + build: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Rust nightly (rustfmt) + uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 + with: + toolchain: nightly + components: rustfmt + + - name: Install Rust stable (clippy) + uses: dtolnay/rust-toolchain@efa25f7f19611383d5b0ccf2d1c8914531636bf9 + with: + toolchain: stable + components: clippy + + - name: Cargo cache + uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + + - name: Lint + run: | + cargo +nightly fmt --all -- --check + cargo clippy --all-targets --all-features -- -D warnings + + - name: Test + run: cargo test --workspace --verbose diff --git a/slonq-core/tests/integration.rs b/slonq-core/tests/integration.rs index 396150d..89c4b04 100644 --- a/slonq-core/tests/integration.rs +++ b/slonq-core/tests/integration.rs @@ -6,7 +6,7 @@ use testcontainers_modules::postgres::Postgres; use tokio_postgres::NoTls; /// The SQL schema required to initialise the `jobs` table and related types. -pub const SCHEMA_SQL: &'static str = r#" +pub const SCHEMA_SQL: &str = r#" CREATE TYPE job_status AS ENUM ('pending', 'in_progress', 'done', 'failed'); CREATE TABLE jobs ( diff --git a/slonq-core/tests/queue_model.rs b/slonq-core/tests/queue_model.rs index 967c9ec..4bb2ba9 100644 --- a/slonq-core/tests/queue_model.rs +++ b/slonq-core/tests/queue_model.rs @@ -127,16 +127,15 @@ impl QueueModel { fn reap_exhausted(&self, st: &mut State) { for job_opt in &mut st.jobs { - if let Some(job) = job_opt { - if job.status == Status::InProgress - && job.visible_at <= st.now - && job.attempt_count >= self.max_attempts - { - job.status = Status::Failed; - job.failed_attempt = Some(job.attempt_count); - job.lease_id = None; - job.leased_by = None; - } + if let Some(job) = job_opt + && job.status == Status::InProgress + && job.visible_at <= st.now + && job.attempt_count >= self.max_attempts + { + job.status = Status::Failed; + job.failed_attempt = Some(job.attempt_count); + job.lease_id = None; + job.leased_by = None; } } } @@ -438,10 +437,10 @@ fn prop_failed_attempt_matches(_: &QueueModel, st: &State) -> bool { fn prop_unique_active_lease_ids(_: &QueueModel, st: &State) -> bool { let mut seen = HashSet::new(); for job in st.jobs.iter().flatten() { - if let Some(l) = job.lease_id { - if !seen.insert(l) { - return false; - } + if let Some(l) = job.lease_id + && !seen.insert(l) + { + return false; } } true @@ -457,12 +456,13 @@ fn prop_no_two_current_owners(_: &QueueModel, st: &State) -> bool { let Some(cur) = job.lease_id else { continue }; let mut owners = 0; for w in &st.workers { - if let Some(mem) = w.held { - if mem.job as usize == jidx && mem.lease_id == cur { - owners += 1; - if owners > 1 { - return false; - } + if let Some(mem) = w.held + && mem.job as usize == jidx + && mem.lease_id == cur + { + owners += 1; + if owners > 1 { + return false; } } } diff --git a/slonq-js/src/lib.rs b/slonq-js/src/lib.rs index 7fa3de3..ae3cbca 100644 --- a/slonq-js/src/lib.rs +++ b/slonq-js/src/lib.rs @@ -27,10 +27,7 @@ fn status_str(status: JobStatus) -> &'static str { } } -fn job_to_object<'cx, C: Context<'cx>>( - cx: &mut C, - job: &Job, -) -> JsResult<'cx, JsObject> { +fn job_to_object<'cx, C: Context<'cx>>(cx: &mut C, job: &Job) -> JsResult<'cx, JsObject> { let obj = cx.empty_object(); let id = cx.number(job.id as f64); @@ -42,8 +39,8 @@ fn job_to_object<'cx, C: Context<'cx>>( let status = cx.string(status_str(job.status)); obj.set(cx, "status", status)?; - let payload_str = serde_json::to_string(&job.payload) - .or_else(|e| cx.throw_error(e.to_string()))?; + let payload_str = + serde_json::to_string(&job.payload).or_else(|e| cx.throw_error(e.to_string()))?; let payload = cx.string(&payload_str); obj.set(cx, "payloadJson", payload)?; @@ -64,10 +61,7 @@ fn job_to_object<'cx, C: Context<'cx>>( Ok(obj) } -fn jobs_to_array<'cx, C: Context<'cx>>( - cx: &mut C, - jobs: &[Job], -) -> JsResult<'cx, JsArray> { +fn jobs_to_array<'cx, C: Context<'cx>>(cx: &mut C, jobs: &[Job]) -> JsResult<'cx, JsArray> { let arr = cx.empty_array(); for (i, job) in jobs.iter().enumerate() { let obj = job_to_object(cx, job)?; @@ -206,7 +200,8 @@ fn pgqueue_nack(mut cx: FunctionContext) -> JsResult { let lease = extract_lease(&mut cx, lease_value)?; let max_attempts = cx.argument::(2)?.value(&mut cx) as i32; let delay_arg = cx.argument::(3)?; - let delay = if delay_arg.is_a::(&mut cx) || delay_arg.is_a::(&mut cx) { + let delay = if delay_arg.is_a::(&mut cx) || delay_arg.is_a::(&mut cx) + { None } else { let secs = delay_arg