@stati/core 1.24.5 → 1.26.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/dist/core/dev.d.ts.map +1 -1
- package/dist/core/dev.js +19 -8
- package/dist/core/preview.d.ts.map +1 -1
- package/dist/core/preview.js +3 -2
- package/dist/core/utils/index.d.ts +2 -2
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +1 -1
- package/dist/core/utils/server.utils.d.ts +35 -0
- package/dist/core/utils/server.utils.d.ts.map +1 -1
- package/dist/core/utils/server.utils.js +59 -0
- package/package.json +1 -1
package/dist/core/dev.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/core/dev.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAe,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAwC7D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb;AAscD,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC,CA4hBxF"}
|
package/dist/core/dev.js
CHANGED
|
@@ -11,7 +11,7 @@ import { clearMarkdownProcessorCache } from './markdown.js';
|
|
|
11
11
|
import { loadCacheManifest, saveCacheManifest, computeNavigationHash, DevServerLockManager, } from './isg/index.js';
|
|
12
12
|
import { loadContent } from './content.js';
|
|
13
13
|
import { buildNavigation } from './navigation.js';
|
|
14
|
-
import { resolveDevPaths, resolveCacheDir, resolvePrettyUrl, createErrorOverlay, parseErrorDetails, TemplateError, createFallbackLogger, mergeServerOptions, createTypeScriptWatcher, isEnabledEnvFlag, refreshCssOutputWatcher, normalizePathForComparison, isPathWithinDirectory, } from './utils/index.js';
|
|
14
|
+
import { resolveDevPaths, resolveCacheDir, resolvePrettyUrl, createErrorOverlay, parseErrorDetails, TemplateError, createFallbackLogger, mergeServerOptions, createTypeScriptWatcher, isEnabledEnvFlag, refreshCssOutputWatcher, normalizePathForComparison, isPathWithinDirectory, assertPortAvailable, toPortUnavailableError, } from './utils/index.js';
|
|
15
15
|
import { setEnv, getEnv } from '../env.js';
|
|
16
16
|
import { DEFAULT_DEV_PORT, DEFAULT_DEV_HOST, TEMPLATE_EXTENSION, DEFAULT_OUT_DIR, } from '../constants.js';
|
|
17
17
|
/**
|
|
@@ -598,6 +598,8 @@ export async function createDevServer(options = {}) {
|
|
|
598
598
|
const devServer = {
|
|
599
599
|
url,
|
|
600
600
|
async start() {
|
|
601
|
+
// Fail fast if the port is taken, before acquiring the lock or running the initial build
|
|
602
|
+
await assertPortAvailable(port, host, 'dev.port');
|
|
601
603
|
// Acquire dev server lock to prevent multiple dev servers in the same directory
|
|
602
604
|
try {
|
|
603
605
|
await devLock.acquireLock();
|
|
@@ -705,14 +707,23 @@ export async function createDevServer(options = {}) {
|
|
|
705
707
|
}
|
|
706
708
|
}
|
|
707
709
|
// Start HTTP server
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
710
|
+
try {
|
|
711
|
+
await new Promise((resolve, reject) => {
|
|
712
|
+
httpServer.listen(port, host, () => {
|
|
713
|
+
resolve();
|
|
714
|
+
});
|
|
715
|
+
httpServer.on('error', (error) => {
|
|
716
|
+
reject(toPortUnavailableError(error, port, host, 'dev.port'));
|
|
717
|
+
});
|
|
714
718
|
});
|
|
715
|
-
}
|
|
719
|
+
}
|
|
720
|
+
catch (error) {
|
|
721
|
+
// Release the lock and watchers acquired above so a failed start doesn't leak them.
|
|
722
|
+
// The HTTP server never started listening, so there is nothing to close.
|
|
723
|
+
httpServer = null;
|
|
724
|
+
await devServer.stop();
|
|
725
|
+
throw error;
|
|
726
|
+
}
|
|
716
727
|
// Set up file watching
|
|
717
728
|
const watchPaths = [srcDir, staticDir].filter(Boolean);
|
|
718
729
|
watcher = chokidar.watch(watchPaths, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/core/preview.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/core/preview.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,mBAAmB,CAAC;AAa7D,MAAM,WAAW,oBAAoB;IACnC,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0BAA0B;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,GAAG,EAAE,MAAM,CAAC;CACb;AAyBD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,aAAa,CAAC,CAmKxB"}
|
package/dist/core/preview.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createServer } from 'node:http';
|
|
|
2
2
|
import { join, extname } from 'node:path';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { loadConfig } from '../config/loader.js';
|
|
5
|
-
import { resolveDevPaths, resolvePrettyUrl, createFallbackLogger, mergeServerOptions, isPathWithinDirectory, } from './utils/index.js';
|
|
5
|
+
import { resolveDevPaths, resolvePrettyUrl, createFallbackLogger, mergeServerOptions, isPathWithinDirectory, assertPortAvailable, toPortUnavailableError, } from './utils/index.js';
|
|
6
6
|
import { DEFAULT_PREVIEW_PORT, DEFAULT_DEV_HOST } from '../constants.js';
|
|
7
7
|
/**
|
|
8
8
|
* Loads and validates configuration for the preview server.
|
|
@@ -111,6 +111,7 @@ export async function createPreviewServer(options = {}) {
|
|
|
111
111
|
const previewServer = {
|
|
112
112
|
url,
|
|
113
113
|
async start() {
|
|
114
|
+
await assertPortAvailable(port, host, 'preview.port');
|
|
114
115
|
// Create HTTP server
|
|
115
116
|
httpServer = createServer(async (req, res) => {
|
|
116
117
|
const requestPath = req.url || '/';
|
|
@@ -136,7 +137,7 @@ export async function createPreviewServer(options = {}) {
|
|
|
136
137
|
resolve();
|
|
137
138
|
});
|
|
138
139
|
httpServer.on('error', (error) => {
|
|
139
|
-
reject(error);
|
|
140
|
+
reject(toPortUnavailableError(error, port, host, 'preview.port'));
|
|
140
141
|
});
|
|
141
142
|
});
|
|
142
143
|
logger.success?.(`Preview server running at ${url}`);
|
|
@@ -13,8 +13,8 @@ export { makeCallablePartial, wrapPartialsAsCallable } from './callable-partials
|
|
|
13
13
|
export type { CallablePartial } from './callable-partials.utils.js';
|
|
14
14
|
export { TemplateError, parseEtaError, createTemplateError } from './template-errors.utils.js';
|
|
15
15
|
export { createNavigationHelpers } from './navigation-helpers.utils.js';
|
|
16
|
-
export { resolvePrettyUrl, mergeServerOptions } from './server.utils.js';
|
|
17
|
-
export type { PrettyUrlResult, MergedServerOptions, MergeServerOptionsParams, } from './server.utils.js';
|
|
16
|
+
export { resolvePrettyUrl, mergeServerOptions, assertPortAvailable, toPortUnavailableError, PortUnavailableError, } from './server.utils.js';
|
|
17
|
+
export type { PrettyUrlResult, MergedServerOptions, MergeServerOptionsParams, PortUnavailableCode, } from './server.utils.js';
|
|
18
18
|
export { createErrorOverlay, parseErrorDetails } from './error-overlay.utils.js';
|
|
19
19
|
export type { ErrorDetails } from './error-overlay.utils.js';
|
|
20
20
|
export { getStatiVersion } from './version.utils.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/utils/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACL,QAAQ,EACR,SAAS,EACT,UAAU,EACV,SAAS,EACT,MAAM,EACN,QAAQ,EACR,OAAO,EACP,IAAI,GACL,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGhD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAG7C,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,2BAA2B,EAC3B,cAAc,EACd,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAG9E,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC3F,YAAY,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAGpE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAG/F,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAGxE,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/utils/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACL,QAAQ,EACR,SAAS,EACT,UAAU,EACV,SAAS,EACT,MAAM,EACN,QAAQ,EACR,OAAO,EACP,IAAI,GACL,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,0BAA0B,EAC1B,qBAAqB,GACtB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,wBAAwB,GACzB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGhD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAG7C,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,2BAA2B,EAC3B,cAAc,EACd,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAG9E,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC3F,YAAY,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAGpE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAG/F,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAGxE,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACjF,YAAY,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGpE,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAGrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,WAAW,EACX,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1E,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAG/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGlD,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAC7F,YAAY,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC"}
|
package/dist/core/utils/index.js
CHANGED
|
@@ -23,7 +23,7 @@ export { TemplateError, parseEtaError, createTemplateError } from './template-er
|
|
|
23
23
|
// Navigation helper utilities
|
|
24
24
|
export { createNavigationHelpers } from './navigation-helpers.utils.js';
|
|
25
25
|
// Server utilities
|
|
26
|
-
export { resolvePrettyUrl, mergeServerOptions } from './server.utils.js';
|
|
26
|
+
export { resolvePrettyUrl, mergeServerOptions, assertPortAvailable, toPortUnavailableError, PortUnavailableError, } from './server.utils.js';
|
|
27
27
|
// Error overlay utilities
|
|
28
28
|
export { createErrorOverlay, parseErrorDetails } from './error-overlay.utils.js';
|
|
29
29
|
// Version utilities
|
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes for which a port is considered unavailable
|
|
3
|
+
*/
|
|
4
|
+
export type PortUnavailableCode = 'EADDRINUSE' | 'EACCES';
|
|
5
|
+
/**
|
|
6
|
+
* Thrown when a server cannot bind to its configured port.
|
|
7
|
+
* The message is user-facing and explains how to pick a different port.
|
|
8
|
+
*/
|
|
9
|
+
export declare class PortUnavailableError extends Error {
|
|
10
|
+
readonly port: number;
|
|
11
|
+
readonly host: string;
|
|
12
|
+
readonly code: PortUnavailableCode;
|
|
13
|
+
constructor(port: number, host: string, code: PortUnavailableCode, configKey?: string);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Converts a server `listen` error into a {@link PortUnavailableError} when the
|
|
17
|
+
* failure is caused by the port being taken or restricted. Other errors are returned as-is.
|
|
18
|
+
*
|
|
19
|
+
* @param error - Error emitted by `server.listen()`
|
|
20
|
+
* @param port - Port the server tried to bind to
|
|
21
|
+
* @param host - Host the server tried to bind to
|
|
22
|
+
* @param configKey - Config key users can set to change the port (e.g. `dev.port`)
|
|
23
|
+
* @returns A PortUnavailableError or the original error
|
|
24
|
+
*/
|
|
25
|
+
export declare function toPortUnavailableError(error: unknown, port: number, host: string, configKey?: string): unknown;
|
|
26
|
+
/**
|
|
27
|
+
* Verifies that a port can be bound on the given host by briefly listening on it.
|
|
28
|
+
* Use this before expensive startup work so an occupied port fails fast.
|
|
29
|
+
*
|
|
30
|
+
* @param port - Port to check
|
|
31
|
+
* @param host - Host to bind to
|
|
32
|
+
* @param configKey - Config key users can set to change the port (e.g. `dev.port`)
|
|
33
|
+
* @throws {PortUnavailableError} When the port is in use or not permitted
|
|
34
|
+
*/
|
|
35
|
+
export declare function assertPortAvailable(port: number, host: string, configKey?: string): Promise<void>;
|
|
1
36
|
/**
|
|
2
37
|
* Result of attempting to resolve a pretty URL path
|
|
3
38
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/server.utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/server.utils.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,QAAQ,CAAC;AAE1D;;;GAGG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;gBAEvB,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,EAAE,SAAS,CAAC,EAAE,MAAM;CAatF;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAMT;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAcjG;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iCAAiC;IACjC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,eAAe,CAAC,CA2D1B;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB,CAAC,CAAC;IACzC,sCAAsC;IACtC,OAAO,EAAE,CAAC,CAAC;IACX,4CAA4C;IAC5C,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;IACrE,4EAA4E;IAC5E,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,OAAO,CAAC;KACf,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,EAC3F,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC,GAClC,mBAAmB,CAQrB"}
|
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
2
|
import { stat } from 'node:fs/promises';
|
|
3
|
+
import { createServer } from 'node:net';
|
|
4
|
+
/**
|
|
5
|
+
* Thrown when a server cannot bind to its configured port.
|
|
6
|
+
* The message is user-facing and explains how to pick a different port.
|
|
7
|
+
*/
|
|
8
|
+
export class PortUnavailableError extends Error {
|
|
9
|
+
port;
|
|
10
|
+
host;
|
|
11
|
+
code;
|
|
12
|
+
constructor(port, host, code, configKey) {
|
|
13
|
+
const reason = code === 'EADDRINUSE'
|
|
14
|
+
? `Port ${port} is already in use on ${host}. Stop the process using it, or choose another port`
|
|
15
|
+
: `Permission denied to use port ${port} on ${host}. Choose another port`;
|
|
16
|
+
const hint = configKey ? ` with --port or \`${configKey}\` in your Stati config.` : '.';
|
|
17
|
+
super(`${reason}${hint}`);
|
|
18
|
+
this.name = 'PortUnavailableError';
|
|
19
|
+
this.port = port;
|
|
20
|
+
this.host = host;
|
|
21
|
+
this.code = code;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Converts a server `listen` error into a {@link PortUnavailableError} when the
|
|
26
|
+
* failure is caused by the port being taken or restricted. Other errors are returned as-is.
|
|
27
|
+
*
|
|
28
|
+
* @param error - Error emitted by `server.listen()`
|
|
29
|
+
* @param port - Port the server tried to bind to
|
|
30
|
+
* @param host - Host the server tried to bind to
|
|
31
|
+
* @param configKey - Config key users can set to change the port (e.g. `dev.port`)
|
|
32
|
+
* @returns A PortUnavailableError or the original error
|
|
33
|
+
*/
|
|
34
|
+
export function toPortUnavailableError(error, port, host, configKey) {
|
|
35
|
+
const code = error?.code;
|
|
36
|
+
if (code === 'EADDRINUSE' || code === 'EACCES') {
|
|
37
|
+
return new PortUnavailableError(port, host, code, configKey);
|
|
38
|
+
}
|
|
39
|
+
return error;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Verifies that a port can be bound on the given host by briefly listening on it.
|
|
43
|
+
* Use this before expensive startup work so an occupied port fails fast.
|
|
44
|
+
*
|
|
45
|
+
* @param port - Port to check
|
|
46
|
+
* @param host - Host to bind to
|
|
47
|
+
* @param configKey - Config key users can set to change the port (e.g. `dev.port`)
|
|
48
|
+
* @throws {PortUnavailableError} When the port is in use or not permitted
|
|
49
|
+
*/
|
|
50
|
+
export function assertPortAvailable(port, host, configKey) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const probe = createServer();
|
|
53
|
+
probe.once('error', (error) => {
|
|
54
|
+
reject(toPortUnavailableError(error, port, host, configKey));
|
|
55
|
+
});
|
|
56
|
+
probe.once('listening', () => {
|
|
57
|
+
probe.close(() => resolve());
|
|
58
|
+
});
|
|
59
|
+
probe.listen(port, host);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
3
62
|
/**
|
|
4
63
|
* Attempts to resolve pretty URL paths by trying various fallback strategies.
|
|
5
64
|
* This handles common patterns like:
|