Skip to content

TypeScript Hook Executor #7622

Description

@wbreza

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 installnpx 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

  • .ts hook scripts auto-detected and executed
  • Standalone .ts scripts execute via npx tsx without requiring a build step or tsconfig.json
  • Project .ts scripts with tsconfig.json are compiled then executed
  • package.json dependencies installed before execution (when present)
  • Missing Node.js → clear error with install URL
  • kind: ts explicit override works in azure.yaml
  • Environment variables passed through to script process
  • Existing shell, Python, and JS hooks unaffected
  • IoC registration as named transient "ts" in container.go

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

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