viberadar 0.3.140 → 0.3.141
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/scanner/index.d.ts +27 -0
- package/dist/scanner/index.d.ts.map +1 -1
- package/dist/scanner/index.js +53 -0
- package/dist/scanner/index.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +165 -3
- package/dist/server/index.js.map +1 -1
- package/dist/ui/dashboard.html +183 -0
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -1499,6 +1499,69 @@ function buildScreenshotInstructions(featureKey, routes) {
|
|
|
1499
1499
|
`- Под каждым скриншотом — курсивная подпись: *Экран входа в систему*`,
|
|
1500
1500
|
].join('\n');
|
|
1501
1501
|
}
|
|
1502
|
+
function buildScenarioPrompt(scenario, featureDocs, currentDoc, nextVersion) {
|
|
1503
|
+
const outPath = `docs/scenarios/${scenario.key}/v${nextVersion}.md`;
|
|
1504
|
+
const isFirstVersion = nextVersion === 1;
|
|
1505
|
+
const featureDocBlocks = featureDocs.map(fd => `### Документация фичи "${fd.label}" (${fd.key}):\n${fd.content.slice(0, 3000)}${fd.content.length > 3000 ? '\n...(обрезано)' : ''}`).join('\n\n');
|
|
1506
|
+
if (isFirstVersion) {
|
|
1507
|
+
return [
|
|
1508
|
+
`Напиши пользовательский сценарий "${scenario.label}".`,
|
|
1509
|
+
``,
|
|
1510
|
+
scenario.description ? `Цель сценария: ${scenario.description}` : '',
|
|
1511
|
+
``,
|
|
1512
|
+
`Сценарий охватывает следующие фичи (в порядке шагов): ${scenario.featureKeys.join(' → ')}`,
|
|
1513
|
+
``,
|
|
1514
|
+
`Ниже — документация по каждой задействованной фиче. Используй её как источник информации:`,
|
|
1515
|
+
``,
|
|
1516
|
+
featureDocBlocks,
|
|
1517
|
+
``,
|
|
1518
|
+
`Задача:`,
|
|
1519
|
+
`1. Напиши единый пошаговый сценарий от лица пользователя`,
|
|
1520
|
+
`2. Каждый шаг должен быть конкретным действием: что открыть, что нажать, что ввести`,
|
|
1521
|
+
`3. Переходы между фичами должны быть плавными — пользователь не видит "фичей", он видит задачу`,
|
|
1522
|
+
``,
|
|
1523
|
+
`Структура документа:`,
|
|
1524
|
+
`# ${scenario.label}`,
|
|
1525
|
+
`> одна строка — какую задачу решает пользователь`,
|
|
1526
|
+
``,
|
|
1527
|
+
`## Что понадобится`,
|
|
1528
|
+
`- список предусловий (что должно быть настроено/готово заранее)`,
|
|
1529
|
+
``,
|
|
1530
|
+
`## Шаги`,
|
|
1531
|
+
`### Шаг 1. [Название действия]`,
|
|
1532
|
+
`(описание + что пользователь видит в итоге)`,
|
|
1533
|
+
`### Шаг 2. ...`,
|
|
1534
|
+
`(и т.д.)`,
|
|
1535
|
+
``,
|
|
1536
|
+
`## Результат`,
|
|
1537
|
+
`Что пользователь получил в итоге всего сценария.`,
|
|
1538
|
+
``,
|
|
1539
|
+
`## Возможные проблемы`,
|
|
1540
|
+
`Таблица | Проблема | Решение |`,
|
|
1541
|
+
``,
|
|
1542
|
+
`Требования:`,
|
|
1543
|
+
`- Простой язык без технических терминов`,
|
|
1544
|
+
`- Не упоминать компоненты, файлы, API`,
|
|
1545
|
+
`- Описывать только то, что видит пользователь`,
|
|
1546
|
+
`- Запиши результат в: ${outPath}`,
|
|
1547
|
+
`- Создай директорию docs/scenarios/${scenario.key}/ если её нет`,
|
|
1548
|
+
].filter(Boolean).join('\n');
|
|
1549
|
+
}
|
|
1550
|
+
return [
|
|
1551
|
+
`Актуализируй пользовательский сценарий "${scenario.label}".`,
|
|
1552
|
+
``,
|
|
1553
|
+
`Текущая версия документа (v${nextVersion - 1}):`,
|
|
1554
|
+
`${currentDoc?.slice(0, 2000) || '(пусто)'}`,
|
|
1555
|
+
``,
|
|
1556
|
+
`Актуальная документация по фичам сценария:`,
|
|
1557
|
+
``,
|
|
1558
|
+
featureDocBlocks,
|
|
1559
|
+
``,
|
|
1560
|
+
`Задача: обнови сценарий с учётом изменений в фичах. Сохрани структуру и стиль.`,
|
|
1561
|
+
`Запиши результат в: ${outPath}`,
|
|
1562
|
+
`Создай директорию docs/scenarios/${scenario.key}/ если её нет`,
|
|
1563
|
+
].filter(Boolean).join('\n');
|
|
1564
|
+
}
|
|
1502
1565
|
function buildActualizeDocsPrompt(feat, modules, currentDoc, nextVersion, changedFiles, screenshotsCaptured = false) {
|
|
1503
1566
|
const sourceFiles = modules
|
|
1504
1567
|
.filter(m => m.featureKeys.includes(feat.key) && m.type !== 'test' && !m.isInfra)
|
|
@@ -2219,6 +2282,46 @@ function startServer({ data: initialData, port, projectRoot }) {
|
|
|
2219
2282
|
}
|
|
2220
2283
|
prompt = buildActualizeDocsPrompt(feat, currentData.modules, currentDoc, nextVersion, changedFiles, screenshotsCaptured);
|
|
2221
2284
|
}
|
|
2285
|
+
else if (task === 'actualize-scenario') {
|
|
2286
|
+
const scenarioKey = featureKey; // reuse featureKey param as scenarioKey
|
|
2287
|
+
if (!scenarioKey || !currentData.scenarios) {
|
|
2288
|
+
failBeforeStart('Сценарий не найден');
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
const scenarioStatus = currentData.scenarios.scenarios.find((s) => s.key === scenarioKey);
|
|
2292
|
+
if (!scenarioStatus) {
|
|
2293
|
+
failBeforeStart(`Сценарий ${scenarioKey} не найден`);
|
|
2294
|
+
return;
|
|
2295
|
+
}
|
|
2296
|
+
const latestVer = scenarioStatus.latestVersion ?? null;
|
|
2297
|
+
const nextVer = (latestVer ?? 0) + 1;
|
|
2298
|
+
let currentDoc = null;
|
|
2299
|
+
if (latestVer !== null) {
|
|
2300
|
+
try {
|
|
2301
|
+
currentDoc = fs.readFileSync(path.join(projectRoot, 'docs', 'scenarios', scenarioKey, `v${latestVer}.md`), 'utf-8');
|
|
2302
|
+
}
|
|
2303
|
+
catch { }
|
|
2304
|
+
}
|
|
2305
|
+
// Read latest docs for each referenced feature
|
|
2306
|
+
const featureDocs = [];
|
|
2307
|
+
for (const fk of scenarioStatus.featureKeys) {
|
|
2308
|
+
const fLabel = scenarioStatus.featureLabels[scenarioStatus.featureKeys.indexOf(fk)] || fk;
|
|
2309
|
+
const fDocDir = path.join(projectRoot, 'docs', 'features', fk);
|
|
2310
|
+
try {
|
|
2311
|
+
const entries = fs.readdirSync(fDocDir);
|
|
2312
|
+
const versions = entries
|
|
2313
|
+
.map((e) => { const m = e.match(/^v(\d+)\.md$/); return m ? { file: e, n: parseInt(m[1], 10) } : null; })
|
|
2314
|
+
.filter((x) => x !== null)
|
|
2315
|
+
.sort((a, b) => b.n - a.n);
|
|
2316
|
+
if (versions.length) {
|
|
2317
|
+
const content = fs.readFileSync(path.join(fDocDir, versions[0].file), 'utf-8');
|
|
2318
|
+
featureDocs.push({ key: fk, label: fLabel, content });
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
catch { }
|
|
2322
|
+
}
|
|
2323
|
+
prompt = buildScenarioPrompt(scenarioStatus, featureDocs, currentDoc, nextVer);
|
|
2324
|
+
}
|
|
2222
2325
|
else if (task === 'generate-pipelines') {
|
|
2223
2326
|
if (!featureKey || !currentData.features) {
|
|
2224
2327
|
failBeforeStart('Фича не найдена');
|
|
@@ -3551,6 +3654,32 @@ function startServer({ data: initialData, port, projectRoot }) {
|
|
|
3551
3654
|
return;
|
|
3552
3655
|
}
|
|
3553
3656
|
// ── Documentation API ─────────────────────────────────────────────────────
|
|
3657
|
+
if (url === '/api/scenarios/content' && req.method === 'GET') {
|
|
3658
|
+
const scenarioKey = parsedUrl.searchParams.get('scenario');
|
|
3659
|
+
if (!scenarioKey) {
|
|
3660
|
+
res.writeHead(400, jsonH);
|
|
3661
|
+
res.end(JSON.stringify({ error: 'Missing scenario param' }));
|
|
3662
|
+
return;
|
|
3663
|
+
}
|
|
3664
|
+
const docDir = path.join(projectRoot, 'docs', 'scenarios', scenarioKey);
|
|
3665
|
+
try {
|
|
3666
|
+
const entries = fs.readdirSync(docDir);
|
|
3667
|
+
const versions = entries
|
|
3668
|
+
.map(e => { const m = e.match(/^v(\d+)\.md$/); return m ? { file: e, n: parseInt(m[1], 10) } : null; })
|
|
3669
|
+
.filter((x) => x !== null)
|
|
3670
|
+
.sort((a, b) => b.n - a.n);
|
|
3671
|
+
if (versions.length === 0)
|
|
3672
|
+
throw new Error('no versions');
|
|
3673
|
+
const content = fs.readFileSync(path.join(docDir, versions[0].file), 'utf-8');
|
|
3674
|
+
res.writeHead(200, jsonH);
|
|
3675
|
+
res.end(JSON.stringify({ content, exists: true, version: versions[0].n }));
|
|
3676
|
+
}
|
|
3677
|
+
catch {
|
|
3678
|
+
res.writeHead(200, jsonH);
|
|
3679
|
+
res.end(JSON.stringify({ content: null, exists: false }));
|
|
3680
|
+
}
|
|
3681
|
+
return;
|
|
3682
|
+
}
|
|
3554
3683
|
if (url === '/api/docs/content' && req.method === 'GET') {
|
|
3555
3684
|
const featureKey = parsedUrl.searchParams.get('feature');
|
|
3556
3685
|
if (!featureKey) {
|
|
@@ -3655,6 +3784,25 @@ function startServer({ data: initialData, port, projectRoot }) {
|
|
|
3655
3784
|
if (url.startsWith('/api/docs/export/md') && req.method === 'GET') {
|
|
3656
3785
|
try {
|
|
3657
3786
|
const featureKey = parsedUrl.searchParams.get('feature');
|
|
3787
|
+
const scenarioKey = parsedUrl.searchParams.get('scenario');
|
|
3788
|
+
const translit = (s) => { const m = { а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'yo', ж: 'zh', з: 'z', и: 'i', й: 'j', к: 'k', л: 'l', м: 'm', н: 'n', о: 'o', п: 'p', р: 'r', с: 's', т: 't', у: 'u', ф: 'f', х: 'h', ц: 'ts', ч: 'ch', ш: 'sh', щ: 'sch', ъ: '', ы: 'y', ь: '', э: 'e', ю: 'yu', я: 'ya' }; return s.toLowerCase().split('').map(c => m[c] ?? c).join('').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); };
|
|
3789
|
+
// Scenario export
|
|
3790
|
+
if (scenarioKey) {
|
|
3791
|
+
const scenario = currentData.scenarios?.scenarios.find((s) => s.key === scenarioKey);
|
|
3792
|
+
if (!scenario?.docExists) {
|
|
3793
|
+
res.writeHead(404, jsonH);
|
|
3794
|
+
res.end(JSON.stringify({ error: 'Scenario doc not found' }));
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3797
|
+
const docDir = path.join(projectRoot, 'docs', 'scenarios', scenarioKey);
|
|
3798
|
+
const entries = fs.readdirSync(docDir);
|
|
3799
|
+
const versions = entries.map((e) => { const mv = e.match(/^v(\d+)\.md$/); return mv ? { file: e, n: parseInt(mv[1], 10) } : null; }).filter((x) => x !== null).sort((a, b) => b.n - a.n);
|
|
3800
|
+
const content = fs.readFileSync(path.join(docDir, versions[0].file), 'utf-8');
|
|
3801
|
+
const buf = Buffer.from(content, 'utf-8');
|
|
3802
|
+
res.writeHead(200, { 'Content-Type': 'text/markdown; charset=utf-8', 'Content-Disposition': `attachment; filename="${translit(scenario.label)}-scenario-v${scenario.latestVersion}.md"`, 'Content-Length': buf.length });
|
|
3803
|
+
res.end(buf);
|
|
3804
|
+
return;
|
|
3805
|
+
}
|
|
3658
3806
|
const docReport = currentData.documentation;
|
|
3659
3807
|
if (!docReport) {
|
|
3660
3808
|
res.writeHead(400, jsonH);
|
|
@@ -3694,7 +3842,6 @@ function startServer({ data: initialData, port, projectRoot }) {
|
|
|
3694
3842
|
catch {
|
|
3695
3843
|
return 'docs';
|
|
3696
3844
|
} })();
|
|
3697
|
-
const translit = (s) => { const m = { а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'yo', ж: 'zh', з: 'z', и: 'i', й: 'j', к: 'k', л: 'l', м: 'm', н: 'n', о: 'o', п: 'p', р: 'r', с: 's', т: 't', у: 'u', ф: 'f', х: 'h', ц: 'ts', ч: 'ch', ш: 'sh', щ: 'sch', ъ: '', ы: 'y', ь: '', э: 'e', ю: 'yu', я: 'ya' }; return s.toLowerCase().split('').map(c => m[c] ?? c).join('').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); };
|
|
3698
3845
|
const filename = featureKey && features.length === 1
|
|
3699
3846
|
? `${translit(features[0].label)}-v${features[0].latestVersion || 1}.md`
|
|
3700
3847
|
: `${projName}-docs.md`;
|
|
@@ -3716,6 +3863,22 @@ function startServer({ data: initialData, port, projectRoot }) {
|
|
|
3716
3863
|
if (url.startsWith('/api/docs/export/docx') && req.method === 'GET') {
|
|
3717
3864
|
try {
|
|
3718
3865
|
const featureKey = parsedUrl.searchParams.get('feature');
|
|
3866
|
+
const scenarioKeyDocx = parsedUrl.searchParams.get('scenario');
|
|
3867
|
+
const translit2 = (s) => { const m = { а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'yo', ж: 'zh', з: 'z', и: 'i', й: 'j', к: 'k', л: 'l', м: 'm', н: 'n', о: 'o', п: 'p', р: 'r', с: 's', т: 't', у: 'u', ф: 'f', х: 'h', ц: 'ts', ч: 'ch', ш: 'sh', щ: 'sch', ъ: '', ы: 'y', ь: '', э: 'e', ю: 'yu', я: 'ya' }; return s.toLowerCase().split('').map(c => m[c] ?? c).join('').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); };
|
|
3868
|
+
// Scenario DOCX export
|
|
3869
|
+
if (scenarioKeyDocx) {
|
|
3870
|
+
const scenario = currentData.scenarios?.scenarios.find((s) => s.key === scenarioKeyDocx);
|
|
3871
|
+
if (!scenario?.docExists) {
|
|
3872
|
+
res.writeHead(404, jsonH);
|
|
3873
|
+
res.end(JSON.stringify({ error: 'Scenario doc not found' }));
|
|
3874
|
+
return;
|
|
3875
|
+
}
|
|
3876
|
+
const docxBuf = (0, docx_1.buildDocx)([{ key: scenarioKeyDocx, label: scenario.label, latestVersion: scenario.latestVersion, docVersions: scenario.docVersions }], path.join(projectRoot, 'docs', 'scenarios'));
|
|
3877
|
+
const filename2 = `${translit2(scenario.label)}-scenario-v${scenario.latestVersion}.docx`;
|
|
3878
|
+
res.writeHead(200, { 'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'Content-Disposition': `attachment; filename="${filename2}"`, 'Content-Length': docxBuf.length });
|
|
3879
|
+
res.end(docxBuf);
|
|
3880
|
+
return;
|
|
3881
|
+
}
|
|
3719
3882
|
const docReport = currentData.documentation;
|
|
3720
3883
|
if (!docReport) {
|
|
3721
3884
|
res.writeHead(400, jsonH);
|
|
@@ -3737,9 +3900,8 @@ function startServer({ data: initialData, port, projectRoot }) {
|
|
|
3737
3900
|
catch {
|
|
3738
3901
|
return 'docs';
|
|
3739
3902
|
} })();
|
|
3740
|
-
const translit = (s) => { const m = { а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'yo', ж: 'zh', з: 'z', и: 'i', й: 'j', к: 'k', л: 'l', м: 'm', н: 'n', о: 'o', п: 'p', р: 'r', с: 's', т: 't', у: 'u', ф: 'f', х: 'h', ц: 'ts', ч: 'ch', ш: 'sh', щ: 'sch', ъ: '', ы: 'y', ь: '', э: 'e', ю: 'yu', я: 'ya' }; return s.toLowerCase().split('').map(c => m[c] ?? c).join('').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); };
|
|
3741
3903
|
const filename = featureKey && features.length === 1
|
|
3742
|
-
? `${
|
|
3904
|
+
? `${translit2(features[0].label)}-v${features[0].latestVersion || 1}.docx`
|
|
3743
3905
|
: `${projName}-docs.docx`;
|
|
3744
3906
|
res.writeHead(200, {
|
|
3745
3907
|
'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|