@stats-forge/github-stats-forge-cli 0.0.2 → 0.2.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/README.md CHANGED
@@ -21,7 +21,7 @@ Requires Node 24 or newer. The cards themselves come from
21
21
  so every option below is the one that package's api accepts — the prompts read the
22
22
  accepted values off it rather than keeping their own copy.
23
23
 
24
- It asks which card you want, then the params that card cannot render without, then
24
+ It asks which card you want, then the options that card cannot render without, then
25
25
  puts you in a menu of every other option it accepts — pick one, answer it, and the
26
26
  menu comes back with the answer beside it. `Generate the card` writes the SVG.
27
27
 
@@ -43,7 +43,7 @@ again. `Quit` (or Ctrl-C) ends the session.
43
43
 
44
44
  ## Saving a card
45
45
 
46
- `Save these options` in the menu writes the card and its params to a JSON file, and
46
+ `Save these options` in the menu writes the card and its options to a JSON file, and
47
47
  `--config` loads one back:
48
48
 
49
49
  ```sh
@@ -53,7 +53,7 @@ github-stats-forge --config my-stats.json # picks up where the file left of
53
53
  ```json
54
54
  {
55
55
  "card": "stats",
56
- "params": {
56
+ "options": {
57
57
  "username": "anuraghazra",
58
58
  "theme": "tokyonight",
59
59
  "show_icons": "true"
@@ -63,7 +63,7 @@ github-stats-forge --config my-stats.json # picks up where the file left of
63
63
 
64
64
  The file holds what a query string holds, so it reads like the URL it stands for and
65
65
  can be edited by hand. Loading one skips both the card prompt and the required
66
- params, landing you straight on the menu — and `--config` doubles as the save target,
66
+ options, landing you straight on the menu — and `--config` doubles as the save target,
67
67
  so the next `Save these options` writes back to it without asking.
68
68
 
69
69
  `--generate` skips the menu too, rendering exactly what the file holds:
package/build/cards.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ApiResult, CardConfig } from '@stats-forge/github-stats-forge-core';
1
+ import type { ApiResult, CardConfig } from '@stats-forge/github-stats-forge-core/api';
2
2
  /**
3
3
  * @file What each card accepts, in the order the prompts walk it.
4
4
  *
@@ -14,7 +14,7 @@ export interface CardOption {
14
14
  /** What the prompt asks. */
15
15
  label: string;
16
16
  kind: OptionKind;
17
- /** The accepted values, for `choice`. */
17
+ /** The accepted values, for `choice` and for a `list` whose values are a closed set. */
18
18
  choices?: ReadonlyArray<string>;
19
19
  /** Shown under the prompt, for anything the label cannot say. */
20
20
  hint?: string;
@@ -29,17 +29,12 @@ export interface CardKind {
29
29
  required: ReadonlyArray<CardOption>;
30
30
  /** Everything else, navigable in any order. */
31
31
  options: ReadonlyArray<CardOption>;
32
- /**
33
- * @param query The answers, as a query string would carry them.
34
- * @param config Tokens the fetchers use.
35
- * @returns The rendered card, or the rendered error.
36
- */
32
+ /** @returns The rendered card, or the rendered error. */
37
33
  render: (query: Record<string, string>, config: CardConfig) => Promise<ApiResult>;
38
34
  }
39
35
  /** Every card, with the options every card shares appended to its own. */
40
36
  export declare const cards: ReadonlyArray<CardKind>;
41
37
  /**
42
- * @param id The card's id, as `--card` takes it.
43
38
  * @returns The card, or `undefined` when nothing renders under that name.
44
39
  */
45
40
  export declare const findCard: (id: string) => CardKind | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"cards.d.ts","sourceRoot":"","sources":["../src/cards.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,sCAAsC,CAAC;AAElF;;;;;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,yCAAyC;IACzC,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;;;;OAIG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;CACnF;AAkSD,0EAA0E;AAC1E,eAAO,MAAM,KAAK,EAAE,aAAa,CAAC,QAAQ,CAGvC,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,QAAQ,GAAI,IAAI,MAAM,KAAG,QAAQ,GAAG,SAAiD,CAAC"}
1
+ {"version":3,"file":"cards.d.ts","sourceRoot":"","sources":["../src/cards.ts"],"names":[],"mappings":"AASA,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;AAqVD,0EAA0E;AAC1E,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,4 @@
1
- import { gist, pin, stats, themes, topLangs, wakatime } from '@stats-forge/github-stats-forge-core';
1
+ import { contributedTo, gist, pin, stats, themes, topLangs, wakatime, } from '@stats-forge/github-stats-forge-core/api';
2
2
  const THEME_NAMES = Object.keys(themes);
3
3
  /** Colors and the theme, which every card accepts. */
4
4
  const COMMON_OPTIONS = [
@@ -32,6 +32,18 @@ const COMMON_OPTIONS = [
32
32
  { name: 'border_radius', label: 'Border radius', kind: 'number' },
33
33
  { name: 'hide_border', label: 'Hide the border', kind: 'boolean' },
34
34
  ];
35
+ /**
36
+ * The two ends of a card's date range.
37
+ *
38
+ * @returns The pair, naming what it counts.
39
+ */
40
+ const rangeOptions = (counted) => {
41
+ const hint = 'A year, a month or a day: 2024, 2024-03, 2024-03-15';
42
+ return [
43
+ { name: 'from', label: `Count ${counted} from`, kind: 'text', hint },
44
+ { name: 'to', label: `Count ${counted} up to`, kind: 'text', hint },
45
+ ];
46
+ };
35
47
  const LOCALE_OPTION = {
36
48
  name: 'locale',
37
49
  label: 'Locale',
@@ -49,13 +61,13 @@ const CARDS = [
49
61
  name: 'show',
50
62
  label: 'Extra stats to show',
51
63
  kind: 'list',
52
- hint: 'e.g. reviews,discussions_started,prs_merged,contributions',
64
+ choices: stats.OPTIONS.show,
53
65
  },
54
66
  {
55
67
  name: 'hide',
56
68
  label: 'Stats to hide',
57
69
  kind: 'list',
58
- hint: 'e.g. stars,commits,prs,issues,contribs',
70
+ choices: stats.OPTIONS.hide,
59
71
  },
60
72
  { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean' },
61
73
  { name: 'hide_rank', label: 'Hide the rank circle', kind: 'boolean' },
@@ -63,19 +75,14 @@ const CARDS = [
63
75
  name: 'rank_icon',
64
76
  label: 'Rank indicator',
65
77
  kind: 'choice',
66
- choices: stats.RANK_ICONS,
78
+ choices: stats.OPTIONS.rank_icon,
67
79
  },
68
80
  {
69
81
  name: 'include_all_commits',
70
82
  label: 'Count commits of all time',
71
83
  kind: 'boolean',
72
84
  },
73
- {
74
- name: 'commits_year',
75
- label: 'Count commits for one year',
76
- kind: 'number',
77
- hint: 'Four digits, e.g. 2025',
78
- },
85
+ ...rangeOptions('commits'),
79
86
  {
80
87
  name: 'exclude_repo',
81
88
  label: 'Repositories to exclude',
@@ -95,7 +102,7 @@ const CARDS = [
95
102
  name: 'role',
96
103
  label: 'Owner affiliations to include',
97
104
  kind: 'list',
98
- hint: 'OWNER, COLLABORATOR, ORGANIZATION_MEMBER',
105
+ choices: stats.OPTIONS.role,
99
106
  },
100
107
  {
101
108
  name: 'contribs_include_own_repos',
@@ -111,7 +118,7 @@ const CARDS = [
111
118
  name: 'number_format',
112
119
  label: 'Number format',
113
120
  kind: 'choice',
114
- choices: ['short', 'long'],
121
+ choices: stats.OPTIONS.number_format,
115
122
  },
116
123
  {
117
124
  name: 'number_precision',
@@ -126,7 +133,7 @@ const CARDS = [
126
133
  { name: 'ring_color', label: 'Rank ring color', kind: 'text' },
127
134
  LOCALE_OPTION,
128
135
  ],
129
- render: (query, config) => stats(query, config),
136
+ render: stats,
130
137
  },
131
138
  {
132
139
  id: 'top-langs',
@@ -138,7 +145,7 @@ const CARDS = [
138
145
  name: 'layout',
139
146
  label: 'Layout',
140
147
  kind: 'choice',
141
- choices: topLangs.LAYOUTS,
148
+ choices: topLangs.OPTIONS.layout,
142
149
  },
143
150
  { name: 'langs_count', label: 'Languages to show', kind: 'number' },
144
151
  { name: 'hide', label: 'Languages to hide', kind: 'list' },
@@ -157,7 +164,7 @@ const CARDS = [
157
164
  name: 'stats_format',
158
165
  label: 'Show values as',
159
166
  kind: 'choice',
160
- choices: topLangs.STATS_FORMATS,
167
+ choices: topLangs.OPTIONS.stats_format,
161
168
  },
162
169
  {
163
170
  name: 'hide_progress',
@@ -170,7 +177,12 @@ const CARDS = [
170
177
  label: 'Progress bar background color',
171
178
  kind: 'text',
172
179
  },
173
- { name: 'role', label: 'Owner affiliations to include', kind: 'list' },
180
+ {
181
+ name: 'role',
182
+ label: 'Owner affiliations to include',
183
+ kind: 'list',
184
+ choices: topLangs.OPTIONS.role,
185
+ },
174
186
  { name: 'custom_title', label: 'Card title', kind: 'text' },
175
187
  { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
176
188
  { name: 'card_width', label: 'Card width', kind: 'number' },
@@ -181,7 +193,7 @@ const CARDS = [
181
193
  },
182
194
  LOCALE_OPTION,
183
195
  ],
184
- render: (query, config) => topLangs(query, config),
196
+ render: topLangs,
185
197
  },
186
198
  {
187
199
  id: 'pin',
@@ -197,7 +209,7 @@ const CARDS = [
197
209
  name: 'show',
198
210
  label: 'Extra stats to show',
199
211
  kind: 'list',
200
- hint: 'e.g. prs_authored,issues_commented',
212
+ choices: pin.OPTIONS.show,
201
213
  },
202
214
  { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean' },
203
215
  {
@@ -217,11 +229,48 @@ const CARDS = [
217
229
  name: 'number_format',
218
230
  label: 'Number format',
219
231
  kind: 'choice',
220
- choices: ['short', 'long'],
232
+ choices: pin.OPTIONS.number_format,
233
+ },
234
+ LOCALE_OPTION,
235
+ ],
236
+ render: pin,
237
+ },
238
+ {
239
+ id: 'contributed-to',
240
+ label: 'Contributed to — repositories you work on, ranked',
241
+ needsToken: true,
242
+ required: [{ name: 'username', label: 'GitHub username', kind: 'text' }],
243
+ options: [
244
+ { name: 'repos_count', label: 'Repositories to show', kind: 'number' },
245
+ {
246
+ name: 'include_own_repos',
247
+ label: 'Include your own repositories',
248
+ kind: 'boolean',
249
+ },
250
+ {
251
+ name: 'exclude_repo',
252
+ label: 'Repositories to exclude',
253
+ kind: 'list',
254
+ hint: 'Each one an owner/name, or just the name',
255
+ },
256
+ ...rangeOptions('contributions'),
257
+ {
258
+ name: 'hide_years',
259
+ label: 'Hide the year marks',
260
+ kind: 'boolean',
261
+ hint: 'One mark per contribution year, filled for the years that repo got one',
262
+ },
263
+ { name: 'custom_title', label: 'Card title', kind: 'text' },
264
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
265
+ { name: 'card_width', label: 'Card width', kind: 'number' },
266
+ {
267
+ name: 'disable_animations',
268
+ label: 'Disable the animations',
269
+ kind: 'boolean',
221
270
  },
222
271
  LOCALE_OPTION,
223
272
  ],
224
- render: (query, config) => pin(query, config),
273
+ render: contributedTo,
225
274
  },
226
275
  {
227
276
  id: 'gist',
@@ -235,8 +284,9 @@ const CARDS = [
235
284
  label: 'Let the browser wrap the description',
236
285
  kind: 'boolean',
237
286
  },
287
+ LOCALE_OPTION,
238
288
  ],
239
- render: (query, config) => gist(query, config),
289
+ render: gist,
240
290
  },
241
291
  {
242
292
  id: 'wakatime',
@@ -248,13 +298,13 @@ const CARDS = [
248
298
  name: 'layout',
249
299
  label: 'Layout',
250
300
  kind: 'choice',
251
- choices: wakatime.LAYOUTS,
301
+ choices: wakatime.OPTIONS.layout,
252
302
  },
253
303
  {
254
304
  name: 'display_format',
255
305
  label: 'Show values as',
256
306
  kind: 'choice',
257
- choices: wakatime.DISPLAY_FORMATS,
307
+ choices: wakatime.OPTIONS.display_format,
258
308
  },
259
309
  { name: 'langs_count', label: 'Languages to show', kind: 'number' },
260
310
  { name: 'hide', label: 'Languages to hide', kind: 'list' },
@@ -280,7 +330,7 @@ const CARDS = [
280
330
  },
281
331
  LOCALE_OPTION,
282
332
  ],
283
- render: (query, config) => wakatime(query, config),
333
+ render: wakatime,
284
334
  },
285
335
  ];
286
336
  /** Every card, with the options every card shares appended to its own. */
@@ -289,7 +339,6 @@ export const cards = CARDS.map((card) => ({
289
339
  options: [...card.options, ...COMMON_OPTIONS],
290
340
  }));
291
341
  /**
292
- * @param id The card's id, as `--card` takes it.
293
342
  * @returns The card, or `undefined` when nothing renders under that name.
294
343
  */
295
344
  export const findCard = (id) => cards.find((card) => card.id === id);
package/build/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { readFile, writeFile } from 'node:fs/promises';
3
3
  import { relative, resolve } from 'node:path';
4
4
  import { parseArgs } from 'node:util';
5
- import { CardConfig } from '@stats-forge/github-stats-forge-core';
5
+ import { CardConfig } from '@stats-forge/github-stats-forge-core/api';
6
6
  import { cards, findCard } from './cards.js';
7
7
  import { askRequired, askSavePath, askToken, navigateOptions, pickCard } from './prompts.js';
8
8
  import { defaultFileName, toQuery } from './query.js';
@@ -45,24 +45,20 @@ const readFlags = () => parseArgs({
45
45
  /**
46
46
  * Renders a card and writes it next to wherever the run was started.
47
47
  *
48
- * @param card The card to render.
49
- * @param query Its params.
50
- * @param config Tokens the fetchers use.
51
- * @param out Where to write it; named after the card when absent.
52
48
  * @returns The file written, or the code that says why nothing was.
53
49
  */
54
50
  const renderAndWrite = async (card, query, config, out) => {
55
51
  const result = await withSpinner(`Rendering the ${card.id} card`, () => card.render(query, config));
56
52
  if (result.status === 'error') {
57
53
  const { code, message, secondaryMessage, param } = result.error;
58
- process.stderr.write([
54
+ process.stderr.write(`${[
59
55
  `Could not render the ${card.id} card.`,
60
56
  ` ${message}${secondaryMessage ? `: ${secondaryMessage}` : ''}`,
61
57
  ` code: ${code}${param ? `, param: ${param}` : ''}`,
62
58
  result.retryable ? ' This one may work on a retry.' : '',
63
59
  ]
64
60
  .filter(Boolean)
65
- .join('\n') + '\n');
61
+ .join('\n')}\n`);
66
62
  return { failed: code };
67
63
  }
68
64
  const file = resolve(process.cwd(), out ?? defaultFileName(card, query));
@@ -73,6 +69,7 @@ const renderAndWrite = async (card, query, config, out) => {
73
69
  };
74
70
  /**
75
71
  * Renders one card and writes it next to wherever the run was started.
72
+ * Sets a non-zero exit code rather than throwing when it could not.
76
73
  *
77
74
  * @returns Nothing; the process exits non-zero when the card could not be rendered.
78
75
  */
@@ -130,14 +127,14 @@ const main = async () => {
130
127
  const config = new CardConfig({ pats: tokens });
131
128
  // `--generate` renders what the file holds and stops: no menu, nothing to answer.
132
129
  if (saved && flags.generate) {
133
- const outcome = await renderAndWrite(card, saved.params, config, flags.out);
130
+ const outcome = await renderAndWrite(card, saved.options, config, flags.out);
134
131
  if ('failed' in outcome) {
135
132
  process.exitCode = 1;
136
133
  }
137
134
  return;
138
135
  }
139
136
  const menu = {
140
- answers: saved ? toAnswers(card, saved.params) : await askRequired(card),
137
+ answers: saved ? toAnswers(card, saved.options) : await askRequired(card),
141
138
  };
142
139
  let savePath = flags.config;
143
140
  /*
@@ -180,13 +177,13 @@ const main = async () => {
180
177
  try {
181
178
  await main();
182
179
  }
183
- catch (err) {
180
+ catch (error) {
184
181
  // A cancelled prompt is a normal way to leave, not a crash.
185
- if (err instanceof Error && err.name === 'ExitPromptError') {
182
+ if (error instanceof Error && error.name === 'ExitPromptError') {
186
183
  process.exitCode = 130;
187
184
  }
188
185
  else {
189
- process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
186
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
190
187
  process.exitCode = 1;
191
188
  }
192
189
  }
@@ -1,14 +1,12 @@
1
- import type { CardKind, CardOption } from './cards.js';
2
- import type { Answer } from './query.js';
1
+ import type { CardKind, CardOption } from './cards.ts';
2
+ import type { Answer } from './query.ts';
3
3
  /**
4
4
  * @file The navigation itself.
5
5
  *
6
- * A card first, then its required params, then a menu of every other option:
6
+ * A card first, then its required options, then a menu of every other option:
7
7
  * pick one, answer it, and land back on the menu with the answer beside it.
8
8
  */
9
- /**
10
- * @returns The card to render.
11
- */
9
+ /** @returns The card to render. */
12
10
  export declare const pickCard: () => Promise<CardKind>;
13
11
  /** How a trip through the option menu ended. */
14
12
  export type MenuChoice = 'generate' | 'save' | 'quit';
@@ -29,23 +27,18 @@ export interface Menu {
29
27
  * The menu is edited in place, so reopening it after a render keeps every answer
30
28
  * and the cursor exactly where they were.
31
29
  *
32
- * @param card The card being built.
33
- * @param menu Answers so far, and where the cursor sat.
34
- * @param status What happened last time round, shown in the menu's own line.
35
30
  * @returns Whether to render the card or to stop.
36
31
  */
37
32
  export declare const navigateOptions: (card: CardKind, menu: Menu, status?: string) => Promise<MenuChoice>;
38
33
  /**
39
- * Asks for the params the card cannot render without.
34
+ * Asks for the options the card cannot render without.
40
35
  *
41
- * @param card The card being built.
42
36
  * @returns The answers, one per required param.
43
37
  */
44
38
  export declare const askRequired: (card: CardKind) => Promise<Map<string, Answer>>;
45
39
  /**
46
40
  * Asks where to write the options, when no `--config` said.
47
41
  *
48
- * @param suggestion The path offered by default.
49
42
  * @returns The path, or `undefined` when the run changed its mind.
50
43
  */
51
44
  export declare const askSavePath: (suggestion: string) => Promise<string | undefined>;
@@ -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;;GAEG;AACH,eAAO,MAAM,QAAQ,QAAa,OAAO,CAAC,QAAQ,CAI9C,CAAC;AA+BL,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;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAC1B,MAAM,QAAQ,EACd,MAAM,IAAI,EACV,SAAS,MAAM,KACd,OAAO,CAAC,UAAU,CAkCpB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAU,MAAM,QAAQ,KAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAY7E,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAU,YAAY,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":"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"}
package/build/prompts.js CHANGED
@@ -1,24 +1,20 @@
1
- import { confirm, input, password, select } from '@inquirer/prompts';
1
+ import { checkbox, confirm, input, password, select } from '@inquirer/prompts';
2
2
  import { cards } from './cards.js';
3
3
  import { describeAnswer } from './query.js';
4
4
  /**
5
5
  * @file The navigation itself.
6
6
  *
7
- * A card first, then its required params, then a menu of every other option:
7
+ * A card first, then its required options, then a menu of every other option:
8
8
  * pick one, answer it, and land back on the menu with the answer beside it.
9
9
  */
10
- /**
11
- * @returns The card to render.
12
- */
13
- export const pickCard = async () => select({
10
+ /** @returns The card to render. */
11
+ export const pickCard = () => select({
14
12
  message: 'Which card?',
15
13
  choices: cards.map((card) => ({ name: card.label, value: card })),
16
14
  });
17
15
  /**
18
16
  * Asks for one option, seeded with whatever it already holds.
19
17
  *
20
- * @param option The option to ask for.
21
- * @param current What it holds now.
22
18
  * @returns The answer, or `undefined` when it was cleared.
23
19
  */
24
20
  const askOption = async (option, current) => {
@@ -26,6 +22,15 @@ const askOption = async (option, current) => {
26
22
  if (option.kind === 'boolean') {
27
23
  return confirm({ message, default: current === true });
28
24
  }
25
+ if (option.kind === 'list' && option.choices) {
26
+ const chosen = new Set(Array.isArray(current) ? current : []);
27
+ const picked = await checkbox({
28
+ message,
29
+ pageSize: 15,
30
+ choices: option.choices.map((value) => ({ value, checked: chosen.has(value) })),
31
+ });
32
+ return picked.length > 0 ? picked : undefined;
33
+ }
29
34
  if (option.kind === 'choice') {
30
35
  const choices = [
31
36
  { name: '— leave unset —', value: undefined },
@@ -45,9 +50,6 @@ const askOption = async (option, current) => {
45
50
  * The menu is edited in place, so reopening it after a render keeps every answer
46
51
  * and the cursor exactly where they were.
47
52
  *
48
- * @param card The card being built.
49
- * @param menu Answers so far, and where the cursor sat.
50
- * @param status What happened last time round, shown in the menu's own line.
51
53
  * @returns Whether to render the card or to stop.
52
54
  */
53
55
  export const navigateOptions = async (card, menu, status) => {
@@ -58,7 +60,8 @@ export const navigateOptions = async (card, menu, status) => {
58
60
  message: status ? `${name} — ${status}` : `${name} — set an option, or generate`,
59
61
  pageSize: 15,
60
62
  // Matched by reference against the values below, so the option objects work.
61
- default: menu.cursor,
63
+ // `default` does not accept an explicit undefined, so an unset cursor omits it.
64
+ ...(menu.cursor !== undefined && { default: menu.cursor }),
62
65
  choices: [
63
66
  { name: 'Generate the card', value: 'generate' },
64
67
  { name: 'Save these options', value: 'save' },
@@ -83,9 +86,8 @@ export const navigateOptions = async (card, menu, status) => {
83
86
  }
84
87
  };
85
88
  /**
86
- * Asks for the params the card cannot render without.
89
+ * Asks for the options the card cannot render without.
87
90
  *
88
- * @param card The card being built.
89
91
  * @returns The answers, one per required param.
90
92
  */
91
93
  export const askRequired = async (card) => {
@@ -101,7 +103,6 @@ export const askRequired = async (card) => {
101
103
  /**
102
104
  * Asks where to write the options, when no `--config` said.
103
105
  *
104
- * @param suggestion The path offered by default.
105
106
  * @returns The path, or `undefined` when the run changed its mind.
106
107
  */
107
108
  export const askSavePath = async (suggestion) => {
package/build/query.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CardKind, CardOption } from './cards.js';
1
+ import type { CardKind, CardOption } from './cards.ts';
2
2
  /**
3
3
  * @file Answers in, query params out.
4
4
  *
@@ -8,29 +8,23 @@ import type { CardKind, CardOption } from './cards.js';
8
8
  /** What a prompt answered, before it becomes a query param. */
9
9
  export type Answer = string | number | boolean | Array<string> | undefined;
10
10
  /**
11
- * @param value What the prompt returned.
12
11
  * @returns The query string form, or `undefined` when there is nothing to send.
13
12
  */
14
13
  export declare const toParam: (value: Answer) => string | undefined;
15
14
  /**
16
- * @param answers What each option was answered with.
17
15
  * @returns The query the card handler is called with.
18
16
  */
19
17
  export declare const toQuery: (answers: ReadonlyMap<string, Answer>) => Record<string, string>;
20
18
  /**
21
19
  * How an answer reads back in the option menu.
22
20
  *
23
- * @param option The option it answers.
24
- * @param value What it was answered with.
25
21
  * @returns The value as the menu shows it.
26
22
  */
27
23
  export declare const describeAnswer: (option: CardOption, value: Answer) => string;
28
24
  /**
29
- * The file a card is written to when `--out` is not given.
25
+ * The `.svg` file a card is written to when `--out` is not given.
30
26
  * Named after the card and whoever it is about, so a directory of them stays readable.
31
27
  *
32
- * @param card The card being rendered.
33
- * @param query The answers it was rendered from.
34
28
  * @returns A file name, ending in `.svg`.
35
29
  */
36
30
  export declare const defaultFileName: (card: CardKind, query: Record<string, string>) => string;
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;;GAKG;AAEH,+DAA+D;AAC/D,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;AAE3E;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,KAAG,MAAM,GAAG,SAQhD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,GAAI,SAAS,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CASnF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,UAAU,EAAE,OAAO,MAAM,KAAG,MAMlE,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,QAAQ,EAAE,OAAO,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAG,MAK/E,CAAC"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;;GAKG;AAEH,+DAA+D;AAC/D,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;AAE3E;;GAEG;AACH,eAAO,MAAM,OAAO,UAAW,MAAM,KAAG,MAAM,GAAG,SAQhD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,YAAa,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CASnF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,WAAY,UAAU,SAAS,MAAM,KAAG,MAMlE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,SAAU,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAG,MAK/E,CAAC"}
package/build/query.js CHANGED
@@ -1,5 +1,4 @@
1
1
  /**
2
- * @param value What the prompt returned.
3
2
  * @returns The query string form, or `undefined` when there is nothing to send.
4
3
  */
5
4
  export const toParam = (value) => {
@@ -12,7 +11,6 @@ export const toParam = (value) => {
12
11
  return String(value);
13
12
  };
14
13
  /**
15
- * @param answers What each option was answered with.
16
14
  * @returns The query the card handler is called with.
17
15
  */
18
16
  export const toQuery = (answers) => {
@@ -28,8 +26,6 @@ export const toQuery = (answers) => {
28
26
  /**
29
27
  * How an answer reads back in the option menu.
30
28
  *
31
- * @param option The option it answers.
32
- * @param value What it was answered with.
33
29
  * @returns The value as the menu shows it.
34
30
  */
35
31
  export const describeAnswer = (option, value) => {
@@ -40,16 +36,14 @@ export const describeAnswer = (option, value) => {
40
36
  return option.kind === 'boolean' ? (value === true ? 'yes' : 'no') : param;
41
37
  };
42
38
  /**
43
- * The file a card is written to when `--out` is not given.
39
+ * The `.svg` file a card is written to when `--out` is not given.
44
40
  * Named after the card and whoever it is about, so a directory of them stays readable.
45
41
  *
46
- * @param card The card being rendered.
47
- * @param query The answers it was rendered from.
48
42
  * @returns A file name, ending in `.svg`.
49
43
  */
50
44
  export const defaultFileName = (card, query) => {
51
45
  const subject = query['username'] ?? query['id'] ?? 'card';
52
- const repo = query['repo'];
46
+ const { repo } = query;
53
47
  const parts = [card.id, subject, repo].filter(Boolean).join('-');
54
- return `${parts.replace(/[^\w.-]/g, '-')}.svg`;
48
+ return `${parts.replaceAll(/[^\w.-]/g, '-')}.svg`;
55
49
  };
@@ -1,39 +1,34 @@
1
- import type { CardKind } from './cards.js';
2
- import type { Answer } from './query.js';
1
+ import type { CardKind } from './cards.ts';
2
+ import type { Answer } from './query.ts';
3
3
  /**
4
- * @param path File to look for, relative to the working directory.
5
4
  * @returns Whether there is something there to load.
6
5
  */
7
6
  export declare const savedCardExists: (path: string) => boolean;
8
7
  /**
9
8
  * Reads a card back off disk.
10
9
  *
11
- * @param path File to read, relative to the working directory.
12
- * @returns The card it names, and its params.
13
10
  * @throws {Error} When the file is not a card this version can render.
11
+ *
12
+ * @returns The card it names, and its options.
14
13
  */
15
14
  export declare const readSavedCard: (path: string) => Promise<{
16
15
  card: CardKind;
17
- params: Record<string, string>;
16
+ options: Record<string, string>;
18
17
  }>;
19
18
  /**
20
19
  * Writes a card down, so the same one can be rendered again later.
21
20
  *
22
- * @param path File to write, relative to the working directory.
23
- * @param card The card being rendered.
24
- * @param params Its params, as they reach the endpoint.
25
21
  * @returns The path written to.
26
22
  */
27
- export declare const writeSavedCard: (path: string, card: CardKind, params: Record<string, string>) => Promise<string>;
23
+ export declare const writeSavedCard: (path: string, card: CardKind, options: Record<string, string>) => Promise<string>;
28
24
  /**
29
- * Turns saved params back into answers the menu can show and edit.
25
+ * Turns saved options back into answers the menu can show and edit.
30
26
  *
31
27
  * Everything on a query string is a string;
32
- * a boolean option becomes one again so its prompt opens on the right answer.
28
+ * a boolean becomes one again, and a list splits back into its values,
29
+ * so each prompt opens on the answer it was saved with.
33
30
  *
34
- * @param card The card the params belong to.
35
- * @param params The saved params.
36
31
  * @returns The answers, ready for the menu.
37
32
  */
38
- export declare const toAnswers: (card: CardKind, params: Record<string, string>) => Map<string, Answer>;
33
+ export declare const toAnswers: (card: CardKind, options: Record<string, string>) => Map<string, Answer>;
39
34
  //# sourceMappingURL=saved-card.d.ts.map