@react-pug/check-types 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/package.json +23 -0
- package/src/cli.js +5 -0
- package/src/index.js +407 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @react-pug/check-types
|
|
2
|
+
|
|
3
|
+
Type-check React-Pug projects through the TypeScript language-service plugin, from the CLI or as a library.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -D @react-pug/check-types
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## CLI
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @react-pug/check-types
|
|
15
|
+
npx @react-pug/check-types .
|
|
16
|
+
npx @react-pug/check-types src/App.tsx src/Button.tsx
|
|
17
|
+
npx @react-pug/check-types --project tsconfig.json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Supported options:
|
|
21
|
+
|
|
22
|
+
- `-p, --project <path>`: explicit `tsconfig.json` file or directory
|
|
23
|
+
- positional file paths: check only selected files while still using the full project context
|
|
24
|
+
- `--tagFunction <name>`: tag function name, default `pug`
|
|
25
|
+
- `--injectCssxjsTypes <never|auto|force>`: cssxjs/startupjs React prop injection mode
|
|
26
|
+
|
|
27
|
+
Default behavior mirrors `tsc` closely:
|
|
28
|
+
|
|
29
|
+
- if `--project` is omitted, the checker searches upward from the target directory for the nearest `tsconfig.json`
|
|
30
|
+
- diagnostics are printed against original source locations, including Pug regions
|
|
31
|
+
- process exits with code `1` when errors are found
|
|
32
|
+
|
|
33
|
+
## Library
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
import { checkTypes } from '@react-pug/check-types'
|
|
37
|
+
|
|
38
|
+
const result = await checkTypes({ cwd: process.cwd() })
|
|
39
|
+
if (!result.ok) {
|
|
40
|
+
for (const line of result.formattedErrors) console.error(line)
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Useful exports:
|
|
45
|
+
|
|
46
|
+
- `checkTypes(options)`
|
|
47
|
+
- `runCli(argv, io?)`
|
|
48
|
+
- `parseArgs(argv)`
|
|
49
|
+
|
|
50
|
+
Published binary:
|
|
51
|
+
|
|
52
|
+
- `check-pug-types`
|
|
53
|
+
|
|
54
|
+
## Notes
|
|
55
|
+
|
|
56
|
+
The checker tries to use the target project's local `typescript` first and falls back to the package dependency if none is available.
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@react-pug/check-types",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"check-pug-types": "./src/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./src/index.js",
|
|
11
|
+
"./cli": "./src/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"typescript": "*"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@react-pug/typescript-plugin-react-pug": "^0.1.3"
|
|
21
|
+
},
|
|
22
|
+
"gitHead": "38ab50d75b83fbab27b7261349cba94db7707840"
|
|
23
|
+
}
|
package/src/cli.js
ADDED
package/src/index.js
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url)
|
|
7
|
+
const packageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
8
|
+
|
|
9
|
+
export function printUsage (stdout = console.log) {
|
|
10
|
+
stdout('Usage: npx @react-pug/check-types [files...] [--project <tsconfig-path>]')
|
|
11
|
+
stdout(' [--tagFunction <name>] [--injectCssxjsTypes <never|auto|force>] [--pretty [true|false]]')
|
|
12
|
+
stdout('')
|
|
13
|
+
stdout('Examples:')
|
|
14
|
+
stdout(' npx @react-pug/check-types')
|
|
15
|
+
stdout(' npx @react-pug/check-types src/App.tsx src/Button.tsx')
|
|
16
|
+
stdout(' npx @react-pug/check-types --project example example/src/App.tsx')
|
|
17
|
+
stdout(' npx @react-pug/check-types example')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function parseArgs (argv) {
|
|
21
|
+
const positionals = []
|
|
22
|
+
let projectPath
|
|
23
|
+
let tagFunction = 'pug'
|
|
24
|
+
let injectCssxjsTypes = 'auto'
|
|
25
|
+
let pretty = 'auto'
|
|
26
|
+
|
|
27
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
28
|
+
const arg = argv[i]
|
|
29
|
+
if (arg === '--help' || arg === '-h') return { help: true }
|
|
30
|
+
if (arg === '--project' || arg === '-p') {
|
|
31
|
+
projectPath = argv[i + 1]
|
|
32
|
+
i += 1
|
|
33
|
+
continue
|
|
34
|
+
}
|
|
35
|
+
if (arg === '--tagFunction') {
|
|
36
|
+
tagFunction = argv[i + 1] ?? tagFunction
|
|
37
|
+
i += 1
|
|
38
|
+
continue
|
|
39
|
+
}
|
|
40
|
+
if (arg === '--injectCssxjsTypes') {
|
|
41
|
+
injectCssxjsTypes = argv[i + 1] ?? injectCssxjsTypes
|
|
42
|
+
i += 1
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
if (arg === '--pretty') {
|
|
46
|
+
const next = argv[i + 1]
|
|
47
|
+
if (next === 'true' || next === 'false') {
|
|
48
|
+
pretty = next === 'true'
|
|
49
|
+
i += 1
|
|
50
|
+
} else {
|
|
51
|
+
pretty = true
|
|
52
|
+
}
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
if (!arg.startsWith('-')) {
|
|
56
|
+
positionals.push(arg)
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
throw new Error(`Unknown argument: ${arg}`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!['never', 'auto', 'force'].includes(injectCssxjsTypes)) {
|
|
63
|
+
throw new Error(`Invalid --injectCssxjsTypes value: ${injectCssxjsTypes}`)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { help: false, positionals, projectPath, tagFunction, injectCssxjsTypes, pretty }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function formatWithPretty (text, code) {
|
|
70
|
+
return `\u001b[${code}m${text}\u001b[0m`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function resolvePrettyOption (pretty, isTTY = false) {
|
|
74
|
+
if (pretty === 'auto') return Boolean(isTTY)
|
|
75
|
+
return Boolean(pretty)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function formatSummary (errorCount, fileCount) {
|
|
79
|
+
if (fileCount > 0) {
|
|
80
|
+
return `Found ${errorCount} TypeScript error${errorCount === 1 ? '' : 's'} in ${fileCount} file${fileCount === 1 ? '' : 's'}.`
|
|
81
|
+
}
|
|
82
|
+
return `Found ${errorCount} TypeScript error${errorCount === 1 ? '' : 's'}.`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function resolveCliTargets (cwd, positionals, explicitProjectPath) {
|
|
86
|
+
if (explicitProjectPath) {
|
|
87
|
+
return { projectDir: '.', filePaths: positionals }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (positionals.length === 1) {
|
|
91
|
+
const candidate = path.resolve(cwd, positionals[0])
|
|
92
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
|
|
93
|
+
return { projectDir: positionals[0], filePaths: [] }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { projectDir: '.', filePaths: positionals }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function loadModuleFromLocations (specifier, locations) {
|
|
101
|
+
let lastError
|
|
102
|
+
for (const location of locations) {
|
|
103
|
+
try {
|
|
104
|
+
const resolved = require.resolve(specifier, { paths: [location] })
|
|
105
|
+
const mod = require(resolved)
|
|
106
|
+
return mod.default ?? mod
|
|
107
|
+
} catch (err) {
|
|
108
|
+
lastError = err
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
throw new Error(`Cannot load ${specifier}.\n${String(lastError)}`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function loadTypeScript (projectDir, cwd = process.cwd()) {
|
|
115
|
+
return loadModuleFromLocations('typescript', [projectDir, cwd, packageDir])
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function loadPlugin (projectDir, cwd = process.cwd()) {
|
|
119
|
+
return loadModuleFromLocations('@react-pug/typescript-plugin-react-pug', [projectDir, cwd, packageDir])
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function resolveTsconfigPath (ts, cwd, projectDirArg = '.', explicitProjectPath) {
|
|
123
|
+
if (explicitProjectPath) {
|
|
124
|
+
const resolved = path.resolve(cwd, explicitProjectPath)
|
|
125
|
+
if (ts.sys.directoryExists?.(resolved)) {
|
|
126
|
+
const found = ts.findConfigFile(resolved, ts.sys.fileExists, 'tsconfig.json')
|
|
127
|
+
if (!found) throw new Error(`Cannot find tsconfig.json inside ${resolved}`)
|
|
128
|
+
return found
|
|
129
|
+
}
|
|
130
|
+
return resolved
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const searchDir = path.resolve(cwd, projectDirArg)
|
|
134
|
+
const found = ts.findConfigFile(searchDir, ts.sys.fileExists, 'tsconfig.json')
|
|
135
|
+
if (!found) {
|
|
136
|
+
throw new Error(`Cannot find tsconfig.json from ${searchDir}`)
|
|
137
|
+
}
|
|
138
|
+
return found
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function createLanguageServiceFromTsconfig ({ ts, tsconfigPath, pluginInit, pluginConfig }) {
|
|
142
|
+
const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile)
|
|
143
|
+
if (configFile.error) {
|
|
144
|
+
return { configErrors: [configFile.error], parsedConfig: undefined, ls: undefined }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const configDir = path.dirname(tsconfigPath)
|
|
148
|
+
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, configDir)
|
|
149
|
+
|
|
150
|
+
const host = {
|
|
151
|
+
getScriptFileNames: () => parsedConfig.fileNames,
|
|
152
|
+
getScriptVersion: fileName => String(ts.sys.getModifiedTime?.(fileName)?.valueOf() ?? 0),
|
|
153
|
+
getScriptSnapshot: fileName => {
|
|
154
|
+
const text = ts.sys.readFile(fileName)
|
|
155
|
+
return text === undefined ? undefined : ts.ScriptSnapshot.fromString(text)
|
|
156
|
+
},
|
|
157
|
+
getCurrentDirectory: () => configDir,
|
|
158
|
+
getCompilationSettings: () => parsedConfig.options,
|
|
159
|
+
getDefaultLibFileName: ts.getDefaultLibFilePath,
|
|
160
|
+
fileExists: ts.sys.fileExists,
|
|
161
|
+
readFile: ts.sys.readFile,
|
|
162
|
+
readDirectory: ts.sys.readDirectory,
|
|
163
|
+
directoryExists: ts.sys.directoryExists,
|
|
164
|
+
getDirectories: ts.sys.getDirectories,
|
|
165
|
+
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
|
|
166
|
+
getNewLine: () => ts.sys.newLine
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const baseLs = ts.createLanguageService(host, ts.createDocumentRegistry())
|
|
170
|
+
const pluginModule = pluginInit({ typescript: ts })
|
|
171
|
+
const proxiedLs = pluginModule.create({
|
|
172
|
+
languageServiceHost: host,
|
|
173
|
+
languageService: baseLs,
|
|
174
|
+
project: {
|
|
175
|
+
getCurrentDirectory: () => configDir,
|
|
176
|
+
projectService: {
|
|
177
|
+
logger: {
|
|
178
|
+
info: () => {}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
serverHost: ts.sys,
|
|
183
|
+
config: pluginConfig
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
return { configErrors: parsedConfig.errors, parsedConfig, ls: proxiedLs }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function collectDiagnostics (ts, ls, parsedConfig, selectedFiles) {
|
|
190
|
+
const diagnostics = []
|
|
191
|
+
const program = ls.getProgram?.()
|
|
192
|
+
if (program) {
|
|
193
|
+
diagnostics.push(...program.getOptionsDiagnostics())
|
|
194
|
+
diagnostics.push(...program.getGlobalDiagnostics())
|
|
195
|
+
}
|
|
196
|
+
const filesToCheck = selectedFiles?.length ? selectedFiles : parsedConfig.fileNames
|
|
197
|
+
for (const fileName of filesToCheck) {
|
|
198
|
+
diagnostics.push(...ls.getSyntacticDiagnostics(fileName))
|
|
199
|
+
diagnostics.push(...ls.getSemanticDiagnostics(fileName))
|
|
200
|
+
}
|
|
201
|
+
return diagnostics
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function uniqDiagnostics (ts, diagnostics) {
|
|
205
|
+
const out = []
|
|
206
|
+
const seen = new Set()
|
|
207
|
+
for (const diag of diagnostics) {
|
|
208
|
+
const key = [
|
|
209
|
+
diag.file?.fileName ?? '',
|
|
210
|
+
diag.start ?? '',
|
|
211
|
+
diag.length ?? '',
|
|
212
|
+
diag.code ?? '',
|
|
213
|
+
ts.flattenDiagnosticMessageText(diag.messageText, '\n')
|
|
214
|
+
].join('|')
|
|
215
|
+
if (seen.has(key)) continue
|
|
216
|
+
seen.add(key)
|
|
217
|
+
out.push(diag)
|
|
218
|
+
}
|
|
219
|
+
return out
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function computeLineAndColumn (text, offset) {
|
|
223
|
+
const safeOffset = Math.max(0, Math.min(offset, text.length))
|
|
224
|
+
let line = 0
|
|
225
|
+
let lineStart = 0
|
|
226
|
+
for (let i = 0; i < safeOffset; i += 1) {
|
|
227
|
+
if (text.charCodeAt(i) === 10) {
|
|
228
|
+
line += 1
|
|
229
|
+
lineStart = i + 1
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { line: line + 1, column: safeOffset - lineStart + 1 }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function formatDiagnostic (ts, diag, cwd, fileTextCache) {
|
|
236
|
+
const category = ts.DiagnosticCategory[diag.category]?.toLowerCase() ?? 'unknown'
|
|
237
|
+
const code = `TS${diag.code}`
|
|
238
|
+
const message = ts.flattenDiagnosticMessageText(diag.messageText, '\n')
|
|
239
|
+
|
|
240
|
+
if (!diag.file || diag.start === undefined) {
|
|
241
|
+
return `${category} ${code}: ${message}`
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const filePath = diag.file.fileName
|
|
245
|
+
const file = path.relative(cwd, filePath) || filePath
|
|
246
|
+
|
|
247
|
+
if (!fileTextCache.has(filePath)) {
|
|
248
|
+
fileTextCache.set(filePath, ts.sys.readFile(filePath) ?? null)
|
|
249
|
+
}
|
|
250
|
+
const text = fileTextCache.get(filePath)
|
|
251
|
+
if (typeof text === 'string') {
|
|
252
|
+
const pos = computeLineAndColumn(text, diag.start)
|
|
253
|
+
return `${file}:${pos.line}:${pos.column} - ${category} ${code}: ${message}`
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const pos = diag.file.getLineAndCharacterOfPosition(diag.start)
|
|
257
|
+
return `${file}:${pos.line + 1}:${pos.character + 1} - ${category} ${code}: ${message}`
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function formatDiagnosticOutput (line, pretty) {
|
|
261
|
+
if (!pretty) return line
|
|
262
|
+
return line
|
|
263
|
+
.replace(/(^|\s)(error)(\s+TS\d+:)/, (_, prefix, word, suffix) => `${prefix}${formatWithPretty(word, '31;1')}${formatWithPretty(suffix.trimStart(), '36;1')}`)
|
|
264
|
+
.replace(/^(.*?:\d+:\d+)/, match => formatWithPretty(match, '90'))
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function normalizeFiles (cwd, configDir, filePaths) {
|
|
268
|
+
return filePaths.map(filePath => {
|
|
269
|
+
const resolved = path.resolve(cwd, filePath)
|
|
270
|
+
const normalized = path.normalize(resolved)
|
|
271
|
+
return path.isAbsolute(normalized) ? normalized : path.resolve(configDir, normalized)
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export async function checkTypes ({
|
|
276
|
+
cwd = process.cwd(),
|
|
277
|
+
projectDir = '.',
|
|
278
|
+
projectPath,
|
|
279
|
+
filePaths = [],
|
|
280
|
+
tagFunction = 'pug',
|
|
281
|
+
injectCssxjsTypes = 'auto',
|
|
282
|
+
loadPluginModule,
|
|
283
|
+
loadTypeScriptModule
|
|
284
|
+
} = {}) {
|
|
285
|
+
const resolvedProjectDir = path.resolve(cwd, projectDir)
|
|
286
|
+
const ts = loadTypeScriptModule ? await loadTypeScriptModule(resolvedProjectDir, cwd) : loadTypeScript(resolvedProjectDir, cwd)
|
|
287
|
+
const pluginInit = loadPluginModule ? await loadPluginModule(resolvedProjectDir, cwd) : loadPlugin(resolvedProjectDir, cwd)
|
|
288
|
+
const tsconfigPath = resolveTsconfigPath(ts, cwd, projectDir, projectPath)
|
|
289
|
+
|
|
290
|
+
const { configErrors, parsedConfig, ls } = createLanguageServiceFromTsconfig({
|
|
291
|
+
ts,
|
|
292
|
+
tsconfigPath,
|
|
293
|
+
pluginInit,
|
|
294
|
+
pluginConfig: {
|
|
295
|
+
enabled: true,
|
|
296
|
+
diagnostics: { enabled: true },
|
|
297
|
+
tagFunction,
|
|
298
|
+
injectCssxjsTypes
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
if (!parsedConfig || !ls) {
|
|
303
|
+
const diagnostics = uniqDiagnostics(ts, configErrors)
|
|
304
|
+
const fileTextCache = new Map()
|
|
305
|
+
const formattedErrors = diagnostics.map(diag => formatDiagnostic(ts, diag, cwd, fileTextCache))
|
|
306
|
+
return {
|
|
307
|
+
ok: false,
|
|
308
|
+
exitCode: 1,
|
|
309
|
+
ts,
|
|
310
|
+
parsedConfig: null,
|
|
311
|
+
diagnostics,
|
|
312
|
+
errors: diagnostics,
|
|
313
|
+
formattedErrors,
|
|
314
|
+
fileCount: 0,
|
|
315
|
+
selectedFiles: [],
|
|
316
|
+
errorFileCount: 0,
|
|
317
|
+
tsconfigPath
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const configDir = path.dirname(tsconfigPath)
|
|
322
|
+
const selectedFiles = filePaths.length ? normalizeFiles(cwd, configDir, filePaths) : []
|
|
323
|
+
const projectFiles = new Set(parsedConfig.fileNames.map(fileName => path.normalize(fileName)))
|
|
324
|
+
const missingFiles = selectedFiles.filter(fileName => !projectFiles.has(path.normalize(fileName)))
|
|
325
|
+
|
|
326
|
+
const diagnostics = uniqDiagnostics(ts, [
|
|
327
|
+
...configErrors,
|
|
328
|
+
...collectDiagnostics(ts, ls, parsedConfig, selectedFiles)
|
|
329
|
+
]).sort((a, b) => {
|
|
330
|
+
const af = a.file?.fileName ?? ''
|
|
331
|
+
const bf = b.file?.fileName ?? ''
|
|
332
|
+
if (af !== bf) return af.localeCompare(bf)
|
|
333
|
+
const as = a.start ?? -1
|
|
334
|
+
const bs = b.start ?? -1
|
|
335
|
+
if (as !== bs) return as - bs
|
|
336
|
+
return a.code - b.code
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
const errors = diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error)
|
|
340
|
+
const fileTextCache = new Map()
|
|
341
|
+
const formattedErrors = [
|
|
342
|
+
...errors.map(diag => formatDiagnostic(ts, diag, cwd, fileTextCache)),
|
|
343
|
+
...missingFiles.map(fileName => `error CHECK0001: File is not part of the resolved TypeScript project: ${path.relative(cwd, fileName) || fileName}`)
|
|
344
|
+
]
|
|
345
|
+
const errorFileCount = new Set(errors.map(diag => diag.file?.fileName).filter(Boolean)).size
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
ok: errors.length === 0 && missingFiles.length === 0,
|
|
349
|
+
exitCode: errors.length === 0 && missingFiles.length === 0 ? 0 : 1,
|
|
350
|
+
ts,
|
|
351
|
+
parsedConfig,
|
|
352
|
+
diagnostics,
|
|
353
|
+
errors,
|
|
354
|
+
formattedErrors,
|
|
355
|
+
fileCount: selectedFiles.length || parsedConfig.fileNames.length,
|
|
356
|
+
selectedFiles,
|
|
357
|
+
missingFiles,
|
|
358
|
+
errorFileCount,
|
|
359
|
+
tsconfigPath
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export async function runCli (argv = process.argv.slice(2), io = {}) {
|
|
364
|
+
const stdout = io.stdout ?? console.log
|
|
365
|
+
const stderr = io.stderr ?? console.error
|
|
366
|
+
const cwd = io.cwd ?? process.cwd()
|
|
367
|
+
const prettyIsTTY = io.stderrIsTTY ?? process.stderr.isTTY
|
|
368
|
+
|
|
369
|
+
let parsed
|
|
370
|
+
try {
|
|
371
|
+
parsed = parseArgs(argv)
|
|
372
|
+
} catch (err) {
|
|
373
|
+
stderr(String(err))
|
|
374
|
+
printUsage(stdout)
|
|
375
|
+
return 1
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (parsed.help) {
|
|
379
|
+
printUsage(stdout)
|
|
380
|
+
return 0
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
try {
|
|
384
|
+
const { projectDir, filePaths } = resolveCliTargets(cwd, parsed.positionals, parsed.projectPath)
|
|
385
|
+
const result = await checkTypes({
|
|
386
|
+
cwd,
|
|
387
|
+
projectDir,
|
|
388
|
+
filePaths,
|
|
389
|
+
projectPath: parsed.projectPath,
|
|
390
|
+
tagFunction: parsed.tagFunction,
|
|
391
|
+
injectCssxjsTypes: parsed.injectCssxjsTypes
|
|
392
|
+
})
|
|
393
|
+
const pretty = resolvePrettyOption(parsed.pretty, prettyIsTTY)
|
|
394
|
+
if (result.ok) {
|
|
395
|
+
const scope = result.selectedFiles.length ? 'selected ' : ''
|
|
396
|
+
stdout(`No TypeScript errors (with pug plugin) in ${result.fileCount} ${scope}file${result.fileCount === 1 ? '' : 's'}.`)
|
|
397
|
+
return 0
|
|
398
|
+
}
|
|
399
|
+
for (const line of result.formattedErrors) stderr(formatDiagnosticOutput(line, pretty))
|
|
400
|
+
const totalErrorCount = result.errors.length + result.missingFiles.length
|
|
401
|
+
stderr(`\n${formatSummary(totalErrorCount, result.errorFileCount)}`)
|
|
402
|
+
return 1
|
|
403
|
+
} catch (err) {
|
|
404
|
+
stderr(err instanceof Error ? err.message : String(err))
|
|
405
|
+
return 1
|
|
406
|
+
}
|
|
407
|
+
}
|