@reformer/builder 1.0.0-beta.1 → 2.0.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.
Files changed (43) hide show
  1. package/README.md +70 -8
  2. package/bin/reformer-builder.d.mts +36 -0
  3. package/bin/reformer-builder.mjs +112 -15
  4. package/dist/assets/App-_6Sb2gZF.js +892 -0
  5. package/dist/assets/BootError-CbjkO8JX.js +1 -0
  6. package/dist/assets/CodeEditor-DTO6OzhB.js +2 -0
  7. package/dist/assets/RawJsonEditor-DUJPoZi0.js +1 -0
  8. package/dist/assets/boot-BQd8o_5d.js +1 -0
  9. package/dist/assets/contract-GGklenj-.js +9 -0
  10. package/dist/assets/{freemarker2-Dc63E2SW.js → freemarker2-IA3H3wuC.js} +2 -2
  11. package/dist/assets/handlebars-CQSli8ll.js +1 -0
  12. package/dist/assets/html-CghVKP1R.js +1 -0
  13. package/dist/assets/index-BCoy6hGR.css +1 -0
  14. package/dist/assets/index-CnoiW_cj.js +10 -0
  15. package/dist/assets/javascript-DSEPacy5.js +1 -0
  16. package/dist/assets/{jsonMode-CemXzEMj.js → jsonMode-W1y09uqP.js} +3 -3
  17. package/dist/assets/liquid-1e577kxN.js +1 -0
  18. package/dist/assets/load-BhxezP-1.js +3 -0
  19. package/dist/assets/mdx-CFjtYju5.js +1 -0
  20. package/dist/assets/{monaco-setup-wGOKhcUo.js → monaco-setup-_iXPlnAT.js} +34 -34
  21. package/dist/assets/python-3-b5o-51.js +1 -0
  22. package/dist/assets/razor-D3TF8v-Q.js +1 -0
  23. package/dist/assets/typescript-BNaAyYgu.js +1 -0
  24. package/dist/assets/validate-B5O3mi2k.js +8 -0
  25. package/dist/assets/xml-DIHK-3EW.js +1 -0
  26. package/dist/assets/yaml-DS08VUGY.js +1 -0
  27. package/dist/index.html +2 -2
  28. package/package.json +5 -2
  29. package/runtime-config.schema.json +77 -0
  30. package/dist/assets/CodeEditor-C27y3JNW.js +0 -2
  31. package/dist/assets/RawJsonEditor-BS5r3i2P.js +0 -1
  32. package/dist/assets/handlebars-CN4ggFMI.js +0 -1
  33. package/dist/assets/html-BJ_0FZ4N.js +0 -1
  34. package/dist/assets/index-BVjlK0OP.js +0 -817
  35. package/dist/assets/index-DYDLCcUp.css +0 -1
  36. package/dist/assets/javascript-ChLWbrPC.js +0 -1
  37. package/dist/assets/liquid-DT0mZNaG.js +0 -1
  38. package/dist/assets/mdx-D1JAVlmB.js +0 -1
  39. package/dist/assets/python-D3Fng_k0.js +0 -1
  40. package/dist/assets/razor-BXCxNJBR.js +0 -1
  41. package/dist/assets/typescript-BtZpkOs3.js +0 -1
  42. package/dist/assets/xml-CeLodlPg.js +0 -1
  43. package/dist/assets/yaml-DSrp7EaV.js +0 -1
package/README.md CHANGED
@@ -19,6 +19,9 @@ npm i -D @reformer/builder
19
19
  npx reformer-builder # поднимет локальный сервер и откроет браузер
20
20
  npx reformer-builder --port 5000
21
21
  npx reformer-builder --no-open # не открывать браузер автоматически
22
+
23
+ # локальная кастомизация клиента: свой каталог компонентов и/или конфиг билдера
24
+ npx reformer-builder --catalog ./component-catalog.json --config ./reformer-builder.config.json
22
25
  ```
23
26
 
24
27
  Или добавьте скрипт в свой `package.json`:
@@ -37,13 +40,18 @@ npm run builder
37
40
 
38
41
  ### Опции CLI
39
42
 
40
- | Опция | По умолчанию | Описание |
41
- | ---------------- | ------------ | ----------------------------------------------- |
42
- | `-p, --port <n>` | `4321` | Порт (если занят — берётся следующий свободный) |
43
- | `--host <h>` | `127.0.0.1` | Хост |
44
- | `--no-open` | — | Не открывать браузер автоматически |
45
- | `-h, --help` | — | Справка |
46
- | `-v, --version` | — | Версия |
43
+ | Опция | По умолчанию | Описание |
44
+ | ------------------ | ------------ | --------------------------------------------------- |
45
+ | `-p, --port <n>` | `4321` | Порт (если занят — берётся следующий свободный) |
46
+ | `--host <h>` | `127.0.0.1` | Хост |
47
+ | `--no-open` | — | Не открывать браузер автоматически |
48
+ | `--catalog <path>` | — | JSON-каталог компонентов (замещает вшитый) |
49
+ | `--config <path>` | — | JSON-конфиг билдера (палитра, UI, брендинг, проект) |
50
+ | `-h, --help` | — | Справка |
51
+ | `-v, --version` | — | Версия |
52
+
53
+ Без `--catalog`/`--config` билдер пытается подхватить `component-catalog.json` и
54
+ `reformer-builder.config.json` из текущей папки; если их нет — работает на вшитых дефолтах.
47
55
 
48
56
  ## Режимы работы
49
57
 
@@ -63,7 +71,61 @@ npm run builder
63
71
 
64
72
  Каталог **вшивается в бандл на этапе сборки** билдера (Vite инлайнит импорт
65
73
  `@reformer/ui-kit/catalog`), поэтому опубликованный `@reformer/builder` самодостаточен и
66
- показывает набор компонентов той версии `@reformer/ui-kit`, с которой был собран.
74
+ показывает набор компонентов той версии `@reformer/ui-kit`, с которой был собран. Этот вшитый
75
+ каталог можно **переопределить при локальном старте** флагом `--catalog` (см. ниже) — без
76
+ пересборки билдера.
77
+
78
+ ## Локальная кастомизация (`--catalog` / `--config`)
79
+
80
+ Клиент может передать при локальном старте два JSON-файла — они читаются launcher'ом и
81
+ применяются в браузере на бутстрапе. Оба **опциональны**: если файла нет — берутся встроенные
82
+ дефолты (билдер стартует и работает как обычно). Если переданный файл невалиден — билдер
83
+ показывает экран с ошибкой валидации (не запускается с частичной конфигурацией).
84
+
85
+ - **`--catalog <path>`** — JSON-каталог компонентов по тому же контракту, что
86
+ `@reformer/ui-kit/catalog` (`{ version, components: [{ name, role, propsSchema, category? }] }`).
87
+ Замещает вшитый каталог: палитра и инспектор строятся из него.
88
+ - **`--config <path>`** — конфиг поведения самого билдера. Все секции опциональны:
89
+
90
+ ```jsonc
91
+ {
92
+ "$schema": "./node_modules/@reformer/builder/runtime-config.schema.json",
93
+ "version": "1.0",
94
+ // Брендинг оболочки
95
+ "branding": {
96
+ "productName": "Acme Forms",
97
+ "title": "Acme Builder",
98
+ "logoUrl": "data:image/svg+xml;...",
99
+ },
100
+ // Палитра: категории, порядок разделов, свёрнутые по умолчанию, глифы-бейджи
101
+ "palette": {
102
+ "categoryByName": { "AcmeRating": "Поля ввода" },
103
+ "order": ["Поля ввода", "Выбор и переключатели", "Контейнеры", "Действия"],
104
+ "collapsedByDefault": ["HTML", "Типографика"],
105
+ "glyphs": { "AcmeRating": "Ar" },
106
+ },
107
+ // Доступные компоненты: whitelist/blacklist + тоглы синтетических записей билдера
108
+ "components": {
109
+ "exclude": ["Chart", "Carousel"],
110
+ "synthetic": {
111
+ "html": true,
112
+ "htmlTags": ["div", "section", "fieldset"],
113
+ "formArray": true,
114
+ "wizard": false,
115
+ },
116
+ },
117
+ // Дефолты интерфейса при старте
118
+ "ui": { "theme": "light", "leftPanel": "palette", "rightOpen": true, "preview": "wire" },
119
+ // Работа с проектом (Mode B discovery) + стартовая схема Mode A (null — пустая форма)
120
+ "project": { "ignoreDirs": ["fixtures"], "seedSchema": null },
121
+ }
122
+ ```
123
+
124
+ Семантика слияния: карты (`palette.categoryByName`, `palette.glyphs`) и списки безопасных
125
+ дефолтов (`project.ignoreDirs`, `project.skipFiles`, `project.formSchemaMarkers`) **дополняют**
126
+ встроенные; `palette.order`, `palette.collapsedByDefault`, `components.*`, `ui.*`, `branding.*` —
127
+ **замещают** дефолт, если заданы. JSON Schema контракта конфига публикуется в пакете как
128
+ `@reformer/builder/runtime-config.schema.json` (для автодополнения в редакторе через `$schema`).
67
129
 
68
130
  > **Требование к упаковке `@reformer/ui-kit`:** чтобы subpath `@reformer/ui-kit/catalog`
69
131
  > резолвился у внешних потребителей, `component-catalog.json` обязан входить в поле `files`
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Типы для тестируемых экспортов launcher'а (`reformer-builder.mjs` — zero-dependency JS без
3
+ * деклараций). Позволяет `.ts`-тестам импортировать хелперы без implicit-any (TS7016).
4
+ */
5
+
6
+ export interface LauncherOpts {
7
+ port: number;
8
+ host: string;
9
+ open: boolean;
10
+ help: boolean;
11
+ version: boolean;
12
+ /** Путь к каталогу компонентов (`--catalog`) или `null` (авто-детект в cwd). */
13
+ catalog: string | null;
14
+ /** Путь к конфигу билдера (`--config`) или `null` (авто-детект в cwd). */
15
+ config: string | null;
16
+ }
17
+
18
+ export interface RuntimeBundleResult {
19
+ payload: { catalog: unknown; config: unknown };
20
+ sources: { catalog: string | null; config: string | null };
21
+ }
22
+
23
+ /** URL раздачи клиентского bundle. */
24
+ export const RUNTIME_BUNDLE_URL: string;
25
+
26
+ export function parseArgs(argv: string[]): LauncherOpts;
27
+
28
+ export function loadRuntimeBundle(
29
+ opts: { catalog: string | null; config: string | null },
30
+ cwd: string
31
+ ): Promise<RuntimeBundleResult>;
32
+
33
+ export function createRequestHandler(
34
+ indexHtmlPath: string,
35
+ runtimeBundleBody: Buffer
36
+ ): (req: unknown, res: unknown) => Promise<void>;
@@ -7,15 +7,27 @@
7
7
  * поднимает `dist/` на localhost и открывает браузер. Серверной логики у билдера нет — вся работа с
8
8
  * файлами проекта идёт в браузере через File System Access API (нужен Chromium-браузер).
9
9
  *
10
+ * Клиент может передать 2 JSON-файла для локальной кастомизации: каталог компонентов (--catalog)
11
+ * и конфиг билдера (--config). Launcher читает их с диска и отдаёт SPA по /__reformer-builder/
12
+ * runtime.json; без флагов пытается авто-подхватить одноимённые файлы из cwd. Отсутствие файлов —
13
+ * билдер работает на вшитых дефолтах.
14
+ *
10
15
  * Использование:
11
- * npx reformer-builder [--port <n>] [--host <h>] [--no-open]
16
+ * npx reformer-builder [--port <n>] [--host <h>] [--no-open] [--catalog <path>] [--config <path>]
12
17
  */
13
18
 
14
19
  import { createServer } from 'node:http';
15
20
  import { readFile, stat } from 'node:fs/promises';
16
21
  import { spawn } from 'node:child_process';
17
22
  import { fileURLToPath } from 'node:url';
18
- import { extname, join, normalize, sep } from 'node:path';
23
+ import { extname, join, normalize, resolve, sep } from 'node:path';
24
+
25
+ /** URL, по которому SPA забирает клиентский bundle (совпадает с RUNTIME_BUNDLE_PATH в src/config/load). */
26
+ export const RUNTIME_BUNDLE_URL = '/__reformer-builder/runtime.json';
27
+
28
+ /** Имена файлов для авто-детекта в cwd, если флаги `--catalog`/`--config` не заданы. */
29
+ const DEFAULT_CATALOG_FILE = 'component-catalog.json';
30
+ const DEFAULT_CONFIG_FILE = 'reformer-builder.config.json';
19
31
 
20
32
  // Без хвостового разделителя: иначе `distDir + sep` даёт двойной слэш и проверка
21
33
  // границы каталога в resolveFsPath() никогда не совпадает (все запросы → 400).
@@ -51,8 +63,64 @@ async function readVersion() {
51
63
  }
52
64
  }
53
65
 
54
- function parseArgs(argv) {
55
- const opts = { port: 4321, host: '127.0.0.1', open: true, help: false, version: false };
66
+ /**
67
+ * Прочитать и распарсить клиентский JSON-файл. Явно переданный (`--catalog`/`--config`) файл
68
+ * обязателен — при ошибке чтения/парсинга завершаемся с сообщением. Авто-детект: отсутствие файла
69
+ * (ENOENT) — тихий пропуск (fallback на вшитые дефолты); присутствует, но битый JSON — ошибка
70
+ * (файл явно предназначен к использованию).
71
+ */
72
+ async function readRuntimeFile(path, { explicit, label }) {
73
+ let text;
74
+ try {
75
+ text = await readFile(path, 'utf8');
76
+ } catch (err) {
77
+ if (explicit) {
78
+ console.error(`reformer-builder: не удалось прочитать ${label} "${path}": ${err.message}`);
79
+ process.exit(1);
80
+ }
81
+ return null; // авто-детект: файла нет — работаем на вшитых дефолтах
82
+ }
83
+ try {
84
+ return JSON.parse(text);
85
+ } catch (err) {
86
+ console.error(`reformer-builder: невалидный JSON в ${label} "${path}": ${err.message}`);
87
+ process.exit(1);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Собрать runtime-bundle для SPA из файлов клиента. Пути берутся из флагов, иначе — авто-детект
93
+ * одноимённых файлов в cwd. Структурную валидацию делает SPA (реюз validateCatalog + AJV конфига);
94
+ * здесь ловим только отсутствие/JSON-синтаксис.
95
+ */
96
+ export async function loadRuntimeBundle(opts, cwd) {
97
+ const catalogPath = resolve(cwd, opts.catalog ?? DEFAULT_CATALOG_FILE);
98
+ const configPath = resolve(cwd, opts.config ?? DEFAULT_CONFIG_FILE);
99
+ const catalog = await readRuntimeFile(catalogPath, {
100
+ explicit: Boolean(opts.catalog),
101
+ label: 'каталог (--catalog)',
102
+ });
103
+ const config = await readRuntimeFile(configPath, {
104
+ explicit: Boolean(opts.config),
105
+ label: 'конфиг (--config)',
106
+ });
107
+ return {
108
+ payload: { catalog: catalog ?? null, config: config ?? null },
109
+ sources: { catalog: catalog ? catalogPath : null, config: config ? configPath : null },
110
+ };
111
+ }
112
+
113
+ export function parseArgs(argv) {
114
+ const opts = {
115
+ port: 4321,
116
+ host: '127.0.0.1',
117
+ open: true,
118
+ help: false,
119
+ version: false,
120
+ // Явно переданные клиентом пути (null — не задан, будет авто-детект в cwd).
121
+ catalog: null,
122
+ config: null,
123
+ };
56
124
  for (let i = 0; i < argv.length; i++) {
57
125
  const a = argv[i];
58
126
  if (a === '--help' || a === '-h') opts.help = true;
@@ -63,6 +131,10 @@ function parseArgs(argv) {
63
131
  else if (a.startsWith('--port=')) opts.port = Number(a.slice('--port='.length));
64
132
  else if (a === '--host') opts.host = String(argv[++i]);
65
133
  else if (a.startsWith('--host=')) opts.host = a.slice('--host='.length);
134
+ else if (a === '--catalog') opts.catalog = String(argv[++i]);
135
+ else if (a.startsWith('--catalog=')) opts.catalog = a.slice('--catalog='.length);
136
+ else if (a === '--config') opts.config = String(argv[++i]);
137
+ else if (a.startsWith('--config=')) opts.config = a.slice('--config='.length);
66
138
  else {
67
139
  console.error(`reformer-builder: неизвестный аргумент "${a}" (см. --help)`);
68
140
  process.exit(1);
@@ -82,11 +154,16 @@ function printHelp() {
82
154
  npx reformer-builder [опции]
83
155
 
84
156
  Опции:
85
- -p, --port <n> Порт (по умолчанию 4321; занят — берётся следующий свободный)
86
- --host <h> Хост (по умолчанию 127.0.0.1)
87
- --no-open Не открывать браузер автоматически
88
- -h, --help Показать эту справку
89
- -v, --version Показать версию
157
+ -p, --port <n> Порт (по умолчанию 4321; занят — берётся следующий свободный)
158
+ --host <h> Хост (по умолчанию 127.0.0.1)
159
+ --no-open Не открывать браузер автоматически
160
+ --catalog <path> Каталог компонентов (JSON) для палитры/инспектора
161
+ --config <path> Конфиг билдера (JSON): палитра, доступные компоненты, дефолты UI, брендинг
162
+ -h, --help Показать эту справку
163
+ -v, --version Показать версию
164
+
165
+ Без --catalog/--config билдер пытается подхватить component-catalog.json и
166
+ reformer-builder.config.json из текущей папки; если их нет — работает на вшитых дефолтах.
90
167
 
91
168
  Примечание: режим «открыть папку проекта» (File System Access API) работает только в
92
169
  Chromium-браузерах (Chrome/Edge/Arc/Brave).`);
@@ -142,7 +219,7 @@ async function sendFile(res, filePath, statusCode = 200) {
142
219
  res.end(body);
143
220
  }
144
221
 
145
- function createRequestHandler(indexHtmlPath) {
222
+ export function createRequestHandler(indexHtmlPath, runtimeBundleBody) {
146
223
  return async (req, res) => {
147
224
  if (req.method !== 'GET' && req.method !== 'HEAD') {
148
225
  res.writeHead(405, { Allow: 'GET, HEAD' });
@@ -150,6 +227,16 @@ function createRequestHandler(indexHtmlPath) {
150
227
  return;
151
228
  }
152
229
  const pathname = (req.url || '/').split('?')[0].split('#')[0];
230
+ // Клиентский runtime-bundle (каталог/конфиг из файлов) — отдаём ДО резолва static/dist.
231
+ if (pathname === RUNTIME_BUNDLE_URL) {
232
+ res.writeHead(200, {
233
+ 'Content-Type': 'application/json; charset=utf-8',
234
+ 'Content-Length': runtimeBundleBody.length,
235
+ 'Cache-Control': 'no-cache',
236
+ });
237
+ res.end(req.method === 'HEAD' ? undefined : runtimeBundleBody);
238
+ return;
239
+ }
153
240
  const target = resolveFsPath(pathname === '/' ? '/index.html' : pathname);
154
241
  if (target === null) {
155
242
  res.writeHead(400);
@@ -233,7 +320,12 @@ async function main() {
233
320
  process.exit(1);
234
321
  }
235
322
 
236
- const server = createServer(createRequestHandler(indexHtmlPath));
323
+ // Клиентский конфиг/каталог из файлов (флаги или авто-детект в cwd). Отдаётся SPA по
324
+ // RUNTIME_BUNDLE_URL; отсутствие файлов ⇒ пустой bundle ⇒ билдер на вшитых дефолтах.
325
+ const runtime = await loadRuntimeBundle(opts, process.cwd());
326
+ const runtimeBundleBody = Buffer.from(JSON.stringify(runtime.payload));
327
+
328
+ const server = createServer(createRequestHandler(indexHtmlPath, runtimeBundleBody));
237
329
  let port;
238
330
  try {
239
331
  port = await listenWithFallback(server, opts.host, opts.port);
@@ -246,6 +338,8 @@ async function main() {
246
338
  const url = `http://${displayHost}:${port}/`;
247
339
  console.log(`\n reformer-builder v${await readVersion()}`);
248
340
  console.log(` Локальный сервер: ${url}`);
341
+ if (runtime.sources.catalog) console.log(` Каталог из файла: ${runtime.sources.catalog}`);
342
+ if (runtime.sources.config) console.log(` Конфиг из файла: ${runtime.sources.config}`);
249
343
  console.log(` Режим «открыть папку проекта» требует Chromium-браузер (File System Access API).`);
250
344
  console.log(` Остановить: Ctrl+C\n`);
251
345
 
@@ -260,7 +354,10 @@ async function main() {
260
354
  process.on('SIGTERM', shutdown);
261
355
  }
262
356
 
263
- main().catch((err) => {
264
- console.error(`reformer-builder: ${err?.stack || err}`);
265
- process.exit(1);
266
- });
357
+ // Не запускаем сервер при импорте из тестов (юнит-тесты дёргают экспортированные хелперы).
358
+ if (process.env.REFORMER_BUILDER_TEST !== '1') {
359
+ main().catch((err) => {
360
+ console.error(`reformer-builder: ${err?.stack || err}`);
361
+ process.exit(1);
362
+ });
363
+ }