layero 0.10.4 → 0.11.0
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 +46 -17
- package/dist/agent.js +36 -13
- package/dist/bin/layero.js +26 -14
- package/dist/commands/deploy.js +267 -72
- package/dist/commands/diagnose.js +23 -8
- package/dist/commands/init.js +38 -19
- package/dist/commands/projects.js +1 -1
- package/dist/detect.js +440 -20
- package/dist/exit-codes.js +1 -0
- package/dist/logs.js +40 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,7 +53,8 @@ bare `npx layero` call then runs the globally installed copy for years.
|
|
|
53
53
|
```bash
|
|
54
54
|
layero login # device flow: prints a URL + code, sign in once (email code or Yandex ID)
|
|
55
55
|
cd my-site
|
|
56
|
-
layero deploy
|
|
56
|
+
layero deploy --dry-run # how the platform will build this folder; uploads nothing, no login needed
|
|
57
|
+
layero deploy # packs, uploads, the platform builds and ships
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
The first `layero deploy` in a directory creates a project and links it via
|
|
@@ -70,9 +71,9 @@ The first `layero deploy` in a directory creates a project and links it via
|
|
|
70
71
|
|
|
71
72
|
| Command | Description |
|
|
72
73
|
|---|---|
|
|
73
|
-
| `layero init` |
|
|
74
|
+
| `layero init` | Write a Layero block into `AGENTS.md` / `CLAUDE.md` / `.cursorrules` (compact index; full skill in `layero-agents`) and scaffold `.layero/project.json`. Optional: `deploy` links the folder by itself. It records no guessed settings. |
|
|
74
75
|
| `layero login` / `logout` / `whoami` | Browser device-flow sign-in, sign-out, current account. |
|
|
75
|
-
| `layero deploy` | Pack the current directory, build on the platform, publish. `--claim` — without an account. |
|
|
76
|
+
| `layero deploy` | Pack the current directory, build on the platform, publish. `--dry-run` — show the build plan only. `--claim` — without an account. |
|
|
76
77
|
| `layero projects list` | Projects on your account with addresses. |
|
|
77
78
|
| `layero projects create --repo <provider>:<owner/repo>` | Create a project from a repository of a connected provider, apply the detected settings and start the first build (what the dashboard's "Start deploy" button does); `--no-deploy` leaves it in the wizard. Events: `project_created`, `source_connected`, `webhook_installed` \| `webhook_unavailable`, then `setup_applied` + `deploy_started` \| `setup_pending` \| `setup_failed`. |
|
|
78
79
|
| `layero projects delete <slug> --yes` | Delete a project. Irreversible; needs a token with scope `admin`. |
|
|
@@ -93,11 +94,21 @@ Run `layero <cmd> --help` for full options.
|
|
|
93
94
|
|
|
94
95
|
## `layero deploy` flags
|
|
95
96
|
|
|
97
|
+
- `--dry-run` — print how the platform will build this folder and exit:
|
|
98
|
+
framework, build command, output folder, where each value comes from
|
|
99
|
+
(`layero.json`, project settings, `package.json`, a framework default), and
|
|
100
|
+
a `hint` / `next_action` when the folder is a monorepo, a frontend +
|
|
101
|
+
backend pair, a custom build script or a server. Uploads nothing, needs no
|
|
102
|
+
login (with a login it also reads the settings of a linked project).
|
|
96
103
|
- `--type <preset>` — framework override: `vite`, `vitepress`, `next`,
|
|
97
104
|
`astro`, `cra`, `sveltekit`, `nuxt`, `gatsby`, `docusaurus`, `eleventy`
|
|
98
|
-
(alias `11ty`), `hugo`, `static`; runtime kinds `node_web`,
|
|
99
|
-
`flask`, `streamlit`, `gradio`, `ssr_next` (aliases
|
|
100
|
-
`django`, …). **Optional** —
|
|
105
|
+
(alias `11ty`), `hugo`, `static`, `generic`; runtime kinds `node_web`,
|
|
106
|
+
`python_web`, `flask`, `streamlit`, `gradio`, `ssr_next` (aliases
|
|
107
|
+
`express`, `fastapi`, `django`, …). **Optional** — detected by the
|
|
108
|
+
platform when omitted. `static` serves the files as they are and never runs
|
|
109
|
+
a build; `generic` runs your own build command (`layero.json`
|
|
110
|
+
`buildCommand` or the `build` script) and serves the folder with
|
|
111
|
+
`index.html`.
|
|
101
112
|
- `--prebuilt [dir]` — ship an already-built artifact instead of building
|
|
102
113
|
remotely. Without an argument, picks the first existing of `dist/`,
|
|
103
114
|
`build/`, `public/`, `out/`, `_site/`, `.output/public/`,
|
|
@@ -106,13 +117,18 @@ Run `layero <cmd> --help` for full options.
|
|
|
106
117
|
- `--name <name>` — project name (only on first deploy).
|
|
107
118
|
- `--project <id_or_slug>` — deploy into an existing project, ignoring
|
|
108
119
|
`./.layero/project.json` (use this in CI, not `--name`).
|
|
109
|
-
- `--prod` —
|
|
110
|
-
|
|
120
|
+
- `--prod` — only for a project **with a connected repository**: publish this
|
|
121
|
+
upload at the live address (without it the upload lands in the project's
|
|
122
|
+
separate `cli` environment). A project without a repository is always
|
|
123
|
+
published live.
|
|
111
124
|
- `--branch <name>` — **refused** (`branch_unsupported`, exit 4), see above.
|
|
112
125
|
- `--claim` — deploy without an account: a temporary project for 72 hours
|
|
113
126
|
plus a `claim_url` for a human to take it over. Turns on by itself when
|
|
114
|
-
there is no token, the run is non-interactive (an agent, not CI)
|
|
115
|
-
|
|
127
|
+
there is no token, the run is non-interactive (an agent, not CI), `--yes`
|
|
128
|
+
is passed and the project is new: no `--project`, and the folder is not
|
|
129
|
+
linked to an account project. An existing project without a token means
|
|
130
|
+
signing in (`auth_required`). With `--project` it is refused
|
|
131
|
+
(`claim_with_project`, exit 4). In CI a missing `LAYERO_TOKEN` stays an error.
|
|
116
132
|
- `--org <slug>` — organization for first-time project creation.
|
|
117
133
|
- `--yes` / `-y` — non-interactive mode.
|
|
118
134
|
- `--json` — JSON-lines events on stdout (for agents and CI).
|
|
@@ -140,7 +156,15 @@ the page in a browser (in agent mode it prints the link).
|
|
|
140
156
|
|
|
141
157
|
## Framework auto-detection
|
|
142
158
|
|
|
143
|
-
`layero deploy` (and `layero init
|
|
159
|
+
`layero deploy --dry-run` (and `layero init`, and the `detected` event of every
|
|
160
|
+
deploy) read your project on disk with the same rules the platform uses. This
|
|
161
|
+
is **advice, not a decision**: the platform detects again on the uploaded
|
|
162
|
+
files, and nothing the CLI guesses is saved to the project — only what you name
|
|
163
|
+
(`--type`, `--root`, fields you write into `.layero/project.json` or
|
|
164
|
+
`layero.json`). `confident: false` means the folder was not recognised; read
|
|
165
|
+
`hint` and `next_action` (an app in a subfolder → `--root <dir>`, frontend +
|
|
166
|
+
backend → `layero.json` with both halves, a custom build script →
|
|
167
|
+
`"framework": "generic"`).
|
|
144
168
|
|
|
145
169
|
| Signal | Framework | `build_cmd` | `output_dir` |
|
|
146
170
|
|---|---|---|---|
|
|
@@ -156,7 +180,8 @@ the page in a browser (in agent mode it prints the link).
|
|
|
156
180
|
| `react-scripts` dep | cra | `npm run build` | `build` |
|
|
157
181
|
| `@11ty/eleventy` dep / `.eleventy.js` / `eleventy.config.*` | eleventy | `npm run build` (or `npx @11ty/eleventy`) | `_site` |
|
|
158
182
|
| `hugo.{toml,yaml,json}` or `config.*` with Hugo markers (`baseURL`, `[markup]`, …) | hugo | `hugo --gc --minify` (no install needed) | `public` |
|
|
159
|
-
|
|
|
183
|
+
| `index.html` at the root, no framework | static | none — served as is | `.` |
|
|
184
|
+
| `package.json` with a `build` script, no known framework | generic | `npm run build` | the folder with `index.html` after the build |
|
|
160
185
|
|
|
161
186
|
## `layero.json` — pin the settings in the repository
|
|
162
187
|
|
|
@@ -284,7 +309,8 @@ Every command emits events — one JSON object per line on stdout:
|
|
|
284
309
|
{"event":"sources","org":"…","providers":[…],"connections":[…]}
|
|
285
310
|
{"event":"source_repos","org":"…","connection_id":"…","repos":[…]}
|
|
286
311
|
{"event":"environments","project":"…","environments":[{"branch":"main","url":"https://…","production":true,…}]}
|
|
287
|
-
{"event":"detected","framework":"…","build_cmd":"…","output_dir":"…","confident":true}
|
|
312
|
+
{"event":"detected","framework":"…","build_cmd":"…"|null,"output_dir":"…"|null,"confident":true,"sources":{…},"hint":"…","next_action":"…"}
|
|
313
|
+
{"event":"plan","framework":"…","build_cmd":"…"|null,"output_dir":"…"|null,"sources":{…},"creates_project":true,"replaces_live_site":true,…} (--dry-run)
|
|
288
314
|
{"event":"packing","files":N,"bytes":N,"sha256":"…"}
|
|
289
315
|
{"event":"uploading"} {"event":"uploaded","archive_key":"…"}
|
|
290
316
|
{"event":"deploy_started","deploy_id":"…"}
|
|
@@ -301,9 +327,11 @@ Every command emits events — one JSON object per line on stdout:
|
|
|
301
327
|
```
|
|
302
328
|
|
|
303
329
|
On `ready`, `url` is the **live public site** — show it as-is and never
|
|
304
|
-
rebuild the hostname from a template. `
|
|
305
|
-
|
|
306
|
-
|
|
330
|
+
rebuild the hostname from a template. `ready` comes once the address answers
|
|
331
|
+
with the site itself (`edge_ready: true`); `edge_ready: false` with `screen`
|
|
332
|
+
means the platform's own page still answered after the wait — the app did not
|
|
333
|
+
come up, read `layero logs --runtime`. `dashboard_url` is the management page,
|
|
334
|
+
not the site. `preview_url` and `edge_eta_seconds` are legacy fields.
|
|
307
335
|
|
|
308
336
|
Errors carry a stable `code` (`auth_required`, `auth_expired`, `auth_timeout`,
|
|
309
337
|
`project_unknown`, `project_not_found`, `cli_deploys_disabled`, `invalid_type`,
|
|
@@ -338,7 +366,8 @@ the rule files themselves. Maximum archive size is 200 MB.
|
|
|
338
366
|
- Per-project link: `./.layero/project.json` — `project_id`, `slug`,
|
|
339
367
|
`organization_slug`, `apex_hostname`, `claim` are managed by the CLI;
|
|
340
368
|
`framework_hint`, `build_cmd`, `output_dir`, `analytics_enabled`,
|
|
341
|
-
`env_vars` are
|
|
369
|
+
`env_vars` are yours: the CLI never writes guesses there, and what you put
|
|
370
|
+
there is applied to a new project as your choice.
|
|
342
371
|
|
|
343
372
|
## Contributing
|
|
344
373
|
|
package/dist/agent.js
CHANGED
|
@@ -127,10 +127,39 @@ function renderHuman(event) {
|
|
|
127
127
|
}
|
|
128
128
|
break;
|
|
129
129
|
case "detected":
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
if (event.runtime_kind) {
|
|
131
|
+
process.stdout.write(`→ Detected ${event.runtime_kind} runtime app — will deploy as a scale-to-zero container\n`);
|
|
132
|
+
}
|
|
133
|
+
else if (event.confident) {
|
|
134
|
+
process.stdout.write(`→ Detected ${event.framework} (build: ${event.build_cmd ?? "none"}, output: ${event.output_dir ?? "found after the build"})\n`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
process.stdout.write(`! Не удалось уверенно определить приложение в этой папке\n`);
|
|
138
|
+
}
|
|
139
|
+
if (event.hint)
|
|
140
|
+
process.stdout.write(` ${event.hint}\n`);
|
|
141
|
+
if (event.next_action)
|
|
142
|
+
process.stdout.write(` → ${event.next_action}\n`);
|
|
143
|
+
break;
|
|
144
|
+
case "plan": {
|
|
145
|
+
const src = event.sources;
|
|
146
|
+
process.stdout.write(`План сборки (ничего не выгружено):\n`);
|
|
147
|
+
process.stdout.write(` фреймворк: ${event.framework} (${src.framework})\n`);
|
|
148
|
+
if (event.runtime_kind)
|
|
149
|
+
process.stdout.write(` запуск: контейнер ${event.runtime_kind}\n`);
|
|
150
|
+
process.stdout.write(` сборка: ${event.build_cmd ?? "не запускается"} (${src.build_cmd})\n`);
|
|
151
|
+
process.stdout.write(` результат: ${event.output_dir ?? "определится после сборки"} (${src.output_dir})\n`);
|
|
152
|
+
if (event.root)
|
|
153
|
+
process.stdout.write(` папка: ${event.root}\n`);
|
|
154
|
+
process.stdout.write(event.replaces_live_site
|
|
155
|
+
? ` выкатка заменит живой сайт\n`
|
|
156
|
+
: ` выкатка ляжет в окружение «cli», живой сайт не изменится\n`);
|
|
157
|
+
if (event.hint)
|
|
158
|
+
process.stdout.write(` ${event.hint}\n`);
|
|
159
|
+
if (event.next_action)
|
|
160
|
+
process.stdout.write(` → ${event.next_action}\n`);
|
|
133
161
|
break;
|
|
162
|
+
}
|
|
134
163
|
case "prebuilt":
|
|
135
164
|
process.stdout.write(`→ Prebuilt mode: shipping ${event.dir}\n`);
|
|
136
165
|
break;
|
|
@@ -186,16 +215,10 @@ function renderHuman(event) {
|
|
|
186
215
|
break;
|
|
187
216
|
case "ready":
|
|
188
217
|
if (event.edge_ready === false) {
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
? ` (edge propagating, ~${event.edge_eta_seconds}s)`
|
|
194
|
-
: " (edge propagating)";
|
|
195
|
-
process.stdout.write(`✓ Built. Live at ${event.url}${eta}\n`);
|
|
196
|
-
if (event.preview_url) {
|
|
197
|
-
process.stdout.write(` Reachable now: ${event.preview_url}\n`);
|
|
198
|
-
}
|
|
218
|
+
// Сборка готова, но по адресу всё ещё отвечает экран платформы, а не
|
|
219
|
+
// приложение: оно не поднялось за время ожидания.
|
|
220
|
+
process.stdout.write(`✓ Built. ${event.url} still shows the platform screen` +
|
|
221
|
+
`${event.screen ? ` (${event.screen})` : ""} — check \`layero logs --runtime\`\n`);
|
|
199
222
|
}
|
|
200
223
|
else {
|
|
201
224
|
process.stdout.write(`✓ Live at ${event.url}\n`);
|
package/dist/bin/layero.js
CHANGED
|
@@ -424,22 +424,33 @@ async function main() {
|
|
|
424
424
|
.action(tokenSetCmd);
|
|
425
425
|
program
|
|
426
426
|
.command("deploy")
|
|
427
|
-
.description("Pack the current directory and deploy it
|
|
427
|
+
.description("Pack the current directory and deploy it: the platform builds it and publishes it. " +
|
|
428
|
+
"A project without a connected repository is published live — every deploy replaces the site at ready.url. " +
|
|
429
|
+
"Framework, build command and output directory are detected by the platform; --dry-run shows the plan first.")
|
|
430
|
+
.option("--dry-run", "show how the platform will build this folder (framework, build command, output folder, where each came from, " +
|
|
431
|
+
"hints for monorepos / frontend+backend / custom build scripts) and exit; uploads nothing, needs no login")
|
|
428
432
|
.option("-t, --type <preset>", "type override — static preset (vite | vitepress | next | astro | cra | sveltekit | nuxt | gatsby | docusaurus | storybook | eleventy | hugo | static) " +
|
|
429
|
-
"or runtime kind for apps the platform RUNS (node_web | python_web | flask | streamlit | gradio | ssr_next; aliases: express, fastapi, django, node, python)"
|
|
433
|
+
"or runtime kind for apps the platform RUNS (node_web | python_web | flask | streamlit | gradio | ssr_next; aliases: express, fastapi, django, node, python). " +
|
|
434
|
+
"`static` serves the files as they are and never runs a build; `generic` runs your own build command " +
|
|
435
|
+
"(layero.json buildCommand or the package.json build script) and serves the folder with index.html")
|
|
430
436
|
.option("--name <name>", "project name (only used on first deploy)")
|
|
431
437
|
.option("--project <id_or_slug>", "deploy into an existing project, ignoring local config")
|
|
432
|
-
.option("-y, --yes", "non-interactive: accept defaults and skip --prod confirmation")
|
|
438
|
+
.option("-y, --yes", "non-interactive: accept defaults and skip the --prod confirmation")
|
|
433
439
|
.option("--config", "(legacy alias of the default behaviour — auto-detect + .layero/project.json values)")
|
|
434
440
|
.option("--prebuilt [dir]", "ship an already-built artifact directory (default auto-pick: dist/build/public/out/_site/...)")
|
|
435
|
-
.option("--root <dir>", "monorepo:
|
|
436
|
-
|
|
437
|
-
.option("--
|
|
441
|
+
.option("--root <dir>", "monorepo: the app's subfolder (e.g. apps/web) that the builder treats as the app root; the whole folder is still uploaded. " +
|
|
442
|
+
"Saved on the project, so later deploys and git pushes use it too")
|
|
443
|
+
.option("--prod", "only for a project WITH a connected repository: publish this upload at the live address. Without it such an upload " +
|
|
444
|
+
"lands in the project's separate `cli` environment. A project without a repository is always published live")
|
|
445
|
+
.option("--promote", "after a successful build, point the live address at this deploy (done by the CLI; same result as --prod). " +
|
|
446
|
+
"Not needed for a project without a repository — it is published live anyway")
|
|
438
447
|
.option("--branch <name>", "REFUSED (branch_unsupported, exit 4): archive uploads always land in the " +
|
|
439
448
|
"reserved `cli` environment, so this flag cannot give you a preview. " +
|
|
440
449
|
"Branch previews come from pushing to a connected repository.")
|
|
441
450
|
.option("--claim", "deploy without an account: a temporary project for 72 hours plus a claim_url for a human to take it over. " +
|
|
442
|
-
"Turns on by itself when there is no token, the run is non-interactive (agent, not CI)
|
|
451
|
+
"Turns on by itself when there is no token, the run is non-interactive (agent, not CI), --yes is passed " +
|
|
452
|
+
"and the project is new (no --project, folder not linked to an account project). " +
|
|
453
|
+
"Refused together with --project (claim_with_project, exit 4).")
|
|
443
454
|
.option("--org <slug>", "Layero organization slug for first-time project creation. Defaults to personal; required when you're a member of multiple orgs and want a non-personal home.")
|
|
444
455
|
// Осознанное подтверждение выкатки, когда предыдущие сборки подряд падают
|
|
445
456
|
// с ОДНОЙ И ТОЙ ЖЕ ошибкой. Намеренно НЕ покрывается `--yes`: смысл стопа
|
|
@@ -447,14 +458,15 @@ async function main() {
|
|
|
447
458
|
// стоит по умолчанию и снял бы стоп, ничего не остановив.
|
|
448
459
|
.option("--confirm-repeated-failure", "proceed even though recent deploys keep failing with the SAME error (the platform stops repeat deploys until you confirm you know what changed)")
|
|
449
460
|
.addHelpText("after", "\nExamples:\n" +
|
|
450
|
-
" $ layero deploy
|
|
451
|
-
" $ layero deploy
|
|
452
|
-
" $ layero deploy --
|
|
453
|
-
" $ layero deploy --
|
|
454
|
-
" $ layero deploy --claim # no account: temporary site + claim link\n" +
|
|
455
|
-
" $ layero deploy --type vite # force a framework preset\n" +
|
|
461
|
+
" $ layero deploy --dry-run # how the platform will build this folder; nothing is uploaded\n" +
|
|
462
|
+
" $ layero deploy # build and publish (no repository: replaces the live site)\n" +
|
|
463
|
+
" $ layero deploy --root apps/web # monorepo: the app lives in a subfolder\n" +
|
|
464
|
+
" $ layero deploy --type generic # own build script, no known framework: run it, serve the result\n" +
|
|
456
465
|
" $ layero deploy --type express # Node backend: platform RUNS it, not serves files\n" +
|
|
457
|
-
" $ layero deploy --
|
|
466
|
+
" $ layero deploy --claim # no account: temporary site + claim link\n" +
|
|
467
|
+
" $ layero deploy --prod --yes # project with a connected repository: publish this upload live (CI)\n" +
|
|
468
|
+
" $ layero deploy --json # machine-readable output for agents\n" +
|
|
469
|
+
"\nIsolated previews come only from pushing a branch of a connected repository (layero projects create --repo).")
|
|
458
470
|
.action(async (opts) => {
|
|
459
471
|
await deployCmd(opts);
|
|
460
472
|
});
|