@@ -247,14 +247,117 @@ jobs:
247247 trap append_telegram_summary EXIT
248248
249249 if [[ -n "${PACKAGE_ARTIFACT_NAME// }" ]]; then
250- mapfile -t package_tgzs < <(find .artifacts/telegram-package-under-test -type f -name "*.tgz" | sort)
251- if [[ "${#package_tgzs[@]}" -ne 1 ]]; then
252- echo "package artifact ${PACKAGE_ARTIFACT_NAME} must contain exactly one .tgz; found ${#package_tgzs[@]}" >&2
253- exit 1
250+ package_dir=".artifacts/telegram-package-under-test"
251+ manifest="${package_dir}/preflight-manifest.json"
252+ if [[ -f "${manifest}" ]]; then
253+ package_tgz="$(
254+ node - "${manifest}" "${package_dir}" <<'NODE'
255+ const crypto = require("node:crypto");
256+ const childProcess = require("node:child_process");
257+ const fs = require("node:fs");
258+ const path = require("node:path");
259+ const [manifestPath, packageDir] = process.argv.slice(2);
260+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
261+ const entries = [
262+ {
263+ packageName: "openclaw",
264+ packageVersion: manifest.packageVersion,
265+ tarballName: manifest.tarballName,
266+ tarballSha256: manifest.tarballSha256,
267+ },
268+ ...(Array.isArray(manifest.dependencyTarballs) ? manifest.dependencyTarballs : []),
269+ ];
270+ const packageNames = new Set();
271+ const tarballNames = new Set();
272+ for (const entry of entries) {
273+ if (
274+ !entry.packageName ||
275+ !entry.packageVersion ||
276+ !entry.tarballName ||
277+ !entry.tarballSha256 ||
278+ entry.tarballName !== path.basename(entry.tarballName)
279+ ) {
280+ throw new Error("package artifact manifest contains invalid tarball metadata");
281+ }
282+ if (packageNames.has(entry.packageName) || tarballNames.has(entry.tarballName)) {
283+ throw new Error("package artifact manifest contains duplicate package metadata");
284+ }
285+ packageNames.add(entry.packageName);
286+ tarballNames.add(entry.tarballName);
287+ const tarballPath = path.join(packageDir, entry.tarballName);
288+ if (!fs.existsSync(tarballPath)) {
289+ throw new Error(`package artifact is missing ${entry.tarballName}`);
290+ }
291+ const digest = crypto.createHash("sha256").update(fs.readFileSync(tarballPath)).digest("hex");
292+ if (digest !== entry.tarballSha256) {
293+ throw new Error(`package artifact digest mismatch for ${entry.packageName}`);
294+ }
295+ const packageJson = JSON.parse(
296+ childProcess.execFileSync("tar", ["-xOf", tarballPath, "package/package.json"], {
297+ encoding: "utf8",
298+ }),
299+ );
300+ if (packageJson.name !== entry.packageName || packageJson.version !== entry.packageVersion) {
301+ throw new Error(`package artifact metadata mismatch for ${entry.packageName}`);
302+ }
303+ }
304+ const artifactTarballs = fs
305+ .readdirSync(packageDir, { withFileTypes: true })
306+ .filter((entry) => entry.isFile() && entry.name.endsWith(".tgz"))
307+ .map((entry) => entry.name);
308+ if (
309+ artifactTarballs.length !== tarballNames.size ||
310+ artifactTarballs.some((name) => !tarballNames.has(name))
311+ ) {
312+ throw new Error("package artifact tarball set does not match preflight manifest");
313+ }
314+ process.stdout.write(path.join(packageDir, manifest.tarballName));
315+ NODE
316+ )"
317+ export OPENCLAW_NPM_TELEGRAM_PACKAGE_DIR="${package_dir}"
318+ else
319+ mapfile -t package_tgzs < <(find "${package_dir}" -type f -name "*.tgz" | sort)
320+ if [[ "${#package_tgzs[@]}" -ne 1 ]]; then
321+ echo "package artifact ${PACKAGE_ARTIFACT_NAME} without a preflight manifest must contain exactly one tgz; found ${#package_tgzs[@]}" >&2
322+ exit 1
323+ fi
324+ package_tgz="${package_tgzs[0]}"
325+ candidate_manifest="${package_dir}/package-candidate.json"
326+ node - "${package_tgz}" "${candidate_manifest}" <<'NODE'
327+ const crypto = require("node:crypto");
328+ const childProcess = require("node:child_process");
329+ const fs = require("node:fs");
330+ const path = require("node:path");
331+ const [tarballPath, manifestPath] = process.argv.slice(2);
332+ const packageJson = JSON.parse(
333+ childProcess.execFileSync("tar", ["-xOf", tarballPath, "package/package.json"], {
334+ encoding: "utf8",
335+ }),
336+ );
337+ if (packageJson.name !== "openclaw" || typeof packageJson.version !== "string") {
338+ throw new Error("package artifact tgz is not a versioned openclaw package");
339+ }
340+ if (fs.existsSync(manifestPath)) {
341+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
342+ const expectedTarballName = path.basename(manifest.tarball || "");
343+ if (
344+ manifest.name !== "openclaw" ||
345+ manifest.version !== packageJson.version ||
346+ expectedTarballName !== path.basename(tarballPath) ||
347+ typeof manifest.sha256 !== "string"
348+ ) {
349+ throw new Error("package candidate manifest does not match the OpenClaw tarball");
350+ }
351+ const digest = crypto.createHash("sha256").update(fs.readFileSync(tarballPath)).digest("hex");
352+ if (digest !== manifest.sha256) {
353+ throw new Error("package candidate digest mismatch");
354+ }
355+ }
356+ NODE
254357 fi
255- export OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ="${package_tgzs[0] }"
358+ export OPENCLAW_NPM_TELEGRAM_PACKAGE_TGZ="${package_tgz }"
256359 if [[ -z "${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL// }" ]]; then
257- export OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL="$(basename "${package_tgzs[0] }")"
360+ export OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL="$(basename "${package_tgz }")"
258361 fi
259362 elif [[ -z "${OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL// }" ]]; then
260363 export OPENCLAW_NPM_TELEGRAM_PACKAGE_LABEL="${OPENCLAW_NPM_TELEGRAM_PACKAGE_SPEC}"
0 commit comments