layero 0.8.3 → 0.8.5

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
@@ -151,6 +151,42 @@ faster deploys, no surprises from the platform's package-manager defaults.
151
151
 
152
152
  Override anything by editing `.layero/project.json` after the first `layero init`.
153
153
 
154
+ ## In CI
155
+
156
+ `layero login` opens a browser — there isn't one on a runner, so a pipeline
157
+ authenticates with a long-lived token instead. Create it at
158
+ [app.layero.ru/settings/cli](https://app.layero.ru/settings/cli) and pass it
159
+ through the environment:
160
+
161
+ ```bash
162
+ LAYERO_TOKEN=... npx layero@latest deploy --prod --yes
163
+ ```
164
+
165
+ `LAYERO_TOKEN` is read **before** `~/.layero/config.json`, deliberately: on a
166
+ developer machine that is already signed in to a different account, the
167
+ opposite order would silently deploy to the wrong place. `--yes` skips the
168
+ confirmation prompt that would otherwise wait forever with nobody to answer it.
169
+
170
+ The token is account-scoped, like a login session, so create a separate one per
171
+ repository — then a leak is contained to that repository. Revoke on the same
172
+ page; running builds start failing immediately.
173
+
174
+ On GitHub Actions there is an official action that wraps the above:
175
+
176
+ ```yaml
177
+ - uses: LayeroInfra/deploy-action@v1
178
+ with:
179
+ token: ${{ secrets.LAYERO_TOKEN }}
180
+ prod: true
181
+ ```
182
+
183
+ Note that if the repository is already linked to a Layero project, a push
184
+ builds it automatically — a pipeline would only duplicate that work. Reach for
185
+ CI when the build itself needs secrets or private dependencies the platform
186
+ does not have, then ship the result with `prebuilt: dist`.
187
+
188
+ Full guide: <https://docs.layero.ru/cli/github-actions>
189
+
154
190
  ## Agent / JSON mode
155
191
 
156
192
  `layero` auto-switches to non-interactive + structured-output mode when any of these is true:
@@ -181,10 +217,20 @@ Event types emitted on stdout:
181
217
  {"event":"error","code":"…","next_action":"…","message":"…"}
182
218
  ```
183
219
 
184
- On `ready`, `url` is the **live public site** (the apex CLI uploads
185
- auto-promote to it), `preview_url` is reachable immediately while the apex CDN
186
- edge warms (`edge_ready=false`), and `dashboard_url` is the management page (not
187
- the site). Not logged in? `deploy` starts the device-flow itself (`auth_required`).
220
+ On `ready`, `url` is the **live public site** — reachable the moment the event
221
+ arrives. Show it as-is and never rebuild the hostname from a template: project
222
+ addresses live in the `layero.app` zone, organizations that have not migrated
223
+ yet still use the older `<org>-<project>.layero.ru` scheme, and a guessed host
224
+ will be wrong for one of the two. `dashboard_url` is the management page, not
225
+ the site.
226
+
227
+ `preview_url`, `edge_ready` and `edge_eta_seconds` are legacy fields from the
228
+ era when user sites sat behind a CDN that needed warming. They no longer do —
229
+ sites are served straight from the platform edge. `preview_url` is `null` for
230
+ projects in the `layero.app` zone. **Do not gate on `edge_ready`**: waiting for
231
+ it means waiting for something that will not arrive.
232
+
233
+ Not logged in? `deploy` starts the device-flow itself (`auth_required`).
188
234
 
189
235
  Errors carry a stable `code` (e.g. `not_logged_in`, `invalid_type`,
190
236
  `project_not_found`, `cli_deploys_disabled`) and a `next_action` hint so
package/dist/agent.js CHANGED
@@ -27,6 +27,18 @@ const AGENT_ENV_VARS = [
27
27
  "CONTINUE_CLI",
28
28
  ];
29
29
  const CI_ENV_VARS = ["CI", "GITHUB_ACTIONS", "GITLAB_CI", "BUILDKITE", "CIRCLECI"];
30
+ /**
31
+ * True when we're on a build runner.
32
+ *
33
+ * Deliberately NOT the same as `!detectMode().interactive`: an AI agent
34
+ * (Cursor, Claude Code) is also non-interactive, but there the device-auth
35
+ * flow is the documented happy path — the agent renders the link and a human
36
+ * clicks it. On a runner nobody can click, so the same flow just hangs until
37
+ * the code expires.
38
+ */
39
+ export function isCiEnv() {
40
+ return CI_ENV_VARS.some((k) => process.env[k] && process.env[k] !== "0");
41
+ }
30
42
  export function detectMode(argv = process.argv) {
31
43
  if (cachedMode)
32
44
  return cachedMode;
@@ -9,7 +9,7 @@ import { packCwd, packDirectory } from "../pack.js";
9
9
  import { streamDeployLogs } from "../logs.js";
10
10
  import { detectProject } from "../detect.js";
11
11
  import { runDeviceLogin } from "../auth.js";
12
- import { LayeroError, detectMode, emit } from "../agent.js";
12
+ import { LayeroError, detectMode, emit, isCiEnv } from "../agent.js";
13
13
  const VALID_TYPES = new Set([
14
14
  "vite",
15
15
  "next",
@@ -310,6 +310,14 @@ export async function deployCmd(opts) {
310
310
  }
311
311
  let cliCfg = await loadConfig();
312
312
  if (!cliCfg.token) {
313
+ // In CI nobody can open a browser, so the device flow can only end one
314
+ // way: fifteen minutes of a hung job and then `auth_expired`. Fail
315
+ // immediately instead, and say what to do — burning a quarter of an hour
316
+ // of someone's runner to reach a foregone conclusion is not acceptable.
317
+ if (isCiEnv()) {
318
+ throw new LayeroError("auth_required", "No credentials in CI. Create a token at https://app.layero.ru/settings/cli " +
319
+ "and pass it as the LAYERO_TOKEN environment variable.", "set_layero_token");
320
+ }
313
321
  // Not authenticated yet. Kick off the browser device-login flow inline
314
322
  // and poll, exactly as the docs describe (B5/I4) — no separate `layero
315
323
  // login` step required. In JSON/agent mode this emits `auth_required`
@@ -9,7 +9,7 @@ function agentDocBlock(framework) {
9
9
  return `${AGENT_BLOCK_MARKER_START}
10
10
  ## Deploying with Layero
11
11
 
12
- This project deploys to https://layero.ru. To ship local changes:
12
+ This project deploys to Layero (https://layero.ru). To ship local changes:
13
13
 
14
14
  \`\`\`bash
15
15
  npx layero@latest deploy
@@ -44,7 +44,7 @@ stdout), the CLI auto-switches to JSON-lines. Key events to watch:
44
44
  | \`detected\` | framework auto-detection result |
45
45
  | \`project_created\` / \`project_linked\` | project bound for this directory |
46
46
  | \`build_log\` | forward only if it contains errors |
47
- | \`ready\` | \`url\` = live public site (show to user, stop). \`preview_url\` = reachable immediately while the apex CDN edge warms; \`edge_ready\`/\`edge_eta_seconds\` say whether the apex is serving yet. \`dashboard_url\` = management page. |
47
+ | \`ready\` | \`url\` = live public site (show to user, stop). \`dashboard_url\` = management page. |
48
48
  | \`error\` | follow \`next_action\` field verbatim |
49
49
 
50
50
  Common error codes and remediation:
@@ -58,17 +58,15 @@ Common error codes and remediation:
58
58
  ### Re-deploys and production
59
59
 
60
60
  A plain \`npx layero deploy\` of a CLI project **publishes to the apex**
61
- \`https://<org>-<project>.layero.ru\` — direct uploads auto-promote, so you do
61
+ \`https://<project>.layero.app\` — direct uploads auto-promote, so you do
62
62
  **not** need \`--prod\` or a separate \`promote\` step. Safe to run repeatedly;
63
63
  each run replaces what the apex serves.
64
64
 
65
- Every deploy also gets a per-deploy preview at
66
- \`https://<org>-<project>-<sha>.preview.layero.ru\` (read it from \`ready.preview_url\`).
67
- The preview is reachable **immediately**; the apex can take a few minutes to
68
- serve on the **first** deploy of a project while the CDN edge issues its cert
69
- and propagates (\`ready.edge_ready=false\` with an \`edge_eta_seconds\` estimate).
70
- Hand the user \`ready.url\` (the apex); if \`edge_ready\` is false, also offer
71
- \`preview_url\` so they can see it right away.
65
+ There is no separate per-deploy preview address: user sites live in the
66
+ \`layero.app\` zone, which has no preview sub-zone and no CDN in front, so the
67
+ apex is reachable the moment the deploy is ready.
68
+
69
+ Hand the user \`ready.url\` and stop that address is live.
72
70
 
73
71
  Use \`--branch <name>\` to deploy to an isolated preview environment that does
74
72
  **not** touch the apex. (\`--prod\` exists for git-connected projects; for
package/dist/config.js CHANGED
@@ -7,19 +7,30 @@ const DEFAULT_API_URL = process.env.LAYERO_API_URL ?? "https://api.layero.ru";
7
7
  async function ensureDir() {
8
8
  await fs.mkdir(CONFIG_DIR, { recursive: true });
9
9
  }
10
+ // Долгоживущий токен для CI. Выпускается в дашборде, кладётся в секреты
11
+ // репозитория и попадает сюда через окружение — на раннере нет ни `layero
12
+ // login` (некому пройти device flow), ни конфиг-файла.
13
+ //
14
+ // Приоритет выше файла намеренно: если на машине есть и то и другое,
15
+ // окружение выигрывает. Иначе локальный конфиг разработчика молча
16
+ // перебивал бы токен, заданный в CI, и деплой уходил бы не в тот аккаунт.
17
+ const ENV_TOKEN = process.env.LAYERO_TOKEN?.trim() || undefined;
10
18
  export async function loadConfig() {
11
19
  try {
12
20
  const raw = await fs.readFile(CONFIG_FILE, "utf-8");
13
21
  const parsed = JSON.parse(raw);
14
22
  return {
15
23
  apiUrl: parsed.apiUrl ?? DEFAULT_API_URL,
16
- token: parsed.token,
17
- user: parsed.user,
24
+ token: ENV_TOKEN ?? parsed.token,
25
+ // Пользователя из файла не подставляем, когда токен пришёл из
26
+ // окружения: файл мог остаться от другого аккаунта, и подпись в
27
+ // выводе врала бы. Кто мы — узнаем у API.
28
+ user: ENV_TOKEN ? undefined : parsed.user,
18
29
  };
19
30
  }
20
31
  catch (err) {
21
32
  if (err?.code === "ENOENT") {
22
- return { apiUrl: DEFAULT_API_URL };
33
+ return { apiUrl: DEFAULT_API_URL, token: ENV_TOKEN };
23
34
  }
24
35
  throw err;
25
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "layero",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
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",
@@ -13,14 +13,23 @@
13
13
  "deploy",
14
14
  "deployment",
15
15
  "hosting",
16
+ "static-hosting",
16
17
  "cdn",
17
18
  "cli",
18
19
  "static-site",
19
20
  "spa",
20
21
  "ssr",
22
+ "vite",
23
+ "nextjs",
24
+ "astro",
25
+ "sveltekit",
21
26
  "ai-agents",
22
27
  "cursor",
23
- "claude-code"
28
+ "claude-code",
29
+ "mcp",
30
+ "vercel-alternative",
31
+ "netlify-alternative",
32
+ "russia"
24
33
  ],
25
34
  "engines": {
26
35
  "node": ">=20"
@@ -52,5 +61,9 @@
52
61
  "@types/node": "^20.14.10",
53
62
  "typescript": "^5.5.4",
54
63
  "vitest": "^4.1.7"
64
+ },
65
+ "author": {
66
+ "name": "Layero",
67
+ "url": "https://layero.ru"
55
68
  }
56
69
  }