mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-09-08 02:08:01 +08:00
Fix: Install only oxlint and oxfmt to ensure CI checks for web files. (#19197)
This commit is contained in:
11
.github/workflows/sep-tests.yml
vendored
11
.github/workflows/sep-tests.yml
vendored
@@ -135,6 +135,13 @@ jobs:
|
||||
# fi
|
||||
# fi
|
||||
|
||||
# lefthook's web-checks hook resolves oxlint/oxfmt through
|
||||
# tools/hooks/web_tools.sh, which needs node and npm on PATH.
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
|
||||
- name: Run Lefthook on changed files
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -159,6 +166,10 @@ jobs:
|
||||
# applying --fix or `git add`, so CI only checks and reports
|
||||
# failures instead of rewriting the working tree.
|
||||
LEFTHOOK_CHECK_ONLY=1 lefthook run pre-commit --files-from-stdin --no-auto-install < "$changed_files"
|
||||
# web-checks is a separate hook because it is not wired into local
|
||||
# commits: it installs a Node toolchain that only CI has a reason to
|
||||
# pay for. It is always check-only, so it needs no CHECK_ONLY flag.
|
||||
lefthook run web-checks --files-from-stdin --no-auto-install < "$changed_files"
|
||||
fi
|
||||
|
||||
- name: Set test level
|
||||
|
||||
62
lefthook.yml
62
lefthook.yml
@@ -1,4 +1,5 @@
|
||||
# There are 8 fix-capable jobs and 6 pure-check jobs.
|
||||
# There are 6 fix-capable jobs and 5 pure-check jobs in pre-commit, plus a
|
||||
# CI-only `web-checks` hook at the bottom of this file.
|
||||
# Dual-mode pre-commit: by default (local dev) jobs fix and stage changes
|
||||
# (`stage_fixed: true` batches the `git add` per job to avoid the index lock
|
||||
# that parallel `git add` calls would hit). In CI, set LEFTHOOK_CHECK_ONLY=1
|
||||
@@ -70,43 +71,6 @@ pre-commit:
|
||||
else
|
||||
gofmt -w {staged_files}
|
||||
fi
|
||||
- name: web-checks
|
||||
group:
|
||||
piped: true
|
||||
jobs:
|
||||
- name: web-deps
|
||||
glob: "web/**/*.{css,less,json,js,jsx,ts,tsx}"
|
||||
run: |
|
||||
TOOL=web/node_modules/.hookbin/node_modules/.bin
|
||||
if [ ! -x "$TOOL/oxfmt" ] || [ ! -x "$TOOL/oxlint" ]; then
|
||||
exit 0
|
||||
fi
|
||||
- name: web-oxfmt
|
||||
glob: "web/**/*.{css,less,json,js,jsx,ts,tsx}"
|
||||
exclude: ["web/**/*.min.js", "web/**/*.min.css", "web/node_modules/**", "web/.next/**", "web/dist/**"]
|
||||
root: "web/"
|
||||
stage_fixed: true
|
||||
run: |
|
||||
OXFMT=node_modules/.hookbin/node_modules/.bin/oxfmt
|
||||
if ! [ -x "$OXFMT" ]; then exit 0; fi
|
||||
if [ "${LEFTHOOK_CHECK_ONLY:-}" = "1" ]; then
|
||||
"$OXFMT" --check {staged_files}
|
||||
else
|
||||
"$OXFMT" {staged_files}
|
||||
fi
|
||||
- name: web-oxlint
|
||||
glob: "web/**/*.{js,jsx,ts,tsx}"
|
||||
exclude: ["web/**/*.min.js", "web/node_modules/**", "web/.next/**", "web/dist/**"]
|
||||
root: "web/"
|
||||
stage_fixed: true
|
||||
run: |
|
||||
OXLINT=node_modules/.hookbin/node_modules/.bin/oxlint
|
||||
if ! [ -x "$OXLINT" ]; then exit 0; fi
|
||||
if [ "${LEFTHOOK_CHECK_ONLY:-}" = "1" ]; then
|
||||
"$OXLINT" {staged_files}
|
||||
else
|
||||
"$OXLINT" --fix {staged_files}
|
||||
fi
|
||||
- name: check-yaml
|
||||
run: python3 tools/hooks/check_files.py yaml
|
||||
- name: check-json
|
||||
@@ -117,3 +81,25 @@ pre-commit:
|
||||
run: python3 tools/hooks/check_files.py merge-conflict
|
||||
- name: check-symlinks
|
||||
run: python3 tools/hooks/check_files.py symlinks
|
||||
|
||||
# CI-only hook. Local commits deliberately do not lint or format web/: the
|
||||
# toolchain is a Node dependency that backend contributors have no other reason
|
||||
# to install. sep-tests.yml runs `lefthook run web-checks` on the files a pull
|
||||
# request changed, and that run is what blocks. To get the same feedback by
|
||||
# hand, run `npm run lint` / `npm run format` from web/.
|
||||
web-checks:
|
||||
# Sequential: on a cold cache both jobs would otherwise run the same
|
||||
# `npm install --prefix` into the same directory at once. Piped, the first one
|
||||
# installs and the second reuses.
|
||||
piped: true
|
||||
jobs:
|
||||
- name: web-oxfmt
|
||||
glob: "web/**/*.{css,less,json,js,jsx,ts,tsx}"
|
||||
exclude: &web_exclude ["web/**/*.min.js", "web/**/*.min.css", "web/package-lock.json", "web/node_modules/**", "web/.next/**", "web/dist/**"]
|
||||
root: "web/"
|
||||
run: '"$(../tools/hooks/web_tools.sh)/oxfmt" --check {files}'
|
||||
- name: web-oxlint
|
||||
glob: "web/**/*.{js,jsx,ts,tsx}"
|
||||
exclude: *web_exclude
|
||||
root: "web/"
|
||||
run: '"$(../tools/hooks/web_tools.sh)/oxlint" {files}'
|
||||
|
||||
63
tools/hooks/web_tools.sh
Executable file
63
tools/hooks/web_tools.sh
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Resolve the oxlint/oxfmt pair used by the web pre-commit jobs, installing the
|
||||
# versions pinned by web/package-lock.json on first use.
|
||||
#
|
||||
# Prints the directory holding both binaries on stdout; every progress message
|
||||
# goes to stderr, so callers can use `BIN=$(tools/hooks/web_tools.sh)`.
|
||||
#
|
||||
# POSIX sh: lefthook runs `run:` blocks through /bin/sh, which is dash on most
|
||||
# Linux runners.
|
||||
set -eu
|
||||
|
||||
repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/../.." && pwd)
|
||||
web_dir="$repo_root/web"
|
||||
lock="$web_dir/package-lock.json"
|
||||
|
||||
if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then
|
||||
echo "web_tools: node/npm not found — they are required to lint and format web/ files" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Version package-lock.json pins $1 to.
|
||||
locked_version() {
|
||||
node -p "require('$lock').packages['node_modules/$1'].version"
|
||||
}
|
||||
|
||||
# Version of an installed package, empty when it is not there.
|
||||
installed_version() {
|
||||
node -p "try{require('$1/package.json').version}catch(e){''}"
|
||||
}
|
||||
|
||||
oxlint_version=$(locked_version oxlint)
|
||||
oxfmt_version=$(locked_version oxfmt)
|
||||
|
||||
# A contributor who ran `npm install` in web/ already has the pinned pair;
|
||||
# reuse it instead of keeping a second copy in sync.
|
||||
if [ "$(installed_version "$web_dir/node_modules/oxlint")" = "$oxlint_version" ] &&
|
||||
[ "$(installed_version "$web_dir/node_modules/oxfmt")" = "$oxfmt_version" ]; then
|
||||
echo "$web_dir/node_modules/.bin"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Otherwise keep the binaries in a version-keyed cache OUTSIDE the repo. Inside
|
||||
# web/node_modules they would be wiped by the next `npm ci`, and npm would treat
|
||||
# the directory as a stray package.
|
||||
cache="${XDG_CACHE_HOME:-$HOME/.cache}/ragflow/web-tools/oxlint-$oxlint_version-oxfmt-$oxfmt_version"
|
||||
cache_bin="$cache/node_modules/.bin"
|
||||
|
||||
if [ ! -x "$cache_bin/oxlint" ] || [ ! -x "$cache_bin/oxfmt" ]; then
|
||||
echo "web_tools: installing oxlint@$oxlint_version oxfmt@$oxfmt_version" >&2
|
||||
mkdir -p "$cache"
|
||||
printf '{"name":"ragflow-web-tools","version":"1.0.0","private":true}\n' >"$cache/package.json"
|
||||
# Read the registry from web/.npmrc explicitly: with --prefix pointing at the
|
||||
# cache, npm's own project-config lookup no longer lands on web/.npmrc.
|
||||
registry=$(cd "$web_dir" && npm config get registry)
|
||||
# oxlint and oxfmt are prebuilt Rust binaries (5 packages, ~35 MB), so this
|
||||
# needs none of web/'s ~1.5 GB dependency tree.
|
||||
npm install --prefix "$cache" --registry "$registry" \
|
||||
--no-audit --no-fund --loglevel=error \
|
||||
"oxlint@$oxlint_version" "oxfmt@$oxfmt_version" >&2
|
||||
fi
|
||||
|
||||
echo "$cache_bin"
|
||||
1
web/src/custom.d.ts
vendored
1
web/src/custom.d.ts
vendored
@@ -23,6 +23,5 @@ declare module '*.md' {
|
||||
|
||||
declare module 'jsoneditor' {
|
||||
const JSONEditor: any;
|
||||
export default JSONEditor;
|
||||
export = JSONEditor;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import { SharedFrom } from '@/constants/chat';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useSetAgent } from '@/hooks/use-agent-request';
|
||||
import { useIsGoBackend } from '@/utils/backend-variant';
|
||||
import { ReactFlowProvider } from '@xyflow/react';
|
||||
import {
|
||||
ChevronDown,
|
||||
@@ -40,7 +41,6 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { ComponentPropsWithoutRef, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSyncExternalStore } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import AgentCanvas from './canvas';
|
||||
import { DropdownProvider } from './canvas/context';
|
||||
@@ -74,11 +74,7 @@ import { useAgentHistoryManager } from './use-agent-history-manager';
|
||||
import { VersionDialog } from './version-dialog';
|
||||
import WebhookSheet from './webhook-sheet';
|
||||
import { RunTooltip } from './flow-tooltip';
|
||||
import {
|
||||
debugRunLimitsTooltipKey,
|
||||
isGoBackend,
|
||||
subscribeBackendLanguage,
|
||||
} from '@/utils/backend-runtime';
|
||||
import { debugRunLimitsTooltipKey } from './utils/debug-run-limits';
|
||||
|
||||
/**
|
||||
* Standardizes dropdown menu item styling for agent management actions.
|
||||
@@ -110,14 +106,12 @@ export default function Agent() {
|
||||
} = useSetModalState();
|
||||
const { t } = useTranslation();
|
||||
useAgentHistoryManager();
|
||||
// Resolves (and re-renders when it resolves) the i18n key for the canvas
|
||||
// "Run" tooltip describing the Go-side debug preview limits. It is shown
|
||||
// ONLY for a dataflow (ingestion pipeline) canvas on the golang backend —
|
||||
// an agent canvas runs the agent, not an ingestion debug preview, so it
|
||||
// must never show this tooltip.
|
||||
const runTooltipKey = useSyncExternalStore(subscribeBackendLanguage, () =>
|
||||
debugRunLimitsTooltipKey(isGoBackend(), isPipeline),
|
||||
);
|
||||
const isGoBackend = useIsGoBackend();
|
||||
// Resolves the i18n key for the canvas "Run" tooltip describing the Go-side
|
||||
// debug preview limits. It is shown ONLY for a dataflow (ingestion pipeline)
|
||||
// canvas on the golang backend — an agent canvas runs the agent, not an
|
||||
// ingestion debug preview, so it must never show this tooltip.
|
||||
const runTooltipKey = debugRunLimitsTooltipKey(isGoBackend, isPipeline);
|
||||
|
||||
const { handleExportJson } = useHandleExportJsonFile();
|
||||
const { saveGraph, loading } = useSaveGraph();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { debugRunLimitsTooltipKey } from '@/utils/backend-runtime';
|
||||
import { debugRunLimitsTooltipKey } from './debug-run-limits';
|
||||
|
||||
describe('debugRunLimitsTooltipKey', () => {
|
||||
it('returns the key only for a dataflow (ingestion pipeline) canvas on golang', () => {
|
||||
21
web/src/pages/agent/utils/debug-run-limits.ts
Normal file
21
web/src/pages/agent/utils/debug-run-limits.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* debugRunLimitsTooltipKey returns the i18n key for the canvas "Run" button
|
||||
* tooltip describing the debug (dry-run) preview limits, or null when the
|
||||
* tooltip should not be shown.
|
||||
*
|
||||
* The tooltip applies ONLY to a dataflow (ingestion pipeline) canvas on the
|
||||
* golang backend. An agent canvas runs the agent/chat, not an ingestion
|
||||
* debug preview, so it must never show this tooltip. The python backend's
|
||||
* debug semantics also differ and are out of scope.
|
||||
*
|
||||
* It is a pure function of (backend language, is-pipeline) so it can be
|
||||
* unit-tested without mocking; the caller reads the backend through
|
||||
* `useIsGoBackend()`.
|
||||
*
|
||||
* The tooltip copy itself never names the backend language; only its
|
||||
* visibility is gated on these conditions.
|
||||
*/
|
||||
export const debugRunLimitsTooltipKey = (
|
||||
isGo: boolean,
|
||||
isPipeline: boolean,
|
||||
): string | null => (isGo && isPipeline ? 'flow.debugRunLimits' : null);
|
||||
@@ -3,7 +3,6 @@ import { transformExtractorParams } from '../../utils';
|
||||
|
||||
let mockIsGoBackend = true;
|
||||
jest.mock('@/utils/backend-runtime', () => ({
|
||||
isGoBackend: () => mockIsGoBackend,
|
||||
getBackendLanguage: () => (mockIsGoBackend ? 'go' : 'python'),
|
||||
}));
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useSaveDatasetSetting } from './hooks';
|
||||
|
||||
let mockIsGoBackend = true;
|
||||
jest.mock('@/utils/backend-runtime', () => ({
|
||||
isGoBackend: () => mockIsGoBackend,
|
||||
getBackendLanguage: () => (mockIsGoBackend ? 'go' : 'python'),
|
||||
}));
|
||||
jest.mock('@/hooks/use-knowledge-request', () => ({
|
||||
|
||||
@@ -2,7 +2,6 @@ import { buildOperatorNode } from '@/utils/pipeline-operator';
|
||||
|
||||
let mockIsGoBackend = true;
|
||||
jest.mock('@/utils/backend-runtime', () => ({
|
||||
isGoBackend: () => mockIsGoBackend,
|
||||
getBackendLanguage: () => (mockIsGoBackend ? 'go' : 'python'),
|
||||
}));
|
||||
|
||||
|
||||
@@ -48,31 +48,6 @@ export const fetchBackendLanguage = (): Promise<string> => promise;
|
||||
|
||||
export const getBackendLanguage = (): string | null => backendLanguage;
|
||||
|
||||
export const isGoBackend = (): boolean => backendLanguage === 'go';
|
||||
|
||||
/**
|
||||
* debugRunLimitsTooltipKey returns the i18n key for the canvas "Run" button
|
||||
* tooltip describing the debug (dry-run) preview limits, or null when the
|
||||
* tooltip should not be shown.
|
||||
*
|
||||
* The tooltip applies ONLY to a dataflow (ingestion pipeline) canvas on the
|
||||
* golang backend. An agent canvas runs the agent/chat, not an ingestion
|
||||
* debug preview, so it must never show this tooltip. The python backend's
|
||||
* debug semantics also differ and are out of scope.
|
||||
*
|
||||
* It is a pure function of (backend language, is-pipeline) so it can be
|
||||
* unit-tested without mocking, and the Agent derives it via
|
||||
* useSyncExternalStore(subscribeBackendLanguage, () =>
|
||||
* debugRunLimitsTooltipKey(isGoBackend(), isPipeline)).
|
||||
*
|
||||
* The tooltip copy itself never names the backend language; only its
|
||||
* visibility is gated on these conditions.
|
||||
*/
|
||||
export const debugRunLimitsTooltipKey = (
|
||||
isGo: boolean,
|
||||
isPipeline: boolean,
|
||||
): string | null => (isGo && isPipeline ? 'flow.debugRunLimits' : null);
|
||||
|
||||
export const subscribeBackendLanguage = (listener: Listener): (() => void) => {
|
||||
listeners.add(listener);
|
||||
return () => {
|
||||
|
||||
Reference in New Issue
Block a user