Skip to content

fix(provider-generator): pick the module docs registry from targetVersions - #484

Merged
so0k merged 4 commits into
open-constructs:mainfrom
jsteinich:fix/module-docs-registry
Oct 27, 2026
Merged

so0k merged 4 commits into
open-constructs:mainfrom
jsteinich:fix/module-docs-registry

Conversation

@jsteinich

@jsteinich jsteinich commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Closes #480.

#393 made generated provider and resource docs links follow the project's declared targetVersions. Module links were missed and stayed hardcoded, so an OpenTofu-only project got Terraform Registry links and prose reading "Docs at Terraform Registry".

Changes

  • ModuleGenerator had no targetVersions at all — threaded through from ConstructsMaker.
  • moduleDocsUrl() added to the Registry abstraction, beside the existing provider docsUrl().
Terraform OpenTofu
module /modules/{ns}/{name}/{tgt}/{ver} /module/{ns}/{name}/{tgt}/**v**{ver}
submodule .../{ver}/submodules/{sub} .../v{ver}/submodule/{sub} (singular)

How the OpenTofu shapes were established

From getDocumentationUrl.ts and router.tsx in opentofu/registry-ui — the same source as the provider paths in #393:

case "module":
  return `/module/${vars.namespace}/${vars.name}/${vars.target_system}/${vars.version || "latest"}`;

Then confirmed against rendered pages, which matters here: search.opentofu.org does not 404 on a bad path. Without the v prefix it quietly serves a version list instead of the module —

  • /module/terraform-aws-modules/iam/aws/v3.12.0 → "Viewing version v3.12.0" ✅
  • /module/terraform-aws-modules/iam/aws/3.12.0 → falls back to a version index ❌
  • /module/terraform-aws-modules/iam/aws/v3.12.0/submodule/iam-account → "Submodule: iam-account" ✅

An HTTP status check would have reported the broken URL as fine. This is the same soft-404 trap noted in #480 and #444.

Verification

  • Terraform output is unchanged: the existing module-generator snapshots — 9 tests, 10 snapshots, including a //modules/ submodule source — pass untouched.
  • 7 new Registry unit tests for both registries, pinned and latest, submodule and not.
  • 6 new generator-level tests covering the wiring end to end: nothing declared → Terraform, both declared → Terraform, OpenTofu-only → OpenTofu, submodule segment, and a local ./module still rendering "Source at". These drive ModuleGenerator directly, so unlike the existing module tests they need no network or terraform binary.
  • @cdktn/commons 101 tests pass; @cdktn/provider-generator builds clean.

Note on a behaviour change

For release notes: a module source whose // path is not a direct child of modules/ (for example //examples/foo or //modules/x/y) used to render a version-less link with the // kept (…/modules/ns/name/tgt//examples/foo), which neither registry serves. It now links to the module's own versioned page. Deep links are emitted only for //modules/<name>, the one shape both registries serve as a page; checked against rendered pages, /submodules/examples/complete is a 404 on the Terraform Registry and an error page on OpenTofu. //modules/<name> sources render exactly as before.

The generic "based on a Terraform module" wording is deliberately left alone — it names the HCL construct, not a registry.

🤖 Generated with Claude Code

@so0k so0k left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test-focused pass, comments inline.

For release notes: a module source with a // path outside modules/ used to render a version-less link with the // kept (…/modules/ns/name/tgt//examples/foo). It now renders …/modules/ns/name/tgt/<version>/submodules/examples/foo.

@jsteinich

Copy link
Copy Markdown
Contributor Author

Re the release-notes point in the review summary: thanks. That note was out of date once the rule changed, and the PR description now has the corrected version: any // path that isn't a direct child of modules/ links to the module's versioned page. //modules/<name> renders exactly as before.

so0k pushed a commit to jsteinich/cdk-terrain that referenced this pull request Oct 26, 2026
`cdktn provider add random@=3.9.0` in an OpenTofu-only project reported

    Pre-built provider does not exist for the given constraints.

and fell back to local generation, even though @cdktn/provider-random exists. The
same command in a Terraform-targeting project finds it.

`getNpmPackageName` compared the full normalized source of each entry in the
published prebuilt map against the incoming constraint. The map keys providers
without a hostname, so its entries normalize under the default Terraform
registry, while the constraint normalizes against whatever the project declares.
In an OpenTofu-only project those never matched. A prebuilt package identifies a
provider rather than a registry, so the comparison is now on namespace and name.

The existing `getNpmPackageName` block is `describe.skip` with a TODO about an
unknown URL - it mocks www.cdk.tf while the code now reads provider.json from
raw.githubusercontent.com - which is part of why this went unnoticed. The three
new cases mock the real URL and are not skipped; only the OpenTofu one fails
against the previous implementation, so Terraform behaviour and the
no-such-package path are unchanged.

Found while auditing what remained after open-constructs#479, open-constructs#489, open-constructs#484 and open-constructs#490: no
integration fixture declares `targetVersions`, so the OpenTofu matrix rows cover
the CLI being OpenTofu rather than the project targeting it. Confirmed by
pointing the fixture at `targetVersions.opentofu` and running under tofu 1.12.6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
so0k pushed a commit that referenced this pull request Oct 26, 2026
…ry (#493)

Independent of #479 / #489 / #484 / #490 — branched off `main`, touches
only `getNpmPackageName`.

## Symptom

`cdktn provider add random@=3.9.0` in an **OpenTofu-only** project
(`targetVersions: { opentofu: ... }`):

```
Checking whether pre-built provider exists for the following constraints:
  provider: random
  version : =3.9.0
Pre-built provider does not exist for the given constraints.
Adding local provider registry.opentofu.org/hashicorp/random with version constraint =3.9.0 to cdktf.json
```

`@cdktn/provider-random@14.1.0` exists, and the identical command in a
Terraform-targeting project finds it. So OpenTofu-only projects silently
never get prebuilt providers — they always fall back to local
generation, which is slower and produces different output.

## Cause

`getNpmPackageName` compared full normalized sources:

```ts
ProviderConstraint.fromConfigEntry(p).source.toLowerCase() ===
  constraint.source.toLowerCase()
```

The published map keys providers *without* a hostname (`{ "random":
"hashicorp/random" }`), so its entries normalize under the default
Terraform registry, while `constraint` normalizes against whatever the
project declares. In an OpenTofu-only project that is
`registry.opentofu.org/...` versus `registry.terraform.io/...` — never
equal.

A prebuilt package identifies a **provider**, not a registry, so the
comparison is now on namespace and name.

## Why no test caught it

The existing `getNpmPackageName` block is `describe.skip`, with `// TODO
rebuild these tests when final url is known` — it mocks `www.cdk.tf`
while the code now reads `provider.json` from
`raw.githubusercontent.com`. So this function had no live unit coverage
at all.

The three new cases mock the real URL and are **not** skipped. Only the
OpenTofu one fails against the previous implementation; the Terraform
case and the no-such-package case pass either way, so behaviour there is
provably unchanged. I left the stale skipped block alone rather than
widen scope. `@cdktn/cli-core`: 18 suites, 201 passed, 6 pre-existing
skips.

## The gap this came from

Found while auditing what remained after #479, #489, #484 and #490. **No
integration fixture declares `targetVersions`**, so the OpenTofu matrix
rows exercise *the CLI being OpenTofu*, not *the project targeting
OpenTofu* — and every defect in this family only appears when
`registryForTargetVersions` returns OpenTofu. Confirmed by pointing the
fixture at `targetVersions.opentofu` and running under tofu 1.12.6; that
is also how the `provider upgrade` failure fixed in #489 was found.

Worth closing that gap properly with a fixture that declares an OpenTofu
target. Filing separately rather than bundling it here.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
jsteinich and others added 4 commits October 26, 2026 23:01
…sions

open-constructs#393 made generated provider and resource docs links follow the project's
declared `targetVersions`. Module links were missed and stayed hardcoded to the
Terraform Registry, so an OpenTofu-only project got Terraform links and prose
reading "Docs at Terraform Registry".

`ModuleGenerator` had no `targetVersions` at all, so this threads it through from
ConstructsMaker, and adds `moduleDocsUrl()` to the `Registry` abstraction
alongside the existing provider `docsUrl()`.

The OpenTofu shapes come from getDocumentationUrl.ts and router.tsx in
opentofu/registry-ui, the same source as the provider paths: `/module/` singular,
a v-prefixed version, and `submodule` singular - where Terraform uses `/modules/`
and `submodules`. Both were checked against a rendered page, because
search.opentofu.org does not 404 on a bad path: without the v prefix it quietly
serves a version list instead of the module, so an HTTP check would have passed
on a URL that shows the user the wrong page.

Terraform output is unchanged - the existing module-generator snapshots, which
cover a submodule source, pass untouched. Submodule parsing is now a split on
`//` rather than a literal replace of `//modules`, so a source pointing at a
subdirectory other than `modules/` also gets its version applied instead of
emitting a version-less link.

The generic "based on a Terraform module" wording is left alone; it names the
HCL construct rather than a registry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The new cases went into a second test file for the same module, at
src/__tests__/registry.test.ts, while commons keeps its tests beside the source
as src/*.test.ts and already had src/registry.test.ts covering docsUrl and
registryForTargetVersions. I had checked src/__tests__/ was empty and concluded
there were no registry tests, without looking where the convention actually puts
them.

Moves the moduleDocsUrl cases into that file as their own describe and drops the
duplicate registryForTargetVersions coverage, which the existing suite already
states more thoroughly. The generator-level tests stay where they are: they
cover targetVersions reaching ModuleGenerator, which is a different contract
from the URL shapes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The earlier tests construct ModuleGenerator directly and pass targetVersions
themselves, so they would keep passing if ConstructsMaker stopped forwarding
`this.options.targetVersions` - the forwarding that was the actual fix for
OpenTofu-only module links. Raised in review.

This case drives ConstructsMaker.generate() with an OpenTofu-only target and a
mocked module schema, and asserts the emitted search.opentofu.org link. With the
forwarding reverted it fails; no network or terraform binary is needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From review, which asked whether `/submodules/examples/foo` is a real page. It is
not. Checked against rendered pages on both registries:

- `//modules/vpc-endpoints` -> Terraform `/submodules/vpc-endpoints` and OpenTofu
  `/submodule/vpc-endpoints` (including `/latest/`) are real submodule pages
- `//examples/complete` -> what the previous commit emitted, Terraform
  `/submodules/examples/complete` is a 404 and OpenTofu shows an error page
- the module's own versioned page is real on both

So splitting on `//` swapped one broken link for another. A deep link is now
emitted only for a direct child of `modules/`, the only shape either registry
serves; any other `//` path - examples, nested paths, other directories - links
to the module page. OpenTofu's router also takes a single path segment for
submodules, so nested paths have no page there either.

Tests: an examples path and a nested modules path, which the previous rule
fails; the versionless OpenTofu submodule URL the reviewer asked about. Drops the
two cases flagged as redundant: both-products precedence, pinned in
registry.test.ts, and the local-module branch this PR does not change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@so0k
so0k force-pushed the fix/module-docs-registry branch from 20cee82 to 4386625 Compare October 26, 2026 16:01
@so0k

so0k commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up (non-blocking): version ranges produce broken module links.

moduleDocsUrl (packages/@cdktn/commons/src/registry.ts:51 and :80) inserts target.version into the URL as written in cdktf.json, where ranges are common.

For example, "~> 19.0" from examples/go/aws/cdktf.json yields https://search.opentofu.org/module/terraform-aws-modules/eks/aws/v~> 19.0.

The space also splits the {@link}, so the link text becomes 19.0 terraform-aws-modules/eks/aws.

Terraform links on main already break the same way, so this PR does not introduce the defect; it carries it over to the OpenTofu URL.

Suggested fix for both registries:

use the version only when it is an exact semver, and fall back to latest otherwise.

That changes Terraform output for projects that use ranges, so it needs its own release note and is better handled as a separate issue than in this PR.

Tests (test-audit, nit):

the "nothing declared" and "OpenTofu only" cases in module-docs-registry.test.ts duplicate registry.test.ts and the target-versions wiring test, so the "OpenTofu Registry" prose assertion could move into the wiring test. The //examples/complete and //modules/x/y cases are the ones that guard this fix.

@jsteinich

Copy link
Copy Markdown
Contributor Author

Thanks — agreed it's pre-existing on the Terraform side and needs a release note, so it's tracked separately as #526, with a fix ready to follow this PR: exact versions (with a leading = allowed) are kept, and anything else links to latest, which I'd confirmed is a real page on both registries. I've folded the test-audit nit into that change rather than pushing here, so your approval stands: the two duplicated cases go, the "OpenTofu Registry" prose assertion moves into the wiring test, and a new case checks that a ranged version keeps the {@link} intact.

@so0k
so0k merged commit 8200330 into open-constructs:main Oct 27, 2026
264 checks passed
so0k pushed a commit that referenced this pull request Oct 27, 2026
… changes (#490)

Fixes [#462](#462).
Independent of #479 / #489 / #484 — branched off `main` and touches only
`filterAlreadyGenerated`.

## The problem

#443 made generated docs links follow the project's declared
`targetVersions`. `filterAlreadyGenerated` decided freshness from the
provider version map and the CDKTN version alone, so this left stale
output:

1. `cdktn get` with no `targetVersions` → bindings carry
`registry.terraform.io` links
2. set `targetVersions: { opentofu: ... }`, provider versions untouched
3. `cdktn get` → provider considered already generated, skipped

`constraints.json` already stamped `targetVersions`, but as a diagnostic
that was explicitly *never read back*, so the information was there and
unused.

## The fix

Compare the registry class, derived through `registryForTargetVersions`,
and regenerate when it differs.

**The class, not the raw ranges, is the correct key — and that isn't a
judgement call.** I traced every use of `targetVersions` inside the
generator: it is consumed *only* through `registryForTargetVersions`,
for `ResourceModel.docsRegistryName` and `linkToDocs`. Nothing else.
`ResourceParser` takes it but only forwards it to `ResourceModel`; it
does not narrow the generated surface. So a version range moving within
one class provably cannot change any generated output, and forcing a
regeneration for it would be pure waste.

That also means the docblock calling the field "purely informational —
never read back" is now wrong on both counts, and its stated rationale
("doesn't affect the generated surface") was already misleading.
Corrected.

## Tests

Five cases; **the two transitions fail against the previous
implementation**, the three that should keep skipping already passed:

| case | expected |
|---|---|
| nothing changed | skip |
| → OpenTofu-only | **regenerate** |
| ← away from OpenTofu-only | **regenerate** |
| range moves within a class | skip |
| both products declared (same class as declaring nothing) | skip |

They drive `filterAlreadyGenerated` directly against a hand-written
`constraints.json`, so they need no network and no terraform binary.
`constructs-maker` and `module-generator` suites unchanged: 27 tests, 10
snapshots.

## Scope note

This also covers generated **module** links once #484 lands, since those
derive from the same class. Not a dependency — the two are independent.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
so0k pushed a commit that referenced this pull request Oct 27, 2026
Fixes [#483](#483),
#488 and #494. **Supersedes
[#479](#479, whose
commits are all included here (see below). Independent of #484, #490 and
#493.

## The bug is worse than the symptom

The visible failure was a stale version in `versions.json`. The actual
behaviour: **`cdktn provider upgrade` in an OpenTofu project downloaded
the provider from `registry.terraform.io`**, silently ignoring the
product the project targets.

`readProviderSchema` writes a throwaway `main.tf.json` and asks the CLI
for the schema:

```ts
config.terraform.required_providers![target.name] = {
  version: target.version,
  source: target.source,
};
```

A source that names a registry **pins the CLI to it**. The two commands
disagreed about whether the source carried a host:

- **`provider add`** passes no `constraintsToGenerate`, so `get` derives
constraints from `cdktf.json` — the short `hashicorp/random`. Bare →
OpenTofu uses its own registry.
- **`provider upgrade`** passed `constraintsToUpdate.map((c) => new
TerraformProviderConstraint(c))`, built from
`ProviderConstraint.fromConfigEntry(provider)` with **no registry
argument**, so normalized to `registry.terraform.io/hashicorp/random`.

Hence one key per host in the same file, old one first, winning any
lookup by provider name:

```json
{ "registry.opentofu.org/hashicorp/random": "3.1.3",
  "registry.terraform.io/hashicorp/random": "3.2.0" }
```

This also explains the `!explicitHostname ||` guard in
`provider-generator.ts`: bare sources are the designed contract, and the
upgrade path was violating it.

## Three fixes

1. **`handlers.ts`** — pass `namespace/name`, leaving resolution to the
CLI. I checked all ten `new TerraformProviderConstraint(...)` sites;
this was the only one handing on a normalized FQPN.
2. **`constructs-maker.ts`** — `updateVersionsFile` no longer carries a
previous entry forward for a provider regenerated this run. It matched
the old entry by provider part but re-inserted it under its *old* fully
qualified key. Defence in depth now that the host above is stable.
3. **`local-provider-versions.ts`** — a third hardcoded
`registry.terraform.io/` strip, found by tracing `DEFAULT_HOSTNAME`.
This one is a *reader*: under OpenTofu, `versionForProvider("random")`
returned nothing, so version detection and `provider list` came up
empty. Now strips any public registry host.

Plus the restored OpenTofu pin, putting the matrix back to three tofu
rows.

## Verification

Run against both CLIs locally, each leaving a single correctly-keyed
entry:

| CLI | `.gen/versions.json` |
|---|---|
| terraform 1.14.3 | `{ "registry.terraform.io/hashicorp/random":
"3.2.0" }` |
| opentofu 1.12.6 | `{ "registry.opentofu.org/hashicorp/random": "3.2.0"
}` |

`typescript/provider-upgrade-command` goes from `1 failed, 3 passed, 1
skipped` to `4 passed, 1 skipped` under OpenTofu, and still passes under
Terraform — that path matters because this changes the source for
*every* upgrade, not just OpenTofu ones. Of the four new
`LocalProviderVersions` tests, the two OpenTofu cases fail against the
previous implementation. `@cdktn/cli-core` 20 suites / 207 tests and
`@cdktn/provider-generator` 22 tests / 10 snapshots unchanged.

## Also folded in: deploy after a provider bump, and coverage for
OpenTofu-targeting projects

Fixes #488 and closes #494 as well. Four more commits, from auditing
what remained once the above was fixed.

**`cdktn deploy` failed for every OpenTofu project after a provider
version bump:**

```
Could not resolve provider hashicorp/random: locked provider
registry.opentofu.org/hashicorp/random 3.6.0 does not match configured
version constraint 3.6.1; must use tofu init -upgrade
```

`CdktfStack` compares the synthesized `required_providers` against
`.terraform.lock.hcl` to decide whether to run `init` and whether `init
-upgrade` is needed, using exact fully-qualified addresses — and it
normalized every source to `registry.terraform.io`. Synthesized sources
are bare in practice: generated bindings copy the source as written in
cdktf.json, and published prebuilt packages ship bare too (checked
`@cdktn/provider-aws` 25.7.2). OpenTofu locks a bare source under
`registry.opentofu.org`, so both checks missed: `init` ran on every
deploy, and a bump never triggered `init -upgrade`, which the plain
`init` then rejected. `lockAddressesFor` now offers every public
registry for a bare source, while an explicit host still has to match
exactly. Projects that fully qualify providers were never affected.

**`typescript/opentofu-target`** is the first fixture that *declares* an
OpenTofu target. The other OpenTofu matrix rows only change which CLI
runs, so nothing exercised registry resolution that follows
`targetVersions` — which is where this whole run of defects lived. It
walks get → synth → deploy → `provider list` → `provider upgrade` →
redeploy, pinned to `tofu1.12.6`. Reverting the lock fix makes its bump
step fail with the error above.

**Two verification-tooling fixes**, both found because they produced
misleading local results:

- `run-against-dist.mjs` installs the CLI from its tarball instead of
`pnpm add cdktn-cli`, which could resolve an earlier build of the same
`0.0.0` version (#488). The glob is `cdktn-cli-[0-9]*.tgz`, so release
builds with stamped versions still work and `cdktn-cli-core-*.tgz` is
excluded.
- `package:js|python|java|dotnet|go` now depend on `build`. They had no
target defaults, so they repacked whatever compiled output was lying
around — a fresh tarball timestamp over stale contents. No CI workflow
invokes them.

Matrix +1 job. `@cdktn/cli-core` passes; the fixture and the Terraform
`diff-deploy-output-destroy` fixture both pass locally against tarballs
extracted and checked for these changes.

## Now also carries #479, plus review fixes

#479 is folded in and closed. On its own it regressed `provider upgrade`
in OpenTofu-only projects: it moved `CdktfConfigManager` onto the
project's registry while the upgrade handler still normalized against
Terraform, so an installed provider looked missing. The handler fix and
the regression test both live here, so the two now land together.

Fixes from review:

- **Lock matching (revised after a second review).** An earlier fix here
made a bare source match only the *running* CLI's registry, identified
with `terraform version` before every init. That was reverted
(b668906). `init` always runs, so nothing was ever skipped, and
OpenTofu migrates a Terraform lock entry on its own. The filter broke
the realistic case: after deploying with Terraform, a provider bump
under OpenTofu got no `-upgrade` and `tofu init` failed, as reproduced
with Terraform 1.14.3 and OpenTofu 1.12.6. It also leaked `terraform
version` output into every diff/deploy/destroy log. A bare source now
matches its lock entry under either public registry; an explicit host
still matches only itself. The decisions stay in `needsLockfileUpdate` /
`needsUpgrade`, and the tests cover the Terraform→OpenTofu bump.
- **`provider upgrade` keeps explicitly named hosts.** Schema generation
had dropped every host. `ProviderConstraint.requiredProvidersSource`
keeps a public or private host the user wrote and drops only one added
by normalization. Writing the upgraded entry back to cdktf.json had the
same problem, found by sakul-learning's scenario runner (368efb3): an
update now keeps the entry's own spelling of its source and changes only
the version.
- **Matrix comment** for the provider-command OpenTofu pins now says
they exercise the CLI, not a project that targets OpenTofu.

Each new test was mutation-checked against the reviewed behaviour.
Locally, against tarballs checked for these changes: `opentofu-target`
passes 6/6 under tofu 1.12.6, `diff-deploy-output-destroy` passes 5/5
under Terraform, and cli-core passes with lint clean.

## Worth knowing about the verification

Two earlier "verification" runs of this fix were **invalid**, and both
looked like real failures:

- `nx run-many -t package` reported success while serving a two-week-old
cached `cdktn-cli` tgz.
- After rebuilding with `--skip-nx-cache`, `run-against-dist.mjs` still
installed an older CLI, because it does `pnpm add cdktn-cli` and the
version is permanently `0.0.0`.

Filed as
[#488](#488) with a
fix. Anyone reproducing this locally should confirm the *installed* CLI
contains their change — and grep the shipped `*.js.map`, since esbuild
strips comments and minifies the bundle.

## Release notes

- **Behaviour change:** in a project whose `targetVersions` declares
only OpenTofu, `provider add` / `provider upgrade` write providers to
`cdktf.json` in the short form (`hashicorp/random`) rather than with
`registry.terraform.io/`. An existing entry keeps the host it was
written with.
- **Behaviour change:** the lock-file checks before `init` match a bare
provider source against a lock entry from either public registry. A
provider bump after switching between Terraform and OpenTofu now runs
`init -upgrade`.
- **Known limitation (OpenTofu 1.12 and later, not caused by this PR):**
a provider whose source names `registry.terraform.io` explicitly fails
to deploy under OpenTofu. `tofu providers lock` adds a second, hash-less
`registry.opentofu.org` entry, and `apply` then fails with `there is no
package for registry.opentofu.org/... cached in .terraform/providers`.
Plain HCL fails the same way (`tofu init`, `tofu providers lock
-platform=linux_amd64`, `tofu apply`); OpenTofu 1.8.2 and 1.9.3 work.
Workaround: use the bare `hashicorp/x` source, or run `tofu init` again
in the stack directory.
- Follow-up: #532 (an explicit host is still dropped from the
regenerated binding and from object-form entries on upgrade).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
so0k pushed a commit that referenced this pull request Oct 27, 2026
🤖 Release PR — merge to cut a new release. Kept open and rebased
as commits land on `main`.
---


<details><summary>0.25.0</summary>

##
[0.25.0](v0.24.0...v0.25.0)
(2026-10-27)


### ⚠ BREAKING CHANGES

* **lib:** `IAssetPackaging` gains `acceptsDirectorySource` as a
required member. Custom implementations of the interface must add it.
* **lib:** ValidateBinaryVersion and ValidateTerraformVersion are
removed (they were only reachable through deep imports, not the cdktn
package root). Synth no longer executes the terraform/tofu binary for
feature validation; set validateInstalledBinary: true in cdktf.json to
verify the installed binary before commands that run it.
* **lib:** resource moves (moved blocks) are now validated at synth
against the project's declared targetVersions (default terraform
>=1.5.7, opentofu >=1.6.0) instead of the locally installed binary.
Projects that declare terraform targets below 1.5.0 now fail synth;
projects whose installed binary is older than their declared targets
should set validateInstalledBinary: true in cdktf.json.
* **lib:** import blocks (importFrom / ImportableResource) are now
validated at synth against the project's declared targetVersions
(default terraform >=1.5.7, opentofu >=1.6.0) instead of the locally
installed binary. Projects that declare terraform targets below 1.5.0
now fail synth; projects whose installed binary is older than their
declared targets should set validateInstalledBinary: true in cdktf.json.
* **lib:** terraform_data resources are now validated at synth against
the project's declared targetVersions (default terraform >=1.5.7,
opentofu >=1.6.0) instead of the locally installed binary. Projects that
declare terraform targets below 1.4.0 now fail synth; projects whose
installed binary is older than their declared targets should set
* **lib:** cloud backends (the cloud block) are now validated at synth
against the project's declared targetVersions (default terraform
>=1.5.7, opentofu >=1.6.0) instead of the locally installed binary.
Projects that declare terraform targets below 1.1.0 now fail synth;
projects whose installed binary is older than their declared targets
should set validateInstalledBinary: true in cdktf.json.
* **lib:** IAssetPackaging gains a required member
omitsDirectoryEntries; external implementations must add it. Custom
TerraformAsset assetHash values are now validated against
`/^[A-Za-z0-9_.-]+$/` and throw if they contain other characters (e.g.
"feature/my-branch").
* **lib:** add asset pipeline foundation types and hash utilities
([#371](#371))

### Features

* **cli:** add a typescript-pnpm init template and deprecate Yarn
([#422](#422))
([81da164](81da164))
* **cli:** move the TypeScript template to tsx and TypeScript 6
([#396](#396))
([b50349d](b50349d))
* **cli:** point AI coding agents at the CDKTN docs MCP server
([#518](#518))
([f257cd9](f257cd9))
* **cli:** rename `--cdktf-version` to `--cdktn-version` in `init`
([#228](#228))
([dd3818e](dd3818e))
* **cli:** replace HashiCorp checkpoint telemetry with Sentry usage
metrics and consent
([#411](#411))
([e17046a](e17046a))
* **cli:** report aggregated stack, provider and command usage metrics
([#413](#413))
([ed9b1e5](ed9b1e5))
* **cli:** report the installed binary, target versions, platform and
package manager in usage metrics
([#412](#412))
([bfe26a8](bfe26a8))
* **cli:** resolve providers from the registry the project targets
([#445](#445))
([78898ae](78898ae))
* **deps:** adopt jsii 6.0 and TypeScript 6
([#395](#395))
([26fa8fa](26fa8fa))
* **gha:** Revert "run the affected unit tests against OpenTofu
([#439](#439))"
([#446](#446))
([ae015ef](ae015ef))
* **gha:** run the affected unit tests against OpenTofu
([#439](#439))
([823682d](823682d))
* **gha:** run the affected unit tests against OpenTofu
([#447](#447))
([c0c4c1c](c0c4c1c))
* **lib:** add asset bundling extension interface
([#442](#442))
([7839625](7839625))
* **lib:** add asset pipeline foundation types and hash utilities
([#371](#371))
([a008d12](a008d12))
* **lib:** add asset staging core with no bundling
([#433](#433))
([44d8bd1](44d8bd1))
* **lib:** add support for single-file bundler outputs
([#464](#464))
([3592db0](3592db0))
* **lib:** pass ignore strategy through terraform asset
([#472](#472))
([705e5b9](705e5b9))
* **tests:** add per-test runtime pinning to the integration matrix
([#405](#405))
([17c2278](17c2278))


### Bug Fixes

* **cli:** catch failures of the background environment collection
([#531](#531))
([c5feed8](c5feed8))
* **cli:** correct the constructs range comment in the TypeScript
template hook
([#408](#408))
([9fc0e43](9fc0e43))
* **cli:** don't crash after a successful deploy when an output is
missing
([#375](#375))
([0bbd64a](0bbd64a))
* **cli:** fail cleanly when the --outputs-file write fails
([#376](#376))
([8f9d03a](8f9d03a))
* **cli:** make `cdktn get` remove stale files for resources removed
from a provider
([#421](#421))
([d6e28dc](d6e28dc))
* **cli:** match prebuilt providers regardless of the project's registry
([#493](#493))
([ea82124](ea82124))
* **cli:** probe with `version` instead of `-v`
([#471](#471))
([b87ee22](b87ee22))
* **cli:** require patched Node 22.23.4 and bump undici for security
fixes
([#516](#516))
([76436f5](76436f5))
* **cli:** resolve bundled handlers next to the entrypoint
([#414](#414))
([49600c1](49600c1))
* **cli:** resolve upgraded providers from the CLI's own registry
([#489](#489))
([10672ee](10672ee))
* **cli:** stop the top-level error handler racing yargs
([#410](#410))
([5d0c2ef](5d0c2ef))
* **deps:** move jsii-rosetta to 6.0.16 for stream-json 3
([#463](#463))
([f1ec16f](f1ec16f))
* **gha:** authenticate to public.ecr.aws in the docker image build
([#519](#519))
([c65bf2f](c65bf2f))
* **gha:** don't fail the pnpm upgrade jobs on ignored dep builds
([#399](#399))
([5b66342](5b66342))
* **gha:** move the jsii package cache out of the container HOME
([#514](#514))
([df5b490](df5b490))
* **gha:** pass -R to gh pr edit in release_github relabel step
([#372](#372))
([358edc7](358edc7))
* **gha:** push only the prerelease tag from prepare-next
([#476](#476))
([848f2a6](848f2a6))
* **lib:** avoid terraform init plugin-cache race in test matchers
([#467](#467))
([26847f9](26847f9))
* **lib:** retry the staging install without --prefer-offline outside CI
([#407](#407))
([7bb26e7](7bb26e7))
* **provider-generator:** pass the default provider configuration a
module requires on get
([#466](#466))
([a5225ab](a5225ab))
* **provider-generator:** pick the docs registry from targetVersions
([#443](#443))
([00f2884](00f2884))
* **provider-generator:** pick the module docs registry from
targetVersions
([#484](#484))
([8200330](8200330))
* **provider-generator:** regenerate bindings when the target registry
changes
([#490](#490))
([813e022](813e022))
* **provider-generator:** satisfy module provider configuration aliases
on get
([#383](#383))
([54a8217](54a8217))
* **provider-generator:** stop a provider_functions resource clobbering
the functions submodule
([#403](#403))
([cf19de9](cf19de9))
* **tests:** await the pip install in the python provider command tests
([#478](#478))
([1c83a36](1c83a36))
* **tests:** bound and retry the dist publish to the local registry
([#425](#425))
([cc9f9e2](cc9f9e2))
* **tests:** build the edge provider bindings in their own cached Nx
target
([#482](#482))
([f480cc9](f480cc9))
* **tests:** drop unused ts-jest transform from shared jest preset
([#382](#382))
([03f6e16](03f6e16))
* **tests:** mark the terraform-cloud target Terraform-only in the
matrix
([#430](#430))
([b0deefa](b0deefa))
* **tests:** run example builds with the Nx cache skipped
([#477](#477))
([3e25e13](3e25e13))
* **tools:** declare the cdktn-cli bundle as a cached Nx output with its
real inputs
([#485](#485))
([10dd118](10dd118))
* **tools:** make the workspace build, package, lint and test on Windows
([#364](#364))
([8dcddbe](8dcddbe))


### Code Refactoring

* **lib:** Remove the ValidateBinaryVersion synth-time binary probe
([#457](#457))
([e60142e](e60142e))
* **lib:** Use declared targetVersions for synth-time validation of
import blocks
([#454](#454))
([c0097f4](c0097f4))
* **lib:** Use declared targetVersions for synth-time validation of
resource moves
([#453](#453))
([43edc1e](43edc1e))
* **lib:** Use declared targetVersions for synth-time validation of
terraform_data
([#455](#455))
([a1c125b](a1c125b))
* **lib:** Use declared targetVersions for synth-time validation of the
cloud backend
([#456](#456))
([4a83507](4a83507))


### Miscellaneous Chores

* add OpenTofu and current Terraform to the jsii-terraform image
([#394](#394))
([2f29d5a](2f29d5a))
* declare complete Nx build outputs so cache hits restore them
([#474](#474))
([794cf77](794cf77))
* **deps:** move the remaining packages to TypeScript 6
([#406](#406))
([c55d360](c55d360))
* **deps:** move the root TypeScript to 6
([#417](#417))
([3cae1e2](3cae1e2))
* **deps:** Update xstate to v5.32.4
([#333](#333))
([5ebf74f](5ebf74f))
* **deps:** upgrade @sentry/node to 10.x
([#409](#409))
([2714fff](2714fff))
* **deps:** use caret ranges for unbundled runtime dependencies
([#503](#503))
([e722357](e722357))
* **docs:** add the test-audit skill for authoring and auditing tests
([#475](#475))
([909c612](909c612))
* **examples:** move the TypeScript examples to tsx and TypeScript 6
([#419](#419))
([857e182](857e182))
* Flexible python version
([#420](#420))
([955f62a](955f62a))
* **gha:** adopt the new jsii-terraform image and test Terraform 1.5.7 +
1.16.1
([#398](#398))
([3ad760b](3ad760b))
* **gha:** gate dependency upgrades on release age
([#418](#418))
([b611a87](b611a87))
* **gha:** retire Docker Hub publishing of jsii-terraform
([#388](#388))
([e53cddf](e53cddf))
* **gha:** select the CLI by binary name instead of concatenating a
version
([#438](#438))
([914da56](914da56))
* **gha:** skip the remote Nx cache in the release workflow
([#487](#487))
([bce91da](bce91da))
* **lib:** stop publishing build state in the cdktn package
([#397](#397))
([51e4a95](51e4a95))
* npm-check-updates && pnpm update
([#326](#326))
([7095f6b](7095f6b))
* **readme:** point to the cdktn-io/action GitHub Action
([#504](#504))
([e387852](e387852)),
closes [#58](#58)
* **tests:** add timeout handling to spawned commands
([#432](#432))
([ddb4a8a](ddb4a8a))
* **tests:** run the function-binding scripts with tsx
([#431](#431))
([fec67bf](fec67bf))


### Tests

* **lib:** ensure TerraformAsset stage within stack own directory
([#434](#434))
([9a80366](9a80366))
* **lib:** move matcher Docker fixture to a provider version OpenTofu
accepts
([#429](#429))
([7b0da12](7b0da12))
* **tests:** move the python providers fixture to Docker provider 3.9.0
([#440](#440))
([a678473](a678473))
* **tests:** move the remaining kreuzwerker/docker pins off unsigned
versions
([#459](#459))
([d855cc1](d855cc1))
* **tests:** move the testing-matchers fixtures to Docker provider 3.x
([#436](#436))
([8e2c60e](8e2c60e))
* **tests:** re-enable the Python edge integration test
([#404](#404))
([0a80626](0a80626))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: open-constructs-cdktn[bot] <291052431+open-constructs-cdktn[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

provider-generator: module docs links always point at the Terraform Registry

2 participants