@stats-forge/github-stats-forge-cli 0.3.2 → 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/build/query.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import type { CardKind, CardOption } from './cards.ts';
2
1
  /**
3
2
  * @file Answers in, query params out.
4
3
  *
5
4
  * The core handlers take exactly what a query string carries — strings — so an
6
5
  * answer becomes one here, and an unanswered option is simply absent.
7
6
  */
7
+ import type { CardKind, CardOption } from './cards.ts';
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
  /**
@@ -15,10 +15,12 @@ export declare const toParam: (value: Answer) => string | undefined;
15
15
  * @returns The query the card handler is called with.
16
16
  */
17
17
  export declare const toQuery: (answers: ReadonlyMap<string, Answer>) => Record<string, string>;
18
+ /** What the menu shows for an option nothing has answered. */
19
+ export declare const UNSET = "\u2014";
18
20
  /**
19
21
  * How an answer reads back in the option menu.
20
22
  *
21
- * @returns The value as the menu shows it.
23
+ * @returns The value as the menu shows it, or {@link UNSET}.
22
24
  */
23
25
  export declare const describeAnswer: (option: CardOption, value: Answer) => string;
24
26
  /**
@@ -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;;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"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEvD,+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,8DAA8D;AAC9D,eAAO,MAAM,KAAK,WAAM,CAAC;AAEzB;;;;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,3 +1,9 @@
1
+ /**
2
+ * @file Answers in, query params out.
3
+ *
4
+ * The core handlers take exactly what a query string carries — strings — so an
5
+ * answer becomes one here, and an unanswered option is simply absent.
6
+ */
1
7
  /**
2
8
  * @returns The query string form, or `undefined` when there is nothing to send.
3
9
  */
@@ -23,15 +29,17 @@ export const toQuery = (answers) => {
23
29
  }
24
30
  return query;
25
31
  };
32
+ /** What the menu shows for an option nothing has answered. */
33
+ export const UNSET = '—';
26
34
  /**
27
35
  * How an answer reads back in the option menu.
28
36
  *
29
- * @returns The value as the menu shows it.
37
+ * @returns The value as the menu shows it, or {@link UNSET}.
30
38
  */
31
39
  export const describeAnswer = (option, value) => {
32
40
  const param = toParam(value);
33
41
  if (param === undefined) {
34
- return '—';
42
+ return UNSET;
35
43
  }
36
44
  return option.kind === 'boolean' ? (value === true ? 'yes' : 'no') : param;
37
45
  };
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @file A card, written down.
3
+ *
4
+ * The file holds what a query string would hold — the card and its options as
5
+ * strings — so it reads like the URL it stands for, and can be edited by hand.
6
+ */
1
7
  import type { CardKind } from './cards.ts';
2
8
  import type { Answer } from './query.ts';
3
9
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"saved-card.d.ts","sourceRoot":"","sources":["../src/saved-card.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAiBzC;;GAEG;AACH,eAAO,MAAM,eAAe,SAAU,MAAM,KAAG,OAAmD,CAAC;AAEnG;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,SAClB,MAAM,KACX,OAAO,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAyB7D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,SACnB,MAAM,QACN,QAAQ,WACL,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC9B,OAAO,CAAC,MAAM,CAKhB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,SAAU,QAAQ,WAAW,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAuB7F,CAAC"}
1
+ {"version":3,"file":"saved-card.d.ts","sourceRoot":"","sources":["../src/saved-card.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAUzC;;GAEG;AACH,eAAO,MAAM,eAAe,SAAU,MAAM,KAAG,OAAmD,CAAC;AAEnG;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,SAClB,MAAM,KACX,OAAO,CAAC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAyB7D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,SACnB,MAAM,QACN,QAAQ,WACL,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC9B,OAAO,CAAC,MAAM,CAKhB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,SAAU,QAAQ,WAAW,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAuB7F,CAAC"}
@@ -1,3 +1,9 @@
1
+ /**
2
+ * @file A card, written down.
3
+ *
4
+ * The file holds what a query string would hold — the card and its options as
5
+ * strings — so it reads like the URL it stands for, and can be edited by hand.
6
+ */
1
7
  import { existsSync } from 'node:fs';
2
8
  import { readFile, writeFile } from 'node:fs/promises';
3
9
  import { resolve } from 'node:path';
package/build/tokens.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { PersonalAccessToken } from '@stats-forge/github-stats-forge-core/api';
2
1
  /**
3
2
  * @file Where the GitHub token comes from.
4
3
  *
@@ -6,6 +5,7 @@ import type { PersonalAccessToken } from '@stats-forge/github-stats-forge-core/a
6
5
  * the flag, an env file, then the environment the shell already carries.
7
6
  * Whatever is left over is asked for interactively, so a first run needs no setup.
8
7
  */
8
+ import type { PersonalAccessToken } from '@stats-forge/github-stats-forge-core/api';
9
9
  /** The file loaded when `--env-file` is not given. */
10
10
  export declare const DEFAULT_ENV_FILE = ".env";
11
11
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAEpF;;;;;;GAMG;AAEH,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAEvC;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,SAAU,MAAM,YAAY,OAAO,KAAG,OAU7D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,QACnB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,KACtC,KAAK,CAAC,mBAAmB,CAOtB,CAAC;AAEP;;;;GAIG;AACH,eAAO,MAAM,aAAa,UACjB,aAAa,CAAC,MAAM,CAAC,OACvB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,KACtC,KAAK,CAAC,mBAAmB,CAO3B,CAAC"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0CAA0C,CAAC;AAEpF,sDAAsD;AACtD,eAAO,MAAM,gBAAgB,SAAS,CAAC;AAEvC;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,SAAU,MAAM,YAAY,OAAO,KAAG,OAU7D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,QACnB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,KACtC,KAAK,CAAC,mBAAmB,CAOtB,CAAC;AAEP;;;;GAIG;AACH,eAAO,MAAM,aAAa,UACjB,aAAa,CAAC,MAAM,CAAC,OACvB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,KACtC,KAAK,CAAC,mBAAmB,CAO3B,CAAC"}
package/build/tokens.js CHANGED
@@ -1,5 +1,3 @@
1
- import { existsSync } from 'node:fs';
2
- import { resolve } from 'node:path';
3
1
  /**
4
2
  * @file Where the GitHub token comes from.
5
3
  *
@@ -7,6 +5,8 @@ import { resolve } from 'node:path';
7
5
  * the flag, an env file, then the environment the shell already carries.
8
6
  * Whatever is left over is asked for interactively, so a first run needs no setup.
9
7
  */
8
+ import { existsSync } from 'node:fs';
9
+ import { resolve } from 'node:path';
10
10
  /** The file loaded when `--env-file` is not given. */
11
11
  export const DEFAULT_ENV_FILE = '.env';
12
12
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stats-forge/github-stats-forge-cli",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Render a GitHub stats card to a local SVG file, one prompt at a time",
5
5
  "keywords": [
6
6
  "cli",
@@ -23,9 +23,10 @@
23
23
  "directory": "packages/cli"
24
24
  },
25
25
  "bin": {
26
- "github-stats-forge": "./build/index.js"
26
+ "github-stats-forge": "./bin.js"
27
27
  },
28
28
  "files": [
29
+ "bin.js",
29
30
  "build",
30
31
  "src"
31
32
  ],
@@ -45,7 +46,7 @@
45
46
  },
46
47
  "dependencies": {
47
48
  "@inquirer/prompts": "8.7.0",
48
- "@stats-forge/github-stats-forge-core": "^0.5.0"
49
+ "@stats-forge/github-stats-forge-core": "^0.6.0"
49
50
  },
50
51
  "devDependencies": {
51
52
  "vitest": "4.1.11"
package/src/cards.ts CHANGED
@@ -1,3 +1,10 @@
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
+ */
7
+
1
8
  import {
2
9
  contributedTo,
3
10
  gist,
@@ -10,18 +17,11 @@ import {
10
17
  } from '@stats-forge/github-stats-forge-core/api';
11
18
  import type { ApiResult, CardConfig } from '@stats-forge/github-stats-forge-core/api';
12
19
 
13
- /**
14
- * @file What each card accepts, in the order the prompts walk it.
15
- *
16
- * The core schemas validate these params;
17
- * this catalog is what makes them navigable, so it carries the prose and the choices a schema has no room for.
18
- */
19
-
20
20
  /** How a param is asked for, and how the answer becomes a query string value. */
21
21
  type OptionKind = 'text' | 'boolean' | 'number' | 'list' | 'choice';
22
22
 
23
23
  /** One param of one card. */
24
- export interface CardOption {
24
+ export interface CardField {
25
25
  /** Query param the answer is written to. */
26
26
  name: string;
27
27
  /** What the prompt asks. */
@@ -33,14 +33,33 @@ export interface CardOption {
33
33
  hint?: string;
34
34
  }
35
35
 
36
+ /** Which section of the option menu a param sits in. */
37
+ export type OptionGroup = 'data' | 'display' | 'text' | 'colors';
38
+
39
+ /**
40
+ * The menu's sections, in the order it shows them.
41
+ * A section no option sits under is dropped, so a card that counts nothing has none.
42
+ */
43
+ export const OPTION_GROUPS: ReadonlyArray<{ group: OptionGroup; label: string }> = [
44
+ { group: 'data', label: 'What it counts' },
45
+ { group: 'display', label: 'What it shows' },
46
+ { group: 'text', label: 'Text and size' },
47
+ { group: 'colors', label: 'Colors and border' },
48
+ ];
49
+
50
+ /** One optional param of one card. It is required so a new option cannot go ungrouped. */
51
+ export interface CardOption extends CardField {
52
+ group: OptionGroup;
53
+ }
54
+
36
55
  /** A card, its params, and the core handler that renders it. */
37
56
  export interface CardKind {
38
57
  id: string;
39
58
  label: string;
40
59
  /** Whether rendering it calls the GitHub API, and so needs a token. */
41
60
  needsToken: boolean;
42
- /** Asked first: the card renders nothing without them. */
43
- required: ReadonlyArray<CardOption>;
61
+ /** Asked first, before the menu opens, so no section applies to them. */
62
+ required: ReadonlyArray<CardField>;
44
63
  /** Everything else, navigable in any order. */
45
64
  options: ReadonlyArray<CardOption>;
46
65
  /** @returns The rendered card, or the rendered error. */
@@ -55,6 +74,7 @@ export const COMMON_OPTIONS: ReadonlyArray<CardOption> = [
55
74
  name: 'theme',
56
75
  label: 'Theme',
57
76
  kind: 'choice',
77
+ group: 'colors',
58
78
  choices: THEME_NAMES,
59
79
  hint: 'An unknown name falls back to the default theme',
60
80
  },
@@ -62,24 +82,27 @@ export const COMMON_OPTIONS: ReadonlyArray<CardOption> = [
62
82
  name: 'title_color',
63
83
  label: 'Title color',
64
84
  kind: 'text',
85
+ group: 'colors',
65
86
  hint: 'Hex, no #',
66
87
  },
67
- { name: 'text_color', label: 'Text color', kind: 'text', hint: 'Hex, no #' },
68
- { name: 'icon_color', label: 'Icon color', kind: 'text', hint: 'Hex, no #' },
88
+ { name: 'text_color', label: 'Text color', kind: 'text', group: 'colors', hint: 'Hex, no #' },
89
+ { name: 'icon_color', label: 'Icon color', kind: 'text', group: 'colors', hint: 'Hex, no #' },
69
90
  {
70
91
  name: 'bg_color',
71
92
  label: 'Background color',
72
93
  kind: 'text',
94
+ group: 'colors',
73
95
  hint: 'Hex, no #, or a gradient: angle,color,color',
74
96
  },
75
97
  {
76
98
  name: 'border_color',
77
99
  label: 'Border color',
78
100
  kind: 'text',
101
+ group: 'colors',
79
102
  hint: 'Hex, no #',
80
103
  },
81
- { name: 'border_radius', label: 'Border radius', kind: 'number' },
82
- { name: 'hide_border', label: 'Hide the border', kind: 'boolean' },
104
+ { name: 'border_radius', label: 'Border radius', kind: 'number', group: 'colors' },
105
+ { name: 'hide_border', label: 'Hide the border', kind: 'boolean', group: 'colors' },
83
106
  ];
84
107
 
85
108
  /**
@@ -90,8 +113,8 @@ export const COMMON_OPTIONS: ReadonlyArray<CardOption> = [
90
113
  const rangeOptions = (counted: string): ReadonlyArray<CardOption> => {
91
114
  const hint = 'A year, a month or a day: 2024, 2024-03, 2024-03-15';
92
115
  return [
93
- { name: 'from', label: `Count ${counted} from`, kind: 'text', hint },
94
- { name: 'to', label: `Count ${counted} up to`, kind: 'text', hint },
116
+ { name: 'from', label: `Count ${counted} from`, kind: 'text', group: 'data', hint },
117
+ { name: 'to', label: `Count ${counted} up to`, kind: 'text', group: 'data', hint },
95
118
  ];
96
119
  };
97
120
 
@@ -99,6 +122,7 @@ const LOCALE_OPTION: CardOption = {
99
122
  name: 'locale',
100
123
  label: 'Locale',
101
124
  kind: 'text',
125
+ group: 'text',
102
126
  hint: 'Two-letter code, e.g. es',
103
127
  };
104
128
 
@@ -113,76 +137,88 @@ const CARDS: ReadonlyArray<CardKind> = [
113
137
  name: 'show',
114
138
  label: 'Extra stats to show',
115
139
  kind: 'list',
140
+ group: 'display',
116
141
  choices: stats.OPTIONS.show,
117
142
  },
118
143
  {
119
144
  name: 'hide',
120
145
  label: 'Stats to hide',
121
146
  kind: 'list',
147
+ group: 'display',
122
148
  choices: stats.OPTIONS.hide,
123
149
  },
124
- { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean' },
125
- { name: 'hide_rank', label: 'Hide the rank circle', kind: 'boolean' },
150
+ { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean', group: 'display' },
151
+ { name: 'hide_rank', label: 'Hide the rank circle', kind: 'boolean', group: 'display' },
126
152
  {
127
153
  name: 'rank_icon',
128
154
  label: 'Rank indicator',
129
155
  kind: 'choice',
156
+ group: 'display',
130
157
  choices: stats.OPTIONS.rank_icon,
131
158
  },
132
159
  {
133
160
  name: 'include_all_commits',
134
161
  label: 'Count commits of all time',
135
162
  kind: 'boolean',
163
+ group: 'data',
136
164
  },
137
165
  ...rangeOptions('commits'),
138
166
  {
139
167
  name: 'exclude_repo',
140
168
  label: 'Repositories to exclude',
141
169
  kind: 'list',
170
+ group: 'data',
142
171
  },
143
172
  {
144
173
  name: 'repo',
145
174
  label: 'Repositories the search-based stats are scoped to',
146
175
  kind: 'list',
176
+ group: 'data',
147
177
  },
148
178
  {
149
179
  name: 'owner',
150
180
  label: 'Owners the search-based stats are scoped to',
151
181
  kind: 'list',
182
+ group: 'data',
152
183
  },
153
184
  {
154
185
  name: 'role',
155
186
  label: 'Owner affiliations to include',
156
187
  kind: 'list',
188
+ group: 'data',
157
189
  choices: stats.OPTIONS.role,
158
190
  },
159
191
  {
160
192
  name: 'contribs_include_own_repos',
161
193
  label: 'Count contributions to your own repositories',
162
194
  kind: 'boolean',
195
+ group: 'data',
163
196
  },
164
- { name: 'custom_title', label: 'Card title', kind: 'text' },
165
- { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
166
- { name: 'card_width', label: 'Card width', kind: 'number' },
167
- { name: 'line_height', label: 'Line height', kind: 'number' },
168
- { name: 'text_bold', label: 'Bold stat values', kind: 'boolean' },
197
+ { name: 'custom_title', label: 'Card title', kind: 'text', group: 'text' },
198
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean', group: 'text' },
199
+ { name: 'card_width', label: 'Card width', kind: 'number', group: 'text' },
200
+ { name: 'line_height', label: 'Line height', kind: 'number', group: 'text' },
201
+ { name: 'text_bold', label: 'Bold stat values', kind: 'boolean', group: 'text' },
169
202
  {
170
203
  name: 'number_format',
171
204
  label: 'Number format',
172
205
  kind: 'choice',
206
+ group: 'text',
173
207
  choices: stats.OPTIONS.number_format,
174
208
  },
175
209
  {
176
210
  name: 'number_precision',
177
211
  label: 'Decimals kept when abbreviating',
178
212
  kind: 'number',
213
+ group: 'text',
179
214
  },
180
215
  {
181
216
  name: 'disable_animations',
182
217
  label: 'Disable the animations',
183
218
  kind: 'boolean',
219
+ group: 'text',
184
220
  },
185
- { name: 'ring_color', label: 'Rank ring color', kind: 'text' },
221
+ { name: 'ring_color', label: 'Rank ring color', kind: 'text', group: 'colors' },
186
222
  LOCALE_OPTION,
187
223
  ],
188
224
  render: stats,
@@ -197,51 +233,59 @@ const CARDS: ReadonlyArray<CardKind> = [
197
233
  name: 'layout',
198
234
  label: 'Layout',
199
235
  kind: 'choice',
236
+ group: 'display',
200
237
  choices: topLangs.OPTIONS.layout,
201
238
  },
202
- { name: 'langs_count', label: 'Languages to show', kind: 'number' },
203
- { name: 'hide', label: 'Languages to hide', kind: 'list' },
204
- { name: 'exclude_repo', label: 'Repositories to exclude', kind: 'list' },
239
+ { name: 'langs_count', label: 'Languages to show', kind: 'number', group: 'data' },
240
+ { name: 'hide', label: 'Languages to hide', kind: 'list', group: 'data' },
241
+ { name: 'exclude_repo', label: 'Repositories to exclude', kind: 'list', group: 'data' },
205
242
  {
206
243
  name: 'size_weight',
207
244
  label: "Weight given to a language's size",
208
245
  kind: 'number',
246
+ group: 'data',
209
247
  },
210
248
  {
211
249
  name: 'count_weight',
212
250
  label: 'Weight given to its repository count',
213
251
  kind: 'number',
252
+ group: 'data',
214
253
  },
215
254
  {
216
255
  name: 'stats_format',
217
256
  label: 'Show values as',
218
257
  kind: 'choice',
258
+ group: 'display',
219
259
  choices: topLangs.OPTIONS.stats_format,
220
260
  },
221
261
  {
222
262
  name: 'hide_progress',
223
263
  label: 'Hide the progress bars',
224
264
  kind: 'boolean',
265
+ group: 'display',
225
266
  },
226
- { name: 'hide_values', label: 'Hide the values', kind: 'boolean' },
267
+ { name: 'hide_values', label: 'Hide the values', kind: 'boolean', group: 'display' },
227
268
  {
228
269
  name: 'prog_bar_bg_color',
229
270
  label: 'Progress bar background color',
230
271
  kind: 'text',
272
+ group: 'colors',
231
273
  },
232
274
  {
233
275
  name: 'role',
234
276
  label: 'Owner affiliations to include',
235
277
  kind: 'list',
278
+ group: 'data',
236
279
  choices: topLangs.OPTIONS.role,
237
280
  },
238
- { name: 'custom_title', label: 'Card title', kind: 'text' },
239
- { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
240
- { name: 'card_width', label: 'Card width', kind: 'number' },
281
+ { name: 'custom_title', label: 'Card title', kind: 'text', group: 'text' },
282
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean', group: 'text' },
283
+ { name: 'card_width', label: 'Card width', kind: 'number', group: 'text' },
241
284
  {
242
285
  name: 'disable_animations',
243
286
  label: 'Disable the animations',
244
287
  kind: 'boolean',
288
+ group: 'text',
245
289
  },
246
290
  LOCALE_OPTION,
247
291
  ],
@@ -256,31 +300,35 @@ const CARDS: ReadonlyArray<CardKind> = [
256
300
  { name: 'repo', label: 'Repository name', kind: 'text' },
257
301
  ],
258
302
  options: [
259
- { name: 'show_owner', label: 'Show the owner', kind: 'boolean' },
303
+ { name: 'show_owner', label: 'Show the owner', kind: 'boolean', group: 'display' },
260
304
  {
261
305
  name: 'show',
262
306
  label: 'Extra stats to show',
263
307
  kind: 'list',
308
+ group: 'display',
264
309
  choices: pin.OPTIONS.show,
265
310
  },
266
- { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean' },
311
+ { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean', group: 'display' },
267
312
  {
268
313
  name: 'description_lines_count',
269
314
  label: 'Lines the description wraps to',
270
315
  kind: 'number',
316
+ group: 'text',
271
317
  },
272
318
  {
273
319
  name: 'browser_rendering',
274
320
  label: 'Let the browser wrap the description',
275
321
  kind: 'boolean',
322
+ group: 'text',
276
323
  },
277
- { name: 'card_width', label: 'Card width', kind: 'number' },
278
- { name: 'line_height', label: 'Line height', kind: 'number' },
279
- { name: 'text_bold', label: 'Bold stat values', kind: 'boolean' },
324
+ { name: 'card_width', label: 'Card width', kind: 'number', group: 'text' },
325
+ { name: 'line_height', label: 'Line height', kind: 'number', group: 'text' },
326
+ { name: 'text_bold', label: 'Bold stat values', kind: 'boolean', group: 'text' },
280
327
  {
281
328
  name: 'number_format',
282
329
  label: 'Number format',
283
330
  kind: 'choice',
331
+ group: 'text',
284
332
  choices: pin.OPTIONS.number_format,
285
333
  },
286
334
  LOCALE_OPTION,
@@ -297,35 +345,40 @@ const CARDS: ReadonlyArray<CardKind> = [
297
345
  name: 'show',
298
346
  label: 'Extra stats to show',
299
347
  kind: 'list',
348
+ group: 'display',
300
349
  choices: org.OPTIONS.show,
301
350
  },
302
351
  {
303
352
  name: 'hide',
304
353
  label: 'Stats to hide',
305
354
  kind: 'list',
355
+ group: 'display',
306
356
  choices: org.OPTIONS.hide,
307
357
  },
308
- { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean' },
358
+ { name: 'show_icons', label: 'Show the stat icons', kind: 'boolean', group: 'display' },
309
359
  {
310
360
  name: 'hide_description',
311
361
  label: "Hide the organization's description",
312
362
  kind: 'boolean',
363
+ group: 'display',
313
364
  },
314
- { name: 'custom_title', label: 'Card title', kind: 'text' },
315
- { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
316
- { name: 'card_width', label: 'Card width', kind: 'number' },
317
- { name: 'line_height', label: 'Line height', kind: 'number' },
318
- { name: 'text_bold', label: 'Bold stat values', kind: 'boolean' },
365
+ { name: 'custom_title', label: 'Card title', kind: 'text', group: 'text' },
366
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean', group: 'text' },
367
+ { name: 'card_width', label: 'Card width', kind: 'number', group: 'text' },
368
+ { name: 'line_height', label: 'Line height', kind: 'number', group: 'text' },
369
+ { name: 'text_bold', label: 'Bold stat values', kind: 'boolean', group: 'text' },
319
370
  {
320
371
  name: 'number_format',
321
372
  label: 'Number format',
322
373
  kind: 'choice',
374
+ group: 'text',
323
375
  choices: org.OPTIONS.number_format,
324
376
  },
325
377
  {
326
378
  name: 'disable_animations',
327
379
  label: 'Disable the animations',
328
380
  kind: 'boolean',
381
+ group: 'text',
329
382
  },
330
383
  LOCALE_OPTION,
331
384
  ],
@@ -337,16 +390,18 @@ const CARDS: ReadonlyArray<CardKind> = [
337
390
  needsToken: true,
338
391
  required: [{ name: 'username', label: 'GitHub username', kind: 'text' }],
339
392
  options: [
340
- { name: 'repos_count', label: 'Repositories to show', kind: 'number' },
393
+ { name: 'repos_count', label: 'Repositories to show', kind: 'number', group: 'data' },
341
394
  {
342
395
  name: 'include_own_repos',
343
396
  label: 'Include your own repositories',
344
397
  kind: 'boolean',
398
+ group: 'data',
345
399
  },
346
400
  {
347
401
  name: 'exclude_repo',
348
402
  label: 'Repositories to exclude',
349
403
  kind: 'list',
404
+ group: 'data',
350
405
  hint: 'Each one an owner/name, or just the name',
351
406
  },
352
407
  ...rangeOptions('contributions'),
@@ -354,15 +409,17 @@ const CARDS: ReadonlyArray<CardKind> = [
354
409
  name: 'hide_years',
355
410
  label: 'Hide the year marks',
356
411
  kind: 'boolean',
412
+ group: 'display',
357
413
  hint: 'One mark per contribution year, filled for the years that repo got one',
358
414
  },
359
- { name: 'custom_title', label: 'Card title', kind: 'text' },
360
- { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
361
- { name: 'card_width', label: 'Card width', kind: 'number' },
415
+ { name: 'custom_title', label: 'Card title', kind: 'text', group: 'text' },
416
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean', group: 'text' },
417
+ { name: 'card_width', label: 'Card width', kind: 'number', group: 'text' },
362
418
  {
363
419
  name: 'disable_animations',
364
420
  label: 'Disable the animations',
365
421
  kind: 'boolean',
422
+ group: 'text',
366
423
  },
367
424
  LOCALE_OPTION,
368
425
  ],
@@ -374,11 +431,12 @@ const CARDS: ReadonlyArray<CardKind> = [
374
431
  needsToken: true,
375
432
  required: [{ name: 'id', label: 'Gist ID', kind: 'text' }],
376
433
  options: [
377
- { name: 'show_owner', label: 'Show the owner', kind: 'boolean' },
434
+ { name: 'show_owner', label: 'Show the owner', kind: 'boolean', group: 'display' },
378
435
  {
379
436
  name: 'browser_rendering',
380
437
  label: 'Let the browser wrap the description',
381
438
  kind: 'boolean',
439
+ group: 'text',
382
440
  },
383
441
  LOCALE_OPTION,
384
442
  ],
@@ -394,34 +452,39 @@ const CARDS: ReadonlyArray<CardKind> = [
394
452
  name: 'layout',
395
453
  label: 'Layout',
396
454
  kind: 'choice',
455
+ group: 'display',
397
456
  choices: wakatime.OPTIONS.layout,
398
457
  },
399
458
  {
400
459
  name: 'display_format',
401
460
  label: 'Show values as',
402
461
  kind: 'choice',
462
+ group: 'display',
403
463
  choices: wakatime.OPTIONS.display_format,
404
464
  },
405
- { name: 'langs_count', label: 'Languages to show', kind: 'number' },
406
- { name: 'hide', label: 'Languages to hide', kind: 'list' },
465
+ { name: 'langs_count', label: 'Languages to show', kind: 'number', group: 'data' },
466
+ { name: 'hide', label: 'Languages to hide', kind: 'list', group: 'data' },
407
467
  {
408
468
  name: 'hide_progress',
409
469
  label: 'Hide the progress bars',
410
470
  kind: 'boolean',
471
+ group: 'display',
411
472
  },
412
- { name: 'custom_title', label: 'Card title', kind: 'text' },
413
- { name: 'hide_title', label: 'Hide the title', kind: 'boolean' },
414
- { name: 'card_width', label: 'Card width', kind: 'number' },
415
- { name: 'line_height', label: 'Line height', kind: 'number' },
473
+ { name: 'custom_title', label: 'Card title', kind: 'text', group: 'text' },
474
+ { name: 'hide_title', label: 'Hide the title', kind: 'boolean', group: 'text' },
475
+ { name: 'card_width', label: 'Card width', kind: 'number', group: 'text' },
476
+ { name: 'line_height', label: 'Line height', kind: 'number', group: 'text' },
416
477
  {
417
478
  name: 'disable_animations',
418
479
  label: 'Disable the animations',
419
480
  kind: 'boolean',
481
+ group: 'text',
420
482
  },
421
483
  {
422
484
  name: 'api_domain',
423
485
  label: 'WakaTime instance',
424
486
  kind: 'text',
487
+ group: 'data',
425
488
  hint: 'Defaults to wakatime.com',
426
489
  },
427
490
  LOCALE_OPTION,
@@ -430,10 +493,14 @@ const CARDS: ReadonlyArray<CardKind> = [
430
493
  },
431
494
  ];
432
495
 
433
- /** Every card, with the options every card shares appended to its own. */
496
+ /**
497
+ * Every card, with the options every card shares beside its own.
498
+ * The shared ones come first so the theme heads the colors section,
499
+ * which is where a card's own color options land behind it.
500
+ */
434
501
  export const cards: ReadonlyArray<CardKind> = CARDS.map((card) => ({
435
502
  ...card,
436
- options: [...card.options, ...COMMON_OPTIONS],
503
+ options: [...COMMON_OPTIONS, ...card.options],
437
504
  }));
438
505
 
439
506
  /**