From 4c4be0b5eca5a0bb49cb20b96f2a53067cc70d2d Mon Sep 17 00:00:00 2001 From: Christophe CAMENSULI Date: Sat, 23 Dec 2023 13:26:13 +0100 Subject: [PATCH] feat: add cli --- src/Cli.ts | 1068 ++++++++++++++++++++++++++++++++++++++ src/Container.ts | 29 +- src/Error.ts | 4 +- src/Event.ts | 16 +- src/Nodefony.ts | 18 +- src/Service.ts | 16 +- src/Tools.ts | 5 +- src/finder/File.ts | 2 +- src/finder/FileResult.ts | 6 +- src/finder/Finder.ts | 3 +- src/finder/Result.ts | 2 +- 11 files changed, 1130 insertions(+), 39 deletions(-) create mode 100644 src/Cli.ts diff --git a/src/Cli.ts b/src/Cli.ts new file mode 100644 index 0000000..84a9b13 --- /dev/null +++ b/src/Cli.ts @@ -0,0 +1,1068 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable no-async-promise-executor */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable max-lines-per-function */ +// const Table = require('cli-table3'); +import path from "node:path" +import fs from "node:fs" +import commander , { program } from "commander"; +import { + spawn, + spawnSync, + SpawnSyncReturns, + SpawnSyncOptionsWithStringEncoding, + SpawnOptions +} + from "node:child_process"; +import moment from "moment"; +import semver from "semver"; +import asciify from "asciify"; +import inquirer from "inquirer" +import Table from "cli-table3" +import {get, random} from "node-emoji" +import clc from "cli-color" +import Service, {DefaultOptions} from "./Service"; +import {extend} from "./Tools" +import Container from "./Container"; +import FileClass from "./FileClass"; +import Event from "./Event" +//import { FSWatcher } from "node:fs"; +import {DebugType, EnvironmentType} from './Nodefony' +import bare from "cli-color/bare"; +import clui from "clui" +import Syslog from "./syslog/Syslog"; +//import Rx from 'rxjs' +import Rx from 'rxjs' +//import {rm, ls, cd ,mkdir, ln, cp ,chmod, ShellString, ShellArray } from 'shelljs' +import shelljs from 'shelljs' + + +interface CliDefaultOptions extends DefaultOptions{ + processName: string + autostart: boolean + asciify: boolean + clear: boolean + color: bare.Format + prompt: string + commander: boolean + signals: boolean + autoLogger: boolean + resize: boolean + version: null + warning: boolean + pid: boolean + promiseRejection: boolean, + font?:string +} + +// const red = clc.red.bold; +// const cyan = clc.cyan.bold; +const blue = clc.blueBright.bold; +const {green} = clc; +// const yellow = clc.yellow.bold; +const magenta = clc.magenta.bold; +const {reset} = clc; // '\x1b[0m'; + + +let processName = null; +if (process.argv && process.argv[1]) { + processName = path.basename(process.argv[1]); +} else { + processName = process.title || "nodefony"; +} + +const defaultTableCli = { + style: { + head: ["cyan"], + border: ["grey"] + } +}; + +const defaultOptions = { + processName, + autostart: true, + asciify: true, + clear: true, + color: blue, + prompt: "default", // "default" || "rxjs" + commander: true, + signals: true, + autoLogger: true, + resize: false, + version: null, + warning: false, + pid: false, + promiseRejection: true +}; + +class Cli extends Service { + public declare options : CliDefaultOptions + public debug : DebugType = false + public environment : EnvironmentType | string + public inquirer : typeof inquirer = inquirer + public commander : typeof program | null = null + //public emoji: typeof emoji = emoji + public pid : number | null + public interactive : boolean = false + public prompt :Rx.Subject| any | null = null + public unhandledRejections: Map, string> = new Map(); + public response: Record = {}; + public timers: Record = {}; + public wrapperLog : (...data: any[]) => void = console.log + public version : string = "" + public clui: typeof clui = clui + public clc :typeof clc = clc + public spinner : clui.Spinner | null = null + public blankLine : (() => void ) | null = null + public columns : number = 0 + public rows : number = 0 + + // eslint-disable-next-line complexity + constructor(name: string, container?: Container , notificationsCenter?: Event | false, options?: CliDefaultOptions) { + options = extend({}, defaultOptions, options); + if(options){ + name ||= options.processName; // Utilisation de l'opérateur de fusion nullish + options = extend({}, defaultOptions, options); + }else{ + options = extend({}, defaultOptions, options); + } + + // Obtenez le nombre d'arguments + const numArgs: number = arguments.length; + switch (numArgs) { + case 0: + case 1:{ + // @ts-ignore + super(name); + break; + } + case 2:{ + // @ts-ignore + super(name, undefined, undefined, container); + break; + } + case 3:{ + // @ts-ignore + super(name, container, undefined, notificationsCenter); + break; + } + default:{ + // @ts-ignore + super(name, container, notificationsCenter, options); + } + } + this.options = options; + if ( process.env.NODE_ENV ){ + this.environment = process.env.NODE_ENV + }else{ + this.environment = "production"; + } + + this.setProcessTitle(); + this.pid = this.options.pid ? this.setPid() : null; + + if (this.options.autoLogger) { + this.initSyslog(); + } + + this.initUi(); + + // Optimisation : Utilisation de fireAsync pour les opérations asynchrones + this.prependOnceListener("onStart", async () => { + try { + this.initPrompt(); + await this.fireAsync("onStart", this); + } catch (e) { + this.log(e, "ERROR"); + } + }); + + this.initCommander(); + + // Gestion des avertissements + if (this.options.warning) { + this.handleWarnings(); + } else { + process.env.NODE_NO_WARNINGS = "1"; // Utilisation d'une chaîne pour la clarté + } + + // Gestion des signaux + if (this.options.signals) { + this.handleSignals(); + } + + // Gestion des rejets de promesses + if (this.options.promiseRejection) { + this.listenRejection(); + } + + // Affichage ASCII (asciify) + if (name && this.options.asciify) { + this.showAsciify(name) + .then(async () => { + if (this.options.autostart) { + await this.fireAsync("onStart", this); + } + }) + .catch((e) => this.log(e, "ERROR")); + } else if (this.options.autostart) { + try { + const func = async function(this: Cli){ + await this.fireAsync("onStart", this); + } + func.call(this) + } catch (e) { + this.log(e, "ERROR"); + } + } + } + + + // eslint-disable-next-line complexity + // constructor (name: string, container?: Container, notificationsCenter?: Event | false , options: CliDefaultOptions ) { + // switch (arguments.length) { + // case 0: + // options = extend({}, defaultOptions); + // name = options.processName; + // super(options.processName, undefined, undefined, options); + // break; + // case 1: + // if (typeof name === "object" && name !== null) { + // options = extend({}, defaultOptions, name); + // name = options.processName; + // super(options.processName, undefined, undefined, options); + // } else { + // options = extend({}, defaultOptions); + // name ||= options.processName; + // super(name, undefined, undefined, options); + // } + // break; + // case 2: + // if (container instanceof Container) { + // options = extend({}, defaultOptions); + // name ||= options.processName; + // super(name, container, undefined, options); + // } else if (typeof container === "object" && container !== null) { + // options = extend({}, defaultOptions, container); + // name ||= options.processName; + // super(name, undefined, undefined, options); + // } else { + // options = extend({}, defaultOptions); + // name ||= options.processName; + // super(name, container, undefined, options); + // } + // break; + // default: + // options = extend({}, defaultOptions, options); + // name ||= options.processName; + // super(name, container, notificationsCenter, options); + // } + // this.options = options + // this.environment = process.env.NODE_ENV || "production"; + // // process.env.NODE_ENV = this.environment; + // this.unhandledRejections = new Map(); + + // this.setProcessTitle(); + // this.pid = null; + // if (this.options.pid) { + // this.setPid(); + // } + // this.wrapperLog = console.log; + // this.response = {}; + // this.timers = {}; + // if (this.options.autoLogger) { + // this.initSyslog(); + // } + // this.initUi(); + // this.prependOnceListener("onStart", async () => { + + // }); + + // this.commander = null; + // this.initCommander(); + + // if (this.options.warning) { + // process.on("warning", (warning) => { + // this.log(warning, "WARNING"); + // this.fire("onNodeWarning", warning, this); + // }); + // } else { + // process.env.NODE_NO_WARNINGS = 1; + // } + + // /** + // * @signals + // */ + // if (this.options.signals) { + // process.on("SIGINT", () => { + // this.blankLine(); + // this.wrapperLog = console.log; + // this.log("SIGINT", "CRITIC"); + // // this.clear(); + // this.fire("onSignal", "SIGINT", this); + // process.nextTick(() => { + // this.terminate(); + // }); + // }); + // process.on("SIGTERM", () => { + // this.blankLine(); + // this.wrapperLog = console.log; + // this.log("SIGTERM", "CRITIC"); + // this.fire("onSignal", "SIGTERM", this); + // process.nextTick(() => { + // this.terminate(); + // }); + // }); + // process.on("SIGHUP", () => { + // this.blankLine(); + // this.wrapperLog = console.log; + // this.log("SIGHUP", "CRITIC"); + // this.fire("onSignal", "SIGHUP", this); + // process.nextTick(() => { + // this.terminate(); + // }); + // }); + // process.on("SIGQUIT", () => { + // this.blankLine(); + // this.wrapperLog = console.log; + // this.log("SIGQUIT", "CRITIC"); + // // this.clear(); + // this.fire("onSignal", "SIGQUIT", this); + // process.nextTick(() => { + // this.terminate(); + // }); + // }); + // process.on("uncaughtException", (err) => { + // this.log(err, "CRITIC", "uncaughtException"); + // }); + // } + + // /** + // * @promiseRejection + // */ + // if (this.options.promiseRejection) { + // this.listenRejection(); + // } + + // /** + // * ASCIIFY + // */ + // if (name && this.options.asciify) { + // this.showAsciify(name) + // .then(async () => { + // if (this.options.autostart) { + // try { + // await this.fireAsync("onStart", this); + // } catch (e) { + // this.log(e, "ERROR"); + // } + // } + // }); + // } else if (this.options.autostart) { + // try { + // async () => { + // try { + // await this.fireAsync("onStart", this); + // } catch (e) { + // this.log(e, "ERROR"); + // } + // }; + // } catch (e) { + // this.log(e, "ERROR"); + // } + // } + // } + + // Méthode privée pour gérer les signaux + private handleSignals(): void { + const signalHandler = (signal: string) => { + if( this.blankLine){ + this.blankLine(); + } + this.wrapperLog = console.log; + this.log(signal, "CRITIC"); + this.fire("onSignal", signal, this); + process.nextTick(() => { + this.terminate(); + }); + }; + process.on("SIGINT", () => signalHandler("SIGINT")); + process.on("SIGTERM", () => signalHandler("SIGTERM")); + process.on("SIGHUP", () => signalHandler("SIGHUP")); + process.on("SIGQUIT", () => signalHandler("SIGQUIT")); + } + + + // Méthode privée pour gérer les avertissements + private handleWarnings(): void { + process.on("warning", (warning) => { + this.log(warning, "WARNING"); + this.fire("onNodeWarning", warning, this); + }); + } + + start () : Promise{ + return new Promise(async (resolve, reject) => { + try { + if (this.options.autostart) { + if (this.options.asciify) { + this.once("onStart", () => resolve(this)); + } else { + await this.fireAsync("onStart", this); + return resolve(this); + } + } else if (this.options.asciify) { + this.once("onAsciify", async () => { + await this.fireAsync("onStart", this); + return resolve(this); + }); + } else { + await this.fireAsync("onStart", this); + return resolve(this); + } + } catch (e) { + return reject(e); + } + }); + } + + idle () { + let resolve = null; + let reject = null; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return (function () { + return { + resolve, + promise, + reject + }; + }()); + // return this.idleId = setInterval(() => {}, 0); + } + + checkVersion (version : string | semver.SemVer | null | undefined = null) { + if (!version) { + version = this.version; + } + const res = semver.valid(version); + if (res) { + return res; + } + throw new Error(`Not valid version : ${version} check http://semver.org `); + } + + async showAsciify (name: string | null = null) { + if (!name) { + name = this.name; + } + + return await this.asciify(` ${name}`, { + font: this.options.font || "standard" + }) + .then((data) => { + this.fire("onAsciify", data); + if (this.options.clear) { + this.clear(); + } + const color = this.options.color || blue; + console.log(color(data)); + return data; + }) + .catch((err) => { + this.log(err, "ERROR"); + throw err; + }); + } + + showBanner () { + const version = this.commander ? this.commander.version() : this.options.version || "1.0.0"; + let banner = null; + if (this.options.version) { + banner = ` Version : ${blue(version)} Platform : ${green(process.platform)} Process : ${green(process.title)} Pid : ${process.pid}`; + if( this.blankLine){ + this.blankLine(); + } + console.log(banner); + } + return banner; + } + + listenRejection () { + process.on("rejectionHandled", (promise) => { + this.log("PROMISE REJECTION EVENT ", "CRITIC", "rejectionHandled"); + this.unhandledRejections.delete(promise); + }); + process.on("unhandledRejection", (reason: string, promise: Promise) => { + this.log(`WARNING !!! PROMISE CHAIN BREAKING : ${reason}`, "WARNING", "unhandledRejection"); + console.trace(promise); + this.unhandledRejections.set(promise, reason); + }); + } + + setPid () : number { + return this.pid = process.pid; + } + + setProcessTitle (name?: string) { + if (name) { + process.title = name.replace(new RegExp("\\s", "gi"), "").toLowerCase(); + } else { + process.title = this.name.replace(new RegExp("\\s", "gi"), "").toLowerCase(); + } + return process.title; + } + + logEnv () { + return `${blue(` \x1b ${this.name}`)} Nodefony Environment : ${magenta(this.environment)}`; + } + + initCommander () { + if (this.options.commander) { + this.commander = program; + if (this.options.version) { + this.setCommandVersion(this.options.version); + } + return program; + } + return null; + } + + initUi () { + this.blankLine = function (this: Cli) { + // return () => { + // console.log("") + // } + const myLine = new this.clui.Line().fill(); + return () => { + myLine.output(); + }; + }.call(this); + if (this.options.resize) { + this.resize(); + } + } + + initPrompt () : void { + this.inquirer = inquirer; + if (this.options.prompt === "rxjs") { + this.prompt = new Rx.Subject(); + const prompt = inquirer.createPromptModule(); + prompt(this.prompt); + } else { + this.prompt = inquirer.createPromptModule(); + } + } + + getFonts () : void { + asciify.getFonts((err, fonts) => { + fonts.forEach((ele)=>{ + this.log(ele) + }); + }); + } + + async asciify (txt: string, options?: object , callback?: (error:Error, data:string) => void ): Promise { + return new Promise((resolve, reject) => { + asciify(txt, extend({ + font: "standard" + }, options), (error, data) => { + if (callback && typeof callback === "function") { + return callback(error, data); + } + if (error) { + return reject(error); + } + return resolve(data); + }); + }); + } + + async parseCommand (argv: readonly string[] | undefined) : Promise{ + if (! this.commander){ + throw new Error(`commander not found`) + } + const parser = await this.commander.parse(argv || process.argv); + const {debug} = this.commander.opts(); + if (debug) { + this.debug = debug; + } else { + this.debug = false; + } + const {interactive} = this.commander.opts(); + if (interactive) { + this.interactive = interactive; + } else { + this.interactive = false; + } + return Promise.resolve(parser); + } + + setOption (flags: string, description?: string , defaultValue?: string | boolean | string[] | undefined) : commander.Command { + if (this.commander){ + return this.commander.option(flags, description, defaultValue); + } + throw new Error(`Commender not found`) + } + + + setCommandVersion (version: string) : commander.Command { + if (this.commander && typeof this.commander.version === "function") { + return this.commander.version(version, "-v, --version", "Nodefony Current Version"); + } + throw new Error(`Commender not found`) + } + + setCommand (nameAndArgs: string, description:string , options?: commander.ExecutableCommandOptions | undefined): commander.Command { + if (this.commander) { + return this.commander.command(nameAndArgs, description, options); + } + throw new Error(`Commender not found`) + } + + showHelp (quit: boolean , context: commander.HelpContext | undefined ) : void | never{ + if( ! this.commander ){ + throw new Error(`Commender not found`) + } + if (quit) { + return this.commander.help(context); + } + return this.commander.outputHelp(context); + } + + createProgress (size: number) { + return new this.clui.Progress(size); + } + + createSparkline (values: number[] , suffix: string) : string{ + if (values) { + try { + return this.clui.Sparkline(values, suffix || ""); + } catch (e) { + this.log(e, "ERROR"); + throw e; + } + } + throw new Error(`Bad vlue : ${values}`) + } + + getSeparator (sep: string | undefined) { + if (sep) { + return new inquirer.Separator(sep); + } + return new inquirer.Separator("--------"); + } + + getSpinner (message: string, design?: string[]) { + return new this.clui.Spinner(message, design ); + } + + startSpinner (message: string, design?: string[]) : clui.Spinner | null { + try { + this.spinner = this.getSpinner(message, design); + this.wrapperLog = this.spinner.message; + this.spinner.start(); + return this.spinner; + } catch (e) { + this.log(e, "ERROR"); + throw e; + } + } + + stopSpinner (/* message, options*/) { + if (this.spinner) { + this.spinner.stop(); + this.wrapperLog = console.log; + this.spinner = null; + return true; + } + this.log(new Error("Spinner is not started "), "ERROR"); + return false; + } + + displayTable (datas : any[], options = defaultTableCli, syslog : Syslog | null = null) { + if (!datas || !datas.length) { + return new Table(extend({}, defaultTableCli, options)); + } + const table = new Table(extend({}, defaultTableCli, options)); + if (datas) { + for (let i = 0; i < datas.length; i++) { + table.push(datas[i]); + } + if (syslog) { + syslog.log(`\n${table.toString()}`); + } else { + console.log(table.toString()); + } + } + return table; + } + + static niceBytes (x: string | number) { + const units :string[] = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] + let n = parseInt(x, 10) || 0, + l = 0; + while (n >= 1024) { + n /= 1024; + l++; + } + return `${n.toFixed(n >= 10 || l < 1 ? 0 : 1)} ${units[l]}`; + } + + static niceUptime (date: moment.MomentInput, suffix: boolean | undefined) { + return moment(date).fromNow(suffix || false); + } + static niceDate (date: moment.MomentInput, format: string | undefined) { + return moment(date).format(format); + } + + getEmoji (name: string) { + if (name) { + return get(name); + } + return random().emoji; + } + + clear () { + console.clear() + } + + reset () { + process.stdout.write(reset); + } + + resize () { + process.stdout.on("resize", () => { + this.columns = process.stdout.columns; + this.rows = process.stdout.rows; + this.fire("onResize", this.columns, this.rows, this); + }); + } + + rm (...files: string[] ) : shelljs.ShellString { + return shelljs.rm(...files ); + } + + // cp (options: string, source: string | string[], dest: string) : ShellString{ + // return cp(options, source, dest ); + // } + + // cd (dir?: string | undefined) : ShellString{ + // return cd( dir); + // } + + // ln (options: string, source: string, dest: string): ShellString { + // return ln(options, source, dest ); + // } + + // mkdir (...dir: string[]): ShellString{ + // return mkdir(...dir ); + // } + + // chmod (options: string, mode: string | number, file: string): ShellString { + // return chmod(options, mode, file); + // } + + // ls (...paths: string[]): ShellArray{ + // return ls(...paths); + // } + + createDirectory ( + myPath: fs.PathLike | fs.PathOrFileDescriptor , + mode : fs.MakeDirectoryOptions | fs.Mode | null | undefined, + callback: (file:FileClass) =>void | null, + force: boolean + ){ + let file = null; + if (!callback) { + return new Promise((resolve, reject) => { + try { + fs.mkdirSync(myPath, mode); + file = new FileClass(myPath); + return resolve(file); + } catch (e: any) { + switch (e.code) { + case "EEXIST": + if (force) { + file = new FileClass(myPath); + return resolve(file); + } + break; + } + return reject(e); + } + }) + .catch((e) => { + throw e; + }); + } + try { + fs.mkdirSync(myPath, mode); + file = new FileClass(myPath); + callback(file); + return file; + } catch (e: any) { + switch (e.code) { + case "EEXIST": + if (force) { + file = new FileClass(myPath); + callback(file); + return file; + } + break; + } + throw e; + } + } + + existsSync (myPath: fs.PathLike) { + if (!myPath) { + throw new Error("existsSync no path found"); + } + return fs.existsSync(myPath); + } + + exists (myPath: fs.PathLike, mode: number | undefined, callback: fs.NoParamCallback) { + if (!myPath) { + throw new Error("exists no path found"); + } + if (!mode) { + mode = fs.constants.R_OK | fs.constants.W_OK; + } + if (callback) { + return fs.access(myPath, mode, callback); + } + return fs.existsSync(myPath); + } + + terminate (code: number= 0, quiet?: boolean) { + if (quiet) { + return code; + } + if (code === 0) { + process.exitCode = code; + } + process.exit(code); + } + + static quit (code: number) { + if (code === 0) { + process.exitCode = code; + } + process.exit(code); + } + + startTimer (name: string) { + if (name in this.timers) { + throw new Error(`Timer : ${name} already exist !! stopTimer to clear`); + } + try { + this.log(`BEGIN TIMER : ${name}`, "INFO"); + this.timers[name] = name; + return console.time(name); + } catch (e) { + if (name in this.timers) { + delete this.timers[name]; + } + throw e; + } + } + + stopTimer (name: string) { + if (!name) { + for (const timer in this.timers) { + this.stopTimer(this.timers[timer]); + } + } + try { + if (name in this.timers) { + this.log(`END TIMER : ${name}`, "INFO"); + delete this.timers[name]; + return console.timeEnd(name); + } + throw new Error(`Timer : ${name} not exist !! startTimer before`); + } catch (e) { + if (name in this.timers) { + delete this.timers[name]; + } + throw e; + } + } + + getCommandManager (manager: string) { + if (process.platform === "win32") { + switch (manager) { + case "npm": + return "npm.cmd"; + case "yarn": + return "yarn.cmd"; + case "pnpm": + return "pnpm.cmd"; + default: + throw new Error(`bad manager : ${manager}`); + } + } else { + switch (manager) { + case "npm": + return "npm"; + case "yarn": + return "yarn"; + case "pnpm": + return "pnpm"; + default: + throw new Error(`bad manager : ${manager}`); + } + } + } + + runPackageManager (argv : string[]= [], cwd :string= path.resolve("."), env: EnvironmentType , manager: string) { + const currentenv = process.env.NODE_ENV; + switch (env) { + case "dev": + case "development": + switch (manager) { + case "npm": + case "yarn": + case "pnpm": + break; + } + process.env.NODE_ENV = "development"; + break; + case "prod": + case "production": + switch (manager) { + case "npm": + argv.push("--omit=dev"); + break; + case "yarn": + argv.push("--production"); + break; + case "pnpm": + argv.push("--prod"); + break; + } + process.env.NODE_ENV = "production"; + break; + default: + process.env.NODE_ENV = this.environment; + } + return new Promise((resolve, reject) => { + try { + this.debug = this.commander ? this.commander.opts().debug || false : false; + this.log(`Command : ${manager} ${argv.join(" ")} in cwd : ${cwd}`); + const exe = this.getCommandManager(manager); + this.spawn(exe, argv, { + cwd, + env: process.env, + stdio: "inherit" + }, (code: number) => { + process.env.NODE_ENV = currentenv; + if (code === 0) { + return resolve(code); + } + return resolve(new Error(`Command : ${manager} ${argv.join(" ")} cwd : ${cwd} Error Code : ${code}`)); + }); + } catch (e) { + process.env.NODE_ENV = currentenv; + this.log(e, "ERROR"); + return reject(e); + } + }); + } + + async npm (argv = [], cwd = path.resolve("."), env : EnvironmentType= "dev") { + return this.runPackageManager(argv, cwd, env, "npm"); + } + + async yarn (argv = [], cwd = path.resolve("."), env : EnvironmentType= "dev") { + return this.runPackageManager(argv, cwd, env, "yarn"); + } + + async pnpm (argv = [], cwd = path.resolve("."), env : EnvironmentType= "dev") { + return this.runPackageManager(argv, cwd, env, "pnpm"); + } + + spawn (command: string, args: readonly string[] | undefined, options : SpawnOptions | undefined , close : ((code:number) =>void) | null= null) { + return new Promise((resolve, reject) => { + let cmd = null; + try { + if (!args ){ + args=[] + } + this.log(`Spawn : ${command} ${args.join(" ")}`, "INFO"); + cmd = spawn(command, args, options || {}); + if (cmd.stdout) { + cmd.stdout.on("data", (data) => { + const str = data.toString(); + if (str) { + if (this.debug) { + this.log(`${command} :\n`, "INFO", "STDOUT"); + } + process.stdout.write(str); + } + }); + } + if (cmd.stderr) { + cmd.stderr.on("data", (data) => { + const str = data.toString(); + if (str) { + if (this.debug) { + this.log(`${command} :\n`, "INFO", "STDERR"); + } + process.stdout.write(str); + } + }); + } + cmd.on("close", (code: number) => { + if (this.debug) { + this.log(`Child Process exited with code ${code}`, "DEBUG"); + } + if (close) { + close(code); + } + if (code !== 0) { + if (!args){ + args =[] + } + this.log(`Spawn : ${command} ${args.join(" ")} Error Code : ${code}`, "ERROR"); + } + return resolve(code); + }); + cmd.on("error", (err) => { + this.log(err, "ERROR"); + return reject(err); + }); + if (cmd.stdin) { + process.stdin.pipe(cmd.stdin); + } + } catch (e) { + this.log(e, "ERROR"); + return reject(e); + } + }); + } + + spawnSync (command: string, args: readonly string[], options: SpawnSyncOptionsWithStringEncoding) : SpawnSyncReturns { + let cmd = null; + try { + cmd = spawnSync(command, args, options); + if (cmd.error) { + throw cmd.error; + } + if (cmd.stderr) { + this.log(cmd.stderr.toString(), "ERROR"); + } + if (cmd.stdout) { + this.log(cmd.stdout.toString(), "INFO"); + } + } catch (e) { + this.log(e, "ERROR"); + throw e; + } + return cmd; + } +} + +export default Cli; diff --git a/src/Container.ts b/src/Container.ts index 9002d5e..732aef1 100644 --- a/src/Container.ts +++ b/src/Container.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import shortId from "shortid"; +import { v4 as uuidv4 } from 'uuid'; import {extend, isPlainObject} from './Tools' const generateId = function (): string { - return shortId.generate(); + return uuidv4(); }; const ISDefined = function (ele: any): boolean { @@ -13,7 +13,7 @@ const ISDefined = function (ele: any): boolean { return false; }; -const parseParameterString = function (this: Container['parameters'] | ProtoParametersPrototype, str: string, value?: any): any { +const parseParameterString = function (this: Container['parameters'] | ProtoParametersPrototype, str: string, value?: any): DynamicParam | null { if (!this) { throw new Error(`Bad call`); } @@ -118,7 +118,7 @@ class Container { } } - public get(name: string) { + public get(name: string) : any{ if (this.services && (name in this.services)) { return this.services[name]; } @@ -145,11 +145,11 @@ class Container { return false; } - public has(name: string) : any | null { + public has(name: string) : boolean | any { if( this.services){ return this.services[name] } - return null + return false } public addScope(name: string) : Scope | object { @@ -166,7 +166,7 @@ class Container { } - public leaveScope(scope: Scope) { + public leaveScope(scope: Scope): void { if (this.scopes[scope.name]) { const sc = this.scopes[scope.name][scope.id]; if (sc) { @@ -176,7 +176,7 @@ class Container { } } - public removeScope(name: string) { + public removeScope(name: string) : void { const scopesForName = this.scopes[name]; if (scopesForName) { const scopesArray = Object.values(scopesForName); @@ -188,7 +188,7 @@ class Container { } } - public setParameters(name: string, ele: T): T | Error { + public setParameters(name: string, ele: T): DynamicParam | null { if (typeof name !== "string") { throw new Error("setParameters : container parameter name must be a string") } @@ -199,7 +199,7 @@ class Container { return parseParameterString.call(this.parameters, name, ele) } - public getParameters(name: string) { + public getParameters(name: string): DynamicParam | null{ //console.log(`main getParameters : ${name}`) if(name){ const res = parseParameterString.call(this.parameters, name); @@ -209,7 +209,7 @@ class Container { throw new Error(`Bad name : ${name}`) } - public clean() { + public clean() : void { this.services = null; this.parameters = null; } @@ -232,7 +232,7 @@ class Scope extends Container { this.parameters = Object.create(this.parent.protoParameters.prototype); } - public getParameters(name: string, merge: boolean = true, deep: boolean = true) { + public override getParameters(name: string, merge: boolean = true, deep: boolean = true) : DynamicParam | null{ const res = parseParameterString.call(this.parameters, name) const obj = this.parent.getParameters(name) if(ISDefined(res)) { @@ -244,11 +244,14 @@ class Scope extends Container { return obj } - public clean() { + public override clean() : void{ this.parent = null; return super.clean(); } } export default Container +export{ + DynamicParam +} diff --git a/src/Error.ts b/src/Error.ts index 9317d2c..6ac618d 100644 --- a/src/Error.ts +++ b/src/Error.ts @@ -93,7 +93,7 @@ const isMongooseError = function (error: Error) { class nodefonyError extends Error { - public code : number | null + public override code : number | null public error? : Error public errorType: string //public actual : string @@ -216,7 +216,7 @@ class nodefonyError extends Error { return "Error"; } - toString () { + override toString () { let err = ""; switch (this.errorType) { case "Error": diff --git a/src/Event.ts b/src/Event.ts index 09c7027..95045a5 100644 --- a/src/Event.ts +++ b/src/Event.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import {EventEmitter} from "node:events"; -import {isEmpty , get, isFunction} from 'lodash' +import _ from 'lodash' +const {isEmpty , get, isFunction} = _ declare module 'events' { interface EventEmitter { @@ -18,7 +19,8 @@ interface EventDefaultInterface { } interface EventOptionInterface { - nbListeners: number + nbListeners?: number + [key: string]: any; } type ContextType = any; @@ -38,7 +40,7 @@ class Event extends EventEmitter { } } - settingsToListen (localSettings: EventDefaultInterface, context?: ContextType ) { + override settingsToListen (localSettings: EventDefaultInterface, context?: ContextType ) { for (const i in localSettings) { const res = regListenOn.exec(i); if (!res) { @@ -52,7 +54,7 @@ class Event extends EventEmitter { } } - listen(context: ContextType, eventName: string | symbol, listener: (...args: any[]) => void): (...args: any[]) => boolean { + override listen(context: ContextType, eventName: string | symbol, listener: (...args: any[]) => void): (...args: any[]) => boolean { const event = eventName; // eslint-disable-next-line @typescript-eslint/no-this-alias const contextClosure = this; @@ -65,11 +67,11 @@ class Event extends EventEmitter { }; } - fire(eventName: string | symbol, ...args: any[]): boolean { + override fire(eventName: string | symbol, ...args: any[]): boolean { return super.emit(eventName, ...args); } - async emitAsync (eventName: string | symbol, ...args: any[]): Promise { + override async emitAsync (eventName: string | symbol, ...args: any[]): Promise { const handler = get(this._events, eventName); if (isEmpty(handler) && !isFunction(handler)) { return false; @@ -95,7 +97,7 @@ class Event extends EventEmitter { return tab; } - async fireAsync (eventName: string | symbol, ...args: any[]) { + override async fireAsync (eventName: string | symbol, ...args: any[]) { return this.emitAsync(eventName, ...args); } } diff --git a/src/Nodefony.ts b/src/Nodefony.ts index decef65..07a7075 100644 --- a/src/Nodefony.ts +++ b/src/Nodefony.ts @@ -5,6 +5,7 @@ import Container from './Container'; import Syslog from './syslog/Syslog'; import Error from './Error' import Service from './Service' +import Cli from './Cli' import { extend, isEmptyObject, @@ -21,6 +22,16 @@ import {version} from '../package.json'; //const require = createRequire(import.meta.url); //const {version} = require("../package.json"); +enum Environment { + "dev", + "development", + "prod", + "production", + "stage" +} +type EnvironmentType = keyof typeof Environment +type DebugType = boolean | string | string[] + class Nodefony { private static instance: Nodefony ; @@ -43,6 +54,9 @@ class Nodefony { static Service = Service; public Service: typeof Service = Service; + static Cli = Cli; + public Cli: typeof Cli = Cli; + public extend : typeof extend = extend; public isEmptyObject : typeof isEmptyObject = isEmptyObject; public isPlainObject : typeof isPlainObject = isPlainObject; @@ -82,6 +96,8 @@ const kernel = Nodefony.getKernel() export default nodefony export { - kernel + kernel, + EnvironmentType, + DebugType } diff --git a/src/Service.ts b/src/Service.ts index 33cfba1..bcb4c2b 100644 --- a/src/Service.ts +++ b/src/Service.ts @@ -1,12 +1,12 @@ /* eslint-disable @typescript-eslint/ban-types */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import Container from "./Container" +import Container, {DynamicParam} from "./Container" import Event , {EventDefaultInterface} from "./Event" import Pdu, {Severity, Msgid, Message, } from './syslog/Pdu' import Syslog ,{SyslogDefaultSettings} from "./syslog/Syslog"; -interface DefaultOptions { +export interface DefaultOptions extends EventDefaultInterface{ events?: { nbListeners: number; } @@ -26,7 +26,7 @@ const settingsSyslog : SyslogDefaultSettings = { class Service { name: string; - private options: DefaultOptions; + protected options: DefaultOptions; private container: Container | null; private kernel: any; // Remplacez ce type par le type réel de kernel si possible private syslog: Syslog | null; @@ -61,7 +61,7 @@ class Service { throw new Error("Service nodefony notificationsCenter not valid, must be an instance of nodefony.Events"); } if (notificationsCenter !== false) { - this.notificationsCenter = new Event(this.options, this, this.options.events); + this.notificationsCenter = new Event(this.options, this, this.options); this.notificationsCenter.on("error", (err: any) => { this.log(err, "ERROR", "Error events"); }); @@ -282,13 +282,13 @@ class Service { return false; } - getParameters(name: string) { - return this.container?.getParameters(name); + getParameters(name: string) : DynamicParam | null { + return (this.container).getParameters(name); } - setParameters(name: string, ele: T) : T | Error { + setParameters(name: string, ele: T) : DynamicParam | null { if( this.container){ - return this.container?.setParameters(name,ele ); + return (this.container).setParameters(name,ele ); } throw new Error(`container not initialized`) } diff --git a/src/Tools.ts b/src/Tools.ts index f9765bb..6a73cc7 100644 --- a/src/Tools.ts +++ b/src/Tools.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import Container from "./Container"; -import {isFunction, isRegExp, isArray} from 'lodash' +import _ from 'lodash'; +const { isArray, isFunction, isRegExp } = _; const myobj = {}; const hasOwn = myobj.hasOwnProperty; const fnToString = hasOwn.toString; @@ -53,7 +54,7 @@ const extend= (...args: any[])=> { // Extend Nodefony itself if only one argument is passed if (i === length) { // eslint-disable-next-line @typescript-eslint/no-this-alias - target = this; + target = {}; i--; } for (; i < length; i++) { diff --git a/src/finder/File.ts b/src/finder/File.ts index eb25012..03cd238 100644 --- a/src/finder/File.ts +++ b/src/finder/File.ts @@ -21,7 +21,7 @@ class File extends FileClass { return this.children.length; } - toJson () : FileInterface{ + override toJson () : FileInterface{ const obj : FileInterface= { path: this.path, name: this.name, diff --git a/src/finder/FileResult.ts b/src/finder/FileResult.ts index 283fd35..3b9d7ef 100644 --- a/src/finder/FileResult.ts +++ b/src/finder/FileResult.ts @@ -11,7 +11,7 @@ class FileResult extends Result { Array.prototype.find } - toString () : string { + override toString () : string { let txt = ""; for (let index = 0; index < this.length; index++) { const info = this[index]; @@ -20,7 +20,7 @@ class FileResult extends Result { return txt; } - toJson (json :any[]= []) : string{ + override toJson (json :any[]= []) : string{ for (let index = 0; index < this.length; index++) { const info :File = this[index]; switch (info.type) { @@ -45,7 +45,7 @@ class FileResult extends Result { return this; } - find (predicate: (value: any, index: number, obj: any[]) => value is S, result : FileResult = new FileResult()) : FileResult { + override find (predicate: (value: any, index: number, obj: any[]) => value is S, result : FileResult = new FileResult()) : FileResult { for (let index = 0; index < this.length; index++) { const info: File = this[index]; const unknownType : unknown= predicate diff --git a/src/finder/Finder.ts b/src/finder/Finder.ts index 8bd6a81..42b4246 100644 --- a/src/finder/Finder.ts +++ b/src/finder/Finder.ts @@ -10,7 +10,8 @@ import FileClass from '../FileClass' import Result from './Result'; import path from 'node:path' import fs from 'node:fs' -import { isNull } from 'lodash'; +import _ from 'lodash'; +const { isNull} = _; interface DefaultSettingsInterface{ recurse?: boolean diff --git a/src/finder/Result.ts b/src/finder/Result.ts index b1666e6..7f02ebb 100644 --- a/src/finder/Result.ts +++ b/src/finder/Result.ts @@ -29,7 +29,7 @@ class Result extends Array { return JSON.stringify(json); } - toString (json : any[]= []) : string{ + override toString (json : any[]= []) : string{ for (let index = 0; index < this.length; index++) { const ele : any= this[index]; json.push(ele);