If this channel isn't in your workspace yet, add it first:
pixi workspace channel add --prepend https://prefix.dev/brads-forgeThen install the package:
pixi add cdktsInstall globally (CLI tools)
For packages that expose command-line tools, install globally to use them anywhere:
pixi global install --channel https://prefix.dev/brads-forge cdktsVariants | Size | |||||
|---|---|---|---|---|---|---|
cdkts-0.8.0-he8ea13f_1.conda | 0.8.0 | he8ea13f_1 (1) | 6 months ago | 44.2 MB | ||
| This package has no required dependencies. | ||||||
cdkts-0.8.0-h3c70cbc_1.conda | 0.8.0 | h3c70cbc_1 (1) | 6 months ago | 48.63 MB | ||
| This package has no required dependencies. | ||||||
cdkts-0.8.0-he0f4bc8_1.conda | 0.8.0 | he0f4bc8_1 (1) | 6 months ago | 46.95 MB | ||
| This package has no required dependencies. | ||||||
cdkts-0.8.0-h81b34b9_1.conda | 0.8.0 | h81b34b9_1 (1) | 6 months ago | 49.22 MB | ||
| This package has no required dependencies. | ||||||
cdkts-0.7.8-he8ea13f_1.conda | 0.7.8 | he8ea13f_1 (1) | 6 months ago | 44.2 MB | ||
| This package has no required dependencies. | ||||||
cdkts-0.7.8-h3c70cbc_1.conda | 0.7.8 | h3c70cbc_1 (1) | 6 months ago | 48.63 MB | ||
| This package has no required dependencies. | ||||||
cdkts-0.7.8-he0f4bc8_1.conda | 0.7.8 | he0f4bc8_1 (1) | 6 months ago | 46.95 MB | ||
| This package has no required dependencies. | ||||||
cdkts-0.7.8-h81b34b9_1.conda | 0.7.8 | h81b34b9_1 (1) | 6 months ago | 49.22 MB | ||
| This package has no required dependencies. | ||||||
cdkts-0.7.7-h81b34b9_1.conda | 0.7.7 | h81b34b9_1 (1) | 8 months ago | 47.27 MB | ||
| This package has no required dependencies. | ||||||
cdkts-0.7.7-he8ea13f_1.conda | 0.7.7 | he8ea13f_1 (1) | 8 months ago | 42.26 MB | ||
| This package has no required dependencies. | ||||||
# Add to your deno.json imports
deno add jsr:@brad-jones/cdkts
// And import using the import map
import { Stack } from "@brad-jones/cdkts/constructs";
import { Project } from "@brad-jones/cdkts/automate";
// Or import directly without any deno config
import { Stack } from "jsr:@brad-jones/cdkts/constructs";
import { Project } from "jsr:@brad-jones/cdkts/automate";
You have at least 3 ways to invoke the CLI using deno
Using Deno Install (Recommended):
deno install --global -A -f jsr:@brad-jones/cdkts/cli
Then just call the installed command like: cdkts apply ./my_stack
see: https://docs.deno.com/runtime/reference/cli/install/
Using the new DX Alias (Deno's version of npx):
deno x jsr:@brad-jones/cdkts/cli or just dx jsr:@brad-jones/cdkts/cli
see: https://deno.com/blog/v2.6
And of course good old Deno Run:
deno run -A jsr:@brad-jones/cdkts/cli
see: https://docs.deno.com/runtime/reference/cli/run/
The CLI is also packaged as a statically compiled binary, that includes an embedded copy of the deno runtime. Consider this to be experimental!
Download direct from: https://github.com/brad-jones/cdkts/releases
Or install with pixi.
Add my channel: https://prefix.dev/channels/brads-forge/packages/cdkts
# Add just to your current pixi workspace
pixi workspace channel add https://prefix.dev/brads-forge
# Or add to your global config
pixi --global config append default-channels https://prefix.dev/brads-forge
# Then install
pixi add cdkts
Create my_stack.ts:
import { Resource, Stack, Terraform } from "@brad-jones/cdkts/constructs";
export default class MyStack extends Stack<typeof MyStack> {
constructor() {
super(`${import.meta.url}#${MyStack.name}`);
new Terraform(this, {
requiredVersion: ">=1,<2.0",
requiredProviders: {
local: {
source: "hashicorp/local",
version: "2.6.1",
},
},
});
new Resource(this, "local_file", "hello", {
filename: "${path.module}/message.txt",
content: "Hello World",
});
}
}
cdkts apply ./my_stack.ts
That's it! CDKTS will:
A Stack is the fundamental unit in CDKTS. It represents a complete Terraform/OpenTofu configuration:
export default class MyStack extends Stack<typeof MyStack> {
static override readonly Props = class extends Stack.Props {
// Define inputs
filename = new Stack.Input();
// Define outputs
contentHash = new Stack.Output();
};
constructor() {
super(`${import.meta.url}#${MyStack.name}`);
// Add resources, providers, etc.
const file = new Resource(this, "local_file", "hello", {
filename: this.inputs.filename,
content: "Hello World",
});
this.outputs = {
contentHash: file.outputs.content_sha256,
};
}
}
CDKTS provides compile-time type safety for stack inputs and outputs:
static override readonly Props = class extends Stack.Props {
// String input (default type)
filename = new Stack.Input();
// Typed input
port = new Stack.Input<number>({ default: 8080 });
// Optional input with description
region = new Stack.Input({
default: "us-west-2",
description: "AWS region"
});
// Outputs
instanceId = new Stack.Output<string>({
description: "The EC2 instance ID",
sensitive: true
});
};
Define any Terraform resource using the generic Resource construct:
new Resource(this, "aws_instance", "web", {
ami: "ami-12345678",
instance_type: "t3.micro",
tags: {
Name: "WebServer",
},
});
The Project class provides programmatic control over the Terraform/OpenTofu lifecycle:
import { Project } from "@brad-jones/cdkts/automate";
import MyStack from "./my_stack.ts";
await using project = new Project({
stack: new MyStack(),
});
// Initialize
await project.init();
// Plan
const plan = await project.plan();
// Apply
const state = await project.apply(plan);
console.log(state.values?.outputs);
// Destroy
await project.destroy();
The examples/ directory contains progressively more advanced examples:
CDKTS provides a CLI that wraps Terraform/OpenTofu with TypeScript stack support:
# Initialize a stack
cdkts init ./my_stack.ts
# Plan changes
cdkts plan ./my_stack.ts
# Apply configuration
cdkts apply ./my_stack.ts
# Destroy infrastructure
cdkts destroy ./my_stack.ts
# Validate configuration
cdkts validate ./my_stack.ts
# View outputs
cdkts output ./my_stack.ts
All commands support pass-through arguments using --:
# Pass flags to terraform/opentofu
cdkts plan ./my_stack.ts -- -var="instance_type=t3.micro" -out=tfplan
cdkts apply ./my_stack.ts -- -auto-approve -parallelism=10
Configure CDKTS behavior via environment variables:
# Use Terraform instead of OpenTofu
export CDKTS_FLAVOR=terraform
# Use a specific binary
export CDKTS_TF_BINARY_PATH=/usr/local/bin/terraform
# Specify version to download
export CDKTS_TF_VERSION=1.11.4
# Set project directory
export CDKTS_PROJECT_DIR=./build/terraform
Execute any Terraform/OpenTofu command not explicitly wrapped:
cdkts show ./my_stack.ts
cdkts state list ./my_stack.ts
cdkts console ./my_stack.ts
Coordinate multiple stacks and pass outputs between them:
import { Project } from "@brad-jones/cdkts/automate";
import Stack1 from "./stack_1.ts";
import Stack2 from "./stack_2.ts";
// Apply first stack
let state1;
{
await using project = new Project({ stack: new Stack1() });
state1 = await project.apply();
}
// Pass outputs to second stack
{
await using project = new Project({
stack: new Stack2({
dbEndpoint: state1.values!.outputs!.dbEndpoint.value,
}),
});
await project.apply();
}
Compile your stack into a single executable that includes everything:
import { StackBundler } from "@brad-jones/cdkts/automate";
await new StackBundler({
stackFilePath: "./my_stack.ts",
outputPath: "./dist/my_stack",
targets: ["linux-x86_64", "darwin-aarch64", "windows-x86_64"],
tfVersion: "1.11.4",
flavor: "tofu",
}).bundle();
This creates executables that:
Also available via the CLI @ cdkts bundle
Write custom Terraform providers in TypeScript:
import { DenoResource } from "@brad-jones/cdkts/constructs";
import { ZodResourceProvider } from "@brad-jones/terraform-provider-denobridge";
import { z } from "@zod/zod";
// >>> Define your type safe schemas (Zod is optional but recommend)
// -----------------------------------------------------------------
const Props = z.object({
path: z.string(),
content: z.string(),
});
const State = z.object({
mtime: z.number(),
});
// >>> Define the Construct class that you use in your Stack
// -----------------------------------------------------------------
export class FileResource extends DenoResource<typeof FileResource> {
static override readonly Props = class extends DenoResource.Props {
override props = new DenoResource.ZodInput(Props);
override state = new DenoResource.ZodOutput(State);
};
constructor(parent: Construct, label: string, props: z.infer<typeof Props>) {
super(parent, label, {
props,
path: import.meta.url, // <<<--- This is the key, it tells the denobridge provider to run this same script.
permissions: { all: true },
});
}
}
// >>> Define the Provider for the Construct (runs via JSON-RPC)
// -----------------------------------------------------------------
if (import.meta.main) {
new ZodResourceProvider(Props, State, {
async create({ path, content }) {
await Deno.writeTextFile(path, content);
return {
id: path,
state: { mtime: (await Deno.stat(path)).mtime!.getTime() },
};
},
async read({ id }) {
const stat = await Deno.stat(id);
return { state: { mtime: stat.mtime!.getTime() } };
},
async update({ id, props: { content } }) {
await Deno.writeTextFile(id, content);
return { state: { mtime: (await Deno.stat(id)).mtime!.getTime() } };
},
async delete({ id }) {
await Deno.remove(id);
},
});
}
See the Deno Bridge example for complete examples of resources, data sources, actions, and ephemeral resources.
Implement custom Terraform/OpenTofu state backends entirely in TypeScript.
The DenoBackend wraps the HTTP backend
and automatically starts a local HTTPS server that routes state operations to your handler methods:
import { DenoBackend, RawHcl, Resource, Stack, Terraform } from "@brad-jones/cdkts/constructs";
export default class MyStack extends Stack<typeof MyStack> {
constructor() {
super(`${import.meta.url}#${MyStack.name}`);
new Terraform(this, {
requiredVersion: ">=1,<2.0",
requiredProviders: {
local: { source: "hashicorp/local", version: "2.6.1" },
},
}, (tf) => {
const stateFile = "./terraform.tfstate";
new DenoBackend(tf, {
handlers: {
getState: async () => {
try {
return await Deno.readFile(stateFile);
} catch {
return null;
}
},
updateState: async (body) => {
await Deno.writeFile(stateFile, body);
},
deleteState: async () => {
try {
await Deno.remove(stateFile);
} catch { /* noop */ }
},
},
});
});
new Resource(this, "local_file", "hello", {
filename: new RawHcl('"${path.module}/message.txt"'),
content: "Hello from Deno Backend!",
});
}
}
Under the hood, DenoBackend:
TF_HTTP_* env varsOptional lock and unlock handlers enable state locking. The construct is designed
to be extended — build higher-level abstractions like file-based, database-backed,
or cloud storage backends on top of it.
See the Deno Backend example for a complete working example.
Construct: Base class for all constructs
Stack: Base class for defining stacks. Is the "root" Construct.Block: Extends the Construct to express any valid HCL block
Terraform: Extends Block, to provide the Terraform settings block
Backend: Extends Block, to provide a generic backend block
LocalBackend: Extends Backend, to provide a type safe local backend blockRemoteBackend: Extends Backend, to provide a type safe remote backend blockHttpBackend: Extends Backend, to provide a type safe HTTP backend block
DenoBackend: Extends HttpBackend, implements a state backend with TypeScript handlersProvider: Extends Block, to provide a generic provider block
DenoBridgeProvider: Extends Provider, to provide a type safe denobridge provider block.Action - Extends Block, to provide a generic action block
DenoAction: Extends Action, to provide a generic denobridge_action block.Resource - Extends Block, to provide a generic resource block
DenoResource: Extends Resource, to provide a generic denobridge_resource block.EphemeralResource - Extends Block, to provide a generic ephemeral block
DenoEphemeralResource: Extends Resource, to provide a generic denobridge_resource block.DataSource - Extends Block, to provide a generic datasource block
DenoDataSource: Extends DataSource, to provide a generic denobridge_datasource block.Variable - Declares an input variable, allowing configurations to be parameterized without modifying source code.Output - Exports a value from a stack, making it available for inspection after apply or for consumption by other configurations.Module - Calls a reusable Terraform/OpenTofu module (local path, registry, or remote source).Local - Assigns a name to an expression so it can be reused multiple times within a configuration without repetition.Check - Validates infrastructure outside the typical resource lifecycle.Assertion - Declares a validation condition within a Check block.Import - Imports existing infrastructure resources into Terraform state so they can be managed as code.Moved - Renames or relocates a resource/module in Terraform state without destroying and recreating it.Removed - Removes a resource from Terraform state without keeping the original resource block.Remember any block that is currently not implemented can still be expressed directly with the Block class!
Project - Lifecycle management (init, plan, apply, destroy)DenoBackendServer - HTTPS server for DenoBackend state handlersStackBundler - Compile stacks to executables