Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2bbaec3
Validate --cwd, --global-prefix and --manifest-path before every command
mikolalysenko Oct 7, 2026
7b56939
Make apply --check verify every manifest patch, not only Go redirects
mikolalysenko Oct 7, 2026
2568c1a
Report every hosted patch per purl in the redirect JSON block
mikolalysenko Oct 7, 2026
835038e
Document path-flag validation, full apply --check and redirect.patches
mikolalysenko Oct 7, 2026
9ffb0e3
Pin redirect.patches in the hosted get and rollout envelope tests
mikolalysenko Oct 7, 2026
87041e1
Match apply --check copies against every release variant of a base
mikolalysenko Oct 7, 2026
e261602
Let get and scan create a missing --manifest-path directory
mikolalysenko Oct 7, 2026
b7b4854
Document the apply --check copy rules and manifest-path exemptions
mikolalysenko Oct 7, 2026
38e222f
Merge remote-tracking branch 'origin/main' into arch-fix/trust-signals
claude Oct 7, 2026
0b8e772
Run apply's yarn PnP refusal before apply --check verifies npm copies
claude Oct 7, 2026
9f40d47
Stop apply --check reporting a variant mismatch as not installed
claude Oct 7, 2026
26d8ce6
Merge remote-tracking branch 'origin/main' into arch-fix/trust-signals
claude Oct 7, 2026
72fe8ca
Fail apply --check on a zero-file manifest record
claude Oct 7, 2026
a939356
Merge origin/main into arch-fix/trust-signals
mikolalysenko Oct 8, 2026
27c5bd8
Keep refused BUNDLE_PATH gem copies in apply --check
mikolalysenko Oct 8, 2026
10148c6
Merge origin/main into arch-fix/trust-signals
mikolalysenko Oct 8, 2026
223a2b1
Merge origin/main into arch-fix/trust-signals
mikolalysenko Oct 8, 2026
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
8 changes: 5 additions & 3 deletions crates/socket-patch-cli/CLI_CONTRACT.md

Large diffs are not rendered by default.

76 changes: 75 additions & 1 deletion crates/socket-patch-cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ pub(crate) const GLOBAL_OPTIONS: &str = "Global options";
//
// **Every** global flag is parseable on **every** subcommand. Commands that
// don't use a given flag ignore it silently — e.g. `list --global` parses
// fine and the `global` field is unused at runtime.
// fine and the `global` field is unused at runtime. The one exception is
// the path flags (`--cwd`, `--global-prefix`, `--manifest-path`): `main`
// validates them on every project command, used or not
// ([`GlobalArgs::validate_paths`]), because a path that names nothing must
// never read as an empty project.
//
// (Plain `//` comments: clap turns a doc comment here into the `--help`
// description of any subcommand that has none of its own.)
Expand Down Expand Up @@ -383,6 +387,76 @@ pub struct GlobalArgs {
}

impl GlobalArgs {
/// Reject path flags that name nothing: `--cwd` and `--global-prefix`
/// (flag or env) must be existing directories, and a `--manifest-path`
/// other than the default must sit in an existing project directory and
/// must not itself be a directory. `main` maps `Err` to the usage exit
/// (2), the same exit a hosted/vendored `scan` PATH that is not a
/// directory gets.
///
/// Without this a typo in `--cwd` / `SOCKET_CWD` read as an empty
/// project: `apply`, `list`, `scan`, `get` and the `vendor --check` CI
/// gate all exited 0 having checked nothing.
///
/// A missing manifest FILE stays legal: hosted and vendored projects
/// have none, and `get` / `scan --mode agent` create it. Only its
/// project directory has to exist — except for the commands that
/// create the manifest (`creates_manifest`: `get`, `scan`), which make
/// a missing directory as they always have; writing it is no vacuous
/// pass.
pub fn validate_paths(&self, creates_manifest: bool) -> Result<(), String> {
let not_dir = |flag: &str, env: &str, path: &Path, what: &str| {
format!("{flag} (or {env}) `{}` {what}", path.display())
};
if !self.cwd.is_dir() {
let what = if self.cwd.exists() {
"is not a directory"
} else {
"does not exist"
};
return Err(not_dir("--cwd", "SOCKET_CWD", &self.cwd, what));
}
if let Some(prefix) = &self.global_prefix {
if !prefix.is_dir() {
let what = if prefix.exists() {
"is not a directory"
} else {
"does not exist"
};
return Err(not_dir(
"--global-prefix",
"SOCKET_GLOBAL_PREFIX",
prefix,
what,
));
}
}
if self.manifest_path != DEFAULT_PATCH_MANIFEST_PATH {
let manifest = self.resolved_manifest_path();
if manifest.is_dir() {
return Err(not_dir(
"--manifest-path",
"SOCKET_MANIFEST_PATH",
&manifest,
"is a directory, not a manifest file",
));
}
let root = self.project_root();
if !creates_manifest && !root.is_dir() {
return Err(not_dir(
"--manifest-path",
"SOCKET_MANIFEST_PATH",
&manifest,
&format!(
"is in a project directory that does not exist ({})",
root.display()
),
));
}
}
Ok(())
}

/// The crawler options this run's `--cwd` / `--global` /
/// `--global-prefix` select.
pub(crate) fn crawler_options(&self) -> socket_patch_core::crawlers::CrawlerOptions {
Expand Down
Loading
Loading