|

feat(package): add ScriptCtx and build_script_cmd (host branches)

Author: Chris Tusa <chris.tusa@leafscale.com>
Date: Jul 05, 2026 16:57
Changeset: 5172a0f6f7e0e2dc0c0388bdc52123face178657
Branch: default

Diff

diff -r a7bebb3624be -r 5172a0f6f7e0 src/core/package.reef
--- a/src/core/package.reef	Tue Jun 09 17:32:18 2026 -0500
+++ b/src/core/package.reef	Sun Jul 05 16:57:05 2026 -0500
@@ -63,6 +63,12 @@
     // Install files with old manifest for upgrade config protection
     fn install_files_upgrade(src_dir: string, root: string, old_entries: [mtree.ManifestEntry], old_entry_count: int): bool
 
+    // Script execution context (chroot-confined maintainer scripts)
+    type ScriptCtx
+    fn new_script_ctx(root: string, do_chroot: bool): ScriptCtx
+    fn script_wants_chroot(root: string, chroot_scripts: bool): bool
+    fn build_script_cmd(ctx: ScriptCtx, script_path: string, script_name: string, args: string, is_sh: bool): string
+
     // Install scripts support (version passed as $VERSION to scripts)
     fn has_scripts(pkg_dir: string): bool
     fn run_pre_install(pkg_dir: string, version: string): bool
@@ -504,6 +510,49 @@
     return dir.dir_exists(scripts_dir)
 end has_scripts
 
+// Context describing where a package's maintainer scripts run.
+// root == "/"  -> normal install on the live system
+// do_chroot    -> confine scripts to root via chroot (root must be != "/")
+type ScriptCtx = struct
+    root: string
+    do_chroot: bool
+end ScriptCtx
+
+// Construct a ScriptCtx.
+fn new_script_ctx(root: string, do_chroot: bool): ScriptCtx
+    return ScriptCtx{ root: root, do_chroot: do_chroot }
+end new_script_ctx
+
+// Pure predicate: should scripts be chrooted for this root + config?
+fn script_wants_chroot(root: string, chroot_scripts: bool): bool
+    if str.equals(root, "/")
+        return false
+    end if
+    return chroot_scripts
+end script_wants_chroot
+
+// The single place a maintainer-script shell command is assembled.
+// is_sh true  -> run under zsh; false -> execute the file directly.
+// Phase 1: only the non-chroot (host) branch exists.
+fn build_script_cmd(ctx: ScriptCtx, script_path: string, script_name: string, args: string, is_sh: bool): string
+    // $CORAL_ROOT value, from the script's point of view:
+    //   confined -> "/"  (added in Task 8);  host -> ctx.root
+    let coral_root = ctx.root
+
+    mut body = ""
+    if is_sh
+        body = "zsh \"" + script_path + "\""
+    else
+        body = "\"" + script_path + "\""
+    end if
+
+    mut cmd = "CORAL_ROOT=\"" + coral_root + "\" " + body
+    if str.length(args) > 0
+        cmd = cmd + " " + args
+    end if
+    return cmd
+end build_script_cmd
+
 // Run a script from the .SCRIPTS directory
 fn run_script(pkg_dir: string, script_name: string, script_args: string): bool
     let scripts_dir = path.join_path(pkg_dir, ".SCRIPTS")
diff -r a7bebb3624be -r 5172a0f6f7e0 test/script_cmd_test.reef
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/script_cmd_test.reef	Sun Jul 05 16:57:05 2026 -0500
@@ -0,0 +1,49 @@
+// Unit tests for package.build_script_cmd — asserts exact command strings.
+import core.package
+import core.str
+
+mut g_fail = 0
+
+proc expect(label: string, got: string, want: string)
+    if str.equals(got, want)
+        println("PASS: " + label)
+    else
+        g_fail = g_fail + 1
+        println("FAIL: " + label)
+        println("   got:  " + got)
+        println("   want: " + want)
+    end if
+end expect
+
+proc main()
+    println("=== build_script_cmd tests ===")
+
+    // Normal install: root "/", no chroot -> CORAL_ROOT="/" prefix
+    let normal = package.new_script_ctx("/", false)
+    expect("normal .sh",
+        package.build_script_cmd(normal, "/tmp/x/.SCRIPTS/post-install.sh", "post-install.sh", "\"1.0\"", true),
+        "CORAL_ROOT=\"/\" zsh \"/tmp/x/.SCRIPTS/post-install.sh\" \"1.0\"")
+
+    // Host-fallback: root "/mnt", do_chroot false -> CORAL_ROOT="/mnt" prefix, host path
+    let host = package.new_script_ctx("/mnt", false)
+    expect("host-fallback .sh",
+        package.build_script_cmd(host, "/tmp/x/.SCRIPTS/post-install.sh", "post-install.sh", "\"1.0\"", true),
+        "CORAL_ROOT=\"/mnt\" zsh \"/tmp/x/.SCRIPTS/post-install.sh\" \"1.0\"")
+
+    // Bare executable (is_sh false): no zsh wrapper
+    expect("normal bare",
+        package.build_script_cmd(normal, "/tmp/x/.SCRIPTS/post-install", "post-install", "\"1.0\"", false),
+        "CORAL_ROOT=\"/\" \"/tmp/x/.SCRIPTS/post-install\" \"1.0\"")
+
+    // Empty args: no trailing space
+    expect("normal no-args",
+        package.build_script_cmd(normal, "/tmp/x/.SCRIPTS/pre-remove.sh", "pre-remove.sh", "", true),
+        "CORAL_ROOT=\"/\" zsh \"/tmp/x/.SCRIPTS/pre-remove.sh\"")
+
+    println("")
+    if g_fail == 0
+        println("RESULT: ALL PASS")
+    else
+        println("RESULT: FAIL (" + str.from_int(g_fail) + " failures)")
+    end if
+end main