diff --git a/zefie_wtvp_minisrv/test.js b/zefie_wtvp_minisrv/test.js index 49c0d926..57562e3f 100644 --- a/zefie_wtvp_minisrv/test.js +++ b/zefie_wtvp_minisrv/test.js @@ -6,6 +6,11 @@ const stat = promisify(fs.stat); const { exec } = require("child_process"); var path = require('path'); +// Usage: node test.js [filename|directory] +// If no argument is provided, checks all JavaScript files in the project +// If filename is provided, checks only that specific file +// If directory is provided, recursively checks all JavaScript files in that directory + async function getFiles(dir) { const subdirs = await readdir(dir); const files = await Promise.all(subdirs.map(async (subdir) => { @@ -15,19 +20,212 @@ async function getFiles(dir) { return files.reduce((a, f) => a.concat(f), []); } -getFiles(__dirname) - .then(files => { - files.forEach(function (file) { - if (path.extname(file) == ".js" && file.indexOf("node_modules") == -1) { - console.log(" * Checking syntax of", file.replace(__dirname + path.sep, "." + path.sep)); - exec("node --check \"" + file + "\"", (error, stdout, stderr) => { - if (stderr.length > 0) { - console.log(`${stderr}`); - return; - } - }); +function checkScopeErrors(file) { + return new Promise((resolve) => { + // Create a temporary ESLint config file to avoid command-line escaping issues + const tempConfigPath = path.join(__dirname, '.temp-eslint-config.json'); + + // Check if file is in ServiceDeps or ServiceVault directories + const normalizedFile = file.replace(/\\/g, '/'); + const isServiceFile = normalizedFile.includes('includes/ServiceDeps') || normalizedFile.includes('includes/ServiceVault'); + + const eslintConfig = { + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module" + }, + "env": { + "node": true, + "es2022": true + }, + "rules": { + "no-redeclare": 2, + "no-undef": 2, + "no-use-before-define": ["error", {"variables": false, "functions": false, "classes": false}], + "block-scoped-var": 2, + "no-const-assign": 2, + "prefer-const": 1, + "no-var": 1 } + }; + + // Add global variables for service files to ignore specific undefined variables + if (isServiceFile) { + eslintConfig.globals = { + "wtvmime": "readonly", + "http": "readonly", + "https": "readonly", + "sharp": "readonly", + "nunjucks": "readonly", + "URL": "readonly", + "URLSearchParams": "readonly", + "wtvshared": "readonly", + "zlib": "readonly", + "clientShowAlert": "readonly", + "WTVClientSessionData": "readonly", + "WTVClientCapabilities": "readonly", + "strftime": "readonly", + "CryptoJS": "readonly", + "crypto": "readonly", + "fs": "readonly", + "path": "readonly", + "debug": "readonly", + "minisrv_config": "readonly", + "socket": "readonly", + "headers": "readonly", + "data": "readonly", + "request_is_async": "readonly", + "minisrv_version_string": "readonly", + "getServiceString": "readonly", + "sendToClient": "readonly", + "service_vaults": "readonly", + "service_deps": "readonly", + "ssid_sessions": "readonly", + "moveArrayKey": "readonly", + "cwd": "readonly", + "request_headers": "readonly", + "session_data": "readonly", + }; + + // Check if this is a privileged service by examining the file path + try { + // Load WTVShared to read configuration + const WTVShared = require('./includes/classes/WTVShared.js')['WTVShared']; + const wtvshared = new WTVShared(null, true); // Load config, suppress output + const config = wtvshared.minisrv_config; + + // Extract service name from file path + // Look for patterns like includes/ServiceVault/wtv-servicename or includes/ServiceDeps/.../wtv-servicename + const serviceMatch = normalizedFile.match(/includes\/Service(?:Vault|Deps)(?:\/[^\/]*)*?\/(wtv-[a-z0-9-]+|[a-z0-9-]+)(?:\/|$)/); + if (serviceMatch) { + let serviceName = serviceMatch[1]; + + // Try both with and without wtv- prefix + const serviceNameWithPrefix = serviceName.startsWith('wtv-') ? serviceName : `wtv-${serviceName}`; + const serviceNameWithoutPrefix = serviceName.startsWith('wtv-') ? serviceName.replace('wtv-', '') : serviceName; + + console.log(`Detected service: ${serviceNameWithPrefix} or ${serviceNameWithoutPrefix}`); + + // Check if either service name exists and is privileged + const service = config.services[serviceNameWithPrefix] || config.services[serviceNameWithoutPrefix]; + + if (service && service.privileged === true) { + console.log(`Service ${serviceNameWithPrefix || serviceNameWithoutPrefix} is privileged - adding extra globals`); + // Add additional globals for privileged services + eslintConfig.globals = { + ...eslintConfig.globals, + "privileged": "readonly", + "SessionStore": "readonly", + "socket_sessions": "readonly", + "reloadConfig": "readonly", + "classPath": "readonly", + "session_data": "readonly", + }; + } + } + } catch (e) { + // If we can't load config, just continue with basic globals + console.log(` * Warning: Could not load config for privileged service detection: ${e.message}`); + } + } + + // Write temporary config file + fs.writeFileSync(tempConfigPath, JSON.stringify(eslintConfig, null, 2)); + + // Use ESLint to check for scope-related errors with let/const + const eslintCmd = `npx eslint --no-eslintrc --config "${tempConfigPath}" --format compact "${file}"`; + + exec(eslintCmd, (error, stdout, stderr) => { + // Clean up temporary config file + try { + fs.unlinkSync(tempConfigPath); + } catch (e) { + // Ignore cleanup errors + } + + if (stdout && stdout.trim().length > 0) { + console.log(` * Scope errors found in ${file.replace(__dirname + path.sep, "." + path.sep)}:`); + console.log(stdout); + } + resolve(); }); + }); +} + +function checkSyntax(file) { + return new Promise((resolve) => { + exec("node --check \"" + file + "\"", (error, stdout, stderr) => { + if (stderr.length > 0) { + console.log(`${stderr}`); + } + resolve(); + }); + }); +} + +getFiles(__dirname) + .then(async files => { + // Check if a specific file or directory was provided as command line argument + const targetPath = process.argv[2]; + let jsFiles; + + if (targetPath) { + // If a specific path is provided, resolve it and check if it exists + const fullPath = path.resolve(__dirname, targetPath); + + if (fs.existsSync(fullPath)) { + const stats = fs.statSync(fullPath); + + if (stats.isFile()) { + // Single file provided + if (path.extname(fullPath) === ".js") { + jsFiles = [fullPath]; + console.log(`Checking specific file: ${targetPath}\n`); + } else { + console.error(`Error: File "${targetPath}" is not a JavaScript file.`); + process.exit(1); + } + } else if (stats.isDirectory()) { + // Directory provided - recursively get all JS files + console.log(`Checking directory: ${targetPath}\n`); + const directoryFiles = await getFiles(fullPath); + jsFiles = directoryFiles.filter(file => + path.extname(file) === ".js" && + file.indexOf("node_modules") === -1 + ); + if (jsFiles.length === 0) { + console.log("No JavaScript files found in the specified directory."); + process.exit(0); + } + console.log(`Found ${jsFiles.length} JavaScript file(s) in directory.\n`); + } else { + console.error(`Error: "${targetPath}" is neither a file nor a directory.`); + process.exit(1); + } + } else { + console.error(`Error: Path "${targetPath}" not found.`); + process.exit(1); + } + } else { + // If no specific path, check all JS files as before + jsFiles = files.filter(file => + path.extname(file) == ".js" && + file.indexOf("node_modules") == -1 + ); + console.log("Running syntax and scope checks on all JavaScript files...\n"); + } + + for (const file of jsFiles) { + console.log(" * Checking", file.replace(__dirname + path.sep, "." + path.sep)); + + // Check syntax first + await checkSyntax(file); + + // Then check for scope errors + await checkScopeErrors(file); + } + + console.log("\nChecks completed."); }) .catch(e => console.error(e));