init project
This commit is contained in:
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { config } from '../config.js';
|
||||
import { resolveWorkspacePath, SandboxViolationError } from './sandbox.js';
|
||||
const BASELINES_DIR = path.join('.web-dev-mcp', 'baselines');
|
||||
function sanitizeBaselineName(name) {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) {
|
||||
throw new SandboxViolationError('Baseline name must be a non-empty string.');
|
||||
}
|
||||
if (trimmed.includes('..') || trimmed.includes('/') || trimmed.includes('\\') || trimmed.includes(':')) {
|
||||
throw new SandboxViolationError(`Invalid baseline name "${name}": use a simple name without path separators or "..".`);
|
||||
}
|
||||
return trimmed.endsWith('.png') ? trimmed : `${trimmed}.png`;
|
||||
}
|
||||
export function resolveBaselinePath(name) {
|
||||
const fileName = sanitizeBaselineName(name);
|
||||
const relative = path.join(BASELINES_DIR, fileName);
|
||||
return resolveWorkspacePath(config.workspaceRoot, relative);
|
||||
}
|
||||
export function ensureBaselinesDir() {
|
||||
const dir = resolveWorkspacePath(config.workspaceRoot, BASELINES_DIR);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
return dir;
|
||||
}
|
||||
export function writeBaseline(name, png) {
|
||||
ensureBaselinesDir();
|
||||
const absolutePath = resolveBaselinePath(name);
|
||||
fs.writeFileSync(absolutePath, png);
|
||||
return {
|
||||
absolutePath,
|
||||
relativePath: path.relative(config.workspaceRoot, absolutePath),
|
||||
};
|
||||
}
|
||||
export function readBaseline(name) {
|
||||
const absolutePath = resolveBaselinePath(name);
|
||||
if (!fs.existsSync(absolutePath)) {
|
||||
throw new Error(`Baseline not found at "${path.relative(config.workspaceRoot, absolutePath)}". ` +
|
||||
'Capture one first with browser_screenshot_baseline.');
|
||||
}
|
||||
return fs.readFileSync(absolutePath);
|
||||
}
|
||||
export function listBaselines() {
|
||||
const dir = resolveWorkspacePath(config.workspaceRoot, BASELINES_DIR);
|
||||
if (!fs.existsSync(dir))
|
||||
return [];
|
||||
return fs
|
||||
.readdirSync(dir)
|
||||
.filter((entry) => entry.toLowerCase().endsWith('.png'))
|
||||
.sort();
|
||||
}
|
||||
//# sourceMappingURL=baselines.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"baselines.js","sourceRoot":"","sources":["../../src/lib/baselines.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
import { PhpFrameworkAdapter } from './phpBase.js';
|
||||
export class CodeIgniterAdapter extends PhpFrameworkAdapter {
|
||||
kind = 'codeigniter';
|
||||
label = 'CodeIgniter 4';
|
||||
devCommand() {
|
||||
return { command: 'php', args: ['spark', 'serve'], cwd: this.root };
|
||||
}
|
||||
buildCommand() {
|
||||
// CodeIgniter has no dedicated production-build step beyond a lean install;
|
||||
// callers that need config/route caching should extend this via `spark`.
|
||||
return { command: 'composer', args: ['install', '--no-dev', '--optimize-autoloader'], cwd: this.root };
|
||||
}
|
||||
readyPattern() {
|
||||
// `php spark serve` prints: "CodeIgniter development server started on http://localhost:8080"
|
||||
return /CodeIgniter development server started on (https?:\/\/\S+)/i;
|
||||
}
|
||||
describe() {
|
||||
return {
|
||||
kind: this.kind,
|
||||
label: this.label,
|
||||
root: this.root,
|
||||
packageManager: this.packageManager,
|
||||
codeigniterVersion: this.composerJson?.require?.['codeigniter4/framework'] ?? null,
|
||||
devUrl: 'http://localhost:8080',
|
||||
};
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=codeigniter.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"codeigniter.js","sourceRoot":"","sources":["../../../src/lib/frameworks/codeigniter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,MAAM,OAAO,kBAAmB,SAAQ,mBAAmB;IAChD,IAAI,GAAG,aAAsB,CAAC;IAC9B,KAAK,GAAG,eAAe,CAAC;IAEjC,UAAU;QACR,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IACtE,CAAC;IAED,YAAY;QACV,4EAA4E;QAC5E,yEAAyE;QACzE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,uBAAuB,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IACzG,CAAC;IAED,YAAY;QACV,8FAA8F;QAC9F,OAAO,6DAA6D,CAAC;IACvE,CAAC;IAED,QAAQ;QACN,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,kBAAkB,EAAE,IAAI,CAAC,YAAY,EAAE,OAAO,EAAE,CAAC,wBAAwB,CAAC,IAAI,IAAI;YAClF,MAAM,EAAE,uBAAuB;SAChC,CAAC;IACJ,CAAC;CACF"}
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
export function readComposerJson(root) {
|
||||
const file = path.join(root, 'composer.json');
|
||||
if (!fs.existsSync(file))
|
||||
return null;
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
export function hasComposerDependency(pkg, name) {
|
||||
if (!pkg)
|
||||
return false;
|
||||
return Boolean(pkg.require?.[name] || pkg['require-dev']?.[name]);
|
||||
}
|
||||
export function vendorBinExists(root, bin) {
|
||||
const suffix = process.platform === 'win32' ? '.bat' : '';
|
||||
return fs.existsSync(path.join(root, 'vendor', 'bin', `${bin}${suffix}`)) || fs.existsSync(path.join(root, 'vendor', 'bin', bin));
|
||||
}
|
||||
//# sourceMappingURL=composer.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"composer.js","sourceRoot":"","sources":["../../../src/lib/frameworks/composer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAS7B,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;IAC9C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAiB,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAwB,EAAE,IAAY;IAC1E,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,OAAO,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,GAAW;IACvD,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1D,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;AACpI,CAAC"}
|
||||
Vendored
+259
@@ -0,0 +1,259 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { readComposerJson, hasComposerDependency } from './composer.js';
|
||||
import { hasDependency, readPackageJson } from './packageManager.js';
|
||||
import { LaravelAdapter } from './laravel.js';
|
||||
import { CodeIgniterAdapter } from './codeigniter.js';
|
||||
import { ReactAdapter } from './react.js';
|
||||
import { VueAdapter, NuxtAdapter } from './vue.js';
|
||||
import { DjangoAdapter, RailsAdapter, SymfonyAdapter } from './djangoRailsSymfony.js';
|
||||
import { GenericAdapter } from './generic.js';
|
||||
const FRONTEND_SUBDIRS = ['frontend', 'client', 'web', 'resources/js-app'];
|
||||
const MONOREPO_GLOBS = ['apps', 'packages', 'services', 'libs'];
|
||||
function fileExists(root, names) {
|
||||
return names.some((name) => fs.existsSync(path.join(root, name)));
|
||||
}
|
||||
function hasVueSfc(root) {
|
||||
const src = path.join(root, 'src');
|
||||
if (!fs.existsSync(src))
|
||||
return false;
|
||||
try {
|
||||
const stack = [src];
|
||||
let checked = 0;
|
||||
while (stack.length > 0 && checked < 200) {
|
||||
const dir = stack.pop();
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
checked += 1;
|
||||
if (entry.isDirectory() && entry.name !== 'node_modules') {
|
||||
stack.push(path.join(dir, entry.name));
|
||||
}
|
||||
else if (entry.isFile() && entry.name.endsWith('.vue')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function detectPhpOrSymfonyBackend(root) {
|
||||
const composerJson = readComposerJson(root);
|
||||
if (!composerJson)
|
||||
return null;
|
||||
const hasArtisan = fs.existsSync(path.join(root, 'artisan'));
|
||||
const hasSpark = fs.existsSync(path.join(root, 'spark'));
|
||||
if (hasComposerDependency(composerJson, 'laravel/framework') || hasArtisan) {
|
||||
return new LaravelAdapter(root);
|
||||
}
|
||||
if (hasComposerDependency(composerJson, 'codeigniter4/framework') || hasSpark) {
|
||||
return new CodeIgniterAdapter(root);
|
||||
}
|
||||
if (hasComposerDependency(composerJson, 'symfony/framework-bundle') ||
|
||||
hasComposerDependency(composerJson, 'symfony/symfony') ||
|
||||
fileExists(root, ['bin/console', 'symfony.lock'])) {
|
||||
return new SymfonyAdapter(root);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function detectDjangoAt(root) {
|
||||
if (fileExists(root, ['manage.py']))
|
||||
return new DjangoAdapter(root);
|
||||
for (const name of ['requirements.txt', 'requirements-dev.txt', 'pyproject.toml']) {
|
||||
const file = path.join(root, name);
|
||||
if (!fs.existsSync(file))
|
||||
continue;
|
||||
const text = fs.readFileSync(file, 'utf8').toLowerCase();
|
||||
if (/\bdjango\b/.test(text))
|
||||
return new DjangoAdapter(root);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function detectRailsAt(root) {
|
||||
const gemfile = path.join(root, 'Gemfile');
|
||||
if (fs.existsSync(gemfile)) {
|
||||
const text = fs.readFileSync(gemfile, 'utf8').toLowerCase();
|
||||
if (/\brails\b/.test(text) || fileExists(root, ['bin/rails', 'config/application.rb'])) {
|
||||
return new RailsAdapter(root);
|
||||
}
|
||||
}
|
||||
if (fileExists(root, ['bin/rails', 'config/application.rb'])) {
|
||||
return new RailsAdapter(root);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function detectBackend(root) {
|
||||
return detectPhpOrSymfonyBackend(root) ?? detectDjangoAt(root) ?? detectRailsAt(root);
|
||||
}
|
||||
function detectFrontendAt(root) {
|
||||
const pkg = readPackageJson(root);
|
||||
if (hasDependency(pkg, 'nuxt') ||
|
||||
fileExists(root, ['nuxt.config.js', 'nuxt.config.ts', 'nuxt.config.mjs'])) {
|
||||
return new NuxtAdapter(root);
|
||||
}
|
||||
if (hasDependency(pkg, 'vue') ||
|
||||
(hasVueSfc(root) && fileExists(root, ['vite.config.js', 'vite.config.ts', 'vite.config.mjs']))) {
|
||||
return new VueAdapter(root);
|
||||
}
|
||||
if (hasDependency(pkg, 'react'))
|
||||
return new ReactAdapter(root);
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Inspects `workspaceRoot` and returns the detected backend/frontend
|
||||
* adapter(s). Supports PHP (Laravel/CI/Symfony), Django, Rails backends and
|
||||
* React/Vue/Nuxt frontends, including sibling frontend folders.
|
||||
*/
|
||||
export function detectProject(workspaceRoot) {
|
||||
const backend = detectBackend(workspaceRoot);
|
||||
let frontend = detectFrontendAt(workspaceRoot);
|
||||
if (backend && !frontend) {
|
||||
for (const subdir of FRONTEND_SUBDIRS) {
|
||||
const candidateRoot = path.join(workspaceRoot, subdir);
|
||||
if (!fs.existsSync(candidateRoot))
|
||||
continue;
|
||||
const candidate = detectFrontendAt(candidateRoot);
|
||||
if (candidate) {
|
||||
frontend = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Pure frontend-only Vue/Nuxt/React at root without backend.
|
||||
if (!backend && !frontend) {
|
||||
frontend = detectFrontendAt(workspaceRoot);
|
||||
}
|
||||
if (backend) {
|
||||
return { backend, frontend, primary: backend };
|
||||
}
|
||||
if (frontend) {
|
||||
return { backend: null, frontend, primary: frontend };
|
||||
}
|
||||
const generic = new GenericAdapter(workspaceRoot);
|
||||
return { backend: null, frontend: null, primary: generic };
|
||||
}
|
||||
export function summarizeDetection(detected) {
|
||||
return {
|
||||
backend: detected.backend?.describe() ?? null,
|
||||
frontend: detected.frontend?.describe() ?? null,
|
||||
primary: detected.primary.kind,
|
||||
combined: Boolean(detected.backend && detected.frontend),
|
||||
};
|
||||
}
|
||||
function readWorkspaceGlobs(root) {
|
||||
const globs = new Set([...MONOREPO_GLOBS, ...FRONTEND_SUBDIRS.map((d) => d.split('/')[0]).filter(Boolean)]);
|
||||
const pkg = readPackageJson(root);
|
||||
const workspaces = pkg?.workspaces;
|
||||
if (Array.isArray(workspaces)) {
|
||||
for (const entry of workspaces) {
|
||||
if (typeof entry === 'string' && entry.includes('/*')) {
|
||||
globs.add(entry.replace(/\/\*$/, ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (workspaces && typeof workspaces === 'object' && Array.isArray(workspaces.packages)) {
|
||||
for (const entry of workspaces.packages) {
|
||||
if (entry.includes('/*'))
|
||||
globs.add(entry.replace(/\/\*$/, ''));
|
||||
}
|
||||
}
|
||||
const pnpm = path.join(root, 'pnpm-workspace.yaml');
|
||||
if (fs.existsSync(pnpm)) {
|
||||
const text = fs.readFileSync(pnpm, 'utf8');
|
||||
for (const match of text.matchAll(/['"]?([A-Za-z0-9_-]+)\/\*['"]?/g)) {
|
||||
if (match[1])
|
||||
globs.add(match[1]);
|
||||
}
|
||||
}
|
||||
return [...globs];
|
||||
}
|
||||
function listImmediateSubdirs(dir) {
|
||||
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory())
|
||||
return [];
|
||||
return fs
|
||||
.readdirSync(dir, { withFileTypes: true })
|
||||
.filter((entry) => entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules' && entry.name !== 'vendor')
|
||||
.map((entry) => entry.name);
|
||||
}
|
||||
function packageLooksLikeProject(dir) {
|
||||
return (fs.existsSync(path.join(dir, 'package.json')) ||
|
||||
fs.existsSync(path.join(dir, 'composer.json')) ||
|
||||
fs.existsSync(path.join(dir, 'artisan')) ||
|
||||
fs.existsSync(path.join(dir, 'spark')) ||
|
||||
fs.existsSync(path.join(dir, 'manage.py')) ||
|
||||
fs.existsSync(path.join(dir, 'Gemfile')) ||
|
||||
fs.existsSync(path.join(dir, 'bin', 'console')));
|
||||
}
|
||||
/**
|
||||
* Scan the workspace for monorepo packages under apps/*, packages/*, services/*,
|
||||
* frontend/client/web, plus the workspace root itself.
|
||||
*/
|
||||
export function detectWorkspace(workspaceRoot) {
|
||||
const absoluteRoot = path.resolve(workspaceRoot);
|
||||
const packages = [];
|
||||
const seen = new Set();
|
||||
const addPackage = (name, absolutePath) => {
|
||||
const normalized = path.resolve(absolutePath);
|
||||
if (seen.has(normalized.toLowerCase()))
|
||||
return;
|
||||
if (!packageLooksLikeProject(normalized) && normalized !== absoluteRoot)
|
||||
return;
|
||||
seen.add(normalized.toLowerCase());
|
||||
const relativePath = path.relative(absoluteRoot, normalized) || '.';
|
||||
packages.push({
|
||||
name,
|
||||
relativePath: relativePath === '' ? '.' : relativePath.replace(/\\/g, '/'),
|
||||
absolutePath: normalized,
|
||||
detected: detectProject(normalized),
|
||||
});
|
||||
};
|
||||
addPackage(path.basename(absoluteRoot) || 'root', absoluteRoot);
|
||||
for (const globRoot of readWorkspaceGlobs(absoluteRoot)) {
|
||||
const base = path.join(absoluteRoot, globRoot);
|
||||
for (const child of listImmediateSubdirs(base)) {
|
||||
addPackage(child, path.join(base, child));
|
||||
}
|
||||
if (FRONTEND_SUBDIRS.some((d) => d === globRoot || d.startsWith(`${globRoot}/`)) && packageLooksLikeProject(base)) {
|
||||
addPackage(globRoot, base);
|
||||
}
|
||||
}
|
||||
return { root: absoluteRoot, packages };
|
||||
}
|
||||
export function summarizeWorkspace(workspace) {
|
||||
return {
|
||||
root: workspace.root,
|
||||
packageCount: workspace.packages.length,
|
||||
packages: workspace.packages.map((pkg) => ({
|
||||
name: pkg.name,
|
||||
path: pkg.relativePath,
|
||||
primary: pkg.detected.primary.kind,
|
||||
combined: Boolean(pkg.detected.backend && pkg.detected.frontend),
|
||||
backend: pkg.detected.backend?.kind ?? null,
|
||||
frontend: pkg.detected.frontend?.kind ?? null,
|
||||
})),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Resolve a package name or relative path to a DetectedPackage inside the workspace.
|
||||
*/
|
||||
export function resolvePackage(workspaceRoot, packageNameOrPath) {
|
||||
const workspace = detectWorkspace(workspaceRoot);
|
||||
if (!packageNameOrPath) {
|
||||
const rootPkg = workspace.packages.find((p) => p.relativePath === '.') ?? workspace.packages[0];
|
||||
if (!rootPkg)
|
||||
throw new Error('No packages detected in the workspace.');
|
||||
return { workspace, pkg: rootPkg };
|
||||
}
|
||||
const needle = packageNameOrPath.replace(/\\/g, '/').replace(/^\.\//, '');
|
||||
const match = workspace.packages.find((p) => p.name === packageNameOrPath ||
|
||||
p.relativePath === needle ||
|
||||
p.relativePath === packageNameOrPath ||
|
||||
p.absolutePath.replace(/\\/g, '/').endsWith(`/${needle}`));
|
||||
if (!match) {
|
||||
const available = workspace.packages.map((p) => `${p.name} (${p.relativePath})`).join(', ');
|
||||
throw new Error(`Unknown package "${packageNameOrPath}". Available: ${available || '(none)'}.`);
|
||||
}
|
||||
return { workspace, pkg: match };
|
||||
}
|
||||
//# sourceMappingURL=detect.js.map
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
+184
@@ -0,0 +1,184 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
function fileExists(root, names) {
|
||||
return names.some((name) => fs.existsSync(path.join(root, name)));
|
||||
}
|
||||
function readRequirements(root) {
|
||||
for (const name of ['requirements.txt', 'requirements-dev.txt', 'pyproject.toml']) {
|
||||
const file = path.join(root, name);
|
||||
if (fs.existsSync(file))
|
||||
return fs.readFileSync(file, 'utf8').toLowerCase();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
/**
|
||||
* Django detection stub: install/dev/test via manage.py / common Python tooling.
|
||||
* Deeper generators land in a later phase.
|
||||
*/
|
||||
export class DjangoAdapter {
|
||||
root;
|
||||
kind = 'django';
|
||||
label = 'Django';
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
}
|
||||
installCommand() {
|
||||
if (fileExists(this.root, ['pyproject.toml']) && fileExists(this.root, ['uv.lock'])) {
|
||||
return { command: 'uv', args: ['sync'], cwd: this.root };
|
||||
}
|
||||
if (fileExists(this.root, ['poetry.lock', 'pyproject.toml'])) {
|
||||
return { command: 'poetry', args: ['install'], cwd: this.root };
|
||||
}
|
||||
if (fileExists(this.root, ['requirements.txt'])) {
|
||||
return { command: 'pip', args: ['install', '-r', 'requirements.txt'], cwd: this.root };
|
||||
}
|
||||
throw new Error(`No Django dependency file found at "${this.root}" (expected requirements.txt, poetry.lock, or uv.lock).`);
|
||||
}
|
||||
devCommand() {
|
||||
if (!fileExists(this.root, ['manage.py'])) {
|
||||
throw new Error(`No manage.py found at "${this.root}".`);
|
||||
}
|
||||
return { command: 'python', args: ['manage.py', 'runserver', '127.0.0.1:8000'], cwd: this.root };
|
||||
}
|
||||
buildCommand() {
|
||||
if (!fileExists(this.root, ['manage.py']))
|
||||
return null;
|
||||
return { command: 'python', args: ['manage.py', 'collectstatic', '--noinput'], cwd: this.root };
|
||||
}
|
||||
testCommand(extraArgs = []) {
|
||||
if (!fileExists(this.root, ['manage.py']))
|
||||
return null;
|
||||
return { command: 'python', args: ['manage.py', 'test', ...extraArgs], cwd: this.root };
|
||||
}
|
||||
lintCommand() {
|
||||
return null;
|
||||
}
|
||||
readyPattern() {
|
||||
return /Starting development server at (https?:\/\/\S+)/i;
|
||||
}
|
||||
describe() {
|
||||
const req = readRequirements(this.root);
|
||||
return {
|
||||
kind: this.kind,
|
||||
label: this.label,
|
||||
root: this.root,
|
||||
hasManagePy: fileExists(this.root, ['manage.py']),
|
||||
notes: 'install/dev/test + generate_*_resource stubs available.',
|
||||
hintsDjango: /django/.test(req),
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Rails detection stub: bundle install + bin/rails server / test.
|
||||
*/
|
||||
export class RailsAdapter {
|
||||
root;
|
||||
kind = 'rails';
|
||||
label = 'Rails';
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
}
|
||||
installCommand() {
|
||||
return { command: 'bundle', args: ['install'], cwd: this.root };
|
||||
}
|
||||
devCommand() {
|
||||
const binRails = path.join(this.root, 'bin', 'rails');
|
||||
if (fs.existsSync(binRails)) {
|
||||
return { command: 'ruby', args: [binRails, 'server', '-b', '127.0.0.1', '-p', '3000'], cwd: this.root };
|
||||
}
|
||||
return { command: 'rails', args: ['server', '-b', '127.0.0.1', '-p', '3000'], cwd: this.root };
|
||||
}
|
||||
buildCommand() {
|
||||
const binRails = path.join(this.root, 'bin', 'rails');
|
||||
if (fs.existsSync(binRails)) {
|
||||
return { command: 'ruby', args: [binRails, 'assets:precompile'], cwd: this.root };
|
||||
}
|
||||
return { command: 'rails', args: ['assets:precompile'], cwd: this.root };
|
||||
}
|
||||
testCommand(extraArgs = []) {
|
||||
const binRails = path.join(this.root, 'bin', 'rails');
|
||||
if (fs.existsSync(binRails)) {
|
||||
return { command: 'ruby', args: [binRails, 'test', ...extraArgs], cwd: this.root };
|
||||
}
|
||||
return { command: 'rails', args: ['test', ...extraArgs], cwd: this.root };
|
||||
}
|
||||
lintCommand() {
|
||||
return null;
|
||||
}
|
||||
readyPattern() {
|
||||
return /Listening on (https?:\/\/\S+)|Puma starting/i;
|
||||
}
|
||||
describe() {
|
||||
return {
|
||||
kind: this.kind,
|
||||
label: this.label,
|
||||
root: this.root,
|
||||
hasBinRails: fs.existsSync(path.join(this.root, 'bin', 'rails')),
|
||||
notes: 'install/dev/test + generate_*_resource stubs available.',
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Symfony detection stub: composer + symfony CLI / php -S patterns via composer scripts when present.
|
||||
*/
|
||||
export class SymfonyAdapter {
|
||||
root;
|
||||
kind = 'symfony';
|
||||
label = 'Symfony';
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
}
|
||||
installCommand() {
|
||||
return { command: 'composer', args: ['install'], cwd: this.root };
|
||||
}
|
||||
devCommand() {
|
||||
// Prefer composer script when present; otherwise PHP built-in server on public/
|
||||
const composerPath = path.join(this.root, 'composer.json');
|
||||
if (fs.existsSync(composerPath)) {
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(composerPath, 'utf8'));
|
||||
if (pkg.scripts?.['serve'] || pkg.scripts?.dev) {
|
||||
const script = pkg.scripts.dev ? 'dev' : 'serve';
|
||||
return { command: 'composer', args: ['run', script], cwd: this.root };
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
return {
|
||||
command: 'php',
|
||||
args: ['-S', '127.0.0.1:8000', '-t', 'public'],
|
||||
cwd: this.root,
|
||||
};
|
||||
}
|
||||
buildCommand() {
|
||||
return { command: 'composer', args: ['install', '--no-dev', '--optimize-autoloader'], cwd: this.root };
|
||||
}
|
||||
testCommand(extraArgs = []) {
|
||||
const phpunit = path.join(this.root, 'bin', 'phpunit');
|
||||
if (fs.existsSync(phpunit) || fs.existsSync(`${phpunit}.bat`)) {
|
||||
return { command: 'php', args: ['bin/phpunit', ...extraArgs], cwd: this.root };
|
||||
}
|
||||
const vendor = path.join(this.root, 'vendor', 'bin', 'phpunit');
|
||||
if (fs.existsSync(vendor) || fs.existsSync(`${vendor}.bat`)) {
|
||||
return { command: 'php', args: [path.join('vendor', 'bin', 'phpunit'), ...extraArgs], cwd: this.root };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
lintCommand() {
|
||||
return null;
|
||||
}
|
||||
readyPattern() {
|
||||
return /(?:Development Server|started|listening).*(https?:\/\/\S+)/i;
|
||||
}
|
||||
describe() {
|
||||
return {
|
||||
kind: this.kind,
|
||||
label: this.label,
|
||||
root: this.root,
|
||||
notes: 'install/dev/test + generate_*_resource stubs available.',
|
||||
};
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=djangoRailsSymfony.js.map
|
||||
File diff suppressed because one or more lines are too long
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
import { detectJsPackageManager, installCommandFor, readPackageJson, runScriptCommandFor, } from './packageManager.js';
|
||||
/**
|
||||
* Fallback adapter for any project that isn't recognized as Laravel,
|
||||
* CodeIgniter, or React. Drives everything off `package.json` scripts when
|
||||
* one exists; otherwise tool calls fail with a clear, actionable message
|
||||
* instead of guessing.
|
||||
*/
|
||||
export class GenericAdapter {
|
||||
root;
|
||||
kind = 'generic';
|
||||
label;
|
||||
packageManager;
|
||||
pkg;
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this.pkg = readPackageJson(root);
|
||||
this.packageManager = this.pkg ? detectJsPackageManager(root) : undefined;
|
||||
this.label = this.pkg ? `Generic Node project (${this.packageManager})` : 'Unrecognized project';
|
||||
}
|
||||
installCommand() {
|
||||
if (!this.packageManager) {
|
||||
throw new Error(`No package.json found at "${this.root}" and no recognized framework detected. ` +
|
||||
`Nothing for run_install to do here.`);
|
||||
}
|
||||
const { command, args } = installCommandFor(this.packageManager);
|
||||
return { command, args, cwd: this.root };
|
||||
}
|
||||
devCommand() {
|
||||
return this.scriptCommand(['dev', 'start', 'serve']);
|
||||
}
|
||||
buildCommand() {
|
||||
return this.scriptCommandOrNull(['build']);
|
||||
}
|
||||
testCommand(extraArgs = []) {
|
||||
return this.scriptCommandOrNull(['test'], extraArgs);
|
||||
}
|
||||
lintCommand(fix = false) {
|
||||
return this.scriptCommandOrNull(fix ? ['format', 'lint:fix'] : ['lint']);
|
||||
}
|
||||
readyPattern() {
|
||||
return /(https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)[:\d]*\S*)|ready|listening/i;
|
||||
}
|
||||
describe() {
|
||||
return {
|
||||
kind: this.kind,
|
||||
label: this.label,
|
||||
root: this.root,
|
||||
packageManager: this.packageManager ?? null,
|
||||
scripts: this.pkg?.scripts ?? {},
|
||||
};
|
||||
}
|
||||
scriptCommand(candidates) {
|
||||
const result = this.scriptCommandOrNull(candidates);
|
||||
if (!result) {
|
||||
throw new Error(`No package.json script found for any of [${candidates.join(', ')}] in "${this.root}". ` +
|
||||
`Add one of these scripts, or use run_script with an explicit script name.`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
scriptCommandOrNull(candidates, extraArgs = []) {
|
||||
if (!this.pkg?.scripts || !this.packageManager)
|
||||
return null;
|
||||
const script = candidates.find((name) => this.pkg?.scripts?.[name]);
|
||||
if (!script)
|
||||
return null;
|
||||
const { command, args } = runScriptCommandFor(this.packageManager, script, extraArgs);
|
||||
return { command, args, cwd: this.root };
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=generic.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"generic.js","sourceRoot":"","sources":["../../../src/lib/frameworks/generic.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+75
@@ -0,0 +1,75 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { hasComposerDependency, readComposerJson } from './composer.js';
|
||||
import { PhpFrameworkAdapter } from './phpBase.js';
|
||||
const DEV_HOST = '127.0.0.1';
|
||||
const DEV_PORT = '8000';
|
||||
function composeFileExists(root) {
|
||||
return ['docker-compose.yml', 'docker-compose.yaml', 'compose.yml', 'compose.yaml'].some((name) => fs.existsSync(path.join(root, name)));
|
||||
}
|
||||
function sailScriptPath(root) {
|
||||
const candidates = [
|
||||
path.join(root, 'vendor', 'bin', 'sail'),
|
||||
path.join(root, 'vendor', 'bin', 'sail.bat'),
|
||||
];
|
||||
return candidates.find((file) => fs.existsSync(file)) ?? null;
|
||||
}
|
||||
/** True when laravel/sail is required and a compose file is present. */
|
||||
export function laravelUsesSail(root) {
|
||||
return Boolean(hasComposerDependency(readComposerJson(root), 'laravel/sail') && composeFileExists(root));
|
||||
}
|
||||
export class LaravelAdapter extends PhpFrameworkAdapter {
|
||||
kind = 'laravel';
|
||||
label = 'Laravel';
|
||||
get usesSail() {
|
||||
return hasComposerDependency(this.composerJson, 'laravel/sail') && composeFileExists(this.root);
|
||||
}
|
||||
devCommand() {
|
||||
if (this.usesSail) {
|
||||
const sail = sailScriptPath(this.root);
|
||||
if (sail) {
|
||||
// Sail is a bash script; invoke via bash so Windows+Git Bash/WSL and Unix share one path.
|
||||
return {
|
||||
command: 'bash',
|
||||
args: [sail.replace(/\\/g, '/'), 'up'],
|
||||
cwd: this.root,
|
||||
};
|
||||
}
|
||||
return {
|
||||
command: 'docker',
|
||||
args: ['compose', 'up'],
|
||||
cwd: this.root,
|
||||
};
|
||||
}
|
||||
return {
|
||||
command: 'php',
|
||||
args: ['artisan', 'serve', `--host=${DEV_HOST}`, `--port=${DEV_PORT}`],
|
||||
cwd: this.root,
|
||||
};
|
||||
}
|
||||
readyPattern() {
|
||||
if (this.usesSail) {
|
||||
return /(?:Container|Service).+(?:Started|Healthy|Running)|Listening on (https?:\/\/\S+)|APP_URL[=:].*(https?:\/\/\S+)|Local:\s+(https?:\/\/\S+)|Server running on \[?(https?:\/\/[^\s\]]+)/i;
|
||||
}
|
||||
// `php artisan serve` prints: "INFO Server running on [http://127.0.0.1:8000]."
|
||||
return /Server running on \[?(https?:\/\/[^\s\]]+)/i;
|
||||
}
|
||||
describe() {
|
||||
const sail = this.usesSail;
|
||||
return {
|
||||
kind: this.kind,
|
||||
label: this.label,
|
||||
root: this.root,
|
||||
packageManager: this.packageManager,
|
||||
laravelVersion: this.composerJson?.require?.['laravel/framework'] ?? null,
|
||||
usesPest: Boolean(this.composerJson?.['require-dev']?.['pestphp/pest']),
|
||||
usesSail: sail,
|
||||
sailScript: sail ? sailScriptPath(this.root) : null,
|
||||
devUrl: sail ? 'http://localhost' : `http://${DEV_HOST}:${DEV_PORT}`,
|
||||
notes: sail
|
||||
? 'Sail detected: start_dev_server uses `vendor/bin/sail up` (or docker compose up).'
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=laravel.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"laravel.js","sourceRoot":"","sources":["../../../src/lib/frameworks/laravel.ts"],"names":[],"mappings":"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"}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
export function detectJsPackageManager(root) {
|
||||
if (fs.existsSync(path.join(root, 'pnpm-lock.yaml')))
|
||||
return 'pnpm';
|
||||
if (fs.existsSync(path.join(root, 'yarn.lock')))
|
||||
return 'yarn';
|
||||
if (fs.existsSync(path.join(root, 'bun.lockb')) || fs.existsSync(path.join(root, 'bun.lock')))
|
||||
return 'bun';
|
||||
return 'npm';
|
||||
}
|
||||
export function installCommandFor(pm) {
|
||||
switch (pm) {
|
||||
case 'pnpm':
|
||||
return { command: 'pnpm', args: ['install'] };
|
||||
case 'yarn':
|
||||
return { command: 'yarn', args: ['install'] };
|
||||
case 'bun':
|
||||
return { command: 'bun', args: ['install'] };
|
||||
case 'npm':
|
||||
default:
|
||||
return { command: 'npm', args: ['install'] };
|
||||
}
|
||||
}
|
||||
export function runScriptCommandFor(pm, script, extraArgs = []) {
|
||||
switch (pm) {
|
||||
case 'pnpm':
|
||||
return { command: 'pnpm', args: ['run', script, ...extraArgs] };
|
||||
case 'yarn':
|
||||
return { command: 'yarn', args: [script, ...extraArgs] };
|
||||
case 'bun':
|
||||
return { command: 'bun', args: ['run', script, ...extraArgs] };
|
||||
case 'npm':
|
||||
default:
|
||||
return { command: 'npm', args: ['run', script, ...(extraArgs.length ? ['--', ...extraArgs] : [])] };
|
||||
}
|
||||
}
|
||||
export function addDependencyCommandFor(pm, packages, options) {
|
||||
if (options.remove) {
|
||||
switch (pm) {
|
||||
case 'pnpm':
|
||||
return { command: 'pnpm', args: ['remove', ...packages] };
|
||||
case 'yarn':
|
||||
return { command: 'yarn', args: ['remove', ...packages] };
|
||||
case 'bun':
|
||||
return { command: 'bun', args: ['remove', ...packages] };
|
||||
case 'npm':
|
||||
default:
|
||||
return { command: 'npm', args: ['uninstall', ...packages] };
|
||||
}
|
||||
}
|
||||
switch (pm) {
|
||||
case 'pnpm':
|
||||
return { command: 'pnpm', args: ['add', ...(options.dev ? ['-D'] : []), ...packages] };
|
||||
case 'yarn':
|
||||
return { command: 'yarn', args: ['add', ...(options.dev ? ['-D'] : []), ...packages] };
|
||||
case 'bun':
|
||||
return { command: 'bun', args: ['add', ...(options.dev ? ['-d'] : []), ...packages] };
|
||||
case 'npm':
|
||||
default:
|
||||
return { command: 'npm', args: ['install', ...(options.dev ? ['-D'] : []), ...packages] };
|
||||
}
|
||||
}
|
||||
export function readPackageJson(root) {
|
||||
const file = path.join(root, 'package.json');
|
||||
if (!fs.existsSync(file))
|
||||
return null;
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
export function hasDependency(pkg, name) {
|
||||
if (!pkg)
|
||||
return false;
|
||||
return Boolean(pkg.dependencies?.[name] || pkg.devDependencies?.[name]);
|
||||
}
|
||||
//# sourceMappingURL=packageManager.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"packageManager.js","sourceRoot":"","sources":["../../../src/lib/frameworks/packageManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC;IACpE,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC;IAC/D,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5G,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAoB;IACpD,QAAQ,EAAE,EAAE,CAAC;QACX,KAAK,MAAM;YACT,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;QAChD,KAAK,MAAM;YACT,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;QAChD,KAAK,KAAK;YACR,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/C,KAAK,KAAK,CAAC;QACX;YACE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;IACjD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,EAAoB,EAAE,MAAc,EAAE,YAAsB,EAAE;IAChG,QAAQ,EAAE,EAAE,CAAC;QACX,KAAK,MAAM;YACT,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC;QAClE,KAAK,MAAM;YACT,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC;QAC3D,KAAK,KAAK;YACR,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,CAAC;QACjE,KAAK,KAAK,CAAC;QACX;YACE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IACxG,CAAC;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,EAAoB,EACpB,QAAkB,EAClB,OAA4C;IAE5C,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,QAAQ,EAAE,EAAE,CAAC;YACX,KAAK,MAAM;gBACT,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;YAC5D,KAAK,MAAM;gBACT,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;YAC5D,KAAK,KAAK;gBACR,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,QAAQ,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;YAC3D,KAAK,KAAK,CAAC;YACX;gBACE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;QAChE,CAAC;IACH,CAAC;IAED,QAAQ,EAAE,EAAE,CAAC;QACX,KAAK,MAAM;YACT,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;QACzF,KAAK,MAAM;YACT,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;QACzF,KAAK,KAAK;YACR,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;QACxF,KAAK,KAAK,CAAC;QACX;YACE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC;IAC9F,CAAC;AACH,CAAC;AAUD,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAC7C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAgB,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAuB,EAAE,IAAY;IACjE,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,eAAe,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1E,CAAC"}
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
import path from 'node:path';
|
||||
import { readComposerJson, vendorBinExists } from './composer.js';
|
||||
/**
|
||||
* Shared behaviour for Composer-based PHP frameworks (Laravel, CodeIgniter).
|
||||
* Subclasses only need to override the bits that actually differ: the CLI
|
||||
* used for scaffolding/migrations (artisan vs spark), the dev-server "ready"
|
||||
* line, and which test runner is used.
|
||||
*/
|
||||
export class PhpFrameworkAdapter {
|
||||
root;
|
||||
packageManager = 'composer';
|
||||
composerJson;
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this.composerJson = readComposerJson(root);
|
||||
}
|
||||
installCommand() {
|
||||
return { command: 'composer', args: ['install'], cwd: this.root };
|
||||
}
|
||||
buildCommand() {
|
||||
return { command: 'composer', args: ['install', '--no-dev', '--optimize-autoloader'], cwd: this.root };
|
||||
}
|
||||
testCommand(extraArgs = []) {
|
||||
if (vendorBinExists(this.root, 'pest')) {
|
||||
return { command: this.vendorBin('pest'), args: extraArgs, cwd: this.root };
|
||||
}
|
||||
if (vendorBinExists(this.root, 'phpunit')) {
|
||||
return { command: this.vendorBin('phpunit'), args: extraArgs, cwd: this.root };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
lintCommand(fix = false) {
|
||||
if (vendorBinExists(this.root, 'pint')) {
|
||||
return { command: this.vendorBin('pint'), args: fix ? [] : ['--test'], cwd: this.root };
|
||||
}
|
||||
if (vendorBinExists(this.root, 'phpcs')) {
|
||||
return { command: this.vendorBin(fix ? 'phpcbf' : 'phpcs'), args: [], cwd: this.root };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
vendorBin(bin) {
|
||||
const suffix = process.platform === 'win32' ? '.bat' : '';
|
||||
return path.join(this.root, 'vendor', 'bin', `${bin}${suffix}`);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=phpBase.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"phpBase.js","sourceRoot":"","sources":["../../../src/lib/frameworks/phpBase.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAqB,MAAM,eAAe,CAAC;AAErF;;;;;GAKG;AACH,MAAM,OAAgB,mBAAmB;IAMlB;IAHZ,cAAc,GAAG,UAAU,CAAC;IAClB,YAAY,CAAsB;IAErD,YAAqB,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;QAC/B,IAAI,CAAC,YAAY,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,cAAc;QACZ,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IACpE,CAAC;IAED,YAAY;QACV,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,uBAAuB,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IACzG,CAAC;IAED,WAAW,CAAC,YAAsB,EAAE;QAClC,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;YACvC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9E,CAAC;QACD,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC;YAC1C,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QACjF,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,WAAW,CAAC,GAAG,GAAG,KAAK;QACrB,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;YACvC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QAC1F,CAAC;QACD,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;YACxC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QACzF,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAES,SAAS,CAAC,GAAW;QAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,GAAG,GAAG,MAAM,EAAE,CAAC,CAAC;IAClE,CAAC;CAKF"}
|
||||
Vendored
+112
@@ -0,0 +1,112 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { detectJsPackageManager, hasDependency, installCommandFor, readPackageJson, runScriptCommandFor, } from './packageManager.js';
|
||||
function detectBundler(root, pkg) {
|
||||
if (hasDependency(pkg, 'next') || fileExists(root, ['next.config.js', 'next.config.mjs', 'next.config.ts'])) {
|
||||
return 'next';
|
||||
}
|
||||
if (fileExists(root, ['vite.config.js', 'vite.config.ts', 'vite.config.mjs']))
|
||||
return 'vite';
|
||||
if (hasDependency(pkg, 'react-scripts'))
|
||||
return 'create-react-app';
|
||||
return 'unknown';
|
||||
}
|
||||
function detectNextRouter(root) {
|
||||
const appLayouts = [
|
||||
path.join(root, 'app', 'layout.tsx'),
|
||||
path.join(root, 'app', 'layout.jsx'),
|
||||
path.join(root, 'src', 'app', 'layout.tsx'),
|
||||
path.join(root, 'src', 'app', 'layout.jsx'),
|
||||
];
|
||||
if (appLayouts.some((p) => fs.existsSync(p)))
|
||||
return 'app';
|
||||
const pagesIndexes = [
|
||||
path.join(root, 'pages', 'index.tsx'),
|
||||
path.join(root, 'pages', 'index.jsx'),
|
||||
path.join(root, 'pages', 'index.js'),
|
||||
path.join(root, 'src', 'pages', 'index.tsx'),
|
||||
path.join(root, 'src', 'pages', 'index.jsx'),
|
||||
];
|
||||
if (pagesIndexes.some((p) => fs.existsSync(p)))
|
||||
return 'pages';
|
||||
return 'unknown';
|
||||
}
|
||||
function fileExists(root, names) {
|
||||
return names.some((name) => fs.existsSync(path.join(root, name)));
|
||||
}
|
||||
export class ReactAdapter {
|
||||
root;
|
||||
kind = 'react';
|
||||
label;
|
||||
packageManager;
|
||||
pkg;
|
||||
bundler;
|
||||
nextRouter;
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this.pkg = readPackageJson(root);
|
||||
this.packageManager = detectJsPackageManager(root);
|
||||
this.bundler = detectBundler(root, this.pkg);
|
||||
this.nextRouter = this.bundler === 'next' ? detectNextRouter(root) : 'unknown';
|
||||
this.label = `React (${this.bundler})`;
|
||||
}
|
||||
installCommand() {
|
||||
const { command, args } = installCommandFor(this.packageManager);
|
||||
return { command, args, cwd: this.root };
|
||||
}
|
||||
devCommand() {
|
||||
const script = this.findScript(['dev', 'start']);
|
||||
if (!script) {
|
||||
throw new Error(`No "dev" or "start" script found in package.json at "${this.root}".`);
|
||||
}
|
||||
const { command, args } = runScriptCommandFor(this.packageManager, script);
|
||||
return { command, args, cwd: this.root };
|
||||
}
|
||||
buildCommand() {
|
||||
return this.scriptCommandOrNull(['build']);
|
||||
}
|
||||
testCommand(extraArgs = []) {
|
||||
return this.scriptCommandOrNull(['test'], extraArgs);
|
||||
}
|
||||
lintCommand(fix = false) {
|
||||
return this.scriptCommandOrNull(fix ? ['format', 'lint:fix'] : ['lint']);
|
||||
}
|
||||
readyPattern() {
|
||||
switch (this.bundler) {
|
||||
case 'vite':
|
||||
return /Local:\s+(https?:\/\/\S+)/i;
|
||||
case 'next':
|
||||
return /(?:Local|ready)[^:]*:\s*(https?:\/\/\S+)|started server on[^,]*,\s*url:\s*(https?:\/\/\S+)/i;
|
||||
case 'create-react-app':
|
||||
return /Local:\s+(https?:\/\/\S+)|Compiled successfully/i;
|
||||
default:
|
||||
return /(https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0)[:\d]*\S*)/i;
|
||||
}
|
||||
}
|
||||
describe() {
|
||||
const isNext = this.bundler === 'next';
|
||||
return {
|
||||
kind: this.kind,
|
||||
label: this.label,
|
||||
root: this.root,
|
||||
packageManager: this.packageManager,
|
||||
bundler: this.bundler,
|
||||
router: isNext ? this.nextRouter : null,
|
||||
rsc: isNext && this.nextRouter === 'app',
|
||||
reactVersion: this.pkg?.dependencies?.react ?? this.pkg?.devDependencies?.react ?? null,
|
||||
typescript: hasDependency(this.pkg, 'typescript') || fileExists(this.root, ['tsconfig.json']),
|
||||
scripts: this.pkg?.scripts ?? {},
|
||||
};
|
||||
}
|
||||
findScript(candidates) {
|
||||
return candidates.find((name) => this.pkg?.scripts?.[name]);
|
||||
}
|
||||
scriptCommandOrNull(candidates, extraArgs = []) {
|
||||
const script = this.findScript(candidates);
|
||||
if (!script)
|
||||
return null;
|
||||
const { command, args } = runScriptCommandFor(this.packageManager, script, extraArgs);
|
||||
return { command, args, cwd: this.root };
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=react.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"react.js","sourceRoot":"","sources":["../../../src/lib/frameworks/react.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Picks which adapter a workflow tool call should act on.
|
||||
* - If the caller passed an explicit `target`, honor it (error if that side wasn't detected).
|
||||
* - If only one side was detected, use it regardless of `target`.
|
||||
* - If both sides were detected and no `target` was given, default to backend
|
||||
* (matches the common "start the API, then the frontend separately" flow)
|
||||
* but callers that care about the frontend by default (e.g. dev server) can
|
||||
* pass `defaultTarget: 'frontend'`.
|
||||
*/
|
||||
export function resolveTarget(detected, target, defaultTarget = 'backend') {
|
||||
if (target === 'backend') {
|
||||
if (!detected.backend) {
|
||||
throw new Error('target "backend" was requested, but no backend (Laravel/CodeIgniter/Symfony/Django/Rails) project was detected.');
|
||||
}
|
||||
return { adapter: detected.backend, target: 'backend' };
|
||||
}
|
||||
if (target === 'frontend') {
|
||||
if (!detected.frontend) {
|
||||
throw new Error('target "frontend" was requested, but no frontend (React/Vue/Nuxt) project was detected.');
|
||||
}
|
||||
return { adapter: detected.frontend, target: 'frontend' };
|
||||
}
|
||||
if (detected.backend && detected.frontend) {
|
||||
const adapter = defaultTarget === 'frontend' ? detected.frontend : detected.backend;
|
||||
return { adapter, target: defaultTarget };
|
||||
}
|
||||
return { adapter: detected.primary, target: 'primary' };
|
||||
}
|
||||
//# sourceMappingURL=resolve.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/lib/frameworks/resolve.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAC3B,QAAyB,EACzB,MAAiC,EACjC,gBAA+B,SAAS;IAExC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CACb,iHAAiH,CAClH,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC1D,CAAC;IACD,IAAI,MAAM,KAAK,UAAU,EAAE,CAAC;QAC1B,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC5D,CAAC;IAED,IAAI,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QAC1C,MAAM,OAAO,GAAG,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;QACpF,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IAC5C,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAC1D,CAAC"}
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=types.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/lib/frameworks/types.ts"],"names":[],"mappings":""}
|
||||
Vendored
+127
@@ -0,0 +1,127 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { detectJsPackageManager, hasDependency, installCommandFor, readPackageJson, runScriptCommandFor, } from './packageManager.js';
|
||||
function fileExists(root, names) {
|
||||
return names.some((name) => fs.existsSync(path.join(root, name)));
|
||||
}
|
||||
/**
|
||||
* Vite + Vue SFC projects (non-Nuxt).
|
||||
*/
|
||||
export class VueAdapter {
|
||||
root;
|
||||
kind = 'vue';
|
||||
label;
|
||||
packageManager;
|
||||
pkg;
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this.pkg = readPackageJson(root);
|
||||
this.packageManager = detectJsPackageManager(root);
|
||||
this.label = 'Vue';
|
||||
}
|
||||
installCommand() {
|
||||
const { command, args } = installCommandFor(this.packageManager);
|
||||
return { command, args, cwd: this.root };
|
||||
}
|
||||
devCommand() {
|
||||
return this.requireScript(['dev', 'start']);
|
||||
}
|
||||
buildCommand() {
|
||||
return this.scriptOrNull(['build']);
|
||||
}
|
||||
testCommand(extraArgs = []) {
|
||||
return this.scriptOrNull(['test'], extraArgs);
|
||||
}
|
||||
lintCommand(fix = false) {
|
||||
return this.scriptOrNull(fix ? ['format', 'lint:fix'] : ['lint']);
|
||||
}
|
||||
readyPattern() {
|
||||
return /Local:\s+(https?:\/\/\S+)/i;
|
||||
}
|
||||
describe() {
|
||||
return {
|
||||
kind: this.kind,
|
||||
label: this.label,
|
||||
root: this.root,
|
||||
packageManager: this.packageManager,
|
||||
vueVersion: this.pkg?.dependencies?.vue ?? this.pkg?.devDependencies?.vue ?? null,
|
||||
typescript: hasDependency(this.pkg, 'typescript') || fileExists(this.root, ['tsconfig.json']),
|
||||
vite: fileExists(this.root, ['vite.config.js', 'vite.config.ts', 'vite.config.mjs']),
|
||||
scripts: this.pkg?.scripts ?? {},
|
||||
};
|
||||
}
|
||||
requireScript(candidates) {
|
||||
const cmd = this.scriptOrNull(candidates);
|
||||
if (!cmd) {
|
||||
throw new Error(`No "${candidates.join('"/"')}" script found in package.json at "${this.root}".`);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
scriptOrNull(candidates, extraArgs = []) {
|
||||
const script = candidates.find((name) => this.pkg?.scripts?.[name]);
|
||||
if (!script)
|
||||
return null;
|
||||
const { command, args } = runScriptCommandFor(this.packageManager, script, extraArgs);
|
||||
return { command, args, cwd: this.root };
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Nuxt (Vue meta-framework) projects.
|
||||
*/
|
||||
export class NuxtAdapter {
|
||||
root;
|
||||
kind = 'nuxt';
|
||||
label = 'Nuxt';
|
||||
packageManager;
|
||||
pkg;
|
||||
constructor(root) {
|
||||
this.root = root;
|
||||
this.pkg = readPackageJson(root);
|
||||
this.packageManager = detectJsPackageManager(root);
|
||||
}
|
||||
installCommand() {
|
||||
const { command, args } = installCommandFor(this.packageManager);
|
||||
return { command, args, cwd: this.root };
|
||||
}
|
||||
devCommand() {
|
||||
return this.requireScript(['dev', 'start']);
|
||||
}
|
||||
buildCommand() {
|
||||
return this.scriptOrNull(['build', 'generate']);
|
||||
}
|
||||
testCommand(extraArgs = []) {
|
||||
return this.scriptOrNull(['test'], extraArgs);
|
||||
}
|
||||
lintCommand(fix = false) {
|
||||
return this.scriptOrNull(fix ? ['format', 'lint:fix'] : ['lint']);
|
||||
}
|
||||
readyPattern() {
|
||||
return /Local:\s+(https?:\/\/\S+)|Nuxt\s+.+ready/i;
|
||||
}
|
||||
describe() {
|
||||
return {
|
||||
kind: this.kind,
|
||||
label: this.label,
|
||||
root: this.root,
|
||||
packageManager: this.packageManager,
|
||||
nuxtVersion: this.pkg?.dependencies?.nuxt ?? this.pkg?.devDependencies?.nuxt ?? null,
|
||||
typescript: hasDependency(this.pkg, 'typescript') || fileExists(this.root, ['tsconfig.json']),
|
||||
scripts: this.pkg?.scripts ?? {},
|
||||
};
|
||||
}
|
||||
requireScript(candidates) {
|
||||
const cmd = this.scriptOrNull(candidates);
|
||||
if (!cmd) {
|
||||
throw new Error(`No "${candidates.join('"/"')}" script found in package.json at "${this.root}".`);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
scriptOrNull(candidates, extraArgs = []) {
|
||||
const script = candidates.find((name) => this.pkg?.scripts?.[name]);
|
||||
if (!script)
|
||||
return null;
|
||||
const { command, args } = runScriptCommandFor(this.packageManager, script, extraArgs);
|
||||
return { command, args, cwd: this.root };
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=vue.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"vue.js","sourceRoot":"","sources":["../../../src/lib/frameworks/vue.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
function write(level, message, meta) {
|
||||
const line = `[${new Date().toISOString()}] [${level.toUpperCase()}] ${message}`;
|
||||
if (meta !== undefined) {
|
||||
process.stderr.write(`${line} ${JSON.stringify(meta)}\n`);
|
||||
}
|
||||
else {
|
||||
process.stderr.write(`${line}\n`);
|
||||
}
|
||||
}
|
||||
export const logger = {
|
||||
debug: (message, meta) => write('debug', message, meta),
|
||||
info: (message, meta) => write('info', message, meta),
|
||||
warn: (message, meta) => write('warn', message, meta),
|
||||
error: (message, meta) => write('error', message, meta),
|
||||
};
|
||||
//# sourceMappingURL=logger.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAOA,SAAS,KAAK,CAAC,KAAY,EAAE,OAAe,EAAE,IAAc;IAC1D,MAAM,IAAI,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,CAAC,WAAW,EAAE,KAAK,OAAO,EAAE,CAAC;IACjF,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;IACpC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,CAAC,OAAe,EAAE,IAAc,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;IACzE,IAAI,EAAE,CAAC,OAAe,EAAE,IAAc,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;IACvE,IAAI,EAAE,CAAC,OAAe,EAAE,IAAc,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;IACvE,KAAK,EAAE,CAAC,OAAe,EAAE,IAAc,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;CAC1E,CAAC"}
|
||||
Vendored
+148
@@ -0,0 +1,148 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { execa } from 'execa';
|
||||
import { config } from '../config.js';
|
||||
import { logger } from './logger.js';
|
||||
class ProcessManager {
|
||||
processes = new Map();
|
||||
start(options) {
|
||||
if (this.runningCount() >= config.maxConcurrentProcesses) {
|
||||
throw new Error(`Refusing to start another process: already at the max of ${config.maxConcurrentProcesses} concurrent managed processes. Stop one first with stop_process.`);
|
||||
}
|
||||
const id = randomUUID();
|
||||
const emitter = new EventEmitter();
|
||||
const subprocess = execa(options.command, options.args, {
|
||||
cwd: options.cwd,
|
||||
env: options.env,
|
||||
reject: false,
|
||||
stdin: 'ignore',
|
||||
forceKillAfterDelay: 5000,
|
||||
});
|
||||
const managed = {
|
||||
id,
|
||||
label: options.label,
|
||||
command: options.command,
|
||||
args: options.args,
|
||||
cwd: options.cwd,
|
||||
pid: subprocess.pid,
|
||||
status: 'running',
|
||||
exitCode: null,
|
||||
startedAt: new Date().toISOString(),
|
||||
emitter,
|
||||
logs: [],
|
||||
subprocess,
|
||||
};
|
||||
const appendLog = (chunk) => {
|
||||
const text = chunk.toString();
|
||||
for (const line of text.split(/\r?\n/)) {
|
||||
if (line.length === 0)
|
||||
continue;
|
||||
managed.logs.push(line);
|
||||
if (managed.logs.length > config.maxBufferedLogLines) {
|
||||
managed.logs.splice(0, managed.logs.length - config.maxBufferedLogLines);
|
||||
}
|
||||
emitter.emit('log', line);
|
||||
}
|
||||
};
|
||||
subprocess.stdout?.on('data', appendLog);
|
||||
subprocess.stderr?.on('data', appendLog);
|
||||
subprocess
|
||||
.then((result) => {
|
||||
managed.status = managed.status === 'stopped' ? 'stopped' : 'exited';
|
||||
managed.exitCode = result.exitCode ?? null;
|
||||
managed.exitedAt = new Date().toISOString();
|
||||
emitter.emit('exit');
|
||||
})
|
||||
.catch((error) => {
|
||||
managed.status = 'error';
|
||||
managed.exitedAt = new Date().toISOString();
|
||||
appendLog(`[process error] ${error instanceof Error ? error.message : String(error)}`);
|
||||
emitter.emit('exit');
|
||||
logger.error(`managed process ${id} failed`, { error: error instanceof Error ? error.message : error });
|
||||
});
|
||||
this.processes.set(id, managed);
|
||||
return this.toInfo(managed);
|
||||
}
|
||||
/**
|
||||
* Resolves once a log line matches `pattern`, the process exits, or `timeoutMs` elapses.
|
||||
* Used by start_dev_server so it can report the actual "ready" URL instead of guessing.
|
||||
*/
|
||||
waitForLog(id, pattern, timeoutMs) {
|
||||
const managed = this.require(id);
|
||||
const existing = managed.logs.find((line) => pattern.test(line));
|
||||
if (existing)
|
||||
return Promise.resolve({ matchedLine: existing, timedOut: false });
|
||||
return new Promise((resolve) => {
|
||||
const cleanup = () => {
|
||||
clearTimeout(timer);
|
||||
managed.emitter.off('log', onLog);
|
||||
managed.emitter.off('exit', onExit);
|
||||
};
|
||||
const onLog = (line) => {
|
||||
if (pattern.test(line)) {
|
||||
cleanup();
|
||||
resolve({ matchedLine: line, timedOut: false });
|
||||
}
|
||||
};
|
||||
const onExit = () => {
|
||||
cleanup();
|
||||
resolve({ matchedLine: null, timedOut: false });
|
||||
};
|
||||
const timer = setTimeout(() => {
|
||||
cleanup();
|
||||
resolve({ matchedLine: null, timedOut: true });
|
||||
}, timeoutMs);
|
||||
managed.emitter.on('log', onLog);
|
||||
managed.emitter.on('exit', onExit);
|
||||
});
|
||||
}
|
||||
stop(id) {
|
||||
const managed = this.require(id);
|
||||
if (managed.status === 'running') {
|
||||
managed.status = 'stopped';
|
||||
managed.subprocess.kill('SIGTERM');
|
||||
}
|
||||
return this.toInfo(managed);
|
||||
}
|
||||
getLogs(id, tail) {
|
||||
const managed = this.require(id);
|
||||
const logs = tail && tail > 0 ? managed.logs.slice(-tail) : managed.logs.slice();
|
||||
return { info: this.toInfo(managed), logs };
|
||||
}
|
||||
list() {
|
||||
return Array.from(this.processes.values()).map((p) => this.toInfo(p));
|
||||
}
|
||||
stopAll() {
|
||||
for (const managed of this.processes.values()) {
|
||||
if (managed.status === 'running') {
|
||||
managed.subprocess.kill('SIGTERM');
|
||||
}
|
||||
}
|
||||
}
|
||||
runningCount() {
|
||||
return Array.from(this.processes.values()).filter((p) => p.status === 'running').length;
|
||||
}
|
||||
require(id) {
|
||||
const managed = this.processes.get(id);
|
||||
if (!managed) {
|
||||
throw new Error(`Unknown processId "${id}". It may have already exited and been forgotten, or never existed.`);
|
||||
}
|
||||
return managed;
|
||||
}
|
||||
toInfo(p) {
|
||||
return {
|
||||
id: p.id,
|
||||
label: p.label,
|
||||
command: p.command,
|
||||
args: p.args,
|
||||
cwd: p.cwd,
|
||||
pid: p.pid,
|
||||
status: p.status,
|
||||
exitCode: p.exitCode,
|
||||
startedAt: p.startedAt,
|
||||
exitedAt: p.exitedAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
export const processManager = new ProcessManager();
|
||||
//# sourceMappingURL=processManager.js.map
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+37
@@ -0,0 +1,37 @@
|
||||
import { execa } from 'execa';
|
||||
import { config } from '../config.js';
|
||||
import { assertAllowedBinary } from './sandbox.js';
|
||||
/**
|
||||
* Runs a command to completion (foreground) and returns its combined,
|
||||
* length-capped output. Intended for install/build/lint/test-style commands
|
||||
* that the agent needs the final result of, as opposed to long-running dev
|
||||
* servers (see processManager.ts for those).
|
||||
*/
|
||||
export async function runCommand(command, args, options) {
|
||||
assertAllowedBinary(command);
|
||||
const timeoutMs = options.timeoutMs ?? config.defaultCommandTimeoutMs;
|
||||
const result = await execa(command, args, {
|
||||
cwd: options.cwd,
|
||||
env: options.env,
|
||||
timeout: timeoutMs,
|
||||
reject: false,
|
||||
all: true,
|
||||
});
|
||||
const rawOutput = result.all ?? [result.stdout, result.stderr].filter(Boolean).join('\n');
|
||||
return {
|
||||
command,
|
||||
args,
|
||||
cwd: options.cwd,
|
||||
exitCode: result.exitCode ?? null,
|
||||
timedOut: result.timedOut,
|
||||
failed: result.failed,
|
||||
output: truncate(String(rawOutput ?? ''), config.maxToolOutputChars),
|
||||
};
|
||||
}
|
||||
function truncate(text, max) {
|
||||
if (text.length <= max)
|
||||
return text;
|
||||
const omitted = text.length - max;
|
||||
return `${text.slice(0, max)}\n... [truncated ${omitted} more characters] ...`;
|
||||
}
|
||||
//# sourceMappingURL=runCommand.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"runCommand.js","sourceRoot":"","sources":["../../src/lib/runCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAkBnD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,OAAe,EACf,IAAc,EACd,OAA0B;IAE1B,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC7B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC,uBAAuB,CAAC;IAEtE,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;QACxC,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,OAAO,EAAE,SAAS;QAClB,MAAM,EAAE,KAAK;QACb,GAAG,EAAE,IAAI;KACV,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1F,OAAO;QACL,OAAO;QACP,IAAI;QACJ,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI;QACjC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,kBAAkB,CAAC;KACrE,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW;IACzC,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC;IAClC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,oBAAoB,OAAO,uBAAuB,CAAC;AACjF,CAAC"}
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
import path from 'node:path';
|
||||
import { config } from '../config.js';
|
||||
export class SandboxViolationError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = 'SandboxViolationError';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Resolves `relativePath` against `root` and guarantees the result stays
|
||||
* inside `root`. Throws SandboxViolationError on any attempt to escape
|
||||
* (e.g. via `..`, absolute paths outside the root, or symlink-style tricks
|
||||
* handled at the OS level by the caller when it actually touches the fs).
|
||||
*/
|
||||
export function resolveWorkspacePath(root, relativePath) {
|
||||
const absoluteRoot = path.resolve(root);
|
||||
const target = path.resolve(absoluteRoot, relativePath || '.');
|
||||
const normalizedRoot = absoluteRoot.toLowerCase();
|
||||
const normalizedTarget = target.toLowerCase();
|
||||
if (normalizedTarget !== normalizedRoot && !normalizedTarget.startsWith(normalizedRoot + path.sep)) {
|
||||
throw new SandboxViolationError(`Refusing to access "${relativePath}": it resolves outside the workspace root (${absoluteRoot}).`);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
function basenameNoExt(binary) {
|
||||
const base = path.basename(binary);
|
||||
return base.replace(/\.(exe|cmd|bat|ps1)$/i, '');
|
||||
}
|
||||
/**
|
||||
* Throws unless `binary` is on the configured allow-list. Command
|
||||
* construction elsewhere in the codebase always passes `args` as a separate
|
||||
* array (never a single shell string), so this allow-list is the only line
|
||||
* of defense needed against arbitrary command execution.
|
||||
*/
|
||||
export function assertAllowedBinary(binary) {
|
||||
const name = basenameNoExt(binary).toLowerCase();
|
||||
const allowed = config.allowedBinaries.some((candidate) => candidate.toLowerCase() === name);
|
||||
if (!allowed) {
|
||||
throw new SandboxViolationError(`Refusing to run "${binary}": it is not on the allow-list (${config.allowedBinaries.join(', ')}).`);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=sandbox.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../src/lib/sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAE,YAAoB;IACrE,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,YAAY,IAAI,GAAG,CAAC,CAAC;IAE/D,MAAM,cAAc,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC;IAClD,MAAM,gBAAgB,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IAE9C,IAAI,gBAAgB,KAAK,cAAc,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACnG,MAAM,IAAI,qBAAqB,CAC7B,uBAAuB,YAAY,8CAA8C,YAAY,IAAI,CAClG,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,aAAa,CAAC,MAAc;IACnC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACnC,OAAO,IAAI,CAAC,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;AACnD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,MAAM,OAAO,GAAI,MAAM,CAAC,eAAqC,CAAC,IAAI,CAChE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,IAAI,CAChD,CAAC;IACF,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,qBAAqB,CAC7B,oBAAoB,MAAM,mCAAmC,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CACnG,CAAC;IACJ,CAAC;AACH,CAAC"}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
function words(input) {
|
||||
return input
|
||||
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
||||
.split(/[\s_-]+/)
|
||||
.filter(Boolean);
|
||||
}
|
||||
export function pascalCase(input) {
|
||||
return words(input)
|
||||
.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
|
||||
.join('');
|
||||
}
|
||||
export function camelCase(input) {
|
||||
const pascal = pascalCase(input);
|
||||
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
||||
}
|
||||
export function kebabCase(input) {
|
||||
return words(input)
|
||||
.map((w) => w.toLowerCase())
|
||||
.join('-');
|
||||
}
|
||||
//# sourceMappingURL=strings.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"strings.js","sourceRoot":"","sources":["../../src/lib/strings.ts"],"names":[],"mappings":"AAAA,SAAS,KAAK,CAAC,KAAa;IAC1B,OAAO,KAAK;SACT,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;SACtC,KAAK,CAAC,SAAS,CAAC;SAChB,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,KAAK,CAAC;SAChB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SAChE,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,KAAK,CAAC,KAAK,CAAC;SAChB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SAC3B,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC"}
|
||||
Reference in New Issue
Block a user