import "./fs-safe-defaults-C5XkOq6n.js";
import { H as findExistingAncestor, N as isWindowsDriveLetterPath, P as isWindowsNetworkPath, a as inspectPathPermissions, b as ensureDirectoryWithinRoot, c as isWorldReadable, l as isWorldWritable, o as isGroupReadable, p as writeViaSiblingTempPath, s as isGroupWritable, u as modeBits } from "./advanced-B7QGBYXk.js";
import { h as FsSafeError, i as isPathInside, o as isSymlinkOpenError } from "./path-Dzgb252f.js";
import { t as sameFileIdentity } from "./file-identity-C_YRh76t.js";
import { b as root } from "./secure-temp-dir-BK_Pe2D-.js";
import fs, { constants } from "node:fs";
import path from "node:path";
import fs$1 from "node:fs/promises";
//#region node_modules/@kodelyth/fs-safe/dist/secure-file.js
const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in constants;
const OPEN_READ_FLAGS = constants.O_RDONLY | (SUPPORTS_NOFOLLOW ? constants.O_NOFOLLOW : 0);
function isAbsolutePathname(value) {
	return path.isAbsolute(value) || isWindowsDriveLetterPath(value, "win32") || isWindowsNetworkPath(value, "win32");
}
function label(options) {
	return options.label ?? "Secure file";
}
async function openSecureHandle(options) {
	if (isWindowsNetworkPath(options.filePath, "win32") && !options.trust?.allowNetworkPath) throw new FsSafeError("invalid-path", `${label(options)} must be a local absolute path.`);
	if (!isAbsolutePathname(options.filePath)) throw new FsSafeError("invalid-path", `${label(options)} must be an absolute path.`);
	const preStat = await fs$1.lstat(options.filePath).catch((err) => {
		throw new FsSafeError("not-found", `${label(options)} is not readable: ${options.filePath}`, { cause: err });
	});
	if (preStat.isDirectory()) throw new FsSafeError("not-file", `${label(options)} must be a file: ${options.filePath}`);
	if (preStat.isSymbolicLink() && !options.trust?.allowSymlink) throw new FsSafeError("symlink", `${label(options)} must not be a symlink: ${options.filePath}`);
	let handle;
	try {
		handle = await fs$1.open(options.filePath, options.trust?.allowSymlink ? constants.O_RDONLY : OPEN_READ_FLAGS);
	} catch (err) {
		if (isSymlinkOpenError(err)) throw new FsSafeError("symlink", `${label(options)} symlink open blocked`, { cause: err });
		throw err;
	}
	try {
		const openedStat = await handle.stat();
		if (!openedStat.isFile()) throw new FsSafeError("not-file", `${label(options)} must be a file: ${options.filePath}`);
		const pathStat = options.trust?.allowSymlink ? await fs$1.stat(options.filePath) : await fs$1.lstat(options.filePath);
		if (!options.trust?.allowSymlink && pathStat.isSymbolicLink()) throw new FsSafeError("symlink", `${label(options)} must not be a symlink: ${options.filePath}`);
		if (!sameFileIdentity(pathStat, openedStat)) throw new FsSafeError("path-mismatch", `${label(options)} changed during open.`);
		const realPath = await fs$1.realpath(options.filePath);
		if (!sameFileIdentity(await fs$1.stat(realPath), openedStat)) throw new FsSafeError("path-mismatch", `${label(options)} real path changed during open.`);
		if (options.io?.maxBytes !== void 0 && openedStat.size > options.io.maxBytes) throw new FsSafeError("too-large", `${label(options)} exceeded maxBytes (${options.io.maxBytes}).`);
		return {
			handle,
			pathStat: openedStat,
			realPath
		};
	} catch (err) {
		await handle.close().catch(() => void 0);
		throw err;
	}
}
async function assertTrustedDirs(options, realPath) {
	if (!options.trust?.trustedDirs || options.trust.trustedDirs.length === 0) return;
	if (!(await Promise.all(options.trust.trustedDirs.map(async (dir) => {
		const resolved = path.resolve(dir);
		return await fs$1.realpath(resolved).catch(() => resolved);
	}))).some((dir) => isPathInside(dir, realPath))) throw new FsSafeError("outside-workspace", `${label(options)} is outside trustedDirs: ${realPath}`);
}
function inspectOpenedPermissions(stat, platform) {
	const bits = modeBits(typeof stat.mode === "number" ? stat.mode : null);
	return {
		ok: true,
		isSymlink: false,
		isDir: stat.isDirectory(),
		mode: typeof stat.mode === "number" ? stat.mode : null,
		bits,
		source: platform === "win32" ? "unknown" : "posix",
		worldWritable: isWorldWritable(bits),
		groupWritable: isGroupWritable(bits),
		worldReadable: isWorldReadable(bits),
		groupReadable: isGroupReadable(bits)
	};
}
async function assertSecurePermissions(options, stat, realPath) {
	if (options.permissions?.allowInsecure) return;
	const platform = options.inject?.platform ?? process.platform;
	const permissions = platform === "win32" ? await inspectPathPermissions(realPath, options.inject) : inspectOpenedPermissions(stat, platform);
	if (!permissions.ok) throw new FsSafeError("permission-unverified", `${label(options)} permissions could not be verified: ${realPath}`);
	if (platform === "win32" && permissions.source === "unknown") throw new FsSafeError("permission-unverified", `${label(options)} ACL verification unavailable on Windows for ${realPath}.`);
	const writableByOthers = permissions.worldWritable || permissions.groupWritable;
	const readableByOthers = permissions.worldReadable || permissions.groupReadable;
	if (writableByOthers || !options.permissions?.allowReadableByOthers && readableByOthers) throw new FsSafeError("insecure-permissions", `${label(options)} permissions are too open: ${realPath}`);
	if (platform !== "win32" && typeof process.getuid === "function" && stat.uid != null) {
		const uid = process.getuid();
		if (stat.uid !== uid) throw new FsSafeError("not-owned", `${label(options)} must be owned by the current user (uid=${uid}): ${realPath}`);
	}
	return permissions;
}
async function readHandleWithTimeout(handle, timeoutMs) {
	if (timeoutMs === void 0 || !Number.isFinite(timeoutMs) || timeoutMs <= 0) return await handle.readFile();
	let timeout;
	try {
		return await Promise.race([handle.readFile(), new Promise((_resolve, reject) => {
			timeout = setTimeout(() => {
				handle.close().catch(() => void 0);
				reject(new FsSafeError("timeout", `secure file read timed out after ${timeoutMs}ms`));
			}, timeoutMs);
		})]);
	} finally {
		if (timeout) clearTimeout(timeout);
	}
}
async function readSecureFile(options) {
	const opened = await openSecureHandle(options);
	try {
		await assertTrustedDirs(options, opened.realPath);
		const permissions = await assertSecurePermissions(options, opened.pathStat, opened.realPath);
		const buffer = await readHandleWithTimeout(opened.handle, options.io?.timeoutMs);
		if (options.io?.maxBytes !== void 0 && buffer.byteLength > options.io.maxBytes) throw new FsSafeError("too-large", `${label(options)} exceeded maxBytes (${options.io.maxBytes}).`);
		return {
			buffer,
			realPath: opened.realPath,
			stat: opened.pathStat,
			permissions
		};
	} finally {
		await opened.handle.close().catch(() => void 0);
	}
}
//#endregion
//#region node_modules/@kodelyth/fs-safe/dist/walk.js
function kindForDirent(dirent) {
	if (dirent.isDirectory()) return "directory";
	if (dirent.isFile()) return "file";
	if (dirent.isSymbolicLink()) return "symlink";
	return "other";
}
function shouldStop(result, options) {
	return options.maxEntries !== void 0 && result.scannedEntryCount >= Math.max(0, options.maxEntries);
}
function buildEntry(params) {
	const fullPath = path.join(params.dir, params.dirent.name);
	const relativePath = path.relative(params.rootDir, fullPath) || params.dirent.name;
	return {
		name: params.dirent.name,
		path: fullPath,
		relativePath,
		depth: params.depth,
		kind: params.kind ?? kindForDirent(params.dirent),
		dirent: params.dirent
	};
}
function resolveSyncKind(fullPath, dirent, symlinks) {
	const kind = kindForDirent(dirent);
	if (kind !== "symlink") return kind;
	if (symlinks === "skip") return null;
	if (symlinks === "include") return "symlink";
	try {
		const stat = fs.statSync(fullPath);
		if (stat.isDirectory()) return "directory";
		if (stat.isFile()) return "file";
	} catch {
		return null;
	}
	return "other";
}
async function resolveAsyncKind(fullPath, dirent, symlinks) {
	const kind = kindForDirent(dirent);
	if (kind !== "symlink") return kind;
	if (symlinks === "skip") return null;
	if (symlinks === "include") return "symlink";
	try {
		const stat = await fs$1.stat(fullPath);
		if (stat.isDirectory()) return "directory";
		if (stat.isFile()) return "file";
	} catch {
		return null;
	}
	return "other";
}
function walkDirectorySync(rootDir, options = {}) {
	const root = path.resolve(rootDir);
	const symlinks = options.symlinks ?? "skip";
	const result = {
		entries: [],
		scannedEntryCount: 0,
		truncated: false
	};
	const visitedDirs = /* @__PURE__ */ new Set();
	function visit(dir, depth) {
		if (options.maxDepth !== void 0 && depth > options.maxDepth) return;
		let realDir;
		try {
			realDir = fs.realpathSync(dir);
		} catch {
			return;
		}
		if (visitedDirs.has(realDir)) return;
		visitedDirs.add(realDir);
		let entries;
		try {
			entries = fs.readdirSync(dir, { withFileTypes: true });
		} catch {
			return;
		}
		for (const dirent of entries) {
			if (shouldStop(result, options)) {
				result.truncated = true;
				return;
			}
			result.scannedEntryCount += 1;
			const fullPath = path.join(dir, dirent.name);
			const kind = resolveSyncKind(fullPath, dirent, symlinks);
			if (!kind) continue;
			const entry = buildEntry({
				rootDir: root,
				dir,
				dirent,
				depth,
				kind
			});
			if (options.include?.(entry) ?? true) result.entries.push(entry);
			if (kind === "directory" && (options.maxDepth === void 0 || depth < options.maxDepth) && (options.descend?.(entry) ?? true)) {
				visit(fullPath, depth + 1);
				if (result.truncated) return;
			}
		}
	}
	visit(root, 1);
	return result;
}
async function walkDirectory(rootDir, options = {}) {
	const root = path.resolve(rootDir);
	const symlinks = options.symlinks ?? "skip";
	const result = {
		entries: [],
		scannedEntryCount: 0,
		truncated: false
	};
	const visitedDirs = /* @__PURE__ */ new Set();
	async function visit(dir, depth) {
		if (options.maxDepth !== void 0 && depth > options.maxDepth) return;
		let realDir;
		try {
			realDir = await fs$1.realpath(dir);
		} catch {
			return;
		}
		if (visitedDirs.has(realDir)) return;
		visitedDirs.add(realDir);
		let entries;
		try {
			entries = await fs$1.readdir(dir, { withFileTypes: true });
		} catch {
			return;
		}
		for (const dirent of entries) {
			if (shouldStop(result, options)) {
				result.truncated = true;
				return;
			}
			result.scannedEntryCount += 1;
			const fullPath = path.join(dir, dirent.name);
			const kind = await resolveAsyncKind(fullPath, dirent, symlinks);
			if (!kind) continue;
			const entry = buildEntry({
				rootDir: root,
				dir,
				dirent,
				depth,
				kind
			});
			if (options.include?.(entry) ?? true) result.entries.push(entry);
			if (kind === "directory" && (options.maxDepth === void 0 || depth < options.maxDepth) && (options.descend?.(entry) ?? true)) {
				await visit(fullPath, depth + 1);
				if (result.truncated) return;
			}
		}
	}
	await visit(root, 1);
	return result;
}
//#endregion
//#region src/infra/fs-safe.ts
async function ensureAbsoluteDirectory(dirPath, options) {
	const absolutePath = path.resolve(dirPath);
	const scopeLabel = options?.scopeLabel ?? "directory";
	const existingAncestor = await findExistingAncestor(absolutePath);
	if (!existingAncestor) return {
		ok: false,
		error: /* @__PURE__ */ new Error(`Invalid path: must stay within ${scopeLabel}`)
	};
	if (existingAncestor === absolutePath) {
		try {
			const stat = await fs$1.lstat(absolutePath);
			if (!stat.isSymbolicLink() && stat.isDirectory()) return {
				ok: true,
				path: absolutePath
			};
		} catch {}
		return {
			ok: false,
			error: /* @__PURE__ */ new Error(`Invalid path: must stay within ${scopeLabel}`)
		};
	}
	const result = await ensureDirectoryWithinRoot({
		rootDir: existingAncestor,
		requestedPath: path.relative(existingAncestor, absolutePath),
		scopeLabel,
		mode: options?.mode
	});
	if (result.ok) return result;
	return {
		ok: false,
		error: new Error(result.error)
	};
}
async function writeExternalFileWithinRoot(options) {
	const targetPath = path.resolve(options.rootDir, options.path);
	await writeViaSiblingTempPath({
		rootDir: options.rootDir,
		targetPath,
		writeTemp: options.write,
		fallbackFileName: options.fallbackFileName,
		tempPrefix: options.tempPrefix
	});
	return { path: targetPath };
}
/** @deprecated Use root(rootDir).read(relativePath, options). */
async function readFileWithinRoot(params) {
	return await (await root(params.rootDir)).read(params.relativePath, {
		hardlinks: params.rejectHardlinks === false ? "allow" : "reject",
		maxBytes: params.maxBytes,
		nonBlockingRead: params.nonBlockingRead,
		symlinks: params.allowSymlinkTargetWithinRoot === true ? "follow-within-root" : "reject"
	});
}
/** @deprecated Use root(rootDir).write(relativePath, data, options). */
async function writeFileWithinRoot(params) {
	await (await root(params.rootDir)).write(params.relativePath, params.data, {
		encoding: params.encoding,
		mkdir: params.mkdir
	});
}
//#endregion
export { walkDirectory as a, writeFileWithinRoot as i, readFileWithinRoot as n, walkDirectorySync as o, writeExternalFileWithinRoot as r, readSecureFile as s, ensureAbsoluteDirectory as t };
