react-doctor 0.8.0-dev.eb2d687 → 0.8.1-dev.0b0b5ac
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/README.md +14 -4
- package/dist/cli.js +1018 -169
- package/dist/index.d.ts +36 -15
- package/dist/index.js +435 -59
- package/dist/lsp.js +436 -60
- package/dist/skills/react-doctor/SKILL.md +5 -0
- package/package.json +6 -6
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]="a9af63ed-7b82-56d3-8549-274ab7f23579")}catch(e){}}();
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as NodeChildProcess from "node:child_process";
|
|
5
5
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -36242,7 +36242,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
36242
36242
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
36243
36243
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
36244
36244
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
36245
|
-
const CORE_PACKAGE_VERSION = "0.8.
|
|
36245
|
+
const CORE_PACKAGE_VERSION = "0.8.1";
|
|
36246
36246
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
36247
36247
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
36248
36248
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
@@ -36571,10 +36571,10 @@ const FALLBACK_TSCONFIG_FILENAMES = ["tsconfig.app.json", "tsconfig.build.json"]
|
|
|
36571
36571
|
const FRAMEWORK_PACKAGES = {
|
|
36572
36572
|
next: "nextjs",
|
|
36573
36573
|
"@tanstack/react-start": "tanstack-start",
|
|
36574
|
-
vite: "vite",
|
|
36575
|
-
"react-scripts": "cra",
|
|
36576
36574
|
"@remix-run/react": "remix",
|
|
36577
36575
|
gatsby: "gatsby",
|
|
36576
|
+
vite: "vite",
|
|
36577
|
+
"react-scripts": "cra",
|
|
36578
36578
|
expo: "expo",
|
|
36579
36579
|
"react-native": "react-native"
|
|
36580
36580
|
};
|
|
@@ -36598,11 +36598,17 @@ const NEXT_CONFIG_FILENAMES = [
|
|
|
36598
36598
|
];
|
|
36599
36599
|
const BABEL_CONFIG_FILENAMES = [
|
|
36600
36600
|
".babelrc",
|
|
36601
|
+
".babelrc.js",
|
|
36601
36602
|
".babelrc.json",
|
|
36603
|
+
".babelrc.cjs",
|
|
36604
|
+
".babelrc.mjs",
|
|
36605
|
+
".babelrc.cts",
|
|
36602
36606
|
"babel.config.js",
|
|
36603
36607
|
"babel.config.json",
|
|
36604
36608
|
"babel.config.cjs",
|
|
36605
|
-
"babel.config.mjs"
|
|
36609
|
+
"babel.config.mjs",
|
|
36610
|
+
"babel.config.ts",
|
|
36611
|
+
"babel.config.cts"
|
|
36606
36612
|
];
|
|
36607
36613
|
const VITE_CONFIG_FILENAMES = [
|
|
36608
36614
|
"vite.config.js",
|
|
@@ -36614,12 +36620,28 @@ const VITE_CONFIG_FILENAMES = [
|
|
|
36614
36620
|
"vitest.config.ts",
|
|
36615
36621
|
"vitest.config.js"
|
|
36616
36622
|
];
|
|
36623
|
+
const RSBUILD_CONFIG_FILENAMES = [
|
|
36624
|
+
"rsbuild.config.ts",
|
|
36625
|
+
"rsbuild.config.js",
|
|
36626
|
+
"rsbuild.config.mts",
|
|
36627
|
+
"rsbuild.config.mjs",
|
|
36628
|
+
"rsbuild.config.cts",
|
|
36629
|
+
"rsbuild.config.cjs"
|
|
36630
|
+
];
|
|
36631
|
+
const RSPACK_CONFIG_FILENAMES = [
|
|
36632
|
+
"rspack.config.ts",
|
|
36633
|
+
"rspack.config.js",
|
|
36634
|
+
"rspack.config.mts",
|
|
36635
|
+
"rspack.config.mjs",
|
|
36636
|
+
"rspack.config.cts",
|
|
36637
|
+
"rspack.config.cjs"
|
|
36638
|
+
];
|
|
36617
36639
|
const EXPO_APP_CONFIG_FILENAMES = [
|
|
36618
36640
|
"app.json",
|
|
36619
36641
|
"app.config.js",
|
|
36620
36642
|
"app.config.ts"
|
|
36621
36643
|
];
|
|
36622
|
-
const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|["']react-compiler["']/;
|
|
36644
|
+
const REACT_COMPILER_PACKAGE_REFERENCE_PATTERN = /babel-plugin-react-compiler|react-compiler-runtime|reactCompilerPreset\s*\(|["']react-compiler["']/;
|
|
36623
36645
|
const REACT_COMPILER_ENABLED_FLAG_PATTERN = /["']?reactCompiler["']?\s*:\s*(?:true\b|\{)/;
|
|
36624
36646
|
const STATIC_EXPORT_OUTPUT_PATTERN = /(?:^|[^.\w])["']?output["']?\s*:\s*["']export["']/m;
|
|
36625
36647
|
const hasCompilerPackage = (packageJson, compilerPackages) => {
|
|
@@ -36654,6 +36676,8 @@ const detectReactCompiler = (directory, packageJson) => {
|
|
|
36654
36676
|
if (hasCompilerInConfigFiles(directory, NEXT_CONFIG_FILENAMES)) return true;
|
|
36655
36677
|
if (hasCompilerInConfigFiles(directory, BABEL_CONFIG_FILENAMES)) return true;
|
|
36656
36678
|
if (hasCompilerInConfigFiles(directory, VITE_CONFIG_FILENAMES)) return true;
|
|
36679
|
+
if (hasCompilerInConfigFiles(directory, RSBUILD_CONFIG_FILENAMES)) return true;
|
|
36680
|
+
if (hasCompilerInConfigFiles(directory, RSPACK_CONFIG_FILENAMES)) return true;
|
|
36657
36681
|
if (hasCompilerInConfigFiles(directory, EXPO_APP_CONFIG_FILENAMES)) return true;
|
|
36658
36682
|
return hasCompilerPackageInAncestors(directory, REACT_COMPILER_TRANSFORM_PACKAGES);
|
|
36659
36683
|
};
|
|
@@ -37101,19 +37125,6 @@ const getPreactVersion = (packageJson) => {
|
|
|
37101
37125
|
...packageJson.devDependencies
|
|
37102
37126
|
}.preact ?? null;
|
|
37103
37127
|
};
|
|
37104
|
-
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
37105
|
-
"@tanstack/react-query",
|
|
37106
|
-
"@tanstack/query-core",
|
|
37107
|
-
"react-query"
|
|
37108
|
-
]);
|
|
37109
|
-
const hasTanStackQuery = (packageJson) => {
|
|
37110
|
-
const allDependencies = {
|
|
37111
|
-
...packageJson.peerDependencies,
|
|
37112
|
-
...packageJson.dependencies,
|
|
37113
|
-
...packageJson.devDependencies
|
|
37114
|
-
};
|
|
37115
|
-
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
37116
|
-
};
|
|
37117
37128
|
const resolveCatalogBackedDependencyVersion = ({ rootDirectory, rootPackageJson, packageName, version }) => {
|
|
37118
37129
|
if (version === null || !isCatalogReference(version)) return version;
|
|
37119
37130
|
const catalogName = extractCatalogName(version);
|
|
@@ -37316,7 +37327,60 @@ const listWorkspacePackages = (rootDirectory) => {
|
|
|
37316
37327
|
}
|
|
37317
37328
|
return packages;
|
|
37318
37329
|
};
|
|
37330
|
+
const getPreferredDependencyVersion = ({ packageJson, packageNames }) => {
|
|
37331
|
+
const allDependencies = {
|
|
37332
|
+
...packageJson.devDependencies,
|
|
37333
|
+
...packageJson.peerDependencies,
|
|
37334
|
+
...packageJson.dependencies
|
|
37335
|
+
};
|
|
37336
|
+
for (const packageName of packageNames) {
|
|
37337
|
+
const version = allDependencies[packageName];
|
|
37338
|
+
if (version !== void 0) return version;
|
|
37339
|
+
}
|
|
37340
|
+
return null;
|
|
37341
|
+
};
|
|
37342
|
+
const TANSTACK_REACT_QUERY_PACKAGES = ["@tanstack/react-query", "react-query"];
|
|
37343
|
+
const getTanStackQueryVersion = (packageJson) => {
|
|
37344
|
+
return getPreferredDependencyVersion({
|
|
37345
|
+
packageJson,
|
|
37346
|
+
packageNames: TANSTACK_REACT_QUERY_PACKAGES
|
|
37347
|
+
});
|
|
37348
|
+
};
|
|
37349
|
+
const STYLED_COMPONENTS_PACKAGES = ["styled-components"];
|
|
37350
|
+
const getStyledComponentsVersion = (packageJson) => {
|
|
37351
|
+
return getPreferredDependencyVersion({
|
|
37352
|
+
packageJson,
|
|
37353
|
+
packageNames: STYLED_COMPONENTS_PACKAGES
|
|
37354
|
+
});
|
|
37355
|
+
};
|
|
37356
|
+
const I18N_PACKAGES = [
|
|
37357
|
+
"i18next",
|
|
37358
|
+
"react-i18next",
|
|
37359
|
+
"next-i18next",
|
|
37360
|
+
"react-intl",
|
|
37361
|
+
"next-intl",
|
|
37362
|
+
"use-intl",
|
|
37363
|
+
"@formatjs/intl",
|
|
37364
|
+
"@lingui/core",
|
|
37365
|
+
"@lingui/react",
|
|
37366
|
+
"typesafe-i18n",
|
|
37367
|
+
"@tolgee/react",
|
|
37368
|
+
"react-intl-universal"
|
|
37369
|
+
];
|
|
37370
|
+
const hasI18nDependency = (packageJson) => {
|
|
37371
|
+
const allDependencies = {
|
|
37372
|
+
...packageJson.peerDependencies,
|
|
37373
|
+
...packageJson.dependencies,
|
|
37374
|
+
...packageJson.devDependencies,
|
|
37375
|
+
...packageJson.optionalDependencies
|
|
37376
|
+
};
|
|
37377
|
+
return I18N_PACKAGES.some((packageName) => allDependencies[packageName] !== void 0);
|
|
37378
|
+
};
|
|
37319
37379
|
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
37380
|
+
const MOBX_REACT_PACKAGE_NAME = "mobx-react";
|
|
37381
|
+
const MOBX_REACT_LITE_PACKAGE_NAME = "mobx-react-lite";
|
|
37382
|
+
const MOBX_STATE_TREE_PACKAGE_NAME = "mobx-state-tree";
|
|
37383
|
+
const MOBX_REACT_OBSERVER_PACKAGE_NAME = "mobx-react-observer";
|
|
37320
37384
|
const SHOPIFY_FLASH_LIST_PACKAGE_NAME = "@shopify/flash-list";
|
|
37321
37385
|
const resolveWorkspaceDependencyVersion = ({ concreteVersion, packageName, rootDirectory, rootPackageJson, sections, workspaceDirectory, workspacePackageJson }) => {
|
|
37322
37386
|
const dependencyDeclaration = getDependencyDeclaration({
|
|
@@ -37335,7 +37399,26 @@ const shouldReplaceReactVersion = (currentVersion, nextVersion) => {
|
|
|
37335
37399
|
if (nextMajor === null) return false;
|
|
37336
37400
|
return nextMajor < currentMajor;
|
|
37337
37401
|
};
|
|
37338
|
-
const
|
|
37402
|
+
const shouldReplaceSupportedDependencyFact = (currentFact, nextFact, latestSupportedMajor) => {
|
|
37403
|
+
if (currentFact.version === null) return true;
|
|
37404
|
+
if (nextFact.version === null) return false;
|
|
37405
|
+
const currentMajor = getLowestDependencyMajor(currentFact.version);
|
|
37406
|
+
const nextMajor = getLowestDependencyMajor(nextFact.version);
|
|
37407
|
+
if (currentMajor === null) return false;
|
|
37408
|
+
if (nextMajor === null) return true;
|
|
37409
|
+
if (currentMajor > latestSupportedMajor) return false;
|
|
37410
|
+
if (nextMajor > latestSupportedMajor) return true;
|
|
37411
|
+
return nextMajor < currentMajor;
|
|
37412
|
+
};
|
|
37413
|
+
const shouldReplaceStyledComponentsVersion = (currentVersion, nextVersion) => {
|
|
37414
|
+
if (!currentVersion) return true;
|
|
37415
|
+
const currentMajor = getLowestDependencyMajor(currentVersion);
|
|
37416
|
+
const nextMajor = getLowestDependencyMajor(nextVersion);
|
|
37417
|
+
if (currentMajor === null) return nextMajor !== null && nextMajor < 6;
|
|
37418
|
+
if (nextMajor === null) return currentMajor >= 6;
|
|
37419
|
+
return nextMajor < currentMajor;
|
|
37420
|
+
};
|
|
37421
|
+
const evaluateManifestFacts = (facts, packageJson, directory, rootDirectory, rootPackageJson) => {
|
|
37339
37422
|
if (facts.expo.version === null) {
|
|
37340
37423
|
const spec = getDependencySpec(packageJson, "expo");
|
|
37341
37424
|
if (spec !== null) facts.expo = {
|
|
@@ -37357,13 +37440,74 @@ const evaluateManifestFacts = (facts, packageJson, directory) => {
|
|
|
37357
37440
|
sourceDirectory: directory
|
|
37358
37441
|
};
|
|
37359
37442
|
}
|
|
37443
|
+
const mobxSpec = getDependencySpec(packageJson, "mobx");
|
|
37444
|
+
if (mobxSpec !== null) {
|
|
37445
|
+
const nextMobxFact = {
|
|
37446
|
+
version: resolveCatalogBackedDependencyVersion({
|
|
37447
|
+
rootDirectory: directory,
|
|
37448
|
+
rootPackageJson: packageJson,
|
|
37449
|
+
packageName: "mobx",
|
|
37450
|
+
version: mobxSpec
|
|
37451
|
+
}),
|
|
37452
|
+
sourceDirectory: directory
|
|
37453
|
+
};
|
|
37454
|
+
if (shouldReplaceSupportedDependencyFact(facts.mobx, nextMobxFact, 6)) facts.mobx = nextMobxFact;
|
|
37455
|
+
}
|
|
37456
|
+
facts.hasMobxReact = facts.hasMobxReact || getDependencySpec(packageJson, MOBX_REACT_PACKAGE_NAME) !== null;
|
|
37457
|
+
facts.hasMobxReactLite = facts.hasMobxReactLite || getDependencySpec(packageJson, MOBX_REACT_LITE_PACKAGE_NAME) !== null;
|
|
37458
|
+
facts.hasMobxStateTree = facts.hasMobxStateTree || getDependencySpec(packageJson, MOBX_STATE_TREE_PACKAGE_NAME) !== null;
|
|
37459
|
+
facts.hasMobxReactObserver = facts.hasMobxReactObserver || getDependencySpec(packageJson, MOBX_REACT_OBSERVER_PACKAGE_NAME) !== null;
|
|
37460
|
+
const zustandSpec = getDependencySpec(packageJson, "zustand");
|
|
37461
|
+
if (zustandSpec !== null) {
|
|
37462
|
+
const nextZustandFact = {
|
|
37463
|
+
version: resolveCatalogBackedDependencyVersion({
|
|
37464
|
+
rootDirectory: directory,
|
|
37465
|
+
rootPackageJson: packageJson,
|
|
37466
|
+
packageName: "zustand",
|
|
37467
|
+
version: zustandSpec
|
|
37468
|
+
}),
|
|
37469
|
+
sourceDirectory: directory
|
|
37470
|
+
};
|
|
37471
|
+
if (shouldReplaceSupportedDependencyFact(facts.zustand, nextZustandFact, 5)) facts.zustand = nextZustandFact;
|
|
37472
|
+
}
|
|
37360
37473
|
if (facts.reanimatedVersion === null) {
|
|
37361
37474
|
const spec = getDependencySpec(packageJson, REANIMATED_DEPENDENCY_NAME);
|
|
37362
37475
|
if (spec !== null) facts.reanimatedVersion = spec;
|
|
37363
37476
|
}
|
|
37477
|
+
if (facts.valtioVersion === null) {
|
|
37478
|
+
const spec = getDependencySpec(packageJson, "valtio");
|
|
37479
|
+
if (spec !== null) facts.valtioVersion = spec;
|
|
37480
|
+
}
|
|
37481
|
+
facts.tanstackQueryVersion ??= getTanStackQueryVersion(packageJson);
|
|
37482
|
+
const styledComponentsVersion = resolveCatalogBackedDependencyVersion({
|
|
37483
|
+
rootDirectory: directory,
|
|
37484
|
+
rootPackageJson: packageJson,
|
|
37485
|
+
packageName: "styled-components",
|
|
37486
|
+
version: getStyledComponentsVersion(packageJson)
|
|
37487
|
+
});
|
|
37488
|
+
if (styledComponentsVersion && shouldReplaceStyledComponentsVersion(facts.styledComponentsVersion, styledComponentsVersion)) facts.styledComponentsVersion = styledComponentsVersion;
|
|
37489
|
+
facts.hasI18nLibrary = facts.hasI18nLibrary || hasI18nDependency(packageJson);
|
|
37364
37490
|
facts.hasReactNativeAwarePackage = facts.hasReactNativeAwarePackage || isPackageJsonReactNativeAware(packageJson);
|
|
37365
37491
|
facts.hasReanimatedAwarePackage = facts.hasReanimatedAwarePackage || isPackageJsonReanimatedAware(packageJson);
|
|
37366
37492
|
facts.hasSsrDependency = facts.hasSsrDependency || isPackageJsonSsrAware(packageJson);
|
|
37493
|
+
const remotionSpec = getDependencySpec(packageJson, "remotion");
|
|
37494
|
+
if (remotionSpec !== null) {
|
|
37495
|
+
facts.hasRemotionDependency = true;
|
|
37496
|
+
const resolvedRemotionVersion = resolveCatalogBackedDependencyVersion({
|
|
37497
|
+
rootDirectory,
|
|
37498
|
+
rootPackageJson,
|
|
37499
|
+
packageName: "remotion",
|
|
37500
|
+
version: remotionSpec
|
|
37501
|
+
});
|
|
37502
|
+
const remotionMajorVersion = resolvedRemotionVersion === null ? null : getLowestDependencyMajor(resolvedRemotionVersion);
|
|
37503
|
+
if (remotionMajorVersion === null) {
|
|
37504
|
+
facts.hasUnknownRemotionVersion = true;
|
|
37505
|
+
facts.remotionVersion = null;
|
|
37506
|
+
} else if (!facts.hasUnknownRemotionVersion) {
|
|
37507
|
+
const currentRemotionMajorVersion = facts.remotionVersion === null ? null : getLowestDependencyMajor(facts.remotionVersion);
|
|
37508
|
+
if (currentRemotionMajorVersion === null || remotionMajorVersion < currentRemotionMajorVersion) facts.remotionVersion = resolvedRemotionVersion;
|
|
37509
|
+
}
|
|
37510
|
+
}
|
|
37367
37511
|
};
|
|
37368
37512
|
const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGroup }) => {
|
|
37369
37513
|
const facts = {
|
|
@@ -37383,12 +37527,31 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
|
|
|
37383
37527
|
version: null,
|
|
37384
37528
|
sourceDirectory: null
|
|
37385
37529
|
},
|
|
37530
|
+
valtioVersion: null,
|
|
37531
|
+
mobx: {
|
|
37532
|
+
version: null,
|
|
37533
|
+
sourceDirectory: null
|
|
37534
|
+
},
|
|
37535
|
+
hasMobxReact: false,
|
|
37536
|
+
hasMobxReactLite: false,
|
|
37537
|
+
hasMobxStateTree: false,
|
|
37538
|
+
hasMobxReactObserver: false,
|
|
37539
|
+
zustand: {
|
|
37540
|
+
version: null,
|
|
37541
|
+
sourceDirectory: null
|
|
37542
|
+
},
|
|
37543
|
+
tanstackQueryVersion: null,
|
|
37544
|
+
styledComponentsVersion: null,
|
|
37545
|
+
hasI18nLibrary: false,
|
|
37386
37546
|
hasReactNativeAwarePackage: false,
|
|
37387
37547
|
hasReanimatedAwarePackage: false,
|
|
37388
37548
|
hasSsrDependency: false,
|
|
37549
|
+
hasRemotionDependency: false,
|
|
37550
|
+
hasUnknownRemotionVersion: false,
|
|
37551
|
+
remotionVersion: null,
|
|
37389
37552
|
reanimatedVersion: null
|
|
37390
37553
|
};
|
|
37391
|
-
evaluateManifestFacts(facts, rootPackageJson, rootDirectory);
|
|
37554
|
+
evaluateManifestFacts(facts, rootPackageJson, rootDirectory, rootDirectory, rootPackageJson);
|
|
37392
37555
|
let isReactGroupSettled = !collectReactGroup;
|
|
37393
37556
|
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
37394
37557
|
for (const pattern of getWorkspacePatterns(rootDirectory, rootPackageJson)) {
|
|
@@ -37397,7 +37560,7 @@ const collectWorkspaceFacts = (rootDirectory, rootPackageJson, { collectReactGro
|
|
|
37397
37560
|
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
37398
37561
|
visitedDirectories.add(workspaceDirectory);
|
|
37399
37562
|
const workspacePackageJson = readPackageJson$1(Path.join(workspaceDirectory, "package.json"));
|
|
37400
|
-
evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory);
|
|
37563
|
+
evaluateManifestFacts(facts, workspacePackageJson, workspaceDirectory, rootDirectory, rootPackageJson);
|
|
37401
37564
|
const info = extractDependencyInfo(workspacePackageJson);
|
|
37402
37565
|
if (info.framework !== "unknown" && frameworkMergeRank(info.framework) < frameworkMergeRank(facts.framework)) facts.framework = info.framework;
|
|
37403
37566
|
if (isReactGroupSettled) continue;
|
|
@@ -37618,11 +37781,27 @@ const discoverProjectWithoutPackageJson = (directory) => {
|
|
|
37618
37781
|
tailwindVersion: null,
|
|
37619
37782
|
zodVersion: null,
|
|
37620
37783
|
zodMajorVersion: null,
|
|
37784
|
+
mobxVersion: null,
|
|
37785
|
+
mobxMajorVersion: null,
|
|
37786
|
+
hasMobxReact: false,
|
|
37787
|
+
hasMobxReactLite: false,
|
|
37788
|
+
hasMobxStateTree: false,
|
|
37789
|
+
hasMobxReactObserver: false,
|
|
37790
|
+
zustandVersion: null,
|
|
37791
|
+
zustandMajorVersion: null,
|
|
37621
37792
|
framework: "unknown",
|
|
37622
37793
|
hasTypeScript: hasOwnTsConfig,
|
|
37623
37794
|
hasReactCompiler: false,
|
|
37624
37795
|
hasReactCompilerLintPlugin: false,
|
|
37625
37796
|
hasTanStackQuery: false,
|
|
37797
|
+
valtioVersion: null,
|
|
37798
|
+
valtioMajorVersion: null,
|
|
37799
|
+
hasRemotion: false,
|
|
37800
|
+
remotionVersion: null,
|
|
37801
|
+
remotionMajorVersion: null,
|
|
37802
|
+
hasI18nLibrary: false,
|
|
37803
|
+
tanstackQueryVersion: null,
|
|
37804
|
+
styledComponentsVersion: null,
|
|
37626
37805
|
hasSsrDependency: false,
|
|
37627
37806
|
preactVersion: null,
|
|
37628
37807
|
preactMajorVersion: null,
|
|
@@ -37721,6 +37900,12 @@ const discoverProject = (directory) => {
|
|
|
37721
37900
|
packageName: SHOPIFY_FLASH_LIST_PACKAGE_NAME,
|
|
37722
37901
|
version: workspaceFacts.shopifyFlashList.version
|
|
37723
37902
|
}) : null;
|
|
37903
|
+
const zustandVersion = resolveCatalogBackedDependencyVersion({
|
|
37904
|
+
rootDirectory: directory,
|
|
37905
|
+
rootPackageJson: packageJson,
|
|
37906
|
+
packageName: "zustand",
|
|
37907
|
+
version: workspaceFacts.zustand.version
|
|
37908
|
+
});
|
|
37724
37909
|
const hasReanimated = hasReactNativeWorkspace && workspaceFacts.hasReanimatedAwarePackage;
|
|
37725
37910
|
const reanimatedVersion = hasReanimated ? workspaceFacts.reanimatedVersion : null;
|
|
37726
37911
|
const nextjsVersion = framework === "nextjs" ? resolveCatalogBackedDependencyVersion({
|
|
@@ -37729,7 +37914,21 @@ const discoverProject = (directory) => {
|
|
|
37729
37914
|
packageName: "next",
|
|
37730
37915
|
version: workspaceFacts.next.version
|
|
37731
37916
|
}) : null;
|
|
37917
|
+
const valtioVersion = resolveCatalogBackedDependencyVersion({
|
|
37918
|
+
rootDirectory: directory,
|
|
37919
|
+
rootPackageJson: packageJson,
|
|
37920
|
+
packageName: "valtio",
|
|
37921
|
+
version: workspaceFacts.valtioVersion
|
|
37922
|
+
});
|
|
37923
|
+
const mobxVersion = resolveCatalogBackedDependencyVersion({
|
|
37924
|
+
rootDirectory: directory,
|
|
37925
|
+
rootPackageJson: packageJson,
|
|
37926
|
+
packageName: "mobx",
|
|
37927
|
+
version: workspaceFacts.mobx.version
|
|
37928
|
+
});
|
|
37732
37929
|
const preactVersion = getPreactVersion(packageJson);
|
|
37930
|
+
const remotionVersion = workspaceFacts.remotionVersion;
|
|
37931
|
+
const tanstackQueryVersion = getTanStackQueryVersion(packageJson) ?? workspaceFacts.tanstackQueryVersion;
|
|
37733
37932
|
const isPreES2023Target = hasTypeScript && detectPreES2023Target(directory);
|
|
37734
37933
|
const projectInfo = {
|
|
37735
37934
|
rootDirectory: directory,
|
|
@@ -37739,11 +37938,27 @@ const discoverProject = (directory) => {
|
|
|
37739
37938
|
tailwindVersion,
|
|
37740
37939
|
zodVersion,
|
|
37741
37940
|
zodMajorVersion: zodVersion === null ? null : getLowestDependencyMajor(zodVersion),
|
|
37941
|
+
mobxVersion,
|
|
37942
|
+
mobxMajorVersion: mobxVersion === null ? null : getLowestDependencyMajor(mobxVersion),
|
|
37943
|
+
hasMobxReact: workspaceFacts.hasMobxReact,
|
|
37944
|
+
hasMobxReactLite: workspaceFacts.hasMobxReactLite,
|
|
37945
|
+
hasMobxStateTree: workspaceFacts.hasMobxStateTree,
|
|
37946
|
+
hasMobxReactObserver: workspaceFacts.hasMobxReactObserver,
|
|
37947
|
+
zustandVersion,
|
|
37948
|
+
zustandMajorVersion: zustandVersion === null ? null : getLowestDependencyMajor(zustandVersion),
|
|
37742
37949
|
framework,
|
|
37743
37950
|
hasTypeScript,
|
|
37744
37951
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
37745
37952
|
hasReactCompilerLintPlugin: detectReactCompilerLintPlugin(directory, packageJson),
|
|
37746
|
-
hasTanStackQuery:
|
|
37953
|
+
hasTanStackQuery: tanstackQueryVersion !== null,
|
|
37954
|
+
hasI18nLibrary: workspaceFacts.hasI18nLibrary,
|
|
37955
|
+
tanstackQueryVersion,
|
|
37956
|
+
styledComponentsVersion: workspaceFacts.styledComponentsVersion,
|
|
37957
|
+
valtioVersion,
|
|
37958
|
+
valtioMajorVersion: valtioVersion === null ? null : getLowestDependencyMajor(valtioVersion),
|
|
37959
|
+
hasRemotion: workspaceFacts.hasRemotionDependency,
|
|
37960
|
+
remotionVersion,
|
|
37961
|
+
remotionMajorVersion: remotionVersion === null ? null : getLowestDependencyMajor(remotionVersion),
|
|
37747
37962
|
hasSsrDependency: workspaceFacts.hasSsrDependency,
|
|
37748
37963
|
preactVersion,
|
|
37749
37964
|
preactMajorVersion: parseReactMajor(preactVersion),
|
|
@@ -37977,7 +38192,6 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY = {
|
|
|
37977
38192
|
"react/jsx-no-new-function-as-prop": "react-doctor/jsx-no-new-function-as-prop",
|
|
37978
38193
|
"react/jsx-no-new-object-as-prop": "react-doctor/jsx-no-new-object-as-prop",
|
|
37979
38194
|
"react/jsx-no-script-url": "react-doctor/jsx-no-script-url",
|
|
37980
|
-
"react/jsx-no-target-blank": "react-doctor/jsx-no-target-blank",
|
|
37981
38195
|
"react/jsx-no-undef": "react-doctor/jsx-no-undef",
|
|
37982
38196
|
"react/jsx-no-useless-fragment": "react-doctor/jsx-no-useless-fragment",
|
|
37983
38197
|
"react/jsx-pascal-case": "react-doctor/jsx-pascal-case",
|
|
@@ -40299,9 +40513,25 @@ const BABEL_CONFIG_FILE_NAMES = [
|
|
|
40299
40513
|
".babelrc.json"
|
|
40300
40514
|
];
|
|
40301
40515
|
const LEGACY_PRESET_SPEC = "module:metro-react-native-babel-preset";
|
|
40516
|
+
const LEGACY_PRESET_PACKAGE = "metro-react-native-babel-preset";
|
|
40517
|
+
const REACT_NATIVE_PACKAGE = "react-native";
|
|
40518
|
+
const PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION = "0.73.0";
|
|
40519
|
+
const LEGACY_PRESET_REFERENCE = new RegExp(`['"]${LEGACY_PRESET_SPEC}['"]`);
|
|
40302
40520
|
const MODERN_PRESET_REFERENCE = new RegExp(`['"]module:@react-native/babel-preset['"]`);
|
|
40303
40521
|
const ENABLE_BABEL_RUNTIME_VERSION = /enableBabelRuntime["']?\s*:\s*['"]/;
|
|
40522
|
+
const isReactNativeVersionAtLeastPresetRename = (rootDirectory, packageJson) => {
|
|
40523
|
+
const installedReactNativeManifestPath = Path.join(rootDirectory, "node_modules", REACT_NATIVE_PACKAGE, "package.json");
|
|
40524
|
+
const reactNativeVersionSpec = (isFile(installedReactNativeManifestPath) ? readPackageJson$1(installedReactNativeManifestPath).version : void 0) ?? getDependencySpec(packageJson, REACT_NATIVE_PACKAGE);
|
|
40525
|
+
if (reactNativeVersionSpec === null || reactNativeVersionSpec === void 0) return false;
|
|
40526
|
+
const normalizedVersionRange = import_semver.validRange(reactNativeVersionSpec);
|
|
40527
|
+
if (normalizedVersionRange === null) return false;
|
|
40528
|
+
const minimumVersion = import_semver.minVersion(normalizedVersionRange);
|
|
40529
|
+
return minimumVersion !== null && import_semver.gte(minimumVersion, PRESET_RENAME_MINIMUM_REACT_NATIVE_VERSION);
|
|
40530
|
+
};
|
|
40531
|
+
const canResolveLegacyPreset = (rootDirectory, packageJson) => getDependencySpec(packageJson, LEGACY_PRESET_PACKAGE) !== null || isFile(Path.join(rootDirectory, "node_modules", LEGACY_PRESET_PACKAGE, "package.json"));
|
|
40304
40532
|
const checkReactNativeMetroBabelPreset = (rootDirectory) => {
|
|
40533
|
+
const packageJson = readPackageJson$1(Path.join(rootDirectory, "package.json"));
|
|
40534
|
+
const shouldReportLegacyPreset = isReactNativeVersionAtLeastPresetRename(rootDirectory, packageJson) && !canResolveLegacyPreset(rootDirectory, packageJson);
|
|
40305
40535
|
for (const fileName of BABEL_CONFIG_FILE_NAMES) {
|
|
40306
40536
|
const filePath = Path.join(rootDirectory, fileName);
|
|
40307
40537
|
if (!isFile(filePath)) continue;
|
|
@@ -40311,7 +40541,7 @@ const checkReactNativeMetroBabelPreset = (rootDirectory) => {
|
|
|
40311
40541
|
} catch {
|
|
40312
40542
|
continue;
|
|
40313
40543
|
}
|
|
40314
|
-
if (
|
|
40544
|
+
if (shouldReportLegacyPreset && LEGACY_PRESET_REFERENCE.test(contents)) return [buildReactNativeDiagnostic({
|
|
40315
40545
|
filePath: fileName,
|
|
40316
40546
|
rule: "rn-no-metro-babel-preset",
|
|
40317
40547
|
severity: "error",
|
|
@@ -40334,12 +40564,6 @@ const checkReactNativeProject = (rootDirectory, project) => {
|
|
|
40334
40564
|
return [...checkReactNativeMetroBabelPreset(rootDirectory), ...checkReactNativeLibraryDependencies(rootDirectory)];
|
|
40335
40565
|
};
|
|
40336
40566
|
const RULE_KEY = "no-vulnerable-react-server-components";
|
|
40337
|
-
const DEPENDENCY_SECTIONS = [
|
|
40338
|
-
"dependencies",
|
|
40339
|
-
"devDependencies",
|
|
40340
|
-
"peerDependencies",
|
|
40341
|
-
"optionalDependencies"
|
|
40342
|
-
];
|
|
40343
40567
|
const REACT_RSC_ADVISORIES_BY_MINOR = {
|
|
40344
40568
|
"19.0": {
|
|
40345
40569
|
rceFixedVersion: "19.0.1",
|
|
@@ -40386,20 +40610,13 @@ const enumerateWorkspaceDirectories = (workspaceRoot) => {
|
|
|
40386
40610
|
for (const pattern of patterns) for (const directory of resolveWorkspaceDirectories(workspaceRoot, pattern)) directories.add(directory);
|
|
40387
40611
|
return [...directories];
|
|
40388
40612
|
};
|
|
40389
|
-
const readDeclaredSpec = (packageJson, packageName) => {
|
|
40390
|
-
for (const section of DEPENDENCY_SECTIONS) {
|
|
40391
|
-
const spec = packageJson[section]?.[packageName];
|
|
40392
|
-
if (typeof spec === "string") return spec;
|
|
40393
|
-
}
|
|
40394
|
-
return null;
|
|
40395
|
-
};
|
|
40396
40613
|
const resolveVersionInDirectory = (directory, packageName, declaredSpecOverride) => {
|
|
40397
40614
|
const manifestPath = Path.join(directory, "node_modules", packageName, "package.json");
|
|
40398
40615
|
if (isFile(manifestPath)) {
|
|
40399
40616
|
const installedVersion = import_semver.valid(readPackageJson$1(manifestPath).version ?? null);
|
|
40400
40617
|
if (installedVersion !== null) return installedVersion;
|
|
40401
40618
|
}
|
|
40402
|
-
const candidateSpecs = [
|
|
40619
|
+
const candidateSpecs = [getDependencySpec(readPackageJson$1(Path.join(directory, "package.json")), packageName), declaredSpecOverride];
|
|
40403
40620
|
for (const spec of candidateSpecs) {
|
|
40404
40621
|
const pinnedVersion = spec === null ? null : import_semver.valid(spec);
|
|
40405
40622
|
if (pinnedVersion !== null) return pinnedVersion;
|
|
@@ -41067,21 +41284,50 @@ const buildCapabilities = (project) => {
|
|
|
41067
41284
|
if (CLIENT_ONLY_FRAMEWORKS.has(project.framework)) capabilities.add("client-only");
|
|
41068
41285
|
if (project.isStaticExport) capabilities.add("nextjs:static-export");
|
|
41069
41286
|
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 15) capabilities.add("nextjs:15");
|
|
41287
|
+
if (project.nextjsMajorVersion !== null && project.nextjsMajorVersion >= 16) capabilities.add("nextjs:16");
|
|
41070
41288
|
addMajorLadder(capabilities, "react", project.reactMajorVersion, 17, 30);
|
|
41071
41289
|
if (project.reactMajorVersion !== null && project.reactMajorVersion >= 19 && isMajorMinorAtLeast(parseReactMajorMinor(project.reactVersion), {
|
|
41072
41290
|
major: 19,
|
|
41073
41291
|
minor: 2
|
|
41074
41292
|
})) capabilities.add("react:19.2");
|
|
41075
|
-
if (project.tailwindVersion !== null)
|
|
41076
|
-
|
|
41077
|
-
|
|
41078
|
-
|
|
41079
|
-
|
|
41293
|
+
if (project.tailwindVersion !== null) {
|
|
41294
|
+
capabilities.add("tailwind");
|
|
41295
|
+
const tailwindVersion = parseTailwindMajorMinor(project.tailwindVersion);
|
|
41296
|
+
if (isMajorMinorAtLeast(tailwindVersion, {
|
|
41297
|
+
major: 3,
|
|
41298
|
+
minor: 4
|
|
41299
|
+
})) capabilities.add("tailwind:3.4");
|
|
41300
|
+
if (tailwindVersion !== null && isMajorMinorAtLeast(tailwindVersion, {
|
|
41301
|
+
major: 4,
|
|
41302
|
+
minor: 0
|
|
41303
|
+
})) capabilities.add("tailwind:4");
|
|
41304
|
+
}
|
|
41080
41305
|
if (project.zodVersion !== null) capabilities.add("zod");
|
|
41081
41306
|
if (project.zodMajorVersion !== null && project.zodMajorVersion >= 4) capabilities.add("zod:4");
|
|
41307
|
+
if (project.mobxVersion !== void 0 && project.mobxVersion !== null || project.hasMobxReact === true || project.hasMobxReactLite === true || project.hasMobxStateTree === true || project.hasMobxReactObserver === true) capabilities.add("mobx");
|
|
41308
|
+
if (project.hasMobxReact === true) capabilities.add("mobx-react");
|
|
41309
|
+
if (project.hasMobxReactLite === true) capabilities.add("mobx-react-lite");
|
|
41310
|
+
if (project.hasMobxReact === true || project.hasMobxReactLite === true) capabilities.add("mobx-react-binding");
|
|
41311
|
+
if (project.hasMobxStateTree === true) capabilities.add("mobx-state-tree");
|
|
41312
|
+
if (project.hasMobxReactObserver === true) capabilities.add("mobx-react-observer");
|
|
41313
|
+
if (project.mobxMajorVersion !== void 0 && project.mobxMajorVersion !== null && project.mobxMajorVersion >= 4 && project.mobxMajorVersion <= 6) addMajorLadder(capabilities, "mobx", project.mobxMajorVersion, 4, 6);
|
|
41314
|
+
if (project.zustandVersion !== void 0 && project.zustandVersion !== null) capabilities.add("zustand");
|
|
41315
|
+
if (project.zustandMajorVersion !== void 0 && project.zustandMajorVersion !== null && project.zustandMajorVersion >= 1 && project.zustandMajorVersion <= 5) addMajorLadder(capabilities, "zustand", project.zustandMajorVersion, 1, 5);
|
|
41082
41316
|
if (project.isPreES2023Target) capabilities.add("pre-es2023");
|
|
41083
41317
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
41084
|
-
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
41318
|
+
if (Boolean(project.hasTanStackQuery) || Boolean(project.tanstackQueryVersion)) capabilities.add("tanstack-query");
|
|
41319
|
+
if (project.styledComponentsVersion) {
|
|
41320
|
+
capabilities.add("styled-components");
|
|
41321
|
+
const styledComponentsMajor = getLowestDependencyMajor(project.styledComponentsVersion);
|
|
41322
|
+
if (styledComponentsMajor !== null && styledComponentsMajor >= 6) capabilities.add("styled-components:6");
|
|
41323
|
+
}
|
|
41324
|
+
if (project.hasI18nLibrary) capabilities.add("i18n");
|
|
41325
|
+
if (project.valtioVersion !== null) capabilities.add("valtio");
|
|
41326
|
+
addMajorLadder(capabilities, "valtio", project.valtioMajorVersion, 1, 10);
|
|
41327
|
+
if (project.hasRemotion) {
|
|
41328
|
+
capabilities.add("remotion");
|
|
41329
|
+
addMajorLadder(capabilities, "remotion", project.remotionMajorVersion ?? null, 4, 30);
|
|
41330
|
+
}
|
|
41085
41331
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
41086
41332
|
if (project.preactVersion !== null) capabilities.add("preact");
|
|
41087
41333
|
addMajorLadder(capabilities, "preact", project.preactMajorVersion, 10, 20);
|
|
@@ -41096,7 +41342,7 @@ const getCapabilities = (project) => {
|
|
|
41096
41342
|
capabilitiesByProject.set(project, capabilities);
|
|
41097
41343
|
return capabilities;
|
|
41098
41344
|
};
|
|
41099
|
-
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhen) => {
|
|
41345
|
+
const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhen, includedTags) => {
|
|
41100
41346
|
if (requires) {
|
|
41101
41347
|
for (const capability of requires) if (!capabilities.has(capability)) return false;
|
|
41102
41348
|
}
|
|
@@ -41107,20 +41353,29 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledWhe
|
|
|
41107
41353
|
if (tags) {
|
|
41108
41354
|
for (const tag of tags) if (ignoredTags.has(tag)) return false;
|
|
41109
41355
|
}
|
|
41356
|
+
if (includedTags && includedTags.size > 0) {
|
|
41357
|
+
if (!tags?.some((tag) => includedTags.has(tag))) return false;
|
|
41358
|
+
}
|
|
41110
41359
|
return true;
|
|
41111
41360
|
};
|
|
41361
|
+
const shouldEnableRuleByDefaultStatus = (options) => options.defaultEnabled !== false || options.hasExplicitOverride === true || options.includeTagDefaults && options.hasIncludedTags;
|
|
41112
41362
|
const yieldToEventLoop = () => new Promise((resolve) => {
|
|
41113
41363
|
setImmediate(resolve);
|
|
41114
41364
|
});
|
|
41115
41365
|
const createSecurityScanSession = (rootDirectory, options) => {
|
|
41116
41366
|
const capabilities = options.project ? getCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
41117
41367
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
41368
|
+
const includedTags = options.includedTags ?? /* @__PURE__ */ new Set();
|
|
41118
41369
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
41119
41370
|
const rule = entry.rule;
|
|
41120
41371
|
const scan = rule.scan;
|
|
41121
41372
|
if (typeof scan !== "function") return [];
|
|
41122
|
-
if (
|
|
41123
|
-
|
|
41373
|
+
if (!shouldEnableRuleByDefaultStatus({
|
|
41374
|
+
defaultEnabled: rule.defaultEnabled,
|
|
41375
|
+
includeTagDefaults: options.includeTagDefaults === true,
|
|
41376
|
+
hasIncludedTags: includedTags.size > 0
|
|
41377
|
+
})) return [];
|
|
41378
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen, includedTags)) return [];
|
|
41124
41379
|
return [{
|
|
41125
41380
|
entry,
|
|
41126
41381
|
scan,
|
|
@@ -42334,6 +42589,35 @@ const parseGithubViewerPermission = (stdout) => {
|
|
|
42334
42589
|
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
42335
42590
|
};
|
|
42336
42591
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
42592
|
+
const parseBaselineDiffPlan = (value) => {
|
|
42593
|
+
const entries = splitNullSeparated(value);
|
|
42594
|
+
const baseFiles = /* @__PURE__ */ new Set();
|
|
42595
|
+
const headFiles = /* @__PURE__ */ new Set();
|
|
42596
|
+
for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 2) {
|
|
42597
|
+
const status = entries[entryIndex];
|
|
42598
|
+
const filePath = entries[entryIndex + 1];
|
|
42599
|
+
if (status === void 0 || filePath === void 0 || status.length !== 1) return null;
|
|
42600
|
+
if (status === "A") {
|
|
42601
|
+
headFiles.add(filePath);
|
|
42602
|
+
continue;
|
|
42603
|
+
}
|
|
42604
|
+
if (status === "D") {
|
|
42605
|
+
baseFiles.add(filePath);
|
|
42606
|
+
continue;
|
|
42607
|
+
}
|
|
42608
|
+
if (status === "M" || status === "T") {
|
|
42609
|
+
baseFiles.add(filePath);
|
|
42610
|
+
headFiles.add(filePath);
|
|
42611
|
+
continue;
|
|
42612
|
+
}
|
|
42613
|
+
return null;
|
|
42614
|
+
}
|
|
42615
|
+
return {
|
|
42616
|
+
baseFiles: [...baseFiles],
|
|
42617
|
+
headFiles: [...headFiles],
|
|
42618
|
+
untrackedFiles: []
|
|
42619
|
+
};
|
|
42620
|
+
};
|
|
42337
42621
|
const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
|
|
42338
42622
|
/**
|
|
42339
42623
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
@@ -42536,6 +42820,45 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42536
42820
|
githubViewerPermission,
|
|
42537
42821
|
branchExists,
|
|
42538
42822
|
mergeBase,
|
|
42823
|
+
baselineDiffPlan: (input) => {
|
|
42824
|
+
if (!isSafeGitRevision(input.ref)) return succeed$2(null);
|
|
42825
|
+
return gen(function* () {
|
|
42826
|
+
const unmerged = yield* runGit(input.directory, [
|
|
42827
|
+
"diff",
|
|
42828
|
+
"--no-ext-diff",
|
|
42829
|
+
"-z",
|
|
42830
|
+
"--name-only",
|
|
42831
|
+
"--diff-filter=U",
|
|
42832
|
+
"--relative"
|
|
42833
|
+
]);
|
|
42834
|
+
if (unmerged.status !== 0 || unmerged.stdout.length > 0) return null;
|
|
42835
|
+
const result = yield* runGit(input.directory, [
|
|
42836
|
+
"diff",
|
|
42837
|
+
"--no-ext-diff",
|
|
42838
|
+
"--no-textconv",
|
|
42839
|
+
"--no-renames",
|
|
42840
|
+
"-z",
|
|
42841
|
+
"--name-status",
|
|
42842
|
+
"--relative",
|
|
42843
|
+
input.ref
|
|
42844
|
+
]);
|
|
42845
|
+
if (result.status !== 0) return null;
|
|
42846
|
+
const plan = parseBaselineDiffPlan(result.stdout);
|
|
42847
|
+
if (plan === null) return null;
|
|
42848
|
+
const untracked = yield* runGit(input.directory, [
|
|
42849
|
+
"ls-files",
|
|
42850
|
+
"--others",
|
|
42851
|
+
"--exclude-standard",
|
|
42852
|
+
"-z"
|
|
42853
|
+
]);
|
|
42854
|
+
if (untracked.status !== 0) return null;
|
|
42855
|
+
return {
|
|
42856
|
+
baseFiles: plan.baseFiles,
|
|
42857
|
+
headFiles: plan.headFiles,
|
|
42858
|
+
untrackedFiles: splitNullSeparated(untracked.stdout)
|
|
42859
|
+
};
|
|
42860
|
+
}).pipe(catch_$1(() => succeed$2(null)), withSpan("Git.baselineDiffPlan"));
|
|
42861
|
+
},
|
|
42539
42862
|
diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
|
|
42540
42863
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
42541
42864
|
if (explicitBaseBranch !== void 0) {
|
|
@@ -42685,6 +43008,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42685
43008
|
githubViewerPermission: () => succeed$2(snapshot.githubViewerPermission ?? null),
|
|
42686
43009
|
branchExists: (_directory, branch) => succeed$2(snapshot.branchExists?.get(branch) ?? false),
|
|
42687
43010
|
mergeBase: ({ ref }) => succeed$2(snapshot.mergeBase?.get(ref) ?? null),
|
|
43011
|
+
baselineDiffPlan: () => succeed$2(snapshot.baselineDiffPlan ?? null),
|
|
42688
43012
|
diffSelection: () => succeed$2(snapshot.diffSelection ?? null),
|
|
42689
43013
|
stagedFilePaths: () => succeed$2(snapshot.stagedFiles ?? []),
|
|
42690
43014
|
showStagedContent: (_directory, relativePath) => succeed$2(snapshot.stagedContent?.get(relativePath) ?? null),
|
|
@@ -43053,8 +43377,9 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
43053
43377
|
}
|
|
43054
43378
|
return enabled;
|
|
43055
43379
|
};
|
|
43056
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
|
|
43057
|
-
const
|
|
43380
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), includedTags = /* @__PURE__ */ new Set(), includeTagDefaults = false, serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
|
|
43381
|
+
const hasIncludedTags = includedTags.size > 0;
|
|
43382
|
+
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" || hasIncludedTags ? null : resolveReactHooksJsPlugin(project.hasReactCompiler || project.hasReactCompilerLintPlugin === true, customRulesOnly);
|
|
43058
43383
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
43059
43384
|
const jsPlugins = [];
|
|
43060
43385
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
@@ -43069,9 +43394,14 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
43069
43394
|
if (rule.scan !== void 0) continue;
|
|
43070
43395
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
43071
43396
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
43072
|
-
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen)) continue;
|
|
43397
|
+
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledWhen, includedTags)) continue;
|
|
43073
43398
|
const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
|
|
43074
|
-
if (
|
|
43399
|
+
if (!shouldEnableRuleByDefaultStatus({
|
|
43400
|
+
defaultEnabled: rule.defaultEnabled,
|
|
43401
|
+
includeTagDefaults,
|
|
43402
|
+
hasIncludedTags,
|
|
43403
|
+
hasExplicitOverride: explicitRuleOverride !== void 0
|
|
43404
|
+
})) continue;
|
|
43075
43405
|
const severity = resolveRuleSeverityOverride({
|
|
43076
43406
|
ruleKey: registryEntry.key,
|
|
43077
43407
|
category: rule.category
|
|
@@ -43080,7 +43410,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
43080
43410
|
enabledReactDoctorRules[registryEntry.key] = severity;
|
|
43081
43411
|
}
|
|
43082
43412
|
const userPluginRules = {};
|
|
43083
|
-
if (ruleSelection !== "sidecar") for (const userPlugin of userPlugins) {
|
|
43413
|
+
if (ruleSelection !== "sidecar" && !hasIncludedTags) for (const userPlugin of userPlugins) {
|
|
43084
43414
|
Object.assign(userPluginRules, buildUserPluginRules(userPlugin, severityControls));
|
|
43085
43415
|
jsPlugins.push(userPlugin.entry);
|
|
43086
43416
|
}
|
|
@@ -43297,6 +43627,19 @@ const createSidecarLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
43297
43627
|
}
|
|
43298
43628
|
};
|
|
43299
43629
|
};
|
|
43630
|
+
const fingerprintCacheByFilePath = /* @__PURE__ */ new Map();
|
|
43631
|
+
const fingerprintFileContents = (filePath, fingerprintLength) => {
|
|
43632
|
+
const fileStats = NFS.statSync(filePath, { bigint: true });
|
|
43633
|
+
const cachedFingerprint = fingerprintCacheByFilePath.get(filePath);
|
|
43634
|
+
if (cachedFingerprint?.modifiedTimeNanoseconds === fileStats.mtimeNs && cachedFingerprint.sizeBytes === fileStats.size) return cachedFingerprint.fingerprint;
|
|
43635
|
+
const fingerprint = crypto.createHash("sha256").update(NFS.readFileSync(filePath)).digest("hex").slice(0, fingerprintLength);
|
|
43636
|
+
fingerprintCacheByFilePath.set(filePath, {
|
|
43637
|
+
fingerprint,
|
|
43638
|
+
modifiedTimeNanoseconds: fileStats.mtimeNs,
|
|
43639
|
+
sizeBytes: fileStats.size
|
|
43640
|
+
});
|
|
43641
|
+
return fingerprint;
|
|
43642
|
+
};
|
|
43300
43643
|
const bundledRequire$3 = createRequire(import.meta.url);
|
|
43301
43644
|
const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
|
|
43302
43645
|
const resolveChildNodeVersion = (nodeBinaryPath) => {
|
|
@@ -43320,6 +43663,13 @@ const TOOLCHAIN_PACKAGE_SPECIFIERS$1 = [
|
|
|
43320
43663
|
"oxlint-plugin-react-doctor/package.json",
|
|
43321
43664
|
"eslint-plugin-react-hooks/package.json"
|
|
43322
43665
|
];
|
|
43666
|
+
const resolvePluginFingerprint = () => {
|
|
43667
|
+
try {
|
|
43668
|
+
return fingerprintFileContents(bundledRequire$3.resolve("oxlint-plugin-react-doctor"), 16);
|
|
43669
|
+
} catch {
|
|
43670
|
+
return "unresolved";
|
|
43671
|
+
}
|
|
43672
|
+
};
|
|
43323
43673
|
const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
|
|
43324
43674
|
const versions = [`node=${resolveChildNodeVersion(nodeBinaryPath)}`];
|
|
43325
43675
|
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS$1) try {
|
|
@@ -43329,6 +43679,7 @@ const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
|
|
|
43329
43679
|
} catch {
|
|
43330
43680
|
versions.push(`${specifier}=missing`);
|
|
43331
43681
|
}
|
|
43682
|
+
versions.push(`oxlint-plugin-react-doctor#fingerprint=${resolvePluginFingerprint()}`);
|
|
43332
43683
|
return versions;
|
|
43333
43684
|
};
|
|
43334
43685
|
const esmRequire = createRequire(import.meta.url);
|
|
@@ -44022,12 +44373,17 @@ const REACT_ERROR_BOUNDARY_MESSAGE = "This try/catch cannot catch errors thrown
|
|
|
44022
44373
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
44023
44374
|
const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
|
|
44024
44375
|
const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
|
|
44376
|
+
const SET_STATE_IN_EFFECT_ACTION = "Prefer deriving or initializing the value before render. If the effect must read a browser API after mount, treat this as advisory or suppress it with `// react-doctor-disable-next-line react-hooks-js/set-state-in-effect`.";
|
|
44025
44377
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
44026
44378
|
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
44027
44379
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
44028
44380
|
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
44029
44381
|
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
44030
44382
|
};
|
|
44383
|
+
const buildSetStateInEffectMessage = (reasonSummary) => {
|
|
44384
|
+
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
44385
|
+
return `This synchronous effect update causes an extra render${normalizedSummary ? `: ${normalizedSummary}` : ""}. ${SET_STATE_IN_EFFECT_ACTION}`;
|
|
44386
|
+
};
|
|
44031
44387
|
const PLUGIN_CATEGORY_MAP = {
|
|
44032
44388
|
react: "Bugs",
|
|
44033
44389
|
"react-hooks": "Bugs",
|
|
@@ -44085,6 +44441,10 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
44085
44441
|
message: REACT_ERROR_BOUNDARY_MESSAGE,
|
|
44086
44442
|
help: reasonDetail || help
|
|
44087
44443
|
};
|
|
44444
|
+
if (rule === "set-state-in-effect") return {
|
|
44445
|
+
message: buildSetStateInEffectMessage(reasonSummary.trim()),
|
|
44446
|
+
help: reasonDetail || help
|
|
44447
|
+
};
|
|
44088
44448
|
return {
|
|
44089
44449
|
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
44090
44450
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
@@ -44795,7 +45155,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
44795
45155
|
* 6. always restore disable directives + clean up the temp dir
|
|
44796
45156
|
*/
|
|
44797
45157
|
const runOxlint = async (options) => {
|
|
44798
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, onFileCoverage, perFileLintCacheEnabled = false, sidecarLintCacheEnabled = false, onCacheStats, onSidecarStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
45158
|
+
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), includedTags = /* @__PURE__ */ new Set(), includeTagDefaults = false, userConfig, configSourceDirectory = rootDirectory, onPartialFailure, onFileCoverage, perFileLintCacheEnabled = false, sidecarLintCacheEnabled = false, onCacheStats, onSidecarStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
44799
45159
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
44800
45160
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
44801
45161
|
validateRuleRegistration();
|
|
@@ -44809,13 +45169,15 @@ const runOxlint = async (options) => {
|
|
|
44809
45169
|
}
|
|
44810
45170
|
const pluginPath = resolvePluginPath();
|
|
44811
45171
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
44812
|
-
const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
45172
|
+
const userPlugins = includedTags.size > 0 ? [] : resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
44813
45173
|
const buildConfig = (overrides) => createOxlintConfig({
|
|
44814
45174
|
pluginPath,
|
|
44815
45175
|
project,
|
|
44816
45176
|
customRulesOnly,
|
|
44817
45177
|
extendsPaths: overrides.extendsPaths,
|
|
44818
45178
|
ignoredTags,
|
|
45179
|
+
includedTags,
|
|
45180
|
+
includeTagDefaults,
|
|
44819
45181
|
serverAuthFunctionNames,
|
|
44820
45182
|
severityControls,
|
|
44821
45183
|
userPlugins,
|
|
@@ -45204,6 +45566,8 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
45204
45566
|
respectInlineDisables: input.respectInlineDisables,
|
|
45205
45567
|
adoptExistingLintConfig: input.adoptExistingLintConfig,
|
|
45206
45568
|
ignoredTags: input.ignoredTags,
|
|
45569
|
+
includedTags: input.includedTags,
|
|
45570
|
+
includeTagDefaults: input.includeTagDefaults,
|
|
45207
45571
|
userConfig: input.userConfig ?? null,
|
|
45208
45572
|
configSourceDirectory: input.configSourceDirectory,
|
|
45209
45573
|
onPartialFailure: (reason) => {
|
|
@@ -46052,7 +46416,9 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
46052
46416
|
const securityScanFailedRef = yield* make$13(false);
|
|
46053
46417
|
const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
|
|
46054
46418
|
project,
|
|
46055
|
-
ignoredTags: input.ignoredTags
|
|
46419
|
+
ignoredTags: input.ignoredTags,
|
|
46420
|
+
includedTags: input.includedTags,
|
|
46421
|
+
includeTagDefaults: input.includeTagDefaults
|
|
46056
46422
|
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
|
|
46057
46423
|
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
46058
46424
|
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
@@ -46143,6 +46509,8 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
46143
46509
|
respectInlineDisables: input.respectInlineDisables,
|
|
46144
46510
|
adoptExistingLintConfig: input.adoptExistingLintConfig,
|
|
46145
46511
|
ignoredTags: input.ignoredTags,
|
|
46512
|
+
includedTags: input.includedTags,
|
|
46513
|
+
includeTagDefaults: input.includeTagDefaults,
|
|
46146
46514
|
userConfig: resolvedConfig.config ?? void 0,
|
|
46147
46515
|
configSourceDirectory: resolvedConfig.configSourceDirectory ?? void 0,
|
|
46148
46516
|
onFileProgress: (scannedFileCount, totalFileCount) => {
|
|
@@ -46421,12 +46789,19 @@ const isPathInsideDirectory = (childAbsolutePath, parentAbsolutePath) => {
|
|
|
46421
46789
|
*/
|
|
46422
46790
|
const materializeSourceTree = (input) => gen(function* () {
|
|
46423
46791
|
const materializedFiles = [];
|
|
46792
|
+
const unmaterializedFiles = [];
|
|
46424
46793
|
const resolvedTempDirectory = path.resolve(input.tempDirectory);
|
|
46425
46794
|
for (const relativePath of input.files) {
|
|
46426
46795
|
const content = yield* input.readContent(relativePath).pipe(orElseSucceed(() => null));
|
|
46427
|
-
if (content === null)
|
|
46796
|
+
if (content === null) {
|
|
46797
|
+
unmaterializedFiles.push(relativePath);
|
|
46798
|
+
continue;
|
|
46799
|
+
}
|
|
46428
46800
|
const candidateTargetPath = path.resolve(resolvedTempDirectory, relativePath);
|
|
46429
|
-
if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory))
|
|
46801
|
+
if (!isPathInsideDirectory(candidateTargetPath, resolvedTempDirectory)) {
|
|
46802
|
+
unmaterializedFiles.push(relativePath);
|
|
46803
|
+
continue;
|
|
46804
|
+
}
|
|
46430
46805
|
yield* sync(() => {
|
|
46431
46806
|
fs.mkdirSync(path.dirname(candidateTargetPath), { recursive: true });
|
|
46432
46807
|
fs.writeFileSync(candidateTargetPath, content);
|
|
@@ -46443,6 +46818,7 @@ const materializeSourceTree = (input) => gen(function* () {
|
|
|
46443
46818
|
return {
|
|
46444
46819
|
tempDirectory: input.tempDirectory,
|
|
46445
46820
|
materializedFiles,
|
|
46821
|
+
unmaterializedFiles,
|
|
46446
46822
|
cleanup: () => {
|
|
46447
46823
|
try {
|
|
46448
46824
|
fs.rmSync(input.tempDirectory, {
|
|
@@ -46712,54 +47088,80 @@ const buildSkippedChecks = (input) => {
|
|
|
46712
47088
|
skippedCheckReasons
|
|
46713
47089
|
};
|
|
46714
47090
|
};
|
|
46715
|
-
const
|
|
47091
|
+
const fingerprintText = (text) => createHash("sha256").update(text).digest("hex");
|
|
47092
|
+
const normalizeEvidence = (evidence) => evidence.replace(/\s+/g, " ").trim();
|
|
47093
|
+
const getDiagnosticMatchKeys = (diagnostic, evidence) => {
|
|
46716
47094
|
const ruleKey = `${diagnostic.plugin}/${diagnostic.rule}`;
|
|
46717
|
-
const
|
|
46718
|
-
|
|
46719
|
-
};
|
|
46720
|
-
|
|
46721
|
-
|
|
46722
|
-
|
|
46723
|
-
|
|
46724
|
-
|
|
46725
|
-
|
|
46726
|
-
|
|
46727
|
-
|
|
46728
|
-
|
|
46729
|
-
|
|
46730
|
-
|
|
46731
|
-
|
|
46732
|
-
|
|
46733
|
-
|
|
46734
|
-
|
|
46735
|
-
|
|
46736
|
-
|
|
46737
|
-
|
|
46738
|
-
|
|
46739
|
-
|
|
46740
|
-
|
|
46741
|
-
|
|
46742
|
-
|
|
46743
|
-
*
|
|
47095
|
+
const messageFingerprint = fingerprintText(`${diagnostic.title ?? ""}\0${diagnostic.message}`);
|
|
47096
|
+
const normalizedEvidence = evidence === null ? "" : normalizeEvidence(evidence);
|
|
47097
|
+
const stableEvidenceKey = normalizedEvidence.length > 0 ? `evidence\0${ruleKey}\0${messageFingerprint}\0${fingerprintText(normalizedEvidence)}` : null;
|
|
47098
|
+
return {
|
|
47099
|
+
stableEvidenceKey,
|
|
47100
|
+
sameFileStableEvidenceKey: stableEvidenceKey === null ? null : `${diagnostic.filePath}\0${stableEvidenceKey}`,
|
|
47101
|
+
sameFileFallbackKey: diagnostic.matchByOccurrence || normalizedEvidence.length === 0 ? `fallback\0${diagnostic.filePath}\0${ruleKey}\0${messageFingerprint}` : null
|
|
47102
|
+
};
|
|
47103
|
+
};
|
|
47104
|
+
const addDiagnosticIndex = (buckets, key, diagnosticIndex) => {
|
|
47105
|
+
if (key === null) return;
|
|
47106
|
+
const diagnosticIndexes = buckets.get(key) ?? [];
|
|
47107
|
+
diagnosticIndexes.push(diagnosticIndex);
|
|
47108
|
+
buckets.set(key, diagnosticIndexes);
|
|
47109
|
+
};
|
|
47110
|
+
const takeMatchingDiagnosticIndex = (buckets, key, matchedDiagnosticIndexes) => {
|
|
47111
|
+
if (key === null) return null;
|
|
47112
|
+
for (const diagnosticIndex of buckets.get(key) ?? []) if (!matchedDiagnosticIndexes.has(diagnosticIndex)) return diagnosticIndex;
|
|
47113
|
+
return null;
|
|
47114
|
+
};
|
|
47115
|
+
const readDiagnosticEvidence = (diagnostic, readEvidence, readLine) => readEvidence?.(diagnostic) ?? readLine(diagnostic.filePath, diagnostic.line);
|
|
47116
|
+
const buildMatchCandidates = (diagnostics, readEvidence, readLine) => diagnostics.map((diagnostic, diagnosticIndex) => ({
|
|
47117
|
+
diagnosticIndex,
|
|
47118
|
+
...getDiagnosticMatchKeys(diagnostic, readDiagnosticEvidence(diagnostic, readEvidence, readLine))
|
|
47119
|
+
}));
|
|
47120
|
+
/**
|
|
47121
|
+
* Diffs a head scan against a base scan using a multiset of construct-level
|
|
47122
|
+
* evidence. Stable identities combine plugin/rule, the diagnostic message,
|
|
47123
|
+
* and normalized diagnosed source, so unchanged findings can move across
|
|
47124
|
+
* files while changed constructs or messages remain new. Cardinality is
|
|
47125
|
+
* retained for identical findings. Diagnostics explicitly marked
|
|
47126
|
+
* `matchByOccurrence` may fall back to same-file plugin/rule/message matching
|
|
47127
|
+
* after same-file strict evidence matching. Cross-file evidence matching runs
|
|
47128
|
+
* last so a copy cannot consume a reformatted local occurrence. Unreadable
|
|
47129
|
+
* evidence uses the same conservative fallback rather than matching across
|
|
47130
|
+
* files without proof.
|
|
46744
47131
|
*/
|
|
46745
47132
|
const computeDiagnosticDelta = (input) => {
|
|
46746
|
-
const
|
|
46747
|
-
|
|
46748
|
-
|
|
46749
|
-
|
|
46750
|
-
|
|
46751
|
-
const
|
|
46752
|
-
|
|
46753
|
-
|
|
46754
|
-
|
|
46755
|
-
|
|
46756
|
-
|
|
46757
|
-
|
|
46758
|
-
|
|
46759
|
-
|
|
47133
|
+
const baseCandidates = buildMatchCandidates(input.baseDiagnostics, input.readBaseEvidence, input.readBaseLine);
|
|
47134
|
+
const headCandidates = buildMatchCandidates(input.headDiagnostics, input.readHeadEvidence, input.readHeadLine);
|
|
47135
|
+
const baseByStableEvidence = /* @__PURE__ */ new Map();
|
|
47136
|
+
const baseBySameFileStableEvidence = /* @__PURE__ */ new Map();
|
|
47137
|
+
const baseBySameFileFallback = /* @__PURE__ */ new Map();
|
|
47138
|
+
for (const candidate of baseCandidates) {
|
|
47139
|
+
addDiagnosticIndex(baseByStableEvidence, candidate.stableEvidenceKey, candidate.diagnosticIndex);
|
|
47140
|
+
addDiagnosticIndex(baseBySameFileStableEvidence, candidate.sameFileStableEvidenceKey, candidate.diagnosticIndex);
|
|
47141
|
+
addDiagnosticIndex(baseBySameFileFallback, candidate.sameFileFallbackKey, candidate.diagnosticIndex);
|
|
47142
|
+
}
|
|
47143
|
+
const matchedHeadDiagnosticIndexes = /* @__PURE__ */ new Set();
|
|
47144
|
+
const matchedBaseDiagnosticIndexes = /* @__PURE__ */ new Set();
|
|
47145
|
+
const matchCandidates = (baseBuckets, getKey, onMatch) => {
|
|
47146
|
+
for (const candidate of headCandidates) {
|
|
47147
|
+
if (matchedHeadDiagnosticIndexes.has(candidate.diagnosticIndex)) continue;
|
|
47148
|
+
const baseDiagnosticIndex = takeMatchingDiagnosticIndex(baseBuckets, getKey(candidate), matchedBaseDiagnosticIndexes);
|
|
47149
|
+
if (baseDiagnosticIndex === null) continue;
|
|
47150
|
+
matchedHeadDiagnosticIndexes.add(candidate.diagnosticIndex);
|
|
47151
|
+
matchedBaseDiagnosticIndexes.add(baseDiagnosticIndex);
|
|
47152
|
+
onMatch?.(candidate.diagnosticIndex, baseDiagnosticIndex);
|
|
47153
|
+
}
|
|
47154
|
+
};
|
|
47155
|
+
matchCandidates(baseBySameFileStableEvidence, (candidate) => candidate.sameFileStableEvidenceKey);
|
|
47156
|
+
matchCandidates(baseBySameFileFallback, (candidate) => candidate.sameFileFallbackKey);
|
|
47157
|
+
let crossFileMatchCount = 0;
|
|
47158
|
+
matchCandidates(baseByStableEvidence, (candidate) => candidate.stableEvidenceKey, (head, base) => {
|
|
47159
|
+
if (input.headDiagnostics[head]?.filePath !== input.baseDiagnostics[base]?.filePath) crossFileMatchCount += 1;
|
|
47160
|
+
});
|
|
46760
47161
|
return {
|
|
46761
|
-
newDiagnostics,
|
|
46762
|
-
fixedCount
|
|
47162
|
+
newDiagnostics: input.headDiagnostics.filter((_diagnostic, diagnosticIndex) => !matchedHeadDiagnosticIndexes.has(diagnosticIndex)),
|
|
47163
|
+
fixedCount: input.baseDiagnostics.length - matchedBaseDiagnosticIndexes.size,
|
|
47164
|
+
crossFileMatchCount
|
|
46763
47165
|
};
|
|
46764
47166
|
};
|
|
46765
47167
|
/**
|
|
@@ -46791,6 +47193,12 @@ const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runProm
|
|
|
46791
47193
|
};
|
|
46792
47194
|
}).pipe(provide(Git.layerNode)));
|
|
46793
47195
|
const filterSourceFiles = (filePaths) => filePaths.filter(isLintableSourceFile);
|
|
47196
|
+
const getBaselineDiffPlan = (directory, ref) => runPromise(gen(function* () {
|
|
47197
|
+
return yield* (yield* Git).baselineDiffPlan({
|
|
47198
|
+
directory,
|
|
47199
|
+
ref
|
|
47200
|
+
});
|
|
47201
|
+
}).pipe(provide(Git.layerNode)));
|
|
46794
47202
|
/**
|
|
46795
47203
|
* Programmatic façade over `Git.changedLineRanges` (the `lines` scope). Diffs
|
|
46796
47204
|
* `files` with `--unified=0` against `baseRef` (or the index when `cached`),
|
|
@@ -47247,7 +47655,7 @@ const makeNoopConsole = () => ({
|
|
|
47247
47655
|
});
|
|
47248
47656
|
//#endregion
|
|
47249
47657
|
//#region src/cli/utils/version.ts
|
|
47250
|
-
const VERSION = "0.8.
|
|
47658
|
+
const VERSION = "0.8.1-dev.0b0b5ac";
|
|
47251
47659
|
//#endregion
|
|
47252
47660
|
//#region src/cli/utils/json-mode.ts
|
|
47253
47661
|
let context = null;
|
|
@@ -47334,7 +47742,11 @@ const getRunId = () => {
|
|
|
47334
47742
|
};
|
|
47335
47743
|
//#endregion
|
|
47336
47744
|
//#region src/cli/utils/build-run-context.ts
|
|
47337
|
-
const ROOT_SUBCOMMANDS = new Set([
|
|
47745
|
+
const ROOT_SUBCOMMANDS = new Set([
|
|
47746
|
+
"design",
|
|
47747
|
+
"install",
|
|
47748
|
+
"setup"
|
|
47749
|
+
]);
|
|
47338
47750
|
const detectInvokedVia = () => {
|
|
47339
47751
|
const userAgent = process.env.npm_config_user_agent;
|
|
47340
47752
|
if (!userAgent) return "unknown";
|
|
@@ -47418,7 +47830,9 @@ const buildSentryProjectContext = (projectInfo) => ({
|
|
|
47418
47830
|
reactMajorVersion: projectInfo.reactMajorVersion,
|
|
47419
47831
|
hasTypeScript: projectInfo.hasTypeScript,
|
|
47420
47832
|
hasReactCompiler: projectInfo.hasReactCompiler,
|
|
47421
|
-
|
|
47833
|
+
tanstackQueryVersion: projectInfo.tanstackQueryVersion,
|
|
47834
|
+
mobxVersion: projectInfo.mobxVersion,
|
|
47835
|
+
styledComponentsVersion: projectInfo.styledComponentsVersion,
|
|
47422
47836
|
tailwindVersion: projectInfo.tailwindVersion,
|
|
47423
47837
|
zodVersion: projectInfo.zodVersion,
|
|
47424
47838
|
preactVersion: projectInfo.preactVersion,
|
|
@@ -47599,13 +48013,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
47599
48013
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
47600
48014
|
* standard `SENTRY_RELEASE` override.
|
|
47601
48015
|
*/
|
|
47602
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.
|
|
48016
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.8.1-dev.0b0b5ac`;
|
|
47603
48017
|
/**
|
|
47604
48018
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
47605
48019
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
47606
48020
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
47607
48021
|
*/
|
|
47608
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.
|
|
48022
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.8.1-dev.0b0b5ac") ? "development" : "production");
|
|
47609
48023
|
/**
|
|
47610
48024
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
47611
48025
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -59931,6 +60345,8 @@ const buildScanResultCacheKey = (input) => {
|
|
|
59931
60345
|
warnings: input.options.warnings,
|
|
59932
60346
|
adoptExistingLintConfig: input.options.adoptExistingLintConfig,
|
|
59933
60347
|
ignoredTags: [...input.options.ignoredTags].sort(),
|
|
60348
|
+
includedTags: [...input.options.includedTags].sort(),
|
|
60349
|
+
includeTagDefaults: input.options.includeTagDefaults,
|
|
59934
60350
|
concurrency: input.options.concurrency,
|
|
59935
60351
|
lintBatchOrdering: resolveLintBatchOrdering(),
|
|
59936
60352
|
baselineRef: input.options.baseline?.ref,
|
|
@@ -60228,6 +60644,7 @@ const buildOutcomeAttributes = (input) => {
|
|
|
60228
60644
|
new: summary.totalDiagnosticCount,
|
|
60229
60645
|
fixed: result.baselineDelta.fixedCount,
|
|
60230
60646
|
baseTotal: result.baselineDelta.baseTotalCount,
|
|
60647
|
+
crossFileMatches: result.baselineDelta.crossFileMatchCount ?? null,
|
|
60231
60648
|
degraded: false
|
|
60232
60649
|
}));
|
|
60233
60650
|
else if (input.gateExempt) attributes["baseline.degraded"] = true;
|
|
@@ -60341,28 +60758,49 @@ const diagnosticIntersectsLineRanges = (diagnostic, lineRanges) => {
|
|
|
60341
60758
|
return lineRanges.some(([rangeStartLine, rangeEndLine]) => diagnostic.line <= rangeEndLine && diagnosticEndLine >= rangeStartLine);
|
|
60342
60759
|
};
|
|
60343
60760
|
//#endregion
|
|
60761
|
+
//#region src/cli/utils/is-materializable-git-source.ts
|
|
60762
|
+
const GIT_LFS_POINTER_HEADER = "version https://git-lfs.github.com/spec/v1";
|
|
60763
|
+
const isMaterializableGitSource = (content) => !content.includes("\0") && !content.startsWith(GIT_LFS_POINTER_HEADER);
|
|
60764
|
+
//#endregion
|
|
60344
60765
|
//#region src/cli/utils/materialize-baseline-files.ts
|
|
60345
60766
|
/**
|
|
60346
|
-
* Materializes the
|
|
60347
|
-
* `
|
|
60348
|
-
*
|
|
60349
|
-
*
|
|
60350
|
-
*
|
|
60351
|
-
*
|
|
60767
|
+
* Materializes the base side of the `ref` → worktree diff into
|
|
60768
|
+
* `tempDirectory`, mirroring the project layout plus head's config files so
|
|
60769
|
+
* both sides lint under the same rules. Git rename heuristics are disabled by
|
|
60770
|
+
* the diff planner, so an old path is retained as a base deletion while its
|
|
60771
|
+
* new path is a head addition. Missing head-only additions are expected;
|
|
60772
|
+
* missing paths that the plan says must exist at base make `isComplete` false.
|
|
60352
60773
|
*/
|
|
60353
60774
|
const materializeBaselineFiles = (input) => runPromise(gen(function* () {
|
|
60354
60775
|
const git = yield* Git;
|
|
60355
|
-
|
|
60776
|
+
const diffPlan = yield* git.baselineDiffPlan({
|
|
60356
60777
|
directory: input.directory,
|
|
60357
|
-
|
|
60778
|
+
ref: input.ref
|
|
60779
|
+
});
|
|
60780
|
+
const baseFiles = input.baseFiles ?? diffPlan?.baseFiles;
|
|
60781
|
+
const headFiles = input.headFiles ?? diffPlan?.headFiles;
|
|
60782
|
+
const untrackedFiles = diffPlan?.untrackedFiles ?? [];
|
|
60783
|
+
if (baseFiles === void 0 || headFiles === void 0) return null;
|
|
60784
|
+
const files = [...new Set([...input.files, ...baseFiles])];
|
|
60785
|
+
const tree = yield* materializeSourceTree({
|
|
60786
|
+
directory: input.directory,
|
|
60787
|
+
files,
|
|
60358
60788
|
tempDirectory: input.tempDirectory,
|
|
60359
60789
|
readContent: (relativePath) => git.showRefContent({
|
|
60360
60790
|
directory: input.directory,
|
|
60361
60791
|
ref: input.ref,
|
|
60362
60792
|
relativePath,
|
|
60363
60793
|
options: { maxBufferBytes: GIT_SHOW_MAX_BUFFER_BYTES }
|
|
60364
|
-
})
|
|
60794
|
+
}).pipe(map$3((content) => content !== null && isMaterializableGitSource(content) ? content : null))
|
|
60365
60795
|
});
|
|
60796
|
+
const materializedFiles = new Set(tree.materializedFiles);
|
|
60797
|
+
return {
|
|
60798
|
+
...tree,
|
|
60799
|
+
baseFiles,
|
|
60800
|
+
headFiles,
|
|
60801
|
+
isComplete: baseFiles.every((filePath) => materializedFiles.has(filePath)),
|
|
60802
|
+
untrackedFiles
|
|
60803
|
+
};
|
|
60366
60804
|
}).pipe(provide(Git.layerNode)));
|
|
60367
60805
|
/**
|
|
60368
60806
|
* Resolves the commit a baseline scan should read base content from — the
|
|
@@ -60385,8 +60823,8 @@ const resolveMergeBaseRef = async (directory, ref) => {
|
|
|
60385
60823
|
//#region src/cli/utils/read-source-line.ts
|
|
60386
60824
|
/**
|
|
60387
60825
|
* Builds a cached `(filePath, line) -> source text` reader rooted at
|
|
60388
|
-
* `rootDirectory`, used
|
|
60389
|
-
*
|
|
60826
|
+
* `rootDirectory`, used as the fallback when construct-level diagnostic
|
|
60827
|
+
* evidence cannot be read. Wraps core's
|
|
60390
60828
|
* `createNodeReadFileLinesSync` with per-file memoization + 1-indexed line
|
|
60391
60829
|
* lookup; unreadable files / out-of-range lines return `null`.
|
|
60392
60830
|
*/
|
|
@@ -60401,10 +60839,342 @@ const createSourceLineReader = (rootDirectory) => {
|
|
|
60401
60839
|
};
|
|
60402
60840
|
};
|
|
60403
60841
|
//#endregion
|
|
60842
|
+
//#region src/cli/utils/read-diagnostic-evidence.ts
|
|
60843
|
+
const SOURCE_EXTENSIONS = [
|
|
60844
|
+
".ts",
|
|
60845
|
+
".tsx",
|
|
60846
|
+
".js",
|
|
60847
|
+
".jsx",
|
|
60848
|
+
".mts",
|
|
60849
|
+
".cts",
|
|
60850
|
+
".mjs",
|
|
60851
|
+
".cjs"
|
|
60852
|
+
];
|
|
60853
|
+
const COMPONENT_WRAPPER_NAMES = new Set(["memo", "forwardRef"]);
|
|
60854
|
+
const getScriptKind = (filePath) => {
|
|
60855
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
60856
|
+
if (extension === ".tsx") return ts.ScriptKind.TSX;
|
|
60857
|
+
if (extension === ".jsx") return ts.ScriptKind.JSX;
|
|
60858
|
+
if (extension === ".js" || extension === ".mjs" || extension === ".cjs") return ts.ScriptKind.JS;
|
|
60859
|
+
return ts.ScriptKind.TS;
|
|
60860
|
+
};
|
|
60861
|
+
const resolveSafeSourcePath = (rootDirectory, filePath) => {
|
|
60862
|
+
try {
|
|
60863
|
+
const resolvedRootDirectory = fs.realpathSync(rootDirectory);
|
|
60864
|
+
const absolutePath = path.resolve(resolvedRootDirectory, filePath);
|
|
60865
|
+
const relativePath = path.relative(resolvedRootDirectory, absolutePath);
|
|
60866
|
+
if (relativePath.length === 0 || relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath)) return null;
|
|
60867
|
+
const stats = fs.lstatSync(absolutePath);
|
|
60868
|
+
if (!stats.isFile() || stats.isSymbolicLink()) return null;
|
|
60869
|
+
const realPath = fs.realpathSync(absolutePath);
|
|
60870
|
+
const realRelativePath = path.relative(resolvedRootDirectory, realPath);
|
|
60871
|
+
return realRelativePath !== ".." && !realRelativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(realRelativePath) ? realPath : null;
|
|
60872
|
+
} catch {
|
|
60873
|
+
return null;
|
|
60874
|
+
}
|
|
60875
|
+
};
|
|
60876
|
+
const readSourceRecord = (rootDirectory, filePath, state) => {
|
|
60877
|
+
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
60878
|
+
if (state.recordByFilePath.has(normalizedFilePath)) return state.recordByFilePath.get(normalizedFilePath) ?? null;
|
|
60879
|
+
const absolutePath = resolveSafeSourcePath(rootDirectory, normalizedFilePath);
|
|
60880
|
+
if (absolutePath === null) {
|
|
60881
|
+
state.recordByFilePath.set(normalizedFilePath, null);
|
|
60882
|
+
return null;
|
|
60883
|
+
}
|
|
60884
|
+
try {
|
|
60885
|
+
const sourceText = fs.readFileSync(absolutePath, "utf-8");
|
|
60886
|
+
const record = {
|
|
60887
|
+
absolutePath,
|
|
60888
|
+
filePath: normalizedFilePath,
|
|
60889
|
+
sourceFile: ts.createSourceFile(absolutePath, sourceText, ts.ScriptTarget.Latest, true, getScriptKind(absolutePath)),
|
|
60890
|
+
sourceText
|
|
60891
|
+
};
|
|
60892
|
+
state.recordByFilePath.set(normalizedFilePath, record);
|
|
60893
|
+
return record;
|
|
60894
|
+
} catch {
|
|
60895
|
+
state.recordByFilePath.set(normalizedFilePath, null);
|
|
60896
|
+
return null;
|
|
60897
|
+
}
|
|
60898
|
+
};
|
|
60899
|
+
const listSourceRecords = (rootDirectory, state) => {
|
|
60900
|
+
if (state.sourceRecords !== null) return state.sourceRecords;
|
|
60901
|
+
state.sourceRecords = listSourceFiles(rootDirectory).flatMap((filePath) => {
|
|
60902
|
+
const record = readSourceRecord(rootDirectory, filePath, state);
|
|
60903
|
+
return record === null ? [] : [record];
|
|
60904
|
+
});
|
|
60905
|
+
return state.sourceRecords;
|
|
60906
|
+
};
|
|
60907
|
+
const unwrapExpression = (expression) => {
|
|
60908
|
+
let currentExpression = expression;
|
|
60909
|
+
while (ts.isParenthesizedExpression(currentExpression) || ts.isAsExpression(currentExpression) || ts.isTypeAssertionExpression(currentExpression) || ts.isNonNullExpression(currentExpression) || ts.isSatisfiesExpression(currentExpression)) currentExpression = currentExpression.expression;
|
|
60910
|
+
return currentExpression;
|
|
60911
|
+
};
|
|
60912
|
+
const getCalleeName = (expression) => {
|
|
60913
|
+
const unwrappedExpression = unwrapExpression(expression);
|
|
60914
|
+
if (ts.isIdentifier(unwrappedExpression)) return unwrappedExpression.text;
|
|
60915
|
+
if (ts.isPropertyAccessExpression(unwrappedExpression)) return unwrappedExpression.name.text;
|
|
60916
|
+
return null;
|
|
60917
|
+
};
|
|
60918
|
+
const isComponentWrapperCall = (node) => {
|
|
60919
|
+
const wrapperName = getCalleeName(node.expression);
|
|
60920
|
+
return wrapperName !== null && COMPONENT_WRAPPER_NAMES.has(wrapperName);
|
|
60921
|
+
};
|
|
60922
|
+
const getFunctionName = (node) => {
|
|
60923
|
+
if (ts.isFunctionDeclaration(node) && node.name !== void 0) return node.name.text;
|
|
60924
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
60925
|
+
let componentExpression = node;
|
|
60926
|
+
while (true) {
|
|
60927
|
+
const parent = componentExpression.parent;
|
|
60928
|
+
if (ts.isParenthesizedExpression(parent) || ts.isAsExpression(parent) || ts.isTypeAssertionExpression(parent) || ts.isNonNullExpression(parent) || ts.isSatisfiesExpression(parent)) {
|
|
60929
|
+
componentExpression = parent;
|
|
60930
|
+
continue;
|
|
60931
|
+
}
|
|
60932
|
+
if (ts.isCallExpression(parent) && parent.arguments[0] === componentExpression && isComponentWrapperCall(parent)) {
|
|
60933
|
+
componentExpression = parent;
|
|
60934
|
+
continue;
|
|
60935
|
+
}
|
|
60936
|
+
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) return parent.name.text;
|
|
60937
|
+
return node.name?.text ?? null;
|
|
60938
|
+
}
|
|
60939
|
+
}
|
|
60940
|
+
return null;
|
|
60941
|
+
};
|
|
60942
|
+
const isComponentFunction = (node) => ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node);
|
|
60943
|
+
const isDefaultExportedComponent = (record, node, componentName) => {
|
|
60944
|
+
if (ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword)) return true;
|
|
60945
|
+
return record.sourceFile.statements.some((statement) => ts.isExportAssignment(statement) && ts.isIdentifier(statement.expression) && statement.expression.text === componentName);
|
|
60946
|
+
};
|
|
60947
|
+
const findEnclosingComponent = (record, diagnostic) => {
|
|
60948
|
+
if (!Number.isInteger(diagnostic.line) || diagnostic.line < 1) return null;
|
|
60949
|
+
const lineIndex = diagnostic.line - 1;
|
|
60950
|
+
const lineStarts = record.sourceFile.getLineStarts();
|
|
60951
|
+
const lineStart = lineStarts[lineIndex];
|
|
60952
|
+
if (lineStart === void 0) return null;
|
|
60953
|
+
const nextLineStart = lineStarts[lineIndex + 1] ?? record.sourceFile.end;
|
|
60954
|
+
const lineEnd = Math.max(lineStart, nextLineStart - 1);
|
|
60955
|
+
const columnIndex = Number.isInteger(diagnostic.column) ? Math.max(0, diagnostic.column - 1) : 0;
|
|
60956
|
+
const diagnosticPosition = Math.min(lineStart + columnIndex, lineEnd);
|
|
60957
|
+
let component = null;
|
|
60958
|
+
let componentWidth = Number.POSITIVE_INFINITY;
|
|
60959
|
+
const visit = (node) => {
|
|
60960
|
+
if (diagnosticPosition < node.getStart(record.sourceFile) || diagnosticPosition > node.end) return;
|
|
60961
|
+
if (isComponentFunction(node)) {
|
|
60962
|
+
const name = getFunctionName(node);
|
|
60963
|
+
const width = node.end - node.getStart(record.sourceFile);
|
|
60964
|
+
if (name !== null && /^[A-Z]/.test(name) && width < componentWidth) {
|
|
60965
|
+
component = {
|
|
60966
|
+
isDefaultExport: isDefaultExportedComponent(record, node, name),
|
|
60967
|
+
name,
|
|
60968
|
+
node,
|
|
60969
|
+
record
|
|
60970
|
+
};
|
|
60971
|
+
componentWidth = width;
|
|
60972
|
+
}
|
|
60973
|
+
}
|
|
60974
|
+
ts.forEachChild(node, visit);
|
|
60975
|
+
};
|
|
60976
|
+
visit(record.sourceFile);
|
|
60977
|
+
return component;
|
|
60978
|
+
};
|
|
60979
|
+
const moduleSpecifierTargetsComponent = (callerRecord, moduleSpecifier, componentRecord) => {
|
|
60980
|
+
if (!moduleSpecifier.startsWith(".")) return false;
|
|
60981
|
+
const unresolvedPath = path.resolve(path.dirname(callerRecord.absolutePath), moduleSpecifier);
|
|
60982
|
+
const specifiedExtension = path.extname(unresolvedPath).toLowerCase();
|
|
60983
|
+
const sourcePathWithoutExtension = SOURCE_EXTENSIONS.includes(specifiedExtension) ? unresolvedPath.slice(0, -specifiedExtension.length) : unresolvedPath;
|
|
60984
|
+
return [
|
|
60985
|
+
unresolvedPath,
|
|
60986
|
+
...SOURCE_EXTENSIONS.map((extension) => `${sourcePathWithoutExtension}${extension}`),
|
|
60987
|
+
...sourcePathWithoutExtension === unresolvedPath ? SOURCE_EXTENSIONS.map((extension) => path.join(unresolvedPath, `index${extension}`)) : []
|
|
60988
|
+
].includes(componentRecord.absolutePath);
|
|
60989
|
+
};
|
|
60990
|
+
const getComponentTagNames = (record, component) => {
|
|
60991
|
+
if (record.absolutePath === component.record.absolutePath) return new Set([component.name]);
|
|
60992
|
+
const tagNames = /* @__PURE__ */ new Set();
|
|
60993
|
+
for (const statement of record.sourceFile.statements) {
|
|
60994
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier) || !moduleSpecifierTargetsComponent(record, statement.moduleSpecifier.text, component.record)) continue;
|
|
60995
|
+
const importClause = statement.importClause;
|
|
60996
|
+
if (component.isDefaultExport && importClause?.name !== void 0) tagNames.add(importClause.name.text);
|
|
60997
|
+
const namedBindings = importClause?.namedBindings;
|
|
60998
|
+
if (namedBindings !== void 0 && ts.isNamedImports(namedBindings)) {
|
|
60999
|
+
for (const element of namedBindings.elements) if ((element.propertyName?.text ?? element.name.text) === component.name) tagNames.add(element.name.text);
|
|
61000
|
+
}
|
|
61001
|
+
}
|
|
61002
|
+
return tagNames;
|
|
61003
|
+
};
|
|
61004
|
+
const getTransparentCall = (functionNode) => {
|
|
61005
|
+
if (functionNode.body === void 0) return null;
|
|
61006
|
+
if (!ts.isBlock(functionNode.body)) {
|
|
61007
|
+
const expression = unwrapExpression(functionNode.body);
|
|
61008
|
+
return ts.isCallExpression(expression) ? expression : null;
|
|
61009
|
+
}
|
|
61010
|
+
if (functionNode.body.statements.length !== 1) return null;
|
|
61011
|
+
const statement = functionNode.body.statements[0];
|
|
61012
|
+
if (statement === void 0) return null;
|
|
61013
|
+
const expression = ts.isExpressionStatement(statement) || ts.isReturnStatement(statement) ? statement.expression : void 0;
|
|
61014
|
+
if (expression === void 0) return null;
|
|
61015
|
+
const unwrappedExpression = unwrapExpression(expression);
|
|
61016
|
+
return ts.isCallExpression(unwrappedExpression) ? unwrappedExpression : null;
|
|
61017
|
+
};
|
|
61018
|
+
const resolveTransparentTarget = (functionNode) => {
|
|
61019
|
+
const call = getTransparentCall(functionNode);
|
|
61020
|
+
if (call === null) return null;
|
|
61021
|
+
const target = unwrapExpression(call.expression);
|
|
61022
|
+
if (!ts.isIdentifier(target) || call.arguments.length !== functionNode.parameters.length) return null;
|
|
61023
|
+
for (const [parameterIndex, parameter] of functionNode.parameters.entries()) {
|
|
61024
|
+
const argument = call.arguments[parameterIndex];
|
|
61025
|
+
const unwrappedArgument = argument === void 0 ? null : unwrapExpression(argument);
|
|
61026
|
+
if (!ts.isIdentifier(parameter.name) || unwrappedArgument === null || !ts.isIdentifier(unwrappedArgument) || unwrappedArgument.text !== parameter.name.text) return null;
|
|
61027
|
+
}
|
|
61028
|
+
return target.text;
|
|
61029
|
+
};
|
|
61030
|
+
const getForwardingFunction = (declaration) => {
|
|
61031
|
+
if (ts.isFunctionDeclaration(declaration)) return declaration;
|
|
61032
|
+
if (!ts.isVariableDeclaration(declaration) || declaration.initializer === void 0) return null;
|
|
61033
|
+
const initializer = unwrapExpression(declaration.initializer);
|
|
61034
|
+
if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) return initializer;
|
|
61035
|
+
if (ts.isCallExpression(initializer) && getCalleeName(initializer.expression) === "useCallback") {
|
|
61036
|
+
const callback = initializer.arguments[0];
|
|
61037
|
+
if (callback !== void 0 && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback))) return callback;
|
|
61038
|
+
}
|
|
61039
|
+
return null;
|
|
61040
|
+
};
|
|
61041
|
+
const getDeclaredBindingName = (node) => {
|
|
61042
|
+
if ((ts.isVariableDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node) || ts.isFunctionDeclaration(node)) && node.name !== void 0 && ts.isIdentifier(node.name)) return node.name.text;
|
|
61043
|
+
if (ts.isImportClause(node)) return node.name?.text ?? null;
|
|
61044
|
+
if (ts.isImportSpecifier(node) || ts.isNamespaceImport(node)) return node.name.text;
|
|
61045
|
+
return null;
|
|
61046
|
+
};
|
|
61047
|
+
const findBindingScope = (node) => {
|
|
61048
|
+
let currentNode = node;
|
|
61049
|
+
while (currentNode !== void 0) {
|
|
61050
|
+
if (ts.isParameter(currentNode) && ts.isFunctionLike(currentNode.parent)) return currentNode.parent;
|
|
61051
|
+
if (ts.isBlock(currentNode) || ts.isModuleBlock(currentNode) || ts.isSourceFile(currentNode)) return currentNode;
|
|
61052
|
+
currentNode = currentNode.parent;
|
|
61053
|
+
}
|
|
61054
|
+
return node.getSourceFile();
|
|
61055
|
+
};
|
|
61056
|
+
const findBindingDeclaration = (record, binding) => {
|
|
61057
|
+
const usagePosition = binding.getStart(record.sourceFile);
|
|
61058
|
+
const candidates = [];
|
|
61059
|
+
const visit = (node) => {
|
|
61060
|
+
if (getDeclaredBindingName(node) === binding.text) {
|
|
61061
|
+
const scope = findBindingScope(node);
|
|
61062
|
+
const isVisibleScope = usagePosition >= scope.getStart(record.sourceFile) && usagePosition <= scope.end;
|
|
61063
|
+
const isVisibleDeclaration = ts.isFunctionDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node) || ts.isImportClause(node) || ts.isImportSpecifier(node) || ts.isNamespaceImport(node) || node.getStart(record.sourceFile) < usagePosition;
|
|
61064
|
+
if (isVisibleScope && isVisibleDeclaration) candidates.push({
|
|
61065
|
+
declaration: node,
|
|
61066
|
+
scope
|
|
61067
|
+
});
|
|
61068
|
+
}
|
|
61069
|
+
ts.forEachChild(node, visit);
|
|
61070
|
+
};
|
|
61071
|
+
visit(record.sourceFile);
|
|
61072
|
+
let nearestCandidate = null;
|
|
61073
|
+
let nearestScopeWidth = Number.POSITIVE_INFINITY;
|
|
61074
|
+
let isNearestScopeAmbiguous = false;
|
|
61075
|
+
for (const candidate of candidates) {
|
|
61076
|
+
const scopeWidth = candidate.scope.end - candidate.scope.getStart(record.sourceFile);
|
|
61077
|
+
if (scopeWidth < nearestScopeWidth) {
|
|
61078
|
+
nearestCandidate = candidate;
|
|
61079
|
+
nearestScopeWidth = scopeWidth;
|
|
61080
|
+
isNearestScopeAmbiguous = false;
|
|
61081
|
+
} else if (scopeWidth === nearestScopeWidth) isNearestScopeAmbiguous = true;
|
|
61082
|
+
}
|
|
61083
|
+
if (isNearestScopeAmbiguous) return null;
|
|
61084
|
+
const declaration = nearestCandidate?.declaration;
|
|
61085
|
+
return declaration !== void 0 && (ts.isVariableDeclaration(declaration) || ts.isFunctionDeclaration(declaration)) ? declaration : null;
|
|
61086
|
+
};
|
|
61087
|
+
const resolveHandlerBinding = (record, binding) => {
|
|
61088
|
+
const declaration = findBindingDeclaration(record, binding);
|
|
61089
|
+
if (declaration === null) return binding.text;
|
|
61090
|
+
const forwardingFunction = getForwardingFunction(declaration);
|
|
61091
|
+
const target = forwardingFunction === null ? null : resolveTransparentTarget(forwardingFunction);
|
|
61092
|
+
return target === null || target === binding.text ? binding.text : target;
|
|
61093
|
+
};
|
|
61094
|
+
const getJsxTagName = (node) => ts.isIdentifier(node.tagName) ? node.tagName.text : null;
|
|
61095
|
+
const getHandlerBinding = (node, propName) => {
|
|
61096
|
+
for (const property of node.attributes.properties) {
|
|
61097
|
+
if (!ts.isJsxAttribute(property) || property.name.getText() !== propName) continue;
|
|
61098
|
+
const initializer = property.initializer;
|
|
61099
|
+
if (initializer === void 0 || !ts.isJsxExpression(initializer)) return null;
|
|
61100
|
+
if (initializer.expression === void 0) return null;
|
|
61101
|
+
const expression = unwrapExpression(initializer.expression);
|
|
61102
|
+
return ts.isIdentifier(expression) ? expression : null;
|
|
61103
|
+
}
|
|
61104
|
+
return null;
|
|
61105
|
+
};
|
|
61106
|
+
const getForwardedHandlerAliases = (rootDirectory, component, propNames, state) => {
|
|
61107
|
+
const cacheKey = `${component.record.filePath}\0${component.name}\0${[...propNames].sort().join("\0")}`;
|
|
61108
|
+
const cachedAliases = state.aliasesByComponent.get(cacheKey);
|
|
61109
|
+
if (cachedAliases !== void 0) return cachedAliases;
|
|
61110
|
+
const bindingsByPropName = /* @__PURE__ */ new Map();
|
|
61111
|
+
const unresolvedPropNames = /* @__PURE__ */ new Set();
|
|
61112
|
+
let callsiteCount = 0;
|
|
61113
|
+
for (const record of listSourceRecords(rootDirectory, state)) {
|
|
61114
|
+
const tagNames = getComponentTagNames(record, component);
|
|
61115
|
+
if (tagNames.size === 0) continue;
|
|
61116
|
+
const visit = (node) => {
|
|
61117
|
+
if (record === component.record && node === component.node) return;
|
|
61118
|
+
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
61119
|
+
const tagName = getJsxTagName(node);
|
|
61120
|
+
if (tagName !== null && tagNames.has(tagName)) {
|
|
61121
|
+
callsiteCount += 1;
|
|
61122
|
+
for (const propName of propNames) {
|
|
61123
|
+
const binding = getHandlerBinding(node, propName);
|
|
61124
|
+
if (binding === null) {
|
|
61125
|
+
unresolvedPropNames.add(propName);
|
|
61126
|
+
continue;
|
|
61127
|
+
}
|
|
61128
|
+
const bindings = bindingsByPropName.get(propName) ?? /* @__PURE__ */ new Set();
|
|
61129
|
+
bindings.add(resolveHandlerBinding(record, binding));
|
|
61130
|
+
bindingsByPropName.set(propName, bindings);
|
|
61131
|
+
}
|
|
61132
|
+
}
|
|
61133
|
+
}
|
|
61134
|
+
ts.forEachChild(node, visit);
|
|
61135
|
+
};
|
|
61136
|
+
visit(record.sourceFile);
|
|
61137
|
+
}
|
|
61138
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
61139
|
+
if (callsiteCount > 0) for (const [propName, bindings] of bindingsByPropName) {
|
|
61140
|
+
if (bindings.size !== 1 || unresolvedPropNames.has(propName)) continue;
|
|
61141
|
+
const [bindingName] = bindings;
|
|
61142
|
+
if (bindingName !== void 0) aliases.set(propName, bindingName);
|
|
61143
|
+
}
|
|
61144
|
+
state.aliasesByComponent.set(cacheKey, aliases);
|
|
61145
|
+
return aliases;
|
|
61146
|
+
};
|
|
61147
|
+
const normalizeForwardedHandlers = (evidence, aliases) => {
|
|
61148
|
+
let normalizedEvidence = evidence;
|
|
61149
|
+
for (const [propName, bindingName] of aliases) normalizedEvidence = normalizedEvidence.replace(new RegExp(`\\{\\s*${propName}\\s*\\}`, "g"), `{${bindingName}}`).replace(new RegExp(`=>\\s*${propName}\\s*\\(`, "g"), `=> ${bindingName}(`);
|
|
61150
|
+
return normalizedEvidence;
|
|
61151
|
+
};
|
|
61152
|
+
const createDiagnosticEvidenceReader = (rootDirectory, options = {}) => {
|
|
61153
|
+
const state = {
|
|
61154
|
+
aliasesByComponent: /* @__PURE__ */ new Map(),
|
|
61155
|
+
recordByFilePath: /* @__PURE__ */ new Map(),
|
|
61156
|
+
sourceRecords: null
|
|
61157
|
+
};
|
|
61158
|
+
return (diagnostic) => {
|
|
61159
|
+
const record = readSourceRecord(rootDirectory, diagnostic.filePath, state);
|
|
61160
|
+
if (record === null || !Number.isInteger(diagnostic.line)) return null;
|
|
61161
|
+
const sourceLines = record.sourceText.split(/\r?\n/);
|
|
61162
|
+
const startLineIndex = Math.max(0, diagnostic.line - 1);
|
|
61163
|
+
const endLineIndex = Math.max(startLineIndex, (diagnostic.endLine ?? diagnostic.line) - 1);
|
|
61164
|
+
const evidence = sourceLines.slice(startLineIndex, endLineIndex + 1).join("\n");
|
|
61165
|
+
if (!options.resolveForwardedHandlers) return evidence;
|
|
61166
|
+
const propNames = new Set(evidence.match(/\bon[A-Z]\w*\b/g) ?? []);
|
|
61167
|
+
if (propNames.size === 0) return evidence;
|
|
61168
|
+
const component = findEnclosingComponent(record, diagnostic);
|
|
61169
|
+
if (component === null) return evidence;
|
|
61170
|
+
return normalizeForwardedHandlers(evidence, getForwardedHandlerAliases(rootDirectory, component, propNames, state));
|
|
61171
|
+
};
|
|
61172
|
+
};
|
|
61173
|
+
//#endregion
|
|
60404
61174
|
//#region src/cli/utils/build-no-score-message.ts
|
|
60405
61175
|
const ENTERPRISE_CONTACT_HINT = `Want something custom to your company? Contact us at ${ENTERPRISE_CONTACT_URL}.`;
|
|
60406
|
-
const buildNoScoreMessage = (isScoreDisabled) => {
|
|
60407
|
-
return `${isScoreDisabled ? "Score disabled by --no-score." : "Score unavailable (could not reach the score API)."} ${ENTERPRISE_CONTACT_HINT}`;
|
|
61176
|
+
const buildNoScoreMessage = (isScoreDisabled, disabledMessage) => {
|
|
61177
|
+
return `${isScoreDisabled ? disabledMessage ?? "Score disabled by --no-score." : "Score unavailable (could not reach the score API)."} ${ENTERPRISE_CONTACT_HINT}`;
|
|
60408
61178
|
};
|
|
60409
61179
|
//#endregion
|
|
60410
61180
|
//#region src/cli/utils/render-agent-guidance.ts
|
|
@@ -61529,40 +62299,47 @@ const buildChangedLineMatcher = (directory, changedLineRanges) => {
|
|
|
61529
62299
|
return diagnosticIntersectsLineRanges(diagnostic, ranges);
|
|
61530
62300
|
};
|
|
61531
62301
|
};
|
|
61532
|
-
const buildIgnoredTags = (userConfig) => {
|
|
62302
|
+
const buildIgnoredTags = (userConfig, includedTags) => {
|
|
61533
62303
|
const tags = /* @__PURE__ */ new Set();
|
|
61534
62304
|
if (userConfig?.ignore?.tags) for (const tag of userConfig.ignore.tags) tags.add(tag);
|
|
62305
|
+
for (const tag of includedTags) tags.delete(tag);
|
|
61535
62306
|
return tags;
|
|
61536
62307
|
};
|
|
61537
|
-
const mergeInspectOptions = (inputOptions, userConfig) =>
|
|
61538
|
-
|
|
61539
|
-
|
|
61540
|
-
|
|
61541
|
-
|
|
61542
|
-
|
|
61543
|
-
|
|
61544
|
-
|
|
61545
|
-
|
|
61546
|
-
|
|
61547
|
-
|
|
61548
|
-
|
|
61549
|
-
|
|
61550
|
-
|
|
61551
|
-
|
|
61552
|
-
|
|
61553
|
-
|
|
61554
|
-
|
|
61555
|
-
|
|
61556
|
-
|
|
61557
|
-
|
|
61558
|
-
|
|
61559
|
-
|
|
61560
|
-
|
|
61561
|
-
|
|
61562
|
-
|
|
61563
|
-
|
|
61564
|
-
|
|
61565
|
-
|
|
62308
|
+
const mergeInspectOptions = (inputOptions, userConfig) => {
|
|
62309
|
+
const includedTags = inputOptions.includedTags ?? /* @__PURE__ */ new Set();
|
|
62310
|
+
return {
|
|
62311
|
+
lint: inputOptions.lint ?? userConfig?.lint ?? true,
|
|
62312
|
+
deadCode: inputOptions.deadCode ?? userConfig?.deadCode ?? true,
|
|
62313
|
+
supplyChain: inputOptions.supplyChain ?? userConfig?.supplyChain?.enabled ?? true,
|
|
62314
|
+
verbose: inputOptions.verbose ?? userConfig?.verbose ?? false,
|
|
62315
|
+
outputDirectory: inputOptions.outputDirectory || null,
|
|
62316
|
+
scoreOnly: inputOptions.scoreOnly ?? false,
|
|
62317
|
+
noScore: inputOptions.noScore ?? userConfig?.noScore ?? false,
|
|
62318
|
+
isCi: inputOptions.isCi ?? false,
|
|
62319
|
+
isCiOrCodingAgentEnvironment: isCiOrCodingAgentEnvironment(),
|
|
62320
|
+
isNonInteractiveEnvironment: isNonInteractiveEnvironment(),
|
|
62321
|
+
silent: inputOptions.silent ?? false,
|
|
62322
|
+
includePaths: inputOptions.includePaths ?? [],
|
|
62323
|
+
customRulesOnly: includedTags.size > 0 ? false : userConfig?.customRulesOnly ?? false,
|
|
62324
|
+
share: userConfig?.share ?? true,
|
|
62325
|
+
respectInlineDisables: inputOptions.respectInlineDisables ?? userConfig?.respectInlineDisables ?? true,
|
|
62326
|
+
warnings: inputOptions.warnings ?? userConfig?.warnings ?? true,
|
|
62327
|
+
categoryFilters: new Set(resolveCliCategories(inputOptions.categoryFilters) ?? []),
|
|
62328
|
+
adoptExistingLintConfig: includedTags.size > 0 ? false : userConfig?.adoptExistingLintConfig ?? true,
|
|
62329
|
+
ignoredTags: buildIgnoredTags(userConfig, includedTags),
|
|
62330
|
+
includedTags,
|
|
62331
|
+
includeTagDefaults: inputOptions.includeTagDefaults ?? false,
|
|
62332
|
+
scoreDisabledMessage: inputOptions.scoreDisabledMessage,
|
|
62333
|
+
outputSurface: inputOptions.outputSurface ?? "cli",
|
|
62334
|
+
suppressRendering: inputOptions.suppressRendering ?? false,
|
|
62335
|
+
concurrentScan: inputOptions.concurrentScan ?? false,
|
|
62336
|
+
concurrency: inputOptions.concurrency,
|
|
62337
|
+
maxDurationMs: inputOptions.maxDurationMs ?? null,
|
|
62338
|
+
baseline: inputOptions.baseline ?? null,
|
|
62339
|
+
changedLineRanges: inputOptions.changedLineRanges ?? null,
|
|
62340
|
+
supplyChainManifestChanged: inputOptions.supplyChainManifestChanged ?? false
|
|
62341
|
+
};
|
|
62342
|
+
};
|
|
61566
62343
|
const deriveScope = (options) => {
|
|
61567
62344
|
if (options.baseline) return "changed";
|
|
61568
62345
|
if (options.changedLineRanges !== null) return "lines";
|
|
@@ -61644,6 +62421,8 @@ const runBaselineComparison = async (params) => {
|
|
|
61644
62421
|
directory: params.directory,
|
|
61645
62422
|
ref: params.baselineRef,
|
|
61646
62423
|
files: params.options.includePaths,
|
|
62424
|
+
baseFiles: params.baseFiles,
|
|
62425
|
+
headFiles: params.headFiles,
|
|
61647
62426
|
tempDirectory
|
|
61648
62427
|
}).catch((error) => {
|
|
61649
62428
|
rmSync(tempDirectory, {
|
|
@@ -61652,7 +62431,24 @@ const runBaselineComparison = async (params) => {
|
|
|
61652
62431
|
});
|
|
61653
62432
|
throw error;
|
|
61654
62433
|
});
|
|
62434
|
+
if (snapshot === null) {
|
|
62435
|
+
rmSync(tempDirectory, {
|
|
62436
|
+
recursive: true,
|
|
62437
|
+
force: true
|
|
62438
|
+
});
|
|
62439
|
+
return null;
|
|
62440
|
+
}
|
|
61655
62441
|
try {
|
|
62442
|
+
if (!snapshot.isComplete) return null;
|
|
62443
|
+
const analyzedHeadFiles = new Set(params.headAnalyzedFiles.map(toForwardSlashes));
|
|
62444
|
+
const baseFiles = new Set(snapshot.baseFiles.map(toForwardSlashes));
|
|
62445
|
+
const trackedHeadFiles = new Set(snapshot.headFiles.map(toForwardSlashes));
|
|
62446
|
+
const expectedHeadFiles = new Set(trackedHeadFiles);
|
|
62447
|
+
for (const filePath of params.options.includePaths) {
|
|
62448
|
+
const normalizedFilePath = toForwardSlashes(filePath);
|
|
62449
|
+
if (!baseFiles.has(normalizedFilePath)) expectedHeadFiles.add(normalizedFilePath);
|
|
62450
|
+
}
|
|
62451
|
+
if (filterSourceFiles([...expectedHeadFiles]).some((filePath) => !analyzedHeadFiles.has(filePath))) return null;
|
|
61656
62452
|
const baseLayers = buildRuntimeLayers({
|
|
61657
62453
|
directory: snapshot.tempDirectory,
|
|
61658
62454
|
hasConfigOverride: true,
|
|
@@ -61668,12 +62464,14 @@ const runBaselineComparison = async (params) => {
|
|
|
61668
62464
|
});
|
|
61669
62465
|
const baseOutput = await runPromise(restoreLegacyThrow(runInspect({
|
|
61670
62466
|
directory: snapshot.tempDirectory,
|
|
61671
|
-
includePaths:
|
|
62467
|
+
includePaths: snapshot.materializedFiles,
|
|
61672
62468
|
customRulesOnly: params.options.customRulesOnly,
|
|
61673
62469
|
respectInlineDisables: params.options.respectInlineDisables,
|
|
61674
62470
|
warnings: params.options.warnings,
|
|
61675
62471
|
adoptExistingLintConfig: params.options.adoptExistingLintConfig,
|
|
61676
62472
|
ignoredTags: params.options.ignoredTags,
|
|
62473
|
+
includedTags: params.options.includedTags,
|
|
62474
|
+
includeTagDefaults: params.options.includeTagDefaults,
|
|
61677
62475
|
nodeBinaryPath: params.resolvedNodeBinaryPath ?? void 0,
|
|
61678
62476
|
runDeadCode: false,
|
|
61679
62477
|
isCi: params.options.isCi,
|
|
@@ -61685,18 +62483,22 @@ const runBaselineComparison = async (params) => {
|
|
|
61685
62483
|
deadlineEpochMs: params.deadlineEpochMs ?? void 0
|
|
61686
62484
|
}, {}).pipe(provide(baseLayers), provideService$2(PerFileLintCacheEnabled, false), provideService$2(SidecarLintCacheEnabled, false), provideService$2(Console, silentConsole))));
|
|
61687
62485
|
if (baseOutput.didLintFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
|
|
62486
|
+
const hasUnscannedUntrackedSourceFiles = filterSourceFiles(snapshot.untrackedFiles.map(toForwardSlashes)).some((filePath) => !analyzedHeadFiles.has(filePath));
|
|
61688
62487
|
const delta = computeDiagnosticDelta({
|
|
61689
62488
|
headDiagnostics: params.headDiagnostics,
|
|
61690
62489
|
baseDiagnostics: baseOutput.diagnostics,
|
|
61691
62490
|
readHeadLine: createSourceLineReader(params.directory),
|
|
61692
|
-
readBaseLine: createSourceLineReader(snapshot.tempDirectory)
|
|
62491
|
+
readBaseLine: createSourceLineReader(snapshot.tempDirectory),
|
|
62492
|
+
readHeadEvidence: createDiagnosticEvidenceReader(params.directory, { resolveForwardedHandlers: true }),
|
|
62493
|
+
readBaseEvidence: createDiagnosticEvidenceReader(snapshot.tempDirectory)
|
|
61693
62494
|
});
|
|
61694
62495
|
return {
|
|
61695
62496
|
displayDiagnostics: delta.newDiagnostics,
|
|
61696
62497
|
baselineDelta: {
|
|
61697
62498
|
baseRef: params.baselineRef,
|
|
61698
|
-
fixedCount: delta.fixedCount,
|
|
61699
|
-
baseTotalCount: baseOutput.diagnostics.length
|
|
62499
|
+
fixedCount: hasUnscannedUntrackedSourceFiles ? 0 : delta.fixedCount,
|
|
62500
|
+
baseTotalCount: baseOutput.diagnostics.length,
|
|
62501
|
+
crossFileMatchCount: delta.crossFileMatchCount
|
|
61700
62502
|
}
|
|
61701
62503
|
};
|
|
61702
62504
|
} finally {
|
|
@@ -61763,6 +62565,8 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
61763
62565
|
warnings: options.warnings,
|
|
61764
62566
|
adoptExistingLintConfig: options.adoptExistingLintConfig,
|
|
61765
62567
|
ignoredTags: options.ignoredTags,
|
|
62568
|
+
includedTags: options.includedTags,
|
|
62569
|
+
includeTagDefaults: options.includeTagDefaults,
|
|
61766
62570
|
nodeBinaryPath: resolvedNodeBinaryPath ?? void 0,
|
|
61767
62571
|
runDeadCode: options.deadCode,
|
|
61768
62572
|
isCi: options.isCi,
|
|
@@ -61803,6 +62607,9 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
61803
62607
|
headDiagnostics: output.diagnostics,
|
|
61804
62608
|
resolvedNodeBinaryPath,
|
|
61805
62609
|
baselineRef: options.baseline.ref,
|
|
62610
|
+
baseFiles: options.baseline.baseFiles,
|
|
62611
|
+
headFiles: options.baseline.headFiles,
|
|
62612
|
+
headAnalyzedFiles: output.analyzedFiles,
|
|
61806
62613
|
deadlineEpochMs
|
|
61807
62614
|
});
|
|
61808
62615
|
if (comparison) {
|
|
@@ -61948,7 +62755,7 @@ const finalizeAndRender = (input) => gen(function* () {
|
|
|
61948
62755
|
securityScanFailed
|
|
61949
62756
|
});
|
|
61950
62757
|
const hasSkippedChecks = skippedChecks.length > 0;
|
|
61951
|
-
const noScoreMessage = buildNoScoreMessage(options.noScore);
|
|
62758
|
+
const noScoreMessage = buildNoScoreMessage(options.noScore, options.scoreDisabledMessage);
|
|
61952
62759
|
const buildResult = () => ({
|
|
61953
62760
|
diagnostics: [...diagnostics],
|
|
61954
62761
|
score,
|
|
@@ -64053,21 +64860,25 @@ const resolveParallelFlag = (parallel) => parallel === false ? 1 : void 0;
|
|
|
64053
64860
|
*/
|
|
64054
64861
|
const resolveCliInspectOptions = (flags, userConfig) => {
|
|
64055
64862
|
const wantsWarningGate = pickBlockingLevel(flags, userConfig) === "warning";
|
|
64863
|
+
const isDesignScan = flags.design === true;
|
|
64056
64864
|
return {
|
|
64057
64865
|
lint: flags.lint,
|
|
64058
|
-
deadCode: flags.deadCode,
|
|
64059
|
-
supplyChain: flags.supplyChain,
|
|
64866
|
+
deadCode: isDesignScan ? false : flags.deadCode,
|
|
64867
|
+
supplyChain: isDesignScan ? false : flags.supplyChain,
|
|
64060
64868
|
verbose: flags.verbose,
|
|
64061
64869
|
outputDirectory: flags.outputDir,
|
|
64062
64870
|
respectInlineDisables: flags.respectInlineDisables === false ? false : void 0,
|
|
64063
64871
|
warnings: wantsWarningGate ? true : flags.warnings,
|
|
64064
64872
|
scoreOnly: flags.score === true,
|
|
64065
|
-
noScore: flags.score === false || flags.telemetry === false ? true : void 0,
|
|
64873
|
+
noScore: isDesignScan || flags.score === false || flags.telemetry === false ? true : void 0,
|
|
64066
64874
|
isCi: isCiEnvironment(),
|
|
64067
64875
|
silent: Boolean(flags.json),
|
|
64068
64876
|
concurrency: resolveParallelFlag(flags.parallel),
|
|
64069
64877
|
maxDurationMs: resolveMaxDurationFlag(flags.maxDuration),
|
|
64070
|
-
categoryFilters: resolveCliCategories(flags.category)
|
|
64878
|
+
categoryFilters: resolveCliCategories(flags.category),
|
|
64879
|
+
includedTags: isDesignScan ? new Set(["design"]) : void 0,
|
|
64880
|
+
includeTagDefaults: isDesignScan ? true : void 0,
|
|
64881
|
+
scoreDisabledMessage: isDesignScan ? "Design scans do not affect the React health score." : void 0
|
|
64071
64882
|
};
|
|
64072
64883
|
};
|
|
64073
64884
|
//#endregion
|
|
@@ -64087,20 +64898,25 @@ const resolveProjectRelativeDirectory = (rootDirectory, projectDirectory) => {
|
|
|
64087
64898
|
return relativeProjectDirectory;
|
|
64088
64899
|
};
|
|
64089
64900
|
//#endregion
|
|
64090
|
-
//#region src/cli/utils/resolve-project-
|
|
64091
|
-
const
|
|
64901
|
+
//#region src/cli/utils/resolve-project-source-file-paths.ts
|
|
64902
|
+
const resolveProjectSourceFilePaths = (rootDirectory, projectDirectory, filePaths) => {
|
|
64092
64903
|
const relativeProjectDirectory = resolveProjectRelativeDirectory(rootDirectory, projectDirectory);
|
|
64093
64904
|
if (relativeProjectDirectory === null) return [];
|
|
64094
|
-
const
|
|
64095
|
-
if (relativeProjectDirectory.length === 0) return
|
|
64905
|
+
const sourceFilePaths = filterSourceFiles([...filePaths]);
|
|
64906
|
+
if (relativeProjectDirectory.length === 0) return sourceFilePaths;
|
|
64096
64907
|
const projectPrefix = `${relativeProjectDirectory}/`;
|
|
64097
|
-
return
|
|
64908
|
+
return sourceFilePaths.flatMap((filePath) => {
|
|
64098
64909
|
const normalizedFilePath = toForwardSlashes(filePath);
|
|
64099
64910
|
if (!normalizedFilePath.startsWith(projectPrefix)) return [];
|
|
64100
64911
|
const projectRelativePath = normalizedFilePath.slice(projectPrefix.length);
|
|
64101
64912
|
return projectRelativePath.length > 0 ? [projectRelativePath] : [];
|
|
64102
64913
|
});
|
|
64103
64914
|
};
|
|
64915
|
+
//#endregion
|
|
64916
|
+
//#region src/cli/utils/resolve-project-diff-include-paths.ts
|
|
64917
|
+
const resolveProjectDiffIncludePaths = (rootDirectory, projectDirectory, diffInfo) => {
|
|
64918
|
+
return resolveProjectSourceFilePaths(rootDirectory, projectDirectory, diffInfo.changedFiles);
|
|
64919
|
+
};
|
|
64104
64920
|
/**
|
|
64105
64921
|
* Re-keys repo-relative changed line ranges (from `getChangedLineRanges`) to
|
|
64106
64922
|
* the scanned project, mirroring `resolveProjectDiffIncludePaths`'s boundary
|
|
@@ -64480,7 +65296,7 @@ const maybeMigrateLegacyConfig = async (requestedDirectory, { isQuiet, isStaged
|
|
|
64480
65296
|
if (!(!isQuiet && !isStaged && process.stdout.isTTY === true && !isCiOrCodingAgentEnvironment())) return;
|
|
64481
65297
|
await runProjectMigrations(requestedDirectory);
|
|
64482
65298
|
};
|
|
64483
|
-
const inspectAction = async (directory, flags) => {
|
|
65299
|
+
const inspectAction = async (directory, flags, invocationCommand = "inspect") => {
|
|
64484
65300
|
const isScoreOnly = Boolean(flags.score);
|
|
64485
65301
|
const isJsonMode = Boolean(flags.json);
|
|
64486
65302
|
const isQuiet = isScoreOnly || isJsonMode;
|
|
@@ -64494,7 +65310,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
64494
65310
|
});
|
|
64495
65311
|
if (flags.jsonOut) recordCount(METRIC.jsonOutUsed, 1);
|
|
64496
65312
|
}
|
|
64497
|
-
recordCount(METRIC.cliInvoked, 1, { command:
|
|
65313
|
+
recordCount(METRIC.cliInvoked, 1, { command: invocationCommand });
|
|
64498
65314
|
try {
|
|
64499
65315
|
validateModeFlags(flags);
|
|
64500
65316
|
if (flags.staged) setJsonReportMode("staged");
|
|
@@ -64639,6 +65455,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
64639
65455
|
const isDiffMode = scope !== "full";
|
|
64640
65456
|
const comparisonBaseRef = isDiffMode && diffInfo && !diffInfo.isCurrentChanges ? diffInfo.baseSha ? await resolveMergeBaseRef(resolvedDirectory, diffInfo.baseSha) : diffInfo.diffBaseRef ?? await resolveMergeBaseRef(resolvedDirectory, diffInfo.baseBranch) : null;
|
|
64641
65457
|
const baselineRef = scope === "changed" ? comparisonBaseRef : null;
|
|
65458
|
+
const baselineDiffPlan = baselineRef === null ? null : await getBaselineDiffPlan(resolvedDirectory, baselineRef);
|
|
64642
65459
|
const linesBaseRef = diffInfo?.isCurrentChanges ? "HEAD" : comparisonBaseRef;
|
|
64643
65460
|
const changedLineRanges = scope === "lines" && diffInfo !== null && (diffInfo.isCurrentChanges || linesBaseRef !== null) && diffInfo !== null ? await getChangedLineRanges({
|
|
64644
65461
|
directory: resolvedDirectory,
|
|
@@ -64669,10 +65486,13 @@ const inspectAction = async (directory, flags) => {
|
|
|
64669
65486
|
const supplyChainEnabled = flags.supplyChain ?? projectConfig?.supplyChain?.enabled !== false;
|
|
64670
65487
|
let includePaths;
|
|
64671
65488
|
let supplyChainManifestChanged = false;
|
|
65489
|
+
const projectBaselineBaseFiles = baselineDiffPlan === null ? null : resolveProjectSourceFilePaths(resolvedDirectory, scanDirectory, baselineDiffPlan.baseFiles);
|
|
65490
|
+
const projectBaselineHeadFiles = baselineDiffPlan === null ? null : resolveProjectSourceFilePaths(resolvedDirectory, scanDirectory, baselineDiffPlan.headFiles);
|
|
64672
65491
|
if (isDiffMode) {
|
|
64673
65492
|
const changedSourceFiles = diffInfo === null ? [] : resolveProjectDiffIncludePaths(resolvedDirectory, scanDirectory, diffInfo);
|
|
64674
65493
|
supplyChainManifestChanged = supplyChainEnabled && diffInfo !== null && projectManifestChanged(resolvedDirectory, scanDirectory, diffInfo);
|
|
64675
|
-
|
|
65494
|
+
const hasBaselineOnlyFiles = (projectBaselineBaseFiles?.length ?? 0) > 0;
|
|
65495
|
+
if (changedSourceFiles.length === 0 && !supplyChainManifestChanged && !hasBaselineOnlyFiles) {
|
|
64676
65496
|
if (!isQuiet) {
|
|
64677
65497
|
cliLogger.dim(`No changed source files in ${scanDirectory}, skipping.`);
|
|
64678
65498
|
cliLogger.break();
|
|
@@ -64680,6 +65500,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
64680
65500
|
return null;
|
|
64681
65501
|
}
|
|
64682
65502
|
includePaths = [...changedSourceFiles];
|
|
65503
|
+
if (includePaths.length === 0 && hasBaselineOnlyFiles) includePaths.push(...projectBaselineBaseFiles ?? []);
|
|
64683
65504
|
if (supplyChainManifestChanged) includePaths.push("package.json");
|
|
64684
65505
|
}
|
|
64685
65506
|
if (!isQuiet && !isMultiProject) cliLogger.dim(" ");
|
|
@@ -64691,7 +65512,11 @@ const inspectAction = async (directory, flags) => {
|
|
|
64691
65512
|
configSourceDirectory: projectConfigSourceDirectory ?? void 0,
|
|
64692
65513
|
suppressRendering: isMultiProject,
|
|
64693
65514
|
concurrentScan: isMultiProject,
|
|
64694
|
-
baseline: baselineRef
|
|
65515
|
+
baseline: baselineRef !== null && projectBaselineBaseFiles !== null && projectBaselineHeadFiles !== null ? {
|
|
65516
|
+
ref: baselineRef,
|
|
65517
|
+
baseFiles: projectBaselineBaseFiles,
|
|
65518
|
+
headFiles: projectBaselineHeadFiles
|
|
65519
|
+
} : void 0,
|
|
64695
65520
|
changedLineRanges: scope === "lines" && changedLineRanges !== null ? resolveProjectChangedLineRanges(resolvedDirectory, scanDirectory, changedLineRanges) : void 0,
|
|
64696
65521
|
supplyChainManifestChanged
|
|
64697
65522
|
});
|
|
@@ -64797,6 +65622,13 @@ const inspectAction = async (directory, flags) => {
|
|
|
64797
65622
|
}
|
|
64798
65623
|
};
|
|
64799
65624
|
//#endregion
|
|
65625
|
+
//#region src/cli/commands/design.ts
|
|
65626
|
+
const designAction = async (directory, flags) => inspectAction(directory, {
|
|
65627
|
+
...flags,
|
|
65628
|
+
design: true,
|
|
65629
|
+
lint: flags.lint ?? true
|
|
65630
|
+
}, "design");
|
|
65631
|
+
//#endregion
|
|
64800
65632
|
//#region src/cli/commands/install.ts
|
|
64801
65633
|
const installAction = async (options, command) => {
|
|
64802
65634
|
recordCount(METRIC.cliInvoked, 1, { command: "install" });
|
|
@@ -65633,6 +66465,7 @@ const INSTALL_FLAG_SPEC = {
|
|
|
65633
66465
|
shortOptionsWithRequiredValues: new Set(["-c"])
|
|
65634
66466
|
};
|
|
65635
66467
|
const COMMAND_FLAG_SPECS = new Map([
|
|
66468
|
+
["design", ROOT_FLAG_SPEC],
|
|
65636
66469
|
["install", INSTALL_FLAG_SPEC],
|
|
65637
66470
|
["setup", INSTALL_FLAG_SPEC],
|
|
65638
66471
|
["version", {
|
|
@@ -65789,6 +66622,7 @@ ${formatExampleLines([
|
|
|
65789
66622
|
["react-doctor --scope changed --base main", "scan only new issues vs. main"],
|
|
65790
66623
|
["react-doctor --project modules/a,modules/b", "score each module separately (names or paths)"],
|
|
65791
66624
|
["react-doctor --staged", "scan staged files (pre-commit hook)"],
|
|
66625
|
+
["react-doctor design", "run the focused UI design audit"],
|
|
65792
66626
|
["react-doctor --category Security", "show only one diagnostic category"],
|
|
65793
66627
|
["react-doctor --blocking warning", "fail CI on warnings too (default: error)"],
|
|
65794
66628
|
["react-doctor --json > report.json", "write a machine-readable report"],
|
|
@@ -65822,6 +66656,20 @@ ${highlighter.dim("Managing CI:")}
|
|
|
65822
66656
|
${highlighter.dim("Learn more:")}
|
|
65823
66657
|
${highlighter.info(CANONICAL_GITHUB_URL)}
|
|
65824
66658
|
`;
|
|
66659
|
+
const renderDesignHelpEpilog = () => `
|
|
66660
|
+
${highlighter.dim("Examples:")}
|
|
66661
|
+
${formatExampleLines([
|
|
66662
|
+
["react-doctor design", "audit UI design in the current project"],
|
|
66663
|
+
["react-doctor design ./apps/web", "audit one application"],
|
|
66664
|
+
["react-doctor design --verbose", "show every design finding"],
|
|
66665
|
+
["react-doctor design --json", "write a design-only JSON report"]
|
|
66666
|
+
])}
|
|
66667
|
+
|
|
66668
|
+
${highlighter.dim("Scope:")}
|
|
66669
|
+
Runs only rules tagged ${highlighter.info("design")}, including focused rules that stay opt-in during a general health scan.
|
|
66670
|
+
Dead-code, supply-chain, external lint-config, custom-plugin, and health-score passes are skipped.
|
|
66671
|
+
Standard scan flags such as ${highlighter.info("--scope")}, ${highlighter.info("--project")}, ${highlighter.info("--verbose")}, and ${highlighter.info("--json")} still work.
|
|
66672
|
+
`;
|
|
65825
66673
|
const renderCiHelpEpilog = () => `
|
|
65826
66674
|
${highlighter.dim("Examples:")}
|
|
65827
66675
|
${formatExampleLines([
|
|
@@ -65843,6 +66691,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
65843
66691
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
65844
66692
|
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much supported JS/TS source to scan/report: full (default), files, changed (only new issues vs base), or lines (issues whose source spans touch changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").option("--include-untracked", "with --scope files/changed/lines, also scan ordinary untracked files (respects .gitignore)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
65845
66693
|
program.action(inspectAction);
|
|
66694
|
+
program.command("design [directory]").description("Run only the focused UI design diagnostics").addHelpText("after", renderDesignHelpEpilog).action((directory, _options, command) => designAction(directory ?? ".", command.optsWithGlobals()));
|
|
65846
66695
|
program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
|
|
65847
66696
|
program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install native non-blocking agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(installAction);
|
|
65848
66697
|
const providerOption = ["--provider <name>", "ci provider: github-actions or gitlab-ci (auto-detected by default)"];
|
|
@@ -65891,4 +66740,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
65891
66740
|
export {};
|
|
65892
66741
|
|
|
65893
66742
|
//# sourceMappingURL=cli.js.map
|
|
65894
|
-
//# debugId=
|
|
66743
|
+
//# debugId=a9af63ed-7b82-56d3-8549-274ab7f23579
|