@astryxdesign/cli 0.6.4-canary.ed2e54e → 0.6.4

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 (145) hide show
  1. package/README.md +66 -64
  2. package/api/component/_adapter.d.mts +0 -25
  3. package/api/component/_adapter.mjs +5 -59
  4. package/api/component/component.d.mts +3 -6
  5. package/api/component/component.doc.mjs +10 -23
  6. package/api/component/component.mjs +9 -249
  7. package/api/component/component.type.d.mts +0 -25
  8. package/api/component/component.type.mjs +0 -44
  9. package/api/discover/_adapter.d.mts +6 -114
  10. package/api/discover/_adapter.mjs +17 -372
  11. package/api/discover/detail/detail.d.mts +6 -18
  12. package/api/discover/detail/detail.mjs +13 -67
  13. package/api/discover/detail/detail.test.mjs +0 -85
  14. package/api/discover/discover.d.mts +9 -3
  15. package/api/discover/discover.doc.mjs +18 -61
  16. package/api/discover/discover.mjs +36 -220
  17. package/api/discover/discover.test.mjs +2 -11
  18. package/api/discover/discover.type.d.mts +8 -147
  19. package/api/discover/discover.type.mjs +12 -102
  20. package/api/discover/list/list.d.mts +6 -20
  21. package/api/discover/list/list.mjs +12 -45
  22. package/api/discover/list/list.test.mjs +0 -46
  23. package/api/discover/search/search.d.mts +16 -18
  24. package/api/discover/search/search.mjs +56 -102
  25. package/api/discover/search/search.test.mjs +10 -144
  26. package/api/docs/docs.test.mjs +0 -2
  27. package/api/doctor/doctor.d.mts +3 -8
  28. package/api/doctor/doctor.mjs +9 -90
  29. package/api/doctor/doctor.test.mjs +10 -122
  30. package/api/index.d.mts +2 -1
  31. package/api/index.mjs +4 -4
  32. package/api/integration/add-helpers.d.mts +2 -5
  33. package/api/integration/add-helpers.mjs +9 -36
  34. package/api/integration/pack-check.mjs +3 -28
  35. package/api/json/index.ts +1 -0
  36. package/api/layout/_adapter.d.mts +34 -0
  37. package/api/layout/_adapter.mjs +148 -0
  38. package/api/layout/check/check.d.mts +16 -0
  39. package/api/layout/check/check.mjs +40 -0
  40. package/api/layout/expand/expand.d.mts +22 -0
  41. package/api/layout/expand/expand.mjs +155 -0
  42. package/api/layout/expand/expand.path-safety.test.mjs +53 -0
  43. package/api/layout/grammar/grammar.d.mts +13 -0
  44. package/api/layout/grammar/grammar.mjs +87 -0
  45. package/api/layout/layout.d.mts +6 -0
  46. package/api/layout/layout.mjs +17 -0
  47. package/api/layout/layout.test.mjs +297 -0
  48. package/api/layout/layout.type.d.mts +89 -0
  49. package/api/layout/layout.type.mjs +103 -0
  50. package/api/layout/layoutCheck.doc.d.mts +11 -0
  51. package/api/layout/layoutCheck.doc.mjs +85 -0
  52. package/api/layout/layoutExpand.doc.d.mts +11 -0
  53. package/api/layout/layoutExpand.doc.mjs +107 -0
  54. package/api/layout/layoutGrammar.doc.d.mts +11 -0
  55. package/api/layout/layoutGrammar.doc.mjs +57 -0
  56. package/api/search/search.test.mjs +0 -18
  57. package/api/template/template-integration.test.mjs +65 -1
  58. package/api/template/template.mjs +1 -1
  59. package/api/theme/add/add.mjs +25 -17
  60. package/api/theme/add/add.staging.test.mjs +23 -40
  61. package/api/theme/build/build.family.test.mjs +12 -7
  62. package/api/theme/build/build.mjs +18 -8
  63. package/api/upgrade/run/run.mjs +4 -6
  64. package/api/upgrade/upgrade.type.mjs +2 -2
  65. package/assets/codemods/__tests__/runner.test.mjs +1 -3
  66. package/assets/codemods/integration-runner.mjs +3 -3
  67. package/assets/codemods/runner.mjs +4 -5
  68. package/assets/docs/internationalization.doc.mjs +5 -7
  69. package/assets/docs/tree/integrations.doc.mjs +1 -20
  70. package/assets/templates/blocks/components/InternationalizationProvider/InternationalizationProvider01ShippedLocale.tsx +1 -1
  71. package/authoring/config/config.doc.mjs +1 -9
  72. package/authoring/config/parse.d.mts +0 -2
  73. package/authoring/config/parse.mjs +0 -19
  74. package/authoring/config/parse.test.mjs +0 -8
  75. package/authoring/config/type.ts +2 -13
  76. package/authoring/doctypes/command/command.doc.mjs +1 -1
  77. package/authoring/doctypes/command/type.ts +1 -1
  78. package/authoring/index.d.mts +0 -1
  79. package/authoring/index.d.ts +0 -10
  80. package/authoring/index.mjs +0 -1
  81. package/clients/cli/command-result-coverage.test.mjs +7 -7
  82. package/clients/cli/commands/component/index.mjs +55 -152
  83. package/clients/cli/commands/component-ownership.test.mjs +0 -89
  84. package/clients/cli/commands/component.doc.mjs +6 -23
  85. package/clients/cli/commands/debug-result-summary.test.mjs +2 -2
  86. package/clients/cli/commands/discover.doc.mjs +9 -53
  87. package/clients/cli/commands/discover.mjs +118 -393
  88. package/clients/cli/commands/docs.test.mjs +0 -29
  89. package/clients/cli/commands/layout-check.doc.mjs +65 -0
  90. package/clients/cli/commands/layout-expand.doc.mjs +83 -0
  91. package/clients/cli/commands/layout-grammar.doc.mjs +30 -0
  92. package/clients/cli/commands/layout.doc.mjs +34 -0
  93. package/clients/cli/commands/layout.error-codes.test.mjs +66 -0
  94. package/clients/cli/commands/layout.exit-parity.test.mjs +41 -0
  95. package/clients/cli/commands/layout.mjs +275 -0
  96. package/clients/cli/commands/layout.path-help.test.mjs +33 -0
  97. package/clients/cli/commands/layout.stdin-cap.test.mjs +47 -0
  98. package/clients/cli/commands/layout.text-fields.test.mjs +39 -0
  99. package/clients/cli/commands/text-json-parity.test.mjs +17 -0
  100. package/clients/cli/index.mjs +4 -0
  101. package/clients/cli/lib/exit-codes.test.mjs +8 -1
  102. package/clients/cli/lib/json-shim.mjs +14 -24
  103. package/clients/cli/lib/json-shim.test.mjs +20 -6
  104. package/clients/cli/lib/manifest.mjs +8 -3
  105. package/clients/cli/lib/manifest.test.mjs +2 -5
  106. package/foundation/discovery/authoring-self-docs.mjs +0 -1
  107. package/foundation/discovery/template-adapter.mjs +1 -1
  108. package/foundation/doc-compiler/doc-loads.test.mjs +12 -0
  109. package/foundation/doc-compiler/tree.test.mjs +1 -9
  110. package/foundation/integrations/integrations.d.mts +1 -14
  111. package/foundation/integrations/integrations.mjs +1 -41
  112. package/foundation/integrations/integrations.test.mjs +0 -31
  113. package/foundation/response/response-types.doc.mjs +21 -15
  114. package/foundation/response/response-types.doc.test.mjs +0 -23
  115. package/foundation/xle/browser.d.mts +3 -3
  116. package/foundation/xle/browser.mjs +3 -3
  117. package/foundation/xle/expand.mjs +2 -2
  118. package/foundation/xle/parse.mjs +1 -1
  119. package/foundation/xle/print.mjs +2 -2
  120. package/foundation/xle/splice.mjs +1 -1
  121. package/package.json +9 -9
  122. package/api/discover/_adapter.test.mjs +0 -215
  123. package/api/discover/_catalog-view.d.mts +0 -115
  124. package/api/discover/_catalog-view.mjs +0 -203
  125. package/api/discover/_catalog-view.test.mjs +0 -128
  126. package/api/discover/detail/item/item.d.mts +0 -26
  127. package/api/discover/detail/item/item.mjs +0 -78
  128. package/api/discover/detail/item/item.test.mjs +0 -73
  129. package/api/integration/pack-check.lifecycle-output.test.mjs +0 -105
  130. package/api/theme/add/add.rollback.test.mjs +0 -158
  131. package/api/theme/build/build.rollback.test.mjs +0 -148
  132. package/api/upgrade/run/files-changed.test.mjs +0 -111
  133. package/assets/codemods/file-count.test.mjs +0 -163
  134. package/assets/docs/tree/component-lookups.doc.mjs +0 -149
  135. package/authoring/discover/discover.doc.d.mts +0 -13
  136. package/authoring/discover/discover.doc.mjs +0 -138
  137. package/authoring/discover/parse.d.mts +0 -24
  138. package/authoring/discover/parse.mjs +0 -128
  139. package/authoring/discover/parse.test.mjs +0 -124
  140. package/authoring/discover/type.ts +0 -87
  141. package/clients/cli/commands/component-batch.test.mjs +0 -341
  142. package/clients/cli/commands/discover.sources.test.mjs +0 -267
  143. package/clients/cli/lib/parse-error-format.test.mjs +0 -81
  144. package/foundation/response/batch.type.d.mts +0 -33
  145. package/foundation/response/batch.type.mjs +0 -34
@@ -1,31 +1,18 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  /**
4
- * @file discover command — browse the integrations you have and could add
4
+ * @file discover command — find external Astryx packages and components
5
5
  *
6
6
  * Usage:
7
- * astryx discover List installed and available packages
8
- * astryx discover @scope/name[@version] One package, with its versions
9
- * astryx discover @scope/name/Component Show docs for an installed component
10
- * astryx discover @scope/name/<item> One item a package adds
11
- * astryx discover searchterm Search every item and package
7
+ * astryx discover List all packages
8
+ * astryx discover @scope/name List components in a package
9
+ * astryx discover @scope/name/Component Show docs for a component
10
+ * astryx discover searchterm Search across all packages
12
11
  */
13
12
 
14
- import {
15
- formatFull,
16
- formatBrief,
17
- formatCompact,
18
- } from '../lib/component-format.mjs';
13
+ import {formatFull, formatBrief, formatCompact} from '../lib/component-format.mjs';
19
14
  import {jsonOut} from '../../../foundation/response/json.mjs';
20
- import {
21
- emit,
22
- section,
23
- text,
24
- record,
25
- records,
26
- list,
27
- code,
28
- } from '../formatters/index.mjs';
15
+ import {emit, section, text, record, records, list, code} from '../formatters/index.mjs';
29
16
  import {cliError} from '../lib/cli-error.mjs';
30
17
  import {discover as discoverApi} from '../../../api/discover/discover.mjs';
31
18
  import {Project} from '../../../foundation/config/project.mjs';
@@ -36,70 +23,25 @@ import {resultSet} from '../../../foundation/debug/index.mjs';
36
23
  import {doc as discoverCommand} from './discover.doc.mjs';
37
24
  import {doc as discoverFn} from '../../../api/discover/discover.doc.mjs';
38
25
 
39
- // Max items to list inline per package before summarizing with "+N more".
40
- const MAX_ITEMS_SHOWN = 10;
41
-
42
- /** @type {Record<string, string>} */
43
- const KIND_PLURALS = {
44
- component: 'components',
45
- template: 'templates',
46
- doc: 'docs',
47
- theme: 'themes',
48
- codemod: 'codemods',
49
- 'agent-doc': 'agent docs',
50
- };
51
-
52
- // The list fields that hold item names, summarized in the package list.
53
- const ITEM_FIELDS = [
54
- 'components',
55
- 'templates',
56
- 'docs',
57
- 'themes',
58
- 'codemods',
59
- 'agentDocs',
60
- ];
26
+ // Max components to list inline per package before summarizing with "+N more".
27
+ const MAX_COMPONENTS_SHOWN = 10;
61
28
 
62
29
  /**
63
- * @typedef {import('../../../api/discover/discover.type.mjs').DiscoverListResponse
30
+ * What the run answered with. `discover` browses the integration packages a
31
+ * project has configured, so the top two views count PACKAGES; naming a
32
+ * component inside one answers with that component instead.
33
+ *
34
+ * @param {import('../../../api/discover/discover.type.mjs').DiscoverListResponse
64
35
  * | import('../../../api/discover/discover.type.mjs').DiscoverDetailResponse
65
36
  * | import('../../../api/discover/discover.type.mjs').DiscoverDetailDocResponse
66
- * | import('../../../api/discover/discover.type.mjs').DiscoverItemResponse
67
- * | import('../../../api/discover/discover.type.mjs').DiscoverSearchResponse} DiscoverResult
68
- */
69
-
70
- /** @type {Record<string, import('../../../foundation/debug/command-result.mjs').ResultSetKind>} */
71
- const RESULT_KINDS = {
72
- package: 'integration',
73
- component: 'component',
74
- template: 'template',
75
- doc: 'doc',
76
- theme: 'theme',
77
- codemod: 'migration',
78
- 'agent-doc': 'doc',
79
- };
80
-
81
- /**
82
- * The run-log kind for a set of item kinds: the one kind, or `mixed`.
83
- * @param {string[]} kinds
84
- * @returns {import('../../../foundation/debug/command-result.mjs').ResultSetKind}
85
- */
86
- function resultKindOf(kinds) {
87
- const mapped = new Set(kinds.map(kind => RESULT_KINDS[kind] ?? 'mixed'));
88
- return mapped.size === 1 ? [...mapped][0] : 'mixed';
89
- }
90
-
91
- /**
92
- * What the run answered with. The list and package views count PACKAGES;
93
- * naming a component or item answers with that item instead.
94
- *
95
- * @param {DiscoverResult} result
37
+ * | import('../../../api/discover/discover.type.mjs').DiscoverSearchResponse} result
96
38
  * @returns {import('../../../foundation/debug/command-result.mjs').CommandResult}
97
39
  */
98
40
  function summarize(result) {
99
41
  switch (result.type) {
100
42
  case 'discover.list':
101
43
  return resultSet({
102
- count: result.data.length + (result.meta?.available?.length ?? 0),
44
+ count: result.data.length,
103
45
  resultKind: 'integration',
104
46
  });
105
47
  case 'discover.detail':
@@ -110,96 +52,14 @@ function summarize(result) {
110
52
  });
111
53
  case 'discover.detail.doc':
112
54
  return resultSet({count: 1, resultKind: 'component', directMatch: true});
113
- case 'discover.item':
114
- return resultSet({
115
- count: 1,
116
- resultKind: resultKindOf([result.data.kind]),
117
- directMatch: true,
118
- });
119
55
  case 'discover.search':
120
56
  return resultSet({
121
- count: result.data.total ?? result.data.matches.length,
122
- resultKind: resultKindOf(result.data.matches.map(m => m.kind)),
57
+ count: result.data.matches.length,
58
+ resultKind: 'component',
123
59
  });
124
60
  }
125
61
  }
126
62
 
127
- /**
128
- * Record options that summarize each item list to its first names.
129
- * @param {boolean} full print every name instead
130
- * @returns {import('../formatters/index.mjs').RecordOptions}
131
- */
132
- function itemListOptions(full) {
133
- if (full) return {};
134
- /** @param {string[]} names */
135
- const summary = names => {
136
- const shown = names.slice(0, MAX_ITEMS_SHOWN);
137
- const remaining = names.length - MAX_ITEMS_SHOWN;
138
- return remaining > 0
139
- ? `${shown.join(', ')}, +${remaining} more`
140
- : shown.join(', ');
141
- };
142
- return {
143
- format: Object.fromEntries(ITEM_FIELDS.map(field => [field, summary])),
144
- };
145
- }
146
-
147
- /**
148
- * One line per source that did not answer live.
149
- * @param {import('../../../api/discover/discover.type.mjs').DiscoverSourceState[]} sources
150
- */
151
- function sourceNotes(sources) {
152
- return sources
153
- .filter(s => s.status !== 'fresh')
154
- .map(s =>
155
- s.status === 'saved'
156
- ? text(
157
- `The discover source from ${s.from} did not answer (${s.error}), so this uses the copy saved ${s.savedAt}.`,
158
- )
159
- : text(`The discover source from ${s.from} is unavailable: ${s.error}`),
160
- );
161
- }
162
-
163
- /**
164
- * @param {import('../../../api/discover/discover.type.mjs').DiscoverSourceState[]} sources
165
- */
166
- function sourceSubtitle(sources) {
167
- const answered = sources.filter(s => s.status !== 'failed');
168
- if (answered.length === 0) return undefined;
169
- return (
170
- 'from ' +
171
- answered
172
- .map(
173
- s =>
174
- `${s.name}${s.generatedAt ? ` (${s.generatedAt.slice(0, 10)})` : ''}`,
175
- )
176
- .join(', ')
177
- );
178
- }
179
-
180
- /**
181
- * @param {import('../../../authoring/discover/type').DiscoverVersion[]} versions
182
- * @param {{shown?: string, installed?: string, latest?: string}} marks
183
- */
184
- function versionLines(versions, marks) {
185
- return versions
186
- .filter(v => !v.prerelease || v.version === marks.shown)
187
- .map(v => {
188
- const tags = [
189
- v.version === marks.latest ? 'latest' : '',
190
- v.version === marks.installed ? 'installed' : '',
191
- v.version === marks.shown &&
192
- v.version !== marks.installed &&
193
- v.version !== marks.latest
194
- ? 'shown'
195
- : '',
196
- v.status !== 'ok' ? v.status : '',
197
- ].filter(Boolean);
198
- const date = v.publishedAt ? v.publishedAt.slice(0, 10) : 'date unknown';
199
- return `${v.version} ${date}${tags.length ? ` ${tags.join(', ')}` : ''}`;
200
- });
201
- }
202
-
203
63
  /**
204
64
  * @param {import('commander').Command} program
205
65
  */
@@ -209,255 +69,120 @@ export function registerDiscover(program) {
209
69
  action:
210
70
  /**
211
71
  * @param {string | undefined} query
212
- * @param {{components?: boolean, type?: string, installed?: boolean, available?: boolean, limit?: string}} options
72
+ * @param {{components?: boolean}} options
213
73
  */
214
74
  async (query, options) => {
215
- const detail = program.opts().detail || 'full';
216
- const json = program.opts().json || false;
217
- const lang = program.opts().lang || null;
218
- const zh = program.opts().zh || false;
219
- const run = getCliInvocation();
220
-
221
- // Non-blocking nudge: if any configured integration has validation
222
- // issues, print one compact line to stderr pointing at
223
- // doctor integration validate. Best-effort; suppressed in --json mode.
224
- try {
225
- const project = await Project.load(process.cwd());
226
- await warnOnIntegrationIssues(project, {json});
227
- } catch {
228
- // Never let the nudge break the command.
229
- }
230
-
231
- /** @type {DiscoverResult} */
232
- let result;
233
- try {
234
- result = await discoverApi(query, {
235
- components: options.components,
236
- lang,
237
- zh,
238
- type: /** @type {any} */ (options.type),
239
- installed: options.installed,
240
- available: options.available,
241
- // Number(), not parseInt(): the API refuses `1.5` and `5abc` itself.
242
- limit: options.limit != null ? Number(options.limit) : undefined,
243
- });
244
- } catch (e) {
245
- const err =
246
- /** @type {import('../../../api/error.mjs').AstryxError} */ (e);
247
- return cliError(err.message, {
248
- suggestions: err.suggestions,
249
- code: err.code,
250
- });
251
- }
252
-
253
- const answered = summarize(result);
254
- if (json) {
255
- // Forward optional meta (e.g. configured flag for discover.list) as a
256
- // sibling of data via jsonOut, so the envelope still carries
257
- // apiVersion and goes through the single sanctioned emit path.
258
- jsonOut(result);
259
- return answered;
260
- }
261
-
262
- switch (result.type) {
263
- case 'discover.list': {
264
- const listOpts = itemListOptions(Boolean(options.components));
265
- const available = result.meta?.available;
266
- const sources = result.meta?.sources ?? [];
75
+ const detail = program.opts().detail || 'full';
76
+ const json = program.opts().json || false;
77
+ const lang = program.opts().lang || null;
78
+ const zh = program.opts().zh || false;
79
+ const run = getCliInvocation();
80
+
81
+ // Non-blocking nudge: if any configured integration has validation
82
+ // issues, print one compact line to stderr pointing at
83
+ // doctor integration validate. Best-effort; suppressed in --json mode.
84
+ try {
85
+ const project = await Project.load(process.cwd());
86
+ await warnOnIntegrationIssues(project, {json});
87
+ } catch {
88
+ // Never let the nudge break the command.
89
+ }
90
+
91
+ let result;
92
+ try {
93
+ result = await discoverApi(query, {components: options.components, lang, zh});
94
+ } catch (e) {
95
+ const err = /** @type {import('../../../api/error.mjs').AstryxError} */ (e);
96
+ return cliError(err.message, {suggestions: err.suggestions, code: err.code});
97
+ }
98
+
99
+ const answered = summarize(result);
100
+ if (json) {
101
+ // Forward optional meta (e.g. configured flag for discover.list) as a
102
+ // sibling of data via jsonOut, so the envelope still carries
103
+ // apiVersion and goes through the single sanctioned emit path.
104
+ jsonOut(result);
105
+ return answered;
106
+ }
267
107
 
268
- // No discover source: the list of what this project has, as always.
269
- if (available === undefined) {
270
- if (result.data.length === 0) {
271
- if (result.meta && result.meta.configured === false) {
272
- emit(
273
- text('No integrations configured.'),
274
- text('Add integration package names to astryx.config.mjs:'),
275
- code(
276
- "export default {\n integrations: ['@scope/your-integration'],\n};",
277
- ),
278
- );
279
- } else {
280
- emit(
281
- text(
282
- 'No external components found in configured integrations.',
283
- ),
284
- );
285
- }
286
- break;
287
- }
108
+ switch (result.type) {
109
+ case 'discover.list': {
110
+ if (result.data.length === 0) {
111
+ if (result.meta && result.meta.configured === false) {
288
112
  emit(
289
- records(result.data, listOpts),
290
- text(
291
- [
292
- 'Usage:',
293
- ` ${run} discover <package> Browse a package`,
294
- ` ${run} discover <package>/Component View component docs`,
295
- ` ${run} discover <search> Search all packages`,
296
- ].join('\n'),
113
+ text('No integrations configured.'),
114
+ text('Add integration package names to astryx.config.mjs:'),
115
+ code(
116
+ "export default {\n integrations: ['@scope/your-integration'],\n};",
297
117
  ),
298
118
  );
299
- break;
119
+ } else {
120
+ emit(text('No external components found in configured integrations.'));
300
121
  }
301
-
302
- const kind = options.type
303
- ? (KIND_PLURALS[options.type] ?? options.type)
304
- : null;
305
- /** @type {import('../formatters/index.mjs').Block[]} */
306
- const blocks = [];
307
- if (!options.available) {
308
- blocks.push(section('Installed'));
309
- blocks.push(
310
- result.data.length > 0
311
- ? records(result.data, listOpts)
312
- : text(
313
- kind
314
- ? `None of this project's integrations add ${kind}.`
315
- : 'None yet.',
316
- ),
317
- );
318
- }
319
- if (!options.installed) {
320
- blocks.push(section('Available', sourceSubtitle(sources)));
321
- blocks.push(
322
- available.length > 0
323
- ? records(available, listOpts)
324
- : text(
325
- !sources.some(s => s.status !== 'failed')
326
- ? 'No source answered, so there is nothing to list.'
327
- : kind
328
- ? `No package you could add adds ${kind}.`
329
- : 'Nothing new to add: this project has every integration its sources list.',
330
- ),
331
- );
332
- }
333
- blocks.push(...sourceNotes(sources));
334
- blocks.push(
335
- text(
336
- [
337
- 'Usage:',
338
- ` ${run} discover <package> Browse a package and its versions`,
339
- ` ${run} discover <package>/<item> View one item`,
340
- ` ${run} discover <search> Search every item and package`,
341
- ].join('\n'),
342
- ),
343
- );
344
- emit(...blocks);
345
122
  break;
346
123
  }
347
124
 
348
- case 'discover.detail': {
349
- const d = result.data;
350
- /** @type {import('../formatters/index.mjs').Block[]} */
351
- const blocks = [record(d, {omit: ['versions', 'install']})];
352
- if (d.versions) {
353
- const shown = d.version;
354
- const lines = versionLines(d.versions, {
355
- shown,
356
- installed: d.installed
357
- ? (d.installedVersion ?? d.version)
358
- : undefined,
359
- latest: d.latest,
360
- });
361
- const prereleases = d.versions.filter(v => v.prerelease).length;
362
- blocks.push(
363
- section(
364
- 'Versions',
365
- `${d.versions.length} published, newest first`,
366
- ),
367
- );
368
- if (lines.length > 0) blocks.push(list(lines));
369
- if (prereleases > 0) {
370
- blocks.push(
371
- text(
372
- `${prereleases === 1 ? '1 prerelease is' : `${prereleases} prereleases are`} not listed. See any version: ${run} discover ${d.name}@<version>`,
373
- ),
374
- );
375
- }
376
- }
377
- if (d.install) {
378
- blocks.push(
379
- section('Add it'),
380
- code(d.install),
381
- text(
382
- 'Astryx loads an integration on the next command once it is a dependency.',
383
- ),
384
- );
385
- } else if (d.installedAs) {
386
- blocks.push(
387
- text(
388
- `This project already has this integration as ${d.installedAs}.`,
389
- ),
390
- );
391
- }
392
- blocks.push(
393
- text(
394
- d.installed
395
- ? `Usage: ${run} discover ${d.name}/<ComponentName>`
396
- : `See one item: ${run} discover ${d.name}/<item>`,
397
- ),
398
- );
399
- emit(...blocks);
400
- break;
125
+ // One record per package, every field of the JSON entry. The default
126
+ // view summarizes each package's components (first N + "+N more");
127
+ // --components lists them all (record comma-joins the full array).
128
+ /** @type {import('../formatters/index.mjs').RecordOptions} */
129
+ const listOpts = {};
130
+ if (!options.components) {
131
+ listOpts.format = {
132
+ components: (/** @type {string[]} */ comps) => {
133
+ const shown = comps.slice(0, MAX_COMPONENTS_SHOWN);
134
+ const remaining = comps.length - MAX_COMPONENTS_SHOWN;
135
+ return remaining > 0
136
+ ? `${shown.join(', ')}, +${remaining} more`
137
+ : shown.join(', ');
138
+ },
139
+ };
401
140
  }
141
+ emit(
142
+ records(result.data, listOpts),
143
+ text(
144
+ [
145
+ 'Usage:',
146
+ ` ${run} discover <package> Browse a package`,
147
+ ` ${run} discover <package>/Component View component docs`,
148
+ ` ${run} discover <search> Search all packages`,
149
+ ].join('\n'),
150
+ ),
151
+ );
152
+ break;
153
+ }
402
154
 
403
- case 'discover.detail.doc': {
404
- const docs = result.data;
405
- const md =
406
- detail === 'brief'
407
- ? formatBrief(docs, docs.name, '')
408
- : detail === 'compact'
409
- ? formatCompact(docs, docs.name, '')
410
- : formatFull(docs);
411
- emit(code(md));
412
- break;
413
- }
155
+ case 'discover.detail': {
156
+ const d = result.data;
157
+ emit(
158
+ record(d),
159
+ text(`Usage: ${run} discover ${d.name}/<ComponentName>`),
160
+ );
161
+ break;
162
+ }
414
163
 
415
- case 'discover.item': {
416
- const d = result.data;
417
- emit(
418
- record(d, {omit: ['install']}),
419
- ...(d.install
420
- ? [
421
- section('Add it'),
422
- code(d.install),
423
- text(
424
- 'Astryx loads an integration on the next command once it is a dependency.',
425
- ),
426
- ]
427
- : []),
428
- text(`Its package: ${run} discover ${d.package}`),
429
- );
430
- break;
431
- }
164
+ case 'discover.detail.doc': {
165
+ const docs = result.data;
166
+ const md =
167
+ detail === 'brief'
168
+ ? formatBrief(docs, docs.name, '')
169
+ : detail === 'compact'
170
+ ? formatCompact(docs, docs.name, '')
171
+ : formatFull(docs);
172
+ emit(code(md));
173
+ break;
174
+ }
432
175
 
433
- case 'discover.search': {
434
- const {query: q, matches, total} = result.data;
435
- const count = total ?? matches.length;
436
- emit(
437
- section(
438
- `Found ${count} ${count === 1 ? 'match' : 'matches'} for "${q}"`,
439
- total != null
440
- ? `showing the first ${matches.length}`
441
- : undefined,
442
- ),
443
- list(
444
- matches.map(m => {
445
- const target =
446
- m.kind === 'package'
447
- ? m.package
448
- : `${m.package}/${m.component}`;
449
- const tags = [
450
- m.kind && m.kind !== 'component' ? m.kind : '',
451
- m.installed === false ? 'not installed' : '',
452
- ].filter(Boolean);
453
- return `${run} discover ${target}${tags.length ? ` (${tags.join(', ')})` : ''}`;
454
- }),
455
- ),
456
- );
457
- break;
458
- }
176
+ case 'discover.search': {
177
+ const {query: q, matches} = result.data;
178
+ emit(
179
+ section(`Found ${matches.length} matches for "${q}"`),
180
+ list(matches.map(m => `${run} discover ${m.package}/${m.component}`)),
181
+ );
182
+ break;
459
183
  }
460
- return answered;
461
- },
184
+ }
185
+ return answered;
186
+ },
462
187
  });
463
188
  }
@@ -216,34 +216,6 @@ describe('the docs tree, one level at a time', () => {
216
216
  expect(JSON.parse(both.stdout).code).toBe('ERR_INVALID_ARGUMENT');
217
217
  }, SLOW);
218
218
 
219
- it('reads the component lookup guide and its output/API examples', async () => {
220
- const index = await runCli([
221
- 'docs',
222
- 'cli/component-lookups',
223
- '--index',
224
- ]);
225
- expect(index.status).toBe(0);
226
- expect(index.stdout).toContain('several');
227
- expect(index.stdout).toContain('selectors');
228
- expect(index.stdout).toContain('output');
229
- expect(index.stdout).toContain('api');
230
-
231
- const output = await runCli([
232
- 'docs',
233
- 'cli/component-lookups',
234
- 'output',
235
- ]);
236
- expect(output.status).toBe(0);
237
- expect(output.stdout).toContain('component.batch');
238
- expect(output.stdout).toContain('ERR_UNKNOWN_COMPONENT');
239
- expect(output.stdout).toContain('exits 1');
240
-
241
- const api = await runCli(['docs', 'cli/component-lookups', 'api']);
242
- expect(api.status).toBe(0);
243
- expect(api.stdout).toContain('component([])');
244
- expect(api.stdout).toContain("component(['Button'])");
245
- }, SLOW);
246
-
247
219
  it('reads the integration guide by its route, and not by its old name', async () => {
248
220
  const guide = await runCli(['docs', 'cli/integrations', '--index']);
249
221
  expect(guide.status).toBe(0);
@@ -280,7 +252,6 @@ describe('the docs tree, one level at a time', () => {
280
252
  const error = JSON.parse(section.stdout);
281
253
  expect(error).toMatchObject({code: 'ERR_UNKNOWN_SECTION'});
282
254
  expect(error.suggestions.map(s => s.name)).toEqual([
283
- 'cli/component-lookups',
284
255
  'cli/integrations',
285
256
  'cli/writing-docs',
286
257
  'cli/commands',