react-doctor 0.2.5 → 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-CISyjOAb.js → cli-logger-Iz5pfDnL.js} +111 -12
- package/dist/cli.js +8 -6
- package/dist/index.js +112 -12
- package/package.json +4 -4
|
@@ -4,7 +4,7 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
4
4
|
import * as Path from "node:path";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import * as fs$1 from "node:fs";
|
|
7
|
-
import fs, { existsSync, readdirSync } from "node:fs";
|
|
7
|
+
import fs, { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
8
8
|
import * as Schema from "effect/Schema";
|
|
9
9
|
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";
|
|
10
10
|
import * as Cause from "effect/Cause";
|
|
@@ -2960,6 +2960,7 @@ const MILLISECONDS_PER_SECOND = 1e3;
|
|
|
2960
2960
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
2961
2961
|
const SHARE_BASE_URL = "https://www.react.doctor/share";
|
|
2962
2962
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
2963
|
+
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
2963
2964
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
2964
2965
|
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
2965
2966
|
const OXLINT_NODE_REQUIREMENT = "^20.19.0 || >=22.12.0";
|
|
@@ -4427,6 +4428,20 @@ const parseGithubRepoFromRemoteUrl = (remoteUrl) => {
|
|
|
4427
4428
|
const urlMatch = /^(?:https?:\/\/github\.com\/|ssh:\/\/git@github\.com\/)([^/\s]+)\/([^/\s]+)$/.exec(withoutGitSuffix);
|
|
4428
4429
|
return urlMatch ? `${urlMatch[1]}/${urlMatch[2]}` : null;
|
|
4429
4430
|
};
|
|
4431
|
+
const parseGithubRepo = (repo) => {
|
|
4432
|
+
const [owner, name, ...extraParts] = repo.split("/");
|
|
4433
|
+
if (owner === void 0 || name === void 0 || extraParts.length > 0) return null;
|
|
4434
|
+
if (owner.length === 0 || name.length === 0) return null;
|
|
4435
|
+
return {
|
|
4436
|
+
owner,
|
|
4437
|
+
name
|
|
4438
|
+
};
|
|
4439
|
+
};
|
|
4440
|
+
const parseGithubViewerPermission = (stdout) => {
|
|
4441
|
+
const value = trimOrNull(stdout);
|
|
4442
|
+
if (value === null || value === "null") return null;
|
|
4443
|
+
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
4444
|
+
};
|
|
4430
4445
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
4431
4446
|
/**
|
|
4432
4447
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
@@ -4452,9 +4467,10 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4452
4467
|
* reason: GitInvocationFailed })` so the rest of the codebase
|
|
4453
4468
|
* sees a single failure channel.
|
|
4454
4469
|
*/
|
|
4455
|
-
const
|
|
4456
|
-
const handle = yield* spawner.spawn(ChildProcess.make(
|
|
4457
|
-
cwd: directory,
|
|
4470
|
+
const runCommand = (input) => Effect.scoped(Effect.gen(function* () {
|
|
4471
|
+
const handle = yield* spawner.spawn(ChildProcess.make(input.command, [...input.args], {
|
|
4472
|
+
cwd: input.directory,
|
|
4473
|
+
env: input.env,
|
|
4458
4474
|
extendEnv: true
|
|
4459
4475
|
}));
|
|
4460
4476
|
const [stdout, stderr, status] = yield* Effect.all([
|
|
@@ -4467,11 +4483,23 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4467
4483
|
stdout,
|
|
4468
4484
|
stderr
|
|
4469
4485
|
};
|
|
4470
|
-
})).pipe(Effect.catchTag("PlatformError", (cause) =>
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4486
|
+
})).pipe(Effect.catchTag("PlatformError", (cause) => {
|
|
4487
|
+
if (input.command !== "git") return Effect.succeed({
|
|
4488
|
+
status: 127,
|
|
4489
|
+
stdout: "",
|
|
4490
|
+
stderr: String(cause)
|
|
4491
|
+
});
|
|
4492
|
+
return new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
4493
|
+
args: [...input.args],
|
|
4494
|
+
directory: input.directory,
|
|
4495
|
+
cause
|
|
4496
|
+
}) });
|
|
4497
|
+
}));
|
|
4498
|
+
const runGit = (directory, args) => runCommand({
|
|
4499
|
+
command: "git",
|
|
4500
|
+
args,
|
|
4501
|
+
directory
|
|
4502
|
+
});
|
|
4475
4503
|
const currentBranch = (directory) => runGit(directory, [
|
|
4476
4504
|
"rev-parse",
|
|
4477
4505
|
"--abbrev-ref",
|
|
@@ -4506,11 +4534,43 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4506
4534
|
"--get",
|
|
4507
4535
|
"remote.origin.url"
|
|
4508
4536
|
]).pipe(Effect.map((result) => result.status === 0 ? parseGithubRepoFromRemoteUrl(result.stdout) : null));
|
|
4537
|
+
const githubViewerPermission = (input) => Effect.gen(function* () {
|
|
4538
|
+
const parsedRepo = parseGithubRepo(input.repo);
|
|
4539
|
+
if (parsedRepo === null) return null;
|
|
4540
|
+
const resultOption = yield* runCommand({
|
|
4541
|
+
command: "gh",
|
|
4542
|
+
args: [
|
|
4543
|
+
"api",
|
|
4544
|
+
"graphql",
|
|
4545
|
+
"-F",
|
|
4546
|
+
`owner=${parsedRepo.owner}`,
|
|
4547
|
+
"-F",
|
|
4548
|
+
`name=${parsedRepo.name}`,
|
|
4549
|
+
"-f",
|
|
4550
|
+
`query=
|
|
4551
|
+
query(\$owner: String!, \$name: String!) {
|
|
4552
|
+
repository(owner: \$owner, name: \$name) {
|
|
4553
|
+
viewerPermission
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
`,
|
|
4557
|
+
"--jq",
|
|
4558
|
+
".data.repository.viewerPermission"
|
|
4559
|
+
],
|
|
4560
|
+
directory: input.directory,
|
|
4561
|
+
env: { GH_PROMPT_DISABLED: "1" }
|
|
4562
|
+
}).pipe(Effect.timeoutOption(GITHUB_VIEWER_PERMISSION_TIMEOUT_MS));
|
|
4563
|
+
if (Option.isNone(resultOption)) return null;
|
|
4564
|
+
const result = resultOption.value;
|
|
4565
|
+
if (result.status !== 0) return null;
|
|
4566
|
+
return parseGithubViewerPermission(result.stdout);
|
|
4567
|
+
}).pipe(Effect.catch(() => Effect.succeed(null)));
|
|
4509
4568
|
return Git.of({
|
|
4510
4569
|
currentBranch,
|
|
4511
4570
|
defaultBranch,
|
|
4512
4571
|
headSha,
|
|
4513
4572
|
githubRepo,
|
|
4573
|
+
githubViewerPermission,
|
|
4514
4574
|
branchExists,
|
|
4515
4575
|
diffSelection: ({ directory, explicitBaseBranch }) => Effect.gen(function* () {
|
|
4516
4576
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* Effect.fail(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
@@ -4605,6 +4665,7 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4605
4665
|
defaultBranch: () => Effect.succeed(snapshot.defaultBranch ?? null),
|
|
4606
4666
|
headSha: () => Effect.succeed(snapshot.headSha ?? null),
|
|
4607
4667
|
githubRepo: () => Effect.succeed(snapshot.githubRepo ?? null),
|
|
4668
|
+
githubViewerPermission: () => Effect.succeed(snapshot.githubViewerPermission ?? null),
|
|
4608
4669
|
branchExists: (_directory, branch) => Effect.succeed(snapshot.branchExists?.get(branch) ?? false),
|
|
4609
4670
|
diffSelection: () => Effect.succeed(snapshot.diffSelection ?? null),
|
|
4610
4671
|
stagedFilePaths: () => Effect.succeed(snapshot.stagedFiles ?? []),
|
|
@@ -5947,7 +6008,11 @@ const calculateScore = async (diagnostics, options = {}) => {
|
|
|
5947
6008
|
...options.metadata?.framework ? { framework: options.metadata.framework } : {},
|
|
5948
6009
|
...options.metadata?.reactVersion ? { reactVersion: options.metadata.reactVersion } : {},
|
|
5949
6010
|
...typeof options.metadata?.sourceFileCount === "number" ? { sourceFileCount: options.metadata.sourceFileCount } : {},
|
|
5950
|
-
...options.metadata?.defaultBranch ? { defaultBranch: options.metadata.defaultBranch } : {}
|
|
6011
|
+
...options.metadata?.defaultBranch ? { defaultBranch: options.metadata.defaultBranch } : {},
|
|
6012
|
+
...options.metadata?.doctorVersion ? { doctorVersion: options.metadata.doctorVersion } : {},
|
|
6013
|
+
...options.metadata?.githubEventName ? { githubEventName: options.metadata.githubEventName } : {},
|
|
6014
|
+
...options.metadata?.githubActorAssociation ? { githubActorAssociation: options.metadata.githubActorAssociation } : {},
|
|
6015
|
+
...options.metadata?.githubViewerPermission ? { githubViewerPermission: options.metadata.githubViewerPermission } : {}
|
|
5951
6016
|
}));
|
|
5952
6017
|
const response = await fetch(requestUrl, {
|
|
5953
6018
|
method: "POST",
|
|
@@ -5992,6 +6057,32 @@ var Score = class Score extends Context.Service()("react-doctor/Score") {
|
|
|
5992
6057
|
}) }));
|
|
5993
6058
|
static layerOf = (result) => Layer.succeed(Score, Score.of({ compute: () => Effect.succeed(result) }));
|
|
5994
6059
|
};
|
|
6060
|
+
const getObjectProperty = (value, propertyName) => {
|
|
6061
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
6062
|
+
return Reflect.get(value, propertyName);
|
|
6063
|
+
};
|
|
6064
|
+
const getStringProperty = (value, propertyName) => {
|
|
6065
|
+
const propertyValue = getObjectProperty(value, propertyName);
|
|
6066
|
+
return typeof propertyValue === "string" && propertyValue.length > 0 ? propertyValue : void 0;
|
|
6067
|
+
};
|
|
6068
|
+
const readGithubEventPayload = (eventPath) => {
|
|
6069
|
+
if (eventPath === void 0 || eventPath.length === 0) return null;
|
|
6070
|
+
try {
|
|
6071
|
+
return JSON.parse(readFileSync(eventPath, "utf8"));
|
|
6072
|
+
} catch {
|
|
6073
|
+
return null;
|
|
6074
|
+
}
|
|
6075
|
+
};
|
|
6076
|
+
const resolveGithubActionsScoreMetadata = (environment = process.env) => {
|
|
6077
|
+
if (environment.GITHUB_ACTIONS !== "true") return {};
|
|
6078
|
+
const pullRequest = getObjectProperty(readGithubEventPayload(environment.GITHUB_EVENT_PATH), "pull_request");
|
|
6079
|
+
const eventName = environment.GITHUB_EVENT_NAME;
|
|
6080
|
+
const actorAssociation = getStringProperty(pullRequest, "author_association");
|
|
6081
|
+
return {
|
|
6082
|
+
...eventName !== void 0 && eventName.length > 0 ? { githubEventName: eventName } : {},
|
|
6083
|
+
...actorAssociation !== void 0 ? { githubActorAssociation: actorAssociation } : {}
|
|
6084
|
+
};
|
|
6085
|
+
};
|
|
5995
6086
|
const NO_HOOKS = {
|
|
5996
6087
|
beforeLint: () => Effect.void,
|
|
5997
6088
|
afterLint: () => Effect.void
|
|
@@ -6047,13 +6138,21 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6047
6138
|
gitService.headSha(scanDirectory).pipe(Effect.orElseSucceed(() => null)),
|
|
6048
6139
|
gitService.defaultBranch(scanDirectory).pipe(Effect.orElseSucceed(() => null))
|
|
6049
6140
|
], { concurrency: 3 });
|
|
6141
|
+
const githubActionsScoreMetadata = input.isCi ? resolveGithubActionsScoreMetadata() : {};
|
|
6142
|
+
const githubViewerPermission = input.resolveLocalGithubViewerPermission === true && !input.isCi && repo !== null ? yield* gitService.githubViewerPermission({
|
|
6143
|
+
directory: scanDirectory,
|
|
6144
|
+
repo
|
|
6145
|
+
}).pipe(Effect.orElseSucceed(() => null)) : null;
|
|
6050
6146
|
const scoreMetadata = {
|
|
6051
6147
|
...repo !== null ? { repo } : {},
|
|
6052
6148
|
...sha !== null ? { sha } : {},
|
|
6053
6149
|
framework: project.framework,
|
|
6054
6150
|
...project.reactVersion !== null ? { reactVersion: project.reactVersion } : {},
|
|
6055
6151
|
sourceFileCount: project.sourceFileCount,
|
|
6056
|
-
...defaultBranch !== null ? { defaultBranch } : {}
|
|
6152
|
+
...defaultBranch !== null ? { defaultBranch } : {},
|
|
6153
|
+
...input.doctorVersion !== void 0 ? { doctorVersion: input.doctorVersion } : {},
|
|
6154
|
+
...githubActionsScoreMetadata,
|
|
6155
|
+
...githubViewerPermission !== null ? { githubViewerPermission } : {}
|
|
6057
6156
|
};
|
|
6058
6157
|
const lintIncludePaths = computeJsxIncludePaths([...input.includePaths]) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
|
|
6059
6158
|
const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
|
|
@@ -6676,4 +6775,4 @@ const cliLogger = {
|
|
|
6676
6775
|
//#endregion
|
|
6677
6776
|
export { formatReactDoctorError as A, toRelativePath as B, buildJsonReportError as C, filterSourceFiles as D, filterDiagnosticsForSurface as E, layerOtlp as F, listWorkspacePackages as I, loadConfigWithSource as L, groupBy as M, highlighter as N, formatErrorChain as O, isReactDoctorError as P, resolveConfigRootDir as R, buildJsonReport as S, discoverReactSubprojects as T, Reporter as _, Config as a, Score as b, Git as c, MILLISECONDS_PER_SECOND as d, NoReactDependencyError as f, ProjectNotFoundError as g, Project as h, CANONICAL_GITHUB_URL as i, getDiffInfo as j, formatFrameworkName as k, LintPartialFailures as l, OXLINT_NODE_REQUIREMENT as m, cli_logger_exports as n, DeadCode as o, NodeResolver as p, AmbiguousProjectError as r, Files as s, cliLogger as t, Linter as u, SHARE_BASE_URL as v, calculateScore as w, StagedFiles as x, SKILL_NAME as y, runInspect as z };
|
|
6678
6777
|
|
|
6679
|
-
//# sourceMappingURL=cli-logger-
|
|
6778
|
+
//# sourceMappingURL=cli-logger-Iz5pfDnL.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __toESM, n as __exportAll, r as __require, t as __commonJSMin } from "./rolldown-runtime-uZX_iqCz.js";
|
|
2
|
-
import { A as formatReactDoctorError, B as toRelativePath, C as buildJsonReportError, D as filterSourceFiles, E as filterDiagnosticsForSurface, F as layerOtlp, I as listWorkspacePackages, L as loadConfigWithSource, M as groupBy, N as highlighter, O as formatErrorChain, P as isReactDoctorError, R as resolveConfigRootDir, S as buildJsonReport, T as discoverReactSubprojects, _ as Reporter, a as Config, b as Score, c as Git, d as MILLISECONDS_PER_SECOND, f as NoReactDependencyError, g as ProjectNotFoundError, h as Project, i as CANONICAL_GITHUB_URL, j as getDiffInfo, k as formatFrameworkName, l as LintPartialFailures, m as OXLINT_NODE_REQUIREMENT, o as DeadCode, p as NodeResolver, r as AmbiguousProjectError, s as Files, t as cliLogger, u as Linter, v as SHARE_BASE_URL, w as calculateScore, x as StagedFiles, y as SKILL_NAME, z as runInspect } from "./cli-logger-
|
|
2
|
+
import { A as formatReactDoctorError, B as toRelativePath, C as buildJsonReportError, D as filterSourceFiles, E as filterDiagnosticsForSurface, F as layerOtlp, I as listWorkspacePackages, L as loadConfigWithSource, M as groupBy, N as highlighter, O as formatErrorChain, P as isReactDoctorError, R as resolveConfigRootDir, S as buildJsonReport, T as discoverReactSubprojects, _ as Reporter, a as Config, b as Score, c as Git, d as MILLISECONDS_PER_SECOND, f as NoReactDependencyError, g as ProjectNotFoundError, h as Project, i as CANONICAL_GITHUB_URL, j as getDiffInfo, k as formatFrameworkName, l as LintPartialFailures, m as OXLINT_NODE_REQUIREMENT, o as DeadCode, p as NodeResolver, r as AmbiguousProjectError, s as Files, t as cliLogger, u as Linter, v as SHARE_BASE_URL, w as calculateScore, x as StagedFiles, y as SKILL_NAME, z as runInspect } from "./cli-logger-Iz5pfDnL.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
5
|
import path, { join } from "node:path";
|
|
@@ -6545,6 +6545,9 @@ const resolveOxlintNodeEffect = (isLintEnabled, isQuiet) => Effect.gen(function*
|
|
|
6545
6545
|
});
|
|
6546
6546
|
const resolveOxlintNode = (isLintEnabled, isQuiet) => Effect.runPromise(resolveOxlintNodeEffect(isLintEnabled, isQuiet).pipe(Effect.provide(NodeResolver.layerNode)));
|
|
6547
6547
|
//#endregion
|
|
6548
|
+
//#region src/cli/utils/version.ts
|
|
6549
|
+
const VERSION = "0.2.6";
|
|
6550
|
+
//#endregion
|
|
6548
6551
|
//#region src/inspect.ts
|
|
6549
6552
|
const silentConsole = new Proxy({}, { get: () => () => void 0 });
|
|
6550
6553
|
const runConsole = (effect) => {
|
|
@@ -6636,7 +6639,9 @@ const runInspectWithRuntime = async (directory, options, userConfig, hasConfigOv
|
|
|
6636
6639
|
ignoredTags: options.ignoredTags,
|
|
6637
6640
|
nodeBinaryPath: resolvedNodeBinaryPath ?? void 0,
|
|
6638
6641
|
runDeadCode: options.deadCode,
|
|
6639
|
-
isCi: options.isCi
|
|
6642
|
+
isCi: options.isCi,
|
|
6643
|
+
doctorVersion: VERSION,
|
|
6644
|
+
resolveLocalGithubViewerPermission: !options.noScore
|
|
6640
6645
|
}, {
|
|
6641
6646
|
beforeLint: (projectInfo, lintIncludePaths) => Effect.gen(function* () {
|
|
6642
6647
|
const lintSourceFileCount = lintIncludePaths?.length ?? projectInfo.sourceFileCount;
|
|
@@ -6825,9 +6830,6 @@ const handleError = (error, options = { shouldExit: true }) => {
|
|
|
6825
6830
|
process.exitCode = 1;
|
|
6826
6831
|
};
|
|
6827
6832
|
//#endregion
|
|
6828
|
-
//#region src/cli/utils/version.ts
|
|
6829
|
-
const VERSION = "0.2.5";
|
|
6830
|
-
//#endregion
|
|
6831
6833
|
//#region src/cli/utils/json-mode.ts
|
|
6832
6834
|
let context = null;
|
|
6833
6835
|
/**
|
|
@@ -7124,7 +7126,7 @@ const warnSetupPromptFailure = async (options, error) => {
|
|
|
7124
7126
|
return;
|
|
7125
7127
|
}
|
|
7126
7128
|
try {
|
|
7127
|
-
const { cliLogger } = await import("./cli-logger-
|
|
7129
|
+
const { cliLogger } = await import("./cli-logger-Iz5pfDnL.js").then((n) => n.n);
|
|
7128
7130
|
cliLogger.warn(message);
|
|
7129
7131
|
} catch {}
|
|
7130
7132
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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";
|
|
@@ -2987,6 +2987,7 @@ const isTailwindAtLeast = (detected, required) => {
|
|
|
2987
2987
|
const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
2988
2988
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
2989
2989
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
2990
|
+
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
2990
2991
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
2991
2992
|
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
2992
2993
|
const GIT_SHOW_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
@@ -4441,6 +4442,20 @@ const parseGithubRepoFromRemoteUrl = (remoteUrl) => {
|
|
|
4441
4442
|
const urlMatch = /^(?:https?:\/\/github\.com\/|ssh:\/\/git@github\.com\/)([^/\s]+)\/([^/\s]+)$/.exec(withoutGitSuffix);
|
|
4442
4443
|
return urlMatch ? `${urlMatch[1]}/${urlMatch[2]}` : null;
|
|
4443
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
|
+
};
|
|
4444
4459
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
4445
4460
|
/**
|
|
4446
4461
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
@@ -4466,9 +4481,10 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4466
4481
|
* reason: GitInvocationFailed })` so the rest of the codebase
|
|
4467
4482
|
* sees a single failure channel.
|
|
4468
4483
|
*/
|
|
4469
|
-
const
|
|
4470
|
-
const handle = yield* spawner.spawn(ChildProcess.make(
|
|
4471
|
-
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,
|
|
4472
4488
|
extendEnv: true
|
|
4473
4489
|
}));
|
|
4474
4490
|
const [stdout, stderr, status] = yield* Effect.all([
|
|
@@ -4481,11 +4497,23 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4481
4497
|
stdout,
|
|
4482
4498
|
stderr
|
|
4483
4499
|
};
|
|
4484
|
-
})).pipe(Effect.catchTag("PlatformError", (cause) =>
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
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
|
+
});
|
|
4489
4517
|
const currentBranch = (directory) => runGit(directory, [
|
|
4490
4518
|
"rev-parse",
|
|
4491
4519
|
"--abbrev-ref",
|
|
@@ -4520,11 +4548,43 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4520
4548
|
"--get",
|
|
4521
4549
|
"remote.origin.url"
|
|
4522
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)));
|
|
4523
4582
|
return Git.of({
|
|
4524
4583
|
currentBranch,
|
|
4525
4584
|
defaultBranch,
|
|
4526
4585
|
headSha,
|
|
4527
4586
|
githubRepo,
|
|
4587
|
+
githubViewerPermission,
|
|
4528
4588
|
branchExists,
|
|
4529
4589
|
diffSelection: ({ directory, explicitBaseBranch }) => Effect.gen(function* () {
|
|
4530
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." }) }));
|
|
@@ -4619,6 +4679,7 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4619
4679
|
defaultBranch: () => Effect.succeed(snapshot.defaultBranch ?? null),
|
|
4620
4680
|
headSha: () => Effect.succeed(snapshot.headSha ?? null),
|
|
4621
4681
|
githubRepo: () => Effect.succeed(snapshot.githubRepo ?? null),
|
|
4682
|
+
githubViewerPermission: () => Effect.succeed(snapshot.githubViewerPermission ?? null),
|
|
4622
4683
|
branchExists: (_directory, branch) => Effect.succeed(snapshot.branchExists?.get(branch) ?? false),
|
|
4623
4684
|
diffSelection: () => Effect.succeed(snapshot.diffSelection ?? null),
|
|
4624
4685
|
stagedFilePaths: () => Effect.succeed(snapshot.stagedFiles ?? []),
|
|
@@ -5961,7 +6022,11 @@ const calculateScore = async (diagnostics, options = {}) => {
|
|
|
5961
6022
|
...options.metadata?.framework ? { framework: options.metadata.framework } : {},
|
|
5962
6023
|
...options.metadata?.reactVersion ? { reactVersion: options.metadata.reactVersion } : {},
|
|
5963
6024
|
...typeof options.metadata?.sourceFileCount === "number" ? { sourceFileCount: options.metadata.sourceFileCount } : {},
|
|
5964
|
-
...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 } : {}
|
|
5965
6030
|
}));
|
|
5966
6031
|
const response = await fetch(requestUrl, {
|
|
5967
6032
|
method: "POST",
|
|
@@ -6006,6 +6071,32 @@ var Score = class Score extends Context.Service()("react-doctor/Score") {
|
|
|
6006
6071
|
}) }));
|
|
6007
6072
|
static layerOf = (result) => Layer.succeed(Score, Score.of({ compute: () => Effect.succeed(result) }));
|
|
6008
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
|
+
};
|
|
6009
6100
|
const NO_HOOKS = {
|
|
6010
6101
|
beforeLint: () => Effect.void,
|
|
6011
6102
|
afterLint: () => Effect.void
|
|
@@ -6061,13 +6152,21 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6061
6152
|
gitService.headSha(scanDirectory).pipe(Effect.orElseSucceed(() => null)),
|
|
6062
6153
|
gitService.defaultBranch(scanDirectory).pipe(Effect.orElseSucceed(() => null))
|
|
6063
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;
|
|
6064
6160
|
const scoreMetadata = {
|
|
6065
6161
|
...repo !== null ? { repo } : {},
|
|
6066
6162
|
...sha !== null ? { sha } : {},
|
|
6067
6163
|
framework: project.framework,
|
|
6068
6164
|
...project.reactVersion !== null ? { reactVersion: project.reactVersion } : {},
|
|
6069
6165
|
sourceFileCount: project.sourceFileCount,
|
|
6070
|
-
...defaultBranch !== null ? { defaultBranch } : {}
|
|
6166
|
+
...defaultBranch !== null ? { defaultBranch } : {},
|
|
6167
|
+
...input.doctorVersion !== void 0 ? { doctorVersion: input.doctorVersion } : {},
|
|
6168
|
+
...githubActionsScoreMetadata,
|
|
6169
|
+
...githubViewerPermission !== null ? { githubViewerPermission } : {}
|
|
6071
6170
|
};
|
|
6072
6171
|
const lintIncludePaths = computeJsxIncludePaths([...input.includePaths]) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
|
|
6073
6172
|
const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
|
|
@@ -6617,7 +6716,8 @@ const diagnose = async (directory, options = {}) => {
|
|
|
6617
6716
|
adoptExistingLintConfig: initialLoadedConfig?.config?.adoptExistingLintConfig ?? true,
|
|
6618
6717
|
ignoredTags: new Set(initialLoadedConfig?.config?.ignore?.tags ?? []),
|
|
6619
6718
|
runDeadCode: options.deadCode ?? initialLoadedConfig?.config?.deadCode ?? true,
|
|
6620
|
-
isCi: false
|
|
6719
|
+
isCi: false,
|
|
6720
|
+
resolveLocalGithubViewerPermission: true
|
|
6621
6721
|
});
|
|
6622
6722
|
const output = await Effect.runPromise(program.pipe(Effect.provide(buildLayerStack()), Effect.provide(layerOtlp), Effect.catchReasons("ReactDoctorError", {
|
|
6623
6723
|
NoReactDependency: (reason) => Effect.die(new NoReactDependencyError(reason.directory)),
|
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",
|
|
@@ -57,15 +57,15 @@
|
|
|
57
57
|
"oxlint": "^1.66.0",
|
|
58
58
|
"prompts": "^2.4.2",
|
|
59
59
|
"typescript": ">=5.0.4 <7",
|
|
60
|
-
"oxlint-plugin-react-doctor": "0.2.
|
|
60
|
+
"oxlint-plugin-react-doctor": "0.2.6"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/prompts": "^2.4.9",
|
|
64
64
|
"commander": "^14.0.3",
|
|
65
65
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
66
66
|
"ora": "^9.4.0",
|
|
67
|
-
"@react-doctor/api": "0.2.
|
|
68
|
-
"@react-doctor/core": "0.2.
|
|
67
|
+
"@react-doctor/api": "0.2.6",
|
|
68
|
+
"@react-doctor/core": "0.2.6"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"eslint-plugin-react-hooks": "^6 || ^7"
|