@plugjs/tsgo 0.0.6 → 0.1.1
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/diagnostics.d.ts +2 -4
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +9 -36
- package/dist/diagnostics.js.map +1 -1
- package/dist/index.d.ts +55 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/typescript.d.ts +15 -6
- package/dist/typescript.d.ts.map +1 -1
- package/dist/typescript.js +113 -106
- package/dist/typescript.js.map +1 -1
- package/dist/utils.d.ts +17 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +58 -0
- package/dist/utils.js.map +1 -0
- package/package.json +6 -6
- package/src/diagnostics.ts +22 -61
- package/src/index.ts +65 -7
- package/src/typescript.ts +131 -112
- package/src/utils.ts +64 -0
- package/dist/projects.d.ts +0 -72
- package/dist/projects.d.ts.map +0 -1
- package/dist/projects.js +0 -209
- package/dist/projects.js.map +0 -1
- package/src/projects.ts +0 -271
package/src/utils.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { assert } from '@plugjs/plug/asserts'
|
|
2
|
+
import { resolveFile } from '@plugjs/plug/paths'
|
|
3
|
+
import { EmitOnly } from 'typescript/async'
|
|
4
|
+
|
|
5
|
+
import type { TypeScriptOptions } from './index.ts'
|
|
6
|
+
import type { AbsolutePath } from '@plugjs/plug/paths'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Find the TypeScript project configuration file for a given path.
|
|
10
|
+
*
|
|
11
|
+
* The path specified can be a `tsconfig.json` file name or a directory
|
|
12
|
+
* containing a `tsconfig.json` file.
|
|
13
|
+
*
|
|
14
|
+
* The returned path is the absolute path of the actual `tsconfig.json` file.
|
|
15
|
+
*/
|
|
16
|
+
export function findProjectConfig(path: AbsolutePath): AbsolutePath {
|
|
17
|
+
// The specified path can be either a file or a directory containing a
|
|
18
|
+
// "tsconfig.json" file: resolve as a file, then assume it's a directory
|
|
19
|
+
let file = resolveFile(path)
|
|
20
|
+
if (!file) file = resolveFile(path, 'tsconfig.json')
|
|
21
|
+
assert(file, `TypeScript configuration file not found in "${path}"`)
|
|
22
|
+
return file
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Convert a diagnostic level option for deprecations into a consistent value. */
|
|
26
|
+
export function diagnosticDeprecationLevel(
|
|
27
|
+
deprecations?: TypeScriptOptions['reportDeprecations'],
|
|
28
|
+
): 'warn' | 'notice' | 'off' {
|
|
29
|
+
switch (deprecations) {
|
|
30
|
+
// If *warn* we report deprecations as warnings
|
|
31
|
+
case 'warn':
|
|
32
|
+
return 'warn'
|
|
33
|
+
// If *off* or *false* we don't report deprecations at all
|
|
34
|
+
case 'off':
|
|
35
|
+
case false:
|
|
36
|
+
return 'off'
|
|
37
|
+
// If *notice* or *true* (default) we report deprecations as notices
|
|
38
|
+
/* coverage ignore next */ case 'notice':
|
|
39
|
+
/* coverage ignore next */ case true:
|
|
40
|
+
default:
|
|
41
|
+
return 'notice'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Convert a emit only option into its corresponding enum value. */
|
|
46
|
+
export function emitOnlyOption(emitOnly?: TypeScriptOptions['emitOnly']): EmitOnly | null {
|
|
47
|
+
switch (emitOnly) {
|
|
48
|
+
// If *none* or *false* we don't emit anything
|
|
49
|
+
case 'none':
|
|
50
|
+
case false:
|
|
51
|
+
return null // This is "none"
|
|
52
|
+
// If *js* we only emit JavaScript files
|
|
53
|
+
case 'js':
|
|
54
|
+
return EmitOnly.OnlyJs
|
|
55
|
+
// If *dts* we only emit declaration files
|
|
56
|
+
case 'dts':
|
|
57
|
+
return EmitOnly.OnlyDts
|
|
58
|
+
// In all other cases we emit all files
|
|
59
|
+
/* coverage ignore next */ case 'all':
|
|
60
|
+
/* coverage ignore next */ case true:
|
|
61
|
+
default:
|
|
62
|
+
return EmitOnly.All
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/projects.d.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import type { ReportRecord } from '@plugjs/plug/logging';
|
|
2
|
-
import type { AbsolutePath } from '@plugjs/plug/paths';
|
|
3
|
-
import type { API, ParsedCommandLine } from 'typescript/unstable/async';
|
|
4
|
-
/** Interface describing the actual (resolved) config file and its references */
|
|
5
|
-
interface ProjectReferences {
|
|
6
|
-
/** The actual path of the configuration file containing the references */
|
|
7
|
-
path: AbsolutePath;
|
|
8
|
-
/** The actual configuration parsed from the configuration file */
|
|
9
|
-
config: ParsedCommandLine;
|
|
10
|
-
/** The actual paths of all configuration files referenced by this project */
|
|
11
|
-
references: AbsolutePath[];
|
|
12
|
-
/** Any errors encountered while reading the configuration file */
|
|
13
|
-
errors: ReportRecord[];
|
|
14
|
-
}
|
|
15
|
-
/** Interface describing a group of cross-referencing projects */
|
|
16
|
-
interface Projects {
|
|
17
|
-
/** All projects to build, each associated with its referencing projects */
|
|
18
|
-
projects: Map<AbsolutePath, Set<AbsolutePath>>;
|
|
19
|
-
/** Any errors encountered while reading the configuration files */
|
|
20
|
-
errors: ReportRecord[];
|
|
21
|
-
}
|
|
22
|
-
/** Interface describing the order of projects, including any cycles and unresolved projects */
|
|
23
|
-
interface ProjectOrder {
|
|
24
|
-
/** The order in which projects should be built */
|
|
25
|
-
order: AbsolutePath[];
|
|
26
|
-
/** Array of project reference cycles */
|
|
27
|
-
cycles: AbsolutePath[][];
|
|
28
|
-
/** Array containing all projects that could not be ordered because of cyclical dependencies */
|
|
29
|
-
unresolved: AbsolutePath[];
|
|
30
|
-
/** Any errors encountered while resolving the project order */
|
|
31
|
-
errors: ReportRecord[];
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Find the TypeScript project configuration file for a given path.
|
|
35
|
-
*
|
|
36
|
-
* The path specified can be a `tsconfig.json` file name or a directory
|
|
37
|
-
* containing a `tsconfig.json` file.
|
|
38
|
-
*
|
|
39
|
-
* The returned path is the absolute path of the actual `tsconfig.json` file.
|
|
40
|
-
*/
|
|
41
|
-
export declare function findProjectConfig(path: AbsolutePath): AbsolutePath;
|
|
42
|
-
/**
|
|
43
|
-
* Read the TypeScript project configuration file for a given path.
|
|
44
|
-
*
|
|
45
|
-
* The path specified can be a `tsconfig.json` file name or a directory
|
|
46
|
-
* containing a `tsconfig.json` file.
|
|
47
|
-
*
|
|
48
|
-
* The returned object contains the resolved configuration file name (in `path`)
|
|
49
|
-
* and the array of resolved project references (the actual resolved files on
|
|
50
|
-
* disk in `references`).
|
|
51
|
-
*
|
|
52
|
-
* Any errors encountered while reading the configuration file or resolving its
|
|
53
|
-
* references are returned in the `errors` array.
|
|
54
|
-
*/
|
|
55
|
-
export declare function readProjectConfig(api: API, path: AbsolutePath): Promise<ProjectReferences>;
|
|
56
|
-
/**
|
|
57
|
-
* Find _all_ project references given a set of initial project paths.
|
|
58
|
-
*
|
|
59
|
-
* The returned map contains all the projects (resolved configuration file
|
|
60
|
-
* names) and their referring projects as values.
|
|
61
|
-
*/
|
|
62
|
-
export declare function findProjectReferences(api: API, ...paths: AbsolutePath[]): Promise<Projects>;
|
|
63
|
-
/**
|
|
64
|
-
* Use Kahn's topological-sort algorithm on a map of projects and their
|
|
65
|
-
* references to determine the order in which they should be built.
|
|
66
|
-
*
|
|
67
|
-
* @param projects A map of projects and their referring projects
|
|
68
|
-
* @returns The order of projects, any unresolved projects, and cycles found
|
|
69
|
-
*/
|
|
70
|
-
export declare function resolveProjectOrder(projects: ReadonlyMap<AbsolutePath, Set<AbsolutePath>>): ProjectOrder;
|
|
71
|
-
export {};
|
|
72
|
-
//# sourceMappingURL=projects.d.ts.map
|
package/dist/projects.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../src/projects.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,GAAG,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAEvE,gFAAgF;AAChF,UAAU,iBAAiB;IACzB,0EAA0E;IAC1E,IAAI,EAAE,YAAY,CAAA;IAClB,kEAAkE;IAClE,MAAM,EAAE,iBAAiB,CAAA;IACzB,6EAA6E;IAC7E,UAAU,EAAE,YAAY,EAAE,CAAA;IAC1B,kEAAkE;IAClE,MAAM,EAAE,YAAY,EAAE,CAAA;CACvB;AAED,iEAAiE;AACjE,UAAU,QAAQ;IAChB,2EAA2E;IAC3E,QAAQ,EAAE,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAA;IAC9C,mEAAmE;IACnE,MAAM,EAAE,YAAY,EAAE,CAAA;CACvB;AAED,+FAA+F;AAC/F,UAAU,YAAY;IACpB,kDAAkD;IAClD,KAAK,EAAE,YAAY,EAAE,CAAA;IACrB,wCAAwC;IACxC,MAAM,EAAE,YAAY,EAAE,EAAE,CAAA;IACxB,+FAA+F;IAC/F,UAAU,EAAE,YAAY,EAAE,CAAA;IAC1B,+DAA+D;IAC/D,MAAM,EAAE,YAAY,EAAE,CAAA;CACvB;AAID;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,YAAY,CAOlE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAqChG;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkDjG;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,WAAW,CAAC,YAAY,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,GAAG,YAAY,CA4FxG"}
|
package/dist/projects.js
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
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
|
-
import { convertProjectDiagnostics } from './diagnostics.js';
|
|
5
|
-
/* ========================================================================== */
|
|
6
|
-
/**
|
|
7
|
-
* Find the TypeScript project configuration file for a given path.
|
|
8
|
-
*
|
|
9
|
-
* The path specified can be a `tsconfig.json` file name or a directory
|
|
10
|
-
* containing a `tsconfig.json` file.
|
|
11
|
-
*
|
|
12
|
-
* The returned path is the absolute path of the actual `tsconfig.json` file.
|
|
13
|
-
*/
|
|
14
|
-
export function findProjectConfig(path) {
|
|
15
|
-
// The specified path can be either a file or a directory containing a
|
|
16
|
-
// "tsconfig.json" file: resolve as a file, then assume it's a directory
|
|
17
|
-
let file = resolveFile(path);
|
|
18
|
-
if (!file)
|
|
19
|
-
file = resolveFile(path, 'tsconfig.json');
|
|
20
|
-
assert(file, `TypeScript configuration file not found in "${path}"`);
|
|
21
|
-
return file;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Read the TypeScript project configuration file for a given path.
|
|
25
|
-
*
|
|
26
|
-
* The path specified can be a `tsconfig.json` file name or a directory
|
|
27
|
-
* containing a `tsconfig.json` file.
|
|
28
|
-
*
|
|
29
|
-
* The returned object contains the resolved configuration file name (in `path`)
|
|
30
|
-
* and the array of resolved project references (the actual resolved files on
|
|
31
|
-
* disk in `references`).
|
|
32
|
-
*
|
|
33
|
-
* Any errors encountered while reading the configuration file or resolving its
|
|
34
|
-
* references are returned in the `errors` array.
|
|
35
|
-
*/
|
|
36
|
-
export async function readProjectConfig(api, path) {
|
|
37
|
-
const file = findProjectConfig(path);
|
|
38
|
-
// Use the TypeScript API to load up this project... We already update the
|
|
39
|
-
// snapshot here, so we don't need to do it again once we end up compiling
|
|
40
|
-
// the whole project...
|
|
41
|
-
const snapshot = await api.updateSnapshot({ openProjects: [file] });
|
|
42
|
-
const project = snapshot.getProject(file);
|
|
43
|
-
assert(project, `Failed to open TypeScript project from "${file}"`);
|
|
44
|
-
assertAbsolutePath(project.configFileName);
|
|
45
|
-
path = project.configFileName; // rewrite the path to the actual
|
|
46
|
-
// Get any diagnostics encountered while parsing the configuration file
|
|
47
|
-
// and any "global" diagnostics (not associated with a specific file)...
|
|
48
|
-
const diagnostics = await Promise.all([
|
|
49
|
-
project.program.getGlobalDiagnostics(),
|
|
50
|
-
project.program.getProgramDiagnostics(),
|
|
51
|
-
project.program.getConfigFileParsingDiagnostics(),
|
|
52
|
-
]).then((results) => results.flat());
|
|
53
|
-
// Convert the diagnostics to our simplified format
|
|
54
|
-
const errors = await convertProjectDiagnostics(diagnostics, project.program, path);
|
|
55
|
-
// Prepare our simplified result object
|
|
56
|
-
const config = project.parsedCommandLine;
|
|
57
|
-
const result = { path, errors, config, references: [] };
|
|
58
|
-
// Resolve the references to absolute paths
|
|
59
|
-
const directory = getAbsoluteParent(path);
|
|
60
|
-
for (const reference of config.projectReferences ?? []) {
|
|
61
|
-
let resolved = resolveFile(directory, reference.path);
|
|
62
|
-
if (!resolved)
|
|
63
|
-
resolved = resolveFile(directory, reference.path, 'tsconfig.json');
|
|
64
|
-
if (resolved)
|
|
65
|
-
result.references.push(resolved);
|
|
66
|
-
}
|
|
67
|
-
// All done
|
|
68
|
-
return result;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Find _all_ project references given a set of initial project paths.
|
|
72
|
-
*
|
|
73
|
-
* The returned map contains all the projects (resolved configuration file
|
|
74
|
-
* names) and their referring projects as values.
|
|
75
|
-
*/
|
|
76
|
-
export async function findProjectReferences(api, ...paths) {
|
|
77
|
-
// The set of *all* projects, whether we have resolved them yet or not.
|
|
78
|
-
const remainingProjects = new Set(paths);
|
|
79
|
-
// The set of all *resolved* projects (that is, opened and parsed)
|
|
80
|
-
const resolvedProjects = new Set();
|
|
81
|
-
// The map of all our projects, keyed by their resolved configuration file
|
|
82
|
-
// name and having the set of projects that refer to it as a value
|
|
83
|
-
const projects = new Map();
|
|
84
|
-
// An array of all errors encountered while reading the configuration files
|
|
85
|
-
const errors = [];
|
|
86
|
-
// Add a referring project to the set of referring projects for a referred project
|
|
87
|
-
function addProject(referredProject, referringProject) {
|
|
88
|
-
let referringProjects = projects.get(referredProject);
|
|
89
|
-
if (!referringProjects) {
|
|
90
|
-
referringProjects = new Set();
|
|
91
|
-
projects.set(referredProject, referringProjects);
|
|
92
|
-
}
|
|
93
|
-
if (referringProject)
|
|
94
|
-
referringProjects.add(referringProject);
|
|
95
|
-
}
|
|
96
|
-
while (remainingProjects.size > 0) {
|
|
97
|
-
// Take and remove the next project from the remaining set
|
|
98
|
-
const project = remainingProjects.values().next().value;
|
|
99
|
-
remainingProjects.delete(project);
|
|
100
|
-
// Read the project configuration file and its references
|
|
101
|
-
const { path, references, errors: e } = await readProjectConfig(api, project);
|
|
102
|
-
// If we already resolved this project, skip it
|
|
103
|
-
if (resolvedProjects.has(path))
|
|
104
|
-
continue;
|
|
105
|
-
// Push any errors encountered while reading the configuration file
|
|
106
|
-
errors.push(...e);
|
|
107
|
-
// Add the project (resolved filename) to the resolved set
|
|
108
|
-
resolvedProjects.add(path);
|
|
109
|
-
// Record the project and its references in our map of projects
|
|
110
|
-
addProject(path);
|
|
111
|
-
// Look at all the project references
|
|
112
|
-
for (const reference of references) {
|
|
113
|
-
// If we haven't resolved this reference yet, add it to the remaining
|
|
114
|
-
if (!resolvedProjects.has(reference))
|
|
115
|
-
remainingProjects.add(reference);
|
|
116
|
-
// Then make sure that we record the referring project for this reference
|
|
117
|
-
addProject(reference, path);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
// Return our projects map and all errors
|
|
121
|
-
return { projects, errors };
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Use Kahn's topological-sort algorithm on a map of projects and their
|
|
125
|
-
* references to determine the order in which they should be built.
|
|
126
|
-
*
|
|
127
|
-
* @param projects A map of projects and their referring projects
|
|
128
|
-
* @returns The order of projects, any unresolved projects, and cycles found
|
|
129
|
-
*/
|
|
130
|
-
export function resolveProjectOrder(projects) {
|
|
131
|
-
// The number of unresolved dependencies for each project
|
|
132
|
-
const counts = Object.create(null);
|
|
133
|
-
// Iterate through all the projects (and their referring projects), counting
|
|
134
|
-
// the number of unresolved dependencies for each project.
|
|
135
|
-
for (const [referred, referring] of projects.entries()) {
|
|
136
|
-
// Ensure the referred project has a dependencies count (even if it is zero)
|
|
137
|
-
counts[referred] = counts[referred] ?? 0;
|
|
138
|
-
// For each referring project, increment its count of unresolved dependencies
|
|
139
|
-
for (const ref of referring)
|
|
140
|
-
counts[ref] = (counts[ref] ?? 0) + 1;
|
|
141
|
-
}
|
|
142
|
-
// The unique set of all known projects (referred and referring)
|
|
143
|
-
const uniques = Object.keys(counts);
|
|
144
|
-
// Projects are "ready" when all dependencies have been resolved (zero count)
|
|
145
|
-
const ready = uniques.filter((project) => counts[project] === 0);
|
|
146
|
-
// The final order of projects (in dependency order)
|
|
147
|
-
const order = [];
|
|
148
|
-
// While there are still projects ready to be processed, take the first one
|
|
149
|
-
for (let project = ready.shift(); project; project = ready.shift()) {
|
|
150
|
-
// Add the project to the final order
|
|
151
|
-
order.push(project);
|
|
152
|
-
// For each project that depends on this project, decrement its unresolved
|
|
153
|
-
// dependency count. If it reaches zero, add it to the ready queue.
|
|
154
|
-
for (const dependent of projects.get(project) ?? /* coverage ignore next */ []) {
|
|
155
|
-
const remaining = (counts[dependent] = counts[dependent] - 1);
|
|
156
|
-
if (remaining === 0)
|
|
157
|
-
ready.push(dependent);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
// When no more projects are "ready", the remaining projects are unresolved
|
|
161
|
-
const unresolved = uniques.filter((project) => counts[project]);
|
|
162
|
-
// If we have no unresolved projects, we can return the order (no cycles)
|
|
163
|
-
if (unresolved.length === 0)
|
|
164
|
-
return { order, unresolved, cycles: [], errors: [] };
|
|
165
|
-
// ===== FIND CYCLES =========================================================
|
|
166
|
-
// Projects we _already_ visited (and thus don't need to visit again)
|
|
167
|
-
const visited = new Set();
|
|
168
|
-
// The list of cycles we have found (each cycle is a list of projects)
|
|
169
|
-
const cycles = [];
|
|
170
|
-
// Recursively visit a project's dependents, looking for cycles
|
|
171
|
-
function visit(project, stack = []) {
|
|
172
|
-
// If this project is already in the current path, we found a cycle
|
|
173
|
-
const index = stack.indexOf(project);
|
|
174
|
-
if (index >= 0) {
|
|
175
|
-
cycles.push([...stack.slice(index), project]);
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
// Don't revisit projects whose dependents have all been processed
|
|
179
|
-
if (visited.has(project))
|
|
180
|
-
return;
|
|
181
|
-
// Add this project to the current path and visit its dependents
|
|
182
|
-
const next = [...stack, project];
|
|
183
|
-
for (const dependent of projects.get(project) ?? /* coverage ignore next */ []) {
|
|
184
|
-
if (unresolved.includes(dependent))
|
|
185
|
-
visit(dependent, next);
|
|
186
|
-
}
|
|
187
|
-
// Mark the project as visited after all its dependents have been processed
|
|
188
|
-
visited.add(project);
|
|
189
|
-
}
|
|
190
|
-
// Visit all the unresolved projects, looking for cycles
|
|
191
|
-
for (const project of unresolved)
|
|
192
|
-
visit(project);
|
|
193
|
-
// Prep our errors
|
|
194
|
-
const errors = [];
|
|
195
|
-
errors.push({
|
|
196
|
-
level: ERROR,
|
|
197
|
-
message: `Found ${$ylw(unresolved.length)} unresolved projects (${$ylw(cycles.length)} cycles)` +
|
|
198
|
-
`\n${$gry('-')} ${unresolved.map($p).join(`\n${$gry('-')} `)}`,
|
|
199
|
-
});
|
|
200
|
-
for (const cycle of cycles) {
|
|
201
|
-
errors.push({
|
|
202
|
-
level: ERROR,
|
|
203
|
-
message: `Project reference cycle:\n${$gry('-')} ${cycle.map($p).join(`\n${$gry('-')} `)}`,
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
// Return the final order, unresolved projects, and cycles found
|
|
207
|
-
return { order, unresolved, cycles, errors };
|
|
208
|
-
}
|
|
209
|
-
//# sourceMappingURL=projects.js.map
|
package/dist/projects.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../src/projects.ts"],"names":[],"mappings":"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"}
|
package/src/projects.ts
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
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
|
-
}
|