react-doctor 0.2.0 → 0.2.1

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/cli.js CHANGED
@@ -101,12 +101,31 @@ const IGNORED_DIRECTORIES = new Set([
101
101
  "out",
102
102
  "storybook-static"
103
103
  ]);
104
+ const IGNORABLE_READDIR_ERROR_CODES = new Set([
105
+ "EACCES",
106
+ "EPERM",
107
+ "ENOENT",
108
+ "ENOTDIR"
109
+ ]);
110
+ const isIgnorableReaddirError = (error) => {
111
+ if (typeof error !== "object" || error === null) return false;
112
+ const errorCode = error.code;
113
+ return typeof errorCode === "string" && IGNORABLE_READDIR_ERROR_CODES.has(errorCode);
114
+ };
115
+ const readDirectoryEntries = (directoryPath) => {
116
+ try {
117
+ return fs.readdirSync(directoryPath, { withFileTypes: true });
118
+ } catch (error) {
119
+ if (isIgnorableReaddirError(error)) return [];
120
+ throw error;
121
+ }
122
+ };
104
123
  const countSourceFilesViaFilesystem = (rootDirectory) => {
105
124
  let count = 0;
106
125
  const stack = [rootDirectory];
107
126
  while (stack.length > 0) {
108
127
  const currentDirectory = stack.pop();
109
- const entries = fs.readdirSync(currentDirectory, { withFileTypes: true });
128
+ const entries = readDirectoryEntries(currentDirectory);
110
129
  for (const entry of entries) {
111
130
  if (entry.isDirectory()) {
112
131
  if (!entry.name.startsWith(".") && !IGNORED_DIRECTORIES.has(entry.name)) stack.push(path.join(currentDirectory, entry.name));
@@ -141,7 +160,7 @@ const readPackageJsonUncached = (packageJsonPath) => {
141
160
  if (error instanceof SyntaxError) return {};
142
161
  if (error instanceof Error && "code" in error) {
143
162
  const { code } = error;
144
- if (code === "EISDIR" || code === "EACCES") return {};
163
+ if (code === "EISDIR" || code === "EACCES" || code === "EPERM" || code === "ENOENT") return {};
145
164
  }
146
165
  throw error;
147
166
  }
@@ -512,6 +531,13 @@ const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
512
531
  version: null
513
532
  };
514
533
  };
534
+ const isDirectory = (directoryPath) => {
535
+ try {
536
+ return fs.statSync(directoryPath).isDirectory();
537
+ } catch {
538
+ return false;
539
+ }
540
+ };
515
541
  const NX_PROJECT_DISCOVERY_DIRS = [
516
542
  "apps",
517
543
  "libs",
@@ -522,8 +548,8 @@ const getNxWorkspaceDirectories = (rootDirectory) => {
522
548
  const collected = [];
523
549
  for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
524
550
  const candidatePath = path.join(rootDirectory, candidate);
525
- if (!fs.existsSync(candidatePath) || !fs.statSync(candidatePath).isDirectory()) continue;
526
- for (const entry of fs.readdirSync(candidatePath, { withFileTypes: true })) {
551
+ if (!isDirectory(candidatePath)) continue;
552
+ for (const entry of readDirectoryEntries(candidatePath)) {
527
553
  if (!entry.isDirectory()) continue;
528
554
  const projectDirectory = path.join(candidatePath, entry.name);
529
555
  if (isFile(path.join(projectDirectory, "project.json")) || isFile(path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
@@ -565,17 +591,17 @@ const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
565
591
  const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
566
592
  if (!cleanPattern.includes("*")) {
567
593
  const directoryPath = path.join(rootDirectory, cleanPattern);
568
- if (fs.existsSync(directoryPath) && isFile(path.join(directoryPath, "package.json"))) return [directoryPath];
594
+ if (isDirectory(directoryPath) && isFile(path.join(directoryPath, "package.json"))) return [directoryPath];
569
595
  return [];
570
596
  }
571
597
  const wildcardIndex = cleanPattern.indexOf("*");
572
598
  const baseDirectory = path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
573
599
  const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
574
- if (!fs.existsSync(baseDirectory) || !fs.statSync(baseDirectory).isDirectory()) return [];
600
+ if (!isDirectory(baseDirectory)) return [];
575
601
  const resolved = [];
576
- for (const entry of fs.readdirSync(baseDirectory)) {
577
- const entryPath = path.join(baseDirectory, entry, suffixAfterWildcard);
578
- if (fs.existsSync(entryPath) && fs.statSync(entryPath).isDirectory() && isFile(path.join(entryPath, "package.json"))) resolved.push(entryPath);
602
+ for (const entry of readDirectoryEntries(baseDirectory)) {
603
+ const entryPath = path.join(baseDirectory, entry.name, suffixAfterWildcard);
604
+ if (isDirectory(entryPath) && isFile(path.join(entryPath, "package.json"))) resolved.push(entryPath);
579
605
  }
580
606
  return resolved;
581
607
  };
@@ -820,7 +846,7 @@ const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
820
846
  });
821
847
  }
822
848
  }
823
- const entries = fs.readdirSync(currentDirectory, { withFileTypes: true }).toSorted((firstEntry, secondEntry) => firstEntry.name.localeCompare(secondEntry.name));
849
+ const entries = readDirectoryEntries(currentDirectory).toSorted((firstEntry, secondEntry) => firstEntry.name.localeCompare(secondEntry.name));
824
850
  for (const entry of entries) {
825
851
  if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
826
852
  pendingDirectories.push(path.join(currentDirectory, entry.name));
@@ -829,7 +855,7 @@ const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
829
855
  return packages;
830
856
  };
831
857
  const discoverReactSubprojects = (rootDirectory) => {
832
- if (!fs.existsSync(rootDirectory) || !fs.statSync(rootDirectory).isDirectory()) return [];
858
+ if (!isDirectory(rootDirectory)) return [];
833
859
  const manifestPackages = listManifestWorkspacePackages(rootDirectory);
834
860
  if (manifestPackages.length > 0) return manifestPackages;
835
861
  return discoverReactSubprojectsByFilesystem(rootDirectory);
@@ -4289,12 +4315,7 @@ const findFilesWithDisableDirectivesViaFilesystem = (rootDirectory, includePaths
4289
4315
  while (stack.length > 0) {
4290
4316
  const current = stack.pop();
4291
4317
  if (current === void 0) continue;
4292
- let entries;
4293
- try {
4294
- entries = fs.readdirSync(current, { withFileTypes: true });
4295
- } catch {
4296
- continue;
4297
- }
4318
+ const entries = readDirectoryEntries(current);
4298
4319
  for (const entry of entries) {
4299
4320
  if (entry.isDirectory()) {
4300
4321
  if (entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
@@ -4426,7 +4447,7 @@ const resolveConfigRootDir = (config, configSourceDirectory) => {
4426
4447
  if (trimmedRootDir.length === 0) return null;
4427
4448
  const resolvedRootDir = path.isAbsolute(trimmedRootDir) ? trimmedRootDir : path.resolve(configSourceDirectory, trimmedRootDir);
4428
4449
  if (resolvedRootDir === configSourceDirectory) return null;
4429
- if (!fs.existsSync(resolvedRootDir) || !fs.statSync(resolvedRootDir).isDirectory()) {
4450
+ if (!isDirectory(resolvedRootDir)) {
4430
4451
  logger.warn(`react-doctor config "rootDir" points to "${rawRootDir}" (resolved to ${resolvedRootDir}), which is not a directory. Ignoring.`);
4431
4452
  return null;
4432
4453
  }
@@ -4452,7 +4473,7 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
4452
4473
  const stack = [rootDirectory];
4453
4474
  while (stack.length > 0) {
4454
4475
  const currentDirectory = stack.pop();
4455
- const entries = fs.readdirSync(currentDirectory, { withFileTypes: true });
4476
+ const entries = readDirectoryEntries(currentDirectory);
4456
4477
  for (const entry of entries) {
4457
4478
  const absolutePath = path.join(currentDirectory, entry.name);
4458
4479
  if (entry.isDirectory()) {
@@ -5977,7 +5998,7 @@ const CI_ENVIRONMENT_VARIABLES = [
5977
5998
  const isCiEnvironment = () => CI_ENVIRONMENT_VARIABLES.some((envVariable) => Boolean(process.env[envVariable])) || process.env.CI === "true";
5978
5999
  //#endregion
5979
6000
  //#region src/cli/utils/version.ts
5980
- const VERSION = "0.2.0";
6001
+ const VERSION = "0.2.1";
5981
6002
  //#endregion
5982
6003
  //#region src/cli/utils/json-mode.ts
5983
6004
  let context = null;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import fs from "node:fs";
1
2
  //#region ../types/dist/index.d.ts
2
3
  //#region src/config.d.ts
3
4
  type FailOnLevel = "error" | "warning" | "none";
@@ -401,7 +402,7 @@ declare class AmbiguousProjectError extends ReactDoctorError {
401
402
  constructor(directory: string, candidates: readonly string[], options?: ErrorOptions);
402
403
  }
403
404
  declare const isReactDoctorError: (value: unknown) => value is ReactDoctorError; //#endregion
404
- //#region src/utils/is-file.d.ts
405
+ //#region src/utils/is-directory.d.ts
405
406
  //#endregion
406
407
  //#region ../core/dist/index.d.ts
407
408
  //#endregion
package/dist/index.js CHANGED
@@ -113,12 +113,31 @@ const IGNORED_DIRECTORIES = new Set([
113
113
  "out",
114
114
  "storybook-static"
115
115
  ]);
116
+ const IGNORABLE_READDIR_ERROR_CODES = new Set([
117
+ "EACCES",
118
+ "EPERM",
119
+ "ENOENT",
120
+ "ENOTDIR"
121
+ ]);
122
+ const isIgnorableReaddirError = (error) => {
123
+ if (typeof error !== "object" || error === null) return false;
124
+ const errorCode = error.code;
125
+ return typeof errorCode === "string" && IGNORABLE_READDIR_ERROR_CODES.has(errorCode);
126
+ };
127
+ const readDirectoryEntries = (directoryPath) => {
128
+ try {
129
+ return fs.readdirSync(directoryPath, { withFileTypes: true });
130
+ } catch (error) {
131
+ if (isIgnorableReaddirError(error)) return [];
132
+ throw error;
133
+ }
134
+ };
116
135
  const countSourceFilesViaFilesystem = (rootDirectory) => {
117
136
  let count = 0;
118
137
  const stack = [rootDirectory];
119
138
  while (stack.length > 0) {
120
139
  const currentDirectory = stack.pop();
121
- const entries = fs.readdirSync(currentDirectory, { withFileTypes: true });
140
+ const entries = readDirectoryEntries(currentDirectory);
122
141
  for (const entry of entries) {
123
142
  if (entry.isDirectory()) {
124
143
  if (!entry.name.startsWith(".") && !IGNORED_DIRECTORIES.has(entry.name)) stack.push(path.join(currentDirectory, entry.name));
@@ -156,7 +175,7 @@ const readPackageJsonUncached = (packageJsonPath) => {
156
175
  if (error instanceof SyntaxError) return {};
157
176
  if (error instanceof Error && "code" in error) {
158
177
  const { code } = error;
159
- if (code === "EISDIR" || code === "EACCES") return {};
178
+ if (code === "EISDIR" || code === "EACCES" || code === "EPERM" || code === "ENOENT") return {};
160
179
  }
161
180
  throw error;
162
181
  }
@@ -527,6 +546,13 @@ const getDependencyDeclaration = ({ packageJson, packageName, sections }) => {
527
546
  version: null
528
547
  };
529
548
  };
549
+ const isDirectory = (directoryPath) => {
550
+ try {
551
+ return fs.statSync(directoryPath).isDirectory();
552
+ } catch {
553
+ return false;
554
+ }
555
+ };
530
556
  const NX_PROJECT_DISCOVERY_DIRS = [
531
557
  "apps",
532
558
  "libs",
@@ -537,8 +563,8 @@ const getNxWorkspaceDirectories = (rootDirectory) => {
537
563
  const collected = [];
538
564
  for (const candidate of NX_PROJECT_DISCOVERY_DIRS) {
539
565
  const candidatePath = path.join(rootDirectory, candidate);
540
- if (!fs.existsSync(candidatePath) || !fs.statSync(candidatePath).isDirectory()) continue;
541
- for (const entry of fs.readdirSync(candidatePath, { withFileTypes: true })) {
566
+ if (!isDirectory(candidatePath)) continue;
567
+ for (const entry of readDirectoryEntries(candidatePath)) {
542
568
  if (!entry.isDirectory()) continue;
543
569
  const projectDirectory = path.join(candidatePath, entry.name);
544
570
  if (isFile(path.join(projectDirectory, "project.json")) || isFile(path.join(projectDirectory, "package.json"))) collected.push(`${candidate}/${entry.name}`);
@@ -580,17 +606,17 @@ const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
580
606
  const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
581
607
  if (!cleanPattern.includes("*")) {
582
608
  const directoryPath = path.join(rootDirectory, cleanPattern);
583
- if (fs.existsSync(directoryPath) && isFile(path.join(directoryPath, "package.json"))) return [directoryPath];
609
+ if (isDirectory(directoryPath) && isFile(path.join(directoryPath, "package.json"))) return [directoryPath];
584
610
  return [];
585
611
  }
586
612
  const wildcardIndex = cleanPattern.indexOf("*");
587
613
  const baseDirectory = path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
588
614
  const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
589
- if (!fs.existsSync(baseDirectory) || !fs.statSync(baseDirectory).isDirectory()) return [];
615
+ if (!isDirectory(baseDirectory)) return [];
590
616
  const resolved = [];
591
- for (const entry of fs.readdirSync(baseDirectory)) {
592
- const entryPath = path.join(baseDirectory, entry, suffixAfterWildcard);
593
- if (fs.existsSync(entryPath) && fs.statSync(entryPath).isDirectory() && isFile(path.join(entryPath, "package.json"))) resolved.push(entryPath);
617
+ for (const entry of readDirectoryEntries(baseDirectory)) {
618
+ const entryPath = path.join(baseDirectory, entry.name, suffixAfterWildcard);
619
+ if (isDirectory(entryPath) && isFile(path.join(entryPath, "package.json"))) resolved.push(entryPath);
594
620
  }
595
621
  return resolved;
596
622
  };
@@ -835,7 +861,7 @@ const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
835
861
  });
836
862
  }
837
863
  }
838
- const entries = fs.readdirSync(currentDirectory, { withFileTypes: true }).toSorted((firstEntry, secondEntry) => firstEntry.name.localeCompare(secondEntry.name));
864
+ const entries = readDirectoryEntries(currentDirectory).toSorted((firstEntry, secondEntry) => firstEntry.name.localeCompare(secondEntry.name));
839
865
  for (const entry of entries) {
840
866
  if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
841
867
  pendingDirectories.push(path.join(currentDirectory, entry.name));
@@ -844,7 +870,7 @@ const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
844
870
  return packages;
845
871
  };
846
872
  const discoverReactSubprojects = (rootDirectory) => {
847
- if (!fs.existsSync(rootDirectory) || !fs.statSync(rootDirectory).isDirectory()) return [];
873
+ if (!isDirectory(rootDirectory)) return [];
848
874
  const manifestPackages = listManifestWorkspacePackages(rootDirectory);
849
875
  if (manifestPackages.length > 0) return manifestPackages;
850
876
  return discoverReactSubprojectsByFilesystem(rootDirectory);
@@ -4249,12 +4275,7 @@ const findFilesWithDisableDirectivesViaFilesystem = (rootDirectory, includePaths
4249
4275
  while (stack.length > 0) {
4250
4276
  const current = stack.pop();
4251
4277
  if (current === void 0) continue;
4252
- let entries;
4253
- try {
4254
- entries = fs.readdirSync(current, { withFileTypes: true });
4255
- } catch {
4256
- continue;
4257
- }
4278
+ const entries = readDirectoryEntries(current);
4258
4279
  for (const entry of entries) {
4259
4280
  if (entry.isDirectory()) {
4260
4281
  if (entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
@@ -4312,7 +4333,7 @@ const resolveConfigRootDir = (config, configSourceDirectory) => {
4312
4333
  if (trimmedRootDir.length === 0) return null;
4313
4334
  const resolvedRootDir = path.isAbsolute(trimmedRootDir) ? trimmedRootDir : path.resolve(configSourceDirectory, trimmedRootDir);
4314
4335
  if (resolvedRootDir === configSourceDirectory) return null;
4315
- if (!fs.existsSync(resolvedRootDir) || !fs.statSync(resolvedRootDir).isDirectory()) {
4336
+ if (!isDirectory(resolvedRootDir)) {
4316
4337
  logger.warn(`react-doctor config "rootDir" points to "${rawRootDir}" (resolved to ${resolvedRootDir}), which is not a directory. Ignoring.`);
4317
4338
  return null;
4318
4339
  }
@@ -4345,7 +4366,7 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
4345
4366
  const stack = [rootDirectory];
4346
4367
  while (stack.length > 0) {
4347
4368
  const currentDirectory = stack.pop();
4348
- const entries = fs.readdirSync(currentDirectory, { withFileTypes: true });
4369
+ const entries = readDirectoryEntries(currentDirectory);
4349
4370
  for (const entry of entries) {
4350
4371
  const absolutePath = path.join(currentDirectory, entry.name);
4351
4372
  if (entry.isDirectory()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Diagnose and fix React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -56,15 +56,15 @@
56
56
  "picocolors": "^1.1.1",
57
57
  "prompts": "^2.4.2",
58
58
  "typescript": ">=5.0.4 <7",
59
- "oxlint-plugin-react-doctor": "0.2.0"
59
+ "oxlint-plugin-react-doctor": "0.2.1"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/prompts": "^2.4.9",
63
63
  "eslint-plugin-react-hooks": "^7.1.1",
64
64
  "eslint-plugin-react-you-might-not-need-an-effect": "^0.10.1",
65
- "@react-doctor/core": "0.2.0",
66
- "@react-doctor/project-info": "0.2.0",
67
- "@react-doctor/types": "0.2.0"
65
+ "@react-doctor/core": "0.2.1",
66
+ "@react-doctor/types": "0.2.1",
67
+ "@react-doctor/project-info": "0.2.1"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "eslint-plugin-react-hooks": "^6 || ^7",