mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-11 20:13:30 +08:00
feat: add ecc2 package manager harness env
This commit is contained in:
@@ -3228,6 +3228,10 @@ fn apply_shared_harness_runtime_env(
|
|||||||
command.env("CLAUDE_SESSION_ID", session_id);
|
command.env("CLAUDE_SESSION_ID", session_id);
|
||||||
command.env("CLAUDE_PROJECT_DIR", working_dir);
|
command.env("CLAUDE_PROJECT_DIR", working_dir);
|
||||||
command.env("CLAUDE_CODE_ENTRYPOINT", "cli");
|
command.env("CLAUDE_CODE_ENTRYPOINT", "cli");
|
||||||
|
if let Some(package_manager) = resolve_project_package_manager(working_dir) {
|
||||||
|
command.env("CLAUDE_PACKAGE_MANAGER", package_manager);
|
||||||
|
command.env("CLAUDE_CODE_PACKAGE_MANAGER", package_manager);
|
||||||
|
}
|
||||||
if let Some(model) = profile.and_then(|profile| profile.model.as_ref()) {
|
if let Some(model) = profile.and_then(|profile| profile.model.as_ref()) {
|
||||||
command.env("CLAUDE_MODEL", model);
|
command.env("CLAUDE_MODEL", model);
|
||||||
}
|
}
|
||||||
@@ -3259,6 +3263,75 @@ fn is_ecc_plugin_root(candidate: &Path) -> bool {
|
|||||||
candidate.join("scripts/lib/utils.js").is_file() && candidate.join("hooks/hooks.json").is_file()
|
candidate.join("scripts/lib/utils.js").is_file() && candidate.join("hooks/hooks.json").is_file()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_project_package_manager(working_dir: &Path) -> Option<&'static str> {
|
||||||
|
if let Ok(package_manager) = std::env::var("CLAUDE_PACKAGE_MANAGER") {
|
||||||
|
if let Some(package_manager) = normalize_package_manager_name(&package_manager) {
|
||||||
|
return Some(package_manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
read_package_manager_from_json(
|
||||||
|
&working_dir.join(".claude").join("package-manager.json"),
|
||||||
|
"packageManager",
|
||||||
|
)
|
||||||
|
.or_else(|| read_package_manager_from_package_json(&working_dir.join("package.json")))
|
||||||
|
.or_else(|| detect_package_manager_from_lockfile(working_dir))
|
||||||
|
.or_else(|| {
|
||||||
|
dirs::home_dir().and_then(|home_dir| {
|
||||||
|
read_package_manager_from_json(
|
||||||
|
&home_dir.join(".claude").join("package-manager.json"),
|
||||||
|
"packageManager",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.or(Some("npm"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_package_manager_from_json(path: &Path, field_name: &str) -> Option<&'static str> {
|
||||||
|
let content = std::fs::read_to_string(path).ok()?;
|
||||||
|
let value: serde_json::Value = serde_json::from_str(&content).ok()?;
|
||||||
|
value
|
||||||
|
.get(field_name)
|
||||||
|
.and_then(|value| value.as_str())
|
||||||
|
.and_then(normalize_package_manager_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_package_manager_from_package_json(path: &Path) -> Option<&'static str> {
|
||||||
|
let package_manager = read_package_manager_from_json(path, "packageManager")?;
|
||||||
|
Some(package_manager)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_package_manager_from_lockfile(working_dir: &Path) -> Option<&'static str> {
|
||||||
|
[
|
||||||
|
("pnpm", "pnpm-lock.yaml"),
|
||||||
|
("bun", "bun.lockb"),
|
||||||
|
("yarn", "yarn.lock"),
|
||||||
|
("npm", "package-lock.json"),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|(package_manager, lockfile)| {
|
||||||
|
working_dir
|
||||||
|
.join(lockfile)
|
||||||
|
.is_file()
|
||||||
|
.then_some(package_manager)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_package_manager_name(package_manager: &str) -> Option<&'static str> {
|
||||||
|
let canonical = package_manager
|
||||||
|
.split('@')
|
||||||
|
.next()
|
||||||
|
.unwrap_or(package_manager)
|
||||||
|
.trim();
|
||||||
|
match canonical {
|
||||||
|
"npm" => Some("npm"),
|
||||||
|
"pnpm" => Some("pnpm"),
|
||||||
|
"yarn" => Some("yarn"),
|
||||||
|
"bun" => Some("bun"),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn normalize_task_for_harness(
|
fn normalize_task_for_harness(
|
||||||
harness: HarnessKind,
|
harness: HarnessKind,
|
||||||
task: &str,
|
task: &str,
|
||||||
@@ -4576,6 +4649,69 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_agent_command_exports_detected_package_manager_env_from_lockfile() -> Result<()> {
|
||||||
|
let tempdir = TestDir::new("manager-package-manager-lockfile")?;
|
||||||
|
let repo_root = tempdir.path().join("repo");
|
||||||
|
fs::create_dir_all(&repo_root)?;
|
||||||
|
write_package_manager_project_files(&repo_root, None, Some("pnpm-lock.yaml"), None)?;
|
||||||
|
|
||||||
|
let cfg = Config::default();
|
||||||
|
let command = build_agent_command(
|
||||||
|
&cfg,
|
||||||
|
"codex",
|
||||||
|
Path::new("codex"),
|
||||||
|
"inspect dependency graph",
|
||||||
|
"sess-pnpm",
|
||||||
|
&repo_root,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let envs = command_env_map(&command);
|
||||||
|
assert_eq!(
|
||||||
|
envs.get("CLAUDE_PACKAGE_MANAGER"),
|
||||||
|
Some(&"pnpm".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
envs.get("CLAUDE_CODE_PACKAGE_MANAGER"),
|
||||||
|
Some(&"pnpm".to_string())
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn build_agent_command_prefers_project_package_manager_config_over_lockfile() -> Result<()> {
|
||||||
|
let tempdir = TestDir::new("manager-package-manager-config")?;
|
||||||
|
let repo_root = tempdir.path().join("repo");
|
||||||
|
fs::create_dir_all(&repo_root)?;
|
||||||
|
write_package_manager_project_files(
|
||||||
|
&repo_root,
|
||||||
|
Some("pnpm@9.0.0"),
|
||||||
|
Some("package-lock.json"),
|
||||||
|
Some("yarn"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let cfg = Config::default();
|
||||||
|
let command = build_agent_command(
|
||||||
|
&cfg,
|
||||||
|
"codex",
|
||||||
|
Path::new("codex"),
|
||||||
|
"inspect dependency graph",
|
||||||
|
"sess-yarn",
|
||||||
|
&repo_root,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let envs = command_env_map(&command);
|
||||||
|
assert_eq!(
|
||||||
|
envs.get("CLAUDE_PACKAGE_MANAGER"),
|
||||||
|
Some(&"yarn".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
envs.get("CLAUDE_CODE_PACKAGE_MANAGER"),
|
||||||
|
Some(&"yarn".to_string())
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_session_record_canonicalizes_known_agent_aliases() -> Result<()> {
|
fn build_session_record_canonicalizes_known_agent_aliases() -> Result<()> {
|
||||||
let tempdir = TestDir::new("manager-canonical-agent-type")?;
|
let tempdir = TestDir::new("manager-canonical-agent-type")?;
|
||||||
@@ -4868,7 +5004,7 @@ mod tests {
|
|||||||
let script_path = root.join("fake-claude.sh");
|
let script_path = root.join("fake-claude.sh");
|
||||||
let log_path = root.join("fake-claude.log");
|
let log_path = root.join("fake-claude.log");
|
||||||
let script = format!(
|
let script = format!(
|
||||||
"#!/usr/bin/env python3\nimport os\nimport pathlib\nimport signal\nimport sys\nimport time\n\nlog_path = pathlib.Path(r\"{}\")\nlog_path.write_text(os.getcwd() + \"\\n\", encoding=\"utf-8\")\nwith log_path.open(\"a\", encoding=\"utf-8\") as handle:\n handle.write(\" \".join(sys.argv[1:]) + \"\\n\")\n handle.write(\"ECC_SESSION_ID=\" + os.environ.get(\"ECC_SESSION_ID\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_SESSION_ID=\" + os.environ.get(\"CLAUDE_SESSION_ID\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_PROJECT_DIR=\" + os.environ.get(\"CLAUDE_PROJECT_DIR\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_CODE_ENTRYPOINT=\" + os.environ.get(\"CLAUDE_CODE_ENTRYPOINT\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_PLUGIN_ROOT=\" + os.environ.get(\"CLAUDE_PLUGIN_ROOT\", \"\") + \"\\n\")\n handle.write(\"ECC_HARNESS=\" + os.environ.get(\"ECC_HARNESS\", \"\") + \"\\n\")\n\ndef handle_term(signum, frame):\n raise SystemExit(0)\n\nsignal.signal(signal.SIGTERM, handle_term)\nwhile True:\n time.sleep(0.1)\n",
|
"#!/usr/bin/env python3\nimport os\nimport pathlib\nimport signal\nimport sys\nimport time\n\nlog_path = pathlib.Path(r\"{}\")\nlog_path.write_text(os.getcwd() + \"\\n\", encoding=\"utf-8\")\nwith log_path.open(\"a\", encoding=\"utf-8\") as handle:\n handle.write(\" \".join(sys.argv[1:]) + \"\\n\")\n handle.write(\"ECC_SESSION_ID=\" + os.environ.get(\"ECC_SESSION_ID\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_SESSION_ID=\" + os.environ.get(\"CLAUDE_SESSION_ID\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_PROJECT_DIR=\" + os.environ.get(\"CLAUDE_PROJECT_DIR\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_CODE_ENTRYPOINT=\" + os.environ.get(\"CLAUDE_CODE_ENTRYPOINT\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_PACKAGE_MANAGER=\" + os.environ.get(\"CLAUDE_PACKAGE_MANAGER\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_CODE_PACKAGE_MANAGER=\" + os.environ.get(\"CLAUDE_CODE_PACKAGE_MANAGER\", \"\") + \"\\n\")\n handle.write(\"CLAUDE_PLUGIN_ROOT=\" + os.environ.get(\"CLAUDE_PLUGIN_ROOT\", \"\") + \"\\n\")\n handle.write(\"ECC_HARNESS=\" + os.environ.get(\"ECC_HARNESS\", \"\") + \"\\n\")\n\ndef handle_term(signum, frame):\n raise SystemExit(0)\n\nsignal.signal(signal.SIGTERM, handle_term)\nwhile True:\n time.sleep(0.1)\n",
|
||||||
log_path.display()
|
log_path.display()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -4911,11 +5047,39 @@ mod tests {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_package_manager_project_files(
|
||||||
|
repo_root: &Path,
|
||||||
|
package_manager_field: Option<&str>,
|
||||||
|
lockfile_name: Option<&str>,
|
||||||
|
project_config_package_manager: Option<&str>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let package_json = match package_manager_field {
|
||||||
|
Some(package_manager_field) => format!(
|
||||||
|
"{{\"name\":\"ecc-smoke\",\"packageManager\":\"{package_manager_field}\"}}\n"
|
||||||
|
),
|
||||||
|
None => "{\"name\":\"ecc-smoke\"}\n".to_string(),
|
||||||
|
};
|
||||||
|
fs::write(repo_root.join("package.json"), package_json)?;
|
||||||
|
if let Some(lockfile_name) = lockfile_name {
|
||||||
|
fs::write(repo_root.join(lockfile_name), "lockfile\n")?;
|
||||||
|
}
|
||||||
|
if let Some(project_config_package_manager) = project_config_package_manager {
|
||||||
|
let claude_dir = repo_root.join(".claude");
|
||||||
|
fs::create_dir_all(&claude_dir)?;
|
||||||
|
fs::write(
|
||||||
|
claude_dir.join("package-manager.json"),
|
||||||
|
format!("{{\"packageManager\":\"{project_config_package_manager}\"}}\n"),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "current_thread")]
|
#[tokio::test(flavor = "current_thread")]
|
||||||
async fn create_session_spawns_process_and_marks_session_running() -> Result<()> {
|
async fn create_session_spawns_process_and_marks_session_running() -> Result<()> {
|
||||||
let tempdir = TestDir::new("manager-create-session")?;
|
let tempdir = TestDir::new("manager-create-session")?;
|
||||||
let repo_root = tempdir.path().join("repo");
|
let repo_root = tempdir.path().join("repo");
|
||||||
init_git_repo(&repo_root)?;
|
init_git_repo(&repo_root)?;
|
||||||
|
write_package_manager_project_files(&repo_root, None, Some("pnpm-lock.yaml"), None)?;
|
||||||
|
|
||||||
let cfg = build_config(tempdir.path());
|
let cfg = build_config(tempdir.path());
|
||||||
let db = StateStore::open(&cfg.db_path)?;
|
let db = StateStore::open(&cfg.db_path)?;
|
||||||
@@ -4952,6 +5116,8 @@ mod tests {
|
|||||||
repo_root.to_string_lossy()
|
repo_root.to_string_lossy()
|
||||||
)));
|
)));
|
||||||
assert!(log.contains("CLAUDE_CODE_ENTRYPOINT=cli"));
|
assert!(log.contains("CLAUDE_CODE_ENTRYPOINT=cli"));
|
||||||
|
assert!(log.contains("CLAUDE_PACKAGE_MANAGER=pnpm"));
|
||||||
|
assert!(log.contains("CLAUDE_CODE_PACKAGE_MANAGER=pnpm"));
|
||||||
assert!(log.contains("ECC_HARNESS=claude"));
|
assert!(log.contains("ECC_HARNESS=claude"));
|
||||||
|
|
||||||
stop_session_with_options(&db, &session_id, false).await?;
|
stop_session_with_options(&db, &session_id, false).await?;
|
||||||
|
|||||||
Reference in New Issue
Block a user