layero 0.9.4 → 0.10.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/LICENSE +21 -0
- package/README.md +203 -167
- package/dist/agent.js +130 -2
- package/dist/api.js +130 -0
- package/dist/bin/layero.js +110 -36
- package/dist/commands/claim.js +135 -0
- package/dist/commands/data-api.js +578 -0
- package/dist/commands/data-probe.js +541 -0
- package/dist/commands/data.js +4 -1
- package/dist/commands/db.js +94 -4
- package/dist/commands/deploy.js +81 -23
- package/dist/commands/env.js +13 -2
- package/dist/commands/envs.js +44 -0
- package/dist/commands/hooks.js +29 -32
- package/dist/commands/init.js +63 -91
- package/dist/commands/link.js +12 -25
- package/dist/commands/logout.js +2 -2
- package/dist/commands/orgs.js +11 -15
- package/dist/commands/projects.js +179 -12
- package/dist/commands/sources.js +127 -0
- package/dist/commands/whoami.js +9 -10
- package/dist/config.js +1 -0
- package/dist/exit-codes.js +102 -0
- package/dist/project-config.js +2 -0
- package/dist/project-ref.js +20 -0
- package/dist/urls.js +21 -0
- package/package.json +10 -5
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import open from "open";
|
|
2
|
+
import { ApiClient, ApiError } from "../api.js";
|
|
3
|
+
import { loadConfig, saveConfig } from "../config.js";
|
|
4
|
+
import { loadProjectConfig, persistProjectLinking } from "../project-config.js";
|
|
5
|
+
import { LayeroError, detectMode, emit } from "../agent.js";
|
|
6
|
+
import { dashboardOrigin } from "../urls.js";
|
|
7
|
+
/**
|
|
8
|
+
* Claimable-проекты (этап 13 AX-аудита).
|
|
9
|
+
*
|
|
10
|
+
* Сценарий: агент работает без аккаунта Layero — токена нет, браузера нет,
|
|
11
|
+
* человек не рядом. Платформа заводит временный проект и выдаёт токен на
|
|
12
|
+
* него; сайт живёт 72 часа; человек забирает его в свой аккаунт по ссылке
|
|
13
|
+
* `claim_url`. Принять заявку можно ТОЛЬКО в панели: CLI и агент не
|
|
14
|
+
* подтверждают её ни при каких флагах.
|
|
15
|
+
*/
|
|
16
|
+
/** Код заявки: из ответа сервера, а если его там нет — из `claim_url`. */
|
|
17
|
+
export function claimCodeOf(created) {
|
|
18
|
+
if (created.claim_code)
|
|
19
|
+
return created.claim_code;
|
|
20
|
+
try {
|
|
21
|
+
const u = new URL(created.claim_url);
|
|
22
|
+
const q = u.searchParams.get("code");
|
|
23
|
+
if (q)
|
|
24
|
+
return q;
|
|
25
|
+
const last = u.pathname.split("/").filter(Boolean).pop();
|
|
26
|
+
if (last)
|
|
27
|
+
return last;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
/* не URL — ниже */
|
|
31
|
+
}
|
|
32
|
+
throw new LayeroError("claimable_unavailable", "платформа не вернула код заявки", "повторите позже; если повторяется — сообщите на https://docs.layero.ru/contacts/");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Создать claimable-проект и запомнить его: токен — в `~/.layero/config.json`
|
|
36
|
+
* (по проекту), код и ссылку — в `.layero/project.json`. Возвращает конфиг
|
|
37
|
+
* с токеном заявки, которым идёт деплой.
|
|
38
|
+
*/
|
|
39
|
+
export async function createClaimable(cfg, cwd, input) {
|
|
40
|
+
const api = new ApiClient(cfg);
|
|
41
|
+
let created;
|
|
42
|
+
try {
|
|
43
|
+
created = await api.createClaimableProject(input);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
if (err instanceof ApiError && (err.status === 404 || err.status === 501)) {
|
|
47
|
+
throw new LayeroError("claimable_unavailable", "деплой без аккаунта на этой платформе ещё не включён", "войдите: `layero login`, либо задайте LAYERO_TOKEN (выпуск — `layero token create` или app.layero.ru/settings/cli)");
|
|
48
|
+
}
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
const code = claimCodeOf(created);
|
|
52
|
+
const next = {
|
|
53
|
+
...cfg,
|
|
54
|
+
token: created.token,
|
|
55
|
+
claim_tokens: { ...(cfg.claim_tokens ?? {}), [created.project_id]: created.token },
|
|
56
|
+
};
|
|
57
|
+
// В файл — только карту токенов заявок, не сам токен как «вход»: иначе
|
|
58
|
+
// следующий `layero whoami` считал бы временный токен аккаунтом.
|
|
59
|
+
await saveConfig({ ...cfg, claim_tokens: next.claim_tokens });
|
|
60
|
+
await persistProjectLinking(cwd, {
|
|
61
|
+
project_id: created.project_id,
|
|
62
|
+
slug: created.slug,
|
|
63
|
+
organization_slug: created.organization,
|
|
64
|
+
apex_hostname: created.url ? new URL(created.url).hostname : `${created.slug}.layero.app`,
|
|
65
|
+
claim: { code, claim_url: created.claim_url, expires_at: created.expires_at },
|
|
66
|
+
});
|
|
67
|
+
return { cfg: next, created, code };
|
|
68
|
+
}
|
|
69
|
+
/** Токен заявки для проекта из `.layero/project.json`, если он есть. */
|
|
70
|
+
export function claimTokenFor(cfg, projectId) {
|
|
71
|
+
if (!projectId)
|
|
72
|
+
return undefined;
|
|
73
|
+
return cfg.claim_tokens?.[projectId];
|
|
74
|
+
}
|
|
75
|
+
function claimUrlFor(cfg, code) {
|
|
76
|
+
return `${dashboardOrigin(cfg.apiUrl)}/claim?code=${encodeURIComponent(code)}`;
|
|
77
|
+
}
|
|
78
|
+
async function resolveCode(cwd, explicit) {
|
|
79
|
+
if (explicit)
|
|
80
|
+
return { code: explicit.trim(), claim_url: null };
|
|
81
|
+
const linked = await loadProjectConfig(cwd);
|
|
82
|
+
if (linked?.claim?.code)
|
|
83
|
+
return { code: linked.claim.code, claim_url: linked.claim.claim_url };
|
|
84
|
+
throw new LayeroError("claim_unknown", "в этой папке нет claimable-проекта", "передайте код: `layero claim status <code>` — или создайте проект без аккаунта: `layero deploy --claim`");
|
|
85
|
+
}
|
|
86
|
+
/** `layero claim status [code]` — что с заявкой: жива, забрана, истекла. */
|
|
87
|
+
export async function claimStatusCmd(code, opts) {
|
|
88
|
+
const cwd = process.cwd();
|
|
89
|
+
const cfg = await loadConfig();
|
|
90
|
+
const ref = await resolveCode(cwd, code);
|
|
91
|
+
// Без токена намеренно: статус заявки — публичный по коду, как и сама ссылка.
|
|
92
|
+
const api = new ApiClient({ apiUrl: cfg.apiUrl });
|
|
93
|
+
let status;
|
|
94
|
+
try {
|
|
95
|
+
status = await api.getClaimStatus(ref.code);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
99
|
+
throw new LayeroError("claim_unknown", `заявки с кодом ${ref.code} нет — истекла или код неверный`, "новый проект без аккаунта: `layero deploy --claim`");
|
|
100
|
+
}
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
emit({
|
|
104
|
+
event: "claim_status",
|
|
105
|
+
code: ref.code,
|
|
106
|
+
status: status.status,
|
|
107
|
+
claimed: status.claimed ?? status.status === "claimed",
|
|
108
|
+
expires_at: status.expires_at ?? null,
|
|
109
|
+
url: status.url ?? null,
|
|
110
|
+
claim_url: status.claim_url ?? ref.claim_url ?? claimUrlFor(cfg, ref.code),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* `layero claim accept <code>` — открыть страницу заявки в панели. Принять
|
|
115
|
+
* можно только человеком, залогиненным в панели: CLI ссылку открывает
|
|
116
|
+
* (в терминале) или печатает (агенту), и на этом его роль кончается.
|
|
117
|
+
*/
|
|
118
|
+
export async function claimAcceptCmd(code, opts) {
|
|
119
|
+
const cwd = process.cwd();
|
|
120
|
+
const cfg = await loadConfig();
|
|
121
|
+
const ref = await resolveCode(cwd, code);
|
|
122
|
+
const claimUrl = ref.claim_url ?? claimUrlFor(cfg, ref.code);
|
|
123
|
+
const mode = detectMode();
|
|
124
|
+
let opened = false;
|
|
125
|
+
if (mode.interactive && !opts.noBrowser) {
|
|
126
|
+
try {
|
|
127
|
+
await open(claimUrl);
|
|
128
|
+
opened = true;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
opened = false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
emit({ event: "claim_accept", code: ref.code, claim_url: claimUrl, opened });
|
|
135
|
+
}
|