tsconfck
Advanced tools
Comparing version 3.0.0-next.3 to 3.0.0-next.4
@@ -53,3 +53,8 @@ #!/usr/bin/env node | ||
if (command === 'find') { | ||
return find(file); | ||
return find(file).then((found) => { | ||
if (!found) { | ||
throw new Error(`no tsconfig found for ${file}`); | ||
} | ||
return found; | ||
}); | ||
} else if (command === 'parse') { | ||
@@ -56,0 +61,0 @@ return JSON.stringify((await parse(file)).tsconfig, null, 2); |
{ | ||
"name": "tsconfck", | ||
"version": "3.0.0-next.3", | ||
"version": "3.0.0-next.4", | ||
"description": "A utility to work with tsconfig.json without typescript", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -0,21 +1,9 @@ | ||
/** @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 | ||
* await it to ensure all find and parse calls are settled before continuing | ||
*/ | ||
async clear() { | ||
if (!this.#clearing) { | ||
this.#clearing = Promise.allSettled([ | ||
...this.#tsconfigPaths.values(), | ||
...this.#parsed.values() | ||
]) | ||
.then(() => { | ||
this.#tsconfigPaths.clear(); | ||
this.#parsed.clear(); | ||
}) | ||
.finally(() => { | ||
this.#clearing = undefined; | ||
}); | ||
} | ||
return this.#clearing; | ||
clear() { | ||
this.#tsconfigPaths.clear(); | ||
this.#parsed.clear(); | ||
} | ||
@@ -35,5 +23,5 @@ | ||
* @param {string} dir | ||
* @returns {import('./public.d.ts').Awaitable<string|null>} | ||
* @returns {Promise<string|null>|string|null} | ||
*/ | ||
async getTSConfigPath(dir) { | ||
getTSConfigPath(dir) { | ||
return this.#tsconfigPaths.get(dir); | ||
@@ -54,3 +42,3 @@ } | ||
* @param {string} file | ||
* @returns {import('./public.d.ts').Awaitable<import('./public.d.ts').TSConfckParseResult | import('./public.d.ts').TSConfckParseNativeResult>} | ||
* @returns {Promise<T>|T} | ||
*/ | ||
@@ -65,7 +53,17 @@ getParseResult(file) { | ||
* @param file | ||
* @param {Promise<import('./public.d.ts').TSConfckParseResult | import('./public.d.ts').TSConfckParseNativeResult>} result | ||
* @param {Promise<T>|T} result | ||
*/ | ||
setParseResult(file, result) { | ||
this.#parsed.set(file, result); | ||
result.then((parsed) => this.#parsed.set(file, parsed)).catch(() => this.#parsed.delete(file)); | ||
result | ||
.then((parsed) => { | ||
if (this.#parsed.get(file) === result) { | ||
this.#parsed.set(file, parsed); | ||
} | ||
}) | ||
.catch(() => { | ||
if (this.#parsed.get(file) === result) { | ||
this.#parsed.delete(file); | ||
} | ||
}); | ||
} | ||
@@ -76,13 +74,4 @@ | ||
* @private | ||
* @param file | ||
*/ | ||
deleteParseResult(file) { | ||
this.#parsed.delete(file); | ||
} | ||
/** | ||
* @internal | ||
* @private | ||
* @param {string} dir | ||
* @param {Promise<string|null>} tsconfigPath | ||
* @param {Promise<string|null>|string|null} tsconfigPath | ||
*/ | ||
@@ -92,4 +81,12 @@ setTSConfigPath(dir, tsconfigPath) { | ||
tsconfigPath | ||
.then((path) => this.#tsconfigPaths.set(dir, path)) | ||
.catch(() => this.#tsconfigPaths.delete(dir)); | ||
.then((path) => { | ||
if (this.#tsconfigPaths.get(dir) === tsconfigPath) { | ||
this.#tsconfigPaths.set(dir, path); | ||
} | ||
}) | ||
.catch(() => { | ||
if (this.#tsconfigPaths.get(dir) === tsconfigPath) { | ||
this.#tsconfigPaths.delete(dir); | ||
} | ||
}); | ||
} | ||
@@ -101,3 +98,3 @@ | ||
* @private | ||
* @type{Map<string,import('./public.d.ts').Awaitable<string|null>>} | ||
* @type{Map<string,(Promise<string|null>|string|null)>} | ||
*/ | ||
@@ -110,8 +107,5 @@ #tsconfigPaths = new Map(); | ||
* @private | ||
* @type {Map<string,import('./public.d.ts').Awaitable<import('./public.d.ts').TSConfckParseResult | import('./public.d.ts').TSConfckParseNativeResult>> } | ||
* @type {Map<string,(Promise<T>|T)> } | ||
*/ | ||
#parsed = new Map(); | ||
/** @type{Promise<void>} */ | ||
#clearing; | ||
} |
import path from 'path'; | ||
import { readdir } from 'fs'; | ||
import { readdir } from 'node:fs'; | ||
@@ -4,0 +4,0 @@ /** |
@@ -1,4 +0,3 @@ | ||
import path from 'path'; | ||
import { promises as fs } from 'fs'; | ||
import path from 'node:path'; | ||
import fs from 'node:fs'; | ||
/** | ||
@@ -9,3 +8,3 @@ * find the closest tsconfig.json file | ||
* @param {import('./public.d.ts').TSConfckFindOptions} [options] - options | ||
* @returns {Promise<string>} absolute path to closest tsconfig.json | ||
* @returns {Promise<string|null>} absolute path to closest tsconfig.json or null if not found | ||
*/ | ||
@@ -19,59 +18,54 @@ export async function find(filename, options) { | ||
const root = options?.root ? path.resolve(options.root) : null; | ||
/** @type {(result: string|null)=>void}*/ | ||
let resolvePathPromise; | ||
/** @type {((result: string|null,err?: ErrnoException)=>void)} */ | ||
let done; | ||
/** @type {Promise<string|null> | string | null}*/ | ||
const pathPromise = new Promise((r) => { | ||
resolvePathPromise = r; | ||
}); | ||
while (dir) { | ||
if (cache) { | ||
if (cache.hasTSConfigPath(dir)) { | ||
const cached = cache.getTSConfigPath(dir); | ||
if (cached.then) { | ||
cached.then(resolvePathPromise); | ||
} else { | ||
resolvePathPromise(/**@type {string|null} */ (cached)); | ||
} | ||
return pathPromise; | ||
const promise = new Promise((resolve, reject) => { | ||
done = (result, err) => { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
cache.setTSConfigPath(dir, pathPromise); | ||
resolve(result); | ||
} | ||
} | ||
const tsconfig = await tsconfigInDir(dir); | ||
if (tsconfig) { | ||
resolvePathPromise(tsconfig); | ||
return pathPromise; | ||
} else { | ||
const parent = path.dirname(dir); | ||
if (root === dir || parent === dir) { | ||
// reached root | ||
break; | ||
} else { | ||
dir = parent; | ||
} | ||
} | ||
} | ||
resolvePathPromise(null); | ||
throw new Error(`no tsconfig file found for ${filename}`); | ||
}; | ||
}); | ||
findUp(dir, promise, done, options?.cache, root); | ||
return promise; | ||
} | ||
/** | ||
* test if tsconfig exists in dir | ||
* | ||
* @param {string} dir | ||
* @returns {Promise<string|undefined>} | ||
* @param {Promise<string|null>} promise | ||
* @param {((result: string|null,err?: ErrnoException)=>void)} done | ||
* @param {import('./cache.js').TSConfckCache} [cache] | ||
* @param {string} [root] | ||
*/ | ||
async function tsconfigInDir(dir) { | ||
function findUp(dir, promise, done, cache, root) { | ||
const tsconfig = path.join(dir, 'tsconfig.json'); | ||
try { | ||
const stat = await fs.stat(tsconfig); | ||
if (stat.isFile() || stat.isFIFO()) { | ||
return tsconfig; | ||
if (cache) { | ||
if (cache.hasTSConfigPath(dir)) { | ||
const cached = cache.getTSConfigPath(dir); | ||
if (cached.then) { | ||
cached.then(done).catch((err) => done(null, err)); | ||
} else { | ||
done(/**@type {string|null} */ (cached)); | ||
} | ||
} else { | ||
cache.setTSConfigPath(dir, promise); | ||
} | ||
} catch (e) { | ||
// ignore does not exist error | ||
if (e.code !== 'ENOENT') { | ||
throw e; | ||
} | ||
fs.stat(tsconfig, (err, stats) => { | ||
if (stats && (stats.isFile() || stats.isFIFO())) { | ||
done(tsconfig); | ||
} else if (err?.code !== 'ENOENT') { | ||
done(null, err); | ||
} else { | ||
let parent; | ||
if (root === dir || (parent = path.dirname(dir)) === dir) { | ||
done(null); | ||
} else { | ||
findUp(parent, promise, done, cache, root); | ||
} | ||
} | ||
} | ||
}); | ||
} |
@@ -22,2 +22,3 @@ import path from 'path'; | ||
export async function parseNative(filename, options) { | ||
/** @type {import('./cache.js').TSConfckCache} */ | ||
const cache = options?.cache; | ||
@@ -31,5 +32,5 @@ if (cache?.hasParseResult(filename)) { | ||
try { | ||
tsconfigFile = await resolveTSConfig(filename); | ||
tsconfigFile = await resolveTSConfig(filename, cache); | ||
if (!tsconfigFile) { | ||
tsconfigFile = await findNative(filename); | ||
tsconfigFile = await findNative(filename, options); | ||
} | ||
@@ -46,5 +47,5 @@ } catch (e) { | ||
} else { | ||
tsconfigFile = await resolveTSConfig(filename); | ||
tsconfigFile = await resolveTSConfig(filename, cache); | ||
if (!tsconfigFile) { | ||
tsconfigFile = await findNative(filename); | ||
tsconfigFile = await findNative(filename, options); | ||
} | ||
@@ -56,3 +57,3 @@ } | ||
if (cache?.hasParseResult(tsconfigFile)) { | ||
result = cache.getParseResult(tsconfigFile); | ||
result = await cache.getParseResult(tsconfigFile); | ||
} else { | ||
@@ -59,0 +60,0 @@ const ts = await loadTS(); |
import path from 'path'; | ||
import { promises as fs } from 'fs'; | ||
import { promises as fs } from 'node:fs'; | ||
import { createRequire } from 'module'; | ||
@@ -14,6 +14,11 @@ import { find } from './find.js'; | ||
const not_found_result = { | ||
tsconfigFile: null, | ||
tsconfig: {} | ||
}; | ||
/** | ||
* parse the closest tsconfig.json file | ||
* | ||
* @param {string} filename - path to a tsconfig.json or a .ts source file (absolute or relative to cwd) | ||
* @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 | ||
@@ -24,2 +29,3 @@ * @returns {Promise<import('./public.d.ts').TSConfckParseResult>} | ||
export async function parse(filename, options) { | ||
/** @type {import('./cache.js').TSConfckCache} */ | ||
const cache = options?.cache; | ||
@@ -37,17 +43,8 @@ if (cache?.hasParseResult(filename)) { | ||
let tsconfigFile; | ||
if (options?.resolveWithEmptyIfConfigNotFound) { | ||
try { | ||
tsconfigFile = (await resolveTSConfig(filename)) || (await find(filename, options)); | ||
} catch (e) { | ||
const notFoundResult = { | ||
tsconfigFile: 'no_tsconfig_file_found', | ||
tsconfig: {} | ||
}; | ||
resolveConfigPromise(notFoundResult); | ||
return configPromise; | ||
} | ||
} else { | ||
tsconfigFile = (await resolveTSConfig(filename)) || (await find(filename, options)); | ||
let tsconfigFile = (await resolveTSConfig(filename, cache)) || (await find(filename, options)); | ||
if (!tsconfigFile) { | ||
resolveConfigPromise(not_found_result); | ||
return configPromise; | ||
} | ||
let result; | ||
@@ -67,3 +64,3 @@ if (filename !== tsconfigFile && cache?.hasParseResult(tsconfigFile)) { | ||
* @param {string} tsconfigFile - path to tsconfig file | ||
* @param {TSConfckCache} cache - cache | ||
* @param {import('./cache.js').TSConfckCache} [cache] - cache | ||
* @param {boolean} [skipCache] - skip cache | ||
@@ -131,3 +128,3 @@ * @returns {Promise<import('./public.d.ts').TSConfckParseResult>} | ||
* @param {import('./public.d.ts').TSConfckParseResult} result | ||
* @param {TSConfckCache} [cache] | ||
* @param {import('./cache.js').TSConfckCache}[cache] | ||
* @returns {Promise<void>} | ||
@@ -134,0 +131,0 @@ */ |
@@ -1,2 +0,2 @@ | ||
import { TSConfckCache } from './cache'; | ||
import { TSConfckCache } from './cache.js'; | ||
@@ -9,3 +9,3 @@ export interface TSConfckFindOptions { | ||
*/ | ||
cache?: TSConfckCache; | ||
cache?: TSConfckCache<TSConfckParseResult | TSConfckParseNativeResult>; | ||
@@ -20,2 +20,6 @@ /** | ||
export interface TSConfckParseOptions extends TSConfckFindOptions { | ||
// same as find options | ||
} | ||
export interface TSConfckFindAllOptions { | ||
@@ -30,10 +34,2 @@ /** | ||
export interface TSConfckParseOptions extends TSConfckFindOptions { | ||
/** | ||
* treat missing tsconfig as empty result instead of an error | ||
* parse resolves with { filename: 'no_tsconfig_file_found',tsconfig:{}} instead of reject with error | ||
*/ | ||
resolveWithEmptyIfConfigNotFound?: boolean; | ||
} | ||
export interface TSConfckParseResult { | ||
@@ -106,3 +102,1 @@ /** | ||
} | ||
export type Awaitable<T> = Promise<T> | T; |
@@ -1,3 +0,3 @@ | ||
import path from 'path'; | ||
import { promises as fs } from 'fs'; | ||
import path from 'node:path'; | ||
import { promises as fs } from 'node:fs'; | ||
@@ -14,2 +14,4 @@ const POSIX_SEP_RE = new RegExp('\\' + path.posix.sep, 'g'); | ||
const IS_POSIX = path.posix.sep === path.sep; | ||
/** | ||
@@ -30,5 +32,6 @@ * loads typescript async to avoid direct dependency | ||
* @param {string} filename | ||
* @param {import('./cache.js').TSConfckCache} [cache] | ||
* @returns {Promise<string|void>} | ||
*/ | ||
export async function resolveTSConfig(filename) { | ||
export async function resolveTSConfig(filename, cache) { | ||
if (path.extname(filename) !== '.json') { | ||
@@ -38,2 +41,15 @@ return; | ||
const tsconfig = path.resolve(filename); | ||
if (cache) { | ||
if (cache.hasParseResult(tsconfig)) { | ||
return tsconfig; | ||
} | ||
if (path.basename(tsconfig) === 'tsconfig.json') { | ||
const dir = path.dirname(tsconfig); | ||
if (cache.hasTSConfigPath(dir)) { | ||
const cached = await cache.getTSConfigPath(dir); | ||
return cached === tsconfig ? tsconfig : undefined; | ||
} | ||
} | ||
} | ||
try { | ||
@@ -63,7 +79,9 @@ const stat = await fs.stat(tsconfig); | ||
*/ | ||
export function posix2native(filename) { | ||
return path.posix.sep !== path.sep && filename.includes(path.posix.sep) | ||
? filename.replace(POSIX_SEP_RE, path.sep) | ||
: filename; | ||
} | ||
export const posix2native = IS_POSIX | ||
? (s) => s | ||
: (filename) => { | ||
return filename.includes(path.posix.sep) | ||
? filename.replace(POSIX_SEP_RE, path.sep) | ||
: filename; | ||
}; | ||
@@ -80,7 +98,9 @@ /** | ||
*/ | ||
export function native2posix(filename) { | ||
return path.posix.sep !== path.sep && filename.includes(path.sep) | ||
? filename.replace(NATIVE_SEP_RE, path.posix.sep) | ||
: filename; | ||
} | ||
export const native2posix = IS_POSIX | ||
? (s) => s | ||
: (filename) => { | ||
return filename.includes(path.sep) | ||
? filename.replace(NATIVE_SEP_RE, path.posix.sep) | ||
: filename; | ||
}; | ||
@@ -97,3 +117,3 @@ /** | ||
export function resolve2posix(dir, filename) { | ||
if (path.sep === path.posix.sep) { | ||
if (IS_POSIX) { | ||
return dir ? path.resolve(dir, filename) : path.resolve(filename); | ||
@@ -100,0 +120,0 @@ } |
@@ -7,5 +7,5 @@ declare module 'tsconfck' { | ||
* @param options - options | ||
* @returns absolute path to closest tsconfig.json | ||
* @returns absolute path to closest tsconfig.json or null if not found | ||
*/ | ||
export function find(filename: string, options?: TSConfckFindOptions | undefined): Promise<string>; | ||
export function find(filename: string, options?: TSConfckFindOptions | undefined): Promise<string | null>; | ||
/** | ||
@@ -36,8 +36,7 @@ * find all tsconfig.json files in dir | ||
export function findNative(filename: string, options?: TSConfckFindOptions | undefined): Promise<string>; | ||
export class TSConfckCache { | ||
export class TSConfckCache<T> { | ||
/** | ||
* clear cache, use this if you have a long running process and tsconfig files have been added,changed or deleted | ||
* await it to ensure all find and parse calls are settled before continuing | ||
*/ | ||
clear(): Promise<void>; | ||
clear(): void; | ||
/** | ||
@@ -50,3 +49,3 @@ * has cached closest tsconfig for files in dir | ||
* */ | ||
getTSConfigPath(dir: string): Awaitable<string | null>; | ||
getTSConfigPath(dir: string): Promise<string | null> | string | null; | ||
/** | ||
@@ -59,8 +58,6 @@ * has parsed tsconfig for file | ||
* */ | ||
getParseResult(file: string): Awaitable<TSConfckParseResult | TSConfckParseNativeResult>; | ||
getParseResult(file: string): Promise<T> | T; | ||
private setParseResult; | ||
private deleteParseResult; | ||
private setTSConfigPath; | ||
@@ -72,3 +69,3 @@ #private; | ||
* | ||
* @param filename - path to a tsconfig.json or a .ts source file (absolute or relative to cwd) | ||
* @param filename - path to a tsconfig .json or a source file or directory (absolute or relative to cwd) | ||
* @param options - options | ||
@@ -139,3 +136,3 @@ * */ | ||
*/ | ||
cache?: TSConfckCache; | ||
cache?: TSConfckCache<TSConfckParseResult | TSConfckParseNativeResult>; | ||
@@ -150,2 +147,6 @@ /** | ||
interface TSConfckParseOptions extends TSConfckFindOptions { | ||
// same as find options | ||
} | ||
interface TSConfckFindAllOptions { | ||
@@ -160,10 +161,2 @@ /** | ||
interface TSConfckParseOptions extends TSConfckFindOptions { | ||
/** | ||
* treat missing tsconfig as empty result instead of an error | ||
* parse resolves with { filename: 'no_tsconfig_file_found',tsconfig:{}} instead of reject with error | ||
*/ | ||
resolveWithEmptyIfConfigNotFound?: boolean; | ||
} | ||
interface TSConfckParseResult { | ||
@@ -236,6 +229,4 @@ /** | ||
} | ||
type Awaitable<T> = Promise<T> | T; | ||
} | ||
//# sourceMappingURL=index.d.ts.map |
Sorry, the diff of this file is not supported yet
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
1
60250
1702