meno-core 1.0.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/bin/cli.ts +281 -0
- package/build-static.ts +298 -0
- package/bunfig.toml +39 -0
- package/entries/client-router.tsx +111 -0
- package/entries/server-router.tsx +71 -0
- package/lib/client/ClientInitializer.test.ts +9 -0
- package/lib/client/ClientInitializer.test.ts.skip +92 -0
- package/lib/client/ClientInitializer.ts +60 -0
- package/lib/client/ErrorBoundary.test.tsx +595 -0
- package/lib/client/ErrorBoundary.tsx +230 -0
- package/lib/client/componentRegistry.test.ts +165 -0
- package/lib/client/componentRegistry.ts +18 -0
- package/lib/client/contexts/ThemeContext.tsx +73 -0
- package/lib/client/core/ComponentBuilder.test.ts +677 -0
- package/lib/client/core/ComponentBuilder.ts +660 -0
- package/lib/client/core/ComponentRenderer.test.tsx +176 -0
- package/lib/client/core/ComponentRenderer.tsx +83 -0
- package/lib/client/core/cmsTemplateProcessor.ts +129 -0
- package/lib/client/elementRegistry.ts +81 -0
- package/lib/client/hmr/HMRManager.tsx +179 -0
- package/lib/client/hmr/index.ts +5 -0
- package/lib/client/hmrWebSocket.test.ts +9 -0
- package/lib/client/hmrWebSocket.ts +250 -0
- package/lib/client/hooks/useColorVariables.test.ts +166 -0
- package/lib/client/hooks/useColorVariables.ts +249 -0
- package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
- package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
- package/lib/client/hydration/HydrationUtils.test.ts +154 -0
- package/lib/client/hydration/HydrationUtils.ts +35 -0
- package/lib/client/i18nConfigService.test.ts +74 -0
- package/lib/client/i18nConfigService.ts +78 -0
- package/lib/client/index.ts +56 -0
- package/lib/client/navigation.test.ts +441 -0
- package/lib/client/navigation.ts +23 -0
- package/lib/client/responsiveStyleResolver.test.ts +491 -0
- package/lib/client/responsiveStyleResolver.ts +184 -0
- package/lib/client/routing/RouteLoader.test.ts +635 -0
- package/lib/client/routing/RouteLoader.ts +347 -0
- package/lib/client/routing/Router.tsx +382 -0
- package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
- package/lib/client/scripts/ScriptExecutor.ts +171 -0
- package/lib/client/scripts/formHandler.ts +103 -0
- package/lib/client/styleProcessor.test.ts +126 -0
- package/lib/client/styleProcessor.ts +92 -0
- package/lib/client/styles/StyleInjector.test.ts +354 -0
- package/lib/client/styles/StyleInjector.ts +154 -0
- package/lib/client/templateEngine.test.ts +660 -0
- package/lib/client/templateEngine.ts +667 -0
- package/lib/client/theme.test.ts +173 -0
- package/lib/client/theme.ts +159 -0
- package/lib/client/utils/toast.ts +46 -0
- package/lib/server/createServer.ts +170 -0
- package/lib/server/cssGenerator.test.ts +172 -0
- package/lib/server/cssGenerator.ts +58 -0
- package/lib/server/fileWatcher.ts +134 -0
- package/lib/server/index.ts +55 -0
- package/lib/server/jsonLoader.test.ts +103 -0
- package/lib/server/jsonLoader.ts +350 -0
- package/lib/server/middleware/cors.test.ts +177 -0
- package/lib/server/middleware/cors.ts +69 -0
- package/lib/server/middleware/errorHandler.test.ts +208 -0
- package/lib/server/middleware/errorHandler.ts +63 -0
- package/lib/server/middleware/index.ts +9 -0
- package/lib/server/middleware/logger.test.ts +233 -0
- package/lib/server/middleware/logger.ts +99 -0
- package/lib/server/pageCache.test.ts +167 -0
- package/lib/server/pageCache.ts +97 -0
- package/lib/server/projectContext.ts +51 -0
- package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
- package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
- package/lib/server/providers/fileSystemPageProvider.ts +83 -0
- package/lib/server/routes/api/cms.test.ts +177 -0
- package/lib/server/routes/api/cms.ts +82 -0
- package/lib/server/routes/api/colors.ts +59 -0
- package/lib/server/routes/api/components.ts +70 -0
- package/lib/server/routes/api/config.test.ts +9 -0
- package/lib/server/routes/api/config.ts +28 -0
- package/lib/server/routes/api/core-routes.ts +182 -0
- package/lib/server/routes/api/functions.ts +170 -0
- package/lib/server/routes/api/index.ts +69 -0
- package/lib/server/routes/api/pages.ts +95 -0
- package/lib/server/routes/api/shared.test.ts +81 -0
- package/lib/server/routes/api/shared.ts +31 -0
- package/lib/server/routes/editor.test.ts +9 -0
- package/lib/server/routes/index.ts +104 -0
- package/lib/server/routes/pages.ts +161 -0
- package/lib/server/routes/static.ts +107 -0
- package/lib/server/services/ColorService.ts +193 -0
- package/lib/server/services/cmsService.test.ts +388 -0
- package/lib/server/services/cmsService.ts +296 -0
- package/lib/server/services/componentService.test.ts +276 -0
- package/lib/server/services/componentService.ts +346 -0
- package/lib/server/services/configService.ts +156 -0
- package/lib/server/services/fileWatcherService.ts +67 -0
- package/lib/server/services/index.ts +10 -0
- package/lib/server/services/pageService.test.ts +258 -0
- package/lib/server/services/pageService.ts +240 -0
- package/lib/server/ssrRenderer.test.ts +1005 -0
- package/lib/server/ssrRenderer.ts +878 -0
- package/lib/server/utilityClassGenerator.ts +11 -0
- package/lib/server/utils/index.ts +5 -0
- package/lib/server/utils/jsonLineMapper.test.ts +100 -0
- package/lib/server/utils/jsonLineMapper.ts +166 -0
- package/lib/server/validateStyleCoverage.test.ts +9 -0
- package/lib/server/validateStyleCoverage.ts +167 -0
- package/lib/server/websocketManager.test.ts +9 -0
- package/lib/server/websocketManager.ts +95 -0
- package/lib/shared/attributeNodeUtils.test.ts +152 -0
- package/lib/shared/attributeNodeUtils.ts +50 -0
- package/lib/shared/breakpoints.test.ts +166 -0
- package/lib/shared/breakpoints.ts +65 -0
- package/lib/shared/colorProperties.test.ts +111 -0
- package/lib/shared/colorProperties.ts +40 -0
- package/lib/shared/colorVariableUtils.test.ts +319 -0
- package/lib/shared/colorVariableUtils.ts +97 -0
- package/lib/shared/constants.test.ts +175 -0
- package/lib/shared/constants.ts +116 -0
- package/lib/shared/cssGeneration.ts +481 -0
- package/lib/shared/cssProperties.test.ts +252 -0
- package/lib/shared/cssProperties.ts +338 -0
- package/lib/shared/elementUtils.test.ts +245 -0
- package/lib/shared/elementUtils.ts +90 -0
- package/lib/shared/fontLoader.ts +97 -0
- package/lib/shared/i18n.test.ts +313 -0
- package/lib/shared/i18n.ts +286 -0
- package/lib/shared/index.ts +50 -0
- package/lib/shared/interfaces/contentProvider.test.ts +9 -0
- package/lib/shared/interfaces/contentProvider.ts +121 -0
- package/lib/shared/nodeUtils.test.ts +320 -0
- package/lib/shared/nodeUtils.ts +220 -0
- package/lib/shared/pathArrayUtils.test.ts +315 -0
- package/lib/shared/pathArrayUtils.ts +17 -0
- package/lib/shared/pathUtils.test.ts +260 -0
- package/lib/shared/pathUtils.ts +244 -0
- package/lib/shared/paths/Path.test.ts +74 -0
- package/lib/shared/paths/Path.ts +23 -0
- package/lib/shared/paths/PathConverter.test.ts +232 -0
- package/lib/shared/paths/PathConverter.ts +141 -0
- package/lib/shared/paths/PathUtils.ts +290 -0
- package/lib/shared/paths/PathValidator.test.ts +193 -0
- package/lib/shared/paths/PathValidator.ts +53 -0
- package/lib/shared/paths/index.ts +48 -0
- package/lib/shared/propResolver.test.ts +639 -0
- package/lib/shared/propResolver.ts +124 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
- package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
- package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
- package/lib/shared/registry/ClientRegistry.test.ts +26 -0
- package/lib/shared/registry/ClientRegistry.ts +15 -0
- package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
- package/lib/shared/registry/ComponentRegistry.ts +100 -0
- package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
- package/lib/shared/registry/NodeTypeManager.ts +94 -0
- package/lib/shared/registry/RegistryManager.test.ts +58 -0
- package/lib/shared/registry/RegistryManager.ts +60 -0
- package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
- package/lib/shared/registry/SSRRegistry.test.ts +26 -0
- package/lib/shared/registry/SSRRegistry.ts +15 -0
- package/lib/shared/registry/createNodeType.ts +175 -0
- package/lib/shared/registry/defineNodeType.ts +73 -0
- package/lib/shared/registry/fieldPresets.ts +109 -0
- package/lib/shared/registry/index.ts +50 -0
- package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
- package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
- package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
- package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
- package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
- package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
- package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
- package/lib/shared/registry/nodeTypes/index.ts +75 -0
- package/lib/shared/responsiveScaling.test.ts +268 -0
- package/lib/shared/responsiveScaling.ts +194 -0
- package/lib/shared/responsiveStyleUtils.test.ts +300 -0
- package/lib/shared/responsiveStyleUtils.ts +139 -0
- package/lib/shared/slugTranslator.test.ts +325 -0
- package/lib/shared/slugTranslator.ts +177 -0
- package/lib/shared/styleNodeUtils.test.ts +132 -0
- package/lib/shared/styleNodeUtils.ts +102 -0
- package/lib/shared/styleUtils.test.ts +238 -0
- package/lib/shared/styleUtils.ts +63 -0
- package/lib/shared/themeDefaults.test.ts +113 -0
- package/lib/shared/themeDefaults.ts +103 -0
- package/lib/shared/tree/PathBuilder.ts +383 -0
- package/lib/shared/treePathUtils.test.ts +539 -0
- package/lib/shared/treePathUtils.ts +339 -0
- package/lib/shared/types/api.ts +58 -0
- package/lib/shared/types/cms.ts +95 -0
- package/lib/shared/types/colors.ts +45 -0
- package/lib/shared/types/components.ts +121 -0
- package/lib/shared/types/errors.test.ts +103 -0
- package/lib/shared/types/errors.ts +69 -0
- package/lib/shared/types/index.ts +96 -0
- package/lib/shared/types/nodes.ts +20 -0
- package/lib/shared/types/rendering.ts +61 -0
- package/lib/shared/types/styles.ts +38 -0
- package/lib/shared/types.ts +11 -0
- package/lib/shared/utilityClassConfig.ts +287 -0
- package/lib/shared/utilityClassMapper.test.ts +140 -0
- package/lib/shared/utilityClassMapper.ts +229 -0
- package/lib/shared/utils/fileUtils.test.ts +99 -0
- package/lib/shared/utils/fileUtils.ts +56 -0
- package/lib/shared/utils.test.ts +261 -0
- package/lib/shared/utils.ts +84 -0
- package/lib/shared/validation/index.ts +7 -0
- package/lib/shared/validation/propValidator.test.ts +178 -0
- package/lib/shared/validation/propValidator.ts +238 -0
- package/lib/shared/validation/schemas.test.ts +177 -0
- package/lib/shared/validation/schemas.ts +401 -0
- package/lib/shared/validation/validators.test.ts +109 -0
- package/lib/shared/validation/validators.ts +304 -0
- package/lib/test-utils/dom-setup.ts +55 -0
- package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
- package/lib/test-utils/factories/DomMockFactory.ts +487 -0
- package/lib/test-utils/factories/EventMockFactory.ts +244 -0
- package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
- package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
- package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
- package/lib/test-utils/factories/index.ts +11 -0
- package/lib/test-utils/fixtures.ts +134 -0
- package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
- package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
- package/lib/test-utils/helpers/index.ts +6 -0
- package/lib/test-utils/helpers.test.ts +73 -0
- package/lib/test-utils/helpers.ts +90 -0
- package/lib/test-utils/index.ts +17 -0
- package/lib/test-utils/mockFactories.ts +92 -0
- package/lib/test-utils/mocks.ts +341 -0
- package/package.json +38 -0
- package/templates/index-router.html +34 -0
- package/tsconfig.json +14 -0
- package/vite.config.ts +43 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Service
|
|
3
|
+
* Centralized configuration loading and access
|
|
4
|
+
*
|
|
5
|
+
* Consolidates multiple config loaders into a single service that loads
|
|
6
|
+
* the project.config.json file once and exposes typed sections.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { BreakpointConfig } from '../../shared/breakpoints';
|
|
10
|
+
import { DEFAULT_BREAKPOINTS } from '../../shared/breakpoints';
|
|
11
|
+
import type { ResponsiveScales } from '../../shared/responsiveScaling';
|
|
12
|
+
import { DEFAULT_RESPONSIVE_SCALES } from '../../shared/responsiveScaling';
|
|
13
|
+
import type { I18nConfig } from '../../shared/types/components';
|
|
14
|
+
import { DEFAULT_I18N_CONFIG, migrateI18nConfig } from '../../shared/i18n';
|
|
15
|
+
import { projectPaths } from '../projectContext';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Icons configuration
|
|
19
|
+
*/
|
|
20
|
+
export interface IconsConfig {
|
|
21
|
+
favicon?: string;
|
|
22
|
+
appleTouchIcon?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Raw project config structure from project.config.json
|
|
27
|
+
*/
|
|
28
|
+
interface RawProjectConfig {
|
|
29
|
+
breakpoints?: Record<string, number>;
|
|
30
|
+
responsiveScales?: Partial<ResponsiveScales>;
|
|
31
|
+
i18n?: unknown;
|
|
32
|
+
icons?: IconsConfig;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* ConfigService
|
|
37
|
+
* Loads project configuration once and provides typed access to sections
|
|
38
|
+
*/
|
|
39
|
+
export class ConfigService {
|
|
40
|
+
private config: RawProjectConfig | null = null;
|
|
41
|
+
private loaded = false;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load configuration from project.config.json
|
|
45
|
+
* Safe to call multiple times - only loads once
|
|
46
|
+
*/
|
|
47
|
+
async load(): Promise<void> {
|
|
48
|
+
if (this.loaded) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const file = Bun.file(projectPaths.config());
|
|
54
|
+
if (await file.exists()) {
|
|
55
|
+
const content = await file.text();
|
|
56
|
+
this.config = JSON.parse(content);
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// Fall through to defaults
|
|
60
|
+
this.config = null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.loaded = true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if configuration has been loaded
|
|
68
|
+
*/
|
|
69
|
+
isLoaded(): boolean {
|
|
70
|
+
return this.loaded;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Reset the service (for testing)
|
|
75
|
+
*/
|
|
76
|
+
reset(): void {
|
|
77
|
+
this.config = null;
|
|
78
|
+
this.loaded = false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get breakpoint configuration
|
|
83
|
+
* Returns validated breakpoints or defaults
|
|
84
|
+
*/
|
|
85
|
+
getBreakpoints(): BreakpointConfig {
|
|
86
|
+
if (!this.config?.breakpoints || typeof this.config.breakpoints !== 'object') {
|
|
87
|
+
return { ...DEFAULT_BREAKPOINTS };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Validate breakpoint values
|
|
91
|
+
const breakpoints: BreakpointConfig = {};
|
|
92
|
+
for (const [key, value] of Object.entries(this.config.breakpoints)) {
|
|
93
|
+
if (typeof value === 'number' && value > 0) {
|
|
94
|
+
breakpoints[key] = value;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Return validated breakpoints or defaults if none valid
|
|
99
|
+
return Object.keys(breakpoints).length > 0 ? breakpoints : { ...DEFAULT_BREAKPOINTS };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get i18n configuration
|
|
104
|
+
* Automatically migrates old string[] format to LocaleConfig[] format
|
|
105
|
+
*/
|
|
106
|
+
getI18n(): I18nConfig {
|
|
107
|
+
if (!this.config?.i18n) {
|
|
108
|
+
return { ...DEFAULT_I18N_CONFIG };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return migrateI18nConfig(this.config.i18n);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get responsive scales configuration
|
|
116
|
+
* Merges with defaults for any missing values
|
|
117
|
+
*/
|
|
118
|
+
getResponsiveScales(): ResponsiveScales {
|
|
119
|
+
if (!this.config?.responsiveScales || typeof this.config.responsiveScales !== 'object') {
|
|
120
|
+
return { ...DEFAULT_RESPONSIVE_SCALES };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
...DEFAULT_RESPONSIVE_SCALES,
|
|
125
|
+
...this.config.responsiveScales,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get icons configuration
|
|
131
|
+
* Returns empty object if not configured
|
|
132
|
+
*/
|
|
133
|
+
getIcons(): IconsConfig {
|
|
134
|
+
if (!this.config?.icons || typeof this.config.icons !== 'object') {
|
|
135
|
+
return {};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return this.config.icons;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get raw config value by key (for extension)
|
|
143
|
+
*/
|
|
144
|
+
getRaw<T>(key: string): T | undefined {
|
|
145
|
+
if (!this.config) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
return (this.config as Record<string, unknown>)[key] as T | undefined;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Singleton instance for global access
|
|
154
|
+
* Use this for convenience, or create your own instance for testing
|
|
155
|
+
*/
|
|
156
|
+
export const configService = new ConfigService();
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Watcher Service
|
|
3
|
+
* Manages file watching for pages and components
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { FileWatcher, type FileWatchCallbacks } from '../fileWatcher';
|
|
8
|
+
import { WebSocketManager } from '../websocketManager';
|
|
9
|
+
import { ComponentService } from './componentService';
|
|
10
|
+
import { PageService } from './pageService';
|
|
11
|
+
import { PageCache } from '../pageCache';
|
|
12
|
+
import { colorService } from './ColorService';
|
|
13
|
+
import { loadJSONFile, mapPageNameToPath } from '../jsonLoader';
|
|
14
|
+
import { projectPaths } from '../projectContext';
|
|
15
|
+
|
|
16
|
+
export class FileWatcherService {
|
|
17
|
+
private fileWatcher: FileWatcher | null = null;
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
private componentService: ComponentService,
|
|
21
|
+
private pageService: PageService,
|
|
22
|
+
private pageCache: PageCache,
|
|
23
|
+
private wsManager: WebSocketManager
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initialize file watchers
|
|
28
|
+
*/
|
|
29
|
+
initialize(): void {
|
|
30
|
+
this.fileWatcher = new FileWatcher({
|
|
31
|
+
onComponentChange: async () => {
|
|
32
|
+
await this.componentService.loadAllComponents();
|
|
33
|
+
this.wsManager.broadcastUpdate('all');
|
|
34
|
+
},
|
|
35
|
+
onPageChange: async (pagePath: string) => {
|
|
36
|
+
const pageName = pagePath === '/' ? 'index' : pagePath.substring(1);
|
|
37
|
+
const content = await loadJSONFile(join(projectPaths.pages(), `${pageName}.json`));
|
|
38
|
+
if (content) {
|
|
39
|
+
this.pageCache.set(pagePath, content);
|
|
40
|
+
this.wsManager.broadcastUpdate(pagePath);
|
|
41
|
+
} else {
|
|
42
|
+
this.pageCache.delete(pagePath);
|
|
43
|
+
this.wsManager.broadcastUpdate(pagePath);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
onColorsChange: async () => {
|
|
47
|
+
// Clear the color service cache so it reloads on next request
|
|
48
|
+
colorService.clearCache();
|
|
49
|
+
// Broadcast to all clients to refresh their color data
|
|
50
|
+
this.wsManager.broadcastColorsUpdate();
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
this.fileWatcher.watchAll();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Stop file watchers
|
|
59
|
+
*/
|
|
60
|
+
stop(): void {
|
|
61
|
+
if (this.fileWatcher) {
|
|
62
|
+
this.fileWatcher.stopAll();
|
|
63
|
+
this.fileWatcher = null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Services
|
|
3
|
+
* Centralized services for server operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { PageService } from './pageService';
|
|
7
|
+
export { ComponentService } from './componentService';
|
|
8
|
+
export { FileWatcherService } from './fileWatcherService';
|
|
9
|
+
export { CMSService } from './cmsService';
|
|
10
|
+
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { test, expect, describe, beforeEach, afterEach, mock } from 'bun:test';
|
|
2
|
+
import { PageService } from './pageService';
|
|
3
|
+
import { PageCache } from '../pageCache';
|
|
4
|
+
import { createMockPageData } from '../../test-utils';
|
|
5
|
+
import { createTypedMockPageProvider, type TypedMockPageProvider } from '../../test-utils/factories/ServerMockFactory';
|
|
6
|
+
|
|
7
|
+
describe('PageService', () => {
|
|
8
|
+
let pageCache: PageCache;
|
|
9
|
+
let pageService: PageService;
|
|
10
|
+
let mockProvider: TypedMockPageProvider;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
pageCache = new PageCache();
|
|
14
|
+
mockProvider = createTypedMockPageProvider();
|
|
15
|
+
pageService = new PageService(pageCache, mockProvider);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
pageCache.clear();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('loadAllPages', () => {
|
|
23
|
+
test('should load all pages from provider', async () => {
|
|
24
|
+
const indexData = createMockPageData({ meta: { title: 'Home' } });
|
|
25
|
+
const aboutData = createMockPageData({ meta: { title: 'About' } });
|
|
26
|
+
|
|
27
|
+
// Set up provider with pages
|
|
28
|
+
mockProvider._pages['/'] = JSON.stringify(indexData);
|
|
29
|
+
mockProvider._pages['/about'] = JSON.stringify(aboutData);
|
|
30
|
+
|
|
31
|
+
await pageService.loadAllPages();
|
|
32
|
+
|
|
33
|
+
const indexPage = pageService.getPage('/');
|
|
34
|
+
const aboutPage = pageService.getPage('/about');
|
|
35
|
+
|
|
36
|
+
expect(indexPage).toBeDefined();
|
|
37
|
+
expect(aboutPage).toBeDefined();
|
|
38
|
+
expect(mockProvider.loadAll).toHaveBeenCalledTimes(1);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('should handle empty provider gracefully', async () => {
|
|
42
|
+
await pageService.loadAllPages();
|
|
43
|
+
|
|
44
|
+
expect(pageService.getAllPagePaths()).toEqual([]);
|
|
45
|
+
expect(mockProvider.loadAll).toHaveBeenCalled();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('should work without provider set', async () => {
|
|
49
|
+
const serviceWithoutProvider = new PageService(pageCache);
|
|
50
|
+
|
|
51
|
+
// Should not throw
|
|
52
|
+
await serviceWithoutProvider.loadAllPages();
|
|
53
|
+
|
|
54
|
+
expect(serviceWithoutProvider.getAllPagePaths()).toEqual([]);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('getPage', () => {
|
|
59
|
+
test('should return page content from cache', () => {
|
|
60
|
+
const testData = createMockPageData();
|
|
61
|
+
pageCache.set('/', JSON.stringify(testData), new Map());
|
|
62
|
+
|
|
63
|
+
const result = pageService.getPage('/');
|
|
64
|
+
|
|
65
|
+
expect(result).toBe(JSON.stringify(testData));
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('should return undefined for non-existent page', () => {
|
|
69
|
+
const result = pageService.getPage('/non-existent');
|
|
70
|
+
|
|
71
|
+
expect(result).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('getPageData', () => {
|
|
76
|
+
test('should return parsed page data', () => {
|
|
77
|
+
const testData = createMockPageData();
|
|
78
|
+
pageCache.set('/', JSON.stringify(testData), new Map());
|
|
79
|
+
|
|
80
|
+
const result = pageService.getPageData('/');
|
|
81
|
+
|
|
82
|
+
expect(result).toBeDefined();
|
|
83
|
+
expect(result?.meta?.title).toBe(testData.meta?.title);
|
|
84
|
+
expect(result?.root).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('should return null for non-existent page', () => {
|
|
88
|
+
const result = pageService.getPageData('/non-existent');
|
|
89
|
+
|
|
90
|
+
expect(result).toBeNull();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('should return null for invalid JSON', () => {
|
|
94
|
+
pageCache.set('/invalid', 'invalid json', new Map());
|
|
95
|
+
|
|
96
|
+
const result = pageService.getPageData('/invalid');
|
|
97
|
+
|
|
98
|
+
expect(result).toBeNull();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('savePage', () => {
|
|
103
|
+
test('should save page via provider', async () => {
|
|
104
|
+
const testData = createMockPageData({ meta: { title: 'Test Page' } });
|
|
105
|
+
|
|
106
|
+
await pageService.savePage('/', testData);
|
|
107
|
+
|
|
108
|
+
expect(mockProvider.save).toHaveBeenCalledWith('/', expect.any(String));
|
|
109
|
+
expect(pageService.getPage('/')).toBeDefined();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('should update cache after saving', async () => {
|
|
113
|
+
const testData = createMockPageData({ meta: { title: 'Updated Page' } });
|
|
114
|
+
|
|
115
|
+
await pageService.savePage('/', testData);
|
|
116
|
+
|
|
117
|
+
const cached = pageService.getPageData('/');
|
|
118
|
+
expect(cached?.meta?.title).toBe('Updated Page');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('should throw error when provider not set', async () => {
|
|
122
|
+
const serviceWithoutProvider = new PageService(pageCache);
|
|
123
|
+
const testData = createMockPageData();
|
|
124
|
+
|
|
125
|
+
await expect(serviceWithoutProvider.savePage('/', testData)).rejects.toThrow(
|
|
126
|
+
'PageProvider not set'
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('should strip _lineMap from saved data', async () => {
|
|
131
|
+
const testData = createMockPageData({ meta: { title: 'Test' } });
|
|
132
|
+
(testData as any)._lineMap = new Map();
|
|
133
|
+
|
|
134
|
+
await pageService.savePage('/', testData);
|
|
135
|
+
|
|
136
|
+
const savedContent = (mockProvider.save as any).mock.calls[0][1];
|
|
137
|
+
const savedData = JSON.parse(savedContent);
|
|
138
|
+
expect(savedData._lineMap).toBeUndefined();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('deletePage', () => {
|
|
143
|
+
test('should delete page from cache', async () => {
|
|
144
|
+
const testData = createMockPageData();
|
|
145
|
+
pageCache.set('/', JSON.stringify(testData), new Map());
|
|
146
|
+
|
|
147
|
+
await pageService.deletePage('/');
|
|
148
|
+
|
|
149
|
+
expect(pageService.getPage('/')).toBeUndefined();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('should delete from storage when deleteFromStorage is true', async () => {
|
|
153
|
+
const testData = createMockPageData();
|
|
154
|
+
pageCache.set('/', JSON.stringify(testData), new Map());
|
|
155
|
+
|
|
156
|
+
await pageService.deletePage('/', true);
|
|
157
|
+
|
|
158
|
+
expect(mockProvider.delete).toHaveBeenCalledWith('/');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('should not delete from storage when deleteFromStorage is false', async () => {
|
|
162
|
+
const testData = createMockPageData();
|
|
163
|
+
pageCache.set('/', JSON.stringify(testData), new Map());
|
|
164
|
+
|
|
165
|
+
await pageService.deletePage('/');
|
|
166
|
+
|
|
167
|
+
expect(mockProvider.delete).not.toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('getAllPagePaths', () => {
|
|
172
|
+
test('should return all page paths from cache', () => {
|
|
173
|
+
pageCache.set('/', JSON.stringify(createMockPageData()), new Map());
|
|
174
|
+
pageCache.set('/about', JSON.stringify(createMockPageData()), new Map());
|
|
175
|
+
|
|
176
|
+
const paths = pageService.getAllPagePaths();
|
|
177
|
+
|
|
178
|
+
expect(paths).toContain('/');
|
|
179
|
+
expect(paths).toContain('/about');
|
|
180
|
+
expect(paths.length).toBe(2);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('should return empty array when cache is empty', () => {
|
|
184
|
+
const paths = pageService.getAllPagePaths();
|
|
185
|
+
|
|
186
|
+
expect(paths).toEqual([]);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('getLineMap', () => {
|
|
191
|
+
test('should return line map from cache', () => {
|
|
192
|
+
const lineMap = new Map([['root', { startLine: 1, endLine: 10 }]]);
|
|
193
|
+
pageCache.set('/', JSON.stringify(createMockPageData()), lineMap);
|
|
194
|
+
|
|
195
|
+
const result = pageService.getLineMap('/');
|
|
196
|
+
|
|
197
|
+
expect(result).toBeDefined();
|
|
198
|
+
expect(result?.get('root')).toEqual({ startLine: 1, endLine: 10 });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('should return undefined for non-existent page', () => {
|
|
202
|
+
const result = pageService.getLineMap('/non-existent');
|
|
203
|
+
|
|
204
|
+
expect(result).toBeUndefined();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('setProvider', () => {
|
|
209
|
+
test('should allow setting provider after construction', async () => {
|
|
210
|
+
const serviceWithoutProvider = new PageService(pageCache);
|
|
211
|
+
const newProvider = createTypedMockPageProvider({
|
|
212
|
+
'/': JSON.stringify(createMockPageData()),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
serviceWithoutProvider.setProvider(newProvider);
|
|
216
|
+
await serviceWithoutProvider.loadAllPages();
|
|
217
|
+
|
|
218
|
+
expect(serviceWithoutProvider.getPage('/')).toBeDefined();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe('getSlugMappings', () => {
|
|
223
|
+
test('should return slug mappings from pages with slugs', () => {
|
|
224
|
+
const pageWithSlugs = createMockPageData({
|
|
225
|
+
meta: { title: 'About', slugs: { en: 'about', pl: 'o-nas' } },
|
|
226
|
+
});
|
|
227
|
+
pageCache.set('/about', JSON.stringify(pageWithSlugs), new Map());
|
|
228
|
+
|
|
229
|
+
const mappings = pageService.getSlugMappings();
|
|
230
|
+
|
|
231
|
+
expect(mappings).toHaveLength(1);
|
|
232
|
+
expect(mappings[0].pageId).toBe('about');
|
|
233
|
+
expect(mappings[0].slugs).toEqual({ en: 'about', pl: 'o-nas' });
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('should create default slug for pages without explicit slugs', () => {
|
|
237
|
+
const pageWithoutSlugs = createMockPageData({ meta: { title: 'About' } });
|
|
238
|
+
pageCache.set('/about', JSON.stringify(pageWithoutSlugs), new Map());
|
|
239
|
+
|
|
240
|
+
const mappings = pageService.getSlugMappings();
|
|
241
|
+
|
|
242
|
+
expect(mappings).toHaveLength(1);
|
|
243
|
+
expect(mappings[0].pageId).toBe('about');
|
|
244
|
+
expect(mappings[0].slugs).toEqual({ _default: 'about' });
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('should handle index page correctly', () => {
|
|
248
|
+
const indexPage = createMockPageData({ meta: { title: 'Home' } });
|
|
249
|
+
pageCache.set('/', JSON.stringify(indexPage), new Map());
|
|
250
|
+
|
|
251
|
+
const mappings = pageService.getSlugMappings();
|
|
252
|
+
|
|
253
|
+
expect(mappings).toHaveLength(1);
|
|
254
|
+
expect(mappings[0].pageId).toBe('index');
|
|
255
|
+
expect(mappings[0].slugs).toEqual({ _default: '' });
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
});
|