194 lines
5.4 KiB
TypeScript
194 lines
5.4 KiB
TypeScript
/**
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2016 Jonas Kello
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
/* eslint-disable */
|
|
|
|
import * as path from 'path';
|
|
import * as fs from 'fs';
|
|
import { json5 } from '../utilsBundle';
|
|
|
|
/**
|
|
* Typing for the parts of tsconfig that we care about
|
|
*/
|
|
interface Tsconfig {
|
|
extends?: string;
|
|
compilerOptions?: {
|
|
baseUrl?: string;
|
|
paths?: { [key: string]: Array<string> };
|
|
strict?: boolean;
|
|
allowJs?: boolean;
|
|
};
|
|
}
|
|
|
|
export interface TsConfigLoaderResult {
|
|
tsConfigPath: string | undefined;
|
|
baseUrl: string | undefined;
|
|
paths: { [key: string]: Array<string> } | undefined;
|
|
serialized: string | undefined;
|
|
allowJs: boolean;
|
|
}
|
|
|
|
export interface TsConfigLoaderParams {
|
|
cwd: string;
|
|
}
|
|
|
|
export function tsConfigLoader({
|
|
cwd,
|
|
}: TsConfigLoaderParams): TsConfigLoaderResult {
|
|
const loadResult = loadSyncDefault(cwd);
|
|
loadResult.serialized = JSON.stringify(loadResult);
|
|
return loadResult;
|
|
}
|
|
|
|
function loadSyncDefault(
|
|
cwd: string,
|
|
): TsConfigLoaderResult {
|
|
// Tsconfig.loadSync uses path.resolve. This is why we can use an absolute path as filename
|
|
|
|
const configPath = resolveConfigPath(cwd);
|
|
|
|
if (!configPath) {
|
|
return {
|
|
tsConfigPath: undefined,
|
|
baseUrl: undefined,
|
|
paths: undefined,
|
|
serialized: undefined,
|
|
allowJs: false,
|
|
};
|
|
}
|
|
const config = loadTsconfig(configPath);
|
|
|
|
return {
|
|
tsConfigPath: configPath,
|
|
baseUrl:
|
|
(config && config.compilerOptions && config.compilerOptions.baseUrl),
|
|
paths: config && config.compilerOptions && config.compilerOptions.paths,
|
|
serialized: undefined,
|
|
allowJs: !!config?.compilerOptions?.allowJs,
|
|
};
|
|
}
|
|
|
|
function resolveConfigPath(cwd: string): string | undefined {
|
|
if (fs.statSync(cwd).isFile()) {
|
|
return path.resolve(cwd);
|
|
}
|
|
|
|
const configAbsolutePath = walkForTsConfig(cwd);
|
|
return configAbsolutePath ? path.resolve(configAbsolutePath) : undefined;
|
|
}
|
|
|
|
export function walkForTsConfig(
|
|
directory: string,
|
|
existsSync: (path: string) => boolean = fs.existsSync
|
|
): string | undefined {
|
|
const configPath = path.join(directory, "./tsconfig.json");
|
|
if (existsSync(configPath)) {
|
|
return configPath;
|
|
}
|
|
|
|
const parentDirectory = path.join(directory, "../");
|
|
|
|
// If we reached the top
|
|
if (directory === parentDirectory) {
|
|
return undefined;
|
|
}
|
|
|
|
return walkForTsConfig(parentDirectory, existsSync);
|
|
}
|
|
|
|
function loadTsconfig(
|
|
configFilePath: string,
|
|
existsSync: (path: string) => boolean = fs.existsSync,
|
|
readFileSync: (filename: string) => string = (filename: string) =>
|
|
fs.readFileSync(filename, "utf8")
|
|
): Tsconfig | undefined {
|
|
if (!existsSync(configFilePath)) {
|
|
return undefined;
|
|
}
|
|
|
|
const configString = readFileSync(configFilePath);
|
|
const cleanedJson = StripBom(configString);
|
|
const config: Tsconfig = json5.parse(cleanedJson);
|
|
let extendedConfig = config.extends;
|
|
|
|
if (extendedConfig) {
|
|
if (
|
|
typeof extendedConfig === "string" &&
|
|
extendedConfig.indexOf(".json") === -1
|
|
) {
|
|
extendedConfig += ".json";
|
|
}
|
|
const currentDir = path.dirname(configFilePath);
|
|
let extendedConfigPath = path.join(currentDir, extendedConfig);
|
|
if (
|
|
extendedConfig.indexOf("/") !== -1 &&
|
|
extendedConfig.indexOf(".") !== -1 &&
|
|
!existsSync(extendedConfigPath)
|
|
) {
|
|
extendedConfigPath = path.join(
|
|
currentDir,
|
|
"node_modules",
|
|
extendedConfig
|
|
);
|
|
}
|
|
|
|
const base =
|
|
loadTsconfig(extendedConfigPath, existsSync, readFileSync) || {};
|
|
|
|
// baseUrl should be interpreted as relative to the base tsconfig,
|
|
// but we need to update it so it is relative to the original tsconfig being loaded
|
|
if (base.compilerOptions && base.compilerOptions.baseUrl) {
|
|
const extendsDir = path.dirname(extendedConfig);
|
|
base.compilerOptions.baseUrl = path.join(
|
|
extendsDir,
|
|
base.compilerOptions.baseUrl
|
|
);
|
|
}
|
|
|
|
return {
|
|
...base,
|
|
...config,
|
|
compilerOptions: {
|
|
...base.compilerOptions,
|
|
...config.compilerOptions,
|
|
},
|
|
};
|
|
}
|
|
return config;
|
|
}
|
|
|
|
function StripBom(string: string) {
|
|
if (typeof string !== 'string') {
|
|
throw new TypeError(`Expected a string, got ${typeof string}`);
|
|
}
|
|
|
|
// Catches EFBBBF (UTF-8 BOM) because the buffer-to-string
|
|
// conversion translates it to FEFF (UTF-16 BOM).
|
|
if (string.charCodeAt(0) === 0xFEFF) {
|
|
return string.slice(1);
|
|
}
|
|
|
|
return string;
|
|
}
|