layero 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/detect.js +85 -4
- package/package.json +1 -1
package/dist/detect.js
CHANGED
|
@@ -361,17 +361,34 @@ async function shapeOf(cwd, snap, plan) {
|
|
|
361
361
|
const start = scriptOf(pkg, "start");
|
|
362
362
|
const serverEntry = await findServerEntry(cwd, pkg);
|
|
363
363
|
if (start || serverEntry) {
|
|
364
|
-
|
|
364
|
+
// Без скрипта `start` сборщик приложений перебирает свой список точек
|
|
365
|
+
// входа (server.js, index.js, …) и `server.mjs` не находит — одного
|
|
366
|
+
// `-t node_web` мало, команду запуска надо назвать (кейс a3).
|
|
365
367
|
return {
|
|
366
368
|
hint: `This looks like a Node server (${start ? `start script \`${start}\`` : `${serverEntry} calls listen()`}), ` +
|
|
367
369
|
"but no known server framework was recognised. A server has to be run in a container, not served as files.",
|
|
368
|
-
next_action:
|
|
370
|
+
next_action: start
|
|
371
|
+
? `npx layero@latest deploy -t node_web (the start script runs the server)`
|
|
372
|
+
: `create layero.json: {"runtime":"node_web","startCommand":"node ${serverEntry}"}`,
|
|
369
373
|
};
|
|
370
374
|
}
|
|
371
375
|
if (snap.requirementsTxt !== null || snap.files.has("pyproject.toml")) {
|
|
376
|
+
const app = await findPythonApp(cwd);
|
|
377
|
+
if (app) {
|
|
378
|
+
// Детект платформы ищет точку входа только в корне (main.py, app.py…);
|
|
379
|
+
// приложение в пакете (`service/web.py`) он не видит. Кейс b3.
|
|
380
|
+
const cmd = app.kind === "wsgi"
|
|
381
|
+
? `gunicorn ${app.target} --bind 0.0.0.0:$PORT`
|
|
382
|
+
: `uvicorn ${app.target} --host 0.0.0.0 --port $PORT`;
|
|
383
|
+
return {
|
|
384
|
+
hint: `A Python web app (${app.framework}) whose entry is not at the root: \`${app.target}\`. ` +
|
|
385
|
+
"The platform looks for main.py / app.py at the root, so name the app and how to start it.",
|
|
386
|
+
next_action: `create layero.json: {"runtime":"python_web","startCommand":"${cmd}"}`,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
372
389
|
return {
|
|
373
390
|
hint: "A Python project, but no known web framework or entry file was recognised.",
|
|
374
|
-
next_action: 'npx layero@latest deploy -t python_web (or layero.json: {"runtime":"python_web","startCommand":"uvicorn
|
|
391
|
+
next_action: 'npx layero@latest deploy -t python_web (or layero.json: {"runtime":"python_web","startCommand":"uvicorn <module>:<app> --host 0.0.0.0 --port $PORT"})',
|
|
375
392
|
};
|
|
376
393
|
}
|
|
377
394
|
return {
|
|
@@ -379,7 +396,60 @@ async function shapeOf(cwd, snap, plan) {
|
|
|
379
396
|
next_action: "run the command from the app's folder (or pass --root <dir>); ready-made files: npx layero@latest deploy --prebuilt <dir>",
|
|
380
397
|
};
|
|
381
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Каталог результата, заданный ФЛАГОМ в скрипте сборки (`vite build --outDir
|
|
401
|
+
* public_html`). Детект читает конфиги, а не флаги, — сборщик без
|
|
402
|
+
* `outputDirectory` ищет в умолчании фреймворка и может отдать исходный
|
|
403
|
+
* `index.html` из корня вместо собранного. Кейс «слепого» агента b1.
|
|
404
|
+
*/
|
|
405
|
+
const OUTDIR_FLAG_RE = /(?:^|\s)--(?:outDir|out-dir|outdir|output-dir|dist-dir)(?:=|\s+)(["']?)([^\s"'&|;]+)\1/;
|
|
406
|
+
function outDirFlag(pkg) {
|
|
407
|
+
const m = OUTDIR_FLAG_RE.exec(scriptOf(pkg, "build") ?? "");
|
|
408
|
+
return m ? m[2].replace(/^\.\//, "").replace(/\/+$/, "") || null : null;
|
|
409
|
+
}
|
|
382
410
|
const SERVER_ENTRY_CANDIDATES = ["server.js", "index.js", "app.js", "main.js", "server.mjs", "index.mjs"];
|
|
411
|
+
// `api = FastAPI(`, `app = Flask(__name__)` — объект приложения на верхнем
|
|
412
|
+
// уровне модуля. ASGI запускает uvicorn, WSGI — gunicorn.
|
|
413
|
+
const PY_APP_RE = /^(\w+)\s*=\s*(FastAPI|Starlette|Flask|Quart|Litestar)\s*\(/m;
|
|
414
|
+
const PY_ASGI = new Set(["FastAPI", "Starlette", "Quart", "Litestar"]);
|
|
415
|
+
const PY_SKIP = new Set([".venv", "venv", "env", "node_modules", ".git", "__pycache__", "site-packages", "tests", "test"]);
|
|
416
|
+
async function findPythonApp(cwd, maxDepth = 3) {
|
|
417
|
+
const found = [];
|
|
418
|
+
async function walk(rel, depth) {
|
|
419
|
+
if (depth > maxDepth || found.length > 3)
|
|
420
|
+
return;
|
|
421
|
+
let entries;
|
|
422
|
+
try {
|
|
423
|
+
entries = await fs.readdir(path.join(cwd, rel), { withFileTypes: true });
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
for (const e of entries) {
|
|
429
|
+
const child = rel ? `${rel}/${e.name}` : e.name;
|
|
430
|
+
if (e.isFile() && e.name.endsWith(".py")) {
|
|
431
|
+
const text = await readText(path.join(cwd, child));
|
|
432
|
+
const m = text ? PY_APP_RE.exec(text.slice(0, 64 * 1024)) : null;
|
|
433
|
+
if (m)
|
|
434
|
+
found.push({ rel: child, name: m[1], framework: m[2] });
|
|
435
|
+
}
|
|
436
|
+
else if (e.isDirectory() && !PY_SKIP.has(e.name) && !e.name.startsWith(".")) {
|
|
437
|
+
await walk(child, depth + 1);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
await walk("", 0);
|
|
442
|
+
// Одно приложение — называем; несколько — гадать, какое главное, не берёмся.
|
|
443
|
+
if (found.length !== 1)
|
|
444
|
+
return null;
|
|
445
|
+
const f = found[0];
|
|
446
|
+
const module = f.rel.replace(/\.py$/, "").replace(/\//g, ".").replace(/\.__init__$/, "");
|
|
447
|
+
return {
|
|
448
|
+
target: `${module}:${f.name}`,
|
|
449
|
+
framework: f.framework,
|
|
450
|
+
kind: PY_ASGI.has(f.framework) ? "asgi" : "wsgi",
|
|
451
|
+
};
|
|
452
|
+
}
|
|
383
453
|
const LISTEN_RE = /\.listen\s*\(|createServer\s*\(/;
|
|
384
454
|
async function findServerEntry(cwd, pkg) {
|
|
385
455
|
const names = [...(typeof pkg?.main === "string" ? [pkg.main] : []), ...SERVER_ENTRY_CANDIDATES];
|
|
@@ -507,6 +577,7 @@ export async function detectProject(cwd, opts = {}) {
|
|
|
507
577
|
let confident = declared || plan.confident;
|
|
508
578
|
let shape = null;
|
|
509
579
|
const notes = [];
|
|
580
|
+
let flagNextAction;
|
|
510
581
|
if (framework === "static") {
|
|
511
582
|
// Статика не собирается НИКОГДА: команда сборки у неё не «true», а
|
|
512
583
|
// отсутствует. Заглушка `true` читалась как команда и уезжала в проект.
|
|
@@ -555,6 +626,16 @@ export async function detectProject(cwd, opts = {}) {
|
|
|
555
626
|
confident = false;
|
|
556
627
|
shape = await shapeOf(cwd, snap, plan);
|
|
557
628
|
}
|
|
629
|
+
const flagDir = fileOutput ? null : outDirFlag(snap.packageJson);
|
|
630
|
+
if (flagDir && flagDir !== outputDir) {
|
|
631
|
+
// План платформы положит результат не туда: она не читает флаги скрипта.
|
|
632
|
+
confident = false;
|
|
633
|
+
notes.push(`The build script sets the output folder with a flag (\`${flagDir}\`), which detection does not read: ` +
|
|
634
|
+
`without outputDirectory the platform looks in "${outputDir ?? "dist"}" and may serve the source index.html instead of the build.`);
|
|
635
|
+
flagNextAction = snap.layeroJson
|
|
636
|
+
? `add "outputDirectory": "${flagDir}" to layero.json`
|
|
637
|
+
: `create layero.json: {"outputDirectory":"${flagDir}"}`;
|
|
638
|
+
}
|
|
558
639
|
if (framework === "generic" && buildCmd === null) {
|
|
559
640
|
// Собирать нечем — и каталог результата у такой папки не «dist по
|
|
560
641
|
// умолчанию», а неизвестен: умолчание фреймворка «Other» здесь вымысел.
|
|
@@ -575,7 +656,7 @@ export async function detectProject(cwd, opts = {}) {
|
|
|
575
656
|
confident,
|
|
576
657
|
sources: { framework: frameworkSource, build_cmd: buildSource, output_dir: outputSource },
|
|
577
658
|
...(hint ? { hint } : {}),
|
|
578
|
-
...(shape?.next_action ? { next_action: shape
|
|
659
|
+
...(shape?.next_action ?? flagNextAction ? { next_action: shape?.next_action ?? flagNextAction } : {}),
|
|
579
660
|
...(shape?.candidates?.length ? { candidates: shape.candidates } : {}),
|
|
580
661
|
...(warning ? { ssr_warning: warning } : {}),
|
|
581
662
|
};
|
package/package.json
CHANGED