Imported from hckhanh/pulumi-any-terraform (
AGENTS.md). Install upstream withnpx skills add hckhanh/pulumi-any-terraform. Copyright stays with the author.
AGENTS.md
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
Project Overview
pulumi-any-terraform is a pnpm + Nx monorepo of dynamically bridged Pulumi providers. Each package wraps a Terraform provider as a TypeScript Pulumi SDK using Pulumi's parameterized provider mechanism (@pulumi/pulumi + terraform-provider).
Critical: ~95% of TypeScript inside packages/ is auto-generated by pulumi-language-nodejs. Files starting with // *** WARNING: this file was generated by pulumi-language-nodejs. *** must never be hand-edited -- they are regenerated by the weekly update workflow and any manual changes will be lost.
Hand-written code lives only in:
tools/-- Nx plugins (TypeScript, ES2022).github/scripts/-- CI automation (TypeScript, run withnodevia--experimental-strip-types)packages/*/scripts/postinstall.js-- per-package build template (rarely changes)docs/-- Fumadocs (Next.js) site- Top-level config (
nx.json,package.json,.syncpackrc.json, etc.)
Repository Layout
.
├── packages/ # Provider SDK packages (mostly generated)
│ ├── better-uptime/ buildkite/ bunnynet/ infisical/ local/
│ ├── logtail/ namecheap/ portainer/ posthog/ teamcity/ time/
│ └── <provider>/
│ ├── *.ts # GENERATED -- do not edit
│ ├── scripts/postinstall.js # Build script (runs tsc, copies package.json into bin/)
│ ├── package.json # Includes `pulumi.parameterization` block
│ ├── project.json # Nx project marker
│ └── tsconfig.json # Triggers BuildPlugin
├── tools/ # Hand-written Nx plugins
│ ├── build.ts # Matches **/*/tsconfig.json -- adds `build` target
│ ├── biome.ts # Matches **/biome.json -- adds `biome:check` / `biome:check:fix`
│ ├── linter.ts # Matches **/project.json -- adds aggregate `check`/`fix`
│ ├── oxfmt.ts # Matches **/project.json -- adds `oxfmt:check`/`oxfmt:write`
│ └── utils/plugin.ts # Abstract Plugin base class for all of the above
├── docs/ # Fumadocs documentation site (Next.js, has its own biome.json)
├── .github/
│ ├── actions/
│ │ └── setup-safe-chain/ # Shared Aikido Safe Chain installer (checksummed)
│ ├── scripts/
│ │ ├── check-updates.ts # Weekly upstream Terraform provider sync
│ │ └── __tests__/ # node:test suite for check-updates.ts
│ └── workflows/ # test.yml, autofix.yml, publish.yml, update.yml
├── .changeset/ # Changesets state and configuration
├── nx.json # Nx workspace + custom plugin registration
├── pnpm-workspace.yaml # Workspace, pnpm overrides, allowBuilds, minimumReleaseAge
├── .syncpackrc.json # package.json field ordering + version pinning rules
├── mise.toml # Toolchain pin (Node 24.15.0, pnpm 10.33.2)
├── .prototools # mise settings (auto-install, telemetry off)
├── tsconfig.json # Hand-written TS config (covers tools/ + .github/scripts)
└── project.json # Root Nx project (root-level targets like syncpack, oxfmt:root)
Common Commands
# Install
pnpm install
# Build everything
pnpm nx run-many -t build
# Run all checks (build + typecheck + test:scripts + syncpack + oxfmt + actionlint + zizmor)
pnpm nx run-many -t check
# Faster: only affected projects (used in CI)
pnpm nx affected -t check
# Auto-fix formatting and dependency-version issues
pnpm nx run-many -t fix
# Root-only formatters / sync
pnpm nx run root:oxfmt:write
pnpm nx run root:syncpack:format
pnpm nx run root:syncpack:fix
# Typecheck hand-written code (tools/ + .github/scripts/)
pnpm nx run root:typecheck
# Run check-updates.ts unit tests
pnpm nx run root:test:scripts
# GitHub Actions lint / security audit
pnpm nx run root:actionlint
pnpm nx run root:zizmor
# Audit
pnpm nx run root:audit
# Release (build + changeset publish) -- runs in CI
pnpm release
Architecture
Generated vs hand-written code
| Path | Type | Editable? |
|---|---|---|
packages/*/*.ts |
Auto-generated Pulumi SDK | No |
packages/*/package.json |
Mostly managed; pulumi.parameterization is critical |
Carefully |
packages/*/scripts/postinstall.js |
Build template (tsc + copy json) |
Rare; keep all in sync |
tools/*.ts |
Hand-written Nx plugins | Yes |
.github/scripts/check-updates.ts |
Weekly upstream sync automation | Yes |
docs/ |
Fumadocs site | Yes |
Nx plugin system
All build orchestration runs through custom Nx plugins in tools/ that extend the abstract Plugin base class (tools/utils/plugin.ts). Each plugin matches a file glob and contributes targets:
| Plugin | Glob | Targets contributed |
|---|---|---|
tools/build.ts |
**/*/tsconfig.json |
build (runs node ./scripts/postinstall.js) |
tools/biome.ts |
**/biome.json |
biome:check, biome:check:fix |
tools/linter.ts |
**/project.json |
aggregate check, fix |
tools/oxfmt.ts |
**/project.json |
oxfmt:check, oxfmt:write |
@nx/js/typescript is also registered in nx.json for typed project graph awareness, with typecheck disabled (we run a single workspace-level typecheck instead).
When adding a new plugin: extend Plugin, set the glob via super(...), implement processFile(file) returning a CreateNodesResult, then add the plugin path to the plugins array in nx.json.
Package build pipeline
tools/build.tsfinds every*/tsconfig.json(so each generated package anddocs/becomes an Nx project).- Its
buildtarget runsnode ./scripts/postinstall.jsfrom eachprojectRoot. postinstall.jsinvokestsc, then copies the package'spackage.jsonintobin/(sogetVersion()can resolve it at runtime).- Output
bin/is the published artifact (declared inpackage.json#files).
Provider update flow (weekly)
.github/scripts/check-updates.ts runs every Monday 00:00 UTC via update.yml:
- Fetches latest versions from the Terraform / OpenTofu registry and GitHub releases for each provider in
packages/. - For any package that's outdated, runs
pulumi package add terraform-provider <registry-url> <version>in a temp dir to regenerate the SDK. - Copies regenerated files back over
packages/<name>/. - Normalizes the upstream changelog (demotes headings, shortens SHAs, oxfmt-formats markdown) and writes a Changesets entry.
- Commits directly to
mainaschore(release): update packages to match upstream terraform providers. - The
publish.ymlworkflow then triggers off theUpdateworkflow_runand runschangesets/actionto release the new package versions to npm.
pulumi.parameterization in each package's package.json is what tells the dynamic provider which Terraform provider/version to bridge -- it must stay in sync with the generated TypeScript.
Code Conventions
Formatting (Biome + Oxfmt, enforced in CI)
| Rule | Value |
|---|---|
| Indentation | 2 spaces |
| Semicolons | asNeeded (Biome) / false (Oxfmt) -- omit |
| Quotes | Single (') |
| JSX quotes | Single |
| Line endings | LF |
| Encoding | UTF-8, trailing newline |
- Biome formats and lints JS/TS/JSON/CSS (only where a
biome.jsonexists -- currentlydocs/). - Oxfmt formats YAML/Markdown/HTML/CSS and hand-written JS/TS workspace-wide.
.oxfmtrc.jsonignorePatternsexclude generatedpackages/sources,pnpm-lock.yaml, allpackage.json,docs/, and skills. Rootoxfmt:*targets also skippackages/anddocs/. PackageCHANGELOG.mdfiles are formatted by oxfmt (Changesets v3 runsoxfmt --writeon them during version).
TypeScript (hand-written tools/ + .github/scripts/)
- Target ES2022,
module: nodenext, strict - Use
import typefor type-only imports - Use the
node:protocol for builtins (import { dirname } from 'node:path') - Extend the
Pluginabstract class for new Nx plugins noEmit: true-- tsc is for typecheck only
Dependencies
- All dependency versions are pinned exactly (no ranges), enforced by Syncpack via the
semverGroupsrule in.syncpackrc.json. Peer deps are exempt.pnpmOverridesis read frompnpm-workspace.yaml(Syncpack 15) and is also exempt so security-patch ranges stay valid. package.jsonfield order is also enforced by Syncpack (sortFirst,sortAz).- pnpm
overridesinpnpm-workspace.yamlapply security patches to transitive deps. allowBuildswhitelists which packages may run install scripts:@swc/core,esbuild,nx,protobufjs,sharp.minimumReleaseAge: 2880(48h) matches Aikido Safe Chain's default window -- new releases must be 48h old before pnpm will install them.- Renovate manages dependency-update PRs (config in
renovate.json); theautofix.ciworkflow skips renovate-authored commits.
Commits
Conventional commits: type(scope): description. Common automated patterns:
chore(release): release packages(Changesets release PR)chore(release): update packages to match upstream terraform providers(weekly upstream sync)chore(deps): update dependency X to vY(Renovate)
CI workflows
| Workflow | Trigger | Purpose |
|---|---|---|
test.yml |
PRs, push to main |
pnpm nx affected -t check (lint + typecheck + build) |
autofix.yml |
PRs, push to main |
pnpm nx affected -t fix then autofix-ci/action |
publish.yml |
push to main, after Update run |
changesets/action -> publish to npm (access: public) |
update.yml |
weekly Mon 00:00 UTC | Regenerate SDKs from upstream Terraform providers |
Workflow conventions:
- All
uses:actions are pinned by full SHA (not tags). - Top-level
permissions: contents: read; jobs escalate as needed. - Node, pnpm, the store cache, and
pnpm installcome from SHA-pinnedpnpm/setup(v11+; notpnpm/action-setup). Renovate regex managers trackversion/runtime: node@and group those bumps withmise.toml. jdx/mise-actionis only used where extra CLIs are needed (test.ymlinstallsactionlintandzizmorviainstall_args+MISE_ENABLE_TOOLS). Do not install the fullmise.tomltoolset in CI.- Aikido Safe Chain is installed after
pnpm/setupin every workflow via the local composite action.github/actions/setup-safe-chain(version-pinned installer, SHA-256 verified — nevercurl | sh) so laterpnpmcommands use the wrapped binary. Renovate updatesSAFE_CHAIN_RELEASEandSAFE_CHAIN_SHA256together via thegithub-release-attachmentsdatasource. Do not setSAFE_CHAIN_VERSION(the installer treats that env var as deprecated). NX_DAEMON: 'false'is set globally in CI.test.ymlandautofix.ymlboth skip when the head commit ischore(release)to avoid loops.
When working in this repo
- Never edit a file that contains the
*** WARNING: this file was generated ***header. Update the upstream Terraform provider or the generation tooling instead. - Before claiming a build works, run
pnpm nx affected -t check-- it covers typecheck, build, oxfmt, syncpack, actionlint, and zizmor. - New user-facing changes need a changeset (
pnpm changeset) staged in.changeset/. - When adding root-level Nx targets, edit
project.json(root). When adding per-project targets, prefer adding them through atools/*.tsplugin so they apply uniformly. - If you change
pnpm-workspace.yaml,.syncpackrc.json, or anypackage.json, re-runpnpm installandpnpm nx run root:syncpack:formatbefore committing. - Don't push directly to
main; develop on a feature branch and let Changesets /autofix.cihandle merge-time bookkeeping.
Code Search with ast-grep
Prefer ast-grep (sg) over plain text search (grep/rg) for any structural code query — finding function calls, class definitions, imports, type annotations, JSX components, control-flow patterns, or refactor targets. Text search returns false positives (matches in strings/comments) and misses formatting variations; ast-grep matches the AST.
When to invoke the ast-grep skill: any time the task involves writing a non-trivial pattern (metavariables, constraints, multi-language rules, YAML rule files, codemods). Invoke via the Skill tool — don't reconstruct ast-grep syntax from memory. The skill knows pattern syntax, language IDs, rule structure, and sg scan/sg run flags.
When to use sg directly without the skill: simple one-off CLI patterns where you already know the exact syntax (e.g. sg -p 'console.log($A)' -l ts). For anything more complex, load the skill first.
When grep/rg is still right: searching plain text (comments, docs, log output, config strings, file names), or when you only need a count/location and don't care about AST structure.
Repo-specific note: the packages/ SDKs are auto-generated — searching them with sg is fine for understanding patterns, but never use sg's rewrite mode (--rewrite / sg run -U) against packages/** since changes will be overwritten by the next upstream sync.