@astryxdesign/cli 0.7.12 → 0.7.13-canary.9993f4c
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/component/__fixtures__/resolutions/modules.txt +1 -1
- 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/blocks/components/InternationalizationProvider/useDirectionHookUsage.doc.mjs +18 -0
- package/assets/templates/blocks/components/InternationalizationProvider/useDirectionHookUsage.tsx +101 -0
- 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 +14 -14
|
@@ -257,7 +257,7 @@ useChatPasteAsToken useChatPasteAsToken Chat/Chat.doc.mjs Chat/useChatPasteAsTok
|
|
|
257
257
|
useChatStreamScroll useChatStreamScroll Chat/Chat.doc.mjs Chat/useChatStreamScroll.ts
|
|
258
258
|
useCloseMobileDrawerOnActivate useCloseMobileDrawerOnActivate AppShell/AppShell.doc.mjs AppShell/useCloseMobileDrawerOnActivate.ts
|
|
259
259
|
useComponentTranslator useComponentTranslator i18n/InternationalizationProvider.doc.mjs i18n/useComponentTranslator.ts
|
|
260
|
-
useDirection useDirection i18n/
|
|
260
|
+
useDirection useDirection i18n/useDirection.doc.mjs i18n/useDirection.ts
|
|
261
261
|
useDrawerDialogPresence useDrawerDialogPresence Drawer/Drawer.doc.mjs Drawer/useDrawerDialogPresence.ts
|
|
262
262
|
useEndLaneReserve useEndLaneReserve Tokenizer/Tokenizer.doc.mjs Tokenizer/useEndLaneReserve.ts
|
|
263
263
|
useFieldStatusAnnouncement useFieldStatusAnnouncement FieldStatus/FieldStatus.doc.mjs FieldStatus/useFieldStatusAnnouncement.ts
|
|
@@ -20,6 +20,21 @@ export function readTextFileIfExists(file: string): string | null;
|
|
|
20
20
|
export function listProjectSourceFiles(root: string, generatedModule: string, options?: {
|
|
21
21
|
stylesheets?: boolean;
|
|
22
22
|
}): string[];
|
|
23
|
+
/**
|
|
24
|
+
* Each file in the project's own source that still uses the placeholder
|
|
25
|
+
* component the CLI writes for a missing icon: the file, the component's
|
|
26
|
+
* name, the meanings its uses name (null when none names one), and how many
|
|
27
|
+
* uses it has. A file reads only as a syntax tree, so a commented-out or
|
|
28
|
+
* quoted copy never counts. Read without executing app code.
|
|
29
|
+
* @param {string} projectDir
|
|
30
|
+
* @returns {Array<{file: string, local: string, meanings: string[] | null, uses: number}>}
|
|
31
|
+
*/
|
|
32
|
+
export function findIconPlaceholders(projectDir: string): Array<{
|
|
33
|
+
file: string;
|
|
34
|
+
local: string;
|
|
35
|
+
meanings: string[] | null;
|
|
36
|
+
uses: number;
|
|
37
|
+
}>;
|
|
23
38
|
/** @param {string} projectDir */
|
|
24
39
|
export function discoverDoctorThemes(projectDir: string): Promise<import("../../foundation/discovery/theme-discovery.mjs").DiscoveredTheme[]>;
|
|
25
40
|
/**
|
|
@@ -231,6 +246,8 @@ export function readBuiltPath(target: string, { fileLimit }?: {
|
|
|
231
246
|
* @returns {string[]}
|
|
232
247
|
*/
|
|
233
248
|
export function builtIntegrationPackages(target: string, cwd: string): string[];
|
|
249
|
+
export { placeholderFixes } from "../../foundation/discovery/template-icons.mjs";
|
|
250
|
+
export { unlistedIconSets } from "../../foundation/discovery/icon-set-discovery.mjs";
|
|
234
251
|
export { resolveRecordedTheme } from "../theme/theme.mjs";
|
|
235
252
|
export { collectUnloadedFonts } from "../theme/build/font-warning.mjs";
|
|
236
253
|
export const SOURCE_EXTENSIONS: Set<string>;
|
package/api/doctor/_adapter.mjs
CHANGED
|
@@ -39,6 +39,9 @@ import {
|
|
|
39
39
|
} from '../../foundation/agent-docs/computer-agent-text.mjs';
|
|
40
40
|
import {computerAgentTextSettings} from '../../foundation/env/user-settings.mjs';
|
|
41
41
|
import * as fs from 'node:fs';
|
|
42
|
+
import {placeholderUses} from '../../foundation/discovery/template-icons.mjs';
|
|
43
|
+
export {placeholderFixes} from '../../foundation/discovery/template-icons.mjs';
|
|
44
|
+
export {unlistedIconSets} from '../../foundation/discovery/icon-set-discovery.mjs';
|
|
42
45
|
import {elementPropOrigins} from '../../foundation/discovery/module-exports.mjs';
|
|
43
46
|
import {cssImports} from '../../foundation/render/embedded-imports.mjs';
|
|
44
47
|
import * as path from 'node:path';
|
|
@@ -150,6 +153,43 @@ export function listProjectSourceFiles(root, generatedModule, options = {}) {
|
|
|
150
153
|
return files;
|
|
151
154
|
}
|
|
152
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Each file in the project's own source that still uses the placeholder
|
|
158
|
+
* component the CLI writes for a missing icon: the file, the component's
|
|
159
|
+
* name, the meanings its uses name (null when none names one), and how many
|
|
160
|
+
* uses it has. A file reads only as a syntax tree, so a commented-out or
|
|
161
|
+
* quoted copy never counts. Read without executing app code.
|
|
162
|
+
* @param {string} projectDir
|
|
163
|
+
* @returns {Array<{file: string, local: string, meanings: string[] | null, uses: number}>}
|
|
164
|
+
*/
|
|
165
|
+
export function findIconPlaceholders(projectDir) {
|
|
166
|
+
/** @type {Array<{file: string, local: string, meanings: string[] | null, uses: number}>} */
|
|
167
|
+
const found = [];
|
|
168
|
+
for (const file of listProjectSourceFiles(projectDir, '')) {
|
|
169
|
+
let source;
|
|
170
|
+
try {
|
|
171
|
+
source = readTextFile(file);
|
|
172
|
+
} catch {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
// Only a file with the placeholder's dash pattern is parsed.
|
|
176
|
+
if (!source.includes('3 2.33')) continue;
|
|
177
|
+
const uses = placeholderUses(source);
|
|
178
|
+
if (!uses || uses.length === 0) continue;
|
|
179
|
+
const shown = path.relative(projectDir, file).split(path.sep).join('/');
|
|
180
|
+
const meanings = [
|
|
181
|
+
...new Set(uses.flatMap(each => each.meanings ?? [])),
|
|
182
|
+
].sort();
|
|
183
|
+
found.push({
|
|
184
|
+
file: shown,
|
|
185
|
+
local: uses[0].local,
|
|
186
|
+
meanings: meanings.length > 0 ? meanings : null,
|
|
187
|
+
uses: uses.length,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return found.sort((a, b) => a.file.localeCompare(b.file));
|
|
191
|
+
}
|
|
192
|
+
|
|
153
193
|
/** @param {string} projectDir */
|
|
154
194
|
export async function discoverDoctorThemes(projectDir) {
|
|
155
195
|
const project = await Project.load(projectDir, {core: 'project'});
|
package/api/doctor/doctor.d.mts
CHANGED
|
@@ -87,16 +87,25 @@ export function checkPeerDeps(ctx: DoctorContext): DoctorCheck;
|
|
|
87
87
|
*/
|
|
88
88
|
export function checkIntegrationIssues(ctx: DoctorContext): DoctorCheck;
|
|
89
89
|
/**
|
|
90
|
-
* The project's icon sets (AST-084 FR17). Fails when `experimental.iconSet`
|
|
91
|
-
* that is not installed,
|
|
92
|
-
*
|
|
93
|
-
*
|
|
90
|
+
* The project's icon sets (AST-084 FR17). Fails when `experimental.iconSet`
|
|
91
|
+
* names a set, main or backup, that is not installed, that is installed but
|
|
92
|
+
* withdrawn (with why), or that two packages ship under one name; and when
|
|
93
|
+
* more than one set is installed and the list leaves one out. Warns for each
|
|
94
|
+
* other installed set that was withdrawn, with why.
|
|
94
95
|
* Icon sets are experimental (spec:AST-017 FR27): only a project that sets
|
|
95
96
|
* `experimental.iconSet` gets the check, so every other report is unchanged.
|
|
96
97
|
* @param {DoctorContext} ctx
|
|
97
98
|
* @returns {DoctorCheck | null}
|
|
98
99
|
*/
|
|
99
100
|
export function checkIconSets(ctx: DoctorContext): DoctorCheck | null;
|
|
101
|
+
/**
|
|
102
|
+
* The placeholder component for a missing icon, still in the project's own
|
|
103
|
+
* source (AST-084): each file and the meanings it stands for, with the commands that give each a
|
|
104
|
+
* real icon. Only a project that sets `experimental.iconSet` gets the check.
|
|
105
|
+
* @param {DoctorContext} ctx
|
|
106
|
+
* @returns {DoctorCheck | null}
|
|
107
|
+
*/
|
|
108
|
+
export function checkIconPlaceholders(ctx: DoctorContext): DoctorCheck | null;
|
|
100
109
|
/**
|
|
101
110
|
* Check 10 — report the detected package manager, and say so when the project's
|
|
102
111
|
* own declaration disagrees with what is on disk.
|
|
@@ -448,8 +457,29 @@ export type DoctorContext = {
|
|
|
448
457
|
status: "selected" | "none" | "ambiguous" | "missing" | "withdrawn";
|
|
449
458
|
reason: string;
|
|
450
459
|
configured?: string;
|
|
460
|
+
backups?: Array<{
|
|
461
|
+
selector: string;
|
|
462
|
+
status: "selected" | "ambiguous" | "missing" | "withdrawn";
|
|
463
|
+
reason: string;
|
|
464
|
+
}>;
|
|
465
|
+
order?: Array<{
|
|
466
|
+
name: string;
|
|
467
|
+
package: string;
|
|
468
|
+
}>;
|
|
451
469
|
};
|
|
452
470
|
} | null | undefined;
|
|
471
|
+
/**
|
|
472
|
+
* Each file in the project's own source that uses the placeholder component
|
|
473
|
+
* the CLI writes for a missing icon, read only in a project that sets
|
|
474
|
+
* `experimental.iconSet`; null otherwise.
|
|
475
|
+
* Experimental: may change or be removed in a patch release.
|
|
476
|
+
*/
|
|
477
|
+
iconPlaceholders?: {
|
|
478
|
+
file: string;
|
|
479
|
+
local: string;
|
|
480
|
+
meanings: string[] | null;
|
|
481
|
+
uses: number;
|
|
482
|
+
}[] | null | undefined;
|
|
453
483
|
/**
|
|
454
484
|
* - Why the CLI could not load the
|
|
455
485
|
* project from its config, when it could not. Checks that need the loaded
|
package/api/doctor/doctor.mjs
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
} from '../../foundation/agent-docs/agent-docs.mjs';
|
|
43
43
|
import {
|
|
44
44
|
CLI_ROOT,
|
|
45
|
+
findPackageRoot,
|
|
45
46
|
findProjectCoreDir,
|
|
46
47
|
findInstalledPackage,
|
|
47
48
|
} from '../../foundation/fs/paths.mjs';
|
|
@@ -107,6 +108,9 @@ import {
|
|
|
107
108
|
projectCoreVersion,
|
|
108
109
|
projectHasOwnCli,
|
|
109
110
|
computerIntegrationsOutside,
|
|
111
|
+
findIconPlaceholders,
|
|
112
|
+
placeholderFixes,
|
|
113
|
+
unlistedIconSets,
|
|
110
114
|
computerIntegrationsToAdd,
|
|
111
115
|
displayHomePath,
|
|
112
116
|
outsideProject,
|
|
@@ -157,9 +161,14 @@ import {checkBuilt} from './built-checks.mjs';
|
|
|
157
161
|
* Combined project-level integration issues, including cross-package template replacement warnings.
|
|
158
162
|
* @property {Error|null} [configError] - Error thrown while resolving the config
|
|
159
163
|
* path (e.g. multiple config files present), surfaced by checkConfig as a FAIL.
|
|
160
|
-
* @property {{sets: Array<{name: string, package: string}>, withdrawn: Array<{name: string, package: string, reason: string}>, choice: {status: 'selected'|'none'|'ambiguous'|'missing'|'withdrawn', reason: string, configured?: string}}|null} [iconSets]
|
|
164
|
+
* @property {{sets: Array<{name: string, package: string}>, withdrawn: Array<{name: string, package: string, reason: string}>, choice: {status: 'selected'|'none'|'ambiguous'|'missing'|'withdrawn', reason: string, configured?: string, backups?: Array<{selector: string, status: 'selected'|'ambiguous'|'missing'|'withdrawn', reason: string}>, order?: Array<{name: string, package: string}>}}|null} [iconSets]
|
|
161
165
|
* The project's icon sets (AST-084 FR17), or null when they could not be read.
|
|
162
166
|
* Experimental: may change or be removed in a patch release.
|
|
167
|
+
* @property {Array<{file: string, local: string, meanings: string[] | null, uses: number}>|null} [iconPlaceholders]
|
|
168
|
+
* Each file in the project's own source that uses the placeholder component
|
|
169
|
+
* the CLI writes for a missing icon, read only in a project that sets
|
|
170
|
+
* `experimental.iconSet`; null otherwise.
|
|
171
|
+
* Experimental: may change or be removed in a patch release.
|
|
163
172
|
* @property {string|null} [projectError] - Why the CLI could not load the
|
|
164
173
|
* project from its config, when it could not. Checks that need the loaded
|
|
165
174
|
* project quote it when they skip, so a skip says what was found.
|
|
@@ -996,10 +1005,11 @@ export function checkIntegrationIssues(ctx) {
|
|
|
996
1005
|
}
|
|
997
1006
|
|
|
998
1007
|
/**
|
|
999
|
-
* The project's icon sets (AST-084 FR17). Fails when `experimental.iconSet`
|
|
1000
|
-
* that is not installed,
|
|
1001
|
-
*
|
|
1002
|
-
*
|
|
1008
|
+
* The project's icon sets (AST-084 FR17). Fails when `experimental.iconSet`
|
|
1009
|
+
* names a set, main or backup, that is not installed, that is installed but
|
|
1010
|
+
* withdrawn (with why), or that two packages ship under one name; and when
|
|
1011
|
+
* more than one set is installed and the list leaves one out. Warns for each
|
|
1012
|
+
* other installed set that was withdrawn, with why.
|
|
1003
1013
|
* Icon sets are experimental (spec:AST-017 FR27): only a project that sets
|
|
1004
1014
|
* `experimental.iconSet` gets the check, so every other report is unchanged.
|
|
1005
1015
|
* @param {DoctorContext} ctx
|
|
@@ -1013,38 +1023,66 @@ export function checkIconSets(ctx) {
|
|
|
1013
1023
|
const {sets, withdrawn, choice} = state;
|
|
1014
1024
|
// Only a project that opts in with `experimental.iconSet` gets the check.
|
|
1015
1025
|
if (!choice.configured) return null;
|
|
1016
|
-
|
|
1026
|
+
const list = [
|
|
1027
|
+
{selector: choice.configured, status: choice.status, reason: choice.reason},
|
|
1028
|
+
...(choice.backups ?? []),
|
|
1029
|
+
];
|
|
1030
|
+
const installFix =
|
|
1031
|
+
'Install the package that ships the set, or remove it from experimental.iconSet in astryx.config (`astryx experimental icon --list` names the installed sets).';
|
|
1032
|
+
const missing = list.filter(each => each.status === 'missing');
|
|
1033
|
+
if (missing.length > 0) {
|
|
1017
1034
|
return {
|
|
1018
1035
|
id,
|
|
1019
1036
|
label,
|
|
1020
1037
|
status: 'fail',
|
|
1021
|
-
message:
|
|
1022
|
-
fix:
|
|
1038
|
+
message: missing.map(each => each.reason).join(' | '),
|
|
1039
|
+
fix: installFix,
|
|
1023
1040
|
};
|
|
1024
1041
|
}
|
|
1025
|
-
|
|
1042
|
+
const gone = list.filter(each => each.status === 'withdrawn');
|
|
1043
|
+
if (gone.length > 0) {
|
|
1026
1044
|
return {
|
|
1027
1045
|
id,
|
|
1028
1046
|
label,
|
|
1029
1047
|
status: 'fail',
|
|
1030
|
-
message:
|
|
1031
|
-
fix: 'Run `astryx integration check` in the package that ships the set, or install the module its data names; or
|
|
1048
|
+
message: gone.map(each => each.reason).join(' | '),
|
|
1049
|
+
fix: 'Run `astryx integration check` in the package that ships the set, or install the module its data names; or remove it from experimental.iconSet in astryx.config (`astryx experimental icon --list` names the valid installed sets).',
|
|
1032
1050
|
};
|
|
1033
1051
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1052
|
+
const shared = list.filter(each => each.status === 'ambiguous');
|
|
1053
|
+
if (shared.length > 0) {
|
|
1054
|
+
return {
|
|
1055
|
+
id,
|
|
1056
|
+
label,
|
|
1057
|
+
status: 'fail',
|
|
1058
|
+
message: shared.map(each => each.reason).join(' | '),
|
|
1059
|
+
fix: 'Write each name in experimental.iconSet as "<package>/<set>" (`astryx experimental icon --list` names them).',
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
const order = choice.order ?? [];
|
|
1063
|
+
const unlisted = sets.filter(set => !order.includes(set));
|
|
1064
|
+
if (sets.length > 1 && unlisted.length > 0) {
|
|
1065
|
+
const {problem, fix} = unlistedIconSets(
|
|
1066
|
+
sets.length,
|
|
1067
|
+
list[0].selector,
|
|
1068
|
+
list.slice(1).map(each => each.selector),
|
|
1069
|
+
unlisted.map(set => `${set.package}/${set.name}`),
|
|
1070
|
+
);
|
|
1071
|
+
return {
|
|
1072
|
+
id,
|
|
1073
|
+
label,
|
|
1074
|
+
status: 'fail',
|
|
1075
|
+
message: `${problem} (experimental.iconSet), so which set a copied template uses is not decided.`,
|
|
1076
|
+
fix,
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
if (withdrawn.length > 0) {
|
|
1039
1080
|
return {
|
|
1040
1081
|
id,
|
|
1041
1082
|
label,
|
|
1042
1083
|
status: 'warn',
|
|
1043
|
-
message:
|
|
1044
|
-
fix:
|
|
1045
|
-
choice.status === 'ambiguous'
|
|
1046
|
-
? 'Set experimental.iconSet in astryx.config to "<package>/<set>" so templates use one set.'
|
|
1047
|
-
: 'Run `astryx integration check` in the package that ships the set, or install its module.',
|
|
1084
|
+
message: withdrawn.map(each => each.reason).join(' | '),
|
|
1085
|
+
fix: 'Run `astryx integration check` in the package that ships the set, or install its module.',
|
|
1048
1086
|
};
|
|
1049
1087
|
}
|
|
1050
1088
|
return {
|
|
@@ -1055,6 +1093,48 @@ export function checkIconSets(ctx) {
|
|
|
1055
1093
|
};
|
|
1056
1094
|
}
|
|
1057
1095
|
|
|
1096
|
+
/**
|
|
1097
|
+
* The placeholder component for a missing icon, still in the project's own
|
|
1098
|
+
* source (AST-084): each file and the meanings it stands for, with the commands that give each a
|
|
1099
|
+
* real icon. Only a project that sets `experimental.iconSet` gets the check.
|
|
1100
|
+
* @param {DoctorContext} ctx
|
|
1101
|
+
* @returns {DoctorCheck | null}
|
|
1102
|
+
*/
|
|
1103
|
+
export function checkIconPlaceholders(ctx) {
|
|
1104
|
+
const choice = ctx.iconSets?.choice;
|
|
1105
|
+
if (!choice?.configured || ctx.iconPlaceholders == null) return null;
|
|
1106
|
+
const id = 'icon-placeholders';
|
|
1107
|
+
const label = 'Placeholder icons';
|
|
1108
|
+
const found = ctx.iconPlaceholders;
|
|
1109
|
+
if (found.length === 0) {
|
|
1110
|
+
return {
|
|
1111
|
+
id,
|
|
1112
|
+
label,
|
|
1113
|
+
status: 'pass',
|
|
1114
|
+
message: 'No copied code uses the placeholder for a missing icon.',
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
const main = choice.configured;
|
|
1118
|
+
const meanings = [
|
|
1119
|
+
...new Set(found.flatMap(each => each.meanings ?? [])),
|
|
1120
|
+
].sort();
|
|
1121
|
+
const where = found
|
|
1122
|
+
.map(
|
|
1123
|
+
each =>
|
|
1124
|
+
`${each.file} (${each.meanings ? each.meanings.join(', ') : 'meaning not recorded'})`,
|
|
1125
|
+
)
|
|
1126
|
+
.join(', ');
|
|
1127
|
+
const uses = found.reduce((total, each) => total + each.uses, 0);
|
|
1128
|
+
const [mapFix, backupFix] = placeholderFixes(meanings, main);
|
|
1129
|
+
return {
|
|
1130
|
+
id,
|
|
1131
|
+
label,
|
|
1132
|
+
status: 'warn',
|
|
1133
|
+
message: `${uses} placeholder icon${uses === 1 ? '' : 's'} in copied code: no set in your list has ${meanings.length > 0 ? `"${meanings.join('", "')}"` : 'these icons'}. ${where}.`,
|
|
1134
|
+
fix: `Add a backup set that has each meaning (\`astryx discover --type icon-set\` lists sets and the definitions they map): \`${backupFix}\`; or, if you author your main set, map it there: \`${mapFix}\`. Then copy the template again, or replace each use of the placeholder (\`${found[0].local}\`) with the icon \`astryx experimental icon <meaning>\` prints, and delete its declaration. A built-in meaning (\`astryx experimental icon --definitions\` marks them) also renders from the theme by name: \`<Icon icon="<name>" />\`. Otherwise, use your own icon component (\`astryx docs icons custom-icons\`).`,
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1058
1138
|
/**
|
|
1059
1139
|
* Check 10 — report the detected package manager, and say so when the project's
|
|
1060
1140
|
* own declaration disagrees with what is on disk.
|
|
@@ -2151,6 +2231,7 @@ export const SYNC_CHECKS = [
|
|
|
2151
2231
|
checkProviderIdentity,
|
|
2152
2232
|
checkIntegrationIssues,
|
|
2153
2233
|
checkIconSets,
|
|
2234
|
+
checkIconPlaceholders,
|
|
2154
2235
|
checkAgentDocs,
|
|
2155
2236
|
checkPeerDeps,
|
|
2156
2237
|
checkPackageManager,
|
|
@@ -2218,6 +2299,8 @@ export async function runChecks(options = {}) {
|
|
|
2218
2299
|
let projectError = null;
|
|
2219
2300
|
/** @type {DoctorContext['iconSets']} */
|
|
2220
2301
|
let iconSets = null;
|
|
2302
|
+
/** @type {DoctorContext['iconPlaceholders']} */
|
|
2303
|
+
let iconPlaceholders = null;
|
|
2221
2304
|
/** @type {import('../../foundation/config/project.mjs').Project | null} */
|
|
2222
2305
|
let project = null;
|
|
2223
2306
|
try {
|
|
@@ -2315,6 +2398,9 @@ export async function runChecks(options = {}) {
|
|
|
2315
2398
|
}
|
|
2316
2399
|
try {
|
|
2317
2400
|
iconSets = await project.iconSets();
|
|
2401
|
+
if (iconSets.choice.configured) {
|
|
2402
|
+
iconPlaceholders = findIconPlaceholders(findPackageRoot(cwd) ?? cwd);
|
|
2403
|
+
}
|
|
2318
2404
|
} catch {
|
|
2319
2405
|
iconSets = null;
|
|
2320
2406
|
}
|
|
@@ -2344,6 +2430,7 @@ export async function runChecks(options = {}) {
|
|
|
2344
2430
|
configError,
|
|
2345
2431
|
projectError,
|
|
2346
2432
|
iconSets,
|
|
2433
|
+
iconPlaceholders,
|
|
2347
2434
|
};
|
|
2348
2435
|
|
|
2349
2436
|
/** @type {DoctorCheck[]} */
|
package/api/icon/_adapter.d.mts
CHANGED
|
@@ -43,14 +43,17 @@ export function loadIconProject(cwd: string): Promise<{
|
|
|
43
43
|
* `experimental.iconSet`, read from the project's package.json; null when
|
|
44
44
|
* nothing says so.
|
|
45
45
|
* @param {string} projectDir
|
|
46
|
+
* @param {{list?: boolean}} [options] `list`: the value written is a list
|
|
46
47
|
* @returns {string | null}
|
|
47
48
|
*/
|
|
48
|
-
export function iconSetConfigCliWarning(projectDir: string
|
|
49
|
+
export function iconSetConfigCliWarning(projectDir: string, options?: {
|
|
50
|
+
list?: boolean;
|
|
51
|
+
}): string | null;
|
|
49
52
|
export { astryxIconDefinitions } from "../../foundation/discovery/icon-definition-discovery.mjs";
|
|
50
|
-
export { iconSetKey } from "../../foundation/discovery/icon-set-discovery.mjs";
|
|
51
|
-
export { setConfigString } from "../../foundation/config/config-writer.mjs";
|
|
52
53
|
export { exportValueKind } from "../../foundation/discovery/module-exports.mjs";
|
|
53
54
|
export { iconRecord, importLine, leadingReading, matchIcon, answeringMatches, rankIcons, scoreIcon } from "../../foundation/discovery/icon-ranking.mjs";
|
|
55
|
+
export { iconSetKey, ICON_USE_ORDER_COMMAND, unlistedIconSets } from "../../foundation/discovery/icon-set-discovery.mjs";
|
|
56
|
+
export { setConfigString, setConfigValue } from "../../foundation/config/config-writer.mjs";
|
|
54
57
|
export { assertIconExport, checkNewDefinition, definitionNameFromInput, definitionFile, findLocalIconSet, writeDefinitionFile, iconIdOf, closestNames, libraryIcons, mappableDefinitions, resolveIconSource, generatedModulePath, generatedNamesOf, generatedOrigins, iconModulePackage, sameIconModule, iconExportName, iconNaming, modulePrefix, moduleNaming, iconNamingWithoutLook, splitLook, setModuleNote, variantExportName, writeGeneratedIcons, writeIconSetData, definitionNameOf, iconIdIn, iconsWithWord, localSourceNote, THEME_MAP_NAME } from "../../foundation/discovery/icon-set-writer.mjs";
|
|
55
58
|
export { addIconDefinition, addIconSet } from "./_authoring.mjs";
|
|
56
59
|
export { checkVariantLook, drawnLooks } from "./_default-look.mjs";
|
package/api/icon/_adapter.mjs
CHANGED
|
@@ -34,7 +34,14 @@ export {
|
|
|
34
34
|
} from '../../foundation/discovery/icon-ranking.mjs';
|
|
35
35
|
export {astryxIconDefinitions} from '../../foundation/discovery/icon-definition-discovery.mjs';
|
|
36
36
|
export {iconSetKey} from '../../foundation/discovery/icon-set-discovery.mjs';
|
|
37
|
-
export {
|
|
37
|
+
export {
|
|
38
|
+
setConfigString,
|
|
39
|
+
setConfigValue,
|
|
40
|
+
} from '../../foundation/config/config-writer.mjs';
|
|
41
|
+
export {
|
|
42
|
+
ICON_USE_ORDER_COMMAND,
|
|
43
|
+
unlistedIconSets,
|
|
44
|
+
} from '../../foundation/discovery/icon-set-discovery.mjs';
|
|
38
45
|
export {
|
|
39
46
|
assertIconExport,
|
|
40
47
|
checkNewDefinition,
|
|
@@ -177,9 +184,10 @@ function findProjectDir(cwd) {
|
|
|
177
184
|
* `experimental.iconSet`, read from the project's package.json; null when
|
|
178
185
|
* nothing says so.
|
|
179
186
|
* @param {string} projectDir
|
|
187
|
+
* @param {{list?: boolean}} [options] `list`: the value written is a list
|
|
180
188
|
* @returns {string | null}
|
|
181
189
|
*/
|
|
182
|
-
export function iconSetConfigCliWarning(projectDir) {
|
|
190
|
+
export function iconSetConfigCliWarning(projectDir, options = {}) {
|
|
183
191
|
let pkg;
|
|
184
192
|
try {
|
|
185
193
|
pkg = JSON.parse(
|
|
@@ -188,5 +196,5 @@ export function iconSetConfigCliWarning(projectDir) {
|
|
|
188
196
|
} catch {
|
|
189
197
|
return null;
|
|
190
198
|
}
|
|
191
|
-
return projectIconSetCliWarning(pkg);
|
|
199
|
+
return projectIconSetCliWarning(pkg, options);
|
|
192
200
|
}
|
package/api/icon/icon.type.d.mts
CHANGED
|
@@ -128,6 +128,14 @@ export type IconSetEntry = {
|
|
|
128
128
|
* - Whether this is the project's icon set.
|
|
129
129
|
*/
|
|
130
130
|
project: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* - The set's place in the project's list: its main set, a backup, or not listed.
|
|
133
|
+
*/
|
|
134
|
+
role: "main" | "backup" | null;
|
|
135
|
+
/**
|
|
136
|
+
* - The set's position in the list, the main set at 1; null when it is not listed.
|
|
137
|
+
*/
|
|
138
|
+
rank: number | null;
|
|
131
139
|
};
|
|
132
140
|
/**
|
|
133
141
|
* Which icon definitions one set's `map` covers.
|
|
@@ -170,6 +178,13 @@ export type IconListResponse = {
|
|
|
170
178
|
status: "selected" | "none" | "ambiguous" | "missing" | "withdrawn";
|
|
171
179
|
projectSet: string | null;
|
|
172
180
|
reason: string;
|
|
181
|
+
order: string[];
|
|
182
|
+
backups: Array<{
|
|
183
|
+
iconSet: string;
|
|
184
|
+
status: "selected" | "ambiguous" | "missing" | "withdrawn";
|
|
185
|
+
reason: string;
|
|
186
|
+
}>;
|
|
187
|
+
next?: string | undefined;
|
|
173
188
|
sets: IconSetEntry[];
|
|
174
189
|
withdrawn: IconWithdrawnEntry[];
|
|
175
190
|
};
|
|
@@ -182,10 +197,16 @@ export type IconUseResponse = {
|
|
|
182
197
|
data: {
|
|
183
198
|
iconSet: string;
|
|
184
199
|
set: string;
|
|
200
|
+
backups: Array<{
|
|
201
|
+
iconSet: string;
|
|
202
|
+
set: string;
|
|
203
|
+
}>;
|
|
185
204
|
config: string;
|
|
186
205
|
created: boolean;
|
|
187
|
-
previous: string | null;
|
|
206
|
+
previous: string | string[] | null;
|
|
188
207
|
changed: boolean;
|
|
208
|
+
unlisted: string[];
|
|
209
|
+
next?: string | undefined;
|
|
189
210
|
warning?: string | undefined;
|
|
190
211
|
};
|
|
191
212
|
};
|
package/api/icon/icon.type.mjs
CHANGED
|
@@ -84,6 +84,8 @@
|
|
|
84
84
|
* @property {string[] | null} variants - The variants the set declares: named looks such as outline or solid; size comes from CSS.
|
|
85
85
|
* @property {IconSetCoverage} coverage - Which icon definitions the set's `map` covers.
|
|
86
86
|
* @property {boolean} project - Whether this is the project's icon set.
|
|
87
|
+
* @property {'main' | 'backup' | null} role - The set's place in the project's list: its main set, a backup, or not listed.
|
|
88
|
+
* @property {number | null} rank - The set's position in the list, the main set at 1; null when it is not listed.
|
|
87
89
|
*/
|
|
88
90
|
|
|
89
91
|
/**
|
|
@@ -113,7 +115,10 @@
|
|
|
113
115
|
* @property {'selected' | 'none' | 'ambiguous' | 'missing' | 'withdrawn'} data.status - The project's icon-set state.
|
|
114
116
|
* @property {string | null} data.projectSet - The project's set, as `<package>/<set>`, when selected.
|
|
115
117
|
* @property {string} data.reason - Why the project has that set, or none.
|
|
116
|
-
* @property {
|
|
118
|
+
* @property {string[]} data.order - The project's list as `<package>/<set>`: the main set, then each backup that resolves; empty when no main set is selected.
|
|
119
|
+
* @property {Array<{iconSet: string, status: 'selected' | 'ambiguous' | 'missing' | 'withdrawn', reason: string}>} data.backups - Each backup the list names, and whether it resolves.
|
|
120
|
+
* @property {string} [data.next] - Present when two or more sets are installed and none is the main set: the command that chooses the order.
|
|
121
|
+
* @property {IconSetEntry[]} data.sets - Installed sets in list order: the main set, its backups, then the rest.
|
|
117
122
|
* @property {IconWithdrawnEntry[]} data.withdrawn - Sets found but withdrawn.
|
|
118
123
|
*/
|
|
119
124
|
|
|
@@ -123,12 +128,15 @@
|
|
|
123
128
|
* @typedef {object} IconUseResponse
|
|
124
129
|
* @property {'icon.use'} type
|
|
125
130
|
* @property {object} data
|
|
126
|
-
* @property {string} data.iconSet - The
|
|
127
|
-
* @property {string} data.set - The set it names, as `<package>/<set>`.
|
|
131
|
+
* @property {string} data.iconSet - The main set, as written to `experimental.iconSet` (alone, or first in the list).
|
|
132
|
+
* @property {string} data.set - The main set it names, as `<package>/<set>`.
|
|
133
|
+
* @property {Array<{iconSet: string, set: string}>} data.backups - The backup sets, in order, as written and as `<package>/<set>`; empty when there are none.
|
|
128
134
|
* @property {string} data.config - The config file, relative to the project.
|
|
129
135
|
* @property {boolean} data.created - Whether the config file was created.
|
|
130
|
-
* @property {string | null} data.previous - The `experimental.iconSet` value it replaced.
|
|
136
|
+
* @property {string | string[] | null} data.previous - The `experimental.iconSet` value it replaced.
|
|
131
137
|
* @property {boolean} data.changed - Whether anything was written.
|
|
138
|
+
* @property {string[]} data.unlisted - Installed sets the list leaves out, as `<package>/<set>`. Doctor fails a project that installs more than one set and leaves one out.
|
|
139
|
+
* @property {string} [data.next] - Present when `unlisted` is not empty: the command that adds them as backups.
|
|
132
140
|
* @property {string} [data.warning] - Present when the project's
|
|
133
141
|
* `@astryxdesign/cli` range admits a release before 0.7.4, which refuses
|
|
134
142
|
* an astryx.config that sets `experimental.iconSet`: what fails, and the
|
package/api/icon/iconUse.doc.mjs
CHANGED
|
@@ -10,13 +10,21 @@ export const doc = {
|
|
|
10
10
|
namespace: 'cli/api',
|
|
11
11
|
stability: 'experimental',
|
|
12
12
|
displayName: 'iconUse()',
|
|
13
|
-
summary: "Choose the project's icon set.",
|
|
13
|
+
summary: "Choose the project's main icon set and backups.",
|
|
14
14
|
description:
|
|
15
|
-
"Writes `experimental.iconSet` in the project's astryx.config (creating astryx.config.mjs when there is none), so templates and blocks the project copies use that set's icons.
|
|
15
|
+
"Writes `experimental.iconSet` in the project's astryx.config (creating astryx.config.mjs when there is none), so templates and blocks the project copies use that set's icons. With no backups it writes the set name as a string. With backups it writes a list, the main set first and then each backup in order: a copied template takes each icon from the first set in the list that has its meaning, and draws a placeholder the copied file declares when none has it. Every set must be installed; a name two installed packages both ship is refused with each `<package>/<set>` form. The receipt lists installed sets the list leaves out (`unlisted`), which Doctor fails, with the command that adds them. Experimental: it may change or be removed in a patch release.",
|
|
16
16
|
importPath: '@astryxdesign/cli/api',
|
|
17
17
|
signature:
|
|
18
|
-
'iconUse(set: string, options?: {cwd?: string}): Promise<IconUseResponse>',
|
|
19
|
-
keywords: [
|
|
18
|
+
'iconUse(set: string, options?: {cwd?: string, backups?: string[]}): Promise<IconUseResponse>',
|
|
19
|
+
keywords: [
|
|
20
|
+
'icon',
|
|
21
|
+
'icon set',
|
|
22
|
+
'use',
|
|
23
|
+
'backup',
|
|
24
|
+
'main set',
|
|
25
|
+
'experimental.iconSet',
|
|
26
|
+
'config',
|
|
27
|
+
],
|
|
20
28
|
params: [
|
|
21
29
|
{
|
|
22
30
|
name: 'set',
|
|
@@ -29,12 +37,18 @@ export const doc = {
|
|
|
29
37
|
type: 'string',
|
|
30
38
|
description: 'The project directory.',
|
|
31
39
|
},
|
|
40
|
+
{
|
|
41
|
+
name: 'options.backups',
|
|
42
|
+
type: 'string[]',
|
|
43
|
+
description:
|
|
44
|
+
'Backup sets, by name or `<package>/<set>`, in the order a copied template tries them after the main set.',
|
|
45
|
+
},
|
|
32
46
|
],
|
|
33
47
|
returns: [
|
|
34
48
|
{
|
|
35
49
|
type: 'icon.use',
|
|
36
50
|
description:
|
|
37
|
-
"The
|
|
51
|
+
"The main set and the backups written, the sets they name, the config file, whether it was created, the value it replaced, the installed sets the list leaves out (`unlisted`, with a `next` command), and a `warning` when the project's @astryxdesign/cli range admits a release that refuses the value: before 0.7.4 for a string, before 0.7.12 for a list.",
|
|
38
52
|
},
|
|
39
53
|
],
|
|
40
54
|
throws: [
|
|
@@ -44,7 +58,7 @@ export const doc = {
|
|
|
44
58
|
},
|
|
45
59
|
{
|
|
46
60
|
code: 'ERR_INVALID_ARGUMENT',
|
|
47
|
-
when: 'two installed packages ship a set with the name',
|
|
61
|
+
when: 'two installed packages ship a set with the name, or the list names one set twice',
|
|
48
62
|
},
|
|
49
63
|
{
|
|
50
64
|
code: 'ERR_WRITE_FAILED',
|
|
@@ -67,7 +81,13 @@ export const doc = {
|
|
|
67
81
|
when: 'the set name is missing',
|
|
68
82
|
},
|
|
69
83
|
],
|
|
70
|
-
examples: [
|
|
84
|
+
examples: [
|
|
85
|
+
{label: 'Use a set', code: "await iconUse('outline');"},
|
|
86
|
+
{
|
|
87
|
+
label: 'Use a main set with a backup',
|
|
88
|
+
code: "await iconUse('outline', {backups: ['@acme/brand-icons/brand']});",
|
|
89
|
+
},
|
|
90
|
+
],
|
|
71
91
|
command: 'experimental icon use',
|
|
72
92
|
related: ['icon', 'iconMap', 'iconAdd'],
|
|
73
93
|
};
|
package/api/icon/list/list.mjs
CHANGED
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file `icon.list` leaf (AST-084 FR7): every installed icon set with the
|
|
5
|
-
* icon definitions its `map` covers, the project's set
|
|
6
|
-
* withdrawn with why.
|
|
5
|
+
* icon definitions its `map` covers, the project's main set and backups in
|
|
6
|
+
* order, and the sets withdrawn with why.
|
|
7
7
|
*
|
|
8
8
|
* @output the `icon.list` envelope
|
|
9
9
|
* @position api/icon/list — dispatched by ../icon.mjs
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
astryxIconDefinitions,
|
|
14
|
+
ICON_USE_ORDER_COMMAND,
|
|
15
|
+
iconSetKey,
|
|
16
|
+
loadIconSets,
|
|
17
|
+
} from '../_adapter.mjs';
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
20
|
* List the installed icon sets.
|
|
@@ -20,6 +25,8 @@ export async function iconList({cwd}) {
|
|
|
20
25
|
const {sets, withdrawn, choice} = await loadIconSets(cwd);
|
|
21
26
|
const astryx = astryxIconDefinitions().definitions.map(each => each.doc);
|
|
22
27
|
const builtIn = astryx.filter(doc => doc.builtIn).map(doc => doc.name);
|
|
28
|
+
const order = choice.status === 'selected' ? (choice.order ?? []) : [];
|
|
29
|
+
const unchosen = choice.status === 'ambiguous' && !choice.configured;
|
|
23
30
|
return {
|
|
24
31
|
type: 'icon.list',
|
|
25
32
|
data: {
|
|
@@ -28,7 +35,19 @@ export async function iconList({cwd}) {
|
|
|
28
35
|
? `${choice.set.package}/${choice.set.name}`
|
|
29
36
|
: null,
|
|
30
37
|
reason: choice.reason,
|
|
38
|
+
order: order.map(iconSetKey),
|
|
39
|
+
backups: (choice.backups ?? []).map(each => ({
|
|
40
|
+
iconSet: each.selector,
|
|
41
|
+
status: each.status,
|
|
42
|
+
reason: each.reason,
|
|
43
|
+
})),
|
|
44
|
+
...(unchosen
|
|
45
|
+
? {
|
|
46
|
+
next: `${sets.length} icon sets are installed and none is your main set: run \`${ICON_USE_ORDER_COMMAND}\`.`,
|
|
47
|
+
}
|
|
48
|
+
: {}),
|
|
31
49
|
sets: sets.map(set => {
|
|
50
|
+
const rank = order.indexOf(set);
|
|
32
51
|
const mapped = new Set(Object.keys(set.data.map ?? {}));
|
|
33
52
|
return {
|
|
34
53
|
name: set.name,
|
|
@@ -52,6 +71,13 @@ export async function iconList({cwd}) {
|
|
|
52
71
|
own: set.definitions.map(each => each.doc.name),
|
|
53
72
|
},
|
|
54
73
|
project: set === choice.set,
|
|
74
|
+
role:
|
|
75
|
+
rank === 0
|
|
76
|
+
? /** @type {const} */ ('main')
|
|
77
|
+
: rank > 0
|
|
78
|
+
? /** @type {const} */ ('backup')
|
|
79
|
+
: null,
|
|
80
|
+
rank: rank === -1 ? null : rank + 1,
|
|
55
81
|
};
|
|
56
82
|
}),
|
|
57
83
|
withdrawn: withdrawn.map(each => ({
|