Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/agent-guide-nk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ingram-tech/agent-guide": patch
---

Mention `@ingram-tech/nk-cli` (the `nk` command) and refine the formatting rule: Biome formats code, Prettier is used only for SQL (via `nk`), which Biome can't format.
5 changes: 5 additions & 0 deletions .changeset/bot-protection-react.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ingram-tech/bot-protection": minor
---

Add a `/react` client export: `useBotProtection(tokenEndpoint)` + `HoneypotInput`, for client components that POST JSON to their own route. Replaces the hand-copied `src/lib/bot-protection.tsx` that had been duplicated across sites, keeping the honeypot field name and timing token in lockstep with the server verifier.
5 changes: 5 additions & 0 deletions .changeset/nk-cli-initial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ingram-tech/nk-cli": minor
---

New package: `nk`, the nextkit CLI. `nk dev` starts Next and boots local Supabase first (wiring its env in) when `supabase/config.toml` is present — replacing per-site `dev.sh`. Adds `nk format` (Biome for code, Prettier for SQL — bundled, so Prettier never lands in app deps), plus `lint` / `check` / `type-check` / `build`. The code formatter sits behind a small indirection so it can move to oxc later via `{ "nk": { "formatter": "oxc" } }`.
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ jobs:
- run: bun install --frozen-lockfile
- name: Lint & format check
run: bun run check
- name: Type-check
run: bun run type-check
# Build before type-check: packages whose deps resolve to built workspace
# types (e.g. newsletter → email's dist/*.d.ts) need dist to exist first.
# check runs first on the clean tree, so Biome never sees dist/.
- name: Build
run: bun run build
- name: Type-check
run: bun run type-check
- name: Test
run: bun run test
55 changes: 52 additions & 3 deletions bun.lock

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"check": "biome check .",
"type-check": "bun run --filter '*' type-check",
"test": "bun run --filter '*' test",
"ci": "bun run check && bun run type-check && bun run test",
"ci": "bun run check && bun run build && bun run type-check && bun run test",
"changeset": "changeset",
"version-packages": "changeset version",
"release": "bun run build && changeset publish",
Expand Down
6 changes: 4 additions & 2 deletions packages/agent-guide/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ package. Stay a thin, standard Next.js app (bun · Biome · strict TS).
(server: `verifyHuman` → silently drop bots; client: honeypot + signed token).
Never ship a form without it.
- **Send email only via `@ingram-tech/email`** — never add another mail client.
- Lint/format with **Biome** (`@ingram-tech/biome-config`); don't reintroduce
ESLint/Prettier.
- Format/lint with **Biome** via `nk` (`@ingram-tech/nk-cli`); don't reintroduce
ESLint, nor Prettier for code (`nk` uses Prettier only for SQL, which Biome
can't format).

## What nextkit provides (reach for these)

- `@ingram-tech/email` — Cloudflare email: `sendEmail`, `fromAddress`
- `@ingram-tech/bot-protection` — invisible form protection (honeypot + timing + Vercel BotID)
- `@ingram-tech/newsletter` — Supabase newsletter: subscribe / send, 1-click unsubscribe
- `@ingram-tech/nk-cli` — the `nk` command: `nk dev` (Next + local Supabase), plus `nk format` / `lint` / `check` / `type-check` / `build`
- `@ingram-tech/biome-config` · `typescript-config` · `test-config` — shared config
- `@ingram-tech/git-hooks` — Biome format-on-commit

Expand Down
45 changes: 45 additions & 0 deletions packages/bot-protection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,51 @@ export async function POST(request: Request) {
`verifyHuman` also accepts a plain object (`{ formData: { ...fields } }`) and a
`timing` window (`{ minMs, maxMs }`). Pass `botid: false` to skip layer 3.

### Client forms (JSON POST)

For client components that POST JSON instead of a server-rendered `<form>`, use
the `/react` hook. It fetches the token from your route's `GET` on mount and
hands you the fields to merge into the body. The route's `GET` returns the
token; its `POST` verifies:

```tsx
"use client";
import { HoneypotInput, useBotProtection } from "@ingram-tech/bot-protection/react";

export function ContactForm() {
const { honeypotRef, botFields } = useBotProtection("/api/contact");

async function onSubmit(values: FormValues) {
await fetch("/api/contact", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ ...values, ...botFields() }),
});
}
return (
<form onSubmit={/* ... */}>
{/* ...your real fields... */}
<HoneypotInput inputRef={honeypotRef} />
</form>
);
}
```

```ts
// app/api/contact/route.ts
import { createFormToken, verifyHuman } from "@ingram-tech/bot-protection";

export const GET = () => Response.json({ token: createFormToken() });

export async function POST(request: Request) {
const body = await request.json();
const result = await verifyHuman({ formData: body });
if (!result.ok) return Response.json({ ok: true }); // silently drop
// ...send the email / save the lead...
return Response.json({ ok: true });
}
```

The honeypot field defaults to a name browsers and password managers won't
autofill (filling it would falsely flag real users). If that default collides
with a real field in your form, override it on both sides — they must match:
Expand Down
4 changes: 4 additions & 0 deletions packages/bot-protection/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
"types": "./dist/honeypot.d.ts",
"import": "./dist/honeypot.js"
},
"./react": {
"types": "./dist/react.d.ts",
"import": "./dist/react.js"
},
"./fields": {
"types": "./dist/fields.d.ts",
"import": "./dist/fields.js"
Expand Down
Loading
Loading