@astryxdesign/cli 0.7.12-canary.f543ec7 → 0.7.12

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 (113) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/api/build/blocks/blocks.d.mts +41 -0
  3. package/api/build/blocks/blocks.mjs +118 -68
  4. package/api/component/__fixtures__/resolutions/modules.txt +3 -3
  5. package/api/docs/_adapter.d.mts +22 -0
  6. package/api/docs/_adapter.mjs +81 -3
  7. package/api/docs/docs.doc.mjs +5 -5
  8. package/api/docs/docs.type.d.mts +21 -0
  9. package/api/docs/docs.type.mjs +11 -0
  10. package/api/docs/list/list.d.mts +5 -1
  11. package/api/docs/list/list.mjs +72 -6
  12. package/api/docs/node/node.mjs +2 -40
  13. package/api/doctor/doctor.d.mts +4 -4
  14. package/api/doctor/doctor.mjs +248 -78
  15. package/api/icon/_authoring.mjs +8 -1
  16. package/api/icon/add/add.mjs +45 -5
  17. package/api/icon/icon.type.d.mts +24 -0
  18. package/api/icon/icon.type.mjs +6 -0
  19. package/api/icon/iconAdd.doc.mjs +1 -1
  20. package/api/icon/map/map.mjs +11 -4
  21. package/api/index.d.mts +1 -0
  22. package/api/index.mjs +1 -0
  23. package/api/integration/add-helpers.d.mts +5 -1
  24. package/api/integration/add-helpers.mjs +1 -1
  25. package/api/render/_adapter.mjs +9 -0
  26. package/api/render/render.doc.mjs +3 -3
  27. package/api/render/render.type.d.mts +1 -1
  28. package/api/render/render.type.mjs +1 -1
  29. package/api/search/ORDERING.md +3 -0
  30. package/api/search/_adapter.d.mts +32 -3
  31. package/api/search/_adapter.mjs +50 -22
  32. package/api/search/_fit-order.d.mts +47 -0
  33. package/api/search/_fit-order.mjs +257 -0
  34. package/api/search/_main-searcher.mjs +15 -5
  35. package/api/search/exportSearchIndex.doc.d.mts +10 -0
  36. package/api/search/exportSearchIndex.doc.mjs +58 -0
  37. package/api/search/rankSearchIndex.doc.d.mts +10 -0
  38. package/api/search/rankSearchIndex.doc.mjs +79 -0
  39. package/api/search/search-index.d.mts +44 -0
  40. package/api/search/search-index.mjs +270 -0
  41. package/api/search/search.type.d.mts +28 -0
  42. package/api/search/search.type.mjs +17 -0
  43. package/api/swizzle/_moduleGraph.d.mts +7 -0
  44. package/api/swizzle/_moduleGraph.mjs +191 -83
  45. package/api/swizzle/copy/copy.mjs +28 -6
  46. package/api/template/skeleton/skeleton.mjs +1 -1
  47. package/api/template/template.mjs +1 -0
  48. package/assets/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +1 -1
  49. package/assets/codemods/transforms/v0.1.0/index.mjs +1 -1
  50. package/assets/discover/official-packages.json +16 -0
  51. package/assets/docs/styling-overview.doc.mjs +3 -3
  52. package/assets/docs/tree/icon-sets.doc.mjs +4 -4
  53. package/assets/docs/tree/render-pages.doc.mjs +3 -3
  54. package/assets/docs/tree/tailwind.doc.mjs +41 -1
  55. package/assets/docs/tree/type-scale.doc.mjs +19 -2
  56. package/assets/templates/blocks/components/Hooks/useInputStatusIconHookUsage.tsx +1 -0
  57. package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.doc.mjs +14 -0
  58. package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.tsx +56 -0
  59. package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.doc.mjs +2 -2
  60. package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.tsx +66 -10
  61. package/assets/templates/blocks/components/Link/useLinkifyHookUsage.doc.mjs +14 -0
  62. package/assets/templates/blocks/components/Link/useLinkifyHookUsage.tsx +39 -0
  63. package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.doc.mjs +14 -0
  64. package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.tsx +83 -0
  65. package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.doc.mjs +14 -0
  66. package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.tsx +41 -0
  67. package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.doc.mjs +14 -0
  68. package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.tsx +97 -0
  69. package/assets/templates/pages/table-filter/page.tsx +15 -23
  70. package/assets/templates/pages/table-grouped/page.tsx +30 -30
  71. package/assets/templates/pages/table-inbox/page.tsx +3 -6
  72. package/assets/templates/themes/butter/butterTheme.ts +1 -1
  73. package/assets/templates/themes/gothic/gothicTheme.ts +16 -0
  74. package/assets/templates/themes/matcha/matchaTheme.ts +1 -1
  75. package/assets/templates/themes/neutral/neutralTheme.ts +6 -0
  76. package/assets/templates/themes/y2k/y2kPaletteRefs.generated.ts +1 -1
  77. package/assets/templates/themes/y2k/y2kTheme.ts +1 -1
  78. package/clients/cli/commands/docs.doc.mjs +5 -1
  79. package/clients/cli/commands/docs.mjs +30 -2
  80. package/clients/cli/commands/experimental/icon.mjs +25 -4
  81. package/clients/cli/commands/experimental-render.doc.mjs +1 -1
  82. package/clients/cli/commands/template.mjs +204 -181
  83. package/clients/cli/lib/resolve-theme.mjs +16 -7
  84. package/foundation/discovery/component-discovery.mjs +1 -1
  85. package/foundation/discovery/hook-discovery.mjs +31 -8
  86. package/foundation/discovery/icon-set-writer.d.mts +21 -5
  87. package/foundation/discovery/icon-set-writer.mjs +114 -13
  88. package/foundation/discovery/template-adapter.mjs +2 -2
  89. package/foundation/doc-compiler/import.d.mts +5 -1
  90. package/foundation/doc-compiler/import.mjs +40 -6
  91. package/foundation/fs/import-url.d.mts +20 -0
  92. package/foundation/fs/import-url.mjs +22 -0
  93. package/foundation/fs/module-loader.d.mts +24 -3
  94. package/foundation/fs/module-loader.mjs +69 -47
  95. package/foundation/render/charts.d.mts +59 -0
  96. package/foundation/render/charts.mjs +216 -0
  97. package/foundation/render/embedded-imports.d.mts +7 -7
  98. package/foundation/render/embedded-imports.mjs +63 -54
  99. package/foundation/render/page-host.d.mts +4 -0
  100. package/foundation/render/page-host.mjs +45 -0
  101. package/foundation/render/page-worker.mjs +350 -57
  102. package/foundation/render/render-page.d.mts +11 -6
  103. package/foundation/render/render-page.mjs +164 -54
  104. package/foundation/render/ts-paths.d.mts +63 -0
  105. package/foundation/render/ts-paths.mjs +138 -0
  106. package/foundation/response/error-codes.doc.mjs +3 -3
  107. package/foundation/response/error-codes.mjs +3 -2
  108. package/foundation/text/levenshtein.d.mts +6 -1
  109. package/foundation/text/levenshtein.mjs +25 -9
  110. package/foundation/text/suggest-order.d.mts +8 -1
  111. package/foundation/text/suggest-order.mjs +72 -6
  112. package/foundation/xle/registry-core.mjs +1 -1
  113. package/package.json +7 -6
@@ -0,0 +1,270 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file A stored search index: the candidates `search` gathers in a folder, as
5
+ * plain JSON, and the ranking of stored candidates with search's own tree.
6
+ *
7
+ * Gathering reads every package's docs and loads its manifest, which runs the
8
+ * package's code. A catalog that lists packages it must not load (a web page
9
+ * that lists every published integration) stores each package's index once,
10
+ * where loading is safe, and ranks the stored indexes per query.
11
+ * @position api/search; search.mjs is the folder-reading search.
12
+ */
13
+
14
+ import {
15
+ docPackageManager,
16
+ runWithDocPackageManager,
17
+ } from '../../foundation/env/package-manager.mjs';
18
+ import {withListingCache} from '../../foundation/fs/listing-cache.mjs';
19
+ import {cliVersion} from '../../foundation/release/run-context.mjs';
20
+ import {runTree} from '../../foundation/route/route.mjs';
21
+ import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
22
+ import {linkSearchResponse} from '../component/_adapter.mjs';
23
+ import {AstryxError} from '../error.mjs';
24
+ import {
25
+ gatherCandidates,
26
+ loadSearchData,
27
+ resolveSearchCore,
28
+ } from './_adapter.mjs';
29
+ import {checkSearchArgs} from './_result.mjs';
30
+ import {SEARCH_TREE} from './tree.mjs';
31
+
32
+ /**
33
+ * Fields a gathered candidate holds that point into the folder it was read
34
+ * from. They decide ownership while gathering and never rank, so an index
35
+ * leaves them out and reads the same wherever it was made.
36
+ */
37
+ const LOCAL_FIELDS = ['_docPath'];
38
+
39
+ /**
40
+ * A component candidate's description as `search` prints it: its inline
41
+ * `{@link}`, `{@pm}` and `{@command}` tokens resolved in the folder the index
42
+ * is made in. The raw description stays the one that is scored.
43
+ */
44
+ const DISPLAY_FIELD = '_displayDescription';
45
+
46
+ /** Stands for the Core the indexes were made with, which ranking never reads. */
47
+ const STORED_CORE = 'stored-index';
48
+
49
+ /**
50
+ * @param {Record<string, unknown>} candidate
51
+ * @returns {Record<string, unknown>}
52
+ */
53
+ function portable(candidate) {
54
+ /** @type {Record<string, unknown>} */
55
+ const out = JSON.parse(JSON.stringify(candidate));
56
+ for (const field of LOCAL_FIELDS) delete out[field];
57
+ return out;
58
+ }
59
+
60
+ /**
61
+ * The candidates `search` ranks in a folder, as plain JSON: store it, and rank
62
+ * it later with {@link rankSearchIndex}.
63
+ *
64
+ * Experimental (spec:AST-017 FR27): may change or be removed in a patch
65
+ * release.
66
+ * @experimental
67
+ * @param {object} [options]
68
+ * @param {string} [options.cwd] - The folder to gather from, as `search` reads it.
69
+ * @param {string} [options.package] - Keep only the candidates this npm package
70
+ * owns (its components, templates, themes, and doc parts).
71
+ * @returns {Promise<import('./search.type.mjs').SearchIndex>}
72
+ */
73
+ export async function exportSearchIndex(options = {}) {
74
+ const {cwd = process.cwd(), package: owner} = options;
75
+ if (owner != null && (typeof owner !== 'string' || owner.trim() === '')) {
76
+ throw new AstryxError(
77
+ `Invalid package "${owner}". Pass an npm package name.`,
78
+ undefined,
79
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
80
+ );
81
+ }
82
+ return runWithDocPackageManager(docPackageManager(cwd), () =>
83
+ withListingCache(async () => {
84
+ const gathered = await gatherCandidates({cwd});
85
+ const kept =
86
+ owner == null
87
+ ? gathered.all
88
+ : gathered.all.filter(candidate => candidate._package === owner);
89
+ const candidates = kept.map(candidate =>
90
+ portable(/** @type {Record<string, unknown>} */ (candidate)),
91
+ );
92
+ await addDisplayDescriptions(candidates, cwd);
93
+ return {
94
+ version: cliVersion(),
95
+ candidates,
96
+ ...(gathered.coreSkipped == null
97
+ ? {}
98
+ : {
99
+ coreSkipped: gathered.coreSkipped,
100
+ ...(gathered.coreSkippedCode == null
101
+ ? {}
102
+ : {coreSkippedCode: gathered.coreSkippedCode}),
103
+ }),
104
+ };
105
+ }),
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Resolves each component description's inline tokens as `search` does for
111
+ * the results it prints, and keeps the printed text beside the scored one.
112
+ * @param {Record<string, unknown>[]} candidates
113
+ * @param {string} cwd
114
+ */
115
+ async function addDisplayDescriptions(candidates, cwd) {
116
+ const linkable = candidates.filter(
117
+ candidate =>
118
+ candidate.domain === 'component' &&
119
+ typeof candidate.description === 'string' &&
120
+ candidate.description.includes('{@'),
121
+ );
122
+ if (linkable.length === 0) return;
123
+ const response = await linkSearchResponse(
124
+ /** @type {any} */ ({
125
+ type: 'search',
126
+ data: {
127
+ results: linkable.map(candidate => ({
128
+ domain: 'component',
129
+ package: candidate._package,
130
+ description: candidate.description,
131
+ })),
132
+ },
133
+ }),
134
+ cwd,
135
+ );
136
+ response.data.results.forEach(
137
+ (/** @type {any} */ result, /** @type {number} */ i) => {
138
+ if (result.description !== linkable[i].description)
139
+ linkable[i][DISPLAY_FIELD] = result.description;
140
+ },
141
+ );
142
+ }
143
+
144
+ /**
145
+ * Ranks stored indexes with search's tree. An index made in one folder answers
146
+ * as `search(query)` does there (a theme result's command is the in-project
147
+ * `theme add`). Indexes made in separate folders, such as one per package
148
+ * (made with `package`) and one for Core, are ranked together as one list; a
149
+ * doc that one package replaces or extends in another is not merged across
150
+ * them. Every index must come from this CLI's version, since the tree reads
151
+ * the candidates' shape. When every index was made where Core could not be
152
+ * had, the answer carries `meta.partial`, as `search` does.
153
+ *
154
+ * Experimental (spec:AST-017 FR27): may change or be removed in a patch
155
+ * release.
156
+ * @experimental
157
+ * @param {string} query - Free-text search term.
158
+ * @param {import('./search.type.mjs').SearchIndex | import('./search.type.mjs').SearchIndex[]} indexes
159
+ * @param {object} [options]
160
+ * @param {import('./search.type.mjs').SearchDomain} [options.type] - Restrict to one domain.
161
+ * @param {number} [options.limit] - Max results (default 20).
162
+ * @returns {Promise<import('./search.type.mjs').SearchResponse>}
163
+ */
164
+ export async function rankSearchIndex(query, indexes, options = {}) {
165
+ checkSearchArgs(query, options);
166
+ const list = Array.isArray(indexes) ? indexes : [indexes];
167
+ const version = cliVersion();
168
+ /** @type {any[]} */
169
+ const all = [];
170
+ for (const index of list) {
171
+ if (index == null || !Array.isArray(index.candidates)) {
172
+ throw new AstryxError(
173
+ 'A search index is {version, candidates}, as exportSearchIndex() returns it.',
174
+ undefined,
175
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
176
+ );
177
+ }
178
+ if (index.version !== version) {
179
+ throw new AstryxError(
180
+ `This search index was made by CLI ${index.version}; this is CLI ${version}. Make it again with this version's exportSearchIndex().`,
181
+ undefined,
182
+ ERROR_CODES.ERR_INVALID_ARGUMENT,
183
+ );
184
+ }
185
+ all.push(...index.candidates);
186
+ }
187
+ const {type} = options;
188
+ const wanted =
189
+ type == null ? all : all.filter(candidate => candidate.domain === type);
190
+ const {searchedComponents, coreSkipped} = coreFlags(list, type);
191
+ const response = /** @type {import('./search.type.mjs').SearchResponse} */ (
192
+ await runTree(
193
+ SEARCH_TREE,
194
+ {query, options: {type, limit: options.limit}},
195
+ {
196
+ all: wanted,
197
+ components: wanted.filter(
198
+ candidate => candidate.domain === 'component',
199
+ ),
200
+ searchedComponents,
201
+ outside: false,
202
+ coreSkipped,
203
+ coreSkippedCode: null,
204
+ data: loadSearchData(),
205
+ },
206
+ )
207
+ );
208
+ return withDisplayDescriptions(response, wanted);
209
+ }
210
+
211
+ /**
212
+ * The catalog flags `search` derives from Core for a request of `type`, by
213
+ * search's own rule (`resolveSearchCore`). Core is missing only when no index
214
+ * had it. An open search then names what it skipped. A docs or themes search
215
+ * never reads Core. A Core domain asked for by name throws the error that
216
+ * `search` throws.
217
+ * @param {import('./search.type.mjs').SearchIndex[]} list
218
+ * @param {import('./search.type.mjs').SearchDomain | undefined} type
219
+ * @returns {{searchedComponents: boolean, coreSkipped: string | null}}
220
+ */
221
+ function coreFlags(list, type) {
222
+ const missing =
223
+ list.length > 0 &&
224
+ list.every(index => typeof index.coreSkipped === 'string');
225
+ const lookup = () => {
226
+ if (!missing) return STORED_CORE;
227
+ throw Object.assign(
228
+ new Error(/** @type {string} */ (list[0].coreSkipped)),
229
+ {
230
+ code: list[0].coreSkippedCode ?? ERROR_CODES.ERR_CORE_NOT_FOUND,
231
+ },
232
+ );
233
+ };
234
+ const {coreSkipped, wants} = resolveSearchCore(type, lookup, STORED_CORE);
235
+ return {searchedComponents: wants('component'), coreSkipped};
236
+ }
237
+
238
+ /**
239
+ * The response with each component result's description as `search` prints
240
+ * it, from the text the index stored when it was made.
241
+ * @param {import('./search.type.mjs').SearchResponse} response
242
+ * @param {any[]} candidates
243
+ * @returns {import('./search.type.mjs').SearchResponse}
244
+ */
245
+ function withDisplayDescriptions(response, candidates) {
246
+ /** @type {Map<string, string>} */
247
+ const display = new Map();
248
+ for (const candidate of candidates) {
249
+ if (
250
+ candidate.domain === 'component' &&
251
+ typeof candidate[DISPLAY_FIELD] === 'string'
252
+ )
253
+ display.set(
254
+ `${candidate._package}\u0000${candidate._resultName ?? candidate.name}`,
255
+ candidate[DISPLAY_FIELD],
256
+ );
257
+ }
258
+ if (display.size === 0) return response;
259
+ return {
260
+ ...response,
261
+ data: {
262
+ ...response.data,
263
+ results: response.data.results.map(result => {
264
+ if (result.domain !== 'component') return result;
265
+ const text = display.get(`${result.package}\u0000${result.name}`);
266
+ return text == null ? result : {...result, description: text};
267
+ }),
268
+ },
269
+ };
270
+ }
@@ -95,6 +95,34 @@ export type SearchResponse = {
95
95
  };
96
96
  } | undefined;
97
97
  };
98
+ /**
99
+ * The candidates `search` ranks in a folder, as `exportSearchIndex()` returns them:
100
+ * plain JSON to store and rank later with `rankSearchIndex()`.
101
+ */
102
+ export type SearchIndex = {
103
+ /**
104
+ * - The CLI version that made it. `rankSearchIndex()`
105
+ * ranks only indexes made by its own version.
106
+ */
107
+ version: string;
108
+ /**
109
+ * - The gathered candidates,
110
+ * each the entry search scores. Their fields are the ranker's input, not a
111
+ * contract: they may change in any release, which is why `version` must match.
112
+ */
113
+ candidates: Record<string, unknown>[];
114
+ /**
115
+ * - Why Core could not be had where the index
116
+ * was made, when it could not: its components, hooks and templates are then
117
+ * missing, and `rankSearchIndex()` answers as `search` does without Core
118
+ * (`meta.partial` for an open search; refused for a domain that reads Core).
119
+ */
120
+ coreSkipped?: string | undefined;
121
+ /**
122
+ * - The error code that Core lookup gave.
123
+ */
124
+ coreSkippedCode?: string | undefined;
125
+ };
98
126
  /**
99
127
  * Options for `search()`.
100
128
  */
@@ -51,6 +51,23 @@
51
51
  * The results are the docs and themes.
52
52
  */
53
53
 
54
+ /**
55
+ * The candidates `search` ranks in a folder, as `exportSearchIndex()` returns them:
56
+ * plain JSON to store and rank later with `rankSearchIndex()`.
57
+ * @experimental May change or be removed in a patch release.
58
+ * @typedef {object} SearchIndex
59
+ * @property {string} version - The CLI version that made it. `rankSearchIndex()`
60
+ * ranks only indexes made by its own version.
61
+ * @property {Record<string, unknown>[]} candidates - The gathered candidates,
62
+ * each the entry search scores. Their fields are the ranker's input, not a
63
+ * contract: they may change in any release, which is why `version` must match.
64
+ * @property {string} [coreSkipped] - Why Core could not be had where the index
65
+ * was made, when it could not: its components, hooks and templates are then
66
+ * missing, and `rankSearchIndex()` answers as `search` does without Core
67
+ * (`meta.partial` for an open search; refused for a domain that reads Core).
68
+ * @property {string} [coreSkippedCode] - The error code that Core lookup gave.
69
+ */
70
+
54
71
  /**
55
72
  * Options for `search()`.
56
73
  * @typedef {object} SearchOptions
@@ -16,6 +16,8 @@
16
16
  * file the copy holds (absolute source path), with the edits its imports need
17
17
  * @property {Map<string, Set<string>>} dependsOn file -> carried files its
18
18
  * computed specifiers point at
19
+ * @property {Map<string, string>} otherPackages the source root of each other
20
+ * Astryx package the copy carries modules from, with its package name
19
21
  * @property {Record<string, string>} dependencies each npm package the copied
20
22
  * files import, other than the owner package and Node's built-in modules,
21
23
  * with the range the owner package declares
@@ -162,6 +164,11 @@ export type CopyPlan = {
162
164
  * computed specifiers point at
163
165
  */
164
166
  dependsOn: Map<string, Set<string>>;
167
+ /**
168
+ * the source root of each other
169
+ * Astryx package the copy carries modules from, with its package name
170
+ */
171
+ otherPackages: Map<string, string>;
165
172
  /**
166
173
  * each npm package the copied
167
174
  * files import, other than the owner package and Node's built-in modules,