@trohde/earos 1.2.0 → 1.3.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/assets/init/docs/getting-started.md +1 -1
- package/assets/init/docs/onboarding/agent-assisted.md +19 -19
- package/assets/init/docs/onboarding/first-assessment.md +18 -18
- package/assets/init/docs/onboarding/governed-review.md +10 -10
- package/assets/init/docs/onboarding/overview.md +15 -15
- package/assets/init/docs/onboarding/scaling-optimization.md +13 -13
- package/assets/init/docs/plans/2026-03-23-001-refactor-site-review-findings-plan.md +195 -0
- package/assets/init/docs/plans/2026-03-23-002-refactor-cli-review-findings-plan.md +736 -0
- package/assets/init/docs/terminology.md +1 -1
- package/bin.js +156 -36
- package/dist/assets/{_basePickBy-PmSUrUsK.js → _basePickBy-BlC_TeV6.js} +1 -1
- package/dist/assets/{_baseUniq-HuZouVIz.js → _baseUniq-CVy7rcC1.js} +1 -1
- package/dist/assets/{arc-CJFxtF3d.js → arc-Cd8wvd7z.js} +1 -1
- package/dist/assets/{architectureDiagram-2XIMDMQ5-XA-oU2UG.js → architectureDiagram-2XIMDMQ5-D_f4_aMp.js} +1 -1
- package/dist/assets/{blockDiagram-WCTKOSBZ-Oxp-wAST.js → blockDiagram-WCTKOSBZ-B-y6N5--.js} +1 -1
- package/dist/assets/{c4Diagram-IC4MRINW-D8m5hQH9.js → c4Diagram-IC4MRINW-C3-v3oNT.js} +1 -1
- package/dist/assets/channel-BSC0F15G.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-D2kBTn2O.js → chunk-4BX2VUAB-CMPwQN83.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-Dxqrf5oZ.js → chunk-55IACEB6-Bdkfhvrr.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DoOEFFQC.js → chunk-FMBD7UC4-ptKQX5uF.js} +1 -1
- package/dist/assets/{chunk-JSJVCQXG-BerphV2K.js → chunk-JSJVCQXG-DO0UU_OX.js} +1 -1
- package/dist/assets/{chunk-KX2RTZJC-CxUAqT05.js → chunk-KX2RTZJC-DRj2OZnD.js} +1 -1
- package/dist/assets/{chunk-NQ4KR5QH-fCqZgFkU.js → chunk-NQ4KR5QH-C4Nsf7ww.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-HlpHnJEy.js → chunk-QZHKN3VN-B1GO0Nwy.js} +1 -1
- package/dist/assets/{chunk-WL4C6EOR-D9yxAHyd.js → chunk-WL4C6EOR-lFR6fjR8.js} +1 -1
- package/dist/assets/classDiagram-VBA2DB6C-BHDWMOEz.js +1 -0
- package/dist/assets/classDiagram-v2-RAHNMMFH-BHDWMOEz.js +1 -0
- package/dist/assets/clone-BdN-3iAD.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-F5xOBvqW.js → cose-bilkent-S5V4N54A-IpR9mVIO.js} +1 -1
- package/dist/assets/{dagre-KLK3FWXG-CD3BTpHv.js → dagre-KLK3FWXG-B4YA6T7N.js} +1 -1
- package/dist/assets/{diagram-E7M64L7V-C3D9MCay.js → diagram-E7M64L7V-Do5l6es_.js} +1 -1
- package/dist/assets/{diagram-IFDJBPK2-zJBVM-GK.js → diagram-IFDJBPK2-D5MxfKVv.js} +1 -1
- package/dist/assets/{diagram-P4PSJMXO-BrmFZOLB.js → diagram-P4PSJMXO-Djr28EgW.js} +1 -1
- package/dist/assets/{erDiagram-INFDFZHY-aSMhKiV2.js → erDiagram-INFDFZHY-BuM-rbCL.js} +1 -1
- package/dist/assets/{flowDiagram-PKNHOUZH-DwgX7l8F.js → flowDiagram-PKNHOUZH-By3WGI7Q.js} +1 -1
- package/dist/assets/{ganttDiagram-A5KZAMGK-C57Hz6QW.js → ganttDiagram-A5KZAMGK-GLmBfK72.js} +1 -1
- package/dist/assets/{gitGraphDiagram-K3NZZRJ6-CuchqqGh.js → gitGraphDiagram-K3NZZRJ6-BN0iXeIv.js} +1 -1
- package/dist/assets/{graph-CPFGBV5J.js → graph-CDzuMtjV.js} +1 -1
- package/dist/assets/{index-DMt1cpG6.js → index-DoeSN_Oe.js} +130 -130
- package/dist/assets/{infoDiagram-LFFYTUFH-Dd_5tfX7.js → infoDiagram-LFFYTUFH-C888gaFw.js} +1 -1
- package/dist/assets/{ishikawaDiagram-PHBUUO56-DwosSEvT.js → ishikawaDiagram-PHBUUO56-ChIO9DG-.js} +1 -1
- package/dist/assets/{journeyDiagram-4ABVD52K-BuCxcsX0.js → journeyDiagram-4ABVD52K-CufMUDcs.js} +1 -1
- package/dist/assets/{kanban-definition-K7BYSVSG-DF_1UCkW.js → kanban-definition-K7BYSVSG-BpsSVpX8.js} +1 -1
- package/dist/assets/{layout-DIcS6m1g.js → layout-B8RWVBSF.js} +1 -1
- package/dist/assets/{linear-BXkwBhoJ.js → linear-BJwxtq9r.js} +1 -1
- package/dist/assets/{mindmap-definition-YRQLILUH-DcDvYagd.js → mindmap-definition-YRQLILUH-C6WPimbf.js} +1 -1
- package/dist/assets/{pieDiagram-SKSYHLDU-BmeDeWDM.js → pieDiagram-SKSYHLDU-DeCGMWf8.js} +1 -1
- package/dist/assets/{quadrantDiagram-337W2JSQ-3zfjULUM.js → quadrantDiagram-337W2JSQ-D9TWaS83.js} +1 -1
- package/dist/assets/{requirementDiagram-Z7DCOOCP-B2wQMJpq.js → requirementDiagram-Z7DCOOCP-DTnuXlAq.js} +1 -1
- package/dist/assets/{sankeyDiagram-WA2Y5GQK-__kKlCTq.js → sankeyDiagram-WA2Y5GQK-B2dplCgD.js} +1 -1
- package/dist/assets/{sequenceDiagram-2WXFIKYE-B7O81Vih.js → sequenceDiagram-2WXFIKYE-cBvgSSju.js} +1 -1
- package/dist/assets/{stateDiagram-RAJIS63D-CcJaDrAK.js → stateDiagram-RAJIS63D-Cwr7VtSX.js} +1 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-B59h7VTZ.js +1 -0
- package/dist/assets/{timeline-definition-YZTLITO2-DSaQQqIU.js → timeline-definition-YZTLITO2-Dkp163fK.js} +1 -1
- package/dist/assets/{treemap-KZPCXAKY-9Hcrd8XD.js → treemap-KZPCXAKY-BUWHa5xU.js} +1 -1
- package/dist/assets/{vennDiagram-LZ73GAT5-BqHNyca2.js → vennDiagram-LZ73GAT5-BihD66ma.js} +1 -1
- package/dist/assets/{xychartDiagram-JWTSCODW-BqeYf6Fk.js → xychartDiagram-JWTSCODW-Cw4lPbuZ.js} +1 -1
- package/dist/index.html +1 -1
- package/export-docx.js +12 -4
- package/init.js +19 -14
- package/manifest-cli.mjs +32 -3
- package/package.json +3 -2
- package/serve.js +44 -19
- package/utils/export-markdown.js +486 -0
- package/dist/assets/channel-SoktpVBQ.js +0 -1
- package/dist/assets/classDiagram-VBA2DB6C-BT2AdZTe.js +0 -1
- package/dist/assets/classDiagram-v2-RAHNMMFH-BT2AdZTe.js +0 -1
- package/dist/assets/clone-DOjIfi5r.js +0 -1
- package/dist/assets/stateDiagram-v2-FVOUBMTO-B2goOPt-.js +0 -1
package/init.js
CHANGED
|
@@ -240,15 +240,20 @@ function createIconAliases(iconsDir, extractedEntries, config) {
|
|
|
240
240
|
let aliasCount = 0;
|
|
241
241
|
const missingAliases = [];
|
|
242
242
|
for (const spec of config.aliasSpecs) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
243
|
+
let bestScore = Number.NEGATIVE_INFINITY;
|
|
244
|
+
let bestEntry = null;
|
|
245
|
+
for (const entry of extractedEntries) {
|
|
246
|
+
const score = scoreAliasCandidate(entry, spec, config);
|
|
247
|
+
if (score > bestScore) {
|
|
248
|
+
bestScore = score;
|
|
249
|
+
bestEntry = entry;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (!bestEntry) {
|
|
248
253
|
missingAliases.push(spec.alias);
|
|
249
254
|
continue;
|
|
250
255
|
}
|
|
251
|
-
copyFileSync(
|
|
256
|
+
copyFileSync(bestEntry.outputPath, join(aliasDir, `${spec.alias}.svg`));
|
|
252
257
|
aliasCount += 1;
|
|
253
258
|
}
|
|
254
259
|
return { aliasCount, missingAliases };
|
|
@@ -309,16 +314,16 @@ export async function initWorkspace(targetDir, options = {}) {
|
|
|
309
314
|
let iconDownloadSummary = '';
|
|
310
315
|
if (options.downloadIcons) {
|
|
311
316
|
const results = [];
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
results.push(
|
|
316
|
-
if (
|
|
317
|
-
console.warn(` Missing ${
|
|
317
|
+
const settled = await Promise.allSettled(ICON_PACKAGES.map(config => downloadIconPackage(target, config)));
|
|
318
|
+
for (const outcome of settled) {
|
|
319
|
+
if (outcome.status === 'fulfilled') {
|
|
320
|
+
results.push(outcome.value);
|
|
321
|
+
if (outcome.value.missingAliases.length) {
|
|
322
|
+
console.warn(` Missing ${outcome.value.name} icon aliases: ${outcome.value.missingAliases.join(', ')}`);
|
|
318
323
|
}
|
|
319
324
|
}
|
|
320
|
-
|
|
321
|
-
console.error(` Failed to download ${
|
|
325
|
+
else {
|
|
326
|
+
console.error(` Failed to download icons: ${outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason)}`);
|
|
322
327
|
}
|
|
323
328
|
}
|
|
324
329
|
if (results.length) {
|
package/manifest-cli.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* EAROS Manifest CLI
|
|
2
|
+
* EAROS Manifest CLI
|
|
3
3
|
*
|
|
4
4
|
* Usage (via bin.js):
|
|
5
5
|
* earos manifest # regenerate
|
|
6
6
|
* earos manifest add <file> # add entry
|
|
7
|
-
* earos manifest check
|
|
7
|
+
* earos manifest check [--json] # verify consistency
|
|
8
|
+
* earos manifest list [--json] # list manifest contents
|
|
8
9
|
*
|
|
9
10
|
* EAROS_REPO_ROOT env var is set by bin.js to the detected repo root.
|
|
10
11
|
*/
|
|
@@ -154,6 +155,7 @@ if (subCmd === 'check') {
|
|
|
154
155
|
console.error('No earos.manifest.yaml found. Run `earos manifest` first.')
|
|
155
156
|
process.exit(1)
|
|
156
157
|
}
|
|
158
|
+
const jsonMode = subArgs.includes('--json')
|
|
157
159
|
const errors = []
|
|
158
160
|
const warnings = []
|
|
159
161
|
|
|
@@ -187,6 +189,11 @@ if (subCmd === 'check') {
|
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
191
|
|
|
192
|
+
if (jsonMode) {
|
|
193
|
+
process.stdout.write(JSON.stringify({ consistent: errors.length === 0, errors, warnings }, null, 2) + '\n')
|
|
194
|
+
process.exit(errors.length > 0 ? 1 : 0)
|
|
195
|
+
}
|
|
196
|
+
|
|
190
197
|
if (errors.length === 0 && warnings.length === 0) {
|
|
191
198
|
console.log('✓ Manifest is consistent with filesystem')
|
|
192
199
|
process.exit(0)
|
|
@@ -202,6 +209,28 @@ if (subCmd === 'check') {
|
|
|
202
209
|
process.exit(errors.length > 0 ? 1 : 0)
|
|
203
210
|
}
|
|
204
211
|
|
|
212
|
+
if (subCmd === 'list') {
|
|
213
|
+
const manifest = loadManifest()
|
|
214
|
+
if (!manifest) {
|
|
215
|
+
console.error('No earos.manifest.yaml found. Run `earos manifest` first.')
|
|
216
|
+
process.exit(1)
|
|
217
|
+
}
|
|
218
|
+
if (subArgs.includes('--json')) {
|
|
219
|
+
process.stdout.write(JSON.stringify(manifest, null, 2) + '\n')
|
|
220
|
+
} else {
|
|
221
|
+
const sections = ['core', 'profiles', 'overlays']
|
|
222
|
+
for (const section of sections) {
|
|
223
|
+
const entries = manifest[section] ?? []
|
|
224
|
+
if (entries.length === 0) continue
|
|
225
|
+
console.log(`\n${section}:`)
|
|
226
|
+
for (const e of entries) {
|
|
227
|
+
console.log(` ${e.path} ${e.rubric_id ?? ''} ${e.title ?? ''}`)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
process.exit(0)
|
|
232
|
+
}
|
|
233
|
+
|
|
205
234
|
console.error(`Unknown manifest subcommand: ${subCmd}`)
|
|
206
|
-
console.error('Usage: earos manifest [generate|add <file>|check]')
|
|
235
|
+
console.error('Usage: earos manifest [generate|add <file>|check|list]')
|
|
207
236
|
process.exit(1)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trohde/earos",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Schema-driven editor and CLI for EaROS architecture assessment rubrics and evaluations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"serve.js",
|
|
28
28
|
"init.js",
|
|
29
29
|
"export-docx.js",
|
|
30
|
+
"utils/",
|
|
30
31
|
"manifest-cli.mjs",
|
|
31
32
|
"schemas/",
|
|
32
33
|
"assets/",
|
|
@@ -58,7 +59,6 @@
|
|
|
58
59
|
"express": "^4.22.1",
|
|
59
60
|
"js-yaml": "^4.1.0",
|
|
60
61
|
"jszip": "^3.10.1",
|
|
61
|
-
"mermaid": "^11.13.0",
|
|
62
62
|
"open": "^10.1.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"@types/react-dom": "^18.3.1",
|
|
77
77
|
"@vitejs/plugin-react": "^4.2.1",
|
|
78
78
|
"esbuild": "^0.27.4",
|
|
79
|
+
"mermaid": "^11.13.0",
|
|
79
80
|
"tsx": "^4.21.0",
|
|
80
81
|
"typescript": "^5.4.5",
|
|
81
82
|
"vite": "^5.2.8"
|
package/serve.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import express from 'express';
|
|
6
6
|
import { createServer } from 'http';
|
|
7
7
|
import { readFileSync, writeFileSync, existsSync, readdirSync } from 'fs';
|
|
8
|
-
import { resolve, dirname } from 'path';
|
|
8
|
+
import { resolve, dirname, sep } from 'path';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
10
|
import yaml from 'js-yaml';
|
|
11
11
|
import open from 'open';
|
|
@@ -24,7 +24,7 @@ function findRepoRoot() {
|
|
|
24
24
|
function safeRepoPath(repoRoot, rawPath) {
|
|
25
25
|
const decoded = decodeURIComponent(rawPath);
|
|
26
26
|
const abs = resolve(repoRoot, decoded);
|
|
27
|
-
if (!abs.startsWith(repoRoot))
|
|
27
|
+
if (abs !== repoRoot && !abs.startsWith(repoRoot + sep))
|
|
28
28
|
return null;
|
|
29
29
|
return abs;
|
|
30
30
|
}
|
|
@@ -45,6 +45,8 @@ function findAvailablePort(preferred) {
|
|
|
45
45
|
});
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
+
let evalCache = null;
|
|
49
|
+
const EVAL_CACHE_TTL = 5000;
|
|
48
50
|
export async function startServer(fileArg) {
|
|
49
51
|
const REPO_ROOT = findRepoRoot();
|
|
50
52
|
const distDir = resolve(__dirname, 'dist');
|
|
@@ -53,7 +55,12 @@ export async function startServer(fileArg) {
|
|
|
53
55
|
process.exit(1);
|
|
54
56
|
}
|
|
55
57
|
const app = express();
|
|
56
|
-
app.use(express.json({ limit: '
|
|
58
|
+
app.use(express.json({ limit: '1mb' }));
|
|
59
|
+
app.use((_req, res, next) => {
|
|
60
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
61
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
62
|
+
next();
|
|
63
|
+
});
|
|
57
64
|
// GET /api/manifest or GET /api/files
|
|
58
65
|
const manifestHandler = (_req, res) => {
|
|
59
66
|
const manifestPath = resolve(REPO_ROOT, 'earos.manifest.yaml');
|
|
@@ -84,19 +91,23 @@ export async function startServer(fileArg) {
|
|
|
84
91
|
catch { /* skip unreadable dirs */ }
|
|
85
92
|
return found;
|
|
86
93
|
}
|
|
87
|
-
|
|
94
|
+
function getCachedEvaluationFiles(repoRoot) {
|
|
95
|
+
if (evalCache && Date.now() - evalCache.ts < EVAL_CACHE_TTL)
|
|
96
|
+
return evalCache.files;
|
|
88
97
|
const files = [];
|
|
89
98
|
for (const dir of ['examples', 'evaluations']) {
|
|
90
|
-
files.push(...findEvaluationFiles(resolve(
|
|
99
|
+
files.push(...findEvaluationFiles(resolve(repoRoot, dir), `${dir}/`));
|
|
91
100
|
}
|
|
101
|
+
evalCache = { files, ts: Date.now() };
|
|
102
|
+
return files;
|
|
103
|
+
}
|
|
104
|
+
app.get('/api/evaluations', (_req, res) => {
|
|
105
|
+
const files = getCachedEvaluationFiles(REPO_ROOT);
|
|
92
106
|
res.json({ files });
|
|
93
107
|
});
|
|
94
108
|
// GET /api/evaluations/summary — lightweight metadata per evaluation file
|
|
95
109
|
app.get('/api/evaluations/summary', (_req, res) => {
|
|
96
|
-
const files =
|
|
97
|
-
for (const dir of ['examples', 'evaluations']) {
|
|
98
|
-
files.push(...findEvaluationFiles(resolve(REPO_ROOT, dir), `${dir}/`));
|
|
99
|
-
}
|
|
110
|
+
const files = getCachedEvaluationFiles(REPO_ROOT);
|
|
100
111
|
const summaries = files.map((f) => {
|
|
101
112
|
const absPath = resolve(REPO_ROOT, f.path);
|
|
102
113
|
try {
|
|
@@ -132,28 +143,38 @@ export async function startServer(fileArg) {
|
|
|
132
143
|
res.json(yaml.load(readFileSync(absPath, 'utf8')));
|
|
133
144
|
}
|
|
134
145
|
catch (e) {
|
|
135
|
-
|
|
146
|
+
console.error('[API error]', e);
|
|
147
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
136
148
|
}
|
|
137
149
|
});
|
|
138
150
|
// POST /api/file/:path
|
|
139
|
-
app.post('/api/file/*', (req, res) => {
|
|
151
|
+
app.post('/api/file/*', express.json({ limit: '5mb' }), (req, res) => {
|
|
140
152
|
const rawPath = req.params[0];
|
|
141
153
|
const absPath = safeRepoPath(REPO_ROOT, rawPath);
|
|
142
154
|
if (!absPath) {
|
|
143
155
|
res.status(403).json({ error: 'Path outside repo root' });
|
|
144
156
|
return;
|
|
145
157
|
}
|
|
158
|
+
if (!absPath.endsWith('.yaml') && !absPath.endsWith('.yml')) {
|
|
159
|
+
res.status(400).json({ error: 'Only YAML files can be written' });
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
146
162
|
try {
|
|
147
163
|
const content = yaml.dump(req.body, { lineWidth: 120, noRefs: true });
|
|
148
164
|
writeFileSync(absPath, content, 'utf8');
|
|
165
|
+
// Invalidate eval cache if writing an evaluation file
|
|
166
|
+
if (absPath.endsWith('.evaluation.yaml') || absPath.includes('/evaluations/')) {
|
|
167
|
+
evalCache = null;
|
|
168
|
+
}
|
|
149
169
|
res.json({ ok: true });
|
|
150
170
|
}
|
|
151
171
|
catch (e) {
|
|
152
|
-
|
|
172
|
+
console.error('[API error]', e);
|
|
173
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
153
174
|
}
|
|
154
175
|
});
|
|
155
176
|
// POST /api/export/docx — generate a Word document from artifact JSON
|
|
156
|
-
app.post('/api/export/docx', async (req, res) => {
|
|
177
|
+
app.post('/api/export/docx', express.json({ limit: '25mb' }), async (req, res) => {
|
|
157
178
|
try {
|
|
158
179
|
const payload = req.body;
|
|
159
180
|
const artifactData = payload?.artifactData ?? payload;
|
|
@@ -176,11 +197,12 @@ export async function startServer(fileArg) {
|
|
|
176
197
|
res.status(400).json({ error: message });
|
|
177
198
|
return;
|
|
178
199
|
}
|
|
179
|
-
|
|
200
|
+
console.error('[API error]', e);
|
|
201
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
180
202
|
}
|
|
181
203
|
});
|
|
182
204
|
// POST /api/export/docx/rubric — generate a Word document from rubric JSON
|
|
183
|
-
app.post('/api/export/docx/rubric', async (req, res) => {
|
|
205
|
+
app.post('/api/export/docx/rubric', express.json({ limit: '25mb' }), async (req, res) => {
|
|
184
206
|
try {
|
|
185
207
|
const data = req.body;
|
|
186
208
|
if (!data || typeof data !== 'object') {
|
|
@@ -194,11 +216,12 @@ export async function startServer(fileArg) {
|
|
|
194
216
|
res.send(buf);
|
|
195
217
|
}
|
|
196
218
|
catch (e) {
|
|
197
|
-
|
|
219
|
+
console.error('[API error]', e);
|
|
220
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
198
221
|
}
|
|
199
222
|
});
|
|
200
223
|
// POST /api/export/docx/evaluation — generate a Word document from evaluation JSON
|
|
201
|
-
app.post('/api/export/docx/evaluation', async (req, res) => {
|
|
224
|
+
app.post('/api/export/docx/evaluation', express.json({ limit: '25mb' }), async (req, res) => {
|
|
202
225
|
try {
|
|
203
226
|
const data = req.body;
|
|
204
227
|
if (!data || typeof data !== 'object') {
|
|
@@ -212,7 +235,8 @@ export async function startServer(fileArg) {
|
|
|
212
235
|
res.send(buf);
|
|
213
236
|
}
|
|
214
237
|
catch (e) {
|
|
215
|
-
|
|
238
|
+
console.error('[API error]', e);
|
|
239
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
216
240
|
}
|
|
217
241
|
});
|
|
218
242
|
// Unknown API routes
|
|
@@ -228,7 +252,8 @@ export async function startServer(fileArg) {
|
|
|
228
252
|
res.sendFile(resolve(distDir, 'index.html'));
|
|
229
253
|
});
|
|
230
254
|
const port = await findAvailablePort(process.env.PORT ? parseInt(process.env.PORT, 10) : 3000);
|
|
231
|
-
|
|
255
|
+
const host = process.env.EAROS_HOST ?? '127.0.0.1';
|
|
256
|
+
app.listen(port, host, () => {
|
|
232
257
|
const url = fileArg
|
|
233
258
|
? `http://localhost:${port}?file=${encodeURIComponent(fileArg)}`
|
|
234
259
|
: `http://localhost:${port}`;
|