1- import { execFileSync , spawnSync , type ExecFileSyncOptionsWithStringEncoding } from 'child_process'
2- import { mkdir } from 'fs/promises'
1+ import { execFileSync , type ExecFileSyncOptionsWithStringEncoding , spawnSync } from 'child_process'
2+ import { mkdir , readdir , unlink , rmdir } from 'fs/promises'
3+ import { dirname , join } from 'path'
34
45const { BRANCH , WORKFLOW , DEST_DIR , REPOSITORY = 'curvefi/curve-frontend' } = process . env
56
@@ -53,10 +54,58 @@ const downloadArtifacts = (runId: string, dest: string) => {
5354 runStreaming ( 'gh' , [ 'run' , 'download' , runId , '--repo' , REPOSITORY , '--dir' , dest ] )
5455}
5556
57+ /**
58+ * Check if a directory contains any .png files (indicating test failures).
59+ */
60+ async function hasScreenshots ( dir : string ) : Promise < boolean > {
61+ const entries = await readdir ( dir , { withFileTypes : true } ) . catch ( ( error : unknown ) => {
62+ if ( ( error as NodeJS . ErrnoException ) . code === 'ENOENT' ) return undefined // missing directory, so no screenshots
63+ throw error
64+ } )
65+ if ( ! entries ) return false // does not exist
66+ if ( entries . length === 0 ) {
67+ console . info ( `Removing empty screenshot directory: ${ dir } ` )
68+ await rmdir ( dir )
69+ return false
70+ }
71+ for ( const entry of entries ) {
72+ const fullPath = join ( dir , entry . name )
73+ if ( entry . isDirectory ( ) ) {
74+ if ( await hasScreenshots ( fullPath ) ) {
75+ return true
76+ }
77+ } else if ( entry . name . endsWith ( '.png' ) ) {
78+ console . info ( `Found screenshot file ${ entry . name } ` )
79+ return true
80+ }
81+ }
82+ return false
83+ }
84+
85+ /**
86+ * Recursively find and delete videos from successful tests (those without matching screenshots).
87+ */
88+ async function cleanupSuccessfulTestVideos ( dir : string ) : Promise < void > {
89+ const entries = await readdir ( dir , { withFileTypes : true } )
90+
91+ for ( const entry of entries ?? [ ] ) {
92+ const fullPath = join ( dir , entry . name )
93+
94+ if ( entry . isDirectory ( ) ) {
95+ await cleanupSuccessfulTestVideos ( fullPath )
96+ } else if ( entry . name . endsWith ( '.mp4' ) ) {
97+ // For a video file like "test.cy.ts.mp4", check if "test.cy.ts/" directory has screenshots
98+ const videoBaseName = entry . name . slice ( 0 , - 4 ) // Remove .mp4 extension
99+ const screenshotDir = join ( dirname ( fullPath ) , videoBaseName )
100+ if ( ! ( await hasScreenshots ( screenshotDir ) ) ) await unlink ( fullPath )
101+ }
102+ }
103+ }
104+
56105/**
57106 * Orchestrate download + extraction of the latest workflow artifacts for the current branch.
58107 */
59- async function downloadLatestArtifacts ( ) : Promise < void > {
108+ async function downloadLatestArtifacts ( { cleanup } : { cleanup : boolean } ) : Promise < void > {
60109 if ( ! hasCommand ( 'gh' ) ) {
61110 throw new Error ( 'GitHub CLI (gh) is required but not installed.' )
62111 }
@@ -78,9 +127,21 @@ async function downloadLatestArtifacts(): Promise<void> {
78127
79128 console . info ( `Downloading artifacts for branch '${ branch } ' (workflow: ${ workflow } , run: ${ runId } ) into '${ dest } '...` )
80129 downloadArtifacts ( runId , dest )
130+
131+ console . info ( 'Cleaning up videos from successful tests...' )
132+ await cleanupSuccessfulTestVideos ( dest )
133+ console . info ( 'Cleanup complete.' )
81134}
82135
83- downloadLatestArtifacts ( ) . catch ( ( error ) => {
136+ /**
137+ * Simple usage:
138+ * node --experimental-strip-types scripts/download-artifacts.ts
139+ *
140+ * Custom usage:
141+ * BRANCH=feature/xyz WORKFLOW=ci.yaml DEST_DIR=tests/artifacts \
142+ * node --experimental-strip-types scripts/download-artifacts.ts --skip-cleanup
143+ */
144+ downloadLatestArtifacts ( { cleanup : ! process . argv . includes ( '--skip-cleanup' ) } ) . catch ( ( error ) => {
84145 console . error ( error instanceof Error ? error . message : error )
85146 process . exitCode = 1
86147} )
0 commit comments