UNPKG

@sidequest/engine

Version:

@sidequest/engine is the core engine of SideQuest, a distributed background job processing system for Node.js and TypeScript.

122 lines (119 loc) 4.85 kB
import { parseStackTrace } from '@sidequest/core'; import { existsSync } from 'node:fs'; import { isAbsolute, dirname, resolve, join } from 'node:path'; import { pathToFileURL } from 'node:url'; /** * Tag used to indicate that a job script should be resolved manually * by searching the filesystem rather than importing directly. */ const MANUAL_SCRIPT_TAG = "sidequest.jobs.js"; /** * Finds a file by searching in the current directory and walking up through parent directories. * * @param fileName - The name of the file to search for. Defaults to "sidequest.jobs.js" * @param startDir - The directory to start searching from. Defaults to process.cwd() * @returns The file URL path to the found file * @throws {Error} If the file is not found in any directory up to the root * * @example * ```typescript * // Find sidequest.jobs.js starting from current directory * const jobsFile = findFileInParentDirs(); * * // Find a specific file starting from current directory * const configFile = findFileInParentDirs("config.json"); * * // Find a file starting from a specific directory * const packageFile = findFileInParentDirs("package.json", "/some/project/dir"); * ``` */ function findSidequestJobsScriptInParentDirs(fileName = MANUAL_SCRIPT_TAG, startDir = process.cwd()) { if (!fileName.trim()) { throw new Error("fileName must be a non-empty string"); } let currentDir = resolve(startDir); const rootDir = resolve("/"); // Keep searching until we reach the root directory while (currentDir !== rootDir) { const filePath = join(currentDir, fileName); if (existsSync(filePath)) { return pathToFileURL(filePath).href; } // Move to parent directory const parentDir = dirname(currentDir); // Safety check to prevent infinite loops if (parentDir === currentDir) { break; } currentDir = parentDir; } // Check the root directory as well const rootFilePath = join(rootDir, fileName); if (existsSync(rootFilePath)) { return pathToFileURL(rootFilePath).href; } throw new Error(`File "${fileName}" not found in "${startDir}" or any parent directory`); } /** * Resolves a script path to a file URL, handling various path formats and resolution strategies. * * This function attempts to resolve script paths in the following order: * 1. If the path is already a valid URL, returns it as-is (for file: protocol URLs) * 2. If the path is absolute, converts it directly to a file URL * 3. If the path is relative, searches for the file in directories from the call stack, * starting from the caller's directory and walking up the stack * * The stack-based resolution helps resolve relative paths based on the script's * execution context rather than the current working directory. * * @param scriptPath - The script path to resolve. Can be a relative path, absolute path, or URL string. * @returns The resolved file URL as a string * @throws {Error} When scriptPath is empty or when the file cannot be found in any searched location * * @example * ```typescript * // Absolute path * resolveScriptPath('/path/to/script.js') // Returns 'file:///path/to/script.js' * * // Relative path (searches based on call stack) * resolveScriptPath('./script.js') // Returns file URL of script.js found in caller's directory * * // Already a URL * resolveScriptPath('file:///path/to/script.js') // Returns 'file:///path/to/script.js' * ``` */ function resolveScriptPath(scriptPath) { scriptPath = scriptPath.trim(); if (!scriptPath) { throw new Error("scriptPath must be a non-empty string"); } // If the scriptPath is already a URL, return as is try { const url = new URL(scriptPath); if (url.protocol === "file:") { return url.href; } } catch { // Not a valid URL, proceed to resolve as file path } // If it's an absolute path, convert directly to file URL if (isAbsolute(scriptPath)) { return pathToFileURL(scriptPath).href; } // Otherwise, search in current and parent directories based on stack trace // This helps in resolving relative paths based on where the script is executed // rather than the current working directory const err = new Error(); const stackFiles = parseStackTrace(err); for (const file of stackFiles) { const parentDir = dirname(file); const resolvedPath = resolve(parentDir, scriptPath); if (existsSync(resolvedPath)) { return pathToFileURL(resolvedPath).href; } } throw new Error(`Unable to resolve script path: ${scriptPath}`); } export { MANUAL_SCRIPT_TAG, findSidequestJobsScriptInParentDirs, resolveScriptPath }; //# sourceMappingURL=manual-loader.js.map