remove <link href='#' /> #58
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/**' | |
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" | |
# Extract ES6 imports - various patterns | |
# import x from 'module' | |
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" | |
# import 'module' (side-effect imports) | |
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" | |
# export { x } from 'module' or export * from 'module' | |
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" | |
# import type { x } from 'module' (TypeScript) | |
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" | |
# Remove subpath imports but keep the base package | |
# e.g., '@tanstack/react-query/devtools' becomes '@tanstack/react-query' | |
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 dependencies used by @librechat/client package | |
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 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, 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 | |
### 🚨 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 |