Skip to content

Commit 299a3c5

Browse files
zhawtofclaude
andcommitted
chore: merge main into branch
Resolves conflicts in package.json (version bump), src/index.ts, and src/validate-block-kit.ts by taking main's side — the conflict files were refactored on main (helpers consolidated into helpers.ts, formatAjvErrors extracted) and this branch only contributed README badges. README auto-merged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 parents f18b0d7 + 429da51 commit 299a3c5

26 files changed

Lines changed: 3930 additions & 42 deletions

.github/dependabot.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: npm
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
open-pull-requests-limit: 10
8+
groups:
9+
dev-dependencies:
10+
dependency-type: development
11+
update-types:
12+
- minor
13+
- patch
14+
15+
- package-ecosystem: github-actions
16+
directory: /
17+
schedule:
18+
interval: weekly
19+
open-pull-requests-limit: 5

.github/workflows/ci.yml

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,87 @@ jobs:
3737
- uses: ./.github/actions/setup
3838
- run: pnpm run typecheck
3939

40+
generated-types-fresh:
41+
# Catches schema edits where contributors forget to re-run generate:types.
42+
# The generated file is committed (so consumers can grep types in the
43+
# repo); this job makes sure that committed copy still matches the schema.
44+
name: Generated types in sync
45+
runs-on: ubuntu-latest
46+
timeout-minutes: 5
47+
steps:
48+
- uses: actions/checkout@v6
49+
- uses: ./.github/actions/setup
50+
- run: pnpm run generate:types
51+
- run: |
52+
# Treat both modified-tracked and untracked-but-expected-to-be-tracked
53+
# as failures. `git ls-files --error-unmatch` returns nonzero when
54+
# the file isn't tracked; `git diff --quiet` checks the tracked diff.
55+
if ! git ls-files --error-unmatch src/types.generated.ts >/dev/null 2>&1; then
56+
echo "::error::src/types.generated.ts is not committed. Run 'pnpm run generate:types' and commit the result."
57+
exit 1
58+
fi
59+
if ! git diff --quiet src/types.generated.ts; then
60+
echo "::error::src/types.generated.ts is stale. Run 'pnpm run generate:types' and commit the result."
61+
git --no-pager diff src/types.generated.ts | head -50
62+
exit 1
63+
fi
64+
4065
test:
4166
name: Test (Node ${{ matrix.node-version }})
4267
runs-on: ubuntu-latest
4368
timeout-minutes: 5
4469
strategy:
70+
fail-fast: false
4571
matrix:
46-
node-version: [20, 22]
72+
node-version: [20, 22, 24]
4773
steps:
4874
- uses: actions/checkout@v6
4975
- uses: ./.github/actions/setup
5076
with:
5177
node-version: ${{ matrix.node-version }}
52-
- run: pnpm test
78+
- run: pnpm run test:coverage
79+
- name: Upload coverage to Codecov
80+
if: matrix.node-version == 22
81+
uses: codecov/codecov-action@v5
82+
with:
83+
token: ${{ secrets.CODECOV_TOKEN }}
84+
files: ./coverage/lcov.info
85+
fail_ci_if_error: false
86+
87+
test-bun:
88+
name: Test (Bun)
89+
runs-on: ubuntu-latest
90+
timeout-minutes: 5
91+
steps:
92+
- uses: actions/checkout@v6
93+
- uses: ./.github/actions/setup
94+
- uses: oven-sh/setup-bun@v2
95+
- run: bun --bun run vitest run
96+
97+
test-deno:
98+
name: Smoke (Deno)
99+
runs-on: ubuntu-latest
100+
timeout-minutes: 5
101+
steps:
102+
- uses: actions/checkout@v6
103+
- uses: ./.github/actions/setup
104+
- run: pnpm run build
105+
- uses: denoland/setup-deno@v1
106+
with:
107+
deno-version: v2.x
108+
# Deno can consume the published ESM build directly. We're not running
109+
# the full vitest suite — just confirming the artifacts load + validate.
110+
- run: deno run --allow-read scripts/smoke-deno.ts
111+
112+
smoke-workers:
113+
name: Smoke (Cloudflare Workers build)
114+
runs-on: ubuntu-latest
115+
timeout-minutes: 5
116+
steps:
117+
- uses: actions/checkout@v6
118+
- uses: ./.github/actions/setup
119+
- run: pnpm run build
120+
# Bundle the standalone validator with esbuild in worker mode. If any
121+
# Node-only API or require() sneaks in, this fails — protecting the
122+
# README's "works in a Worker" claim.
123+
- run: pnpm exec esbuild --bundle --format=esm --platform=neutral --target=es2022 --outfile=/tmp/worker.js scripts/smoke-worker.ts

.github/workflows/dependabot-automerge.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
steps:
1414
- name: Fetch Dependabot metadata
1515
id: metadata
16-
uses: dependabot/fetch-metadata@v2
16+
uses: dependabot/fetch-metadata@v3
1717
with:
1818
github-token: "${{ secrets.GITHUB_TOKEN }}"
1919

.github/workflows/release-please.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
release-please:
1313
runs-on: ubuntu-latest
1414
steps:
15-
- uses: googleapis/release-please-action@v4
15+
- uses: googleapis/release-please-action@v5
1616
with:
1717
config-file: release-please-config.json
1818
manifest-file: .release-please-manifest.json

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "0.1.0-alpha.0"
2+
".": "0.1.1-alpha.1"
33
}

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,10 @@
11
# Changelog
2+
3+
## [0.1.1-alpha.1](https://github.com/TightknitAI/slack-block-kit-validator/compare/slack-block-kit-validator-v0.1.0-alpha.1...slack-block-kit-validator-v0.1.1-alpha.1) (2026-05-15)
4+
5+
6+
### Bug Fixes
7+
8+
* exports order, release-please manifest drift, missing dependabot config ([#7](https://github.com/TightknitAI/slack-block-kit-validator/issues/7)) ([b18ecb6](https://github.com/TightknitAI/slack-block-kit-validator/commit/b18ecb6435c6a8562e82cc6ceb54f855574df61e))
9+
10+
## Changelog

README.md

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# slack-block-kit-validator
22

33
[![CI](https://github.com/TightknitAI/slack-block-kit-validator/actions/workflows/ci.yml/badge.svg)](https://github.com/TightknitAI/slack-block-kit-validator/actions/workflows/ci.yml)
4+
[![codecov](https://codecov.io/gh/TightknitAI/slack-block-kit-validator/branch/main/graph/badge.svg)](https://codecov.io/gh/TightknitAI/slack-block-kit-validator)
45
[![npm version](https://img.shields.io/npm/v/@tightknitai/slack-block-kit-validator.svg)](https://www.npmjs.com/package/@tightknitai/slack-block-kit-validator)
56
[![bundle size](https://img.shields.io/bundlejs/size/@tightknitai/slack-block-kit-validator)](https://bundlejs.com/?q=%40tightknitai%2Fslack-block-kit-validator)
67
[![JSON Schema](https://img.shields.io/badge/JSON%20Schema-2020--12-blue)](https://json-schema.org/draft/2020-12/release-notes)
@@ -157,16 +158,31 @@ Server-side rules that need app-config context or deep equality checks the schem
157158

158159
`validateBlockKit` strips properties whose value is `undefined` before running Ajv. `JSON.stringify` drops these before the payload reaches Slack, so common builder patterns like `value: foo ?? undefined` are no-ops on the wire but would otherwise trip the schema's `additionalProperties: false`. Explicit `null` values are preserved — they survive `JSON.stringify` and may legitimately fail the schema.
159160

160-
## Runtime & bundle considerations
161+
## Entry points
161162

162-
Ajv v8 + ajv-formats weigh ~50–60 KB gzipped, which is fine for Node test runs but noticeable in a Cloudflare Worker bundle. If you want runtime validation inside a Worker, use the package's `compile:standalone` script to emit a self-contained validator that doesn't import Ajv at runtime:
163+
The package ships several entry points so you only pay for what you use:
163164

164-
```sh
165-
pnpm dlx ajv compile --spec=draft2020 --strict=false -c ajv-formats \
166-
-s node_modules/@tightknitai/slack-block-kit-validator/dist/slack-block-kit.schema.json \
167-
-o standalone-validator.js
165+
| Entry | Pulls Ajv? | Use when |
166+
|---|---|---|
167+
| `@tightknitai/slack-block-kit-validator` | yes | The full wrapper. `validateBlockKit`, helpers, schema, types. |
168+
| `@tightknitai/slack-block-kit-validator/helpers` | no | Just the pure helpers — compose with Zod, TypeBox, your own validator. |
169+
| `@tightknitai/slack-block-kit-validator/schema` | no | Just `slackBlockKitSchema` as a JS module. |
170+
| `@tightknitai/slack-block-kit-validator/schema.json` | no | The raw JSON Schema file (for non-JS consumers). |
171+
| `@tightknitai/slack-block-kit-validator/standalone` | no — precompiled | Self-contained validators for environments where Ajv is too heavy (Cloudflare Workers, Deno deploy). Exports `validateBlocks`, `validateModal`, `validateHome`. |
172+
| `@tightknitai/slack-block-kit-validator/types` | no — types only | TypeScript types generated from the schema. `Block`, `SectionBlock`, `ModalView`, etc. |
173+
174+
```ts
175+
// Worker-friendly: no Ajv at runtime
176+
import { validateBlocks } from "@tightknitai/slack-block-kit-validator/standalone";
177+
178+
// Types straight from the schema (always in sync with what the validator accepts)
179+
import type { Block, SectionBlock } from "@tightknitai/slack-block-kit-validator/types";
168180
```
169181

182+
## Runtime & bundle considerations
183+
184+
The wrapper entry pulls in Ajv v8 + ajv-formats. The standalone entry has no runtime dependencies (its Ajv usage is fully inlined at build time) but is larger as a single bundle.
185+
170186
For most apps the recommended pattern is **validate in tests only** — use `validateBlockKit` in unit tests next to your block builders, and let CI catch regressions before they hit production.
171187

172188
## Keeping up with Slack

bench/index.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { Bench } from "tinybench";
2+
import { validateBlockKit } from "../src/validate-block-kit.js";
3+
import { findDuplicateBlockIds } from "../src/helpers/find-duplicate-block-ids.js";
4+
import { checkFocusOnLoadUniqueness } from "../src/helpers/check-focus-on-load-uniqueness.js";
5+
import { checkSurfaceCompatibility } from "../src/helpers/check-surface-compatibility.js";
6+
7+
const buildSimpleBlocks = (n: number): unknown[] =>
8+
Array.from({ length: n }, (_, i) => ({
9+
type: "section",
10+
block_id: `b${i}`,
11+
text: { type: "mrkdwn", text: `Block ${i} — hello *world*` },
12+
}));
13+
14+
const buildMixedBlocks = (n: number): unknown[] =>
15+
Array.from({ length: n }, (_, i) => {
16+
const kind = i % 5;
17+
if (kind === 0) return { type: "divider", block_id: `b${i}` };
18+
if (kind === 1)
19+
return {
20+
type: "section",
21+
block_id: `b${i}`,
22+
text: { type: "mrkdwn", text: `Block ${i} — hello *world*` },
23+
};
24+
if (kind === 2)
25+
return {
26+
type: "header",
27+
block_id: `b${i}`,
28+
text: { type: "plain_text", text: `Section ${i}` },
29+
};
30+
if (kind === 3)
31+
return {
32+
type: "actions",
33+
block_id: `b${i}`,
34+
elements: [
35+
{
36+
type: "button",
37+
text: { type: "plain_text", text: "Go" },
38+
action_id: `a${i}`,
39+
value: String(i),
40+
},
41+
],
42+
};
43+
return {
44+
type: "context",
45+
block_id: `b${i}`,
46+
elements: [{ type: "mrkdwn", text: `Footer ${i}` }],
47+
};
48+
});
49+
50+
const SMALL = buildSimpleBlocks(5);
51+
const TYPICAL = buildMixedBlocks(20);
52+
const LARGE = buildMixedBlocks(50);
53+
54+
const INVALID = [
55+
{ type: "section" },
56+
{ type: "header", text: { type: "plain_text", text: "x".repeat(200) } },
57+
];
58+
59+
const main = async (): Promise<void> => {
60+
const bench = new Bench({ time: 500 });
61+
62+
bench
63+
.add("validateBlockKit / 5-block valid", () => {
64+
validateBlockKit(SMALL);
65+
})
66+
.add("validateBlockKit / 20-block valid", () => {
67+
validateBlockKit(TYPICAL);
68+
})
69+
.add("validateBlockKit / 50-block valid", () => {
70+
validateBlockKit(LARGE);
71+
})
72+
.add("validateBlockKit / 2-block invalid (error formatting)", () => {
73+
validateBlockKit(INVALID);
74+
})
75+
.add("findDuplicateBlockIds / 50 blocks", () => {
76+
findDuplicateBlockIds(LARGE as never);
77+
})
78+
.add("checkFocusOnLoadUniqueness / 50 blocks", () => {
79+
checkFocusOnLoadUniqueness(LARGE);
80+
})
81+
.add("checkSurfaceCompatibility / 50 blocks on message", () => {
82+
checkSurfaceCompatibility(LARGE as never, "message");
83+
});
84+
85+
await bench.run();
86+
87+
console.log("\nresults (ops/sec, higher is better):");
88+
console.table(
89+
bench.tasks.map(({ name, result }) => {
90+
// tinybench's result type is a union that includes an aborted variant
91+
// missing latency/throughput. Narrow on `state` before reading them.
92+
if (result?.state !== "completed") {
93+
return { task: name, "ops/sec": "—", "avg µs/op": "—", samples: 0 };
94+
}
95+
return {
96+
task: name,
97+
"ops/sec": result.throughput.mean.toFixed(0),
98+
"avg µs/op": (result.latency.mean * 1000).toFixed(2),
99+
samples: result.latency.samplesCount,
100+
};
101+
}),
102+
);
103+
};
104+
105+
main().catch((err) => {
106+
console.error(err);
107+
process.exit(1);
108+
});

package.json

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,51 @@
11
{
22
"name": "@tightknitai/slack-block-kit-validator",
3-
"version": "0.1.0-alpha.1",
3+
"version": "0.1.1-alpha.1",
44
"description": "JSON Schema (draft 2020-12) and validation helpers for Slack Block Kit payloads.",
55
"type": "module",
66
"main": "./dist/index.js",
77
"types": "./dist/index.d.ts",
88
"exports": {
99
".": {
10-
"import": "./dist/index.js",
11-
"types": "./dist/index.d.ts"
10+
"types": "./dist/index.d.ts",
11+
"import": "./dist/index.js"
1212
},
13-
"./schema.json": "./dist/slack-block-kit.schema.json"
13+
"./helpers": {
14+
"import": "./dist/helpers.js",
15+
"types": "./dist/helpers.d.ts"
16+
},
17+
"./schema": {
18+
"import": "./dist/schema.js",
19+
"types": "./dist/schema.d.ts"
20+
},
21+
"./schema.json": "./dist/slack-block-kit.schema.json",
22+
"./standalone": {
23+
"import": "./dist/standalone-validator.js",
24+
"types": "./dist/standalone-validator.d.ts"
25+
},
26+
"./types": {
27+
"types": "./dist/types.d.ts",
28+
"import": "./dist/types.js"
29+
}
1430
},
1531
"files": [
1632
"dist"
1733
],
1834
"sideEffects": false,
1935
"scripts": {
20-
"build": "tsc && cp src/slack-block-kit.schema.json dist/slack-block-kit.schema.json",
36+
"generate:types": "tsx scripts/generate-types.ts",
37+
"compile:standalone": "tsx scripts/build-standalone.ts",
38+
"build": "pnpm run generate:types && tsc && cp src/slack-block-kit.schema.json dist/slack-block-kit.schema.json && pnpm run compile:standalone",
2139
"build:clean": "rm -rf dist && pnpm run build",
22-
"typecheck": "tsc --noEmit",
40+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
2341
"test": "vitest run",
2442
"test:watch": "vitest",
2543
"test:coverage": "vitest run --coverage",
44+
"bench": "tsx bench/index.ts",
2645
"lint": "biome check .",
2746
"lint:fix": "biome check --fix .",
2847
"format": "biome format --write .",
2948
"validate-schema": "ajv compile --spec=draft2020 --strict=false -c ajv-formats -s src/slack-block-kit.schema.json",
30-
"compile:standalone": "mkdir -p dist && ajv compile --spec=draft2020 --strict=false -c ajv-formats -s src/slack-block-kit.schema.json -o dist/standalone-validator.js",
3149
"prepare": "lefthook install",
3250
"prepublishOnly": "pnpm run build:clean && pnpm test"
3351
},
@@ -37,9 +55,15 @@
3755
},
3856
"devDependencies": {
3957
"@biomejs/biome": "^2.4.11",
58+
"@types/node": "^25.8.0",
4059
"@vitest/coverage-v8": "^4.1.4",
4160
"ajv-cli": "^5.0.0",
61+
"esbuild": "^0.28.0",
62+
"fast-check": "^4.8.0",
63+
"json-schema-to-typescript": "^15.0.4",
4264
"lefthook": "^2.1.5",
65+
"tinybench": "^6.0.2",
66+
"tsx": "^4.22.0",
4367
"typescript": "^5.9.0",
4468
"vitest": "^4.1.4"
4569
},
@@ -59,10 +83,21 @@
5983
"packageManager": "pnpm@10.32.1",
6084
"keywords": [
6185
"slack",
86+
"slack-api",
87+
"slack-block-kit",
6288
"block-kit",
89+
"blockkit",
90+
"slack-blocks",
91+
"slack-message",
92+
"slack-modal",
6393
"json-schema",
94+
"jsonschema",
95+
"json-schema-validator",
6496
"validator",
65-
"ajv"
97+
"validation",
98+
"ajv",
99+
"typescript",
100+
"schema"
66101
],
67102
"pnpm": {
68103
"overrides": {

0 commit comments

Comments
 (0)