Files
Ncurator/scripts/test-workflow.mjs
T

191 lines
4.4 KiB
JavaScript

import { readdir, stat } from 'node:fs/promises'
import path from 'node:path'
import { spawnSync } from 'node:child_process'
const ROOT = process.cwd()
const TESTS_ROOT = path.join(ROOT, 'tests')
const resolveLocalBin = (name) =>
path.join(ROOT, 'node_modules', '.bin', process.platform === 'win32' ? `${name}.cmd` : name)
const VITEST_BIN = resolveLocalBin('vitest')
const ELECTRON_VITE_BIN = resolveLocalBin('electron-vite')
const parseArgs = (argv) => {
const parsed = {
domain: '',
layer: '',
suite: '',
groups: [],
caseId: '',
runs: 1
}
for (let index = 0; index < argv.length; index += 1) {
const current = argv[index]
const next = argv[index + 1]
if (current === '--domain' && next) {
parsed.domain = next
index += 1
continue
}
if (current === '--layer' && next) {
parsed.layer = next
index += 1
continue
}
if (current === '--suite' && next) {
parsed.suite = next
index += 1
continue
}
if (current === '--group' && next) {
parsed.groups.push(next)
index += 1
continue
}
if (current === '--case' && next) {
parsed.caseId = next
index += 1
continue
}
if (current === '--runs' && next) {
parsed.runs = Math.max(1, Number(next))
index += 1
}
}
return parsed
}
const walk = async (target) => {
const entries = await readdir(target, { withFileTypes: true })
const files = []
for (const entry of entries) {
const resolved = path.join(target, entry.name)
if (entry.isDirectory()) {
files.push(...(await walk(resolved)))
continue
}
files.push(resolved)
}
return files
}
const resolveScopeDir = (input) => {
const parts = [TESTS_ROOT]
if (input.domain) parts.push(input.domain)
if (input.layer) parts.push(input.layer)
if (input.suite) parts.push(input.suite)
if (input.groups.length > 0) parts.push(...input.groups)
return path.join(...parts)
}
const findTestFiles = async (input) => {
const scopeDir = resolveScopeDir(input)
try {
const scopeStat = await stat(scopeDir)
if (!scopeStat.isDirectory()) return []
} catch {
return []
}
const allFiles = await walk(scopeDir)
return allFiles
.filter((filePath) => filePath.endsWith('.test.ts'))
.filter((filePath) => {
if (!input.caseId) return true
return path.basename(filePath, '.test.ts') === input.caseId
})
.sort()
}
const runVitest = (input) => {
const commandArgs = ['--config', input.configPath, 'run', ...input.testFiles]
const result = spawnSync(VITEST_BIN, commandArgs, {
cwd: ROOT,
stdio: 'inherit'
})
if (typeof result.status === 'number') return result.status
return 1
}
const buildE2EApp = () => {
const result = spawnSync(ELECTRON_VITE_BIN, ['build'], {
cwd: ROOT,
stdio: 'inherit'
})
if (typeof result.status === 'number') return result.status
return 1
}
const formatScope = (input) =>
[input.domain, input.layer, input.suite, ...input.groups].filter(Boolean).join('/')
const runLayerWorkflow = async (input) => {
const testFiles = await findTestFiles(input)
if (testFiles.length === 0) {
process.stdout.write(`[test-workflow] no tests found for ${formatScope(input)}\n`)
return 0
}
if (input.layer === 'e2e') {
const buildCode = buildE2EApp()
if (buildCode !== 0) {
return buildCode
}
}
let exitCode = 0
for (let iteration = 0; iteration < input.runs; iteration += 1) {
const code = runVitest({
configPath: input.layer === 'backend' ? 'vitest.backend.config.mts' : 'vitest.e2e.config.mts',
testFiles
})
if (code !== 0) {
exitCode = code
break
}
}
return exitCode
}
const main = async () => {
const args = parseArgs(process.argv.slice(2))
if (!args.domain) {
throw new Error('missing required flag: --domain')
}
let exitCode = 0
if (!args.layer || args.layer === 'backend') {
exitCode = await runLayerWorkflow({
...args,
layer: 'backend'
})
}
if (exitCode === 0 && args.layer === 'e2e') {
exitCode = await runLayerWorkflow({
...args,
layer: 'backend'
})
}
if (exitCode === 0 && (!args.layer || args.layer === 'e2e')) {
exitCode = await runLayerWorkflow({
...args,
layer: 'e2e'
})
}
process.exit(exitCode)
}
main().catch((error) => {
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
process.exit(1)
})