mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 08:47:00 +00:00
fix(setup): preserve concurrent client configuration edits (#4855)
* fix(setup): preserve concurrent client configuration edits * fix(setup): normalize configuration transaction queue paths * fix(setup): report concurrent snapshot read conflicts * test(setup): isolate Pi workflows from installed Hopper * test: align inherited fixtures with current analysis contracts * test(ida): assert search recovery instead of diagnostic prose * fix(setup): share configuration queue keys across directory aliases Resolve parent-directory aliases for regular and missing configuration paths so a directory symlink and its real directory serialize setup instead of racing the same file. Co-authored-by: morluto <morluto@users.noreply.github.com> --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <morluto@users.noreply.github.com>
This commit is contained in:
11 files changed
+752
-56
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { lstat, realpath } from "node:fs/promises";
|
||||
import { basename, dirname, join, resolve } from "node:path";
|
||||
|
||||
interface ClientConfigPathStats {
|
||||
readonly uid?: number;
|
||||
@@ -14,21 +15,32 @@ interface ClientConfigPathFileSystem {
|
||||
const systemFileSystem: ClientConfigPathFileSystem = { lstat, realpath };
|
||||
|
||||
/**
|
||||
* Resolve an existing, user-owned config symlink to one stable transaction
|
||||
* path. Missing regular paths remain eligible for first-run creation.
|
||||
* Resolve one stable transaction path for a configuration file.
|
||||
* An existing user-owned config symlink resolves to its target file.
|
||||
* Parent-directory aliases resolve for regular and missing files, so a
|
||||
* directory symlink and its real directory share one queue key. Missing
|
||||
* paths stay eligible for first-run creation.
|
||||
*/
|
||||
export const resolveClientConfigTransactionPath = async (
|
||||
requestedPath: string,
|
||||
fileSystem: ClientConfigPathFileSystem = systemFileSystem,
|
||||
): Promise<string | undefined> => {
|
||||
let requestedStats: ClientConfigPathStats;
|
||||
let requestedStats: ClientConfigPathStats | undefined;
|
||||
try {
|
||||
requestedStats = await fileSystem.lstat(requestedPath);
|
||||
} catch (cause: unknown) {
|
||||
return isMissing(cause) ? requestedPath : undefined;
|
||||
if (!isMissing(cause)) return undefined;
|
||||
}
|
||||
if (!requestedStats.isSymbolicLink()) return requestedPath;
|
||||
if (requestedStats?.isSymbolicLink() === true)
|
||||
return resolveOwnedConfigSymlink(requestedPath, requestedStats, fileSystem);
|
||||
return canonicalRegularConfigPath(requestedPath, fileSystem);
|
||||
};
|
||||
|
||||
const resolveOwnedConfigSymlink = async (
|
||||
requestedPath: string,
|
||||
requestedStats: ClientConfigPathStats,
|
||||
fileSystem: ClientConfigPathFileSystem,
|
||||
): Promise<string | undefined> => {
|
||||
const currentUid =
|
||||
typeof process.getuid === "function" ? process.getuid() : undefined;
|
||||
if (
|
||||
@@ -51,5 +63,33 @@ export const resolveClientConfigTransactionPath = async (
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve parent-directory aliases, including when the file or an intermediate
|
||||
* directory is absent. A realpath failure keeps the requested spelling.
|
||||
*/
|
||||
const canonicalRegularConfigPath = async (
|
||||
requestedPath: string,
|
||||
fileSystem: ClientConfigPathFileSystem,
|
||||
): Promise<string> => {
|
||||
if (fileSystem.realpath === undefined) return requestedPath;
|
||||
let ancestor = resolve(requestedPath);
|
||||
const missing: string[] = [];
|
||||
while (true) {
|
||||
try {
|
||||
return join(await fileSystem.realpath(ancestor), ...missing);
|
||||
} catch (cause: unknown) {
|
||||
if (!isAbsentPath(cause) || dirname(ancestor) === ancestor)
|
||||
return requestedPath;
|
||||
missing.unshift(basename(ancestor));
|
||||
ancestor = dirname(ancestor);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const isMissing = (cause: unknown): boolean =>
|
||||
cause instanceof Error && "code" in cause && cause.code === "ENOENT";
|
||||
|
||||
const isAbsentPath = (cause: unknown): boolean =>
|
||||
cause instanceof Error &&
|
||||
"code" in cause &&
|
||||
(cause.code === "ENOENT" || cause.code === "ENOTDIR");
|
||||
Reference in new issue
Block a user