mikser-io 9.62.0 → 9.64.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.
package/CLAUDE.md CHANGED
@@ -423,6 +423,13 @@ Test coverage: `test/unit/source-sweep.test.js`.
423
423
  camelCase: `import { vector } from 'mikser-io-vector'`,
424
424
  `import { renderHbs } from 'mikser-io'`. Consumer uses
425
425
  `plugins: [vector({...})]` — never the bare string.
426
+ - **Route paths**: a plugin mounts at `/<name>`, with a `base` or `path`
427
+ option to move it — api `/api`, auth `/auth`, drive `/drive`, forms
428
+ `/forms`, mcp `/mcp`, preview `/preview`, vector `/vector`, decap
429
+ `/admin`. The output folder is served from `/`, so a route CAN shadow a
430
+ page; the accepted answer is a predictable name plus a way to change
431
+ it, not a reserved prefix. Follow the eight, do not invent a ninth
432
+ shape.
426
433
  - **Tool names**: the registry (`src/tools.js`) holds BARE names —
427
434
  `explain`, `verify`, `sources`, `search`. The `mikser_` prefix is MCP's
428
435
  namespacing, because its tool names are flat across every connected
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikser-io",
3
- "version": "9.62.0",
3
+ "version": "9.64.0",
4
4
  "description": "A mixer for content: entities in, configurable render pipelines, outputs of any kind. Static sites are the canonical recipe, not the definition — the same engine renders PDFs, emails and whatever a renderer plugin produces. Files are the source of truth, every lifecycle phase is observable, and the build graph is queryable by an agent.",
5
5
  "main": "index.js",
6
6
  "exports": {
package/src/routes.js CHANGED
@@ -79,6 +79,21 @@ export function routeLocation(displayPath) {
79
79
  // authLabel bracketed reachability text override for the log.
80
80
  //
81
81
  // Returns the recorded descriptor.
82
+ // WHERE a plugin mounts: `/<name>`, overridable.
83
+ //
84
+ // Not a new rule — the one every plugin already follows. api at /api, auth at
85
+ // /auth, drive at /drive, forms at /forms, mcp at /mcp, preview at /preview,
86
+ // vector at /vector, decap at /admin. Each takes a `base` or `path` option so
87
+ // a project whose content wants that path can move it.
88
+ //
89
+ // The output folder is served from `/`, so a plugin route can shadow a page.
90
+ // That is a real cost and this is the accepted answer to it: a predictable
91
+ // name, and a way to change it. A reserved prefix like `/$name` would make
92
+ // the collision impossible, and would also make this plugin the only one
93
+ // shaped differently from the other eight — which is a worse trade than the
94
+ // collision it avoids, because the collision is rare and visible and the
95
+ // inconsistency is permanent.
96
+ //
82
97
  export function registerRoute({
83
98
  path,
84
99
  plugin,
package/src/server.js CHANGED
@@ -22,11 +22,25 @@
22
22
 
23
23
  import path from 'node:path'
24
24
  import { fileURLToPath } from 'node:url'
25
+ import { networkInterfaces } from 'node:os'
25
26
 
26
27
  import runtime from './runtime.js'
27
28
  import { useLogger } from './engine.js'
28
29
  import { onInitialized, onLoad, onLoaded } from './lifecycle.js'
29
30
 
31
+ // Every non-loopback IPv4 address this machine answers on.
32
+ //
33
+ // IPv4 only, deliberately. The non-internal IPv6 addresses on a typical
34
+ // machine are link-local (fe80::), which need a zone index to be usable and
35
+ // are not what anyone is going to type into a phone — listing them would make
36
+ // the useful lines harder to find, which is the opposite of the point.
37
+ export function localAddresses() {
38
+ return Object.values(networkInterfaces())
39
+ .flat()
40
+ .filter(iface => iface && iface.family === 'IPv4' && !iface.internal)
41
+ .map(iface => iface.address)
42
+ }
43
+
30
44
  // Extend the engine's commander with server-related flags. Called by
31
45
  // engine.js's onInitialize callback AFTER engine's own options chain
32
46
  // and BEFORE the .parse() call — preserves the order of options in
@@ -214,6 +228,28 @@ export function setupServer() {
214
228
  // back to the bind URL when no public origin is set.
215
229
  const externalUrl = runtime.options.url ?? `http://localhost:${runtime.options.port}`
216
230
  logger.info('Server listening: %s', externalUrl)
231
+
232
+ // The addresses that are NOT localhost.
233
+ //
234
+ // listen(port) binds every interface, so the server is
235
+ // already reachable from a phone or a second machine —
236
+ // and the one address printed above is the single one
237
+ // that will not work from either. Anyone testing a
238
+ // layout on a real device has to go and find their own
239
+ // IP, every time, on a machine that already knows it.
240
+ //
241
+ // Only when no public URL is configured. Behind a proxy
242
+ // or a tunnel the LAN address is not how the site is
243
+ // reached, and offering it would be an invitation to
244
+ // debug the wrong origin.
245
+ if (!runtime.options.url) {
246
+ const lan = localAddresses()
247
+ if (lan.length) {
248
+ logger.info('Also on: %s', lan
249
+ .map(address => `http://${address}:${runtime.options.port}`)
250
+ .join(' '))
251
+ }
252
+ }
217
253
  resolve()
218
254
  })
219
255