Skip to content

JavaScript Hook Executor #7621

Description

@wbreza

Background & Motivation

Developers writing azd hooks in JavaScript need the same zero-config experience delivered for Python in Phase 1. JavaScript is one of the most popular languages in the Azure ecosystem, and many azd users already have Node.js projects.

Parent Epic: #7435 — Multi-Language Hook Support
Phase 1 PR: #7451 — delivered the extensible framework and Python support

User Story

As an azd user, I want to write hook scripts in JavaScript (.js files) so that I can use my preferred language for pre/post-provisioning and deployment automation.

Existing Infrastructure (from Phase 1)

The following already exist and are shared by all new executors:

  • HookExecutor interface (pkg/tools/script.go) — Prepare/Execute/Cleanup lifecycle
  • HookKind constant js (pkg/tools/language/executor.go)
  • InferKindFromPath() — maps .js→js
  • Project discovery (pkg/tools/language/project_discovery.go) — walk-up for package.json
  • IoC-based executor resolution in hooks_runner.go
  • node.Cli singleton in cmd/container.go
  • Schema support for kind: js in azure.yaml.json
  • ExecutionContext with Cwd, EnvVars, BoundaryDir, etc.

Solution Approach

  • Create jsExecutor in pkg/tools/language/js_executor.go implementing HookExecutor
  • Prepare():
    • Validate Node.js is installed via existing node.Cli.CheckInstalled() — hard error with ErrorWithSuggestion if missing (include install URL)
    • Use DiscoverProjectFile() with package.json to find project root
    • If package.json found: auto-detect package manager from lock files (npm/pnpm/yarn) using existing node.Cli detection, then run install
    • If standalone (no package.json): skip dependency installation
  • Execute():
    • With project: run node script.js with project's node_modules available
    • Standalone: run node script.js directly
    • Pass environment variables via ExecutionContext.EnvVars
    • Respect ExecutionContext.Cwd for working directory
    • Respect ExecutionContext.Interactive for stdin/stdout
  • Cleanup(): No-op (no temp files created)
  • Register "js" as named transient in cmd/container.go alongside existing "sh", "pwsh", "python"
  • Update docs/language-hooks.md with JavaScript section (configuration examples, package manager detection, package.json handling)

Acceptance Criteria

  • .js hook scripts auto-detected and executed via Node.js
  • package.json discovered via walk-up, dependencies installed before execution
  • Package manager auto-detected from lock files (package-lock.json → npm, pnpm-lock.yaml → pnpm, yarn.lock → yarn)
  • Standalone .js scripts (no package.json) execute without dependency installation
  • Missing Node.js → clear error with install URL via ErrorWithSuggestion
  • Environment variables passed through to script process
  • kind: js explicit override works in azure.yaml
  • Existing shell and Python hooks unaffected (no regression)
  • IoC registration as named transient "js" in container.go

Out of Scope

  • TypeScript compilation/execution (separate issue)
  • Deno or Bun runtime support
  • Global npm package installation

Testing Expectations

  • Unit tests for jsExecutor (Prepare, Execute, Cleanup) with mocked node.Cli and CommandRunner
  • Unit tests for package manager detection from lock files
  • E2E test: JS hook with package.json → npm install → execute → verify output
  • E2E test: standalone JS script → execute without install
  • E2E test: missing Node.js → error with suggestion
  • E2E test: continueOnError: true behavior
  • E2E test: service-level JS hooks

Constraints

  • Must reuse existing node.Cli singleton — do not create a separate Node wrapper
  • Follow the Python executor pattern for consistency (Prepare/Execute/Cleanup lifecycle)
  • Inline JavaScript hooks are rejected (file-based only, consistent with Python)

Related Issues

Metadata

Metadata

Assignees

Labels

Fields

No fields configured for Feature.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions