@guritso/terminal 1.1.1 → 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.
@@ -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
@@ -1,5 +1,5 @@
1
1
  declare module "@guritso/terminal" {
2
- interface Terminal {
2
+ export interface Terminal {
3
3
  verbose: number;
4
4
  readonly levels: {
5
5
  readonly info: string;
@@ -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,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,9 +6,8 @@
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
  };
@@ -183,7 +189,18 @@ terminal.clear = () => {
183
189
  * @param {number} verbose - The verbose level
184
190
  */
185
191
  terminal.setVerbose = (verbose) => {
186
- 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
+
187
204
  };
188
205
 
189
- 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
@@ -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.2",
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