Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

96 changes: 96 additions & 0 deletions packages/nx-cloudflare-e2e/src/binding.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { describe, it, expect, beforeAll, afterAll } from 'bun:test';
import { uniq, fileExists, tmpProjPath } from '@nx/plugin/testing';
import { createTestProject, cleanup, runCLI } from '@naxodev/e2e-utils';
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';

// Exercises the binding generator end-to-end: scaffolds a Worker via real C3,
// then runs `:binding` against the published tarball to verify config edits,
// code stubs, and migrations land correctly in a real consumer workspace.
describe('binding generator', () => {
let priorFlatConfig: string | undefined;

beforeAll(() => {
priorFlatConfig = process.env.ESLINT_USE_FLAT_CONFIG;
process.env.ESLINT_USE_FLAT_CONFIG = 'false';
createTestProject('nx-cloudflare');
}, 300_000);

afterAll(() => {
if (priorFlatConfig === undefined) {
delete process.env.ESLINT_USE_FLAT_CONFIG;
} else {
process.env.ESLINT_USE_FLAT_CONFIG = priorFlatConfig;
}
cleanup();
});

it('adds a KV binding to an existing Worker', () => {
const app = uniq('bindingkv');

runCLI(
`generate @naxodev/nx-cloudflare:create-cloudflare --directory="apps/${app}" --type=hello-world --lang=ts --no-interactive`,
{ env: { NX_ADD_PLUGINS: 'true' } }
);

runCLI(
`generate @naxodev/nx-cloudflare:binding --project="${app}" --type=kv --binding=MY_KV --id=test-id-123 --skipTypegen --no-interactive`
);

const root = join(tmpProjPath(), `apps/${app}`);
const wrangler = readFileSync(join(root, 'wrangler.jsonc'), 'utf-8');
expect(wrangler).toContain('kv_namespaces');
expect(wrangler).toContain('MY_KV');
expect(wrangler).toContain('test-id-123');
}, 300_000);

it('adds a Durable Object binding with a migration and class stub', () => {
const app = uniq('bindingdo');

runCLI(
`generate @naxodev/nx-cloudflare:create-cloudflare --directory="apps/${app}" --type=hello-world --lang=ts --no-interactive`,
{ env: { NX_ADD_PLUGINS: 'true' } }
);

runCLI(
`generate @naxodev/nx-cloudflare:binding --project="${app}" --type=do --binding=MY_DO --name=MyDurableObject --skipTypegen --no-interactive`
);

const root = join(tmpProjPath(), `apps/${app}`);
const wrangler = readFileSync(join(root, 'wrangler.jsonc'), 'utf-8');
expect(wrangler).toContain('durable_objects');
expect(wrangler).toContain('MyDurableObject');
expect(wrangler).toContain('migrations');
expect(wrangler).toContain('"v1"');

expect(fileExists(join(root, 'src/my-durable-object.ts'))).toBeTruthy();
const stub = readFileSync(join(root, 'src/my-durable-object.ts'), 'utf-8');
expect(stub).toContain('export class MyDurableObject');

const index = readFileSync(join(root, 'src/index.ts'), 'utf-8');
expect(index).toContain("export * from './my-durable-object'");
}, 300_000);

it('adds a Queue binding with producer + consumer and a queue handler', () => {
const app = uniq('bindingqueue');

runCLI(
`generate @naxodev/nx-cloudflare:create-cloudflare --directory="apps/${app}" --type=hello-world --lang=ts --no-interactive`,
{ env: { NX_ADD_PLUGINS: 'true' } }
);

runCLI(
`generate @naxodev/nx-cloudflare:binding --project="${app}" --type=queue --binding=MY_QUEUE --name=my-queue --skipTypegen --no-interactive`
);

const root = join(tmpProjPath(), `apps/${app}`);
const wrangler = readFileSync(join(root, 'wrangler.jsonc'), 'utf-8');
expect(wrangler).toContain('queues');
expect(wrangler).toContain('producers');
expect(wrangler).toContain('consumers');
expect(wrangler).toContain('my-queue');

const index = readFileSync(join(root, 'src/index.ts'), 'utf-8');
expect(index).toContain('async queue(');
}, 300_000);
});
73 changes: 73 additions & 0 deletions packages/nx-cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Nx plugin for Cloudflare.
- ✅ Vitest tests support
- ✅ Inferred `serve`, `deploy`, `typegen`, `version-upload`, and `tail` targets (via the `@naxodev/nx-cloudflare/plugin` Crystal plugin).
- ✅ Generate Cloudflare Worker Library
- ✅ Add bindings to an existing Worker (KV, R2, D1, Durable Objects, Queues, Workflows, Service/RPC)

## Installation

Expand Down Expand Up @@ -125,6 +126,78 @@ See the [`wrangler dev`](https://developers.cloudflare.com/workers/wrangler/comm

`worker-configuration.d.ts` (your typed `Env` and runtime types) is the `typegen` target's declared output, so it is treated as a generated build artifact — the generator git-ignores it instead of committing it. Run `nx typegen <my-app>` to (re)generate it, and re-run after changing bindings or `compatibility_date` in your `wrangler` config.

### Bindings

#### Adding a binding to an existing Worker

```bash
nx g @naxodev/nx-cloudflare:binding --project=my-worker --type=kv --binding=MY_KV
```

The binding generator wires a binding into an existing Worker's `wrangler.jsonc`, stubs the required code (Durable Object / Workflow classes, queue consumer handler), adds Durable Object migrations, emits a matching Vitest spec, and refreshes `wrangler types` so the `Env` interface picks up the new binding.

Supported types:

| Type | Config key | Code stub | Provisionable via `--create` |
| ---------- | ----------------- | ---------------------------------------------------------- | --------------------------------- |
| `kv` | `kv_namespaces` | — (env binding typed by `wrangler types`) | ✅ `wrangler kv namespace create` |
| `r2` | `r2_buckets` | — | ✅ `wrangler r2 bucket create` |
| `d1` | `d1_databases` | — | ✅ `wrangler d1 create` |
| `do` | `durable_objects` | `export class X extends DurableObject<Env> {}` + migration | ❌ (code class, not a resource) |
| `queue` | `queues` | `queue()` handler injected into `src/index.ts` | ✅ `wrangler queues create` |
| `workflow` | `workflows` | `export class X extends WorkflowEntrypoint<Env, P> {}` | ❌ |
| `service` | `services` | — (env binding typed by `wrangler types`) | ❌ (reference to another Worker) |

Available options:

| Option | Type | Default | Description |
| ------------ | ------------------------------------------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------- |
| project | string | \*required | The target Worker project. |
| type | `kv` \| `r2` \| `d1` \| `do` \| `queue` \| `workflow` \| `service` | \*required | The kind of binding to add. |
| binding | string | \*required | The `Env` key name (SCREAMING_SNAKE_CASE), e.g. `MY_KV`. |
| name | string | — | Class name (`do`/`workflow`, PascalCase), queue name (`queue`), or service name (`service`). Required for those types. |
| id | string | — | Existing KV namespace id or D1 database id. Auto-captured when `--create` is used. |
| databaseName | string | — | D1 database name. Required for `d1` (or `--create`). |
| bucketName | string | — | R2 bucket name. Required for `r2` (or `--create`). |
| create | boolean | false | Provision the remote resource via Wrangler (KV/R2/D1/Queue only). Fills the id/name automatically. |
| skipTests | boolean | false | Skip generating a Vitest spec for the binding stub. |
| skipFormat | boolean | false | Skip formatting files. |
| skipTypegen | boolean | false | Skip auto-running `wrangler types` after writing the binding. |

#### Examples

Add a KV namespace with an existing id:

```bash
nx g @naxodev/nx-cloudflare:binding --project=my-worker --type=kv --binding=MY_KV --id=abc123
```

Provision a new KV namespace and wire it automatically:

```bash
nx g @naxodev/nx-cloudflare:binding --project=my-worker --type=kv --binding=MY_KV --create
```

Add a Durable Object (writes the binding, a migration, a class stub, and a test):

```bash
nx g @naxodev/nx-cloudflare:binding --project=my-worker --type=do --binding=MY_DO --name=MyDurableObject
```

Add a Queue (writes producer + consumer config and injects a `queue()` handler into `src/index.ts`):

```bash
nx g @naxodev/nx-cloudflare:binding --project=my-worker --type=queue --binding=MY_QUEUE --name=my-queue
```

Add a Service/RPC binding:

```bash
nx g @naxodev/nx-cloudflare:binding --project=my-worker --type=service --binding=AUTH_SERVICE --name=auth-worker
```

The generator preserves JSONC comments in `wrangler.jsonc` and errors if the binding name already exists — re-run with a different `--binding` or remove the existing entry first.

### Cloudflare Worker Library

#### Generating a new Cloudflare Worker Library
Expand Down
5 changes: 5 additions & 0 deletions packages/nx-cloudflare/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
"schema": "./src/generators/library/schema.json",
"description": "library generator",
"aliases": ["lib"]
},
"binding": {
"factory": "./src/generators/binding/generator",
"schema": "./src/generators/binding/schema.json",
"description": "Add a binding (KV, R2, D1, Durable Object, Queue, Workflow, Service) to a Worker — edits wrangler.jsonc, stubs code + migrations, emits a test, and refreshes wrangler types"
}
}
}
3 changes: 2 additions & 1 deletion packages/nx-cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"dependencies": {
"tslib": "^2.3.0",
"@nx/devkit": "^23.0.0",
"@nx/js": "^23.0.0"
"@nx/js": "^23.0.0",
"jsonc-parser": "^3.3.0"
},
"peerDependencies": {
"wrangler": "^4.0.0"
Expand Down
Loading
Loading