import type { LockFile, ModelManifest, DependencySpec, ExtendedDependencySpec, PathAliases, WorkspaceManagerOptions } from './types.js'; /** * Coordinates workspace discovery and manifest/lock file reading. * * **Multi-Root Support:** * Maintains separate contexts for each workspace root (directory with model.yaml). * This enables correct resolution in multi-project setups where sub-projects * have their own model.yaml files. * * This is a read-only service for the LSP - it does NOT: * - Generate lock files (use CLI: `dlang install`) * - Download packages (use CLI: `dlang install`) * - Make network requests * * The LSP uses this to: * - Find the workspace root (where model.yaml is) * - Read manifest configuration (path aliases, dependencies) * - Read lock file (to resolve cached package locations) */ export declare class ManifestManager { private readonly manifestFiles; private readonly lockFiles; /** * Cache of workspace contexts by resolved workspace root path. * Supports multiple independent workspaces in a single session. */ private readonly workspaceContexts; /** * Cache mapping start paths to their resolved workspace roots. * Avoids repeated directory tree walking for the same paths. */ private readonly pathToRootCache; /** * PRS-017 R11: Cached set of directories known to contain a manifest file. * Populated during `findWorkspaceRoot()` walks and updated incrementally * when manifest creation/deletion events arrive via `onManifestEvent()`. * Prevents redundant filesystem walks for paths already explored. */ private readonly knownManifestDirs; /** * The currently active workspace root (set by last initialize() call). * Used by methods like getWorkspaceRoot(), getManifest(), etc. * * SAFETY: This is shared mutable state that can be clobbered when concurrent * initialize() calls interleave. Each call sets activeRoot immediately after * the async findWorkspaceRoot() resolves, so two overlapping calls for different * paths can leave activeRoot pointing at whichever resolved last. In practice * this is benign because: (a) each workspace context is independently cached in * workspaceContexts, and (b) the LSP serialises most document operations. * A full mutex would eliminate the race but adds significant complexity. */ private activeRoot; constructor(options?: WorkspaceManagerOptions); /** * Returns the active workspace context, or undefined if not initialized. * All methods that need context should call this after ensureInitialized(). */ private getActiveContext; /** * Finds the workspace root and loads any existing lock file. * * **Multi-Root Support:** * Each call may switch to a different workspace context based on the startPath. * The workspace root is the nearest ancestor directory containing model.yaml. * * @param startPath - Directory to start searching from (usually document directory) */ initialize(startPath: string): Promise; /** * Initializes a workspace context by loading its lock file. */ private initializeContext; /** * Returns the absolute path of the workspace root. * @throws Error if {@link initialize} has not completed successfully. */ getWorkspaceRoot(): string; /** * Returns the project-local package cache directory. * Per PRS-010: .dlang/packages/ * * If the current workspace root is inside a cached package, * walks up to find the actual project root's cache directory. */ getCacheDir(): string; /** * Finds the actual project root when inside a cached package. * * Cached packages are stored in: /.dlang/packages//// * If workspaceRoot is inside this structure, returns * Otherwise returns workspaceRoot unchanged. */ private findProjectRootFromCache; /** * Resolves the manifest file path within the workspace, if present. */ getManifestPath(): Promise; /** * Returns the parsed manifest when present, otherwise undefined. * Uses cached contents when unchanged on disk. */ getManifest(): Promise; /** * Returns the cached manifest synchronously (if available). * Used by LSP features that need synchronous access (like completion). * Returns undefined if manifest hasn't been loaded yet. */ getCachedManifest(): ModelManifest | undefined; /** * Ensures the manifest is loaded and returns it. * Use this over getCachedManifest() when you need to guarantee the manifest * is available (e.g., in async LSP operations like completions). * * @returns The manifest or undefined if no model.yaml exists */ ensureManifestLoaded(): Promise; /** * Gets the currently cached lock file. * Returns undefined if no lock file exists (run `dlang install` to create one). */ getLockFile(): Promise; /** * Reloads the lock file from disk. */ refreshLockFile(): Promise; /** * Invalidates all cached data (manifest and lock file). * Call this when config files change externally (e.g., from CLI commands). * * After invalidation, the next call to getManifest() or getLockFile() * will re-read from disk. */ invalidateCache(): void; /** * Invalidates only the manifest cache. * Call this when model.yaml changes. */ invalidateManifestCache(): void; /** * Invalidates only the lock file cache. * Call this when model.lock changes. */ invalidateLockCache(): void; /** * PRS-017 R11: Incrementally updates the workspace layout cache * when a manifest file is created or deleted. * * @param manifestDir - Directory where the manifest was created/deleted * @param created - true if manifest was created, false if deleted */ onManifestEvent(manifestDir: string, created: boolean): void; /** * Returns the path aliases from the manifest, if present. */ getPathAliases(): Promise; /** * Normalizes a dependency entry to its extended form. * Handles both short form (string version) and extended form (object). * * In the new format, the key IS the owner/package, so source is derived from key * ONLY for git dependencies (not for path-based local dependencies). */ normalizeDependency(key: string, dep: DependencySpec): ExtendedDependencySpec; /** * Resolves a dependency import specifier to its cached package path. * * @param specifier - Import specifier (owner/package format, may include subpaths) * @returns Path to the cached package entry point, or undefined if not found */ resolveDependencyPath(specifier: string): Promise; /** * Reads the entry point from a cached package's model.yaml. */ private readPackageEntry; private ensureInitialized; private loadLockFileFromDisk; private tryReadLockFile; private loadManifest; /** * Reads, validates, and caches a manifest file. */ private readAndCacheManifest; /** * Handles errors from manifest loading, distinguishing recoverable * errors (missing file, parse errors) from unexpected ones. */ private handleManifestError; /** * Clears the manifest cache on the given context, if available. */ private clearManifestCache; /** * Validates manifest structure and dependency configurations. * Throws detailed errors for invalid manifests. * * Supports both new format (owner/package: version) and extended format. */ private validateManifest; /** * Validates path aliases for security and correctness. */ private validatePathAliases; /** * Validates local path dependencies for security. * Ensures paths don't escape workspace boundary. */ private validateLocalPath; private parseJsonLockFile; /** * Finds workspace root by walking up from startPath looking for model.yaml. * Uses configurable manifest files if specified in constructor options. * PRS-017 R11: Consults `knownManifestDirs` before hitting the filesystem. */ private findWorkspaceRoot; private containsManifest; /** * Computes a SHA-256 hex digest of the given content. * Used for content-hash based cache validation (PRS-017 R5). */ private computeHash; }