roster-server 2.3.6 → 2.3.8

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/README.md CHANGED
@@ -309,12 +309,13 @@ await roster.start();
309
309
  const url = roster.getUrl('example.com');
310
310
  console.log(url);
311
311
  // Local mode: http://localhost:9465
312
+ // Local subdomain: http://api.localhost:9465
312
313
  // Production mode: https://example.com
313
314
  ```
314
315
 
315
316
  This method:
316
317
  - Returns the correct URL based on your environment (`local: true/false`)
317
- - In **local mode**: Returns `http://localhost:{port}` with the assigned port
318
+ - In **local mode**: Returns `http://localhost:{port}` for apex domains and `http://{subdomain}.localhost:{port}` for subdomains
318
319
  - In **production mode**: Returns `https://{domain}` (or with custom port if configured)
319
320
  - Handles `www.` prefix automatically (returns same URL)
320
321
  - Returns `null` for domains that aren't registered
@@ -327,9 +328,12 @@ import Roster from 'roster-server';
327
328
  // Local development
328
329
  const localRoster = new Roster({ local: true });
329
330
  localRoster.register('example.com', handler);
331
+ localRoster.register('api.example.com', handler);
330
332
  await localRoster.start();
331
333
  console.log(localRoster.getUrl('example.com'));
332
334
  // → http://localhost:9465
335
+ console.log(localRoster.getUrl('api.example.com'));
336
+ // → http://api.localhost:7342
333
337
 
334
338
  // Production
335
339
  const prodRoster = new Roster({ local: false });
@@ -0,0 +1,12 @@
1
+ ---
2
+ id: wrxv04lnq9
3
+ type: decision
4
+ title: 'Decision: Local subdomain URL format'
5
+ created: '2026-03-16 17:15:01'
6
+ ---
7
+ # Decision: Local subdomain URL format
8
+
9
+ **What**: In local mode, `getUrl(domain)` now returns `http://<subdomain>.localhost:<port>` for subdomains, while apex domains keep `http://localhost:<port>`.
10
+ **Where**: Implemented in `index.js` (`getUrl` local branch), validated in `test/roster-server.test.js`, and documented in `README.md`.
11
+ **Why**: Local URLs should preserve subdomain semantics so multi-tenant/subdomain routes are explicit and easier to test.
12
+ **Alternatives rejected**: Keeping all local URLs as `http://localhost:<port>` was rejected because it hides host intent for subdomain-based apps.
package/index.js CHANGED
@@ -606,7 +606,22 @@ class Roster {
606
606
  if (this.local) {
607
607
  const pattern = resolved.siteKey.split(':')[0];
608
608
  if (this.domainPorts && this.domainPorts[pattern] !== undefined) {
609
- return `http://localhost:${this.domainPorts[pattern]}`;
609
+ const labels = cleanDomain.split('.').filter(Boolean);
610
+ let localHost = 'localhost';
611
+
612
+ // For wildcard matches, preserve the full left-side subdomain chain.
613
+ if (pattern.startsWith('*.')) {
614
+ const root = wildcardRoot(pattern);
615
+ if (root && hostMatchesWildcard(cleanDomain, pattern)) {
616
+ const subdomain = cleanDomain.slice(0, -(root.length + 1));
617
+ if (subdomain) localHost = `${subdomain}.localhost`;
618
+ }
619
+ } else if (labels.length > 2) {
620
+ // For exact subdomains (e.g. api.example.com), map to api.localhost.
621
+ localHost = `${labels.slice(0, -2).join('.')}.localhost`;
622
+ }
623
+
624
+ return `http://${localHost}:${this.domainPorts[pattern]}`;
610
625
  }
611
626
  return null;
612
627
  }
@@ -778,7 +793,7 @@ class Roster {
778
793
  }
779
794
  }
780
795
  if (!msg || msg === 'undefined') msg = `[${event}] (no details)`;
781
- if (eventDomain && !msg.includes(`[domain:${eventDomain}]`)) {
796
+ if (eventDomain && !msg.includes(`[${eventDomain}]`)) {
782
797
  msg = `[domain:${eventDomain}] ${msg}`;
783
798
  }
784
799
  // Suppress known benign warnings from ACME when using acme-dns-01-cli
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roster-server",
3
- "version": "2.3.6",
3
+ "version": "2.3.8",
4
4
  "description": "👾 RosterServer - A domain host router to host multiple HTTPS.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -218,7 +218,7 @@ describe('Roster', () => {
218
218
  roster.register('*.example.com', () => {});
219
219
  roster.domainPorts = { '*.example.com': 9999 };
220
220
  roster.local = true;
221
- assert.strictEqual(roster.getUrl('api.example.com'), 'http://localhost:9999');
221
+ assert.strictEqual(roster.getUrl('api.example.com'), 'http://api.localhost:9999');
222
222
  });
223
223
  it('returns https URL for wildcard-matched host in production', () => {
224
224
  const roster = new Roster({ local: false });
@@ -359,6 +359,13 @@ describe('Roster', () => {
359
359
  roster.local = true;
360
360
  assert.strictEqual(roster.getUrl('exact.local'), 'http://localhost:4567');
361
361
  });
362
+ it('returns http://subdomain.localhost:PORT in local mode for exact subdomain', () => {
363
+ const roster = new Roster({ local: true });
364
+ roster.register('api.example.com', () => {});
365
+ roster.domainPorts = { 'api.example.com': 5678 };
366
+ roster.local = true;
367
+ assert.strictEqual(roster.getUrl('api.example.com'), 'http://api.localhost:5678');
368
+ });
362
369
  it('returns https URL in production for registered domain', () => {
363
370
  const roster = new Roster({ local: false });
364
371
  roster.register('example.com', () => {});