Skip to content

fix(cli): resolve upgraded providers from the CLI's own registry - #489

Merged
so0k merged 15 commits into
open-constructs:mainfrom
jsteinich:fix/opentofu-upgrade-registry
Oct 27, 2026
Merged

so0k merged 15 commits into
open-constructs:mainfrom
jsteinich:fix/opentofu-upgrade-registry

Conversation

@jsteinich

@jsteinich jsteinich commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #483, #488 and #494. Supersedes #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:

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:

{ "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 (tests: run-against-dist can install a stale CLI locally, so integration runs silently test old code #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 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: provider upgrade: keep an explicit provider host in the generated binding and in object-form entries #532 (an explicit host is still dropped from the regenerated binding and from object-form entries on upgrade).

🤖 Generated with Claude Code

@jsteinich
jsteinich marked this pull request as ready for review October 9, 2026 02:09
@jsteinich
jsteinich requested a review from a team as a code owner October 9, 2026 02:09
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>
@so0k
so0k force-pushed the fix/opentofu-upgrade-registry branch from 94db4c7 to 13e03aa Compare October 26, 2026 14:21
@so0k

so0k commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

terraform version output now shows up in every diff / deploy / destroy

Finding. After a79880b, every Terraform user sees the CLI's version text in their stack log on each diff, deploy and destroy, once per stack. That includes Your version of Terraform is out of date! and, once providers are installed, the + provider … lines.

Cause. CdktfStack.initalizeTerraform now calls runningCliRegistry() unconditionally, and that calls TerraformCli.version().

public async version(): Promise<string> {
try {
return await exec(
terraformBinaryName,
["version"],
{
cwd: this.workdir,
env: process.env,
signal: this.abortSignal,
noColor: true,
},
this.onStdout("version"),
this.onStderr("version"),
);
} catch {

version() passes this.onStdout("version") to exec, which is the stack's log handler, so the output reaches onLog and the UI.

On main this only ran behind validateInstalledBinary: true, because validateInstalledBinaryIfConfigured returns early without it.

So for most projects this is a new process spawn per stack init as well as new output.

The other version probes, commons terraformCli() and provider-schema's getFetchingCliVersion(), are cached per process and stay out of the log.

Does the lock check need to know the running CLI?

a79880b justified identifying the running CLI with two switching cases. I ran both directions with plain HCL using the same init invocations cdktn would issue:

  • Terraform 1.7.5 and OpenTofu 1.12.6,
  • a shared plugin cache,
  • hashicorp/random,
  • one stack directory deployed by one CLI and then run by the other.
Scenario Lock before cdktn's init with 72da894 (any public registry) cdktn's init with a79880b (running CLI only)
Terraform → OpenTofu, no bump registry.terraform.io 3.6.0 tofu init ✅ OpenTofu rewrites the lock entry to registry.opentofu.org itself ("Dependency lock file entries automatically updated… version selections were preserved"), apply is a no-op tofu init + providers lock ✅
Terraform → OpenTofu, bump to 3.6.1 registry.terraform.io 3.6.0 tofu init -upgrade ✅ plain tofu init ❌ Could not resolve provider hashicorp/random: locked provider … must use tofu init -upgrade
OpenTofu → Terraform, no bump registry.opentofu.org 3.6.0 terraform init ✅ keeps both entries (OpenTofu state references its own provider), apply is a no-op not run
OpenTofu → Terraform, bump registry.opentofu.org 3.6.0 terraform init -upgrade ✅ not run
  • Case 1 in the commit message ("counted as locked, init was skipped, and OpenTofu then failed on the lock file"): init always runs. The lock checks only add -upgrade and the follow-up providers lock -platform=linux_amd64. OpenTofu migrates the Terraform entry on its own, so nothing fails.
  • The bump after a switch fails because of the running-CLI filter. a79880b ignores the other registry's entry, so no -upgrade is added and OpenTofu rejects the plain init. 72da894 handles it.
  • Case 2 (a stale entry for the inactive registry forcing an unneeded -upgrade) is real, but only after a switch, and it costs one extra -upgrade.

Switching binaries over the same .terraform and lock file is awkward in plain HCL too, so I'd rather not add per-init CLI detection to work around it.

Suggested fix

One commit on this branch: revert the running-CLI part of a79880b and keep its tests.

  • Remove runningCliRegistry() and the cliRegistry parameter. A bare source matches its lock entry under any public registry, as in 72da894. A source that names a host still matches exactly.
  • Keep needsLockfileUpdate / needsUpgrade as the exported functions production calls, with their tests minus the cliRegistry cases, including the lock that holds both registries.
  • Make lockAddressesFor module-private and drop its describe block, since the two decision functions already cover it.

This removes the log output and the extra spawn. It also removes the forced providers lock on every init when the CLI can't be identified (e.g. a wrapper set as TERRAFORM_BINARY_NAME).

Follow-up, not this PR: warning when the declared target and the running binary disagree belongs in the opt-in validateInstalledBinary check, ideally once per command from the cached commons terraformCli() probe rather than per stack.

Happy to push that commit here if that works for you.

@jsteinich

Copy link
Copy Markdown
Contributor Author

Thanks for running the switch cases. You're right on every point, and I reproduced the decisive one with Terraform 1.14.3 and OpenTofu 1.12.6: after a Terraform deploy, bumping 3.6.0 → 3.6.1 under OpenTofu gets no -upgrade with the running-CLI filter, and tofu init fails with "must use tofu init -upgrade". Matching either registry upgrades cleanly. My first justification was also simply wrong: init always runs, so nothing was ever skipped.

I've pushed the change you outlined as b668906. It reverts the running-CLI detection, so there's no extra version spawn and nothing extra in the stack log. needsLockfileUpdate / needsUpgrade stay as the functions production calls, and their tests now cover the Terraform→OpenTofu bump; filtering bare sources to one registry fails three of them. lockAddressesFor is module-private. Agreed that a declared-target vs running-binary warning belongs in the opt-in validateInstalledBinary check, using the cached commons probe; happy to take that as a follow-up.

@sakul-learning

sakul-learning commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Empirical repro (cdktn-cli@next 0.25.0-pre.60): provider upgrade random@=3.6.1 in an OpenTofu-targeted project leaves stale OpenTofu 3.6.0 + new Terraform 3.6.1 entries in .gen/versions.json.
b668906d4 keeps one OpenTofu 3.6.1 entry, reports the provider version, and redeploys; Terraform→OpenTofu + bump also fails on @next and passes on the branch.

Two follow-ups:

  • shorthand upgrade drops an explicitly configured OpenTofu hostname;
  • OpenTofu deployment of an explicitly Terraform-hosted provider fails after lock refresh.

Repro setup and scenario runner: https://github.com/sakul-learning/cdktn-provider-registry-scenarios

jsteinich and others added 15 commits October 27, 2026 12:02
…ness

Three assumptions in the integration layer only hold under Terraform, so the
fixtures could not run against OpenTofu.

versions.json is keyed by fully qualified provider name, whose host is whichever
registry the fetching CLI used. Five assertions indexed those keys with a
literal `registry.terraform.io/...`, which reads `undefined` under OpenTofu.
Adds `providerVersion()` to the test helper, matching on the provider part the
way the provider-generator unit test already does, and uses it at every site.
Two of the five are in `onWindows` branches that no job runs today, since
`windows_integration` is `if: false`; they are corrected so re-enabling that job
does not reintroduce the problem. The helper still returns `undefined` for an
absent provider, so the assertions stay real guards.

`TestDriver.validate()` shelled out to a bare `terraform`. The CI image points
that symlink at DEFAULT_TERRAFORM_VERSION, so the helper validated with 1.16.1
even on the 1.5.7 matrix rows, and would have run Terraform on an OpenTofu row.
It now honours TERRAFORM_BINARY_NAME, which the workflow already exports.

Pins the two provider-command targets to OpenTofu as well. They drive provider
resolution through the CLI's own registry, so they are what would catch a
regression in the registry selection added by open-constructs#445. Their Terraform entries
restate the `tested` default so the pin does not silently drop 1.5.7; the matrix
grows by two jobs.

Also normalises path separators in the matrix builder. `jest --listTests` emits
Windows separators, so no target matched the `test/` prefix and every configured
pin tripped the "not a discovered test target" guard, making the builder
impossible to run outside CI.

Verified every provider, module and exact version the fixtures pin is present on
registry.opentofu.org, checking a known-absent provider and a nonsense path
first to confirm that endpoint 404s rather than soft-404ing like search.opentofu.org.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…istry

`CdktfConfigManager` stripped the literal `registry.terraform.io/` prefix and
compared existing entries via `ProviderConstraint.fromConfigEntry()` with no
registry, which defaults to Terraform. Both only hold for a Terraform project,
so open-constructs#445 left this file behind when it made provider resolution registry-aware.

In an OpenTofu project `cdktn provider upgrade random@=3.2.0` produced:

    ["hashicorp/random@=3.1.3", "registry.opentofu.org/hashicorp/random@=3.2.0"]

The prefix did not match so the source stayed qualified, and the comparison
normalised the existing entry under the wrong registry, so it was not recognised
as the same provider and was left in place next to the new one. The project was
left with two conflicting constraints for one provider.

The manager now derives its registry from the `targetVersions` of the very file
it manages, which is the same source of truth open-constructs#445 used, and needs no plumbing
from callers. The host is dropped only when it matches that registry, so a
source explicitly qualified with another registry stays qualified - OpenTofu
users have been told to fully qualify providers and that must keep working.

Terraform behaviour is unchanged: of the five new test cases, the four covering
OpenTofu fail against the previous implementation and the Terraform one passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…fect

Pinning typescript/provider-upgrade-command to OpenTofu exposed a real defect,
not a test problem: `cdktn provider upgrade` leaves `.gen/versions.json` with two
entries for one provider, keyed by different registry hosts, and the stale one is
read back. Filed as open-constructs#483 with the reproduction. The target goes back to the
`tested` Terraform cross-product until that is fixed; provider-add-command and
provider-features keep their OpenTofu pins and pass.

Also makes `providerVersion()` throw when a versions.json holds more than one
entry for the same provider. It previously returned the first match, which was
only accidentally the stale one - had insertion order gone the other way the
duplicate would have passed silently. A loud failure naming both entries is what
this helper owes its callers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fixes open-constructs#483. `cdktn provider upgrade` in an OpenTofu project fetched the provider
from registry.terraform.io, and left `.gen/versions.json` holding two entries for
one provider with the stale version winning any lookup by name.

`readProviderSchema` writes a throwaway `main.tf.json` whose `required_providers`
source comes straight from the target, and a source that names a registry pins
the CLI to it. The upgrade handler built `constraintsToGenerate` from
`ProviderConstraint.fromConfigEntry(provider)` with no registry, which normalizes
to `registry.terraform.io/...`, so that host reached the schema fetch. The add
path passes no `constraintsToGenerate`, so `get` derived bare constraints from
cdktf.json and OpenTofu resolved them from its own registry - which is why the
two commands disagreed and the file ended up with one key per host. It now
passes `namespace/name`, leaving resolution to the CLI. Of the ten
`new TerraformProviderConstraint(...)` sites, this was the only one handing on a
normalized FQPN.

`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, so a host change left both - defence in
depth now that the host above is stable.

`LocalProviderVersions` stripped the literal `registry.terraform.io/` prefix, so
under OpenTofu `versionForProvider("random")` found nothing and version detection
and `provider list` came up empty. It now strips any public registry host.

Restores the OpenTofu pin on typescript/provider-upgrade-command that open-constructs#479 had to
drop, bringing the matrix back to three tofu rows.

Verified against both CLIs locally, each leaving a single correctly-keyed entry:
terraform 1.14.3 gives registry.terraform.io/hashicorp/random 3.2.0, opentofu
1.12.6 gives registry.opentofu.org/hashicorp/random 3.2.0. Of the four new
LocalProviderVersions tests, the two OpenTofu cases fail against the previous
implementation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`cdktn provider upgrade` failed outright in an OpenTofu-only project:

    Usage Error: Trying to upgrade random but it is not installed,
    please use "cdktn provider add random" to add it.

even with `hashicorp/random@=3.1.3` declared in cdktf.json.

The handler built its constraint with `ProviderConstraint.fromConfigEntry()` and
no registry, which defaults to Terraform, so it normalized to
`registry.terraform.io/hashicorp/random`. `CdktfConfigManager` resolves the
project's entries against the registry the project declares, giving
`registry.opentofu.org/hashicorp/random`, so `hasProvider` compared two
different sources and reported an installed provider as missing.

It now resolves the argument the way the project's own entries resolve. Without
this the earlier commit on this branch only fixed the schema fetch, so the
command still aborted before reaching it in exactly the projects it was meant
to help.

No integration fixture declares `targetVersions`, which is why this was not
caught: the OpenTofu matrix rows cover the CLI being OpenTofu, not the project
targeting it. Verified by pointing the fixture at `targetVersions.opentofu` and
running under tofu 1.12.6 - failing before, and afterwards leaving cdktf.json at
=3.2.0 with a single `registry.opentofu.org/hashicorp/random` entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ked it

`cdktn deploy` failed for an 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

Before diff and deploy, CdktfStack compares the synthesized `required_providers`
against `.terraform.lock.hcl` to decide whether to run `init` and whether an
`init -upgrade` is needed. Both lookups were exact on the fully qualified
address, and `requiredProviders()` normalized each source with
`new ProviderConstraint(source, version)` - no registry, so always
`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 sources too
(checked @cdktn/provider-aws 25.7.2 and @cdktn/provider-random 15.0.1). OpenTofu
locks a bare source under its own registry, so the comparison missed for every
OpenTofu project in both directions:

- `checkNeedsLockfileUpdate` never found the provider, so every diff and deploy
  ran `init`
- `checkNeedsUpgrade` looked the lock entry up by its own address, missed, and
  treated the provider as no longer used, so a version bump never triggered
  `init -upgrade` - and the plain `init` that did run rejects the bump, as above

`lockAddressesFor` now returns the addresses that can satisfy a source: exactly
its own for a source that names a host, and one per public registry for a bare
source, since which one the CLI locked depends on whether it is Terraform or
OpenTofu. Both checks go through it. An explicit host still has to match
exactly; treating every public registry as equivalent would report an explicit
`registry.terraform.io` source as locked when only the OpenTofu entry exists,
skip `init`, and leave the CLI to fail. Projects that fully qualify their
providers were never affected, since their sources match exactly.

Verified end to end with the OpenTofu-target integration fixture: with this
change reverted, its provider-bump test fails with the error above; with it, the
bump deploys. Terraform projects are unchanged - Terraform locks a bare source
under registry.terraform.io, which was the only address checked before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes open-constructs#488. `run-against-dist.mjs` published dist/js to Verdaccio and then ran
`pnpm add cdktn-cli`. Every local build carries the same version, and nothing
invalidates pnpm's view of it - only Verdaccio's storage is cleaned - so pnpm
could install an earlier build it already held. Observed directly: a freshly
built tarball containing a fix, and an installed `bundle/bin/cmds/handlers.js`
from that morning whose shipped source map did not contain it. The run reported
normally, so a stale CLI looked like real evidence.

The install now names the tarball path. The glob is `cdktn-cli-[0-9]*.tgz`
rather than a fixed `0.0.0` because release builds stamp real versions through
align-version, and the digit keeps `cdktn-cli-core-*.tgz` - which a plain
`cdktn-cli-*` would also match - out of it. Anything other than exactly one
match fails the run with the files found. `--registry` stays so the CLI's
@cdktn/* dependencies still resolve from Verdaccio.

CI was not affected, since jobs start from a clean container; this only bit
local verification, which is where a fix is iterated and a stale result is most
likely to be misread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes open-constructs#494. Every fixture left `targetVersions` unset, and `cdktn init` writes
none, so `registryForTargetVersions()` returned the Terraform registry in every
integration run. The matrix's OpenTofu rows only changed which CLI ran, and
nothing exercised a project whose registry resolution follows its declared
target - which is where the recent run of OpenTofu defects lived.

`typescript/opentofu-target` declares `targetVersions: { opentofu: ">=1.6.0" }`
and is pinned to the OpenTofu runtime alone, since driving an OpenTofu-targeting
project with the Terraform CLI is not a pairing worth paying a job for. One
shared project walks the lifecycle a user does:

- bindings come from the OpenTofu registry: one `registry.opentofu.org` entry in
  versions.json, and docs links to search.opentofu.org
- synth keeps the provider source bare, since a host would pin the CLI
- deploy installs from registry.opentofu.org
- `provider list` reports the generated version, through the
  LocalProviderVersions reader
- a provider bump with `provider upgrade` redeploys at the new version

Each assertion was checked to fail for the defect it guards rather than pass
incidentally. Reverting the lock-file matching fix makes the bump fail with
"locked provider registry.opentofu.org/hashicorp/random 3.6.0 does not match
configured version constraint 3.6.1". The fixture also ran against a CLI shown
by extracting its tarball to contain the fix, after three separate stale-build
layers had produced misleading results.

Adds one matrix job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`package` depends on `build`, but `package:js`, `package:python`,
`package:java`, `package:dotnet` and `package:go` had no target defaults, so
they repacked whatever compiled output was already in `lib/` and `bundle/`. The
tarball gets a fresh timestamp while its contents are stale, which is the
hardest kind of stale artifact to notice: run-against-dist.mjs's own error
message points people at `pnpm run package:js`, and a fix made since the last
full build silently goes untested.

Observed directly: after source edits, `package:js` produced a 07:06 tarball
whose `lib/features.js` was from 06:21 and lacked the change. With
`dependsOn: ["build"]` the same command runs the dependency build chain first.

Only `dependsOn` is added, not caching: a cached `package` target is what served
a weeks-old CLI tarball in the first place. `build` is cached, so the added
dependency costs a cache hit when nothing changed. No CI workflow invokes these
targets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test/Readme.md still said "OpenTofu is currently not part of the CI image;
OpenTofu runs are local only for now". The image has shipped OpenTofu as
`tofu<version>` since the jsii-terraform image update, unit tests tagged
`unit-test:terraform` run once under it, and four integration targets are now
pinned to it.

Documents the `opentofu` block in .terraform.versions.json, how a test opts into
OpenTofu through `pinnedRuntimes` and is kept out of it with `terraformOnly`, and
the distinction that open-constructs#494 turned on: running under the OpenTofu CLI does not
exercise a project that targets OpenTofu, because registry resolution follows
the project's declared `targetVersions`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review found that `lockAddressesFor` accepted a bare source's lock entry from
either public registry. A bare source resolves according to the CLI that runs,
so Terraform and OpenTofu entries are not interchangeable:

- a project deployed with Terraform and then run with OpenTofu has only a
  registry.terraform.io entry; that counted as locked, `init` was skipped, and
  OpenTofu then failed on the lock file
- with both entries present, a stale entry for the inactive registry could force
  an unnecessary `init -upgrade`

CdktfStack now identifies the running CLI from `version` output and resolves a
bare source against that CLI's registry only. When it cannot be identified,
both public registries are candidates and the decisions stay conservative:
refresh unless every candidate is locked, and upgrade if any conflicts. A source
that names a host still matches exactly that host.

The two decisions move out of CdktfStack's private methods into
`needsLockfileUpdate` and `needsUpgrade`, so the tests exercise the code
production calls. They cover a lock holding both public-registry entries with
conflicting constraints, a switch from Terraform to OpenTofu, an unidentified
CLI, and an explicit host against another registry's entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review found that `provider upgrade` rebuilt every updated constraint for schema
generation as `namespace/name`, dropping any host. In an OpenTofu-targeted
project, upgrading `registry.terraform.io/hashicorp/random` kept the Terraform
source in cdktf.json while generation fetched `hashicorp/random`, which OpenTofu
resolves from its own registry - bindings from a different registry than the
configured provider. Private hosts were dropped the same way.

ProviderConstraint now records whether its source named a host as given, and
`requiredProvidersSource` keeps such a host while still dropping one added by
normalization. The handler uses it, and the rule is unit tested where the
handler cannot be.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of open-constructs#479 noted that the comment on the provider-add/upgrade OpenTofu pins
claimed they catch regressions in registry selection. Their fixtures declare no
targetVersions, so they exercise the OpenTofu CLI, not a project targeting
OpenTofu; typescript/opentofu-target covers that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ry again

Reverts the running-CLI part of a79880b, from review. That commit made the lock
checks match a bare provider source only against the registry of the CLI that
was running, identified by calling `terraform version` before every init. It was
justified by two switching cases, and neither held up:

- "counted as locked, `init` was skipped, and OpenTofu then failed on the lock
  file" - `init` always runs; these checks only add `-upgrade` and a follow-up
  `providers lock`. OpenTofu rewrites a registry.terraform.io entry to its own
  registry on init and keeps the selected version.
- a stale entry for the other registry forcing an unneeded `-upgrade` - real, but
  only after a switch, and it costs one extra `-upgrade`.

And the filter broke the realistic case. Reproduced with Terraform 1.14.3 and
OpenTofu 1.12.6 on a stack deployed by Terraform and then bumped from 3.6.0 to
3.6.1 under OpenTofu: the filter ignored the Terraform entry, added no
`-upgrade`, and `tofu init` failed with "locked provider ... must use tofu init
-upgrade". Matching either public registry adds `-upgrade` and the bump applies.

It also put `terraform version` output - including "Your version of Terraform is
out of date!" - into every diff, deploy and destroy log, once per stack, and
spawned that process for every project; on main it only ran behind the opt-in
validateInstalledBinary.

A bare source again matches its lock entry under any public registry; a source
that names a host still matches only that host. `needsLockfileUpdate` and
`needsUpgrade` stay the exported functions CdktfStack calls, and their tests now
cover the Terraform-to-OpenTofu bump. `lockAddressesFor` is module-private.
Filtering bare sources to one registry fails three of the cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sakul-learning's scenario runner showed `provider upgrade random@=3.6.1` turning
`registry.opentofu.org/hashicorp/random@=3.6.0` in an OpenTofu-targeted
project's cdktf.json into `hashicorp/random@=3.6.1`. Matching the entry was the
fix in this PR, but the update was then written back through the step that
shortens the project registry's own host, so the host the user wrote was
dropped. The fully qualified upgrade argument dropped it the same way.

An update now keeps the existing entry's spelling of its source and changes only
the version. New entries are still written in the short form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@so0k

so0k commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Independent rerun of the scenario runner against cdktn-cli@0.25.0-pre.60, b668906 and 368efb3 (Terraform 1.7.5, OpenTofu 1.12.6):

Scenario pre.60 b668906 368efb3
OpenTofu target, bare source, upgrade to 3.6.1 ❌ stale OpenTofu 3.6.0 + Terraform 3.6.1 in versions.json ✅ ✅
Terraform → OpenTofu, bump ❌ redeploy fails ✅ ✅
OpenTofu → Terraform, bump ❌ stale entry kept ✅ ✅
Explicit registry.opentofu.org, shorthand upgrade ❌ "not installed" ❌ host dropped from cdktf.json ✅ cdktf.json keeps the host
Explicit registry.terraform.io under OpenTofu ❌ ❌ ❌
  • Explicit host on upgrade: fixed in cdktf.json by 368efb3. The regenerated binding still uses the argument's bare source until the next cdktn get, and object-form entries still lose the host. This is low impact and tracked in provider upgrade: keep an explicit provider host in the generated binding and in object-form entries #532.
  • Explicit registry.terraform.io under OpenTofu: OpenTofu behaviour, not a cdktn defect. Plain HCL fails the same way on OpenTofu 1.12.3, 1.12.6 and 1.13.2 (tofu providers lock adds a hash-less registry.opentofu.org entry); 1.8.2 and 1.9.3 work. Documented under Release notes in the PR body.

Rebased onto main (no changes to the PR commits) and set to auto-merge.

@so0k
so0k enabled auto-merge (squash) October 27, 2026 05:04
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
so0k merged commit 10672ee into open-constructs:main Oct 27, 2026
268 checks passed
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

3 participants