@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.
@@ -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;AAsC7D,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,CAihBxF"}
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
- await new Promise((resolve, reject) => {
709
- httpServer.listen(port, host, () => {
710
- resolve();
711
- });
712
- httpServer.on('error', (error) => {
713
- reject(error);
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;AAW7D,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,CAiKxB"}
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"}
@@ -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,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACzE,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,wBAAwB,GACzB,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"}
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"}
@@ -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":"AAGA;;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
+ {"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:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stati/core",
3
- "version": "1.24.5",
3
+ "version": "1.26.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",