posimai-root/scripts/check-registrations.js

106 lines
3.3 KiB
JavaScript
Raw Normal View History

#!/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();