@astryxdesign/cli 0.7.2-canary.e2909cf → 0.7.2-canary.e4bdd70

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 (194) hide show
  1. package/README.md +15 -14
  2. package/api/build/help/help.mjs +1 -1
  3. package/api/component/_adapter.d.mts +41 -1
  4. package/api/component/_adapter.mjs +93 -7
  5. package/api/component/component.mjs +22 -5
  6. package/api/component/integration-export-name.test.mjs +644 -0
  7. package/api/docs/_adapter.d.mts +11 -0
  8. package/api/docs/_adapter.mjs +244 -1
  9. package/api/docs/integrationDocs.test.mjs +124 -0
  10. package/api/doctor/theme-checks.mjs +1 -1
  11. package/api/gap-report/gap-report.mjs +67 -4
  12. package/api/index.d.mts +2 -0
  13. package/api/index.mjs +2 -0
  14. package/api/init/run/run.mjs +2 -2
  15. package/api/integration/_adapter.d.mts +12 -0
  16. package/api/integration/_adapter.mjs +54 -0
  17. package/api/integration/add-contribution.component-names.test.mjs +66 -0
  18. package/api/integration/add-contribution.mjs +8 -4
  19. package/api/integration/add-contribution.test.mjs +4 -2
  20. package/api/integration/authoring-checks.mjs +11 -2
  21. package/api/integration/authoring-checks.test.mjs +39 -0
  22. package/api/integration/authoring-checks.type.d.mts +9 -0
  23. package/api/integration/authoring-checks.type.mjs +14 -1
  24. package/api/integration/check/check.d.mts +96 -0
  25. package/api/integration/check/check.mjs +357 -0
  26. package/api/integration/check/check.test.mjs +489 -0
  27. package/api/integration/integration-check.type.d.mts +118 -0
  28. package/api/integration/integration-check.type.mjs +84 -0
  29. package/api/integration/integrationCheck.doc.d.mts +10 -0
  30. package/api/integration/integrationCheck.doc.mjs +73 -0
  31. package/api/integration/integrationComponentConflicts.doc.mjs +36 -5
  32. package/api/integration/pack-check.mjs +51 -97
  33. package/api/integration/pack-check.test.mjs +359 -0
  34. package/api/integration/validate-integration.mjs +8 -0
  35. package/api/integration/validate-integration.test.mjs +11 -0
  36. package/api/integration/validate-integration.type.d.mts +8 -0
  37. package/api/integration/validate-integration.type.mjs +12 -1
  38. package/api/integration/validateIntegration.doc.mjs +17 -3
  39. package/api/search/search.d.mts +22 -6
  40. package/api/search/search.domain-priority.test.mjs +93 -18
  41. package/api/search/search.integration-check.test.mjs +24 -0
  42. package/api/search/search.mjs +133 -22
  43. package/api/swizzle/copy/copy.mjs +28 -14
  44. package/api/theme/build/build.family.test.mjs +54 -6
  45. package/api/theme/build/build.mjs +114 -17
  46. package/api/theme/themeBuild.doc.mjs +4 -4
  47. package/assets/docs/README.md +10 -0
  48. package/assets/docs/author-a-theme.doc.mjs +1 -1
  49. package/assets/docs/getting-started.doc.mjs +3 -3
  50. package/assets/docs/styling-overview.doc.mjs +1 -1
  51. package/assets/docs/theme.doc.dense.mjs +1 -1
  52. package/assets/docs/theme.doc.mjs +1 -1
  53. package/assets/docs/theme.doc.zh.mjs +1 -1
  54. package/assets/docs/tree/add-a-component.doc.mjs +11 -1
  55. package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
  56. package/assets/docs/tree/add-a-topic.doc.mjs +10 -7
  57. package/assets/docs/tree/agent-guidance.doc.mjs +2 -2
  58. package/assets/docs/tree/block-template.doc.mjs +8 -20
  59. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  60. package/assets/docs/tree/check-the-props.doc.mjs +196 -0
  61. package/assets/docs/tree/check-your-docs.doc.mjs +51 -5
  62. package/assets/docs/tree/checks.doc.mjs +24 -16
  63. package/assets/docs/tree/codemods.doc.mjs +4 -4
  64. package/assets/docs/tree/component-family.doc.mjs +8 -6
  65. package/assets/docs/tree/component-imports.doc.mjs +10 -2
  66. package/assets/docs/tree/components.doc.mjs +1 -1
  67. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +7 -18
  68. package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
  69. package/assets/docs/tree/describe-the-component.doc.mjs +2 -2
  70. package/assets/docs/tree/docs.doc.mjs +3 -3
  71. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  72. package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
  73. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  74. package/assets/docs/tree/extend-or-replace.doc.mjs +6 -6
  75. package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
  76. package/assets/docs/tree/generate-a-palette.doc.mjs +5 -3
  77. package/assets/docs/tree/grade-template-with-agent.doc.mjs +1 -1
  78. package/assets/docs/tree/integrations.doc.mjs +8 -0
  79. package/assets/docs/tree/links.doc.mjs +6 -5
  80. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  81. package/assets/docs/tree/page-template.doc.mjs +4 -8
  82. package/assets/docs/tree/publishing.doc.mjs +1 -1
  83. package/assets/docs/tree/quick-start.doc.mjs +37 -21
  84. package/assets/docs/tree/replace-a-core-component.doc.mjs +4 -6
  85. package/assets/docs/tree/replace-a-core-template.doc.mjs +6 -8
  86. package/assets/docs/tree/sections-and-placement.doc.mjs +4 -6
  87. package/assets/docs/tree/see-it-in-an-app.doc.mjs +4 -2
  88. package/assets/docs/tree/short-and-findable.doc.mjs +6 -5
  89. package/assets/docs/tree/single-component.doc.mjs +15 -6
  90. package/assets/docs/tree/start-a-template.doc.mjs +3 -3
  91. package/assets/docs/tree/subcomponent.doc.mjs +6 -6
  92. package/assets/docs/tree/template-assets.doc.mjs +2 -2
  93. package/assets/docs/tree/template-doc-overview.doc.mjs +4 -15
  94. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  95. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  96. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  97. package/assets/docs/tree/template-styles.doc.mjs +2 -2
  98. package/assets/docs/tree/templates.doc.mjs +1 -1
  99. package/assets/docs/tree/test-in-an-app.doc.mjs +3 -3
  100. package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
  101. package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
  102. package/assets/docs/tree/troubleshooting.doc.mjs +13 -5
  103. package/assets/docs/tree/type-scale.doc.mjs +3 -3
  104. package/assets/docs/tree/upgrading.doc.mjs +3 -3
  105. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +3 -1
  106. package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
  107. package/assets/docs/tree/versioning.doc.mjs +7 -6
  108. package/assets/docs/tree/write-the-template-file.doc.mjs +4 -3
  109. package/assets/docs/use-a-theme.doc.mjs +2 -2
  110. package/assets/theme.template.ts +1 -1
  111. package/authoring/codemod/codemod.doc.mjs +7 -3
  112. package/authoring/config/config.doc.mjs +9 -6
  113. package/authoring/config/type.ts +4 -2
  114. package/authoring/discover/discover.doc.mjs +1 -1
  115. package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
  116. package/authoring/doctypes/command/command.doc.mjs +14 -12
  117. package/authoring/doctypes/component/component.doc.mjs +29 -26
  118. package/authoring/doctypes/component/type.ts +13 -8
  119. package/authoring/doctypes/enum/enum.doc.mjs +7 -4
  120. package/authoring/doctypes/function/function.doc.mjs +9 -8
  121. package/authoring/doctypes/hook/hook.doc.mjs +5 -4
  122. package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
  123. package/authoring/doctypes/reference/reference.doc.mjs +11 -10
  124. package/authoring/doctypes/schema/schema.doc.mjs +9 -9
  125. package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
  126. package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
  127. package/authoring/doctypes/template/parse.d.mts +6 -2
  128. package/authoring/doctypes/template/parse.mjs +3 -1
  129. package/authoring/doctypes/template/template.doc.mjs +3 -3
  130. package/authoring/doctypes/template/type.ts +22 -0
  131. package/authoring/doctypes/theme/theme.doc.mjs +2 -2
  132. package/authoring/gap-report/gap-report.doc.mjs +2 -2
  133. package/authoring/identity/identity.doc.mjs +3 -3
  134. package/authoring/index.d.ts +1 -0
  135. package/authoring/integration/integration.doc.mjs +12 -4
  136. package/authoring/integration/parse.test.mjs +10 -0
  137. package/authoring/integration/schema.d.mts +2 -0
  138. package/authoring/integration/schema.mjs +1 -0
  139. package/authoring/integration/type.ts +4 -0
  140. package/clients/cli/commands/build-theme.batch-report.test.mjs +3 -1
  141. package/clients/cli/commands/build-theme.mjs +1 -1
  142. package/clients/cli/commands/build-theme.path-safety.test.mjs +155 -21
  143. package/clients/cli/commands/doctor-integration-components.doc.mjs +15 -4
  144. package/clients/cli/commands/doctor-integration-validate.doc.mjs +11 -2
  145. package/clients/cli/commands/doctor-integration.test.mjs +837 -0
  146. package/clients/cli/commands/doctor.mjs +190 -0
  147. package/clients/cli/commands/integration-check.doc.mjs +85 -0
  148. package/clients/cli/commands/integration-check.test.mjs +267 -0
  149. package/clients/cli/commands/integration.doc.mjs +6 -2
  150. package/clients/cli/commands/integration.mjs +107 -1
  151. package/clients/cli/commands/text-json-parity.test.mjs +5 -0
  152. package/clients/cli/commands/theme-build.doc.mjs +2 -2
  153. package/clients/cli/index.mjs +1 -0
  154. package/foundation/config/project.mjs +82 -0
  155. package/foundation/discovery/authoring-self-docs.mjs +2 -1
  156. package/foundation/discovery/component-discovery.d.mts +178 -10
  157. package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
  158. package/foundation/discovery/component-discovery.mjs +659 -62
  159. package/foundation/discovery/component-replacement.mjs +18 -0
  160. package/foundation/discovery/component-replacement.test.mjs +40 -0
  161. package/foundation/discovery/doc-types.d.mts +157 -0
  162. package/foundation/discovery/doc-types.mjs +1224 -0
  163. package/foundation/discovery/doc-types.test.mjs +652 -0
  164. package/foundation/discovery/docs-discovery.d.mts +81 -0
  165. package/foundation/discovery/docs-discovery.mjs +57 -0
  166. package/foundation/discovery/integration-doc-files.d.mts +22 -0
  167. package/foundation/discovery/integration-doc-files.mjs +231 -0
  168. package/foundation/discovery/integration-doc-files.test.mjs +246 -0
  169. package/foundation/discovery/module-exports.d.mts +27 -0
  170. package/foundation/discovery/module-exports.mjs +356 -31
  171. package/foundation/discovery/module-exports.test.mjs +172 -0
  172. package/foundation/discovery/package-tsconfig.d.mts +14 -0
  173. package/foundation/discovery/package-tsconfig.mjs +228 -0
  174. package/foundation/discovery/package-tsconfig.test.mjs +210 -0
  175. package/foundation/discovery/project-typescript.d.mts +23 -0
  176. package/foundation/discovery/project-typescript.mjs +61 -0
  177. package/foundation/discovery/prop-drift.d.mts +278 -0
  178. package/foundation/discovery/prop-drift.mjs +2263 -0
  179. package/foundation/discovery/prop-drift.test.mjs +2219 -0
  180. package/foundation/discovery/type-match.d.mts +53 -0
  181. package/foundation/discovery/type-match.mjs +660 -0
  182. package/foundation/discovery/type-match.test.mjs +169 -0
  183. package/foundation/discovery/type-text.d.mts +68 -0
  184. package/foundation/discovery/type-text.mjs +658 -0
  185. package/foundation/discovery/type-text.test.mjs +196 -0
  186. package/foundation/discovery/unresolved-references.d.mts +62 -0
  187. package/foundation/discovery/unresolved-references.mjs +790 -0
  188. package/foundation/discovery/unresolved-references.test.mjs +567 -0
  189. package/foundation/doc-compiler/doc-loads.test.mjs +6 -0
  190. package/foundation/doc-compiler/links.d.mts +9 -5
  191. package/foundation/doc-compiler/links.mjs +4 -2
  192. package/foundation/integrations/contribution-inventory.mjs +6 -1
  193. package/foundation/response/response-types.doc.mjs +7 -2
  194. package/package.json +9 -9
@@ -0,0 +1,73 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file FunctionDoc for `integrationCheck()` / `astryx integration check`.
5
+ * Colocated with the API function it documents; the shape source of truth stays
6
+ * in `integration-check.type.mjs`.
7
+ */
8
+
9
+ /** @type {import('@astryxdesign/cli/authoring').FunctionDoc} */
10
+ export const doc = {
11
+ type: 'function',
12
+ kind: 'api',
13
+ name: 'integrationCheck',
14
+ namespace: 'cli/api',
15
+ displayName: 'integrationCheck()',
16
+ summary:
17
+ 'Run every source check against the integration package in the current folder, as its build does: validate, components, templates, and docs.',
18
+ description:
19
+ 'Runs `validateIntegration`, `integrationComponentConflicts`, ' +
20
+ '`integrationTemplateConflicts`, and `integrationDocConflicts` against the ' +
21
+ 'integration package in `options.cwd`, and counts each result the way that ' +
22
+ "check's own subcommand exits. The run fails when any of the four would fail on " +
23
+ 'its own. With `strict` it also fails on any warning and on any check that did ' +
24
+ 'not run: a package with no integration manifest, or a comparison a check ' +
25
+ "carries that was skipped, such as one that needs the project's TypeScript. " +
26
+ '`strict` adds no check: it reports those warnings and skipped work again, as ' +
27
+ "errors in the checks' own issue format. " +
28
+ 'A new check, or a new failure code, joins only in a minor release, named in the release notes.',
29
+ importPath: '@astryxdesign/cli/api',
30
+ signature:
31
+ 'integrationCheck(options?: IntegrationCheckOptions): Promise<IntegrationCheckCommandResponse>',
32
+ keywords: ['integration', 'check', 'build', 'strict', 'validate', 'doctor'],
33
+ params: [
34
+ {
35
+ name: 'options.cwd',
36
+ type: 'string',
37
+ default: 'process.cwd()',
38
+ description: 'The integration package folder to check.',
39
+ },
40
+ {
41
+ name: 'options.strict',
42
+ type: 'boolean',
43
+ default: 'false',
44
+ description:
45
+ 'Also fail on any warning and on any check that did not run, not only on errors.',
46
+ },
47
+ ],
48
+ returns: [
49
+ {
50
+ type: 'integration.check',
51
+ description:
52
+ "`data.validated` (false when no integration manifest was found, so nothing was checked), `data.name` and `data.version` of the package, `data.strict`, and `data.checks`, one entry per check in the order they run (`validate`, `components`, `templates`, `docs`), each with `check`, `errors` (what makes that check's own subcommand exit 1), `warnings`, `skipped` (why work did not run), `failed` (whether that subcommand exits 1), and `response`, the envelope the check returns on its own. The totals are `data.errors`, `data.warnings`, and `data.skipped` (each reason prefixed with its check); an issue every check starts from, such as one about the manifest, counts once, and every other finding counts on its own. `data.unchecked` counts the items the checks list as not compared, which never fail it. With `strict`, `data.failures` lists each warning and each piece of skipped work once again as an error in the checks' own issue format ({check, severity: `error`, code, message, component, prop, fix, file, line}, each where it applies); it is empty without `strict`. `data.failed` decides the exit code.",
53
+ },
54
+ ],
55
+ examples: [
56
+ {
57
+ label: 'Check the local package',
58
+ code: 'const r = await integrationCheck();',
59
+ },
60
+ {
61
+ label: 'Fail on warnings too',
62
+ code: 'const {data} = await integrationCheck({strict: true});\nif (data.failed) process.exitCode = 1;',
63
+ },
64
+ ],
65
+ command: 'integration check',
66
+ related: [
67
+ 'validateIntegration',
68
+ 'integrationComponentConflicts',
69
+ 'integrationTemplateConflicts',
70
+ 'integrationDocConflicts',
71
+ 'integrationPackCheck',
72
+ ],
73
+ };
@@ -10,17 +10,45 @@ export const doc = {
10
10
  namespace: 'cli/api',
11
11
  displayName: 'integrationComponentConflicts()',
12
12
  summary:
13
- 'Check integration component names and replaces declarations against Core.',
13
+ 'Check integration component names, replaces declarations, and documented props.',
14
14
  description:
15
15
  'Loads one local or installed integration, compares its component names with ' +
16
16
  'Core, and returns non-blocking conflicts with the exact package-qualified ' +
17
17
  "component command required to keep an intentional overlap. Each component's " +
18
18
  'replaces is checked too: its findings are issues, errors for a package that ' +
19
- 'declares the CLI range that turns replacement on and warnings otherwise.',
19
+ 'declares the CLI range that turns replacement on and warnings otherwise. ' +
20
+ "Each component doc's props are compared with the component's TypeScript props, " +
21
+ "read with the project's own `typescript`: a documented prop the code lacks, a " +
22
+ 'prop the package declares that the doc omits, a wrong `required`, or a type ' +
23
+ 'that does not match. A type is judged only when it reads exactly: the same ' +
24
+ 'text as the source, once whitespace, quotes and union order are set aside (exactly, if it contains `readonly`), ' +
25
+ 'or primitives, literals, arrays and unions of those, or a name for one, on ' +
26
+ 'both sides, a field such as `status.type` included, and for a union of ' +
27
+ "props types, the type of any one member. A field the prop's variants give " +
28
+ "different types is listed when the doc gives only some variants' types. A " +
29
+ 'mutable array type matches a readonly one, because callers can pass it; a ' +
30
+ 'readonly one documented for a mutable array is a mismatch. ' +
31
+ '`Name = …` with a union after it ' +
32
+ "must match in both parts; one value after it (`Size = 'md'`) is read as a " +
33
+ "default. A name only TypeScript's own libraries declare is not resolved, and " +
34
+ '`React.X` resolves through React where the prop is declared. Every ' +
35
+ 'other type is listed as not compared, never reported, and so is a name an ' +
36
+ 'index signature takes unless exactly one the package declares takes it; ' +
37
+ 'with a TypeScript older than 4.8 no type is judged. ' +
38
+ 'Those findings are warnings in `propDrift`; without TypeScript it is ' +
39
+ '`skipped` with a reason.',
20
40
  importPath: '@astryxdesign/cli/api',
21
41
  signature:
22
42
  'integrationComponentConflicts(pkg?: string, options?: IntegrationAuthoringOptions): Promise<IntegrationComponentConflictResponse>',
23
- keywords: ['integration', 'component', 'conflict', 'authoring', 'doctor'],
43
+ keywords: [
44
+ 'integration',
45
+ 'component',
46
+ 'conflict',
47
+ 'authoring',
48
+ 'doctor',
49
+ 'props',
50
+ 'drift',
51
+ ],
24
52
  params: [
25
53
  {name: 'pkg', type: 'string', description: 'Installed package; omit for the local package.'},
26
54
  {name: 'options.cwd', type: 'string', description: 'Resolution directory.'},
@@ -29,11 +57,14 @@ export const doc = {
29
57
  {
30
58
  type: 'integration.component-conflicts',
31
59
  description:
32
- '`validated` (false when no integration manifest was found, so nothing was inspected), integration identity, structural and replaces issues, and Core component-name conflicts with package-qualified commands. An active replacement that keeps the Core name it replaces is not a conflict.',
60
+ '`validated` (false when no integration manifest was found, so nothing was inspected), integration identity, structural and replaces issues, and Core component-name conflicts with package-qualified commands. An active replacement that keeps the Core name it replaces is not a conflict. When the manifest loads and declares a components root, `propDrift` holds the props comparison: `status` (`checked` | `skipped`), `reason` when skipped, `typescript`, `components` compared, `typesCompared` (documented prop types judged), `typesSkipped` (why none was, with a TypeScript older than 4.8), `findings` ({component, prop, kind: `not_in_code` | `not_in_doc` | `required_mismatch` | `type_mismatch`, severity: `warning`, message, doc, source, docType?, codeType?, docRequired?, codeRequired?}), `unchecked` ({component, doc, reason}), `typesNotCompared` ({component, doc, reason, props}: every other documented prop type, grouped by why it was not judged: not a form the check judges exactly (a doc type that does not parse, or a prop a mapped type builds from keys the check cannot trace, included), a field the prop\'s variants give different types, a standard prop such as `className`, or a prop declared outside the package), and `propsNotChecked` when a mapped type builds props from keys the check cannot trace to the package ({component, doc, reason, props}: no warning is drawn for those props).',
33
61
  },
34
62
  ],
35
63
  examples: [
36
- {label: 'Check the local integration', code: 'await integrationComponentConflicts();'},
64
+ {
65
+ label: 'Check the local integration',
66
+ code: 'await integrationComponentConflicts();',
67
+ },
37
68
  ],
38
69
  command: 'doctor integration components',
39
70
  related: ['integrationTemplateConflicts', 'component'],
@@ -24,7 +24,6 @@ import * as path from 'node:path';
24
24
  import * as os from 'node:os';
25
25
  import {fileURLToPath} from 'node:url';
26
26
  import {spawnSync} from 'node:child_process';
27
- import jscodeshift from 'jscodeshift';
28
27
  import {assertWithin} from '../../foundation/fs/path-safety.mjs';
29
28
  import {resolvePackageDir} from '../../foundation/integrations/integrations.mjs';
30
29
  import {
@@ -41,6 +40,7 @@ import {
41
40
  } from '../../foundation/discovery/docs-discovery.mjs';
42
41
  import {discoverIntegrationThemes} from '../../foundation/discovery/theme-discovery.mjs';
43
42
  import {inspectPackageExport} from '../../foundation/discovery/package-exports.mjs';
43
+ import {moduleExportsName} from '../../foundation/discovery/module-exports.mjs';
44
44
  import {
45
45
  ThemeImportError,
46
46
  resolveThemeImports,
@@ -51,6 +51,9 @@ import {
51
51
  } from '../theme/build/build.mjs';
52
52
  import {
53
53
  discoverIntegrationComponents,
54
+ docStem,
55
+ familyImports,
56
+ isIntegrationMember,
54
57
  resolveIntegrationImportPath,
55
58
  } from '../../foundation/discovery/component-discovery.mjs';
56
59
  import {loadComponentReplacements} from '../component/_adapter.mjs';
@@ -307,98 +310,6 @@ function extractTarball(tgzPath, extractDir) {
307
310
  }
308
311
  }
309
312
 
310
- const MODULE_EXTENSIONS = ['.mjs', '.js', '.mts', '.ts', '.tsx', '.jsx'];
311
- const moduleParser = jscodeshift.withParser('tsx');
312
-
313
- /** @param {unknown} specifier @param {string} fromFile */
314
- function resolveLocalModule(specifier, fromFile) {
315
- if (typeof specifier !== 'string' || !specifier.startsWith('.')) return null;
316
- const base = path.resolve(path.dirname(fromFile), specifier);
317
- const candidates = [
318
- base,
319
- ...MODULE_EXTENSIONS.map(extension => `${base}${extension}`),
320
- ...MODULE_EXTENSIONS.map(extension => path.join(base, `index${extension}`)),
321
- ];
322
- return candidates.find(candidate => fs.existsSync(candidate)) ?? null;
323
- }
324
-
325
- /** @param {any} declaration @param {string} exportName */
326
- function declarationExports(declaration, exportName) {
327
- if (!declaration) return false;
328
- if (['FunctionDeclaration', 'ClassDeclaration'].includes(declaration.type)) {
329
- return declaration.id?.name === exportName;
330
- }
331
- if (declaration.type !== 'VariableDeclaration') return false;
332
- return declaration.declarations.some(
333
- (/** @type {any} */ item) =>
334
- item.id?.type === 'Identifier' && item.id.name === exportName,
335
- );
336
- }
337
-
338
- /**
339
- * Check one resolved module's static export surface without executing package
340
- * code. Local ESM re-exports are followed recursively.
341
- *
342
- * @param {string} file
343
- * @param {string} exportName
344
- * @param {Set<string>} [seen]
345
- */
346
- function moduleExportsName(file, exportName, seen = new Set()) {
347
- const identity = `${file}\0${exportName}`;
348
- if (seen.has(identity) || !fs.existsSync(file)) return false;
349
- seen.add(identity);
350
-
351
- let root;
352
- try {
353
- root = moduleParser(fs.readFileSync(file, 'utf-8'));
354
- } catch {
355
- return false;
356
- }
357
-
358
- let found = false;
359
- root
360
- .find(moduleParser.ExportNamedDeclaration)
361
- .forEach((/** @type {any} */ exportPath) => {
362
- if (found) return;
363
- const node = exportPath.node;
364
- if (declarationExports(node.declaration, exportName)) {
365
- found = true;
366
- return;
367
- }
368
- for (const specifier of node.specifiers ?? []) {
369
- const exported = specifier.exported?.name ?? specifier.exported?.value;
370
- if (exported !== exportName) continue;
371
- if (!node.source) {
372
- found = true;
373
- return;
374
- }
375
- const target = resolveLocalModule(node.source.value, file);
376
- const imported =
377
- specifier.local?.name ?? specifier.local?.value ?? exportName;
378
- if (target && moduleExportsName(target, imported, seen)) {
379
- found = true;
380
- return;
381
- }
382
- }
383
- });
384
- if (found) return true;
385
- if (
386
- exportName === 'default' &&
387
- root.find(moduleParser.ExportDefaultDeclaration).size() > 0
388
- ) {
389
- return true;
390
- }
391
-
392
- root
393
- .find(moduleParser.ExportAllDeclaration)
394
- .forEach((/** @type {any} */ exportPath) => {
395
- if (found) return;
396
- const target = resolveLocalModule(exportPath.node.source?.value, file);
397
- if (target && moduleExportsName(target, exportName, seen)) found = true;
398
- });
399
- return found;
400
- }
401
-
402
313
  /**
403
314
  * Whether any of these doc files has a section that sets `id`.
404
315
  * @param {string[]} files
@@ -472,15 +383,20 @@ async function validatePackedComponentExports(integration, scratchBase) {
472
383
  const issues = [];
473
384
  /** @type {Array<{name: string, specifier: string}>} */
474
385
  const components = [];
386
+ // A family doc backs several records: load it, and report it, once.
387
+ const loads = new Map();
475
388
  for (const record of records) {
389
+ const first = !loads.has(record.docPath);
390
+ if (first) loads.set(record.docPath, loadComponentDoc(record.docPath));
476
391
  let docs;
477
392
  try {
478
- docs = await loadComponentDoc(record.docPath);
393
+ docs = await loads.get(record.docPath);
479
394
  } catch (err) {
395
+ if (!first) continue;
480
396
  issues.push(
481
397
  error(
482
398
  'component_doc_unloadable',
483
- `Component "${record.name}" doc at "${record.docPath}" could not be loaded: ${err instanceof Error ? err.message : String(err)}`,
399
+ `Component "${docStem(record.docPath)}" doc at "${record.docPath}" could not be loaded: ${err instanceof Error ? err.message : String(err)}`,
484
400
  ),
485
401
  );
486
402
  continue;
@@ -1085,6 +1001,24 @@ export async function integrationPackCheck(options = {}) {
1085
1001
  const fileInv = computeRequiredFiles(loaded);
1086
1002
  const {identities: localIdentities, errors: localDiscoveryErrors} =
1087
1003
  await collectIdentities(loaded);
1004
+ // A family lists its members from the module its import names, which the
1005
+ // pack lifecycle may build or rebuild, so they are compared as local
1006
+ // discovery lists them after the pack.
1007
+ const familiesBefore = familyImports(loaded).map(family => family.docPath);
1008
+ /**
1009
+ * @param {Array<{name: string, docPath: string|null}>} records
1010
+ * @param {Set<string>} families
1011
+ */
1012
+ const membersOf = (records, families) =>
1013
+ records
1014
+ .filter(
1015
+ r =>
1016
+ r.docPath != null &&
1017
+ families.has(r.docPath) &&
1018
+ isIntegrationMember(r),
1019
+ )
1020
+ .map(r => r.name);
1021
+ const recordsBefore = discoverIntegrationComponents(loaded);
1088
1022
  for (const discoveryError of localDiscoveryErrors) {
1089
1023
  issues.push(
1090
1024
  error(
@@ -1345,7 +1279,27 @@ export async function integrationPackCheck(options = {}) {
1345
1279
  );
1346
1280
  }
1347
1281
 
1348
- issues.push(...compareIdentities(localIdentities, packedIdentities));
1282
+ const families = new Set([
1283
+ ...familiesBefore,
1284
+ ...familyImports(loaded).map(family => family.docPath),
1285
+ ]);
1286
+ let localComponents = localIdentities.components;
1287
+ if (families.size > 0) {
1288
+ const stale = new Set(membersOf(recordsBefore, families));
1289
+ const built = new Set(
1290
+ membersOf(discoverIntegrationComponents(loaded), families),
1291
+ );
1292
+ const {identities: afterPack} = await collectIdentities(loaded);
1293
+ localComponents = [
1294
+ ...new Set([
1295
+ ...localComponents.filter(name => !stale.has(name)),
1296
+ ...afterPack.components.filter(name => built.has(name)),
1297
+ ]),
1298
+ ].sort();
1299
+ }
1300
+ // The receipt reports the local inventory that was compared.
1301
+ const comparedLocal = {...localIdentities, components: localComponents};
1302
+ issues.push(...compareIdentities(comparedLocal, packedIdentities));
1349
1303
  issues.push(
1350
1304
  ...(await validatePackedThemeExports(
1351
1305
  loaded,
@@ -1367,7 +1321,7 @@ export async function integrationPackCheck(options = {}) {
1367
1321
  pkgVersion,
1368
1322
  fileInv,
1369
1323
  packResult,
1370
- localIdentities,
1324
+ comparedLocal,
1371
1325
  packedIdentities,
1372
1326
  issues,
1373
1327
  );