chore(repo): dog-food @websnacksjs/conventional in this repo

This commit is contained in:
M. George Hansen 2025-08-19 18:35:47 +12:00
parent 99a7717410
commit a941565528
Signed by: mgeorgehansen
SSH key fingerprint: SHA256:JlIGiQLPyQ2RHTH3a2oVlb20Xkh9Glr8DUF4YTXHJxM
6 changed files with 177 additions and 5121 deletions

4
.gitignore vendored
View file

@ -35,6 +35,10 @@
!/flake.nix !/flake.nix
!/flake.lock !/flake.lock
### Repository configuration ###
!/.husky/commit-msg
!/conventional.config.js
### Workspace configuration ### ### Workspace configuration ###
!/package.json !/package.json
!/package-lock.json !/package-lock.json

1
.husky/commit-msg Normal file
View file

@ -0,0 +1 @@
npx conventional commit-msg "$1"

125
conventional.config.js Normal file
View file

@ -0,0 +1,125 @@
import * as fs from "node:fs/promises";
import { defineConfig } from "@websnacksjs/conventional";
/**
* @param {import("@websnacksjs/conventional").CommitMessage} message
* @returns {void}
*/
const validateRepoScopedCommit = (message) => {
const supportedTypes = ["docs", "chore"];
if (!supportedTypes.includes(message.type)) {
throw new Error(
`${JSON.stringify(message.type)} is not a supported repo-scoped commit type ` +
`(must be one of ${JSON.stringify(supportedTypes).replaceAll(",", ", ")})`,
);
}
};
const packages = await fs.readdir(new URL("./packages", import.meta.url));
const validScopes = ["repo", ...packages];
/**
* @param {import("@websnacksjs/conventional").CommitMessage} message
* @returns {void}
*/
const validatePackageScopedCommit = (message) => {
const supportedTypes = ["feat", "fix", "docs", "test", "chore"];
if (!supportedTypes.includes(message.type)) {
throw new Error(
`${JSON.stringify(message.type)} is not a supported package-scoped commit type ` +
`(must be one of ${JSON.stringify(supportedTypes).replaceAll(",", ", ")})`,
);
}
};
/**
* @param {string} value
* @returns {void}
*/
const validateUrl = (value) => {
try {
new URL(value);
} catch {
const error = new Error(
`expected valid URL but got ${JSON.stringify(value)}`,
);
Error.captureStackTrace(error, validateUrl);
throw error;
}
};
/**
* @param {import("@websnacksjs/conventional").Footer[]} footers
* @returns void
*/
const validateFooters = (footers) => {
/** @type {string[]} */
const unsupportedFooters = [];
/** @type {{footer: string, reason: Error}[]} */
const invalidFooters = [];
for (const { key, value } of footers) {
try {
switch (key) {
case "Merge-request": {
validateUrl(value);
break;
}
default: {
unsupportedFooters.push(key);
}
}
} catch (error) {
if (!(error instanceof Error)) {
throw new Error(
`caught unexpected non-error value ${JSON.stringify(error)}`,
);
}
invalidFooters.push({ footer: key, reason: error });
}
}
const errorMessageParts = [];
if (unsupportedFooters.length > 0) {
let message = `unspported footers in message:`;
for (const footer of unsupportedFooters) {
message += `\n\t- ${footer}`;
}
errorMessageParts.push(message);
}
if (invalidFooters.length > 0) {
let message = `invalid footers in message:`;
for (const { footer, reason } of invalidFooters) {
message += `\n\t- ${footer}: ${reason.message}`;
}
errorMessageParts.push(message);
}
if (errorMessageParts.length > 0) {
const message = errorMessageParts.join("\n\n");
throw new Error(message);
}
};
export default defineConfig({
validateCommitMessage(message) {
if (!message.scope) {
throw new Error(
`missing required scope (use "repo" for monorepo-related commits or "@websnacksjs/:package" for package-specific commits)`,
);
}
if (message.scope === "repo") {
validateRepoScopedCommit(message);
} else if (packages.includes(message.scope)) {
validatePackageScopedCommit(message);
} else {
throw new Error(
[
`scope ${JSON.stringify(message.scope)} is unsupported`,
`(try one of ${JSON.stringify(validScopes).replace(",", ", ")})`,
].join(" "),
);
}
validateFooters(message.footers);
},
});

5151
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,9 +3,11 @@
"./packages/*" "./packages/*"
], ],
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"build": "tsc --build", "build": "tsc --build",
"clean": "git clean -dxi --exclude .direnv" "clean": "git clean -dxi --exclude .direnv",
"prepare": "husky"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "=2.1.3", "@biomejs/biome": "=2.1.3",
@ -13,6 +15,8 @@
"@tsconfig/strictest": "^2.0.5", "@tsconfig/strictest": "^2.0.5",
"@types/deno": "^2.3.0", "@types/deno": "^2.3.0",
"@types/node": "^24.2.0", "@types/node": "^24.2.0",
"@websnacksjs/conventional": "^0.1.0",
"husky": "^9.1.7",
"typescript": "^5.9.2" "typescript": "^5.9.2"
} }
} }

View file

@ -1,5 +1,14 @@
{ {
"files": [], "extends": ["./tsconfig.common.json"],
"compilerOptions": {
"rootDir": ".",
"module": "nodenext",
"moduleResolution": "nodenext",
"allowJs": true,
"noEmit": true,
"checkJs": true
},
"include": ["./*.config.js"],
"references": [ "references": [
{ {
"path": "./packages/conventional" "path": "./packages/conventional"