deslop-js 0.0.2 → 0.0.3

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 CHANGED
@@ -165,7 +165,19 @@ const IMPLICIT_DEPENDENCIES = new Set([
165
165
  "prettier",
166
166
  "husky",
167
167
  "lint-staged",
168
- "tslib"
168
+ "tslib",
169
+ "@babel/core",
170
+ "postcss",
171
+ "cross-env",
172
+ "sass",
173
+ "node-sass",
174
+ "less",
175
+ "oxlint",
176
+ "biome",
177
+ "@biomejs/biome",
178
+ "patch-package",
179
+ "simple-git-hooks",
180
+ "lefthook"
169
181
  ]);
170
182
  const BUILTIN_MODULES = new Set([
171
183
  "assert",
@@ -1877,7 +1889,9 @@ const extractPackageJsonEntries = async (packageJsonPath) => {
1877
1889
  "module",
1878
1890
  "browser",
1879
1891
  "types",
1880
- "typings"
1892
+ "typings",
1893
+ "style",
1894
+ "source"
1881
1895
  ]) if (typeof packageJson[field] === "string") entries.push(resolveEntryPath(packageJson[field], rootDir));
1882
1896
  if (packageJson.exports) {
1883
1897
  const exportEntries = [];
@@ -2677,7 +2691,9 @@ const TEST_FRAMEWORK_PATTERNS = [
2677
2691
  enablers: [
2678
2692
  "jest",
2679
2693
  "@jest/core",
2680
- "ts-jest"
2694
+ "ts-jest",
2695
+ "react-scripts",
2696
+ "react-app-rewired"
2681
2697
  ],
2682
2698
  configFileActivators: [
2683
2699
  "jest.config.ts",
@@ -2922,6 +2938,51 @@ const FRAMEWORK_PATTERNS = [
2922
2938
  ],
2923
2939
  contentIgnorePatterns: ["versioned_sidebars/**"]
2924
2940
  },
2941
+ {
2942
+ enablers: [
2943
+ "fumadocs-core",
2944
+ "fumadocs-ui",
2945
+ "fumadocs-mdx"
2946
+ ],
2947
+ enablerPrefixes: ["fumadocs-"],
2948
+ entryPatterns: ["content/**/*.{md,mdx}", "content/**/*.{ts,tsx,js,jsx}"],
2949
+ alwaysUsed: ["source.config.{ts,js,mjs}"]
2950
+ },
2951
+ {
2952
+ enablers: [
2953
+ "nextra",
2954
+ "nextra-theme-docs",
2955
+ "nextra-theme-blog"
2956
+ ],
2957
+ enablerPrefixes: ["nextra-"],
2958
+ entryPatterns: [
2959
+ "pages/**/*.{md,mdx}",
2960
+ "src/pages/**/*.{md,mdx}",
2961
+ "content/**/*.{md,mdx}"
2962
+ ],
2963
+ alwaysUsed: []
2964
+ },
2965
+ {
2966
+ enablers: [
2967
+ "contentlayer",
2968
+ "contentlayer2",
2969
+ "contentlayer-source-files"
2970
+ ],
2971
+ enablerPrefixes: ["contentlayer"],
2972
+ entryPatterns: ["content/**/*.{md,mdx}", "posts/**/*.{md,mdx}"],
2973
+ alwaysUsed: ["contentlayer.config.{ts,js,mjs}"]
2974
+ },
2975
+ {
2976
+ enablers: ["@graphql-codegen/cli", "@graphql-codegen/core"],
2977
+ enablerPrefixes: ["@graphql-codegen/"],
2978
+ entryPatterns: ["**/*.graphql", "**/*.gql"],
2979
+ alwaysUsed: [
2980
+ "codegen.{ts,js,yml,yaml}",
2981
+ "codegen.config.{ts,js}",
2982
+ ".graphqlrc.{ts,js,json,yml,yaml}",
2983
+ "graphql.config.{ts,js,json,yml,yaml}"
2984
+ ]
2985
+ },
2925
2986
  {
2926
2987
  enablers: ["eslint", "@eslint/js"],
2927
2988
  enablerPrefixes: [],
@@ -4480,6 +4541,16 @@ const detectDeadExports = (graph, config) => {
4480
4541
  const buildUsageMap = (graph) => {
4481
4542
  const usedExportKeys = /* @__PURE__ */ new Set();
4482
4543
  const sourceToTargetMap = buildSourceToTargetsMap(graph);
4544
+ for (const module of graph.modules) {
4545
+ if (!module.isEntryPoint) continue;
4546
+ for (const edge of graph.edges) {
4547
+ if (edge.source !== module.fileId.index || !edge.isReExportEdge) continue;
4548
+ const targetModule = graph.modules[edge.target];
4549
+ if (!targetModule) continue;
4550
+ if (edge.reExportedNames.includes("*")) markAllExportsUsedRecursive(targetModule, graph, sourceToTargetMap, usedExportKeys, /* @__PURE__ */ new Set());
4551
+ else for (const mapping of edge.reExportMappings) markExportUsedRecursive(targetModule.fileId.path, mapping.originalName, graph, sourceToTargetMap, usedExportKeys, /* @__PURE__ */ new Set());
4552
+ }
4553
+ }
4483
4554
  for (const edge of graph.edges) {
4484
4555
  const targetModule = graph.modules[edge.target];
4485
4556
  if (!targetModule) continue;
@@ -4583,6 +4654,23 @@ const extractPackageName = (specifier) => {
4583
4654
 
4584
4655
  //#endregion
4585
4656
  //#region src/report/packages.ts
4657
+ const discoverAllPackageJsonPaths = (rootDir) => {
4658
+ const paths = [(0, node_path.join)(rootDir, "package.json")];
4659
+ const workspacePackageJsons = fast_glob.default.sync("**/package.json", {
4660
+ cwd: rootDir,
4661
+ absolute: true,
4662
+ onlyFiles: true,
4663
+ ignore: [
4664
+ "**/node_modules/**",
4665
+ "**/dist/**",
4666
+ "**/build/**",
4667
+ "**/.git/**"
4668
+ ],
4669
+ deep: 5
4670
+ });
4671
+ for (const workspacePath of workspacePackageJsons) if (workspacePath !== paths[0] && !paths.includes(workspacePath)) paths.push(workspacePath);
4672
+ return paths;
4673
+ };
4586
4674
  const detectStalePackages = (graph, config) => {
4587
4675
  const packageJsonPath = (0, node_path.resolve)(config.rootDir, "package.json");
4588
4676
  let packageJson;
@@ -4597,7 +4685,28 @@ const detectStalePackages = (graph, config) => {
4597
4685
  const declaredDependencies = /* @__PURE__ */ new Map();
4598
4686
  for (const dependencyName of Object.keys(dependencies)) declaredDependencies.set(dependencyName, false);
4599
4687
  for (const dependencyName of Object.keys(devDependencies)) declaredDependencies.set(dependencyName, true);
4688
+ const declaredNames = new Set(declaredDependencies.keys());
4600
4689
  const usedPackageNames = collectUsedPackages(graph);
4690
+ const allPackageJsonPaths = discoverAllPackageJsonPaths(config.rootDir);
4691
+ const binToPackage = buildBinToPackageMap(config.rootDir, declaredNames);
4692
+ for (const workspacePackageJsonPath of allPackageJsonPaths) {
4693
+ const scriptReferenced = collectScriptReferencedPackages(workspacePackageJsonPath, declaredNames, binToPackage);
4694
+ for (const packageName of scriptReferenced) usedPackageNames.add(packageName);
4695
+ const packageJsonConfigReferenced = collectPackageJsonConfigReferences(workspacePackageJsonPath, declaredNames);
4696
+ for (const packageName of packageJsonConfigReferenced) usedPackageNames.add(packageName);
4697
+ }
4698
+ const configReferenced = collectConfigReferencedPackages(config.rootDir, graph, declaredNames);
4699
+ for (const packageName of configReferenced) usedPackageNames.add(packageName);
4700
+ const tsconfigReferenced = collectTsconfigReferencedPackages(config.rootDir);
4701
+ for (const packageName of tsconfigReferenced) usedPackageNames.add(packageName);
4702
+ if (hasJsxFiles(graph)) {
4703
+ if (declaredNames.has("react")) usedPackageNames.add("react");
4704
+ if (declaredNames.has("react-dom")) usedPackageNames.add("react-dom");
4705
+ if (declaredNames.has("react-native")) usedPackageNames.add("react-native");
4706
+ if (declaredNames.has("react-native-web")) usedPackageNames.add("react-native-web");
4707
+ }
4708
+ const peerSatisfied = collectPeerSatisfiedPackages(config.rootDir, declaredNames, usedPackageNames);
4709
+ for (const packageName of peerSatisfied) usedPackageNames.add(packageName);
4601
4710
  const unusedDependencies = [];
4602
4711
  for (const [dependencyName, isDevDependency] of declaredDependencies) {
4603
4712
  if (isAlwaysConsideredUsed(dependencyName)) continue;
@@ -4616,13 +4725,265 @@ const collectUsedPackages = (graph) => {
4616
4725
  }
4617
4726
  return usedPackages;
4618
4727
  };
4728
+ const hasJsxFiles = (graph) => graph.modules.some((module) => {
4729
+ const filePath = module.fileId.path;
4730
+ return filePath.endsWith(".tsx") || filePath.endsWith(".jsx");
4731
+ });
4732
+ const collectPeerSatisfiedPackages = (rootDir, declaredNames, confirmedUsedNames) => {
4733
+ const peerSatisfied = /* @__PURE__ */ new Set();
4734
+ const nodeModulesDir = (0, node_path.join)(rootDir, "node_modules");
4735
+ for (const installedName of declaredNames) {
4736
+ if (!confirmedUsedNames.has(installedName)) continue;
4737
+ const packageJsonPath = installedName.startsWith("@") ? (0, node_path.join)(nodeModulesDir, ...installedName.split("/"), "package.json") : (0, node_path.join)(nodeModulesDir, installedName, "package.json");
4738
+ try {
4739
+ const content = (0, node_fs.readFileSync)(packageJsonPath, "utf-8");
4740
+ const peerDeps = JSON.parse(content).peerDependencies;
4741
+ if (peerDeps && typeof peerDeps === "object") {
4742
+ for (const peerName of Object.keys(peerDeps)) if (declaredNames.has(peerName)) peerSatisfied.add(peerName);
4743
+ }
4744
+ } catch {
4745
+ continue;
4746
+ }
4747
+ }
4748
+ return peerSatisfied;
4749
+ };
4750
+ const SHELL_SPLIT_PATTERN = /\s*(?:&&|\|\||[;&|])\s*/;
4751
+ const CLI_BINARY_TO_PACKAGE = {
4752
+ "react-scripts": "react-scripts",
4753
+ "webpack-cli": "webpack-cli",
4754
+ "webpack-dev-server": "webpack-dev-server",
4755
+ vitest: "vitest",
4756
+ jest: "jest",
4757
+ prisma: "prisma",
4758
+ sequelize: "sequelize-cli",
4759
+ rimraf: "rimraf",
4760
+ concurrently: "concurrently",
4761
+ parcel: "parcel",
4762
+ rescript: "rescript",
4763
+ webstudio: "webstudio",
4764
+ cap: "@capacitor/cli",
4765
+ "source-map-explorer": "source-map-explorer",
4766
+ "ts-standard": "ts-standard",
4767
+ "rndebugger-open": "react-native-debugger-open",
4768
+ "simple-git-hooks": "simple-git-hooks",
4769
+ "generate-arg-types": "@webstudio-is/generate-arg-types",
4770
+ email: "@react-email/preview-server"
4771
+ };
4772
+ const ENV_WRAPPER_BINARY_SET = new Set([
4773
+ "cross-env",
4774
+ "dotenv",
4775
+ "dotenv-flow",
4776
+ "env-cmd"
4777
+ ]);
4778
+ const INLINE_ENV_VAR_PATTERN = /^[A-Z_][A-Z0-9_]*=/;
4779
+ const buildBinToPackageMap = (rootDir, declaredNames) => {
4780
+ const binToPackage = /* @__PURE__ */ new Map();
4781
+ for (const [binary, packageName] of Object.entries(CLI_BINARY_TO_PACKAGE)) binToPackage.set(binary, packageName);
4782
+ for (const packageName of declaredNames) {
4783
+ const packageBinJsonPath = packageName.startsWith("@") ? (0, node_path.join)(rootDir, "node_modules", ...packageName.split("/"), "package.json") : (0, node_path.join)(rootDir, "node_modules", packageName, "package.json");
4784
+ try {
4785
+ const binContent = (0, node_fs.readFileSync)(packageBinJsonPath, "utf-8");
4786
+ const binPackageJson = JSON.parse(binContent);
4787
+ if (typeof binPackageJson.bin === "string") binToPackage.set(packageName.split("/").pop(), packageName);
4788
+ else if (typeof binPackageJson.bin === "object" && binPackageJson.bin !== null) for (const binaryName of Object.keys(binPackageJson.bin)) binToPackage.set(binaryName, packageName);
4789
+ } catch {
4790
+ continue;
4791
+ }
4792
+ }
4793
+ return binToPackage;
4794
+ };
4795
+ const collectScriptReferencedPackages = (packageJsonPath, declaredNames, binToPackage) => {
4796
+ const referenced = /* @__PURE__ */ new Set();
4797
+ try {
4798
+ const content = (0, node_fs.readFileSync)(packageJsonPath, "utf-8");
4799
+ const scripts = JSON.parse(content).scripts;
4800
+ if (!scripts || typeof scripts !== "object") return referenced;
4801
+ for (const scriptCommand of Object.values(scripts)) {
4802
+ if (typeof scriptCommand !== "string") continue;
4803
+ const segments = scriptCommand.split(SHELL_SPLIT_PATTERN);
4804
+ for (const segment of segments) {
4805
+ const tokens = segment.trim().split(/\s+/);
4806
+ if (tokens.length === 0) continue;
4807
+ let binaryIndex = 0;
4808
+ const firstToken = tokens[0].replace(/^.*\//, "");
4809
+ if (ENV_WRAPPER_BINARY_SET.has(firstToken)) {
4810
+ const envPackage = binToPackage.get(firstToken);
4811
+ if (envPackage && declaredNames.has(envPackage)) referenced.add(envPackage);
4812
+ binaryIndex = 1;
4813
+ while (binaryIndex < tokens.length && INLINE_ENV_VAR_PATTERN.test(tokens[binaryIndex])) binaryIndex++;
4814
+ if (binaryIndex >= tokens.length) continue;
4815
+ }
4816
+ while (binaryIndex < tokens.length && INLINE_ENV_VAR_PATTERN.test(tokens[binaryIndex])) binaryIndex++;
4817
+ if (binaryIndex >= tokens.length) continue;
4818
+ const binaryToken = tokens[binaryIndex].replace(/^.*\//, "");
4819
+ const effectiveBinary = binaryToken === "npx" || binaryToken === "pnpx" || binaryToken === "bunx" ? tokens[binaryIndex + 1]?.replace(/^.*\//, "") ?? "" : binaryToken;
4820
+ for (const candidateBinary of [binaryToken, effectiveBinary]) {
4821
+ if (!candidateBinary) continue;
4822
+ const mappedPackage = binToPackage.get(candidateBinary);
4823
+ if (mappedPackage && declaredNames.has(mappedPackage)) referenced.add(mappedPackage);
4824
+ if (declaredNames.has(candidateBinary)) referenced.add(candidateBinary);
4825
+ }
4826
+ }
4827
+ }
4828
+ } catch {
4829
+ return referenced;
4830
+ }
4831
+ return referenced;
4832
+ };
4833
+ const CONFIG_FILE_GLOBS = [
4834
+ "postcss.config.{js,cjs,mjs,ts}",
4835
+ ".babelrc",
4836
+ ".babelrc.{js,cjs,mjs,json}",
4837
+ "babel.config.{js,cjs,mjs,json,ts}",
4838
+ ".eslintrc",
4839
+ ".eslintrc.{js,cjs,mjs,json,yaml,yml}",
4840
+ "eslint.config.{js,cjs,mjs,ts,mts,cts}",
4841
+ "webpack.config.{js,ts,mjs,cjs}",
4842
+ "**/webpack*.config.{js,ts,mjs,cjs}",
4843
+ "vite.config.{js,ts,mjs,mts}",
4844
+ "rollup.config.{js,ts,mjs,cjs}",
4845
+ ".storybook/main.{js,ts,mjs,cjs}",
4846
+ "docusaurus.config.{js,ts,mjs}",
4847
+ "next.config.{js,ts,mjs,mts}",
4848
+ "tailwind.config.{js,ts,cjs,mjs}",
4849
+ "jest.config.{js,ts,mjs,cjs}",
4850
+ "vitest.config.{js,ts,mjs,mts}",
4851
+ "app.json",
4852
+ "forge.config.{js,ts,cjs}",
4853
+ "wrangler.toml",
4854
+ "wrangler.json",
4855
+ "wrangler.jsonc",
4856
+ "metro.config.{js,ts}",
4857
+ "electron.vite.config.{js,ts,mjs}",
4858
+ "api-extractor.json"
4859
+ ];
4860
+ const collectConfigReferencedPackages = (rootDir, graph, declaredNames) => {
4861
+ const referenced = /* @__PURE__ */ new Set();
4862
+ for (const module of graph.modules) {
4863
+ if (!module.isConfigFile) continue;
4864
+ try {
4865
+ const content = (0, node_fs.readFileSync)(module.fileId.path, "utf-8");
4866
+ for (const packageName of declaredNames) if (content.includes(packageName)) referenced.add(packageName);
4867
+ } catch {
4868
+ continue;
4869
+ }
4870
+ }
4871
+ const configFiles = fast_glob.default.sync(CONFIG_FILE_GLOBS, {
4872
+ cwd: rootDir,
4873
+ absolute: true,
4874
+ onlyFiles: true,
4875
+ ignore: ["**/node_modules/**"],
4876
+ dot: true,
4877
+ deep: 3
4878
+ });
4879
+ for (const configPath of configFiles) try {
4880
+ const content = (0, node_fs.readFileSync)(configPath, "utf-8");
4881
+ for (const packageName of declaredNames) if (content.includes(packageName)) referenced.add(packageName);
4882
+ } catch {
4883
+ continue;
4884
+ }
4885
+ return referenced;
4886
+ };
4887
+ const PACKAGE_JSON_CONFIG_SECTIONS = [
4888
+ "jest",
4889
+ "babel",
4890
+ "eslintConfig",
4891
+ "prettier",
4892
+ "stylelint",
4893
+ "lint-staged",
4894
+ "commitlint",
4895
+ "browserslist",
4896
+ "postcss",
4897
+ "ava"
4898
+ ];
4899
+ const collectPackageJsonConfigReferences = (packageJsonPath, declaredNames) => {
4900
+ const referenced = /* @__PURE__ */ new Set();
4901
+ try {
4902
+ const content = (0, node_fs.readFileSync)(packageJsonPath, "utf-8");
4903
+ const packageJson = JSON.parse(content);
4904
+ for (const sectionName of PACKAGE_JSON_CONFIG_SECTIONS) {
4905
+ const sectionValue = packageJson[sectionName];
4906
+ if (!sectionValue || typeof sectionValue !== "object") continue;
4907
+ const sectionText = JSON.stringify(sectionValue);
4908
+ for (const packageName of declaredNames) if (sectionText.includes(packageName)) referenced.add(packageName);
4909
+ }
4910
+ } catch {
4911
+ return referenced;
4912
+ }
4913
+ return referenced;
4914
+ };
4915
+ const TSCONFIG_GLOBS = [
4916
+ "tsconfig.json",
4917
+ "tsconfig.*.json",
4918
+ "jsconfig.json",
4919
+ "**/tsconfig.json",
4920
+ "**/tsconfig.*.json"
4921
+ ];
4922
+ const collectTsconfigReferencedPackages = (rootDir) => {
4923
+ const referenced = /* @__PURE__ */ new Set();
4924
+ const tsconfigFiles = fast_glob.default.sync(TSCONFIG_GLOBS, {
4925
+ cwd: rootDir,
4926
+ absolute: true,
4927
+ onlyFiles: true,
4928
+ ignore: ["**/node_modules/**"],
4929
+ dot: false,
4930
+ deep: 4
4931
+ });
4932
+ for (const tsconfigPath of tsconfigFiles) try {
4933
+ const cleaned = (0, node_fs.readFileSync)(tsconfigPath, "utf-8").replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
4934
+ const parsed = JSON.parse(cleaned);
4935
+ if (typeof parsed.extends === "string") {
4936
+ const extendsPackage = extractExtendsPackageName(parsed.extends);
4937
+ if (extendsPackage) referenced.add(extendsPackage);
4938
+ }
4939
+ if (Array.isArray(parsed.extends)) {
4940
+ for (const extendsEntry of parsed.extends) if (typeof extendsEntry === "string") {
4941
+ const extendsPackage = extractExtendsPackageName(extendsEntry);
4942
+ if (extendsPackage) referenced.add(extendsPackage);
4943
+ }
4944
+ }
4945
+ const compilerOptions = parsed.compilerOptions;
4946
+ if (compilerOptions?.jsxImportSource && typeof compilerOptions.jsxImportSource === "string") referenced.add(compilerOptions.jsxImportSource);
4947
+ if (Array.isArray(compilerOptions?.types)) {
4948
+ for (const typesEntry of compilerOptions.types) if (typeof typesEntry === "string") {
4949
+ const typesPackage = extractPackageName(typesEntry);
4950
+ if (typesPackage) referenced.add(typesPackage);
4951
+ }
4952
+ }
4953
+ } catch {
4954
+ continue;
4955
+ }
4956
+ return referenced;
4957
+ };
4958
+ const extractExtendsPackageName = (extendsValue) => {
4959
+ if (extendsValue.startsWith(".") || extendsValue.startsWith("/")) return void 0;
4960
+ if (extendsValue.startsWith("@")) {
4961
+ const parts = extendsValue.split("/");
4962
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : void 0;
4963
+ }
4964
+ return extendsValue.split("/")[0];
4965
+ };
4966
+ const ALWAYS_USED_PREFIXES = [
4967
+ "@types/",
4968
+ "eslint-config-",
4969
+ "eslint-plugin-",
4970
+ "@eslint/",
4971
+ "prettier-plugin-",
4972
+ "@commitlint/",
4973
+ "babel-plugin-",
4974
+ "babel-preset-",
4975
+ "@babel/plugin-",
4976
+ "@babel/preset-",
4977
+ "@fontsource/",
4978
+ "@next/",
4979
+ "@svgr/",
4980
+ "@docusaurus/",
4981
+ "stylelint-config-",
4982
+ "stylelint-plugin-"
4983
+ ];
4619
4984
  const isAlwaysConsideredUsed = (dependencyName) => {
4620
4985
  if (IMPLICIT_DEPENDENCIES.has(dependencyName)) return true;
4621
- if (dependencyName.startsWith("@types/")) return true;
4622
- if (dependencyName.startsWith("eslint-config-")) return true;
4623
- if (dependencyName.startsWith("eslint-plugin-")) return true;
4624
- if (dependencyName.startsWith("prettier-plugin-")) return true;
4625
- return false;
4986
+ return ALWAYS_USED_PREFIXES.some((prefix) => dependencyName.startsWith(prefix));
4626
4987
  };
4627
4988
 
4628
4989
  //#endregion
package/dist/index.mjs CHANGED
@@ -135,7 +135,19 @@ const IMPLICIT_DEPENDENCIES = new Set([
135
135
  "prettier",
136
136
  "husky",
137
137
  "lint-staged",
138
- "tslib"
138
+ "tslib",
139
+ "@babel/core",
140
+ "postcss",
141
+ "cross-env",
142
+ "sass",
143
+ "node-sass",
144
+ "less",
145
+ "oxlint",
146
+ "biome",
147
+ "@biomejs/biome",
148
+ "patch-package",
149
+ "simple-git-hooks",
150
+ "lefthook"
139
151
  ]);
140
152
  const BUILTIN_MODULES = new Set([
141
153
  "assert",
@@ -1847,7 +1859,9 @@ const extractPackageJsonEntries = async (packageJsonPath) => {
1847
1859
  "module",
1848
1860
  "browser",
1849
1861
  "types",
1850
- "typings"
1862
+ "typings",
1863
+ "style",
1864
+ "source"
1851
1865
  ]) if (typeof packageJson[field] === "string") entries.push(resolveEntryPath(packageJson[field], rootDir));
1852
1866
  if (packageJson.exports) {
1853
1867
  const exportEntries = [];
@@ -2647,7 +2661,9 @@ const TEST_FRAMEWORK_PATTERNS = [
2647
2661
  enablers: [
2648
2662
  "jest",
2649
2663
  "@jest/core",
2650
- "ts-jest"
2664
+ "ts-jest",
2665
+ "react-scripts",
2666
+ "react-app-rewired"
2651
2667
  ],
2652
2668
  configFileActivators: [
2653
2669
  "jest.config.ts",
@@ -2892,6 +2908,51 @@ const FRAMEWORK_PATTERNS = [
2892
2908
  ],
2893
2909
  contentIgnorePatterns: ["versioned_sidebars/**"]
2894
2910
  },
2911
+ {
2912
+ enablers: [
2913
+ "fumadocs-core",
2914
+ "fumadocs-ui",
2915
+ "fumadocs-mdx"
2916
+ ],
2917
+ enablerPrefixes: ["fumadocs-"],
2918
+ entryPatterns: ["content/**/*.{md,mdx}", "content/**/*.{ts,tsx,js,jsx}"],
2919
+ alwaysUsed: ["source.config.{ts,js,mjs}"]
2920
+ },
2921
+ {
2922
+ enablers: [
2923
+ "nextra",
2924
+ "nextra-theme-docs",
2925
+ "nextra-theme-blog"
2926
+ ],
2927
+ enablerPrefixes: ["nextra-"],
2928
+ entryPatterns: [
2929
+ "pages/**/*.{md,mdx}",
2930
+ "src/pages/**/*.{md,mdx}",
2931
+ "content/**/*.{md,mdx}"
2932
+ ],
2933
+ alwaysUsed: []
2934
+ },
2935
+ {
2936
+ enablers: [
2937
+ "contentlayer",
2938
+ "contentlayer2",
2939
+ "contentlayer-source-files"
2940
+ ],
2941
+ enablerPrefixes: ["contentlayer"],
2942
+ entryPatterns: ["content/**/*.{md,mdx}", "posts/**/*.{md,mdx}"],
2943
+ alwaysUsed: ["contentlayer.config.{ts,js,mjs}"]
2944
+ },
2945
+ {
2946
+ enablers: ["@graphql-codegen/cli", "@graphql-codegen/core"],
2947
+ enablerPrefixes: ["@graphql-codegen/"],
2948
+ entryPatterns: ["**/*.graphql", "**/*.gql"],
2949
+ alwaysUsed: [
2950
+ "codegen.{ts,js,yml,yaml}",
2951
+ "codegen.config.{ts,js}",
2952
+ ".graphqlrc.{ts,js,json,yml,yaml}",
2953
+ "graphql.config.{ts,js,json,yml,yaml}"
2954
+ ]
2955
+ },
2895
2956
  {
2896
2957
  enablers: ["eslint", "@eslint/js"],
2897
2958
  enablerPrefixes: [],
@@ -4450,6 +4511,16 @@ const detectDeadExports = (graph, config) => {
4450
4511
  const buildUsageMap = (graph) => {
4451
4512
  const usedExportKeys = /* @__PURE__ */ new Set();
4452
4513
  const sourceToTargetMap = buildSourceToTargetsMap(graph);
4514
+ for (const module of graph.modules) {
4515
+ if (!module.isEntryPoint) continue;
4516
+ for (const edge of graph.edges) {
4517
+ if (edge.source !== module.fileId.index || !edge.isReExportEdge) continue;
4518
+ const targetModule = graph.modules[edge.target];
4519
+ if (!targetModule) continue;
4520
+ if (edge.reExportedNames.includes("*")) markAllExportsUsedRecursive(targetModule, graph, sourceToTargetMap, usedExportKeys, /* @__PURE__ */ new Set());
4521
+ else for (const mapping of edge.reExportMappings) markExportUsedRecursive(targetModule.fileId.path, mapping.originalName, graph, sourceToTargetMap, usedExportKeys, /* @__PURE__ */ new Set());
4522
+ }
4523
+ }
4453
4524
  for (const edge of graph.edges) {
4454
4525
  const targetModule = graph.modules[edge.target];
4455
4526
  if (!targetModule) continue;
@@ -4553,6 +4624,23 @@ const extractPackageName = (specifier) => {
4553
4624
 
4554
4625
  //#endregion
4555
4626
  //#region src/report/packages.ts
4627
+ const discoverAllPackageJsonPaths = (rootDir) => {
4628
+ const paths = [join(rootDir, "package.json")];
4629
+ const workspacePackageJsons = fg.sync("**/package.json", {
4630
+ cwd: rootDir,
4631
+ absolute: true,
4632
+ onlyFiles: true,
4633
+ ignore: [
4634
+ "**/node_modules/**",
4635
+ "**/dist/**",
4636
+ "**/build/**",
4637
+ "**/.git/**"
4638
+ ],
4639
+ deep: 5
4640
+ });
4641
+ for (const workspacePath of workspacePackageJsons) if (workspacePath !== paths[0] && !paths.includes(workspacePath)) paths.push(workspacePath);
4642
+ return paths;
4643
+ };
4556
4644
  const detectStalePackages = (graph, config) => {
4557
4645
  const packageJsonPath = resolve(config.rootDir, "package.json");
4558
4646
  let packageJson;
@@ -4567,7 +4655,28 @@ const detectStalePackages = (graph, config) => {
4567
4655
  const declaredDependencies = /* @__PURE__ */ new Map();
4568
4656
  for (const dependencyName of Object.keys(dependencies)) declaredDependencies.set(dependencyName, false);
4569
4657
  for (const dependencyName of Object.keys(devDependencies)) declaredDependencies.set(dependencyName, true);
4658
+ const declaredNames = new Set(declaredDependencies.keys());
4570
4659
  const usedPackageNames = collectUsedPackages(graph);
4660
+ const allPackageJsonPaths = discoverAllPackageJsonPaths(config.rootDir);
4661
+ const binToPackage = buildBinToPackageMap(config.rootDir, declaredNames);
4662
+ for (const workspacePackageJsonPath of allPackageJsonPaths) {
4663
+ const scriptReferenced = collectScriptReferencedPackages(workspacePackageJsonPath, declaredNames, binToPackage);
4664
+ for (const packageName of scriptReferenced) usedPackageNames.add(packageName);
4665
+ const packageJsonConfigReferenced = collectPackageJsonConfigReferences(workspacePackageJsonPath, declaredNames);
4666
+ for (const packageName of packageJsonConfigReferenced) usedPackageNames.add(packageName);
4667
+ }
4668
+ const configReferenced = collectConfigReferencedPackages(config.rootDir, graph, declaredNames);
4669
+ for (const packageName of configReferenced) usedPackageNames.add(packageName);
4670
+ const tsconfigReferenced = collectTsconfigReferencedPackages(config.rootDir);
4671
+ for (const packageName of tsconfigReferenced) usedPackageNames.add(packageName);
4672
+ if (hasJsxFiles(graph)) {
4673
+ if (declaredNames.has("react")) usedPackageNames.add("react");
4674
+ if (declaredNames.has("react-dom")) usedPackageNames.add("react-dom");
4675
+ if (declaredNames.has("react-native")) usedPackageNames.add("react-native");
4676
+ if (declaredNames.has("react-native-web")) usedPackageNames.add("react-native-web");
4677
+ }
4678
+ const peerSatisfied = collectPeerSatisfiedPackages(config.rootDir, declaredNames, usedPackageNames);
4679
+ for (const packageName of peerSatisfied) usedPackageNames.add(packageName);
4571
4680
  const unusedDependencies = [];
4572
4681
  for (const [dependencyName, isDevDependency] of declaredDependencies) {
4573
4682
  if (isAlwaysConsideredUsed(dependencyName)) continue;
@@ -4586,13 +4695,265 @@ const collectUsedPackages = (graph) => {
4586
4695
  }
4587
4696
  return usedPackages;
4588
4697
  };
4698
+ const hasJsxFiles = (graph) => graph.modules.some((module) => {
4699
+ const filePath = module.fileId.path;
4700
+ return filePath.endsWith(".tsx") || filePath.endsWith(".jsx");
4701
+ });
4702
+ const collectPeerSatisfiedPackages = (rootDir, declaredNames, confirmedUsedNames) => {
4703
+ const peerSatisfied = /* @__PURE__ */ new Set();
4704
+ const nodeModulesDir = join(rootDir, "node_modules");
4705
+ for (const installedName of declaredNames) {
4706
+ if (!confirmedUsedNames.has(installedName)) continue;
4707
+ const packageJsonPath = installedName.startsWith("@") ? join(nodeModulesDir, ...installedName.split("/"), "package.json") : join(nodeModulesDir, installedName, "package.json");
4708
+ try {
4709
+ const content = readFileSync(packageJsonPath, "utf-8");
4710
+ const peerDeps = JSON.parse(content).peerDependencies;
4711
+ if (peerDeps && typeof peerDeps === "object") {
4712
+ for (const peerName of Object.keys(peerDeps)) if (declaredNames.has(peerName)) peerSatisfied.add(peerName);
4713
+ }
4714
+ } catch {
4715
+ continue;
4716
+ }
4717
+ }
4718
+ return peerSatisfied;
4719
+ };
4720
+ const SHELL_SPLIT_PATTERN = /\s*(?:&&|\|\||[;&|])\s*/;
4721
+ const CLI_BINARY_TO_PACKAGE = {
4722
+ "react-scripts": "react-scripts",
4723
+ "webpack-cli": "webpack-cli",
4724
+ "webpack-dev-server": "webpack-dev-server",
4725
+ vitest: "vitest",
4726
+ jest: "jest",
4727
+ prisma: "prisma",
4728
+ sequelize: "sequelize-cli",
4729
+ rimraf: "rimraf",
4730
+ concurrently: "concurrently",
4731
+ parcel: "parcel",
4732
+ rescript: "rescript",
4733
+ webstudio: "webstudio",
4734
+ cap: "@capacitor/cli",
4735
+ "source-map-explorer": "source-map-explorer",
4736
+ "ts-standard": "ts-standard",
4737
+ "rndebugger-open": "react-native-debugger-open",
4738
+ "simple-git-hooks": "simple-git-hooks",
4739
+ "generate-arg-types": "@webstudio-is/generate-arg-types",
4740
+ email: "@react-email/preview-server"
4741
+ };
4742
+ const ENV_WRAPPER_BINARY_SET = new Set([
4743
+ "cross-env",
4744
+ "dotenv",
4745
+ "dotenv-flow",
4746
+ "env-cmd"
4747
+ ]);
4748
+ const INLINE_ENV_VAR_PATTERN = /^[A-Z_][A-Z0-9_]*=/;
4749
+ const buildBinToPackageMap = (rootDir, declaredNames) => {
4750
+ const binToPackage = /* @__PURE__ */ new Map();
4751
+ for (const [binary, packageName] of Object.entries(CLI_BINARY_TO_PACKAGE)) binToPackage.set(binary, packageName);
4752
+ for (const packageName of declaredNames) {
4753
+ const packageBinJsonPath = packageName.startsWith("@") ? join(rootDir, "node_modules", ...packageName.split("/"), "package.json") : join(rootDir, "node_modules", packageName, "package.json");
4754
+ try {
4755
+ const binContent = readFileSync(packageBinJsonPath, "utf-8");
4756
+ const binPackageJson = JSON.parse(binContent);
4757
+ if (typeof binPackageJson.bin === "string") binToPackage.set(packageName.split("/").pop(), packageName);
4758
+ else if (typeof binPackageJson.bin === "object" && binPackageJson.bin !== null) for (const binaryName of Object.keys(binPackageJson.bin)) binToPackage.set(binaryName, packageName);
4759
+ } catch {
4760
+ continue;
4761
+ }
4762
+ }
4763
+ return binToPackage;
4764
+ };
4765
+ const collectScriptReferencedPackages = (packageJsonPath, declaredNames, binToPackage) => {
4766
+ const referenced = /* @__PURE__ */ new Set();
4767
+ try {
4768
+ const content = readFileSync(packageJsonPath, "utf-8");
4769
+ const scripts = JSON.parse(content).scripts;
4770
+ if (!scripts || typeof scripts !== "object") return referenced;
4771
+ for (const scriptCommand of Object.values(scripts)) {
4772
+ if (typeof scriptCommand !== "string") continue;
4773
+ const segments = scriptCommand.split(SHELL_SPLIT_PATTERN);
4774
+ for (const segment of segments) {
4775
+ const tokens = segment.trim().split(/\s+/);
4776
+ if (tokens.length === 0) continue;
4777
+ let binaryIndex = 0;
4778
+ const firstToken = tokens[0].replace(/^.*\//, "");
4779
+ if (ENV_WRAPPER_BINARY_SET.has(firstToken)) {
4780
+ const envPackage = binToPackage.get(firstToken);
4781
+ if (envPackage && declaredNames.has(envPackage)) referenced.add(envPackage);
4782
+ binaryIndex = 1;
4783
+ while (binaryIndex < tokens.length && INLINE_ENV_VAR_PATTERN.test(tokens[binaryIndex])) binaryIndex++;
4784
+ if (binaryIndex >= tokens.length) continue;
4785
+ }
4786
+ while (binaryIndex < tokens.length && INLINE_ENV_VAR_PATTERN.test(tokens[binaryIndex])) binaryIndex++;
4787
+ if (binaryIndex >= tokens.length) continue;
4788
+ const binaryToken = tokens[binaryIndex].replace(/^.*\//, "");
4789
+ const effectiveBinary = binaryToken === "npx" || binaryToken === "pnpx" || binaryToken === "bunx" ? tokens[binaryIndex + 1]?.replace(/^.*\//, "") ?? "" : binaryToken;
4790
+ for (const candidateBinary of [binaryToken, effectiveBinary]) {
4791
+ if (!candidateBinary) continue;
4792
+ const mappedPackage = binToPackage.get(candidateBinary);
4793
+ if (mappedPackage && declaredNames.has(mappedPackage)) referenced.add(mappedPackage);
4794
+ if (declaredNames.has(candidateBinary)) referenced.add(candidateBinary);
4795
+ }
4796
+ }
4797
+ }
4798
+ } catch {
4799
+ return referenced;
4800
+ }
4801
+ return referenced;
4802
+ };
4803
+ const CONFIG_FILE_GLOBS = [
4804
+ "postcss.config.{js,cjs,mjs,ts}",
4805
+ ".babelrc",
4806
+ ".babelrc.{js,cjs,mjs,json}",
4807
+ "babel.config.{js,cjs,mjs,json,ts}",
4808
+ ".eslintrc",
4809
+ ".eslintrc.{js,cjs,mjs,json,yaml,yml}",
4810
+ "eslint.config.{js,cjs,mjs,ts,mts,cts}",
4811
+ "webpack.config.{js,ts,mjs,cjs}",
4812
+ "**/webpack*.config.{js,ts,mjs,cjs}",
4813
+ "vite.config.{js,ts,mjs,mts}",
4814
+ "rollup.config.{js,ts,mjs,cjs}",
4815
+ ".storybook/main.{js,ts,mjs,cjs}",
4816
+ "docusaurus.config.{js,ts,mjs}",
4817
+ "next.config.{js,ts,mjs,mts}",
4818
+ "tailwind.config.{js,ts,cjs,mjs}",
4819
+ "jest.config.{js,ts,mjs,cjs}",
4820
+ "vitest.config.{js,ts,mjs,mts}",
4821
+ "app.json",
4822
+ "forge.config.{js,ts,cjs}",
4823
+ "wrangler.toml",
4824
+ "wrangler.json",
4825
+ "wrangler.jsonc",
4826
+ "metro.config.{js,ts}",
4827
+ "electron.vite.config.{js,ts,mjs}",
4828
+ "api-extractor.json"
4829
+ ];
4830
+ const collectConfigReferencedPackages = (rootDir, graph, declaredNames) => {
4831
+ const referenced = /* @__PURE__ */ new Set();
4832
+ for (const module of graph.modules) {
4833
+ if (!module.isConfigFile) continue;
4834
+ try {
4835
+ const content = readFileSync(module.fileId.path, "utf-8");
4836
+ for (const packageName of declaredNames) if (content.includes(packageName)) referenced.add(packageName);
4837
+ } catch {
4838
+ continue;
4839
+ }
4840
+ }
4841
+ const configFiles = fg.sync(CONFIG_FILE_GLOBS, {
4842
+ cwd: rootDir,
4843
+ absolute: true,
4844
+ onlyFiles: true,
4845
+ ignore: ["**/node_modules/**"],
4846
+ dot: true,
4847
+ deep: 3
4848
+ });
4849
+ for (const configPath of configFiles) try {
4850
+ const content = readFileSync(configPath, "utf-8");
4851
+ for (const packageName of declaredNames) if (content.includes(packageName)) referenced.add(packageName);
4852
+ } catch {
4853
+ continue;
4854
+ }
4855
+ return referenced;
4856
+ };
4857
+ const PACKAGE_JSON_CONFIG_SECTIONS = [
4858
+ "jest",
4859
+ "babel",
4860
+ "eslintConfig",
4861
+ "prettier",
4862
+ "stylelint",
4863
+ "lint-staged",
4864
+ "commitlint",
4865
+ "browserslist",
4866
+ "postcss",
4867
+ "ava"
4868
+ ];
4869
+ const collectPackageJsonConfigReferences = (packageJsonPath, declaredNames) => {
4870
+ const referenced = /* @__PURE__ */ new Set();
4871
+ try {
4872
+ const content = readFileSync(packageJsonPath, "utf-8");
4873
+ const packageJson = JSON.parse(content);
4874
+ for (const sectionName of PACKAGE_JSON_CONFIG_SECTIONS) {
4875
+ const sectionValue = packageJson[sectionName];
4876
+ if (!sectionValue || typeof sectionValue !== "object") continue;
4877
+ const sectionText = JSON.stringify(sectionValue);
4878
+ for (const packageName of declaredNames) if (sectionText.includes(packageName)) referenced.add(packageName);
4879
+ }
4880
+ } catch {
4881
+ return referenced;
4882
+ }
4883
+ return referenced;
4884
+ };
4885
+ const TSCONFIG_GLOBS = [
4886
+ "tsconfig.json",
4887
+ "tsconfig.*.json",
4888
+ "jsconfig.json",
4889
+ "**/tsconfig.json",
4890
+ "**/tsconfig.*.json"
4891
+ ];
4892
+ const collectTsconfigReferencedPackages = (rootDir) => {
4893
+ const referenced = /* @__PURE__ */ new Set();
4894
+ const tsconfigFiles = fg.sync(TSCONFIG_GLOBS, {
4895
+ cwd: rootDir,
4896
+ absolute: true,
4897
+ onlyFiles: true,
4898
+ ignore: ["**/node_modules/**"],
4899
+ dot: false,
4900
+ deep: 4
4901
+ });
4902
+ for (const tsconfigPath of tsconfigFiles) try {
4903
+ const cleaned = readFileSync(tsconfigPath, "utf-8").replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
4904
+ const parsed = JSON.parse(cleaned);
4905
+ if (typeof parsed.extends === "string") {
4906
+ const extendsPackage = extractExtendsPackageName(parsed.extends);
4907
+ if (extendsPackage) referenced.add(extendsPackage);
4908
+ }
4909
+ if (Array.isArray(parsed.extends)) {
4910
+ for (const extendsEntry of parsed.extends) if (typeof extendsEntry === "string") {
4911
+ const extendsPackage = extractExtendsPackageName(extendsEntry);
4912
+ if (extendsPackage) referenced.add(extendsPackage);
4913
+ }
4914
+ }
4915
+ const compilerOptions = parsed.compilerOptions;
4916
+ if (compilerOptions?.jsxImportSource && typeof compilerOptions.jsxImportSource === "string") referenced.add(compilerOptions.jsxImportSource);
4917
+ if (Array.isArray(compilerOptions?.types)) {
4918
+ for (const typesEntry of compilerOptions.types) if (typeof typesEntry === "string") {
4919
+ const typesPackage = extractPackageName(typesEntry);
4920
+ if (typesPackage) referenced.add(typesPackage);
4921
+ }
4922
+ }
4923
+ } catch {
4924
+ continue;
4925
+ }
4926
+ return referenced;
4927
+ };
4928
+ const extractExtendsPackageName = (extendsValue) => {
4929
+ if (extendsValue.startsWith(".") || extendsValue.startsWith("/")) return void 0;
4930
+ if (extendsValue.startsWith("@")) {
4931
+ const parts = extendsValue.split("/");
4932
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : void 0;
4933
+ }
4934
+ return extendsValue.split("/")[0];
4935
+ };
4936
+ const ALWAYS_USED_PREFIXES = [
4937
+ "@types/",
4938
+ "eslint-config-",
4939
+ "eslint-plugin-",
4940
+ "@eslint/",
4941
+ "prettier-plugin-",
4942
+ "@commitlint/",
4943
+ "babel-plugin-",
4944
+ "babel-preset-",
4945
+ "@babel/plugin-",
4946
+ "@babel/preset-",
4947
+ "@fontsource/",
4948
+ "@next/",
4949
+ "@svgr/",
4950
+ "@docusaurus/",
4951
+ "stylelint-config-",
4952
+ "stylelint-plugin-"
4953
+ ];
4589
4954
  const isAlwaysConsideredUsed = (dependencyName) => {
4590
4955
  if (IMPLICIT_DEPENDENCIES.has(dependencyName)) return true;
4591
- if (dependencyName.startsWith("@types/")) return true;
4592
- if (dependencyName.startsWith("eslint-config-")) return true;
4593
- if (dependencyName.startsWith("eslint-plugin-")) return true;
4594
- if (dependencyName.startsWith("prettier-plugin-")) return true;
4595
- return false;
4956
+ return ALWAYS_USED_PREFIXES.some((prefix) => dependencyName.startsWith(prefix));
4596
4957
  };
4597
4958
 
4598
4959
  //#endregion
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "deslop-js",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Deslop JavaScript code",
5
5
  "keywords": [
6
6
  "dead-code",
7
- "unused",
7
+ "dependencies",
8
8
  "exports",
9
9
  "files",
10
- "dependencies",
11
- "typescript",
12
10
  "javascript",
13
- "oxc"
11
+ "oxc",
12
+ "typescript",
13
+ "unused"
14
14
  ],
15
15
  "license": "MIT",
16
16
  "author": {