@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.
- package/README.md +15 -14
- package/api/build/help/help.mjs +1 -1
- package/api/component/_adapter.d.mts +41 -1
- package/api/component/_adapter.mjs +93 -7
- package/api/component/component.mjs +22 -5
- package/api/component/integration-export-name.test.mjs +644 -0
- package/api/docs/_adapter.d.mts +11 -0
- package/api/docs/_adapter.mjs +244 -1
- package/api/docs/integrationDocs.test.mjs +124 -0
- package/api/doctor/theme-checks.mjs +1 -1
- package/api/gap-report/gap-report.mjs +67 -4
- package/api/index.d.mts +2 -0
- package/api/index.mjs +2 -0
- package/api/init/run/run.mjs +2 -2
- package/api/integration/_adapter.d.mts +12 -0
- package/api/integration/_adapter.mjs +54 -0
- package/api/integration/add-contribution.component-names.test.mjs +66 -0
- package/api/integration/add-contribution.mjs +8 -4
- package/api/integration/add-contribution.test.mjs +4 -2
- package/api/integration/authoring-checks.mjs +11 -2
- package/api/integration/authoring-checks.test.mjs +39 -0
- package/api/integration/authoring-checks.type.d.mts +9 -0
- package/api/integration/authoring-checks.type.mjs +14 -1
- package/api/integration/check/check.d.mts +96 -0
- package/api/integration/check/check.mjs +357 -0
- package/api/integration/check/check.test.mjs +489 -0
- package/api/integration/integration-check.type.d.mts +118 -0
- package/api/integration/integration-check.type.mjs +84 -0
- package/api/integration/integrationCheck.doc.d.mts +10 -0
- package/api/integration/integrationCheck.doc.mjs +73 -0
- package/api/integration/integrationComponentConflicts.doc.mjs +36 -5
- package/api/integration/pack-check.mjs +51 -97
- package/api/integration/pack-check.test.mjs +359 -0
- package/api/integration/validate-integration.mjs +8 -0
- package/api/integration/validate-integration.test.mjs +11 -0
- package/api/integration/validate-integration.type.d.mts +8 -0
- package/api/integration/validate-integration.type.mjs +12 -1
- package/api/integration/validateIntegration.doc.mjs +17 -3
- package/api/search/search.d.mts +22 -6
- package/api/search/search.domain-priority.test.mjs +93 -18
- package/api/search/search.integration-check.test.mjs +24 -0
- package/api/search/search.mjs +133 -22
- package/api/swizzle/copy/copy.mjs +28 -14
- package/api/theme/build/build.family.test.mjs +54 -6
- package/api/theme/build/build.mjs +114 -17
- package/api/theme/themeBuild.doc.mjs +4 -4
- package/assets/docs/README.md +10 -0
- package/assets/docs/author-a-theme.doc.mjs +1 -1
- package/assets/docs/getting-started.doc.mjs +3 -3
- package/assets/docs/styling-overview.doc.mjs +1 -1
- package/assets/docs/theme.doc.dense.mjs +1 -1
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/docs/theme.doc.zh.mjs +1 -1
- package/assets/docs/tree/add-a-component.doc.mjs +11 -1
- package/assets/docs/tree/add-a-theme.doc.mjs +1 -1
- package/assets/docs/tree/add-a-topic.doc.mjs +10 -7
- package/assets/docs/tree/agent-guidance.doc.mjs +2 -2
- package/assets/docs/tree/block-template.doc.mjs +8 -20
- package/assets/docs/tree/build-the-template.doc.mjs +1 -1
- package/assets/docs/tree/check-the-props.doc.mjs +196 -0
- package/assets/docs/tree/check-your-docs.doc.mjs +51 -5
- package/assets/docs/tree/checks.doc.mjs +24 -16
- package/assets/docs/tree/codemods.doc.mjs +4 -4
- package/assets/docs/tree/component-family.doc.mjs +8 -6
- package/assets/docs/tree/component-imports.doc.mjs +10 -2
- package/assets/docs/tree/components.doc.mjs +1 -1
- package/assets/docs/tree/debug-and-gap-reports.doc.mjs +7 -18
- package/assets/docs/tree/define-the-theme.doc.mjs +6 -5
- package/assets/docs/tree/describe-the-component.doc.mjs +2 -2
- package/assets/docs/tree/docs.doc.mjs +3 -3
- package/assets/docs/tree/document-the-template.doc.mjs +1 -1
- package/assets/docs/tree/document-the-theme.doc.mjs +1 -1
- package/assets/docs/tree/export-template-assets.doc.mjs +1 -1
- package/assets/docs/tree/extend-or-replace.doc.mjs +6 -6
- package/assets/docs/tree/fonts-and-assets.doc.mjs +2 -2
- package/assets/docs/tree/generate-a-palette.doc.mjs +5 -3
- package/assets/docs/tree/grade-template-with-agent.doc.mjs +1 -1
- package/assets/docs/tree/integrations.doc.mjs +8 -0
- package/assets/docs/tree/links.doc.mjs +6 -5
- package/assets/docs/tree/package-and-test.doc.mjs +1 -1
- package/assets/docs/tree/page-template.doc.mjs +4 -8
- package/assets/docs/tree/publishing.doc.mjs +1 -1
- package/assets/docs/tree/quick-start.doc.mjs +37 -21
- package/assets/docs/tree/replace-a-core-component.doc.mjs +4 -6
- package/assets/docs/tree/replace-a-core-template.doc.mjs +6 -8
- package/assets/docs/tree/sections-and-placement.doc.mjs +4 -6
- package/assets/docs/tree/see-it-in-an-app.doc.mjs +4 -2
- package/assets/docs/tree/short-and-findable.doc.mjs +6 -5
- package/assets/docs/tree/single-component.doc.mjs +15 -6
- package/assets/docs/tree/start-a-template.doc.mjs +3 -3
- package/assets/docs/tree/subcomponent.doc.mjs +6 -6
- package/assets/docs/tree/template-assets.doc.mjs +2 -2
- package/assets/docs/tree/template-doc-overview.doc.mjs +4 -15
- package/assets/docs/tree/template-fonts.doc.mjs +1 -1
- package/assets/docs/tree/template-icons.doc.mjs +1 -1
- package/assets/docs/tree/template-images-media.doc.mjs +1 -1
- package/assets/docs/tree/template-styles.doc.mjs +2 -2
- package/assets/docs/tree/templates.doc.mjs +1 -1
- package/assets/docs/tree/test-in-an-app.doc.mjs +3 -3
- package/assets/docs/tree/test-template-in-app.doc.mjs +1 -1
- package/assets/docs/tree/tokens-and-setup.doc.mjs +19 -15
- package/assets/docs/tree/troubleshooting.doc.mjs +13 -5
- package/assets/docs/tree/type-scale.doc.mjs +3 -3
- package/assets/docs/tree/upgrading.doc.mjs +3 -3
- package/assets/docs/tree/use-a-theme-in-an-app.doc.mjs +3 -1
- package/assets/docs/tree/verify-packed-template.doc.mjs +1 -1
- package/assets/docs/tree/versioning.doc.mjs +7 -6
- package/assets/docs/tree/write-the-template-file.doc.mjs +4 -3
- package/assets/docs/use-a-theme.doc.mjs +2 -2
- package/assets/theme.template.ts +1 -1
- package/authoring/codemod/codemod.doc.mjs +7 -3
- package/authoring/config/config.doc.mjs +9 -6
- package/authoring/config/type.ts +4 -2
- package/authoring/discover/discover.doc.mjs +1 -1
- package/authoring/doctypes/base/graph-fields.doc.mjs +6 -4
- package/authoring/doctypes/command/command.doc.mjs +14 -12
- package/authoring/doctypes/component/component.doc.mjs +29 -26
- package/authoring/doctypes/component/type.ts +13 -8
- package/authoring/doctypes/enum/enum.doc.mjs +7 -4
- package/authoring/doctypes/function/function.doc.mjs +9 -8
- package/authoring/doctypes/hook/hook.doc.mjs +5 -4
- package/authoring/doctypes/namespace/namespace.doc.mjs +7 -6
- package/authoring/doctypes/reference/reference.doc.mjs +11 -10
- package/authoring/doctypes/schema/schema.doc.mjs +9 -9
- package/authoring/doctypes/template/integration-template.doc.d.mts +11 -0
- package/authoring/doctypes/template/integration-template.doc.mjs +49 -0
- package/authoring/doctypes/template/parse.d.mts +6 -2
- package/authoring/doctypes/template/parse.mjs +3 -1
- package/authoring/doctypes/template/template.doc.mjs +3 -3
- package/authoring/doctypes/template/type.ts +22 -0
- package/authoring/doctypes/theme/theme.doc.mjs +2 -2
- package/authoring/gap-report/gap-report.doc.mjs +2 -2
- package/authoring/identity/identity.doc.mjs +3 -3
- package/authoring/index.d.ts +1 -0
- package/authoring/integration/integration.doc.mjs +12 -4
- package/authoring/integration/parse.test.mjs +10 -0
- package/authoring/integration/schema.d.mts +2 -0
- package/authoring/integration/schema.mjs +1 -0
- package/authoring/integration/type.ts +4 -0
- package/clients/cli/commands/build-theme.batch-report.test.mjs +3 -1
- package/clients/cli/commands/build-theme.mjs +1 -1
- package/clients/cli/commands/build-theme.path-safety.test.mjs +155 -21
- package/clients/cli/commands/doctor-integration-components.doc.mjs +15 -4
- package/clients/cli/commands/doctor-integration-validate.doc.mjs +11 -2
- package/clients/cli/commands/doctor-integration.test.mjs +837 -0
- package/clients/cli/commands/doctor.mjs +190 -0
- package/clients/cli/commands/integration-check.doc.mjs +85 -0
- package/clients/cli/commands/integration-check.test.mjs +267 -0
- package/clients/cli/commands/integration.doc.mjs +6 -2
- package/clients/cli/commands/integration.mjs +107 -1
- package/clients/cli/commands/text-json-parity.test.mjs +5 -0
- package/clients/cli/commands/theme-build.doc.mjs +2 -2
- package/clients/cli/index.mjs +1 -0
- package/foundation/config/project.mjs +82 -0
- package/foundation/discovery/authoring-self-docs.mjs +2 -1
- package/foundation/discovery/component-discovery.d.mts +178 -10
- package/foundation/discovery/component-discovery.export-names.test.mjs +760 -0
- package/foundation/discovery/component-discovery.mjs +659 -62
- package/foundation/discovery/component-replacement.mjs +18 -0
- package/foundation/discovery/component-replacement.test.mjs +40 -0
- package/foundation/discovery/doc-types.d.mts +157 -0
- package/foundation/discovery/doc-types.mjs +1224 -0
- package/foundation/discovery/doc-types.test.mjs +652 -0
- package/foundation/discovery/docs-discovery.d.mts +81 -0
- package/foundation/discovery/docs-discovery.mjs +57 -0
- package/foundation/discovery/integration-doc-files.d.mts +22 -0
- package/foundation/discovery/integration-doc-files.mjs +231 -0
- package/foundation/discovery/integration-doc-files.test.mjs +246 -0
- package/foundation/discovery/module-exports.d.mts +27 -0
- package/foundation/discovery/module-exports.mjs +356 -31
- package/foundation/discovery/module-exports.test.mjs +172 -0
- package/foundation/discovery/package-tsconfig.d.mts +14 -0
- package/foundation/discovery/package-tsconfig.mjs +228 -0
- package/foundation/discovery/package-tsconfig.test.mjs +210 -0
- package/foundation/discovery/project-typescript.d.mts +23 -0
- package/foundation/discovery/project-typescript.mjs +61 -0
- package/foundation/discovery/prop-drift.d.mts +278 -0
- package/foundation/discovery/prop-drift.mjs +2263 -0
- package/foundation/discovery/prop-drift.test.mjs +2219 -0
- package/foundation/discovery/type-match.d.mts +53 -0
- package/foundation/discovery/type-match.mjs +660 -0
- package/foundation/discovery/type-match.test.mjs +169 -0
- package/foundation/discovery/type-text.d.mts +68 -0
- package/foundation/discovery/type-text.mjs +658 -0
- package/foundation/discovery/type-text.test.mjs +196 -0
- package/foundation/discovery/unresolved-references.d.mts +62 -0
- package/foundation/discovery/unresolved-references.mjs +790 -0
- package/foundation/discovery/unresolved-references.test.mjs +567 -0
- package/foundation/doc-compiler/doc-loads.test.mjs +6 -0
- package/foundation/doc-compiler/links.d.mts +9 -5
- package/foundation/doc-compiler/links.mjs +4 -2
- package/foundation/integrations/contribution-inventory.mjs +6 -1
- package/foundation/response/response-types.doc.mjs +7 -2
- package/package.json +9 -9
|
@@ -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
|
+
}
|