layero 0.10.5 → 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 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 # detects the framework, packs, uploads, builds, ships
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` | Scaffold `.layero/project.json` and write a Layero block into `AGENTS.md` / `CLAUDE.md` / `.cursorrules` (compact index; full skill in `layero-agents`). |
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`, `python_web`,
99
- `flask`, `streamlit`, `gradio`, `ssr_next` (aliases `express`, `fastapi`,
100
- `django`, …). **Optional** — auto-detected when omitted.
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,8 +117,10 @@ 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` — target the production environment of a repository-linked
110
- project. Redundant for CLI projects: they auto-promote on every deploy.
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
@@ -143,7 +156,15 @@ the page in a browser (in agent mode it prints the link).
143
156
 
144
157
  ## Framework auto-detection
145
158
 
146
- `layero deploy` (and `layero init`) read your project on disk and pick sane defaults:
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"`).
147
168
 
148
169
  | Signal | Framework | `build_cmd` | `output_dir` |
149
170
  |---|---|---|---|
@@ -159,7 +180,8 @@ the page in a browser (in agent mode it prints the link).
159
180
  | `react-scripts` dep | cra | `npm run build` | `build` |
160
181
  | `@11ty/eleventy` dep / `.eleventy.js` / `eleventy.config.*` | eleventy | `npm run build` (or `npx @11ty/eleventy`) | `_site` |
161
182
  | `hugo.{toml,yaml,json}` or `config.*` with Hugo markers (`baseURL`, `[markup]`, …) | hugo | `hugo --gc --minify` (no install needed) | `public` |
162
- | any `.html` at root, no `package.json` | static | `true` (no-op) | `.` |
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 |
163
185
 
164
186
  ## `layero.json` — pin the settings in the repository
165
187
 
@@ -287,7 +309,8 @@ Every command emits events — one JSON object per line on stdout:
287
309
  {"event":"sources","org":"…","providers":[…],"connections":[…]}
288
310
  {"event":"source_repos","org":"…","connection_id":"…","repos":[…]}
289
311
  {"event":"environments","project":"…","environments":[{"branch":"main","url":"https://…","production":true,…}]}
290
- {"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)
291
314
  {"event":"packing","files":N,"bytes":N,"sha256":"…"}
292
315
  {"event":"uploading"} {"event":"uploaded","archive_key":"…"}
293
316
  {"event":"deploy_started","deploy_id":"…"}
@@ -304,9 +327,11 @@ Every command emits events — one JSON object per line on stdout:
304
327
  ```
305
328
 
306
329
  On `ready`, `url` is the **live public site** — show it as-is and never
307
- rebuild the hostname from a template. `dashboard_url` is the management page,
308
- not the site. `preview_url`, `edge_ready` and `edge_eta_seconds` are legacy
309
- fields; do not wait for them.
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.
310
335
 
311
336
  Errors carry a stable `code` (`auth_required`, `auth_expired`, `auth_timeout`,
312
337
  `project_unknown`, `project_not_found`, `cli_deploys_disabled`, `invalid_type`,
@@ -341,7 +366,8 @@ the rule files themselves. Maximum archive size is 200 MB.
341
366
  - Per-project link: `./.layero/project.json` — `project_id`, `slug`,
342
367
  `organization_slug`, `apex_hostname`, `claim` are managed by the CLI;
343
368
  `framework_hint`, `build_cmd`, `output_dir`, `analytics_enabled`,
344
- `env_vars` are user-editable and override auto-detection.
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.
345
371
 
346
372
  ## Contributing
347
373
 
package/dist/agent.js CHANGED
@@ -127,10 +127,39 @@ function renderHuman(event) {
127
127
  }
128
128
  break;
129
129
  case "detected":
130
- process.stdout.write(event.runtime_kind
131
- ? `→ Detected ${event.runtime_kind} runtime app — will deploy as a scale-to-zero container\n`
132
- : `→ Detected ${event.framework} (build: ${event.build_cmd}, output: ${event.output_dir})\n`);
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
- // Built & published, but the CDN edge for the apex is still
190
- // propagating (first deploy of a new hostname can take a few
191
- // minutes on YC CDN). The preview host is reachable immediately.
192
- const eta = typeof event.edge_eta_seconds === "number" && event.edge_eta_seconds > 0
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`);
@@ -424,17 +424,26 @@ async function main() {
424
424
  .action(tokenSetCmd);
425
425
  program
426
426
  .command("deploy")
427
- .description("Pack the current directory and deploy it. Framework, build command and output directory are auto-detected.")
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: subdirectory inside the repo that the builder treats as the app root (saved on the project; future GitHub-push and hook triggers use the same value)")
436
- .option("--prod", "deploy to production (replaces apex_hostname's active deploy). Without this flag, deploys go to the project's CLI preview pseudo-branch.")
437
- .option("--promote", "pin the project apex to this deploy after a successful build (V071). Works for any branch — e.g. `--promote` without --prod publishes a CLI preview straight to production.")
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.")
@@ -449,14 +458,15 @@ async function main() {
449
458
  // стоит по умолчанию и снял бы стоп, ничего не остановив.
450
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)")
451
460
  .addHelpText("after", "\nExamples:\n" +
452
- " $ layero deploy # preview deploy (CLI pseudo-branch), auto-detect framework\n" +
453
- " $ layero deploy --prod # production deploy (interactive confirm)\n" +
454
- " $ layero deploy --prod --yes # production deploy, no prompt (CI)\n" +
455
- " $ layero deploy --promote # preview deploy + pin apex (one-shot publish)\n" +
456
- " $ layero deploy --claim # no account: temporary site + claim link\n" +
457
- " $ 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" +
458
465
  " $ layero deploy --type express # Node backend: platform RUNS it, not serves files\n" +
459
- " $ layero deploy --json # machine-readable output for agents")
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).")
460
470
  .action(async (opts) => {
461
471
  await deployCmd(opts);
462
472
  });