react-doctor 0.9.12-dev.afa1780 → 0.9.12-dev.ca30808
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/{build-section-divider-B9vlCDHV.js → build-section-divider-DGcPvzjX.js} +7 -7
- package/dist/{ci-rLzlDu2j.js → ci-DZ9QnUV4.js} +12 -12
- package/dist/cli.js +65 -37
- package/dist/{design-CzVqEwWg.js → design-BlxQroSp.js} +4 -4
- package/dist/{devtools-BiC-AP3B.js → devtools-CC2PAZ85.js} +3 -3
- package/dist/{exit-gracefully-Bq-JF68q.js → exit-gracefully-Dt1ix6Xh.js} +6 -6
- package/dist/{get-east-asian-width-Cglw13G7.js → get-east-asian-width-Bv2TD3Xj.js} +3 -3
- package/dist/{handle-error-DUSoFUPL.js → handle-error-Cslt8b7C.js} +5 -5
- package/dist/index.js +232 -121
- package/dist/{inspect-CNeq8v8s.js → inspect-Dd1qqPJf.js} +38 -29
- package/dist/{install-DP8oO5sw.js → install-DeWeUjqr.js} +9 -9
- package/dist/{install-doctor-script-DbnMjPjQ.js → install-doctor-script-DbKR-GHo.js} +5 -5
- package/dist/{install-github-workflow-CS_YO_OJ.js → install-github-workflow-Bkv_CMdF.js} +10 -6
- package/dist/{install-react-doctor-BTiagoIZ.js → install-react-doctor-DkfLDtWd.js} +8 -8
- package/dist/{jsx-runtime-DGcmMln7.js → jsx-runtime-DwPH8QhA.js} +7 -7
- package/dist/path-format-DnHpmJwG.js +9 -0
- package/dist/{print-branded-header-bKbewJRI.js → print-branded-header-CFNvp2ey.js} +4 -4
- package/dist/project-analysis-worker.js +53 -52
- package/dist/{prompt-runtime-scan-url-CvsHajZD.js → prompt-runtime-scan-url-BuXp7_n4.js} +6 -6
- package/dist/{read-object-file-DIGDC3MB.js → read-object-file-CEbx-anl.js} +4 -4
- package/dist/{record-metric-C4-VKG9G.js → record-metric-BzGcVSov.js} +228 -116
- package/dist/{report-error-1lrvJd3S.js → report-error-ZMTPN93d.js} +5 -5
- package/dist/{report-workflow-result-C42nztEL.js → report-workflow-result-CKUjTJpB.js} +5 -5
- package/dist/{resolve-scope-YUP_rtf3.js → resolve-scope-BgrQEVtj.js} +8 -10
- package/dist/{rules-BY4vq5-i.js → rules-DizN5gJ6.js} +6 -6
- package/dist/{run-scan-app-DHDnmfX6.js → run-scan-app-CjSGMmOE.js} +16 -14
- package/dist/{runtime-scan-ClVvgojx.js → runtime-scan-DINB89wl.js} +9 -9
- package/dist/{sanitize-runtime-url-DVwfWWZy.js → sanitize-runtime-url-CFvLC0Vp.js} +4 -4
- package/dist/{scan-C9YMcGrG.js → scan-CEvjogRY.js} +17 -11
- package/dist/{should-fail-scan-gate-CtPrWy9N.js → should-fail-scan-gate-gpuv_UOL.js} +19 -12
- package/dist/{should-skip-prompts-b1KKh2nz.js → should-skip-prompts-F0NaNR8z.js} +4 -4
- package/dist/{should-use-tui-Dh_kmKLh.js → should-use-tui-Dve0A4YR.js} +4 -4
- package/dist/{telemetry-runtime-CDAL4uhm.js → telemetry-runtime-Bs7DsGaF.js} +4 -4
- package/dist/{unref-stdin-BKT_s38a.js → unref-stdin-SK3WfwxS.js} +3 -3
- package/dist/{version-CDNMEvQU.js → version-DVa-lN1C.js} +4 -4
- package/dist/{why-fw7vWXR7.js → why-BqR3fLtE.js} +4 -4
- package/dist/{with-run-span-V4LjJBYW.js → with-run-span-NbAvibgv.js} +5 -5
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
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]="efcc9c57-f9b3-579e-af01-d0b87088c43f")}catch(e){}}();
|
|
3
3
|
import { i as __toESM, r as __require, t as __commonJSMin } from "./chunk-ChKA3wi6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as path$1$1 from "node:path";
|
|
6
|
-
import path from "node:path";
|
|
6
|
+
import path, { join, posix } from "node:path";
|
|
7
7
|
import * as fs$1$3 from "node:fs";
|
|
8
|
-
import fs from "node:fs";
|
|
8
|
+
import fs, { existsSync } from "node:fs";
|
|
9
9
|
import * as NodeChildProcess from "node:child_process";
|
|
10
10
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
11
11
|
import ts from "typescript";
|
|
12
|
+
import { parseSync } from "oxc-parser";
|
|
12
13
|
import { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_DEPENDENCY_COLLECTORS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, FRAMEWORK_TOKENS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_OPT_IN_PROJECT_RULE_IDS, REACT_DOCTOR_PROJECT_RULES, REACT_DOCTOR_RULES, REACT_DOCTOR_RULE_REGISTRY, REACT_DOCTOR_SCAN_RULES, classifySecurityScanFile, collectCrossFileDependencyProbes, resetManifestCaches, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor/core";
|
|
13
14
|
import * as Crypto from "node:crypto";
|
|
14
15
|
import crypto, { createHash, randomUUID } from "node:crypto";
|
|
@@ -18,7 +19,7 @@ import { fileURLToPath } from "node:url";
|
|
|
18
19
|
import * as OS from "node:os";
|
|
19
20
|
import os from "node:os";
|
|
20
21
|
import { createJiti } from "jiti";
|
|
21
|
-
import { parseJSON5 } from "confbox";
|
|
22
|
+
import { parseJSON5, parseJSONC } from "confbox";
|
|
22
23
|
import * as fs$1 from "node:fs/promises";
|
|
23
24
|
import { PassThrough } from "node:stream";
|
|
24
25
|
import { convertToTSX } from "@astrojs/compiler/sync";
|
|
@@ -1883,7 +1884,7 @@ var require_picomatch$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
1883
1884
|
module.exports = picomatch;
|
|
1884
1885
|
}));
|
|
1885
1886
|
//#endregion
|
|
1886
|
-
//#region ../core/dist/collect-unplugin-auto-import-global-scopes-
|
|
1887
|
+
//#region ../core/dist/collect-unplugin-auto-import-global-scopes-hOS9iAJg.js
|
|
1887
1888
|
var import_picomatch = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1888
1889
|
const pico = require_picomatch$1();
|
|
1889
1890
|
const utils = require_utils();
|
|
@@ -2397,6 +2398,74 @@ const compileGlobPatternsLenient = (patterns, onInvalid) => {
|
|
|
2397
2398
|
}
|
|
2398
2399
|
return compiled;
|
|
2399
2400
|
};
|
|
2401
|
+
const isOxcAstNode = (value) => Boolean(value) && typeof value === "object" && typeof value.type === "string";
|
|
2402
|
+
const getStaticModuleSpecifier = (value) => {
|
|
2403
|
+
if (!isOxcAstNode(value)) return void 0;
|
|
2404
|
+
if (typeof value.value === "string") return value.value;
|
|
2405
|
+
if (value.type === "TemplateLiteral" && Array.isArray(value.expressions) && value.expressions.length === 0 && Array.isArray(value.quasis) && isOxcAstNode(value.quasis[0]) && value.quasis[0].value && typeof value.quasis[0].value === "object" && "cooked" in value.quasis[0].value && typeof value.quasis[0].value.cooked === "string") return value.quasis[0].value.cooked;
|
|
2406
|
+
};
|
|
2407
|
+
const collectStaticModuleSpecifiers = (sourceText, options = {}) => {
|
|
2408
|
+
const { filePath = "module.tsx", includeTypeOnly = true } = options;
|
|
2409
|
+
const parsedModule = parseSync(filePath, sourceText, { sourceType: "unambiguous" });
|
|
2410
|
+
const moduleSpecifiers = /* @__PURE__ */ new Set();
|
|
2411
|
+
const addSpecifier = (value) => {
|
|
2412
|
+
const moduleSpecifier = getStaticModuleSpecifier(value);
|
|
2413
|
+
if (moduleSpecifier) moduleSpecifiers.add(moduleSpecifier);
|
|
2414
|
+
};
|
|
2415
|
+
const visitNode = (value) => {
|
|
2416
|
+
if (Array.isArray(value)) {
|
|
2417
|
+
for (const child of value) visitNode(child);
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
if (!isOxcAstNode(value)) return;
|
|
2421
|
+
if (value.type === "ImportDeclaration") {
|
|
2422
|
+
const specifiers = Array.isArray(value.specifiers) ? value.specifiers : [];
|
|
2423
|
+
const hasRuntimeSpecifier = specifiers.some((specifier) => !isOxcAstNode(specifier) || specifier.importKind !== "type");
|
|
2424
|
+
if (includeTypeOnly || value.importKind !== "type" && (specifiers.length === 0 || hasRuntimeSpecifier)) addSpecifier(value.source);
|
|
2425
|
+
} else if (value.type === "ExportNamedDeclaration") {
|
|
2426
|
+
const specifiers = Array.isArray(value.specifiers) ? value.specifiers : [];
|
|
2427
|
+
const hasRuntimeSpecifier = specifiers.some((specifier) => !isOxcAstNode(specifier) || specifier.exportKind !== "type");
|
|
2428
|
+
if (includeTypeOnly || value.exportKind !== "type" && (specifiers.length === 0 || hasRuntimeSpecifier)) addSpecifier(value.source);
|
|
2429
|
+
} else if (value.type === "ExportAllDeclaration" || value.type === "ImportExpression") {
|
|
2430
|
+
if (includeTypeOnly || value.exportKind !== "type") addSpecifier(value.source);
|
|
2431
|
+
} else if (value.type === "TSImportType") {
|
|
2432
|
+
if (includeTypeOnly) addSpecifier(value.source);
|
|
2433
|
+
} else if (value.type === "TSExternalModuleReference") addSpecifier(value.expression);
|
|
2434
|
+
else if (value.type === "CallExpression" && isOxcAstNode(value.callee) && value.callee.type === "Identifier" && value.callee.name === "require" && Array.isArray(value.arguments) && value.arguments.length > 0) addSpecifier(value.arguments[0]);
|
|
2435
|
+
for (const child of Object.values(value)) visitNode(child);
|
|
2436
|
+
};
|
|
2437
|
+
visitNode(parsedModule.program);
|
|
2438
|
+
return moduleSpecifiers;
|
|
2439
|
+
};
|
|
2440
|
+
const toPosixPath = (filePath) => filePath.replace(/\\/g, "/");
|
|
2441
|
+
const isPathInsideDirectoryOrEqual = (candidatePath, directoryPath) => {
|
|
2442
|
+
const normalizedCandidatePath = posix.normalize(toPosixPath(candidatePath));
|
|
2443
|
+
const normalizedDirectoryPath = posix.normalize(toPosixPath(directoryPath));
|
|
2444
|
+
const relativePath = posix.relative(normalizedDirectoryPath, normalizedCandidatePath);
|
|
2445
|
+
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith("../") && !posix.isAbsolute(relativePath);
|
|
2446
|
+
};
|
|
2447
|
+
const RESOLVABLE_EXTENSIONS = [
|
|
2448
|
+
".ts",
|
|
2449
|
+
".tsx",
|
|
2450
|
+
".js",
|
|
2451
|
+
".jsx",
|
|
2452
|
+
".mjs",
|
|
2453
|
+
".mts",
|
|
2454
|
+
".cjs",
|
|
2455
|
+
".cts",
|
|
2456
|
+
".es6"
|
|
2457
|
+
];
|
|
2458
|
+
const resolveEntryWithExtensions = (basePath) => {
|
|
2459
|
+
if (existsSync(basePath)) return basePath;
|
|
2460
|
+
for (const extension of RESOLVABLE_EXTENSIONS) {
|
|
2461
|
+
const withExtension = basePath + extension;
|
|
2462
|
+
if (existsSync(withExtension)) return withExtension;
|
|
2463
|
+
}
|
|
2464
|
+
for (const extension of RESOLVABLE_EXTENSIONS) {
|
|
2465
|
+
const indexCandidate = join(basePath, `index${extension}`);
|
|
2466
|
+
if (existsSync(indexCandidate)) return indexCandidate;
|
|
2467
|
+
}
|
|
2468
|
+
};
|
|
2400
2469
|
/**
|
|
2401
2470
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
2402
2471
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -53517,7 +53586,7 @@ const await_ = fiberAwait;
|
|
|
53517
53586
|
* @category combinators
|
|
53518
53587
|
* @since 2.0.0
|
|
53519
53588
|
*/
|
|
53520
|
-
const join = fiberJoin;
|
|
53589
|
+
const join$1 = fiberJoin;
|
|
53521
53590
|
/**
|
|
53522
53591
|
* Interrupts a fiber, causing it to stop executing and clean up any
|
|
53523
53592
|
* acquired resources.
|
|
@@ -55514,7 +55583,7 @@ const mapEffectConcurrent = (self, f, options) => fromTransformBracket(fnUntrace
|
|
|
55514
55583
|
const fiber = runFork(f(value, i++));
|
|
55515
55584
|
trackFiber(fiber);
|
|
55516
55585
|
fiber.addObserver(onExit);
|
|
55517
|
-
return offer(effects, join(fiber));
|
|
55586
|
+
return offer(effects, join$1(fiber));
|
|
55518
55587
|
}), forever({ disableYield: true }), catchCause$1((cause) => offer(effects, failCause$3(cause)).pipe(andThen(failCause$1(effects, cause)))), forkIn(forkedScope));
|
|
55519
55588
|
}
|
|
55520
55589
|
return take(queue);
|
|
@@ -55840,9 +55909,9 @@ const mergeAll = /* @__PURE__ */ dual(2, (channels, { bufferSize = 16, concurren
|
|
|
55840
55909
|
if (semaphore) if (fibers.size < concurrencyN) yield* semaphore.take(1);
|
|
55841
55910
|
else {
|
|
55842
55911
|
pullFiber = yield* forkChild(pull);
|
|
55843
|
-
yield* raceFirst(semaphore.take(1), andThen(join(pullFiber), never));
|
|
55912
|
+
yield* raceFirst(semaphore.take(1), andThen(join$1(pullFiber), never));
|
|
55844
55913
|
}
|
|
55845
|
-
const channel = pullFiber === void 0 ? yield* pull : yield* join(pullFiber);
|
|
55914
|
+
const channel = pullFiber === void 0 ? yield* pull : yield* join$1(pullFiber);
|
|
55846
55915
|
const childScope = forkUnsafe(forkedScope);
|
|
55847
55916
|
const childPull = yield* toTransform(channel)(upstream, childScope);
|
|
55848
55917
|
while (fibers.size >= concurrencyN) {
|
|
@@ -67600,12 +67669,26 @@ const getEquivalentRuleKeys = (ruleKey) => {
|
|
|
67600
67669
|
const nativeRuleKey = canonicalizeRuleKey(ruleKey);
|
|
67601
67670
|
return [nativeRuleKey, ...getLegacyRuleKeysForNative(nativeRuleKey)];
|
|
67602
67671
|
};
|
|
67603
|
-
|
|
67604
|
-
|
|
67605
|
-
|
|
67606
|
-
|
|
67607
|
-
|
|
67672
|
+
/**
|
|
67673
|
+
* Resolves the absolute path to read for a diagnostic's `filePath`,
|
|
67674
|
+
* accounting for the various shapes oxlint emits:
|
|
67675
|
+
*
|
|
67676
|
+
* - `file://` URL — decode to an absolute filesystem path.
|
|
67677
|
+
* - Absolute POSIX (`/abs/path/file.tsx`) — pass through.
|
|
67678
|
+
* - Absolute Windows (`C:/...` or `C:\...`) — pass through.
|
|
67679
|
+
* - `./relative` or bare relative — join against `rootDirectory`.
|
|
67680
|
+
*
|
|
67681
|
+
* Shared between the streaming diagnostic pipeline and the legacy
|
|
67682
|
+
* array-shaped `mergeAndFilterDiagnostics` wrapper so file-line lookups
|
|
67683
|
+
* use one canonical resolution path.
|
|
67684
|
+
*/
|
|
67685
|
+
const resolveCandidateReadPath = (rootDirectory, filePath) => {
|
|
67686
|
+
if (filePath.startsWith("file://")) return fileURLToPath(filePath);
|
|
67687
|
+
const normalizedFile = filePath.replace(/\\/g, "/");
|
|
67688
|
+
if (normalizedFile.startsWith("/") || /^[a-zA-Z]:\//.test(normalizedFile) || /^[a-zA-Z]:\\/.test(filePath)) return filePath;
|
|
67689
|
+
return `${rootDirectory.replace(/\\/g, "/").replace(/\/$/, "")}/${normalizedFile.replace(/^\.\//, "")}`;
|
|
67608
67690
|
};
|
|
67691
|
+
const toNormalizedRelativePath = (filePath, rootDirectory) => path$1$1.relative(path$1$1.resolve(rootDirectory), path$1$1.resolve(resolveCandidateReadPath(rootDirectory, filePath))).replaceAll("\\", "/") || ".";
|
|
67609
67692
|
const warnConfigIssue = (message) => {
|
|
67610
67693
|
process.stderr.write(`[react-doctor] ${message}\n`);
|
|
67611
67694
|
};
|
|
@@ -67654,7 +67737,7 @@ const compileIgnoreOverrides = (userConfig) => {
|
|
|
67654
67737
|
};
|
|
67655
67738
|
const isDiagnosticIgnoredByOverrides = (diagnostic, rootDirectory, overrides) => {
|
|
67656
67739
|
if (overrides.length === 0) return false;
|
|
67657
|
-
const relativeFilePath =
|
|
67740
|
+
const relativeFilePath = toNormalizedRelativePath(diagnostic.filePath, rootDirectory);
|
|
67658
67741
|
const ruleIdentifier = `${diagnostic.plugin}/${diagnostic.rule}`;
|
|
67659
67742
|
return overrides.some((override) => override.filePatterns.some((pattern) => pattern.test(relativeFilePath)) && hasMatchingRuleOverride(override.ruleIds, ruleIdentifier));
|
|
67660
67743
|
};
|
|
@@ -67994,7 +68077,7 @@ const compileIgnoredFilePatterns = (userConfig) => {
|
|
|
67994
68077
|
};
|
|
67995
68078
|
const isFileIgnoredByPatterns = (filePath, rootDirectory, patterns) => {
|
|
67996
68079
|
if (patterns.length === 0) return false;
|
|
67997
|
-
const relativePath =
|
|
68080
|
+
const relativePath = toNormalizedRelativePath(filePath, rootDirectory);
|
|
67998
68081
|
return patterns.some((pattern) => pattern.test(relativePath));
|
|
67999
68082
|
};
|
|
68000
68083
|
const SCRIPT_EXTENSION_FRAGMENT = "[cm]?[jt]sx?";
|
|
@@ -68122,25 +68205,6 @@ const classifyPackageRole = (filename) => {
|
|
|
68122
68205
|
return result;
|
|
68123
68206
|
};
|
|
68124
68207
|
/**
|
|
68125
|
-
* Resolves the absolute path to read for a diagnostic's `filePath`,
|
|
68126
|
-
* accounting for the various shapes oxlint emits:
|
|
68127
|
-
*
|
|
68128
|
-
* - `file://` URL — decode to an absolute filesystem path.
|
|
68129
|
-
* - Absolute POSIX (`/abs/path/file.tsx`) — pass through.
|
|
68130
|
-
* - Absolute Windows (`C:/...` or `C:\...`) — pass through.
|
|
68131
|
-
* - `./relative` or bare relative — join against `rootDirectory`.
|
|
68132
|
-
*
|
|
68133
|
-
* Shared between the streaming diagnostic pipeline and the legacy
|
|
68134
|
-
* array-shaped `mergeAndFilterDiagnostics` wrapper so file-line lookups
|
|
68135
|
-
* use one canonical resolution path.
|
|
68136
|
-
*/
|
|
68137
|
-
const resolveCandidateReadPath = (rootDirectory, filePath) => {
|
|
68138
|
-
if (filePath.startsWith("file://")) return fileURLToPath(filePath);
|
|
68139
|
-
const normalizedFile = filePath.replace(/\\/g, "/");
|
|
68140
|
-
if (normalizedFile.startsWith("/") || /^[a-zA-Z]:\//.test(normalizedFile) || /^[a-zA-Z]:\\/.test(filePath)) return filePath;
|
|
68141
|
-
return `${rootDirectory.replace(/\\/g, "/").replace(/\/$/, "")}/${normalizedFile.replace(/^\.\//, "")}`;
|
|
68142
|
-
};
|
|
68143
|
-
/**
|
|
68144
68208
|
* Shared raw-line scanners that detect whether a diagnostic site is
|
|
68145
68209
|
* enclosed by a configured `textComponents` entry or a
|
|
68146
68210
|
* `rawTextWrapperComponents` entry. Both checks are used by the
|
|
@@ -69575,6 +69639,28 @@ const checkExpoEnvLocalFiles = (context) => {
|
|
|
69575
69639
|
help: `Add \`.env*.local\` to your .gitignore. If already committed, untrack with \`git rm --cached ${committedEnvFiles.join(" ")}\``
|
|
69576
69640
|
})];
|
|
69577
69641
|
};
|
|
69642
|
+
const JAVASCRIPT_MODULE_FILE_PATTERN = /\.[cm]?[jt]sx?$/;
|
|
69643
|
+
const hasStaticModuleSubpath = (rootDirectory, packageName) => {
|
|
69644
|
+
const packageSubpathPrefix = `${packageName}/`;
|
|
69645
|
+
for (const { absolutePath, name } of walkSourceTreeFiles(rootDirectory)) {
|
|
69646
|
+
if (!JAVASCRIPT_MODULE_FILE_PATTERN.test(name)) continue;
|
|
69647
|
+
let sourceText;
|
|
69648
|
+
try {
|
|
69649
|
+
sourceText = fs$1$3.readFileSync(absolutePath, "utf-8");
|
|
69650
|
+
} catch {
|
|
69651
|
+
continue;
|
|
69652
|
+
}
|
|
69653
|
+
if (!sourceText.includes(packageSubpathPrefix)) continue;
|
|
69654
|
+
let moduleSpecifiers;
|
|
69655
|
+
try {
|
|
69656
|
+
moduleSpecifiers = collectStaticModuleSpecifiers(sourceText, { filePath: absolutePath });
|
|
69657
|
+
} catch {
|
|
69658
|
+
continue;
|
|
69659
|
+
}
|
|
69660
|
+
for (const moduleSpecifier of moduleSpecifiers) if (moduleSpecifier.startsWith(packageSubpathPrefix)) return true;
|
|
69661
|
+
}
|
|
69662
|
+
return false;
|
|
69663
|
+
};
|
|
69578
69664
|
const isExpoSdkAtLeast = (expoSdkMajor, minMajor) => expoSdkMajor !== null && expoSdkMajor >= minMajor;
|
|
69579
69665
|
const UNIMODULES_HELP = "Remove every `@unimodules/*` and `react-native-unimodules` package — their functionality now lives in `expo-modules-core`. See https://expo.fyi/r/sdk-44-remove-unimodules";
|
|
69580
69666
|
const FIREBASE_HELP = "Use the Firebase JS SDK or React Native Firebase directly. See https://expo.fyi/firebase-migration-guide";
|
|
@@ -69628,7 +69714,8 @@ const FLAGGED_DEPENDENCIES = [
|
|
|
69628
69714
|
packageName: "@expo/metro-config",
|
|
69629
69715
|
rule: "expo-no-redundant-dependency",
|
|
69630
69716
|
message: "\"@expo/metro-config\" should not be a direct dependency. Expo pins the compatible Metro config, and a direct entry can drift to a version that breaks bundling",
|
|
69631
|
-
help: "Remove `@expo/metro-config` and import `expo/metro-config` in your metro.config.js"
|
|
69717
|
+
help: "Remove `@expo/metro-config` and import `expo/metro-config` in your metro.config.js",
|
|
69718
|
+
skipWhenSubpathImported: true
|
|
69632
69719
|
},
|
|
69633
69720
|
{
|
|
69634
69721
|
packageName: "@types/react-native",
|
|
@@ -69689,8 +69776,9 @@ const FLAGGED_DEPENDENCIES = [
|
|
|
69689
69776
|
];
|
|
69690
69777
|
const checkExpoFlaggedDependencies = (context) => FLAGGED_DEPENDENCIES.filter((flaggedDependency) => {
|
|
69691
69778
|
if (!context.directDependencyNames.has(flaggedDependency.packageName)) return false;
|
|
69692
|
-
if (flaggedDependency.minSdkMajor
|
|
69693
|
-
|
|
69779
|
+
if (flaggedDependency.minSdkMajor !== void 0 && !isExpoSdkAtLeast(context.expoSdkMajor, flaggedDependency.minSdkMajor)) return false;
|
|
69780
|
+
if (flaggedDependency.skipWhenSubpathImported && hasStaticModuleSubpath(context.rootDirectory, flaggedDependency.packageName)) return false;
|
|
69781
|
+
return true;
|
|
69694
69782
|
}).map((flaggedDependency) => buildExpoDiagnostic({
|
|
69695
69783
|
rule: flaggedDependency.rule,
|
|
69696
69784
|
message: flaggedDependency.message,
|
|
@@ -69759,18 +69847,58 @@ const METRO_CONFIG_FILE_NAMES = [
|
|
|
69759
69847
|
const EXPO_METRO_CONFIG_EXTEND_SIGNALS = [
|
|
69760
69848
|
"expo/metro-config",
|
|
69761
69849
|
"@sentry/react-native/metro",
|
|
69762
|
-
"getSentryExpoConfig"
|
|
69850
|
+
"getSentryExpoConfig",
|
|
69851
|
+
"posthog-react-native/metro",
|
|
69852
|
+
"getPostHogExpoConfig"
|
|
69763
69853
|
];
|
|
69854
|
+
const resolveLocalMetroConfigModule = (moduleSpecifier, importingFilePath, rootDirectory) => {
|
|
69855
|
+
if (!moduleSpecifier.startsWith(".")) return void 0;
|
|
69856
|
+
const importedPath = path$1$1.resolve(path$1$1.dirname(importingFilePath), moduleSpecifier);
|
|
69857
|
+
const candidateBasePaths = [
|
|
69858
|
+
importedPath,
|
|
69859
|
+
importedPath.replace(/\.[cm]?js$/, ""),
|
|
69860
|
+
path$1$1.join(importedPath, "index")
|
|
69861
|
+
];
|
|
69862
|
+
for (const candidateBasePath of candidateBasePaths) {
|
|
69863
|
+
const resolvedPath = resolveEntryWithExtensions(candidateBasePath);
|
|
69864
|
+
if (resolvedPath !== void 0 && isFile(resolvedPath) && isPathInsideDirectoryOrEqual(resolvedPath, rootDirectory)) return resolvedPath;
|
|
69865
|
+
}
|
|
69866
|
+
};
|
|
69867
|
+
const hasExpoMetroConfigSignal = (metroConfigPath, rootDirectory) => {
|
|
69868
|
+
const pendingFilePaths = [metroConfigPath];
|
|
69869
|
+
const visitedFilePaths = /* @__PURE__ */ new Set();
|
|
69870
|
+
while (pendingFilePaths.length > 0) {
|
|
69871
|
+
const currentFilePath = pendingFilePaths.pop();
|
|
69872
|
+
if (currentFilePath === void 0 || visitedFilePaths.has(currentFilePath)) continue;
|
|
69873
|
+
if (visitedFilePaths.size >= 100) return true;
|
|
69874
|
+
visitedFilePaths.add(currentFilePath);
|
|
69875
|
+
let contents;
|
|
69876
|
+
try {
|
|
69877
|
+
contents = fs$1$3.readFileSync(currentFilePath, "utf-8");
|
|
69878
|
+
} catch {
|
|
69879
|
+
return true;
|
|
69880
|
+
}
|
|
69881
|
+
if (EXPO_METRO_CONFIG_EXTEND_SIGNALS.some((signal) => contents.includes(signal))) return true;
|
|
69882
|
+
let moduleSpecifiers;
|
|
69883
|
+
try {
|
|
69884
|
+
moduleSpecifiers = collectStaticModuleSpecifiers(contents, {
|
|
69885
|
+
filePath: currentFilePath,
|
|
69886
|
+
includeTypeOnly: false
|
|
69887
|
+
});
|
|
69888
|
+
} catch {
|
|
69889
|
+
continue;
|
|
69890
|
+
}
|
|
69891
|
+
for (const moduleSpecifier of moduleSpecifiers) {
|
|
69892
|
+
const localModulePath = resolveLocalMetroConfigModule(moduleSpecifier, currentFilePath, rootDirectory);
|
|
69893
|
+
if (localModulePath !== void 0 && !visitedFilePaths.has(localModulePath)) pendingFilePaths.push(localModulePath);
|
|
69894
|
+
}
|
|
69895
|
+
}
|
|
69896
|
+
return false;
|
|
69897
|
+
};
|
|
69764
69898
|
const checkExpoMetroConfig = (context) => {
|
|
69765
69899
|
const metroConfigPath = METRO_CONFIG_FILE_NAMES.map((fileName) => path$1$1.join(context.rootDirectory, fileName)).find((candidatePath) => isFile(candidatePath));
|
|
69766
69900
|
if (metroConfigPath === void 0) return [];
|
|
69767
|
-
|
|
69768
|
-
try {
|
|
69769
|
-
contents = fs$1$3.readFileSync(metroConfigPath, "utf-8");
|
|
69770
|
-
} catch {
|
|
69771
|
-
return [];
|
|
69772
|
-
}
|
|
69773
|
-
if (EXPO_METRO_CONFIG_EXTEND_SIGNALS.some((signal) => contents.includes(signal))) return [];
|
|
69901
|
+
if (hasExpoMetroConfigSignal(metroConfigPath, context.rootDirectory)) return [];
|
|
69774
69902
|
return [buildExpoDiagnostic({
|
|
69775
69903
|
rule: "expo-metro-config",
|
|
69776
69904
|
filePath: path$1$1.basename(metroConfigPath),
|
|
@@ -71512,6 +71640,12 @@ const withProjectAnalysisWorkerSlot = async (task, abortSignal) => {
|
|
|
71512
71640
|
});
|
|
71513
71641
|
return projectAnalysisWorkerSlots.run(task, abortSignal);
|
|
71514
71642
|
};
|
|
71643
|
+
const toRelativePath = (filePath, rootDirectory) => {
|
|
71644
|
+
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
71645
|
+
const normalizedRoot = rootDirectory.replace(/\\/g, "/").replace(/\/$/, "") + "/";
|
|
71646
|
+
if (normalizedFilePath.startsWith(normalizedRoot)) return normalizedFilePath.slice(normalizedRoot.length);
|
|
71647
|
+
return normalizedFilePath.replace(/^\.\//, "");
|
|
71648
|
+
};
|
|
71515
71649
|
const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
|
|
71516
71650
|
"react-doctor",
|
|
71517
71651
|
"eslint-plugin-react-doctor",
|
|
@@ -72261,7 +72395,6 @@ const readTextFileUpToCharacterLimit = async (input) => {
|
|
|
72261
72395
|
input.signal?.throwIfAborted();
|
|
72262
72396
|
return buffer.subarray(0, readOffset).toString("utf-8");
|
|
72263
72397
|
};
|
|
72264
|
-
const toNormalizedRelativePath = (filePath, rootDirectory) => path$1$1.relative(path$1$1.resolve(rootDirectory), path$1$1.resolve(rootDirectory, filePath)).replaceAll("\\", "/") || ".";
|
|
72265
72398
|
const buildJsxSourceReader = async (input) => {
|
|
72266
72399
|
const ignoredFilePatterns = compileGlobPatternsLenient(input.ignorePatterns ?? [], (error) => warnConfigIssue(`ignore.files: ${error.message}`));
|
|
72267
72400
|
const sourceFiles = (await listSourceFilesWithSizeCooperative(input.rootDirectory, input.signal)).filter((sourceFile) => JSX_DUPLICATION_SOURCE_FILE_PATTERN.test(sourceFile.path) && classifyFileContext(sourceFile.path) === "production" && !isFileIgnoredByPatterns(sourceFile.path, input.rootDirectory, ignoredFilePatterns));
|
|
@@ -73025,59 +73158,11 @@ const isLocalPathExtend = (entry) => {
|
|
|
73025
73158
|
for (const prefix of EXTENDS_LOCAL_PATH_PREFIXES) if (entry.startsWith(prefix)) return true;
|
|
73026
73159
|
return false;
|
|
73027
73160
|
};
|
|
73028
|
-
const stripJsoncComments = (raw) => {
|
|
73029
|
-
let result = "";
|
|
73030
|
-
let cursor = 0;
|
|
73031
|
-
let inString = false;
|
|
73032
|
-
let stringQuote = "";
|
|
73033
|
-
while (cursor < raw.length) {
|
|
73034
|
-
const character = raw[cursor];
|
|
73035
|
-
const nextCharacter = raw[cursor + 1];
|
|
73036
|
-
if (inString) {
|
|
73037
|
-
result += character;
|
|
73038
|
-
if (character === "\\" && cursor + 1 < raw.length) {
|
|
73039
|
-
result += nextCharacter;
|
|
73040
|
-
cursor += 2;
|
|
73041
|
-
continue;
|
|
73042
|
-
}
|
|
73043
|
-
if (character === stringQuote) inString = false;
|
|
73044
|
-
cursor += 1;
|
|
73045
|
-
continue;
|
|
73046
|
-
}
|
|
73047
|
-
if (character === "\"" || character === "'") {
|
|
73048
|
-
inString = true;
|
|
73049
|
-
stringQuote = character;
|
|
73050
|
-
result += character;
|
|
73051
|
-
cursor += 1;
|
|
73052
|
-
continue;
|
|
73053
|
-
}
|
|
73054
|
-
if (character === "/" && nextCharacter === "/") {
|
|
73055
|
-
const lineEndIndex = raw.indexOf("\n", cursor);
|
|
73056
|
-
cursor = lineEndIndex === -1 ? raw.length : lineEndIndex;
|
|
73057
|
-
continue;
|
|
73058
|
-
}
|
|
73059
|
-
if (character === "/" && nextCharacter === "*") {
|
|
73060
|
-
const blockEndIndex = raw.indexOf("*/", cursor + 2);
|
|
73061
|
-
cursor = blockEndIndex === -1 ? raw.length : blockEndIndex + 2;
|
|
73062
|
-
continue;
|
|
73063
|
-
}
|
|
73064
|
-
result += character;
|
|
73065
|
-
cursor += 1;
|
|
73066
|
-
}
|
|
73067
|
-
return result;
|
|
73068
|
-
};
|
|
73069
|
-
const parseJsonOrJsonc = (raw) => {
|
|
73070
|
-
try {
|
|
73071
|
-
return JSON.parse(raw);
|
|
73072
|
-
} catch {
|
|
73073
|
-
return JSON.parse(stripJsoncComments(raw));
|
|
73074
|
-
}
|
|
73075
|
-
};
|
|
73076
73161
|
const canOxlintExtendConfig = (configPath) => {
|
|
73077
73162
|
if (!configPath.endsWith(".eslintrc.json")) return true;
|
|
73078
73163
|
let parsed;
|
|
73079
73164
|
try {
|
|
73080
|
-
parsed =
|
|
73165
|
+
parsed = parseJSONC(fs$1$3.readFileSync(configPath, "utf-8"), { allowTrailingComma: true });
|
|
73081
73166
|
} catch {
|
|
73082
73167
|
return true;
|
|
73083
73168
|
}
|
|
@@ -73189,6 +73274,25 @@ const detectUserLintConfigPaths = (rootDirectory) => {
|
|
|
73189
73274
|
}
|
|
73190
73275
|
return [];
|
|
73191
73276
|
};
|
|
73277
|
+
const readAdoptedLintConfigSettings = (configPaths) => {
|
|
73278
|
+
const mergedSettings = /* @__PURE__ */ new Map();
|
|
73279
|
+
for (const configPath of configPaths) {
|
|
73280
|
+
let parsed;
|
|
73281
|
+
try {
|
|
73282
|
+
parsed = parseJSONC(fs$1$3.readFileSync(configPath, "utf-8"), { allowTrailingComma: true });
|
|
73283
|
+
} catch {
|
|
73284
|
+
continue;
|
|
73285
|
+
}
|
|
73286
|
+
if (!isPlainObject(parsed)) continue;
|
|
73287
|
+
const settings = parsed.settings;
|
|
73288
|
+
if (!isPlainObject(settings)) continue;
|
|
73289
|
+
for (const [pluginName, pluginSettings] of Object.entries(settings)) {
|
|
73290
|
+
if (pluginName === "react-doctor") continue;
|
|
73291
|
+
mergedSettings.set(pluginName, pluginSettings);
|
|
73292
|
+
}
|
|
73293
|
+
}
|
|
73294
|
+
return Object.fromEntries(mergedSettings);
|
|
73295
|
+
};
|
|
73192
73296
|
const DISABLE_DIRECTIVE_PATTERN = /(eslint|oxlint)-disable/;
|
|
73193
73297
|
const getBackupRootDirectory = (rootDirectory) => path$1$1.join(rootDirectory, ...DISABLE_DIRECTIVE_BACKUP_DIRECTORY_SEGMENTS);
|
|
73194
73298
|
const getBackupPath = (rootDirectory, absolutePath) => {
|
|
@@ -73585,7 +73689,7 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
73585
73689
|
}
|
|
73586
73690
|
return enabled;
|
|
73587
73691
|
};
|
|
73588
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), includedTags = /* @__PURE__ */ new Set(), includeTagDefaults = false, runtimeGlobals, unpluginAutoImportGlobalScopes, serverAuthFunctionNames, projectIndexModuleSources, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
|
|
73692
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), includedTags = /* @__PURE__ */ new Set(), includeTagDefaults = false, runtimeGlobals, unpluginAutoImportGlobalScopes, serverAuthFunctionNames, projectIndexModuleSources, severityControls, adoptedSettings = {}, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection, sidecarRuleIdFilter }) => {
|
|
73589
73693
|
const hasIncludedTags = includedTags.size > 0;
|
|
73590
73694
|
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" || hasIncludedTags ? null : resolveReactHooksJsPlugin(project.hasReactCompiler || project.hasReactCompilerLintPlugin === true, customRulesOnly);
|
|
73591
73695
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
@@ -73640,23 +73744,26 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
73640
73744
|
},
|
|
73641
73745
|
plugins: [],
|
|
73642
73746
|
jsPlugins: [...jsPlugins, pluginPath],
|
|
73643
|
-
settings: {
|
|
73644
|
-
|
|
73645
|
-
|
|
73646
|
-
|
|
73647
|
-
|
|
73648
|
-
|
|
73649
|
-
|
|
73650
|
-
|
|
73651
|
-
unpluginAutoImportGlobalScopes
|
|
73652
|
-
|
|
73653
|
-
|
|
73654
|
-
|
|
73655
|
-
|
|
73656
|
-
|
|
73657
|
-
|
|
73658
|
-
|
|
73659
|
-
|
|
73747
|
+
settings: {
|
|
73748
|
+
...adoptedSettings,
|
|
73749
|
+
"react-doctor": {
|
|
73750
|
+
portedRuleMode: "curated",
|
|
73751
|
+
framework: project.framework,
|
|
73752
|
+
rootDirectory: settingsRootDirectory,
|
|
73753
|
+
capabilities: [...capabilities].sort(),
|
|
73754
|
+
...runtimeGlobals && runtimeGlobals.length > 0 ? { runtimeGlobals: [...runtimeGlobals] } : {},
|
|
73755
|
+
...unpluginAutoImportGlobalScopes && unpluginAutoImportGlobalScopes.length > 0 ? {
|
|
73756
|
+
unpluginAutoImportRootDirectories: [...new Set([settingsRootDirectory, project.rootDirectory])],
|
|
73757
|
+
unpluginAutoImportGlobalScopes: unpluginAutoImportGlobalScopes.map((scope) => ({
|
|
73758
|
+
directory: scope.directory,
|
|
73759
|
+
names: [...scope.names]
|
|
73760
|
+
}))
|
|
73761
|
+
} : {},
|
|
73762
|
+
...project.shopifyFlashListMajorVersion !== null ? { shopifyFlashListMajorVersion: project.shopifyFlashListMajorVersion } : {},
|
|
73763
|
+
...serverAuthFunctionNames && serverAuthFunctionNames.length > 0 ? { serverAuthFunctionNames: [...serverAuthFunctionNames] } : {},
|
|
73764
|
+
...projectIndexModuleSources !== void 0 ? { projectIndexModuleSources: [...projectIndexModuleSources] } : {}
|
|
73765
|
+
}
|
|
73766
|
+
},
|
|
73660
73767
|
rules: {
|
|
73661
73768
|
...reactCompilerRules,
|
|
73662
73769
|
...enabledReactDoctorRules,
|
|
@@ -75765,7 +75872,9 @@ const runOxlint = async (options) => {
|
|
|
75765
75872
|
return [];
|
|
75766
75873
|
}
|
|
75767
75874
|
const pluginPath = resolvePluginPath();
|
|
75768
|
-
const
|
|
75875
|
+
const detectedConfigPaths = adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : [];
|
|
75876
|
+
const extendsPaths = detectedConfigPaths.filter(canOxlintExtendConfig);
|
|
75877
|
+
const adoptedSettings = detectedConfigPaths.length > 0 ? readAdoptedLintConfigSettings(detectedConfigPaths) : {};
|
|
75769
75878
|
const userPlugins = includedTags.size > 0 ? [] : resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
75770
75879
|
const restoreDisableDirectives = await neutralizeDisableDirectives(rootDirectory, includePaths ?? options.precomputedSourceFiles?.map((sourceFile) => sourceFile.path), { recoverOnly: respectInlineDisables });
|
|
75771
75880
|
const configDirectory = fs$1$3.mkdtempSync(path$1$1.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
@@ -75827,6 +75936,7 @@ const runOxlint = async (options) => {
|
|
|
75827
75936
|
serverAuthFunctionNames,
|
|
75828
75937
|
projectIndexModuleSources,
|
|
75829
75938
|
severityControls,
|
|
75939
|
+
adoptedSettings,
|
|
75830
75940
|
userPlugins,
|
|
75831
75941
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
75832
75942
|
ruleSelection: overrides.ruleSelection,
|
|
@@ -77209,9 +77319,9 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
77209
77319
|
if (!lintFailureState.didFail) if (maintainabilityFailureState.didFail) yield* scanProgress.fail(MAINTAINABILITY_FAIL_TEXT);
|
|
77210
77320
|
else if (input.suppressScanSummary) yield* scanProgress.stop();
|
|
77211
77321
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
77212
|
-
const supplyChainResult = yield* join(supplyChainFiber);
|
|
77322
|
+
const supplyChainResult = yield* join$1(supplyChainFiber);
|
|
77213
77323
|
const supplyChainCollected = supplyChainResult.diagnostics;
|
|
77214
|
-
const securityScanCollected = yield* join(securityScanFiber);
|
|
77324
|
+
const securityScanCollected = yield* join$1(securityScanFiber);
|
|
77215
77325
|
yield* reporterService.finalize;
|
|
77216
77326
|
const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
|
|
77217
77327
|
...envCollected,
|
|
@@ -77220,7 +77330,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
77220
77330
|
...lintCollected,
|
|
77221
77331
|
...maintainabilityCollected
|
|
77222
77332
|
]));
|
|
77223
|
-
const githubViewerPermission = yield* join(githubViewerPermissionFiber);
|
|
77333
|
+
const githubViewerPermission = yield* join$1(githubViewerPermissionFiber);
|
|
77224
77334
|
const scoreMetadata = {
|
|
77225
77335
|
...repo !== null ? { repo } : {},
|
|
77226
77336
|
...sha !== null ? { sha } : {},
|
|
@@ -77586,8 +77696,9 @@ const findWorstScoredProject = (projects) => {
|
|
|
77586
77696
|
return worst;
|
|
77587
77697
|
};
|
|
77588
77698
|
const toJsonReportDiagnostic = (diagnostic, projectRoot, reportRoot) => {
|
|
77589
|
-
const
|
|
77590
|
-
const
|
|
77699
|
+
const resolvedFilePath = resolveCandidateReadPath(projectRoot, diagnostic.filePath);
|
|
77700
|
+
const normalizedFilePath = toNormalizedRelativePath(resolvedFilePath, projectRoot);
|
|
77701
|
+
const reportRelativeFilePath = toNormalizedRelativePath(resolvedFilePath, reportRoot);
|
|
77591
77702
|
const ruleIdentity = getDiagnosticRuleIdentity(diagnostic);
|
|
77592
77703
|
return {
|
|
77593
77704
|
...diagnostic,
|
|
@@ -78070,4 +78181,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
78070
78181
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, hasReactRuntime, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
78071
78182
|
|
|
78072
78183
|
//# sourceMappingURL=index.js.map
|
|
78073
|
-
//# debugId=
|
|
78184
|
+
//# debugId=efcc9c57-f9b3-579e-af01-d0b87088c43f
|