@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
@@ -3,13 +3,19 @@
3
3
 
4
4
  /**
5
5
  * @param {string} [query]
6
- * @param {import('./discover.type.mjs').DiscoverOptions} [options]
6
+ * @param {object} [options]
7
+ * @param {boolean} [options.components]
8
+ * @param {string} [options.lang]
9
+ * @param {boolean} [options.zh]
7
10
  * @returns {Promise<
8
11
  * import('./discover.type.mjs').DiscoverListResponse |
9
12
  * import('./discover.type.mjs').DiscoverDetailResponse |
10
13
  * import('./discover.type.mjs').DiscoverDetailDocResponse |
11
- * import('./discover.type.mjs').DiscoverItemResponse |
12
14
  * import('./discover.type.mjs').DiscoverSearchResponse
13
15
  * >}
14
16
  */
15
- export function discover(query?: string, options?: import("./discover.type.mjs").DiscoverOptions): Promise<import("./discover.type.mjs").DiscoverListResponse | import("./discover.type.mjs").DiscoverDetailResponse | import("./discover.type.mjs").DiscoverDetailDocResponse | import("./discover.type.mjs").DiscoverItemResponse | import("./discover.type.mjs").DiscoverSearchResponse>;
17
+ export function discover(query?: string, options?: {
18
+ components?: boolean | undefined;
19
+ lang?: string | undefined;
20
+ zh?: boolean | undefined;
21
+ }): Promise<import("./discover.type.mjs").DiscoverListResponse | import("./discover.type.mjs").DiscoverDetailResponse | import("./discover.type.mjs").DiscoverDetailDocResponse | import("./discover.type.mjs").DiscoverSearchResponse>;
@@ -13,25 +13,22 @@ export const doc = {
13
13
  name: 'discover',
14
14
  namespace: 'cli/api',
15
15
  displayName: 'discover()',
16
- summary:
17
- 'Browse and search integrations: the ones a project has and, through discover sources, the ones it could add.',
16
+ summary: 'Browse and search components from configured external packages.',
18
17
  description:
19
- 'Lists the integrations a project loads and, when the project or an integration provides a discover source, ' +
20
- 'the packages it could add, with what each one adds per kind. An @scope/name query shows one package with every ' +
21
- 'version its source knows; @scope/name@version shows one version; @scope/name/Component returns an installed ' +
22
- "component's validated doc, and any other item path returns that item. A free-text term searches every item and " +
23
- 'package. Discover only reads: it prints the command that adds a package and never runs it.',
18
+ 'Explores components contributed by configured external packages and integrations ' +
19
+ 'the ones that declare a components root. With no query it lists those packages; ' +
20
+ 'an @scope/name query browses one package; @scope/name/Component (or a free-text ' +
21
+ "term that resolves to a single component) returns that component's validated doc; " +
22
+ 'a free-text term with several matches returns the candidate list.',
24
23
  importPath: '@astryxdesign/cli/api',
25
24
  signature:
26
- 'discover(query?: string, options?: DiscoverOptions): Promise<DiscoverListResponse | DiscoverDetailResponse | DiscoverDetailDocResponse | DiscoverItemResponse | DiscoverSearchResponse>',
25
+ 'discover(query?: string, options?: DiscoverOptions): Promise<DiscoverListResponse | DiscoverDetailResponse | DiscoverDetailDocResponse | DiscoverSearchResponse>',
27
26
  keywords: [
28
27
  'discover',
29
28
  'packages',
30
29
  'integrations',
31
30
  'external',
32
31
  'components',
33
- 'catalog',
34
- 'versions',
35
32
  'search',
36
33
  ],
37
34
  params: [
@@ -39,39 +36,13 @@ export const doc = {
39
36
  name: 'query',
40
37
  type: 'string',
41
38
  description:
42
- 'A package (@scope/name, optionally @version), an item path (@scope/name/<item>), or a free-text term. Omit to list packages.',
39
+ 'An @scope/name package, an @scope/name/Component path, or a free-text term. Omit to list all configured packages.',
43
40
  },
44
41
  {
45
42
  name: 'options.components',
46
43
  type: 'boolean',
47
44
  description:
48
- 'In the CLI package list, print every component, and every other item, of each package instead of the first 10. A display flag for the CLI renderer; the programmatic response is unchanged.',
49
- },
50
- {
51
- name: 'options.type',
52
- type: "'component' | 'template' | 'doc' | 'theme' | 'codemod' | 'agent-doc'",
53
- description:
54
- 'Only one kind: in the list, packages that add it; in a search, items of that kind.',
55
- },
56
- {
57
- name: 'options.installed',
58
- type: 'boolean',
59
- description:
60
- 'Only what the project has. Cannot be set with options.available.',
61
- default: 'false',
62
- },
63
- {
64
- name: 'options.available',
65
- type: 'boolean',
66
- description:
67
- 'Only what the project could add. Cannot be set with options.installed.',
68
- default: 'false',
69
- },
70
- {
71
- name: 'options.limit',
72
- type: 'number',
73
- description: 'Max number of search results, a positive integer.',
74
- default: '20',
45
+ 'In the CLI package list, print every component of each package instead of the first 10. A display flag for the CLI renderer; the programmatic response is unchanged.',
75
46
  },
76
47
  {
77
48
  name: 'options.lang',
@@ -89,27 +60,21 @@ export const doc = {
89
60
  {
90
61
  type: 'discover.list',
91
62
  description:
92
- 'The installed integrations (name, category, components, version, and a list per other kind they add). With a discover source, meta.available lists what the project could add and meta.sources reports each source. When the list is empty it carries meta.configured.',
63
+ 'The configured external packages (name, category, components, version, description). When empty it carries meta.configured to distinguish "nothing configured" from "configured but nothing discovered".',
93
64
  },
94
65
  {
95
66
  type: 'discover.detail',
96
- description:
97
- 'One package: what the shown version adds, whether the project has it, its versions and latest release when a source knows them, and the command that adds it when the project does not have it.',
67
+ description: 'A single package entry, for an @scope/name query.',
98
68
  },
99
69
  {
100
70
  type: 'discover.detail.doc',
101
71
  description:
102
- 'The validated ComponentDoc for one installed component, for an @scope/name/Component query.',
103
- },
104
- {
105
- type: 'discover.item',
106
- description:
107
- 'One item that is not an installed component: its kind, name, the package and version that add it, and whether the project has the package.',
72
+ 'The validated ComponentDoc for one external component: an @scope/name/Component query, or a free-text term that resolves to exactly one component.',
108
73
  },
109
74
  {
110
75
  type: 'discover.search',
111
76
  description:
112
- 'The query echoed back plus every matching item and package, each with its kind and whether the project has it, even when one name matches exactly or only one item matches; total is set when the limit cut the list.',
77
+ 'The query echoed back plus the matching {package, component} pairs, when a free-text term matches several components.',
113
78
  },
114
79
  ],
115
80
  throws: [
@@ -117,21 +82,17 @@ export const doc = {
117
82
  code: 'ERR_INVALID_ARGUMENT',
118
83
  when: 'the query is a non-string value, or a free-text search is run with an empty query',
119
84
  },
120
- {
121
- code: 'ERR_INVALID_OPTION',
122
- when: 'options.type is not a known kind, options.limit is not a positive integer, or options.installed and options.available are both set',
123
- },
124
85
  {
125
86
  code: 'ERR_UNKNOWN_PACKAGE',
126
- when: 'neither the project nor any discover source has the package',
87
+ when: 'the @scope/name package is not among the configured packages',
127
88
  },
128
89
  {
129
90
  code: 'ERR_UNKNOWN_COMPONENT',
130
- when: 'the item is not in the named package',
91
+ when: 'the component is not found in the named @scope/name package',
131
92
  },
132
93
  {
133
94
  code: 'ERR_NOT_FOUND',
134
- when: 'a free-text term matches nothing, or the requested version is not published',
95
+ when: 'a free-text term matches no component in any package',
135
96
  },
136
97
  {
137
98
  code: 'ERR_INVALID_DOC',
@@ -139,14 +100,10 @@ export const doc = {
139
100
  },
140
101
  ],
141
102
  examples: [
142
- {label: 'List packages', code: 'const {data, meta} = await discover();'},
103
+ {label: 'List packages', code: 'const {data} = await discover();'},
143
104
  {label: 'Browse a package', code: "await discover('@acme/ui');"},
144
- {label: 'One version', code: "await discover('@acme/ui@2.1.0');"},
145
105
  {label: 'Show a component doc', code: "await discover('@acme/ui/Button');"},
146
- {
147
- label: 'Search templates to add',
148
- code: "await discover('dashboard', {type: 'template', available: true});",
149
- },
106
+ {label: 'Free-text search', code: "await discover('button');"},
150
107
  ],
151
108
  command: 'discover',
152
109
  related: ['component', 'search', 'template'],
@@ -3,193 +3,55 @@
3
3
  /**
4
4
  * @file Programmatic API for the discover command — dispatcher + barrel.
5
5
  *
6
- * `discover()` browses the integrations a project has and, through discover
7
- * sources, the ones it could add. It only reads: the package manager installs.
8
- * Its job is to resolve the project and its sources (via ./_adapter) and route
9
- * to the leaf that owns each response shape:
6
+ * `discover()` keeps the exact same signature and response union it always had,
7
+ * so `api/index.mjs` and the CLI consumer are untouched. Its only job now is to
8
+ * discover the external packages (via ./_adapter) and route to the leaf that
9
+ * owns each response shape:
10
10
  *
11
- * discover -> ./list/list.mjs discover.list
12
- * discover <package>[@<version>] -> ./detail/detail.mjs discover.detail
13
- * discover <package>/<Component> -> ./detail/doc/doc.mjs discover.detail.doc
14
- * discover <package>/<item> -> ./detail/item/item.mjs discover.item
15
- * discover <words> -> ./search/search.mjs discover.search (always a list)
11
+ * discover.list -> ./list/list.mjs
12
+ * discover.detail -> ./detail/detail.mjs
13
+ * discover.detail.doc -> ./detail/doc/doc.mjs
14
+ * discover.search -> ./search/search.mjs
16
15
  *
17
- * Source results are additive: they ride in `meta.available`/`meta.sources`,
18
- * in extra fields, or as extra search matches, so every existing field keeps
19
- * its meaning.
20
- *
21
- * @position api/discover — thin router over ./_adapter, ./_catalog-view, and
22
- * the discover leaves.
16
+ * @position api/discover — thin router over ./_adapter + the discover leaves.
23
17
  */
24
18
 
25
- import {
26
- addCommand,
27
- callSources,
28
- declaredDependencies,
29
- describeInstalled,
30
- discoverPackages,
31
- findComponent,
32
- hasSources,
33
- } from './_adapter.mjs';
34
- import {
35
- availableEntries,
36
- catalogState,
37
- searchItems,
38
- withLatest,
39
- } from './_catalog-view.mjs';
19
+ import {discoverPackages} from './_adapter.mjs';
40
20
  import {list} from './list/list.mjs';
41
21
  import {detail} from './detail/detail.mjs';
42
- import {doc, docFromResult} from './detail/doc/doc.mjs';
43
- import {item} from './detail/item/item.mjs';
22
+ import {doc} from './detail/doc/doc.mjs';
44
23
  import {search} from './search/search.mjs';
45
24
  import {AstryxError} from '../error.mjs';
46
25
  import {ERROR_CODES} from '../../foundation/response/error-codes.mjs';
47
- import {levenshteinDistance} from '../../foundation/text/string-utils.mjs';
48
- import {DISCOVER_KINDS} from '../../authoring/discover/parse.mjs';
49
-
50
- const DEFAULT_LIMIT = 20;
51
-
52
- /** @type {import('./_adapter.mjs').CatalogResult} */
53
- const NO_CATALOG = {sources: [], packages: []};
54
-
55
- /**
56
- * Split a package-shaped query: `name`, `name@version`, or `name/item`, with an
57
- * optional `@scope/` prefix on the name.
58
- * @param {string} query
59
- * @returns {{scoped: boolean, name: string, version?: string, item?: string}}
60
- */
61
- function splitTarget(query) {
62
- const scoped = query.startsWith('@');
63
- const firstSlash = query.indexOf('/');
64
- let head = query;
65
- /** @type {string | undefined} */
66
- let rest;
67
- const split = scoped
68
- ? firstSlash < 0
69
- ? -1
70
- : query.indexOf('/', firstSlash + 1)
71
- : firstSlash;
72
- if (split > 0) {
73
- head = query.slice(0, split);
74
- rest = query.slice(split + 1) || undefined;
75
- }
76
- const at = head.indexOf('@', 1);
77
- return {
78
- scoped,
79
- name: at > 0 ? head.slice(0, at) : head,
80
- ...(at > 0 && head.slice(at + 1) ? {version: head.slice(at + 1)} : {}),
81
- ...(rest ? {item: rest} : {}),
82
- };
83
- }
84
-
85
- /**
86
- * @param {{type?: unknown, installed?: boolean, available?: boolean, limit?: unknown}} options
87
- */
88
- function checkOptions({type, installed, available, limit}) {
89
- if (
90
- type != null &&
91
- !(/** @type {readonly unknown[]} */ (DISCOVER_KINDS).includes(type))
92
- ) {
93
- throw new AstryxError(
94
- `Unknown --type "${String(type)}"`,
95
- DISCOVER_KINDS.map(kind => ({name: kind, reason: 'item kind'})),
96
- ERROR_CODES.ERR_INVALID_OPTION,
97
- );
98
- }
99
- if (installed && available) {
100
- throw new AstryxError(
101
- '--installed and --available cannot be used together',
102
- undefined,
103
- ERROR_CODES.ERR_INVALID_OPTION,
104
- );
105
- }
106
- if (
107
- limit != null &&
108
- !(typeof limit === 'number' && Number.isInteger(limit) && limit > 0)
109
- ) {
110
- throw new AstryxError(
111
- `--limit must be a positive integer, not "${String(limit)}"`,
112
- undefined,
113
- ERROR_CODES.ERR_INVALID_OPTION,
114
- );
115
- }
116
- }
117
-
118
- /**
119
- * Items a source lists for a package that look like the one asked for: names
120
- * that contain it, else the closest few by spelling.
121
- * @param {import('../../authoring/discover/type.js').DiscoverPackage} entry
122
- * @param {string} wanted
123
- * @returns {Array<{name: string, reason: string}>}
124
- */
125
- function similarItems(entry, wanted) {
126
- const lower = wanted.toLowerCase();
127
- const names = [...new Set(entry.contributions.map(c => c.name))];
128
- const hits = names.filter(name => name.toLowerCase().includes(lower));
129
- const picked =
130
- hits.length > 0
131
- ? hits.slice(0, 5)
132
- : names
133
- .map(name => ({
134
- name,
135
- distance: levenshteinDistance(lower, name.toLowerCase()),
136
- }))
137
- .filter(m => m.distance <= 3)
138
- .sort((a, b) => a.distance - b.distance)
139
- .slice(0, 5)
140
- .map(m => m.name);
141
- return picked.map(name => ({
142
- name: `${entry.package}/${name}`,
143
- reason: 'similar name',
144
- }));
145
- }
146
26
 
147
27
  /**
148
28
  * @param {string} [query]
149
- * @param {import('./discover.type.mjs').DiscoverOptions} [options]
29
+ * @param {object} [options]
30
+ * @param {boolean} [options.components]
31
+ * @param {string} [options.lang]
32
+ * @param {boolean} [options.zh]
150
33
  * @returns {Promise<
151
34
  * import('./discover.type.mjs').DiscoverListResponse |
152
35
  * import('./discover.type.mjs').DiscoverDetailResponse |
153
36
  * import('./discover.type.mjs').DiscoverDetailDocResponse |
154
- * import('./discover.type.mjs').DiscoverItemResponse |
155
37
  * import('./discover.type.mjs').DiscoverSearchResponse
156
38
  * >}
157
39
  */
158
40
  export async function discover(query, options = {}) {
159
- const {lang = null, zh = false, type, limit} = options;
160
- checkOptions(options);
161
- const only = options.installed
162
- ? /** @type {const} */ ('installed')
163
- : options.available
164
- ? /** @type {const} */ ('available')
165
- : undefined;
166
-
167
- const {packages: scanned, configured, project} = await discoverPackages();
168
- const withSources = hasSources(project);
169
- const packages = await describeInstalled(project, scanned);
170
- const declared = declaredDependencies(project);
171
- const add = addCommand(project);
41
+ const {lang = null, zh = false} = options;
42
+ const {packages, configured} = await discoverPackages();
172
43
 
173
- /** @param {{package?: string, version?: string}} [request] */
174
- const catalogFor = async request =>
175
- withSources ? await callSources(project, request) : NO_CATALOG;
44
+ // No discoverable packages — either nothing configured, or configured but
45
+ // empty. The list leaf owns the empty envelope + `configured` flag. This
46
+ // short-circuits before query parsing, exactly as the flat command did.
47
+ if (packages.length === 0) return list(packages, {configured});
176
48
 
177
- // No query, or nothing to look through: the list.
178
- if (!query || (packages.length === 0 && !withSources)) {
179
- if (!withSources) return list(packages, {configured, type, only});
180
- const catalog = await catalogFor();
181
- return list(withLatest(packages, catalog), {
182
- configured,
183
- type,
184
- only,
185
- available: availableEntries(catalog, packages, declared),
186
- sources: catalog.sources,
187
- });
188
- }
49
+ // No query: the full package list.
50
+ if (!query) return list(packages, {configured});
189
51
 
190
- // A non-string (truthy) query would crash the parsing below with a raw
191
- // TypeError (no ERR_* code → downgrades to ERR_UNKNOWN). The CLI only ever
192
- // passes a string, but the public API must fail with a code.
52
+ // A non-string (truthy) query would crash `.startsWith`/`.indexOf` below
53
+ // with a raw TypeError (no ERR_* code → downgrades to ERR_UNKNOWN). The CLI
54
+ // only ever passes a string, but the public API must fail with a code.
193
55
  if (typeof query !== 'string') {
194
56
  throw new AstryxError(
195
57
  `Invalid query "${String(query)}"`,
@@ -198,63 +60,17 @@ export async function discover(query, options = {}) {
198
60
  );
199
61
  }
200
62
 
201
- const target = splitTarget(query);
202
- const installedNames = new Set(packages.map(p => p.name));
203
-
204
- /**
205
- * One package, one version, or one item, once `target.name` is known to be
206
- * a package the project or a source has.
207
- * @param {import('./_adapter.mjs').CatalogResult} catalog
208
- */
209
- const packageView = async catalog => {
210
- const entry = catalog.packages.find(p => p.package === target.name);
211
- const installedAs = entry
212
- ? catalogState(entry, installedNames, declared).installedAs
213
- : undefined;
214
- const context = {catalog: entry, version: target.version, add, installedAs};
215
- if (!target.item) return detail(packages, target.name, context);
216
- const found = item(packages, target.name, target.item, context);
217
- if (found) return found;
218
- // Only an installed package has docs to fall back to. For one that is
219
- // only in a source, the item is what is unknown, not the package.
220
- if (entry && !packages.some(p => p.name === target.name)) {
221
- throw new AstryxError(
222
- `"${target.item}" not found in ${target.name}`,
223
- similarItems(entry, target.item),
224
- ERROR_CODES.ERR_UNKNOWN_COMPONENT,
225
- );
63
+ // Scoped package query: @scope/name or @scope/name/Component
64
+ if (query.startsWith('@')) {
65
+ const slashIdx = query.indexOf('/', query.indexOf('/') + 1);
66
+ if (slashIdx > 0) {
67
+ const pkgName = query.slice(0, slashIdx);
68
+ const compName = query.slice(slashIdx + 1);
69
+ return await doc(packages, pkgName, compName, {lang, zh});
226
70
  }
227
- return await doc(packages, target.name, target.item, {lang, zh});
228
- };
229
-
230
- // An installed component keeps resolving to its full doc, before any source
231
- // is asked anything.
232
- const installedPkg = packages.find(p => p.name === target.name);
233
- if (installedPkg && target.item && !target.version) {
234
- const resolved = findComponent([installedPkg], target.item);
235
- if (resolved) return await docFromResult(resolved, {lang, zh});
236
- }
237
-
238
- if (target.scoped) {
239
- return await packageView(
240
- await catalogFor({package: target.name, version: target.version}),
241
- );
242
- }
243
-
244
- const listing = await catalogFor();
245
- const isPackage =
246
- installedPkg != null ||
247
- listing.packages.some(p => p.package === target.name);
248
- if (isPackage && (target.version || target.item || target.name === query)) {
249
- return await packageView(
250
- await catalogFor({package: target.name, version: target.version}),
251
- );
71
+ return detail(packages, query);
252
72
  }
253
73
 
254
- return await search(packages, query, {
255
- items: searchItems(packages, listing, declared),
256
- type,
257
- only,
258
- limit: limit ?? DEFAULT_LIMIT,
259
- });
74
+ // Free-text search across all packages.
75
+ return await search(packages, query, {lang, zh});
260
76
  }
@@ -68,19 +68,10 @@ describe('discover() dispatcher routing', () => {
68
68
  it('free-text query with multiple matches -> discover.search envelope', async () => {
69
69
  // 'a' is a substring of both Alpha and Beta (no exact match), so the search
70
70
  // leaf returns the multi-match discover.search envelope. Proves free-text
71
- // routing reaches the search leaf and projects its envelope. Search also
72
- // matches package names, so the package itself is listed after them.
71
+ // routing reaches the search leaf and projects its envelope.
73
72
  const r = await discover('a');
74
73
  expect(r.type).toBe('discover.search');
75
- expect(
76
- r.data.matches.filter(m => m.kind === 'component').map(m => m.component),
77
- ).toEqual(['Alpha', 'Beta']);
78
- expect(r.data.matches).toContainEqual({
79
- package: '@acme/widgets',
80
- component: '@acme/widgets',
81
- kind: 'package',
82
- installed: true,
83
- });
74
+ expect(r.data.matches.map(m => m.component).sort()).toEqual(['Alpha', 'Beta']);
84
75
  });
85
76
 
86
77
  it('free-text query with no match still routes to the search leaf (coded not-found)', async () => {