@guritso/terminal 1.1.1 → 1.1.3

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.
@@ -17,6 +17,7 @@ jobs:
17
17
  node-version: 22.x
18
18
  - run: npm ci
19
19
  - run: npm test
20
+ - run: npm run build --if-present
20
21
 
21
22
  publish-npm:
22
23
  needs: build
package/index.cjs ADDED
@@ -0,0 +1 @@
1
+ module.exports = require("./lib/cjs/terminal.cjs");
package/index.d.ts CHANGED
@@ -20,5 +20,5 @@ declare module "@guritso/terminal" {
20
20
  }
21
21
 
22
22
  const terminal: Terminal;
23
- export default terminal;
23
+ export = terminal;
24
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,291 @@
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 MIT
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 MIT
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
+ if (typeof console.backup === "function") {
245
+ return false;
246
+ }
247
+
248
+ // backup the original console.error
249
+ console.backup = console.error;
250
+ } else {
251
+ throw new Error("console.error is not found");
252
+ }
253
+
254
+ // replace the console.error with the terminal.log
255
+ console.error = terminal.log;
256
+
257
+ return true;
258
+ };
259
+
260
+ /**
261
+ * Clear the terminal
262
+ */
263
+ terminal.clear = () => {
264
+ if (stdout.isTTY) {
265
+ stdout.clearLine();
266
+ return true;
267
+ }
268
+
269
+ return false;
270
+ };
271
+
272
+ /**
273
+ * Set the verbose level
274
+ *
275
+ * @param {number} verbose - The verbose level
276
+ */
277
+ terminal.setVerbose = (verbose) => {
278
+ if (!isNaN(Number(verbose))) {
279
+ const verboseLevel = Number(verbose);
280
+
281
+ if (verboseLevel < 0) {
282
+ terminal.verbose = 0;
283
+ } else if (verboseLevel > 2) {
284
+ terminal.verbose = 2;
285
+ } else {
286
+ terminal.verbose = verboseLevel;
287
+ }
288
+ }
289
+ };
290
+
291
+ module.exports = terminal;
package/lib/terminal.js CHANGED
@@ -1,14 +1,13 @@
1
1
  /*!
2
2
  * Terminal
3
3
  * Copyright (c) 2024 @GuriTso
4
- * @license GPL-3.0
4
+ * @license MIT
5
5
  */
6
6
 
7
7
  "use strict";
8
8
 
9
- const { getErrorNames, formatUrl } = require("./utils");
10
- const co = require("./utils").color;
11
- const { readFileSync } = require("fs");
9
+ import { color as co, getErrorNames, formatUrl } from "./utils.js";
10
+ import { readFileSync } from "fs";
12
11
 
13
12
  /**
14
13
  * @typedef {Object} Terminal
@@ -79,43 +78,50 @@ terminal.start = function start(host, port) {
79
78
  * @param {string} data
80
79
  */
81
80
  terminal.pass = function pass(data) {
82
- terminal.clear();
83
- 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
+ }
84
92
  };
85
93
 
86
94
  /**
87
95
  * display logs messages, error, info, etc.
88
96
  *
89
- * @param {string} data
97
+ * @param {any} data
90
98
  */
91
99
  terminal.log = function log(data) {
92
100
  const { verbose, levels } = terminal;
93
101
 
94
102
  let level = levels.info;
95
103
 
96
- if (Number(verbose) === 0) return;
104
+ if (verbose === 0) return;
97
105
 
98
106
  if (terminal.isError(data)) {
99
107
  level = levels.fail;
100
108
  }
101
109
 
102
- if (Number(verbose) === 1 && stdout.isTTY) {
110
+ if (verbose === 1 && stdout.isTTY) {
103
111
  terminal.clear();
104
112
 
105
- stdout.write(co(`\r%H1 ${level}%H ${data}`));
113
+ stdout.write(co(`\r%H1 ${level}%H ${String(data)}`));
106
114
  } else {
107
- stdout.write(co(`%H1 ${level}%H `));
108
-
109
115
  if (level === terminal.levels.info) {
110
116
  if (typeof data === "object") {
111
117
  // skipcq: JS-0002
112
- console.log(data);
118
+ console.log(co(`%H1 ${level}%H`), data);
113
119
  } else {
114
- stdout.write(`${co(data)}\n`);
120
+ stdout.write(co(`%H1 ${level}%H ${String(data)}\n`));
115
121
  }
116
122
  } else {
117
123
  // skipcq: JS-0002
118
- console.log(data);
124
+ console.log(co(`%H1 ${level}%H`), data);
119
125
  }
120
126
  }
121
127
  };
@@ -148,11 +154,10 @@ terminal.isError = (data) => {
148
154
  */
149
155
  terminal.setup = () => {
150
156
  if (typeof console === "object" && console.error) {
151
-
152
157
  if (typeof console.backup === "function") {
153
- return false
158
+ return false;
154
159
  }
155
-
160
+
156
161
  // backup the original console.error
157
162
  console.backup = console.error;
158
163
  } else {
@@ -162,7 +167,7 @@ terminal.setup = () => {
162
167
  // replace the console.error with the terminal.log
163
168
  console.error = terminal.log;
164
169
 
165
- return true
170
+ return true;
166
171
  };
167
172
 
168
173
  /**
@@ -171,10 +176,10 @@ terminal.setup = () => {
171
176
  terminal.clear = () => {
172
177
  if (stdout.isTTY) {
173
178
  stdout.clearLine();
174
- return true
179
+ return true;
175
180
  }
176
181
 
177
- return false
182
+ return false;
178
183
  };
179
184
 
180
185
  /**
@@ -183,7 +188,17 @@ terminal.clear = () => {
183
188
  * @param {number} verbose - The verbose level
184
189
  */
185
190
  terminal.setVerbose = (verbose) => {
186
- terminal.verbose = verbose;
191
+ if (!isNaN(Number(verbose))) {
192
+ const verboseLevel = Number(verbose);
193
+
194
+ if (verboseLevel < 0) {
195
+ terminal.verbose = 0;
196
+ } else if (verboseLevel > 2) {
197
+ terminal.verbose = 2;
198
+ } else {
199
+ terminal.verbose = verboseLevel;
200
+ }
201
+ }
187
202
  };
188
203
 
189
- module.exports = terminal;
204
+ export default terminal;
package/lib/utils.js CHANGED
@@ -1,10 +1,10 @@
1
1
  /*!
2
2
  * Terminal
3
3
  * Copyright (c) 2024 @GuriTso
4
- * @license GPL-3.0
4
+ * @license MIT
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
@@ -87,7 +87,4 @@ function formatUrl(host, port) {
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
3
  "description": "A terminal node utility for enhanced logging and error handling",
4
- "version": "1.1.1",
5
- "main": "index.js",
4
+ "version": "1.1.3",
5
+ "main": "index.cjs",
6
+ "module": "index.js",
6
7
  "types": "index.d.ts",
8
+ "type": "module",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./index.js",
12
+ "require": "./index.cjs"
13
+ }
14
+ },
7
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",
@@ -22,7 +31,8 @@
22
31
  ],
23
32
  "author": "GuriTso",
24
33
  "devDependencies": {
25
- "jest": "^29.7.0"
34
+ "jest": "^29.7.0",
35
+ "rollup": "^4.22.4"
26
36
  },
27
37
  "directories": {
28
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