Class: RepoTender::Launchd::Plist
- Inherits:
-
Object
- Object
- RepoTender::Launchd::Plist
- Defined in:
- lib/repo_tender/launchd/plist.rb
Overview
Hand-rolled launchd plist emitter. The slice forbids a plist gem (PRD §2, AGENTS.md) — this class emits an XML property list as a string and is validated offline with ‘plutil -lint`.
The plist produced here is a fixed-shape StartInterval-driven agent that:
* runs `repo-tender sync` non-interactively under the
repo's mise-managed Ruby (so the right toolchain is in
effect without `mise activate`, which is broken
non-interactively);
* is classified as a Background process (lower scheduling
+ I/O priority — sync is a periodic background job);
* writes its stdout/stderr to absolute log files under
the log dir (launchd owns the redirect, the sync process
rotates its own log on each run — see LogRotator);
* has NO `KeepAlive` key — it is a periodic, not a daemon.
The caller is responsible for passing absolute paths. We do NOT ‘File.expand_path` here — that would mask the caller’s responsibility to pass absolute paths (the G1 / G3 gates assert that no ‘~` or `$HOME` appears in any value).
Constant Summary collapse
- HEADER =
The plist’s outer XML decl + DOCTYPE — required by plutil’s lint and by launchd’s parser.
<<~XML <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> XML
- FOOTER =
"</dict>\n</plist>\n"
Class Method Summary collapse
-
.call(label:, refresh_interval:, log_dir:, repo_root:, mise_toml:, mise_bin:, ruby_bin:, bin_path:) ⇒ String
Emit a launchd plist for a sync job.
Class Method Details
.call(label:, refresh_interval:, log_dir:, repo_root:, mise_toml:, mise_bin:, ruby_bin:, bin_path:) ⇒ String
Emit a launchd plist for a sync job.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/repo_tender/launchd/plist.rb', line 49 def call(label:, refresh_interval:, log_dir:, repo_root:, mise_toml:, mise_bin:, ruby_bin:, bin_path:) raise ArgumentError, "label is required" if label.to_s.empty? raise ArgumentError, "refresh_interval must be > 0" unless refresh_interval.is_a?(Integer) && refresh_interval > 0 %w[log_dir repo_root mise_toml mise_bin ruby_bin bin_path].each do |k| v = binding.local_variable_get(k) raise ArgumentError, "#{k} must be absolute (got #{v.inspect})" unless v.is_a?(String) && File.absolute_path?(v) end out_log = File.join(log_dir, "#{label}.out.log") err_log = File.join(log_dir, "#{label}.err.log") body = +"" body << key("Label") << string(label) << "\n" body << key("ProgramArguments") << "\n" << array([ mise_bin, "exec", "--", ruby_bin, bin_path, "sync" ]) body << key("WorkingDirectory") << string(repo_root) << "\n" body << key("EnvironmentVariables") << "\n" << dict({ "MISE_CONFIG_FILE" => mise_toml }) body << key("StartInterval") << integer(refresh_interval) << "\n" body << key("RunAtLoad") << boolean(true) << "\n" body << key("ProcessType") << string("Background") << "\n" body << key("StandardOutPath") << string(out_log) << "\n" body << key("StandardErrorPath") << string(err_log) << "\n" HEADER + body + FOOTER end |