react-doctor 0.2.14-dev.75c1f99 → 0.2.14-dev.8921575
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 +139 -7
- package/dist/index.js +136 -4
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="d012dbb2-27e9-5092-9eab-c7fa4573f0b2")}catch(e){}}();
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
5
5
|
import * as Path from "node:path";
|
|
@@ -8016,6 +8016,61 @@ const checkExpoPackageJsonConflicts = (context) => {
|
|
|
8016
8016
|
}));
|
|
8017
8017
|
return diagnostics;
|
|
8018
8018
|
};
|
|
8019
|
+
const APP_CONFIG_JSON_FILES = ["app.config.json", "app.json"];
|
|
8020
|
+
const APP_CONFIG_DYNAMIC_FILES = [
|
|
8021
|
+
"app.config.ts",
|
|
8022
|
+
"app.config.js",
|
|
8023
|
+
"app.config.cjs",
|
|
8024
|
+
"app.config.mjs"
|
|
8025
|
+
];
|
|
8026
|
+
const ExpoConfigSchema = Schema.Struct({
|
|
8027
|
+
newArchEnabled: Schema.optional(Schema.Boolean),
|
|
8028
|
+
updates: Schema.optional(Schema.Struct({ disableAntiBrickingMeasures: Schema.optional(Schema.Boolean) }))
|
|
8029
|
+
});
|
|
8030
|
+
const AppManifestSchema = Schema.Struct({ expo: Schema.optional(ExpoConfigSchema) });
|
|
8031
|
+
const NO_CONFIG = {
|
|
8032
|
+
config: null,
|
|
8033
|
+
configFile: null
|
|
8034
|
+
};
|
|
8035
|
+
const decodeExpoConfig = (filePath) => {
|
|
8036
|
+
let raw;
|
|
8037
|
+
try {
|
|
8038
|
+
raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
8039
|
+
} catch {
|
|
8040
|
+
return null;
|
|
8041
|
+
}
|
|
8042
|
+
return Option.getOrNull(Schema.decodeUnknownOption(AppManifestSchema)(raw))?.expo ?? null;
|
|
8043
|
+
};
|
|
8044
|
+
const readExpoAppConfig = (rootDirectory) => {
|
|
8045
|
+
if (APP_CONFIG_DYNAMIC_FILES.some((fileName) => isFile(path.join(rootDirectory, fileName)))) return NO_CONFIG;
|
|
8046
|
+
for (const fileName of APP_CONFIG_JSON_FILES) {
|
|
8047
|
+
const filePath = path.join(rootDirectory, fileName);
|
|
8048
|
+
if (!isFile(filePath)) continue;
|
|
8049
|
+
const config = decodeExpoConfig(filePath);
|
|
8050
|
+
if (config) return {
|
|
8051
|
+
config,
|
|
8052
|
+
configFile: fileName
|
|
8053
|
+
};
|
|
8054
|
+
}
|
|
8055
|
+
return NO_CONFIG;
|
|
8056
|
+
};
|
|
8057
|
+
const REANIMATED_PACKAGE = "react-native-reanimated";
|
|
8058
|
+
const WORKLETS_PACKAGE = "react-native-worklets";
|
|
8059
|
+
const FIRST_NEW_ARCH_ONLY_REANIMATED_MAJOR = 4;
|
|
8060
|
+
const checkExpoReanimatedNewArch = (context) => {
|
|
8061
|
+
const reanimatedSpec = context.packageJson.dependencies?.[REANIMATED_PACKAGE] ?? context.packageJson.devDependencies?.[REANIMATED_PACKAGE];
|
|
8062
|
+
const reanimatedMajor = reanimatedSpec === void 0 ? null : getLowestDependencyMajor(reanimatedSpec);
|
|
8063
|
+
if (!(reanimatedMajor !== null && reanimatedMajor >= FIRST_NEW_ARCH_ONLY_REANIMATED_MAJOR || context.directDependencyNames.has(WORKLETS_PACKAGE))) return [];
|
|
8064
|
+
const appConfig = readExpoAppConfig(context.rootDirectory);
|
|
8065
|
+
if (appConfig.config?.newArchEnabled !== false) return [];
|
|
8066
|
+
return [buildExpoDiagnostic({
|
|
8067
|
+
rule: "expo-reanimated-v4-requires-new-arch",
|
|
8068
|
+
severity: "error",
|
|
8069
|
+
filePath: appConfig.configFile ?? "app.json",
|
|
8070
|
+
message: "react-native-reanimated v4 supports only the New Architecture, but `newArchEnabled: false` is set in your app config, so the app will crash on first launch.",
|
|
8071
|
+
help: "Remove `newArchEnabled: false` from your app config (the New Architecture is the default on SDK 52+), or pin react-native-reanimated to v3 if you must stay on the legacy architecture."
|
|
8072
|
+
})];
|
|
8073
|
+
};
|
|
8019
8074
|
const EXPO_ROUTER_REACT_NAVIGATION_MIN_SDK_MAJOR = 56;
|
|
8020
8075
|
const EXPO_ROUTER_REACT_NAVIGATION_MAX_SDK_MAJOR_EXCLUSIVE = 57;
|
|
8021
8076
|
const checkExpoRouterReactNavigation = (context) => {
|
|
@@ -8031,6 +8086,17 @@ const checkExpoRouterReactNavigation = (context) => {
|
|
|
8031
8086
|
help: "Remove these `@react-navigation/*` packages and replace direct imports with their expo-router equivalents. See https://docs.expo.dev/router/migrate/sdk-55-to-56/"
|
|
8032
8087
|
})];
|
|
8033
8088
|
};
|
|
8089
|
+
const checkExpoUpdatesConfig = (context) => {
|
|
8090
|
+
const appConfig = readExpoAppConfig(context.rootDirectory);
|
|
8091
|
+
if (appConfig.config?.updates?.disableAntiBrickingMeasures !== true) return [];
|
|
8092
|
+
return [buildExpoDiagnostic({
|
|
8093
|
+
rule: "expo-updates-no-unsafe-production-config",
|
|
8094
|
+
severity: "error",
|
|
8095
|
+
filePath: appConfig.configFile ?? "app.json",
|
|
8096
|
+
message: "`updates.disableAntiBrickingMeasures: true` disables expo-updates' recovery safeguards and is liable to leave installed apps in a permanently bricked state, so it must not be used in production.",
|
|
8097
|
+
help: "Remove `disableAntiBrickingMeasures` from your app config's `updates` block. See https://docs.expo.dev/versions/latest/config/app/#updates"
|
|
8098
|
+
})];
|
|
8099
|
+
};
|
|
8034
8100
|
const VECTOR_ICONS_MIN_SDK_MAJOR = 56;
|
|
8035
8101
|
const SCOPED_VECTOR_ICONS_NAMESPACE = "@react-native-vector-icons/";
|
|
8036
8102
|
const CONFLICTING_VECTOR_ICONS_PACKAGES = ["@expo/vector-icons", "react-native-vector-icons"];
|
|
@@ -8057,7 +8123,9 @@ const checkExpoProject = (rootDirectory, project) => {
|
|
|
8057
8123
|
...checkExpoLockfile(context),
|
|
8058
8124
|
...checkExpoGitignore(context),
|
|
8059
8125
|
...checkExpoEnvLocalFiles(context),
|
|
8060
|
-
...checkExpoMetroConfig(context)
|
|
8126
|
+
...checkExpoMetroConfig(context),
|
|
8127
|
+
...checkExpoReanimatedNewArch(context),
|
|
8128
|
+
...checkExpoUpdatesConfig(context)
|
|
8061
8129
|
];
|
|
8062
8130
|
};
|
|
8063
8131
|
const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
@@ -8174,6 +8242,69 @@ const checkPnpmHardening = (rootDirectory) => {
|
|
|
8174
8242
|
}));
|
|
8175
8243
|
return diagnostics;
|
|
8176
8244
|
};
|
|
8245
|
+
const BUILDER_BOB_PACKAGE = "react-native-builder-bob";
|
|
8246
|
+
const isBuilderBobLibrary = (packageJson) => {
|
|
8247
|
+
const bobConfig = packageJson[BUILDER_BOB_PACKAGE];
|
|
8248
|
+
return typeof bobConfig === "object" && bobConfig !== null;
|
|
8249
|
+
};
|
|
8250
|
+
const checkReactNativeLibraryDependencies = (rootDirectory) => {
|
|
8251
|
+
const packageJson = readPackageJson$1(path.join(rootDirectory, "package.json"));
|
|
8252
|
+
if (!isBuilderBobLibrary(packageJson)) return [];
|
|
8253
|
+
const misplaced = ["react", "react-native"].filter((name) => packageJson.dependencies?.[name] !== void 0);
|
|
8254
|
+
if (misplaced.length === 0) return [];
|
|
8255
|
+
const quoted = misplaced.map((name) => `"${name}"`).join(" and ");
|
|
8256
|
+
return [{
|
|
8257
|
+
filePath: "package.json",
|
|
8258
|
+
plugin: "react-doctor",
|
|
8259
|
+
rule: "rn-library-react-in-dependencies",
|
|
8260
|
+
severity: "warning",
|
|
8261
|
+
message: `This react-native-builder-bob library lists ${quoted} in \`dependencies\` — that ships a second copy into consumer apps, causing "Invalid hook call" (duplicate React) and duplicate-native-module crashes.`,
|
|
8262
|
+
help: `Move ${quoted} to \`peerDependencies\` (keep ${misplaced.length === 1 ? "it" : "them"} in \`devDependencies\` for local development).`,
|
|
8263
|
+
line: 0,
|
|
8264
|
+
column: 0,
|
|
8265
|
+
category: "Correctness"
|
|
8266
|
+
}];
|
|
8267
|
+
};
|
|
8268
|
+
const BABEL_CONFIG_FILE_NAMES = [
|
|
8269
|
+
"babel.config.js",
|
|
8270
|
+
"babel.config.cjs",
|
|
8271
|
+
"babel.config.mjs",
|
|
8272
|
+
"babel.config.json",
|
|
8273
|
+
".babelrc",
|
|
8274
|
+
".babelrc.js",
|
|
8275
|
+
".babelrc.json"
|
|
8276
|
+
];
|
|
8277
|
+
const LEGACY_PRESET_SPEC = "module:metro-react-native-babel-preset";
|
|
8278
|
+
const checkReactNativeMetroBabelPreset = (rootDirectory) => {
|
|
8279
|
+
for (const fileName of BABEL_CONFIG_FILE_NAMES) {
|
|
8280
|
+
const filePath = path.join(rootDirectory, fileName);
|
|
8281
|
+
if (!isFile(filePath)) continue;
|
|
8282
|
+
let contents;
|
|
8283
|
+
try {
|
|
8284
|
+
contents = fs.readFileSync(filePath, "utf-8");
|
|
8285
|
+
} catch {
|
|
8286
|
+
continue;
|
|
8287
|
+
}
|
|
8288
|
+
if (!contents.includes(LEGACY_PRESET_SPEC)) continue;
|
|
8289
|
+
return [{
|
|
8290
|
+
filePath: fileName,
|
|
8291
|
+
plugin: "react-doctor",
|
|
8292
|
+
rule: "rn-no-metro-babel-preset",
|
|
8293
|
+
severity: "error",
|
|
8294
|
+
message: "`module:metro-react-native-babel-preset` was renamed to `@react-native/babel-preset` and is no longer installed by React Native 0.73+ — this preset reference fails to resolve and breaks the Metro/Babel transform.",
|
|
8295
|
+
help: "Replace the preset with `module:@react-native/babel-preset` (or `babel-preset-expo` on Expo) and remove the old `metro-react-native-babel-preset` dependency.",
|
|
8296
|
+
line: 0,
|
|
8297
|
+
column: 0,
|
|
8298
|
+
category: "Correctness"
|
|
8299
|
+
}];
|
|
8300
|
+
}
|
|
8301
|
+
return [];
|
|
8302
|
+
};
|
|
8303
|
+
const isReactNativeProject = (project) => project.framework === "react-native" || project.framework === "expo" || project.hasReactNativeWorkspace || project.expoVersion !== null;
|
|
8304
|
+
const checkReactNativeProject = (rootDirectory, project) => {
|
|
8305
|
+
if (!isReactNativeProject(project)) return [];
|
|
8306
|
+
return [...checkReactNativeMetroBabelPreset(rootDirectory), ...checkReactNativeLibraryDependencies(rootDirectory)];
|
|
8307
|
+
};
|
|
8177
8308
|
const REDUCED_MOTION_GREP_PATTERN = "prefers-reduced-motion|useReducedMotion|MotionConfig|reducedMotion";
|
|
8178
8309
|
const REDUCED_MOTION_FILE_GLOBS = [
|
|
8179
8310
|
"*.ts",
|
|
@@ -10931,7 +11062,8 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
10931
11062
|
const environmentDiagnostics = isDiffMode ? [] : [
|
|
10932
11063
|
...checkReducedMotion(scanDirectory),
|
|
10933
11064
|
...checkPnpmHardening(scanDirectory),
|
|
10934
|
-
...checkExpoProject(scanDirectory, project)
|
|
11065
|
+
...checkExpoProject(scanDirectory, project),
|
|
11066
|
+
...checkReactNativeProject(scanDirectory, project)
|
|
10935
11067
|
];
|
|
10936
11068
|
const envCollected = yield* Stream.runCollect(applyPerElementPipeline(Stream.fromIterable(environmentDiagnostics)));
|
|
10937
11069
|
const lintFailure = yield* Ref.make({
|
|
@@ -11669,7 +11801,7 @@ const makeNoopConsole = () => ({
|
|
|
11669
11801
|
});
|
|
11670
11802
|
//#endregion
|
|
11671
11803
|
//#region src/cli/utils/version.ts
|
|
11672
|
-
const VERSION = "0.2.14-dev.
|
|
11804
|
+
const VERSION = "0.2.14-dev.8921575";
|
|
11673
11805
|
//#endregion
|
|
11674
11806
|
//#region src/cli/utils/json-mode.ts
|
|
11675
11807
|
let context = null;
|
|
@@ -11985,13 +12117,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
11985
12117
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
11986
12118
|
* standard `SENTRY_RELEASE` override.
|
|
11987
12119
|
*/
|
|
11988
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.2.14-dev.
|
|
12120
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.2.14-dev.8921575`;
|
|
11989
12121
|
/**
|
|
11990
12122
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
11991
12123
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
11992
12124
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
11993
12125
|
*/
|
|
11994
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.2.14-dev.
|
|
12126
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.2.14-dev.8921575") ? "development" : "production");
|
|
11995
12127
|
/**
|
|
11996
12128
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
11997
12129
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -19525,4 +19657,4 @@ program.parseAsync(argv).then(() => flushSentry()).catch(async (error) => {
|
|
|
19525
19657
|
export {};
|
|
19526
19658
|
|
|
19527
19659
|
//# sourceMappingURL=cli.js.map
|
|
19528
|
-
//# debugId=
|
|
19660
|
+
//# debugId=d012dbb2-27e9-5092-9eab-c7fa4573f0b2
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="df20b0d3-9e4e-52e0-8991-ce8a6349a04a")}catch(e){}}();
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as Schema from "effect/Schema";
|
|
5
5
|
import * as fs$1 from "node:fs";
|
|
@@ -4966,6 +4966,61 @@ const checkExpoPackageJsonConflicts = (context) => {
|
|
|
4966
4966
|
}));
|
|
4967
4967
|
return diagnostics;
|
|
4968
4968
|
};
|
|
4969
|
+
const APP_CONFIG_JSON_FILES = ["app.config.json", "app.json"];
|
|
4970
|
+
const APP_CONFIG_DYNAMIC_FILES = [
|
|
4971
|
+
"app.config.ts",
|
|
4972
|
+
"app.config.js",
|
|
4973
|
+
"app.config.cjs",
|
|
4974
|
+
"app.config.mjs"
|
|
4975
|
+
];
|
|
4976
|
+
const ExpoConfigSchema = Schema.Struct({
|
|
4977
|
+
newArchEnabled: Schema.optional(Schema.Boolean),
|
|
4978
|
+
updates: Schema.optional(Schema.Struct({ disableAntiBrickingMeasures: Schema.optional(Schema.Boolean) }))
|
|
4979
|
+
});
|
|
4980
|
+
const AppManifestSchema = Schema.Struct({ expo: Schema.optional(ExpoConfigSchema) });
|
|
4981
|
+
const NO_CONFIG = {
|
|
4982
|
+
config: null,
|
|
4983
|
+
configFile: null
|
|
4984
|
+
};
|
|
4985
|
+
const decodeExpoConfig = (filePath) => {
|
|
4986
|
+
let raw;
|
|
4987
|
+
try {
|
|
4988
|
+
raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
4989
|
+
} catch {
|
|
4990
|
+
return null;
|
|
4991
|
+
}
|
|
4992
|
+
return Option.getOrNull(Schema.decodeUnknownOption(AppManifestSchema)(raw))?.expo ?? null;
|
|
4993
|
+
};
|
|
4994
|
+
const readExpoAppConfig = (rootDirectory) => {
|
|
4995
|
+
if (APP_CONFIG_DYNAMIC_FILES.some((fileName) => isFile(path.join(rootDirectory, fileName)))) return NO_CONFIG;
|
|
4996
|
+
for (const fileName of APP_CONFIG_JSON_FILES) {
|
|
4997
|
+
const filePath = path.join(rootDirectory, fileName);
|
|
4998
|
+
if (!isFile(filePath)) continue;
|
|
4999
|
+
const config = decodeExpoConfig(filePath);
|
|
5000
|
+
if (config) return {
|
|
5001
|
+
config,
|
|
5002
|
+
configFile: fileName
|
|
5003
|
+
};
|
|
5004
|
+
}
|
|
5005
|
+
return NO_CONFIG;
|
|
5006
|
+
};
|
|
5007
|
+
const REANIMATED_PACKAGE = "react-native-reanimated";
|
|
5008
|
+
const WORKLETS_PACKAGE = "react-native-worklets";
|
|
5009
|
+
const FIRST_NEW_ARCH_ONLY_REANIMATED_MAJOR = 4;
|
|
5010
|
+
const checkExpoReanimatedNewArch = (context) => {
|
|
5011
|
+
const reanimatedSpec = context.packageJson.dependencies?.[REANIMATED_PACKAGE] ?? context.packageJson.devDependencies?.[REANIMATED_PACKAGE];
|
|
5012
|
+
const reanimatedMajor = reanimatedSpec === void 0 ? null : getLowestDependencyMajor(reanimatedSpec);
|
|
5013
|
+
if (!(reanimatedMajor !== null && reanimatedMajor >= FIRST_NEW_ARCH_ONLY_REANIMATED_MAJOR || context.directDependencyNames.has(WORKLETS_PACKAGE))) return [];
|
|
5014
|
+
const appConfig = readExpoAppConfig(context.rootDirectory);
|
|
5015
|
+
if (appConfig.config?.newArchEnabled !== false) return [];
|
|
5016
|
+
return [buildExpoDiagnostic({
|
|
5017
|
+
rule: "expo-reanimated-v4-requires-new-arch",
|
|
5018
|
+
severity: "error",
|
|
5019
|
+
filePath: appConfig.configFile ?? "app.json",
|
|
5020
|
+
message: "react-native-reanimated v4 supports only the New Architecture, but `newArchEnabled: false` is set in your app config, so the app will crash on first launch.",
|
|
5021
|
+
help: "Remove `newArchEnabled: false` from your app config (the New Architecture is the default on SDK 52+), or pin react-native-reanimated to v3 if you must stay on the legacy architecture."
|
|
5022
|
+
})];
|
|
5023
|
+
};
|
|
4969
5024
|
const EXPO_ROUTER_REACT_NAVIGATION_MIN_SDK_MAJOR = 56;
|
|
4970
5025
|
const EXPO_ROUTER_REACT_NAVIGATION_MAX_SDK_MAJOR_EXCLUSIVE = 57;
|
|
4971
5026
|
const checkExpoRouterReactNavigation = (context) => {
|
|
@@ -4981,6 +5036,17 @@ const checkExpoRouterReactNavigation = (context) => {
|
|
|
4981
5036
|
help: "Remove these `@react-navigation/*` packages and replace direct imports with their expo-router equivalents. See https://docs.expo.dev/router/migrate/sdk-55-to-56/"
|
|
4982
5037
|
})];
|
|
4983
5038
|
};
|
|
5039
|
+
const checkExpoUpdatesConfig = (context) => {
|
|
5040
|
+
const appConfig = readExpoAppConfig(context.rootDirectory);
|
|
5041
|
+
if (appConfig.config?.updates?.disableAntiBrickingMeasures !== true) return [];
|
|
5042
|
+
return [buildExpoDiagnostic({
|
|
5043
|
+
rule: "expo-updates-no-unsafe-production-config",
|
|
5044
|
+
severity: "error",
|
|
5045
|
+
filePath: appConfig.configFile ?? "app.json",
|
|
5046
|
+
message: "`updates.disableAntiBrickingMeasures: true` disables expo-updates' recovery safeguards and is liable to leave installed apps in a permanently bricked state, so it must not be used in production.",
|
|
5047
|
+
help: "Remove `disableAntiBrickingMeasures` from your app config's `updates` block. See https://docs.expo.dev/versions/latest/config/app/#updates"
|
|
5048
|
+
})];
|
|
5049
|
+
};
|
|
4984
5050
|
const VECTOR_ICONS_MIN_SDK_MAJOR = 56;
|
|
4985
5051
|
const SCOPED_VECTOR_ICONS_NAMESPACE = "@react-native-vector-icons/";
|
|
4986
5052
|
const CONFLICTING_VECTOR_ICONS_PACKAGES = ["@expo/vector-icons", "react-native-vector-icons"];
|
|
@@ -5007,7 +5073,9 @@ const checkExpoProject = (rootDirectory, project) => {
|
|
|
5007
5073
|
...checkExpoLockfile(context),
|
|
5008
5074
|
...checkExpoGitignore(context),
|
|
5009
5075
|
...checkExpoEnvLocalFiles(context),
|
|
5010
|
-
...checkExpoMetroConfig(context)
|
|
5076
|
+
...checkExpoMetroConfig(context),
|
|
5077
|
+
...checkExpoReanimatedNewArch(context),
|
|
5078
|
+
...checkExpoUpdatesConfig(context)
|
|
5011
5079
|
];
|
|
5012
5080
|
};
|
|
5013
5081
|
const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
@@ -5124,6 +5192,69 @@ const checkPnpmHardening = (rootDirectory) => {
|
|
|
5124
5192
|
}));
|
|
5125
5193
|
return diagnostics;
|
|
5126
5194
|
};
|
|
5195
|
+
const BUILDER_BOB_PACKAGE = "react-native-builder-bob";
|
|
5196
|
+
const isBuilderBobLibrary = (packageJson) => {
|
|
5197
|
+
const bobConfig = packageJson[BUILDER_BOB_PACKAGE];
|
|
5198
|
+
return typeof bobConfig === "object" && bobConfig !== null;
|
|
5199
|
+
};
|
|
5200
|
+
const checkReactNativeLibraryDependencies = (rootDirectory) => {
|
|
5201
|
+
const packageJson = readPackageJson(path.join(rootDirectory, "package.json"));
|
|
5202
|
+
if (!isBuilderBobLibrary(packageJson)) return [];
|
|
5203
|
+
const misplaced = ["react", "react-native"].filter((name) => packageJson.dependencies?.[name] !== void 0);
|
|
5204
|
+
if (misplaced.length === 0) return [];
|
|
5205
|
+
const quoted = misplaced.map((name) => `"${name}"`).join(" and ");
|
|
5206
|
+
return [{
|
|
5207
|
+
filePath: "package.json",
|
|
5208
|
+
plugin: "react-doctor",
|
|
5209
|
+
rule: "rn-library-react-in-dependencies",
|
|
5210
|
+
severity: "warning",
|
|
5211
|
+
message: `This react-native-builder-bob library lists ${quoted} in \`dependencies\` — that ships a second copy into consumer apps, causing "Invalid hook call" (duplicate React) and duplicate-native-module crashes.`,
|
|
5212
|
+
help: `Move ${quoted} to \`peerDependencies\` (keep ${misplaced.length === 1 ? "it" : "them"} in \`devDependencies\` for local development).`,
|
|
5213
|
+
line: 0,
|
|
5214
|
+
column: 0,
|
|
5215
|
+
category: "Correctness"
|
|
5216
|
+
}];
|
|
5217
|
+
};
|
|
5218
|
+
const BABEL_CONFIG_FILE_NAMES = [
|
|
5219
|
+
"babel.config.js",
|
|
5220
|
+
"babel.config.cjs",
|
|
5221
|
+
"babel.config.mjs",
|
|
5222
|
+
"babel.config.json",
|
|
5223
|
+
".babelrc",
|
|
5224
|
+
".babelrc.js",
|
|
5225
|
+
".babelrc.json"
|
|
5226
|
+
];
|
|
5227
|
+
const LEGACY_PRESET_SPEC = "module:metro-react-native-babel-preset";
|
|
5228
|
+
const checkReactNativeMetroBabelPreset = (rootDirectory) => {
|
|
5229
|
+
for (const fileName of BABEL_CONFIG_FILE_NAMES) {
|
|
5230
|
+
const filePath = path.join(rootDirectory, fileName);
|
|
5231
|
+
if (!isFile(filePath)) continue;
|
|
5232
|
+
let contents;
|
|
5233
|
+
try {
|
|
5234
|
+
contents = fs.readFileSync(filePath, "utf-8");
|
|
5235
|
+
} catch {
|
|
5236
|
+
continue;
|
|
5237
|
+
}
|
|
5238
|
+
if (!contents.includes(LEGACY_PRESET_SPEC)) continue;
|
|
5239
|
+
return [{
|
|
5240
|
+
filePath: fileName,
|
|
5241
|
+
plugin: "react-doctor",
|
|
5242
|
+
rule: "rn-no-metro-babel-preset",
|
|
5243
|
+
severity: "error",
|
|
5244
|
+
message: "`module:metro-react-native-babel-preset` was renamed to `@react-native/babel-preset` and is no longer installed by React Native 0.73+ — this preset reference fails to resolve and breaks the Metro/Babel transform.",
|
|
5245
|
+
help: "Replace the preset with `module:@react-native/babel-preset` (or `babel-preset-expo` on Expo) and remove the old `metro-react-native-babel-preset` dependency.",
|
|
5246
|
+
line: 0,
|
|
5247
|
+
column: 0,
|
|
5248
|
+
category: "Correctness"
|
|
5249
|
+
}];
|
|
5250
|
+
}
|
|
5251
|
+
return [];
|
|
5252
|
+
};
|
|
5253
|
+
const isReactNativeProject = (project) => project.framework === "react-native" || project.framework === "expo" || project.hasReactNativeWorkspace || project.expoVersion !== null;
|
|
5254
|
+
const checkReactNativeProject = (rootDirectory, project) => {
|
|
5255
|
+
if (!isReactNativeProject(project)) return [];
|
|
5256
|
+
return [...checkReactNativeMetroBabelPreset(rootDirectory), ...checkReactNativeLibraryDependencies(rootDirectory)];
|
|
5257
|
+
};
|
|
5127
5258
|
const REDUCED_MOTION_GREP_PATTERN = "prefers-reduced-motion|useReducedMotion|MotionConfig|reducedMotion";
|
|
5128
5259
|
const REDUCED_MOTION_FILE_GLOBS = [
|
|
5129
5260
|
"*.ts",
|
|
@@ -7884,7 +8015,8 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
7884
8015
|
const environmentDiagnostics = isDiffMode ? [] : [
|
|
7885
8016
|
...checkReducedMotion(scanDirectory),
|
|
7886
8017
|
...checkPnpmHardening(scanDirectory),
|
|
7887
|
-
...checkExpoProject(scanDirectory, project)
|
|
8018
|
+
...checkExpoProject(scanDirectory, project),
|
|
8019
|
+
...checkReactNativeProject(scanDirectory, project)
|
|
7888
8020
|
];
|
|
7889
8021
|
const envCollected = yield* Stream.runCollect(applyPerElementPipeline(Stream.fromIterable(environmentDiagnostics)));
|
|
7890
8022
|
const lintFailure = yield* Ref.make({
|
|
@@ -8488,4 +8620,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
8488
8620
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
8489
8621
|
|
|
8490
8622
|
//# sourceMappingURL=index.js.map
|
|
8491
|
-
//# debugId=
|
|
8623
|
+
//# debugId=df20b0d3-9e4e-52e0-8991-ce8a6349a04a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.2.14-dev.
|
|
3
|
+
"version": "0.2.14-dev.8921575",
|
|
4
4
|
"description": "Diagnose and fix React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"oxlint": "^1.66.0",
|
|
64
64
|
"prompts": "^2.4.2",
|
|
65
65
|
"typescript": ">=5.0.4 <7",
|
|
66
|
-
"oxlint-plugin-react-doctor": "0.2.14-dev.
|
|
66
|
+
"oxlint-plugin-react-doctor": "0.2.14-dev.8921575"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@types/babel__code-frame": "^7.27.0",
|
|
@@ -71,8 +71,8 @@
|
|
|
71
71
|
"@xterm/headless": "^6.0.0",
|
|
72
72
|
"commander": "^14.0.3",
|
|
73
73
|
"ora": "^9.4.0",
|
|
74
|
-
"@react-doctor/
|
|
75
|
-
"@react-doctor/
|
|
74
|
+
"@react-doctor/core": "0.2.14",
|
|
75
|
+
"@react-doctor/api": "0.2.14"
|
|
76
76
|
},
|
|
77
77
|
"engines": {
|
|
78
78
|
"node": "^20.19.0 || >=22.12.0"
|