mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 00:36:35 +00:00
fix(cli): preserve Amp MCP registration settings (#7995)
* fix(cli): preserve Amp registration environment and report failures (#7538, #7536) * fix(cli): normalize equals-form mcp add agents before Incur parses them (#7538) * fix(cli): remove the Amp settings backup after a verified registration (#7538) * fix(cli): secure Amp registration backups * test(cli): cover Amp registration backup permissions * fix(cli): retain Amp registration recovery backups * fix(cli): create private Amp recovery copies * test(cli): fix Amp backup assertion typing * fix(cli): preserve typed Amp backup failures * fix(cli): handle Amp backup cleanup failures
This commit is contained in:
1 parent
124e97eac5
commit
f25d1e3e9f
3 files changed
+517
No files matched your search
@@ -0,0 +1,78 @@
|
||||
import { chmodSync, lstatSync } from "node:fs";
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
|
||||
import {
|
||||
preserveCurrentRecoveryBackup,
|
||||
type RecoveryBackupPreservationFailure,
|
||||
} from "../application/SetupRecoveryBackup.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
|
||||
const errorDetail = (cause: unknown): string =>
|
||||
cause instanceof Error ? cause.message : String(cause);
|
||||
|
||||
const isMissing = (cause: unknown): boolean =>
|
||||
typeof cause === "object" &&
|
||||
cause !== null &&
|
||||
"code" in cause &&
|
||||
cause.code === "ENOENT";
|
||||
|
||||
const restrictExistingBackup = (
|
||||
backupPath: string,
|
||||
): Result<void, RecoveryBackupPreservationFailure> => {
|
||||
let existing: ReturnType<typeof lstatSync>;
|
||||
try {
|
||||
existing = lstatSync(backupPath);
|
||||
} catch (cause: unknown) {
|
||||
if (isMissing(cause)) return ok(undefined);
|
||||
return err({
|
||||
kind: "unreadable",
|
||||
path: backupPath,
|
||||
detail: `The existing Amp settings backup could not be checked: ${errorDetail(cause)}`,
|
||||
});
|
||||
}
|
||||
if (!existing.isFile())
|
||||
return err({
|
||||
kind: "occupied",
|
||||
path: backupPath,
|
||||
detail: "The existing Amp settings backup is not a regular file.",
|
||||
});
|
||||
try {
|
||||
chmodSync(backupPath, 0o600);
|
||||
} catch (cause: unknown) {
|
||||
return err({
|
||||
kind: "unreadable",
|
||||
path: backupPath,
|
||||
detail: `The existing Amp settings backup permissions could not be secured: ${errorDetail(cause)}`,
|
||||
});
|
||||
}
|
||||
return ok(undefined);
|
||||
};
|
||||
|
||||
const copyPrivateBackup = async (
|
||||
sourcePath: string,
|
||||
backupPath: string,
|
||||
): Promise<void> => {
|
||||
await writeFile(backupPath, await readFile(sourcePath), {
|
||||
flag: "wx",
|
||||
mode: 0o600,
|
||||
});
|
||||
};
|
||||
|
||||
export const writeAmpRegistrationBackup = async (
|
||||
settingsPath: string,
|
||||
settingsText: string,
|
||||
): Promise<Result<string, RecoveryBackupPreservationFailure>> => {
|
||||
const firstBackupPath = `${settingsPath}.rea.backup`;
|
||||
const firstRestricted = restrictExistingBackup(firstBackupPath);
|
||||
if (!firstRestricted.ok) return firstRestricted;
|
||||
const preserved = await preserveCurrentRecoveryBackup(
|
||||
firstBackupPath,
|
||||
settingsPath,
|
||||
settingsText,
|
||||
{ copy: copyPrivateBackup },
|
||||
);
|
||||
if (!preserved.ok) return preserved;
|
||||
const selectedRestricted = restrictExistingBackup(preserved.value);
|
||||
if (!selectedRestricted.ok) return selectedRestricted;
|
||||
return ok(preserved.value);
|
||||
};
|
||||
Reference in new issue
Block a user