mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 18:31:34 +01:00
fix(config): preserve env files during managed updates
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
2ea22d6cea
commit
31a54faef6
5 files changed
+351
-92
No files matched your search
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { PaperclipConfig } from "@paperclipai/shared";
|
||||
import { updateEnvFileContents, writeEnvFileAtomicallyIfChanged } from "@paperclipai/shared/env-file";
|
||||
import { resolvePaperclipConfigPath, resolvePaperclipEnvPath } from "./paths.js";
|
||||
|
||||
function nonEmpty(value: string | null | undefined): string | null {
|
||||
@@ -76,73 +77,14 @@ function readEnvEntries(envPath: string): Record<string, string> {
|
||||
return parseEnvFile(fs.readFileSync(envPath, "utf8"));
|
||||
}
|
||||
|
||||
function formatEnvEntries(entries: Record<string, string>): string {
|
||||
function emptyWorktreeEnvFileContents(): string {
|
||||
return [
|
||||
"# Paperclip environment variables",
|
||||
"# Generated by Paperclip worktree repair",
|
||||
...Object.entries(entries).map(([key, value]) => `${key}=${JSON.stringify(value)}`),
|
||||
"",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function trailingEnvComment(rawValue: string): string {
|
||||
let quote: "\"" | "'" | null = null;
|
||||
let escaped = false;
|
||||
|
||||
for (let index = 0; index < rawValue.length; index += 1) {
|
||||
const character = rawValue[index];
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (quote === "\"" && character === "\\") {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
if (quote !== null) {
|
||||
if (character === quote) quote = null;
|
||||
continue;
|
||||
}
|
||||
if (character === "\"" || character === "'") {
|
||||
quote = character;
|
||||
continue;
|
||||
}
|
||||
if (character !== "#" || index === 0 || !/\s/.test(rawValue[index - 1] ?? "")) continue;
|
||||
|
||||
let commentStart = index;
|
||||
while (commentStart > 0 && /\s/.test(rawValue[commentStart - 1] ?? "")) {
|
||||
commentStart -= 1;
|
||||
}
|
||||
return rawValue.slice(commentStart);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function updateEnvFileContents(contents: string, entries: Record<string, string>): string {
|
||||
const newline = contents.includes("\r\n") ? "\r\n" : "\n";
|
||||
const missingEntries = new Map(Object.entries(entries));
|
||||
const lines = contents.split(/\r?\n/).map((rawLine) => {
|
||||
const match = rawLine.match(/^(\s*(?:export\s+)?)([A-Za-z_][A-Za-z0-9_]*)(\s*=\s*)(.*)$/);
|
||||
if (!match) return rawLine;
|
||||
|
||||
const [, prefix, key, separator, rawValue] = match;
|
||||
const value = entries[key];
|
||||
if (value === undefined) return rawLine;
|
||||
|
||||
missingEntries.delete(key);
|
||||
return `${prefix}${key}${separator}${JSON.stringify(value)}${trailingEnvComment(rawValue)}`;
|
||||
});
|
||||
|
||||
const insertionIndex = lines.at(-1) === "" ? lines.length - 1 : lines.length;
|
||||
lines.splice(
|
||||
insertionIndex,
|
||||
0,
|
||||
...Array.from(missingEntries, ([key, value]) => `${key}=${JSON.stringify(value)}`),
|
||||
);
|
||||
return lines.join(newline);
|
||||
}
|
||||
|
||||
function isPathInside(candidatePath: string, rootPath: string): boolean {
|
||||
const candidate = path.resolve(candidatePath);
|
||||
const root = path.resolve(rootPath);
|
||||
@@ -623,7 +565,6 @@ export function maybeRepairLegacyWorktreeConfigAndEnvFiles(): {
|
||||
}
|
||||
}
|
||||
|
||||
const existingEnvEntries = readEnvEntries(context.envPath);
|
||||
const managedEnvEntries: Record<string, string> = {
|
||||
PAPERCLIP_HOME: context.homeDir,
|
||||
PAPERCLIP_INSTANCE_ID: context.instanceId,
|
||||
@@ -636,20 +577,18 @@ export function maybeRepairLegacyWorktreeConfigAndEnvFiles(): {
|
||||
|
||||
process.env.PAPERCLIP_DB_BACKUP_ENABLED = "false";
|
||||
|
||||
const repairedEnv = Object.entries(managedEnvEntries).some(
|
||||
([key, value]) => existingEnvEntries[key] !== value,
|
||||
const existingContents = fs.existsSync(context.envPath)
|
||||
? fs.readFileSync(context.envPath, "utf8")
|
||||
: null;
|
||||
const repairedContents = updateEnvFileContents(
|
||||
existingContents ?? emptyWorktreeEnvFileContents(),
|
||||
managedEnvEntries,
|
||||
{ valueEncoding: "json" },
|
||||
);
|
||||
const repairedEnv = existingContents !== repairedContents;
|
||||
|
||||
if (repairedEnv) {
|
||||
fs.mkdirSync(path.dirname(context.envPath), { recursive: true });
|
||||
const existingContents = fs.existsSync(context.envPath)
|
||||
? fs.readFileSync(context.envPath, "utf8")
|
||||
: null;
|
||||
const repairedContents =
|
||||
existingContents === null
|
||||
? formatEnvEntries(managedEnvEntries)
|
||||
: updateEnvFileContents(existingContents, managedEnvEntries);
|
||||
fs.writeFileSync(context.envPath, repairedContents, { mode: 0o600 });
|
||||
writeEnvFileAtomicallyIfChanged(context.envPath, existingContents, repairedContents);
|
||||
}
|
||||
|
||||
return { repairedConfig, repairedEnv };
|
||||
|
||||
Reference in new issue
Block a user