@nmakarov/cli-toolkit 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2896,6 +2896,288 @@ var Db = class {
2896
2896
  return this.isConnected && this.knexInstance !== null;
2897
2897
  }
2898
2898
  };
2899
+
2900
+ // src/logger/index.ts
2901
+ import chalk from "chalk";
2902
+ import util from "util";
2903
+
2904
+ // src/logger/transports.ts
2905
+ var ConsoleTransport = class {
2906
+ write(payload) {
2907
+ console.info(payload);
2908
+ }
2909
+ };
2910
+ var ParentProcessTransport = class {
2911
+ write(payload) {
2912
+ if (process.env.VITEST || process.env.NODE_ENV === "test") {
2913
+ console.info(payload);
2914
+ return;
2915
+ }
2916
+ if (typeof process.send === "function" && process.connected === true) {
2917
+ process.send(payload);
2918
+ } else {
2919
+ console.info(payload);
2920
+ }
2921
+ }
2922
+ };
2923
+
2924
+ // src/logger/index.ts
2925
+ var ALL_LEVELS = [
2926
+ "silly",
2927
+ "debug",
2928
+ "logic",
2929
+ "info",
2930
+ "notice",
2931
+ "warn",
2932
+ "error",
2933
+ "results",
2934
+ "request",
2935
+ "response",
2936
+ "progress"
2937
+ ];
2938
+ var LEVEL_COLORS = {
2939
+ error: chalk.red.bold,
2940
+ warn: chalk.rgb(255, 165, 0),
2941
+ notice: chalk.cyan,
2942
+ info: chalk.white.bold,
2943
+ logic: chalk.gray,
2944
+ debug: chalk.gray,
2945
+ silly: chalk.gray,
2946
+ request: chalk.green,
2947
+ response: chalk.yellow,
2948
+ progress: chalk.green,
2949
+ results: chalk.magenta
2950
+ };
2951
+ var CliToolkitLogger = class {
2952
+ options;
2953
+ transport;
2954
+ startTimes = {};
2955
+ lastProgressTimes = {};
2956
+ constructor(options = {}) {
2957
+ this.options = this.normalizeOptions(options);
2958
+ this.transport = this.options.route === "ipc" ? new ParentProcessTransport() : new ConsoleTransport();
2959
+ }
2960
+ setMode(mode) {
2961
+ if (!this.isValidMode(mode)) {
2962
+ throw new Error(`Unsupported logger mode: ${mode}`);
2963
+ }
2964
+ this.options.mode = mode;
2965
+ }
2966
+ debug(message, ...chunks) {
2967
+ this.out({ level: "debug", message, chunks });
2968
+ }
2969
+ info(message, ...chunks) {
2970
+ this.out({ level: "info", message, chunks });
2971
+ }
2972
+ notice(message, ...chunks) {
2973
+ this.out({ level: "notice", message, chunks });
2974
+ }
2975
+ warn(message, ...chunks) {
2976
+ this.out({ level: "warn", message, chunks });
2977
+ }
2978
+ error(message, ...chunks) {
2979
+ this.out({ level: "error", message, chunks });
2980
+ }
2981
+ logic(message, ...chunks) {
2982
+ this.out({ level: "logic", message, chunks });
2983
+ }
2984
+ silly(message, ...chunks) {
2985
+ this.out({ level: "silly", message, chunks });
2986
+ }
2987
+ results(results) {
2988
+ this.out({ level: "results", message: "results", results });
2989
+ }
2990
+ request(operation, ...chunks) {
2991
+ const message = this.inspectChunks([operation, ...chunks]);
2992
+ this.out({ level: "request", message });
2993
+ }
2994
+ response(operation, ...chunks) {
2995
+ const message = this.inspectChunks([operation, ...chunks]);
2996
+ this.out({ level: "response", message });
2997
+ }
2998
+ progress(message, opts) {
2999
+ const { prefix, count, total } = opts;
3000
+ const paddedTotal = String(total).length;
3001
+ const paddedCount = String(count).padStart(paddedTotal, " ");
3002
+ const payload = {
3003
+ level: "progress",
3004
+ message,
3005
+ count: paddedCount,
3006
+ total,
3007
+ prefix
3008
+ };
3009
+ if (!this.startTimes[prefix ?? ""]) {
3010
+ this.startTimes[prefix ?? ""] = Date.now();
3011
+ }
3012
+ if (this.options.progressTimes) {
3013
+ const elapsedSeconds = (Date.now() - this.startTimes[prefix ?? ""]) / 1e3;
3014
+ let remaining = -1;
3015
+ if (count > 1) {
3016
+ const rate = elapsedSeconds / (count - 1);
3017
+ remaining = (total - count) * rate;
3018
+ }
3019
+ payload.elapsed = this.round(elapsedSeconds, 2);
3020
+ payload.remaining = remaining >= 0 ? this.round(remaining, 2) : remaining;
3021
+ }
3022
+ if (count >= total) {
3023
+ delete this.startTimes[prefix ?? ""];
3024
+ delete this.lastProgressTimes[prefix ?? ""];
3025
+ }
3026
+ if (this.shouldOutputProgress(prefix ?? "", count, total)) {
3027
+ this.out(payload);
3028
+ if (this.options.progressThrottle && prefix) {
3029
+ this.lastProgressTimes[prefix] = Date.now();
3030
+ }
3031
+ }
3032
+ }
3033
+ shouldOutputProgress(prefix, count, total) {
3034
+ if (!this.options.progressThrottle) {
3035
+ return true;
3036
+ }
3037
+ if (count === 1 || count === total || !prefix) {
3038
+ return true;
3039
+ }
3040
+ const lastTime = this.lastProgressTimes[prefix];
3041
+ if (!lastTime) {
3042
+ return true;
3043
+ }
3044
+ return Date.now() - lastTime >= this.options.progressThrottle;
3045
+ }
3046
+ out(struct) {
3047
+ if (this.options.silent) {
3048
+ return;
3049
+ }
3050
+ if (!this.options.levels.includes(struct.level)) {
3051
+ return;
3052
+ }
3053
+ if (this.options.prefix && !struct.prefix) {
3054
+ struct.prefix = this.options.prefix;
3055
+ }
3056
+ const output = this.options.mode === "json" ? struct : this.formatLog(struct);
3057
+ this.transport.write(output);
3058
+ }
3059
+ formatLog(struct) {
3060
+ const parts = [];
3061
+ const now = /* @__PURE__ */ new Date();
3062
+ if (this.options.timestamp) {
3063
+ parts.push(now.toISOString());
3064
+ }
3065
+ if (this.options.showLevel) {
3066
+ parts.push(struct.level.toUpperCase());
3067
+ }
3068
+ if (struct.level === "progress") {
3069
+ if (struct.prefix) {
3070
+ parts.push(LEVEL_COLORS[struct.level].bold(struct.prefix));
3071
+ }
3072
+ if (struct.count !== void 0 && struct.total !== void 0) {
3073
+ parts.push(LEVEL_COLORS[struct.level](`${struct.count}/${struct.total}`));
3074
+ }
3075
+ } else if (struct.prefix) {
3076
+ parts.push(chalk.cyan(`[${struct.prefix}]`));
3077
+ }
3078
+ if (struct.message) {
3079
+ const formatter = LEVEL_COLORS[struct.level] ?? chalk.white;
3080
+ parts.push(formatter.bold(struct.message));
3081
+ }
3082
+ if (struct.level === "progress") {
3083
+ if (struct.elapsed !== void 0 && struct.remaining !== void 0) {
3084
+ const formatter = LEVEL_COLORS[struct.level];
3085
+ parts.push(formatter(`${struct.elapsed}/${struct.remaining}`));
3086
+ }
3087
+ }
3088
+ if (struct.chunks && struct.chunks.length) {
3089
+ parts.push(this.inspectChunks(struct.chunks));
3090
+ }
3091
+ if (struct.results) {
3092
+ const formatter = LEVEL_COLORS[struct.level] ?? chalk.white;
3093
+ parts.push(formatter(JSON.stringify(struct.results, null, 4)));
3094
+ }
3095
+ return parts.join(" ");
3096
+ }
3097
+ inspectChunks(chunks) {
3098
+ return chunks.map((chunk) => util.inspect(chunk, { colors: true, depth: null })).join(" ");
3099
+ }
3100
+ normalizeOptions(options) {
3101
+ const { route, mode, prefix, silent, showLevel, timestamp, levels, progress } = options;
3102
+ const shouldUseIpc = this.shouldUseIpcRoute();
3103
+ const normalized = {
3104
+ mode: this.isValidMode(mode) ? mode : "text",
3105
+ route: route ?? (shouldUseIpc ? "ipc" : "console"),
3106
+ prefix,
3107
+ silent: silent ?? false,
3108
+ showLevel: showLevel ?? true,
3109
+ timestamp: timestamp ?? false,
3110
+ levels: this.normalizeLevels(levels),
3111
+ progressTimes: progress?.withTimes ?? false,
3112
+ progressThrottle: progress?.throttleMs
3113
+ };
3114
+ return normalized;
3115
+ }
3116
+ shouldUseIpcRoute() {
3117
+ if (process.env.VITEST || process.env.NODE_ENV === "test") {
3118
+ return false;
3119
+ }
3120
+ return typeof process.send === "function" && process.connected === true;
3121
+ }
3122
+ normalizeLevels(levels) {
3123
+ if (!levels || !levels.length) {
3124
+ return ALL_LEVELS;
3125
+ }
3126
+ const includes = levels.filter((level) => !level.startsWith("-"));
3127
+ const excludes = levels.filter((level) => level.startsWith("-")).map((level) => level.slice(1));
3128
+ const unknown = [...includes, ...excludes].filter((level) => !ALL_LEVELS.includes(level));
3129
+ if (unknown.length) {
3130
+ console.warn(`[Logger] Unknown level(s): ${unknown.join(", ")}`);
3131
+ }
3132
+ const base = includes.length ? includes : ALL_LEVELS;
3133
+ return base.filter((level) => !excludes.includes(level));
3134
+ }
3135
+ isValidMode(mode) {
3136
+ return mode === void 0 || mode === null || mode === "text" || mode === "json";
3137
+ }
3138
+ round(value, places) {
3139
+ const factor = Math.pow(10, places);
3140
+ return Math.round(value * factor) / factor;
3141
+ }
3142
+ };
3143
+
3144
+ // src/init/index.ts
3145
+ import { EventEmitter } from "events";
3146
+ function setup(opts = {}) {
3147
+ const args = new Args({
3148
+ overrides: opts.overrides || {},
3149
+ defaults: opts.defaults || {}
3150
+ });
3151
+ const params = new Params({ args }, opts.overrides || {});
3152
+ const loggerOptions = opts.logger || {};
3153
+ const logger = new CliToolkitLogger({
3154
+ mode: loggerOptions.mode || "text",
3155
+ route: loggerOptions.route || "console",
3156
+ prefix: loggerOptions.prefix,
3157
+ silent: loggerOptions.silent,
3158
+ showLevel: loggerOptions.showLevel,
3159
+ timestamp: loggerOptions.timestamp,
3160
+ levels: loggerOptions.levels
3161
+ });
3162
+ const cleanupFunctions = [];
3163
+ const context = {
3164
+ args,
3165
+ params,
3166
+ logger,
3167
+ emitter: new EventEmitter(),
3168
+ isStop: () => false,
3169
+ // Will be set in init function
3170
+ cleanupFunctions,
3171
+ registerCleanup: (fn) => {
3172
+ cleanupFunctions.push(fn);
3173
+ }
3174
+ };
3175
+ logger.debug("[setup] completed successfully");
3176
+ return context;
3177
+ }
3178
+ function setupContext(opts = {}) {
3179
+ return setup(opts);
3180
+ }
2899
3181
  export {
2900
3182
  Args,
2901
3183
  Db,
@@ -2927,6 +3209,7 @@ export {
2927
3209
  joiEdateType,
2928
3210
  joiStringArrayType,
2929
3211
  organizeFooterMessages,
3212
+ setupContext,
2930
3213
  showListScreen,
2931
3214
  showMenuScreen,
2932
3215
  showMultiColumnListScreen,