react-doctor 0.6.3-dev.ced746f → 0.6.3-dev.ea9a775
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +313 -461
- package/dist/index.js +30 -117
- package/dist/lsp.js +35 -120
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="d9c72b21-3e94-5923-b692-077f137a0078")}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";
|
|
@@ -36137,7 +36137,6 @@ const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
|
36137
36137
|
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
36138
36138
|
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
36139
36139
|
const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
36140
|
-
const SUPPLY_CHAIN_CACHE_TTL_MS = 864e5;
|
|
36141
36140
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
36142
36141
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
36143
36142
|
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
|
|
@@ -40609,37 +40608,22 @@ const failOpenReadJson = (filePath, fallback) => {
|
|
|
40609
40608
|
return fallback;
|
|
40610
40609
|
}
|
|
40611
40610
|
};
|
|
40612
|
-
const hashFileContents = (filePath) => {
|
|
40613
|
-
try {
|
|
40614
|
-
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
40615
|
-
} catch {
|
|
40616
|
-
return null;
|
|
40617
|
-
}
|
|
40618
|
-
};
|
|
40619
40611
|
const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
|
|
40620
40612
|
const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
|
|
40621
40613
|
const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
|
|
40622
40614
|
const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
|
|
40623
40615
|
const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
|
|
40624
|
-
|
|
40625
|
-
|
|
40626
|
-
* `/`-separated path. Taken BEFORE the (long) analysis so a stored result is
|
|
40627
|
-
* verified against the tree it started from.
|
|
40628
|
-
*/
|
|
40629
|
-
const collectAnalyzedFileStats = (rootDirectory) => {
|
|
40630
|
-
const statByRelativePath = /* @__PURE__ */ new Map();
|
|
40616
|
+
const collectAnalyzedFileFingerprints = (rootDirectory) => {
|
|
40617
|
+
const fingerprints = [];
|
|
40631
40618
|
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
40632
40619
|
if (!isFingerprintedFile(name)) continue;
|
|
40633
40620
|
try {
|
|
40634
40621
|
const fileStat = NFS.statSync(absolutePath);
|
|
40635
40622
|
const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
|
|
40636
|
-
|
|
40637
|
-
mtimeMs: fileStat.mtimeMs,
|
|
40638
|
-
size: fileStat.size
|
|
40639
|
-
});
|
|
40623
|
+
fingerprints.push(`${relativePath}:${fileStat.mtimeMs}:${fileStat.size}`);
|
|
40640
40624
|
} catch {}
|
|
40641
40625
|
}
|
|
40642
|
-
return
|
|
40626
|
+
return fingerprints.sort();
|
|
40643
40627
|
};
|
|
40644
40628
|
const bundledRequire$2 = createRequire(import.meta.url);
|
|
40645
40629
|
const resolveDeslopVersion = () => {
|
|
@@ -40651,13 +40635,14 @@ const resolveDeslopVersion = () => {
|
|
|
40651
40635
|
}
|
|
40652
40636
|
};
|
|
40653
40637
|
const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
|
|
40654
|
-
schemaVersion:
|
|
40638
|
+
schemaVersion: 1,
|
|
40655
40639
|
coreVersion: input.coreVersion,
|
|
40656
40640
|
deslopVersion: resolveDeslopVersion(),
|
|
40657
40641
|
deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
|
|
40658
40642
|
entryPatterns: input.entryPatterns,
|
|
40659
40643
|
ignorePatterns: input.ignorePatterns,
|
|
40660
|
-
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
|
|
40644
|
+
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/"),
|
|
40645
|
+
files: collectAnalyzedFileFingerprints(input.rootDirectory)
|
|
40661
40646
|
})).digest("hex");
|
|
40662
40647
|
const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
|
|
40663
40648
|
const decodeCachedDiagnostics = (raw) => {
|
|
@@ -40668,68 +40653,16 @@ const decodeCachedDiagnostics = (raw) => {
|
|
|
40668
40653
|
return null;
|
|
40669
40654
|
}
|
|
40670
40655
|
};
|
|
40671
|
-
const
|
|
40672
|
-
const
|
|
40673
|
-
|
|
40674
|
-
|
|
40675
|
-
if (persisted === null || !isRecord$1(persisted) || persisted.version !== 2 || persisted.key !== input.cacheKey || !isRecord$1(persisted.files) || !Array.isArray(persisted.diagnostics)) return null;
|
|
40676
|
-
const storedFileEntries = Object.entries(persisted.files);
|
|
40677
|
-
if (storedFileEntries.length !== input.currentFileStats.size) return null;
|
|
40678
|
-
const repairedFiles = {};
|
|
40679
|
-
let repairedCount = 0;
|
|
40680
|
-
for (const [relativePath, storedIdentity] of storedFileEntries) {
|
|
40681
|
-
if (!isPersistedFileIdentity(storedIdentity)) return null;
|
|
40682
|
-
const currentStat = input.currentFileStats.get(relativePath);
|
|
40683
|
-
if (currentStat === void 0) return null;
|
|
40684
|
-
const [storedMtimeMs, storedSize, storedContentHash] = storedIdentity;
|
|
40685
|
-
if (currentStat.mtimeMs === storedMtimeMs && currentStat.size === storedSize) {
|
|
40686
|
-
repairedFiles[relativePath] = storedIdentity;
|
|
40687
|
-
continue;
|
|
40688
|
-
}
|
|
40689
|
-
if (currentStat.size !== storedSize) return null;
|
|
40690
|
-
const currentContentHash = hashFileContents(Path.join(input.rootDirectory, relativePath));
|
|
40691
|
-
if (currentContentHash === null || currentContentHash !== storedContentHash) return null;
|
|
40692
|
-
repairedFiles[relativePath] = [
|
|
40693
|
-
currentStat.mtimeMs,
|
|
40694
|
-
currentStat.size,
|
|
40695
|
-
storedContentHash
|
|
40696
|
-
];
|
|
40697
|
-
repairedCount += 1;
|
|
40698
|
-
}
|
|
40699
|
-
const diagnostics = decodeCachedDiagnostics(persisted.diagnostics);
|
|
40700
|
-
if (diagnostics === null) return null;
|
|
40701
|
-
if (repairedCount > 0) atomicWriteJson(cacheFilePath, {
|
|
40702
|
-
version: 2,
|
|
40703
|
-
key: input.cacheKey,
|
|
40704
|
-
files: repairedFiles,
|
|
40705
|
-
diagnostics: persisted.diagnostics
|
|
40706
|
-
});
|
|
40707
|
-
return diagnostics;
|
|
40656
|
+
const lookupDeadCodeResultCache = (cacheDirectory, cacheKey) => {
|
|
40657
|
+
const persisted = failOpenReadJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), null);
|
|
40658
|
+
if (persisted === null || !isRecord$1(persisted) || persisted.version !== 1 || persisted.key !== cacheKey || !Array.isArray(persisted.diagnostics)) return null;
|
|
40659
|
+
return decodeCachedDiagnostics(persisted.diagnostics);
|
|
40708
40660
|
};
|
|
40709
|
-
const storeDeadCodeResultCache = (
|
|
40710
|
-
|
|
40711
|
-
|
|
40712
|
-
|
|
40713
|
-
|
|
40714
|
-
if (contentHash === null) return;
|
|
40715
|
-
let currentStat;
|
|
40716
|
-
try {
|
|
40717
|
-
currentStat = NFS.statSync(absolutePath);
|
|
40718
|
-
} catch {
|
|
40719
|
-
return;
|
|
40720
|
-
}
|
|
40721
|
-
if (currentStat.mtimeMs !== snapshotStat.mtimeMs || currentStat.size !== snapshotStat.size) return;
|
|
40722
|
-
persistedFiles[relativePath] = [
|
|
40723
|
-
snapshotStat.mtimeMs,
|
|
40724
|
-
snapshotStat.size,
|
|
40725
|
-
contentHash
|
|
40726
|
-
];
|
|
40727
|
-
}
|
|
40728
|
-
atomicWriteJson(Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
|
|
40729
|
-
version: 2,
|
|
40730
|
-
key: input.cacheKey,
|
|
40731
|
-
files: persistedFiles,
|
|
40732
|
-
diagnostics: input.diagnostics
|
|
40661
|
+
const storeDeadCodeResultCache = (cacheDirectory, cacheKey, diagnostics) => {
|
|
40662
|
+
atomicWriteJson(Path.join(cacheDirectory, DEAD_CODE_CACHE_FILENAME), {
|
|
40663
|
+
version: 1,
|
|
40664
|
+
key: cacheKey,
|
|
40665
|
+
diagnostics
|
|
40733
40666
|
});
|
|
40734
40667
|
};
|
|
40735
40668
|
const readSystemFacts = () => ({
|
|
@@ -41139,22 +41072,16 @@ const checkDeadCode = async (options) => {
|
|
|
41139
41072
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
41140
41073
|
const tsConfigPath = resolveTsConfigPath(rootDirectory);
|
|
41141
41074
|
const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
|
|
41142
|
-
const
|
|
41143
|
-
const cacheKey = fileStatsSnapshot === null ? null : computeDeadCodeCacheKey({
|
|
41075
|
+
const cacheKey = options.cacheEnabled ? computeDeadCodeCacheKey({
|
|
41144
41076
|
rootDirectory,
|
|
41145
41077
|
entryPatterns,
|
|
41146
41078
|
ignorePatterns,
|
|
41147
41079
|
tsConfigPath,
|
|
41148
41080
|
deslopJsModuleSpecifier,
|
|
41149
41081
|
coreVersion: CORE_PACKAGE_VERSION
|
|
41150
|
-
});
|
|
41151
|
-
if (cacheKey !== null
|
|
41152
|
-
const cachedDiagnostics = lookupDeadCodeResultCache(
|
|
41153
|
-
cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
|
|
41154
|
-
cacheKey,
|
|
41155
|
-
rootDirectory,
|
|
41156
|
-
currentFileStats: fileStatsSnapshot
|
|
41157
|
-
});
|
|
41082
|
+
}) : null;
|
|
41083
|
+
if (cacheKey !== null) {
|
|
41084
|
+
const cachedDiagnostics = lookupDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey);
|
|
41158
41085
|
options.onCacheOutcome?.(cachedDiagnostics !== null);
|
|
41159
41086
|
if (cachedDiagnostics !== null) return [...cachedDiagnostics];
|
|
41160
41087
|
}
|
|
@@ -41228,13 +41155,7 @@ const checkDeadCode = async (options) => {
|
|
|
41228
41155
|
category: DEAD_CODE_CATEGORY
|
|
41229
41156
|
});
|
|
41230
41157
|
}
|
|
41231
|
-
if (cacheKey !== null
|
|
41232
|
-
cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
|
|
41233
|
-
cacheKey,
|
|
41234
|
-
rootDirectory,
|
|
41235
|
-
snapshotFileStats: fileStatsSnapshot,
|
|
41236
|
-
diagnostics
|
|
41237
|
-
});
|
|
41158
|
+
if (cacheKey !== null) storeDeadCodeResultCache(resolveReactDoctorCacheDir(rootDirectory), cacheKey, diagnostics);
|
|
41238
41159
|
return diagnostics;
|
|
41239
41160
|
};
|
|
41240
41161
|
const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
|
|
@@ -42330,6 +42251,13 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
42330
42251
|
}
|
|
42331
42252
|
};
|
|
42332
42253
|
};
|
|
42254
|
+
const hashFileContents = (filePath) => {
|
|
42255
|
+
try {
|
|
42256
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
42257
|
+
} catch {
|
|
42258
|
+
return null;
|
|
42259
|
+
}
|
|
42260
|
+
};
|
|
42333
42261
|
const ABSENT_CONTENT_ANSWER = "absent";
|
|
42334
42262
|
/**
|
|
42335
42263
|
* Answers dependency probes for the sidecar lint cache — used both when
|
|
@@ -44470,20 +44398,6 @@ const writeCachedSocketBody = (cacheFile, body) => {
|
|
|
44470
44398
|
}));
|
|
44471
44399
|
} catch {}
|
|
44472
44400
|
};
|
|
44473
|
-
const pruneExpiredSocketCache = (cacheDirectory) => {
|
|
44474
|
-
try {
|
|
44475
|
-
const supplyChainCacheDirectory = Path.join(cacheDirectory, SUPPLY_CHAIN_CACHE_SUBDIR);
|
|
44476
|
-
const expiryThresholdMs = Date.now() - SUPPLY_CHAIN_CACHE_TTL_MS;
|
|
44477
|
-
for (const entryName of NFS.readdirSync(supplyChainCacheDirectory)) {
|
|
44478
|
-
const entryPath = Path.join(supplyChainCacheDirectory, entryName);
|
|
44479
|
-
try {
|
|
44480
|
-
if (NFS.statSync(entryPath).mtimeMs < expiryThresholdMs) NFS.rmSync(entryPath);
|
|
44481
|
-
} catch {
|
|
44482
|
-
continue;
|
|
44483
|
-
}
|
|
44484
|
-
}
|
|
44485
|
-
} catch {}
|
|
44486
|
-
};
|
|
44487
44401
|
const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
|
|
44488
44402
|
const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
|
|
44489
44403
|
if (cacheFile !== null) {
|
|
@@ -44611,7 +44525,6 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
44611
44525
|
const dependencies = collectDependenciesToScore(readPackageJson$1(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
44612
44526
|
if (dependencies.length === 0) return [];
|
|
44613
44527
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
44614
|
-
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
44615
44528
|
const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
|
|
44616
44529
|
const diagnostics = [];
|
|
44617
44530
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
@@ -45497,9 +45410,11 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
45497
45410
|
const parts = [`v=${version}`];
|
|
45498
45411
|
let directory = projectDirectory;
|
|
45499
45412
|
for (;;) {
|
|
45500
|
-
for (const filename of CONFIG_FINGERPRINT_FILENAMES) {
|
|
45501
|
-
const
|
|
45502
|
-
|
|
45413
|
+
for (const filename of CONFIG_FINGERPRINT_FILENAMES) try {
|
|
45414
|
+
const stat = fs.statSync(path.join(directory, filename));
|
|
45415
|
+
parts.push(`${directory}/${filename}=${stat.mtimeMs}:${stat.size}`);
|
|
45416
|
+
} catch {
|
|
45417
|
+
continue;
|
|
45503
45418
|
}
|
|
45504
45419
|
const parent = path.dirname(directory);
|
|
45505
45420
|
if (parent === directory) break;
|
|
@@ -45795,7 +45710,6 @@ const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.
|
|
|
45795
45710
|
const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
|
|
45796
45711
|
const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
|
|
45797
45712
|
const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
|
|
45798
|
-
const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
|
|
45799
45713
|
const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
45800
45714
|
const GH_DEFAULT_BRANCH_PROBE_TIMEOUT_MS = 5e3;
|
|
45801
45715
|
const CI_TRUST_COMPANIES = "PayPal, Rippling, and Alibaba";
|
|
@@ -45887,7 +45801,7 @@ const makeNoopConsole = () => ({
|
|
|
45887
45801
|
});
|
|
45888
45802
|
//#endregion
|
|
45889
45803
|
//#region src/cli/utils/version.ts
|
|
45890
|
-
const VERSION = "0.6.3-dev.
|
|
45804
|
+
const VERSION = "0.6.3-dev.ea9a775";
|
|
45891
45805
|
//#endregion
|
|
45892
45806
|
//#region src/cli/utils/json-mode.ts
|
|
45893
45807
|
let context = null;
|
|
@@ -46256,13 +46170,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
46256
46170
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
46257
46171
|
* standard `SENTRY_RELEASE` override.
|
|
46258
46172
|
*/
|
|
46259
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.3-dev.
|
|
46173
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.3-dev.ea9a775`;
|
|
46260
46174
|
/**
|
|
46261
46175
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
46262
46176
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
46263
46177
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
46264
46178
|
*/
|
|
46265
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.3-dev.
|
|
46179
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.3-dev.ea9a775") ? "development" : "production");
|
|
46266
46180
|
/**
|
|
46267
46181
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
46268
46182
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -58340,276 +58254,6 @@ const buildRuleBlastRadii = (diagnostics) => buildSortedRuleGroups(diagnostics).
|
|
|
58340
58254
|
})).toSorted((left, right) => right.fileCount - left.fileCount);
|
|
58341
58255
|
const findMigrationScaleBuckets = (diagnostics) => buildRuleBlastRadii(diagnostics).filter((bucket) => bucket.fileCount >= 40);
|
|
58342
58256
|
//#endregion
|
|
58343
|
-
//#region src/cli/utils/scan-result-cache.ts
|
|
58344
|
-
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
58345
|
-
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
58346
|
-
"oxlint/package.json",
|
|
58347
|
-
"oxlint-plugin-react-doctor/package.json",
|
|
58348
|
-
"deslop-js/package.json",
|
|
58349
|
-
"eslint-plugin-react-hooks/package.json"
|
|
58350
|
-
];
|
|
58351
|
-
const bundledRequire = createRequire(import.meta.url);
|
|
58352
|
-
const normalizeForStableJson = (value) => {
|
|
58353
|
-
if (value === null) return null;
|
|
58354
|
-
if (value === void 0) return void 0;
|
|
58355
|
-
const valueType = typeof value;
|
|
58356
|
-
if (valueType === "string" || valueType === "number" || valueType === "boolean") return value;
|
|
58357
|
-
if (valueType === "bigint" || valueType === "function" || valueType === "symbol") throw new Error("Unsupported cache key value");
|
|
58358
|
-
if (Array.isArray(value)) return value.map((item) => {
|
|
58359
|
-
const normalized = normalizeForStableJson(item);
|
|
58360
|
-
return normalized === void 0 ? null : normalized;
|
|
58361
|
-
});
|
|
58362
|
-
const prototype = Object.getPrototypeOf(value);
|
|
58363
|
-
if (prototype !== Object.prototype && prototype !== null) throw new Error("Unsupported cache key object");
|
|
58364
|
-
if (!isRecord(value)) throw new Error("Unsupported cache key object");
|
|
58365
|
-
const normalizedRecord = {};
|
|
58366
|
-
for (const key of Object.keys(value).sort()) {
|
|
58367
|
-
const normalized = normalizeForStableJson(value[key]);
|
|
58368
|
-
if (normalized !== void 0) normalizedRecord[key] = normalized;
|
|
58369
|
-
}
|
|
58370
|
-
return normalizedRecord;
|
|
58371
|
-
};
|
|
58372
|
-
const stringifyStableJson = (value) => {
|
|
58373
|
-
try {
|
|
58374
|
-
return JSON.stringify(normalizeForStableJson(value));
|
|
58375
|
-
} catch {
|
|
58376
|
-
return null;
|
|
58377
|
-
}
|
|
58378
|
-
};
|
|
58379
|
-
const hashString = (value) => crypto.createHash("sha1").update(value).digest("hex");
|
|
58380
|
-
const readHeadSha = (projectDirectory) => runGit(projectDirectory, ["rev-parse", "HEAD"]);
|
|
58381
|
-
const parseWorktreeStatusRecords = (statusOutput) => {
|
|
58382
|
-
const entries = [];
|
|
58383
|
-
const tokens = statusOutput.split("\0");
|
|
58384
|
-
for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
|
|
58385
|
-
let record = tokens[tokenIndex];
|
|
58386
|
-
if (record.length === 0) continue;
|
|
58387
|
-
if (record.length < 3 || record[2] !== " ") {
|
|
58388
|
-
if (tokenIndex !== 0) return null;
|
|
58389
|
-
record = ` ${record}`;
|
|
58390
|
-
}
|
|
58391
|
-
if (record.length < 4 || record[2] !== " ") return null;
|
|
58392
|
-
const statusCode = record.slice(0, 2);
|
|
58393
|
-
const recordPath = record.slice(3);
|
|
58394
|
-
if (statusCode.includes("R") || statusCode.includes("C")) {
|
|
58395
|
-
tokenIndex += 1;
|
|
58396
|
-
const originPath = tokens[tokenIndex];
|
|
58397
|
-
if (originPath === void 0 || originPath.length === 0) return null;
|
|
58398
|
-
entries.push({
|
|
58399
|
-
statusCode,
|
|
58400
|
-
path: recordPath,
|
|
58401
|
-
originPath
|
|
58402
|
-
});
|
|
58403
|
-
continue;
|
|
58404
|
-
}
|
|
58405
|
-
entries.push({
|
|
58406
|
-
statusCode,
|
|
58407
|
-
path: recordPath
|
|
58408
|
-
});
|
|
58409
|
-
}
|
|
58410
|
-
return entries;
|
|
58411
|
-
};
|
|
58412
|
-
const buildDirtyPathContentFingerprint = (absolutePath, statusCode) => {
|
|
58413
|
-
let stats;
|
|
58414
|
-
try {
|
|
58415
|
-
stats = fs.lstatSync(absolutePath);
|
|
58416
|
-
} catch {
|
|
58417
|
-
return statusCode.includes("D") ? "deleted" : null;
|
|
58418
|
-
}
|
|
58419
|
-
if (stats.isSymbolicLink()) try {
|
|
58420
|
-
return `link:${fs.readlinkSync(absolutePath)}`;
|
|
58421
|
-
} catch {
|
|
58422
|
-
return null;
|
|
58423
|
-
}
|
|
58424
|
-
if (!stats.isFile()) return null;
|
|
58425
|
-
if (stats.size > 10485760) return `stat:${stats.mtimeMs}:${stats.size}`;
|
|
58426
|
-
return hashFileContents(absolutePath);
|
|
58427
|
-
};
|
|
58428
|
-
const buildWorktreeFingerprint = (projectDirectory) => {
|
|
58429
|
-
const statusOutput = runGit(projectDirectory, [
|
|
58430
|
-
"status",
|
|
58431
|
-
"--porcelain=v1",
|
|
58432
|
-
"-z",
|
|
58433
|
-
"--untracked-files=all"
|
|
58434
|
-
]);
|
|
58435
|
-
if (statusOutput === null) return null;
|
|
58436
|
-
if (statusOutput.length === 0) return [];
|
|
58437
|
-
const statusEntries = parseWorktreeStatusRecords(statusOutput);
|
|
58438
|
-
if (statusEntries === null) return null;
|
|
58439
|
-
if (statusEntries.length > 300) return null;
|
|
58440
|
-
const repositoryRoot = runGit(projectDirectory, ["rev-parse", "--show-toplevel"]);
|
|
58441
|
-
if (repositoryRoot === null) return null;
|
|
58442
|
-
const dirtyEntries = [];
|
|
58443
|
-
for (const statusEntry of statusEntries) {
|
|
58444
|
-
const contentFingerprint = buildDirtyPathContentFingerprint(path.join(repositoryRoot, statusEntry.path), statusEntry.statusCode);
|
|
58445
|
-
if (contentFingerprint === null) return null;
|
|
58446
|
-
dirtyEntries.push({
|
|
58447
|
-
...statusEntry,
|
|
58448
|
-
contentFingerprint
|
|
58449
|
-
});
|
|
58450
|
-
}
|
|
58451
|
-
return dirtyEntries.sort((firstEntry, secondEntry) => {
|
|
58452
|
-
const firstSortKey = `${firstEntry.path}${firstEntry.statusCode}`;
|
|
58453
|
-
const secondSortKey = `${secondEntry.path}${secondEntry.statusCode}`;
|
|
58454
|
-
return firstSortKey < secondSortKey ? -1 : firstSortKey > secondSortKey ? 1 : 0;
|
|
58455
|
-
});
|
|
58456
|
-
};
|
|
58457
|
-
const DOTENV_FILE_NAME_PATTERN = /^\.env(\.|$)/;
|
|
58458
|
-
const resolveDotenvFingerprint = (projectDirectory) => {
|
|
58459
|
-
try {
|
|
58460
|
-
return fs.readdirSync(projectDirectory).filter((entryName) => DOTENV_FILE_NAME_PATTERN.test(entryName)).sort().map((entryName) => `${entryName}=${hashFileContents(path.join(projectDirectory, entryName)) ?? "unreadable"}`);
|
|
58461
|
-
} catch {
|
|
58462
|
-
return [];
|
|
58463
|
-
}
|
|
58464
|
-
};
|
|
58465
|
-
const hasHiddenTrackedFileState = (projectDirectory) => {
|
|
58466
|
-
const output = runGit(projectDirectory, ["ls-files", "-v"]);
|
|
58467
|
-
if (output === null) return true;
|
|
58468
|
-
return output.split("\n").some((line) => line.length > 0 && line[0] !== "H");
|
|
58469
|
-
};
|
|
58470
|
-
const resolveCacheFilePath = (projectDirectory) => path.join(resolveReactDoctorCacheDir(projectDirectory), SCAN_RESULT_CACHE_FILENAME);
|
|
58471
|
-
const readPersistedCache = (cacheFilePath) => {
|
|
58472
|
-
try {
|
|
58473
|
-
const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
|
|
58474
|
-
if (!isRecord(parsed) || parsed.version !== 3) return {
|
|
58475
|
-
version: 3,
|
|
58476
|
-
entries: []
|
|
58477
|
-
};
|
|
58478
|
-
if (!Array.isArray(parsed.entries)) return {
|
|
58479
|
-
version: 3,
|
|
58480
|
-
entries: []
|
|
58481
|
-
};
|
|
58482
|
-
const entries = [];
|
|
58483
|
-
for (const entry of parsed.entries) {
|
|
58484
|
-
if (!isRecord(entry) || typeof entry.key !== "string" || typeof entry.createdAtMs !== "number") continue;
|
|
58485
|
-
if (!isRecord(entry.payload) || !Array.isArray(entry.payload.diagnostics)) continue;
|
|
58486
|
-
entries.push(entry);
|
|
58487
|
-
}
|
|
58488
|
-
return {
|
|
58489
|
-
version: 3,
|
|
58490
|
-
entries
|
|
58491
|
-
};
|
|
58492
|
-
} catch {
|
|
58493
|
-
return {
|
|
58494
|
-
version: 3,
|
|
58495
|
-
entries: []
|
|
58496
|
-
};
|
|
58497
|
-
}
|
|
58498
|
-
};
|
|
58499
|
-
const writePersistedCache = (cacheFilePath, cache) => {
|
|
58500
|
-
try {
|
|
58501
|
-
fs.mkdirSync(path.dirname(cacheFilePath), { recursive: true });
|
|
58502
|
-
const tempPath = `${cacheFilePath}.${process.pid}.tmp`;
|
|
58503
|
-
fs.writeFileSync(tempPath, JSON.stringify(cache));
|
|
58504
|
-
fs.renameSync(tempPath, cacheFilePath);
|
|
58505
|
-
} catch {
|
|
58506
|
-
return;
|
|
58507
|
-
}
|
|
58508
|
-
};
|
|
58509
|
-
/**
|
|
58510
|
-
* The global cache off-switch (`REACT_DOCTOR_NO_CACHE`), which disables every
|
|
58511
|
-
* cache subsystem: this whole-repo scan cache plus core's per-file lint,
|
|
58512
|
-
* sidecar, and dead-code caches (their `Context.Reference` defaults read the
|
|
58513
|
-
* same variable). Exported for the wide event's `cache.temperature`
|
|
58514
|
-
* derivation, which reports `"disabled"` instead of `"cold"` when the switch
|
|
58515
|
-
* is on. Granular knobs (`REACT_DOCTOR_NO_FILE_CACHE`, …) are deliberately
|
|
58516
|
-
* not consulted — they leave the other subsystems live.
|
|
58517
|
-
*/
|
|
58518
|
-
const isCacheGloballyDisabled = () => CACHE_DISABLED_VALUES.has(process.env.REACT_DOCTOR_NO_CACHE?.toLowerCase() ?? "");
|
|
58519
|
-
const resolveProjectIdentity = (projectDirectory) => {
|
|
58520
|
-
try {
|
|
58521
|
-
return fs.realpathSync.native(projectDirectory);
|
|
58522
|
-
} catch {
|
|
58523
|
-
return path.resolve(projectDirectory);
|
|
58524
|
-
}
|
|
58525
|
-
};
|
|
58526
|
-
const fileFingerprint = (filePath) => {
|
|
58527
|
-
try {
|
|
58528
|
-
const stat = fs.statSync(filePath);
|
|
58529
|
-
return `${filePath}:${stat.mtimeMs}:${stat.size}`;
|
|
58530
|
-
} catch {
|
|
58531
|
-
return null;
|
|
58532
|
-
}
|
|
58533
|
-
};
|
|
58534
|
-
const resolveToolchainFingerprint = (nodeBinaryPath) => {
|
|
58535
|
-
const fingerprints = [];
|
|
58536
|
-
if (nodeBinaryPath !== null) fingerprints.push(nodeBinaryPath === process.execPath ? `node=${process.version}` : `node=${fileFingerprint(nodeBinaryPath) ?? "unreadable"}`);
|
|
58537
|
-
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
58538
|
-
const packageJson = bundledRequire(specifier);
|
|
58539
|
-
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
58540
|
-
fingerprints.push(`${specifier}=${version}`);
|
|
58541
|
-
} catch {
|
|
58542
|
-
fingerprints.push(`${specifier}=missing`);
|
|
58543
|
-
}
|
|
58544
|
-
return fingerprints;
|
|
58545
|
-
};
|
|
58546
|
-
const buildScanResultCacheKey = (input) => {
|
|
58547
|
-
if (isCacheGloballyDisabled()) return null;
|
|
58548
|
-
if (hasHiddenTrackedFileState(input.projectDirectory)) return null;
|
|
58549
|
-
const worktreeFingerprint = buildWorktreeFingerprint(input.projectDirectory);
|
|
58550
|
-
if (worktreeFingerprint === null) return null;
|
|
58551
|
-
const headSha = readHeadSha(input.projectDirectory);
|
|
58552
|
-
if (headSha === null) return null;
|
|
58553
|
-
if (stringifyStableJson(input.userConfig) === null) return null;
|
|
58554
|
-
const cacheKeyJson = stringifyStableJson({
|
|
58555
|
-
schemaVersion: 3,
|
|
58556
|
-
projectIdentity: resolveProjectIdentity(input.projectDirectory),
|
|
58557
|
-
headSha,
|
|
58558
|
-
worktreeFingerprint,
|
|
58559
|
-
dotenvFingerprint: resolveDotenvFingerprint(input.projectDirectory),
|
|
58560
|
-
reactDoctorVersion: input.version,
|
|
58561
|
-
nodeVersion: process.version,
|
|
58562
|
-
toolchainFingerprint: resolveToolchainFingerprint(input.nodeBinaryPath),
|
|
58563
|
-
configFingerprint: computeConfigFingerprint(input.projectDirectory, input.version),
|
|
58564
|
-
hasConfigOverride: input.hasConfigOverride,
|
|
58565
|
-
configSourceDirectory: input.configSourceDirectory,
|
|
58566
|
-
userConfig: input.userConfig,
|
|
58567
|
-
engineOptions: {
|
|
58568
|
-
lint: input.options.lint,
|
|
58569
|
-
deadCode: input.options.deadCode,
|
|
58570
|
-
includePaths: [...input.options.includePaths].sort(),
|
|
58571
|
-
customRulesOnly: input.options.customRulesOnly,
|
|
58572
|
-
respectInlineDisables: input.options.respectInlineDisables,
|
|
58573
|
-
warnings: input.options.warnings,
|
|
58574
|
-
adoptExistingLintConfig: input.options.adoptExistingLintConfig,
|
|
58575
|
-
ignoredTags: [...input.options.ignoredTags].sort(),
|
|
58576
|
-
concurrency: input.options.concurrency,
|
|
58577
|
-
lintBatchOrdering: resolveLintBatchOrdering(),
|
|
58578
|
-
baselineRef: input.options.baseline?.ref,
|
|
58579
|
-
changedLineRanges: input.options.changedLineRanges ?? void 0,
|
|
58580
|
-
noScore: input.options.noScore,
|
|
58581
|
-
isCi: input.options.isCi,
|
|
58582
|
-
suppressRendering: input.options.suppressRendering,
|
|
58583
|
-
supplyChainManifestChanged: input.options.supplyChainManifestChanged
|
|
58584
|
-
}
|
|
58585
|
-
});
|
|
58586
|
-
return cacheKeyJson === null ? null : hashString(cacheKeyJson);
|
|
58587
|
-
};
|
|
58588
|
-
const createScanResultCache = (projectDirectory) => {
|
|
58589
|
-
const cacheFilePath = resolveCacheFilePath(projectDirectory);
|
|
58590
|
-
const persistedCache = readPersistedCache(cacheFilePath);
|
|
58591
|
-
const entries = /* @__PURE__ */ new Map();
|
|
58592
|
-
for (const entry of persistedCache.entries) entries.set(entry.key, entry);
|
|
58593
|
-
const persist = () => {
|
|
58594
|
-
writePersistedCache(cacheFilePath, {
|
|
58595
|
-
version: 3,
|
|
58596
|
-
entries: [...entries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
|
|
58597
|
-
});
|
|
58598
|
-
};
|
|
58599
|
-
return {
|
|
58600
|
-
lookup: (key) => entries.get(key)?.payload ?? null,
|
|
58601
|
-
store: (key, payload) => {
|
|
58602
|
-
entries.set(key, {
|
|
58603
|
-
key,
|
|
58604
|
-
createdAtMs: Date.now(),
|
|
58605
|
-
payload
|
|
58606
|
-
});
|
|
58607
|
-
persist();
|
|
58608
|
-
}
|
|
58609
|
-
};
|
|
58610
|
-
};
|
|
58611
|
-
const shouldStoreScanPayload = (payload) => !payload.didLintFail && !payload.didDeadCodeFail && payload.lintPartialFailures.length === 0 && !payload.supplyChainOverlapTimedOut && payload.securityScanFailed !== true;
|
|
58612
|
-
//#endregion
|
|
58613
58257
|
//#region src/cli/utils/should-block-ci.ts
|
|
58614
58258
|
const shouldBlockCi = (diagnostics, blockingLevel) => {
|
|
58615
58259
|
if (blockingLevel === "none") return false;
|
|
@@ -58651,63 +58295,6 @@ const readEnvBoolean = (name) => {
|
|
|
58651
58295
|
if (value === void 0) return null;
|
|
58652
58296
|
return value.toLowerCase() === "true" || value === "1";
|
|
58653
58297
|
};
|
|
58654
|
-
const ratioOf = (numerator, denominator) => denominator != null && denominator > 0 ? (numerator ?? 0) / denominator : null;
|
|
58655
|
-
const resolveDeadCodeReuseRatio = (result) => {
|
|
58656
|
-
if (result.deadCodeCacheHit === true) return 1;
|
|
58657
|
-
const summaryTotal = (result.deadCodeSummaryCacheHits ?? 0) + (result.deadCodeSummaryCacheMisses ?? 0);
|
|
58658
|
-
if (summaryTotal > 0) return (result.deadCodeSummaryCacheHits ?? 0) / summaryTotal;
|
|
58659
|
-
return result.deadCodeCacheHit === false ? 0 : null;
|
|
58660
|
-
};
|
|
58661
|
-
/**
|
|
58662
|
-
* One queryable cache temperature per scan, derived from the whole stack:
|
|
58663
|
-
*
|
|
58664
|
-
* - `"turbo"` — whole-repo scan-result replay (the explicit
|
|
58665
|
-
* `wholeRepoCacheHit` flag from the CLI's cachedPayload
|
|
58666
|
-
* branch; no scan work ran).
|
|
58667
|
-
* - `"warm"` — any incremental reuse: per-file lint hits, sidecar
|
|
58668
|
-
* replays, a dead-code whole-result hit, or dead-code
|
|
58669
|
-
* summary-cache hits.
|
|
58670
|
-
* - `"disabled"` — zero reuse because the global `REACT_DOCTOR_NO_CACHE`
|
|
58671
|
-
* off-switch is on. Granular per-cache opt-outs
|
|
58672
|
-
* (`REACT_DOCTOR_NO_FILE_CACHE`, …) still read warm/cold,
|
|
58673
|
-
* since the other subsystems stay live.
|
|
58674
|
-
* - `"cold"` — caches on, zero reuse (first scan / everything changed).
|
|
58675
|
-
*
|
|
58676
|
-
* `cache.warmth` is the headline reuse magnitude in [0, 1]: the plain mean of
|
|
58677
|
-
* the subsystem reuse fractions known this run (lint hit ratio, sidecar
|
|
58678
|
-
* replay ratio, dead-code reuse), skipping subsystems that never consulted a
|
|
58679
|
-
* cache; `1` on turbo, dropped when nothing consulted any cache. Deliberately
|
|
58680
|
-
* unweighted — the per-subsystem dims stay the precise signal; warmth is the
|
|
58681
|
-
* p50/p90-able summary. Emitted only on the success path.
|
|
58682
|
-
*/
|
|
58683
|
-
const buildCacheAttributes = (input) => {
|
|
58684
|
-
if (input.result === void 0) return {};
|
|
58685
|
-
if (input.wholeRepoCacheHit) return withNamespace("cache", {
|
|
58686
|
-
wholeRepoHit: true,
|
|
58687
|
-
temperature: "turbo",
|
|
58688
|
-
warmth: 1
|
|
58689
|
-
});
|
|
58690
|
-
const result = input.result;
|
|
58691
|
-
const subsystemReuseRatios = [
|
|
58692
|
-
ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount),
|
|
58693
|
-
ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount),
|
|
58694
|
-
resolveDeadCodeReuseRatio(result)
|
|
58695
|
-
];
|
|
58696
|
-
let knownSubsystemCount = 0;
|
|
58697
|
-
let reuseRatioSum = 0;
|
|
58698
|
-
for (const reuseRatio of subsystemReuseRatios) {
|
|
58699
|
-
if (reuseRatio === null) continue;
|
|
58700
|
-
knownSubsystemCount += 1;
|
|
58701
|
-
reuseRatioSum += reuseRatio;
|
|
58702
|
-
}
|
|
58703
|
-
const warmth = knownSubsystemCount > 0 ? reuseRatioSum / knownSubsystemCount : null;
|
|
58704
|
-
const temperature = warmth !== null && warmth > 0 ? "warm" : isCacheGloballyDisabled() ? "disabled" : "cold";
|
|
58705
|
-
return withNamespace("cache", {
|
|
58706
|
-
wholeRepoHit: input.wholeRepoCacheHit ?? null,
|
|
58707
|
-
temperature,
|
|
58708
|
-
warmth
|
|
58709
|
-
});
|
|
58710
|
-
};
|
|
58711
58298
|
const resolveVersionPin = (versionInput) => {
|
|
58712
58299
|
if (versionInput === void 0 || versionInput.trim() === "") return null;
|
|
58713
58300
|
if (versionInput === "latest") return "latest";
|
|
@@ -58813,10 +58400,10 @@ const buildOutcomeAttributes = (input) => {
|
|
|
58813
58400
|
deadlineSkippedFileCount: input.lintDeadlineSkippedFileCount ?? null,
|
|
58814
58401
|
cacheHitFiles: result.lintCacheHitFileCount ?? null,
|
|
58815
58402
|
cacheTotalFiles: result.lintCacheTotalFileCount ?? null,
|
|
58816
|
-
cacheHitRatio:
|
|
58403
|
+
cacheHitRatio: result.lintCacheTotalFileCount != null && result.lintCacheTotalFileCount > 0 ? (result.lintCacheHitFileCount ?? 0) / result.lintCacheTotalFileCount : null,
|
|
58817
58404
|
sidecarReplayedFiles: result.lintSidecarReplayedFileCount ?? null,
|
|
58818
58405
|
sidecarTotalFiles: result.lintSidecarTotalFileCount ?? null,
|
|
58819
|
-
sidecarReplayRatio:
|
|
58406
|
+
sidecarReplayRatio: result.lintSidecarTotalFileCount != null && result.lintSidecarTotalFileCount > 0 ? (result.lintSidecarReplayedFileCount ?? 0) / result.lintSidecarTotalFileCount : null
|
|
58820
58407
|
}),
|
|
58821
58408
|
...withNamespace("deadCode", {
|
|
58822
58409
|
failed: input.didDeadCodeFail ?? null,
|
|
@@ -58883,7 +58470,7 @@ const buildScanAttributes = (input) => {
|
|
|
58883
58470
|
* Projects a scan into the namespaced attribute set for its root span — the
|
|
58884
58471
|
* canonical per-scan "wide event". Every attribute carries a dotted namespace
|
|
58885
58472
|
* that groups it by concept (`scan.*` config, `action.*` CI knobs, `outcome.*`
|
|
58886
|
-
* verdict, `diag.*` findings, `score.*`, `lint.*`, `deadCode.*`,
|
|
58473
|
+
* verdict, `diag.*` findings, `score.*`, `lint.*`, `deadCode.*`,
|
|
58887
58474
|
* `supplyChain.*`, `timing.*`, `migration.*`, `baseline.*`) so the attributes
|
|
58888
58475
|
* tree up in Sentry's attribute browser and stay filter-/group-/aggregate-able
|
|
58889
58476
|
* in the Spans dataset. Pure and exported so the projection (outcome
|
|
@@ -58897,8 +58484,7 @@ const buildScanAttributes = (input) => {
|
|
|
58897
58484
|
const buildRunEventAttributes = (input) => toSpanAttributes({
|
|
58898
58485
|
...buildScanAttributes(input),
|
|
58899
58486
|
...buildActionAttributes(),
|
|
58900
|
-
...buildOutcomeAttributes(input)
|
|
58901
|
-
...buildCacheAttributes(input)
|
|
58487
|
+
...buildOutcomeAttributes(input)
|
|
58902
58488
|
});
|
|
58903
58489
|
/**
|
|
58904
58490
|
* Stamps the wide-event attributes onto the run's root span. A guarded no-op
|
|
@@ -60108,6 +59694,275 @@ const resolveCliCategories = (categoryFlag) => {
|
|
|
60108
59694
|
return resolvedCategories.length > 0 ? resolvedCategories : void 0;
|
|
60109
59695
|
};
|
|
60110
59696
|
//#endregion
|
|
59697
|
+
//#region src/cli/utils/scan-result-cache.ts
|
|
59698
|
+
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
59699
|
+
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
59700
|
+
"oxlint",
|
|
59701
|
+
"oxlint/package.json",
|
|
59702
|
+
"oxlint-plugin-react-doctor",
|
|
59703
|
+
"deslop-js/package.json",
|
|
59704
|
+
"eslint-plugin-react-hooks/package.json"
|
|
59705
|
+
];
|
|
59706
|
+
const bundledRequire = createRequire(import.meta.url);
|
|
59707
|
+
const normalizeForStableJson = (value) => {
|
|
59708
|
+
if (value === null) return null;
|
|
59709
|
+
if (value === void 0) return void 0;
|
|
59710
|
+
const valueType = typeof value;
|
|
59711
|
+
if (valueType === "string" || valueType === "number" || valueType === "boolean") return value;
|
|
59712
|
+
if (valueType === "bigint" || valueType === "function" || valueType === "symbol") throw new Error("Unsupported cache key value");
|
|
59713
|
+
if (Array.isArray(value)) return value.map((item) => {
|
|
59714
|
+
const normalized = normalizeForStableJson(item);
|
|
59715
|
+
return normalized === void 0 ? null : normalized;
|
|
59716
|
+
});
|
|
59717
|
+
const prototype = Object.getPrototypeOf(value);
|
|
59718
|
+
if (prototype !== Object.prototype && prototype !== null) throw new Error("Unsupported cache key object");
|
|
59719
|
+
if (!isRecord(value)) throw new Error("Unsupported cache key object");
|
|
59720
|
+
const normalizedRecord = {};
|
|
59721
|
+
for (const key of Object.keys(value).sort()) {
|
|
59722
|
+
const normalized = normalizeForStableJson(value[key]);
|
|
59723
|
+
if (normalized !== void 0) normalizedRecord[key] = normalized;
|
|
59724
|
+
}
|
|
59725
|
+
return normalizedRecord;
|
|
59726
|
+
};
|
|
59727
|
+
const stringifyStableJson = (value) => {
|
|
59728
|
+
try {
|
|
59729
|
+
return JSON.stringify(normalizeForStableJson(value));
|
|
59730
|
+
} catch {
|
|
59731
|
+
return null;
|
|
59732
|
+
}
|
|
59733
|
+
};
|
|
59734
|
+
const hashString = (value) => crypto.createHash("sha1").update(value).digest("hex");
|
|
59735
|
+
const readHeadSha = (projectDirectory) => runGit(projectDirectory, ["rev-parse", "HEAD"]);
|
|
59736
|
+
const parseWorktreeStatusRecords = (statusOutput) => {
|
|
59737
|
+
const entries = [];
|
|
59738
|
+
const tokens = statusOutput.split("\0");
|
|
59739
|
+
for (let tokenIndex = 0; tokenIndex < tokens.length; tokenIndex += 1) {
|
|
59740
|
+
let record = tokens[tokenIndex];
|
|
59741
|
+
if (record.length === 0) continue;
|
|
59742
|
+
if (record.length < 3 || record[2] !== " ") {
|
|
59743
|
+
if (tokenIndex !== 0) return null;
|
|
59744
|
+
record = ` ${record}`;
|
|
59745
|
+
}
|
|
59746
|
+
if (record.length < 4 || record[2] !== " ") return null;
|
|
59747
|
+
const statusCode = record.slice(0, 2);
|
|
59748
|
+
const recordPath = record.slice(3);
|
|
59749
|
+
if (statusCode.includes("R") || statusCode.includes("C")) {
|
|
59750
|
+
tokenIndex += 1;
|
|
59751
|
+
const originPath = tokens[tokenIndex];
|
|
59752
|
+
if (originPath === void 0 || originPath.length === 0) return null;
|
|
59753
|
+
entries.push({
|
|
59754
|
+
statusCode,
|
|
59755
|
+
path: recordPath,
|
|
59756
|
+
originPath
|
|
59757
|
+
});
|
|
59758
|
+
continue;
|
|
59759
|
+
}
|
|
59760
|
+
entries.push({
|
|
59761
|
+
statusCode,
|
|
59762
|
+
path: recordPath
|
|
59763
|
+
});
|
|
59764
|
+
}
|
|
59765
|
+
return entries;
|
|
59766
|
+
};
|
|
59767
|
+
const buildDirtyPathContentFingerprint = (absolutePath, statusCode) => {
|
|
59768
|
+
let stats;
|
|
59769
|
+
try {
|
|
59770
|
+
stats = fs.lstatSync(absolutePath);
|
|
59771
|
+
} catch {
|
|
59772
|
+
return statusCode.includes("D") ? "deleted" : null;
|
|
59773
|
+
}
|
|
59774
|
+
if (stats.isSymbolicLink()) try {
|
|
59775
|
+
return `link:${fs.readlinkSync(absolutePath)}`;
|
|
59776
|
+
} catch {
|
|
59777
|
+
return null;
|
|
59778
|
+
}
|
|
59779
|
+
if (!stats.isFile()) return null;
|
|
59780
|
+
if (stats.size > 10485760) return `stat:${stats.mtimeMs}:${stats.size}`;
|
|
59781
|
+
return hashFileContents(absolutePath);
|
|
59782
|
+
};
|
|
59783
|
+
const buildWorktreeFingerprint = (projectDirectory) => {
|
|
59784
|
+
const statusOutput = runGit(projectDirectory, [
|
|
59785
|
+
"status",
|
|
59786
|
+
"--porcelain=v1",
|
|
59787
|
+
"-z",
|
|
59788
|
+
"--untracked-files=all"
|
|
59789
|
+
]);
|
|
59790
|
+
if (statusOutput === null) return null;
|
|
59791
|
+
if (statusOutput.length === 0) return [];
|
|
59792
|
+
const statusEntries = parseWorktreeStatusRecords(statusOutput);
|
|
59793
|
+
if (statusEntries === null) return null;
|
|
59794
|
+
if (statusEntries.length > 300) return null;
|
|
59795
|
+
const repositoryRoot = runGit(projectDirectory, ["rev-parse", "--show-toplevel"]);
|
|
59796
|
+
if (repositoryRoot === null) return null;
|
|
59797
|
+
const dirtyEntries = [];
|
|
59798
|
+
for (const statusEntry of statusEntries) {
|
|
59799
|
+
const contentFingerprint = buildDirtyPathContentFingerprint(path.join(repositoryRoot, statusEntry.path), statusEntry.statusCode);
|
|
59800
|
+
if (contentFingerprint === null) return null;
|
|
59801
|
+
dirtyEntries.push({
|
|
59802
|
+
...statusEntry,
|
|
59803
|
+
contentFingerprint
|
|
59804
|
+
});
|
|
59805
|
+
}
|
|
59806
|
+
return dirtyEntries.sort((firstEntry, secondEntry) => {
|
|
59807
|
+
const firstSortKey = `${firstEntry.path}${firstEntry.statusCode}`;
|
|
59808
|
+
const secondSortKey = `${secondEntry.path}${secondEntry.statusCode}`;
|
|
59809
|
+
return firstSortKey < secondSortKey ? -1 : firstSortKey > secondSortKey ? 1 : 0;
|
|
59810
|
+
});
|
|
59811
|
+
};
|
|
59812
|
+
const DOTENV_FILE_NAME_PATTERN = /^\.env(\.|$)/;
|
|
59813
|
+
const resolveDotenvFingerprint = (projectDirectory) => {
|
|
59814
|
+
try {
|
|
59815
|
+
return fs.readdirSync(projectDirectory).filter((entryName) => DOTENV_FILE_NAME_PATTERN.test(entryName)).sort().map((entryName) => `${entryName}=${fileFingerprint(path.join(projectDirectory, entryName)) ?? "unreadable"}`);
|
|
59816
|
+
} catch {
|
|
59817
|
+
return [];
|
|
59818
|
+
}
|
|
59819
|
+
};
|
|
59820
|
+
const hasHiddenTrackedFileState = (projectDirectory) => {
|
|
59821
|
+
const output = runGit(projectDirectory, ["ls-files", "-v"]);
|
|
59822
|
+
if (output === null) return true;
|
|
59823
|
+
return output.split("\n").some((line) => line.length > 0 && line[0] !== "H");
|
|
59824
|
+
};
|
|
59825
|
+
const resolveCacheFilePath = (projectDirectory) => {
|
|
59826
|
+
const nodeModulesDirectory = path.join(projectDirectory, "node_modules");
|
|
59827
|
+
if (fs.existsSync(nodeModulesDirectory)) return path.join(nodeModulesDirectory, ".cache", "react-doctor", "scan-cache.json");
|
|
59828
|
+
const projectHash = hashString(projectDirectory).slice(0, 16);
|
|
59829
|
+
return path.join(os.tmpdir(), "react-doctor-cache", `scan-${projectHash}.json`);
|
|
59830
|
+
};
|
|
59831
|
+
const readPersistedCache = (cacheFilePath) => {
|
|
59832
|
+
try {
|
|
59833
|
+
const parsed = JSON.parse(fs.readFileSync(cacheFilePath, "utf8"));
|
|
59834
|
+
if (!isRecord(parsed) || parsed.version !== 3) return {
|
|
59835
|
+
version: 3,
|
|
59836
|
+
entries: []
|
|
59837
|
+
};
|
|
59838
|
+
if (!Array.isArray(parsed.entries)) return {
|
|
59839
|
+
version: 3,
|
|
59840
|
+
entries: []
|
|
59841
|
+
};
|
|
59842
|
+
const entries = [];
|
|
59843
|
+
for (const entry of parsed.entries) {
|
|
59844
|
+
if (!isRecord(entry) || typeof entry.key !== "string" || typeof entry.createdAtMs !== "number") continue;
|
|
59845
|
+
if (!isRecord(entry.payload) || !Array.isArray(entry.payload.diagnostics)) continue;
|
|
59846
|
+
entries.push(entry);
|
|
59847
|
+
}
|
|
59848
|
+
return {
|
|
59849
|
+
version: 3,
|
|
59850
|
+
entries
|
|
59851
|
+
};
|
|
59852
|
+
} catch {
|
|
59853
|
+
return {
|
|
59854
|
+
version: 3,
|
|
59855
|
+
entries: []
|
|
59856
|
+
};
|
|
59857
|
+
}
|
|
59858
|
+
};
|
|
59859
|
+
const writePersistedCache = (cacheFilePath, cache) => {
|
|
59860
|
+
try {
|
|
59861
|
+
fs.mkdirSync(path.dirname(cacheFilePath), { recursive: true });
|
|
59862
|
+
const tempPath = `${cacheFilePath}.${process.pid}.tmp`;
|
|
59863
|
+
fs.writeFileSync(tempPath, JSON.stringify(cache));
|
|
59864
|
+
fs.renameSync(tempPath, cacheFilePath);
|
|
59865
|
+
} catch {
|
|
59866
|
+
return;
|
|
59867
|
+
}
|
|
59868
|
+
};
|
|
59869
|
+
const isScanResultCacheDisabled = () => CACHE_DISABLED_VALUES.has(process.env.REACT_DOCTOR_NO_CACHE?.toLowerCase() ?? "");
|
|
59870
|
+
const resolveProjectIdentity = (projectDirectory) => {
|
|
59871
|
+
try {
|
|
59872
|
+
return fs.realpathSync.native(projectDirectory);
|
|
59873
|
+
} catch {
|
|
59874
|
+
return path.resolve(projectDirectory);
|
|
59875
|
+
}
|
|
59876
|
+
};
|
|
59877
|
+
const fileFingerprint = (filePath) => {
|
|
59878
|
+
try {
|
|
59879
|
+
const stat = fs.statSync(filePath);
|
|
59880
|
+
return `${filePath}:${stat.mtimeMs}:${stat.size}`;
|
|
59881
|
+
} catch {
|
|
59882
|
+
return null;
|
|
59883
|
+
}
|
|
59884
|
+
};
|
|
59885
|
+
const resolveToolchainFingerprint = (nodeBinaryPath) => {
|
|
59886
|
+
const fingerprints = [];
|
|
59887
|
+
if (nodeBinaryPath !== null) {
|
|
59888
|
+
const fingerprint = fileFingerprint(nodeBinaryPath);
|
|
59889
|
+
if (fingerprint !== null) fingerprints.push(`node=${fingerprint}`);
|
|
59890
|
+
}
|
|
59891
|
+
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
59892
|
+
const fingerprint = fileFingerprint(bundledRequire.resolve(specifier));
|
|
59893
|
+
if (fingerprint !== null) fingerprints.push(`${specifier}=${fingerprint}`);
|
|
59894
|
+
} catch {
|
|
59895
|
+
continue;
|
|
59896
|
+
}
|
|
59897
|
+
return fingerprints;
|
|
59898
|
+
};
|
|
59899
|
+
const buildScanResultCacheKey = (input) => {
|
|
59900
|
+
if (isScanResultCacheDisabled()) return null;
|
|
59901
|
+
if (hasHiddenTrackedFileState(input.projectDirectory)) return null;
|
|
59902
|
+
const worktreeFingerprint = buildWorktreeFingerprint(input.projectDirectory);
|
|
59903
|
+
if (worktreeFingerprint === null) return null;
|
|
59904
|
+
const headSha = readHeadSha(input.projectDirectory);
|
|
59905
|
+
if (headSha === null) return null;
|
|
59906
|
+
if (stringifyStableJson(input.userConfig) === null) return null;
|
|
59907
|
+
const cacheKeyJson = stringifyStableJson({
|
|
59908
|
+
schemaVersion: 3,
|
|
59909
|
+
projectIdentity: resolveProjectIdentity(input.projectDirectory),
|
|
59910
|
+
headSha,
|
|
59911
|
+
worktreeFingerprint,
|
|
59912
|
+
dotenvFingerprint: resolveDotenvFingerprint(input.projectDirectory),
|
|
59913
|
+
reactDoctorVersion: input.version,
|
|
59914
|
+
nodeVersion: process.version,
|
|
59915
|
+
toolchainFingerprint: resolveToolchainFingerprint(input.nodeBinaryPath),
|
|
59916
|
+
configFingerprint: computeConfigFingerprint(input.projectDirectory, input.version),
|
|
59917
|
+
hasConfigOverride: input.hasConfigOverride,
|
|
59918
|
+
configSourceDirectory: input.configSourceDirectory,
|
|
59919
|
+
userConfig: input.userConfig,
|
|
59920
|
+
engineOptions: {
|
|
59921
|
+
lint: input.options.lint,
|
|
59922
|
+
deadCode: input.options.deadCode,
|
|
59923
|
+
includePaths: [...input.options.includePaths].sort(),
|
|
59924
|
+
customRulesOnly: input.options.customRulesOnly,
|
|
59925
|
+
respectInlineDisables: input.options.respectInlineDisables,
|
|
59926
|
+
warnings: input.options.warnings,
|
|
59927
|
+
adoptExistingLintConfig: input.options.adoptExistingLintConfig,
|
|
59928
|
+
ignoredTags: [...input.options.ignoredTags].sort(),
|
|
59929
|
+
concurrency: input.options.concurrency,
|
|
59930
|
+
lintBatchOrdering: resolveLintBatchOrdering(),
|
|
59931
|
+
baselineRef: input.options.baseline?.ref,
|
|
59932
|
+
changedLineRanges: input.options.changedLineRanges ?? void 0,
|
|
59933
|
+
noScore: input.options.noScore,
|
|
59934
|
+
isCi: input.options.isCi,
|
|
59935
|
+
suppressRendering: input.options.suppressRendering,
|
|
59936
|
+
supplyChainManifestChanged: input.options.supplyChainManifestChanged
|
|
59937
|
+
}
|
|
59938
|
+
});
|
|
59939
|
+
return cacheKeyJson === null ? null : hashString(cacheKeyJson);
|
|
59940
|
+
};
|
|
59941
|
+
const createScanResultCache = (projectDirectory) => {
|
|
59942
|
+
const cacheFilePath = resolveCacheFilePath(projectDirectory);
|
|
59943
|
+
const persistedCache = readPersistedCache(cacheFilePath);
|
|
59944
|
+
const entries = /* @__PURE__ */ new Map();
|
|
59945
|
+
for (const entry of persistedCache.entries) entries.set(entry.key, entry);
|
|
59946
|
+
const persist = () => {
|
|
59947
|
+
writePersistedCache(cacheFilePath, {
|
|
59948
|
+
version: 3,
|
|
59949
|
+
entries: [...entries.values()].sort((firstEntry, secondEntry) => secondEntry.createdAtMs - firstEntry.createdAtMs).slice(0, 20)
|
|
59950
|
+
});
|
|
59951
|
+
};
|
|
59952
|
+
return {
|
|
59953
|
+
lookup: (key) => entries.get(key)?.payload ?? null,
|
|
59954
|
+
store: (key, payload) => {
|
|
59955
|
+
entries.set(key, {
|
|
59956
|
+
key,
|
|
59957
|
+
createdAtMs: Date.now(),
|
|
59958
|
+
payload
|
|
59959
|
+
});
|
|
59960
|
+
persist();
|
|
59961
|
+
}
|
|
59962
|
+
};
|
|
59963
|
+
};
|
|
59964
|
+
const shouldStoreScanPayload = (payload) => !payload.didLintFail && !payload.didDeadCodeFail && payload.lintPartialFailures.length === 0 && !payload.supplyChainOverlapTimedOut && payload.securityScanFailed !== true;
|
|
59965
|
+
//#endregion
|
|
60111
59966
|
//#region src/inspect.ts
|
|
60112
59967
|
const silentConsole = makeNoopConsole();
|
|
60113
59968
|
const runConsole = (effect) => {
|
|
@@ -60284,7 +60139,7 @@ const runBaselineComparison = async (params) => {
|
|
|
60284
60139
|
suppressScanSummary: true,
|
|
60285
60140
|
supplyChainManifestChanged: params.options.supplyChainManifestChanged,
|
|
60286
60141
|
deadlineEpochMs: params.deadlineEpochMs ?? void 0
|
|
60287
|
-
}, {}).pipe(provide(baseLayers), provideService$2(
|
|
60142
|
+
}, {}).pipe(provide(baseLayers), provideService$2(Console, silentConsole))));
|
|
60288
60143
|
if (baseOutput.didLintFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
|
|
60289
60144
|
const delta = computeDiagnosticDelta({
|
|
60290
60145
|
headDiagnostics: params.headDiagnostics,
|
|
@@ -60337,8 +60192,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
60337
60192
|
startTime,
|
|
60338
60193
|
rootSentrySpan,
|
|
60339
60194
|
scanMode: cachedPayload.baselineDelta ? "baseline" : isDiffMode ? "diff" : "full",
|
|
60340
|
-
baselineDegraded
|
|
60341
|
-
wholeRepoCacheHit: true
|
|
60195
|
+
baselineDegraded
|
|
60342
60196
|
});
|
|
60343
60197
|
recordOnboardingCompletion(options);
|
|
60344
60198
|
return result;
|
|
@@ -60447,7 +60301,6 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
60447
60301
|
rootSentrySpan,
|
|
60448
60302
|
scanMode: baselineDelta ? "baseline" : isDiffMode ? "diff" : "full",
|
|
60449
60303
|
baselineDegraded,
|
|
60450
|
-
wholeRepoCacheHit: false,
|
|
60451
60304
|
lintCacheHitFileCount: output.lintCacheHitFileCount,
|
|
60452
60305
|
lintCacheTotalFileCount: output.lintCacheTotalFileCount,
|
|
60453
60306
|
lintSidecarReplayedFileCount: output.lintSidecarReplayedFileCount,
|
|
@@ -60518,7 +60371,6 @@ const renderAndRecordScan = async (input) => {
|
|
|
60518
60371
|
result,
|
|
60519
60372
|
mode: input.scanMode,
|
|
60520
60373
|
gateExempt: input.baselineDegraded,
|
|
60521
|
-
wholeRepoCacheHit: input.wholeRepoCacheHit,
|
|
60522
60374
|
didLintFail: input.payload.didLintFail,
|
|
60523
60375
|
lintFailureReasonKind: input.payload.lintFailureReasonKind,
|
|
60524
60376
|
lintPartialFailureCount: input.payload.lintPartialFailures.length,
|
|
@@ -64412,4 +64264,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
64412
64264
|
export {};
|
|
64413
64265
|
|
|
64414
64266
|
//# sourceMappingURL=cli.js.map
|
|
64415
|
-
//# debugId=
|
|
64267
|
+
//# debugId=d9c72b21-3e94-5923-b692-077f137a0078
|