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
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
|
+
}
|
package/build-static.ts
ADDED
|
@@ -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"]
|