1+ import fs from 'fs-extra'
2+ import shell from 'shelljs'
3+ import { dirname , basename } from 'path'
4+ import fetch from 'node-fetch'
5+ import { extension } from 'mime-types'
6+ import credentials from './credentials.js'
7+
8+ const { username, token, folder } = credentials
9+
10+ function request ( path , options = { } ) {
11+ return new Promise ( ( resolve , reject ) => {
12+ const baseUrl = path . substr ( 0 , 4 ) !== 'http' ? 'https://api.github.com' : ''
13+ fetch ( `${ baseUrl } ${ path } ` , {
14+ headers : {
15+ Authorization : `Token ${ token } `
16+ } ,
17+ ...options
18+ } )
19+ . then ( resp => {
20+ resolve ( resp )
21+ } )
22+ . catch ( err => {
23+ reject ( err )
24+ } )
25+ } )
26+ }
27+
28+ function requestJson ( path , options ) {
29+ return new Promise ( async ( resolve , reject ) => {
30+ try {
31+ const response = await request ( path , options )
32+ const json = await response . json ( )
33+ resolve ( json )
34+ } catch ( err ) {
35+ reject ( err )
36+ }
37+ } )
38+ }
39+
40+ function requestAll ( path , options ) {
41+ return new Promise ( async ( resolve , reject ) => {
42+ try {
43+ let items = [ ]
44+ let page = 1
45+ while ( page !== null ) {
46+ const separator = path . indexOf ( '?' ) === - 1 ? '?' : '&'
47+ const moreItemsResponse = await request ( `${ path } ${ separator } page=${ page } ` , options )
48+ const moreItems = await moreItemsResponse . json ( )
49+ if ( moreItems . length ) {
50+ items = [ ...items , ...moreItems ]
51+ page ++
52+ } else {
53+ page = null
54+ }
55+ }
56+ resolve ( items )
57+ } catch ( err ) {
58+ reject ( err )
59+ }
60+ } )
61+ }
62+
63+ function downloadFile ( sourceFileUrl , targetFilePath ) {
64+ return new Promise ( async ( resolve , reject ) => {
65+ const response = await request ( sourceFileUrl )
66+ const ext = extension ( [ ...response . headers ] . filter ( obj => obj [ 0 ] === 'content-type' ) [ 0 ] [ 1 ] )
67+ targetFilePath = targetFilePath + ( ext ? '.' + ext : '' )
68+ const fileStream = fs . createWriteStream ( targetFilePath )
69+ response . body . pipe ( fileStream )
70+ response . body . on ( 'error' , reject )
71+ fileStream . on ( 'finish' , ( ) => {
72+ resolve ( targetFilePath )
73+ } )
74+ } )
75+ }
76+
77+ function downloadAssets ( body , folder , filename ) {
78+ return new Promise ( async ( resolve , reject ) => {
79+ try {
80+ const assets = body ?. match ( / [ " ( ] h t t p s : \/ \/ g i t h u b \. c o m \/ ( .+ ) \/ a s s e t s \/ ( .+ ) [ ) " ] / g) || [ ]
81+ for ( const assetId in assets ) {
82+ const targetFilename = filename . replace ( '{id}' , assetId )
83+ const targetPath = folder + '/' + targetFilename
84+ const sourceUrl = assets [ assetId ] . replace ( / ^ [ " ( ] ( .+ ) [ ) " ] $ / , '$1' )
85+ fs . ensureDirSync ( folder )
86+ const realTargetFilename = basename ( await downloadFile ( sourceUrl , targetPath ) )
87+ body = body . replace ( `"${ sourceUrl } "` , '"file://./assets/' + realTargetFilename + '"' )
88+ body = body . replace ( `(${ sourceUrl } )` , '(file://./assets/' + realTargetFilename + ')' )
89+ }
90+ resolve ( body )
91+ } catch ( err ) {
92+ reject ( err )
93+ }
94+ } )
95+ }
96+
97+ function writeJSON ( path , json ) {
98+ fs . ensureDirSync ( dirname ( path ) )
99+ fs . writeJsonSync ( path , json , { spaces : 2 } )
100+ }
101+
102+ async function backup ( ) {
103+ try {
104+
105+ // Reset the backup folder
106+ shell . exec ( `rm -r ${ folder } ` )
107+ fs . ensureDirSync ( folder )
108+
109+ // Get repositories
110+ const repositories = await requestAll ( '/user/repos' )
111+
112+ // Save repositories
113+ writeJSON ( `${ folder } /repositories.json` , repositories )
114+
115+ // Loop repositories
116+ for ( const repository of repositories ) {
117+
118+ // Get issues
119+ const issues = await requestAll ( `/repos/${ username } /${ repository . name } /issues?state=all` )
120+
121+ // Loop issues
122+ for ( const issueId in issues ) {
123+
124+ // Download issue assets
125+ issues [ issueId ] . body = await downloadAssets (
126+ issues [ issueId ] . body ,
127+ `${ folder } /repositories/${ repository . name } /assets` ,
128+ `issue_${ issueId } _{id}`
129+ )
130+
131+ // Get issue comments
132+ const comments = await requestAll ( issues [ issueId ] . comments_url )
133+
134+ // Add issue comments to issues JSON
135+ issues [ issueId ] . comments = comments
136+
137+ // Loop issue comments
138+ for ( const commentId in comments ) {
139+
140+ // Download issue assets
141+ issues [ issueId ] . comments [ commentId ] . body = await downloadAssets (
142+ issues [ issueId ] . comments [ commentId ] . body ,
143+ `${ folder } /repositories/${ repository . name } /assets` ,
144+ `issue_${ issueId } _comment_${ commentId } _{id}`
145+ )
146+
147+ }
148+
149+ }
150+
151+ // Save issues
152+ writeJSON ( `${ folder } /repositories/${ repository . name } /issues.json` , issues )
153+
154+ // Clone repository
155+ shell . exec ( `git clone https://${ token } @github.com/${ username } /${ repository . name } .git ${ folder } /repositories/${ repository . name } /repository` )
156+
157+ }
158+
159+ // Get user details
160+ const user = await requestJson ( '/user' )
161+ writeJSON ( `${ folder } /user/user.json` , user )
162+
163+ // Get starred repositories
164+ const starred = await requestAll ( '/user/starred' )
165+ writeJSON ( `${ folder } /user/starred.json` , starred )
166+
167+ // Complete script
168+ console . log ( 'Backup completed!' )
169+ shell . exit ( 1 )
170+
171+ } catch ( err ) {
172+ throw err
173+ }
174+ }
175+
176+ backup ( )
0 commit comments