@nexssp/plugin
Version:
Plugin system for not only the Nexss Programmer.
273 lines (240 loc) • 8.64 kB
JavaScript
/**
* Copyright © 2018-2025 Nexss.com / Marcin Polak mapoart@gmail.com. All rights reserved.
* This source code is governed by MIT license, please check LICENSE file.
*/
/**
* Creates Plugin definition (all params optional)
* @param {string} plugin - path to the plugin
* //@param {string} path - path to the plugin
* @param {regexp|string} trigger - plugin cmd needs to pass the test
* @param {string} triggerValue - default is provess.argv[2]
* @param {string} commandsPath - folder where the commands are
* @param {array} ommit - ommits files eg. '!*\/**\/*.nexss-test.js'
* @param {bool} through - if true, if no criteria met, it will go to the next step.
*/
function nexssPlugin({
plugin /* required */,
aliases,
trigger,
triggerValue /* default is process.argv[2] */,
path,
commandsPath = 'src/cli/commands',
through,
ommit = [],
}) {
const { bold, green, red } = require('@nexssp/ansi')
const _path = require('path')
if (!plugin && !path) {
throw new Error(red("'plugin' or 'path' is required for nexssPlugin."))
} else if (plugin && path) {
console.error(`Please us only one of the properties: plugin or path.`)
console.error(`You have used:`)
console.error(`plugin: ${plugin}`)
console.error(`path: ${path}`)
process.exit()
}
let _ignore = ['!*/**/*.nexss-test.js']
if (ommit) {
_ignore = [..._ignore, ...ommit]
}
const _arguments = arguments
const _log = require('@nexssp/logdebug')
const _name = plugin || ''
const __name = _name.split('/').slice(-1)[0]
if (_name) path = getPluginPath(plugin)
_NEXSS_COMMANDS_FOLDER = _path.join(path, commandsPath)
function getHelpFiles() {
const fg = require('fast-glob')
const helpGlob = `${_NEXSS_COMMANDS_FOLDER}/*.md`.replace(/\\/g, '/')
_log.di(`@help glob:`, helpGlob)
const files = fg.sync([helpGlob], {
ignore: _ignore,
})
return files
}
function helpContent() {
const { helpContent } = require('./src/help')
const helpFiles = getHelpFiles()
// console.log(helpFiles, __name, path);
return helpContent(helpFiles, __name, path)
// process.exit(1);
}
// If dynamic runCommand was executed recursive so already passed.
if (aliases || trigger) {
trigger = aliases ? new RegExp(`^(${__name}|${aliases.join('|')})$`) : trigger
_log.di(`@plugin: Trigger '${trigger}' exists - check..`)
if (!triggerValue) {
triggerValue = process.argv[2]
_log.di(`@plugin: TriggerValue does not exist. Using process.argv[2]:`, triggerValue)
}
// const emptyPluginObject = {
// start: () => "trigger not passed",
// runCommand: () => "trigger not passed",
// displayCommandHelp,
// };
if (trigger instanceof RegExp) {
if (!trigger.test(triggerValue)) {
_log.dy(`@plugin: REGEXP: Trigger ${triggerValue} didn't pass the test: ${trigger}`)
return { getHelpFiles, helpContent }
} else {
_log.dg(`@plugin: REGEXP: Trigger ${triggerValue} passed the test: ${trigger}`)
}
} else if (triggerValue !== trigger) {
_log.dy(`@plugin: STRING: Trigger ${triggerValue} didn't pass the test: ${trigger}`)
return { getHelpFiles, helpContent }
} else {
_log.dg(`@plugin: STRING: Trigger ${triggerValue} passed the test: ${trigger}`)
}
}
let _fs
let _version
const start = () => {
_log.dg(`@plugin: starting ${_name} at: `, commandsPath)
_fs = require('fs')
let info = _name
const _packageJsonPath = _path.join(path, 'package.json')
if (_fs.existsSync(_packageJsonPath)) {
const { version, name } = require(_packageJsonPath)
if (process.argv.includes('--version')) {
console.log(version)
process.exit(0)
}
_version = version
info = name
if (_name && _name !== name) {
throw new Error(
red(`Name of the plugin specified '${_name}' has not been matched with the '${name}'`)
)
}
}
if (!through && info) {
// console.log(`${info} - v${bold(green(_version))}`) // Removed to not display info about plugin on every run, just on debug
_log.dy(`${info} - v${bold(green(_version))}`)
} else {
_log.dy(`@plugin: name not found (package.json): `, commandsPath)
}
_started = true
}
const getAliases = () => {
if (_fs.existsSync(`${path}/aliases.json`)) {
return require(`${path}/aliases.json`)
}
}
function validateArguments(command, args) {
_log.di('Validating arguments..', command, args)
if (command && command.startsWith('-')) {
_log.error('Command cannot starts with -')
process.exit(0)
}
return true
}
// 'Dynamic' is used when first command does not exist.
// But there is default folder which contains commands
// myplugin xxxoptional install ...
// then xxxoptional is the dynamic.
// we us it for example for dynamic variable
function runCommand(command, args = [], dynamic, localArgs = { through }) {
// If we run from other clients, we can see that there is passed plugin name
// as parameter, we move this out
if (command === __name) {
_log.dc('Shifting arguments [first is the same as plugin name] from:', command, args)
command = args[0]
args = args.slice(1)
}
_log.dg(`@plugin: running command `, {
command,
args,
dynamic,
localArgs,
})
if (!validateArguments(command, args)) {
return true
}
if (command == undefined) {
_log.dg(`@plugin: displaying help..`)
displayCommandHelp()
return true
}
const commandAliases = getAliases()
if (commandAliases && commandAliases[command]) {
command = commandAliases[command]
} else {
_log.dg(`@plugin: aliases not found. `)
}
// return false if not exists and go through is enabled (we just go through)
if (!command && through) {
return false
}
if (command === 'help') {
displayCommandHelp()
return
}
const subpluginPath = _path.resolve(_NEXSS_COMMANDS_FOLDER, '../')
const router = _path.join(subpluginPath, '_router.js')
_log.dy(`@plugin: checking router at:`, router)
if (_fs.existsSync(router)) {
_log.dg(`@plugin ${__name}: router has been found:`, router)
const resultFromRouter = require(router)(command, args, command, localArgs)
_log.dg(`@plugin: router returned`, resultFromRouter)
// console.log({ resultFromRouter });
return resultFromRouter
} else {
_log.dy(`@plugin: router has NOT been found:`, router)
const commandFile = `${_path.join(_NEXSS_COMMANDS_FOLDER, command)}.js`
if (_fs.existsSync(commandFile)) {
if (args[0] !== 'help') {
_log.dy(`@plugin: found command at:`, commandFile, `loading..`)
return require(commandFile)(command, args)
} else {
_log.dg(`@plugin: loading help..`)
let mdExploded = commandFile.split(/\./)
mdExploded.pop()
const helpFile = `${mdExploded.join('.')}.md`
const content = _fs.readFileSync(helpFile).toString()
const { displayMarkdown } = require('./lib/markdown')
displayMarkdown(content)
return true
}
} else {
if (!through) {
console.log(bold(red(`'${command}' not found.`)))
// process.exitCode = 1
console.log(bold('Available Commands:'))
}
_log.dr(`@plugin: command NOT found at:`, commandFile, 'Loading help for this command')
_log.dy(`Command '${command}' has not been found for ${_name}.`)
displayCommandHelp()
// return true
}
}
}
function displayCommandHelp() {
const { helpDisplay, helpContent } = require('./src/help')
const helpFiles = getHelpFiles()
// console.log(helpFiles, __name, path);
helpDisplay(helpContent(helpFiles, __name, path))
// process.exit(1);
}
const { applyTracker } = require('@nexssp/logdebug/tracker')
return applyTracker(
{
displayCommandHelp,
helpContent,
getHelpFiles,
getAliases,
start,
runCommand,
},
null
)
}
function getPluginPath(plugin) {
const { dirname } = require('path')
const Module = module.constructor
const moduleMainFilePath = dirname(Module._resolveFilename(plugin, module.parent))
const moduleRootPath = require('path').resolve(moduleMainFilePath, '../')
return moduleRootPath
}
nexssPlugin.getPluginPath = getPluginPath
nexssPlugin.helpDisplay = require('./src/help').helpDisplay
module.exports = nexssPlugin