layero 0.11.9 → 0.11.11

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.
@@ -540,7 +540,11 @@ function asAccountStateError(err) {
540
540
  if (err.status >= 400 && err.status < 500) {
541
541
  const detail = detailOf(err.body);
542
542
  if (detail) {
543
- return new LayeroError(detail.code ?? `http_${err.status}`, detail.message, detail.next_action ?? "исправьте запрос и повторите");
543
+ return new LayeroError(detail.code ?? `http_${err.status}`, detail.message,
544
+ // Общий совет — последний: причина и лекарство чаще всего уже в тексте
545
+ // отказа (подсказки SQL про роли и Авторизацию, T-20261025-9), и
546
+ // «исправьте запрос» уводил агента от них.
547
+ detail.next_action ?? "причина и что сделать — в тексте отказа; исправьте и повторите");
544
548
  }
545
549
  }
546
550
  return null;
@@ -438,9 +438,12 @@ export async function dataGrantCmd(object, opts) {
438
438
  console.log(planLines(done).join("\n"));
439
439
  console.log(`\n${chalk.green("✓")} доступ изменён`);
440
440
  }
441
- // Что снимает переприменение: агент узла при каждом включении API отзывает у
442
- // ролей Data API всё в схеме `public` (`runtime/userdb-agent/agent.py`, `api_enable`).
443
- const REPAIR_LOSS = "роли Data API потеряют права на схему public (USAGE) и на все её таблицы";
441
+ // Что снимает переприменение (сверено с агентом узла 25.10.2026,
442
+ // `runtime/userdb-agent/agent.py`, `api_enable`): права на таблицы и USAGE на
443
+ // `public` у существующих ролей СОХРАНЯЮТСЯ (с T-20260914-3 снимается только у
444
+ // роли, созданной этим же включением; USAGE на `public` выдаётся с 25.10).
445
+ // Снимаются только умолчания, которые сами открывали ролям НОВЫЕ таблицы.
446
+ const REPAIR_LOSS = "умолчания, которые сами открывали ролям Data API новые таблицы, будут сняты; уже выданные права на таблицы сохранятся";
444
447
  /**
445
448
  * Включение Data API.
446
449
  *
@@ -456,7 +459,7 @@ const REPAIR_LOSS = "роли Data API потеряют права на схем
456
459
  */
457
460
  export async function dataEnableCmd(opts) {
458
461
  if (opts.repair && !opts.db) {
459
- throw new LayeroError("database_unknown", `переприменение Data API снимает права ролей на схему public — базу нужно указать явно`, "layero data enable --db <база> --repair");
462
+ throw new LayeroError("database_unknown", `переприменение Data API меняет умолчания прав у ролей базы — базу нужно указать явно`, "layero data enable --db <база> --repair");
460
463
  }
461
464
  const { api, org, db, ref } = await target(opts, "enable");
462
465
  if (db.api_enabled && !opts.repair) {
@@ -499,9 +502,8 @@ export async function dataEnableCmd(opts) {
499
502
  // агент снимает права только в `public`, уровни в `api` и `app` остаются.
500
503
  console.log(reapplied
501
504
  ? `${chalk.green("✓")} Data API переприменён у базы «${db.name}»\n` +
502
- chalk.yellow(" Роли Data API лишились прав на схему public (USAGE) и на все её таблицы. " +
503
- "Уровни таблиц в схемах api и app сохранились; таблицы public, если они были открыты, " +
504
- "откройте заново: layero data methods, затем layero data grant")
505
+ chalk.yellow(" Выданные права на таблицы сохранились. Сняты только умолчания, которые сами открывали " +
506
+ "новые таблицы ролям Data API: новые таблицы открывайте явно — layero data grant")
505
507
  : `${chalk.green("✓")} Data API включён у базы «${db.name}»`);
506
508
  if (publicKey)
507
509
  console.log(`\n публичный ключ: ${publicKey}`);
@@ -572,7 +574,7 @@ export function registerDataApiCommands(data, program) {
572
574
  .command("enable")
573
575
  .description("Enable the Data API for a database: sets up the api schema and roles, issues a public key. All methods start closed.")
574
576
  .option("--with-secret", "also issue a secret key for the server")
575
- .option("--repair", "re-apply the roles and the api schema for a database whose Data API is already enabled; revokes the roles' privileges on schema public")
577
+ .option("--repair", "re-apply the roles and the api schema for a database whose Data API is already enabled; table grants are kept, default privileges for new tables are removed")
576
578
  .option("-y, --yes", "do not ask for confirmation for --repair")).action(async (opts) => dataEnableCmd(json(opts)));
577
579
  registerDataProbeCommand(data, withDb, json);
578
580
  }
@@ -66,12 +66,16 @@ export async function dataEnvCmd(opts) {
66
66
  const project = await projectRef(api, opts);
67
67
  const vars = await api.dataEnv(project.id);
68
68
  if (!Object.keys(vars).length) {
69
+ // 🚨 Пустой ответ — не «всё в порядке» (T-20261025-9): агент видел
70
+ // `vars: {}` без подсказки и решал, что писать нечего. Говорим, почему
71
+ // пусто и что сделать, тем же `next_action`, что у отказов.
72
+ const next = `подключите к проекту базу с Data API: layero db connect <база> --project ${project.slug}; ` +
73
+ "Data API у базы: layero data enable --db <база> (или панель → Базы данных → база → Data API → «Включить Data API»)";
69
74
  if (mode.json) {
70
- emit({ event: "data_env", project: project.slug, vars: {} });
75
+ emit({ event: "data_env", project: project.slug, vars: {}, reason: "no_data_api_database", next_action: next });
71
76
  return;
72
77
  }
73
- console.log(chalk.dim(`у проекта "${project.slug}" нет базы с включённым Data API\n` +
74
- "включить: панель → база → «API» → «Включить API»"));
78
+ console.log(chalk.dim(`у проекта "${project.slug}" нет подключённой базы с включённым Data API\n${next}`));
75
79
  return;
76
80
  }
77
81
  if (mode.json) {
@@ -167,18 +167,29 @@ export async function dbCreateCmd(name, opts) {
167
167
  }
168
168
  const api = new ApiClient(config);
169
169
  const org = await orgOf(api, opts);
170
- const created = await api.createDatabase(org, {
171
- name,
172
- // 🚨 ФЛАГА «БЕЗ НАПОЛНЕНИЯ» БОЛЬШЕ НЕТ. Он существовал, пока пресет
173
- // заводил три таблицы с примерами: человек со своей схемой получал чужие
174
- // таблицы молча, и отказаться было нечем. Тестовых таблиц в пресете не
175
- // осталось — он настраивает роли, функцию состояния и умолчания прав,
176
- // и вычищать после него нечего. Значит и отказываться не от чего.
170
+ // 🚨 ФЛАГА «БЕЗ НАПОЛНЕНИЯ» БОЛЬШЕ НЕТ. Он существовал, пока пресет
171
+ // заводил три таблицы с примерами: человек со своей схемой получал чужие
172
+ // таблицы молча, и отказаться было нечем. Тестовых таблиц в пресете не
173
+ // осталось — он настраивает роли, функцию состояния и умолчания прав,
174
+ // и вычищать после него нечего. Значит и отказываться не от чего.
175
+ const created = await api.createDatabase(org, { name }).catch((err) => {
176
+ // 409 — место бесплатной базы занято или лимит тарифа (T-20261025-9):
177
+ // общий совет «исправьте запрос» здесь ничего не говорил.
178
+ if (err instanceof LayeroError && err.code === "http_409") {
179
+ throw new LayeroError(err.code, err.message, "удалите ненужную базу (layero db list; удаление — в панели: Базы данных → база → Настройки) " +
180
+ "или заведите базу со своей конфигурацией в панели");
181
+ }
182
+ throw err;
177
183
  });
178
184
  // Пароль отдаётся ОДИН РАЗ — второй раз его не покажет никто, только
179
185
  // ротация. Поэтому он и в JSON-режиме, и в терминале.
180
186
  if (mode.json || opts.json) {
181
- emit({ event: "database_created", org, name, ...created });
187
+ // 🚨 Пароль — в ОДНОМ поле (T-20261025-12). Раньше он шёл ещё и отдельным
188
+ // `password`: агент маскировал строку подключения и не замечал второе
189
+ // поле, и пароль уезжал в вывод инструмента. `secrets` называет, что
190
+ // маскировать.
191
+ emit({ event: "database_created", org, name, connection_string: created.connection_string,
192
+ secrets: ["connection_string"] });
182
193
  return;
183
194
  }
184
195
  // 🚨 БАЗА ЕЩЁ НЕ ГОТОВА В ЭТОТ МОМЕНТ. Ручка стала асинхронной: она пишет
@@ -209,13 +209,6 @@ async function assertSandboxServesFiles(cwd, opts, existing, detectFolder) {
209
209
  return;
210
210
  throw new LayeroError("claim_static_only", `деплой без аккаунта (--claim) выкладывает только статику и SPA, а эта папка — серверное приложение (${kind})`, "sign in and deploy from the account: `npx layero@latest login`, then run the same deploy without --claim");
211
211
  }
212
- /** Обёртка временного сайта по его адресу: `<панель>/preview/<метка>`.
213
- * Считается здесь для повторной выкатки — платформа отдаёт поле только при
214
- * создании песочницы. */
215
- function sandboxPreviewUrl(siteUrl) {
216
- const label = new URL(siteUrl).hostname.split(".")[0];
217
- return `https://app.layero.ru/preview/${label}`;
218
- }
219
212
  /**
220
213
  * 🚨 ДЕТЕКТ — ОДИН НА ВЫКАТКУ. Гейт core «входов анализа не становится
221
214
  * больше» считает запуски детекта в этом файле: каждый лишний — ещё один
@@ -942,15 +935,15 @@ export async function deployCmd(opts) {
942
935
  }
943
936
  // Claimable: ссылка «забрать» — ДО `ready`, потому что после `ready`
944
937
  // агент не читает, а без этой ссылки сайт исчезнет через час.
945
- // `preview_url` — обёртка панели: пока сайт временный, человеку
946
- // открывается только она; `url` остаётся для проверок и агентов.
938
+ // `preview_url` — ссылка для людей. С 22.10.2026 это сам сайт
939
+ // `<метка>.preview.layero.app`: обёртки панели больше нет.
947
940
  if (claimable) {
948
941
  emit({
949
942
  event: "claimable",
950
943
  project_id: claimable.project_id,
951
944
  slug: claimable.slug,
952
945
  url: liveUrl,
953
- preview_url: claimable.preview_url ?? sandboxPreviewUrl(liveUrl),
946
+ preview_url: claimable.preview_url ?? liveUrl,
954
947
  claim_url: claimable.claim_url,
955
948
  expires_at: claimable.expires_at,
956
949
  });
@@ -961,7 +954,7 @@ export async function deployCmd(opts) {
961
954
  project_id: project.id,
962
955
  slug: project.slug,
963
956
  url: liveUrl,
964
- preview_url: sandboxPreviewUrl(liveUrl),
957
+ preview_url: liveUrl,
965
958
  claim_url: reusedClaim.claim_url,
966
959
  expires_at: reusedClaim.expires_at,
967
960
  });
package/dist/detect.js CHANGED
@@ -122,8 +122,8 @@ const JS_COMMENT_RE = /\/\/[^\n]*|\/\*[\s\S]*?\*\//gm;
122
122
  function hasDep(pkg, name) {
123
123
  return Boolean(pkg && ((pkg.dependencies ?? {})[name] ?? (pkg.devDependencies ?? {})[name]));
124
124
  }
125
- /** Mirror of the builder's static-host pre-flight warning for the two
126
- * frameworks that build a server by default (Nuxt, SvelteKit). */
125
+ /** Mirror of the builder's pre-flight warning for the two frameworks whose
126
+ * default build may not run on Layero as is (Nuxt, SvelteKit). */
127
127
  function ssrWarning(snap, framework) {
128
128
  const pkg = snap.packageJson;
129
129
  if (framework === "nuxt") {
@@ -150,10 +150,21 @@ function ssrWarning(snap, framework) {
150
150
  return undefined;
151
151
  const allDeps = { ...(pkg?.dependencies ?? {}), ...(pkg?.devDependencies ?? {}) };
152
152
  const serverAdapter = Object.keys(allDeps).find((d) => d.startsWith("@sveltejs/adapter-") && d !== "@sveltejs/adapter-static");
153
+ // adapter-node is a server Layero runs in a container — nothing to warn
154
+ // about. Until 29.09.2026 this said "Layero hosts static sites only",
155
+ // which stopped being true when containers arrived (T-20260929-7).
156
+ if (serverAdapter === "@sveltejs/adapter-node")
157
+ return undefined;
158
+ const choose = "Pick one yourself: @sveltejs/adapter-static builds a static site, " +
159
+ "@sveltejs/adapter-node builds a server that Layero runs in a container.";
160
+ if (serverAdapter === "@sveltejs/adapter-auto") {
161
+ return ("SvelteKit uses @sveltejs/adapter-auto: it picks an adapter for platforms it knows, " +
162
+ `Layero is not one of them, so the build will produce nothing. ${choose}`);
163
+ }
153
164
  if (serverAdapter) {
154
- return `SvelteKit использует ${serverAdapter} (серверный адаптер). Layero хостит только статику — поставьте @sveltejs/adapter-static.`;
165
+ return `SvelteKit uses ${serverAdapter}, an adapter for another platform; its output will not run on Layero. ${choose}`;
155
166
  }
156
- return "SvelteKit без явного адаптера — поставьте @sveltejs/adapter-static для статического хостинга.";
167
+ return `SvelteKit has no adapter, so the build has nothing to output. ${choose}`;
157
168
  }
158
169
  return undefined;
159
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "layero",
3
- "version": "0.11.9",
3
+ "version": "0.11.11",
4
4
  "description": "Layero CLI — deploy a directory or a repo to hosting with build servers in Russia. JSON events and exit codes for AI agents and CI.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -57,7 +57,7 @@
57
57
  "chalk": "^5.3.0",
58
58
  "commander": "^12.1.0",
59
59
  "ignore": "^5.3.2",
60
- "layero-detection": "^0.1.11",
60
+ "layero-detection": "^0.1.12",
61
61
  "open": "^10.1.0",
62
62
  "tar": "^7.4.3"
63
63
  },