verify-project-state.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. #!/usr/bin/env node
  2. /**
  3. * GrabZilla 2.1 - Project State Verification Script
  4. *
  5. * Checks:
  6. * - Binaries exist and are executable
  7. * - Tests can run and pass
  8. * - App can launch (headless check)
  9. * - Dependencies are installed
  10. * - File structure is intact
  11. *
  12. * Outputs JSON with complete state assessment.
  13. *
  14. * Usage: node verify-project-state.js
  15. */
  16. const fs = require('fs')
  17. const path = require('path')
  18. const { execSync, spawn } = require('child_process')
  19. // ANSI colors for terminal output
  20. const colors = {
  21. reset: '\x1b[0m',
  22. green: '\x1b[32m',
  23. red: '\x1b[31m',
  24. yellow: '\x1b[33m',
  25. blue: '\x1b[34m',
  26. cyan: '\x1b[36m'
  27. }
  28. const log = {
  29. info: (msg) => console.log(`${colors.cyan}ℹ${colors.reset} ${msg}`),
  30. success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`),
  31. error: (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`),
  32. warning: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`),
  33. section: (msg) => console.log(`\n${colors.blue}▶${colors.reset} ${msg}`)
  34. }
  35. // State object to collect all verification results
  36. const state = {
  37. timestamp: new Date().toISOString(),
  38. status: 'unknown', // Will be: green, yellow, red
  39. binaries: {
  40. ytdlp: { exists: false, executable: false, version: null },
  41. ffmpeg: { exists: false, executable: false, version: null }
  42. },
  43. tests: {
  44. total: 0,
  45. passing: 0,
  46. failing: 0,
  47. passRate: 0,
  48. suites: {}
  49. },
  50. app: {
  51. launches: false,
  52. error: null
  53. },
  54. dependencies: {
  55. installed: false,
  56. count: 0
  57. },
  58. files: {
  59. critical: [],
  60. missing: []
  61. },
  62. health: {
  63. score: 0, // 0-100
  64. issues: [],
  65. recommendations: []
  66. }
  67. }
  68. /**
  69. * Check if binaries exist and are executable
  70. */
  71. function checkBinaries() {
  72. log.section('Checking Binaries')
  73. const binariesDir = path.join(__dirname, 'binaries')
  74. const platform = process.platform
  75. const ext = platform === 'win32' ? '.exe' : ''
  76. // Check yt-dlp
  77. const ytdlpPath = path.join(binariesDir, `yt-dlp${ext}`)
  78. state.binaries.ytdlp.exists = fs.existsSync(ytdlpPath)
  79. if (state.binaries.ytdlp.exists) {
  80. try {
  81. // Check if executable
  82. fs.accessSync(ytdlpPath, fs.constants.X_OK)
  83. state.binaries.ytdlp.executable = true
  84. // Get version
  85. const version = execSync(`"${ytdlpPath}" --version`, { encoding: 'utf8' }).trim()
  86. state.binaries.ytdlp.version = version
  87. log.success(`yt-dlp found and executable (v${version})`)
  88. } catch (error) {
  89. state.binaries.ytdlp.executable = false
  90. log.error(`yt-dlp found but not executable: ${error.message}`)
  91. state.health.issues.push('yt-dlp is not executable - run: chmod +x binaries/yt-dlp')
  92. }
  93. } else {
  94. log.error('yt-dlp not found')
  95. state.health.issues.push('yt-dlp missing - run: node setup.js')
  96. }
  97. // Check ffmpeg
  98. const ffmpegPath = path.join(binariesDir, `ffmpeg${ext}`)
  99. state.binaries.ffmpeg.exists = fs.existsSync(ffmpegPath)
  100. if (state.binaries.ffmpeg.exists) {
  101. try {
  102. // Check if executable
  103. fs.accessSync(ffmpegPath, fs.constants.X_OK)
  104. state.binaries.ffmpeg.executable = true
  105. // Get version (ffmpeg outputs to stderr)
  106. const output = execSync(`"${ffmpegPath}" -version 2>&1`, { encoding: 'utf8' })
  107. const versionMatch = output.match(/ffmpeg version ([^\s]+)/)
  108. state.binaries.ffmpeg.version = versionMatch ? versionMatch[1] : 'unknown'
  109. log.success(`ffmpeg found and executable (v${state.binaries.ffmpeg.version})`)
  110. } catch (error) {
  111. state.binaries.ffmpeg.executable = false
  112. log.error(`ffmpeg found but not executable: ${error.message}`)
  113. state.health.issues.push('ffmpeg is not executable - run: chmod +x binaries/ffmpeg')
  114. }
  115. } else {
  116. log.error('ffmpeg not found')
  117. state.health.issues.push('ffmpeg missing - run: node setup.js')
  118. }
  119. }
  120. /**
  121. * Check if dependencies are installed
  122. */
  123. function checkDependencies() {
  124. log.section('Checking Dependencies')
  125. const nodeModulesPath = path.join(__dirname, 'node_modules')
  126. state.dependencies.installed = fs.existsSync(nodeModulesPath)
  127. if (state.dependencies.installed) {
  128. try {
  129. const pkgJson = require('./package.json')
  130. const allDeps = {
  131. ...pkgJson.dependencies,
  132. ...pkgJson.devDependencies
  133. }
  134. state.dependencies.count = Object.keys(allDeps).length
  135. log.success(`Dependencies installed (${state.dependencies.count} packages)`)
  136. } catch (error) {
  137. log.error(`Error reading package.json: ${error.message}`)
  138. state.health.issues.push('Cannot read package.json')
  139. }
  140. } else {
  141. log.error('node_modules not found')
  142. state.health.issues.push('Dependencies not installed - run: npm install')
  143. }
  144. }
  145. /**
  146. * Check critical files exist
  147. */
  148. function checkCriticalFiles() {
  149. log.section('Checking Critical Files')
  150. const criticalFiles = [
  151. 'src/main.js',
  152. 'src/preload.js',
  153. 'src/download-manager.js',
  154. 'scripts/app.js',
  155. 'scripts/models/Video.js',
  156. 'scripts/models/AppState.js',
  157. 'scripts/services/metadata-service.js',
  158. 'scripts/utils/url-validator.js',
  159. 'scripts/utils/ipc-integration.js',
  160. 'index.html',
  161. 'styles/main.css',
  162. 'package.json'
  163. ]
  164. criticalFiles.forEach(file => {
  165. const fullPath = path.join(__dirname, file)
  166. if (fs.existsSync(fullPath)) {
  167. state.files.critical.push(file)
  168. } else {
  169. state.files.missing.push(file)
  170. log.error(`Missing critical file: ${file}`)
  171. state.health.issues.push(`Critical file missing: ${file}`)
  172. }
  173. })
  174. log.success(`${state.files.critical.length}/${criticalFiles.length} critical files present`)
  175. }
  176. /**
  177. * Run tests and capture results
  178. */
  179. async function runTests() {
  180. log.section('Running Tests')
  181. return new Promise((resolve) => {
  182. try {
  183. // Run tests and capture output
  184. const output = execSync('npm test 2>&1', {
  185. encoding: 'utf8',
  186. maxBuffer: 10 * 1024 * 1024 // 10MB buffer
  187. })
  188. // Parse test results
  189. parseTestOutput(output)
  190. resolve()
  191. } catch (error) {
  192. // Tests may "fail" but still provide output
  193. parseTestOutput(error.stdout || error.message)
  194. resolve()
  195. }
  196. })
  197. }
  198. /**
  199. * Parse test output to extract statistics
  200. */
  201. function parseTestOutput(output) {
  202. // Extract test counts
  203. const passedMatch = output.match(/(\d+) passed/)
  204. const failedMatch = output.match(/(\d+) failed/)
  205. if (passedMatch) {
  206. state.tests.passing = parseInt(passedMatch[1], 10)
  207. }
  208. if (failedMatch) {
  209. state.tests.failing = parseInt(failedMatch[1], 10)
  210. }
  211. state.tests.total = state.tests.passing + state.tests.failing
  212. state.tests.passRate = state.tests.total > 0
  213. ? Math.round((state.tests.passing / state.tests.total) * 100)
  214. : 0
  215. // Extract suite results
  216. const suiteMatches = output.matchAll(/([✓❌]) ([A-Za-z\s]+Tests)\s+(PASSED|FAILED)/g)
  217. for (const match of suiteMatches) {
  218. const [, icon, name, status] = match
  219. state.tests.suites[name.trim()] = status === 'PASSED'
  220. }
  221. log.info(`Test Results: ${state.tests.passing}/${state.tests.total} passing (${state.tests.passRate}%)`)
  222. if (state.tests.passRate < 95) {
  223. state.health.issues.push(`Test pass rate below 95%: ${state.tests.passRate}%`)
  224. }
  225. if (state.tests.failing > 2) {
  226. state.health.issues.push(`Too many failing tests: ${state.tests.failing}`)
  227. }
  228. }
  229. /**
  230. * Check if app can launch (simple check)
  231. */
  232. function checkAppLaunch() {
  233. log.section('Checking App Launch Capability')
  234. try {
  235. // Check if main.js can be required without errors
  236. const mainPath = path.join(__dirname, 'src', 'main.js')
  237. if (fs.existsSync(mainPath)) {
  238. const mainContent = fs.readFileSync(mainPath, 'utf8')
  239. // Check for critical patterns
  240. const hasCreateWindow = mainContent.includes('createWindow')
  241. const hasIpcHandlers = mainContent.includes('ipcMain.handle')
  242. const hasAppReady = mainContent.includes('app.whenReady')
  243. if (hasCreateWindow && hasIpcHandlers && hasAppReady) {
  244. state.app.launches = true
  245. log.success('App structure looks valid')
  246. } else {
  247. state.app.error = 'Missing critical app initialization code'
  248. log.warning('App may not launch correctly')
  249. state.health.issues.push('App missing critical initialization code')
  250. }
  251. } else {
  252. state.app.error = 'main.js not found'
  253. log.error('Cannot find src/main.js')
  254. state.health.issues.push('src/main.js not found')
  255. }
  256. } catch (error) {
  257. state.app.error = error.message
  258. log.error(`App check failed: ${error.message}`)
  259. state.health.issues.push(`App check error: ${error.message}`)
  260. }
  261. }
  262. /**
  263. * Calculate overall health score
  264. */
  265. function calculateHealthScore() {
  266. let score = 0
  267. // Binaries (30 points)
  268. if (state.binaries.ytdlp.exists && state.binaries.ytdlp.executable) score += 15
  269. if (state.binaries.ffmpeg.exists && state.binaries.ffmpeg.executable) score += 15
  270. // Dependencies (10 points)
  271. if (state.dependencies.installed) score += 10
  272. // Critical files (20 points)
  273. const fileRatio = state.files.critical.length / (state.files.critical.length + state.files.missing.length)
  274. score += Math.round(fileRatio * 20)
  275. // Tests (30 points)
  276. score += Math.round((state.tests.passRate / 100) * 30)
  277. // App launch (10 points)
  278. if (state.app.launches) score += 10
  279. state.health.score = Math.min(100, score)
  280. // Determine status
  281. if (state.health.score >= 90) {
  282. state.status = 'green'
  283. } else if (state.health.score >= 70) {
  284. state.status = 'yellow'
  285. } else {
  286. state.status = 'red'
  287. }
  288. }
  289. /**
  290. * Generate recommendations
  291. */
  292. function generateRecommendations() {
  293. // Binary recommendations
  294. if (!state.binaries.ytdlp.exists || !state.binaries.ffmpeg.exists) {
  295. state.health.recommendations.push('Run "node setup.js" to download missing binaries')
  296. }
  297. if (!state.binaries.ytdlp.executable || !state.binaries.ffmpeg.executable) {
  298. state.health.recommendations.push('Run "chmod +x binaries/*" to make binaries executable')
  299. }
  300. // Dependency recommendations
  301. if (!state.dependencies.installed) {
  302. state.health.recommendations.push('Run "npm install" to install dependencies')
  303. }
  304. // Test recommendations
  305. if (state.tests.passRate < 95) {
  306. state.health.recommendations.push('Fix failing tests to improve stability')
  307. }
  308. // File recommendations
  309. if (state.files.missing.length > 0) {
  310. state.health.recommendations.push('Restore missing critical files from git')
  311. }
  312. // Overall recommendations
  313. if (state.status === 'green') {
  314. state.health.recommendations.push('Project is healthy - ready for development')
  315. } else if (state.status === 'yellow') {
  316. state.health.recommendations.push('Project has minor issues - fix before major changes')
  317. } else {
  318. state.health.recommendations.push('Project has critical issues - fix immediately')
  319. }
  320. }
  321. /**
  322. * Print summary report
  323. */
  324. function printSummary() {
  325. console.log('\n' + '='.repeat(60))
  326. console.log(' GRABZILLA 2.1 - PROJECT STATE VERIFICATION')
  327. console.log('='.repeat(60))
  328. // Status badge
  329. const statusColor = state.status === 'green' ? colors.green
  330. : state.status === 'yellow' ? colors.yellow
  331. : colors.red
  332. const statusEmoji = state.status === 'green' ? '🟢'
  333. : state.status === 'yellow' ? '🟡'
  334. : '🔴'
  335. console.log(`\nStatus: ${statusEmoji} ${statusColor}${state.status.toUpperCase()}${colors.reset} (Health Score: ${state.health.score}/100)`)
  336. // Binaries
  337. console.log('\nBinaries:')
  338. console.log(` yt-dlp: ${state.binaries.ytdlp.exists && state.binaries.ytdlp.executable ? '✓' : '✗'} ${state.binaries.ytdlp.version || 'not found'}`)
  339. console.log(` ffmpeg: ${state.binaries.ffmpeg.exists && state.binaries.ffmpeg.executable ? '✓' : '✗'} ${state.binaries.ffmpeg.version || 'not found'}`)
  340. // Tests
  341. console.log('\nTests:')
  342. console.log(` Total: ${state.tests.total}`)
  343. console.log(` Passing: ${state.tests.passing} (${state.tests.passRate}%)`)
  344. console.log(` Failing: ${state.tests.failing}`)
  345. // Dependencies
  346. console.log('\nDependencies:')
  347. console.log(` Installed: ${state.dependencies.installed ? '✓' : '✗'} (${state.dependencies.count} packages)`)
  348. // Files
  349. console.log('\nCritical Files:')
  350. console.log(` Present: ${state.files.critical.length}`)
  351. console.log(` Missing: ${state.files.missing.length}`)
  352. // App
  353. console.log('\nApp Launch:')
  354. console.log(` Can Launch: ${state.app.launches ? '✓' : '✗'}`)
  355. if (state.app.error) {
  356. console.log(` Error: ${state.app.error}`)
  357. }
  358. // Issues
  359. if (state.health.issues.length > 0) {
  360. console.log('\n⚠ Issues Found:')
  361. state.health.issues.forEach((issue, i) => {
  362. console.log(` ${i + 1}. ${issue}`)
  363. })
  364. }
  365. // Recommendations
  366. console.log('\n📋 Recommendations:')
  367. state.health.recommendations.forEach((rec, i) => {
  368. console.log(` ${i + 1}. ${rec}`)
  369. })
  370. console.log('\n' + '='.repeat(60))
  371. console.log(`Verification completed at ${new Date().toLocaleString()}`)
  372. console.log('='.repeat(60) + '\n')
  373. }
  374. /**
  375. * Main verification function
  376. */
  377. async function verify() {
  378. console.log('\n🔍 Starting GrabZilla 2.1 verification...\n')
  379. checkBinaries()
  380. checkDependencies()
  381. checkCriticalFiles()
  382. await runTests()
  383. checkAppLaunch()
  384. calculateHealthScore()
  385. generateRecommendations()
  386. printSummary()
  387. // Export JSON
  388. const jsonPath = path.join(__dirname, 'project-state.json')
  389. fs.writeFileSync(jsonPath, JSON.stringify(state, null, 2))
  390. log.success(`Full state exported to: ${jsonPath}`)
  391. // Exit code based on status
  392. const exitCode = state.status === 'green' ? 0
  393. : state.status === 'yellow' ? 1
  394. : 2
  395. process.exit(exitCode)
  396. }
  397. // Run verification
  398. verify().catch(error => {
  399. console.error('Verification failed:', error)
  400. process.exit(3)
  401. })