run.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import fs from "fs/promises"
  2. import path from "path"
  3. import { fileURLToPath } from "url"
  4. import { execa } from "execa"
  5. const __dirname = path.dirname(fileURLToPath(import.meta.url))
  6. const cliRoot = path.resolve(__dirname, "../..")
  7. const casesDir = path.resolve(__dirname, "cases")
  8. interface RunnerOptions {
  9. listOnly: boolean
  10. match?: string
  11. }
  12. function parseArgs(argv: string[]): RunnerOptions {
  13. let listOnly = false
  14. let match: string | undefined
  15. for (let i = 0; i < argv.length; i++) {
  16. const arg = argv[i]
  17. if (arg === "--list") {
  18. listOnly = true
  19. continue
  20. }
  21. if (arg === "--match") {
  22. match = argv[i + 1]
  23. i += 1
  24. continue
  25. }
  26. }
  27. return { listOnly, match }
  28. }
  29. async function discoverCaseFiles(match?: string): Promise<string[]> {
  30. const entries = await fs.readdir(casesDir, { withFileTypes: true })
  31. const files = entries
  32. .filter((entry) => entry.isFile() && entry.name.endsWith(".ts"))
  33. .map((entry) => path.resolve(casesDir, entry.name))
  34. .sort((a, b) => a.localeCompare(b))
  35. if (!match) {
  36. return files
  37. }
  38. const normalized = match.toLowerCase()
  39. return files.filter((file) => path.basename(file).toLowerCase().includes(normalized))
  40. }
  41. async function runCase(caseFile: string): Promise<void> {
  42. const caseName = path.basename(caseFile, ".ts")
  43. console.log(`\n[RUN] ${caseName}`)
  44. await execa("tsx", [caseFile], {
  45. cwd: cliRoot,
  46. stdio: "inherit",
  47. reject: true,
  48. env: {
  49. ...process.env,
  50. ROO_CLI_ROOT: cliRoot,
  51. },
  52. })
  53. console.log(`[PASS] ${caseName}`)
  54. }
  55. async function main() {
  56. const options = parseArgs(process.argv.slice(2))
  57. const caseFiles = await discoverCaseFiles(options.match)
  58. if (caseFiles.length === 0) {
  59. throw new Error(
  60. options.match ? `no integration cases matched --match "${options.match}"` : "no integration cases found",
  61. )
  62. }
  63. if (options.listOnly) {
  64. console.log("Available integration cases:")
  65. for (const file of caseFiles) {
  66. console.log(`- ${path.basename(file, ".ts")}`)
  67. }
  68. return
  69. }
  70. const failures: Array<{ caseName: string; error: string }> = []
  71. for (const caseFile of caseFiles) {
  72. const caseName = path.basename(caseFile, ".ts")
  73. try {
  74. await runCase(caseFile)
  75. } catch (error) {
  76. const errorText = error instanceof Error ? error.message : String(error)
  77. failures.push({ caseName, error: errorText })
  78. console.error(`[FAIL] ${caseName}: ${errorText}`)
  79. }
  80. }
  81. const total = caseFiles.length
  82. const passed = total - failures.length
  83. console.log(`\nSummary: ${passed}/${total} passed`)
  84. if (failures.length > 0) {
  85. process.exitCode = 1
  86. }
  87. }
  88. main().catch((error) => {
  89. console.error(`[FAIL] ${error instanceof Error ? error.message : String(error)}`)
  90. process.exit(1)
  91. })