roster-server
Advanced tools
| --- | ||
| id: fralm92cih | ||
| type: bugfix | ||
| title: 'Bugfix: local mode startup logs show subdomain.localhost' | ||
| created: '2026-03-16 17:19:26' | ||
| --- | ||
| # Bug: local mode startup logs ignored subdomain localhost format | ||
| **Symptom**: Startup logs still printed `http://localhost:<port>` for subdomains (e.g. `api.example.com`) even after local URL behavior changed. | ||
| **Root cause**: `startLocalMode()` log message used a hardcoded `localhost` string. | ||
| **Solution**: Added `localHostForDomain(domain)` helper and reused it in both `getUrl()` and local-mode startup logs so they stay consistent. | ||
| **Location**: `index.js` (`localHostForDomain`, `getUrl`, `startLocalMode` listen log). |
| module.exports = (httpsServer) => { | ||
| return (req, res) => { | ||
| res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); | ||
| res.end('"subdomain.example.com: Crazy from thinking, wanting to be reasonable, and the heart has reasons that reason itself will never understand."'); | ||
| res.end('"api.example.com: Crazy from thinking, wanting to be reasonable, and the heart has reasons that reason itself will never understand."'); | ||
| }; | ||
| }; |
@@ -7,3 +7,3 @@ const express = require('express'); | ||
| res.setHeader('Content-Type', 'text/plain; charset=utf-8'); | ||
| res.send('"subdomain.example.com: Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."'); | ||
| res.send('"express.example.com: Loco de pensar, queriendo entrar en razón, y el corazón tiene razones que la propia razón nunca entenderá."'); | ||
| }); | ||
@@ -10,0 +10,0 @@ |
+18
-19
@@ -112,2 +112,15 @@ const fs = require('fs'); | ||
| function normalizeDomainForLocalHost(domain) { | ||
| return (domain || '').trim().toLowerCase().replace(/^www\./, ''); | ||
| } | ||
| function localHostForDomain(normalizedDomain) { | ||
| const normalized = normalizedDomain; | ||
| if (!normalized) return 'localhost'; | ||
| if (normalized.startsWith('*.')) return '*.localhost'; | ||
| const labels = normalized.split('.').filter(Boolean); | ||
| if (labels.length > 2) return `${labels.slice(0, -2).join('.')}.localhost`; | ||
| return 'localhost'; | ||
| } | ||
| // Virtual Server that completely isolates applications | ||
@@ -601,3 +614,3 @@ class VirtualServer extends EventEmitter { | ||
| getUrl(domain) { | ||
| const cleanDomain = domain.startsWith('www.') ? domain.slice(4) : domain; | ||
| const cleanDomain = normalizeDomainForLocalHost(domain); | ||
@@ -611,18 +624,3 @@ const exactMatch = this.sites[cleanDomain] || this.sites[`www.${cleanDomain}`]; | ||
| if (this.domainPorts && this.domainPorts[pattern] !== undefined) { | ||
| const labels = cleanDomain.split('.').filter(Boolean); | ||
| let localHost = 'localhost'; | ||
| // For wildcard matches, preserve the full left-side subdomain chain. | ||
| if (pattern.startsWith('*.')) { | ||
| const root = wildcardRoot(pattern); | ||
| if (root && hostMatchesWildcard(cleanDomain, pattern)) { | ||
| const subdomain = cleanDomain.slice(0, -(root.length + 1)); | ||
| if (subdomain) localHost = `${subdomain}.localhost`; | ||
| } | ||
| } else if (labels.length > 2) { | ||
| // For exact subdomains (e.g. api.example.com), map to api.localhost. | ||
| localHost = `${labels.slice(0, -2).join('.')}.localhost`; | ||
| } | ||
| return `http://${localHost}:${this.domainPorts[pattern]}`; | ||
| return `http://${localHostForDomain(cleanDomain)}:${this.domainPorts[pattern]}`; | ||
| } | ||
@@ -725,3 +723,4 @@ return null; | ||
| httpServer.listen(port, 'localhost', () => { | ||
| log.info(`🌐 ${domain} → http://localhost:${port}`); | ||
| const cleanDomain = normalizeDomainForLocalHost(domain); | ||
| log.info(`🌐 ${domain} → http://${localHostForDomain(cleanDomain)}:${port}`); | ||
| }); | ||
@@ -801,3 +800,3 @@ | ||
| if (eventDomain && !msg.includes(`[${eventDomain}]`)) { | ||
| msg = `[domain:${eventDomain}] ${msg}`; | ||
| msg = `[${eventDomain}] ${msg}`; | ||
| } | ||
@@ -804,0 +803,0 @@ // Suppress known benign warnings from ACME when using acme-dns-01-cli |
+1
-1
| { | ||
| "name": "roster-server", | ||
| "version": "2.3.8", | ||
| "version": "2.3.10", | ||
| "description": "👾 RosterServer - A domain host router to host multiple HTTPS.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
| --- | ||
| id: wrxv04lnq9 | ||
| type: decision | ||
| title: 'Decision: Local subdomain URL format' | ||
| created: '2026-03-16 17:15:01' | ||
| --- | ||
| # Decision: Local subdomain URL format | ||
| **What**: In local mode, `getUrl(domain)` now returns `http://<subdomain>.localhost:<port>` for subdomains, while apex domains keep `http://localhost:<port>`. | ||
| **Where**: Implemented in `index.js` (`getUrl` local branch), validated in `test/roster-server.test.js`, and documented in `README.md`. | ||
| **Why**: Local URLs should preserve subdomain semantics so multi-tenant/subdomain routes are explicit and easier to test. | ||
| **Alternatives rejected**: Keeping all local URLs as `http://localhost:<port>` was rejected because it hides host intent for subdomain-based apps. |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
570163
-0.05%9687
-0.01%