Registry ignore helper
One-time ESLint and Prettier hook so installed registry files stay out of format checks.
Every registry item writes .registry/<name>.json — just the paths that item
owns. Those files are overwritten on every reinstall. eslint.config.mjs and
.prettierignore are never touched; they belong to you.
This helper reads every manifest under .registry/ and returns those paths.
Wire it once. After that, installing a new item is enough.
Installation
npx shadcn@latest add @uqudo/registry-ignoreAny other Uqudo item pulls this in as a dependency, so you usually already have
it after the first add.
Usage
Do this once per project. Do not add ignore entries by hand for each new item.
ESLint
Import the helper in eslint.config.mjs and ignore what it returns:
import { getRegistryIgnores } from "./.registry/ignore.mjs"
export default [
{ ignores: getRegistryIgnores() },
// ...your existing config
]Prettier
Prettier cannot import a JS helper from .prettierignore. Point your format
scripts at the helper instead — it writes .registry/.prettierignore from the
manifests and keeps your own .prettierignore:
{
"scripts": {
"format:check": "node .registry/ignore.mjs prettier --check .",
"format:fix": "node .registry/ignore.mjs prettier --write ."
}
}npx shadcn@latest add never rewrites those scripts, eslint.config.mjs, or
.prettierignore.
This repository does not use the helper on itself. The files here are the source of truth and should stay linted and formatted.
API Reference
getRegistryIgnores
import { getRegistryIgnores } from "./.registry/ignore.mjs"
Reads every .registry/*.json manifest and returns the owned paths. Each path
is also emitted with a src/ prefix so a Next.js src/ directory matches.
Prop
Type
writePrettierIgnore
Writes .registry/.prettierignore from the same list. The CLI calls this
before spawning Prettier.
node .registry/ignore.mjsCLI
node .registry/ignore.mjs prettier --check .Resolves prettier from the project's node_modules. No network fetch.
Implementation
import { spawn } from "node:child_process"
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"
import { createRequire } from "node:module"
import { dirname, join } from "node:path"
import { fileURLToPath } from "node:url"
const registryDir = dirname(fileURLToPath(import.meta.url))
function readOwnedPaths() {
const paths = new Set()
for (const name of readdirSync(registryDir)) {
if (!name.endsWith(".json")) continue
let parsed
try {
parsed = JSON.parse(readFileSync(join(registryDir, name), "utf8"))
} catch {
continue
}
const files = Array.isArray(parsed) ? parsed : parsed?.files
if (!Array.isArray(files)) continue
for (const file of files) {
if (typeof file === "string" && file.length > 0) {
paths.add(file)
}
}
}
return [...paths].sort()
}
/**
* Paths owned by installed registry items. `src/` variants are included so a
* Next.js `src/` directory matches the same targets.
*/
export function getRegistryIgnores() {
const ignores = new Set()
for (const file of readOwnedPaths()) {
ignores.add(file)
if (!file.startsWith("src/") && !file.startsWith(".registry/")) {
ignores.add(`src/${file}`)
}
}
return [...ignores]
}
/** Writes `.registry/.prettierignore` from the manifests. Returns its path. */
export function writePrettierIgnore() {
const file = join(registryDir, ".prettierignore")
writeFileSync(file, `${getRegistryIgnores().join("\n")}\n`)
return file
}
function resolvePrettier() {
try {
return createRequire(join(process.cwd(), "package.json")).resolve(
"prettier/bin/prettier.cjs"
)
} catch {
return "prettier"
}
}
function runPrettier(args) {
const extraIgnore = writePrettierIgnore()
const ignoreArgs = existsSync(join(process.cwd(), ".prettierignore"))
? ["--ignore-path", ".prettierignore"]
: []
ignoreArgs.push("--ignore-path", extraIgnore)
const child = spawn(resolvePrettier(), [...ignoreArgs, ...args], {
stdio: "inherit",
})
child.on("exit", (code) => {
process.exit(code ?? 1)
})
}
const invokedDirectly =
Boolean(process.argv[1]) && fileURLToPath(import.meta.url) === process.argv[1]
if (invokedDirectly) {
const args = process.argv.slice(2)
if (args[0] === "prettier") {
runPrettier(args.slice(1))
} else if (args.length > 0) {
runPrettier(args)
} else {
writePrettierIgnore()
}
}