platform-tooling/scripts/node-modules-lock.sh

144 lines
4 KiB
Bash
Executable file

#!/bin/bash
# node-modules-lock.sh - Manage node_modules write protection
#
# PURPOSE: Enforce that node_modules is read-only to prevent accidental edits.
# Packages should be modified at ~/Code/@packages/, not in node_modules.
#
# USAGE:
# ./tooling/scripts/node-modules-lock.sh lock [dir] # Make read-only (postinstall)
# ./tooling/scripts/node-modules-lock.sh unlock [dir] # Make writable (preinstall)
# ./tooling/scripts/node-modules-lock.sh status [dir] # Show current state
# ./tooling/scripts/node-modules-lock.sh lock-all # Lock all node_modules in monorepo
#
# Called automatically by package.json preinstall/postinstall hooks.
# [dir] defaults to current working directory if not specified.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Target directory: use argument if provided, else current working directory
TARGET_DIR="${2:-$(pwd)}"
NODE_MODULES="$TARGET_DIR/node_modules"
usage() {
echo "Usage: $0 {lock|unlock|status|lock-all|unlock-all|status-all} [dir]"
echo ""
echo "Commands:"
echo " lock [dir] - Make node_modules read-only (run after pnpm install)"
echo " unlock [dir] - Make node_modules writable (run before pnpm install)"
echo " status [dir] - Show current protection status"
echo " lock-all - Lock ALL node_modules in monorepo"
echo " unlock-all - Unlock ALL node_modules in monorepo"
echo " status-all - Show status of ALL node_modules in monorepo"
echo ""
echo "[dir] defaults to current working directory"
exit 1
}
status() {
if [ ! -d "$NODE_MODULES" ]; then
echo "node_modules: does not exist"
return 0
fi
# Check if .bin directory is writable (indicator of lock state)
if [ -w "$NODE_MODULES/.bin" ]; then
echo "node_modules: UNLOCKED (writable)"
else
echo "node_modules: LOCKED (read-only)"
fi
}
lock() {
if [ ! -d "$NODE_MODULES" ]; then
echo "node_modules does not exist, skipping lock"
return 0
fi
# Lock all top-level entries EXCEPT:
# - .pnpm* (pnpm's internal state files - pnpm needs write access)
# - .vite* (Vite cache directories - Vite 6+ needs write access)
# Use 2>/dev/null to ignore already-locked files
find "$NODE_MODULES" -mindepth 1 -maxdepth 1 ! -name '.pnpm*' ! -name '.vite*' -exec chmod -R a-w {} + 2>/dev/null || true
echo "node_modules: LOCKED"
}
unlock() {
if [ ! -d "$NODE_MODULES" ]; then
echo "node_modules does not exist, skipping unlock"
return 0
fi
chmod -R u+w "$NODE_MODULES"
echo "node_modules: UNLOCKED"
}
# Find all node_modules directories in the monorepo
find_all_node_modules() {
find "$REPO_ROOT" -type d -name "node_modules" -not -path "*/node_modules/*" 2>/dev/null
}
lock_all() {
echo "Locking all node_modules in monorepo..."
local count=0
while IFS= read -r nm_dir; do
NODE_MODULES="$nm_dir"
lock
count=$((count + 1))
done < <(find_all_node_modules)
echo "Locked $count node_modules directories"
}
unlock_all() {
echo "Unlocking all node_modules in monorepo..."
local count=0
while IFS= read -r nm_dir; do
NODE_MODULES="$nm_dir"
unlock
count=$((count + 1))
done < <(find_all_node_modules)
echo "Unlocked $count node_modules directories"
}
status_all() {
echo "Status of all node_modules in monorepo:"
echo ""
while IFS= read -r nm_dir; do
local rel_path="${nm_dir#$REPO_ROOT/}"
NODE_MODULES="$nm_dir"
printf " %-60s " "$rel_path"
if [ -w "$nm_dir/.bin" ] 2>/dev/null; then
echo "UNLOCKED"
else
echo "LOCKED"
fi
done < <(find_all_node_modules)
}
case "${1:-}" in
lock)
lock
;;
unlock)
unlock
;;
status)
status
;;
lock-all)
lock_all
;;
unlock-all)
unlock_all
;;
status-all)
status_all
;;
*)
usage
;;
esac