@plugjs/tsgo 0.0.6 → 0.1.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/dist/utils.js ADDED
@@ -0,0 +1,58 @@
1
+ import { assert } from '@plugjs/plug/asserts';
2
+ import { resolveFile } from '@plugjs/plug/paths';
3
+ import { EmitOnly } from 'typescript/unstable/async';
4
+ /**
5
+ * Find the TypeScript project configuration file for a given path.
6
+ *
7
+ * The path specified can be a `tsconfig.json` file name or a directory
8
+ * containing a `tsconfig.json` file.
9
+ *
10
+ * The returned path is the absolute path of the actual `tsconfig.json` file.
11
+ */
12
+ export function findProjectConfig(path) {
13
+ // The specified path can be either a file or a directory containing a
14
+ // "tsconfig.json" file: resolve as a file, then assume it's a directory
15
+ let file = resolveFile(path);
16
+ if (!file)
17
+ file = resolveFile(path, 'tsconfig.json');
18
+ assert(file, `TypeScript configuration file not found in "${path}"`);
19
+ return file;
20
+ }
21
+ /** Convert a diagnostic level option for deprecations into a consistent value. */
22
+ export function diagnosticDeprecationLevel(deprecations) {
23
+ switch (deprecations) {
24
+ // If *warn* we report deprecations as warnings
25
+ case 'warn':
26
+ return 'warn';
27
+ // If *off* or *false* we don't report deprecations at all
28
+ case 'off':
29
+ case false:
30
+ return 'off';
31
+ // If *notice* or *true* (default) we report deprecations as notices
32
+ /* coverage ignore next */ case 'notice':
33
+ /* coverage ignore next */ case true:
34
+ default:
35
+ return 'notice';
36
+ }
37
+ }
38
+ /** Convert a emit only option into its corresponding enum value. */
39
+ export function emitOnlyOption(emitOnly) {
40
+ switch (emitOnly) {
41
+ // If *none* or *false* we don't emit anything
42
+ case 'none':
43
+ case false:
44
+ return null; // This is "none"
45
+ // If *js* we only emit JavaScript files
46
+ case 'js':
47
+ return EmitOnly.OnlyJs;
48
+ // If *dts* we only emit declaration files
49
+ case 'dts':
50
+ return EmitOnly.OnlyDts;
51
+ // In all other cases we emit all files
52
+ /* coverage ignore next */ case 'all':
53
+ /* coverage ignore next */ case true:
54
+ default:
55
+ return EmitOnly.All;
56
+ }
57
+ }
58
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAKpD;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAkB;IAClD,sEAAsE;IACtE,wEAAwE;IACxE,IAAI,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,CAAC,IAAI;QAAE,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,eAAe,CAAC,CAAA;IACpD,MAAM,CAAC,IAAI,EAAE,+CAA+C,IAAI,GAAG,CAAC,CAAA;IACpE,OAAO,IAAI,CAAA;AACb,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,0BAA0B,CACxC,YAAsD;IAEtD,QAAQ,YAAY,EAAE,CAAC;QACrB,+CAA+C;QAC/C,KAAK,MAAM;YACT,OAAO,MAAM,CAAA;QACf,0DAA0D;QAC1D,KAAK,KAAK,CAAC;QACX,KAAK,KAAK;YACR,OAAO,KAAK,CAAA;QACd,oEAAoE;QACpE,0BAA0B,CAAC,KAAK,QAAQ,CAAC;QACzC,0BAA0B,CAAC,KAAK,IAAI,CAAC;QACrC;YACE,OAAO,QAAQ,CAAA;IACnB,CAAC;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,cAAc,CAAC,QAAwC;IACrE,QAAQ,QAAQ,EAAE,CAAC;QACjB,8CAA8C;QAC9C,KAAK,MAAM,CAAC;QACZ,KAAK,KAAK;YACR,OAAO,IAAI,CAAA,CAAC,iBAAiB;QAC/B,wCAAwC;QACxC,KAAK,IAAI;YACP,OAAO,QAAQ,CAAC,MAAM,CAAA;QACxB,0CAA0C;QAC1C,KAAK,KAAK;YACR,OAAO,QAAQ,CAAC,OAAO,CAAA;QACzB,uCAAuC;QACvC,0BAA0B,CAAC,KAAK,KAAK,CAAC;QACtC,0BAA0B,CAAC,KAAK,IAAI,CAAC;QACrC;YACE,OAAO,QAAQ,CAAC,GAAG,CAAA;IACvB,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plugjs/tsgo",
3
- "version": "0.0.6",
3
+ "version": "0.1.0",
4
4
  "description": "TypeScript 7.1 support for PlugJS",
5
5
  "keywords": [
6
6
  "build",
@@ -55,12 +55,12 @@
55
55
  "typescript": "^7.1.0-dev"
56
56
  },
57
57
  "devDependencies": {
58
- "@plugjs/cov8": "^0.7.8",
59
- "@plugjs/expect5": "^0.7.8",
60
- "@plugjs/oxc": "^0.1.9",
61
- "@plugjs/plug": "^0.7.8"
58
+ "@plugjs/cov8": "^0.7.11",
59
+ "@plugjs/expect5": "^0.7.11",
60
+ "@plugjs/oxc": "^0.1.13",
61
+ "@plugjs/plug": "^0.7.11"
62
62
  },
63
63
  "peerDependencies": {
64
- "@plugjs/plug": "^0.7.8"
64
+ "@plugjs/plug": "^0.7.11"
65
65
  }
66
66
  }
@@ -3,11 +3,9 @@ import { assertAbsolutePath } from '@plugjs/plug/paths'
3
3
  import { DiagnosticCategory } from 'typescript/unstable/async'
4
4
 
5
5
  import type { ReportRecord } from '@plugjs/plug/logging'
6
- import type { AbsolutePath } from '@plugjs/plug/paths'
7
- import type { SourceFile } from 'typescript/unstable/ast'
8
- import type { Diagnostic, Program } from 'typescript/unstable/async'
6
+ import type { Diagnostic } from 'typescript/unstable/async'
9
7
 
10
- function convertDiagnostic(diagnostic: Diagnostic, sourceFile?: SourceFile): ReportRecord {
8
+ function convertDiagnostic(diagnostic: Diagnostic): ReportRecord {
11
9
  const record: ReportRecord & { tags: string[] } = {
12
10
  level: NOTICE,
13
11
  message: diagnostic.text,
@@ -31,22 +29,21 @@ function convertDiagnostic(diagnostic: Diagnostic, sourceFile?: SourceFile): Rep
31
29
  record.file = diagnostic.fileName
32
30
  }
33
31
 
34
- if (sourceFile) {
35
- const pos = sourceFile.getLineAndCharacterOfPosition(diagnostic.pos)
32
+ if (diagnostic.startPosition) {
33
+ const pos = diagnostic.startPosition
36
34
  record.line = pos.line + 1
37
35
  record.column = pos.character + 1
38
36
  record.length = diagnostic.end - diagnostic.pos
39
- record.source = sourceFile.getFullText()
37
+ record.source = diagnostic.source
40
38
  }
41
39
 
42
40
  return record
43
41
  }
44
42
 
45
- export async function convertDiagnostics(
43
+ export function convertDiagnostics(
46
44
  diagnostics: readonly Diagnostic[],
47
- program: Program,
48
45
  deprecations: 'warn' | 'notice' | 'off' = 'notice',
49
- ): Promise<ReportRecord[]> {
46
+ ): ReportRecord[] {
50
47
  // Filter out any duplicate diagnostic *before* we convert them to report
51
48
  // records (that might be expensive because of source file lookups)
52
49
  const seen = new Set<string>()
@@ -65,59 +62,23 @@ export async function convertDiagnostics(
65
62
  })
66
63
 
67
64
  // Convert all diagnostics to report records
68
- const converted = await Promise.all(
69
- unique.map(async (diagnostic) => {
70
- // Anything not an error is not critical
71
- const isNonCritical = diagnostic.category !== DiagnosticCategory.Error
72
-
73
- // First of all see how we need to handle non-error deprecations: when
74
- // "off" we suppress them, when "warn" we convert them to warnings...
75
- if (diagnostic.reportsDeprecated && isNonCritical) {
76
- if (deprecations === 'off') return // simply filter this out...
77
- if (deprecations === 'warn') {
78
- diagnostic = { ...diagnostic, category: DiagnosticCategory.Warning }
79
- }
65
+ const converted = unique.map((diagnostic) => {
66
+ // Anything not an error is not critical
67
+ const isNonCritical = diagnostic.category !== DiagnosticCategory.Error
68
+
69
+ // First of all see how we need to handle non-error deprecations: when
70
+ // "off" we suppress them, when "warn" we convert them to warnings...
71
+ if (diagnostic.reportsDeprecated && isNonCritical) {
72
+ if (deprecations === 'off') return // simply filter this out...
73
+ if (deprecations === 'warn') {
74
+ diagnostic = { ...diagnostic, category: DiagnosticCategory.Warning }
80
75
  }
76
+ }
81
77
 
82
- // First figure out where this diagnostic came from...
83
- const sourceFileMetadata = diagnostic.fileName
84
- ? await program.getSourceFileMetadata(diagnostic.fileName)
85
- : /* coverage ignore next */ undefined
86
-
87
- // If the source file is in the default library (e.g. "lib.dom.ts") or
88
- // from an external library (in "node_modules") then we ONLY keep errors
89
- // and (we ignore things like warnings, suggestions, ...)
90
- const isExternal = sourceFileMetadata?.isDefaultLibrary || sourceFileMetadata?.isFromExternalLibrary
91
- if (isExternal && isNonCritical) return
92
-
93
- // Get the source file and its metadata (if any) for the diagnostic...
94
- // We do this *after* the external check above as getting the source file
95
- // is quite expensive (especially for things like "lib.es5.dts")...
96
- const sourceFile = diagnostic.fileName
97
- ? ((await program.getSourceFile(diagnostic.fileName)) ??
98
- (await program.getConfigSourceFile(diagnostic.fileName)))
99
- : /* coverage ignore next */ undefined
100
-
101
- // Convert our diagnostic to a report record and return it
102
- return convertDiagnostic(diagnostic, sourceFile)
103
- }),
104
- )
78
+ // Convert our diagnostic to a report record and return it
79
+ return convertDiagnostic(diagnostic)
80
+ })
105
81
 
106
82
  // Filter out any null/undefined records
107
83
  return converted.filter((record): record is ReportRecord => !!record)
108
84
  }
109
-
110
- export async function convertProjectDiagnostics(
111
- diagnostics: readonly Diagnostic[],
112
- program: Program,
113
- fileName: AbsolutePath,
114
- ): Promise<ReportRecord[]> {
115
- // Make sure that we have a file name for the diagnostics
116
- const fileDiagnostics = diagnostics.map((diagnostic) => {
117
- if (diagnostic.fileName) return diagnostic
118
- return { ...diagnostic, fileName }
119
- })
120
-
121
- // Convert the diagnostics to report records and return them
122
- return convertDiagnostics(fileDiagnostics, program)
123
- }
package/src/index.ts CHANGED
@@ -1,12 +1,39 @@
1
1
  import { install } from '@plugjs/plug/pipe'
2
2
 
3
- import { TypeScript } from './typescript.ts'
3
+ import { TypeScript, TypeScriptBuild } from './typescript.ts'
4
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
5
+ import type { OverrideCompilerOptions } from 'typescript/unstable/async'
6
+
7
+ /** Options for the `typescriptBuild` plug. */
8
+ export interface TypeScriptBuildOptions {
9
+ /**
10
+ * Force building all projects in the build. Defaults to `true`.
11
+ *
12
+ * This is equivalent to running `tsc --build --force`.
13
+ */
14
+ force?: boolean
15
+
16
+ /**
17
+ * Skip building downstream projects on error in upstream project. Defaults
18
+ * to `false`.
19
+ *
20
+ * This is equivalent to running `tsc --build --stopBuildOnErrors`.
21
+ */
22
+ stopBuildOnErrors?: boolean
23
+
24
+ /**
25
+ * Enable verbose logging during the build. Defaults to `true`.
26
+ *
27
+ * This is equivalent to running `tsc --build --verbose`.
28
+ */
29
+ verbose?: boolean
30
+
31
+ /** Override the default TypeScript compiler options for the build. */
32
+ overrideCompilerOptions?: OverrideCompilerOptions
33
+ }
9
34
 
35
+ /** Options for the `typescript` plug. */
36
+ export interface TypeScriptOptions {
10
37
  /**
11
38
  * Controls how deprecation diagnostics are reported. Use `'notice'` or
12
39
  * `true` for notices, `'warn'` for warnings, and `'off'` or `false` to
@@ -23,8 +50,14 @@ export interface TypeScriptOptions {
23
50
  emitOnly?: 'js' | 'dts' | 'all' | 'none' | boolean
24
51
  }
25
52
 
26
- /** Options for the {@link tsc} and {@link tsbuild} helpers. */
27
- export interface TscOptions extends Omit<TypeScriptOptions, 'projectReferences'> {
53
+ /** Options for the {@link tsc} helper. */
54
+ export interface TscOptions extends TypeScriptOptions {
55
+ /** Base directory used to resolve the configuration path. */
56
+ directory?: string
57
+ }
58
+
59
+ /** Options for the {@link tscBuild} helper. */
60
+ export interface TscBuildOptions extends TypeScriptBuildOptions {
28
61
  /** Base directory used to resolve the configuration path. */
29
62
  directory?: string
30
63
  }
@@ -35,6 +68,8 @@ declare module '@plugjs/plug' {
35
68
  * Run the {@link https://www.typescriptlang.org/ TypeScript Compiler}
36
69
  * version 7 (native) over the `tsconfig.json` configuration files
37
70
  * given as input to this {@link Pipe} instance.
71
+ *
72
+ * This is equivalent to running `tsc --project`.
38
73
  */
39
74
  typescript(): Pipe
40
75
 
@@ -43,12 +78,35 @@ declare module '@plugjs/plug' {
43
78
  * version 7 (native) over the `tsconfig.json` configuration files
44
79
  * given as input to this {@link Pipe} instance.
45
80
  *
81
+ * This is equivalent to running `tsc --project`.
82
+ *
46
83
  * @param options {@link TypeScriptOptions} to use for the build.
47
84
  */
48
85
  typescript(options: TypeScriptOptions): Pipe
86
+
87
+ /**
88
+ * Run the {@link https://www.typescriptlang.org/ TypeScript Builder}
89
+ * version 7 (native) over the `tsconfig.json` configuration files
90
+ * given as input to this {@link Pipe} instance.
91
+ *
92
+ * This is equivalent to running `tsc --build`.
93
+ */
94
+ typescriptBuild(): Promise<undefined>
95
+
96
+ /**
97
+ * Run the {@link https://www.typescriptlang.org/ TypeScript Builder}
98
+ * version 7 (native) over the `tsconfig.json` configuration files
99
+ * given as input to this {@link Pipe} instance.
100
+ *
101
+ * This is equivalent to running `tsc --build`.
102
+ *
103
+ * @param options {@link TypeScriptBuildOptions} to use for the build.
104
+ */
105
+ typescriptBuild(options: TypeScriptBuildOptions): Promise<undefined>
49
106
  }
50
107
  }
51
108
 
52
109
  install('typescript', TypeScript)
110
+ install('typescriptBuild', TypeScriptBuild)
53
111
 
54
112
  export { tsbuild, tsc } from './typescript.ts'
package/src/typescript.ts CHANGED
@@ -1,166 +1,133 @@
1
1
  import { using } from '@plugjs/plug'
2
- import { assert } from '@plugjs/plug/asserts'
3
2
  import { Files } from '@plugjs/plug/files'
4
3
  import { ERROR } from '@plugjs/plug/logging'
5
- import { commonPath, getAbsoluteParent, resolveAbsolutePath } from '@plugjs/plug/paths'
6
- import { API, EmitOnly } from 'typescript/unstable/async'
4
+ import { assertAbsolutePath, commonPath, isAbsolutePath } from '@plugjs/plug/paths'
5
+ import { API, DiagnosticCategory } from 'typescript/unstable/async'
7
6
 
8
7
  import { convertDiagnostics } from './diagnostics.ts'
9
- import { findProjectConfig, findProjectReferences, readProjectConfig, resolveProjectOrder } from './projects.ts'
8
+ import { diagnosticDeprecationLevel, emitOnlyOption, findProjectConfig } from './utils.ts'
10
9
 
11
- import type { TscOptions, TypeScriptOptions } from './index.ts'
10
+ import type { TscBuildOptions, TscOptions, TypeScriptBuildOptions, TypeScriptOptions } from './index.ts'
12
11
  import type { Pipe } from '@plugjs/plug'
13
12
  import type { AbsolutePath } from '@plugjs/plug/paths'
14
13
  import type { Context, Plug } from '@plugjs/plug/pipe'
15
- import type { Snapshot } from 'typescript/unstable/async'
14
+ import type { BuildOrchestrator } from 'typescript/unstable/async'
15
+ import type { Diagnostic, RootedFilePath } from 'typescript/unstable/proto'
16
16
 
17
17
  /* ========================================================================== *
18
18
  * TYPESCRIPT 7 PLUG IMPLEMENTATION *
19
19
  * ========================================================================== */
20
20
 
21
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
- }
22
+ constructor(private readonly _options: TypeScriptOptions = {}) {}
70
23
 
71
24
  async pipe(files: Files, context: Context): Promise<Files> {
25
+ // Convert our options
26
+ const deprecationsLevel = diagnosticDeprecationLevel(this._options.reportDeprecations)
27
+ const emitOnly = emitOnlyOption(this._options.emitOnly)
28
+
72
29
  // Create our TypeScript API and report for this plug
73
- const api = new API({ cwd: files.directory })
74
30
  const report = context.log.report('TypeScript Report')
75
31
 
76
32
  // This is the list of emitted files to return to the pipe...
77
33
  const emittedFiles: AbsolutePath[] = []
78
34
 
35
+ let api: API | undefined = undefined
79
36
  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
- }
37
+ api = new API({ cwd: files.directory })
114
38
 
115
- // If we have any errors at this point, we can't continue...
116
- if (report.errors) report.done()
117
- }
39
+ // If we're not building references, then we just build our files
40
+ const configs = [...files.absolutePaths()].map(findProjectConfig)
41
+ const snapshot = await api.createSnapshot({ openProjects: configs })
42
+ const projects = snapshot.getProjects()
118
43
 
119
44
  // Now build all projects in the order we determined earlier
120
45
  for (const project of projects) {
121
46
  // Get the actual TypeScript program for this project...
122
- const program = snapshot.getProject(project)?.program
123
- assert(program, `Project "${project}" not found in snapshot`)
47
+ const program = project.program
124
48
 
125
49
  // Collect compilation diagnostics for this project (has files)
126
- const diagnostics = await Promise.all([
50
+ const fullDiagnostics = await Promise.all([
127
51
  program.getBindDiagnostics(),
52
+ program.getConfigFileParsingDiagnostics(),
53
+ program.getDeclarationDiagnostics(),
54
+ program.getGlobalDiagnostics(),
55
+ program.getProgramDiagnostics(),
128
56
  program.getSemanticDiagnostics(),
129
- program.getSyntacticDiagnostics(),
130
57
  program.getSuggestionDiagnostics(),
131
- program.getDeclarationDiagnostics(),
58
+ program.getSyntacticDiagnostics(),
132
59
  ]).then((results) => results.flat())
133
60
 
61
+ // Keep a set of external files we can ignore non-critical diags for
62
+ const externalFiles = new Set<RootedFilePath>()
63
+
64
+ // Filter out diagnostics that are not relevant for this project
65
+ const now = Date.now()
66
+ const diagnostics = (
67
+ await Promise.all(
68
+ fullDiagnostics.map(async (diagnostic) => {
69
+ // Always keep all the errors...
70
+ if (diagnostic.category === DiagnosticCategory.Error) return diagnostic
71
+
72
+ // coverage ignore if // Keep diagnostics that do not have an associated file
73
+ if (!diagnostic.fileName) return diagnostic
74
+
75
+ // coverage ignore if // Before we do all the async resolution below, check our cache
76
+ if (externalFiles.has(diagnostic.fileName)) return undefined
77
+
78
+ // Get the source file metadata for this diagnostic (expensive)
79
+ // and filter out anything from external libraries....
80
+ const metadata = await program.getSourceFileMetadata(diagnostic.fileName)
81
+ if (metadata?.isDefaultLibrary || metadata?.isFromExternalLibrary) {
82
+ externalFiles.add(diagnostic.fileName)
83
+ return undefined
84
+ }
85
+
86
+ // Anything else, we keep!
87
+ return diagnostic
88
+ }),
89
+ )
90
+ ).filter((diagnostic): diagnostic is Diagnostic => !!diagnostic)
91
+ context.log.warn(`Filtered diagnostics in ${Date.now() - now}ms`)
92
+
134
93
  // When emitting, let's emit the files
135
- if (this._emitOnly !== null) {
94
+ if (emitOnly !== null) {
136
95
  // Emit the files for this program and collect diagnostics
137
- const emitted = await program.emit(this._emitOnly)
96
+ const emitted = await program.emit(emitOnly)
138
97
  diagnostics.push(...emitted.diagnostics)
139
98
 
140
99
  // Make sure that we add any emitted file to the list to be returned
141
100
  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)))
101
+ emitted.emittedFiles.forEach((file) => {
102
+ assertAbsolutePath(file)
103
+ emittedFiles.push(file)
104
+ })
145
105
  }
146
106
 
147
107
  // Report an error if emitting was skipped for some reason (we should
148
108
  // have some diagnostics associated with this, but still...)
149
109
  // coverage ignore if // don't know how to test this...
150
110
  if (emitted.emitSkipped) {
151
- report.add({
152
- level: ERROR,
153
- message: `Unable to emit files for this project`,
154
- file: project,
155
- })
111
+ if (project.configFileName && isAbsolutePath(project.configFileName)) {
112
+ report.add({
113
+ level: ERROR,
114
+ message: `Unable to emit files for this project`,
115
+ file: project.configFileName,
116
+ })
117
+ } else {
118
+ report.add({
119
+ level: ERROR,
120
+ message: `Unable to emit files for unknown project`,
121
+ })
122
+ }
156
123
  }
157
124
  }
158
125
 
159
126
  // Add all diagnostics to the report
160
- report.add(...(await convertDiagnostics(diagnostics, program, this._reportDeprecations)))
127
+ report.add(...convertDiagnostics(diagnostics, deprecationsLevel))
161
128
  }
162
129
  } finally {
163
- await api.close()
130
+ if (api) await api.close()
164
131
  }
165
132
 
166
133
  report.done()
@@ -172,6 +139,48 @@ export class TypeScript implements Plug<Files> {
172
139
  }
173
140
  }
174
141
 
142
+ export class TypeScriptBuild implements Plug<void> {
143
+ constructor(private readonly _options: TypeScriptBuildOptions = {}) {}
144
+
145
+ async pipe(files: Files, context: Context): Promise<void> {
146
+ // Destructure the build options for easier access
147
+ const { overrideCompilerOptions = {}, stopBuildOnErrors = false, verbose = true, force = true } = this._options
148
+
149
+ // Create our TypeScript API and report for this plug
150
+ const report = context.log.report('TypeScript Build Report')
151
+
152
+ let api: API | undefined = undefined
153
+ let orchestrator: BuildOrchestrator | undefined = undefined
154
+ try {
155
+ api = new API({ cwd: files.directory })
156
+ orchestrator = await api.createBuildOrchestrator([...files.absolutePaths()], {
157
+ cwd: files.directory,
158
+ // From our options
159
+ overrideCompilerOptions,
160
+ stopBuildOnErrors,
161
+ verbose,
162
+ force,
163
+ })
164
+
165
+ const results = await orchestrator.build()
166
+
167
+ if (results.diagnostics) report.add(...convertDiagnostics(results.diagnostics))
168
+
169
+ if (results.status !== 0) {
170
+ report.add({
171
+ level: ERROR,
172
+ message: `Build failed with status code ${results.status}`,
173
+ })
174
+ }
175
+ } finally {
176
+ if (orchestrator) await orchestrator.dispose()
177
+ if (api) await api.close()
178
+ }
179
+
180
+ report.done()
181
+ }
182
+ }
183
+
175
184
  /* ========================================================================== *
176
185
  * TYPESCRIPT 7 HELPERS *
177
186
  * ========================================================================== */
@@ -184,7 +193,7 @@ export class TypeScript implements Plug<Files> {
184
193
  */
185
194
  export function tsc(config: string, options: TscOptions = {}): Pipe {
186
195
  const { directory, ...opts } = options
187
- return using(config, { directory }).plug(new TypeScript({ ...opts, projectReferences: false }))
196
+ return using(config, { directory }).plug(new TypeScript(opts))
188
197
  }
189
198
 
190
199
  /* ========================================================================== */
@@ -196,7 +205,7 @@ export function tsc(config: string, options: TscOptions = {}): Pipe {
196
205
  *
197
206
  * This is roughly equivalent to the command line `tsc --build <config>`.
198
207
  */
199
- export function tsbuild(config: string, options: TscOptions = {}): Pipe {
208
+ export function tsbuild(config: string, options: TscBuildOptions): Promise<undefined> {
200
209
  const { directory, ...opts } = options
201
- return using(config, { directory }).plug(new TypeScript({ ...opts, projectReferences: true }))
210
+ return using(config, { directory }).plug(new TypeScriptBuild(opts))
202
211
  }