@@ -147,11 +147,24 @@ namespace pxt.github {
147147 files : Map < string > ;
148148 }
149149
150+ function copyCachedPackage ( cachedPackage : CachedPackage ) : CachedPackage {
151+ return {
152+ files : { ...cachedPackage . files }
153+ } ;
154+ }
155+
156+ function cachedPackageFromFallback ( fallbackPackageFiles ?: pxt . Map < string > ) : CachedPackage {
157+ if ( ! fallbackPackageFiles ) return undefined ;
158+ return {
159+ files : { ...fallbackPackageFiles }
160+ } ;
161+ }
162+
150163 // caching
151164 export interface IGithubDb {
152165 latestVersionAsync ( repopath : string , config : PackagesConfig ) : Promise < string > ;
153166 loadConfigAsync ( repopath : string , tag : string ) : Promise < pxt . PackageConfig > ;
154- loadPackageAsync ( repopath : string , tag : string ) : Promise < CachedPackage > ;
167+ loadPackageAsync ( repopath : string , tag : string , fallbackPackageFiles ?: pxt . Map < string > ) : Promise < CachedPackage > ;
155168 loadTutorialMarkdown ( repopath : string , tag ?: string ) : Promise < CachedPackage > ;
156169 cacheReposAsync ( response : GHTutorialResponse ) : Promise < void > ;
157170 }
@@ -273,7 +286,7 @@ namespace pxt.github {
273286 return resolved
274287 }
275288
276- async loadPackageAsync ( repopath : string , tag : string ) : Promise < CachedPackage > {
289+ async loadPackageAsync ( repopath : string , tag : string , fallbackPackageFiles ?: pxt . Map < string > ) : Promise < CachedPackage > {
277290 if ( ! tag ) {
278291 pxt . debug ( `load pkg: default to master branch` )
279292 tag = "master" ;
@@ -282,48 +295,49 @@ namespace pxt.github {
282295 // try using github proxy first
283296 if ( hasProxy ( ) ) {
284297 try {
285- return await this . proxyWithCdnLoadPackageAsync ( repopath , tag ) . then ( v => U . clone ( v ) ) ;
298+ return await this . proxyWithCdnLoadPackageAsync ( repopath , tag ) . then ( copyCachedPackage ) ;
286299 } catch ( e ) {
287300 ghProxyHandleException ( e ) ;
288301 }
289302 }
290303
291304 // try using github apis
292- return await this . githubLoadPackageAsync ( repopath , tag ) ;
305+ return await this . githubLoadPackageAsync ( repopath , tag , fallbackPackageFiles ) ;
293306 }
294307
295- private githubLoadPackageAsync ( repopath : string , tag : string ) : Promise < CachedPackage > {
296- return tagToShaAsync ( repopath , tag )
297- . then ( sha => {
298- // cache lookup
299- const key = `${ repopath } /${ sha } ` ;
300- let res = this . packages [ key ] ;
301- if ( res ) {
302- pxt . debug ( `github cache ${ repopath } /${ tag } /text` ) ;
303- return Promise . resolve ( U . clone ( res ) ) ;
304- }
308+ private async githubLoadPackageAsync ( repopath : string , tag : string , fallbackPackageFiles ?: pxt . Map < string > ) : Promise < CachedPackage > {
309+ try {
310+ const sha = await tagToShaAsync ( repopath , tag ) ;
311+
312+ // cache lookup
313+ const key = `${ repopath } /${ sha } ` ;
314+ let res = this . packages [ key ] ;
315+ if ( res ) {
316+ pxt . debug ( `github cache ${ repopath } /${ tag } /text` ) ;
317+ return copyCachedPackage ( res ) ;
318+ }
305319
306- // load and cache
307- pxt . log ( `Downloading ${ repopath } /${ tag } -> ${ sha } ` )
308- return downloadTextAsync ( repopath , sha , pxt . CONFIG_NAME )
309- . then ( pkg => {
310- const current : CachedPackage = {
311- files : { }
312- }
313- current . files [ pxt . CONFIG_NAME ] = pkg
314- const cfg : pxt . PackageConfig = JSON . parse ( pkg )
315- return U . promiseMapAll ( pxt . allPkgFiles ( cfg ) . slice ( 1 ) ,
316- fn => downloadTextAsync ( repopath , sha , fn )
317- . then ( text => {
318- current . files [ fn ] = text
319- } ) )
320- . then ( ( ) => {
321- // cache!
322- this . packages [ key ] = current ;
323- return U . clone ( current ) ;
324- } )
325- } )
326- } )
320+ // load and cache
321+ pxt . log ( `Downloading ${ repopath } /${ tag } -> ${ sha } ` )
322+ const pkg = await downloadTextAsync ( repopath , sha , pxt . CONFIG_NAME ) ;
323+ const current : CachedPackage = {
324+ files : { }
325+ }
326+ current . files [ pxt . CONFIG_NAME ] = pkg
327+ const cfg : pxt . PackageConfig = JSON . parse ( pkg )
328+ await U . promiseMapAll ( pxt . allPkgFiles ( cfg ) . slice ( 1 ) , async fn => {
329+ current . files [ fn ] = await downloadTextAsync ( repopath , sha , fn ) ;
330+ } ) ;
331+
332+ // cache!
333+ this . packages [ key ] = current ;
334+ return copyCachedPackage ( current ) ;
335+ }
336+ catch ( e ) {
337+ const fallbackPackage = cachedPackageFromFallback ( fallbackPackageFiles ) ;
338+ if ( fallbackPackage ) return fallbackPackage ;
339+ throw e ;
340+ }
327341 }
328342
329343 async loadTutorialMarkdown ( repopath : string , tag ?: string ) {
@@ -731,7 +745,7 @@ namespace pxt.github {
731745 return await db . loadConfigAsync ( repopath , tag )
732746 }
733747
734- export async function downloadPackageAsync ( repoWithTag : string , config : pxt . PackagesConfig ) : Promise < CachedPackage > {
748+ export async function downloadPackageAsync ( repoWithTag : string , config : pxt . PackagesConfig , fallbackPackageFiles ?: pxt . Map < string > ) : Promise < CachedPackage > {
735749 const p = parseRepoId ( repoWithTag )
736750 if ( ! p ) {
737751 pxt . log ( 'Unknown GitHub syntax' ) ;
@@ -746,9 +760,16 @@ namespace pxt.github {
746760
747761 // always try to upgrade unbound versions
748762 if ( ! p . tag ) {
749- p . tag = await db . latestVersionAsync ( p . slug , config )
763+ try {
764+ p . tag = await db . latestVersionAsync ( p . slug , config )
765+ }
766+ catch ( e ) {
767+ const fallbackPackage = cachedPackageFromFallback ( fallbackPackageFiles ) ;
768+ if ( fallbackPackage ) return fallbackPackage ;
769+ throw e ;
770+ }
750771 }
751- const cached = await db . loadPackageAsync ( p . fullName , p . tag )
772+ const cached = await db . loadPackageAsync ( p . fullName , p . tag , fallbackPackageFiles )
752773 const dv = upgradedDisablesVariants ( config , repoWithTag )
753774 if ( dv ) {
754775 const cfg = Package . parseAndValidConfig ( cached . files [ pxt . CONFIG_NAME ] )
@@ -775,7 +796,11 @@ namespace pxt.github {
775796 return { version, config } ;
776797 }
777798
778- export async function cacheProjectDependenciesAsync ( cfg : pxt . PackageConfig ) : Promise < void > {
799+ export async function cacheProjectDependenciesAsync ( cfg : pxt . PackageConfig , fallbackPackageFilesById ?: pxt . Map < pxt . Map < string > > ) : Promise < void > {
800+ await cacheProjectDependenciesCoreAsync ( cfg , { } , fallbackPackageFilesById ) ;
801+ }
802+
803+ async function cacheProjectDependenciesCoreAsync ( cfg : pxt . PackageConfig , checked : pxt . Map < boolean > , fallbackPackageFilesById ?: pxt . Map < pxt . Map < string > > ) : Promise < void > {
779804 const ghExtensions = Object . keys ( cfg . dependencies )
780805 ?. filter ( dep => isGithubId ( cfg . dependencies [ dep ] ) ) ;
781806
@@ -786,10 +811,15 @@ namespace pxt.github {
786811 ghExtensions . map (
787812 async ext => {
788813 const extSrc = cfg . dependencies [ ext ] ;
789- const ghPkg = await downloadPackageAsync ( extSrc , pkgConfig ) ;
814+ if ( checked [ extSrc ] ) return ;
815+ checked [ extSrc ] = true ;
816+
817+ const ghPkg = await downloadPackageAsync ( extSrc , pkgConfig , fallbackPackageFilesById ?. [ extSrc ] ) ;
790818 if ( ! ghPkg ) {
791819 throw new Error ( lf ( "Cannot load extension {0} from {1}" , ext , extSrc ) ) ;
792820 }
821+ const ghPkgCfg = Package . parseAndValidConfig ( ghPkg . files [ pxt . CONFIG_NAME ] ) ;
822+ if ( ghPkgCfg ) await cacheProjectDependenciesCoreAsync ( ghPkgCfg , checked , fallbackPackageFilesById ) ;
793823 }
794824 )
795825 ) ;
0 commit comments