Uqudo UIregistry

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-ignore

Any 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:

eslint.config.mjs
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:

package.json
{
  "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.mjs

CLI

node .registry/ignore.mjs prettier --check .

Resolves prettier from the project's node_modules. No network fetch.

Implementation

.registry/ignore.mjs
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()
  }
}

On this page