diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..d599807c6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +# The proxy image builds from the repository root because it consumes the +# in-tree Rust SDK. Keep the context limited to the two runtime crates. +** +!.dockerignore +!proxy/ +!proxy/Dockerfile +!proxy/Cargo.toml +!proxy/Cargo.lock +!proxy/src/ +!proxy/src/** +!sdk/ +!sdk/rust/ +!sdk/rust/Cargo.toml +!sdk/rust/src/ +!sdk/rust/src/** +!sdk/rust/assets/ +!sdk/rust/assets/** diff --git a/.github/workflows/proxy-container.yml b/.github/workflows/proxy-container.yml index 0153c694c..275f040da 100644 --- a/.github/workflows/proxy-container.yml +++ b/.github/workflows/proxy-container.yml @@ -8,19 +8,25 @@ on: branches: [master] paths: - ".github/workflows/proxy-container.yml" - - "proxy/.dockerignore" + - ".dockerignore" - "proxy/Dockerfile" - "proxy/Cargo.toml" - "proxy/Cargo.lock" - "proxy/src/**" + - "sdk/rust/Cargo.toml" + - "sdk/rust/src/**" + - "sdk/rust/assets/**" pull_request: paths: - ".github/workflows/proxy-container.yml" - - "proxy/.dockerignore" + - ".dockerignore" - "proxy/Dockerfile" - "proxy/Cargo.toml" - "proxy/Cargo.lock" - "proxy/src/**" + - "sdk/rust/Cargo.toml" + - "sdk/rust/src/**" + - "sdk/rust/assets/**" jobs: proxy-container: @@ -45,7 +51,7 @@ jobs: - name: Build proxy container without publishing uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # was v5 with: - context: proxy + context: . file: proxy/Dockerfile platforms: ${{ matrix.platform }} cache-from: type=gha,scope=proxy-${{ matrix.platform }} diff --git a/.github/workflows/proxy-rust.yml b/.github/workflows/proxy-rust.yml index 76e1c1952..58fe9f35d 100644 --- a/.github/workflows/proxy-rust.yml +++ b/.github/workflows/proxy-rust.yml @@ -18,6 +18,9 @@ on: - "proxy/src/**" - "proxy/tests/**" - "proxy/examples/**" + - "sdk/rust/Cargo.toml" + - "sdk/rust/src/**" + - "sdk/rust/assets/**" pull_request: paths: - ".github/workflows/proxy-rust.yml" @@ -31,6 +34,9 @@ on: - "proxy/src/**" - "proxy/tests/**" - "proxy/examples/**" + - "sdk/rust/Cargo.toml" + - "sdk/rust/src/**" + - "sdk/rust/assets/**" env: CARGO_TERM_COLOR: always diff --git a/.github/workflows/proxy-supply-chain.yml b/.github/workflows/proxy-supply-chain.yml index 5faa645dd..0ef5e6e5e 100644 --- a/.github/workflows/proxy-supply-chain.yml +++ b/.github/workflows/proxy-supply-chain.yml @@ -7,6 +7,7 @@ on: - "proxy/deny.toml" - "proxy/Cargo.toml" - "proxy/Cargo.lock" + - "sdk/rust/Cargo.toml" push: branches: [master] paths: @@ -14,6 +15,7 @@ on: - "proxy/deny.toml" - "proxy/Cargo.toml" - "proxy/Cargo.lock" + - "sdk/rust/Cargo.toml" schedule: - cron: "29 7 * * *" workflow_dispatch: diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml index 80f0cfc80..968000e75 100644 --- a/.github/workflows/rust-tests.yml +++ b/.github/workflows/rust-tests.yml @@ -9,8 +9,14 @@ on: paths: - ".github/workflows/rust-tests.yml" - "frontend/src-tauri/**" + - "proxy/Cargo.toml" + - "proxy/src/**" + - "sdk/rust/Cargo.toml" + - "sdk/rust/src/**" + - "sdk/rust/assets/**" - "scripts/ci/_common.sh" - "scripts/ci/rust.sh" + - "scripts/ci/verify-local-rust-deps.sh" - "flake.nix" - "flake.lock" pull_request: @@ -18,8 +24,14 @@ on: paths: - ".github/workflows/rust-tests.yml" - "frontend/src-tauri/**" + - "proxy/Cargo.toml" + - "proxy/src/**" + - "sdk/rust/Cargo.toml" + - "sdk/rust/src/**" + - "sdk/rust/assets/**" - "scripts/ci/_common.sh" - "scripts/ci/rust.sh" + - "scripts/ci/verify-local-rust-deps.sh" - "flake.nix" - "flake.lock" @@ -46,7 +58,9 @@ jobs: ${{ runner.os }}-sccache- - name: Run Rust unit tests - run: nix develop --no-update-lock-file .#ci -c ./scripts/ci/rust.sh + run: | + nix develop --no-update-lock-file .#ci -c ./scripts/ci/verify-local-rust-deps.sh + nix develop --no-update-lock-file .#ci -c ./scripts/ci/rust.sh - name: Show sccache stats if: always() diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml index 58e0d5d74..d2204a2c6 100644 --- a/.github/workflows/supply-chain.yml +++ b/.github/workflows/supply-chain.yml @@ -7,6 +7,8 @@ on: - "deny.toml" - "frontend/src-tauri/Cargo.toml" - "frontend/src-tauri/Cargo.lock" + - "proxy/Cargo.toml" + - "sdk/rust/Cargo.toml" push: branches: [master] paths: @@ -14,6 +16,8 @@ on: - "deny.toml" - "frontend/src-tauri/Cargo.toml" - "frontend/src-tauri/Cargo.lock" + - "proxy/Cargo.toml" + - "sdk/rust/Cargo.toml" schedule: - cron: "41 6 * * *" workflow_dispatch: diff --git a/AGENTS.md b/AGENTS.md index ad5376729..2528ae59b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,21 +44,18 @@ OpenSecret is its required backend. Keep these runtime paths distinct: - Local proxy: a separate user-facing OpenAI-compatible relay. Research chat and Agent Mode do not internally route through it. -The OpenSecret SDK source lives under `sdk/`. Treat `frontend/package.json` as -the authority for whether the browser client consumes a published version or -the in-tree `file:../sdk` package. Native Agent Mode continues to consume the -published Rust crate pinned in `frontend/src-tauri/Cargo.toml` until the proxy -and Rust consumers switch together. Do not assume the TypeScript and Rust SDKs -have identical transports, retries, or API coverage. A backend contract change -that Maple consumes needs compatibility checks for every affected client path. +The OpenSecret SDK source lives under `sdk/`. The browser client consumes the +in-tree `file:../sdk` package, and the desktop Tauri app plus proxy consume the +in-tree `sdk/rust` crate. Do not assume the TypeScript and Rust SDKs have +identical transports, retries, or API coverage. A backend contract change that +Maple consumes needs compatibility checks for every affected client path. The standalone proxy source lives under `proxy/`. From the repository root, run its Rust commands through `nix develop --no-update-lock-file ./proxy -c bash -lc 'cd proxy && ...'`; -root path-scoped workflows own proxy CI. Until the coordinated Rust dependency -switch lands, the Tauri app and proxy continue to consume their published -crate dependencies, so a proxy-only source change is not yet an application -build input. +root path-scoped workflows own proxy CI. Proxy and Rust SDK runtime changes are +desktop application build inputs; container, test, documentation, and +standalone lockfile changes remain independent. ## Code ownership and placement diff --git a/README.md b/README.md index 790a60555..cca39fa7a 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,9 @@ Agent Mode embeds Goose and uses the Rust OpenSecret SDK through Tauri. The local OpenAI-compatible proxy is a separate user-facing service. The OpenSecret SDK source and its upstream Git history live under -[`sdk/`](sdk/README.md), and Maple's TypeScript client consumes that in-tree -package. The proxy source and its upstream history live under -[`proxy/`](proxy/README.md). Native Maple and the proxy still consume published -Rust crates until their local references switch together. +[`sdk/`](sdk/README.md), and Maple consumes its in-tree TypeScript and Rust +packages. The proxy source and its upstream history live under +[`proxy/`](proxy/README.md); desktop Maple consumes that in-tree crate too. ## Quick start diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock index 60a6819af..b488abc79 100644 --- a/frontend/src-tauri/Cargo.lock +++ b/frontend/src-tauri/Cargo.lock @@ -4766,9 +4766,7 @@ dependencies = [ [[package]] name = "maple-proxy" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce86619142c6c60420edf577c4fe41df6e1d3bb3a63429da130b6ccab81cec72" +version = "0.3.3" dependencies = [ "anyhow", "async-stream", @@ -5586,8 +5584,6 @@ dependencies = [ [[package]] name = "opensecret" version = "3.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3323b7adba9f171cc9277b7d51fb881e4a3f37c860eb533bcb0ae434bf4294e1" dependencies = [ "aes-gcm", "anyhow", diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index f0c2af2f1..75948e1ae 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -36,11 +36,8 @@ tauri-plugin-os = "2.3.2" tauri-plugin-sign-in-with-apple = "1.0.2" tokio = { version = "1.0", features = ["io-std", "io-util", "net", "process", "sync", "rt-multi-thread", "macros", "time"] } once_cell = "1.18.0" -maple-proxy = "0.3.2" tauri-plugin-fs = "2.5.1" anyhow = "1.0" -axum = "0.8" -tower-http = { version = "0.6", features = ["cors"] } pdf_oxide = { version = "=0.3.74", git = "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/OpenSecretCloud/pdf_oxide.git", rev = "f24b43ba997dd91ce60839640a8ce3ac92a87a5d", features = ["ocr-ort", "rendering"] } # Keep the pre-1.0 Word parser exact-pinned. office_oxide is already in the # PDFOxide graph; Maple adds strict DOC/DOCX container and semantic guards. @@ -66,7 +63,10 @@ ort = { version = "=2.0.0-rc.11", default-features = false, features = ["std", " # history. goose = { git = "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/aaif-goose/goose.git", rev = "f9c7aaccde4834810dfd13d5efa8f0d39ba28a20", package = "goose", default-features = false } goose-providers = { git = "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/aaif-goose/goose.git", rev = "f9c7aaccde4834810dfd13d5efa8f0d39ba28a20", package = "goose-providers", default-features = false } -opensecret = "3.6.2" +maple-proxy = { version = "0.3.3", path = "../../proxy" } +opensecret = { version = "3.6.2", path = "../../sdk/rust" } +axum = "0.8" +tower-http = { version = "0.6", features = ["cors"] } rand = "0.8.6" async-trait = "0.1" rmcp = { version = "=3.1.2", default-features = false, features = ["client", "transport-streamable-http-client-reqwest"] } diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index 80e79def7..19d9a54e6 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -14,9 +14,11 @@ mod legacy_tts_cleanup; #[cfg(desktop)] mod maple_api; mod onnxruntime; +#[cfg(desktop)] mod open_secret_config; mod pdf_extractor; mod pdf_ocr; +#[cfg(desktop)] mod proxy; #[cfg(desktop)] mod updater_preferences; diff --git a/proxy/.dockerignore b/proxy/.dockerignore deleted file mode 100644 index d0ecd390b..000000000 --- a/proxy/.dockerignore +++ /dev/null @@ -1,55 +0,0 @@ -# Git -.git -.gitignore - -# Build artifacts -target/ -Dockerfile -.dockerignore - -# Development files -.env -.env.local -*.log -*.pid -*.swp -*.swo -*~ - -# IDE -.vscode/ -.idea/ -*.iml - -# Documentation -README.md -LICENSE -docs/ - -# CI/CD -.github/ -.gitlab-ci.yml -.travis.yml - -# Test coverage -tarpaulin-report.html -cobertura.xml -coverage/ - -# Nix -flake.nix -flake.lock -result -result-* - -# Development scripts -justfile -setup-hooks.sh - -# macOS -.DS_Store - -# Temporary files -tmp/ -temp/ -*.tmp \ No newline at end of file diff --git a/proxy/Cargo.lock b/proxy/Cargo.lock index 4297f7141..9ea07a2f9 100644 --- a/proxy/Cargo.lock +++ b/proxy/Cargo.lock @@ -1445,7 +1445,7 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "maple-proxy" -version = "0.3.2" +version = "0.3.3" dependencies = [ "anyhow", "async-stream", @@ -1638,8 +1638,6 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "opensecret" version = "3.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3323b7adba9f171cc9277b7d51fb881e4a3f37c860eb533bcb0ae434bf4294e1" dependencies = [ "aes-gcm", "anyhow", diff --git a/proxy/Cargo.toml b/proxy/Cargo.toml index 94556df0a..bbe4b81e7 100644 --- a/proxy/Cargo.toml +++ b/proxy/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "maple-proxy" -version = "0.3.2" +version = "0.3.3" edition = "2021" authors = ["OpenSecret"] description = "Lightweight OpenAI-compatible proxy server for Maple/OpenSecret TEE infrastructure" @@ -29,7 +29,7 @@ path = "src/main.rs" [dependencies] # OpenSecret SDK -opensecret = "3.6.2" +opensecret = { version = "3.6.2", path = "../sdk/rust" } # Web server axum = { version = "0.8.4", features = ["http2", "macros"] } diff --git a/proxy/Dockerfile b/proxy/Dockerfile index e292b9ee3..2f0b7f520 100644 --- a/proxy/Dockerfile +++ b/proxy/Dockerfile @@ -1,29 +1,20 @@ -# Build stage with cargo-chef for dependency caching -FROM docker.io/lukemathwalker/cargo-chef:latest-rust-1 AS chef +# Build the proxy and its in-tree Rust SDK dependency together. +FROM docker.io/library/rust:1.89.0-bookworm AS builder WORKDIR /app -# Plan stage - prepare dependency list for caching -FROM chef AS planner -COPY Cargo.toml Cargo.lock ./ -COPY src ./src -RUN cargo chef prepare --recipe-path recipe.json - -# Build stage - build dependencies separately for caching -FROM chef AS builder - # Install build dependencies RUN apt-get update && apt-get install -y \ pkg-config \ libssl-dev \ && rm -rf /var/lib/apt/lists/* -# Copy and build dependencies (cached if unchanged) -COPY --from=planner /app/recipe.json recipe.json -RUN cargo chef cook --locked --release --recipe-path recipe.json - # Copy source code and build the application -COPY Cargo.toml Cargo.lock ./ -COPY src ./src +COPY proxy/Cargo.toml proxy/Cargo.lock ./proxy/ +COPY proxy/src ./proxy/src +COPY sdk/rust/Cargo.toml ./sdk/rust/ +COPY sdk/rust/src ./sdk/rust/src +COPY sdk/rust/assets ./sdk/rust/assets +WORKDIR /app/proxy RUN cargo build --locked --release --bin maple-proxy # Runtime stage - minimal image for production @@ -42,7 +33,7 @@ RUN useradd -m -u 1001 -s /bin/bash maple WORKDIR /app # Copy the binary from builder -COPY --from=builder /app/target/release/maple-proxy /usr/local/bin/maple-proxy +COPY --from=builder /app/proxy/target/release/maple-proxy /usr/local/bin/maple-proxy # Set ownership RUN chown -R maple:maple /app @@ -64,7 +55,6 @@ ENV MAPLE_HOST=0.0.0.0 \ # Expose the port EXPOSE 8080 -# Health check # Health check (curl needs to be installed) HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1 diff --git a/proxy/README.md b/proxy/README.md index c642b92e9..a96f3b61a 100644 --- a/proxy/README.md +++ b/proxy/README.md @@ -34,6 +34,9 @@ Add to your `Cargo.toml`: maple-proxy = "0.3.2" ``` +Crates.io publishing remains separate from Maple application releases; the +example above uses the latest published crate version. + ## ⚙️ Configuration Set environment variables or use command-line arguments: @@ -298,7 +301,7 @@ image: ghcr.io/opensecretcloud/maple-proxy:latest 2. **Option B: Build your own image** ```bash -docker build -t maple-proxy:latest . +docker build -f Dockerfile -t maple-proxy:latest .. ``` 3. **Run with docker-compose:** diff --git a/proxy/docker-compose.yml b/proxy/docker-compose.yml index 4e347efb6..9f772cfe3 100644 --- a/proxy/docker-compose.yml +++ b/proxy/docker-compose.yml @@ -3,8 +3,8 @@ version: '3.8' services: maple-proxy: build: - context: . - dockerfile: Dockerfile + context: .. + dockerfile: proxy/Dockerfile cache_from: - maple-proxy:builder - maple-proxy:latest diff --git a/proxy/justfile b/proxy/justfile index a3b03e130..e5de5fb63 100644 --- a/proxy/justfile +++ b/proxy/justfile @@ -171,7 +171,7 @@ env: # Build Docker image docker-build: @echo "🐳 Building Docker image with {{container}}..." - @{{container}} build -t maple-proxy:latest . + @{{container}} build -f Dockerfile -t maple-proxy:latest .. @echo "✅ Docker image built: maple-proxy:latest" # Run Docker container @@ -247,7 +247,7 @@ ghcr-push tag="latest": # Build and push to GHCR ghcr-build-push tag="latest": @echo "🐳 Building and pushing to GHCR..." - @{{container}} build -t ghcr.io/opensecretcloud/maple-proxy:{{tag}} . + @{{container}} build -f Dockerfile -t ghcr.io/opensecretcloud/maple-proxy:{{tag}} .. @{{container}} push ghcr.io/opensecretcloud/maple-proxy:{{tag}} @echo "✅ Image available at ghcr.io/opensecretcloud/maple-proxy:{{tag}}" diff --git a/scripts/ci/change_detection.py b/scripts/ci/change_detection.py index 240e95eb8..38f012abc 100644 --- a/scripts/ci/change_detection.py +++ b/scripts/ci/change_detection.py @@ -16,6 +16,7 @@ INERT_ROOT_FILES = frozenset( { ".gitignore", + ".dockerignore", ".repo_ignore", "AGENTS.md", "deny.toml", @@ -42,6 +43,36 @@ "sdk/vite.config.ts", } ) +SDK_RUST_RUNTIME_PREFIXES = ("sdk/rust/src/", "sdk/rust/assets/") +SDK_RUST_INERT_PREFIXES = ("sdk/rust/tests/", "sdk/rust/examples/") +SDK_RUST_INERT_FILES = frozenset( + { + "sdk/rust/.env.example", + "sdk/rust/Cargo.lock", + "sdk/rust/LICENSE", + "sdk/rust/README.md", + } +) +PROXY_RUNTIME_PREFIXES = ("proxy/src/",) +PROXY_INERT_PREFIXES = ("proxy/tests/", "proxy/examples/") +PROXY_INERT_FILES = frozenset( + { + "proxy/.env.example", + "proxy/.gitignore", + "proxy/Cargo.lock", + "proxy/Dockerfile", + "proxy/LICENSE", + "proxy/README.md", + "proxy/clippy.toml", + "proxy/deny.toml", + "proxy/docker-compose.yml", + "proxy/flake.lock", + "proxy/flake.nix", + "proxy/justfile", + "proxy/rust-toolchain.toml", + "proxy/rustfmt.toml", + } +) IOS_ONNX_INPUTS = frozenset( { @@ -155,12 +186,22 @@ def classify_path(path: str) -> frozenset[str]: return frozenset() if path in SDK_FRONTEND_FILES or path.startswith(SDK_FRONTEND_PREFIXES): return frozenset({"frontend"}) + if path == "sdk/rust/Cargo.toml" or path.startswith(SDK_RUST_RUNTIME_PREFIXES): + return DESKTOP_PLATFORMS + if path in SDK_RUST_INERT_FILES or path.startswith(SDK_RUST_INERT_PREFIXES): + return frozenset() + if path.startswith("sdk/rust/"): + # Unknown files in a consumed Rust crate may be build inputs. + return DESKTOP_PLATFORMS if path.startswith("sdk/"): return frozenset() - if path.startswith("proxy/"): - # Proxy has its own path-scoped checks until the native app starts - # consuming the in-tree crate in the follow-up dependency switch. + if path == "proxy/Cargo.toml" or path.startswith(PROXY_RUNTIME_PREFIXES): + return DESKTOP_PLATFORMS + if path in PROXY_INERT_FILES or path.startswith(PROXY_INERT_PREFIXES): return frozenset() + if path.startswith("proxy/"): + # Unknown files in the consumed proxy crate may be build inputs. + return DESKTOP_PLATFORMS if path in PURE_FRONTEND_FILES or path.startswith(PURE_FRONTEND_PREFIXES): return frozenset({"frontend"}) if path.startswith("frontend/src-tauri/"): diff --git a/scripts/ci/test_change_detection.py b/scripts/ci/test_change_detection.py index 6b6f58797..7c7451f55 100644 --- a/scripts/ci/test_change_detection.py +++ b/scripts/ci/test_change_detection.py @@ -17,15 +17,37 @@ def assert_routes(self, paths: list[str], *names: str) -> None: def test_documentation_and_independent_components_skip_maple_app_builds(self) -> None: self.assert_routes(["README.md", "docs/monorepo-plan.md"]) - self.assert_routes(["sdk/rust/src/client.rs"]) self.assert_routes(["sdk/README.md", "sdk/test/client.test.ts"]) self.assert_routes(["sdk/src/lib/test/models.test.ts"]) self.assert_routes(["sdk/src/lib/test/integration/web.test.ts"]) self.assert_routes(["updates/src/index.ts"]) - self.assert_routes(["proxy/src/proxy.rs"]) - self.assert_routes(["proxy/Cargo.toml", "proxy/Dockerfile"]) self.assert_routes([".githooks/pre-commit", "justfile", "zapstore.yaml"]) + def test_in_tree_rust_runtime_inputs_mark_desktop_lanes(self) -> None: + for path in ( + "proxy/Cargo.toml", + "proxy/src/proxy.rs", + "sdk/rust/Cargo.toml", + "sdk/rust/src/client.rs", + "sdk/rust/assets/aws_nitro_root.der", + "proxy/build.rs", + "sdk/rust/build.rs", + ): + with self.subTest(path=path): + self.assert_routes([path], "macos", "linux", "windows") + + def test_standalone_rust_inputs_skip_maple_app_builds(self) -> None: + for path in ( + "proxy/Cargo.lock", + "proxy/Dockerfile", + "proxy/tests/health_test.rs", + "sdk/rust/Cargo.lock", + "sdk/rust/tests/client.rs", + "sdk/rust/examples/api_usage.rs", + ): + with self.subTest(path=path): + self.assert_routes([path]) + def test_typescript_sdk_inputs_mark_only_the_frontend_lane(self) -> None: self.assert_routes(["sdk/src/lib/index.ts"], "frontend") self.assert_routes(["sdk/package.json"], "frontend") diff --git a/scripts/ci/verify-local-rust-deps.sh b/scripts/ci/verify-local-rust-deps.sh new file mode 100755 index 000000000..087021c28 --- /dev/null +++ b/scripts/ci/verify-local-rust-deps.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" +metadata_file="$(mktemp)" +trap 'rm -f "${metadata_file}"' EXIT + +cd "${repo_root}" +cargo metadata \ + --locked \ + --manifest-path frontend/src-tauri/Cargo.toml \ + --format-version 1 > "${metadata_file}" + +jq -e --arg root "${repo_root}" ' + ([.packages[] | select(.name == "opensecret")] | length) == 1 and + ([.packages[] | select(.name == "maple-proxy")] | length) == 1 and + ([.packages[] | select(.name == "opensecret")][0].source == null) and + ([.packages[] | select(.name == "opensecret")][0].manifest_path == ($root + "/sdk/rust/Cargo.toml")) and + ([.packages[] | select(.name == "maple-proxy")][0].source == null) and + ([.packages[] | select(.name == "maple-proxy")][0].manifest_path == ($root + "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/proxy/Cargo.toml")) +' "${metadata_file}" > /dev/null + +echo "Maple resolves exactly one in-tree OpenSecret SDK and proxy crate."