@ix-xs/node-comfort 1.0.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/core/CLI.js +95 -0
- package/core/Checker.js +127 -0
- package/core/FS.js +765 -0
- package/core/Logger.js +321 -0
- package/core/Stepper.js +115 -0
- package/core/Storage.js +350 -0
- package/core/Utils.js +206 -0
- package/index.js +17 -0
- package/package.json +17 -0
package/core/Logger.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
const _x = "\x1B";
|
|
2
|
+
const _style = {
|
|
3
|
+
normal: 0,
|
|
4
|
+
bold: 1,
|
|
5
|
+
italic: 3,
|
|
6
|
+
underline: 4,
|
|
7
|
+
overline: 9,
|
|
8
|
+
};
|
|
9
|
+
const _color = {
|
|
10
|
+
fg: {
|
|
11
|
+
black: 30,
|
|
12
|
+
red: 31,
|
|
13
|
+
green: 32,
|
|
14
|
+
yellow: 33,
|
|
15
|
+
blue: 34,
|
|
16
|
+
magenta: 35,
|
|
17
|
+
cyan: 36,
|
|
18
|
+
white: 37,
|
|
19
|
+
gray: 90,
|
|
20
|
+
redBright: 91,
|
|
21
|
+
greenBright: 92,
|
|
22
|
+
yellowBright: 93,
|
|
23
|
+
blueBright: 94,
|
|
24
|
+
magentaBright: 95,
|
|
25
|
+
cyanBright: 96,
|
|
26
|
+
whiteBright: 97,
|
|
27
|
+
rgb: 38,
|
|
28
|
+
},
|
|
29
|
+
bg: {
|
|
30
|
+
black: 40,
|
|
31
|
+
red: 41,
|
|
32
|
+
green: 42,
|
|
33
|
+
yellow: 43,
|
|
34
|
+
blue: 44,
|
|
35
|
+
magenta: 45,
|
|
36
|
+
cyan: 46,
|
|
37
|
+
white: 47,
|
|
38
|
+
gray: 100,
|
|
39
|
+
redBright: 101,
|
|
40
|
+
greenBright: 102,
|
|
41
|
+
yellowBright: 103,
|
|
42
|
+
blueBright: 104,
|
|
43
|
+
magentaBright: 105,
|
|
44
|
+
cyanBright: 106,
|
|
45
|
+
whiteBright: 107,
|
|
46
|
+
rgb: 48,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const _options = {
|
|
50
|
+
delimiters: {
|
|
51
|
+
start: "<%",
|
|
52
|
+
end: "%>",
|
|
53
|
+
},
|
|
54
|
+
timestamp: true,
|
|
55
|
+
};
|
|
56
|
+
const _groups = [];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Gets ANSI escape code for style/color.
|
|
60
|
+
* @private
|
|
61
|
+
* @param {string} name - Style or color name.
|
|
62
|
+
* @returns {number|null} ANSI code or null.
|
|
63
|
+
*/
|
|
64
|
+
const _getCode = (name) => {
|
|
65
|
+
if (_style[name] !== undefined) return _style[name];
|
|
66
|
+
if (_color.fg[name] !== undefined) return _color.fg[name];
|
|
67
|
+
if (name.startsWith("bg")) {
|
|
68
|
+
const bg = name.charAt(2).toLowerCase() + name.slice(3);
|
|
69
|
+
if (_color.bg[bg] !== undefined) return _color.bg[bg];
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Parses template tokens into styles and text.
|
|
76
|
+
* @private
|
|
77
|
+
* @param {string} input - Template string.
|
|
78
|
+
* @returns {{codes: number[], text: string}} Parsed result.
|
|
79
|
+
*/
|
|
80
|
+
const _parse = (input) => {
|
|
81
|
+
const tokens = input.trim().split(/\s+/);
|
|
82
|
+
const styles = [];
|
|
83
|
+
const fgs = [];
|
|
84
|
+
const bgs = [];
|
|
85
|
+
const txts = [];
|
|
86
|
+
|
|
87
|
+
for (const token of tokens) {
|
|
88
|
+
const code = _getCode(token);
|
|
89
|
+
if (code === null) {
|
|
90
|
+
txts.push(token);
|
|
91
|
+
} else {
|
|
92
|
+
if (_style[token] !== undefined) {
|
|
93
|
+
styles.push(code);
|
|
94
|
+
} else if (token.startsWith("bg")) {
|
|
95
|
+
bgs.push(code);
|
|
96
|
+
} else {
|
|
97
|
+
fgs.push(code);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const codes = [...styles, ...bgs, ...fgs];
|
|
103
|
+
const text = input
|
|
104
|
+
.replace(
|
|
105
|
+
/^\s*((redBright|greenBright|yellowBright|blueBright|magentaBright|cyanBright|whiteBright|red|green|yellow|blue|magenta|cyan|white|gray|black|bold|italic|underline|overline|bg\w+|\s+)*)/gi,
|
|
106
|
+
"",
|
|
107
|
+
)
|
|
108
|
+
.trimStart();
|
|
109
|
+
return { codes, text };
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Builds ANSI escape sequence from codes.
|
|
114
|
+
* @private
|
|
115
|
+
* @param {number[]} codes - ANSI codes.
|
|
116
|
+
* @returns {string} Escape sequence.
|
|
117
|
+
*/
|
|
118
|
+
const _build = (codes) => {
|
|
119
|
+
if (codes.length === 0) return "";
|
|
120
|
+
return `${_x}[${codes.join(";")}m`;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const _apply = (str) => {
|
|
124
|
+
const esc_start = _options.delimiters.start.replace(
|
|
125
|
+
/[.*+?^${}()|[\]\\]/g,
|
|
126
|
+
"\\$&",
|
|
127
|
+
);
|
|
128
|
+
const esc_end = _options.delimiters.end.replace(
|
|
129
|
+
/[.*+?^${}()|[\]\\]/g,
|
|
130
|
+
"\\$&",
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
return str.replace(
|
|
134
|
+
new RegExp(`${esc_start}([\\s\\S]*?)${esc_end}`, "g"),
|
|
135
|
+
(match, content) => {
|
|
136
|
+
const { codes, text } = _parse(content);
|
|
137
|
+
if (codes.length === 0 || text === "") return text;
|
|
138
|
+
|
|
139
|
+
if (text.includes("\n")) {
|
|
140
|
+
return text
|
|
141
|
+
.split(/\\r?\\n/)
|
|
142
|
+
.filter((line) => line.trim())
|
|
143
|
+
.map((line) => `${_build(codes)}${line}${_x}[0m`)
|
|
144
|
+
.join("\\n");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return `${_build(codes)}${text}${_x}[0m`;
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @private
|
|
154
|
+
* @returns {string}
|
|
155
|
+
*/
|
|
156
|
+
const _timestamp = () => {
|
|
157
|
+
if (!_options.timestamp) return "";
|
|
158
|
+
return _apply(
|
|
159
|
+
`<%gray [${new Date().toLocaleTimeString(undefined, { hour12: false })}]%>`,
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Generates indentation based on group depth.
|
|
165
|
+
* @private
|
|
166
|
+
* @returns {string} Indentation spaces.
|
|
167
|
+
*/
|
|
168
|
+
const _indent = () => {
|
|
169
|
+
return " ".repeat(0 + _groups.length);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @module logger
|
|
174
|
+
* @description
|
|
175
|
+
* Beautiful ANSI-colored console logger with grouping, timestamps, and template syntax.
|
|
176
|
+
*
|
|
177
|
+
* Supports 16+ colors, styles (bold, italic, underline), background colors,
|
|
178
|
+
* nested groups with indentation, and customizable delimiters.
|
|
179
|
+
*
|
|
180
|
+
* ## Syntax
|
|
181
|
+
* ```
|
|
182
|
+
* <%red bold This is red and bold%>
|
|
183
|
+
* <%bgBlue whiteBright ERROR%>
|
|
184
|
+
* <%yellowBright [WARN]%>
|
|
185
|
+
* ```
|
|
186
|
+
*
|
|
187
|
+
* Available styles: `normal`, `bold`, `italic`, `underline`, `overline`
|
|
188
|
+
* Colors: `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, `gray`
|
|
189
|
+
* Bright colors: `redBright`, `greenBright`, `yellowBright`, `blueBright`, `magentaBright`, `cyanBright`, `whiteBright`
|
|
190
|
+
* Backgrounds: `bgBlack`, `bgRed`, `bgGreen`, etc.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* const nodeComfort = require("@ix-xs/node-comfort");
|
|
194
|
+
*
|
|
195
|
+
* nodeComfort.log("<%greenBright ✓ Success%> Operation completed!");
|
|
196
|
+
* nodeComfort.group("<%blue Processing files%>").log("File 1").groupEnd();
|
|
197
|
+
* const log = nodeComfort.logify({ users: 42 }); // → ANSI string
|
|
198
|
+
*/
|
|
199
|
+
module.exports = {
|
|
200
|
+
/**
|
|
201
|
+
* Logs content with colors, timestamp, and group indentation.
|
|
202
|
+
* Supports strings, objects (pretty JSON), and chaining.
|
|
203
|
+
*
|
|
204
|
+
* @param {any} content - Content to log (string, object, or primitive).
|
|
205
|
+
* @returns {this} Chainable instance.
|
|
206
|
+
* @example
|
|
207
|
+
* nodeComfort.log("<%greenBright ✓ Done%>");
|
|
208
|
+
* nodeComfort.log({ users: 42, active: true });
|
|
209
|
+
* nodeComfort.group("Files").log("Processing...").groupEnd();
|
|
210
|
+
*/
|
|
211
|
+
log(content) {
|
|
212
|
+
let str;
|
|
213
|
+
if (typeof content === "string") {
|
|
214
|
+
str = content;
|
|
215
|
+
} else if (typeof content === "object") {
|
|
216
|
+
str = JSON.stringify(content, null, 2);
|
|
217
|
+
} else {
|
|
218
|
+
str = String(content);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const prefix = `${_timestamp()}${_indent()}`;
|
|
222
|
+
const fullStr = prefix ? `${prefix} ${str}` : str;
|
|
223
|
+
|
|
224
|
+
const formatted = _apply(fullStr);
|
|
225
|
+
|
|
226
|
+
if (formatted.includes("\n")) {
|
|
227
|
+
const lines = formatted.split("\n");
|
|
228
|
+
|
|
229
|
+
if (lines[0].trim()) console.log(lines[0]);
|
|
230
|
+
|
|
231
|
+
const indentOnly = _indent();
|
|
232
|
+
for (let i = 1; i < lines.length; i++) {
|
|
233
|
+
if (lines[i].trim()) {
|
|
234
|
+
console.log(`${indentOnly}${lines[i]}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
console.log(formatted);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return this;
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Formats content with colors, timestamp, and group indentation (no console output).
|
|
246
|
+
*
|
|
247
|
+
* @param {any} content - Content to format.
|
|
248
|
+
* @returns {string} Formatted string.
|
|
249
|
+
* @example
|
|
250
|
+
* const msg = nodeComfort.logify("<%redBright ERROR%> Invalid input");
|
|
251
|
+
* console.log(msg);
|
|
252
|
+
*/
|
|
253
|
+
logify(content) {
|
|
254
|
+
let str;
|
|
255
|
+
if (typeof content === "string") {
|
|
256
|
+
str = content;
|
|
257
|
+
} else if (typeof content === "object") {
|
|
258
|
+
str = JSON.stringify(content, null, 2);
|
|
259
|
+
} else {
|
|
260
|
+
str = String(content);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const prefix = `${_timestamp()}${_indent()}`;
|
|
264
|
+
return _apply(prefix ? `${prefix} ${str}` : str);
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Starts a new log group with ▼ indicator and increased indentation.
|
|
269
|
+
*
|
|
270
|
+
* @param {string} label - Group label (supports colors).
|
|
271
|
+
* @returns {this} Chainable instance.
|
|
272
|
+
* @example
|
|
273
|
+
* nodeComfort.group("<%yellow Processing%>").log("Step 1").groupEnd();
|
|
274
|
+
*/
|
|
275
|
+
group(label) {
|
|
276
|
+
_groups.push(label);
|
|
277
|
+
const prefix = `${_timestamp()}${_indent()}`;
|
|
278
|
+
console.log(prefix ? `${prefix}▼ ${label}` : `▼ ${label}`);
|
|
279
|
+
return this;
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Ends current group and reduces indentation.
|
|
284
|
+
*
|
|
285
|
+
* @returns {this} Chainable instance.
|
|
286
|
+
* @example
|
|
287
|
+
* nodeComfort.group("Work").groupEnd();
|
|
288
|
+
*/
|
|
289
|
+
groupEnd() {
|
|
290
|
+
if (_groups.length > 0) _groups.pop();
|
|
291
|
+
return this;
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Configures logger options (delimiters and timestamp).
|
|
296
|
+
*
|
|
297
|
+
* @param {object} [options] - Logger options.
|
|
298
|
+
* @param {object} [options.delimiters] - Template delimiters.
|
|
299
|
+
* @param {string} [options.delimiters.start="<%="] - Start delimiter.
|
|
300
|
+
* @param {string} [options.delimiters.end="%>"] - End delimiter.
|
|
301
|
+
* @param {boolean} [options.timestamp=true] - Show timestamps.
|
|
302
|
+
* @returns {this} Chainable instance.
|
|
303
|
+
* @example
|
|
304
|
+
* nodeComfort.setLogger({
|
|
305
|
+
* delimiters: { start: "{{", end: "}}" },
|
|
306
|
+
* timestamp: false
|
|
307
|
+
* }).log("{{ bgGreen new delimiters }}");
|
|
308
|
+
*/
|
|
309
|
+
setLogger(options = _options) {
|
|
310
|
+
options = {
|
|
311
|
+
delimiters: {
|
|
312
|
+
start: options?.delimiters?.start ?? _options.delimiters.start,
|
|
313
|
+
end: options?.delimiters?.end ?? _options.delimiters.end,
|
|
314
|
+
},
|
|
315
|
+
timestamp: options?.timestamp ?? _options.timestamp,
|
|
316
|
+
};
|
|
317
|
+
_options.delimiters = options.delimiters;
|
|
318
|
+
_options.timestamp = options.timestamp;
|
|
319
|
+
return this;
|
|
320
|
+
},
|
|
321
|
+
};
|
package/core/Stepper.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Stepper
|
|
3
|
+
* @description
|
|
4
|
+
* Fluent step controller for sequential task execution with navigation, skipping, and indexing.
|
|
5
|
+
*
|
|
6
|
+
* Perfect for CLI wizards, build pipelines, tutorials, or any sequential workflow.
|
|
7
|
+
* Supports forward/backward navigation, jumping to specific steps, ignoring steps, and reset.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const nodeComfort = require("@ix-xs/node-comfort");
|
|
11
|
+
*
|
|
12
|
+
* // Simple 3-step process
|
|
13
|
+
* nodeComfort.step([
|
|
14
|
+
* () => console.log("Step 1: Validate config"),
|
|
15
|
+
* () => console.log("Step 2: Build assets"),
|
|
16
|
+
* () => console.log("Step 3: Deploy")
|
|
17
|
+
* ])
|
|
18
|
+
* .next().next().next(); // Execute all steps
|
|
19
|
+
*
|
|
20
|
+
* // Interactive navigation
|
|
21
|
+
* const wizard = nodeComfort.step([
|
|
22
|
+
* () => nodeComfort.log("<%yellow 1%> Enter name"),
|
|
23
|
+
* () => nodeComfort.log("<%yellow 2%> Enter email"),
|
|
24
|
+
* () => nodeComfort.log("<%green ✓%> Complete!")
|
|
25
|
+
* ]);
|
|
26
|
+
*
|
|
27
|
+
* wizard.next().back().ignore().next(); // 1 → back to 0 → skip 1 → execute 2
|
|
28
|
+
*/
|
|
29
|
+
module.exports = {
|
|
30
|
+
/**
|
|
31
|
+
* Creates a step controller for sequential function execution.
|
|
32
|
+
*
|
|
33
|
+
* Returns fluent API with `next()`, `back()`, `at()`, `ignore()`, `reset()` methods.
|
|
34
|
+
*
|
|
35
|
+
* @param {Array<() => void>} steps - Array of step functions to execute.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const steps = nodeComfort.step([
|
|
39
|
+
* () => console.log("Install dependencies"),
|
|
40
|
+
* () => console.log("Compile TypeScript"),
|
|
41
|
+
* () => console.log("Run tests")
|
|
42
|
+
* ]);
|
|
43
|
+
*
|
|
44
|
+
* steps.next().next(); // Execute steps 0, 1
|
|
45
|
+
* steps.back(); // Go back to step 0
|
|
46
|
+
* steps.at(2); // Jump to step 2
|
|
47
|
+
*/
|
|
48
|
+
step(steps) {
|
|
49
|
+
let i = -1;
|
|
50
|
+
|
|
51
|
+
const _ = {
|
|
52
|
+
currentIndex: i,
|
|
53
|
+
currentStep: steps[i],
|
|
54
|
+
/**
|
|
55
|
+
* Advances to next step and executes it (if exists).
|
|
56
|
+
*/
|
|
57
|
+
next() {
|
|
58
|
+
if (i < steps.length - 1) {
|
|
59
|
+
i++;
|
|
60
|
+
if (typeof steps[i] === "function") steps[i]();
|
|
61
|
+
}
|
|
62
|
+
return _;
|
|
63
|
+
},
|
|
64
|
+
/**
|
|
65
|
+
* Goes back to previous step and re-executes it (if exists).
|
|
66
|
+
*/
|
|
67
|
+
back() {
|
|
68
|
+
if (i > 0) {
|
|
69
|
+
i--;
|
|
70
|
+
if (typeof steps[i] === "function") steps[i]();
|
|
71
|
+
}
|
|
72
|
+
return _;
|
|
73
|
+
},
|
|
74
|
+
/**
|
|
75
|
+
* Jumps to specific step index and executes it.
|
|
76
|
+
* @param {number} index - Step index (0 to steps.length-1).
|
|
77
|
+
* @example
|
|
78
|
+
* steps.at(2); // Jump to 3rd step
|
|
79
|
+
*/
|
|
80
|
+
at(index) {
|
|
81
|
+
if (index >= 0 && index < steps.length) {
|
|
82
|
+
i = index;
|
|
83
|
+
if (typeof steps[i] === "function") steps[i]();
|
|
84
|
+
}
|
|
85
|
+
return _;
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* Advances index without executing current step.
|
|
89
|
+
* @example
|
|
90
|
+
* steps.next().ignore().next(); // Execute 0, skip 1, execute 2
|
|
91
|
+
*/
|
|
92
|
+
ignore() {
|
|
93
|
+
if (i < steps.length - 1) {
|
|
94
|
+
i++;
|
|
95
|
+
}
|
|
96
|
+
return _;
|
|
97
|
+
},
|
|
98
|
+
/**
|
|
99
|
+
* Resets to initial state (index: -1).
|
|
100
|
+
*/
|
|
101
|
+
reset() {
|
|
102
|
+
i = -1;
|
|
103
|
+
return _;
|
|
104
|
+
},
|
|
105
|
+
all() {
|
|
106
|
+
while (i < steps.length - 1) {
|
|
107
|
+
this.next();
|
|
108
|
+
}
|
|
109
|
+
return _;
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return _;
|
|
114
|
+
},
|
|
115
|
+
};
|