| // support runas lauched directly from shell. | // support runas lauched directly from shell. | ||||
| // pass in environment in hta to shellexecute. | // pass in environment in hta to shellexecute. | ||||
| const https = require('https') | |||||
| const http = require('http'); | |||||
| const { Console } = require('console'); | |||||
| const { env } = require('process'); | |||||
| const RESTAPI = (function(){ | |||||
| // Singleton | |||||
| function RESTAPI(){} | |||||
| // RESTAPI.create = RESTAPI; // Returns the one singe instance which is the class itself | |||||
| // const httpsoptions = { | |||||
| // hostname: 'whatever.com', | |||||
| // port: 443, | |||||
| // path: '/todos', | |||||
| // method: 'POST', | |||||
| // headers: { | |||||
| // 'Content-Type': 'application/json', | |||||
| // 'Content-Length': data.length | |||||
| // } | |||||
| // } | |||||
| RESTAPI.method = function(options, resolve, reject){ | |||||
| options.headers = options.headers || { 'Content-Type': 'application/json' } | |||||
| const data = options.payload ? new TextEncoder().encode( JSON.stringify(options.payload) ) : null | |||||
| options.headers = options.payload ? (options.headers || { | |||||
| 'Content-Type': 'application/json', | |||||
| 'Content-Length': data.length | |||||
| }) : (options.headers || {}) | |||||
| !options.headers.Authorization && usertokens[options.username] ? (()=>{ | |||||
| options.headers.Authorization = `token ${usertokens[options.username]}` | |||||
| delete options.username | |||||
| onauthenticated() | |||||
| })() : (()=>{ | |||||
| if(!usertokens[options.username] && !options.isAuthCall) { | |||||
| RESTAPI.authenticate( utils.assign( {isAuthCall : true}, options ) , onauthenticated, function(err){ | |||||
| // PB : TODO -- Retry without auth... | |||||
| console.error('Auth failed or not accessible') | |||||
| reject(err) | |||||
| }) | |||||
| } | |||||
| else onauthenticated() | |||||
| })() | |||||
| function onauthenticated(){ | |||||
| var acquirer = getHTTPorS(options) | |||||
| const req = acquirer.request(options, res => { | |||||
| if (res.statusCode < 200 || res.statusCode >= 300) { | |||||
| // new Error('statusCode=' + res.statusCode) | |||||
| return reject('statusCode = ' + res.statusCode); | |||||
| } | |||||
| var body = []; | |||||
| // res.setEncoding('utf8'); | |||||
| res.on('data', (chunk)=>{ body.push(chunk); }); | |||||
| res.on('end', ()=>{ | |||||
| try { | |||||
| if(res.headers['content-type'] && res.headers['content-type'].split(';').find( (i)=> i.trim() === 'application/json' )) body = JSON.parse(Buffer.concat(body).toString()); | |||||
| else body = Buffer.concat(body).toString(); | |||||
| } catch(e) { return reject(e); } | |||||
| resolve(body); | |||||
| }); | |||||
| }); | |||||
| req.on('error', error => { reject(error) }) | |||||
| if(options.payload) req.write(data) | |||||
| req.end() | |||||
| } | |||||
| } | |||||
| var usertokens = {} | |||||
| RESTAPI.authenticate = function(options, resolve, reject){ | |||||
| options.headers = options.headers || { 'Content-Type': 'application/json' } | |||||
| if(!usertokens[options.username]) { | |||||
| // Authenticate and acquire token. | |||||
| // https://git.bbh/api/v1/users/<username>/tokens | |||||
| // curl -XPOST -H "Content-Type: application/json" -k -d '{"name":"demo"}' -u demo:demo123 http://git.bbh/api/v1/users/demo/tokens | |||||
| var _options = Object.assign({}, options) | |||||
| // _options.username = 'demo' | |||||
| // _options.password = 'demo123' | |||||
| _options.method = 'POST' | |||||
| _options.headers.Authorization = `Basic ${Buffer.from(`${_options.username}:${_options.password}`).toString('base64')}` | |||||
| _options.path = `/api/v1/users/${_options.username}/tokens` | |||||
| var postoptions = { name : _options.username } | |||||
| delete _options.username | |||||
| delete _options.password | |||||
| _options.payload = postoptions | |||||
| RESTAPI.post( _options, function(tokenresp){ | |||||
| // tokenresp = JSON.parse(tokenresp) | |||||
| usertokens[options.username] = tokenresp.sha1 | |||||
| resolve(tokenresp) | |||||
| }, | |||||
| function(err){ reject(err) } | |||||
| ) | |||||
| } | |||||
| else resolve(tokenresp.sha1) | |||||
| } | |||||
| RESTAPI.put = RESTAPI.get = RESTAPI.post = RESTAPI.method | |||||
| return RESTAPI | |||||
| })(); | |||||
| var getHTTPorS = function(options){ return options.protocol.startsWith('https') ? https : http; } | |||||
| const GITEA = (function(){ | |||||
| function GITEA(){} | |||||
| GITEA.APIROOT = '/api/v1' | |||||
| GITEA.repository = { | |||||
| fork( httpoptions, cmdoptions, giteaoptions, resolve, reject ){ | |||||
| // forkoptions = { owner : httpoptions.username, repo : {{reoptoFork}} } | |||||
| // giteaoptions = { | |||||
| // organization string | |||||
| // --- organization name, if forking into an organization | |||||
| // } | |||||
| // http://try.gitea.io/api/v1/repos/{owner}/{repo}/forks | |||||
| httpoptions.path = `${GITEA.APIROOT}/repos/${cmdoptions.owner}/${cmdoptions.repo}/forks` | |||||
| httpoptions.method = 'POST' | |||||
| httpoptions.payload = giteaoptions; | |||||
| return RESTAPI.post(httpoptions, resolve || function(){}, reject || function(){} ) | |||||
| } | |||||
| , updateattributes( httpoptions, cmdoptions, giteaoptions, resolve, reject ){ | |||||
| httpoptions.path = `${GITEA.APIROOT}/repos/${cmdoptions.owner}/${cmdoptions.repo}` | |||||
| httpoptions.method = 'PATCH' | |||||
| httpoptions.payload = giteaoptions; | |||||
| return RESTAPI.post(httpoptions, resolve || function(){}, reject || function(){} ) | |||||
| } | |||||
| , exists( httpoptions, repo, owner ) { | |||||
| // /users/{username} // Get a user | |||||
| httpoptions.path = `${GITEA.APIROOT}/repos/${owner}/${repo}` | |||||
| httpoptions.method = 'GET' | |||||
| return new Promise( (resolve, reject) => { | |||||
| RESTAPI.get(httpoptions, function(o){ | |||||
| if(o.id) return resolve(true) | |||||
| return resolve(false) }, reject || function(){ return false } ) | |||||
| }) | |||||
| } | |||||
| , addcollaborator( httpoptions, repodef, owner, collaborator, permission ) { | |||||
| permission = permission || 'Read' | |||||
| httpoptions.payload = { permission } | |||||
| httpoptions.path = `${GITEA.APIROOT}/repos/${owner}/${repodef.repo}/collaborators/${collaborator}` | |||||
| httpoptions.method = 'PUT' | |||||
| return new Promise( (resolve, reject) => { | |||||
| RESTAPI.put(httpoptions, function(o){ | |||||
| // | |||||
| if(o === "") return resolve(true) | |||||
| return resolve(false) }, reject || function(){ return false } ) | |||||
| }) | |||||
| } | |||||
| } | |||||
| GITEA.user = { | |||||
| getuser( httpoptions, cmdoptions, giteaoptions, resolve, reject ){ | |||||
| // /users/{username} // Get a user | |||||
| httpoptions.path = `${GITEA.APIROOT}/users/${httpoptions.username}` | |||||
| httpoptions.method = 'GET' | |||||
| return RESTAPI.get(httpoptions, giteaoptions, resolve || function(){}, reject || function(){} ) | |||||
| } | |||||
| } | |||||
| return GITEA | |||||
| })(); | |||||
| // Wrapper for Git shell operations. Some meta operations will map to a bunch of GIT commands. | |||||
| const GIT = (function(){ | |||||
| function GIT(){} | |||||
| Object.assign(GIT, { | |||||
| 'switch user'(username){ | |||||
| var httpoptions = new URL(selectedinstance.reposerver); | |||||
| httpoptions.username = selectedinstance.username | |||||
| httpoptions.password = selectedinstance.password | |||||
| return GITEA.user.getuser(httpoptions).then(()=>{ | |||||
| return nodeShellExec('git', ['config', '--replace-all', 'user.name', username], | |||||
| { | |||||
| inherit: true, shell: true, | |||||
| env: ENV | |||||
| , cwd: instanceroot + '/' + repo | |||||
| , runas: processedArgs.runas | |||||
| , title: `'git', ${['config', '--replace-all', 'user.name', username].join(' ')}` | |||||
| }) | |||||
| } | |||||
| ) | |||||
| .catch(e => { | |||||
| console.error(e + 'Could not switch. Probably no such user.') | |||||
| }) | |||||
| } | |||||
| }) | |||||
| return GIT | |||||
| })(); | |||||
| // PB : NOTE -- iife doesnt work if previous statement is not terminated by ; | // PB : NOTE -- iife doesnt work if previous statement is not terminated by ; | ||||
| (function () { | (function () { | ||||
| "use strict"; | "use strict"; | ||||
| } | } | ||||
| , 'addcollaborator' : { | , 'addcollaborator' : { | ||||
| // Usage : | // Usage : | ||||
| // elxr addcollaborator developer | |||||
| // elxr addcollaborator instancename developer | |||||
| cmdFn : function(args){ | cmdFn : function(args){ | ||||
| var __each = function(args){ | var __each = function(args){ | ||||
| var repodef = selectedinstance.reposindexed[repo]; | var repodef = selectedinstance.reposindexed[repo]; | ||||
| if(!repodef) return | if(!repodef) return | ||||
| var remotenames = selectedinstance.selectedremotes.concat( selectedinstance.permanentremotes ) | |||||
| var remotenames = (selectedinstance.selectedremotes || []).concat( selectedinstance.permanentremotes ) | |||||
| var remotes = selectedinstance.reposerverinstances[selectedinstance.reposerver].remotes | var remotes = selectedinstance.reposerverinstances[selectedinstance.reposerver].remotes | ||||
| // console.log('-----------------------------------------------------') | // console.log('-----------------------------------------------------') | ||||
| // console.log(repo) | // console.log(repo) | ||||
| var promises = [] | var promises = [] | ||||
| var repos = Object.keys(selectedinstance.reposindexed) | var repos = Object.keys(selectedinstance.reposindexed) | ||||
| repos.forEach(repo => { promises.push(__each({ repo, collaborator : args._[1]})) }) | |||||
| repos.forEach(repo => { promises.push(__each({ repo, collaborator : args._[2]})) }) // PB : TODO -- Move to toArgs. | |||||
| return Promise.all(promises).then(pr => { console.log('addcollaborator done') }) | return Promise.all(promises).then(pr => { console.log('addcollaborator done') }) | ||||
| } | } | ||||
| } | } | ||||
| var pendingpulls = []; | var pendingpulls = []; | ||||
| def.repos.forEach((def) => { | def.repos.forEach((def) => { | ||||
| pendingpulls.push( | pendingpulls.push( | ||||
| // PB : TODO serializing to prevent out of memory. | |||||
| // Need to optimize as batches and streams... | |||||
| function(){ | function(){ | ||||
| // if(def.repo === 'setup') return; | // if(def.repo === 'setup') return; | ||||
| // if(def.repo !== 'chess-client-lib') return; | // if(def.repo !== 'chess-client-lib') return; | ||||
| localInstanceDetected : false | localInstanceDetected : false | ||||
| } | } | ||||
| const https = require('https') | |||||
| const http = require('http'); | |||||
| const { Console } = require('console'); | |||||
| const { env } = require('process'); | |||||
| const RESTAPI = (function(){ | |||||
| // Singleton | |||||
| function RESTAPI(){} | |||||
| // RESTAPI.create = RESTAPI; // Returns the one singe instance which is the class itself | |||||
| // const httpsoptions = { | |||||
| // hostname: 'whatever.com', | |||||
| // port: 443, | |||||
| // path: '/todos', | |||||
| // method: 'POST', | |||||
| // headers: { | |||||
| // 'Content-Type': 'application/json', | |||||
| // 'Content-Length': data.length | |||||
| // } | |||||
| // } | |||||
| RESTAPI.method = function(options, resolve, reject){ | |||||
| options.headers = options.headers || { 'Content-Type': 'application/json' } | |||||
| const data = options.payload ? new TextEncoder().encode( JSON.stringify(options.payload) ) : null | |||||
| options.headers = options.payload ? (options.headers || { | |||||
| 'Content-Type': 'application/json', | |||||
| 'Content-Length': data.length | |||||
| }) : (options.headers || {}) | |||||
| !options.headers.Authorization && usertokens[options.username] ? (()=>{ | |||||
| options.headers.Authorization = `token ${usertokens[options.username]}` | |||||
| delete options.username | |||||
| onauthenticated() | |||||
| })() : (()=>{ | |||||
| if(!usertokens[options.username] && !options.isAuthCall) { | |||||
| RESTAPI.authenticate( utils.assign( {isAuthCall : true}, options ) , onauthenticated, function(err){ | |||||
| // PB : TODO -- Retry without auth... | |||||
| console.error('Auth failed or not accessible') | |||||
| reject(err) | |||||
| }) | |||||
| } | |||||
| else onauthenticated() | |||||
| })() | |||||
| function onauthenticated(){ | |||||
| var acquirer = getHTTPorS(options) | |||||
| const req = acquirer.request(options, res => { | |||||
| if (res.statusCode < 200 || res.statusCode >= 300) { | |||||
| // new Error('statusCode=' + res.statusCode) | |||||
| return reject('statusCode = ' + res.statusCode); | |||||
| } | |||||
| var body = []; | |||||
| // res.setEncoding('utf8'); | |||||
| res.on('data', (chunk)=>{ body.push(chunk); }); | |||||
| res.on('end', ()=>{ | |||||
| try { | |||||
| if(res.headers['content-type'] && res.headers['content-type'].split(';').find( (i)=> i.trim() === 'application/json' )) body = JSON.parse(Buffer.concat(body).toString()); | |||||
| else body = Buffer.concat(body).toString(); | |||||
| } catch(e) { return reject(e); } | |||||
| resolve(body); | |||||
| }); | |||||
| }); | |||||
| req.on('error', error => { reject(error) }) | |||||
| if(options.payload) req.write(data) | |||||
| req.end() | |||||
| } | |||||
| } | |||||
| var usertokens = {} | |||||
| RESTAPI.authenticate = function(options, resolve, reject){ | |||||
| options.headers = options.headers || { 'Content-Type': 'application/json' } | |||||
| if(!usertokens[options.username]) { | |||||
| // Authenticate and acquire token. | |||||
| // https://git.bbh/api/v1/users/<username>/tokens | |||||
| // curl -XPOST -H "Content-Type: application/json" -k -d '{"name":"demo"}' -u demo:demo123 http://git.bbh/api/v1/users/demo/tokens | |||||
| var _options = Object.assign({}, options) | |||||
| // _options.username = 'demo' | |||||
| // _options.password = 'demo123' | |||||
| _options.method = 'POST' | |||||
| _options.headers.Authorization = `Basic ${Buffer.from(`${_options.username}:${_options.password}`).toString('base64')}` | |||||
| _options.path = `/api/v1/users/${_options.username}/tokens` | |||||
| var postoptions = { name : _options.username } | |||||
| delete _options.username | |||||
| delete _options.password | |||||
| _options.payload = postoptions | |||||
| RESTAPI.post( _options, function(tokenresp){ | |||||
| // tokenresp = JSON.parse(tokenresp) | |||||
| usertokens[options.username] = tokenresp.sha1 | |||||
| resolve(tokenresp) | |||||
| }, | |||||
| function(err){ reject(err) } | |||||
| ) | |||||
| } | |||||
| else resolve(tokenresp.sha1) | |||||
| } | |||||
| RESTAPI.put = RESTAPI.get = RESTAPI.post = RESTAPI.method | |||||
| return RESTAPI | |||||
| })(); | |||||
| var getHTTPorS = function(options){ return options.protocol.startsWith('https') ? https : http; } | |||||
| const GITEA = (function(){ | |||||
| function GITEA(){} | |||||
| GITEA.APIROOT = '/api/v1' | |||||
| GITEA.repository = { | |||||
| fork( httpoptions, cmdoptions, giteaoptions, resolve, reject ){ | |||||
| // forkoptions = { owner : httpoptions.username, repo : {{reoptoFork}} } | |||||
| // giteaoptions = { | |||||
| // organization string | |||||
| // --- organization name, if forking into an organization | |||||
| // } | |||||
| // http://try.gitea.io/api/v1/repos/{owner}/{repo}/forks | |||||
| httpoptions.path = `${GITEA.APIROOT}/repos/${cmdoptions.owner}/${cmdoptions.repo}/forks` | |||||
| httpoptions.method = 'POST' | |||||
| httpoptions.payload = giteaoptions; | |||||
| return RESTAPI.post(httpoptions, resolve || function(){}, reject || function(){} ) | |||||
| } | |||||
| , updateattributes( httpoptions, cmdoptions, giteaoptions, resolve, reject ){ | |||||
| httpoptions.path = `${GITEA.APIROOT}/repos/${cmdoptions.owner}/${cmdoptions.repo}` | |||||
| httpoptions.method = 'PATCH' | |||||
| httpoptions.payload = giteaoptions; | |||||
| return RESTAPI.post(httpoptions, resolve || function(){}, reject || function(){} ) | |||||
| } | |||||
| , exists( httpoptions, repo, owner ) { | |||||
| // /users/{username} // Get a user | |||||
| httpoptions.path = `${GITEA.APIROOT}/repos/${owner}/${repo}` | |||||
| httpoptions.method = 'GET' | |||||
| return new Promise( (resolve, reject) => { | |||||
| RESTAPI.get(httpoptions, function(o){ | |||||
| if(o.id) return resolve(true) | |||||
| return resolve(false) }, reject || function(){ return false } ) | |||||
| }) | |||||
| } | |||||
| , addcollaborator( httpoptions, repodef, owner, collaborator, permission ) { | |||||
| permission = permission || 'Read' | |||||
| httpoptions.payload = { permission } | |||||
| httpoptions.path = `${GITEA.APIROOT}/repos/${owner}/${repodef.repo}/collaborators/${collaborator}` | |||||
| httpoptions.method = 'PUT' | |||||
| return new Promise( (resolve, reject) => { | |||||
| RESTAPI.put(httpoptions, function(o){ | |||||
| // | |||||
| if(o === "") return resolve(true) | |||||
| return resolve(false) }, reject || function(){ return false } ) | |||||
| }) | |||||
| } | |||||
| } | |||||
| GITEA.user = { | |||||
| getuser( httpoptions, cmdoptions, giteaoptions, resolve, reject ){ | |||||
| // /users/{username} // Get a user | |||||
| httpoptions.path = `${GITEA.APIROOT}/users/${httpoptions.username}` | |||||
| httpoptions.method = 'GET' | |||||
| return RESTAPI.get(httpoptions, giteaoptions, resolve || function(){}, reject || function(){} ) | |||||
| } | |||||
| } | |||||
| return GITEA | |||||
| })(); | |||||
| // Wrapper for Git shell operations. Some meta operations will map to a bunch of GIT commands. | |||||
| const GIT = (function(){ | |||||
| function GIT(){} | |||||
| Object.assign(GIT, { | |||||
| 'switch user'(username){ | |||||
| var httpoptions = new URL(selectedinstance.reposerver); | |||||
| httpoptions.username = selectedinstance.username | |||||
| httpoptions.password = selectedinstance.password | |||||
| return GITEA.user.getuser(httpoptions).then(()=>{ | |||||
| return nodeShellExec('git', ['config', '--replace-all', 'user.name', username], | |||||
| { | |||||
| inherit: true, shell: true, | |||||
| env: ENV | |||||
| , cwd: instanceroot + '/' + repo | |||||
| , runas: processedArgs.runas | |||||
| , title: `'git', ${['config', '--replace-all', 'user.name', username].join(' ')}` | |||||
| }) | |||||
| } | |||||
| ) | |||||
| .catch(e => { | |||||
| console.error(e + 'Could not switch. Probably no such user.') | |||||
| }) | |||||
| } | |||||
| }) | |||||
| return GIT | |||||
| })(); | |||||
| function createInstanceData(target, source) { | function createInstanceData(target, source) { | ||||
| var sourceinstance = source || target; | var sourceinstance = source || target; |