import { h as pathExists } from "./fs-safe-DiVKX-F2.js";
import { _ as assertCanonicalPathWithinBase } from "./channel-catalog-registry-D2drDK4i.js";
import { c as resolvePackedRootDir, n as loadZipArchiveWithPreflight, s as resolveArchiveKind, t as extractArchive } from "./archive-DkzH7hK_.js";
import { p as resolveUserPath } from "./utils-Ba75iQOz.js";
import { r as runCommandWithTimeout } from "./exec-BN4Fyfp2.js";
import "./archive-CIFeaTPP.js";
import { a as withTempDir } from "./install-source-utils-CEkXWmbY.js";
import { n as createSafeNpmInstallEnv, t as createSafeNpmInstallArgs } from "./safe-package-install-Ssz6cNEs.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region src/infra/extract-zip-parallel.ts
const ZIP_UNIX_FILE_TYPE_MASK = 61440;
const ZIP_UNIX_SYMLINK_TYPE = 40960;
function isZipSymlink(unixPerms) {
	return typeof unixPerms === "number" && (unixPerms & ZIP_UNIX_FILE_TYPE_MASK) === ZIP_UNIX_SYMLINK_TYPE;
}
/**
* Fast parallel ZIP extractor for fresh (empty) destination directories.
*
* @openclaw/fs-safe's extractZip calls fs.lstat for every path segment of
* every entry to detect pre-existing symlinks. On Windows NTFS each lstat
* costs ~20ms, so a 200-entry archive takes 200×3×20ms ≈ 12s in lstat alone,
* then another ~10s for the mergeExtractedTreeIntoDestination second pass.
*
* This extractor uses string-based path.resolve comparison instead of lstat.
* That is safe when destDir is a freshly-created empty directory — the caller
* (withExtractedArchiveRoot) always passes such a dir. A fresh dir cannot
* contain pre-existing symlinks, so lstat traversal detection is redundant.
*
* Security guarantees preserved:
*   - Zip-slip / path traversal: blocked via path.resolve string check.
*   - Symlink entries: rejected by inspecting JSZip unixPermissions bits.
*   - Archive size / entry count limits: enforced by loadZipArchiveWithPreflight.
*/
async function extractZipParallel(params) {
	const destDirResolved = path.resolve(params.destDir);
	const destDirPrefix = destDirResolved + path.sep;
	function assertInside(entryName) {
		const resolved = path.resolve(destDirResolved, entryName);
		if (resolved !== destDirResolved && !resolved.startsWith(destDirPrefix)) throw new Error(`archive path traversal: ${entryName}`);
		return resolved;
	}
	const zip = await loadZipArchiveWithPreflight(await fs.readFile(params.archivePath), void 0);
	const entries = Object.values(zip.files);
	for (const entry of entries) {
		if (!entry.name) continue;
		assertInside(entry.name);
		if (!entry.dir && isZipSymlink(entry.unixPermissions)) throw new Error(`zip entry is a symlink: ${entry.name}`);
	}
	const dirs = /* @__PURE__ */ new Set();
	for (const entry of entries) {
		if (!entry.name) continue;
		const resolved = path.resolve(destDirResolved, entry.name);
		dirs.add(entry.dir ? resolved : path.dirname(resolved));
	}
	for (const dir of [...dirs].sort()) await fs.mkdir(dir, { recursive: true });
	await Promise.all(entries.filter((e) => !e.dir && e.name).map(async (entry) => {
		const outPath = path.resolve(destDirResolved, entry.name);
		const content = await entry.async("nodebuffer");
		await fs.writeFile(outPath, content);
	}));
}
//#endregion
//#region src/infra/install-flow.ts
async function resolveExistingInstallPath(inputPath) {
	const resolvedPath = resolveUserPath(inputPath);
	if (!await pathExists(resolvedPath)) return {
		ok: false,
		error: `path not found: ${resolvedPath}`
	};
	return {
		ok: true,
		resolvedPath,
		stat: await fs.stat(resolvedPath)
	};
}
async function withExtractedArchiveRoot(params) {
	return await withTempDir(params.tempDirPrefix, async (tmpDir) => {
		const extractDir = path.join(tmpDir, "extract");
		await fs.mkdir(extractDir, { recursive: true });
		params.logger?.info?.(`Extracting ${params.archivePath}…`);
		try {
			if (resolveArchiveKind(params.archivePath) === "zip") await extractZipParallel({
				archivePath: params.archivePath,
				destDir: extractDir
			});
			else await extractArchive({
				archivePath: params.archivePath,
				destDir: extractDir,
				timeoutMs: params.timeoutMs,
				logger: params.logger
			});
		} catch (err) {
			return {
				ok: false,
				error: `failed to extract archive: ${String(err)}`
			};
		}
		let rootDir = "";
		try {
			rootDir = await resolvePackedRootDir(extractDir, { rootMarkers: params.rootMarkers });
		} catch (err) {
			return {
				ok: false,
				error: String(err)
			};
		}
		return await params.onExtracted(rootDir);
	});
}
//#endregion
//#region src/infra/install-package-dir.ts
const INSTALL_BASE_CHANGED_ERROR_MESSAGE = "install base directory changed during install";
const INSTALL_BASE_CHANGED_ABORT_WARNING = "Install base directory changed during install; aborting staged publish.";
const INSTALL_BASE_CHANGED_BACKUP_WARNING = "Install base directory changed before backup cleanup; leaving backup in place.";
const STAGED_NPM_PROJECT_CONFIG_NAME = ".npmrc";
const STAGED_NPM_PROJECT_CONFIG_PREFIX = ".openclaw-install-hidden-npmrc-";
const WINDOWS_RENAME_RETRY_DELAYS_MS = [
	50,
	100,
	200,
	400,
	800
];
const WINDOWS_TRANSIENT_RENAME_CODES = new Set([
	"EPERM",
	"EBUSY",
	"EACCES"
]);
function isTransientWindowsRenameError(err) {
	if (process.platform !== "win32") return false;
	const code = err?.code;
	return typeof code === "string" && WINDOWS_TRANSIENT_RENAME_CODES.has(code);
}
function sleep(ms) {
	return new Promise((resolve) => setTimeout(resolve, ms));
}
async function renameWithWindowsRetry(from, to) {
	try {
		await fs.rename(from, to);
		return;
	} catch (err) {
		if (!isTransientWindowsRenameError(err)) throw err;
		for (const delay of WINDOWS_RENAME_RETRY_DELAYS_MS) {
			await sleep(delay);
			try {
				await fs.rename(from, to);
				return;
			} catch (retryErr) {
				if (!isTransientWindowsRenameError(retryErr)) throw retryErr;
			}
		}
		let cpErr = null;
		for (const delay of [0, ...WINDOWS_RENAME_RETRY_DELAYS_MS]) {
			if (delay > 0) await sleep(delay);
			try {
				await fs.cp(from, to, {
					recursive: true,
					force: true,
					verbatimSymlinks: true
				});
				cpErr = null;
				break;
			} catch (err2) {
				cpErr = err2;
				if (!isTransientWindowsRenameError(err2)) throw err2;
			}
		}
		if (cpErr) throw cpErr;
		await fs.rm(from, {
			recursive: true,
			force: true,
			maxRetries: 5,
			retryDelay: 100
		}).catch(() => void 0);
	}
}
function isObjectRecord(value) {
	return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
async function sanitizeManifestForNpmInstall(targetDir) {
	const manifestPath = path.join(targetDir, "package.json");
	let manifestRaw = "";
	try {
		manifestRaw = await fs.readFile(manifestPath, "utf-8");
	} catch {
		return;
	}
	let manifest;
	try {
		const parsed = JSON.parse(manifestRaw);
		if (!isObjectRecord(parsed)) return;
		manifest = parsed;
	} catch {
		return;
	}
	const devDependencies = manifest.devDependencies;
	if (!isObjectRecord(devDependencies)) return;
	const filteredEntries = Object.entries(devDependencies).filter(([, rawSpec]) => {
		return !(typeof rawSpec === "string" ? rawSpec.trim() : "").startsWith("workspace:");
	});
	if (filteredEntries.length === Object.keys(devDependencies).length) return;
	if (filteredEntries.length === 0) delete manifest.devDependencies;
	else manifest.devDependencies = Object.fromEntries(filteredEntries);
	await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf-8");
}
async function hideProjectNpmConfigForInstall(targetDir) {
	const originalPath = path.join(targetDir, STAGED_NPM_PROJECT_CONFIG_NAME);
	let hiddenDir = "";
	try {
		hiddenDir = await fs.mkdtemp(path.join(targetDir, STAGED_NPM_PROJECT_CONFIG_PREFIX));
		const hiddenPath = path.join(hiddenDir, STAGED_NPM_PROJECT_CONFIG_NAME);
		await fs.rename(originalPath, hiddenPath);
		return {
			hiddenDir,
			originalPath,
			hiddenPath
		};
	} catch (error) {
		if (hiddenDir) await fs.rm(hiddenDir, {
			recursive: true,
			force: true
		}).catch(() => void 0);
		if (error.code === "ENOENT") return null;
		throw error;
	}
}
async function restoreProjectNpmConfigAfterInstall(hiddenConfig) {
	if (!hiddenConfig) return;
	await fs.rename(hiddenConfig.hiddenPath, hiddenConfig.originalPath);
	await fs.rm(hiddenConfig.hiddenDir, {
		recursive: true,
		force: true
	});
}
async function assertInstallBoundaryPaths(params) {
	for (const candidatePath of params.candidatePaths) await assertCanonicalPathWithinBase({
		baseDir: params.installBaseDir,
		candidatePath,
		boundaryLabel: "install directory"
	});
}
function isRelativePathInsideBase(relativePath) {
	return Boolean(relativePath) && relativePath !== ".." && !relativePath.startsWith(`..${path.sep}`);
}
function isInstallBaseChangedError(error) {
	return error instanceof Error && error.message === INSTALL_BASE_CHANGED_ERROR_MESSAGE;
}
async function assertInstallBaseStable(params) {
	if (!(await fs.stat(params.installBaseDir)).isDirectory()) throw new Error(INSTALL_BASE_CHANGED_ERROR_MESSAGE);
	if (await fs.realpath(params.installBaseDir) !== params.expectedRealPath) throw new Error(INSTALL_BASE_CHANGED_ERROR_MESSAGE);
}
async function cleanupInstallTempDir(dirPath) {
	if (!dirPath) return;
	await fs.rm(dirPath, {
		recursive: true,
		force: true
	}).catch(() => void 0);
}
async function resolveInstallPublishTarget(params) {
	const installBaseResolved = path.resolve(params.installBaseDir);
	const targetResolved = path.resolve(params.targetDir);
	const targetRelativePath = path.relative(installBaseResolved, targetResolved);
	if (!isRelativePathInsideBase(targetRelativePath)) throw new Error("invalid install target path");
	const installBaseRealPath = await fs.realpath(params.installBaseDir);
	return {
		installBaseRealPath,
		canonicalTargetDir: path.join(installBaseRealPath, targetRelativePath)
	};
}
async function installPackageDir(params) {
	params.logger?.info?.(`Installing to ${params.targetDir}…`);
	const installBaseDir = path.dirname(params.targetDir);
	let initialInstallBaseRealPath;
	try {
		await fs.mkdir(installBaseDir, { recursive: true });
		initialInstallBaseRealPath = await fs.realpath(installBaseDir);
		await assertInstallBoundaryPaths({
			installBaseDir,
			candidatePaths: [params.targetDir]
		});
	} catch (err) {
		return {
			ok: false,
			error: `${params.copyErrorPrefix}: ${String(err)}`
		};
	}
	let installBaseRealPath;
	let canonicalTargetDir;
	try {
		({installBaseRealPath, canonicalTargetDir} = await resolveInstallPublishTarget({
			installBaseDir,
			targetDir: params.targetDir
		}));
		if (installBaseRealPath !== initialInstallBaseRealPath) throw new Error(INSTALL_BASE_CHANGED_ERROR_MESSAGE);
	} catch (err) {
		if (isInstallBaseChangedError(err)) params.logger?.warn?.(INSTALL_BASE_CHANGED_ABORT_WARNING);
		return {
			ok: false,
			error: `${params.copyErrorPrefix}: ${String(err)}`
		};
	}
	let stageDir = null;
	let backupDir = null;
	const fail = async (error, cause) => {
		if (isInstallBaseChangedError(cause)) params.logger?.warn?.(INSTALL_BASE_CHANGED_ABORT_WARNING);
		else {
			await restoreBackup();
			if (stageDir) {
				await cleanupInstallTempDir(stageDir);
				stageDir = null;
			}
		}
		return {
			ok: false,
			error
		};
	};
	const failWithCode = async (params, cause) => {
		const failed = await fail(params.error, cause);
		return params.code ? {
			...failed,
			code: params.code
		} : failed;
	};
	const restoreBackup = async () => {
		if (!backupDir) return;
		await renameWithWindowsRetry(backupDir, canonicalTargetDir).catch(() => void 0);
		backupDir = null;
	};
	try {
		await assertInstallBoundaryPaths({
			installBaseDir: installBaseRealPath,
			candidatePaths: [canonicalTargetDir]
		});
		stageDir = await fs.mkdtemp(path.join(installBaseRealPath, ".openclaw-install-stage-"));
		await fs.cp(params.sourceDir, stageDir, {
			recursive: true,
			verbatimSymlinks: true
		});
	} catch (err) {
		return await fail(`${params.copyErrorPrefix}: ${String(err)}`, err);
	}
	try {
		await params.afterCopy?.(stageDir);
	} catch (err) {
		return await fail(`post-copy validation failed: ${String(err)}`, err);
	}
	if (params.hasDeps) try {
		await sanitizeManifestForNpmInstall(stageDir);
		const hiddenProjectNpmConfig = await hideProjectNpmConfigForInstall(stageDir);
		params.logger?.info?.(params.depsLogMessage);
		const npmRes = await (async () => {
			try {
				return await runCommandWithTimeout(["npm", ...createSafeNpmInstallArgs({
					omitDev: true,
					loglevel: "error"
				})], {
					timeoutMs: Math.max(params.timeoutMs, 3e5),
					cwd: stageDir,
					env: createSafeNpmInstallEnv(process.env)
				});
			} finally {
				await restoreProjectNpmConfigAfterInstall(hiddenProjectNpmConfig);
			}
		})();
		if (npmRes.code !== 0) return await fail(`npm install failed: ${npmRes.stderr.trim() || npmRes.stdout.trim()}`);
	} catch (error) {
		return await fail(`npm install failed: ${String(error)}`, error);
	}
	if (params.afterInstall) try {
		const postInstallResult = await params.afterInstall(stageDir);
		if (!postInstallResult.ok) return await failWithCode(postInstallResult);
	} catch (err) {
		return await fail(`post-install validation failed: ${String(err)}`, err);
	}
	if (params.mode === "update" && await pathExists(canonicalTargetDir)) {
		const backupRoot = path.join(installBaseRealPath, ".openclaw-install-backups");
		backupDir = path.join(backupRoot, `${path.basename(canonicalTargetDir)}-${Date.now()}`);
		try {
			await fs.mkdir(backupRoot, { recursive: true });
			await assertInstallBoundaryPaths({
				installBaseDir: installBaseRealPath,
				candidatePaths: [backupDir]
			});
			await assertInstallBaseStable({
				installBaseDir,
				expectedRealPath: installBaseRealPath
			});
			await renameWithWindowsRetry(canonicalTargetDir, backupDir);
		} catch (err) {
			return await fail(`${params.copyErrorPrefix}: ${String(err)}`, err);
		}
	}
	try {
		await assertInstallBaseStable({
			installBaseDir,
			expectedRealPath: installBaseRealPath
		});
		await renameWithWindowsRetry(stageDir, canonicalTargetDir);
		stageDir = null;
	} catch (err) {
		return await fail(`${params.copyErrorPrefix}: ${String(err)}`, err);
	}
	if (backupDir) try {
		await assertInstallBaseStable({
			installBaseDir,
			expectedRealPath: installBaseRealPath
		});
	} catch (err) {
		if (isInstallBaseChangedError(err)) params.logger?.warn?.(INSTALL_BASE_CHANGED_BACKUP_WARNING);
		backupDir = null;
	}
	if (backupDir) await fs.rm(backupDir, {
		recursive: true,
		force: true
	}).catch(() => void 0);
	if (stageDir) await cleanupInstallTempDir(stageDir);
	return { ok: true };
}
async function installPackageDirWithManifestDeps(params) {
	return installPackageDir({
		...params,
		hasDeps: Object.keys(params.manifestDependencies ?? {}).length > 0
	});
}
//#endregion
export { withExtractedArchiveRoot as i, installPackageDirWithManifestDeps as n, resolveExistingInstallPath as r, installPackageDir as t };
