Compare commits

...

2 Commits

Author SHA1 Message Date
zefie
0c5dc17ae6 create depreciation scanning tool 2026-05-03 15:23:23 -04:00
zefie
4347543ef7 don't expose version on pc services if hide_minisrv_config is true 2026-05-03 15:15:27 -04:00
6 changed files with 412 additions and 19 deletions

View File

@@ -228,24 +228,43 @@ function getServiceString(service_name, overrides = {}) {
}
const DEPRECIATED_CONFIG_PATH = path.join(__dirname, 'includes', 'depreciated.json');
function loadDepreciatedPatterns() {
try {
if (!fs.existsSync(DEPRECIATED_CONFIG_PATH)) {
return {};
}
const raw = fs.readFileSync(DEPRECIATED_CONFIG_PATH, 'utf8');
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed) || parsed.length === 0) {
return {};
}
const mapped = parsed
.filter((entry) => entry && typeof entry.pattern === 'string')
.map((entry) => ({
id: entry.id || entry.pattern,
pattern: new RegExp(entry.pattern, entry.flags || 'g'),
message: entry.message || 'Deprecated API usage found',
removeVersion: entry.removeVersion || 'unknown',
replacement: entry.replacement || null
}));
return mapped.length > 0 ? mapped : {};
} catch (error) {
console.warn('Failed to load includes/depreciated.json, using fallback deprecation patterns:', error.message);
return {};
}
}
// Deprecation warnings configuration
const deprecationWarnings = {
// Array of deprecated patterns with their details
patterns: [
{
// Example deprecations - you can modify these as needed
pattern: /session\_data\.hasCap\s*\(/g,
message: "session_data.hasCap() is deprecated and will be removed",
removeVersion: "0.9.80",
replacement: "Use session_data.capabilities.get() instead"
},
{
pattern: /(?<!wtvshared\.)getServiceString\s*\(/g,
message: "getServiceString() is deprecated and will be removed",
removeVersion: "0.9.80",
replacement: "Use wtvshared.getServiceString() instead"
}
],
patterns: loadDepreciatedPatterns(),
// Enable/disable deprecation warnings globally
enabled: true,

View File

@@ -1,15 +1,17 @@
const minisrv_service_file = true;
const title = minisrv_config.config.hide_minisrv_version ? "zefie's minisrv PC Services" : `zefie minisrv v${minisrv_config.version} PC Services`;
headers = `200 OK
Content-Type: text/html`
data = `<html>
<head>
<title>zefie minisrv v${minisrv_config.version}</title>
<title>${title}</title>
</head>
<body bgcolor="#000000" text="#449944">
<p>
Welcome to the zefie minisrv v${minisrv_config.version} PC Services
Welcome to ${title}
</p>
<hr>
<a href="/viewergen/">WebTV Viewer Generator</a><br>

View File

@@ -37,7 +37,7 @@ if (minisrv_config.config.hide_incomplete_features) {
}
/* We need to fix most webtv viewers for this, since they spoof a build that doesn't support messenger?
if (!session_data.hasCap("client-can-use-messenger")) {
if (!session_data.capabilities.get("client-can-use-messenger")) {
removeSettingByUrl("wtv-setup:/messenger");
}
*/

View File

@@ -0,0 +1,18 @@
[
{
"id": "session_data.hasCap",
"pattern": "session\\_data\\.hasCap\\s*\\(",
"flags": "g",
"message": "session_data.hasCap() is deprecated and will be removed",
"removeVersion": "0.9.80",
"replacement": "Use session_data.capabilities.get() instead"
},
{
"id": "getServiceString",
"pattern": "(?<!wtvshared\\.)getServiceString\\s*\\(",
"flags": "g",
"message": "getServiceString() is deprecated and will be removed",
"removeVersion": "0.9.80",
"replacement": "Use wtvshared.getServiceString() instead"
}
]

View File

@@ -9,7 +9,8 @@
"start": "node app.js",
"test": "node test.js",
"debug": "cross-env DEBUG=* node app.js",
"modem-proxy": "node modem_proxy.js"
"modem-proxy": "node modem_proxy.js",
"scan-service-deprecations": "node tools/scan_service_vault_deprecations.js"
},
"author": {
"name": "zefie",

View File

@@ -0,0 +1,353 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const ROOT_DIR = path.resolve(__dirname, '..');
const DEFAULT_USER_CONFIG_PATH = path.join(ROOT_DIR, 'user_config.json');
const DEFAULT_BASE_CONFIG_PATH = path.join(ROOT_DIR, 'includes', 'config.json');
const DEPRECIATED_CONFIG_PATH = path.join(ROOT_DIR, 'includes', 'depreciated.json');
function printUsage() {
console.log('Usage: node tools/scan_service_vault_deprecations.js [options]');
console.log('');
console.log('Options:');
console.log(' --vault <path> Add a vault root to scan (can be repeated).');
console.log(' --config <path> Config file to read ServiceVaults from (default: user_config.json).');
console.log(' --base-config <path> Base config fallback (default: includes/config.json).');
console.log(' --ext <csv> File extensions to scan (default: .js). Example: --ext .js,.txt');
console.log(' --json Emit machine-readable JSON output.');
console.log(' --fail-on-found Exit with code 2 if any deprecations are found.');
console.log(' -h, --help Show this help text.');
}
function parseJsonWithComments(json) {
if (typeof json !== 'string') json = json ? json.toString() : '';
let result = '';
let i = 0;
let isString = false;
let isEscape = false;
let isBlockComment = false;
let isLineComment = false;
while (i < json.length) {
const ch = json[i];
const next = json[i + 1];
if (!isString && !isEscape && ch === '/' && next === '*') {
isBlockComment = true;
i += 1;
} else if (isBlockComment && ch === '*' && next === '/') {
isBlockComment = false;
i += 1;
} else if (!isString && !isEscape && ch === '/' && next === '/') {
isLineComment = true;
i += 1;
} else if (isLineComment && (ch === '\n' || ch === '\r')) {
isLineComment = false;
} else if (!isBlockComment && !isLineComment) {
if (ch === '"' && !isEscape) {
isString = !isString;
}
isEscape = ch === '\\' && !isEscape;
result += ch;
}
i += 1;
}
return JSON.parse(result);
}
function readConfigIfExists(filePath) {
if (!filePath || !fs.existsSync(filePath)) return null;
const raw = fs.readFileSync(filePath, 'utf8');
if (!raw.trim()) return {};
return parseJsonWithComments(raw);
}
function loadDepreciatedPatterns() {
try {
if (!fs.existsSync(DEPRECIATED_CONFIG_PATH)) {
return {};
}
const raw = fs.readFileSync(DEPRECIATED_CONFIG_PATH, 'utf8');
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed) || parsed.length === 0) {
return {};
}
const mapped = parsed
.filter((entry) => entry && typeof entry.pattern === 'string')
.map((entry) => ({
id: entry.id || entry.pattern,
pattern: new RegExp(entry.pattern, entry.flags || 'g'),
message: entry.message || 'Deprecated API usage found',
removeVersion: entry.removeVersion || null,
replacement: entry.replacement || null
}));
return mapped.length > 0 ? mapped : {};
} catch (error) {
console.warn(`Warning: failed to load ${DEPRECIATED_CONFIG_PATH}: ${error.message}`);
return {};
}
}
function parseArgs(argv) {
const args = argv.slice(2);
const options = {
vaults: [],
configPath: DEFAULT_USER_CONFIG_PATH,
baseConfigPath: DEFAULT_BASE_CONFIG_PATH,
extensions: new Set(['.js']),
json: false,
failOnFound: false
};
for (let i = 0; i < args.length; i += 1) {
const arg = args[i];
if (arg === '-h' || arg === '--help') {
printUsage();
process.exit(0);
}
if (arg === '--vault') {
i += 1;
const value = args[i];
if (!value) throw new Error('Missing value for --vault');
options.vaults.push(value);
continue;
}
if (arg === '--config') {
i += 1;
const value = args[i];
if (!value) throw new Error('Missing value for --config');
options.configPath = path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
continue;
}
if (arg === '--base-config') {
i += 1;
const value = args[i];
if (!value) throw new Error('Missing value for --base-config');
options.baseConfigPath = path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
continue;
}
if (arg === '--ext') {
i += 1;
const value = args[i];
if (!value) throw new Error('Missing value for --ext');
options.extensions = new Set(
value
.split(',')
.map((ext) => ext.trim().toLowerCase())
.filter(Boolean)
.map((ext) => (ext.startsWith('.') ? ext : `.${ext}`))
);
continue;
}
if (arg === '--json') {
options.json = true;
continue;
}
if (arg === '--fail-on-found') {
options.failOnFound = true;
continue;
}
throw new Error(`Unknown argument: ${arg}`);
}
return options;
}
function getConfiguredVaults(configPath, baseConfigPath) {
const userConfig = readConfigIfExists(configPath) || {};
const baseConfig = readConfigIfExists(baseConfigPath) || {};
const userVaults = userConfig.config && Array.isArray(userConfig.config.ServiceVaults)
? userConfig.config.ServiceVaults
: null;
const baseVaults = baseConfig.config && Array.isArray(baseConfig.config.ServiceVaults)
? baseConfig.config.ServiceVaults
: null;
const selected = userVaults && userVaults.length > 0 ? userVaults : (baseVaults || []);
return selected.map((vault) => path.resolve(ROOT_DIR, String(vault)));
}
function walkFiles(rootDir, extensions, fileList = []) {
if (!fs.existsSync(rootDir)) return fileList;
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(rootDir, entry.name);
if (entry.isDirectory()) {
walkFiles(fullPath, extensions, fileList);
continue;
}
if (!entry.isFile()) continue;
const ext = path.extname(entry.name).toLowerCase();
if (extensions.has(ext)) {
fileList.push(fullPath);
}
}
return fileList;
}
function toLineColumn(text, index) {
let line = 1;
let column = 1;
for (let i = 0; i < index; i += 1) {
if (text[i] === '\n') {
line += 1;
column = 1;
} else {
column += 1;
}
}
return { line, column };
}
function scanFile(filePath, patterns) {
const content = fs.readFileSync(filePath, 'utf8');
const findings = [];
for (const rule of patterns) {
rule.pattern.lastIndex = 0;
let match;
while ((match = rule.pattern.exec(content)) !== null) {
const loc = toLineColumn(content, match.index);
findings.push({
ruleId: rule.id,
match: match[0],
message: rule.message,
removeVersion: rule.removeVersion,
replacement: rule.replacement,
line: loc.line,
column: loc.column
});
}
}
return findings;
}
function formatRelative(targetPath) {
return path.relative(ROOT_DIR, targetPath) || targetPath;
}
function main() {
const options = parseArgs(process.argv);
const deprecationPatterns = loadDepreciatedPatterns();
if (deprecationPatterns.length === 0) {
console.warn('No deprecation patterns found. Exiting without scanning.');
process.exit(0);
}
const configuredVaults = getConfiguredVaults(options.configPath, options.baseConfigPath);
const explicitVaults = options.vaults.map((vault) => (
path.isAbsolute(vault) ? path.resolve(vault) : path.resolve(process.cwd(), vault)
));
const vaultsToScan = [...new Set([...configuredVaults, ...explicitVaults])];
if (vaultsToScan.length === 0) {
throw new Error('No ServiceVault paths found. Define config.ServiceVaults or pass --vault.');
}
const missingVaults = [];
const filesToScan = [];
for (const vaultPath of vaultsToScan) {
if (!fs.existsSync(vaultPath)) {
missingVaults.push(vaultPath);
continue;
}
walkFiles(vaultPath, options.extensions, filesToScan);
}
const results = [];
let totalFindings = 0;
for (const filePath of filesToScan) {
const findings = scanFile(filePath, deprecationPatterns);
if (findings.length > 0) {
totalFindings += findings.length;
results.push({ file: filePath, findings });
}
}
if (options.json) {
const payload = {
rootDir: ROOT_DIR,
scannedVaults: vaultsToScan,
missingVaults,
scannedFiles: filesToScan.length,
matchedFiles: results.length,
totalFindings,
results
};
console.log(JSON.stringify(payload, null, 2));
} else {
console.log('ServiceVault deprecation scan');
console.log(`- Vault roots: ${vaultsToScan.length}`);
console.log(`- Missing vault roots: ${missingVaults.length}`);
console.log(`- Files scanned: ${filesToScan.length}`);
console.log(`- Files with deprecations: ${results.length}`);
console.log(`- Total deprecations: ${totalFindings}`);
if (missingVaults.length > 0) {
console.log('');
console.log('Missing vault roots:');
for (const missing of missingVaults) {
console.log(` - ${formatRelative(missing)}`);
}
}
if (results.length > 0) {
console.log('');
for (const result of results) {
console.log(formatRelative(result.file));
for (const finding of result.findings) {
console.log(` ${finding.line}:${finding.column} ${finding.ruleId}`);
console.log(` ${finding.message}`);
if (finding.removeVersion) {
console.log(` Remove version: ${finding.removeVersion}`);
}
console.log(` Match: ${finding.match}`);
if (finding.replacement) {
console.log(` Fix: ${finding.replacement}`);
}
}
}
}
}
if (totalFindings > 0 && options.failOnFound) {
process.exit(2);
}
}
try {
main();
} catch (error) {
console.error(`Error: ${error.message || error}`);
process.exit(1);
}