@nbtca/prompt 1.4.2 → 1.5.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.
Files changed (67) hide show
  1. package/README.md +27 -58
  2. package/SECURITY.md +16 -45
  3. package/dist/app/app.js +53 -55
  4. package/dist/app/chrome.js +67 -50
  5. package/dist/app/fields/list-field.js +12 -25
  6. package/dist/app/fields/text-field.js +3 -8
  7. package/dist/app/frame.js +2 -21
  8. package/dist/app/keys.js +10 -2
  9. package/dist/app/views/docs-render.js +31 -24
  10. package/dist/app/views/docs.js +211 -60
  11. package/dist/app/views/events-render.js +19 -26
  12. package/dist/app/views/events.js +44 -31
  13. package/dist/app/views/home.js +28 -30
  14. package/dist/app/views/schedule-grid-cursor.js +9 -18
  15. package/dist/app/views/schedule-render.js +47 -71
  16. package/dist/app/views/schedule.js +158 -81
  17. package/dist/app/views/settings-render.js +8 -19
  18. package/dist/app/views/settings.js +92 -17
  19. package/dist/auth/cookie-transport.js +31 -32
  20. package/dist/auth/errors.js +3 -1
  21. package/dist/auth/nbt-auth.js +42 -25
  22. package/dist/auth/session-store.js +17 -9
  23. package/dist/config/data.js +9 -11
  24. package/dist/config/preferences.js +14 -7
  25. package/dist/core/calendar-day.js +37 -0
  26. package/dist/core/capabilities.js +6 -3
  27. package/dist/core/components/confirm.js +9 -8
  28. package/dist/core/components/menu.js +41 -16
  29. package/dist/core/components/messages.js +12 -4
  30. package/dist/core/components/painter.js +3 -1
  31. package/dist/core/components/spinner.js +17 -6
  32. package/dist/core/components/text-input.js +24 -18
  33. package/dist/core/icons.js +2 -2
  34. package/dist/core/logo.js +23 -5
  35. package/dist/core/motion.js +25 -19
  36. package/dist/core/text.js +182 -69
  37. package/dist/core/theme.js +0 -28
  38. package/dist/core/transitions.js +2 -2
  39. package/dist/core/ui.js +15 -13
  40. package/dist/core/vim-keys.js +9 -15
  41. package/dist/features/about.js +23 -0
  42. package/dist/features/calendar-heatmap.js +16 -40
  43. package/dist/features/calendar-query.js +1 -2
  44. package/dist/features/calendar.js +12 -185
  45. package/dist/features/docs.js +436 -275
  46. package/dist/features/schedule-render.js +65 -101
  47. package/dist/features/schedule-store.js +51 -9
  48. package/dist/features/schedule-view.js +46 -213
  49. package/dist/features/status.js +44 -56
  50. package/dist/features/student-timetable.js +73 -95
  51. package/dist/features/theme.js +6 -2
  52. package/dist/features/timetable-sanitize.js +40 -0
  53. package/dist/features/update.js +9 -27
  54. package/dist/i18n/index.js +83 -19
  55. package/dist/i18n/locales/en.json +1 -1
  56. package/dist/i18n/locales/zh.json +1 -1
  57. package/dist/index.js +83 -58
  58. package/dist/logo/ca-dotmatrix.txt +16 -18
  59. package/dist/main.js +7 -48
  60. package/package.json +27 -18
  61. package/bin/nbtca-welcome.js +0 -2
  62. package/dist/core/components/screen.js +0 -18
  63. package/dist/core/menu.js +0 -68
  64. package/dist/features/links.js +0 -36
  65. package/dist/features/schedule-query.js +0 -47
  66. package/dist/features/settings.js +0 -127
  67. package/dist/logo/ca-logo.png +0 -0
@@ -1,212 +1,57 @@
1
- import path from 'node:path';
2
- import { createNbtTimetableClient, timetableToIcs, } from '@nbtca/nbtcal/timetable';
3
- import { runMenu, menuFooter } from '../core/components/menu.js';
4
- import { runTextInput } from '../core/components/text-input.js';
5
- import { enterScreen, breadcrumb } from '../core/transitions.js';
6
- import { createSpinner, success, error } from '../core/ui.js';
7
- import { c, type, space } from '../core/theme.js';
8
- import { t, fmt } from '../i18n/index.js';
9
- import { createSessionStore } from '../auth/session-store.js';
10
- import { withAuthenticatedSession, resolveTerm, relevantTerms, writePrivateIcs, isSessionExpired, safeMessage, JWXT_ORIGIN, } from './student-timetable.js';
11
- import { currentWeekNumber, campusWeekday, meetingsOnDay, nextMeeting } from './schedule-query.js';
12
- import { renderNextClassBanner, renderTodayClasses, renderTodayTimeline, renderWeekGrid } from './schedule-render.js';
13
- import { termKey, loadWeekOne, saveWeekOne, saveTimetableCache, saveCurrentPointer, loadCurrentPointer, loadTimetableCache, clearScheduleCache, } from './schedule-store.js';
14
- /** Loads a saved week-one Monday for `key`, or prompts for and persists a new one.
15
- * Returns null when the user cancels or enters an unparsable date (caller aborts). */
16
- async function ensureWeekOne(key) {
17
- const saved = loadWeekOne(key);
18
- if (saved)
19
- return saved;
20
- const value = await runTextInput({
21
- message: t().timetable.promptWeekOne,
22
- placeholder: 'YYYY-MM-DD',
23
- });
24
- if (value === null)
1
+ import { createTimetableSchedule } from '@nbtca/nbtcal/timetable';
2
+ import { addLocalDays, parseLocalMonday } from '../core/calendar-day.js';
3
+ import { renderNextClassBanner, renderTodayTimeline } from './schedule-render.js';
4
+ import { loadCurrentPointer, loadTimetableCache } from './schedule-store.js';
5
+ import { sanitizeTimetable } from './timetable-sanitize.js';
6
+ const WEEKDAYS = [1, 2, 3, 4, 5, 6, 7];
7
+ function loadCachedTimetable() {
8
+ const pointer = loadCurrentPointer();
9
+ if (!pointer)
25
10
  return null;
26
- const trimmed = value.trim();
27
- if (!/^\d{4}-\d{2}-\d{2}$/.test(trimmed))
11
+ const value = loadTimetableCache(pointer.termKey);
12
+ if (!value || typeof value !== 'object')
28
13
  return null;
29
- if (Number.isNaN(new Date(`${trimmed}T00:00:00`).getTime()))
30
- return null;
31
- saveWeekOne(key, trimmed);
32
- return trimmed;
33
- }
34
- /** Fetches a term's timetable behind a spinner, caching it on success. Returns null
35
- * (after reporting the error) on failure so callers can keep the previous state. */
36
- async function fetchTimetableWithSpinner(client, term, key, weekOne) {
37
- const trans = t();
38
- const spinner = createSpinner(trans.calendar.loading);
39
- try {
40
- const tt = await client.fetchTerm(term);
41
- spinner.stop();
42
- saveTimetableCache(key, tt);
43
- saveCurrentPointer(key, weekOne);
44
- return tt;
45
- }
46
- catch (err) {
47
- if (isSessionExpired(err)) {
48
- spinner.stop();
49
- throw err;
50
- }
51
- spinner.error(trans.timetable.genericError);
14
+ const candidate = value;
15
+ if (!Array.isArray(candidate.meetings) ||
16
+ !Array.isArray(candidate.periods) ||
17
+ !Array.isArray(candidate.unresolvedItems) ||
18
+ !Array.isArray(candidate.calendarDays) ||
19
+ !Array.isArray(candidate.warnings)) {
52
20
  return null;
53
21
  }
54
- }
55
- function renderHub(tt, weekOne, now) {
56
- const week = currentWeekNumber(weekOne, now);
57
- const today = meetingsOnDay(tt.meetings, campusWeekday(now), week);
58
- const trans = t();
59
- console.log();
60
- const banner = renderNextClassBanner(nextMeeting(tt.meetings, tt.periods, weekOne, now), now);
61
- console.log(banner || `${space.indent}${type.hint(trans.timetable.noNextClass)}`);
62
- console.log();
63
- console.log(renderTodayClasses(today, tt.periods, now));
64
- console.log();
65
- return { week, today };
66
- }
67
- async function switchTerm(client, catalog) {
68
- const trans = t();
69
- const picked = await runMenu({
70
- title: trans.timetable.hubSwitchTerm,
71
- options: relevantTerms(catalog).map((tm) => ({
72
- value: `${tm.academicYear}:${tm.semester}`,
73
- label: tm.academicYearLabel,
74
- hint: tm.current ? trans.common.current : undefined,
75
- })),
76
- footer: menuFooter(),
77
- });
78
- if (picked === null)
22
+ const fetchedAt = new Date(candidate.fetchedAt);
23
+ if (Number.isNaN(fetchedAt.getTime()))
79
24
  return null;
80
- const term = resolveTerm(catalog, picked);
81
- const key = termKey(term);
82
- const weekOne = await ensureWeekOne(key);
83
- if (!weekOne)
84
- return null;
85
- const tt = await fetchTimetableWithSpinner(client, term, key, weekOne);
86
- if (!tt)
87
- return null;
88
- return { term, key, weekOne, tt };
89
- }
90
- function exportTimetable(tt, term, key, weekOne) {
91
- const trans = t();
92
- const ics = timetableToIcs(tt, { weekOneMonday: weekOne, calendarName: `NBT ${term.academicYearLabel}` });
93
- const out = `timetable-${key}.ics`;
94
- try {
95
- writePrivateIcs(out, ics);
96
- success(fmt(trans.timetable.exported, { count: tt.meetings.length, file: path.resolve(out) }));
97
- }
98
- catch {
99
- error(trans.timetable.genericError);
100
- }
101
- }
102
- /** Interactive schedule hub: login/restore, resolve the current term, load or prompt
103
- * for the week-one Monday, fetch the timetable, then loop a menu of today / week-grid /
104
- * switch-term / export actions until the user cancels. */
105
- export async function showSchedule() {
106
- const trans = t();
107
- await enterScreen(breadcrumb(trans.timetable.menuEntry));
108
- try {
109
- await withAuthenticatedSession(async (session) => {
110
- const client = createNbtTimetableClient(session.timetableTransport, { baseUrl: JWXT_ORIGIN });
111
- const catalog = await client.listTerms();
112
- let term = resolveTerm(catalog);
113
- let key = termKey(term);
114
- let weekOne = await ensureWeekOne(key);
115
- if (!weekOne)
116
- return 0;
117
- const initial = await fetchTimetableWithSpinner(client, term, key, weekOne);
118
- if (!initial)
119
- return 1;
120
- let tt = initial;
121
- while (true) {
122
- const now = new Date();
123
- const { week, today } = renderHub(tt, weekOne, now);
124
- const action = await runMenu({
125
- title: `${trans.timetable.menuEntry} ${c.muted(term.academicYearLabel)} ${c.muted(trans.timetable.weekLabel + String(week))}`,
126
- options: [
127
- { value: 'today', label: trans.timetable.hubToday, hint: String(today.length) },
128
- { value: 'week', label: trans.timetable.hubWeek },
129
- { value: 'term', label: trans.timetable.hubSwitchTerm, hint: term.academicYearLabel },
130
- { value: 'export', label: trans.timetable.hubExport },
131
- { value: 'logout', label: trans.timetable.hubLogout },
132
- ],
133
- footer: menuFooter(),
134
- });
135
- if (action === null)
136
- return 0;
137
- if (action === 'today') {
138
- continue; // The next loop iteration repaints today's classes.
139
- }
140
- if (action === 'week') {
141
- console.log();
142
- console.log(renderWeekGrid(tt.meetings, tt.periods, week, now));
143
- console.log();
144
- continue;
145
- }
146
- if (action === 'term') {
147
- const switched = await switchTerm(client, catalog);
148
- if (!switched)
149
- continue;
150
- term = switched.term;
151
- key = switched.key;
152
- weekOne = switched.weekOne;
153
- tt = switched.tt;
154
- continue;
155
- }
156
- if (action === 'export') {
157
- exportTimetable(tt, term, key, weekOne);
158
- continue;
159
- }
160
- if (action === 'logout') {
161
- createSessionStore().clear();
162
- clearScheduleCache();
163
- return 0;
164
- }
165
- }
166
- }, {
167
- oneShot: false,
168
- isInteractive: true,
169
- store: createSessionStore(),
170
- stderr: process.stderr,
171
- });
172
- }
173
- catch (err) {
174
- error(safeMessage(err));
175
- }
25
+ return {
26
+ timetable: sanitizeTimetable({ ...candidate, fetchedAt }),
27
+ weekOneMonday: pointer.weekOneMonday,
28
+ };
176
29
  }
177
- /** Best-effort, cache-only startup line: reads the last-used term pointer and its
178
- * cached timetable (no network) and renders the next-class banner, or '' if the
179
- * student isn't "set up" yet or anything about the cache is missing/corrupt. */
180
30
  export function peekNextClassLine(now = new Date()) {
181
31
  try {
182
- const ptr = loadCurrentPointer();
183
- if (!ptr)
32
+ const cached = loadCachedTimetable();
33
+ if (!cached)
184
34
  return '';
185
- const cached = loadTimetableCache(ptr.termKey);
186
- if (!cached || !Array.isArray(cached.meetings) || !Array.isArray(cached.periods))
187
- return '';
188
- const next = nextMeeting(cached.meetings, cached.periods, ptr.weekOneMonday, now);
189
- return renderNextClassBanner(next, now);
35
+ const schedule = createTimetableSchedule(cached.timetable, {
36
+ weekOneMonday: cached.weekOneMonday,
37
+ });
38
+ return renderNextClassBanner(schedule.next(now), now);
190
39
  }
191
40
  catch {
192
41
  return '';
193
42
  }
194
43
  }
195
- /** Cache-only (no network) render of today's classes for the current term, or
196
- * [] if not set up. Uses the same renderTodayTimeline Schedule's own hub
197
- * renders, so Home's preview and Schedule's detail view show the exact same
198
- * visual language for the exact same data instead of drifting apart. */
199
44
  export function peekTodayLines(now = new Date()) {
200
45
  try {
201
- const ptr = loadCurrentPointer();
202
- if (!ptr)
203
- return [];
204
- const cached = loadTimetableCache(ptr.termKey);
205
- if (!cached || !Array.isArray(cached.meetings) || !Array.isArray(cached.periods))
46
+ const cached = loadCachedTimetable();
47
+ if (!cached)
206
48
  return [];
207
- const week = currentWeekNumber(ptr.weekOneMonday, now);
208
- const today = meetingsOnDay(cached.meetings, campusWeekday(now), week);
209
- return renderTodayTimeline(today, cached.periods, now).split('\n');
49
+ const schedule = createTimetableSchedule(cached.timetable, {
50
+ weekOneMonday: cached.weekOneMonday,
51
+ });
52
+ const week = schedule.weekAt(now);
53
+ const today = schedule.meetingsOnDay(week, schedule.weekdayAt(now));
54
+ return renderTodayTimeline(today, cached.timetable.periods, now).split('\n');
210
55
  }
211
56
  catch {
212
57
  return [];
@@ -214,38 +59,26 @@ export function peekTodayLines(now = new Date()) {
214
59
  }
215
60
  export function peekWeekAheadInfo(now = new Date()) {
216
61
  try {
217
- const ptr = loadCurrentPointer();
218
- if (!ptr)
219
- return null;
220
- const cached = loadTimetableCache(ptr.termKey);
221
- if (!cached || !Array.isArray(cached.meetings))
62
+ const cached = loadCachedTimetable();
63
+ if (!cached)
222
64
  return null;
223
- const week = currentWeekNumber(ptr.weekOneMonday, now);
65
+ const schedule = createTimetableSchedule(cached.timetable, {
66
+ weekOneMonday: cached.weekOneMonday,
67
+ });
68
+ const week = schedule.weekAt(now);
224
69
  if (week < 1)
225
70
  return null;
226
- const meetings = cached.meetings;
227
- const classDays = [1, 2, 3, 4, 5, 6, 7].map((wd) => meetingsOnDay(meetings, wd, week).length > 0);
228
- const base = new Date(`${ptr.weekOneMonday}T00:00:00`);
229
- const weekStartDate = new Date(base.getTime() + (week - 1) * 7 * 86400000);
71
+ const classDays = WEEKDAYS.map((weekday) => schedule.meetingsOnDay(week, weekday).length > 0);
72
+ const weekStartDate = addLocalDays(parseLocalMonday(cached.weekOneMonday), (week - 1) * 7);
230
73
  return { weekStartDate, classDays };
231
74
  }
232
75
  catch {
233
76
  return null;
234
77
  }
235
78
  }
236
- /** Best-effort, cache-only (no network) read of how many timetable items
237
- * still need the student's attention — the same data buildHubField()
238
- * (schedule.ts) already surfaces inside Schedule's own hub menu, exposed
239
- * here so Home can show it too without a second source of truth. */
240
79
  export function peekUnresolvedCount() {
241
80
  try {
242
- const ptr = loadCurrentPointer();
243
- if (!ptr)
244
- return 0;
245
- const cached = loadTimetableCache(ptr.termKey);
246
- if (!cached || !Array.isArray(cached.unresolvedItems))
247
- return 0;
248
- return cached.unresolvedItems.length;
81
+ return loadCachedTimetable()?.timetable.unresolvedItems.length ?? 0;
249
82
  }
250
83
  catch {
251
84
  return 0;
@@ -1,12 +1,8 @@
1
1
  import chalk from 'chalk';
2
2
  import { APP_INFO, URLS } from '../config/data.js';
3
- import { getCapabilities } from '../core/capabilities.js';
4
- import { createPainter } from '../core/components/painter.js';
5
3
  import { pickIcon } from '../core/icons.js';
6
- import { padEndV, visualWidth } from '../core/text.js';
4
+ import { padEndV, sanitizeTerminalLine, visualWidth } from '../core/text.js';
7
5
  import { c } from '../core/theme.js';
8
- import { createSpinner } from '../core/ui.js';
9
- import { enterScreen, breadcrumb } from '../core/transitions.js';
10
6
  import { t } from '../i18n/index.js';
11
7
  function getServiceTargets() {
12
8
  const trans = t();
@@ -23,28 +19,42 @@ function getServiceTargets() {
23
19
  }
24
20
  async function checkService(name, url, timeoutMs) {
25
21
  const start = Date.now();
22
+ const controller = new AbortController();
23
+ const timeout = setTimeout(() => {
24
+ controller.abort();
25
+ }, timeoutMs);
26
+ timeout.unref();
27
+ let response;
26
28
  try {
27
- const controller = new AbortController();
28
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
29
- const response = await fetch(url, {
29
+ response = await fetch(url, {
30
30
  signal: controller.signal,
31
31
  redirect: 'follow',
32
32
  headers: { 'User-Agent': `NBTCA-CLI/${APP_INFO.version}` },
33
33
  });
34
- clearTimeout(timeout);
35
34
  const latencyMs = Date.now() - start;
36
35
  const ok = response.status >= 200 && response.status < 400;
37
36
  return { name, url, ok, statusCode: response.status, latencyMs };
38
37
  }
39
38
  catch (err) {
40
39
  const latencyMs = Date.now() - start;
41
- const error = err instanceof Error
42
- ? (err.name === 'AbortError' ? 'Request timed out' : err.message)
43
- : String(err);
40
+ const error = sanitizeTerminalLine(err instanceof Error
41
+ ? err.name === 'AbortError'
42
+ ? 'Request timed out'
43
+ : err.message
44
+ : String(err));
44
45
  return { name, url, ok: false, latencyMs, error };
45
46
  }
47
+ finally {
48
+ clearTimeout(timeout);
49
+ try {
50
+ await response?.body?.cancel();
51
+ }
52
+ catch {
53
+ // The response may already be closed by the runtime.
54
+ }
55
+ }
46
56
  }
47
- export async function checkServiceWithRetry(target, timeoutMs, retries) {
57
+ async function checkServiceWithRetry(target, timeoutMs, retries) {
48
58
  let lastResult = await checkService(target.name, target.url, timeoutMs);
49
59
  if (!lastResult.ok) {
50
60
  for (let attempt = 0; attempt < retries; attempt++) {
@@ -55,12 +65,16 @@ export async function checkServiceWithRetry(target, timeoutMs, retries) {
55
65
  break;
56
66
  }
57
67
  }
58
- return { ...lastResult, group: target.group, intranet: target.intranet };
68
+ return {
69
+ ...lastResult,
70
+ group: target.group,
71
+ ...(target.intranet === undefined ? {} : { intranet: target.intranet }),
72
+ };
59
73
  }
60
74
  export async function checkServices(options = {}) {
61
75
  const timeoutMs = options.timeoutMs ?? 6000;
62
76
  const retries = options.retries ?? 1;
63
- return Promise.all(getServiceTargets().map(t => checkServiceWithRetry(t, timeoutMs, retries)));
77
+ return Promise.all(getServiceTargets().map((t) => checkServiceWithRetry(t, timeoutMs, retries)));
64
78
  }
65
79
  export function serializeServiceStatus(items) {
66
80
  return items.map((item) => ({
@@ -75,12 +89,12 @@ export function serializeServiceStatus(items) {
75
89
  }));
76
90
  }
77
91
  export function hasServiceFailures(items) {
78
- return items.some(item => !item.ok && !item.intranet);
92
+ return items.some((item) => !item.ok && !item.intranet);
79
93
  }
80
94
  export function countServiceHealth(items) {
81
95
  let up = 0;
82
96
  let down = 0;
83
- for (const item of items.filter(i => !i.intranet)) {
97
+ for (const item of items.filter((i) => !i.intranet)) {
84
98
  if (item.ok)
85
99
  up++;
86
100
  else
@@ -97,7 +111,7 @@ export function renderServiceStatusTable(items, options) {
97
111
  const applyCyan = useColor ? chalk.cyan : id;
98
112
  const applyLatency = useColor ? c.latency : (ms) => `${ms}ms`;
99
113
  const trans = t();
100
- const nameWidth = Math.max(...items.map(i => visualWidth(i.name)), visualWidth(trans.status.service));
114
+ const nameWidth = Math.max(...items.map((i) => visualWidth(i.name)), visualWidth(trans.status.service));
101
115
  const statusWidth = 10;
102
116
  const onIcon = pickIcon('●', '+');
103
117
  const offIcon = pickIcon('✕', '!');
@@ -109,14 +123,22 @@ export function renderServiceStatusTable(items, options) {
109
123
  if (item.group !== currentGroup) {
110
124
  if (currentGroup !== undefined)
111
125
  lines.push('');
112
- const groupLabel = item.group === 'nbtca' ? trans.status.groupNbtca :
113
- item.group === 'external' ? trans.status.groupExternal :
114
- item.group === 'intranet' ? trans.status.groupIntranet : '';
126
+ const groupLabel = item.group === 'nbtca'
127
+ ? trans.status.groupNbtca
128
+ : item.group === 'external'
129
+ ? trans.status.groupExternal
130
+ : item.group === 'intranet'
131
+ ? trans.status.groupIntranet
132
+ : '';
115
133
  lines.push(` ${applyDim(groupLabel)}`);
116
134
  lines.push(` ${applyDim(sep.repeat(nameWidth + statusWidth + 12))}`);
117
135
  currentGroup = item.group;
118
136
  }
119
- const nameCol = padEndV(item.pending ? applyDim(item.name) : (item.intranet ? applyDim(item.name) : applyCyan(item.name)), nameWidth);
137
+ const nameCol = padEndV(item.pending
138
+ ? applyDim(item.name)
139
+ : item.intranet
140
+ ? applyDim(item.name)
141
+ : applyCyan(item.name), nameWidth);
120
142
  let statusLabel;
121
143
  if (item.pending) {
122
144
  statusLabel = applyDim(pickIcon('…', '.'));
@@ -138,37 +160,3 @@ export function renderServiceStatusTable(items, options) {
138
160
  }
139
161
  return lines.join('\n');
140
162
  }
141
- export async function showServiceStatus() {
142
- const trans = t();
143
- await enterScreen(breadcrumb(trans.menu.status));
144
- const targets = getServiceTargets();
145
- if (getCapabilities().reducedMotion) {
146
- const spinner = createSpinner(trans.status.checking);
147
- const items = await checkServices();
148
- const hasFailures = hasServiceFailures(items);
149
- if (hasFailures)
150
- spinner.error(trans.status.summaryFail);
151
- else
152
- spinner.stop(trans.status.summaryOk);
153
- console.log();
154
- console.log(renderServiceStatusTable(items, { color: !!process.stdout.isTTY }));
155
- console.log();
156
- return items;
157
- }
158
- const items = targets.map((tg) => ({
159
- name: tg.name, url: tg.url, ok: false, group: tg.group, intranet: tg.intranet, pending: true,
160
- }));
161
- const paint = createPainter(() => renderServiceStatusTable(items, { color: getCapabilities().color }));
162
- console.log();
163
- paint();
164
- await Promise.all(targets.map(async (target, i) => {
165
- const status = await checkServiceWithRetry(target, 6000, 1);
166
- items[i] = { ...status, pending: false };
167
- paint();
168
- }));
169
- console.log('\n');
170
- const hasFailures = hasServiceFailures(items);
171
- console.log(hasFailures ? c.warn(trans.status.summaryFail) : c.success(trans.status.summaryOk));
172
- console.log();
173
- return items;
174
- }