#!/usr/bin/env node /** * check-registrations.js * 新規アプリがすべての登録ファイルに存在するかを一括検証する * 使い方: node scripts/check-registrations.js [app-id] * app-id を省略すると全アプリを一括チェック */ import { readFileSync, existsSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = join(__dirname, '..'); // 登録が必要なファイル const TARGETS = [ { label: 'projects.json', path: 'posimai-dashboard/src/data/projects.json', check: (content, id) => { const data = JSON.parse(content); return data.projects.some(p => p.id === id); }, }, { label: 'apps/page.tsx (APP_CATEGORIES)', path: 'posimai-dashboard/src/app/apps/page.tsx', check: (content, id) => content.includes(`"${id}"`), }, { label: 'ecosystem/page.tsx (NODES)', path: 'posimai-dashboard/src/app/ecosystem/page.tsx', check: (content, id) => { // id は "posimai-xxx" → node id は "xxx" const shortId = id.replace(/^posimai-/, ''); return content.includes(`id: "${shortId}"`) || content.includes(`id: "${id}"`); }, }, { label: 'roadmap.json', path: 'posimai-roadmap/roadmap.json', check: (content, id) => { const data = JSON.parse(content); return (data.apps ?? []).concat(data.other ?? []).some(a => a.id === id) || content.includes(`"id": "${id}"`); }, }, { label: 'timeline/page.tsx (直近イベント)', path: 'posimai-dashboard/src/app/timeline/page.tsx', check: (content, id) => content.includes(`"${id}"`), }, { label: 'atlas.json', path: 'posimai-atlas/atlas.json', check: (content, id) => { // atlas は posimai-apps ノードにまとめて記載する設計なので // short id (guard) または full id (posimai-guard) が含まれればOK const shortId = id.replace(/^posimai-/, ''); return content.includes(id) || content.includes(shortId); }, }, ]; // チェック対象のアプリID(引数 or 全 posimai-* ディレクトリ) function getAppIds() { const arg = process.argv[2]; if (arg) return [arg]; // projects.json から全IDを取得 const pjson = JSON.parse(readFileSync(join(ROOT, 'posimai-dashboard/src/data/projects.json'), 'utf8')); return pjson.projects.map(p => p.id); } function run() { const appIds = getAppIds(); let anyFail = false; for (const id of appIds) { const missing = []; for (const target of TARGETS) { const filePath = join(ROOT, target.path); if (!existsSync(filePath)) { missing.push(`${target.label} (ファイル不在)`); continue; } const content = readFileSync(filePath, 'utf8'); if (!target.check(content, id)) missing.push(target.label); } if (missing.length > 0) { anyFail = true; console.log(`\n[MISSING] ${id}`); missing.forEach(m => console.log(` - ${m}`)); } else { console.log(`[OK] ${id}`); } } if (anyFail) { console.log('\n登録が不足しているアプリがあります。CLAUDE.md の「アプリ追加・更新時」ルールを確認してください。'); process.exit(1); } else { console.log('\n全アプリの登録は完了しています。'); } } run();