📌 feat: pin agents and models in the sidebar #3636
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Detect Unused NPM Packages | |
| on: | |
| pull_request: | |
| paths: | |
| - 'package.json' | |
| - 'package-lock.json' | |
| - 'client/**' | |
| - 'api/**' | |
| - 'packages/client/**' | |
| - 'packages/api/**' | |
| jobs: | |
| detect-unused-packages: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Use Node.js 20.x | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: 'npm' | |
| - name: Install depcheck | |
| run: npm install -g depcheck | |
| - name: Validate JSON files | |
| run: | | |
| for FILE in package.json client/package.json api/package.json packages/client/package.json; do | |
| if [[ -f "$FILE" ]]; then | |
| jq empty "$FILE" || (echo "::error title=Invalid JSON::$FILE is invalid" && exit 1) | |
| fi | |
| done | |
| - name: Extract Dependencies Used in Scripts | |
| id: extract-used-scripts | |
| run: | | |
| extract_deps_from_scripts() { | |
| local package_file=$1 | |
| if [[ -f "$package_file" ]]; then | |
| jq -r '.scripts | to_entries[].value' "$package_file" | \ | |
| grep -oE '([a-zA-Z0-9_-]+)' | sort -u > used_scripts.txt | |
| else | |
| touch used_scripts.txt | |
| fi | |
| } | |
| extract_deps_from_scripts "package.json" | |
| mv used_scripts.txt root_used_deps.txt | |
| extract_deps_from_scripts "client/package.json" | |
| mv used_scripts.txt client_used_deps.txt | |
| extract_deps_from_scripts "api/package.json" | |
| mv used_scripts.txt api_used_deps.txt | |
| - name: Extract Dependencies Used in Source Code | |
| id: extract-used-code | |
| run: | | |
| extract_deps_from_code() { | |
| local folder=$1 | |
| local output_file=$2 | |
| # Initialize empty output file | |
| > "$output_file" | |
| if [[ -d "$folder" ]]; then | |
| # Extract require() statements (use explicit includes for portability) | |
| grep -rEho "require\\(['\"]([a-zA-Z0-9@/._-]+)['\"]\\)" "$folder" \ | |
| --include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \ | |
| sed -E "s/require\\(['\"]([a-zA-Z0-9@/._-]+)['\"]\\)/\1/" >> "$output_file" || true | |
| # Extract ES6 imports - import x from 'module' | |
| grep -rEho "import .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \ | |
| --include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \ | |
| sed -E "s/import .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true | |
| # import 'module' (side-effect imports) | |
| grep -rEho "import ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \ | |
| --include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \ | |
| sed -E "s/import ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true | |
| # export { x } from 'module' or export * from 'module' | |
| grep -rEho "export .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \ | |
| --include='*.js' --include='*.ts' --include='*.tsx' --include='*.jsx' --include='*.mjs' --include='*.cjs' 2>/dev/null | \ | |
| sed -E "s/export .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true | |
| # import type { x } from 'module' (TypeScript) | |
| grep -rEho "import type .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" \ | |
| --include='*.ts' --include='*.tsx' 2>/dev/null | \ | |
| sed -E "s/import type .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" || true | |
| # Remove subpath imports but keep the base package | |
| # For scoped packages: '@scope/pkg/subpath' -> '@scope/pkg' | |
| # For regular packages: 'pkg/subpath' -> 'pkg' | |
| # Scoped packages (must keep @scope/package, strip anything after) | |
| sed -i -E 's|^(@[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+)/.*|\1|' "$output_file" 2>/dev/null || true | |
| # Non-scoped packages (keep package name, strip subpath) | |
| sed -i -E 's|^([a-zA-Z0-9_-]+)/.*|\1|' "$output_file" 2>/dev/null || true | |
| sort -u "$output_file" -o "$output_file" | |
| fi | |
| } | |
| extract_deps_from_code "." root_used_code.txt | |
| extract_deps_from_code "client" client_used_code.txt | |
| extract_deps_from_code "api" api_used_code.txt | |
| # Extract dependencies used by workspace packages | |
| # These packages are used in the workspace but dependencies are provided by parent package.json | |
| extract_deps_from_code "packages/client" packages_client_used_code.txt | |
| extract_deps_from_code "packages/api" packages_api_used_code.txt | |
| - name: Get @librechat/client dependencies | |
| id: get-librechat-client-deps | |
| run: | | |
| if [[ -f "packages/client/package.json" ]]; then | |
| # Get all dependencies from @librechat/client (dependencies, devDependencies, and peerDependencies) | |
| DEPS=$(jq -r '.dependencies // {} | keys[]' packages/client/package.json 2>/dev/null || echo "") | |
| DEV_DEPS=$(jq -r '.devDependencies // {} | keys[]' packages/client/package.json 2>/dev/null || echo "") | |
| PEER_DEPS=$(jq -r '.peerDependencies // {} | keys[]' packages/client/package.json 2>/dev/null || echo "") | |
| # Combine all dependencies | |
| echo "$DEPS" > librechat_client_deps.txt | |
| echo "$DEV_DEPS" >> librechat_client_deps.txt | |
| echo "$PEER_DEPS" >> librechat_client_deps.txt | |
| # Also include dependencies that are imported in packages/client | |
| cat packages_client_used_code.txt >> librechat_client_deps.txt | |
| # Remove empty lines and sort | |
| grep -v '^$' librechat_client_deps.txt | sort -u > temp_deps.txt | |
| mv temp_deps.txt librechat_client_deps.txt | |
| else | |
| touch librechat_client_deps.txt | |
| fi | |
| - name: Get @librechat/api dependencies | |
| id: get-librechat-api-deps | |
| run: | | |
| if [[ -f "packages/api/package.json" ]]; then | |
| # Get all dependencies from @librechat/api (dependencies, devDependencies, and peerDependencies) | |
| DEPS=$(jq -r '.dependencies // {} | keys[]' packages/api/package.json 2>/dev/null || echo "") | |
| DEV_DEPS=$(jq -r '.devDependencies // {} | keys[]' packages/api/package.json 2>/dev/null || echo "") | |
| PEER_DEPS=$(jq -r '.peerDependencies // {} | keys[]' packages/api/package.json 2>/dev/null || echo "") | |
| # Combine all dependencies | |
| echo "$DEPS" > librechat_api_deps.txt | |
| echo "$DEV_DEPS" >> librechat_api_deps.txt | |
| echo "$PEER_DEPS" >> librechat_api_deps.txt | |
| # Also include dependencies that are imported in packages/api | |
| cat packages_api_used_code.txt >> librechat_api_deps.txt | |
| # Remove empty lines and sort | |
| grep -v '^$' librechat_api_deps.txt | sort -u > temp_deps.txt | |
| mv temp_deps.txt librechat_api_deps.txt | |
| else | |
| touch librechat_api_deps.txt | |
| fi | |
| - name: Extract Workspace Dependencies | |
| id: extract-workspace-deps | |
| run: | | |
| # Function to get dependencies from a workspace package that are used by another package | |
| get_workspace_package_deps() { | |
| local package_json=$1 | |
| local output_file=$2 | |
| # Get all workspace dependencies (starting with @librechat/) | |
| if [[ -f "$package_json" ]]; then | |
| local workspace_deps=$(jq -r '.dependencies // {} | to_entries[] | select(.key | startswith("@librechat/")) | .key' "$package_json" 2>/dev/null || echo "") | |
| # For each workspace dependency, get its dependencies | |
| for dep in $workspace_deps; do | |
| # Convert @librechat/api to packages/api | |
| local workspace_path=$(echo "$dep" | sed 's/@librechat\//packages\//') | |
| local workspace_package_json="${workspace_path}/package.json" | |
| if [[ -f "$workspace_package_json" ]]; then | |
| # Extract all dependencies from the workspace package | |
| jq -r '.dependencies // {} | keys[]' "$workspace_package_json" 2>/dev/null >> "$output_file" | |
| # Also extract peerDependencies | |
| jq -r '.peerDependencies // {} | keys[]' "$workspace_package_json" 2>/dev/null >> "$output_file" | |
| fi | |
| done | |
| fi | |
| if [[ -f "$output_file" ]]; then | |
| sort -u "$output_file" -o "$output_file" | |
| else | |
| touch "$output_file" | |
| fi | |
| } | |
| # Get workspace dependencies for each package | |
| get_workspace_package_deps "package.json" root_workspace_deps.txt | |
| get_workspace_package_deps "client/package.json" client_workspace_deps.txt | |
| get_workspace_package_deps "api/package.json" api_workspace_deps.txt | |
| - name: Run depcheck for root package.json | |
| id: check-root | |
| run: | | |
| if [[ -f "package.json" ]]; then | |
| UNUSED=$(depcheck --json | jq -r '.dependencies | join("\n")' || echo "") | |
| # Exclude dependencies used in scripts, code, and workspace packages | |
| UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat root_used_deps.txt root_used_code.txt root_workspace_deps.txt | sort) || echo "") | |
| echo "ROOT_UNUSED<<EOF" >> $GITHUB_ENV | |
| echo "$UNUSED" >> $GITHUB_ENV | |
| echo "EOF" >> $GITHUB_ENV | |
| fi | |
| - name: Run depcheck for client/package.json | |
| id: check-client | |
| run: | | |
| if [[ -f "client/package.json" ]]; then | |
| chmod -R 755 client | |
| cd client | |
| UNUSED=$(depcheck --json | jq -r '.dependencies | join("\n")' || echo "") | |
| # Exclude dependencies used in scripts, code, workspace packages, and @librechat/client imports | |
| UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat ../client_used_deps.txt ../client_used_code.txt ../client_workspace_deps.txt ../packages_client_used_code.txt ../librechat_client_deps.txt 2>/dev/null | sort -u) || echo "") | |
| # Filter out false positives | |
| UNUSED=$(echo "$UNUSED" | grep -v "^micromark-extension-llm-math$" || echo "") | |
| echo "CLIENT_UNUSED<<EOF" >> $GITHUB_ENV | |
| echo "$UNUSED" >> $GITHUB_ENV | |
| echo "EOF" >> $GITHUB_ENV | |
| cd .. | |
| fi | |
| - name: Run depcheck for api/package.json | |
| id: check-api | |
| run: | | |
| if [[ -f "api/package.json" ]]; then | |
| chmod -R 755 api | |
| cd api | |
| UNUSED=$(depcheck --json | jq -r '.dependencies | join("\n")' || echo "") | |
| # Exclude dependencies used in scripts, code, workspace packages, and @librechat/api imports | |
| UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat ../api_used_deps.txt ../api_used_code.txt ../api_workspace_deps.txt ../packages_api_used_code.txt ../librechat_api_deps.txt 2>/dev/null | sort -u) || echo "") | |
| echo "API_UNUSED<<EOF" >> $GITHUB_ENV | |
| echo "$UNUSED" >> $GITHUB_ENV | |
| echo "EOF" >> $GITHUB_ENV | |
| cd .. | |
| fi | |
| - name: Post comment on PR if unused dependencies are found | |
| if: env.ROOT_UNUSED != '' || env.CLIENT_UNUSED != '' || env.API_UNUSED != '' | |
| run: | | |
| PR_NUMBER=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") | |
| ROOT_LIST=$(echo "$ROOT_UNUSED" | awk '{print "- `" $0 "`"}') | |
| CLIENT_LIST=$(echo "$CLIENT_UNUSED" | awk '{print "- `" $0 "`"}') | |
| API_LIST=$(echo "$API_UNUSED" | awk '{print "- `" $0 "`"}') | |
| COMMENT_BODY=$(cat <<EOF | |
| ### 🚨 Unused NPM Packages Detected | |
| The following **unused dependencies** were found: | |
| $(if [[ ! -z "$ROOT_UNUSED" ]]; then echo "#### 📂 Root \`package.json\`"; echo ""; echo "$ROOT_LIST"; echo ""; fi) | |
| $(if [[ ! -z "$CLIENT_UNUSED" ]]; then echo "#### 📂 Client \`client/package.json\`"; echo ""; echo "$CLIENT_LIST"; echo ""; fi) | |
| $(if [[ ! -z "$API_UNUSED" ]]; then echo "#### 📂 API \`api/package.json\`"; echo ""; echo "$API_LIST"; echo ""; fi) | |
| ⚠️ **Please remove these unused dependencies to keep your project clean.** | |
| EOF | |
| ) | |
| gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \ | |
| -f body="$COMMENT_BODY" \ | |
| -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Fail workflow if unused dependencies found | |
| if: env.ROOT_UNUSED != '' || env.CLIENT_UNUSED != '' || env.API_UNUSED != '' | |
| run: exit 1 |