dependabot/npm_and_yarn/astro-5.16.4
copilot/add-i18n-support-for-english
dependabot/npm_and_yarn/astro-5.16.4
dependabot/npm_and_yarn/katex-0.16.27
dependabot/npm_and_yarn/prettier-3.7.4
dependabot/npm_and_yarn/tailwindcss-4.1.16
dependabot/npm_and_yarn/tailwindcss/vite-4.1.16
dependabot/npm_and_yarn/typescript-eslint-8.46.2
dependabot/npm_and_yarn/typescript-eslint/parser-8.46.2
development
main
| 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() |