@foxford/den 3.1.1 → 3.1.3

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/bin/den.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
 
4
- var _chunkOKRO4G4Lcjs = require('../chunk-OKRO4G4L.cjs');
4
+ var _chunkZBB5SBEVcjs = require('../chunk-ZBB5SBEV.cjs');
5
5
  require('../chunk-A6ZPAM6Z.cjs');
6
6
 
7
7
 
@@ -69,11 +69,11 @@ function run(argv) {
69
69
  }
70
70
  const target = { base: options["base"], root: (_a = options["root"]) != null ? _a : process.cwd() };
71
71
  if (command === "build") {
72
- yield _chunkOKRO4G4Lcjs.buildApp.call(void 0, target);
72
+ yield _chunkZBB5SBEVcjs.buildApp.call(void 0, target);
73
73
  return;
74
74
  }
75
75
  if (command === "dev") {
76
- yield _chunkOKRO4G4Lcjs.devApp.call(void 0, target);
76
+ yield _chunkZBB5SBEVcjs.devApp.call(void 0, target);
77
77
  return;
78
78
  }
79
79
  fail(`\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u0430\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u0430 \xAB${command}\xBB`);
package/bin/den.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  buildApp,
3
3
  devApp
4
- } from "../chunk-REFU7UMG.js";
4
+ } from "../chunk-YECHY3NR.js";
5
5
  import "../chunk-TPBI6TOU.js";
6
6
  import {
7
7
  __async
package/builder/index.cjs CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkOKRO4G4Lcjs = require('../chunk-OKRO4G4L.cjs');
5
+ var _chunkZBB5SBEVcjs = require('../chunk-ZBB5SBEV.cjs');
6
6
  require('../chunk-A6ZPAM6Z.cjs');
7
7
  require('../chunk-T5QVCXVB.cjs');
8
8
 
9
9
 
10
10
 
11
11
 
12
- exports.buildApp = _chunkOKRO4G4Lcjs.buildApp; exports.defineAppConfig = _chunkOKRO4G4Lcjs.defineAppConfig; exports.devApp = _chunkOKRO4G4Lcjs.devApp;
12
+ exports.buildApp = _chunkZBB5SBEVcjs.buildApp; exports.defineAppConfig = _chunkZBB5SBEVcjs.defineAppConfig; exports.devApp = _chunkZBB5SBEVcjs.devApp;
@@ -93,12 +93,16 @@ interface AppConfig {
93
93
  */
94
94
  declare function defineAppConfig(config: AppConfig): AppConfig;
95
95
 
96
+ /** Режим сборки: им плагины выбирают рантайм фреймворка. */
97
+ type BuildMode = 'production' | 'development';
96
98
  /** Что нужно собрать. */
97
99
  interface BuildAppOptions {
98
100
  /** Корень приложения. */
99
101
  root: string;
100
102
  /** Префикс адресов артефактов: в проде CDN, в разработке развязка у хоста. */
101
103
  base?: string;
104
+ /** Режим сборки; по умолчанию `production`. */
105
+ mode?: BuildMode;
102
106
  }
103
107
  /**
104
108
  * Собирает приложение целиком.
@@ -93,12 +93,16 @@ interface AppConfig {
93
93
  */
94
94
  declare function defineAppConfig(config: AppConfig): AppConfig;
95
95
 
96
+ /** Режим сборки: им плагины выбирают рантайм фреймворка. */
97
+ type BuildMode = 'production' | 'development';
96
98
  /** Что нужно собрать. */
97
99
  interface BuildAppOptions {
98
100
  /** Корень приложения. */
99
101
  root: string;
100
102
  /** Префикс адресов артефактов: в проде CDN, в разработке развязка у хоста. */
101
103
  base?: string;
104
+ /** Режим сборки; по умолчанию `production`. */
105
+ mode?: BuildMode;
102
106
  }
103
107
  /**
104
108
  * Собирает приложение целиком.
package/builder/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  buildApp,
3
3
  defineAppConfig,
4
4
  devApp
5
- } from "../chunk-REFU7UMG.js";
5
+ } from "../chunk-YECHY3NR.js";
6
6
  import "../chunk-TPBI6TOU.js";
7
7
  import "../chunk-6FNC3XMI.js";
8
8
  export {
@@ -62,11 +62,13 @@ function collectAssets(outDir, base) {
62
62
  return { css, js };
63
63
  }
64
64
 
65
+ // src/serve/port.ts
66
+ var DEFAULT_PORT = 3e3;
67
+
65
68
  // src/builder/entries.ts
66
69
  var VIRTUAL_CLIENT = "virtual:den/client";
67
70
  var VIRTUAL_SERVER_APP = "virtual:den/server-app";
68
71
  var VIRTUAL_SERVER_PROCESS = "virtual:den/server-process";
69
- var DEFAULT_PORT = 3e3;
70
72
  function toResolvedId(id) {
71
73
  return `\0${id}`;
72
74
  }
@@ -179,7 +181,7 @@ function dependenciesOf(root) {
179
181
  }
180
182
  function viteConfigFor(options) {
181
183
  var _a, _b, _c;
182
- const { assets, base, config, configPath, entryFileNames, input, outDir, root, target } = options;
184
+ const { assets, base, config, configPath, entryFileNames, input, mode, outDir, root, target } = options;
183
185
  const entry = (_a = config.entry) != null ? _a : "src/index.ts";
184
186
  const composed = {
185
187
  base,
@@ -197,6 +199,9 @@ function viteConfigFor(options) {
197
199
  ssr: target === "server"
198
200
  },
199
201
  configFile: false,
202
+ // Режим объявляем явно: плагины сборщика выбирают по нему рантайм фреймворка, а через
203
+ // программный вызов он иначе не доедет.
204
+ mode,
200
205
  plugins: [...config.preset.plugins, denAppModules({ assets, config, configPath, entry: path2.join(root, entry) })],
201
206
  root,
202
207
  ssr: { noExternal: (_c = (_b = config.server) == null ? void 0 : _b.noExternal) != null ? _c : [] }
@@ -206,7 +211,8 @@ function viteConfigFor(options) {
206
211
  function buildApp(options) {
207
212
  return __async(this, null, function* () {
208
213
  var _a, _b;
209
- const { base = "/" } = options;
214
+ const { base = "/", mode = "production" } = options;
215
+ process.env["NODE_ENV"] = mode;
210
216
  const root = path2.resolve(options.root);
211
217
  const { config, configPath } = yield loadAppConfig(root);
212
218
  const refusal = (_b = (_a = config.preset).validate) == null ? void 0 : _b.call(_a, dependenciesOf(root));
@@ -214,7 +220,7 @@ function buildApp(options) {
214
220
  throw new Error(`den: ${refusal}`);
215
221
  }
216
222
  const { build } = yield import("vite");
217
- const shared = { base, config, configPath, root };
223
+ const shared = { base, config, configPath, mode, root };
218
224
  const empty = { css: [], js: [] };
219
225
  logger.info(`\u0441\u0431\u043E\u0440\u043A\u0430 \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u043E\u0433\u043E \u0432\u044B\u0445\u043E\u0434\u0430 (${config.preset.view})`);
220
226
  yield build(viteConfigFor(__spreadProps(__spreadValues({}, shared), { assets: empty, input: VIRTUAL_CLIENT, outDir: OUT_CLIENT, target: "client" })));
@@ -247,18 +253,97 @@ function buildApp(options) {
247
253
  import { spawn } from "child_process";
248
254
  import fs3 from "fs";
249
255
  import path3 from "path";
256
+ import { pathToFileURL } from "url";
257
+
258
+ // src/builder/banner.ts
259
+ import os from "os";
260
+ var TOOL = "den";
261
+ var PAD = 11;
262
+ var ENDPOINTS = ["/_render?url=<\u043F\u0443\u0442\u044C>", "/_routes", "/_health"];
263
+ var OUTPUTS = ["build/client", "build/server", "build/server.mjs"];
264
+ var SHORTCUTS = [
265
+ { key: "r", title: "\u043F\u0435\u0440\u0435\u0441\u043E\u0431\u0440\u0430\u0442\u044C" },
266
+ { key: "h", title: "\u0441\u043F\u0440\u0430\u0432\u043A\u0430" },
267
+ { key: "q", title: "\u0432\u044B\u0439\u0442\u0438" }
268
+ ];
269
+ function networkAddress() {
270
+ for (const addresses of Object.values(os.networkInterfaces())) {
271
+ for (const address of addresses != null ? addresses : []) {
272
+ if (address.family === "IPv4" && !address.internal) {
273
+ return address.address;
274
+ }
275
+ }
276
+ }
277
+ return void 0;
278
+ }
279
+ function assetsSummary(assets) {
280
+ if (!assets) {
281
+ return "\u043D\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u044B";
282
+ }
283
+ return `${String(assets.js.length)} js, ${String(assets.css.length)} css`;
284
+ }
285
+ function bannerLines(facts2) {
286
+ var _a;
287
+ const port = (_a = process.env["PORT"]) != null ? _a : String(DEFAULT_PORT);
288
+ const network = networkAddress();
289
+ const label = (title) => ` \u2192 ${title.padEnd(PAD)}`;
290
+ return [
291
+ "",
292
+ ` ${TOOL} ${facts2.mode} \u0441\u043E\u0431\u0440\u0430\u043D\u043E \u0437\u0430 ${String(facts2.elapsedMs)}\u043C\u0441`,
293
+ "",
294
+ ...facts2.name === void 0 ? [] : [`${label("\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435")}${facts2.name}`],
295
+ ...facts2.routes === void 0 ? [] : [`${label("\u0430\u0434\u0440\u0435\u0441\u0430")}${facts2.routes.join(" ")}`],
296
+ `${label("\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E")}http://localhost:${port}`,
297
+ ...network === void 0 ? [] : [`${label("\u0432 \u0441\u0435\u0442\u0438")}http://${network}:${port}`],
298
+ `${label("\u0440\u0443\u0447\u043A\u0438")}${ENDPOINTS.join(" ")}`,
299
+ `${label("\u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u044B")}${assetsSummary(facts2.assets)}`,
300
+ `${label("\u0432\u044B\u0445\u043E\u0434\u044B")}${OUTPUTS.join(", ")}`,
301
+ `${label("\u043F\u0440\u0430\u0432\u043A\u0438")}${facts2.watched.join(", ")}`,
302
+ `${label("\u043A\u043B\u0430\u0432\u0438\u0448\u0438")}${SHORTCUTS.map(({ key, title }) => `${key} \u2014 ${title}`).join(", ")}`,
303
+ ""
304
+ ];
305
+ }
306
+ function helpLines() {
307
+ return ["", " \u043A\u043B\u0430\u0432\u0438\u0448\u0430 + enter:", ...SHORTCUTS.map(({ key, title }) => ` ${key} ${title}`), ""];
308
+ }
309
+
310
+ // src/builder/dev.ts
250
311
  var SERVER_ENTRY = "build/server.mjs";
251
312
  var WATCHED = ["src", "den.config.ts"];
313
+ var DEBUG_SCOPES = ["@foxford/den:*"];
314
+ function debugPattern(name) {
315
+ return (name === void 0 ? DEBUG_SCOPES : [`${name}:*`, ...DEBUG_SCOPES]).join(",");
316
+ }
252
317
  var DEBOUNCE_MS = 150;
253
318
  var SHUTDOWN_TIMEOUT_MS = 5e3;
319
+ function facts(root) {
320
+ return __async(this, null, function* () {
321
+ try {
322
+ const loaded = yield import(pathToFileURL(path3.join(root, "build/server/index.mjs")).href);
323
+ const app = loaded.default;
324
+ return { assets: app == null ? void 0 : app.assets, name: app == null ? void 0 : app.name, routes: app == null ? void 0 : app.routes };
325
+ } catch (e) {
326
+ return {};
327
+ }
328
+ });
329
+ }
330
+ function describer(root) {
331
+ let described = null;
332
+ return () => __async(null, null, function* () {
333
+ described != null ? described : described = yield facts(root);
334
+ return described;
335
+ });
336
+ }
254
337
  function devApp(options) {
255
338
  return __async(this, null, function* () {
339
+ logger.setDebug(true);
256
340
  const root = path3.resolve(options.root);
257
341
  const entry = path3.join(root, SERVER_ENTRY);
258
342
  let child = null;
259
343
  let pending = null;
260
344
  let building = false;
261
345
  let dirty = false;
346
+ const describe = describer(root);
262
347
  function signalGroup(pid, signal) {
263
348
  try {
264
349
  process.kill(-pid, signal);
@@ -283,9 +368,11 @@ function devApp(options) {
283
368
  });
284
369
  });
285
370
  }
286
- function start() {
371
+ function start(name) {
372
+ var _a;
287
373
  const started = spawn(process.execPath, [...process.execArgv, entry], {
288
374
  detached: true,
375
+ env: __spreadProps(__spreadValues({}, process.env), { DEBUG: (_a = process.env["DEBUG"]) != null ? _a : debugPattern(name) }),
289
376
  stdio: "inherit"
290
377
  });
291
378
  child = started;
@@ -297,30 +384,57 @@ function devApp(options) {
297
384
  logger.error(`\u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441\u0430\u043C (\u043A\u043E\u0434 ${String(code)}, \u0441\u0438\u0433\u043D\u0430\u043B ${String(signal)})`);
298
385
  });
299
386
  }
387
+ function show(lines) {
388
+ process.stdout.write(`${lines.join("\n")}
389
+ `);
390
+ }
391
+ function listenForKeys() {
392
+ if (!process.stdin.isTTY) {
393
+ return;
394
+ }
395
+ process.stdin.setEncoding("utf8");
396
+ process.stdin.on("data", (chunk) => {
397
+ const key = chunk.trim().toLowerCase();
398
+ if (key === "r") {
399
+ void rebuild();
400
+ }
401
+ if (key === "h") {
402
+ show(helpLines());
403
+ }
404
+ if (key === "q") {
405
+ void stop().then(() => process.exit(0));
406
+ }
407
+ });
408
+ }
300
409
  function rebuild() {
301
410
  return __async(this, null, function* () {
302
411
  if (building) {
303
412
  dirty = true;
304
- return;
413
+ return false;
305
414
  }
306
415
  building = true;
416
+ let built2 = false;
307
417
  try {
308
418
  do {
309
419
  dirty = false;
310
420
  yield stop();
311
421
  try {
312
- yield buildApp(options);
313
- start();
422
+ yield buildApp(__spreadProps(__spreadValues({}, options), { mode: "development" }));
423
+ start((yield describe()).name);
424
+ built2 = true;
314
425
  } catch (error) {
315
- logger.error("\u0441\u0431\u043E\u0440\u043A\u0430 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430, \u0436\u0434\u0443 \u043F\u0440\u0430\u0432\u043A\u0438:", error);
426
+ logger.error("\u0441\u0431\u043E\u0440\u043A\u0430 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430 \u2014 \u0441\u0435\u0440\u0432\u0438\u0441 \u041D\u0415 \u041F\u041E\u0414\u041D\u042F\u0422, \u0436\u0434\u0443 \u043F\u0440\u0430\u0432\u043A\u0438:", error);
316
427
  }
317
428
  } while (dirty);
318
429
  } finally {
319
430
  building = false;
320
431
  }
432
+ return built2;
321
433
  });
322
434
  }
323
- yield rebuild();
435
+ const startedAt = Date.now();
436
+ const built = yield rebuild();
437
+ const elapsed = Date.now() - startedAt;
324
438
  for (const target of WATCHED) {
325
439
  const watched = path3.join(root, target);
326
440
  if (!fs3.existsSync(watched)) {
@@ -333,7 +447,10 @@ function devApp(options) {
333
447
  pending = setTimeout(() => void rebuild(), DEBOUNCE_MS);
334
448
  });
335
449
  }
336
- logger.info(`\u0436\u0434\u0443 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u0432 ${WATCHED.join(", ")}`);
450
+ if (built) {
451
+ show(bannerLines(__spreadValues({ elapsedMs: elapsed, mode: "development", watched: WATCHED }, yield describe())));
452
+ }
453
+ listenForKeys();
337
454
  for (const signal of ["SIGINT", "SIGTERM"]) {
338
455
  process.once(signal, () => {
339
456
  void stop().then(() => process.exit(0));
@@ -62,11 +62,13 @@ function collectAssets(outDir, base) {
62
62
  return { css, js };
63
63
  }
64
64
 
65
+ // src/serve/port.ts
66
+ var DEFAULT_PORT = 3e3;
67
+
65
68
  // src/builder/entries.ts
66
69
  var VIRTUAL_CLIENT = "virtual:den/client";
67
70
  var VIRTUAL_SERVER_APP = "virtual:den/server-app";
68
71
  var VIRTUAL_SERVER_PROCESS = "virtual:den/server-process";
69
- var DEFAULT_PORT = 3e3;
70
72
  function toResolvedId(id) {
71
73
  return `\0${id}`;
72
74
  }
@@ -179,7 +181,7 @@ function dependenciesOf(root) {
179
181
  }
180
182
  function viteConfigFor(options) {
181
183
  var _a, _b, _c;
182
- const { assets, base, config, configPath, entryFileNames, input, outDir, root, target } = options;
184
+ const { assets, base, config, configPath, entryFileNames, input, mode, outDir, root, target } = options;
183
185
  const entry = (_a = config.entry) != null ? _a : "src/index.ts";
184
186
  const composed = {
185
187
  base,
@@ -197,6 +199,9 @@ function viteConfigFor(options) {
197
199
  ssr: target === "server"
198
200
  },
199
201
  configFile: false,
202
+ // Режим объявляем явно: плагины сборщика выбирают по нему рантайм фреймворка, а через
203
+ // программный вызов он иначе не доедет.
204
+ mode,
200
205
  plugins: [...config.preset.plugins, denAppModules({ assets, config, configPath, entry: _path2.default.join(root, entry) })],
201
206
  root,
202
207
  ssr: { noExternal: (_c = (_b = config.server) == null ? void 0 : _b.noExternal) != null ? _c : [] }
@@ -206,7 +211,8 @@ function viteConfigFor(options) {
206
211
  function buildApp(options) {
207
212
  return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
208
213
  var _a, _b;
209
- const { base = "/" } = options;
214
+ const { base = "/", mode = "production" } = options;
215
+ process.env["NODE_ENV"] = mode;
210
216
  const root = _path2.default.resolve(options.root);
211
217
  const { config, configPath } = yield loadAppConfig(root);
212
218
  const refusal = (_b = (_a = config.preset).validate) == null ? void 0 : _b.call(_a, dependenciesOf(root));
@@ -214,7 +220,7 @@ function buildApp(options) {
214
220
  throw new Error(`den: ${refusal}`);
215
221
  }
216
222
  const { build } = yield Promise.resolve().then(() => _interopRequireWildcard(require("vite")));
217
- const shared = { base, config, configPath, root };
223
+ const shared = { base, config, configPath, mode, root };
218
224
  const empty = { css: [], js: [] };
219
225
  _chunkA6ZPAM6Zcjs.logger.info(`\u0441\u0431\u043E\u0440\u043A\u0430 \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u043E\u0433\u043E \u0432\u044B\u0445\u043E\u0434\u0430 (${config.preset.view})`);
220
226
  yield build(viteConfigFor(_chunkT5QVCXVBcjs.__spreadProps.call(void 0, _chunkT5QVCXVBcjs.__spreadValues.call(void 0, {}, shared), { assets: empty, input: VIRTUAL_CLIENT, outDir: OUT_CLIENT, target: "client" })));
@@ -247,18 +253,97 @@ function buildApp(options) {
247
253
  var _child_process = require('child_process');
248
254
 
249
255
 
256
+ var _url = require('url');
257
+
258
+ // src/builder/banner.ts
259
+ var _os = require('os'); var _os2 = _interopRequireDefault(_os);
260
+ var TOOL = "den";
261
+ var PAD = 11;
262
+ var ENDPOINTS = ["/_render?url=<\u043F\u0443\u0442\u044C>", "/_routes", "/_health"];
263
+ var OUTPUTS = ["build/client", "build/server", "build/server.mjs"];
264
+ var SHORTCUTS = [
265
+ { key: "r", title: "\u043F\u0435\u0440\u0435\u0441\u043E\u0431\u0440\u0430\u0442\u044C" },
266
+ { key: "h", title: "\u0441\u043F\u0440\u0430\u0432\u043A\u0430" },
267
+ { key: "q", title: "\u0432\u044B\u0439\u0442\u0438" }
268
+ ];
269
+ function networkAddress() {
270
+ for (const addresses of Object.values(_os2.default.networkInterfaces())) {
271
+ for (const address of addresses != null ? addresses : []) {
272
+ if (address.family === "IPv4" && !address.internal) {
273
+ return address.address;
274
+ }
275
+ }
276
+ }
277
+ return void 0;
278
+ }
279
+ function assetsSummary(assets) {
280
+ if (!assets) {
281
+ return "\u043D\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u044B";
282
+ }
283
+ return `${String(assets.js.length)} js, ${String(assets.css.length)} css`;
284
+ }
285
+ function bannerLines(facts2) {
286
+ var _a;
287
+ const port = (_a = process.env["PORT"]) != null ? _a : String(DEFAULT_PORT);
288
+ const network = networkAddress();
289
+ const label = (title) => ` \u2192 ${title.padEnd(PAD)}`;
290
+ return [
291
+ "",
292
+ ` ${TOOL} ${facts2.mode} \u0441\u043E\u0431\u0440\u0430\u043D\u043E \u0437\u0430 ${String(facts2.elapsedMs)}\u043C\u0441`,
293
+ "",
294
+ ...facts2.name === void 0 ? [] : [`${label("\u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435")}${facts2.name}`],
295
+ ...facts2.routes === void 0 ? [] : [`${label("\u0430\u0434\u0440\u0435\u0441\u0430")}${facts2.routes.join(" ")}`],
296
+ `${label("\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E")}http://localhost:${port}`,
297
+ ...network === void 0 ? [] : [`${label("\u0432 \u0441\u0435\u0442\u0438")}http://${network}:${port}`],
298
+ `${label("\u0440\u0443\u0447\u043A\u0438")}${ENDPOINTS.join(" ")}`,
299
+ `${label("\u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u044B")}${assetsSummary(facts2.assets)}`,
300
+ `${label("\u0432\u044B\u0445\u043E\u0434\u044B")}${OUTPUTS.join(", ")}`,
301
+ `${label("\u043F\u0440\u0430\u0432\u043A\u0438")}${facts2.watched.join(", ")}`,
302
+ `${label("\u043A\u043B\u0430\u0432\u0438\u0448\u0438")}${SHORTCUTS.map(({ key, title }) => `${key} \u2014 ${title}`).join(", ")}`,
303
+ ""
304
+ ];
305
+ }
306
+ function helpLines() {
307
+ return ["", " \u043A\u043B\u0430\u0432\u0438\u0448\u0430 + enter:", ...SHORTCUTS.map(({ key, title }) => ` ${key} ${title}`), ""];
308
+ }
309
+
310
+ // src/builder/dev.ts
250
311
  var SERVER_ENTRY = "build/server.mjs";
251
312
  var WATCHED = ["src", "den.config.ts"];
313
+ var DEBUG_SCOPES = ["@foxford/den:*"];
314
+ function debugPattern(name) {
315
+ return (name === void 0 ? DEBUG_SCOPES : [`${name}:*`, ...DEBUG_SCOPES]).join(",");
316
+ }
252
317
  var DEBOUNCE_MS = 150;
253
318
  var SHUTDOWN_TIMEOUT_MS = 5e3;
319
+ function facts(root) {
320
+ return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
321
+ try {
322
+ const loaded = yield Promise.resolve().then(() => _interopRequireWildcard(require(_url.pathToFileURL.call(void 0, _path2.default.join(root, "build/server/index.mjs")).href)));
323
+ const app = loaded.default;
324
+ return { assets: app == null ? void 0 : app.assets, name: app == null ? void 0 : app.name, routes: app == null ? void 0 : app.routes };
325
+ } catch (e) {
326
+ return {};
327
+ }
328
+ });
329
+ }
330
+ function describer(root) {
331
+ let described = null;
332
+ return () => _chunkT5QVCXVBcjs.__async.call(void 0, null, null, function* () {
333
+ described != null ? described : described = yield facts(root);
334
+ return described;
335
+ });
336
+ }
254
337
  function devApp(options) {
255
338
  return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
339
+ _chunkA6ZPAM6Zcjs.logger.setDebug(true);
256
340
  const root = _path2.default.resolve(options.root);
257
341
  const entry = _path2.default.join(root, SERVER_ENTRY);
258
342
  let child = null;
259
343
  let pending = null;
260
344
  let building = false;
261
345
  let dirty = false;
346
+ const describe = describer(root);
262
347
  function signalGroup(pid, signal) {
263
348
  try {
264
349
  process.kill(-pid, signal);
@@ -283,9 +368,11 @@ function devApp(options) {
283
368
  });
284
369
  });
285
370
  }
286
- function start() {
371
+ function start(name) {
372
+ var _a;
287
373
  const started = _child_process.spawn.call(void 0, process.execPath, [...process.execArgv, entry], {
288
374
  detached: true,
375
+ env: _chunkT5QVCXVBcjs.__spreadProps.call(void 0, _chunkT5QVCXVBcjs.__spreadValues.call(void 0, {}, process.env), { DEBUG: (_a = process.env["DEBUG"]) != null ? _a : debugPattern(name) }),
289
376
  stdio: "inherit"
290
377
  });
291
378
  child = started;
@@ -297,30 +384,57 @@ function devApp(options) {
297
384
  _chunkA6ZPAM6Zcjs.logger.error(`\u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u043B\u0441\u044F \u0441\u0430\u043C (\u043A\u043E\u0434 ${String(code)}, \u0441\u0438\u0433\u043D\u0430\u043B ${String(signal)})`);
298
385
  });
299
386
  }
387
+ function show(lines) {
388
+ process.stdout.write(`${lines.join("\n")}
389
+ `);
390
+ }
391
+ function listenForKeys() {
392
+ if (!process.stdin.isTTY) {
393
+ return;
394
+ }
395
+ process.stdin.setEncoding("utf8");
396
+ process.stdin.on("data", (chunk) => {
397
+ const key = chunk.trim().toLowerCase();
398
+ if (key === "r") {
399
+ void rebuild();
400
+ }
401
+ if (key === "h") {
402
+ show(helpLines());
403
+ }
404
+ if (key === "q") {
405
+ void stop().then(() => process.exit(0));
406
+ }
407
+ });
408
+ }
300
409
  function rebuild() {
301
410
  return _chunkT5QVCXVBcjs.__async.call(void 0, this, null, function* () {
302
411
  if (building) {
303
412
  dirty = true;
304
- return;
413
+ return false;
305
414
  }
306
415
  building = true;
416
+ let built2 = false;
307
417
  try {
308
418
  do {
309
419
  dirty = false;
310
420
  yield stop();
311
421
  try {
312
- yield buildApp(options);
313
- start();
422
+ yield buildApp(_chunkT5QVCXVBcjs.__spreadProps.call(void 0, _chunkT5QVCXVBcjs.__spreadValues.call(void 0, {}, options), { mode: "development" }));
423
+ start((yield describe()).name);
424
+ built2 = true;
314
425
  } catch (error) {
315
- _chunkA6ZPAM6Zcjs.logger.error("\u0441\u0431\u043E\u0440\u043A\u0430 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430, \u0436\u0434\u0443 \u043F\u0440\u0430\u0432\u043A\u0438:", error);
426
+ _chunkA6ZPAM6Zcjs.logger.error("\u0441\u0431\u043E\u0440\u043A\u0430 \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430 \u2014 \u0441\u0435\u0440\u0432\u0438\u0441 \u041D\u0415 \u041F\u041E\u0414\u041D\u042F\u0422, \u0436\u0434\u0443 \u043F\u0440\u0430\u0432\u043A\u0438:", error);
316
427
  }
317
428
  } while (dirty);
318
429
  } finally {
319
430
  building = false;
320
431
  }
432
+ return built2;
321
433
  });
322
434
  }
323
- yield rebuild();
435
+ const startedAt = Date.now();
436
+ const built = yield rebuild();
437
+ const elapsed = Date.now() - startedAt;
324
438
  for (const target of WATCHED) {
325
439
  const watched = _path2.default.join(root, target);
326
440
  if (!_fs2.default.existsSync(watched)) {
@@ -333,7 +447,10 @@ function devApp(options) {
333
447
  pending = setTimeout(() => void rebuild(), DEBOUNCE_MS);
334
448
  });
335
449
  }
336
- _chunkA6ZPAM6Zcjs.logger.info(`\u0436\u0434\u0443 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u0432 ${WATCHED.join(", ")}`);
450
+ if (built) {
451
+ show(bannerLines(_chunkT5QVCXVBcjs.__spreadValues.call(void 0, { elapsedMs: elapsed, mode: "development", watched: WATCHED }, yield describe())));
452
+ }
453
+ listenForKeys();
337
454
  for (const signal of ["SIGINT", "SIGTERM"]) {
338
455
  process.once(signal, () => {
339
456
  void stop().then(() => process.exit(0));
package/island/index.cjs CHANGED
@@ -23,6 +23,21 @@ require('../chunk-A6ZPAM6Z.cjs');
23
23
 
24
24
  var _chunkT5QVCXVBcjs = require('../chunk-T5QVCXVB.cjs');
25
25
 
26
+ // src/island/navigate.ts
27
+ var NAVIGATE_EVENT = "den:navigate";
28
+ function navigateIsland(node, route) {
29
+ node.dispatchEvent(new CustomEvent(NAVIGATE_EVENT, { detail: route }));
30
+ }
31
+ function onIslandNavigate(node, onNavigate) {
32
+ const listener = (event) => {
33
+ onNavigate(event.detail);
34
+ };
35
+ node.addEventListener(NAVIGATE_EVENT, listener);
36
+ return () => {
37
+ node.removeEventListener(NAVIGATE_EVENT, listener);
38
+ };
39
+ }
40
+
26
41
  // src/island/slot-address.ts
27
42
  function slotAddress(app, slot) {
28
43
  return `${app}:${slot}`;
@@ -88,4 +103,7 @@ function describeIsland(descriptor, denState) {
88
103
 
89
104
 
90
105
 
91
- exports.ViewLayerToken = _chunkXOJ2VWX4cjs.ViewLayerToken; exports.activateIslandViewModels = _chunkZOU6W6ZQcjs.activateIslandViewModels; exports.collectDocumentContribution = _chunkZOU6W6ZQcjs.collectDocumentContribution; exports.collectIslandState = _chunkZOU6W6ZQcjs.collectIslandState; exports.createIslandContainer = _chunkZOU6W6ZQcjs.createIslandContainer; exports.describeIsland = describeIsland; exports.getAppContainer = _chunkZOU6W6ZQcjs.getAppContainer; exports.getIslandsSnapshot = getIslandsSnapshot; exports.hydrateIslandState = _chunkZOU6W6ZQcjs.hydrateIslandState; exports.isDocumentSource = _chunkZOU6W6ZQcjs.isDocumentSource; exports.matchRoute = _chunkZOU6W6ZQcjs.matchRoute; exports.normalizeRoutes = _chunkZOU6W6ZQcjs.normalizeRoutes; exports.pathPattern = _chunkZOU6W6ZQcjs.pathPattern; exports.registerIsland = registerIsland; exports.renderApp = _chunkZOU6W6ZQcjs.renderApp; exports.resolveEagerUnits = _chunkZOU6W6ZQcjs.resolveEagerUnits; exports.resolveViewAdapter = _chunkXOJ2VWX4cjs.resolveViewAdapter; exports.slotAddress = slotAddress; exports.subscribeIslands = subscribeIslands; exports.viewAdapterOf = _chunkXOJ2VWX4cjs.viewAdapterOf;
106
+
107
+
108
+
109
+ exports.NAVIGATE_EVENT = NAVIGATE_EVENT; exports.ViewLayerToken = _chunkXOJ2VWX4cjs.ViewLayerToken; exports.activateIslandViewModels = _chunkZOU6W6ZQcjs.activateIslandViewModels; exports.collectDocumentContribution = _chunkZOU6W6ZQcjs.collectDocumentContribution; exports.collectIslandState = _chunkZOU6W6ZQcjs.collectIslandState; exports.createIslandContainer = _chunkZOU6W6ZQcjs.createIslandContainer; exports.describeIsland = describeIsland; exports.getAppContainer = _chunkZOU6W6ZQcjs.getAppContainer; exports.getIslandsSnapshot = getIslandsSnapshot; exports.hydrateIslandState = _chunkZOU6W6ZQcjs.hydrateIslandState; exports.isDocumentSource = _chunkZOU6W6ZQcjs.isDocumentSource; exports.matchRoute = _chunkZOU6W6ZQcjs.matchRoute; exports.navigateIsland = navigateIsland; exports.normalizeRoutes = _chunkZOU6W6ZQcjs.normalizeRoutes; exports.onIslandNavigate = onIslandNavigate; exports.pathPattern = _chunkZOU6W6ZQcjs.pathPattern; exports.registerIsland = registerIsland; exports.renderApp = _chunkZOU6W6ZQcjs.renderApp; exports.resolveEagerUnits = _chunkZOU6W6ZQcjs.resolveEagerUnits; exports.resolveViewAdapter = _chunkXOJ2VWX4cjs.resolveViewAdapter; exports.slotAddress = slotAddress; exports.subscribeIslands = subscribeIslands; exports.viewAdapterOf = _chunkXOJ2VWX4cjs.viewAdapterOf;
@@ -162,6 +162,27 @@ declare function collectDocumentContribution(container: Container, descriptor: I
162
162
  */
163
163
  declare function resolveEagerUnits(container: Container, descriptor: IslandDescriptor): void;
164
164
 
165
+ /** Имя события перехода. Одно на обе стороны — иначе сторона молча не услышит. */
166
+ declare const NAVIGATE_EVENT = "den:navigate";
167
+ /**
168
+ * Сообщает приложению новый адрес.
169
+ *
170
+ * Состояние с адресом НЕ едет: на клиентском переходе его никто не считал — хост чужой
171
+ * жизненный цикл не исполняет, — и приложение считает его само, активацией своих VM.
172
+ *
173
+ * @param node - Узел приложения (его граница в документе)
174
+ * @param route - Адрес, на который переходим
175
+ */
176
+ declare function navigateIsland(node: EventTarget, route: IslandRoute): void;
177
+ /**
178
+ * Подписывает приложение на переходы, о которых сообщает хост.
179
+ *
180
+ * @param node - Узел приложения
181
+ * @param onNavigate - Что делать с новым адресом
182
+ * @returns Отписка
183
+ */
184
+ declare function onIslandNavigate(node: EventTarget, onNavigate: (route: IslandRoute) => void): () => void;
185
+
165
186
  /** Запрос на рендер приложения. */
166
187
  interface RenderAppRequest {
167
188
  /** Путь без search, с ведущим слэшем: `/legal/general`. */
@@ -276,4 +297,4 @@ declare function getAppContainer(): Container;
276
297
  */
277
298
  declare function slotAddress(app: string, slot: string): string;
278
299
 
279
- export { AppAssets, type CreateIslandContainerOptions, type DocumentContribution, type DocumentHead, type DocumentSource, IslandDescriptor, type IslandRecord, type IslandRoute, type RenderAppRequest, type RenderAppResult, activateIslandViewModels, collectDocumentContribution, collectIslandState, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, isDocumentSource, registerIsland, renderApp, resolveEagerUnits, slotAddress, subscribeIslands };
300
+ export { AppAssets, type CreateIslandContainerOptions, type DocumentContribution, type DocumentHead, type DocumentSource, IslandDescriptor, type IslandRecord, type IslandRoute, NAVIGATE_EVENT, type RenderAppRequest, type RenderAppResult, activateIslandViewModels, collectDocumentContribution, collectIslandState, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, isDocumentSource, navigateIsland, onIslandNavigate, registerIsland, renderApp, resolveEagerUnits, slotAddress, subscribeIslands };
package/island/index.d.ts CHANGED
@@ -162,6 +162,27 @@ declare function collectDocumentContribution(container: Container, descriptor: I
162
162
  */
163
163
  declare function resolveEagerUnits(container: Container, descriptor: IslandDescriptor): void;
164
164
 
165
+ /** Имя события перехода. Одно на обе стороны — иначе сторона молча не услышит. */
166
+ declare const NAVIGATE_EVENT = "den:navigate";
167
+ /**
168
+ * Сообщает приложению новый адрес.
169
+ *
170
+ * Состояние с адресом НЕ едет: на клиентском переходе его никто не считал — хост чужой
171
+ * жизненный цикл не исполняет, — и приложение считает его само, активацией своих VM.
172
+ *
173
+ * @param node - Узел приложения (его граница в документе)
174
+ * @param route - Адрес, на который переходим
175
+ */
176
+ declare function navigateIsland(node: EventTarget, route: IslandRoute): void;
177
+ /**
178
+ * Подписывает приложение на переходы, о которых сообщает хост.
179
+ *
180
+ * @param node - Узел приложения
181
+ * @param onNavigate - Что делать с новым адресом
182
+ * @returns Отписка
183
+ */
184
+ declare function onIslandNavigate(node: EventTarget, onNavigate: (route: IslandRoute) => void): () => void;
185
+
165
186
  /** Запрос на рендер приложения. */
166
187
  interface RenderAppRequest {
167
188
  /** Путь без search, с ведущим слэшем: `/legal/general`. */
@@ -276,4 +297,4 @@ declare function getAppContainer(): Container;
276
297
  */
277
298
  declare function slotAddress(app: string, slot: string): string;
278
299
 
279
- export { AppAssets, type CreateIslandContainerOptions, type DocumentContribution, type DocumentHead, type DocumentSource, IslandDescriptor, type IslandRecord, type IslandRoute, type RenderAppRequest, type RenderAppResult, activateIslandViewModels, collectDocumentContribution, collectIslandState, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, isDocumentSource, registerIsland, renderApp, resolveEagerUnits, slotAddress, subscribeIslands };
300
+ export { AppAssets, type CreateIslandContainerOptions, type DocumentContribution, type DocumentHead, type DocumentSource, IslandDescriptor, type IslandRecord, type IslandRoute, NAVIGATE_EVENT, type RenderAppRequest, type RenderAppResult, activateIslandViewModels, collectDocumentContribution, collectIslandState, createIslandContainer, describeIsland, getAppContainer, getIslandsSnapshot, hydrateIslandState, isDocumentSource, navigateIsland, onIslandNavigate, registerIsland, renderApp, resolveEagerUnits, slotAddress, subscribeIslands };
package/island/index.js CHANGED
@@ -23,6 +23,21 @@ import {
23
23
  __spreadValues
24
24
  } from "../chunk-6FNC3XMI.js";
25
25
 
26
+ // src/island/navigate.ts
27
+ var NAVIGATE_EVENT = "den:navigate";
28
+ function navigateIsland(node, route) {
29
+ node.dispatchEvent(new CustomEvent(NAVIGATE_EVENT, { detail: route }));
30
+ }
31
+ function onIslandNavigate(node, onNavigate) {
32
+ const listener = (event) => {
33
+ onNavigate(event.detail);
34
+ };
35
+ node.addEventListener(NAVIGATE_EVENT, listener);
36
+ return () => {
37
+ node.removeEventListener(NAVIGATE_EVENT, listener);
38
+ };
39
+ }
40
+
26
41
  // src/island/slot-address.ts
27
42
  function slotAddress(app, slot) {
28
43
  return `${app}:${slot}`;
@@ -68,6 +83,7 @@ function describeIsland(descriptor, denState) {
68
83
  };
69
84
  }
70
85
  export {
86
+ NAVIGATE_EVENT,
71
87
  ViewLayerToken,
72
88
  activateIslandViewModels,
73
89
  collectDocumentContribution,
@@ -79,7 +95,9 @@ export {
79
95
  hydrateIslandState,
80
96
  isDocumentSource,
81
97
  matchRoute,
98
+ navigateIsland,
82
99
  normalizeRoutes,
100
+ onIslandNavigate,
83
101
  pathPattern,
84
102
  registerIsland,
85
103
  renderApp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxford/den",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "description": "Den — декларативный метафреймворк Foxford (core runtime)",
5
5
  "keywords": [
6
6
  "foxford",
@@ -24,8 +24,8 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "fastify": "^5.11.2",
27
- "@foxford/logger": "^1.2.2",
28
- "@foxford/ioc": "^1.1.2"
27
+ "@foxford/ioc": "^1.1.2",
28
+ "@foxford/logger": "^1.2.2"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "vite": ">=5.0.0"
@@ -89,14 +89,14 @@
89
89
  "chunk-C7BM4DGX.cjs",
90
90
  "chunk-E23E2VUC.js",
91
91
  "chunk-MCN4GFEQ.js",
92
- "chunk-OKRO4G4L.cjs",
93
- "chunk-REFU7UMG.js",
94
92
  "chunk-T5QVCXVB.cjs",
95
93
  "chunk-TPBI6TOU.js",
96
94
  "chunk-TPHJUDHG.js",
97
95
  "chunk-WBHHHICS.js",
98
96
  "chunk-XOJ2VWX4.cjs",
97
+ "chunk-YECHY3NR.js",
99
98
  "chunk-Z5BL4DJA.cjs",
99
+ "chunk-ZBB5SBEV.cjs",
100
100
  "chunk-ZOU6W6ZQ.cjs",
101
101
  "define-repository-CwAXed2X.d.cts",
102
102
  "define-repository-q_T4hqeP.d.ts",
@@ -125,6 +125,6 @@
125
125
  "view-adapter-B4gEb-ms.d.ts",
126
126
  "view-adapter-DJ7yB6Ml.d.cts"
127
127
  ],
128
- "sha": "dd4b7ca",
128
+ "sha": "c633e3b",
129
129
  "scripts": {}
130
130
  }