// @generated by scripts/build-runtime.mjs; do not edit. // @ts-nocheck -- generated JavaScript uses a .ts extension for Pi's Jiti loader. // src/git-config.ts var DEFAULT_GIT_BRANCH = "pi-sync"; var DEFAULT_GIT_DIRECTORY = "pi-sync"; function normalizeGitRemote(value) { const normalized = normalizeOptionalString(value); if (!normalized) return void 0; if (hasControlCharacter(normalized) || /\s/u.test(normalized) || normalized.startsWith("-") || normalized.includes("\\")) { throw new Error("Invalid pi-sync Git remote."); } if (!normalized.includes("://") && /^(?:[A-Za-z0-9._-]+@)?(?:\[[0-9A-Fa-f:]+\]|[A-Za-z0-9.-]+):(?!-)[^:].+$/u.test(normalized)) { return normalized; } if (/^[A-Za-z][A-Za-z0-9+.-]*:/u.test(normalized) && !normalized.includes("://")) { throw new Error("Invalid pi-sync Git remote: unsupported transport or remote-helper syntax."); } let url; try { url = new URL(normalized); } catch { throw new Error("Invalid pi-sync Git remote: use an SSH or HTTPS remote."); } if (url.protocol !== "https:" && url.protocol !== "ssh:") { throw new Error("Invalid pi-sync Git remote: only SSH and HTTPS are supported."); } if (url.password || url.protocol === "https:" && url.username) { throw new Error("Invalid pi-sync Git remote: URL credentials or userinfo are not allowed."); } if (url.search || url.hash || !url.hostname || !url.pathname || url.pathname === "/") { throw new Error("Invalid pi-sync Git remote."); } return url.toString(); } function normalizeGitRemoteIdentity(value) { const normalized = normalizeGitRemote(value); if (!normalized) return ""; if (!normalized.includes("://")) { const match = /^(?[A-Za-z0-9._-]+@)?(?\[[0-9A-Fa-f:]+\]|[A-Za-z0-9.-]+):(?.+)$/u.exec( normalized ); if (!match?.groups) return normalized; const userAndHost = `${match.groups.user ?? ""}${match.groups.host.toLowerCase()}`; const remotePath = match.groups.path.replace(/\/+$/gu, ""); return remotePath.startsWith("/") ? `scp-absolute://${userAndHost}${remotePath}` : `ssh://${userAndHost}/${remotePath}`; } const url = new URL(normalized); url.hostname = url.hostname.toLowerCase(); if (url.protocol === "ssh:" && url.port === "22") url.port = ""; url.pathname = url.pathname.replace(/\/+$/gu, ""); if (url.protocol === "ssh:") { return `ssh://${url.username ? `${url.username}@` : ""}${url.host}${url.pathname}`; } return url.toString(); } function normalizeGitBranch(value) { const branch = normalizeOptionalString(value) ?? DEFAULT_GIT_BRANCH; if (branch === "@" || branch.startsWith("-") || branch.startsWith("refs/") || branch.startsWith("/") || branch.endsWith("/") || branch.endsWith(".") || branch.includes("..") || branch.includes("@{") || branch.includes("\\") || hasControlCharacter(branch) || /[ ~^:?*[\]]/u.test(branch) || branch.split("/").some((segment) => !segment || segment.startsWith(".") || segment.endsWith(".lock"))) { throw new Error("Invalid pi-sync Git branch."); } return branch; } function normalizeGitDirectory(value) { const directory = trimSlashes(normalizeOptionalString(value) ?? DEFAULT_GIT_DIRECTORY); if (!directory || directory.startsWith("-") || directory.includes("\\") || hasControlCharacter(directory) || directory.split("/").some((segment) => !segment || segment === "." || segment === ".." || segment === ".git")) { throw new Error("Invalid pi-sync Git directory."); } return directory; } function validateGitNamespace(value) { if (value.length > 256 || value === "." || value === ".." || value.includes("/") || value.includes("\\") || hasControlCharacter(value)) { throw new Error("Invalid pi-sync Git namespace."); } } function normalizeOptionalString(value) { if (value !== void 0 && typeof value !== "string") { throw new Error("Invalid pi-sync settings: expected a string."); } const normalized = value?.trim(); return normalized || void 0; } function trimSlashes(value) { return value.replace(/^\/+|\/+$/gu, ""); } function hasControlCharacter(value) { return [...value].some((character) => { const code = character.codePointAt(0) ?? 0; return code < 32 || code >= 127 && code <= 159; }); } // src/state-directory.ts import { lstatSync } from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; import { getAgentDir } from "@earendil-works/pi-coding-agent"; import lockfile from "proper-lockfile"; // src/lock-policy.ts var LOCK_GUARD_STALE_MS = 3e4; var LOCK_GUARD_UPDATE_MS = 1e4; // src/lockfile-fs.ts import { mkdir, mkdirSync, realpath, realpathSync, rmdir, rmdirSync, stat, statSync, utimes, utimesSync } from "node:fs"; var LOCKFILE_FS_ADAPTER = { mkdir, mkdirSync, realpath, realpathSync, rmdir, rmdirSync, stat, statSync, utimes, utimesSync }; // src/state-directory.ts var CANONICAL_DIRECTORY_NAME = "pi-sync"; var LEGACY_DIRECTORY_NAME = ".pisync"; var MIGRATION_LOCK_NAME = ".pi-sync-state-migration.lock"; var MIGRATION_LOCK_STALE_MS = 3e4; var MIGRATION_LOCK_UPDATE_MS = 1e4; var sharedStateDirectoryGuards = /* @__PURE__ */ new Map(); function stateDir() { const roots = inspectStateRoots(); if (roots.canonical) return canonicalStateDir(); if (roots.legacy) return legacyStateDir(); return canonicalStateDir(); } function legacyStateDir() { return path.join(getAgentDir(), LEGACY_DIRECTORY_NAME); } function stateDirectoryMigrationNotice() { const roots = inspectStateRoots(); if (!roots.legacy) return void 0; return "Legacy pi-sync state is still stored in .pisync. Close other Pi sessions, then run /sync migrate-state to move it to pi-sync/."; } async function withStateDirectoryAccess(fn) { const roots = inspectStateRoots(); if (!roots.legacy) return fn(); return runWithStateDirectoryGuard(await acquireSharedStateDirectoryGuard(), fn); } async function migrateLegacyStateDirectory() { const initial = inspectStateRoots(); if (initial.canonical || !initial.legacy) return { status: "ready" }; let guard; try { guard = await acquireStateDirectoryGuard(); } catch (error) { if (!isLockHeldError(error)) throw error; return { status: "deferred", message: "pi-sync state migration was deferred because another state user is active. Close other Pi sessions and retry." }; } return runWithStateDirectoryGuard(guard, async () => { const roots = inspectStateRoots(); if (roots.canonical || !roots.legacy) return { status: "ready" }; const legacyLock = path.join(legacyStateDir(), "lock"); const legacyGuardHeld = await lockfile.check(legacyLock, { fs: LOCKFILE_FS_ADAPTER, lockfilePath: `${legacyLock}.guard`, realpath: false, stale: LOCK_GUARD_STALE_MS }); if (legacyGuardHeld || await pathExists(legacyLock)) { return { status: "deferred", message: "pi-sync state migration was deferred because the legacy directory is busy. Close other Pi sessions, clear any confirmed stale sync lock, and retry." }; } guard.throwIfCompromised(); await fs.rename(legacyStateDir(), canonicalStateDir()); guard.throwIfCompromised(); inspectStateRoots(); return { status: "migrated", message: `Migrated pi-sync state from ${legacyStateDir()} to ${canonicalStateDir()}.` }; }); } async function acquireSharedStateDirectoryGuard() { const key = migrationLockPath(); for (; ; ) { let shared = sharedStateDirectoryGuards.get(key); if (shared?.closing) { await shared.closing; continue; } if (!shared) { shared = { guard: acquireStateDirectoryGuard(), users: 0 }; sharedStateDirectoryGuards.set(key, shared); } shared.users += 1; let guard; try { guard = await shared.guard; } catch (error) { if (sharedStateDirectoryGuards.get(key) === shared) { sharedStateDirectoryGuards.delete(key); } throw error; } let released = false; return { throwIfCompromised: guard.throwIfCompromised, release: async () => { if (released) return; released = true; shared.users -= 1; if (shared.users > 0) return; const closing = guard.release(); shared.closing = closing; try { await closing; } finally { if (sharedStateDirectoryGuards.get(key) === shared) { sharedStateDirectoryGuards.delete(key); } } } }; } } async function acquireStateDirectoryGuard() { let compromisedError; const release = await lockfile.lock(getAgentDir(), { fs: LOCKFILE_FS_ADAPTER, lockfilePath: migrationLockPath(), realpath: false, stale: MIGRATION_LOCK_STALE_MS, update: MIGRATION_LOCK_UPDATE_MS, retries: { retries: 20, minTimeout: 10, maxTimeout: 50 }, onCompromised: (error) => { compromisedError = error; } }); return { release, throwIfCompromised: () => { if (compromisedError) throw compromisedError; } }; } async function runWithStateDirectoryGuard(guard, fn) { let result; let failed = false; let failure; try { guard.throwIfCompromised(); result = await fn(); guard.throwIfCompromised(); } catch (error) { failed = true; failure = error; } try { await guard.release(); } catch (error) { if (!failed) { failed = true; failure = error; } } if (failed) throw failure; return result; } function isLockHeldError(error) { return error.code === "ELOCKED"; } function canonicalStateDir() { return path.join(getAgentDir(), CANONICAL_DIRECTORY_NAME); } function migrationLockPath() { return path.join(getAgentDir(), MIGRATION_LOCK_NAME); } function inspectStateRoots() { const canonical = isDirectoryRoot(canonicalStateDir(), "canonical"); const legacy = isDirectoryRoot(legacyStateDir(), "legacy"); if (canonical && legacy) { throw new Error( `Both legacy ${legacyStateDir()} and canonical ${canonicalStateDir()} pi-sync state directories exist. Close all Pi sessions and reconcile the directories manually; pi-sync will not merge or delete either directory.` ); } return { canonical, legacy }; } function isDirectoryRoot(directory, label) { try { const entry = lstatSync(directory); if (entry.isSymbolicLink()) { throw new Error( `Refusing to use ${label} pi-sync state directory symbolic link: ${directory}` ); } if (!entry.isDirectory()) { throw new Error(`${label} pi-sync state path is not a directory: ${directory}`); } return true; } catch (error) { if (error.code === "ENOENT") return false; throw error; } } async function pathExists(filePath) { try { await fs.lstat(filePath); return true; } catch (error) { if (error.code === "ENOENT") return false; throw error; } } export { normalizeGitRemote, normalizeGitRemoteIdentity, normalizeGitBranch, normalizeGitDirectory, validateGitNamespace, LOCK_GUARD_STALE_MS, LOCK_GUARD_UPDATE_MS, LOCKFILE_FS_ADAPTER, stateDir, stateDirectoryMigrationNotice, withStateDirectoryAccess, migrateLegacyStateDirectory }; //# sourceMappingURL=chunk-F5QL2GPY.ts.map