openclaw/scripts/write-official-channel-cata...

105 lines
3.3 KiB
JavaScript

import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
import { writeTextFileIfChanged } from "./runtime-postbuild-shared.mjs";
export const OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH = "dist/channel-catalog.json";
function isRecord(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function trimString(value) {
return typeof value === "string" ? value.trim() : "";
}
function toCatalogInstall(value, packageName) {
const install = isRecord(value) ? value : {};
const npmSpec = trimString(install.npmSpec) || packageName;
if (!npmSpec) {
return null;
}
const localPath = trimString(install.localPath);
const defaultChoice = trimString(install.defaultChoice);
return {
npmSpec,
...(localPath ? { localPath } : {}),
...(defaultChoice === "npm" || defaultChoice === "local" ? { defaultChoice } : {}),
};
}
function buildCatalogEntry(packageJson) {
if (!isRecord(packageJson)) {
return null;
}
const packageName = trimString(packageJson.name);
const manifest = isRecord(packageJson.openclaw) ? packageJson.openclaw : null;
const release = manifest && isRecord(manifest.release) ? manifest.release : null;
const channel = manifest && isRecord(manifest.channel) ? manifest.channel : null;
if (!packageName || !channel || release?.publishToNpm !== true) {
return null;
}
const install = toCatalogInstall(manifest.install, packageName);
if (!install) {
return null;
}
const version = trimString(packageJson.version);
const description = trimString(packageJson.description);
return {
name: packageName,
...(version ? { version } : {}),
...(description ? { description } : {}),
openclaw: {
channel,
install,
},
};
}
export function buildOfficialChannelCatalog(params = {}) {
const repoRoot = params.cwd ?? params.repoRoot ?? process.cwd();
const extensionsRoot = path.join(repoRoot, "extensions");
const entries = [];
if (!fs.existsSync(extensionsRoot)) {
return { entries };
}
for (const dirent of fs.readdirSync(extensionsRoot, { withFileTypes: true })) {
if (!dirent.isDirectory()) {
continue;
}
const packageJsonPath = path.join(extensionsRoot, dirent.name, "package.json");
if (!fs.existsSync(packageJsonPath)) {
continue;
}
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
const entry = buildCatalogEntry(packageJson);
if (entry) {
entries.push(entry);
}
} catch {
// Ignore invalid package metadata and keep generating the rest of the catalog.
}
}
entries.sort((left, right) => {
const leftId = trimString(left.openclaw?.channel?.id) || left.name;
const rightId = trimString(right.openclaw?.channel?.id) || right.name;
return leftId.localeCompare(rightId);
});
return { entries };
}
export function writeOfficialChannelCatalog(params = {}) {
const repoRoot = params.cwd ?? params.repoRoot ?? process.cwd();
const outputPath = path.join(repoRoot, OFFICIAL_CHANNEL_CATALOG_RELATIVE_PATH);
const catalog = buildOfficialChannelCatalog({ repoRoot });
writeTextFileIfChanged(outputPath, `${JSON.stringify(catalog, null, 2)}\n`);
}
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
writeOfficialChannelCatalog();
}