UNPKG

@grucloud/core

Version:

GruCloud core, generate infrastructure code

309 lines (282 loc) 8.13 kB
const Spinnies = require("spinnies"); const assert = require("assert"); const fse = require("fs-extra"); const path = require("path"); const { pipe, tap, filter, not, any, or, get, assign } = require("rubico"); const { pluck, isEmpty, when, callProp, last } = require("rubico/x"); const logger = require("../logger")({ prefix: "CliUtils" }); const { tos } = require("../tos"); const { ProviderGru } = require("../ProviderGru"); const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; const spinner = { interval: 300, frames }; const runAsyncCommand = async ({ ws, text, command }) => { console.log(`${text}`); assert(text); assert(command); const spinnies = new Spinnies({ spinner }); //logger.debug(`runAsyncCommand: ${JSON.stringify({ text })}`); const spinnerList = []; const spinnerMap = new Map(); const onStateChange = ({ context, previousState, nextState, error = {}, indent, ...other }) => { ws && ws.send( JSON.stringify({ command: "logs", data: { context, nextState, error } }) ); // logger.info( // `onStateChange: ${JSON.stringify({ // context, // previousState, // nextState, // //other, // })}` // ); assert(context, "onStateChange: missing context"); const onDoneDefault = ({ state, spinnerMap }) => { // logger.debug( // `onDoneDefault: uri: ${uri} `, // JSON.stringify(spinnies.spinners.mock) // ); spinnies.update(uri, { text: displayText(state), status: "succeed", }); spinnerMap.delete(uri); }; const onErrorDefault = ({ spinnerMap }) => { logger.debug(`onErrorDefault: delete uri: ${uri} `); spinnerMap.delete(uri); }; const { uri, displayText, onDone = onDoneDefault, onError = onErrorDefault, hide = false, } = context; assert(displayText, "onStateChange: missing context displayText"); assert(uri, "onStateChange: missing context uri"); if (process.env.CONTINUOUS_INTEGRATION) { return; } switch (nextState) { case "WAITING": { //logger.debug(`spinnies: create uri: ${uri}`); spinnerList.push(uri); assert( !spinnies.pick(uri), `${uri} already created, list: ${tos(spinnerList)}` ); spinnerMap.set(uri, { state: context.state }); if (!hide) { spinnies.add(uri, { text: displayText(context.state), indent, color: "yellow", }); } break; } case "RUNNING": { //logger.debug(`spinnies RUNNING uri: ${uri}`); const spinner = spinnerMap.get(uri); if (!spinner) { logger.debug(`event RUNNING but ${uri} was not created`); return; } if (!hide) { const spinny = spinnies.pick(uri); assert( spinny, `spinnies create in running state: ${uri}, spinnerList: ${spinnerList.join( "\n" )}` ); spinnies.update(uri, { text: displayText(spinner.state), color: "cyanBright", status: "spinning", }); } break; } case "DONE": { //logger.debug(`spinnies DONE uri: ${uri} `); if (!hide) { const spinny = spinnies.pick(uri); if (!spinny) { //logger.debug(`DONE event: ${uri} was not created`); return; } } const spinner = spinnerMap.get(uri); if (!spinner) { logger.info( `event DONE but ${uri} was not created or is already deleted` ); // assert( // false, // `event DONE but ${uri} was not created or is already deleted` // ); return; } onDone({ state: spinner.state, spinnerMap, spinnies }); break; } case "ERROR": { logger.error(`spinnies: uri: ${uri} ERROR: ${tos(error)}`); if (!hide) { const spinny = spinnies.pick(uri); if (!spinny) { logger.error( `ERROR event: ${uri} was not created, error: ${error}` ); return; } assert(error, `should have set the error, id: ${uri}`); const spinner = spinnerMap.get(uri); if (!spinner) { logger.error(`spinnies ERROR: uri: ${uri}, error: ${tos(error)}`); return; } const textWithError = `${displayText(spinner.state).padEnd( 30, " " )} ${error.Message || ""} ${error.message || ""}`; logger.error(textWithError); spinnies.fail(uri, { text: textWithError }); } onError({ state: spinner.state, spinnerMap, spinnies }); break; } default: assert(false, `unknown state ${nextState}`); } }; try { const result = await command({ onStateChange }); //logger.debug(`runAsyncCommand end of : ${text}`); spinnies.stopAll(); return result; } catch (error) { spinnies.stopAll(); logger.info(`runAsyncCommand: error for command: ${text}`); logger.debug(error); throw error; } }; exports.runAsyncCommand = runAsyncCommand; const displayProviderList = pipe([ tap((param) => { assert(true); }), pluck("name"), tap((list) => { assert(list[0]); }), callProp("join", ", "), ]); exports.displayProviderList = displayProviderList; const filterProvider = ({ commandOptions: { provider: providerOptions = [] } }) => ({ provider }) => pipe([ tap(() => { assert(provider); }), () => provider, or([ () => isEmpty(providerOptions), (provider) => any((providerName) => new RegExp(`${providerName}`, "i").test(provider.name) )(providerOptions), ]), tap((keep) => { // logger.debug( // `filterProvider ${provider.name}: ${providerOptions}, keep: ${keep}` // ); }), ])(); exports.filterProvider = filterProvider; exports.setupProviders = ({ mapGloblalNameToResource, commandOptions = {}, programOptions = {}, } = {}) => (infra) => pipe([ tap(() => { assert(infra); assert(mapGloblalNameToResource); }), () => infra, assign({ stacks: pipe([ get("stacks"), when(isEmpty, () => [infra]), //TODO infra validation, has provider ? filter(not(isEmpty)), filter(filterProvider({ commandOptions })), tap.if(isEmpty, () => { throw Error("no provider provided"); }), ]), }), (infraNew) => ({ providerGru: ProviderGru({ mapGloblalNameToResource, commandOptions, programOptions, ...infraNew, }), }), ])(); exports.saveToJson = ({ ws, command, commandOptions = {}, programOptions = {} }) => (result) => pipe([ tap(() => { ws && ws.send( JSON.stringify({ command: command, programOptions, data: result }) ); assert(programOptions.workingDirectory); }), () => programOptions, get("json", commandOptions.inventory), when( not(isEmpty), pipe([ (filename) => path.resolve(programOptions.workingDirectory, filename), tap((fullPath) => { logger.debug(`saveToJson: ${fullPath}`); }), (fullPath) => fse.outputFile( fullPath, JSON.stringify( { command, commandOptions, programOptions, result }, null, 4 ) ), ]) ), () => result, ])(); exports.defaultTitle = pipe([ tap((programOptions) => { assert(programOptions); }), get("workingDirectory", process.cwd()), callProp("split", path.sep), last, ]);