@guritso/terminal 1.1.0 → 1.1.2

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/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @guritso/terminal
2
2
 
3
- A terminal node utility for logging and error handling.
3
+ A terminal node utility for enhanced logging and error handling.
4
4
 
5
5
  | example |
6
6
  | ------- |
7
- | ![example](https://i.ibb.co/sm49x0x/Captura-de-tela-de-2024-09-23-05-59-20.png) |
7
+ | ![example](https://i.ibb.co/WHD4mXn/guritso-terminal-preview.png) |
8
8
 
9
9
  ## Installation
10
10
 
@@ -12,18 +12,23 @@ To install the package:
12
12
 
13
13
  ```bash
14
14
  npm install @guritso/terminal
15
- # or
16
- pnpm install @guritso/terminal
17
- # or
18
- yarn add @guritso/terminal
19
15
  ```
20
16
 
21
17
  ## Usage
22
18
 
23
19
  To use the package, import it into your project:
24
20
 
21
+ ```javascript
22
+ const terminal = require('@guritso/terminal');
23
+ ```
24
+
25
+ or ESM:
26
+
25
27
  ```javascript
26
28
  import terminal from '@guritso/terminal';
29
+ ```
30
+
31
+ ```javascript
27
32
  // Setup the terminal (this is necessary to use the console.error function)
28
33
  terminal.setup();
29
34
 
@@ -67,14 +72,14 @@ Displays a log message.
67
72
 
68
73
  ### `terminal.setVerbose(verbose)`
69
74
 
70
- Sets the verbose level. (0 = no output (does't apply for start()), 1 = same line output (does't apply for pass), 2 = new line output)
75
+ Sets the verbose level.
71
76
 
72
77
  - `verbose` (number): The verbose level.
73
-
74
-
75
- ## Methods
78
+ - `0`: no output (does't apply for `terminal.start()`)
79
+ - `1`: same line output (does't apply for `terminal.pass()`)
80
+ - `2`: new line output (default)
76
81
 
77
82
  ## License
78
83
 
79
- This project is licensed under the GPL-3.0 License. See the [LICENSE](./LICENSE) file for details.
84
+ [MIT License](./LICENSE)
80
85
 
package/index.cjs ADDED
@@ -0,0 +1 @@
1
+ module.exports = require("./lib/cjs/terminal.cjs");
package/index.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ declare module "@guritso/terminal" {
2
+ export interface Terminal {
3
+ verbose: number;
4
+ readonly levels: {
5
+ readonly info: string;
6
+ readonly fail: string;
7
+ readonly pass: string;
8
+ };
9
+ projectInfo: {
10
+ name: string;
11
+ version: string;
12
+ };
13
+ start(host?: string, port?: number): void;
14
+ pass(message: unknown): void;
15
+ log(message: unknown): void;
16
+ setup(): boolean;
17
+ clear(): boolean;
18
+ setVerbose(verbose: number): void;
19
+ isError(data: unknown): boolean;
20
+ }
21
+
22
+ const terminal: Terminal;
23
+ export = terminal;
24
+ }
package/index.js CHANGED
@@ -1 +1,3 @@
1
- module.exports = require('./lib/terminal.js');
1
+ import terminal from "./lib/terminal.js";
2
+
3
+ export default terminal;
@@ -0,0 +1,293 @@
1
+ 'use strict';
2
+
3
+ var url = require('url');
4
+ var fs = require('fs');
5
+
6
+ /*!
7
+ * Terminal
8
+ * Copyright (c) 2024 @GuriTso
9
+ * @license GPL-3.0
10
+ */
11
+
12
+
13
+ /**
14
+ * This function is used to colorize the text
15
+ * @param {string} txt - The text to colorize
16
+ * @returns {string | any} - The colorized text
17
+ * @example
18
+ * color("Hello %H1 World") // "Hello \x1b[1mWorld\x1b[0m"
19
+ * color("%h1 start here%H nothing here") // "\x1b[1m start here\x1b[0m nothing here"
20
+ */
21
+ function color(txt) {
22
+ if (typeof txt !== "string") return txt;
23
+
24
+ const arr = txt.split("%H");
25
+ let res = arr.join("");
26
+
27
+ for (const a of arr) {
28
+ const [n, ...w] = a.split(" ");
29
+
30
+ if (!n || !txt.includes(`%H${n}`) || isNaN(n)) continue;
31
+
32
+ const mo = `\x1b[${n}m${w.join(" ")}\x1b[0m`;
33
+
34
+ res = res.replace(a, mo);
35
+ }
36
+ return res;
37
+ }
38
+ /**
39
+ * Get a list of common error names
40
+ *
41
+ * @returns {string[]}
42
+ */
43
+ function getErrorNames() {
44
+ return Object.getOwnPropertyNames(global).filter((name) => {
45
+ try {
46
+ return (
47
+ typeof global[name] === "function" &&
48
+ global[name].prototype instanceof Error
49
+ );
50
+ } catch (e) {
51
+ return false;
52
+ }
53
+ });
54
+ }
55
+ /**
56
+ * Format the URL based on the provided host and port.
57
+ *
58
+ * @param {string} host - The host to format.
59
+ * @param {number} port - The port to format.
60
+ * @returns {string} - The formatted URL.
61
+ */
62
+ function formatUrl(host, port) {
63
+ const locals = ["localhost", "127.0.0.1", "0.0.0.0"];
64
+
65
+ try {
66
+ if (typeof host !== "string") {
67
+ return new Error("host must be a string");
68
+ }
69
+
70
+ const url$1 = new url.URL(host);
71
+
72
+ if (port && port !== url$1.port) {
73
+ url$1.port = port;
74
+ }
75
+
76
+ if (!url$1.protocol) {
77
+ url$1.protocol = "http:";
78
+ } else if (url$1.protocol !== "https:" && url$1.protocol !== "http:") {
79
+ url$1.protocol = "http:";
80
+ }
81
+
82
+ return { url: url$1.toString(), port: url$1.port };
83
+ } catch (error) {
84
+ if (locals.includes(host)) {
85
+ return formatUrl("http://localhost", port);
86
+ } else {
87
+ return error;
88
+ }
89
+ }
90
+ }
91
+
92
+ /*!
93
+ * Terminal
94
+ * Copyright (c) 2024 @GuriTso
95
+ * @license GPL-3.0
96
+ */
97
+
98
+
99
+ /**
100
+ * @typedef {Object} Terminal
101
+ * @property {number} verbose
102
+ * @property {Object} levels
103
+ * @property {string} levels.info
104
+ * @property {string} levels.fail
105
+ * @property {string} levels.pass
106
+ * @property {function(string, number): void} start
107
+ * @property {function(string): void} pass
108
+ * @property {function(string): void} log
109
+ * @property {function(): void} setup
110
+ * @property {function(): void} clear
111
+ * @property {function(number): void} setVerbose
112
+ * @property {function(any): boolean} isError
113
+ * @property {Object} projectInfo
114
+ */
115
+
116
+ /** @type {Terminal} */
117
+ const terminal = {
118
+ verbose: 2,
119
+ levels: {
120
+ info: color("%H100 INFO:%H"),
121
+ fail: color("%H41 FAIL:%H"),
122
+ pass: color("%H42 PASS:%H"),
123
+ },
124
+ };
125
+
126
+ const { stdout } = process;
127
+
128
+ terminal.projectInfo = (() => {
129
+ try {
130
+ const { name, version } = JSON.parse(
131
+ fs.readFileSync("./package.json", "utf-8")
132
+ );
133
+
134
+ return { name, version };
135
+ } catch (error) {
136
+ return { name: "unknown", version: "unknown" };
137
+ }
138
+ })();
139
+
140
+ /**
141
+ * display the project info and host, port
142
+ *
143
+ * @param {string} host - The host to display
144
+ * @param {number} port - The port number to display
145
+ */
146
+ terminal.start = function start(host, port) {
147
+ const projectInfo = terminal.projectInfo;
148
+ const hostInfo = formatUrl(host, port);
149
+
150
+ const headLines = [
151
+ `\n%H46 name:%H%H44 ${projectInfo.name} `,
152
+ `%H105 version:%H%H41 ${projectInfo.version} %H\n`,
153
+ hostInfo?.url ? `%H43 host:%H95 ${hostInfo.url}\n` : "",
154
+ hostInfo?.port ? `%H45 port:%H94 ${hostInfo.port}\n` : "",
155
+ ];
156
+
157
+ for (const line of headLines) {
158
+ stdout.write(color(line));
159
+ }
160
+ };
161
+
162
+ /**
163
+ * display successful messages
164
+ *
165
+ * @param {string} data
166
+ */
167
+ terminal.pass = function pass(data) {
168
+ const { verbose, levels } = terminal;
169
+
170
+ if (verbose === 0) return;
171
+
172
+ if (typeof data === "object") {
173
+ // skipcq: JS-0002
174
+ console.log(color(`\r%H1 ${levels.pass}%H`),data);
175
+ } else {
176
+ terminal.clear();
177
+ stdout.write(color(`\r%H1 ${levels.pass}%H ${String(data)}\n`));
178
+ }
179
+ };
180
+
181
+ /**
182
+ * display logs messages, error, info, etc.
183
+ *
184
+ * @param {any} data
185
+ */
186
+ terminal.log = function log(data) {
187
+ const { verbose, levels } = terminal;
188
+
189
+ let level = levels.info;
190
+
191
+ if (verbose === 0) return;
192
+
193
+ if (terminal.isError(data)) {
194
+ level = levels.fail;
195
+ }
196
+
197
+ if (verbose === 1 && stdout.isTTY) {
198
+ terminal.clear();
199
+
200
+ stdout.write(color(`\r%H1 ${level}%H ${String(data)}`));
201
+ } else {
202
+ if (level === terminal.levels.info) {
203
+ if (typeof data === "object") {
204
+ // skipcq: JS-0002
205
+ console.log(color(`%H1 ${level}%H `), data);
206
+ } else {
207
+ stdout.write(color(`%H1 ${level}%H ${String(data)}\n`));
208
+ }
209
+ } else {
210
+ // skipcq: JS-0002
211
+ console.log(color(`%H1 ${level}%H `), data);
212
+ }
213
+ }
214
+ };
215
+
216
+ /**
217
+ * Check if the data is an error
218
+ *
219
+ * @param {any} data
220
+ * @returns {boolean}
221
+ */
222
+ terminal.isError = (data) => {
223
+ if (data instanceof Error) {
224
+ return true;
225
+ }
226
+
227
+ if (typeof data === "string") {
228
+ const errorKeywords = getErrorNames().map((name) => name);
229
+ errorKeywords.push("Error:");
230
+
231
+ return errorKeywords.some((keyword) => data.includes(keyword));
232
+ }
233
+
234
+ return false;
235
+ };
236
+
237
+ /**
238
+ * Setup the console.error to use the terminal.log function
239
+ *
240
+ * @returns {boolean} - true if the setup was successful, false otherwise
241
+ */
242
+ terminal.setup = () => {
243
+ if (typeof console === "object" && console.error) {
244
+
245
+ if (typeof console.backup === "function") {
246
+ return false
247
+ }
248
+
249
+ // backup the original console.error
250
+ console.backup = console.error;
251
+ } else {
252
+ throw new Error("console.error is not found");
253
+ }
254
+
255
+ // replace the console.error with the terminal.log
256
+ console.error = terminal.log;
257
+
258
+ return true
259
+ };
260
+
261
+ /**
262
+ * Clear the terminal
263
+ */
264
+ terminal.clear = () => {
265
+ if (stdout.isTTY) {
266
+ stdout.clearLine();
267
+ return true
268
+ }
269
+
270
+ return false
271
+ };
272
+
273
+ /**
274
+ * Set the verbose level
275
+ *
276
+ * @param {number} verbose - The verbose level
277
+ */
278
+ terminal.setVerbose = (verbose) => {
279
+ if (!isNaN(Number(verbose))) {
280
+ const verboseLevel = Number(verbose);
281
+
282
+ if (verboseLevel < 0) {
283
+ terminal.verbose = 0;
284
+ } else if (verboseLevel > 2) {
285
+ terminal.verbose = 2;
286
+ } else {
287
+ terminal.verbose = verboseLevel;
288
+ }
289
+ }
290
+
291
+ };
292
+
293
+ module.exports = terminal;
package/lib/terminal.js CHANGED
@@ -6,10 +6,8 @@
6
6
 
7
7
  "use strict";
8
8
 
9
- const getErrorNames = require("./utils").getErrorNames;
10
- const formatUrl = require("./utils").formatUrl;
11
- const co = require("./utils").color;
12
- const { readFileSync } = require("fs");
9
+ import { color as co, getErrorNames, formatUrl } from "./utils.js";
10
+ import { readFileSync } from "fs";
13
11
 
14
12
  /**
15
13
  * @typedef {Object} Terminal
@@ -53,7 +51,7 @@ terminal.projectInfo = (() => {
53
51
  })();
54
52
 
55
53
  /**
56
- * display the project info and the port
54
+ * display the project info and host, port
57
55
  *
58
56
  * @param {string} host - The host to display
59
57
  * @param {number} port - The port number to display
@@ -75,48 +73,55 @@ terminal.start = function start(host, port) {
75
73
  };
76
74
 
77
75
  /**
78
- * display the online message
76
+ * display successful messages
79
77
  *
80
78
  * @param {string} data
81
79
  */
82
80
  terminal.pass = function pass(data) {
83
- terminal.clear();
84
- stdout.write(co(`\r%H1 ${terminal.levels.pass}%H ${data}\n`));
81
+ const { verbose, levels } = terminal;
82
+
83
+ if (verbose === 0) return;
84
+
85
+ if (typeof data === "object") {
86
+ // skipcq: JS-0002
87
+ console.log(co(`\r%H1 ${levels.pass}%H`),data);
88
+ } else {
89
+ terminal.clear();
90
+ stdout.write(co(`\r%H1 ${levels.pass}%H ${String(data)}\n`));
91
+ }
85
92
  };
86
93
 
87
94
  /**
88
- * display logs messages
95
+ * display logs messages, error, info, etc.
89
96
  *
90
- * @param {string} data
97
+ * @param {any} data
91
98
  */
92
99
  terminal.log = function log(data) {
93
100
  const { verbose, levels } = terminal;
94
101
 
95
102
  let level = levels.info;
96
103
 
97
- if (Number(verbose) === 0) return;
104
+ if (verbose === 0) return;
98
105
 
99
106
  if (terminal.isError(data)) {
100
107
  level = levels.fail;
101
108
  }
102
109
 
103
- if (Number(verbose) === 1 && stdout.isTTY) {
110
+ if (verbose === 1 && stdout.isTTY) {
104
111
  terminal.clear();
105
112
 
106
- stdout.write(co(`\r%H1 ${level}%H ${data}`));
113
+ stdout.write(co(`\r%H1 ${level}%H ${String(data)}`));
107
114
  } else {
108
- stdout.write(co(`%H1 ${level}%H `));
109
-
110
115
  if (level === terminal.levels.info) {
111
116
  if (typeof data === "object") {
112
117
  // skipcq: JS-0002
113
- console.log(data);
118
+ console.log(co(`%H1 ${level}%H `), data);
114
119
  } else {
115
- stdout.write(`${co(data)}\n`);
120
+ stdout.write(co(`%H1 ${level}%H ${String(data)}\n`));
116
121
  }
117
122
  } else {
118
123
  // skipcq: JS-0002
119
- console.log(data);
124
+ console.log(co(`%H1 ${level}%H `), data);
120
125
  }
121
126
  }
122
127
  };
@@ -133,7 +138,7 @@ terminal.isError = (data) => {
133
138
  }
134
139
 
135
140
  if (typeof data === "string") {
136
- const errorKeywords = getErrorNames().map((name) => `Error: ${name}`);
141
+ const errorKeywords = getErrorNames().map((name) => name);
137
142
  errorKeywords.push("Error:");
138
143
 
139
144
  return errorKeywords.some((keyword) => data.includes(keyword));
@@ -184,7 +189,18 @@ terminal.clear = () => {
184
189
  * @param {number} verbose - The verbose level
185
190
  */
186
191
  terminal.setVerbose = (verbose) => {
187
- terminal.verbose = verbose;
192
+ if (!isNaN(Number(verbose))) {
193
+ const verboseLevel = Number(verbose);
194
+
195
+ if (verboseLevel < 0) {
196
+ terminal.verbose = 0;
197
+ } else if (verboseLevel > 2) {
198
+ terminal.verbose = 2;
199
+ } else {
200
+ terminal.verbose = verboseLevel;
201
+ }
202
+ }
203
+
188
204
  };
189
205
 
190
- module.exports = terminal;
206
+ export default terminal;
package/lib/utils.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * @license GPL-3.0
5
5
  */
6
6
 
7
- const { URL } = require("url");
7
+ import { URL } from "url";
8
8
 
9
9
  /**
10
10
  * This function is used to colorize the text
@@ -80,14 +80,11 @@ function formatUrl(host, port) {
80
80
  return { url: url.toString(), port: url.port };
81
81
  } catch (error) {
82
82
  if (locals.includes(host)) {
83
- return formatUrl(`http://${host}`, port);
83
+ return formatUrl("http://localhost", port);
84
84
  } else {
85
85
  return error;
86
86
  }
87
87
  }
88
88
  };
89
89
 
90
- exports.color = color;
91
- exports.getErrorNames = getErrorNames;
92
- exports.formatUrl = formatUrl;
93
-
90
+ export { color, getErrorNames, formatUrl };
package/package.json CHANGED
@@ -1,12 +1,21 @@
1
1
  {
2
2
  "name": "@guritso/terminal",
3
- "description": "A terminal node utility for logging and error handling",
4
- "version": "1.1.0",
3
+ "description": "A terminal node utility for enhanced logging and error handling",
4
+ "version": "1.1.2",
5
5
  "main": "index.cjs",
6
6
  "module": "index.js",
7
- "license": "GPL-3.0",
7
+ "types": "index.d.ts",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./index.js",
12
+ "require": "./index.cjs"
13
+ }
14
+ },
15
+ "license": "MIT",
8
16
  "scripts": {
9
- "test": "jest"
17
+ "test": "jest",
18
+ "build": "rollup -c"
10
19
  },
11
20
  "repository": {
12
21
  "type": "git",
@@ -17,11 +26,13 @@
17
26
  "logging",
18
27
  "error-handling",
19
28
  "cli",
20
- "utility"
29
+ "utility",
30
+ "backend"
21
31
  ],
22
32
  "author": "GuriTso",
23
33
  "devDependencies": {
24
- "jest": "^29.7.0"
34
+ "jest": "^29.7.0",
35
+ "rollup": "^4.22.4"
25
36
  },
26
37
  "directories": {
27
38
  "lib": "lib",
@@ -0,0 +1,8 @@
1
+ export default {
2
+ input: 'lib/terminal.js',
3
+ output: {
4
+ file: 'lib/cjs/terminal.cjs',
5
+ format: 'cjs',
6
+ },
7
+ external: ['fs', 'url']
8
+ };
@@ -1,4 +1,6 @@
1
- const terminal = require("../index.js");
1
+ const terminal = require("../index.cjs");
2
+
3
+ const levels = terminal.levels;
2
4
 
3
5
  describe("Terminal Module", () => {
4
6
  beforeEach(() => {
@@ -29,6 +31,23 @@ describe("Terminal Module", () => {
29
31
  expect(mockWrite).toHaveBeenCalledWith(expect.stringContaining("PASS"));
30
32
  mockWrite.mockRestore();
31
33
  });
34
+
35
+ it("should use console.log instead of stdout.write when verbose is 2 and msg is an object", () => {
36
+ terminal.setVerbose(2);
37
+ const mockLog = jest.spyOn(console, "log").mockImplementation(() => true);
38
+ const mockWrite = jest
39
+ .spyOn(process.stdout, "write")
40
+ .mockImplementation(() => true);
41
+
42
+ const message = { key: "value" };
43
+ terminal.pass(message);
44
+
45
+ expect(mockLog).toHaveBeenCalledWith(expect.stringContaining(levels.pass), message);
46
+ expect(mockWrite).not.toHaveBeenCalled();
47
+
48
+ mockLog.mockRestore();
49
+ mockWrite.mockRestore();
50
+ });
32
51
  });
33
52
 
34
53
  describe("log", () => {
@@ -39,12 +58,13 @@ describe("Terminal Module", () => {
39
58
  .mockImplementation(() => true);
40
59
 
41
60
  terminal.log("This is a log message");
61
+ terminal.pass("This is a pass message");
42
62
  expect(mockWrite).not.toHaveBeenCalled();
43
63
 
44
64
  mockWrite.mockRestore();
45
65
  });
46
66
 
47
- it("should write info message to stdout when verbose is 2", () => {
67
+ it("should write info message to stdout when verbose is 2 and msg is a string", () => {
48
68
  terminal.setVerbose(2);
49
69
  const mockWrite = jest
50
70
  .spyOn(process.stdout, "write")
@@ -55,25 +75,39 @@ describe("Terminal Module", () => {
55
75
  mockWrite.mockRestore();
56
76
  });
57
77
 
58
- it("should write error message to stdout when it is an error", () => {
78
+ it("should use console.log instead of stdout.write when verbose is 2 and msg is an error string", () => {
59
79
  terminal.setVerbose(2);
80
+ const mockLog = jest.spyOn(console, "log").mockImplementation(() => true);
60
81
  const mockWrite = jest
61
82
  .spyOn(process.stdout, "write")
62
83
  .mockImplementation(() => true);
63
84
 
64
- terminal.log("Error: Something went wrong");
65
- expect(mockWrite).toHaveBeenCalledWith(expect.stringContaining("FAIL"));
85
+ const errorMessage = "Error: Something went wrong";
86
+
87
+ terminal.log(errorMessage);
88
+
89
+ expect(mockLog).toHaveBeenCalledWith(expect.stringContaining(levels.fail), errorMessage);
90
+ expect(mockWrite).not.toHaveBeenCalled();
91
+
92
+ mockLog.mockRestore();
66
93
  mockWrite.mockRestore();
67
94
  });
68
95
 
69
- it("should write object to console.log when data is an object", () => {
96
+ it("should use console.log instead of stdout.write when verbose is 2 and msg is an object", () => {
70
97
  terminal.setVerbose(2);
71
98
  const mockLog = jest.spyOn(console, "log").mockImplementation(() => true);
99
+ const mockWrite = jest
100
+ .spyOn(process.stdout, "write")
101
+ .mockImplementation(() => true);
72
102
 
73
- terminal.log({ key: "value" });
74
- expect(mockLog).toHaveBeenCalledWith({ key: "value" });
103
+ const message = { key: "value" };
104
+ terminal.log(message);
105
+
106
+ expect(mockLog).toHaveBeenCalledWith(expect.stringContaining(levels.info), message);
107
+ expect(mockWrite).not.toHaveBeenCalled();
75
108
 
76
109
  mockLog.mockRestore();
110
+ mockWrite.mockRestore();
77
111
  });
78
112
  });
79
113