Template-local reference for wrapper authors.
Source of truth for engine behavior is ReqPack.wiki/Extending-Writing-Lua-Plugins.md.
If you are turning this template into real plugin, use this order:
- Read this file once front to back.
- Read
metadata.json,reqpack.lua,run.lua, and.reqpack-test/core/*.lua. - Edit
metadata.jsonsonamematches your plugin id. - Replace all
templateplaceholders with real plugin id, system name, and binary name. - Put package-manager existence check in
plugin.init(). - Implement methods in this order:
getMissingPackagesinstallinstallLocalremoveupdatelistsearchinfooutdated
- Update
.reqpack-test/core/*.lua. - Run
rqp test-plugin --plugin . --preset corefrom plugin root.
Expected bundle layout:
<plugin-id>/
metadata.json
reqpack.lua
run.lua
scripts/
install.lua
remove.lua
.reqpack-test/
core/
Files and roles:
metadata.json: bundle metadata.namebecomes plugin id used for discovery.reqpack.lua: bundle manifest. KeepapiVersion = 1; add ReqPack-sidedependshere.run.lua: real wrapper implementation loaded byLuaBridge.scripts/install.luaandscripts/remove.lua: required for bundle validity even when wrapper logic lives inrun.lua..reqpack-test/core/*.lua: hermetic test cases used byrqp test-plugin --preset core.README.md: quickstart for people opening template repo first.
Actual timing matters for wrapper authors:
- ReqPack validates bundle layout from
metadata.jsonandreqpack.lua. - First plugin construction executes
run.luaimmediately. - During construction ReqPack may read:
plugin.getName()plugin.getVersion()plugin.getSecurityMetadata()plugin.fileExtensions
- Only after contract validation does ReqPack call optional
plugin.init(). - Action/query methods run later as planner and executor need them.
- Optional
plugin.shutdown()runs when plugin registry shuts down.
Implications:
- top-level
run.luacode runs beforeplugin.init() - metadata methods should stay side-effect free
plugin.fileExtensionsmust be populated beforeinit()if local-target detection depends on itgetCategories()can also be used on constructed plugin beforeinit()
Main script must expose global plugin table.
Required methods:
function plugin.getName() end
function plugin.getVersion() end
function plugin.getRequirements() end
function plugin.getCategories() end
function plugin.getMissingPackages(packages) end
function plugin.install(context, packages) end
function plugin.installLocal(context, path) end
function plugin.remove(context, packages) end
function plugin.update(context, packages) end
function plugin.list(context) end
function plugin.search(context, prompt) end
function plugin.info(context, packageName) endUseful optional methods:
function plugin.init() end
function plugin.shutdown() end
function plugin.outdated(context) end
function plugin.resolvePackage(context, package) end
function plugin.resolveProxyRequest(context, request) end
function plugin.getSecurityMetadata() end
function plugin.pack(context, projectPath, outputPath, flags) endOptional static data:
plugin.fileExtensions = { ".rpm", ".deb" }Return rules:
- boolean-style action methods treat no return as success
- query methods treat no return as empty result
getMissingPackages()should return only packages that still need work
ReqPack injects these globals before executing run.lua:
REQPACK_PLUGIN_ID
REQPACK_PLUGIN_DIR
REQPACK_PLUGIN_SCRIPT
reqpackprint(...) is also redirected into ReqPack output.
Current global namespace:
local result = reqpack.exec.run("command -v your-tool >/dev/null 2>&1")
local host = reqpack.hostImportant differences from context.exec.run(...):
- only plain command-string overload exists
- output is tied to plugin scope, not current item id
reqpack.hostis bridge-global host snapshot captured when plugin bridge is created
ReqPack passes context into action methods and most optional runtime hooks.
context.plugin.id
context.plugin.dir
context.plugin.scriptArray of request/runtime flags currently active for this plugin call.
Per-call host snapshot. Same shape as reqpack.host, but created for current call instead of bridge construction.
Top-level sections:
context.host.platform
context.host.os
context.host.kernel
context.host.cpu
context.host.memory
context.host.gpus
context.host.storage
context.host.cacheExamples inside those tables include fields such as:
context.host.platform.osFamily
context.host.platform.arch
context.host.os.id
context.host.cpu.logicalCores
context.host.memory.totalBytes
context.host.cache.expiresAtEpochUse context.host when you want freshest host view during action execution.
Available when current system has proxy config.
context.proxy.default
context.proxy.targets
context.proxy.optionsArray of repository entries for current ecosystem. Each entry can expose fields such as:
repo.id
repo.url
repo.priority
repo.enabled
repo.type
repo.auth
repo.validation
repo.scopecontext.log.debug("...")
context.log.info("...")
context.log.warn("...")
context.log.error("...")context.tx.status(42)
context.tx.progress(50)
context.tx.progress({ percent = 50, current = 10, currentUnit = "MB" })
context.tx.begin_step("install packages")
context.tx.commit()
context.tx.success()
context.tx.failed("install failed")Notes:
progress(50)is valid shorthand for percent updates- table payloads can include
percent,current,currentUnit,total,totalUnit,speed,speedUnit
Use these to tell ReqPack what happened:
context.events.installed(payload)
context.events.deleted(payload)
context.events.updated(payload)
context.events.listed(payload)
context.events.searched(payload)
context.events.informed(payload)
context.events.outdated(payload)
context.events.unavailable(payload)Payloads are serialized into text records. Use deterministic tables so tests stay stable.
context.artifacts.register({ type = "file", path = "/tmp/out" })Use this when wrapper produces artifacts, especially in optional plugin.pack() flows.
Overloads:
local result = context.exec.run("command")
local result = context.exec.run("command", rules)Return shape visible in Lua:
result.success
result.exitCode
result.stdout
result.stderrImportant behavior:
- shell command runs as
/bin/sh -c <command> - stdout/stderr transcript is merged into
result.stdout - on failure without runner read error, merged transcript is copied into
result.stderr - use this form inside action methods so output stays tied to current item when ReqPack has one
local tmpDir = context.fs.get_tmp_dir()ReqPack deletes these temp directories during plugin shutdown.
local ok = context.net.download(url, destinationPath)Return type is boolean only.
Lua side does not receive full DownloadResult object.
Prefer:
context.exec.run(...)inside action methodsreqpack.exec.run(...)for top-level checks such asplugin.init()context.hostfor call-time host decisionsreqpack.hostonly when bridge-time snapshot is enough
Use exec rules when wrapper must react to command output while command is still running.
Top-level shape:
local rules = {
initial = "default",
rules = {
{
state = "default",
source = "line",
regex = "^Loaded (.+)$",
repeat = true,
stop = false,
actions = {
{ type = "log", level = "info", message = "${1}" },
},
},
},
}Schema rules that matter most for template authors:
- top-level keys allowed only:
initial,rules rulesmust be contiguous 1-based array-style table- each rule must include
source,regex,actions sourcemust belineorscreenactionsmust be non-empty contiguous 1-based array-style table
Common action types:
logstatusprogressbegin_stepsuccessfailedeventartifactsendfor PTY-driven commandsstatefor internal rule-state tracking
Runner selection:
- empty ruleset: plain shell runner
- rules with no
screenrule and nosendaction: line runner - any
screenrule or anysendaction: PTY runner
Important placeholders:
${0}full regex match${1},${2}, ... capture groups- missing capture resolves to empty string
If rule shape is malformed, command fails immediately with success = false and exitCode = 1.
Return only packages that still need work.
Examples:
- install: package not installed yet
- remove: package currently installed
- update: package has newer version available
Lazy return packages works, but planning quality gets worse.
Return arrays of package info tables.
Common fields:
{
name = "curl",
version = "8.0.1",
latestVersion = "8.1.0",
type = "package",
summary = "Transfer tool",
description = "Longer description",
architecture = "x86_64",
}Return one package info table.
ReqPack accepts many more fields than template uses, including homepage, license, dependencies, provides, tags, and extraFields.
If summary is empty, ReqPack copies description into it.
Use this when exact version lookup is possible and you want better SBOM/audit coverage.
Must return table like:
{
targetSystem = "maven",
packages = { "org.junit:junit:4.13" },
flags = { "arch=noarch" },
}Supported keys:
targetSystemrequiredpackagesoptional string arraylocalPathoptional stringflagsoptional string array
Important rules:
packagesandlocalPathare mutually exclusive- returning
nilis treated as resolution failure, not pass-through
Optional table used for trust, thin-layer exec policy, and vulnerability mapping.
ReqPack may read it before plugin.init().
Core commands:
rqp test-plugin --plugin . --preset core
rqp test-plugin --plugin . --case ./.reqpack-test/core/info.lua
rqp test-plugin --plugin . --cases ./.reqpack-test/core --report ./plugin-test-report.jsonCase files return Lua table with:
namerequestfakeExecexpect
Typical fields:
request = {
action = "install",
system = "demo",
prompt = "curl",
localPath = "/tmp/demo.tgz",
packages = {
{ name = "curl", version = "8.0.0" }
},
}Use localPath with action = "install" to test plugin.installLocal(context, path).
fakeExec is ordered array of substring-match rules.
fakeExec = {
{
match = "demo-pm install curl",
exitCode = 0,
stdout = "done\n",
stderr = "",
success = true,
}
}Behavior:
- first rule whose
matchstring appears inside executed command wins - if
successis omitted, runner derives it fromexitCode == 0 - if no rule matches command, test runner fails command with
exitCode = 127 - unmatched command stderr is
no fakeExec rule matched command: ...
This affects both:
context.exec.run(...)reqpack.exec.run(...)
If plugin.init() runs binary checks, include matching fake-exec rules in test cases or presets.
Common expectation keys:
expect = {
success = true,
commands = { "demo-pm install curl" },
stdout = { "done\n" },
stderr = {},
events = { "installed", "success" },
eventPayloads = {
success = "ok",
},
resultCount = 1,
resultName = "curl",
resultVersion = "8.0.0",
}Template ships starter core cases for:
installinstallLocalremoveupdatelistsearchinfooutdated
- Edit
metadata.jsonfirst. - Replace all
templateplaceholders in code and tests. - Keep
reqpack.lua.dependsaligned with real ReqPack-side dependencies. - Add deterministic binary check in
plugin.init()if needed. - Implement
getMissingPackages()with real installed-state logic. - Use
context.exec.run(...)in action methods. - Emit
context.tx.*andcontext.events.*for visible behavior. - Update
.reqpack-test/core/*.luabefore expanding behavior further. - Run
rqp test-plugin --plugin . --preset core.
Read full repo docs when template-local reference still is not enough:
ReqPack.wiki/Extending-Writing-Lua-Plugins.mdReqPack.wiki/Extending-Testing-Lua-Plugins.md