@astryxdesign/cli 0.7.12 → 0.7.13-canary.a5ad1f2
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/doctor/_adapter.d.mts +17 -0
- package/api/doctor/_adapter.mjs +40 -0
- package/api/doctor/doctor.d.mts +34 -4
- package/api/doctor/doctor.mjs +108 -21
- package/api/icon/_adapter.d.mts +6 -3
- package/api/icon/_adapter.mjs +11 -3
- package/api/icon/icon.type.d.mts +22 -1
- package/api/icon/icon.type.mjs +12 -4
- package/api/icon/iconUse.doc.mjs +27 -7
- package/api/icon/list/list.mjs +29 -3
- package/api/icon/use/use.d.mts +7 -3
- package/api/icon/use/use.mjs +68 -18
- package/api/integration/validate-integration.d.mts +4 -0
- package/api/integration/validate-integration.mjs +32 -2
- package/api/integration/validate-integration.type.d.mts +15 -0
- package/api/integration/validate-integration.type.mjs +6 -1
- package/api/template/copy/copy.mjs +4 -2
- package/api/template/template.type.d.mts +42 -4
- package/api/template/template.type.mjs +9 -6
- package/assets/discover/official-packages.json +4 -0
- package/assets/docs/tree/icon-sets.doc.mjs +91 -7
- package/assets/icons/definitions/alignBottom.doc.mjs +9 -0
- package/assets/icons/definitions/alignJustify.doc.mjs +9 -0
- package/assets/icons/definitions/alignMiddle.doc.mjs +9 -0
- package/assets/icons/definitions/alignTop.doc.mjs +9 -0
- package/assets/icons/definitions/angle.doc.mjs +9 -0
- package/assets/icons/definitions/cloud.doc.mjs +9 -0
- package/assets/icons/definitions/collection.doc.mjs +9 -0
- package/assets/icons/definitions/contrast.doc.mjs +9 -0
- package/assets/icons/definitions/cornerRadius.doc.mjs +9 -0
- package/assets/icons/definitions/cut.doc.mjs +9 -0
- package/assets/icons/definitions/fingerprint.doc.mjs +9 -0
- package/assets/icons/definitions/fire.doc.mjs +9 -0
- package/assets/icons/definitions/frame.doc.mjs +9 -0
- package/assets/icons/definitions/gift.doc.mjs +9 -0
- package/assets/icons/definitions/group.doc.mjs +9 -0
- package/assets/icons/definitions/integration.doc.mjs +9 -0
- package/assets/icons/definitions/lowercase.doc.mjs +9 -0
- package/assets/icons/definitions/paddingHorizontal.doc.mjs +9 -0
- package/assets/icons/definitions/paddingPerSide.doc.mjs +9 -0
- package/assets/icons/definitions/paddingVertical.doc.mjs +9 -0
- package/assets/icons/definitions/panelBottom.doc.mjs +9 -0
- package/assets/icons/definitions/photoEffect.doc.mjs +9 -0
- package/assets/icons/definitions/radiusPerCorner.doc.mjs +9 -0
- package/assets/icons/definitions/rotateCounterclockwise.doc.mjs +9 -0
- package/assets/icons/definitions/tablet.doc.mjs +9 -0
- package/assets/icons/definitions/text.doc.mjs +9 -0
- package/assets/icons/definitions/textRegular.doc.mjs +9 -0
- package/assets/icons/definitions/transfer.doc.mjs +9 -0
- package/assets/icons/definitions/uppercase.doc.mjs +9 -0
- package/assets/icons/definitions/userRemove.doc.mjs +9 -0
- package/assets/templates/blocks/components/BaseTypeahead/BaseTypeaheadMenu.doc.mjs +23 -0
- package/assets/templates/blocks/components/BaseTypeahead/BaseTypeaheadMenu.tsx +199 -0
- package/assets/templates/blocks/components/ButtonGroup/ButtonGroupShowcase.doc.mjs +1 -0
- package/assets/templates/blocks/components/ChatSystemMessage/ChatSystemMessageStatusUpdates.doc.mjs +7 -2
- package/assets/templates/pages/canvas-editor/template.doc.mjs +20 -0
- package/assets/templates/pages/editor/template.doc.mjs +1 -0
- package/assets/templates/pages/form-wizard-vertical/template.doc.mjs +1 -0
- package/assets/templates/pages/settings-dialog/template.doc.mjs +4 -0
- package/assets/templates/pages/shell-top-nav/template.doc.mjs +3 -0
- package/assets/templates/pages/table-collapsible/template.doc.mjs +1 -0
- package/assets/templates/pages/table-comparison/template.doc.mjs +1 -0
- package/authoring/config/config.doc.mjs +4 -4
- package/authoring/config/parse.d.mts +1 -1
- package/authoring/config/parse.mjs +3 -1
- package/authoring/config/type.ts +7 -5
- package/authoring/doctypes/template/template.doc.mjs +1 -1
- package/authoring/doctypes/template/type.ts +5 -3
- package/clients/cli/commands/experimental/icon.mjs +38 -9
- package/clients/cli/commands/experimental-icon-use.doc.mjs +9 -4
- package/clients/cli/commands/template.mjs +12 -3
- package/foundation/config/config-writer.d.mts +14 -0
- package/foundation/config/config-writer.mjs +73 -17
- package/foundation/discovery/icon-set-discovery.d.mts +77 -7
- package/foundation/discovery/icon-set-discovery.mjs +128 -42
- package/foundation/discovery/template-icon-notes.d.mts +46 -0
- package/foundation/discovery/template-icon-notes.mjs +94 -16
- package/foundation/discovery/template-icons.d.mts +247 -0
- package/foundation/discovery/template-icons.mjs +591 -37
- package/foundation/integrations/cli-requirement.d.mts +13 -4
- package/foundation/integrations/cli-requirement.mjs +18 -6
- package/package.json +6 -6
|
@@ -99,6 +99,15 @@ export function hasLicenseNotice(dir) {
|
|
|
99
99
|
* @property {string} reason
|
|
100
100
|
*/
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* One backup set the project's list names, and what it resolves to.
|
|
104
|
+
* @typedef {object} IconSetBackup
|
|
105
|
+
* @property {string} selector the name as the list writes it
|
|
106
|
+
* @property {'selected' | 'ambiguous' | 'missing' | 'withdrawn'} status
|
|
107
|
+
* @property {DiscoveredIconSet} [set] the set, when selected
|
|
108
|
+
* @property {string} reason one sentence saying why
|
|
109
|
+
*/
|
|
110
|
+
|
|
102
111
|
/**
|
|
103
112
|
* The project's icon set (FR8).
|
|
104
113
|
* @typedef {object} IconSetChoice
|
|
@@ -109,7 +118,13 @@ export function hasLicenseNotice(dir) {
|
|
|
109
118
|
* `withdrawn`: it names a set that is installed but withdrawn (invalid, or
|
|
110
119
|
* its module does not resolve), and `reason` says why
|
|
111
120
|
* @property {DiscoveredIconSet} [set] the project's set, when selected
|
|
112
|
-
* @property {string} [configured] the `experimental.iconSet`
|
|
121
|
+
* @property {string} [configured] the main set `experimental.iconSet` names
|
|
122
|
+
* (its first entry, when it is a list), when set
|
|
123
|
+
* @property {IconSetBackup[]} [backups] the backup sets the list names after
|
|
124
|
+
* the main set, in order, when it names any
|
|
125
|
+
* @property {DiscoveredIconSet[]} [order] the sets a template icon is looked
|
|
126
|
+
* up in, in order: the main set, then each backup that resolves. Present
|
|
127
|
+
* only when the main set is selected
|
|
113
128
|
* @property {string} reason one sentence saying why
|
|
114
129
|
*/
|
|
115
130
|
|
|
@@ -407,55 +422,126 @@ export function withdrawnNamed(withdrawn, selector) {
|
|
|
407
422
|
}
|
|
408
423
|
|
|
409
424
|
/**
|
|
410
|
-
*
|
|
411
|
-
*
|
|
425
|
+
* The entries of an `experimental.iconSet` value, main set first: a string is
|
|
426
|
+
* a one-entry list.
|
|
427
|
+
* @param {string | readonly string[] | undefined} configured
|
|
428
|
+
* @returns {string[]}
|
|
429
|
+
*/
|
|
430
|
+
export function iconSetList(configured) {
|
|
431
|
+
if (configured == null) return [];
|
|
432
|
+
return typeof configured === 'string' ? [configured] : [...configured];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* What one name in the list resolves to: a set, or why not.
|
|
437
|
+
* @param {DiscoveredIconSet[]} sets
|
|
438
|
+
* @param {string} selector `"<set>"` or `"<package>/<set>"`
|
|
439
|
+
* @param {WithdrawnIconSet[]} withdrawn
|
|
440
|
+
* @returns {{status: 'selected' | 'ambiguous' | 'missing' | 'withdrawn', set?: DiscoveredIconSet, reason: string}}
|
|
441
|
+
*/
|
|
442
|
+
function resolveSelector(sets, selector, withdrawn) {
|
|
443
|
+
const exact = sets.filter(set => `${set.package}/${set.name}` === selector);
|
|
444
|
+
const byName =
|
|
445
|
+
exact.length > 0 ? exact : sets.filter(set => set.name === selector);
|
|
446
|
+
if (byName.length === 1) {
|
|
447
|
+
return {
|
|
448
|
+
status: 'selected',
|
|
449
|
+
set: byName[0],
|
|
450
|
+
reason: `experimental.iconSet names "${selector}".`,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
if (byName.length > 1) {
|
|
454
|
+
return {
|
|
455
|
+
status: 'ambiguous',
|
|
456
|
+
reason: `experimental.iconSet "${selector}" names a set in ${byName.map(set => set.package).join(' and ')}; write it as "<package>/<set>".`,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
const gone = withdrawnNamed(withdrawn, selector);
|
|
460
|
+
if (gone.length > 0) {
|
|
461
|
+
return {
|
|
462
|
+
status: 'withdrawn',
|
|
463
|
+
reason: `experimental.iconSet names "${selector}", which is installed but withdrawn: ${gone.map(each => each.reason).join(' ')}`,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
status: 'missing',
|
|
468
|
+
reason: `experimental.iconSet names "${selector}", but no installed set has that name${sets.length > 0 ? ` (installed: ${sets.map(iconSetKey).join(', ')})` : ''}.`,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** The command that chooses a main set and backups, as the CLI prints it. */
|
|
473
|
+
export const ICON_USE_ORDER_COMMAND =
|
|
474
|
+
'astryx experimental icon use <main> --backup <set>';
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Installed sets the project's list leaves out: the problem, and the fix that
|
|
478
|
+
* adds them as backups after the list's own. Doctor and `experimental icon
|
|
479
|
+
* use` print the same words.
|
|
480
|
+
* @param {number} installed how many sets are installed
|
|
481
|
+
* @param {string} main the main set as the list writes it
|
|
482
|
+
* @param {string[]} backups the list's backups, in order
|
|
483
|
+
* @param {string[]} unlisted the installed sets the list leaves out
|
|
484
|
+
* @returns {{problem: string, command: string, fix: string}}
|
|
485
|
+
*/
|
|
486
|
+
export function unlistedIconSets(installed, main, backups, unlisted) {
|
|
487
|
+
const them = unlisted.length === 1 ? 'it' : 'them';
|
|
488
|
+
const command = `astryx experimental icon use ${main}${[...backups, ...unlisted].map(each => ` --backup ${each}`).join('')}`;
|
|
489
|
+
return {
|
|
490
|
+
problem: `${installed} icon sets are installed and ${unlisted.join(', ')} ${unlisted.length === 1 ? 'is' : 'are'} not in your list`,
|
|
491
|
+
command,
|
|
492
|
+
fix: `Add ${them} as backup with \`${command}\`, or uninstall ${them}.`,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Pick the project's icon set (FR8, FR17): the main set `experimental.iconSet`
|
|
498
|
+
* names, as `"<set>"` or `"<package>/<set>"`, or as the first entry of a list
|
|
499
|
+
* whose other entries are backup sets in order; otherwise the only installed
|
|
412
500
|
* set; otherwise none. A configured name that matches no valid set but an
|
|
413
501
|
* installed set that was withdrawn says so, with why.
|
|
414
502
|
* @param {DiscoveredIconSet[]} sets installed sets, in integration load order
|
|
415
|
-
* @param {string | undefined} configured the `experimental.iconSet` config value
|
|
503
|
+
* @param {string | readonly string[] | undefined} configured the `experimental.iconSet` config value
|
|
416
504
|
* @param {WithdrawnIconSet[]} [withdrawn] installed sets that were withdrawn
|
|
417
505
|
* @returns {IconSetChoice}
|
|
418
506
|
*/
|
|
419
507
|
export function chooseProjectIconSet(sets, configured, withdrawn = []) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
return {
|
|
436
|
-
status: 'ambiguous',
|
|
437
|
-
configured,
|
|
438
|
-
reason: `experimental.iconSet "${configured}" names a set in ${byName.map(set => set.package).join(' and ')}; write it as "<package>/<set>".`,
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
const gone = withdrawnNamed(withdrawn, configured);
|
|
442
|
-
if (gone.length > 0) {
|
|
443
|
-
return {
|
|
444
|
-
status: 'withdrawn',
|
|
445
|
-
configured,
|
|
446
|
-
reason: `experimental.iconSet names "${configured}", which is installed but withdrawn: ${gone.map(each => each.reason).join(' ')}`,
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
return {
|
|
450
|
-
status: 'missing',
|
|
451
|
-
configured,
|
|
452
|
-
reason: `experimental.iconSet names "${configured}", but no installed set has that name${sets.length > 0 ? ` (installed: ${sets.map(iconSetKey).join(', ')})` : ''}.`,
|
|
508
|
+
const [main, ...rest] = iconSetList(configured);
|
|
509
|
+
if (main) {
|
|
510
|
+
const resolved = resolveSelector(sets, main, withdrawn);
|
|
511
|
+
/** @type {IconSetBackup[]} */
|
|
512
|
+
const backups = rest.map(selector => ({
|
|
513
|
+
selector,
|
|
514
|
+
...resolveSelector(sets, selector, withdrawn),
|
|
515
|
+
}));
|
|
516
|
+
/** @type {IconSetChoice} */
|
|
517
|
+
const choice = {
|
|
518
|
+
status: resolved.status,
|
|
519
|
+
...(resolved.set ? {set: resolved.set} : {}),
|
|
520
|
+
configured: main,
|
|
521
|
+
...(backups.length > 0 ? {backups} : {}),
|
|
522
|
+
reason: resolved.reason,
|
|
453
523
|
};
|
|
524
|
+
if (resolved.set) {
|
|
525
|
+
/** @type {DiscoveredIconSet[]} */
|
|
526
|
+
const order = [resolved.set];
|
|
527
|
+
for (const backup of backups) {
|
|
528
|
+
if (backup.set && !order.includes(backup.set)) order.push(backup.set);
|
|
529
|
+
}
|
|
530
|
+
choice.order = order;
|
|
531
|
+
if (order.length > 1) {
|
|
532
|
+
choice.reason = `experimental.iconSet names "${main}", with ${order
|
|
533
|
+
.slice(1)
|
|
534
|
+
.map(set => `"${iconSetKey(set)}"`)
|
|
535
|
+
.join(', then ')} as backup.`;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return choice;
|
|
454
539
|
}
|
|
455
540
|
if (sets.length === 1) {
|
|
456
541
|
return {
|
|
457
542
|
status: 'selected',
|
|
458
543
|
set: sets[0],
|
|
544
|
+
order: [sets[0]],
|
|
459
545
|
reason: `"${iconSetKey(sets[0])}" is the only installed icon set.`,
|
|
460
546
|
};
|
|
461
547
|
}
|
|
@@ -464,7 +550,7 @@ export function chooseProjectIconSet(sets, configured, withdrawn = []) {
|
|
|
464
550
|
}
|
|
465
551
|
return {
|
|
466
552
|
status: 'ambiguous',
|
|
467
|
-
reason: `${sets.length} icon sets are installed (${sets.map(iconSetKey).join(', ')}) and astryx.config sets no experimental.iconSet.`,
|
|
553
|
+
reason: `${sets.length} icon sets are installed (${sets.map(iconSetKey).join(', ')}) and astryx.config sets no experimental.iconSet, so none is your main set.`,
|
|
468
554
|
};
|
|
469
555
|
}
|
|
470
556
|
|
|
@@ -477,13 +563,13 @@ export function iconSetKey(set) {
|
|
|
477
563
|
}
|
|
478
564
|
|
|
479
565
|
/**
|
|
480
|
-
* Sets in tie order (FR6, FR8): the project's set first,
|
|
566
|
+
* Sets in tie order (FR6, FR8): the project's set and its backups first, in
|
|
567
|
+
* list order, then load order.
|
|
481
568
|
* @param {DiscoveredIconSet[]} sets
|
|
482
569
|
* @param {IconSetChoice} choice
|
|
483
570
|
* @returns {DiscoveredIconSet[]}
|
|
484
571
|
*/
|
|
485
572
|
export function orderIconSets(sets, choice) {
|
|
486
|
-
const first = choice.set;
|
|
487
|
-
|
|
488
|
-
return [first, ...sets.filter(set => set !== first)];
|
|
573
|
+
const first = choice.order ?? (choice.set ? [choice.set] : []);
|
|
574
|
+
return [...first, ...sets.filter(set => !first.includes(set))];
|
|
489
575
|
}
|
|
@@ -11,5 +11,51 @@ export function templateIconNotes(loaded: any, packageDir: string): Promise<Arra
|
|
|
11
11
|
code: string;
|
|
12
12
|
message: string;
|
|
13
13
|
}>>;
|
|
14
|
+
/**
|
|
15
|
+
* One item the undeclared-icon check could not judge.
|
|
16
|
+
* @typedef {object} TemplateIconUnchecked
|
|
17
|
+
* @property {string} file the template, relative to the package
|
|
18
|
+
* @property {string} icon the imported name
|
|
19
|
+
* @property {string} module the module it is imported from
|
|
20
|
+
* @property {string} reason why, and what would let the check judge it
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* One note per icon a template imports but does not declare in `icons`, and
|
|
24
|
+
* each import the check cannot judge. Only a package whose templates declare
|
|
25
|
+
* `icons` gets either: a package that never labels its icons sees no change.
|
|
26
|
+
* @param {any} loaded a loaded integration (absolute roots)
|
|
27
|
+
* @param {string} packageDir
|
|
28
|
+
* @returns {Promise<{notes: Array<{code: string, message: string}>, unchecked: TemplateIconUnchecked[]}>}
|
|
29
|
+
*/
|
|
30
|
+
export function templateIconUndeclaredNotes(loaded: any, packageDir: string): Promise<{
|
|
31
|
+
notes: Array<{
|
|
32
|
+
code: string;
|
|
33
|
+
message: string;
|
|
34
|
+
}>;
|
|
35
|
+
unchecked: TemplateIconUnchecked[];
|
|
36
|
+
}>;
|
|
14
37
|
/** A template `icons` key its source never imports. */
|
|
15
38
|
export const TEMPLATE_ICON_NOT_IMPORTED: "template_icon_not_imported";
|
|
39
|
+
/** An icon a template imports that its `icons` does not declare. */
|
|
40
|
+
export const TEMPLATE_ICON_UNDECLARED: "template_icon_undeclared";
|
|
41
|
+
/**
|
|
42
|
+
* One item the undeclared-icon check could not judge.
|
|
43
|
+
*/
|
|
44
|
+
export type TemplateIconUnchecked = {
|
|
45
|
+
/**
|
|
46
|
+
* the template, relative to the package
|
|
47
|
+
*/
|
|
48
|
+
file: string;
|
|
49
|
+
/**
|
|
50
|
+
* the imported name
|
|
51
|
+
*/
|
|
52
|
+
icon: string;
|
|
53
|
+
/**
|
|
54
|
+
* the module it is imported from
|
|
55
|
+
*/
|
|
56
|
+
module: string;
|
|
57
|
+
/**
|
|
58
|
+
* why, and what would let the check judge it
|
|
59
|
+
*/
|
|
60
|
+
reason: string;
|
|
61
|
+
};
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @file
|
|
5
|
-
* FR10).
|
|
4
|
+
* @file A template's `icons` checked against the icons its source imports.
|
|
6
5
|
*
|
|
7
6
|
* A template's `icons` names each icon it imports, by the name it imports or,
|
|
8
7
|
* for a default import, its local name. A key that no import or re-export in
|
|
9
8
|
* the template's source binds can never map to a project's icon set, so each
|
|
10
|
-
* one gets a note with its fix.
|
|
9
|
+
* one gets a note with its fix. An icon the template imports but does not
|
|
10
|
+
* declare never maps either: in a package whose templates declare `icons`,
|
|
11
|
+
* each one gets a note too, which `validate` marks `next`. An import counts
|
|
12
|
+
* only when the template renders it as an element without children or
|
|
13
|
+
* passes it as a value. Any other import from that module, a generic
|
|
14
|
+
* renderer, and an import that matches an icon by its name only, are listed
|
|
15
|
+
* as not checked instead.
|
|
11
16
|
*
|
|
12
17
|
* @input A loaded integration (absolute roots) and its package directory.
|
|
13
|
-
* @output One `template_icon_not_imported` note per such key
|
|
14
|
-
*
|
|
15
|
-
*
|
|
18
|
+
* @output One `template_icon_not_imported` note per such key, one
|
|
19
|
+
* `template_icon_undeclared` note per undeclared icon, and each import not
|
|
20
|
+
* rendered as an icon, generic renderer, or import matched by its name
|
|
21
|
+
* only, as not checked. A note is never an error or a warning, even under
|
|
22
|
+
* `--strict`.
|
|
16
23
|
* @position foundation/discovery; read by `doctor integration validate` (and
|
|
17
24
|
* so `integration check`) for the package being authored.
|
|
18
25
|
*/
|
|
@@ -21,46 +28,117 @@ import * as fs from 'node:fs';
|
|
|
21
28
|
import * as path from 'node:path';
|
|
22
29
|
import {loadTopicModule} from './docs-discovery.mjs';
|
|
23
30
|
import {discoverIntegrationTemplatesForOne} from './template-adapter.mjs';
|
|
24
|
-
import {iconsNotImported} from './template-icons.mjs';
|
|
31
|
+
import {iconsNotImported, undeclaredTemplateIcons} from './template-icons.mjs';
|
|
25
32
|
|
|
26
33
|
/** A template `icons` key its source never imports. */
|
|
27
34
|
export const TEMPLATE_ICON_NOT_IMPORTED = 'template_icon_not_imported';
|
|
28
35
|
|
|
36
|
+
/** An icon a template imports that its `icons` does not declare. */
|
|
37
|
+
export const TEMPLATE_ICON_UNDECLARED = 'template_icon_undeclared';
|
|
38
|
+
|
|
29
39
|
/**
|
|
30
|
-
*
|
|
40
|
+
* Each template in the package, with its source and its doc's `icons`. A
|
|
41
|
+
* template whose doc or source cannot be read is left out.
|
|
31
42
|
* @param {any} loaded a loaded integration (absolute roots)
|
|
32
43
|
* @param {string} packageDir
|
|
33
|
-
* @returns {Promise<Array<{
|
|
44
|
+
* @returns {Promise<Array<{id: string, docPath: string, source: string, declared: Record<string, string> | null}>>}
|
|
34
45
|
*/
|
|
35
|
-
|
|
46
|
+
async function readTemplates(loaded, packageDir) {
|
|
36
47
|
if (!loaded.templates) return [];
|
|
37
48
|
const {templates} = await discoverIntegrationTemplatesForOne(loaded).catch(
|
|
38
49
|
() => ({templates: []}),
|
|
39
50
|
);
|
|
40
|
-
/** @type {Array<{code: string, message: string}>} */
|
|
41
|
-
const notes = [];
|
|
42
51
|
/** @param {string} file */
|
|
43
52
|
const shown = file =>
|
|
44
53
|
path.relative(packageDir, file).split(path.sep).join('/');
|
|
54
|
+
/** @type {Array<{id: string, docPath: string, source: string, declared: Record<string, string> | null}>} */
|
|
55
|
+
const read = [];
|
|
45
56
|
for (const template of templates) {
|
|
46
57
|
if (!template.docPath || !template.filePath) continue;
|
|
47
58
|
const doc = /** @type {any} */ (
|
|
48
59
|
await loadTopicModule(template.docPath).catch(() => null)
|
|
49
60
|
);
|
|
50
|
-
const declared = doc?.icons;
|
|
51
|
-
if (declared == null || typeof declared !== 'object') continue;
|
|
52
61
|
let source;
|
|
53
62
|
try {
|
|
54
63
|
source = fs.readFileSync(template.filePath, 'utf-8');
|
|
55
64
|
} catch {
|
|
56
65
|
continue;
|
|
57
66
|
}
|
|
58
|
-
|
|
67
|
+
read.push({
|
|
68
|
+
id: shown(template.filePath),
|
|
69
|
+
docPath: shown(template.docPath),
|
|
70
|
+
source,
|
|
71
|
+
declared:
|
|
72
|
+
doc?.icons != null && typeof doc.icons === 'object' ? doc.icons : null,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return read;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* One note per template `icons` key the template's source never imports.
|
|
80
|
+
* @param {any} loaded a loaded integration (absolute roots)
|
|
81
|
+
* @param {string} packageDir
|
|
82
|
+
* @returns {Promise<Array<{code: string, message: string}>>}
|
|
83
|
+
*/
|
|
84
|
+
export async function templateIconNotes(loaded, packageDir) {
|
|
85
|
+
/** @type {Array<{code: string, message: string}>} */
|
|
86
|
+
const notes = [];
|
|
87
|
+
for (const template of await readTemplates(loaded, packageDir)) {
|
|
88
|
+
if (!template.declared) continue;
|
|
89
|
+
for (const key of iconsNotImported(template.source, template.declared) ??
|
|
90
|
+
[]) {
|
|
59
91
|
notes.push({
|
|
60
92
|
code: TEMPLATE_ICON_NOT_IMPORTED,
|
|
61
|
-
message: `${
|
|
93
|
+
message: `${template.docPath} declares icon "${key}" in \`icons\`, which ${template.id} does not import, so it never maps to a project's icon set. Name a named import by the name it imports and a default import by its local name, or remove "${key}" from \`icons\`.`,
|
|
62
94
|
});
|
|
63
95
|
}
|
|
64
96
|
}
|
|
65
97
|
return notes;
|
|
66
98
|
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* One item the undeclared-icon check could not judge.
|
|
102
|
+
* @typedef {object} TemplateIconUnchecked
|
|
103
|
+
* @property {string} file the template, relative to the package
|
|
104
|
+
* @property {string} icon the imported name
|
|
105
|
+
* @property {string} module the module it is imported from
|
|
106
|
+
* @property {string} reason why, and what would let the check judge it
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* One note per icon a template imports but does not declare in `icons`, and
|
|
111
|
+
* each import the check cannot judge. Only a package whose templates declare
|
|
112
|
+
* `icons` gets either: a package that never labels its icons sees no change.
|
|
113
|
+
* @param {any} loaded a loaded integration (absolute roots)
|
|
114
|
+
* @param {string} packageDir
|
|
115
|
+
* @returns {Promise<{notes: Array<{code: string, message: string}>, unchecked: TemplateIconUnchecked[]}>}
|
|
116
|
+
*/
|
|
117
|
+
export async function templateIconUndeclaredNotes(loaded, packageDir) {
|
|
118
|
+
const read = (await readTemplates(loaded, packageDir)).map(each => ({
|
|
119
|
+
...each,
|
|
120
|
+
declared: each.declared ?? {},
|
|
121
|
+
}));
|
|
122
|
+
if (!read.some(each => Object.keys(each.declared).length > 0)) {
|
|
123
|
+
return {notes: [], unchecked: []};
|
|
124
|
+
}
|
|
125
|
+
const docOf = new Map(read.map(each => [each.id, each.docPath]));
|
|
126
|
+
const {undeclared, unchecked} = undeclaredTemplateIcons(read);
|
|
127
|
+
return {
|
|
128
|
+
notes: undeclared.map(({id, key, module}) => ({
|
|
129
|
+
code: TEMPLATE_ICON_UNDECLARED,
|
|
130
|
+
message: `${id} imports icon "${key}" from ${module}, but ${docOf.get(id)} does not declare it in \`icons\`, so it never follows a project's icon set. Add \`${key}: '<definition>'\` to \`icons\` (\`astryx experimental icon --definitions\` lists the definitions).`,
|
|
131
|
+
})),
|
|
132
|
+
unchecked: unchecked.map(({id, key, module, why}) => ({
|
|
133
|
+
file: id,
|
|
134
|
+
icon: key,
|
|
135
|
+
module,
|
|
136
|
+
reason:
|
|
137
|
+
why === 'renderer'
|
|
138
|
+
? `"${key}" is a generic renderer: it is a bare \`Icon\`, or every use gives it an icon prop, so it draws no one icon definition and needs no \`icons\` key.`
|
|
139
|
+
: why === 'use'
|
|
140
|
+
? `"${key}" is not rendered as an icon: the template never renders it as an element without children or passes it as a value, so it needs no \`icons\` key. Declaring it would make \`astryx template\` swap it for an icon.`
|
|
141
|
+
: `"${key}" matches an icon by its name only: no template in the package declares an icon from ${module}, so the check cannot tell whether it draws one icon definition. Declare an icon from ${module} in a template's \`icons\` to have its imports checked.`,
|
|
142
|
+
})),
|
|
143
|
+
};
|
|
144
|
+
}
|