@plugjs/tsgo 0.0.0
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/AGENTS.md +9 -0
- package/LICENSE.md +211 -0
- package/NOTICE.md +13 -0
- package/README.md +100 -0
- package/dist/diagnostics.d.ts +6 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +99 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/projects.d.ts +72 -0
- package/dist/projects.d.ts.map +1 -0
- package/dist/projects.js +209 -0
- package/dist/projects.js.map +1 -0
- package/dist/typescript.d.ts +27 -0
- package/dist/typescript.d.ts.map +1 -0
- package/dist/typescript.js +176 -0
- package/dist/typescript.js.map +1 -0
- package/package.json +66 -0
- package/src/diagnostics.ts +123 -0
- package/src/index.ts +54 -0
- package/src/projects.ts +271 -0
- package/src/tsconfig.json +15 -0
- package/src/typescript.ts +202 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { install } from '@plugjs/plug/pipe'
|
|
2
|
+
|
|
3
|
+
import { TypeScript } from './typescript.ts'
|
|
4
|
+
|
|
5
|
+
/** Options for the {@link Pipe.typescript TypeScript} plug. */
|
|
6
|
+
export interface TypeScriptOptions {
|
|
7
|
+
/** Whether referenced projects are built too. Defaults to `false`. */
|
|
8
|
+
projectReferences?: boolean
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Controls how deprecation diagnostics are reported. Use `'notice'` or
|
|
12
|
+
* `true` for notices, `'warn'` for warnings, and `'off'` or `false` to
|
|
13
|
+
* silence them. Defaults to `'notice'`.
|
|
14
|
+
*/
|
|
15
|
+
reportDeprecations?: 'warn' | 'notice' | 'off' | boolean
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Controls which files TypeScript emits. Use `'all'` or `true` for
|
|
19
|
+
* JavaScript and declaration files, `'js'` for JavaScript files, `'dts'`
|
|
20
|
+
* for declaration files, and `'none'` or `false` to type-check without
|
|
21
|
+
* emitting files. Defaults to `'all'`.
|
|
22
|
+
*/
|
|
23
|
+
emitOnly?: 'js' | 'dts' | 'all' | 'none' | boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Options for the {@link tsc} and {@link tsbuild} helpers. */
|
|
27
|
+
export interface TscOptions extends Omit<TypeScriptOptions, 'projectReferences'> {
|
|
28
|
+
/** Base directory used to resolve the configuration path. */
|
|
29
|
+
directory?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare module '@plugjs/plug' {
|
|
33
|
+
export interface Pipe {
|
|
34
|
+
/**
|
|
35
|
+
* Run the {@link https://www.typescriptlang.org/ TypeScript Compiler}
|
|
36
|
+
* version 7 (native) over the `tsconfig.json` configuration files
|
|
37
|
+
* given as input to this {@link Pipe} instance.
|
|
38
|
+
*/
|
|
39
|
+
typescript(): Pipe
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Run the {@link https://www.typescriptlang.org/ TypeScript Compiler}
|
|
43
|
+
* version 7 (native) over the `tsconfig.json` configuration files
|
|
44
|
+
* given as input to this {@link Pipe} instance.
|
|
45
|
+
*
|
|
46
|
+
* @param options {@link TypeScriptOptions} to use for the build.
|
|
47
|
+
*/
|
|
48
|
+
typescript(options: TypeScriptOptions): Pipe
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
install('typescript', TypeScript)
|
|
53
|
+
|
|
54
|
+
export { tsbuild, tsc } from './typescript.ts'
|
package/src/projects.ts
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { assert } from '@plugjs/plug/asserts'
|
|
2
|
+
import { $gry, $p, $ylw, ERROR } from '@plugjs/plug/logging'
|
|
3
|
+
import { assertAbsolutePath, getAbsoluteParent, resolveFile } from '@plugjs/plug/paths'
|
|
4
|
+
|
|
5
|
+
import { convertProjectDiagnostics } from './diagnostics.ts'
|
|
6
|
+
|
|
7
|
+
import type { ReportRecord } from '@plugjs/plug/logging'
|
|
8
|
+
import type { AbsolutePath } from '@plugjs/plug/paths'
|
|
9
|
+
import type { API, ParsedCommandLine } from 'typescript/unstable/async'
|
|
10
|
+
|
|
11
|
+
/** Interface describing the actual (resolved) config file and its references */
|
|
12
|
+
interface ProjectReferences {
|
|
13
|
+
/** The actual path of the configuration file containing the references */
|
|
14
|
+
path: AbsolutePath
|
|
15
|
+
/** The actual configuration parsed from the configuration file */
|
|
16
|
+
config: ParsedCommandLine
|
|
17
|
+
/** The actual paths of all configuration files referenced by this project */
|
|
18
|
+
references: AbsolutePath[]
|
|
19
|
+
/** Any errors encountered while reading the configuration file */
|
|
20
|
+
errors: ReportRecord[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Interface describing a group of cross-referencing projects */
|
|
24
|
+
interface Projects {
|
|
25
|
+
/** All projects to build, each associated with its referencing projects */
|
|
26
|
+
projects: Map<AbsolutePath, Set<AbsolutePath>>
|
|
27
|
+
/** Any errors encountered while reading the configuration files */
|
|
28
|
+
errors: ReportRecord[]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Interface describing the order of projects, including any cycles and unresolved projects */
|
|
32
|
+
interface ProjectOrder {
|
|
33
|
+
/** The order in which projects should be built */
|
|
34
|
+
order: AbsolutePath[]
|
|
35
|
+
/** Array of project reference cycles */
|
|
36
|
+
cycles: AbsolutePath[][]
|
|
37
|
+
/** Array containing all projects that could not be ordered because of cyclical dependencies */
|
|
38
|
+
unresolved: AbsolutePath[]
|
|
39
|
+
/** Any errors encountered while resolving the project order */
|
|
40
|
+
errors: ReportRecord[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* ========================================================================== */
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Find the TypeScript project configuration file for a given path.
|
|
47
|
+
*
|
|
48
|
+
* The path specified can be a `tsconfig.json` file name or a directory
|
|
49
|
+
* containing a `tsconfig.json` file.
|
|
50
|
+
*
|
|
51
|
+
* The returned path is the absolute path of the actual `tsconfig.json` file.
|
|
52
|
+
*/
|
|
53
|
+
export function findProjectConfig(path: AbsolutePath): AbsolutePath {
|
|
54
|
+
// The specified path can be either a file or a directory containing a
|
|
55
|
+
// "tsconfig.json" file: resolve as a file, then assume it's a directory
|
|
56
|
+
let file = resolveFile(path)
|
|
57
|
+
if (!file) file = resolveFile(path, 'tsconfig.json')
|
|
58
|
+
assert(file, `TypeScript configuration file not found in "${path}"`)
|
|
59
|
+
return file
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Read the TypeScript project configuration file for a given path.
|
|
64
|
+
*
|
|
65
|
+
* The path specified can be a `tsconfig.json` file name or a directory
|
|
66
|
+
* containing a `tsconfig.json` file.
|
|
67
|
+
*
|
|
68
|
+
* The returned object contains the resolved configuration file name (in `path`)
|
|
69
|
+
* and the array of resolved project references (the actual resolved files on
|
|
70
|
+
* disk in `references`).
|
|
71
|
+
*
|
|
72
|
+
* Any errors encountered while reading the configuration file or resolving its
|
|
73
|
+
* references are returned in the `errors` array.
|
|
74
|
+
*/
|
|
75
|
+
export async function readProjectConfig(api: API, path: AbsolutePath): Promise<ProjectReferences> {
|
|
76
|
+
const file = findProjectConfig(path)
|
|
77
|
+
|
|
78
|
+
// Use the TypeScript API to load up this project... We already update the
|
|
79
|
+
// snapshot here, so we don't need to do it again once we end up compiling
|
|
80
|
+
// the whole project...
|
|
81
|
+
const snapshot = await api.updateSnapshot({ openProjects: [file] })
|
|
82
|
+
const project = snapshot.getProject(file)
|
|
83
|
+
assert(project, `Failed to open TypeScript project from "${file}"`)
|
|
84
|
+
assertAbsolutePath(project.configFileName)
|
|
85
|
+
path = project.configFileName // rewrite the path to the actual
|
|
86
|
+
|
|
87
|
+
// Get any diagnostics encountered while parsing the configuration file
|
|
88
|
+
// and any "global" diagnostics (not associated with a specific file)...
|
|
89
|
+
const diagnostics = await Promise.all([
|
|
90
|
+
project.program.getGlobalDiagnostics(),
|
|
91
|
+
project.program.getProgramDiagnostics(),
|
|
92
|
+
project.program.getConfigFileParsingDiagnostics(),
|
|
93
|
+
]).then((results) => results.flat())
|
|
94
|
+
|
|
95
|
+
// Convert the diagnostics to our simplified format
|
|
96
|
+
const errors = await convertProjectDiagnostics(diagnostics, project.program, path)
|
|
97
|
+
|
|
98
|
+
// Prepare our simplified result object
|
|
99
|
+
const config = project.parsedCommandLine
|
|
100
|
+
const result: ProjectReferences = { path, errors, config, references: [] }
|
|
101
|
+
|
|
102
|
+
// Resolve the references to absolute paths
|
|
103
|
+
const directory = getAbsoluteParent(path)
|
|
104
|
+
for (const reference of config.projectReferences ?? []) {
|
|
105
|
+
let resolved = resolveFile(directory, reference.path)
|
|
106
|
+
if (!resolved) resolved = resolveFile(directory, reference.path, 'tsconfig.json')
|
|
107
|
+
if (resolved) result.references.push(resolved)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// All done
|
|
111
|
+
return result
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Find _all_ project references given a set of initial project paths.
|
|
116
|
+
*
|
|
117
|
+
* The returned map contains all the projects (resolved configuration file
|
|
118
|
+
* names) and their referring projects as values.
|
|
119
|
+
*/
|
|
120
|
+
export async function findProjectReferences(api: API, ...paths: AbsolutePath[]): Promise<Projects> {
|
|
121
|
+
// The set of *all* projects, whether we have resolved them yet or not.
|
|
122
|
+
const remainingProjects = new Set<AbsolutePath>(paths)
|
|
123
|
+
// The set of all *resolved* projects (that is, opened and parsed)
|
|
124
|
+
const resolvedProjects = new Set<AbsolutePath>()
|
|
125
|
+
|
|
126
|
+
// The map of all our projects, keyed by their resolved configuration file
|
|
127
|
+
// name and having the set of projects that refer to it as a value
|
|
128
|
+
const projects = new Map<AbsolutePath, Set<AbsolutePath>>()
|
|
129
|
+
|
|
130
|
+
// An array of all errors encountered while reading the configuration files
|
|
131
|
+
const errors: ReportRecord[] = []
|
|
132
|
+
|
|
133
|
+
// Add a referring project to the set of referring projects for a referred project
|
|
134
|
+
function addProject(referredProject: AbsolutePath, referringProject?: AbsolutePath): void {
|
|
135
|
+
let referringProjects = projects.get(referredProject)
|
|
136
|
+
if (!referringProjects) {
|
|
137
|
+
referringProjects = new Set()
|
|
138
|
+
projects.set(referredProject, referringProjects)
|
|
139
|
+
}
|
|
140
|
+
if (referringProject) referringProjects.add(referringProject)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
while (remainingProjects.size > 0) {
|
|
144
|
+
// Take and remove the next project from the remaining set
|
|
145
|
+
const project = remainingProjects.values().next().value!
|
|
146
|
+
remainingProjects.delete(project)
|
|
147
|
+
|
|
148
|
+
// Read the project configuration file and its references
|
|
149
|
+
const { path, references, errors: e } = await readProjectConfig(api, project)
|
|
150
|
+
// If we already resolved this project, skip it
|
|
151
|
+
if (resolvedProjects.has(path)) continue
|
|
152
|
+
// Push any errors encountered while reading the configuration file
|
|
153
|
+
errors.push(...e)
|
|
154
|
+
// Add the project (resolved filename) to the resolved set
|
|
155
|
+
resolvedProjects.add(path)
|
|
156
|
+
// Record the project and its references in our map of projects
|
|
157
|
+
addProject(path)
|
|
158
|
+
|
|
159
|
+
// Look at all the project references
|
|
160
|
+
for (const reference of references) {
|
|
161
|
+
// If we haven't resolved this reference yet, add it to the remaining
|
|
162
|
+
if (!resolvedProjects.has(reference)) remainingProjects.add(reference)
|
|
163
|
+
// Then make sure that we record the referring project for this reference
|
|
164
|
+
addProject(reference, path)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Return our projects map and all errors
|
|
169
|
+
return { projects, errors }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Use Kahn's topological-sort algorithm on a map of projects and their
|
|
174
|
+
* references to determine the order in which they should be built.
|
|
175
|
+
*
|
|
176
|
+
* @param projects A map of projects and their referring projects
|
|
177
|
+
* @returns The order of projects, any unresolved projects, and cycles found
|
|
178
|
+
*/
|
|
179
|
+
export function resolveProjectOrder(projects: ReadonlyMap<AbsolutePath, Set<AbsolutePath>>): ProjectOrder {
|
|
180
|
+
// The number of unresolved dependencies for each project
|
|
181
|
+
const counts: Record<AbsolutePath, number> = Object.create(null)
|
|
182
|
+
|
|
183
|
+
// Iterate through all the projects (and their referring projects), counting
|
|
184
|
+
// the number of unresolved dependencies for each project.
|
|
185
|
+
for (const [referred, referring] of projects.entries()) {
|
|
186
|
+
// Ensure the referred project has a dependencies count (even if it is zero)
|
|
187
|
+
counts[referred] = counts[referred] ?? 0
|
|
188
|
+
// For each referring project, increment its count of unresolved dependencies
|
|
189
|
+
for (const ref of referring) counts[ref] = (counts[ref] ?? 0) + 1
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// The unique set of all known projects (referred and referring)
|
|
193
|
+
const uniques = Object.keys(counts) as AbsolutePath[]
|
|
194
|
+
|
|
195
|
+
// Projects are "ready" when all dependencies have been resolved (zero count)
|
|
196
|
+
const ready = uniques.filter((project) => counts[project] === 0)
|
|
197
|
+
|
|
198
|
+
// The final order of projects (in dependency order)
|
|
199
|
+
const order: AbsolutePath[] = []
|
|
200
|
+
|
|
201
|
+
// While there are still projects ready to be processed, take the first one
|
|
202
|
+
for (let project = ready.shift(); project; project = ready.shift()) {
|
|
203
|
+
// Add the project to the final order
|
|
204
|
+
order.push(project)
|
|
205
|
+
|
|
206
|
+
// For each project that depends on this project, decrement its unresolved
|
|
207
|
+
// dependency count. If it reaches zero, add it to the ready queue.
|
|
208
|
+
for (const dependent of projects.get(project) ?? /* coverage ignore next */ []) {
|
|
209
|
+
const remaining = (counts[dependent] = counts[dependent]! - 1)
|
|
210
|
+
if (remaining === 0) ready.push(dependent)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// When no more projects are "ready", the remaining projects are unresolved
|
|
215
|
+
const unresolved = uniques.filter((project) => counts[project])
|
|
216
|
+
|
|
217
|
+
// If we have no unresolved projects, we can return the order (no cycles)
|
|
218
|
+
if (unresolved.length === 0) return { order, unresolved, cycles: [], errors: [] }
|
|
219
|
+
|
|
220
|
+
// ===== FIND CYCLES =========================================================
|
|
221
|
+
|
|
222
|
+
// Projects we _already_ visited (and thus don't need to visit again)
|
|
223
|
+
const visited = new Set<AbsolutePath>()
|
|
224
|
+
|
|
225
|
+
// The list of cycles we have found (each cycle is a list of projects)
|
|
226
|
+
const cycles: AbsolutePath[][] = []
|
|
227
|
+
|
|
228
|
+
// Recursively visit a project's dependents, looking for cycles
|
|
229
|
+
function visit(project: AbsolutePath, stack: AbsolutePath[] = []): void {
|
|
230
|
+
// If this project is already in the current path, we found a cycle
|
|
231
|
+
const index = stack.indexOf(project)
|
|
232
|
+
if (index >= 0) {
|
|
233
|
+
cycles.push([...stack.slice(index), project])
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Don't revisit projects whose dependents have all been processed
|
|
238
|
+
if (visited.has(project)) return
|
|
239
|
+
|
|
240
|
+
// Add this project to the current path and visit its dependents
|
|
241
|
+
const next = [...stack, project]
|
|
242
|
+
for (const dependent of projects.get(project) ?? /* coverage ignore next */ []) {
|
|
243
|
+
if (unresolved.includes(dependent)) visit(dependent, next)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Mark the project as visited after all its dependents have been processed
|
|
247
|
+
visited.add(project)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Visit all the unresolved projects, looking for cycles
|
|
251
|
+
for (const project of unresolved) visit(project)
|
|
252
|
+
|
|
253
|
+
// Prep our errors
|
|
254
|
+
const errors: ReportRecord[] = []
|
|
255
|
+
errors.push({
|
|
256
|
+
level: ERROR,
|
|
257
|
+
message:
|
|
258
|
+
`Found ${$ylw(unresolved.length)} unresolved projects (${$ylw(cycles.length)} cycles)` +
|
|
259
|
+
`\n${$gry('-')} ${unresolved.map($p).join(`\n${$gry('-')} `)}`,
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
for (const cycle of cycles) {
|
|
263
|
+
errors.push({
|
|
264
|
+
level: ERROR,
|
|
265
|
+
message: `Project reference cycle:\n${$gry('-')} ${cycle.map($p).join(`\n${$gry('-')} `)}`,
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Return the final order, unresolved projects, and cycles found
|
|
270
|
+
return { order, unresolved, cycles, errors }
|
|
271
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Inherit options from the root configuration file
|
|
3
|
+
"extends": "../tsconfig.json",
|
|
4
|
+
// Make sure we actually emit the transpiled files from here
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"rootDir": ".",
|
|
7
|
+
"noEmit": false,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"declarationMap": true,
|
|
11
|
+
"outDir": "../dist"
|
|
12
|
+
},
|
|
13
|
+
// Only include what's in the sources here
|
|
14
|
+
"include": ["."]
|
|
15
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { using } from '@plugjs/plug'
|
|
2
|
+
import { assert } from '@plugjs/plug/asserts'
|
|
3
|
+
import { Files } from '@plugjs/plug/files'
|
|
4
|
+
import { ERROR } from '@plugjs/plug/logging'
|
|
5
|
+
import { commonPath, getAbsoluteParent, resolveAbsolutePath } from '@plugjs/plug/paths'
|
|
6
|
+
import { API, EmitOnly } from 'typescript/unstable/async'
|
|
7
|
+
|
|
8
|
+
import { convertDiagnostics } from './diagnostics.ts'
|
|
9
|
+
import { findProjectConfig, findProjectReferences, readProjectConfig, resolveProjectOrder } from './projects.ts'
|
|
10
|
+
|
|
11
|
+
import type { TscOptions, TypeScriptOptions } from './index.ts'
|
|
12
|
+
import type { Pipe } from '@plugjs/plug'
|
|
13
|
+
import type { AbsolutePath } from '@plugjs/plug/paths'
|
|
14
|
+
import type { Context, Plug } from '@plugjs/plug/pipe'
|
|
15
|
+
import type { Snapshot } from 'typescript/unstable/async'
|
|
16
|
+
|
|
17
|
+
/* ========================================================================== *
|
|
18
|
+
* TYPESCRIPT 7 PLUG IMPLEMENTATION *
|
|
19
|
+
* ========================================================================== */
|
|
20
|
+
|
|
21
|
+
export class TypeScript implements Plug<Files> {
|
|
22
|
+
private _emitOnly: EmitOnly | null
|
|
23
|
+
private _reportDeprecations: 'warn' | 'notice' | 'off'
|
|
24
|
+
private _projectReferences: boolean
|
|
25
|
+
|
|
26
|
+
constructor(options: TypeScriptOptions = {}) {
|
|
27
|
+
const { projectReferences, reportDeprecations, emitOnly } = options
|
|
28
|
+
this._projectReferences = !!projectReferences
|
|
29
|
+
|
|
30
|
+
switch (emitOnly) {
|
|
31
|
+
// If *none* or *false* we don't emit any files at all
|
|
32
|
+
case 'none':
|
|
33
|
+
case false:
|
|
34
|
+
this._emitOnly = null
|
|
35
|
+
break
|
|
36
|
+
// If *js* we only emit JavaScript files
|
|
37
|
+
case 'js':
|
|
38
|
+
this._emitOnly = EmitOnly.OnlyJs
|
|
39
|
+
break
|
|
40
|
+
// If *dts* we only emit declaration files
|
|
41
|
+
case 'dts':
|
|
42
|
+
this._emitOnly = EmitOnly.OnlyDts
|
|
43
|
+
break
|
|
44
|
+
// If *all* or *true* (default) we emit all files
|
|
45
|
+
case 'all':
|
|
46
|
+
case true:
|
|
47
|
+
default:
|
|
48
|
+
this._emitOnly = EmitOnly.All
|
|
49
|
+
break
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
switch (reportDeprecations) {
|
|
53
|
+
// If *warn* we report deprecations as warnings
|
|
54
|
+
case 'warn':
|
|
55
|
+
this._reportDeprecations = 'warn'
|
|
56
|
+
break
|
|
57
|
+
// If *off* or *false* we don't report deprecations at all
|
|
58
|
+
case 'off':
|
|
59
|
+
case false:
|
|
60
|
+
this._reportDeprecations = 'off'
|
|
61
|
+
break
|
|
62
|
+
// If *notice* or *true* (default) we report deprecations as notices
|
|
63
|
+
case 'notice':
|
|
64
|
+
case true:
|
|
65
|
+
default:
|
|
66
|
+
this._reportDeprecations = 'notice'
|
|
67
|
+
break
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async pipe(files: Files, context: Context): Promise<Files> {
|
|
72
|
+
// Create our TypeScript API and report for this plug
|
|
73
|
+
const api = new API({ cwd: files.directory })
|
|
74
|
+
const report = context.log.report('TypeScript Report')
|
|
75
|
+
|
|
76
|
+
// This is the list of emitted files to return to the pipe...
|
|
77
|
+
const emittedFiles: AbsolutePath[] = []
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
// The list of projects to build (in order) and the snapshot used
|
|
81
|
+
// to build them.
|
|
82
|
+
let projects: AbsolutePath[]
|
|
83
|
+
let snapshot: Snapshot
|
|
84
|
+
|
|
85
|
+
// First of all figure out whether we have to build project references
|
|
86
|
+
// too or not... This will determine whether we have to resolve them....
|
|
87
|
+
if (this._projectReferences) {
|
|
88
|
+
// Find all project references for the given configuration files
|
|
89
|
+
const projectReferences = await findProjectReferences(api, ...files.absolutePaths())
|
|
90
|
+
report.add(...projectReferences.errors)
|
|
91
|
+
if (report.errors) report.done()
|
|
92
|
+
|
|
93
|
+
// Then determine the build order for all projects and their references
|
|
94
|
+
const projectOrder = resolveProjectOrder(projectReferences.projects)
|
|
95
|
+
report.add(...projectOrder.errors)
|
|
96
|
+
if (report.errors) report.done()
|
|
97
|
+
|
|
98
|
+
// At this point, our list of projects to build is the "order"
|
|
99
|
+
snapshot = await api.updateSnapshot({ openProjects: projectOrder.order })
|
|
100
|
+
projects = [...projectOrder.order]
|
|
101
|
+
} else {
|
|
102
|
+
// If we're not building references, then we just build our files
|
|
103
|
+
const configs = [...files.absolutePaths()].map(findProjectConfig)
|
|
104
|
+
snapshot = await api.updateSnapshot({ openProjects: configs })
|
|
105
|
+
projects = [...configs]
|
|
106
|
+
|
|
107
|
+
// When resolving the project references above, we already processed
|
|
108
|
+
// any configuration files and added any errors to the report...
|
|
109
|
+
// In this case, we have to do it ourselves by reading each config!
|
|
110
|
+
for (const config of configs) {
|
|
111
|
+
const { errors } = await readProjectConfig(api, config)
|
|
112
|
+
report.add(...errors)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// If we have any errors at this point, we can't continue...
|
|
116
|
+
if (report.errors) report.done()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Now build all projects in the order we determined earlier
|
|
120
|
+
for (const project of projects) {
|
|
121
|
+
// Get the actual TypeScript program for this project...
|
|
122
|
+
const program = snapshot.getProject(project)?.program
|
|
123
|
+
assert(program, `Project "${project}" not found in snapshot`)
|
|
124
|
+
|
|
125
|
+
// Collect compilation diagnostics for this project (has files)
|
|
126
|
+
const diagnostics = await Promise.all([
|
|
127
|
+
program.getBindDiagnostics(),
|
|
128
|
+
program.getSemanticDiagnostics(),
|
|
129
|
+
program.getSyntacticDiagnostics(),
|
|
130
|
+
program.getSuggestionDiagnostics(),
|
|
131
|
+
program.getDeclarationDiagnostics(),
|
|
132
|
+
]).then((results) => results.flat())
|
|
133
|
+
|
|
134
|
+
// When emitting, let's emit the files
|
|
135
|
+
if (this._emitOnly !== null) {
|
|
136
|
+
// Emit the files for this program and collect diagnostics
|
|
137
|
+
const emitted = await program.emit(this._emitOnly)
|
|
138
|
+
diagnostics.push(...emitted.diagnostics)
|
|
139
|
+
|
|
140
|
+
// Make sure that we add any emitted file to the list to be returned
|
|
141
|
+
if (emitted.emittedFiles?.length) {
|
|
142
|
+
const directory = getAbsoluteParent(project)
|
|
143
|
+
const mapper = resolveAbsolutePath.bind(null, directory)
|
|
144
|
+
emittedFiles.push(...emitted.emittedFiles.map((file) => mapper(file)))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Report an error if emitting was skipped for some reason (we should
|
|
148
|
+
// have some diagnostics associated with this, but still...)
|
|
149
|
+
// coverage ignore if // don't know how to test this...
|
|
150
|
+
if (emitted.emitSkipped) {
|
|
151
|
+
report.add({
|
|
152
|
+
level: ERROR,
|
|
153
|
+
message: `Unable to emit files for this project`,
|
|
154
|
+
file: project,
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Add all diagnostics to the report
|
|
160
|
+
report.add(...(await convertDiagnostics(diagnostics, program, this._reportDeprecations)))
|
|
161
|
+
}
|
|
162
|
+
} finally {
|
|
163
|
+
await api.close()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
report.done()
|
|
167
|
+
|
|
168
|
+
const outputPath = commonPath(files.directory, ...emittedFiles)
|
|
169
|
+
return Files.builder(outputPath)
|
|
170
|
+
.add(...emittedFiles)
|
|
171
|
+
.build()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* ========================================================================== *
|
|
176
|
+
* TYPESCRIPT 7 HELPERS *
|
|
177
|
+
* ========================================================================== */
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Compile the project identified by the specified configuration file
|
|
181
|
+
* (either a `tsconfig.json` or a directory containing one).
|
|
182
|
+
*
|
|
183
|
+
* This is roughly equivalent to the command line `tsc --project <config>`.
|
|
184
|
+
*/
|
|
185
|
+
export function tsc(config: string, options: TscOptions = {}): Pipe {
|
|
186
|
+
const { directory, ...opts } = options
|
|
187
|
+
return using(config, { directory }).plug(new TypeScript({ ...opts, projectReferences: false }))
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* ========================================================================== */
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Compile the project identified by the specified configuration file
|
|
194
|
+
* (either a `tsconfig.json` or a directory containing one) and all of its
|
|
195
|
+
* references.
|
|
196
|
+
*
|
|
197
|
+
* This is roughly equivalent to the command line `tsc --build <config>`.
|
|
198
|
+
*/
|
|
199
|
+
export function tsbuild(config: string, options: TscOptions = {}): Pipe {
|
|
200
|
+
const { directory, ...opts } = options
|
|
201
|
+
return using(config, { directory }).plug(new TypeScript({ ...opts, projectReferences: true }))
|
|
202
|
+
}
|