@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,76 +1,44 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  /**
4
- * @file discover.search leaf — free-text search across installed packages and,
5
- * when the project has discover sources, everything it could add.
4
+ * @file discover.search leaf — free-text search across external packages.
6
5
  *
7
- * A free-text query always answers with a list, so its response type depends
8
- * on the form of the query and never on what the project has:
9
- * 1. any matches -> discover.search
10
- * 2. fuzzy hits (distance <= 3) -> throw ERR_NOT_FOUND with suggestions
11
- * 3. otherwise -> throw ERR_NOT_FOUND
12
- * Matches are installed components, the project's other installed items,
13
- * packages, and every item a source lists for a package the project could add.
14
- * A caller opens one item by its package path, which ../detail answers.
6
+ * Resolution order (matching the flat command exactly):
7
+ * 1. exact component name -> discover.detail.doc
8
+ * 2. single substring hit -> discover.detail.doc
9
+ * 3. multiple substring hits -> discover.search
10
+ * 4. fuzzy hits (distance <= 3) -> throw ERR_NOT_FOUND with suggestions
11
+ * 5. otherwise -> throw ERR_NOT_FOUND
15
12
  *
16
- * @position api/discover/search — pure ranking over the installed component
17
- * names and ../_catalog-view's search items.
13
+ * @position api/discover/search — projection over ../_adapter's resolver;
14
+ * delegates the single-component cases to ../detail/doc.
18
15
  */
19
16
 
17
+ import {findComponent} from '../_adapter.mjs';
18
+ import {docFromResult} from '../detail/doc/doc.mjs';
20
19
  import {levenshteinDistance} from '../../../foundation/text/string-utils.mjs';
21
20
  import {AstryxError} from '../../error.mjs';
22
21
  import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
23
- import {DISCOVER_KINDS} from '../../../authoring/discover/parse.mjs';
24
22
 
25
23
  /**
26
24
  * @typedef {import('../_package-scanner.mjs').ScannedPackage} ScannedPackage
27
- * @typedef {import('../_catalog-view.mjs').SearchItem} SearchItem
28
25
  */
29
26
 
30
- const KIND_ORDER = ['package', ...DISCOVER_KINDS];
31
-
32
- /**
33
- * How well an item matches: 0 exact name, 1 name prefix, 2 name substring,
34
- * 3 title, summary, keyword, or description; null for no match.
35
- * @param {SearchItem} item
36
- * @param {string} lower
37
- * @returns {number | null}
38
- */
39
- function rank(item, lower) {
40
- const name = item.name.toLowerCase();
41
- if (name === lower) return 0;
42
- if (name.startsWith(lower)) return 1;
43
- if (name.includes(lower)) return 2;
44
- const text = [
45
- item.title,
46
- item.summary,
47
- item.description,
48
- ...(item.keywords ?? []),
49
- ]
50
- .filter(Boolean)
51
- .join('\n')
52
- .toLowerCase();
53
- return text.includes(lower) ? 3 : null;
54
- }
55
-
56
27
  /**
57
28
  * Search all packages for `query` (a free-text term that never starts with
58
- * `@`). Resolves to a search response when anything matches, even a single
59
- * item or an exact component name, or throws AstryxError (ERR_NOT_FOUND) —
60
- * with fuzzy suggestions when any exist.
29
+ * `@`). Resolves to a single component's docs when unambiguous, a search
30
+ * response when several match, or throws AstryxError (ERR_NOT_FOUND) — with
31
+ * fuzzy suggestions when any exist.
61
32
  *
62
- * @param {ScannedPackage[]} packages installed packages
33
+ * @param {ScannedPackage[]} packages
63
34
  * @param {string} query
64
- * @param {{
65
- * items?: SearchItem[],
66
- * type?: import('../../../authoring/discover/type').DiscoverKind,
67
- * only?: 'installed' | 'available',
68
- * limit?: number,
69
- * }} opts
70
- * @returns {Promise<import('../discover.type.mjs').DiscoverSearchResponse>}
35
+ * @param {{lang?: string | null, zh?: boolean}} opts
36
+ * @returns {Promise<
37
+ * import('../discover.type.mjs').DiscoverDetailDocResponse |
38
+ * import('../discover.type.mjs').DiscoverSearchResponse
39
+ * >}
71
40
  */
72
- export async function search(packages, query, opts) {
73
- const {items = [], type, only, limit} = opts;
41
+ export async function search(packages, query, {lang, zh}) {
74
42
  // An empty query must error, not match every component via `.includes('')`
75
43
  // (parity with the api/search leaf). The discover() dispatcher already routes
76
44
  // an empty query to list, but the leaf must be safe on its own.
@@ -83,64 +51,50 @@ export async function search(packages, query, opts) {
83
51
  }
84
52
  const lower = query.toLowerCase();
85
53
 
86
- /** @type {SearchItem[]} */
87
- const installedComponents = packages.flatMap(pkg =>
88
- pkg.components.map(name => ({
89
- package: pkg.name,
90
- kind: /** @type {const} */ ('component'),
91
- name,
92
- installed: true,
93
- })),
94
- );
54
+ const exact = findComponent(packages, query);
55
+ if (exact) return await docFromResult(exact, {lang, zh});
95
56
 
96
- const ranked = [...installedComponents, ...items]
97
- .filter(item => (type == null ? true : item.kind === type))
98
- .filter(item =>
99
- only === 'installed'
100
- ? item.installed
101
- : only === 'available'
102
- ? !item.installed
103
- : true,
104
- )
105
- .map(item => ({item, score: rank(item, lower)}))
106
- .filter(
107
- /** @returns {m is {item: SearchItem, score: number}} */
108
- m => m.score != null,
109
- )
110
- .sort(
111
- (a, b) =>
112
- a.score - b.score ||
113
- Number(b.item.installed) - Number(a.item.installed) ||
114
- KIND_ORDER.indexOf(a.item.kind) - KIND_ORDER.indexOf(b.item.kind) ||
115
- a.item.package.localeCompare(b.item.package) ||
116
- a.item.name.localeCompare(b.item.name),
117
- );
57
+ const substringMatches =
58
+ /** @type {Array<{pkg: ScannedPackage, comp: string}>} */ ([]);
59
+ for (const pkg of packages) {
60
+ for (const comp of pkg.components) {
61
+ if (comp.toLowerCase().includes(lower)) {
62
+ substringMatches.push({pkg, comp});
63
+ }
64
+ }
65
+ }
118
66
 
119
- if (ranked.length > 0) {
120
- const matches = ranked.map(({item}) => ({
121
- package: item.package,
122
- component: item.name,
123
- kind: item.kind,
124
- installed: item.installed,
125
- ...(item.title ? {title: item.title} : {}),
126
- ...(item.summary ? {summary: item.summary} : {}),
127
- }));
128
- const shown = limit == null ? matches : matches.slice(0, limit);
67
+ if (substringMatches.length === 1) {
68
+ const match = substringMatches[0];
69
+ const result = findComponent([match.pkg], match.comp);
70
+ if (result) return await docFromResult(result, {lang, zh});
71
+ }
72
+
73
+ if (substringMatches.length > 1) {
129
74
  return {
130
75
  type: 'discover.search',
131
76
  data: {
132
77
  query,
133
- matches: shown,
134
- ...(shown.length < matches.length ? {total: matches.length} : {}),
78
+ matches: substringMatches.map(m => ({
79
+ package: m.pkg.name,
80
+ component: m.comp,
81
+ })),
135
82
  },
136
83
  };
137
84
  }
138
85
 
139
- // Fuzzy fallback over installed components.
140
- const fuzzyMatches = installedComponents
141
- .map(entry => ({
142
- ...entry,
143
- distance: levenshteinDistance(lower, entry.name.toLowerCase()),
86
+ // Fuzzy fallback
87
+ const allComponents =
88
+ /** @type {Array<{pkg: ScannedPackage, comp: string}>} */ ([]);
89
+ for (const pkg of packages) {
90
+ for (const comp of pkg.components) {
91
+ allComponents.push({pkg, comp});
92
+ }
93
+ }
94
+ const fuzzyMatches = allComponents
95
+ .map(item => ({
96
+ ...item,
97
+ distance: levenshteinDistance(lower, item.comp.toLowerCase()),
144
98
  }))
145
99
  .filter(m => m.distance <= 3)
146
100
  .sort((a, b) => a.distance - b.distance)
@@ -150,7 +104,7 @@ export async function search(packages, query, opts) {
150
104
  throw new AstryxError(
151
105
  `"${query}" not found`,
152
106
  fuzzyMatches.map(m => ({
153
- name: m.package + '/' + m.name,
107
+ name: m.pkg.name + '/' + m.comp,
154
108
  reason: 'similar name',
155
109
  })),
156
110
  ERROR_CODES.ERR_NOT_FOUND,
@@ -1,8 +1,8 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  /**
4
- * @file Colocated tests for the discover.search leaf, over a small temp docs
5
- * directory shaped like a scanned package.
4
+ * @file Colocated tests for the discover.search leaf. Exact/single matches load
5
+ * a real `.doc.mjs`, so these drive a small temp docs directory.
6
6
  */
7
7
 
8
8
  import {describe, it, expect, beforeAll, afterAll} from 'vitest';
@@ -47,31 +47,16 @@ afterAll(() => {
47
47
  });
48
48
 
49
49
  describe('discover.search leaf', () => {
50
- it('lists an exact component name first, with every other match', async () => {
50
+ it('an exact component name resolves to its docs', async () => {
51
51
  const res = await search(packages, 'Alpha', {});
52
- expect(res.type).toBe('discover.search');
53
- expect(res.data.matches.map(m => m.component)).toEqual([
54
- 'Alpha',
55
- 'AlphaCard',
56
- ]);
52
+ expect(res.type).toBe('discover.detail.doc');
53
+ expect(res.data.name).toBe('Alpha');
57
54
  });
58
55
 
59
- it('lists a single match too, so the response type never depends on the data', async () => {
56
+ it('a single substring match resolves to its docs', async () => {
60
57
  const res = await search(packages, 'card', {});
61
- expect(res).toEqual({
62
- type: 'discover.search',
63
- data: {
64
- query: 'card',
65
- matches: [
66
- {
67
- package: '@acme/widgets',
68
- component: 'AlphaCard',
69
- kind: 'component',
70
- installed: true,
71
- },
72
- ],
73
- },
74
- });
58
+ expect(res.type).toBe('discover.detail.doc');
59
+ expect(res.data.name).toBe('AlphaCard');
75
60
  });
76
61
 
77
62
  it('multiple substring matches return a search response', async () => {
@@ -81,18 +66,8 @@ describe('discover.search leaf', () => {
81
66
  data: {
82
67
  query: 'alph',
83
68
  matches: [
84
- {
85
- package: '@acme/widgets',
86
- component: 'Alpha',
87
- kind: 'component',
88
- installed: true,
89
- },
90
- {
91
- package: '@acme/widgets',
92
- component: 'AlphaCard',
93
- kind: 'component',
94
- installed: true,
95
- },
69
+ {package: '@acme/widgets', component: 'Alpha'},
70
+ {package: '@acme/widgets', component: 'AlphaCard'},
96
71
  ],
97
72
  },
98
73
  });
@@ -141,112 +116,3 @@ describe('discover.search leaf — empty query (parity with api/search)', () =>
141
116
  });
142
117
  });
143
118
  });
144
-
145
- describe('discover.search leaf across every kind and source', () => {
146
- /** @type {any[]} */
147
- const items = [
148
- {
149
- package: '@acme/widgets',
150
- kind: 'template',
151
- name: 'pages/AlphaHome',
152
- installed: true,
153
- },
154
- {
155
- package: '@acme/charts',
156
- kind: 'package',
157
- name: '@acme/charts',
158
- installed: false,
159
- description: 'Charts for alpha dashboards',
160
- },
161
- {
162
- package: '@acme/charts',
163
- kind: 'component',
164
- name: 'AlphaChart',
165
- installed: false,
166
- },
167
- {
168
- package: '@acme/charts',
169
- kind: 'doc',
170
- name: 'guide',
171
- installed: false,
172
- summary: 'How to chart',
173
- },
174
- ];
175
-
176
- it('ranks every match by how closely its name matches, installed first among equals', async () => {
177
- const res = await search(packages, 'alph', {items});
178
- expect(res.type).toBe('discover.search');
179
- expect(
180
- res.data.matches.map(m => [m.kind, m.component, m.installed]),
181
- ).toEqual([
182
- ['component', 'Alpha', true],
183
- ['component', 'AlphaCard', true],
184
- ['component', 'AlphaChart', false],
185
- ['template', 'pages/AlphaHome', true],
186
- ['package', '@acme/charts', false],
187
- ]);
188
- });
189
-
190
- it('lists an exact installed component name with the matches from every source', async () => {
191
- const res = await search(packages, 'Alpha', {items});
192
- expect(res.type).toBe('discover.search');
193
- expect(res.data.matches[0]).toEqual({
194
- package: '@acme/widgets',
195
- component: 'Alpha',
196
- kind: 'component',
197
- installed: true,
198
- });
199
- expect(res.data.matches.map(m => m.component)).toContain('AlphaChart');
200
- });
201
-
202
- it('lists a single partial component match when other items match too', async () => {
203
- const res = await search(packages, 'card', {
204
- items: [
205
- ...items,
206
- {
207
- package: '@acme/charts',
208
- kind: 'template',
209
- name: 'pages/CardGrid',
210
- installed: false,
211
- },
212
- ],
213
- });
214
- expect(res.type).toBe('discover.search');
215
- expect(res.data.matches.map(m => m.component)).toEqual([
216
- 'AlphaCard',
217
- 'pages/CardGrid',
218
- ]);
219
- });
220
-
221
- it('lists one installed component when nothing else matches', async () => {
222
- const res = await search(packages, 'card', {items});
223
- expect(res.type).toBe('discover.search');
224
- expect(res.data.matches.map(m => m.component)).toEqual(['AlphaCard']);
225
- });
226
-
227
- it('keeps one kind with type, and one side with only', async () => {
228
- const templates = await search(packages, 'alph', {items, type: 'template'});
229
- expect(templates.data.matches.map(m => m.component)).toEqual([
230
- 'pages/AlphaHome',
231
- ]);
232
- const available = await search(packages, 'alph', {
233
- items,
234
- only: 'available',
235
- });
236
- expect(available.data.matches.map(m => m.component)).toEqual([
237
- 'AlphaChart',
238
- '@acme/charts',
239
- ]);
240
- });
241
-
242
- it('caps the list at limit and reports the total', async () => {
243
- const res = await search(packages, 'alph', {items, limit: 2});
244
- expect(res.data.matches).toHaveLength(2);
245
- expect(res.data.total).toBe(5);
246
- });
247
-
248
- it('matches titles, summaries, keywords, and descriptions too', async () => {
249
- const res = await search(packages, 'how to chart', {items});
250
- expect(res.data.matches.map(m => m.component)).toEqual(['guide']);
251
- });
252
- });
@@ -146,7 +146,6 @@ describe('docs() dispatcher routing', () => {
146
146
  // A guide moves across its namespace's slot, as any tree node does.
147
147
  expect(index.data.links).toEqual({
148
148
  up: 'astryx docs cli',
149
- previous: 'astryx docs cli/component-lookups',
150
149
  next: 'astryx docs cli/writing-docs',
151
150
  });
152
151
  const first = (await docs('cli/integrations', index.data.sections[0].id)).data;
@@ -210,7 +209,6 @@ describe('docs() dispatcher routing', () => {
210
209
  expect(err).toBeInstanceOf(AstryxError);
211
210
  expect(err.code).toBe('ERR_UNKNOWN_SECTION');
212
211
  expect(err.suggestions.map(s => s.name)).toEqual([
213
- 'cli/component-lookups',
214
212
  'cli/integrations',
215
213
  'cli/writing-docs',
216
214
  'cli/commands',
@@ -257,20 +257,15 @@ export type DoctorContext = {
257
257
  * could not be built, when it could not.
258
258
  */
259
259
  docsCatalogError?: string | null | undefined;
260
+ /**
261
+ * Combined project-level integration issues, including cross-package template replacement warnings.
262
+ */
260
263
  integrationIssues?: {
261
264
  package: string;
262
265
  code: string;
263
266
  severity: "warning" | "error";
264
267
  message: string;
265
268
  }[] | null | undefined;
266
- /**
267
- * installed dependencies whose integration manifest could not be loaded
268
- * Combined project-level integration issues, including cross-package template replacement warnings.
269
- */
270
- autolinkFailures?: {
271
- spec: string;
272
- error: string;
273
- }[] | null | undefined;
274
269
  /**
275
270
  * - Error thrown while resolving the config
276
271
  * path (e.g. multiple config files present), surfaced by checkConfig as a FAIL.
@@ -26,11 +26,7 @@ import * as path from 'node:path';
26
26
  import {MIN_NODE_VERSION, isNodeVersionSupported} from '../../foundation/env/node-version.mjs';
27
27
  import {CLI_ROOT, findCoreDir, findInstalledPackage} from '../../foundation/fs/paths.mjs';
28
28
  import {explainPackageManager, getCliInvocation} from '../../foundation/env/package-manager.mjs';
29
- import {
30
- findConfigPath,
31
- Project,
32
- providerLedgerOf,
33
- } from '../../foundation/config/project.mjs';
29
+ import {findConfigPath, Project} from '../../foundation/config/project.mjs';
34
30
  import {DocsCatalog} from '../../foundation/discovery/docs-discovery.mjs';
35
31
  import {buildDocsIndexData} from '../../foundation/discovery/docs-section-key.mjs';
36
32
  import {
@@ -80,8 +76,6 @@ import {semverCompare, isValidSemver, satisfiesRange} from '../../foundation/env
80
76
  * @property {string|null} [docsCatalogError] - Why the project's docs catalog
81
77
  * could not be built, when it could not.
82
78
  * @property {Array<{package: string, code: string, severity: 'warning'|'error', message: string}>|null} [integrationIssues]
83
- * @property {Array<{spec: string, error: string}>|null} [autolinkFailures]
84
- * installed dependencies whose integration manifest could not be loaded
85
79
  * Combined project-level integration issues, including cross-package template replacement warnings.
86
80
  * @property {Error|null} [configError] - Error thrown while resolving the config
87
81
  * path (e.g. multiple config files present), surfaced by checkConfig as a FAIL.
@@ -423,9 +417,6 @@ export function checkImplicitIntegrations(ctx) {
423
417
  const implicit = ctx.integrations.filter(
424
418
  integration => integration.__autolinked,
425
419
  );
426
- // A dependency whose manifest cannot be loaded leaves no loaded record, so
427
- // without this line doctor would say no dependency ships a manifest at all.
428
- const unreadable = describeUnreadableManifests(ctx.autolinkFailures ?? []);
429
420
 
430
421
  if (implicit.length === 0) {
431
422
  return {
@@ -433,12 +424,9 @@ export function checkImplicitIntegrations(ctx) {
433
424
  label,
434
425
  status: 'info',
435
426
  message:
436
- (ctx.integrations.length > 0
427
+ ctx.integrations.length > 0
437
428
  ? 'None — every loaded integration is named in astryx.config.'
438
- : unreadable
439
- ? 'None loaded.'
440
- : 'None — no installed dependency ships an astryx.integration.* manifest.') +
441
- unreadable,
429
+ : 'None — no installed dependency ships an astryx.integration.* manifest.',
442
430
  };
443
431
  }
444
432
 
@@ -451,25 +439,11 @@ export function checkImplicitIntegrations(ctx) {
451
439
  integration.__spec && integration.__spec !== integration.name
452
440
  ? ` (declared as "${integration.__spec}")`
453
441
  : '';
454
- // A declared root counts only when it exists: the manifest's keys are a
455
- // claim, and `integration-issues` reports the ones that are not true.
456
- const declared = ['components', 'templates', 'themes', 'docs', 'codemods'].filter(
442
+ const roots = ['components', 'templates', 'themes', 'docs', 'codemods'].filter(
457
443
  root => integration[/** @type {'components'} */ (root)],
458
444
  );
459
- const missing = declared.filter(root => {
460
- const dir = integration[/** @type {'components'} */ (root)];
461
- if (typeof dir !== 'string') return false;
462
- const base =
463
- typeof integration.__packageDir === 'string'
464
- ? integration.__packageDir
465
- : (ctx.cwd ?? process.cwd());
466
- return !fs.existsSync(path.isAbsolute(dir) ? dir : path.resolve(base, dir));
467
- });
468
- const roots = declared.filter(root => !missing.includes(root));
469
445
  const contributes = roots.length > 0 ? roots.join(', ') : 'nothing';
470
- const absent =
471
- missing.length > 0 ? ` (declared ${missing.join(', ')} missing on disk)` : '';
472
- return `${integration.name}${version}${alias} from ${integration.__dependencyField}, contributing ${contributes}${absent}`;
446
+ return `${integration.name}${version}${alias} from ${integration.__dependencyField}, contributing ${contributes}`;
473
447
  });
474
448
 
475
449
  const plural = implicit.length === 1 ? '' : 's';
@@ -479,8 +453,7 @@ export function checkImplicitIntegrations(ctx) {
479
453
  status: 'info',
480
454
  message:
481
455
  `${implicit.length} integration${plural} loaded from installed ` +
482
- `dependencies with no astryx.config entry: ${described.join('; ')}.` +
483
- unreadable,
456
+ `dependencies with no astryx.config entry: ${described.join('; ')}.`,
484
457
  fix:
485
458
  'Nothing to fix. Keep these dependencies installed. The CLI links them ' +
486
459
  'from package.json, so an unused-dependency check that looks only for ' +
@@ -489,30 +462,6 @@ export function checkImplicitIntegrations(ctx) {
489
462
  };
490
463
  }
491
464
 
492
- /**
493
- * The sentence `implicit-integrations` adds for dependencies whose manifest
494
- * could not be loaded, or '' when there are none. Still informational: the
495
- * package is a dependency's own bug, which `doctor integration validate`
496
- * diagnoses, but doctor must not report it as absent.
497
- * @param {Array<{spec: string, error: string}>} failures
498
- * @returns {string}
499
- */
500
- function describeUnreadableManifests(failures) {
501
- if (failures.length === 0) return '';
502
- const one = failures.length === 1;
503
- const listed = failures
504
- .map(({spec, error}) => {
505
- const reason = String(error).split('\n')[0].slice(0, 160);
506
- return `${spec} (${reason})`;
507
- })
508
- .join('; ');
509
- return (
510
- ` ${failures.length} installed ${one ? 'dependency ships' : 'dependencies ship'} ` +
511
- `an astryx.integration.* manifest that could not be loaded, so ${one ? 'it contributes' : 'they contribute'} ` +
512
- `nothing: ${listed}. Run \`astryx doctor integration validate <package>\` for details.`
513
- );
514
- }
515
-
516
465
  /**
517
466
  * Check 7 — agent docs exist and contain the Astryx section markers.
518
467
  * @param {DoctorContext} ctx
@@ -789,26 +738,12 @@ export function checkProviderIdentity(ctx) {
789
738
  integration =>
790
739
  integration.providerId != null && integration.__loadError == null,
791
740
  ).length;
792
- // An integration that could not be read has no provider ID to check, so the
793
- // count above is not a complete survey. Say so instead of counting silently.
794
- const unread = ctx.integrations.filter(
795
- integration => integration.__loadError != null,
796
- ).length;
797
- const unreadNote =
798
- unread === 0
799
- ? ''
800
- : unread === 1
801
- ? ' 1 loaded integration could not be read, so its provider ID is unknown.'
802
- : ` ${unread} loaded integrations could not be read, so their provider IDs are unknown.`;
803
741
  if (count === 0) {
804
742
  return {
805
743
  id,
806
744
  label,
807
745
  status: 'info',
808
- message:
809
- (unread === 0
810
- ? 'None — no loaded integration has a provider identity.'
811
- : 'No readable integration has a provider identity.') + unreadNote,
746
+ message: 'None — no loaded integration has a provider identity.',
812
747
  };
813
748
  }
814
749
  return {
@@ -816,10 +751,9 @@ export function checkProviderIdentity(ctx) {
816
751
  label,
817
752
  status: 'pass',
818
753
  message:
819
- (count === 1
754
+ count === 1
820
755
  ? '1 loaded integration has its own provider ID.'
821
- : `${count} loaded integrations each have their own provider ID.`) +
822
- unreadNote,
756
+ : `${count} loaded integrations each have their own provider ID.`,
823
757
  };
824
758
  }
825
759
 
@@ -1264,25 +1198,11 @@ export async function runChecks(options = {}) {
1264
1198
  let docsCatalogError = null;
1265
1199
  /** @type {Array<{package: string, code: string, severity: 'warning'|'error', message: string}>|null} */
1266
1200
  let integrationIssues = null;
1267
- /** @type {Array<{spec: string, error: string}>|null} */
1268
- let autolinkFailures = null;
1269
1201
  try {
1270
1202
  const project = await Project.load(cwd);
1271
1203
  configTheme =
1272
1204
  /** @type {{theme?: string}} */ (project.config ?? {}).theme ?? null;
1273
1205
  integrations = project.loadedIntegrations;
1274
- // An installed dependency whose manifest cannot be loaded is kept out of
1275
- // loadedIntegrations on purpose. The provider ledger still records it.
1276
- autolinkFailures = [...providerLedgerOf(project).values()]
1277
- .filter(
1278
- entry =>
1279
- entry.outcome === 'load-failed' &&
1280
- entry.candidate.source === 'autolinked',
1281
- )
1282
- .map(entry => ({
1283
- spec: entry.candidate.spec ?? entry.label,
1284
- error: entry.error ?? 'its manifest could not be loaded',
1285
- }));
1286
1206
  try {
1287
1207
  docsCatalog = await project.docs();
1288
1208
  docsCatalogIssues = (await project.issues()).filter(
@@ -1309,7 +1229,6 @@ export async function runChecks(options = {}) {
1309
1229
  docsCatalogIssues,
1310
1230
  docsCatalogError,
1311
1231
  integrationIssues,
1312
- autolinkFailures,
1313
1232
  configError,
1314
1233
  };
1315
1234