mirror of https://github.com/openclaw/openclaw.git
213 lines
6.4 KiB
JavaScript
213 lines
6.4 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { loadChannelConfigSurfaceModule } from "./load-channel-config-surface.ts";
|
|
|
|
const GENERATED_BY = "scripts/generate-bundled-channel-config-metadata.ts";
|
|
const DEFAULT_OUTPUT_PATH = "src/config/bundled-channel-config-metadata.generated.ts";
|
|
|
|
type BundledPluginSource = {
|
|
dirName: string;
|
|
pluginDir: string;
|
|
manifestPath: string;
|
|
manifest: {
|
|
id: string;
|
|
channels?: unknown;
|
|
name?: string;
|
|
description?: string;
|
|
} & Record<string, unknown>;
|
|
packageJson?: Record<string, unknown>;
|
|
};
|
|
|
|
const { collectBundledPluginSources } = (await import(
|
|
new URL("./lib/bundled-plugin-source-utils.mjs", import.meta.url).href
|
|
)) as {
|
|
collectBundledPluginSources: (params?: {
|
|
repoRoot?: string;
|
|
requirePackageJson?: boolean;
|
|
}) => BundledPluginSource[];
|
|
};
|
|
|
|
const { formatGeneratedModule } = (await import(
|
|
new URL("./lib/format-generated-module.mjs", import.meta.url).href
|
|
)) as {
|
|
formatGeneratedModule: (
|
|
source: string,
|
|
options: {
|
|
repoRoot: string;
|
|
outputPath: string;
|
|
errorLabel: string;
|
|
},
|
|
) => string;
|
|
};
|
|
|
|
const { writeGeneratedOutput } = (await import(
|
|
new URL("./lib/generated-output-utils.mjs", import.meta.url).href
|
|
)) as {
|
|
writeGeneratedOutput: (params: {
|
|
repoRoot: string;
|
|
outputPath: string;
|
|
next: string;
|
|
check?: boolean;
|
|
}) => {
|
|
changed: boolean;
|
|
wrote: boolean;
|
|
outputPath: string;
|
|
};
|
|
};
|
|
|
|
type BundledChannelConfigMetadata = {
|
|
pluginId: string;
|
|
channelId: string;
|
|
label?: string;
|
|
description?: string;
|
|
schema: Record<string, unknown>;
|
|
uiHints?: Record<string, unknown>;
|
|
};
|
|
|
|
function resolveChannelConfigSchemaModulePath(rootDir: string): string | null {
|
|
const candidates = [
|
|
path.join(rootDir, "src", "config-schema.ts"),
|
|
path.join(rootDir, "src", "config-schema.js"),
|
|
path.join(rootDir, "src", "config-schema.mts"),
|
|
path.join(rootDir, "src", "config-schema.mjs"),
|
|
];
|
|
for (const candidate of candidates) {
|
|
if (fs.existsSync(candidate)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function resolvePackageChannelMeta(source: BundledPluginSource) {
|
|
const openclawMeta =
|
|
source.packageJson &&
|
|
typeof source.packageJson === "object" &&
|
|
!Array.isArray(source.packageJson) &&
|
|
"openclaw" in source.packageJson
|
|
? (source.packageJson.openclaw as Record<string, unknown> | undefined)
|
|
: undefined;
|
|
const channelMeta =
|
|
openclawMeta &&
|
|
typeof openclawMeta.channel === "object" &&
|
|
openclawMeta.channel &&
|
|
!Array.isArray(openclawMeta.channel)
|
|
? (openclawMeta.channel as Record<string, unknown>)
|
|
: undefined;
|
|
return channelMeta;
|
|
}
|
|
|
|
function resolveRootLabel(source: BundledPluginSource, channelId: string): string | undefined {
|
|
const channelMeta = resolvePackageChannelMeta(source);
|
|
if (channelMeta?.id === channelId && typeof channelMeta.label === "string") {
|
|
return channelMeta.label.trim();
|
|
}
|
|
if (typeof source.manifest?.name === "string" && source.manifest.name.trim()) {
|
|
return source.manifest.name.trim();
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function resolveRootDescription(
|
|
source: BundledPluginSource,
|
|
channelId: string,
|
|
): string | undefined {
|
|
const channelMeta = resolvePackageChannelMeta(source);
|
|
if (channelMeta?.id === channelId && typeof channelMeta.blurb === "string") {
|
|
return channelMeta.blurb.trim();
|
|
}
|
|
if (typeof source.manifest?.description === "string" && source.manifest.description.trim()) {
|
|
return source.manifest.description.trim();
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function formatTypeScriptModule(source: string, outputPath: string, repoRoot: string): string {
|
|
return formatGeneratedModule(source, {
|
|
repoRoot,
|
|
outputPath,
|
|
errorLabel: "bundled channel config metadata",
|
|
});
|
|
}
|
|
|
|
export async function collectBundledChannelConfigMetadata(params?: { repoRoot?: string }) {
|
|
const repoRoot = path.resolve(params?.repoRoot ?? process.cwd());
|
|
const sources = collectBundledPluginSources({ repoRoot, requirePackageJson: true });
|
|
const entries: BundledChannelConfigMetadata[] = [];
|
|
|
|
for (const source of sources) {
|
|
const channelIds = Array.isArray(source.manifest?.channels)
|
|
? source.manifest.channels.filter(
|
|
(entry: unknown): entry is string => typeof entry === "string" && entry.trim().length > 0,
|
|
)
|
|
: [];
|
|
if (channelIds.length === 0) {
|
|
continue;
|
|
}
|
|
const modulePath = resolveChannelConfigSchemaModulePath(source.pluginDir);
|
|
if (!modulePath) {
|
|
continue;
|
|
}
|
|
const surface = await loadChannelConfigSurfaceModule(modulePath, { repoRoot });
|
|
if (!surface?.schema) {
|
|
continue;
|
|
}
|
|
for (const channelId of channelIds) {
|
|
const label = resolveRootLabel(source, channelId);
|
|
const description = resolveRootDescription(source, channelId);
|
|
entries.push({
|
|
pluginId: String(source.manifest.id),
|
|
channelId,
|
|
...(label ? { label } : {}),
|
|
...(description ? { description } : {}),
|
|
schema: surface.schema,
|
|
...(Object.keys(surface.uiHints ?? {}).length > 0 ? { uiHints: surface.uiHints } : {}),
|
|
});
|
|
}
|
|
}
|
|
|
|
return entries.toSorted((left, right) => left.channelId.localeCompare(right.channelId));
|
|
}
|
|
|
|
export async function writeBundledChannelConfigMetadataModule(params?: {
|
|
repoRoot?: string;
|
|
outputPath?: string;
|
|
check?: boolean;
|
|
}) {
|
|
const repoRoot = path.resolve(params?.repoRoot ?? process.cwd());
|
|
const outputPath = params?.outputPath ?? DEFAULT_OUTPUT_PATH;
|
|
const entries = await collectBundledChannelConfigMetadata({ repoRoot });
|
|
const next = formatTypeScriptModule(
|
|
`// Auto-generated by ${GENERATED_BY}. Do not edit directly.
|
|
|
|
export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = ${JSON.stringify(entries, null, 2)} as const;
|
|
`,
|
|
outputPath,
|
|
repoRoot,
|
|
);
|
|
return writeGeneratedOutput({
|
|
repoRoot,
|
|
outputPath,
|
|
next,
|
|
check: params?.check,
|
|
});
|
|
}
|
|
|
|
if (import.meta.url === new URL(process.argv[1] ?? "", "file://").href) {
|
|
const check = process.argv.includes("--check");
|
|
const result = await writeBundledChannelConfigMetadataModule({ check });
|
|
if (!result.changed) {
|
|
process.exitCode = 0;
|
|
} else if (check) {
|
|
console.error(
|
|
`[bundled-channel-config-metadata] stale generated output at ${path.relative(process.cwd(), result.outputPath)}`,
|
|
);
|
|
process.exitCode = 1;
|
|
} else {
|
|
console.log(
|
|
`[bundled-channel-config-metadata] wrote ${path.relative(process.cwd(), result.outputPath)}`,
|
|
);
|
|
}
|
|
}
|