@foxford/den 3.0.0 → 3.1.1

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 (57) hide show
  1. package/README.mdx +266 -2
  2. package/adapter.cjs +1 -1
  3. package/adapter.d.cts +1 -1
  4. package/adapter.d.ts +1 -1
  5. package/adapter.js +1 -1
  6. package/bin/den.cjs +88 -0
  7. package/bin/den.d.cts +2 -0
  8. package/bin/den.d.ts +2 -0
  9. package/bin/den.js +88 -0
  10. package/builder/index.cjs +12 -0
  11. package/builder/index.d.cts +120 -0
  12. package/builder/index.d.ts +120 -0
  13. package/builder/index.js +12 -0
  14. package/chunk-3RX2OSUL.js +21 -0
  15. package/{chunk-HJO26HIQ.js → chunk-6FNC3XMI.js} +4 -0
  16. package/chunk-6QV4L6R2.cjs +21 -0
  17. package/{chunk-WRQC6BVJ.js → chunk-MCN4GFEQ.js} +4 -19
  18. package/chunk-OKRO4G4L.cjs +354 -0
  19. package/chunk-REFU7UMG.js +354 -0
  20. package/{chunk-XHYR3SGG.cjs → chunk-T5QVCXVB.cjs} +5 -1
  21. package/chunk-TPHJUDHG.js +230 -0
  22. package/{chunk-5DJZZML5.cjs → chunk-Z5BL4DJA.cjs} +10 -25
  23. package/chunk-ZOU6W6ZQ.cjs +230 -0
  24. package/{define-repository-MDEWHqM7.d.cts → define-repository-CwAXed2X.d.cts} +1 -1
  25. package/{define-repository-Ca62_YCy.d.ts → define-repository-q_T4hqeP.d.ts} +1 -1
  26. package/{define-slot-Dil8Kr1A.d.ts → define-slot-Cngzae6i.d.ts} +1 -1
  27. package/{define-slot-BKvArA02.d.cts → define-slot-DYNCLDJY.d.cts} +1 -1
  28. package/define.cjs +6 -4
  29. package/define.d.cts +3 -3
  30. package/define.d.ts +3 -3
  31. package/define.js +5 -3
  32. package/den.js +60 -0
  33. package/descriptor-DQ6Gi7A_.d.ts +98 -0
  34. package/descriptor-Dujg_1on.d.cts +98 -0
  35. package/index.cjs +25 -23
  36. package/index.d.cts +7 -5
  37. package/index.d.ts +7 -5
  38. package/index.js +12 -10
  39. package/island/index.cjs +12 -157
  40. package/island/index.d.cts +40 -30
  41. package/island/index.d.ts +40 -30
  42. package/island/index.js +19 -164
  43. package/package.json +59 -16
  44. package/routes-0uXJ0fux.d.ts +64 -0
  45. package/routes-B0hDrkiF.d.cts +64 -0
  46. package/serve/index.cjs +137 -0
  47. package/serve/index.d.cts +60 -0
  48. package/serve/index.d.ts +60 -0
  49. package/serve/index.js +137 -0
  50. package/server-render-CocWXQKC.d.cts +48 -0
  51. package/server-render-CocWXQKC.d.ts +48 -0
  52. package/{types-COwVwgzE.d.cts → types-Dx5qGiwk.d.cts} +1 -1
  53. package/{types-COwVwgzE.d.ts → types-Dx5qGiwk.d.ts} +1 -1
  54. package/view-adapter-B4gEb-ms.d.ts +77 -0
  55. package/view-adapter-DJ7yB6Ml.d.cts +77 -0
  56. package/view-adapter-BvDC5O4y.d.cts +0 -188
  57. package/view-adapter-DtNgyjIf.d.ts +0 -188
package/README.mdx CHANGED
@@ -4,6 +4,270 @@ title: Den
4
4
 
5
5
  # @foxford/den
6
6
 
7
- Den — декларативный метафреймворк Foxford (core runtime).
7
+ Декларативный метафреймворк Foxford: приложение объявляет, из чего оно состоит, а исполняет
8
+ это движок. Ядро host-агностично — про Vike и Next знают отдельные пакеты
9
+ (`@foxford/den-vike`, `@foxford/den-next`), про React — `@foxford/den-react`.
8
10
 
9
- Содержит core runtime: define* API, lifecycle, container orchestration, NavigationManager.
11
+ Здесь примеры. Почему устроено именно так в [SPECIFICATION.md](./SPECIFICATION.md).
12
+
13
+ ## Единицы приложения
14
+
15
+ Слои объявляются `define*`-функциями и складываются в контейнер. Зависимости приходят через
16
+ `requires` и резолвятся движком, поэтому единица не знает, кто её создаёт.
17
+
18
+ ```ts
19
+ // content-page.repository.ts
20
+ import { defineRepository } from '@foxford/den/define'
21
+
22
+ export default defineRepository(ContentPageRepositoryToken, {
23
+ create: () => ({
24
+ getPage: async (path: string) => fetch(`/api/pages${path}`).then((res) => res.json()),
25
+ }),
26
+ })
27
+ ```
28
+
29
+ ```ts
30
+ // content-page.vm.ts
31
+ import { defineViewModel } from '@foxford/den/define'
32
+ import { ViewModel } from '@foxford/vm'
33
+
34
+ import type { IslandRoute } from '@foxford/den/island'
35
+
36
+ export class ContentPageVM extends ViewModel<ReturnType<typeof makeState>> {
37
+ constructor(private readonly service: ContentPageService) {
38
+ super(makeState())
39
+ }
40
+
41
+ /** Зовёт движок при рендере страницы — на сервере и в браузере одинаково. */
42
+ async activate(route: IslandRoute): Promise<void> {
43
+ this.state.set(await loadContentPage(this.service, route.pathname))
44
+ }
45
+ }
46
+
47
+ export default defineViewModel(ContentPageVmToken, {
48
+ create: ([service]) => new ContentPageVM(service),
49
+ requires: [ContentPageServiceToken] as const,
50
+ })
51
+ ```
52
+
53
+ ## Заявка на адреса и её параметры
54
+
55
+ Приложение объявляет, какие пути берётся обслуживать. Заявка — **регулярка**: она строго
56
+ выразительнее шаблона (`'.*'` как ловец остатка, префиксы, альтернативы), и порядок в списке
57
+ значим — хост отдаёт путь первому заявившему, поэтому частные записи ставятся выше общих.
58
+
59
+ Параметры адреса — именованные группы. Писать их руками не нужно, для этого `pathPattern`:
60
+
61
+ ```ts
62
+ import { pathPattern } from '@foxford/den/island'
63
+
64
+ routes: [
65
+ pathPattern('/course/:id'), // → '^/course/(?<id>[^/]+)$'
66
+ '^/legal', // обычная регулярка рядом
67
+ ]
68
+ ```
69
+
70
+ Выделяет их `matchRoute` — один матчер на всех: по нему хост решает, кому отдать адрес,
71
+ а рантайм приложения — своё ли это. Разойдись эти два ответа, приложение получало бы запросы
72
+ по путям, которых не заявляло. Значения раскодированы (`/legal/%D0%BE...` → `оферта`).
73
+
74
+ ## Шов рендера: разметка и состояние одним вызовом
75
+
76
+ `renderApp` — то, чем хост спрашивает у приложения страницу. Внутри движок поднимает контейнер,
77
+ активирует VM по адресу, рисует дерево и снимает состояние; дескриптор при этом остаётся
78
+ декларацией без поведения.
79
+
80
+ ```ts
81
+ import { renderApp } from '@foxford/den/island'
82
+
83
+ const { status, html, state, head, headHtml, redirect, assets } = await renderApp(app, {
84
+ pathname: '/legal/general',
85
+ search: '?from=footer',
86
+ context: { cookies, headers }, // реквизиты браузерного запроса
87
+ })
88
+ ```
89
+
90
+ `status` — исход запроса: `200` значит «вот страница», всё остальное значит, что страницы по
91
+ этому адресу нет, и тело такого ответа выбирает хост. `state` едет в документ и гидрирует
92
+ остров в браузере, `headHtml` — стили, собранные во время рендера. `assets` — ссылки на
93
+ initial-чанки клиентской сборки, их подключает хост; байты приложение не отдаёт ни в одном
94
+ транспорте.
95
+
96
+ ### Вклад в документ
97
+
98
+ Заголовок и исход сообщает VM — необязательным методом `describeDocument`. Полем дескриптора
99
+ это быть не может: после активации только VM знает, нашлась ли страница по адресу.
100
+
101
+ ```ts
102
+ export class ContentPageVM extends ViewModel<State> {
103
+ /** В ручку ходим ЗДЕСЬ. */
104
+ async activate(route: IslandRoute): Promise<void> {
105
+ this.state.set(await loadContentPage(this.service, route.pathname))
106
+ }
107
+
108
+ /** А здесь только решаем по тому, что уже приехало. */
109
+ describeDocument(): DocumentContribution {
110
+ const { page } = this.state.get()
111
+
112
+ if (page === null) {
113
+ return { status: 404 } // хост ответит честным 404
114
+ }
115
+
116
+ return { head: { description: page.description, title: page.title } }
117
+ }
118
+ }
119
+ ```
120
+
121
+ `describeDocument` синхронный намеренно. Приложению, которому «есть ли такая страница» известно
122
+ только от бэкенда, второй поход не нужен: движок ждёт активацию ДО сбора вклада, поэтому к
123
+ моменту вызова ответ уже лежит в состоянии. Разреши мы тут асинхронность — у рендера появилась
124
+ бы вторая фаза походов в сеть, а логика загрузки разъехалась бы по двум местам.
125
+
126
+ Редирект объявляется там же: `{ redirect: '/user/login' }` — без статуса это `302`.
127
+
128
+ ## Сетевой транспорт: то же самое по HTTP
129
+
130
+ Приложение поднимается отдельным сервисом **без единой строки своего серверного кода** —
131
+ дескриптор тот же, что и в процессе хоста. Точку входа процесса выпускает `den build`
132
+ (см. «Сборка приложения»), поэтому прод это голая нода:
133
+
134
+ ```jsonc
135
+ // package.json приложения
136
+ "scripts": {
137
+ "dev": "den dev",
138
+ "build": "den build",
139
+ "start": "node build/server.mjs"
140
+ }
141
+ ```
142
+
143
+ Рантайм вокруг дескриптора отдаёт три ручки:
144
+
145
+ | Ручка | Ответ |
146
+ | --- | --- |
147
+ | `GET /_render?url=<path+search>` | `200` + ответ `renderApp` телом |
148
+ | `GET /_routes` | `200` + массив regex-строк заявки на адреса |
149
+ | `GET /_health` | `200 ok` |
150
+
151
+ **Статус HTTP описывает транспорт, а исход страницы едет полем `status` в теле.** `200` значит
152
+ «приложение ответило» — хоть `404`, хоть редиректом. Всё остальное значит «до приложения не
153
+ дошли»: `400` — сломанный запрос, `404` — неизвестная ручка или путь вне заявки, `500` — рендер
154
+ упал. Так `404` от приложения не путается с `404` от неверно настроенного прокси.
155
+
156
+ Браузерные заголовки рантайм читает только из `X-Forwarded-*` и кладёт в `RequestContextToken`;
157
+ `Cookie` — обычным именем.
158
+
159
+ ### Расширение снаружи: пример с Sentry
160
+
161
+ Основа — fastify, и отдаётся она плагином, поэтому Sentry, метрики, cors и трейсинг ставит
162
+ ПОТРЕБИТЕЛЬ. Своих опций под каждую такую потребность пакет не заводит.
163
+
164
+ Инициализация Sentry должна пройти раньше всего остального кода, поэтому она живёт отдельным
165
+ модулем и грузится флагом `--import`, а не из точки входа:
166
+
167
+ ```ts
168
+ // instrument.js — грузится раньше приложения
169
+ import * as Sentry from '@sentry/node'
170
+
171
+ Sentry.init({
172
+ dsn: process.env.SENTRY_DSN,
173
+ environment: process.env.NODE_ENV,
174
+ tracesSampleRate: 0.1,
175
+ })
176
+ ```
177
+
178
+ Обработчик ошибок вешается уже на готовый инстанс — это и есть `configure`:
179
+
180
+ ```ts
181
+ // configure.js — дефолтный экспорт получает инстанс fastify до прослушивания
182
+ import * as Sentry from '@sentry/node'
183
+
184
+ export default (instance) => {
185
+ Sentry.setupFastifyErrorHandler(instance)
186
+
187
+ instance.get('/metrics', () => renderMetrics())
188
+ }
189
+ ```
190
+
191
+ Такое расширение объявляется в конфиге приложения полем `server.configure` — своего серверного
192
+ кода по-прежнему ноль. Запуск с инструментацией:
193
+ `NODE_OPTIONS='--import ./instrument.js' pnpm start`.
194
+
195
+ Если сервер нужен свой — общий порт с чужими ручками, свой порядок плагинов, — берите плагин
196
+ и монтируйте куда угодно, хоть под префиксом:
197
+
198
+ ```ts
199
+ import Fastify from 'fastify'
200
+ import * as Sentry from '@sentry/node'
201
+ import { appPlugin } from '@foxford/den/serve'
202
+
203
+ const instance = Fastify()
204
+ Sentry.setupFastifyErrorHandler(instance)
205
+
206
+ await instance.register(appPlugin, { app, prefix: '/den' })
207
+ await instance.listen({ port: 3342 })
208
+ ```
209
+
210
+ `serveApp` поверх плагина добавляет логирование запросов (в терминах `@foxford/logger`, корень —
211
+ имя приложения, рендер дольше порога уходит в `warn`) и закрытие по `SIGTERM`/`SIGINT`.
212
+
213
+ ## Сборка приложения: `den build`
214
+
215
+ Приложение объявляет дескриптор и `den.config.ts`. Конфиг сборщика, вход гидрации и точку входа
216
+ процесса писать не нужно — их собирает команда.
217
+
218
+ ```ts
219
+ // den.config.ts
220
+ import { defineAppConfig } from '@foxford/den/build'
221
+ import { react } from '@foxford/den-react/preset'
222
+
223
+ export default defineAppConfig({
224
+ preset: react({ babel: { plugins: [['babel-plugin-styled-components', { ssr: true }]] } }),
225
+ server: {
226
+ setup: () => installServerDom(),
227
+ render: (tree, render) => ({ head: sheet.getStyleTags(), html: render(sheet.collectStyles(tree)) }),
228
+ container: (c) => c.bind(HttpConfigToken).toValue({ baseUrl: process.env.API_ORIGIN }),
229
+ configure: (instance) => Sentry.setupFastifyErrorHandler(instance),
230
+ noExternal: ['@foxford/ui', 'styled-components'],
231
+ },
232
+ })
233
+ ```
234
+
235
+ `den build [--root <каталог>] [--base <префикс>]` выпускает три выхода, `den dev` — то же плюс
236
+ подъём процесса и пересборка на изменение исходников:
237
+
238
+ | Выход | Что внутри | Кто потребляет |
239
+ | --- | --- | --- |
240
+ | `build/client` | браузерный бандл и манифест | браузер, по ссылкам из ответа рендера |
241
+ | `build/server` | дескриптор с вплавленными серверными кусками конфига | хост, импортом в своём процессе |
242
+ | `build/server.mjs` | тот же дескриптор под сетевым рантаймом | `node build/server.mjs` |
243
+
244
+ **Про фреймворк команда не знает.** Чем собирать и чем оживлять, приносит пресет из пакета
245
+ адаптера, поэтому приложение на другом фреймворке подключается своим пресетом и в командах не
246
+ меняется ничего.
247
+
248
+ **Конфиг — вход СБОРКИ, а не рантайм-сосед дескриптора.** Серверные куски вплавляются в
249
+ серверный выход, и дескриптор остаётся декларацией — иначе он не годился бы входом клиентской
250
+ сборки: сборщик не выкинет сбор стилей, пока объект дескриптора на него ссылается. Соседом
251
+ конфиг быть не может: приложение в процессе хоста импортируется хостом целиком, конфига у хоста
252
+ нет, и до движка серверные куски не доехали бы.
253
+
254
+ **Движок в серверный выход не вбандливается.** У него модульное состояние, app-контейнер —
255
+ синглтон, и своя копия внутри приложения даёт второй контейнер: приложение биндит в один,
256
+ а резолвит из другого. Симптом — «view-слой не объявлен» на приложении, которое его объявило.
257
+ Клиентский выход наоборот собирается целиком: браузер голых имён модулей не резолвит.
258
+
259
+ **Настройку процесса дожидается движок.** `server.setup` зовётся один раз на процесс до первого
260
+ рендера, включая приложение в процессе хоста, где обвязки нет и дождаться было бы некому.
261
+ Упавшая настройка не повторяется: процесс сломан, и прятать это за случайным успехом нельзя.
262
+
263
+ ## Подпути
264
+
265
+ | Подпуть | Что там |
266
+ | --- | --- |
267
+ | `@foxford/den` | рантайм, `define*`, менеджеры контейнеров и состояния |
268
+ | `@foxford/den/define` | те же `define*` без сайд-эффекта установки резолвера |
269
+ | `@foxford/den/adapter` | шов резолва для адаптеров хостов |
270
+ | `@foxford/den/island` | остров: жизненный цикл, шов `renderApp`, порт `ViewAdapter` |
271
+ | `@foxford/den/serve` | сетевой транспорт: `serveApp`, `appPlugin` |
272
+ | `@foxford/den/build` | конфиг приложения и сборка: `defineAppConfig`, `buildApp` |
273
+ | `@foxford/den/bin` | точка входа команды `den` |
package/adapter.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
 
4
4
  var _chunkC7BM4DGXcjs = require('./chunk-C7BM4DGX.cjs');
5
- require('./chunk-XHYR3SGG.cjs');
5
+ require('./chunk-T5QVCXVB.cjs');
6
6
 
7
7
 
8
8
 
package/adapter.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { R as RequestContext } from './types-COwVwgzE.cjs';
1
+ export { R as RequestContext } from './types-Dx5qGiwk.cjs';
2
2
  import { Token } from '@foxford/ioc';
3
3
 
4
4
  /**
package/adapter.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { R as RequestContext } from './types-COwVwgzE.js';
1
+ export { R as RequestContext } from './types-Dx5qGiwk.js';
2
2
  import { Token } from '@foxford/ioc';
3
3
 
4
4
  /**
package/adapter.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  installResolver,
3
3
  resolveDefinition
4
4
  } from "./chunk-WBHHHICS.js";
5
- import "./chunk-HJO26HIQ.js";
5
+ import "./chunk-6FNC3XMI.js";
6
6
  export {
7
7
  installResolver,
8
8
  resolveDefinition
package/bin/den.cjs ADDED
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+
3
+
4
+ var _chunkOKRO4G4Lcjs = require('../chunk-OKRO4G4L.cjs');
5
+ require('../chunk-A6ZPAM6Z.cjs');
6
+
7
+
8
+ var _chunkT5QVCXVBcjs = require('../chunk-T5QVCXVB.cjs');
9
+
10
+ // src/bin/cli.ts
11
+ var USAGE = `den <\u043A\u043E\u043C\u0430\u043D\u0434\u0430> [\u043E\u043F\u0446\u0438\u0438]
12
+
13
+ \u041A\u043E\u043C\u0430\u043D\u0434\u044B:
14
+ build \u0441\u043E\u0431\u0440\u0430\u0442\u044C \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435: \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u0438\u0439 \u0432\u044B\u0445\u043E\u0434, \u0441\u0435\u0440\u0432\u0435\u0440\u043D\u044B\u0439 \u0434\u0435\u0441\u043A\u0440\u0438\u043F\u0442\u043E\u0440 \u0438 \u0442\u043E\u0447\u043A\u0443
15
+ \u0432\u0445\u043E\u0434\u0430 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430 (build/client, build/server, build/server.mjs)
16
+ dev \u0442\u043E \u0436\u0435 \u043F\u043B\u044E\u0441 \u043F\u043E\u0434\u044A\u0451\u043C \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430 \u0438 \u043F\u0435\u0440\u0435\u0441\u0431\u043E\u0440\u043A\u0430 \u043D\u0430 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0435 \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A\u043E\u0432
17
+
18
+ \u041E\u043F\u0446\u0438\u0438:
19
+ --root <\u043A\u0430\u0442\u0430\u043B\u043E\u0433> \u043A\u043E\u0440\u0435\u043D\u044C \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433)
20
+ --base <\u043F\u0440\u0435\u0444\u0438\u043A\u0441> \u043F\u0440\u0435\u0444\u0438\u043A\u0441 \u0430\u0434\u0440\u0435\u0441\u043E\u0432 \u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u043E\u0432: \u0432 \u043F\u0440\u043E\u0434\u0435 CDN, \u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u043E\u0442\u043A\u0435 \u0440\u0430\u0437\u0432\u044F\u0437\u043A\u0430
21
+ \u0443 \u0445\u043E\u0441\u0442\u0430 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E /)
22
+ -h, --help \u044D\u0442\u0430 \u0441\u043F\u0440\u0430\u0432\u043A\u0430
23
+ `;
24
+ function parseArgs(argv) {
25
+ const options = {};
26
+ let command;
27
+ for (let i = 0; i < argv.length; i += 1) {
28
+ const arg = argv[i];
29
+ if (arg === "-h" || arg === "--help") {
30
+ return { help: true, options: {} };
31
+ }
32
+ if (arg === "--root" || arg === "--base") {
33
+ i += 1;
34
+ options[arg.slice(2)] = argv[i];
35
+ continue;
36
+ }
37
+ if ((arg == null ? void 0 : arg.startsWith("-")) === true) {
38
+ throw new Error(`\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043E\u043F\u0446\u0438\u044F ${arg}`);
39
+ }
40
+ if (command !== void 0) {
41
+ throw new Error("\u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u043E\u0434\u043D\u0430");
42
+ }
43
+ command = arg;
44
+ }
45
+ return { command, options };
46
+ }
47
+ function fail(message) {
48
+ process.stderr.write(`den: ${message}
49
+
50
+ ${USAGE}`);
51
+ process.exit(1);
52
+ }
53
+ function run(argv) {
54
+ return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
55
+ var _a;
56
+ let parsed;
57
+ try {
58
+ parsed = parseArgs(argv);
59
+ } catch (error) {
60
+ fail(error instanceof Error ? error.message : String(error));
61
+ }
62
+ if (parsed.help === true) {
63
+ process.stdout.write(USAGE);
64
+ return;
65
+ }
66
+ const { command, options } = parsed;
67
+ if (command === void 0) {
68
+ fail("\u043D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u0430");
69
+ }
70
+ const target = { base: options["base"], root: (_a = options["root"]) != null ? _a : process.cwd() };
71
+ if (command === "build") {
72
+ yield _chunkOKRO4G4Lcjs.buildApp.call(void 0, target);
73
+ return;
74
+ }
75
+ if (command === "dev") {
76
+ yield _chunkOKRO4G4Lcjs.devApp.call(void 0, target);
77
+ return;
78
+ }
79
+ fail(`\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \xAB${command}\xBB`);
80
+ });
81
+ }
82
+
83
+ // src/bin/den.ts
84
+ run(process.argv.slice(2)).catch((error) => {
85
+ process.stderr.write(`den: ${error instanceof Error ? error.message : String(error)}
86
+ `);
87
+ process.exit(1);
88
+ });
package/bin/den.d.cts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/bin/den.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/bin/den.js ADDED
@@ -0,0 +1,88 @@
1
+ import {
2
+ buildApp,
3
+ devApp
4
+ } from "../chunk-REFU7UMG.js";
5
+ import "../chunk-TPBI6TOU.js";
6
+ import {
7
+ __async
8
+ } from "../chunk-6FNC3XMI.js";
9
+
10
+ // src/bin/cli.ts
11
+ var USAGE = `den <\u043A\u043E\u043C\u0430\u043D\u0434\u0430> [\u043E\u043F\u0446\u0438\u0438]
12
+
13
+ \u041A\u043E\u043C\u0430\u043D\u0434\u044B:
14
+ build \u0441\u043E\u0431\u0440\u0430\u0442\u044C \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435: \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u0438\u0439 \u0432\u044B\u0445\u043E\u0434, \u0441\u0435\u0440\u0432\u0435\u0440\u043D\u044B\u0439 \u0434\u0435\u0441\u043A\u0440\u0438\u043F\u0442\u043E\u0440 \u0438 \u0442\u043E\u0447\u043A\u0443
15
+ \u0432\u0445\u043E\u0434\u0430 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430 (build/client, build/server, build/server.mjs)
16
+ dev \u0442\u043E \u0436\u0435 \u043F\u043B\u044E\u0441 \u043F\u043E\u0434\u044A\u0451\u043C \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430 \u0438 \u043F\u0435\u0440\u0435\u0441\u0431\u043E\u0440\u043A\u0430 \u043D\u0430 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0435 \u0438\u0441\u0445\u043E\u0434\u043D\u0438\u043A\u043E\u0432
17
+
18
+ \u041E\u043F\u0446\u0438\u0438:
19
+ --root <\u043A\u0430\u0442\u0430\u043B\u043E\u0433> \u043A\u043E\u0440\u0435\u043D\u044C \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433)
20
+ --base <\u043F\u0440\u0435\u0444\u0438\u043A\u0441> \u043F\u0440\u0435\u0444\u0438\u043A\u0441 \u0430\u0434\u0440\u0435\u0441\u043E\u0432 \u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u043E\u0432: \u0432 \u043F\u0440\u043E\u0434\u0435 CDN, \u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u043E\u0442\u043A\u0435 \u0440\u0430\u0437\u0432\u044F\u0437\u043A\u0430
21
+ \u0443 \u0445\u043E\u0441\u0442\u0430 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E /)
22
+ -h, --help \u044D\u0442\u0430 \u0441\u043F\u0440\u0430\u0432\u043A\u0430
23
+ `;
24
+ function parseArgs(argv) {
25
+ const options = {};
26
+ let command;
27
+ for (let i = 0; i < argv.length; i += 1) {
28
+ const arg = argv[i];
29
+ if (arg === "-h" || arg === "--help") {
30
+ return { help: true, options: {} };
31
+ }
32
+ if (arg === "--root" || arg === "--base") {
33
+ i += 1;
34
+ options[arg.slice(2)] = argv[i];
35
+ continue;
36
+ }
37
+ if ((arg == null ? void 0 : arg.startsWith("-")) === true) {
38
+ throw new Error(`\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043E\u043F\u0446\u0438\u044F ${arg}`);
39
+ }
40
+ if (command !== void 0) {
41
+ throw new Error("\u043A\u043E\u043C\u0430\u043D\u0434\u0430 \u0443\u043A\u0430\u0437\u044B\u0432\u0430\u0435\u0442\u0441\u044F \u043E\u0434\u043D\u0430");
42
+ }
43
+ command = arg;
44
+ }
45
+ return { command, options };
46
+ }
47
+ function fail(message) {
48
+ process.stderr.write(`den: ${message}
49
+
50
+ ${USAGE}`);
51
+ process.exit(1);
52
+ }
53
+ function run(argv) {
54
+ return __async(this, null, function* () {
55
+ var _a;
56
+ let parsed;
57
+ try {
58
+ parsed = parseArgs(argv);
59
+ } catch (error) {
60
+ fail(error instanceof Error ? error.message : String(error));
61
+ }
62
+ if (parsed.help === true) {
63
+ process.stdout.write(USAGE);
64
+ return;
65
+ }
66
+ const { command, options } = parsed;
67
+ if (command === void 0) {
68
+ fail("\u043D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D\u0430 \u043A\u043E\u043C\u0430\u043D\u0434\u0430");
69
+ }
70
+ const target = { base: options["base"], root: (_a = options["root"]) != null ? _a : process.cwd() };
71
+ if (command === "build") {
72
+ yield buildApp(target);
73
+ return;
74
+ }
75
+ if (command === "dev") {
76
+ yield devApp(target);
77
+ return;
78
+ }
79
+ fail(`\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \xAB${command}\xBB`);
80
+ });
81
+ }
82
+
83
+ // src/bin/den.ts
84
+ run(process.argv.slice(2)).catch((error) => {
85
+ process.stderr.write(`den: ${error instanceof Error ? error.message : String(error)}
86
+ `);
87
+ process.exit(1);
88
+ });
@@ -0,0 +1,12 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+ var _chunkOKRO4G4Lcjs = require('../chunk-OKRO4G4L.cjs');
6
+ require('../chunk-A6ZPAM6Z.cjs');
7
+ require('../chunk-T5QVCXVB.cjs');
8
+
9
+
10
+
11
+
12
+ exports.buildApp = _chunkOKRO4G4Lcjs.buildApp; exports.defineAppConfig = _chunkOKRO4G4Lcjs.defineAppConfig; exports.devApp = _chunkOKRO4G4Lcjs.devApp;
@@ -0,0 +1,120 @@
1
+ import { a as ServerRender } from '../server-render-CocWXQKC.cjs';
2
+ import { Container } from '@foxford/ioc';
3
+ import { FastifyInstance } from 'fastify';
4
+ import { PluginOption } from 'vite';
5
+
6
+ /**
7
+ * Пресет фреймворка: чем собирать его исходники и чем оживлять фрагмент в браузере.
8
+ *
9
+ * Отдаёт его пакет адаптера — он единственный, кто знает и то, и другое. Команды сборки
10
+ * пресет не разбирают, а укладывают: `plugins` — в конфиг сборщика, `clientEntry` — в
11
+ * сгенерированный клиентский вход.
12
+ */
13
+ interface AppBuildPreset {
14
+ /** Имя фреймворка — им пресет представляется в логах и сообщениях об ошибках. */
15
+ view: string;
16
+ /** Плагины сборщика, которыми рисуется этот фреймворк. */
17
+ plugins: PluginOption[];
18
+ /**
19
+ * Модуль с `hydrateApp(descriptor)` — по нему сборка генерирует клиентский вход.
20
+ *
21
+ * Специфицируется именем, а не функцией: вход собирается в браузерный бандл, и функция из
22
+ * процесса сборки в него не переедет.
23
+ */
24
+ clientEntry: string;
25
+ /**
26
+ * Проверка, что пресету хватает объявленного: получает зависимости приложения, возвращает
27
+ * причину отказа либо `null`.
28
+ *
29
+ * Здесь, а не в командах: какая зависимость требует сборочной поддержки, знает пресет —
30
+ * командам об этом знать нечем, и захардкоженный список сделал бы их привязанными к стеку.
31
+ * Нужна проверка потому, что цена ошибки — молчание: имена классов разъезжаются между
32
+ * выходами, и это видно только сломанной гидрацией в браузере.
33
+ */
34
+ validate?: (dependencies: string[]) => string | null;
35
+ }
36
+ /** Серверная часть конфига — всё, что вплавляется в серверный выход. */
37
+ interface AppServerConfig {
38
+ /**
39
+ * Настройка процесса перед первым рендером: разбор разметки, полифиллы, прогрев.
40
+ *
41
+ * Дожидается её движок, один раз на процесс, — приложению ждать не нужно.
42
+ */
43
+ setup?: () => void | Promise<void>;
44
+ /**
45
+ * Обёртка серверного рендера: через неё собираются стили, которые фреймворк заводит по
46
+ * ходу рендера, и уезжают в `<head>` разметкой.
47
+ */
48
+ render?: ServerRender;
49
+ /**
50
+ * Биндинги app-контейнера: адрес ручек, ключи, всё, что приложение читает через `requires`.
51
+ *
52
+ * Контейнер приложения — родитель контейнеров островов, поэтому положенное здесь резолвится
53
+ * по parent-chain на любом рендере.
54
+ */
55
+ container?: (container: Container) => void;
56
+ /**
57
+ * Расширение сетевого инстанса до начала прослушивания: Sentry, метрики, cors.
58
+ *
59
+ * Зовётся только сетевым транспортом — в процессе хоста инстанса нет.
60
+ */
61
+ configure?: (instance: FastifyInstance) => void | Promise<void>;
62
+ /**
63
+ * Зависимости, которые серверный выход обязан собрать ИСХОДНИКАМИ, а не оставить внешним
64
+ * модулем.
65
+ *
66
+ * Нужно тем пакетам, чей выход не переживает загрузку через node-ESM: интероп
67
+ * default-экспорта разъезжается, и вместо фабрики приходит пространство имён модуля.
68
+ */
69
+ noExternal?: Array<string | RegExp>;
70
+ }
71
+ /** Конфиг приложения. */
72
+ interface AppConfig {
73
+ /** Модуль дескриптора; по умолчанию `src/index.ts`. */
74
+ entry?: string;
75
+ /** Пресет фреймворка — из пакета адаптера. */
76
+ preset: AppBuildPreset;
77
+ server?: AppServerConfig;
78
+ /**
79
+ * Последнее слово за приложением: правит собранный конфиг сборщика перед запуском.
80
+ *
81
+ * Щель на случай, когда коробки не хватило. Зовётся отдельно для каждого выхода, поэтому
82
+ * правка адресуется тому, кому предназначена.
83
+ */
84
+ vite?: (config: Record<string, unknown>, target: 'server' | 'client') => Record<string, unknown>;
85
+ }
86
+ /**
87
+ * Объявляет конфиг приложения.
88
+ *
89
+ * Функция, а не голый объект, ради типизации на месте: интерфейс проверяется там, где конфиг
90
+ * написан, а не там, где его прочитали команды.
91
+ *
92
+ * @param config - Конфиг приложения
93
+ */
94
+ declare function defineAppConfig(config: AppConfig): AppConfig;
95
+
96
+ /** Что нужно собрать. */
97
+ interface BuildAppOptions {
98
+ /** Корень приложения. */
99
+ root: string;
100
+ /** Префикс адресов артефактов: в проде CDN, в разработке развязка у хоста. */
101
+ base?: string;
102
+ }
103
+ /**
104
+ * Собирает приложение целиком.
105
+ *
106
+ * @param options - Корень приложения и префикс адресов артефактов
107
+ * @throws Если конфига нет либо пресет сообщил, что ему не хватает объявленного
108
+ */
109
+ declare function buildApp(options: BuildAppOptions): Promise<void>;
110
+
111
+ /** Наблюдение берёт те же опции, что сборка: путь у них один. */
112
+ type DevAppOptions = BuildAppOptions;
113
+ /**
114
+ * Собирает приложение и держит его поднятым, пересобирая на изменение исходников.
115
+ *
116
+ * @param options - Корень приложения и префикс адресов артефактов
117
+ */
118
+ declare function devApp(options: DevAppOptions): Promise<void>;
119
+
120
+ export { type AppBuildPreset, type AppConfig, type AppServerConfig, type BuildAppOptions, type DevAppOptions, buildApp, defineAppConfig, devApp };