LibreChat/package.json
Danny Avila d369c649ed
🪥 chore: Run CI's Static Checks on Each Commit's Diff (#15303)
* 🪝 chore: Run Static Checks on Every Commit

Adds `scripts/static-checks.mts`, a local port of the Static Checks CI job
(.github/workflows/static-checks.yml) scoped to the files in a diff. It
resolves the changed-file list, applies the same `dorny/paths-filter` groups
the job uses, and runs whichever checks those paths activate — ESLint,
Prettier, import order, ESLint config validation, package.json validation, and
(behind `--full`) config migration tests, unused i18n keys and depcheck. Like
the job, every selected check runs even after one fails and the failures are
summarized at the end.

The pre-commit hook keeps lint-staged for the per-file layer, which verifies
the exact staged content of partially staged files, then runs the script for
everything lint-staged cannot cover. lint-staged now uses the job's ESLint
invocation, so warnings fail locally the way they fail CI.

The slow gates stay opt-in (`npm run static-checks:full`, or
`STATIC_CHECKS_FULL=1`) to keep commit latency unchanged.

Hooks were never installed: `config/prepare.js` existed but no `prepare` script
called it, so the hook only ran where `core.hooksPath` had been set by hand.
Replaces it with an inline `prepare` — both Dockerfiles run `npm ci` after
copying only the manifests, so a `node config/prepare.js` step would fail the
image build, and husky is absent from `--omit=dev` installs.

The i18n scan is a single pass over the source identifiers rather than one grep
per key, verified to flag exactly the same keys as the CI loop (including the
substring and dynamic-key cases) in 0.5s instead of 14s.

* 🩹 fix: Address Codex Round 1 on the Static Checks Runner

Activate gates from the unfiltered changed-path list. `dorny/paths-filter`
matches deleted paths too, so gating on the `--diff-filter=ACMRTUXB` list the
per-file steps use let a delete-only commit — the last reference to a
translation key, say — slip past the i18n and depcheck gates. The two lists are
now derived separately, the way CI derives them.

Pass `-m` to `git diff-tree` in `--commit` mode. Without it a merge commit
emits no paths at all, so `--commit <merge-sha>` reported "Nothing to check";
a real merge in this repo's history goes from 0 to 97 files.

Build the workspaces the config suite imports instead of skipping when `dist`
is absent. The three `dist` directories are gitignored and `npm ci` does not
produce them, so a fresh checkout reported a pass for a gate that never ran —
and an existing `dist` could be stale. Each is a sub-second tsdown build.

Resolve a global depcheck through a shell on Windows, where npm exposes it as
`depcheck.cmd` and `spawnSync` cannot see the shim.

Skip dot directories when walking for imports. `.claude/worktrees/` can hold a
full checkout per branch — 117 on this machine — and the root-wide scan behind
the depcheck gate walked every one of them.

Records the remaining boundary in the header: the per-file checks see exact
staged content via lint-staged, while the tree-wide gates read the working
tree, as running them by hand would.

* 🩹 fix: Address Codex Round 2 on the Static Checks Runner

Treat an unresolvable checker as a failure. ESLint and Prettier missing meant
the runner printed "All affected static checks passed" without having linted
anything; only depcheck, which CI installs globally and this documents as
optional, may still skip.

Catch per-check exceptions. The runner promises that every selected check runs
even after a failure, but a throw — a malformed translation JSON, say —
escaped and cancelled the checks after it. Each is now recorded as that
check's failure; verified that depcheck still runs after i18n throws.

Restrict `--commit` to the checked-out commit. Paths came from the named
commit while contents came from the working tree, so an older revision was
scored against the wrong file contents: a file added then deleted vanished,
and one modified since was read at its newer contents. It now fails with a
pointer to `--against`.

Reject unknown options. `--ful` silently ran the fast tier and `--commmit HEAD`
treated `HEAD` as a file path, both exiting 0 and implying gates had run.

Cover the runner in CI. `scripts/**` was absent from the workflow's trigger
paths, so a PR touching only the script the pre-commit hook now depends on got
no Static Checks run — and ESLint has no flat-config match for
`scripts/**/*.mts`, so nothing else loads it either. Adds the trigger path, a
`runner` filter group and a step that runs the script against the PR's own diff.

* 🔗 feat: Add Circular Dependency and TypeScript Gates

Both already run in CI as jobs of the Backend Unit Tests workflow; this brings
them to the local runner so they land before a push rather than after.

Circular dependencies (`node config/circular-deps.mjs`) is fast enough at 0.9s
to sit in the per-commit tier, gated on the same paths that trigger the CI job.

TypeScript stays opt-in behind `--full`: the five projects cost between 2.3s
and 20.9s each, which is too much per commit. Each project declares the paths
that can affect it — its own sources plus its upstream packages — so an edit to
data-provider still typechecks data-schemas, api, packages/client and client,
while an `api/**`-only change runs none of them, since no typechecked project
includes that directory. The builds a project's imports resolve through are
made first, mirroring the CI jobs' dependency on the build artifacts, through a
helper the config suite now shares.

Also addresses codex round 3:

Reject conflicting target selectors. `--against origin/dev package.json`
silently checked only the file, and `--against <bad-ref> --commit HEAD` never
resolved the bad base, so a caller could believe a range had been checked.

Require a clean worktree in `--commit` mode. The HEAD-only restriction was not
enough: contents still come from the working tree, so an uncommitted edit was
scored against the named commit — an invalid uncommitted package.json failing a
valid HEAD, or an uncommitted fix masking a defect in it.

The summary now names how many checks were skipped rather than reporting a
bare pass, and a typecheck failure carries the stale-workspace-build hint —
inside a git worktree `librechat-data-provider` resolves to the main checkout,
whose dist can predate the branch and shows up as missing properties.

* 🩹 fix: Address Codex Round 4 on the Static Checks Runner

Diff `--against` from the merge base. A two-dot diff reports the base branch's
own commits in reverse once it advances, so `--against origin/dev` scored 64
files for a branch that changed 7, activating gates for files the branch never
touched. Three dots makes the documented PR-style command mean what it says.

Typecheck on root manifest changes. Both review workflows trigger their
TypeScript jobs on package.json and package-lock.json, because a dependency or
@types bump breaks compilation on its own; the local filter ignored them, so
`static-checks:full` passed where CI would fail.

Validate every workspace manifest. The list mirrored the four the CI step
happens to name, so a malformed packages/api, data-provider or data-schemas
manifest passed validation in the revision modes, which have no lint-staged
pass behind them. Both lists now cover all seven.

Make the runner smoke execute a check. `--list` never runs one, and a
script-only PR activates no group, so the CI coverage added for exactly that
case could pass with the execution path untouched. It now runs against an
explicit target.

* 🩹 fix: Address Codex Round 5 on the Static Checks Runner

Activate the JSON gate for every manifest it validates. Round 4 added the four
workspace manifests to the validation list but not to the filter that turns the
gate on, so a malformed packages/data-provider or data-schemas manifest still
passed when it was the only changed file — the list grew and the trigger did
not. The same two entries also feed the unused-package calculation, reached
through api/package.json's @librechat/data-schemas dependency.

Include the owning workflows in the imported gates' filters. Circular
dependencies and TypeScript come from the review workflows, both of which list
their own YAML in `on.paths` and therefore rerun those jobs when the workflow
changes; locally the gates stayed inactive, so a change to how they are built
or invoked could bypass the local equivalent. Added to the group filters and to
the per-project predicates, since a workflow-only change would otherwise
activate the group and then select no project.

Bound command batches by characters rather than file count. Windows caps a
command line at 32767 characters, far below POSIX ARG_MAX, and a count does not
bound that: 400 of this repository's longer paths already come to 30176
characters before the executable and fixed arguments. Verified that a list
spanning several batches still reports a defect in its final file.
2026-08-28 10:08:37 -04:00

251 lines
14 KiB
JSON

{
"name": "LibreChat",
"version": "v0.8.8-rc1",
"description": "",
"packageManager": "npm@11.13.0",
"workspaces": [
"api",
"client",
"packages/*"
],
"scripts": {
"prepare": "node -e \"import('husky').then((h) => h.default()).catch(() => {})\"",
"update": "node config/update.js",
"add-balance": "node config/add-balance.js",
"set-balance": "node config/set-balance.js",
"list-balances": "node config/list-balances.js",
"user-stats": "node config/user-stats.js",
"rebuild:package-lock": "node config/packages",
"reinstall": "node config/update.js -l -g",
"smart-reinstall": "node config/smart-reinstall.js",
"b:reinstall": "bun config/update.js -b -l -g",
"reinstall:docker": "node config/update.js -d -g",
"update:local": "node config/update.js -l",
"update:docker": "node config/update.js -d",
"update:single": "node config/update.js -s",
"update:sudo": "node config/update.js --sudo",
"update:deployed": "node config/deployed-update.js",
"rebase:deployed": "node config/deployed-update.js --rebase",
"start:deployed": "docker compose -f ./deploy-compose.yml up -d || docker-compose -f ./deploy-compose.yml up -d",
"stop:deployed": "docker compose -f ./deploy-compose.yml down || docker-compose -f ./deploy-compose.yml down",
"upgrade": "node config/upgrade.js",
"create-user": "node config/create-user.js",
"invite-user": "node config/invite-user.js",
"list-users": "node config/list-users.js",
"reset-password": "node config/reset-password.js",
"ban-user": "node config/ban-user.js",
"delete-user": "node config/delete-user.js",
"reset-meili-sync": "node config/reset-meili-sync.js",
"update-banner": "node config/update-banner.js",
"delete-banner": "node config/delete-banner.js",
"backend": "cross-env NODE_ENV=production node api/server/index.js",
"backend:inspect": "cross-env NODE_ENV=production node --inspect --expose-gc api/server/index.js",
"backend:dev": "cross-env NODE_ENV=development npx nodemon api/server/index.js",
"redis:single": "bash scripts/redis-mode.sh single",
"redis:cluster": "bash scripts/redis-mode.sh cluster",
"redis:stop": "bash scripts/redis-mode.sh stop",
"backend:redis:single": "cross-env USE_REDIS=true USE_REDIS_CLUSTER=false REDIS_URI=redis://127.0.0.1:6379 npm run backend",
"backend:redis:cluster": "cross-env USE_REDIS=true USE_REDIS_CLUSTER=true REDIS_URI=redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003 npm run backend",
"backend:dev:redis:single": "cross-env USE_REDIS=true USE_REDIS_CLUSTER=false REDIS_URI=redis://127.0.0.1:6379 npm run backend:dev",
"backend:dev:redis:cluster": "cross-env USE_REDIS=true USE_REDIS_CLUSTER=true REDIS_URI=redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003 npm run backend:dev",
"backend:experimental": "cross-env NODE_ENV=production node api/server/experimental.js",
"backend:stop": "node config/stop-backend.js",
"build:data-provider": "cd packages/data-provider && npm run build",
"build:api": "cd packages/api && npm run build",
"build:data-schemas": "cd packages/data-schemas && npm run build",
"build:client": "cd client && npm run build",
"build:client-package": "cd packages/client && npm run build",
"build:packages": "npm run build:data-provider && npm run build:data-schemas && npm run build:api && npm run build:client-package",
"build": "npx turbo run build",
"build:safe": "npx turbo run build --no-daemon",
"frontend": "npm run build:data-provider && npm run build:data-schemas && npm run build:api && npm run build:client-package && cd client && npm run build",
"frontend:ci": "npm run build:data-provider && npm run build:client-package && cd client && npm run build:ci",
"frontend:dev": "cd client && npm run dev",
"e2e:prepare": "npm run frontend",
"e2e": "npm run e2e:prepare && playwright test --config=e2e/playwright.config.local.ts",
"e2e:headed": "npm run e2e:prepare && playwright test --config=e2e/playwright.config.local.ts --headed",
"e2e:a11y": "npm run e2e:prepare && playwright test --config=e2e/playwright.config.a11y.ts --headed",
"e2e:ci": "npm run e2e:prepare && playwright test --config=e2e/playwright.config.ts",
"e2e:mock": "npm run e2e:prepare && playwright test --config=e2e/playwright.config.mock.ts",
"e2e:mock:redis": "npm run e2e:prepare && cross-env E2E_STREAM_STORE=redis playwright test --config=e2e/playwright.config.mock.ts",
"e2e:mock:redis:transport": "npm run e2e:prepare && cross-env E2E_STREAM_STORE=redis playwright test --config=e2e/playwright.config.redis.ts",
"e2e:benchmark:agents": "npm run e2e:prepare && playwright test --config=e2e/playwright.config.benchmark.ts agent-startup.latency.spec.ts",
"e2e:benchmark:navigation": "npm run e2e:prepare && playwright test --config=e2e/playwright.config.navigation-perf.ts",
"e2e:benchmark:reasoning": "npm run e2e:prepare && playwright test --config=e2e/playwright.config.reasoning-perf.ts",
"e2e:bombadil": "npm run e2e:prepare && playwright test --config=e2e/playwright.config.bombadil.ts",
"e2e:bombadil:run": "playwright test --config=e2e/playwright.config.bombadil.ts",
"e2e:bombadil:branch-reload": "npm run e2e:prepare && cross-env BOMBADIL_SPECIFICATION=branch-reload.specification.ts BOMBADIL_TIME_LIMIT=30s playwright test --config=e2e/playwright.config.bombadil.ts",
"e2e:bombadil:branch-reload:run": "cross-env BOMBADIL_SPECIFICATION=branch-reload.specification.ts BOMBADIL_TIME_LIMIT=30s playwright test --config=e2e/playwright.config.bombadil.ts",
"e2e:bombadil:fork-lifecycle": "npm run e2e:prepare && cross-env BOMBADIL_SPECIFICATION=fork-lifecycle.specification.ts BOMBADIL_TIME_LIMIT=30s playwright test --config=e2e/playwright.config.bombadil.ts",
"e2e:bombadil:fork-lifecycle:run": "cross-env BOMBADIL_SPECIFICATION=fork-lifecycle.specification.ts BOMBADIL_TIME_LIMIT=30s playwright test --config=e2e/playwright.config.bombadil.ts",
"e2e:bombadil:model-lifecycle": "npm run e2e:prepare && cross-env BOMBADIL_SPECIFICATION=model-lifecycle.specification.ts BOMBADIL_TIME_LIMIT=30s playwright test --config=e2e/playwright.config.bombadil.ts",
"e2e:bombadil:model-lifecycle:run": "cross-env BOMBADIL_SPECIFICATION=model-lifecycle.specification.ts BOMBADIL_TIME_LIMIT=30s playwright test --config=e2e/playwright.config.bombadil.ts",
"e2e:bombadil:hitl": "npm run e2e:prepare && cross-env BOMBADIL_SPECIFICATION=hitl-lifecycle.specification.ts BOMBADIL_TIME_LIMIT=45s playwright test --config=e2e/playwright.config.bombadil.ts",
"e2e:bombadil:hitl:run": "cross-env BOMBADIL_SPECIFICATION=hitl-lifecycle.specification.ts BOMBADIL_TIME_LIMIT=45s playwright test --config=e2e/playwright.config.bombadil.ts",
"e2e:bombadil:steering": "npm run e2e:prepare && cross-env BOMBADIL_SPECIFICATION=steering-lifecycle.specification.ts BOMBADIL_TIME_LIMIT=75s playwright test --config=e2e/playwright.config.bombadil.ts",
"e2e:bombadil:steering:run": "cross-env BOMBADIL_SPECIFICATION=steering-lifecycle.specification.ts BOMBADIL_TIME_LIMIT=75s playwright test --config=e2e/playwright.config.bombadil.ts",
"e2e:mock:enforce": "npm run e2e:prepare && cross-env E2E_MODEL_SPECS_ENFORCE=true playwright test --config=e2e/playwright.config.mock.ts enforced-model-specs.spec.ts",
"e2e:mock:ci": "npm run e2e:prepare && playwright test --config=e2e/playwright.config.mock.ts",
"e2e:debug": "npm run e2e:prepare && cross-env PWDEBUG=1 playwright test --config=e2e/playwright.config.local.ts",
"e2e:record": "npm run e2e:prepare && cross-env E2E_BASE_URL=http://localhost:3333 node e2e/setup/record.js",
"e2e:record:local": "npm run e2e:prepare && node e2e/setup/record.js --profile=local",
"e2e:codegen": "npx playwright codegen --target=playwright-test --test-id-attribute=data-testid --load-storage=e2e/storageState.json http://localhost:3080/c/new",
"e2e:login": "npx playwright codegen --save-storage=e2e/storageState.json http://localhost:3080/login",
"e2e:github": "act -W .github/workflows/playwright.yml --secret-file my.secrets",
"test:client": "cd client && npm run test:ci",
"test:api": "cd api && npm run test:ci",
"test:packages:api": "cd packages/api && npm run test:ci",
"test:packages:data-provider": "cd packages/data-provider && npm run test:ci",
"test:packages:data-schemas": "cd packages/data-schemas && npm run test:ci",
"test:config": "jest --config config/jest.config.js",
"test:all": "npm run test:client && npm run test:api && npm run test:packages:api && npm run test:packages:data-provider && npm run test:packages:data-schemas",
"e2e:update": "npm run e2e:prepare && playwright test --config=e2e/playwright.config.local.ts --update-snapshots",
"e2e:report": "npx playwright show-report e2e/playwright-report",
"lint:fix": "eslint . --fix",
"lint": "eslint .",
"sort-imports": "node scripts/sort-imports.mts",
"sort-imports:check": "node scripts/sort-imports.mts --check",
"static-checks": "node scripts/static-checks.mts",
"static-checks:full": "node scripts/static-checks.mts --full",
"format": "npx prettier --write \"{,!(node_modules|venv)/**/}*.{js,jsx,ts,tsx}\"",
"b:api": "NODE_ENV=production bun run api/server/index.js",
"b:api-inspect": "NODE_ENV=production bun --inspect run api/server/index.js",
"b:api:dev": "NODE_ENV=production bun run --watch api/server/index.js",
"b:data": "cd packages/data-provider && bun run b:build",
"b:build:client-package": "cd packages/client && bun run b:build",
"b:data-schemas": "cd packages/data-schemas && bun run b:build",
"b:build:api": "cd packages/api && bun run b:build",
"b:build:client": "cd client && bun --bun run b:build",
"b:client": "bun --bun run b:data && bun --bun run b:data-schemas && bun --bun run b:build:api && bun --bun run b:build:client-package && bun --bun run b:build:client",
"b:client:dev": "cd client && bun run b:dev",
"b:test:client": "cd client && bun run b:test",
"b:test:api": "cd api && bun run b:test",
"b:balance": "bun config/add-balance.js",
"b:list-balances": "bun config/list-balances.js",
"reset-terms": "node config/reset-terms.js",
"migrate:terms-timestamp": "node config/migrate-terms-timestamp.js",
"flush-cache": "node config/flush-cache.js",
"migrate:agent-permissions:dry-run": "node config/migrate-agent-permissions.js --dry-run",
"migrate:agent-permissions": "node config/migrate-agent-permissions.js",
"migrate:agent-permissions:batch": "node config/migrate-agent-permissions.js --batch-size=50",
"migrate:prompt-permissions:dry-run": "node config/migrate-prompt-permissions.js --dry-run",
"migrate:prompt-permissions": "node config/migrate-prompt-permissions.js",
"migrate:prompt-permissions:batch": "node config/migrate-prompt-permissions.js --batch-size=50",
"migrate:shared-link-permissions:dry-run": "node config/migrate-shared-link-permissions.js --dry-run",
"migrate:shared-link-permissions": "node config/migrate-shared-link-permissions.js",
"migrate:shared-link-permissions:batch": "node config/migrate-shared-link-permissions.js --batch-size=50",
"migrate:orphaned-agent-files:dry-run": "node config/migrate-orphaned-agent-files.js --dry-run",
"migrate:orphaned-agent-files": "node config/migrate-orphaned-agent-files.js",
"migrate:orphaned-agent-files:batch": "node config/migrate-orphaned-agent-files.js --batch-size=50",
"migrate:code-file-duplicates:dry-run": "node config/migrate-code-file-duplicates.js --dry-run",
"migrate:code-file-duplicates": "node config/migrate-code-file-duplicates.js",
"migrate:code-file-duplicates:batch": "node config/migrate-code-file-duplicates.js --batch-size=50"
},
"repository": {
"type": "git",
"url": "git+https://github.com/danny-avila/LibreChat.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/danny-avila/LibreChat/issues"
},
"homepage": "https://librechat.ai/",
"devDependencies": {
"@antithesishq/bombadil": "0.6.1",
"@axe-core/playwright": "^4.10.1",
"@eslint/compat": "^1.2.6",
"@eslint/eslintrc": "^3.3.4",
"@eslint/js": "^9.20.0",
"@playwright/test": "^1.62.1",
"@types/react-virtualized": "^9.22.0",
"brace-expansion": "^2.1.2",
"caniuse-lite": "^1.0.30001741",
"cross-env": "^7.0.3",
"elliptic": "^6.6.1",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.0.1",
"eslint-import-resolver-typescript": "^3.7.0",
"eslint-plugin-i18next": "^6.1.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^29.1.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.14.0",
"husky": "^9.1.7",
"jest": "^30.2.0",
"lint-staged": "^15.4.3",
"prettier": "^3.5.0",
"prettier-plugin-tailwindcss": "^0.6.11",
"turbo": "^2.10.5",
"typescript-eslint": "^8.60.1"
},
"overrides": {
"minimatch@3": {
"brace-expansion": "^1.1.16"
},
"minimatch@5": {
"brace-expansion": "^2.1.2"
},
"minimatch@9": {
"brace-expansion": "^2.1.2"
},
"minimatch@10": {
"brace-expansion": "^5.0.8"
},
"@xmldom/xmldom": "^0.8.13",
"elliptic": "^6.6.1",
"form-data": "^4.0.6",
"langsmith": "^0.6.0",
"postcss": "^8.5.18",
"tslib": "^2.8.1",
"fast-xml-parser": "5.7.2",
"serialize-javascript": "7.0.5",
"mdast-util-gfm-autolink-literal": "2.0.0",
"remark-gfm": {
"mdast-util-gfm-autolink-literal": "2.0.0"
},
"mdast-util-gfm": {
"mdast-util-gfm-autolink-literal": "2.0.0"
},
"katex": "^0.16.21",
"rehype-katex": {
"katex": "^0.16.21"
},
"remark-math": {
"micromark-extension-math": {
"katex": "^0.16.21"
}
},
"eslint": {
"ajv": "6.14.0"
},
"underscore": "1.13.8",
"hono": "^4.12.25",
"@hono/node-server": "^2.0.11",
"monaco-editor": {
"dompurify": "3.4.12"
},
"test-exclude": "^8.0.0",
"js-yaml": "^4.2.0",
"ws": "^8.21.0",
"@opentelemetry/core": "^2.9.0",
"@opentelemetry/propagator-jaeger": "^2.10.0",
"protobufjs": "^7.6.5",
"gaxios": {
"uuid": "^11.1.1"
}
},
"nodemonConfig": {
"ignore": [
"api/data/",
"data/",
"client/",
"admin/",
"packages/"
]
}
}