mirror of
https://github.com/openclaw/openclaw.git
synced 2026-09-28 14:12:28 +08:00
122 lines
4.8 KiB
TypeScript
122 lines
4.8 KiB
TypeScript
// Make Appcast tests cover release appcast script behavior.
|
|
import { spawnSync } from "node:child_process";
|
|
import {
|
|
copyFileSync,
|
|
existsSync,
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
rmSync,
|
|
writeFileSync,
|
|
} from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const scriptPath = "scripts/make_appcast.sh";
|
|
|
|
describe("make_appcast cleanup", () => {
|
|
it("does not reference release notes before their path is assigned", () => {
|
|
const script = readFileSync(scriptPath, "utf8");
|
|
const setupBlock = script.slice(
|
|
script.indexOf('TMP_DIR="$(mktemp -d)"'),
|
|
script.indexOf('cp -f "$ZIP" "$TMP_DIR/$ZIP_NAME"'),
|
|
);
|
|
|
|
expect(setupBlock).toContain('NOTES_HTML=""');
|
|
expect(setupBlock.indexOf('NOTES_HTML=""')).toBeLessThan(
|
|
setupBlock.indexOf("trap cleanup EXIT"),
|
|
);
|
|
expect(setupBlock).toContain(
|
|
'if [[ -n "$NOTES_HTML" && "${KEEP_SPARKLE_NOTES:-0}" != "1" ]]; then',
|
|
);
|
|
expect(setupBlock).toContain('rm -f "$NOTES_HTML"');
|
|
});
|
|
|
|
it.skipIf(process.platform === "win32").each([
|
|
["2026.8.2", undefined],
|
|
["2026.8.2-beta.1", "beta"],
|
|
["2026.8.2.beta.1", "beta"],
|
|
["2026.8.33", "extended-stable"],
|
|
["2026.8.2-alpha.1", "refused"],
|
|
])("generates the correct Sparkle channel for %s with system Bash", (version, channel) => {
|
|
const root = mkdtempSync(path.join(tmpdir(), "openclaw-appcast-"));
|
|
try {
|
|
const scripts = path.join(root, "scripts");
|
|
mkdirSync(scripts);
|
|
copyFileSync(scriptPath, path.join(scripts, "make_appcast.sh"));
|
|
writeFileSync(
|
|
path.join(scripts, "changelog-to-html.sh"),
|
|
"#!/bin/sh\necho '<p>Release notes</p>'\n",
|
|
{ mode: 0o755 },
|
|
);
|
|
const zip = path.join(root, `OpenClaw-${version}.zip`);
|
|
writeFileSync(zip, "fixture archive");
|
|
writeFileSync(path.join(root, "appcast.xml"), "previous feed");
|
|
const generator = path.join(root, "generate-appcast");
|
|
writeFileSync(
|
|
generator,
|
|
`#!/usr/bin/env node
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const args = process.argv.slice(2);
|
|
fs.writeFileSync(path.join(__dirname, "arguments.json"), JSON.stringify(args));
|
|
// Sparkle names the feed after the app's SUFeedURL basename unless -o pins it.
|
|
const output = args.includes("-o") ? args[args.indexOf("-o") + 1] : path.join(args.at(-1), "appcast-arm64.xml");
|
|
fs.writeFileSync(output, '<rss><channel><item><sparkle:shortVersionString>${version}</sparkle:shortVersionString><enclosure sparkle:edSignature="fixture-signature" /></item></channel></rss>');
|
|
`,
|
|
{ mode: 0o755 },
|
|
);
|
|
const result = spawnSync("/bin/bash", [path.join(scripts, "make_appcast.sh"), zip], {
|
|
encoding: "utf8",
|
|
env: {
|
|
...process.env,
|
|
SPARKLE_PRIVATE_KEY_FILE: path.join(root, "unused-fixture-key"),
|
|
SPARKLE_GENERATE_APPCAST: generator,
|
|
SPARKLE_RELEASE_VERSION: version,
|
|
KEEP_SPARKLE_NOTES: "0",
|
|
},
|
|
});
|
|
const argsFile = path.join(root, "arguments.json");
|
|
if (channel === "refused") {
|
|
expect(result.status).not.toBe(0);
|
|
expect(result.stderr).toContain("Alpha releases do not ship via Sparkle");
|
|
expect(existsSync(argsFile)).toBe(false);
|
|
expect(readFileSync(path.join(root, "appcast.xml"), "utf8")).toBe("previous feed");
|
|
return;
|
|
}
|
|
expect(result.status, result.stderr).toBe(0);
|
|
const args = JSON.parse(readFileSync(argsFile, "utf8")) as string[];
|
|
expect(args).toContain("--embed-release-notes");
|
|
if (channel) {
|
|
expect(args.slice(args.indexOf("--channel"), args.indexOf("--channel") + 2)).toEqual([
|
|
"--channel",
|
|
channel,
|
|
]);
|
|
} else {
|
|
expect(args).not.toContain("--channel");
|
|
}
|
|
expect(args).not.toContain("");
|
|
expect(args[args.indexOf("-o") + 1]).toBe(path.join(args.at(-1) ?? "", "appcast.xml"));
|
|
expect(readFileSync(path.join(root, "appcast.xml"), "utf8")).toContain(
|
|
`<sparkle:shortVersionString>${version}</sparkle:shortVersionString>`,
|
|
);
|
|
expect(existsSync(path.join(root, `OpenClaw-${version}.html`))).toBe(false);
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("prefers the host-architecture Sparkle tool and requires a signed entry", () => {
|
|
const script = readFileSync(scriptPath, "utf8");
|
|
|
|
expect(script).toContain('if [[ -n "${SPARKLE_GENERATE_APPCAST:-}" ]]');
|
|
expect(script).toContain('"$ROOT/apps/macos/.build/$host_arch"');
|
|
expect(script).toContain('if [[ -d "$bundled_root" ]]');
|
|
expect(script.indexOf('"$ROOT/apps/macos/.build/$host_arch"')).toBeLessThan(
|
|
script.indexOf("command -v generate_appcast"),
|
|
);
|
|
expect(script).toContain("is missing sparkle:edSignature");
|
|
});
|
|
});
|