Project File Discovery
Part of #7435 — Multi-Language Hook Support
User Story
As a template consumer, I want azd to automatically find my hook's dependency files (requirements.txt, package.json, etc.) so that dependencies are installed without me configuring anything extra.
Solution Approach
Create pkg/tools/language/project_discovery.go (NEW):
- Implement
DiscoverProjectFile(scriptDir, boundaryDir string, fileNames []string) (string, error):
- Walk up parent directories from
scriptDir looking for any file in fileNames.
- Stop at
boundaryDir (inclusive) — never escape the project/service root.
- Return the directory containing the first match, or empty string if not found.
- Return error only for filesystem errors, not for "not found".
- Define helper constants:
PythonProjectFiles = []string{"requirements.txt", "pyproject.toml"}
NodeProjectFiles = []string{"package.json"} (future)
DotNetProjectFilePattern = "*.csproj" (future, needs glob)
Create pkg/tools/language/project_discovery_test.go (NEW):
- Use
t.TempDir() for isolated filesystem fixtures.
- Table-driven tests (follow
pkg/tools/python/python_test.go pattern):
- Project file at script directory level
- Project file one level up
- Project file at boundary directory (root)
- No project file found → returns empty string
- Script outside boundary → returns error
- Both
requirements.txt and pyproject.toml present → first match wins per slice order
- Empty directory tree
Files: pkg/tools/language/project_discovery.go (NEW), pkg/tools/language/project_discovery_test.go (NEW)
Acceptance Criteria
References
Project File Discovery
Part of #7435 — Multi-Language Hook Support
User Story
As a template consumer, I want azd to automatically find my hook's dependency files (requirements.txt, package.json, etc.) so that dependencies are installed without me configuring anything extra.
Solution Approach
Create
pkg/tools/language/project_discovery.go(NEW):DiscoverProjectFile(scriptDir, boundaryDir string, fileNames []string) (string, error):scriptDirlooking for any file infileNames.boundaryDir(inclusive) — never escape the project/service root.PythonProjectFiles = []string{"requirements.txt", "pyproject.toml"}NodeProjectFiles = []string{"package.json"}(future)DotNetProjectFilePattern = "*.csproj"(future, needs glob)Create
pkg/tools/language/project_discovery_test.go(NEW):t.TempDir()for isolated filesystem fixtures.pkg/tools/python/python_test.gopattern):requirements.txtandpyproject.tomlpresent → first match wins per slice orderFiles:
pkg/tools/language/project_discovery.go(NEW),pkg/tools/language/project_discovery_test.go(NEW)Acceptance Criteria
scriptDir == boundaryDiredge case handled correctlyReferences