layero 0.8.19 → 0.8.21
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/README.md +4 -4
- package/dist/bin/layero.js +52 -1
- package/dist/commands/init.js +1 -1
- package/dist/update-notifier.js +12 -2
- package/package.json +1 -2
- package/scripts/postinstall.cjs +1 -1
- package/dist/_detection/detect_core.js +0 -393
- package/dist/_detection/spec.js +0 -664
package/README.md
CHANGED
|
@@ -7,9 +7,9 @@ CLI for [Layero](https://layero.ru) — publish a local directory with one comma
|
|
|
7
7
|
If you're an agent reading this: the user asked you to deploy their site with Layero. Do exactly this — no git repo, no GitHub setup, no dashboard visits required:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx layero@latest init
|
|
11
|
-
npx layero login
|
|
12
|
-
npx layero deploy --json
|
|
10
|
+
npx layero@latest init # scaffolds .layero/ + adds a block to AGENTS.md
|
|
11
|
+
npx layero@latest login # one-time: prints a URL, user clicks once
|
|
12
|
+
npx layero@latest deploy --json # ships current dir, streams JSON events to stdout
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
`layero deploy` prints `{"event":"ready","url":"https://..."}` on success. Show the URL to the user. That's the whole job.
|
|
@@ -33,7 +33,7 @@ Requires Node.js ≥ 20.
|
|
|
33
33
|
## Quick start
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
layero login # device-flow: prints a URL + code, you sign in once (
|
|
36
|
+
layero login # device-flow: prints a URL + code, you sign in once (email code or Yandex ID)
|
|
37
37
|
cd my-site
|
|
38
38
|
layero deploy # auto-detects framework, packs, uploads, builds, ships
|
|
39
39
|
```
|
package/dist/bin/layero.js
CHANGED
|
@@ -22,6 +22,7 @@ import { envListCmd, envSetCmd, envUnsetCmd } from "../commands/env.js";
|
|
|
22
22
|
import { analyticsConnectCmd, analyticsDisconnectCmd, analyticsStatsCmd, analyticsStatusCmd, } from "../commands/analytics.js";
|
|
23
23
|
import { domainsAddCmd, domainsListCmd, domainsPrimaryCmd, domainsRemoveCmd, domainsVerifyCmd, } from "../commands/domains.js";
|
|
24
24
|
import { LayeroError, detectMode, emit } from "../agent.js";
|
|
25
|
+
import { ApiError } from "../api.js";
|
|
25
26
|
import { notifyIfOutdated } from "../update-notifier.js";
|
|
26
27
|
// Read version from the shipped package.json (two levels up from dist/bin/).
|
|
27
28
|
const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
@@ -36,7 +37,7 @@ async function main() {
|
|
|
36
37
|
.option("--debug", "print a full stack trace when a command errors");
|
|
37
38
|
program
|
|
38
39
|
.command("login")
|
|
39
|
-
.description("Authenticate via browser (
|
|
40
|
+
.description("Authenticate via browser (email code or Yandex ID). Opens a one-time URL — no localhost server required.")
|
|
40
41
|
.addHelpText("after", "\nExamples:\n $ layero login\n $ npx layero login")
|
|
41
42
|
.action(async (opts) => {
|
|
42
43
|
await loginCmd(opts);
|
|
@@ -293,7 +294,57 @@ async function main() {
|
|
|
293
294
|
// `--json` stdout stays parseable. Bounded + never-throwing by construction.
|
|
294
295
|
await notifyIfOutdated(VERSION);
|
|
295
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Ответы API, которые означают НОРМАЛЬНОЕ состояние аккаунта, а не поломку.
|
|
299
|
+
*
|
|
300
|
+
* До 02.08.2026 их не разбирал никто, и они доезжали до пользователя как
|
|
301
|
+
* `code: "internal"` с советом «сообщите о баге»:
|
|
302
|
+
*
|
|
303
|
+
* Error: internal
|
|
304
|
+
* API GET /auth/me → 401: {"detail":"Invalid token"}
|
|
305
|
+
* → re-run with --debug for a stack trace, or report at …/contacts/
|
|
306
|
+
*
|
|
307
|
+
* 401 здесь — не редкость, а расписание: TTL токена 168 часов, то есть КАЖДЫЙ
|
|
308
|
+
* пользователь CLI упирается в это раз в неделю. На момент проверки токен был
|
|
309
|
+
* протухшим у 66 из 115 человек, деплоивших через CLI за месяц. Ни один из них
|
|
310
|
+
* не получил единственную нужную подсказку — «выполните layero login».
|
|
311
|
+
*
|
|
312
|
+
* 402 — лимит тарифа (например, `max_projects` на free). Тоже обычное дело, а
|
|
313
|
+
* не сбой платформы: чинится сменой тарифа или удалением проекта, и текст
|
|
314
|
+
* ошибки обязан говорить именно это.
|
|
315
|
+
*/
|
|
316
|
+
function asAccountStateError(err) {
|
|
317
|
+
if (!(err instanceof ApiError))
|
|
318
|
+
return null;
|
|
319
|
+
if (err.status === 401) {
|
|
320
|
+
return new LayeroError("auth_expired", "Вход больше не действует — токен протух или сессия отозвана.", "выполните `layero login`");
|
|
321
|
+
}
|
|
322
|
+
if (err.status === 402) {
|
|
323
|
+
let detail = {};
|
|
324
|
+
try {
|
|
325
|
+
const parsed = JSON.parse(err.body);
|
|
326
|
+
detail = parsed.detail ?? {};
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
/* тело не JSON — обойдёмся общим текстом */
|
|
330
|
+
}
|
|
331
|
+
const limits = {
|
|
332
|
+
max_projects: "проектов",
|
|
333
|
+
custom_domains: "своих доменов",
|
|
334
|
+
team_orgs: "командных организаций",
|
|
335
|
+
};
|
|
336
|
+
const what = detail.feature ? limits[detail.feature] ?? detail.feature : null;
|
|
337
|
+
const counts = typeof detail.limit === "number" && typeof detail.observed === "number"
|
|
338
|
+
? ` (${detail.observed} при лимите ${detail.limit})`
|
|
339
|
+
: "";
|
|
340
|
+
return new LayeroError("plan_limit", what
|
|
341
|
+
? `Тариф не позволяет больше ${what}${counts}.`
|
|
342
|
+
: "Действие недоступно на текущем тарифе.", "смените тариф на app.layero.ru/billing или освободите место, удалив ненужное");
|
|
343
|
+
}
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
296
346
|
main().catch((err) => {
|
|
347
|
+
err = asAccountStateError(err) ?? err;
|
|
297
348
|
const mode = detectMode();
|
|
298
349
|
// `--debug` (real global flag) prints the full stack trace for any error,
|
|
299
350
|
// structured or not, so the next_action hint that mentions it is honest.
|
package/dist/commands/init.js
CHANGED
|
@@ -28,7 +28,7 @@ device-flow automatically and emits a JSON line:
|
|
|
28
28
|
\`\`\`
|
|
29
29
|
|
|
30
30
|
Render the \`url\` as a clickable link in chat. The user opens it, signs in
|
|
31
|
-
(
|
|
31
|
+
(by an emailed code or with Yandex ID — Layero creates the account on first sign-in),
|
|
32
32
|
clicks "Разрешить доступ", and the CLI's poll loop picks up the token within
|
|
33
33
|
2 seconds. No localhost server is involved — the browser can be on a
|
|
34
34
|
different machine than the CLI.
|
package/dist/update-notifier.js
CHANGED
|
@@ -22,7 +22,16 @@
|
|
|
22
22
|
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import os from "node:os";
|
|
25
|
-
|
|
25
|
+
// ВАЖНО: пакумент (`/layero`), а НЕ `/layero/latest`.
|
|
26
|
+
//
|
|
27
|
+
// Сокращённый тип `application/vnd.npm.install-v1+json` npm отдаёт только на
|
|
28
|
+
// пакументе. На адресе конкретной версии (`/<pkg>/latest`) тот же заголовок
|
|
29
|
+
// даёт **406 Not Acceptable** — а `fetchLatest` глотает любой не-ok и молча
|
|
30
|
+
// возвращает null. Проверено 02.08.2026: сочетание было именно таким, и нагон
|
|
31
|
+
// версии не печатался НИ РАЗУ ни в одном релизе. Тесты этого не поймали,
|
|
32
|
+
// потому что проверяли только чистые `compareVersions`/`updateNotice`, а до
|
|
33
|
+
// сети не доходили.
|
|
34
|
+
const REGISTRY_URL = "https://registry.npmjs.org/layero";
|
|
26
35
|
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // once a day is plenty
|
|
27
36
|
const FETCH_TIMEOUT_MS = 1500;
|
|
28
37
|
function cachePath() {
|
|
@@ -73,7 +82,8 @@ async function fetchLatest() {
|
|
|
73
82
|
if (!res.ok)
|
|
74
83
|
return null;
|
|
75
84
|
const body = (await res.json());
|
|
76
|
-
|
|
85
|
+
const latest = body["dist-tags"]?.latest;
|
|
86
|
+
return typeof latest === "string" ? latest : null;
|
|
77
87
|
}
|
|
78
88
|
finally {
|
|
79
89
|
clearTimeout(timer);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "layero",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.21",
|
|
4
4
|
"description": "Layero CLI — publish a local site with one command. No git, no GitHub, agent-friendly (Cursor, Claude Code).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
"deployment",
|
|
15
15
|
"hosting",
|
|
16
16
|
"static-hosting",
|
|
17
|
-
"cdn",
|
|
18
17
|
"cli",
|
|
19
18
|
"static-site",
|
|
20
19
|
"spa",
|
package/scripts/postinstall.cjs
CHANGED
|
@@ -38,7 +38,7 @@ const lines = [
|
|
|
38
38
|
"",
|
|
39
39
|
`${c.green}${c.bold}✨ Layero CLI installed${c.reset}`,
|
|
40
40
|
"",
|
|
41
|
-
` ${c.cyan}${cmd("layero login")}${c.reset} ${c.dim}sign in (
|
|
41
|
+
` ${c.cyan}${cmd("layero login")}${c.reset} ${c.dim}sign in (email code or Yandex ID)${c.reset}`,
|
|
42
42
|
` ${c.cyan}${cmd("layero init")}${c.reset} ${c.dim}scaffold .layero/ + agent docs${c.reset}`,
|
|
43
43
|
` ${c.cyan}${cmd("layero deploy")}${c.reset} ${c.dim}ship the current directory${c.reset}`,
|
|
44
44
|
"",
|
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
// GENERATED by core/detection/gen.mjs — DO NOT EDIT. Canonical: core/detection/.
|
|
2
|
-
// Unified framework / build-command / output / runtime detection (TypeScript).
|
|
3
|
-
//
|
|
4
|
-
// A faithful port of detect_core.py — SAME spec, SAME algorithm — kept in
|
|
5
|
-
// lockstep with it. PURE: a `Snapshot` in, a `BuildPlan` out, no `node:fs`,
|
|
6
|
-
// so it is safe to vendor into BOTH the CLI (Node) and the control-plane
|
|
7
|
-
// (browser). Each caller builds the Snapshot at its own fidelity (the CLI from
|
|
8
|
-
// the filesystem, drop-deploy from the dropped file tree).
|
|
9
|
-
//
|
|
10
|
-
// stdlib/spec only — vendored verbatim by gen.mjs into <consumer>/_detection/.
|
|
11
|
-
// The spec is emitted as a TS const (spec.ts) so there is no JSON-import-at-
|
|
12
|
-
// runtime friction under tsc + Node ESM. Edit core/detection/, rerun gen.mjs.
|
|
13
|
-
import { SPEC } from "./spec.js";
|
|
14
|
-
const CONFIG_EXTENSIONS = [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"];
|
|
15
|
-
const JS_COMMENT_RE = /\/\/[^\n]*|\/\*[\s\S]*?\*\//gm;
|
|
16
|
-
const NEXT_EXPORT_RE = /output\s*:\s*['"]export['"]/m;
|
|
17
|
-
const NEXT_DISTDIR_RE = /distDir\s*:\s*['"]([^'"]+)['"]/m;
|
|
18
|
-
const OUTPUT_KEY_RE = /\b(?:outDir|distDir|output_dir)\s*[:=]\s*['"]([^'"]+)['"]/;
|
|
19
|
-
const NEXT_CONFIG_NAMES = ["next.config.mjs", "next.config.ts", "next.config.js", "next.config.cjs"];
|
|
20
|
-
function hasDep(pkg, name) {
|
|
21
|
-
if (!pkg)
|
|
22
|
-
return false;
|
|
23
|
-
return Boolean((pkg.dependencies ?? {})[name] ?? (pkg.devDependencies ?? {})[name]);
|
|
24
|
-
}
|
|
25
|
-
function scriptsOf(pkg) {
|
|
26
|
-
return (pkg?.scripts && typeof pkg.scripts === "object") ? pkg.scripts : {};
|
|
27
|
-
}
|
|
28
|
-
export function detectPackageManager(files) {
|
|
29
|
-
const pm = SPEC.package_managers;
|
|
30
|
-
for (const name of pm.order) {
|
|
31
|
-
const mgr = pm.managers[name];
|
|
32
|
-
if (mgr.lockfiles.some((lf) => files.has(lf)))
|
|
33
|
-
return { name, ...mgr, hasLock: true };
|
|
34
|
-
}
|
|
35
|
-
return { name: "npm", ...pm.managers.npm, hasLock: false };
|
|
36
|
-
}
|
|
37
|
-
function configBasenamePresent(snap, basename) {
|
|
38
|
-
return CONFIG_EXTENSIONS.some((ext) => snap.files.has(basename + ext));
|
|
39
|
-
}
|
|
40
|
-
function frameworkMatches(fw, snap) {
|
|
41
|
-
const sig = fw.signals;
|
|
42
|
-
const pkg = snap.packageJson;
|
|
43
|
-
if (sig.always)
|
|
44
|
-
return true;
|
|
45
|
-
for (const dep of sig.deps ?? [])
|
|
46
|
-
if (hasDep(pkg, dep))
|
|
47
|
-
return true;
|
|
48
|
-
for (const cf of sig.config_files ?? [])
|
|
49
|
-
if (snap.files.has(cf))
|
|
50
|
-
return true;
|
|
51
|
-
if (sig.config_basename && configBasenamePresent(snap, sig.config_basename))
|
|
52
|
-
return true;
|
|
53
|
-
for (const name of sig.script_names ?? [])
|
|
54
|
-
if (name in scriptsOf(pkg))
|
|
55
|
-
return true;
|
|
56
|
-
for (const sub of sig.script_contains ?? []) {
|
|
57
|
-
if (Object.values(scriptsOf(pkg)).some((v) => typeof v === "string" && v.includes(sub)))
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
for (const d of sig.dir_markers ?? [])
|
|
61
|
-
if (snap.dirs.has(d))
|
|
62
|
-
return true;
|
|
63
|
-
if (sig.config_tokens) {
|
|
64
|
-
for (const cf of sig.config_token_files ?? []) {
|
|
65
|
-
if (snap.files.has(cf)) {
|
|
66
|
-
const text = snap.configTexts[cf] ?? "";
|
|
67
|
-
if (sig.config_tokens.some((tok) => text.includes(tok)))
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (sig.html_when_no_spa_deps) {
|
|
73
|
-
if (pkg !== null && (sig.spa_hint_deps ?? []).some((d) => hasDep(pkg, d)))
|
|
74
|
-
return false;
|
|
75
|
-
return snap.hasHtml;
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
const stripJsComments = (t) => t.replace(JS_COMMENT_RE, "");
|
|
80
|
-
export function nextConfigStaticExport(text) {
|
|
81
|
-
if (text === null || text === undefined)
|
|
82
|
-
return null;
|
|
83
|
-
return NEXT_EXPORT_RE.test(stripJsComments(text));
|
|
84
|
-
}
|
|
85
|
-
function extractOutkey(text) {
|
|
86
|
-
if (!text)
|
|
87
|
-
return null;
|
|
88
|
-
const m = OUTPUT_KEY_RE.exec(text);
|
|
89
|
-
if (!m)
|
|
90
|
-
return null;
|
|
91
|
-
let val = m[1].trim();
|
|
92
|
-
if (val.startsWith("./"))
|
|
93
|
-
val = val.slice(2);
|
|
94
|
-
return val || null;
|
|
95
|
-
}
|
|
96
|
-
function extractNextDistdir(text) {
|
|
97
|
-
if (!text)
|
|
98
|
-
return null;
|
|
99
|
-
const m = NEXT_DISTDIR_RE.exec(stripJsComments(text));
|
|
100
|
-
return m ? m[1] : null;
|
|
101
|
-
}
|
|
102
|
-
export function angularOutputDir(text) {
|
|
103
|
-
if (!text)
|
|
104
|
-
return null;
|
|
105
|
-
let cfg;
|
|
106
|
-
try {
|
|
107
|
-
cfg = JSON.parse(text);
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
if (!cfg || typeof cfg !== "object")
|
|
113
|
-
return null;
|
|
114
|
-
const projects = cfg.projects;
|
|
115
|
-
if (!projects || typeof projects !== "object")
|
|
116
|
-
return null;
|
|
117
|
-
const names = Object.keys(projects);
|
|
118
|
-
if (!names.length)
|
|
119
|
-
return null;
|
|
120
|
-
const def = cfg.defaultProject;
|
|
121
|
-
const name = def && projects[def] ? def : names[0];
|
|
122
|
-
const buildCfg = projects[name]?.architect?.build;
|
|
123
|
-
if (!buildCfg || typeof buildCfg !== "object")
|
|
124
|
-
return null;
|
|
125
|
-
const opts = buildCfg.options ?? {};
|
|
126
|
-
const raw = typeof opts === "object" && opts ? opts.outputPath : undefined;
|
|
127
|
-
let out = typeof raw === "string" && raw.trim() ? raw.trim() : `dist/${name}`;
|
|
128
|
-
if (out.startsWith("./"))
|
|
129
|
-
out = out.slice(2);
|
|
130
|
-
const builder = typeof buildCfg.builder === "string" ? buildCfg.builder : "";
|
|
131
|
-
return builder.endsWith(":application") ? `${out}/browser` : out;
|
|
132
|
-
}
|
|
133
|
-
function nextConfigName(snap) {
|
|
134
|
-
for (const n of NEXT_CONFIG_NAMES)
|
|
135
|
-
if (n in snap.configTexts || snap.files.has(n))
|
|
136
|
-
return n;
|
|
137
|
-
return NEXT_CONFIG_NAMES[0];
|
|
138
|
-
}
|
|
139
|
-
function configTextForBasename(snap, basename) {
|
|
140
|
-
for (const ext of CONFIG_EXTENSIONS) {
|
|
141
|
-
const t = snap.configTexts[basename + ext];
|
|
142
|
-
if (t !== undefined)
|
|
143
|
-
return t;
|
|
144
|
-
}
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
function resolveOutputDir(fw, snap) {
|
|
148
|
-
const out = fw.output_dir;
|
|
149
|
-
const def = out.default;
|
|
150
|
-
switch (out.extract) {
|
|
151
|
-
case "next_distdir":
|
|
152
|
-
return extractNextDistdir(snap.configTexts[nextConfigName(snap)]) ?? def;
|
|
153
|
-
case "outkey":
|
|
154
|
-
return extractOutkey(configTextForBasename(snap, out.extract_config_basename ?? "")) ?? def;
|
|
155
|
-
case "angular":
|
|
156
|
-
return angularOutputDir(snap.configTexts["angular.json"]) ?? def;
|
|
157
|
-
case "vitepress_docs":
|
|
158
|
-
return [...snap.files].some((f) => f.startsWith("docs/.vitepress/config")) ? "docs/.vitepress/dist" : def;
|
|
159
|
-
default:
|
|
160
|
-
return def;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
function resolveBuildCmd(fw, snap, pmRun) {
|
|
164
|
-
const build = fw.build;
|
|
165
|
-
if (build.no_build)
|
|
166
|
-
return null;
|
|
167
|
-
const scripts = scriptsOf(snap.packageJson);
|
|
168
|
-
for (const entry of build.scripts ?? []) {
|
|
169
|
-
const val = scripts[entry.name];
|
|
170
|
-
if (typeof val !== "string" || !val.trim())
|
|
171
|
-
continue;
|
|
172
|
-
if (entry.contains && !val.includes(entry.contains))
|
|
173
|
-
continue;
|
|
174
|
-
return `${pmRun} ${entry.name}`;
|
|
175
|
-
}
|
|
176
|
-
const fallback = build.fallback;
|
|
177
|
-
if (!fallback)
|
|
178
|
-
return null;
|
|
179
|
-
return build.npx === false ? fallback : `npx ${fallback}`;
|
|
180
|
-
}
|
|
181
|
-
function sortedFrameworks() {
|
|
182
|
-
return [...SPEC.frameworks].sort((a, b) => a.order - b.order);
|
|
183
|
-
}
|
|
184
|
-
function matchFramework(snap) {
|
|
185
|
-
for (const fw of sortedFrameworks())
|
|
186
|
-
if (frameworkMatches(fw, snap))
|
|
187
|
-
return fw;
|
|
188
|
-
return SPEC.frameworks.find((f) => f.name === "generic");
|
|
189
|
-
}
|
|
190
|
-
function frameworkByName(name) {
|
|
191
|
-
const n = (name ?? "").trim().toLowerCase();
|
|
192
|
-
if (!n)
|
|
193
|
-
return null;
|
|
194
|
-
for (const fw of SPEC.frameworks)
|
|
195
|
-
if (fw.name === n || (fw.aliases ?? []).includes(n))
|
|
196
|
-
return fw;
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
function staticUnit(pm) {
|
|
200
|
-
return {
|
|
201
|
-
role: "static", framework: "static", root: "", packageManager: pm.name,
|
|
202
|
-
installCmd: null, buildCmd: null, outputDir: ".", startCmd: null, runtimeKind: null, port: null,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
function spaUnit(fw, snap, pm) {
|
|
206
|
-
return {
|
|
207
|
-
role: "static", framework: fw.name, root: "", packageManager: pm.name,
|
|
208
|
-
installCmd: fw.skip_install ? null : pm.install,
|
|
209
|
-
buildCmd: resolveBuildCmd(fw, snap, pm.run),
|
|
210
|
-
outputDir: resolveOutputDir(fw, snap),
|
|
211
|
-
startCmd: null, runtimeKind: null, port: null,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
export function detectFramework(snap, frameworkHint) {
|
|
215
|
-
const pm = detectPackageManager(snap.files);
|
|
216
|
-
let unknown = null;
|
|
217
|
-
let fw = null;
|
|
218
|
-
if (frameworkHint) {
|
|
219
|
-
fw = frameworkByName(frameworkHint);
|
|
220
|
-
if (fw === null)
|
|
221
|
-
unknown = frameworkHint;
|
|
222
|
-
}
|
|
223
|
-
if (fw === null)
|
|
224
|
-
fw = matchFramework(snap);
|
|
225
|
-
if (fw.name === "static")
|
|
226
|
-
return [staticUnit(pm), unknown];
|
|
227
|
-
return [spaUnit(fw, snap, pm), unknown];
|
|
228
|
-
}
|
|
229
|
-
export function detectRuntime(snap) {
|
|
230
|
-
const rt = SPEC.runtime;
|
|
231
|
-
const pkg = snap.packageJson;
|
|
232
|
-
if (hasDep(pkg, "next")) {
|
|
233
|
-
const text = snap.configTexts[nextConfigName(snap)];
|
|
234
|
-
if (nextConfigStaticExport(text ?? null) === false)
|
|
235
|
-
return "ssr_next";
|
|
236
|
-
}
|
|
237
|
-
if (pkg && rt.node_web_signals.some((d) => hasDep(pkg, d)) &&
|
|
238
|
-
!rt.node_frontend_deps.some((d) => hasDep(pkg, d))) {
|
|
239
|
-
return "node_web";
|
|
240
|
-
}
|
|
241
|
-
const reqs = (snap.requirementsTxt ?? "").toLowerCase();
|
|
242
|
-
if (reqs) {
|
|
243
|
-
const hasEntry = rt.python_entrypoints.some((f) => snap.files.has(f));
|
|
244
|
-
if (reqs.includes("streamlit") && snap.files.has("app.py"))
|
|
245
|
-
return "streamlit";
|
|
246
|
-
if (reqs.includes("gradio") && snap.files.has("app.py"))
|
|
247
|
-
return "gradio";
|
|
248
|
-
if (hasEntry && rt.py_web_signals.some((s) => reqs.includes(s)))
|
|
249
|
-
return "python_web";
|
|
250
|
-
}
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
function runtimeFromLayero(layero) {
|
|
254
|
-
if (!layero || typeof layero !== "object")
|
|
255
|
-
return null;
|
|
256
|
-
const rt = SPEC.runtime;
|
|
257
|
-
const aliases = rt.layero_runtime_aliases;
|
|
258
|
-
let kind = layero.runtime;
|
|
259
|
-
if (typeof kind === "string") {
|
|
260
|
-
kind = aliases[kind] ?? kind;
|
|
261
|
-
if (kind in rt.kinds)
|
|
262
|
-
return [kind, layero];
|
|
263
|
-
}
|
|
264
|
-
const fe = layero.frontend, be = layero.backend;
|
|
265
|
-
if (fe && typeof fe === "object" && be && typeof be === "object") {
|
|
266
|
-
let beKind = be.runtime;
|
|
267
|
-
if (typeof beKind === "string") {
|
|
268
|
-
beKind = aliases[beKind] ?? beKind;
|
|
269
|
-
if (["python_web", "node_web", "ssr_next"].includes(beKind))
|
|
270
|
-
return [`__fullstack__:${beKind}`, layero];
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
const PY_FRAMEWORK_ORDER = [
|
|
276
|
-
["django", "django"], ["fastapi", "fastapi"], ["flask", "flask"], ["litestar", "litestar"],
|
|
277
|
-
["starlite", "litestar"], ["quart", "quart"], ["sanic", "sanic"], ["blacksheep", "blacksheep"],
|
|
278
|
-
["falcon", "falcon"], ["bottle", "bottle"], ["pyramid", "pyramid"], ["aiohttp", "aiohttp"],
|
|
279
|
-
["tornado", "tornado"], ["starlette", "starlette"],
|
|
280
|
-
];
|
|
281
|
-
const NODE_FRAMEWORK_ORDER = [
|
|
282
|
-
["@nestjs/core", "nestjs"], ["@adonisjs/core", "adonis"], ["fastify", "fastify"],
|
|
283
|
-
["@hapi/hapi", "hapi"], ["hapi", "hapi"], ["koa", "koa"], ["hono", "hono"],
|
|
284
|
-
["restify", "restify"], ["express", "express"],
|
|
285
|
-
];
|
|
286
|
-
export function detectRuntimeFramework(snap) {
|
|
287
|
-
const reqs = (snap.requirementsTxt ?? "").toLowerCase();
|
|
288
|
-
if (reqs)
|
|
289
|
-
for (const [token, name] of PY_FRAMEWORK_ORDER)
|
|
290
|
-
if (reqs.includes(token))
|
|
291
|
-
return name;
|
|
292
|
-
if (snap.packageJson)
|
|
293
|
-
for (const [dep, name] of NODE_FRAMEWORK_ORDER)
|
|
294
|
-
if (hasDep(snap.packageJson, dep))
|
|
295
|
-
return name;
|
|
296
|
-
return null;
|
|
297
|
-
}
|
|
298
|
-
function runtimePlan(snap, pm, projectType) {
|
|
299
|
-
const meta = SPEC.runtime.kinds[projectType];
|
|
300
|
-
const framework = detectRuntimeFramework(snap) ?? projectType;
|
|
301
|
-
const unit = {
|
|
302
|
-
role: "backend", framework, root: "",
|
|
303
|
-
packageManager: snap.packageJson ? pm.name : null,
|
|
304
|
-
installCmd: null, buildCmd: null, outputDir: null,
|
|
305
|
-
startCmd: null, runtimeKind: meta.runtime_kind, port: meta.port,
|
|
306
|
-
};
|
|
307
|
-
return {
|
|
308
|
-
projectKind: projectType === "ssr_next" ? "ssr" : "runtime",
|
|
309
|
-
framework, buildCmd: null, outputDir: null, packageManager: pm.name,
|
|
310
|
-
projectType, runtimeKind: meta.runtime_kind, units: [unit],
|
|
311
|
-
apiPrefix: null, confident: true, label: projectType,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
function fullstackPlan(_snap, pm, layero, beProjectType) {
|
|
315
|
-
const meta = SPEC.runtime.kinds[beProjectType];
|
|
316
|
-
const fe = (layero.frontend ?? {});
|
|
317
|
-
const be = (layero.backend ?? {});
|
|
318
|
-
const apiPrefix = String(layero.api_prefix ?? "/api").trim() || "/api";
|
|
319
|
-
const feUnit = {
|
|
320
|
-
role: "frontend", framework: String(fe.framework ?? "generic"),
|
|
321
|
-
root: String(fe.root ?? "").replace(/^\/+|\/+$/g, ""),
|
|
322
|
-
packageManager: null, installCmd: null,
|
|
323
|
-
buildCmd: fe.build ? String(fe.build) : null,
|
|
324
|
-
outputDir: String(fe.output ?? "dist").replace(/^\/+|\/+$/g, ""),
|
|
325
|
-
startCmd: null, runtimeKind: null, port: null,
|
|
326
|
-
};
|
|
327
|
-
const beUnit = {
|
|
328
|
-
role: "backend", framework: beProjectType,
|
|
329
|
-
root: String(be.root ?? "").replace(/^\/+|\/+$/g, ""),
|
|
330
|
-
packageManager: null, installCmd: null, buildCmd: null, outputDir: null,
|
|
331
|
-
startCmd: be.start ? String(be.start) : null,
|
|
332
|
-
runtimeKind: meta.runtime_kind, port: Number(be.port ?? meta.port),
|
|
333
|
-
};
|
|
334
|
-
return {
|
|
335
|
-
projectKind: "fullstack", framework: beProjectType, buildCmd: null, outputDir: null,
|
|
336
|
-
packageManager: pm.name, projectType: beProjectType, runtimeKind: meta.runtime_kind,
|
|
337
|
-
units: [feUnit, beUnit], apiPrefix, confident: true, label: "Fullstack",
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
export function detect(snap, frameworkHint) {
|
|
341
|
-
const pm = detectPackageManager(snap.files);
|
|
342
|
-
const lj = runtimeFromLayero(snap.layeroJson);
|
|
343
|
-
if (lj !== null) {
|
|
344
|
-
const [kind, layero] = lj;
|
|
345
|
-
if (kind.startsWith("__fullstack__:"))
|
|
346
|
-
return fullstackPlan(snap, pm, layero, kind.split(":")[1]);
|
|
347
|
-
return runtimePlan(snap, pm, kind);
|
|
348
|
-
}
|
|
349
|
-
const rkind = detectRuntime(snap);
|
|
350
|
-
if (rkind !== null)
|
|
351
|
-
return runtimePlan(snap, pm, rkind);
|
|
352
|
-
let fw = frameworkHint ? frameworkByName(frameworkHint) : null;
|
|
353
|
-
if (fw === null)
|
|
354
|
-
fw = matchFramework(snap);
|
|
355
|
-
let unit;
|
|
356
|
-
let kind;
|
|
357
|
-
let confident;
|
|
358
|
-
if (fw.name === "static") {
|
|
359
|
-
unit = staticUnit(pm);
|
|
360
|
-
kind = "static";
|
|
361
|
-
confident = snap.hasHtml;
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
unit = spaUnit(fw, snap, pm);
|
|
365
|
-
kind = "spa";
|
|
366
|
-
confident = fw.name !== "generic";
|
|
367
|
-
}
|
|
368
|
-
return {
|
|
369
|
-
projectKind: kind, framework: fw.name, buildCmd: unit.buildCmd, outputDir: unit.outputDir,
|
|
370
|
-
packageManager: pm.name, projectType: "spa", runtimeKind: null, units: [unit],
|
|
371
|
-
apiPrefix: null, confident, label: fw.label,
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
/** Build a Snapshot from already-read inputs (caller does the IO). */
|
|
375
|
-
export function snapshotFromInputs(opts) {
|
|
376
|
-
const files = new Set(opts.files ?? []);
|
|
377
|
-
const hasHtml = opts.hasHtml ?? [...files].some((f) => f.endsWith(".html") || f.endsWith(".htm"));
|
|
378
|
-
return {
|
|
379
|
-
packageJson: opts.packageJson ?? null,
|
|
380
|
-
requirementsTxt: opts.requirementsTxt ?? null,
|
|
381
|
-
files,
|
|
382
|
-
dirs: new Set(opts.dirs ?? []),
|
|
383
|
-
configTexts: opts.configTexts ?? {},
|
|
384
|
-
hasHtml,
|
|
385
|
-
layeroJson: opts.layeroJson ?? null,
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
// Config files whose CONTENT the detector reads (for the caller's snapshot builder).
|
|
389
|
-
export const CONFIG_TEXT_FILES = [
|
|
390
|
-
...NEXT_CONFIG_NAMES, "angular.json", "config.toml", "config.yaml", "config.json",
|
|
391
|
-
];
|
|
392
|
-
export const CONFIG_TEXT_BASENAMES = ["vite.config", "astro.config"];
|
|
393
|
-
export { CONFIG_EXTENSIONS };
|
package/dist/_detection/spec.js
DELETED
|
@@ -1,664 +0,0 @@
|
|
|
1
|
-
// GENERATED by core/detection/gen.mjs — DO NOT EDIT. Canonical: core/detection/detection.spec.json.
|
|
2
|
-
// eslint-disable-next-line
|
|
3
|
-
export const SPEC = {
|
|
4
|
-
"$schema": "./schema.json",
|
|
5
|
-
"spec_version": 1,
|
|
6
|
-
"_doc": "SINGLE SOURCE OF TRUTH for Layero framework/build/output/runtime detection. Transcribed from the canonical builder classes (core/builder/src/frameworks/*.py + runtime_detect.py + pkg_manager.py). Consumed by detect_core.py (backend + builder + runtime-builder) and detect_core.ts (CLI + control-plane). DATA ONLY — disk-aware refinement (discover_served_root, start-command entry probing) lives in builder code keyed by the identity this spec produces. See core/detection/README.md.",
|
|
7
|
-
"frameworks": [
|
|
8
|
-
{
|
|
9
|
-
"name": "nextjs",
|
|
10
|
-
"label": "Next.js",
|
|
11
|
-
"order": 10,
|
|
12
|
-
"aliases": [
|
|
13
|
-
"next",
|
|
14
|
-
"next.js"
|
|
15
|
-
],
|
|
16
|
-
"signals": {
|
|
17
|
-
"deps": [
|
|
18
|
-
"next"
|
|
19
|
-
],
|
|
20
|
-
"config_files": [
|
|
21
|
-
"next.config.js",
|
|
22
|
-
"next.config.mjs",
|
|
23
|
-
"next.config.ts",
|
|
24
|
-
"next.config.cjs"
|
|
25
|
-
]
|
|
26
|
-
},
|
|
27
|
-
"build": {
|
|
28
|
-
"scripts": [
|
|
29
|
-
{
|
|
30
|
-
"name": "build"
|
|
31
|
-
}
|
|
32
|
-
],
|
|
33
|
-
"fallback": "next build"
|
|
34
|
-
},
|
|
35
|
-
"output_dir": {
|
|
36
|
-
"default": "out",
|
|
37
|
-
"extract": "next_distdir",
|
|
38
|
-
"reject_root_override": true
|
|
39
|
-
},
|
|
40
|
-
"ssr": {
|
|
41
|
-
"rule": "next_no_export",
|
|
42
|
-
"runtime_kind": "ssr_node"
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"name": "nuxt",
|
|
47
|
-
"label": "Nuxt",
|
|
48
|
-
"order": 20,
|
|
49
|
-
"aliases": [
|
|
50
|
-
"nuxtjs",
|
|
51
|
-
"nuxt3",
|
|
52
|
-
"nuxt2"
|
|
53
|
-
],
|
|
54
|
-
"signals": {
|
|
55
|
-
"deps": [
|
|
56
|
-
"nuxt",
|
|
57
|
-
"nuxt3"
|
|
58
|
-
],
|
|
59
|
-
"config_files": [
|
|
60
|
-
"nuxt.config.ts",
|
|
61
|
-
"nuxt.config.js",
|
|
62
|
-
"nuxt.config.mjs"
|
|
63
|
-
]
|
|
64
|
-
},
|
|
65
|
-
"build": {
|
|
66
|
-
"scripts": [
|
|
67
|
-
{
|
|
68
|
-
"name": "generate"
|
|
69
|
-
}
|
|
70
|
-
],
|
|
71
|
-
"fallback": "nuxt generate"
|
|
72
|
-
},
|
|
73
|
-
"output_dir": {
|
|
74
|
-
"default": ".output/public"
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
"name": "remix",
|
|
79
|
-
"label": "Remix / React Router v7",
|
|
80
|
-
"order": 30,
|
|
81
|
-
"aliases": [
|
|
82
|
-
"react-router",
|
|
83
|
-
"react-router-v7",
|
|
84
|
-
"rrv7",
|
|
85
|
-
"rr7",
|
|
86
|
-
"remix-run"
|
|
87
|
-
],
|
|
88
|
-
"signals": {
|
|
89
|
-
"deps": [
|
|
90
|
-
"@remix-run/dev",
|
|
91
|
-
"@remix-run/react",
|
|
92
|
-
"@remix-run/node",
|
|
93
|
-
"@remix-run/serve",
|
|
94
|
-
"@react-router/dev",
|
|
95
|
-
"@react-router/node",
|
|
96
|
-
"@react-router/serve"
|
|
97
|
-
],
|
|
98
|
-
"config_files": [
|
|
99
|
-
"react-router.config.ts",
|
|
100
|
-
"react-router.config.js"
|
|
101
|
-
]
|
|
102
|
-
},
|
|
103
|
-
"build": {
|
|
104
|
-
"scripts": [
|
|
105
|
-
{
|
|
106
|
-
"name": "build"
|
|
107
|
-
}
|
|
108
|
-
],
|
|
109
|
-
"fallback": "react-router build"
|
|
110
|
-
},
|
|
111
|
-
"output_dir": {
|
|
112
|
-
"default": "build/client"
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
"name": "sveltekit",
|
|
117
|
-
"label": "SvelteKit",
|
|
118
|
-
"order": 40,
|
|
119
|
-
"aliases": [
|
|
120
|
-
"svelte",
|
|
121
|
-
"svelte-kit",
|
|
122
|
-
"kit"
|
|
123
|
-
],
|
|
124
|
-
"signals": {
|
|
125
|
-
"deps": [
|
|
126
|
-
"@sveltejs/kit"
|
|
127
|
-
],
|
|
128
|
-
"config_files": [
|
|
129
|
-
"svelte.config.js",
|
|
130
|
-
"svelte.config.ts"
|
|
131
|
-
]
|
|
132
|
-
},
|
|
133
|
-
"build": {
|
|
134
|
-
"scripts": [
|
|
135
|
-
{
|
|
136
|
-
"name": "build"
|
|
137
|
-
}
|
|
138
|
-
],
|
|
139
|
-
"fallback": "vite build"
|
|
140
|
-
},
|
|
141
|
-
"output_dir": {
|
|
142
|
-
"default": "build"
|
|
143
|
-
}
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
"name": "gatsby",
|
|
147
|
-
"label": "Gatsby",
|
|
148
|
-
"order": 50,
|
|
149
|
-
"aliases": [
|
|
150
|
-
"gatsbyjs"
|
|
151
|
-
],
|
|
152
|
-
"signals": {
|
|
153
|
-
"deps": [
|
|
154
|
-
"gatsby"
|
|
155
|
-
],
|
|
156
|
-
"config_files": [
|
|
157
|
-
"gatsby-config.js",
|
|
158
|
-
"gatsby-config.ts"
|
|
159
|
-
]
|
|
160
|
-
},
|
|
161
|
-
"build": {
|
|
162
|
-
"scripts": [
|
|
163
|
-
{
|
|
164
|
-
"name": "build"
|
|
165
|
-
}
|
|
166
|
-
],
|
|
167
|
-
"fallback": "gatsby build"
|
|
168
|
-
},
|
|
169
|
-
"output_dir": {
|
|
170
|
-
"default": "public"
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
"name": "astro",
|
|
175
|
-
"label": "Astro",
|
|
176
|
-
"order": 60,
|
|
177
|
-
"aliases": [
|
|
178
|
-
"astrojs",
|
|
179
|
-
"astro.build"
|
|
180
|
-
],
|
|
181
|
-
"signals": {
|
|
182
|
-
"deps": [
|
|
183
|
-
"astro"
|
|
184
|
-
],
|
|
185
|
-
"config_basename": "astro.config"
|
|
186
|
-
},
|
|
187
|
-
"build": {
|
|
188
|
-
"scripts": [
|
|
189
|
-
{
|
|
190
|
-
"name": "build"
|
|
191
|
-
}
|
|
192
|
-
],
|
|
193
|
-
"fallback": "astro build"
|
|
194
|
-
},
|
|
195
|
-
"output_dir": {
|
|
196
|
-
"default": "dist",
|
|
197
|
-
"extract": "outkey",
|
|
198
|
-
"extract_config_basename": "astro.config"
|
|
199
|
-
}
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
"name": "docusaurus",
|
|
203
|
-
"label": "Docusaurus",
|
|
204
|
-
"order": 70,
|
|
205
|
-
"aliases": [
|
|
206
|
-
"docusaurus2",
|
|
207
|
-
"docusaurus3"
|
|
208
|
-
],
|
|
209
|
-
"signals": {
|
|
210
|
-
"deps": [
|
|
211
|
-
"@docusaurus/core"
|
|
212
|
-
],
|
|
213
|
-
"config_files": [
|
|
214
|
-
"docusaurus.config.js",
|
|
215
|
-
"docusaurus.config.ts",
|
|
216
|
-
"docusaurus.config.mjs"
|
|
217
|
-
]
|
|
218
|
-
},
|
|
219
|
-
"build": {
|
|
220
|
-
"scripts": [
|
|
221
|
-
{
|
|
222
|
-
"name": "build"
|
|
223
|
-
}
|
|
224
|
-
],
|
|
225
|
-
"fallback": "docusaurus build"
|
|
226
|
-
},
|
|
227
|
-
"output_dir": {
|
|
228
|
-
"default": "build"
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
"name": "storybook",
|
|
233
|
-
"label": "Storybook",
|
|
234
|
-
"order": 80,
|
|
235
|
-
"aliases": [],
|
|
236
|
-
"signals": {
|
|
237
|
-
"deps": [
|
|
238
|
-
"@storybook/cli",
|
|
239
|
-
"@storybook/react",
|
|
240
|
-
"@storybook/react-vite",
|
|
241
|
-
"@storybook/react-webpack5",
|
|
242
|
-
"@storybook/vue",
|
|
243
|
-
"@storybook/vue3",
|
|
244
|
-
"@storybook/vue3-vite",
|
|
245
|
-
"@storybook/svelte",
|
|
246
|
-
"@storybook/svelte-vite",
|
|
247
|
-
"@storybook/web-components",
|
|
248
|
-
"@storybook/web-components-vite",
|
|
249
|
-
"@storybook/preact",
|
|
250
|
-
"@storybook/angular",
|
|
251
|
-
"@storybook/nextjs",
|
|
252
|
-
"@storybook/html",
|
|
253
|
-
"@storybook/html-vite",
|
|
254
|
-
"storybook"
|
|
255
|
-
],
|
|
256
|
-
"script_names": [
|
|
257
|
-
"build-storybook"
|
|
258
|
-
],
|
|
259
|
-
"script_contains": [
|
|
260
|
-
"storybook build"
|
|
261
|
-
],
|
|
262
|
-
"dir_markers": [
|
|
263
|
-
".storybook"
|
|
264
|
-
]
|
|
265
|
-
},
|
|
266
|
-
"build": {
|
|
267
|
-
"scripts": [
|
|
268
|
-
{
|
|
269
|
-
"name": "build-storybook"
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
"name": "build",
|
|
273
|
-
"contains": "storybook"
|
|
274
|
-
}
|
|
275
|
-
],
|
|
276
|
-
"fallback": "storybook build"
|
|
277
|
-
},
|
|
278
|
-
"output_dir": {
|
|
279
|
-
"default": "storybook-static"
|
|
280
|
-
}
|
|
281
|
-
},
|
|
282
|
-
{
|
|
283
|
-
"name": "vitepress",
|
|
284
|
-
"label": "VitePress",
|
|
285
|
-
"order": 90,
|
|
286
|
-
"aliases": [],
|
|
287
|
-
"signals": {
|
|
288
|
-
"deps": [
|
|
289
|
-
"vitepress"
|
|
290
|
-
],
|
|
291
|
-
"config_files": [
|
|
292
|
-
".vitepress/config.ts",
|
|
293
|
-
".vitepress/config.js",
|
|
294
|
-
".vitepress/config.mts",
|
|
295
|
-
".vitepress/config.mjs",
|
|
296
|
-
"docs/.vitepress/config.ts",
|
|
297
|
-
"docs/.vitepress/config.js",
|
|
298
|
-
"docs/.vitepress/config.mts",
|
|
299
|
-
"docs/.vitepress/config.mjs"
|
|
300
|
-
]
|
|
301
|
-
},
|
|
302
|
-
"build": {
|
|
303
|
-
"scripts": [
|
|
304
|
-
{
|
|
305
|
-
"name": "docs:build"
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
"name": "build"
|
|
309
|
-
}
|
|
310
|
-
],
|
|
311
|
-
"fallback": "vitepress build"
|
|
312
|
-
},
|
|
313
|
-
"output_dir": {
|
|
314
|
-
"default": ".vitepress/dist",
|
|
315
|
-
"extract": "vitepress_docs"
|
|
316
|
-
}
|
|
317
|
-
},
|
|
318
|
-
{
|
|
319
|
-
"name": "vite",
|
|
320
|
-
"label": "Vite",
|
|
321
|
-
"order": 100,
|
|
322
|
-
"aliases": [
|
|
323
|
-
"vitejs"
|
|
324
|
-
],
|
|
325
|
-
"signals": {
|
|
326
|
-
"deps": [
|
|
327
|
-
"vite"
|
|
328
|
-
],
|
|
329
|
-
"config_basename": "vite.config"
|
|
330
|
-
},
|
|
331
|
-
"build": {
|
|
332
|
-
"scripts": [
|
|
333
|
-
{
|
|
334
|
-
"name": "build"
|
|
335
|
-
}
|
|
336
|
-
],
|
|
337
|
-
"fallback": "vite build"
|
|
338
|
-
},
|
|
339
|
-
"output_dir": {
|
|
340
|
-
"default": "dist",
|
|
341
|
-
"extract": "outkey",
|
|
342
|
-
"extract_config_basename": "vite.config"
|
|
343
|
-
}
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
"name": "angular",
|
|
347
|
-
"label": "Angular",
|
|
348
|
-
"order": 110,
|
|
349
|
-
"aliases": [
|
|
350
|
-
"ng",
|
|
351
|
-
"angularjs"
|
|
352
|
-
],
|
|
353
|
-
"signals": {
|
|
354
|
-
"deps": [
|
|
355
|
-
"@angular/core"
|
|
356
|
-
],
|
|
357
|
-
"config_files": [
|
|
358
|
-
"angular.json"
|
|
359
|
-
]
|
|
360
|
-
},
|
|
361
|
-
"build": {
|
|
362
|
-
"scripts": [
|
|
363
|
-
{
|
|
364
|
-
"name": "build"
|
|
365
|
-
}
|
|
366
|
-
],
|
|
367
|
-
"fallback": "ng build"
|
|
368
|
-
},
|
|
369
|
-
"output_dir": {
|
|
370
|
-
"default": "dist",
|
|
371
|
-
"extract": "angular",
|
|
372
|
-
"extract_config_basename": "angular.json"
|
|
373
|
-
}
|
|
374
|
-
},
|
|
375
|
-
{
|
|
376
|
-
"name": "cra",
|
|
377
|
-
"label": "Create React App",
|
|
378
|
-
"order": 120,
|
|
379
|
-
"aliases": [
|
|
380
|
-
"create-react-app",
|
|
381
|
-
"react-scripts"
|
|
382
|
-
],
|
|
383
|
-
"signals": {
|
|
384
|
-
"deps": [
|
|
385
|
-
"react-scripts"
|
|
386
|
-
]
|
|
387
|
-
},
|
|
388
|
-
"build": {
|
|
389
|
-
"scripts": [
|
|
390
|
-
{
|
|
391
|
-
"name": "build"
|
|
392
|
-
}
|
|
393
|
-
],
|
|
394
|
-
"fallback": "react-scripts build"
|
|
395
|
-
},
|
|
396
|
-
"output_dir": {
|
|
397
|
-
"default": "build"
|
|
398
|
-
}
|
|
399
|
-
},
|
|
400
|
-
{
|
|
401
|
-
"name": "eleventy",
|
|
402
|
-
"label": "Eleventy (11ty)",
|
|
403
|
-
"order": 130,
|
|
404
|
-
"aliases": [
|
|
405
|
-
"11ty"
|
|
406
|
-
],
|
|
407
|
-
"signals": {
|
|
408
|
-
"deps": [
|
|
409
|
-
"@11ty/eleventy",
|
|
410
|
-
"eleventy"
|
|
411
|
-
],
|
|
412
|
-
"config_files": [
|
|
413
|
-
".eleventy.js",
|
|
414
|
-
"eleventy.config.js",
|
|
415
|
-
"eleventy.config.mjs",
|
|
416
|
-
"eleventy.config.cjs"
|
|
417
|
-
]
|
|
418
|
-
},
|
|
419
|
-
"build": {
|
|
420
|
-
"scripts": [
|
|
421
|
-
{
|
|
422
|
-
"name": "build"
|
|
423
|
-
}
|
|
424
|
-
],
|
|
425
|
-
"fallback": "@11ty/eleventy"
|
|
426
|
-
},
|
|
427
|
-
"output_dir": {
|
|
428
|
-
"default": "_site"
|
|
429
|
-
}
|
|
430
|
-
},
|
|
431
|
-
{
|
|
432
|
-
"name": "hugo",
|
|
433
|
-
"label": "Hugo",
|
|
434
|
-
"order": 140,
|
|
435
|
-
"aliases": [],
|
|
436
|
-
"signals": {
|
|
437
|
-
"config_files": [
|
|
438
|
-
"hugo.toml",
|
|
439
|
-
"hugo.yaml",
|
|
440
|
-
"hugo.json"
|
|
441
|
-
],
|
|
442
|
-
"config_token_files": [
|
|
443
|
-
"config.toml",
|
|
444
|
-
"config.yaml",
|
|
445
|
-
"config.json"
|
|
446
|
-
],
|
|
447
|
-
"config_tokens": [
|
|
448
|
-
"baseURL",
|
|
449
|
-
"baseurl",
|
|
450
|
-
"languageCode",
|
|
451
|
-
"languagecode",
|
|
452
|
-
"[params]",
|
|
453
|
-
"[markup]",
|
|
454
|
-
"[menu",
|
|
455
|
-
"[taxonomies]",
|
|
456
|
-
"hugoVersion",
|
|
457
|
-
"minVersion",
|
|
458
|
-
"theme"
|
|
459
|
-
]
|
|
460
|
-
},
|
|
461
|
-
"build": {
|
|
462
|
-
"scripts": [],
|
|
463
|
-
"fallback": "hugo --gc --minify",
|
|
464
|
-
"npx": false
|
|
465
|
-
},
|
|
466
|
-
"skip_install": true,
|
|
467
|
-
"output_dir": {
|
|
468
|
-
"default": "public"
|
|
469
|
-
}
|
|
470
|
-
},
|
|
471
|
-
{
|
|
472
|
-
"name": "static",
|
|
473
|
-
"label": "Static (no build)",
|
|
474
|
-
"order": 150,
|
|
475
|
-
"aliases": [
|
|
476
|
-
"html",
|
|
477
|
-
"plain"
|
|
478
|
-
],
|
|
479
|
-
"signals": {
|
|
480
|
-
"html_when_no_spa_deps": true,
|
|
481
|
-
"spa_hint_deps": [
|
|
482
|
-
"next",
|
|
483
|
-
"react-scripts",
|
|
484
|
-
"vite",
|
|
485
|
-
"astro",
|
|
486
|
-
"nuxt",
|
|
487
|
-
"@sveltejs/kit",
|
|
488
|
-
"gatsby"
|
|
489
|
-
]
|
|
490
|
-
},
|
|
491
|
-
"build": {
|
|
492
|
-
"no_build": true
|
|
493
|
-
},
|
|
494
|
-
"output_dir": {
|
|
495
|
-
"default": "."
|
|
496
|
-
}
|
|
497
|
-
},
|
|
498
|
-
{
|
|
499
|
-
"name": "generic",
|
|
500
|
-
"label": "Other",
|
|
501
|
-
"order": 160,
|
|
502
|
-
"aliases": [
|
|
503
|
-
"manual",
|
|
504
|
-
"other",
|
|
505
|
-
"custom"
|
|
506
|
-
],
|
|
507
|
-
"signals": {
|
|
508
|
-
"always": true
|
|
509
|
-
},
|
|
510
|
-
"build": {
|
|
511
|
-
"scripts": [
|
|
512
|
-
{
|
|
513
|
-
"name": "build"
|
|
514
|
-
}
|
|
515
|
-
],
|
|
516
|
-
"fallback": null
|
|
517
|
-
},
|
|
518
|
-
"output_dir": {
|
|
519
|
-
"default": "dist"
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
],
|
|
523
|
-
"runtime": {
|
|
524
|
-
"_doc": "Runtime app routing. project_type (user-facing) ↔ runtime_kind (internal/Activator) differ for Next: project_type=ssr_next → runtime_kind=ssr_node. flask is legacy → python_web.",
|
|
525
|
-
"py_web_signals": [
|
|
526
|
-
"fastapi",
|
|
527
|
-
"starlette",
|
|
528
|
-
"litestar",
|
|
529
|
-
"starlite",
|
|
530
|
-
"quart",
|
|
531
|
-
"sanic",
|
|
532
|
-
"blacksheep",
|
|
533
|
-
"hypercorn",
|
|
534
|
-
"daphne",
|
|
535
|
-
"uvicorn",
|
|
536
|
-
"flask",
|
|
537
|
-
"falcon",
|
|
538
|
-
"bottle",
|
|
539
|
-
"pyramid",
|
|
540
|
-
"cherrypy",
|
|
541
|
-
"werkzeug",
|
|
542
|
-
"gunicorn",
|
|
543
|
-
"aiohttp",
|
|
544
|
-
"tornado",
|
|
545
|
-
"django"
|
|
546
|
-
],
|
|
547
|
-
"node_web_signals": [
|
|
548
|
-
"express",
|
|
549
|
-
"fastify",
|
|
550
|
-
"koa",
|
|
551
|
-
"@nestjs/core",
|
|
552
|
-
"@hapi/hapi",
|
|
553
|
-
"hapi",
|
|
554
|
-
"hono",
|
|
555
|
-
"@adonisjs/core",
|
|
556
|
-
"restify",
|
|
557
|
-
"polka",
|
|
558
|
-
"@feathersjs/feathers",
|
|
559
|
-
"sails",
|
|
560
|
-
"h3",
|
|
561
|
-
"json-server"
|
|
562
|
-
],
|
|
563
|
-
"node_frontend_deps": [
|
|
564
|
-
"next",
|
|
565
|
-
"nuxt",
|
|
566
|
-
"vite",
|
|
567
|
-
"react-scripts",
|
|
568
|
-
"@angular/core",
|
|
569
|
-
"@sveltejs/kit",
|
|
570
|
-
"gatsby",
|
|
571
|
-
"astro",
|
|
572
|
-
"@docusaurus/core",
|
|
573
|
-
"@11ty/eleventy",
|
|
574
|
-
"vitepress"
|
|
575
|
-
],
|
|
576
|
-
"python_entrypoints": [
|
|
577
|
-
"app.py",
|
|
578
|
-
"main.py",
|
|
579
|
-
"manage.py"
|
|
580
|
-
],
|
|
581
|
-
"kinds": {
|
|
582
|
-
"ssr_next": {
|
|
583
|
-
"project_type": "ssr_next",
|
|
584
|
-
"runtime_kind": "ssr_node",
|
|
585
|
-
"port": 8080
|
|
586
|
-
},
|
|
587
|
-
"node_web": {
|
|
588
|
-
"project_type": "node_web",
|
|
589
|
-
"runtime_kind": "node_web",
|
|
590
|
-
"port": 3000
|
|
591
|
-
},
|
|
592
|
-
"python_web": {
|
|
593
|
-
"project_type": "python_web",
|
|
594
|
-
"runtime_kind": "python_web",
|
|
595
|
-
"port": 8000
|
|
596
|
-
},
|
|
597
|
-
"streamlit": {
|
|
598
|
-
"project_type": "streamlit",
|
|
599
|
-
"runtime_kind": "streamlit",
|
|
600
|
-
"port": 8501,
|
|
601
|
-
"requires_files": [
|
|
602
|
-
"app.py"
|
|
603
|
-
]
|
|
604
|
-
},
|
|
605
|
-
"gradio": {
|
|
606
|
-
"project_type": "gradio",
|
|
607
|
-
"runtime_kind": "gradio",
|
|
608
|
-
"port": 7860,
|
|
609
|
-
"requires_files": [
|
|
610
|
-
"app.py"
|
|
611
|
-
]
|
|
612
|
-
}
|
|
613
|
-
},
|
|
614
|
-
"layero_runtime_aliases": {
|
|
615
|
-
"ssr_node": "ssr_next",
|
|
616
|
-
"flask": "python_web"
|
|
617
|
-
}
|
|
618
|
-
},
|
|
619
|
-
"package_managers": {
|
|
620
|
-
"_doc": "Lockfile → package manager. install_cmd uses the frozen-lockfile form; install_fallback when no lockfile. run = `<run> <script>`. Mirrors builder/src/pkg_manager.py.",
|
|
621
|
-
"order": [
|
|
622
|
-
"bun",
|
|
623
|
-
"pnpm",
|
|
624
|
-
"yarn",
|
|
625
|
-
"npm"
|
|
626
|
-
],
|
|
627
|
-
"managers": {
|
|
628
|
-
"bun": {
|
|
629
|
-
"lockfiles": [
|
|
630
|
-
"bun.lockb",
|
|
631
|
-
"bun.lock"
|
|
632
|
-
],
|
|
633
|
-
"run": "bun run",
|
|
634
|
-
"install": "bun install --frozen-lockfile --ignore-scripts --no-save",
|
|
635
|
-
"install_fallback": "bun install --ignore-scripts --no-save"
|
|
636
|
-
},
|
|
637
|
-
"pnpm": {
|
|
638
|
-
"lockfiles": [
|
|
639
|
-
"pnpm-lock.yaml"
|
|
640
|
-
],
|
|
641
|
-
"run": "pnpm run",
|
|
642
|
-
"install": "pnpm install --frozen-lockfile --reporter=append-only --ignore-scripts",
|
|
643
|
-
"install_fallback": "pnpm install --reporter=append-only --ignore-scripts"
|
|
644
|
-
},
|
|
645
|
-
"yarn": {
|
|
646
|
-
"lockfiles": [
|
|
647
|
-
"yarn.lock"
|
|
648
|
-
],
|
|
649
|
-
"run": "yarn",
|
|
650
|
-
"install": "yarn install --frozen-lockfile --non-interactive --no-progress --ignore-scripts",
|
|
651
|
-
"install_fallback": "yarn install --non-interactive --no-progress --ignore-scripts"
|
|
652
|
-
},
|
|
653
|
-
"npm": {
|
|
654
|
-
"lockfiles": [
|
|
655
|
-
"package-lock.json",
|
|
656
|
-
"npm-shrinkwrap.json"
|
|
657
|
-
],
|
|
658
|
-
"run": "npm run",
|
|
659
|
-
"install": "npm ci --no-audit --no-fund --ignore-scripts",
|
|
660
|
-
"install_fallback": "npm install --no-audit --no-fund --ignore-scripts"
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
};
|