1 #!/usr/bin/env node
2
3 /**
4 * Test script to verify GitHub API connection and releases
5 *
6 * Usage:
7 * GITHUB_TOKEN=your_token node scripts/test-github-releases.js
8 *
9 * Or with .env file:
10 * node scripts/test-github-releases.js
11 *
12 * Environment variables required:
13 * GITHUB_TOKEN - Your GitHub personal access token
14 */
15
16 import { readFileSync } from 'fs'
17 import { fileURLToPath } from 'url'
18 import { dirname, resolve } from 'path'
19
20 // Try to load environment variables from .env file
21 const __filename = fileURLToPath(import.meta.url)
22 const __dirname = dirname(__filename)
23 const envPath = resolve(__dirname, '..', '.env')
24
25 try {
26 const envContent = readFileSync(envPath, 'utf-8')
27 envContent.split('\n').forEach((line) => {
28 const match = line.match(/^\s*([^#][^=]+?)\s*=\s*(.+?)\s*$/)
29 if (match) {
30 const key = match[1]
31 const value = match[2].replace(/^['"]|['"]$/g, '')
32 if (!process.env[key]) {
33 process.env[key] = value
34 }
35 }
36 })
37 } catch (err) {
38 // .env file doesn't exist, that's okay
39 }
40
41 const GITHUB_TOKEN = process.env.GITHUB_TOKEN
42 const GITHUB_REPO = 'setoelkahfi/pepakbasajawa'
43
44 const colors = {
45 reset: '\x1b[0m',
46 bright: '\x1b[1m',
47 red: '\x1b[31m',
48 green: '\x1b[32m',
49 yellow: '\x1b[33m',
50 blue: '\x1b[34m',
51 cyan: '\x1b[36m'
52 }
53
54 function log(message, color = 'reset') {
55 console.log(`${colors[color]}${message}${colors.reset}`)
56 }
57
58 function formatBytes(bytes) {
59 if (bytes === 0) return '0 Bytes'
60 const k = 1024
61 const sizes = ['Bytes', 'KB', 'MB', 'GB']
62 const i = Math.floor(Math.log(bytes) / Math.log(k))
63 return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]
64 }
65
66 async function testGitHubConnection() {
67 log('\n🔍 Testing GitHub API Connection\n', 'bright')
68
69 // Check if token exists
70 if (!GITHUB_TOKEN) {
71 log('❌ ERROR: GITHUB_TOKEN environment variable is not set', 'red')
72 log('\nPlease create a .env file with:', 'yellow')
73 log('GITHUB_TOKEN=ghp_your_token_here\n', 'cyan')
74 log('Or run with:', 'yellow')
75 log('GITHUB_TOKEN=your_token node scripts/test-github-releases.js\n', 'cyan')
76 process.exit(1)
77 }
78
79 log(`✓ GitHub token found (${GITHUB_TOKEN.substring(0, 10)}...)`, 'green')
80 log(`✓ Repository: ${GITHUB_REPO}`, 'green')
81
82 try {
83 // Test API connection with user endpoint
84 log('\n📡 Testing GitHub API authentication...', 'blue')
85 const userResponse = await fetch('https://api.github.com/user', {
86 headers: {
87 Authorization: `Bearer ${GITHUB_TOKEN}`,
88 Accept: 'application/vnd.github+json',
89 'X-GitHub-Api-Version': '2022-11-28'
90 }
91 })
92
93 if (!userResponse.ok) {
94 throw new Error(
95 `GitHub API authentication failed: ${userResponse.status} ${userResponse.statusText}`
96 )
97 }
98
99 const user = await userResponse.json()
100 log(`✓ Authenticated as: ${user.login}`, 'green')
101
102 // Fetch releases
103 log('\n📦 Fetching latest release...', 'blue')
104 const releaseResponse = await fetch(
105 `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`,
106 {
107 headers: {
108 Authorization: `Bearer ${GITHUB_TOKEN}`,
109 Accept: 'application/vnd.github+json',
110 'X-GitHub-Api-Version': '2022-11-28'
111 }
112 }
113 )
114
115 if (releaseResponse.status === 404) {
116 log('⚠️ No releases found in repository', 'yellow')
117 log('\nTo create a release:', 'yellow')
118 log(`1. Go to: https://github.com/${GITHUB_REPO}/releases`, 'cyan')
119 log('2. Click "Draft a new release"', 'cyan')
120 log('3. Create a tag (e.g., v1.0.0)', 'cyan')
121 log('4. Upload your app files', 'cyan')
122 log('5. Click "Publish release"\n', 'cyan')
123 process.exit(0)
124 }
125
126 if (!releaseResponse.ok) {
127 throw new Error(
128 `Failed to fetch releases: ${releaseResponse.status} ${releaseResponse.statusText}`
129 )
130 }
131
132 const release = await releaseResponse.json()
133 log(`✓ Found release: ${release.name || release.tag_name}`, 'green')
134 log(` Version: ${release.tag_name}`, 'cyan')
135 log(` Published: ${new Date(release.published_at).toLocaleString()}`, 'cyan')
136 log(` Assets: ${release.assets.length}`, 'cyan')
137
138 if (release.assets.length === 0) {
139 log('\n⚠️ Release has no assets', 'yellow')
140 log('Upload app files to the release on GitHub\n', 'yellow')
141 process.exit(0)
142 }
143
144 // Analyze assets
145 log('\n📋 Release Assets:', 'bright')
146 log('─'.repeat(80), 'cyan')
147
148 const platformStats = {
149 macOS: [],
150 Windows: [],
151 Linux: [],
152 Unknown: []
153 }
154
155 for (const asset of release.assets) {
156 const filename = asset.name.toLowerCase()
157 let platform = 'Unknown'
158 let architecture = ''
159
160 // Detect platform
161 if (filename.includes('mac') || filename.includes('darwin') || filename.endsWith('.dmg')) {
162 platform = 'macOS'
163 if (
164 filename.includes('arm64') ||
165 filename.includes('aarch64') ||
166 filename.includes('apple')
167 ) {
168 architecture = 'Apple Silicon'
169 } else if (
170 filename.includes('x64') ||
171 filename.includes('intel') ||
172 filename.includes('x86_64')
173 ) {
174 architecture = 'Intel'
175 } else {
176 architecture = 'Universal'
177 }
178 } else if (
179 filename.includes('win') ||
180 filename.endsWith('.exe') ||
181 filename.endsWith('.msi')
182 ) {
183 platform = 'Windows'
184 if (filename.includes('x64') || filename.includes('x86_64')) {
185 architecture = 'x64'
186 } else if (filename.includes('arm64')) {
187 architecture = 'ARM64'
188 } else {
189 architecture = 'x64'
190 }
191 } else if (
192 filename.includes('linux') ||
193 filename.endsWith('.appimage') ||
194 filename.endsWith('.deb') ||
195 filename.endsWith('.rpm') ||
196 filename.endsWith('.tar.gz')
197 ) {
198 platform = 'Linux'
199 if (filename.includes('arm64') || filename.includes('aarch64')) {
200 architecture = 'ARM64'
201 } else if (filename.includes('x64') || filename.includes('x86_64')) {
202 architecture = 'x64'
203 } else {
204 architecture = 'x64'
205 }
206 }
207
208 platformStats[platform].push({
209 name: asset.name,
210 size: asset.size,
211 architecture,
212 downloads: asset.download_count
213 })
214
215 const icon =
216 platform === 'macOS'
217 ? '🍎'
218 : platform === 'Windows'
219 ? '🪟'
220 : platform === 'Linux'
221 ? '🐧'
222 : '❓'
223 log(`${icon} ${asset.name}`, 'bright')
224 log(` Platform: ${platform}${architecture ? ` (${architecture})` : ''}`, 'cyan')
225 log(` Size: ${formatBytes(asset.size)}`, 'cyan')
226 log(` Downloads: ${asset.download_count}`, 'cyan')
227 log(` ID: ${asset.id}`, 'cyan')
228 log('')
229 }
230
231 // Summary
232 log('─'.repeat(80), 'cyan')
233 log('\n📊 Summary:', 'bright')
234 log(
235 ` macOS: ${platformStats.macOS.length} asset(s)`,
236 platformStats.macOS.length > 0 ? 'green' : 'yellow'
237 )
238 log(
239 ` Windows: ${platformStats.Windows.length} asset(s)`,
240 platformStats.Windows.length > 0 ? 'green' : 'yellow'
241 )
242 log(
243 ` Linux: ${platformStats.Linux.length} asset(s)`,
244 platformStats.Linux.length > 0 ? 'green' : 'yellow'
245 )
246
247 if (platformStats.Unknown.length > 0) {
248 log(` Unknown: ${platformStats.Unknown.length} asset(s)`, 'yellow')
249 log('\n⚠️ Some assets could not be categorized:', 'yellow')
250 platformStats.Unknown.forEach((asset) => {
251 log(` - ${asset.name}`, 'yellow')
252 })
253 log('\nConsider renaming to match patterns:', 'yellow')
254 log(' - macOS: *-macos-arm64.dmg or *-macos-x64.dmg', 'cyan')
255 log(' - Windows: *-windows-x64.exe', 'cyan')
256 log(' - Linux: *-linux-x64.AppImage', 'cyan')
257 }
258
259 // Test asset download
260 log('\n🔗 Testing asset download URL...', 'blue')
261 const firstAsset = release.assets[0]
262 const assetResponse = await fetch(
263 `https://api.github.com/repos/${GITHUB_REPO}/releases/assets/${firstAsset.id}`,
264 {
265 method: 'HEAD',
266 headers: {
267 Authorization: `Bearer ${GITHUB_TOKEN}`,
268 Accept: 'application/octet-stream',
269 'X-GitHub-Api-Version': '2022-11-28'
270 }
271 }
272 )
273
274 if (assetResponse.ok) {
275 log('✓ Asset download URLs are accessible', 'green')
276 } else {
277 log('⚠️ Asset download test returned status: ' + assetResponse.status, 'yellow')
278 }
279
280 // Success message
281 log('\n✅ All tests passed!', 'green')
282 log('\nYour download page should work correctly.', 'bright')
283 log('Start your dev server with: npm run dev', 'cyan')
284 log('Then visit: http://localhost:4321/download\n', 'cyan')
285 } catch (error) {
286 log('\n❌ ERROR:', 'red')
287 log(error.message, 'red')
288 log('\nTroubleshooting:', 'yellow')
289 log('1. Verify your GitHub token has "repo" scope', 'cyan')
290 log('2. Check that the repository name is correct', 'cyan')
291 log("3. Ensure the token hasn't expired", 'cyan')
292 log('4. Try generating a new token at: https://github.com/settings/tokens\n', 'cyan')
293 process.exit(1)
294 }
295 }
296
297 // Run the test
298 testGitHubConnection()