Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ crates/socket-patch-core/tests/fixtures/vendor/** -text
# compares the result byte for byte, so a CRLF checkout would change both
# the replayed wiring files and the expected revert.
crates/socket-patch-cli/tests/fixtures/legacy-ledgers/** -text

# The owned Gradle settings script is embedded with include_str! and
# written into user repos byte for byte; a CRLF checkout would change it.
crates/socket-patch-core/src/vendor/jvm/socket-patch.settings.gradle -text
59 changes: 46 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,8 @@ jobs:
retention-days: 3

e2e:
needs: [test, e2e-build]
# These jobs consume e2e-build's binaries and can run alongside unit tests.
needs: [e2e-build]
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -1069,6 +1070,18 @@ jobs:
- {os: ubuntu-latest, suite: e2e_vendor_maven_build, maven: '3.9.16'}
- {os: ubuntu-latest, suite: e2e_vendor_maven_build, maven: '4.0.0-rc-6'}
- {os: macos-latest, suite: e2e_vendor_maven_build, maven: '3.9.16'}
- {os: ubuntu-latest, suite: e2e_vendor_jvm_build, maven: '3.6.3', test_filter: '--ignored maven_reactor'}
- {os: ubuntu-latest, suite: e2e_vendor_jvm_build, maven: '3.8.9', test_filter: '--ignored maven_reactor'}
- {os: ubuntu-latest, suite: e2e_vendor_jvm_build, maven: '3.9.2', test_filter: '--ignored maven_reactor'}
- {os: ubuntu-latest, suite: e2e_vendor_jvm_build, maven: '3.9.16', test_filter: '--ignored maven_reactor'}
- {os: ubuntu-latest, suite: e2e_vendor_jvm_build, maven: '4.0.0-rc-6', test_filter: '--ignored maven_reactor'}
- {os: macos-latest, suite: e2e_vendor_jvm_build, maven: '3.9.16', test_filter: '--ignored maven_reactor'}
- {os: windows-latest, suite: e2e_vendor_jvm_build, maven: '3.9.16', test_filter: '--ignored maven_reactor'}
- {os: ubuntu-latest, suite: e2e_vendor_jvm_build, gradle: '6.9.4', java: '11', test_filter: '--ignored gradle_multi_project'}
- {os: ubuntu-latest, suite: e2e_vendor_jvm_build, gradle: '7.6.4', java: '17', test_filter: '--ignored gradle_multi_project'}
- {os: ubuntu-latest, suite: e2e_vendor_jvm_build, gradle: '8.14.3', java: '17', test_filter: '--ignored gradle_multi_project'}
- {os: ubuntu-latest, suite: e2e_vendor_jvm_build, gradle: '9.8.0', java: '17', test_filter: '--ignored gradle_multi_project'}
- {os: windows-latest, suite: e2e_vendor_jvm_build, gradle: '8.14.3', java: '17', test_filter: '--ignored gradle_multi_project'}
# Real .NET SDK capstones: hosted + vendored nuget, one leg per SDK
# major (the suite pins the major through a sandbox global.json):
# the oldest and newest here, 7-9 on ubuntu in e2e-full.
Expand Down Expand Up @@ -1238,28 +1251,46 @@ jobs:
php-version: '8.2'
tools: composer:${{ matrix.composer }}

- name: Setup Java (Maven legs)
if: matrix.maven != ''
- name: Setup Java (Maven and Gradle legs)
if: matrix.maven != '' || matrix.gradle != ''
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: temurin
java-version: '17'
java-version: ${{ matrix.java || '17' }}

- name: Install Maven ${{ matrix.maven }}
if: matrix.maven != ''
- name: Install Maven ${{ matrix.maven || '3.9.16' }}
if: matrix.maven != '' || matrix.gradle != ''
# Straight from the Apache archive (sha512-verified), so a leg gets
# exactly the release it names rather than the runner's Maven.
shell: bash
env:
MAVEN_VERSION: ${{ matrix.maven }}
MAVEN_VERSION: ${{ matrix.maven || '3.9.16' }}
run: |
major="${MAVEN_VERSION%%.*}"
url="https://archive.apache.org/dist/maven/maven-${major}/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz"
curl -fsSL --retry 3 "$url" -o "$RUNNER_TEMP/maven.tgz"
sum="$(curl -fsSL --retry 3 "$url.sha512" | cut -d' ' -f1)"
echo "$sum $RUNNER_TEMP/maven.tgz" | shasum -a 512 -c -
tar -xzf "$RUNNER_TEMP/maven.tgz" -C "$RUNNER_TEMP"
echo "SOCKET_PATCH_MAVEN_E2E_MVN=$RUNNER_TEMP/apache-maven-${MAVEN_VERSION}/bin/mvn" >> "$GITHUB_ENV"
curl -fsSL --retry 3 "$url.sha512" -o "$RUNNER_TEMP/maven.sha512"
python -c 'import hashlib, pathlib, os; p=pathlib.Path(os.environ["RUNNER_TEMP"]); assert hashlib.sha512((p/"maven.tgz").read_bytes()).hexdigest() == (p/"maven.sha512").read_text().split()[0]'
# Python accepts native Windows paths for both archive and destination.
python -m tarfile -e "$RUNNER_TEMP/maven.tgz" "$RUNNER_TEMP"
launcher="$RUNNER_TEMP/apache-maven-${MAVEN_VERSION}/bin/mvn"
if [ "$RUNNER_OS" = Windows ]; then launcher="${launcher}.cmd"; fi
echo "SOCKET_PATCH_MAVEN_E2E_MVN=$launcher" >> "$GITHUB_ENV"

- name: Install Gradle ${{ matrix.gradle }}
if: matrix.gradle != ''
shell: bash
env:
GRADLE_VERSION: ${{ matrix.gradle }}
run: |
url="https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip"
curl -fsSL --retry 3 "$url" -o "$RUNNER_TEMP/gradle.zip"
curl -fsSL --retry 3 "$url.sha256" -o "$RUNNER_TEMP/gradle.sha256"
python -c 'import hashlib, pathlib, os; p=pathlib.Path(os.environ["RUNNER_TEMP"]); assert hashlib.sha256((p/"gradle.zip").read_bytes()).hexdigest() == (p/"gradle.sha256").read_text().strip()'
unzip -q "$RUNNER_TEMP/gradle.zip" -d "$RUNNER_TEMP"
launcher="$RUNNER_TEMP/gradle-${GRADLE_VERSION}/bin/gradle"
if [ "$RUNNER_OS" = Windows ]; then launcher="${launcher}.bat"; fi
echo "SOCKET_PATCH_GRADLE_E2E_GRADLE=$launcher" >> "$GITHUB_ENV"

- name: Setup .NET SDK
if: matrix.dotnet != ''
Expand Down Expand Up @@ -1363,8 +1394,10 @@ jobs:
SOCKET_PATCH_BUNDLER_E2E_VERSION: ${{ matrix.bundler }}
SOCKET_PATCH_COMPOSER_E2E_REQUIRED: ${{ matrix.composer != '' && '1' || '' }}
SOCKET_PATCH_COMPOSER_E2E_VERSION: ${{ matrix.composer }}
SOCKET_PATCH_MAVEN_E2E_REQUIRED: ${{ matrix.maven != '' && '1' || '' }}
SOCKET_PATCH_MAVEN_E2E_VERSION: ${{ matrix.maven }}
SOCKET_PATCH_MAVEN_E2E_REQUIRED: ${{ (matrix.maven != '' || matrix.gradle != '') && '1' || '' }}
SOCKET_PATCH_MAVEN_E2E_VERSION: ${{ matrix.maven || (matrix.gradle != '' && '3.9.16') || '' }}
SOCKET_PATCH_GRADLE_E2E_REQUIRED: ${{ matrix.gradle != '' && '1' || '' }}
SOCKET_PATCH_GRADLE_E2E_VERSION: ${{ matrix.gradle }}
SOCKET_PATCH_DOTNET_E2E_REQUIRED: ${{ matrix.dotnet != '' && '1' || '' }}
SOCKET_PATCH_DOTNET_E2E_VERSION: ${{ matrix.dotnet }}
SOCKET_PATCH_DENO_E2E_REQUIRED: ${{ matrix.deno != '' && '1' || '' }}
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ and `vendor` (committed patched packages), with `list` for inspection. See the

### Added

- Vendored Maven reactors and Gradle builds, with committed repositories,
reversible wiring, repair, rollback, and VEX. Reactors use suffixed versions;
Gradle preserves coordinates and lockfiles, checks artifact hashes, and updates
existing verification metadata. `vendor --check` audits artifacts and wiring
offline; `--local-repo` checks Maven cache conflicts and `--maven-config=none`
selects the fallback file repository. Single-POM vendoring is unchanged.
- `socket.yml` patch policy for paths, ecosystems, packages, severity, and per-run
limits. `scan --package`, `--min-severity`, `--max-new-patches`, and
`--no-socket-yml` support targeted and gradual rollout. Already-patched packages
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ The CLI supports npm, PyPI, Cargo, Go, RubyGems, Maven, Composer, NuGet, and Den
Mode and package-manager support vary: Deno uses agent mode, for example. Check the
[ecosystem support matrix](docs/ecosystems.md) before choosing a mode.

Vendored Maven reactors and Gradle 6.8+ builds are supported. See
[JVM vendoring](docs/design/maven-vendoring.md) for supported project shapes,
cache behavior, and offline checks.

## Common commands

```sh
Expand Down
26 changes: 25 additions & 1 deletion crates/socket-patch-cli/CLI_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Rows are in `--help` order (v5.0): the hosted/vendored workflow (`scan` → `vex

## Global arguments

Every subcommand accepts the same set of "global" flags via a single shared `GlobalArgs` struct that's `#[command(flatten)]`-ed into each per-command struct (`crates/socket-patch-cli/src/args.rs`). Subcommands that don't actually consume a given flag accept it silently — e.g. `list --global` parses fine and is a no-op. Every flag also has an environment-variable binding; precedence is **CLI arg > env var > default** — and for exactly three keys (`--api-token`, `--org`, `--api-url`) the JS socket-cli's persisted login sits between env var and default: **CLI arg > env var (canonical, then `SOCKET_CLI_*` alias) > socket-cli `config.json` > default**. See "Persisted configuration" under Environment variables.
Every subcommand accepts the same set of "global" flags via a single shared `GlobalArgs` struct that's `#[command(flatten)]`-ed into each per-command struct (`crates/socket-patch-cli/src/args.rs`). Subcommands that don't actually consume a given flag accept it silently — e.g. `list --global` parses fine and is a no-op. For flags with an environment-variable binding, precedence is **CLI arg > env var > default** — and for exactly three keys (`--api-token`, `--org`, `--api-url`) the JS socket-cli's persisted login sits between env var and default: **CLI arg > env var (canonical, then `SOCKET_CLI_*` alias) > socket-cli `config.json` > default**. See "Persisted configuration" under Environment variables.

| Long | Short | Env var | Default | Type | Semantic |
|---|---|---|---|---|---|
Expand All @@ -55,6 +55,7 @@ Every subcommand accepts the same set of "global" flags via a single shared `Glo
| `--ecosystems` | `-e` | `SOCKET_ECOSYSTEMS` | (all) | CSV → `Vec<String>` | Restrict to these ecosystems |
| `--download-mode` | — | `SOCKET_DOWNLOAD_MODE` | **`diff`** | enum: `diff` \| `file` (`package` was removed and is rejected) | Patch artifact format |
| `--vendor-source` | — | `SOCKET_VENDOR_SOURCE` | **`auto`** | enum: `auto` \| `service` \| `build` | How `vendor` acquires the installable artifact (see "Prebuilt vendor artifacts") |
| `--maven-config` | — | — | (recorded choice, else `auto`) | enum: `auto` \| `none` | Maven reactor vendoring: write the repository tail (`auto`) or use only the fallback file repository (`none`). The choice persists in the vendor ledger. |
| `--vendor-url` | — | `SOCKET_VENDOR_URL` | (active API/proxy base) | string | Base host for the vendoring-service package-reference request |
| `--patch-server-url` | — | `SOCKET_PATCH_SERVER_URL` | (server-returned) | string | Override the host of the prebuilt-archive download URL (local-dev / testing) |
| `--offline` | — | `SOCKET_OFFLINE` | `false` | bool | **Strict airgap on every command** — never contact the network |
Expand Down Expand Up @@ -87,6 +88,8 @@ Beyond the globals above, each subcommand defines a small set of local arguments
| `apply` | `--check` | — | Read-only audit that the committed **Go** `replace`-redirects match the manifest (CI / GitHub-App auditing) — Go ONLY (cargo patches in place, so there is no redirect to audit). Lock-free, crawl-free, offline-safe; exits 0 in sync, 1 on drift. Vendored modules are excluded from the audit |
| `vendor` | `--force` / `-f` | `SOCKET_FORCE` | Tolerate missing patch-target files in the stage + bypass the variant probe. A beforeHash mismatch no longer needs it: vendor staging auto-overwrites with the verified patched content (`vendor_content_mismatch_overwritten` warning) |
| `vendor` | `--revert` | `SOCKET_VENDOR_REVERT` | Undo vendoring: restore recorded original lockfile fragments + remove `.socket/vendor/` artifacts. Works without a manifest. A package vendored over a hosted pin returns to its upstream registry entry, never to hosted (see "Takeover reconciliation") |
| `vendor` | `--check` | — | Offline, read-only artifact and wiring audit; exits 1 on drift. Conflicts with `--revert`. |
| `vendor` | `--local-repo <path>` | — | With `--check`, also inspect suffixed Maven jar/POM copies in this cache for conflicts. |
| `apply`, `scan`, `vendor` | `--vex` | `SOCKET_VEX` | Generate an OpenVEX 0.2.0 document at this path on a successful run; see "embedded VEX" below |
| `apply`, `scan`, `vendor` | `--vex-product`, `--vex-no-verify`, `--vex-doc-id`, `--vex-compact` | `SOCKET_VEX_PRODUCT`, `SOCKET_VEX_NO_VERIFY`, `SOCKET_VEX_DOC_ID`, `SOCKET_VEX_COMPACT` | Passthrough to the embedded VEX builder; mirror the standalone `vex` knobs. Inert unless `--vex` is set |
| `scan` | positional `[PATHS]...` | — | (v5.0) Meaning depends on the mode. **Hosted / vendored** (bare `scan` included): each PATH, or directory glob (`apps/*`), is a project directory scanned on its own as if it were `--cwd`. **Agent** (and a mode-less `--prune`/`--global` report): path globs scoping DISCOVERY to packages installed under matching paths (`packages/foo`, `apps/**`). See "Path-scoped scans" below |
Expand Down Expand Up @@ -1722,3 +1725,24 @@ Every item in this document is locked in by at least one of:
- **Async `run()` integration tests** in `tests/cli_parse_list.rs`, `tests/cli_parse_remove.rs` — exercise the no-network error paths and assert JSON shape via `serde_json::from_str::<Value>` + per-key assertions.

If you add a new flag/subcommand/JSON key, add a test here that locks the new surface in the same PR.


### Vendored JVM support (v5)

Maven reactors and Gradle 6.8+ route to the JVM backend automatically. Ledger
entries use ecosystem `jvm` with Maven PURLs. Revert, remove, rollback and repair
share the v5 vendored backend; existing prototype wiring remains readable.
See [the JVM design](../../docs/design/maven-vendoring.md) for supported shapes.

`vendor --check` is an offline, read-only audit. Healthy entries emit `verified`
with `vendor_check_ok`; drift emits `failed` with `vendor_check_failed`, a
`partialFailure` envelope and exit 1. Missing ledger entries fail with
`vendor_ledger_missing`. Offline upstream metadata is reported as the run warning
`vendor_jvm_upstream_unverified`. The check never starts an API client or writes
lock/recovery files. `--check` conflicts with `--revert`.

`vendor --check --local-repo <path>` additionally checks existing suffixed Maven
jar/POM copies for conflicting bytes. `--maven-config auto|none` is a global
vendoring option so scan/get/repair receive it too; omission preserves the
ledger's recorded choice. Switching existing auto-config wiring to `none`
requires reverting it first. `none` cannot be combined with a repository ban.
7 changes: 7 additions & 0 deletions crates/socket-patch-cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ pub struct GlobalArgs {
)]
pub vendor_source: String,

/// Vendored Maven: auto writes the repository tail; none uses only the file repository.
/// The choice is preserved on subsequent runs.
#[arg(help_heading = GLOBAL_OPTIONS, long, value_parser = ["auto", "none"])]
pub maven_config: Option<String>,

/// Base URL for the patch vendoring service. Defaults to the active API base (`--api-url`) when
/// authenticated or the proxy base (`--proxy-url`) otherwise. Override to
/// point `vendor` at staging / local dev independently of `--api-url`.
Expand Down Expand Up @@ -507,6 +512,7 @@ impl GlobalArgs {
use_public_proxy: bool,
) -> VendorServiceConfig {
VendorServiceConfig {
maven_config: self.maven_config.as_deref().map(|v| v != "none"),
source: VendorSource::parse(&self.vendor_source).unwrap_or_default(),
client,
use_public_proxy,
Expand Down Expand Up @@ -675,6 +681,7 @@ impl Default for GlobalArgs {
ecosystems: None,
download_mode: "diff".to_string(),
vendor_source: "auto".to_string(),
maven_config: None,
vendor_url: None,
patch_server_url: None,
offline: false,
Expand Down
Loading
Loading