diff --git a/scripts/create-dmg.sh b/scripts/create-dmg.sh index 7393f3a4c38..13051fc3f16 100755 --- a/scripts/create-dmg.sh +++ b/scripts/create-dmg.sh @@ -49,6 +49,46 @@ DMG_APP_POS="${DMG_APP_POS:-125 160}" DMG_APPS_POS="${DMG_APPS_POS:-375 160}" DMG_EXTRA_SECTORS="${DMG_EXTRA_SECTORS:-2048}" +require_integer_list() { + local name="$1" + local raw="$2" + local expected_count="$3" + local values=() + local value + + if [[ "$raw" == *$'\n'* || "$raw" == *$'\r'* ]]; then + echo "Error: $name must be a single line of integer values: '$raw'" >&2 + exit 1 + fi + + read -r -a values <<< "$raw" + if [[ "${#values[@]}" -ne "$expected_count" ]]; then + echo "Error: $name must contain $expected_count integer value(s): '$raw'" >&2 + exit 1 + fi + + for value in "${values[@]}"; do + if [[ ! "$value" =~ ^-?[0-9]+$ ]]; then + echo "Error: $name must contain only integer values: '$raw'" >&2 + exit 1 + fi + done +} + +require_positive_integer() { + local name="$1" + local raw="$2" + if [[ ! "$raw" =~ ^[1-9][0-9]*$ ]]; then + echo "Error: $name must be a positive integer: '$raw'" >&2 + exit 1 + fi +} + +require_integer_list DMG_WINDOW_BOUNDS "$DMG_WINDOW_BOUNDS" 4 +require_integer_list DMG_APP_POS "$DMG_APP_POS" 2 +require_integer_list DMG_APPS_POS "$DMG_APPS_POS" 2 +require_positive_integer DMG_ICON_SIZE "$DMG_ICON_SIZE" + to_applescript_list4() { local raw="$1" echo "$raw" | awk '{ printf "%s, %s, %s, %s", $1, $2, $3, $4 }' diff --git a/test/scripts/create-dmg.test.ts b/test/scripts/create-dmg.test.ts index d35e6d5aba8..2fcb3dfad55 100644 --- a/test/scripts/create-dmg.test.ts +++ b/test/scripts/create-dmg.test.ts @@ -265,6 +265,42 @@ describe.runIf(process.platform === "darwin")("create-dmg ownership boundaries", expect(readFileSync(tools.hdiutilLog, "utf8")).not.toContain("detach"); }); + it("fails before image creation when Finder layout values are malformed", () => { + const app = makeValidApp(); + const outputDir = mkdtempSync(path.join(tmpdir(), "openclaw-create-dmg-output-")); + tempDirs.push(outputDir); + const output = path.join(outputDir, "OpenClaw.dmg"); + const tools = makeFakeDmgTools(); + + const result = runScript([app, output], { + ...tools.env, + DMG_WINDOW_BOUNDS: "400 nope 900 420", + }); + + expect(result.status).toBe(1); + expect(result.stderr).toContain("DMG_WINDOW_BOUNDS must contain only integer values"); + expect(existsSync(output)).toBe(false); + expect(existsSync(tools.hdiutilLog) ? readFileSync(tools.hdiutilLog, "utf8") : "").toBe(""); + }); + + it("fails before image creation when Finder layout values span multiple lines", () => { + const app = makeValidApp(); + const outputDir = mkdtempSync(path.join(tmpdir(), "openclaw-create-dmg-output-")); + tempDirs.push(outputDir); + const output = path.join(outputDir, "OpenClaw.dmg"); + const tools = makeFakeDmgTools(); + + const result = runScript([app, output], { + ...tools.env, + DMG_WINDOW_BOUNDS: "400 100 900 420\nnope", + }); + + expect(result.status).toBe(1); + expect(result.stderr).toContain("DMG_WINDOW_BOUNDS must be a single line"); + expect(existsSync(output)).toBe(false); + expect(existsSync(tools.hdiutilLog) ? readFileSync(tools.hdiutilLog, "utf8") : "").toBe(""); + }); + it("preserves an existing output when verification fails", () => { const app = makeValidApp(); const outputDir = mkdtempSync(path.join(tmpdir(), "openclaw-create-dmg-output-"));