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.
- package/LICENSE +26 -0
- package/README.md +347 -0
- package/build.js +127 -0
- package/lib/account-info.js +476 -0
- package/lib/colors.js +49 -0
- package/lib/commit.js +885 -0
- package/lib/config-command.js +310 -0
- package/lib/config-manager.js +344 -0
- package/lib/config.js +150 -0
- package/lib/devlog.js +182 -0
- package/lib/env-display.js +162 -0
- package/lib/git-info.js +509 -0
- package/lib/header.js +152 -0
- package/lib/input-utils.js +116 -0
- package/lib/logger.js +285 -0
- package/lib/machine-fingerprint.js +229 -0
- package/lib/maiass-command.js +79 -0
- package/lib/maiass-pipeline.js +1204 -0
- package/lib/maiass-variables.js +152 -0
- package/lib/secure-storage.js +256 -0
- package/lib/symbols.js +200 -0
- package/lib/token-validator.js +184 -0
- package/lib/version-command.js +256 -0
- package/lib/version-manager.js +902 -0
- package/maiass-standalone.cjs +148 -0
- package/maiass.cjs +34 -0
- package/maiass.mjs +167 -0
- package/package.json +45 -0
- package/setup-env.js +83 -0
package/lib/git-info.js
ADDED
|
@@ -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
|
+
};
|