@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.
- package/api/build/kit/rank.mjs +21 -4
- package/api/build/kit/words.d.mts +1 -0
- package/api/build/kit/words.mjs +1 -1
- package/api/icon/_adapter.d.mts +3 -2
- package/api/icon/_adapter.mjs +8 -2
- package/api/icon/_authoring.d.mts +1 -0
- package/api/icon/_authoring.mjs +586 -66
- package/api/icon/_default-look.d.mts +155 -0
- package/api/icon/_default-look.mjs +474 -0
- package/api/icon/_records.d.mts +1 -1
- package/api/icon/_records.mjs +1 -0
- package/api/icon/add/add.mjs +224 -38
- package/api/icon/icon.type.d.mts +22 -2
- package/api/icon/icon.type.mjs +6 -2
- package/api/icon/iconAdd.doc.mjs +2 -2
- package/api/icon/iconNewDefinition.doc.mjs +3 -2
- package/api/icon/iconNewSet.doc.mjs +4 -4
- package/api/icon/search/search.mjs +10 -9
- package/api/integration/add-helpers.d.mts +11 -1
- package/api/integration/add-helpers.mjs +5 -2
- package/api/integration/authoring-checks.d.mts +0 -2
- package/api/integration/authoring-checks.mjs +1 -6
- package/api/logger.mjs +2 -1
- package/api/swizzle/_adapter.d.mts +1 -0
- package/api/swizzle/_adapter.mjs +4 -1
- package/api/swizzle/_moduleGraph.d.mts +171 -0
- package/api/swizzle/_moduleGraph.mjs +795 -0
- package/api/swizzle/copy/copy.d.mts +2 -43
- package/api/swizzle/copy/copy.mjs +195 -314
- package/api/swizzle/swizzle.d.mts +0 -2
- package/api/swizzle/swizzle.doc.mjs +9 -4
- package/api/swizzle/swizzle.mjs +3 -7
- package/api/swizzle/swizzle.type.d.mts +2 -1
- package/api/swizzle/swizzle.type.mjs +3 -2
- package/assets/discover/official-packages.json +20 -0
- package/assets/docs/tree/icon-sets.doc.mjs +11 -5
- package/assets/templates/blocks/components/Table/TableRowIndexTable.doc.mjs +1 -1
- package/assets/templates/blocks/components/Table/TableRowIndexTable.tsx +2 -2
- package/assets/templates/pages/course-detail/page.tsx +699 -0
- package/assets/templates/pages/course-detail/template.doc.mjs +37 -0
- package/assets/templates/pages/file-explorer/page.tsx +103 -79
- package/assets/templates/pages/table-grouped/page.tsx +160 -232
- package/assets/templates/pages/table-grouped/template.doc.mjs +0 -2
- package/assets/templates/pages/theme-showcase/page.tsx +724 -792
- package/assets/templates/pages/theme-showcase/template.doc.mjs +4 -5
- package/authoring/doctypes/template/type.ts +1 -0
- package/clients/cli/commands/experimental/icon.mjs +31 -10
- package/clients/cli/commands/experimental-icon-add.doc.mjs +1 -1
- package/clients/cli/commands/experimental-icon-new-definition.doc.mjs +2 -1
- package/clients/cli/commands/experimental-icon-new-set.doc.mjs +2 -2
- package/clients/cli/commands/swizzle.doc.mjs +8 -3
- package/clients/cli/commands/swizzle.mjs +21 -9
- package/foundation/discovery/component-loader.d.mts +1 -1
- package/foundation/discovery/doc-types.mjs +9 -2
- package/foundation/discovery/icon-ranking.d.mts +27 -3
- package/foundation/discovery/icon-ranking.mjs +125 -8
- package/foundation/discovery/icon-set-checks.mjs +44 -35
- package/foundation/discovery/icon-set-discovery.d.mts +13 -0
- package/foundation/discovery/icon-set-discovery.mjs +25 -0
- package/foundation/discovery/icon-set-writer.d.mts +238 -16
- package/foundation/discovery/icon-set-writer.mjs +676 -79
- package/foundation/discovery/module-exports.d.mts +3 -2
- package/foundation/discovery/module-exports.mjs +167 -37
- package/foundation/fs/paths.d.mts +2 -1
- package/foundation/fs/paths.mjs +3 -4
- package/foundation/text/shell-arg.d.mts +14 -0
- package/foundation/text/shell-arg.mjs +17 -0
- package/package.json +6 -6
package/api/icon/add/add.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
astryxIconDefinitions,
|
|
32
32
|
authoring,
|
|
33
33
|
checkNewDefinition,
|
|
34
|
+
definitionNameFromInput,
|
|
34
35
|
closestNames,
|
|
35
36
|
findLocalIconSet,
|
|
36
37
|
iconIdOf,
|
|
@@ -40,12 +41,19 @@ import {
|
|
|
40
41
|
generatedNamesOf,
|
|
41
42
|
generatedOrigins,
|
|
42
43
|
iconModulePackage,
|
|
43
|
-
iconNaming,
|
|
44
44
|
localSourceNote,
|
|
45
45
|
definitionNameOf,
|
|
46
46
|
libraryIcons,
|
|
47
|
-
|
|
47
|
+
moduleNaming,
|
|
48
|
+
iconNamingWithoutLook,
|
|
49
|
+
iconNaming,
|
|
50
|
+
iconExportName,
|
|
51
|
+
sameIconModule,
|
|
52
|
+
splitLook,
|
|
48
53
|
variantExportName,
|
|
54
|
+
answeringMatches,
|
|
55
|
+
checkVariantLook,
|
|
56
|
+
drawnLooks,
|
|
49
57
|
rankIcons,
|
|
50
58
|
resolveIconSource,
|
|
51
59
|
setModuleNote,
|
|
@@ -56,6 +64,8 @@ import {
|
|
|
56
64
|
refuseOutsideProject,
|
|
57
65
|
} from '../_adapter.mjs';
|
|
58
66
|
import {passed, refuseIgnoredPairs} from '../_options.mjs';
|
|
67
|
+
import {logger} from '../../logger.mjs';
|
|
68
|
+
import {shellArg} from '../../../foundation/text/shell-arg.mjs';
|
|
59
69
|
|
|
60
70
|
/** The lowest score a definition match needs: an exact word or name. */
|
|
61
71
|
const FIT_FLOOR = 80;
|
|
@@ -79,7 +89,7 @@ export async function iconAdd(exportName, options) {
|
|
|
79
89
|
from,
|
|
80
90
|
set: selector,
|
|
81
91
|
definition: givenDefinition,
|
|
82
|
-
newDefinition,
|
|
92
|
+
newDefinition: givenNewDefinition,
|
|
83
93
|
description,
|
|
84
94
|
words,
|
|
85
95
|
useDefault = false,
|
|
@@ -88,6 +98,11 @@ export async function iconAdd(exportName, options) {
|
|
|
88
98
|
all = false,
|
|
89
99
|
} = options ?? {};
|
|
90
100
|
refuseOutsideProject(cwd, 'experimental icon add', 'the icon-set package');
|
|
101
|
+
// The docs' kebab-case (`ai-helper`) names the same new definition.
|
|
102
|
+
const newDefinition =
|
|
103
|
+
givenNewDefinition == null
|
|
104
|
+
? undefined
|
|
105
|
+
: definitionNameFromInput(givenNewDefinition);
|
|
91
106
|
// `--all` adds a declared variant to existing icons, so it names no icon
|
|
92
107
|
// and maps nothing; a description and words describe only a new
|
|
93
108
|
// definition (spec:AST-042 FR5).
|
|
@@ -177,14 +192,21 @@ export async function iconAdd(exportName, options) {
|
|
|
177
192
|
const generated = generatedModulePath(set, packageDir);
|
|
178
193
|
const module = set.data.module;
|
|
179
194
|
// A library prefix every icon export shares (`feather` in `featherEye`)
|
|
180
|
-
// is left out of the id and the set's export name, as the bulk wrap does
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
|
|
195
|
+
// is left out of the id and the set's export name, as the bulk wrap does,
|
|
196
|
+
// and so are the look words a library's names end in (`Outline24WGHT500`
|
|
197
|
+
// in `XOutline24WGHT500Icon`). A ./ path is the author's own files, named
|
|
198
|
+
// as written: never a library prefix (`CheckDouble` and
|
|
199
|
+
// `CheckDoubleSolid` share `check`).
|
|
200
|
+
const libraryNaming =
|
|
184
201
|
useDefault || source.module.startsWith('./')
|
|
185
|
-
? []
|
|
186
|
-
:
|
|
202
|
+
? {prefix: [], positions: []}
|
|
203
|
+
: moduleNaming(source.file, {
|
|
204
|
+
adapter: Boolean(set.data.adapter),
|
|
205
|
+
module: source.module,
|
|
206
|
+
progress: line => logger.warn(line),
|
|
207
|
+
});
|
|
187
208
|
const nameWords = iconIdOf(exportName).split('-');
|
|
209
|
+
const shared = libraryNaming.prefix;
|
|
188
210
|
const prefix =
|
|
189
211
|
shared.length > 0 &&
|
|
190
212
|
nameWords.length > shared.length &&
|
|
@@ -199,11 +221,32 @@ export async function iconAdd(exportName, options) {
|
|
|
199
221
|
sourceExport,
|
|
200
222
|
);
|
|
201
223
|
const known = set.data.icons.find(each => already.includes(each.export));
|
|
202
|
-
const
|
|
224
|
+
const glyph = known
|
|
203
225
|
? {id: known.id, name: known.export}
|
|
204
|
-
:
|
|
205
|
-
|
|
226
|
+
: iconNamingWithoutLook(exportName, {
|
|
227
|
+
prefix,
|
|
228
|
+
positions: libraryNaming.positions,
|
|
229
|
+
});
|
|
206
230
|
const asVariant = variant != null;
|
|
231
|
+
// The set may already export the glyph's name for another export of the
|
|
232
|
+
// library (its outline look, when this is the filled one). This export
|
|
233
|
+
// then keeps its look words in its id and name, or takes its name from
|
|
234
|
+
// --id, and the listing offers it as a variant of that icon instead.
|
|
235
|
+
const origin = generatedOrigins(packageDir, generated).get(glyph.name);
|
|
236
|
+
const glyphTaken =
|
|
237
|
+
!known &&
|
|
238
|
+
!asVariant &&
|
|
239
|
+
origin != null &&
|
|
240
|
+
!(
|
|
241
|
+
sameIconModule(origin.module, source.module) &&
|
|
242
|
+
origin.source === sourceExport
|
|
243
|
+
);
|
|
244
|
+
const naming = !glyphTaken
|
|
245
|
+
? glyph
|
|
246
|
+
: givenId != null
|
|
247
|
+
? {id: givenId, name: nameFromId(givenId)}
|
|
248
|
+
: iconNaming(exportName, prefix);
|
|
249
|
+
const id = givenId ?? naming.id;
|
|
207
250
|
// The name the set exports it as: `<Name>Icon`, or for a variant the
|
|
208
251
|
// icon's name with the variant's name.
|
|
209
252
|
const baseIcon = set.data.icons.find(each => each.id === id);
|
|
@@ -219,7 +262,20 @@ export async function iconAdd(exportName, options) {
|
|
|
219
262
|
}
|
|
220
263
|
|
|
221
264
|
if (definition == null && newDefinition == null && !asVariant) {
|
|
222
|
-
return definitionMatches(set, exportName, from, id
|
|
265
|
+
return definitionMatches(set, exportName, from, id, {
|
|
266
|
+
useDefault,
|
|
267
|
+
id: givenId,
|
|
268
|
+
look:
|
|
269
|
+
glyphTaken && origin != null
|
|
270
|
+
? lookVariantOf(
|
|
271
|
+
iconNaming(exportName, prefix).id,
|
|
272
|
+
iconNaming(origin.source, prefix).id,
|
|
273
|
+
libraryNaming.positions,
|
|
274
|
+
set,
|
|
275
|
+
)
|
|
276
|
+
: null,
|
|
277
|
+
glyphId: glyph.id,
|
|
278
|
+
});
|
|
223
279
|
}
|
|
224
280
|
|
|
225
281
|
/** @type {{name: string, description: string, words: string[]} | null} */
|
|
@@ -250,7 +306,7 @@ export async function iconAdd(exportName, options) {
|
|
|
250
306
|
}),
|
|
251
307
|
),
|
|
252
308
|
{
|
|
253
|
-
name: `--new-definition ${definition} --description '<what it is for>' --words a,b`,
|
|
309
|
+
name: `--new-definition ${definitionNameFromInput(definition)} --description '<what it is for>' --words a,b`,
|
|
254
310
|
reason: 'add it as your own definition',
|
|
255
311
|
},
|
|
256
312
|
],
|
|
@@ -262,6 +318,15 @@ export async function iconAdd(exportName, options) {
|
|
|
262
318
|
const icons = set.data.icons.map(each => ({...each}));
|
|
263
319
|
const existing = icons.find(each => each.id === id);
|
|
264
320
|
if (asVariant) {
|
|
321
|
+
checkVariantLook({
|
|
322
|
+
set,
|
|
323
|
+
variant,
|
|
324
|
+
module: source.module,
|
|
325
|
+
positions: libraryNaming.positions,
|
|
326
|
+
drawn: drawnLooks(set, packageDir, each =>
|
|
327
|
+
sameIconModule(each, source.module) ? libraryNaming.positions : null,
|
|
328
|
+
),
|
|
329
|
+
});
|
|
265
330
|
if (!existing) {
|
|
266
331
|
throw new AstryxError(
|
|
267
332
|
`Icon set "${set.name}" has no icon "${id}" to add a variant to. Add the icon first, or pass --id.`,
|
|
@@ -275,6 +340,18 @@ export async function iconAdd(exportName, options) {
|
|
|
275
340
|
ERROR_CODES.ERR_NOT_FOUND,
|
|
276
341
|
);
|
|
277
342
|
}
|
|
343
|
+
const own = generatedOrigins(packageDir, generated).get(existing.export);
|
|
344
|
+
if (
|
|
345
|
+
own != null &&
|
|
346
|
+
own.source === sourceExport &&
|
|
347
|
+
sameIconModule(own.module, source.module)
|
|
348
|
+
) {
|
|
349
|
+
throw new AstryxError(
|
|
350
|
+
`"${sourceExport}" is icon "${id}"'s own export, which draws its default look, so it can't be its ${variant} variant too.`,
|
|
351
|
+
undefined,
|
|
352
|
+
ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
278
355
|
const variants = [...(existing.variants ?? [])];
|
|
279
356
|
const same = variants.find(each => each.export === setName);
|
|
280
357
|
if (!same) {
|
|
@@ -394,9 +471,13 @@ export async function iconAdd(exportName, options) {
|
|
|
394
471
|
* @param {string} exportName
|
|
395
472
|
* @param {string} from
|
|
396
473
|
* @param {string} id
|
|
474
|
+
* @param {{useDefault?: boolean, id?: string, look?: string | null, glyphId?: string}} passed the
|
|
475
|
+
* options the author passed, repeated in each listed command so it runs as
|
|
476
|
+
* printed; `look`, when the set already has an icon for the export's
|
|
477
|
+
* glyph (`glyphId`): the variant it is of that icon
|
|
397
478
|
* @returns {import('../icon.type.mjs').IconDefinitionMatchesResponse}
|
|
398
479
|
*/
|
|
399
|
-
function definitionMatches(set, exportName, from, id) {
|
|
480
|
+
function definitionMatches(set, exportName, from, id, passed = {}) {
|
|
400
481
|
const astryx = astryxIconDefinitions().definitions.map(each => ({
|
|
401
482
|
doc: each.doc,
|
|
402
483
|
source: /** @type {const} */ ('astryx'),
|
|
@@ -432,14 +513,19 @@ function definitionMatches(set, exportName, from, id) {
|
|
|
432
513
|
/** @type {Map<string, {score: number, reason: string}>} */
|
|
433
514
|
const best = new Map();
|
|
434
515
|
for (const query of queries) {
|
|
435
|
-
for (const match of rankIcons([asSet], query)) {
|
|
516
|
+
for (const match of answeringMatches(rankIcons([asSet], query))) {
|
|
436
517
|
const prior = best.get(match.export);
|
|
437
518
|
if (!prior || match.score > prior.score) {
|
|
438
519
|
best.set(match.export, {score: match.score, reason: match.reason});
|
|
439
520
|
}
|
|
440
521
|
}
|
|
441
522
|
}
|
|
442
|
-
const base =
|
|
523
|
+
const base = [
|
|
524
|
+
`astryx experimental icon add ${exportName} --from ${shellArg(from)}`,
|
|
525
|
+
`--set ${shellArg(set.name)}`,
|
|
526
|
+
...(passed.useDefault ? ['--default'] : []),
|
|
527
|
+
...(passed.id != null ? [`--id ${shellArg(passed.id)}`] : []),
|
|
528
|
+
].join(' ');
|
|
443
529
|
const matches = all
|
|
444
530
|
.filter(({doc}) => best.has(doc.name))
|
|
445
531
|
.map(({doc, source}) => {
|
|
@@ -463,6 +549,15 @@ function definitionMatches(set, exportName, from, id) {
|
|
|
463
549
|
)
|
|
464
550
|
.slice(0, MATCH_LIMIT);
|
|
465
551
|
const fits = matches.some(match => match.score >= FIT_FLOOR);
|
|
552
|
+
const asLook =
|
|
553
|
+
passed.look && passed.glyphId
|
|
554
|
+
? [
|
|
555
|
+
`astryx experimental icon add ${exportName} --from ${shellArg(from)}`,
|
|
556
|
+
`--set ${shellArg(set.name)}`,
|
|
557
|
+
...(passed.useDefault ? ['--default'] : []),
|
|
558
|
+
`--id ${passed.glyphId} --variant ${passed.look}`,
|
|
559
|
+
].join(' ')
|
|
560
|
+
: null;
|
|
466
561
|
const name = id.replace(/-([a-z0-9])/gu, (_, c) => c.toUpperCase());
|
|
467
562
|
return {
|
|
468
563
|
type: 'icon.definitionMatches',
|
|
@@ -474,6 +569,7 @@ function definitionMatches(set, exportName, from, id) {
|
|
|
474
569
|
newDefinition: fits
|
|
475
570
|
? null
|
|
476
571
|
: `${base} --new-definition ${/^[a-z]/u.test(name) ? name : `icon${name}`} --description '<what the icon is for>' --words <word>,<word>`,
|
|
572
|
+
...(asLook ? {variant: asLook} : {}),
|
|
477
573
|
},
|
|
478
574
|
};
|
|
479
575
|
}
|
|
@@ -502,29 +598,34 @@ async function addVariantForAll({cwd, from, selector, variant}) {
|
|
|
502
598
|
}
|
|
503
599
|
const where = await findLocalIconSet(cwd, selector);
|
|
504
600
|
const {packageDir, set} = where;
|
|
505
|
-
const declared = set.data.variants ?? [];
|
|
506
|
-
if (!declared.includes(variant)) {
|
|
507
|
-
throw new AstryxError(
|
|
508
|
-
`Icon set "${set.name}" declares no variant "${variant}"${declared.length > 0 ? ` (it declares ${declared.join(', ')})` : ''}. Declare it first.`,
|
|
509
|
-
[
|
|
510
|
-
{
|
|
511
|
-
name: `astryx experimental icon new-set ${set.name} --from <its library> --variants ${[...new Set([...(declared.length > 0 ? declared : ['<default>']), variant])].join(',')}`,
|
|
512
|
-
reason: 'declare the variants',
|
|
513
|
-
},
|
|
514
|
-
],
|
|
515
|
-
ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
if (set.data.default?.variant === variant) {
|
|
519
|
-
throw new AstryxError(
|
|
520
|
-
`"${variant}" is the set's default variant: each icon's own export already draws it.`,
|
|
521
|
-
undefined,
|
|
522
|
-
ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
601
|
const source = resolveIconSource(from, where);
|
|
526
|
-
|
|
602
|
+
// In a library that draws each icon in several looks, the variant is the
|
|
603
|
+
// glyph's look the variant's name picks (`filled`), and an icon is known
|
|
604
|
+
// by any look of its glyph.
|
|
605
|
+
/** @type {Array<Set<string>>} */
|
|
606
|
+
let positions = [];
|
|
607
|
+
const found = libraryIcons(source.file, {
|
|
608
|
+
adapter: Boolean(set.data.adapter),
|
|
609
|
+
prefer: [variant],
|
|
610
|
+
module: source.module,
|
|
611
|
+
progress: line => logger.warn(line),
|
|
612
|
+
summary: each => {
|
|
613
|
+
positions = each.positions.map(words => new Set(words));
|
|
614
|
+
},
|
|
615
|
+
});
|
|
616
|
+
checkVariantLook({
|
|
617
|
+
set,
|
|
618
|
+
variant,
|
|
619
|
+
module: source.module,
|
|
620
|
+
positions,
|
|
621
|
+
drawn: drawnLooks(set, packageDir, each =>
|
|
622
|
+
sameIconModule(each, source.module) ? positions : null,
|
|
623
|
+
),
|
|
624
|
+
});
|
|
527
625
|
const byId = new Map(found.map(each => [each.id, each]));
|
|
626
|
+
const byLook = new Map(
|
|
627
|
+
found.flatMap(each => (each.looks ?? []).map(look => [look, each])),
|
|
628
|
+
);
|
|
528
629
|
// An existing icon is known by the library export it re-exports, then by
|
|
529
630
|
// its own export, then by its id: an icon whose id or name an older rule
|
|
530
631
|
// wrote (`squares-2-x2`, `Squares2X2Icon`) still meets its variant
|
|
@@ -548,6 +649,8 @@ async function addVariantForAll({cwd, from, selector, variant}) {
|
|
|
548
649
|
const already = [];
|
|
549
650
|
/** @type {string[]} */
|
|
550
651
|
const missing = [];
|
|
652
|
+
/** @type {string[]} icons whose own export is the one the variant picks */
|
|
653
|
+
const own = [];
|
|
551
654
|
const icons = set.data.icons.map(icon => {
|
|
552
655
|
const origin = origins.get(icon.export);
|
|
553
656
|
if (origin && iconModulePackage(origin.module) !== variantPackage) {
|
|
@@ -557,6 +660,7 @@ async function addVariantForAll({cwd, from, selector, variant}) {
|
|
|
557
660
|
origin && origin.source !== 'default' ? origin.source : undefined;
|
|
558
661
|
const match =
|
|
559
662
|
(librarySource ? byLibraryExport.get(librarySource) : undefined) ??
|
|
663
|
+
(librarySource ? byLook.get(librarySource) : undefined) ??
|
|
560
664
|
byName.get(icon.export) ??
|
|
561
665
|
byId.get(icon.id);
|
|
562
666
|
if (!match) {
|
|
@@ -567,6 +671,21 @@ async function addVariantForAll({cwd, from, selector, variant}) {
|
|
|
567
671
|
missing.push(icon.id);
|
|
568
672
|
return icon;
|
|
569
673
|
}
|
|
674
|
+
// A glyph the library draws in no look of that name has no variant,
|
|
675
|
+
// and neither has one whose own export is the one that look picks.
|
|
676
|
+
if (match.look && !match.look.includes(variant)) {
|
|
677
|
+
missing.push(icon.id);
|
|
678
|
+
return icon;
|
|
679
|
+
}
|
|
680
|
+
if (
|
|
681
|
+
match.export === librarySource &&
|
|
682
|
+
origin != null &&
|
|
683
|
+
sameIconModule(origin.module, source.module)
|
|
684
|
+
) {
|
|
685
|
+
own.push(icon.id);
|
|
686
|
+
missing.push(icon.id);
|
|
687
|
+
return icon;
|
|
688
|
+
}
|
|
570
689
|
const name = variantExportName(icon.export, variant);
|
|
571
690
|
const variants = [...(icon.variants ?? [])];
|
|
572
691
|
if (variants.some(each => each.variant === variant)) {
|
|
@@ -583,6 +702,29 @@ async function addVariantForAll({cwd, from, selector, variant}) {
|
|
|
583
702
|
added.push(icon.id);
|
|
584
703
|
return {...icon, variants};
|
|
585
704
|
});
|
|
705
|
+
if (
|
|
706
|
+
added.length === 0 &&
|
|
707
|
+
already.length === 0 &&
|
|
708
|
+
own.length > 0 &&
|
|
709
|
+
set.data.default?.variant == null
|
|
710
|
+
) {
|
|
711
|
+
// The module is the one the icons' own exports come from, so the
|
|
712
|
+
// variant names the look they draw now: the set's undeclared default.
|
|
713
|
+
const variants = [
|
|
714
|
+
variant,
|
|
715
|
+
...(set.data.variants ?? []).filter(each => each !== variant),
|
|
716
|
+
];
|
|
717
|
+
throw new AstryxError(
|
|
718
|
+
`"${source.module}" is the module the ${own.length} icons of "${set.name}" come from: its exports are those icons in the look they draw now, so "${variant}" is that look and can't be a variant of them. "${set.name}" declares no default variant; declare "${variant}" as the default first.`,
|
|
719
|
+
[
|
|
720
|
+
{
|
|
721
|
+
name: `astryx experimental icon new-set ${set.name} --variants ${variants.join(',')} --default-variant ${variant}`,
|
|
722
|
+
reason: `declare ${variant}, the look each icon draws, as the default`,
|
|
723
|
+
},
|
|
724
|
+
],
|
|
725
|
+
ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
726
|
+
);
|
|
727
|
+
}
|
|
586
728
|
if (added.length === 0 && already.length === 0) {
|
|
587
729
|
throw new AstryxError(
|
|
588
730
|
`"${source.module}" exports no icon with the id of an icon in "${set.name}". Pass the module that exports the ${variant} variant under the same names.`,
|
|
@@ -627,3 +769,47 @@ async function addVariantForAll({cwd, from, selector, variant}) {
|
|
|
627
769
|
},
|
|
628
770
|
};
|
|
629
771
|
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* The set's export name for an id: `x-filled` is `XFilledIcon`.
|
|
775
|
+
* @param {string} id
|
|
776
|
+
*/
|
|
777
|
+
function nameFromId(id) {
|
|
778
|
+
return iconExportName(
|
|
779
|
+
id
|
|
780
|
+
.split('-')
|
|
781
|
+
.map(word => word[0].toUpperCase() + word.slice(1))
|
|
782
|
+
.join(''),
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* The variant an export of a library of looks is of the icon the set
|
|
788
|
+
* already wraps for its glyph: its word at the first look position, in name
|
|
789
|
+
* order, where it differs from the wrapped export. That word is a variant
|
|
790
|
+
* only when the set declares it and it is not the default, which the icon's
|
|
791
|
+
* own export draws. Otherwise null, so the listing offers no variant
|
|
792
|
+
* command that the set's data would refuse.
|
|
793
|
+
* @param {string} id the export's id, look words kept
|
|
794
|
+
* @param {string} wrappedId the wrapped export's id, look words kept
|
|
795
|
+
* @param {Array<Set<string>>} positions the library's look positions, the
|
|
796
|
+
* last word first
|
|
797
|
+
* @param {import('../../../foundation/discovery/icon-set-discovery.mjs').DiscoveredIconSet} set
|
|
798
|
+
* @returns {string | null}
|
|
799
|
+
*/
|
|
800
|
+
function lookVariantOf(id, wrappedId, positions, set) {
|
|
801
|
+
const {look} = splitLook(id.split('-'), positions);
|
|
802
|
+
const {look: wrapped} = splitLook(wrappedId.split('-'), positions);
|
|
803
|
+
let word = null;
|
|
804
|
+
for (let index = look.length - 1; index >= 0; index--) {
|
|
805
|
+
if (look[index] !== wrapped[index]) {
|
|
806
|
+
word = look[index];
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return word != null &&
|
|
811
|
+
(set.data.variants ?? []).includes(word) &&
|
|
812
|
+
word !== set.data.default?.variant
|
|
813
|
+
? word
|
|
814
|
+
: null;
|
|
815
|
+
}
|
package/api/icon/icon.type.d.mts
CHANGED
|
@@ -260,7 +260,7 @@ export type IconAddOptions = {
|
|
|
260
260
|
*/
|
|
261
261
|
definition?: string | undefined;
|
|
262
262
|
/**
|
|
263
|
-
* - Add the set's own definition with this name and map the icon to it.
|
|
263
|
+
* - Add the set's own definition with this name (camelCase, or kebab-case written as camelCase) and map the icon to it.
|
|
264
264
|
*/
|
|
265
265
|
newDefinition?: string | undefined;
|
|
266
266
|
/**
|
|
@@ -354,6 +354,7 @@ export type IconDefinitionMatchesResponse = {
|
|
|
354
354
|
id: string;
|
|
355
355
|
matches: IconDefinitionMatch[];
|
|
356
356
|
newDefinition: string | null;
|
|
357
|
+
variant?: string | undefined;
|
|
357
358
|
};
|
|
358
359
|
};
|
|
359
360
|
/**
|
|
@@ -438,7 +439,7 @@ export type IconNewSetOptions = {
|
|
|
438
439
|
*/
|
|
439
440
|
variants?: string | string[] | undefined;
|
|
440
441
|
/**
|
|
441
|
-
* - With `variants`: the variant each icon's own export draws; the first variant
|
|
442
|
+
* - With `variants`: the variant each icon's own export draws; when absent, the look the icons' exports draw in a library whose names encode looks; otherwise the set's current default, else, on a set without icons, the first variant. A set with icons changes it only with `from`, and only in a library whose names encode looks.
|
|
442
443
|
*/
|
|
443
444
|
defaultVariant?: string | undefined;
|
|
444
445
|
/**
|
|
@@ -511,6 +512,25 @@ export type IconNewData = {
|
|
|
511
512
|
* - With `from` on an existing set: the set was refreshed, not created.
|
|
512
513
|
*/
|
|
513
514
|
refreshed?: boolean | undefined;
|
|
515
|
+
/**
|
|
516
|
+
* - Without `from` on an existing set: its variants, default variant, display name, or description were updated in place, not created.
|
|
517
|
+
*/
|
|
518
|
+
updated?: boolean | undefined;
|
|
519
|
+
/**
|
|
520
|
+
* - On a refresh from a package root: the icons that read one file of that package (`from`) and now import from the root (`to`); each keeps its id, export, `map` entries, variants, and words.
|
|
521
|
+
*/
|
|
522
|
+
moved?: {
|
|
523
|
+
id: string;
|
|
524
|
+
from: string;
|
|
525
|
+
to: string;
|
|
526
|
+
}[] | undefined;
|
|
527
|
+
/**
|
|
528
|
+
* - With `from`: the wrapped library (`name@version`) ships no license file to copy and the set has none. `path` is where to add a NOTICE file that names the library and its license; the CLI never writes it.
|
|
529
|
+
*/
|
|
530
|
+
licenseNoticeMissing?: {
|
|
531
|
+
library: string;
|
|
532
|
+
path: string;
|
|
533
|
+
} | undefined;
|
|
514
534
|
/**
|
|
515
535
|
* - On a refresh: the icons whose id this release's id rule changes, matched by the export the library still has; each keeps its `map` entries, variants, and words.
|
|
516
536
|
*/
|
package/api/icon/icon.type.mjs
CHANGED
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
* @property {string} from - The module the icon is exported from: a package, a package subpath, or a relative path in this package.
|
|
184
184
|
* @property {string} [set] - The set to edit, when the package ships several.
|
|
185
185
|
* @property {string} [definition] - The icon definition to map the icon to.
|
|
186
|
-
* @property {string} [newDefinition] - Add the set's own definition with this name and map the icon to it.
|
|
186
|
+
* @property {string} [newDefinition] - Add the set's own definition with this name (camelCase, or kebab-case written as camelCase) and map the icon to it.
|
|
187
187
|
* @property {string} [description] - With `newDefinition`: what the icon is for.
|
|
188
188
|
* @property {string[]} [words] - With `newDefinition`: words people search for.
|
|
189
189
|
* @property {boolean} [useDefault] - The module's default export is the icon.
|
|
@@ -249,6 +249,7 @@
|
|
|
249
249
|
* @property {string} data.id - The id the icon would get.
|
|
250
250
|
* @property {IconDefinitionMatch[]} data.matches - At most 10, best first.
|
|
251
251
|
* @property {string | null} data.newDefinition - The command that adds the set's own definition, when no match fits.
|
|
252
|
+
* @property {string} [data.variant] - When the set already has an icon for the export's glyph from another export of the library: the command that adds this export as that icon's variant.
|
|
252
253
|
*/
|
|
253
254
|
|
|
254
255
|
/**
|
|
@@ -293,7 +294,7 @@
|
|
|
293
294
|
* @property {string} [from] - A module whose every icon component the set wraps: a package, a package subpath, or a ./ path in the package.
|
|
294
295
|
* @property {string[] | string} [exclude] - With `from`: export names that are not icons; a string is split on commas.
|
|
295
296
|
* @property {string[] | string} [variants] - The variants the set supplies, such as `outline,solid`; a string is split on commas.
|
|
296
|
-
* @property {string} [defaultVariant] - With `variants`: the variant each icon's own export draws; the first variant
|
|
297
|
+
* @property {string} [defaultVariant] - With `variants`: the variant each icon's own export draws; when absent, the look the icons' exports draw in a library whose names encode looks; otherwise the set's current default, else, on a set without icons, the first variant. A set with icons changes it only with `from`, and only in a library whose names encode looks.
|
|
297
298
|
* @property {string} [displayName] - The descriptor's display name; the set name in title case when absent.
|
|
298
299
|
* @property {string} [description] - The descriptor's one-line description; `<Display name> icons.` when absent.
|
|
299
300
|
*/
|
|
@@ -324,6 +325,9 @@
|
|
|
324
325
|
* @property {string} [from]
|
|
325
326
|
* @property {number} [icons] - With `from`: how many icons it wrote.
|
|
326
327
|
* @property {boolean} [refreshed] - With `from` on an existing set: the set was refreshed, not created.
|
|
328
|
+
* @property {boolean} [updated] - Without `from` on an existing set: its variants, default variant, display name, or description were updated in place, not created.
|
|
329
|
+
* @property {Array<{id: string, from: string, to: string}>} [moved] - On a refresh from a package root: the icons that read one file of that package (`from`) and now import from the root (`to`); each keeps its id, export, `map` entries, variants, and words.
|
|
330
|
+
* @property {{library: string, path: string}} [licenseNoticeMissing] - With `from`: the wrapped library (`name@version`) ships no license file to copy and the set has none. `path` is where to add a NOTICE file that names the library and its license; the CLI never writes it.
|
|
327
331
|
* @property {Array<{from: string, to: string}>} [renamed] - On a refresh: the icons whose id this release's id rule changes, matched by the export the library still has; each keeps its `map` entries, variants, and words.
|
|
328
332
|
* @property {Array<{definition: string, icon: string}>} [mapped] - With `from`: the `map` entries an icon with the definition's exact name gives (on a refresh, every such entry, kept or new); check each.
|
|
329
333
|
* @property {Array<{definition: string, icon: string, word: string}>} [suggestions] - With `from`: for each unmapped Astryx definition, up to three icons it might map to, best first: the definition's name in another spelling or as another name of an icon, ids that start with it, an id or another name that is one of its words, then two-word ids that are a one-word word sized, shaped, or numbered (`grid-2x2`); never a negated form, the opposite direction, or an icon already mapped. `word` is the name or word that matched. Never written.
|
package/api/icon/iconAdd.doc.mjs
CHANGED
|
@@ -53,7 +53,7 @@ export const doc = {
|
|
|
53
53
|
name: 'options.newDefinition',
|
|
54
54
|
type: 'string',
|
|
55
55
|
description:
|
|
56
|
-
"Add the set's own definition with this name, then map the icon to it.",
|
|
56
|
+
"Add the set's own definition with this name, in camelCase (`aiHelper`) or kebab-case (`ai-helper`), then map the icon to it.",
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
name: 'options.description',
|
|
@@ -119,7 +119,7 @@ export const doc = {
|
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
code: 'ERR_INVALID_ARGUMENT',
|
|
122
|
-
when:
|
|
122
|
+
when: "the export is data rather than a component, a new definition is named like an Astryx one, or the variant is not one the set declares, is the look each icon's own export already draws, or is no look word of a library whose export names encode looks",
|
|
123
123
|
},
|
|
124
124
|
{code: 'ERR_FILE_EXISTS', when: 'another icon already has the id'},
|
|
125
125
|
{
|
|
@@ -19,7 +19,8 @@ export const doc = {
|
|
|
19
19
|
{
|
|
20
20
|
name: 'name',
|
|
21
21
|
type: 'string',
|
|
22
|
-
description:
|
|
22
|
+
description:
|
|
23
|
+
'The definition name, in camelCase (`rocketLaunch`) or kebab-case (`rocket-launch`); it is written in camelCase.',
|
|
23
24
|
required: true,
|
|
24
25
|
},
|
|
25
26
|
{
|
|
@@ -66,7 +67,7 @@ export const doc = {
|
|
|
66
67
|
throws: [
|
|
67
68
|
{
|
|
68
69
|
code: 'ERR_INVALID_ARGUMENT',
|
|
69
|
-
when: 'the name is
|
|
70
|
+
when: 'the name is neither camelCase nor kebab-case, or Astryx already has it',
|
|
70
71
|
},
|
|
71
72
|
{
|
|
72
73
|
code: 'ERR_MISSING_ARGUMENT',
|
|
@@ -10,7 +10,7 @@ export const doc = {
|
|
|
10
10
|
displayName: 'iconNewSet()',
|
|
11
11
|
summary: 'Add an icon set to an integration package.',
|
|
12
12
|
description:
|
|
13
|
-
"Creates an integration manifest when the package has none, writes the set directory with its strongly typed same-stem IconSetDoc and a starter data file (schemaVersion 1, module set to the package name, no icons and an empty map yet), and the set's generated module with its declaration file and an empty themeIcons theme map; with from, it wraps a library's icons into them, records the installed library and version as the data file's source, copies the library's license notices into the set, uses the library's license, maps each Astryx definition whose name is an icon id, and returns up to three likely icons for each other definition as suggestions (by its name, its words, and the other names the library gives an icon; never a part of a word, a negated form, or the opposite direction), never written. It declares the manifest's `experimental.iconSets` root on first use, and includes the manifest and root in package.json files when the package already uses an allowlist. It never writes the package's entry, exports, main, types, files list, peers, or scripts: its notes name each change the package needs as text to copy. Every path is confined to the package and existing
|
|
13
|
+
"Creates an integration manifest when the package has none, writes the set directory with its strongly typed same-stem IconSetDoc and a starter data file (schemaVersion 1, module set to the package name, no icons and an empty map yet), and the set's generated module with its declaration file and an empty themeIcons theme map; with from, it wraps a library's icons into them, records the installed library and version as the data file's source, copies the library's license notices into the set, uses the library's license, maps each Astryx definition whose name is an icon id, and returns up to three likely icons for each other definition as suggestions (by its name, its words, and the other names the library gives an icon; never a part of a word, a negated form, or the opposite direction), never written. A library whose export names end in style, size and weight words gives one icon per glyph, named without those words, in the look the default variant names, else the library's usual default (outline, regular, the middle weight). Over an existing set, `from` refreshes it; without `from`, `variants`, `defaultVariant`, `displayName` and `description` update it in place and leave its icons, map and generated module unchanged (a variant an icon has must stay declared, and a set with icons keeps its default variant). It declares the manifest's `experimental.iconSets` root on first use, and includes the manifest and root in package.json files when the package already uses an allowlist. It never writes the package's entry, exports, main, types, files list, peers, or scripts: its notes name each change the package needs as text to copy. Every path is confined to the package, and an existing set is changed only as above. Experimental: it may change or be removed in a patch release.",
|
|
14
14
|
importPath: '@astryxdesign/cli/api',
|
|
15
15
|
signature:
|
|
16
16
|
'iconNewSet(name: string, options?: IconNewSetOptions): Promise<IconNewSetResponse>',
|
|
@@ -56,7 +56,7 @@ export const doc = {
|
|
|
56
56
|
name: 'options.defaultVariant',
|
|
57
57
|
type: 'string',
|
|
58
58
|
description:
|
|
59
|
-
"With variants: the variant each icon's own export draws, written to the data file's default; the first variant when
|
|
59
|
+
"With variants: the variant each icon's own export draws, written to the data file's default. In a library whose export names encode looks, it must be one of the library's look words, and when absent it is the look the icons' exports draw (with from and no icons yet, the library's usual default), which variants must list. Otherwise, when absent, an existing set keeps its default while variants declares it; a set without icons takes the first variant, and a set with icons and none declared needs it. Without from, a set with icons refuses a default other than the look they draw; when its icons' export names name no looks, it refuses one other than its declared default with from too.",
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
name: 'options.displayName',
|
|
@@ -81,11 +81,11 @@ export const doc = {
|
|
|
81
81
|
throws: [
|
|
82
82
|
{
|
|
83
83
|
code: 'ERR_INVALID_ARGUMENT',
|
|
84
|
-
when: 'the name is not lowercase kebab-case, package.json has no name, or a variant name or the default variant is invalid',
|
|
84
|
+
when: 'the name is not lowercase kebab-case, package.json has no name, or a variant name or the default variant is invalid; the default variant is no look word of a library whose export names encode looks; or the default variant would change on a set that has icons: without `from` (wrap the library again with `from` and `defaultVariant`), or with or without it when the export names of its icons name no looks (add the look as a variant instead)',
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
code: 'ERR_INVALID_OPTION',
|
|
88
|
-
when: '`exclude` is given without `from`, or `defaultVariant` without `variants`',
|
|
88
|
+
when: '`exclude` is given without `from`, or `defaultVariant` without `variants`; `variants` leave out the default variant: the look the icons draw in a library whose export names encode looks, or the declared default of a set with icons whose export names name no looks; or such a set declares no default and `defaultVariant` names none',
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
code: 'ERR_FILE_EXISTS',
|
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
import {AstryxError} from '../../error.mjs';
|
|
13
13
|
import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
|
|
14
14
|
import {loadIconSets} from '../_adapter.mjs';
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
import {
|
|
16
|
+
answeringMatches,
|
|
17
|
+
rankIcons,
|
|
18
|
+
setsNamed,
|
|
19
|
+
unusableSet,
|
|
20
|
+
} from '../_records.mjs';
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Every icon of one set, by id: `--set <name> --all-icons`, so an author can read
|
|
@@ -75,11 +77,10 @@ export async function iconSearch(query, {cwd, set, limit}) {
|
|
|
75
77
|
if (searched.length === 0) throw unusableSet(sets, withdrawn, set);
|
|
76
78
|
}
|
|
77
79
|
const ranked = rankIcons(searched, query);
|
|
78
|
-
// A
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
: ranked;
|
|
80
|
+
// A word start (`start` for `star`) answers only when nothing matches a
|
|
81
|
+
// whole word, and a partial match (`pin` inside `shopping`) only when
|
|
82
|
+
// nothing matches a whole word or a word start.
|
|
83
|
+
const matches = answeringMatches(ranked);
|
|
83
84
|
return {
|
|
84
85
|
type: 'icon.search',
|
|
85
86
|
data: {
|
|
@@ -92,7 +92,7 @@ export function loadCurrentManifest(manifestFile: string, manifestExists: boolea
|
|
|
92
92
|
* @param {string} manifestPath
|
|
93
93
|
* @param {string[]} files
|
|
94
94
|
* @param {boolean} dryRun
|
|
95
|
-
* @returns {{type: 'integration.add', data: Omit<import('./integration-authoring.type.mjs').IntegrationAddData, 'kind'> & {kind: K, icons?: number, refreshed?: boolean, renamed?: Array<{from: string, to: string}>, mapped?: Array<{definition: string, icon: string}>, suggestions?: Array<{definition: string, icon: string, word: string}>, notes?: string[]}}}
|
|
95
|
+
* @returns {{type: 'integration.add', data: Omit<import('./integration-authoring.type.mjs').IntegrationAddData, 'kind'> & {kind: K, icons?: number, refreshed?: boolean, updated?: boolean, renamed?: Array<{from: string, to: string}>, moved?: Array<{id: string, from: string, to: string}>, licenseNoticeMissing?: {library: string, path: string}, mapped?: Array<{definition: string, icon: string}>, suggestions?: Array<{definition: string, icon: string, word: string}>, notes?: string[]}}}
|
|
96
96
|
*/
|
|
97
97
|
export function receipt<K extends import("./integration-authoring.type.mjs").IntegrationAddData["kind"] | import("../icon/icon.type.mjs").IconNewData["kind"]>(kind: K, name: string, root: {
|
|
98
98
|
path: string;
|
|
@@ -103,10 +103,20 @@ export function receipt<K extends import("./integration-authoring.type.mjs").Int
|
|
|
103
103
|
kind: K;
|
|
104
104
|
icons?: number;
|
|
105
105
|
refreshed?: boolean;
|
|
106
|
+
updated?: boolean;
|
|
106
107
|
renamed?: Array<{
|
|
107
108
|
from: string;
|
|
108
109
|
to: string;
|
|
109
110
|
}>;
|
|
111
|
+
moved?: Array<{
|
|
112
|
+
id: string;
|
|
113
|
+
from: string;
|
|
114
|
+
to: string;
|
|
115
|
+
}>;
|
|
116
|
+
licenseNoticeMissing?: {
|
|
117
|
+
library: string;
|
|
118
|
+
path: string;
|
|
119
|
+
};
|
|
110
120
|
mapped?: Array<{
|
|
111
121
|
definition: string;
|
|
112
122
|
icon: string;
|