maiass 5.7.31

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,509 @@
1
+ // Git branch detection and information extraction utility
2
+ import { execSync } from 'child_process';
3
+ import { log, logger } from './logger.js';
4
+ import colors from './colors.js';
5
+ import { SYMBOLS } from './symbols.js';
6
+
7
+ /**
8
+ * Git information object structure
9
+ * @typedef {Object} GitInfo
10
+ * @property {string} branch - Current branch name
11
+ * @property {string} author - Git author name
12
+ * @property {string} email - Git author email
13
+ * @property {string|null} jiraTicket - JIRA ticket number if found in branch name
14
+ * @property {boolean} isFeatureBranch - True if this is a feature branch
15
+ * @property {boolean} isReleaseBranch - True if this is a release branch
16
+ * @property {boolean} isMainBranch - True if this is main/main branch
17
+ * @property {boolean} isDevelopBranch - True if this is develop branch
18
+ * @property {boolean} isStagingBranch - True if this is staging branch
19
+ * @property {string} branchType - Branch type classification
20
+ * @property {Object} remote - Remote repository information
21
+ * @property {Object} status - Git status information (staged, unstaged, untracked files)
22
+ * @property {boolean} hasChanges - True if there are any changes (staged or unstaged)
23
+ * @property {boolean} isClean - True if working directory is clean
24
+ */
25
+
26
+ /**
27
+ * Execute git command safely
28
+ * @param {string} command - Git command to execute
29
+ * @param {boolean} silent - Whether to suppress errors
30
+ * @returns {string|null} Command output or null if failed
31
+ */
32
+ function executeGitCommand(command, silent = false) {
33
+ try {
34
+ const result = execSync(command, {
35
+ encoding: 'utf8',
36
+ stdio: silent ? 'pipe' : ['pipe', 'pipe', 'ignore']
37
+ });
38
+
39
+ // For git status --porcelain, preserve leading spaces but remove trailing newline
40
+ if (command.includes('git status --porcelain')) {
41
+ return result.replace(/\n$/, ''); // Only remove trailing newline
42
+ }
43
+
44
+ return result.trim(); // Normal trim for other commands
45
+ } catch (error) {
46
+ if (!silent) {
47
+ console.error(`Git command failed: ${command}`);
48
+ console.error(error.message);
49
+ }
50
+ return null;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Check if we're in a git repository
56
+ * @returns {boolean} True if in a git repository
57
+ */
58
+ export function isGitRepository() {
59
+ return executeGitCommand('git rev-parse --is-inside-work-tree', true) === 'true';
60
+ }
61
+
62
+ /**
63
+ * Get current git branch name
64
+ * @returns {string|null} Current branch name or null if not in git repo
65
+ */
66
+ export function getCurrentBranch() {
67
+ return executeGitCommand('git rev-parse --abbrev-ref HEAD', true);
68
+ }
69
+
70
+ /**
71
+ * Get git author information
72
+ * @returns {Object} Author information with name and email
73
+ */
74
+ export function getGitAuthor() {
75
+ const name = executeGitCommand('git config user.name', true) || 'Unknown';
76
+ const email = executeGitCommand('git config user.email', true) || 'unknown@example.com';
77
+
78
+ return { name, email };
79
+ }
80
+
81
+ /**
82
+ * Extract JIRA ticket number from branch name
83
+ * Pattern matches: feature/ABC-123, bugfix/XYZ-456, etc.
84
+ * Looks for pattern after last slash: [A-Z]+-[0-9]+
85
+ * @param {string} branchName - Branch name to analyze
86
+ * @returns {string|null} JIRA ticket number or null if not found
87
+ */
88
+ export function extractJiraTicket(branchName) {
89
+ if (!branchName) return null;
90
+
91
+ // Match after the last slash
92
+ const afterSlash = branchName.match(/.*\/([A-Z]+-[0-9]+)/);
93
+ if (afterSlash) return afterSlash[1];
94
+
95
+ // Match at the beginning of the branch name
96
+ const atStart = branchName.match(/^([A-Z]+-[0-9]+)/);
97
+ if (atStart) return atStart[1];
98
+
99
+ return null;
100
+ }
101
+
102
+ /**
103
+ * Classify branch type based on name and patterns
104
+ * @param {string} branchName - Branch name to classify
105
+ * @param {Object} branchConfig - Branch configuration from environment
106
+ * @returns {Object} Branch classification information
107
+ */
108
+ export function classifyBranch(branchName, branchConfig = {}) {
109
+ if (!branchName) return { type: 'unknown', isSpecial: false };
110
+
111
+ const {
112
+ developBranch = 'develop',
113
+ stagingBranch = 'staging',
114
+ mainBranch = 'main'
115
+ } = branchConfig;
116
+
117
+ const lowerBranch = branchName.toLowerCase();
118
+
119
+ // Check for exact matches first
120
+ if (branchName === mainBranch || branchName === 'main') {
121
+ return { type: 'main', isSpecial: true, isMain: true };
122
+ }
123
+
124
+ if (branchName === developBranch) {
125
+ return { type: 'develop', isSpecial: true, isDevelop: true };
126
+ }
127
+
128
+ if (branchName === stagingBranch) {
129
+ return { type: 'staging', isSpecial: true, isStaging: true };
130
+ }
131
+
132
+ // Check for pattern-based matches
133
+ if (lowerBranch.startsWith('feature/') || lowerBranch.startsWith('feat/')) {
134
+ return { type: 'feature', isSpecial: false, isFeature: true };
135
+ }
136
+
137
+ if (lowerBranch.startsWith('bugfix/') || lowerBranch.startsWith('bug/') || lowerBranch.startsWith('fix/')) {
138
+ return { type: 'bugfix', isSpecial: false, isBugfix: true };
139
+ }
140
+
141
+ if (lowerBranch.startsWith('hotfix/')) {
142
+ return { type: 'hotfix', isSpecial: true, isHotfix: true };
143
+ }
144
+
145
+ if (lowerBranch.startsWith('release/') || lowerBranch.startsWith('releases/')) {
146
+ return { type: 'release', isSpecial: true, isRelease: true };
147
+ }
148
+
149
+ if (lowerBranch.startsWith('chore/')) {
150
+ return { type: 'chore', isSpecial: false, isChore: true };
151
+ }
152
+
153
+ if (lowerBranch.startsWith('docs/') || lowerBranch.startsWith('doc/')) {
154
+ return { type: 'documentation', isSpecial: false, isDocs: true };
155
+ }
156
+
157
+ return { type: 'other', isSpecial: false, isOther: true };
158
+ }
159
+
160
+ /**
161
+ * Get git status information (staged, unstaged, untracked files)
162
+ * @returns {Object} Git status with file counts and lists
163
+ */
164
+ export function getGitStatus() {
165
+ if (!isGitRepository()) {
166
+ return null;
167
+ }
168
+
169
+ try {
170
+ // Get git status in porcelain format for easy parsing
171
+ const statusOutput = executeGitCommand('git status --porcelain', true);
172
+
173
+ if (!statusOutput) {
174
+ return {
175
+ staged: [],
176
+ unstaged: [],
177
+ untracked: [],
178
+ stagedCount: 0,
179
+ unstagedCount: 0,
180
+ untrackedCount: 0,
181
+ hasChanges: false,
182
+ isClean: true
183
+ };
184
+ }
185
+
186
+ const staged = [];
187
+ const unstaged = [];
188
+ const untracked = [];
189
+
190
+ const lines = statusOutput.split('\n').filter(line => line.trim());
191
+
192
+ lines.forEach(line => {
193
+ if (line.length < 3) return;
194
+
195
+ const indexStatus = line[0]; // Staged changes
196
+ const workingStatus = line[1]; // Unstaged changes
197
+ const filename = line.substring(3);
198
+
199
+ // Check for staged changes
200
+ if (indexStatus !== ' ' && indexStatus !== '?') {
201
+ staged.push({
202
+ file: filename,
203
+ status: indexStatus,
204
+ statusText: getStatusText(indexStatus)
205
+ });
206
+ }
207
+
208
+ // Check for unstaged changes
209
+ if (workingStatus !== ' ' && workingStatus !== '?') {
210
+ unstaged.push({
211
+ file: filename,
212
+ status: workingStatus,
213
+ statusText: getStatusText(workingStatus)
214
+ });
215
+ }
216
+
217
+ // Check for untracked files
218
+ if (indexStatus === '?' && workingStatus === '?') {
219
+ untracked.push({
220
+ file: filename,
221
+ status: '?',
222
+ statusText: 'untracked'
223
+ });
224
+ }
225
+ });
226
+
227
+ const stagedCount = staged.length;
228
+ const unstagedCount = unstaged.length;
229
+ const untrackedCount = untracked.length;
230
+ const hasChanges = stagedCount > 0 || unstagedCount > 0 || untrackedCount > 0;
231
+
232
+ return {
233
+ staged,
234
+ unstaged,
235
+ untracked,
236
+ stagedCount,
237
+ unstagedCount,
238
+ untrackedCount,
239
+ hasChanges,
240
+ isClean: !hasChanges
241
+ };
242
+ } catch (error) {
243
+ return null;
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Convert git status code to human-readable text
249
+ * @param {string} statusCode - Git status code
250
+ * @returns {string} Human-readable status
251
+ */
252
+ function getStatusText(statusCode) {
253
+ switch (statusCode) {
254
+ case 'M': return 'modified';
255
+ case 'A': return 'added';
256
+ case 'D': return 'deleted';
257
+ case 'R': return 'renamed';
258
+ case 'C': return 'copied';
259
+ case 'U': return 'unmerged';
260
+ case '?': return 'untracked';
261
+ case '!': return 'ignored';
262
+ default: return 'unknown';
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Get remote repository information
268
+ * @returns {Object} Remote repository information
269
+ */
270
+ export function getRemoteInfo() {
271
+ const remoteUrl = executeGitCommand('git remote get-url origin', true);
272
+
273
+ if (!remoteUrl) {
274
+ return { url: null, provider: null, owner: null, repo: null };
275
+ }
276
+
277
+ // Parse GitHub URLs
278
+ const githubMatch = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/\.]+)/);
279
+ if (githubMatch) {
280
+ return {
281
+ url: remoteUrl,
282
+ provider: 'github',
283
+ owner: githubMatch[1],
284
+ repo: githubMatch[2]
285
+ };
286
+ }
287
+
288
+ // Parse Bitbucket URLs
289
+ const bitbucketMatch = remoteUrl.match(/bitbucket\.org[:/]([^/]+)\/([^/\.]+)/);
290
+ if (bitbucketMatch) {
291
+ return {
292
+ url: remoteUrl,
293
+ provider: 'bitbucket',
294
+ owner: bitbucketMatch[1],
295
+ repo: bitbucketMatch[2]
296
+ };
297
+ }
298
+
299
+ // Parse GitLab URLs
300
+ const gitlabMatch = remoteUrl.match(/gitlab\.com[:/]([^/]+)\/([^/\.]+)/);
301
+ if (gitlabMatch) {
302
+ return {
303
+ url: remoteUrl,
304
+ provider: 'gitlab',
305
+ owner: gitlabMatch[1],
306
+ repo: gitlabMatch[2]
307
+ };
308
+ }
309
+
310
+ return {
311
+ url: remoteUrl,
312
+ provider: 'unknown',
313
+ owner: null,
314
+ repo: null
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Get comprehensive git information
320
+ * @param {Object} options - Configuration options
321
+ * @returns {GitInfo} Complete git information with isRepository flag
322
+ */
323
+ export function getGitInfo(options = {}) {
324
+ if (!isGitRepository()) {
325
+ return {
326
+ isRepository: false,
327
+ currentBranch: null,
328
+ hasChanges: false,
329
+ stagedChanges: [],
330
+ unstagedChanges: [],
331
+ gitAuthor: null,
332
+ jiraTicket: null
333
+ };
334
+ }
335
+
336
+ const branch = getCurrentBranch();
337
+ if (!branch) {
338
+ return {
339
+ isRepository: true,
340
+ currentBranch: null,
341
+ hasChanges: false,
342
+ stagedChanges: [],
343
+ unstagedChanges: [],
344
+ gitAuthor: null,
345
+ jiraTicket: null
346
+ };
347
+ }
348
+
349
+ const author = getGitAuthor();
350
+ const jiraTicket = extractJiraTicket(branch);
351
+ const remote = getRemoteInfo();
352
+ const status = getGitStatus();
353
+
354
+ // Get branch configuration from environment or options
355
+ const branchConfig = {
356
+ developBranch: process.env.MAIASS_DEVELOPBRANCH || options.developBranch || 'develop',
357
+ stagingBranch: process.env.MAIASS_STAGINGBRANCH || options.stagingBranch || 'staging',
358
+ mainBranch: process.env.MAIASS_MAINBRANCH || options.mainBranch || 'main'
359
+ };
360
+
361
+ const classification = classifyBranch(branch, branchConfig);
362
+
363
+ return {
364
+ isRepository: true,
365
+ currentBranch: branch,
366
+ branch,
367
+ author: author.name,
368
+ email: author.email,
369
+ gitAuthor: `${author.name} <${author.email}>`,
370
+ jiraTicket,
371
+ branchType: classification.type,
372
+ isFeatureBranch: classification.isFeature || false,
373
+ isReleaseBranch: classification.isRelease || false,
374
+ isMainBranch: classification.isMain || false,
375
+ isDevelopBranch: classification.isDevelop || false,
376
+ isStagingBranch: classification.isStaging || false,
377
+ isBugfixBranch: classification.isBugfix || false,
378
+ isHotfixBranch: classification.isHotfix || false,
379
+ isSpecialBranch: classification.isSpecial || false,
380
+ remote,
381
+ status,
382
+ hasChanges: status ? status.hasChanges : false,
383
+ isClean: status ? status.isClean : true,
384
+ stagedChanges: status ? status.stagedChanges : [],
385
+ unstagedChanges: status ? status.unstagedChanges : [],
386
+ branchConfig
387
+ };
388
+ }
389
+
390
+ /**
391
+ * Display git information in a formatted way
392
+ * @param {GitInfo} gitInfo - Git information to display
393
+ * @param {Object} options - Display options
394
+ */
395
+ export function displayGitInfo(gitInfo, options = {}) {
396
+ const { showRemote = true, showAuthor = true, showStatus = true } = options;
397
+
398
+ if (!gitInfo) {
399
+ log.error(SYMBOLS.CROSS, 'Not in a git repository');
400
+ return;
401
+ }
402
+
403
+ log.info(SYMBOLS.GEAR, 'Git Information');
404
+ console.log();
405
+
406
+ // Branch information
407
+ const branchColor = gitInfo.isSpecialBranch ? colors.BYellow : colors.BGreen;
408
+ console.log(` ${colors.BBlue('Current Branch:')} ${branchColor(gitInfo.branch)}`);
409
+ console.log(` ${colors.BBlue('Branch Type:')} ${colors.White(gitInfo.branchType)}`);
410
+
411
+ // JIRA ticket if found
412
+ if (gitInfo.jiraTicket) {
413
+ console.log(` ${colors.BBlue('JIRA Ticket:')} ${colors.BPurple(gitInfo.jiraTicket)}`);
414
+ }
415
+
416
+ // Author information
417
+ if (showAuthor) {
418
+ console.log(` ${colors.BBlue('Author:')} ${colors.White(gitInfo.author)}`);
419
+ console.log(` ${colors.BBlue('Email:')} ${colors.Gray(gitInfo.email)}`);
420
+ }
421
+
422
+ // Git status information
423
+ if (showStatus && gitInfo.status) {
424
+ const status = gitInfo.status;
425
+ const statusColor = status.isClean ? colors.Green : colors.Yellow;
426
+ const statusText = status.isClean ? 'Clean' : 'Has changes';
427
+ console.log(` ${colors.BBlue('Status:')} ${statusColor(statusText)}`);
428
+
429
+ if (!status.isClean) {
430
+ if (status.stagedCount > 0) {
431
+ console.log(` ${colors.BBlue('Staged:')} ${colors.Green(status.stagedCount + ' file' + (status.stagedCount === 1 ? '' : 's'))}`);
432
+ }
433
+ if (status.unstagedCount > 0) {
434
+ console.log(` ${colors.BBlue('Unstaged:')} ${colors.Yellow(status.unstagedCount + ' file' + (status.unstagedCount === 1 ? '' : 's'))}`);
435
+ }
436
+ if (status.untrackedCount > 0) {
437
+ console.log(` ${colors.BBlue('Untracked:')} ${colors.Red(status.untrackedCount + ' file' + (status.untrackedCount === 1 ? '' : 's'))}`);
438
+ }
439
+ }
440
+ }
441
+
442
+ // Remote information
443
+ if (showRemote && gitInfo.remote.url) {
444
+ console.log(` ${colors.BBlue('Remote:')} ${colors.Cyan(gitInfo.remote.provider || 'unknown')} ${colors.Gray('(' + gitInfo.remote.url + ')')}`);
445
+ if (gitInfo.remote.owner && gitInfo.remote.repo) {
446
+ console.log(` ${colors.BBlue('Repository:')} ${colors.White(gitInfo.remote.owner + '/' + gitInfo.remote.repo)}`);
447
+ }
448
+ }
449
+
450
+ console.log();
451
+ }
452
+
453
+ /**
454
+ * Validate branch for operations (similar to branchDetection in maiass.sh)
455
+ * @param {GitInfo} gitInfo - Git information
456
+ * @returns {Object} Validation result with warnings and recommendations
457
+ */
458
+ export function validateBranchForOperations(gitInfo) {
459
+ if (!gitInfo) {
460
+ return {
461
+ valid: false,
462
+ error: 'Not in a git repository',
463
+ warnings: [],
464
+ recommendations: []
465
+ };
466
+ }
467
+
468
+ const warnings = [];
469
+ const recommendations = [];
470
+ let valid = true;
471
+
472
+ // Check if on main/main branch
473
+ if (gitInfo.isMainBranch) {
474
+ warnings.push(`You are on the ${gitInfo.branch} branch`);
475
+ recommendations.push(`Consider switching to ${gitInfo.branchConfig.developBranch} for development work`);
476
+ }
477
+
478
+ // Check if on release branch
479
+ if (gitInfo.isReleaseBranch) {
480
+ warnings.push(`You are on a release branch: ${gitInfo.branch}`);
481
+ recommendations.push('Release branches should only be used for release preparation');
482
+ }
483
+
484
+ // Check if on staging branch
485
+ if (gitInfo.isStagingBranch) {
486
+ warnings.push(`You are on the ${gitInfo.branch} branch`);
487
+ recommendations.push(`Consider switching to ${gitInfo.branchConfig.developBranch} for development work`);
488
+ }
489
+
490
+ return {
491
+ valid,
492
+ warnings,
493
+ recommendations,
494
+ gitInfo
495
+ };
496
+ }
497
+
498
+ // Export all functions as default object
499
+ export default {
500
+ isGitRepository,
501
+ getCurrentBranch,
502
+ getGitAuthor,
503
+ extractJiraTicket,
504
+ classifyBranch,
505
+ getRemoteInfo,
506
+ getGitInfo,
507
+ displayGitInfo,
508
+ validateBranchForOperations
509
+ };
package/lib/header.js ADDED
@@ -0,0 +1,152 @@
1
+ // Header display for MAIASS - matches bashmaiass branding
2
+ import colors from './colors.js';
3
+ import chalk from 'chalk';
4
+
5
+ /**
6
+ * Check if terminal supports Unicode
7
+ * @returns {boolean}
8
+ */
9
+ function supportsUnicode() {
10
+ // Check environment variables
11
+ const lang = process.env.LANG || process.env.LC_ALL || '';
12
+ if (lang.toLowerCase().includes('utf')) return true;
13
+
14
+ // Windows typically doesn't support Unicode well in older terminals
15
+ if (process.platform === 'win32') {
16
+ // Windows Terminal and newer PowerShell support Unicode
17
+ return process.env.WT_SESSION || process.env.TERM_PROGRAM === 'vscode';
18
+ }
19
+
20
+ // macOS and Linux typically support Unicode
21
+ return true;
22
+ }
23
+
24
+ /**
25
+ * Check if terminal supports 256 colors
26
+ * @returns {boolean}
27
+ */
28
+ function supports256Color() {
29
+ const term = process.env.TERM || '';
30
+ return term.includes('256') || term.includes('xterm');
31
+ }
32
+
33
+ /**
34
+ * Check if terminal supports truecolor (24-bit)
35
+ * @returns {boolean}
36
+ */
37
+ function supportsTruecolor() {
38
+ const colorterm = process.env.COLORTERM || '';
39
+ return colorterm === 'truecolor' || colorterm === '24bit';
40
+ }
41
+
42
+ /**
43
+ * Create a gradient line using the best available color support
44
+ * @param {number} length - Length of the line
45
+ * @param {string} startHex - Starting color (hex format)
46
+ * @param {string} endHex - Ending color (hex format)
47
+ * @returns {string} Colored gradient line
48
+ */
49
+ function printGradientLine(length = 60, startHex = '#f7b2c4', endHex = '#6b0022') {
50
+ const useUnicode = supportsUnicode();
51
+ const char = useUnicode ? '═' : '=';
52
+
53
+ // Truecolor support (best quality)
54
+ if (supportsTruecolor()) {
55
+ const r1 = parseInt(startHex.slice(1, 3), 16);
56
+ const g1 = parseInt(startHex.slice(3, 5), 16);
57
+ const b1 = parseInt(startHex.slice(5, 7), 16);
58
+
59
+ const r2 = parseInt(endHex.slice(1, 3), 16);
60
+ const g2 = parseInt(endHex.slice(3, 5), 16);
61
+ const b2 = parseInt(endHex.slice(5, 7), 16);
62
+
63
+ let line = '';
64
+ for (let i = 0; i < length; i++) {
65
+ const t = length > 1 ? i / (length - 1) : 0;
66
+ const r = Math.round(r1 + (r2 - r1) * t);
67
+ const g = Math.round(g1 + (g2 - g1) * t);
68
+ const b = Math.round(b1 + (b2 - b1) * t);
69
+ line += chalk.rgb(r, g, b)(char);
70
+ }
71
+ return line;
72
+ }
73
+
74
+ // 256-color fallback
75
+ if (supports256Color()) {
76
+ // Pink -> burgundy palette (matching bashmaiass)
77
+ const palette = [224, 217, 218, 212, 211, 210, 205, 204, 198, 197, 161, 125, 89, 88, 52];
78
+ const total = palette.length;
79
+ const per = Math.ceil(length / total);
80
+
81
+ let line = '';
82
+ let printed = 0;
83
+
84
+ for (const code of palette) {
85
+ if (printed >= length) break;
86
+ const count = Math.min(per, length - printed);
87
+ line += chalk.ansi256(code)(char.repeat(count));
88
+ printed += count;
89
+ }
90
+
91
+ return line;
92
+ }
93
+
94
+ // Plain fallback (no colors)
95
+ return char.repeat(length);
96
+ }
97
+
98
+ /**
99
+ * Create colored MAIASS text with gradient
100
+ * @returns {string} Colored MAIASS text
101
+ */
102
+ function colourMaiass() {
103
+ // Soft pink -> burgundy across M A I A S S
104
+ const cols = [218, 211, 205, 198, 161, 88];
105
+ const word = 'MAIASS';
106
+
107
+ if (supports256Color()) {
108
+ let colored = '';
109
+ for (let i = 0; i < word.length; i++) {
110
+ colored += chalk.ansi256(cols[i])(word[i]);
111
+ }
112
+ return colored;
113
+ }
114
+
115
+ // Fallback to simple bold
116
+ return chalk.bold(word);
117
+ }
118
+
119
+ /**
120
+ * Display the MAIASS header (matches bashmaiass style)
121
+ * @param {string} version - Current version number
122
+ */
123
+ export function displayHeader(version) {
124
+ const maiass = colourMaiass();
125
+ const prefix = colors.BSoftPink('|))');
126
+ const welcomeText = colors.BBlue(' Welcome to ');
127
+ const versionText = colors.Blue(` (node) v${version} `);
128
+
129
+ console.log(printGradientLine(60, '#0000FF', '#29CCC1'));
130
+ console.log(`${prefix}${welcomeText}${maiass}${versionText}`);
131
+ console.log(printGradientLine(60, '#0000FF', '#29CCC1'));
132
+ }
133
+
134
+ /**
135
+ * Display a simple separator line
136
+ * @param {number} length - Length of the line
137
+ * @param {string} char - Character to use
138
+ * @param {Function} colorFn - Color function to apply
139
+ */
140
+ export function displaySeparator(length = 50, char = '─', colorFn = colors.BCyan) {
141
+ const useUnicode = supportsUnicode();
142
+ const actualChar = useUnicode ? char : '-';
143
+ console.log(colorFn(actualChar.repeat(length)));
144
+ }
145
+
146
+ export default {
147
+ displayHeader,
148
+ displaySeparator,
149
+ supportsUnicode,
150
+ supports256Color,
151
+ supportsTruecolor
152
+ };