qoi-cli
Version:
520 lines (509 loc) • 13.8 kB
JavaScript
import Module, { builtinModules } from 'module';
import path from 'path';
import ts from 'typescript';
import fs from 'fs';
import fs$1 from 'node:fs';
import path__default from 'node:path';
let LogLevel = /*#__PURE__*/function (LogLevel) {
LogLevel[LogLevel["None"] = 0] = "None";
LogLevel[LogLevel["Error"] = 1] = "Error";
LogLevel[LogLevel["Warning"] = 2] = "Warning";
LogLevel[LogLevel["Info"] = 3] = "Info";
LogLevel[LogLevel["Debug"] = 4] = "Debug";
LogLevel[LogLevel["Trace"] = 5] = "Trace";
return LogLevel;
}({});
function convertLogLevel(level) {
switch (level) {
case "none":
return LogLevel.None;
case "error":
return LogLevel.Error;
case "warn":
return LogLevel.Warning;
case "info":
return LogLevel.Info;
case "debug":
return LogLevel.Debug;
case "trace":
return LogLevel.Trace;
default:
return undefined;
}
}
const Reset = "\x1b[0m";
const FgRed = "\x1b[31m";
const FgYellow = "\x1b[33m";
const FgBlue = "\x1b[34m";
const FgCyan = "\x1b[36m";
const FgMagenta = "\x1b[35m";
function fromTYPESCRIPT_PATHS_LOG_LEVEL() {
const env = process.env["TYPESCRIPT_PATHS_LOG_LEVEL"];
switch (env) {
case "none":
return LogLevel.None;
case "error":
return LogLevel.Error;
case "warn":
return LogLevel.Warning;
case "info":
return LogLevel.Info;
case "debug":
return LogLevel.Debug;
case "trace":
return LogLevel.Trace;
default:
return undefined;
}
}
function createLogger({
logLevel = fromTYPESCRIPT_PATHS_LOG_LEVEL() || LogLevel.Info,
colors = true,
ID = "typescript-paths"
} = {}) {
return function log(level, ...args) {
if (logLevel < level) {
return;
}
if (ID) {
args.unshift(`[${ID}]:`);
}
if (colors) {
args = args.map(a => {
if (typeof a !== "string") return a;
switch (level) {
case LogLevel.Error:
return FgRed + a + Reset;
case LogLevel.Warning:
return FgYellow + a + Reset;
case LogLevel.Info:
return FgBlue + a + Reset;
case LogLevel.Debug:
return FgCyan + a + Reset;
case LogLevel.Trace:
return FgMagenta + a + Reset;
}
});
}
switch (level) {
case LogLevel.Error:
console.error(...args);
break;
case LogLevel.Warning:
console.warn(...args);
break;
case LogLevel.Info:
console.info(...args);
break;
case LogLevel.Debug:
console.log(...args);
break;
case LogLevel.Trace:
console.log(...args);
break;
}
};
}
function getTsConfig({
tsConfigPath,
log = createLogger(),
host = ts.sys
}) {
const {
error,
config
} = ts.readConfigFile(tsConfigPath, host.readFile);
if (error) {
let hasError = false;
switch (error.category) {
case ts.DiagnosticCategory.Error:
log(LogLevel.Error, error.messageText);
hasError = true;
break;
}
if (hasError) return undefined;
}
let {
options: compilerOptions,
errors,
fileNames,
projectReferences
} = ts.parseJsonConfigFileContent(config, host, path.resolve(path.dirname(tsConfigPath)));
if (errors.length > 0) {
let hasError = false;
for (const error of errors) {
switch (error.category) {
case ts.DiagnosticCategory.Error:
log(LogLevel.Error, error.messageText);
hasError = true;
break;
}
}
if (hasError) return undefined;
}
const ret = {
filePath: path.resolve(tsConfigPath),
compilerOptions,
fileNames: fileNames.map(path.normalize),
extends: config.extends
};
if (projectReferences) {
ret.references = [];
for (const r of projectReferences) {
let tsConfigPath = r.path;
try {
const stat = fs.lstatSync(tsConfigPath);
if (stat.isDirectory()) {
tsConfigPath = path.join(tsConfigPath, "tsconfig.json");
}
} catch (err) {
const error = err;
log(LogLevel.Error, error.message);
return undefined;
}
const cfg = getTsConfig({
tsConfigPath,
log,
host
});
if (cfg) ret.references.push(cfg);
}
}
return ret;
}
function createMappings({
paths,
log = createLogger(),
respectCoreModule = true
}) {
const countWildcard = value => value.match(/\*/g)?.length ?? 0;
const mappings = [];
for (const pattern of Object.keys(paths)) {
if (countWildcard(pattern) > 1) {
log(LogLevel.Warning, `Pattern '${pattern}' can have at most one '*' character.`);
continue;
}
const wildcard = pattern.indexOf("*");
if (respectCoreModule) {
let skip = false;
for (const key of builtinModules) {
if (pattern === key || pattern.startsWith(key + "/")) {
log(LogLevel.Warning, `path pattern '${pattern}' is ignored.`);
log(LogLevel.Info, `respect core module '${key}'.`);
skip = true;
}
}
if (skip) continue;
}
const targets = paths[pattern].filter(target => {
if (countWildcard(target) > 1) {
log(LogLevel.Warning, `Substitution '${target}' in pattern '${pattern}' can have at most one '*' character.`);
return false;
}
return true;
});
if (targets.length === 0) {
continue;
}
if (pattern === "*") {
mappings.push({
wildcard: true,
pattern,
prefix: "",
suffix: "",
targets
});
continue;
}
mappings.push({
wildcard: wildcard !== -1,
pattern,
prefix: pattern.substring(0, wildcard),
suffix: pattern.substring(wildcard + 1),
targets
});
}
for (const mapping of mappings) {
log(LogLevel.Debug, `pattern: '${mapping.pattern}' targets: '${mapping.targets}'`);
}
return mappings;
}
function isPatternMatch(prefix, suffix, candidate) {
return candidate.length >= prefix.length + suffix.length && candidate.startsWith(prefix) && candidate.endsWith(suffix);
}
function findMatch(moduleName, mappings) {
let longestMatchedPrefixLength = -1;
let matched;
for (const mapping of mappings) {
const {
wildcard,
prefix,
suffix,
pattern
} = mapping;
if (wildcard && isPatternMatch(prefix, suffix, moduleName)) {
if (longestMatchedPrefixLength < prefix.length) {
longestMatchedPrefixLength = prefix.length;
matched = mapping;
}
} else if (moduleName === pattern) {
matched = mapping;
break;
}
}
return matched;
}
function resolveModuleName({
mappings,
request,
importer,
compilerOptions,
host,
falllback
}) {
const matched = findMatch(request, mappings);
if (!matched) {
return undefined;
}
const matchedWildcard = request.slice(matched.prefix.length, request.length - matched.suffix.length);
for (const target of matched.targets) {
const updated = matched.wildcard ? target.replace("*", matchedWildcard) : target;
const base = compilerOptions.baseUrl ?? compilerOptions.pathsBasePath;
const moduleName = path.resolve(base, updated);
const ext = path.extname(moduleName);
switch (ext) {
case ".ts":
case ".tsx":
case ".json":
return moduleName;
case ".js":
case ".jsx":
if (compilerOptions.module === ts.ModuleKind.NodeNext) {
break;
}
return moduleName;
}
const result = ts.resolveModuleName(moduleName, importer, compilerOptions, host);
if (result?.resolvedModule) {
return path.normalize(result.resolvedModule.resolvedFileName);
}
if (falllback?.(moduleName)) return moduleName;
}
return undefined;
}
function fromTS_NODE_PROJECT() {
const env = process.env["TS_NODE_PROJECT"];
if (env) return env.split(path.delimiter).filter(Boolean);
return undefined;
}
function createHandler({
searchPath,
tsConfigPath = fromTS_NODE_PROJECT(),
respectCoreModule = true,
log = createLogger(),
falllback
} = {}) {
if (!tsConfigPath) {
if (searchPath && searchPath instanceof Array) {
tsConfigPath = searchPath.map(p => ts.findConfigFile(p, ts.sys.fileExists)).filter(v => Boolean(v));
} else {
tsConfigPath = ts.findConfigFile(searchPath || ts.sys.getCurrentDirectory(), ts.sys.fileExists) || [];
}
}
const host = {
...ts.sys,
fileExists(filename) {
if (filename.endsWith(ts.Extension.Dts)) return false;
return ts.sys.fileExists(filename);
}
};
const services = [];
const configs = spreadTsConfig(tsConfigPath);
if (!configs) {
// can't read tsconfig files
return undefined;
}
for (const config of configs) {
addServices(config);
}
return (request, importer) => services.reduce((result, srv) => {
if (result) {
return result;
}
const {
compilerOptions,
cache,
fileNames,
mappings
} = srv;
const exist = cache.get(importer);
if (exist !== undefined) {
cache.delete(importer);
cache.set(importer, exist);
if (!exist) return undefined;
} else if (fileNames.indexOf(importer) === -1) {
if (cache.size === 1 << 8) cache.delete(cache.keys().next().value);
cache.set(importer, false);
return undefined;
} else {
if (cache.size === 1 << 8) cache.delete(cache.keys().next().value);
cache.set(importer, true);
}
return resolveModuleName({
compilerOptions,
host,
importer,
request,
mappings,
falllback
});
}, undefined);
function addServices(config) {
const {
compilerOptions,
fileNames,
references
} = config;
if (!compilerOptions.paths || compilerOptions.paths instanceof Array) return;
services.push({
compilerOptions,
fileNames,
mappings: createMappings({
log,
respectCoreModule,
paths: compilerOptions.paths
}),
cache: new Map()
});
if (references) {
for (const config of references) {
addServices(config);
}
}
}
function spreadTsConfig(tsConfigPath) {
if (typeof tsConfigPath === "string") {
tsConfigPath = [tsConfigPath];
} else if (!(tsConfigPath instanceof Array)) {
tsConfigPath = [tsConfigPath];
}
const configs = [];
for (const configPayloadOrPath of tsConfigPath) {
if (typeof configPayloadOrPath === "string") {
log(LogLevel.Trace, `loading: ${configPayloadOrPath}`);
}
const config = typeof configPayloadOrPath === "string" ? getTsConfig({
tsConfigPath: configPayloadOrPath,
host: ts.sys,
log
}) : configPayloadOrPath;
if (!config) {
return undefined;
}
configs.push(config);
}
// const resolvedConfigs = configs
// .map(c => {
// if (c.filePath && c.extends) {
// let tsconfigPath = path.join(path.dirname(c.filePath), c.extends)
// if (!tsconfigPath.endsWith(".json")) {
// tsconfigPath = tsconfigPath + ".json"
// }
// const exts = spreadTsConfig(tsconfigPath)
// if (exts) {
// return [c, ...exts]
// }
// }
// return c
// })
// .flat()
// for (const v of resolvedConfigs) {
// if (v == undefined) {
// return undefined
// }
// }
return configs;
}
}
function register({
tsConfigPath,
respectCoreModule,
logLevel,
colors,
loggerID,
falllback
} = {}) {
const log = createLogger({
logLevel: convertLogLevel(logLevel),
colors,
ID: loggerID
});
const handler = createHandler({
tsConfigPath,
respectCoreModule,
log,
falllback
});
if (!handler) {
return () => {};
}
const originalResolveFilename = Module["_resolveFilename"];
Module["_resolveFilename"] = function (request, parent, ...args) {
if (!parent) return originalResolveFilename.apply(this, arguments);
const moduleName = handler(request, parent.filename);
if (moduleName) {
log(LogLevel.Debug, `${request} -> ${moduleName}`);
return originalResolveFilename.apply(this, [moduleName, parent, ...args]);
}
return originalResolveFilename.apply(this, arguments);
};
return () => {
Module["_resolveFilename"] = originalResolveFilename;
};
}
// src/plugin.ts
var PLUGIN_NAME = "tsconfig-paths";
function tsConfigPaths({
tsConfigPath,
respectCoreModule,
logLevel,
colors = true
} = {}) {
let log;
let handler;
return {
name: PLUGIN_NAME,
buildStart() {
log = createLogger({ logLevel: convertLogLevel(logLevel), colors, ID: PLUGIN_NAME });
log(LogLevel.Debug, `typescript version: ${ts.version}`);
handler = createHandler({
log,
tsConfigPath,
respectCoreModule,
falllback: (moduleName) => fs$1.existsSync(moduleName)
});
return;
},
async resolveId(request, importer, options) {
if (!importer || request.startsWith("\0")) {
return null;
}
const moduleName = handler == null ? void 0 : handler(request, importer);
if (!moduleName) {
return this.resolve(request, importer, { skipSelf: true, ...options });
}
log(LogLevel.Debug, `${request} -> ${moduleName}`);
if (!path__default.extname(moduleName)) {
return this.resolve(moduleName, importer, { skipSelf: true, ...options });
}
return moduleName;
}
};
}
// src/index.mts
var src_default = tsConfigPaths;
const tsconfigPaths = ()=>src_default();
const register$$ = (options)=>{
return register(options);
};
export { register$$, tsconfigPaths };