git-push-time 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.
- package/.github/workflows/npm-publish.yml +1 -2
- package/README.md +219 -215
- package/bin/index.js +25 -23
- package/lib/commands/github.js +16 -10
- package/lib/commands/pull.js +3 -2
- package/lib/commands/push.js +84 -37
- package/lib/git.js +24 -0
- package/lib/prompt-helpers.js +194 -62
- package/lib/ui.js +359 -100
- package/package.json +2 -1
package/lib/prompt-helpers.js
CHANGED
|
@@ -1,62 +1,194 @@
|
|
|
1
|
-
import { input, select } from '@inquirer/prompts';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
1
|
+
import { input, select } from '@inquirer/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { boxTopLeft, boxRow, boxBottom, getW, truncateVisual } from './ui.js';
|
|
4
|
+
|
|
5
|
+
export const BACK = '__back__';
|
|
6
|
+
export const BACK_TOKENS = new Set(['..', '/back', '<<']);
|
|
7
|
+
|
|
8
|
+
const BORDER = chalk.hex('#6366F1');
|
|
9
|
+
const ACCENT = chalk.hex('#38BDF8');
|
|
10
|
+
const DIM = chalk.hex('#64748B');
|
|
11
|
+
const HINT = chalk.hex('#94A3B8');
|
|
12
|
+
|
|
13
|
+
export function isBackInput(v) {
|
|
14
|
+
return BACK_TOKENS.has(String(v || '').trim());
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function cleanTitle(t) {
|
|
18
|
+
return String(t ?? '').trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function printOpenHeader(title, hintLines = []) {
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(boxTopLeft(chalk.bold.hex('#F8FAFC')(cleanTitle(title)), BORDER));
|
|
24
|
+
for (const h of hintLines) {
|
|
25
|
+
if (!h) continue;
|
|
26
|
+
console.log(boxRow(HINT(truncateVisual(String(h), getW() - 6)), BORDER));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function printCloseWithAnswer(label, value) {
|
|
31
|
+
const W = getW();
|
|
32
|
+
const answer = value === BACK || value === undefined
|
|
33
|
+
? chalk.hex('#FCD34D')('« Quay lại')
|
|
34
|
+
: chalk.hex('#34D399').bold(truncateVisual(String(value ?? ''), Math.max(10, W - 14)));
|
|
35
|
+
console.log(
|
|
36
|
+
boxRow(
|
|
37
|
+
`${chalk.hex('#34D399')('✓')} ${chalk.hex('#CBD5E1')(truncateVisual(cleanTitle(label), Math.max(8, W - 30)) + ':')} ${answer}`,
|
|
38
|
+
BORDER,
|
|
39
|
+
),
|
|
40
|
+
);
|
|
41
|
+
console.log(boxBottom(BORDER));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function printClosePlain() {
|
|
45
|
+
console.log(boxBottom(BORDER));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function shortChoiceName(choices, value) {
|
|
49
|
+
try {
|
|
50
|
+
const found = (choices || []).find((c) => c && c.value === value);
|
|
51
|
+
if (!found) return String(value ?? '');
|
|
52
|
+
// name có thể chứa màu chalk -> strip bằng cách lấy raw rồi cắt
|
|
53
|
+
return String(found.short || found.name || found.value || '').replace(/\u001b\[[0-9;]*m/g, '');
|
|
54
|
+
} catch {
|
|
55
|
+
return String(value ?? '');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const baseInputTheme = {
|
|
60
|
+
prefix: { idle: '', done: '' },
|
|
61
|
+
style: {
|
|
62
|
+
message: (t) => t,
|
|
63
|
+
answer: (t) => chalk.hex('#34D399').bold(t),
|
|
64
|
+
defaultAnswer: (t) => DIM(`(${t})`),
|
|
65
|
+
error: (t) => chalk.hex('#FDA4AF')(` x ${t}`),
|
|
66
|
+
help: (t) => DIM(t),
|
|
67
|
+
highlight: (t) => chalk.hex('#38BDF8').bold(t),
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const baseSelectTheme = {
|
|
72
|
+
prefix: { idle: '', done: '' },
|
|
73
|
+
icon: { cursor: chalk.hex('#38BDF8').bold('❯ ') },
|
|
74
|
+
style: {
|
|
75
|
+
message: (t) => t,
|
|
76
|
+
answer: (t) => chalk.hex('#34D399').bold(t),
|
|
77
|
+
error: (t) => chalk.hex('#FDA4AF')(` x ${t}`),
|
|
78
|
+
help: (t) => DIM(t),
|
|
79
|
+
highlight: (t) => chalk.hex('#38BDF8').bold(t),
|
|
80
|
+
description: (t) => DIM(t),
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
function promptLine(label) {
|
|
85
|
+
// Dòng nhập liệu mang viền trái │ để thị giác nằm gọn trong khung
|
|
86
|
+
// đang mở phía trên (boxTop) và sẽ đóng (boxBottom) sau khi xong.
|
|
87
|
+
return `${BORDER('│')} ${ACCENT('❯')} ${chalk.whiteBright(cleanTitle(label))}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Select nằm TRONG khung: header mở -> choices -> footer đóng + đáp án.
|
|
92
|
+
*/
|
|
93
|
+
export async function selectBox({ message, choices, hint, pageSize, ...rest }) {
|
|
94
|
+
const { clearPromptOnDone: _ignore, theme: customTheme, ...cfg } = rest;
|
|
95
|
+
const hintLines = hint ? (Array.isArray(hint) ? hint : [hint]) : [];
|
|
96
|
+
printOpenHeader(message, hintLines);
|
|
97
|
+
|
|
98
|
+
let value;
|
|
99
|
+
try {
|
|
100
|
+
value = await select(
|
|
101
|
+
{
|
|
102
|
+
message: promptLine('Chọn'),
|
|
103
|
+
choices,
|
|
104
|
+
pageSize: pageSize || Math.min(12, (choices || []).length + 2),
|
|
105
|
+
theme: { ...baseSelectTheme, ...(customTheme || {}) },
|
|
106
|
+
...cfg,
|
|
107
|
+
},
|
|
108
|
+
{ clearPromptOnDone: true },
|
|
109
|
+
);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
printClosePlain();
|
|
112
|
+
throw e;
|
|
113
|
+
}
|
|
114
|
+
printCloseWithAnswer(message, shortChoiceName(choices, value));
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Prompt select có sẵn lựa chọn « Quay lại (nằm trong khung).
|
|
120
|
+
*/
|
|
121
|
+
export async function selectWithBack({ message, choices, hint, pageSize, ...rest }) {
|
|
122
|
+
const hasBack = (choices || []).some((c) => c && c.value === BACK);
|
|
123
|
+
const finalChoices = hasBack
|
|
124
|
+
? choices
|
|
125
|
+
: [
|
|
126
|
+
...choices,
|
|
127
|
+
{ name: '« Quay lại (bước trước)', value: BACK, description: 'Về bước trước / menu chính' }
|
|
128
|
+
];
|
|
129
|
+
const backHint = hint ?? '↑↓ di chuyển · Enter chọn · chọn « Quay lại để lùi bước';
|
|
130
|
+
return await selectBox({ message, choices: finalChoices, hint: backHint, pageSize, ...rest });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Prompt input nằm TRONG khung: header mở -> ô gõ (│ › ...) -> footer + đáp án.
|
|
135
|
+
*/
|
|
136
|
+
export async function inputBox({ message, promptLabel = 'Nhập', hint, default: def, validate, ...rest }) {
|
|
137
|
+
const { clearPromptOnDone: _ignore, theme: customTheme, ...cfg } = rest;
|
|
138
|
+
const hintLines = [];
|
|
139
|
+
if (hint) hintLines.push(...(Array.isArray(hint) ? hint : [hint]));
|
|
140
|
+
if (def) hintLines.push(`Mặc định: ${def}`);
|
|
141
|
+
printOpenHeader(message, hintLines);
|
|
142
|
+
|
|
143
|
+
const wrappedValidate = (val) => {
|
|
144
|
+
if (isBackInput(val)) return true;
|
|
145
|
+
if (validate) return validate(val);
|
|
146
|
+
return true;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
let val;
|
|
150
|
+
try {
|
|
151
|
+
val = await input(
|
|
152
|
+
{
|
|
153
|
+
message: promptLine(promptLabel),
|
|
154
|
+
default: def,
|
|
155
|
+
validate: wrappedValidate,
|
|
156
|
+
theme: { ...baseInputTheme, ...(customTheme || {}) },
|
|
157
|
+
...cfg,
|
|
158
|
+
},
|
|
159
|
+
{ clearPromptOnDone: true },
|
|
160
|
+
);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
printClosePlain();
|
|
163
|
+
throw e;
|
|
164
|
+
}
|
|
165
|
+
if (isBackInput(val)) {
|
|
166
|
+
printCloseWithAnswer(message, BACK);
|
|
167
|
+
return BACK;
|
|
168
|
+
}
|
|
169
|
+
printCloseWithAnswer(message, val || def || '');
|
|
170
|
+
return val;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Prompt input có hướng dẫn gõ ".." để quay lại (nằm trong khung).
|
|
175
|
+
*/
|
|
176
|
+
export async function inputWithBack({ message, hint, validate, ...rest }) {
|
|
177
|
+
const backHint = hint ?? 'Gõ ".." để quay lại bước trước';
|
|
178
|
+
return await inputBox({ message, hint: backHint, validate, ...rest });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Hỏi Tiếp tục / Quay lại menu / Thoát (nằm trong khung).
|
|
183
|
+
*/
|
|
184
|
+
export async function promptContinueOrBack(message = 'Bạn muốn tiếp tục hay quay lại?') {
|
|
185
|
+
return await selectBox({
|
|
186
|
+
message,
|
|
187
|
+
hint: 'Chọn bước tiếp theo',
|
|
188
|
+
choices: [
|
|
189
|
+
{ name: 'Tiếp tục', value: 'continue', description: 'Đi tiếp bước kế tiếp' },
|
|
190
|
+
{ name: 'Quay lại bảng điều khiển', value: 'back', description: 'Về màn hình chọn hành động' },
|
|
191
|
+
{ name: 'Thoát', value: 'exit', description: 'Dừng, không làm gì' }
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
}
|