922a57fe34
Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。 此為重建的乾淨歷史起點(移除曾誤 commit 的 GCP SA 金鑰,舊歷史保留在 richblack/arcrun 與本地 backup 分支)。含: - acr init --self-hosted installer(建 KV/R2 + codeload 拉預編譯 wasm + wrangler deploy + seed recipe) - recipe push 把關(資料外流提醒 + 打通檢查) - 19 個正當零件預編譯 wasm(claude_api/km_writer/kbdb_upsert_block 排除:違反 DECISIONS §1) - CLI / cypher-executor / registry / 完整 SDD Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
122 lines
3.7 KiB
JavaScript
122 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
||
// Backfill component metadata 進 registry index
|
||
// SDD: matrix/arcrun/.agents/specs/component-registry-canon/design.md Phase 1
|
||
//
|
||
// 用法:
|
||
// node scripts/backfill-index.mjs --dry-run # 看會送什麼
|
||
// node scripts/backfill-index.mjs # 真的灌
|
||
//
|
||
// 流程:
|
||
// 1. 掃 ../components/*/component.contract.yaml
|
||
// 2. 解析 YAML(用 zero-dep 簡易 parser,contract 是 well-formed YAML)
|
||
// 3. 對每個 contract POST registry.arcrun.dev/components/index-only
|
||
// 4. 印 success / already_indexed / fail 統計
|
||
|
||
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
||
import { join, dirname } from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
|
||
const __filename = fileURLToPath(import.meta.url);
|
||
const __dirname = dirname(__filename);
|
||
const COMPONENTS_DIR = join(__dirname, '..', 'components');
|
||
const REGISTRY_URL = process.env.REGISTRY_URL ?? 'https://registry.arcrun.dev';
|
||
const DRY_RUN = process.argv.includes('--dry-run');
|
||
|
||
// YAML 是 well-formed contract.yaml,用 js-yaml 解析最穩
|
||
async function parseYaml(text) {
|
||
const { load } = await import('js-yaml');
|
||
return load(text);
|
||
}
|
||
|
||
function listComponents() {
|
||
return readdirSync(COMPONENTS_DIR)
|
||
.filter((name) => {
|
||
const p = join(COMPONENTS_DIR, name);
|
||
return statSync(p).isDirectory();
|
||
})
|
||
.sort();
|
||
}
|
||
|
||
async function readContract(name) {
|
||
const path = join(COMPONENTS_DIR, name, 'component.contract.yaml');
|
||
const text = readFileSync(path, 'utf-8');
|
||
return parseYaml(text);
|
||
}
|
||
|
||
async function postIndexOnly(contract) {
|
||
const res = await fetch(`${REGISTRY_URL}/components/index-only`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ contract }),
|
||
});
|
||
const body = await res.text();
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(body);
|
||
} catch {
|
||
parsed = { raw: body };
|
||
}
|
||
return { status: res.status, body: parsed };
|
||
}
|
||
|
||
async function main() {
|
||
console.log('=== arcrun Component Registry Backfill ===');
|
||
console.log(`Registry: ${REGISTRY_URL}`);
|
||
console.log(`Mode: ${DRY_RUN ? 'DRY RUN' : 'LIVE'}`);
|
||
console.log();
|
||
|
||
const names = listComponents();
|
||
console.log(`Found ${names.length} components in ${COMPONENTS_DIR}`);
|
||
console.log();
|
||
|
||
const stats = { created: 0, already: 0, fail: 0 };
|
||
|
||
for (const name of names) {
|
||
let contract;
|
||
try {
|
||
contract = await readContract(name);
|
||
} catch (e) {
|
||
console.log(` ✗ ${name.padEnd(28)} READ FAIL: ${e.message}`);
|
||
stats.fail++;
|
||
continue;
|
||
}
|
||
|
||
const cid = contract.canonical_id ?? '(no canonical_id)';
|
||
const ver = contract.version ?? '(no version)';
|
||
|
||
if (DRY_RUN) {
|
||
console.log(` → ${name.padEnd(28)} ${cid} ${ver}`);
|
||
continue;
|
||
}
|
||
|
||
try {
|
||
const { status, body } = await postIndexOnly(contract);
|
||
if (status === 200 && body.already_indexed) {
|
||
console.log(` = ${name.padEnd(28)} ${cid} ${ver} [already]`);
|
||
stats.already++;
|
||
} else if (status === 201) {
|
||
console.log(` ✓ ${name.padEnd(28)} ${cid} ${ver} [${body.component_hash_id}]`);
|
||
stats.created++;
|
||
} else {
|
||
console.log(` ✗ ${name.padEnd(28)} ${cid} ${ver} HTTP ${status}: ${JSON.stringify(body).slice(0, 200)}`);
|
||
stats.fail++;
|
||
}
|
||
} catch (e) {
|
||
console.log(` ✗ ${name.padEnd(28)} POST FAIL: ${e.message}`);
|
||
stats.fail++;
|
||
}
|
||
}
|
||
|
||
console.log();
|
||
console.log('=== Summary ===');
|
||
console.log(`Created: ${stats.created}`);
|
||
console.log(`Already indexed: ${stats.already}`);
|
||
console.log(`Failed: ${stats.fail}`);
|
||
process.exit(stats.fail > 0 ? 1 : 0);
|
||
}
|
||
|
||
main().catch((e) => {
|
||
console.error('Fatal:', e);
|
||
process.exit(1);
|
||
});
|