@astryxdesign/cli 0.7.11 → 0.7.12-canary.ccb4a6d

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