layero 0.7.2 → 0.7.4
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/agent.js +3 -1
- package/dist/detect.js +134 -0
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -107,7 +107,9 @@ function renderHuman(event) {
|
|
|
107
107
|
process.stdout.write(`→ Project ${event.slug}\n`);
|
|
108
108
|
break;
|
|
109
109
|
case "detected":
|
|
110
|
-
process.stdout.write(
|
|
110
|
+
process.stdout.write(event.runtime_kind
|
|
111
|
+
? `→ Detected ${event.runtime_kind} runtime app — will deploy as a scale-to-zero container\n`
|
|
112
|
+
: `→ Detected ${event.framework} (build: ${event.build_cmd}, output: ${event.output_dir})\n`);
|
|
111
113
|
break;
|
|
112
114
|
case "prebuilt":
|
|
113
115
|
process.stdout.write(`→ Prebuilt mode: shipping ${event.dir}\n`);
|
package/dist/detect.js
CHANGED
|
@@ -26,6 +26,25 @@ async function fileExists(cwd, ...candidates) {
|
|
|
26
26
|
}
|
|
27
27
|
return false;
|
|
28
28
|
}
|
|
29
|
+
// Python runtime: `app.py` entry + a runtime lib in requirements.txt.
|
|
30
|
+
// Lockstep with detect_runtime() (backend) / runtime_detect.py (builder):
|
|
31
|
+
// app.py + requirements.txt mentioning streamlit | gradio | flask.
|
|
32
|
+
async function detectPythonRuntime(cwd) {
|
|
33
|
+
if (!(await fileExists(cwd, "app.py")))
|
|
34
|
+
return null;
|
|
35
|
+
let reqs;
|
|
36
|
+
try {
|
|
37
|
+
reqs = (await fs.readFile(path.join(cwd, "requirements.txt"), "utf-8")).toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
for (const lib of ["streamlit", "gradio", "flask"]) {
|
|
43
|
+
if (reqs.includes(lib))
|
|
44
|
+
return lib;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
29
48
|
// Mirrors `frameworks/nextjs.py:_NEXT_STATIC_EXPORT_RE` and
|
|
30
49
|
// `next_config_static_export()` so CLI and builder can't disagree on
|
|
31
50
|
// what counts as a static-export Next.js project. The class-of-bug we
|
|
@@ -137,6 +156,25 @@ export async function detectProject(cwd) {
|
|
|
137
156
|
}),
|
|
138
157
|
};
|
|
139
158
|
}
|
|
159
|
+
// Remix / React Router v7 before SvelteKit/Vite — RR7 ships Vite
|
|
160
|
+
// internally, so the Vite-dep check would otherwise win. Mirrors the
|
|
161
|
+
// builder's RemixFramework + backend `remix` table entry (output
|
|
162
|
+
// build/client). Was missing from the CLI entirely — same dual-detector
|
|
163
|
+
// gap class as Angular: a Remix repo deployed via the CLI fell through
|
|
164
|
+
// to the static fallback and shipped raw sources.
|
|
165
|
+
if (hasDep(pkg, "@remix-run/dev") ||
|
|
166
|
+
hasDep(pkg, "@remix-run/react") ||
|
|
167
|
+
hasDep(pkg, "@remix-run/node") ||
|
|
168
|
+
hasDep(pkg, "@react-router/dev") ||
|
|
169
|
+
hasDep(pkg, "@react-router/node") ||
|
|
170
|
+
(await fileExists(cwd, "react-router.config.ts", "react-router.config.js"))) {
|
|
171
|
+
return {
|
|
172
|
+
framework_hint: "remix",
|
|
173
|
+
build_cmd: buildCmd(pkg, "npx react-router build"),
|
|
174
|
+
output_dir: "build/client",
|
|
175
|
+
confident: true,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
140
178
|
if (hasDep(pkg, "@sveltejs/kit") || (await fileExists(cwd, "svelte.config.js"))) {
|
|
141
179
|
// SvelteKit needs an adapter. adapter-static = SPA path; anything
|
|
142
180
|
// else (adapter-node, adapter-auto, …) is server-side. Mirrors
|
|
@@ -254,6 +292,22 @@ export async function detectProject(cwd) {
|
|
|
254
292
|
confident: true,
|
|
255
293
|
};
|
|
256
294
|
}
|
|
295
|
+
// Angular after Vite — mirrors builder ALL order. Angular ships its
|
|
296
|
+
// own CLI (`ng build`) and writes to `dist/{project}/` (Angular <17)
|
|
297
|
+
// or `dist/{project}/browser/` (17+ `application` builder), NOT bare
|
|
298
|
+
// `dist`. Until this branch existed the CLI fell through to the
|
|
299
|
+
// static fallback (`output_dir='.'`), so the first deploy of an
|
|
300
|
+
// Angular repo shipped the raw sources — the dist/<project> S3 404
|
|
301
|
+
// incident. `angularOutputDir` parses angular.json to pin the path
|
|
302
|
+
// (lockstep with `frameworks/angular.py:extract_output_dir`).
|
|
303
|
+
if (hasDep(pkg, "@angular/core") || (await fileExists(cwd, "angular.json"))) {
|
|
304
|
+
return {
|
|
305
|
+
framework_hint: "angular",
|
|
306
|
+
build_cmd: buildCmd(pkg, "npx ng build"),
|
|
307
|
+
output_dir: await angularOutputDir(cwd),
|
|
308
|
+
confident: true,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
257
311
|
if (hasDep(pkg, "react-scripts")) {
|
|
258
312
|
return {
|
|
259
313
|
framework_hint: "cra",
|
|
@@ -277,6 +331,32 @@ export async function detectProject(cwd) {
|
|
|
277
331
|
confident: true,
|
|
278
332
|
};
|
|
279
333
|
}
|
|
334
|
+
// package.json present but no framework matched → a custom Node build,
|
|
335
|
+
// NOT a static site. Mirrors the backend's detect(): `pkg is None →
|
|
336
|
+
// _STATIC`, otherwise `_GENERIC` (npm run build → dist). Falling through
|
|
337
|
+
// to the static fallback here shipped the unbuilt sources — exactly the
|
|
338
|
+
// `diplomtest` case (Express app + custom build script detected as static
|
|
339
|
+
// by the CLI but `generic` by the backend).
|
|
340
|
+
return {
|
|
341
|
+
framework_hint: "generic",
|
|
342
|
+
build_cmd: buildCmd(pkg, "npm run build"),
|
|
343
|
+
output_dir: "dist",
|
|
344
|
+
confident: false,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
// Python runtimes (Streamlit / Gradio / Flask): an `app.py` entry plus a
|
|
348
|
+
// runtime lib in requirements.txt. Mirrors detect_runtime() / runtime_detect.py.
|
|
349
|
+
// No package.json, so without this they fall to the static fallback and ship
|
|
350
|
+
// `app.py` as a static file (never runs) — the `streamlit-hello` case.
|
|
351
|
+
const pyRuntime = await detectPythonRuntime(cwd);
|
|
352
|
+
if (pyRuntime) {
|
|
353
|
+
return {
|
|
354
|
+
framework_hint: "static",
|
|
355
|
+
build_cmd: "true",
|
|
356
|
+
output_dir: ".",
|
|
357
|
+
confident: true,
|
|
358
|
+
runtime_kind: pyRuntime,
|
|
359
|
+
};
|
|
280
360
|
}
|
|
281
361
|
// Non-Node SSGs (Hugo today). Recognise repos without package.json
|
|
282
362
|
// before we fall back to "static" with a no-op build command — Hugo
|
|
@@ -363,3 +443,57 @@ async function hasHugoConfigMarker(cwd) {
|
|
|
363
443
|
}
|
|
364
444
|
return false;
|
|
365
445
|
}
|
|
446
|
+
// Resolve Angular's real build output directory from angular.json.
|
|
447
|
+
// Lockstep with `core/builder/src/frameworks/angular.py:extract_output_dir`
|
|
448
|
+
// — same dual-detector-drift class as Next.js static-export, so the two
|
|
449
|
+
// implementations must agree (parity fixtures in
|
|
450
|
+
// core/tests/fixtures/framework-detect/angular-*).
|
|
451
|
+
//
|
|
452
|
+
// Resolution:
|
|
453
|
+
// 1. defaultProject if set & present, else first project in `projects`
|
|
454
|
+
// 2. project.architect.build.options.outputPath, else the CLI default
|
|
455
|
+
// `dist/{projectName}`
|
|
456
|
+
// 3. strip leading `./`
|
|
457
|
+
// 4. append `/browser` for the Angular 17+ `application` builder
|
|
458
|
+
// (builder id ends with `:application`) — it writes the served
|
|
459
|
+
// assets one level deeper. We also probe disk in case the repo was
|
|
460
|
+
// built locally before `layero deploy`.
|
|
461
|
+
// Returns the framework default `dist` on any parse error — matches
|
|
462
|
+
// AngularFramework.default_output_dir, and the builder re-derives the
|
|
463
|
+
// exact path at upload time anyway.
|
|
464
|
+
async function angularOutputDir(cwd) {
|
|
465
|
+
const FALLBACK = "dist";
|
|
466
|
+
let cfg;
|
|
467
|
+
try {
|
|
468
|
+
cfg = JSON.parse(await fs.readFile(path.join(cwd, "angular.json"), "utf-8"));
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
return FALLBACK;
|
|
472
|
+
}
|
|
473
|
+
const projects = cfg?.projects;
|
|
474
|
+
if (!projects || typeof projects !== "object")
|
|
475
|
+
return FALLBACK;
|
|
476
|
+
const firstName = Object.keys(projects)[0];
|
|
477
|
+
if (!firstName)
|
|
478
|
+
return FALLBACK;
|
|
479
|
+
const defaultName = cfg.defaultProject;
|
|
480
|
+
const projectName = defaultName && projects[defaultName] ? defaultName : firstName;
|
|
481
|
+
const buildCfg = projects[projectName]?.architect?.build;
|
|
482
|
+
if (!buildCfg || typeof buildCfg !== "object")
|
|
483
|
+
return FALLBACK;
|
|
484
|
+
const opts = (buildCfg.options ?? {});
|
|
485
|
+
const rawOut = typeof opts === "object" && opts ? opts.outputPath : undefined;
|
|
486
|
+
let out = typeof rawOut === "string" && rawOut.trim() ? rawOut.trim() : `dist/${projectName}`;
|
|
487
|
+
if (out.startsWith("./"))
|
|
488
|
+
out = out.slice(2);
|
|
489
|
+
const builderId = typeof buildCfg.builder === "string" ? buildCfg.builder : "";
|
|
490
|
+
const isApplicationBuilder = builderId.endsWith(":application");
|
|
491
|
+
let browserExists = false;
|
|
492
|
+
try {
|
|
493
|
+
browserExists = (await fs.stat(path.join(cwd, out, "browser"))).isDirectory();
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
// not built locally — rely on the builder-id signal below
|
|
497
|
+
}
|
|
498
|
+
return browserExists || isApplicationBuilder ? `${out}/browser` : out;
|
|
499
|
+
}
|