| name: Detect Unused NPM Packages |
|
|
| on: |
| pull_request: |
| paths: |
| - 'package.json' |
| - 'package-lock.json' |
| - 'client/**' |
| - 'api/**' |
| - 'packages/client/**' |
|
|
| 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 |
| if [[ -d "$folder" ]]; then |
| # Extract require() statements |
| grep -rEho "require\\(['\"]([a-zA-Z0-9@/._-]+)['\"]\\)" "$folder" --include=\*.{js,ts,tsx,jsx,mjs,cjs} | \ |
| sed -E "s/require\\(['\"]([a-zA-Z0-9@/._-]+)['\"]\\)/\1/" > "$output_file" |
| |
| |
| |
| grep -rEho "import .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" --include=\*.{js,ts,tsx,jsx,mjs,cjs} | \ |
| sed -E "s/import .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" |
| |
| |
| grep -rEho "import ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" --include=\*.{js,ts,tsx,jsx,mjs,cjs} | \ |
| sed -E "s/import ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" |
| |
| |
| grep -rEho "export .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" --include=\*.{js,ts,tsx,jsx,mjs,cjs} | \ |
| sed -E "s/export .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" |
| |
| |
| grep -rEho "import type .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]" "$folder" --include=\*.{ts,tsx} | \ |
| sed -E "s/import type .* from ['\"]([a-zA-Z0-9@/._-]+)['\"]/\1/" >> "$output_file" |
| |
| |
| |
| sed -i -E 's|^(@?[a-zA-Z0-9-]+(/[a-zA-Z0-9-]+)?)/.*|\1|' "$output_file" |
| |
| sort -u "$output_file" -o "$output_file" |
| else |
| touch "$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_deps_from_code "packages/client" packages_client_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: 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_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, and workspace packages |
| UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat ../client_used_deps.txt ../client_used_code.txt ../client_workspace_deps.txt | sort) || 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, and workspace packages |
| UNUSED=$(comm -23 <(echo "$UNUSED" | sort) <(cat ../api_used_deps.txt ../api_used_code.txt ../api_workspace_deps.txt | sort) || 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 |
| |
|
|
| 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 |