mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
446 lines • 19.9 kB
JavaScript
/**
* Environment Analyzer for Cross-Platform Compatibility
* Analyzes environment variables, dependencies, and system requirements
*/
import fs from 'fs-extra';
import * as path from 'path';
import { glob } from 'glob';
export class EnvironmentAnalyzer {
projectRoot;
constructor(projectRoot) {
this.projectRoot = projectRoot;
}
isTestFile(filePath) {
const testPatterns = [
/\.test\.[jt]sx?$/,
/\.spec\.[jt]sx?$/,
/\/__tests__\//,
/\/tests?\//,
/\/e2e\//,
/test-utils/
];
return testPatterns.some(pattern => pattern.test(filePath));
}
isAnalyzerFile(filePath) {
return /\/analyzers?\//.test(filePath);
}
async analyze(issues) {
const environment = {
nodeVersionCompatibility: [],
dependencies: [],
environmentVariables: [],
shellCompatibility: {
bashScripts: [],
powershellScripts: [],
batchScripts: [],
crossPlatformIssues: []
},
fileSystemRequirements: {
pathSeparatorIssues: [],
caseSensitivityIssues: [],
reservedNameIssues: [],
longPathIssues: [],
permissionIssues: []
}
};
await Promise.all([
this.analyzeNodeVersion(environment),
this.analyzeDependencies(issues, environment),
this.analyzeEnvironmentVariables(issues, environment),
this.analyzeSystemAPIs(issues),
this.analyzeNetworkInterfaces(issues),
this.analyzeCharacterEncoding(issues),
this.analyzeLineEndings(issues)
]);
return environment;
}
async analyzeNodeVersion(environment) {
try {
const packageJsonPath = path.join(this.projectRoot, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJson(packageJsonPath);
if (packageJson.engines?.node) {
environment.nodeVersionCompatibility.push(packageJson.engines.node);
}
}
}
catch (error) {
// Skip if package.json can't be read
}
}
async analyzeDependencies(issues, environment) {
try {
const packageJsonPath = path.join(this.projectRoot, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJson(packageJsonPath);
const allDeps = {
...packageJson.dependencies,
...packageJson.devDependencies
};
for (const [name, version] of Object.entries(allDeps)) {
const depAnalysis = {
name,
version: version,
platforms: ['all'],
issues: [],
nativeModules: false
};
// Check for known native modules
const nativeModules = [
'node-sass', 'canvas', 'sharp', 'sqlite3', 'bcrypt',
'fsevents', 'node-gyp', 'leveldown', 'serialport',
'usb', 'node-hid', 'cpu-features', 'microtime',
'node-expat', 'node-rdkafka', 'grpc', '@grpc/grpc-js',
'deasync', 'fibers', 'node-addon-api'
];
if (nativeModules.includes(name)) {
depAnalysis.nativeModules = true;
depAnalysis.issues.push('Requires native compilation');
issues.push({
file: 'package.json',
severity: 'medium',
category: 'Dependencies',
type: 'native_module',
message: `Native module "${name}" requires compilation`,
affectedPlatforms: ['all'],
recommendation: 'Ensure build tools are available on all target platforms'
});
}
// Platform-specific dependencies
if (name === 'fsevents') {
depAnalysis.platforms = ['macOS'];
depAnalysis.issues.push('macOS only - file system events');
}
if (name.includes('win32') || name.includes('windows')) {
depAnalysis.platforms = ['windows'];
depAnalysis.issues.push('Windows-specific dependency');
}
// Check for optional dependencies
if (packageJson.optionalDependencies && name in packageJson.optionalDependencies) {
depAnalysis.issues.push('Optional dependency - may not install on all platforms');
}
environment.dependencies.push(depAnalysis);
}
// Check for platform-specific scripts
if (packageJson.scripts) {
for (const [scriptName, scriptValue] of Object.entries(packageJson.scripts)) {
if (typeof scriptValue === 'string') {
if (scriptValue.includes('node-gyp')) {
issues.push({
file: 'package.json',
severity: 'low',
category: 'Build',
type: 'native_build',
message: `Script "${scriptName}" uses node-gyp for native compilation`,
affectedPlatforms: ['all'],
recommendation: 'Ensure Python and build tools are available'
});
}
}
}
}
}
}
catch (error) {
// Skip if package.json can't be read
}
}
async analyzeEnvironmentVariables(issues, environment) {
const envPatterns = [
{
pattern: /process\.env\.HOME/g,
variable: 'HOME',
message: 'HOME environment variable - not set on Windows',
platforms: ['linux', 'macOS', 'unix'],
recommendation: 'Use os.homedir() or check both HOME and USERPROFILE'
},
{
pattern: /process\.env\.USERPROFILE/g,
variable: 'USERPROFILE',
message: 'USERPROFILE environment variable - Windows specific',
platforms: ['windows'],
recommendation: 'Use os.homedir() for cross-platform compatibility'
},
{
pattern: /process\.env\.TEMP/g,
variable: 'TEMP',
message: 'TEMP environment variable - Windows specific',
platforms: ['windows'],
recommendation: 'Use os.tmpdir() for cross-platform temp directory'
},
{
pattern: /process\.env\.TMPDIR/g,
variable: 'TMPDIR',
message: 'TMPDIR environment variable - Unix specific',
platforms: ['linux', 'macOS', 'unix'],
recommendation: 'Use os.tmpdir() for cross-platform temp directory'
},
{
pattern: /process\.env\.USER/g,
variable: 'USER',
message: 'USER environment variable - not set on Windows',
platforms: ['linux', 'macOS', 'unix'],
recommendation: 'Use os.userInfo().username for cross-platform'
},
{
pattern: /process\.env\.USERNAME/g,
variable: 'USERNAME',
message: 'USERNAME environment variable - Windows specific',
platforms: ['windows'],
recommendation: 'Use os.userInfo().username for cross-platform'
},
{
pattern: /process\.env\.SHELL/g,
variable: 'SHELL',
message: 'SHELL environment variable - not set on Windows',
platforms: ['linux', 'macOS', 'unix'],
recommendation: 'Check platform before relying on SHELL variable'
},
{
pattern: /process\.env\.COMSPEC/g,
variable: 'COMSPEC',
message: 'COMSPEC environment variable - Windows command interpreter',
platforms: ['windows'],
recommendation: 'Check platform before using shell-specific variables'
}
];
const foundVars = new Map();
try {
const files = await glob('**/*.{js,ts,jsx,tsx}', {
cwd: this.projectRoot,
ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**']
});
for (const file of files) {
const filePath = path.join(this.projectRoot, file);
const isTest = this.isTestFile(file);
const isAnalyzer = this.isAnalyzerFile(file);
try {
const content = await fs.readFile(filePath, 'utf-8');
const lines = content.split('\n');
lines.forEach((line, index) => {
for (const pattern of envPatterns) {
if (pattern.pattern.test(line)) {
// Skip test files with intentional bad patterns
if (isTest) {
const context = lines.slice(Math.max(0, index - 3), Math.min(lines.length, index + 3)).join('\n');
if (context.includes('cross-platform issues for testing') ||
context.includes('// File with') && context.includes('issues')) {
return;
}
}
// Skip analyzer example patterns
if (isAnalyzer && (line.includes('bad:') || line.includes('const home = process.env.HOME;'))) {
return;
}
if (!foundVars.has(pattern.variable)) {
foundVars.set(pattern.variable, {
variable: pattern.variable,
usage: [],
platformSpecific: pattern.platforms[0] !== 'all',
recommendations: [pattern.recommendation]
});
}
foundVars.get(pattern.variable).usage.push(`${file}:${index + 1}`);
issues.push({
file,
line: index + 1,
severity: 'medium',
category: 'Environment',
type: 'env_variable',
message: pattern.message,
affectedPlatforms: pattern.platforms,
recommendation: pattern.recommendation,
codeSnippet: line.trim()
});
}
}
});
}
catch (error) {
// Skip files that can't be read
}
}
}
catch (error) {
// Skip if glob fails
}
environment.environmentVariables = Array.from(foundVars.values());
}
async analyzeSystemAPIs(issues) {
const apiPatterns = [
{
pattern: /require\(['"]child_process['"]\)\.exec/g,
type: 'exec_usage',
message: 'exec() spawns shell - behavior differs across platforms',
severity: 'medium',
recommendation: 'Consider using spawn() with shell: false for consistency'
},
{
pattern: /os\.platform\(\)/g,
type: 'platform_detection',
message: 'Platform detection - good practice',
severity: 'low',
recommendation: 'Continue using platform detection for compatibility'
},
{
pattern: /os\.EOL/g,
type: 'eol_usage',
message: 'Using os.EOL for line endings - good practice',
severity: 'low',
recommendation: 'Continue using os.EOL for cross-platform line endings'
},
{
pattern: /\\r\\n|\\n\\r/g,
type: 'hardcoded_line_endings',
message: 'Hardcoded line endings detected',
severity: 'low',
recommendation: 'Use os.EOL for cross-platform line endings'
},
{
pattern: /os\.arch\(\)/g,
type: 'architecture_check',
message: 'Architecture detection - ensure handling all cases',
severity: 'low',
recommendation: 'Handle x64, arm64, ia32, and other architectures'
}
];
await this.scanWithPatterns(issues, apiPatterns, 'System APIs');
}
async analyzeNetworkInterfaces(issues) {
const networkPatterns = [
{
pattern: /127\.0\.0\.1|localhost/g,
type: 'localhost_reference',
message: 'Localhost reference - generally cross-platform',
severity: 'low',
recommendation: 'Consider IPv6 ::1 for complete compatibility'
},
{
pattern: /0\.0\.0\.0/g,
type: 'bind_all_interfaces',
message: 'Binding to all interfaces - behavior may vary',
severity: 'low',
recommendation: 'Be aware of platform-specific network configurations'
},
{
pattern: /\:\:1/g,
type: 'ipv6_localhost',
message: 'IPv6 localhost - ensure IPv6 is enabled',
severity: 'low',
recommendation: 'Check IPv6 availability on target platforms'
}
];
await this.scanWithPatterns(issues, networkPatterns, 'Network');
}
async analyzeCharacterEncoding(issues) {
const encodingPatterns = [
{
pattern: /encoding:\s*['"](?!utf-?8)/gi,
type: 'non_utf8_encoding',
message: 'Non-UTF8 encoding specified',
severity: 'medium',
recommendation: 'Use UTF-8 for maximum compatibility'
},
{
pattern: /Buffer\.from\([^,]+\)(?!.*,\s*['"]utf-?8)/g,
type: 'buffer_default_encoding',
message: 'Buffer without explicit encoding - uses UTF-8 by default',
severity: 'low',
recommendation: 'Explicitly specify encoding for clarity'
},
{
pattern: /readFile[^(]*\([^)]+\)(?!.*,\s*['"]utf-?8)/g,
type: 'file_read_binary',
message: 'File read without encoding - returns Buffer',
severity: 'low',
recommendation: 'Specify encoding if text file is expected'
}
];
await this.scanWithPatterns(issues, encodingPatterns, 'Character Encoding');
}
async analyzeLineEndings(issues) {
try {
const textFiles = await glob('**/*.{js,ts,jsx,tsx,json,md,txt,yml,yaml}', {
cwd: this.projectRoot,
ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**']
});
for (const file of textFiles) {
const filePath = path.join(this.projectRoot, file);
try {
const content = await fs.readFile(filePath, 'utf-8');
const hasCRLF = content.includes('\r\n');
const hasLF = content.includes('\n') && !content.includes('\r\n');
if (hasCRLF && hasLF) {
issues.push({
file,
severity: 'medium',
category: 'Line Endings',
type: 'mixed_line_endings',
message: 'File has mixed line endings (CRLF and LF)',
affectedPlatforms: ['all'],
recommendation: 'Use consistent line endings, preferably LF'
});
}
}
catch (error) {
// Skip files that can't be read
}
}
// Check for .gitattributes
const gitattributesPath = path.join(this.projectRoot, '.gitattributes');
if (!(await fs.pathExists(gitattributesPath))) {
issues.push({
file: '.gitattributes',
severity: 'low',
category: 'Configuration',
type: 'missing_gitattributes',
message: 'No .gitattributes file for line ending configuration',
affectedPlatforms: ['all'],
recommendation: 'Add .gitattributes with "* text=auto" for consistent line endings'
});
}
}
catch (error) {
// Skip if analysis fails
}
}
async scanWithPatterns(issues, patterns, category) {
try {
const files = await glob('**/*.{js,ts,jsx,tsx}', {
cwd: this.projectRoot,
ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**']
});
for (const file of files) {
const filePath = path.join(this.projectRoot, file);
try {
const content = await fs.readFile(filePath, 'utf-8');
const lines = content.split('\n');
lines.forEach((line, index) => {
for (const pattern of patterns) {
if (pattern.pattern.test(line)) {
issues.push({
file,
line: index + 1,
severity: pattern.severity,
category,
type: pattern.type,
message: pattern.message,
affectedPlatforms: ['all'],
recommendation: pattern.recommendation,
codeSnippet: line.trim()
});
}
}
});
}
catch (error) {
// Skip files that can't be read
}
}
}
catch (error) {
// Skip if glob fails
}
}
}
//# sourceMappingURL=EnvironmentAnalyzer.js.map