mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 00:47:57 +00:00
* feat: align skills.sh catalog presentation * refactor: share skill detail shell with skills.sh * test: seed skills.sh route fixtures locally * refactor: share full skill detail page view * feat: refine skills.sh detail presentation * feat: refine skills.sh detail metadata * fix: refine skills.sh detail spacing
159 lines
4.2 KiB
TypeScript
159 lines
4.2 KiB
TypeScript
#!/usr/bin/env bun
|
|
|
|
import { spawnSync } from "node:child_process";
|
|
|
|
type SeedOptions = {
|
|
previewName: string | null;
|
|
};
|
|
|
|
type SeedStep = {
|
|
command: string;
|
|
args: string[];
|
|
};
|
|
|
|
const CONVEX_FUNCTIONS_READY_TIMEOUT_MS = 120_000;
|
|
const REACHABILITY_POLL_MS = 500;
|
|
|
|
export function parseSeedArgs(args: string[]): SeedOptions {
|
|
const options: SeedOptions = { previewName: null };
|
|
|
|
for (let index = 0; index < args.length; index += 1) {
|
|
const arg = args[index];
|
|
if (arg === "--preview-name") {
|
|
options.previewName = readValue(args, ++index, arg);
|
|
} else if (arg.startsWith("--preview-name=")) {
|
|
options.previewName = arg.slice("--preview-name=".length);
|
|
} else {
|
|
throw new Error(`Unknown argument: ${arg}`);
|
|
}
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
export function buildSeedSteps(options: SeedOptions): SeedStep[] {
|
|
const convexTargetArgs = options.previewName
|
|
? ["--preview-name", options.previewName]
|
|
: ["--no-push"];
|
|
const corpusTargetArgs = options.previewName ? ["--preview-name", options.previewName] : [];
|
|
|
|
return [
|
|
{
|
|
command: "bunx",
|
|
args: ["convex", "run", ...convexTargetArgs, "devSeed:seedLocalFixtures"],
|
|
},
|
|
{
|
|
command: "bunx",
|
|
args: ["convex", "run", ...convexTargetArgs, "devSeed:seedCanonicalSearchFixture"],
|
|
},
|
|
{
|
|
command: "bun",
|
|
args: ["scripts/public-corpus/seed-public-corpus.ts", ...corpusTargetArgs],
|
|
},
|
|
{
|
|
command: "bun",
|
|
args: ["scripts/public-corpus/seed-catalog-presentation.ts", ...corpusTargetArgs],
|
|
},
|
|
{
|
|
command: "bunx",
|
|
args: ["convex", "run", ...convexTargetArgs, "statsMaintenance:updateGlobalStatsAction"],
|
|
},
|
|
];
|
|
}
|
|
|
|
export function isConvexFunctionUnavailableOutput(output: string) {
|
|
return (
|
|
output.includes("Could not find function for") &&
|
|
output.includes("Did you forget to run `npx convex dev`")
|
|
);
|
|
}
|
|
|
|
export function assertSeedTargetAllowed(
|
|
options: SeedOptions,
|
|
env: NodeJS.ProcessEnv = process.env,
|
|
) {
|
|
if (options.previewName) {
|
|
assertPreviewSeedTargetAllowed(env);
|
|
return;
|
|
}
|
|
|
|
const deployment = env.CONVEX_DEPLOYMENT?.trim();
|
|
if (
|
|
deployment === "anonymous-agent" ||
|
|
deployment?.startsWith("anonymous:") ||
|
|
deployment?.startsWith("local:")
|
|
) {
|
|
return;
|
|
}
|
|
throw new Error("Shared seed without --deployment requires a local Convex deployment");
|
|
}
|
|
|
|
export function assertPreviewSeedTargetAllowed(env: NodeJS.ProcessEnv = process.env) {
|
|
if (!env.CONVEX_DEPLOY_KEY?.trim().startsWith("preview:")) {
|
|
throw new Error("Shared preview seed requires a Convex Preview deploy key");
|
|
}
|
|
}
|
|
|
|
async function runStep(step: SeedStep) {
|
|
if (step.command !== "bunx" || step.args[0] !== "convex" || step.args[1] !== "run") {
|
|
return (
|
|
spawnSync(step.command, step.args, {
|
|
cwd: process.cwd(),
|
|
env: process.env,
|
|
stdio: "inherit",
|
|
}).status ?? 1
|
|
);
|
|
}
|
|
|
|
const startedAt = Date.now();
|
|
while (true) {
|
|
const result = spawnSync(step.command, step.args, {
|
|
cwd: process.cwd(),
|
|
encoding: "utf8",
|
|
env: process.env,
|
|
});
|
|
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
if (result.status === 0) {
|
|
if (output) process.stdout.write(output);
|
|
return 0;
|
|
}
|
|
if (
|
|
!isConvexFunctionUnavailableOutput(output) ||
|
|
Date.now() - startedAt >= CONVEX_FUNCTIONS_READY_TIMEOUT_MS
|
|
) {
|
|
if (output) process.stdout.write(output);
|
|
return result.status ?? 1;
|
|
}
|
|
console.log("Convex functions are not queryable yet; retrying...");
|
|
await sleep(REACHABILITY_POLL_MS);
|
|
}
|
|
}
|
|
|
|
function sleep(ms: number) {
|
|
return new Promise<void>((resolve) => {
|
|
setTimeout(resolve, ms);
|
|
});
|
|
}
|
|
|
|
function readValue(args: string[], index: number, flag: string) {
|
|
const value = args[index]?.trim();
|
|
if (!value) throw new Error(`Missing value for ${flag}`);
|
|
return value;
|
|
}
|
|
|
|
async function main() {
|
|
const options = parseSeedArgs(process.argv.slice(2));
|
|
assertSeedTargetAllowed(options);
|
|
for (const step of buildSeedSteps(options)) {
|
|
const status = await runStep(step);
|
|
if (status !== 0) process.exit(status);
|
|
}
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
main().catch((error: unknown) => {
|
|
console.error(error instanceof Error ? error.message : String(error));
|
|
process.exit(1);
|
|
});
|
|
}
|