Project Browsers 1.0
This commit is contained in:
132
lib/scanner.js
Normal file
132
lib/scanner.js
Normal file
@@ -0,0 +1,132 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { parseEnvFile } = require('./env-parser');
|
||||
|
||||
const BASE_DIR = path.resolve(__dirname, '../../');
|
||||
const SKIP_DIRS = new Set([
|
||||
'backups', 'varia', 'wn-golem-starter', 'summercms.io', 'docs', 'projects-browser'
|
||||
]);
|
||||
|
||||
function extractPortFromUrl(url) {
|
||||
if (!url) return null;
|
||||
try {
|
||||
const u = new URL(url);
|
||||
const port = parseInt(u.port, 10);
|
||||
return port || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function detectFrontend(dirPath) {
|
||||
try {
|
||||
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory() || !entry.name.startsWith('vue-')) continue;
|
||||
const frontendDir = path.join(dirPath, entry.name);
|
||||
const pkgPath = path.join(frontendDir, 'package.json');
|
||||
if (!fs.existsSync(pkgPath)) continue;
|
||||
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||
if (!pkg.scripts?.dev) continue;
|
||||
} catch { continue; }
|
||||
|
||||
const hasPnpmLock = fs.existsSync(path.join(frontendDir, 'pnpm-lock.yaml'));
|
||||
return {
|
||||
dir: entry.name,
|
||||
fullPath: frontendDir,
|
||||
packageManager: hasPnpmLock ? 'pnpm' : 'npm',
|
||||
};
|
||||
}
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
|
||||
function scanProjects() {
|
||||
const projects = [];
|
||||
const entries = fs.readdirSync(BASE_DIR, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory() || SKIP_DIRS.has(entry.name) || entry.name.startsWith('.')) continue;
|
||||
|
||||
const dirPath = path.join(BASE_DIR, entry.name);
|
||||
|
||||
// Check if artisan exists directly
|
||||
if (fs.existsSync(path.join(dirPath, 'artisan'))) {
|
||||
projects.push(buildProjectMeta(dirPath, entry.name));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check one level deeper
|
||||
try {
|
||||
const subEntries = fs.readdirSync(dirPath, { withFileTypes: true });
|
||||
for (const sub of subEntries) {
|
||||
if (!sub.isDirectory() || sub.name.startsWith('.')) continue;
|
||||
const subPath = path.join(dirPath, sub.name);
|
||||
if (fs.existsSync(path.join(subPath, 'artisan'))) {
|
||||
projects.push(buildProjectMeta(subPath, entry.name));
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// Sort by slug for deterministic port assignment
|
||||
projects.sort((a, b) => a.slug.localeCompare(b.slug));
|
||||
|
||||
// Assign API ports to projects without one
|
||||
let nextPort = 8100;
|
||||
const usedPorts = new Set(projects.filter(p => p.port).map(p => p.port));
|
||||
|
||||
for (const project of projects) {
|
||||
if (!project.port) {
|
||||
while (usedPorts.has(nextPort)) nextPort++;
|
||||
project.port = nextPort;
|
||||
project.portSource = 'assigned';
|
||||
usedPorts.add(nextPort);
|
||||
nextPort++;
|
||||
}
|
||||
}
|
||||
|
||||
// Assign frontend ports deterministically from 3010+
|
||||
let nextFrontendPort = 3010;
|
||||
for (const project of projects) {
|
||||
if (project.frontend) {
|
||||
project.frontend.port = nextFrontendPort++;
|
||||
}
|
||||
}
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
function buildProjectMeta(dirPath, slug) {
|
||||
const envPath = path.join(dirPath, '.env');
|
||||
const hasEnv = fs.existsSync(envPath);
|
||||
const env = hasEnv ? parseEnvFile(envPath) : {};
|
||||
|
||||
const port = extractPortFromUrl(env.APP_URL);
|
||||
|
||||
// Make SQLite path relative if it starts with the project path
|
||||
let dbName = env.DB_DATABASE || null;
|
||||
if (dbName && dbName.startsWith(dirPath)) {
|
||||
dbName = path.relative(dirPath, dbName);
|
||||
}
|
||||
|
||||
const frontend = detectFrontend(dirPath);
|
||||
const hasDocs = fs.existsSync(path.join(dirPath, 'docs'));
|
||||
|
||||
return {
|
||||
slug,
|
||||
name: slug,
|
||||
path: dirPath,
|
||||
port: port,
|
||||
portSource: port ? '.env' : null,
|
||||
hasEnv,
|
||||
dbType: env.DB_CONNECTION || null,
|
||||
dbName,
|
||||
frontend,
|
||||
hasDocs,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { scanProjects };
|
||||
Reference in New Issue
Block a user