Files
awesome-opencode/scripts/export-json.js
T
John SmithandClaude Opus 5.5 bb2827524b feat(readme): fail-closed generation with topic/kind groups, V1/V2 badges, counts and recently added
A single unparseable entry used to blank a whole README category and still exit 0. generate-readme and export-json now validate the whole catalog and refuse to write on any problem, check that every entry renders exactly once, and escape contributor text. Plugins are grouped by primary topic and other categories by kind; plugins show V2 / V1 only / unverified; section headers show counts; a Recently Added table comes from git history. registry.json gains kind, topics, also, compat, repoPath and npmPackage (existing fields unchanged).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-17 13:00:22 -04:00

98 lines
2.8 KiB
JavaScript

#!/usr/bin/env node
/**
* Export registry data as a flat JSON list.
* Usage:
* node scripts/export-json.js --pretty --output dist/registry.json
* node scripts/export-json.js --pretty > dist/registry.json
*
* Fails without writing anything if any entry is invalid.
*/
const fs = require('fs');
const path = require('path');
const { checkCatalog, formatProblems, loadCatalog } = require('./utils/catalog');
function parseArgs(args) {
const outputFlagIndex = args.findIndex((arg) => arg === '--output');
const outputPath = outputFlagIndex !== -1 && args[outputFlagIndex + 1]
? args[outputFlagIndex + 1]
: null;
const pretty = args.includes('--pretty');
return { outputPath, pretty };
}
function sanitizeArray(value, fallback) {
if (Array.isArray(value) && value.length > 0) {
return value;
}
return fallback;
}
// type, productId and the original fields keep their names for downstream consumers (opencode.cafe);
// newer fields are additive.
function mapEntry({ category, id, data }) {
return {
productId: id,
type: category,
displayName: data.name,
repoUrl: data.repo,
repoPath: data.path,
npmPackage: data.npm,
tagline: data.tagline,
description: data.description,
kind: data.kind,
also: sanitizeArray(data.also, undefined),
topics: sanitizeArray(data.topics, []),
compat: data.compat || (category === 'plugins' ? 'unknown' : 'n/a'),
scope: sanitizeArray(data.scope, ['global']),
tags: sanitizeArray(data.tags, []),
homepageUrl: data.homepage,
installation: data.installation,
minVersion: data.min_version
};
}
function stripUndefined(data) {
const cleaned = {};
for (const [key, value] of Object.entries(data)) {
if (value !== undefined) {
cleaned[key] = value;
}
}
return cleaned;
}
function main() {
const { outputPath, pretty } = parseArgs(process.argv.slice(2));
const log = (...args) => (outputPath ? console.log(...args) : console.error(...args));
const { entries, errors } = loadCatalog();
const problems = [...errors, ...checkCatalog(entries)];
if (problems.length > 0) {
log(formatProblems(problems));
throw new Error(`${problems.length} validation problem(s); nothing was exported`);
}
const results = entries.map(e => stripUndefined(mapEntry(e)));
results.sort((a, b) => a.type.localeCompare(b.type) || a.productId.localeCompare(b.productId));
const json = JSON.stringify(results, null, pretty ? 2 : 0);
if (outputPath) {
const outputDir = path.dirname(outputPath);
if (outputDir && outputDir !== '.') {
fs.mkdirSync(outputDir, { recursive: true });
}
fs.writeFileSync(outputPath, json + '\n');
log(`Saved ${results.length} entries to ${outputPath}`);
} else {
process.stdout.write(json);
}
}
try {
main();
} catch (err) {
console.error('Export failed:', err.message);
process.exit(1);
}