mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-21 21:15:09 +00:00
* fix(setup): merge plist/systemd env vars instead of overwriting
setup.mjs previously wrote the launchd plist and the Linux systemd unit
with writeFileSync(path, NEW_TEMPLATE_STRING), which silently dropped any
user-customised env vars (CLAUDE_HEARTBEAT_INTERVAL, CLAUDE_CACHE_TTL,
etc.) on every re-setup or upgrade. This change introduces
scripts/lib/plist-merge.mjs which preserves keys present only in the
existing file and lets the template's known keys win. Linux variant uses
the same logic against Environment=KEY=VALUE lines.
Tests added in test-features.mjs cover preserve / override / first-install
for both formats.
No cli.js citation needed: this is OCP-internal installer behaviour with
no corresponding cli.js operation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(setup): plist-merge code-quality follow-up
Three issues raised by the code-quality reviewer on 0fd1838:
1. mergeSystemdEnv now early-returns when the template has no
Environment= anchor, instead of silently dropping preserved lines
(defensive guard for a future template change).
2. Both parsers (parsePlistEnv, parseSystemdEnv) now Buffer-normalise
their input, removing the TypeError-vs-coerce asymmetry.
3. Two idempotency tests added (calling merge twice on the same
input/output pair must be stable).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
87 lines
2.9 KiB
JavaScript
87 lines
2.9 KiB
JavaScript
// scripts/lib/plist-merge.mjs
|
|
//
|
|
// Preserves user-customised env vars when setup.mjs rewrites the unit file.
|
|
//
|
|
// Rule:
|
|
// - keys present in NEW template → template value wins (template is source of truth)
|
|
// - keys ONLY in EXISTING (not in template) → preserved verbatim
|
|
//
|
|
// No new dependencies — regex-based, plist <key>X</key><string>Y</string> shape
|
|
// is stable enough for our hand-written templates in setup.mjs.
|
|
|
|
const PLIST_KV_RE = /<key>([^<]+)<\/key>\s*<string>([^<]*)<\/string>/g;
|
|
|
|
export function parsePlistEnv(plistContent) {
|
|
if (!plistContent) return {};
|
|
if (Buffer.isBuffer(plistContent)) plistContent = plistContent.toString("utf8");
|
|
// Restrict to the EnvironmentVariables dict to avoid catching Label, etc.
|
|
const envBlock = plistContent.match(/<key>EnvironmentVariables<\/key>\s*<dict>([\s\S]*?)<\/dict>/);
|
|
if (!envBlock) return {};
|
|
const out = {};
|
|
let m;
|
|
PLIST_KV_RE.lastIndex = 0;
|
|
while ((m = PLIST_KV_RE.exec(envBlock[1])) !== null) {
|
|
out[m[1]] = m[2];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export function mergePlistEnv(existing, template) {
|
|
if (!existing) return template;
|
|
const existingEnv = parsePlistEnv(existing);
|
|
const templateEnv = parsePlistEnv(template);
|
|
const KNOWN = new Set(Object.keys(templateEnv));
|
|
|
|
const preserved = {};
|
|
for (const [k, v] of Object.entries(existingEnv)) {
|
|
if (!KNOWN.has(k)) preserved[k] = v;
|
|
}
|
|
if (Object.keys(preserved).length === 0) return template;
|
|
|
|
const lines = Object.entries(preserved)
|
|
.map(([k, v]) => ` <key>${k}</key>\n <string>${v}</string>`)
|
|
.join("\n");
|
|
|
|
// Inject before the closing </dict> of EnvironmentVariables
|
|
return template.replace(
|
|
/(<key>EnvironmentVariables<\/key>\s*<dict>[\s\S]*?)(\n\s*<\/dict>)/,
|
|
`$1\n${lines}$2`
|
|
);
|
|
}
|
|
|
|
const SYSTEMD_KV_RE = /^Environment=([^=]+)=(.*)$/gm;
|
|
|
|
export function parseSystemdEnv(serviceContent) {
|
|
if (!serviceContent) return {};
|
|
if (Buffer.isBuffer(serviceContent)) serviceContent = serviceContent.toString("utf8");
|
|
const out = {};
|
|
let m;
|
|
SYSTEMD_KV_RE.lastIndex = 0;
|
|
while ((m = SYSTEMD_KV_RE.exec(serviceContent)) !== null) {
|
|
out[m[1]] = m[2];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export function mergeSystemdEnv(existing, template) {
|
|
if (!existing) return template;
|
|
const existingEnv = parseSystemdEnv(existing);
|
|
const templateEnv = parseSystemdEnv(template);
|
|
const KNOWN = new Set(Object.keys(templateEnv));
|
|
|
|
const preservedLines = Object.entries(existingEnv)
|
|
.filter(([k]) => !KNOWN.has(k))
|
|
.map(([k, v]) => `Environment=${k}=${v}`);
|
|
if (preservedLines.length === 0) return template;
|
|
|
|
// Guard: if template has no Environment= anchor, cannot inject — return template as-is.
|
|
// (In practice the OCP systemd template always has Environment= lines.)
|
|
if (!/^Environment=/m.test(template)) return template;
|
|
|
|
// Inject after the last existing Environment= line in the template
|
|
return template.replace(
|
|
/(^Environment=[^\n]+\n)((?!Environment=).*$)/ms,
|
|
`$1${preservedLines.join("\n")}\n$2`
|
|
);
|
|
}
|