envprobe 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/src/config.js ADDED
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Configuration file management
3
+ * Supports .envcheckrc, .envcheckrc.json, envcheck.config.js
4
+ */
5
+
6
+ import { readFileSync, existsSync, writeFileSync } from 'fs';
7
+ import { join } from 'path';
8
+
9
+ /**
10
+ * Load configuration from file
11
+ */
12
+ export function loadConfig(cwd = '.') {
13
+ const configFiles = [
14
+ '.envcheckrc',
15
+ '.envcheckrc.json',
16
+ 'envcheck.config.json',
17
+ '.envcheckrc.js',
18
+ 'envcheck.config.js',
19
+ ];
20
+
21
+ for (const file of configFiles) {
22
+ const configPath = join(cwd, file);
23
+
24
+ if (existsSync(configPath)) {
25
+ try {
26
+ if (file.endsWith('.js')) {
27
+ // Dynamic import for JS config files
28
+ return loadJSConfig(configPath);
29
+ } else {
30
+ // JSON config
31
+ const content = readFileSync(configPath, 'utf-8');
32
+ return JSON.parse(content);
33
+ }
34
+ } catch (error) {
35
+ console.warn(`Warning: Failed to load config from ${file}: ${error.message}`);
36
+ }
37
+ }
38
+ }
39
+
40
+ return null;
41
+ }
42
+
43
+ /**
44
+ * Load JavaScript config file
45
+ */
46
+ async function loadJSConfig(configPath) {
47
+ try {
48
+ const module = await import(configPath);
49
+ return module.default || module;
50
+ } catch (error) {
51
+ throw new Error(`Failed to load JS config: ${error.message}`);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Save configuration to file
57
+ */
58
+ export function saveConfig(config, cwd = '.', filename = '.envcheckrc.json') {
59
+ const configPath = join(cwd, filename);
60
+
61
+ try {
62
+ const content = JSON.stringify(config, null, 2);
63
+ writeFileSync(configPath, content, 'utf-8');
64
+ return configPath;
65
+ } catch (error) {
66
+ throw new Error(`Failed to save config: ${error.message}`);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Merge CLI options with config file
72
+ */
73
+ export function mergeConfig(cliOptions, fileConfig) {
74
+ if (!fileConfig) return cliOptions;
75
+
76
+ return {
77
+ ...fileConfig,
78
+ ...cliOptions,
79
+ // Merge arrays
80
+ ignore: [...(fileConfig.ignore || []), ...(cliOptions.ignore || [])],
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Validate configuration
86
+ */
87
+ export function validateConfig(config) {
88
+ const errors = [];
89
+
90
+ if (config.format && !['text', 'json', 'github'].includes(config.format)) {
91
+ errors.push(`Invalid format: ${config.format}`);
92
+ }
93
+
94
+ if (config.failOn && !['missing', 'unused', 'undocumented', 'all', 'none'].includes(config.failOn)) {
95
+ errors.push(`Invalid failOn: ${config.failOn}`);
96
+ }
97
+
98
+ if (config.ignore && !Array.isArray(config.ignore)) {
99
+ errors.push('ignore must be an array');
100
+ }
101
+
102
+ return errors;
103
+ }
104
+
105
+ /**
106
+ * Get default configuration
107
+ */
108
+ export function getDefaultConfig() {
109
+ return {
110
+ path: '.',
111
+ envFile: '.env.example',
112
+ format: 'text',
113
+ failOn: 'none',
114
+ ignore: ['node_modules/**', 'dist/**', 'build/**', '.git/**'],
115
+ noColor: false,
116
+ quiet: false,
117
+ };
118
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * GitHub Actions Formatter Module
3
+ *
4
+ * Formats analysis results as GitHub Actions workflow commands for CI/CD integration.
5
+ * Produces ::error and ::warning annotations that appear in GitHub's UI.
6
+ *
7
+ * @see https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
8
+ */
9
+
10
+ /**
11
+ * Formats analysis results as GitHub Actions annotations
12
+ *
13
+ * @param {Object} result - Analysis result from analyzer
14
+ * @param {Array} result.missing - Missing variable issues
15
+ * @param {Array} result.unused - Unused variable issues
16
+ * @param {Array} result.undocumented - Undocumented variable issues
17
+ * @param {Object} result.summary - Summary statistics
18
+ * @returns {string} GitHub Actions formatted output
19
+ */
20
+ export function formatGitHub(result) {
21
+ const annotations = [];
22
+
23
+ // Format missing variables as errors
24
+ for (const issue of result.missing) {
25
+ annotations.push(...formatMissingAnnotations(issue));
26
+ }
27
+
28
+ // Format unused variables as warnings
29
+ for (const issue of result.unused) {
30
+ annotations.push(formatUnusedAnnotation(issue));
31
+ }
32
+
33
+ // Format undocumented variables as warnings
34
+ for (const issue of result.undocumented) {
35
+ annotations.push(formatUndocumentedAnnotation(issue));
36
+ }
37
+
38
+ // Add summary notice
39
+ annotations.push(formatSummaryNotice(result.summary));
40
+
41
+ return annotations.join('\n');
42
+ }
43
+
44
+ /**
45
+ * Formats missing variable issue as GitHub error annotations
46
+ * Creates one error annotation per file reference
47
+ *
48
+ * @param {Object} issue - Missing variable issue
49
+ * @param {string} issue.varName - Variable name
50
+ * @param {Array} issue.references - File references where variable is used
51
+ * @returns {Array<string>} Array of error annotations
52
+ */
53
+ export function formatMissingAnnotations(issue) {
54
+ return issue.references.map(ref => {
55
+ const message = `Missing environment variable: ${issue.varName} is used but not defined in .env.example`;
56
+ return formatErrorAnnotation(ref.filePath, ref.lineNumber, message);
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Formats unused variable issue as GitHub warning annotation
62
+ *
63
+ * @param {Object} issue - Unused variable issue
64
+ * @param {string} issue.varName - Variable name
65
+ * @param {Object} issue.definition - Definition location
66
+ * @returns {string} Warning annotation
67
+ */
68
+ export function formatUnusedAnnotation(issue) {
69
+ const message = `Unused environment variable: ${issue.varName} is defined in .env.example but never used`;
70
+ return formatWarningAnnotation('.env.example', issue.definition.lineNumber, message);
71
+ }
72
+
73
+ /**
74
+ * Formats undocumented variable issue as GitHub warning annotation
75
+ *
76
+ * @param {Object} issue - Undocumented variable issue
77
+ * @param {string} issue.varName - Variable name
78
+ * @param {Object} issue.definition - Definition location
79
+ * @returns {string} Warning annotation
80
+ */
81
+ export function formatUndocumentedAnnotation(issue) {
82
+ const message = `Undocumented environment variable: ${issue.varName} is missing a comment in .env.example`;
83
+ return formatWarningAnnotation('.env.example', issue.definition.lineNumber, message);
84
+ }
85
+
86
+ /**
87
+ * Formats summary as GitHub notice annotation
88
+ *
89
+ * @param {Object} summary - Summary statistics
90
+ * @returns {string} Notice annotation
91
+ */
92
+ export function formatSummaryNotice(summary) {
93
+ const parts = [];
94
+
95
+ if (summary.totalMissing > 0) {
96
+ parts.push(`${summary.totalMissing} missing`);
97
+ }
98
+
99
+ if (summary.totalUnused > 0) {
100
+ parts.push(`${summary.totalUnused} unused`);
101
+ }
102
+
103
+ if (summary.totalUndocumented > 0) {
104
+ parts.push(`${summary.totalUndocumented} undocumented`);
105
+ }
106
+
107
+ if (parts.length === 0) {
108
+ return '::notice::Environment check passed - no issues found';
109
+ }
110
+
111
+ return `::notice::Environment check completed - ${parts.join(', ')}`;
112
+ }
113
+
114
+ /**
115
+ * Formats a GitHub Actions error annotation
116
+ *
117
+ * @param {string} file - File path
118
+ * @param {number} line - Line number
119
+ * @param {string} message - Error message
120
+ * @returns {string} Formatted error annotation
121
+ */
122
+ export function formatErrorAnnotation(file, line, message) {
123
+ return `::error file=${escapeProperty(file)},line=${line}::${escapeMessage(message)}`;
124
+ }
125
+
126
+ /**
127
+ * Formats a GitHub Actions warning annotation
128
+ *
129
+ * @param {string} file - File path
130
+ * @param {number} line - Line number
131
+ * @param {string} message - Warning message
132
+ * @returns {string} Formatted warning annotation
133
+ */
134
+ export function formatWarningAnnotation(file, line, message) {
135
+ return `::warning file=${escapeProperty(file)},line=${line}::${escapeMessage(message)}`;
136
+ }
137
+
138
+ /**
139
+ * Escapes special characters in annotation properties (file, line, col)
140
+ *
141
+ * @param {string} value - Property value to escape
142
+ * @returns {string} Escaped value
143
+ */
144
+ export function escapeProperty(value) {
145
+ return value
146
+ .replace(/%/g, '%25')
147
+ .replace(/\r/g, '%0D')
148
+ .replace(/\n/g, '%0A')
149
+ .replace(/:/g, '%3A')
150
+ .replace(/,/g, '%2C');
151
+ }
152
+
153
+ /**
154
+ * Escapes special characters in annotation messages
155
+ *
156
+ * @param {string} message - Message to escape
157
+ * @returns {string} Escaped message
158
+ */
159
+ export function escapeMessage(message) {
160
+ return message
161
+ .replace(/%/g, '%25')
162
+ .replace(/\r/g, '%0D')
163
+ .replace(/\n/g, '%0A');
164
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * JSON Formatter Module
3
+ *
4
+ * Formats analysis results as valid JSON for machine parsing and CI/CD integration.
5
+ * Produces structured output with all issue categories and summary statistics.
6
+ */
7
+
8
+ /**
9
+ * Formats analysis results as JSON
10
+ *
11
+ * @param {Object} result - Analysis result from analyzer
12
+ * @param {Array} result.missing - Missing variable issues
13
+ * @param {Array} result.unused - Unused variable issues
14
+ * @param {Array} result.undocumented - Undocumented variable issues
15
+ * @param {Object} result.summary - Summary statistics
16
+ * @returns {string} JSON-formatted string
17
+ */
18
+ export function formatJSON(result) {
19
+ const output = {
20
+ missing: formatMissingIssues(result.missing),
21
+ unused: formatUnusedIssues(result.unused),
22
+ undocumented: formatUndocumentedIssues(result.undocumented),
23
+ summary: formatSummary(result.summary)
24
+ };
25
+
26
+ return JSON.stringify(output, null, 2);
27
+ }
28
+
29
+ /**
30
+ * Formats missing variable issues for JSON output
31
+ *
32
+ * @param {Array} missing - Array of missing variable issues
33
+ * @returns {Array} Formatted missing issues
34
+ */
35
+ export function formatMissingIssues(missing) {
36
+ return missing.map(issue => ({
37
+ varName: issue.varName,
38
+ references: issue.references.map(ref => ({
39
+ filePath: ref.filePath,
40
+ lineNumber: ref.lineNumber,
41
+ pattern: ref.pattern
42
+ }))
43
+ }));
44
+ }
45
+
46
+ /**
47
+ * Formats unused variable issues for JSON output
48
+ *
49
+ * @param {Array} unused - Array of unused variable issues
50
+ * @returns {Array} Formatted unused issues
51
+ */
52
+ export function formatUnusedIssues(unused) {
53
+ return unused.map(issue => ({
54
+ varName: issue.varName,
55
+ definition: {
56
+ lineNumber: issue.definition.lineNumber,
57
+ hasComment: issue.definition.hasComment,
58
+ comment: issue.definition.comment
59
+ }
60
+ }));
61
+ }
62
+
63
+ /**
64
+ * Formats undocumented variable issues for JSON output
65
+ *
66
+ * @param {Array} undocumented - Array of undocumented variable issues
67
+ * @returns {Array} Formatted undocumented issues
68
+ */
69
+ export function formatUndocumentedIssues(undocumented) {
70
+ return undocumented.map(issue => ({
71
+ varName: issue.varName,
72
+ references: issue.references.map(ref => ({
73
+ filePath: ref.filePath,
74
+ lineNumber: ref.lineNumber,
75
+ pattern: ref.pattern
76
+ })),
77
+ definition: {
78
+ lineNumber: issue.definition.lineNumber,
79
+ hasComment: issue.definition.hasComment,
80
+ comment: issue.definition.comment
81
+ }
82
+ }));
83
+ }
84
+
85
+ /**
86
+ * Formats summary statistics for JSON output
87
+ *
88
+ * @param {Object} summary - Summary statistics object
89
+ * @returns {Object} Formatted summary
90
+ */
91
+ export function formatSummary(summary) {
92
+ return {
93
+ totalMissing: summary.totalMissing,
94
+ totalUnused: summary.totalUnused,
95
+ totalUndocumented: summary.totalUndocumented,
96
+ totalReferences: summary.totalReferences,
97
+ totalDefinitions: summary.totalDefinitions
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Validates that a string is valid JSON
103
+ *
104
+ * @param {string} jsonString - String to validate
105
+ * @returns {boolean} True if valid JSON, false otherwise
106
+ */
107
+ export function isValidJSON(jsonString) {
108
+ try {
109
+ JSON.parse(jsonString);
110
+ return true;
111
+ } catch (error) {
112
+ return false;
113
+ }
114
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Table formatter for better visual output
3
+ */
4
+
5
+ /**
6
+ * Format data as a table
7
+ */
8
+ export function formatTable(headers, rows, options = {}) {
9
+ const { maxWidth = 100, padding = 2 } = options;
10
+
11
+ if (rows.length === 0) {
12
+ return '';
13
+ }
14
+
15
+ // Calculate column widths
16
+ const columnWidths = headers.map((header, i) => {
17
+ const headerWidth = header.length;
18
+ const maxRowWidth = Math.max(
19
+ ...rows.map(row => String(row[i] || '').length)
20
+ );
21
+ return Math.min(Math.max(headerWidth, maxRowWidth) + padding, maxWidth);
22
+ });
23
+
24
+ // Create separator
25
+ const separator = '─'.repeat(columnWidths.reduce((a, b) => a + b, 0) + columnWidths.length + 1);
26
+
27
+ // Format header
28
+ const headerRow = '│ ' + headers.map((header, i) =>
29
+ header.padEnd(columnWidths[i])
30
+ ).join('│ ') + '│';
31
+
32
+ // Format rows
33
+ const dataRows = rows.map(row =>
34
+ '│ ' + row.map((cell, i) =>
35
+ String(cell || '').padEnd(columnWidths[i])
36
+ ).join('│ ') + '│'
37
+ );
38
+
39
+ return [
40
+ '┌' + separator + '┐',
41
+ headerRow,
42
+ '├' + separator + '┤',
43
+ ...dataRows,
44
+ '└' + separator + '┘',
45
+ ].join('\n');
46
+ }
47
+
48
+ /**
49
+ * Format summary statistics
50
+ */
51
+ export function formatSummary(result) {
52
+ const total = result.missing.length + result.unused.length + result.undocumented.length;
53
+
54
+ const stats = [
55
+ ['Category', 'Count', 'Status'],
56
+ ['Missing', result.missing.length, result.missing.length > 0 ? '❌' : '✅'],
57
+ ['Unused', result.unused.length, result.unused.length > 0 ? '⚠️' : '✅'],
58
+ ['Undocumented', result.undocumented.length, result.undocumented.length > 0 ? 'ℹ️' : '✅'],
59
+ ['Total Issues', total, total > 0 ? '⚠️' : '✅'],
60
+ ];
61
+
62
+ return formatTable(stats[0], stats.slice(1));
63
+ }
64
+
65
+ /**
66
+ * Format issues as a tree structure
67
+ */
68
+ export function formatTree(issues, title) {
69
+ if (issues.length === 0) {
70
+ return '';
71
+ }
72
+
73
+ const lines = [`\n${title}:`];
74
+
75
+ issues.forEach((issue, index) => {
76
+ const isLast = index === issues.length - 1;
77
+ const prefix = isLast ? '└─' : '├─';
78
+ const childPrefix = isLast ? ' ' : '│ ';
79
+
80
+ lines.push(`${prefix} ${issue.varName}`);
81
+
82
+ if (issue.locations) {
83
+ issue.locations.forEach((loc, locIndex) => {
84
+ const isLastLoc = locIndex === issue.locations.length - 1;
85
+ const locPrefix = isLastLoc ? '└─' : '├─';
86
+ lines.push(`${childPrefix}${locPrefix} ${loc.filePath}:${loc.lineNumber}`);
87
+ });
88
+ }
89
+ });
90
+
91
+ return lines.join('\n');
92
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Text Formatter Module
3
+ *
4
+ * Formats analysis results as human-readable text output with:
5
+ * - Colored categories (red, yellow, green)
6
+ * - Emoji icons for visual clarity
7
+ * - File reference listings with line numbers
8
+ * - Summary statistics
9
+ * - Support for --no-color flag
10
+ */
11
+
12
+ /**
13
+ * ANSI color codes for terminal output
14
+ */
15
+ const COLORS = {
16
+ RED: '\x1b[31m',
17
+ YELLOW: '\x1b[33m',
18
+ GREEN: '\x1b[32m',
19
+ RESET: '\x1b[0m',
20
+ BOLD: '\x1b[1m',
21
+ DIM: '\x1b[2m'
22
+ };
23
+
24
+ /**
25
+ * Emoji icons for issue categories
26
+ */
27
+ const ICONS = {
28
+ MISSING: '🔴',
29
+ UNUSED: '🟡',
30
+ UNDOCUMENTED: '🟢'
31
+ };
32
+
33
+ /**
34
+ * Formats analysis results as colored text output
35
+ *
36
+ * @param {{missing: Array, unused: Array, undocumented: Array, summary: Object}} result - Analysis result
37
+ * @param {{noColor: boolean, quiet: boolean}} options - Formatting options
38
+ * @returns {string} Formatted text output
39
+ */
40
+ export function formatText(result, options = {}) {
41
+ const { noColor = false, quiet = false } = options;
42
+
43
+ // If quiet mode and no issues, return empty string
44
+ if (quiet && hasNoIssues(result)) {
45
+ return '';
46
+ }
47
+
48
+ const sections = [];
49
+
50
+ // Format MISSING section
51
+ if (result.missing.length > 0) {
52
+ sections.push(formatMissingSection(result.missing, noColor));
53
+ }
54
+
55
+ // Format UNUSED section
56
+ if (result.unused.length > 0) {
57
+ sections.push(formatUnusedSection(result.unused, noColor));
58
+ }
59
+
60
+ // Format UNDOCUMENTED section
61
+ if (result.undocumented.length > 0) {
62
+ sections.push(formatUndocumentedSection(result.undocumented, noColor));
63
+ }
64
+
65
+ // Add summary line
66
+ sections.push(formatSummary(result.summary, noColor));
67
+
68
+ return sections.join('\n\n');
69
+ }
70
+
71
+ /**
72
+ * Checks if analysis result has no issues
73
+ *
74
+ * @param {{missing: Array, unused: Array, undocumented: Array}} result
75
+ * @returns {boolean} True if no issues found
76
+ */
77
+ export function hasNoIssues(result) {
78
+ return result.missing.length === 0 &&
79
+ result.unused.length === 0 &&
80
+ result.undocumented.length === 0;
81
+ }
82
+
83
+ /**
84
+ * Formats MISSING variables section
85
+ *
86
+ * @param {Array<{varName: string, references: Array}>} missing - Missing variable issues
87
+ * @param {boolean} noColor - Disable colored output
88
+ * @returns {string} Formatted section
89
+ */
90
+ export function formatMissingSection(missing, noColor) {
91
+ const icon = ICONS.MISSING;
92
+ const title = colorize('MISSING', COLORS.RED, noColor);
93
+ const count = missing.length;
94
+
95
+ let output = `${icon} ${title} (${count})\n`;
96
+ output += 'Variables used in code but not in .env.example:\n';
97
+
98
+ for (const issue of missing) {
99
+ output += ` - ${colorize(issue.varName, COLORS.BOLD, noColor)}\n`;
100
+
101
+ // List all file references
102
+ for (const ref of issue.references) {
103
+ output += ` ${colorize('→', COLORS.DIM, noColor)} ${ref.filePath}:${ref.lineNumber}\n`;
104
+ }
105
+ }
106
+
107
+ return output.trimEnd();
108
+ }
109
+
110
+ /**
111
+ * Formats UNUSED variables section
112
+ *
113
+ * @param {Array<{varName: string, definition: Object}>} unused - Unused variable issues
114
+ * @param {boolean} noColor - Disable colored output
115
+ * @returns {string} Formatted section
116
+ */
117
+ export function formatUnusedSection(unused, noColor) {
118
+ const icon = ICONS.UNUSED;
119
+ const title = colorize('UNUSED', COLORS.YELLOW, noColor);
120
+ const count = unused.length;
121
+
122
+ let output = `${icon} ${title} (${count})\n`;
123
+ output += 'Variables in .env.example but never used:\n';
124
+
125
+ for (const issue of unused) {
126
+ output += ` - ${colorize(issue.varName, COLORS.BOLD, noColor)}`;
127
+ output += ` ${colorize(`(.env.example:${issue.definition.lineNumber})`, COLORS.DIM, noColor)}\n`;
128
+ }
129
+
130
+ return output.trimEnd();
131
+ }
132
+
133
+ /**
134
+ * Formats UNDOCUMENTED variables section
135
+ *
136
+ * @param {Array<{varName: string, references: Array, definition: Object}>} undocumented - Undocumented variable issues
137
+ * @param {boolean} noColor - Disable colored output
138
+ * @returns {string} Formatted section
139
+ */
140
+ export function formatUndocumentedSection(undocumented, noColor) {
141
+ const icon = ICONS.UNDOCUMENTED;
142
+ const title = colorize('UNDOCUMENTED', COLORS.GREEN, noColor);
143
+ const count = undocumented.length;
144
+
145
+ let output = `${icon} ${title} (${count})\n`;
146
+ output += 'Variables used and defined but missing comments:\n';
147
+
148
+ for (const issue of undocumented) {
149
+ output += ` - ${colorize(issue.varName, COLORS.BOLD, noColor)}`;
150
+ output += ` ${colorize(`(.env.example:${issue.definition.lineNumber})`, COLORS.DIM, noColor)}\n`;
151
+ }
152
+
153
+ return output.trimEnd();
154
+ }
155
+
156
+ /**
157
+ * Formats summary statistics line
158
+ *
159
+ * @param {{totalMissing: number, totalUnused: number, totalUndocumented: number}} summary - Summary statistics
160
+ * @param {boolean} noColor - Disable colored output
161
+ * @returns {string} Formatted summary
162
+ */
163
+ export function formatSummary(summary, noColor) {
164
+ const parts = [];
165
+
166
+ if (summary.totalMissing > 0) {
167
+ parts.push(colorize(`${summary.totalMissing} missing`, COLORS.RED, noColor));
168
+ }
169
+
170
+ if (summary.totalUnused > 0) {
171
+ parts.push(colorize(`${summary.totalUnused} unused`, COLORS.YELLOW, noColor));
172
+ }
173
+
174
+ if (summary.totalUndocumented > 0) {
175
+ parts.push(colorize(`${summary.totalUndocumented} undocumented`, COLORS.GREEN, noColor));
176
+ }
177
+
178
+ if (parts.length === 0) {
179
+ return colorize('✓ No issues found', COLORS.GREEN, noColor);
180
+ }
181
+
182
+ return `Summary: ${parts.join(', ')}`;
183
+ }
184
+
185
+ /**
186
+ * Applies ANSI color codes to text
187
+ *
188
+ * @param {string} text - Text to colorize
189
+ * @param {string} color - ANSI color code
190
+ * @param {boolean} noColor - Disable colored output
191
+ * @returns {string} Colorized text or plain text if noColor is true
192
+ */
193
+ export function colorize(text, color, noColor) {
194
+ if (noColor) {
195
+ return text;
196
+ }
197
+ return `${color}${text}${COLORS.RESET}`;
198
+ }