























@@ -19,6 +19,7 @@ const DEFAULT_OUTPUT_NAME = "openclaw-current.tgz";
1919const PACKAGE_URL_DOWNLOAD_TIMEOUT_MS = 60_000;
2020const PACKAGE_URL_MAX_BYTES = 250 * 1024 * 1024;
2121const PACKAGE_URL_MAX_REDIRECTS = 5;
22+export const ARTIFACT_TARBALL_SCAN_MAX_ENTRIES = 10_000;
2223const COMMAND_STDOUT_CAPTURE_MAX_CHARS = 8 * 1024 * 1024;
2324const COMMAND_STDERR_CAPTURE_MAX_CHARS = 128 * 1024;
2425const COMMAND_TIMEOUT_KILL_AFTER_MS = 5_000;
@@ -280,20 +281,6 @@ function formatCapturedCommandOutput(buffer) {
280281281282export const runCommandForTest = run;
282283283-async function walkFiles(dir) {
284-const entries = await fs.readdir(dir, { withFileTypes: true });
285-const files = [];
286-for (const entry of entries) {
287-const absolute = path.join(dir, entry.name);
288-if (entry.isDirectory()) {
289-files.push(...(await walkFiles(absolute)));
290-} else if (entry.isFile()) {
291-files.push(absolute);
292-}
293-}
294-return files;
295-}
296-297284async function sha256(file) {
298285const hash = createHash("sha256");
299286const handle = await fs.open(file, "r");
@@ -326,17 +313,51 @@ async function assertExpectedSha256(file, expected) {
326313}
327314328315async function findSingleTarball(dir) {
329-const files = (await walkFiles(path.resolve(ROOT_DIR, dir)))
330-.filter((file) => /\.t(?:ar\.)?gz$/u.test(path.basename(file)))
331-.toSorted((a, b) => a.localeCompare(b));
332-if (files.length !== 1) {
316+const root = path.resolve(ROOT_DIR, dir);
317+const pending = [root];
318+const tarballs = [];
319+let scannedEntries = 0;
320+321+while (pending.length > 0) {
322+const currentDir = pending.pop();
323+if (!currentDir) {
324+continue;
325+}
326+const handle = await fs.opendir(currentDir);
327+for await (const entry of handle) {
328+scannedEntries += 1;
329+if (scannedEntries > ARTIFACT_TARBALL_SCAN_MAX_ENTRIES) {
330+throw new Error(
331+`source=artifact scan exceeded ${ARTIFACT_TARBALL_SCAN_MAX_ENTRIES} filesystem entries under ${dir}; provide a smaller artifact directory containing exactly one .tgz.`,
332+);
333+}
334+335+const absolute = path.join(currentDir, entry.name);
336+if (entry.isDirectory()) {
337+pending.push(absolute);
338+continue;
339+}
340+if (entry.isFile() && /\.t(?:ar\.)?gz$/u.test(entry.name)) {
341+tarballs.push(absolute);
342+if (tarballs.length > 1) {
343+throw new Error(
344+`source=artifact requires exactly one .tgz under ${dir}; found at least 2: ${tarballs.toSorted((a, b) => a.localeCompare(b)).join(", ")}`,
345+);
346+}
347+}
348+}
349+}
350+351+if (tarballs.length !== 1) {
333352throw new Error(
334-`source=artifact requires exactly one .tgz under ${dir}; found ${files.length}: ${files.join(", ")}`,
353+`source=artifact requires exactly one .tgz under ${dir}; found ${tarballs.length}: ${tarballs.join(", ")}`,
335354);
336355}
337-return files[0];
356+return tarballs[0];
338357}
339358359+export const findSingleTarballForTest = findSingleTarball;
360+340361export async function readArtifactPackageCandidateMetadata(dir) {
341362const metadataPath = path.join(path.resolve(ROOT_DIR, dir), "package-candidate.json");
342363let raw;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。