From 9c1d2c3b3a94a5c0a903fda3f00d8e76cae83ae8 Mon Sep 17 00:00:00 2001
From: justinjohn0306 <34035011+justinjohn0306@users.noreply.github.com>
Date: Fri, 9 Oct 2026 01:58:29 +0530
Subject: [PATCH] Add scripts/check-vba-roundtrip.sh: verify VBA preservation
on a real file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Builds a macro-enabled .xlsm with a fake xl/vbaProject.bin, runs it through
`gridcraft-cli run`, and asserts the blob comes back byte-identical with the
macro-enabled content type and vbaProject relationship intact — the
open → edit → save path a user would exercise by hand.
---
scripts/check-vba-roundtrip.sh | 86 ++++++++++++++++++++++++++++++++++
1 file changed, 86 insertions(+)
create mode 100644 scripts/check-vba-roundtrip.sh
diff --git a/scripts/check-vba-roundtrip.sh b/scripts/check-vba-roundtrip.sh
new file mode 100644
index 000000000..5aef3a5f8
--- /dev/null
+++ b/scripts/check-vba-roundtrip.sh
@@ -0,0 +1,86 @@
+#!/usr/bin/env bash
+# Verify that GridCraft preserves VBA macros (xl/vbaProject.bin) across a round-trip.
+#
+# bash scripts/check-vba-roundtrip.sh
+#
+# What it does: builds a macro-enabled .xlsm containing a fake vbaProject.bin, runs it
+# through `gridcraft-cli run`, and checks the blob comes back byte-identical with the
+# macro-enabled content type + relationship intact. Requires python3 (zip) and a
+# release build of gridcraft-cli.
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+CLI="$ROOT/target/release/gridcraft-cli.exe"
+[ -x "$CLI" ] || CLI="$ROOT/target/release/gridcraft-cli"
+PYTHON="${PYTHON:-python3}"
+
+# A fresh temp dir. Under Git Bash (MSYS) the shell's /tmp is not a path the native
+# Windows python.exe or the engine understand when embedded in JSON, so convert to the
+# platform-native form when cygpath is available.
+TMP="$(mktemp -d)"
+if command -v cygpath >/dev/null 2>&1; then
+ NATIVE_TMP="$(cygpath -w "$TMP")" # for python (native Windows path)
+ JSON_TMP="$(cygpath -m "$TMP")" # for JSON params (forward slashes: no escaping)
+else
+ NATIVE_TMP="$TMP"
+ JSON_TMP="$TMP"
+fi
+
+# 1. Build a macro-enabled .xlsm with a fake OLE/CFB vbaProject.bin.
+"$PYTHON" - "$NATIVE_TMP" <<'PY'
+import zipfile, os, sys
+tmp = sys.argv[1]
+main_ct = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"
+ct = (''
+ ''
+ ''
+ f''
+ ''
+ '')
+root = (''
+ '')
+wb = (''
+ '')
+rel = (''
+ ''
+ '')
+sheet = ''
+vba = b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1" + b"HELLO-VBA-PROJECT-BYTES" + bytes(range(32))
+with zipfile.ZipFile(os.path.join(tmp, "macro_demo.xlsm"), "w", zipfile.ZIP_DEFLATED) as z:
+ z.writestr("[Content_Types].xml", ct)
+ z.writestr("_rels/.rels", root)
+ z.writestr("xl/workbook.xml", wb)
+ z.writestr("xl/_rels/workbook.xml.rels", rel)
+ z.writestr("xl/worksheets/sheet1.xml", sheet)
+ z.writestr("xl/vbaProject.bin", vba)
+open(os.path.join(tmp, "vba_expected.bin"), "wb").write(vba)
+print("built", os.path.join(tmp, "macro_demo.xlsm"))
+PY
+
+# 2. Round-trip through the engine.
+echo "--- view.macros on the input (what the app sees) ---"
+"$CLI" run --in "$NATIVE_TMP/macro_demo.xlsm" --cmd 'view.macros={}' | tail -1
+"$CLI" run --in "$NATIVE_TMP/macro_demo.xlsm" --cmd "file.save={\"path\":\"$JSON_TMP/macro_out.xlsm\"}" | tail -1
+
+# 3. Assert the blob survived byte-identical with correct package wiring.
+"$PYTHON" - "$NATIVE_TMP" <<'PY'
+import zipfile, os, sys
+tmp = sys.argv[1]
+exp = open(os.path.join(tmp, "vba_expected.bin"), "rb").read()
+with zipfile.ZipFile(os.path.join(tmp, "macro_out.xlsm")) as z:
+ got = z.read("xl/vbaProject.bin")
+ ct = z.read("[Content_Types].xml").decode()
+ rels = z.read("xl/_rels/workbook.xml.rels").decode()
+ok = (got == exp
+ and "macroEnabled.main+xml" in ct
+ and "/xl/vbaProject.bin" in ct
+ and "relationships/vbaProject" in rels)
+print("blob byte-identical:", got == exp)
+print("macro-enabled main content type:", "macroEnabled.main+xml" in ct)
+print("vbaProject content-type override:", "/xl/vbaProject.bin" in ct)
+print("vbaProject relationship:", "relationships/vbaProject" in rels)
+sys.exit(0 if ok else 1)
+PY
+
+echo "PASS: the VBA project survived the round-trip."
\ No newline at end of file