@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/_authoring.mjs
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import * as fs from 'node:fs';
|
|
14
14
|
import * as path from 'node:path';
|
|
15
15
|
import {AstryxError} from '../error.mjs';
|
|
16
|
+
import {logger} from '../logger.mjs';
|
|
16
17
|
import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
|
|
17
18
|
import {
|
|
18
19
|
assertWithin,
|
|
@@ -41,8 +42,13 @@ import {
|
|
|
41
42
|
iconSetDataText,
|
|
42
43
|
journalFile,
|
|
43
44
|
librarySource,
|
|
45
|
+
everyLibraryIcon,
|
|
44
46
|
libraryIcons,
|
|
47
|
+
libraryLooks,
|
|
48
|
+
lookPositions,
|
|
49
|
+
moduleNaming,
|
|
45
50
|
nonIconExports,
|
|
51
|
+
packageRootMoves,
|
|
46
52
|
resolveIconSource,
|
|
47
53
|
peerNote,
|
|
48
54
|
typecheckNote,
|
|
@@ -50,6 +56,16 @@ import {
|
|
|
50
56
|
writeGeneratedIcons,
|
|
51
57
|
writeIconSetData,
|
|
52
58
|
} from '../../foundation/discovery/icon-set-writer.mjs';
|
|
59
|
+
import {
|
|
60
|
+
describedLooks,
|
|
61
|
+
drawnLooks,
|
|
62
|
+
resolveDefaultLook,
|
|
63
|
+
} from './_default-look.mjs';
|
|
64
|
+
import {shellArg} from '../../foundation/text/shell-arg.mjs';
|
|
65
|
+
import {
|
|
66
|
+
hasLicenseNotice,
|
|
67
|
+
licenseNoticeFix,
|
|
68
|
+
} from '../../foundation/discovery/icon-set-discovery.mjs';
|
|
53
69
|
import {astryxIconDefinitions} from '../../foundation/discovery/icon-definition-discovery.mjs';
|
|
54
70
|
import {suggestedIcons} from '../../foundation/discovery/icon-set-checks.mjs';
|
|
55
71
|
import {
|
|
@@ -99,7 +115,7 @@ function quoteJs(value) {
|
|
|
99
115
|
*/
|
|
100
116
|
export async function addIconSet(name, options) {
|
|
101
117
|
const {cwd = process.cwd(), dryRun = false, from} = options;
|
|
102
|
-
const
|
|
118
|
+
const given = variantOptions(options);
|
|
103
119
|
const exclude =
|
|
104
120
|
typeof options.exclude === 'string'
|
|
105
121
|
? options.exclude
|
|
@@ -192,7 +208,30 @@ export async function addIconSet(name, options) {
|
|
|
192
208
|
dryRun,
|
|
193
209
|
from,
|
|
194
210
|
exclude,
|
|
195
|
-
looks,
|
|
211
|
+
looks: given,
|
|
212
|
+
describe: {
|
|
213
|
+
displayName: options.displayName,
|
|
214
|
+
description: options.description,
|
|
215
|
+
},
|
|
216
|
+
packageDir,
|
|
217
|
+
rootPath,
|
|
218
|
+
manifestPath: projectPath(path.relative(packageDir, manifestFile)),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// Without --from, an existing set takes new variants, a default variant, a
|
|
222
|
+
// display name or a description in place, and nothing else changes.
|
|
223
|
+
if (
|
|
224
|
+
from == null &&
|
|
225
|
+
fs.existsSync(docFile) &&
|
|
226
|
+
fs.existsSync(dataFile) &&
|
|
227
|
+
(given != null ||
|
|
228
|
+
options.displayName?.trim() ||
|
|
229
|
+
options.description?.trim())
|
|
230
|
+
) {
|
|
231
|
+
return redeclareIconSet(name, {
|
|
232
|
+
cwd,
|
|
233
|
+
dryRun,
|
|
234
|
+
looks: given,
|
|
196
235
|
describe: {
|
|
197
236
|
displayName: options.displayName,
|
|
198
237
|
description: options.description,
|
|
@@ -227,14 +266,48 @@ export async function addIconSet(name, options) {
|
|
|
227
266
|
let skipped = [];
|
|
228
267
|
/** @type {ReturnType<typeof librarySource>} */
|
|
229
268
|
let library = null;
|
|
269
|
+
/** @type {{summary: import('../../foundation/discovery/icon-set-writer.mjs').LibraryLookSummary | null}} */
|
|
270
|
+
const lookFound = {summary: null};
|
|
271
|
+
// Without --from the set has no icons yet, so no look to match.
|
|
272
|
+
let looks =
|
|
273
|
+
from == null
|
|
274
|
+
? resolveDefaultLook({
|
|
275
|
+
name,
|
|
276
|
+
given,
|
|
277
|
+
current: undefined,
|
|
278
|
+
icons: 0,
|
|
279
|
+
drawn: null,
|
|
280
|
+
library: null,
|
|
281
|
+
}).looks
|
|
282
|
+
: null;
|
|
230
283
|
if (from != null) {
|
|
231
284
|
const source = await iconAuthoring(() =>
|
|
232
285
|
resolveIconSource(from, {packageDir, pkg}),
|
|
233
286
|
);
|
|
234
|
-
const found = libraryIcons(source.file, {
|
|
287
|
+
const found = libraryIcons(source.file, {
|
|
288
|
+
exclude,
|
|
289
|
+
// The library's look words decide the default before any glyph is
|
|
290
|
+
// picked, so each icon's export draws it.
|
|
291
|
+
prefer: positions => {
|
|
292
|
+
looks = resolveDefaultLook({
|
|
293
|
+
name,
|
|
294
|
+
given,
|
|
295
|
+
current: undefined,
|
|
296
|
+
icons: 0,
|
|
297
|
+
drawn: null,
|
|
298
|
+
library: {module: source.module, positions},
|
|
299
|
+
}).looks;
|
|
300
|
+
return looks ? [looks.default] : [];
|
|
301
|
+
},
|
|
302
|
+
module: source.module,
|
|
303
|
+
progress: line => logger.warn(line),
|
|
304
|
+
summary: each => {
|
|
305
|
+
lookFound.summary = each;
|
|
306
|
+
},
|
|
307
|
+
});
|
|
235
308
|
if (found.length === 0) {
|
|
236
309
|
throw new AstryxError(
|
|
237
|
-
`"${from}" exports no icon components this command can read. If its icons are data (objects or SVG strings), run \`astryx experimental icon new-set ${name}\`, declare an "adapter" in ${name}.icons.json, and run this command again; or add icons one at a time with \`astryx experimental icon add <Export> --from ${from}\`.`,
|
|
310
|
+
`"${from}" exports no icon components this command can read. If its icons are data (objects or SVG strings), run \`astryx experimental icon new-set ${name}\`, declare an "adapter" in ${name}.icons.json, and run this command again; or add icons one at a time with \`astryx experimental icon add <Export> --from ${from} --set ${name}\`.`,
|
|
238
311
|
undefined,
|
|
239
312
|
ERROR_CODES.ERR_NOT_FOUND,
|
|
240
313
|
);
|
|
@@ -413,6 +486,11 @@ export async function addIconSet(name, options) {
|
|
|
413
486
|
writtenFiles,
|
|
414
487
|
dryRun,
|
|
415
488
|
);
|
|
489
|
+
const missingNotice = noticeToAdd(
|
|
490
|
+
library,
|
|
491
|
+
packageDir,
|
|
492
|
+
projectPath(path.relative(packageDir, setDir)),
|
|
493
|
+
);
|
|
416
494
|
{
|
|
417
495
|
result.data.icons = icons.length;
|
|
418
496
|
if (from != null) {
|
|
@@ -452,14 +530,220 @@ export async function addIconSet(name, options) {
|
|
|
452
530
|
`Left out ${skipped.length} exports that are not icons: ${skipped.slice(0, 12).join(', ')}${skipped.length > 12 ? ', …' : ''}.`,
|
|
453
531
|
]
|
|
454
532
|
: []),
|
|
455
|
-
...
|
|
456
|
-
|
|
533
|
+
...(from != null && lookFound.summary == null
|
|
534
|
+
? [`Found ${icons.length} icons in ${from}.`]
|
|
535
|
+
: []),
|
|
536
|
+
...(from != null && lookFound.summary != null
|
|
537
|
+
? lookNotes(lookFound.summary, icons.length, from, looks, name)
|
|
538
|
+
: []),
|
|
539
|
+
...(missingNotice ? [missingNotice.note] : []),
|
|
540
|
+
...mappedNotes(map, name),
|
|
541
|
+
...suggestionNotes(suggestions, name),
|
|
457
542
|
'Run `astryx integration check` to see the icon definitions still to map.',
|
|
458
543
|
];
|
|
544
|
+
if (missingNotice) {
|
|
545
|
+
result.data.licenseNoticeMissing = missingNotice.receipt;
|
|
546
|
+
}
|
|
459
547
|
}
|
|
460
548
|
return result;
|
|
461
549
|
}
|
|
462
550
|
|
|
551
|
+
/**
|
|
552
|
+
* The license notice the author must add when the wrapped library ships no
|
|
553
|
+
* license file to copy and the package root has none. The CLI never writes
|
|
554
|
+
* it: the license text is the author's to state. Null when nothing is
|
|
555
|
+
* missing.
|
|
556
|
+
* @param {{package: string, version: string, notices: string[]} | null} library
|
|
557
|
+
* @param {string} packageDir
|
|
558
|
+
* @param {string} setDir the set directory, from the package root
|
|
559
|
+
* @returns {{note: string, receipt: {library: string, path: string}} | null}
|
|
560
|
+
*/
|
|
561
|
+
function noticeToAdd(library, packageDir, setDir) {
|
|
562
|
+
if (!library || library.notices.length > 0) return null;
|
|
563
|
+
if (hasLicenseNotice(packageDir)) return null;
|
|
564
|
+
const named = `${library.package}@${library.version}`;
|
|
565
|
+
return {
|
|
566
|
+
note: `${named} ships no LICENSE or NOTICE file to copy, so the set has no license notice. ${licenseNoticeFix(setDir, named)}`,
|
|
567
|
+
receipt: {library: named, path: `${setDir}/NOTICE`},
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* A set descriptor's source with `--display-name` and `--description`
|
|
573
|
+
* written into its fields; the rest as it was.
|
|
574
|
+
* @param {string} source
|
|
575
|
+
* @param {{displayName?: string, description?: string}} describe
|
|
576
|
+
*/
|
|
577
|
+
function describedDoc(source, describe) {
|
|
578
|
+
let out = source;
|
|
579
|
+
for (const [field, value] of /** @type {const} */ ([
|
|
580
|
+
['displayName', describe.displayName],
|
|
581
|
+
['description', describe.description],
|
|
582
|
+
])) {
|
|
583
|
+
if (!value?.trim()) continue;
|
|
584
|
+
out = out.replace(
|
|
585
|
+
new RegExp(`^(\\s*${field}:\\s*)(['"\`])(?:\\\\.|(?!\\2).)*\\2,?$`, 'mu'),
|
|
586
|
+
(_, lead) => `${lead}${quoteJs(value.trim())},`,
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
return out;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* `new-set <name>` without `--from` over an existing set: declare its
|
|
594
|
+
* variants and default variant, and rewrite its display name and
|
|
595
|
+
* description, in place. Its icons, map and generated module stay as they
|
|
596
|
+
* are. A variant an icon still has must stay declared, and the default
|
|
597
|
+
* variant is one no icon lists as an extra look. A set with icons keeps its
|
|
598
|
+
* default variant: only a refresh with `--from` from a library whose names
|
|
599
|
+
* name its looks moves it. With none declared,
|
|
600
|
+
* its default is the look its icons' exports draw.
|
|
601
|
+
* @param {string} name
|
|
602
|
+
* @param {{cwd: string, dryRun: boolean, looks: Looks | null, describe: {displayName?: string, description?: string}, packageDir: string, rootPath: string, manifestPath: string}} options
|
|
603
|
+
*/
|
|
604
|
+
async function redeclareIconSet(
|
|
605
|
+
name,
|
|
606
|
+
{cwd, dryRun, looks: given, describe, packageDir, rootPath, manifestPath},
|
|
607
|
+
) {
|
|
608
|
+
return iconAuthoring(async () => {
|
|
609
|
+
const where = await findLocalIconSet(cwd, name);
|
|
610
|
+
const {set} = where;
|
|
611
|
+
const current = set.data.default?.variant;
|
|
612
|
+
const drawn =
|
|
613
|
+
given && set.data.icons.length > 0
|
|
614
|
+
? drawnLooks(set, packageDir, module =>
|
|
615
|
+
libraryPositions(module, where, Boolean(set.data.adapter)),
|
|
616
|
+
)
|
|
617
|
+
: null;
|
|
618
|
+
const {looks} = resolveDefaultLook({
|
|
619
|
+
name,
|
|
620
|
+
given,
|
|
621
|
+
current,
|
|
622
|
+
icons: set.data.icons.length,
|
|
623
|
+
drawn,
|
|
624
|
+
library: null,
|
|
625
|
+
});
|
|
626
|
+
if (looks) {
|
|
627
|
+
const declared = new Set(looks.variants);
|
|
628
|
+
const dropped = set.data.icons.flatMap(icon =>
|
|
629
|
+
(icon.variants ?? [])
|
|
630
|
+
.filter(each => !declared.has(each.variant))
|
|
631
|
+
.map(each => `${icon.id} (${each.variant})`),
|
|
632
|
+
);
|
|
633
|
+
if (dropped.length > 0) {
|
|
634
|
+
throw new AstryxError(
|
|
635
|
+
`These icons still have a variant --variants leaves out: ${dropped.slice(0, 8).join(', ')}${dropped.length > 8 ? ', …' : ''}. Keep it in --variants.`,
|
|
636
|
+
[
|
|
637
|
+
{
|
|
638
|
+
name: `--variants ${[...new Set([...looks.variants, ...set.data.icons.flatMap(icon => (icon.variants ?? []).map(each => each.variant))])].join(',')}`,
|
|
639
|
+
reason: 'keep every variant an icon has',
|
|
640
|
+
},
|
|
641
|
+
],
|
|
642
|
+
ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
const asExtra = set.data.icons
|
|
646
|
+
.filter(icon =>
|
|
647
|
+
(icon.variants ?? []).some(each => each.variant === looks.default),
|
|
648
|
+
)
|
|
649
|
+
.map(icon => icon.id);
|
|
650
|
+
if (asExtra.length > 0) {
|
|
651
|
+
throw new AstryxError(
|
|
652
|
+
`"${looks.default}" can't be the default variant: ${asExtra.length} icons list it as another look (${asExtra.slice(0, 8).join(', ')}${asExtra.length > 8 ? ', …' : ''}), and each icon's own export draws the default.`,
|
|
653
|
+
undefined,
|
|
654
|
+
ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const next = {
|
|
659
|
+
...set.data,
|
|
660
|
+
...(looks
|
|
661
|
+
? {variants: looks.variants, default: {variant: looks.default}}
|
|
662
|
+
: {}),
|
|
663
|
+
};
|
|
664
|
+
const docFile = path.join(set.dir, `${set.name}.doc.mjs`);
|
|
665
|
+
const docBefore = fs.readFileSync(docFile, 'utf-8');
|
|
666
|
+
const docAfter = describedDoc(docBefore, describe);
|
|
667
|
+
const dataChanged =
|
|
668
|
+
iconSetDataText(next) !== fs.readFileSync(set.dataPath, 'utf-8');
|
|
669
|
+
/** @type {string[]} */
|
|
670
|
+
const files = [];
|
|
671
|
+
if (dataChanged) {
|
|
672
|
+
files.push(projectPath(path.relative(packageDir, set.dataPath)));
|
|
673
|
+
}
|
|
674
|
+
if (docAfter !== docBefore) {
|
|
675
|
+
files.push(projectPath(path.relative(packageDir, docFile)));
|
|
676
|
+
}
|
|
677
|
+
if (!dryRun) {
|
|
678
|
+
if (dataChanged) writeIconSetData(set, next);
|
|
679
|
+
if (docAfter !== docBefore) {
|
|
680
|
+
journalFile(docFile);
|
|
681
|
+
fs.writeFileSync(docFile, docAfter);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
const result = receipt(
|
|
685
|
+
'icon-set',
|
|
686
|
+
name,
|
|
687
|
+
{path: rootPath, created: false},
|
|
688
|
+
manifestPath,
|
|
689
|
+
files,
|
|
690
|
+
dryRun,
|
|
691
|
+
);
|
|
692
|
+
result.data.icons = set.data.icons.length;
|
|
693
|
+
result.data.updated = true;
|
|
694
|
+
// A look the icons' own library draws comes from that library, so its
|
|
695
|
+
// command runs as printed; any other look comes from a module the
|
|
696
|
+
// author names.
|
|
697
|
+
/** @param {string} variant */
|
|
698
|
+
const fromFor = variant =>
|
|
699
|
+
drawn != null && drawn.positions.some(each => each.has(variant))
|
|
700
|
+
? shellArg(drawn.module)
|
|
701
|
+
: '<module>';
|
|
702
|
+
const added = (looks?.variants ?? []).filter(
|
|
703
|
+
variant =>
|
|
704
|
+
variant !== looks?.default &&
|
|
705
|
+
!(set.data.variants ?? []).includes(variant),
|
|
706
|
+
);
|
|
707
|
+
result.data.notes = [
|
|
708
|
+
files.length === 0
|
|
709
|
+
? `Icon set "${name}" already declares this; nothing changed.`
|
|
710
|
+
: `Updated icon set "${name}" in place; its icons, map and generated module are unchanged.`,
|
|
711
|
+
...(added.length > 0
|
|
712
|
+
? [
|
|
713
|
+
`Give each icon its ${added.join(', ')} look: ${added.map(variant => `\`astryx experimental icon add --variant ${variant} --from ${fromFor(variant)} --all --set ${name}\``).join(', ')}, or one icon with \`astryx experimental icon add <Export> --from <module> --id <icon> --variant <name> --set ${name}\`.`,
|
|
714
|
+
]
|
|
715
|
+
: []),
|
|
716
|
+
];
|
|
717
|
+
return result;
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* The receipt's notes for a library that draws each icon in several looks
|
|
723
|
+
* (a style, a size, a weight): what it found, the look the set took, and
|
|
724
|
+
* how to add another look as a variant.
|
|
725
|
+
* @param {import('../../foundation/discovery/icon-set-writer.mjs').LibraryLookSummary} summary
|
|
726
|
+
* @param {number} count icons in the set
|
|
727
|
+
* @param {string} from
|
|
728
|
+
* @param {{variants: string[], default: string} | null} looks
|
|
729
|
+
* @param {string} name the set's name
|
|
730
|
+
* @returns {string[]}
|
|
731
|
+
*/
|
|
732
|
+
function lookNotes(summary, count, from, looks, name) {
|
|
733
|
+
const described = describedLooks(summary.positions);
|
|
734
|
+
const taken = [...summary.preferred].reverse().join(' ');
|
|
735
|
+
const styles = new Set(summary.positions.flat());
|
|
736
|
+
const others = (looks?.variants ?? []).filter(
|
|
737
|
+
variant => variant !== looks?.default && styles.has(variant),
|
|
738
|
+
);
|
|
739
|
+
return [
|
|
740
|
+
`Found ${count} icons in ${from}, each drawn in several looks (${described}) by ${summary.exports} exports. The set wraps each icon once, in the ${taken} look, named without the look words (\`x\`, \`XIcon\`).`,
|
|
741
|
+
others.length > 0
|
|
742
|
+
? `Add the ${others.join(', ')} look${others.length > 1 ? 's' : ''} as variant${others.length > 1 ? 's' : ''}: ${others.map(variant => `\`astryx experimental icon add --variant ${variant} --from ${from} --all --set ${name}\``).join(', ')}.`
|
|
743
|
+
: `To offer another look as a variant, declare it with \`astryx experimental icon new-set ${name} --variants ${[...summary.preferred].reverse()[0]},<look>\` (no --from: the set's icons stay as they are) and run \`astryx experimental icon add --variant <look> --from ${from} --all --set ${name}\`.`,
|
|
744
|
+
];
|
|
745
|
+
}
|
|
746
|
+
|
|
463
747
|
/**
|
|
464
748
|
* An icon's words from its id: each part, leaving out a bare number
|
|
465
749
|
* (`volume-2` is `volume`) unless the id is only numbers.
|
|
@@ -520,36 +804,40 @@ function definitionSuggestions(icons, map) {
|
|
|
520
804
|
/**
|
|
521
805
|
* The receipt's note for the suggestions, if any.
|
|
522
806
|
* @param {Array<{definition: string, icon: string}>} suggestions
|
|
807
|
+
* @param {string} name the set's name
|
|
523
808
|
*/
|
|
524
|
-
function suggestionNotes(suggestions) {
|
|
809
|
+
function suggestionNotes(suggestions, name) {
|
|
525
810
|
if (suggestions.length === 0) return [];
|
|
526
811
|
const count = new Set(suggestions.map(each => each.definition)).size;
|
|
527
812
|
return [
|
|
528
|
-
`${count} unmapped icon definitions have possible icons (listed under Suggestions; nothing was written for them). Check each meaning, then map the ones that fit: \`astryx experimental icon map <definition> <icon
|
|
813
|
+
`${count} unmapped icon definitions have possible icons (listed under Suggestions; nothing was written for them). Check each meaning, then map the ones that fit: \`astryx experimental icon map <definition> <icon> --set ${name}\`.`,
|
|
529
814
|
];
|
|
530
815
|
}
|
|
531
816
|
|
|
532
817
|
/**
|
|
533
818
|
* The receipt's note for the entries an exact name wrote, if any.
|
|
534
819
|
* @param {Record<string, string>} map
|
|
820
|
+
* @param {string} name the set's name
|
|
535
821
|
*/
|
|
536
|
-
function mappedNotes(map) {
|
|
822
|
+
function mappedNotes(map, name) {
|
|
537
823
|
const count = Object.keys(map).length;
|
|
538
824
|
if (count === 0) return [];
|
|
539
825
|
return [
|
|
540
|
-
`${count} icon definitions were mapped because an icon has their exact name (listed under Mapped by name). A library can draw another meaning under the same name, such as a backspace key named delete: check each, and remap with \`astryx experimental icon map <definition> <icon
|
|
826
|
+
`${count} icon definitions were mapped because an icon has their exact name (listed under Mapped by name). A library can draw another meaning under the same name, such as a backspace key named delete: check each, and remap with \`astryx experimental icon map <definition> <icon> --set ${name}\`.`,
|
|
541
827
|
];
|
|
542
828
|
}
|
|
543
829
|
|
|
830
|
+
/** @typedef {import('./_default-look.mjs').Looks} Looks */
|
|
831
|
+
|
|
544
832
|
/** A variant name: lower-kebab, such as `outline` or `mini-solid`. */
|
|
545
833
|
const VARIANT_NAME_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
|
|
546
834
|
|
|
547
835
|
/**
|
|
548
836
|
* The variants and default variant `--variants` and `--default-variant`
|
|
549
|
-
* declare, or
|
|
550
|
-
*
|
|
837
|
+
* declare, or null when neither is given. Unless named, `default` is the
|
|
838
|
+
* first variant until {@link resolveDefaultLook} decides it.
|
|
551
839
|
* @param {{variants?: string[] | string, defaultVariant?: string}} options
|
|
552
|
-
* @returns {
|
|
840
|
+
* @returns {Looks | null}
|
|
553
841
|
*/
|
|
554
842
|
function variantOptions(options) {
|
|
555
843
|
const given =
|
|
@@ -594,7 +882,28 @@ function variantOptions(options) {
|
|
|
594
882
|
ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
595
883
|
);
|
|
596
884
|
}
|
|
597
|
-
return {variants, default: chosen || variants[0]};
|
|
885
|
+
return {variants, default: chosen || variants[0], named: Boolean(chosen)};
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* The look positions of a library the set's generated module names, or
|
|
890
|
+
* null when it can't be read.
|
|
891
|
+
* @param {string} module
|
|
892
|
+
* @param {{packageDir: string, pkg: Record<string, any>}} where
|
|
893
|
+
* @param {boolean} adapter
|
|
894
|
+
* @returns {Array<Set<string>> | null}
|
|
895
|
+
*/
|
|
896
|
+
function libraryPositions(module, where, adapter) {
|
|
897
|
+
try {
|
|
898
|
+
const source = resolveIconSource(module, where);
|
|
899
|
+
return moduleNaming(source.file, {
|
|
900
|
+
adapter,
|
|
901
|
+
module,
|
|
902
|
+
progress: line => logger.warn(line),
|
|
903
|
+
}).positions;
|
|
904
|
+
} catch {
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
598
907
|
}
|
|
599
908
|
|
|
600
909
|
/**
|
|
@@ -626,13 +935,77 @@ async function iconAuthoring(step) {
|
|
|
626
935
|
}
|
|
627
936
|
}
|
|
628
937
|
|
|
938
|
+
/**
|
|
939
|
+
* Whether an id is one an id rule derives from a library glyph: the glyph id,
|
|
940
|
+
* or its library export, broken into words another way (`squares-2-x2` for
|
|
941
|
+
* `squares-2x2`, `glyph-eye` for `glyphEye`). Any other id is the author's.
|
|
942
|
+
* @param {string} id
|
|
943
|
+
* @param {{id: string, export: string}} glyph
|
|
944
|
+
*/
|
|
945
|
+
function derivedId(id, glyph) {
|
|
946
|
+
const flat = (/** @type {string} */ text) =>
|
|
947
|
+
text.toLowerCase().replace(/[^a-z0-9]/gu, '');
|
|
948
|
+
return [glyph.id, glyph.export, glyph.export.replace(/Icon$/u, '')]
|
|
949
|
+
.map(flat)
|
|
950
|
+
.includes(flat(id));
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Keep the root moves that give each root export to one export of the set.
|
|
955
|
+
* A root export that an export of the set already reads, or that an earlier
|
|
956
|
+
* move takes, stays where it is. The icon that would move keeps its own file,
|
|
957
|
+
* and its variants stay with it. Returns the kept moves and the ids of the
|
|
958
|
+
* icons that stay (with `(<variant>)` for a variant that stays).
|
|
959
|
+
* @param {{data: {module: string, icons: Array<{id: string, export: string, module?: string, variants?: Array<{variant: string, export: string}>}>}}} set
|
|
960
|
+
* @param {Map<string, {module: string, source: string}>} origins
|
|
961
|
+
* @param {string} root
|
|
962
|
+
* @param {Map<string, {source: string, from: string}>} moves
|
|
963
|
+
*/
|
|
964
|
+
function claimRootMoves(set, origins, root, moves) {
|
|
965
|
+
/** @type {Map<string, {source: string, from: string}>} */
|
|
966
|
+
const kept = new Map();
|
|
967
|
+
/** @type {string[]} */
|
|
968
|
+
const skipped = [];
|
|
969
|
+
const claimed = new Set(
|
|
970
|
+
[...origins.values()]
|
|
971
|
+
.filter(origin => sameIconModule(origin.module, root))
|
|
972
|
+
.map(origin => origin.source),
|
|
973
|
+
);
|
|
974
|
+
for (const icon of set.data.icons) {
|
|
975
|
+
if ((icon.module ?? set.data.module) !== set.data.module) continue;
|
|
976
|
+
const own = moves.get(icon.export);
|
|
977
|
+
const origin = origins.get(icon.export);
|
|
978
|
+
const readsRoot = origin != null && sameIconModule(origin.module, root);
|
|
979
|
+
if (own) {
|
|
980
|
+
if (claimed.has(own.source)) {
|
|
981
|
+
skipped.push(icon.id);
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
claimed.add(own.source);
|
|
985
|
+
kept.set(icon.export, own);
|
|
986
|
+
} else if (!readsRoot) continue;
|
|
987
|
+
for (const each of icon.variants ?? []) {
|
|
988
|
+
const move = moves.get(each.export);
|
|
989
|
+
if (!move) continue;
|
|
990
|
+
if (claimed.has(move.source)) {
|
|
991
|
+
skipped.push(`${icon.id} (${each.variant})`);
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
claimed.add(move.source);
|
|
995
|
+
kept.set(each.export, move);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
skipped.sort();
|
|
999
|
+
return {moves: kept, skipped};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
629
1002
|
/**
|
|
630
1003
|
* Wrap a library into an existing set again: its icons are replaced by what
|
|
631
1004
|
* the library exports now (an icon whose words the author extended keeps
|
|
632
1005
|
* them), the generated module gains the new exports, `map` keeps every entry
|
|
633
1006
|
* whose icon still exists, and the receipt names the entries that lost theirs.
|
|
634
1007
|
* @param {string} name
|
|
635
|
-
* @param {{cwd: string, dryRun: boolean, from: string, exclude: string[], looks:
|
|
1008
|
+
* @param {{cwd: string, dryRun: boolean, from: string, exclude: string[], looks: Looks | null, describe: {displayName?: string, description?: string}, packageDir: string, rootPath: string, manifestPath: string}} options
|
|
636
1009
|
*/
|
|
637
1010
|
async function refreshIconSet(
|
|
638
1011
|
name,
|
|
@@ -641,7 +1014,7 @@ async function refreshIconSet(
|
|
|
641
1014
|
dryRun,
|
|
642
1015
|
from,
|
|
643
1016
|
exclude,
|
|
644
|
-
looks,
|
|
1017
|
+
looks: given,
|
|
645
1018
|
describe,
|
|
646
1019
|
packageDir,
|
|
647
1020
|
rootPath,
|
|
@@ -651,24 +1024,73 @@ async function refreshIconSet(
|
|
|
651
1024
|
return iconAuthoring(async () => {
|
|
652
1025
|
const where = await findLocalIconSet(cwd, name);
|
|
653
1026
|
const {set} = where;
|
|
1027
|
+
const current = set.data.default?.variant;
|
|
654
1028
|
const source = resolveIconSource(from, where);
|
|
655
1029
|
// Exports left out before stay out; --exclude adds to them.
|
|
656
1030
|
const excluded = [
|
|
657
1031
|
...new Set([...(set.data.exclude ?? []), ...exclude]),
|
|
658
1032
|
].sort();
|
|
659
|
-
const
|
|
1033
|
+
const everyIcon = everyLibraryIcon(source.file, {
|
|
660
1034
|
adapter: Boolean(set.data.adapter),
|
|
661
1035
|
exclude: excluded,
|
|
1036
|
+
module: source.module,
|
|
1037
|
+
progress: line => logger.warn(line),
|
|
662
1038
|
});
|
|
1039
|
+
const libraryLookPositions = lookPositions(everyIcon.map(icon => icon.id));
|
|
1040
|
+
// The look the icons from this library draw now decides whether a named
|
|
1041
|
+
// default moves them.
|
|
1042
|
+
const {looks, moved} = resolveDefaultLook({
|
|
1043
|
+
name,
|
|
1044
|
+
given,
|
|
1045
|
+
current,
|
|
1046
|
+
icons: set.data.icons.length,
|
|
1047
|
+
drawn: drawnLooks(set, packageDir, () => libraryLookPositions, {
|
|
1048
|
+
module: source.module,
|
|
1049
|
+
}),
|
|
1050
|
+
library: {module: source.module, positions: libraryLookPositions},
|
|
1051
|
+
});
|
|
1052
|
+
if (looks) {
|
|
1053
|
+
const asExtra = set.data.icons
|
|
1054
|
+
.filter(icon =>
|
|
1055
|
+
(icon.variants ?? []).some(each => each.variant === looks.default),
|
|
1056
|
+
)
|
|
1057
|
+
.map(icon => icon.id);
|
|
1058
|
+
if (asExtra.length > 0) {
|
|
1059
|
+
throw new AstryxError(
|
|
1060
|
+
`"${looks.default}" can't be the default variant: ${asExtra.length} icons list it as another look (${asExtra.slice(0, 8).join(', ')}${asExtra.length > 8 ? ', …' : ''}), and each icon's own export draws the default.`,
|
|
1061
|
+
undefined,
|
|
1062
|
+
ERROR_CODES.ERR_INVALID_ARGUMENT,
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
663
1067
|
// A refresh changes only the icons the refreshed module supplies: those
|
|
664
1068
|
// the generated module re-exports or adapts from it. Every other icon
|
|
665
1069
|
// (your own `./` artwork, another library's) is kept as it is, with its
|
|
666
1070
|
// map entries and variants.
|
|
667
1071
|
const generatedBefore = generatedModulePath(set, packageDir);
|
|
668
1072
|
const origins = generatedOrigins(packageDir, generatedBefore);
|
|
1073
|
+
// A refresh from a package root also supplies the exports that read one
|
|
1074
|
+
// file of that package, when the root exports the same icon. They move to
|
|
1075
|
+
// the root import, so the set has one import style.
|
|
1076
|
+
const {moves: rootMoves, skipped: skippedMoves} = claimRootMoves(
|
|
1077
|
+
set,
|
|
1078
|
+
origins,
|
|
1079
|
+
source.module,
|
|
1080
|
+
packageRootMoves(
|
|
1081
|
+
origins,
|
|
1082
|
+
source,
|
|
1083
|
+
everyIcon.map(icon => icon.export),
|
|
1084
|
+
where,
|
|
1085
|
+
),
|
|
1086
|
+
);
|
|
1087
|
+
/** @param {string} name the set's export */
|
|
1088
|
+
const libraryExportOf = name =>
|
|
1089
|
+
rootMoves.get(name)?.source ?? origins.get(name)?.source;
|
|
669
1090
|
/** @param {{export: string, module?: string}} icon */
|
|
670
1091
|
const suppliedHere = icon => {
|
|
671
1092
|
if ((icon.module ?? set.data.module) !== set.data.module) return false;
|
|
1093
|
+
if (rootMoves.has(icon.export)) return true;
|
|
672
1094
|
const origin = origins.get(icon.export);
|
|
673
1095
|
return origin != null && sameIconModule(origin.module, source.module);
|
|
674
1096
|
};
|
|
@@ -676,39 +1098,93 @@ async function refreshIconSet(
|
|
|
676
1098
|
const others = set.data.icons.filter(icon => !suppliedHere(icon));
|
|
677
1099
|
// An existing icon is the same icon when the library still exports it:
|
|
678
1100
|
// its identity is the library export its generated module re-exports
|
|
679
|
-
// (`HiSquares2X2Icon
|
|
680
|
-
// public names are derived, and a
|
|
681
|
-
// (`squares-2-x2` is now
|
|
682
|
-
//
|
|
683
|
-
//
|
|
1101
|
+
// (`HiSquares2X2Icon`, or the root export a per-file icon moves to), then
|
|
1102
|
+
// its public export, then its id. Ids and public names are derived, and a
|
|
1103
|
+
// newer rule may spell them differently (`squares-2-x2` is now
|
|
1104
|
+
// `squares-2x2`); an icon matched this way keeps its public export byte
|
|
1105
|
+
// for byte, every field it had (its variants, the words the author added,
|
|
1106
|
+
// its module), and its map entries. The same rule holds on every refresh,
|
|
1107
|
+
// so a second refresh changes nothing the first one wrote.
|
|
1108
|
+
const moving = new Set(supplied.filter(icon => rootMoves.has(icon.export)));
|
|
684
1109
|
const byId = new Map(supplied.map(icon => [icon.id, icon]));
|
|
685
1110
|
const byExport = new Map(supplied.map(icon => [icon.export, icon]));
|
|
686
1111
|
const bySource = new Map(
|
|
687
|
-
supplied.map(icon => [
|
|
1112
|
+
supplied.map(icon => [libraryExportOf(icon.export), icon]),
|
|
1113
|
+
);
|
|
1114
|
+
// One icon per glyph. A look the set already wraps keeps its icon and id.
|
|
1115
|
+
// A new glyph comes in once, in the preferred look. A new default variant
|
|
1116
|
+
// moves each glyph's icon to that look.
|
|
1117
|
+
const preferred = looks?.default ?? current;
|
|
1118
|
+
const {icons: looksOf} = libraryLooks(
|
|
1119
|
+
everyIcon,
|
|
1120
|
+
preferred ? [preferred] : [],
|
|
688
1121
|
);
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1122
|
+
const iconByExport = new Map(everyIcon.map(icon => [icon.export, icon]));
|
|
1123
|
+
/** @type {string[]} the set's exports whose statement now names another look */
|
|
1124
|
+
const repicked = [];
|
|
1125
|
+
/** @type {Map<string, (typeof supplied)[number]>} the new look's export → the icon it now draws */
|
|
1126
|
+
const repickedBy = new Map();
|
|
1127
|
+
const found = looksOf.flatMap(icon => {
|
|
1128
|
+
const wrapped = (icon.looks ?? []).filter(look => bySource.has(look));
|
|
1129
|
+
if (wrapped.length === 0) return [icon];
|
|
1130
|
+
return wrapped.map(look => {
|
|
1131
|
+
const prior = /** @type {(typeof supplied)[number]} */ (
|
|
1132
|
+
bySource.get(look)
|
|
1133
|
+
);
|
|
1134
|
+
// A moved icon is the glyph its root export draws, whatever its id.
|
|
1135
|
+
const sameGlyph = moving.has(prior) || prior.id === icon.id;
|
|
1136
|
+
if (moved && sameGlyph && !bySource.has(icon.export)) {
|
|
1137
|
+
repicked.push(prior.export);
|
|
1138
|
+
repickedBy.set(icon.export, prior);
|
|
1139
|
+
return icon;
|
|
1140
|
+
}
|
|
1141
|
+
const own = /** @type {(typeof everyIcon)[number]} */ (
|
|
1142
|
+
iconByExport.get(look)
|
|
1143
|
+
);
|
|
1144
|
+
// Read by the glyph's id when the set already names it so.
|
|
1145
|
+
return prior.id === icon.id
|
|
1146
|
+
? {...own, id: icon.id, name: icon.name}
|
|
1147
|
+
: own;
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1150
|
+
/** @param {{export: string}} glyph */
|
|
1151
|
+
const resolvedPrior = glyph =>
|
|
1152
|
+
repickedBy.get(glyph.export) ?? bySource.get(glyph.export) ?? null;
|
|
1153
|
+
// An icon its library export already gives to one glyph is never given to
|
|
1154
|
+
// a second glyph by name or id: that glyph is left out.
|
|
1155
|
+
const resolved = new Set(found.map(resolvedPrior).filter(Boolean));
|
|
1156
|
+
const takenIds = new Set(others.map(icon => icon.id));
|
|
1157
|
+
const takenExports = new Set(others.map(icon => icon.export));
|
|
1158
|
+
/** @type {string[]} library icons left out: an icon of the set has the id or name */
|
|
698
1159
|
const shadowed = [];
|
|
699
1160
|
/** @type {Map<string, string>} old id → new id */
|
|
700
1161
|
const renames = new Map();
|
|
701
1162
|
/** @type {Map<string, string>} the library export → the set's export */
|
|
702
1163
|
const publicName = new Map();
|
|
703
1164
|
const fromLibrary = found.flatMap(icon => {
|
|
704
|
-
const
|
|
1165
|
+
const byOrigin = resolvedPrior(icon);
|
|
1166
|
+
const byName = byOrigin
|
|
1167
|
+
? null
|
|
1168
|
+
: (byExport.get(icon.name) ?? byId.get(icon.id) ?? null);
|
|
1169
|
+
const prior = byOrigin ?? (resolved.has(byName) ? null : byName);
|
|
705
1170
|
const name = prior?.export ?? icon.name;
|
|
706
|
-
if (
|
|
1171
|
+
if (
|
|
1172
|
+
!prior &&
|
|
1173
|
+
(byName || takenIds.has(icon.id) || takenExports.has(name))
|
|
1174
|
+
) {
|
|
707
1175
|
shadowed.push(icon.id);
|
|
708
1176
|
return [];
|
|
709
1177
|
}
|
|
710
|
-
if (prior && prior.id !== icon.id) renames.set(prior.id, icon.id);
|
|
711
1178
|
publicName.set(icon.export, name);
|
|
1179
|
+
if (prior && byOrigin && !derivedId(prior.id, icon)) {
|
|
1180
|
+
return [
|
|
1181
|
+
{
|
|
1182
|
+
...prior,
|
|
1183
|
+
words: [...new Set([...(prior.words ?? []), ...icon.aliases])],
|
|
1184
|
+
},
|
|
1185
|
+
];
|
|
1186
|
+
}
|
|
1187
|
+
if (prior && prior.id !== icon.id) renames.set(prior.id, icon.id);
|
|
712
1188
|
// The words its old id gave are the old rule's, not the author's.
|
|
713
1189
|
const oldIdWords =
|
|
714
1190
|
prior && prior.id !== icon.id ? new Set(idWords(prior.id)) : new Set();
|
|
@@ -728,17 +1204,42 @@ async function refreshIconSet(
|
|
|
728
1204
|
];
|
|
729
1205
|
});
|
|
730
1206
|
const icons = [...fromLibrary, ...others];
|
|
1207
|
+
// An icon is listed under `moved` when it or one of its variants moves.
|
|
1208
|
+
/** @type {Array<{id: string, from: string, to: string}>} */
|
|
1209
|
+
const movedIcons = [];
|
|
1210
|
+
/** @type {import('../../foundation/discovery/icon-set-writer.mjs').GeneratedIcon[]} */
|
|
1211
|
+
const movedVariants = [];
|
|
1212
|
+
for (const icon of fromLibrary) {
|
|
1213
|
+
const variantMoves = (icon.variants ?? []).filter(each =>
|
|
1214
|
+
rootMoves.has(each.export),
|
|
1215
|
+
);
|
|
1216
|
+
for (const each of variantMoves) {
|
|
1217
|
+
const move = /** @type {{source: string, from: string}} */ (
|
|
1218
|
+
rootMoves.get(each.export)
|
|
1219
|
+
);
|
|
1220
|
+
movedVariants.push({
|
|
1221
|
+
name: each.export,
|
|
1222
|
+
from: source.module,
|
|
1223
|
+
source: move.source,
|
|
1224
|
+
adapt: iconByExport.get(move.source)?.adapt ?? false,
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
const from = (
|
|
1228
|
+
rootMoves.get(icon.export) ?? rootMoves.get(variantMoves[0]?.export)
|
|
1229
|
+
)?.from;
|
|
1230
|
+
if (from) movedIcons.push({id: icon.id, from, to: source.module});
|
|
1231
|
+
}
|
|
1232
|
+
movedIcons.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
731
1233
|
// The icons this module supplied that it no longer exports: they leave
|
|
732
1234
|
// the set and its generated module, and the receipt names them.
|
|
733
1235
|
// Each is named for why it left: `--exclude` (or the data file's
|
|
734
1236
|
// `exclude`) names its library export, or the library no longer exports
|
|
735
1237
|
// it.
|
|
736
1238
|
const excludedNames = new Set(excluded);
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
);
|
|
1239
|
+
const keptExports = new Set(fromLibrary.map(each => each.export));
|
|
1240
|
+
const gone = supplied.filter(icon => !keptExports.has(icon.export));
|
|
740
1241
|
const isExcluded = (/** @type {{export: string}} */ icon) =>
|
|
741
|
-
excludedNames.has(
|
|
1242
|
+
excludedNames.has(libraryExportOf(icon.export) ?? '') ||
|
|
742
1243
|
excludedNames.has(icon.export);
|
|
743
1244
|
const goneExcluded = gone.filter(isExcluded);
|
|
744
1245
|
const goneMissing = gone.filter(icon => !isExcluded(icon));
|
|
@@ -782,20 +1283,7 @@ async function refreshIconSet(
|
|
|
782
1283
|
// --display-name and --description rewrite the descriptor's fields.
|
|
783
1284
|
const docFile = path.join(set.dir, `${set.name}.doc.mjs`);
|
|
784
1285
|
const docBefore = fs.readFileSync(docFile, 'utf-8');
|
|
785
|
-
let docAfter = docBefore;
|
|
786
|
-
for (const [field, value] of /** @type {const} */ ([
|
|
787
|
-
['displayName', describe.displayName],
|
|
788
|
-
['description', describe.description],
|
|
789
|
-
])) {
|
|
790
|
-
if (!value?.trim()) continue;
|
|
791
|
-
docAfter = docAfter.replace(
|
|
792
|
-
new RegExp(
|
|
793
|
-
`^(\\s*${field}:\\s*)(['"\`])(?:\\\\.|(?!\\2).)*\\2,?$`,
|
|
794
|
-
'mu',
|
|
795
|
-
),
|
|
796
|
-
(_, lead) => `${lead}${quoteJs(value.trim())},`,
|
|
797
|
-
);
|
|
798
|
-
}
|
|
1286
|
+
let docAfter = describedDoc(docBefore, describe);
|
|
799
1287
|
// A refresh that first records the library as the set's source does what
|
|
800
1288
|
// `new-set --from` does: the descriptor takes the library's license, but
|
|
801
1289
|
// only over the value `new-set` wrote by default (the package's license,
|
|
@@ -840,21 +1328,31 @@ async function refreshIconSet(
|
|
|
840
1328
|
files.push(projectPath(path.relative(packageDir, target)));
|
|
841
1329
|
}
|
|
842
1330
|
if (!dryRun) {
|
|
1331
|
+
const rewritten = found
|
|
1332
|
+
.map(icon => ({
|
|
1333
|
+
name: publicName.get(icon.export) ?? icon.name,
|
|
1334
|
+
from: source.module,
|
|
1335
|
+
source: icon.export,
|
|
1336
|
+
adapt: icon.adapt,
|
|
1337
|
+
}))
|
|
1338
|
+
.filter(icon => publicName.has(icon.source))
|
|
1339
|
+
.concat(movedVariants);
|
|
1340
|
+
const rewrittenNames = new Set(rewritten.map(icon => icon.name));
|
|
843
1341
|
const written = writeGeneratedIcons(
|
|
844
1342
|
packageDir,
|
|
845
1343
|
generated,
|
|
846
1344
|
next,
|
|
847
|
-
|
|
848
|
-
.map(icon => ({
|
|
849
|
-
name: publicName.get(icon.export) ?? icon.name,
|
|
850
|
-
from: source.module,
|
|
851
|
-
source: icon.export,
|
|
852
|
-
adapt: icon.adapt,
|
|
853
|
-
}))
|
|
854
|
-
.filter(icon => publicName.has(icon.source)),
|
|
1345
|
+
rewritten,
|
|
855
1346
|
// The icons the library no longer exports leave the module too; a
|
|
856
|
-
// renamed icon keeps its export.
|
|
857
|
-
|
|
1347
|
+
// renamed icon keeps its export. An icon moved to the new default
|
|
1348
|
+
// look keeps its export, which now names that look. An export moved
|
|
1349
|
+
// to the root is written again with the root import. Every other
|
|
1350
|
+
// export stays as it is, because the data file still lists it.
|
|
1351
|
+
[
|
|
1352
|
+
...gone.map(icon => icon.export),
|
|
1353
|
+
...repicked,
|
|
1354
|
+
...[...rootMoves.keys()].filter(name => rewrittenNames.has(name)),
|
|
1355
|
+
],
|
|
858
1356
|
);
|
|
859
1357
|
files.push(...written.files);
|
|
860
1358
|
try {
|
|
@@ -906,15 +1404,32 @@ async function refreshIconSet(
|
|
|
906
1404
|
if (renames.size > 0) {
|
|
907
1405
|
result.data.renamed = [...renames].map(([from, to]) => ({from, to}));
|
|
908
1406
|
}
|
|
1407
|
+
if (movedIcons.length > 0) result.data.moved = movedIcons;
|
|
1408
|
+
const missingNotice = hasLicenseNotice(set.dir)
|
|
1409
|
+
? null
|
|
1410
|
+
: noticeToAdd(
|
|
1411
|
+
library,
|
|
1412
|
+
packageDir,
|
|
1413
|
+
projectPath(path.relative(packageDir, set.dir)),
|
|
1414
|
+
);
|
|
1415
|
+
if (missingNotice) {
|
|
1416
|
+
result.data.licenseNoticeMissing = missingNotice.receipt;
|
|
1417
|
+
}
|
|
909
1418
|
result.data.notes = [
|
|
910
1419
|
...(licenseNote ? [licenseNote] : []),
|
|
1420
|
+
...(missingNotice ? [missingNotice.note] : []),
|
|
911
1421
|
...(renames.size > 0
|
|
912
1422
|
? [
|
|
913
1423
|
`${renames.size} icons have a new id under this release's id rule, and keep their map entries, variants, and words: ${[...renames].map(([from, to]) => `${from} -> ${to}`).join(', ')}.`,
|
|
914
1424
|
]
|
|
915
1425
|
: []),
|
|
916
|
-
...
|
|
917
|
-
|
|
1426
|
+
...(movedIcons.length > 0
|
|
1427
|
+
? [
|
|
1428
|
+
`${movedIcons.length} icons now import from ${source.module} instead of one file each, and keep their ids, exports, map entries, variants, and words: ${movedIcons.map(each => each.id).join(', ')}.`,
|
|
1429
|
+
]
|
|
1430
|
+
: []),
|
|
1431
|
+
...mappedNotes(byName, name),
|
|
1432
|
+
...suggestionNotes(suggestions, name),
|
|
918
1433
|
...(goneMissing.length > 0
|
|
919
1434
|
? [
|
|
920
1435
|
`${goneMissing.length} icons the library no longer exports were removed from the set: ${goneMissing.map(icon => icon.id).join(', ')}.`,
|
|
@@ -937,7 +1452,12 @@ async function refreshIconSet(
|
|
|
937
1452
|
: []),
|
|
938
1453
|
...(shadowed.length > 0
|
|
939
1454
|
? [
|
|
940
|
-
`Left out ${shadowed.length} library icons whose id or name an icon of
|
|
1455
|
+
`Left out ${shadowed.length} library icons whose id or name an icon of the set already has: ${shadowed.join(', ')}.`,
|
|
1456
|
+
]
|
|
1457
|
+
: []),
|
|
1458
|
+
...(skippedMoves.length > 0
|
|
1459
|
+
? [
|
|
1460
|
+
`${skippedMoves.length} icons stay on their own file, because another icon of the set already reads the same export of ${source.module}: ${skippedMoves.join(', ')}.`,
|
|
941
1461
|
]
|
|
942
1462
|
: []),
|
|
943
1463
|
astryxIconDefinitions().definitions.every(({doc}) => map[doc.name])
|