@hyperfixi/testing-framework 2.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/dist/assertions-BImuwZsB.d.mts +458 -0
- package/dist/assertions-BImuwZsB.d.ts +458 -0
- package/dist/assertions.d.mts +1 -0
- package/dist/assertions.d.ts +1 -0
- package/dist/assertions.js +625 -0
- package/dist/assertions.js.map +1 -0
- package/dist/assertions.mjs +621 -0
- package/dist/assertions.mjs.map +1 -0
- package/dist/index.d.mts +246 -0
- package/dist/index.d.ts +246 -0
- package/dist/index.js +1465 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1435 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +121 -0
- package/src/assertions.test.ts +490 -0
- package/src/assertions.ts +779 -0
- package/src/index.test.ts +490 -0
- package/src/index.ts +601 -0
- package/src/jsdom.d.ts +7 -0
- package/src/multilingual/README.md +295 -0
- package/src/multilingual/bundle-builder.ts +345 -0
- package/src/multilingual/cli.ts +224 -0
- package/src/multilingual/index.ts +68 -0
- package/src/multilingual/orchestrator.ts +275 -0
- package/src/multilingual/pattern-loader.ts +176 -0
- package/src/multilingual/reporters/console-reporter.ts +294 -0
- package/src/multilingual/reporters/json-reporter.ts +99 -0
- package/src/multilingual/reporters/regression-reporter.ts +263 -0
- package/src/multilingual/tools/analyze-failures.ts +326 -0
- package/src/multilingual/types.ts +268 -0
- package/src/multilingual/validators/parse-diagnostics.ts +337 -0
- package/src/multilingual/validators/parse-validator.ts +193 -0
- package/src/multilingual/validators/size-validator.ts +187 -0
- package/src/runner.test.ts +813 -0
- package/src/runner.ts +639 -0
- package/src/types.ts +505 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Multilingual Testing Framework
|
|
2
|
+
|
|
3
|
+
Automated validation system for HyperFixi's multilingual hyperscript support across 13 languages.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This framework validates the multilingual system by:
|
|
8
|
+
|
|
9
|
+
- Loading patterns from the patterns-reference database (689 translations)
|
|
10
|
+
- Building or selecting appropriate language bundles
|
|
11
|
+
- Validating parsing across all languages
|
|
12
|
+
- Tracking bundle sizes and performance
|
|
13
|
+
- Comparing against baselines for regression detection
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Test all languages in quick mode (10 patterns/language)
|
|
19
|
+
npm run test:multilingual
|
|
20
|
+
|
|
21
|
+
# Test specific language with verbose output
|
|
22
|
+
npm run test:multilingual -- --language ja --verbose
|
|
23
|
+
|
|
24
|
+
# Test multiple languages in full mode
|
|
25
|
+
npm run test:multilingual -- --languages ja,ko,es --full
|
|
26
|
+
|
|
27
|
+
# Compare against baseline
|
|
28
|
+
npm run test:multilingual -- --regression
|
|
29
|
+
|
|
30
|
+
# Save current results as new baseline
|
|
31
|
+
npm run test:multilingual -- --save-baseline
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Architecture
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
multilingual/
|
|
38
|
+
├── cli.ts # Command-line interface
|
|
39
|
+
├── orchestrator.ts # Main test runner
|
|
40
|
+
├── types.ts # TypeScript types
|
|
41
|
+
├── pattern-loader.ts # Database query layer
|
|
42
|
+
├── bundle-builder.ts # Bundle selection/generation
|
|
43
|
+
├── validators/
|
|
44
|
+
│ ├── parse-validator.ts # Parse validation
|
|
45
|
+
│ └── size-validator.ts # Bundle size validation
|
|
46
|
+
└── reporters/
|
|
47
|
+
├── console-reporter.ts # Minimal console output
|
|
48
|
+
├── json-reporter.ts # Structured JSON results
|
|
49
|
+
└── regression-reporter.ts # Baseline comparison
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Test Flow
|
|
53
|
+
|
|
54
|
+
1. **Load Configuration** - Parse CLI arguments
|
|
55
|
+
2. **Load Patterns** - Query patterns-reference database
|
|
56
|
+
3. **Select/Build Bundle** - Find or generate appropriate bundle
|
|
57
|
+
4. **Validate Parsing** - Test each pattern with semantic parser
|
|
58
|
+
5. **Report Results** - Output to console, JSON, and regression reports
|
|
59
|
+
|
|
60
|
+
## CLI Options
|
|
61
|
+
|
|
62
|
+
### Language Selection
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
-l, --language <code> # Test specific language (en, ja, es, etc.)
|
|
66
|
+
--languages <codes> # Test multiple languages (comma-separated)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Bundle Options
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
-b, --bundle <name> # Use specific bundle
|
|
73
|
+
--build # Build bundle before testing
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Test Modes
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
-m, --mode <mode> # 'quick' or 'full' (default: quick)
|
|
80
|
+
--quick # Quick mode (10 patterns per language)
|
|
81
|
+
--full # Full mode (all patterns)
|
|
82
|
+
--limit <n> # Patterns per language in quick mode
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Output Options
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
-v, --verbose # Enable verbose output
|
|
89
|
+
-r, --regression # Compare to baseline
|
|
90
|
+
--save-baseline # Save results as new baseline
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Filtering
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
-c, --confidence <n> # Minimum confidence threshold (0-1)
|
|
97
|
+
--verified-only # Only test verified translations
|
|
98
|
+
--categories <cats> # Filter by categories (comma-separated)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Examples
|
|
102
|
+
|
|
103
|
+
### Test Japanese with verbose output
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm run test:multilingual -- --language ja --verbose
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Expected output:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
Multilingual Test Runner v1.0.0
|
|
113
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
114
|
+
|
|
115
|
+
Languages: ja (1)
|
|
116
|
+
Mode: quick mode (10 patterns/lang)
|
|
117
|
+
|
|
118
|
+
✓ JA: 53/53 (100%) ⏱ 0.8s conf: 0.92
|
|
119
|
+
|
|
120
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
121
|
+
Summary: ✓ PASS 53/53 Duration: 0.9s
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Test all priority languages
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npm run test:multilingual -- --languages en,ja,ko,es --full
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Run regression test
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npm run test:multilingual -- --regression
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Expected regression output:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
Regression Analysis
|
|
140
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
141
|
+
→ EN
|
|
142
|
+
Parse Rate: +2.1%
|
|
143
|
+
New Successes: 2
|
|
144
|
+
|
|
145
|
+
↑ JA
|
|
146
|
+
Parse Rate: +5.3%
|
|
147
|
+
Avg Confidence: +0.04
|
|
148
|
+
New Successes: 3
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Save new baseline
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npm run test:multilingual -- --full --save-baseline
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Programmatic Usage
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { runMultilingualTests } from '@hyperfixi/testing-framework/multilingual';
|
|
161
|
+
|
|
162
|
+
const results = await runMultilingualTests({
|
|
163
|
+
languages: ['ja', 'ko'],
|
|
164
|
+
mode: 'full',
|
|
165
|
+
regression: true,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
console.log(`Tested ${results.summary.totalPatterns} patterns`);
|
|
169
|
+
console.log(
|
|
170
|
+
`Success rate: ${((results.summary.totalSuccess / results.summary.totalPatterns) * 100).toFixed(1)}%`
|
|
171
|
+
);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Output Files
|
|
175
|
+
|
|
176
|
+
### JSON Results
|
|
177
|
+
|
|
178
|
+
Location: `./test-results/results.json`
|
|
179
|
+
|
|
180
|
+
Structure:
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"timestamp": "2026-01-17T10:30:00Z",
|
|
185
|
+
"commit": "614da020",
|
|
186
|
+
"languageResults": [
|
|
187
|
+
{
|
|
188
|
+
"language": "ja",
|
|
189
|
+
"parseSuccess": 53,
|
|
190
|
+
"parseFailure": 0,
|
|
191
|
+
"parseRate": 1.0,
|
|
192
|
+
"avgConfidence": 0.92
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
"bundles": {
|
|
196
|
+
"browser-ja": {
|
|
197
|
+
"size": 20480,
|
|
198
|
+
"languages": ["ja"]
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Baseline
|
|
205
|
+
|
|
206
|
+
Location: `./test-results/baseline.json`
|
|
207
|
+
|
|
208
|
+
Structure:
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"timestamp": "2026-01-17T10:00:00Z",
|
|
213
|
+
"commit": "614da020",
|
|
214
|
+
"languages": {
|
|
215
|
+
"ja": {
|
|
216
|
+
"parseSuccess": 51,
|
|
217
|
+
"parseFailure": 2,
|
|
218
|
+
"parseRate": 0.96,
|
|
219
|
+
"avgConfidence": 0.88
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Supported Languages
|
|
226
|
+
|
|
227
|
+
| Code | Language | Word Order | Status |
|
|
228
|
+
| ---- | ---------- | ---------- | ------------------ |
|
|
229
|
+
| en | English | SVO | ✅ High coverage |
|
|
230
|
+
| ja | Japanese | SOV | ✅ High coverage |
|
|
231
|
+
| ko | Korean | SOV | ✅ High coverage |
|
|
232
|
+
| es | Spanish | SVO | ✅ High coverage |
|
|
233
|
+
| zh | Chinese | SVO | ✅ High coverage |
|
|
234
|
+
| ar | Arabic | VSO | ✅ High coverage |
|
|
235
|
+
| pt | Portuguese | SVO | ✅ Medium coverage |
|
|
236
|
+
| tr | Turkish | SOV | ✅ Medium coverage |
|
|
237
|
+
| de | German | V2 | ✅ Medium coverage |
|
|
238
|
+
| fr | French | SVO | ✅ Medium coverage |
|
|
239
|
+
| id | Indonesian | SVO | ✅ Medium coverage |
|
|
240
|
+
| qu | Quechua | SOV | ⚠️ Low coverage |
|
|
241
|
+
| sw | Swahili | SVO | ⚠️ Low coverage |
|
|
242
|
+
|
|
243
|
+
## Integration with CI
|
|
244
|
+
|
|
245
|
+
Add to `.github/workflows/test.yml`:
|
|
246
|
+
|
|
247
|
+
```yaml
|
|
248
|
+
- name: Run Multilingual Tests
|
|
249
|
+
run: |
|
|
250
|
+
npm run test:multilingual -- --full --regression
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Troubleshooting
|
|
254
|
+
|
|
255
|
+
### Bundle not found
|
|
256
|
+
|
|
257
|
+
If you see "Bundle not found", build it first:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
npm run test:multilingual -- --build --language ja
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Patterns database not populated
|
|
264
|
+
|
|
265
|
+
Ensure the patterns database is populated:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
cd packages/patterns-reference
|
|
269
|
+
npm run populate
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Low confidence scores
|
|
273
|
+
|
|
274
|
+
Enable verbose mode to see which patterns are failing:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
npm run test:multilingual -- --verbose --language ja
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Future Enhancements
|
|
281
|
+
|
|
282
|
+
- [ ] Browser execution tests (Playwright)
|
|
283
|
+
- [ ] Performance benchmarks
|
|
284
|
+
- [ ] Visual regression for multilingual examples
|
|
285
|
+
- [ ] Auto-fix suggestions for common translation errors
|
|
286
|
+
- [ ] Coverage heatmap visualization
|
|
287
|
+
|
|
288
|
+
## Contributing
|
|
289
|
+
|
|
290
|
+
When adding a new language:
|
|
291
|
+
|
|
292
|
+
1. Add patterns to patterns-reference database
|
|
293
|
+
2. Generate translations with `npm run populate`
|
|
294
|
+
3. Run tests: `npm run test:multilingual -- --language <code>`
|
|
295
|
+
4. Save baseline: `npm run test:multilingual -- --language <code> --save-baseline`
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundle Builder - Manages language bundle generation and selection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync, statSync } from 'node:fs';
|
|
6
|
+
import { join, dirname } from 'node:path';
|
|
7
|
+
import { exec } from 'node:child_process';
|
|
8
|
+
import { promisify } from 'node:util';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import type { LanguageCode, BundleInfo, BundleBuildOptions } from './types';
|
|
11
|
+
|
|
12
|
+
const execAsync = promisify(exec);
|
|
13
|
+
|
|
14
|
+
// ESM __dirname equivalent
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
|
|
18
|
+
// Predefined bundle mappings
|
|
19
|
+
const PREDEFINED_BUNDLES = new Map<string, LanguageCode[]>([
|
|
20
|
+
// All 23 languages
|
|
21
|
+
[
|
|
22
|
+
'browser',
|
|
23
|
+
[
|
|
24
|
+
'en',
|
|
25
|
+
'es',
|
|
26
|
+
'ja',
|
|
27
|
+
'ar',
|
|
28
|
+
'ko',
|
|
29
|
+
'zh',
|
|
30
|
+
'tr',
|
|
31
|
+
'pt',
|
|
32
|
+
'fr',
|
|
33
|
+
'de',
|
|
34
|
+
'id',
|
|
35
|
+
'qu',
|
|
36
|
+
'sw',
|
|
37
|
+
'bn',
|
|
38
|
+
'hi',
|
|
39
|
+
'it',
|
|
40
|
+
'ms',
|
|
41
|
+
'pl',
|
|
42
|
+
'ru',
|
|
43
|
+
'th',
|
|
44
|
+
'tl',
|
|
45
|
+
'uk',
|
|
46
|
+
'vi',
|
|
47
|
+
],
|
|
48
|
+
],
|
|
49
|
+
['browser-priority', ['en', 'es', 'pt', 'fr', 'de', 'ja', 'zh', 'ko', 'ar', 'tr', 'id']], // 11 priority
|
|
50
|
+
['browser-western', ['en', 'es', 'pt', 'fr', 'de']], // Western languages
|
|
51
|
+
['browser-east-asian', ['ja', 'zh', 'ko']], // East Asian languages
|
|
52
|
+
// Individual language bundles
|
|
53
|
+
['browser-ar', ['ar']],
|
|
54
|
+
['browser-bn', ['bn']],
|
|
55
|
+
['browser-de', ['de']],
|
|
56
|
+
['browser-en', ['en']],
|
|
57
|
+
['browser-es', ['es']],
|
|
58
|
+
['browser-fr', ['fr']],
|
|
59
|
+
['browser-hi', ['hi']],
|
|
60
|
+
['browser-id', ['id']],
|
|
61
|
+
['browser-it', ['it']],
|
|
62
|
+
['browser-ja', ['ja']],
|
|
63
|
+
['browser-ko', ['ko']],
|
|
64
|
+
['browser-ms', ['ms']],
|
|
65
|
+
['browser-pl', ['pl']],
|
|
66
|
+
['browser-pt', ['pt']],
|
|
67
|
+
['browser-qu', ['qu']],
|
|
68
|
+
['browser-ru', ['ru']],
|
|
69
|
+
['browser-sw', ['sw']],
|
|
70
|
+
['browser-th', ['th']],
|
|
71
|
+
['browser-tl', ['tl']],
|
|
72
|
+
['browser-tr', ['tr']],
|
|
73
|
+
['browser-uk', ['uk']],
|
|
74
|
+
['browser-vi', ['vi']],
|
|
75
|
+
['browser-zh', ['zh']],
|
|
76
|
+
// Utility bundles
|
|
77
|
+
['browser-core', []], // Core bundle (language-agnostic)
|
|
78
|
+
['browser-lazy', []], // Lazy-loading bundle
|
|
79
|
+
['browser-en-tr', ['en', 'tr']], // English + Turkish
|
|
80
|
+
['browser-es-en', ['es', 'en']], // Spanish + English
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get bundle path in semantic package
|
|
85
|
+
*/
|
|
86
|
+
function getBundlePath(bundleName: string): string {
|
|
87
|
+
// Resolve from this file's location: testing-framework/src/multilingual
|
|
88
|
+
// __dirname = /path/to/hyperfixi/packages/testing-framework/src/multilingual
|
|
89
|
+
// Go up 3 levels to project root, then into packages/semantic
|
|
90
|
+
const projectRoot = join(__dirname, '../../..');
|
|
91
|
+
const semanticRoot = join(projectRoot, 'semantic');
|
|
92
|
+
// Bundle naming: browser-{group}.{group}.global.js
|
|
93
|
+
const group = bundleName.replace('browser-', '');
|
|
94
|
+
return join(semanticRoot, 'dist', `${bundleName}.${group}.global.js`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Find existing bundle for languages
|
|
99
|
+
*/
|
|
100
|
+
export async function findBundleForLanguages(
|
|
101
|
+
languages: LanguageCode[]
|
|
102
|
+
): Promise<BundleInfo | null> {
|
|
103
|
+
const langSet = new Set(languages);
|
|
104
|
+
|
|
105
|
+
// Find the smallest predefined bundle that contains all required languages
|
|
106
|
+
let bestBundle: { name: string; langs: LanguageCode[] } | null = null;
|
|
107
|
+
let minSize = Infinity;
|
|
108
|
+
|
|
109
|
+
for (const [bundleName, bundleLangs] of PREDEFINED_BUNDLES.entries()) {
|
|
110
|
+
const bundleSet = new Set(bundleLangs);
|
|
111
|
+
|
|
112
|
+
// Check if bundle contains all required languages
|
|
113
|
+
const containsAll = languages.every(lang => bundleSet.has(lang));
|
|
114
|
+
if (!containsAll) continue;
|
|
115
|
+
|
|
116
|
+
// Prefer smaller bundles
|
|
117
|
+
if (bundleLangs.length < minSize) {
|
|
118
|
+
minSize = bundleLangs.length;
|
|
119
|
+
bestBundle = { name: bundleName, langs: bundleLangs };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!bestBundle) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const bundlePath = getBundlePath(bestBundle.name);
|
|
128
|
+
const exists = existsSync(bundlePath);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
name: bestBundle.name,
|
|
132
|
+
path: bundlePath,
|
|
133
|
+
languages: bestBundle.langs,
|
|
134
|
+
size: exists ? statSync(bundlePath).size : 0,
|
|
135
|
+
exists,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get bundle info by name
|
|
141
|
+
*/
|
|
142
|
+
export async function getBundleInfo(bundleName: string): Promise<BundleInfo | null> {
|
|
143
|
+
const languages = PREDEFINED_BUNDLES.get(bundleName);
|
|
144
|
+
if (!languages) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const bundlePath = getBundlePath(bundleName);
|
|
149
|
+
const exists = existsSync(bundlePath);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
name: bundleName,
|
|
153
|
+
path: bundlePath,
|
|
154
|
+
languages,
|
|
155
|
+
size: exists ? statSync(bundlePath).size : 0,
|
|
156
|
+
exists,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Build bundle for languages
|
|
162
|
+
*/
|
|
163
|
+
export async function buildBundle(options: BundleBuildOptions): Promise<BundleInfo> {
|
|
164
|
+
const { languages, groupName } = options;
|
|
165
|
+
|
|
166
|
+
// Generate bundle using the generate-bundle.mjs script
|
|
167
|
+
const semanticRoot = join(process.cwd(), 'packages/semantic');
|
|
168
|
+
const scriptPath = join(semanticRoot, 'scripts/generate-bundle.mjs');
|
|
169
|
+
|
|
170
|
+
let bundleName: string;
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
if (groupName) {
|
|
174
|
+
// Use predefined group
|
|
175
|
+
const cmd = `node ${scriptPath} --group ${groupName} ${options.updateConfig ? '--auto' : ''}`;
|
|
176
|
+
await execAsync(cmd, { cwd: semanticRoot });
|
|
177
|
+
bundleName = `browser-${groupName}`;
|
|
178
|
+
} else {
|
|
179
|
+
// Use specific languages
|
|
180
|
+
const langCodes = languages.join(' ');
|
|
181
|
+
const cmd = `node ${scriptPath} ${langCodes} ${options.updateConfig ? '--auto' : ''}`;
|
|
182
|
+
await execAsync(cmd, { cwd: semanticRoot });
|
|
183
|
+
bundleName = `browser-${languages.join('-')}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Build the bundle
|
|
187
|
+
await execAsync('npm run build', { cwd: semanticRoot });
|
|
188
|
+
|
|
189
|
+
const bundlePath = options.outputPath || getBundlePath(bundleName);
|
|
190
|
+
const exists = existsSync(bundlePath);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
name: bundleName,
|
|
194
|
+
path: bundlePath,
|
|
195
|
+
languages,
|
|
196
|
+
size: exists ? statSync(bundlePath).size : 0,
|
|
197
|
+
exists,
|
|
198
|
+
};
|
|
199
|
+
} catch (error) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Failed to build bundle: ${error instanceof Error ? error.message : String(error)}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Select or build bundle for testing
|
|
208
|
+
*/
|
|
209
|
+
export async function selectBundle(
|
|
210
|
+
languages: LanguageCode[],
|
|
211
|
+
build: boolean = false
|
|
212
|
+
): Promise<BundleInfo> {
|
|
213
|
+
// First, try to find existing bundle
|
|
214
|
+
let bundle = await findBundleForLanguages(languages);
|
|
215
|
+
|
|
216
|
+
// If bundle exists and we don't need to build, use it
|
|
217
|
+
if (bundle && bundle.exists && !build) {
|
|
218
|
+
return bundle;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// If build is requested or no bundle exists, build it
|
|
222
|
+
if (build || !bundle || !bundle.exists) {
|
|
223
|
+
// Determine if we can use a predefined group
|
|
224
|
+
const groupName = findGroupForLanguages(languages);
|
|
225
|
+
|
|
226
|
+
bundle = await buildBundle({
|
|
227
|
+
languages,
|
|
228
|
+
groupName: groupName !== null ? groupName : undefined,
|
|
229
|
+
outputPath: undefined,
|
|
230
|
+
updateConfig: false, // Don't modify configs during testing
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!bundle.exists) {
|
|
235
|
+
throw new Error(`Bundle not found: ${bundle.path}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return bundle;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Find predefined group name for languages
|
|
243
|
+
*/
|
|
244
|
+
function findGroupForLanguages(languages: LanguageCode[]): string | null {
|
|
245
|
+
const langSet = new Set(languages);
|
|
246
|
+
|
|
247
|
+
for (const [bundleName, bundleLangs] of PREDEFINED_BUNDLES.entries()) {
|
|
248
|
+
const bundleSet = new Set(bundleLangs);
|
|
249
|
+
|
|
250
|
+
// Exact match
|
|
251
|
+
if (bundleLangs.length === languages.length && languages.every(lang => bundleSet.has(lang))) {
|
|
252
|
+
return bundleName.replace('browser-', '');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* List all available bundles
|
|
261
|
+
*/
|
|
262
|
+
export async function listAvailableBundles(): Promise<BundleInfo[]> {
|
|
263
|
+
const bundles: BundleInfo[] = [];
|
|
264
|
+
|
|
265
|
+
for (const [bundleName, languages] of PREDEFINED_BUNDLES.entries()) {
|
|
266
|
+
const bundlePath = getBundlePath(bundleName);
|
|
267
|
+
const exists = existsSync(bundlePath);
|
|
268
|
+
|
|
269
|
+
bundles.push({
|
|
270
|
+
name: bundleName,
|
|
271
|
+
path: bundlePath,
|
|
272
|
+
languages,
|
|
273
|
+
size: exists ? statSync(bundlePath).size : 0,
|
|
274
|
+
exists,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return bundles;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Check if bundle exists
|
|
283
|
+
*/
|
|
284
|
+
export async function bundleExists(bundleName: string): Promise<boolean> {
|
|
285
|
+
const bundlePath = getBundlePath(bundleName);
|
|
286
|
+
return existsSync(bundlePath);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get bundle size in bytes
|
|
291
|
+
*/
|
|
292
|
+
export async function getBundleSize(bundleName: string): Promise<number> {
|
|
293
|
+
const bundlePath = getBundlePath(bundleName);
|
|
294
|
+
if (!existsSync(bundlePath)) {
|
|
295
|
+
return 0;
|
|
296
|
+
}
|
|
297
|
+
return statSync(bundlePath).size;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Estimate bundle size for languages (without building)
|
|
302
|
+
*/
|
|
303
|
+
export function estimateBundleSize(languages: LanguageCode[]): {
|
|
304
|
+
estimatedSize: number;
|
|
305
|
+
estimatedGzipSize: number;
|
|
306
|
+
} {
|
|
307
|
+
const SIZES: Record<string, number> = {
|
|
308
|
+
en: 25,
|
|
309
|
+
es: 22,
|
|
310
|
+
ja: 28,
|
|
311
|
+
ar: 24,
|
|
312
|
+
ko: 22,
|
|
313
|
+
zh: 20,
|
|
314
|
+
tr: 20,
|
|
315
|
+
pt: 22,
|
|
316
|
+
fr: 22,
|
|
317
|
+
de: 24,
|
|
318
|
+
id: 18,
|
|
319
|
+
qu: 16,
|
|
320
|
+
sw: 18,
|
|
321
|
+
bn: 20,
|
|
322
|
+
hi: 20,
|
|
323
|
+
it: 22,
|
|
324
|
+
ms: 18,
|
|
325
|
+
pl: 20,
|
|
326
|
+
ru: 22,
|
|
327
|
+
th: 20,
|
|
328
|
+
tl: 18,
|
|
329
|
+
uk: 22,
|
|
330
|
+
vi: 20,
|
|
331
|
+
base: 45,
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const GZIP_RATIO = 0.35;
|
|
335
|
+
|
|
336
|
+
let totalSize = SIZES['base'] || 45; // Base infrastructure
|
|
337
|
+
for (const lang of languages) {
|
|
338
|
+
totalSize += SIZES[lang] || 20; // Default to 20 KB if unknown
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
estimatedSize: totalSize * 1024, // Convert to bytes
|
|
343
|
+
estimatedGzipSize: Math.round(totalSize * 1024 * GZIP_RATIO),
|
|
344
|
+
};
|
|
345
|
+
}
|