@astryxdesign/cli 0.7.2-canary.f16dc9f → 0.7.2-canary.f2b60a0

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 (243) 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/component.test.mjs +420 -167
  7. package/api/component/integration-export-name.test.mjs +644 -0
  8. package/api/component/list/list.mjs +42 -4
  9. package/api/docs/_adapter.d.mts +11 -0
  10. package/api/docs/_adapter.mjs +244 -1
  11. package/api/docs/integrationDocs.test.mjs +124 -0
  12. package/api/doctor/doctor.mjs +27 -3
  13. package/api/doctor/doctor.test.mjs +43 -0
  14. package/api/doctor/theme-checks.mjs +1 -1
  15. package/api/gap-report/gap-report.mjs +67 -4
  16. package/api/index.d.mts +2 -0
  17. package/api/index.mjs +2 -0
  18. package/api/init/run/run.mjs +2 -2
  19. package/api/integration/_adapter.d.mts +12 -0
  20. package/api/integration/_adapter.mjs +54 -0
  21. package/api/integration/add-contribution.component-names.test.mjs +66 -0
  22. package/api/integration/add-contribution.mjs +8 -4
  23. package/api/integration/add-contribution.test.mjs +4 -2
  24. package/api/integration/authoring-checks.mjs +11 -2
  25. package/api/integration/authoring-checks.test.mjs +39 -0
  26. package/api/integration/authoring-checks.type.d.mts +9 -0
  27. package/api/integration/authoring-checks.type.mjs +14 -1
  28. package/api/integration/check/check.d.mts +96 -0
  29. package/api/integration/check/check.mjs +357 -0
  30. package/api/integration/check/check.test.mjs +489 -0
  31. package/api/integration/integration-check.type.d.mts +118 -0
  32. package/api/integration/integration-check.type.mjs +84 -0
  33. package/api/integration/integrationCheck.doc.d.mts +10 -0
  34. package/api/integration/integrationCheck.doc.mjs +73 -0
  35. package/api/integration/integrationComponentConflicts.doc.mjs +36 -5
  36. package/api/integration/pack-check.mjs +61 -107
  37. package/api/integration/pack-check.test.mjs +359 -0
  38. package/api/integration/validate-integration.mjs +8 -0
  39. package/api/integration/validate-integration.test.mjs +11 -0
  40. package/api/integration/validate-integration.type.d.mts +8 -0
  41. package/api/integration/validate-integration.type.mjs +12 -1
  42. package/api/integration/validateIntegration.doc.mjs +17 -3
  43. package/api/search/search.d.mts +22 -6
  44. package/api/search/search.domain-priority.test.mjs +108 -18
  45. package/api/search/search.integration-check.test.mjs +24 -0
  46. package/api/search/search.mjs +141 -25
  47. package/api/search/search.test.mjs +36 -0
  48. package/api/swizzle/copy/copy.mjs +28 -14
  49. package/api/template/template.test.mjs +120 -3
  50. package/api/theme/build/build.family.test.mjs +64 -0
  51. package/api/theme/build/build.mjs +180 -24
  52. package/api/theme/themeBuild.doc.mjs +4 -4
  53. package/assets/codemods/transforms/next/__tests__/migrate-list-edge-compensation-to-edge.test.mjs +120 -0
  54. package/assets/codemods/transforms/next/index.mjs +11 -1
  55. package/assets/codemods/transforms/next/migrate-list-edge-compensation-to-edge.mjs +198 -0
  56. package/assets/docs/README.md +10 -0
  57. package/assets/docs/author-a-theme.doc.mjs +1 -1
  58. package/assets/docs/getting-started.doc.mjs +3 -3
  59. package/assets/docs/styling-overview.doc.mjs +1 -1
  60. package/assets/docs/theme.doc.dense.mjs +1 -1
  61. package/assets/docs/theme.doc.mjs +1 -1
  62. package/assets/docs/theme.doc.zh.mjs +1 -1
  63. package/assets/docs/tree/add-a-component.doc.mjs +11 -1
  64. package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
  65. package/assets/docs/tree/add-a-topic.doc.mjs +10 -7
  66. package/assets/docs/tree/agent-guidance.doc.mjs +2 -2
  67. package/assets/docs/tree/block-template.doc.mjs +8 -20
  68. package/assets/docs/tree/build-the-template.doc.mjs +1 -1
  69. package/assets/docs/tree/check-the-props.doc.mjs +196 -0
  70. package/assets/docs/tree/check-your-docs.doc.mjs +51 -5
  71. package/assets/docs/tree/checks.doc.mjs +24 -16
  72. package/assets/docs/tree/codemods.doc.mjs +4 -4
  73. package/assets/docs/tree/component-family.doc.mjs +8 -6
  74. package/assets/docs/tree/component-imports.doc.mjs +10 -2
  75. package/assets/docs/tree/components.doc.mjs +1 -1
  76. package/assets/docs/tree/debug-and-gap-reports.doc.mjs +7 -18
  77. package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
  78. package/assets/docs/tree/describe-the-component.doc.mjs +2 -2
  79. package/assets/docs/tree/docs.doc.mjs +3 -3
  80. package/assets/docs/tree/document-the-template.doc.mjs +1 -1
  81. package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
  82. package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
  83. package/assets/docs/tree/extend-or-replace.doc.mjs +6 -6
  84. package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
  85. package/assets/docs/tree/generate-a-palette.doc.mjs +5 -3
  86. package/assets/docs/tree/grade-template-with-agent.doc.mjs +1 -1
  87. package/assets/docs/tree/integrations.doc.mjs +8 -0
  88. package/assets/docs/tree/links.doc.mjs +6 -5
  89. package/assets/docs/tree/migration-setup.doc.mjs +1 -0
  90. package/assets/docs/tree/migration-verification.doc.mjs +0 -1
  91. package/assets/docs/tree/migration.doc.mjs +0 -1
  92. package/assets/docs/tree/package-and-test.doc.mjs +1 -1
  93. package/assets/docs/tree/page-template.doc.mjs +4 -8
  94. package/assets/docs/tree/publishing.doc.mjs +1 -1
  95. package/assets/docs/tree/quick-start.doc.mjs +37 -21
  96. package/assets/docs/tree/replace-a-core-component.doc.mjs +4 -6
  97. package/assets/docs/tree/replace-a-core-template.doc.mjs +6 -8
  98. package/assets/docs/tree/sections-and-placement.doc.mjs +4 -6
  99. package/assets/docs/tree/see-it-in-an-app.doc.mjs +4 -2
  100. package/assets/docs/tree/short-and-findable.doc.mjs +6 -5
  101. package/assets/docs/tree/single-component.doc.mjs +15 -6
  102. package/assets/docs/tree/start-a-template.doc.mjs +3 -3
  103. package/assets/docs/tree/subcomponent.doc.mjs +6 -6
  104. package/assets/docs/tree/template-assets.doc.mjs +2 -2
  105. package/assets/docs/tree/template-doc-overview.doc.mjs +4 -15
  106. package/assets/docs/tree/template-fonts.doc.mjs +1 -1
  107. package/assets/docs/tree/template-icons.doc.mjs +1 -1
  108. package/assets/docs/tree/template-images-media.doc.mjs +1 -1
  109. package/assets/docs/tree/template-styles.doc.mjs +2 -2
  110. package/assets/docs/tree/templates.doc.mjs +1 -1
  111. package/assets/docs/tree/test-in-an-app.doc.mjs +3 -3
  112. package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
  113. package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
  114. package/assets/docs/tree/troubleshooting.doc.mjs +13 -5
  115. package/assets/docs/tree/type-scale.doc.mjs +3 -3
  116. package/assets/docs/tree/upgrading.doc.mjs +3 -3
  117. package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +3 -1
  118. package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
  119. package/assets/docs/tree/versioning.doc.mjs +7 -6
  120. package/assets/docs/tree/write-the-template-file.doc.mjs +4 -3
  121. package/assets/docs/use-a-theme.doc.mjs +2 -2
  122. package/assets/docs/working-with-ai.doc.mjs +43 -4
  123. package/assets/templates/blocks/components/Drawer/DrawerForm.doc.mjs +24 -0
  124. package/assets/templates/blocks/components/Drawer/DrawerForm.tsx +62 -0
  125. package/assets/templates/blocks/components/Drawer/DrawerScrim.doc.mjs +22 -0
  126. package/assets/templates/blocks/components/Drawer/DrawerScrim.tsx +68 -0
  127. package/assets/templates/blocks/components/Drawer/DrawerShowcase.doc.mjs +22 -0
  128. package/assets/templates/blocks/components/Drawer/DrawerShowcase.tsx +41 -0
  129. package/assets/templates/blocks/components/Drawer/DrawerStart.doc.mjs +21 -0
  130. package/assets/templates/blocks/components/Drawer/DrawerStart.tsx +41 -0
  131. package/assets/templates/blocks/components/Drawer/DrawerWidths.doc.mjs +22 -0
  132. package/assets/templates/blocks/components/Drawer/DrawerWidths.tsx +54 -0
  133. package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderShowcase.doc.mjs +15 -0
  134. package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderShowcase.tsx +73 -0
  135. package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderSubtitleEndContent.doc.mjs +22 -0
  136. package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderSubtitleEndContent.tsx +49 -0
  137. package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderWithCloseButton.doc.mjs +21 -0
  138. package/assets/templates/blocks/components/DrawerHeader/DrawerHeaderWithCloseButton.tsx +42 -0
  139. package/assets/templates/pages/detail-page/page.tsx +3 -3
  140. package/assets/templates/pages/table-inbox/page.tsx +10 -8
  141. package/assets/theme.template.ts +1 -1
  142. package/authoring/codemod/codemod.doc.mjs +7 -3
  143. package/authoring/config/config.doc.mjs +9 -6
  144. package/authoring/config/type.ts +4 -2
  145. package/authoring/discover/discover.doc.mjs +1 -1
  146. package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
  147. package/authoring/doctypes/base/type.ts +6 -0
  148. package/authoring/doctypes/command/command.doc.mjs +14 -12
  149. package/authoring/doctypes/component/component.doc.mjs +31 -28
  150. package/authoring/doctypes/component/type.ts +13 -8
  151. package/authoring/doctypes/enum/enum.doc.mjs +7 -4
  152. package/authoring/doctypes/function/function.doc.mjs +9 -8
  153. package/authoring/doctypes/hook/hook.doc.mjs +5 -4
  154. package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
  155. package/authoring/doctypes/reference/reference.doc.mjs +11 -10
  156. package/authoring/doctypes/schema/schema.doc.mjs +9 -9
  157. package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
  158. package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
  159. package/authoring/doctypes/template/parse.d.mts +6 -2
  160. package/authoring/doctypes/template/parse.mjs +3 -1
  161. package/authoring/doctypes/template/template.doc.mjs +3 -3
  162. package/authoring/doctypes/template/type.ts +22 -0
  163. package/authoring/doctypes/theme/theme.doc.mjs +2 -2
  164. package/authoring/gap-report/gap-report.doc.mjs +2 -2
  165. package/authoring/identity/identity.doc.mjs +3 -3
  166. package/authoring/index.d.ts +1 -0
  167. package/authoring/integration/integration.doc.mjs +12 -4
  168. package/authoring/integration/parse.test.mjs +10 -0
  169. package/authoring/integration/schema.d.mts +2 -0
  170. package/authoring/integration/schema.mjs +1 -0
  171. package/authoring/integration/type.ts +4 -0
  172. package/clients/cli/commands/build-theme.batch-report.test.mjs +3 -1
  173. package/clients/cli/commands/build-theme.mjs +1 -1
  174. package/clients/cli/commands/build-theme.path-safety.test.mjs +224 -6
  175. package/clients/cli/commands/build-theme.registry.test.mjs +5 -2
  176. package/clients/cli/commands/cli-postinstall.test.mjs +49 -1
  177. package/clients/cli/commands/doctor-integration-components.doc.mjs +15 -4
  178. package/clients/cli/commands/doctor-integration-validate.doc.mjs +11 -2
  179. package/clients/cli/commands/doctor-integration.test.mjs +837 -0
  180. package/clients/cli/commands/doctor.mjs +190 -0
  181. package/clients/cli/commands/integration-check.doc.mjs +85 -0
  182. package/clients/cli/commands/integration-check.test.mjs +267 -0
  183. package/clients/cli/commands/integration.doc.mjs +6 -2
  184. package/clients/cli/commands/integration.mjs +107 -1
  185. package/clients/cli/commands/setup-nudge.test.mjs +79 -1
  186. package/clients/cli/commands/swizzle.mjs +5 -3
  187. package/clients/cli/commands/swizzle.routing.test.mjs +18 -1
  188. package/clients/cli/commands/text-json-parity.test.mjs +5 -0
  189. package/clients/cli/commands/theme-build.doc.mjs +2 -2
  190. package/clients/cli/index.mjs +30 -5
  191. package/clients/cli/lib/component-format.mjs +5 -1
  192. package/foundation/agent-docs/agent-doc-state.d.mts +14 -0
  193. package/foundation/agent-docs/agent-doc-state.mjs +38 -0
  194. package/foundation/agent-docs/agent-docs.d.mts +2 -1
  195. package/foundation/agent-docs/agent-docs.mjs +8 -6
  196. package/foundation/config/project.mjs +82 -0
  197. package/foundation/discovery/authoring-self-docs.mjs +2 -1
  198. package/foundation/discovery/component-discovery.d.mts +190 -10
  199. package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
  200. package/foundation/discovery/component-discovery.mjs +864 -56
  201. package/foundation/discovery/component-discovery.test.mjs +96 -0
  202. package/foundation/discovery/component-replacement.mjs +18 -0
  203. package/foundation/discovery/component-replacement.test.mjs +40 -0
  204. package/foundation/discovery/doc-types.d.mts +157 -0
  205. package/foundation/discovery/doc-types.mjs +1224 -0
  206. package/foundation/discovery/doc-types.test.mjs +652 -0
  207. package/foundation/discovery/docs-discovery.d.mts +81 -0
  208. package/foundation/discovery/docs-discovery.mjs +57 -0
  209. package/foundation/discovery/integration-doc-files.d.mts +22 -0
  210. package/foundation/discovery/integration-doc-files.mjs +231 -0
  211. package/foundation/discovery/integration-doc-files.test.mjs +246 -0
  212. package/foundation/discovery/module-exports.d.mts +27 -0
  213. package/foundation/discovery/module-exports.mjs +356 -31
  214. package/foundation/discovery/module-exports.test.mjs +172 -0
  215. package/foundation/discovery/package-tsconfig.d.mts +14 -0
  216. package/foundation/discovery/package-tsconfig.mjs +228 -0
  217. package/foundation/discovery/package-tsconfig.test.mjs +210 -0
  218. package/foundation/discovery/project-typescript.d.mts +23 -0
  219. package/foundation/discovery/project-typescript.mjs +61 -0
  220. package/foundation/discovery/prop-drift.d.mts +278 -0
  221. package/foundation/discovery/prop-drift.mjs +2263 -0
  222. package/foundation/discovery/prop-drift.test.mjs +2219 -0
  223. package/foundation/discovery/template-adapter.mjs +74 -5
  224. package/foundation/discovery/type-match.d.mts +53 -0
  225. package/foundation/discovery/type-match.mjs +660 -0
  226. package/foundation/discovery/type-match.test.mjs +169 -0
  227. package/foundation/discovery/type-text.d.mts +68 -0
  228. package/foundation/discovery/type-text.mjs +658 -0
  229. package/foundation/discovery/type-text.test.mjs +196 -0
  230. package/foundation/discovery/unresolved-references.d.mts +62 -0
  231. package/foundation/discovery/unresolved-references.mjs +790 -0
  232. package/foundation/discovery/unresolved-references.test.mjs +567 -0
  233. package/foundation/doc-compiler/doc-loads.test.mjs +11 -1
  234. package/foundation/doc-compiler/inputs.mjs +23 -0
  235. package/foundation/doc-compiler/inputs.test.mjs +0 -1
  236. package/foundation/doc-compiler/links.d.mts +9 -5
  237. package/foundation/doc-compiler/links.mjs +4 -2
  238. package/foundation/integrations/contribution-inventory.mjs +6 -1
  239. package/foundation/response/response-types.doc.mjs +7 -2
  240. package/foundation/xle/registry.mjs +4 -0
  241. package/foundation/xle/registry.test.mjs +28 -0
  242. package/package.json +10 -10
  243. package/scripts/postinstall.mjs +3 -3
@@ -0,0 +1,228 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Read a package's TypeScript project the way `tsc` reads it, so a
5
+ * check that reads the package's types with its own compiler sees what the
6
+ * package compiles against.
7
+ *
8
+ * - The config is the nearest `tsconfig.json`: the package's own, or a
9
+ * monorepo's root config for a local package. An app's config above an
10
+ * installed package (one below `node_modules`) is not the package's, and is
11
+ * not read.
12
+ * - A solution-style config (`"files": []` with `references`) lends the
13
+ * referenced config, at any depth, that includes the files to read.
14
+ * - The declaration files the config includes, included files that declare
15
+ * globals or augment a module, and included scripts (no import or export)
16
+ * join the files to read, so ambient types resolve as `tsc` resolves them.
17
+ * - Without a config, defaults read TSX the way bundlers do. A config with
18
+ * errors is reported, never read as defaults.
19
+ *
20
+ * @input The project's own `typescript`, the package directory, and the
21
+ * source files to read.
22
+ * @output Compiler options, the declaration files to add, and the config's
23
+ * error, if any.
24
+ * @position Foundation discovery, used by the documented-props comparison.
25
+ */
26
+
27
+ import * as fs from 'node:fs';
28
+ import * as path from 'node:path';
29
+
30
+ /**
31
+ * Whether a file the config includes declares types every file sees: a
32
+ * declaration file, a file that declares globals or augments a module, or a
33
+ * script (a `.ts` file that TypeScript reads as no module).
34
+ * @param {any} ts
35
+ * @param {string} file
36
+ */
37
+ function isAmbient(ts, file) {
38
+ if (/\.d\.[cm]?ts$/.test(file)) return true;
39
+ let text;
40
+ try {
41
+ text = fs.readFileSync(file, 'utf8');
42
+ } catch {
43
+ return false;
44
+ }
45
+ if (/\bdeclare\s+(?:global|module)\b/.test(text)) return true;
46
+ if (!/\.tsx?$/.test(file)) return false;
47
+ // Without the words, no module; with them, TypeScript decides.
48
+ return (
49
+ !/\b(?:import|export)\b/.test(text) ||
50
+ !ts.isExternalModule(
51
+ ts.createSourceFile(file, text, ts.ScriptTarget.Latest),
52
+ )
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Read a config's `files` entries as `tsc` does, and report the first it
58
+ * cannot find. TypeScript itself substitutes `${configDir}` (on a version
59
+ * that has it), resolves each entry against the config that writes it, and
60
+ * tries the TypeScript extensions for one written without its own, so this
61
+ * cannot drift from `tsc`. The parsed file names then hold the files found.
62
+ * A file the `include` matched that is gone by the time it is read was
63
+ * another process's, and is not checked here.
64
+ * @param {any} ts
65
+ * @param {any} raw the config's JSON
66
+ * @param {string} configPath
67
+ * @param {any} config the parsed config, whose file names are updated
68
+ * @returns {string | null} the message `tsc` gives for the first entry it
69
+ * cannot find (TS6053, or TS6231 for a folder)
70
+ */
71
+ function readFilesEntries(ts, raw, configPath, config) {
72
+ const literal = ts.parseJsonConfigFileContent(
73
+ raw,
74
+ {...ts.sys, readDirectory: () => []},
75
+ path.dirname(configPath),
76
+ undefined,
77
+ configPath,
78
+ ).fileNames;
79
+ if (literal.length === 0 || typeof ts.createProgram !== 'function') {
80
+ return null;
81
+ }
82
+ // Only the entries themselves: no library, no types, no imports.
83
+ const program = ts.createProgram({
84
+ rootNames: literal,
85
+ options: {
86
+ ...config.options,
87
+ noLib: true,
88
+ noResolve: true,
89
+ types: [],
90
+ noEmit: true,
91
+ },
92
+ });
93
+ const missing = program
94
+ .getOptionsDiagnostics()
95
+ .find(/** @param {any} d */ d => d.code === 6053 || d.code === 6231);
96
+ if (missing) {
97
+ const text = missing.messageText;
98
+ return typeof text === 'string' ? text : text.messageText;
99
+ }
100
+ const entries = new Set(
101
+ literal.map(/** @param {string} f */ f => path.resolve(f)),
102
+ );
103
+ config.fileNames = [
104
+ ...config.fileNames.filter(
105
+ /** @param {string} f */ f => !entries.has(path.resolve(f)),
106
+ ),
107
+ ...program.getSourceFiles().map(/** @param {any} sf */ sf => sf.fileName),
108
+ ];
109
+ return null;
110
+ }
111
+
112
+ /**
113
+ * @param {any} ts the project's own `typescript`
114
+ * @param {string} packageDir
115
+ * @param {string[]} sources the files to read
116
+ * @returns {{options: any, declarations: string[], error?: string}}
117
+ */
118
+ export function readPackageTsconfig(ts, packageDir, sources) {
119
+ const defaults = {
120
+ jsx: ts.JsxEmit.ReactJSX,
121
+ target: ts.ScriptTarget.ES2022,
122
+ module: ts.ModuleKind.ESNext,
123
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
124
+ strict: true,
125
+ allowJs: true,
126
+ esModuleInterop: true,
127
+ resolveJsonModule: true,
128
+ allowImportingTsExtensions: true,
129
+ };
130
+ /** @type {string | undefined} */
131
+ let error;
132
+ /** @type {(configPath: string) => any} */
133
+ const parse = configPath => {
134
+ const read = ts.readConfigFile(configPath, ts.sys.readFile);
135
+ const config = read.error
136
+ ? {errors: [read.error], fileNames: [], options: {}}
137
+ : ts.parseJsonConfigFileContent(
138
+ read.config,
139
+ ts.sys,
140
+ path.dirname(configPath),
141
+ undefined,
142
+ configPath,
143
+ );
144
+ // "No inputs were found" is how a solution-style config reads.
145
+ const problem = config.errors.find(
146
+ /** @param {any} d */ d => d.code !== 18003,
147
+ );
148
+ const where = path.relative(packageDir, configPath);
149
+ if (problem) {
150
+ config.error = `${where}: ${ts.flattenDiagnosticMessageText(problem.messageText, ' ')}`;
151
+ } else if (!read.error) {
152
+ const missing = readFilesEntries(ts, read.config, configPath, config);
153
+ if (missing) {
154
+ // Paths read from the package, as the config's other errors are.
155
+ const dir = path.dirname(configPath).split(path.sep).join('/');
156
+ const rel = path.relative(packageDir, path.dirname(configPath));
157
+ config.error = `${where}: ${missing
158
+ .split(`${dir}/`)
159
+ .join(rel ? `${rel.split(path.sep).join('/')}/` : '')}`;
160
+ }
161
+ }
162
+ return config;
163
+ };
164
+ /** @type {any} */
165
+ let parsed;
166
+ try {
167
+ const found = ts.findConfigFile(
168
+ packageDir,
169
+ ts.sys.fileExists,
170
+ 'tsconfig.json',
171
+ );
172
+ // A config above the package is an app's when the package is installed
173
+ // below it (`node_modules`), and the package's own otherwise (a
174
+ // monorepo's root config, as `tsc` reads it).
175
+ const installed =
176
+ found &&
177
+ path
178
+ .relative(path.dirname(found), packageDir)
179
+ .split(path.sep)
180
+ .includes('node_modules');
181
+ parsed = found && !installed ? parse(found) : null;
182
+ const own = new Set(sources.map(file => path.resolve(file)));
183
+ /** @type {(config: any) => boolean} */
184
+ const includes = config =>
185
+ config.fileNames.some(
186
+ /** @param {string} file */ file => own.has(path.resolve(file)),
187
+ );
188
+ // Every reference, at any depth, each config once (a cycle ends). One
189
+ // that is missing or doesn't parse is the project's error when no config
190
+ // includes the sources, as `tsc` reports it.
191
+ const visited = new Set(found ? [path.resolve(found)] : []);
192
+ /** @type {any} */
193
+ let broken = null;
194
+ /** @type {(config: any) => any} */
195
+ const including = config => {
196
+ if (includes(config)) return config;
197
+ for (const ref of config.projectReferences ?? []) {
198
+ const next = path.resolve(ts.resolveProjectReferencePath(ref));
199
+ if (visited.has(next)) continue;
200
+ visited.add(next);
201
+ const referenced = parse(next);
202
+ if (referenced.error) broken ??= referenced;
203
+ const hit = including(referenced);
204
+ if (hit) return hit;
205
+ }
206
+ return null;
207
+ };
208
+ if (parsed) parsed = including(parsed) ?? broken ?? parsed;
209
+ } catch {
210
+ parsed = null;
211
+ }
212
+ error = parsed?.error;
213
+ const options = parsed ? {...parsed.options} : defaults;
214
+ if (options.jsx == null) options.jsx = defaults.jsx;
215
+ return {
216
+ options: {
217
+ ...options,
218
+ noEmit: true,
219
+ skipLibCheck: true,
220
+ incremental: false,
221
+ composite: false,
222
+ },
223
+ ...(error ? {error} : {}),
224
+ declarations: (parsed?.fileNames ?? []).filter(
225
+ /** @param {string} file */ file => isAmbient(ts, file),
226
+ ),
227
+ };
228
+ }
@@ -0,0 +1,210 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file readPackageTsconfig(): the package's own config, a solution-style
5
+ * config's references, a monorepo root's config for a local package but not
6
+ * an app's above an installed one, ambient declaration files, and a config
7
+ * with errors.
8
+ */
9
+
10
+ import * as fs from 'node:fs';
11
+ import * as os from 'node:os';
12
+ import * as path from 'node:path';
13
+ import {createRequire} from 'node:module';
14
+ import {afterEach, beforeEach, describe, expect, it} from 'vitest';
15
+ import {readPackageTsconfig} from './package-tsconfig.mjs';
16
+
17
+ const require = createRequire(import.meta.url);
18
+ const ts = require('typescript');
19
+
20
+ /** @type {string} */
21
+ let root;
22
+
23
+ beforeEach(() => {
24
+ root = fs.realpathSync(
25
+ fs.mkdtempSync(path.join(os.tmpdir(), 'astryx-tsconfig-')),
26
+ );
27
+ });
28
+
29
+ afterEach(() => {
30
+ fs.rmSync(root, {recursive: true, force: true});
31
+ });
32
+
33
+ /**
34
+ * @param {string} rel
35
+ * @param {string | object} content
36
+ */
37
+ function write(rel, content) {
38
+ const file = path.join(root, rel);
39
+ fs.mkdirSync(path.dirname(file), {recursive: true});
40
+ fs.writeFileSync(
41
+ file,
42
+ typeof content === 'string' ? content : JSON.stringify(content),
43
+ );
44
+ return file;
45
+ }
46
+
47
+ const OPTIONS = {strict: true, jsx: 'react-jsx', paths: {'@/*': ['./src/*']}};
48
+
49
+ describe('readPackageTsconfig', () => {
50
+ it('reads defaults without a config, and the config the package has, with the declaration files it includes', () => {
51
+ const source = write('src/Button.tsx', 'export {};\n');
52
+ const defaults = readPackageTsconfig(ts, root, [source]);
53
+ expect(defaults.options).toMatchObject({
54
+ jsx: ts.JsxEmit.ReactJSX,
55
+ strict: true,
56
+ noEmit: true,
57
+ });
58
+ expect(defaults.declarations).toEqual([]);
59
+ expect(defaults.error).toBeUndefined();
60
+
61
+ write('tsconfig.json', {
62
+ compilerOptions: OPTIONS,
63
+ include: ['src', 'types'],
64
+ });
65
+ const ambient = write('types/ambient.d.ts', 'interface A {}\n');
66
+ const global = write('src/global.ts', 'declare global {}\nexport {};\n');
67
+ // A script (no import or export) declares globals too, an `export`
68
+ // inside its namespace included.
69
+ const script = write('types/props.ts', 'interface ScriptProps {}\n');
70
+ const namespace = write(
71
+ 'types/acme.ts',
72
+ 'declare namespace Acme {\n export interface Props {}\n}\n',
73
+ );
74
+ write('src/module.ts', 'export interface ModuleProps {}\n');
75
+
76
+ const own = readPackageTsconfig(ts, root, [source]);
77
+
78
+ expect(own.options.paths).toEqual(OPTIONS.paths);
79
+ expect(own.declarations.sort()).toEqual(
80
+ [global, ambient, script, namespace].sort(),
81
+ );
82
+ });
83
+
84
+ it("follows a solution-style config's references, at any depth and through a cycle, to the config that includes the sources", () => {
85
+ const source = write('src/Button.tsx', 'export {};\n');
86
+ const chain = [
87
+ 'tsconfig.json',
88
+ 'l1.json',
89
+ 'l2.json',
90
+ 'l3.json',
91
+ 'l4.json',
92
+ 'l5.json',
93
+ ];
94
+ chain.forEach((file, i) =>
95
+ write(file, {
96
+ files: [],
97
+ // Each level also refers back to the root, a cycle tsc rejects but
98
+ // the walk must not loop on.
99
+ references: [
100
+ {path: `./${chain[i + 1] ?? 'app.json'}`},
101
+ {path: './tsconfig.json'},
102
+ ],
103
+ }),
104
+ );
105
+ write('app.json', {compilerOptions: OPTIONS, include: ['src']});
106
+
107
+ const project = readPackageTsconfig(ts, root, [source]);
108
+
109
+ expect(project.error).toBeUndefined();
110
+ expect(project.options.paths).toEqual(OPTIONS.paths);
111
+
112
+ // A reference that is missing or doesn't parse, when no config includes
113
+ // the sources, is the project's error, as `tsc` reports it.
114
+ write('tsconfig.json', {files: [], references: [{path: './gone.json'}]});
115
+ expect(readPackageTsconfig(ts, root, [source]).error).toMatch(
116
+ /^gone\.json: .*gone\.json/,
117
+ );
118
+ write('broken.json', '{"compilerOptions": ');
119
+ write('tsconfig.json', {files: [], references: [{path: './broken.json'}]});
120
+ expect(readPackageTsconfig(ts, root, [source]).error).toMatch(
121
+ /^broken\.json: /,
122
+ );
123
+ });
124
+
125
+ it("reads a monorepo root's config for a local package, and not an app's config for a package installed below it", () => {
126
+ write('tsconfig.json', {compilerOptions: OPTIONS});
127
+ const local = path.join(root, 'packages', 'local');
128
+ const installed = path.join(root, 'node_modules', '@acme', 'widgets');
129
+ const localSource = write('packages/local/src/Button.tsx', 'export {};\n');
130
+ const installedSource = write(
131
+ 'node_modules/@acme/widgets/src/Button.tsx',
132
+ 'export {};\n',
133
+ );
134
+
135
+ expect(readPackageTsconfig(ts, local, [localSource]).options.paths).toEqual(
136
+ OPTIONS.paths,
137
+ );
138
+ expect(
139
+ readPackageTsconfig(ts, installed, [installedSource]).options.paths,
140
+ ).toBeUndefined();
141
+ });
142
+
143
+ it('reports a config with errors instead of reading defaults', () => {
144
+ const source = write('src/Button.tsx', 'export {};\n');
145
+ write('tsconfig.json', {files: ['src/Button.tsx', 'src/missing.ts']});
146
+ expect(readPackageTsconfig(ts, root, [source]).error).toMatch(
147
+ /^tsconfig\.json: .*src\/missing\.ts.* not found/,
148
+ );
149
+
150
+ write('tsconfig.json', {extends: './missing-base.json'});
151
+ expect(readPackageTsconfig(ts, root, [source]).error).toMatch(
152
+ /^tsconfig\.json: .*missing-base\.json/,
153
+ );
154
+
155
+ write('tsconfig.json', '{"compilerOptions": ');
156
+ expect(readPackageTsconfig(ts, root, [source]).error).toMatch(
157
+ /^tsconfig\.json: /,
158
+ );
159
+ });
160
+
161
+ it('reads each `files` entry as `tsc` finds it', () => {
162
+ const source = write('src/Button.tsx', 'export {};\n');
163
+ write('src/Card.ts', 'export {};\n');
164
+ write('types/globals.ts', 'interface AcmeGlobal { tone?: string }\n');
165
+ write('configs/base.json', {files: ['${configDir}/src/Button.tsx']});
166
+ /** @type {(config: object) => any} */
167
+ const read = config => {
168
+ write('tsconfig.json', {compilerOptions: OPTIONS, ...config});
169
+ return readPackageTsconfig(ts, root, [source]);
170
+ };
171
+
172
+ // `${configDir}`, written in the package's config or in a shared base it
173
+ // extends, and an entry without its extension compile with `tsc -p`.
174
+ expect(
175
+ read({files: ['${configDir}/src/Button.tsx']}).error,
176
+ ).toBeUndefined();
177
+ expect(read({extends: './configs/base.json'}).error).toBeUndefined();
178
+ const bare = read({files: ['src/Button', 'src/Card', 'types/globals']});
179
+ expect(bare.error).toBeUndefined();
180
+ expect(bare.declarations).toEqual([path.join(root, 'types', 'globals.ts')]);
181
+
182
+ // An entry `tsc` can't find is an error: a missing file (TS6053), and a
183
+ // folder (TS6231).
184
+ expect(read({files: ['src/Button.tsx', 'src/gone.ts']}).error).toMatch(
185
+ /^tsconfig\.json: File 'src\/gone\.ts' not found\.$/,
186
+ );
187
+ expect(read({files: ['src']}).error).toMatch(
188
+ /^tsconfig\.json: Could not resolve the path 'src' with the extensions/,
189
+ );
190
+ });
191
+
192
+ it('reports only a missing `files` entry, not a file the `include` matched that is gone by the time it is read', () => {
193
+ const source = write('src/Button.tsx', 'export {};\n');
194
+ write('tsconfig.json', {compilerOptions: OPTIONS, include: ['src']});
195
+ // A file another process writes and removes while the config is read.
196
+ const gone = path.join(root, 'src', 'gone.ts');
197
+ const racing = {
198
+ ...ts,
199
+ sys: {
200
+ ...ts.sys,
201
+ readDirectory: (/** @type {any[]} */ ...args) => [
202
+ ...ts.sys.readDirectory(...args),
203
+ gone,
204
+ ],
205
+ },
206
+ };
207
+
208
+ expect(readPackageTsconfig(racing, root, [source]).error).toBeUndefined();
209
+ });
210
+ });
@@ -0,0 +1,23 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /**
5
+ * Resolve the project's own `typescript`, trying each directory in order.
6
+ * @param {Array<string | null | undefined>} dirs
7
+ * @returns {{ts: any, version: string} | {ts: null, version: string} | null}
8
+ * `ts: null` when a TypeScript is installed but has no `createProgram`
9
+ */
10
+ export function resolveProjectTypeScript(dirs: Array<string | null | undefined>): {
11
+ ts: any;
12
+ version: string;
13
+ } | {
14
+ ts: null;
15
+ version: string;
16
+ } | null;
17
+ /**
18
+ * Why a check that needs the compiler did not run, when the TypeScript
19
+ * installed has no JavaScript compiler API.
20
+ * @param {string} version
21
+ * @param {string} outcome what was not done, such as `docs were not checked`
22
+ */
23
+ export function unusableTypeScriptReason(version: string, outcome: string): string;
@@ -0,0 +1,61 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Find the project's own TypeScript compiler.
5
+ *
6
+ * @input Directories to resolve `typescript` from, in order.
7
+ * @output The compiler module and its version; or, when the only TypeScript
8
+ * installed has no JavaScript compiler API to read types with, its version
9
+ * alone; or null when none is installed.
10
+ * @position Shared by the author checks that read source with the project's
11
+ * compiler, so each finds it, and says why it cannot use it, the same way.
12
+ */
13
+
14
+ import * as path from 'node:path';
15
+ import {createRequire} from 'node:module';
16
+
17
+ /**
18
+ * Resolve the project's own `typescript`, trying each directory in order.
19
+ * @param {Array<string | null | undefined>} dirs
20
+ * @returns {{ts: any, version: string} | {ts: null, version: string} | null}
21
+ * `ts: null` when a TypeScript is installed but has no `createProgram`
22
+ */
23
+ export function resolveProjectTypeScript(dirs) {
24
+ /** @type {string | null} */
25
+ let unusable = null;
26
+ for (const dir of dirs) {
27
+ if (!dir) continue;
28
+ const require = createRequire(path.join(path.resolve(dir), 'package.json'));
29
+ /** The version of the `typescript` package installed here, or null. */
30
+ const installed = () => {
31
+ try {
32
+ const {version} = require('typescript/package.json');
33
+ return typeof version === 'string' && version !== '' ? version : null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ };
38
+ try {
39
+ const ts = require('typescript');
40
+ if (ts && typeof ts.createProgram === 'function') {
41
+ return {ts, version: String(ts.version)};
42
+ }
43
+ unusable ??= installed();
44
+ } catch {
45
+ // Not installed from here, or installed without an entry this CLI can
46
+ // load; try the next directory.
47
+ unusable ??= installed();
48
+ }
49
+ }
50
+ return unusable ? {ts: null, version: unusable} : null;
51
+ }
52
+
53
+ /**
54
+ * Why a check that needs the compiler did not run, when the TypeScript
55
+ * installed has no JavaScript compiler API.
56
+ * @param {string} version
57
+ * @param {string} outcome what was not done, such as `docs were not checked`
58
+ */
59
+ export function unusableTypeScriptReason(version, outcome) {
60
+ return `TypeScript ${version} is installed in this project, but it has no JavaScript compiler API (\`createProgram\`) to read types with, so ${outcome}.`;
61
+ }