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.
Files changed (3) hide show
  1. package/dist/index.cjs +150 -48
  2. package/dist/index.mjs +150 -48
  3. 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 normalizedRoot = (0, node_path.resolve)(rootDir);
3723
- while (currentDirectory.length >= normalizedRoot.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 for (const candidate of [
3801
- "tsconfig.json",
3802
- "tsconfig.web.json",
3803
- "tsconfig.app.json",
3804
- "tsconfig.base.json"
3805
- ]) {
3806
- const candidatePath = (0, node_path.resolve)(config.rootDir, candidate);
3807
- if (cachedExistsSync(candidatePath)) {
3808
- rootTsconfigPath = candidatePath;
3809
- break;
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 getBaseUrlDirectory = (tsconfigFile) => {
3824
- const cached = tsconfigBaseUrlCache.get(tsconfigFile);
3825
- if (cached !== void 0) return cached;
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 baseUrl = JSON.parse(cleanedContent).compilerOptions?.baseUrl;
3829
- if (baseUrl) {
3830
- const absoluteBaseUrl = (0, node_path.resolve)((0, node_path.dirname)(tsconfigFile), baseUrl);
3831
- tsconfigBaseUrlCache.set(tsconfigFile, absoluteBaseUrl);
3832
- return absoluteBaseUrl;
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
- tsconfigBaseUrlCache.set(tsconfigFile, void 0);
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 getPathAliases = (tsconfigFile) => {
3849
- const cached = tsconfigPathAliasCache.get(tsconfigFile);
3850
- if (cached) return cached;
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 > 0) {
3856
- const cleanedContent = stripJsonComments(tsconfigContent);
3857
- const tsconfigJson = JSON.parse(cleanedContent);
3858
- const paths = tsconfigJson.compilerOptions?.paths;
3859
- const baseUrl = tsconfigJson.compilerOptions?.baseUrl ?? ".";
3860
- if (paths && typeof paths === "object") {
3861
- for (const [pattern, targets] of Object.entries(paths)) if (Array.isArray(targets)) aliasMap.set(pattern, targets.map((target) => (0, node_path.resolve)(tsconfigDir, baseUrl, target)));
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
- if (aliasMap.size === 0 && hasNextJsDependency) if (cachedExistsSync((0, node_path.resolve)(tsconfigDir, "src"))) aliasMap.set("@/*", [(0, node_path.resolve)(tsconfigDir, "src/*")]);
3866
- else aliasMap.set("@/*", [(0, node_path.resolve)(tsconfigDir, "*")]);
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 isTestFile = (filePath) => TEST_FILE_PATTERN.test(filePath);
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 (isTestFile(module.fileId.path)) continue;
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/report/packages.ts
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
- })), { hasReactNative });
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 normalizedRoot = resolve(rootDir);
3693
- while (currentDirectory.length >= normalizedRoot.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 for (const candidate of [
3771
- "tsconfig.json",
3772
- "tsconfig.web.json",
3773
- "tsconfig.app.json",
3774
- "tsconfig.base.json"
3775
- ]) {
3776
- const candidatePath = resolve(config.rootDir, candidate);
3777
- if (cachedExistsSync(candidatePath)) {
3778
- rootTsconfigPath = candidatePath;
3779
- break;
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 getBaseUrlDirectory = (tsconfigFile) => {
3794
- const cached = tsconfigBaseUrlCache.get(tsconfigFile);
3795
- if (cached !== void 0) return cached;
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 baseUrl = JSON.parse(cleanedContent).compilerOptions?.baseUrl;
3799
- if (baseUrl) {
3800
- const absoluteBaseUrl = resolve(dirname(tsconfigFile), baseUrl);
3801
- tsconfigBaseUrlCache.set(tsconfigFile, absoluteBaseUrl);
3802
- return absoluteBaseUrl;
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
- tsconfigBaseUrlCache.set(tsconfigFile, void 0);
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 getPathAliases = (tsconfigFile) => {
3819
- const cached = tsconfigPathAliasCache.get(tsconfigFile);
3820
- if (cached) return cached;
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 > 0) {
3826
- const cleanedContent = stripJsonComments(tsconfigContent);
3827
- const tsconfigJson = JSON.parse(cleanedContent);
3828
- const paths = tsconfigJson.compilerOptions?.paths;
3829
- const baseUrl = tsconfigJson.compilerOptions?.baseUrl ?? ".";
3830
- if (paths && typeof paths === "object") {
3831
- for (const [pattern, targets] of Object.entries(paths)) if (Array.isArray(targets)) aliasMap.set(pattern, targets.map((target) => resolve(tsconfigDir, baseUrl, target)));
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
- if (aliasMap.size === 0 && hasNextJsDependency) if (cachedExistsSync(resolve(tsconfigDir, "src"))) aliasMap.set("@/*", [resolve(tsconfigDir, "src/*")]);
3836
- else aliasMap.set("@/*", [resolve(tsconfigDir, "*")]);
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 isTestFile = (filePath) => TEST_FILE_PATTERN.test(filePath);
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 (isTestFile(module.fileId.path)) continue;
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/report/packages.ts
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
- })), { hasReactNative });
5479
+ })), {
5480
+ hasReactNative,
5481
+ monorepoRoot
5482
+ });
5381
5483
  const graphInputs = [];
5382
5484
  for (const file of files) {
5383
5485
  const parsedModule = parseSourceFile(file.path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deslop-js",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Deslop JavaScript code",
5
5
  "keywords": [
6
6
  "dead-code",