@luckystack/server 0.8.4 → 0.8.5
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/CHANGELOG.md +11 -0
- package/CLAUDE.md +1 -0
- package/dist/index.d.ts +18 -1
- package/dist/index.js +28 -10
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `collectOverlayEntries(packageDir)` — the single owner of what a `luckystack/<pkg>/` folder contributes at boot, exported so `scripts/bundleServer.mjs` decides identically instead of reimplementing the walk (ADR 0047).
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Test files in a `luckystack/<pkg>/` overlay folder are no longer imported at boot or bundled into the production server. A test file that only performs side effects (registering a stub adapter, say) previously ran in production without failing loudly. Covers `.tests.` / `.test.` / `.spec.` plus `__tests__` / `__mocks__` folders.
|
|
17
|
+
- A subdirectory inside an overlay package folder is now reported instead of skipped in silence. The walk is flat by design, so overlay code parked one level down never ran and nothing pointed at it.
|
|
18
|
+
|
|
19
|
+
- Composed presets accept a repeated NESTED function namespace. Equivalence is now checked recursively, so a namespace object rebuilt per preset (any function module living in a subdirectory of a `serverFunctionDirs` root) no longer reads as a differing implementation and rejects the compose at boot. A genuinely differing leaf implementation still fails closed.
|
|
20
|
+
|
|
10
21
|
## [0.8.4] - 2026-08-17
|
|
11
22
|
|
|
12
23
|
### Changed
|
package/CLAUDE.md
CHANGED
|
@@ -29,6 +29,7 @@ One-call server bootstrap for a LuckyStack project. Wires together a raw Node.js
|
|
|
29
29
|
| `RunningLuckyStackServer.stop(options?)` / `.close(options?)` | Graceful shutdown (MIS-016). Stops accepting new connections, dispatches the core `preServerStop` hook, flushes error-trackers (bounded by a per-step timeout race), then closes io + http + the Redis-adapter pub/sub clients. Each step isolated + time-bounded so one hanging step can't stall shutdown. Idempotent. `options`: `{ reason?, timeoutMs? }` (default `timeoutMs` 10000). | -> docs/create-server.md |
|
|
30
30
|
| `bootstrapLuckyStack(options)` | High-level entry: auto-imports `luckystack/<pkg>/*.ts` overlay in topological order, then delegates to `createLuckyStackServer`. An overlay file that fails to import (classic: it imports a package removed via `luckystack remove`) aborts boot with an ACTIONABLE error naming the file, instead of a raw ERR_MODULE_NOT_FOUND. | -> docs/create-server.md |
|
|
31
31
|
| `OVERLAY_ORDER` (exported const) | The canonical overlay walk order (`core, deploy, login, email, sentry, presence, cron, docs-ui, server`). Exported so the consumer's `scripts/bundleServer.mjs` imports it at build time — a hardcoded bundler copy once silently dropped the cron slot from prod; `overlayOrderParity.test.ts` pins the bundlers' fallback lists to this. | -> docs/create-server.md |
|
|
32
|
+
| `collectOverlayEntries(packageDir)` | Single owner of what one `luckystack/<pkg>/` folder contributes: `{ fileNames, ignoredDirectoryNames }`. `index.*` first, then the rest alphabetically; `.ts`/`.js` only; **test files excluded** (`.tests.`/`.test.`/`.spec.`, plus `__tests__`/`__mocks__` folders) so they never boot or reach the production bundle. The walk is FLAT — any other subdirectory comes back in `ignoredDirectoryNames` and the loader warns, instead of dropping overlay code in silence. Exported so `scripts/bundleServer.mjs` decides identically. ADR 0047. | -> docs/create-server.md |
|
|
32
33
|
| `registerOverlayLoader(loader)` | Production-bundle seam: the server bundler (`scripts/bundleServer.mjs`) generates an entry that statically imports the overlay files and registers this loader; `bootstrapLuckyStack` then runs it INSTEAD of the runtime `.ts` folder walk (plain `node` cannot import `.ts` — ERR_UNKNOWN_FILE_EXTENSION). | -> docs/create-server.md |
|
|
33
34
|
| `verifyBootstrap(requirements?)` | Pre-flight check for ProjectConfig / DeployConfig / ServicesConfig / OAuth / RuntimeMapsProvider / LocalizedNormalizer. Throws one descriptive `Error`. | -> docs/create-server.md |
|
|
34
35
|
| `parseServerArgv(argv)` | Pure parser: validates positional `<bundles> [port]` and returns `{ bundles, port }`. Throws on malformed input. | -> docs/argv-parsing.md |
|
package/dist/index.d.ts
CHANGED
|
@@ -197,6 +197,23 @@ interface BootstrapLuckyStackOptions extends CreateLuckyStackServerOptions {
|
|
|
197
197
|
declare const OVERLAY_ORDER: string[];
|
|
198
198
|
type OverlayLoader = () => Promise<void>;
|
|
199
199
|
declare const registerOverlayLoader: (loader: OverlayLoader) => void;
|
|
200
|
+
interface OverlayEntries {
|
|
201
|
+
/** Files to import, in load order: `index.*` first, then the rest alphabetically. */
|
|
202
|
+
fileNames: string[];
|
|
203
|
+
/**
|
|
204
|
+
* Subdirectories the flat walk skipped, minus conventional test folders.
|
|
205
|
+
* The caller warns about these — silently dropping them meant overlay code
|
|
206
|
+
* one level down simply never ran, with nothing to point at.
|
|
207
|
+
*/
|
|
208
|
+
ignoredDirectoryNames: string[];
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Decide what a single overlay package folder contributes. Exported because
|
|
212
|
+
* both the runtime walk here and `scripts/bundleServer.mjs` must agree exactly:
|
|
213
|
+
* a file the bundler includes but the loader skips (or the reverse) is a
|
|
214
|
+
* dev/prod divergence, which is the defect class ADR 0046 exists for.
|
|
215
|
+
*/
|
|
216
|
+
declare const collectOverlayEntries: (packageDir: string) => OverlayEntries;
|
|
200
217
|
declare const bootstrapLuckyStack: (options?: BootstrapLuckyStackOptions) => Promise<RunningLuckyStackServer>;
|
|
201
218
|
|
|
202
219
|
interface BootstrapRequirements {
|
|
@@ -308,4 +325,4 @@ declare const registerSecurityHeaders: (builder: SecurityHeadersBuilder | null)
|
|
|
308
325
|
/** Read the active builder (or null). */
|
|
309
326
|
declare const getSecurityHeadersBuilder: () => SecurityHeadersBuilder | null;
|
|
310
327
|
|
|
311
|
-
export { type BootstrapLuckyStackOptions, type BootstrapRequirements, type CreateLuckyStackServerOptions, type CustomRouteHandler, type CustomRoutePhase, type FaviconHandler, type GeneratedRuntimeMapsModule, OVERLAY_ORDER, type OnLocationUpdatePayload, type OnSocketConnectPayload, type OnSocketDisconnectPayload, type OriginExemptMatcher, type ParsedServerArgv, type PostRoomJoinPayload, type PostRoomLeavePayload, type PreRoomJoinPayload, type PreRoomLeavePayload, type ProdRuntimeMapsLoaderOptions, type RegisterCustomRouteOptions, type RouteContext, type RunningLuckyStackServer, type SecurityHeadersBuilder, type StaticFileHandler, type StopLuckyStackServerOptions, applyServerArgv, bootstrapLuckyStack, clearCustomRoutes, clearOriginExemptPaths, createLuckyStackServer, createProdRuntimeMapsProvider, getCustomRoutes, getOriginExemptPaths, getParsedBundles, getParsedPort, getPreParamsCustomRoutes, getSecurityHeadersBuilder, isOriginExemptPath, parseServerArgv, registerCustomRoute, registerOriginExemptPath, registerOverlayLoader, registerProdRuntimeMapsProvider, registerSecurityHeaders, verifyBootstrap };
|
|
328
|
+
export { type BootstrapLuckyStackOptions, type BootstrapRequirements, type CreateLuckyStackServerOptions, type CustomRouteHandler, type CustomRoutePhase, type FaviconHandler, type GeneratedRuntimeMapsModule, OVERLAY_ORDER, type OnLocationUpdatePayload, type OnSocketConnectPayload, type OnSocketDisconnectPayload, type OriginExemptMatcher, type OverlayEntries, type ParsedServerArgv, type PostRoomJoinPayload, type PostRoomLeavePayload, type PreRoomJoinPayload, type PreRoomLeavePayload, type ProdRuntimeMapsLoaderOptions, type RegisterCustomRouteOptions, type RouteContext, type RunningLuckyStackServer, type SecurityHeadersBuilder, type StaticFileHandler, type StopLuckyStackServerOptions, applyServerArgv, bootstrapLuckyStack, clearCustomRoutes, clearOriginExemptPaths, collectOverlayEntries, createLuckyStackServer, createProdRuntimeMapsProvider, getCustomRoutes, getOriginExemptPaths, getParsedBundles, getParsedPort, getPreParamsCustomRoutes, getSecurityHeadersBuilder, isOriginExemptPath, parseServerArgv, registerCustomRoute, registerOriginExemptPath, registerOverlayLoader, registerProdRuntimeMapsProvider, registerSecurityHeaders, verifyBootstrap };
|
package/dist/index.js
CHANGED
|
@@ -2156,7 +2156,7 @@ var areEquivalentFunctionEntries = (left, right) => {
|
|
|
2156
2156
|
if (!isRuntimeMapRecord(left) || !isRuntimeMapRecord(right)) return false;
|
|
2157
2157
|
const leftKeys = Object.keys(left);
|
|
2158
2158
|
const rightKeys = Object.keys(right);
|
|
2159
|
-
return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(right, key) &&
|
|
2159
|
+
return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(right, key) && areEquivalentFunctionEntries(left[key], right[key]));
|
|
2160
2160
|
};
|
|
2161
2161
|
var mergeInto = (target, source, kind, fromPreset, keyOrigin) => {
|
|
2162
2162
|
for (const key of Object.keys(source)) {
|
|
@@ -2520,7 +2520,7 @@ var createLuckyStackServer = async (options = {}) => {
|
|
|
2520
2520
|
import path3 from "path";
|
|
2521
2521
|
import fs2 from "fs";
|
|
2522
2522
|
import { pathToFileURL } from "url";
|
|
2523
|
-
import { ROOT_DIR, tryCatch as tryCatch11 } from "@luckystack/core";
|
|
2523
|
+
import { ROOT_DIR, tryCatch as tryCatch11, getLogger as getLogger14, isTestFile, isTestDirectory } from "@luckystack/core";
|
|
2524
2524
|
var OVERLAY_ORDER = [
|
|
2525
2525
|
// Core registries first — clients, paths, routing rules. Anything below
|
|
2526
2526
|
// depends on these being in place.
|
|
@@ -2558,6 +2558,24 @@ var registeredOverlayLoader = null;
|
|
|
2558
2558
|
var registerOverlayLoader = (loader) => {
|
|
2559
2559
|
registeredOverlayLoader = loader;
|
|
2560
2560
|
};
|
|
2561
|
+
var collectOverlayEntries = (packageDir) => {
|
|
2562
|
+
const indexCandidates = ["index.ts", "index.js"];
|
|
2563
|
+
const entries = fs2.readdirSync(packageDir, { withFileTypes: true }).toSorted((left, right) => left.name.localeCompare(right.name));
|
|
2564
|
+
const ignoredDirectoryNames = [];
|
|
2565
|
+
const rest = [];
|
|
2566
|
+
for (const entry of entries) {
|
|
2567
|
+
if (entry.isDirectory()) {
|
|
2568
|
+
if (!isTestDirectory(entry.name)) ignoredDirectoryNames.push(entry.name);
|
|
2569
|
+
continue;
|
|
2570
|
+
}
|
|
2571
|
+
if (indexCandidates.includes(entry.name)) continue;
|
|
2572
|
+
if (!entry.name.endsWith(".ts") && !entry.name.endsWith(".js")) continue;
|
|
2573
|
+
if (isTestFile(entry.name)) continue;
|
|
2574
|
+
rest.push(entry.name);
|
|
2575
|
+
}
|
|
2576
|
+
const indexFileNames = indexCandidates.filter((candidate) => entries.some((entry) => entry.isFile() && entry.name === candidate));
|
|
2577
|
+
return { fileNames: [...indexFileNames, ...rest], ignoredDirectoryNames };
|
|
2578
|
+
};
|
|
2561
2579
|
var loadOverlayFolder = async (overlayRoot) => {
|
|
2562
2580
|
const overlayAbs = path3.isAbsolute(overlayRoot) ? overlayRoot : path3.join(ROOT_DIR, overlayRoot);
|
|
2563
2581
|
if (!fs2.existsSync(overlayAbs)) {
|
|
@@ -2566,15 +2584,14 @@ var loadOverlayFolder = async (overlayRoot) => {
|
|
|
2566
2584
|
for (const packageName of OVERLAY_ORDER) {
|
|
2567
2585
|
const packageDir = path3.join(overlayAbs, packageName);
|
|
2568
2586
|
if (!fs2.existsSync(packageDir)) continue;
|
|
2569
|
-
const
|
|
2570
|
-
for (const
|
|
2571
|
-
|
|
2587
|
+
const { fileNames, ignoredDirectoryNames } = collectOverlayEntries(packageDir);
|
|
2588
|
+
for (const directoryName of ignoredDirectoryNames) {
|
|
2589
|
+
getLogger14().warn(
|
|
2590
|
+
`[luckystack:overlay] ignoring \`${overlayRoot}/${packageName}/${directoryName}/\` \u2014 the overlay walk does not recurse. Move the file(s) up into \`${overlayRoot}/${packageName}/\`, or import them from a file that is there.`
|
|
2591
|
+
);
|
|
2572
2592
|
}
|
|
2573
|
-
const
|
|
2574
|
-
|
|
2575
|
-
if (indexCandidates.includes(entry)) continue;
|
|
2576
|
-
if (!entry.endsWith(".ts") && !entry.endsWith(".js")) continue;
|
|
2577
|
-
await importIfExists(path3.join(packageDir, entry));
|
|
2593
|
+
for (const fileName of fileNames) {
|
|
2594
|
+
await importIfExists(path3.join(packageDir, fileName));
|
|
2578
2595
|
}
|
|
2579
2596
|
}
|
|
2580
2597
|
};
|
|
@@ -2614,6 +2631,7 @@ export {
|
|
|
2614
2631
|
bootstrapLuckyStack,
|
|
2615
2632
|
clearCustomRoutes,
|
|
2616
2633
|
clearOriginExemptPaths,
|
|
2634
|
+
collectOverlayEntries,
|
|
2617
2635
|
createLuckyStackServer,
|
|
2618
2636
|
createProdRuntimeMapsProvider,
|
|
2619
2637
|
getCustomRoutes,
|