mid coding commit
This commit is contained in:
478
neode-ui/src/utils/dummyApps.ts
Normal file
478
neode-ui/src/utils/dummyApps.ts
Normal file
@@ -0,0 +1,478 @@
|
||||
// Dummy apps data for first launch
|
||||
// This can be easily replaced with real package data later
|
||||
// Similar to how atob and k484 are handled
|
||||
|
||||
import type { PackageDataEntry } from '../types/api'
|
||||
import { PackageState, ServiceStatus } from '../types/api'
|
||||
|
||||
export const dummyApps: Record<string, PackageDataEntry> = {
|
||||
'bitcoin': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'Bitcoin Core node for the Neode network',
|
||||
icon: '/assets/img/app-icons/bitcoin.svg'
|
||||
},
|
||||
manifest: {
|
||||
id: 'bitcoin',
|
||||
title: 'Bitcoin Core',
|
||||
version: '24.0.0',
|
||||
description: {
|
||||
short: 'Full Bitcoin node implementation',
|
||||
long: 'Bitcoin Core is the reference implementation of Bitcoin. It provides a full node implementation that validates and relays transactions, maintains the blockchain, and serves as a wallet.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': 'https://github.com/Start9Labs/bitcoind-startos',
|
||||
'upstream-repo': 'https://github.com/bitcoin/bitcoin',
|
||||
'support-site': 'https://github.com/bitcoin/bitcoin/issues',
|
||||
'marketing-site': 'https://bitcoin.org',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'bitcoin.onion',
|
||||
'lan-address': 'http://localhost:8332'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'btcpay-server': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'BTCPay Server payment processor',
|
||||
icon: '/assets/img/app-icons/btcpay-server.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 'btcpay-server',
|
||||
title: 'BTCPay Server',
|
||||
version: '1.12.0',
|
||||
description: {
|
||||
short: 'Self-hosted Bitcoin payment processor',
|
||||
long: 'BTCPay Server is a free, open-source cryptocurrency payment processor. Accept Bitcoin payments without intermediaries or fees. Complete merchant solution with invoicing, point of sale, and more.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': 'https://github.com/Start9Labs/btcpayserver-startos',
|
||||
'upstream-repo': 'https://github.com/btcpayserver/btcpayserver',
|
||||
'support-site': 'https://github.com/btcpayserver/btcpayserver/issues',
|
||||
'marketing-site': 'https://btcpayserver.org',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'btcpay.onion',
|
||||
'lan-address': 'http://localhost:14142'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'homeassistant': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'Apache-2.0',
|
||||
instructions: 'Home automation platform',
|
||||
icon: '/assets/img/app-icons/homeassistant.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 'homeassistant',
|
||||
title: 'Home Assistant',
|
||||
version: '2024.1.0',
|
||||
description: {
|
||||
short: 'Open source home automation platform',
|
||||
long: 'Home Assistant is an open-source home automation platform running on Python. It tracks and controls all devices at home and offers a platform for automating control.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'Apache-2.0',
|
||||
'wrapper-repo': 'https://github.com/Start9Labs/home-assistant-startos',
|
||||
'upstream-repo': 'https://github.com/home-assistant/core',
|
||||
'support-site': 'https://github.com/home-assistant/core/issues',
|
||||
'marketing-site': 'https://www.home-assistant.io',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'homeassistant.onion',
|
||||
'lan-address': 'http://localhost:8123'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'grafana': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'Apache-2.0',
|
||||
instructions: 'Analytics and monitoring platform',
|
||||
icon: '/assets/img/grafana.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 'grafana',
|
||||
title: 'Grafana',
|
||||
version: '10.2.0',
|
||||
description: {
|
||||
short: 'Analytics and monitoring platform',
|
||||
long: 'Grafana is an open-source analytics and monitoring platform. Create dashboards, query metrics, and visualize data from multiple sources.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'Apache-2.0',
|
||||
'wrapper-repo': 'https://github.com/grafana/grafana',
|
||||
'upstream-repo': 'https://github.com/grafana/grafana',
|
||||
'support-site': 'https://github.com/grafana/grafana/issues',
|
||||
'marketing-site': 'https://grafana.com',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'grafana.onion',
|
||||
'lan-address': 'http://localhost:3000'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'endurain': {
|
||||
state: PackageState.Stopped,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'Endurain application',
|
||||
icon: '/assets/img/endurain.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 'endurain',
|
||||
title: 'Endurain',
|
||||
version: '1.0.0',
|
||||
description: {
|
||||
short: 'Endurain application platform',
|
||||
long: 'Endurain provides a platform for decentralized applications and services.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': 'https://github.com/endurain/endurain',
|
||||
'upstream-repo': 'https://github.com/endurain/endurain',
|
||||
'support-site': 'https://github.com/endurain/endurain/issues',
|
||||
'marketing-site': 'https://endurain.io',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'endurain.onion',
|
||||
'lan-address': 'http://localhost:8080'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Stopped
|
||||
}
|
||||
},
|
||||
'fedimint': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'Federated Bitcoin mint',
|
||||
icon: '/assets/img/icon-fedimint.jpeg'
|
||||
},
|
||||
manifest: {
|
||||
id: 'fedimint',
|
||||
title: 'Fedimint',
|
||||
version: '0.3.0',
|
||||
description: {
|
||||
short: 'Federated Bitcoin minting service',
|
||||
long: 'Fedimint is a federated Bitcoin mint that enables private, scalable Bitcoin transactions through a federation of guardians.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': 'https://github.com/fedimint/fedimint',
|
||||
'upstream-repo': 'https://github.com/fedimint/fedimint',
|
||||
'support-site': 'https://github.com/fedimint/fedimint/issues',
|
||||
'marketing-site': 'https://fedimint.org',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'fedimint.onion',
|
||||
'lan-address': 'http://localhost:8173'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'morphos-server': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'MorphOS server application',
|
||||
icon: '/assets/img/morphos.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 'morphos-server',
|
||||
title: 'MorphOS Server',
|
||||
version: '1.0.0',
|
||||
description: {
|
||||
short: 'MorphOS server platform',
|
||||
long: 'MorphOS Server provides a flexible server platform for various applications and services.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': 'https://github.com/morphos/morphos',
|
||||
'upstream-repo': 'https://github.com/morphos/morphos',
|
||||
'support-site': 'https://github.com/morphos/morphos/issues',
|
||||
'marketing-site': 'https://morphos.io',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'morphos.onion',
|
||||
'lan-address': 'http://localhost:8081'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'lightning-stack': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'Lightning Network stack',
|
||||
icon: '/assets/img/app-icons/lightning-stack.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 'lightning-stack',
|
||||
title: 'Lightning Stack',
|
||||
version: '0.12.0',
|
||||
description: {
|
||||
short: 'Complete Lightning Network implementation',
|
||||
long: 'Lightning Stack provides a complete Lightning Network node implementation with LND, CLN, and supporting services for fast Bitcoin transactions.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': 'https://github.com/Start9Labs/lnd-startos',
|
||||
'upstream-repo': 'https://github.com/lightningnetwork/lnd',
|
||||
'support-site': 'https://github.com/lightningnetwork/lnd/issues',
|
||||
'marketing-site': 'https://lightning.network',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'lightning.onion',
|
||||
'lan-address': 'http://localhost:9735'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'mempool': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'AGPL-3.0',
|
||||
instructions: 'Bitcoin mempool explorer',
|
||||
icon: '/assets/img/app-icons/mempool.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 'mempool',
|
||||
title: 'Mempool',
|
||||
version: '2.5.0',
|
||||
description: {
|
||||
short: 'Bitcoin mempool and blockchain explorer',
|
||||
long: 'Mempool is an open-source Bitcoin mempool and blockchain explorer. View transactions, blocks, and network statistics in real-time.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'AGPL-3.0',
|
||||
'wrapper-repo': 'https://github.com/Start9Labs/mempool-startos',
|
||||
'upstream-repo': 'https://github.com/mempool/mempool',
|
||||
'support-site': 'https://github.com/mempool/mempool/issues',
|
||||
'marketing-site': 'https://mempool.space',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'mempool.onion',
|
||||
'lan-address': 'http://localhost:4080'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'ollama': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MIT',
|
||||
instructions: 'Local AI model runner',
|
||||
icon: '/assets/img/ollama.webp'
|
||||
},
|
||||
manifest: {
|
||||
id: 'ollama',
|
||||
title: 'Ollama',
|
||||
version: '0.1.0',
|
||||
description: {
|
||||
short: 'Run large language models locally',
|
||||
long: 'Ollama allows you to run large language models locally on your Neode server. Download and run models like Llama, Mistral, and more without cloud dependencies.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MIT',
|
||||
'wrapper-repo': 'https://github.com/ollama/ollama',
|
||||
'upstream-repo': 'https://github.com/ollama/ollama',
|
||||
'support-site': 'https://github.com/ollama/ollama/issues',
|
||||
'marketing-site': 'https://ollama.ai',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'ollama.onion',
|
||||
'lan-address': 'http://localhost:11434'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'searxng': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'AGPL-3.0',
|
||||
instructions: 'Privacy-respecting search engine',
|
||||
icon: '/assets/img/app-icons/searxng.png'
|
||||
},
|
||||
manifest: {
|
||||
id: 'searxng',
|
||||
title: 'SearXNG',
|
||||
version: '2024.1.0',
|
||||
description: {
|
||||
short: 'Privacy-respecting metasearch engine',
|
||||
long: 'SearXNG is a privacy-respecting, hackable metasearch engine. Aggregate results from multiple search engines without tracking or ads.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'AGPL-3.0',
|
||||
'wrapper-repo': 'https://github.com/searxng/searxng',
|
||||
'upstream-repo': 'https://github.com/searxng/searxng',
|
||||
'support-site': 'https://github.com/searxng/searxng/issues',
|
||||
'marketing-site': 'https://searxng.org',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'searxng.onion',
|
||||
'lan-address': 'http://localhost:8082'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'onlyoffice': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'AGPL-3.0',
|
||||
instructions: 'Office suite and document collaboration',
|
||||
icon: '/assets/img/onlyoffice.webp'
|
||||
},
|
||||
manifest: {
|
||||
id: 'onlyoffice',
|
||||
title: 'OnlyOffice',
|
||||
version: '7.5.0',
|
||||
description: {
|
||||
short: 'Office suite and document collaboration',
|
||||
long: 'OnlyOffice is a secure office suite that enables real-time collaborative editing of documents, spreadsheets, and presentations. Self-hosted alternative to Google Workspace and Microsoft 365.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'AGPL-3.0',
|
||||
'wrapper-repo': 'https://github.com/ONLYOFFICE/DocumentServer',
|
||||
'upstream-repo': 'https://github.com/ONLYOFFICE/DocumentServer',
|
||||
'support-site': 'https://github.com/ONLYOFFICE/DocumentServer/issues',
|
||||
'marketing-site': 'https://www.onlyoffice.com',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'onlyoffice.onion',
|
||||
'lan-address': 'http://localhost:8083'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
},
|
||||
'penpot': {
|
||||
state: PackageState.Running,
|
||||
'static-files': {
|
||||
license: 'MPL-2.0',
|
||||
instructions: 'Design and prototyping platform',
|
||||
icon: '/assets/img/penpot.webp'
|
||||
},
|
||||
manifest: {
|
||||
id: 'penpot',
|
||||
title: 'Penpot',
|
||||
version: '2.0.0',
|
||||
description: {
|
||||
short: 'Open-source design and prototyping platform',
|
||||
long: 'Penpot is an open-source design and prototyping platform for teams. Create designs, prototypes, and collaborate in real-time. Self-hosted alternative to Figma.'
|
||||
},
|
||||
'release-notes': 'Initial release',
|
||||
license: 'MPL-2.0',
|
||||
'wrapper-repo': 'https://github.com/penpot/penpot',
|
||||
'upstream-repo': 'https://github.com/penpot/penpot',
|
||||
'support-site': 'https://github.com/penpot/penpot/issues',
|
||||
'marketing-site': 'https://penpot.app',
|
||||
'donation-url': null
|
||||
},
|
||||
installed: {
|
||||
'current-dependents': {},
|
||||
'current-dependencies': {},
|
||||
'last-backup': null,
|
||||
'interface-addresses': {
|
||||
main: {
|
||||
'tor-address': 'penpot.onion',
|
||||
'lan-address': 'http://localhost:9001'
|
||||
}
|
||||
},
|
||||
status: ServiceStatus.Running
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
182
neode-ui/src/utils/githubAppInfo.ts
Normal file
182
neode-ui/src/utils/githubAppInfo.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
// Utility to fetch app information from GitHub repositories
|
||||
// Used to get icons, descriptions, and other metadata for dummy apps
|
||||
|
||||
export interface GitHubAppInfo {
|
||||
icon?: string
|
||||
description?: string
|
||||
readme?: string
|
||||
homepage?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch app information from GitHub repository
|
||||
* @param repoUrl GitHub repository URL (e.g., https://github.com/start9labs/bitcoin)
|
||||
* @param appId App ID to help find the correct repository
|
||||
*/
|
||||
export async function fetchGitHubAppInfo(repoUrl: string, appId: string): Promise<GitHubAppInfo> {
|
||||
try {
|
||||
// Extract owner and repo from URL
|
||||
const match = repoUrl.match(/github\.com\/([^\/]+)\/([^\/]+)/)
|
||||
if (!match) {
|
||||
console.warn(`[GitHub] Invalid repo URL: ${repoUrl}`)
|
||||
return {}
|
||||
}
|
||||
|
||||
const [, owner, repo] = match
|
||||
|
||||
// Try to find Start9 wrapper repo first (e.g., bitcoin-startos)
|
||||
const start9RepoName = `${appId}-startos`
|
||||
let targetOwner = owner
|
||||
let targetRepo = repo
|
||||
|
||||
// If the repo URL doesn't match the expected pattern, try Start9Labs
|
||||
if (repo && !repo.includes('startos') && !repo.includes('start9')) {
|
||||
// Try Start9Labs wrapper repo
|
||||
try {
|
||||
const start9RepoUrl = `https://api.github.com/repos/Start9Labs/${start9RepoName}`
|
||||
const start9Response = await fetch(start9RepoUrl)
|
||||
if (start9Response.ok) {
|
||||
targetOwner = 'Start9Labs'
|
||||
targetRepo = start9RepoName
|
||||
}
|
||||
} catch (e) {
|
||||
// Fall back to original repo
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch repository info
|
||||
const repoApiUrl = `https://api.github.com/repos/${targetOwner}/${targetRepo}`
|
||||
const repoResponse = await fetch(repoApiUrl)
|
||||
|
||||
if (!repoResponse.ok) {
|
||||
console.warn(`[GitHub] Failed to fetch repo ${targetOwner}/${targetRepo}: ${repoResponse.status}`)
|
||||
return {}
|
||||
}
|
||||
|
||||
const repoData = await repoResponse.json()
|
||||
|
||||
// Fetch README
|
||||
let readme = ''
|
||||
try {
|
||||
const readmeResponse = await fetch(`https://api.github.com/repos/${targetOwner}/${targetRepo}/readme`)
|
||||
if (readmeResponse.ok) {
|
||||
const readmeData = await readmeResponse.json()
|
||||
readme = atob(readmeData.content) // Base64 decode
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[GitHub] Failed to fetch README for ${targetOwner}/${targetRepo}`)
|
||||
}
|
||||
|
||||
// Try to find icon in repository
|
||||
// Common locations: icon.png, icon.svg, assets/icon.png, etc.
|
||||
let icon: string | undefined
|
||||
const iconPaths = [
|
||||
'icon.png',
|
||||
'icon.svg',
|
||||
'assets/icon.png',
|
||||
'assets/icon.svg',
|
||||
'icon/icon.png',
|
||||
'icon/icon.svg'
|
||||
]
|
||||
|
||||
for (const iconPath of iconPaths) {
|
||||
try {
|
||||
const iconResponse = await fetch(`https://api.github.com/repos/${targetOwner}/${targetRepo}/contents/${iconPath}`)
|
||||
if (iconResponse.ok) {
|
||||
const iconData = await iconResponse.json()
|
||||
if (iconData.download_url) {
|
||||
icon = iconData.download_url
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Try next path
|
||||
}
|
||||
}
|
||||
|
||||
// If no icon found, try to get from releases/assets
|
||||
if (!icon) {
|
||||
try {
|
||||
const releasesResponse = await fetch(`https://api.github.com/repos/${targetOwner}/${targetRepo}/releases/latest`)
|
||||
if (releasesResponse.ok) {
|
||||
const releasesData = await releasesResponse.json()
|
||||
const asset = releasesData.assets?.find((a: any) =>
|
||||
a.name.includes('icon') || a.name.includes('logo')
|
||||
)
|
||||
if (asset) {
|
||||
icon = asset.browser_download_url
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// No icon from releases
|
||||
}
|
||||
}
|
||||
|
||||
// If still no icon, try raw GitHub content URLs for common icon names
|
||||
if (!icon) {
|
||||
const rawIconPaths = [
|
||||
`https://raw.githubusercontent.com/${targetOwner}/${targetRepo}/main/icon.png`,
|
||||
`https://raw.githubusercontent.com/${targetOwner}/${targetRepo}/main/icon.svg`,
|
||||
`https://raw.githubusercontent.com/${targetOwner}/${targetRepo}/master/icon.png`,
|
||||
`https://raw.githubusercontent.com/${targetOwner}/${targetRepo}/master/icon.svg`,
|
||||
`https://raw.githubusercontent.com/${targetOwner}/${targetRepo}/main/assets/icon.png`,
|
||||
`https://raw.githubusercontent.com/${targetOwner}/${targetRepo}/main/assets/icon.svg`,
|
||||
]
|
||||
|
||||
// Test each URL
|
||||
for (const iconUrl of rawIconPaths) {
|
||||
try {
|
||||
const testResponse = await fetch(iconUrl, { method: 'HEAD' })
|
||||
if (testResponse.ok) {
|
||||
icon = iconUrl
|
||||
break
|
||||
}
|
||||
} catch (e) {
|
||||
// Try next URL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
icon,
|
||||
description: repoData.description || '',
|
||||
readme,
|
||||
homepage: repoData.homepage || repoData.html_url
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[GitHub] Error fetching app info for ${repoUrl}:`, error)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch fetch app info for multiple apps
|
||||
*/
|
||||
export async function fetchMultipleAppInfo(
|
||||
apps: Array<{ id: string; 'wrapper-repo': string }>
|
||||
): Promise<Record<string, GitHubAppInfo>> {
|
||||
const results: Record<string, GitHubAppInfo> = {}
|
||||
|
||||
// Fetch in parallel with rate limiting (max 5 concurrent)
|
||||
const batchSize = 5
|
||||
for (let i = 0; i < apps.length; i += batchSize) {
|
||||
const batch = apps.slice(i, i + batchSize)
|
||||
const batchPromises = batch.map(async (app) => {
|
||||
const info = await fetchGitHubAppInfo(app['wrapper-repo'], app.id)
|
||||
return { id: app.id, info }
|
||||
})
|
||||
|
||||
const batchResults = await Promise.all(batchPromises)
|
||||
batchResults.forEach(({ id, info }) => {
|
||||
results[id] = info
|
||||
})
|
||||
|
||||
// Rate limit: wait 1 second between batches
|
||||
if (i + batchSize < apps.length) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user