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

46
node_modules/tsconfck/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,46 @@
MIT License
Copyright (c) 2021-present dominikg and [contributors](https://github.com/dominikg/tsconfck/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-- Licenses for 3rd-party code included in tsconfck --
# strip-bom and strip-json-comments
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

237
node_modules/tsconfck/README.md generated vendored Normal file
View File

@@ -0,0 +1,237 @@
# tsconfck
[![npm version](https://img.shields.io/npm/v/tsconfck)](https://www.npmjs.com/package/tsconfck)
[![CI](https://github.com/dominikg/tsconfck/actions/workflows/test.yml/badge.svg)](https://github.com/dominikg/tsconfck/actions/workflows/test.yml)
A utility to find and parse tsconfig files without depending on typescript
# Why
Because no simple official api exists and tsconfig isn't actual json.
# Features
- [x] find closest tsconfig (tsconfig.json or jsconfig.json)
- [x] convert tsconfig to actual json and parse it
- [x] resolve "extends"
- [x] resolve "references" of solution-style tsconfig
- [x] optional caching for improved performance
- [x] optional findNative and parseNative to use official typescript api
- [x] zero dependencies (typescript optional)
- [x] extensive testsuite
- [x] completely async and optimized (it's [fast](https://github.com/dominikg/tsconfck/blob/main/docs/benchmark.md))
- [x] tiny [4.8KB gzip](https://pkg-size.dev/tsconfck)
- [x] unbundled esm js, no sourcemaps needed
- [x] [types](https://github.com/dominikg/tsconfck/blob/main/packages/tsconfck/types/index.d.ts) generated with [dts-buddy](https://github.com/Rich-Harris/dts-buddy)
# Users
Used by [vite](https://github.com/vitejs/vite)\*, [vite-tsconfig-paths](https://github.com/aleclarson/vite-tsconfig-paths), [astro](https://github.com/withastro/astro) and [many more](https://github.com/dominikg/tsconfck/network/dependents)
> (\*) vite bundles tsconfck so it is listed as a devDependency
# Install
```shell
npm install --save-dev tsconfck # or pnpm, yarn
```
# Usage
## without typescript installed
```js
import { parse } from 'tsconfck';
const {
tsconfigFile, // full path to found tsconfig
tsconfig, // tsconfig object including merged values from extended configs
extended, // separate unmerged results of all tsconfig files that contributed to tsconfig
solution, // solution result if tsconfig is part of a solution
referenced // referenced tsconfig results if tsconfig is a solution
} = await parse('foo/bar.ts');
```
## with typescript
```js
import { parseNative } from 'tsconfck';
const {
tsconfigFile, // full path to found tsconfig
tsconfig, // tsconfig object including merged values from extended configs, normalized
result, // output of ts.parseJsonConfigFileContent
solution, // solution result if tsconfig is part of a solution
referenced // referenced tsconfig results if tsconfig is a solution
} = await parseNative('foo/bar.ts');
```
## API
see [API-DOCS](docs/api.md)
## Advanced
### ignoring tsconfig for files inside node_modules
esbuild ignores node_modules so when you want to use tsconfck with esbuild, you can set `ignoreNodeModules: true`
```js
import { find, parse } from 'tsconfck';
// returns some-lib/tsconfig.json
const fooTSConfig = await find('node_modules/some-lib/src/foo.ts');
// returns null
const fooTSConfigIgnored = await find('node_modules/some-lib/src/foo.ts', {
ignoreNodeModules: true
});
// returns empty config
const { tsconfig } = await parse('node_modules/some-lib/src/foo.ts', { ignoreNodeModules: true });
```
### caching
a TSConfckCache instance can be created and passed to find and parse functions to reduce overhead when they are called often within the same project
```js
import { find, parse, TSCOnfckCache } from 'tsconfck';
// 1. create cache instance
const cache = new TSCOnfckCache();
// 2. pass cache instance in options
const fooTSConfig = await find(('src/foo.ts', { cache })); // stores tsconfig for src in cache
const barTSConfig = await find(('src/bar.ts', { cache })); // reuses tsconfig result for src without fs call
const fooResult = await parse('src/foo.ts', { cache }); // uses cached path for tsconfig, stores parse result in cache
const barResult = await parse('src/bar.ts', { cache }); // uses cached parse result without fs call or resolving
```
#### cache invalidation
You are responsible for clearing the cache if tsconfig files are added/removed/changed after reading them during the cache lifetime.
Call `cache.clear()` and also discard all previous compilation results based previously cached configs.
#### cache mutation
Returned results are direct cache objects. If you want to modify them, deep-clone first.
#### cache reuse
Never use the same cache instance for mixed calls of find/findNative or parse/parseNative as result structures are different
### root
This option can be used to limit finding tsconfig files outside of a root directory
```js
import { parse, TSConfckCache } from 'tsconfck';
const root = '.';
const parseOptions = { root };
// these calls are not going to look for tsconfig files outside root
const fooResult = await find('src/foo.ts', parseOptions);
const barResult = await parse('src/bar.ts', parseOptions);
```
> Using the root option can lead to errors if there is no tsconfig found inside root.
### error handling
find and parse reject for errors they encounter, but return null or empty result if no config was found
If you want them to error instead, test the result and throw
```js
import { parse } from 'tsconfck';
find('some/path/without/tsconfig/foo.ts').then((result) => {
if (result === null) {
throw new Error('not found');
}
return result;
});
parse('some/path/without/tsconfig/foo.ts').then((result) => {
if (result.tsconfigFile === null) {
throw new Error('not found');
}
return result;
});
```
### TSConfig type (optional, requires typescript as devDependency)
```ts
import type { TSConfig } from 'pkg-types';
```
Check out https://github.com/unjs/pkg-types
### cli
A simple cli wrapper is included, you can use it like this
#### find
```shell
# prints /path/to/tsconfig.json on stdout
tsconfck find src/index.ts
```
#### find-all
```shell
# prints all tsconfig.json in dir on stdout
tsconfck find-all src/
```
#### parse
```shell
# print content of ParseResult.tsconfig on stdout
tsconfck parse src/index.ts
# print to file
tsconfck parse src/index.ts > output.json
```
#### parse-result
```shell
# print content of ParseResult on stdout
tsconfck parse-result src/index.ts
# print to file
tsconfck parse-result src/index.ts > output.json
```
#### help
```shell
# print usage
tsconfck -h # or --help, -?, help
```
# Links
- [changelog](CHANGELOG.md)
# Develop
This repo uses
- [pnpm](https://pnpm.io)
- [changesets](https://github.com/changesets/changesets)
In every PR you have to add a changeset by running `pnpm changeset` and following the prompts
PRs are going to be squash-merged
```shell
# install dependencies
pnpm install
# run tests
pnpm test
#run tests in watch mode (doesn't require dev in parallel)
pnpm test:watch
```
# License
[MIT](./LICENSE)

76
node_modules/tsconfck/bin/tsconfck.js generated vendored Executable file
View File

@@ -0,0 +1,76 @@
#!/usr/bin/env node
import { parse, find, findAll } from '../src/index.js';
import * as process from 'node:process';
const HELP_TEXT = `
Usage: tsconfck <command> <file> [args]
Commands: find, find-all, parse, parse-result
Args:
-js : find/parse jsconfig.json instead of tsconfig.json
Examples:
find tsconfig.json for a file
> tsconfck find src/index.ts
find all tsconfig files in current dir
> tsconfck find-all .
parse tsconfig for a file
> tsconfck parse src/index.ts
`;
const HELP_ARGS = ['-h', '--help', '-?', 'help'];
const JS_ARG = '-js';
const COMMANDS = ['find', 'find-all', 'find-all', 'parse', 'parse-result'];
function needsHelp(args) {
if (args.some((arg) => HELP_ARGS.includes(arg))) {
return HELP_TEXT;
}
const expectedLength = args.includes(JS_ARG) ? 3 : 2;
if (args.length !== expectedLength) {
return 'invalid number of arguments\n' + HELP_TEXT;
} else if (!COMMANDS.includes(args[0])) {
return 'invalid command ' + args[0] + '\n' + HELP_TEXT;
}
}
async function main() {
const args = process.argv.slice(2);
const help = needsHelp(args);
if (help) {
return help;
}
const command = args[0];
const file = args[1];
const isJS = args[2] === JS_ARG;
const findOptions = isJS ? { configName: 'jsconfig.json' } : undefined;
if (command === 'find') {
return find(file, findOptions).then((found) => {
if (!found) {
throw new Error(`no tsconfig found for ${file}`);
}
return found;
});
} else if (command === 'parse') {
return JSON.stringify((await parse(file, findOptions)).tsconfig, null, 2);
} else if (command === 'parse-result') {
return JSON.stringify(await parse(file, findOptions), null, 2);
} else if (command === 'find-all') {
return (
await findAll(file || '.', { configNames: [isJS ? 'jsconfig.json' : 'tsconfig.json'] })
).join('\n');
}
}
main().then(
(result) => {
process.stdout.write(result);
process.stdout.write('\n');
},
(err) => {
console.error(err.message, err);
// eslint-disable-next-line n/no-process-exit
process.exit(1);
}
);

71
node_modules/tsconfck/package.json generated vendored Normal file
View File

@@ -0,0 +1,71 @@
{
"name": "tsconfck",
"version": "3.1.6",
"description": "A utility to work with tsconfig.json without typescript",
"license": "MIT",
"author": "dominikg",
"files": [
"bin",
"src",
"types",
"README.md",
"LICENSE",
"package.json"
],
"type": "module",
"bin": "bin/tsconfck.js",
"types": "types/index.d.ts",
"exports": {
".": {
"import": {
"types": "./types/index.d.ts",
"default": "./src/index.js"
}
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/dominikg/tsconfck.git",
"directory": "packages/tsconfck"
},
"keywords": [
"typescript",
"tsconfig",
"tsconfig.json",
"jsconfig",
"jsconfig.json"
],
"bugs": {
"url": "https://github.com/dominikg/tsconfck/issues"
},
"homepage": "https://github.com/dominikg/tsconfck/tree/main/packages/tsconfck#readme",
"peerDependencies": {
"typescript": "^5.0.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
},
"devDependencies": {
"@tsconfig/node18": "^18.2.4",
"@tsconfig/strictest": "^2.0.5",
"@vitest/coverage-v8": "^3.0.9",
"esbuild": "^0.25.1",
"isaacscript-tsconfig": "^7.0.1",
"tiny-glob": "^0.2.9",
"typescript": "^5.8.2",
"vitest": "^3.0.9"
},
"engines": {
"node": "^18 || >=20"
},
"scripts": {
"check:publint": "publint --strict",
"check:types": "tsc --noEmit",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"test:watch": "vitest",
"dts-buddy": "dts-buddy -m \"tsconfck:src/index.js\""
}
}

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))}`)
);
}
}

262
node_modules/tsconfck/types/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,262 @@
declare module 'tsconfck' {
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;
}
export class TSConfckCache<T> {
/**
* clear cache, use this if you have a long running process and tsconfig files have been added,changed or deleted
*/
clear(): void;
/**
* has cached closest config for files in dir
* */
hasConfigPath(dir: string, configName?: string): boolean;
/**
* get cached closest tsconfig for files in dir
* @throws {unknown} if cached value is an error
*/
getConfigPath(dir: string, configName?: string): Promise<string | null> | string | null;
/**
* has parsed tsconfig for file
* */
hasParseResult(file: string): boolean;
/**
* get parsed tsconfig for file
* @throws {unknown} if cached value is an error
*/
getParseResult(file: string): Promise<T> | T;
/**
* @param isRootFile a flag to check if current file which involking the parse() api, used to distinguish the normal cache which only parsed by parseFile()
* */
private setParseResult;
private setConfigPath;
#private;
}
/**
* find the closest tsconfig.json file
*
* @param filename - path to file to find tsconfig for (absolute or relative to cwd)
* @param options - options
* @returns absolute path to closest tsconfig.json or null if not found
*/
export function find(filename: string, options?: TSConfckFindOptions): Promise<string | null>;
/**
* find all tsconfig.json files in dir
*
* @param dir - path to dir (absolute or relative to cwd)
* @param options - options
* @returns list of absolute paths to all found tsconfig.json files
*/
export function findAll(dir: string, options?: TSConfckFindAllOptions): Promise<string[]>;
/**
* convert content of tsconfig.json to regular json
*
* @param tsconfigJson - content of tsconfig.json
* @returns content as regular json, comments and dangling commas have been replaced with whitespace
*/
export function toJson(tsconfigJson: string): string;
/**
* find the closest tsconfig.json file using native ts.findConfigFile
*
* You must have `typescript` installed to use this
*
* @param filename - path to file to find tsconfig for (absolute or relative to cwd)
* @param options - options
* @returns absolute path to closest tsconfig.json
*/
export function findNative(filename: string, options?: TSConfckFindOptions): Promise<string>;
/**
* parse the closest tsconfig.json file
*
* @param filename - path to a tsconfig .json or a source file or directory (absolute or relative to cwd)
* @param options - options
* */
export function parse(filename: string, options?: TSConfckParseOptions): Promise<TSConfckParseResult>;
export class TSConfckParseError extends Error {
/**
*
* @param message - error message
* @param code - error code
* @param tsconfigFile - path to tsconfig file
* @param cause - cause of this error
*/
constructor(message: string, code: string, tsconfigFile: string, cause: Error | null);
/**
* error code
* */
code: string;
/**
* error cause
* */
cause: Error | undefined;
/**
* absolute path of tsconfig file where the error happened
* */
tsconfigFile: string;
}
/**
* parse the closest tsconfig.json file with typescript native functions
*
* You need to have `typescript` installed to use this
*
* @param filename - path to a tsconfig .json or a source file (absolute or relative to cwd)
* @param options - options
* */
export function parseNative(filename: string, options?: TSConfckParseNativeOptions): Promise<TSConfckParseNativeResult>;
export class TSConfckParseNativeError extends Error {
/**
*
* @param diagnostic - diagnostics of ts
* @param tsconfigFile - file that errored
* @param result - parsed result, if any
*/
constructor(diagnostic: TSDiagnosticError, tsconfigFile: string, result: any | null);
/**
* code of typescript diagnostic, prefixed with "TS "
* */
code: string;
/**
* full ts diagnostic that caused this error
* */
diagnostic: TSDiagnosticError;
/**
* native result if present, contains all errors in result.errors
* */
result: any | undefined;
/**
* absolute path of tsconfig file where the error happened
* */
tsconfigFile: string;
}
/**
* {
* code: number;
* category: number;
* messageText: string;
* start?: number;
* } TSDiagnosticError
*/
type TSDiagnosticError = any;
export {};
}
//# sourceMappingURL=index.d.ts.map

46
node_modules/tsconfck/types/index.d.ts.map generated vendored Normal file
View File

@@ -0,0 +1,46 @@
{
"version": 3,
"file": "index.d.ts",
"names": [
"TSConfckFindOptions",
"TSConfckParseOptions",
"TSConfckFindAllOptions",
"TSConfckParseResult",
"TSConfckParseNativeOptions",
"TSConfckParseNativeResult",
"TSConfckCache",
"find",
"findAll",
"toJson",
"findNative",
"parse",
"TSConfckParseError",
"parseNative",
"TSConfckParseNativeError",
"TSDiagnosticError"
],
"sources": [
"../src/public.d.ts",
"../src/cache.js",
"../src/find.js",
"../src/find-all.js",
"../src/to-json.js",
"../src/find-native.js",
"../src/parse.js",
"../src/parse-native.js",
"../src/parse-native.d.ts"
],
"sourcesContent": [
null,
null,
null,
null,
null,
null,
null,
null,
null
],
"mappings": ";kBAEiBA,mBAAmBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAkCnBC,oBAAoBA;;;;kBAIpBC,sBAAsBA;;;;;;;;;;;;;;;kBAetBC,mBAAmBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA6BnBC,0BAA0BA;;;;;;;;;;;;kBAY1BC,yBAAyBA;;;;;;;;;;;;;;;;;;;;;;;;;;cC/F7BC,aAAaA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCSJC,IAAIA;;;;;;;;iBCYJC,OAAOA;;;;;;;iBCTbC,MAAMA;;;;;;;;;;iBCDAC,UAAUA;;;;;;;iBCgBVC,KAAKA;cA8VdC,kBAAkBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBC9VTC,WAAWA;cAoOpBC,wBAAwBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MChNzBC,iBAAiBA",
"ignoreList": []
}