11import { mkdtemp , readdir , stat , rm } from "fs/promises" ;
2- import { spawn } from "child_process" ;
3- import { CliFlags , RepoOwner } from "./types" ;
4- import { getRepoAndOwner , waitOnChildProcessToExit } from "./utils" ;
2+ import { ChildProcess , spawn as actualSpawn } from "child_process" ;
53import ArrayStringMap from "array-string-map" ;
64import filterAsync from "node-filter-async" ;
75import { tmpdir } from "os" ;
6+ import { getRepoAndOwner , waitOnChildProcessToExit } from "./utils" ;
7+ import { CliFlags , RepoOwner } from "./types" ;
8+
9+ // We need to make a spawn cache. If an exception is thrown,
10+ // we need to be able to comply with the --terminate flag..
11+ const spawnedProcesses : Map < ChildProcess , null > = new Map ( ) ;
12+
13+ async function spawn ( command : string , args ?: string [ ] , options ?: object ) {
14+ const childProcess = await actualSpawn ( command , args , options ) ;
15+ spawnedProcesses . set ( childProcess , null ) ;
16+ childProcess . on ( "exit" , ( ) => spawnedProcesses . delete ( childProcess ) ) ;
17+ return childProcess ;
18+ }
19+
20+ // eslint-disable-next-line no-undef -- NodeJS is a fake namespace by TypeScript.
21+ function terminateSpawnCache ( signal : NodeJS . Signals = "SIGTERM" ) {
22+ for ( const childProcess of spawnedProcesses . keys ( ) ) {
23+ childProcess . kill ( signal ) ;
24+ }
25+ }
826
927export default async function handler ( script : string , flags : CliFlags ) {
10- console . log ( script , flags ) ;
1128 const { owner : defaultOwner , _ : repoNames } = flags ;
29+ let { search } = flags ;
1230 const repos : RepoOwner [ ] = [ ] ;
1331 for ( const repoName of repoNames ) {
1432 try {
@@ -20,35 +38,39 @@ export default async function handler(script: string, flags: CliFlags) {
2038 process . exit ( 1 ) ;
2139 }
2240 }
41+
2342 const tempDir = await mkdtemp ( `${ tmpdir ( ) } /github-run-script-` ) ;
2443 try {
2544 const directoryMapping : ArrayStringMap < RepoOwner , string > =
2645 new ArrayStringMap ( ) ;
2746 const scanDirectories : Map < string , string [ ] > = new Map ( ) ;
28- if ( flags . search ) {
47+ if ( search ) {
2948 // If it's one path, it won't be in an array. This converts it into an array.
30- if ( typeof flags . search === "string" ) {
31- flags . search = [ flags . search ] ;
49+ if ( typeof search === "string" ) {
50+ search = [ search ] ;
3251 }
52+
3353 // What this code block does is simple. It stores in `scanDirectories`
3454 // a mapping of source path name to an array of directories in that directory.
3555 await Promise . all (
36- flags . search . map ( async ( path ) => {
56+ search . map ( async ( path ) => {
3757 scanDirectories . set (
3858 path ,
39- await filterAsync ( await readdir ( path ) , async ( item ) => {
40- return ( await stat ( `${ path } /${ item } ` ) ) . isDirectory ( ) ;
41- } )
59+ await filterAsync ( await readdir ( path ) , async ( item ) =>
60+ ( await stat ( `${ path } /${ item } ` ) ) . isDirectory ( )
61+ )
4262 ) ;
4363 } )
4464 ) ;
4565 }
66+
4667 await Promise . all (
4768 repos . map ( async ( [ owner , repo ] ) => {
4869 // First, we need to check if the repository exists in `scanDirectories`.
49- // TODO: Handle cases where the same repo is present multiple times in
50- // TODO: different directories, or if two repos with the same name but
51- // TODO: different owners is provided. (Maybe we can check `.git`.)
70+ // TODO:
71+ // Handle cases where the same repo is present multiple times in
72+ // different directories, or if two repos with the same name but
73+ // different owners is provided. (Maybe we can check `.git`.)
5274 for ( const [ path , directories ] of scanDirectories ) {
5375 for ( const directory of directories ) {
5476 if ( repo === directory ) {
@@ -57,12 +79,14 @@ export default async function handler(script: string, flags: CliFlags) {
5779 break ;
5880 }
5981 }
82+
6083 // If we already found a match earlier, no need to re-iterate over the other
6184 // directories.
6285 if ( directoryMapping . has ( [ owner , repo ] ) ) {
6386 break ;
6487 }
6588 }
89+
6690 // Deal wit the special case where we did not find a match. Time to clone.
6791 if ( ! directoryMapping . has ( [ owner , repo ] ) ) {
6892 const destPath = `${ tempDir } /${ repo } ` ;
@@ -75,6 +99,7 @@ export default async function handler(script: string, flags: CliFlags) {
7599 await waitOnChildProcessToExit ( childProc ) ;
76100 directoryMapping . set ( [ owner , repo ] , destPath ) ;
77101 }
102+
78103 // Time to execute the script!
79104 const path = directoryMapping . get ( [ owner , repo ] ) ;
80105 const childProc = await spawn ( script , [ ] , {
@@ -92,6 +117,11 @@ export default async function handler(script: string, flags: CliFlags) {
92117 } )
93118 ) ;
94119 } finally {
120+ if ( flags . terminate ?? true ) {
121+ // eslint-disable-next-line no-undef -- NodeJS is a fake namespace by TypeScript.
122+ terminateSpawnCache ( flags . signal as NodeJS . Signals ) ;
123+ }
124+
95125 // We need to clean up the temporary directory.
96126 await rm ( tempDir , { recursive : true , force : true } ) ;
97127 }
0 commit comments