@application-kit/acceptance 0.0.0-stage → 0.1.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/CHANGELOG.md +7 -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 +108 -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 +104 -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 +104 -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 +93 -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,21 @@
|
|
|
1
|
+
export interface ProjectInterface {
|
|
2
|
+
readonly board: string | undefined;
|
|
3
|
+
readonly commit: string | undefined;
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly repo: string;
|
|
6
|
+
readonly revision: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
export interface ProjectsInputInterface {
|
|
9
|
+
readonly cwd: string;
|
|
10
|
+
readonly projects: string;
|
|
11
|
+
readonly revisions: ReadonlyMap<string, string>;
|
|
12
|
+
}
|
|
13
|
+
export interface WalkInterface {
|
|
14
|
+
readonly notes: readonly string[];
|
|
15
|
+
readonly projects: readonly ProjectInterface[];
|
|
16
|
+
}
|
|
17
|
+
export declare const parseRevisions: (cwd: string, projects: string, items: readonly string[]) => Promise<Map<string, string>>;
|
|
18
|
+
export declare const walkProjects: (input: ProjectsInputInterface) => Promise<WalkInterface>;
|
|
19
|
+
export declare const sourceOf: (project: ProjectInterface) => string;
|
|
20
|
+
export declare const projectStatuses: (cwd: string, project: ProjectInterface) => Promise<Map<string, string>>;
|
|
21
|
+
export declare const storyKey: (file: string) => string;
|
|
@@ -0,0 +1,104 @@
|
|
|
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.storyKey = exports.projectStatuses = exports.sourceOf = exports.walkProjects = exports.parseRevisions = void 0;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const board_js_1 = require("./board.js");
|
|
10
|
+
const config_js_1 = require("./config.js");
|
|
11
|
+
const features_js_1 = require("./features.js");
|
|
12
|
+
const git_js_1 = require("./git.js");
|
|
13
|
+
const python_text_js_1 = require("./python-text.js");
|
|
14
|
+
const ownBoard = 'tasks/board.yaml';
|
|
15
|
+
const commitSuffix = '^{commit}';
|
|
16
|
+
const splitRevision = (item) => {
|
|
17
|
+
const index = item.indexOf('=');
|
|
18
|
+
return [item.slice(0, index), item.slice(index + 1)];
|
|
19
|
+
};
|
|
20
|
+
const parseRevisions = async (cwd, projects, items) => {
|
|
21
|
+
const parsed = new Map(items.filter((item) => item.includes('=')).map((item) => splitRevision(item)));
|
|
22
|
+
if (parsed.size !== items.length) {
|
|
23
|
+
throw new config_js_1.ConfigError('--ref ждёт <проект>=<ref>');
|
|
24
|
+
}
|
|
25
|
+
if (!await (0, features_js_1.isDirectory)(node_path_1.default.resolve(cwd, projects))) {
|
|
26
|
+
throw new config_js_1.ConfigError(`каталога проектов ${projects} нет`);
|
|
27
|
+
}
|
|
28
|
+
return parsed;
|
|
29
|
+
};
|
|
30
|
+
exports.parseRevisions = parseRevisions;
|
|
31
|
+
const projectNames = async (cwd, projects) => {
|
|
32
|
+
const names = await (0, promises_1.readdir)(node_path_1.default.resolve(cwd, projects));
|
|
33
|
+
const flags = await Promise.all(names.map(async (name) => (0, features_js_1.isDirectory)(node_path_1.default.resolve(cwd, projects, name))));
|
|
34
|
+
return (0, python_text_js_1.sortedText)(names.filter((_, index) => flags[index] === true));
|
|
35
|
+
};
|
|
36
|
+
const skipNote = (name, commit, revision) => {
|
|
37
|
+
const missing = commit === undefined ? 'ref ' : `${ownBoard} на `;
|
|
38
|
+
return `проект ${name} пропущен — нет ${missing}${revision}`;
|
|
39
|
+
};
|
|
40
|
+
const isText = (text) => text !== undefined && text !== '';
|
|
41
|
+
const foreignProject = async (input, name) => {
|
|
42
|
+
const repo = (0, features_js_1.joinPath)(input.projects, name);
|
|
43
|
+
const revision = input.revisions.get(name) ?? 'origin/main';
|
|
44
|
+
const commit = await (0, git_js_1.git)(input.cwd, repo, ['rev-parse', '--verify', '-q', `${revision}${commitSuffix}`]);
|
|
45
|
+
const board = await (0, git_js_1.git)(input.cwd, repo, ['show', `${revision}:${ownBoard}`]);
|
|
46
|
+
return !isText(board) || !isText(commit)
|
|
47
|
+
? skipNote(name, commit, revision)
|
|
48
|
+
: { board, commit: commit.trim(), name, repo, revision };
|
|
49
|
+
};
|
|
50
|
+
const ownProject = (name) => ({
|
|
51
|
+
board: undefined,
|
|
52
|
+
commit: undefined,
|
|
53
|
+
name,
|
|
54
|
+
repo: git_js_1.currentRepo,
|
|
55
|
+
revision: undefined,
|
|
56
|
+
});
|
|
57
|
+
const probe = async (input, own, name) => {
|
|
58
|
+
const common = await (0, git_js_1.commonDirectory)(input.cwd, (0, features_js_1.joinPath)(input.projects, name));
|
|
59
|
+
return own !== undefined && common === own ? ownProject(name) : foreignProject(input, name);
|
|
60
|
+
};
|
|
61
|
+
const checkRevisions = (input, names) => {
|
|
62
|
+
const unknown = (0, python_text_js_1.sortedText)([...input.revisions.keys()].filter((name) => !names.includes(name)));
|
|
63
|
+
if (unknown.length > 0) {
|
|
64
|
+
throw new config_js_1.ConfigError(`--ref для проектов, которых нет в ${input.projects}: ${unknown.join(', ')}`);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const isProject = (item) => typeof item === 'object';
|
|
68
|
+
const withOwn = (found, hasOwnBoard) => {
|
|
69
|
+
const isOwnSeen = found.some((project) => project.revision === undefined);
|
|
70
|
+
const projects = found.filter((project) => hasOwnBoard || project.revision !== undefined);
|
|
71
|
+
return isOwnSeen || !hasOwnBoard ? projects : [...projects, ownProject('.')];
|
|
72
|
+
};
|
|
73
|
+
const walkProjects = async (input) => {
|
|
74
|
+
const own = await (0, git_js_1.commonDirectory)(input.cwd, git_js_1.currentRepo);
|
|
75
|
+
const names = await projectNames(input.cwd, input.projects);
|
|
76
|
+
checkRevisions(input, names);
|
|
77
|
+
const hasOwnBoard = await (0, features_js_1.isFile)(input.cwd, ownBoard);
|
|
78
|
+
const probes = await Promise.all(names.map(async (name) => probe(input, own, name)));
|
|
79
|
+
return {
|
|
80
|
+
notes: probes.filter((item) => typeof item === 'string'),
|
|
81
|
+
projects: withOwn(probes.filter((item) => isProject(item)), hasOwnBoard),
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
exports.walkProjects = walkProjects;
|
|
85
|
+
const sourceOf = (project) => (project.revision === undefined
|
|
86
|
+
? `${project.name} — рабочее дерево`
|
|
87
|
+
: `${project.name} — ${project.revision} ${(project.commit ?? '').slice(0, 9)}`);
|
|
88
|
+
exports.sourceOf = sourceOf;
|
|
89
|
+
const projectStatuses = async (cwd, project) => {
|
|
90
|
+
if (project.revision !== undefined) {
|
|
91
|
+
return (0, board_js_1.boardStatuses)(project.board ?? '', `проекта ${project.name} на ${project.revision}`);
|
|
92
|
+
}
|
|
93
|
+
if (await (0, features_js_1.isFile)(cwd, 'tasks/status.log')) {
|
|
94
|
+
throw new config_js_1.ConfigError('рядом с board лежит status.log, упразднённый форматом 6: статусы живут в самом board');
|
|
95
|
+
}
|
|
96
|
+
return (0, board_js_1.boardStatuses)(await (0, features_js_1.readText)(cwd, ownBoard), ownBoard);
|
|
97
|
+
};
|
|
98
|
+
exports.projectStatuses = projectStatuses;
|
|
99
|
+
const storyKey = (file) => {
|
|
100
|
+
const name = node_path_1.default.posix.basename(file);
|
|
101
|
+
const dot = name.lastIndexOf('.');
|
|
102
|
+
return dot > 0 ? name.slice(0, dot) : name;
|
|
103
|
+
};
|
|
104
|
+
exports.storyKey = storyKey;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const splitLines: (text: string) => string[];
|
|
2
|
+
export declare const universalNewlines: (text: string) => string;
|
|
3
|
+
export declare const compareText: (left: string, right: string) => number;
|
|
4
|
+
export declare const sortedText: (values: Iterable<string>) => string[];
|
|
5
|
+
export declare const pyRepr: (value: unknown) => string;
|
|
6
|
+
export declare const pyText: (value: unknown) => string;
|
|
7
|
+
export declare const roundTenth: (value: number) => number;
|
|
8
|
+
export declare const capitalize: (text: string) => string;
|
|
9
|
+
export declare const escapeHtml: (text: string) => string;
|
|
10
|
+
export declare const quotePath: (text: string) => string;
|
|
11
|
+
export declare const messageOf: (error: unknown) => string;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.messageOf = exports.quotePath = exports.escapeHtml = exports.capitalize = exports.roundTenth = exports.pyText = exports.pyRepr = exports.sortedText = exports.compareText = exports.universalNewlines = exports.splitLines = void 0;
|
|
4
|
+
const breakChars = [0x0A, 0x0B, 0x0C, 0x0D, 0x1C, 0x1D, 0x1E, 0x85, 0x20_28, 0x20_29]
|
|
5
|
+
.map((code) => String.fromCodePoint(code))
|
|
6
|
+
.join('');
|
|
7
|
+
const lineBreak = new RegExp(`\r\n|[${breakChars}]`, 'u');
|
|
8
|
+
const notPrintable = /[\p{C}\p{Z}]/u;
|
|
9
|
+
const urlSafe = /[\w\-./~]/u;
|
|
10
|
+
const namedEscapes = { '\t': String.raw `\t`, '\n': String.raw `\n`, '\r': String.raw `\r` };
|
|
11
|
+
const splitLines = (text) => {
|
|
12
|
+
const lines = text.split(lineBreak);
|
|
13
|
+
return lines.at(-1) === '' ? lines.slice(0, -1) : lines;
|
|
14
|
+
};
|
|
15
|
+
exports.splitLines = splitLines;
|
|
16
|
+
const universalNewlines = (text) => text.replaceAll(/\r\n?/gu, '\n');
|
|
17
|
+
exports.universalNewlines = universalNewlines;
|
|
18
|
+
const codePoints = (text) => Array.from(text, (char) => char.codePointAt(0) ?? 0);
|
|
19
|
+
const compareText = (left, right) => {
|
|
20
|
+
const leftPoints = codePoints(left);
|
|
21
|
+
const rightPoints = codePoints(right);
|
|
22
|
+
const index = leftPoints.findIndex((point, position) => point !== rightPoints[position]);
|
|
23
|
+
return index === -1 ? leftPoints.length - rightPoints.length : (leftPoints[index] ?? 0) - (rightPoints[index] ?? -1);
|
|
24
|
+
};
|
|
25
|
+
exports.compareText = compareText;
|
|
26
|
+
const sortedText = (values) => [...values].toSorted(exports.compareText);
|
|
27
|
+
exports.sortedText = sortedText;
|
|
28
|
+
const hexEscape = (char) => {
|
|
29
|
+
const hex = (char.codePointAt(0) ?? 0).toString(16);
|
|
30
|
+
if (hex.length <= 2) {
|
|
31
|
+
return String.raw `\x${hex.padStart(2, '0')}`;
|
|
32
|
+
}
|
|
33
|
+
return hex.length <= 4 ? String.raw `\u${hex.padStart(4, '0')}` : String.raw `\U${hex.padStart(8, '0')}`;
|
|
34
|
+
};
|
|
35
|
+
const escapePrintable = (char) => (char === ' ' || !notPrintable.test(char) ? char : hexEscape(char));
|
|
36
|
+
const escapeQuoted = (char, quote) => {
|
|
37
|
+
const isSpecial = char === quote || char === '\\';
|
|
38
|
+
return isSpecial ? `\\${char}` : escapePrintable(char);
|
|
39
|
+
};
|
|
40
|
+
const reprText = (text) => {
|
|
41
|
+
const quote = text.includes('\'') && !text.includes('"') ? '"' : '\'';
|
|
42
|
+
const body = Array.from(text, (char) => namedEscapes[char] ?? escapeQuoted(char, quote)).join('');
|
|
43
|
+
return `${quote}${body}${quote}`;
|
|
44
|
+
};
|
|
45
|
+
const booleanRepr = (isTrue) => (isTrue ? 'True' : 'False');
|
|
46
|
+
const numberRepr = (value) => (typeof value === 'number' ? String(value) : 'None');
|
|
47
|
+
const reprScalar = (value) => (typeof value === 'boolean' ? booleanRepr(value) : numberRepr(value));
|
|
48
|
+
const reprObject = (value, repr) => {
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
return `[${value.map((item) => repr(item)).join(', ')}]`;
|
|
51
|
+
}
|
|
52
|
+
return `{${Object.entries(value).map(([key, item]) => `${reprText(key)}: ${repr(item)}`)
|
|
53
|
+
.join(', ')}}`;
|
|
54
|
+
};
|
|
55
|
+
const pyRepr = (value) => {
|
|
56
|
+
if (typeof value === 'string') {
|
|
57
|
+
return reprText(value);
|
|
58
|
+
}
|
|
59
|
+
return typeof value === 'object' && value !== null ? reprObject(value, exports.pyRepr) : reprScalar(value);
|
|
60
|
+
};
|
|
61
|
+
exports.pyRepr = pyRepr;
|
|
62
|
+
const pyText = (value) => (typeof value === 'string' ? value : (0, exports.pyRepr)(value));
|
|
63
|
+
exports.pyText = pyText;
|
|
64
|
+
const roundTenth = (value) => {
|
|
65
|
+
const quarters = value * 4;
|
|
66
|
+
if (Number.isSafeInteger(quarters) && Math.abs(quarters % 2) === 1) {
|
|
67
|
+
const lower = Math.floor(value * 10);
|
|
68
|
+
return (lower % 2 === 0 ? lower : lower + 1) / 10;
|
|
69
|
+
}
|
|
70
|
+
return Number(value.toFixed(1));
|
|
71
|
+
};
|
|
72
|
+
exports.roundTenth = roundTenth;
|
|
73
|
+
const capitalize = (text) => {
|
|
74
|
+
const [first = '', ...rest] = [...text];
|
|
75
|
+
return `${first.toUpperCase()}${rest.join('').toLowerCase()}`;
|
|
76
|
+
};
|
|
77
|
+
exports.capitalize = capitalize;
|
|
78
|
+
const escapeHtml = (text) => text
|
|
79
|
+
.replaceAll('&', '&')
|
|
80
|
+
.replaceAll('<', '<')
|
|
81
|
+
.replaceAll('>', '>')
|
|
82
|
+
.replaceAll('"', '"')
|
|
83
|
+
.replaceAll('\'', ''');
|
|
84
|
+
exports.escapeHtml = escapeHtml;
|
|
85
|
+
const quoteByte = (byte) => `%${byte.toString(16).toUpperCase()
|
|
86
|
+
.padStart(2, '0')}`;
|
|
87
|
+
const quoteBytes = (char) => [...new TextEncoder().encode(char)]
|
|
88
|
+
.map((byte) => quoteByte(byte))
|
|
89
|
+
.join('');
|
|
90
|
+
const quoteChar = (char) => (urlSafe.test(char) ? char : quoteBytes(char));
|
|
91
|
+
const quotePath = (text) => Array.from(text, (char) => quoteChar(char)).join('');
|
|
92
|
+
exports.quotePath = quotePath;
|
|
93
|
+
const isError = (error) => error instanceof Error;
|
|
94
|
+
const messageOf = (error) => (isError(error) ? error.message : String(error));
|
|
95
|
+
exports.messageOf = messageOf;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface BlobLocationInterface {
|
|
2
|
+
readonly commit: string | undefined;
|
|
3
|
+
readonly web: string | undefined;
|
|
4
|
+
}
|
|
5
|
+
export declare const webUrl: (cwd: string, repo: string) => Promise<string | undefined>;
|
|
6
|
+
export declare const blobUrl: (location: BlobLocationInterface, file: string, line?: number) => string | undefined;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.blobUrl = exports.webUrl = void 0;
|
|
4
|
+
const git_js_1 = require("./git.js");
|
|
5
|
+
const python_text_js_1 = require("./python-text.js");
|
|
6
|
+
const remoteForms = [
|
|
7
|
+
/^(?<scheme>https?):\/\/(?:[^/@]*@)?(?<host>[^/@]+)\/(?<path>.+?)(?:\.git)?\/?$/u,
|
|
8
|
+
/^ssh:\/\/(?:[^/@]*@)?(?<host>[^/:@]+)(?::\d+)?\/(?<path>.+?)(?:\.git)?\/?$/u,
|
|
9
|
+
/^[^/:@]+@(?<host>[^/:@]+):(?<path>[^/].*?)(?:\.git)?\/?$/u,
|
|
10
|
+
];
|
|
11
|
+
const webUrl = async (cwd, repo) => {
|
|
12
|
+
const remote = await (0, git_js_1.git)(cwd, repo, ['remote', 'get-url', 'origin']);
|
|
13
|
+
if (remote === undefined) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const address = remote.trim();
|
|
17
|
+
const groups = remoteForms.map((form) => form.exec(address)?.groups).find((found) => found !== undefined);
|
|
18
|
+
return groups === undefined ? undefined : `${groups.scheme ?? 'https'}://${groups.host}/${groups.path}`;
|
|
19
|
+
};
|
|
20
|
+
exports.webUrl = webUrl;
|
|
21
|
+
const hasCommit = (location) => location.commit !== undefined && location.commit !== '';
|
|
22
|
+
const blobUrl = (location, file, line) => {
|
|
23
|
+
if (location.web === undefined || !hasCommit(location)) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const anchor = line === undefined ? '' : `#L${line}`;
|
|
27
|
+
return `${location.web}/-/blob/${location.commit}/${(0, python_text_js_1.quotePath)(file)}${anchor}`;
|
|
28
|
+
};
|
|
29
|
+
exports.blobUrl = blobUrl;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface RemovedListInterface {
|
|
2
|
+
readonly defects: readonly string[];
|
|
3
|
+
readonly entries: ReadonlyMap<string, number>;
|
|
4
|
+
}
|
|
5
|
+
export interface RemovedRowInterface {
|
|
6
|
+
readonly commit: string;
|
|
7
|
+
readonly date: string;
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly line: number;
|
|
10
|
+
readonly reason: string;
|
|
11
|
+
readonly replacedBy: string;
|
|
12
|
+
readonly story: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const removedRows: (cwd: string, file: string) => Promise<RemovedRowInterface[]>;
|
|
15
|
+
export declare const parseRemoved: (cwd: string, file: string) => Promise<RemovedListInterface>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseRemoved = exports.removedRows = void 0;
|
|
4
|
+
const features_js_1 = require("./features.js");
|
|
5
|
+
const python_text_js_1 = require("./python-text.js");
|
|
6
|
+
const fieldColumns = [
|
|
7
|
+
['дата', 'date'],
|
|
8
|
+
['причина', 'reason'],
|
|
9
|
+
['story', 'story'],
|
|
10
|
+
['чем заменён', 'replacedBy'],
|
|
11
|
+
['коммит', 'commit'],
|
|
12
|
+
];
|
|
13
|
+
const emptyCells = new Set(['', '-', '—']);
|
|
14
|
+
const separatorCell = /^[ \-:]*$/u;
|
|
15
|
+
const stripChars = (text, chars) => {
|
|
16
|
+
const kept = [...text];
|
|
17
|
+
const start = kept.findIndex((char) => !chars.includes(char));
|
|
18
|
+
const end = kept.findLastIndex((char) => !chars.includes(char));
|
|
19
|
+
return kept.slice(start, end + 1).join('');
|
|
20
|
+
};
|
|
21
|
+
const cellsOf = (line) => stripChars(line.trim(), '|')
|
|
22
|
+
.split('|')
|
|
23
|
+
.map((cell) => stripChars(cell.trim(), '`').trim());
|
|
24
|
+
const cellAt = (cells, index) => cells[index] ?? '';
|
|
25
|
+
const isRecord = (first) => first !== 'id' && !separatorCell.test(first);
|
|
26
|
+
const rowOf = (cells, line) => ({
|
|
27
|
+
commit: cellAt(cells, 5),
|
|
28
|
+
date: cellAt(cells, 1),
|
|
29
|
+
id: cellAt(cells, 0).replace(/^@+/u, ''),
|
|
30
|
+
line,
|
|
31
|
+
reason: cellAt(cells, 2),
|
|
32
|
+
replacedBy: cellAt(cells, 4),
|
|
33
|
+
story: cellAt(cells, 3),
|
|
34
|
+
});
|
|
35
|
+
const recordOf = (line, index) => {
|
|
36
|
+
const cells = cellsOf(line);
|
|
37
|
+
const isRow = line.trimStart().startsWith('|') && isRecord(cellAt(cells, 0));
|
|
38
|
+
return isRow ? [rowOf(cells, index + 1)] : [];
|
|
39
|
+
};
|
|
40
|
+
const removedRows = async (cwd, file) => {
|
|
41
|
+
if (!await (0, features_js_1.isFile)(cwd, file)) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
const text = await (0, features_js_1.readText)(cwd, file);
|
|
45
|
+
return (0, python_text_js_1.splitLines)(text).flatMap((line, index) => recordOf(line, index));
|
|
46
|
+
};
|
|
47
|
+
exports.removedRows = removedRows;
|
|
48
|
+
const missingFields = (row) => fieldColumns
|
|
49
|
+
.filter(([, column]) => emptyCells.has(row[column]))
|
|
50
|
+
.map(([name]) => name);
|
|
51
|
+
const rowDefects = (row, file) => {
|
|
52
|
+
const missing = missingFields(row);
|
|
53
|
+
return missing.length > 0 ? [`запись ${row.id} в ${file}:${row.line} без полей: ${missing.join(', ')}`] : [];
|
|
54
|
+
};
|
|
55
|
+
const parseRemoved = async (cwd, file) => {
|
|
56
|
+
const rows = await (0, exports.removedRows)(cwd, file);
|
|
57
|
+
return {
|
|
58
|
+
defects: rows.flatMap((row) => rowDefects(row, file)),
|
|
59
|
+
entries: new Map(rows.map((row) => [row.id, row.line])),
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
exports.parseRemoved = parseRemoved;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatReport = void 0;
|
|
4
|
+
const formatReport = (report) => [
|
|
5
|
+
report.summary,
|
|
6
|
+
...report.notes.map((note) => `примечание: ${note}`),
|
|
7
|
+
...report.defects.map((defect) => `дефект: ${defect}`),
|
|
8
|
+
`итого: дефектов ${report.defects.length}`,
|
|
9
|
+
].map((line) => `${line}\n`).join('');
|
|
10
|
+
exports.formatReport = formatReport;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface RunContextInterface {
|
|
2
|
+
readonly cwd: string;
|
|
3
|
+
readonly now: Date;
|
|
4
|
+
}
|
|
5
|
+
export interface RunResultInterface {
|
|
6
|
+
readonly code: number;
|
|
7
|
+
readonly stderr: string;
|
|
8
|
+
readonly stdout: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const runScenarios: (argv: readonly string[], context: RunContextInterface) => Promise<RunResultInterface>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runScenarios = void 0;
|
|
4
|
+
const node_util_1 = require("node:util");
|
|
5
|
+
const classes_js_1 = require("./classes.js");
|
|
6
|
+
const config_js_1 = require("./config.js");
|
|
7
|
+
const coverage_js_1 = require("./coverage.js");
|
|
8
|
+
const dates_js_1 = require("./dates.js");
|
|
9
|
+
const inventory_js_1 = require("./inventory.js");
|
|
10
|
+
const links_js_1 = require("./links.js");
|
|
11
|
+
const lint_js_1 = require("./lint.js");
|
|
12
|
+
const projects_js_1 = require("./projects.js");
|
|
13
|
+
const python_text_js_1 = require("./python-text.js");
|
|
14
|
+
const report_js_1 = require("./report.js");
|
|
15
|
+
const usage = `usage: scenarios [--config <файл>] <команда>
|
|
16
|
+
lint [--list-json <файл>] [--unused-steps <файл>]
|
|
17
|
+
removed --base <ref>
|
|
18
|
+
links --projects <каталог> [--ref <проект>=<ref>]…
|
|
19
|
+
classes --projects <каталог> [--ref <проект>=<ref>]… [--list]
|
|
20
|
+
coverage --projects <каталог> [--ref <проект>=<ref>]… [--since <дата>] [--until <дата>]
|
|
21
|
+
[--out <файл.html>] [--json <файл.json>]
|
|
22
|
+
inventory --out <файл.json> [--story <файл>]…
|
|
23
|
+
Коды возврата: 0 — дефектов нет; 1 — есть дефекты; 2 — ошибка вызова или конфига.
|
|
24
|
+
`;
|
|
25
|
+
const text = { type: 'string' };
|
|
26
|
+
const defaultConfig = 'scenarios.config.json';
|
|
27
|
+
const configPrefix = '--config=';
|
|
28
|
+
const flagErrorCode = 'ERR_PARSE_ARGS';
|
|
29
|
+
const projectOptions = { projects: text, ref: { multiple: true, type: 'string' } };
|
|
30
|
+
const commandOptions = {
|
|
31
|
+
classes: { ...projectOptions, list: { type: 'boolean' } },
|
|
32
|
+
coverage: { ...projectOptions, json: text, out: text, since: text, until: text },
|
|
33
|
+
inventory: { out: text, story: { multiple: true, type: 'string' } },
|
|
34
|
+
links: projectOptions,
|
|
35
|
+
lint: { 'list-json': text, 'unused-steps': text },
|
|
36
|
+
removed: { base: text },
|
|
37
|
+
};
|
|
38
|
+
const requiredOptions = {
|
|
39
|
+
classes: ['projects'],
|
|
40
|
+
coverage: ['projects'],
|
|
41
|
+
inventory: ['out'],
|
|
42
|
+
links: ['projects'],
|
|
43
|
+
lint: [],
|
|
44
|
+
removed: ['base'],
|
|
45
|
+
};
|
|
46
|
+
class UsageError extends Error {
|
|
47
|
+
name = 'UsageError';
|
|
48
|
+
}
|
|
49
|
+
const optional = (values, name) => {
|
|
50
|
+
const value = values[name];
|
|
51
|
+
return typeof value === 'string' ? value : undefined;
|
|
52
|
+
};
|
|
53
|
+
const required = (values, name) => {
|
|
54
|
+
const value = optional(values, name);
|
|
55
|
+
if (value === undefined) {
|
|
56
|
+
throw new UsageError(`нужен --${name}`);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
};
|
|
60
|
+
const projectInput = async (context, config, values) => {
|
|
61
|
+
const projects = required(values, 'projects');
|
|
62
|
+
const items = Array.isArray(values.ref) ? values.ref.map(String) : [];
|
|
63
|
+
const revisions = await (0, projects_js_1.parseRevisions)(context.cwd, projects, items);
|
|
64
|
+
return { config, cwd: context.cwd, projects, revisions };
|
|
65
|
+
};
|
|
66
|
+
const coverage = async (context, config, values) => {
|
|
67
|
+
const today = (0, dates_js_1.localToday)(context.now);
|
|
68
|
+
const period = (0, dates_js_1.periodOf)({ since: optional(values, 'since'), today, until: optional(values, 'until') });
|
|
69
|
+
const input = await projectInput(context, config, values);
|
|
70
|
+
return (0, coverage_js_1.coverageCommand)(input, { json: optional(values, 'json'), out: optional(values, 'out'), period, today });
|
|
71
|
+
};
|
|
72
|
+
const handlers = {
|
|
73
|
+
classes: async (context, config, values) => {
|
|
74
|
+
const input = await projectInput(context, config, values);
|
|
75
|
+
return (0, classes_js_1.classesCommand)(input, values.list === true);
|
|
76
|
+
},
|
|
77
|
+
coverage,
|
|
78
|
+
inventory: async (context, config, values) => (0, inventory_js_1.inventoryCommand)(context.cwd, config, {
|
|
79
|
+
out: required(values, 'out'),
|
|
80
|
+
stories: Array.isArray(values.story) ? values.story.map(String) : [],
|
|
81
|
+
}),
|
|
82
|
+
links: async (context, config, values) => (0, links_js_1.linksCommand)(config, await projectInput(context, config, values)),
|
|
83
|
+
lint: async (context, config, values) => (0, lint_js_1.lintCommand)(context.cwd, config, {
|
|
84
|
+
listJson: optional(values, 'list-json'),
|
|
85
|
+
unusedSteps: optional(values, 'unused-steps'),
|
|
86
|
+
}),
|
|
87
|
+
removed: async (context, config, values) => (0, lint_js_1.removedCommand)(context.cwd, config, required(values, 'base')),
|
|
88
|
+
};
|
|
89
|
+
const isCommand = (name) => Object.hasOwn(commandOptions, name);
|
|
90
|
+
const inlineConfig = (first, argv) => {
|
|
91
|
+
const isInline = first?.startsWith(configPrefix) === true;
|
|
92
|
+
return isInline ? [first.slice(configPrefix.length), argv.slice(1)] : [defaultConfig, argv];
|
|
93
|
+
};
|
|
94
|
+
const splitGlobal = (argv) => {
|
|
95
|
+
const [first, second] = argv;
|
|
96
|
+
return first === '--config' && second !== undefined ? [second, argv.slice(2)] : inlineConfig(first, argv);
|
|
97
|
+
};
|
|
98
|
+
const commandOf = (name) => {
|
|
99
|
+
if (!isCommand(name)) {
|
|
100
|
+
throw new UsageError(name === '' ? 'нужна команда' : `команды «${name}» нет`);
|
|
101
|
+
}
|
|
102
|
+
return name;
|
|
103
|
+
};
|
|
104
|
+
const parseCommand = (argv) => {
|
|
105
|
+
const [configFile, rest] = splitGlobal(argv);
|
|
106
|
+
const [name = '', ...options] = rest;
|
|
107
|
+
const command = commandOf(name);
|
|
108
|
+
const parsed = (0, node_util_1.parseArgs)({ allowPositionals: false, args: options, options: commandOptions[command], strict: true });
|
|
109
|
+
const { values } = parsed;
|
|
110
|
+
const names = requiredOptions[command];
|
|
111
|
+
for (const option of names) {
|
|
112
|
+
required(values, option);
|
|
113
|
+
}
|
|
114
|
+
return { command, configFile, values };
|
|
115
|
+
};
|
|
116
|
+
const codeOf = (error) => String(Reflect.get(new Object(error), 'code'));
|
|
117
|
+
const isUsageError = (error) => error instanceof UsageError || codeOf(error).startsWith(flagErrorCode);
|
|
118
|
+
const failure = (error) => {
|
|
119
|
+
if (error instanceof config_js_1.ConfigError) {
|
|
120
|
+
return { code: 2, stderr: `ошибка: ${error.message}\n`, stdout: '' };
|
|
121
|
+
}
|
|
122
|
+
if (isUsageError(error)) {
|
|
123
|
+
return { code: 2, stderr: `${usage}ошибка вызова: ${(0, python_text_js_1.messageOf)(error)}\n`, stdout: '' };
|
|
124
|
+
}
|
|
125
|
+
throw error;
|
|
126
|
+
};
|
|
127
|
+
const isHelp = (argv) => argv.includes('--help') || argv.includes('-h');
|
|
128
|
+
const execute = async (argv, context) => {
|
|
129
|
+
const { command, configFile, values } = parseCommand(argv);
|
|
130
|
+
const config = await (0, config_js_1.loadConfig)(configFile, context.cwd);
|
|
131
|
+
const report = await handlers[command](context, config, values);
|
|
132
|
+
return { code: report.defects.length > 0 ? 1 : 0, stderr: '', stdout: (0, report_js_1.formatReport)(report) };
|
|
133
|
+
};
|
|
134
|
+
const runScenarios = async (argv, context) => {
|
|
135
|
+
if (isHelp(argv)) {
|
|
136
|
+
return { code: 0, stderr: '', stdout: usage };
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
return await execute(argv, context);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return failure(error);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
exports.runScenarios = runScenarios;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ProjectsInputInterface } from './projects.js';
|
|
2
|
+
export interface LinkRowInterface {
|
|
3
|
+
readonly file: string;
|
|
4
|
+
readonly line: number;
|
|
5
|
+
readonly project: string;
|
|
6
|
+
readonly status: string;
|
|
7
|
+
readonly text: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ReadProjectsInterface {
|
|
10
|
+
readonly notes: readonly string[];
|
|
11
|
+
readonly rows: readonly LinkRowInterface[];
|
|
12
|
+
readonly sources: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export declare const linkToken: RegExp;
|
|
15
|
+
export declare const readProjects: (input: ProjectsInputInterface) => Promise<ReadProjectsInterface>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readProjects = exports.linkToken = void 0;
|
|
4
|
+
const config_js_1 = require("./config.js");
|
|
5
|
+
const features_js_1 = require("./features.js");
|
|
6
|
+
const git_js_1 = require("./git.js");
|
|
7
|
+
const projects_js_1 = require("./projects.js");
|
|
8
|
+
const python_text_js_1 = require("./python-text.js");
|
|
9
|
+
exports.linkToken = /@(?<project>[\da-z][\d\-a-z]*):AC-[\d\-.A-Za-z]*[\dA-Za-z]/gu;
|
|
10
|
+
const linkGrep = '@[a-z0-9][a-z0-9-]*:AC-';
|
|
11
|
+
const hasLink = (text) => text.match(exports.linkToken) !== null;
|
|
12
|
+
const fileLines = async (cwd, file) => {
|
|
13
|
+
const text = await (0, features_js_1.readText)(cwd, file);
|
|
14
|
+
return (0, python_text_js_1.splitLines)(text).flatMap((line, index) => (hasLink(line) ? [[file, index + 1, line]] : []));
|
|
15
|
+
};
|
|
16
|
+
const ownTaskLines = async (cwd) => {
|
|
17
|
+
const files = await (0, features_js_1.walkFiles)(cwd, 'tasks', '.md');
|
|
18
|
+
const lines = await Promise.all(files.map(async (file) => fileLines(cwd, file)));
|
|
19
|
+
return lines.flat();
|
|
20
|
+
};
|
|
21
|
+
const splitGrepLine = (line) => {
|
|
22
|
+
const first = line.indexOf(':');
|
|
23
|
+
const second = line.indexOf(':', first + 1);
|
|
24
|
+
const file = line.slice(0, first);
|
|
25
|
+
return second === -1 || !file.endsWith('.md')
|
|
26
|
+
? []
|
|
27
|
+
: [[file, Number(line.slice(first + 1, second)), line.slice(second + 1)]];
|
|
28
|
+
};
|
|
29
|
+
const revisionTaskLines = async (cwd, project, revision) => {
|
|
30
|
+
const done = await (0, git_js_1.runGit)({ cwd, repo: project.repo }, ['grep', '-n', '-I', '-E', linkGrep, revision, '--', 'tasks']);
|
|
31
|
+
if (done.code > 1 || done.code < 0) {
|
|
32
|
+
throw new config_js_1.ConfigError(`git grep в ${project.repo} на ${revision} не прошёл: ${done.stderr.trim()}`);
|
|
33
|
+
}
|
|
34
|
+
return (0, python_text_js_1.splitLines)((0, git_js_1.stdoutText)(done))
|
|
35
|
+
.flatMap((line) => splitGrepLine(line.slice(revision.length + 1)));
|
|
36
|
+
};
|
|
37
|
+
const taskLines = async (cwd, project) => (project.revision === undefined ? ownTaskLines(cwd) : revisionTaskLines(cwd, project, project.revision));
|
|
38
|
+
const projectRows = async (cwd, project) => {
|
|
39
|
+
const statuses = await (0, projects_js_1.projectStatuses)(cwd, project);
|
|
40
|
+
const lines = await taskLines(cwd, project);
|
|
41
|
+
return lines.map(([file, line, text]) => ({
|
|
42
|
+
file,
|
|
43
|
+
line,
|
|
44
|
+
project: project.name,
|
|
45
|
+
status: statuses.get((0, projects_js_1.storyKey)(file)) ?? '',
|
|
46
|
+
text,
|
|
47
|
+
}));
|
|
48
|
+
};
|
|
49
|
+
const readProjects = async (input) => {
|
|
50
|
+
const walk = await (0, projects_js_1.walkProjects)(input);
|
|
51
|
+
const rows = await Promise.all(walk.projects.map(async (project) => projectRows(input.cwd, project)));
|
|
52
|
+
return { notes: walk.notes, rows: rows.flat(), sources: walk.projects.map((project) => (0, projects_js_1.sourceOf)(project)) };
|
|
53
|
+
};
|
|
54
|
+
exports.readProjects = readProjects;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { runScenarios } from '../scenarios/run.js';
|
|
4
|
+
const main = async () => {
|
|
5
|
+
const result = await runScenarios(process.argv.slice(2), { cwd: process.cwd(), now: new Date() });
|
|
6
|
+
process.stdout.write(result.stdout);
|
|
7
|
+
process.stderr.write(result.stderr);
|
|
8
|
+
process.exitCode = result.code;
|
|
9
|
+
};
|
|
10
|
+
void main();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './application-scenarios.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './application-scenarios.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './resolve-cadence.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './resolve-cadence.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface CadenceInterface {
|
|
2
|
+
readonly exclude: readonly string[];
|
|
3
|
+
readonly include: readonly string[];
|
|
4
|
+
readonly name: string;
|
|
5
|
+
}
|
|
6
|
+
type Variables = Readonly<Record<string, string | undefined>>;
|
|
7
|
+
export declare const resolveCadence: (variables: Variables) => CadenceInterface;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const regression = { exclude: ['@nightly'], include: [], name: 'regression' };
|
|
2
|
+
const smoke = { exclude: [], include: ['@smoke'], name: 'smoke' };
|
|
3
|
+
const nightly = { exclude: [], include: ['@nightly'], name: 'nightly' };
|
|
4
|
+
const cadences = [nightly, regression, smoke];
|
|
5
|
+
export const resolveCadence = (variables) => {
|
|
6
|
+
const requested = variables.ACCEPTANCE_CADENCE ?? '';
|
|
7
|
+
const name = requested === '' ? regression.name : requested;
|
|
8
|
+
const cadence = cadences.find((candidate) => candidate.name === name);
|
|
9
|
+
if (cadence === undefined) {
|
|
10
|
+
const known = cadences.map((candidate) => candidate.name).join(', ');
|
|
11
|
+
throw new Error(`ACCEPTANCE_CADENCE: каденса «${name}» нет (есть: ${known})`);
|
|
12
|
+
}
|
|
13
|
+
return cadence;
|
|
14
|
+
};
|