react-doctor 0.6.3-dev.ea9a775 → 0.7.0-dev.574cba6
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 +462 -314
- package/dist/index.js +118 -31
- package/dist/lsp.js +122 -37
- 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]="a88b7ed4-e1d2-587a-a826-36b16313d032")}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";
|
|
@@ -36130,13 +36130,14 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
|
36130
36130
|
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
36131
36131
|
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
36132
36132
|
const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
|
|
36133
|
-
const CORE_PACKAGE_VERSION = "0.
|
|
36133
|
+
const CORE_PACKAGE_VERSION = "0.7.0";
|
|
36134
36134
|
const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
|
|
36135
36135
|
const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
|
|
36136
36136
|
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;
|
|
36140
36141
|
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
36141
36142
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
36142
36143
|
const hasIgnoredPathSegment = (relativePath) => relativePath.split(/[/\\]/).slice(0, -1).some((segment) => IGNORED_DIRECTORIES.has(segment));
|
|
@@ -40608,22 +40609,37 @@ const failOpenReadJson = (filePath, fallback) => {
|
|
|
40608
40609
|
return fallback;
|
|
40609
40610
|
}
|
|
40610
40611
|
};
|
|
40612
|
+
const hashFileContents = (filePath) => {
|
|
40613
|
+
try {
|
|
40614
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
40615
|
+
} catch {
|
|
40616
|
+
return null;
|
|
40617
|
+
}
|
|
40618
|
+
};
|
|
40611
40619
|
const ANALYZED_FILE_EXTENSIONS = new Set(DEFAULT_EXTENSIONS);
|
|
40612
40620
|
const CORE_SIDE_MANIFEST_NAMES = ["knip.json", "deno.lock"];
|
|
40613
40621
|
const ANALYZED_MANIFEST_NAMES = new Set([...ANALYZED_MANIFEST_FILENAMES, ...CORE_SIDE_MANIFEST_NAMES]);
|
|
40614
40622
|
const isTsConfigLikeFile = (fileName) => (fileName.startsWith("tsconfig") || fileName.startsWith("jsconfig")) && fileName.endsWith(".json");
|
|
40615
40623
|
const isFingerprintedFile = (fileName) => ANALYZED_FILE_EXTENSIONS.has(Path.extname(fileName).toLowerCase()) || ANALYZED_MANIFEST_NAMES.has(fileName) || isTsConfigLikeFile(fileName);
|
|
40616
|
-
|
|
40617
|
-
|
|
40624
|
+
/**
|
|
40625
|
+
* Stat snapshot of every file the analysis reads, keyed by root-relative
|
|
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();
|
|
40618
40631
|
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
40619
40632
|
if (!isFingerprintedFile(name)) continue;
|
|
40620
40633
|
try {
|
|
40621
40634
|
const fileStat = NFS.statSync(absolutePath);
|
|
40622
40635
|
const relativePath = Path.relative(rootDirectory, absolutePath).replace(/\\/g, "/");
|
|
40623
|
-
|
|
40636
|
+
statByRelativePath.set(relativePath, {
|
|
40637
|
+
mtimeMs: fileStat.mtimeMs,
|
|
40638
|
+
size: fileStat.size
|
|
40639
|
+
});
|
|
40624
40640
|
} catch {}
|
|
40625
40641
|
}
|
|
40626
|
-
return
|
|
40642
|
+
return statByRelativePath;
|
|
40627
40643
|
};
|
|
40628
40644
|
const bundledRequire$2 = createRequire(import.meta.url);
|
|
40629
40645
|
const resolveDeslopVersion = () => {
|
|
@@ -40635,14 +40651,13 @@ const resolveDeslopVersion = () => {
|
|
|
40635
40651
|
}
|
|
40636
40652
|
};
|
|
40637
40653
|
const computeDeadCodeCacheKey = (input) => crypto.createHash("sha1").update(JSON.stringify({
|
|
40638
|
-
schemaVersion:
|
|
40654
|
+
schemaVersion: 2,
|
|
40639
40655
|
coreVersion: input.coreVersion,
|
|
40640
40656
|
deslopVersion: resolveDeslopVersion(),
|
|
40641
40657
|
deslopJsModuleSpecifier: input.deslopJsModuleSpecifier,
|
|
40642
40658
|
entryPatterns: input.entryPatterns,
|
|
40643
40659
|
ignorePatterns: input.ignorePatterns,
|
|
40644
|
-
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
|
|
40645
|
-
files: collectAnalyzedFileFingerprints(input.rootDirectory)
|
|
40660
|
+
tsConfigFile: input.tsConfigPath === void 0 ? null : Path.relative(input.rootDirectory, input.tsConfigPath).replace(/\\/g, "/")
|
|
40646
40661
|
})).digest("hex");
|
|
40647
40662
|
const validateDiagnostic$2 = decodeUnknownSync(Diagnostic);
|
|
40648
40663
|
const decodeCachedDiagnostics = (raw) => {
|
|
@@ -40653,16 +40668,68 @@ const decodeCachedDiagnostics = (raw) => {
|
|
|
40653
40668
|
return null;
|
|
40654
40669
|
}
|
|
40655
40670
|
};
|
|
40656
|
-
const
|
|
40657
|
-
|
|
40658
|
-
|
|
40659
|
-
|
|
40671
|
+
const isPersistedFileIdentity = (value) => Array.isArray(value) && value.length === 3 && typeof value[0] === "number" && typeof value[1] === "number" && typeof value[2] === "string";
|
|
40672
|
+
const lookupDeadCodeResultCache = (input) => {
|
|
40673
|
+
const cacheFilePath = Path.join(input.cacheDirectory, DEAD_CODE_CACHE_FILENAME);
|
|
40674
|
+
const persisted = failOpenReadJson(cacheFilePath, null);
|
|
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;
|
|
40660
40708
|
};
|
|
40661
|
-
const storeDeadCodeResultCache = (
|
|
40662
|
-
|
|
40663
|
-
|
|
40664
|
-
|
|
40665
|
-
|
|
40709
|
+
const storeDeadCodeResultCache = (input) => {
|
|
40710
|
+
const persistedFiles = {};
|
|
40711
|
+
for (const [relativePath, snapshotStat] of input.snapshotFileStats) {
|
|
40712
|
+
const absolutePath = Path.join(input.rootDirectory, relativePath);
|
|
40713
|
+
const contentHash = hashFileContents(absolutePath);
|
|
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
|
|
40666
40733
|
});
|
|
40667
40734
|
};
|
|
40668
40735
|
const readSystemFacts = () => ({
|
|
@@ -41072,16 +41139,22 @@ const checkDeadCode = async (options) => {
|
|
|
41072
41139
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
41073
41140
|
const tsConfigPath = resolveTsConfigPath(rootDirectory);
|
|
41074
41141
|
const deslopJsModuleSpecifier = options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js");
|
|
41075
|
-
const
|
|
41142
|
+
const fileStatsSnapshot = options.cacheEnabled ? collectAnalyzedFileStats(rootDirectory) : null;
|
|
41143
|
+
const cacheKey = fileStatsSnapshot === null ? null : computeDeadCodeCacheKey({
|
|
41076
41144
|
rootDirectory,
|
|
41077
41145
|
entryPatterns,
|
|
41078
41146
|
ignorePatterns,
|
|
41079
41147
|
tsConfigPath,
|
|
41080
41148
|
deslopJsModuleSpecifier,
|
|
41081
41149
|
coreVersion: CORE_PACKAGE_VERSION
|
|
41082
|
-
})
|
|
41083
|
-
if (cacheKey !== null) {
|
|
41084
|
-
const cachedDiagnostics = lookupDeadCodeResultCache(
|
|
41150
|
+
});
|
|
41151
|
+
if (cacheKey !== null && fileStatsSnapshot !== null) {
|
|
41152
|
+
const cachedDiagnostics = lookupDeadCodeResultCache({
|
|
41153
|
+
cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
|
|
41154
|
+
cacheKey,
|
|
41155
|
+
rootDirectory,
|
|
41156
|
+
currentFileStats: fileStatsSnapshot
|
|
41157
|
+
});
|
|
41085
41158
|
options.onCacheOutcome?.(cachedDiagnostics !== null);
|
|
41086
41159
|
if (cachedDiagnostics !== null) return [...cachedDiagnostics];
|
|
41087
41160
|
}
|
|
@@ -41155,7 +41228,13 @@ const checkDeadCode = async (options) => {
|
|
|
41155
41228
|
category: DEAD_CODE_CATEGORY
|
|
41156
41229
|
});
|
|
41157
41230
|
}
|
|
41158
|
-
if (cacheKey !== null) storeDeadCodeResultCache(
|
|
41231
|
+
if (cacheKey !== null && fileStatsSnapshot !== null) storeDeadCodeResultCache({
|
|
41232
|
+
cacheDirectory: resolveReactDoctorCacheDir(rootDirectory),
|
|
41233
|
+
cacheKey,
|
|
41234
|
+
rootDirectory,
|
|
41235
|
+
snapshotFileStats: fileStatsSnapshot,
|
|
41236
|
+
diagnostics
|
|
41237
|
+
});
|
|
41159
41238
|
return diagnostics;
|
|
41160
41239
|
};
|
|
41161
41240
|
const DEAD_CODE_RULE_KEY_PREFIX = `${DEAD_CODE_PLUGIN}/`;
|
|
@@ -42251,13 +42330,6 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
42251
42330
|
}
|
|
42252
42331
|
};
|
|
42253
42332
|
};
|
|
42254
|
-
const hashFileContents = (filePath) => {
|
|
42255
|
-
try {
|
|
42256
|
-
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
42257
|
-
} catch {
|
|
42258
|
-
return null;
|
|
42259
|
-
}
|
|
42260
|
-
};
|
|
42261
42333
|
const ABSENT_CONTENT_ANSWER = "absent";
|
|
42262
42334
|
/**
|
|
42263
42335
|
* Answers dependency probes for the sidecar lint cache — used both when
|
|
@@ -44398,6 +44470,20 @@ const writeCachedSocketBody = (cacheFile, body) => {
|
|
|
44398
44470
|
}));
|
|
44399
44471
|
} catch {}
|
|
44400
44472
|
};
|
|
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
|
+
};
|
|
44401
44487
|
const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
|
|
44402
44488
|
const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
|
|
44403
44489
|
if (cacheFile !== null) {
|
|
@@ -44525,6 +44611,7 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
44525
44611
|
const dependencies = collectDependenciesToScore(readPackageJson$1(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
44526
44612
|
if (dependencies.length === 0) return [];
|
|
44527
44613
|
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
44614
|
+
if (cacheDirectory !== null) pruneExpiredSocketCache(cacheDirectory);
|
|
44528
44615
|
const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
|
|
44529
44616
|
const diagnostics = [];
|
|
44530
44617
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
@@ -45410,11 +45497,9 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
45410
45497
|
const parts = [`v=${version}`];
|
|
45411
45498
|
let directory = projectDirectory;
|
|
45412
45499
|
for (;;) {
|
|
45413
|
-
for (const filename of CONFIG_FINGERPRINT_FILENAMES)
|
|
45414
|
-
const
|
|
45415
|
-
parts.push(`${directory}/${filename}=${
|
|
45416
|
-
} catch {
|
|
45417
|
-
continue;
|
|
45500
|
+
for (const filename of CONFIG_FINGERPRINT_FILENAMES) {
|
|
45501
|
+
const contentHash = hashFileContents(path.join(directory, filename));
|
|
45502
|
+
if (contentHash !== null) parts.push(`${directory}/${filename}=${contentHash}`);
|
|
45418
45503
|
}
|
|
45419
45504
|
const parent = path.dirname(directory);
|
|
45420
45505
|
if (parent === directory) break;
|
|
@@ -45710,6 +45795,7 @@ const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.
|
|
|
45710
45795
|
const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
|
|
45711
45796
|
const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
|
|
45712
45797
|
const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
|
|
45798
|
+
const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
|
|
45713
45799
|
const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
45714
45800
|
const GH_DEFAULT_BRANCH_PROBE_TIMEOUT_MS = 5e3;
|
|
45715
45801
|
const CI_TRUST_COMPANIES = "PayPal, Rippling, and Alibaba";
|
|
@@ -45801,7 +45887,7 @@ const makeNoopConsole = () => ({
|
|
|
45801
45887
|
});
|
|
45802
45888
|
//#endregion
|
|
45803
45889
|
//#region src/cli/utils/version.ts
|
|
45804
|
-
const VERSION = "0.
|
|
45890
|
+
const VERSION = "0.7.0-dev.574cba6";
|
|
45805
45891
|
//#endregion
|
|
45806
45892
|
//#region src/cli/utils/json-mode.ts
|
|
45807
45893
|
let context = null;
|
|
@@ -46170,13 +46256,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
46170
46256
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
46171
46257
|
* standard `SENTRY_RELEASE` override.
|
|
46172
46258
|
*/
|
|
46173
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.
|
|
46259
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.0-dev.574cba6`;
|
|
46174
46260
|
/**
|
|
46175
46261
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
46176
46262
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
46177
46263
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
46178
46264
|
*/
|
|
46179
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.
|
|
46265
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.0-dev.574cba6") ? "development" : "production");
|
|
46180
46266
|
/**
|
|
46181
46267
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
46182
46268
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -58254,6 +58340,276 @@ const buildRuleBlastRadii = (diagnostics) => buildSortedRuleGroups(diagnostics).
|
|
|
58254
58340
|
})).toSorted((left, right) => right.fileCount - left.fileCount);
|
|
58255
58341
|
const findMigrationScaleBuckets = (diagnostics) => buildRuleBlastRadii(diagnostics).filter((bucket) => bucket.fileCount >= 40);
|
|
58256
58342
|
//#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
|
|
58257
58613
|
//#region src/cli/utils/should-block-ci.ts
|
|
58258
58614
|
const shouldBlockCi = (diagnostics, blockingLevel) => {
|
|
58259
58615
|
if (blockingLevel === "none") return false;
|
|
@@ -58295,6 +58651,63 @@ const readEnvBoolean = (name) => {
|
|
|
58295
58651
|
if (value === void 0) return null;
|
|
58296
58652
|
return value.toLowerCase() === "true" || value === "1";
|
|
58297
58653
|
};
|
|
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
|
+
};
|
|
58298
58711
|
const resolveVersionPin = (versionInput) => {
|
|
58299
58712
|
if (versionInput === void 0 || versionInput.trim() === "") return null;
|
|
58300
58713
|
if (versionInput === "latest") return "latest";
|
|
@@ -58400,10 +58813,10 @@ const buildOutcomeAttributes = (input) => {
|
|
|
58400
58813
|
deadlineSkippedFileCount: input.lintDeadlineSkippedFileCount ?? null,
|
|
58401
58814
|
cacheHitFiles: result.lintCacheHitFileCount ?? null,
|
|
58402
58815
|
cacheTotalFiles: result.lintCacheTotalFileCount ?? null,
|
|
58403
|
-
cacheHitRatio:
|
|
58816
|
+
cacheHitRatio: ratioOf(result.lintCacheHitFileCount, result.lintCacheTotalFileCount),
|
|
58404
58817
|
sidecarReplayedFiles: result.lintSidecarReplayedFileCount ?? null,
|
|
58405
58818
|
sidecarTotalFiles: result.lintSidecarTotalFileCount ?? null,
|
|
58406
|
-
sidecarReplayRatio:
|
|
58819
|
+
sidecarReplayRatio: ratioOf(result.lintSidecarReplayedFileCount, result.lintSidecarTotalFileCount)
|
|
58407
58820
|
}),
|
|
58408
58821
|
...withNamespace("deadCode", {
|
|
58409
58822
|
failed: input.didDeadCodeFail ?? null,
|
|
@@ -58470,7 +58883,7 @@ const buildScanAttributes = (input) => {
|
|
|
58470
58883
|
* Projects a scan into the namespaced attribute set for its root span — the
|
|
58471
58884
|
* canonical per-scan "wide event". Every attribute carries a dotted namespace
|
|
58472
58885
|
* that groups it by concept (`scan.*` config, `action.*` CI knobs, `outcome.*`
|
|
58473
|
-
* verdict, `diag.*` findings, `score.*`, `lint.*`, `deadCode.*`,
|
|
58886
|
+
* verdict, `diag.*` findings, `score.*`, `lint.*`, `deadCode.*`, `cache.*`,
|
|
58474
58887
|
* `supplyChain.*`, `timing.*`, `migration.*`, `baseline.*`) so the attributes
|
|
58475
58888
|
* tree up in Sentry's attribute browser and stay filter-/group-/aggregate-able
|
|
58476
58889
|
* in the Spans dataset. Pure and exported so the projection (outcome
|
|
@@ -58484,7 +58897,8 @@ const buildScanAttributes = (input) => {
|
|
|
58484
58897
|
const buildRunEventAttributes = (input) => toSpanAttributes({
|
|
58485
58898
|
...buildScanAttributes(input),
|
|
58486
58899
|
...buildActionAttributes(),
|
|
58487
|
-
...buildOutcomeAttributes(input)
|
|
58900
|
+
...buildOutcomeAttributes(input),
|
|
58901
|
+
...buildCacheAttributes(input)
|
|
58488
58902
|
});
|
|
58489
58903
|
/**
|
|
58490
58904
|
* Stamps the wide-event attributes onto the run's root span. A guarded no-op
|
|
@@ -59694,275 +60108,6 @@ const resolveCliCategories = (categoryFlag) => {
|
|
|
59694
60108
|
return resolvedCategories.length > 0 ? resolvedCategories : void 0;
|
|
59695
60109
|
};
|
|
59696
60110
|
//#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
|
|
59966
60111
|
//#region src/inspect.ts
|
|
59967
60112
|
const silentConsole = makeNoopConsole();
|
|
59968
60113
|
const runConsole = (effect) => {
|
|
@@ -60139,7 +60284,7 @@ const runBaselineComparison = async (params) => {
|
|
|
60139
60284
|
suppressScanSummary: true,
|
|
60140
60285
|
supplyChainManifestChanged: params.options.supplyChainManifestChanged,
|
|
60141
60286
|
deadlineEpochMs: params.deadlineEpochMs ?? void 0
|
|
60142
|
-
}, {}).pipe(provide(baseLayers), provideService$2(Console, silentConsole))));
|
|
60287
|
+
}, {}).pipe(provide(baseLayers), provideService$2(PerFileLintCacheEnabled, false), provideService$2(SidecarLintCacheEnabled, false), provideService$2(Console, silentConsole))));
|
|
60143
60288
|
if (baseOutput.didLintFail || countIncompleteLintFiles(baseOutput.lintPartialFailures) > 0) return null;
|
|
60144
60289
|
const delta = computeDiagnosticDelta({
|
|
60145
60290
|
headDiagnostics: params.headDiagnostics,
|
|
@@ -60192,7 +60337,8 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
60192
60337
|
startTime,
|
|
60193
60338
|
rootSentrySpan,
|
|
60194
60339
|
scanMode: cachedPayload.baselineDelta ? "baseline" : isDiffMode ? "diff" : "full",
|
|
60195
|
-
baselineDegraded
|
|
60340
|
+
baselineDegraded,
|
|
60341
|
+
wholeRepoCacheHit: true
|
|
60196
60342
|
});
|
|
60197
60343
|
recordOnboardingCompletion(options);
|
|
60198
60344
|
return result;
|
|
@@ -60301,6 +60447,7 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
60301
60447
|
rootSentrySpan,
|
|
60302
60448
|
scanMode: baselineDelta ? "baseline" : isDiffMode ? "diff" : "full",
|
|
60303
60449
|
baselineDegraded,
|
|
60450
|
+
wholeRepoCacheHit: false,
|
|
60304
60451
|
lintCacheHitFileCount: output.lintCacheHitFileCount,
|
|
60305
60452
|
lintCacheTotalFileCount: output.lintCacheTotalFileCount,
|
|
60306
60453
|
lintSidecarReplayedFileCount: output.lintSidecarReplayedFileCount,
|
|
@@ -60371,6 +60518,7 @@ const renderAndRecordScan = async (input) => {
|
|
|
60371
60518
|
result,
|
|
60372
60519
|
mode: input.scanMode,
|
|
60373
60520
|
gateExempt: input.baselineDegraded,
|
|
60521
|
+
wholeRepoCacheHit: input.wholeRepoCacheHit,
|
|
60374
60522
|
didLintFail: input.payload.didLintFail,
|
|
60375
60523
|
lintFailureReasonKind: input.payload.lintFailureReasonKind,
|
|
60376
60524
|
lintPartialFailureCount: input.payload.lintPartialFailures.length,
|
|
@@ -64264,4 +64412,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
64264
64412
|
export {};
|
|
64265
64413
|
|
|
64266
64414
|
//# sourceMappingURL=cli.js.map
|
|
64267
|
-
//# debugId=
|
|
64415
|
+
//# debugId=a88b7ed4-e1d2-587a-a826-36b16313d032
|