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
17 changes: 8 additions & 9 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,18 @@ jobs:
- name: ✅ Validate
run: yarn validate

- name: ⚡ Build release executable
run: yarn build:release

- name: 📦 Create npm package
run: yarn script:package-npm && yarn copy-files

- name: 🧪 Smoke-test npm package
run: yarn package:smoke
- name: 📦 Build cross-platform package
run: yarn package

- name: 🔎 Verify artifact layout
run: |
test -f dist/portal/browser/index.html
test -x dist/quick-commitlint/bin/quick-commitlint
test -x dist/quick-commitlint/bin/quick-commitlint.js
test -x dist/quick-commitlint/bin/native/darwin-arm64/quick-commitlint
test -x dist/quick-commitlint/bin/native/darwin-x64/quick-commitlint
test -x dist/quick-commitlint/bin/native/linux-arm64/quick-commitlint
test -x dist/quick-commitlint/bin/native/linux-x64/quick-commitlint
test -f dist/quick-commitlint/bin/native/win32-x64/quick-commitlint.exe
test -f dist/quick-commitlint/package.json

- name: 🚀 Publish npm
Expand Down
47 changes: 38 additions & 9 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,49 @@ jobs:
with:
coverage-file: coverage/merged/kcov-merged/cobertura.xml

- name: ⚡ Build release executable
run: yarn build:release

- name: 📦 Create npm package
run: yarn script:package-npm && yarn copy-files

- name: 🧪 Smoke-test npm package
run: yarn package:smoke
- name: 📦 Build cross-platform package
run: yarn package

- name: 🔎 Verify artifact layout
run: |
test -f dist/portal/browser/index.html
test -x dist/quick-commitlint/bin/quick-commitlint
test -x dist/quick-commitlint/bin/quick-commitlint.js
test -x dist/quick-commitlint/bin/native/darwin-arm64/quick-commitlint
test -x dist/quick-commitlint/bin/native/darwin-x64/quick-commitlint
test -x dist/quick-commitlint/bin/native/linux-arm64/quick-commitlint
test -x dist/quick-commitlint/bin/native/linux-x64/quick-commitlint
test -f dist/quick-commitlint/bin/native/win32-x64/quick-commitlint.exe
test -f dist/quick-commitlint/package.json

- name: 🏁 Benchmark performance gate
run: yarn benchmark

package-smoke-test:
name: 📦 Package Smoke Test (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
runs-on: ${{ matrix.os }}

steps:
- name: 🗜️ Checkout
uses: actions/checkout@v7

- name: 🗜️ Setup Node
uses: actions/setup-node@v6
with:
node-version: 24

- name: 🗜️ Setup Zig
uses: mlugg/setup-zig@v2
with:
version: 0.16.0

- name: 🛠️ Install dependencies
uses: ./.github/actions/cached-deps
with:
caching: 'false'

- name: 📦 Build and smoke-test cross-platform package
run: yarn package
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Toolchain

- Use Zig 0.16.0, Node.js 24, and Yarn 1.22.22.
- Node is used for development and publishing only; the published command must execute the Zig binary directly.
- Node is used for development, publishing, and the small platform launcher; all linting executes in the bundled Zig binary.
- Treat `package.json` as the sole source of truth for the release version. Keep `build.zig.zon` at the neutral `0.0.0` placeholder and derive build/test/package expectations from `package.json`.
- Never change the `build.zig.zon` fingerprint.

Expand Down
20 changes: 9 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@

</div>

`quick-commitlint` checks Conventional Commit messages with a native Linux x86-64 executable. Node and Yarn are used to distribute the package, but the installed command starts the Zig binary directly and has no runtime dependencies.
`quick-commitlint` checks Conventional Commit messages with native Zig executables for macOS, Linux, and Windows. A small Node.js launcher selects the executable bundled for the current platform; the package has no runtime npm dependencies.

## ✨ Why use it?

| Feature | Details |
| -------------------------------- | --------------------------------------------------------------------------------- |
| ⚡ **Fast native executable** | Starts in about one millisecond without loading a JavaScript runtime. |
| 📦 **Zero runtime dependencies** | npm installs a standalone, statically linked Zig binary. |
| ⚡ **Fast native linting** | A thin launcher hands all linting work to the bundled Zig executable. |
| 📦 **Zero package dependencies** | All supported native executables ship in the npm package. |
| 🧩 **Familiar configuration** | Uses commitlint-style rule tuples with built-in conventional and Angular presets. |
| 🔒 **Strict and predictable** | Rejects malformed JSON, unknown options, duplicate keys, and invalid UTF-8. |
| 🌍 **International subjects** | Counts Unicode code points while keeping syntax checks fast and ASCII-based. |
Expand All @@ -40,7 +40,7 @@ npm install quick-commitlint --save-dev
yarn add quick-commitlint --dev
```

The initial release supports Linux x86-64 only.
The package includes native executables for macOS arm64/x64, Linux arm64/x64, and Windows x64. The launcher requires Node.js 24 or 25.

## 🪝 Use in a commit hook

Expand Down Expand Up @@ -131,15 +131,12 @@ yarn install
yarn validate
```

Build the publishable Linux x86-64 package and test its tarball:
Build the publishable cross-platform package and test its tarball:

```bash
yarn clean
yarn validate
yarn build:release
yarn script:package-npm
yarn copy-files
yarn package:smoke
yarn package
```

Build artifacts are kept as sibling outputs with no overlap between the website and published native package:
Expand All @@ -149,7 +146,9 @@ dist/
├── portal/
│ └── browser/ # Angular SSG output for GitHub Pages
└── quick-commitlint/
├── bin/quick-commitlint # Native Zig executable
├── bin/
│ ├── quick-commitlint.js # Platform launcher
│ └── native/ # Five platform-specific Zig executables
├── LICENSE
├── README.md
└── package.json # Publishable npm manifest
Expand All @@ -169,7 +168,6 @@ yarn benchmark
The README animation is defined in `docs/terminal-demo.tape` and rendered with the official VHS Docker image:

```bash
yarn build:release
yarn demo:render
```

Expand Down
71 changes: 71 additions & 0 deletions npm/quick-commitlint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env node
'use strict';

const { spawnSync } = require('node:child_process');
const { existsSync } = require('node:fs');
const { join } = require('node:path');

const supportedPlatforms = new Map([
['darwin-arm64', 'quick-commitlint'],
['darwin-x64', 'quick-commitlint'],
['linux-arm64', 'quick-commitlint'],
['linux-x64', 'quick-commitlint'],
['win32-x64', 'quick-commitlint.exe'],
]);

function getBinaryPath(platform = process.platform, arch = process.arch, baseDir = __dirname) {
const id = `${platform}-${arch}`;
const binary = supportedPlatforms.get(id);

if (!binary) {
throw new Error(
`Unsupported platform ${id}. Supported platforms: ${[...supportedPlatforms.keys()].join(', ')}.`,
);
}

return join(baseDir, 'native', id, binary);
}

function resolveBinary(platform = process.platform, arch = process.arch, baseDir = __dirname) {
const id = `${platform}-${arch}`;
const binaryPath = getBinaryPath(platform, arch, baseDir);
if (!existsSync(binaryPath)) {
throw new Error(`The package is missing its ${id} executable at ${binaryPath}.`);
}

return binaryPath;
}

function shouldUseExecve(platform = process.platform, getExecve = () => process.execve) {
if (platform !== 'darwin' && platform !== 'linux') return false;
return typeof getExecve() === 'function';
}

function run(args = process.argv.slice(2)) {
const binaryPath = resolveBinary();
if (shouldUseExecve()) {
process.execve(binaryPath, [binaryPath, ...args], process.env);
}

const result = spawnSync(binaryPath, args, { stdio: 'inherit' });

if (result.error) throw result.error;
if (result.signal) {
process.kill(process.pid, result.signal);
return;
}

process.exitCode = result.status ?? 1;
}

if (require.main === module) {
try {
run();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`quick-commitlint: ${message}`);
process.exitCode = 1;
}
}

module.exports = { getBinaryPath, resolveBinary, run, shouldUseExecve };
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,30 @@
"private": false,
"description": "A very fast native commit message linter built with Zig",
"scripts": {
"validate": "yarn format:check && yarn typecheck:scripts && yarn build && yarn test && yarn test:integration && yarn test:differential && yarn portal:validate",
"validate": "yarn format:check && yarn typecheck:scripts && yarn test:launcher && yarn build && yarn test && yarn test:integration && yarn test:differential && yarn portal:validate",
"format:check": "zig fmt --check src/root.zig src/main.zig src/cli.zig src/config.zig src/lint.zig build.zig",
"typecheck:scripts": "tsc --project scripts/tsconfig.json",
"build": "zig build",
"build:release": "zig build -Doptimize=ReleaseFast -Dtarget=x86_64-linux -Dcpu=baseline",
"build:platforms": "tsx ./scripts/build-platforms.ts",
"build:release": "yarn build:platforms",
"start": "zig build run",
"test": "zig build test",
"test:launcher": "node --test ./scripts/quick-commitlint.test.cjs",
"test:integration": "tsx ./scripts/integration.ts",
"test:differential": "tsx ./scripts/differential.ts",
"coverage:build": "rimraf coverage zig-out/coverage-tests && zig build coverage",
"fuzz:build": "zig build fuzz",
"clean": "rimraf zig-out dist projects/portal/dist",
"prepare": "husky",
"copy-files": "copyfiles -u 2 zig-out/bin/quick-commitlint dist/quick-commitlint/bin && copyfiles README.md LICENSE dist/quick-commitlint",
"package": "yarn build:release && yarn script:package-npm && yarn package:smoke",
"script:package-npm": "tsx ./scripts/package-npm.ts",
"script:package-github": "tsx ./scripts/package-github.ts",
"package:smoke": "tsx ./scripts/smoke-package.ts",
"benchmark": "tsx ./scripts/benchmark.ts",
"demo:render": "bash ./.agents/skills/recreate-terminal-demo/scripts/render-demo.sh",
"version": "auto-changelog -p && git add CHANGELOG.md",
"postversion": "git push && git push --follow-tags",
"publish:beta": "yarn && yarn clean && yarn validate && yarn build:release && yarn script:package-npm && yarn copy-files && npm publish --folder dist/quick-commitlint --tag beta",
"publish:beta": "yarn && yarn clean && yarn validate && yarn package && npm publish --folder dist/quick-commitlint --tag beta",
"release:beta": "npm version prerelease -m \"chore(update): prerelease %s β\"",
"release:patch": "git checkout master && npm version patch -m \"chore(update): patch release %s 🐛\"",
"release:minor": "git checkout master && npm version minor -m \"chore(update): release %s 🚀\"",
Expand All @@ -42,7 +44,7 @@
"portal:build": "ng build portal",
"portal:build:pages": "ng build portal --base-href /quick-commitlint/",
"portal:ssg:verify": "node --experimental-strip-types --no-warnings ./scripts/verify-portal-ssg.ts",
"portal:format:check": "prettier --check README.md package.json \"projects/portal/src/**/*.{ts,html,css,md}\" projects/portal/.postcssrc.json angular.json eslint.config.js projects/portal/eslint.config.js scripts/verify-portal-ssg.ts scripts/package-npm.ts scripts/package-github.ts scripts/smoke-package.ts scripts/benchmark.ts \".github/workflows/*.yml\"",
"portal:format:check": "prettier --check README.md package.json \"projects/portal/src/**/*.{ts,html,css,md}\" projects/portal/.postcssrc.json angular.json eslint.config.js projects/portal/eslint.config.js npm/quick-commitlint.js scripts/verify-portal-ssg.ts scripts/build-platforms.ts scripts/platforms.ts scripts/quick-commitlint.test.cjs scripts/package-npm.ts scripts/package-github.ts scripts/smoke-package.ts scripts/benchmark.ts \".github/workflows/*.yml\"",
"portal:validate": "yarn portal:format:check && yarn portal:lint && yarn portal:test && yarn portal:build && yarn portal:ssg:verify"
},
"devDependencies": {
Expand Down Expand Up @@ -70,7 +72,6 @@
"angular-eslint": "22.1.0",
"auto-changelog": "^2.6.0",
"axe-core": "^4.10.3",
"copyfiles": "^2.4.1",
"eslint": "^10.6.0",
"fs-extra": "^11.3.6",
"husky": "^9.1.7",
Expand Down
8 changes: 4 additions & 4 deletions projects/portal/src/app/docs/content/compatibility.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Commitlint compatibility

Quick Commitlint is a focused native implementation of a tested subset of commitlint behavior. It is designed for projects that want the included Conventional or Angular rules without starting Node.js for every commit.
Quick Commitlint is a focused native implementation of a tested subset of commitlint behavior. It is designed for projects that want the included Conventional or Angular rules without loading commitlint's JavaScript configuration and rule stack for every commit.

It is not a drop-in replacement for every commitlint configuration.

Expand All @@ -19,7 +19,7 @@ The repository runs differential cases against `@commitlint/cli`, `@commitlint/c

| Area | Quick Commitlint | Full commitlint |
| --------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------- |
| Runtime | Native Linux x86-64 Zig binary | Node.js |
| Runtime | Small Node.js launcher plus a native Zig linting executable | Node.js |
| Default configuration | Built-in `conventional` when no config is found | Normally requires a discovered or supplied configuration |
| Configuration files | Strict `.quick-commitlint.json` JSON | Multiple JS/TS/JSON/YAML formats through its configuration stack |
| Presets | Exactly `conventional` and `angular` | Shareable npm configurations and `extends` |
Expand Down Expand Up @@ -58,7 +58,7 @@ Quick Commitlint is a good fit when:

- one of the two built-in presets is close to the desired policy;
- the supported JSON overrides are sufficient;
- fast native startup and a dependency-free installed command matter;
- Linux x86-64 is the deployment platform.
- fast native linting and no additional runtime npm dependencies matter;
- macOS arm64/x64, Linux arm64/x64, or Windows x64 is the deployment platform.

Use full commitlint when the project depends on shareable npm configurations, plugins, custom parsers, custom ignores, or rules outside the supported subset.
9 changes: 3 additions & 6 deletions projects/portal/src/app/docs/content/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ yarn validate
```bash
yarn clean
yarn validate
yarn build:release
yarn script:package-npm
yarn copy-files
yarn package:smoke
yarn package
```

## Benchmark
Expand All @@ -49,9 +46,9 @@ Packaging changes require the release build and smoke test in addition to normal
```bash
yarn build:release
yarn script:package-npm
yarn copy-files
yarn package:smoke
yarn script:package-github
git diff --check
```

The portal builds into `dist/portal`; the native npm package builds into `dist/quick-commitlint`.
The portal builds into `dist/portal`; the cross-platform npm package builds into `dist/quick-commitlint`.
4 changes: 2 additions & 2 deletions projects/portal/src/app/docs/content/getting-started.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Getting started

Quick Commitlint is a native Linux x86-64 command that checks commit messages against a focused set of commitlint-compatible rules. The installed command starts the Zig binary directly and has no Node.js runtime dependencies.
Quick Commitlint checks commit messages against a focused set of commitlint-compatible rules using a native Zig executable. A small Node.js launcher selects the executable bundled for the current platform, with no additional runtime npm dependencies.

## Install

Expand Down Expand Up @@ -41,4 +41,4 @@ Continue with [Usage](docs/usage/), review the accepted [commit message format](

## Platform support

The current package supports Linux x86-64. Node and a package manager are only used to download the package; the command itself is a native executable.
The package supports macOS arm64/x64, Linux arm64/x64, and Windows x64. The launcher requires Node.js 24 or 25 and uses it only to select and spawn the matching bundled Zig executable.
16 changes: 8 additions & 8 deletions projects/portal/src/app/pages/home/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<p class="eyebrow">Native Conventional Commit linting</p>
<h1 id="hero-title" tabindex="-1">Commit message checks at native speed.</h1>
<p class="hero-copy">
Quick Commitlint is a strict, dependency-free linter built with Zig. It starts in about a
millisecond and understands familiar commitlint-style presets and rule tuples.
Quick Commitlint is a strict native linter built with Zig. It understands familiar
commitlint-style presets and rule tuples across macOS, Linux, and Windows.
</p>
<div class="hero-actions">
<a mat-flat-button routerLink="/docs/getting-started">Get started</a>
Expand All @@ -29,17 +29,17 @@ <h2 id="benefits-title">Small tool, focused job</h2>
<mat-card appearance="outlined">
<mat-card-header><mat-card-title>Fast native executable</mat-card-title></mat-card-header>
<mat-card-content>
Starts without loading a JavaScript runtime and reports elapsed lint time with every
result.
A thin platform launcher hands linting to a bundled Zig executable and reports elapsed
lint time with every result.
</mat-card-content>
</mat-card>
<mat-card appearance="outlined">
<mat-card-header
><mat-card-title>Zero runtime dependencies</mat-card-title></mat-card-header
><mat-card-title>Zero package dependencies</mat-card-title></mat-card-header
>
<mat-card-content>
npm distributes a standalone Zig binary. Node and Yarn are only used for delivery and
development.
The npm package includes all supported native executables without installing additional
runtime packages.
</mat-card-content>
</mat-card>
<mat-card appearance="outlined">
Expand All @@ -56,7 +56,7 @@ <h2 id="benefits-title">Small tool, focused job</h2>
<div>
<p class="eyebrow">Ready in one command</p>
<h2 id="install-title">Install as a development tool</h2>
<p>The current package targets Linux x86-64.</p>
<p>Supports macOS arm64/x64, Linux arm64/x64, and Windows x64.</p>
</div>
<div class="install-commands" aria-label="Installation commands">
<pre><code>npm install quick-commitlint --save-dev</code></pre>
Expand Down
Loading
Loading