Runner: Projekterkennung
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bc5fddf13f
commit
e2c517a015
3 files changed
+240
No files matched your search
@@ -0,0 +1,83 @@
|
||||
// Reine Projekterkennung (Spec 1.2 „Erkennung“): static | build | dev, Paketmanager, Befehle.
|
||||
import { statSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const DEFAULT_OUTPUTS = ['dist', 'build', 'out', 'web-build'];
|
||||
const DEFAULT_PORT = 5173;
|
||||
|
||||
const safeRel = (p) => typeof p === 'string' && p !== '' && p.length <= 200 && !p.startsWith('/')
|
||||
&& !p.includes('\0') && !p.split(/[\\/]/).includes('..');
|
||||
|
||||
function scriptsOf(files) {
|
||||
try {
|
||||
const txt = files.read('package.json');
|
||||
if (txt == null) return {};
|
||||
const s = JSON.parse(txt)?.scripts;
|
||||
return s && typeof s === 'object' && !Array.isArray(s) ? s : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
const hasScript = (scripts, name) => typeof scripts[name] === 'string' && scripts[name].trim() !== '';
|
||||
|
||||
function detectPm(files) {
|
||||
if (files.has('pnpm-lock.yaml')) return 'pnpm';
|
||||
if (files.has('yarn.lock')) return 'yarn';
|
||||
if (files.has('bun.lockb') || files.has('bun.lock')) return 'bun';
|
||||
return 'npm';
|
||||
}
|
||||
|
||||
function installCmd(pm, files) {
|
||||
if (pm === 'npm') {
|
||||
return files.has('package-lock.json') || files.has('npm-shrinkwrap.json')
|
||||
? ['npm', 'ci', '--include=dev']
|
||||
: ['npm', 'install', '--include=dev', '--no-audit', '--no-fund'];
|
||||
}
|
||||
return [pm, 'install', '--frozen-lockfile'];
|
||||
}
|
||||
|
||||
// npm/pnpm brauchen `--` vor den Skript-Argumenten, yarn/bun reichen sie direkt durch.
|
||||
function devCmdFor(pm, port) {
|
||||
const args = ['--host', '0.0.0.0', '--port', String(port)];
|
||||
return pm === 'npm' || pm === 'pnpm' ? [pm, 'run', 'dev', '--', ...args] : [pm, 'run', 'dev', ...args];
|
||||
}
|
||||
|
||||
export function detectProject(files, { mode, index, previewJson } = {}) {
|
||||
const pj = previewJson && typeof previewJson === 'object' && !Array.isArray(previewJson) ? previewJson : {};
|
||||
const scripts = scriptsOf(files);
|
||||
const pm = detectPm(files);
|
||||
const devPort = Number.isInteger(pj.devPort) && pj.devPort >= 1024 && pj.devPort <= 65535 ? pj.devPort : DEFAULT_PORT;
|
||||
const outputDirs = safeRel(pj.output) ? [pj.output] : [...DEFAULT_OUTPUTS];
|
||||
const indexFile = [index, pj.index].find(safeRel) || 'index.html';
|
||||
const hasIndex = files.has(indexFile);
|
||||
const hasBuild = hasScript(scripts, 'build');
|
||||
const hasDev = hasScript(scripts, 'dev');
|
||||
const m = mode === 'build' || mode === 'dev' ? mode : 'auto';
|
||||
|
||||
const base = { pm, install: installCmd(pm, files), buildCmd: hasBuild ? [pm, 'run', 'build'] : null, devCmd: hasDev ? devCmdFor(pm, devPort) : null, outputDirs, devPort };
|
||||
const asBuild = () => ({ kind: 'build', ...base });
|
||||
const asDev = () => ({ kind: 'dev', ...base });
|
||||
const asStatic = (hint) => ({ kind: 'static', ...base, install: [], buildCmd: null, devCmd: null, outputDirs: [], index: indexFile, ...(hint ? { hint } : {}) });
|
||||
|
||||
if (m === 'build') return hasBuild ? asBuild() : { ...asBuild(), error: 'Kein build-Skript in package.json' };
|
||||
if (m === 'dev') return hasDev ? asDev() : { ...asDev(), error: 'Kein dev-Skript in package.json' };
|
||||
|
||||
if (hasIndex && !hasBuild) return asStatic();
|
||||
if (hasBuild) return asBuild();
|
||||
if (hasDev) return asDev();
|
||||
return asStatic(`Hinweis: Weder ${indexFile} noch ein build-/dev-Skript gefunden – Inhalt wird unverändert als statische Seite bereitgestellt.`);
|
||||
}
|
||||
|
||||
// Dateisystem-Adapter für detectProject; previewJson wird aus .webklar-preview.json gelesen, wenn nicht übergeben.
|
||||
export function detectFromDir(dir, opts = {}) {
|
||||
const abs = (p) => join(dir, p);
|
||||
const files = {
|
||||
has: (p) => { try { return statSync(abs(p)).isFile(); } catch { return false; } },
|
||||
read: (p) => { try { return readFileSync(abs(p), 'utf8'); } catch { return null; } },
|
||||
};
|
||||
let previewJson = opts.previewJson;
|
||||
if (previewJson === undefined) {
|
||||
try { previewJson = JSON.parse(files.read('.webklar-preview.json')); } catch { previewJson = {}; }
|
||||
}
|
||||
return detectProject(files, { ...opts, previewJson });
|
||||
}
|
||||
Reference in new issue
Block a user