first pass
This commit is contained in:
147
cli/detectors/dotnet.js
Normal file
147
cli/detectors/dotnet.js
Normal file
@@ -0,0 +1,147 @@
|
||||
import { readFileSync, existsSync, readdirSync } from 'fs';
|
||||
import { join, basename } from 'path';
|
||||
import { glob } from 'glob';
|
||||
|
||||
/**
|
||||
* Detect .NET project type and configuration
|
||||
*/
|
||||
export async function detectDotNet(projectPath) {
|
||||
// Find .csproj files
|
||||
const csprojFiles = await glob('*.csproj', { cwd: projectPath });
|
||||
|
||||
if (csprojFiles.length === 0) {
|
||||
// Check for .sln file (solution)
|
||||
const slnFiles = await glob('*.sln', { cwd: projectPath });
|
||||
if (slnFiles.length > 0) {
|
||||
return {
|
||||
type: 'dotnet-solution',
|
||||
dockerizable: false,
|
||||
reason: 'Solution files require building individual projects',
|
||||
template: null
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const csprojFile = csprojFiles[0];
|
||||
const csprojPath = join(projectPath, csprojFile);
|
||||
const csprojContent = readFileSync(csprojPath, 'utf-8');
|
||||
|
||||
// Detect .NET version
|
||||
const targetFrameworkMatch = csprojContent.match(/<TargetFramework>([^<]+)<\/TargetFramework>/);
|
||||
const targetFramework = targetFrameworkMatch ? targetFrameworkMatch[1] : 'net8.0';
|
||||
|
||||
// Extract version number (e.g., net9.0 -> 9.0)
|
||||
const versionMatch = targetFramework.match(/net(\d+\.\d+)/);
|
||||
const dotnetVersion = versionMatch ? versionMatch[1] : '8.0';
|
||||
|
||||
// Detect project type
|
||||
const isBlazor = csprojContent.includes('Microsoft.AspNetCore.Components') ||
|
||||
csprojContent.includes('MudBlazor') ||
|
||||
csprojContent.includes('Blazor');
|
||||
|
||||
const isWebAPI = csprojContent.includes('Microsoft.NET.Sdk.Web') &&
|
||||
!isBlazor;
|
||||
|
||||
const isConsole = csprojContent.includes('Microsoft.NET.Sdk') &&
|
||||
!csprojContent.includes('Microsoft.NET.Sdk.Web');
|
||||
|
||||
// Get assembly name for DLL
|
||||
const assemblyNameMatch = csprojContent.match(/<AssemblyName>([^<]+)<\/AssemblyName>/);
|
||||
const projectName = basename(csprojFile, '.csproj');
|
||||
const assemblyName = assemblyNameMatch ? assemblyNameMatch[1] : projectName;
|
||||
const dllName = `${assemblyName}.dll`;
|
||||
|
||||
if (isBlazor) {
|
||||
return {
|
||||
type: 'dotnet-blazor',
|
||||
dockerizable: true,
|
||||
template: 'dotnet/blazor',
|
||||
port: 8080,
|
||||
entryPoint: dllName,
|
||||
buildCommand: `dotnet publish -c Release`,
|
||||
description: 'Blazor web application',
|
||||
csprojFile,
|
||||
dllName,
|
||||
dotnetVersion,
|
||||
targetFramework
|
||||
};
|
||||
}
|
||||
|
||||
if (isWebAPI) {
|
||||
return {
|
||||
type: 'dotnet-webapi',
|
||||
dockerizable: true,
|
||||
template: 'dotnet/webapi',
|
||||
port: 8080,
|
||||
entryPoint: dllName,
|
||||
buildCommand: `dotnet publish -c Release`,
|
||||
description: '.NET Web API',
|
||||
csprojFile,
|
||||
dllName,
|
||||
dotnetVersion,
|
||||
targetFramework
|
||||
};
|
||||
}
|
||||
|
||||
if (isConsole) {
|
||||
return {
|
||||
type: 'dotnet-console',
|
||||
dockerizable: true,
|
||||
template: 'dotnet/console',
|
||||
port: null,
|
||||
entryPoint: dllName,
|
||||
buildCommand: `dotnet publish -c Release`,
|
||||
description: '.NET Console application',
|
||||
csprojFile,
|
||||
dllName,
|
||||
dotnetVersion,
|
||||
targetFramework
|
||||
};
|
||||
}
|
||||
|
||||
// Default to web
|
||||
return {
|
||||
type: 'dotnet-generic',
|
||||
dockerizable: true,
|
||||
template: 'dotnet/webapi',
|
||||
port: 8080,
|
||||
entryPoint: dllName,
|
||||
buildCommand: `dotnet publish -c Release`,
|
||||
description: '.NET application',
|
||||
csprojFile,
|
||||
dllName,
|
||||
dotnetVersion,
|
||||
targetFramework
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get additional info about .NET project
|
||||
*/
|
||||
export async function getDotNetInfo(projectPath) {
|
||||
const csprojFiles = await glob('*.csproj', { cwd: projectPath });
|
||||
|
||||
if (csprojFiles.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const csprojFile = csprojFiles[0];
|
||||
const csprojPath = join(projectPath, csprojFile);
|
||||
const content = readFileSync(csprojPath, 'utf-8');
|
||||
|
||||
// Extract package references
|
||||
const packageRefs = content.match(/<PackageReference\s+Include="([^"]+)"/g) || [];
|
||||
const packages = packageRefs.map(ref => {
|
||||
const match = ref.match(/Include="([^"]+)"/);
|
||||
return match ? match[1] : null;
|
||||
}).filter(Boolean);
|
||||
|
||||
return {
|
||||
csprojFile,
|
||||
packages,
|
||||
hasLaunchSettings: existsSync(join(projectPath, 'Properties', 'launchSettings.json')),
|
||||
hasAppSettings: existsSync(join(projectPath, 'appsettings.json')),
|
||||
hasWwwroot: existsSync(join(projectPath, 'wwwroot'))
|
||||
};
|
||||
}
|
||||
126
cli/detectors/index.js
Normal file
126
cli/detectors/index.js
Normal file
@@ -0,0 +1,126 @@
|
||||
import { detectNodeJS, getNodeJSInfo } from './nodejs.js';
|
||||
import { detectPython, getPythonInfo } from './python.js';
|
||||
import { detectDotNet, getDotNetInfo } from './dotnet.js';
|
||||
import { detectStatic, getStaticInfo } from './static.js';
|
||||
import { basename } from 'path';
|
||||
|
||||
/**
|
||||
* Detect project type by running all detectors
|
||||
* Returns the first successful detection
|
||||
*/
|
||||
export async function detectProject(projectPath) {
|
||||
const projectName = basename(projectPath);
|
||||
|
||||
// Run detectors in order of priority
|
||||
// Node.js first (most common), then Python, .NET, and finally static
|
||||
|
||||
// 1. Node.js detection
|
||||
const nodeResult = detectNodeJS(projectPath);
|
||||
if (nodeResult) {
|
||||
return {
|
||||
...nodeResult,
|
||||
projectName,
|
||||
projectPath
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Python detection
|
||||
const pythonResult = detectPython(projectPath);
|
||||
if (pythonResult) {
|
||||
return {
|
||||
...pythonResult,
|
||||
projectName,
|
||||
projectPath
|
||||
};
|
||||
}
|
||||
|
||||
// 3. .NET detection
|
||||
const dotnetResult = await detectDotNet(projectPath);
|
||||
if (dotnetResult) {
|
||||
return {
|
||||
...dotnetResult,
|
||||
projectName,
|
||||
projectPath
|
||||
};
|
||||
}
|
||||
|
||||
// 4. Static site detection
|
||||
const staticResult = await detectStatic(projectPath);
|
||||
if (staticResult) {
|
||||
return {
|
||||
...staticResult,
|
||||
projectName,
|
||||
projectPath
|
||||
};
|
||||
}
|
||||
|
||||
// No detection
|
||||
return {
|
||||
type: 'unknown',
|
||||
dockerizable: false,
|
||||
reason: 'Could not determine project type. No package.json, requirements.txt, .csproj, or index.html found.',
|
||||
projectName,
|
||||
projectPath,
|
||||
template: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed info about a project
|
||||
*/
|
||||
export async function getProjectInfo(projectPath, type) {
|
||||
switch (true) {
|
||||
case type.startsWith('nodejs'):
|
||||
return getNodeJSInfo(projectPath);
|
||||
case type.startsWith('python'):
|
||||
return getPythonInfo(projectPath);
|
||||
case type.startsWith('dotnet'):
|
||||
return await getDotNetInfo(projectPath);
|
||||
case type.startsWith('static') || type.startsWith('flutter'):
|
||||
return await getStaticInfo(projectPath);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a path is a valid project directory
|
||||
*/
|
||||
export function isValidProject(projectPath) {
|
||||
// Exclude common non-project directories
|
||||
const excludePatterns = [
|
||||
'node_modules',
|
||||
'.git',
|
||||
'.vscode',
|
||||
'.idea',
|
||||
'dist',
|
||||
'build',
|
||||
'coverage',
|
||||
'__pycache__',
|
||||
'venv',
|
||||
'.venv'
|
||||
];
|
||||
|
||||
const name = basename(projectPath);
|
||||
return !excludePatterns.includes(name) && !name.startsWith('.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all project types supported
|
||||
*/
|
||||
export function getSupportedTypes() {
|
||||
return [
|
||||
{ type: 'nodejs-express', description: 'Express.js server', template: 'nodejs/express' },
|
||||
{ type: 'nodejs-vite-react', description: 'Vite + React SPA', template: 'nodejs/vite-react' },
|
||||
{ type: 'nodejs-vite-react-ssr', description: 'Vite + React with Express SSR', template: 'nodejs/vite-react-ssr' },
|
||||
{ type: 'nodejs-generic', description: 'Generic Node.js application', template: 'nodejs/express' },
|
||||
{ type: 'python-standard', description: 'Standard Python application', template: 'python/standard' },
|
||||
{ type: 'python-ml-pytorch', description: 'Python ML/AI with PyTorch', template: 'python/ml-pytorch' },
|
||||
{ type: 'dotnet-blazor', description: '.NET Blazor web application', template: 'dotnet/blazor' },
|
||||
{ type: 'dotnet-webapi', description: '.NET Web API', template: 'dotnet/webapi' },
|
||||
{ type: 'static-nginx', description: 'Static website with Nginx', template: 'static/nginx' },
|
||||
{ type: 'flutter-web', description: 'Flutter web application', template: 'static/nginx' }
|
||||
];
|
||||
}
|
||||
|
||||
export { detectNodeJS, detectPython, detectDotNet, detectStatic };
|
||||
210
cli/detectors/nodejs.js
Normal file
210
cli/detectors/nodejs.js
Normal file
@@ -0,0 +1,210 @@
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
/**
|
||||
* Detect Node.js project type and configuration
|
||||
*/
|
||||
export function detectNodeJS(projectPath) {
|
||||
const packageJsonPath = join(projectPath, 'package.json');
|
||||
|
||||
if (!existsSync(packageJsonPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let pkg;
|
||||
try {
|
||||
pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const deps = pkg.dependencies || {};
|
||||
const devDeps = pkg.devDependencies || {};
|
||||
const scripts = pkg.scripts || {};
|
||||
|
||||
// Check for Electron (not dockerizable)
|
||||
if (deps.electron || devDeps.electron) {
|
||||
return {
|
||||
type: 'nodejs-electron',
|
||||
dockerizable: false,
|
||||
reason: 'Electron apps are desktop applications and cannot be containerized',
|
||||
template: null
|
||||
};
|
||||
}
|
||||
|
||||
// Detect project subtype
|
||||
const hasVite = !!devDeps.vite;
|
||||
const hasReact = !!(deps.react || devDeps.react);
|
||||
const hasExpress = !!deps.express;
|
||||
const hasSocketIO = !!(deps['socket.io'] || deps['socket.io-client']);
|
||||
const hasConcurrently = !!devDeps.concurrently;
|
||||
|
||||
// Check for server-side rendering setup (Vite + React + Express with concurrent dev)
|
||||
if (hasVite && hasReact && hasExpress && (hasConcurrently || scripts.dev?.includes('concurrently'))) {
|
||||
return {
|
||||
type: 'nodejs-vite-react-ssr',
|
||||
dockerizable: true,
|
||||
template: 'nodejs/vite-react-ssr',
|
||||
port: detectPort(pkg) || 3000,
|
||||
entryPoint: detectEntryPoint(pkg, projectPath, 'ssr'),
|
||||
buildCommand: 'npm run build',
|
||||
description: 'Vite + React with Express SSR'
|
||||
};
|
||||
}
|
||||
|
||||
// Vite + React SPA (no backend)
|
||||
if (hasVite && hasReact && !hasExpress) {
|
||||
return {
|
||||
type: 'nodejs-vite-react',
|
||||
dockerizable: true,
|
||||
template: 'nodejs/vite-react',
|
||||
port: 80, // Nginx serves static files on port 80
|
||||
entryPoint: null,
|
||||
buildCommand: 'npm run build',
|
||||
description: 'Vite + React SPA (served by Nginx)'
|
||||
};
|
||||
}
|
||||
|
||||
// Express server (with or without Socket.io)
|
||||
if (hasExpress) {
|
||||
return {
|
||||
type: 'nodejs-express',
|
||||
dockerizable: true,
|
||||
template: 'nodejs/express',
|
||||
port: detectPort(pkg) || 3000,
|
||||
entryPoint: detectEntryPoint(pkg, projectPath, 'express'),
|
||||
buildCommand: detectBuildCommand(pkg),
|
||||
description: hasSocketIO ? 'Express + Socket.io server' : 'Express server'
|
||||
};
|
||||
}
|
||||
|
||||
// Generic Node.js project (has package.json but no clear type)
|
||||
if (pkg.main || scripts.start) {
|
||||
return {
|
||||
type: 'nodejs-generic',
|
||||
dockerizable: true,
|
||||
template: 'nodejs/express',
|
||||
port: detectPort(pkg) || 3000,
|
||||
entryPoint: detectEntryPoint(pkg, projectPath, 'generic'),
|
||||
buildCommand: detectBuildCommand(pkg),
|
||||
description: 'Generic Node.js application'
|
||||
};
|
||||
}
|
||||
|
||||
// Has package.json but nothing to run
|
||||
return {
|
||||
type: 'nodejs-unknown',
|
||||
dockerizable: false,
|
||||
reason: 'No start script or main entry point found',
|
||||
template: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the default port from package.json or environment
|
||||
*/
|
||||
function detectPort(pkg) {
|
||||
const scripts = pkg.scripts || {};
|
||||
|
||||
// Check start script for port
|
||||
const startScript = scripts.start || '';
|
||||
const portMatch = startScript.match(/PORT[=\s]+(\d+)/i) ||
|
||||
startScript.match(/-p\s+(\d+)/) ||
|
||||
startScript.match(/--port\s+(\d+)/);
|
||||
if (portMatch) {
|
||||
return parseInt(portMatch[1], 10);
|
||||
}
|
||||
|
||||
// Check for common port patterns in dev script
|
||||
const devScript = scripts.dev || '';
|
||||
const devPortMatch = devScript.match(/:(\d{4})/);
|
||||
if (devPortMatch) {
|
||||
return parseInt(devPortMatch[1], 10);
|
||||
}
|
||||
|
||||
// Default based on common frameworks
|
||||
if (pkg.dependencies?.['next']) return 3000;
|
||||
if (pkg.devDependencies?.vite) return 5173;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the entry point file
|
||||
*/
|
||||
function detectEntryPoint(pkg, projectPath, type) {
|
||||
// Check package.json main field
|
||||
if (pkg.main) {
|
||||
return pkg.main;
|
||||
}
|
||||
|
||||
// Check for common entry points
|
||||
const commonEntries = [
|
||||
'server.js',
|
||||
'server.mjs',
|
||||
'src/server.js',
|
||||
'src/server.mjs',
|
||||
'src/index.js',
|
||||
'src/index.mjs',
|
||||
'index.js',
|
||||
'app.js',
|
||||
'src/app.js'
|
||||
];
|
||||
|
||||
for (const entry of commonEntries) {
|
||||
if (existsSync(join(projectPath, entry))) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
// For SSR projects, look for server files
|
||||
if (type === 'ssr') {
|
||||
const ssrEntries = ['server.mjs', 'server.js', 'src/server.mjs', 'src/server.js'];
|
||||
for (const entry of ssrEntries) {
|
||||
if (existsSync(join(projectPath, entry))) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default
|
||||
return 'index.js';
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if project has a build command
|
||||
*/
|
||||
function detectBuildCommand(pkg) {
|
||||
const scripts = pkg.scripts || {};
|
||||
|
||||
if (scripts.build) {
|
||||
return 'npm run build';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get additional info about the Node.js project
|
||||
*/
|
||||
export function getNodeJSInfo(projectPath) {
|
||||
const packageJsonPath = join(projectPath, 'package.json');
|
||||
|
||||
if (!existsSync(packageJsonPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
||||
|
||||
return {
|
||||
name: pkg.name,
|
||||
version: pkg.version,
|
||||
hasLockFile: existsSync(join(projectPath, 'package-lock.json')),
|
||||
hasYarnLock: existsSync(join(projectPath, 'yarn.lock')),
|
||||
hasPnpmLock: existsSync(join(projectPath, 'pnpm-lock.yaml')),
|
||||
nodeVersion: pkg.engines?.node || null,
|
||||
scripts: Object.keys(pkg.scripts || {}),
|
||||
dependencies: Object.keys(pkg.dependencies || {}),
|
||||
devDependencies: Object.keys(pkg.devDependencies || {})
|
||||
};
|
||||
}
|
||||
186
cli/detectors/python.js
Normal file
186
cli/detectors/python.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
/**
|
||||
* Detect Python project type and configuration
|
||||
*/
|
||||
export function detectPython(projectPath) {
|
||||
const requirementsPath = join(projectPath, 'requirements.txt');
|
||||
const pyprojectPath = join(projectPath, 'pyproject.toml');
|
||||
const setupPath = join(projectPath, 'setup.py');
|
||||
|
||||
const hasRequirements = existsSync(requirementsPath);
|
||||
const hasPyproject = existsSync(pyprojectPath);
|
||||
const hasSetup = existsSync(setupPath);
|
||||
|
||||
if (!hasRequirements && !hasPyproject && !hasSetup) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read requirements to detect project type
|
||||
let requirements = '';
|
||||
if (hasRequirements) {
|
||||
requirements = readFileSync(requirementsPath, 'utf-8').toLowerCase();
|
||||
}
|
||||
|
||||
// Read pyproject.toml for additional info
|
||||
let pyproject = '';
|
||||
if (hasPyproject) {
|
||||
pyproject = readFileSync(pyprojectPath, 'utf-8').toLowerCase();
|
||||
}
|
||||
|
||||
// Detect ML/PyTorch project
|
||||
const mlIndicators = [
|
||||
'torch',
|
||||
'pytorch',
|
||||
'tensorflow',
|
||||
'keras',
|
||||
'transformers',
|
||||
'opencv',
|
||||
'faster-whisper',
|
||||
'whisper',
|
||||
'scikit-learn',
|
||||
'numpy'
|
||||
];
|
||||
|
||||
const isML = mlIndicators.some(ind => requirements.includes(ind) || pyproject.includes(ind));
|
||||
|
||||
if (isML) {
|
||||
return {
|
||||
type: 'python-ml-pytorch',
|
||||
dockerizable: true,
|
||||
template: 'python/ml-pytorch',
|
||||
port: detectPythonPort(projectPath) || 8000,
|
||||
entryPoint: detectPythonEntryPoint(projectPath),
|
||||
buildCommand: null,
|
||||
description: 'Python ML/AI application',
|
||||
systemDeps: detectSystemDeps(requirements)
|
||||
};
|
||||
}
|
||||
|
||||
// Check for web frameworks
|
||||
const hasFlask = requirements.includes('flask') || pyproject.includes('flask');
|
||||
const hasFastAPI = requirements.includes('fastapi') || pyproject.includes('fastapi');
|
||||
const hasDjango = requirements.includes('django') || pyproject.includes('django');
|
||||
|
||||
let description = 'Python application';
|
||||
let port = 8000;
|
||||
|
||||
if (hasFlask) {
|
||||
description = 'Flask web application';
|
||||
port = 5000;
|
||||
} else if (hasFastAPI) {
|
||||
description = 'FastAPI web application';
|
||||
port = 8000;
|
||||
} else if (hasDjango) {
|
||||
description = 'Django web application';
|
||||
port = 8000;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'python-standard',
|
||||
dockerizable: true,
|
||||
template: 'python/standard',
|
||||
port: detectPythonPort(projectPath) || port,
|
||||
entryPoint: detectPythonEntryPoint(projectPath),
|
||||
buildCommand: null,
|
||||
description,
|
||||
framework: hasFlask ? 'flask' : hasFastAPI ? 'fastapi' : hasDjango ? 'django' : null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect Python entry point
|
||||
*/
|
||||
function detectPythonEntryPoint(projectPath) {
|
||||
const commonEntries = [
|
||||
'main.py',
|
||||
'app.py',
|
||||
'server.py',
|
||||
'run.py',
|
||||
'src/main.py',
|
||||
'src/app.py'
|
||||
];
|
||||
|
||||
for (const entry of commonEntries) {
|
||||
if (existsSync(join(projectPath, entry))) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return 'main.py';
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect port from Python files
|
||||
*/
|
||||
function detectPythonPort(projectPath) {
|
||||
const mainFiles = ['main.py', 'app.py', 'server.py', 'run.py'];
|
||||
|
||||
for (const file of mainFiles) {
|
||||
const filePath = join(projectPath, file);
|
||||
if (existsSync(filePath)) {
|
||||
const content = readFileSync(filePath, 'utf-8');
|
||||
|
||||
// Look for port definitions
|
||||
const portMatch = content.match(/port[=\s:]+(\d{4})/i) ||
|
||||
content.match(/PORT[=\s:]+(\d{4})/i);
|
||||
if (portMatch) {
|
||||
return parseInt(portMatch[1], 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect system dependencies needed for ML projects
|
||||
*/
|
||||
function detectSystemDeps(requirements) {
|
||||
const deps = [];
|
||||
|
||||
if (requirements.includes('opencv') || requirements.includes('cv2')) {
|
||||
deps.push('libgl1-mesa-glx', 'libglib2.0-0');
|
||||
}
|
||||
|
||||
if (requirements.includes('whisper') || requirements.includes('faster-whisper')) {
|
||||
deps.push('ffmpeg');
|
||||
}
|
||||
|
||||
if (requirements.includes('soundfile') || requirements.includes('librosa')) {
|
||||
deps.push('libsndfile1');
|
||||
}
|
||||
|
||||
if (requirements.includes('pillow') || requirements.includes('pil')) {
|
||||
deps.push('libjpeg-dev', 'zlib1g-dev');
|
||||
}
|
||||
|
||||
return deps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get additional info about Python project
|
||||
*/
|
||||
export function getPythonInfo(projectPath) {
|
||||
const requirementsPath = join(projectPath, 'requirements.txt');
|
||||
const pyprojectPath = join(projectPath, 'pyproject.toml');
|
||||
|
||||
const info = {
|
||||
hasRequirements: existsSync(requirementsPath),
|
||||
hasPyproject: existsSync(pyprojectPath),
|
||||
hasSetupPy: existsSync(join(projectPath, 'setup.py')),
|
||||
hasVenv: existsSync(join(projectPath, 'venv')) || existsSync(join(projectPath, '.venv')),
|
||||
dependencies: []
|
||||
};
|
||||
|
||||
if (info.hasRequirements) {
|
||||
const content = readFileSync(requirementsPath, 'utf-8');
|
||||
info.dependencies = content
|
||||
.split('\n')
|
||||
.filter(line => line.trim() && !line.startsWith('#'))
|
||||
.map(line => line.split('==')[0].split('>=')[0].split('<=')[0].trim());
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
98
cli/detectors/static.js
Normal file
98
cli/detectors/static.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import { existsSync, readdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { glob } from 'glob';
|
||||
|
||||
/**
|
||||
* Detect static site or Flutter web project
|
||||
*/
|
||||
export async function detectStatic(projectPath) {
|
||||
// Check for Flutter project first
|
||||
const pubspecPath = join(projectPath, 'pubspec.yaml');
|
||||
if (existsSync(pubspecPath)) {
|
||||
return {
|
||||
type: 'flutter-web',
|
||||
dockerizable: true,
|
||||
template: 'static/nginx',
|
||||
port: 80,
|
||||
entryPoint: null,
|
||||
buildCommand: 'flutter build web',
|
||||
description: 'Flutter web application (builds to static files)',
|
||||
buildDir: 'build/web',
|
||||
note: 'Run "flutter build web" before Docker build'
|
||||
};
|
||||
}
|
||||
|
||||
// Check for index.html (static site)
|
||||
const indexPath = join(projectPath, 'index.html');
|
||||
const hasIndexHtml = existsSync(indexPath);
|
||||
|
||||
// Check for common static site directories
|
||||
const hasPublicDir = existsSync(join(projectPath, 'public', 'index.html'));
|
||||
const hasDistDir = existsSync(join(projectPath, 'dist', 'index.html'));
|
||||
const hasBuildDir = existsSync(join(projectPath, 'build', 'index.html'));
|
||||
|
||||
if (!hasIndexHtml && !hasPublicDir && !hasDistDir && !hasBuildDir) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine the source directory
|
||||
let sourceDir = '.';
|
||||
if (hasPublicDir) sourceDir = 'public';
|
||||
else if (hasDistDir) sourceDir = 'dist';
|
||||
else if (hasBuildDir) sourceDir = 'build';
|
||||
|
||||
// Check for PHP files (simple PHP site)
|
||||
const phpFiles = await glob('*.php', { cwd: projectPath });
|
||||
if (phpFiles.length > 0) {
|
||||
return {
|
||||
type: 'static-php',
|
||||
dockerizable: true,
|
||||
template: 'static/php',
|
||||
port: 80,
|
||||
entryPoint: null,
|
||||
buildCommand: null,
|
||||
description: 'PHP static site',
|
||||
sourceDir,
|
||||
note: 'Uses PHP-FPM with Nginx'
|
||||
};
|
||||
}
|
||||
|
||||
// Pure static site (HTML/CSS/JS)
|
||||
return {
|
||||
type: 'static-nginx',
|
||||
dockerizable: true,
|
||||
template: 'static/nginx',
|
||||
port: 80,
|
||||
entryPoint: null,
|
||||
buildCommand: null,
|
||||
description: 'Static website (served by Nginx)',
|
||||
sourceDir
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get additional info about static site
|
||||
*/
|
||||
export async function getStaticInfo(projectPath) {
|
||||
const info = {
|
||||
hasIndexHtml: existsSync(join(projectPath, 'index.html')),
|
||||
hasPackageJson: existsSync(join(projectPath, 'package.json')),
|
||||
files: {
|
||||
html: (await glob('**/*.html', { cwd: projectPath, ignore: 'node_modules/**' })).length,
|
||||
css: (await glob('**/*.css', { cwd: projectPath, ignore: 'node_modules/**' })).length,
|
||||
js: (await glob('**/*.js', { cwd: projectPath, ignore: 'node_modules/**' })).length,
|
||||
php: (await glob('**/*.php', { cwd: projectPath, ignore: 'node_modules/**' })).length
|
||||
},
|
||||
directories: []
|
||||
};
|
||||
|
||||
// Check for common directories
|
||||
const dirs = ['public', 'dist', 'build', 'assets', 'css', 'js', 'images'];
|
||||
for (const dir of dirs) {
|
||||
if (existsSync(join(projectPath, dir))) {
|
||||
info.directories.push(dir);
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
Reference in New Issue
Block a user