layero 0.8.10 → 0.8.12
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 +3 -0
- package/dist/bin/layero.js +4 -0
- package/dist/commands/init.js +1 -1
- package/dist/update-notifier.js +125 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ npx layero deploy --json # ships current dir, streams JSON events to stdout
|
|
|
14
14
|
|
|
15
15
|
`layero deploy` prints `{"event":"ready","url":"https://..."}` on success. Show the URL to the user. That's the whole job.
|
|
16
16
|
|
|
17
|
+
**If the user has no code yet** and wants a landing page built from scratch, this CLI is the wrong tool. Layero runs a remote MCP server for that — `https://mcp.layero.ru/mcp` (Streamable HTTP), registered as `ru.layero/layero` in the official MCP registry. It builds the page from a two-or-three-question brief and deploys it. Install it with `claude plugin marketplace add LayeroInfra/layero-claude && claude plugin install layero@layero-claude`, `codex mcp add layero --url https://mcp.layero.ru/mcp --transport http`, or one click in Cursor from [land.layero.ru](https://land.layero.ru). Details: [docs.layero.ru/en/plugin/intro](https://docs.layero.ru/en/plugin/intro).
|
|
18
|
+
|
|
17
19
|
## Install
|
|
18
20
|
|
|
19
21
|
```bash
|
|
@@ -265,3 +267,4 @@ you chose to hide. Maximum archive size is 200 MB.
|
|
|
265
267
|
- Website: https://layero.ru
|
|
266
268
|
- Docs: https://docs.layero.ru
|
|
267
269
|
- Support: https://docs.layero.ru/contacts/
|
|
270
|
+
- MCP server / IDE plugin: https://docs.layero.ru/en/plugin/intro
|
package/dist/bin/layero.js
CHANGED
|
@@ -17,6 +17,7 @@ import { loginCmd } from "../commands/login.js";
|
|
|
17
17
|
import { orgsListCmd } from "../commands/orgs.js";
|
|
18
18
|
import { initCmd } from "../commands/init.js";
|
|
19
19
|
import { LayeroError, detectMode, emit } from "../agent.js";
|
|
20
|
+
import { notifyIfOutdated } from "../update-notifier.js";
|
|
20
21
|
// Read version from the shipped package.json (two levels up from dist/bin/).
|
|
21
22
|
const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
22
23
|
const VERSION = JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
@@ -194,6 +195,9 @@ async function main() {
|
|
|
194
195
|
await deployCmd(opts);
|
|
195
196
|
});
|
|
196
197
|
await program.parseAsync(process.argv);
|
|
198
|
+
// AFTER the command, so the nag never delays real work — and on stderr, so
|
|
199
|
+
// `--json` stdout stays parseable. Bounded + never-throwing by construction.
|
|
200
|
+
await notifyIfOutdated(VERSION);
|
|
197
201
|
}
|
|
198
202
|
main().catch((err) => {
|
|
199
203
|
const mode = detectMode();
|
package/dist/commands/init.js
CHANGED
|
@@ -85,7 +85,7 @@ or a \`dist/\`), ship the artifact directly and skip the server-side
|
|
|
85
85
|
npx layero@latest deploy --prebuilt out
|
|
86
86
|
\`\`\`
|
|
87
87
|
|
|
88
|
-
Full reference: https://docs.layero.ru/cli/agents
|
|
88
|
+
Full reference: https://docs.layero.ru/en/cli/agents
|
|
89
89
|
${AGENT_BLOCK_MARKER_END}
|
|
90
90
|
`;
|
|
91
91
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tell the user when their CLI is behind the published version.
|
|
3
|
+
*
|
|
4
|
+
* WHY this exists (2026-07-27): a stale CLI silently withholds every fix we
|
|
5
|
+
* ship. The `project_type` auto-flip for runtime apps landed 2026-05-26, yet
|
|
6
|
+
* prod still logged 20 failed deploys across 16 projects over the following
|
|
7
|
+
* month with "this repo looks like 'ssr_next' but the project is configured as
|
|
8
|
+
* 'spa'" — the exact crash that fix prevents. The machine that found this was
|
|
9
|
+
* itself running 0.5.3 against a published 0.8.11 and had never been told.
|
|
10
|
+
*
|
|
11
|
+
* A version nag is therefore not cosmetic: it is the delivery mechanism for
|
|
12
|
+
* every stability fix that already exists.
|
|
13
|
+
*
|
|
14
|
+
* Constraints this respects, in order of importance:
|
|
15
|
+
* 1. NEVER corrupt `--json`. Agents and CI parse stdout as JSON-lines, so the
|
|
16
|
+
* notice goes to STDERR, always, in every mode.
|
|
17
|
+
* 2. NEVER slow a deploy down. One 1.5s-timeout request, at most once every
|
|
18
|
+
* CHECK_INTERVAL_MS, and the result is cached on disk.
|
|
19
|
+
* 3. NEVER fail a command. Every path swallows its errors — an offline
|
|
20
|
+
* machine, a proxy, a corrupt cache file must all be silent no-ops.
|
|
21
|
+
*/
|
|
22
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import os from "node:os";
|
|
25
|
+
const REGISTRY_URL = "https://registry.npmjs.org/layero/latest";
|
|
26
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // once a day is plenty
|
|
27
|
+
const FETCH_TIMEOUT_MS = 1500;
|
|
28
|
+
function cachePath() {
|
|
29
|
+
return path.join(os.homedir(), ".layero", "update-check.json");
|
|
30
|
+
}
|
|
31
|
+
/** -1 = a < b, 0 = equal, 1 = a > b. Plain numeric semver; a pre-release tag
|
|
32
|
+
* (1.2.3-beta.1) compares by its numeric core, which is all we need here. */
|
|
33
|
+
export function compareVersions(a, b) {
|
|
34
|
+
const core = (v) => (v.split("-")[0] ?? "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
35
|
+
const [x, y] = [core(a), core(b)];
|
|
36
|
+
for (let i = 0; i < Math.max(x.length, y.length); i++) {
|
|
37
|
+
const d = (x[i] ?? 0) - (y[i] ?? 0);
|
|
38
|
+
if (d !== 0)
|
|
39
|
+
return d > 0 ? 1 : -1;
|
|
40
|
+
}
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
async function readCache() {
|
|
44
|
+
try {
|
|
45
|
+
const raw = await readFile(cachePath(), "utf-8");
|
|
46
|
+
const parsed = JSON.parse(raw);
|
|
47
|
+
if (typeof parsed.checked_at !== "number" || typeof parsed.latest !== "string")
|
|
48
|
+
return null;
|
|
49
|
+
return parsed;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function writeCache(latest) {
|
|
56
|
+
try {
|
|
57
|
+
await mkdir(path.dirname(cachePath()), { recursive: true });
|
|
58
|
+
await writeFile(cachePath(), JSON.stringify({ checked_at: Date.now(), latest }), "utf-8");
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* a read-only HOME must not break the CLI */
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function fetchLatest() {
|
|
65
|
+
try {
|
|
66
|
+
const ctl = new AbortController();
|
|
67
|
+
const timer = setTimeout(() => ctl.abort(), FETCH_TIMEOUT_MS);
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetch(REGISTRY_URL, {
|
|
70
|
+
signal: ctl.signal,
|
|
71
|
+
headers: { accept: "application/vnd.npm.install-v1+json" },
|
|
72
|
+
});
|
|
73
|
+
if (!res.ok)
|
|
74
|
+
return null;
|
|
75
|
+
const body = (await res.json());
|
|
76
|
+
return typeof body.version === "string" ? body.version : null;
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve the newest published version, using the on-disk cache when fresh.
|
|
88
|
+
* Returns null when unknown (offline, throttled, first run with no network).
|
|
89
|
+
*/
|
|
90
|
+
export async function resolveLatest(now = Date.now()) {
|
|
91
|
+
const cached = await readCache();
|
|
92
|
+
if (cached && now - cached.checked_at < CHECK_INTERVAL_MS)
|
|
93
|
+
return cached.latest;
|
|
94
|
+
const latest = await fetchLatest();
|
|
95
|
+
if (latest)
|
|
96
|
+
await writeCache(latest);
|
|
97
|
+
return latest ?? cached?.latest ?? null;
|
|
98
|
+
}
|
|
99
|
+
/** The notice text, or null when the CLI is current. Pure — unit-testable. */
|
|
100
|
+
export function updateNotice(current, latest) {
|
|
101
|
+
if (!latest)
|
|
102
|
+
return null;
|
|
103
|
+
if (compareVersions(current, latest) >= 0)
|
|
104
|
+
return null;
|
|
105
|
+
return (`\n Доступна новая версия Layero CLI: ${current} → ${latest}\n` +
|
|
106
|
+
` Обновиться: npm i -g layero@latest\n` +
|
|
107
|
+
` Старые версии не получают исправлений сборки и деплоя.\n`);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Best-effort check + print to stderr. Awaiting this is safe: it is bounded by
|
|
111
|
+
* FETCH_TIMEOUT_MS and never rejects. Opt out with LAYERO_NO_UPDATE_CHECK=1.
|
|
112
|
+
*/
|
|
113
|
+
export async function notifyIfOutdated(current) {
|
|
114
|
+
try {
|
|
115
|
+
if (process.env.LAYERO_NO_UPDATE_CHECK)
|
|
116
|
+
return;
|
|
117
|
+
const notice = updateNotice(current, await resolveLatest());
|
|
118
|
+
// stderr on purpose — stdout is the JSON-lines channel for agents.
|
|
119
|
+
if (notice)
|
|
120
|
+
process.stderr.write(notice);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
/* a version nag must never be the reason a deploy fails */
|
|
124
|
+
}
|
|
125
|
+
}
|