2019-05-07 19:37:37 +00:00
import { getFolderForPath , isProbablyBinary , stringToByteArray } from "./util" ;
import { FileData } from "./workertypes" ;
import { CodeProject } from "./project" ;
// in index.html
declare var exports ;
declare var firebase ;
2019-05-08 13:39:57 +00:00
export interface GHSession {
2019-05-08 23:15:26 +00:00
url : string ; // github url
user : string ; // user name
reponame : string ; // repo name
repopath : string ; // "user/repo"
prefix : string ; // file prefix, "local/" or ""
repo : any ; // [repo object]
mainPath? : string ; // main file path
2019-05-07 19:37:37 +00:00
paths? : string [ ] ;
}
2019-05-08 13:39:57 +00:00
const README_md_template = "$NAME\n=====\n\nCompatible with the [$PLATFORM](http://8bitworkshop.com/redir.html?platform=$PLATFORM&importURL=$GITHUBURL) platform in [8bitworkshop](http://8bitworkshop.com/). Main file is [$MAINFILE]($MAINFILE#mainfile).\n" ;
2019-05-07 19:37:37 +00:00
export class GithubService {
2019-05-08 17:42:24 +00:00
githubCons ;
githubToken ;
2019-05-07 19:37:37 +00:00
github ;
store ;
project : CodeProject ;
branch : string = "master" ;
2019-05-08 17:42:24 +00:00
constructor ( githubCons : ( ) = > any , githubToken :string , store , project : CodeProject ) {
this . githubCons = githubCons ;
this . githubToken = githubToken ;
2019-05-07 19:37:37 +00:00
this . store = store ;
this . project = project ;
2019-05-08 17:42:24 +00:00
this . recreateGithub ( ) ;
}
recreateGithub() {
this . github = new this . githubCons ( { token :this.githubToken } ) ;
}
login ( ) : Promise < void > {
// already logged in? return immediately
if ( this . githubToken && this . githubToken . length ) {
return new Promise < void > ( ( yes , no ) = > {
yes ( ) ;
} ) ;
}
// login via popup
var provider = new firebase . auth . GithubAuthProvider ( ) ;
provider . addScope ( 'repo' ) ;
return firebase . auth ( ) . signInWithPopup ( provider ) . then ( ( result ) = > {
this . githubToken = result . credential . accessToken ;
var user = result . user ;
this . recreateGithub ( ) ;
document . cookie = "__github_key=" + this . githubToken + ";path=/;max-age=31536000" ;
console . log ( "Stored GitHub OAUTH key" ) ;
} ) . catch ( ( error ) = > {
console . log ( error ) ;
alert ( "Could not login to GitHub: " + error ) ;
} ) ;
2019-05-07 19:37:37 +00:00
}
isFileIgnored ( s : string ) : boolean {
s = s . toUpperCase ( ) ;
if ( s . startsWith ( "LICENSE" ) ) return true ;
if ( s . startsWith ( "README" ) ) return true ;
if ( s . startsWith ( "." ) ) return true ;
return false ;
}
parseGithubURL ( ghurl :string ) {
var toks = ghurl . split ( '/' ) ;
if ( toks . length < 5 ) return null ;
if ( toks [ 0 ] != 'https:' ) return null ;
if ( toks [ 2 ] != 'github.com' ) return null ;
2019-05-08 23:15:26 +00:00
return { user :toks [ 3 ] , repo :toks [ 4 ] , repopath :toks [ 3 ] + '/' + toks [ 4 ] } ;
2019-05-07 19:37:37 +00:00
}
2019-05-08 13:39:57 +00:00
getGithubSession ( ghurl :string ) : Promise < GHSession > {
2019-05-07 19:37:37 +00:00
return new Promise ( ( yes , no ) = > {
var urlparse = this . parseGithubURL ( ghurl ) ;
if ( ! urlparse ) {
no ( "Please enter a valid GitHub URL." ) ;
}
var sess = {
url : ghurl ,
user : urlparse.user ,
reponame : urlparse.repo ,
2019-05-08 23:15:26 +00:00
repopath : urlparse.repopath ,
prefix : '' , //this.getPrefix(urlparse.user, urlparse.repo),
2019-05-07 19:37:37 +00:00
repo : this.github.repos ( urlparse . user , urlparse . repo )
} ;
yes ( sess ) ;
} ) ;
}
2019-05-08 23:15:26 +00:00
getRepos() {
return JSON . parse ( localStorage . getItem ( '__repos' ) || '{}' ) ;
2019-05-07 19:37:37 +00:00
}
2019-05-08 23:15:26 +00:00
bind ( sess :GHSession , dobind :boolean ) {
var repos = this . getRepos ( ) ;
if ( dobind ) {
repos [ sess . repopath ] = sess . url ;
} else {
delete repos [ sess . repopath ] ;
}
localStorage . setItem ( '__repos' , JSON . stringify ( repos ) ) ;
2019-05-07 19:37:37 +00:00
}
2019-05-08 13:39:57 +00:00
2019-05-07 19:37:37 +00:00
import ( ghurl :string ) : Promise < GHSession > {
var sess : GHSession ;
2019-05-08 13:39:57 +00:00
return this . getGithubSession ( ghurl ) . then ( ( session ) = > {
sess = session ;
// load README
return sess . repo . contents ( 'README.md' ) . read ( ) ;
} )
. catch ( ( ) = > {
2019-05-08 17:42:24 +00:00
console . log ( 'no README.md found' )
2019-05-08 13:39:57 +00:00
return '' ; // empty README
} )
. then ( ( readme ) = > {
var m ;
// check README for main file
const re8main = /\(([^)]+)#mainfile\)/ ;
m = re8main . exec ( readme ) ;
2019-05-08 17:42:24 +00:00
if ( m && m [ 1 ] ) {
2019-05-08 13:39:57 +00:00
console . log ( "main path: '" + m [ 1 ] + "'" ) ;
sess . mainPath = m [ 1 ] ;
}
// check README for proper platform
const re8plat = /8bitworkshop.com[^)]+platform=(\w+)/ ;
m = re8plat . exec ( readme ) ;
if ( m && ! this . project . platform_id . startsWith ( m [ 1 ] ) ) {
throw "Platform mismatch: Repository is " + m [ 1 ] + ", you have " + this . project . platform_id + " selected." ;
}
// get head commit
2019-05-08 17:42:24 +00:00
return sess ;
2019-05-08 13:39:57 +00:00
} ) ;
}
2019-05-08 23:15:26 +00:00
pull ( ghurl :string , deststore ? ) : Promise < GHSession > {
2019-05-08 13:39:57 +00:00
var sess : GHSession ;
return this . getGithubSession ( ghurl ) . then ( ( session ) = > {
2019-05-07 19:37:37 +00:00
sess = session ;
return sess . repo . commits ( this . branch ) . fetch ( ) ;
} )
. then ( ( sha ) = > {
return sess . repo . git . trees ( sha . sha ) . fetch ( ) ;
} )
. then ( ( tree ) = > {
let blobreads = [ ] ;
sess . paths = [ ] ;
tree . tree . forEach ( ( item ) = > {
console . log ( item . path , item . type , item . size ) ;
sess . paths . push ( item . path ) ;
if ( item . type == 'blob' && ! this . isFileIgnored ( item . path ) ) {
var read = sess . repo . git . blobs ( item . sha ) . readBinary ( ) . then ( ( blob ) = > {
var path = sess . prefix + item . path ;
var size = item . size ;
var isBinary = isProbablyBinary ( blob ) ;
var data = isBinary ? stringToByteArray ( blob ) : blob ; //byteArrayToUTF8(blob);
2019-05-08 23:15:26 +00:00
return ( deststore || this . store ) . setItem ( path , data ) ;
2019-05-07 19:37:37 +00:00
} ) ;
blobreads . push ( read ) ;
} else {
console . log ( "ignoring " + item . path ) ;
}
} ) ;
return Promise . all ( blobreads ) ;
} )
. then ( ( blobs ) = > {
this . bind ( sess , true ) ;
return sess ;
} ) ;
}
2019-05-08 17:42:24 +00:00
importAndPull ( ghurl :string ) {
return this . import ( ghurl ) . then ( ( sess ) = > {
return this . pull ( ghurl ) ;
} ) ;
}
2019-05-07 19:37:37 +00:00
publish ( reponame :string , desc :string , license :string , isprivate :boolean ) : Promise < GHSession > {
2019-05-08 13:39:57 +00:00
var repo ;
2019-05-07 19:37:37 +00:00
return this . github . user . repos . create ( {
name : reponame ,
description : desc ,
private : isprivate ,
2019-05-08 13:39:57 +00:00
auto_init : false ,
2019-05-07 19:37:37 +00:00
license_template : license
} )
2019-05-08 13:39:57 +00:00
. then ( ( _repo ) = > {
repo = _repo ;
// create README.md
var s = README_md_template ;
s = s . replace ( /\$NAME/g , reponame ) ;
s = s . replace ( /\$PLATFORM/g , this . project . platform_id ) ;
s = s . replace ( /\$IMPORTURL/g , repo . html_url ) ;
s = s . replace ( /\$MAINFILE/g , this . project . stripLocalPath ( this . project . mainPath ) ) ;
var config = {
message : '8bitworkshop: updated metadata in README.md' ,
content : btoa ( s )
}
return repo . contents ( 'README.md' ) . add ( config ) ;
} )
. then ( ( ) = > {
return this . getGithubSession ( repo . htmlUrl ) ;
} )
. then ( ( sess ) = > {
2019-05-07 19:37:37 +00:00
this . bind ( sess , true ) ;
return sess ;
} ) ;
}
commitPush ( ghurl :string , message :string , files : { path :string , data :FileData } [ ] ) : Promise < GHSession > {
var sess : GHSession ;
var repo ;
var head ;
var tree ;
2019-05-08 13:39:57 +00:00
return this . getGithubSession ( ghurl ) . then ( ( session ) = > {
2019-05-07 19:37:37 +00:00
sess = session ;
repo = sess . repo ;
return repo . git . refs . heads ( this . branch ) . fetch ( ) ;
} ) . then ( ( _head ) = > {
head = _head ;
return repo . git . trees ( head . object . sha ) . fetch ( ) ;
} ) . then ( ( _tree ) = > {
tree = _tree ;
return Promise . all ( files . map ( ( file ) = > {
return repo . git . blobs . create ( {
content : file.data ,
encoding : 'utf-8'
} ) ;
} ) ) ;
} ) . then ( ( blobs ) = > {
return repo . git . trees . create ( {
tree : files.map ( ( file , index ) = > {
return {
path : file.path ,
mode : '100644' ,
type : 'blob' ,
sha : blobs [ index ] [ 'sha' ]
} ;
} ) ,
base_tree : tree.sha
} ) ;
} ) . then ( ( tree ) = > {
return repo . git . commits . create ( {
message : message ,
tree : tree.sha ,
parents : [
head . object . sha
]
} ) ;
} ) . then ( ( commit ) = > {
return repo . git . refs . heads ( this . branch ) . update ( {
sha : commit.sha
} ) ;
} ) . then ( ( update ) = > {
return sess ;
} ) ;
}
}