Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9aafb49
feat: add multi-language hook support (Phase 1 — Python)
wbreza Apr 2, 2026
8f16385
fix: address CI lint failures (spelling, permissions, line length)
wbreza Apr 2, 2026
388663e
fix: align dependency file priority with framework services
wbreza Apr 2, 2026
dc4e57c
fix: auto-infer dir from run path for language hooks
wbreza Apr 2, 2026
601f4cb
fix: resolve test failures for error mapping and service hooks
wbreza Apr 2, 2026
fe88e8f
refactor: unify shell and language hooks under single ScriptExecutor
wbreza Apr 3, 2026
8506839
refactor: IoC-based HookExecutor with ExecutionContext
wbreza Apr 3, 2026
b119bd1
fix: register HookExecutors in service hooks validation test
wbreza Apr 3, 2026
3b44f9f
fix: adapt layer hook tests for HookExecutor architecture
wbreza Apr 3, 2026
60cad82
refactor: move inline temp-file creation to executor Prepare()
wbreza Apr 6, 2026
31c9683
refactor: eliminate script-vs-language distinction from common hook c…
wbreza Apr 6, 2026
2021769
fix: address PR review feedback — security, bugs, and code quality
wbreza Apr 6, 2026
2d8adc3
fix: address PR review feedback from jongio
wbreza Apr 7, 2026
30a93d7
refactor: rename Language→Kind, ScriptLanguage→HookKind
wbreza Apr 7, 2026
3f1017b
fix: add 'venvs' to cspell overrides for hooks_runner.go
wbreza Apr 7, 2026
1763fde
fix: remove unused shellToKind function (golangci-lint unused)
wbreza Apr 7, 2026
6f0d21b
fix: update project_config_coverage3_test.go to use language.HookKind…
wbreza Apr 7, 2026
0e56a27
fix: cross-platform path handling in hook validation
wbreza Apr 7, 2026
7adae7e
fix: set execute permission on inline temp scripts for Linux/Mac
wbreza Apr 7, 2026
ff8e0da
fix: resolve run paths relative to dir when dir is explicitly set
wbreza Apr 7, 2026
30a8751
refactor: resolve paths once in validate(), eliminate dirExplicit
wbreza Apr 7, 2026
01cc528
fix: move venvs cspell override to models.go
wbreza Apr 7, 2026
8162310
refactor: remove Language field, replace hardcoded strings with HookK…
wbreza Apr 7, 2026
097ed07
fix: address PR review feedback (schema, path traversal, cleanup orde…
wbreza Apr 8, 2026
f6247cb
fix: log cleanup errors, add cspell override for copylocks
wbreza Apr 9, 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
1 change: 1 addition & 0 deletions cli/azd/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ azd.sln
**/target

# Coverage artifacts (generated by mage coverage:* targets)
cover
cover-local.out
cover-ci-combined.out
coverage.html
Expand Down
29 changes: 29 additions & 0 deletions cli/azd/.vscode/cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@ overrides:
- println
- myext
- Fatalf
- filename: extensions/microsoft.azd.ai.builder/internal/cmd/start.go
words:
- dall
- datasource
- vectorizing
- filename: pkg/project/dockerfile_builder.go
words:
- WORKDIR
Expand Down Expand Up @@ -307,6 +312,12 @@ overrides:
- filename: pkg/extensions/manager.go
words:
- myext
- filename: extensions/microsoft.azd.extensions/internal/resources/languages/**/.gitignore
words:
- rsuser
- userosscache
- docstates
- dylib
- filename: docs/recording-functional-tests-guide.md
words:
- httptest
Expand Down Expand Up @@ -342,18 +353,36 @@ overrides:
words:
- WORKDIR
- workdir
- filename: extensions/microsoft.azd.demo/internal/cmd/metadata.go
words:
- invopop
- filename: extensions/microsoft.azd.concurx/internal/cmd/prompt_model.go
words:
- textinput
- filename: pkg/azdext/scope_detector.go
words:
- fakeazure
- filename: "{pkg/azapi/permissions.go,pkg/infra/provisioning/bicep/bicep_provider.go}"
words:
- ABAC
- filename: extensions/azure.ai.models/internal/cmd/custom_create.go
words:
- Qwen
- filename: pkg/infra/provisioning/bicep/local_preflight.go
words:
- actioned
- filename: pkg/project/project.go
words:
- copylocks
- filename: pkg/ext/models.go
words:
- unvalidated
- venvs
- workdir
- filename: docs/code-coverage-guide.md
words:
- covdata
- GOWORK
ignorePaths:
- "**/*_test.go"
- "**/mock*.go"
Expand Down
15 changes: 15 additions & 0 deletions cli/azd/cmd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,17 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/state"
"github.com/azure/azure-dev/cli/azd/pkg/templates"
"github.com/azure/azure-dev/cli/azd/pkg/tools/az"
"github.com/azure/azure-dev/cli/azd/pkg/tools/bash"
"github.com/azure/azure-dev/cli/azd/pkg/tools/docker"
"github.com/azure/azure-dev/cli/azd/pkg/tools/dotnet"
"github.com/azure/azure-dev/cli/azd/pkg/tools/git"
"github.com/azure/azure-dev/cli/azd/pkg/tools/github"
"github.com/azure/azure-dev/cli/azd/pkg/tools/javac"
"github.com/azure/azure-dev/cli/azd/pkg/tools/kubectl"
"github.com/azure/azure-dev/cli/azd/pkg/tools/language"
"github.com/azure/azure-dev/cli/azd/pkg/tools/maven"
"github.com/azure/azure-dev/cli/azd/pkg/tools/node"
"github.com/azure/azure-dev/cli/azd/pkg/tools/powershell"
"github.com/azure/azure-dev/cli/azd/pkg/tools/python"
"github.com/azure/azure-dev/cli/azd/pkg/tools/swa"
"github.com/azure/azure-dev/cli/azd/pkg/workflow"
Expand Down Expand Up @@ -811,6 +814,18 @@ func registerCommonDependencies(container *ioc.NestedContainer) {

container.MustRegisterNamedScoped(string(project.ServiceLanguageDocker), project.NewDockerProjectAsFrameworkService)

// Hook executors registered by language name (transient — fresh per hook invocation).
// The HooksRunner resolves these via serviceLocator.ResolveNamed().
hookExecutorMap := map[language.HookKind]any{
language.HookKindBash: bash.NewExecutor,
language.HookKindPowerShell: powershell.NewExecutor,
language.HookKindPython: language.NewPythonExecutor,
}

for kind, constructor := range hookExecutorMap {
container.MustRegisterNamedTransient(string(kind), constructor)
}

// Pipelines
container.MustRegisterScoped(pipeline.NewPipelineManager)
container.MustRegisterSingleton(func(flags *pipelineConfigFlags) *pipeline.PipelineManagerArgs {
Expand Down
2 changes: 1 addition & 1 deletion cli/azd/cmd/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ func (hra *hooksRunAction) execHook(
hooksManager, hra.commandRunner, hra.envManager, hra.console, cwd, hooksMap, hra.env, hra.serviceLocator)

// Always run in interactive mode for 'azd hooks run', to help with testing/debugging
runOptions := &tools.ExecOptions{
runOptions := &tools.ExecutionContext{
Interactive: new(true),
}

Expand Down
23 changes: 17 additions & 6 deletions cli/azd/cmd/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,22 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/ext"
"github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning"
"github.com/azure/azure-dev/cli/azd/pkg/project"
"github.com/azure/azure-dev/cli/azd/pkg/tools/language"
"github.com/azure/azure-dev/cli/azd/test/mocks"
"github.com/azure/azure-dev/cli/azd/test/mocks/mockenv"
"github.com/azure/azure-dev/cli/azd/test/mocks/mocktools"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

// registerHookExecutors delegates to the shared test helper in test/mocks/mocktools.
func registerHookExecutors(mockCtx *mocks.MockContext) {
mocktools.RegisterHookExecutors(mockCtx)
}

func Test_HooksRunAction_RunsLayerHooks(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
env := environment.NewWithValues("test", nil)
envManager := &mockenv.MockEnvManager{}
envManager.On("Reload", mock.Anything, mock.Anything).Return(nil)
Expand All @@ -42,7 +50,7 @@ func Test_HooksRunAction_RunsLayerHooks(t *testing.T) {
Path: "infra/core",
Hooks: provisioning.HooksConfig{
"preprovision": {{
Shell: ext.ShellTypeBash,
Shell: string(language.HookKindBash),
Run: "echo core",
}},
},
Expand All @@ -52,7 +60,7 @@ func Test_HooksRunAction_RunsLayerHooks(t *testing.T) {
Path: absoluteLayerPath,
Hooks: provisioning.HooksConfig{
"preprovision": {{
Shell: ext.ShellTypeBash,
Shell: string(language.HookKindBash),
Run: "echo shared",
}},
},
Expand Down Expand Up @@ -92,6 +100,7 @@ func Test_HooksRunAction_RunsLayerHooks(t *testing.T) {

func Test_HooksRunAction_FiltersLayerHooks(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
env := environment.NewWithValues("test", nil)
envManager := &mockenv.MockEnvManager{}
envManager.On("Reload", mock.Anything, mock.Anything).Return(nil)
Expand All @@ -109,7 +118,7 @@ func Test_HooksRunAction_FiltersLayerHooks(t *testing.T) {
Path: "infra/core",
Hooks: provisioning.HooksConfig{
"preprovision": {{
Shell: ext.ShellTypeBash,
Shell: string(language.HookKindBash),
Run: "echo core",
}},
},
Expand All @@ -119,7 +128,7 @@ func Test_HooksRunAction_FiltersLayerHooks(t *testing.T) {
Path: "infra/shared",
Hooks: provisioning.HooksConfig{
"preprovision": {{
Shell: ext.ShellTypeBash,
Shell: string(language.HookKindBash),
Run: "echo shared",
}},
},
Expand Down Expand Up @@ -158,6 +167,7 @@ func Test_HooksRunAction_FiltersLayerHooks(t *testing.T) {

func Test_HooksRunAction_SetsTelemetryTypeForLayer(t *testing.T) {
mockContext := mocks.NewMockContext(context.Background())
registerHookExecutors(mockContext)
env := environment.NewWithValues("test", nil)
envManager := &mockenv.MockEnvManager{}
envManager.On("Reload", mock.Anything, mock.Anything).Return(nil)
Expand All @@ -178,7 +188,7 @@ func Test_HooksRunAction_SetsTelemetryTypeForLayer(t *testing.T) {
Path: "infra/core",
Hooks: provisioning.HooksConfig{
"preprovision": {{
Shell: ext.ShellTypeBash,
Shell: string(language.HookKindBash),
Run: "echo core",
}},
},
Expand Down Expand Up @@ -272,5 +282,6 @@ func Test_HooksRunAction_ValidatesLayerHooksRelativeToLayerPath(t *testing.T) {
err := action.validateAndWarnHooks(*mockContext.Context)
require.NoError(t, err)
require.False(t, layerHook.IsUsingDefaultShell())
require.Equal(t, ext.ScriptTypeUnknown, layerHook.Shell)
// validate() infers language from the .sh file extension
require.Equal(t, language.HookKindBash, layerHook.Kind)
}
Loading
Loading