@@ -13,7 +13,7 @@ const DATA_DIR = path.join(os.homedir(), '.blockmine');
1313const PLUGINS_BASE_DIR = path . join ( DATA_DIR , 'storage' , 'plugins' ) ;
1414
1515const TELEMETRY_ENABLED = true ;
16- const STATS_SERVER_URL = 'http://185.65.200.184:3000' ;
16+ const STATS_SERVER_URL = process . env . STATS_SERVER_URL || 'http://185.65.200.184:3000' ;
1717
1818function normalizeGithubRepoUrl ( repoUrl ) {
1919 if ( ! repoUrl || typeof repoUrl !== 'string' ) return null ;
@@ -97,6 +97,83 @@ async function fetchJsonWithTimeout(url, timeoutMs = 7000) {
9797 }
9898}
9999
100+ async function fetchGithubResponseWithTimeout ( url , options = { } , timeoutMs = 10000 ) {
101+ const controller = new AbortController ( ) ;
102+ const timeoutId = setTimeout ( ( ) => controller . abort ( ) , timeoutMs ) ;
103+
104+ try {
105+ const response = await fetch ( url , {
106+ ...options ,
107+ headers : getGithubHeaders ( options . headers || { } ) ,
108+ signal : controller . signal
109+ } ) ;
110+
111+ if ( ! response . ok ) {
112+ const error = new Error ( `GitHub request failed with status ${ response . status } .` ) ;
113+ error . status = response . status ;
114+ throw error ;
115+ }
116+
117+ return response ;
118+ } catch ( error ) {
119+ if ( error . name === 'AbortError' ) {
120+ const timeoutError = new Error ( 'GitHub request timed out.' ) ;
121+ timeoutError . name = 'AbortError' ;
122+ throw timeoutError ;
123+ }
124+
125+ throw error ;
126+ } finally {
127+ clearTimeout ( timeoutId ) ;
128+ }
129+ }
130+
131+ async function fetchGithubRepoInfo ( repoUrl ) {
132+ const ownerRepo = parseGithubOwnerRepo ( repoUrl ) ;
133+ if ( ! ownerRepo ) return null ;
134+
135+ return fetchJsonWithTimeout ( `https://api.github.com/repos/${ ownerRepo . owner } /${ ownerRepo . repo } ` ) ;
136+ }
137+
138+ async function downloadGithubArchive ( repoUrl , ref ) {
139+ const ownerRepo = parseGithubOwnerRepo ( repoUrl ) ;
140+ if ( ! ownerRepo ) {
141+ throw new Error ( 'Invalid GitHub repository URL.' ) ;
142+ }
143+
144+ const response = await fetchGithubResponseWithTimeout (
145+ `https://api.github.com/repos/${ ownerRepo . owner } /${ ownerRepo . repo } /zipball/${ encodeURIComponent ( ref ) } ` ,
146+ { headers : { Accept : 'application/vnd.github+json' } } ,
147+ 15000
148+ ) ;
149+
150+ return response ;
151+ }
152+
153+ async function fetchGithubPackageVersion ( repoUrl , ref = null ) {
154+ const ownerRepo = parseGithubOwnerRepo ( repoUrl ) ;
155+ if ( ! ownerRepo ) return null ;
156+
157+ const resolvedRef = ref || ( await fetchGithubRepoInfo ( repoUrl ) ) ?. default_branch ;
158+ if ( ! resolvedRef ) return null ;
159+
160+ const packageJsonData = await fetchJsonWithTimeout (
161+ `https://api.github.com/repos/${ ownerRepo . owner } /${ ownerRepo . repo } /contents/package.json?ref=${ encodeURIComponent ( resolvedRef ) } `
162+ ) ;
163+
164+ if ( ! packageJsonData ?. content ) {
165+ return null ;
166+ }
167+
168+ try {
169+ const packageJsonRaw = Buffer . from ( packageJsonData . content , packageJsonData . encoding || 'base64' ) . toString ( 'utf8' ) ;
170+ const packageJson = JSON . parse ( packageJsonRaw ) ;
171+ return typeof packageJson ?. version === 'string' ? packageJson . version : null ;
172+ } catch {
173+ return null ;
174+ }
175+ }
176+
100177async function fetchLatestGithubVersionTag ( repoUrl ) {
101178 const ownerRepo = parseGithubOwnerRepo ( repoUrl ) ;
102179 if ( ! ownerRepo ) return null ;
@@ -307,33 +384,30 @@ class PluginManager {
307384 try {
308385 const url = new URL ( repoUrl ) ;
309386 const repoPath = url . pathname . replace ( / ^ \/ | \. g i t $ / g, '' ) ;
387+ const repoInfo = tag ? null : await fetchGithubRepoInfo ( repoUrl ) ;
388+ if ( repoInfo ?. default_branch ) {
389+ sourceRef = repoInfo . default_branch ;
390+ }
310391
311392 let response ;
312393
313- // Если указан тег - скачиваем конкретный релиз
314394 if ( tag ) {
315- const archiveUrlTag = `https://github.com/${ repoPath } /archive/refs/tags/${ encodeURIComponent ( tag ) } .zip` ;
316395 console . log ( `[PluginManager] Скачиваем релиз ${ tag } из ${ repoUrl } ...` ) ;
317396 try {
318- response = await fetch ( archiveUrlTag ) ;
397+ response = await downloadGithubArchive ( repoUrl , tag ) ;
319398 } catch ( err ) {
320399 throw new Error ( `Ошибка сети при скачивании релиза ${ tag } : ${ err . message || err } ` ) ;
321400 }
322- if ( ! response . ok ) {
323- throw new Error ( `Не удалось скачать релиз ${ tag } . Статус: ${ response . status } . Возможно, тег не существует.` ) ;
324- }
325401 } else {
326- // Если тег не указан - скачиваем последний коммит из main/master (старое поведение)
327- const archiveUrlMain = `https://github.com/${ repoPath } /archive/refs/heads/main.zip` ;
328- const archiveUrlMaster = `https://github.com/${ repoPath } /archive/refs/heads/master.zip` ;
329-
330- response = await fetch ( archiveUrlMain ) ;
331- if ( ! response . ok ) {
332- console . log ( `[PluginManager] Ветка 'main' не найдена для ${ repoUrl } , пробую 'master'...` ) ;
333- sourceRef = 'master' ;
334- response = await fetch ( archiveUrlMaster ) ;
335- if ( ! response . ok ) {
336- throw new Error ( `Не удалось скачать архив плагина. Статус: ${ response . status } ` ) ;
402+ try {
403+ response = await downloadGithubArchive ( repoUrl , sourceRef ) ;
404+ } catch ( err ) {
405+ if ( ! repoInfo && sourceRef !== 'master' ) {
406+ console . log ( `[PluginManager] Ветка '${ sourceRef } ' не найдена для ${ repoUrl } , пробую 'master'...` ) ;
407+ sourceRef = 'master' ;
408+ response = await downloadGithubArchive ( repoUrl , sourceRef ) ;
409+ } else {
410+ throw new Error ( `Не удалось скачать архив плагина: ${ err . message || err } ` ) ;
337411 }
338412 }
339413 }
@@ -540,30 +614,37 @@ class PluginManager {
540614 catalogMapByName . get ( String ( plugin . name ) . toLowerCase ( ) ) ||
541615 ( repoName ? catalogMapByRepoName . get ( repoName ) : null ) ;
542616
617+ const targetRepoUrl = catalogInfo ?. repoUrl || normalizedSourceUri || normalizeGithubRepoUrl ( plugin . sourceUri ) || plugin . sourceUri ;
543618 let latestTagRaw =
544619 catalogInfo ?. latestTag ||
545620 catalogInfo ?. recommendedVersion ||
546621 catalogInfo ?. version ||
547622 catalogInfo ?. latestVersion ||
548623 catalogInfo ?. tag ;
549624
550- if ( ! catalogInfo ) continue ;
551-
552625 if ( ! latestTagRaw ) {
553- const cacheKey = normalizedSourceUri || plugin . sourceUri || plugin . name ;
626+ const cacheKey = targetRepoUrl || plugin . name ;
554627 if ( latestTagCache . has ( cacheKey ) ) {
555628 latestTagRaw = latestTagCache . get ( cacheKey ) ;
556629 } else {
557- const fetchedTag = catalogInfo . repoUrl ? await fetchLatestGithubVersionTag ( catalogInfo . repoUrl ) : null ;
630+ const fetchedTag = targetRepoUrl ? await fetchLatestGithubVersionTag ( targetRepoUrl ) : null ;
558631 latestTagCache . set ( cacheKey , fetchedTag ) ;
559632 latestTagRaw = fetchedTag ;
560633 }
561634 }
562635
563- if ( ! latestTagRaw ) continue ;
636+ let latestVersionRaw = latestTagRaw ;
637+ if ( ! latestVersionRaw && targetRepoUrl ) {
638+ latestVersionRaw = await fetchGithubPackageVersion (
639+ targetRepoUrl ,
640+ plugin . sourceRefType === 'branch' ? plugin . sourceRef : null
641+ ) ;
642+ }
643+
644+ if ( ! latestVersionRaw ) continue ;
564645
565646 const localSemver = semver . coerce ( plugin . version ) ;
566- const remoteSemver = semver . coerce ( latestTagRaw ) ;
647+ const remoteSemver = semver . coerce ( latestVersionRaw ) ;
567648 if ( ! localSemver || ! remoteSemver ) continue ;
568649
569650 if ( semver . gt ( remoteSemver . version , localSemver . version ) ) {
@@ -573,8 +654,8 @@ class PluginManager {
573654 sourceUri : plugin . sourceUri ,
574655 currentVersion : localSemver . version ,
575656 recommendedVersion : remoteSemver . version ,
576- latestTag : catalogInfo ?. latestTag || latestTagRaw , // 🏷️ Добавляем тег для использования при обновлении
577- targetRepoUrl : catalogInfo ?. repoUrl || plugin . sourceUri ,
657+ latestTag : catalogInfo ?. latestTag || latestTagRaw || null ,
658+ targetRepoUrl,
578659 } ) ;
579660 }
580661 } catch ( error ) {
0 commit comments