mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Add subdirectory file discovery to arc-devcontainer
When neither .devcontainer/devcontainer.json nor .devcontainer.json exists, scan .devcontainer/ for subdirectories containing a devcontainer.json. Subdirectories are sorted alphabetically and the first match is used. Standard locations still take priority. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d437f038d0
commit
62b4e9df74
7 changed files with 94 additions and 1 deletions
|
|
@ -357,17 +357,57 @@ impl DevcontainerResolver {
|
|||
return Ok((path.to_path_buf(), parsed));
|
||||
}
|
||||
|
||||
// Subdirectory format: scan .devcontainer/ for subdirs containing devcontainer.json
|
||||
let devcontainer_dir = path.join(".devcontainer");
|
||||
if devcontainer_dir.is_dir() {
|
||||
let mut subdirs: Vec<PathBuf> = std::fs::read_dir(&devcontainer_dir)
|
||||
.map_err(|source| DevcontainerError::ReadFile {
|
||||
path: devcontainer_dir.clone(),
|
||||
source,
|
||||
})?
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter(|entry| entry.path().is_dir())
|
||||
.map(|entry| entry.path())
|
||||
.filter(|dir| dir.join("devcontainer.json").exists())
|
||||
.collect();
|
||||
|
||||
// Sort alphabetically to get deterministic first pick
|
||||
subdirs.sort();
|
||||
|
||||
if let Some(subdir) = subdirs.first() {
|
||||
let candidate = subdir.join("devcontainer.json");
|
||||
let raw = std::fs::read_to_string(&candidate).map_err(|source| {
|
||||
DevcontainerError::ReadFile {
|
||||
path: candidate.clone(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
let stripped = jsonc::strip_jsonc(&raw);
|
||||
let parsed: DevcontainerJson = serde_json::from_str(&stripped)?;
|
||||
return Ok((candidate, parsed));
|
||||
}
|
||||
}
|
||||
|
||||
Err(DevcontainerError::NotFound(path.to_path_buf()))
|
||||
}
|
||||
|
||||
fn repo_root_from_json_path<'a>(json_path: &Path, original_path: &'a Path) -> &'a Path {
|
||||
// If json_path is inside .devcontainer/<subdir>/, the repo root is two levels up
|
||||
// If json_path is inside .devcontainer/, the repo root is one level up
|
||||
if let Some(parent) = json_path.parent() {
|
||||
if parent.file_name().is_some_and(|n| n == ".devcontainer") {
|
||||
if let Some(repo_root) = parent.parent() {
|
||||
// Only return repo_root if it matches the original path structure
|
||||
let _ = repo_root;
|
||||
}
|
||||
} else if let Some(grandparent) = parent.parent() {
|
||||
if grandparent
|
||||
.file_name()
|
||||
.is_some_and(|n| n == ".devcontainer")
|
||||
{
|
||||
if let Some(repo_root) = grandparent.parent() {
|
||||
let _ = repo_root;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
original_path
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"image": "mcr.microsoft.com/devcontainers/python:3.12",
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
|
||||
"remoteUser": "alpha-user"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"image": "node:20",
|
||||
"remoteUser": "beta-user"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
|
||||
"remoteUser": "standard-user"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"image": "mcr.microsoft.com/devcontainers/python:3.12",
|
||||
"remoteUser": "python-user"
|
||||
}
|
||||
|
|
@ -126,6 +126,39 @@ async fn resolve_not_found() {
|
|||
assert!(err.to_string().contains("no devcontainer.json found"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_subdirectory_mode() {
|
||||
let config = DevcontainerResolver::resolve(&fixture_path("subdirectory-mode"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(config.dockerfile.contains("FROM mcr.microsoft.com/devcontainers/python:3.12"));
|
||||
assert_eq!(config.remote_user.as_deref(), Some("vscode"));
|
||||
assert_eq!(config.workspace_folder, "/workspaces/subdirectory-mode");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_subdirectory_multiple_picks_alphabetical_first() {
|
||||
let config = DevcontainerResolver::resolve(&fixture_path("subdirectory-multiple"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// "alpha" sorts before "beta", so alpha's config is used
|
||||
assert!(config.dockerfile.contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu"));
|
||||
assert_eq!(config.remote_user.as_deref(), Some("alpha-user"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolve_subdirectory_standard_wins_over_subdirs() {
|
||||
let config = DevcontainerResolver::resolve(&fixture_path("subdirectory-with-standard"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Standard .devcontainer/devcontainer.json takes priority over subdirectory format
|
||||
assert!(config.dockerfile.contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu"));
|
||||
assert_eq!(config.remote_user.as_deref(), Some("standard-user"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn generated_dockerfile_is_well_formed() {
|
||||
let config = DevcontainerResolver::resolve(&fixture_path("image-only"))
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue