@tlog/shared 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/README.md +141 -0
- package/dist/builders.d.ts +27 -0
- package/dist/builders.d.ts.map +1 -0
- package/dist/builders.js +53 -0
- package/dist/builders.js.map +1 -0
- package/dist/domain.d.ts +64 -0
- package/dist/domain.d.ts.map +1 -0
- package/dist/domain.js +40 -0
- package/dist/domain.js.map +1 -0
- package/dist/filter.d.ts +27 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.js +90 -0
- package/dist/filter.js.map +1 -0
- package/dist/id-index.d.ts +26 -0
- package/dist/id-index.d.ts.map +1 -0
- package/dist/id-index.js +99 -0
- package/dist/id-index.js.map +1 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/naming.d.ts +6 -0
- package/dist/naming.d.ts.map +1 -0
- package/dist/naming.js +32 -0
- package/dist/naming.js.map +1 -0
- package/dist/result.d.ts +25 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +10 -0
- package/dist/result.js.map +1 -0
- package/dist/schemas.d.ts +299 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +88 -0
- package/dist/schemas.js.map +1 -0
- package/dist/statistics.d.ts +20 -0
- package/dist/statistics.d.ts.map +1 -0
- package/dist/statistics.js +59 -0
- package/dist/statistics.js.map +1 -0
- package/dist/template.d.ts +23 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +51 -0
- package/dist/template.js.map +1 -0
- package/dist/validation.d.ts +14 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +77 -0
- package/dist/validation.js.map +1 -0
- package/dist/yaml-io.d.ts +10 -0
- package/dist/yaml-io.d.ts.map +1 -0
- package/dist/yaml-io.js +43 -0
- package/dist/yaml-io.js.map +1 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# @tlog/shared
|
|
2
|
+
|
|
3
|
+
`@tlog/shared` は CLI / MCP / VS Code extension で共通利用するドメインモデルとコアロジックを提供する。
|
|
4
|
+
|
|
5
|
+
## Task Coverage
|
|
6
|
+
|
|
7
|
+
- `TASK-001` Domain types / enums: `src/domain.ts`, `src/schemas.ts`
|
|
8
|
+
- `TASK-002` Schema validator: `src/validation.ts`
|
|
9
|
+
- `TASK-003` YAML parser/serializer: `src/yaml-io.ts`
|
|
10
|
+
- `TASK-004` ID index / reference resolution: `src/id-index.ts`
|
|
11
|
+
- `TASK-005` File naming / slug policy: `src/naming.ts`
|
|
12
|
+
- `TASK-006` Default builders: `src/builders.ts`
|
|
13
|
+
- `TASK-007` Search / filter engine: `src/filter.ts`
|
|
14
|
+
- `TASK-008` Statistics / burndown engine: `src/statistics.ts`
|
|
15
|
+
- `TASK-009` Template apply / extract: `src/template.ts`
|
|
16
|
+
- `TASK-010` Error model / result contract: `src/result.ts`
|
|
17
|
+
|
|
18
|
+
## API Contracts
|
|
19
|
+
|
|
20
|
+
### Domain (`TASK-001`)
|
|
21
|
+
|
|
22
|
+
- `type Suite`
|
|
23
|
+
- input/output fields: `id`, `title`, `tags`, `description`, `scoped`, `owners`, `duration`, `related`, `remarks`
|
|
24
|
+
- `type TestCase`
|
|
25
|
+
- input/output fields: `id`, `title`, `tags`, `description`, `scoped`, `status`, `operations`, `related`, `remarks`, `completedDay`, `tests`, `issues`
|
|
26
|
+
- `type TestItem`
|
|
27
|
+
- input/output fields: `name`, `expected`, `actual`, `trails`, `status`
|
|
28
|
+
- `type Issue`
|
|
29
|
+
- input/output fields: `incident`, `owners`, `causes`, `solutions`, `status`, `detectedDay`, `completedDay`, `related`, `remarks`
|
|
30
|
+
- status enums
|
|
31
|
+
- testcase status: `"todo" | "doing" | "done" | null`
|
|
32
|
+
- test result status: `"pass" | "fail" | "skip" | "block" | null`
|
|
33
|
+
- issue status: `"open" | "doing" | "resolved" | "pending"`
|
|
34
|
+
- date
|
|
35
|
+
- `type TlogDateString`
|
|
36
|
+
- guard: `isTlogDateString(value: string): value is TlogDateString`
|
|
37
|
+
- converter: `asTlogDateString(value: string): TlogDateString`
|
|
38
|
+
|
|
39
|
+
### Validation (`TASK-002`)
|
|
40
|
+
|
|
41
|
+
- `validateSuite(input: unknown): ValidationResult<Suite>`
|
|
42
|
+
- `validateCase(input: unknown): ValidationResult<TestCase>`
|
|
43
|
+
- `ValidationResult<T>`
|
|
44
|
+
- `ok: boolean`
|
|
45
|
+
- `data?: T` (`ok === true` のとき)
|
|
46
|
+
- `errors: ValidationDiagnostic[]`
|
|
47
|
+
- `warnings: ValidationDiagnostic[]`
|
|
48
|
+
- `ValidationDiagnostic`
|
|
49
|
+
- `path: string`
|
|
50
|
+
- `message: string`
|
|
51
|
+
|
|
52
|
+
### YAML I/O (`TASK-003`)
|
|
53
|
+
|
|
54
|
+
- `parseYaml<T>(source: string): T`
|
|
55
|
+
- invalid YAML の場合 `TlogYamlParseError` を throw
|
|
56
|
+
- `TlogYamlParseError` fields: `line`, `column`, `message`
|
|
57
|
+
- `stringifyYaml(value: unknown): string`
|
|
58
|
+
- `readYamlFile<T>(path: string): Promise<T>`
|
|
59
|
+
- `writeYamlFileAtomic(path: string, value: unknown): Promise<void>`
|
|
60
|
+
- temp file 書き込み後に rename する atomic write
|
|
61
|
+
|
|
62
|
+
### ID Resolution (`TASK-004`)
|
|
63
|
+
|
|
64
|
+
- `buildIdIndex(rootDir: string): Promise<IdIndex>`
|
|
65
|
+
- `resolveById(index: IdIndex, id: string): TlogIndexedEntity | undefined`
|
|
66
|
+
- `resolveRelated(index: IdIndex, source: { related: string[] }): RelatedResolution`
|
|
67
|
+
- `IdIndex`
|
|
68
|
+
- `byId: Map<string, TlogIndexedEntity>`
|
|
69
|
+
- `entities: TlogIndexedEntity[]`
|
|
70
|
+
- `duplicates: { id: string; paths: string[] }[]`
|
|
71
|
+
|
|
72
|
+
### Naming (`TASK-005`)
|
|
73
|
+
|
|
74
|
+
- `slugifyTitle(title: string): string`
|
|
75
|
+
- `ensureUniqueSlug(baseSlug: string, usedSlugs: Set<string>): string`
|
|
76
|
+
- `buildSuiteFileName(id: string, title: string): string`
|
|
77
|
+
- `buildCaseFileName(id: string, title: string): string`
|
|
78
|
+
- `normalizeTlogPath(input: string): string`
|
|
79
|
+
|
|
80
|
+
### Builders (`TASK-006`)
|
|
81
|
+
|
|
82
|
+
- `buildDefaultSuite(input: BuildDefaultSuiteInput): Suite`
|
|
83
|
+
- `buildDefaultCase(input: BuildDefaultCaseInput): TestCase`
|
|
84
|
+
- builder は内部で `validateSuite` / `validateCase` を実行し、invalid な場合 throw する
|
|
85
|
+
|
|
86
|
+
### Filter Engine (`TASK-007`)
|
|
87
|
+
|
|
88
|
+
- `evaluateFilters(entity: Suite | TestCase, filters: SearchFilters): FilterMeta`
|
|
89
|
+
- `filterEntities<T extends Suite | TestCase>(entities: T[], filters: SearchFilters): { items: T[]; meta: FilterMeta }`
|
|
90
|
+
- `SearchFilters`
|
|
91
|
+
- `tags?: string[]`
|
|
92
|
+
- `owners?: string[]`
|
|
93
|
+
- `testcaseStatus?: TestCaseStatus[]`
|
|
94
|
+
- `testStatus?: TestResultStatus[]`
|
|
95
|
+
- `date?: DateFilter`
|
|
96
|
+
|
|
97
|
+
### Statistics (`TASK-008`)
|
|
98
|
+
|
|
99
|
+
- `summarizeStatus(cases: TestCase[]): StatusSummary`
|
|
100
|
+
- `todo`, `doing`, `done`, `total`
|
|
101
|
+
- `calculateBurndown(cases: TestCase[], start: string, end: string): BurndownResult`
|
|
102
|
+
- `summary: StatusSummary`
|
|
103
|
+
- `buckets: BurndownBucket[]`
|
|
104
|
+
- `anomalies: string[]` (`invalid_date_range`, `no_target_cases` など)
|
|
105
|
+
|
|
106
|
+
### Template (`TASK-009`)
|
|
107
|
+
|
|
108
|
+
- `applyTemplate(suiteInput, caseInput, template?): { suite: Suite; testCase: TestCase }`
|
|
109
|
+
- `extractTemplateFromDirectory(rootDir: string): Promise<TlogTemplate>`
|
|
110
|
+
- `validateTemplate(template: TlogTemplate): { valid: boolean; errors: string[] }`
|
|
111
|
+
|
|
112
|
+
### Result Contract (`TASK-010`)
|
|
113
|
+
|
|
114
|
+
- `type Result<T, E = TlogError>`
|
|
115
|
+
- success: `{ ok: true; data: T; warnings: TlogWarning[] }`
|
|
116
|
+
- failure: `{ ok: false; error: E; warnings: TlogWarning[] }`
|
|
117
|
+
- `ok<T>(data: T, warnings?: TlogWarning[]): Result<T>`
|
|
118
|
+
- `err<E>(error: E, warnings?: TlogWarning[]): Result<never, E>`
|
|
119
|
+
- `serializeResult(result): string` (`--json` / MCP response / VS Code diagnostics への橋渡し用)
|
|
120
|
+
|
|
121
|
+
## Usage Example
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import {
|
|
125
|
+
buildDefaultCase,
|
|
126
|
+
buildDefaultSuite,
|
|
127
|
+
validateCase,
|
|
128
|
+
writeYamlFileAtomic
|
|
129
|
+
} from "@tlog/shared";
|
|
130
|
+
|
|
131
|
+
const suite = buildDefaultSuite({ id: "suite-login", title: "Login Suite" });
|
|
132
|
+
const testCase = buildDefaultCase({ id: "case-login-001", title: "Login happy path" });
|
|
133
|
+
|
|
134
|
+
const caseValidation = validateCase(testCase);
|
|
135
|
+
if (!caseValidation.ok) {
|
|
136
|
+
throw new Error(caseValidation.errors.map((e) => e.message).join(", "));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await writeYamlFileAtomic("tests/index.yaml", suite);
|
|
140
|
+
await writeYamlFileAtomic("tests/case-login-001.yaml", testCase);
|
|
141
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Duration, type Suite, type TestCase, type TestCaseStatus } from "./domain.js";
|
|
2
|
+
export interface BuildDefaultSuiteInput {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
scoped?: boolean;
|
|
7
|
+
tags?: string[];
|
|
8
|
+
owners?: string[];
|
|
9
|
+
duration?: Duration;
|
|
10
|
+
related?: string[];
|
|
11
|
+
remarks?: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface BuildDefaultCaseInput {
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
scoped?: boolean;
|
|
18
|
+
status?: TestCaseStatus;
|
|
19
|
+
tags?: string[];
|
|
20
|
+
operations?: string[];
|
|
21
|
+
related?: string[];
|
|
22
|
+
remarks?: string[];
|
|
23
|
+
completedDay?: string | null;
|
|
24
|
+
}
|
|
25
|
+
export declare function buildDefaultSuite(input: BuildDefaultSuiteInput): Suite;
|
|
26
|
+
export declare function buildDefaultCase(input: BuildDefaultCaseInput): TestCase;
|
|
27
|
+
//# sourceMappingURL=builders.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../src/builders.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,QAAQ,EAAE,KAAK,KAAK,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAG9G,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAcD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,KAAK,CAmBtE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,QAAQ,CAyBvE"}
|
package/dist/builders.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { asTlogDateString } from "./domain.js";
|
|
2
|
+
import { validateCase, validateSuite } from "./validation.js";
|
|
3
|
+
function todayDateString() {
|
|
4
|
+
return new Date().toISOString().slice(0, 10);
|
|
5
|
+
}
|
|
6
|
+
function buildDefaultDuration() {
|
|
7
|
+
const today = asTlogDateString(todayDateString());
|
|
8
|
+
return {
|
|
9
|
+
scheduled: { start: today, end: today },
|
|
10
|
+
actual: { start: today, end: today }
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function buildDefaultSuite(input) {
|
|
14
|
+
const candidate = {
|
|
15
|
+
id: input.id,
|
|
16
|
+
title: input.title,
|
|
17
|
+
tags: input.tags ?? [],
|
|
18
|
+
description: input.description ?? "",
|
|
19
|
+
scoped: input.scoped ?? true,
|
|
20
|
+
owners: input.owners ?? [],
|
|
21
|
+
duration: input.duration ?? buildDefaultDuration(),
|
|
22
|
+
related: input.related ?? [],
|
|
23
|
+
remarks: input.remarks ?? []
|
|
24
|
+
};
|
|
25
|
+
const validation = validateSuite(candidate);
|
|
26
|
+
if (!validation.ok || !validation.data) {
|
|
27
|
+
throw new Error(`Invalid suite defaults: ${validation.errors.map((err) => err.path).join(", ")}`);
|
|
28
|
+
}
|
|
29
|
+
return validation.data;
|
|
30
|
+
}
|
|
31
|
+
export function buildDefaultCase(input) {
|
|
32
|
+
const completedDay = input.completedDay === null ? null : asTlogDateString(input.completedDay ?? todayDateString());
|
|
33
|
+
const candidate = {
|
|
34
|
+
id: input.id,
|
|
35
|
+
title: input.title,
|
|
36
|
+
tags: input.tags ?? [],
|
|
37
|
+
description: input.description ?? "",
|
|
38
|
+
scoped: input.scoped ?? true,
|
|
39
|
+
status: input.status ?? "todo",
|
|
40
|
+
operations: input.operations ?? [],
|
|
41
|
+
related: input.related ?? [],
|
|
42
|
+
remarks: input.remarks ?? [],
|
|
43
|
+
completedDay,
|
|
44
|
+
tests: [],
|
|
45
|
+
issues: []
|
|
46
|
+
};
|
|
47
|
+
const validation = validateCase(candidate);
|
|
48
|
+
if (!validation.ok || !validation.data) {
|
|
49
|
+
throw new Error(`Invalid case defaults: ${validation.errors.map((err) => err.path).join(", ")}`);
|
|
50
|
+
}
|
|
51
|
+
return validation.data;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=builders.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builders.js","sourceRoot":"","sources":["../src/builders.ts"],"names":[],"mappings":"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"}
|
package/dist/domain.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export declare const TESTCASE_STATUSES: readonly ["todo", "doing", "done", null];
|
|
2
|
+
export type TestCaseStatus = (typeof TESTCASE_STATUSES)[number];
|
|
3
|
+
export declare const TEST_RESULT_STATUSES: readonly ["pass", "fail", "skip", "block", null];
|
|
4
|
+
export type TestResultStatus = (typeof TEST_RESULT_STATUSES)[number];
|
|
5
|
+
export declare const ISSUE_STATUSES: readonly ["open", "doing", "resolved", "pending"];
|
|
6
|
+
export type IssueStatus = (typeof ISSUE_STATUSES)[number];
|
|
7
|
+
export type TlogDateString = string & {
|
|
8
|
+
readonly __tlogDateBrand: "TlogDateString";
|
|
9
|
+
};
|
|
10
|
+
export interface DurationRange {
|
|
11
|
+
start: TlogDateString;
|
|
12
|
+
end: TlogDateString;
|
|
13
|
+
}
|
|
14
|
+
export interface Duration {
|
|
15
|
+
scheduled: DurationRange;
|
|
16
|
+
actual: DurationRange;
|
|
17
|
+
}
|
|
18
|
+
export interface Suite {
|
|
19
|
+
id: string;
|
|
20
|
+
title: string;
|
|
21
|
+
tags: string[];
|
|
22
|
+
description: string;
|
|
23
|
+
scoped: boolean;
|
|
24
|
+
owners: string[];
|
|
25
|
+
duration: Duration;
|
|
26
|
+
related: string[];
|
|
27
|
+
remarks: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface TestItem {
|
|
30
|
+
name: string;
|
|
31
|
+
expected: string;
|
|
32
|
+
actual: string;
|
|
33
|
+
trails: string[];
|
|
34
|
+
status: TestResultStatus;
|
|
35
|
+
}
|
|
36
|
+
export interface Issue {
|
|
37
|
+
incident: string;
|
|
38
|
+
owners: string[];
|
|
39
|
+
causes: string[];
|
|
40
|
+
solutions: string[];
|
|
41
|
+
status: IssueStatus;
|
|
42
|
+
detectedDay: TlogDateString | null;
|
|
43
|
+
completedDay: TlogDateString | null;
|
|
44
|
+
related: string[];
|
|
45
|
+
remarks: string[];
|
|
46
|
+
}
|
|
47
|
+
export interface TestCase {
|
|
48
|
+
id: string;
|
|
49
|
+
title: string;
|
|
50
|
+
tags: string[];
|
|
51
|
+
description: string;
|
|
52
|
+
scoped: boolean;
|
|
53
|
+
status: TestCaseStatus;
|
|
54
|
+
operations: string[];
|
|
55
|
+
related: string[];
|
|
56
|
+
remarks: string[];
|
|
57
|
+
completedDay: TlogDateString | null;
|
|
58
|
+
tests: TestItem[];
|
|
59
|
+
issues: Issue[];
|
|
60
|
+
}
|
|
61
|
+
export declare function isTlogDateString(value: string): value is TlogDateString;
|
|
62
|
+
export declare function asTlogDateString(value: string): TlogDateString;
|
|
63
|
+
export declare const defaultSuite: Suite;
|
|
64
|
+
//# sourceMappingURL=domain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../src/domain.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,0CAA2C,CAAC;AAC1E,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE,eAAO,MAAM,oBAAoB,kDAAmD,CAAC;AACrF,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAErE,eAAO,MAAM,cAAc,mDAAoD,CAAC;AAChF,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1D,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,eAAe,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAErF,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,cAAc,CAAC;IACtB,GAAG,EAAE,cAAc,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,aAAa,CAAC;IACzB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,cAAc,GAAG,IAAI,CAAC;IACnC,YAAY,EAAE,cAAc,GAAG,IAAI,CAAC;IACpC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,cAAc,GAAG,IAAI,CAAC;IACpC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,cAAc,CAWvE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAM9D;AAED,eAAO,MAAM,YAAY,EAAE,KAmB1B,CAAC"}
|
package/dist/domain.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const TESTCASE_STATUSES = ["todo", "doing", "done", null];
|
|
2
|
+
export const TEST_RESULT_STATUSES = ["pass", "fail", "skip", "block", null];
|
|
3
|
+
export const ISSUE_STATUSES = ["open", "doing", "resolved", "pending"];
|
|
4
|
+
export function isTlogDateString(value) {
|
|
5
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
const date = new Date(`${value}T00:00:00.000Z`);
|
|
9
|
+
if (Number.isNaN(date.getTime())) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
return date.toISOString().slice(0, 10) === value;
|
|
13
|
+
}
|
|
14
|
+
export function asTlogDateString(value) {
|
|
15
|
+
if (!isTlogDateString(value)) {
|
|
16
|
+
throw new Error(`Invalid date format: ${value}. Expected YYYY-MM-DD.`);
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
export const defaultSuite = {
|
|
21
|
+
id: "default",
|
|
22
|
+
title: "Default Suite",
|
|
23
|
+
tags: [],
|
|
24
|
+
description: "tlog test suite",
|
|
25
|
+
scoped: true,
|
|
26
|
+
owners: [],
|
|
27
|
+
duration: {
|
|
28
|
+
scheduled: {
|
|
29
|
+
start: asTlogDateString("1970-01-01"),
|
|
30
|
+
end: asTlogDateString("1970-01-01")
|
|
31
|
+
},
|
|
32
|
+
actual: {
|
|
33
|
+
start: asTlogDateString("1970-01-01"),
|
|
34
|
+
end: asTlogDateString("1970-01-01")
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
related: [],
|
|
38
|
+
remarks: []
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=domain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain.js","sourceRoot":"","sources":["../src/domain.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAU,CAAC;AAG1E,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAU,CAAC;AAGrF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,CAAU,CAAC;AA8DhF,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACvC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,KAAK,gBAAgB,CAAC,CAAC;IAChD,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QACjC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,wBAAwB,KAAK,wBAAwB,CAAC,CAAC;IACzE,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAU;IACjC,EAAE,EAAE,SAAS;IACb,KAAK,EAAE,eAAe;IACtB,IAAI,EAAE,EAAE;IACR,WAAW,EAAE,iBAAiB;IAC9B,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,EAAE;IACV,QAAQ,EAAE;QACR,SAAS,EAAE;YACT,KAAK,EAAE,gBAAgB,CAAC,YAAY,CAAC;YACrC,GAAG,EAAE,gBAAgB,CAAC,YAAY,CAAC;SACpC;QACD,MAAM,EAAE;YACN,KAAK,EAAE,gBAAgB,CAAC,YAAY,CAAC;YACrC,GAAG,EAAE,gBAAgB,CAAC,YAAY,CAAC;SACpC;KACF;IACD,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,EAAE;CACZ,CAAC"}
|
package/dist/filter.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Suite, TestCase, TestCaseStatus, TestResultStatus } from "./domain.js";
|
|
2
|
+
export type DateOperator = "between" | "onOrAfter" | "onOrBefore";
|
|
3
|
+
export interface DateFilter {
|
|
4
|
+
field: "completedDay" | "duration.scheduled.start" | "duration.scheduled.end";
|
|
5
|
+
operator: DateOperator;
|
|
6
|
+
from: string;
|
|
7
|
+
to?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SearchFilters {
|
|
10
|
+
tags?: string[];
|
|
11
|
+
owners?: string[];
|
|
12
|
+
testcaseStatus?: TestCaseStatus[];
|
|
13
|
+
testStatus?: TestResultStatus[];
|
|
14
|
+
date?: DateFilter;
|
|
15
|
+
}
|
|
16
|
+
export interface FilterMeta {
|
|
17
|
+
matched: boolean;
|
|
18
|
+
checkedConditions: number;
|
|
19
|
+
matchedConditions: number;
|
|
20
|
+
reasons: string[];
|
|
21
|
+
}
|
|
22
|
+
export declare function evaluateFilters(entity: Suite | TestCase, filters: SearchFilters): FilterMeta;
|
|
23
|
+
export declare function filterEntities<T extends Suite | TestCase>(entities: T[], filters: SearchFilters): {
|
|
24
|
+
items: T[];
|
|
25
|
+
meta: FilterMeta;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.d.ts","sourceRoot":"","sources":["../src/filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAErF,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,YAAY,CAAC;AAElE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,cAAc,GAAG,0BAA0B,GAAG,wBAAwB,CAAC;IAC9E,QAAQ,EAAE,YAAY,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,cAAc,CAAC,EAAE,cAAc,EAAE,CAAC;IAClC,UAAU,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAChC,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAmCD,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,GAAG,QAAQ,EAAE,OAAO,EAAE,aAAa,GAAG,UAAU,CAgD5F;AAED,wBAAgB,cAAc,CAAC,CAAC,SAAS,KAAK,GAAG,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,aAAa,GAAG;IACjG,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;CAClB,CAoBA"}
|
package/dist/filter.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
function intersects(values, conditions) {
|
|
2
|
+
return conditions.some((condition) => values.includes(condition));
|
|
3
|
+
}
|
|
4
|
+
function compareDate(value, operator, from, to) {
|
|
5
|
+
if (operator === "onOrAfter") {
|
|
6
|
+
return value >= from;
|
|
7
|
+
}
|
|
8
|
+
if (operator === "onOrBefore") {
|
|
9
|
+
return value <= from;
|
|
10
|
+
}
|
|
11
|
+
if (!to) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return value >= from && value <= to;
|
|
15
|
+
}
|
|
16
|
+
function extractDate(entity, field) {
|
|
17
|
+
if (field === "completedDay") {
|
|
18
|
+
return "completedDay" in entity ? entity.completedDay : null;
|
|
19
|
+
}
|
|
20
|
+
if (field === "duration.scheduled.start") {
|
|
21
|
+
return "duration" in entity ? entity.duration.scheduled.start : null;
|
|
22
|
+
}
|
|
23
|
+
if (field === "duration.scheduled.end") {
|
|
24
|
+
return "duration" in entity ? entity.duration.scheduled.end : null;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
export function evaluateFilters(entity, filters) {
|
|
29
|
+
let checkedConditions = 0;
|
|
30
|
+
let matchedConditions = 0;
|
|
31
|
+
const reasons = [];
|
|
32
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
33
|
+
checkedConditions += 1;
|
|
34
|
+
if (intersects(entity.tags, filters.tags)) {
|
|
35
|
+
matchedConditions += 1;
|
|
36
|
+
reasons.push("tags");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (filters.owners && filters.owners.length > 0 && "owners" in entity) {
|
|
40
|
+
checkedConditions += 1;
|
|
41
|
+
if (intersects(entity.owners, filters.owners)) {
|
|
42
|
+
matchedConditions += 1;
|
|
43
|
+
reasons.push("owners");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (filters.testcaseStatus && filters.testcaseStatus.length > 0 && "status" in entity) {
|
|
47
|
+
checkedConditions += 1;
|
|
48
|
+
if (filters.testcaseStatus.includes(entity.status)) {
|
|
49
|
+
matchedConditions += 1;
|
|
50
|
+
reasons.push("testcaseStatus");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (filters.testStatus && filters.testStatus.length > 0 && "tests" in entity) {
|
|
54
|
+
checkedConditions += 1;
|
|
55
|
+
if (entity.tests.some((item) => filters.testStatus?.includes(item.status))) {
|
|
56
|
+
matchedConditions += 1;
|
|
57
|
+
reasons.push("testStatus");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (filters.date) {
|
|
61
|
+
checkedConditions += 1;
|
|
62
|
+
const value = extractDate(entity, filters.date.field);
|
|
63
|
+
if (value && compareDate(value, filters.date.operator, filters.date.from, filters.date.to)) {
|
|
64
|
+
matchedConditions += 1;
|
|
65
|
+
reasons.push("date");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const matched = checkedConditions === matchedConditions;
|
|
69
|
+
return { matched, checkedConditions, matchedConditions, reasons };
|
|
70
|
+
}
|
|
71
|
+
export function filterEntities(entities, filters) {
|
|
72
|
+
const evaluations = entities.map((entity) => ({
|
|
73
|
+
entity,
|
|
74
|
+
meta: evaluateFilters(entity, filters)
|
|
75
|
+
}));
|
|
76
|
+
const filtered = evaluations.filter(({ meta }) => meta.matched).map(({ entity }) => entity);
|
|
77
|
+
const checkedConditions = evaluations.reduce((sum, { meta }) => sum + meta.checkedConditions, 0);
|
|
78
|
+
const matchedConditions = evaluations.reduce((sum, { meta }) => sum + meta.matchedConditions, 0);
|
|
79
|
+
const reasons = Array.from(new Set(evaluations.flatMap(({ meta }) => meta.reasons)));
|
|
80
|
+
return {
|
|
81
|
+
items: filtered,
|
|
82
|
+
meta: {
|
|
83
|
+
matched: filtered.length > 0,
|
|
84
|
+
checkedConditions,
|
|
85
|
+
matchedConditions,
|
|
86
|
+
reasons
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter.js","sourceRoot":"","sources":["../src/filter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type TlogEntityType = "suite" | "case";
|
|
2
|
+
export interface TlogIndexedEntity {
|
|
3
|
+
id: string;
|
|
4
|
+
type: TlogEntityType;
|
|
5
|
+
path: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
related: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface DuplicateId {
|
|
10
|
+
id: string;
|
|
11
|
+
paths: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface IdIndex {
|
|
14
|
+
byId: Map<string, TlogIndexedEntity>;
|
|
15
|
+
entities: TlogIndexedEntity[];
|
|
16
|
+
duplicates: DuplicateId[];
|
|
17
|
+
}
|
|
18
|
+
export interface RelatedResolution {
|
|
19
|
+
resolved: TlogIndexedEntity[];
|
|
20
|
+
missing: string[];
|
|
21
|
+
}
|
|
22
|
+
export declare function buildIdIndex(rootDir: string): Promise<IdIndex>;
|
|
23
|
+
export declare function resolveById(index: IdIndex, id: string): TlogIndexedEntity | undefined;
|
|
24
|
+
export declare function resolveRelated(index: IdIndex, source: Pick<TlogIndexedEntity, "related">): RelatedResolution;
|
|
25
|
+
export declare function detectEntityType(path: string): TlogEntityType;
|
|
26
|
+
//# sourceMappingURL=id-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-index.d.ts","sourceRoot":"","sources":["../src/id-index.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,MAAM,CAAC;AAE9C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IACrC,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,UAAU,EAAE,WAAW,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AA8DD,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA2BpE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAErF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,CAAC,GAAG,iBAAiB,CAc5G;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAW7D"}
|
package/dist/id-index.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { readYamlFile } from "./yaml-io.js";
|
|
4
|
+
async function walkYamlFiles(rootDir) {
|
|
5
|
+
const queue = [rootDir];
|
|
6
|
+
const result = [];
|
|
7
|
+
while (queue.length > 0) {
|
|
8
|
+
const current = queue.shift();
|
|
9
|
+
if (!current) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
const fullPath = join(current, entry.name);
|
|
15
|
+
if (entry.isDirectory()) {
|
|
16
|
+
queue.push(fullPath);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (!entry.isFile()) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (entry.name.endsWith(".yaml")) {
|
|
23
|
+
result.push({ path: fullPath, type: detectEntityType(fullPath) });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
function parseEntity(raw, path, type) {
|
|
30
|
+
if (!raw || typeof raw !== "object") {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const data = raw;
|
|
34
|
+
if (typeof data.id !== "string" || data.id.length === 0) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const title = typeof data.title === "string" ? data.title : undefined;
|
|
38
|
+
const related = Array.isArray(data.related)
|
|
39
|
+
? data.related.filter((item) => typeof item === "string")
|
|
40
|
+
: [];
|
|
41
|
+
return {
|
|
42
|
+
id: data.id,
|
|
43
|
+
type,
|
|
44
|
+
path,
|
|
45
|
+
title,
|
|
46
|
+
related
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export async function buildIdIndex(rootDir) {
|
|
50
|
+
const candidates = await walkYamlFiles(rootDir);
|
|
51
|
+
const byId = new Map();
|
|
52
|
+
const entities = [];
|
|
53
|
+
const duplicatePaths = new Map();
|
|
54
|
+
for (const candidate of candidates) {
|
|
55
|
+
const parsed = parseEntity(await readYamlFile(candidate.path), candidate.path, candidate.type);
|
|
56
|
+
if (!parsed) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
entities.push(parsed);
|
|
60
|
+
const exists = byId.get(parsed.id);
|
|
61
|
+
if (!exists) {
|
|
62
|
+
byId.set(parsed.id, parsed);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const paths = duplicatePaths.get(parsed.id) ?? [exists.path];
|
|
66
|
+
paths.push(parsed.path);
|
|
67
|
+
duplicatePaths.set(parsed.id, paths);
|
|
68
|
+
}
|
|
69
|
+
const duplicates = Array.from(duplicatePaths.entries()).map(([id, paths]) => ({ id, paths }));
|
|
70
|
+
return { byId, entities, duplicates };
|
|
71
|
+
}
|
|
72
|
+
export function resolveById(index, id) {
|
|
73
|
+
return index.byId.get(id);
|
|
74
|
+
}
|
|
75
|
+
export function resolveRelated(index, source) {
|
|
76
|
+
const resolved = [];
|
|
77
|
+
const missing = [];
|
|
78
|
+
for (const id of source.related) {
|
|
79
|
+
const entity = index.byId.get(id);
|
|
80
|
+
if (entity) {
|
|
81
|
+
resolved.push(entity);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
missing.push(id);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { resolved, missing };
|
|
88
|
+
}
|
|
89
|
+
export function detectEntityType(path) {
|
|
90
|
+
const name = basename(path);
|
|
91
|
+
if (name === "index.yaml" || name.endsWith(".suite.yaml")) {
|
|
92
|
+
return "suite";
|
|
93
|
+
}
|
|
94
|
+
if (name.endsWith(".testcase.yaml")) {
|
|
95
|
+
return "case";
|
|
96
|
+
}
|
|
97
|
+
return "case";
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=id-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-index.js","sourceRoot":"","sources":["../src/id-index.ts"],"names":[],"mappings":"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"}
|