@stonyx/logs 1.0.1-beta.10 → 1.0.1-beta.12

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.
@@ -0,0 +1,10 @@
1
+ import chalk from 'chalk';
2
+ export type ChalkColorFn = (text: string) => string;
3
+ export type ColorSetting = string | ChalkColorFn;
4
+ export default class Color {
5
+ types: Record<string, ChalkColorFn>;
6
+ getLogColor(type: string): ChalkColorFn;
7
+ getChalkInstance(): typeof chalk;
8
+ setLogColor(type: string, setting: ColorSetting): void;
9
+ settingToChalkColorFunction(setting: ColorSetting): ChalkColorFn;
10
+ }
package/dist/color.js ADDED
@@ -0,0 +1,39 @@
1
+ import chalk from 'chalk';
2
+ export default class Color {
3
+ types = {};
4
+ getLogColor(type) {
5
+ return this.types[type];
6
+ }
7
+ getChalkInstance() {
8
+ return chalk;
9
+ }
10
+ setLogColor(type, setting) {
11
+ const chalkColorFunction = this.settingToChalkColorFunction(setting);
12
+ this.types[type] = chalkColorFunction;
13
+ }
14
+ // retrieves chalk color function, and fully validates output
15
+ settingToChalkColorFunction(setting) {
16
+ const errorMessage = 'Invalid chalk color function.'
17
+ + 'For help with color settings, see https://github.com/abofs/stonyx-logs#defining-logs--colors';
18
+ switch (typeof setting) {
19
+ case 'string':
20
+ const chalkColorFunction = (setting[0] === '#')
21
+ ? chalk.hex(setting)
22
+ : chalk[setting];
23
+ if (!chalkColorFunction
24
+ || typeof chalkColorFunction !== 'function'
25
+ || typeof chalkColorFunction('') !== 'string') {
26
+ throw errorMessage;
27
+ }
28
+ return chalkColorFunction;
29
+ case 'function':
30
+ // validate that given function returns a string
31
+ if (typeof setting('') !== 'string') {
32
+ throw errorMessage;
33
+ }
34
+ return setting;
35
+ default:
36
+ throw errorMessage;
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,29 @@
1
+ import Color, { type ColorSetting } from './color.js';
2
+ export interface LogOptions {
3
+ logToFileByDefault: boolean;
4
+ logTimestamp: boolean;
5
+ path: string;
6
+ prefix: string;
7
+ suffix: string;
8
+ filename: string;
9
+ additionalLogs: Record<string, ColorSetting>;
10
+ systemLogs: Record<string, ColorSetting>;
11
+ }
12
+ export default class Log {
13
+ options: LogOptions;
14
+ color: Color;
15
+ typeOptions: Record<string, Partial<LogOptions>>;
16
+ [key: string]: unknown;
17
+ constructor(options?: Partial<LogOptions>);
18
+ defineType(type: string, setting: ColorSetting, options?: Partial<LogOptions> | null): void;
19
+ createConvenienceMethod(type: string): void;
20
+ logAction(type: string, content: string, logToFile?: boolean, overwrite?: boolean): Promise<void>;
21
+ getOptionForType(type: string, option: keyof LogOptions): LogOptions[keyof LogOptions];
22
+ chalk(): ReturnType<Color['getChalkInstance']>;
23
+ log(content: string, type: string, logToFile: boolean, overwrite: boolean): Promise<void>;
24
+ debug(content: unknown, logToFile?: boolean, overwrite?: boolean): Promise<void>;
25
+ writeToFile(type: string, content: string, overwrite: boolean): Promise<void>;
26
+ resolveFilename(template: string, type: string): string;
27
+ validateFileAndDirectory(path: string, targetLog: string): Promise<void>;
28
+ sanitizePath(path: string): string;
29
+ }
package/dist/index.js ADDED
@@ -0,0 +1,174 @@
1
+ import { mkdirSync, promises as fsp } from 'fs';
2
+ import { fileURLToPath } from 'url';
3
+ import { hostname } from 'os';
4
+ import projectPath from 'path';
5
+ import Color from './color.js';
6
+ const defaultOptions = {
7
+ logToFileByDefault: false,
8
+ logTimestamp: false,
9
+ path: 'logs/',
10
+ prefix: '',
11
+ suffix: '',
12
+ filename: '',
13
+ additionalLogs: {},
14
+ systemLogs: {
15
+ info: 'cyan',
16
+ warn: 'yellow',
17
+ error: 'red',
18
+ },
19
+ };
20
+ // used to sanitize defineType() options input
21
+ const optionKeys = Object.keys(defaultOptions);
22
+ export default class Log {
23
+ options;
24
+ color;
25
+ typeOptions = {};
26
+ constructor(options = {}) {
27
+ const merged = {
28
+ ...defaultOptions,
29
+ ...options,
30
+ };
31
+ this.options = merged;
32
+ this.options.path = this.sanitizePath(this.options.path);
33
+ const { additionalLogs, systemLogs } = merged;
34
+ const logs = {
35
+ ...systemLogs,
36
+ ...additionalLogs,
37
+ };
38
+ this.color = new Color();
39
+ this.typeOptions = {};
40
+ // create direct convenience methods for logging
41
+ for (const type of Object.keys(logs)) {
42
+ this.defineType(type, logs[type]);
43
+ }
44
+ }
45
+ // records setting and options for log type, and creates convenience method ie: log.info()
46
+ defineType(type, setting, options = null) {
47
+ this.color.setLogColor(type, setting);
48
+ // create convenience method if it doesn't exist
49
+ if (!this[type])
50
+ this.createConvenienceMethod(type);
51
+ if (!options)
52
+ return;
53
+ if (typeof options !== 'object')
54
+ throw 'The options param must be an object.';
55
+ for (const option of Object.keys(options)) {
56
+ if (!optionKeys.includes(option)) {
57
+ throw `${option} is not a valid configuration object.`
58
+ + '\n For a list of available options, see https://github.com/abofs/stonyx-logs#configuration';
59
+ }
60
+ // sanitize path input
61
+ if (option === 'path') {
62
+ options[option] = this.sanitizePath(options[option]);
63
+ }
64
+ }
65
+ this.typeOptions[type] = options;
66
+ }
67
+ // proxy through `logAction` method in order to set defaults based on argument presence
68
+ createConvenienceMethod(type) {
69
+ this[type] = (content, logToFile, overwrite = false) => this.logAction(type, content, logToFile, overwrite);
70
+ }
71
+ // validates params and sets configuration-based defaults for logging
72
+ logAction(type, content, logToFile, overwrite) {
73
+ // set logToFile default based on class options when not set
74
+ if (logToFile === undefined)
75
+ logToFile = this.getOptionForType(type, 'logToFileByDefault');
76
+ // treat overwrite default as true for log type "debug"
77
+ if (type === 'debug' && overwrite === undefined)
78
+ overwrite = true;
79
+ return this.log(content, type, logToFile, overwrite ?? false);
80
+ }
81
+ // retrieves option setting for given type, default to global
82
+ getOptionForType(type, option) {
83
+ const options = this.typeOptions[type];
84
+ if (!options || !options[option])
85
+ return this.options[option];
86
+ return options[option];
87
+ }
88
+ // exposes chalk for custom color options via defineType
89
+ chalk() {
90
+ return this.color.getChalkInstance();
91
+ }
92
+ // logs to console, and conditionally to file
93
+ async log(content, type, logToFile, overwrite) {
94
+ const logTimestamp = this.getOptionForType(type, 'logTimestamp');
95
+ const timestamp = `[${new Date().toLocaleString('en-US')}]`;
96
+ const chalkColorFunction = this.color.getLogColor(type);
97
+ let prefix = this.getOptionForType(type, 'prefix');
98
+ let suffix = this.getOptionForType(type, 'suffix');
99
+ if (logTimestamp)
100
+ prefix += `${timestamp} `;
101
+ if (prefix)
102
+ prefix = chalkColorFunction(prefix);
103
+ if (suffix)
104
+ suffix = chalkColorFunction(suffix);
105
+ const coloredLog = chalkColorFunction(content);
106
+ console.log(`${prefix}${coloredLog}${suffix}`); // eslint-disable-line no-console
107
+ if (!logToFile)
108
+ return;
109
+ await this.writeToFile(type, `${timestamp} ${content}\n`, overwrite);
110
+ }
111
+ // direct hardcoded debug method (log to file functionality is limited)
112
+ async debug(content, logToFile = false, overwrite = true) {
113
+ console.dir(content, { depth: 6 }); // eslint-disable-line no-console
114
+ if (!logToFile)
115
+ return;
116
+ await this.writeToFile('debug', JSON.stringify(content, null, 2), overwrite);
117
+ }
118
+ async writeToFile(type, content, overwrite) {
119
+ const path = this.getOptionForType(type, 'path');
120
+ const filenameTemplate = this.getOptionForType(type, 'filename');
121
+ const resolvedName = this.resolveFilename(filenameTemplate, type);
122
+ const targetLog = `${path}${resolvedName}`;
123
+ await this.validateFileAndDirectory(path, targetLog);
124
+ const fileAction = overwrite ? fsp.writeFile : fsp.appendFile;
125
+ await fileAction(targetLog, content);
126
+ }
127
+ // resolves template variables in a filename string
128
+ resolveFilename(template, type) {
129
+ // default to '{type}.log' when no template is configured
130
+ if (!template)
131
+ return `${type}.log`;
132
+ const now = new Date();
133
+ const yyyy = now.getFullYear();
134
+ const mm = String(now.getMonth() + 1).padStart(2, '0');
135
+ const dd = String(now.getDate()).padStart(2, '0');
136
+ const variables = {
137
+ date: `${yyyy}-${mm}-${dd}`,
138
+ type,
139
+ pid: process.pid,
140
+ hostname: hostname(),
141
+ };
142
+ const resolved = template.replace(/\{(\w+)\}/g, (match, key) => {
143
+ return variables[key] !== undefined ? String(variables[key]) : match;
144
+ });
145
+ // sanitize: prevent path traversal and disallow directory separators
146
+ return resolved.replace(/\.\./g, '').replace(/[/\\]/g, '');
147
+ }
148
+ // attempts to create file and/or directory if they don't already exist
149
+ async validateFileAndDirectory(path, targetLog) {
150
+ const errorMethod = this.error || console.error;
151
+ mkdirSync(path, { recursive: true });
152
+ await fsp.access(targetLog).catch(() => {
153
+ fsp.writeFile(targetLog, '').catch(() => {
154
+ errorMethod(`Failed to create log file: ${targetLog}.`
155
+ + '\n Verify that the application runner has write permissions');
156
+ });
157
+ });
158
+ }
159
+ // method to conditionally sanitize user configuration input
160
+ sanitizePath(path) {
161
+ const moduleDir = projectPath.dirname(fileURLToPath(import.meta.url));
162
+ const delim = moduleDir.includes('node_modules') ? 'node_modules' : 'src';
163
+ const splitDir = moduleDir.split(delim);
164
+ if (splitDir.length < 2)
165
+ throw ('Failed to locate your project\'s root directory.');
166
+ // use project root directory behind path
167
+ path = projectPath.resolve(splitDir[0], path);
168
+ // force path property to contain a trailing "/"
169
+ if (path[path.length - 1] !== '/') {
170
+ path += '/';
171
+ }
172
+ return path;
173
+ }
174
+ }
package/package.json CHANGED
@@ -1,15 +1,22 @@
1
1
  {
2
2
  "name": "@stonyx/logs",
3
- "version": "1.0.1-beta.10",
3
+ "version": "1.0.1-beta.12",
4
4
  "description": "Simplified logging for node applications",
5
5
  "type": "module",
6
- "main": "src/index.js",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
7
8
  "exports": {
8
- ".": "./src/index.js",
9
- "./color": "./src/color.js"
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./color": {
14
+ "types": "./dist/color.d.ts",
15
+ "default": "./dist/color.js"
16
+ }
10
17
  },
11
18
  "files": [
12
- "src",
19
+ "dist",
13
20
  "README.md"
14
21
  ],
15
22
  "publishConfig": {
@@ -43,13 +50,19 @@
43
50
  "chalk": "^5.3.0"
44
51
  },
45
52
  "devDependencies": {
53
+ "@types/node": "^25.5.2",
54
+ "@types/qunit": "^2.19.13",
55
+ "@types/sinon": "^21.0.1",
46
56
  "eslint": "^8.27.0",
47
57
  "eslint-plugin-node": "^11.1.0",
48
58
  "qunit": "^2.19.3",
49
- "sinon": "^17.0.0"
59
+ "sinon": "^17.0.0",
60
+ "typescript": "^5.8.3"
50
61
  },
51
62
  "scripts": {
52
- "test": "qunit 'test/unit/**/*-test.js'",
63
+ "build": "tsc",
64
+ "build:test": "tsc -p tsconfig.test.json",
65
+ "test": "pnpm build && pnpm build:test && qunit 'dist-test/test/unit/**/*-test.js'",
53
66
  "lint": "eslint . --fix"
54
67
  }
55
68
  }
package/src/color.js DELETED
@@ -1,50 +0,0 @@
1
- import chalk from 'chalk';
2
-
3
- export default class Color {
4
- constructor() {
5
- this.types = [];
6
- }
7
-
8
- // retrieves configured color function for log type
9
- getLogColor(type) {
10
- return this.types[type];
11
- }
12
-
13
- getChalkInstance() {
14
- return chalk;
15
- }
16
-
17
- setLogColor(type, setting) {
18
- const chalkColorFunction = this.settingToChalkColorFunction(setting);
19
- this.types[type] = chalkColorFunction;
20
- }
21
-
22
- // retrieves chalk color function, and fully validates output
23
- settingToChalkColorFunction(setting) {
24
- const errorMessage = 'Invalid chalk color function.'
25
- + 'For help with color settings, see https://github.com/abofs/stonyx-logs#defining-logs--colors';
26
-
27
- switch (typeof setting) {
28
- case 'string':
29
- const chalkColorFunction = (setting[0] === '#') ? chalk.hex(setting) : chalk[setting];
30
- if (!chalkColorFunction
31
- || typeof chalkColorFunction !== 'function'
32
- || typeof chalkColorFunction('') !== 'string') {
33
- throw errorMessage;
34
- }
35
-
36
- return chalkColorFunction;
37
-
38
- case 'function':
39
- // validate that given function returns a string
40
- if (typeof setting('') !== 'string') {
41
- throw errorMessage;
42
- }
43
-
44
- return setting;
45
-
46
- default:
47
- throw errorMessage;
48
- }
49
- }
50
- }
package/src/index.js DELETED
@@ -1,201 +0,0 @@
1
- import { mkdirSync, promises as fsp } from 'fs';
2
- import { fileURLToPath } from 'url';
3
- import { hostname } from 'os';
4
- import projectPath from 'path';
5
- import Color from './color.js';
6
-
7
- const defaultOptions = {
8
- logToFileByDefault: false, // default setting (overridable by logToFile param)
9
- logTimestamp: false, // option to include timestamp in console logs
10
- path: 'logs/', // default log directory (relative to main project root directory)
11
- prefix: '',
12
- suffix: '',
13
- filename: '', // template for log file name (e.g. 'error-{date}.log'), defaults to '{type}.log'
14
-
15
- // log types with corresponding color settings
16
- additionalLogs: {},
17
- systemLogs: {
18
- info: 'cyan',
19
- warn: 'yellow',
20
- error: 'red',
21
- },
22
- };
23
-
24
- // used to sanitize defineType() options input
25
- const optionKeys = Object.keys(defaultOptions);
26
-
27
- export default class Log {
28
- constructor(options = defaultOptions) {
29
- options = {
30
- ...defaultOptions,
31
- ...options,
32
- };
33
- this.options = options;
34
- this.options.path = this.sanitizePath(this.options.path);
35
-
36
- const { additionalLogs, systemLogs } = options;
37
- const logs = {
38
- ...systemLogs,
39
- ...additionalLogs,
40
- };
41
-
42
- this.color = new Color();
43
- this.typeOptions = [];
44
-
45
- // create direct convenience methods for logging
46
- for (const type of Object.keys(logs)) {
47
- this.defineType(type, logs[type]);
48
- }
49
- }
50
-
51
- // records setting and options for log type, and creates convenience method ie: log.info()
52
- defineType(type, setting, options = null) {
53
- this.color.setLogColor(type, setting);
54
-
55
- // create convenience method if it doesn't exist
56
- if (!this[type]) this.createConvenienceMethod(type);
57
-
58
- if (!options) return;
59
- if (typeof options !== 'object') throw 'The options param must be an object.';
60
-
61
- for (let option of Object.keys(options)) {
62
- if (!optionKeys.includes(option)) {
63
- throw `${option} is not a valid configuration object.`
64
- + '\n For a list of available options, see https://github.com/abofs/stonyx-logs#configuration';
65
- }
66
-
67
- // sanitize path input
68
- if (option === 'path') options[option] = this.sanitizePath(options[option]);
69
- }
70
-
71
- this.typeOptions[type] = options;
72
- }
73
-
74
- // proxy through `logAction` method in order to set defaults based on argument presence
75
- createConvenienceMethod(type) {
76
- this[type] = (content, logToFile, overwrite = false) =>
77
- this.logAction(type, content, logToFile, overwrite);
78
- }
79
-
80
- // validates params and sets configuration-based defaults for logging
81
- logAction(type, content, logToFile, overwrite) {
82
- // set logToFile default based on class options when not set
83
- if (arguments[2] === undefined) logToFile = this.getOptionForType(type, 'logToFileByDefault');
84
-
85
- // treat overwrite default as true for log type "debug"
86
- if (type === 'debug' && arguments[3] === undefined) overwrite = true;
87
-
88
- return this.log(content, type, logToFile, overwrite);
89
- }
90
-
91
- // retrieves option setting for given type, default to global
92
- getOptionForType(type, option) {
93
- const options = this.typeOptions[type];
94
- if (!options || !options[option]) return this.options[option];
95
-
96
- return options[option];
97
- }
98
-
99
- // exposes chalk for custom color options via defineType
100
- chalk() {
101
- return this.color.getChalkInstance();
102
- }
103
-
104
- // logs to console, and conditionally to file
105
- async log(content, type, logToFile, overwrite) {
106
- const logTimestamp = this.getOptionForType(type, 'logTimestamp');
107
- const timestamp = `[${new Date().toLocaleString('en-US')}]`;
108
- const chalkColorFunction = this.color.getLogColor(type);
109
- let prefix = this.getOptionForType(type, 'prefix');
110
- let suffix = this.getOptionForType(type, 'suffix');
111
- if (logTimestamp) prefix += `${timestamp} `;
112
- if (prefix) prefix = chalkColorFunction(prefix);
113
- if (suffix) suffix = chalkColorFunction(suffix);
114
- const coloredLog = chalkColorFunction(content);
115
-
116
- console.log(`${prefix}${coloredLog}${suffix}`); // eslint-disable-line no-console
117
-
118
- if (!logToFile) return;
119
-
120
- return this.writeToFile(type, `${timestamp} ${content}\n`, overwrite);
121
- }
122
-
123
- // direct hardcoded debug method (log to file functionality is limited)
124
- async debug(content, logToFile = false, overwrite = true) {
125
- console.dir(content, { depth: 6 }); // eslint-disable-line no-console
126
-
127
- if (!logToFile) return;
128
-
129
- return this.writeToFile('debug', JSON.stringify(content, null, 2), overwrite);
130
- }
131
-
132
- async writeToFile(type, content, overwrite) {
133
- const path = this.getOptionForType(type, 'path');
134
- const filenameTemplate = this.getOptionForType(type, 'filename');
135
- const resolvedName = this.resolveFilename(filenameTemplate, type);
136
- const targetLog = `${path}${resolvedName}`;
137
- await this.validateFileAndDirectory(path, targetLog);
138
-
139
- const fileAction = overwrite ? fsp.writeFile : fsp.appendFile;
140
-
141
- return fileAction(targetLog, content);
142
- }
143
-
144
- // resolves template variables in a filename string
145
- resolveFilename(template, type) {
146
- // default to '{type}.log' when no template is configured
147
- if (!template) return `${type}.log`;
148
-
149
- const now = new Date();
150
- const yyyy = now.getFullYear();
151
- const mm = String(now.getMonth() + 1).padStart(2, '0');
152
- const dd = String(now.getDate()).padStart(2, '0');
153
-
154
- const variables = {
155
- date: `${yyyy}-${mm}-${dd}`,
156
- type,
157
- pid: process.pid,
158
- hostname: hostname(),
159
- };
160
-
161
- const resolved = template.replace(/\{(\w+)\}/g, (match, key) => {
162
- return variables[key] !== undefined ? variables[key] : match;
163
- });
164
-
165
- // sanitize: prevent path traversal and disallow directory separators
166
- return resolved.replace(/\.\./g, '').replace(/[/\\]/g, '');
167
- }
168
-
169
- // attempts to create file and/or directory if they don't already exist
170
- async validateFileAndDirectory(path, targetLog) {
171
- const errorMethod = this.error || console.error; // prefer native method unless removed by user
172
-
173
- mkdirSync(path, { recursive: true });
174
-
175
- await fsp.access(targetLog).catch(() => {
176
- fsp.writeFile(targetLog, '').catch(() => {
177
- errorMethod(`Failed to create log file: ${targetLog}.`
178
- + '\n Verify that the application runner has write permissions');
179
- });
180
- });
181
- }
182
-
183
- // method to conditionally sanitize user configuration input
184
- sanitizePath(path) {
185
- const moduleDir = projectPath.dirname(fileURLToPath(import.meta.url));
186
- const delim = moduleDir.includes('node_modules') ? 'node_modules' : 'src';
187
- const splitDir = moduleDir.split(delim);
188
-
189
- if (splitDir.length < 2) throw ('Failed to locate your project\'s root directory.');
190
-
191
- // use project root directory behind path
192
- path = projectPath.resolve(splitDir[0], path);
193
-
194
- // force path property to contain a trailing "/"
195
- if (path[path.length - 1] !== '/') {
196
- path += '/';
197
- }
198
-
199
- return path;
200
- }
201
- }