1+ // This is a build script temporarily.
2+ // If development of https://github.com/TypescriptPrime/userscript-build-toolkit is completed,
3+ // this script will be replaced by that package.
4+
5+ import * as ESBuild from 'esbuild'
6+ import * as Zod from 'zod'
7+ import PackageJson from '@npmcli/package-json'
8+ import * as Semver from 'semver'
9+ import * as Fs from 'node:fs'
10+ import * as NodeHttps from 'node:https'
11+ import * as Process from 'node:process'
12+
13+ let BuildType : 'production' | 'development' = Zod . string ( ) . refine ( BT => BT === 'production' || BT === 'development' ) . default ( 'production' ) . parse ( Process . argv [ 4 ] ?? 'production' )
14+ console . log ( 'Build type set to:' , BuildType )
15+
16+ let Version : string = ( await PackageJson . load ( './' ) ) . content . version
17+ Version = await Zod . string ( ) . refine ( V => Semver . valid ( V ) !== null ) . parseAsync ( Version )
18+ console . log ( 'Applying version value:' , Version )
19+
20+ let DomainsList : Set < string > = new Set ( )
21+
22+ const IABSellersJsonURL = 'https://info.ad-shield.io/sellers.json'
23+ const IABSellersJsonResponse : { StatusCode : number , Headers : Record < string , string | string [ ] > , Body : string } = await new Promise ( ( Resolve , Reject ) => {
24+ const IABSellersJsonReq = NodeHttps . get ( {
25+ hostname : new URL ( IABSellersJsonURL ) . hostname ,
26+ path : new URL ( IABSellersJsonURL ) . pathname ,
27+ headers : {
28+ 'user-agent' : 'node/v24.12.0 linux x64 workspaces/true'
29+ } ,
30+ ciphers : 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256' ,
31+ ecdhCurve : 'X25519MLKEM768' ,
32+ minVersion : 'TLSv1.3'
33+ } , ( Res ) => {
34+ const Chunks : Buffer [ ] = [ ]
35+ Res . on ( 'data' , ( Chunk : Buffer ) => Chunks . push ( Chunk ) )
36+ Res . on ( 'end' , ( ) => {
37+ Resolve ( {
38+ StatusCode : Res . statusCode ,
39+ Headers : Res . headers ,
40+ Body : new TextDecoder ( ) . decode ( Buffer . concat ( Chunks ) )
41+ } )
42+ } )
43+ IABSellersJsonReq . on ( 'error' , ( Err ) => Reject ( Err ) )
44+ } )
45+ } )
46+ console . log ( 'Fetched IAB Sellers.json with status code:' , IABSellersJsonResponse . StatusCode )
47+ let IABSellersJsonData = JSON . parse ( IABSellersJsonResponse . Body ) as {
48+ // eslint-disable-next-line @typescript-eslint/naming-convention
49+ sellers : Array < {
50+ // eslint-disable-next-line @typescript-eslint/naming-convention
51+ seller_id : number ,
52+ // eslint-disable-next-line @typescript-eslint/naming-convention
53+ seller_type : string ,
54+ // eslint-disable-next-line @typescript-eslint/naming-convention
55+ name : string ,
56+ // eslint-disable-next-line @typescript-eslint/naming-convention
57+ domain : string
58+ } >
59+ }
60+ IABSellersJsonData = await Zod . object ( {
61+ sellers : Zod . array ( Zod . object ( {
62+ seller_id : Zod . number ( ) ,
63+ seller_type : Zod . string ( ) ,
64+ name : Zod . string ( ) ,
65+ domain : Zod . string ( ) . refine ( D => {
66+ try {
67+ new URL ( `https://${ D } ` )
68+ } catch {
69+ return false
70+ }
71+ return true
72+ } )
73+ } ) )
74+ } ) . parseAsync ( IABSellersJsonData )
75+ console . log ( 'Validated IAB Sellers.json data' )
76+ for ( const SellerEntry of IABSellersJsonData . sellers ) {
77+ DomainsList . add ( SellerEntry . domain )
78+ }
79+ console . log ( 'Collected' , DomainsList . size , 'unique domains from IAB Sellers.json' )
80+
81+ const HeaderLocation = './sources/banner.txt'
82+ let ConvertedHeader : string = ''
83+ for ( const Line of Fs . readFileSync ( HeaderLocation , 'utf-8' ) . split ( '\n' ) ) {
84+ if ( Line . includes ( '%%VERSION_VALUE%%' ) ) {
85+ ConvertedHeader += Line . replaceAll ( '%%VERSION_VALUE%%' , Version ) + '\n'
86+ } else if ( Line . includes ( '%%NAME%%' ) ) {
87+ ConvertedHeader += Line . replaceAll ( '%%NAME%%' , BuildType === 'production' ? 'tinyShield' : 'tinyShield (Development)' ) + '\n'
88+ } else if ( Line === '%%DOMAIN_INJECTION%%' ) {
89+ for ( const DomainEntry of DomainsList ) {
90+ ConvertedHeader += `// @match *://${ DomainEntry } /*\n`
91+ ConvertedHeader += `// @match *://*.${ DomainEntry } /*\n`
92+ }
93+ } else {
94+ ConvertedHeader += Line + '\n'
95+ }
96+ }
97+ console . log ( 'Generated header with domain injections and processing' )
98+ let AttachHeaderPath = `/tmp/${ crypto . randomUUID ( ) } `
99+ Fs . writeFileSync ( AttachHeaderPath , ConvertedHeader , 'utf-8' )
100+ console . log ( 'Written temporary header file to:' , AttachHeaderPath )
101+ await ESBuild . build ( {
102+ entryPoints : [ './sources/src/index.ts' ] ,
103+ bundle : true ,
104+ minify : true ,
105+ define : {
106+ global : 'window'
107+ } ,
108+ inject : [ './sources/esbuild.inject.ts' ] ,
109+ banner : {
110+ js : Fs . readFileSync ( AttachHeaderPath , 'utf-8' )
111+ } ,
112+ target : [ 'es2024' , 'chrome119' , 'firefox117' , 'safari121' ] ,
113+ outfile : BuildType === 'production' ? './dist/tinyshield.user.js' : './dist/tinyshield.dev.user.js' ,
114+ } )
115+ console . log ( 'Build completed' )
116+ Fs . rmSync ( AttachHeaderPath )
117+ console . log ( 'Temporary header file removed' )
0 commit comments