1- import { ChildProcess , execFile } from 'child_process ' ;
1+ import { execa , ResultPromise } from 'execa ' ;
22import * as fs from 'fs/promises' ;
33import * as tmp from 'tmp' ;
44import * as yargs from 'yargs' ;
55
6- function isErrorExists ( e : NodeJS . ErrnoException ) : boolean {
7- return e . code === 'EEXIST' ;
8- }
9-
106type GithubSource = {
117 org : string ;
128 repo : string ;
139} & ( { tag : string } | { ref : string } ) ;
1410
15- async function wait ( p : ChildProcess ) : Promise < void > {
16- p . stderr ?. pipe ( process . stderr ) ;
17- p . stdout ?. pipe ( process . stdout ) ;
18- return new Promise ( ( resolve , reject ) => {
19- p . on ( 'exit' , ( code ) => {
20- if ( code === 0 ) {
21- resolve ( ) ;
22- } else {
23- reject ( new Error ( `Process exited with code ${ code } ` ) ) ;
24- }
25- } ) ;
11+ export function getDescription ( source : GithubSource ) : string {
12+ if ( 'tag' in source ) {
13+ return `${ source . org } /${ source . repo } #${ source . tag } ` ;
14+ } else if ( 'ref' in source ) {
15+ return `${ source . org } /${ source . repo } #${ source . ref } ` ;
16+ } else {
17+ throw new Error ( 'Invalid GithubSource, must have either tag or ref' ) ;
18+ }
19+ }
20+
21+ export type VendorConfig = GithubSource & {
22+ targetDir : string ;
23+ removeFiles : string [ ] ;
24+ /** Git commit range with patches */
25+ cherryPick : {
26+ start : string ;
27+ end : string ;
28+ } | null ;
29+ } ;
30+
31+ function isErrorExists ( e : NodeJS . ErrnoException ) : boolean {
32+ return e . code === 'EEXIST' ;
33+ }
34+
35+ async function gitCommit ( { path, title, message } : { path : string ; title : string ; message : string } ) : Promise < void > {
36+ await execa ( 'git' , [ 'add' , path ] ) ;
37+ await execa ( 'git' , [ 'commit' , path , '-m' , title , '-m' , message , '--no-verify' ] , {
38+ stdio : 'inherit' ,
39+ } ) ;
40+ }
41+
42+ async function gitCherryPick ( revRange : { start : string ; end : string } ) : Promise < void > {
43+ const range = `${ revRange . start } ^..${ revRange . end } ` ;
44+ await execa ( 'git' , [ 'cherry-pick' , range ] , {
45+ stdio : 'inherit' ,
46+ } ) ;
47+ }
48+
49+ async function removeDevAndTestFiles ( removePaths : string [ ] , targetDir : string ) : Promise < void > {
50+ for ( const path of removePaths ) {
51+ console . log ( `Removing dev/test file: ${ path } ` ) ;
52+ const fullPath = `${ targetDir } /${ path } ` ;
53+ await fs . rm ( fullPath , { recursive : true , force : true } ) ;
54+ }
55+ await gitCommit ( {
56+ path : targetDir ,
57+ title : 'chore: remove dev and test files' ,
58+ message : 'This commit removes unnecessary development and test files from the vendor directory.' ,
2659 } ) ;
2760}
2861
@@ -58,6 +91,12 @@ async function fetchArchive(lib: GithubSource, outfile: string): Promise<void> {
5891 await fs . writeFile ( outfile , Buffer . from ( await result . arrayBuffer ( ) ) ) ;
5992}
6093
94+ function pipe ( cmd : ResultPromise ) : ResultPromise {
95+ cmd . stdout ?. pipe ( process . stdout ) ;
96+ cmd . stderr ?. pipe ( process . stderr ) ;
97+ return cmd ;
98+ }
99+
61100async function extractArchive ( archivePath : string , targetDir : string ) : Promise < void > {
62101 try {
63102 await fs . mkdir ( targetDir , { recursive : true } ) ;
@@ -66,18 +105,42 @@ async function extractArchive(archivePath: string, targetDir: string): Promise<v
66105 throw e ;
67106 }
68107 }
69- await wait ( execFile ( 'tar' , [ '-C' , targetDir , '--strip-components' , '1' , '-xzf' , archivePath ] ) ) ;
108+ await pipe ( execa ( 'tar' , [ '-C' , targetDir , '--strip-components' , '1' , '-xzf' , archivePath ] ) ) ;
70109}
71110
72- type VendorConfig = GithubSource & {
73- targetDir : string ;
74- } ;
75-
76- async function main ( cfgs : VendorConfig [ ] ) {
111+ async function cmdVendor (
112+ cfgs : VendorConfig [ ] ,
113+ opts : {
114+ removeFiles : boolean ;
115+ cherryPick : boolean ;
116+ }
117+ ) {
77118 for ( const cfg of cfgs ) {
119+ const desc = getDescription ( cfg ) ;
78120 const archivePath = getArchivePath ( cfg ) ;
79121 await fetchArchive ( cfg , archivePath ) ;
80122 await extractArchive ( archivePath , cfg . targetDir ) ;
123+ await gitCommit ( {
124+ path : cfg . targetDir ,
125+ title : `chore: vendor ${ desc } ` ,
126+ message : `This commit was generated by the vendor-github-repo script.` ,
127+ } ) ;
128+
129+ if ( cfg . removeFiles . length === 0 ) {
130+ console . log ( 'No files to remove for' , cfg . repo ) ;
131+ } else if ( opts . removeFiles ) {
132+ await removeDevAndTestFiles ( cfg . removeFiles , cfg . targetDir ) ;
133+ } else {
134+ console . log ( `Skipping removal of dev/test files for ${ cfg . repo } , use --removeFiles to enable.` ) ;
135+ }
136+
137+ if ( cfg . cherryPick === null ) {
138+ console . log ( `No patch set defined for ${ cfg . repo } , skipping rebase.` ) ;
139+ } else if ( opts . removeFiles ) {
140+ await gitCherryPick ( cfg . cherryPick ) ;
141+ } else {
142+ console . log ( `Skipping cherry-pick for ${ cfg . repo } , use --cherryPick to enable.` ) ;
143+ }
81144 }
82145}
83146
@@ -87,24 +150,77 @@ const vendorConfigs: VendorConfig[] = [
87150 repo : 'btc-staking-ts' ,
88151 tag : 'v1.0.3' ,
89152 targetDir : 'modules/babylonlabs-io-btc-staking-ts' ,
153+ removeFiles : [
154+ '.eslintrc.json' ,
155+ '.github/' ,
156+ '.husky/' ,
157+ '.npmrc' ,
158+ '.nvmrc' ,
159+ '.prettierignore' ,
160+ '.prettierrc.json' ,
161+ 'docs/' ,
162+ 'tests/' ,
163+ 'README.md' ,
164+ ] ,
165+ cherryPick : {
166+ start : '8b8261b8b639d09cbe1223615797c18a2788cd89' ,
167+ end : '06110dd3e892df326261cd79ead158c28370add7' ,
168+ } ,
90169 } ,
91170] ;
92171
172+ function getMatches ( name : string , version : string | undefined ) : VendorConfig [ ] {
173+ const matches = vendorConfigs . filter ( ( cfg ) => {
174+ if ( name !== cfg . repo ) {
175+ return false ;
176+ }
177+ if ( 'tag' in cfg && version !== undefined ) {
178+ return cfg . tag === version ;
179+ }
180+ return true ;
181+ } ) ;
182+ if ( matches . length === 0 ) {
183+ throw new Error ( `no such vendor config ${ name } version ${ version } ` ) ;
184+ }
185+ if ( matches . length > 1 ) {
186+ throw new Error ( `ambiguous vendor config ${ name } ` ) ;
187+ }
188+ return matches ;
189+ }
190+
191+ const optName = {
192+ type : 'string' ,
193+ demand : true ,
194+ describe : 'Name of the vendor config to use, e.g. btc-staking-ts' ,
195+ } as const ;
196+
197+ const optVersion = {
198+ type : 'string' ,
199+ describe : 'Version of the vendor config to use, e.g. v2.3.4' ,
200+ } as const ;
201+
93202yargs
94203 . command ( {
95204 command : 'vendor' ,
205+ describe : 'Vendor a github repo' ,
96206 builder ( a ) {
97- return a . options ( { name : { type : 'string' } } ) ;
207+ return a . options ( {
208+ name : optName ,
209+ pkgVersion : optVersion ,
210+ removeFiles : {
211+ type : 'boolean' ,
212+ default : true ,
213+ describe : 'Remove dev/test files after extracting the archive' ,
214+ } ,
215+ cherryPick : {
216+ type : 'boolean' ,
217+ default : true ,
218+ describe : 'Apply cherry-pick patches after extracting the archive' ,
219+ } ,
220+ } ) ;
98221 } ,
99222 async handler ( a ) {
100- const matches = vendorConfigs . filter ( ( cfg ) => a . name === cfg . repo ) ;
101- if ( matches . length === 0 ) {
102- throw new Error ( `no such vendor config ${ a . name } ` ) ;
103- }
104- if ( matches . length > 1 ) {
105- throw new Error ( `ambiguous vendor config ${ a . name } ` ) ;
106- }
107- await main ( matches ) ;
223+ await cmdVendor ( getMatches ( a . name , a . pkgVersion ) , a ) ;
108224 } ,
109225 } )
110226 . help ( )
0 commit comments