* 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>
96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
import { lstat, realpath } from "node:fs/promises";
|
|
import { basename, dirname, join, resolve } from "node:path";
|
|
|
|
interface ClientConfigPathStats {
|
|
readonly uid?: number;
|
|
isFile?(): boolean;
|
|
isSymbolicLink(): boolean;
|
|
}
|
|
|
|
interface ClientConfigPathFileSystem {
|
|
lstat(path: string): Promise<ClientConfigPathStats>;
|
|
realpath?(path: string): Promise<string>;
|
|
}
|
|
|
|
const systemFileSystem: ClientConfigPathFileSystem = { lstat, realpath };
|
|
|
|
/**
|
|
* 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 | undefined;
|
|
try {
|
|
requestedStats = await fileSystem.lstat(requestedPath);
|
|
} catch (cause: unknown) {
|
|
if (!isMissing(cause)) return undefined;
|
|
}
|
|
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 (
|
|
currentUid === undefined ||
|
|
requestedStats.uid !== currentUid ||
|
|
fileSystem.realpath === undefined
|
|
)
|
|
return undefined;
|
|
|
|
try {
|
|
const canonicalPath = await fileSystem.realpath(requestedPath);
|
|
const targetStats = await fileSystem.lstat(canonicalPath);
|
|
return targetStats.isFile?.() === true && targetStats.uid === currentUid
|
|
? canonicalPath
|
|
: undefined;
|
|
} catch (cause: unknown) {
|
|
// best-effort cleanup: unreadable or vanishing config paths mean no registration.
|
|
void cause;
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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");
|