Refactor routing in App component to enhance navigation and improve error handling by integrating dynamic routes and updating the NotFound route.

This commit is contained in:
becarta
2025-05-23 12:43:00 +02:00
parent f40db0f5c9
commit a544759a3b
11127 changed files with 1647032 additions and 0 deletions

129
node_modules/tsconfck/src/cache.js generated vendored Normal file
View File

@@ -0,0 +1,129 @@
/** @template T */
export class TSConfckCache {
/**
* clear cache, use this if you have a long running process and tsconfig files have been added,changed or deleted
*/
clear() {
this.#configPaths.clear();
this.#parsed.clear();
}
/**
* has cached closest config for files in dir
* @param {string} dir
* @param {string} [configName=tsconfig.json]
* @returns {boolean}
*/
hasConfigPath(dir, configName = 'tsconfig.json') {
return this.#configPaths.has(`${dir}/${configName}`);
}
/**
* get cached closest tsconfig for files in dir
* @param {string} dir
* @param {string} [configName=tsconfig.json]
* @returns {Promise<string|null>|string|null}
* @throws {unknown} if cached value is an error
*/
getConfigPath(dir, configName = 'tsconfig.json') {
const key = `${dir}/${configName}`;
const value = this.#configPaths.get(key);
if (value == null || value.length || value.then) {
return value;
} else {
throw value;
}
}
/**
* has parsed tsconfig for file
* @param {string} file
* @returns {boolean}
*/
hasParseResult(file) {
return this.#parsed.has(file);
}
/**
* get parsed tsconfig for file
* @param {string} file
* @returns {Promise<T>|T}
* @throws {unknown} if cached value is an error
*/
getParseResult(file) {
const value = this.#parsed.get(file);
if (value.then || value.tsconfig) {
return value;
} else {
throw value; // cached error, rethrow
}
}
/**
* @internal
* @private
* @param file
* @param {boolean} isRootFile a flag to check if current file which involking the parse() api, used to distinguish the normal cache which only parsed by parseFile()
* @param {Promise<T>} result
*/
setParseResult(file, result, isRootFile = false) {
// _isRootFile_ is a temporary property for Promise result, used to prevent deadlock with cache
Object.defineProperty(result, '_isRootFile_', {
value: isRootFile,
writable: false,
enumerable: false,
configurable: false
});
this.#parsed.set(file, result);
result
.then((parsed) => {
if (this.#parsed.get(file) === result) {
this.#parsed.set(file, parsed);
}
})
.catch((e) => {
if (this.#parsed.get(file) === result) {
this.#parsed.set(file, e);
}
});
}
/**
* @internal
* @private
* @param {string} dir
* @param {Promise<string|null>} configPath
* @param {string} [configName=tsconfig.json]
*/
setConfigPath(dir, configPath, configName = 'tsconfig.json') {
const key = `${dir}/${configName}`;
this.#configPaths.set(key, configPath);
configPath
.then((path) => {
if (this.#configPaths.get(key) === configPath) {
this.#configPaths.set(key, path);
}
})
.catch((e) => {
if (this.#configPaths.get(key) === configPath) {
this.#configPaths.set(key, e);
}
});
}
/**
* map directories to their closest tsconfig.json
* @internal
* @private
* @type{Map<string,(Promise<string|null>|string|null)>}
*/
#configPaths = new Map();
/**
* map files to their parsed tsconfig result
* @internal
* @private
* @type {Map<string,(Promise<T>|T)> }
*/
#parsed = new Map();
}

71
node_modules/tsconfck/src/find-all.js generated vendored Normal file
View File

@@ -0,0 +1,71 @@
import path from 'node:path';
import { readdir } from 'node:fs';
/**
* @typedef WalkState
* @interface
* @property {string[]} files - files
* @property {number} calls - number of ongoing calls
* @property {(dir: string)=>boolean} skip - function to skip dirs
* @property {boolean} err - error flag
* @property {string[]} configNames - config file names
*/
const sep = path.sep;
/**
* find all tsconfig.json files in dir
*
* @param {string} dir - path to dir (absolute or relative to cwd)
* @param {import('./public.d.ts').TSConfckFindAllOptions} [options] - options
* @returns {Promise<string[]>} list of absolute paths to all found tsconfig.json files
*/
export async function findAll(dir, options) {
/** @type WalkState */
const state = {
files: [],
calls: 0,
skip: options?.skip,
err: false,
configNames: options?.configNames ?? ['tsconfig.json']
};
return new Promise((resolve, reject) => {
walk(path.resolve(dir), state, (err, files) => (err ? reject(err) : resolve(files)));
});
}
/**
*
* @param {string} dir
* @param {WalkState} state
* @param {(err: NodeJS.ErrnoException | null, files?: string[]) => void} done
*/
function walk(dir, state, done) {
if (state.err) {
return;
}
state.calls++;
readdir(dir, { withFileTypes: true }, (err, entries = []) => {
if (state.err) {
return;
}
// skip deleted or inaccessible directories
if (err && !(err.code === 'ENOENT' || err.code === 'EACCES' || err.code === 'EPERM')) {
state.err = true;
done(err);
} else {
for (const ent of entries) {
if (ent.isDirectory() && !state.skip?.(ent.name)) {
walk(`${dir}${sep}${ent.name}`, state, done);
} else if (ent.isFile() && state.configNames.includes(ent.name)) {
state.files.push(`${dir}${sep}${ent.name}`);
}
}
if (--state.calls === 0) {
if (!state.err) {
done(null, state.files);
}
}
}
});
}

70
node_modules/tsconfck/src/find-native.js generated vendored Normal file
View File

@@ -0,0 +1,70 @@
import path from 'node:path';
import { isInNodeModules, loadTS, native2posix } from './util.js';
/**
* find the closest tsconfig.json file using native ts.findConfigFile
*
* You must have `typescript` installed to use this
*
* @param {string} filename - path to file to find tsconfig for (absolute or relative to cwd)
* @param {import('./public.d.ts').TSConfckFindOptions} [options] - options
* @returns {Promise<string>} absolute path to closest tsconfig.json
*/
export async function findNative(filename, options) {
let dir = native2posix(path.dirname(path.resolve(filename)));
if (options?.ignoreNodeModules && isInNodeModules(dir)) {
return null;
}
const cache = options?.cache;
const root = options?.root ? native2posix(path.resolve(options.root)) : undefined;
const configName = options?.configName ?? 'tsconfig.json';
if (cache?.hasConfigPath(dir, configName)) {
return cache.getConfigPath(dir, configName);
}
const ts = await loadTS();
const { findConfigFile, sys } = ts;
let tsconfigFile = findConfigFile(dir, sys.fileExists, configName);
if (!tsconfigFile || is_out_of_root(tsconfigFile, root)) {
tsconfigFile = null;
}
if (cache) {
cache_result(tsconfigFile, dir, cache, root, configName);
}
return tsconfigFile;
}
/**
*
* @param {string} tsconfigFile
* @param {string} root
*/
function is_out_of_root(tsconfigFile, root) {
return root && !tsconfigFile.startsWith(root);
}
/**
* add all intermediate directories between fileDir and tsconfigFile to cache
* if no tsconfig was found, go up until root
* @param {string|null} tsconfigFile
* @param {string} fileDir
* @param {import('./cache.js').TSConfckCache} cache
* @param {string|undefined} root
* @param {string} configName
*/
function cache_result(tsconfigFile, fileDir, cache, root, configName) {
const tsconfigDir = tsconfigFile ? path.dirname(tsconfigFile) : root;
const directories = [];
let dir = fileDir;
while (dir) {
directories.push(dir);
const parent = path.dirname(dir);
if (tsconfigDir === dir || parent === dir) {
break;
} else {
dir = parent;
}
}
directories.forEach((d) => {
cache.setConfigPath(d, Promise.resolve(tsconfigFile), configName);
});
}

70
node_modules/tsconfck/src/find.js generated vendored Normal file
View File

@@ -0,0 +1,70 @@
import path from 'node:path';
import fs from 'node:fs';
import { isInNodeModules, makePromise } from './util.js';
/**
* find the closest tsconfig.json file
*
* @param {string} filename - path to file to find tsconfig for (absolute or relative to cwd)
* @param {import('./public.d.ts').TSConfckFindOptions} [options] - options
* @returns {Promise<string|null>} absolute path to closest tsconfig.json or null if not found
*/
export async function find(filename, options) {
let dir = path.dirname(path.resolve(filename));
if (options?.ignoreNodeModules && isInNodeModules(dir)) {
return null;
}
const cache = options?.cache;
const configName = options?.configName ?? 'tsconfig.json';
if (cache?.hasConfigPath(dir, configName)) {
return cache.getConfigPath(dir, configName);
}
const { /** @type {Promise<string|null>} */ promise, resolve, reject } = makePromise();
if (options?.root && !path.isAbsolute(options.root)) {
options.root = path.resolve(options.root);
}
findUp(dir, { promise, resolve, reject }, options);
return promise;
}
/**
*
* @param {string} dir
* @param {{promise:Promise<string|null>,resolve:(result:string|null)=>void,reject:(err:any)=>void}} madePromise
* @param {import('./public.d.ts').TSConfckFindOptions} [options] - options
*/
function findUp(dir, { resolve, reject, promise }, options) {
const { cache, root, configName } = options ?? {};
if (cache) {
if (cache.hasConfigPath(dir, configName)) {
let cached;
try {
cached = cache.getConfigPath(dir, configName);
} catch (e) {
reject(e);
return;
}
if (cached?.then) {
cached.then(resolve).catch(reject);
} else {
resolve(cached);
}
} else {
cache.setConfigPath(dir, promise, configName);
}
}
const tsconfig = path.join(dir, options?.configName ?? 'tsconfig.json');
fs.stat(tsconfig, (err, stats) => {
if (stats && (stats.isFile() || stats.isFIFO())) {
resolve(tsconfig);
} else if (err?.code !== 'ENOENT') {
reject(err);
} else {
let parent;
if (root === dir || (parent = path.dirname(dir)) === dir) {
resolve(null);
} else {
findUp(parent, { promise, resolve, reject }, options);
}
}
});
}

7
node_modules/tsconfck/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,7 @@
export { find } from './find.js';
export { findAll } from './find-all.js';
export { toJson } from './to-json.js';
export { parse, TSConfckParseError } from './parse.js';
export { findNative } from './find-native.js';
export { parseNative, TSConfckParseNativeError } from './parse-native.js';
export { TSConfckCache } from './cache.js';

305
node_modules/tsconfck/src/parse-native.js generated vendored Normal file
View File

@@ -0,0 +1,305 @@
import path from 'node:path';
import {
makePromise,
loadTS,
native2posix,
resolveReferencedTSConfigFiles,
resolveSolutionTSConfig,
resolveTSConfigJson,
replaceTokens
} from './util.js';
import { findNative } from './find-native.js';
const notFoundResult = {
tsconfigFile: null,
tsconfig: {},
result: null
};
/**
* parse the closest tsconfig.json file with typescript native functions
*
* You need to have `typescript` installed to use this
*
* @param {string} filename - path to a tsconfig .json or a source file (absolute or relative to cwd)
* @param {import('./public.d.ts').TSConfckParseNativeOptions} [options] - options
* @returns {Promise<import('./public.d.ts').TSConfckParseNativeResult>}
* @throws {TSConfckParseNativeError}
*/
export async function parseNative(filename, options) {
/** @type {import('./cache.js').TSConfckCache} */
const cache = options?.cache;
if (cache?.hasParseResult(filename)) {
return cache.getParseResult(filename);
}
const {
resolve,
reject,
/** @type {Promise<import('./public.d.ts').TSConfckParseNativeResult>}*/
promise
} = makePromise();
cache?.setParseResult(filename, promise);
try {
const tsconfigFile =
(await resolveTSConfigJson(filename, cache)) || (await findNative(filename, options));
if (!tsconfigFile) {
resolve(notFoundResult);
return promise;
}
/** @type {import('./public.d.ts').TSConfckParseNativeResult} */
let result;
if (filename !== tsconfigFile && cache?.hasParseResult(tsconfigFile)) {
result = await cache.getParseResult(tsconfigFile);
} else {
const ts = await loadTS();
result = await parseFile(tsconfigFile, ts, options, filename === tsconfigFile);
await parseReferences(result, ts, options);
cache?.setParseResult(tsconfigFile, Promise.resolve(result));
}
resolve(resolveSolutionTSConfig(filename, result));
return promise;
} catch (e) {
reject(e);
return promise;
}
}
/**
*
* @param {string} tsconfigFile
* @param {any} ts
* @param {import('./public.d.ts').TSConfckParseNativeOptions} [options]
* @param {boolean} [skipCache]
* @returns {import('./public.d.ts').TSConfckParseNativeResult}
*/
function parseFile(tsconfigFile, ts, options, skipCache) {
const cache = options?.cache;
if (!skipCache && cache?.hasParseResult(tsconfigFile)) {
return cache.getParseResult(tsconfigFile);
}
const posixTSConfigFile = native2posix(tsconfigFile);
const { parseJsonConfigFileContent, readConfigFile, sys } = ts;
const { config, error } = readConfigFile(posixTSConfigFile, sys.readFile);
if (error) {
throw new TSConfckParseNativeError(error, tsconfigFile, null);
}
const host = {
useCaseSensitiveFileNames: false,
readDirectory: sys.readDirectory,
fileExists: sys.fileExists,
readFile: sys.readFile
};
if (options?.ignoreSourceFiles) {
config.files = [];
config.include = [];
}
const nativeResult = parseJsonConfigFileContent(
config,
host,
path.dirname(posixTSConfigFile),
undefined,
posixTSConfigFile
);
checkErrors(nativeResult, tsconfigFile);
/** @type {import('./public.d.ts').TSConfckParseNativeResult} */
const result = {
tsconfigFile,
tsconfig: result2tsconfig(nativeResult, ts, tsconfigFile),
result: nativeResult
};
if (!skipCache) {
cache?.setParseResult(tsconfigFile, Promise.resolve(result));
}
return result;
}
/**
*
* @param {import('./public.d.ts').TSConfckParseNativeResult} result
* @param {any} ts
* @param {import('./public.d.ts').TSConfckParseNativeOptions} [options]
*/
async function parseReferences(result, ts, options) {
if (!result.tsconfig.references) {
return;
}
const referencedFiles = resolveReferencedTSConfigFiles(result, options);
result.referenced = await Promise.all(
referencedFiles.map((file) => parseFile(file, ts, options))
);
result.referenced.forEach((ref) => (ref.solution = result));
}
/**
* check errors reported by parseJsonConfigFileContent
*
* ignores errors related to missing input files as these may happen regularly in programmatic use
* and do not affect the config itself
*
* @param {any} nativeResult - native typescript parse result to check for errors
* @param {string} tsconfigFile
* @throws {TSConfckParseNativeError} for critical error
*/
function checkErrors(nativeResult, tsconfigFile) {
const ignoredErrorCodes = [
// see https://github.com/microsoft/TypeScript/blob/main/src/compiler/diagnosticMessages.json
18002, // empty files list
18003 // no inputs
];
const criticalError = nativeResult.errors?.find(
(error) => error.category === 1 && !ignoredErrorCodes.includes(error.code)
);
if (criticalError) {
throw new TSConfckParseNativeError(criticalError, tsconfigFile, nativeResult);
}
}
/**
* convert the result of `parseJsonConfigFileContent` to a tsconfig that can be parsed again
*
* - use merged compilerOptions
* - strip prefix and postfix of compilerOptions.lib
* - convert enum values back to string
*
* @param {any} result
* @param {any} ts typescript
* @param {string} tsconfigFile
* @returns {object} tsconfig with merged compilerOptions and enums restored to their string form
*/
function result2tsconfig(result, ts, tsconfigFile) {
// dereference result.raw so changes below don't modify original
const tsconfig = JSON.parse(JSON.stringify(result.raw));
// for some reason the extended compilerOptions are not available in result.raw but only in result.options
// and contain an extra fields 'configFilePath' and 'pathsBasePath'. Use everything but those 2
const ignoredOptions = ['configFilePath', 'pathsBasePath'];
if (result.options && Object.keys(result.options).some((o) => !ignoredOptions.includes(o))) {
tsconfig.compilerOptions = {
...result.options
};
for (const ignored of ignoredOptions) {
delete tsconfig.compilerOptions[ignored];
}
}
const compilerOptions = tsconfig.compilerOptions;
if (compilerOptions) {
if (compilerOptions.lib != null) {
// remove lib. and .dts from lib.es2019.d.ts etc
compilerOptions.lib = compilerOptions.lib.map((x) =>
x.replace(/^lib\./, '').replace(/\.d\.ts$/, '')
);
}
const enumProperties = [
{ name: 'importsNotUsedAsValues', enumeration: ts.ImportsNotUsedAsValues },
{ name: 'module', enumeration: ts.ModuleKind },
{
name: 'moduleResolution',
enumeration: {
...ts.ModuleResolutionKind,
2: 'node' /*ts.ModuleResolutionKind uses "Node10" but in tsconfig it is just node"*/
}
},
{
name: 'newLine',
enumeration: { 0: 'crlf', 1: 'lf' } /*ts.NewLineKind uses different names*/
},
{ name: 'target', enumeration: ts.ScriptTarget }
];
for (const prop of enumProperties) {
if (compilerOptions[prop.name] != null && typeof compilerOptions[prop.name] === 'number') {
compilerOptions[prop.name] = prop.enumeration[compilerOptions[prop.name]].toLowerCase();
}
}
if (compilerOptions.target === 'latest') {
compilerOptions.target = 'esnext'; // why ts, why?
}
}
// merged watchOptions
if (result.watchOptions) {
tsconfig.watchOptions = {
...result.watchOptions
};
}
const watchOptions = tsconfig.watchOptions;
if (watchOptions) {
const enumProperties = [
{ name: 'watchFile', enumeration: ts.WatchFileKind },
{ name: 'watchDirectory', enumeration: ts.WatchDirectoryKind },
{ name: 'fallbackPolling', enumeration: ts.PollingWatchKind }
];
for (const prop of enumProperties) {
if (watchOptions[prop.name] != null && typeof watchOptions[prop.name] === 'number') {
const enumVal = prop.enumeration[watchOptions[prop.name]];
watchOptions[prop.name] = enumVal.charAt(0).toLowerCase() + enumVal.slice(1);
}
}
}
if (tsconfig.compileOnSave === false) {
// ts adds this property even if it isn't present in the actual config
// delete if it is false to match content of tsconfig
delete tsconfig.compileOnSave;
}
// ts itself has not replaced all tokens at this point, make sure they are
const parseResult = { tsconfig, tsconfigFile };
replaceTokens(parseResult);
return parseResult.tsconfig;
}
export class TSConfckParseNativeError extends Error {
/**
*
* @param {TSDiagnosticError} diagnostic - diagnostics of ts
* @param {string} tsconfigFile - file that errored
* @param {any?} result - parsed result, if any
*/
constructor(diagnostic, tsconfigFile, result) {
super(diagnostic.messageText);
// Set the prototype explicitly.
Object.setPrototypeOf(this, TSConfckParseNativeError.prototype);
this.name = TSConfckParseNativeError.name;
this.code = `TS ${diagnostic.code}`;
this.diagnostic = diagnostic;
this.result = result;
this.tsconfigFile = tsconfigFile;
}
/**
* code of typescript diagnostic, prefixed with "TS "
* @type {string}
*/
code;
/**
* full ts diagnostic that caused this error
* @type {TSDiagnosticError}
*/
diagnostic;
/**
* absolute path of tsconfig file where the error happened
* @type {string}
*/
tsconfigFile;
/**
* native result if present, contains all errors in result.errors
* @type {any|undefined}
*/
result;
}
/** @typedef TSDiagnosticError {
code: number;
category: number;
messageText: string;
start?: number;
} TSDiagnosticError */

441
node_modules/tsconfck/src/parse.js generated vendored Normal file
View File

@@ -0,0 +1,441 @@
import path from 'node:path';
import { promises as fs } from 'node:fs';
import { createRequire } from 'module';
import { find } from './find.js';
import { toJson } from './to-json.js';
import {
makePromise,
native2posix,
resolve2posix,
replaceTokens,
resolveReferencedTSConfigFiles,
resolveSolutionTSConfig,
resolveTSConfigJson
} from './util.js';
const not_found_result = {
tsconfigFile: null,
tsconfig: {}
};
/**
* parse the closest tsconfig.json file
*
* @param {string} filename - path to a tsconfig .json or a source file or directory (absolute or relative to cwd)
* @param {import('./public.d.ts').TSConfckParseOptions} [options] - options
* @returns {Promise<import('./public.d.ts').TSConfckParseResult>}
* @throws {TSConfckParseError}
*/
export async function parse(filename, options) {
/** @type {import('./cache.js').TSConfckCache} */
const cache = options?.cache;
if (cache?.hasParseResult(filename)) {
return getParsedDeep(filename, cache, options);
}
const {
resolve,
reject,
/** @type {Promise<import('./public.d.ts').TSConfckParseResult>}*/
promise
} = makePromise();
cache?.setParseResult(filename, promise, true);
try {
let tsconfigFile =
(await resolveTSConfigJson(filename, cache)) || (await find(filename, options));
if (!tsconfigFile) {
resolve(not_found_result);
return promise;
}
let result;
if (filename !== tsconfigFile && cache?.hasParseResult(tsconfigFile)) {
result = await getParsedDeep(tsconfigFile, cache, options);
} else {
result = await parseFile(tsconfigFile, cache, filename === tsconfigFile);
await Promise.all([parseExtends(result, cache), parseReferences(result, options)]);
}
replaceTokens(result);
resolve(resolveSolutionTSConfig(filename, result));
} catch (e) {
reject(e);
}
return promise;
}
/**
* ensure extends and references are parsed
*
* @param {string} filename - cached file
* @param {import('./cache.js').TSConfckCache} cache - cache
* @param {import('./public.d.ts').TSConfckParseOptions} options - options
*/
async function getParsedDeep(filename, cache, options) {
const result = await cache.getParseResult(filename);
if (
(result.tsconfig.extends && !result.extended) ||
(result.tsconfig.references && !result.referenced)
) {
const promise = Promise.all([
parseExtends(result, cache),
parseReferences(result, options)
]).then(() => result);
cache.setParseResult(filename, promise, true);
return promise;
}
return result;
}
/**
*
* @param {string} tsconfigFile - path to tsconfig file
* @param {import('./cache.js').TSConfckCache} [cache] - cache
* @param {boolean} [skipCache] - skip cache
* @returns {Promise<import('./public.d.ts').TSConfckParseResult>}
*/
async function parseFile(tsconfigFile, cache, skipCache) {
if (
!skipCache &&
cache?.hasParseResult(tsconfigFile) &&
!cache.getParseResult(tsconfigFile)._isRootFile_
) {
return cache.getParseResult(tsconfigFile);
}
const promise = fs
.readFile(tsconfigFile, 'utf-8')
.then(toJson)
.then((json) => {
const parsed = JSON.parse(json);
applyDefaults(parsed, tsconfigFile);
return {
tsconfigFile,
tsconfig: normalizeTSConfig(parsed, path.dirname(tsconfigFile))
};
})
.catch((e) => {
throw new TSConfckParseError(
`parsing ${tsconfigFile} failed: ${e}`,
'PARSE_FILE',
tsconfigFile,
e
);
});
if (
!skipCache &&
(!cache?.hasParseResult(tsconfigFile) || !cache.getParseResult(tsconfigFile)._isRootFile_)
) {
cache?.setParseResult(tsconfigFile, promise);
}
return promise;
}
/**
* normalize to match the output of ts.parseJsonConfigFileContent
*
* @param {any} tsconfig - typescript tsconfig output
* @param {string} dir - directory
*/
function normalizeTSConfig(tsconfig, dir) {
// set baseUrl to absolute path
const baseUrl = tsconfig.compilerOptions?.baseUrl;
if (baseUrl && !baseUrl.startsWith('${') && !path.isAbsolute(baseUrl)) {
tsconfig.compilerOptions.baseUrl = resolve2posix(dir, baseUrl);
}
return tsconfig;
}
/**
*
* @param {import('./public.d.ts').TSConfckParseResult} result
* @param {import('./public.d.ts').TSConfckParseOptions} [options]
* @returns {Promise<void>}
*/
async function parseReferences(result, options) {
if (!result.tsconfig.references) {
return;
}
const referencedFiles = resolveReferencedTSConfigFiles(result, options);
const referenced = await Promise.all(
referencedFiles.map((file) => parseFile(file, options?.cache))
);
await Promise.all(referenced.map((ref) => parseExtends(ref, options?.cache)));
referenced.forEach((ref) => {
ref.solution = result;
replaceTokens(ref);
});
result.referenced = referenced;
}
/**
* @param {import('./public.d.ts').TSConfckParseResult} result
* @param {import('./cache.js').TSConfckCache}[cache]
* @returns {Promise<void>}
*/
async function parseExtends(result, cache) {
if (!result.tsconfig.extends) {
return;
}
// use result as first element in extended
// but dereference tsconfig so that mergeExtended can modify the original without affecting extended[0]
/** @type {import('./public.d.ts').TSConfckParseResult[]} */
const extended = [
{ tsconfigFile: result.tsconfigFile, tsconfig: JSON.parse(JSON.stringify(result.tsconfig)) }
];
// flatten extends graph into extended
let pos = 0;
/** @type {string[]} */
const extendsPath = [];
let currentBranchDepth = 0;
while (pos < extended.length) {
const extending = extended[pos];
extendsPath.push(extending.tsconfigFile);
if (extending.tsconfig.extends) {
// keep following this branch
currentBranchDepth += 1;
/** @type {string[]} */
let resolvedExtends;
if (!Array.isArray(extending.tsconfig.extends)) {
resolvedExtends = [resolveExtends(extending.tsconfig.extends, extending.tsconfigFile)];
} else {
// reverse because typescript 5.0 treats ['a','b','c'] as c extends b extends a
resolvedExtends = extending.tsconfig.extends
.reverse()
.map((ex) => resolveExtends(ex, extending.tsconfigFile));
}
const circularExtends = resolvedExtends.find((tsconfigFile) =>
extendsPath.includes(tsconfigFile)
);
if (circularExtends) {
const circle = extendsPath.concat([circularExtends]).join(' -> ');
throw new TSConfckParseError(
`Circular dependency in "extends": ${circle}`,
'EXTENDS_CIRCULAR',
result.tsconfigFile
);
}
// add new extends to the list directly after current
extended.splice(
pos + 1,
0,
...(await Promise.all(resolvedExtends.map((file) => parseFile(file, cache))))
);
} else {
// reached a leaf, backtrack to the last branching point and continue
extendsPath.splice(-currentBranchDepth);
currentBranchDepth = 0;
}
pos = pos + 1;
}
result.extended = extended;
// skip first as it is the original config
for (const ext of result.extended.slice(1)) {
extendTSConfig(result, ext);
}
}
/**
*
* @param {string} extended
* @param {string} from
* @returns {string}
*/
function resolveExtends(extended, from) {
// see #149 and #220
if (['.', '..'].includes(extended)) {
extended = extended + '/tsconfig.json';
}
const req = createRequire(from);
let error;
try {
return req.resolve(extended);
} catch (e) {
error = e;
}
if (extended[0] !== '.' && !path.isAbsolute(extended)) {
try {
return req.resolve(`${extended}/tsconfig.json`);
} catch (e) {
error = e;
}
}
throw new TSConfckParseError(
`failed to resolve "extends":"${extended}" in ${from}`,
'EXTENDS_RESOLVE',
from,
error
);
}
// references, extends and custom keys are not carried over
const EXTENDABLE_KEYS = [
'compilerOptions',
'files',
'include',
'exclude',
'watchOptions',
'compileOnSave',
'typeAcquisition',
'buildOptions'
];
/**
*
* @param {import('./public.d.ts').TSConfckParseResult} extending
* @param {import('./public.d.ts').TSConfckParseResult} extended
* @returns void
*/
function extendTSConfig(extending, extended) {
const extendingConfig = extending.tsconfig;
const extendedConfig = extended.tsconfig;
const relativePath = native2posix(
path.relative(path.dirname(extending.tsconfigFile), path.dirname(extended.tsconfigFile))
);
for (const key of Object.keys(extendedConfig).filter((key) => EXTENDABLE_KEYS.includes(key))) {
if (key === 'compilerOptions') {
if (!extendingConfig.compilerOptions) {
extendingConfig.compilerOptions = {};
}
for (const option of Object.keys(extendedConfig.compilerOptions)) {
if (Object.prototype.hasOwnProperty.call(extendingConfig.compilerOptions, option)) {
continue; // already set
}
extendingConfig.compilerOptions[option] = rebaseRelative(
option,
extendedConfig.compilerOptions[option],
relativePath
);
}
} else if (extendingConfig[key] === undefined) {
if (key === 'watchOptions') {
extendingConfig.watchOptions = {};
for (const option of Object.keys(extendedConfig.watchOptions)) {
extendingConfig.watchOptions[option] = rebaseRelative(
option,
extendedConfig.watchOptions[option],
relativePath
);
}
} else {
extendingConfig[key] = rebaseRelative(key, extendedConfig[key], relativePath);
}
}
}
}
const REBASE_KEYS = [
// root
'files',
'include',
'exclude',
// compilerOptions
'baseUrl',
'rootDir',
'rootDirs',
'typeRoots',
'outDir',
'outFile',
'declarationDir',
// watchOptions
'excludeDirectories',
'excludeFiles'
];
/** @typedef {string | string[]} PathValue */
/**
*
* @param {string} key
* @param {PathValue} value
* @param {string} prependPath
* @returns {PathValue}
*/
function rebaseRelative(key, value, prependPath) {
if (!REBASE_KEYS.includes(key)) {
return value;
}
if (Array.isArray(value)) {
return value.map((x) => rebasePath(x, prependPath));
} else {
return rebasePath(value, prependPath);
}
}
/**
*
* @param {string} value
* @param {string} prependPath
* @returns {string}
*/
function rebasePath(value, prependPath) {
if (path.isAbsolute(value) || value.startsWith('${configDir}')) {
return value;
} else {
// relative paths use posix syntax in tsconfig
return path.posix.normalize(path.posix.join(prependPath, value));
}
}
export class TSConfckParseError extends Error {
/**
* error code
* @type {string}
*/
code;
/**
* error cause
* @type { Error | undefined}
*/
cause;
/**
* absolute path of tsconfig file where the error happened
* @type {string}
*/
tsconfigFile;
/**
*
* @param {string} message - error message
* @param {string} code - error code
* @param {string} tsconfigFile - path to tsconfig file
* @param {Error?} cause - cause of this error
*/
constructor(message, code, tsconfigFile, cause) {
super(message);
// Set the prototype explicitly.
Object.setPrototypeOf(this, TSConfckParseError.prototype);
this.name = TSConfckParseError.name;
this.code = code;
this.cause = cause;
this.tsconfigFile = tsconfigFile;
}
}
/**
*
* @param {any} tsconfig
* @param {string} tsconfigFile
*/
function applyDefaults(tsconfig, tsconfigFile) {
if (isJSConfig(tsconfigFile)) {
tsconfig.compilerOptions = {
...DEFAULT_JSCONFIG_COMPILER_OPTIONS,
...tsconfig.compilerOptions
};
}
}
const DEFAULT_JSCONFIG_COMPILER_OPTIONS = {
allowJs: true,
maxNodeModuleJsDepth: 2,
allowSyntheticDefaultImports: true,
skipLibCheck: true,
noEmit: true
};
/**
* @param {string} configFileName
*/
function isJSConfig(configFileName) {
return path.basename(configFileName) === 'jsconfig.json';
}

125
node_modules/tsconfck/src/public.d.ts generated vendored Normal file
View File

@@ -0,0 +1,125 @@
import { TSConfckCache } from './cache.js';
export interface TSConfckFindOptions {
/**
* A cache to improve performance for multiple calls in the same project
*
* Warning: You must clear this cache in case tsconfig files are added/removed during it's lifetime
*/
cache?: TSConfckCache<TSConfckParseResult | TSConfckParseNativeResult>;
/**
* project root dir, does not continue scanning outside of this directory.
*
* Improves performance but may lead to different results from native typescript when no tsconfig is found inside root
*/
root?: string;
/**
* set to true if you don't want to find tsconfig for files inside node_modules
*
* This is useful if you want to use the output with esbuild.transform as esbuild itself also ignores node_modules
*
* @default false
*/
ignoreNodeModules?: boolean;
/**
* Override the default name of the config file to find.
*
* Use `jsconfig.json` in projects that have typechecking for js files with jsconfig.json
*
* @default tsconfig.json
*/
configName?: string;
}
export interface TSConfckParseOptions extends TSConfckFindOptions {
// same as find options
}
export interface TSConfckFindAllOptions {
/**
* helper to skip subdirectories when scanning for tsconfig.json
*
* eg ` dir => dir === 'node_modules' || dir === '.git'`
*/
skip?: (dir: string) => boolean;
/**
* list of config filenames to include, use ["tsconfig.json","jsconfig.json"] if you need both
*
* @default ["tsconfig.json"]
*/
configNames?: string[];
}
export interface TSConfckParseResult {
/**
* absolute path to parsed tsconfig.json
*/
tsconfigFile: string;
/**
* parsed result, including merged values from extended
*/
tsconfig: any;
/**
* ParseResult for parent solution
*/
solution?: TSConfckParseResult;
/**
* ParseResults for all tsconfig files referenced in a solution
*/
referenced?: TSConfckParseResult[];
/**
* ParseResult for all tsconfig files
*
* [a,b,c] where a extends b and b extends c
*/
extended?: TSConfckParseResult[];
}
export interface TSConfckParseNativeOptions extends TSConfckParseOptions {
/**
* Set this option to true to force typescript to ignore all source files.
*
* This is faster - especially for large projects - but comes with 2 caveats
*
* 1) output tsconfig always has `files: [],include: []` instead of any real values configured.
* 2) as a result of 1), it won't be able to resolve solution-style references and always return the closest tsconfig
*/
ignoreSourceFiles?: boolean;
}
export interface TSConfckParseNativeResult {
/**
* absolute path to parsed tsconfig.json
*/
tsconfigFile: string;
/**
* parsed result, including merged values from extended and normalized
*/
tsconfig: any;
/**
* ParseResult for parent solution
*/
solution?: TSConfckParseNativeResult;
/**
* ParseNativeResults for all tsconfig files referenced in a solution
*/
referenced?: TSConfckParseNativeResult[];
/**
* full output of ts.parseJsonConfigFileContent
*/
result: any;
}
// eslint-disable-next-line n/no-missing-import
export * from './index.js';

167
node_modules/tsconfck/src/to-json.js generated vendored Normal file
View File

@@ -0,0 +1,167 @@
/*
this file contains code from strip-bom and strip-json-comments by Sindre Sorhus
https://github.com/sindresorhus/strip-json-comments/blob/v4.0.0/index.js
https://github.com/sindresorhus/strip-bom/blob/v5.0.0/index.js
licensed under MIT, see ../LICENSE
*/
/**
* convert content of tsconfig.json to regular json
*
* @param {string} tsconfigJson - content of tsconfig.json
* @returns {string} content as regular json, comments and dangling commas have been replaced with whitespace
*/
export function toJson(tsconfigJson) {
const stripped = stripDanglingComma(stripJsonComments(stripBom(tsconfigJson)));
if (stripped.trim() === '') {
// only whitespace left after stripping, return empty object so that JSON.parse still works
return '{}';
} else {
return stripped;
}
}
/**
* replace dangling commas from pseudo-json string with single space
* implementation heavily inspired by strip-json-comments
*
* @param {string} pseudoJson
* @returns {string}
*/
function stripDanglingComma(pseudoJson) {
let insideString = false;
let offset = 0;
let result = '';
let danglingCommaPos = null;
for (let i = 0; i < pseudoJson.length; i++) {
const currentCharacter = pseudoJson[i];
if (currentCharacter === '"') {
const escaped = isEscaped(pseudoJson, i);
if (!escaped) {
insideString = !insideString;
}
}
if (insideString) {
danglingCommaPos = null;
continue;
}
if (currentCharacter === ',') {
danglingCommaPos = i;
continue;
}
if (danglingCommaPos) {
if (currentCharacter === '}' || currentCharacter === ']') {
result += pseudoJson.slice(offset, danglingCommaPos) + ' ';
offset = danglingCommaPos + 1;
danglingCommaPos = null;
} else if (!currentCharacter.match(/\s/)) {
danglingCommaPos = null;
}
}
}
return result + pseudoJson.substring(offset);
}
// start strip-json-comments
/**
*
* @param {string} jsonString
* @param {number} quotePosition
* @returns {boolean}
*/
function isEscaped(jsonString, quotePosition) {
let index = quotePosition - 1;
let backslashCount = 0;
while (jsonString[index] === '\\') {
index -= 1;
backslashCount += 1;
}
return Boolean(backslashCount % 2);
}
/**
*
* @param {string} string
* @param {number?} start
* @param {number?} end
*/
function strip(string, start, end) {
return string.slice(start, end).replace(/\S/g, ' ');
}
const singleComment = Symbol('singleComment');
const multiComment = Symbol('multiComment');
/**
* @param {string} jsonString
* @returns {string}
*/
function stripJsonComments(jsonString) {
let isInsideString = false;
/** @type {false | symbol} */
let isInsideComment = false;
let offset = 0;
let result = '';
for (let index = 0; index < jsonString.length; index++) {
const currentCharacter = jsonString[index];
const nextCharacter = jsonString[index + 1];
if (!isInsideComment && currentCharacter === '"') {
const escaped = isEscaped(jsonString, index);
if (!escaped) {
isInsideString = !isInsideString;
}
}
if (isInsideString) {
continue;
}
if (!isInsideComment && currentCharacter + nextCharacter === '//') {
result += jsonString.slice(offset, index);
offset = index;
isInsideComment = singleComment;
index++;
} else if (isInsideComment === singleComment && currentCharacter + nextCharacter === '\r\n') {
index++;
isInsideComment = false;
result += strip(jsonString, offset, index);
offset = index;
} else if (isInsideComment === singleComment && currentCharacter === '\n') {
isInsideComment = false;
result += strip(jsonString, offset, index);
offset = index;
} else if (!isInsideComment && currentCharacter + nextCharacter === '/*') {
result += jsonString.slice(offset, index);
offset = index;
isInsideComment = multiComment;
index++;
} else if (isInsideComment === multiComment && currentCharacter + nextCharacter === '*/') {
index++;
isInsideComment = false;
result += strip(jsonString, offset, index + 1);
offset = index + 1;
}
}
return result + (isInsideComment ? strip(jsonString.slice(offset)) : jsonString.slice(offset));
}
// end strip-json-comments
// start strip-bom
/**
* @param {string} string
* @returns {string}
*/
function stripBom(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;
}
// end strip-bom

339
node_modules/tsconfck/src/util.js generated vendored Normal file
View File

@@ -0,0 +1,339 @@
import path from 'node:path';
import { promises as fs } from 'node:fs';
const POSIX_SEP_RE = new RegExp('\\' + path.posix.sep, 'g');
const NATIVE_SEP_RE = new RegExp('\\' + path.sep, 'g');
/** @type {Map<string,RegExp>}*/
const PATTERN_REGEX_CACHE = new Map();
const GLOB_ALL_PATTERN = `**/*`;
const TS_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts'];
const JS_EXTENSIONS = ['.js', '.jsx', '.mjs', '.cjs'];
const TSJS_EXTENSIONS = TS_EXTENSIONS.concat(JS_EXTENSIONS);
const TS_EXTENSIONS_RE_GROUP = `\\.(?:${TS_EXTENSIONS.map((ext) => ext.substring(1)).join('|')})`;
const TSJS_EXTENSIONS_RE_GROUP = `\\.(?:${TSJS_EXTENSIONS.map((ext) => ext.substring(1)).join(
'|'
)})`;
const IS_POSIX = path.posix.sep === path.sep;
/**
* @template T
* @returns {{resolve:(result:T)=>void, reject:(error:any)=>void, promise: Promise<T>}}
*/
export function makePromise() {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
/**
* loads typescript async to avoid direct dependency
* @returns {Promise<any>}
*/
export async function loadTS() {
try {
return import('typescript').then((m) => m.default);
} catch (e) {
console.error('typescript must be installed to use "native" functions');
throw e;
}
}
/**
* @param {string} filename
* @param {import('./cache.js').TSConfckCache} [cache]
* @returns {Promise<string|void>}
*/
export async function resolveTSConfigJson(filename, cache) {
if (path.extname(filename) !== '.json') {
return; // ignore files that are not json
}
const tsconfig = path.resolve(filename);
if (cache && (cache.hasParseResult(tsconfig) || cache.hasParseResult(filename))) {
return tsconfig;
}
return fs.stat(tsconfig).then((stat) => {
if (stat.isFile() || stat.isFIFO()) {
return tsconfig;
} else {
throw new Error(`${filename} exists but is not a regular file.`);
}
});
}
/**
*
* @param {string} dir an absolute directory path
* @returns {boolean} if dir path includes a node_modules segment
*/
export const isInNodeModules = IS_POSIX
? (dir) => dir.includes('/node_modules/')
: (dir) => dir.match(/[/\\]node_modules[/\\]/);
/**
* convert posix separator to native separator
*
* eg.
* windows: C:/foo/bar -> c:\foo\bar
* linux: /foo/bar -> /foo/bar
*
* @param {string} filename with posix separators
* @returns {string} filename with native separators
*/
export const posix2native = IS_POSIX
? (filename) => filename
: (filename) => filename.replace(POSIX_SEP_RE, path.sep);
/**
* convert native separator to posix separator
*
* eg.
* windows: C:\foo\bar -> c:/foo/bar
* linux: /foo/bar -> /foo/bar
*
* @param {string} filename - filename with native separators
* @returns {string} filename with posix separators
*/
export const native2posix = IS_POSIX
? (filename) => filename
: (filename) => filename.replace(NATIVE_SEP_RE, path.posix.sep);
/**
* converts params to native separator, resolves path and converts native back to posix
*
* needed on windows to handle posix paths in tsconfig
*
* @param dir {string|null} directory to resolve from
* @param filename {string} filename or pattern to resolve
* @returns string
*/
export const resolve2posix = IS_POSIX
? (dir, filename) => (dir ? path.resolve(dir, filename) : path.resolve(filename))
: (dir, filename) =>
native2posix(
dir
? path.resolve(posix2native(dir), posix2native(filename))
: path.resolve(posix2native(filename))
);
/**
*
* @param {import('./public.d.ts').TSConfckParseResult} result
* @param {import('./public.d.ts').TSConfckParseOptions} [options]
* @returns {string[]}
*/
export function resolveReferencedTSConfigFiles(result, options) {
const dir = path.dirname(result.tsconfigFile);
return result.tsconfig.references.map((ref) => {
const refPath = ref.path.endsWith('.json')
? ref.path
: path.join(ref.path, options?.configName ?? 'tsconfig.json');
return resolve2posix(dir, refPath);
});
}
/**
* @param {string} filename
* @param {import('./public.d.ts').TSConfckParseResult} result
* @returns {import('./public.d.ts').TSConfckParseResult}
*/
export function resolveSolutionTSConfig(filename, result) {
const allowJs = result.tsconfig.compilerOptions?.allowJs;
const extensions = allowJs ? TSJS_EXTENSIONS : TS_EXTENSIONS;
if (
result.referenced &&
extensions.some((ext) => filename.endsWith(ext)) &&
!isIncluded(filename, result)
) {
const solutionTSConfig = result.referenced.find((referenced) =>
isIncluded(filename, referenced)
);
if (solutionTSConfig) {
return solutionTSConfig;
}
}
return result;
}
/**
*
* @param {string} filename
* @param {import('./public.d.ts').TSConfckParseResult} result
* @returns {boolean}
*/
function isIncluded(filename, result) {
const dir = native2posix(path.dirname(result.tsconfigFile));
const files = (result.tsconfig.files || []).map((file) => resolve2posix(dir, file));
const absoluteFilename = resolve2posix(null, filename);
if (files.includes(filename)) {
return true;
}
const allowJs = result.tsconfig.compilerOptions?.allowJs;
const isIncluded = isGlobMatch(
absoluteFilename,
dir,
result.tsconfig.include || (result.tsconfig.files ? [] : [GLOB_ALL_PATTERN]),
allowJs
);
if (isIncluded) {
const isExcluded = isGlobMatch(absoluteFilename, dir, result.tsconfig.exclude || [], allowJs);
return !isExcluded;
}
return false;
}
/**
* test filenames agains glob patterns in tsconfig
*
* @param filename {string} posix style abolute path to filename to test
* @param dir {string} posix style absolute path to directory of tsconfig containing patterns
* @param patterns {string[]} glob patterns to match against
* @param allowJs {boolean} allowJs setting in tsconfig to include js extensions in checks
* @returns {boolean} true when at least one pattern matches filename
*/
export function isGlobMatch(filename, dir, patterns, allowJs) {
const extensions = allowJs ? TSJS_EXTENSIONS : TS_EXTENSIONS;
return patterns.some((pattern) => {
// filename must end with part of pattern that comes after last wildcard
let lastWildcardIndex = pattern.length;
let hasWildcard = false;
let hasExtension = false;
let hasSlash = false;
let lastSlashIndex = -1;
for (let i = pattern.length - 1; i > -1; i--) {
const c = pattern[i];
if (!hasWildcard) {
if (c === '*' || c === '?') {
lastWildcardIndex = i;
hasWildcard = true;
}
}
if (!hasSlash) {
if (c === '.') {
hasExtension = true;
} else if (c === '/') {
lastSlashIndex = i;
hasSlash = true;
}
}
if (hasWildcard && hasSlash) {
break;
}
}
if (!hasExtension && (!hasWildcard || lastWildcardIndex < lastSlashIndex)) {
// add implicit glob
pattern += `${pattern.endsWith('/') ? '' : '/'}${GLOB_ALL_PATTERN}`;
lastWildcardIndex = pattern.length - 1;
hasWildcard = true;
}
// if pattern does not end with wildcard, filename must end with pattern after last wildcard
if (
lastWildcardIndex < pattern.length - 1 &&
!filename.endsWith(pattern.slice(lastWildcardIndex + 1))
) {
return false;
}
// if pattern ends with *, filename must end with a default extension
if (pattern.endsWith('*') && !extensions.some((ext) => filename.endsWith(ext))) {
return false;
}
// for **/* , filename must start with the dir
if (pattern === GLOB_ALL_PATTERN) {
return filename.startsWith(`${dir}/`);
}
const resolvedPattern = resolve2posix(dir, pattern);
// filename must start with part of pattern that comes before first wildcard
let firstWildcardIndex = -1;
for (let i = 0; i < resolvedPattern.length; i++) {
if (resolvedPattern[i] === '*' || resolvedPattern[i] === '?') {
firstWildcardIndex = i;
hasWildcard = true;
break;
}
}
if (
firstWildcardIndex > 1 &&
!filename.startsWith(resolvedPattern.slice(0, firstWildcardIndex - 1))
) {
return false;
}
if (!hasWildcard) {
// no wildcard in pattern, filename must be equal to resolved pattern
return filename === resolvedPattern;
} else if (
firstWildcardIndex + GLOB_ALL_PATTERN.length ===
resolvedPattern.length - (pattern.length - 1 - lastWildcardIndex) &&
resolvedPattern.slice(firstWildcardIndex, firstWildcardIndex + GLOB_ALL_PATTERN.length) ===
GLOB_ALL_PATTERN
) {
// singular glob-all pattern and we already validated prefix and suffix matches
return true;
}
// complex pattern, use regex to check it
if (PATTERN_REGEX_CACHE.has(resolvedPattern)) {
return PATTERN_REGEX_CACHE.get(resolvedPattern).test(filename);
}
const regex = pattern2regex(resolvedPattern, allowJs);
PATTERN_REGEX_CACHE.set(resolvedPattern, regex);
return regex.test(filename);
});
}
/**
* @param {string} resolvedPattern
* @param {boolean} allowJs
* @returns {RegExp}
*/
function pattern2regex(resolvedPattern, allowJs) {
let regexStr = '^';
for (let i = 0; i < resolvedPattern.length; i++) {
const char = resolvedPattern[i];
if (char === '?') {
regexStr += '[^\\/]';
continue;
}
if (char === '*') {
if (resolvedPattern[i + 1] === '*' && resolvedPattern[i + 2] === '/') {
i += 2;
regexStr += '(?:[^\\/]*\\/)*'; // zero or more path segments
continue;
}
regexStr += '[^\\/]*';
continue;
}
if ('/.+^${}()|[]\\'.includes(char)) {
regexStr += `\\`;
}
regexStr += char;
}
// add known file endings if pattern ends on *
if (resolvedPattern.endsWith('*')) {
regexStr += allowJs ? TSJS_EXTENSIONS_RE_GROUP : TS_EXTENSIONS_RE_GROUP;
}
regexStr += '$';
return new RegExp(regexStr);
}
/**
* replace tokens like ${configDir}
* @param {import('./public.d.ts').TSConfckParseResult} result
*/
export function replaceTokens(result) {
if (result.tsconfig) {
result.tsconfig = JSON.parse(
JSON.stringify(result.tsconfig)
// replace ${configDir}
.replaceAll(/"\${configDir}/g, `"${native2posix(path.dirname(result.tsconfigFile))}`)
);
}
}