UNPKG

reactium

Version:

A CLI for creating Reactium / Actinium projects.

247 lines (211 loc) 7.24 kB
import ora from 'ora'; import Actinium from 'parse/node.js'; import handlebars from 'handlebars'; import * as SDK from '@atomic-reactor/reactium-sdk-core/core'; import path from 'node:path'; import crypto from 'node:crypto'; import { fileURLToPath } from 'node:url'; import ChildProcess from 'node:child_process'; import _ from 'underscore'; import axios from 'axios'; import op from 'object-path'; import chalk from 'chalk'; import fs from 'fs-extra'; import { globbySync as globby } from './globby-patch.js'; import moment from 'moment'; import os from 'os'; import semver from 'semver'; import prettier from 'prettier'; import portscanner from 'portscanner'; import generator from './lib/generator.js'; import table from 'text-table'; import * as tar from 'tar'; import slugify from 'slugify'; import DeleteEmpty from 'delete-empty'; import ActionSequence from 'action-sequence'; import inquirer from 'inquirer'; import prompt from 'prompt'; import decompress from '@atomic-reactor/decompress'; import { error, message } from './lib/messenger.js'; import pad from './lib/pad.js'; import * as AuthSDK from './lib/auth.js'; import { useSpinner } from './lib/useSpinner.js'; import targetApp from './lib/targetApp.js'; const Hook = SDK.Hook; const normalizePath = (...args) => path.normalize(path.join(...args)); const root = path.dirname(fileURLToPath(import.meta.url)); const config = fs.readJsonSync(normalizePath(root, 'config.json')); const packagejson = fs.readJsonSync(normalizePath(root, 'package.json')); const importer = (path, options) => { options = options || {}; if (String(path).endsWith('.json')) { return fs.readJsonSync(path); } return import(`file://${path}`, options); } const bootstrap = async () => { const { spawn } = ChildProcess; const deleteEmpty = DeleteEmpty.sync; const cwd = path.resolve(process.cwd()); const homedir = os.homedir(); const ver = op.get(packagejson, 'version'); global.Actinium = Actinium; global.Hook = Hook; global.Spinner = ora({ spinner: 'dots', color: 'cyan' }); // Extend Spinner Spinner.message = (...args) => { Spinner.start(); Spinner.text = args.join(' '); }; let props = { config, cwd, homedir, root, ver }; const runCommand = (cmd, args = [], opt = {}) => new Promise((resolve, reject) => { const options = { shell: true, stdio: 'inherit', ...opt }; const ps = spawn(cmd, args, options); ps.on('error', err => { console.log(`Error executing ${cmd}`); console.error(err); reject(err); return; }); ps.on('close', code => { if (code !== 0) { console.error(`Error executing ${cmd}`); reject(); } else { resolve(ps); } }); }); const mergeActions = (...args) => args.reduce((output, actions, i) => { Object.keys(actions).forEach(key => op.set(output, `${key}-${i}`, op.get(actions, key)), ); return output; }, {}); const initialize = async props => { // import arlic-hooks.js files const hooks = globby([path.join(cwd, '/**/arcli-hooks.js')]); while (hooks.length > 0) { const hook = hooks.shift(); await importer(hook); } // Get application config const appConfigFile = normalizePath(cwd, '.core', '.cli', 'config.json'); if (fs.existsSync(appConfigFile)) { const appConfig = await importer(appConfigFile); props.config = Object.assign(props.config, appConfig); } // Get local config const localConfigFile = normalizePath(homedir, '.arcli', 'config.json'); props.localConfigFile = localConfigFile; if (fs.existsSync(localConfigFile)) { const localConfig = await importer(localConfigFile); props.config = Object.assign(props.config, localConfig); } else { // Create the localized config if it doesn't exist const contents = JSON.stringify(props.config, null, 2); fs.ensureFileSync(localConfigFile); fs.writeFileSync(localConfigFile, contents); } // Get project config const projConfigFile = normalizePath(cwd, '.cli', 'config.json'); if (fs.existsSync(projConfigFile)) { const projConfig = await importer(projConfigFile); props.config = Object.assign(props.config, projConfig); } return props; }; const normalizeCommandPath = dir => path .normalize( String(`${dir}/**/*index.js`) .replace(/\[root\]/gi, global.arcli.props.root) .replace(/\[cwd\]/gi, global.arcli.props.cwd), ) .split(/[\\\/]/g) .join(path.posix.sep); const rootCommands = () => globby(normalizeCommandPath('[root]/commands')) // fix volume paths on Windows .map(p => p.replace(/^([a-z]{1}):/i, '/$1:')); const flagsToParams = ({ opt = {}, flags }) => flags.reduce((obj, key) => { let val = opt[key]; val = typeof val === 'function' ? undefined : val; if (val) { obj[key] = val; } return obj; }, {}); const isEmpty = p => Boolean(fs.existsSync(p) ? fs.readdirSync(p).length < 1 : true); // Glob the functions const commands = () => { // Find commands const dirs = config.commands || []; const globs = dirs .filter(dir => dir !== '[root]/commands') .map(dir => normalizeCommandPath(dir)); const deep = op.get(config, 'depth', 25); return globby(globs, { deep }) // fix volume paths on Windows .map(p => p.replace(/^([a-z]{1}):/i, '/$1:')); }; global.arcli = { _, ...AuthSDK, ActionSequence, Actinium, Spinner, axios, chalk, commands, crypto, decompress, deleteEmpty, error, fileURLToPath, flagsToParams, fs, path, generator, globby, handlebars, importer, inquirer, isEmpty, homedir, mergeActions, message, moment, normalizePath, op, ora, pad, portscanner, prettier, prompt, props, root, rootCommands, runCommand, semver, slugify, table, tar, targetApp, tmp: normalizePath(homedir, 'tmp'), useSpinner, SDK, }; // Build the props object global.arcli.props = await initialize(global.arcli.props); global.arcli.props.inquirer = inquirer; global.arcli.props.prompt = prompt; global.arcli.prefix = chalk.cyan( String(arcli.props.config.prompt.prefix).trim(), ); return global.arcli; }; export default bootstrap;