react-doctor 0.2.4 → 0.2.6
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-logger-Iz5pfDnL.js +6778 -0
- package/dist/cli.js +366 -6704
- package/dist/index.js +235 -18
- package/dist/rolldown-runtime-uZX_iqCz.js +35 -0
- package/package.json +7 -6
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import * as Schema from "effect/Schema";
|
|
3
3
|
import * as fs$1 from "node:fs";
|
|
4
|
-
import fs, { existsSync, readdirSync } from "node:fs";
|
|
4
|
+
import fs, { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
5
5
|
import * as Path from "node:path";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { spawn, spawnSync } from "node:child_process";
|
|
8
8
|
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES } from "oxlint-plugin-react-doctor";
|
|
9
9
|
import * as Cause from "effect/Cause";
|
|
10
|
-
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient";
|
|
11
10
|
import * as Config$1 from "effect/Config";
|
|
12
11
|
import * as Effect from "effect/Effect";
|
|
13
12
|
import * as Layer from "effect/Layer";
|
|
14
13
|
import * as Redacted from "effect/Redacted";
|
|
14
|
+
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
15
15
|
import * as Otlp from "effect/unstable/observability/Otlp";
|
|
16
16
|
import * as Context from "effect/Context";
|
|
17
17
|
import * as Filter from "effect/Filter";
|
|
@@ -20,7 +20,9 @@ import * as Ref from "effect/Ref";
|
|
|
20
20
|
import * as Stream from "effect/Stream";
|
|
21
21
|
import * as Cache from "effect/Cache";
|
|
22
22
|
import * as Console from "effect/Console";
|
|
23
|
-
import * as
|
|
23
|
+
import * as NodeChildProcessSpawner from "@effect/platform-node-shared/NodeChildProcessSpawner";
|
|
24
|
+
import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem";
|
|
25
|
+
import * as NodePath from "@effect/platform-node-shared/NodePath";
|
|
24
26
|
import * as ChildProcess from "effect/unstable/process/ChildProcess";
|
|
25
27
|
import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner";
|
|
26
28
|
import os from "node:os";
|
|
@@ -2985,6 +2987,7 @@ const isTailwindAtLeast = (detected, required) => {
|
|
|
2985
2987
|
const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
2986
2988
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
2987
2989
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
2990
|
+
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
2988
2991
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
2989
2992
|
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
2990
2993
|
const GIT_SHOW_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
@@ -3005,6 +3008,7 @@ const STAGED_FILES_PROJECT_CONFIG_FILENAMES = [
|
|
|
3005
3008
|
];
|
|
3006
3009
|
const OXLINT_OUTPUT_MAX_BYTES = 50 * 1024 * 1024;
|
|
3007
3010
|
const OXLINT_SPAWN_TIMEOUT_MS = 6e4;
|
|
3011
|
+
const RECOMMENDED_PNPM_MINIMUM_RELEASE_AGE_MINUTES = 10080;
|
|
3008
3012
|
const MAX_GLOB_PATTERN_LENGTH_CHARS = 1024;
|
|
3009
3013
|
var InvalidGlobPatternError = class extends Error {
|
|
3010
3014
|
pattern;
|
|
@@ -3716,7 +3720,7 @@ const layerOtlp = Layer.unwrap(Effect.gen(function* () {
|
|
|
3716
3720
|
baseUrl: endpoint.value,
|
|
3717
3721
|
resource: { serviceName: TRACER_PROJECT_NAME },
|
|
3718
3722
|
headers
|
|
3719
|
-
}).pipe(Layer.provide(
|
|
3723
|
+
}).pipe(Layer.provide(FetchHttpClient.layer));
|
|
3720
3724
|
}).pipe(Effect.orDie));
|
|
3721
3725
|
Schema.String.pipe(Schema.brand("OxlintBinaryPath"));
|
|
3722
3726
|
Schema.String.pipe(Schema.brand("NodeBinaryPath"));
|
|
@@ -3729,6 +3733,120 @@ Context.Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => {
|
|
|
3729
3733
|
} });
|
|
3730
3734
|
Context.Reference("react-doctor/OxlintOutputMaxBytes", { defaultValue: () => OXLINT_OUTPUT_MAX_BYTES });
|
|
3731
3735
|
Context.Reference("react-doctor/StagedFilesTempDirPrefix", { defaultValue: () => "react-doctor-staged-" });
|
|
3736
|
+
const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
3737
|
+
const PNPM_LOCKFILE = "pnpm-lock.yaml";
|
|
3738
|
+
const PACKAGE_JSON_FILE = "package.json";
|
|
3739
|
+
const PNPM_HARDENING_RULE_KEY = "require-pnpm-hardening";
|
|
3740
|
+
const UTF8_BOM_CHAR = "";
|
|
3741
|
+
const HARDENING_SETTING_KEYS = new Set([
|
|
3742
|
+
"minimumReleaseAge",
|
|
3743
|
+
"blockExoticSubdeps",
|
|
3744
|
+
"trustPolicy"
|
|
3745
|
+
]);
|
|
3746
|
+
const stripInlineComment = (rawValue) => {
|
|
3747
|
+
let activeQuote = null;
|
|
3748
|
+
for (let charIndex = 0; charIndex < rawValue.length; charIndex += 1) {
|
|
3749
|
+
const currentChar = rawValue[charIndex];
|
|
3750
|
+
if (activeQuote !== null) {
|
|
3751
|
+
if (currentChar === activeQuote) activeQuote = null;
|
|
3752
|
+
continue;
|
|
3753
|
+
}
|
|
3754
|
+
if (currentChar === "\"" || currentChar === "'") {
|
|
3755
|
+
activeQuote = currentChar;
|
|
3756
|
+
continue;
|
|
3757
|
+
}
|
|
3758
|
+
if (currentChar !== "#") continue;
|
|
3759
|
+
const previousChar = rawValue[charIndex - 1];
|
|
3760
|
+
if (charIndex === 0 || previousChar !== void 0 && /\s/.test(previousChar)) return rawValue.slice(0, charIndex);
|
|
3761
|
+
}
|
|
3762
|
+
return rawValue;
|
|
3763
|
+
};
|
|
3764
|
+
const unquote = (rawValue) => rawValue.replace(/^["']|["']$/g, "");
|
|
3765
|
+
const stripBom = (rawContent) => rawContent.startsWith(UTF8_BOM_CHAR) ? rawContent.slice(1) : rawContent;
|
|
3766
|
+
const parseHardeningSettings = (content) => {
|
|
3767
|
+
let minimumReleaseAge = null;
|
|
3768
|
+
let blockExoticSubdeps = null;
|
|
3769
|
+
let trustPolicy = null;
|
|
3770
|
+
const lines = stripBom(content).split(/\r?\n/);
|
|
3771
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
3772
|
+
const lineText = lines[lineIndex];
|
|
3773
|
+
if (lineText === void 0) continue;
|
|
3774
|
+
if (lineText.search(/\S/) !== 0) continue;
|
|
3775
|
+
const trimmedLine = lineText.trim();
|
|
3776
|
+
if (trimmedLine.startsWith("#")) continue;
|
|
3777
|
+
const colonIndex = trimmedLine.indexOf(":");
|
|
3778
|
+
if (colonIndex <= 0) continue;
|
|
3779
|
+
const settingKey = unquote(trimmedLine.slice(0, colonIndex).trim());
|
|
3780
|
+
if (!HARDENING_SETTING_KEYS.has(settingKey)) continue;
|
|
3781
|
+
const inlineValue = stripInlineComment(trimmedLine.slice(colonIndex + 1)).trim();
|
|
3782
|
+
if (inlineValue.length === 0) continue;
|
|
3783
|
+
const scalar = {
|
|
3784
|
+
value: unquote(inlineValue),
|
|
3785
|
+
line: lineIndex + 1,
|
|
3786
|
+
column: lineText.search(/\S/) + 1
|
|
3787
|
+
};
|
|
3788
|
+
if (settingKey === "minimumReleaseAge") minimumReleaseAge = scalar;
|
|
3789
|
+
else if (settingKey === "blockExoticSubdeps") blockExoticSubdeps = scalar;
|
|
3790
|
+
else if (settingKey === "trustPolicy") trustPolicy = scalar;
|
|
3791
|
+
}
|
|
3792
|
+
return {
|
|
3793
|
+
minimumReleaseAge,
|
|
3794
|
+
blockExoticSubdeps,
|
|
3795
|
+
trustPolicy
|
|
3796
|
+
};
|
|
3797
|
+
};
|
|
3798
|
+
const isPnpmManagedProject = (rootDirectory) => {
|
|
3799
|
+
if (isFile(path.join(rootDirectory, PNPM_LOCKFILE))) return true;
|
|
3800
|
+
if (isFile(path.join(rootDirectory, PNPM_WORKSPACE_FILE))) return true;
|
|
3801
|
+
const packageJsonPath = path.join(rootDirectory, PACKAGE_JSON_FILE);
|
|
3802
|
+
if (!isFile(packageJsonPath)) return false;
|
|
3803
|
+
try {
|
|
3804
|
+
const packageJsonRaw = fs.readFileSync(packageJsonPath, "utf-8");
|
|
3805
|
+
const packageJson = JSON.parse(packageJsonRaw);
|
|
3806
|
+
if (packageJson !== null && typeof packageJson === "object" && "packageManager" in packageJson && typeof packageJson.packageManager === "string" && packageJson.packageManager.startsWith("pnpm@")) return true;
|
|
3807
|
+
} catch {
|
|
3808
|
+
return false;
|
|
3809
|
+
}
|
|
3810
|
+
return false;
|
|
3811
|
+
};
|
|
3812
|
+
const buildHardeningDiagnostic = (input) => ({
|
|
3813
|
+
filePath: PNPM_WORKSPACE_FILE,
|
|
3814
|
+
plugin: "react-doctor",
|
|
3815
|
+
rule: PNPM_HARDENING_RULE_KEY,
|
|
3816
|
+
severity: "warning",
|
|
3817
|
+
message: input.message,
|
|
3818
|
+
help: input.help,
|
|
3819
|
+
line: input.line ?? 0,
|
|
3820
|
+
column: input.column ?? 0,
|
|
3821
|
+
category: "Security"
|
|
3822
|
+
});
|
|
3823
|
+
const checkPnpmHardening = (rootDirectory) => {
|
|
3824
|
+
if (!isPnpmManagedProject(rootDirectory)) return [];
|
|
3825
|
+
const workspacePath = path.join(rootDirectory, PNPM_WORKSPACE_FILE);
|
|
3826
|
+
const settings = parseHardeningSettings(isFile(workspacePath) ? fs.readFileSync(workspacePath, "utf-8") : "");
|
|
3827
|
+
const diagnostics = [];
|
|
3828
|
+
if (settings.minimumReleaseAge === null) diagnostics.push(buildHardeningDiagnostic({
|
|
3829
|
+
message: "pnpm-workspace.yaml is missing `minimumReleaseAge` — newly published versions can ship malware that gets caught and unpublished within hours",
|
|
3830
|
+
help: `Add \`minimumReleaseAge: ${RECOMMENDED_PNPM_MINIMUM_RELEASE_AGE_MINUTES}\` (7 days) to pnpm-workspace.yaml to delay installs until releases have had time to be vetted`
|
|
3831
|
+
}));
|
|
3832
|
+
if (settings.blockExoticSubdeps !== null && settings.blockExoticSubdeps.value.toLowerCase() === "false") diagnostics.push(buildHardeningDiagnostic({
|
|
3833
|
+
line: settings.blockExoticSubdeps.line,
|
|
3834
|
+
column: settings.blockExoticSubdeps.column,
|
|
3835
|
+
message: "`blockExoticSubdeps: false` allows transitive deps from `git:`, `file:`, or tarball URLs — a known supply-chain bypass of the npm registry",
|
|
3836
|
+
help: "Set `blockExoticSubdeps: true` (the default in recent pnpm v11) so transitive deps must come from the registry"
|
|
3837
|
+
}));
|
|
3838
|
+
if (settings.trustPolicy === null) diagnostics.push(buildHardeningDiagnostic({
|
|
3839
|
+
message: "pnpm-workspace.yaml is missing `trustPolicy` — without `no-downgrade`, pnpm silently accepts packages whose trust signals (provenance, signatures) weaken between updates",
|
|
3840
|
+
help: "Add `trustPolicy: no-downgrade` to pnpm-workspace.yaml"
|
|
3841
|
+
}));
|
|
3842
|
+
else if (settings.trustPolicy.value !== "no-downgrade") diagnostics.push(buildHardeningDiagnostic({
|
|
3843
|
+
line: settings.trustPolicy.line,
|
|
3844
|
+
column: settings.trustPolicy.column,
|
|
3845
|
+
message: `\`trustPolicy: ${settings.trustPolicy.value}\` is weaker than \`no-downgrade\` — packages may lose trust signals between updates without you noticing`,
|
|
3846
|
+
help: "Set `trustPolicy: no-downgrade` so pnpm refuses to downgrade trust between resolutions"
|
|
3847
|
+
}));
|
|
3848
|
+
return diagnostics;
|
|
3849
|
+
};
|
|
3732
3850
|
const REDUCED_MOTION_GREP_PATTERN = "prefers-reduced-motion|useReducedMotion|MotionConfig|reducedMotion";
|
|
3733
3851
|
const REDUCED_MOTION_FILE_GLOBS = [
|
|
3734
3852
|
"*.ts",
|
|
@@ -4324,6 +4442,20 @@ const parseGithubRepoFromRemoteUrl = (remoteUrl) => {
|
|
|
4324
4442
|
const urlMatch = /^(?:https?:\/\/github\.com\/|ssh:\/\/git@github\.com\/)([^/\s]+)\/([^/\s]+)$/.exec(withoutGitSuffix);
|
|
4325
4443
|
return urlMatch ? `${urlMatch[1]}/${urlMatch[2]}` : null;
|
|
4326
4444
|
};
|
|
4445
|
+
const parseGithubRepo = (repo) => {
|
|
4446
|
+
const [owner, name, ...extraParts] = repo.split("/");
|
|
4447
|
+
if (owner === void 0 || name === void 0 || extraParts.length > 0) return null;
|
|
4448
|
+
if (owner.length === 0 || name.length === 0) return null;
|
|
4449
|
+
return {
|
|
4450
|
+
owner,
|
|
4451
|
+
name
|
|
4452
|
+
};
|
|
4453
|
+
};
|
|
4454
|
+
const parseGithubViewerPermission = (stdout) => {
|
|
4455
|
+
const value = trimOrNull(stdout);
|
|
4456
|
+
if (value === null || value === "null") return null;
|
|
4457
|
+
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
4458
|
+
};
|
|
4327
4459
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
4328
4460
|
/**
|
|
4329
4461
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
@@ -4349,9 +4481,10 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4349
4481
|
* reason: GitInvocationFailed })` so the rest of the codebase
|
|
4350
4482
|
* sees a single failure channel.
|
|
4351
4483
|
*/
|
|
4352
|
-
const
|
|
4353
|
-
const handle = yield* spawner.spawn(ChildProcess.make(
|
|
4354
|
-
cwd: directory,
|
|
4484
|
+
const runCommand = (input) => Effect.scoped(Effect.gen(function* () {
|
|
4485
|
+
const handle = yield* spawner.spawn(ChildProcess.make(input.command, [...input.args], {
|
|
4486
|
+
cwd: input.directory,
|
|
4487
|
+
env: input.env,
|
|
4355
4488
|
extendEnv: true
|
|
4356
4489
|
}));
|
|
4357
4490
|
const [stdout, stderr, status] = yield* Effect.all([
|
|
@@ -4364,11 +4497,23 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4364
4497
|
stdout,
|
|
4365
4498
|
stderr
|
|
4366
4499
|
};
|
|
4367
|
-
})).pipe(Effect.catchTag("PlatformError", (cause) =>
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4500
|
+
})).pipe(Effect.catchTag("PlatformError", (cause) => {
|
|
4501
|
+
if (input.command !== "git") return Effect.succeed({
|
|
4502
|
+
status: 127,
|
|
4503
|
+
stdout: "",
|
|
4504
|
+
stderr: String(cause)
|
|
4505
|
+
});
|
|
4506
|
+
return new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
4507
|
+
args: [...input.args],
|
|
4508
|
+
directory: input.directory,
|
|
4509
|
+
cause
|
|
4510
|
+
}) });
|
|
4511
|
+
}));
|
|
4512
|
+
const runGit = (directory, args) => runCommand({
|
|
4513
|
+
command: "git",
|
|
4514
|
+
args,
|
|
4515
|
+
directory
|
|
4516
|
+
});
|
|
4372
4517
|
const currentBranch = (directory) => runGit(directory, [
|
|
4373
4518
|
"rev-parse",
|
|
4374
4519
|
"--abbrev-ref",
|
|
@@ -4403,11 +4548,43 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4403
4548
|
"--get",
|
|
4404
4549
|
"remote.origin.url"
|
|
4405
4550
|
]).pipe(Effect.map((result) => result.status === 0 ? parseGithubRepoFromRemoteUrl(result.stdout) : null));
|
|
4551
|
+
const githubViewerPermission = (input) => Effect.gen(function* () {
|
|
4552
|
+
const parsedRepo = parseGithubRepo(input.repo);
|
|
4553
|
+
if (parsedRepo === null) return null;
|
|
4554
|
+
const resultOption = yield* runCommand({
|
|
4555
|
+
command: "gh",
|
|
4556
|
+
args: [
|
|
4557
|
+
"api",
|
|
4558
|
+
"graphql",
|
|
4559
|
+
"-F",
|
|
4560
|
+
`owner=${parsedRepo.owner}`,
|
|
4561
|
+
"-F",
|
|
4562
|
+
`name=${parsedRepo.name}`,
|
|
4563
|
+
"-f",
|
|
4564
|
+
`query=
|
|
4565
|
+
query(\$owner: String!, \$name: String!) {
|
|
4566
|
+
repository(owner: \$owner, name: \$name) {
|
|
4567
|
+
viewerPermission
|
|
4568
|
+
}
|
|
4569
|
+
}
|
|
4570
|
+
`,
|
|
4571
|
+
"--jq",
|
|
4572
|
+
".data.repository.viewerPermission"
|
|
4573
|
+
],
|
|
4574
|
+
directory: input.directory,
|
|
4575
|
+
env: { GH_PROMPT_DISABLED: "1" }
|
|
4576
|
+
}).pipe(Effect.timeoutOption(GITHUB_VIEWER_PERMISSION_TIMEOUT_MS));
|
|
4577
|
+
if (Option.isNone(resultOption)) return null;
|
|
4578
|
+
const result = resultOption.value;
|
|
4579
|
+
if (result.status !== 0) return null;
|
|
4580
|
+
return parseGithubViewerPermission(result.stdout);
|
|
4581
|
+
}).pipe(Effect.catch(() => Effect.succeed(null)));
|
|
4406
4582
|
return Git.of({
|
|
4407
4583
|
currentBranch,
|
|
4408
4584
|
defaultBranch,
|
|
4409
4585
|
headSha,
|
|
4410
4586
|
githubRepo,
|
|
4587
|
+
githubViewerPermission,
|
|
4411
4588
|
branchExists,
|
|
4412
4589
|
diffSelection: ({ directory, explicitBaseBranch }) => Effect.gen(function* () {
|
|
4413
4590
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* Effect.fail(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
@@ -4489,7 +4666,7 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4489
4666
|
};
|
|
4490
4667
|
})
|
|
4491
4668
|
});
|
|
4492
|
-
})).pipe(Layer.provide(
|
|
4669
|
+
})).pipe(Layer.provide(NodeChildProcessSpawner.layer.pipe(Layer.provide(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)))));
|
|
4493
4670
|
/**
|
|
4494
4671
|
* Test layer driven by a deterministic snapshot. Each key is a
|
|
4495
4672
|
* convenience pre-canned response so tests don't have to enumerate
|
|
@@ -4502,6 +4679,7 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4502
4679
|
defaultBranch: () => Effect.succeed(snapshot.defaultBranch ?? null),
|
|
4503
4680
|
headSha: () => Effect.succeed(snapshot.headSha ?? null),
|
|
4504
4681
|
githubRepo: () => Effect.succeed(snapshot.githubRepo ?? null),
|
|
4682
|
+
githubViewerPermission: () => Effect.succeed(snapshot.githubViewerPermission ?? null),
|
|
4505
4683
|
branchExists: (_directory, branch) => Effect.succeed(snapshot.branchExists?.get(branch) ?? false),
|
|
4506
4684
|
diffSelection: () => Effect.succeed(snapshot.diffSelection ?? null),
|
|
4507
4685
|
stagedFilePaths: () => Effect.succeed(snapshot.stagedFiles ?? []),
|
|
@@ -5844,7 +6022,11 @@ const calculateScore = async (diagnostics, options = {}) => {
|
|
|
5844
6022
|
...options.metadata?.framework ? { framework: options.metadata.framework } : {},
|
|
5845
6023
|
...options.metadata?.reactVersion ? { reactVersion: options.metadata.reactVersion } : {},
|
|
5846
6024
|
...typeof options.metadata?.sourceFileCount === "number" ? { sourceFileCount: options.metadata.sourceFileCount } : {},
|
|
5847
|
-
...options.metadata?.defaultBranch ? { defaultBranch: options.metadata.defaultBranch } : {}
|
|
6025
|
+
...options.metadata?.defaultBranch ? { defaultBranch: options.metadata.defaultBranch } : {},
|
|
6026
|
+
...options.metadata?.doctorVersion ? { doctorVersion: options.metadata.doctorVersion } : {},
|
|
6027
|
+
...options.metadata?.githubEventName ? { githubEventName: options.metadata.githubEventName } : {},
|
|
6028
|
+
...options.metadata?.githubActorAssociation ? { githubActorAssociation: options.metadata.githubActorAssociation } : {},
|
|
6029
|
+
...options.metadata?.githubViewerPermission ? { githubViewerPermission: options.metadata.githubViewerPermission } : {}
|
|
5848
6030
|
}));
|
|
5849
6031
|
const response = await fetch(requestUrl, {
|
|
5850
6032
|
method: "POST",
|
|
@@ -5889,6 +6071,32 @@ var Score = class Score extends Context.Service()("react-doctor/Score") {
|
|
|
5889
6071
|
}) }));
|
|
5890
6072
|
static layerOf = (result) => Layer.succeed(Score, Score.of({ compute: () => Effect.succeed(result) }));
|
|
5891
6073
|
};
|
|
6074
|
+
const getObjectProperty = (value, propertyName) => {
|
|
6075
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
6076
|
+
return Reflect.get(value, propertyName);
|
|
6077
|
+
};
|
|
6078
|
+
const getStringProperty = (value, propertyName) => {
|
|
6079
|
+
const propertyValue = getObjectProperty(value, propertyName);
|
|
6080
|
+
return typeof propertyValue === "string" && propertyValue.length > 0 ? propertyValue : void 0;
|
|
6081
|
+
};
|
|
6082
|
+
const readGithubEventPayload = (eventPath) => {
|
|
6083
|
+
if (eventPath === void 0 || eventPath.length === 0) return null;
|
|
6084
|
+
try {
|
|
6085
|
+
return JSON.parse(readFileSync(eventPath, "utf8"));
|
|
6086
|
+
} catch {
|
|
6087
|
+
return null;
|
|
6088
|
+
}
|
|
6089
|
+
};
|
|
6090
|
+
const resolveGithubActionsScoreMetadata = (environment = process.env) => {
|
|
6091
|
+
if (environment.GITHUB_ACTIONS !== "true") return {};
|
|
6092
|
+
const pullRequest = getObjectProperty(readGithubEventPayload(environment.GITHUB_EVENT_PATH), "pull_request");
|
|
6093
|
+
const eventName = environment.GITHUB_EVENT_NAME;
|
|
6094
|
+
const actorAssociation = getStringProperty(pullRequest, "author_association");
|
|
6095
|
+
return {
|
|
6096
|
+
...eventName !== void 0 && eventName.length > 0 ? { githubEventName: eventName } : {},
|
|
6097
|
+
...actorAssociation !== void 0 ? { githubActorAssociation: actorAssociation } : {}
|
|
6098
|
+
};
|
|
6099
|
+
};
|
|
5892
6100
|
const NO_HOOKS = {
|
|
5893
6101
|
beforeLint: () => Effect.void,
|
|
5894
6102
|
afterLint: () => Effect.void
|
|
@@ -5912,7 +6120,7 @@ const fileReader = (filesService, rootDirectory) => (filePath) => {
|
|
|
5912
6120
|
* Config.resolve(directory)
|
|
5913
6121
|
* -> Project.discover(resolvedDirectory)
|
|
5914
6122
|
* -> Git metadata for score attribution
|
|
5915
|
-
* -> Stream.fromIterable(
|
|
6123
|
+
* -> Stream.fromIterable(env diagnostics: reduced-motion + pnpm hardening)
|
|
5916
6124
|
* -> Stream.concat(Linter.run(...)) [folds ReactDoctorError into Ref]
|
|
5917
6125
|
* -> Stream.concat(DeadCode.run(...)) [folds Error into Ref]
|
|
5918
6126
|
* -> Stream.filterMap(perElementPipeline.apply) [auto-suppress / severity / ignore / inline]
|
|
@@ -5944,13 +6152,21 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
5944
6152
|
gitService.headSha(scanDirectory).pipe(Effect.orElseSucceed(() => null)),
|
|
5945
6153
|
gitService.defaultBranch(scanDirectory).pipe(Effect.orElseSucceed(() => null))
|
|
5946
6154
|
], { concurrency: 3 });
|
|
6155
|
+
const githubActionsScoreMetadata = input.isCi ? resolveGithubActionsScoreMetadata() : {};
|
|
6156
|
+
const githubViewerPermission = input.resolveLocalGithubViewerPermission === true && !input.isCi && repo !== null ? yield* gitService.githubViewerPermission({
|
|
6157
|
+
directory: scanDirectory,
|
|
6158
|
+
repo
|
|
6159
|
+
}).pipe(Effect.orElseSucceed(() => null)) : null;
|
|
5947
6160
|
const scoreMetadata = {
|
|
5948
6161
|
...repo !== null ? { repo } : {},
|
|
5949
6162
|
...sha !== null ? { sha } : {},
|
|
5950
6163
|
framework: project.framework,
|
|
5951
6164
|
...project.reactVersion !== null ? { reactVersion: project.reactVersion } : {},
|
|
5952
6165
|
sourceFileCount: project.sourceFileCount,
|
|
5953
|
-
...defaultBranch !== null ? { defaultBranch } : {}
|
|
6166
|
+
...defaultBranch !== null ? { defaultBranch } : {},
|
|
6167
|
+
...input.doctorVersion !== void 0 ? { doctorVersion: input.doctorVersion } : {},
|
|
6168
|
+
...githubActionsScoreMetadata,
|
|
6169
|
+
...githubViewerPermission !== null ? { githubViewerPermission } : {}
|
|
5954
6170
|
};
|
|
5955
6171
|
const lintIncludePaths = computeJsxIncludePaths([...input.includePaths]) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
|
|
5956
6172
|
const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
|
|
@@ -5972,7 +6188,7 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
5972
6188
|
readFileLinesSync: fileReader(filesService, scanDirectory),
|
|
5973
6189
|
respectInlineDisables: input.respectInlineDisables
|
|
5974
6190
|
});
|
|
5975
|
-
const environmentDiagnostics = isDiffMode ? [] : checkReducedMotion(scanDirectory);
|
|
6191
|
+
const environmentDiagnostics = isDiffMode ? [] : [...checkReducedMotion(scanDirectory), ...checkPnpmHardening(scanDirectory)];
|
|
5976
6192
|
const emptyDiagnosticStream = Stream.empty;
|
|
5977
6193
|
const rawLintStream = linterService.run({
|
|
5978
6194
|
rootDirectory: scanDirectory,
|
|
@@ -6500,7 +6716,8 @@ const diagnose = async (directory, options = {}) => {
|
|
|
6500
6716
|
adoptExistingLintConfig: initialLoadedConfig?.config?.adoptExistingLintConfig ?? true,
|
|
6501
6717
|
ignoredTags: new Set(initialLoadedConfig?.config?.ignore?.tags ?? []),
|
|
6502
6718
|
runDeadCode: options.deadCode ?? initialLoadedConfig?.config?.deadCode ?? true,
|
|
6503
|
-
isCi: false
|
|
6719
|
+
isCi: false,
|
|
6720
|
+
resolveLocalGithubViewerPermission: true
|
|
6504
6721
|
});
|
|
6505
6722
|
const output = await Effect.runPromise(program.pipe(Effect.provide(buildLayerStack()), Effect.provide(layerOtlp), Effect.catchReasons("ReactDoctorError", {
|
|
6506
6723
|
NoReactDependency: (reason) => Effect.die(new NoReactDependencyError(reason.directory)),
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
10
|
+
var __exportAll = (all, no_symbols) => {
|
|
11
|
+
let target = {};
|
|
12
|
+
for (var name in all) __defProp(target, name, {
|
|
13
|
+
get: all[name],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
17
|
+
return target;
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
21
|
+
key = keys[i];
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
23
|
+
get: ((k) => from[k]).bind(null, key),
|
|
24
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
30
|
+
value: mod,
|
|
31
|
+
enumerable: true
|
|
32
|
+
}) : target, mod));
|
|
33
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
34
|
+
//#endregion
|
|
35
|
+
export { __toESM as i, __exportAll as n, __require as r, __commonJSMin as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Diagnose and fix React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -49,22 +49,23 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@effect/platform-node": "4.0.0-beta.70",
|
|
52
|
+
"@effect/platform-node-shared": "4.0.0-beta.70",
|
|
53
53
|
"agent-install": "0.0.5",
|
|
54
|
+
"conf": "^15.1.0",
|
|
54
55
|
"deslop-js": "^0.0.12",
|
|
55
56
|
"effect": "4.0.0-beta.70",
|
|
56
57
|
"oxlint": "^1.66.0",
|
|
57
58
|
"prompts": "^2.4.2",
|
|
58
59
|
"typescript": ">=5.0.4 <7",
|
|
59
|
-
"oxlint-plugin-react-doctor": "0.2.
|
|
60
|
+
"oxlint-plugin-react-doctor": "0.2.6"
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|
|
62
63
|
"@types/prompts": "^2.4.9",
|
|
63
64
|
"commander": "^14.0.3",
|
|
64
65
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
65
66
|
"ora": "^9.4.0",
|
|
66
|
-
"@react-doctor/api": "0.2.
|
|
67
|
-
"@react-doctor/core": "0.2.
|
|
67
|
+
"@react-doctor/api": "0.2.6",
|
|
68
|
+
"@react-doctor/core": "0.2.6"
|
|
68
69
|
},
|
|
69
70
|
"peerDependencies": {
|
|
70
71
|
"eslint-plugin-react-hooks": "^6 || ^7"
|
|
@@ -75,7 +76,7 @@
|
|
|
75
76
|
}
|
|
76
77
|
},
|
|
77
78
|
"engines": {
|
|
78
|
-
"node": ">=22.
|
|
79
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
79
80
|
},
|
|
80
81
|
"scripts": {
|
|
81
82
|
"dev": "vp pack --watch",
|