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:
129
node_modules/tsconfck/src/cache.js
generated
vendored
Normal file
129
node_modules/tsconfck/src/cache.js
generated
vendored
Normal 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
71
node_modules/tsconfck/src/find-all.js
generated
vendored
Normal 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
70
node_modules/tsconfck/src/find-native.js
generated
vendored
Normal 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
70
node_modules/tsconfck/src/find.js
generated
vendored
Normal 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
7
node_modules/tsconfck/src/index.js
generated
vendored
Normal 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
305
node_modules/tsconfck/src/parse-native.js
generated
vendored
Normal 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
441
node_modules/tsconfck/src/parse.js
generated
vendored
Normal 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
125
node_modules/tsconfck/src/public.d.ts
generated
vendored
Normal 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
167
node_modules/tsconfck/src/to-json.js
generated
vendored
Normal 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
339
node_modules/tsconfck/src/util.js
generated
vendored
Normal 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))}`)
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user