2026-02-01 15:20:17 +05:30
|
|
|
const fs = require('fs');
|
|
|
|
|
const { execSync } = require('child_process');
|
|
|
|
|
const path = require('path');
|
|
|
|
|
const os = require('os');
|
|
|
|
|
|
|
|
|
|
// Package managers to test
|
|
|
|
|
const PACKAGE_MANAGERS = ['npm', 'pnpm'];
|
|
|
|
|
|
|
|
|
|
// Well-known dependencies to install
|
|
|
|
|
const TEST_DEPENDENCIES = ['lodash'];
|
|
|
|
|
|
|
|
|
|
const results = { passed: 0, failed: 0, tests: [] };
|
|
|
|
|
|
|
|
|
|
function test(name, fn) {
|
|
|
|
|
try {
|
|
|
|
|
const result = fn();
|
|
|
|
|
results.tests.push({ name, status: result ? 'PASS' : 'FAIL', error: null });
|
|
|
|
|
result ? results.passed++ : results.failed++;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
results.tests.push({ name, status: 'ERROR', error: e.message });
|
|
|
|
|
results.failed++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function exec(cmd, options = {}) {
|
|
|
|
|
try {
|
|
|
|
|
const output = execSync(cmd, {
|
|
|
|
|
encoding: 'utf8',
|
|
|
|
|
timeout: 120000, // 2 minutes
|
|
|
|
|
...options
|
|
|
|
|
});
|
|
|
|
|
return { success: true, output };
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return { success: false, error: e.message, output: e.stdout || '' };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createTempDir(prefix) {
|
|
|
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function cleanup(dir) {
|
|
|
|
|
try {
|
|
|
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// Ignore cleanup errors
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-07 10:03:23 +05:30
|
|
|
// npm-restrictive allow_write includes /tmp/**. Use cache/store paths under /tmp so
|
|
|
|
|
// bubblewrap can bind /tmp read-write without pre-creating ~/.npm or ~/.cache/pnpm.
|
|
|
|
|
function pmEnv(pm) {
|
|
|
|
|
const base = path.join(os.tmpdir(), 'pmg-e2e');
|
|
|
|
|
fs.mkdirSync(base, { recursive: true });
|
|
|
|
|
|
|
|
|
|
const env = { ...process.env };
|
|
|
|
|
env.npm_config_cache = path.join(base, 'npm-cache');
|
|
|
|
|
fs.mkdirSync(env.npm_config_cache, { recursive: true });
|
|
|
|
|
|
|
|
|
|
if (pm === 'pnpm') {
|
|
|
|
|
env.PNPM_HOME = path.join(base, 'pnpm-home');
|
|
|
|
|
env.npm_config_store_dir = path.join(base, 'pnpm-store');
|
|
|
|
|
fs.mkdirSync(env.PNPM_HOME, { recursive: true });
|
|
|
|
|
fs.mkdirSync(env.npm_config_store_dir, { recursive: true });
|
|
|
|
|
env.PATH = `${env.PNPM_HOME}${path.delimiter}${env.PATH}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return env;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-01 15:20:17 +05:30
|
|
|
function testPackageManager(pm) {
|
|
|
|
|
const testDir = createTempDir(`pmg-e2e-${pm}-`);
|
2026-06-07 10:03:23 +05:30
|
|
|
const env = pmEnv(pm);
|
2026-02-01 15:20:17 +05:30
|
|
|
console.log(`\n Test directory: ${testDir}`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Initialize project
|
|
|
|
|
test(`${pm}: Initialize project`, () => {
|
|
|
|
|
const initCmd = pm === 'npm' ? 'npm init -y' : 'pnpm init';
|
2026-06-07 10:03:23 +05:30
|
|
|
const result = exec(initCmd, { cwd: testDir, env });
|
2026-02-01 15:20:17 +05:30
|
|
|
if (!result.success) {
|
|
|
|
|
console.log(` ❌ FAIL: ${result.error}`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const packageJsonExists = fs.existsSync(path.join(testDir, 'package.json'));
|
|
|
|
|
if (packageJsonExists) {
|
|
|
|
|
console.log(` ✅ PASS: Project initialized`);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
console.log(` ❌ FAIL: package.json not created`);
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Add dependencies
|
|
|
|
|
const depsStr = TEST_DEPENDENCIES.join(', ');
|
|
|
|
|
test(`${pm}: Add dependencies (${depsStr})`, () => {
|
|
|
|
|
const depsList = TEST_DEPENDENCIES.join(' ');
|
|
|
|
|
const addCmd = pm === 'npm'
|
|
|
|
|
? `npm install ${depsList}`
|
|
|
|
|
: `pnpm add ${depsList}`;
|
2026-06-07 10:03:23 +05:30
|
|
|
const result = exec(addCmd, { cwd: testDir, env });
|
2026-02-01 15:20:17 +05:30
|
|
|
if (!result.success) {
|
|
|
|
|
console.log(` ❌ FAIL: ${result.error}`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify dependencies were added to package.json
|
|
|
|
|
const packageJson = JSON.parse(fs.readFileSync(path.join(testDir, 'package.json'), 'utf8'));
|
|
|
|
|
const missingDeps = TEST_DEPENDENCIES.filter(
|
|
|
|
|
dep => !packageJson.dependencies || !packageJson.dependencies[dep]
|
|
|
|
|
);
|
|
|
|
|
if (missingDeps.length === 0) {
|
|
|
|
|
console.log(` ✅ PASS: Dependencies added`);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
console.log(` ❌ FAIL: Missing dependencies in package.json: ${missingDeps.join(', ')}`);
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Verify node_modules exists
|
|
|
|
|
test(`${pm}: Verify node_modules created`, () => {
|
|
|
|
|
const nodeModulesExists = fs.existsSync(path.join(testDir, 'node_modules'));
|
|
|
|
|
if (!nodeModulesExists) {
|
|
|
|
|
console.log(` ❌ FAIL: node_modules missing`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const missingDeps = TEST_DEPENDENCIES.filter(
|
|
|
|
|
dep => !fs.existsSync(path.join(testDir, 'node_modules', dep))
|
|
|
|
|
);
|
|
|
|
|
if (missingDeps.length === 0) {
|
|
|
|
|
console.log(` ✅ PASS: node_modules and dependencies exist`);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
console.log(` ❌ FAIL: Missing in node_modules: ${missingDeps.join(', ')}`);
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Clean install (remove node_modules and reinstall)
|
|
|
|
|
test(`${pm}: Clean install`, () => {
|
|
|
|
|
// Remove node_modules
|
|
|
|
|
const nodeModulesPath = path.join(testDir, 'node_modules');
|
|
|
|
|
fs.rmSync(nodeModulesPath, { recursive: true, force: true });
|
|
|
|
|
|
|
|
|
|
// Reinstall
|
|
|
|
|
const installCmd = pm === 'npm' ? 'npm install' : 'pnpm install';
|
2026-06-07 10:03:23 +05:30
|
|
|
const result = exec(installCmd, { cwd: testDir, env });
|
2026-02-01 15:20:17 +05:30
|
|
|
if (!result.success) {
|
|
|
|
|
console.log(` ❌ FAIL: ${result.error}`);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify node_modules recreated with all dependencies
|
|
|
|
|
const missingDeps = TEST_DEPENDENCIES.filter(
|
|
|
|
|
dep => !fs.existsSync(path.join(testDir, 'node_modules', dep))
|
|
|
|
|
);
|
|
|
|
|
if (missingDeps.length === 0) {
|
|
|
|
|
console.log(` ✅ PASS: Clean install successful`);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
console.log(` ❌ FAIL: Dependencies not reinstalled: ${missingDeps.join(', ')}`);
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
cleanup(testDir);
|
|
|
|
|
console.log(` Cleaned up: ${testDir}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Main
|
|
|
|
|
console.log('=== PMG Package Manager E2E Tests ===\n');
|
|
|
|
|
console.log('This script tests basic npm/pnpm flows to ensure sandbox compatibility.\n');
|
|
|
|
|
|
|
|
|
|
for (const pm of PACKAGE_MANAGERS) {
|
|
|
|
|
// Check if package manager is available
|
|
|
|
|
const checkResult = exec(`which ${pm}`);
|
|
|
|
|
if (!checkResult.success) {
|
|
|
|
|
console.log(`--- Skipping ${pm} (not installed) ---`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`--- Testing ${pm.toUpperCase()} ---`);
|
|
|
|
|
testPackageManager(pm);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Summary
|
|
|
|
|
console.log('\n=== SUMMARY ===');
|
|
|
|
|
console.log(`Passed: ${results.passed}/${results.tests.length}`);
|
|
|
|
|
console.log(`Failed: ${results.failed}/${results.tests.length}`);
|
|
|
|
|
|
|
|
|
|
if (results.failed > 0) {
|
|
|
|
|
console.log('\nFailed tests:');
|
|
|
|
|
results.tests.filter(t => t.status !== 'PASS').forEach(t => {
|
|
|
|
|
console.log(` - ${t.name}: ${t.status} ${t.error || ''}`);
|
|
|
|
|
});
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('\nAll tests passed!');
|