























@@ -611,6 +611,169 @@ CLAWHUB_PLUGIN_SPEC="${OPENCLAW_PLUGINS_E2E_CLAWHUB_SPEC:-clawhub:openclaw-now4r
611611CLAWHUB_PLUGIN_ID="${OPENCLAW_PLUGINS_E2E_CLAWHUB_ID:-now4real}"
612612export CLAWHUB_PLUGIN_SPEC CLAWHUB_PLUGIN_ID
613613614+start_clawhub_fixture_server() {
615+ local fixture_dir="$1"
616+ local server_log="$fixture_dir/clawhub-fixture.log"
617+ local server_port_file="$fixture_dir/clawhub-fixture-port"
618+ local server_pid_file="$fixture_dir/clawhub-fixture-pid"
619+620+ node - <<'NODE' "$server_port_file" >"$server_log" 2>&1 &
621+const crypto = require("node:crypto");
622+const http = require("node:http");
623+const path = require("node:path");
624+const { createRequire } = require("node:module");
625+626+const portFile = process.argv[2];
627+const requireFromApp = createRequire(path.join(process.cwd(), "package.json"));
628+const JSZip = requireFromApp("jszip");
629+const packageName = "openclaw-now4real";
630+const pluginId = "now4real";
631+const version = "0.1.2";
632+633+async function main() {
634+ const zip = new JSZip();
635+ zip.file(
636+ "package/package.json",
637+ `${JSON.stringify(
638+ {
639+ name: packageName,
640+ version,
641+ openclaw: { extensions: ["./index.js"] },
642+ },
643+ null,
644+ 2,
645+ )}\n`,
646+ { date: new Date(0) },
647+ );
648+ zip.file(
649+ "package/index.js",
650+ `module.exports = {
651+ id: "${pluginId}",
652+ name: "Now 4 Real",
653+ register(api) {
654+ api.registerGatewayMethod("now4real.ping", async () => ({ ok: true }));
655+ },
656+};
657+`,
658+ { date: new Date(0) },
659+ );
660+ zip.file(
661+ "package/openclaw.plugin.json",
662+ `${JSON.stringify(
663+ {
664+ id: pluginId,
665+ configSchema: {
666+ type: "object",
667+ properties: {},
668+ },
669+ },
670+ null,
671+ 2,
672+ )}\n`,
673+ { date: new Date(0) },
674+ );
675+676+ const archive = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
677+ const sha256hash = crypto.createHash("sha256").update(archive).digest("hex");
678+679+ const json = (response, value) => {
680+ response.writeHead(200, { "content-type": "application/json" });
681+ response.end(`${JSON.stringify(value)}\n`);
682+ };
683+684+ const server = http.createServer((request, response) => {
685+ const url = new URL(request.url, "http://127.0.0.1");
686+ if (request.method !== "GET") {
687+ response.writeHead(405);
688+ response.end("method not allowed");
689+ return;
690+ }
691+ if (url.pathname === `/api/v1/packages/${encodeURIComponent(packageName)}`) {
692+ json(response, {
693+ package: {
694+ name: packageName,
695+ displayName: "Now 4 Real",
696+ family: "code-plugin",
697+ channel: "official",
698+ isOfficial: true,
699+ runtimeId: pluginId,
700+ latestVersion: version,
701+ createdAt: 0,
702+ updatedAt: 0,
703+ compatibility: {
704+ pluginApiRange: ">=2026.4.11",
705+ minGatewayVersion: "2026.4.11",
706+ },
707+ },
708+ });
709+ return;
710+ }
711+ if (
712+ url.pathname === `/api/v1/packages/${encodeURIComponent(packageName)}/versions/${version}`
713+ ) {
714+ json(response, {
715+ version: {
716+ version,
717+ createdAt: 0,
718+ changelog: "Fixture package for Docker plugin E2E.",
719+ sha256hash,
720+ compatibility: {
721+ pluginApiRange: ">=2026.4.11",
722+ minGatewayVersion: "2026.4.11",
723+ },
724+ },
725+ });
726+ return;
727+ }
728+ if (url.pathname === `/api/v1/packages/${encodeURIComponent(packageName)}/download`) {
729+ response.writeHead(200, {
730+ "content-type": "application/zip",
731+ "content-length": String(archive.length),
732+ });
733+ response.end(archive);
734+ return;
735+ }
736+ response.writeHead(404, { "content-type": "text/plain" });
737+ response.end(`not found: ${url.pathname}`);
738+ });
739+740+ server.listen(0, "127.0.0.1", () => {
741+ require("node:fs").writeFileSync(portFile, String(server.address().port));
742+ });
743+}
744+745+main().catch((error) => {
746+ console.error(error);
747+ process.exit(1);
748+});
749+NODE
750+ local server_pid="$!"
751+ echo "$server_pid" > "$server_pid_file"
752+753+ for _ in $(seq 1 100); do
754+ if [[ -s "$server_port_file" ]]; then
755+ export OPENCLAW_CLAWHUB_URL="http://127.0.0.1:$(cat "$server_port_file")"
756+ trap 'if [[ -f "'"$server_pid_file"'" ]]; then kill "$(cat "'"$server_pid_file"'")" 2>/dev/null || true; fi' EXIT
757+ return 0
758+ fi
759+ if ! kill -0 "$server_pid" 2>/dev/null; then
760+ cat "$server_log"
761+ return 1
762+ fi
763+ sleep 0.1
764+ done
765+766+ cat "$server_log"
767+ echo "Timed out waiting for ClawHub fixture server." >&2
768+ return 1
769+}
770+771+if [[ -z "${OPENCLAW_CLAWHUB_URL:-}" && -z "${CLAWHUB_URL:-}" ]]; then
772+ # Keep the release-path smoke hermetic; live ClawHub can rate-limit CI.
773+ clawhub_fixture_dir="$(mktemp -d "/tmp/openclaw-clawhub-fixture.XXXXXX")"
774+ start_clawhub_fixture_server "$clawhub_fixture_dir"
775+fi
776+614777node - <<'NODE'
615778const spec = process.env.CLAWHUB_PLUGIN_SPEC;
616779if (!spec?.startsWith("clawhub:")) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。