Skip to content

Commit d6ad119

Browse files
committed
Add executable_path to RunConfig and use it in config loading
1 parent 3fac3d7 commit d6ad119

File tree

19 files changed

+278
-51
lines changed

19 files changed

+278
-51
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,15 @@ codeowners gv --no-cache
213213
- `vendored_gems_path` (default: `'vendored/'`)
214214
- `cache_directory` (default: `'tmp/cache/codeowners'`)
215215
- `ignore_dirs` (default includes: `.git`, `node_modules`, `tmp`, etc.)
216+
- `executable_name` (default: `'codeowners'`): Customize the command name shown in validation error messages. Useful when using `codeowners-rs` via wrappers like the [code_ownership](https://github.com/rubyatscale/code_ownership) Ruby gem.
217+
218+
Example configuration with custom executable name:
219+
220+
```yaml
221+
owned_globs:
222+
- '{app,components,config,frontend,lib,packs,spec}/**/*.{rb,rake,js,jsx,ts,tsx}'
223+
executable_name: 'bin/codeownership' # For Ruby gem wrapper
224+
```
216225

217226
See examples in `tests/fixtures/**/config/` for reference setups.
218227

src/cli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ pub fn cli() -> Result<RunResult, RunnerError> {
114114
codeowners_file_path,
115115
project_root,
116116
no_cache: args.no_cache,
117+
executable_path: None,
117118
};
118119

119120
let runner_result = match args.command {

src/crosscheck.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
ownership::file_owner_resolver::find_file_owners,
77
project::Project,
88
project_builder::ProjectBuilder,
9-
runner::{RunConfig, RunResult, config_from_path, team_for_file_from_codeowners},
9+
runner::{RunConfig, RunResult, config_from_run_config, team_for_file_from_codeowners},
1010
};
1111

1212
pub fn crosscheck_owners(run_config: &RunConfig, cache: &Cache) -> RunResult {
@@ -43,7 +43,7 @@ fn do_crosscheck_owners(run_config: &RunConfig, cache: &Cache) -> Result<Vec<Str
4343
}
4444

4545
fn load_config(run_config: &RunConfig) -> Result<Config, String> {
46-
config_from_path(&run_config.config_path).map_err(|e| e.to_string())
46+
config_from_run_config(run_config).map_err(|e| e.to_string())
4747
}
4848

4949
fn build_project(config: &Config, run_config: &RunConfig, cache: &Cache) -> Result<Project, String> {

src/runner.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{path::Path, process::Command};
1+
use std::process::Command;
22

33
use error_stack::{Result, ResultExt};
44
use serde::Serialize;
@@ -44,15 +44,20 @@ where
4444
runnable(runner)
4545
}
4646

47-
pub(crate) fn config_from_path(path: &Path) -> Result<Config, Error> {
48-
match crate::config::Config::load_from_path(path) {
49-
Ok(c) => Ok(c),
47+
pub(crate) fn config_from_run_config(run_config: &RunConfig) -> Result<Config, Error> {
48+
match crate::config::Config::load_from_path(&run_config.config_path) {
49+
Ok(mut c) => {
50+
if let Some(executable_name) = &run_config.executable_name() {
51+
c.executable_name = executable_name.clone();
52+
}
53+
Ok(c)
54+
}
5055
Err(msg) => Err(error_stack::Report::new(Error::Io(msg))),
5156
}
5257
}
5358
impl Runner {
5459
pub fn new(run_config: &RunConfig) -> Result<Self, Error> {
55-
let config = config_from_path(&run_config.config_path)?;
60+
let config = config_from_run_config(run_config)?;
5661

5762
let cache: Cache = if run_config.no_cache {
5863
NoopCache::default().into()

src/runner/api.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use std::collections::HashMap;
22

3-
use crate::ownership::FileOwner;
43
use crate::project::Team;
4+
use crate::{ownership::FileOwner, runner::config_from_run_config};
55

6-
use super::{Error, ForFileResult, RunConfig, RunResult, config_from_path, run};
6+
use super::{Error, ForFileResult, RunConfig, RunResult, run};
77

88
pub fn for_file(run_config: &RunConfig, file_path: &str, from_codeowners: bool, json: bool) -> RunResult {
99
if from_codeowners {
@@ -38,7 +38,7 @@ pub fn crosscheck_owners(run_config: &RunConfig) -> RunResult {
3838

3939
// Returns all owners for a file without creating a Runner (performance optimized)
4040
pub fn owners_for_file(run_config: &RunConfig, file_path: &str) -> error_stack::Result<Vec<FileOwner>, Error> {
41-
let config = config_from_path(&run_config.config_path)?;
41+
let config = config_from_run_config(run_config)?;
4242
use crate::ownership::file_owner_resolver::find_file_owners;
4343
let owners = find_file_owners(&run_config.project_root, &config, std::path::Path::new(file_path)).map_err(Error::Io)?;
4444
Ok(owners)
@@ -60,7 +60,7 @@ pub fn teams_for_files_from_codeowners(
6060
run_config: &RunConfig,
6161
file_paths: &[String],
6262
) -> error_stack::Result<HashMap<String, Option<Team>>, Error> {
63-
let config = config_from_path(&run_config.config_path)?;
63+
let config = config_from_run_config(run_config)?;
6464
let res = crate::ownership::codeowners_query::teams_for_files_from_codeowners(
6565
&run_config.project_root,
6666
&run_config.codeowners_file_path,
@@ -80,7 +80,7 @@ pub fn team_for_file_from_codeowners(run_config: &RunConfig, file_path: &str) ->
8080

8181
// Fast path that avoids creating a full Runner for single file queries
8282
fn for_file_optimized(run_config: &RunConfig, file_path: &str, json: bool) -> RunResult {
83-
let config = match config_from_path(&run_config.config_path) {
83+
let config = match config_from_run_config(run_config) {
8484
Ok(c) => c,
8585
Err(err) => {
8686
return RunResult::from_io_error(Error::Io(err.to_string()), json);

src/runner/types.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use std::path::PathBuf;
44
use error_stack::Context;
55
use serde::{Deserialize, Serialize};
66

7+
use crate::path_utils::relative_to_buf;
8+
79
#[derive(Debug, Default, Serialize, Deserialize)]
810
pub struct RunResult {
911
pub validation_errors: Vec<String>,
@@ -17,6 +19,15 @@ pub struct RunConfig {
1719
pub codeowners_file_path: PathBuf,
1820
pub config_path: PathBuf,
1921
pub no_cache: bool,
22+
pub executable_path: Option<PathBuf>,
23+
}
24+
25+
impl RunConfig {
26+
pub fn executable_name(&self) -> Option<String> {
27+
self.executable_path
28+
.as_ref()
29+
.map(|path| relative_to_buf(&self.project_root, path).to_string_lossy().to_string())
30+
}
2031
}
2132

2233
#[derive(Debug, Serialize)]

tests/common/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ pub fn build_run_config(project_root: &Path, codeowners_rel_path: &str) -> RunCo
157157
codeowners_file_path,
158158
config_path,
159159
no_cache: true,
160+
executable_path: None,
160161
}
161162
}
162163

tests/custom_executable_name_test.rs

Lines changed: 0 additions & 32 deletions
This file was deleted.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use indoc::indoc;
2+
use predicates::prelude::*;
3+
use std::error::Error;
4+
5+
mod common;
6+
use common::OutputStream;
7+
use common::run_codeowners;
8+
9+
#[test]
10+
fn test_validate_with_custom_executable_name() -> Result<(), Box<dyn Error>> {
11+
// When executable_name is configured, error should show that command
12+
run_codeowners(
13+
"custom_executable_name",
14+
&["validate"],
15+
false,
16+
OutputStream::Stdout,
17+
predicate::str::contains("Run `bin/codeownership generate`"),
18+
)?;
19+
Ok(())
20+
}
21+
22+
#[test]
23+
fn test_validate_with_default_executable_name() -> Result<(), Box<dyn Error>> {
24+
// When executable_name is not configured, error should show default "codeowners"
25+
run_codeowners(
26+
"default_executable_name",
27+
&["validate"],
28+
false,
29+
OutputStream::Stdout,
30+
predicate::str::contains("Run `codeowners generate`"),
31+
)?;
32+
Ok(())
33+
}
34+
35+
#[test]
36+
fn test_custom_executable_name_full_error_message() -> Result<(), Box<dyn Error>> {
37+
// Verify the complete error message format with custom executable
38+
run_codeowners(
39+
"custom_executable_name",
40+
&["validate"],
41+
false,
42+
OutputStream::Stdout,
43+
predicate::eq(indoc! {"
44+
45+
CODEOWNERS out of date. Run `bin/codeownership generate` to update the CODEOWNERS file
46+
47+
"}),
48+
)?;
49+
Ok(())
50+
}
51+
52+
#[test]
53+
fn test_default_executable_name_full_error_message() -> Result<(), Box<dyn Error>> {
54+
// Verify the complete error message format with default executable
55+
run_codeowners(
56+
"default_executable_name",
57+
&["validate"],
58+
false,
59+
OutputStream::Stdout,
60+
predicate::eq(indoc! {"
61+
62+
CODEOWNERS out of date. Run `codeowners generate` to update the CODEOWNERS file
63+
64+
"}),
65+
)?;
66+
Ok(())
67+
}
Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1-
# This is a stale CODEOWNERS file - it's intentionally wrong to trigger the error
2-
ruby/app/payments/bar.rb @PaymentTeam
1+
# STOP! - DO NOT EDIT THIS FILE MANUALLY
2+
# This file was automatically generated by "codeowners".
3+
#
4+
# CODEOWNERS is used for GitHub to suggest code/file owners to various GitHub
5+
# teams. This is useful when developers create Pull Requests since the
6+
# code/file owner is notified. Reference GitHub docs for more details:
7+
# https://help.github.com/en/articles/about-code-owners
38

9+
# Outdated content to trigger validation error
10+
/app/old.rb @FooTeam

0 commit comments

Comments
 (0)