2026-02-20 01:11:30 -08:00
/**
* Run Tests Tool
*
* Custom OpenCode tool to run test suites with various options.
* Automatically detects the package manager and test framework.
*/
2026-04-05 15:11:19 -07:00
import { tool , type ToolDefinition } from "@opencode-ai/plugin/tool"
2026-02-20 01:11:30 -08:00
import * as path from "path"
import * as fs from "fs"
2026-04-05 15:11:19 -07:00
const runTestsTool : ToolDefinition = tool ({
2026-02-20 01:11:30 -08:00
description :
"Run the test suite with optional coverage, watch mode, or specific test patterns. Automatically detects package manager (npm, pnpm, yarn, bun) and test framework." ,
args : {
pattern : tool.schema
. string ()
. optional ()
. describe ( "Test file pattern or specific test name to run" ),
coverage : tool.schema
. boolean ()
. optional ()
. describe ( "Run with coverage reporting (default: false)" ),
watch : tool.schema
. boolean ()
. optional ()
. describe ( "Run in watch mode for continuous testing (default: false)" ),
updateSnapshots : tool.schema
. boolean ()
. optional ()
. describe ( "Update Jest/Vitest snapshots (default: false)" ),
},
async execute ( args , context ) {
const { pattern , coverage , watch , updateSnapshots } = args
const cwd = context . worktree || context . directory
// Detect package manager
const packageManager = await detectPackageManager ( cwd )
// Detect test framework
const testFramework = await detectTestFramework ( cwd )
// Build command
let cmd : string [] = [ packageManager ]
if ( packageManager === "npm" ) {
cmd . push ( "run" , "test" )
} else {
cmd . push ( "test" )
}
// Add options based on framework
const testArgs : string [] = []
if ( coverage ) {
testArgs . push ( "--coverage" )
}
if ( watch ) {
testArgs . push ( "--watch" )
}
if ( updateSnapshots ) {
testArgs . push ( "-u" )
}
if ( pattern ) {
if ( testFramework === "jest" || testFramework === "vitest" ) {
testArgs . push ( "--testPathPattern" , pattern )
} else {
testArgs . push ( pattern )
}
}
// Add -- separator for npm
if ( testArgs . length > 0 ) {
if ( packageManager === "npm" ) {
cmd . push ( "--" )
}
cmd . push (... testArgs )
}
const command = cmd . join ( " " )
return JSON . stringify ({
command ,
packageManager ,
testFramework ,
options : {
pattern : pattern || "all tests" ,
coverage : coverage || false ,
watch : watch || false ,
updateSnapshots : updateSnapshots || false ,
},
instructions : `Run this command to execute tests: \ n \ n ${ command } ` ,
})
},
})
2026-04-05 15:11:19 -07:00
export default runTestsTool
2026-02-20 01:11:30 -08:00
async function detectPackageManager ( cwd : string ) : Promise < string > {
const lockFiles : Record < string , string > = {
"bun.lockb" : "bun" ,
"pnpm-lock.yaml" : "pnpm" ,
"yarn.lock" : "yarn" ,
"package-lock.json" : "npm" ,
}
for ( const [ lockFile , pm ] of Object . entries ( lockFiles )) {
if ( fs . existsSync ( path . join ( cwd , lockFile ))) {
return pm
}
}
return "npm"
}
async function detectTestFramework ( cwd : string ) : Promise < string > {
const packageJsonPath = path . join ( cwd , "package.json" )
if ( fs . existsSync ( packageJsonPath )) {
try {
const packageJson = JSON . parse ( fs . readFileSync ( packageJsonPath , "utf-8" ))
const deps = {
... packageJson . dependencies ,
... packageJson . devDependencies ,
}
if ( deps . vitest ) return "vitest"
if ( deps . jest ) return "jest"
if ( deps . mocha ) return "mocha"
if ( deps . ava ) return "ava"
if ( deps . tap ) return "tap"
} catch {
// Ignore parse errors
}
}
return "unknown"
}