@astryxdesign/cli 0.7.11 → 0.7.12-canary.ccb4a6d

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 (68) hide show
  1. package/api/build/kit/rank.mjs +21 -4
  2. package/api/build/kit/words.d.mts +1 -0
  3. package/api/build/kit/words.mjs +1 -1
  4. package/api/icon/_adapter.d.mts +3 -2
  5. package/api/icon/_adapter.mjs +8 -2
  6. package/api/icon/_authoring.d.mts +1 -0
  7. package/api/icon/_authoring.mjs +586 -66
  8. package/api/icon/_default-look.d.mts +155 -0
  9. package/api/icon/_default-look.mjs +474 -0
  10. package/api/icon/_records.d.mts +1 -1
  11. package/api/icon/_records.mjs +1 -0
  12. package/api/icon/add/add.mjs +224 -38
  13. package/api/icon/icon.type.d.mts +22 -2
  14. package/api/icon/icon.type.mjs +6 -2
  15. package/api/icon/iconAdd.doc.mjs +2 -2
  16. package/api/icon/iconNewDefinition.doc.mjs +3 -2
  17. package/api/icon/iconNewSet.doc.mjs +4 -4
  18. package/api/icon/search/search.mjs +10 -9
  19. package/api/integration/add-helpers.d.mts +11 -1
  20. package/api/integration/add-helpers.mjs +5 -2
  21. package/api/integration/authoring-checks.d.mts +0 -2
  22. package/api/integration/authoring-checks.mjs +1 -6
  23. package/api/logger.mjs +2 -1
  24. package/api/swizzle/_adapter.d.mts +1 -0
  25. package/api/swizzle/_adapter.mjs +4 -1
  26. package/api/swizzle/_moduleGraph.d.mts +171 -0
  27. package/api/swizzle/_moduleGraph.mjs +795 -0
  28. package/api/swizzle/copy/copy.d.mts +2 -43
  29. package/api/swizzle/copy/copy.mjs +195 -314
  30. package/api/swizzle/swizzle.d.mts +0 -2
  31. package/api/swizzle/swizzle.doc.mjs +9 -4
  32. package/api/swizzle/swizzle.mjs +3 -7
  33. package/api/swizzle/swizzle.type.d.mts +2 -1
  34. package/api/swizzle/swizzle.type.mjs +3 -2
  35. package/assets/discover/official-packages.json +20 -0
  36. package/assets/docs/tree/icon-sets.doc.mjs +11 -5
  37. package/assets/templates/blocks/components/Table/TableRowIndexTable.doc.mjs +1 -1
  38. package/assets/templates/blocks/components/Table/TableRowIndexTable.tsx +2 -2
  39. package/assets/templates/pages/course-detail/page.tsx +699 -0
  40. package/assets/templates/pages/course-detail/template.doc.mjs +37 -0
  41. package/assets/templates/pages/file-explorer/page.tsx +103 -79
  42. package/assets/templates/pages/table-grouped/page.tsx +160 -232
  43. package/assets/templates/pages/table-grouped/template.doc.mjs +0 -2
  44. package/assets/templates/pages/theme-showcase/page.tsx +724 -792
  45. package/assets/templates/pages/theme-showcase/template.doc.mjs +4 -5
  46. package/authoring/doctypes/template/type.ts +1 -0
  47. package/clients/cli/commands/experimental/icon.mjs +31 -10
  48. package/clients/cli/commands/experimental-icon-add.doc.mjs +1 -1
  49. package/clients/cli/commands/experimental-icon-new-definition.doc.mjs +2 -1
  50. package/clients/cli/commands/experimental-icon-new-set.doc.mjs +2 -2
  51. package/clients/cli/commands/swizzle.doc.mjs +8 -3
  52. package/clients/cli/commands/swizzle.mjs +21 -9
  53. package/foundation/discovery/component-loader.d.mts +1 -1
  54. package/foundation/discovery/doc-types.mjs +9 -2
  55. package/foundation/discovery/icon-ranking.d.mts +27 -3
  56. package/foundation/discovery/icon-ranking.mjs +125 -8
  57. package/foundation/discovery/icon-set-checks.mjs +44 -35
  58. package/foundation/discovery/icon-set-discovery.d.mts +13 -0
  59. package/foundation/discovery/icon-set-discovery.mjs +25 -0
  60. package/foundation/discovery/icon-set-writer.d.mts +238 -16
  61. package/foundation/discovery/icon-set-writer.mjs +676 -79
  62. package/foundation/discovery/module-exports.d.mts +3 -2
  63. package/foundation/discovery/module-exports.mjs +167 -37
  64. package/foundation/fs/paths.d.mts +2 -1
  65. package/foundation/fs/paths.mjs +3 -4
  66. package/foundation/text/shell-arg.d.mts +14 -0
  67. package/foundation/text/shell-arg.mjs +17 -0
  68. package/package.json +6 -6
@@ -0,0 +1,155 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in api/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /**
5
+ * The variants and default variant a command declares.
6
+ * @typedef {object} Looks
7
+ * @property {string[]} variants
8
+ * @property {string} default
9
+ * @property {boolean} [named] whether `--default-variant` named the default
10
+ */
11
+ /**
12
+ * What a set's icons draw now, read from the look words of the library
13
+ * exports its generated module re-exports.
14
+ * @typedef {object} DrawnLooks
15
+ * @property {string} module the library most of the set's icons come from
16
+ * @property {Array<Set<string>>} positions that library's look positions,
17
+ * the last word of a name first; empty when its names encode no looks
18
+ * @property {Array<string | null>} words the word most icons' exports have
19
+ * at each position
20
+ * @property {number} icons how many of the set's icons come from it
21
+ */
22
+ /**
23
+ * @typedef {import('../../foundation/discovery/icon-set-discovery.mjs').DiscoveredIconSet} LookSet
24
+ */
25
+ /**
26
+ * A library's look words in the order its names write them, one position
27
+ * after another: `filled or outline; 24; wght300, wght400, wght500`.
28
+ * @param {string[][]} positions each position's words, sorted, the last
29
+ * word of a name first
30
+ */
31
+ export function describedLooks(positions: string[][]): string;
32
+ /**
33
+ * What the set's icons draw now ({@link DrawnLooks}), or null when no icon
34
+ * re-exports a named library export.
35
+ * @param {LookSet} set
36
+ * @param {string} packageDir
37
+ * @param {(module: string) => Array<Set<string>> | null} positionsOf the
38
+ * look positions of a library the set's generated module names
39
+ * @param {{module?: string}} [options] `module`: read only the icons that
40
+ * come from this library
41
+ * @returns {DrawnLooks | null}
42
+ */
43
+ export function drawnLooks(set: LookSet, packageDir: string, positionsOf: (module: string) => Array<Set<string>> | null, options?: {
44
+ module?: string;
45
+ }): DrawnLooks | null;
46
+ /**
47
+ * The variants and default variant to write for a command that declares
48
+ * looks, and whether the set's icons must move to a new look. Every path
49
+ * that writes `default.variant` uses it:
50
+ *
51
+ * - The drawn look is read from every look word of the exports the set
52
+ * wraps (`drawn`), or, with no icons from the library yet, is the look
53
+ * the library is wrapped in by default.
54
+ * - A named default must be a look word the library has.
55
+ * - A named default other than the drawn look moves the icons only on a
56
+ * refresh (`library` given), where each glyph that has the look takes it;
57
+ * in place it is refused with the refresh command.
58
+ * - With none named, the default is the drawn look, which `--variants` must
59
+ * list.
60
+ *
61
+ * A library whose names encode no looks has no look words: there the named
62
+ * variant, the set's current default, or the first variant is the default.
63
+ * Its icons keep their exports on every path, so a set with icons keeps a
64
+ * declared default, `--variants` must list it, and with none declared the
65
+ * command names it.
66
+ * @param {object} args
67
+ * @param {string} args.name the set
68
+ * @param {Looks | null} args.given the variants and default the command names
69
+ * @param {string | undefined} args.current the set's `default.variant`
70
+ * @param {number} args.icons how many icons the set has
71
+ * @param {DrawnLooks | null} args.drawn what the set's icons draw now
72
+ * @param {{module: string, positions: Array<Set<string>>} | null} args.library
73
+ * the library `--from` wraps; null in place
74
+ * @returns {{looks: Looks | null, moved: boolean}}
75
+ */
76
+ export function resolveDefaultLook({ name, given, current, icons, drawn, library, }: {
77
+ name: string;
78
+ given: Looks | null;
79
+ current: string | undefined;
80
+ icons: number;
81
+ drawn: DrawnLooks | null;
82
+ library: {
83
+ module: string;
84
+ positions: Array<Set<string>>;
85
+ } | null;
86
+ }): {
87
+ looks: Looks | null;
88
+ moved: boolean;
89
+ };
90
+ /**
91
+ * The command that declares `variant` beside the set's variants. It names
92
+ * the default variant the set keeps, so it runs as printed.
93
+ * @param {LookSet} set
94
+ * @param {string} variant
95
+ * @param {DrawnLooks | null} drawn
96
+ * @returns {{name: string, reason: string}}
97
+ */
98
+ export function declareVariantSuggestion(set: LookSet, variant: string, drawn: DrawnLooks | null): {
99
+ name: string;
100
+ reason: string;
101
+ };
102
+ /**
103
+ * Check that `add --variant` may add `variant` to the set's icons: a look
104
+ * word the library has, not the look each icon's own export draws (the
105
+ * default, declared or drawn), and declared by the set.
106
+ * @param {object} args
107
+ * @param {LookSet} args.set
108
+ * @param {string} args.variant
109
+ * @param {string} args.module the module the variant comes from
110
+ * @param {Array<Set<string>>} args.positions that module's look positions
111
+ * @param {DrawnLooks | null} args.drawn what the set's icons draw now
112
+ */
113
+ export function checkVariantLook({ set, variant, module, positions, drawn }: {
114
+ set: LookSet;
115
+ variant: string;
116
+ module: string;
117
+ positions: Array<Set<string>>;
118
+ drawn: DrawnLooks | null;
119
+ }): void;
120
+ /**
121
+ * The variants and default variant a command declares.
122
+ */
123
+ export type Looks = {
124
+ variants: string[];
125
+ default: string;
126
+ /**
127
+ * whether `--default-variant` named the default
128
+ */
129
+ named?: boolean | undefined;
130
+ };
131
+ /**
132
+ * What a set's icons draw now, read from the look words of the library
133
+ * exports its generated module re-exports.
134
+ */
135
+ export type DrawnLooks = {
136
+ /**
137
+ * the library most of the set's icons come from
138
+ */
139
+ module: string;
140
+ /**
141
+ * that library's look positions,
142
+ * the last word of a name first; empty when its names encode no looks
143
+ */
144
+ positions: Array<Set<string>>;
145
+ /**
146
+ * the word most icons' exports have
147
+ * at each position
148
+ */
149
+ words: Array<string | null>;
150
+ /**
151
+ * how many of the set's icons come from it
152
+ */
153
+ icons: number;
154
+ };
155
+ export type LookSet = import("../../foundation/discovery/icon-set-discovery.mjs").DiscoveredIconSet;
@@ -0,0 +1,474 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file The one rule for an icon set's default variant: it is the look each
5
+ * icon's own export draws.
6
+ * @input the set's data and generated module, the wrapped library's look
7
+ * words, and the variants and default variant a command names
8
+ * @output the variants and default variant to write, or an AstryxError that
9
+ * names the library's look words, the command that moves the look, or the
10
+ * variant to add when the library's names name no looks
11
+ * @position used by `new-set` (create, refresh, in place) and by
12
+ * `add --variant` (one icon or `--all`); every command that writes or keeps
13
+ * `default.variant`, or adds a look, asks this module first
14
+ */
15
+
16
+ import {AstryxError} from '../error.mjs';
17
+ import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
18
+ import {
19
+ generatedModulePath,
20
+ generatedOrigins,
21
+ iconIdOf,
22
+ preferredLook,
23
+ sameIconModule,
24
+ splitLook,
25
+ } from '../../foundation/discovery/icon-set-writer.mjs';
26
+ import {shellArg} from '../../foundation/text/shell-arg.mjs';
27
+
28
+ /**
29
+ * The variants and default variant a command declares.
30
+ * @typedef {object} Looks
31
+ * @property {string[]} variants
32
+ * @property {string} default
33
+ * @property {boolean} [named] whether `--default-variant` named the default
34
+ */
35
+
36
+ /**
37
+ * What a set's icons draw now, read from the look words of the library
38
+ * exports its generated module re-exports.
39
+ * @typedef {object} DrawnLooks
40
+ * @property {string} module the library most of the set's icons come from
41
+ * @property {Array<Set<string>>} positions that library's look positions,
42
+ * the last word of a name first; empty when its names encode no looks
43
+ * @property {Array<string | null>} words the word most icons' exports have
44
+ * at each position
45
+ * @property {number} icons how many of the set's icons come from it
46
+ */
47
+
48
+ /**
49
+ * @typedef {import('../../foundation/discovery/icon-set-discovery.mjs').DiscoveredIconSet} LookSet
50
+ */
51
+
52
+ /**
53
+ * A library's look words in the order its names write them, one position
54
+ * after another: `filled or outline; 24; wght300, wght400, wght500`.
55
+ * @param {string[][]} positions each position's words, sorted, the last
56
+ * word of a name first
57
+ */
58
+ export function describedLooks(positions) {
59
+ return [...positions]
60
+ .reverse()
61
+ .map(words =>
62
+ words.length > 3
63
+ ? `${words[0]} to ${words.at(-1)}`
64
+ : words.join(words.length === 2 ? ' or ' : ', '),
65
+ )
66
+ .join('; ');
67
+ }
68
+
69
+ /** @param {Array<Set<string>>} positions */
70
+ const sortedWords = positions =>
71
+ positions.map(position => [...position].sort());
72
+
73
+ /**
74
+ * The words of the positions that name a style (`filled`, `outline`), not
75
+ * only numbers (`24`, `wght400`): the words a variant is usually named by.
76
+ * @param {Array<Set<string>>} positions
77
+ */
78
+ function styleWords(positions) {
79
+ return sortedWords(positions)
80
+ .filter(words => words.some(word => !/^[a-z]*\d+$/u.test(word)))
81
+ .reverse()
82
+ .flat();
83
+ }
84
+
85
+ /**
86
+ * The look the set's icons draw at the first position, in name order, whose
87
+ * drawn word is one of `among`; else at the first style position.
88
+ * @param {Array<Set<string>>} positions
89
+ * @param {Array<string | null>} words the drawn word at each position
90
+ * @param {string[]} [among]
91
+ * @returns {string | null}
92
+ */
93
+ function drawnWord(positions, words, among = []) {
94
+ for (let index = positions.length - 1; index >= 0; index--) {
95
+ const word = words[index];
96
+ if (word != null && among.includes(word)) return word;
97
+ }
98
+ for (let index = positions.length - 1; index >= 0; index--) {
99
+ const word = words[index];
100
+ if (
101
+ word != null &&
102
+ [...positions[index]].some(w => !/^[a-z]*\d+$/u.test(w))
103
+ )
104
+ return word;
105
+ }
106
+ return null;
107
+ }
108
+
109
+ /**
110
+ * What the set's icons draw now ({@link DrawnLooks}), or null when no icon
111
+ * re-exports a named library export.
112
+ * @param {LookSet} set
113
+ * @param {string} packageDir
114
+ * @param {(module: string) => Array<Set<string>> | null} positionsOf the
115
+ * look positions of a library the set's generated module names
116
+ * @param {{module?: string}} [options] `module`: read only the icons that
117
+ * come from this library
118
+ * @returns {DrawnLooks | null}
119
+ */
120
+ export function drawnLooks(set, packageDir, positionsOf, options = {}) {
121
+ const origins = generatedOrigins(
122
+ packageDir,
123
+ generatedModulePath(set, packageDir),
124
+ );
125
+ /** @type {Map<string, string[]>} */
126
+ const byModule = new Map();
127
+ for (const icon of set.data.icons) {
128
+ if ((icon.module ?? set.data.module) !== set.data.module) continue;
129
+ const origin = origins.get(icon.export);
130
+ if (!origin || origin.source === 'default') continue;
131
+ if (
132
+ options.module != null &&
133
+ !sameIconModule(origin.module, options.module)
134
+ )
135
+ continue;
136
+ const sources = byModule.get(origin.module) ?? [];
137
+ sources.push(origin.source);
138
+ byModule.set(origin.module, sources);
139
+ }
140
+ const ranked = [...byModule].sort(
141
+ (a, b) =>
142
+ b[1].length - a[1].length || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0),
143
+ );
144
+ if (ranked.length === 0) return null;
145
+ for (const [module, sources] of ranked) {
146
+ const positions = positionsOf(module) ?? [];
147
+ if (positions.length === 0) continue;
148
+ /** @type {Array<Map<string, number>>} */
149
+ const counts = positions.map(() => new Map());
150
+ for (const source of sources) {
151
+ const {look} = splitLook(iconIdOf(source).split('-'), positions);
152
+ look.forEach((word, index) => {
153
+ if (word != null) {
154
+ counts[index].set(word, (counts[index].get(word) ?? 0) + 1);
155
+ }
156
+ });
157
+ }
158
+ const words = counts.map(count => {
159
+ /** @type {string | null} */
160
+ let best = null;
161
+ let most = 0;
162
+ for (const [word, n] of [...count].sort((a, b) =>
163
+ a[0] < b[0] ? -1 : 1,
164
+ )) {
165
+ if (n > most) {
166
+ best = word;
167
+ most = n;
168
+ }
169
+ }
170
+ return best;
171
+ });
172
+ return {module, positions, words, icons: sources.length};
173
+ }
174
+ const [module, sources] = ranked[0];
175
+ return {module, positions: [], words: [], icons: sources.length};
176
+ }
177
+
178
+ /**
179
+ * The refusal for a look word the library does not have.
180
+ * @param {string} word
181
+ * @param {string} flag `--default-variant` or `--variant`
182
+ * @param {string} module
183
+ * @param {Array<Set<string>>} positions
184
+ */
185
+ function unknownLook(word, flag, module, positions) {
186
+ return new AstryxError(
187
+ `"${word}" is no look of ${module}: its exports name their looks with ${describedLooks(sortedWords(positions))}. Name one of these words, so each icon's export draws that look.`,
188
+ styleWords(positions).map(each => ({
189
+ name: `${flag} ${each}`,
190
+ reason: `a look ${module} names`,
191
+ })),
192
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
193
+ );
194
+ }
195
+
196
+ /**
197
+ * The command that wraps the library again in a new default look.
198
+ * @param {string} name
199
+ * @param {string | undefined} module
200
+ * @param {Looks} looks
201
+ */
202
+ function refreshCommand(name, module, looks) {
203
+ return `astryx experimental icon new-set ${name} --from ${module ? shellArg(module) : '<module>'} --variants ${looks.variants.join(',')} --default-variant ${looks.default}`;
204
+ }
205
+
206
+ /**
207
+ * The refusal for a default variant that would move in place.
208
+ * @param {string} name
209
+ * @param {number} count icons that draw `now`
210
+ * @param {string} now the look they draw
211
+ * @param {Looks} looks
212
+ * @param {string | undefined} module
213
+ * @param {string | undefined} current
214
+ */
215
+ function inPlaceMove(name, count, now, looks, module, current) {
216
+ return new AstryxError(
217
+ `Icon set "${name}" can't ${current != null ? `move its default variant from "${current}" to "${looks.default}"` : `take "${looks.default}" as its default variant`} in place: each of its ${count} icons still draws "${now}". Wrap the library again with the new default, so each icon takes that look.`,
218
+ [
219
+ {
220
+ name: refreshCommand(name, module, looks),
221
+ reason: 'wrap the library again in the new default look',
222
+ },
223
+ ],
224
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
225
+ );
226
+ }
227
+
228
+ /**
229
+ * The refusal for a default variant that would move on a set whose
230
+ * library's export names name no looks. The icons' exports stay as they
231
+ * are on every path, so only another variant gives them another look.
232
+ * @param {string} name
233
+ * @param {number} count the set's icons
234
+ * @param {string} current the set's default variant
235
+ * @param {Looks} looks
236
+ */
237
+ function unencodedMove(name, count, current, looks) {
238
+ const variants = [...new Set([current, ...looks.variants])];
239
+ return new AstryxError(
240
+ `Icon set "${name}" can't move its default variant from "${current}" to "${looks.default}": its icons' export names name no looks, so each of its ${count} icons draws "${current}", the look its icons' module draws. Keep "${current}" as the default, declare "${looks.default}" as a variant, and add it from the module that draws it (fill <module> with that module).`,
241
+ [
242
+ {
243
+ name: `astryx experimental icon new-set ${name} --variants ${variants.join(',')} --default-variant ${current}`,
244
+ reason: `declare ${looks.default} as a variant and keep ${current} as the default`,
245
+ },
246
+ {
247
+ name: `astryx experimental icon add --variant ${looks.default} --from <module> --all --set ${name}`,
248
+ reason: `give each icon its ${looks.default} look from the module that draws it`,
249
+ },
250
+ ],
251
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
252
+ );
253
+ }
254
+
255
+ /**
256
+ * The refusal for `--variants` that leave out the default variant of a set
257
+ * whose library's export names name no looks. Its icons keep their exports,
258
+ * so they still draw the default, and the set must keep declaring it.
259
+ * @param {string} name
260
+ * @param {number} count the set's icons
261
+ * @param {string} current the set's default variant
262
+ * @param {Looks} looks
263
+ * @param {string} from ` --from <module>` on a refresh, else empty
264
+ */
265
+ function droppedDefault(name, count, current, looks, from) {
266
+ const others = looks.variants.filter(variant => variant !== current);
267
+ return new AstryxError(
268
+ `--variants (${looks.variants.join(', ')}) leaves out "${current}", the default variant of icon set "${name}". Its icons' export names name no looks, so each of its ${count} icons still draws "${current}", the look its icons' module draws. List "${current}" in --variants, and add each other look as a variant from the module that draws it (fill <module> with that module).`,
269
+ [
270
+ {
271
+ name: `astryx experimental icon new-set ${name}${from} --variants ${[current, ...others].join(',')}`,
272
+ reason: `keep ${current}, the look each icon draws, as the default`,
273
+ },
274
+ ...others.map(variant => ({
275
+ name: `astryx experimental icon add --variant ${variant} --from <module> --all --set ${name}`,
276
+ reason: `give each icon its ${variant} look from the module that draws it`,
277
+ })),
278
+ ],
279
+ ERROR_CODES.ERR_INVALID_OPTION,
280
+ );
281
+ }
282
+
283
+ /**
284
+ * The variants and default variant to write for a command that declares
285
+ * looks, and whether the set's icons must move to a new look. Every path
286
+ * that writes `default.variant` uses it:
287
+ *
288
+ * - The drawn look is read from every look word of the exports the set
289
+ * wraps (`drawn`), or, with no icons from the library yet, is the look
290
+ * the library is wrapped in by default.
291
+ * - A named default must be a look word the library has.
292
+ * - A named default other than the drawn look moves the icons only on a
293
+ * refresh (`library` given), where each glyph that has the look takes it;
294
+ * in place it is refused with the refresh command.
295
+ * - With none named, the default is the drawn look, which `--variants` must
296
+ * list.
297
+ *
298
+ * A library whose names encode no looks has no look words: there the named
299
+ * variant, the set's current default, or the first variant is the default.
300
+ * Its icons keep their exports on every path, so a set with icons keeps a
301
+ * declared default, `--variants` must list it, and with none declared the
302
+ * command names it.
303
+ * @param {object} args
304
+ * @param {string} args.name the set
305
+ * @param {Looks | null} args.given the variants and default the command names
306
+ * @param {string | undefined} args.current the set's `default.variant`
307
+ * @param {number} args.icons how many icons the set has
308
+ * @param {DrawnLooks | null} args.drawn what the set's icons draw now
309
+ * @param {{module: string, positions: Array<Set<string>>} | null} args.library
310
+ * the library `--from` wraps; null in place
311
+ * @returns {{looks: Looks | null, moved: boolean}}
312
+ */
313
+ export function resolveDefaultLook({
314
+ name,
315
+ given,
316
+ current,
317
+ icons,
318
+ drawn,
319
+ library,
320
+ }) {
321
+ if (!given) return {looks: null, moved: false};
322
+ const refresh = library != null;
323
+ const positions =
324
+ library != null ? library.positions : (drawn?.positions ?? []);
325
+ const module = library?.module ?? drawn?.module;
326
+ const now = drawn != null && drawn.positions.length > 0 ? drawn : null;
327
+ if (positions.length === 0) {
328
+ return unencodedDefault({name, given, current, icons, module, refresh});
329
+ }
330
+ if (given.named) {
331
+ const chosen = given.default;
332
+ const index = positions.findIndex(position => position.has(chosen));
333
+ if (index < 0) {
334
+ throw unknownLook(
335
+ chosen,
336
+ '--default-variant',
337
+ /** @type {string} */ (module),
338
+ positions,
339
+ );
340
+ }
341
+ const drawnHere = now?.words[index] ?? null;
342
+ if (now != null && drawnHere != null && drawnHere !== chosen) {
343
+ if (!refresh) {
344
+ throw inPlaceMove(name, now.icons, drawnHere, given, module, current);
345
+ }
346
+ return {looks: given, moved: true};
347
+ }
348
+ return {looks: given, moved: false};
349
+ }
350
+ const words = now != null ? now.words : preferredLook(positions, []);
351
+ const look = drawnWord(positions, words, given.variants);
352
+ if (look == null || !given.variants.includes(look)) {
353
+ const shown = look ?? '<look>';
354
+ throw new AstryxError(
355
+ `None of --variants (${given.variants.join(', ')}) is the look ${now != null ? `the set's ${now.icons} icons draw` : `${module} is wrapped in by default`} ("${shown}"), and each icon's own export draws the default variant. List it in --variants, or name the default with --default-variant.`,
356
+ [
357
+ {
358
+ name: `astryx experimental icon new-set ${name}${refresh ? ` --from ${shellArg(/** @type {string} */ (module))}` : ''} --variants ${[shown, ...given.variants].join(',')}`,
359
+ reason: `declare ${shown}, the look each icon draws, as the default`,
360
+ },
361
+ ],
362
+ ERROR_CODES.ERR_INVALID_OPTION,
363
+ );
364
+ }
365
+ return {looks: {...given, default: look}, moved: false};
366
+ }
367
+
368
+ /**
369
+ * {@link resolveDefaultLook} for a library whose names encode no looks.
370
+ * @param {{name: string, given: Looks, current: string | undefined, icons: number, module: string | undefined, refresh: boolean}} args
371
+ * @returns {{looks: Looks, moved: boolean}}
372
+ */
373
+ function unencodedDefault({name, given, current, icons, module, refresh}) {
374
+ if (given.named) {
375
+ if (current != null && given.default !== current && icons > 0) {
376
+ throw unencodedMove(name, icons, current, given);
377
+ }
378
+ return {looks: given, moved: false};
379
+ }
380
+ if (current != null && given.variants.includes(current)) {
381
+ return {looks: {...given, default: current}, moved: false};
382
+ }
383
+ const from = refresh && module ? ` --from ${shellArg(module)}` : '';
384
+ if (current != null && icons > 0) {
385
+ throw droppedDefault(name, icons, current, given, from);
386
+ }
387
+ if (current == null && icons > 0) {
388
+ throw new AstryxError(
389
+ `Icon set "${name}" declares no default variant, and its icons' export names name none of ${given.variants.join(', ')}. Name the look its ${icons} icons draw with --default-variant.`,
390
+ given.variants.map(variant => ({
391
+ name: `astryx experimental icon new-set ${name}${from} --variants ${given.variants.join(',')} --default-variant ${variant}`,
392
+ reason: `if each icon draws ${variant}`,
393
+ })),
394
+ ERROR_CODES.ERR_INVALID_OPTION,
395
+ );
396
+ }
397
+ return {looks: given, moved: false};
398
+ }
399
+
400
+ /**
401
+ * The default variant a command that declares `variant` beside the set's
402
+ * variants names: the set's own, else the look its icons draw.
403
+ * @param {LookSet} set
404
+ * @param {DrawnLooks | null} drawn
405
+ * @returns {string | null}
406
+ */
407
+ function keptDefault(set, drawn) {
408
+ const current = set.data.default?.variant;
409
+ if (current) return current;
410
+ if (drawn == null || drawn.positions.length === 0) return null;
411
+ return drawnWord(drawn.positions, drawn.words, set.data.variants ?? []);
412
+ }
413
+
414
+ /**
415
+ * The command that declares `variant` beside the set's variants. It names
416
+ * the default variant the set keeps, so it runs as printed.
417
+ * @param {LookSet} set
418
+ * @param {string} variant
419
+ * @param {DrawnLooks | null} drawn
420
+ * @returns {{name: string, reason: string}}
421
+ */
422
+ export function declareVariantSuggestion(set, variant, drawn) {
423
+ const kept = keptDefault(set, drawn) ?? '<default>';
424
+ const declared = set.data.variants ?? [];
425
+ const variants = [
426
+ ...new Set([
427
+ ...(declared.includes(kept) ? [] : [kept]),
428
+ ...declared,
429
+ variant,
430
+ ]),
431
+ ];
432
+ return {
433
+ name: `astryx experimental icon new-set ${set.name} --variants ${variants.join(',')} --default-variant ${kept}`,
434
+ reason:
435
+ kept === '<default>'
436
+ ? 'declare the variants; fill <default> with the look the icons draw'
437
+ : 'declare the variants',
438
+ };
439
+ }
440
+
441
+ /**
442
+ * Check that `add --variant` may add `variant` to the set's icons: a look
443
+ * word the library has, not the look each icon's own export draws (the
444
+ * default, declared or drawn), and declared by the set.
445
+ * @param {object} args
446
+ * @param {LookSet} args.set
447
+ * @param {string} args.variant
448
+ * @param {string} args.module the module the variant comes from
449
+ * @param {Array<Set<string>>} args.positions that module's look positions
450
+ * @param {DrawnLooks | null} args.drawn what the set's icons draw now
451
+ */
452
+ export function checkVariantLook({set, variant, module, positions, drawn}) {
453
+ if (positions.length > 0 && !positions.some(each => each.has(variant))) {
454
+ throw unknownLook(variant, '--variant', module, positions);
455
+ }
456
+ const drawnPositions = drawn?.positions ?? [];
457
+ const index = drawnPositions.findIndex(each => each.has(variant));
458
+ const drawnHere = index >= 0 ? drawn?.words[index] : null;
459
+ if (set.data.default?.variant === variant || drawnHere === variant) {
460
+ throw new AstryxError(
461
+ `"${variant}" is the look each icon's own export in "${set.name}" already draws${set.data.default?.variant === variant ? ' (its default variant)' : ''}, so a variant of it would export the same artwork twice.`,
462
+ undefined,
463
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
464
+ );
465
+ }
466
+ const declared = set.data.variants ?? [];
467
+ if (!declared.includes(variant)) {
468
+ throw new AstryxError(
469
+ `Icon set "${set.name}" declares no variant "${variant}"${declared.length > 0 ? ` (it declares ${declared.join(', ')})` : ''}. Declare it first${keptDefault(set, drawn) == null ? ', and name the look its icons draw now as the default variant in place of <default>' : ''}.`,
470
+ [declareVariantSuggestion(set, variant, drawn)],
471
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
472
+ );
473
+ }
474
+ }
@@ -63,4 +63,4 @@ export type IconSetIcon = import("../../authoring/doctypes/icon-set/type").IconS
63
63
  export type IconRecord = import("./icon.type.mjs").IconRecord;
64
64
  export type IconMatchEntry = import("./icon.type.mjs").IconMatchEntry;
65
65
  import { AstryxError } from '../error.mjs';
66
- export { iconRecord, importLine, leadingReading, matchIcon, rankIcons, scoreIcon } from "./_adapter.mjs";
66
+ export { answeringMatches, iconRecord, importLine, leadingReading, matchIcon, rankIcons, scoreIcon } from "./_adapter.mjs";
@@ -15,6 +15,7 @@ import {AstryxError} from '../error.mjs';
15
15
  import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
16
16
  import {levenshteinDistance} from '../../foundation/text/string-utils.mjs';
17
17
  export {
18
+ answeringMatches,
18
19
  iconRecord,
19
20
  importLine,
20
21
  leadingReading,