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.
Files changed (231) hide show
  1. package/bin/cli.ts +281 -0
  2. package/build-static.ts +298 -0
  3. package/bunfig.toml +39 -0
  4. package/entries/client-router.tsx +111 -0
  5. package/entries/server-router.tsx +71 -0
  6. package/lib/client/ClientInitializer.test.ts +9 -0
  7. package/lib/client/ClientInitializer.test.ts.skip +92 -0
  8. package/lib/client/ClientInitializer.ts +60 -0
  9. package/lib/client/ErrorBoundary.test.tsx +595 -0
  10. package/lib/client/ErrorBoundary.tsx +230 -0
  11. package/lib/client/componentRegistry.test.ts +165 -0
  12. package/lib/client/componentRegistry.ts +18 -0
  13. package/lib/client/contexts/ThemeContext.tsx +73 -0
  14. package/lib/client/core/ComponentBuilder.test.ts +677 -0
  15. package/lib/client/core/ComponentBuilder.ts +660 -0
  16. package/lib/client/core/ComponentRenderer.test.tsx +176 -0
  17. package/lib/client/core/ComponentRenderer.tsx +83 -0
  18. package/lib/client/core/cmsTemplateProcessor.ts +129 -0
  19. package/lib/client/elementRegistry.ts +81 -0
  20. package/lib/client/hmr/HMRManager.tsx +179 -0
  21. package/lib/client/hmr/index.ts +5 -0
  22. package/lib/client/hmrWebSocket.test.ts +9 -0
  23. package/lib/client/hmrWebSocket.ts +250 -0
  24. package/lib/client/hooks/useColorVariables.test.ts +166 -0
  25. package/lib/client/hooks/useColorVariables.ts +249 -0
  26. package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
  27. package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
  28. package/lib/client/hydration/HydrationUtils.test.ts +154 -0
  29. package/lib/client/hydration/HydrationUtils.ts +35 -0
  30. package/lib/client/i18nConfigService.test.ts +74 -0
  31. package/lib/client/i18nConfigService.ts +78 -0
  32. package/lib/client/index.ts +56 -0
  33. package/lib/client/navigation.test.ts +441 -0
  34. package/lib/client/navigation.ts +23 -0
  35. package/lib/client/responsiveStyleResolver.test.ts +491 -0
  36. package/lib/client/responsiveStyleResolver.ts +184 -0
  37. package/lib/client/routing/RouteLoader.test.ts +635 -0
  38. package/lib/client/routing/RouteLoader.ts +347 -0
  39. package/lib/client/routing/Router.tsx +382 -0
  40. package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
  41. package/lib/client/scripts/ScriptExecutor.ts +171 -0
  42. package/lib/client/scripts/formHandler.ts +103 -0
  43. package/lib/client/styleProcessor.test.ts +126 -0
  44. package/lib/client/styleProcessor.ts +92 -0
  45. package/lib/client/styles/StyleInjector.test.ts +354 -0
  46. package/lib/client/styles/StyleInjector.ts +154 -0
  47. package/lib/client/templateEngine.test.ts +660 -0
  48. package/lib/client/templateEngine.ts +667 -0
  49. package/lib/client/theme.test.ts +173 -0
  50. package/lib/client/theme.ts +159 -0
  51. package/lib/client/utils/toast.ts +46 -0
  52. package/lib/server/createServer.ts +170 -0
  53. package/lib/server/cssGenerator.test.ts +172 -0
  54. package/lib/server/cssGenerator.ts +58 -0
  55. package/lib/server/fileWatcher.ts +134 -0
  56. package/lib/server/index.ts +55 -0
  57. package/lib/server/jsonLoader.test.ts +103 -0
  58. package/lib/server/jsonLoader.ts +350 -0
  59. package/lib/server/middleware/cors.test.ts +177 -0
  60. package/lib/server/middleware/cors.ts +69 -0
  61. package/lib/server/middleware/errorHandler.test.ts +208 -0
  62. package/lib/server/middleware/errorHandler.ts +63 -0
  63. package/lib/server/middleware/index.ts +9 -0
  64. package/lib/server/middleware/logger.test.ts +233 -0
  65. package/lib/server/middleware/logger.ts +99 -0
  66. package/lib/server/pageCache.test.ts +167 -0
  67. package/lib/server/pageCache.ts +97 -0
  68. package/lib/server/projectContext.ts +51 -0
  69. package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
  70. package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
  71. package/lib/server/providers/fileSystemPageProvider.ts +83 -0
  72. package/lib/server/routes/api/cms.test.ts +177 -0
  73. package/lib/server/routes/api/cms.ts +82 -0
  74. package/lib/server/routes/api/colors.ts +59 -0
  75. package/lib/server/routes/api/components.ts +70 -0
  76. package/lib/server/routes/api/config.test.ts +9 -0
  77. package/lib/server/routes/api/config.ts +28 -0
  78. package/lib/server/routes/api/core-routes.ts +182 -0
  79. package/lib/server/routes/api/functions.ts +170 -0
  80. package/lib/server/routes/api/index.ts +69 -0
  81. package/lib/server/routes/api/pages.ts +95 -0
  82. package/lib/server/routes/api/shared.test.ts +81 -0
  83. package/lib/server/routes/api/shared.ts +31 -0
  84. package/lib/server/routes/editor.test.ts +9 -0
  85. package/lib/server/routes/index.ts +104 -0
  86. package/lib/server/routes/pages.ts +161 -0
  87. package/lib/server/routes/static.ts +107 -0
  88. package/lib/server/services/ColorService.ts +193 -0
  89. package/lib/server/services/cmsService.test.ts +388 -0
  90. package/lib/server/services/cmsService.ts +296 -0
  91. package/lib/server/services/componentService.test.ts +276 -0
  92. package/lib/server/services/componentService.ts +346 -0
  93. package/lib/server/services/configService.ts +156 -0
  94. package/lib/server/services/fileWatcherService.ts +67 -0
  95. package/lib/server/services/index.ts +10 -0
  96. package/lib/server/services/pageService.test.ts +258 -0
  97. package/lib/server/services/pageService.ts +240 -0
  98. package/lib/server/ssrRenderer.test.ts +1005 -0
  99. package/lib/server/ssrRenderer.ts +878 -0
  100. package/lib/server/utilityClassGenerator.ts +11 -0
  101. package/lib/server/utils/index.ts +5 -0
  102. package/lib/server/utils/jsonLineMapper.test.ts +100 -0
  103. package/lib/server/utils/jsonLineMapper.ts +166 -0
  104. package/lib/server/validateStyleCoverage.test.ts +9 -0
  105. package/lib/server/validateStyleCoverage.ts +167 -0
  106. package/lib/server/websocketManager.test.ts +9 -0
  107. package/lib/server/websocketManager.ts +95 -0
  108. package/lib/shared/attributeNodeUtils.test.ts +152 -0
  109. package/lib/shared/attributeNodeUtils.ts +50 -0
  110. package/lib/shared/breakpoints.test.ts +166 -0
  111. package/lib/shared/breakpoints.ts +65 -0
  112. package/lib/shared/colorProperties.test.ts +111 -0
  113. package/lib/shared/colorProperties.ts +40 -0
  114. package/lib/shared/colorVariableUtils.test.ts +319 -0
  115. package/lib/shared/colorVariableUtils.ts +97 -0
  116. package/lib/shared/constants.test.ts +175 -0
  117. package/lib/shared/constants.ts +116 -0
  118. package/lib/shared/cssGeneration.ts +481 -0
  119. package/lib/shared/cssProperties.test.ts +252 -0
  120. package/lib/shared/cssProperties.ts +338 -0
  121. package/lib/shared/elementUtils.test.ts +245 -0
  122. package/lib/shared/elementUtils.ts +90 -0
  123. package/lib/shared/fontLoader.ts +97 -0
  124. package/lib/shared/i18n.test.ts +313 -0
  125. package/lib/shared/i18n.ts +286 -0
  126. package/lib/shared/index.ts +50 -0
  127. package/lib/shared/interfaces/contentProvider.test.ts +9 -0
  128. package/lib/shared/interfaces/contentProvider.ts +121 -0
  129. package/lib/shared/nodeUtils.test.ts +320 -0
  130. package/lib/shared/nodeUtils.ts +220 -0
  131. package/lib/shared/pathArrayUtils.test.ts +315 -0
  132. package/lib/shared/pathArrayUtils.ts +17 -0
  133. package/lib/shared/pathUtils.test.ts +260 -0
  134. package/lib/shared/pathUtils.ts +244 -0
  135. package/lib/shared/paths/Path.test.ts +74 -0
  136. package/lib/shared/paths/Path.ts +23 -0
  137. package/lib/shared/paths/PathConverter.test.ts +232 -0
  138. package/lib/shared/paths/PathConverter.ts +141 -0
  139. package/lib/shared/paths/PathUtils.ts +290 -0
  140. package/lib/shared/paths/PathValidator.test.ts +193 -0
  141. package/lib/shared/paths/PathValidator.ts +53 -0
  142. package/lib/shared/paths/index.ts +48 -0
  143. package/lib/shared/propResolver.test.ts +639 -0
  144. package/lib/shared/propResolver.ts +124 -0
  145. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
  146. package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
  147. package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
  148. package/lib/shared/registry/ClientRegistry.test.ts +26 -0
  149. package/lib/shared/registry/ClientRegistry.ts +15 -0
  150. package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
  151. package/lib/shared/registry/ComponentRegistry.ts +100 -0
  152. package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
  153. package/lib/shared/registry/NodeTypeManager.ts +94 -0
  154. package/lib/shared/registry/RegistryManager.test.ts +58 -0
  155. package/lib/shared/registry/RegistryManager.ts +60 -0
  156. package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
  157. package/lib/shared/registry/SSRRegistry.test.ts +26 -0
  158. package/lib/shared/registry/SSRRegistry.ts +15 -0
  159. package/lib/shared/registry/createNodeType.ts +175 -0
  160. package/lib/shared/registry/defineNodeType.ts +73 -0
  161. package/lib/shared/registry/fieldPresets.ts +109 -0
  162. package/lib/shared/registry/index.ts +50 -0
  163. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
  164. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
  165. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
  166. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
  167. package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
  168. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
  169. package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
  170. package/lib/shared/registry/nodeTypes/index.ts +75 -0
  171. package/lib/shared/responsiveScaling.test.ts +268 -0
  172. package/lib/shared/responsiveScaling.ts +194 -0
  173. package/lib/shared/responsiveStyleUtils.test.ts +300 -0
  174. package/lib/shared/responsiveStyleUtils.ts +139 -0
  175. package/lib/shared/slugTranslator.test.ts +325 -0
  176. package/lib/shared/slugTranslator.ts +177 -0
  177. package/lib/shared/styleNodeUtils.test.ts +132 -0
  178. package/lib/shared/styleNodeUtils.ts +102 -0
  179. package/lib/shared/styleUtils.test.ts +238 -0
  180. package/lib/shared/styleUtils.ts +63 -0
  181. package/lib/shared/themeDefaults.test.ts +113 -0
  182. package/lib/shared/themeDefaults.ts +103 -0
  183. package/lib/shared/tree/PathBuilder.ts +383 -0
  184. package/lib/shared/treePathUtils.test.ts +539 -0
  185. package/lib/shared/treePathUtils.ts +339 -0
  186. package/lib/shared/types/api.ts +58 -0
  187. package/lib/shared/types/cms.ts +95 -0
  188. package/lib/shared/types/colors.ts +45 -0
  189. package/lib/shared/types/components.ts +121 -0
  190. package/lib/shared/types/errors.test.ts +103 -0
  191. package/lib/shared/types/errors.ts +69 -0
  192. package/lib/shared/types/index.ts +96 -0
  193. package/lib/shared/types/nodes.ts +20 -0
  194. package/lib/shared/types/rendering.ts +61 -0
  195. package/lib/shared/types/styles.ts +38 -0
  196. package/lib/shared/types.ts +11 -0
  197. package/lib/shared/utilityClassConfig.ts +287 -0
  198. package/lib/shared/utilityClassMapper.test.ts +140 -0
  199. package/lib/shared/utilityClassMapper.ts +229 -0
  200. package/lib/shared/utils/fileUtils.test.ts +99 -0
  201. package/lib/shared/utils/fileUtils.ts +56 -0
  202. package/lib/shared/utils.test.ts +261 -0
  203. package/lib/shared/utils.ts +84 -0
  204. package/lib/shared/validation/index.ts +7 -0
  205. package/lib/shared/validation/propValidator.test.ts +178 -0
  206. package/lib/shared/validation/propValidator.ts +238 -0
  207. package/lib/shared/validation/schemas.test.ts +177 -0
  208. package/lib/shared/validation/schemas.ts +401 -0
  209. package/lib/shared/validation/validators.test.ts +109 -0
  210. package/lib/shared/validation/validators.ts +304 -0
  211. package/lib/test-utils/dom-setup.ts +55 -0
  212. package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
  213. package/lib/test-utils/factories/DomMockFactory.ts +487 -0
  214. package/lib/test-utils/factories/EventMockFactory.ts +244 -0
  215. package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
  216. package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
  217. package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
  218. package/lib/test-utils/factories/index.ts +11 -0
  219. package/lib/test-utils/fixtures.ts +134 -0
  220. package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
  221. package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
  222. package/lib/test-utils/helpers/index.ts +6 -0
  223. package/lib/test-utils/helpers.test.ts +73 -0
  224. package/lib/test-utils/helpers.ts +90 -0
  225. package/lib/test-utils/index.ts +17 -0
  226. package/lib/test-utils/mockFactories.ts +92 -0
  227. package/lib/test-utils/mocks.ts +341 -0
  228. package/package.json +38 -0
  229. package/templates/index-router.html +34 -0
  230. package/tsconfig.json +14 -0
  231. package/vite.config.ts +43 -0
package/bin/cli.ts ADDED
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * uplostudio CLI
4
+ * Commands: dev, build, init
5
+ */
6
+
7
+ import { resolve, join } from 'path';
8
+ import { existsSync, mkdirSync, writeFileSync, cpSync } from 'fs';
9
+ import { setProjectRoot } from '../lib/server/projectContext';
10
+
11
+ const args = process.argv.slice(2);
12
+ const command = args[0];
13
+
14
+ function printHelp() {
15
+ console.log(`
16
+ uplostudio - Visual editor for JSON-based pages
17
+
18
+ Requirements:
19
+ - Bun (https://bun.sh)
20
+
21
+ Usage:
22
+ uplostudio <command> [options]
23
+
24
+ Commands:
25
+ dev Start development server with hot reload
26
+ build Build static HTML files for production
27
+ init Initialize a new project
28
+
29
+ Examples:
30
+ uplostudio dev Start dev server in current directory
31
+ uplostudio build Build static files to ./dist
32
+ uplostudio init my-project Create new project in ./my-project
33
+ `);
34
+ }
35
+
36
+ async function runDev() {
37
+ const projectRoot = process.cwd();
38
+
39
+ // Validate project structure
40
+ if (!existsSync(join(projectRoot, 'pages'))) {
41
+ console.error('āŒ No pages directory found. Are you in a valid project directory?');
42
+ console.error(' Run "uplostudio init <project-name>" to create a new project.');
43
+ process.exit(1);
44
+ }
45
+
46
+ // Set project root for path resolution
47
+ setProjectRoot(projectRoot);
48
+
49
+ console.log(`šŸ“ Project root: ${projectRoot}`);
50
+
51
+ // Import and run the server (it handles all initialization internally)
52
+ await import('../entries/server-router');
53
+ }
54
+
55
+ async function runBuild() {
56
+ const projectRoot = process.cwd();
57
+
58
+ // Validate project structure
59
+ if (!existsSync(join(projectRoot, 'pages'))) {
60
+ console.error('āŒ No pages directory found. Are you in a valid project directory?');
61
+ process.exit(1);
62
+ }
63
+
64
+ // Set project root for path resolution
65
+ setProjectRoot(projectRoot);
66
+
67
+ console.log(`šŸ“ Building project: ${projectRoot}`);
68
+
69
+ // Import and run build
70
+ await import('../build-static.ts');
71
+ }
72
+
73
+ async function runInit(projectName?: string) {
74
+ if (!projectName) {
75
+ console.error('āŒ Please provide a project name: uplostudio init <project-name>');
76
+ process.exit(1);
77
+ }
78
+
79
+ const projectPath = resolve(process.cwd(), projectName);
80
+
81
+ if (existsSync(projectPath)) {
82
+ console.error(`āŒ Directory "${projectName}" already exists.`);
83
+ process.exit(1);
84
+ }
85
+
86
+ console.log(`\nšŸš€ Creating new uplostudio project: ${projectName}\n`);
87
+
88
+ // Create project structure
89
+ mkdirSync(projectPath, { recursive: true });
90
+ mkdirSync(join(projectPath, 'pages'));
91
+ mkdirSync(join(projectPath, 'components'));
92
+ mkdirSync(join(projectPath, 'fonts'));
93
+ mkdirSync(join(projectPath, 'images'));
94
+
95
+ // Create default project.config.json
96
+ const defaultConfig = {
97
+ fonts: [],
98
+ editor: {
99
+ theme: 'dark'
100
+ }
101
+ };
102
+ writeFileSync(
103
+ join(projectPath, 'project.config.json'),
104
+ JSON.stringify(defaultConfig, null, 2)
105
+ );
106
+
107
+ // Create default colors.json
108
+ const defaultColors = {
109
+ default: 'light',
110
+ themes: {
111
+ light: {
112
+ label: 'Light',
113
+ colors: {
114
+ text: '#1f2937',
115
+ bg: '#ffffff'
116
+ }
117
+ }
118
+ }
119
+ };
120
+ writeFileSync(
121
+ join(projectPath, 'colors.json'),
122
+ JSON.stringify(defaultColors, null, 2)
123
+ );
124
+
125
+ // Create package.json for deployment
126
+ const packageJson = {
127
+ name: projectName,
128
+ version: '1.0.0',
129
+ private: true,
130
+ scripts: {
131
+ dev: 'uplostudio dev',
132
+ build: 'uplostudio build',
133
+ },
134
+ dependencies: {
135
+ uplostudio: '^1.0.3'
136
+ }
137
+ };
138
+ writeFileSync(
139
+ join(projectPath, 'package.json'),
140
+ JSON.stringify(packageJson, null, 2)
141
+ );
142
+
143
+ // Create .gitignore
144
+ const gitignore = `# Dependencies
145
+ node_modules/
146
+
147
+ # Build output
148
+ dist/
149
+
150
+ # OS files
151
+ .DS_Store
152
+ `;
153
+ writeFileSync(
154
+ join(projectPath, '.gitignore'),
155
+ gitignore
156
+ );
157
+
158
+ // Create netlify.toml
159
+ const netlifyConfig = `[build]
160
+ command = "bun install && bun run build"
161
+ publish = "dist"
162
+
163
+ [build.environment]
164
+ BUN_VERSION = "1"
165
+ `;
166
+ writeFileSync(
167
+ join(projectPath, 'netlify.toml'),
168
+ netlifyConfig
169
+ );
170
+
171
+ // Create vercel.json
172
+ const vercelConfig = {
173
+ buildCommand: 'bun run build',
174
+ outputDirectory: 'dist',
175
+ installCommand: 'bun install'
176
+ };
177
+ writeFileSync(
178
+ join(projectPath, 'vercel.json'),
179
+ JSON.stringify(vercelConfig, null, 2)
180
+ );
181
+
182
+ // Create wrangler.toml for Cloudflare Pages
183
+ const cloudflareConfig = `name = "${projectName}"
184
+ compatibility_date = "2024-01-01"
185
+ pages_build_output_dir = "dist"
186
+
187
+ [build]
188
+ command = "bun install && bun run build"
189
+ `;
190
+ writeFileSync(
191
+ join(projectPath, 'wrangler.toml'),
192
+ cloudflareConfig
193
+ );
194
+
195
+ // Create default index page
196
+ const defaultPage = {
197
+ meta: {
198
+ title: 'Your Project Title',
199
+ description: 'Your Project Description',
200
+ keywords: '',
201
+ ogTitle: 'Your Project Title',
202
+ ogDescription: 'Your Project Description',
203
+ ogType: 'website'
204
+ },
205
+ root: {
206
+ type: 'node',
207
+ tag: 'div',
208
+ style: {
209
+ base: {
210
+ padding: '40px'
211
+ },
212
+ tablet: {},
213
+ mobile: {}
214
+ },
215
+ children: [
216
+ {
217
+ type: 'node',
218
+ tag: 'h1',
219
+ style: {
220
+ base: {}
221
+ },
222
+ children: 'Hello'
223
+ }
224
+ ]
225
+ }
226
+ };
227
+ writeFileSync(
228
+ join(projectPath, 'pages', 'index.json'),
229
+ JSON.stringify(defaultPage, null, 2)
230
+ );
231
+
232
+ console.log('āœ… Created project structure:');
233
+ console.log(` ${projectName}/`);
234
+ console.log(' ā”œā”€ā”€ pages/');
235
+ console.log(' │ └── index.json');
236
+ console.log(' ā”œā”€ā”€ components/');
237
+ console.log(' ā”œā”€ā”€ fonts/');
238
+ console.log(' ā”œā”€ā”€ images/');
239
+ console.log(' ā”œā”€ā”€ colors.json');
240
+ console.log(' ā”œā”€ā”€ project.config.json');
241
+ console.log(' ā”œā”€ā”€ package.json');
242
+ console.log(' ā”œā”€ā”€ netlify.toml');
243
+ console.log(' ā”œā”€ā”€ vercel.json');
244
+ console.log(' ā”œā”€ā”€ wrangler.toml');
245
+ console.log(' └── .gitignore');
246
+ console.log('\nšŸŽ‰ Project created successfully!\n');
247
+ console.log('Requirements:');
248
+ console.log(' Bun - https://bun.sh');
249
+ console.log('');
250
+ console.log('Next steps:');
251
+ console.log(` cd ${projectName}`);
252
+ console.log(' bun install # Install dependencies');
253
+ console.log(' bun run dev # Start dev server');
254
+ console.log('');
255
+ console.log('Deploy:');
256
+ console.log(' Push to GitHub and connect to Netlify/Vercel/Cloudflare');
257
+ console.log(' Config files included - auto-detected!');
258
+ console.log('');
259
+ }
260
+
261
+ // Main
262
+ switch (command) {
263
+ case 'dev':
264
+ runDev();
265
+ break;
266
+ case 'build':
267
+ runBuild();
268
+ break;
269
+ case 'init':
270
+ runInit(args[1]);
271
+ break;
272
+ case '--help':
273
+ case '-h':
274
+ case undefined:
275
+ printHelp();
276
+ break;
277
+ default:
278
+ console.error(`Unknown command: ${command}`);
279
+ printHelp();
280
+ process.exit(1);
281
+ }
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Static Site Generation Build Script
3
+ * Pre-generates HTML files for all pages at build time
4
+ */
5
+
6
+ import { existsSync, readdirSync, mkdirSync, rmSync, statSync, copyFileSync } from "fs";
7
+ import { writeFile } from "fs/promises";
8
+ import { join } from "path";
9
+ import {
10
+ loadJSONFile,
11
+ loadComponentDirectory,
12
+ mapPageNameToPath,
13
+ parseJSON,
14
+ loadI18nConfig
15
+ } from "./lib/server/jsonLoader";
16
+ import { generateSSRHTML } from "./lib/server/ssrRenderer";
17
+ import { projectPaths } from "./lib/server/projectContext";
18
+ import { loadProjectConfig } from "./lib/shared/fontLoader";
19
+ import type { ComponentDefinition, JSONPage } from "./lib/shared/types";
20
+ import type { SlugMap } from "./lib/shared/slugTranslator";
21
+
22
+ /**
23
+ * Recursively copy directory contents
24
+ */
25
+ function copyDirectory(src: string, dest: string): void {
26
+ if (!existsSync(src)) {
27
+ return;
28
+ }
29
+
30
+ if (!existsSync(dest)) {
31
+ mkdirSync(dest, { recursive: true });
32
+ }
33
+
34
+ const files = readdirSync(src);
35
+
36
+ for (const file of files) {
37
+ const srcPath = join(src, file);
38
+ const destPath = join(dest, file);
39
+ const stat = statSync(srcPath);
40
+
41
+ if (stat.isDirectory()) {
42
+ copyDirectory(srcPath, destPath);
43
+ } else {
44
+ copyFileSync(srcPath, destPath);
45
+ }
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Get locale-specific output path for a page with translated slug support
51
+ * Default locale files go to root, other locales to subdirectories
52
+ * Uses translated slugs from meta.slugs if available
53
+ */
54
+ function getLocalizedOutputPath(
55
+ basePath: string,
56
+ locale: string,
57
+ defaultLocale: string,
58
+ distDir: string,
59
+ slugs?: Record<string, string>
60
+ ): string {
61
+ // Get translated slug for this locale, or fall back to default path
62
+ let slug: string;
63
+ if (slugs && slugs[locale]) {
64
+ slug = slugs[locale];
65
+ } else if (basePath === "/") {
66
+ slug = "";
67
+ } else {
68
+ slug = basePath.substring(1);
69
+ }
70
+
71
+ // Build filename from slug
72
+ const fileName = slug === "" ? "index.html" : `${slug}.html`;
73
+
74
+ if (locale === defaultLocale) {
75
+ return `${distDir}/${fileName}`;
76
+ }
77
+ return `${distDir}/${locale}/${fileName}`;
78
+ }
79
+
80
+ /**
81
+ * Get display path for logging (the actual URL the user will visit)
82
+ */
83
+ function getDisplayPath(
84
+ basePath: string,
85
+ locale: string,
86
+ defaultLocale: string,
87
+ slugs?: Record<string, string>
88
+ ): string {
89
+ // Get translated slug for this locale
90
+ let slug: string;
91
+ if (slugs && slugs[locale]) {
92
+ slug = slugs[locale];
93
+ } else if (basePath === "/") {
94
+ slug = "";
95
+ } else {
96
+ slug = basePath.substring(1);
97
+ }
98
+
99
+ if (locale === defaultLocale) {
100
+ return slug === "" ? "/" : `/${slug}`;
101
+ }
102
+ return slug === "" ? `/${locale}` : `/${locale}/${slug}`;
103
+ }
104
+
105
+ /**
106
+ * Clean dist directory, keeping only production files
107
+ */
108
+ function cleanDist(): void {
109
+ const distDir = projectPaths.dist();
110
+ if (!existsSync(distDir)) {
111
+ mkdirSync(distDir, { recursive: true });
112
+ return;
113
+ }
114
+
115
+ const files = readdirSync(distDir);
116
+ let cleaned = 0;
117
+
118
+ for (const file of files) {
119
+ // Keep fonts, images, and icons
120
+ if (
121
+ file === "fonts" ||
122
+ file === "images" ||
123
+ file === "icons"
124
+ ) {
125
+ continue;
126
+ }
127
+
128
+ // Remove everything else (editor files, old HTML, etc.)
129
+ try {
130
+ const filePath = join(distDir, file);
131
+ const stat = statSync(filePath);
132
+
133
+ if (stat.isDirectory()) {
134
+ rmSync(filePath, { recursive: true, force: true });
135
+ } else {
136
+ rmSync(filePath, { force: true });
137
+ }
138
+ cleaned++;
139
+ } catch (error) {
140
+ console.warn(`āš ļø Could not remove ${file}:`, error);
141
+ }
142
+ }
143
+
144
+ if (cleaned > 0) {
145
+ console.log(`🧹 Cleaned ${cleaned} file(s) from dist\n`);
146
+ }
147
+ }
148
+
149
+
150
+ /**
151
+ * Main build function
152
+ */
153
+ async function buildStaticPages(): Promise<void> {
154
+ console.log("šŸ—ļø Building static HTML files...\n");
155
+
156
+ // Load project config first
157
+ await loadProjectConfig();
158
+
159
+ // Load i18n config for multi-locale build
160
+ const i18nConfig = await loadI18nConfig();
161
+ console.log(`🌐 Locales: ${i18nConfig.locales.map(l => l.code).join(", ")} (default: ${i18nConfig.defaultLocale})\n`);
162
+
163
+ // Clean dist directory (removes editor files, old HTML)
164
+ cleanDist();
165
+
166
+ // Copy fonts, images, icons, and functions directories to dist
167
+ console.log("šŸ“¦ Copying assets...");
168
+ const distDir = projectPaths.dist();
169
+ copyDirectory(projectPaths.fonts(), join(distDir, "fonts"));
170
+ copyDirectory(projectPaths.images(), join(distDir, "images"));
171
+ copyDirectory(projectPaths.icons(), join(distDir, "icons"));
172
+
173
+ // Copy functions folder for Cloudflare Pages
174
+ const functionsDir = projectPaths.functions();
175
+ if (existsSync(functionsDir)) {
176
+ copyDirectory(functionsDir, join(distDir, "functions"));
177
+ console.log("āœ… Assets and functions copied\n");
178
+ } else {
179
+ console.log("āœ… Assets copied\n");
180
+ }
181
+
182
+ // Load all global components
183
+ const components = await loadComponentDirectory(projectPaths.components());
184
+ const globalComponents: Record<string, ComponentDefinition> = {};
185
+ components.forEach((value, key) => {
186
+ globalComponents[key] = value;
187
+ });
188
+
189
+ console.log(`āœ… Loaded ${components.size} global component(s)\n`);
190
+
191
+ // Load all pages
192
+ const pagesDir = projectPaths.pages();
193
+ if (!existsSync(pagesDir)) {
194
+ console.error("āŒ Pages directory not found!");
195
+ process.exit(1);
196
+ }
197
+
198
+ const pageFiles = readdirSync(pagesDir).filter((f) => f.endsWith(".json"));
199
+
200
+ if (pageFiles.length === 0) {
201
+ console.warn("āš ļø No pages found in ./pages directory");
202
+ return;
203
+ }
204
+
205
+ console.log(`šŸ“„ Found ${pageFiles.length} page(s) to build\n`);
206
+
207
+ // First pass: collect all slug mappings for LocaleList SSR
208
+ const slugMappings: SlugMap[] = [];
209
+ for (const file of pageFiles) {
210
+ const pageName = file.replace(".json", "");
211
+ const basePath = mapPageNameToPath(pageName);
212
+ const pageContent = await loadJSONFile(join(pagesDir, file));
213
+ if (pageContent) {
214
+ try {
215
+ const pageData = parseJSON<JSONPage>(pageContent);
216
+ if (pageData.meta?.slugs) {
217
+ const pageId = basePath === '/' ? 'index' : basePath.substring(1);
218
+ slugMappings.push({ pageId, slugs: pageData.meta.slugs });
219
+ }
220
+ } catch { /* ignore parse errors in first pass */ }
221
+ }
222
+ }
223
+
224
+ let successCount = 0;
225
+ let errorCount = 0;
226
+
227
+ // Build each page for each locale
228
+ for (const file of pageFiles) {
229
+ const pageName = file.replace(".json", "");
230
+ const basePath = mapPageNameToPath(pageName);
231
+ const pageContent = await loadJSONFile(join(pagesDir, file));
232
+
233
+ if (!pageContent) {
234
+ console.warn(`āš ļø Skipping ${basePath} (empty file)`);
235
+ errorCount++;
236
+ continue;
237
+ }
238
+
239
+ try {
240
+ const pageData = parseJSON<JSONPage>(pageContent);
241
+
242
+ // Get translated slugs from page meta (if available)
243
+ const slugs = pageData.meta?.slugs;
244
+
245
+ // Generate HTML for each locale
246
+ for (const localeConfig of i18nConfig.locales) {
247
+ const locale = localeConfig.code;
248
+ const baseUrl = ""; // Empty for relative paths
249
+
250
+ // Build the URL path that will be used for this locale
251
+ const urlPath = getDisplayPath(basePath, locale, i18nConfig.defaultLocale, slugs);
252
+
253
+ const html = await generateSSRHTML(pageData, globalComponents, urlPath, baseUrl, true, locale, slugMappings);
254
+
255
+ // Determine locale-specific output path with translated slug
256
+ const outputPath = getLocalizedOutputPath(basePath, locale, i18nConfig.defaultLocale, distDir, slugs);
257
+
258
+ // Ensure directory exists
259
+ const outputDir = outputPath.substring(0, outputPath.lastIndexOf("/"));
260
+ if (outputDir && !existsSync(outputDir)) {
261
+ mkdirSync(outputDir, { recursive: true });
262
+ }
263
+
264
+ await writeFile(outputPath, html, "utf-8");
265
+
266
+ console.log(`āœ… Built: ${urlPath} → ${outputPath}`);
267
+ successCount++;
268
+ }
269
+
270
+ } catch (error) {
271
+ console.error(`āŒ Error building ${basePath}:`, error);
272
+ errorCount++;
273
+ }
274
+ }
275
+
276
+ console.log("\n" + "=".repeat(50));
277
+ console.log(`✨ Build complete!`);
278
+ console.log(` āœ… Success: ${successCount}`);
279
+ if (errorCount > 0) {
280
+ console.log(` āŒ Errors: ${errorCount}`);
281
+ }
282
+ console.log(`\nšŸ“¦ Production files in /dist:`);
283
+ console.log(` - *.html (Static pages with vanilla JS)`);
284
+ console.log(` - fonts/ (Custom fonts)`);
285
+ console.log(` - images/ (Image assets)`);
286
+ console.log(` - icons/ (Favicon and icons)`);
287
+ if (existsSync(functionsDir)) {
288
+ console.log(` - functions/ (Cloudflare Pages Functions)`);
289
+ }
290
+ console.log(` - No React, no client-router āœ“`);
291
+ }
292
+
293
+ // Run build
294
+ buildStaticPages().catch((error) => {
295
+ console.error("āŒ Build failed:", error);
296
+ process.exit(1);
297
+ });
298
+
package/bunfig.toml ADDED
@@ -0,0 +1,39 @@
1
+ # Bun Configuration for @meno/core
2
+ # Reference: https://bun.sh/docs/bunfig
3
+
4
+ [test]
5
+ # Test framework configuration
6
+ # Bun uses Jest-compatible test syntax
7
+
8
+ # Preload DOM environment for client-side tests
9
+ preload = ["./lib/test-utils/dom-setup.ts"]
10
+
11
+ # Coverage thresholds - fail if coverage is below these percentages
12
+ # Enforces minimum code coverage standards across the project
13
+ # Target: 80% for production-quality codebase
14
+ # Reference: https://bun.sh/docs/cli/test
15
+ coverageThreshold = {
16
+ lines = 80,
17
+ functions = 80,
18
+ branches = 75,
19
+ statements = 80
20
+ }
21
+
22
+ # Test timeout in milliseconds (default: 5000)
23
+ # Increased for async operations and complex component rendering
24
+ timeout = 10000
25
+
26
+ # Root directory for test discovery
27
+ root = "."
28
+
29
+ # Test file patterns to include
30
+ # Bun looks for: *.test.ts, *.test.tsx, *.test.js, *.spec.ts, etc.
31
+ testNamePattern = ".*"
32
+
33
+ # Skip patterns to exclude from test discovery
34
+ # tests/ contains Playwright E2E tests that should be run with `bunx playwright test`
35
+ testPathIgnorePatterns = ["node_modules", "dist", "\\.next", "tests/"]
36
+
37
+ # Coverage reporter options
38
+ # Generates coverage reports for analysis
39
+ coverageReporters = ["text", "text-summary", "html", "lcov"]