@application-kit/acceptance 0.0.0-stage → 0.1.1
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/CHANGELOG.md +13 -0
- package/dist/cjs/bin/application-scenarios.d.ts +2 -0
- package/dist/cjs/bin/application-scenarios.js +15 -0
- package/dist/cjs/bin/index.d.ts +1 -0
- package/dist/cjs/bin/index.js +17 -0
- package/dist/cjs/cadence/index.d.ts +1 -0
- package/dist/cjs/cadence/index.js +17 -0
- package/dist/cjs/cadence/resolve-cadence.d.ts +8 -0
- package/dist/cjs/cadence/resolve-cadence.js +18 -0
- package/dist/cjs/environments/environment.interface.d.ts +5 -0
- package/dist/cjs/environments/environment.interface.js +2 -0
- package/dist/cjs/environments/index.d.ts +3 -0
- package/dist/cjs/environments/index.js +19 -0
- package/dist/cjs/environments/resolve-environment.d.ts +10 -0
- package/dist/cjs/environments/resolve-environment.js +20 -0
- package/dist/cjs/environments/secrets.d.ts +4 -0
- package/dist/cjs/environments/secrets.js +14 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js +18 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/scenarios/acceptance-class.d.ts +26 -0
- package/dist/cjs/scenarios/acceptance-class.js +100 -0
- package/dist/cjs/scenarios/board.d.ts +6 -0
- package/dist/cjs/scenarios/board.js +98 -0
- package/dist/cjs/scenarios/classes.d.ts +16 -0
- package/dist/cjs/scenarios/classes.js +48 -0
- package/dist/cjs/scenarios/classify.d.ts +35 -0
- package/dist/cjs/scenarios/classify.js +110 -0
- package/dist/cjs/scenarios/config.d.ts +17 -0
- package/dist/cjs/scenarios/config.js +67 -0
- package/dist/cjs/scenarios/coverage-data.d.ts +53 -0
- package/dist/cjs/scenarios/coverage-data.js +118 -0
- package/dist/cjs/scenarios/coverage-json.d.ts +2 -0
- package/dist/cjs/scenarios/coverage-json.js +94 -0
- package/dist/cjs/scenarios/coverage-open.d.ts +16 -0
- package/dist/cjs/scenarios/coverage-open.js +76 -0
- package/dist/cjs/scenarios/coverage-page.d.ts +2 -0
- package/dist/cjs/scenarios/coverage-page.js +108 -0
- package/dist/cjs/scenarios/coverage-removed.d.ts +13 -0
- package/dist/cjs/scenarios/coverage-removed.js +30 -0
- package/dist/cjs/scenarios/coverage.d.ts +26 -0
- package/dist/cjs/scenarios/coverage.js +91 -0
- package/dist/cjs/scenarios/dates.d.ts +8 -0
- package/dist/cjs/scenarios/dates.js +93 -0
- package/dist/cjs/scenarios/declared.d.ts +18 -0
- package/dist/cjs/scenarios/declared.js +69 -0
- package/dist/cjs/scenarios/features.d.ts +13 -0
- package/dist/cjs/scenarios/features.js +112 -0
- package/dist/cjs/scenarios/gherkin.d.ts +10 -0
- package/dist/cjs/scenarios/gherkin.js +75 -0
- package/dist/cjs/scenarios/git.d.ts +15 -0
- package/dist/cjs/scenarios/git.js +83 -0
- package/dist/cjs/scenarios/index.d.ts +27 -0
- package/dist/cjs/scenarios/index.js +43 -0
- package/dist/cjs/scenarios/inventory.d.ts +8 -0
- package/dist/cjs/scenarios/inventory.js +55 -0
- package/dist/cjs/scenarios/links.d.ts +4 -0
- package/dist/cjs/scenarios/links.js +99 -0
- package/dist/cjs/scenarios/lint-inputs.d.ts +12 -0
- package/dist/cjs/scenarios/lint-inputs.js +118 -0
- package/dist/cjs/scenarios/lint.d.ts +8 -0
- package/dist/cjs/scenarios/lint.js +94 -0
- package/dist/cjs/scenarios/projects.d.ts +21 -0
- package/dist/cjs/scenarios/projects.js +115 -0
- package/dist/cjs/scenarios/python-text.d.ts +11 -0
- package/dist/cjs/scenarios/python-text.js +95 -0
- package/dist/cjs/scenarios/remote.d.ts +6 -0
- package/dist/cjs/scenarios/remote.js +29 -0
- package/dist/cjs/scenarios/removed-list.d.ts +15 -0
- package/dist/cjs/scenarios/removed-list.js +62 -0
- package/dist/cjs/scenarios/report.d.ts +6 -0
- package/dist/cjs/scenarios/report.js +10 -0
- package/dist/cjs/scenarios/run.d.ts +10 -0
- package/dist/cjs/scenarios/run.js +145 -0
- package/dist/cjs/scenarios/task-links.d.ts +15 -0
- package/dist/cjs/scenarios/task-links.js +54 -0
- package/dist/esm/bin/application-scenarios.d.ts +2 -0
- package/dist/esm/bin/application-scenarios.js +10 -0
- package/dist/esm/bin/index.d.ts +1 -0
- package/dist/esm/bin/index.js +1 -0
- package/dist/esm/cadence/index.d.ts +1 -0
- package/dist/esm/cadence/index.js +1 -0
- package/dist/esm/cadence/resolve-cadence.d.ts +8 -0
- package/dist/esm/cadence/resolve-cadence.js +14 -0
- package/dist/esm/environments/environment.interface.d.ts +5 -0
- package/dist/esm/environments/environment.interface.js +1 -0
- package/dist/esm/environments/index.d.ts +3 -0
- package/dist/esm/environments/index.js +3 -0
- package/dist/esm/environments/resolve-environment.d.ts +10 -0
- package/dist/esm/environments/resolve-environment.js +16 -0
- package/dist/esm/environments/secrets.d.ts +4 -0
- package/dist/esm/environments/secrets.js +9 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/scenarios/acceptance-class.d.ts +26 -0
- package/dist/esm/scenarios/acceptance-class.js +93 -0
- package/dist/esm/scenarios/board.d.ts +6 -0
- package/dist/esm/scenarios/board.js +92 -0
- package/dist/esm/scenarios/classes.d.ts +16 -0
- package/dist/esm/scenarios/classes.js +42 -0
- package/dist/esm/scenarios/classify.d.ts +35 -0
- package/dist/esm/scenarios/classify.js +106 -0
- package/dist/esm/scenarios/config.d.ts +17 -0
- package/dist/esm/scenarios/config.js +58 -0
- package/dist/esm/scenarios/coverage-data.d.ts +53 -0
- package/dist/esm/scenarios/coverage-data.js +109 -0
- package/dist/esm/scenarios/coverage-json.d.ts +2 -0
- package/dist/esm/scenarios/coverage-json.js +90 -0
- package/dist/esm/scenarios/coverage-open.d.ts +16 -0
- package/dist/esm/scenarios/coverage-open.js +71 -0
- package/dist/esm/scenarios/coverage-page.d.ts +2 -0
- package/dist/esm/scenarios/coverage-page.js +104 -0
- package/dist/esm/scenarios/coverage-removed.d.ts +13 -0
- package/dist/esm/scenarios/coverage-removed.js +26 -0
- package/dist/esm/scenarios/coverage.d.ts +26 -0
- package/dist/esm/scenarios/coverage.js +84 -0
- package/dist/esm/scenarios/dates.d.ts +8 -0
- package/dist/esm/scenarios/dates.js +87 -0
- package/dist/esm/scenarios/declared.d.ts +18 -0
- package/dist/esm/scenarios/declared.js +62 -0
- package/dist/esm/scenarios/features.d.ts +13 -0
- package/dist/esm/scenarios/features.js +96 -0
- package/dist/esm/scenarios/gherkin.d.ts +10 -0
- package/dist/esm/scenarios/gherkin.js +68 -0
- package/dist/esm/scenarios/git.d.ts +15 -0
- package/dist/esm/scenarios/git.js +72 -0
- package/dist/esm/scenarios/index.d.ts +27 -0
- package/dist/esm/scenarios/index.js +27 -0
- package/dist/esm/scenarios/inventory.d.ts +8 -0
- package/dist/esm/scenarios/inventory.js +48 -0
- package/dist/esm/scenarios/links.d.ts +4 -0
- package/dist/esm/scenarios/links.js +95 -0
- package/dist/esm/scenarios/lint-inputs.d.ts +12 -0
- package/dist/esm/scenarios/lint-inputs.js +110 -0
- package/dist/esm/scenarios/lint.d.ts +8 -0
- package/dist/esm/scenarios/lint.js +89 -0
- package/dist/esm/scenarios/projects.d.ts +21 -0
- package/dist/esm/scenarios/projects.js +104 -0
- package/dist/esm/scenarios/python-text.d.ts +11 -0
- package/dist/esm/scenarios/python-text.js +81 -0
- package/dist/esm/scenarios/remote.d.ts +6 -0
- package/dist/esm/scenarios/remote.js +24 -0
- package/dist/esm/scenarios/removed-list.d.ts +15 -0
- package/dist/esm/scenarios/removed-list.js +57 -0
- package/dist/esm/scenarios/report.d.ts +6 -0
- package/dist/esm/scenarios/report.js +6 -0
- package/dist/esm/scenarios/run.d.ts +10 -0
- package/dist/esm/scenarios/run.js +141 -0
- package/dist/esm/scenarios/task-links.d.ts +15 -0
- package/dist/esm/scenarios/task-links.js +50 -0
- package/package.json +65 -3
- package/README.md +0 -3
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderPage = void 0;
|
|
4
|
+
const coverage_data_js_1 = require("./coverage-data.js");
|
|
5
|
+
const coverage_open_js_1 = require("./coverage-open.js");
|
|
6
|
+
const python_text_js_1 = require("./python-text.js");
|
|
7
|
+
const pageStyle = 'body{font:14px/1.5 system-ui,sans-serif;margin:24px;color:#1f2328;background:#fff}'
|
|
8
|
+
+ 'h1{font-size:22px}h2{font-size:17px;margin-top:28px}p.note{color:#59636e;max-width:880px}'
|
|
9
|
+
+ 'table{border-collapse:collapse;margin:8px 0}'
|
|
10
|
+
+ 'th,td{border:1px solid #d0d7de;padding:4px 8px;text-align:left;vertical-align:top}th{background:#f6f8fa}'
|
|
11
|
+
+ '@media (prefers-color-scheme:dark){body{color:#e6edf3;background:#0d1117}th{background:#161b22}'
|
|
12
|
+
+ 'th,td{border-color:#30363d}p.note{color:#9198a1}a{color:#4493f8}}';
|
|
13
|
+
const note = 'Классы приёмки story: e2e — сценарий этого репозитория против стенда снаружи, code-tests — тесты кода в '
|
|
14
|
+
+ 'репозитории проекта, none — проверять нечего; story с набором классов учтена в каждом. Покрыта — закрытая story '
|
|
15
|
+
+ 'с e2e, у которой хоть один сценарий идёт в прогон: не снят и без отметок @todo, @not-implemented, @quarantine. '
|
|
16
|
+
+ 'Знаменатель — закрытые story с e2e без тех, все сценарии которых сняты.';
|
|
17
|
+
const projectHead = [
|
|
18
|
+
'Проект',
|
|
19
|
+
'Задачи на',
|
|
20
|
+
'Закрытых',
|
|
21
|
+
'Знаменатель',
|
|
22
|
+
'Покрыто',
|
|
23
|
+
'Доля',
|
|
24
|
+
...coverage_data_js_1.uncoveredOrder.map((state) => (0, python_text_js_1.capitalize)(coverage_data_js_1.uncoveredLabels[state])),
|
|
25
|
+
'Все сняты',
|
|
26
|
+
'code-tests',
|
|
27
|
+
'none',
|
|
28
|
+
'Без класса',
|
|
29
|
+
];
|
|
30
|
+
const linkTo = (text, url) => (url === undefined ? (0, python_text_js_1.escapeHtml)(text) : `<a href="${(0, python_text_js_1.escapeHtml)(url)}">${(0, python_text_js_1.escapeHtml)(text)}</a>`);
|
|
31
|
+
const table = (head, rows) => {
|
|
32
|
+
if (rows.length === 0) {
|
|
33
|
+
return '<p>нет</p>';
|
|
34
|
+
}
|
|
35
|
+
const cells = head.map((cell) => `<th>${(0, python_text_js_1.escapeHtml)(cell)}</th>`).join('');
|
|
36
|
+
const body = rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join('')}</tr>`).join('');
|
|
37
|
+
return `<table><tr>${cells}</tr>${body}</table>`;
|
|
38
|
+
};
|
|
39
|
+
const items = (title, values) => {
|
|
40
|
+
const list = values.map((value) => `<li>${(0, python_text_js_1.escapeHtml)(value)}</li>`).join('');
|
|
41
|
+
const body = values.length > 0 ? `<ul>${list}</ul>` : '<p>нет</p>';
|
|
42
|
+
return `<h2>${(0, python_text_js_1.escapeHtml)(title)} (${values.length})</h2>${body}`;
|
|
43
|
+
};
|
|
44
|
+
const numbers = (values) => values.map((value) => (0, python_text_js_1.escapeHtml)(String(value)));
|
|
45
|
+
const shareCell = (share) => (share === undefined ? '—' : (0, python_text_js_1.escapeHtml)(`${share}%`));
|
|
46
|
+
const projectRows = (data) => data.projects.map((item) => [
|
|
47
|
+
(0, python_text_js_1.escapeHtml)(item.project),
|
|
48
|
+
(0, python_text_js_1.escapeHtml)(`${item.revision} ${item.commit.slice(0, 9)}`),
|
|
49
|
+
...numbers([item.closed, item.denominator, item.covered]),
|
|
50
|
+
shareCell(item.share),
|
|
51
|
+
...numbers(coverage_data_js_1.uncoveredOrder.map((state) => item.uncovered[state])),
|
|
52
|
+
...numbers([item.allRemoved, item.codeTests, item.none, item.unclassified]),
|
|
53
|
+
]);
|
|
54
|
+
const uncoveredRows = (data) => data.uncovered.map((story) => [
|
|
55
|
+
linkTo(`${story.project}:${story.story}`, story.url),
|
|
56
|
+
(0, python_text_js_1.escapeHtml)(coverage_data_js_1.uncoveredLabels[story.state]),
|
|
57
|
+
(0, python_text_js_1.escapeHtml)(story.scenarios.join(', ') || '—'),
|
|
58
|
+
]);
|
|
59
|
+
const openNote = 'Незакрытые story (статус board — не done и не rejected) по готовности теста; в долю покрытия они не '
|
|
60
|
+
+ 'входят. Тест есть — хоть один сценарий story с шагами не снят и не только ждёт реализации (в том числе в '
|
|
61
|
+
+ 'карантине); класс не задан — поля нет, заготовка TODO или находка поля.';
|
|
62
|
+
const openGroupRows = (data) => {
|
|
63
|
+
const counts = (0, coverage_open_js_1.openCounts)(data.open);
|
|
64
|
+
return coverage_open_js_1.openOrder.map((group) => [(0, python_text_js_1.escapeHtml)((0, python_text_js_1.capitalize)(coverage_open_js_1.openLabels[group])), (0, python_text_js_1.escapeHtml)(String(counts[group]))]);
|
|
65
|
+
};
|
|
66
|
+
const openRows = (data) => data.open.map((story) => [
|
|
67
|
+
linkTo(`${story.project}:${story.story}`, story.url),
|
|
68
|
+
(0, python_text_js_1.escapeHtml)(story.status),
|
|
69
|
+
(0, python_text_js_1.escapeHtml)(coverage_open_js_1.openLabels[story.group]),
|
|
70
|
+
(0, python_text_js_1.escapeHtml)(story.scenarios.join(', ') || '—'),
|
|
71
|
+
]);
|
|
72
|
+
const referrersCell = (scenario) => {
|
|
73
|
+
const lines = scenario.stories
|
|
74
|
+
.map((story) => (0, python_text_js_1.escapeHtml)(`${story.project}/${story.file}:${story.line} — ${story.status || 'вне board'}`));
|
|
75
|
+
return lines.join('<br>') || 'ссылок нет';
|
|
76
|
+
};
|
|
77
|
+
const pendingRows = (data) => data.pending.map((scenario) => [
|
|
78
|
+
linkTo(scenario.id, scenario.url),
|
|
79
|
+
(0, python_text_js_1.escapeHtml)(scenario.title),
|
|
80
|
+
(0, python_text_js_1.escapeHtml)(`${scenario.file}:${scenario.line}`),
|
|
81
|
+
referrersCell(scenario),
|
|
82
|
+
]);
|
|
83
|
+
const removedRows = (data) => data.removed
|
|
84
|
+
.map((row) => [row.id, row.date, row.reason, row.story, row.replacedBy, row.commit].map((value) => (0, python_text_js_1.escapeHtml)(value)));
|
|
85
|
+
const renderPage = (data) => [
|
|
86
|
+
'<!doctype html><html lang="ru"><head><meta charset="utf-8">',
|
|
87
|
+
'<meta name="viewport" content="width=device-width,initial-scale=1">',
|
|
88
|
+
`<title>Покрытие задач</title><style>${pageStyle}</style></head><body>`,
|
|
89
|
+
'<h1>Покрытие задач приёмочными тестами</h1>',
|
|
90
|
+
`<p class="note">Дата: ${(0, python_text_js_1.escapeHtml)(data.date)}. ${note}</p>`,
|
|
91
|
+
'<h2>Покрытие</h2>',
|
|
92
|
+
table(projectHead, projectRows(data)),
|
|
93
|
+
`<h2>Закрытые без включённого теста (${data.uncovered.length})</h2>`,
|
|
94
|
+
table(['Story', 'Состояние', 'Сценарии story'], uncoveredRows(data)),
|
|
95
|
+
`<h2>Незакрытые по готовности теста (${data.open.length})</h2>`,
|
|
96
|
+
`<p class="note">${openNote}</p>`,
|
|
97
|
+
table(['Группа', 'Story'], openGroupRows(data)),
|
|
98
|
+
table(['Story', 'Статус', 'Группа', 'Сценарии story'], openRows(data)),
|
|
99
|
+
`<h2>Ждут реализации (${data.pending.length})</h2>`,
|
|
100
|
+
'<p class="note">Тест написан раньше реализации: сценарий с отметкой @not-implemented.</p>',
|
|
101
|
+
table(['Сценарий', 'Заголовок', 'Файл', 'Story со ссылкой'], pendingRows(data)),
|
|
102
|
+
`<h2>Снято за ${(0, python_text_js_1.escapeHtml)(data.period.since)} — ${(0, python_text_js_1.escapeHtml)(data.period.until)} (${data.removed.length})</h2>`,
|
|
103
|
+
table(['id', 'Дата', 'Причина', 'Story', 'Чем заменён', 'Коммит'], removedRows(data)),
|
|
104
|
+
items('Дефекты разметки', data.defects),
|
|
105
|
+
items('Примечания', data.notes),
|
|
106
|
+
'</body></html>\n',
|
|
107
|
+
].join('');
|
|
108
|
+
exports.renderPage = renderPage;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface RemovedInInterface {
|
|
2
|
+
readonly notes: readonly string[];
|
|
3
|
+
readonly rows: readonly RemovedInPeriodInterface[];
|
|
4
|
+
}
|
|
5
|
+
export interface RemovedInPeriodInterface {
|
|
6
|
+
readonly commit: string;
|
|
7
|
+
readonly date: string;
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly reason: string;
|
|
10
|
+
readonly replacedBy: string;
|
|
11
|
+
readonly story: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const removedIn: (cwd: string, file: string, period: readonly [string, string]) => Promise<RemovedInInterface>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.removedIn = void 0;
|
|
4
|
+
const dates_js_1 = require("./dates.js");
|
|
5
|
+
const python_text_js_1 = require("./python-text.js");
|
|
6
|
+
const removed_list_js_1 = require("./removed-list.js");
|
|
7
|
+
const datePattern = /^\d{4}-\d{2}-\d{2}$/u;
|
|
8
|
+
const parsedDay = (text) => {
|
|
9
|
+
try {
|
|
10
|
+
return [(0, dates_js_1.parseDate)(text)];
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const dayOf = (text) => (datePattern.test(text) ? parsedDay(text)[0] : undefined);
|
|
17
|
+
const inPeriod = (row, period, day) => {
|
|
18
|
+
const { commit, date, id, reason, replacedBy, story } = row;
|
|
19
|
+
return period[0] <= day && day <= period[1] ? [{ commit, date, id, reason, replacedBy, story }] : [];
|
|
20
|
+
};
|
|
21
|
+
const dateNote = (row, file) => `запись ${row.id} в ${file}:${row.line}: дата ${(0, python_text_js_1.pyRepr)(row.date)} не по формату ГГГГ-ММ-ДД — в «снято за период» не входит`;
|
|
22
|
+
const removedIn = async (cwd, file, period) => {
|
|
23
|
+
const rows = await (0, removed_list_js_1.removedRows)(cwd, file);
|
|
24
|
+
const dated = rows.map((row) => [row, dayOf(row.date)]);
|
|
25
|
+
return {
|
|
26
|
+
notes: dated.filter(([, day]) => day === undefined).map(([row]) => dateNote(row, file)),
|
|
27
|
+
rows: dated.flatMap(([row, day]) => (day === undefined ? [] : inPeriod(row, period, day))),
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
exports.removedIn = removedIn;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CoverageProjectInterface, PendingInterface, UncoveredStoryInterface } from './coverage-data.js';
|
|
2
|
+
import type { OpenRowInterface } from './coverage-open.js';
|
|
3
|
+
import type { RemovedInPeriodInterface } from './coverage-removed.js';
|
|
4
|
+
import type { DeclaredInputInterface } from './declared.js';
|
|
5
|
+
import type { ReportInterface } from './report.js';
|
|
6
|
+
export interface CoverageDataInterface {
|
|
7
|
+
readonly date: string;
|
|
8
|
+
readonly defects: readonly string[];
|
|
9
|
+
readonly notes: readonly string[];
|
|
10
|
+
readonly open: readonly OpenRowInterface[];
|
|
11
|
+
readonly pending: readonly PendingInterface[];
|
|
12
|
+
readonly period: Readonly<{
|
|
13
|
+
since: string;
|
|
14
|
+
until: string;
|
|
15
|
+
}>;
|
|
16
|
+
readonly projects: readonly CoverageProjectInterface[];
|
|
17
|
+
readonly removed: readonly RemovedInPeriodInterface[];
|
|
18
|
+
readonly uncovered: readonly UncoveredStoryInterface[];
|
|
19
|
+
}
|
|
20
|
+
export interface CoverageOptionsInterface {
|
|
21
|
+
readonly json: string | undefined;
|
|
22
|
+
readonly out: string | undefined;
|
|
23
|
+
readonly period: readonly [string, string];
|
|
24
|
+
readonly today: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const coverageCommand: (input: DeclaredInputInterface, options: CoverageOptionsInterface) => Promise<ReportInterface>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.coverageCommand = void 0;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const classes_js_1 = require("./classes.js");
|
|
10
|
+
const config_js_1 = require("./config.js");
|
|
11
|
+
const coverage_data_js_1 = require("./coverage-data.js");
|
|
12
|
+
const coverage_json_js_1 = require("./coverage-json.js");
|
|
13
|
+
const coverage_open_js_1 = require("./coverage-open.js");
|
|
14
|
+
const coverage_page_js_1 = require("./coverage-page.js");
|
|
15
|
+
const coverage_removed_js_1 = require("./coverage-removed.js");
|
|
16
|
+
const features_js_1 = require("./features.js");
|
|
17
|
+
const python_text_js_1 = require("./python-text.js");
|
|
18
|
+
const removed_list_js_1 = require("./removed-list.js");
|
|
19
|
+
const task_links_js_1 = require("./task-links.js");
|
|
20
|
+
const projectPart = (item) => {
|
|
21
|
+
const share = item.share === undefined ? '—' : `${item.share}%`;
|
|
22
|
+
const states = coverage_data_js_1.uncoveredOrder.map((state) => `${coverage_data_js_1.uncoveredLabels[state]} ${item.uncovered[state]}`).join(', ');
|
|
23
|
+
const total = coverage_data_js_1.uncoveredOrder.reduce((sum, state) => sum + item.uncovered[state], 0);
|
|
24
|
+
return `${item.project} — покрыто ${item.covered} из ${item.denominator} (${share}), без включённого теста ${total} (${states})`;
|
|
25
|
+
};
|
|
26
|
+
const coverageLine = (projects) => {
|
|
27
|
+
const parts = projects.map((item) => projectPart(item)).join(' · ');
|
|
28
|
+
return `покрытие закрытых story: ${parts || classes_js_1.noProjects}`;
|
|
29
|
+
};
|
|
30
|
+
const openPart = (item, rows) => {
|
|
31
|
+
const own = rows.filter((row) => row.project === item.project.name);
|
|
32
|
+
const counts = (0, coverage_open_js_1.openCounts)(own);
|
|
33
|
+
const groups = coverage_open_js_1.openOrder.map((group) => `${coverage_open_js_1.openLabels[group]} ${counts[group]}`).join(', ');
|
|
34
|
+
return `${item.project.name} — по группам ${own.length}, на board ${item.open.length} (${groups})`;
|
|
35
|
+
};
|
|
36
|
+
const openLine = (classified, rows) => {
|
|
37
|
+
const parts = classified.map((item) => openPart(item, rows)).join(' · ');
|
|
38
|
+
return `незакрытые story: ${parts || classes_js_1.noProjects}`;
|
|
39
|
+
};
|
|
40
|
+
const writeOut = async (cwd, file, text) => {
|
|
41
|
+
try {
|
|
42
|
+
await (0, promises_1.mkdir)(node_path_1.default.dirname(node_path_1.default.resolve(cwd, file)), { recursive: true });
|
|
43
|
+
await (0, promises_1.writeFile)(node_path_1.default.resolve(cwd, file), text);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new config_js_1.ConfigError(`${file} не записывается: ${(0, python_text_js_1.messageOf)(error)}`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const scenariosOf = async (input, files) => {
|
|
50
|
+
const removed = await (0, removed_list_js_1.parseRemoved)(input.cwd, input.config.removed);
|
|
51
|
+
return { config: input.config, cwd: input.cwd, ids: (0, features_js_1.scenarioIds)(input.config, files), removed: removed.entries };
|
|
52
|
+
};
|
|
53
|
+
const collect = async (input, options) => {
|
|
54
|
+
const files = await (0, features_js_1.worktreeFeatures)(input.cwd, input.config);
|
|
55
|
+
const scenarios = await scenariosOf(input, files);
|
|
56
|
+
const { classified, defects, notes } = await (0, classes_js_1.classifyDeclared)(scenarios, input);
|
|
57
|
+
const { projects, uncovered } = await (0, coverage_data_js_1.coverageOf)(classified, scenarios);
|
|
58
|
+
const read = await (0, task_links_js_1.readProjects)(input);
|
|
59
|
+
const pending = await (0, coverage_data_js_1.pendingScenarios)(scenarios, files, read.rows);
|
|
60
|
+
const open = await (0, coverage_open_js_1.openRows)(classified, scenarios);
|
|
61
|
+
const inPeriod = await (0, coverage_removed_js_1.removedIn)(input.cwd, input.config.removed, options.period);
|
|
62
|
+
const data = {
|
|
63
|
+
date: options.today,
|
|
64
|
+
defects,
|
|
65
|
+
notes: [...notes, ...inPeriod.notes],
|
|
66
|
+
open,
|
|
67
|
+
pending,
|
|
68
|
+
period: { since: options.period[0], until: options.period[1] },
|
|
69
|
+
projects,
|
|
70
|
+
removed: inPeriod.rows,
|
|
71
|
+
uncovered,
|
|
72
|
+
};
|
|
73
|
+
return { data, open: openLine(classified, open), sources: (0, classes_js_1.sourcesLine)(classified.map((item) => item.project)) };
|
|
74
|
+
};
|
|
75
|
+
const isGiven = (file) => file !== undefined && file !== '';
|
|
76
|
+
const writeIfGiven = async (cwd, target) => {
|
|
77
|
+
if (!isGiven(target.file)) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
await writeOut(cwd, target.file, target.text);
|
|
81
|
+
return [`${target.label}: ${target.file}`];
|
|
82
|
+
};
|
|
83
|
+
const coverageCommand = async (input, options) => {
|
|
84
|
+
const { data, open, sources } = await collect(input, options);
|
|
85
|
+
const counts = `ждут реализации ${data.pending.length}, снято за ${data.period.since} — ${data.period.until}: ${data.removed.length}`;
|
|
86
|
+
const json = await writeIfGiven(input.cwd, { file: options.json, label: 'данные', text: (0, coverage_json_js_1.coverageJson)(data) });
|
|
87
|
+
const page = await writeIfGiven(input.cwd, { file: options.out, label: 'страница', text: (0, coverage_page_js_1.renderPage)(data) });
|
|
88
|
+
const summary = [coverageLine(data.projects), open, sources, counts, ...json, ...page].join('\n');
|
|
89
|
+
return { defects: data.defects, notes: data.notes, summary };
|
|
90
|
+
};
|
|
91
|
+
exports.coverageCommand = coverageCommand;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface PeriodOptionsInterface {
|
|
2
|
+
readonly since: string | undefined;
|
|
3
|
+
readonly today: string;
|
|
4
|
+
readonly until: string | undefined;
|
|
5
|
+
}
|
|
6
|
+
export declare const parseDate: (text: string) => string;
|
|
7
|
+
export declare const localToday: (now: Date) => string;
|
|
8
|
+
export declare const periodOf: (options: PeriodOptionsInterface) => readonly [string, string];
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.periodOf = exports.localToday = exports.parseDate = void 0;
|
|
4
|
+
const config_js_1 = require("./config.js");
|
|
5
|
+
const python_text_js_1 = require("./python-text.js");
|
|
6
|
+
const isoForm = /^(?<year>\d{4})(?<dash>-?)(?<month>\d{2})\k<dash>(?<day>\d{2})$/u;
|
|
7
|
+
const periodDays = 30;
|
|
8
|
+
const monthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
9
|
+
const eraDays = 146_097;
|
|
10
|
+
const epochShift = 719_468;
|
|
11
|
+
const isLeap = (year) => (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
12
|
+
const daysIn = (year, month) => {
|
|
13
|
+
const isLeapFebruary = month === 2 && isLeap(year);
|
|
14
|
+
return isLeapFebruary ? 29 : monthDays[month - 1] ?? 0;
|
|
15
|
+
};
|
|
16
|
+
const dayError = ({ day, month, year }) => {
|
|
17
|
+
const last = daysIn(year, month);
|
|
18
|
+
return day < 1 || day > last ? `day ${day} must be in range 1..${last} for month ${month} in year ${year}` : undefined;
|
|
19
|
+
};
|
|
20
|
+
const rangeError = (civil) => {
|
|
21
|
+
if (civil.year < 1) {
|
|
22
|
+
return `year must be in 1..9999, not ${civil.year}`;
|
|
23
|
+
}
|
|
24
|
+
return civil.month < 1 || civil.month > 12 ? `month must be in 1..12, not ${civil.month}` : dayError(civil);
|
|
25
|
+
};
|
|
26
|
+
const pad = (value, width) => String(value).padStart(width, '0');
|
|
27
|
+
const format = ({ day, month, year }) => `${pad(year, 4)}-${pad(month, 2)}-${pad(day, 2)}`;
|
|
28
|
+
const marchMonth = (month) => (month > 2 ? month - 3 : month + 9);
|
|
29
|
+
const toDays = ({ day, month, year }) => {
|
|
30
|
+
const shifted = month <= 2 ? year - 1 : year;
|
|
31
|
+
const era = Math.floor(shifted / 400);
|
|
32
|
+
const yearOfEra = shifted - (era * 400);
|
|
33
|
+
const dayOfYear = Math.floor(((153 * marchMonth(month)) + 2) / 5) + day - 1;
|
|
34
|
+
const leapDays = Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100);
|
|
35
|
+
return (era * eraDays) + (yearOfEra * 365) + leapDays + dayOfYear - epochShift;
|
|
36
|
+
};
|
|
37
|
+
const yearOfEraOf = (dayOfEra) => {
|
|
38
|
+
const leapDays = Math.floor(dayOfEra / 1460) - Math.floor(dayOfEra / 36_524) + Math.floor(dayOfEra / (eraDays - 1));
|
|
39
|
+
return Math.floor((dayOfEra - leapDays) / 365);
|
|
40
|
+
};
|
|
41
|
+
const fromDays = (days) => {
|
|
42
|
+
const shifted = days + epochShift;
|
|
43
|
+
const era = Math.floor(shifted / eraDays);
|
|
44
|
+
const dayOfEra = shifted - (era * eraDays);
|
|
45
|
+
const yearOfEra = yearOfEraOf(dayOfEra);
|
|
46
|
+
const dayOfYear = dayOfEra - ((365 * yearOfEra) + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100));
|
|
47
|
+
const monthIndex = Math.floor(((5 * dayOfYear) + 2) / 153);
|
|
48
|
+
const month = monthIndex < 10 ? monthIndex + 3 : monthIndex - 9;
|
|
49
|
+
const day = dayOfYear - Math.floor(((153 * monthIndex) + 2) / 5) + 1;
|
|
50
|
+
return { day, month, year: yearOfEra + (era * 400) + (month <= 2 ? 1 : 0) };
|
|
51
|
+
};
|
|
52
|
+
const parseDate = (text) => {
|
|
53
|
+
const groups = isoForm.exec(text)?.groups;
|
|
54
|
+
if (groups === undefined) {
|
|
55
|
+
throw new Error(`Invalid isoformat string: ${(0, python_text_js_1.pyRepr)(text)}`);
|
|
56
|
+
}
|
|
57
|
+
const civil = { day: Number(groups.day), month: Number(groups.month), year: Number(groups.year) };
|
|
58
|
+
const problem = rangeError(civil);
|
|
59
|
+
if (problem !== undefined) {
|
|
60
|
+
throw new Error(problem);
|
|
61
|
+
}
|
|
62
|
+
return format(civil);
|
|
63
|
+
};
|
|
64
|
+
exports.parseDate = parseDate;
|
|
65
|
+
const localToday = (now) => format({
|
|
66
|
+
day: now.getDate(),
|
|
67
|
+
month: now.getMonth() + 1,
|
|
68
|
+
year: now.getFullYear(),
|
|
69
|
+
});
|
|
70
|
+
exports.localToday = localToday;
|
|
71
|
+
const minusDays = (date, days) => {
|
|
72
|
+
const [year = 0, month = 0, day = 0] = date.split('-').map(Number);
|
|
73
|
+
return format(fromDays(toDays({ day, month, year }) - days));
|
|
74
|
+
};
|
|
75
|
+
const isUnset = (text) => text === undefined || text === '';
|
|
76
|
+
const dateOr = (text, fallback) => (isUnset(text) ? fallback() : (0, exports.parseDate)(text));
|
|
77
|
+
const parsePeriod = (options) => {
|
|
78
|
+
try {
|
|
79
|
+
const until = dateOr(options.until, () => options.today);
|
|
80
|
+
return [dateOr(options.since, () => minusDays(until, periodDays)), until];
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw new config_js_1.ConfigError(`дата периода не по формату ГГГГ-ММ-ДД: ${(0, python_text_js_1.messageOf)(error)}`);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const periodOf = (options) => {
|
|
87
|
+
const [since, until] = parsePeriod(options);
|
|
88
|
+
if (since > until) {
|
|
89
|
+
throw new config_js_1.ConfigError(`--since ${since} позже --until ${until}`);
|
|
90
|
+
}
|
|
91
|
+
return [since, until];
|
|
92
|
+
};
|
|
93
|
+
exports.periodOf = periodOf;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ConfigInterface } from './config.js';
|
|
2
|
+
import type { ProjectInterface } from './projects.js';
|
|
3
|
+
export interface DeclaredInputInterface {
|
|
4
|
+
readonly config: ConfigInterface;
|
|
5
|
+
readonly cwd: string;
|
|
6
|
+
readonly projects: string;
|
|
7
|
+
readonly revisions: ReadonlyMap<string, string>;
|
|
8
|
+
}
|
|
9
|
+
export interface DeclaredInterface {
|
|
10
|
+
readonly declared: readonly DeclaredProjectInterface[];
|
|
11
|
+
readonly notes: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
interface DeclaredProjectInterface {
|
|
14
|
+
readonly defect: string | undefined;
|
|
15
|
+
readonly project: ProjectInterface;
|
|
16
|
+
}
|
|
17
|
+
export declare const declaredProjects: (input: DeclaredInputInterface) => Promise<DeclaredInterface>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.declaredProjects = void 0;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const board_js_1 = require("./board.js");
|
|
9
|
+
const config_js_1 = require("./config.js");
|
|
10
|
+
const features_js_1 = require("./features.js");
|
|
11
|
+
const git_js_1 = require("./git.js");
|
|
12
|
+
const projects_js_1 = require("./projects.js");
|
|
13
|
+
const python_text_js_1 = require("./python-text.js");
|
|
14
|
+
const repoFromKey = (value, projects) => {
|
|
15
|
+
const text = (0, python_text_js_1.pyText)(value).trim();
|
|
16
|
+
if (text.startsWith('projects/')) {
|
|
17
|
+
return (0, features_js_1.joinPath)(projects, text.slice('projects/'.length));
|
|
18
|
+
}
|
|
19
|
+
return node_path_1.default.isAbsolute(text) ? text : undefined;
|
|
20
|
+
};
|
|
21
|
+
const keyValue = (project) => {
|
|
22
|
+
if (project.board === undefined) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
let header;
|
|
26
|
+
try {
|
|
27
|
+
header = (0, board_js_1.parseBoard)(project.board);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new config_js_1.ConfigError(`board проекта ${project.name} на ${project.revision} не читается: ${(0, python_text_js_1.messageOf)(error)}`);
|
|
31
|
+
}
|
|
32
|
+
return (0, board_js_1.isRecord)(header) ? header[board_js_1.acceptanceKey] : undefined;
|
|
33
|
+
};
|
|
34
|
+
const isSet = (value) => {
|
|
35
|
+
if ((0, board_js_1.isRecord)(value)) {
|
|
36
|
+
return Object.keys(value).length > 0;
|
|
37
|
+
}
|
|
38
|
+
return Array.isArray(value) ? value.length > 0 : Boolean(value);
|
|
39
|
+
};
|
|
40
|
+
const defectOf = (config, project, value) => {
|
|
41
|
+
const defect = `board проекта ${project.name} объявляет этот репозиторий тестов (${board_js_1.acceptanceKey}: ${(0, python_text_js_1.pyText)(value)}), а «id» `
|
|
42
|
+
+ 'конфига проект не покрывает — ссылки его story не распознаются';
|
|
43
|
+
return (0, config_js_1.isProjectCovered)(config, project.name) ? undefined : defect;
|
|
44
|
+
};
|
|
45
|
+
const isOwnRepo = async (cwd, own, repo) => {
|
|
46
|
+
const common = await (0, git_js_1.commonDirectory)(cwd, repo);
|
|
47
|
+
return own !== undefined && common === own;
|
|
48
|
+
};
|
|
49
|
+
const probe = async (input, own, project) => {
|
|
50
|
+
const value = keyValue(project);
|
|
51
|
+
if (!isSet(value)) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const repo = repoFromKey(value, input.projects);
|
|
55
|
+
if (repo === undefined) {
|
|
56
|
+
return `проект ${project.name} пропущен — ${board_js_1.acceptanceKey}: ${(0, python_text_js_1.pyRepr)(value)}, ждём projects/<репозиторий> или абсолютный путь`;
|
|
57
|
+
}
|
|
58
|
+
return await isOwnRepo(input.cwd, own, repo) ? { defect: defectOf(input.config, project, value), project } : undefined;
|
|
59
|
+
};
|
|
60
|
+
const declaredProjects = async (input) => {
|
|
61
|
+
const own = await (0, git_js_1.commonDirectory)(input.cwd, '.');
|
|
62
|
+
const walk = await (0, projects_js_1.walkProjects)(input);
|
|
63
|
+
const probes = await Promise.all(walk.projects.map(async (project) => probe(input, own, project)));
|
|
64
|
+
return {
|
|
65
|
+
declared: probes.filter((item) => typeof item === 'object'),
|
|
66
|
+
notes: [...walk.notes, ...probes.filter((item) => typeof item === 'string')],
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
exports.declaredProjects = declaredProjects;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ConfigInterface } from './config.js';
|
|
2
|
+
import type { ScenarioInterface } from './gherkin.js';
|
|
3
|
+
export type FilesType = ReadonlyMap<string, string>;
|
|
4
|
+
export declare const joinPath: (directory: string, name: string) => string;
|
|
5
|
+
export declare const isFile: (cwd: string, file: string) => Promise<boolean>;
|
|
6
|
+
export declare const isDirectory: (target: string) => Promise<boolean>;
|
|
7
|
+
export declare const walkFiles: (cwd: string, directory: string, extension: string) => Promise<string[]>;
|
|
8
|
+
export declare const readText: (cwd: string, file: string) => Promise<string>;
|
|
9
|
+
export declare const worktreeFeatures: (cwd: string, config: ConfigInterface) => Promise<FilesType>;
|
|
10
|
+
export declare const baseFeatures: (cwd: string, config: ConfigInterface, base: string) => Promise<FilesType>;
|
|
11
|
+
export declare const allScenarios: (files: FilesType) => ScenarioInterface[];
|
|
12
|
+
export declare const scenarioIds: (config: ConfigInterface, files: FilesType) => Map<string, ScenarioInterface[]>;
|
|
13
|
+
export declare const countMark: (scenarios: readonly ScenarioInterface[], mark: string) => number;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.countMark = exports.scenarioIds = exports.allScenarios = exports.baseFeatures = exports.worktreeFeatures = exports.readText = exports.walkFiles = exports.isDirectory = exports.isFile = exports.joinPath = void 0;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const config_js_1 = require("./config.js");
|
|
10
|
+
const gherkin_js_1 = require("./gherkin.js");
|
|
11
|
+
const git_js_1 = require("./git.js");
|
|
12
|
+
const python_text_js_1 = require("./python-text.js");
|
|
13
|
+
const joinPath = (directory, name) => {
|
|
14
|
+
const separator = directory.endsWith('/') ? '' : '/';
|
|
15
|
+
return `${directory}${separator}${name}`;
|
|
16
|
+
};
|
|
17
|
+
exports.joinPath = joinPath;
|
|
18
|
+
const isFile = async (cwd, file) => {
|
|
19
|
+
try {
|
|
20
|
+
await (0, promises_1.access)(node_path_1.default.resolve(cwd, file));
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
exports.isFile = isFile;
|
|
28
|
+
const isDirectory = async (target) => {
|
|
29
|
+
try {
|
|
30
|
+
const found = await (0, promises_1.stat)(target);
|
|
31
|
+
return found.isDirectory();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
exports.isDirectory = isDirectory;
|
|
38
|
+
const kindOf = async (entry, absolute) => {
|
|
39
|
+
if (entry.isSymbolicLink()) {
|
|
40
|
+
return await (0, exports.isDirectory)(absolute) ? undefined : 'file';
|
|
41
|
+
}
|
|
42
|
+
return entry.isDirectory() ? 'directory' : 'file';
|
|
43
|
+
};
|
|
44
|
+
const listDirectory = async (absolute) => {
|
|
45
|
+
try {
|
|
46
|
+
return await (0, promises_1.readdir)(absolute, { withFileTypes: true });
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const kindsOf = async (cwd, directory) => {
|
|
53
|
+
const entries = await listDirectory(node_path_1.default.resolve(cwd, directory));
|
|
54
|
+
const kinds = await Promise.all(entries.map(async (entry) => {
|
|
55
|
+
const kind = await kindOf(entry, node_path_1.default.resolve(cwd, directory, entry.name));
|
|
56
|
+
return [entry.name, kind];
|
|
57
|
+
}));
|
|
58
|
+
return new Map(kinds);
|
|
59
|
+
};
|
|
60
|
+
const walkFiles = async (cwd, directory, extension) => {
|
|
61
|
+
const kinds = await kindsOf(cwd, directory);
|
|
62
|
+
const names = (0, python_text_js_1.sortedText)(kinds.keys());
|
|
63
|
+
const files = names.filter((name) => kinds.get(name) === 'file' && name.endsWith(extension));
|
|
64
|
+
const directories = names.filter((name) => kinds.get(name) === 'directory');
|
|
65
|
+
const nested = await Promise.all(directories
|
|
66
|
+
.map(async (name) => (0, exports.walkFiles)(cwd, (0, exports.joinPath)(directory, name), extension)));
|
|
67
|
+
return [...files.map((name) => (0, exports.joinPath)(directory, name)), ...nested.flat()];
|
|
68
|
+
};
|
|
69
|
+
exports.walkFiles = walkFiles;
|
|
70
|
+
const readText = async (cwd, file) => {
|
|
71
|
+
const text = await (0, promises_1.readFile)(node_path_1.default.resolve(cwd, file), 'utf8');
|
|
72
|
+
return (0, python_text_js_1.universalNewlines)(text);
|
|
73
|
+
};
|
|
74
|
+
exports.readText = readText;
|
|
75
|
+
const readAll = async (files, read) => {
|
|
76
|
+
const texts = await Promise.all(files.map(async (file) => [file, await read(file)]));
|
|
77
|
+
return new Map(texts);
|
|
78
|
+
};
|
|
79
|
+
const worktreeFeatures = async (cwd, config) => {
|
|
80
|
+
const files = await (0, exports.walkFiles)(cwd, config.features, '.feature');
|
|
81
|
+
return readAll(files, async (file) => (0, exports.readText)(cwd, file));
|
|
82
|
+
};
|
|
83
|
+
exports.worktreeFeatures = worktreeFeatures;
|
|
84
|
+
const showOrEmpty = async (cwd, revision) => {
|
|
85
|
+
const text = await (0, git_js_1.git)(cwd, git_js_1.currentRepo, ['show', revision]);
|
|
86
|
+
return text ?? '';
|
|
87
|
+
};
|
|
88
|
+
const baseFeatures = async (cwd, config, base) => {
|
|
89
|
+
const listed = await (0, git_js_1.runGit)({ cwd, repo: git_js_1.currentRepo }, ['ls-tree', '-r', '--name-only', base, '--', config.features]);
|
|
90
|
+
if (listed.code !== 0) {
|
|
91
|
+
throw new config_js_1.ConfigError(`git ls-tree ${base} не прошёл: ${listed.stderr.trim()}`);
|
|
92
|
+
}
|
|
93
|
+
const files = (0, git_js_1.stdoutText)(listed).split('\n')
|
|
94
|
+
.filter((file) => file.endsWith('.feature'));
|
|
95
|
+
return readAll(files, async (file) => showOrEmpty(cwd, `${base}:./${file}`));
|
|
96
|
+
};
|
|
97
|
+
exports.baseFeatures = baseFeatures;
|
|
98
|
+
const allScenarios = (files) => [...files]
|
|
99
|
+
.flatMap(([file, text]) => (0, gherkin_js_1.parseFeature)(text, file));
|
|
100
|
+
exports.allScenarios = allScenarios;
|
|
101
|
+
const scenarioIds = (config, files) => {
|
|
102
|
+
const tagged = (0, exports.allScenarios)(files).flatMap((scenario) => scenario.tags
|
|
103
|
+
.filter((tag) => config.id.test(tag))
|
|
104
|
+
.map((tag) => ({ scenario, tag })));
|
|
105
|
+
const grouped = Map.groupBy(tagged, ({ tag }) => tag);
|
|
106
|
+
return new Map([...grouped].map(([tag, items]) => [tag, items.map(({ scenario }) => scenario)]));
|
|
107
|
+
};
|
|
108
|
+
exports.scenarioIds = scenarioIds;
|
|
109
|
+
const countMark = (scenarios, mark) => scenarios
|
|
110
|
+
.filter((scenario) => scenario.tags.includes(mark))
|
|
111
|
+
.length;
|
|
112
|
+
exports.countMark = countMark;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface ScenarioInterface {
|
|
2
|
+
readonly file: string;
|
|
3
|
+
readonly line: number;
|
|
4
|
+
readonly tags: readonly string[];
|
|
5
|
+
readonly title: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const parseFeature: (text: string, file: string) => ScenarioInterface[];
|
|
8
|
+
export declare const featureTags: (text: string) => readonly string[];
|
|
9
|
+
export declare const featureLanguage: (text: string) => string;
|
|
10
|
+
export declare const where: (scenario: ScenarioInterface) => string;
|