1- #!/usr/bin/env node
2- /* eslint-disable @typescript-eslint/no-var-requires */
1+ // scripts/verify-release-assets.js
32
4- const fs = require ( "fs" ) ;
5- const path = require ( "path" ) ;
6- const https = require ( "https" ) ;
3+ import fetch from "node-fetch" ;
74
8- /**
9- * Simple fetch polyfill using https module to avoid dependencies
10- */
11- function fetch ( url , options = { } ) {
12- return new Promise ( ( resolve , reject ) => {
13- const req = https . request ( url , {
14- method : options . method || 'GET' ,
15- headers : options . headers || { } ,
16- } , ( res ) => {
17- const chunks = [ ] ;
18- res . on ( 'data' , ( chunk ) => chunks . push ( chunk ) ) ;
19- res . on ( 'end' , ( ) => {
20- const body = Buffer . concat ( chunks ) . toString ( 'utf8' ) ;
21- resolve ( {
22- ok : res . statusCode >= 200 && res . statusCode < 300 ,
23- status : res . statusCode ,
24- statusText : res . statusMessage ,
25- text : ( ) => Promise . resolve ( body ) ,
26- json : ( ) => {
27- try {
28- return Promise . resolve ( JSON . parse ( body ) ) ;
29- } catch ( e ) {
30- return Promise . reject ( e ) ;
31- }
32- } ,
33- } ) ;
34- } ) ;
35- } ) ;
5+ // GitHub API configuration
6+ const owner = "SFARPak" ;
7+ const repo = "AliFullStack" ;
8+ const token = process . env . GITHUB_TOKEN ;
369
37- req . on ( 'error' , ( err ) => reject ( err ) ) ;
38- req . end ( ) ;
39- } ) ;
10+ if ( ! token ) {
11+ console . error ( "β GITHUB_TOKEN not provided." ) ;
12+ process . exit ( 1 ) ;
4013}
4114
42- /**
43- * Verifies that all expected binary assets are present in the GitHub release
44- * for the version specified in package.json
45- */
46- async function verifyReleaseAssets ( ) {
47- try {
48- // Read version from package.json
49- const packagePath = path . join ( __dirname , ".." , "package.json" ) ;
50- if ( ! fs . existsSync ( packagePath ) ) {
51- console . error ( "β package.json not found at" , packagePath ) ;
52- process . exit ( 1 ) ;
53- }
54- const packageJson = JSON . parse ( fs . readFileSync ( packagePath , "utf8" ) ) ;
55- const version = packageJson . version ;
56-
57- console . log ( `π Verifying release assets for version ${ version } ...` ) ;
58-
59- // GitHub API configuration
60- const owner = "SFARPak" ;
61- const repo = "AliFullStack" ;
62- const token = process . env . GITHUB_TOKEN ;
63-
64- if ( ! token ) {
65- console . error ( "β Missing GITHUB_TOKEN environment variable!" ) ;
66- process . exit ( 1 ) ;
67- }
68-
69- const inGitHubActions = process . env . GITHUB_ACTIONS === "true" ;
70-
71- if ( ! inGitHubActions ) {
72- console . log ( "π Checking GITHUB_TOKEN permissions..." ) ;
73- // Validate token by calling /user
74- const userCheck = await fetch ( "https://api.github.com/user" , {
75- headers : {
76- Authorization : `token ${ token } ` ,
77- Accept : "application/vnd.github.v3+json" ,
78- "User-Agent" : "alifullstack-release-verifier" ,
79- } ,
80- } ) ;
81-
82- if ( ! userCheck . ok ) {
83- const body = await userCheck . text ( ) ;
84- console . error ( "β Token authentication failed!" ) ;
85- console . error ( `Status: ${ userCheck . status } ${ userCheck . statusText } ` ) ;
86- console . error ( `Response body: ${ body } ` ) ;
87- process . exit ( 1 ) ;
88- }
15+ async function getJson ( url ) {
16+ const res = await fetch ( url , {
17+ headers : { Authorization : `Bearer ${ token } ` , "User-Agent" : "verify-script" }
18+ } ) ;
8919
90- const userData = await userCheck . json ( ) ;
91- console . log ( `β
Authenticated as: ${ userData . login } ` ) ;
92- } else {
93- console . log ( "π Running inside GitHub Actions β no user authentication check needed" ) ;
94- // quick repo check to ensure token can access repo
95- const appCheck = await fetch ( `https://api.github.com/repos/${ owner } /${ repo } ` , {
96- headers : {
97- Authorization : `token ${ token } ` ,
98- Accept : "application/vnd.github.v3+json" ,
99- "User-Agent" : "alifullstack-release-verifier" ,
100- } ,
101- } ) ;
20+ if ( res . status === 404 ) return null ;
21+ if ( ! res . ok ) {
22+ console . error ( `β GitHub API error ${ res . status } : ${ await res . text ( ) } ` ) ;
23+ process . exit ( 1 ) ;
24+ }
10225
103- if ( ! appCheck . ok ) {
104- const body = await appCheck . text ( ) ;
105- console . error ( "β Token authentication failed on repo check!" ) ;
106- console . error ( `Status: ${ appCheck . status } ${ appCheck . statusText } ` ) ;
107- console . error ( `Response body: ${ body } ` ) ;
108- process . exit ( 1 ) ;
109- }
26+ return res . json ( ) ;
27+ }
11028
111- const repoData = await appCheck . json ( ) ;
112- console . log ( `β
Token authenticated for repository: ${ repoData . full_name } ` ) ;
113- }
29+ async function main ( ) {
30+ const tag = process . env . GITHUB_REF_NAME || process . env . TAG_NAME ;
11431
115- const tagName = `v${ version } ` ;
116- const maxRetries = 8 ;
117- const baseDelay = 10000 ; // 10 seconds
118- let release = null ;
119- let lastError = null ;
32+ if ( ! tag ) {
33+ console . error ( "β No TAG_NAME or GITHUB_REF_NAME detected." ) ;
34+ process . exit ( 1 ) ;
35+ }
12036
121- // Try to fetch the release by tag name. This avoids scanning the entire releases list.
122- for ( let attempt = 1 ; attempt <= maxRetries ; attempt ++ ) {
123- try {
124- console . log ( `π‘ Attempt ${ attempt } /${ maxRetries } : Fetching release by tag: ${ tagName } ` ) ;
37+ console . log ( `π Looking up release for tag: ${ tag } ` ) ;
12538
126- const releaseUrl = `https://api.github.com/repos/${ owner } /${ repo } /releases/tags/${ tagName } ` ;
127- const response = await fetch ( releaseUrl , {
128- headers : {
129- Authorization : `token ${ token } ` ,
130- Accept : "application/vnd.github.v3+json" ,
131- "User-Agent" : "alifullstack-release-verifier" ,
132- } ,
133- } ) ;
39+ // -------------------------------------------------------------------------
40+ // 1. Try standard API (does NOT work for draft releases)
41+ // -------------------------------------------------------------------------
42+ let release = await getJson (
43+ `https://api.github.com/repos/${ owner } /${ repo } /releases/tags/${ tag } `
44+ ) ;
13445
135- if ( ! response . ok ) {
136- const body = await response . text ( ) ;
137- console . warn ( `β οΈ GitHub API returned ${ response . status } ${ response . statusText } ` ) ;
138- console . warn ( "Response body:" , body ) ;
139- if ( response . status === 404 ) {
140- console . warn ( `β οΈ Release ${ tagName } not found (404). Will retry.` ) ;
141- } else {
142- console . warn ( "β οΈ Non-404 response; will retry after delay." ) ;
143- }
144- if ( attempt < maxRetries ) {
145- const delay = baseDelay * attempt ;
146- console . log ( `β³ Waiting ${ delay / 1000 } s before retry...` ) ;
147- await new Promise ( ( r ) => setTimeout ( r , delay ) ) ;
148- continue ;
149- } else {
150- throw new Error ( `Failed to fetch release: ${ response . status } ` ) ;
151- }
152- }
46+ if ( ! release ) {
47+ console . log ( "β οΈ Tag API returned no release (likely a draft release). Falling back to full release listβ¦" ) ;
15348
154- release = await response . json ( ) ;
49+ // ---------------------------------------------------------------------
50+ // 2. Fetch all releases and find manually (works with drafts)
51+ // ---------------------------------------------------------------------
52+ const releases = await getJson (
53+ `https://api.github.com/repos/${ owner } /${ repo } /releases?per_page=100`
54+ ) ;
15555
156- console . log ( `β
Found release: ${ release . tag_name } (${ release . draft ? "DRAFT" : "PUBLISHED" } )` ) ;
157- // If release exists but has zero assets, wait and retry (registrations can be delayed)
158- const assets = release . assets || [ ] ;
159- console . log ( `π¦ Found ${ assets . length } assets in release ${ tagName } ` ) ;
160- if ( assets . length === 0 && attempt < maxRetries ) {
161- const delay = baseDelay * attempt ;
162- console . log ( `β οΈ No assets present yet. Waiting ${ delay / 1000 } s before retry...` ) ;
163- await new Promise ( ( r ) => setTimeout ( r , delay ) ) ;
164- continue ;
165- }
166- break ;
167- } catch ( err ) {
168- lastError = err ;
169- console . error ( `β Attempt ${ attempt } failed: ${ err . message } ` ) ;
170- if ( attempt < maxRetries ) {
171- const delay = baseDelay * attempt ;
172- console . log ( `β³ Retrying in ${ delay / 1000 } s...` ) ;
173- await new Promise ( ( r ) => setTimeout ( r , delay ) ) ;
174- }
175- }
176- }
56+ release = releases . find ( r => r . tag_name === tag ) ;
17757
17858 if ( ! release ) {
179- console . error ( `β Release ${ tagName } not found after ${ maxRetries } attempts` ) ;
180- if ( lastError ) console . error ( `Last error: ${ lastError . message } ` ) ;
59+ console . error ( `β Release for tag ${ tag } not found even in full release list.` ) ;
18160 process . exit ( 1 ) ;
18261 }
62+ }
18363
184- const assets = release . assets || [ ] ;
64+ console . log ( `β Found release: ${ release . name || release . tag_name } ` ) ;
65+ console . log ( ` Draft: ${ release . draft } ` ) ;
66+ console . log ( ` Prerelease: ${ release . prerelease } ` ) ;
18567
186- console . log ( `π¦ Found ${ assets . length } assets in release ${ tagName } ` ) ;
187- console . log ( `π Release status: ${ release . draft ? "DRAFT" : "PUBLISHED" } ` ) ;
188- console . log ( "" ) ;
68+ // -------------------------------------------------------------------------
69+ // 3. Reject draft releases (electron-builder default)
70+ // -------------------------------------------------------------------------
71+ if ( release . draft ) {
72+ console . error ( "β Release is still a DRAFT β publish it before verifying assets." ) ;
73+ process . exit ( 1 ) ;
74+ }
18975
190- // --- Define expected assets ---
191- const normalizeVersionForPlatform = ( version , platform ) => {
192- if ( ! version . includes ( "beta" ) ) return version ;
76+ // -------------------------------------------------------------------------
77+ // 4. Check assets
78+ // -------------------------------------------------------------------------
79+ if ( ! release . assets || release . assets . length === 0 ) {
80+ console . error ( "β No assets found in release. Build/upload may have failed." ) ;
81+ process . exit ( 1 ) ;
82+ }
19383
194- switch ( platform ) {
195- case "rpm" :
196- case "deb" :
197- return version . replace ( "-beta." , ".beta." ) ;
198- case "nupkg" :
199- return version . replace ( "-beta." , "-beta" ) ;
200- default :
201- return version ;
202- }
203- } ;
84+ console . log ( `π¦ Found ${ release . assets . length } assets:` ) ;
85+ for ( const asset of release . assets ) {
86+ console . log ( ` - ${ asset . name } (${ asset . size } bytes)` ) ;
87+ }
20488
205- const expectedAssets = [
206- `alifullstack-${ normalizeVersionForPlatform ( version , "rpm" ) } -1.x86_64.rpm` ,
207- `alifullstack-${ normalizeVersionForPlatform ( version , "nupkg" ) } -full.nupkg` ,
208- `alifullstack-${ version } .Setup.exe` ,
209- `alifullstack-darwin-arm64-${ version } .zip` ,
210- `alifullstack-darwin-x64-${ version } .zip` ,
211- `alifullstack_${ normalizeVersionForPlatform ( version , "deb" ) } _amd64.deb` ,
212- "RELEASES" ,
213- ] ;
89+ console . log ( "π All assets successfully uploaded and release is published!" ) ;
90+ }
21491
215- console . log ( "π Expected assets:" ) ;
216- expectedAssets . forEach ( ( a ) => console . log ( ` - ${ a } ` ) ) ;
217- console . log ( "" ) ;
92+ main ( ) . catch ( ( err ) => {
93+ console . error ( "β Script error:" , err ) ;
94+ process . exit ( 1 ) ;
95+ } ) ;
21896
219- const actualAssets = assets . map ( ( a ) => a . name ) ;
220- console . log ( "π Actual assets:" ) ;
221- if ( actualAssets . length === 0 ) {
222- console . log ( "(none)" ) ;
223- } else {
224- actualAssets . forEach ( ( a ) => console . log ( ` - ${ a } ` ) ) ;
225- }
226- console . log ( "" ) ;
22797
228- // --- Compare assets ---
229- const missingAssets = expectedAssets . filter ( ( a ) => ! actualAssets . includes ( a ) ) ;
230- if ( missingAssets . length > 0 ) {
231- console . error ( "β VERIFICATION FAILED! Missing assets:" ) ;
232- missingAssets . forEach ( ( a ) => console . error ( ` - ${ a } ` ) ) ;
233- console . error ( "" ) ;
234- // For debugging, emit the full release JSON to help identify naming differences
235- console . error ( "π Full release JSON preview (first 2000 chars):" ) ;
236- try {
237- const releaseJson = JSON . stringify ( release , null , 2 ) ;
238- console . error ( releaseJson . substring ( 0 , 2000 ) ) ;
239- } catch ( _ ) {
240- // ignore
241- }
242- process . exit ( 1 ) ;
243- }
24498
245- const unexpectedAssets = actualAssets . filter ( ( a ) => ! expectedAssets . includes ( a ) ) ;
246- if ( unexpectedAssets . length > 0 ) {
247- console . warn ( "β οΈ Unexpected assets found:" ) ;
248- unexpectedAssets . forEach ( ( a ) => console . warn ( ` - ${ a } ` ) ) ;
249- console . warn ( "" ) ;
250- }
25199
252- console . log ( "β
VERIFICATION PASSED!" ) ;
253- console . log ( `π All ${ expectedAssets . length } expected assets are present in release ${ tagName } ` ) ;
254- console . log ( "" ) ;
255- console . log ( "π Release Summary:" ) ;
256- console . log ( ` Release: ${ release . name || tagName } ` ) ;
257- console . log ( ` Tag: ${ release . tag_name } ` ) ;
258- console . log ( ` Published: ${ release . published_at } ` ) ;
259- console . log ( ` URL: ${ release . html_url } ` ) ;
260- } catch ( error ) {
261- console . error ( "β Error verifying release assets:" , error && error . message ? error . message : error ) ;
262- process . exit ( 1 ) ;
263- }
264- }
265100
266- // Run the verification
267- verifyReleaseAssets ( ) ;
0 commit comments