| @@ -16,8 +16,6 @@ const runtimestamp = (new Date()).getTime(); | |||
| function getVersion() { return BUILD_VERSION; } | |||
| console.log(getVersion()); | |||
| // 'use strict'; | |||
| // PB : TODO -- make sure folder context is proper coz we can now run elxr from anywhere. | |||
| @@ -65,17 +63,49 @@ console.dir(processedArgs) | |||
| // } | |||
| // }) | |||
| var subcommandlabels = { | |||
| remote : (`remote ${processedArgs._[1] || ''}`).trim() | |||
| // | |||
| // elxr cli operations | |||
| var noprerequisites = { | |||
| add : true, 'set-url' : true, 'repo-relocate' : true | |||
| , remote : true, 'c' : true, 'h' : true | |||
| , httpget : true, getuser : true | |||
| , 'switch user' : true | |||
| , 'switch' : true | |||
| // , 'undefined' : true | |||
| } | |||
| var interpretrun = function(){ | |||
| var reconfirmcmds = { create : true } | |||
| var subcommandlabels = { | |||
| remote : (`remote ${processedArgs._[1] || ''}`).trim() | |||
| , switch : (`switch ${processedArgs._[1] || ''}`).trim() | |||
| } | |||
| var cmds = { | |||
| 'remote' : function() { | |||
| var cmds = { | |||
| remote : { | |||
| // return a interpreted set of arguments for this comd run context. | |||
| interpret() { | |||
| return { cmd : subcommandlabels['remote'], runchoice : 'c' } | |||
| } | |||
| } | |||
| , switch : { | |||
| interpret() { | |||
| return { cmd : subcommandlabels['switch'], runchoice : 'c', username : processedArgs._[2] | |||
| , reposerver : __default.reposerver | |||
| } | |||
| } | |||
| , getPossiblePrompts(){ return { username : true, reposerver : 'http://git.bbh' } } // Requires only one argument... | |||
| } | |||
| , 'switch user' : { | |||
| interpret() { | |||
| return { cmd : subcommandlabels['switch'], runchoice : 'c', username : processedArgs._[2] } | |||
| } | |||
| , getPossiblePrompts(){ return { username : true } } // Requires only one argument... | |||
| } | |||
| } | |||
| var interpretrun = function(){ | |||
| var cmd = processedArgs._[0]; | |||
| var clioverrides = { cmd } | |||
| @@ -83,14 +113,21 @@ var interpretrun = function(){ | |||
| : (process.env.NODE_ENV && process.env.NODE_ENV.trim()) | |||
| ? clioverrides.node_env = (process.env.NODE_ENV && process.env.NODE_ENV.trim()) : null; | |||
| return cmds[cmd] ? cmds[cmd]() : (function(){ | |||
| return cmds[cmd] ? cmds[cmd].interpret() : (function(){ | |||
| processedArgs._[1] ? clioverrides.instanceName = processedArgs._[1]: null; | |||
| return clioverrides | |||
| // return clioverrides | |||
| cmds[cmd] = { | |||
| interpret() { | |||
| return Object.assign(clioverrides, { cmd, runchoice : 'c' }) | |||
| } | |||
| , getPossiblePrompts(){ return { cmd, username : true, password : true, | |||
| instanceName : true, instanceType : true, reposerver : true } } | |||
| } | |||
| return cmds[cmd].interpret() | |||
| })() | |||
| } | |||
| var clioverrides = interpretrun() | |||
| console.dir(clioverrides) | |||
| @@ -351,15 +388,6 @@ var exludeMergeRepos = []; | |||
| var useGitPull = processedArgs.useGitPull || false; | |||
| var configPromise = null | |||
| // elxr cli operations | |||
| var noprerequisites = { | |||
| add : true, 'set-url' : true, 'repo-relocate' : true | |||
| , remote : true, 'c' : true, 'h' : true | |||
| , httpget : true, getuser : true | |||
| // , 'undefined' : true | |||
| } | |||
| var reconfirmcmds = { create : true } | |||
| var op = { | |||
| 'h': () => { console.log(elxr.help()); return '-h' } | |||
| @@ -1668,7 +1696,7 @@ var op = { | |||
| //Switch to target branch | |||
| .then( () => { | |||
| if(!mergesource || branch === mergesource ) return Promise.resolve(true) | |||
| return any(elevatedRunasRepos.map((repodef) => performCheckout({ repo : repodef.repo, branch}))) | |||
| return any(gitRepos.map((repodef) => performCheckout({ repo : repodef.repo, branch}))) | |||
| }) | |||
| .then( //Merge source branch to target branch | |||
| () => { | |||
| @@ -1859,7 +1887,7 @@ var op = { | |||
| return createInstance(selectedinstance) } | |||
| , 'httpget' : () => { | |||
| // HTTPAPI.get({ | |||
| // RESTAPI.get({ | |||
| // hostname: 'git.bbh', | |||
| // // port: 443, | |||
| // protocol : 'http:', | |||
| @@ -1872,6 +1900,10 @@ var op = { | |||
| , 'getuser' : ()=>{ | |||
| return prerequisites.git.getuser().then(u=>{ console.log(u)}) | |||
| } | |||
| , 'switch user' : (tousername)=>{ | |||
| return GIT['switch user'](tousername) | |||
| } | |||
| } | |||
| var util = require('util'); | |||
| @@ -2490,23 +2522,23 @@ if(clioverrides.reconfirm) { | |||
| } | |||
| else { var reconfirm = {}; } | |||
| var shouldPrompt = function(k, mustPrompt, target){ | |||
| return ((mustPrompt[k] !== undefined && mustPrompt[k] !== null) && target[k] !== mustPrompt[k] | |||
| || (mustPrompt[k] === undefined || mustPrompt[k] === null) && (target[k] === undefined || target[k] === null) | |||
| var shouldPrompt = function(k, possiblePrompts, target){ | |||
| return ((possiblePrompts[k] !== undefined && possiblePrompts[k] !== null) && target[k] !== possiblePrompts[k] | |||
| || (possiblePrompts[k] === undefined || possiblePrompts[k] === null) && (target[k] === undefined || target[k] === null) | |||
| || reconfirm[k]) | |||
| } | |||
| var getBoundEachPrompt = function(target, mustPrompt, promptables, choices, promptsfilter) { | |||
| var getBoundEachPrompt = function(target, possiblePrompts, promptables, choices, promptsfilter) { | |||
| return function(prompts, k, i, a){ | |||
| // No local instances config found. We use a default initialized instance available in selectedinstance | |||
| // Confirm those that were not supplied as user choices in runtime args and proceed to reattempt. | |||
| // PB : TODO -- selectedinstance === __default check to prompt everything... | |||
| if( shouldPrompt(k, mustPrompt, target) ) { | |||
| if( shouldPrompt(k, possiblePrompts, target) ) { | |||
| delete reconfirm[k]; | |||
| // console.log(k) | |||
| // console.dir(mustPrompt); //console.dir(target) | |||
| // console.dir(possiblePrompts); //console.dir(target) | |||
| prompts.push(async ()=>{ | |||
| // PB : NOTE -- Important in async cases when this needs to be in the same state as when it was invoked. | |||
| // We take a snapshot... Shallow.. !! If required deep should be used based on use case. | |||
| @@ -2517,7 +2549,7 @@ var getBoundEachPrompt = function(target, mustPrompt, promptables, choices, prom | |||
| return await target[k] | |||
| }) | |||
| } | |||
| delete mustPrompt[k] | |||
| delete possiblePrompts[k] // PB : TODO We should keep this around instead of deleting so we can do a second pass if required. | |||
| return prompts | |||
| } | |||
| } | |||
| @@ -2947,13 +2979,14 @@ function findlocalinstances(chessinstances, instanceoptions){ | |||
| var selectedinstance = null; | |||
| var chessinstances = { current_run : {} }; | |||
| var promptkeys = { | |||
| cmd : processedArgs._[0] | |||
| cmd : processedArgs._[0] || 'pull' | |||
| // Try not to prompt anything unless absolutely necessary or reconfirm is forced. | |||
| // 'instanceName' : true | |||
| // , 'node_env' : true | |||
| , username : '' | |||
| // , username : '' | |||
| // , runchoice : 'c' | |||
| } | |||
| promptkeys.runchoice = promptkeys.cmd ? 'c' : undefined | |||
| // promptkeys.runchoice = promptkeys.cmd ? 'c' : undefined | |||
| function createChessInsance( cfg ){ | |||
| var inst = {}; | |||
| @@ -2968,11 +3001,11 @@ var choices = { | |||
| , username : ['guest', 'chessdemo', 'demo'] | |||
| } | |||
| var getInteractionPoints = function(detectedinstanceoptions, promptkeys, promptsfilter){ | |||
| var getInteractionPoints = function(detectedinstanceoptions, possiblePrompts, promptsfilter){ | |||
| var instances = [] | |||
| var reposervers = []; | |||
| var instnaceNames = [] | |||
| var instanceNames = [] | |||
| var instanceTypes = ['development', 'production']; | |||
| Object.keys( chessinstances).forEach(instanceName => { | |||
| if(instanceName === 'current_run') return; | |||
| @@ -2982,16 +3015,16 @@ var getInteractionPoints = function(detectedinstanceoptions, promptkeys, prompts | |||
| if(instance.reposerver) reposervers.push(instance.reposerver) | |||
| instances.push(instance) | |||
| instanceTypes.push(instance.node_env) | |||
| instnaceNames.push(instance.instanceName) | |||
| instanceNames.push(instance.instanceName) | |||
| }) | |||
| }) | |||
| instances = instances.concat(detectedinstanceoptions) | |||
| if(selectedinstance['instanceName']) instnaceNames.push(selectedinstance['instanceName']) | |||
| if(promptkeys['instanceName']) instnaceNames.push(promptkeys['instanceName']) | |||
| if(selectedinstance['instanceName']) instanceNames.push(selectedinstance['instanceName']) | |||
| if(possiblePrompts['instanceName']) instanceNames.push(possiblePrompts['instanceName']) | |||
| if(selectedinstance['reposervers']) reposervers = reposervers.concat(selectedinstance['reposervers']) | |||
| choices['instanceName'] = Array.from( new Set(instnaceNames.concat(choices['instanceName'])) ) | |||
| choices['instanceName'] = Array.from( new Set(instanceNames.concat(choices['instanceName'])) ) | |||
| choices['reposerver'] = Array.from( new Set(reposervers.concat(choices['reposerver'])) ) | |||
| choices['instanceType'] = Array.from( new Set(instanceTypes.concat(choices['instanceType'])) ) | |||
| @@ -3004,44 +3037,40 @@ var detection_state = { | |||
| const https = require('https') | |||
| const http = require('http') | |||
| const HTTPAPI = (function(){ | |||
| const RESTAPI = (function(){ | |||
| // Singleton | |||
| function HTTPAPI(){} | |||
| // HTTPAPI.create = HTTPAPI; // Returns the one singe instance which is the class itself | |||
| HTTPAPI.post = function(options, jsonpostpayload, resolve, reject){ | |||
| var _post = function(options, jsonpostpayload, resolve, reject) { | |||
| console.dir(options) | |||
| console.dir(jsonpostpayload) | |||
| const data = new TextEncoder().encode( | |||
| JSON.stringify(jsonpostpayload) | |||
| ) | |||
| options.headers = options.headers || { | |||
| 'Content-Type': 'application/json', | |||
| 'Content-Length': data.length | |||
| function RESTAPI(){} | |||
| // RESTAPI.create = RESTAPI; // Returns the one singe instance which is the class itself | |||
| // const options = { | |||
| // hostname: 'whatever.com', | |||
| // port: 443, | |||
| // path: '/todos', | |||
| // method: 'POST', | |||
| // headers: { | |||
| // 'Content-Type': 'application/json', | |||
| // 'Content-Length': data.length | |||
| // } | |||
| // } | |||
| RESTAPI.method = function(options, resolve, reject){ | |||
| var __method = function() { | |||
| options.headers = options.headers || { 'Content-Type': 'application/json' } | |||
| if(options.payload) { | |||
| const data = new TextEncoder().encode( JSON.stringify(options.payload) ) | |||
| options.headers = options.headers || { | |||
| 'Content-Type': 'application/json', | |||
| 'Content-Length': data.length | |||
| } | |||
| } | |||
| if(!options.authenticatepost) options.headers.Authorization = `token ${usertokens[options.username]}` | |||
| // const options = { | |||
| // hostname: 'whatever.com', | |||
| // port: 443, | |||
| // path: '/todos', | |||
| // method: 'POST', | |||
| // headers: { | |||
| // 'Content-Type': 'application/json', | |||
| // 'Content-Length': data.length | |||
| // } | |||
| // } | |||
| var acquirer = getHTTP_S(options) | |||
| var acquirer = getHTTPorS(options) | |||
| const req = acquirer.request(options, res => { | |||
| if (res.statusCode < 200 || res.statusCode >= 300) { | |||
| return reject(new Error('statusCode=' + res.statusCode)); | |||
| } | |||
| var body = []; | |||
| // res.setEncoding('utf8'); | |||
| res.on('data', function(chunk) { | |||
| body.push(chunk); | |||
| }); | |||
| @@ -3060,29 +3089,28 @@ const HTTPAPI = (function(){ | |||
| reject(error) | |||
| }) | |||
| req.write(data) | |||
| if(options.payload) req.write(data) | |||
| req.end() | |||
| } | |||
| if(!options.authenticatepost && !usertokens[options.username]) { | |||
| HTTPAPI.authenticate(options, function(tokenresp){ | |||
| RESTAPI.authenticate(options, function(tokenresp){ | |||
| usertokens[options.username] = tokenresp.sha1 | |||
| _post(options, jsonpostpayload, resolve, reject) | |||
| __method(options, resolve, reject) | |||
| }, | |||
| function(err){ throw err} | |||
| ) | |||
| } | |||
| else _post(options, jsonpostpayload, resolve, reject) | |||
| else __method(options, resolve, reject) | |||
| } | |||
| RESTAPI.post = RESTAPI.method | |||
| var usertokens = {} | |||
| HTTPAPI.authenticate = function(options, resolve, reject){ | |||
| 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 | |||
| @@ -3098,7 +3126,8 @@ const HTTPAPI = (function(){ | |||
| var postoptions = { name : _options.username } | |||
| delete _options.username | |||
| delete _options.password | |||
| HTTPAPI.post( _options, postoptions, function(tokenresp){ | |||
| _options.payload = postoptions | |||
| RESTAPI.post( _options, function(tokenresp){ | |||
| // tokenresp = JSON.parse(tokenresp) | |||
| usertokens[options.username] = tokenresp.sha1 | |||
| resolve(tokenresp) | |||
| @@ -3109,66 +3138,12 @@ const HTTPAPI = (function(){ | |||
| else resolve(tokenresp.sha1) | |||
| } | |||
| HTTPAPI.get = function(options, resolve, reject){ | |||
| options.headers = options.headers || { 'Content-Type': 'application/json' } | |||
| if(!usertokens[options.username]) { | |||
| HTTPAPI.authenticate(options, function(tokenresp){ | |||
| usertokens[options.username] = tokenresp.sha1 | |||
| _get(options, resolve, reject) | |||
| }, | |||
| function(err){ throw err} | |||
| ) | |||
| } | |||
| else _get(options, resolve, reject) | |||
| var _get = function() { | |||
| options.headers.Authorization = `token ${usertokens[options.username]}` | |||
| // const options = { | |||
| // hostname: 'whatever.com', | |||
| // port: 443, | |||
| // path: '/todos', | |||
| // method: 'POST', | |||
| // headers: { | |||
| // 'Content-Type': 'application/json', | |||
| // 'Content-Length': data.length | |||
| // } | |||
| // } | |||
| var acquirer = getHTTP_S(options) | |||
| const req = acquirer.request(options, res => { | |||
| if (res.statusCode < 200 || res.statusCode >= 300) { | |||
| return reject(new Error('statusCode=' + res.statusCode)); | |||
| } | |||
| var body = []; | |||
| res.setEncoding('utf8'); | |||
| res.on('data', function(chunk) { | |||
| body.push(chunk); | |||
| }); | |||
| res.on('end', function() { | |||
| try { | |||
| body = JSON.parse(Buffer.concat(body).toString()); | |||
| } catch(e) { | |||
| reject(e); | |||
| } | |||
| resolve(body); | |||
| }); | |||
| }); | |||
| req.on('error', error => { | |||
| console.error(error) | |||
| reject(error) | |||
| }) | |||
| req.end() | |||
| } | |||
| } | |||
| RESTAPI.get = RESTAPI.post = RESTAPI.method | |||
| return HTTPAPI | |||
| return RESTAPI | |||
| })(); | |||
| var getHTTP_S = function(options){ return options.protocol.startsWith('https') ? https : http; } | |||
| var getHTTPorS = function(options){ return options.protocol.startsWith('https') ? https : http; } | |||
| const GITEA = (function(){ | |||
| @@ -3183,19 +3158,60 @@ const GITEA = (function(){ | |||
| // http://try.gitea.io/api/v1/repos/{owner}/{repo}/forks | |||
| httpoptions.path = `/api/v1/repos/${cmdoptions.owner}/${cmdoptions.repo}/forks` | |||
| httpoptions.method = 'POST' | |||
| return HTTPAPI.post(httpoptions, giteaoptions, resolve || function(){}, reject || function(){} ) | |||
| httpoptions.payload = giteaoptions; | |||
| return RESTAPI.post(httpoptions, resolve || function(){}, reject || function(){} ) | |||
| } | |||
| , updateattributes( httpoptions, cmdoptions, giteaoptions, resolve, reject ){ | |||
| httpoptions.path = `/api/v1/repos/${cmdoptions.owner}/${cmdoptions.repo}` | |||
| httpoptions.method = 'PATCH' | |||
| return HTTPAPI.post(httpoptions, giteaoptions, resolve || function(){}, reject || function(){} ) | |||
| httpoptions.payload = giteaoptions; | |||
| return RESTAPI.post(httpoptions, resolve || function(){}, reject || function(){} ) | |||
| } | |||
| } | |||
| GITEA.user = { | |||
| getuser( httpoptions, cmdoptions, giteaoptions, resolve, reject ){ | |||
| // /users/{username} // Get a user | |||
| httpoptions.path = `/api/v1/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 server = new URL(selectedinstance.reposerver); | |||
| return GITEA.user.getuser({ hostname : server.host, protocol : server.protocol | |||
| , username : selectedinstance.username, password : selectedinstance.password | |||
| }).then(()=>{ | |||
| return nodeShellExec('git', ['config', '--replace-all', 'user.name', username], | |||
| { | |||
| inherit: true, shell: true, | |||
| env: process.env | |||
| , cwd: instanceroot + '/' + repo | |||
| , runas: processedArgs.runas | |||
| , title: `git core.symlinks --replace-all true for ${selectedinstance.reposerver + `/${defaultRepoOwner}/` + repo + '.git'}` | |||
| }) | |||
| } | |||
| ) | |||
| .catch(e => { | |||
| console.error(e + 'Could not switch. Probably no such user.') | |||
| }) | |||
| } | |||
| }) | |||
| return GIT | |||
| })(); | |||
| function createInstance(selectedinstance) { | |||
| // http://try.gitea.io/api/v1/org/{org}/repos | |||
| @@ -3359,57 +3375,61 @@ acquireElevationState().then(() => { | |||
| detectedinstanceoptions.splice(0,0, __default) | |||
| var cmdinstance = cmds[clioverrides.cmd] | |||
| var cmdprompts = cmdinstance.getPossiblePrompts() | |||
| // PB : TODO -- Most recent should be at the tip ! at index 0 so utils.reverseassign is required !!! | |||
| selectedinstance = utils.assign( ...detectedinstanceoptions.slice(-2), promptkeys ) | |||
| // promptkeys = utils.assign(promptkeys, clioverrides) | |||
| console.dir(selectedinstance) | |||
| try { | |||
| chessinstances = acquirelocalinstances(selectedinstance); | |||
| findlocalinstances(chessinstances, detectedinstanceoptions) | |||
| initinstances(selectedinstance) // use the local instances for defaults if at all possible. | |||
| var todo = any( getInteractionPoints(detectedinstanceoptions, promptkeys) ).then(()=>{ | |||
| var inst = initinstances(selectedinstance) | |||
| detection_state.didWeFindInstance = true; | |||
| return inst; | |||
| }) | |||
| } | |||
| catch (e) { | |||
| // PB : TODO -- verbose mode warning.. console.warn(e) // Missing chessinstances is not an error... | |||
| var todo = any( getInteractionPoints(detectedinstanceoptions, promptkeys) ).then(()=>{ | |||
| return initinstances(selectedinstance) | |||
| }) | |||
| // if(!processedArgs._[0] || !selectedinstance.node_env || !selectedinstance.instanceName){ | |||
| // // Weve not been told what to do. | |||
| // todo = todo.then(() => { return acquireChoices(selectedinstance) }) | |||
| // } | |||
| if(cmdprompts.instanceName) { | |||
| // not an instanceless cmd. | |||
| console.dir(selectedinstance) | |||
| try { | |||
| todo = todo.then(() => { | |||
| try { | |||
| chessinstances = acquirelocalinstances(selectedinstance) | |||
| findlocalinstances(chessinstances, detectedinstanceoptions) | |||
| detectedinstanceoptions.splice(0,0, __default) | |||
| initinstances(selectedinstance) | |||
| chessinstances = acquirelocalinstances(selectedinstance); | |||
| findlocalinstances(chessinstances, detectedinstanceoptions) | |||
| initinstances(selectedinstance) // use the local instances for defaults if at all possible. | |||
| var todo = any( getInteractionPoints(detectedinstanceoptions, promptkeys) ).then(()=>{ | |||
| var inst = initinstances(selectedinstance) | |||
| detection_state.didWeFindInstance = true; | |||
| } | |||
| catch (e) { | |||
| // console.error(e) | |||
| console.log('No local instances config found in current root = ' + selectedinstance.root); | |||
| console.log('A config will be createed with the instance and environment chosen...') | |||
| // return (async ()=>{return await __default.reposerver})().then(()=>{ | |||
| // // selectedinstance = Object.assign(detectedInstance, clioverrides); | |||
| // return selectedinstance = Object.assign(__default, selectedinstance); | |||
| // }) | |||
| detection_state.didWeFindInstance = false; | |||
| return selectedinstance | |||
| } | |||
| }) | |||
| return inst; | |||
| }) | |||
| } | |||
| catch (e) { | |||
| // PB : TODO -- verbose mode warning.. console.warn(e) // Missing chessinstances is not an error... | |||
| var todo = any( getInteractionPoints(detectedinstanceoptions, promptkeys) ).then(()=>{ | |||
| return initinstances(selectedinstance) | |||
| }) | |||
| // if(!processedArgs._[0] || !selectedinstance.node_env || !selectedinstance.instanceName){ | |||
| // // Weve not been told what to do. | |||
| // todo = todo.then(() => { return acquireChoices(selectedinstance) }) | |||
| // } | |||
| todo = todo.then(() => { | |||
| try { | |||
| chessinstances = acquirelocalinstances(selectedinstance) | |||
| findlocalinstances(chessinstances, detectedinstanceoptions) | |||
| detectedinstanceoptions.splice(0,0, __default) | |||
| initinstances(selectedinstance) | |||
| detection_state.didWeFindInstance = true; | |||
| } | |||
| catch (e) { | |||
| // console.error(e) | |||
| console.log('No local instances config found in current root = ' + selectedinstance.root); | |||
| console.log('A config will be createed with the instance and environment chosen...') | |||
| // return (async ()=>{return await __default.reposerver})().then(()=>{ | |||
| // // selectedinstance = Object.assign(detectedInstance, clioverrides); | |||
| // return selectedinstance = Object.assign(__default, selectedinstance); | |||
| // }) | |||
| detection_state.didWeFindInstance = false; | |||
| return selectedinstance | |||
| } | |||
| }) | |||
| } | |||
| return todo | |||
| } | |||
| return todo | |||
| else return Promise.resolve(true) | |||
| }) | |||
| .then(()=>{ | |||