URL & Hostname Utilities
parseHostname
Parse and normalize a hostname input for use as a .localhost subdomain.
function parseHostname ( input : string ) : string
Behavior:
Strips protocol prefixes (http://, https://)
Converts to lowercase
Appends .localhost if not present
Validates hostname characters (letters, digits, hyphens, dots only)
Enforces DNS label length limits (63 characters per label)
Rejects consecutive dots and invalid patterns
The hostname to parse. Can include protocol prefixes, which will be stripped.
Returns: Normalized .localhost hostname
Throws: Error if the hostname is invalid
Examples:
import { parseHostname } from "portless" ;
parseHostname ( "myapp" );
// => "myapp.localhost"
parseHostname ( "api.myapp" );
// => "api.myapp.localhost"
parseHostname ( "http://myapp.localhost" );
// => "myapp.localhost"
parseHostname ( "MyApp" );
// => "myapp.localhost" (converted to lowercase)
// Invalid examples (throw errors):
parseHostname ( "my app" ); // Error: spaces not allowed
parseHostname ( "my_app" ); // Error: underscores not allowed
parseHostname ( "" ); // Error: hostname cannot be empty
Format a .localhost URL with the correct protocol and port.
function formatUrl (
hostname : string ,
proxyPort : number ,
tls ?: boolean
) : string
Constructs a complete URL for a .localhost hostname. Omits the port when it matches the protocol default (80 for HTTP, 443 for HTTPS).
The .localhost hostname (e.g., “api.myapp.localhost”)
Whether to use HTTPS protocol
Returns: Complete URL string
Examples:
import { formatUrl } from "portless" ;
formatUrl ( "api.localhost" , 1355 );
// => "http://api.localhost:1355"
formatUrl ( "api.localhost" , 80 );
// => "http://api.localhost" (port omitted)
formatUrl ( "api.localhost" , 443 , true );
// => "https://api.localhost" (port omitted)
formatUrl ( "api.localhost" , 8443 , true );
// => "https://api.localhost:8443"
escapeHtml
Escape HTML special characters to prevent XSS attacks.
function escapeHtml ( str : string ) : string
Converts &, <, >, ", and ' to their HTML entity equivalents.
Returns: HTML-safe string
Examples:
import { escapeHtml } from "portless" ;
escapeHtml ( "<script>alert('XSS')</script>" );
// => "<script>alert('XSS')</script>"
escapeHtml ( 'Hello "world" & <friends>' );
// => "Hello "world" & <friends>"
System Utilities
isErrnoException
Type guard for Node.js system errors with error codes.
function isErrnoException ( err : unknown ) : err is NodeJS . ErrnoException
Check if an error object is a Node.js system error with an error code like EADDRINUSE, EACCES, etc.
The error object to check
Returns: true if the error is a NodeJS.ErrnoException, false otherwise
Examples:
import { isErrnoException } from "portless" ;
try {
// Some operation that might fail
fs . readFileSync ( "/nonexistent" );
} catch ( err ) {
if ( isErrnoException ( err )) {
if ( err . code === "ENOENT" ) {
console . log ( "File not found" );
} else if ( err . code === "EACCES" ) {
console . log ( "Permission denied" );
}
}
}
fixOwnership
Fix file ownership when running under sudo.
function fixOwnership ( ... paths : string []) : void
When running as root via sudo, changes file ownership back to the real user. This prevents files created by sudo from being owned by root. No-op when not running as root.
File or directory paths to fix ownership on
Examples:
import { fixOwnership } from "portless" ;
import * as fs from "node:fs" ;
// Create a file under sudo
fs . writeFileSync ( "/tmp/portless/routes.json" , "[]" );
// Fix ownership so the real user can access it later
fixOwnership ( "/tmp/portless/routes.json" );
/etc/hosts Management
These functions manipulate /etc/hosts to add or remove .localhost entries for Safari compatibility.
syncHostsFile
Sync /etc/hosts to include entries for given hostnames.
function syncHostsFile ( hostnames : string []) : boolean
Adds a portless-managed block to /etc/hosts mapping each hostname to 127.0.0.1. Requires root access (sudo).
Array of .localhost hostnames to add
Returns: true on success, false on failure
Examples:
import { syncHostsFile } from "portless" ;
syncHostsFile ([ "api.localhost" , "app.localhost" ]);
// Adds to /etc/hosts:
// # portless-start
// 127.0.0.1 api.localhost
// 127.0.0.1 app.localhost
// # portless-end
cleanHostsFile
Remove the portless-managed block from /etc/hosts.
function cleanHostsFile () : boolean
Removes all entries added by syncHostsFile. Requires root access (sudo).
Returns: true on success, false on failure
Examples:
import { cleanHostsFile } from "portless" ;
cleanHostsFile ();
// Removes the portless-managed block from /etc/hosts
Extract portless-managed entries from /etc/hosts content.
function extractManagedBlock ( content : string ) : string []
The raw /etc/hosts file content
Returns: Array of lines between portless markers (excluding the markers themselves)
Examples:
import { extractManagedBlock } from "portless" ;
import * as fs from "node:fs" ;
const content = fs . readFileSync ( "/etc/hosts" , "utf-8" );
const managed = extractManagedBlock ( content );
// => ["127.0.0.1 api.localhost", "127.0.0.1 app.localhost"]
removeBlock
Remove the portless-managed block from /etc/hosts content.
function removeBlock ( content : string ) : string
The raw /etc/hosts file content
Returns: Content with portless block removed
Examples:
import { removeBlock } from "portless" ;
import * as fs from "node:fs" ;
const content = fs . readFileSync ( "/etc/hosts" , "utf-8" );
const cleaned = removeBlock ( content );
fs . writeFileSync ( "/etc/hosts" , cleaned );
buildBlock
Build a portless-managed block for given hostnames.
function buildBlock ( hostnames : string []) : string
Array of .localhost hostnames
Returns: String containing the portless-managed block with markers
Examples:
import { buildBlock } from "portless" ;
const block = buildBlock ([ "api.localhost" , "app.localhost" ]);
console . log ( block );
// # portless-start
// 127.0.0.1 api.localhost
// 127.0.0.1 app.localhost
// # portless-end
getManagedHostnames
Get currently managed hostnames from /etc/hosts.
function getManagedHostnames () : string []
Returns: Array of hostnames currently in the portless-managed block
Examples:
import { getManagedHostnames } from "portless" ;
const hostnames = getManagedHostnames ();
// => ["api.localhost", "app.localhost"]
checkLocalhostResolution
Check whether a .localhost subdomain resolves to 127.0.0.1.
function checkLocalhostResolution ( hostname : string ) : Promise < boolean >
Tests if the system DNS resolver correctly resolves a .localhost hostname to 127.0.0.1. Useful for detecting Safari/DNS issues.
The .localhost hostname to check
Returns: Promise that resolves to true if resolution works, false otherwise
Examples:
import { checkLocalhostResolution } from "portless" ;
const works = await checkLocalhostResolution ( "api.localhost" );
if ( ! works ) {
console . log ( "DNS resolution failed - Safari may not work" );
console . log ( "Run: sudo portless hosts sync" );
}
Import Examples
// ESM
import {
parseHostname ,
formatUrl ,
escapeHtml ,
isErrnoException ,
fixOwnership ,
syncHostsFile ,
cleanHostsFile ,
extractManagedBlock ,
removeBlock ,
buildBlock ,
getManagedHostnames ,
checkLocalhostResolution ,
} from "portless" ;
// CommonJS
const {
parseHostname ,
formatUrl ,
escapeHtml ,
isErrnoException ,
fixOwnership ,
syncHostsFile ,
cleanHostsFile ,
} = require ( "portless" );
API Overview Getting started with the programmatic API
Type Definitions TypeScript interfaces and types
Safari DNS Learn about /etc/hosts management