fix(sdk): code hygiene (#1357)

This commit is contained in:
David Duong
2025-07-07 14:44:29 +02:00
committed by GitHub
parent a138a8fa6c
commit c8d7a0aa9a
20 changed files with 207 additions and 62 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@langchain/langgraph-sdk": patch
---
Add missing optional peer dependency on react-dom
+78
View File
@@ -0,0 +1,78 @@
module.exports = {
extends: [
"airbnb-base",
"eslint:recommended",
"prettier",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended-legacy",
],
parserOptions: {
ecmaVersion: 12,
parser: "@typescript-eslint/parser",
project: "./tsconfig.json",
sourceType: "module",
},
plugins: ["@typescript-eslint", "no-instanceof"],
ignorePatterns: [
".eslintrc.cjs",
"scripts",
"node_modules",
"dist",
"dist-cjs",
"*.js",
"*.cjs",
"*.d.ts",
],
rules: {
"no-process-env": 2,
"no-instanceof/no-instanceof": 2,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-shadow": 0,
"@typescript-eslint/no-empty-interface": 0,
"@typescript-eslint/no-use-before-define": ["error", "nofunc"],
"@typescript-eslint/no-unused-vars": ["warn", { args: "none" }],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
"arrow-body-style": 0,
camelcase: 0,
"class-methods-use-this": 0,
"import/extensions": [2, "ignorePackages"],
"import/no-extraneous-dependencies": [
"error",
{ devDependencies: ["**/*.test.ts", "**/*.test-d.ts"] },
],
"import/no-unresolved": 0,
"import/prefer-default-export": 0,
"keyword-spacing": "error",
"max-classes-per-file": 0,
"max-len": 0,
"no-await-in-loop": 0,
"no-bitwise": 0,
"no-console": 0,
"no-empty-function": 0,
"no-restricted-syntax": 0,
"no-shadow": 0,
"no-continue": 0,
"no-void": 0,
"no-underscore-dangle": 0,
"no-use-before-define": 0,
"no-useless-constructor": 0,
"no-return-await": 0,
"consistent-return": 0,
"no-else-return": 0,
"func-names": 0,
"no-lonely-if": 0,
"prefer-rest-params": 0,
"new-cap": ["error", { properties: false, capIsNew: false }],
},
overrides: [
{
files: ["src/tests/**/*.ts"],
rules: {
"no-instanceof/no-instanceof": 0,
"@typescript-eslint/no-explicit-any": 0,
},
},
],
};
+18 -2
View File
@@ -8,8 +8,11 @@
"build": "yarn turbo:command build:internal --filter=@langchain/langgraph-sdk",
"build:internal": "yarn clean && yarn lc_build --create-entrypoints --pre --tree-shaking",
"prepack": "yarn run build",
"format:check": "prettier --check src",
"format": "prettier --write src",
"lint": "prettier --check src",
"lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js,.jsx,.tsx src/",
"lint": "yarn lint:eslint",
"lint:fix": "yarn lint:eslint --fix",
"test": "vitest run",
"typedoc": "typedoc && typedoc src/react/index.ts --out docs/react --options typedoc.react.json && typedoc src/auth/index.ts --out docs/auth --options typedoc.auth.json"
},
@@ -29,6 +32,15 @@
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"eslint": "^8.33.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-no-instanceof": "^1.0.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react-hooks": "^5.2.0",
"prettier": "^2.8.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
@@ -37,7 +49,8 @@
},
"peerDependencies": {
"@langchain/core": ">=0.2.31 <0.4.0",
"react": "^18 || ^19"
"react": "^18 || ^19",
"react-dom": "^18 || ^19"
},
"peerDependenciesMeta": {
"@langchain/core": {
@@ -45,6 +58,9 @@
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
},
"exports": {
+1
View File
@@ -65,6 +65,7 @@ const HTTP_STATUS_MAPPING: { [key: number]: string } = {
export class HTTPException extends Error {
status: number;
headers: HeadersInit;
constructor(
+1 -1
View File
@@ -9,7 +9,7 @@ import type {
} from "./types.js";
export class Auth<
TExtra = {},
TExtra = {}, // eslint-disable-line @typescript-eslint/ban-types
TAuthReturn extends BaseAuthReturn = BaseAuthReturn,
TUser extends BaseUser = ToUserLike<TAuthReturn>
> {
+2 -2
View File
@@ -98,7 +98,7 @@ interface ThreadDelete {
*/
interface ThreadSearch {
thread_id?: Maybe<string>;
status?: Maybe<"idle" | "busy" | "interrupted" | "error" | (string & {})>;
status?: Maybe<"idle" | "busy" | "interrupted" | "error" | (string & {})>; // eslint-disable-line @typescript-eslint/ban-types
metadata?: Maybe<Record<string, unknown>>;
values?: Maybe<Record<string, unknown>>;
limit?: Maybe<number>;
@@ -330,7 +330,7 @@ type CallbackParameter<
Event extends string = string,
Resource extends string = string,
Action extends string = string,
Value extends unknown = unknown,
Value = unknown,
TUser extends BaseUser = BaseUser
> = {
event: Event;
+19 -13
View File
@@ -49,6 +49,7 @@ function* iterateHeaders(
let iter: Iterable<(HeaderValue | HeaderValue | null[])[]>;
let shouldClear = false;
// eslint-disable-next-line no-instanceof/no-instanceof
if (headers instanceof Headers) {
const entries: [string, string][] = [];
headers.forEach((value, name) => {
@@ -62,7 +63,7 @@ function* iterateHeaders(
iter = Object.entries(headers ?? {});
}
for (let item of iter) {
for (const item of iter) {
const name = item[0];
if (typeof name !== "string")
throw new TypeError(
@@ -263,7 +264,7 @@ class BaseClient {
for (const [key, value] of Object.entries(mutatedOptions.params)) {
if (value == null) continue;
let strValue =
const strValue =
typeof value === "string" || typeof value === "number"
? value.toString()
: JSON.stringify(value);
@@ -345,7 +346,7 @@ export class CronsClient extends BaseClient {
assistantId: string,
payload?: CronsCreatePayload
): Promise<CronCreateForThreadResponse> {
const json: Record<string, any> = {
const json: Record<string, unknown> = {
schedule: payload?.schedule,
input: payload?.input,
config: payload?.config,
@@ -377,7 +378,7 @@ export class CronsClient extends BaseClient {
assistantId: string,
payload?: CronsCreatePayload
): Promise<CronCreateResponse> {
const json: Record<string, any> = {
const json: Record<string, unknown> = {
schedule: payload?.schedule,
input: payload?.input,
config: payload?.config,
@@ -879,7 +880,7 @@ export class ThreadsClient<
return this.fetch<void>(`/threads/${threadId}/state`, {
method: "PATCH",
json: { metadata: metadata },
json: { metadata },
});
}
@@ -973,7 +974,7 @@ export class RunsClient<
TUpdateType,
TCustomEventType
> {
const json: Record<string, any> = {
const json: Record<string, unknown> = {
input: payload?.input,
command: payload?.command,
config: payload?.config,
@@ -1011,6 +1012,7 @@ export class RunsClient<
const runMetadata = getRunMetadataFromResponse(response);
if (runMetadata) payload?.onRunCreated?.(runMetadata);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stream: ReadableStream<{ event: any; data: any }> = (
response.body || new ReadableStream({ start: (ctrl) => ctrl.close() })
)
@@ -1033,7 +1035,7 @@ export class RunsClient<
assistantId: string,
payload?: RunsCreatePayload
): Promise<Run> {
const json: Record<string, any> = {
const json: Record<string, unknown> = {
input: payload?.input,
command: payload?.command,
config: payload?.config,
@@ -1120,7 +1122,7 @@ export class RunsClient<
assistantId: string,
payload?: RunsWaitPayload
): Promise<ThreadState["values"]> {
const json: Record<string, any> = {
const json: Record<string, unknown> = {
input: payload?.input,
command: payload?.command,
config: payload?.config,
@@ -1239,7 +1241,7 @@ export class RunsClient<
method: "POST",
params: {
wait: wait ? "1" : "0",
action: action,
action,
},
});
}
@@ -1287,10 +1289,12 @@ export class RunsClient<
streamMode?: StreamMode | StreamMode[];
}
| AbortSignal
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): AsyncGenerator<{ id?: string; event: StreamEvent; data: any }> {
const opts =
typeof options === "object" &&
options != null &&
// eslint-disable-next-line no-instanceof/no-instanceof
options instanceof AbortSignal
? { signal: options }
: options;
@@ -1315,6 +1319,7 @@ export class RunsClient<
)
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const stream: ReadableStream<{ event: string; data: any }> = (
response.body || new ReadableStream({ start: (ctrl) => ctrl.close() })
)
@@ -1341,6 +1346,7 @@ export class RunsClient<
interface APIItem {
namespace: string[];
key: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: Record<string, any>;
created_at: string;
updated_at: string;
@@ -1373,7 +1379,7 @@ export class StoreClient extends BaseClient {
async putItem(
namespace: string[],
key: string,
value: Record<string, any>,
value: Record<string, unknown>,
options?: {
index?: false | string[] | null;
ttl?: number | null;
@@ -1441,7 +1447,7 @@ export class StoreClient extends BaseClient {
}
});
const params: Record<string, any> = {
const params: Record<string, unknown> = {
namespace: namespace.join("."),
key,
};
@@ -1524,7 +1530,7 @@ export class StoreClient extends BaseClient {
async searchItems(
namespacePrefix: string[],
options?: {
filter?: Record<string, any>;
filter?: Record<string, unknown>;
limit?: number;
offset?: number;
query?: string;
@@ -1603,7 +1609,7 @@ class UiClient extends BaseClient {
}
async getComponent(assistantId: string, agentName: string): Promise<string> {
return UiClient["getOrCached"](
return UiClient.getOrCached(
`${this.apiUrl}-${assistantId}-${agentName}`,
async () => {
const response = await this.asyncCaller.fetch(
+11 -9
View File
@@ -1,11 +1,10 @@
"use client";
import { useStream } from "../react/index.js";
import type { UIMessage } from "./types.js";
import * as React from "react";
import * as ReactDOM from "react-dom";
import * as JsxRuntime from "react/jsx-runtime";
import type { UIMessage } from "./types.js";
import { useStream } from "../react/index.js";
import type { UseStream } from "../react/stream.js";
const UseStreamContext = React.createContext<{
@@ -64,6 +63,7 @@ interface ComponentTarget {
class ComponentStore {
private cache: Record<string, ComponentTarget> = {};
private boundCache: Record<
string,
{
@@ -71,6 +71,7 @@ class ComponentStore {
getSnapshot: () => ComponentTarget | undefined;
}
> = {};
private callbacks: Record<
string,
((
@@ -184,17 +185,18 @@ export function LoadExternalComponent({
const clientComponent = components?.[message.name];
const hasClientComponent = clientComponent != null;
const fallbackComponent = isReactNode(fallback)
? fallback
: typeof fallback === "object" && fallback != null
? fallback?.[message.name]
: null;
let fallbackComponent = null;
if (isReactNode(fallback)) {
fallbackComponent = fallback;
} else if (typeof fallback === "object" && fallback != null) {
fallbackComponent = fallback?.[message.name];
}
const uiNamespace = namespace ?? stream.assistantId;
const uiClient = stream.client["~ui"];
React.useEffect(() => {
if (hasClientComponent) return;
uiClient.getComponent(uiNamespace, message.name).then((html) => {
void uiClient.getComponent(uiNamespace, message.name).then((html) => {
const dom = ref.current;
if (!dom) return;
const root = dom.shadowRoot ?? dom.attachShadow({ mode: "open" });
+1
View File
@@ -1,4 +1,5 @@
import { bootstrapUiContext } from "./client.js";
bootstrapUiContext();
export {
+1 -1
View File
@@ -32,7 +32,7 @@ export const typedUi = <Decl extends Record<string, ElementType>>(
}
) => {
type PropMap = { [K in keyof Decl]: ComponentPropsWithoutRef<Decl[K]> };
let items: (UIMessage | RemoveUIMessage)[] = [];
const items: (UIMessage | RemoveUIMessage)[] = [];
const stateKey = options?.stateKey ?? "ui";
const runId = (config.metadata?.run_id as string | undefined) ?? config.runId;
+1
View File
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ThreadState } from "../schema.js";
interface Node<StateType = any> {
+35 -26
View File
@@ -1,6 +1,22 @@
/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
"use client";
import {
type RefObject,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import {
type BaseMessageChunk,
type BaseMessage,
coerceMessageLikeToMessage,
convertToChunk,
isBaseMessageChunk,
} from "@langchain/core/messages";
import { Client, getClientConfigHash, type ClientConfig } from "../client.js";
import type {
Command,
@@ -30,22 +46,6 @@ import type {
ValuesStreamEvent,
} from "../types.stream.js";
import {
type RefObject,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import {
type BaseMessageChunk,
type BaseMessage,
coerceMessageLikeToMessage,
convertToChunk,
isBaseMessageChunk,
} from "@langchain/core/messages";
class StreamError extends Error {
constructor(data: { error?: string; name?: string; message: string }) {
super(data.message);
@@ -83,6 +83,7 @@ class MessageTupleManager {
// TODO: this is sometimes sent from the API
// figure out how to prevent this or move this to LC.js
if (serialized.type.endsWith("MessageChunk")) {
// eslint-disable-next-line no-param-reassign
serialized.type = serialized.type
.slice(0, -"MessageChunk".length)
.toLowerCase() as Message["type"];
@@ -91,7 +92,7 @@ class MessageTupleManager {
const message = coerceMessageLikeToMessage(serialized);
const chunk = tryConvertToChunk(message);
const id = (chunk ?? message).id;
const { id } = chunk ?? message;
if (!id) {
console.warn(
"No message ID found for chunk, ignoring in state",
@@ -134,33 +135,38 @@ function unique<T>(array: T[]) {
}
function findLastIndex<T>(array: T[], predicate: (item: T) => boolean) {
for (let i = array.length - 1; i >= 0; i--) {
for (let i = array.length - 1; i >= 0; i -= 1) {
if (predicate(array[i])) return i;
}
return -1;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface Node<StateType = any> {
type: "node";
value: ThreadState<StateType>;
path: string[];
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface Fork<StateType = any> {
type: "fork";
items: Array<Sequence<StateType>>;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface Sequence<StateType = any> {
type: "sequence";
items: Array<Node<StateType> | Fork<StateType>>;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface ValidFork<StateType = any> {
type: "fork";
items: Array<ValidSequence<StateType>>;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface ValidSequence<StateType = any> {
type: "sequence";
items: [Node<StateType>, ...(Node<StateType> | ValidFork<StateType>)[]];
@@ -228,8 +234,8 @@ function getBranchSequence<StateType extends Record<string, unknown>>(
for (const value of children) {
const id = value.checkpoint.checkpoint_id!;
let sequence = task.sequence;
let path = task.path;
let { sequence } = task;
let { path } = task;
if (fork != null) {
sequence = { type: "sequence", items: [] };
fork.items.unshift(sequence);
@@ -341,12 +347,12 @@ function useThreadHistory<StateType extends Record<string, unknown>>(
clearCallbackRef.current?.();
return Promise.resolve([]);
},
[]
[clearCallbackRef]
);
useEffect(() => {
if (submittingRef.current) return;
fetcher(threadId);
void fetcher(threadId);
}, [fetcher, clientHash, submittingRef, threadId]);
return {
@@ -775,6 +781,7 @@ export function useStream<
| ErrorStreamEvent
| FeedbackStreamEvent;
// eslint-disable-next-line prefer-const
let { assistantId, messagesKey, onCreated, onError, onFinish } = options;
const reconnectOnMountRef = useRef(options.reconnectOnMount);
@@ -948,7 +955,7 @@ export function useStream<
if (runMetadataStorage && threadId) {
const runId = runMetadataStorage.getItem(`lg:stream:${threadId}`);
if (runId) client.runs.cancel(threadId, runId);
if (runId) void client.runs.cancel(threadId, runId);
runMetadataStorage.removeItem(`lg:stream:${threadId}`);
}
@@ -1039,7 +1046,7 @@ export function useStream<
} catch (error) {
if (
!(
error instanceof Error &&
error instanceof Error && // eslint-disable-line no-instanceof/no-instanceof
(error.name === "AbortError" || error.name === "TimeoutError")
)
) {
@@ -1062,6 +1069,7 @@ export function useStream<
lastEventId?: string,
options?: { streamMode?: StreamMode | StreamMode[] }
) => {
// eslint-disable-next-line no-param-reassign
lastEventId ??= "-1";
if (!threadId) return;
await consumeStream(async (signal: AbortSignal) => {
@@ -1124,6 +1132,7 @@ export function useStream<
const checkpoint =
submitOptions?.checkpoint ?? threadHead?.checkpoint ?? undefined;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
if (checkpoint != null) delete checkpoint.thread_id;
let rejoinKey: `lg:stream:${string}` | undefined;
@@ -1200,7 +1209,7 @@ export function useStream<
useEffect(() => {
if (reconnectKey && reconnectRef.current.shouldReconnect) {
reconnectRef.current.shouldReconnect = false;
joinStreamRef.current?.(reconnectKey.runId);
void joinStreamRef.current?.(reconnectKey.runId);
}
}, [reconnectKey]);
@@ -1220,7 +1229,7 @@ export function useStream<
isLoading,
stop,
submit,
submit, // eslint-disable-line @typescript-eslint/no-misused-promises
joinStream,
+3 -2
View File
@@ -81,7 +81,7 @@ export interface GraphSchema {
export type Subgraphs = Record<string, GraphSchema>;
export type Metadata = Optional<{
source?: "input" | "loop" | "update" | (string & {});
source?: "input" | "loop" | "update" | (string & {}); // eslint-disable-line @typescript-eslint/ban-types
step?: number;
@@ -146,7 +146,7 @@ export interface AssistantGraph {
*/
export interface Interrupt<TValue = unknown> {
value?: TValue;
when: "during" | (string & {});
when: "during" | (string & {}); // eslint-disable-line @typescript-eslint/ban-types
resumable?: boolean;
ns?: string[];
}
@@ -283,6 +283,7 @@ export interface ListNamespaceResponse {
export interface Item {
namespace: string[];
key: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: Record<string, any>;
createdAt: string;
updatedAt: string;
+1
View File
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// Wrap the default fetch call due to issues with illegal invocations
// in some environments:
// https://stackoverflow.com/questions/69876859/why-does-bind-fix-failed-to-execute-fetch-on-window-illegal-invocation-err
+1
View File
@@ -32,6 +32,7 @@ export type AIMessage = BaseMessage & {
tool_calls?:
| {
name: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: { [x: string]: any };
id?: string | undefined;
type?: "tool_call" | undefined;
+1 -1
View File
@@ -158,7 +158,7 @@ export type EventsStreamEvent = {
| "tool"
| "retriever"
| "prompt"}_${"start" | "stream" | "end"}`
| (string & {});
| (string & {}); // eslint-disable-line @typescript-eslint/ban-types
name: string;
tags: string[];
run_id: string;
+1 -1
View File
@@ -16,7 +16,7 @@ export type StreamEvent =
| "messages/metadata"
| "messages/complete"
| "messages"
| (string & {});
| (string & {}); // eslint-disable-line @typescript-eslint/ban-types
export interface Send {
node: string;
+4 -2
View File
@@ -37,7 +37,7 @@ export interface AsyncCallerParams {
*
* By default we expect the `fetch` is available in the global scope.
*/
fetch?: typeof fetch | ((...args: any[]) => any);
fetch?: typeof fetch | ((...args: any[]) => any); // eslint-disable-line @typescript-eslint/no-explicit-any
}
export interface AsyncCallerCallOptions {
@@ -58,6 +58,7 @@ function isResponse(x: unknown): x is Response {
*/
class HTTPError extends Error {
status: number;
text: string;
response?: Response;
@@ -135,7 +136,7 @@ export class AsyncCaller {
callable: T,
...args: Parameters<T>
): Promise<Awaited<ReturnType<T>>> {
const onFailedResponseHook = this.onFailedResponseHook;
const { onFailedResponseHook } = this;
return this.queue.add(
() =>
pRetry(
@@ -166,6 +167,7 @@ export class AsyncCaller {
throw error;
}
// eslint-disable-next-line no-instanceof/no-instanceof
if (error instanceof HTTPError) {
if (STATUS_NO_RETRY.includes(error.status)) {
throw error;
+2 -2
View File
@@ -142,7 +142,7 @@ export function SSEDecoder() {
} else if (fieldName === "id") {
if (value.indexOf(NULL) === -1) lastEventId = decoder.decode(value);
} else if (fieldName === "retry") {
const retryNum = Number.parseInt(decoder.decode(value));
const retryNum = Number.parseInt(decoder.decode(value), 10);
if (!Number.isNaN(retryNum)) retry = retryNum;
}
},
@@ -161,7 +161,7 @@ export function SSEDecoder() {
function joinArrays(data: ArrayLike<number>[]) {
const totalLength = data.reduce((acc, curr) => acc + curr.length, 0);
let merged = new Uint8Array(totalLength);
const merged = new Uint8Array(totalLength);
let offset = 0;
for (const c of data) {
merged.set(c, offset);
+21
View File
@@ -2044,6 +2044,15 @@ __metadata:
"@types/react": "npm:^19.0.8"
"@types/react-dom": "npm:^19.0.3"
"@types/uuid": "npm:^9.0.1"
"@typescript-eslint/eslint-plugin": "npm:^6.12.0"
"@typescript-eslint/parser": "npm:^6.12.0"
eslint: "npm:^8.33.0"
eslint-config-airbnb-base: "npm:^15.0.0"
eslint-config-prettier: "npm:^8.6.0"
eslint-plugin-import: "npm:^2.29.1"
eslint-plugin-no-instanceof: "npm:^1.0.1"
eslint-plugin-prettier: "npm:^4.2.1"
eslint-plugin-react-hooks: "npm:^5.2.0"
p-queue: "npm:^6.6.2"
p-retry: "npm:4"
prettier: "npm:^2.8.3"
@@ -2055,11 +2064,14 @@ __metadata:
peerDependencies:
"@langchain/core": ">=0.2.31 <0.4.0"
react: ^18 || ^19
react-dom: ^18 || ^19
peerDependenciesMeta:
"@langchain/core":
optional: true
react:
optional: true
react-dom:
optional: true
languageName: unknown
linkType: soft
@@ -7399,6 +7411,15 @@ __metadata:
languageName: node
linkType: hard
"eslint-plugin-react-hooks@npm:^5.2.0":
version: 5.2.0
resolution: "eslint-plugin-react-hooks@npm:5.2.0"
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
checksum: 10/ebb79e9cf69ae06e3a7876536653c5e556b5fd8cd9dc49577f10a6e728360e7b6f5ce91f4339b33e93b26e3bb23805418f8b5e75db80baddd617b1dffe73bed1
languageName: node
linkType: hard
"eslint-scope@npm:^7.2.2":
version: 7.2.2
resolution: "eslint-scope@npm:7.2.2"