mirror of
https://github.com/morluto/rea.git
synced 2026-11-02 21:06:04 +00:00
* 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
79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
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);
|
|
};
|