Skip to content
Permalink
Newer
Older
100644 172 lines (149 sloc) 4.49 KB
Ignoring revisions in .git-blame-ignore-revs.
1
import * as path from "path";
2
import * as fs from "fs";
3
// tslint:disable:no-require-imports
4
import JSON5 = require("json5");
5
import StripBom = require("strip-bom");
6
// tslint:enable:no-require-imports
7
8
/**
9
* Typing for the parts of tsconfig that we care about
10
*/
11
export interface Tsconfig {
12
extends?: string;
13
compilerOptions?: {
14
baseUrl?: string;
15
paths?: { [key: string]: Array<string> };
16
strict?: boolean;
17
};
18
}
19
20
export interface TsConfigLoaderResult {
21
tsConfigPath: string | undefined;
22
baseUrl: string | undefined;
23
paths: { [key: string]: Array<string> } | undefined;
24
}
25
26
export interface TsConfigLoaderParams {
27
getEnv: (key: string) => string | undefined;
28
cwd: string;
29
loadSync?(
30
cwd: string,
31
filename?: string,
32
baseUrl?: string
33
): TsConfigLoaderResult;
34
}
35
36
export function tsConfigLoader({
37
getEnv,
38
cwd,
39
loadSync = loadSyncDefault,
40
}: TsConfigLoaderParams): TsConfigLoaderResult {
41
const TS_NODE_PROJECT = getEnv("TS_NODE_PROJECT");
42
const TS_NODE_BASEURL = getEnv("TS_NODE_BASEURL");
43
44
// tsconfig.loadSync handles if TS_NODE_PROJECT is a file or directory
45
// and also overrides baseURL if TS_NODE_BASEURL is available.
46
const loadResult = loadSync(cwd, TS_NODE_PROJECT, TS_NODE_BASEURL);
47
return loadResult;
48
}
49
50
function loadSyncDefault(
51
cwd: string,
52
filename?: string,
53
baseUrl?: string
54
): TsConfigLoaderResult {
55
// Tsconfig.loadSync uses path.resolve. This is why we can use an absolute path as filename
56
57
const configPath = resolveConfigPath(cwd, filename);
58
59
if (!configPath) {
60
return {
61
tsConfigPath: undefined,
62
baseUrl: undefined,
63
paths: undefined,
64
};
65
}
66
const config = loadTsconfig(configPath);
67
68
return {
69
tsConfigPath: configPath,
70
baseUrl:
71
baseUrl ||
72
(config && config.compilerOptions && config.compilerOptions.baseUrl),
73
paths: config && config.compilerOptions && config.compilerOptions.paths,
74
};
75
}
76
77
function resolveConfigPath(cwd: string, filename?: string): string | undefined {
78
if (filename) {
79
const absolutePath = fs.lstatSync(filename).isDirectory()
80
? path.resolve(filename, "./tsconfig.json")
81
: path.resolve(cwd, filename);
82
83
return absolutePath;
84
}
85
86
if (fs.statSync(cwd).isFile()) {
87
return path.resolve(cwd);
88
}
89
90
const configAbsolutePath = walkForTsConfig(cwd);
91
return configAbsolutePath ? path.resolve(configAbsolutePath) : undefined;
92
}
93
94
export function walkForTsConfig(
95
directory: string,
96
existsSync: (path: string) => boolean = fs.existsSync
97
): string | undefined {
98
const configPath = path.join(directory, "./tsconfig.json");
99
if (existsSync(configPath)) {
100
return configPath;
101
}
102
103
const parentDirectory = path.join(directory, "../");
104
105
// If we reached the top
106
if (directory === parentDirectory) {
107
return undefined;
108
}
109
110
return walkForTsConfig(parentDirectory, existsSync);
111
}
112
113
export function loadTsconfig(
114
configFilePath: string,
115
existsSync: (path: string) => boolean = fs.existsSync,
116
readFileSync: (filename: string) => string = (filename: string) =>
117
fs.readFileSync(filename, "utf8")
118
): Tsconfig | undefined {
119
if (!existsSync(configFilePath)) {
120
return undefined;
121
}
122
123
const configString = readFileSync(configFilePath);
124
const cleanedJson = StripBom(configString);
125
const config: Tsconfig = JSON5.parse(cleanedJson);
126
let extendedConfig = config.extends;
127
128
if (extendedConfig) {
129
if (
130
typeof extendedConfig === "string" &&
131
extendedConfig.indexOf(".json") === -1
132
) {
133
extendedConfig += ".json";
134
}
135
const currentDir = path.dirname(configFilePath);
136
let extendedConfigPath = path.join(currentDir, extendedConfig);
137
if (
138
extendedConfig.indexOf("/") !== -1 &&
139
extendedConfig.indexOf(".") !== -1 &&
140
!existsSync(extendedConfigPath)
141
) {
142
extendedConfigPath = path.join(
143
currentDir,
144
"node_modules",
145
extendedConfig
146
);
147
}
148
149
const base =
150
loadTsconfig(extendedConfigPath, existsSync, readFileSync) || {};
151
152
// baseUrl should be interpreted as relative to the base tsconfig,
153
// but we need to update it so it is relative to the original tsconfig being loaded
154
if (base.compilerOptions && base.compilerOptions.baseUrl) {
155
const extendsDir = path.dirname(extendedConfig);
156
base.compilerOptions.baseUrl = path.join(
157
extendsDir,
158
base.compilerOptions.baseUrl
159
);
160
}
161
162
return {
163
...base,
164
...config,
165
compilerOptions: {
166
...base.compilerOptions,
167
...config.compilerOptions,
168
},
169
};
170
}
171
return config;
172
}