Tighten quire.stdlib mirror API
Caller resolves the auth header so mirror works with any credential
source, not just the runtime secret store. `λ` and a small `cat` helper
replace the manual nil-checks and imperative push-args construction.
Assisted-by: Claude Opus 4.7 via Claude Code
diff --git a/docs/CI-FENNEL.md b/docs/CI-FENNEL.md
index 7ce54ea..c055ac1 100644
--- a/docs/CI-FENNEL.md
+++ b/docs/CI-FENNEL.md
@@ -244,18 +244,19 @@ The kernel (`sh`/`secret`/`jobs`) stays small. Higher-level operations like tag-
(ci.job :mirror [:quire/push :test]
(fn []
- (let [push (runtime.jobs :quire/push)]
- (mirror {:url "https://github.com/example/repo.git"
- :secret :github_auth_header
- :sha push.sha
- :tag (.. "quire-" (string.sub push.sha 1 8))
- :git-dir (. push :git-dir)
- :refs ["refs/heads/main"]}))))
+ (let [push (runtime.jobs :quire/push)
+ auth (runtime.secret :github_auth_header)]
+ (mirror {:url "https://github.com/example/repo.git"
+ :auth-header auth
+ :sha push.sha
+ :tag (.. "quire-" (string.sub push.sha 1 8))
+ :git-dir (. push :git-dir)
+ :refs ["refs/heads/main"]}))))
```
Available helpers:
-* `(mirror opts)` — tag a commit and push it (plus optional refs) to a remote. `opts.url`, `opts.secret`, `opts.sha`, `opts.tag`, and `opts.git-dir` are required; `opts.refs` defaults to `[]`. The auth header (resolved via `runtime.secret`) is passed via `GIT_CONFIG_*` env vars rather than `-c http.extraHeader=…` in argv, so it doesn't appear in `ps` listings. Returns `{:tag :pushed_refs}`. Raises on missing required opts, unknown secrets, or non-zero git exits.
+* `(mirror opts)` — tag a commit and push it (plus optional refs) to a remote. `opts.url`, `opts.auth-header`, `opts.sha`, `opts.tag`, and `opts.git-dir` are required; `opts.refs` defaults to `[]`. The caller resolves the credential (typically via `runtime.secret`) and passes the full HTTP header line as `:auth-header`; mirror passes it to git via `GIT_CONFIG_*` env vars rather than `-c http.extraHeader=…` in argv, so it doesn't appear in `ps` listings. Returns `{:tag :pushed_refs}`. Raises on missing required opts or non-zero git exits.
`(ci.mirror …)` (the registration-time form) remains as a convenience wrapper that registers a singleton `quire/mirror` job. Use the stdlib form when you want to mirror conditionally or as part of a larger run-fn.
diff --git a/quire-core/src/ci/runtime.rs b/quire-core/src/ci/runtime.rs
index 96ddfbf..1234446 100644
--- a/quire-core/src/ci/runtime.rs
+++ b/quire-core/src/ci/runtime.rs
@@ -975,11 +975,12 @@ mod tests {
(local {{: mirror}} (require :quire.stdlib))
(ci.job :go [:quire/push]
(fn []
- (mirror {{:url "{url}"
- :secret :github_token
- :sha "{sha}"
- :tag "v1"
- :git-dir "{git_dir}"}})))"#,
+ (let [auth (runtime.secret :github_token)]
+ (mirror {{:url "{url}"
+ :auth-header auth
+ :sha "{sha}"
+ :tag "v1"
+ :git-dir "{git_dir}"}}))))"#,
url = format!("file://{}", target.display()),
sha = sha,
git_dir = bare.display(),
@@ -999,39 +1000,13 @@ mod tests {
(local {: mirror} (require :quire.stdlib))
(ci.job :go [:quire/push]
(fn []
- (mirror {:secret :github_token :sha "x" :tag "v1" :git-dir "/tmp"})))"#;
+ (mirror {:auth-header "x" :sha "x" :tag "v1" :git-dir "/tmp"})))"#;
let (_runtime, run_fn) = rt(source, HashMap::new());
let err = run_fn.call::<mlua::Value>(()).unwrap_err();
let msg = err.to_string();
assert!(
- msg.contains("missing required option :url"),
+ msg.contains("Missing argument url"),
"expected missing-:url error, got: {msg}"
);
}
-
- #[test]
- fn stdlib_mirror_errors_on_unknown_secret() {
- let (_dir, bare, target, sha) = bare_repo_with_target();
- let source = format!(
- r#"(local ci (require :quire.ci))
-(local {{: mirror}} (require :quire.stdlib))
-(ci.job :go [:quire/push]
- (fn []
- (mirror {{:url "{url}"
- :secret :nope
- :sha "{sha}"
- :tag "v1"
- :git-dir "{git_dir}"}})))"#,
- url = format!("file://{}", target.display()),
- sha = sha,
- git_dir = bare.display(),
- );
- let (_runtime, run_fn) = rt(&source, HashMap::new());
- let err = run_fn.call::<mlua::Value>(()).unwrap_err();
- let msg = err.to_string();
- assert!(
- msg.contains("unknown secret") && msg.contains("nope"),
- "expected unknown-secret error mentioning the name, got: {msg}"
- );
- }
}
diff --git a/quire-core/src/ci/stdlib.fnl b/quire-core/src/ci/stdlib.fnl
index c5d5c7d..c028d12 100644
--- a/quire-core/src/ci/stdlib.fnl
+++ b/quire-core/src/ci/stdlib.fnl
@@ -5,34 +5,37 @@
(local M {})
-(fn missing [field]
- (error (.. "quire.stdlib.mirror: missing required option :" field)))
-
(fn trim [s]
(string.gsub s "%s+$" ""))
+(fn cat [...]
+ ;; Concatenate sequence tables into a fresh sequence.
+ (let [out []]
+ (each [_ t (ipairs [...])]
+ (each [_ x (ipairs t)]
+ (table.insert out x)))
+ out))
+
;; (mirror opts)
;;
;; Tag a commit and push the tag (plus optional refs) to a remote.
;;
-;; opts: {:url — remote URL (required)
-;; :secret — secret name resolved via runtime.secret (required)
-;; :sha — commit to tag (required)
-;; :tag — tag name (required)
-;; :git-dir — bare git directory the run is scoped to (required)
-;; :refs — extra refs to push alongside the tag (optional, default [])}
+;; opts: {:url — remote URL (required)
+;; :auth-header — full HTTP header line passed to git as
+;; `http.extraHeader`; resolve via
+;; `runtime.secret` at the call site (required)
+;; :sha — commit to tag (required)
+;; :tag — tag name (required)
+;; :git-dir — bare git directory the run is scoped to (required)
+;; :refs — extra refs to push alongside the tag
+;; (optional, default [])}
;;
-;; Returns {:tag :pushed_refs}. Raises on missing required opts,
-;; unknown secrets, or non-zero git exits.
-(fn M.mirror [opts]
- (let [{: secret : sh} (require :quire.runtime)
- url (or opts.url (missing :url))
- secret-name (or opts.secret (missing :secret))
- sha (or opts.sha (missing :sha))
- tag (or opts.tag (missing :tag))
- git-dir (or (. opts :git-dir) (missing :git-dir))
- refs (or opts.refs [])
- auth-header (secret secret-name)
+;; Returns {:tag :pushed_refs}. Raises on missing required opts or
+;; non-zero git exits. `lambda` checks the required bindings for nil
+;; at the call site.
+(λ M.mirror [{: url : auth-header : sha : tag : git-dir :refs ?refs}]
+ (let [{: sh} (require :quire.runtime)
+ refs (or ?refs [])
;; Pass http.extraHeader via GIT_CONFIG_* env (git 2.31+)
;; instead of `-c http.extraHeader=…` in argv. Keeps the auth
;; header out of `ps` and out of any argv logging we add
@@ -45,13 +48,12 @@
tag-result (sh [:git :tag tag sha] sh-opts)]
(when (not= 0 tag-result.exit)
(error (.. "git tag failed: " (trim tag-result.stderr))))
- (let [push-args [:git :push :--porcelain url]]
- (each [_ ref (ipairs refs)]
- (table.insert push-args ref))
- (table.insert push-args (.. :refs/tags/ tag))
- (let [push-result (sh push-args sh-opts)]
- (when (not= 0 push-result.exit)
- (error (.. "git push failed: " (trim push-result.stderr))))
- {: tag :pushed_refs refs}))))
+ (let [push-args (cat [:git :push :--porcelain url]
+ refs
+ [(.. :refs/tags/ tag)])
+ push-result (sh push-args sh-opts)]
+ (when (not= 0 push-result.exit)
+ (error (.. "git push failed: " (trim push-result.stderr))))
+ {: tag :pushed_refs refs})))
M