| @@ -38,6 +38,205 @@ function getVersion() { return BUILD_VERSION; } | |||
| // support runas lauched directly from shell. | |||
| // 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 ; | |||
| (function () { | |||
| "use strict"; | |||
| @@ -346,7 +545,7 @@ shell_verse.acquireElevationState().then((isElevated) => { | |||
| } | |||
| , 'addcollaborator' : { | |||
| // Usage : | |||
| // elxr addcollaborator developer | |||
| // elxr addcollaborator instancename developer | |||
| cmdFn : function(args){ | |||
| var __each = function(args){ | |||
| @@ -357,7 +556,7 @@ shell_verse.acquireElevationState().then((isElevated) => { | |||
| var repodef = selectedinstance.reposindexed[repo]; | |||
| if(!repodef) return | |||
| var remotenames = selectedinstance.selectedremotes.concat( selectedinstance.permanentremotes ) | |||
| var remotenames = (selectedinstance.selectedremotes || []).concat( selectedinstance.permanentremotes ) | |||
| var remotes = selectedinstance.reposerverinstances[selectedinstance.reposerver].remotes | |||
| // console.log('-----------------------------------------------------') | |||
| // console.log(repo) | |||
| @@ -406,7 +605,7 @@ shell_verse.acquireElevationState().then((isElevated) => { | |||
| var promises = [] | |||
| 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') }) | |||
| } | |||
| } | |||
| @@ -3919,6 +4118,8 @@ shell_verse.acquireElevationState().then((isElevated) => { | |||
| var pendingpulls = []; | |||
| def.repos.forEach((def) => { | |||
| pendingpulls.push( | |||
| // PB : TODO serializing to prevent out of memory. | |||
| // Need to optimize as batches and streams... | |||
| function(){ | |||
| // if(def.repo === 'setup') return; | |||
| // if(def.repo !== 'chess-client-lib') return; | |||
| @@ -4740,203 +4941,6 @@ shell_verse.acquireElevationState().then((isElevated) => { | |||
| 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) { | |||
| var sourceinstance = source || target; | |||