mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-19 09:44:07 +00:00
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>
81 lines
2.6 KiB
JavaScript
81 lines
2.6 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 {};
|
|
// 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 {};
|
|
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;
|
|
|
|
// Inject after the last existing Environment= line in the template
|
|
return template.replace(
|
|
/(^Environment=[^\n]+\n)((?!Environment=).*$)/ms,
|
|
`$1${preservedLines.join("\n")}\n$2`
|
|
);
|
|
}
|