This release is 4 versions behind 0.6.1 — the latest version of @corespeed/zypher. Jump to latest
@corespeed/zypher@0.4.4Built and signed on GitHub ActionsBuilt and signed on GitHub Actions
Built and signed on GitHub Actions
An open-source agent framework for building production-ready agentic AI agents
This package works with DenoIt is unknown whether this package works with Cloudflare Workers, Node.js, Bun, Browsers




JSR Score
88%
Published
a month ago (0.4.4)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421import type { ErrorDetector } from "./interface.ts"; import { extractErrorOutput } from "./utils.ts"; import { fileExists } from "../../utils/mod.ts"; // Workaround for Deno.Command not throwing an error when the command returns a non-zero exit code import { exec } from "node:child_process"; import { promisify } from "node:util"; import * as path from "jsr:@std/path@^1.1.2"; const execAsync = promisify(exec); /** * Detector for ESLint errors in JavaScript/TypeScript projects */ export class ESLintErrorDetector implements ErrorDetector { name = "ESLint"; description = "Detects code style and potential issues using ESLint"; async isApplicable(workingDirectory: string): Promise<boolean> { try { const packageJson = await readPackageJson(workingDirectory); // Check if eslint is in dependencies or devDependencies const hasEslint = hasDependency(packageJson, "eslint"); // Also check if there are lint scripts const hasLintScript = hasScript(packageJson, "lint") || hasScript(packageJson, "eslint") || !!findScriptByPattern(packageJson, "lint"); return hasEslint || hasLintScript; } catch { return false; } } async detect(workingDirectory: string): Promise<string | null> { try { // Determine the command to run const commandConfig = await this.determineCommand(workingDirectory); // Execute the command try { // If we get here, the command succeeded (no errors) await new Deno.Command(commandConfig.cmd, { args: commandConfig.args || [], cwd: workingDirectory, }).output(); return null; } catch (error) { // Command failed, which likely means it found errors const errorOutput = extractErrorOutput( error, (output) => this.filterNonErrors(output), ); if (errorOutput) { return `ESLint errors detected:\n${errorOutput}`; } } return null; } catch (error) { console.warn("Error running ESLint check:", error); return null; } } /** * Determines the command to run for ESLint * * @param {string} workingDirectory - The working directory to determine the command for * @returns {Promise<CommandConfig>} The command configuration to execute * @private */ private async determineCommand( workingDirectory: string, ): Promise<CommandConfig> { const packageJson = await readPackageJson(workingDirectory); // If package.json has a lint script, use it if (packageJson?.scripts) { // Find the appropriate script name let scriptName: string | undefined; if (hasScript(packageJson, "lint")) { scriptName = "lint"; } else if (hasScript(packageJson, "eslint")) { scriptName = "eslint"; } else { scriptName = findScriptByPattern(packageJson, "lint"); } if (scriptName) { return await getRunCommand(workingDirectory, [scriptName]); } } // For direct commands, we'll use the eslint binary directly // but still respect the package manager via getRunCommand return await getRunCommand( workingDirectory, ["eslint", ".", "--ext", ".js,.jsx,.ts,.tsx"], ); } /** * Filters out npm notices and other non-error output * * @param {string} output - The command output to filter * @returns {string} Filtered output containing only actual errors */ private filterNonErrors(output: string): string { // Split by lines const lines = output.split("\n"); // Filter out npm notices and empty lines const errorLines = lines.filter((line) => { // Skip npm notices if (line.trim().startsWith("npm notice")) return false; // Skip empty lines if (line.trim() === "") return false; // Skip npm warnings that aren't related to the code if (line.includes("npm WARN")) return false; return true; }); return errorLines.join("\n"); } } /** * Detector for TypeScript compiler errors */ export class TypeScriptErrorDetector implements ErrorDetector { name = "TypeScript"; description = "Detects type errors using the TypeScript compiler"; async isApplicable(workingDirectory: string): Promise<boolean> { try { // Check package.json first const packageJson = await readPackageJson(workingDirectory); if (packageJson) { // Check if typescript is in dependencies const hasTypeScript = hasDependency(packageJson, "typescript"); // Check if there are type-check scripts const hasTypeCheckScript = hasScript(packageJson, "type-check") || hasScript(packageJson, "typecheck") || hasScript(packageJson, "tsc") || !!findScriptByPattern(packageJson, "type") || !!findScriptByPattern(packageJson, "tsc"); if (hasTypeScript || hasTypeCheckScript) { return true; } } // Fallback to checking for tsconfig.json return await fileExists(path.join(workingDirectory, "tsconfig.json")); } catch { return false; } } async detect(workingDirectory: string): Promise<string | null> { try { // Determine the command to run const commandConfig = await this.determineCommand(workingDirectory); // Execute the command try { // If we get here, the command succeeded (no errors) await execAsync( `${commandConfig.cmd} ${commandConfig.args?.join(" ")}`, { cwd: workingDirectory }, ); return null; } catch (error) { // Command failed, which likely means it found errors const errorOutput = extractErrorOutput( error, (output) => this.filterNonErrors(output), ); if (errorOutput) { return `TypeScript errors detected:\n${errorOutput}`; } } return null; } catch (error) { console.warn("Error running TypeScript check:", error); return null; } } /** * Determines the command to run for TypeScript * * @param {string} workingDirectory - The current working directory * @returns {Promise<CommandConfig>} The command configuration to execute * @private */ private async determineCommand( workingDirectory: string, ): Promise<CommandConfig> { const packageJson = await readPackageJson(workingDirectory); // If package.json has a type-check script, use it if (packageJson?.scripts) { // Find the appropriate script name let scriptName: string | undefined; if (hasScript(packageJson, "type-check")) { scriptName = "type-check"; } else if (hasScript(packageJson, "typecheck")) { scriptName = "typecheck"; } else if (hasScript(packageJson, "tsc")) { scriptName = "tsc"; } else { scriptName = findScriptByPattern(packageJson, "type") ?? findScriptByPattern(packageJson, "tsc"); } if (scriptName) { return await getRunCommand(workingDirectory, [scriptName]); } } // For direct commands, we'll use the tsc binary directly // but still respect the package manager via getRunCommand return await getRunCommand(workingDirectory, ["tsc", "--noEmit"]); } /** * Filters out npm notices and other non-error output * * @param {string} output - The command output to filter * @returns {string} Filtered output containing only actual errors */ private filterNonErrors(output: string): string { // Split by lines const lines = output.split("\n"); // Filter out npm notices and empty lines const errorLines = lines.filter((line) => { // Skip npm notices if (line.trim().startsWith("npm notice")) return false; // Skip empty lines if (line.trim() === "") return false; // Skip npm warnings that aren't related to the code if (line.includes("npm WARN")) return false; return true; }); return errorLines.join("\n"); } } /** * Type definition for package.json structure */ export interface PackageJson { name?: string; version?: string; dependencies?: Record<string, string>; devDependencies?: Record<string, string>; scripts?: Record<string, string>; [key: string]: unknown; } /** * Command configuration type for Deno.Command */ export interface CommandConfig { cmd: string; args?: string[]; } /** * Detects the preferred package manager for a Node.js project. * * @param {string} workingDirectory - The working directory to detect the package manager for * @returns {Promise<'npm' | 'yarn' | 'pnpm' | 'bun'>} The detected package manager */ export async function detectPackageManager(workingDirectory: string): Promise< "npm" | "yarn" | "pnpm" | "bun" > { // Check for lockfiles to determine package manager if ( (await fileExists(path.join(workingDirectory, "bun.lock"))) || (await fileExists(path.join(workingDirectory, "bun.lockb"))) ) { return "bun"; } if (await fileExists(path.join(workingDirectory, "pnpm-lock.yaml"))) { return "pnpm"; } if (await fileExists(path.join(workingDirectory, "yarn.lock"))) { return "yarn"; } // Default to npm return "npm"; } /** * Gets the run command for the detected package manager. * * @param {string} workingDirectory - The working directory to get the run command for * @param {string} script - The script name to run * @returns {Promise<CommandConfig>} The command configuration to execute */ export async function getRunCommand( workingDirectory: string, script: string[], ): Promise<CommandConfig> { const packageManager = await detectPackageManager(workingDirectory); switch (packageManager) { case "yarn": return { cmd: "yarn", args: script }; case "pnpm": return { cmd: "pnpm", args: script }; case "bun": return { cmd: "bun", args: ["run", ...script] }; default: return { cmd: "npm", args: ["run", ...script] }; } } /** * Reads and parses the package.json file. * * @param {string} workingDirectory - The working directory to read the package.json from * @returns {Promise<PackageJson | null>} The parsed package.json or null if not found/invalid */ export async function readPackageJson( workingDirectory: string, ): Promise<PackageJson | null> { try { const content = await Deno.readTextFile( path.join(workingDirectory, "package.json"), ); return JSON.parse(content) as PackageJson; } catch { return null; } } /** * Safely checks if a dependency exists in package.json * * @param {PackageJson | null} packageJson - The package.json object * @param {string} dependency - The dependency name to check * @returns {boolean} True if the dependency exists in dependencies or devDependencies */ export function hasDependency( packageJson: PackageJson | null, dependency: string, ): boolean { if (!packageJson) return false; return !!( (packageJson.dependencies && dependency in packageJson.dependencies) ?? (packageJson.devDependencies && dependency in packageJson.devDependencies) ); } /** * Safely checks if a script exists in package.json * * @param {PackageJson | null} packageJson - The package.json object * @param {string} scriptName - The exact script name to check * @returns {boolean} True if the script exists */ export function hasScript( packageJson: PackageJson | null, scriptName: string, ): boolean { if (!packageJson?.scripts) return false; return scriptName in packageJson.scripts; } /** * Safely checks if any script matching a pattern exists in package.json * * @param {PackageJson | null} packageJson - The package.json object * @param {string} pattern - The pattern to match in script names * @returns {string | undefined} The name of the first matching script, or undefined if none found */ export function findScriptByPattern( packageJson: PackageJson | null, pattern: string, ): string | undefined { if (!packageJson?.scripts) return undefined; return Object.keys(packageJson.scripts).find((script) => script.includes(pattern) ); } /** * Safely gets a script from package.json * * @param {PackageJson | null} packageJson - The package.json object * @param {string} scriptName - The script name to get * @returns {string | undefined} The script content or undefined if not found */ export function getScript( packageJson: PackageJson | null, scriptName: string, ): string | undefined { if (!packageJson?.scripts) return undefined; return packageJson.scripts[scriptName]; }