Fix bin/publish: copy docs.dist from project root

Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-10-21 02:08:33 +00:00
commit f6fac6c4bc
79758 changed files with 10547827 additions and 0 deletions

62
bin/README.md Executable file
View File

@@ -0,0 +1,62 @@
# RSX Binary Tools
This directory contains standalone command-line tools for the RSX framework.
## rsx-format
A standalone PHP formatter for RSX files that is completely independent of Laravel.
### Features
- **Laravel-independent**: Runs without requiring Laravel bootstrap
- **Namespace management**: Automatically updates namespaces based on file location
- **Use statement generation**: Detects and adds required use statements
- **PHP 8 Attribute support**: Properly detects and resolves attribute classes
- **Pint integration**: Applies Laravel Pint formatting after fixing structure
- **Smart detection**: Only formats files that need it
### Usage
```bash
# Format a single file
php bin/rsx-format path/to/file.php
# Force formatting even if file appears formatted
php bin/rsx-format path/to/file.php --force
```
### How It Works
1. **Detects file type**: RSX, app, or other directories
2. **For RSX class files**:
- Updates namespace to match directory structure
- Extracts all referenced classes (including PHP 8 attributes)
- Generates and updates use statements
- Adds LLM directive markers
- Normalizes spacing
- Applies Pint formatting
3. **For non-class files**: Just applies Pint formatting
### VS Code Integration
The formatter is automatically run on save for all PHP files via the VS Code settings:
```json
"emeraldwalk.runonsave": {
"commands": [{
"match": "\\.php$",
"cmd": "php ${workspaceFolder}/bin/rsx-format ${file}"
}]
}
```
### Why Standalone?
The formatter needs to be able to process PHP files that may not be valid PHP yet (missing use statements). If it depended on Laravel's autoloader, it would fail when trying to load classes with syntax errors, creating a chicken-and-egg problem.
By being standalone, it can:
- Parse files using simple regex patterns
- Add missing use statements to make files valid
- Then apply more sophisticated formatting
This ensures files can always be formatted, even when they're in an invalid state.

141
bin/formatters/json-formatter Executable file
View File

@@ -0,0 +1,141 @@
#!/usr/bin/env php
<?php
/**
* JSON Code Formatter
*
* Formats JSON files with proper indentation and validation
*/
class JsonFormatter
{
private $verbose = false;
/**
* Main entry point
*/
public function run($argv)
{
// Parse command line arguments
$files = [];
for ($i = 1; $i < count($argv); $i++) {
$arg = $argv[$i];
if ($arg === '--verbose' || $arg === '-v') {
$this->verbose = true;
} elseif ($arg === '--help' || $arg === '-h') {
$this->show_help();
exit(0);
} elseif (substr($arg, 0, 1) !== '-') {
$files[] = $arg;
}
}
// If no files specified, show help
if (empty($files)) {
$this->show_help();
exit(1);
}
$this->format_files($files);
}
/**
* Format specified files
*/
private function format_files($paths)
{
foreach ($paths as $path) {
// Make path absolute if relative
if (substr($path, 0, 1) !== '/') {
$path = getcwd() . '/' . $path;
}
if (is_file($path)) {
$this->format_file($path);
} else {
$this->warning("Path not found: $path");
}
}
}
/**
* Format a single JSON file
*/
private function format_file($file_path)
{
// Skip non-JSON files
if (substr($file_path, -5) !== '.json') {
return;
}
$content = file_get_contents($file_path);
$decoded = json_decode($content);
if (json_last_error() !== JSON_ERROR_NONE) {
$this->error("Invalid JSON in $file_path: " . json_last_error_msg());
return;
}
$formatted = json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n";
if ($content !== $formatted) {
file_put_contents($file_path, $formatted);
$this->success("✓ Formatted: $file_path");
} else {
if ($this->verbose) {
$this->info("Already formatted: $file_path");
}
}
}
/**
* Show help message
*/
private function show_help()
{
$this->output("JSON Formatter");
$this->output("");
$this->output("Usage: json-formatter [options] files...");
$this->output("");
$this->output("Options:");
$this->output(" -v, --verbose Show detailed output");
$this->output(" -h, --help Show this help message");
$this->output("");
$this->output("Examples:");
$this->output(" json-formatter package.json");
$this->output(" json-formatter composer.json tsconfig.json");
}
/**
* Output helpers
*/
private function output($message)
{
echo $message . PHP_EOL;
}
private function success($message)
{
echo "\033[32m" . $message . "\033[0m" . PHP_EOL;
}
private function info($message)
{
echo "\033[36m" . $message . "\033[0m" . PHP_EOL;
}
private function warning($message)
{
echo "\033[33m" . $message . "\033[0m" . PHP_EOL;
}
private function error($message)
{
echo "\033[31m" . $message . "\033[0m" . PHP_EOL;
}
}
// Run the formatter
$formatter = new JsonFormatter();
$formatter->run($argv);

1029
bin/formatters/php-formatter Executable file

File diff suppressed because it is too large Load Diff

383
bin/framework-pull-upstream.sh Executable file
View File

@@ -0,0 +1,383 @@
#!/bin/bash
# RSpade Framework Update Script
# Pulls updates from rspade_upstream remote while preserving ./rsx in project mode
# This script runs BEFORE Laravel loads to update framework code independently
set -e
# Parse arguments
NO_REBUILD=false
for arg in "$@"; do
if [ "$arg" = "--no-rebuild" ]; then
NO_REBUILD=true
fi
done
# Determine project mode: Check for .rspade-dev-environment or rsx/.git
IS_PROJECT_MODE=false
if [ -f ".rspade-dev-environment" ] || [ -d "rsx/.git" ]; then
IS_PROJECT_MODE=true
fi
# In project mode, rsx/.git MUST exist
if [ "$IS_PROJECT_MODE" = true ] && [ ! -d "rsx/.git" ]; then
echo "ERROR: ./rsx is not a git repository."
echo ""
echo "The development environment appears corrupted."
echo "Expected ./rsx to be an independent git repository."
exit 1
fi
# Check if framework is forked
if [ -f ".rspade-forked-framework" ]; then
echo "ERROR: Framework is in forked mode."
echo ""
echo "You have taken full ownership of the RSpade framework codebase."
echo "Automatic updates are disabled to prevent overwriting your changes."
echo ""
echo "To manually update from upstream:"
echo " 1. git fetch rspade_upstream"
echo " 2. git diff rspade_upstream/rspade_selfupdate"
echo " 3. Manually merge desired changes"
echo " 4. Test thoroughly"
echo ""
echo "For detailed procedures: php artisan rsx:man framework_fork"
exit 1
fi
# Check .env for IS_FRAMEWORK_DEVELOPER=true
if [ -f ".env" ]; then
if grep -q "^IS_FRAMEWORK_DEVELOPER=true" .env; then
echo "ERROR: This command is disabled for framework developers."
echo ""
echo "This command is for application developers to pull framework updates."
echo "Framework developers should use standard git commands to manage the monorepo."
echo ""
echo "To use this command, set IS_FRAMEWORK_DEVELOPER=false in .env"
exit 1
fi
fi
# Check APP_ENV for production
if [ -f ".env" ]; then
if grep -q "^APP_ENV=production" .env; then
echo "ERROR: Framework updates are disabled in production mode."
echo ""
echo "Framework updates should be incorporated into and tested in a"
echo "development environment before deploying to production."
echo ""
echo "Update in development, test thoroughly, then deploy to production."
exit 1
fi
fi
# Verify rspade_upstream remote exists
if ! git remote get-url rspade_upstream >/dev/null 2>&1; then
echo "ERROR: Remote \"rspade_upstream\" not configured."
echo ""
echo "Configure the upstream remote:"
echo " git remote add rspade_upstream <upstream-url>"
exit 1
fi
echo ""
echo "=== Pull RSpade Framework Updates ==="
echo ""
# Detect current branch and set upstream branch
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>&1)
UPSTREAM_BRANCH="rspade_upstream/${CURRENT_BRANCH}"
# Verify upstream branch exists
if ! git rev-parse "$UPSTREAM_BRANCH" >/dev/null 2>&1; then
echo "ERROR: Upstream branch $UPSTREAM_BRANCH not found"
echo ""
echo "Current branch: $CURRENT_BRANCH"
echo "Expected upstream: $UPSTREAM_BRANCH"
echo ""
echo "Available upstream branches:"
git branch -r | grep rspade_upstream/ | sed 's/^/ /'
exit 1
fi
if [ "$IS_PROJECT_MODE" = true ]; then
echo "Mode: Project (./rsx will be preserved)"
else
echo "Mode: Framework development (./rsx will be updated)"
fi
echo "Branch: $CURRENT_BRANCH$UPSTREAM_BRANCH"
echo ""
# Fetch updates
echo "Fetching updates from rspade_upstream..."
if ! git fetch rspade_upstream 2>&1; then
echo "ERROR: Failed to fetch updates"
exit 1
fi
echo " ✓ Updates fetched"
# Check if already up-to-date
CURRENT_HEAD=$(git rev-parse HEAD 2>&1)
UPSTREAM_HEAD=$(git rev-parse "$UPSTREAM_BRANCH" 2>&1)
if [ "$CURRENT_HEAD" = "$UPSTREAM_HEAD" ]; then
echo ""
echo "✓ Already up to date"
echo ""
exit 0
fi
# In project mode, check for uncommitted changes outside ./rsx
if [ "$IS_PROJECT_MODE" = true ]; then
# Get uncommitted changes (modified, staged, deleted) excluding ./rsx
UNCOMMITTED_CHANGES=$(git diff --name-only -- . ":(exclude)rsx" 2>&1)
STAGED_CHANGES=$(git diff --cached --name-only -- . ":(exclude)rsx" 2>&1)
# Combine and deduplicate (|| true prevents grep exit code 1 from killing script)
ALL_CHANGES=$(echo -e "${UNCOMMITTED_CHANGES}\n${STAGED_CHANGES}" | sort -u | grep -v "^$" || true)
if [ -n "$ALL_CHANGES" ]; then
if [ "$STASH_CHANGES" = false ]; then
echo ""
echo "════════════════════════════════════════════════════════════════════════════════"
echo "❌ ERROR: Uncommitted framework changes detected"
echo "════════════════════════════════════════════════════════════════════════════════"
echo ""
echo "You are in PROJECT MODE where ./rsx is your application repository."
echo "Framework files outside ./rsx should NEVER be modified in project mode."
echo ""
echo "The following framework files have uncommitted changes:"
echo ""
echo "$ALL_CHANGES" | sed 's/^/ /'
echo ""
echo "These changes must be resolved before updating the framework."
echo ""
echo "OPTIONS:"
echo ""
echo " 1⃣ DISCARD FRAMEWORK CHANGES (recommended)"
echo " If these were accidental edits, reset them:"
echo ""
echo " git checkout HEAD -- [file paths]"
echo ""
echo " Or reset all framework changes:"
echo " git checkout HEAD -- . ':!rsx'"
echo ""
echo " 2⃣ STASH CHANGES AND UPDATE"
echo " Your changes will be stashed automatically, then the framework"
echo " will be updated. You can review or restore the stashed changes after:"
echo ""
echo " php artisan rsx:framework:pull --stash-changes"
echo ""
echo " After update, review your stashed changes:"
echo " git stash list"
echo " git stash show"
echo " git stash pop (to restore)"
echo ""
echo " 3⃣ COMMIT THEN UPDATE (framework developers only)"
echo " If you're intentionally modifying the framework:"
echo ""
echo " git add -A"
echo " git commit -m \"Framework modifications\""
echo " php artisan rsx:framework:pull"
echo ""
echo " 4⃣ FORK THE FRAMEWORK"
echo " If you need to preserve framework modifications permanently:"
echo ""
echo " php artisan rsx:man framework_fork"
echo ""
echo " This documents taking full control of the framework codebase."
echo ""
echo "════════════════════════════════════════════════════════════════════════════════"
echo "Note to LLM agents:"
echo "════════════════════════════════════════════════════════════════════════════════"
echo ""
echo "You have attempted to update the framework with uncommitted changes present."
echo ""
echo "REQUIRED ACTIONS:"
echo ""
echo " 1. Show the developer the full list of uncommitted files above"
echo " 2. Run 'git diff' to show what changed in those files"
echo " 3. Ask the developer explicitly: 'These framework files were modified."
echo " Do you want me to discard these changes and update the framework,"
echo " or stash them and proceed with the update?'"
echo ""
echo "You MUST NOT proceed until the developer has made a decision."
echo "You MUST NOT automatically use --stash-changes without explicit permission."
echo ""
echo "════════════════════════════════════════════════════════════════════════════════"
exit 1
else
# Stash changes with descriptive message
echo ""
echo "⚠️ Stashing uncommitted framework changes..."
echo ""
STASH_MESSAGE="Auto-stashed by framework update with --stash-changes on $(date '+%Y-%m-%d %H:%M:%S')"
if ! git stash push -u -m "$STASH_MESSAGE" -- . ":(exclude)rsx" 2>&1; then
echo "ERROR: Failed to stash changes"
exit 1
fi
echo " ✓ Changes stashed"
echo ""
echo "Your changes have been saved. After update completes, you can review them:"
echo " git stash list"
echo " git stash show"
echo " git stash pop (to restore changes)"
echo ""
fi
fi
fi
# Extract and display changelog
echo ""
echo "⚠ RSpade framework has upstream changes"
echo ""
# Get commit subjects, filter out Claude Code attribution and blank lines
CHANGELOG=$(git log --pretty=format:"%s" HEAD.."$UPSTREAM_BRANCH" 2>&1 | \
grep -v "Claude Code" | \
grep -v "Co-Authored-By:" | \
grep -v "^$" | \
sed 's/^/ • /')
if [ -n "$CHANGELOG" ]; then
echo "Changelog:"
echo ""
echo "$CHANGELOG"
echo ""
fi
# Show file diff stats (exclude ./rsx if in project mode)
if [ "$IS_PROJECT_MODE" = true ]; then
DIFF_STATS=$(git diff --stat HEAD.."$UPSTREAM_BRANCH" -- . ":(exclude)rsx" 2>&1)
else
DIFF_STATS=$(git diff --stat HEAD.."$UPSTREAM_BRANCH" 2>&1)
fi
if [ -n "$DIFF_STATS" ]; then
echo "Files that will be updated:"
echo ""
echo "$DIFF_STATS" | sed 's/^/ /'
echo ""
fi
# Apply updates based on mode
echo "Applying framework updates..."
if [ "$IS_PROJECT_MODE" = true ]; then
# Project mode: merge with --no-ff (preserves ./rsx via .gitattributes)
if ! git merge --no-ff "$UPSTREAM_BRANCH" 2>&1; then
echo "ERROR: Framework update failed."
echo ""
# Show current git state
GIT_STATUS=$(git status --short 2>&1)
if [ -n "$GIT_STATUS" ]; then
echo "Current git state:"
echo ""
echo "$GIT_STATUS" | sed 's/^/ /'
echo ""
fi
echo "Check status: php artisan rsx:framework:status"
exit 1
fi
echo " ✓ Framework updated (./rsx preserved by .gitattributes)"
else
# Framework mode: fast-forward only (updates everything including ./rsx)
MERGE_OUTPUT=$(git merge --ff-only "$UPSTREAM_BRANCH" 2>&1) || {
if echo "$MERGE_OUTPUT" | grep -q "up to date\|up-to-date"; then
echo " ✓ Already up to date"
else
echo "ERROR: Framework update failed."
echo ""
echo "ERROR: Cannot fast-forward: you have local modifications"
echo ""
# Show modified files
GIT_STATUS=$(git status --short 2>&1)
if [ -n "$GIT_STATUS" ]; then
echo "Modified files blocking update:"
echo ""
echo "$GIT_STATUS" | sed 's/^/ /'
echo ""
fi
echo "To resolve, either:"
echo " 1. Commit your changes and try again"
echo " 2. Discard modifications: git reset --hard $UPSTREAM_BRANCH"
echo " 3. Check status: php artisan rsx:framework:status"
exit 1
fi
}
echo " ✓ Framework updated"
fi
echo ""
echo "✓ Framework updated successfully"
echo ""
if [ "$IS_PROJECT_MODE" = true ]; then
echo "Your application code in ./rsx was preserved."
echo ""
fi
# Rebuild caches after framework update
if [ "$NO_REBUILD" = true ]; then
echo "⚠️ Cleaning caches only (--no-rebuild specified)"
echo ""
if ! php artisan rsx:clean; then
echo "ERROR: Cache clean failed"
exit 1
fi
echo " ✓ Caches cleaned"
echo ""
echo "⚠ Manifest and bundles NOT rebuilt - you must run manually:"
echo " php artisan rsx:manifest:build"
echo " php artisan rsx:bundle:compile"
echo ""
else
echo "⚠️ Regenerating framework caches"
echo ""
echo " This rebuild is only required because the framework itself was updated."
echo " Never run these commands manually unless there are catastrophic errors."
echo ""
# Step 1: Clean caches
echo "Step 1/3: Cleaning caches..."
if ! php artisan rsx:clean; then
echo "ERROR: Cache clean failed"
exit 1
fi
echo " ✓ Caches cleaned"
echo ""
# Step 2: Rebuild manifest
echo "Step 2/3: Rebuilding manifest..."
if ! php artisan rsx:manifest:build; then
echo "ERROR: Manifest rebuild failed"
echo " Run manually: php artisan rsx:manifest:build"
exit 1
fi
echo " ✓ Manifest rebuilt"
echo ""
# Step 3: Compile bundles
echo "Step 3/3: Compiling all bundles..."
if ! php artisan rsx:bundle:compile; then
echo "ERROR: Bundle compilation failed"
echo " Check errors above and fix bundle issues"
exit 1
fi
echo " ✓ All bundles compiled"
echo ""
fi
echo "✅ Framework update complete"
echo ""
exit 0

51
bin/git-hooks/pre-commit Executable file
View File

@@ -0,0 +1,51 @@
#!/bin/bash
#
# Pre-commit hook for RSpade Framework
# Runs code quality checks before allowing commits
#
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Run the code quality checker with --silent flag
php artisan rsx:check --silent --pre-commit-tests
# Capture the exit code
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo ""
echo -e "${RED}❌ Commit blocked: Code quality violations found${NC}"
echo ""
echo "Please fix the violations above before committing."
echo "You can run 'php artisan rsx:check' to see the issues again."
echo ""
echo "To bypass this check (not recommended), use:"
echo " git commit --no-verify"
echo ""
echo -e "${RED}════════════════════════════════════════════════════════════════════════════════${NC}"
echo -e "${RED}Note to LLM agents:${NC} The code quality check has failed, and you are NOT allowed"
echo "to commit code, even through git override, until this issue has been resolved."
echo "This is non optional under any circumstances even if the user instructed you to"
echo "do a commit, the issues must be remedied before any commit is completed."
echo ""
echo "Violations of this directive will result in disciplinary action."
echo ""
echo "Please investigate each of these issues RIGHT NOW, and inform the user of the"
echo "discovered issue, your analysis of the situation, your recommended course of"
echo "action for remediation, and ask for direction on how to proceed with remediation,"
echo "for each issue listed, RIGHT NOW."
echo ""
echo "Do not attempt to commit again until this situation has been remedied."
echo -e "${RED}════════════════════════════════════════════════════════════════════════════════${NC}"
exit 1
fi
echo ""
echo -e "${GREEN}✅ Code quality checks passed${NC}"
echo ""
exit 0

99
bin/install-git-hooks.sh Executable file
View File

@@ -0,0 +1,99 @@
#!/bin/bash
#
# Git Hooks Installation Script for RSpade Framework
# This script installs version-controlled git hooks into the local repository
#
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Get the repository root (where .git directory is)
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
if [ -z "$REPO_ROOT" ]; then
echo -e "${RED}Error: Not in a git repository${NC}"
exit 1
fi
HOOKS_SOURCE_DIR="$REPO_ROOT/bin/git-hooks"
GIT_HOOKS_DIR="$REPO_ROOT/.git/hooks"
echo -e "${CYAN}RSpade Git Hooks Installer${NC}"
echo "============================"
echo ""
# Check if source hooks directory exists
if [ ! -d "$HOOKS_SOURCE_DIR" ]; then
echo -e "${RED}Error: Git hooks source directory not found at $HOOKS_SOURCE_DIR${NC}"
exit 1
fi
# Create .git/hooks directory if it doesn't exist
if [ ! -d "$GIT_HOOKS_DIR" ]; then
mkdir -p "$GIT_HOOKS_DIR"
echo -e "${GREEN}Created .git/hooks directory${NC}"
fi
# Function to install a hook
install_hook() {
local hook_name=$1
local source_file="$HOOKS_SOURCE_DIR/$hook_name"
local target_file="$GIT_HOOKS_DIR/$hook_name"
if [ ! -f "$source_file" ]; then
echo -e "${YELLOW}Skipping $hook_name (not found in source)${NC}"
return
fi
# Check if hook already exists
if [ -e "$target_file" ]; then
if [ -L "$target_file" ]; then
# It's a symlink, check if it points to our hook
current_target=$(readlink "$target_file")
if [ "$current_target" = "$source_file" ]; then
echo -e "${GREEN}${NC} $hook_name already installed"
return
else
echo -e "${YELLOW}Updating $hook_name symlink${NC}"
rm "$target_file"
fi
else
# It's a regular file, back it up
backup_file="${target_file}.backup.$(date +%Y%m%d_%H%M%S)"
echo -e "${YELLOW}Backing up existing $hook_name to $(basename $backup_file)${NC}"
mv "$target_file" "$backup_file"
fi
fi
# Create symlink
ln -s "$source_file" "$target_file"
echo -e "${GREEN}✓ Installed $hook_name${NC}"
}
# Install available hooks
echo "Installing git hooks..."
echo ""
install_hook "pre-commit"
# Future hooks can be added here:
# install_hook "pre-push"
# install_hook "commit-msg"
# install_hook "prepare-commit-msg"
echo ""
echo -e "${GREEN}Git hooks installation complete!${NC}"
echo ""
echo "Installed hooks will:"
echo " • Run code quality checks before commits"
echo " • Prevent commits with code standard violations"
echo ""
echo "To bypass hooks temporarily (not recommended):"
echo " git commit --no-verify"
echo ""
echo "To uninstall hooks:"
echo " rm .git/hooks/pre-commit"

50
bin/js-linter.js Executable file
View File

@@ -0,0 +1,50 @@
#!/usr/bin/env node
/**
* JavaScript Syntax Linter for Code Quality Checks
*
* This script checks JavaScript files for syntax errors using acorn parser.
*
* Usage: node js-linter.js <input-file>
* Exit codes:
* 0 - No syntax errors
* 1 - Syntax error found
*/
const fs = require('fs');
const acorn = require('acorn');
// Get input file from command line arguments
const inputFile = process.argv[2];
if (!inputFile) {
console.error('Usage: node js-linter.js <input-file>');
process.exit(1);
}
try {
const code = fs.readFileSync(inputFile, 'utf8');
// Parse the code to check for syntax errors
acorn.parse(code, {
ecmaVersion: 'latest',
sourceType: 'module',
allowReturnOutsideFunction: true,
allowImportExportEverywhere: true,
allowAwaitOutsideFunction: true,
allowSuperOutsideMethod: true,
allowHashBang: true
});
// Silent success - no output when no errors
process.exit(0);
} catch (error) {
// Output error information
console.error('Syntax error: ' + error.message);
if (error.loc) {
console.error('Line: ' + error.loc.line);
console.error('Column: ' + error.loc.column);
}
process.exit(1);
}

136
bin/js-sanitizer.js Executable file
View File

@@ -0,0 +1,136 @@
#!/usr/bin/env node
/**
* JavaScript Sanitizer for Code Quality Checks
*
* This script removes comments and replaces string contents with spaces
* while preserving line numbers and column positions for accurate violation reporting.
*
* Usage: node js-sanitizer.js <input-file>
* Output: Sanitized JavaScript to stdout
*/
const fs = require('fs');
const decomment = require('decomment');
const acorn = require('acorn');
// Get input file from command line arguments
const inputFile = process.argv[2];
if (!inputFile) {
console.error('Usage: node js-sanitizer.js <input-file>');
process.exit(1);
}
// Read the input file
let code;
try {
code = fs.readFileSync(inputFile, 'utf8');
} catch (error) {
console.error(`Error reading file: ${error.message}`);
process.exit(1);
}
// Step 1: Remove comments while preserving spaces
let sanitized;
try {
sanitized = decomment(code, {
space: true, // Replace comments with spaces to preserve line numbers
tolerant: true // Handle potential syntax issues
});
} catch (error) {
// If decomment fails, continue with original code
sanitized = code;
}
// Step 2: Parse the code to find string literals and replace their contents
try {
// Parse with location tracking
const ast = acorn.parse(sanitized, {
ecmaVersion: 'latest',
sourceType: 'module',
locations: true,
ranges: true,
allowReturnOutsideFunction: true,
allowImportExportEverywhere: true,
allowAwaitOutsideFunction: true,
allowSuperOutsideMethod: true,
allowHashBang: true,
onComment: () => {} // Ignore comments (already removed)
});
// Convert code to array of characters for manipulation
const chars = sanitized.split('');
// Function to replace string content with spaces
function replaceStringContent(node) {
if (node.type === 'Literal' && typeof node.value === 'string') {
// For string literals, replace the content (but keep the quotes)
const start = node.range[0];
const end = node.range[1];
// Find the quote character (first non-whitespace character)
let quoteChar = chars[start];
let quoteEnd = chars[end - 1];
// Replace everything between quotes with spaces
for (let i = start + 1; i < end - 1; i++) {
if (chars[i] !== '\n') { // Preserve newlines for line counting
chars[i] = ' ';
}
}
} else if (node.type === 'TemplateLiteral') {
// For template literals, replace the raw text parts
for (let quasi of node.quasis) {
const start = quasi.range[0];
const end = quasi.range[1];
// Replace content between backticks or ${...}
for (let i = start + 1; i < end - 1; i++) {
if (chars[i] !== '\n' && chars[i] !== '$' && chars[i] !== '{' && chars[i] !== '}') {
chars[i] = ' ';
}
}
}
}
}
// Walk the AST to find all string literals
function walk(node) {
if (!node) return;
// Process current node
replaceStringContent(node);
// Recursively process all child nodes
for (let key in node) {
if (key === 'range' || key === 'loc' || key === 'start' || key === 'end') {
continue; // Skip location properties
}
const value = node[key];
if (Array.isArray(value)) {
for (let item of value) {
if (typeof item === 'object' && item !== null) {
walk(item);
}
}
} else if (typeof value === 'object' && value !== null) {
walk(value);
}
}
}
// Walk the AST to process all strings
walk(ast);
// Convert back to string
sanitized = chars.join('');
} catch (error) {
// If parsing fails (e.g., syntax error), we still have comments removed
// Just continue with the decommented version
}
// Output the sanitized code
process.stdout.write(sanitized);

1086
bin/route-debug.js Executable file

File diff suppressed because it is too large Load Diff

536
bin/rsx-format Executable file
View File

@@ -0,0 +1,536 @@
#!/usr/bin/env php
<?php
/**
* RSX Code Formatter Router
*
* Routes formatting requests to the appropriate formatter based on file type
*
* Hidden feature: --auto-reformat-periodic
* Performs intelligent reformatting of ./rsx directory only:
* - First run: Builds cache without formatting
* - Subsequent runs: Only formats changed/new files
* - Automatically excludes vendor/node_modules/public
* - Updates cache with file state after formatting
* Note: Intentionally not shown in --help output
*/
// Add error reporting for debugging
error_reporting(E_ALL);
ini_set('display_errors', 1);
class FormatterRouter
{
private $formatters_dir;
private $verbose = false;
private $cache_file;
private $cache_data = [];
private $cache_dirty = false;
private $auto_reformat_mode = false;
private $project_root;
/**
* Directories to exclude from formatting
*/
private $excluded_dirs = ['vendor', 'node_modules', 'public'];
/**
* Map of file extensions to formatter scripts
*/
private $formatters = [
'php' => 'php-formatter',
'json' => 'json-formatter',
// Future formatters can be added here
// 'js' => 'js-formatter',
// 'scss' => 'style-formatter',
// 'css' => 'style-formatter',
];
public function __construct()
{
$this->formatters_dir = __DIR__ . DIRECTORY_SEPARATOR . 'formatters';
$this->project_root = dirname(__DIR__);
$this->cache_file = $this->project_root . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . '.rsx-formatter-cache.json';
// Ensure cache directory exists
$cache_dir = dirname($this->cache_file);
if (!is_dir($cache_dir)) {
mkdir($cache_dir, 0777, true);
}
}
/**
* Main entry point
*/
public function run($argv)
{
// Check for auto-reformat-periodic mode first
if (in_array('--auto-reformat-periodic', $argv)) {
$this->auto_reformat_mode = true;
$this->handle_auto_reformat_periodic();
return;
}
// Parse command line arguments
$files = [];
$formatter_args = [];
for ($i = 1; $i < count($argv); $i++) {
$arg = $argv[$i];
if ($arg === '--verbose' || $arg === '-v') {
$this->verbose = true;
$formatter_args[] = $arg;
} elseif ($arg === '--help' || $arg === '-h') {
$this->show_help();
exit(0);
} elseif (substr($arg, 0, 1) !== '-') {
$files[] = $arg;
} else {
$formatter_args[] = $arg;
}
}
// If no files specified, format current directory
if (empty($files)) {
$files = ['.'];
}
// Load cache for regular mode too (to update it)
$this->load_cache();
$this->route_files($files, $formatter_args);
// Save cache if it was modified
$this->save_cache();
}
/**
* Handle --auto-reformat-periodic mode
*/
private function handle_auto_reformat_periodic()
{
$rsx_dir = $this->project_root . DIRECTORY_SEPARATOR . 'rsx';
if (!is_dir($rsx_dir)) {
$this->warning("RSX directory not found: $rsx_dir");
return;
}
// Load existing cache
$this->load_cache();
if (empty($this->cache_data)) {
// First run - build cache without formatting
$this->info("Building initial formatter cache...");
$files = $this->scan_directory($rsx_dir);
foreach ($files as $file) {
$this->cache_data[$file] = [
'mtime' => filemtime($file),
'size' => filesize($file),
'formatted_at' => time()
];
}
$this->cache_dirty = true;
$this->save_cache();
$this->success("Cache built with " . count($files) . " files");
return;
}
// Subsequent run - check for changes
$this->info("Checking for file changes...");
$files_to_format = [];
$files_to_remove = [];
// Check existing cached files
foreach ($this->cache_data as $file => $cache_entry) {
if (!file_exists($file)) {
// File was deleted
$files_to_remove[] = $file;
$this->info("Removed from cache: $file");
} else {
// Check if file changed
$current_mtime = filemtime($file);
$current_size = filesize($file);
if ($current_mtime != $cache_entry['mtime'] || $current_size != $cache_entry['size']) {
$files_to_format[] = $file;
if ($this->verbose) {
$this->info("Changed: $file");
}
}
}
}
// Remove deleted files from cache
foreach ($files_to_remove as $file) {
unset($this->cache_data[$file]);
$this->cache_dirty = true;
}
// Scan for new files
$current_files = $this->scan_directory($rsx_dir);
foreach ($current_files as $file) {
if (!isset($this->cache_data[$file])) {
$files_to_format[] = $file;
if ($this->verbose) {
$this->info("New file: $file");
}
}
}
if (empty($files_to_format)) {
$this->success("No files need formatting");
$this->save_cache();
return;
}
// Format the files that need it
$this->info("Formatting " . count($files_to_format) . " files...");
$this->route_files($files_to_format, $this->verbose ? ['-v'] : []);
// Save updated cache
$this->save_cache();
}
/**
* Scan directory for formattable files
*/
private function scan_directory($dir)
{
$files = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveCallbackFilterIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
function ($file, $key, $iterator) {
// Skip excluded directories
if ($iterator->hasChildren() && $this->is_excluded_dir($file->getFilename())) {
return false;
}
return true;
}
),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$path = $file->getPathname();
$extension = $this->get_file_extension($path);
if (isset($this->formatters[$extension])) {
$files[] = $path;
}
}
}
return $files;
}
/**
* Check if directory name should be excluded
*/
private function is_excluded_dir($dirname)
{
return in_array($dirname, $this->excluded_dirs);
}
/**
* Check if path contains excluded directories
*/
private function is_excluded_path($path)
{
$sep = DIRECTORY_SEPARATOR;
foreach ($this->excluded_dirs as $excluded) {
if (strpos($path, $sep . $excluded . $sep) !== false) {
return true;
}
}
return false;
}
/**
* Check if a path is absolute
*/
private function is_absolute_path($path)
{
// Unix absolute path
if (substr($path, 0, 1) === '/') {
return true;
}
// Windows absolute path (C:\ or C:/)
if (preg_match('/^[a-zA-Z]:[\\\\|\/]/', $path)) {
return true;
}
return false;
}
/**
* Load cache from file
*/
private function load_cache()
{
if (file_exists($this->cache_file)) {
$content = file_get_contents($this->cache_file);
$this->cache_data = json_decode($content, true) ?: [];
}
}
/**
* Save cache to file if dirty
*/
private function save_cache()
{
if ($this->cache_dirty) {
file_put_contents(
$this->cache_file,
json_encode($this->cache_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);
if ($this->verbose) {
$this->info("Cache updated");
}
}
}
/**
* Update cache entry for a file
*/
private function update_cache_entry($file_path)
{
if (file_exists($file_path)) {
$this->cache_data[$file_path] = [
'mtime' => filemtime($file_path),
'size' => filesize($file_path),
'formatted_at' => time()
];
$this->cache_dirty = true;
}
}
/**
* Route files to appropriate formatters
*/
private function route_files($paths, $formatter_args)
{
// Group files by formatter
$files_by_formatter = [];
foreach ($paths as $path) {
// Make path absolute if relative (works on both Windows and Unix)
if (!$this->is_absolute_path($path)) {
$path = getcwd() . DIRECTORY_SEPARATOR . $path;
}
if (is_file($path)) {
// Skip excluded paths
if ($this->is_excluded_path($path)) {
if ($this->verbose) {
$this->info("Skipping excluded: $path");
}
continue;
}
$extension = $this->get_file_extension($path);
if (isset($this->formatters[$extension])) {
$formatter = $this->formatters[$extension];
if (!isset($files_by_formatter[$formatter])) {
$files_by_formatter[$formatter] = [];
}
$files_by_formatter[$formatter][] = $path;
} elseif ($this->verbose) {
$this->info("No formatter for: $path");
}
} elseif (is_dir($path)) {
// For directories, recursively find all files
$this->route_directory($path, $files_by_formatter);
} else {
$this->warning("Path not found: $path");
}
}
// Execute formatters
foreach ($files_by_formatter as $formatter => $files) {
$this->execute_formatter($formatter, $files, $formatter_args);
}
}
/**
* Route all files in a directory
*/
private function route_directory($dir_path, &$files_by_formatter)
{
$iterator = new RecursiveIteratorIterator(
new RecursiveCallbackFilterIterator(
new RecursiveDirectoryIterator($dir_path, RecursiveDirectoryIterator::SKIP_DOTS),
function ($file, $key, $iterator) {
// Skip excluded directories
if ($iterator->hasChildren() && $this->is_excluded_dir($file->getFilename())) {
return false;
}
return true;
}
),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$path = $file->getPathname();
// Double-check path doesn't contain excluded dirs
if ($this->is_excluded_path($path)) {
continue;
}
$extension = $this->get_file_extension($path);
if (isset($this->formatters[$extension])) {
$formatter = $this->formatters[$extension];
if (!isset($files_by_formatter[$formatter])) {
$files_by_formatter[$formatter] = [];
}
$files_by_formatter[$formatter][] = $path;
}
}
}
}
/**
* Execute a formatter with files
*/
private function execute_formatter($formatter, $files, $args)
{
$formatter_path = $this->formatters_dir . DIRECTORY_SEPARATOR . $formatter;
if (!file_exists($formatter_path)) {
$this->error("Formatter not found: $formatter");
return;
}
// Process files one by one to track success
foreach ($files as $file) {
// Preserve original modification time to prevent VS Code conflicts
$original_mtime = file_exists($file) ? filemtime($file) : null;
// Build command for single file
// Use 'php' command which works on all platforms
$command = 'php ' . escapeshellarg($formatter_path);
// Add arguments
foreach ($args as $arg) {
$command .= ' ' . escapeshellarg($arg);
}
// Add file
$command .= ' ' . escapeshellarg($file);
// Execute formatter and capture output
ob_start();
passthru($command, $return_code);
$output = ob_get_clean();
// Output the formatter's output
echo $output;
// Update cache if formatting was successful
if ($return_code === 0) {
// Check if output indicates success (look for success marker)
if (strpos($output, '✓') !== false || strpos($output, 'Formatted') !== false) {
$this->update_cache_entry($file);
// Restore original modification time to prevent VS Code conflicts
// This allows VS Code to reload the file seamlessly without detecting external changes
if ($original_mtime !== null && file_exists($file)) {
touch($file, $original_mtime);
}
}
} elseif ($this->verbose) {
$this->warning("Formatter $formatter exited with code $return_code for $file");
}
}
}
/**
* Get file extension
*/
private function get_file_extension($path)
{
// Handle .formatting.tmp files from VS Code
if (substr($path, -15) === '.formatting.tmp') {
// Get the actual extension from before .formatting.tmp
$actual_path = substr($path, 0, -15);
$info = pathinfo($actual_path);
return isset($info['extension']) ? strtolower($info['extension']) : '';
}
// Handle .blade.php files
if (substr($path, -10) === '.blade.php') {
return 'blade';
}
$info = pathinfo($path);
return isset($info['extension']) ? strtolower($info['extension']) : '';
}
/**
* Show help message
*/
private function show_help()
{
$this->output("RSX Code Formatter");
$this->output("");
$this->output("Usage: rsx-format [options] [files...]");
$this->output("");
$this->output("Options:");
$this->output(" -v, --verbose Show detailed output");
$this->output(" -h, --help Show this help message");
$this->output("");
$this->output("Supported file types:");
foreach ($this->formatters as $ext => $formatter) {
$this->output(" .$ext - $formatter");
}
$this->output("");
$this->output("Examples:");
$this->output(" rsx-format # Format all files in current directory");
$this->output(" rsx-format app/ # Format all files in app directory");
$this->output(" rsx-format file.php file.json # Format specific files");
$this->output("");
$this->output("Note: vendor/, node_modules/, and public/ directories are automatically excluded");
}
/**
* Output helpers
*/
private function output($message)
{
echo $message . PHP_EOL;
}
private function success($message)
{
echo "\033[32m" . $message . "\033[0m" . PHP_EOL;
}
private function info($message)
{
echo "\033[36m" . $message . "\033[0m" . PHP_EOL;
}
private function warning($message)
{
echo "\033[33m" . $message . "\033[0m" . PHP_EOL;
}
private function error($message)
{
echo "\033[31m" . $message . "\033[0m" . PHP_EOL;
}
}
// Run the router
$router = new FormatterRouter();
$router->run($argv);

15
bin/update-jqhtml Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Update all jqhtml packages to latest versions
cd "$(dirname "$0")"
cd ..
echo "Updating jqhtml packages to latest versions..."
# Update all @jqhtml packages
npm update @jqhtml/core @jqhtml/parser @jqhtml/router @jqhtml/vscode-extension @jqhtml/webpack-loader
rm -rf storage/rsx_*
# Show installed versions
echo ""
echo "Current jqhtml package versions:"
npm list 2>/dev/null | grep "@jqhtml/" | grep -v deduped