1
1
import { 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" ;
5
3
import ArrayStringMap from "array-string-map" ;
6
4
import filterAsync from "node-filter-async" ;
7
5
import { 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
+ }
8
26
9
27
export default async function handler ( script : string , flags : CliFlags ) {
10
- console . log ( script , flags ) ;
11
28
const { owner : defaultOwner , _ : repoNames } = flags ;
29
+ let { search } = flags ;
12
30
const repos : RepoOwner [ ] = [ ] ;
13
31
for ( const repoName of repoNames ) {
14
32
try {
@@ -20,35 +38,39 @@ export default async function handler(script: string, flags: CliFlags) {
20
38
process . exit ( 1 ) ;
21
39
}
22
40
}
41
+
23
42
const tempDir = await mkdtemp ( `${ tmpdir ( ) } /github-run-script-` ) ;
24
43
try {
25
44
const directoryMapping : ArrayStringMap < RepoOwner , string > =
26
45
new ArrayStringMap ( ) ;
27
46
const scanDirectories : Map < string , string [ ] > = new Map ( ) ;
28
- if ( flags . search ) {
47
+ if ( search ) {
29
48
// 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 ] ;
32
51
}
52
+
33
53
// What this code block does is simple. It stores in `scanDirectories`
34
54
// a mapping of source path name to an array of directories in that directory.
35
55
await Promise . all (
36
- flags . search . map ( async ( path ) => {
56
+ search . map ( async ( path ) => {
37
57
scanDirectories . set (
38
58
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
+ )
42
62
) ;
43
63
} )
44
64
) ;
45
65
}
66
+
46
67
await Promise . all (
47
68
repos . map ( async ( [ owner , repo ] ) => {
48
69
// 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`.)
52
74
for ( const [ path , directories ] of scanDirectories ) {
53
75
for ( const directory of directories ) {
54
76
if ( repo === directory ) {
@@ -57,12 +79,14 @@ export default async function handler(script: string, flags: CliFlags) {
57
79
break ;
58
80
}
59
81
}
82
+
60
83
// If we already found a match earlier, no need to re-iterate over the other
61
84
// directories.
62
85
if ( directoryMapping . has ( [ owner , repo ] ) ) {
63
86
break ;
64
87
}
65
88
}
89
+
66
90
// Deal wit the special case where we did not find a match. Time to clone.
67
91
if ( ! directoryMapping . has ( [ owner , repo ] ) ) {
68
92
const destPath = `${ tempDir } /${ repo } ` ;
@@ -75,6 +99,7 @@ export default async function handler(script: string, flags: CliFlags) {
75
99
await waitOnChildProcessToExit ( childProc ) ;
76
100
directoryMapping . set ( [ owner , repo ] , destPath ) ;
77
101
}
102
+
78
103
// Time to execute the script!
79
104
const path = directoryMapping . get ( [ owner , repo ] ) ;
80
105
const childProc = await spawn ( script , [ ] , {
@@ -92,6 +117,11 @@ export default async function handler(script: string, flags: CliFlags) {
92
117
} )
93
118
) ;
94
119
} 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
+
95
125
// We need to clean up the temporary directory.
96
126
await rm ( tempDir , { recursive : true , force : true } ) ;
97
127
}
0 commit comments