├── .babelrc ├── .gitignore ├── .vscode ├── getNet.js ├── pack-zip.js ├── run-webpack.js ├── server.crt ├── server.key ├── settings.json ├── start-dev.js └── start-server.js ├── icon.png ├── main.d.ts ├── package.json ├── plugin.json ├── postcss.config.js ├── readme.md ├── src ├── GitHubAPI │ ├── Gist.js │ ├── GitHub.js │ ├── Issue.js │ ├── Markdown.js │ ├── Organization.js │ ├── Project.js │ ├── RateLimit.js │ ├── Repository.js │ ├── Requestable.js │ ├── Search.js │ ├── Team.js │ └── User.js ├── githubFs.js └── main.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "env": { 6 | "test": { 7 | "presets": [ 8 | "@babel/env" 9 | ] 10 | } 11 | }, 12 | "plugins": [ 13 | "html-tag-js/jsx/jsx-to-tag.js", 14 | "html-tag-js/jsx/syntax-parser.js", 15 | [ 16 | "@babel/plugin-transform-runtime" 17 | ] 18 | ], 19 | "compact": false 20 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | dist.zip 4 | dist -------------------------------------------------------------------------------- /.vscode/getNet.js: -------------------------------------------------------------------------------- 1 | const { networkInterfaces } = require('os'); 2 | 3 | module.exports = async (mode = 'dev') => { 4 | const { WiFi, Ethernet } = getIp(); 5 | const [ip] = WiFi || Ethernet; 6 | const port = '5500'; 7 | const src = `https://${ip || '10.0.0'}:${port}`; 8 | console.log('Server starting at: ', src); 9 | return { ip, port }; 10 | }; 11 | 12 | function getIp() { 13 | const nets = networkInterfaces(); 14 | const results = {}; // Or just '{}', an empty object 15 | 16 | Object.keys(nets).forEach((name) => { 17 | nets[name].forEach((net) => { 18 | // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses 19 | // 'IPv4' is in Node <= 17, from 18 it's a number 4 or 6 20 | const familyV4Value = typeof net.family === 'string' ? 'IPv4' : 4; 21 | if (net.family === familyV4Value && !net.internal) { 22 | if (!results[name]) { 23 | results[name] = []; 24 | } 25 | results[name].push(net.address); 26 | } 27 | }); 28 | }); 29 | return results; 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/pack-zip.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const jszip = require('jszip'); 4 | 5 | const iconFile = path.join(__dirname, '../icon.png'); 6 | const pluginJSON = path.join(__dirname, '../plugin.json'); 7 | const distFolder = path.join(__dirname, '../dist'); 8 | let readmeDotMd = path.join(__dirname, '../readme.md'); 9 | 10 | if (!fs.existsSync(readmeDotMd)) { 11 | readmeDotMd = path.join(__dirname, '../README.md'); 12 | } 13 | 14 | // create zip file of dist folder 15 | 16 | const zip = new jszip(); 17 | 18 | zip.file('icon.png', fs.readFileSync(iconFile)); 19 | zip.file('plugin.json', fs.readFileSync(pluginJSON)); 20 | zip.file('readme.md', fs.readFileSync(readmeDotMd)); 21 | 22 | loadFile('', distFolder); 23 | 24 | zip 25 | .generateNodeStream({ type: 'nodebuffer', streamFiles: true }) 26 | .pipe(fs.createWriteStream(path.join(__dirname, '../dist.zip'))) 27 | .on('finish', () => { 28 | console.log('dist.zip written.'); 29 | }); 30 | 31 | function loadFile(root, folder) { 32 | const distFiles = fs.readdirSync(folder); 33 | distFiles.forEach((file) => { 34 | 35 | const stat = fs.statSync(path.join(folder, file)); 36 | 37 | if (stat.isDirectory()) { 38 | zip.folder(file); 39 | loadFile(path.join(root, file), path.join(folder, file)); 40 | return; 41 | } 42 | 43 | if (!/LICENSE.txt/.test(file)) { 44 | zip.file(path.join(root, file), fs.readFileSync(path.join(folder, file))); 45 | } 46 | }); 47 | } -------------------------------------------------------------------------------- /.vscode/run-webpack.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const { spawn } = require('child_process'); 3 | const path = require('path'); 4 | 5 | const webpack = spawn('npx.cmd', ['webpack', '--mode=development', '--watch'], { cwd: path.resolve(__dirname, '../') }); 6 | 7 | webpack.on('error', (webpackError) => { 8 | if (webpackError) { 9 | console.error(webpackError); 10 | process.exit(1); 11 | } 12 | }); 13 | 14 | webpack.stdout.on('data', (chunk) => { 15 | const stdout = chunk.toString(); 16 | console.log(stdout); 17 | process.send(stdout); 18 | }); 19 | 20 | webpack.stdout.on('error', (error) => { 21 | console.log(error); 22 | }); 23 | 24 | webpack.stderr.on('data', (chunk) => { 25 | const stderr = chunk.toString(); 26 | console.log(stderr); 27 | }); 28 | -------------------------------------------------------------------------------- /.vscode/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICfjCCAecCFGoKwe9jqvLXZUsAKK8R9rBoxQBVMA0GCSqGSIb3DQEBCwUAMH4x 3 | CzAJBgNVBAYTAklOMRMwEQYDVQQIDApBaml0IEt1bWFyMQwwCgYDVQQHDANCU1Ax 4 | DjAMBgNVBAoMBUZYREJHMQwwCgYDVQQLDANERVYxDTALBgNVBAMMBEFqaXQxHzAd 5 | BgkqhkiG9w0BCQEWEG1lQGFqaXRrdW1hci5kZXYwHhcNMjIwODIxMDc0NjI1WhcN 6 | MjMwODIxMDc0NjI1WjB+MQswCQYDVQQGEwJJTjETMBEGA1UECAwKQWppdCBLdW1h 7 | cjEMMAoGA1UEBwwDQlNQMQ4wDAYDVQQKDAVGWERCRzEMMAoGA1UECwwDREVWMQ0w 8 | CwYDVQQDDARBaml0MR8wHQYJKoZIhvcNAQkBFhBtZUBhaml0a3VtYXIuZGV2MIGf 9 | MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClD9GyID1GNGCdsq8vshf2h/tMUzxW 10 | mWqOoBi8ZSUrQGoZJ4vxSk5+kPkSvaZzj4zs+LnUMqIXPu1KSIXflVRXzAvh5VIE 11 | 7M0ithYYbGPSyviUWpatdLCvLCOPXZPTXc+66mY7RnCVzlBqGKOR2H4goeGWq0zG 12 | Q+V3pAM7gLUJsQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAHpMjw8nYPtJ20jO2i6M 13 | kJg09Hk91g63HEx4noV8WallM80exNYWJgzNI69UIRdh78QAEknJh43TSF61bVpo 14 | i+mtBHzZLyPv4LHKNN+6eBoMxi58tbWVZXQB4awW6d1AstHY10aSygOmOUKLtGxr 15 | lYt6v4QwWrm3j7oNCDRDwxlj 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /.vscode/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,F6E1E7807FC07585 4 | 5 | 1RxeEBdxtQ0+Erd+wmDLuaHy07d81+5uqCBZ1FVkzFOReCwDHFvqT9pyo00soIBJ 6 | ECcUOQyVoV7XyQKZVna+XwQJ8WoiF7R0dVeP7q1E8whFhVD+ybnwvCHSe9Zv1DTo 7 | 8R74rrAqRRKOf0aFEt2DR3sO9vdljOQY0JSTOefFisJs++FSDGSMPzyoUjyGzix+ 8 | jOcbA9BjPoossVRNSNta9q7IMZRvYnF+mqbeKrlQ7dDV6BBCICJ15syzp0FFZcry 9 | 7Upmstp+HtFphDr1ABaXlbSzPIzj+lYBro4vV4v/FuyGigwzYhiftTzypz0sVV2u 10 | yOSIGkQkNrg+0iaD35BuLzuZnKvlmjwBeFL0xlN0oh2yUSqveTUwiyGXhJxqjuKe 11 | lK9LEkKFtkj+BB0gwKy0aHNYM7Z3F2FfNGd/FlxxEbZMfORm03W/I3ploJLKk6kO 12 | H69Rkh+5lPsO0q89YBuqptiJH4cgKSup+yWH8LASbz+nuxLEKJTasJZJFEFxO62v 13 | gVHARgwv/V5HYqE4FF860mQs/ZiRVJfTN1HWZ4OpEHjJMuDhWLCyqxHeLMvL8nxd 14 | 5qm9cGoguHWmv7JLe/R238AZhYg6eBybg+WAqOJZ2LdMQjAKFa5+oWezCAk1uLI9 15 | v12C5EBYZFI7znx2C4A+YAN7a3HAf+p6o467c1aL/8CQdb37soSpdnAKApx1uFBp 16 | TBxPrNXBOkND/bdU/w4E1lqMPg5KPFNn3gYe7YTB0fG4YqrBfpA0uswBdNHf4u4E 17 | u2Q99Fw9dIsj/BMkwFxTWM0Mb119VPyZm5nd5L4Y0GZmhND4UyVV0A== 18 | -----END RSA PRIVATE KEY----- 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dart.lineLength": 50, 3 | "prettier.printWidth": 50, 4 | "cSpell.words": [ 5 | "acode", 6 | "Ajit", 7 | "clearcache", 8 | "deadlyjack", 9 | "deletegist", 10 | "deletegistfile", 11 | "Kumar", 12 | "opengist", 13 | "pallete", 14 | "selectrepo", 15 | "updatetoken" 16 | ], 17 | } -------------------------------------------------------------------------------- /.vscode/start-dev.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const { fork, spawn } = require('child_process'); 3 | const path = require('path'); 4 | 5 | main(); 6 | 7 | async function main() { 8 | let serverStarted = false; 9 | console.log('+--------------+'); 10 | console.log('| Starting dev |'); 11 | console.log('+--------------+'); 12 | const webpack = fork(path.resolve(__dirname, './run-webpack.js')); 13 | webpack.on('message', (chunk) => { 14 | if (!serverStarted && chunk.search(/compiled\ssuccessfully/)) { 15 | startServer(); 16 | serverStarted = true; 17 | } 18 | }); 19 | 20 | webpack.on('error', (err) => { 21 | console.log('WEBPACK ERROR', err); 22 | webpack.kill(1); 23 | process.exit(1); 24 | }); 25 | } 26 | 27 | async function startServer() { 28 | const server = fork(path.resolve(__dirname, './start-server.js')); 29 | server.on('error', (err) => { 30 | console.log('SERVER ERROR', err); 31 | server.kill(1); 32 | process.exit(1); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /.vscode/start-server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const liveServer = require('live-server'); 5 | const getNet = require('./getNet'); 6 | 7 | const serverCrt = path.resolve(__dirname, 'server.crt'); 8 | const serverKey = path.resolve(__dirname, 'server.key'); 9 | 10 | main(); 11 | 12 | async function main() { 13 | const { ip: host, port } = await getNet('dev'); 14 | process.cwd = () => __dirname; 15 | liveServer.start({ 16 | open: false, 17 | port, 18 | host, 19 | cors: true, 20 | root: '../', 21 | ignore: 'node_modules,platforms,plugins', 22 | file: 'index.html', 23 | https: { 24 | cert: fs.readFileSync(serverCrt), 25 | key: fs.readFileSync(serverKey), 26 | passphrase: '1234', 27 | }, 28 | middleware: [(req, res, next) => { 29 | const url = req.originalUrl; 30 | const www = '../platforms/android/app/src/main/assets/www/'; 31 | 32 | if (url === '/cordova.js') { 33 | const file = path.resolve(__dirname, www, 'cordova.js'); 34 | sendFile(res, file); 35 | return; 36 | } 37 | 38 | if (url === '/cordova_plugins.js') { 39 | const file = path.resolve(__dirname, www, 'cordova_plugins.js'); 40 | sendFile(res, file); 41 | return; 42 | } 43 | 44 | next(); 45 | }], 46 | }); 47 | 48 | process.send('OK'); 49 | } 50 | 51 | function sendFile(res, filePath) { 52 | if (fs.existsSync(filePath)) { 53 | const stat = fs.statSync(filePath); 54 | 55 | res.writeHead(200, { 56 | 'Content-Type': 'application/javascript', 57 | 'Content-Length': stat.size, 58 | }); 59 | 60 | const readStream = fs.createReadStream(filePath); 61 | readStream.pipe(res); 62 | return; 63 | } 64 | 65 | res.writeHead(404, { 'Content-Type': 'text/plain' }); 66 | res.end(`ERROR cannot get ${filePath}`); 67 | } 68 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Acode-Foundation/acode-plugin-github/3430ec653b825c60df33e59ec8406784a2642dd5/icon.png -------------------------------------------------------------------------------- /main.d.ts: -------------------------------------------------------------------------------- 1 | type Formatter = { 2 | id: string; 3 | name: string; 4 | exts: Array; 5 | format(): Promise; 6 | }; 7 | 8 | type CancelOption = { 9 | timeout: number; 10 | callback(): void; 11 | }; 12 | 13 | type Loader = { 14 | setTitle(title: string): void; 15 | setMessage(message: string): void; 16 | hide(): void; 17 | show(): void; 18 | destroy(): void; 19 | }; 20 | 21 | type PromptOptions = { 22 | match: RegExp; 23 | required: boolean; 24 | placeholder: string; 25 | test(value: any): boolean; 26 | }; 27 | 28 | type SelectOptions = { 29 | onCancel(): void; 30 | hideOnSelect: boolean; 31 | textTransform: boolean; 32 | default: any; 33 | }; 34 | interface Acode { 35 | readonly exitAppMessage: string; 36 | readonly formatters: Array; 37 | exec(command: string, value?: any): boolean; 38 | setLoadingMessage(message: string): void; 39 | initPlugin(pluginId: string, baseUrl: string, $page: HTMLElement): void; 40 | unmountPlugin(pluginId: string): void; 41 | registerFormatter( 42 | id: string, 43 | extensions: Array, 44 | format: () => Promise, 45 | ): void; 46 | unregisterFormatter(id: string): void; 47 | fsOperation(file: string): FileSystem; 48 | newEditorFile(filename: string, options: object): object; 49 | alert(title: string, message: string, onhide: () => void): void; 50 | loader(title: string, message: string, options: CancelOption): Loader; 51 | prompt( 52 | message: string, 53 | defaultValue: string | number | boolean, 54 | type: string, 55 | options: PromptOptions, 56 | ): Promise; 57 | confirm(title: string, message: string): Promise; 58 | select( 59 | title: string, 60 | options: Array<[value: string, text: string, icon: string] | string>, 61 | config: SelectOptions, 62 | ): Promise; 63 | multiPrompt( 64 | title: string, 65 | inputs: Array, 66 | help: string, 67 | ): Promise>; 68 | fileBrowser( 69 | mode: 'file' | 'folder', 70 | info: string, 71 | openLast: boolean, 72 | ): Promise; 73 | toInternalUrl(url: string): Promise; 74 | $menuToggler: HTMLElement; 75 | $editMenuToggler: HTMLElement; 76 | pluginServer: Server; 77 | webServer: Server; 78 | $quickToolToggler: HTMLElement; 79 | $headerToggler: HTMLElement; 80 | } 81 | 82 | interface fileBrowserSettings { 83 | showHiddenFiles: 'on' | 'off'; 84 | sortByName: 'on' | 'off'; 85 | } 86 | 87 | interface searchSettings { 88 | wrap: boolean; 89 | caseSensitive: boolean; 90 | regExp: boolean; 91 | wholeWord: boolean; 92 | } 93 | 94 | interface Settings { 95 | animation: 'system' | boolean; 96 | autosave: number; 97 | fileBrowser: fileBrowserSettings; 98 | maxFileSize: number; 99 | filesNotAllowed: string[]; 100 | formatter: Map; 101 | search: searchSettings; 102 | lang: string; 103 | fontSize: string; 104 | editorTheme: string; 105 | appTheme: string; 106 | textWrap: boolean; 107 | softTab: boolean; 108 | tabSize: number; 109 | linenumbers: boolean; 110 | formatOnSave: boolean; 111 | linting: boolean; 112 | previewMode: 'browser' | 'inapp'; 113 | showSpaces: boolean; 114 | openFileListPos: 'sidebar' | 'header'; 115 | quickTools: boolean; 116 | editorFont: string; 117 | vibrateOnTap: boolean; 118 | fullscreen: boolean; 119 | smartCompletion: boolean; 120 | floatingButton: boolean; 121 | liveAutoCompletion: boolean; 122 | showPrintMargin: boolean; 123 | scrollbarSize: number; 124 | confirmOnExit: boolean; 125 | customTheme: Map; 126 | customThemeMode: 'light' | 'dark'; 127 | lineHeight: number; 128 | leftMargin: number; 129 | checkFiles: boolean; 130 | desktopMode: boolean; 131 | console: 'legacy' | 'eruda'; 132 | keyboardMode: 'CODE' | 'NORMAL'; 133 | keyboardMode: 'NO_SUGGESTIONS' | 'NO_SUGGESTIONS_AGGRESSIVE' | 'NORMAL'; 134 | showAd: boolean; 135 | disableCache: boolean; 136 | diagonalScrolling: boolean; 137 | reverseScrolling: boolean; 138 | teardropTimeout: number; 139 | teardropSize: 20 | 40 | 60; 140 | scrollSpeed: number; 141 | } 142 | 143 | interface AppSettings { 144 | value: Settings; 145 | update(settings?: Settings, showToast?: boolean): Promise; 146 | update(showToast?: boolean): Promise; 147 | defaultSettings: Settings; 148 | reset(): Promise; 149 | onload: () => void; 150 | onsave: () => void; 151 | loaded: boolean; 152 | isFileAllowed(ext: string): boolean; 153 | on( 154 | eventName: 'reset' | 'update', 155 | callback: (this: Settings, settings: Settings | string) => void, 156 | ): void; 157 | off( 158 | eventName: 'reset' | 'update', 159 | callback: (this: Settings, settings: Settings | string) => void, 160 | ): void; 161 | applyAutoSaveSetting(): void; 162 | applyAnimationSetting(): void; 163 | applyLangSetting(): void; 164 | } 165 | 166 | interface ActionStackOptions { 167 | id: string; 168 | action(): void; 169 | } 170 | 171 | interface ActionStack { 172 | push(options: ActionStackOptions): void; 173 | pop(): ActionStack; 174 | remove(id: string): void; 175 | has(id: string): boolean; 176 | length: number; 177 | /** 178 | * Sets a mark to recently pushed action 179 | */ 180 | setMark(): void; 181 | /** 182 | * Remove all actions that are pushed after marked positions (using `setMark()`) 183 | */ 184 | clearFromMark(): void; 185 | /** 186 | * Callback function when app is to close 187 | */ 188 | onCloseApp: () => void; 189 | } 190 | 191 | interface Fold { 192 | range: AceAjax.Range; 193 | ranges: Array; 194 | placeholder: string; 195 | } 196 | 197 | interface FileStatus { 198 | canRead: boolean; 199 | canWrite: boolean; 200 | exists: boolean; //indicates if file can be found on device storage 201 | isDirectory: boolean; 202 | isFile: boolean; 203 | isVirtual: boolean; 204 | lastModified: number; 205 | length: number; 206 | name: string; 207 | type: string; 208 | uri: string; 209 | } 210 | 211 | interface OriginObject { 212 | origin: string; 213 | query: string; 214 | } 215 | 216 | interface URLObject { 217 | url: string; 218 | query: string; 219 | } 220 | 221 | interface fileData { 222 | file: FileEntry; 223 | data: ArrayBuffer; 224 | } 225 | 226 | interface ExternalFs { 227 | readFile(): Promise; 228 | createFile( 229 | parent: string, 230 | filename: string, 231 | data: string, 232 | ): Promise<'SUCCESS'>; 233 | createDir(parent: string, path: string): Promise<'SUCCESS'>; 234 | delete(filename: string): Promise<'SUCCESS'>; 235 | writeFile(filename: string, content: string): Promise<'SUCCESS'>; 236 | renameFile(src: string, newname: string): Promise<'SUCCESS'>; 237 | copy(src: string, dest: string): Promise<'SUCCESS'>; 238 | move(src: string, dest: string): Promise<'SUCCESS'>; 239 | stats(src: string): Promise; 240 | uuid: string; 241 | } 242 | 243 | interface RemoteFs { 244 | listDir(path: string): Promise>; 245 | readFile(path: string): Promise; 246 | createFile(filename: string, data: string): Promise; 247 | createDir(path: string): Promise; 248 | delete(name: string): Promise; 249 | writeFile(filename: string, content: string): Promise; 250 | rename(src: string, newname: string): Promise; 251 | copyTo(src: string, dest: string): Promise; 252 | currentDirectory(): Promise; 253 | homeDirectory(): Promise; 254 | stats(src: string): Promise; 255 | /** 256 | * Resolve with true if file exists else resolve if false. Rejects if any error is generated. 257 | */ 258 | exists(): Promise; 259 | origin: string; 260 | originObjec: OriginObject; 261 | } 262 | 263 | interface InternalFs { 264 | copyTo(dest: string): Promise; 265 | moveTo(dest: string): Promise; 266 | listDir(path: string): Promise; 267 | createDir(parent: string, dirname: string): Promise; 268 | delete(filename: string): Promise; 269 | readFile(filename: string): Promise; 270 | writeFile( 271 | filename: string, 272 | content: string, 273 | create: boolean, 274 | exclusive: boolean, 275 | ): Promise; 276 | renameFile(src: string, newname: string): Promise; 277 | stats(src: string): Promise; 278 | exists(): Promise; 279 | } 280 | 281 | interface FsEntry { 282 | url: string; 283 | isDirectory: boolean; 284 | isFile: boolean; 285 | } 286 | 287 | interface FileSystem { 288 | lsDir(): Promise>; 289 | readFile(): Promise; 290 | readFile(encoding: string): Promise; 291 | writeFile(content: string): Promise; 292 | createFile(name: string, data: string): Promise; 293 | createDirectory(name: string): Promise; 294 | delete(): Promise; 295 | copyTo(dest: string): Promise; 296 | moveTo(dset: string): Promise; 297 | renameTo(newName: string): Promise; 298 | exists(): Promise; 299 | stat(): Promise; 300 | } 301 | 302 | interface externalStorageData { 303 | path: string; 304 | name: string; 305 | origin: string; 306 | } 307 | 308 | interface elementContainer { 309 | [key: string]: HTMLElement; 310 | } 311 | 312 | interface GistFile { 313 | filename: string; 314 | content: string; 315 | } 316 | 317 | interface GistFiles { 318 | [filename: string]: GistFile; 319 | } 320 | 321 | interface Repository {} 322 | 323 | interface Repo { 324 | readonly sha: string; 325 | name: string; 326 | data: string; 327 | repo: string; 328 | path: string; 329 | branch: 'master' | 'main' | string; 330 | commitMessage: string; 331 | setName(name: string): Promise; 332 | setData(data: string): Promise; 333 | repository: Repository; 334 | } 335 | 336 | interface Gist { 337 | readonly id: string; 338 | readonly isNew: boolean; 339 | files: GistFiles; 340 | setName(name: string, newName: string): Promise; 341 | setData(name: string, text: string): Promise; 342 | addFile(name: string): void; 343 | removeFile(name: string): Promise; 344 | } 345 | 346 | interface GitRecord { 347 | get(sha: string): Promise; 348 | add(gitFileRecord: Repo): void; 349 | remove(sha: string): Repo; 350 | update(sha: string, gitFileRecord: Repo): void; 351 | } 352 | 353 | interface GistRecord { 354 | get(id: string): Gist; 355 | add(gist: any, isNew?: boolean): void; 356 | remove(gist: Gist): Gist; 357 | update(gist: Gist): void; 358 | reset(): void; 359 | } 360 | 361 | interface Window { 362 | restoreTheme(): void; 363 | } 364 | 365 | interface Input { 366 | id: string; 367 | type: 368 | | 'text' 369 | | 'numberic' 370 | | 'tel' 371 | | 'search' 372 | | 'email' 373 | | 'url' 374 | | 'checkbox' 375 | | 'radio' 376 | | 'group' 377 | | 'button'; 378 | match: RegExp; 379 | value: string; 380 | name: string; 381 | required: boolean; 382 | hints(options: Array): void; 383 | placeholder: string; 384 | disabled: boolean; 385 | onclick(this: HTMLElement): void; 386 | } 387 | 388 | interface String { 389 | /** 390 | * Capitalize the string for e.g. converts "this is a string" to "This Is A string" 391 | */ 392 | capitalize(): string; 393 | /** 394 | * Capitalize a character at given index for e.g. 395 | * ```js 396 | * "this is a string".capitalize(0) //"This is a string" 397 | * ``` 398 | */ 399 | capitalize(index: number): string; 400 | /** 401 | * Returns hashcode of the string 402 | */ 403 | hashCode(): string; 404 | /** 405 | * Subtract the string passed in argument from the given string, 406 | * For e.g. ```"myname".subtract("my") //"name"``` 407 | */ 408 | subtract(str: string): string; 409 | } 410 | 411 | /** 412 | * Returns fully decoded url 413 | * @param url 414 | */ 415 | declare function decodeURL(url: string): string; 416 | 417 | /** 418 | * App settings 419 | */ 420 | declare var appSettings: AppSettings; 421 | /** 422 | * Predefined strings for language support 423 | */ 424 | declare var strings: Map; 425 | /** 426 | * Handles back button click 427 | */ 428 | declare var acode: Acode; 429 | 430 | declare var ASSETS_DIRECTORY: string; 431 | declare var CACHE_STORAGE: string; 432 | declare var DATA_STORAGE: string; 433 | declare var PLUGIN_DIR: string; 434 | declare var DOES_SUPPORT_THEME: boolean; 435 | declare var IS_FREE_VERSION: boolean; 436 | declare var KEYBINDING_FILE: string; 437 | declare var ANDROID_SDK_INT: number; 438 | 439 | declare var ace: AceAjax.Ace; 440 | declare var app: HTMLBodyElement; 441 | declare var root: HTMLElement; 442 | declare var freeze: boolean; 443 | declare var saveTimeout: number; 444 | declare var actionStack: ActionStack; 445 | declare var addedFolder: Array; 446 | declare var gitRecord: GitRecord; 447 | declare var gistRecord: GistRecord; 448 | declare var gitRecordFile: string; 449 | declare var gistRecordFile: string; 450 | declare var toastQueue: Array; 451 | declare var toast: (message: string) => void; 452 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "acode-plugin-github", 3 | "version": "1.3.1", 4 | "description": "Github plugin for acode editor", 5 | "main": "dist/main.js", 6 | "repository": "https://github.com/deadlyjack/acode-plugin-github.git", 7 | "author": "Ajit ", 8 | "license": "MIT", 9 | "dependencies": { 10 | "github-api": "^3.4.0", 11 | "html-tag-js": "^1.1.37", 12 | "mime-types": "^2.1.35" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "^7.21.0", 16 | "@babel/plugin-transform-runtime": "^7.21.0", 17 | "@babel/preset-env": "^7.18.10", 18 | "babel-loader": "^9.1.2", 19 | "jszip": "^3.10.1", 20 | "live-server": "^1.2.2", 21 | "path-browserify": "^1.0.1", 22 | "webpack": "^5.76.0", 23 | "webpack-cli": "^5.0.1" 24 | }, 25 | "scripts": { 26 | "build": "webpack", 27 | "build-release": "webpack --mode production", 28 | "start-dev": "node .vscode/start-dev" 29 | }, 30 | "browserslist": "cover 100%,not android < 5", 31 | "resolutions": { 32 | "terser": ">=5.16.6", 33 | "glob-parent": ">=5.1.2" 34 | } 35 | } -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "acode.plugin.github", 3 | "name": "Github", 4 | "main": "dist/main.js", 5 | "version": "1.3.1", 6 | "readme": "readme.md", 7 | "icon": "icon.png", 8 | "files": [], 9 | "author": { 10 | "name": "Ajit Kumar", 11 | "email": "me@ajitkumar.dev", 12 | "github": "deadlyjack" 13 | } 14 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')({}) 4 | ] 5 | }; -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Github plugin 2 | 3 | Access your gist/repositories without cloning/downloading it. To access search `Github` in command pallete. 4 | 5 | ## Commands 6 | 7 | - **Open repo**: Open list of repositories to select and after you select repository or branch it opens the repository in sidebar. 8 | - **Open gist**: Lets you open/create gist file. 9 | - **Delete Gist**: Delete selected gist. 10 | - **Delete gist file**: Delete selected gist file. 11 | - **Clear github cache**: Deletes cached repositories and gists list. 12 | 13 | ## Updates 14 | 15 | - **1.2.1** 16 | - Upload non text file using 'insert-file' option. 17 | - Updated api according to latest acode. 18 | - **1.1.3** 19 | - Updated to work with latest acode. 20 | - **1.1.2** 21 | - Ask for commit message. 22 | - **1.0.3** 23 | - Bugs fixes. 24 | - **1.0.2** 25 | - Create/Delete file, directory in repository. 26 | -------------------------------------------------------------------------------- /src/GitHubAPI/Gist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | 10 | /** 11 | * A Gist can retrieve and modify gists. 12 | */ 13 | class Gist extends Requestable { 14 | /** 15 | * Create a Gist. 16 | * @param {string} id - the id of the gist (not required when creating a gist) 17 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 18 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 19 | */ 20 | constructor(id, auth, apiBase) { 21 | super(auth, apiBase); 22 | this.__id = id; 23 | } 24 | 25 | /** 26 | * Fetch a gist. 27 | * @see https://developer.github.com/v3/gists/#get-a-single-gist 28 | * @param {Requestable.callback} [cb] - will receive the gist 29 | * @return {Promise} - the Promise for the http request 30 | */ 31 | read(cb) { 32 | return this._request('GET', `/gists/${this.__id}`, null, cb); 33 | } 34 | 35 | /** 36 | * Create a new gist. 37 | * @see https://developer.github.com/v3/gists/#create-a-gist 38 | * @param {Object} gist - the data for the new gist 39 | * @param {Requestable.callback} [cb] - will receive the new gist upon creation 40 | * @return {Promise} - the Promise for the http request 41 | */ 42 | create(gist, cb) { 43 | return this._request('POST', '/gists', gist, cb).then((response) => { 44 | this.__id = response.data.id; 45 | return response; 46 | }); 47 | } 48 | 49 | /** 50 | * Delete a gist. 51 | * @see https://developer.github.com/v3/gists/#delete-a-gist 52 | * @param {Requestable.callback} [cb] - will receive true if the request succeeds 53 | * @return {Promise} - the Promise for the http request 54 | */ 55 | delete(cb) { 56 | return this._request('DELETE', `/gists/${this.__id}`, null, cb); 57 | } 58 | 59 | /** 60 | * Fork a gist. 61 | * @see https://developer.github.com/v3/gists/#fork-a-gist 62 | * @param {Requestable.callback} [cb] - the function that will receive the gist 63 | * @return {Promise} - the Promise for the http request 64 | */ 65 | fork(cb) { 66 | return this._request('POST', `/gists/${this.__id}/forks`, null, cb); 67 | } 68 | 69 | /** 70 | * Update a gist. 71 | * @see https://developer.github.com/v3/gists/#edit-a-gist 72 | * @param {Object} gist - the new data for the gist 73 | * @param {Requestable.callback} [cb] - the function that receives the API result 74 | * @return {Promise} - the Promise for the http request 75 | */ 76 | update(gist, cb) { 77 | return this._request('PATCH', `/gists/${this.__id}`, gist, cb); 78 | } 79 | 80 | /** 81 | * Star a gist. 82 | * @see https://developer.github.com/v3/gists/#star-a-gist 83 | * @param {Requestable.callback} [cb] - will receive true if the request is successful 84 | * @return {Promise} - the Promise for the http request 85 | */ 86 | star(cb) { 87 | return this._request('PUT', `/gists/${this.__id}/star`, null, cb); 88 | } 89 | 90 | /** 91 | * Unstar a gist. 92 | * @see https://developer.github.com/v3/gists/#unstar-a-gist 93 | * @param {Requestable.callback} [cb] - will receive true if the request is successful 94 | * @return {Promise} - the Promise for the http request 95 | */ 96 | unstar(cb) { 97 | return this._request('DELETE', `/gists/${this.__id}/star`, null, cb); 98 | } 99 | 100 | /** 101 | * Check if a gist is starred by the user. 102 | * @see https://developer.github.com/v3/gists/#check-if-a-gist-is-starred 103 | * @param {Requestable.callback} [cb] - will receive true if the gist is starred and false if the gist is not starred 104 | * @return {Promise} - the Promise for the http request 105 | */ 106 | isStarred(cb) { 107 | return this._request204or404(`/gists/${this.__id}/star`, null, cb); 108 | } 109 | 110 | /** 111 | * List the gist's commits 112 | * @see https://developer.github.com/v3/gists/#list-gist-commits 113 | * @param {Requestable.callback} [cb] - will receive the array of commits 114 | * @return {Promise} - the Promise for the http request 115 | */ 116 | listCommits(cb) { 117 | return this._requestAllPages(`/gists/${this.__id}/commits`, null, cb); 118 | } 119 | 120 | /** 121 | * Fetch one of the gist's revision. 122 | * @see https://developer.github.com/v3/gists/#get-a-specific-revision-of-a-gist 123 | * @param {string} revision - the id of the revision 124 | * @param {Requestable.callback} [cb] - will receive the revision 125 | * @return {Promise} - the Promise for the http request 126 | */ 127 | getRevision(revision, cb) { 128 | return this._request('GET', `/gists/${this.__id}/${revision}`, null, cb); 129 | } 130 | 131 | /** 132 | * List the gist's comments 133 | * @see https://developer.github.com/v3/gists/comments/#list-comments-on-a-gist 134 | * @param {Requestable.callback} [cb] - will receive the array of comments 135 | * @return {Promise} - the promise for the http request 136 | */ 137 | listComments(cb) { 138 | return this._requestAllPages(`/gists/${this.__id}/comments`, null, cb); 139 | } 140 | 141 | /** 142 | * Fetch one of the gist's comments 143 | * @see https://developer.github.com/v3/gists/comments/#get-a-single-comment 144 | * @param {number} comment - the id of the comment 145 | * @param {Requestable.callback} [cb] - will receive the comment 146 | * @return {Promise} - the Promise for the http request 147 | */ 148 | getComment(comment, cb) { 149 | return this._request( 150 | 'GET', 151 | `/gists/${this.__id}/comments/${comment}`, 152 | null, 153 | cb, 154 | ); 155 | } 156 | 157 | /** 158 | * Comment on a gist 159 | * @see https://developer.github.com/v3/gists/comments/#create-a-comment 160 | * @param {string} comment - the comment to add 161 | * @param {Requestable.callback} [cb] - the function that receives the API result 162 | * @return {Promise} - the Promise for the http request 163 | */ 164 | createComment(comment, cb) { 165 | return this._request( 166 | 'POST', 167 | `/gists/${this.__id}/comments`, 168 | { 169 | body: comment, 170 | }, 171 | cb, 172 | ); 173 | } 174 | 175 | /** 176 | * Edit a comment on the gist 177 | * @see https://developer.github.com/v3/gists/comments/#edit-a-comment 178 | * @param {number} comment - the id of the comment 179 | * @param {string} body - the new comment 180 | * @param {Requestable.callback} [cb] - will receive the modified comment 181 | * @return {Promise} - the promise for the http request 182 | */ 183 | editComment(comment, body, cb) { 184 | return this._request( 185 | 'PATCH', 186 | `/gists/${this.__id}/comments/${comment}`, 187 | { 188 | body: body, 189 | }, 190 | cb, 191 | ); 192 | } 193 | 194 | /** 195 | * Delete a comment on the gist. 196 | * @see https://developer.github.com/v3/gists/comments/#delete-a-comment 197 | * @param {number} comment - the id of the comment 198 | * @param {Requestable.callback} [cb] - will receive true if the request succeeds 199 | * @return {Promise} - the Promise for the http request 200 | */ 201 | deleteComment(comment, cb) { 202 | return this._request( 203 | 'DELETE', 204 | `/gists/${this.__id}/comments/${comment}`, 205 | null, 206 | cb, 207 | ); 208 | } 209 | } 210 | 211 | export default Gist; 212 | -------------------------------------------------------------------------------- /src/GitHubAPI/GitHub.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | /* eslint valid-jsdoc: ["error", {"requireReturnDescription": false}] */ 8 | 9 | import Gist from './Gist'; 10 | import User from './User'; 11 | import Issue from './Issue'; 12 | import Search from './Search'; 13 | import RateLimit from './RateLimit'; 14 | import Repository from './Repository'; 15 | import Organization from './Organization'; 16 | import Team from './Team'; 17 | import Markdown from './Markdown'; 18 | import Project from './Project'; 19 | 20 | /** 21 | * GitHub encapsulates the functionality to create various API wrapper objects. 22 | */ 23 | class GitHub { 24 | /** 25 | * Create a new GitHub. 26 | * @param {Requestable.auth} [auth] - the credentials to authenticate to Github. If auth is 27 | * not provided requests will be made unauthenticated 28 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 29 | */ 30 | constructor(auth, apiBase = 'https://api.github.com') { 31 | this.__apiBase = apiBase; 32 | this.__auth = auth || {}; 33 | } 34 | 35 | /** 36 | * Create a new Gist wrapper 37 | * @param {string} [id] - the id for the gist, leave undefined when creating a new gist 38 | * @return {Gist} 39 | */ 40 | getGist(id) { 41 | return new Gist(id, this.__auth, this.__apiBase); 42 | } 43 | 44 | /** 45 | * Create a new User wrapper 46 | * @param {string} [user] - the name of the user to get information about 47 | * leave undefined for the authenticated user 48 | * @return {User} 49 | */ 50 | getUser(user) { 51 | return new User(user, this.__auth, this.__apiBase); 52 | } 53 | 54 | /** 55 | * Create a new Organization wrapper 56 | * @param {string} organization - the name of the organization 57 | * @return {Organization} 58 | */ 59 | getOrganization(organization) { 60 | return new Organization(organization, this.__auth, this.__apiBase); 61 | } 62 | 63 | /** 64 | * create a new Team wrapper 65 | * @param {string} teamId - the name of the team 66 | * @return {team} 67 | */ 68 | getTeam(teamId) { 69 | return new Team(teamId, this.__auth, this.__apiBase); 70 | } 71 | 72 | /** 73 | * Create a new Repository wrapper 74 | * @param {string} user - the user who owns the repository 75 | * @param {string} repo - the name of the repository 76 | * @return {Repository} 77 | */ 78 | getRepo(user, repo) { 79 | return new Repository( 80 | this._getFullName(user, repo), 81 | this.__auth, 82 | this.__apiBase, 83 | ); 84 | } 85 | 86 | /** 87 | * Create a new Issue wrapper 88 | * @param {string} user - the user who owns the repository 89 | * @param {string} repo - the name of the repository 90 | * @return {Issue} 91 | */ 92 | getIssues(user, repo) { 93 | return new Issue( 94 | this._getFullName(user, repo), 95 | this.__auth, 96 | this.__apiBase, 97 | ); 98 | } 99 | 100 | /** 101 | * Create a new Search wrapper 102 | * @param {string} query - the query to search for 103 | * @return {Search} 104 | */ 105 | search(query) { 106 | return new Search(query, this.__auth, this.__apiBase); 107 | } 108 | 109 | /** 110 | * Create a new RateLimit wrapper 111 | * @return {RateLimit} 112 | */ 113 | getRateLimit() { 114 | return new RateLimit(this.__auth, this.__apiBase); 115 | } 116 | 117 | /** 118 | * Create a new Markdown wrapper 119 | * @return {Markdown} 120 | */ 121 | getMarkdown() { 122 | return new Markdown(this.__auth, this.__apiBase); 123 | } 124 | 125 | /** 126 | * Create a new Project wrapper 127 | * @param {string} id - the id of the project 128 | * @return {Project} 129 | */ 130 | getProject(id) { 131 | return new Project(id, this.__auth, this.__apiBase); 132 | } 133 | 134 | /** 135 | * Computes the full repository name 136 | * @param {string} user - the username (or the full name) 137 | * @param {string} repo - the repository name, must not be passed if `user` is the full name 138 | * @return {string} the repository's full name 139 | */ 140 | _getFullName(user, repo) { 141 | let fullname = user; 142 | 143 | if (repo) { 144 | fullname = `${user}/${repo}`; 145 | } 146 | 147 | return fullname; 148 | } 149 | } 150 | 151 | export default GitHub; 152 | -------------------------------------------------------------------------------- /src/GitHubAPI/Issue.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | 10 | /** 11 | * Issue wraps the functionality to get issues for repositories 12 | */ 13 | class Issue extends Requestable { 14 | /** 15 | * Create a new Issue 16 | * @param {string} repository - the full name of the repository (`:user/:repo`) to get issues for 17 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 18 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 19 | */ 20 | constructor(repository, auth, apiBase) { 21 | super(auth, apiBase); 22 | this.__repository = repository; 23 | } 24 | 25 | /** 26 | * Create a new issue 27 | * @see https://developer.github.com/v3/issues/#create-an-issue 28 | * @param {Object} issueData - the issue to create 29 | * @param {Requestable.callback} [cb] - will receive the created issue 30 | * @return {Promise} - the promise for the http request 31 | */ 32 | createIssue(issueData, cb) { 33 | return this._request( 34 | 'POST', 35 | `/repos/${this.__repository}/issues`, 36 | issueData, 37 | cb, 38 | ); 39 | } 40 | 41 | /** 42 | * List the issues for the repository 43 | * @see https://developer.github.com/v3/issues/#list-issues-for-a-repository 44 | * @param {Object} options - filtering options 45 | * @param {Requestable.callback} [cb] - will receive the array of issues 46 | * @return {Promise} - the promise for the http request 47 | */ 48 | listIssues(options, cb) { 49 | return this._requestAllPages( 50 | `/repos/${this.__repository}/issues`, 51 | options, 52 | cb, 53 | ); 54 | } 55 | 56 | /** 57 | * List the events for an issue 58 | * @see https://developer.github.com/v3/issues/events/#list-events-for-an-issue 59 | * @param {number} issue - the issue to get events for 60 | * @param {Requestable.callback} [cb] - will receive the list of events 61 | * @return {Promise} - the promise for the http request 62 | */ 63 | listIssueEvents(issue, cb) { 64 | return this._request( 65 | 'GET', 66 | `/repos/${this.__repository}/issues/${issue}/events`, 67 | null, 68 | cb, 69 | ); 70 | } 71 | 72 | /** 73 | * List comments on an issue 74 | * @see https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue 75 | * @param {number} issue - the id of the issue to get comments from 76 | * @param {Requestable.callback} [cb] - will receive the comments 77 | * @return {Promise} - the promise for the http request 78 | */ 79 | listIssueComments(issue, cb) { 80 | return this._request( 81 | 'GET', 82 | `/repos/${this.__repository}/issues/${issue}/comments`, 83 | null, 84 | cb, 85 | ); 86 | } 87 | 88 | /** 89 | * Get a single comment on an issue 90 | * @see https://developer.github.com/v3/issues/comments/#get-a-single-comment 91 | * @param {number} id - the comment id to get 92 | * @param {Requestable.callback} [cb] - will receive the comment 93 | * @return {Promise} - the promise for the http request 94 | */ 95 | getIssueComment(id, cb) { 96 | return this._request( 97 | 'GET', 98 | `/repos/${this.__repository}/issues/comments/${id}`, 99 | null, 100 | cb, 101 | ); 102 | } 103 | 104 | /** 105 | * Comment on an issue 106 | * @see https://developer.github.com/v3/issues/comments/#create-a-comment 107 | * @param {number} issue - the id of the issue to comment on 108 | * @param {string} comment - the comment to add 109 | * @param {Requestable.callback} [cb] - will receive the created comment 110 | * @return {Promise} - the promise for the http request 111 | */ 112 | createIssueComment(issue, comment, cb) { 113 | return this._request( 114 | 'POST', 115 | `/repos/${this.__repository}/issues/${issue}/comments`, 116 | { 117 | body: comment, 118 | }, 119 | cb, 120 | ); 121 | } 122 | 123 | /** 124 | * Edit a comment on an issue 125 | * @see https://developer.github.com/v3/issues/comments/#edit-a-comment 126 | * @param {number} id - the comment id to edit 127 | * @param {string} comment - the comment to edit 128 | * @param {Requestable.callback} [cb] - will receive the edited comment 129 | * @return {Promise} - the promise for the http request 130 | */ 131 | editIssueComment(id, comment, cb) { 132 | return this._request( 133 | 'PATCH', 134 | `/repos/${this.__repository}/issues/comments/${id}`, 135 | { 136 | body: comment, 137 | }, 138 | cb, 139 | ); 140 | } 141 | 142 | /** 143 | * Delete a comment on an issue 144 | * @see https://developer.github.com/v3/issues/comments/#delete-a-comment 145 | * @param {number} id - the comment id to delete 146 | * @param {Requestable.callback} [cb] - will receive true if the request is successful 147 | * @return {Promise} - the promise for the http request 148 | */ 149 | deleteIssueComment(id, cb) { 150 | return this._request( 151 | 'DELETE', 152 | `/repos/${this.__repository}/issues/comments/${id}`, 153 | null, 154 | cb, 155 | ); 156 | } 157 | 158 | /** 159 | * Edit an issue 160 | * @see https://developer.github.com/v3/issues/#edit-an-issue 161 | * @param {number} issue - the issue number to edit 162 | * @param {Object} issueData - the new issue data 163 | * @param {Requestable.callback} [cb] - will receive the modified issue 164 | * @return {Promise} - the promise for the http request 165 | */ 166 | editIssue(issue, issueData, cb) { 167 | return this._request( 168 | 'PATCH', 169 | `/repos/${this.__repository}/issues/${issue}`, 170 | issueData, 171 | cb, 172 | ); 173 | } 174 | 175 | /** 176 | * Get a particular issue 177 | * @see https://developer.github.com/v3/issues/#get-a-single-issue 178 | * @param {number} issue - the issue number to fetch 179 | * @param {Requestable.callback} [cb] - will receive the issue 180 | * @return {Promise} - the promise for the http request 181 | */ 182 | getIssue(issue, cb) { 183 | return this._request( 184 | 'GET', 185 | `/repos/${this.__repository}/issues/${issue}`, 186 | null, 187 | cb, 188 | ); 189 | } 190 | 191 | /** 192 | * List the milestones for the repository 193 | * @see https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository 194 | * @param {Object} options - filtering options 195 | * @param {Requestable.callback} [cb] - will receive the array of milestones 196 | * @return {Promise} - the promise for the http request 197 | */ 198 | listMilestones(options, cb) { 199 | return this._request( 200 | 'GET', 201 | `/repos/${this.__repository}/milestones`, 202 | options, 203 | cb, 204 | ); 205 | } 206 | 207 | /** 208 | * Get a milestone 209 | * @see https://developer.github.com/v3/issues/milestones/#get-a-single-milestone 210 | * @param {string} milestone - the id of the milestone to fetch 211 | * @param {Requestable.callback} [cb] - will receive the milestone 212 | * @return {Promise} - the promise for the http request 213 | */ 214 | getMilestone(milestone, cb) { 215 | return this._request( 216 | 'GET', 217 | `/repos/${this.__repository}/milestones/${milestone}`, 218 | null, 219 | cb, 220 | ); 221 | } 222 | 223 | /** 224 | * Create a new milestone 225 | * @see https://developer.github.com/v3/issues/milestones/#create-a-milestone 226 | * @param {Object} milestoneData - the milestone definition 227 | * @param {Requestable.callback} [cb] - will receive the milestone 228 | * @return {Promise} - the promise for the http request 229 | */ 230 | createMilestone(milestoneData, cb) { 231 | return this._request( 232 | 'POST', 233 | `/repos/${this.__repository}/milestones`, 234 | milestoneData, 235 | cb, 236 | ); 237 | } 238 | 239 | /** 240 | * Edit a milestone 241 | * @see https://developer.github.com/v3/issues/milestones/#update-a-milestone 242 | * @param {string} milestone - the id of the milestone to edit 243 | * @param {Object} milestoneData - the updates to make to the milestone 244 | * @param {Requestable.callback} [cb] - will receive the updated milestone 245 | * @return {Promise} - the promise for the http request 246 | */ 247 | editMilestone(milestone, milestoneData, cb) { 248 | return this._request( 249 | 'PATCH', 250 | `/repos/${this.__repository}/milestones/${milestone}`, 251 | milestoneData, 252 | cb, 253 | ); 254 | } 255 | 256 | /** 257 | * Delete a milestone (this is distinct from closing a milestone) 258 | * @see https://developer.github.com/v3/issues/milestones/#delete-a-milestone 259 | * @param {string} milestone - the id of the milestone to delete 260 | * @param {Requestable.callback} [cb] - will receive the status 261 | * @return {Promise} - the promise for the http request 262 | */ 263 | deleteMilestone(milestone, cb) { 264 | return this._request( 265 | 'DELETE', 266 | `/repos/${this.__repository}/milestones/${milestone}`, 267 | null, 268 | cb, 269 | ); 270 | } 271 | 272 | /** 273 | * Create a new label 274 | * @see https://developer.github.com/v3/issues/labels/#create-a-label 275 | * @param {Object} labelData - the label definition 276 | * @param {Requestable.callback} [cb] - will receive the object representing the label 277 | * @return {Promise} - the promise for the http request 278 | */ 279 | createLabel(labelData, cb) { 280 | return this._request( 281 | 'POST', 282 | `/repos/${this.__repository}/labels`, 283 | labelData, 284 | cb, 285 | ); 286 | } 287 | 288 | /** 289 | * List the labels for the repository 290 | * @see https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository 291 | * @param {Object} options - filtering options 292 | * @param {Requestable.callback} [cb] - will receive the array of labels 293 | * @return {Promise} - the promise for the http request 294 | */ 295 | listLabels(options, cb) { 296 | return this._request( 297 | 'GET', 298 | `/repos/${this.__repository}/labels`, 299 | options, 300 | cb, 301 | ); 302 | } 303 | 304 | /** 305 | * Get a label 306 | * @see https://developer.github.com/v3/issues/labels/#get-a-single-label 307 | * @param {string} label - the name of the label to fetch 308 | * @param {Requestable.callback} [cb] - will receive the label 309 | * @return {Promise} - the promise for the http request 310 | */ 311 | getLabel(label, cb) { 312 | return this._request( 313 | 'GET', 314 | `/repos/${this.__repository}/labels/${label}`, 315 | null, 316 | cb, 317 | ); 318 | } 319 | 320 | /** 321 | * Edit a label 322 | * @see https://developer.github.com/v3/issues/labels/#update-a-label 323 | * @param {string} label - the name of the label to edit 324 | * @param {Object} labelData - the updates to make to the label 325 | * @param {Requestable.callback} [cb] - will receive the updated label 326 | * @return {Promise} - the promise for the http request 327 | */ 328 | editLabel(label, labelData, cb) { 329 | return this._request( 330 | 'PATCH', 331 | `/repos/${this.__repository}/labels/${label}`, 332 | labelData, 333 | cb, 334 | ); 335 | } 336 | 337 | /** 338 | * Delete a label 339 | * @see https://developer.github.com/v3/issues/labels/#delete-a-label 340 | * @param {string} label - the name of the label to delete 341 | * @param {Requestable.callback} [cb] - will receive the status 342 | * @return {Promise} - the promise for the http request 343 | */ 344 | deleteLabel(label, cb) { 345 | return this._request( 346 | 'DELETE', 347 | `/repos/${this.__repository}/labels/${label}`, 348 | null, 349 | cb, 350 | ); 351 | } 352 | } 353 | 354 | export default Issue; 355 | -------------------------------------------------------------------------------- /src/GitHubAPI/Markdown.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | 10 | /** 11 | * Renders html from Markdown text 12 | */ 13 | class Markdown extends Requestable { 14 | /** 15 | * construct a Markdown 16 | * @param {Requestable.auth} auth - the credentials to authenticate to GitHub 17 | * @param {string} [apiBase] - the base Github API URL 18 | * @return {Promise} - the promise for the http request 19 | */ 20 | constructor(auth, apiBase) { 21 | super(auth, apiBase); 22 | } 23 | 24 | /** 25 | * Render html from Markdown text. 26 | * @see https://developer.github.com/v3/markdown/#render-an-arbitrary-markdown-document 27 | * @param {Object} options - conversion options 28 | * @param {string} [options.text] - the markdown text to convert 29 | * @param {string} [options.mode=markdown] - can be either `markdown` or `gfm` 30 | * @param {string} [options.context] - repository name if mode is gfm 31 | * @param {Requestable.callback} [cb] - will receive the converted html 32 | * @return {Promise} - the promise for the http request 33 | */ 34 | render(options, cb) { 35 | return this._request('POST', '/markdown', options, cb, true); 36 | } 37 | } 38 | 39 | export default Markdown; 40 | -------------------------------------------------------------------------------- /src/GitHubAPI/Organization.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | 10 | /** 11 | * Organization encapsulates the functionality to create repositories in organizations 12 | */ 13 | class Organization extends Requestable { 14 | /** 15 | * Create a new Organization 16 | * @param {string} organization - the name of the organization 17 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 18 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 19 | */ 20 | constructor(organization, auth, apiBase) { 21 | super(auth, apiBase); 22 | this.__name = organization; 23 | } 24 | 25 | /** 26 | * Create a repository in an organization 27 | * @see https://developer.github.com/v3/repos/#create 28 | * @param {Object} options - the repository definition 29 | * @param {Requestable.callback} [cb] - will receive the created repository 30 | * @return {Promise} - the promise for the http request 31 | */ 32 | createRepo(options, cb) { 33 | return this._request('POST', `/orgs/${this.__name}/repos`, options, cb); 34 | } 35 | 36 | /** 37 | * List the repositories in an organization 38 | * @see https://developer.github.com/v3/repos/#list-organization-repositories 39 | * @param {Requestable.callback} [cb] - will receive the list of repositories 40 | * @return {Promise} - the promise for the http request 41 | */ 42 | getRepos(cb) { 43 | let requestOptions = this._getOptionsWithDefaults({ 44 | direction: 'desc', 45 | }); 46 | 47 | return this._requestAllPages( 48 | `/orgs/${this.__name}/repos`, 49 | requestOptions, 50 | cb, 51 | ); 52 | } 53 | 54 | /** 55 | * Query if the user is a member or not 56 | * @param {string} username - the user in question 57 | * @param {Requestable.callback} [cb] - will receive true if the user is a member 58 | * @return {Promise} - the promise for the http request 59 | */ 60 | isMember(username, cb) { 61 | return this._request204or404( 62 | `/orgs/${this.__name}/members/${username}`, 63 | null, 64 | cb, 65 | ); 66 | } 67 | 68 | /** 69 | * List the users who are members of the company 70 | * @see https://developer.github.com/v3/orgs/members/#members-list 71 | * @param {object} options - filtering options 72 | * @param {string} [options.filter=all] - can be either `2fa_disabled` or `all` 73 | * @param {string} [options.role=all] - can be one of: `all`, `admin`, or `member` 74 | * @param {Requestable.callback} [cb] - will receive the list of users 75 | * @return {Promise} - the promise for the http request 76 | */ 77 | listMembers(options, cb) { 78 | return this._request('GET', `/orgs/${this.__name}/members`, options, cb); 79 | } 80 | 81 | /** 82 | * List the Teams in the Organization 83 | * @see https://developer.github.com/v3/orgs/teams/#list-teams 84 | * @param {Requestable.callback} [cb] - will receive the list of teams 85 | * @return {Promise} - the promise for the http request 86 | */ 87 | getTeams(cb) { 88 | return this._requestAllPages(`/orgs/${this.__name}/teams`, undefined, cb); 89 | } 90 | 91 | /** 92 | * Create a team 93 | * @see https://developer.github.com/v3/orgs/teams/#create-team 94 | * @param {object} options - Team creation parameters 95 | * @param {string} options.name - The name of the team 96 | * @param {string} [options.description] - Team description 97 | * @param {string} [options.repo_names] - Repos to add the team to 98 | * @param {string} [options.privacy=secret] - The level of privacy the team should have. Can be either one 99 | * of: `secret`, or `closed` 100 | * @param {Requestable.callback} [cb] - will receive the created team 101 | * @return {Promise} - the promise for the http request 102 | */ 103 | createTeam(options, cb) { 104 | return this._request('POST', `/orgs/${this.__name}/teams`, options, cb); 105 | } 106 | 107 | /** 108 | * Get information about all projects 109 | * @see https://developer.github.com/v3/projects/#list-organization-projects 110 | * @param {Requestable.callback} [cb] - will receive the list of projects 111 | * @return {Promise} - the promise for the http request 112 | */ 113 | listProjects(cb) { 114 | return this._requestAllPages( 115 | `/orgs/${this.__name}/projects`, 116 | { 117 | AcceptHeader: 'inertia-preview', 118 | }, 119 | cb, 120 | ); 121 | } 122 | 123 | /** 124 | * Create a new project 125 | * @see https://developer.github.com/v3/repos/projects/#create-a-project 126 | * @param {Object} options - the description of the project 127 | * @param {Requestable.callback} cb - will receive the newly created project 128 | * @return {Promise} - the promise for the http request 129 | */ 130 | createProject(options, cb) { 131 | options = options || {}; 132 | options.AcceptHeader = 'inertia-preview'; 133 | return this._request('POST', `/orgs/${this.__name}/projects`, options, cb); 134 | } 135 | } 136 | 137 | export default Organization; 138 | -------------------------------------------------------------------------------- /src/GitHubAPI/Project.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | 10 | /** 11 | * Project encapsulates the functionality to create, query, and modify cards and columns. 12 | */ 13 | class Project extends Requestable { 14 | /** 15 | * Create a Project. 16 | * @param {string} id - the id of the project 17 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 18 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 19 | */ 20 | constructor(id, auth, apiBase) { 21 | super(auth, apiBase, 'inertia-preview'); 22 | this.__id = id; 23 | } 24 | 25 | /** 26 | * Get information about a project 27 | * @see https://developer.github.com/v3/projects/#get-a-project 28 | * @param {Requestable.callback} cb - will receive the project information 29 | * @return {Promise} - the promise for the http request 30 | */ 31 | getProject(cb) { 32 | return this._request('GET', `/projects/${this.__id}`, null, cb); 33 | } 34 | 35 | /** 36 | * Edit a project 37 | * @see https://developer.github.com/v3/projects/#update-a-project 38 | * @param {Object} options - the description of the project 39 | * @param {Requestable.callback} cb - will receive the modified project 40 | * @return {Promise} - the promise for the http request 41 | */ 42 | updateProject(options, cb) { 43 | return this._request('PATCH', `/projects/${this.__id}`, options, cb); 44 | } 45 | 46 | /** 47 | * Delete a project 48 | * @see https://developer.github.com/v3/projects/#delete-a-project 49 | * @param {Requestable.callback} cb - will receive true if the operation is successful 50 | * @return {Promise} - the promise for the http request 51 | */ 52 | deleteProject(cb) { 53 | return this._request('DELETE', `/projects/${this.__id}`, null, cb); 54 | } 55 | 56 | /** 57 | * Get information about all columns of a project 58 | * @see https://developer.github.com/v3/projects/columns/#list-project-columns 59 | * @param {Requestable.callback} [cb] - will receive the list of columns 60 | * @return {Promise} - the promise for the http request 61 | */ 62 | listProjectColumns(cb) { 63 | return this._requestAllPages(`/projects/${this.__id}/columns`, null, cb); 64 | } 65 | 66 | /** 67 | * Get information about a column 68 | * @see https://developer.github.com/v3/projects/columns/#get-a-project-column 69 | * @param {string} colId - the id of the column 70 | * @param {Requestable.callback} cb - will receive the column information 71 | * @return {Promise} - the promise for the http request 72 | */ 73 | getProjectColumn(colId, cb) { 74 | return this._request('GET', `/projects/columns/${colId}`, null, cb); 75 | } 76 | 77 | /** 78 | * Create a new column 79 | * @see https://developer.github.com/v3/projects/columns/#create-a-project-column 80 | * @param {Object} options - the description of the column 81 | * @param {Requestable.callback} cb - will receive the newly created column 82 | * @return {Promise} - the promise for the http request 83 | */ 84 | createProjectColumn(options, cb) { 85 | return this._request('POST', `/projects/${this.__id}/columns`, options, cb); 86 | } 87 | 88 | /** 89 | * Edit a column 90 | * @see https://developer.github.com/v3/projects/columns/#update-a-project-column 91 | * @param {string} colId - the column id 92 | * @param {Object} options - the description of the column 93 | * @param {Requestable.callback} cb - will receive the modified column 94 | * @return {Promise} - the promise for the http request 95 | */ 96 | updateProjectColumn(colId, options, cb) { 97 | return this._request('PATCH', `/projects/columns/${colId}`, options, cb); 98 | } 99 | 100 | /** 101 | * Delete a column 102 | * @see https://developer.github.com/v3/projects/columns/#delete-a-project-column 103 | * @param {string} colId - the column to be deleted 104 | * @param {Requestable.callback} cb - will receive true if the operation is successful 105 | * @return {Promise} - the promise for the http request 106 | */ 107 | deleteProjectColumn(colId, cb) { 108 | return this._request('DELETE', `/projects/columns/${colId}`, null, cb); 109 | } 110 | 111 | /** 112 | * Move a column 113 | * @see https://developer.github.com/v3/projects/columns/#move-a-project-column 114 | * @param {string} colId - the column to be moved 115 | * @param {string} position - can be one of first, last, or after:, 116 | * where is the id value of a column in the same project. 117 | * @param {Requestable.callback} cb - will receive true if the operation is successful 118 | * @return {Promise} - the promise for the http request 119 | */ 120 | moveProjectColumn(colId, position, cb) { 121 | return this._request( 122 | 'POST', 123 | `/projects/columns/${colId}/moves`, 124 | { 125 | position: position, 126 | }, 127 | cb, 128 | ); 129 | } 130 | 131 | /** 132 | * Get information about all cards of a project 133 | * @see https://developer.github.com/v3/projects/cards/#list-project-cards 134 | * @param {Requestable.callback} [cb] - will receive the list of cards 135 | * @return {Promise} - the promise for the http request 136 | */ 137 | listProjectCards(cb) { 138 | return this.listProjectColumns() 139 | .then(({ data }) => { 140 | return Promise.all( 141 | data.map((column) => { 142 | return this._requestAllPages( 143 | `/projects/columns/${column.id}/cards`, 144 | null, 145 | ); 146 | }), 147 | ); 148 | }) 149 | .then((cardsInColumns) => { 150 | const cards = cardsInColumns.reduce((prev, { data }) => { 151 | prev.push(...data); 152 | return prev; 153 | }, []); 154 | if (cb) { 155 | cb(null, cards); 156 | } 157 | return cards; 158 | }) 159 | .catch((err) => { 160 | if (cb) { 161 | cb(err); 162 | return; 163 | } 164 | throw err; 165 | }); 166 | } 167 | 168 | /** 169 | * Get information about all cards of a column 170 | * @see https://developer.github.com/v3/projects/cards/#list-project-cards 171 | * @param {string} colId - the id of the column 172 | * @param {Requestable.callback} [cb] - will receive the list of cards 173 | * @return {Promise} - the promise for the http request 174 | */ 175 | listColumnCards(colId, cb) { 176 | return this._requestAllPages(`/projects/columns/${colId}/cards`, null, cb); 177 | } 178 | 179 | /** 180 | * Get information about a card 181 | * @see https://developer.github.com/v3/projects/cards/#get-a-project-card 182 | * @param {string} cardId - the id of the card 183 | * @param {Requestable.callback} cb - will receive the card information 184 | * @return {Promise} - the promise for the http request 185 | */ 186 | getProjectCard(cardId, cb) { 187 | return this._request('GET', `/projects/columns/cards/${cardId}`, null, cb); 188 | } 189 | 190 | /** 191 | * Create a new card 192 | * @see https://developer.github.com/v3/projects/cards/#create-a-project-card 193 | * @param {string} colId - the column id 194 | * @param {Object} options - the description of the card 195 | * @param {Requestable.callback} cb - will receive the newly created card 196 | * @return {Promise} - the promise for the http request 197 | */ 198 | createProjectCard(colId, options, cb) { 199 | return this._request( 200 | 'POST', 201 | `/projects/columns/${colId}/cards`, 202 | options, 203 | cb, 204 | ); 205 | } 206 | 207 | /** 208 | * Edit a card 209 | * @see https://developer.github.com/v3/projects/cards/#update-a-project-card 210 | * @param {string} cardId - the card id 211 | * @param {Object} options - the description of the card 212 | * @param {Requestable.callback} cb - will receive the modified card 213 | * @return {Promise} - the promise for the http request 214 | */ 215 | updateProjectCard(cardId, options, cb) { 216 | return this._request( 217 | 'PATCH', 218 | `/projects/columns/cards/${cardId}`, 219 | options, 220 | cb, 221 | ); 222 | } 223 | 224 | /** 225 | * Delete a card 226 | * @see https://developer.github.com/v3/projects/cards/#delete-a-project-card 227 | * @param {string} cardId - the card to be deleted 228 | * @param {Requestable.callback} cb - will receive true if the operation is successful 229 | * @return {Promise} - the promise for the http request 230 | */ 231 | deleteProjectCard(cardId, cb) { 232 | return this._request( 233 | 'DELETE', 234 | `/projects/columns/cards/${cardId}`, 235 | null, 236 | cb, 237 | ); 238 | } 239 | 240 | /** 241 | * Move a card 242 | * @see https://developer.github.com/v3/projects/cards/#move-a-project-card 243 | * @param {string} cardId - the card to be moved 244 | * @param {string} position - can be one of top, bottom, or after:, 245 | * where is the id value of a card in the same project. 246 | * @param {string} colId - the id value of a column in the same project. 247 | * @param {Requestable.callback} cb - will receive true if the operation is successful 248 | * @return {Promise} - the promise for the http request 249 | */ 250 | moveProjectCard(cardId, position, colId, cb) { 251 | return this._request( 252 | 'POST', 253 | `/projects/columns/cards/${cardId}/moves`, 254 | { 255 | position: position, 256 | column_id: colId, 257 | }, // eslint-disable-line camelcase 258 | cb, 259 | ); 260 | } 261 | } 262 | 263 | export default Project; 264 | -------------------------------------------------------------------------------- /src/GitHubAPI/RateLimit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | 10 | /** 11 | * RateLimit allows users to query their rate-limit status 12 | */ 13 | class RateLimit extends Requestable { 14 | /** 15 | * construct a RateLimit 16 | * @param {Requestable.auth} auth - the credentials to authenticate to GitHub 17 | * @param {string} [apiBase] - the base Github API URL 18 | * @return {Promise} - the promise for the http request 19 | */ 20 | constructor(auth, apiBase) { 21 | super(auth, apiBase); 22 | } 23 | 24 | /** 25 | * Query the current rate limit 26 | * @see https://developer.github.com/v3/rate_limit/ 27 | * @param {Requestable.callback} [cb] - will receive the rate-limit data 28 | * @return {Promise} - the promise for the http request 29 | */ 30 | getRateLimit(cb) { 31 | return this._request('GET', '/rate_limit', null, cb); 32 | } 33 | } 34 | 35 | export default RateLimit; 36 | -------------------------------------------------------------------------------- /src/GitHubAPI/Repository.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | import Utf8 from 'utf8'; 10 | import { Base64 } from 'js-base64'; 11 | import debug from 'debug'; 12 | const log = debug('github:repository'); 13 | 14 | /** 15 | * Repository encapsulates the functionality to create, query, and modify files. 16 | */ 17 | class Repository extends Requestable { 18 | /** 19 | * Create a Repository. 20 | * @param {string} fullname - the full name of the repository 21 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 22 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 23 | */ 24 | constructor(fullname, auth, apiBase) { 25 | super(auth, apiBase); 26 | this.__fullname = fullname; 27 | this.__currentTree = { 28 | branch: null, 29 | sha: null, 30 | }; 31 | } 32 | 33 | /** 34 | * Get a reference 35 | * @see https://developer.github.com/v3/git/refs/#get-a-reference 36 | * @param {string} ref - the reference to get 37 | * @param {Requestable.callback} [cb] - will receive the reference's refSpec or a list of refSpecs that match `ref` 38 | * @return {Promise} - the promise for the http request 39 | */ 40 | getRef(ref, cb) { 41 | return this._request( 42 | 'GET', 43 | `/repos/${this.__fullname}/git/refs/${ref}`, 44 | null, 45 | cb, 46 | ); 47 | } 48 | 49 | /** 50 | * Create a reference 51 | * @see https://developer.github.com/v3/git/refs/#create-a-reference 52 | * @param {Object} options - the object describing the ref 53 | * @param {Requestable.callback} [cb] - will receive the ref 54 | * @return {Promise} - the promise for the http request 55 | */ 56 | createRef(options, cb) { 57 | return this._request( 58 | 'POST', 59 | `/repos/${this.__fullname}/git/refs`, 60 | options, 61 | cb, 62 | ); 63 | } 64 | 65 | /** 66 | * Delete a reference 67 | * @see https://developer.github.com/v3/git/refs/#delete-a-reference 68 | * @param {string} ref - the name of the ref to delte 69 | * @param {Requestable.callback} [cb] - will receive true if the request is successful 70 | * @return {Promise} - the promise for the http request 71 | */ 72 | deleteRef(ref, cb) { 73 | return this._request( 74 | 'DELETE', 75 | `/repos/${this.__fullname}/git/refs/${ref}`, 76 | null, 77 | cb, 78 | ); 79 | } 80 | 81 | /** 82 | * Delete a repository 83 | * @see https://developer.github.com/v3/repos/#delete-a-repository 84 | * @param {Requestable.callback} [cb] - will receive true if the request is successful 85 | * @return {Promise} - the promise for the http request 86 | */ 87 | deleteRepo(cb) { 88 | return this._request('DELETE', `/repos/${this.__fullname}`, null, cb); 89 | } 90 | 91 | /** 92 | * List the tags on a repository 93 | * @see https://developer.github.com/v3/repos/#list-tags 94 | * @param {Requestable.callback} [cb] - will receive the tag data 95 | * @return {Promise} - the promise for the http request 96 | */ 97 | listTags(cb) { 98 | return this._request('GET', `/repos/${this.__fullname}/tags`, null, cb); 99 | } 100 | 101 | /** 102 | * List the open pull requests on the repository 103 | * @see https://developer.github.com/v3/pulls/#list-pull-requests 104 | * @param {Object} options - options to filter the search 105 | * @param {Requestable.callback} [cb] - will receive the list of PRs 106 | * @return {Promise} - the promise for the http request 107 | */ 108 | listPullRequests(options, cb) { 109 | options = options || {}; 110 | return this._request('GET', `/repos/${this.__fullname}/pulls`, options, cb); 111 | } 112 | 113 | /** 114 | * Get information about a specific pull request 115 | * @see https://developer.github.com/v3/pulls/#get-a-single-pull-request 116 | * @param {number} number - the PR you wish to fetch 117 | * @param {Requestable.callback} [cb] - will receive the PR from the API 118 | * @return {Promise} - the promise for the http request 119 | */ 120 | getPullRequest(number, cb) { 121 | return this._request( 122 | 'GET', 123 | `/repos/${this.__fullname}/pulls/${number}`, 124 | null, 125 | cb, 126 | ); 127 | } 128 | 129 | /** 130 | * List the files of a specific pull request 131 | * @see https://developer.github.com/v3/pulls/#list-pull-requests-files 132 | * @param {number|string} number - the PR you wish to fetch 133 | * @param {Requestable.callback} [cb] - will receive the list of files from the API 134 | * @return {Promise} - the promise for the http request 135 | */ 136 | listPullRequestFiles(number, cb) { 137 | return this._request( 138 | 'GET', 139 | `/repos/${this.__fullname}/pulls/${number}/files`, 140 | null, 141 | cb, 142 | ); 143 | } 144 | 145 | /** 146 | * Compare two branches/commits/repositories 147 | * @see https://developer.github.com/v3/repos/commits/#compare-two-commits 148 | * @param {string} base - the base commit 149 | * @param {string} head - the head commit 150 | * @param {Requestable.callback} cb - will receive the comparison 151 | * @return {Promise} - the promise for the http request 152 | */ 153 | compareBranches(base, head, cb) { 154 | return this._request( 155 | 'GET', 156 | `/repos/${this.__fullname}/compare/${base}...${head}`, 157 | null, 158 | cb, 159 | ); 160 | } 161 | 162 | /** 163 | * List all the branches for the repository 164 | * @see https://developer.github.com/v3/repos/#list-branches 165 | * @param {Requestable.callback} cb - will receive the list of branches 166 | * @return {Promise} - the promise for the http request 167 | */ 168 | listBranches(cb) { 169 | return this._request('GET', `/repos/${this.__fullname}/branches`, { per_page: 100 }, cb); 170 | } 171 | 172 | /** 173 | * Get a raw blob from the repository 174 | * @see https://developer.github.com/v3/git/blobs/#get-a-blob 175 | * @param {string} sha - the sha of the blob to fetch 176 | * @param {Requestable.callback} cb - will receive the blob from the API 177 | * @return {Promise} - the promise for the http request 178 | */ 179 | getBlob(sha, cb) { 180 | let resType = 'raw'; 181 | if (typeof cb === 'string') resType = cb; 182 | return this._request( 183 | 'GET', 184 | `/repos/${this.__fullname}/git/blobs/${sha}`, 185 | null, 186 | cb, 187 | resType, 188 | ); 189 | } 190 | 191 | /** 192 | * Get a single branch 193 | * @see https://developer.github.com/v3/repos/branches/#get-branch 194 | * @param {string} branch - the name of the branch to fetch 195 | * @param {Requestable.callback} cb - will receive the branch from the API 196 | * @returns {Promise} - the promise for the http request 197 | */ 198 | getBranch(branch, cb) { 199 | return this._request( 200 | 'GET', 201 | `/repos/${this.__fullname}/branches/${branch}`, 202 | null, 203 | cb, 204 | ); 205 | } 206 | 207 | /** 208 | * Get a commit from the repository 209 | * @see https://developer.github.com/v3/repos/commits/#get-a-single-commit 210 | * @param {string} sha - the sha for the commit to fetch 211 | * @param {Requestable.callback} cb - will receive the commit data 212 | * @return {Promise} - the promise for the http request 213 | */ 214 | getCommit(sha, cb) { 215 | return this._request( 216 | 'GET', 217 | `/repos/${this.__fullname}/git/commits/${sha}`, 218 | null, 219 | cb, 220 | ); 221 | } 222 | 223 | /** 224 | * List the commits on a repository, optionally filtering by path, author or time range 225 | * @see https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository 226 | * @param {Object} [options] - the filtering options for commits 227 | * @param {string} [options.sha] - the SHA or branch to start from 228 | * @param {string} [options.path] - the path to search on 229 | * @param {string} [options.author] - the commit author 230 | * @param {(Date|string)} [options.since] - only commits after this date will be returned 231 | * @param {(Date|string)} [options.until] - only commits before this date will be returned 232 | * @param {Requestable.callback} cb - will receive the list of commits found matching the criteria 233 | * @return {Promise} - the promise for the http request 234 | */ 235 | listCommits(options, cb) { 236 | options = options || {}; 237 | if (typeof options === 'function') { 238 | cb = options; 239 | options = {}; 240 | } 241 | options.since = this._dateToISO(options.since); 242 | options.until = this._dateToISO(options.until); 243 | 244 | return this._request( 245 | 'GET', 246 | `/repos/${this.__fullname}/commits`, 247 | options, 248 | cb, 249 | ); 250 | } 251 | 252 | /** 253 | * Gets a single commit information for a repository 254 | * @see https://developer.github.com/v3/repos/commits/#get-a-single-commit 255 | * @param {string} ref - the reference for the commit-ish 256 | * @param {Requestable.callback} cb - will receive the commit information 257 | * @return {Promise} - the promise for the http request 258 | */ 259 | getSingleCommit(ref, cb) { 260 | ref = ref || ''; 261 | return this._request( 262 | 'GET', 263 | `/repos/${this.__fullname}/commits/${ref}`, 264 | null, 265 | cb, 266 | ); 267 | } 268 | 269 | /** 270 | * Get tha sha for a particular object in the repository. This is a convenience function 271 | * @see https://developer.github.com/v3/repos/contents/#get-contents 272 | * @param {string} [branch] - the branch to look in, or the repository's default branch if omitted 273 | * @param {string} path - the path of the file or directory 274 | * @param {Requestable.callback} cb - will receive a description of the requested object, including a `SHA` property 275 | * @return {Promise} - the promise for the http request 276 | */ 277 | getSha(branch, path, cb) { 278 | branch = branch ? `?ref=${branch}` : ''; 279 | return this._request( 280 | 'GET', 281 | `/repos/${this.__fullname}/contents/${path}${branch}`, 282 | null, 283 | cb, 284 | ); 285 | } 286 | 287 | /** 288 | * List the commit statuses for a particular sha, branch, or tag 289 | * @see https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref 290 | * @param {string} sha - the sha, branch, or tag to get statuses for 291 | * @param {Requestable.callback} cb - will receive the list of statuses 292 | * @return {Promise} - the promise for the http request 293 | */ 294 | listStatuses(sha, cb) { 295 | return this._request( 296 | 'GET', 297 | `/repos/${this.__fullname}/commits/${sha}/statuses`, 298 | null, 299 | cb, 300 | ); 301 | } 302 | 303 | /** 304 | * Get the combined view of commit statuses for a particular sha, branch, or tag 305 | * @see https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref 306 | * @param {string} sha - the sha, branch, or tag to get the combined status for 307 | * @param {Requestable.callback} cb - will receive the combined status 308 | * @returns {Promise} - the promise for the http request 309 | */ 310 | getCombinedStatus(sha, cb) { 311 | return this._request( 312 | 'GET', 313 | `/repos/${this.__fullname}/commits/${sha}/status`, 314 | null, 315 | cb, 316 | ); 317 | } 318 | 319 | /** 320 | * Get a description of a git tree 321 | * @see https://developer.github.com/v3/git/trees/#get-a-tree 322 | * @param {string} treeSHA - the SHA of the tree to fetch 323 | * @param {Requestable.callback} cb - will receive the callback data 324 | * @return {Promise} - the promise for the http request 325 | */ 326 | getTree(treeSHA, cb) { 327 | return this._request( 328 | 'GET', 329 | `/repos/${this.__fullname}/git/trees/${treeSHA}`, 330 | null, 331 | cb, 332 | ); 333 | } 334 | 335 | /** 336 | * Create a blob 337 | * @see https://developer.github.com/v3/git/blobs/#create-a-blob 338 | * @param {(string|Buffer|Blob)} content - the content to add to the repository 339 | * @param {Requestable.callback} cb - will receive the details of the created blob 340 | * @return {Promise} - the promise for the http request 341 | */ 342 | createBlob(content, cb) { 343 | let postBody = this._getContentObject(content); 344 | 345 | log('sending content', postBody); 346 | return this._request( 347 | 'POST', 348 | `/repos/${this.__fullname}/git/blobs`, 349 | postBody, 350 | cb, 351 | ); 352 | } 353 | 354 | /** 355 | * Get the object that represents the provided content 356 | * @param {string|Buffer|Blob} content - the content to send to the server 357 | * @return {Object} the representation of `content` for the GitHub API 358 | */ 359 | _getContentObject(content) { 360 | if (typeof content === 'string') { 361 | log('contet is a string'); 362 | return { 363 | content: Utf8.encode(content), 364 | encoding: 'utf-8', 365 | }; 366 | } else if (typeof Buffer !== 'undefined' && content instanceof Buffer) { 367 | log('We appear to be in Node'); 368 | return { 369 | content: content.toString('base64'), 370 | encoding: 'base64', 371 | }; 372 | } else if (typeof Blob !== 'undefined' && content instanceof Blob) { 373 | log('We appear to be in the browser'); 374 | return { 375 | content: Base64.encode(content), 376 | encoding: 'base64', 377 | }; 378 | } else { 379 | // eslint-disable-line 380 | log( 381 | `Not sure what this content is: ${typeof content}, ${JSON.stringify( 382 | content, 383 | )}`, 384 | ); 385 | throw new Error( 386 | 'Unknown content passed to postBlob. Must be string or Buffer (node) or Blob (web)', 387 | ); 388 | } 389 | } 390 | 391 | /** 392 | * Update a tree in Git 393 | * @see https://developer.github.com/v3/git/trees/#create-a-tree 394 | * @param {string} baseTreeSHA - the SHA of the tree to update 395 | * @param {string} path - the path for the new file 396 | * @param {string} blobSHA - the SHA for the blob to put at `path` 397 | * @param {Requestable.callback} cb - will receive the new tree that is created 398 | * @return {Promise} - the promise for the http request 399 | * @deprecated use {@link Repository#createTree} instead 400 | */ 401 | updateTree(baseTreeSHA, path, blobSHA, cb) { 402 | let newTree = { 403 | base_tree: baseTreeSHA, // eslint-disable-line 404 | tree: [ 405 | { 406 | path: path, 407 | sha: blobSHA, 408 | mode: '100644', 409 | type: 'blob', 410 | }, 411 | ], 412 | }; 413 | 414 | return this._request( 415 | 'POST', 416 | `/repos/${this.__fullname}/git/trees`, 417 | newTree, 418 | cb, 419 | ); 420 | } 421 | 422 | /** 423 | * Create a new tree in git 424 | * @see https://developer.github.com/v3/git/trees/#create-a-tree 425 | * @param {Object} tree - the tree to create 426 | * @param {string} baseSHA - the root sha of the tree 427 | * @param {Requestable.callback} cb - will receive the new tree that is created 428 | * @return {Promise} - the promise for the http request 429 | */ 430 | createTree(tree, baseSHA, cb) { 431 | return this._request( 432 | 'POST', 433 | `/repos/${this.__fullname}/git/trees`, 434 | { 435 | tree, 436 | base_tree: baseSHA, // eslint-disable-line camelcase 437 | }, 438 | cb, 439 | ); 440 | } 441 | 442 | /** 443 | * Add a commit to the repository 444 | * @see https://developer.github.com/v3/git/commits/#create-a-commit 445 | * @param {string} parent - the SHA of the parent commit 446 | * @param {string} tree - the SHA of the tree for this commit 447 | * @param {string} message - the commit message 448 | * @param {Object} [options] - commit options 449 | * @param {Object} [options.author] - the author of the commit 450 | * @param {Object} [options.commiter] - the committer 451 | * @param {Requestable.callback} cb - will receive the commit that is created 452 | * @return {Promise} - the promise for the http request 453 | */ 454 | commit(parent, tree, message, options, cb) { 455 | if (typeof options === 'function') { 456 | cb = options; 457 | options = {}; 458 | } 459 | 460 | let data = { 461 | message, 462 | tree, 463 | parents: [parent], 464 | }; 465 | 466 | data = Object.assign({}, options, data); 467 | 468 | return this._request( 469 | 'POST', 470 | `/repos/${this.__fullname}/git/commits`, 471 | data, 472 | cb, 473 | ).then((response) => { 474 | this.__currentTree.sha = response.data.sha; // Update latest commit 475 | return response; 476 | }); 477 | } 478 | 479 | /** 480 | * Update a ref 481 | * @see https://developer.github.com/v3/git/refs/#update-a-reference 482 | * @param {string} ref - the ref to update 483 | * @param {string} commitSHA - the SHA to point the reference to 484 | * @param {boolean} force - indicates whether to force or ensure a fast-forward update 485 | * @param {Requestable.callback} cb - will receive the updated ref back 486 | * @return {Promise} - the promise for the http request 487 | */ 488 | updateHead(ref, commitSHA, force, cb) { 489 | return this._request( 490 | 'PATCH', 491 | `/repos/${this.__fullname}/git/refs/${ref}`, 492 | { 493 | sha: commitSHA, 494 | force: force, 495 | }, 496 | cb, 497 | ); 498 | } 499 | 500 | /** 501 | * Update commit status 502 | * @see https://developer.github.com/v3/repos/statuses/ 503 | * @param {string} commitSHA - the SHA of the commit that should be updated 504 | * @param {object} options - Commit status parameters 505 | * @param {string} options.state - The state of the status. Can be one of: pending, success, error, or failure. 506 | * @param {string} [options.target_url] - The target URL to associate with this status. 507 | * @param {string} [options.description] - A short description of the status. 508 | * @param {string} [options.context] - A string label to differentiate this status among CI systems. 509 | * @param {Requestable.callback} cb - will receive the updated commit back 510 | * @return {Promise} - the promise for the http request 511 | */ 512 | updateStatus(commitSHA, options, cb) { 513 | return this._request( 514 | 'POST', 515 | `/repos/${this.__fullname}/statuses/${commitSHA}`, 516 | options, 517 | cb, 518 | ); 519 | } 520 | 521 | /** 522 | * Update repository information 523 | * @see https://developer.github.com/v3/repos/#edit 524 | * @param {object} options - New parameters that will be set to the repository 525 | * @param {string} options.name - Name of the repository 526 | * @param {string} [options.description] - A short description of the repository 527 | * @param {string} [options.homepage] - A URL with more information about the repository 528 | * @param {boolean} [options.private] - Either true to make the repository private, or false to make it public. 529 | * @param {boolean} [options.has_issues] - Either true to enable issues for this repository, false to disable them. 530 | * @param {boolean} [options.has_wiki] - Either true to enable the wiki for this repository, false to disable it. 531 | * @param {boolean} [options.has_downloads] - Either true to enable downloads, false to disable them. 532 | * @param {string} [options.default_branch] - Updates the default branch for this repository. 533 | * @param {Requestable.callback} cb - will receive the updated repository back 534 | * @return {Promise} - the promise for the http request 535 | */ 536 | updateRepository(options, cb) { 537 | return this._request('PATCH', `/repos/${this.__fullname}`, options, cb); 538 | } 539 | 540 | /** 541 | * Get information about the repository 542 | * @see https://developer.github.com/v3/repos/#get 543 | * @param {Requestable.callback} cb - will receive the information about the repository 544 | * @return {Promise} - the promise for the http request 545 | */ 546 | getDetails(cb) { 547 | return this._request('GET', `/repos/${this.__fullname}`, null, cb); 548 | } 549 | 550 | /** 551 | * List the contributors to the repository 552 | * @see https://developer.github.com/v3/repos/#list-contributors 553 | * @param {Requestable.callback} cb - will receive the list of contributors 554 | * @return {Promise} - the promise for the http request 555 | */ 556 | getContributors(cb) { 557 | return this._request( 558 | 'GET', 559 | `/repos/${this.__fullname}/contributors`, 560 | null, 561 | cb, 562 | ); 563 | } 564 | 565 | /** 566 | * List the contributor stats to the repository 567 | * @see https://developer.github.com/v3/repos/#list-contributors 568 | * @param {Requestable.callback} cb - will receive the list of contributors 569 | * @return {Promise} - the promise for the http request 570 | */ 571 | getContributorStats(cb) { 572 | return this._request( 573 | 'GET', 574 | `/repos/${this.__fullname}/stats/contributors`, 575 | null, 576 | cb, 577 | ); 578 | } 579 | 580 | /** 581 | * List the users who are collaborators on the repository. The currently authenticated user must have 582 | * push access to use this method 583 | * @see https://developer.github.com/v3/repos/collaborators/#list-collaborators 584 | * @param {Requestable.callback} cb - will receive the list of collaborators 585 | * @return {Promise} - the promise for the http request 586 | */ 587 | getCollaborators(cb) { 588 | return this._request( 589 | 'GET', 590 | `/repos/${this.__fullname}/collaborators`, 591 | null, 592 | cb, 593 | ); 594 | } 595 | 596 | /** 597 | * Check if a user is a collaborator on the repository 598 | * @see https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-collaborator 599 | * @param {string} username - the user to check 600 | * @param {Requestable.callback} cb - will receive true if the user is a collaborator and false if they are not 601 | * @return {Promise} - the promise for the http request {Boolean} [description] 602 | */ 603 | isCollaborator(username, cb) { 604 | return this._request( 605 | 'GET', 606 | `/repos/${this.__fullname}/collaborators/${username}`, 607 | null, 608 | cb, 609 | ); 610 | } 611 | 612 | /** 613 | * Get the contents of a repository 614 | * @see https://developer.github.com/v3/repos/contents/#get-contents 615 | * @param {string} ref - the ref to check 616 | * @param {string} path - the path containing the content to fetch 617 | * @param {boolean} raw - `true` if the results should be returned raw instead of GitHub's normalized format 618 | * @param {Requestable.callback} cb - will receive the fetched data 619 | * @return {Promise} - the promise for the http request 620 | */ 621 | getContents(ref, path, raw, cb) { 622 | path = path ? `${encodeURI(path)}` : ''; 623 | return this._request( 624 | 'GET', 625 | `/repos/${this.__fullname}/contents/${path}`, 626 | { 627 | ref, 628 | }, 629 | cb, 630 | raw, 631 | ); 632 | } 633 | 634 | /** 635 | * Get the README of a repository 636 | * @see https://developer.github.com/v3/repos/contents/#get-the-readme 637 | * @param {string} ref - the ref to check 638 | * @param {boolean} raw - `true` if the results should be returned raw instead of GitHub's normalized format 639 | * @param {Requestable.callback} cb - will receive the fetched data 640 | * @return {Promise} - the promise for the http request 641 | */ 642 | getReadme(ref, raw, cb) { 643 | return this._request( 644 | 'GET', 645 | `/repos/${this.__fullname}/readme`, 646 | { 647 | ref, 648 | }, 649 | cb, 650 | raw, 651 | ); 652 | } 653 | 654 | /** 655 | * Fork a repository 656 | * @see https://developer.github.com/v3/repos/forks/#create-a-fork 657 | * @param {Requestable.callback} cb - will receive the information about the newly created fork 658 | * @return {Promise} - the promise for the http request 659 | */ 660 | fork(cb) { 661 | return this._request('POST', `/repos/${this.__fullname}/forks`, null, cb); 662 | } 663 | 664 | /** 665 | * Fork a repository to an organization 666 | * @see https://developer.github.com/v3/repos/forks/#create-a-fork 667 | * @param {String} org - organization where you'd like to create the fork. 668 | * @param {Requestable.callback} cb - will receive the information about the newly created fork 669 | * @return {Promise} - the promise for the http request 670 | * 671 | */ 672 | forkToOrg(org, cb) { 673 | return this._request( 674 | 'POST', 675 | `/repos/${this.__fullname}/forks?organization=${org}`, 676 | null, 677 | cb, 678 | ); 679 | } 680 | 681 | /** 682 | * List a repository's forks 683 | * @see https://developer.github.com/v3/repos/forks/#list-forks 684 | * @param {Requestable.callback} cb - will receive the list of repositories forked from this one 685 | * @return {Promise} - the promise for the http request 686 | */ 687 | listForks(cb) { 688 | return this._request('GET', `/repos/${this.__fullname}/forks`, null, cb); 689 | } 690 | 691 | /** 692 | * Create a new branch from an existing branch. 693 | * @param {string} [oldBranch=master] - the name of the existing branch 694 | * @param {string} newBranch - the name of the new branch 695 | * @param {Requestable.callback} cb - will receive the commit data for the head of the new branch 696 | * @return {Promise} - the promise for the http request 697 | */ 698 | createBranch(oldBranch, newBranch, cb) { 699 | if (typeof newBranch === 'function') { 700 | cb = newBranch; 701 | newBranch = oldBranch; 702 | oldBranch = 'master'; 703 | } 704 | 705 | return this.getRef(`heads/${oldBranch}`).then((response) => { 706 | let sha = response.data.object.sha; 707 | return this.createRef( 708 | { 709 | sha, 710 | ref: `refs/heads/${newBranch}`, 711 | }, 712 | cb, 713 | ); 714 | }); 715 | } 716 | 717 | /** 718 | * Create a new pull request 719 | * @see https://developer.github.com/v3/pulls/#create-a-pull-request 720 | * @param {Object} options - the pull request description 721 | * @param {Requestable.callback} cb - will receive the new pull request 722 | * @return {Promise} - the promise for the http request 723 | */ 724 | createPullRequest(options, cb) { 725 | return this._request( 726 | 'POST', 727 | `/repos/${this.__fullname}/pulls`, 728 | options, 729 | cb, 730 | ); 731 | } 732 | 733 | /** 734 | * Update a pull request 735 | * @see https://developer.github.com/v3/pulls/#update-a-pull-request 736 | * @param {number|string} number - the number of the pull request to update 737 | * @param {Object} options - the pull request description 738 | * @param {Requestable.callback} [cb] - will receive the pull request information 739 | * @return {Promise} - the promise for the http request 740 | */ 741 | updatePullRequest(number, options, cb) { 742 | return this._request( 743 | 'PATCH', 744 | `/repos/${this.__fullname}/pulls/${number}`, 745 | options, 746 | cb, 747 | ); 748 | } 749 | 750 | /** 751 | * List the hooks for the repository 752 | * @see https://developer.github.com/v3/repos/hooks/#list-hooks 753 | * @param {Requestable.callback} cb - will receive the list of hooks 754 | * @return {Promise} - the promise for the http request 755 | */ 756 | listHooks(cb) { 757 | return this._request('GET', `/repos/${this.__fullname}/hooks`, null, cb); 758 | } 759 | 760 | /** 761 | * Get a hook for the repository 762 | * @see https://developer.github.com/v3/repos/hooks/#get-single-hook 763 | * @param {number} id - the id of the webook 764 | * @param {Requestable.callback} cb - will receive the details of the webook 765 | * @return {Promise} - the promise for the http request 766 | */ 767 | getHook(id, cb) { 768 | return this._request( 769 | 'GET', 770 | `/repos/${this.__fullname}/hooks/${id}`, 771 | null, 772 | cb, 773 | ); 774 | } 775 | 776 | /** 777 | * Add a new hook to the repository 778 | * @see https://developer.github.com/v3/repos/hooks/#create-a-hook 779 | * @param {Object} options - the configuration describing the new hook 780 | * @param {Requestable.callback} cb - will receive the new webhook 781 | * @return {Promise} - the promise for the http request 782 | */ 783 | createHook(options, cb) { 784 | return this._request( 785 | 'POST', 786 | `/repos/${this.__fullname}/hooks`, 787 | options, 788 | cb, 789 | ); 790 | } 791 | 792 | /** 793 | * Edit an existing webhook 794 | * @see https://developer.github.com/v3/repos/hooks/#edit-a-hook 795 | * @param {number} id - the id of the webhook 796 | * @param {Object} options - the new description of the webhook 797 | * @param {Requestable.callback} cb - will receive the updated webhook 798 | * @return {Promise} - the promise for the http request 799 | */ 800 | updateHook(id, options, cb) { 801 | return this._request( 802 | 'PATCH', 803 | `/repos/${this.__fullname}/hooks/${id}`, 804 | options, 805 | cb, 806 | ); 807 | } 808 | 809 | /** 810 | * Delete a webhook 811 | * @see https://developer.github.com/v3/repos/hooks/#delete-a-hook 812 | * @param {number} id - the id of the webhook to be deleted 813 | * @param {Requestable.callback} cb - will receive true if the call is successful 814 | * @return {Promise} - the promise for the http request 815 | */ 816 | deleteHook(id, cb) { 817 | return this._request( 818 | 'DELETE', 819 | `/repos/${this.__fullname}/hooks/${id}`, 820 | null, 821 | cb, 822 | ); 823 | } 824 | 825 | /** 826 | * List the deploy keys for the repository 827 | * @see https://developer.github.com/v3/repos/keys/#list-deploy-keys 828 | * @param {Requestable.callback} cb - will receive the list of deploy keys 829 | * @return {Promise} - the promise for the http request 830 | */ 831 | listKeys(cb) { 832 | return this._request('GET', `/repos/${this.__fullname}/keys`, null, cb); 833 | } 834 | 835 | /** 836 | * Get a deploy key for the repository 837 | * @see https://developer.github.com/v3/repos/keys/#get-a-deploy-key 838 | * @param {number} id - the id of the deploy key 839 | * @param {Requestable.callback} cb - will receive the details of the deploy key 840 | * @return {Promise} - the promise for the http request 841 | */ 842 | getKey(id, cb) { 843 | return this._request( 844 | 'GET', 845 | `/repos/${this.__fullname}/keys/${id}`, 846 | null, 847 | cb, 848 | ); 849 | } 850 | 851 | /** 852 | * Add a new deploy key to the repository 853 | * @see https://developer.github.com/v3/repos/keys/#add-a-new-deploy-key 854 | * @param {Object} options - the configuration describing the new deploy key 855 | * @param {Requestable.callback} cb - will receive the new deploy key 856 | * @return {Promise} - the promise for the http request 857 | */ 858 | createKey(options, cb) { 859 | return this._request('POST', `/repos/${this.__fullname}/keys`, options, cb); 860 | } 861 | 862 | /** 863 | * Delete a deploy key 864 | * @see https://developer.github.com/v3/repos/keys/#remove-a-deploy-key 865 | * @param {number} id - the id of the deploy key to be deleted 866 | * @param {Requestable.callback} cb - will receive true if the call is successful 867 | * @return {Promise} - the promise for the http request 868 | */ 869 | deleteKey(id, cb) { 870 | return this._request( 871 | 'DELETE', 872 | `/repos/${this.__fullname}/keys/${id}`, 873 | null, 874 | cb, 875 | ); 876 | } 877 | 878 | /** 879 | * Delete a file from a branch 880 | * @see https://developer.github.com/v3/repos/contents/#delete-a-file 881 | * @param {string} branch - the branch to delete from, or the default branch if not specified 882 | * @param {string} path - the path of the file to remove 883 | * @param {Requestable.callback} cb - will receive the commit in which the delete occurred 884 | * @return {Promise} - the promise for the http request 885 | */ 886 | deleteFile(branch, path, cb) { 887 | return this.getSha(branch, path).then((response) => { 888 | const deleteCommit = { 889 | message: `Delete the file at '${path}'`, 890 | sha: response.data.sha, 891 | branch, 892 | }; 893 | return this._request( 894 | 'DELETE', 895 | `/repos/${this.__fullname}/contents/${path}`, 896 | deleteCommit, 897 | cb, 898 | ); 899 | }); 900 | } 901 | 902 | /** 903 | * Change all references in a repo from oldPath to new_path 904 | * @param {string} branch - the branch to carry out the reference change, or the default branch if not specified 905 | * @param {string} oldPath - original path 906 | * @param {string} newPath - new reference path 907 | * @param {Requestable.callback} cb - will receive the commit in which the move occurred 908 | * @return {Promise} - the promise for the http request 909 | */ 910 | move(branch, oldPath, newPath, cb) { 911 | let oldSha; 912 | return this.getRef(`heads/${branch}`) 913 | .then(({ data: { object } }) => 914 | this.getTree(`${object.sha}?recursive=true`), 915 | ) 916 | .then(({ data: { tree, sha } }) => { 917 | oldSha = sha; 918 | let newTree = tree.map((ref) => { 919 | if (ref.path === oldPath) { 920 | ref.path = newPath; 921 | } 922 | if (ref.type === 'tree') { 923 | delete ref.sha; 924 | } 925 | return ref; 926 | }); 927 | return this.createTree(newTree); 928 | }) 929 | .then(({ data: tree }) => 930 | this.commit(oldSha, tree.sha, `Renamed '${oldPath}' to '${newPath}'`), 931 | ) 932 | .then(({ data: commit }) => 933 | this.updateHead(`heads/${branch}`, commit.sha, true, cb), 934 | ); 935 | } 936 | 937 | /** 938 | * Write a file to the repository 939 | * @see https://developer.github.com/v3/repos/contents/#update-a-file 940 | * @param {string} branch - the name of the branch 941 | * @param {string} path - the path for the file 942 | * @param {string} content - the contents of the file 943 | * @param {string} message - the commit message 944 | * @param {Object} [options] - commit options 945 | * @param {Object} [options.author] - the author of the commit 946 | * @param {Object} [options.commiter] - the committer 947 | * @param {boolean} [options.encode] - true if the content should be base64 encoded 948 | * @param {Requestable.callback} cb - will receive the new commit 949 | * @return {Promise} - the promise for the http request 950 | */ 951 | writeFile(branch, path, content, message, options, cb) { 952 | options = options || {}; 953 | if (typeof options === 'function') { 954 | cb = options; 955 | options = {}; 956 | } 957 | let filePath = path ? encodeURI(path) : ''; 958 | let shouldEncode = options.encode !== false; 959 | let commit = { 960 | branch, 961 | message, 962 | author: options.author, 963 | committer: options.committer, 964 | content: shouldEncode ? Base64.encode(content) : content, 965 | }; 966 | 967 | return this.getSha(branch, filePath).then( 968 | (response) => { 969 | commit.sha = response.data.sha; 970 | return this._request( 971 | 'PUT', 972 | `/repos/${this.__fullname}/contents/${filePath}`, 973 | commit, 974 | cb, 975 | ); 976 | }, 977 | () => { 978 | return this._request( 979 | 'PUT', 980 | `/repos/${this.__fullname}/contents/${filePath}`, 981 | commit, 982 | cb, 983 | ); 984 | }, 985 | ); 986 | } 987 | 988 | /** 989 | * Check if a repository is starred by you 990 | * @see https://developer.github.com/v3/activity/starring/#check-if-you-are-starring-a-repository 991 | * @param {Requestable.callback} cb - will receive true if the repository is starred and false if the repository 992 | * is not starred 993 | * @return {Promise} - the promise for the http request {Boolean} [description] 994 | */ 995 | isStarred(cb) { 996 | return this._request204or404(`/user/starred/${this.__fullname}`, null, cb); 997 | } 998 | 999 | /** 1000 | * Star a repository 1001 | * @see https://developer.github.com/v3/activity/starring/#star-a-repository 1002 | * @param {Requestable.callback} cb - will receive true if the repository is starred 1003 | * @return {Promise} - the promise for the http request 1004 | */ 1005 | star(cb) { 1006 | return this._request('PUT', `/user/starred/${this.__fullname}`, null, cb); 1007 | } 1008 | 1009 | /** 1010 | * Unstar a repository 1011 | * @see https://developer.github.com/v3/activity/starring/#unstar-a-repository 1012 | * @param {Requestable.callback} cb - will receive true if the repository is unstarred 1013 | * @return {Promise} - the promise for the http request 1014 | */ 1015 | unstar(cb) { 1016 | return this._request( 1017 | 'DELETE', 1018 | `/user/starred/${this.__fullname}`, 1019 | null, 1020 | cb, 1021 | ); 1022 | } 1023 | 1024 | /** 1025 | * Create a new release 1026 | * @see https://developer.github.com/v3/repos/releases/#create-a-release 1027 | * @param {Object} options - the description of the release 1028 | * @param {Requestable.callback} cb - will receive the newly created release 1029 | * @return {Promise} - the promise for the http request 1030 | */ 1031 | createRelease(options, cb) { 1032 | return this._request( 1033 | 'POST', 1034 | `/repos/${this.__fullname}/releases`, 1035 | options, 1036 | cb, 1037 | ); 1038 | } 1039 | 1040 | /** 1041 | * Edit a release 1042 | * @see https://developer.github.com/v3/repos/releases/#edit-a-release 1043 | * @param {string} id - the id of the release 1044 | * @param {Object} options - the description of the release 1045 | * @param {Requestable.callback} cb - will receive the modified release 1046 | * @return {Promise} - the promise for the http request 1047 | */ 1048 | updateRelease(id, options, cb) { 1049 | return this._request( 1050 | 'PATCH', 1051 | `/repos/${this.__fullname}/releases/${id}`, 1052 | options, 1053 | cb, 1054 | ); 1055 | } 1056 | 1057 | /** 1058 | * Get information about all releases 1059 | * @see https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository 1060 | * @param {Requestable.callback} cb - will receive the release information 1061 | * @return {Promise} - the promise for the http request 1062 | */ 1063 | listReleases(cb) { 1064 | return this._request('GET', `/repos/${this.__fullname}/releases`, null, cb); 1065 | } 1066 | 1067 | /** 1068 | * Get information about a release 1069 | * @see https://developer.github.com/v3/repos/releases/#get-a-single-release 1070 | * @param {string} id - the id of the release 1071 | * @param {Requestable.callback} cb - will receive the release information 1072 | * @return {Promise} - the promise for the http request 1073 | */ 1074 | getRelease(id, cb) { 1075 | return this._request( 1076 | 'GET', 1077 | `/repos/${this.__fullname}/releases/${id}`, 1078 | null, 1079 | cb, 1080 | ); 1081 | } 1082 | 1083 | /** 1084 | * Delete a release 1085 | * @see https://developer.github.com/v3/repos/releases/#delete-a-release 1086 | * @param {string} id - the release to be deleted 1087 | * @param {Requestable.callback} cb - will receive true if the operation is successful 1088 | * @return {Promise} - the promise for the http request 1089 | */ 1090 | deleteRelease(id, cb) { 1091 | return this._request( 1092 | 'DELETE', 1093 | `/repos/${this.__fullname}/releases/${id}`, 1094 | null, 1095 | cb, 1096 | ); 1097 | } 1098 | 1099 | /** 1100 | * Merge a pull request 1101 | * @see https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button 1102 | * @param {number|string} number - the number of the pull request to merge 1103 | * @param {Object} options - the merge options for the pull request 1104 | * @param {Requestable.callback} [cb] - will receive the merge information if the operation is successful 1105 | * @return {Promise} - the promise for the http request 1106 | */ 1107 | mergePullRequest(number, options, cb) { 1108 | return this._request( 1109 | 'PUT', 1110 | `/repos/${this.__fullname}/pulls/${number}/merge`, 1111 | options, 1112 | cb, 1113 | ); 1114 | } 1115 | 1116 | /** 1117 | * Get information about all projects 1118 | * @see https://developer.github.com/v3/projects/#list-repository-projects 1119 | * @param {Requestable.callback} [cb] - will receive the list of projects 1120 | * @return {Promise} - the promise for the http request 1121 | */ 1122 | listProjects(cb) { 1123 | return this._requestAllPages( 1124 | `/repos/${this.__fullname}/projects`, 1125 | { 1126 | AcceptHeader: 'inertia-preview', 1127 | }, 1128 | cb, 1129 | ); 1130 | } 1131 | 1132 | /** 1133 | * Create a new project 1134 | * @see https://developer.github.com/v3/projects/#create-a-repository-project 1135 | * @param {Object} options - the description of the project 1136 | * @param {Requestable.callback} cb - will receive the newly created project 1137 | * @return {Promise} - the promise for the http request 1138 | */ 1139 | createProject(options, cb) { 1140 | options = options || {}; 1141 | options.AcceptHeader = 'inertia-preview'; 1142 | return this._request( 1143 | 'POST', 1144 | `/repos/${this.__fullname}/projects`, 1145 | options, 1146 | cb, 1147 | ); 1148 | } 1149 | } 1150 | 1151 | export default Repository; 1152 | -------------------------------------------------------------------------------- /src/GitHubAPI/Requestable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import ajax from 'axios'; 9 | import debug from 'debug'; 10 | import { Base64 } from 'js-base64'; 11 | 12 | const log = debug('github:request'); 13 | const Url = acode.require('url'); 14 | 15 | /** 16 | * The error structure returned when a network call fails 17 | */ 18 | class ResponseError extends Error { 19 | /** 20 | * Construct a new ResponseError 21 | * @param {string} message - an message to return instead of the the default error message 22 | * @param {string} path - the requested path 23 | * @param {Object} response - the object returned by Axios 24 | */ 25 | constructor(message, path, response) { 26 | super(message); 27 | this.path = path; 28 | this.request = response.config; 29 | this.response = (response || {}).response || response; 30 | this.status = response.status; 31 | } 32 | } 33 | 34 | /** 35 | * Requestable wraps the logic for making http requests to the API 36 | */ 37 | class Requestable { 38 | /** 39 | * Either a username and password or an oauth token for Github 40 | * @typedef {Object} Requestable.auth 41 | * @prop {string} [username] - the Github username 42 | * @prop {string} [password] - the user's password 43 | * @prop {token} [token] - an OAuth token 44 | */ 45 | /** 46 | * Initialize the http internals. 47 | * @param {Requestable.auth} [auth] - the credentials to authenticate to Github. If auth is 48 | * not provided request will be made unauthenticated 49 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 50 | * @param {string} [AcceptHeader=v3] - the accept header for the requests 51 | */ 52 | constructor(auth, apiBase, AcceptHeader) { 53 | this.__apiBase = apiBase || 'https://api.github.com'; 54 | this.__auth = { 55 | token: auth.token, 56 | username: auth.username, 57 | password: auth.password, 58 | }; 59 | this.__AcceptHeader = AcceptHeader || 'v3'; 60 | 61 | if (auth.token) { 62 | this.__authorizationHeader = 'token ' + auth.token; 63 | } else if (auth.username && auth.password) { 64 | this.__authorizationHeader = 65 | 'Basic ' + Base64.encode(auth.username + ':' + auth.password); 66 | } 67 | } 68 | 69 | /** 70 | * Compute the URL to use to make a request. 71 | * @private 72 | * @param {string} path - either a URL relative to the API base or an absolute URL 73 | * @return {string} - the URL to use 74 | */ 75 | __getURL(path) { 76 | let url = path; 77 | 78 | if (path.indexOf('//') === -1) { 79 | url = this.__apiBase + path; 80 | } 81 | 82 | let newCacheBuster = 'timestamp=' + new Date().getTime(); 83 | return url.replace(/(timestamp=\d+)/, newCacheBuster); 84 | } 85 | 86 | /** 87 | * Compute the headers required for an API request. 88 | * @private 89 | * @param {boolean} raw - if the request should be treated as JSON or as a raw request 90 | * @param {string} AcceptHeader - the accept header for the request 91 | * @return {Object} - the headers to use in the request 92 | */ 93 | __getRequestHeaders(raw, AcceptHeader) { 94 | let headers = { 95 | 'Content-Type': 'application/json;charset=UTF-8', 96 | Accept: 'application/vnd.github.' + (AcceptHeader || this.__AcceptHeader), 97 | }; 98 | 99 | if (raw) { 100 | headers.Accept += '.raw'; 101 | } 102 | headers.Accept += '+json'; 103 | 104 | if (this.__authorizationHeader) { 105 | headers.Authorization = this.__authorizationHeader; 106 | } 107 | 108 | return headers; 109 | } 110 | 111 | /** 112 | * Sets the default options for API requests 113 | * @protected 114 | * @param {Object} [requestOptions={}] - the current options for the request 115 | * @return {Object} - the options to pass to the request 116 | */ 117 | _getOptionsWithDefaults(requestOptions = {}) { 118 | if (!(requestOptions.visibility || requestOptions.affiliation)) { 119 | requestOptions.type = requestOptions.type || 'all'; 120 | } 121 | requestOptions.sort = requestOptions.sort || 'updated'; 122 | requestOptions.per_page = requestOptions.per_page || '100'; // eslint-disable-line 123 | 124 | return requestOptions; 125 | } 126 | 127 | /** 128 | * if a `Date` is passed to this function it will be converted to an ISO string 129 | * @param {*} date - the object to attempt to coerce into an ISO date string 130 | * @return {string} - the ISO representation of `date` or whatever was passed in if it was not a date 131 | */ 132 | _dateToISO(date) { 133 | if (date && date instanceof Date) { 134 | date = date.toISOString(); 135 | } 136 | 137 | return date; 138 | } 139 | 140 | /** 141 | * A function that receives the result of the API request. 142 | * @callback Requestable.callback 143 | * @param {Requestable.Error} error - the error returned by the API or `null` 144 | * @param {(Object|true)} result - the data returned by the API or `true` if the API returns `204 No Content` 145 | * @param {Object} request - the raw {@linkcode https://github.com/mzabriskie/axios#response-schema Response} 146 | */ 147 | /** 148 | * Make a request. 149 | * @param {string} method - the method for the request (GET, PUT, POST, DELETE) 150 | * @param {string} path - the path for the request 151 | * @param {*} [data] - the data to send to the server. For HTTP methods that don't have a body the data 152 | * will be sent as query parameters 153 | * @param {Requestable.callback} [cb] - the callback for the request 154 | * @param {boolean} [raw=false] - if the request should be sent as raw. If this is a falsy value then the 155 | * request will be made as JSON 156 | * @return {Promise} - the Promise for the http request 157 | */ 158 | _request(method, path, data, cb, raw) { 159 | const url = this.__getURL(path); 160 | 161 | const AcceptHeader = (data || {}).AcceptHeader; 162 | if (AcceptHeader) { 163 | delete data.AcceptHeader; 164 | } 165 | const headers = this.__getRequestHeaders(raw, AcceptHeader); 166 | 167 | let queryParams = {}; 168 | 169 | const shouldUseDataAsParams = 170 | data && typeof data === 'object' && methodHasNoBody(method); 171 | if (shouldUseDataAsParams) { 172 | queryParams = data; 173 | data = undefined; 174 | } 175 | 176 | let newUrl = url; 177 | 178 | if (method === 'GET') { 179 | const { url: pureUrl, query = '' } = Url.parse(url); 180 | const dateTime = `_datetime=${new Date().getTime()}`; 181 | const newQuery = query ? `${query}&${dateTime}` : `?${dateTime}`; 182 | newUrl = `${pureUrl}${newQuery}`; 183 | } 184 | 185 | const config = { 186 | url: newUrl, 187 | method: method, 188 | headers: headers, 189 | params: queryParams, 190 | data: data, 191 | responseType: raw ? (raw === 'raw' ? 'text' : raw) : 'json', 192 | }; 193 | 194 | log(`${config.method} to ${config.url}`); 195 | const requestPromise = ajax(config).catch(callbackErrorOrThrow(cb, path)); 196 | 197 | if (typeof cb === 'function') { 198 | requestPromise.then((response) => { 199 | if (response.data && Object.keys(response.data).length > 0) { 200 | // When data has results 201 | cb(null, response.data, response); 202 | } else if ( 203 | config.method !== 'GET' && 204 | Object.keys(response.data).length < 1 205 | ) { 206 | // True when successful submit a request and receive a empty object 207 | cb(null, response.status < 300, response); 208 | } else { 209 | cb(null, response.data, response); 210 | } 211 | }); 212 | } 213 | 214 | return requestPromise; 215 | } 216 | 217 | /** 218 | * Make a request to an endpoint the returns 204 when true and 404 when false 219 | * @param {string} path - the path to request 220 | * @param {Object} data - any query parameters for the request 221 | * @param {Requestable.callback} cb - the callback that will receive `true` or `false` 222 | * @param {method} [method=GET] - HTTP Method to use 223 | * @return {Promise} - the promise for the http request 224 | */ 225 | _request204or404(path, data, cb, method = 'GET') { 226 | return this._request(method, path, data).then( 227 | function success(response) { 228 | if (cb) { 229 | cb(null, true, response); 230 | } 231 | return true; 232 | }, 233 | function failure(response) { 234 | if (response.response.status === 404) { 235 | if (cb) { 236 | cb(null, false, response); 237 | } 238 | return false; 239 | } 240 | 241 | if (cb) { 242 | cb(response); 243 | } 244 | throw response; 245 | }, 246 | ); 247 | } 248 | 249 | /** 250 | * Make a request and fetch all the available data. Github will paginate responses so for queries 251 | * that might span multiple pages this method is preferred to {@link Requestable#request} 252 | * @param {string} path - the path to request 253 | * @param {Object} options - the query parameters to include 254 | * @param {Requestable.callback} [cb] - the function to receive the data. The returned data will always be an array. 255 | * @param {Object[]} results - the partial results. This argument is intended for internal use only. 256 | * @return {Promise} - a promise which will resolve when all pages have been fetched 257 | * @deprecated This will be folded into {@link Requestable#_request} in the 2.0 release. 258 | */ 259 | _requestAllPages(path, options, cb, results) { 260 | results = results || []; 261 | 262 | return this._request('GET', path, options) 263 | .then((response) => { 264 | let thisGroup; 265 | if (response.data instanceof Array) { 266 | thisGroup = response.data; 267 | } else if (response.data.items instanceof Array) { 268 | thisGroup = response.data.items; 269 | } else { 270 | let message = `cannot figure out how to append ${response.data} to the result set`; 271 | throw new ResponseError(message, path, response); 272 | } 273 | results.push(...thisGroup); 274 | 275 | const nextUrl = getNextPage(response.headers.link); 276 | if (nextUrl) { 277 | if (!options) { 278 | options = {}; 279 | } 280 | options.page = parseInt( 281 | nextUrl 282 | .match(/([&\?]page=[0-9]*)/g) 283 | .shift() 284 | .split('=') 285 | .pop(), 286 | ); 287 | if (!(options && typeof options.page !== 'number')) { 288 | log(`getting next page: ${nextUrl}`); 289 | return this._requestAllPages(nextUrl, options, cb, results); 290 | } 291 | } 292 | 293 | if (cb) { 294 | cb(null, results, response); 295 | } 296 | 297 | response.data = results; 298 | return response; 299 | }) 300 | .catch(callbackErrorOrThrow(cb, path)); 301 | } 302 | } 303 | 304 | export default Requestable; 305 | 306 | // ////////////////////////// // 307 | // Private helper functions // 308 | // ////////////////////////// // 309 | const METHODS_WITH_NO_BODY = ['GET', 'HEAD', 'DELETE']; 310 | 311 | function methodHasNoBody(method) { 312 | return METHODS_WITH_NO_BODY.indexOf(method) !== -1; 313 | } 314 | 315 | function getNextPage(linksHeader = '') { 316 | const links = linksHeader.split(/\s*,\s*/); // splits and strips the urls 317 | return links.reduce(function (nextUrl, link) { 318 | if (link.search(/rel="next"/) !== -1) { 319 | return (link.match(/<(.*)>/) || [])[1]; 320 | } 321 | 322 | return nextUrl; 323 | }, undefined); 324 | } 325 | 326 | function callbackErrorOrThrow(cb, path) { 327 | return function handler(object) { 328 | let error; 329 | if (object.hasOwnProperty('config')) { 330 | const { 331 | response: { status, statusText }, 332 | config: { method, url }, 333 | } = object; 334 | let message = `${status} error making request ${method} ${url}: "${statusText}"`; 335 | error = new ResponseError(message, path, object); 336 | log(`${message} ${JSON.stringify(object.data)}`); 337 | } else { 338 | error = object; 339 | } 340 | if (cb) { 341 | log('going to error callback'); 342 | cb(error); 343 | } else { 344 | log('throwing error'); 345 | throw error; 346 | } 347 | }; 348 | } 349 | -------------------------------------------------------------------------------- /src/GitHubAPI/Search.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | import debug from 'debug'; 10 | const log = debug('github:search'); 11 | 12 | /** 13 | * Wrap the Search API 14 | */ 15 | class Search extends Requestable { 16 | /** 17 | * Create a Search 18 | * @param {Object} defaults - defaults for the search 19 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 20 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 21 | */ 22 | constructor(defaults, auth, apiBase) { 23 | super(auth, apiBase); 24 | this.__defaults = this._getOptionsWithDefaults(defaults); 25 | } 26 | 27 | /** 28 | * Available search options 29 | * @see https://developer.github.com/v3/search/#parameters 30 | * @typedef {Object} Search.Params 31 | * @param {string} q - the query to make 32 | * @param {string} sort - the sort field, one of `stars`, `forks`, or `updated`. 33 | * Default is [best match](https://developer.github.com/v3/search/#ranking-search-results) 34 | * @param {string} order - the ordering, either `asc` or `desc` 35 | */ 36 | /** 37 | * Perform a search on the GitHub API 38 | * @private 39 | * @param {string} path - the scope of the search 40 | * @param {Search.Params} [withOptions] - additional parameters for the search 41 | * @param {Requestable.callback} [cb] - will receive the results of the search 42 | * @return {Promise} - the promise for the http request 43 | */ 44 | _search(path, withOptions = {}, cb = undefined) { 45 | let requestOptions = {}; 46 | Object.keys(this.__defaults).forEach((prop) => { 47 | requestOptions[prop] = this.__defaults[prop]; 48 | }); 49 | Object.keys(withOptions).forEach((prop) => { 50 | requestOptions[prop] = withOptions[prop]; 51 | }); 52 | 53 | log(`searching ${path} with options:`, requestOptions); 54 | return this._requestAllPages(`/search/${path}`, requestOptions, cb); 55 | } 56 | 57 | /** 58 | * Search for repositories 59 | * @see https://developer.github.com/v3/search/#search-repositories 60 | * @param {Search.Params} [options] - additional parameters for the search 61 | * @param {Requestable.callback} [cb] - will receive the results of the search 62 | * @return {Promise} - the promise for the http request 63 | */ 64 | forRepositories(options, cb) { 65 | return this._search('repositories', options, cb); 66 | } 67 | 68 | /** 69 | * Search for code 70 | * @see https://developer.github.com/v3/search/#search-code 71 | * @param {Search.Params} [options] - additional parameters for the search 72 | * @param {Requestable.callback} [cb] - will receive the results of the search 73 | * @return {Promise} - the promise for the http request 74 | */ 75 | forCode(options, cb) { 76 | return this._search('code', options, cb); 77 | } 78 | 79 | /** 80 | * Search for issues 81 | * @see https://developer.github.com/v3/search/#search-issues 82 | * @param {Search.Params} [options] - additional parameters for the search 83 | * @param {Requestable.callback} [cb] - will receive the results of the search 84 | * @return {Promise} - the promise for the http request 85 | */ 86 | forIssues(options, cb) { 87 | return this._search('issues', options, cb); 88 | } 89 | 90 | /** 91 | * Search for users 92 | * @see https://developer.github.com/v3/search/#search-users 93 | * @param {Search.Params} [options] - additional parameters for the search 94 | * @param {Requestable.callback} [cb] - will receive the results of the search 95 | * @return {Promise} - the promise for the http request 96 | */ 97 | forUsers(options, cb) { 98 | return this._search('users', options, cb); 99 | } 100 | } 101 | 102 | export default Search; 103 | -------------------------------------------------------------------------------- /src/GitHubAPI/Team.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2016 Matt Smith (Development Seed) 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | import debug from 'debug'; 10 | const log = debug('github:team'); 11 | 12 | /** 13 | * A Team allows scoping of API requests to a particular Github Organization Team. 14 | */ 15 | class Team extends Requestable { 16 | /** 17 | * Create a Team. 18 | * @param {string} [teamId] - the id for the team 19 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 20 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 21 | */ 22 | constructor(teamId, auth, apiBase) { 23 | super(auth, apiBase); 24 | this.__teamId = teamId; 25 | } 26 | 27 | /** 28 | * Get Team information 29 | * @see https://developer.github.com/v3/orgs/teams/#get-team 30 | * @param {Requestable.callback} [cb] - will receive the team 31 | * @return {Promise} - the promise for the http request 32 | */ 33 | getTeam(cb) { 34 | log(`Fetching Team ${this.__teamId}`); 35 | return this._request('Get', `/teams/${this.__teamId}`, undefined, cb); 36 | } 37 | 38 | /** 39 | * List the Team's repositories 40 | * @see https://developer.github.com/v3/orgs/teams/#list-team-repos 41 | * @param {Requestable.callback} [cb] - will receive the list of repositories 42 | * @return {Promise} - the promise for the http request 43 | */ 44 | listRepos(cb) { 45 | log(`Fetching repositories for Team ${this.__teamId}`); 46 | return this._requestAllPages( 47 | `/teams/${this.__teamId}/repos`, 48 | undefined, 49 | cb, 50 | ); 51 | } 52 | 53 | /** 54 | * Edit Team information 55 | * @see https://developer.github.com/v3/orgs/teams/#edit-team 56 | * @param {object} options - Parameters for team edit 57 | * @param {string} options.name - The name of the team 58 | * @param {string} [options.description] - Team description 59 | * @param {string} [options.repo_names] - Repos to add the team to 60 | * @param {string} [options.privacy=secret] - The level of privacy the team should have. Can be either one 61 | * of: `secret`, or `closed` 62 | * @param {Requestable.callback} [cb] - will receive the updated team 63 | * @return {Promise} - the promise for the http request 64 | */ 65 | editTeam(options, cb) { 66 | log(`Editing Team ${this.__teamId}`); 67 | return this._request('PATCH', `/teams/${this.__teamId}`, options, cb); 68 | } 69 | 70 | /** 71 | * List the users who are members of the Team 72 | * @see https://developer.github.com/v3/orgs/teams/#list-team-members 73 | * @param {object} options - Parameters for listing team users 74 | * @param {string} [options.role=all] - can be one of: `all`, `maintainer`, or `member` 75 | * @param {Requestable.callback} [cb] - will receive the list of users 76 | * @return {Promise} - the promise for the http request 77 | */ 78 | listMembers(options, cb) { 79 | log(`Getting members of Team ${this.__teamId}`); 80 | return this._requestAllPages( 81 | `/teams/${this.__teamId}/members`, 82 | options, 83 | cb, 84 | ); 85 | } 86 | 87 | /** 88 | * Get Team membership status for a user 89 | * @see https://developer.github.com/v3/orgs/teams/#get-team-membership 90 | * @param {string} username - can be one of: `all`, `maintainer`, or `member` 91 | * @param {Requestable.callback} [cb] - will receive the membership status of a user 92 | * @return {Promise} - the promise for the http request 93 | */ 94 | getMembership(username, cb) { 95 | log(`Getting membership of user ${username} in Team ${this.__teamId}`); 96 | return this._request( 97 | 'GET', 98 | `/teams/${this.__teamId}/memberships/${username}`, 99 | undefined, 100 | cb, 101 | ); 102 | } 103 | 104 | /** 105 | * Add a member to the Team 106 | * @see https://developer.github.com/v3/orgs/teams/#add-team-membership 107 | * @param {string} username - can be one of: `all`, `maintainer`, or `member` 108 | * @param {object} options - Parameters for adding a team member 109 | * @param {string} [options.role=member] - The role that this user should have in the team. Can be one 110 | * of: `member`, or `maintainer` 111 | * @param {Requestable.callback} [cb] - will receive the membership status of added user 112 | * @return {Promise} - the promise for the http request 113 | */ 114 | addMembership(username, options, cb) { 115 | log(`Adding user ${username} to Team ${this.__teamId}`); 116 | return this._request( 117 | 'PUT', 118 | `/teams/${this.__teamId}/memberships/${username}`, 119 | options, 120 | cb, 121 | ); 122 | } 123 | 124 | /** 125 | * Get repo management status for team 126 | * @see https://developer.github.com/v3/orgs/teams/#remove-team-membership 127 | * @param {string} owner - Organization name 128 | * @param {string} repo - Repo name 129 | * @param {Requestable.callback} [cb] - will receive the membership status of added user 130 | * @return {Promise} - the promise for the http request 131 | */ 132 | isManagedRepo(owner, repo, cb) { 133 | log( 134 | `Getting repo management by Team ${this.__teamId} for repo ${owner}/${repo}`, 135 | ); 136 | return this._request204or404( 137 | `/teams/${this.__teamId}/repos/${owner}/${repo}`, 138 | undefined, 139 | cb, 140 | ); 141 | } 142 | 143 | /** 144 | * Add or Update repo management status for team 145 | * @see https://developer.github.com/v3/orgs/teams/#add-or-update-team-repository 146 | * @param {string} owner - Organization name 147 | * @param {string} repo - Repo name 148 | * @param {object} options - Parameters for adding or updating repo management for the team 149 | * @param {string} [options.permission] - The permission to grant the team on this repository. Can be one 150 | * of: `pull`, `push`, or `admin` 151 | * @param {Requestable.callback} [cb] - will receive the membership status of added user 152 | * @return {Promise} - the promise for the http request 153 | */ 154 | manageRepo(owner, repo, options, cb) { 155 | log( 156 | `Adding or Updating repo management by Team ${this.__teamId} for repo ${owner}/${repo}`, 157 | ); 158 | return this._request204or404( 159 | `/teams/${this.__teamId}/repos/${owner}/${repo}`, 160 | options, 161 | cb, 162 | 'PUT', 163 | ); 164 | } 165 | 166 | /** 167 | * Remove repo management status for team 168 | * @see https://developer.github.com/v3/orgs/teams/#remove-team-repository 169 | * @param {string} owner - Organization name 170 | * @param {string} repo - Repo name 171 | * @param {Requestable.callback} [cb] - will receive the membership status of added user 172 | * @return {Promise} - the promise for the http request 173 | */ 174 | unmanageRepo(owner, repo, cb) { 175 | log( 176 | `Remove repo management by Team ${this.__teamId} for repo ${owner}/${repo}`, 177 | ); 178 | return this._request204or404( 179 | `/teams/${this.__teamId}/repos/${owner}/${repo}`, 180 | undefined, 181 | cb, 182 | 'DELETE', 183 | ); 184 | } 185 | 186 | /** 187 | * Delete Team 188 | * @see https://developer.github.com/v3/orgs/teams/#delete-team 189 | * @param {Requestable.callback} [cb] - will receive the list of repositories 190 | * @return {Promise} - the promise for the http request 191 | */ 192 | deleteTeam(cb) { 193 | log(`Deleting Team ${this.__teamId}`); 194 | return this._request204or404( 195 | `/teams/${this.__teamId}`, 196 | undefined, 197 | cb, 198 | 'DELETE', 199 | ); 200 | } 201 | } 202 | 203 | export default Team; 204 | -------------------------------------------------------------------------------- /src/GitHubAPI/User.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @copyright 2013 Michael Aufreiter (Development Seed) and 2016 Yahoo Inc. 4 | * @license Licensed under {@link https://spdx.org/licenses/BSD-3-Clause-Clear.html BSD-3-Clause-Clear}. 5 | * Github.js is freely distributable. 6 | */ 7 | 8 | import Requestable from './Requestable'; 9 | import debug from 'debug'; 10 | const log = debug('github:user'); 11 | 12 | /** 13 | * A User allows scoping of API requests to a particular Github user. 14 | */ 15 | class User extends Requestable { 16 | /** 17 | * Create a User. 18 | * @param {string} [username] - the user to use for user-scoped queries 19 | * @param {Requestable.auth} [auth] - information required to authenticate to Github 20 | * @param {string} [apiBase=https://api.github.com] - the base Github API URL 21 | */ 22 | constructor(username, auth, apiBase) { 23 | super(auth, apiBase); 24 | this.__user = username; 25 | } 26 | 27 | /** 28 | * Get the url for the request. (dependent on if we're requesting for the authenticated user or not) 29 | * @private 30 | * @param {string} endpoint - the endpoint being requested 31 | * @return {string} - the resolved endpoint 32 | */ 33 | __getScopedUrl(endpoint) { 34 | if (this.__user) { 35 | return endpoint 36 | ? `/users/${this.__user}/${endpoint}` 37 | : `/users/${this.__user}`; 38 | } else { 39 | // eslint-disable-line 40 | switch (endpoint) { 41 | case '': 42 | return '/user'; 43 | 44 | case 'notifications': 45 | case 'gists': 46 | return `/${endpoint}`; 47 | 48 | default: 49 | return `/user/${endpoint}`; 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * List the user's repositories 56 | * @see https://developer.github.com/v3/repos/#list-user-repositories 57 | * @param {Object} [options={}] - any options to refine the search 58 | * @param {Requestable.callback} [cb] - will receive the list of repositories 59 | * @return {Promise} - the promise for the http request 60 | */ 61 | listRepos(options, cb) { 62 | if (typeof options === 'function') { 63 | cb = options; 64 | options = {}; 65 | } 66 | 67 | options = this._getOptionsWithDefaults(options); 68 | 69 | log(`Fetching repositories with options: ${JSON.stringify(options)}`); 70 | return this._requestAllPages(this.__getScopedUrl('repos'), options, cb); 71 | } 72 | 73 | /** 74 | * List the orgs that the user belongs to 75 | * @see https://developer.github.com/v3/orgs/#list-user-organizations 76 | * @param {Requestable.callback} [cb] - will receive the list of organizations 77 | * @return {Promise} - the promise for the http request 78 | */ 79 | listOrgs(cb) { 80 | return this._request('GET', this.__getScopedUrl('orgs'), null, cb); 81 | } 82 | 83 | /** 84 | * List followers of a user 85 | * @see https://developer.github.com/v3/users/followers/#list-followers-of-a-user 86 | * @param {Requestable.callback} [cb] - will receive the list of followers 87 | * @return {Promise} - the promise for the http request 88 | */ 89 | listFollowers(cb) { 90 | return this._request('GET', this.__getScopedUrl('followers'), null, cb); 91 | } 92 | 93 | /** 94 | * List users followed by another user 95 | * @see https://developer.github.com/v3/users/followers/#list-users-followed-by-another-user 96 | * @param {Requestable.callback} [cb] - will receive the list of who a user is following 97 | * @return {Promise} - the promise for the http request 98 | */ 99 | listFollowing(cb) { 100 | return this._request('GET', this.__getScopedUrl('following'), null, cb); 101 | } 102 | 103 | /** 104 | * List the user's gists 105 | * @see https://developer.github.com/v3/gists/#list-a-users-gists 106 | * @param {Requestable.callback} [cb] - will receive the list of gists 107 | * @return {Promise} - the promise for the http request 108 | */ 109 | listGists(cb) { 110 | return this._request('GET', this.__getScopedUrl('gists'), null, cb); 111 | } 112 | 113 | /** 114 | * List the user's notifications 115 | * @see https://developer.github.com/v3/activity/notifications/#list-your-notifications 116 | * @param {Object} [options={}] - any options to refine the search 117 | * @param {Requestable.callback} [cb] - will receive the list of repositories 118 | * @return {Promise} - the promise for the http request 119 | */ 120 | listNotifications(options, cb) { 121 | options = options || {}; 122 | if (typeof options === 'function') { 123 | cb = options; 124 | options = {}; 125 | } 126 | 127 | options.since = this._dateToISO(options.since); 128 | options.before = this._dateToISO(options.before); 129 | 130 | return this._request( 131 | 'GET', 132 | this.__getScopedUrl('notifications'), 133 | options, 134 | cb, 135 | ); 136 | } 137 | 138 | /** 139 | * Show the user's profile 140 | * @see https://developer.github.com/v3/users/#get-a-single-user 141 | * @param {Requestable.callback} [cb] - will receive the user's information 142 | * @return {Promise} - the promise for the http request 143 | */ 144 | getProfile(cb) { 145 | return this._request('GET', this.__getScopedUrl(''), null, cb); 146 | } 147 | 148 | /** 149 | * Gets the list of starred repositories for the user 150 | * @see https://developer.github.com/v3/activity/starring/#list-repositories-being-starred 151 | * @param {Requestable.callback} [cb] - will receive the list of starred repositories 152 | * @return {Promise} - the promise for the http request 153 | */ 154 | listStarredRepos(cb) { 155 | let requestOptions = this._getOptionsWithDefaults(); 156 | return this._requestAllPages( 157 | this.__getScopedUrl('starred'), 158 | requestOptions, 159 | cb, 160 | ); 161 | } 162 | 163 | /** 164 | * Gets the list of starred gists for the user 165 | * @see https://developer.github.com/v3/gists/#list-starred-gists 166 | * @param {Object} [options={}] - any options to refine the search 167 | * @param {Requestable.callback} [cb] - will receive the list of gists 168 | * @return {Promise} - the promise for the http request 169 | */ 170 | listStarredGists(options, cb) { 171 | options = options || {}; 172 | if (typeof options === 'function') { 173 | cb = options; 174 | options = {}; 175 | } 176 | options.since = this._dateToISO(options.since); 177 | return this._request('GET', '/gists/starred', options, cb); 178 | } 179 | 180 | /** 181 | * List email addresses for a user 182 | * @see https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user 183 | * @param {Requestable.callback} [cb] - will receive the list of emails 184 | * @return {Promise} - the promise for the http request 185 | */ 186 | getEmails(cb) { 187 | return this._request('GET', '/user/emails', null, cb); 188 | } 189 | 190 | /** 191 | * Have the authenticated user follow this user 192 | * @see https://developer.github.com/v3/users/followers/#follow-a-user 193 | * @param {string} username - the user to follow 194 | * @param {Requestable.callback} [cb] - will receive true if the request succeeds 195 | * @return {Promise} - the promise for the http request 196 | */ 197 | follow(username, cb) { 198 | return this._request('PUT', `/user/following/${username}`, null, cb); 199 | } 200 | 201 | /** 202 | * Have the currently authenticated user unfollow this user 203 | * @see https://developer.github.com/v3/users/followers/#follow-a-user 204 | * @param {string} username - the user to unfollow 205 | * @param {Requestable.callback} [cb] - receives true if the request succeeds 206 | * @return {Promise} - the promise for the http request 207 | */ 208 | unfollow(username, cb) { 209 | return this._request('DELETE', `/user/following/${username}`, null, cb); 210 | } 211 | 212 | /** 213 | * Create a new repository for the currently authenticated user 214 | * @see https://developer.github.com/v3/repos/#create 215 | * @param {object} options - the repository definition 216 | * @param {Requestable.callback} [cb] - will receive the API response 217 | * @return {Promise} - the promise for the http request 218 | */ 219 | createRepo(options, cb) { 220 | return this._request('POST', '/user/repos', options, cb); 221 | } 222 | } 223 | 224 | export default User; 225 | -------------------------------------------------------------------------------- /src/githubFs.js: -------------------------------------------------------------------------------- 1 | import GitHub from './GitHubAPI/GitHub'; 2 | import { lookup } from 'mime-types'; 3 | import Repository from './GitHubAPI/Repository'; 4 | import Gist from './GitHubAPI/Gist'; 5 | 6 | const Url = acode.require('url'); 7 | const fsOperation = acode.require('fs') || acode.require('fsOperation'); 8 | const helpers = acode.require('helpers'); 9 | const prompt = acode.require('prompt'); 10 | const encodings = acode.require('encodings'); 11 | 12 | const test = (url) => /^gh:/.test(url); 13 | 14 | githubFs.remove = () => { 15 | fsOperation.remove(test); 16 | }; 17 | 18 | /** 19 | * 20 | * @param {string} user 21 | * @param {'repo' | 'gist'} type 22 | * @param {string} repo 23 | * @param {string} path 24 | * @param {string} branch 25 | * @returns 26 | */ 27 | githubFs.constructUrl = (type, user, repo, path, branch) => { 28 | if (type === 'gist') { 29 | // user is gist id 30 | // repo is filename 31 | return `gh://gist/${user}/${repo}`; 32 | } 33 | let url = `gh://${type}/${user}/${repo}`; 34 | if (branch) { 35 | url += `@${branch}`; 36 | } 37 | if (path) { 38 | url = Url.join(url, path); 39 | } 40 | return url; 41 | }; 42 | 43 | export default function githubFs(token, settings) { 44 | fsOperation.extend(test, (url) => { 45 | const { user, type, repo, path, gist } = parseUrl(url); 46 | if (type === 'repo') { 47 | return readRepo(user, repo, path); 48 | } 49 | 50 | if (type === 'gist') { 51 | return readGist(gist, path); 52 | } 53 | 54 | throw new Error('Invalid github url'); 55 | }); 56 | 57 | /** 58 | * Parse url to get type, user, repo and path 59 | * @param {string} url 60 | */ 61 | function parseUrl(url) { 62 | url = url.replace(/^gh:\/\//, ''); 63 | const [type, user, repo, ...path] = url.split('/'); 64 | 65 | // gist doesn't have user 66 | if (type === 'gist') { 67 | return { 68 | /**@type {string} */ 69 | gist: user, 70 | /**@type {string} */ 71 | path: repo, 72 | type: 'gist', 73 | } 74 | } 75 | 76 | return { 77 | /**@type {string} */ 78 | user, 79 | /**@type {'repo'|'gist'} */ 80 | type, 81 | /**@type {string} */ 82 | repo, 83 | /**@type {string} */ 84 | path: path.join('/'), 85 | }; 86 | } 87 | 88 | /** 89 | * Get commit message from user 90 | * @param {string} message 91 | * @returns 92 | */ 93 | async function getCommitMessage(message) { 94 | if (settings.askCommitMessage) { 95 | const res = await prompt('Commit message', message, 'text'); 96 | if (!res) { 97 | const error = new Error('Commit aborted'); 98 | error.code = 0; 99 | error.toString = () => error.message; 100 | throw error; 101 | } 102 | return res; 103 | } 104 | return message; 105 | } 106 | 107 | /** 108 | * 109 | * @param {string} user 110 | * @param {string} repoAtBranch 111 | * @param {string} path 112 | * @returns 113 | */ 114 | function readRepo(user, repoAtBranch, path) { 115 | /**@type {GitHub} */ 116 | let gh; 117 | /**@type {Repository} */ 118 | let repo; 119 | const [repoName, branch] = repoAtBranch.split('@'); 120 | let sha = ''; 121 | const getSha = async () => { 122 | if (!sha && path) { 123 | const res = await repo.getSha(branch, path); 124 | sha = res.data.sha; 125 | } 126 | }; 127 | 128 | const init = async () => { 129 | if (gh) return; 130 | gh = new GitHub({ token: await token() }); 131 | repo = gh.getRepo(user, repoName); 132 | } 133 | 134 | return { 135 | async lsDir() { 136 | await init(); 137 | const res = await repo.getSha(branch, path); 138 | const { data } = res; 139 | 140 | return data.map(({ name: filename, path, type }) => { 141 | return { 142 | name: filename, 143 | isDirectory: type === 'dir', 144 | isFile: type === 'file', 145 | url: githubFs.constructUrl('repo', user, repoName, path, branch), 146 | } 147 | }); 148 | }, 149 | async readFile(encoding) { 150 | if (!path) throw new Error('Cannot read root directory') 151 | await init(); 152 | await getSha(); 153 | let { data } = await repo.getBlob(sha, 'blob'); 154 | data = await data.arrayBuffer(); 155 | 156 | if (encoding) { 157 | if (encodings?.decode) { 158 | const decoded = await encodings.decode(data, encoding); 159 | if (decoded) return decoded; 160 | } 161 | 162 | /**@deprecated just for backward compatibility */ 163 | return helpers.decodeText(data, encoding); 164 | } 165 | 166 | return data; 167 | }, 168 | async writeFile(data, encoding) { 169 | if (!path) throw new Error('Cannot write to root directory') 170 | const commitMessage = await getCommitMessage(`update ${path}`); 171 | if (!commitMessage) return; 172 | 173 | let encode = true; 174 | 175 | if (encoding) { 176 | if (data instanceof ArrayBuffer && encodings?.decode) { 177 | data = await encodings.decode(data, encoding); 178 | } 179 | 180 | if (encoding && encodings?.encode) { 181 | data = await encodings.encode(data, encoding); 182 | } 183 | 184 | if (data instanceof ArrayBuffer && encodings?.decode) { 185 | data = await encodings.decode(data, encoding); 186 | } 187 | } else if (data instanceof ArrayBuffer) { 188 | // convert to base64 189 | data = await bufferToBase64(data); 190 | encode = false; 191 | } 192 | 193 | await init(); 194 | await repo.writeFile(branch, path, data, commitMessage, { encode }); 195 | }, 196 | async createFile(name, data = '') { 197 | await init(); 198 | const newPath = path === '' ? name : Url.join(path, name); 199 | // check if file exists 200 | let sha; 201 | let encode = true; 202 | try { 203 | sha = await repo.getSha(branch, newPath); 204 | } catch (e) { 205 | // file doesn't exist 206 | } 207 | 208 | if (sha) { 209 | throw new Error('File already exists'); 210 | } 211 | 212 | if (data instanceof ArrayBuffer) { 213 | // convert to base64 214 | data = await bufferToBase64(data); 215 | encode = false; 216 | } 217 | 218 | const commitMessage = await getCommitMessage(`create ${newPath}`); 219 | if (!commitMessage) return; 220 | await repo.writeFile(branch, newPath, data, commitMessage, { encode }); 221 | return githubFs.constructUrl('repo', user, repoName, newPath, branch); 222 | }, 223 | async createDirectory(dirname) { 224 | await init(); 225 | let newPath = path === '' ? dirname : Url.join(path, dirname); 226 | // check if file exists 227 | let sha; 228 | try { 229 | sha = await repo.getSha(branch, newPath); 230 | } catch (e) { 231 | // file doesn't exist 232 | } 233 | 234 | if (sha) { 235 | throw new Error('Directory already exists'); 236 | } 237 | 238 | const createPath = Url.join(newPath, '.gitkeep'); 239 | const commitMessage = await getCommitMessage(`create ${newPath}`); 240 | if (!commitMessage) return; 241 | await repo.writeFile(branch, createPath, '', commitMessage); 242 | return githubFs.constructUrl('repo', user, repoName, newPath, branch); 243 | }, 244 | async copyTo(dest) { 245 | throw new Error('Not supported'); 246 | }, 247 | async delete() { 248 | if (!path) throw new Error('Cannot delete root'); 249 | await init(); 250 | await getSha(); 251 | const commitMessage = await getCommitMessage(`delete ${path}`); 252 | if (!commitMessage) return; 253 | await repo.deleteFile(branch, path, commitMessage, sha); 254 | }, 255 | async moveTo(dest) { 256 | throw new Error('Not supported'); 257 | // if (!path) throw new Error('Cannot move root'); 258 | // await init(); 259 | // const { path: destPath } = parseUrl(dest); 260 | // const newName = Url.join(destPath, Url.basename(path)); 261 | // const res = await move(newName); 262 | // return res; 263 | }, 264 | async renameTo(name) { 265 | throw new Error('Not supported'); 266 | // if (!path) throw new Error('Cannot rename root'); 267 | // await init(); 268 | // const newName = Url.join(Url.dirname(path), name); 269 | // const res = await move(newName); 270 | // return res; 271 | }, 272 | async exists() { 273 | if (!path) return true; 274 | await init(); 275 | try { 276 | await repo.getSha(branch, path); 277 | return true; 278 | } catch (e) { 279 | return false; 280 | } 281 | }, 282 | async stat() { 283 | if (!path) { 284 | return { 285 | length: 0, 286 | name: `github/${user}/${repoName}`, 287 | isDirectory: true, 288 | isFile: false, 289 | } 290 | } 291 | await init(); 292 | await getSha(); 293 | const content = await repo.getBlob(sha); 294 | return { 295 | length: content.data.length, 296 | name: path.split('/').pop(), 297 | isDirectory: path.endsWith('/'), 298 | isFile: !path.endsWith('/'), 299 | type: lookup(path), 300 | }; 301 | }, 302 | } 303 | } 304 | 305 | function readGist(gistId, path) { 306 | /**@type {string} */ 307 | let file; 308 | /**@type {GitHub} */ 309 | let gh; 310 | /**@type {Gist} */ 311 | let gist; 312 | const getFile = async () => { 313 | if (!file) { 314 | const { data } = await gist.read(); 315 | file = data.files[path]; 316 | } 317 | return file; 318 | } 319 | const init = async () => { 320 | if (gh) return; 321 | gh = new GitHub({ token: await token() }); 322 | gist = gh.getGist(gistId); 323 | } 324 | 325 | return { 326 | async lsDir() { 327 | throw new Error('Not supported'); 328 | }, 329 | async readFile() { 330 | await init(); 331 | const { content: data } = await getFile(); 332 | return data; 333 | }, 334 | async writeFile(data, encoding) { 335 | await init(); 336 | 337 | encoding = settings.value.defaultFileEncoding || 'utf-8'; 338 | 339 | if (encoding) { 340 | if (data instanceof ArrayBuffer && encodings?.decode) { 341 | data = await encodings.decode(data, encoding); 342 | } 343 | 344 | if (encoding && encodings?.encode) { 345 | data = await encodings.encode(data, encoding); 346 | } 347 | 348 | if (data instanceof ArrayBuffer && encodings?.decode) { 349 | data = await encodings.decode(data, encoding); 350 | } 351 | } 352 | 353 | await gist.update({ 354 | files: { 355 | [path]: { 356 | content: data, 357 | } 358 | } 359 | }); 360 | }, 361 | async createFile(name, data) { 362 | throw new Error('Not supported'); 363 | }, 364 | async createDirectory() { 365 | throw new Error('Not supported'); 366 | }, 367 | async copyTo() { 368 | throw new Error('Not supported'); 369 | }, 370 | async delete() { 371 | throw new Error('Not supported'); 372 | }, 373 | async moveTo() { 374 | throw new Error('Not supported'); 375 | }, 376 | async renameTo() { 377 | throw new Error('Not supported'); 378 | }, 379 | async exists() { 380 | await init(); 381 | return !!await getFile(); 382 | }, 383 | async stat() { 384 | await init(); 385 | await getFile(); 386 | return { 387 | length: file.size, 388 | name: path, 389 | isDirectory: false, 390 | isFile: true, 391 | type: lookup(path), 392 | }; 393 | }, 394 | } 395 | } 396 | } 397 | 398 | async function bufferToBase64(buffer) { 399 | const blob = new Blob([buffer]); 400 | const reader = new FileReader(); 401 | 402 | reader.readAsDataURL(blob); 403 | return new Promise((resolve, reject) => { 404 | reader.onloadend = () => { 405 | // strip off the data: url prefix 406 | const content = reader.result.slice(reader.result.indexOf(',') + 1); 407 | resolve(content); 408 | }; 409 | 410 | reader.onerror = reject; 411 | }); 412 | } 413 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import GitHub from './GitHubAPI/GitHub'; 2 | import plugin from '../plugin.json'; 3 | import githubFs from './githubFs'; 4 | 5 | const prompt = acode.require('prompt'); 6 | const confirm = acode.require('confirm'); 7 | const palette = acode.require('palette') || acode.require('pallete'); 8 | const helpers = acode.require('helpers'); 9 | const multiPrompt = acode.require('multiPrompt'); 10 | const openFolder = acode.require('openFolder'); 11 | const EditorFile = acode.require('EditorFile'); 12 | const appSettings = acode.require('settings'); 13 | const toast = acode.require('toast'); 14 | const fsOperation = acode.require('fsOperation'); 15 | 16 | if (!Blob.prototype.arrayBuffer) { 17 | Blob.prototype.arrayBuffer = function () { 18 | return new Promise((resolve, reject) => { 19 | const reader = new FileReader(); 20 | reader.onload = () => resolve(reader.result); 21 | reader.onerror = reject; 22 | reader.readAsArrayBuffer(this); 23 | }); 24 | }; 25 | } 26 | 27 | class AcodePlugin { 28 | token = ''; 29 | NEW = `${helpers.uuid()}_NEW`; 30 | #fsInitialized = false; 31 | #repos = []; 32 | #gists = []; 33 | 34 | async init() { 35 | this.commands.forEach(command => { 36 | editorManager.editor.commands.addCommand(command); 37 | }); 38 | 39 | this.token = localStorage.getItem('github-token'); 40 | await this.initFs(); 41 | 42 | tutorial(plugin.id, (hide) => { 43 | const commands = editorManager.editor.commands.byName; 44 | const openCommandPalette = commands.openCommandPalette || commands.openCommandPallete; 45 | const message = "Github plugin is installed successfully, open command palette and search 'open repository' to open a github repository."; 46 | let key = 'Ctrl+Shift+P'; 47 | if (openCommandPalette) { 48 | key = openCommandPalette.bindKey.win; 49 | } 50 | 51 | if (!key) { 52 | const onclick = async () => { 53 | const EditorFile = acode.require('EditorFile'); 54 | const fileInfo = await fsOperation(KEYBINDING_FILE).stat(); 55 | new EditorFile(fileInfo.name, { uri: KEYBINDING_FILE, render: true }); 56 | hide(); 57 | }; 58 | return

{message} Shortcut to open command pallete is not set, Click here set shortcut or use '...' icon in quick tools.

59 | } 60 | 61 | return

{message} To open command palette use combination {key} or use '...' icon in quick tools.

; 62 | }); 63 | } 64 | 65 | async initFs() { 66 | if (this.#fsInitialized) return; 67 | githubFs.remove(); 68 | githubFs(this.getToken.bind(this), this.settings); 69 | this.#fsInitialized = true; 70 | } 71 | 72 | async getToken() { 73 | if (this.token) return this.token; 74 | await this.updateToken(); 75 | return this.token; 76 | } 77 | 78 | async destroy() { 79 | githubFs.remove(); 80 | this.commands.forEach(command => { 81 | editorManager.editor.commands.removeCommand(command.name); 82 | }); 83 | } 84 | 85 | async openRepo() { 86 | await this.initFs(); 87 | this.token = await this.getToken(); 88 | palette( 89 | this.listRepositories.bind(this), 90 | this.selectBranch.bind(this), 91 | 'Type to search repository', 92 | ); 93 | } 94 | 95 | async selectBranch(repo) { 96 | const [user, repoName] = repo.split('/'); 97 | palette( 98 | this.listBranches.bind(this, user, repoName), 99 | (branch) => this.openRepoAsFolder(user, repoName, branch) 100 | .catch(helpers.error), 101 | 'Type to search branch', 102 | ); 103 | } 104 | 105 | async deleteGist() { 106 | await this.initFs(); 107 | const gist = await new Promise((resolve) => { 108 | palette( 109 | this.listGists.bind(this, false), 110 | resolve, 111 | 'Type to search gist', 112 | ); 113 | }); 114 | const confirmation = await confirm(strings['warning'], 'Delete this gist?'); 115 | if (!confirmation) return; 116 | 117 | const gh = await this.#GitHub(); 118 | const gistApi = gh.getGist(gist); 119 | await gistApi.delete(); 120 | this.#gists = this.#gists.filter(g => g.id !== gist); 121 | window.toast('Gist deleted'); 122 | } 123 | 124 | async deleteGistFile() { 125 | await this.initFs(); 126 | const gist = await new Promise((resolve) => { 127 | palette( 128 | this.listGists.bind(this, false), 129 | resolve, 130 | 'Type to search gist', 131 | ); 132 | }); 133 | 134 | const file = await new Promise((resolve) => { 135 | palette( 136 | this.listGistFiles.bind(this, gist, false), 137 | resolve, 138 | 'Type to search file', 139 | ); 140 | }); 141 | 142 | const confirmation = await confirm(strings['warning'], 'Delete this file?'); 143 | if (!confirmation) return; 144 | 145 | const gh = await this.#GitHub(); 146 | const gistApi = gh.getGist(gist); 147 | await gistApi.update({ 148 | files: { 149 | [file]: null, 150 | }, 151 | }); 152 | const cachedGist = this.#getGist(gist); 153 | if (cachedGist) cachedGist.files = cachedGist.files.filter(f => f.filename !== file); 154 | window.toast('File deleted'); 155 | } 156 | 157 | async openRepoAsFolder(user, repoName, branch) { 158 | const cachedRepo = this.#getRepo(user, repoName); 159 | if (branch === this.NEW) { 160 | const { from, branch: newBranch } = await multiPrompt( 161 | strings['create new branch'], 162 | [{ 163 | id: 'from', 164 | placeholder: strings['use branch'], 165 | hints: (setHints) => { 166 | setHints(cachedRepo.branches); 167 | }, 168 | type: 'text', 169 | }, 170 | { 171 | id: 'branch', 172 | placeholder: strings['new branch'], 173 | type: 'text', 174 | match: /^[a-z\-_0-9]+$/i, 175 | }], 176 | ); 177 | branch = newBranch; 178 | const gh = await this.#GitHub(); 179 | const repo = gh.getRepo(user, repoName); 180 | await repo.createBranch(from, newBranch); 181 | } 182 | 183 | if (branch === '..') { 184 | this.openRepo(); 185 | return; 186 | } 187 | 188 | const url = githubFs.constructUrl('repo', user, repoName, '/', branch); 189 | openFolder(url, { 190 | name: `${user}/${repoName}/${branch}`, 191 | saveState: false, 192 | }); 193 | } 194 | 195 | async openGist() { 196 | await this.initFs(); 197 | this.token = await this.getToken(); 198 | 199 | palette( 200 | this.listGists.bind(this), 201 | this.openGistFile.bind(this), 202 | 'Type to search gist', 203 | ); 204 | } 205 | 206 | async openGistFile(gist) { 207 | let url; 208 | let thisFilename; 209 | if (gist === this.NEW) { 210 | const { description, name, public: isPublic } = await multiPrompt( 211 | 'New gist', 212 | [{ 213 | id: 'description', 214 | placeholder: 'Description', 215 | type: 'text', 216 | }, 217 | { 218 | id: 'name', 219 | placeholder: 'File name*', 220 | type: 'text', 221 | required: true, 222 | }, 223 | [ 224 | 'Visibility', 225 | { 226 | id: 'public', 227 | name: 'visibility', 228 | value: true, 229 | placeholder: 'Public', 230 | type: 'radio', 231 | }, 232 | { 233 | id: 'private', 234 | name: 'visibility', 235 | value: false, 236 | placeholder: 'Private', 237 | type: 'radio', 238 | } 239 | ]], 240 | ).catch(() => { 241 | window.toast(strings['cancelled']); 242 | }); 243 | 244 | helpers.showTitleLoader(); 245 | const gh = await this.#GitHub(); 246 | const gist = gh.getGist(); 247 | const { data } = await gist.create({ 248 | description, 249 | public: isPublic, 250 | files: { 251 | [name]: { 252 | content: '# New gist', 253 | }, 254 | }, 255 | }); 256 | this.#gists.push(this.#formatGist(data)); 257 | thisFilename = name; 258 | url = githubFs.constructUrl('gist', data.id, name); 259 | helpers.removeTitleLoader(); 260 | } else { 261 | await new Promise((resolve) => { 262 | palette( 263 | this.listGistFiles.bind(this, gist), 264 | async (file) => { 265 | if (file === this.NEW) { 266 | const filename = await prompt('Enter file name', '', 'text', { 267 | required: true, 268 | placeholder: 'filename', 269 | }); 270 | if (!filename) { 271 | window.toast(strings['cancelled']); 272 | } 273 | helpers.showTitleLoader(); 274 | const gh = await this.#GitHub(); 275 | await gh.getGist(gist).update({ 276 | files: { 277 | [filename]: { 278 | content: '# New gist file', 279 | }, 280 | }, 281 | }); 282 | const cachedGist = this.#getGist(gist); 283 | cachedGist.files?.push({ 284 | text: filename, 285 | value: filename, 286 | }); 287 | helpers.removeTitleLoader(); 288 | thisFilename = filename; 289 | url = githubFs.constructUrl('gist', gist, filename); 290 | resolve(); 291 | return; 292 | } 293 | 294 | url = githubFs.constructUrl('gist', gist, file); 295 | thisFilename = file; 296 | resolve(); 297 | }, 298 | 'Type to search gist file', 299 | ); 300 | }); 301 | } 302 | 303 | new EditorFile(thisFilename, { 304 | uri: url, 305 | render: true, 306 | }); 307 | 308 | } 309 | 310 | async updateToken() { 311 | const result = await prompt('Enter github token', '', 'text', { 312 | required: true, 313 | placeholder: 'token', 314 | }); 315 | 316 | if (result) { 317 | this.token = result; 318 | this.#fsInitialized = false; 319 | localStorage.setItem('github-token', result); 320 | await this.initFs(); 321 | } 322 | } 323 | 324 | async listRepositories() { 325 | if (this.#repos.length) { 326 | return [...this.#repos]; 327 | } 328 | const gh = await this.#GitHub(); 329 | const user = gh.getUser(); 330 | const repos = await user.listRepos(); 331 | const { data } = repos; 332 | 333 | const list = data.map((repo) => { 334 | const { name, owner, visibility } = repo; 335 | return { 336 | text: `
337 | ${name} 338 | ${visibility} 339 |
`, 340 | value: `${owner.login}/${name}`, 341 | } 342 | }); 343 | this.#repos = [...list]; 344 | return list; 345 | } 346 | 347 | async listBranches(user, repoName) { 348 | let list = []; 349 | const cachedRepo = this.#getRepo(user, repoName); 350 | if (cachedRepo && cachedRepo.branches) { 351 | list = [...cachedRepo.branches]; 352 | } else { 353 | const gh = await this.#GitHub(); 354 | const repo = gh.getRepo(user, repoName); 355 | const branches = await repo.listBranches(); 356 | const { data } = branches; 357 | 358 | list = data.map((branch) => { 359 | return { 360 | text: branch.name, 361 | value: branch.name, 362 | } 363 | }); 364 | 365 | if (cachedRepo) { 366 | cachedRepo.branches = [...list]; 367 | } 368 | } 369 | 370 | list.push({ 371 | text: 'New branch', 372 | value: this.NEW, 373 | }); 374 | 375 | list.unshift({ 376 | text: '..', 377 | value: '..', 378 | }); 379 | 380 | return list; 381 | } 382 | 383 | async listGists(showAddNew = true) { 384 | let list = []; 385 | if (this.#gists.length) { 386 | list = [...this.#gists]; 387 | } else { 388 | const gh = await this.#GitHub(); 389 | const user = gh.getUser(); 390 | const gists = await user.listGists(); 391 | const { data } = gists; 392 | 393 | list = data.map(this.#formatGist); 394 | 395 | this.#gists = [...list]; 396 | } 397 | 398 | if (showAddNew) { 399 | list.push({ 400 | text: this.#highlightedText('New gist'), 401 | value: this.NEW, 402 | }); 403 | } 404 | 405 | return list; 406 | } 407 | 408 | async listGistFiles(gistId, showAddNew = true) { 409 | let list = []; 410 | const cachedGist = this.#getGist(gistId); 411 | if (cachedGist && cachedGist.files) { 412 | list = [...cachedGist.files]; 413 | } else { 414 | const gh = await this.#GitHub(); 415 | const gist = gh.getGist(gistId); 416 | const { data: { files, owner } } = await gist.read(); 417 | 418 | list = Object.values(files).map(({ filename }) => { 419 | return { 420 | text: filename, 421 | value: filename, 422 | } 423 | }); 424 | 425 | if (cachedGist) { 426 | cachedGist.files = [...list]; 427 | } 428 | } 429 | 430 | if (showAddNew) { 431 | list.push({ 432 | text: this.#highlightedText('New file'), 433 | value: this.NEW, 434 | }); 435 | } 436 | 437 | return list; 438 | } 439 | 440 | #highlightedText(text) { 441 | return `${text}`; 442 | } 443 | 444 | #formatGist(gist) { 445 | const { description, owner, files } = gist; 446 | const file = Object.values(files)[0]; 447 | return { 448 | text: `
449 | ${description || file.filename} 450 |
`, 451 | value: gist.id, 452 | } 453 | } 454 | 455 | #getRepo(user, repoName) { 456 | return this.#repos.find(repo => repo.value === `${user}/${repoName}`); 457 | } 458 | 459 | #getGist(gistId) { 460 | return this.#gists.find(gist => gist.value === gistId); 461 | } 462 | 463 | async #GitHub() { 464 | return new GitHub({ token: await this.getToken() }); 465 | } 466 | 467 | get commands() { 468 | return [ 469 | { 470 | name: 'github:repository:selectrepo', 471 | description: 'Open repository', 472 | exec: this.openRepo.bind(this), 473 | }, 474 | { 475 | name: 'github:gist:opengist', 476 | description: 'Open gist', 477 | exec: this.openGist.bind(this), 478 | }, 479 | { 480 | name: 'github:gist:deletegist', 481 | description: 'Delete gist', 482 | exec: this.deleteGist.bind(this), 483 | }, 484 | { 485 | name: 'github:gist:deletegistfile', 486 | description: 'Delete gist file', 487 | exec: this.deleteGistFile.bind(this), 488 | }, 489 | { 490 | name: 'github:updatetoken', 491 | description: 'Update github token', 492 | exec: this.updateToken.bind(this), 493 | }, 494 | { 495 | name: 'github:clearcache', 496 | description: 'Clear github cache', 497 | exec: () => { 498 | this.#repos = []; 499 | this.#gists = []; 500 | } 501 | } 502 | ] 503 | } 504 | 505 | get settings() { 506 | const settings = appSettings.value[plugin.id]; 507 | if (!settings) { 508 | appSettings.value[plugin.id] = { 509 | askCommitMessage: true, 510 | }; 511 | appSettings.update(); 512 | } 513 | return appSettings.value[plugin.id]; 514 | } 515 | 516 | get settingsJson() { 517 | const list = [ 518 | { 519 | key: 'askCommitMessage', 520 | text: 'Ask for commit message', 521 | checkbox: this.settings.askCommitMessage, 522 | } 523 | ]; 524 | 525 | return { 526 | list, 527 | cb: (key, value) => { 528 | this.settings[key] = value; 529 | appSettings.update(); 530 | } 531 | } 532 | } 533 | } 534 | 535 | /** 536 | * Create a toast message 537 | * @param {string} id 538 | * @param {string|HTMLElement|(hide: ()=>void)=>HTMLElement} message 539 | * @returns 540 | */ 541 | function tutorial(id, message) { 542 | if (!toast) return; 543 | if (localStorage.getItem(id) === 'true') return; 544 | localStorage.setItem(id, 'true'); 545 | 546 | if (typeof message === 'function') { 547 | message = message(toast.hide); 548 | } 549 | 550 | toast(message, false, '#17c', '#fff'); 551 | } 552 | 553 | if (window.acode) { 554 | const acodePlugin = new AcodePlugin(); 555 | acode.setPluginInit(plugin.id, async (baseUrl, $page, { cacheFileUrl, cacheFile }) => { 556 | if (!baseUrl.endsWith('/')) { 557 | baseUrl += '/'; 558 | } 559 | acodePlugin.baseUrl = baseUrl; 560 | await acodePlugin.init($page, cacheFile, cacheFileUrl); 561 | }, acodePlugin.settingsJson); 562 | acode.setPluginUnmount(plugin.id, () => { 563 | acodePlugin.destroy(); 564 | }); 565 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { exec } = require('child_process'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | module.exports = (env, options) => { 6 | const { mode = 'development' } = options; 7 | 8 | if (mode === 'production') { 9 | fs.rmdirSync(path.resolve(__dirname, 'dist'), { recursive: true }); 10 | } 11 | 12 | const rules = [ 13 | { 14 | test: /\.m?js$/, 15 | use: [ 16 | 'html-tag-js/jsx/tag-loader.js', 17 | { 18 | loader: 'babel-loader', 19 | options: { 20 | presets: ['@babel/preset-env'], 21 | }, 22 | }, 23 | ], 24 | }, 25 | ]; 26 | 27 | const main = { 28 | mode, 29 | entry: { 30 | main: './src/main.js', 31 | }, 32 | output: { 33 | path: path.resolve(__dirname, 'dist'), 34 | filename: '[name].js', 35 | chunkFilename: '[name].js', 36 | }, 37 | module: { 38 | rules, 39 | }, 40 | resolve: { 41 | fallback: { 42 | path: require.resolve('path-browserify'), 43 | }, 44 | }, 45 | plugins: [ 46 | { 47 | apply: (compiler) => { 48 | compiler.hooks.afterDone.tap('pack-zip', () => { 49 | // run pack-zip.js 50 | exec('node .vscode/pack-zip.js', (err, stdout, stderr) => { 51 | if (err) { 52 | console.error(err); 53 | return; 54 | } 55 | console.log(stdout); 56 | }); 57 | }); 58 | } 59 | } 60 | ], 61 | }; 62 | 63 | return [main]; 64 | } --------------------------------------------------------------------------------