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
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

We love contributions! This guide will help you get started quickly.

> Editing this file? `pnpm lint` checks that every `pnpm` script and repo path
> named here still exists.

## What is this project?

TrakRF Docs is the documentation site for the TrakRF platform, built with [Docusaurus](https://docusaurus.io/). It covers product documentation, API references, guides, and tutorials.
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
"build": "docusaurus build",
"serve": "docusaurus serve --host 0.0.0.0",
"typecheck": "tsc",
"lint": "prettier --check .",
"lint": "prettier --check . && node scripts/check-contributing.mjs",
"lint:fix": "prettier --write .",
"clear": "docusaurus clear",
"swizzle": "docusaurus swizzle",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
"write-heading-ids": "docusaurus write-heading-ids",
"check:contributing": "node scripts/check-contributing.mjs"
},
"dependencies": {
"@docusaurus/core": "3.9.2",
Expand Down
81 changes: 81 additions & 0 deletions scripts/check-contributing.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env node
// Gives CONTRIBUTING.md a consumer.
//
// Nothing read that file, so it rotted: dead `cp` commands, branch conventions
// nobody followed, a rule contradicting CLAUDE.md. Staleness announces itself
// when someone follows an instruction and it fails — but only if someone does.
// This is that someone. Run from `pnpm lint`, so the validate gate catches drift.

import { readFileSync, existsSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join, resolve } from "node:path";

const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const docPath = join(repoRoot, "CONTRIBUTING.md");

// pnpm subcommands that are pnpm's own, not this repo's scripts.
const PNPM_BUILTINS = new Set([
"install",
"add",
"remove",
"update",
"dlx",
"exec",
"run",
"why",
"outdated",
"audit",
"store",
"list",
"test",
"start",
"publish",
]);

// Things that look like paths but are not repo files: URLs, placeholders, and
// absolute paths — a leading slash means an API route (`/api/v1/`), not a file.
const NOT_REPO_PATHS = /^(https?:|\/|<|YOUR_|\.\.?$|node_modules)/;

const text = readFileSync(docPath, "utf8");
const problems = [];

// --- pnpm scripts -----------------------------------------------------------
const pkg = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8"));
const scripts = new Set(Object.keys(pkg.scripts ?? {}));

for (const [, name] of text.matchAll(/\bpnpm\s+([a-z][a-z0-9:_-]*)/gi)) {
if (PNPM_BUILTINS.has(name) || scripts.has(name)) continue;
problems.push(`pnpm script "${name}" is referenced but not in package.json`);
}

// --- repo-relative paths in inline code spans -------------------------------
// Only inline spans: fenced blocks and prose carry examples, not claims.
const inlineCode = [...text.matchAll(/`([^`\n]+)`/g)].map((m) => m[1].trim());

// A span is a path claim only if it ends in a known file extension or a
// trailing slash. Anything looser also matches branch-name examples such as
// `docs/tra-0000-update-getting-started`, which are illustrations rather than
// claims — and a check that fails on a correct file is a check someone turns
// off.
const FILE_EXT = /\.(md|json|ts|tsx|js|mjs|yml|yaml|sql|sh)$/;

for (const span of inlineCode) {
const looksLikePath = FILE_EXT.test(span) || span.endsWith("/");
if (!looksLikePath || NOT_REPO_PATHS.test(span)) continue;
if (!existsSync(join(repoRoot, span))) {
problems.push(`path \`${span}\` is referenced but does not exist`);
}
}

// --- report -----------------------------------------------------------------
if (problems.length > 0) {
console.error("CONTRIBUTING.md references things that no longer exist:\n");
for (const p of [...new Set(problems)]) console.error(` - ${p}`);
console.error(
"\nFix the file or the reference. This check exists because nothing else\n" +
"reads CONTRIBUTING.md, so drift here is otherwise silent.",
);
process.exit(1);
}

console.log("CONTRIBUTING.md: all referenced scripts and paths resolve.");
Loading