deslop-js 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +150 -48
- package/dist/index.mjs +150 -48
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -196,7 +196,11 @@ const IMPLICIT_DEPENDENCIES = new Set([
|
|
|
196
196
|
"terser",
|
|
197
197
|
"autoprefixer",
|
|
198
198
|
"tailwindcss",
|
|
199
|
-
"react-test-renderer"
|
|
199
|
+
"react-test-renderer",
|
|
200
|
+
"esbuild",
|
|
201
|
+
"typedoc",
|
|
202
|
+
"commitizen",
|
|
203
|
+
"cz-conventional-changelog"
|
|
200
204
|
]);
|
|
201
205
|
const BUILTIN_MODULES = new Set([
|
|
202
206
|
"assert",
|
|
@@ -3715,12 +3719,13 @@ const TSCONFIG_FILENAMES = [
|
|
|
3715
3719
|
"tsconfig.json",
|
|
3716
3720
|
"tsconfig.web.json",
|
|
3717
3721
|
"tsconfig.app.json",
|
|
3718
|
-
"tsconfig.base.json"
|
|
3722
|
+
"tsconfig.base.json",
|
|
3723
|
+
"jsconfig.json"
|
|
3719
3724
|
];
|
|
3720
|
-
const findNearestTsconfig = (fromDir, rootDir) => {
|
|
3725
|
+
const findNearestTsconfig = (fromDir, rootDir, monorepoRootDir) => {
|
|
3721
3726
|
let currentDirectory = fromDir;
|
|
3722
|
-
const
|
|
3723
|
-
while (currentDirectory.length >=
|
|
3727
|
+
const stopAt = monorepoRootDir ? (0, node_path.resolve)(monorepoRootDir) : (0, node_path.resolve)(rootDir);
|
|
3728
|
+
while (currentDirectory.length >= stopAt.length) {
|
|
3724
3729
|
for (const tsconfigFilename of TSCONFIG_FILENAMES) {
|
|
3725
3730
|
const tsconfigCandidate = (0, node_path.join)(currentDirectory, tsconfigFilename);
|
|
3726
3731
|
if (cachedExistsSync(tsconfigCandidate)) return tsconfigCandidate;
|
|
@@ -3797,16 +3802,17 @@ const createResolver = (config, workspacePackages = [], options = {}) => {
|
|
|
3797
3802
|
for (const workspacePackage of workspacePackages) workspaceNameToDirectory.set(workspacePackage.name, workspacePackage.directory);
|
|
3798
3803
|
let rootTsconfigPath;
|
|
3799
3804
|
if (config.tsConfigPath) rootTsconfigPath = (0, node_path.resolve)(config.rootDir, config.tsConfigPath);
|
|
3800
|
-
else
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3805
|
+
else {
|
|
3806
|
+
const tsconfigSearchDirs = options.monorepoRoot ? [config.rootDir, options.monorepoRoot] : [config.rootDir];
|
|
3807
|
+
for (const searchDir of tsconfigSearchDirs) {
|
|
3808
|
+
for (const candidate of TSCONFIG_FILENAMES) {
|
|
3809
|
+
const candidatePath = (0, node_path.resolve)(searchDir, candidate);
|
|
3810
|
+
if (cachedExistsSync(candidatePath)) {
|
|
3811
|
+
rootTsconfigPath = candidatePath;
|
|
3812
|
+
break;
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
if (rootTsconfigPath) break;
|
|
3810
3816
|
}
|
|
3811
3817
|
}
|
|
3812
3818
|
const tsconfigPathCache = /* @__PURE__ */ new Map();
|
|
@@ -3815,24 +3821,56 @@ const createResolver = (config, workspacePackages = [], options = {}) => {
|
|
|
3815
3821
|
const fileDir = (0, node_path.dirname)(filePath);
|
|
3816
3822
|
const cached = tsconfigPathCache.get(fileDir);
|
|
3817
3823
|
if (cached !== void 0) return cached;
|
|
3818
|
-
const tsconfigResult = findNearestTsconfig(fileDir, config.rootDir) ?? rootTsconfigPath;
|
|
3824
|
+
const tsconfigResult = findNearestTsconfig(fileDir, config.rootDir, options.monorepoRoot) ?? rootTsconfigPath;
|
|
3819
3825
|
tsconfigPathCache.set(fileDir, tsconfigResult);
|
|
3820
3826
|
return tsconfigResult;
|
|
3821
3827
|
};
|
|
3822
3828
|
const tsconfigBaseUrlCache = /* @__PURE__ */ new Map();
|
|
3823
|
-
const
|
|
3824
|
-
|
|
3825
|
-
|
|
3829
|
+
const resolveExtendsPath = (extendsValue, fromDir) => {
|
|
3830
|
+
if (extendsValue.startsWith(".")) {
|
|
3831
|
+
const absolutePath = (0, node_path.resolve)(fromDir, extendsValue);
|
|
3832
|
+
if (cachedExistsSync(absolutePath)) return absolutePath;
|
|
3833
|
+
if (cachedExistsSync(absolutePath + ".json")) return absolutePath + ".json";
|
|
3834
|
+
return;
|
|
3835
|
+
}
|
|
3836
|
+
const packagePath = (0, node_path.join)(options.monorepoRoot ?? config.rootDir, "node_modules", extendsValue);
|
|
3837
|
+
if (cachedExistsSync(packagePath)) return packagePath;
|
|
3838
|
+
if (cachedExistsSync(packagePath + ".json")) return packagePath + ".json";
|
|
3839
|
+
const localPackagePath = (0, node_path.join)(fromDir, "node_modules", extendsValue);
|
|
3840
|
+
if (cachedExistsSync(localPackagePath)) return localPackagePath;
|
|
3841
|
+
if (cachedExistsSync(localPackagePath + ".json")) return localPackagePath + ".json";
|
|
3842
|
+
};
|
|
3843
|
+
const collectExtendsEntries = (tsconfigJson) => {
|
|
3844
|
+
if (typeof tsconfigJson.extends === "string") return [tsconfigJson.extends];
|
|
3845
|
+
if (Array.isArray(tsconfigJson.extends)) return tsconfigJson.extends.filter((entry) => typeof entry === "string");
|
|
3846
|
+
return [];
|
|
3847
|
+
};
|
|
3848
|
+
const extractBaseUrlFromTsconfig = (tsconfigFile, visitedFiles) => {
|
|
3849
|
+
if (visitedFiles.has(tsconfigFile)) return void 0;
|
|
3850
|
+
visitedFiles.add(tsconfigFile);
|
|
3826
3851
|
try {
|
|
3827
3852
|
const cleanedContent = stripJsonComments(cachedReadFileSync(tsconfigFile));
|
|
3828
|
-
const
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3853
|
+
const tsconfigJson = JSON.parse(cleanedContent);
|
|
3854
|
+
const tsconfigDir = (0, node_path.dirname)(tsconfigFile);
|
|
3855
|
+
const baseUrl = tsconfigJson.compilerOptions?.baseUrl;
|
|
3856
|
+
if (baseUrl) return (0, node_path.resolve)(tsconfigDir, baseUrl);
|
|
3857
|
+
for (const extendsEntry of collectExtendsEntries(tsconfigJson)) {
|
|
3858
|
+
const resolvedPath = resolveExtendsPath(extendsEntry, tsconfigDir);
|
|
3859
|
+
if (resolvedPath) {
|
|
3860
|
+
const result = extractBaseUrlFromTsconfig(resolvedPath, visitedFiles);
|
|
3861
|
+
if (result) return result;
|
|
3862
|
+
}
|
|
3833
3863
|
}
|
|
3834
|
-
} catch {
|
|
3835
|
-
|
|
3864
|
+
} catch {
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
};
|
|
3868
|
+
const getBaseUrlDirectory = (tsconfigFile) => {
|
|
3869
|
+
const cached = tsconfigBaseUrlCache.get(tsconfigFile);
|
|
3870
|
+
if (cached !== void 0) return cached;
|
|
3871
|
+
const result = extractBaseUrlFromTsconfig(tsconfigFile, /* @__PURE__ */ new Set());
|
|
3872
|
+
tsconfigBaseUrlCache.set(tsconfigFile, result);
|
|
3873
|
+
return result;
|
|
3836
3874
|
};
|
|
3837
3875
|
const hasNextJsDependency = (() => {
|
|
3838
3876
|
try {
|
|
@@ -3845,25 +3883,46 @@ const createResolver = (config, workspacePackages = [], options = {}) => {
|
|
|
3845
3883
|
return false;
|
|
3846
3884
|
}
|
|
3847
3885
|
})();
|
|
3848
|
-
const
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
const aliasMap = /* @__PURE__ */ new Map();
|
|
3852
|
-
const tsconfigDir = (0, node_path.dirname)(tsconfigFile);
|
|
3886
|
+
const extractPathsFromTsconfig = (tsconfigFile, visitedFiles) => {
|
|
3887
|
+
if (visitedFiles.has(tsconfigFile)) return void 0;
|
|
3888
|
+
visitedFiles.add(tsconfigFile);
|
|
3853
3889
|
try {
|
|
3854
3890
|
const tsconfigContent = cachedReadFileSync(tsconfigFile).trim();
|
|
3855
|
-
if (tsconfigContent.length
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3891
|
+
if (tsconfigContent.length === 0) return void 0;
|
|
3892
|
+
const cleanedContent = stripJsonComments(tsconfigContent);
|
|
3893
|
+
const tsconfigJson = JSON.parse(cleanedContent);
|
|
3894
|
+
const tsconfigDir = (0, node_path.dirname)(tsconfigFile);
|
|
3895
|
+
const paths = tsconfigJson.compilerOptions?.paths;
|
|
3896
|
+
const baseUrl = tsconfigJson.compilerOptions?.baseUrl;
|
|
3897
|
+
if (paths && typeof paths === "object") return {
|
|
3898
|
+
paths,
|
|
3899
|
+
baseUrl: baseUrl ?? ".",
|
|
3900
|
+
tsconfigDir
|
|
3901
|
+
};
|
|
3902
|
+
for (const extendsEntry of collectExtendsEntries(tsconfigJson)) {
|
|
3903
|
+
const resolvedPath = resolveExtendsPath(extendsEntry, tsconfigDir);
|
|
3904
|
+
if (resolvedPath) {
|
|
3905
|
+
const result = extractPathsFromTsconfig(resolvedPath, visitedFiles);
|
|
3906
|
+
if (result) return result;
|
|
3862
3907
|
}
|
|
3863
3908
|
}
|
|
3864
|
-
} catch {
|
|
3865
|
-
|
|
3866
|
-
|
|
3909
|
+
} catch {
|
|
3910
|
+
return;
|
|
3911
|
+
}
|
|
3912
|
+
};
|
|
3913
|
+
const getPathAliases = (tsconfigFile) => {
|
|
3914
|
+
const cached = tsconfigPathAliasCache.get(tsconfigFile);
|
|
3915
|
+
if (cached) return cached;
|
|
3916
|
+
const aliasMap = /* @__PURE__ */ new Map();
|
|
3917
|
+
const extracted = extractPathsFromTsconfig(tsconfigFile, /* @__PURE__ */ new Set());
|
|
3918
|
+
if (extracted) {
|
|
3919
|
+
for (const [pattern, targets] of Object.entries(extracted.paths)) if (Array.isArray(targets)) aliasMap.set(pattern, targets.map((target) => (0, node_path.resolve)(extracted.tsconfigDir, extracted.baseUrl, target)));
|
|
3920
|
+
}
|
|
3921
|
+
if (aliasMap.size === 0 && hasNextJsDependency) {
|
|
3922
|
+
const tsconfigDir = (0, node_path.dirname)(tsconfigFile);
|
|
3923
|
+
if (cachedExistsSync((0, node_path.resolve)(tsconfigDir, "src"))) aliasMap.set("@/*", [(0, node_path.resolve)(tsconfigDir, "src/*")]);
|
|
3924
|
+
else aliasMap.set("@/*", [(0, node_path.resolve)(tsconfigDir, "*")]);
|
|
3925
|
+
}
|
|
3867
3926
|
tsconfigPathAliasCache.set(tsconfigFile, aliasMap);
|
|
3868
3927
|
return aliasMap;
|
|
3869
3928
|
};
|
|
@@ -4509,13 +4568,15 @@ const EXCLUDED_EXTENSIONS = new Set([
|
|
|
4509
4568
|
".graphql",
|
|
4510
4569
|
".gql"
|
|
4511
4570
|
]);
|
|
4512
|
-
const TEST_FILE_PATTERN = /(?:\.(?:test|spec|stories|story)\.|(?:^|\/)__tests__\/)/;
|
|
4571
|
+
const TEST_FILE_PATTERN = /(?:\.(?:test|spec|stories|story|cy)\.|(?:^|\/)__tests__\/)/;
|
|
4572
|
+
const EXCLUDED_DIRECTORY_PATTERN = /(?:^|\/)(?:e2e|cypress|playwright|__fixtures__|__snapshots__|scripts)\/(?!.*node_modules)/;
|
|
4573
|
+
const CONFIG_FILE_PATTERN = /(?:^|\/)(?:[^/]+\.config\.[tj]sx?$|[^/]+\.setup\.[tj]sx?$|setupTests\.[tj]sx?$|jest\.setup\.[tj]sx?$|vitest\.setup\.[tj]sx?$)/;
|
|
4513
4574
|
const hasExcludedExtension = (filePath) => {
|
|
4514
4575
|
const lastDot = filePath.lastIndexOf(".");
|
|
4515
4576
|
if (lastDot === -1) return false;
|
|
4516
4577
|
return EXCLUDED_EXTENSIONS.has(filePath.slice(lastDot));
|
|
4517
4578
|
};
|
|
4518
|
-
const
|
|
4579
|
+
const isExcludedByPattern = (filePath) => TEST_FILE_PATTERN.test(filePath) || EXCLUDED_DIRECTORY_PATTERN.test(filePath) || CONFIG_FILE_PATTERN.test(filePath);
|
|
4519
4580
|
const detectOrphanFiles = (graph) => {
|
|
4520
4581
|
const unusedFiles = [];
|
|
4521
4582
|
for (const module of graph.modules) {
|
|
@@ -4524,7 +4585,7 @@ const detectOrphanFiles = (graph) => {
|
|
|
4524
4585
|
if (module.isDeclarationFile) continue;
|
|
4525
4586
|
if (module.isConfigFile) continue;
|
|
4526
4587
|
if (hasExcludedExtension(module.fileId.path)) continue;
|
|
4527
|
-
if (
|
|
4588
|
+
if (isExcludedByPattern(module.fileId.path)) continue;
|
|
4528
4589
|
if (isBarrelWithReachableSources(module, graph)) continue;
|
|
4529
4590
|
if (hasReachableDirectImporter(module.fileId.index, graph)) continue;
|
|
4530
4591
|
unusedFiles.push({ path: module.fileId.path });
|
|
@@ -4688,7 +4749,7 @@ const extractPackageName = (specifier) => {
|
|
|
4688
4749
|
};
|
|
4689
4750
|
|
|
4690
4751
|
//#endregion
|
|
4691
|
-
//#region src/
|
|
4752
|
+
//#region src/utils/find-monorepo-root.ts
|
|
4692
4753
|
const MONOREPO_ROOT_MARKERS = [
|
|
4693
4754
|
"pnpm-workspace.yaml",
|
|
4694
4755
|
"pnpm-workspace.yml",
|
|
@@ -4734,6 +4795,9 @@ const findMonorepoRoot = (rootDir) => {
|
|
|
4734
4795
|
for (const lockfile of LOCKFILE_MARKERS) if ((0, node_fs.existsSync)((0, node_path.join)(currentDirectory, lockfile))) return currentDirectory;
|
|
4735
4796
|
}
|
|
4736
4797
|
};
|
|
4798
|
+
|
|
4799
|
+
//#endregion
|
|
4800
|
+
//#region src/report/packages.ts
|
|
4737
4801
|
const discoverAllPackageJsonPaths = (rootDir) => {
|
|
4738
4802
|
const paths = [(0, node_path.join)(rootDir, "package.json")];
|
|
4739
4803
|
const workspacePackageJsons = fast_glob.default.sync("**/package.json", {
|
|
@@ -4794,6 +4858,30 @@ const detectStalePackages = (graph, config) => {
|
|
|
4794
4858
|
if (declaredNames.has("react-native")) usedPackageNames.add("react-native");
|
|
4795
4859
|
if (declaredNames.has("react-native-web")) usedPackageNames.add("react-native-web");
|
|
4796
4860
|
}
|
|
4861
|
+
if (declaredNames.has("react-dom")) {
|
|
4862
|
+
if ([
|
|
4863
|
+
"next",
|
|
4864
|
+
"gatsby",
|
|
4865
|
+
"@remix-run/react",
|
|
4866
|
+
"react-router-dom",
|
|
4867
|
+
"vite",
|
|
4868
|
+
"@docusaurus/core",
|
|
4869
|
+
"react-scripts",
|
|
4870
|
+
"astro",
|
|
4871
|
+
"@tanstack/react-router",
|
|
4872
|
+
"@tanstack/react-start",
|
|
4873
|
+
"react-app-rewired"
|
|
4874
|
+
].some((framework) => declaredNames.has(framework) || usedPackageNames.has(framework))) usedPackageNames.add("react-dom");
|
|
4875
|
+
}
|
|
4876
|
+
if (declaredNames.has("react") && declaredNames.has("react-dom")) {
|
|
4877
|
+
const packageJsonPath = (0, node_path.resolve)(config.rootDir, "package.json");
|
|
4878
|
+
try {
|
|
4879
|
+
const content = (0, node_fs.readFileSync)(packageJsonPath, "utf-8");
|
|
4880
|
+
const peerDeps = JSON.parse(content).peerDependencies ?? {};
|
|
4881
|
+
if ("react" in peerDeps && declaredDependencies.get("react") === true) usedPackageNames.add("react");
|
|
4882
|
+
if ("react-dom" in peerDeps && declaredDependencies.get("react-dom") === true) usedPackageNames.add("react-dom");
|
|
4883
|
+
} catch {}
|
|
4884
|
+
}
|
|
4797
4885
|
const peerSatisfied = collectPeerSatisfiedPackages(nodeModulesRoot, declaredNames, usedPackageNames);
|
|
4798
4886
|
for (const packageName of peerSatisfied) usedPackageNames.add(packageName);
|
|
4799
4887
|
const candidateUnused = /* @__PURE__ */ new Set();
|
|
@@ -5006,7 +5094,11 @@ const PACKAGE_JSON_CONFIG_SECTIONS = [
|
|
|
5006
5094
|
"commitlint",
|
|
5007
5095
|
"browserslist",
|
|
5008
5096
|
"postcss",
|
|
5009
|
-
"ava"
|
|
5097
|
+
"ava",
|
|
5098
|
+
"config",
|
|
5099
|
+
"pnpm",
|
|
5100
|
+
"resolutions",
|
|
5101
|
+
"overrides"
|
|
5010
5102
|
];
|
|
5011
5103
|
const collectPackageJsonConfigReferences = (packageJsonPath, declaredNames) => {
|
|
5012
5104
|
const referenced = /* @__PURE__ */ new Set();
|
|
@@ -5145,6 +5237,7 @@ const ALWAYS_USED_PREFIXES = [
|
|
|
5145
5237
|
"postcss-",
|
|
5146
5238
|
"@tailwindcss/",
|
|
5147
5239
|
"rollup-plugin-",
|
|
5240
|
+
"@rollup/",
|
|
5148
5241
|
"vite-plugin-",
|
|
5149
5242
|
"@vitejs/",
|
|
5150
5243
|
"webpack-",
|
|
@@ -5381,7 +5474,13 @@ const defineConfig = (options) => ({
|
|
|
5381
5474
|
const analyze = async (config) => {
|
|
5382
5475
|
const pipelineStartTime = performance.now();
|
|
5383
5476
|
const workspaceDiscovery = resolveWorkspaces((0, node_path.resolve)(config.rootDir));
|
|
5384
|
-
const workspacePackages = workspaceDiscovery.packages;
|
|
5477
|
+
const workspacePackages = [...workspaceDiscovery.packages];
|
|
5478
|
+
const monorepoRoot = findMonorepoRoot(config.rootDir);
|
|
5479
|
+
if (monorepoRoot) {
|
|
5480
|
+
const monorepoWorkspaces = resolveWorkspaces(monorepoRoot);
|
|
5481
|
+
const existingDirectories = new Set(workspacePackages.map((workspacePackage) => workspacePackage.directory));
|
|
5482
|
+
for (const monorepoPackage of monorepoWorkspaces.packages) if (!existingDirectories.has(monorepoPackage.directory)) workspacePackages.push(monorepoPackage);
|
|
5483
|
+
}
|
|
5385
5484
|
const frameworkIgnorePatterns = getFrameworkExclusions(config.rootDir);
|
|
5386
5485
|
const absoluteRoot = (0, node_path.resolve)(config.rootDir);
|
|
5387
5486
|
const outputDirectoryExclusions = OUTPUT_DIRECTORIES.flatMap((outputDirectory) => {
|
|
@@ -5407,7 +5506,10 @@ const analyze = async (config) => {
|
|
|
5407
5506
|
const moduleResolver = createResolver(config, workspacePackages.map((workspacePackage) => ({
|
|
5408
5507
|
name: workspacePackage.name,
|
|
5409
5508
|
directory: workspacePackage.directory
|
|
5410
|
-
})), {
|
|
5509
|
+
})), {
|
|
5510
|
+
hasReactNative,
|
|
5511
|
+
monorepoRoot
|
|
5512
|
+
});
|
|
5411
5513
|
const graphInputs = [];
|
|
5412
5514
|
for (const file of files) {
|
|
5413
5515
|
const parsedModule = parseSourceFile(file.path);
|
package/dist/index.mjs
CHANGED
|
@@ -166,7 +166,11 @@ const IMPLICIT_DEPENDENCIES = new Set([
|
|
|
166
166
|
"terser",
|
|
167
167
|
"autoprefixer",
|
|
168
168
|
"tailwindcss",
|
|
169
|
-
"react-test-renderer"
|
|
169
|
+
"react-test-renderer",
|
|
170
|
+
"esbuild",
|
|
171
|
+
"typedoc",
|
|
172
|
+
"commitizen",
|
|
173
|
+
"cz-conventional-changelog"
|
|
170
174
|
]);
|
|
171
175
|
const BUILTIN_MODULES = new Set([
|
|
172
176
|
"assert",
|
|
@@ -3685,12 +3689,13 @@ const TSCONFIG_FILENAMES = [
|
|
|
3685
3689
|
"tsconfig.json",
|
|
3686
3690
|
"tsconfig.web.json",
|
|
3687
3691
|
"tsconfig.app.json",
|
|
3688
|
-
"tsconfig.base.json"
|
|
3692
|
+
"tsconfig.base.json",
|
|
3693
|
+
"jsconfig.json"
|
|
3689
3694
|
];
|
|
3690
|
-
const findNearestTsconfig = (fromDir, rootDir) => {
|
|
3695
|
+
const findNearestTsconfig = (fromDir, rootDir, monorepoRootDir) => {
|
|
3691
3696
|
let currentDirectory = fromDir;
|
|
3692
|
-
const
|
|
3693
|
-
while (currentDirectory.length >=
|
|
3697
|
+
const stopAt = monorepoRootDir ? resolve(monorepoRootDir) : resolve(rootDir);
|
|
3698
|
+
while (currentDirectory.length >= stopAt.length) {
|
|
3694
3699
|
for (const tsconfigFilename of TSCONFIG_FILENAMES) {
|
|
3695
3700
|
const tsconfigCandidate = join(currentDirectory, tsconfigFilename);
|
|
3696
3701
|
if (cachedExistsSync(tsconfigCandidate)) return tsconfigCandidate;
|
|
@@ -3767,16 +3772,17 @@ const createResolver = (config, workspacePackages = [], options = {}) => {
|
|
|
3767
3772
|
for (const workspacePackage of workspacePackages) workspaceNameToDirectory.set(workspacePackage.name, workspacePackage.directory);
|
|
3768
3773
|
let rootTsconfigPath;
|
|
3769
3774
|
if (config.tsConfigPath) rootTsconfigPath = resolve(config.rootDir, config.tsConfigPath);
|
|
3770
|
-
else
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3775
|
+
else {
|
|
3776
|
+
const tsconfigSearchDirs = options.monorepoRoot ? [config.rootDir, options.monorepoRoot] : [config.rootDir];
|
|
3777
|
+
for (const searchDir of tsconfigSearchDirs) {
|
|
3778
|
+
for (const candidate of TSCONFIG_FILENAMES) {
|
|
3779
|
+
const candidatePath = resolve(searchDir, candidate);
|
|
3780
|
+
if (cachedExistsSync(candidatePath)) {
|
|
3781
|
+
rootTsconfigPath = candidatePath;
|
|
3782
|
+
break;
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
if (rootTsconfigPath) break;
|
|
3780
3786
|
}
|
|
3781
3787
|
}
|
|
3782
3788
|
const tsconfigPathCache = /* @__PURE__ */ new Map();
|
|
@@ -3785,24 +3791,56 @@ const createResolver = (config, workspacePackages = [], options = {}) => {
|
|
|
3785
3791
|
const fileDir = dirname(filePath);
|
|
3786
3792
|
const cached = tsconfigPathCache.get(fileDir);
|
|
3787
3793
|
if (cached !== void 0) return cached;
|
|
3788
|
-
const tsconfigResult = findNearestTsconfig(fileDir, config.rootDir) ?? rootTsconfigPath;
|
|
3794
|
+
const tsconfigResult = findNearestTsconfig(fileDir, config.rootDir, options.monorepoRoot) ?? rootTsconfigPath;
|
|
3789
3795
|
tsconfigPathCache.set(fileDir, tsconfigResult);
|
|
3790
3796
|
return tsconfigResult;
|
|
3791
3797
|
};
|
|
3792
3798
|
const tsconfigBaseUrlCache = /* @__PURE__ */ new Map();
|
|
3793
|
-
const
|
|
3794
|
-
|
|
3795
|
-
|
|
3799
|
+
const resolveExtendsPath = (extendsValue, fromDir) => {
|
|
3800
|
+
if (extendsValue.startsWith(".")) {
|
|
3801
|
+
const absolutePath = resolve(fromDir, extendsValue);
|
|
3802
|
+
if (cachedExistsSync(absolutePath)) return absolutePath;
|
|
3803
|
+
if (cachedExistsSync(absolutePath + ".json")) return absolutePath + ".json";
|
|
3804
|
+
return;
|
|
3805
|
+
}
|
|
3806
|
+
const packagePath = join(options.monorepoRoot ?? config.rootDir, "node_modules", extendsValue);
|
|
3807
|
+
if (cachedExistsSync(packagePath)) return packagePath;
|
|
3808
|
+
if (cachedExistsSync(packagePath + ".json")) return packagePath + ".json";
|
|
3809
|
+
const localPackagePath = join(fromDir, "node_modules", extendsValue);
|
|
3810
|
+
if (cachedExistsSync(localPackagePath)) return localPackagePath;
|
|
3811
|
+
if (cachedExistsSync(localPackagePath + ".json")) return localPackagePath + ".json";
|
|
3812
|
+
};
|
|
3813
|
+
const collectExtendsEntries = (tsconfigJson) => {
|
|
3814
|
+
if (typeof tsconfigJson.extends === "string") return [tsconfigJson.extends];
|
|
3815
|
+
if (Array.isArray(tsconfigJson.extends)) return tsconfigJson.extends.filter((entry) => typeof entry === "string");
|
|
3816
|
+
return [];
|
|
3817
|
+
};
|
|
3818
|
+
const extractBaseUrlFromTsconfig = (tsconfigFile, visitedFiles) => {
|
|
3819
|
+
if (visitedFiles.has(tsconfigFile)) return void 0;
|
|
3820
|
+
visitedFiles.add(tsconfigFile);
|
|
3796
3821
|
try {
|
|
3797
3822
|
const cleanedContent = stripJsonComments(cachedReadFileSync(tsconfigFile));
|
|
3798
|
-
const
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3823
|
+
const tsconfigJson = JSON.parse(cleanedContent);
|
|
3824
|
+
const tsconfigDir = dirname(tsconfigFile);
|
|
3825
|
+
const baseUrl = tsconfigJson.compilerOptions?.baseUrl;
|
|
3826
|
+
if (baseUrl) return resolve(tsconfigDir, baseUrl);
|
|
3827
|
+
for (const extendsEntry of collectExtendsEntries(tsconfigJson)) {
|
|
3828
|
+
const resolvedPath = resolveExtendsPath(extendsEntry, tsconfigDir);
|
|
3829
|
+
if (resolvedPath) {
|
|
3830
|
+
const result = extractBaseUrlFromTsconfig(resolvedPath, visitedFiles);
|
|
3831
|
+
if (result) return result;
|
|
3832
|
+
}
|
|
3803
3833
|
}
|
|
3804
|
-
} catch {
|
|
3805
|
-
|
|
3834
|
+
} catch {
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
3837
|
+
};
|
|
3838
|
+
const getBaseUrlDirectory = (tsconfigFile) => {
|
|
3839
|
+
const cached = tsconfigBaseUrlCache.get(tsconfigFile);
|
|
3840
|
+
if (cached !== void 0) return cached;
|
|
3841
|
+
const result = extractBaseUrlFromTsconfig(tsconfigFile, /* @__PURE__ */ new Set());
|
|
3842
|
+
tsconfigBaseUrlCache.set(tsconfigFile, result);
|
|
3843
|
+
return result;
|
|
3806
3844
|
};
|
|
3807
3845
|
const hasNextJsDependency = (() => {
|
|
3808
3846
|
try {
|
|
@@ -3815,25 +3853,46 @@ const createResolver = (config, workspacePackages = [], options = {}) => {
|
|
|
3815
3853
|
return false;
|
|
3816
3854
|
}
|
|
3817
3855
|
})();
|
|
3818
|
-
const
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
const aliasMap = /* @__PURE__ */ new Map();
|
|
3822
|
-
const tsconfigDir = dirname(tsconfigFile);
|
|
3856
|
+
const extractPathsFromTsconfig = (tsconfigFile, visitedFiles) => {
|
|
3857
|
+
if (visitedFiles.has(tsconfigFile)) return void 0;
|
|
3858
|
+
visitedFiles.add(tsconfigFile);
|
|
3823
3859
|
try {
|
|
3824
3860
|
const tsconfigContent = cachedReadFileSync(tsconfigFile).trim();
|
|
3825
|
-
if (tsconfigContent.length
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3861
|
+
if (tsconfigContent.length === 0) return void 0;
|
|
3862
|
+
const cleanedContent = stripJsonComments(tsconfigContent);
|
|
3863
|
+
const tsconfigJson = JSON.parse(cleanedContent);
|
|
3864
|
+
const tsconfigDir = dirname(tsconfigFile);
|
|
3865
|
+
const paths = tsconfigJson.compilerOptions?.paths;
|
|
3866
|
+
const baseUrl = tsconfigJson.compilerOptions?.baseUrl;
|
|
3867
|
+
if (paths && typeof paths === "object") return {
|
|
3868
|
+
paths,
|
|
3869
|
+
baseUrl: baseUrl ?? ".",
|
|
3870
|
+
tsconfigDir
|
|
3871
|
+
};
|
|
3872
|
+
for (const extendsEntry of collectExtendsEntries(tsconfigJson)) {
|
|
3873
|
+
const resolvedPath = resolveExtendsPath(extendsEntry, tsconfigDir);
|
|
3874
|
+
if (resolvedPath) {
|
|
3875
|
+
const result = extractPathsFromTsconfig(resolvedPath, visitedFiles);
|
|
3876
|
+
if (result) return result;
|
|
3832
3877
|
}
|
|
3833
3878
|
}
|
|
3834
|
-
} catch {
|
|
3835
|
-
|
|
3836
|
-
|
|
3879
|
+
} catch {
|
|
3880
|
+
return;
|
|
3881
|
+
}
|
|
3882
|
+
};
|
|
3883
|
+
const getPathAliases = (tsconfigFile) => {
|
|
3884
|
+
const cached = tsconfigPathAliasCache.get(tsconfigFile);
|
|
3885
|
+
if (cached) return cached;
|
|
3886
|
+
const aliasMap = /* @__PURE__ */ new Map();
|
|
3887
|
+
const extracted = extractPathsFromTsconfig(tsconfigFile, /* @__PURE__ */ new Set());
|
|
3888
|
+
if (extracted) {
|
|
3889
|
+
for (const [pattern, targets] of Object.entries(extracted.paths)) if (Array.isArray(targets)) aliasMap.set(pattern, targets.map((target) => resolve(extracted.tsconfigDir, extracted.baseUrl, target)));
|
|
3890
|
+
}
|
|
3891
|
+
if (aliasMap.size === 0 && hasNextJsDependency) {
|
|
3892
|
+
const tsconfigDir = dirname(tsconfigFile);
|
|
3893
|
+
if (cachedExistsSync(resolve(tsconfigDir, "src"))) aliasMap.set("@/*", [resolve(tsconfigDir, "src/*")]);
|
|
3894
|
+
else aliasMap.set("@/*", [resolve(tsconfigDir, "*")]);
|
|
3895
|
+
}
|
|
3837
3896
|
tsconfigPathAliasCache.set(tsconfigFile, aliasMap);
|
|
3838
3897
|
return aliasMap;
|
|
3839
3898
|
};
|
|
@@ -4479,13 +4538,15 @@ const EXCLUDED_EXTENSIONS = new Set([
|
|
|
4479
4538
|
".graphql",
|
|
4480
4539
|
".gql"
|
|
4481
4540
|
]);
|
|
4482
|
-
const TEST_FILE_PATTERN = /(?:\.(?:test|spec|stories|story)\.|(?:^|\/)__tests__\/)/;
|
|
4541
|
+
const TEST_FILE_PATTERN = /(?:\.(?:test|spec|stories|story|cy)\.|(?:^|\/)__tests__\/)/;
|
|
4542
|
+
const EXCLUDED_DIRECTORY_PATTERN = /(?:^|\/)(?:e2e|cypress|playwright|__fixtures__|__snapshots__|scripts)\/(?!.*node_modules)/;
|
|
4543
|
+
const CONFIG_FILE_PATTERN = /(?:^|\/)(?:[^/]+\.config\.[tj]sx?$|[^/]+\.setup\.[tj]sx?$|setupTests\.[tj]sx?$|jest\.setup\.[tj]sx?$|vitest\.setup\.[tj]sx?$)/;
|
|
4483
4544
|
const hasExcludedExtension = (filePath) => {
|
|
4484
4545
|
const lastDot = filePath.lastIndexOf(".");
|
|
4485
4546
|
if (lastDot === -1) return false;
|
|
4486
4547
|
return EXCLUDED_EXTENSIONS.has(filePath.slice(lastDot));
|
|
4487
4548
|
};
|
|
4488
|
-
const
|
|
4549
|
+
const isExcludedByPattern = (filePath) => TEST_FILE_PATTERN.test(filePath) || EXCLUDED_DIRECTORY_PATTERN.test(filePath) || CONFIG_FILE_PATTERN.test(filePath);
|
|
4489
4550
|
const detectOrphanFiles = (graph) => {
|
|
4490
4551
|
const unusedFiles = [];
|
|
4491
4552
|
for (const module of graph.modules) {
|
|
@@ -4494,7 +4555,7 @@ const detectOrphanFiles = (graph) => {
|
|
|
4494
4555
|
if (module.isDeclarationFile) continue;
|
|
4495
4556
|
if (module.isConfigFile) continue;
|
|
4496
4557
|
if (hasExcludedExtension(module.fileId.path)) continue;
|
|
4497
|
-
if (
|
|
4558
|
+
if (isExcludedByPattern(module.fileId.path)) continue;
|
|
4498
4559
|
if (isBarrelWithReachableSources(module, graph)) continue;
|
|
4499
4560
|
if (hasReachableDirectImporter(module.fileId.index, graph)) continue;
|
|
4500
4561
|
unusedFiles.push({ path: module.fileId.path });
|
|
@@ -4658,7 +4719,7 @@ const extractPackageName = (specifier) => {
|
|
|
4658
4719
|
};
|
|
4659
4720
|
|
|
4660
4721
|
//#endregion
|
|
4661
|
-
//#region src/
|
|
4722
|
+
//#region src/utils/find-monorepo-root.ts
|
|
4662
4723
|
const MONOREPO_ROOT_MARKERS = [
|
|
4663
4724
|
"pnpm-workspace.yaml",
|
|
4664
4725
|
"pnpm-workspace.yml",
|
|
@@ -4704,6 +4765,9 @@ const findMonorepoRoot = (rootDir) => {
|
|
|
4704
4765
|
for (const lockfile of LOCKFILE_MARKERS) if (existsSync(join(currentDirectory, lockfile))) return currentDirectory;
|
|
4705
4766
|
}
|
|
4706
4767
|
};
|
|
4768
|
+
|
|
4769
|
+
//#endregion
|
|
4770
|
+
//#region src/report/packages.ts
|
|
4707
4771
|
const discoverAllPackageJsonPaths = (rootDir) => {
|
|
4708
4772
|
const paths = [join(rootDir, "package.json")];
|
|
4709
4773
|
const workspacePackageJsons = fg.sync("**/package.json", {
|
|
@@ -4764,6 +4828,30 @@ const detectStalePackages = (graph, config) => {
|
|
|
4764
4828
|
if (declaredNames.has("react-native")) usedPackageNames.add("react-native");
|
|
4765
4829
|
if (declaredNames.has("react-native-web")) usedPackageNames.add("react-native-web");
|
|
4766
4830
|
}
|
|
4831
|
+
if (declaredNames.has("react-dom")) {
|
|
4832
|
+
if ([
|
|
4833
|
+
"next",
|
|
4834
|
+
"gatsby",
|
|
4835
|
+
"@remix-run/react",
|
|
4836
|
+
"react-router-dom",
|
|
4837
|
+
"vite",
|
|
4838
|
+
"@docusaurus/core",
|
|
4839
|
+
"react-scripts",
|
|
4840
|
+
"astro",
|
|
4841
|
+
"@tanstack/react-router",
|
|
4842
|
+
"@tanstack/react-start",
|
|
4843
|
+
"react-app-rewired"
|
|
4844
|
+
].some((framework) => declaredNames.has(framework) || usedPackageNames.has(framework))) usedPackageNames.add("react-dom");
|
|
4845
|
+
}
|
|
4846
|
+
if (declaredNames.has("react") && declaredNames.has("react-dom")) {
|
|
4847
|
+
const packageJsonPath = resolve(config.rootDir, "package.json");
|
|
4848
|
+
try {
|
|
4849
|
+
const content = readFileSync(packageJsonPath, "utf-8");
|
|
4850
|
+
const peerDeps = JSON.parse(content).peerDependencies ?? {};
|
|
4851
|
+
if ("react" in peerDeps && declaredDependencies.get("react") === true) usedPackageNames.add("react");
|
|
4852
|
+
if ("react-dom" in peerDeps && declaredDependencies.get("react-dom") === true) usedPackageNames.add("react-dom");
|
|
4853
|
+
} catch {}
|
|
4854
|
+
}
|
|
4767
4855
|
const peerSatisfied = collectPeerSatisfiedPackages(nodeModulesRoot, declaredNames, usedPackageNames);
|
|
4768
4856
|
for (const packageName of peerSatisfied) usedPackageNames.add(packageName);
|
|
4769
4857
|
const candidateUnused = /* @__PURE__ */ new Set();
|
|
@@ -4976,7 +5064,11 @@ const PACKAGE_JSON_CONFIG_SECTIONS = [
|
|
|
4976
5064
|
"commitlint",
|
|
4977
5065
|
"browserslist",
|
|
4978
5066
|
"postcss",
|
|
4979
|
-
"ava"
|
|
5067
|
+
"ava",
|
|
5068
|
+
"config",
|
|
5069
|
+
"pnpm",
|
|
5070
|
+
"resolutions",
|
|
5071
|
+
"overrides"
|
|
4980
5072
|
];
|
|
4981
5073
|
const collectPackageJsonConfigReferences = (packageJsonPath, declaredNames) => {
|
|
4982
5074
|
const referenced = /* @__PURE__ */ new Set();
|
|
@@ -5115,6 +5207,7 @@ const ALWAYS_USED_PREFIXES = [
|
|
|
5115
5207
|
"postcss-",
|
|
5116
5208
|
"@tailwindcss/",
|
|
5117
5209
|
"rollup-plugin-",
|
|
5210
|
+
"@rollup/",
|
|
5118
5211
|
"vite-plugin-",
|
|
5119
5212
|
"@vitejs/",
|
|
5120
5213
|
"webpack-",
|
|
@@ -5351,7 +5444,13 @@ const defineConfig = (options) => ({
|
|
|
5351
5444
|
const analyze = async (config) => {
|
|
5352
5445
|
const pipelineStartTime = performance.now();
|
|
5353
5446
|
const workspaceDiscovery = resolveWorkspaces(resolve(config.rootDir));
|
|
5354
|
-
const workspacePackages = workspaceDiscovery.packages;
|
|
5447
|
+
const workspacePackages = [...workspaceDiscovery.packages];
|
|
5448
|
+
const monorepoRoot = findMonorepoRoot(config.rootDir);
|
|
5449
|
+
if (monorepoRoot) {
|
|
5450
|
+
const monorepoWorkspaces = resolveWorkspaces(monorepoRoot);
|
|
5451
|
+
const existingDirectories = new Set(workspacePackages.map((workspacePackage) => workspacePackage.directory));
|
|
5452
|
+
for (const monorepoPackage of monorepoWorkspaces.packages) if (!existingDirectories.has(monorepoPackage.directory)) workspacePackages.push(monorepoPackage);
|
|
5453
|
+
}
|
|
5355
5454
|
const frameworkIgnorePatterns = getFrameworkExclusions(config.rootDir);
|
|
5356
5455
|
const absoluteRoot = resolve(config.rootDir);
|
|
5357
5456
|
const outputDirectoryExclusions = OUTPUT_DIRECTORIES.flatMap((outputDirectory) => {
|
|
@@ -5377,7 +5476,10 @@ const analyze = async (config) => {
|
|
|
5377
5476
|
const moduleResolver = createResolver(config, workspacePackages.map((workspacePackage) => ({
|
|
5378
5477
|
name: workspacePackage.name,
|
|
5379
5478
|
directory: workspacePackage.directory
|
|
5380
|
-
})), {
|
|
5479
|
+
})), {
|
|
5480
|
+
hasReactNative,
|
|
5481
|
+
monorepoRoot
|
|
5482
|
+
});
|
|
5381
5483
|
const graphInputs = [];
|
|
5382
5484
|
for (const file of files) {
|
|
5383
5485
|
const parsedModule = parseSourceFile(file.path);
|