@getxflow/cli 0.1.4 → 0.1.7

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.
@@ -5,15 +5,14 @@ exports.init = init;
5
5
  exports.link = link;
6
6
  exports.list = list;
7
7
  exports.get = get;
8
- exports.open = open;
9
8
  const node_fs_1 = require("node:fs");
10
9
  const node_path_1 = require("node:path");
11
10
  const api_1 = require("../api");
12
11
  const args_1 = require("../args");
13
12
  const config_1 = require("../config");
14
13
  const errors_1 = require("../errors");
15
- const open_url_1 = require("../open-url");
16
14
  const session_1 = require("../session");
15
+ const skills_1 = require("./skills");
17
16
  const template_1 = require("../template");
18
17
  const zip_1 = require("../zip");
19
18
  const ui_1 = require("../ui");
@@ -99,24 +98,24 @@ async function init(args) {
99
98
  (0, ui_1.note)((0, ui_1.dim)(' Разберитесь с папкой и подберите его: xflow link ' + project.id));
100
99
  throw e;
101
100
  }
101
+ (0, skills_1.installSkillQuietly)(target);
102
102
  (0, ui_1.ok)(`Проект «${project.name}» создан: ${files.length} файлов шаблона`);
103
103
  (0, ui_1.out)('');
104
104
  (0, ui_1.out)(` ${(0, ui_1.bold)('Дальше:')}`);
105
105
  (0, ui_1.out)(` cd ${(0, node_path_1.basename)(target)}`);
106
106
  (0, ui_1.out)(' npm install');
107
107
  (0, ui_1.out)(' npm run dev # разработка');
108
- (0, ui_1.out)(' xflow skills # рассказать вашему ИИ-агенту про платформу');
109
108
  (0, ui_1.out)(' xflow deploy # отправить код, собрать и выложить');
110
109
  }
111
110
  /** Связать текущую папку с уже существующим проектом. */
112
111
  async function link(args) {
113
- const projectId = args.words[0];
114
- if (!projectId) {
115
- throw new errors_1.CliError('Нужен идентификатор проекта', 'Список проектов: xflow projects list');
116
- }
117
112
  const root = (0, config_1.findProjectRoot)() ?? process.cwd();
118
113
  const existing = (0, node_fs_1.existsSync)((0, node_path_1.join)(root, config_1.CONFIG_FILE)) ? (0, config_1.readConfig)(root) : undefined;
119
114
  const client = (0, session_1.connect)(existing);
115
+ const projectId = args.words[0];
116
+ if (!projectId) {
117
+ throw new errors_1.CliError('Нужен идентификатор проекта', 'Список: xflow projects list');
118
+ }
120
119
  const card = await (0, api_1.apiJson)(client, `/api/v1/projects/${projectId}`);
121
120
  const dir = existing ? root : process.cwd();
122
121
  (0, config_1.writeConfig)(dir, {
@@ -131,6 +130,7 @@ async function link(args) {
131
130
  write(dir, '.env', (0, template_1.envFile)(card.project_token, client.apiUrl, card.functions));
132
131
  (0, ui_1.note)((0, ui_1.dim)(' Создан .env с токеном проекта и адресами функций'));
133
132
  }
133
+ (0, skills_1.installSkillQuietly)(dir);
134
134
  (0, ui_1.ok)(`Папка связана с проектом «${card.name}»`);
135
135
  (0, ui_1.note)((0, ui_1.dim)(` ${(0, node_path_1.join)(dir, config_1.CONFIG_FILE)}`));
136
136
  }
@@ -145,7 +145,7 @@ async function list() {
145
145
  (0, ui_1.table)(projects.map((p) => [
146
146
  p.id,
147
147
  p.name,
148
- p.live_url ? 'опубликован' : p.dev_url ? 'только dev' : 'без сборки',
148
+ p.live_deploy_id ? 'опубликован' : p.dev_deploy_id ? 'только dev' : 'без сборки',
149
149
  (0, ui_1.formatAge)(p.updated_at),
150
150
  ]));
151
151
  }
@@ -162,9 +162,11 @@ async function get(args) {
162
162
  if (card.description)
163
163
  (0, ui_1.out)(card.description);
164
164
  (0, ui_1.out)('');
165
- (0, ui_1.out)(`dev: ${card.dev_url ?? '—'}`);
166
- (0, ui_1.out)(`live: ${card.live_url ?? '— (не публиковался)'}`);
167
- (0, ui_1.out)(`база: ${card.database ? `${card.database.name} (${card.database.schema})` : '—'}`);
165
+ (0, ui_1.out)(`Собранная версия: ${card.dev_deploy_id ?? '— (сборки нет)'}`);
166
+ (0, ui_1.out)(`Видят посетители: ${card.live_deploy_id ?? '— (не публиковался)'}`);
167
+ (0, ui_1.out)(`База: ${card.database ? `${card.database.name} (${card.database.schema})` : '—'}`);
168
+ (0, ui_1.out)('');
169
+ (0, ui_1.out)(card.project_url);
168
170
  if (card.functions.length > 0) {
169
171
  (0, ui_1.out)('');
170
172
  (0, ui_1.out)('Функции:');
@@ -176,15 +178,3 @@ async function get(args) {
176
178
  (0, ui_1.table)(card.schedules.map((s) => [` ${s.function_name}`, s.cron_expression, s.status]));
177
179
  }
178
180
  }
179
- async function open(args) {
180
- const { config } = (0, config_1.requireProject)();
181
- const client = (0, session_1.connect)(config);
182
- const card = await (0, api_1.apiJson)(client, `/api/v1/projects/${config.projectId}`);
183
- const live = (0, args_1.flagBool)(args, 'live');
184
- const url = live ? card.live_url : card.dev_url;
185
- if (!url) {
186
- throw new errors_1.CliError(live ? 'Проект не публиковался' : 'У проекта нет сборки', live ? 'Опубликовать текущую версию: xflow publish' : 'Собрать и выложить: xflow deploy');
187
- }
188
- (0, ui_1.note)((0, ui_1.dim)(url));
189
- (0, open_url_1.openUrl)(url);
190
- }
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.installSkillQuietly = installSkillQuietly;
3
4
  exports.skills = skills;
4
5
  const node_fs_1 = require("node:fs");
5
6
  const node_os_1 = require("node:os");
@@ -9,13 +10,38 @@ const errors_1 = require("../errors");
9
10
  const ui_1 = require("../ui");
10
11
  /**
11
12
  * Формат SKILL.md общий для инструментов (agentskills.io), а папку каждый ищет
12
- * свою: Claude Code в .claude/skills, Codex и OpenClaw в .agents/skills.
13
- * Кладём один и тот же файл в обе, иначе пришлось бы спрашивать, чей агент.
13
+ * свою: Claude Code в .claude/skills, Codex и OpenClaw в .agents/skills. Cursor к
14
+ * общему формату не пришёл и читает .cursor/rules/*.mdc, поэтому туда едет тот же
15
+ * текст под его заголовком. Спрашивать, чей агент, мы не хотим: раскладываем всем.
14
16
  */
15
- const ROOTS = [
17
+ const SKILL_DIRS = [
16
18
  ['.claude', 'skills'],
17
19
  ['.agents', 'skills'],
18
20
  ];
21
+ /**
22
+ * В домашней папке набор другой. Общий .agents/skills там читает OpenClaw, а
23
+ * Codex смотрит его только внутри репозитория: свой личный он держит отдельно,
24
+ * в .codex/skills. Без этой папки `--global` тихо не работает для Codex.
25
+ */
26
+ const GLOBAL_SKILL_DIRS = [...SKILL_DIRS, ['.codex', 'skills']];
27
+ const CURSOR_RULE = ['.cursor', 'rules', 'xflow.mdc'];
28
+ /**
29
+ * Указатель в AGENTS.md.
30
+ *
31
+ * Скилл подхватывается лениво: только когда описание совпало с задачей. Просьба
32
+ * «добавь таблицу клиентов» с ним не совпадает, и агент не узнаёт, что проект
33
+ * вообще живёт на платформе. AGENTS.md читается всегда и без условий, поэтому
34
+ * пара строк здесь решает ту часть, которую ленивый скилл закрыть не может.
35
+ */
36
+ const POINTER_MARKER = '<!-- xflow-skill -->';
37
+ const POINTER = `${POINTER_MARKER}
38
+ ## XFlow
39
+
40
+ This project is hosted on the XFlow platform and ships through the \`xflow\` CLI, not
41
+ through a git push. Read \`.agents/skills/xflow/SKILL.md\` before deploying, publishing,
42
+ rolling back, touching the database or migrations, cloud functions, schedules,
43
+ environment variables or production logs. Command list: \`xflow help\`.
44
+ `;
19
45
  /** Скилл едет в пакете рядом с dist: у CLI и у инструкций для агента одна версия. */
20
46
  function skillSource() {
21
47
  const path = (0, node_path_1.join)(__dirname, '..', '..', 'skills', 'xflow', 'SKILL.md');
@@ -24,27 +50,79 @@ function skillSource() {
24
50
  }
25
51
  return (0, node_fs_1.readFileSync)(path, 'utf-8');
26
52
  }
53
+ /** Тот же текст под заголовком Cursor: поля у него свои, а тело общее. */
54
+ function cursorRule(skill) {
55
+ const description = /^description:\s*(.+)$/m.exec(skill)?.[1] ?? 'XFlow platform';
56
+ const body = skill.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '');
57
+ return `---\ndescription: ${description}\nalwaysApply: false\n---\n\n${body}`;
58
+ }
59
+ /** Пишет, только если содержимое отличается. Возвращает путь тронутого файла. */
60
+ function writeIfChanged(path, content) {
61
+ const before = (0, node_fs_1.existsSync)(path) ? (0, node_fs_1.readFileSync)(path, 'utf-8') : null;
62
+ if (before === content)
63
+ return null;
64
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
65
+ (0, node_fs_1.writeFileSync)(path, content, 'utf-8');
66
+ return path;
67
+ }
27
68
  /**
28
- * Разложить скилл платформы по папкам, где его найдёт агент.
69
+ * Дописать указатель в AGENTS.md: только в конец и только один раз по маркеру.
70
+ * Файл принадлежит пользователю, там его правила, переписывать его мы не вправе.
71
+ * CLAUDE.md не трогаем вовсе: Claude Code и так читает .claude/skills.
72
+ */
73
+ function appendPointer(base) {
74
+ const path = (0, node_path_1.join)(base, 'AGENTS.md');
75
+ const before = (0, node_fs_1.existsSync)(path) ? (0, node_fs_1.readFileSync)(path, 'utf-8') : null;
76
+ if (before?.includes(POINTER_MARKER))
77
+ return null;
78
+ (0, node_fs_1.writeFileSync)(path, before ? `${before.replace(/\s*$/, '')}\n\n${POINTER}` : POINTER, 'utf-8');
79
+ return path;
80
+ }
81
+ /** Разложить инструкцию по папкам, где её найдёт агент. Отдаёт тронутые файлы. */
82
+ function install(base, global) {
83
+ const skill = skillSource();
84
+ const touched = [];
85
+ for (const dir of global ? GLOBAL_SKILL_DIRS : SKILL_DIRS) {
86
+ const path = writeIfChanged((0, node_path_1.join)(base, ...dir, 'xflow', 'SKILL.md'), skill);
87
+ if (path)
88
+ touched.push(path);
89
+ }
90
+ // Правило Cursor и указатель кладём только рядом с проектом: глобальных
91
+ // аналогов у них нет, а в домашней папке их никто не читает.
92
+ if (!global) {
93
+ const rule = writeIfChanged((0, node_path_1.join)(base, ...CURSOR_RULE), cursorRule(skill));
94
+ if (rule)
95
+ touched.push(rule);
96
+ const pointer = appendPointer(base);
97
+ if (pointer)
98
+ touched.push(pointer);
99
+ }
100
+ return touched;
101
+ }
102
+ /**
103
+ * Установка попутно, из `init` и `link`.
29
104
  *
30
- * Файлы AGENTS.md и CLAUDE.md не трогаем: они принадлежат пользователю, а наше
31
- * знание живёт в своей папке и переустанавливается поверх без спора за файл.
105
+ * Отдельной командой до этого доходили единицы, и агент оставался без знания о
106
+ * платформе той самой, на которой ему предстоит работать. При этом инструкция
107
+ * не то, ради чего запускали команду, поэтому её сбой не роняет привязку папки.
32
108
  */
33
- function skills(args) {
34
- const content = skillSource();
35
- const global = (0, args_1.flagBool)(args, 'global');
36
- const base = global ? (0, node_os_1.homedir)() : process.cwd();
37
- for (const root of ROOTS) {
38
- const target = (0, node_path_1.join)(base, ...root, 'xflow', 'SKILL.md');
39
- const before = (0, node_fs_1.existsSync)(target) ? (0, node_fs_1.readFileSync)(target, 'utf-8') : null;
40
- if (before === content) {
41
- (0, ui_1.note)((0, ui_1.dim)(` уже актуален: ${target}`));
42
- continue;
109
+ function installSkillQuietly(base) {
110
+ try {
111
+ if (install(base, false).length > 0) {
112
+ (0, ui_1.note)((0, ui_1.dim)(' Инструкция о платформе разложена для ИИ-агента'));
43
113
  }
44
- (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(target), { recursive: true });
45
- (0, node_fs_1.writeFileSync)(target, content, 'utf-8');
46
- (0, ui_1.out)(`${before === null ? 'создан' : 'обновлён'}: ${target}`);
47
114
  }
115
+ catch {
116
+ (0, ui_1.note)((0, ui_1.dim)(' Инструкцию для ИИ-агента положить не удалось: xflow skills'));
117
+ }
118
+ }
119
+ function skills(args) {
120
+ const global = (0, args_1.flagBool)(args, 'global');
121
+ const touched = install(global ? (0, node_os_1.homedir)() : process.cwd(), global);
122
+ for (const path of touched)
123
+ (0, ui_1.out)(path);
124
+ if (touched.length === 0)
125
+ (0, ui_1.note)((0, ui_1.dim)(' всё уже актуально'));
48
126
  (0, ui_1.ok)(global ? 'Скилл xflow доступен во всех проектах' : 'Скилл xflow на месте');
49
127
  (0, ui_1.note)((0, ui_1.dim)(' После обновления CLI повторите команду: скилл обновляется вместе с ним'));
50
128
  }
@@ -183,6 +183,8 @@ async function status() {
183
183
  }
184
184
  }
185
185
  (0, ui_1.out)('');
186
- (0, ui_1.out)(`dev: ${card.dev_url ?? '—'}`);
187
- (0, ui_1.out)(`live: ${card.live_url ?? '— (не публиковался)'}`);
186
+ (0, ui_1.out)(`Собранная версия: ${card.dev_deploy_id ?? '— (сборки нет)'}`);
187
+ (0, ui_1.out)(`Видят посетители: ${card.live_deploy_id ?? '— (не публиковался)'}`);
188
+ (0, ui_1.out)('');
189
+ (0, ui_1.out)(card.project_url);
188
190
  }
package/dist/help.js CHANGED
@@ -18,8 +18,9 @@ ${(0, ui_1.bold)('Начало работы')}
18
18
  xflow login вход через браузер
19
19
  xflow init [папка] новый проект из шаблона платформы
20
20
  xflow templates какие шаблоны доступны
21
- xflow link <id> связать текущую папку с существующим проектом
22
- xflow skills [--global] положить инструкцию о платформе для ИИ-агента
21
+ xflow link <id> связать текущую папку с проектом (id: xflow projects list)
22
+ xflow skills [--global] обновить инструкцию о платформе для ИИ-агента
23
+ xflow mcp install доступ к платформе для агента без терминала
23
24
 
24
25
  ${(0, ui_1.bold)('Код')}
25
26
  xflow status что на сервере и чем отличается локальная копия
@@ -28,10 +29,9 @@ ${(0, ui_1.bold)('Код')}
28
29
  забрать исходники (по умолчанию последнюю ревизию)
29
30
 
30
31
  ${(0, ui_1.bold)('Выкладка')}
31
- xflow deploy [--no-push] [--skip-build]
32
- отправить код, собрать локально и выложить на dev
32
+ xflow deploy [--no-push] отправить код, собрать на платформе и выложить на dev
33
33
  xflow publish показать dev-версию посетителям
34
- xflow rollback <номер версии> вернуть dev-адрес на прошлую версию
34
+ xflow rollback <номер версии> вернуть проект на прошлую версию
35
35
  xflow deployments история версий
36
36
 
37
37
  ${(0, ui_1.bold)('Функции')}
@@ -44,6 +44,9 @@ ${(0, ui_1.bold)('Функции')}
44
44
  xflow schedules set <имя> "0 3 ? * * *"
45
45
  запускать функцию по расписанию (UTC)
46
46
  xflow schedules rm <имя> снять расписание
47
+ xflow env [check] переменные функций: что записано, чего не хватает
48
+ xflow env set ИМЯ=значение записать переменную
49
+ xflow env rm ИМЯ удалить переменную
47
50
 
48
51
  ${(0, ui_1.bold)('База данных')}
49
52
  xflow db status какие миграции применены, а какие ждут
@@ -54,7 +57,6 @@ ${(0, ui_1.bold)('Справочное')}
54
57
  xflow logs [--limit N] ошибки выложенного приложения в браузере
55
58
  xflow projects list проекты организации
56
59
  xflow projects get [id] карточка проекта
57
- xflow open [--live] открыть приложение в браузере
58
60
  xflow whoami чей ключ и что он может
59
61
  xflow logout забыть ключ
60
62
 
@@ -106,6 +108,27 @@ ${(0, ui_1.bold)('xflow functions invoke')}.
106
108
 
107
109
  Ошибки браузера собирает ${(0, ui_1.bold)('src/utils/error-logger.ts')} шаблона и только с
108
110
  выложенных адресов: локальный ${(0, ui_1.bold)('npm run dev')} сюда не пишет.`,
111
+ env: `${(0, ui_1.bold)('xflow env')} — переменные окружения функций
112
+
113
+ Ключи, пароли и адреса чужих сервисов хранит платформа, а не репозиторий.
114
+ Значение уходит наверх и обратно не возвращается: увидеть его можно только внутри
115
+ функции. Так что своё же значение придётся хранить там, где вы его взяли.
116
+
117
+ xflow env что записано (имена, не значения)
118
+ xflow env check чего не хватает функциям этого проекта
119
+ xflow env set SMTP_PASSWORD=… записать
120
+ xflow env rm SMTP_PASSWORD удалить
121
+
122
+ --scope project видно только этому проекту, а не всей организации
123
+
124
+ ${(0, ui_1.bold)('check')} читает исходники в ${(0, ui_1.bold)('functions/')} и ищет обращения ${(0, ui_1.bold)('process.env.ИМЯ')}. Это же
125
+ правило действует на выкатке: функция получает только те переменные, которые сама
126
+ упоминает по имени. Имя, собранное выражением (${(0, ui_1.bold)("process.env['KEY_' + n]")}), в окружение
127
+ не попадёт — читайте переменные буквально.
128
+
129
+ Значение доезжает до функции на выкатке, а не в момент записи: после ${(0, ui_1.bold)('env set')} нужно
130
+ ${(0, ui_1.bold)('xflow functions deploy')} затронутых функций. То же и после удаления: в уже выложенной
131
+ функции старое значение останется до следующей выкатки.`,
109
132
  schedules: `${(0, ui_1.bold)('xflow schedules')} — запуск функций по времени
110
133
 
111
134
  Расписание это таймер-триггер Yandex: он зовёт функцию сам, без участия
@@ -138,22 +161,46 @@ ${(0, ui_1.bold)('xflow functions invoke')}.
138
161
  Печатает статус, время ответа и тело. Ненулевой код возврата на статусе 4xx и
139
162
  5xx: в CI такой вызов должен ронять шаг. Причину падения показывает
140
163
  ${(0, ui_1.bold)('xflow functions logs <имя>')}.`,
164
+ mcp: `${(0, ui_1.bold)('xflow mcp install')} — подключить агента к платформе напрямую
165
+
166
+ Прописывает сервер MCP в конфигурацию клиента, у которого есть своя команда
167
+ подключения: сегодня это Claude Code, остальным команда покажет, что вписать
168
+ руками. После этого агент управляет платформой без терминала: смотрит базу,
169
+ применяет миграции, выкладывает и вызывает функции, ставит расписания, читает
170
+ логи. Команды остаются: код, сборку и выкладку по-прежнему делает CLI, гнать
171
+ исходники через MCP значит забивать контекст агента и тратить ваши деньги.
172
+
173
+ Ключ доступа команда берёт из вашего входа и на экран не выводит: обычно её
174
+ запускает сам агент, а всё напечатанное попадает в его контекст и в историю
175
+ переписки.
176
+
177
+ --show-token всё же показать ключ (не под агентом)
178
+
179
+ Ключ один на человека в организации, и область у него не сужается: агент видит
180
+ все проекты организации и называет нужный сам. Проект он берёт из аргумента
181
+ ${(0, ui_1.bold)('project_id')}, а не из вашей папки — папки он не видит.`,
141
182
  skills: `${(0, ui_1.bold)('xflow skills')} — инструкция о платформе для ИИ-агента
142
183
 
143
- Кладёт файл в формате Agent Skills (agentskills.io): что такое XFlow, как выкладывать
144
- и публиковать, откуда брать компоненты дизайн-системы, где смотреть ошибки прода.
145
- Агент подхватывает его сам, когда задача про платформу, и не тратит контекст в
146
- остальное время.
184
+ Про XFlow агенту знать неоткуда: в его обучении платформы нет. Инструкция объясняет,
185
+ как выкладывать и публиковать, откуда брать компоненты дизайн-системы, где смотреть
186
+ ошибки прода. ${(0, ui_1.bold)('init')} и ${(0, ui_1.bold)('link')} раскладывают её сами эта команда нужна, чтобы
187
+ обновить её после обновления CLI: они едут одной версией.
147
188
 
148
- Файл общий для инструментов, а папки у них разные, поэтому кладём в обе:
149
- ${(0, ui_1.bold)('.claude/skills/xflow/')} читает Claude Code, ${(0, ui_1.bold)('.agents/skills/xflow/')} — Codex и OpenClaw.
189
+ Формат общий (agentskills.io), а папки у инструментов разные, поэтому кладём во все:
150
190
 
151
- --global положить в домашнюю папку, тогда скилл виден во всех проектах
191
+ .claude/skills/xflow/SKILL.md Claude Code
192
+ .agents/skills/xflow/SKILL.md Codex, OpenClaw
193
+ .cursor/rules/xflow.mdc Cursor: общий формат он не читает
152
194
 
153
- ${(0, ui_1.bold)('AGENTS.md')} и ${(0, ui_1.bold)('CLAUDE.md')} команда не трогает: это ваши файлы, в них ваши правила.
154
- Наше знание живёт в своей папке и переустанавливается поверх без спора за файл.
195
+ --global то же в домашней папке, тогда скилл виден во всех проектах.
196
+ Там добавляется ~/.codex/skills: общий каталог Codex смотрит
197
+ только внутри репозитория. Правило Cursor и AGENTS.md остаются
198
+ проектными, глобальных аналогов у них нет
155
199
 
156
- Скилл едет вместе с CLI, поэтому после обновления пакета команду стоит повторить.`,
200
+ Плюс несколько строк-указателей в ${(0, ui_1.bold)('AGENTS.md')}. Скилл подхватывается лениво, только
201
+ когда описание совпало с задачей, и на «добавь таблицу клиентов» он не сработает.
202
+ AGENTS.md агент читает всегда, поэтому указатель дописывается туда — в конец файла и
203
+ один раз. ${(0, ui_1.bold)('CLAUDE.md')} не трогаем: Claude Code и так читает .claude/skills.`,
157
204
  push: `${(0, ui_1.bold)('xflow push')} — отправить исходники
158
205
 
159
206
  Уходит вся рабочая копия целиком, одной ревизией. Не отправляются: node_modules,
@@ -178,18 +225,20 @@ ${(0, ui_1.bold)('AGENTS.md')} и ${(0, ui_1.bold)('CLAUDE.md')} команда
178
225
  --force перезаписать папку целиком`,
179
226
  deploy: `${(0, ui_1.bold)('xflow deploy')} — собрать и выложить
180
227
 
181
- Три шага: отправка исходников, локальная сборка, заливка результата. Команда
182
- сборки и каталог результата берутся из xflow.json (по умолчанию npm run build
183
- и dist).
228
+ Два шага: отправка исходников и сборка на платформе. Команда сборки и каталог
229
+ результата берутся из xflow.json (по умолчанию npm run build и dist).
184
230
 
185
- --no-push не отправлять исходники, выложить из последней серверной ревизии
186
- --skip-build не собирать, взять готовый каталог сборки
231
+ --no-push не отправлять исходники, собрать из последней серверной ревизии
187
232
  --force разрешить перезапись серверной ревизии при отправке
188
233
 
189
- Сборка идёт на вашей машине, поэтому её результат зависит от вашей версии Node.
190
- Платформа раздаёт статику: сборка без index.html в корне отклоняется.
234
+ Собирает платформа, в чистой песочнице с одинаковой для всех версией Node,
235
+ поэтому «у меня работало» больше не зависит от вашей машины. Перед сборкой
236
+ проект проверяется на соответствие шаблону: несоответствия печатаются списком,
237
+ и сборка не запускается вовсе.
191
238
 
192
- Выложенное видно только по dev-адресу. Посетителям — после ${(0, ui_1.bold)('xflow publish')}.`,
239
+ Собранную версию видно на странице проекта, ссылку CLI печатает. Посетителям — после
240
+ ${(0, ui_1.bold)('xflow publish')}. Адрес самой сборки не печатается: в нём зашит номер версии, и
241
+ после следующей публикации такая ссылка молча отдаёт старую копию.`,
193
242
  init: `${(0, ui_1.bold)('xflow init')} [папка] — новый проект
194
243
 
195
244
  Разворачивает шаблон платформы: React на Vite, Tailwind, набор компонентов
@@ -209,7 +258,7 @@ ${(0, ui_1.bold)('AGENTS.md')} и ${(0, ui_1.bold)('CLAUDE.md')} команда
209
258
  как подобрать его командой link.`,
210
259
  rollback: `${(0, ui_1.bold)('xflow rollback')} <номер версии> — вернуть прошлую сборку
211
260
 
212
- Переключает dev-адрес на выбранную версию. Возвращается только сборка: исходники
261
+ Переключает проект на выбранную сборку. Возвращается только она: исходники
213
262
  остаются на своей ревизии, и вернуть их — отдельное решение
214
263
  (${(0, ui_1.bold)('xflow pull --revision N --into ./старая-версия')}).
215
264
 
package/dist/template.js CHANGED
@@ -20,7 +20,7 @@ const README = `# %NAME%
20
20
  \`\`\`bash
21
21
  npm install # зависимости
22
22
  npm run dev # разработка на localhost:3000
23
- xflow deploy # отправить код, собрать и выложить на dev-адрес
23
+ xflow deploy # отправить код и собрать на платформе
24
24
  xflow publish # показать dev-версию посетителям
25
25
  \`\`\`
26
26
 
package/dist/tree.js CHANGED
@@ -4,7 +4,6 @@ exports.loadIgnoreRules = loadIgnoreRules;
4
4
  exports.collectFiles = collectFiles;
5
5
  exports.treeHash = treeHash;
6
6
  exports.heaviest = heaviest;
7
- exports.collectArtifact = collectArtifact;
8
7
  const node_crypto_1 = require("node:crypto");
9
8
  const node_fs_1 = require("node:fs");
10
9
  const node_path_1 = require("node:path");
@@ -134,28 +133,3 @@ function treeHash(files) {
134
133
  function heaviest(files, count = 5) {
135
134
  return [...files].sort((a, b) => b.content.length - a.content.length).slice(0, count);
136
135
  }
137
- /** Собрать дерево из каталога сборки: пути внутри артефакта считаются от него. */
138
- function collectArtifact(dir) {
139
- const files = [];
140
- const walk = (current) => {
141
- for (const entry of (0, node_fs_1.readdirSync)(current, { withFileTypes: true })) {
142
- const full = (0, node_path_1.join)(current, entry.name);
143
- if (entry.isSymbolicLink())
144
- continue;
145
- if (entry.isDirectory()) {
146
- walk(full);
147
- continue;
148
- }
149
- if (!entry.isFile())
150
- continue;
151
- files.push({
152
- path: (0, node_path_1.relative)(dir, full).split(node_path_1.sep).join('/'),
153
- content: (0, node_fs_1.readFileSync)(full),
154
- });
155
- }
156
- };
157
- if (!(0, node_fs_1.existsSync)(dir) || !(0, node_fs_1.statSync)(dir).isDirectory())
158
- return files;
159
- walk(dir);
160
- return files;
161
- }
package/dist/types.js CHANGED
@@ -1,3 +1,10 @@
1
1
  "use strict";
2
- /** Ответы публичного API, которые разбирает CLI. */
2
+ /**
3
+ * Ответы публичного API, которые разбирает CLI.
4
+ *
5
+ * Адресов собранного приложения здесь нет: платформа их наружу не отдаёт. В них
6
+ * зашит номер версии, и после следующей публикации такая ссылка продолжает
7
+ * отвечать старой копией, а не ломается. Версии называем номерами, ссылка одна
8
+ * и ведёт на страницу проекта.
9
+ */
3
10
  Object.defineProperty(exports, "__esModule", { value: true });
package/dist/version.js CHANGED
@@ -6,6 +6,6 @@ exports.DEFAULT_API_URL = exports.CLI_VERSION = void 0;
6
6
  * рантайме нельзя, после сборки он лежит на уровень выше dist и в бандл не
7
7
  * попадает.
8
8
  */
9
- exports.CLI_VERSION = '0.1.4';
9
+ exports.CLI_VERSION = '0.1.7';
10
10
  /** Адрес платформы по умолчанию. Переопределяется XFLOW_API_URL и полем `api` в xflow.json. */
11
11
  exports.DEFAULT_API_URL = 'https://app.getxflow.com';
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
- {
2
- "name": "@getxflow/cli",
3
- "version": "0.1.4",
4
- "description": "CLI платформы XFlow: синхронизация кода, деплой и публикация приложений",
5
- "license": "UNLICENSED",
6
- "engines": {
7
- "node": ">=20"
8
- },
9
- "bin": {
10
- "xflow": "dist/bin.js"
11
- },
12
- "files": [
13
- "dist",
14
- "skills"
15
- ],
16
- "scripts": {
17
- "build": "tsc -p tsconfig.json"
18
- },
19
- "publishConfig": {
20
- "access": "public"
21
- }
22
- }
1
+ {
2
+ "name": "@getxflow/cli",
3
+ "version": "0.1.7",
4
+ "description": "CLI платформы XFlow: синхронизация кода, деплой и публикация приложений",
5
+ "license": "UNLICENSED",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "bin": {
10
+ "xflow": "dist/bin.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "skills"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc -p tsconfig.json"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ }
22
+ }