@stats-forge/github-stats-forge-cli 0.3.1 → 0.4.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.
package/bin.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // pnpm links the bin at install time, before anything is built, so it cannot live in build/.
3
+ import './build/index.js';
package/build/cards.d.ts CHANGED
@@ -1,14 +1,14 @@
1
- import type { ApiResult, CardConfig } from '@stats-forge/github-stats-forge-core/api';
2
1
  /**
3
2
  * @file What each card accepts, in the order the prompts walk it.
4
3
  *
5
4
  * The core schemas validate these params;
6
5
  * this catalog is what makes them navigable, so it carries the prose and the choices a schema has no room for.
7
6
  */
7
+ import type { ApiResult, CardConfig } from '@stats-forge/github-stats-forge-core/api';
8
8
  /** How a param is asked for, and how the answer becomes a query string value. */
9
9
  type OptionKind = 'text' | 'boolean' | 'number' | 'list' | 'choice';
10
10
  /** One param of one card. */
11
- export interface CardOption {
11
+ export interface CardField {
12
12
  /** Query param the answer is written to. */
13
13
  name: string;
14
14
  /** What the prompt asks. */
@@ -19,14 +19,28 @@ export interface CardOption {
19
19
  /** Shown under the prompt, for anything the label cannot say. */
20
20
  hint?: string;
21
21
  }
22
+ /** Which section of the option menu a param sits in. */
23
+ export type OptionGroup = 'data' | 'display' | 'text' | 'colors';
24
+ /**
25
+ * The menu's sections, in the order it shows them.
26
+ * A section no option sits under is dropped, so a card that counts nothing has none.
27
+ */
28
+ export declare const OPTION_GROUPS: ReadonlyArray<{
29
+ group: OptionGroup;
30
+ label: string;
31
+ }>;
32
+ /** One optional param of one card. It is required so a new option cannot go ungrouped. */
33
+ export interface CardOption extends CardField {
34
+ group: OptionGroup;
35
+ }
22
36
  /** A card, its params, and the core handler that renders it. */
23
37
  export interface CardKind {
24
38
  id: string;
25
39
  label: string;
26
40
  /** Whether rendering it calls the GitHub API, and so needs a token. */
27
41
  needsToken: boolean;
28
- /** Asked first: the card renders nothing without them. */
29
- required: ReadonlyArray<CardOption>;
42
+ /** Asked first, before the menu opens, so no section applies to them. */
43
+ required: ReadonlyArray<CardField>;
30
44
  /** Everything else, navigable in any order. */
31
45
  options: ReadonlyArray<CardOption>;
32
46
  /** @returns The rendered card, or the rendered error. */
@@ -34,7 +48,11 @@ export interface CardKind {
34
48
  }
35
49
  /** Colors and the theme, which every card accepts. */
36
50
  export declare const COMMON_OPTIONS: ReadonlyArray<CardOption>;
37
- /** Every card, with the options every card shares appended to its own. */
51
+ /**
52
+ * Every card, with the options every card shares beside its own.
53
+ * The shared ones come first so the theme heads the colors section,
54
+ * which is where a card's own color options land behind it.
55
+ */
38
56
  export declare const cards: ReadonlyArray<CardKind>;
39
57
  /**
40
58
  * @returns The card, or `undefined` when nothing renders under that name.
@@ -1 +1 @@
1
- {"version":3,"file":"cards.d.ts","sourceRoot":"","sources":["../src/cards.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAEtF;;;;;GAKG;AAEH,iFAAiF;AACjF,KAAK,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEpE,6BAA6B;AAC7B,MAAM,WAAW,UAAU;IACzB,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,UAAU,CAAC;IACjB,wFAAwF;IACxF,OAAO,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAChC,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,gEAAgE;AAChE,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,UAAU,EAAE,OAAO,CAAC;IACpB,0DAA0D;IAC1D,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACpC,+CAA+C;IAC/C,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACnC,yDAAyD;IACzD,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;CACnF;AAID,sDAAsD;AACtD,eAAO,MAAM,cAAc,EAAE,aAAa,CAAC,UAAU,CA8BpD,CAAC;AA8VF,0EAA0E;AAC1E,eAAO,MAAM,KAAK,EAAE,aAAa,CAAC,QAAQ,CAGvC,CAAC;AAEJ;;GAEG;AACH,eAAO,MAAM,QAAQ,OAAQ,MAAM,KAAG,QAAQ,GAAG,SAAiD,CAAC"}
1
+ {"version":3,"file":"cards.d.ts","sourceRoot":"","sources":["../src/cards.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAEtF,iFAAiF;AACjF,KAAK,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEpE,6BAA6B;AAC7B,MAAM,WAAW,SAAS;IACxB,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,UAAU,CAAC;IACjB,wFAAwF;IACxF,OAAO,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAChC,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wDAAwD;AACxD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEjE;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,aAAa,CAAC;IAAE,KAAK,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAK9E,CAAC;AAEF,0FAA0F;AAC1F,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,KAAK,EAAE,WAAW,CAAC;CACpB;AAED,gEAAgE;AAChE,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,UAAU,EAAE,OAAO,CAAC;IACpB,yEAAyE;IACzE,QAAQ,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;IACnC,+CAA+C;IAC/C,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACnC,yDAAyD;IACzD,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;CACnF;AAID,sDAAsD;AACtD,eAAO,MAAM,cAAc,EAAE,aAAa,CAAC,UAAU,CAkCpD,CAAC;AAsYF;;;;GAIG;AACH,eAAO,MAAM,KAAK,EAAE,aAAa,CAAC,QAAQ,CAGvC,CAAC;AAEJ;;GAEG;AACH,eAAO,MAAM,QAAQ,OAAQ,MAAM,KAAG,QAAQ,GAAG,SAAiD,CAAC"}
package/build/cards.js CHANGED
@@ -1,4 +1,20 @@
1
+ /**
2
+ * @file What each card accepts, in the order the prompts walk it.
3
+ *
4
+ * The core schemas validate these params;
5
+ * this catalog is what makes them navigable, so it carries the prose and the choices a schema has no room for.
6
+ */
1
7
  import { contributedTo, gist, org, pin, stats, themes, topLangs, wakatime, } from '@stats-forge/github-stats-forge-core/api';
8
+ /**
9
+ * The menu's sections, in the order it shows them.
10
+ * A section no option sits under is dropped, so a card that counts nothing has none.
11
+ */
12
+ export const OPTION_GROUPS = [
13
+ { group: 'data', label: 'What it counts' },
14
+ { group: 'display', label: 'What it shows' },
15
+ { group: 'text', label: 'Text and size' },
16
+ { group: 'colors', label: 'Colors and border' },
17
+ ];
2
18
  const THEME_NAMES = Object.keys(themes);
3
19
  /** Colors and the theme, which every card accepts. */
4
20
  export const COMMON_OPTIONS = [
@@ -6,6 +22,7 @@ export const COMMON_OPTIONS = [
6
22
  name: 'theme',
7
23
  label: 'Theme',
8
24
  kind: 'choice',
25
+ group: 'colors',
9
26
  choices: THEME_NAMES,
10
27
  hint: 'An unknown name falls back to the default theme',
11
28
  },
@@ -13,24 +30,27 @@ export const COMMON_OPTIONS = [
13
30
  name: 'title_color',
14
31
  label: 'Title color',
15
32
  kind: 'text',
33
+ group: 'colors',
16
34
  hint: 'Hex, no #',
17
35
  },
18
- { name: 'text_color', label: 'Text color', kind: 'text', hint: 'Hex, no #' },
19
- { name: 'icon_color', label: 'Icon color', kind: 'text', hint: 'Hex, no #' },
36
+ { name: 'text_color', label: 'Text color', kind: 'text', group: 'colors', hint: 'Hex, no #' },
37
+ { name: 'icon_color', label: 'Icon color', kind: 'text', group: 'colors', hint: 'Hex, no #' },
20
38
  {
21
39
  name: 'bg_color',
22
40
  label: 'Background color',
23
41
  kind: 'text',
42
+ group: 'colors',
24
43
  hint: 'Hex, no #, or a gradient: angle,color,color',
25
44
  },
26
45
  {
27
46
  name: 'border_color',
28
47
  label: 'Border color',
29
48
  kind: 'text',
49
+ group: 'colors',
30
50
  hint: 'Hex, no #',
31
51
  },
32
- { name: 'border_radius', label: 'Border radius', kind: 'number' },
33
- { name: 'hide_border', label: 'Hide the border', kind: 'boolean' },
52
+ { name: 'border_radius', label: 'Border radius', kind: 'number', group: 'colors' },
53
+ { name: 'hide_border', label: 'Hide the border', kind: 'boolean', group: 'colors' },
34
54
  ];
35
55
  /**
36
56
  * The two ends of a card's date range.
@@ -40,14 +60,15 @@ export const COMMON_OPTIONS = [
40
60
  const rangeOptions = (counted) => {
41
61
  const hint = 'A year, a month or a day: 2024, 2024-03, 2024-03-15';
42
62
  return [
43
- { name: 'from', label: `Count ${counted} from`, kind: 'text', hint },
44
- { name: 'to', label: `Count ${counted} up to`, kind: 'text', hint },
63
+ { name: 'from', label: `Count ${counted} from`, kind: 'text', group: 'data', hint },
64
+ { name: 'to', label: `Count ${counted} up to`, kind: 'text', group: 'data', hint },
45
65
  ];
46
66
  };
47
67
  const LOCALE_OPTION = {
48
68
  name: 'locale',
49
69
  label: 'Locale',
50
70
  kind: 'text',
71
+ group: 'text',
51
72
  hint: 'Two-letter code, e.g. es',
52
73
  };
53
74
  const CARDS = [
@@ -61,76 +82,88 @@ const CARDS = [
61
82
  name: 'show',
62
83
  label: 'Extra stats to show',
63
84
  kind: 'list',
85
+ group: 'display',
64
86
  choices: stats.OPTIONS.show,
65
87
  },
66
88
  {
67
89
  name: 'hide',
68
90
  label: 'Stats to hide',
69
91
  kind: 'list',
92
+ group: 'display',
70
93
  choices: stats.OPTIONS.hide,
71
94
  },
72
- { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean' },
73
- { name: 'hide_rank', label: 'Hide the rank circle', kind: 'boolean' },
95
+ { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean', group: 'display' },
96
+ { name: 'hide_rank', label: 'Hide the rank circle', kind: 'boolean', group: 'display' },
74
97
  {
75
98
  name: 'rank_icon',
76
99
  label: 'Rank indicator',
77
100
  kind: 'choice',
101
+ group: 'display',
78
102
  choices: stats.OPTIONS.rank_icon,
79
103
  },
80
104
  {
81
105
  name: 'include_all_commits',
82
106
  label: 'Count commits of all time',
83
107
  kind: 'boolean',
108
+ group: 'data',
84
109
  },
85
110
  ...rangeOptions('commits'),
86
111
  {
87
112
  name: 'exclude_repo',
88
113
  label: 'Repositories to exclude',
89
114
  kind: 'list',
115
+ group: 'data',
90
116
  },
91
117
  {
92
118
  name: 'repo',
93
119
  label: 'Repositories the search-based stats are scoped to',
94
120
  kind: 'list',
121
+ group: 'data',
95
122
  },
96
123
  {
97
124
  name: 'owner',
98
125
  label: 'Owners the search-based stats are scoped to',
99
126
  kind: 'list',
127
+ group: 'data',
100
128
  },
101
129
  {
102
130
  name: 'role',
103
131
  label: 'Owner affiliations to include',
104
132
  kind: 'list',
133
+ group: 'data',
105
134
  choices: stats.OPTIONS.role,
106
135
  },
107
136
  {
108
137
  name: 'contribs_include_own_repos',
109
138
  label: 'Count contributions to your own repositories',
110
139
  kind: 'boolean',
140
+ group: 'data',
111
141
  },
112
- { name: 'custom_title', label: 'Card title', kind: 'text' },
113
- { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
114
- { name: 'card_width', label: 'Card width', kind: 'number' },
115
- { name: 'line_height', label: 'Line height', kind: 'number' },
116
- { name: 'text_bold', label: 'Bold stat values', kind: 'boolean' },
142
+ { name: 'custom_title', label: 'Card title', kind: 'text', group: 'text' },
143
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean', group: 'text' },
144
+ { name: 'card_width', label: 'Card width', kind: 'number', group: 'text' },
145
+ { name: 'line_height', label: 'Line height', kind: 'number', group: 'text' },
146
+ { name: 'text_bold', label: 'Bold stat values', kind: 'boolean', group: 'text' },
117
147
  {
118
148
  name: 'number_format',
119
149
  label: 'Number format',
120
150
  kind: 'choice',
151
+ group: 'text',
121
152
  choices: stats.OPTIONS.number_format,
122
153
  },
123
154
  {
124
155
  name: 'number_precision',
125
156
  label: 'Decimals kept when abbreviating',
126
157
  kind: 'number',
158
+ group: 'text',
127
159
  },
128
160
  {
129
161
  name: 'disable_animations',
130
162
  label: 'Disable the animations',
131
163
  kind: 'boolean',
164
+ group: 'text',
132
165
  },
133
- { name: 'ring_color', label: 'Rank ring color', kind: 'text' },
166
+ { name: 'ring_color', label: 'Rank ring color', kind: 'text', group: 'colors' },
134
167
  LOCALE_OPTION,
135
168
  ],
136
169
  render: stats,
@@ -145,51 +178,59 @@ const CARDS = [
145
178
  name: 'layout',
146
179
  label: 'Layout',
147
180
  kind: 'choice',
181
+ group: 'display',
148
182
  choices: topLangs.OPTIONS.layout,
149
183
  },
150
- { name: 'langs_count', label: 'Languages to show', kind: 'number' },
151
- { name: 'hide', label: 'Languages to hide', kind: 'list' },
152
- { name: 'exclude_repo', label: 'Repositories to exclude', kind: 'list' },
184
+ { name: 'langs_count', label: 'Languages to show', kind: 'number', group: 'data' },
185
+ { name: 'hide', label: 'Languages to hide', kind: 'list', group: 'data' },
186
+ { name: 'exclude_repo', label: 'Repositories to exclude', kind: 'list', group: 'data' },
153
187
  {
154
188
  name: 'size_weight',
155
189
  label: "Weight given to a language's size",
156
190
  kind: 'number',
191
+ group: 'data',
157
192
  },
158
193
  {
159
194
  name: 'count_weight',
160
195
  label: 'Weight given to its repository count',
161
196
  kind: 'number',
197
+ group: 'data',
162
198
  },
163
199
  {
164
200
  name: 'stats_format',
165
201
  label: 'Show values as',
166
202
  kind: 'choice',
203
+ group: 'display',
167
204
  choices: topLangs.OPTIONS.stats_format,
168
205
  },
169
206
  {
170
207
  name: 'hide_progress',
171
208
  label: 'Hide the progress bars',
172
209
  kind: 'boolean',
210
+ group: 'display',
173
211
  },
174
- { name: 'hide_values', label: 'Hide the values', kind: 'boolean' },
212
+ { name: 'hide_values', label: 'Hide the values', kind: 'boolean', group: 'display' },
175
213
  {
176
214
  name: 'prog_bar_bg_color',
177
215
  label: 'Progress bar background color',
178
216
  kind: 'text',
217
+ group: 'colors',
179
218
  },
180
219
  {
181
220
  name: 'role',
182
221
  label: 'Owner affiliations to include',
183
222
  kind: 'list',
223
+ group: 'data',
184
224
  choices: topLangs.OPTIONS.role,
185
225
  },
186
- { name: 'custom_title', label: 'Card title', kind: 'text' },
187
- { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
188
- { name: 'card_width', label: 'Card width', kind: 'number' },
226
+ { name: 'custom_title', label: 'Card title', kind: 'text', group: 'text' },
227
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean', group: 'text' },
228
+ { name: 'card_width', label: 'Card width', kind: 'number', group: 'text' },
189
229
  {
190
230
  name: 'disable_animations',
191
231
  label: 'Disable the animations',
192
232
  kind: 'boolean',
233
+ group: 'text',
193
234
  },
194
235
  LOCALE_OPTION,
195
236
  ],
@@ -204,31 +245,35 @@ const CARDS = [
204
245
  { name: 'repo', label: 'Repository name', kind: 'text' },
205
246
  ],
206
247
  options: [
207
- { name: 'show_owner', label: 'Show the owner', kind: 'boolean' },
248
+ { name: 'show_owner', label: 'Show the owner', kind: 'boolean', group: 'display' },
208
249
  {
209
250
  name: 'show',
210
251
  label: 'Extra stats to show',
211
252
  kind: 'list',
253
+ group: 'display',
212
254
  choices: pin.OPTIONS.show,
213
255
  },
214
- { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean' },
256
+ { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean', group: 'display' },
215
257
  {
216
258
  name: 'description_lines_count',
217
259
  label: 'Lines the description wraps to',
218
260
  kind: 'number',
261
+ group: 'text',
219
262
  },
220
263
  {
221
264
  name: 'browser_rendering',
222
265
  label: 'Let the browser wrap the description',
223
266
  kind: 'boolean',
267
+ group: 'text',
224
268
  },
225
- { name: 'card_width', label: 'Card width', kind: 'number' },
226
- { name: 'line_height', label: 'Line height', kind: 'number' },
227
- { name: 'text_bold', label: 'Bold stat values', kind: 'boolean' },
269
+ { name: 'card_width', label: 'Card width', kind: 'number', group: 'text' },
270
+ { name: 'line_height', label: 'Line height', kind: 'number', group: 'text' },
271
+ { name: 'text_bold', label: 'Bold stat values', kind: 'boolean', group: 'text' },
228
272
  {
229
273
  name: 'number_format',
230
274
  label: 'Number format',
231
275
  kind: 'choice',
276
+ group: 'text',
232
277
  choices: pin.OPTIONS.number_format,
233
278
  },
234
279
  LOCALE_OPTION,
@@ -245,35 +290,40 @@ const CARDS = [
245
290
  name: 'show',
246
291
  label: 'Extra stats to show',
247
292
  kind: 'list',
293
+ group: 'display',
248
294
  choices: org.OPTIONS.show,
249
295
  },
250
296
  {
251
297
  name: 'hide',
252
298
  label: 'Stats to hide',
253
299
  kind: 'list',
300
+ group: 'display',
254
301
  choices: org.OPTIONS.hide,
255
302
  },
256
- { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean' },
303
+ { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean', group: 'display' },
257
304
  {
258
305
  name: 'hide_description',
259
306
  label: "Hide the organization's description",
260
307
  kind: 'boolean',
308
+ group: 'display',
261
309
  },
262
- { name: 'custom_title', label: 'Card title', kind: 'text' },
263
- { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
264
- { name: 'card_width', label: 'Card width', kind: 'number' },
265
- { name: 'line_height', label: 'Line height', kind: 'number' },
266
- { name: 'text_bold', label: 'Bold stat values', kind: 'boolean' },
310
+ { name: 'custom_title', label: 'Card title', kind: 'text', group: 'text' },
311
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean', group: 'text' },
312
+ { name: 'card_width', label: 'Card width', kind: 'number', group: 'text' },
313
+ { name: 'line_height', label: 'Line height', kind: 'number', group: 'text' },
314
+ { name: 'text_bold', label: 'Bold stat values', kind: 'boolean', group: 'text' },
267
315
  {
268
316
  name: 'number_format',
269
317
  label: 'Number format',
270
318
  kind: 'choice',
319
+ group: 'text',
271
320
  choices: org.OPTIONS.number_format,
272
321
  },
273
322
  {
274
323
  name: 'disable_animations',
275
324
  label: 'Disable the animations',
276
325
  kind: 'boolean',
326
+ group: 'text',
277
327
  },
278
328
  LOCALE_OPTION,
279
329
  ],
@@ -285,16 +335,18 @@ const CARDS = [
285
335
  needsToken: true,
286
336
  required: [{ name: 'username', label: 'GitHub username', kind: 'text' }],
287
337
  options: [
288
- { name: 'repos_count', label: 'Repositories to show', kind: 'number' },
338
+ { name: 'repos_count', label: 'Repositories to show', kind: 'number', group: 'data' },
289
339
  {
290
340
  name: 'include_own_repos',
291
341
  label: 'Include your own repositories',
292
342
  kind: 'boolean',
343
+ group: 'data',
293
344
  },
294
345
  {
295
346
  name: 'exclude_repo',
296
347
  label: 'Repositories to exclude',
297
348
  kind: 'list',
349
+ group: 'data',
298
350
  hint: 'Each one an owner/name, or just the name',
299
351
  },
300
352
  ...rangeOptions('contributions'),
@@ -302,15 +354,17 @@ const CARDS = [
302
354
  name: 'hide_years',
303
355
  label: 'Hide the year marks',
304
356
  kind: 'boolean',
357
+ group: 'display',
305
358
  hint: 'One mark per contribution year, filled for the years that repo got one',
306
359
  },
307
- { name: 'custom_title', label: 'Card title', kind: 'text' },
308
- { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
309
- { name: 'card_width', label: 'Card width', kind: 'number' },
360
+ { name: 'custom_title', label: 'Card title', kind: 'text', group: 'text' },
361
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean', group: 'text' },
362
+ { name: 'card_width', label: 'Card width', kind: 'number', group: 'text' },
310
363
  {
311
364
  name: 'disable_animations',
312
365
  label: 'Disable the animations',
313
366
  kind: 'boolean',
367
+ group: 'text',
314
368
  },
315
369
  LOCALE_OPTION,
316
370
  ],
@@ -322,11 +376,12 @@ const CARDS = [
322
376
  needsToken: true,
323
377
  required: [{ name: 'id', label: 'Gist ID', kind: 'text' }],
324
378
  options: [
325
- { name: 'show_owner', label: 'Show the owner', kind: 'boolean' },
379
+ { name: 'show_owner', label: 'Show the owner', kind: 'boolean', group: 'display' },
326
380
  {
327
381
  name: 'browser_rendering',
328
382
  label: 'Let the browser wrap the description',
329
383
  kind: 'boolean',
384
+ group: 'text',
330
385
  },
331
386
  LOCALE_OPTION,
332
387
  ],
@@ -342,34 +397,39 @@ const CARDS = [
342
397
  name: 'layout',
343
398
  label: 'Layout',
344
399
  kind: 'choice',
400
+ group: 'display',
345
401
  choices: wakatime.OPTIONS.layout,
346
402
  },
347
403
  {
348
404
  name: 'display_format',
349
405
  label: 'Show values as',
350
406
  kind: 'choice',
407
+ group: 'display',
351
408
  choices: wakatime.OPTIONS.display_format,
352
409
  },
353
- { name: 'langs_count', label: 'Languages to show', kind: 'number' },
354
- { name: 'hide', label: 'Languages to hide', kind: 'list' },
410
+ { name: 'langs_count', label: 'Languages to show', kind: 'number', group: 'data' },
411
+ { name: 'hide', label: 'Languages to hide', kind: 'list', group: 'data' },
355
412
  {
356
413
  name: 'hide_progress',
357
414
  label: 'Hide the progress bars',
358
415
  kind: 'boolean',
416
+ group: 'display',
359
417
  },
360
- { name: 'custom_title', label: 'Card title', kind: 'text' },
361
- { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
362
- { name: 'card_width', label: 'Card width', kind: 'number' },
363
- { name: 'line_height', label: 'Line height', kind: 'number' },
418
+ { name: 'custom_title', label: 'Card title', kind: 'text', group: 'text' },
419
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean', group: 'text' },
420
+ { name: 'card_width', label: 'Card width', kind: 'number', group: 'text' },
421
+ { name: 'line_height', label: 'Line height', kind: 'number', group: 'text' },
364
422
  {
365
423
  name: 'disable_animations',
366
424
  label: 'Disable the animations',
367
425
  kind: 'boolean',
426
+ group: 'text',
368
427
  },
369
428
  {
370
429
  name: 'api_domain',
371
430
  label: 'WakaTime instance',
372
431
  kind: 'text',
432
+ group: 'data',
373
433
  hint: 'Defaults to wakatime.com',
374
434
  },
375
435
  LOCALE_OPTION,
@@ -377,10 +437,14 @@ const CARDS = [
377
437
  render: wakatime,
378
438
  },
379
439
  ];
380
- /** Every card, with the options every card shares appended to its own. */
440
+ /**
441
+ * Every card, with the options every card shares beside its own.
442
+ * The shared ones come first so the theme heads the colors section,
443
+ * which is where a card's own color options land behind it.
444
+ */
381
445
  export const cards = CARDS.map((card) => ({
382
446
  ...card,
383
- options: [...card.options, ...COMMON_OPTIONS],
447
+ options: [...COMMON_OPTIONS, ...card.options],
384
448
  }));
385
449
  /**
386
450
  * @returns The card, or `undefined` when nothing renders under that name.
package/build/index.js CHANGED
File without changes
@@ -1,11 +1,12 @@
1
- import type { CardKind, CardOption } from './cards.ts';
2
- import type { Answer } from './query.ts';
3
1
  /**
4
2
  * @file The navigation itself.
5
3
  *
6
- * A card first, then its required options, then a menu of every other option:
7
- * pick one, answer it, and land back on the menu with the answer beside it.
4
+ * A card first, then its required options, then a menu of every other option
5
+ * under the section it belongs to: pick one, answer it, and land back on the
6
+ * menu with the answer beside it.
8
7
  */
8
+ import type { CardKind, CardOption } from './cards.ts';
9
+ import type { Answer } from './query.ts';
9
10
  /** @returns The card to render. */
10
11
  export declare const pickCard: () => Promise<CardKind>;
11
12
  /** How a trip through the option menu ended. */
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGzC;;;;;GAKG;AAEH,mCAAmC;AACnC,eAAO,MAAM,QAAQ,QAAO,OAAO,CAAC,QAAQ,CAIxC,CAAC;AAuCL,gDAAgD;AAChD,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AAEtD,sDAAsD;AACtD,MAAM,WAAW,IAAI;IACnB,uCAAuC;IACvC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;CAC9C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,SACpB,QAAQ,QACR,IAAI,WACD,MAAM,KACd,OAAO,CAAC,UAAU,CAmCpB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,SAAgB,QAAQ,KAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAY7E,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,eAAsB,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAMhF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,QAAa,OAAO,CAAC,MAAM,GAAG,SAAS,CAM3D,CAAC"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGzC,mCAAmC;AACnC,eAAO,MAAM,QAAQ,QAAO,OAAO,CAAC,QAAQ,CAIxC,CAAC;AAuCL,gDAAgD;AAChD,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,CAAC;AA8BtD,sDAAsD;AACtD,MAAM,WAAW,IAAI;IACnB,uCAAuC;IACvC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;CAC9C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,SACpB,QAAQ,QACR,IAAI,WACD,MAAM,KACd,OAAO,CAAC,UAAU,CAsDpB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,SAAgB,QAAQ,KAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAY7E,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,eAAsB,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAMhF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,QAAa,OAAO,CAAC,MAAM,GAAG,SAAS,CAM3D,CAAC"}
package/build/prompts.js CHANGED
@@ -1,12 +1,14 @@
1
- import { checkbox, confirm, input, password, select } from '@inquirer/prompts';
2
- import { cards } from './cards.js';
3
- import { describeAnswer } from './query.js';
4
1
  /**
5
2
  * @file The navigation itself.
6
3
  *
7
- * A card first, then its required options, then a menu of every other option:
8
- * pick one, answer it, and land back on the menu with the answer beside it.
4
+ * A card first, then its required options, then a menu of every other option
5
+ * under the section it belongs to: pick one, answer it, and land back on the
6
+ * menu with the answer beside it.
9
7
  */
8
+ import { styleText } from 'node:util';
9
+ import { checkbox, confirm, input, password, select, Separator } from '@inquirer/prompts';
10
+ import { cards, OPTION_GROUPS } from './cards.js';
11
+ import { describeAnswer, UNSET } from './query.js';
10
12
  /** @returns The card to render. */
11
13
  export const pickCard = () => select({
12
14
  message: 'Which card?',
@@ -44,6 +46,29 @@ const askOption = async (option, current) => {
44
46
  });
45
47
  return answer.trim() === '' ? undefined : answer.trim();
46
48
  };
49
+ /** Rows the list may use: the terminal, less the message, the help line and some air. */
50
+ const menuHeight = () => process.stdout.isTTY ? Math.max(10, process.stdout.rows - 6) : 15;
51
+ /**
52
+ * A section heading, ruled out to the width of the labels under it.
53
+ *
54
+ * @returns A `Separator`, so the cursor steps straight over it.
55
+ */
56
+ const heading = (label, width) => new Separator(styleText('dim', `── ${label} ${'─'.repeat(Math.max(2, width - label.length))}`));
57
+ /**
58
+ * Typing jumps to the first row whose label starts with what was typed, which is
59
+ * what keeps the three actions one key away however far down the list the cursor sits.
60
+ *
61
+ * @returns The line under the list.
62
+ */
63
+ const keysHelpTip = (keys) => {
64
+ const all = [
65
+ ...keys,
66
+ ['type', 'to jump — g generate, s save, q quit'],
67
+ ];
68
+ return all
69
+ .map(([key, action]) => `${styleText('bold', key)} ${styleText('dim', action)}`)
70
+ .join(styleText('dim', ' • '));
71
+ };
47
72
  /**
48
73
  * Walks a card's options until the run asks for the card, or to leave.
49
74
  *
@@ -55,21 +80,37 @@ const askOption = async (option, current) => {
55
80
  export const navigateOptions = async (card, menu, status) => {
56
81
  // The label carries a description after an em dash; the menu wants the name.
57
82
  const [name = card.id] = card.label.split(' — ');
83
+ const width = Math.max(...card.options.map((option) => option.label.length));
58
84
  for (;;) {
85
+ // A section none of this card's options sit under is dropped, heading and all.
86
+ const sections = OPTION_GROUPS.flatMap(({ group, label }) => {
87
+ const options = card.options.filter((option) => option.group === group);
88
+ if (options.length === 0) {
89
+ return [];
90
+ }
91
+ return [
92
+ heading(label, width),
93
+ ...options.map((option) => {
94
+ // An unanswered option recedes, so what is set reads as the foreground.
95
+ const answer = describeAnswer(option, menu.answers.get(option.name));
96
+ const value = answer === UNSET ? styleText('dim', answer) : answer;
97
+ return { name: `${option.label.padEnd(width)} ${value}`, value: option };
98
+ }),
99
+ ];
100
+ });
59
101
  const choice = await select({
60
102
  message: status ? `${name} — ${status}` : `${name} — set an option, or generate`,
61
- pageSize: 15,
103
+ pageSize: menuHeight(),
104
+ theme: { style: { keysHelpTip } },
62
105
  // Matched by reference against the values below, so the option objects work.
63
106
  // `default` does not accept an explicit undefined, so an unset cursor omits it.
64
107
  ...(menu.cursor !== undefined && { default: menu.cursor }),
65
108
  choices: [
109
+ heading('Actions', width),
66
110
  { name: 'Generate the card', value: 'generate' },
67
111
  { name: 'Save these options', value: 'save' },
68
112
  { name: 'Quit', value: 'quit' },
69
- ...card.options.map((option) => ({
70
- name: `${option.label.padEnd(38)} ${describeAnswer(option, menu.answers.get(option.name))}`,
71
- value: option,
72
- })),
113
+ ...sections,
73
114
  ],
74
115
  });
75
116
  menu.cursor = choice;