Background & Motivation
TypeScript is the dominant choice for type-safe JavaScript development. Many azd users write their application code in TypeScript and want hooks in the same language. TypeScript requires a compilation step, making the executor more complex than JavaScript but following the same Node.js toolchain.
Parent Epic: #7435 — Multi-Language Hook Support
Phase 1 PR: #7451 — delivered the extensible framework and Python support
Depends on: #7621 (JavaScript Hook Executor) — shares Node.js patterns, package manager detection, node.Cli usage
User Story
As an azd user, I want to write hook scripts in TypeScript (.ts files) so that I can use type-safe JavaScript with the same zero-config experience as Python and JavaScript hooks.
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 ts (pkg/tools/language/executor.go)
InferKindFromPath() — maps .ts→ts
- 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: ts in azure.yaml.json
ExecutionContext with Cwd, EnvVars, BoundaryDir, etc.
Solution Approach
- Create
tsExecutor in pkg/tools/language/ts_executor.go implementing HookExecutor
- Two execution modes:
- Standalone mode (no
tsconfig.json): Use npx tsx script.ts for zero-config execution — no build step needed
- Project mode (with
tsconfig.json): npm install → npx tsc (or project build script) → node dist/script.js
- Prepare():
- Validate Node.js is installed via
node.Cli.CheckInstalled()
- Discover
tsconfig.json and package.json via DiscoverProjectFile()
- If project mode: install deps (reuse JS executor's package manager detection), then build
- If standalone: verify
tsx is available (install as needed via npx)
- Execute():
- Standalone:
npx tsx script.ts
- Project:
node on compiled output (resolve from tsconfig outDir)
- Pass environment variables, respect Cwd and Interactive
- Cleanup(): No-op
- Register
"ts" as named transient in cmd/container.go
- Update
docs/language-hooks.md with TypeScript section
Acceptance Criteria
Out of Scope
- ESM/CJS module resolution complexities beyond what tsx handles
- Custom TypeScript compiler configurations (only standard tsc)
- Deno TypeScript execution
Testing Expectations
- Unit tests for
tsExecutor (both standalone and project modes)
- Unit tests for
tsconfig.json detection and outDir resolution
- E2E test: standalone
.ts script → tsx execution → verify output
- E2E test: project
.ts with tsconfig → build → execute → verify
- E2E test: missing Node.js → error with suggestion
- E2E test:
continueOnError: true behavior
Constraints
- Must reuse
node.Cli and share package manager detection with JS executor
- Standalone mode should not require any project scaffolding (zero-config)
- Follow the Prepare/Execute/Cleanup lifecycle pattern
Related Issues
Background & Motivation
TypeScript is the dominant choice for type-safe JavaScript development. Many azd users write their application code in TypeScript and want hooks in the same language. TypeScript requires a compilation step, making the executor more complex than JavaScript but following the same Node.js toolchain.
Parent Epic: #7435 — Multi-Language Hook Support
Phase 1 PR: #7451 — delivered the extensible framework and Python support
Depends on: #7621 (JavaScript Hook Executor) — shares Node.js patterns, package manager detection,
node.CliusageUser Story
As an azd user, I want to write hook scripts in TypeScript (
.tsfiles) so that I can use type-safe JavaScript with the same zero-config experience as Python and JavaScript hooks.Existing Infrastructure (from Phase 1)
The following already exist and are shared by all new executors:
HookExecutorinterface (pkg/tools/script.go) — Prepare/Execute/Cleanup lifecycleHookKindconstantts(pkg/tools/language/executor.go)InferKindFromPath()— maps.ts→tspkg/tools/language/project_discovery.go) — walk-up forpackage.jsonhooks_runner.gonode.Clisingleton incmd/container.gokind: tsinazure.yaml.jsonExecutionContextwith Cwd, EnvVars, BoundaryDir, etc.Solution Approach
tsExecutorinpkg/tools/language/ts_executor.goimplementingHookExecutortsconfig.json): Usenpx tsx script.tsfor zero-config execution — no build step neededtsconfig.json):npm install→npx tsc(or project build script) →node dist/script.jsnode.Cli.CheckInstalled()tsconfig.jsonandpackage.jsonviaDiscoverProjectFile()tsxis available (install as needed vianpx)npx tsx script.tsnodeon compiled output (resolve from tsconfigoutDir)"ts"as named transient incmd/container.godocs/language-hooks.mdwith TypeScript sectionAcceptance Criteria
.tshook scripts auto-detected and executed.tsscripts execute vianpx tsxwithout requiring a build step ortsconfig.json.tsscripts withtsconfig.jsonare compiled then executedpackage.jsondependencies installed before execution (when present)kind: tsexplicit override works in azure.yaml"ts"in container.goOut of Scope
Testing Expectations
tsExecutor(both standalone and project modes)tsconfig.jsondetection and outDir resolution.tsscript → tsx execution → verify output.tswith tsconfig → build → execute → verifycontinueOnError: truebehaviorConstraints
node.Cliand share package manager detection with JS executorRelated Issues
node.Cliusage