Product design skills

scripts/detect_block_themes.mjs

A supporting file of the wp-patterns skill, shown as source.

import fs from "node:fs";
import path from "node:path";

const DEFAULT_IGNORES = new Set([
  ".git",
  "node_modules",
  "vendor",
  "dist",
  "build",
  "coverage",
  ".next",
  ".turbo",
]);

function statSafe(p) {
  try {
    return fs.statSync(p);
  } catch {
    return null;
  }
}

function existsDir(p) {
  const st = statSafe(p);
  return Boolean(st && st.isDirectory());
}

function readJsonSafe(p) {
  try {
    return JSON.parse(fs.readFileSync(p, "utf8"));
  } catch {
    return null;
  }
}

function findFilesRecursive(repoRoot, predicate, { maxFiles = 6000, maxDepth = 10 } = {}) {
  const results = [];
  const queue = [{ dir: repoRoot, depth: 0 }];
  let visited = 0;

  while (queue.length > 0) {
    const { dir, depth } = queue.shift();
    if (depth > maxDepth) continue;

    let entries;
    try {
      entries = fs.readdirSync(dir, { withFileTypes: true });
    } catch {
      continue;
    }

    for (const ent of entries) {
      const fullPath = path.join(dir, ent.name);
      if (ent.isDirectory()) {
        if (DEFAULT_IGNORES.has(ent.name)) continue;
        queue.push({ dir: fullPath, depth: depth + 1 });
        continue;
      }
      if (!ent.isFile()) continue;

      visited += 1;
      if (visited > maxFiles) return { results, truncated: true };
      if (predicate(fullPath)) results.push(fullPath);
    }
  }

  return { results, truncated: false };
}

function summarizeTheme(repoRoot, themeJsonPath) {
  const json = readJsonSafe(themeJsonPath);
  const rel = path.relative(repoRoot, themeJsonPath);
  const rootDir = path.dirname(rel);

  const templatesDir = path.join(repoRoot, rootDir, "templates");
  const partsDir = path.join(repoRoot, rootDir, "parts");
  const patternsDir = path.join(repoRoot, rootDir, "patterns");
  const stylesDir = path.join(repoRoot, rootDir, "styles");

  const hasTemplates = existsDir(templatesDir);
  const hasParts = existsDir(partsDir);

  return {
    themeRoot: rootDir,
    themeJson: rel,
    version: typeof json?.version === "number" ? json.version : null,
    hasTemplates,
    hasParts,
    hasPatterns: existsDir(patternsDir),
    hasStyles: existsDir(stylesDir),
    isBlockTheme: hasTemplates || hasParts,
  };
}

function main() {
  const repoRoot = process.cwd();

  const { results: themeJsonFiles, truncated } = findFilesRecursive(repoRoot, (p) => path.basename(p) === "theme.json", {
    maxFiles: 8000,
    maxDepth: 12,
  });

  const themes = themeJsonFiles.map((p) => summarizeTheme(repoRoot, p));

  const report = {
    tool: { name: "detect_block_themes", version: "0.1.0" },
    repoRoot,
    truncated,
    count: themes.length,
    themes,
  };

  process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
}

main();