Zillowe FoundationZillowe Documentation

Lua API Cookbook

Common patterns and best practices for writing Zoi package definitions.

This cookbook provides a collection of common patterns and "recipes" for handling various scenarios when creating .pkg.lua files.

Platform Mapping

Upstream projects often use different naming conventions for OS and Architecture than Zoi's standard (linux-amd64, macos-arm64, etc.). You can use a Lua table to map Zoi's system info to upstream values.

local platform_map = {
    ["linux-amd64"]   = "x86_64-unknown-linux-gnu",
    ["linux-arm64"]   = "aarch64-unknown-linux-gnu",
    ["macos-amd64"]   = "x86_64-apple-darwin",
    ["macos-arm64"]   = "aarch64-apple-darwin",
    ["windows-amd64"] = "x86_64-pc-windows-msvc",
}

local current_platform = SYSTEM.OS .. "-" .. SYSTEM.ARCH
local target_triple = platform_map[current_platform]

function prepare()
    local url = "https://example.com/releases/download/v" .. PKG.version .. "/" .. target_triple .. ".tar.gz"
    UTILS.EXTRACT(url, "source")
end

Conditional Build Types

Packages that support both source and pre-compiled builds should use the BUILD_TYPE variable to branch logic in prepare and package.

function prepare()
    if BUILD_TYPE == "pre-compiled" then
        local ext = (SYSTEM.OS == "windows") and "zip" or "tar.gz"
        local url = string.format("https://example.com/bin-%s.%s", SYSTEM.OS, ext)
        UTILS.EXTRACT(url, "bin")
    elseif BUILD_TYPE == "source" then
        cmd("git clone " .. PKG.git .. " source")
    end
end

function package()
    if BUILD_TYPE == "source" then
        cmd("cd source && make")
        zcp("source/my-app", "${pkgstore}/bin/my-app")
    else
        zcp("bin/my-app", "${pkgstore}/bin/my-app")
    end
end

Dynamic Versioning from GitHub

Instead of hardcoding a version, you can fetch the latest release tag directly from GitHub using Zoi's utility functions.

local version = ZOI.VERSION or UTILS.FETCH.GITHUB.LATEST.release({ repo = "Zillowe/Zoi" })

metadata({
    name = "zoi",
    version = version,
    -- ...
})

Cloning Specific Git Tags or Revisions

When building from source, you often need to clone a specific version of a repository rather than the default main or master branch. Since Zoi executes standard shell commands via the cmd() function, you can pass standard Git arguments.

Cloning a Specific Tag

To clone a specific tag efficiently (without downloading the entire git history), use the --branch and --depth 1 flags:

function prepare()
    -- Clone a specific tag (e.g. v1.2.3) based on the package version
    local tag = "v" .. PKG.version
    cmd("git clone --depth 1 --branch " .. tag .. " " .. PKG.git .. " source")
end

Checking out a Specific Commit Revision

Git's clone --branch flag does not support specific commit hashes. To build from an exact commit revision, you must clone the repository first and then check out the commit:

local commit_hash = "a1b2c3d4e5f6g7h8i9j0"

function prepare()
    -- Clone the repository and checkout the specific commit
    cmd("git clone " .. PKG.git .. " source")
    cmd("cd source && git checkout " .. commit_hash)
end

Advanced Verification

When a project provides a central checksums.txt file, you can use UTILS.PARSE.checksumFile to extract the relevant hash for your specific platform archive.

function verify()
    if BUILD_TYPE == "pre-compiled" then
        local checksum_url = "https://example.com/releases/download/v" .. PKG.version .. "/checksums.txt"
        local content = UTILS.FETCH.url(checksum_url)

        -- Zoi platform archives follow a pattern
        local filename = string.format("my-app-%s-%s.tar.gz", SYSTEM.OS, SYSTEM.ARCH)
        local hash = UTILS.PARSE.checksumFile(content, filename)

        if not hash then return false end

        return verifyHash(BUILD_DIR .. "/" .. filename, "sha256-" .. hash)
    end
    return true
end

Bundling Local Assets

If your package requires a configuration file or icon that is stored alongside the .pkg.lua file, use ${pkgluadir} to reference it.

-- File structure:
--   my-pkg/
--     my-pkg.pkg.lua
--     default-config.toml

function package()
    -- Copy local config to the package store
    zcp("${pkgluadir}/default-config.toml", "${pkgstore}/share/default-config.toml")

    -- Copy a local script to user home (use with caution)
    zcp("${pkgluadir}/init-script.sh", "${usrhome}/.config/my-app/init.sh")
end

Multi-stage Build Logic

For complex builds, you might want to install build-time dependencies into a temporary directory or use specific output paths.

function package()
    -- Create a temporary install prefix inside BUILD_DIR
    local prefix = BUILD_DIR .. "/install_root"

    -- Run build and install to that prefix
    cmd("cd source && ./configure --prefix=" .. prefix)
    cmd("cd source && make install")

    -- Selectively copy only what you need to the Zoi store
    zcp("install_root/bin", "${pkgstore}/bin")
    zcp("install_root/lib", "${pkgstore}/lib")
end

Shared Logic with INCLUDE

Keep your package definitions DRY (Don't Repeat Yourself) by including external Lua scripts. This is especially useful for large lists of hashes or common build functions.

-- shared_build.pkg.lua
function common_make()
  cmd("make clean")
  cmd("make -j2")
end

-- package.pkg.lua
INCLUDE("shared_build.pkg.lua")

function package()
  -- Use function defined in shared_build.pkg.lua
  common_make()
  zcp("output/bin", "${pkgstore}/bin")
end

Use the .pkg.lua extension for included files if you want them to be clearly associated with Zoi packaging, or .lua for general utility scripts. Note that INCLUDE uses paths relative to the current script.

Using Hooks for System Integration

Use hooks to perform actions that zcp cannot do, such as updating a system database or reloading a daemon.

hooks({
    post_install = {
        linux = {
            "systemctl daemon-reload",
            "systemctl enable --now my-app.service"
        },
        default = { "echo 'Installation complete!'" }
    },
    pre_remove = {
        linux = { "systemctl disable --now my-app.service" }
    }
})

Shell Completions

Use zshell() to bundle shell completion scripts with your package. Zoi automatically symlinks them into the user's global completions directory and cleans them up on uninstall.

-- my-cli.pkg.lua
metadata({
  name = "my-cli",
  repo = "community",
  version = "1.0.0",
  description = "A CLI tool with shell completions",
  bins = { "my-cli" },
  types = { "pre-compiled" },
})

function prepare()
  UTILS.EXTRACT("https://example.com/my-cli-" .. SYSTEM.OS .. ".tar.zst", "src")
end

function package()
  zcp("src/my-cli", "${pkgstore}/bin/my-cli")

  -- Stage shell completions
  zshell("${pkgluadir}/completions/_my-cli", "zsh")
  zshell("${pkgluadir}/completions/my-cli.bash", "bash")
  zshell("${pkgluadir}/completions/my-cli.fish", "fish")
end

After installation, completions are available at:

  • zsh: ~/.zoi/pkgs/shell/zsh/my-cli/_my-cli (add ~/.zoi/pkgs/shell/zsh to fpath)
  • bash: ~/.zoi/pkgs/shell/bash/my-cli/my-cli.bash
  • fish: ~/.zoi/pkgs/shell/fish/my-cli/my-cli.fish

A software organization

2026 © All Rights Reserved.

  • All the content is available under CC BY-SA 4.0, expect where otherwise stated.

Last updated on