@nbtca/prompt 1.5.0 → 1.5.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/dist/app/app.js +142 -13
- package/dist/app/chrome.js +13 -2
- package/dist/app/frame.js +14 -2
- package/dist/app/keys.js +109 -1
- package/dist/app/views/docs-render.js +3 -2
- package/dist/app/views/docs.js +90 -25
- package/dist/app/views/events-render.js +2 -1
- package/dist/app/views/events.js +13 -2
- package/dist/app/views/home.js +46 -24
- package/dist/app/views/schedule-render.js +4 -3
- package/dist/app/views/schedule.js +101 -33
- package/dist/cli.js +570 -0
- package/dist/config/preferences.js +7 -0
- package/dist/core/canvas.js +1 -0
- package/dist/core/components/menu.js +23 -22
- package/dist/core/components/spinner.js +17 -1
- package/dist/core/text.js +7 -3
- package/dist/core/vim-keys.js +149 -6
- package/dist/features/calendar-store.js +27 -0
- package/dist/features/calendar.js +55 -6
- package/dist/features/docs-client.js +225 -0
- package/dist/features/docs.js +224 -68
- package/dist/features/links.js +51 -0
- package/dist/features/status.js +83 -14
- package/dist/features/student-timetable.js +1 -2
- package/dist/features/theme.js +3 -3
- package/dist/features/update.js +3 -2
- package/dist/i18n/locales/en.json +7 -2
- package/dist/i18n/locales/zh.json +7 -2
- package/dist/index.js +5 -498
- package/package.json +2 -1
package/dist/cli.js
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { main } from './main.js';
|
|
3
|
+
import { fetchEvents, fetchHeatmapBuckets, renderEventsTable, serializeEvents, } from './features/calendar.js';
|
|
4
|
+
import { renderHeatmap } from './features/calendar-heatmap.js';
|
|
5
|
+
import { checkServices, countServiceHealth, hasServiceFailures, renderServiceStatusTable, serializeServiceStatus, } from './features/status.js';
|
|
6
|
+
import { pickIcon } from './core/icons.js';
|
|
7
|
+
import { applyColorModePreference } from './config/preferences.js';
|
|
8
|
+
import { openDocsInBrowser } from './features/docs.js';
|
|
9
|
+
import { runThemeCommand } from './features/theme.js';
|
|
10
|
+
import { saveLanguagePreference, t, fmt } from './i18n/index.js';
|
|
11
|
+
import { clearScreen, handleGracefulExit } from './core/ui.js';
|
|
12
|
+
import { APP_INFO, URLS } from './config/data.js';
|
|
13
|
+
import { runUpdateCheck } from './features/update.js';
|
|
14
|
+
import { runStudentTimetableCommand } from './features/student-timetable.js';
|
|
15
|
+
import { showAbout } from './features/about.js';
|
|
16
|
+
import { openUrlInBrowser } from './features/links.js';
|
|
17
|
+
const ACTION_ALIASES = {
|
|
18
|
+
events: 'events',
|
|
19
|
+
event: 'events',
|
|
20
|
+
repair: 'repair',
|
|
21
|
+
docs: 'docs',
|
|
22
|
+
doc: 'docs',
|
|
23
|
+
website: 'website',
|
|
24
|
+
web: 'website',
|
|
25
|
+
github: 'github',
|
|
26
|
+
gh: 'github',
|
|
27
|
+
roadmap: 'roadmap',
|
|
28
|
+
board: 'roadmap',
|
|
29
|
+
about: 'about',
|
|
30
|
+
status: 'status',
|
|
31
|
+
};
|
|
32
|
+
const URL_ACTIONS = {
|
|
33
|
+
repair: URLS.repair,
|
|
34
|
+
website: URLS.homepage,
|
|
35
|
+
github: URLS.github,
|
|
36
|
+
roadmap: URLS.roadmap,
|
|
37
|
+
};
|
|
38
|
+
const KNOWN_FLAGS = new Set([
|
|
39
|
+
'--help',
|
|
40
|
+
'--version',
|
|
41
|
+
'--open',
|
|
42
|
+
'--json',
|
|
43
|
+
'--plain',
|
|
44
|
+
'--no-logo',
|
|
45
|
+
'--watch',
|
|
46
|
+
'--today',
|
|
47
|
+
'--heatmap',
|
|
48
|
+
'--week',
|
|
49
|
+
'--month',
|
|
50
|
+
'--one-shot',
|
|
51
|
+
'--no-save',
|
|
52
|
+
]);
|
|
53
|
+
const KNOWN_FLAG_PREFIXES = [
|
|
54
|
+
'--interval=',
|
|
55
|
+
'--timeout=',
|
|
56
|
+
'--retries=',
|
|
57
|
+
'--next=',
|
|
58
|
+
'--search=',
|
|
59
|
+
'--term=',
|
|
60
|
+
'--output=',
|
|
61
|
+
'--week-one=',
|
|
62
|
+
];
|
|
63
|
+
const STATUS_WATCH_INTERVAL_MIN = 3;
|
|
64
|
+
const STATUS_WATCH_INTERVAL_MAX = 300;
|
|
65
|
+
const STATUS_TIMEOUT_MIN = 1000;
|
|
66
|
+
const STATUS_TIMEOUT_MAX = 20000;
|
|
67
|
+
const STATUS_RETRIES_MIN = 0;
|
|
68
|
+
const STATUS_RETRIES_MAX = 5;
|
|
69
|
+
const ASCII_DECIMAL_INTEGER = /^[0-9]+$/;
|
|
70
|
+
function parseArgs(argv) {
|
|
71
|
+
const flags = new Set();
|
|
72
|
+
const positionals = [];
|
|
73
|
+
for (const token of argv) {
|
|
74
|
+
if (token.startsWith('--')) {
|
|
75
|
+
flags.add(token);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
positionals.push(token);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
...(positionals[0] === undefined ? {} : { command: positionals[0].toLowerCase() }),
|
|
83
|
+
args: positionals.slice(1),
|
|
84
|
+
flags,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function isTty(value) {
|
|
88
|
+
return value === true;
|
|
89
|
+
}
|
|
90
|
+
function parseAsciiDecimalInteger(value) {
|
|
91
|
+
if (!ASCII_DECIMAL_INTEGER.test(value))
|
|
92
|
+
return undefined;
|
|
93
|
+
const parsed = Number(value);
|
|
94
|
+
return Number.isSafeInteger(parsed) ? parsed : undefined;
|
|
95
|
+
}
|
|
96
|
+
function terminalWidth() {
|
|
97
|
+
const stdoutColumns = process.stdout.columns;
|
|
98
|
+
if (typeof stdoutColumns === 'number' && Number.isFinite(stdoutColumns) && stdoutColumns > 0) {
|
|
99
|
+
return Math.floor(stdoutColumns);
|
|
100
|
+
}
|
|
101
|
+
const environmentColumns = process.env['COLUMNS'];
|
|
102
|
+
if (environmentColumns === undefined)
|
|
103
|
+
return undefined;
|
|
104
|
+
const parsed = parseAsciiDecimalInteger(environmentColumns);
|
|
105
|
+
return parsed !== undefined && parsed > 0 ? parsed : undefined;
|
|
106
|
+
}
|
|
107
|
+
function hasInteractiveTerminal() {
|
|
108
|
+
return isTty(process.stdin.isTTY) && isTty(process.stdout.isTTY);
|
|
109
|
+
}
|
|
110
|
+
function getAllowedFlagsFor(command) {
|
|
111
|
+
const allowed = new Set(['--help', '--plain']);
|
|
112
|
+
if (!command) {
|
|
113
|
+
allowed.add('--no-logo');
|
|
114
|
+
allowed.add('--version');
|
|
115
|
+
return allowed;
|
|
116
|
+
}
|
|
117
|
+
if (command === 'lang' || command === 'language')
|
|
118
|
+
return allowed;
|
|
119
|
+
if (command === 'theme')
|
|
120
|
+
return allowed;
|
|
121
|
+
if (command === 'schedule' || command === 'timetable') {
|
|
122
|
+
allowed.add('--one-shot');
|
|
123
|
+
allowed.add('--no-save');
|
|
124
|
+
return allowed;
|
|
125
|
+
}
|
|
126
|
+
const action = ACTION_ALIASES[command];
|
|
127
|
+
if (!action)
|
|
128
|
+
return allowed;
|
|
129
|
+
switch (action) {
|
|
130
|
+
case 'events':
|
|
131
|
+
allowed.add('--json');
|
|
132
|
+
allowed.add('--today');
|
|
133
|
+
allowed.add('--heatmap');
|
|
134
|
+
allowed.add('--week');
|
|
135
|
+
allowed.add('--month');
|
|
136
|
+
return allowed;
|
|
137
|
+
case 'status':
|
|
138
|
+
allowed.add('--json');
|
|
139
|
+
allowed.add('--watch');
|
|
140
|
+
return allowed;
|
|
141
|
+
case 'repair':
|
|
142
|
+
case 'website':
|
|
143
|
+
case 'github':
|
|
144
|
+
case 'roadmap':
|
|
145
|
+
case 'docs':
|
|
146
|
+
allowed.add('--open');
|
|
147
|
+
return allowed;
|
|
148
|
+
case 'about':
|
|
149
|
+
default:
|
|
150
|
+
return allowed;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function getAllowedFlagPrefixesFor(command) {
|
|
154
|
+
if (!command)
|
|
155
|
+
return [];
|
|
156
|
+
if (command === 'schedule' || command === 'timetable') {
|
|
157
|
+
return ['--term=', '--output=', '--week-one='];
|
|
158
|
+
}
|
|
159
|
+
const action = ACTION_ALIASES[command];
|
|
160
|
+
if (action === 'events')
|
|
161
|
+
return ['--next=', '--search='];
|
|
162
|
+
if (action === 'status')
|
|
163
|
+
return ['--interval=', '--timeout=', '--retries='];
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
function validateFlags(command, flags) {
|
|
167
|
+
const unknownFlag = Array.from(flags).find((flag) => {
|
|
168
|
+
if (KNOWN_FLAGS.has(flag))
|
|
169
|
+
return false;
|
|
170
|
+
return !KNOWN_FLAG_PREFIXES.some((prefix) => flag.startsWith(prefix));
|
|
171
|
+
});
|
|
172
|
+
if (unknownFlag) {
|
|
173
|
+
const trans0 = t();
|
|
174
|
+
console.error(chalk.red(fmt(trans0.cli.unknownFlag, { flag: unknownFlag })));
|
|
175
|
+
console.error(chalk.dim(trans0.cli.unknownFlagHint));
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
const allowed = getAllowedFlagsFor(command);
|
|
179
|
+
const allowedPrefixes = getAllowedFlagPrefixesFor(command);
|
|
180
|
+
const disallowedFlag = Array.from(flags).find((flag) => {
|
|
181
|
+
if (allowed.has(flag))
|
|
182
|
+
return false;
|
|
183
|
+
return !allowedPrefixes.some((prefix) => flag.startsWith(prefix));
|
|
184
|
+
});
|
|
185
|
+
if (disallowedFlag) {
|
|
186
|
+
const trans1 = t();
|
|
187
|
+
console.error(chalk.red(fmt(trans1.cli.invalidFlag, { flag: disallowedFlag })));
|
|
188
|
+
console.error(chalk.dim(trans1.cli.invalidFlagHint));
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function rejectUnexpectedArguments(command, args) {
|
|
193
|
+
if (args.length === 0)
|
|
194
|
+
return;
|
|
195
|
+
const trans = t().cli;
|
|
196
|
+
console.error(chalk.red(fmt(trans.unexpectedArguments, { command })));
|
|
197
|
+
console.error(chalk.dim(trans.invalidFlagHint));
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
function printHelp() {
|
|
201
|
+
const trans = t();
|
|
202
|
+
const c = trans.cli;
|
|
203
|
+
console.log(chalk.bold('NBTCA Prompt'));
|
|
204
|
+
console.log();
|
|
205
|
+
console.log(c.usage);
|
|
206
|
+
console.log(` nbtca ${c.interactive}`);
|
|
207
|
+
console.log(` nbtca <command> [flags] ${c.runCommand}`);
|
|
208
|
+
console.log();
|
|
209
|
+
console.log(c.commands);
|
|
210
|
+
console.log(` events ${trans.menu.eventsDesc}`);
|
|
211
|
+
console.log(` docs ${trans.menu.docsDesc}`);
|
|
212
|
+
console.log(` status ${trans.menu.statusDesc}`);
|
|
213
|
+
console.log(' schedule <login|logout|status|terms|export>');
|
|
214
|
+
console.log(` ${c.cmdSchedule}`);
|
|
215
|
+
console.log(` website ${c.cmdWebsite}`);
|
|
216
|
+
console.log(` github ${c.cmdGithub}`);
|
|
217
|
+
console.log(` roadmap ${c.cmdRoadmap}`);
|
|
218
|
+
console.log(` repair ${c.cmdRepair}`);
|
|
219
|
+
console.log(` theme ${c.cmdTheme}`);
|
|
220
|
+
console.log(` lang <zh|en> ${c.cmdLang}`);
|
|
221
|
+
console.log(` update ${c.cmdUpdate}`);
|
|
222
|
+
console.log();
|
|
223
|
+
console.log(c.flags);
|
|
224
|
+
console.log(` --version ${c.flagVersion}`);
|
|
225
|
+
console.log(` --help ${c.flagHelp}`);
|
|
226
|
+
console.log(` --open ${c.flagOpen}`);
|
|
227
|
+
console.log(` --json ${c.flagJson}`);
|
|
228
|
+
console.log(` --heatmap ${c.flagHeatmap}`);
|
|
229
|
+
console.log(` --today ${c.flagToday}`);
|
|
230
|
+
console.log(` --week ${c.flagWeek}`);
|
|
231
|
+
console.log(` --month ${c.flagMonth}`);
|
|
232
|
+
console.log(` --search=<q> ${c.flagSearch}`);
|
|
233
|
+
console.log(` --next=<n> ${c.flagNext}`);
|
|
234
|
+
console.log(` --watch ${c.flagWatch}`);
|
|
235
|
+
console.log(` --interval=<s> ${c.flagInterval}`);
|
|
236
|
+
console.log(` --timeout=<ms> ${c.flagTimeout}`);
|
|
237
|
+
console.log(` --retries=<n> ${c.flagRetries}`);
|
|
238
|
+
console.log(` --plain ${c.flagPlain}`);
|
|
239
|
+
console.log(` --no-logo ${c.flagNoLogo}`);
|
|
240
|
+
console.log(` --one-shot ${c.flagOneShot}`);
|
|
241
|
+
console.log(` --no-save ${c.flagNoSave}`);
|
|
242
|
+
console.log(` --term=<year:code> ${c.flagTerm}`);
|
|
243
|
+
console.log(` --output=<path> ${c.flagOutput}`);
|
|
244
|
+
console.log(` --week-one=<date> ${c.flagWeekOne}`);
|
|
245
|
+
}
|
|
246
|
+
async function runEventsCommand(flags) {
|
|
247
|
+
const searchFlag = Array.from(flags).find((flag) => flag.startsWith('--search='));
|
|
248
|
+
const nextFlag = Array.from(flags).find((flag) => flag.startsWith('--next='));
|
|
249
|
+
const rangeFlags = ['--today', '--week', '--month'].filter((flag) => flags.has(flag));
|
|
250
|
+
if (flags.has('--heatmap') &&
|
|
251
|
+
(rangeFlags.length > 0 || nextFlag !== undefined || searchFlag !== undefined)) {
|
|
252
|
+
console.error(chalk.red(t().cli.eventsHeatmapConflict));
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
if (rangeFlags.length > 1) {
|
|
256
|
+
console.error(chalk.red(t().cli.eventsRangeConflict));
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
const next = nextFlag ? parseAsciiDecimalInteger(nextFlag.slice('--next='.length)) : undefined;
|
|
260
|
+
if (nextFlag && (next === undefined || next < 1)) {
|
|
261
|
+
console.error(chalk.red(t().cli.invalidNext));
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
if (flags.has('--heatmap')) {
|
|
265
|
+
const buckets = await fetchHeatmapBuckets();
|
|
266
|
+
if (flags.has('--json')) {
|
|
267
|
+
process.stdout.write(JSON.stringify(buckets, null, 2) + '\n');
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
const useColor = !flags.has('--plain') && isTty(process.stdout.isTTY);
|
|
271
|
+
console.log(renderHeatmap(buckets, new Date(), { color: useColor }));
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const { weekRange, monthRange } = await import('./features/calendar-query.js');
|
|
276
|
+
const { fetchInRange } = await import('./features/calendar.js');
|
|
277
|
+
const now0 = new Date();
|
|
278
|
+
let events;
|
|
279
|
+
if (flags.has('--week')) {
|
|
280
|
+
const r = weekRange(now0);
|
|
281
|
+
events = await fetchInRange(r.start, r.end);
|
|
282
|
+
}
|
|
283
|
+
else if (flags.has('--month')) {
|
|
284
|
+
const r = monthRange(now0);
|
|
285
|
+
events = await fetchInRange(r.start, r.end);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
events = await fetchEvents();
|
|
289
|
+
}
|
|
290
|
+
if (searchFlag) {
|
|
291
|
+
const q = searchFlag.slice('--search='.length).toLowerCase();
|
|
292
|
+
events = events.filter((e) => `${e.title} ${e.location}`.toLowerCase().includes(q));
|
|
293
|
+
}
|
|
294
|
+
if (flags.has('--today')) {
|
|
295
|
+
const now = new Date();
|
|
296
|
+
events = events.filter((e) => {
|
|
297
|
+
const d = e.startDate;
|
|
298
|
+
return (d.getFullYear() === now.getFullYear() &&
|
|
299
|
+
d.getMonth() === now.getMonth() &&
|
|
300
|
+
d.getDate() === now.getDate());
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
if (next !== undefined)
|
|
304
|
+
events = events.slice(0, next);
|
|
305
|
+
if (flags.has('--json')) {
|
|
306
|
+
process.stdout.write(JSON.stringify(serializeEvents(events), null, 2) + '\n');
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const useColor = !flags.has('--plain') && isTty(process.stdout.isTTY);
|
|
310
|
+
const width = terminalWidth();
|
|
311
|
+
console.log(renderEventsTable(events, {
|
|
312
|
+
color: useColor,
|
|
313
|
+
...(width === undefined ? {} : { width }),
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
async function runStatusCommand(flags) {
|
|
317
|
+
const trans = t();
|
|
318
|
+
const watch = flags.has('--watch');
|
|
319
|
+
const intervalFlag = Array.from(flags).find((flag) => flag.startsWith('--interval='));
|
|
320
|
+
const timeoutFlag = Array.from(flags).find((flag) => flag.startsWith('--timeout='));
|
|
321
|
+
const retriesFlag = Array.from(flags).find((flag) => flag.startsWith('--retries='));
|
|
322
|
+
const intervalSeconds = intervalFlag
|
|
323
|
+
? parseAsciiDecimalInteger(intervalFlag.slice('--interval='.length))
|
|
324
|
+
: 10;
|
|
325
|
+
const timeoutMs = timeoutFlag
|
|
326
|
+
? parseAsciiDecimalInteger(timeoutFlag.slice('--timeout='.length))
|
|
327
|
+
: 6000;
|
|
328
|
+
const retries = retriesFlag
|
|
329
|
+
? parseAsciiDecimalInteger(retriesFlag.slice('--retries='.length))
|
|
330
|
+
: 1;
|
|
331
|
+
if (!watch && intervalFlag) {
|
|
332
|
+
console.error(chalk.red(trans.status.intervalNeedsWatch));
|
|
333
|
+
process.exit(1);
|
|
334
|
+
}
|
|
335
|
+
if (timeoutMs === undefined || timeoutMs < STATUS_TIMEOUT_MIN || timeoutMs > STATUS_TIMEOUT_MAX) {
|
|
336
|
+
console.error(chalk.red(fmt(trans.status.invalidTimeout, { min: STATUS_TIMEOUT_MIN, max: STATUS_TIMEOUT_MAX })));
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
if (retries === undefined || retries < STATUS_RETRIES_MIN || retries > STATUS_RETRIES_MAX) {
|
|
340
|
+
console.error(chalk.red(fmt(trans.status.invalidRetries, { min: STATUS_RETRIES_MIN, max: STATUS_RETRIES_MAX })));
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
if (watch && flags.has('--json')) {
|
|
344
|
+
console.error(chalk.red(trans.status.watchJsonConflict));
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
if (watch) {
|
|
348
|
+
if (intervalSeconds === undefined ||
|
|
349
|
+
intervalSeconds < STATUS_WATCH_INTERVAL_MIN ||
|
|
350
|
+
intervalSeconds > STATUS_WATCH_INTERVAL_MAX) {
|
|
351
|
+
console.error(chalk.red(fmt(trans.status.invalidInterval, {
|
|
352
|
+
min: STATUS_WATCH_INTERVAL_MIN,
|
|
353
|
+
max: STATUS_WATCH_INTERVAL_MAX,
|
|
354
|
+
})));
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
if (!hasInteractiveTerminal()) {
|
|
358
|
+
console.error(chalk.red(trans.status.watchRequiresTty));
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
const stopController = new AbortController();
|
|
362
|
+
const isStopped = () => stopController.signal.aborted;
|
|
363
|
+
const onSigint = () => {
|
|
364
|
+
process.exitCode = 130;
|
|
365
|
+
stopController.abort();
|
|
366
|
+
};
|
|
367
|
+
process.once('SIGINT', onSigint);
|
|
368
|
+
console.log(chalk.dim(`${fmt(trans.status.watchStarted, { seconds: intervalSeconds })} | ${trans.status.watchHint}`));
|
|
369
|
+
try {
|
|
370
|
+
while (!isStopped()) {
|
|
371
|
+
let services;
|
|
372
|
+
try {
|
|
373
|
+
services = await checkServices({ timeoutMs, retries, signal: stopController.signal });
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
if (isStopped())
|
|
377
|
+
break;
|
|
378
|
+
throw error;
|
|
379
|
+
}
|
|
380
|
+
if (isStopped())
|
|
381
|
+
break;
|
|
382
|
+
const hasFailures = hasServiceFailures(services);
|
|
383
|
+
const hasIntranetFailures = services.some((service) => service.intranet && !service.ok);
|
|
384
|
+
const health = countServiceHealth(services);
|
|
385
|
+
clearScreen();
|
|
386
|
+
console.log(chalk.bold(`${trans.status.watchUpdated}: ${new Date().toLocaleString()}`));
|
|
387
|
+
console.log(chalk.dim(`${trans.status.up}: ${health.up} | ${trans.status.down}: ${health.down} | ${trans.status.watchHint}`));
|
|
388
|
+
console.log();
|
|
389
|
+
const useColor = !flags.has('--plain') && isTty(process.stdout.isTTY);
|
|
390
|
+
console.log(renderServiceStatusTable(services, { color: useColor }));
|
|
391
|
+
if (hasFailures) {
|
|
392
|
+
console.log(chalk.yellow(trans.status.summaryFail));
|
|
393
|
+
}
|
|
394
|
+
else if (hasIntranetFailures) {
|
|
395
|
+
console.log(chalk.yellow(trans.status.summaryIntranet));
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
console.log(chalk.green(trans.status.summaryOk));
|
|
399
|
+
}
|
|
400
|
+
await new Promise((resolve) => {
|
|
401
|
+
const stopWait = () => {
|
|
402
|
+
clearTimeout(timer);
|
|
403
|
+
stopController.signal.removeEventListener('abort', stopWait);
|
|
404
|
+
resolve();
|
|
405
|
+
};
|
|
406
|
+
const timer = setTimeout(stopWait, intervalSeconds * 1000);
|
|
407
|
+
stopController.signal.addEventListener('abort', stopWait, { once: true });
|
|
408
|
+
if (stopController.signal.aborted)
|
|
409
|
+
stopWait();
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
finally {
|
|
414
|
+
process.removeListener('SIGINT', onSigint);
|
|
415
|
+
}
|
|
416
|
+
console.log();
|
|
417
|
+
console.log(chalk.dim(t().common.goodbye));
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
const services = await checkServices({ timeoutMs, retries });
|
|
421
|
+
const hasFailures = hasServiceFailures(services);
|
|
422
|
+
const hasIntranetFailures = services.some((service) => service.intranet && !service.ok);
|
|
423
|
+
if (flags.has('--json')) {
|
|
424
|
+
process.stdout.write(JSON.stringify(serializeServiceStatus(services), null, 2) + '\n');
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
const useColor = !flags.has('--plain') && isTty(process.stdout.isTTY);
|
|
428
|
+
console.log(renderServiceStatusTable(services, { color: useColor }));
|
|
429
|
+
if (hasFailures) {
|
|
430
|
+
console.error(chalk.yellow(t().status.summaryFail));
|
|
431
|
+
}
|
|
432
|
+
else if (hasIntranetFailures) {
|
|
433
|
+
console.log(chalk.yellow(t().status.summaryIntranet));
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
console.log(chalk.green(t().status.summaryOk));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return !hasFailures;
|
|
440
|
+
}
|
|
441
|
+
function maybeDisableColor(flags) {
|
|
442
|
+
applyColorModePreference(flags.has('--plain'));
|
|
443
|
+
}
|
|
444
|
+
async function runCommandMode(argv) {
|
|
445
|
+
const { command, args, flags } = parseArgs(argv);
|
|
446
|
+
maybeDisableColor(flags);
|
|
447
|
+
if (flags.has('--version') ||
|
|
448
|
+
command === '--version' ||
|
|
449
|
+
command === '-v' ||
|
|
450
|
+
command === 'version') {
|
|
451
|
+
if (command === '-v' || command === 'version')
|
|
452
|
+
rejectUnexpectedArguments(command, args);
|
|
453
|
+
console.log(APP_INFO.version);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (flags.has('--help') || command === '--help' || command === '-h' || command === 'help') {
|
|
457
|
+
if (command === '-h' || command === 'help')
|
|
458
|
+
rejectUnexpectedArguments(command, args);
|
|
459
|
+
printHelp();
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
validateFlags(command, flags);
|
|
463
|
+
if (!command) {
|
|
464
|
+
if (!hasInteractiveTerminal()) {
|
|
465
|
+
const cliTrans = t().cli;
|
|
466
|
+
console.error(chalk.red(cliTrans.requiresTty));
|
|
467
|
+
console.error(chalk.dim(cliTrans.requiresTtyHint));
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
await main({ skipLogo: flags.has('--no-logo') });
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (command === 'lang' || command === 'language') {
|
|
474
|
+
rejectUnexpectedArguments(command, args.slice(1));
|
|
475
|
+
const language = (args[0] ?? '').toLowerCase();
|
|
476
|
+
if (language !== 'zh' && language !== 'en') {
|
|
477
|
+
console.error(chalk.red(t().cli.invalidLang));
|
|
478
|
+
process.exit(1);
|
|
479
|
+
}
|
|
480
|
+
const persisted = saveLanguagePreference(language);
|
|
481
|
+
if (persisted) {
|
|
482
|
+
console.log(chalk.green(`${pickIcon('✓', 'OK')}: ${t().language.changed}`));
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
console.log(chalk.yellow(`${pickIcon('⚠', 'WARN')}: ${t().language.changedSessionOnly}`));
|
|
486
|
+
}
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (command === 'theme') {
|
|
490
|
+
const themeScope = args[0];
|
|
491
|
+
if (args.length > 2 || (themeScope === 'reset' && args.length > 1)) {
|
|
492
|
+
rejectUnexpectedArguments(command, args.slice(themeScope === 'reset' ? 1 : 2));
|
|
493
|
+
}
|
|
494
|
+
const result = runThemeCommand(args, { forcePlain: flags.has('--plain') });
|
|
495
|
+
if (!result.ok) {
|
|
496
|
+
console.error(chalk.red(result.message));
|
|
497
|
+
process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
if (result.message) {
|
|
500
|
+
console.log(chalk.green(`${pickIcon('✓', 'OK')}: ${result.message}`));
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (command === 'update') {
|
|
505
|
+
rejectUnexpectedArguments(command, args);
|
|
506
|
+
if (!(await runUpdateCheck()))
|
|
507
|
+
process.exitCode = 1;
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (command === 'schedule' || command === 'timetable') {
|
|
511
|
+
if (args.length > 1) {
|
|
512
|
+
console.error(chalk.red(t().timetable.invalidArguments));
|
|
513
|
+
process.exitCode = 1;
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const exitCode = await runStudentTimetableCommand(args[0], { flags });
|
|
517
|
+
if (exitCode !== 0)
|
|
518
|
+
process.exitCode = exitCode;
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const action = ACTION_ALIASES[command];
|
|
522
|
+
if (!action) {
|
|
523
|
+
const cliT = t().cli;
|
|
524
|
+
console.error(chalk.red(fmt(cliT.unknownCommand, { command })));
|
|
525
|
+
console.error(chalk.dim(cliT.unknownCommandHint));
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
rejectUnexpectedArguments(command, args);
|
|
529
|
+
if (action === 'events') {
|
|
530
|
+
await runEventsCommand(flags);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
if (action === 'status') {
|
|
534
|
+
const ok = await runStatusCommand(flags);
|
|
535
|
+
if (!ok)
|
|
536
|
+
process.exitCode = 1;
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (action === 'docs') {
|
|
540
|
+
if (flags.has('--open')) {
|
|
541
|
+
const opened = await openDocsInBrowser();
|
|
542
|
+
if (!opened)
|
|
543
|
+
process.exitCode = 1;
|
|
544
|
+
}
|
|
545
|
+
else if (!hasInteractiveTerminal()) {
|
|
546
|
+
process.stdout.write(URLS.docs + '\n');
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
const { showDocsMenu } = await import('./features/docs.js');
|
|
550
|
+
await showDocsMenu();
|
|
551
|
+
}
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (action === 'about') {
|
|
555
|
+
showAbout();
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const mappedUrl = URL_ACTIONS[action];
|
|
559
|
+
if (mappedUrl) {
|
|
560
|
+
if (flags.has('--open')) {
|
|
561
|
+
if (!(await openUrlInBrowser(mappedUrl)))
|
|
562
|
+
process.exitCode = 1;
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
process.stdout.write(mappedUrl + '\n');
|
|
566
|
+
}
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
await runCommandMode(process.argv.slice(2)).catch(handleGracefulExit);
|
|
@@ -8,6 +8,7 @@ const DEFAULT_PREFERENCES = {
|
|
|
8
8
|
};
|
|
9
9
|
const detectedColorLevel = chalk.level;
|
|
10
10
|
const inheritedNoColor = process.env['NO_COLOR'];
|
|
11
|
+
const inheritedForceColor = process.env['FORCE_COLOR'];
|
|
11
12
|
function getPreferencesPath() {
|
|
12
13
|
return path.join(getConfigDir(), 'preferences.json');
|
|
13
14
|
}
|
|
@@ -69,12 +70,14 @@ export function resolveColorMode() {
|
|
|
69
70
|
export function applyColorModePreference(forcePlain) {
|
|
70
71
|
const mode = forcePlain ? 'off' : resolveColorMode();
|
|
71
72
|
if (mode === 'off') {
|
|
73
|
+
delete process.env['FORCE_COLOR'];
|
|
72
74
|
process.env['NO_COLOR'] = '1';
|
|
73
75
|
chalk.level = 0;
|
|
74
76
|
return;
|
|
75
77
|
}
|
|
76
78
|
if (mode === 'on') {
|
|
77
79
|
delete process.env['NO_COLOR'];
|
|
80
|
+
delete process.env['FORCE_COLOR'];
|
|
78
81
|
chalk.level = 3;
|
|
79
82
|
return;
|
|
80
83
|
}
|
|
@@ -82,5 +85,9 @@ export function applyColorModePreference(forcePlain) {
|
|
|
82
85
|
delete process.env['NO_COLOR'];
|
|
83
86
|
else
|
|
84
87
|
process.env['NO_COLOR'] = inheritedNoColor;
|
|
88
|
+
if (inheritedForceColor === undefined)
|
|
89
|
+
delete process.env['FORCE_COLOR'];
|
|
90
|
+
else
|
|
91
|
+
process.env['FORCE_COLOR'] = inheritedForceColor;
|
|
85
92
|
chalk.level = detectedColorLevel;
|
|
86
93
|
}
|