refactor: share browser path helpers

This commit is contained in:
Peter Steinberger
2026-04-20 16:27:11 +01:00
parent c292d58d91
commit 74967abd51
2 changed files with 35 additions and 64 deletions

View File

@@ -30,6 +30,16 @@ function safeWriteJson(filePath: string, data: Record<string, unknown>) {
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
}
function asRecord(value: unknown): Record<string, unknown> | null {
return typeof value === "object" && value !== null && !Array.isArray(value)
? (value as Record<string, unknown>)
: null;
}
function readNestedRecord(root: unknown, key: string): Record<string, unknown> | null {
return asRecord(asRecord(root)?.[key]);
}
function setDeep(obj: Record<string, unknown>, keys: string[], value: unknown) {
let node: Record<string, unknown> = obj;
for (const key of keys.slice(0, -1)) {
@@ -65,42 +75,11 @@ export function isProfileDecorated(
const localState = safeReadJson(localStatePath);
const profile = localState?.profile;
const infoCache =
typeof profile === "object" && profile !== null && !Array.isArray(profile)
? (profile as Record<string, unknown>).info_cache
: null;
const info =
typeof infoCache === "object" &&
infoCache !== null &&
!Array.isArray(infoCache) &&
typeof (infoCache as Record<string, unknown>).Default === "object" &&
(infoCache as Record<string, unknown>).Default !== null &&
!Array.isArray((infoCache as Record<string, unknown>).Default)
? ((infoCache as Record<string, unknown>).Default as Record<string, unknown>)
: null;
const info = readNestedRecord(readNestedRecord(profile, "info_cache"), "Default");
const prefs = safeReadJson(preferencesPath);
const browserTheme = (() => {
const browser = prefs?.browser;
const theme =
typeof browser === "object" && browser !== null && !Array.isArray(browser)
? (browser as Record<string, unknown>).theme
: null;
return typeof theme === "object" && theme !== null && !Array.isArray(theme)
? (theme as Record<string, unknown>)
: null;
})();
const autogeneratedTheme = (() => {
const autogenerated = prefs?.autogenerated;
const theme =
typeof autogenerated === "object" && autogenerated !== null && !Array.isArray(autogenerated)
? (autogenerated as Record<string, unknown>).theme
: null;
return typeof theme === "object" && theme !== null && !Array.isArray(theme)
? (theme as Record<string, unknown>)
: null;
})();
const browserTheme = readNestedRecord(prefs?.browser, "theme");
const autogeneratedTheme = readNestedRecord(prefs?.autogenerated, "theme");
const nameOk = typeof info?.name === "string" ? info.name === desiredName : true;

View File

@@ -30,6 +30,12 @@ export const DEFAULT_DOWNLOAD_DIR = path.join(DEFAULT_BROWSER_TMP_DIR, "download
export const DEFAULT_UPLOAD_DIR = path.join(DEFAULT_BROWSER_TMP_DIR, "uploads");
type InvalidPathResult = { ok: false; error: string };
type ResolvePathsWithinRootParams = {
rootDir: string;
requestedPaths: string[];
scopeLabel: string;
};
type ResolvePathsWithinRootResult = { ok: true; paths: string[] } | InvalidPathResult;
function invalidPath(scopeLabel: string): InvalidPathResult {
return {
@@ -146,11 +152,9 @@ export async function resolveWritablePathWithinRoot(params: {
return lexical;
}
export function resolvePathsWithinRoot(params: {
rootDir: string;
requestedPaths: string[];
scopeLabel: string;
}): { ok: true; paths: string[] } | { ok: false; error: string } {
export function resolvePathsWithinRoot(
params: ResolvePathsWithinRootParams,
): ResolvePathsWithinRootResult {
const resolvedPaths: string[] = [];
for (const raw of params.requestedPaths) {
const pathResult = resolvePathWithinRoot({
@@ -166,34 +170,22 @@ export function resolvePathsWithinRoot(params: {
return { ok: true, paths: resolvedPaths };
}
export async function resolveExistingPathsWithinRoot(params: {
rootDir: string;
requestedPaths: string[];
scopeLabel: string;
}): Promise<{ ok: true; paths: string[] } | { ok: false; error: string }> {
return await resolveCheckedPathsWithinRoot({
...params,
allowMissingFallback: true,
});
export async function resolveExistingPathsWithinRoot(
params: ResolvePathsWithinRootParams,
): Promise<ResolvePathsWithinRootResult> {
return await resolveCheckedPathsWithinRoot(params, true);
}
export async function resolveStrictExistingPathsWithinRoot(params: {
rootDir: string;
requestedPaths: string[];
scopeLabel: string;
}): Promise<{ ok: true; paths: string[] } | { ok: false; error: string }> {
return await resolveCheckedPathsWithinRoot({
...params,
allowMissingFallback: false,
});
export async function resolveStrictExistingPathsWithinRoot(
params: ResolvePathsWithinRootParams,
): Promise<ResolvePathsWithinRootResult> {
return await resolveCheckedPathsWithinRoot(params, false);
}
async function resolveCheckedPathsWithinRoot(params: {
rootDir: string;
requestedPaths: string[];
scopeLabel: string;
allowMissingFallback: boolean;
}): Promise<{ ok: true; paths: string[] } | { ok: false; error: string }> {
async function resolveCheckedPathsWithinRoot(
params: ResolvePathsWithinRootParams,
allowMissingFallback: boolean,
): Promise<ResolvePathsWithinRootResult> {
const rootDir = path.resolve(params.rootDir);
// Keep historical behavior for missing roots and rely on openFileWithinRoot for final checks.
const rootRealPath = await resolveRealPathIfExists(rootDir);
@@ -253,7 +245,7 @@ async function resolveCheckedPathsWithinRoot(params: {
});
resolvedPaths.push(opened.realPath);
} catch (err) {
if (params.allowMissingFallback && err instanceof SafeOpenError && err.code === "not-found") {
if (allowMissingFallback && err instanceof SafeOpenError && err.code === "not-found") {
// Preserve historical behavior for paths that do not exist yet.
resolvedPaths.push(pathResult.fallbackPath);
continue;