Files
klubhaus-doorbell/libraries/FastLED/ci/setup-js-linting-fast.py
2026-02-12 00:45:31 -08:00

285 lines
9.0 KiB
Python

#!/usr/bin/env -S uv run --script
"""
Fast JavaScript linting setup for FastLED using Node.js + ESLint
Downloads Node.js binary and sets up fast linting - much faster than Deno
Uses uv run --script for dynamic package management
"""
import io
import json
import os
import platform
import shutil
import subprocess
import sys
import tarfile
import zipfile
from pathlib import Path
# Import httpx for HTTP requests (dynamically managed by uv)
import httpx
# Force UTF-8 output for Windows consoles
if sys.platform.startswith("win"):
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
# Configuration
NODE_VERSION = "20.11.0" # LTS version
TOOLS_DIR = Path(".cache/js-tools")
NODE_DIR = TOOLS_DIR / "node"
def get_node_download_info():
"""Get Node.js download URL and filename based on platform"""
system = platform.system().lower()
machine = platform.machine().lower()
# Map architecture names
if machine in ["x86_64", "amd64"]:
arch = "x64"
elif machine in ["aarch64", "arm64"]:
arch = "arm64"
else:
raise ValueError(f"Unsupported architecture: {machine}")
if system == "windows":
filename = f"node-v{NODE_VERSION}-win-{arch}.zip"
is_zip = True
elif system == "darwin":
filename = f"node-v{NODE_VERSION}-darwin-{arch}.tar.gz"
is_zip = False
elif system == "linux":
filename = f"node-v{NODE_VERSION}-linux-{arch}.tar.xz"
is_zip = False
else:
raise ValueError(f"Unsupported platform: {system}")
url = f"https://nodejs.org/dist/v{NODE_VERSION}/{filename}"
return url, filename, is_zip
def download_and_extract_node():
"""Download and extract Node.js binary"""
url, filename, is_zip = get_node_download_info()
download_path = TOOLS_DIR / filename
# Get architecture for extraction path
machine = platform.machine().lower()
if machine in ["x86_64", "amd64"]:
arch = "x64"
elif machine in ["aarch64", "arm64"]:
arch = "arm64"
else:
raise ValueError(f"Unsupported architecture: {machine}")
print(f"Downloading Node.js v{NODE_VERSION}...")
TOOLS_DIR.mkdir(exist_ok=True)
NODE_DIR.mkdir(exist_ok=True)
if not download_path.exists():
print(f"Downloading from: {url}")
with httpx.stream("GET", url, follow_redirects=True) as response:
response.raise_for_status()
with open(download_path, "wb") as f:
for chunk in response.iter_bytes(chunk_size=8192):
f.write(chunk)
print(f"SUCCESS: Downloaded {filename}")
# Extract Node.js
if platform.system() == "Windows":
node_exe = NODE_DIR / "node.exe"
else:
node_exe = NODE_DIR / "bin" / "node"
if not node_exe.exists():
print("Extracting Node.js...")
if is_zip:
with zipfile.ZipFile(download_path, "r") as zip_ref:
zip_ref.extractall(NODE_DIR)
# Move contents from nested folder to NODE_DIR
nested_dir = NODE_DIR / f"node-v{NODE_VERSION}-win-{arch}"
if nested_dir.exists():
for item in nested_dir.iterdir():
if item.is_dir():
# For directories, move contents recursively
target_dir = NODE_DIR / item.name
if target_dir.exists():
shutil.rmtree(target_dir)
shutil.move(str(item), str(target_dir))
else:
# For files, move directly
target_file = NODE_DIR / item.name
if target_file.exists():
target_file.unlink()
item.rename(target_file)
nested_dir.rmdir()
else:
with tarfile.open(download_path, "r:*") as tar_ref:
tar_ref.extractall(NODE_DIR)
# Move contents from nested folder to NODE_DIR
nested_dir = (
NODE_DIR / f"node-v{NODE_VERSION}-{platform.system().lower()}-x64"
)
if nested_dir.exists():
for item in nested_dir.iterdir():
item.rename(NODE_DIR / item.name)
nested_dir.rmdir()
print("SUCCESS: Node.js extracted")
# Clean up download file
if download_path.exists():
download_path.unlink()
return node_exe
def setup_eslint():
"""Install ESLint and create configuration"""
if platform.system() == "Windows":
node_exe = NODE_DIR / "node.exe"
npm_exe = NODE_DIR / "npm.cmd"
else:
node_exe = NODE_DIR / "bin" / "node"
npm_exe = NODE_DIR / "bin" / "npm"
print("Installing ESLint...")
# Create package.json
package_json = {
"name": "fastled-js-linting",
"version": "1.0.0",
"private": True,
"dependencies": {"eslint": "^8.56.0"},
}
with open(TOOLS_DIR / "package.json", "w") as f:
json.dump(package_json, f, indent=2)
# Install ESLint
try:
# Use absolute path for npm executable
npm_exe_abs = (
(TOOLS_DIR / "node" / "npm.cmd").resolve()
if platform.system() == "Windows"
else (TOOLS_DIR / "node" / "bin" / "npm").resolve()
)
# On Windows, use shell=True to properly execute .cmd files
if platform.system() == "Windows":
result = subprocess.run(
[str(npm_exe_abs), "install"],
cwd=TOOLS_DIR,
check=True,
capture_output=True,
text=True,
shell=True,
)
else:
result = subprocess.run(
[str(npm_exe_abs), "install"],
cwd=TOOLS_DIR,
check=True,
capture_output=True,
text=True,
)
# npm install succeeded
except subprocess.CalledProcessError as e:
# Show stderr output for debugging
print(f"npm install stderr: {e.stderr}")
print(f"npm install stdout: {e.stdout}")
raise
# ESLint config is now in ci/.eslintrc.js (tracked in git)
# No need to create it here since it's already in version control
print("SUCCESS: ESLint configured")
def create_fast_lint_script():
"""Create fast linting script"""
if platform.system() == "Windows":
node_exe = NODE_DIR / "node.exe"
eslint_exe = TOOLS_DIR / "node_modules" / ".bin" / "eslint.cmd"
else:
node_exe = NODE_DIR / "bin" / "node"
eslint_exe = TOOLS_DIR / "node_modules" / ".bin" / "eslint"
script_content = f"""#!/bin/bash
# FastLED JavaScript Linting Script (Node.js + ESLint - FAST!)
# Colors for output
RED='\\033[0;31m'
GREEN='\\033[0;32m'
YELLOW='\\033[1;33m'
BLUE='\\033[0;34m'
NC='\\033[0m' # No Color
echo -e "${{BLUE}}FAST FastLED JavaScript Linting (Node.js + ESLint - FAST!)${{NC}}"
# Check if ESLint is installed
if [ ! -f ".cache/js-tools/node_modules/.bin/eslint{".cmd" if platform.system() == "Windows" else ""}" ]; then
echo -e "${{RED}}ERROR: ESLint not found. Run: uv run ci/setup-js-linting-fast.py${{NC}}"
exit 1
fi
# Find JavaScript files in WASM platform
JS_FILES=$(find src/platforms/wasm -name "*.js" -type f 2>/dev/null)
if [ -z "$JS_FILES" ]; then
echo -e "${{YELLOW}}WARNING: No JavaScript files found in src/platforms/wasm/${{NC}}"
exit 0
fi
echo -e "${{BLUE}}Found JavaScript files:${{NC}}"
echo "$JS_FILES" | sed 's/^/ /'
# Run ESLint
echo -e "${{BLUE}}Running ESLint...${{NC}}"
cd .cache/js-tools
if "./node_modules/.bin/eslint{".cmd" if platform.system() == "Windows" else ""}" --no-eslintrc --no-inline-config -c .eslintrc.js ../../src/platforms/wasm/compiler/*.js ../../src/platforms/wasm/compiler/modules/*.js; then
echo -e "${{GREEN}}SUCCESS: JavaScript linting completed successfully${{NC}}"
else
echo -e "${{RED}}ERROR: JavaScript linting failed${{NC}}"
exit 1
fi
"""
# Lint script is now in ci/lint-js-fast (tracked in git)
# No need to create it here since it's already in version control
script_path = Path("ci/lint-js-fast")
# Script already exists in ci/ and is tracked in git
# Just ensure it's executable
if platform.system() != "Windows" and script_path.exists():
os.chmod(script_path, 0o755)
print("SUCCESS: Fast lint script created")
def main():
"""Main setup function"""
print("Setting up fast JavaScript linting (Node.js + ESLint)...")
try:
node_exe = download_and_extract_node()
setup_eslint()
create_fast_lint_script()
print("\\nFast JavaScript linting setup complete!")
print("\\nUsage:")
print(" bash ci/lint-js-fast # Fast linting with ESLint")
print(" For more info: ci/js/README.md")
except Exception as e:
print(f"Setup failed: {e}")
sys.exit(1)
if __name__ == "__main__":
main()