mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-24 07:01:49 +00:00
fix: restore lightweight root help startup
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
function dedupe(values: string[]): string[] {
|
||||
const seen = new Set<string>();
|
||||
@@ -16,7 +15,8 @@ function dedupe(values: string[]): string[] {
|
||||
return out;
|
||||
}
|
||||
|
||||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const scriptPath = fileURLToPath(import.meta.url);
|
||||
const scriptDir = path.dirname(scriptPath);
|
||||
const rootDir = path.resolve(scriptDir, "..");
|
||||
const distDir = path.join(rootDir, "dist");
|
||||
const outputPath = path.join(distDir, "cli-startup-metadata.json");
|
||||
@@ -38,13 +38,15 @@ type ExtensionChannelEntry = {
|
||||
label: string;
|
||||
};
|
||||
|
||||
function readBundledChannelCatalogIds(): string[] {
|
||||
export function readBundledChannelCatalogIds(
|
||||
extensionsDirOverride: string = extensionsDir,
|
||||
): string[] {
|
||||
const entries: ExtensionChannelEntry[] = [];
|
||||
for (const dirEntry of readdirSync(extensionsDir, { withFileTypes: true })) {
|
||||
for (const dirEntry of readdirSync(extensionsDirOverride, { withFileTypes: true })) {
|
||||
if (!dirEntry.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
const packageJsonPath = path.join(extensionsDir, dirEntry.name, "package.json");
|
||||
const packageJsonPath = path.join(extensionsDirOverride, dirEntry.name, "package.json");
|
||||
try {
|
||||
const raw = readFileSync(packageJsonPath, "utf8");
|
||||
const parsed = JSON.parse(raw) as {
|
||||
@@ -76,19 +78,7 @@ function readBundledChannelCatalogIds(): string[] {
|
||||
.map((entry) => entry.id);
|
||||
}
|
||||
|
||||
async function renderBundledRootHelpText(): Promise<string> {
|
||||
const bundleName = readdirSync(distDir).find(
|
||||
(entry) => entry.startsWith("root-help-") && entry.endsWith(".js"),
|
||||
);
|
||||
if (!bundleName) {
|
||||
throw new Error("No root-help bundle found in dist; cannot write CLI startup metadata.");
|
||||
}
|
||||
const moduleUrl = pathToFileURL(path.join(distDir, bundleName)).href;
|
||||
const mod = (await import(moduleUrl)) as { outputRootHelp?: () => void };
|
||||
if (typeof mod.outputRootHelp !== "function") {
|
||||
throw new Error(`Bundle ${bundleName} does not export outputRootHelp.`);
|
||||
}
|
||||
|
||||
async function captureStdout(action: () => void | Promise<void>): Promise<string> {
|
||||
let output = "";
|
||||
const originalWrite = process.stdout.write.bind(process.stdout);
|
||||
const captureWrite: typeof process.stdout.write = ((chunk: string | Uint8Array) => {
|
||||
@@ -97,28 +87,61 @@ async function renderBundledRootHelpText(): Promise<string> {
|
||||
}) as typeof process.stdout.write;
|
||||
process.stdout.write = captureWrite;
|
||||
try {
|
||||
mod.outputRootHelp();
|
||||
await action();
|
||||
} finally {
|
||||
process.stdout.write = originalWrite;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
const catalog = readBundledChannelCatalogIds();
|
||||
const channelOptions = dedupe([...CORE_CHANNEL_ORDER, ...catalog]);
|
||||
const rootHelpText = await renderBundledRootHelpText();
|
||||
export async function renderBundledRootHelpText(
|
||||
distDirOverride: string = distDir,
|
||||
): Promise<string> {
|
||||
const bundleName = readdirSync(distDirOverride).find(
|
||||
(entry) => entry.startsWith("root-help-") && entry.endsWith(".js"),
|
||||
);
|
||||
if (!bundleName) {
|
||||
throw new Error("No root-help bundle found in dist; cannot write CLI startup metadata.");
|
||||
}
|
||||
const moduleUrl = pathToFileURL(path.join(distDirOverride, bundleName)).href;
|
||||
const mod = (await import(moduleUrl)) as { outputRootHelp?: () => void | Promise<void> };
|
||||
if (typeof mod.outputRootHelp !== "function") {
|
||||
throw new Error(`Bundle ${bundleName} does not export outputRootHelp.`);
|
||||
}
|
||||
|
||||
mkdirSync(distDir, { recursive: true });
|
||||
writeFileSync(
|
||||
outputPath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
generatedBy: "scripts/write-cli-startup-metadata.ts",
|
||||
channelOptions,
|
||||
rootHelpText,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
return captureStdout(async () => {
|
||||
await mod.outputRootHelp?.();
|
||||
});
|
||||
}
|
||||
|
||||
export async function writeCliStartupMetadata(options?: {
|
||||
distDir?: string;
|
||||
outputPath?: string;
|
||||
extensionsDir?: string;
|
||||
}): Promise<void> {
|
||||
const resolvedDistDir = options?.distDir ?? distDir;
|
||||
const resolvedOutputPath = options?.outputPath ?? outputPath;
|
||||
const resolvedExtensionsDir = options?.extensionsDir ?? extensionsDir;
|
||||
const catalog = readBundledChannelCatalogIds(resolvedExtensionsDir);
|
||||
const channelOptions = dedupe([...CORE_CHANNEL_ORDER, ...catalog]);
|
||||
const rootHelpText = await renderBundledRootHelpText(resolvedDistDir);
|
||||
|
||||
mkdirSync(resolvedDistDir, { recursive: true });
|
||||
writeFileSync(
|
||||
resolvedOutputPath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
generatedBy: "scripts/write-cli-startup-metadata.ts",
|
||||
channelOptions,
|
||||
rootHelpText,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === scriptPath) {
|
||||
await writeCliStartupMetadata();
|
||||
}
|
||||
|
||||
78
test/scripts/write-cli-startup-metadata.test.ts
Normal file
78
test/scripts/write-cli-startup-metadata.test.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
renderBundledRootHelpText,
|
||||
writeCliStartupMetadata,
|
||||
} from "../../scripts/write-cli-startup-metadata.ts";
|
||||
|
||||
function createTempDir(prefix: string): string {
|
||||
return mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
}
|
||||
|
||||
describe("write-cli-startup-metadata", () => {
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("captures async root help bundle output", async () => {
|
||||
const distDir = createTempDir("openclaw-root-help-");
|
||||
tempDirs.push(distDir);
|
||||
writeFileSync(
|
||||
path.join(distDir, "root-help-async.js"),
|
||||
[
|
||||
"export async function outputRootHelp() {",
|
||||
" await Promise.resolve();",
|
||||
" process.stdout.write('OpenClaw help\\n');",
|
||||
"}",
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
await expect(renderBundledRootHelpText(distDir)).resolves.toBe("OpenClaw help\n");
|
||||
});
|
||||
|
||||
it("writes startup metadata with populated root help text", async () => {
|
||||
const tempRoot = createTempDir("openclaw-startup-metadata-");
|
||||
tempDirs.push(tempRoot);
|
||||
const distDir = path.join(tempRoot, "dist");
|
||||
const extensionsDir = path.join(tempRoot, "extensions");
|
||||
const outputPath = path.join(distDir, "cli-startup-metadata.json");
|
||||
|
||||
mkdirSync(distDir, { recursive: true });
|
||||
mkdirSync(path.join(extensionsDir, "matrix"), { recursive: true });
|
||||
writeFileSync(
|
||||
path.join(distDir, "root-help-fixture.js"),
|
||||
"export async function outputRootHelp() { process.stdout.write('Usage: openclaw\\n'); }\n",
|
||||
"utf8",
|
||||
);
|
||||
writeFileSync(
|
||||
path.join(extensionsDir, "matrix", "package.json"),
|
||||
JSON.stringify({
|
||||
openclaw: {
|
||||
channel: {
|
||||
id: "matrix",
|
||||
order: 120,
|
||||
label: "Matrix",
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
await writeCliStartupMetadata({ distDir, outputPath, extensionsDir });
|
||||
|
||||
const written = JSON.parse(readFileSync(outputPath, "utf8")) as {
|
||||
channelOptions: string[];
|
||||
rootHelpText: string;
|
||||
};
|
||||
expect(written.channelOptions).toContain("matrix");
|
||||
expect(written.rootHelpText).toBe("Usage: openclaw\n");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user