@allurereport/core-api 3.3.1 → 3.4.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.
@@ -38,6 +38,7 @@ export type CategoryGroupCustomSelector = {
38
38
  };
39
39
  export type CategoryGroupSelector = CategoryGroupBuiltInSelector | CategoryGroupCustomSelector;
40
40
  export type CategoryRule = {
41
+ id?: string;
41
42
  name: string;
42
43
  matchers?: CategoryMatcher;
43
44
  groupBy?: readonly CategoryGroupSelector[];
@@ -55,6 +56,7 @@ export type CategoriesStore = {
55
56
  nodes: Record<string, CategoryNode>;
56
57
  };
57
58
  export interface CategoryDefinition extends Pick<CategoryRule, "name" | "expand" | "hide" | "groupEnvironments"> {
59
+ id: string;
58
60
  matchers: Matcher[];
59
61
  groupBy: CategoryGroupSelector[];
60
62
  groupByMessage: boolean;
@@ -32,6 +32,28 @@ export const DEFAULT_ERROR_CATEGORIES = [
32
32
  const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
33
33
  const toRegExp = (v) => (v instanceof RegExp ? v : new RegExp(v));
34
34
  const isMatcherArray = (value) => Array.isArray(value);
35
+ const hasControlChars = (value) => {
36
+ for (let index = 0; index < value.length; index++) {
37
+ const code = value.charCodeAt(index);
38
+ if (code <= 0x1f || (code >= 0x7f && code <= 0x9f)) {
39
+ return true;
40
+ }
41
+ }
42
+ return false;
43
+ };
44
+ const normalizeCategoryId = (id) => {
45
+ if (typeof id !== "string") {
46
+ return { valid: false, reason: "id must be a string" };
47
+ }
48
+ const normalized = id.trim();
49
+ if (normalized.length === 0) {
50
+ return { valid: false, reason: "id must not be empty" };
51
+ }
52
+ if (hasControlChars(normalized)) {
53
+ return { valid: false, reason: "id must not contain control characters" };
54
+ }
55
+ return { valid: true, normalized };
56
+ };
35
57
  const normalizeMatchers = (rule, index) => {
36
58
  const compatKeysUsed = rule.matchedStatuses !== undefined ||
37
59
  rule.messageRegex !== undefined ||
@@ -85,6 +107,7 @@ export const normalizeCategoriesConfig = (cfg) => {
85
107
  const rules = rawRules.length ? rawRules : [];
86
108
  const normalized = [];
87
109
  const seen = new Map();
110
+ const sourceIdsByNormalizedId = new Map();
88
111
  const applyRule = (rule, index) => {
89
112
  if (!isPlainObject(rule)) {
90
113
  throw new Error(`categories[${index}] must be an object`);
@@ -92,8 +115,16 @@ export const normalizeCategoriesConfig = (cfg) => {
92
115
  if (typeof rule.name !== "string" || !rule.name.trim()) {
93
116
  throw new Error(`categories[${index}].name must be non-empty string`);
94
117
  }
118
+ const idValidationResult = normalizeCategoryId(rule.id ?? rule.name);
119
+ if (!idValidationResult.valid) {
120
+ throw new Error(`categories[${index}].id ${idValidationResult.reason}`);
121
+ }
122
+ const normalizedId = idValidationResult.normalized;
123
+ const sourceIds = sourceIdsByNormalizedId.get(normalizedId) ?? new Set();
124
+ sourceIds.add(rule.id ?? rule.name);
125
+ sourceIdsByNormalizedId.set(normalizedId, sourceIds);
95
126
  const matchers = normalizeMatchers(rule, index);
96
- const existing = seen.get(rule.name);
127
+ const existing = seen.get(normalizedId);
97
128
  if (existing) {
98
129
  existing.matchers.push(...matchers);
99
130
  return;
@@ -118,6 +149,7 @@ export const normalizeCategoriesConfig = (cfg) => {
118
149
  }
119
150
  }
120
151
  const norm = {
152
+ id: normalizedId,
121
153
  name: rule.name,
122
154
  matchers,
123
155
  groupBy,
@@ -127,11 +159,19 @@ export const normalizeCategoriesConfig = (cfg) => {
127
159
  hide: rule.hide ?? false,
128
160
  index,
129
161
  };
130
- seen.set(rule.name, norm);
162
+ seen.set(normalizedId, norm);
131
163
  normalized.push(norm);
132
164
  };
133
165
  rules.forEach(applyRule);
134
166
  DEFAULT_ERROR_CATEGORIES.forEach((rule, index) => applyRule(rule, rules.length + index));
167
+ sourceIdsByNormalizedId.forEach((sourceIds, normalizedId) => {
168
+ if (sourceIds.size <= 1) {
169
+ return;
170
+ }
171
+ throw new Error(`categories: normalized id ${JSON.stringify(normalizedId)} is produced by source ids [${Array.from(sourceIds)
172
+ .map((id) => JSON.stringify(id))
173
+ .join(",")}]`);
174
+ });
135
175
  return normalized;
136
176
  };
137
177
  const matchObjectMatcher = (m, d) => {
@@ -3,6 +3,7 @@ import type { SeverityLevel, TestStatus } from "./model.js";
3
3
  export declare const statusesList: readonly TestStatus[];
4
4
  export declare const severityLevels: readonly SeverityLevel[];
5
5
  export declare const severityLabelName = "severity";
6
+ export declare const fallbackTestCaseIdLabelName = "_fallbackTestCaseId";
6
7
  export declare const unsuccessfulStatuses: Set<TestStatus>;
7
8
  export declare const successfulStatuses: Set<TestStatus>;
8
9
  export declare const includedInSuccessRate: Set<TestStatus>;
package/dist/constants.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export const statusesList = ["failed", "broken", "passed", "skipped", "unknown"];
2
2
  export const severityLevels = ["blocker", "critical", "normal", "minor", "trivial"];
3
3
  export const severityLabelName = "severity";
4
+ export const fallbackTestCaseIdLabelName = "_fallbackTestCaseId";
4
5
  export const unsuccessfulStatuses = new Set(["failed", "broken"]);
5
6
  export const successfulStatuses = new Set(["passed"]);
6
7
  export const includedInSuccessRate = new Set([...unsuccessfulStatuses, ...successfulStatuses]);
@@ -3,11 +3,16 @@ export interface EnvironmentItem {
3
3
  name: string;
4
4
  values: string[];
5
5
  }
6
+ export interface EnvironmentIdentity {
7
+ id: string;
8
+ name: string;
9
+ }
6
10
  export type ReportVariables = Record<string, string>;
7
11
  export type EnvironmentMatcherPayload = {
8
12
  labels: TestLabel[];
9
13
  };
10
14
  export type EnvironmentDescriptor = {
15
+ name?: string;
11
16
  variables?: ReportVariables;
12
17
  matcher: (payload: EnvironmentMatcherPayload) => boolean;
13
18
  };
package/dist/index.d.ts CHANGED
@@ -23,3 +23,5 @@ export * from "./utils/environment.js";
23
23
  export * from "./utils/history.js";
24
24
  export * from "./utils/strings.js";
25
25
  export * from "./utils/dictionary.js";
26
+ export * from "./utils/path.js";
27
+ export * from "./utils/url.js";
package/dist/index.js CHANGED
@@ -13,3 +13,5 @@ export * from "./utils/environment.js";
13
13
  export * from "./utils/history.js";
14
14
  export * from "./utils/strings.js";
15
15
  export * from "./utils/dictionary.js";
16
+ export * from "./utils/path.js";
17
+ export * from "./utils/url.js";
package/dist/static.d.ts CHANGED
@@ -6,6 +6,7 @@ export declare const createStylesLinkTag: (src: string) => string;
6
6
  export declare const createFontLinkTag: (src: string) => string;
7
7
  export declare const createFaviconLinkTag: (src: string) => string;
8
8
  export declare const createBaseUrlScript: () => string;
9
+ export declare const stringifyForInlineScript: (value: unknown) => string;
9
10
  export declare const createReportDataScript: (reportFiles?: {
10
11
  name: string;
11
12
  value: string;
package/dist/static.js CHANGED
@@ -23,6 +23,14 @@ export const createBaseUrlScript = () => {
23
23
  </script>
24
24
  `;
25
25
  };
26
+ export const stringifyForInlineScript = (value) => {
27
+ return JSON.stringify(value)
28
+ .replaceAll("<", "\\u003C")
29
+ .replaceAll(">", "\\u003E")
30
+ .replaceAll("&", "\\u0026")
31
+ .replaceAll("\u2028", "\\u2028")
32
+ .replaceAll("\u2029", "\\u2029");
33
+ };
26
34
  export const createReportDataScript = (reportFiles = []) => {
27
35
  if (!reportFiles?.length) {
28
36
  return `
@@ -31,7 +39,9 @@ export const createReportDataScript = (reportFiles = []) => {
31
39
  </script>
32
40
  `;
33
41
  }
34
- const reportFilesDeclaration = reportFiles.map(({ name, value }) => `d('${name}','${value}')`).join(",");
42
+ const reportFilesDeclaration = reportFiles
43
+ .map(({ name, value }) => `d(${JSON.stringify(name)},${JSON.stringify(value)})`)
44
+ .join(",");
35
45
  return `
36
46
  <script async>
37
47
  window.allureReportDataReady = false;
@@ -1,5 +1,18 @@
1
- import type { EnvironmentsConfig } from "../environment.js";
2
- import type { TestEnvGroup, TestResult } from "../model.js";
1
+ import type { EnvironmentIdentity } from "../environment.js";
2
+ import type { TestEnvGroup } from "../model.js";
3
3
  export declare const DEFAULT_ENVIRONMENT = "default";
4
- export declare const matchEnvironment: (envConfig: EnvironmentsConfig, tr: Pick<TestResult, "labels">) => string;
4
+ export declare const MAX_ENVIRONMENT_NAME_LENGTH = 64;
5
+ export declare const MAX_ENVIRONMENT_ID_LENGTH = 64;
6
+ export declare const DEFAULT_ENVIRONMENT_IDENTITY: EnvironmentIdentity;
7
+ export type EnvironmentValidationResult = {
8
+ valid: true;
9
+ normalized: string;
10
+ } | {
11
+ valid: false;
12
+ reason: string;
13
+ };
14
+ export declare const validateEnvironmentName: (name: unknown) => EnvironmentValidationResult;
15
+ export declare const validateEnvironmentId: (environmentId: unknown) => EnvironmentValidationResult;
16
+ export declare const assertValidEnvironmentName: (name: unknown, source?: string) => string;
17
+ export declare const formatNormalizedEnvironmentCollision: (sourcePath: string, normalized: string, originalKeys: string[]) => string;
5
18
  export declare const getRealEnvsCount: (group: TestEnvGroup) => number;
@@ -1,10 +1,80 @@
1
1
  export const DEFAULT_ENVIRONMENT = "default";
2
- export const matchEnvironment = (envConfig, tr) => {
3
- return (Object.entries(envConfig).find(([, { matcher }]) => matcher({ labels: tr.labels }))?.[0] ?? DEFAULT_ENVIRONMENT);
2
+ export const MAX_ENVIRONMENT_NAME_LENGTH = 64;
3
+ export const MAX_ENVIRONMENT_ID_LENGTH = 64;
4
+ export const DEFAULT_ENVIRONMENT_IDENTITY = {
5
+ id: DEFAULT_ENVIRONMENT,
6
+ name: DEFAULT_ENVIRONMENT,
4
7
  };
8
+ const hasControlChars = (value) => {
9
+ for (let i = 0; i < value.length; i++) {
10
+ const code = value.charCodeAt(i);
11
+ if (code <= 0x1f || (code >= 0x7f && code <= 0x9f)) {
12
+ return true;
13
+ }
14
+ }
15
+ return false;
16
+ };
17
+ const hasPathLikeSegments = (value) => {
18
+ if (value.includes("/") || value.includes("\\")) {
19
+ return true;
20
+ }
21
+ return value === "." || value === "..";
22
+ };
23
+ export const validateEnvironmentName = (name) => {
24
+ if (typeof name !== "string") {
25
+ return { valid: false, reason: "name must be a string" };
26
+ }
27
+ const normalized = name.trim();
28
+ if (normalized.length === 0) {
29
+ return { valid: false, reason: "name must not be empty" };
30
+ }
31
+ if (normalized.length > MAX_ENVIRONMENT_NAME_LENGTH) {
32
+ return {
33
+ valid: false,
34
+ reason: `name must not exceed ${MAX_ENVIRONMENT_NAME_LENGTH} characters`,
35
+ };
36
+ }
37
+ if (hasControlChars(normalized)) {
38
+ return { valid: false, reason: "name must not contain control characters" };
39
+ }
40
+ if (hasPathLikeSegments(normalized)) {
41
+ return { valid: false, reason: "name must not contain path-like segments" };
42
+ }
43
+ return { valid: true, normalized };
44
+ };
45
+ export const validateEnvironmentId = (environmentId) => {
46
+ if (typeof environmentId !== "string") {
47
+ return { valid: false, reason: "id must be a string" };
48
+ }
49
+ const normalized = environmentId.trim();
50
+ if (normalized.length === 0) {
51
+ return { valid: false, reason: "id must not be empty" };
52
+ }
53
+ if (normalized.length > MAX_ENVIRONMENT_ID_LENGTH) {
54
+ return {
55
+ valid: false,
56
+ reason: `id must not exceed ${MAX_ENVIRONMENT_ID_LENGTH} characters`,
57
+ };
58
+ }
59
+ if (!/^[A-Za-z0-9_-]+$/.test(normalized)) {
60
+ return {
61
+ valid: false,
62
+ reason: "id must contain only latin letters, digits, underscores, and hyphens",
63
+ };
64
+ }
65
+ return { valid: true, normalized };
66
+ };
67
+ export const assertValidEnvironmentName = (name, source = "environment name") => {
68
+ const validationResult = validateEnvironmentName(name);
69
+ if (!validationResult.valid) {
70
+ throw new Error(`Invalid ${source} ${JSON.stringify(name)}: ${validationResult.reason}`);
71
+ }
72
+ return validationResult.normalized;
73
+ };
74
+ export const formatNormalizedEnvironmentCollision = (sourcePath, normalized, originalKeys) => `${sourcePath}: normalized key ${JSON.stringify(normalized)} is produced by original keys [${originalKeys.map((key) => JSON.stringify(key)).join(",")}]`;
5
75
  export const getRealEnvsCount = (group) => {
6
76
  const { testResultsByEnv = {} } = group ?? {};
7
- const envsCount = Object.keys(testResultsByEnv).length ?? 0;
77
+ const envsCount = Object.keys(testResultsByEnv).length;
8
78
  if (envsCount <= 1 && DEFAULT_ENVIRONMENT in testResultsByEnv) {
9
79
  return 0;
10
80
  }
@@ -1,3 +1,10 @@
1
1
  import type { HistoryDataPoint, HistoryTestResult } from "../history.js";
2
+ import type { TestParameter } from "../metadata.js";
2
3
  import type { TestResult } from "../model.js";
4
+ export declare const stringifyHistoryParams: (parameters?: TestParameter[]) => string;
5
+ export declare const getFallbackHistoryId: (tr: Pick<TestResult, "labels" | "parameters">) => string | undefined;
6
+ export declare const getHistoryIdCandidates: (tr: Pick<TestResult, "historyId" | "labels" | "parameters">) => string[];
7
+ export declare const filterUnknownByKnownIssues: (trs: TestResult[], knownIssueHistoryIds: ReadonlySet<string>) => TestResult[];
8
+ export declare const normalizeHistoryDataPointUrls: (historyDataPoint: HistoryDataPoint) => HistoryDataPoint;
9
+ export declare const selectHistoryTestResults: (historyDataPoints: HistoryDataPoint[], historyIdCandidates: readonly string[]) => HistoryTestResult[];
3
10
  export declare const htrsByTr: (hdps: HistoryDataPoint[], tr: TestResult | HistoryTestResult) => HistoryTestResult[];
@@ -1,22 +1,89 @@
1
- export const htrsByTr = (hdps, tr) => {
2
- if (!tr?.historyId) {
1
+ import { createHash } from "node:crypto";
2
+ import { fallbackTestCaseIdLabelName } from "../constants.js";
3
+ import { findLastByLabelName } from "./label.js";
4
+ const md5 = (data) => createHash("md5").update(data).digest("hex");
5
+ const parametersCompare = (a, b) => {
6
+ return (a.name ?? "").localeCompare(b.name ?? "") || (a.value ?? "").localeCompare(b.value ?? "");
7
+ };
8
+ export const stringifyHistoryParams = (parameters = []) => {
9
+ return [...parameters]
10
+ .filter((parameter) => !parameter?.excluded)
11
+ .sort(parametersCompare)
12
+ .map((parameter) => `${parameter.name}:${parameter.value}`)
13
+ .join(",");
14
+ };
15
+ export const getFallbackHistoryId = (tr) => {
16
+ const fallbackTestCaseId = findLastByLabelName(tr.labels ?? [], fallbackTestCaseIdLabelName);
17
+ if (!fallbackTestCaseId) {
18
+ return undefined;
19
+ }
20
+ return `${fallbackTestCaseId}.${md5(stringifyHistoryParams(tr.parameters ?? []))}`;
21
+ };
22
+ export const getHistoryIdCandidates = (tr) => {
23
+ const result = [];
24
+ if (tr.historyId) {
25
+ result.push(tr.historyId);
26
+ }
27
+ const fallbackHistoryId = getFallbackHistoryId(tr);
28
+ if (fallbackHistoryId && !result.includes(fallbackHistoryId)) {
29
+ result.push(fallbackHistoryId);
30
+ }
31
+ return result;
32
+ };
33
+ export const filterUnknownByKnownIssues = (trs, knownIssueHistoryIds) => {
34
+ return trs.filter((tr) => {
35
+ const historyIdCandidates = getHistoryIdCandidates(tr);
36
+ if (historyIdCandidates.length === 0) {
37
+ return true;
38
+ }
39
+ return historyIdCandidates.every((historyId) => !knownIssueHistoryIds.has(historyId));
40
+ });
41
+ };
42
+ export const normalizeHistoryDataPointUrls = (historyDataPoint) => {
43
+ const { url } = historyDataPoint;
44
+ if (!url) {
45
+ return historyDataPoint;
46
+ }
47
+ let testResults = historyDataPoint.testResults;
48
+ for (const [historyId, historyTestResult] of Object.entries(historyDataPoint.testResults)) {
49
+ if (historyTestResult.url) {
50
+ continue;
51
+ }
52
+ if (testResults === historyDataPoint.testResults) {
53
+ testResults = { ...historyDataPoint.testResults };
54
+ }
55
+ testResults[historyId] = {
56
+ ...historyTestResult,
57
+ url,
58
+ };
59
+ }
60
+ if (testResults === historyDataPoint.testResults) {
61
+ return historyDataPoint;
62
+ }
63
+ return {
64
+ ...historyDataPoint,
65
+ testResults,
66
+ };
67
+ };
68
+ export const selectHistoryTestResults = (historyDataPoints, historyIdCandidates) => {
69
+ if (historyIdCandidates.length === 0) {
3
70
  return [];
4
71
  }
5
- return hdps.reduce((acc, dp) => {
6
- const htr = dp.testResults[tr.historyId];
7
- if (htr) {
8
- if (dp.url) {
9
- const url = new URL(dp.url);
10
- url.hash = tr.id;
11
- acc.push({
12
- ...htr,
13
- url: url.toString(),
14
- });
15
- }
16
- else {
17
- acc.push(htr);
72
+ return historyDataPoints.reduce((acc, historyDataPoint) => {
73
+ for (const historyId of historyIdCandidates) {
74
+ const historyTestResult = historyDataPoint.testResults[historyId];
75
+ if (!historyTestResult) {
76
+ continue;
18
77
  }
78
+ acc.push(historyTestResult);
79
+ break;
19
80
  }
20
81
  return acc;
21
82
  }, []);
22
83
  };
84
+ export const htrsByTr = (hdps, tr) => {
85
+ if (!tr?.historyId) {
86
+ return [];
87
+ }
88
+ return selectHistoryTestResults(hdps, [tr.historyId]);
89
+ };
@@ -1,3 +1,4 @@
1
1
  import type { TestLabel } from "../index.js";
2
2
  export declare const findByLabelName: (labels: TestLabel[], name: string) => string | undefined;
3
3
  export declare const findLastByLabelName: (labels: TestLabel[], name: string) => string | undefined;
4
+ export declare const shouldHideLabel: (labelName: string, matchers?: readonly (string | RegExp)[]) => boolean;
@@ -9,3 +9,14 @@ export const findLastByLabelName = (labels, name) => {
9
9
  }
10
10
  return undefined;
11
11
  };
12
+ export const shouldHideLabel = (labelName, matchers = []) => {
13
+ if (labelName.startsWith("_")) {
14
+ return true;
15
+ }
16
+ return matchers.some((matcher) => {
17
+ if (typeof matcher === "string") {
18
+ return matcher === labelName;
19
+ }
20
+ return new RegExp(matcher.source, matcher.flags).test(labelName);
21
+ });
22
+ };
@@ -0,0 +1,2 @@
1
+ export declare const toPosixPath: (path: string) => string;
2
+ export declare const joinPosixPath: (...parts: string[]) => string;
@@ -0,0 +1,11 @@
1
+ export const toPosixPath = (path) => path.replace(/\\/g, "/");
2
+ export const joinPosixPath = (...parts) => {
3
+ const segments = parts.map(toPosixPath).join("/").split("/");
4
+ const nonEmptySegments = [];
5
+ for (const segment of segments) {
6
+ if (segment.length > 0) {
7
+ nonEmptySegments.push(segment);
8
+ }
9
+ }
10
+ return nonEmptySegments.join("/");
11
+ };
@@ -0,0 +1 @@
1
+ export declare const sanitizeExternalUrl: (value: unknown) => string | undefined;
@@ -0,0 +1,24 @@
1
+ const ALLOWED_EXTERNAL_URL_PROTOCOLS = new Set(["http:", "https:", "mailto:", "tel:"]);
2
+ export const sanitizeExternalUrl = (value) => {
3
+ if (typeof value !== "string") {
4
+ return undefined;
5
+ }
6
+ const normalized = value.trim();
7
+ if (normalized.length === 0) {
8
+ return undefined;
9
+ }
10
+ const schemeMatch = normalized.match(/^([A-Za-z][A-Za-z0-9+.-]*):/);
11
+ if (!schemeMatch) {
12
+ return undefined;
13
+ }
14
+ const protocol = `${schemeMatch[1].toLowerCase()}:`;
15
+ if (!ALLOWED_EXTERNAL_URL_PROTOCOLS.has(protocol)) {
16
+ return undefined;
17
+ }
18
+ try {
19
+ return new URL(normalized).toString();
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ };
package/package.json CHANGED
@@ -1,50 +1,39 @@
1
1
  {
2
2
  "name": "@allurereport/core-api",
3
- "version": "3.3.1",
3
+ "version": "3.4.1",
4
4
  "description": "Allure Core API",
5
5
  "keywords": [
6
6
  "allure"
7
7
  ],
8
- "repository": "https://github.com/allure-framework/allure3",
9
8
  "license": "Apache-2.0",
10
9
  "author": "Qameta Software",
10
+ "repository": "https://github.com/allure-framework/allure3",
11
+ "files": [
12
+ "dist"
13
+ ],
11
14
  "type": "module",
12
- "exports": {
13
- ".": "./dist/index.js"
14
- },
15
15
  "main": "./dist/index.js",
16
16
  "module": "./dist/index.js",
17
17
  "types": "./dist/index.d.ts",
18
- "files": [
19
- "dist"
20
- ],
18
+ "exports": {
19
+ ".": "./dist/index.js"
20
+ },
21
21
  "scripts": {
22
22
  "build": "run clean && tsc --project ./tsconfig.json",
23
23
  "clean": "rimraf ./dist",
24
- "eslint": "eslint ./src/**/*.{js,jsx,ts,tsx}",
25
- "eslint:format": "eslint --fix ./src/**/*.{js,jsx,ts,tsx}",
26
- "test": "rimraf ./out && vitest run"
24
+ "test": "rimraf ./out && vitest run",
25
+ "lint": "oxlint --import-plugin src test features stories",
26
+ "lint:fix": "oxlint --import-plugin --fix src test features stories"
27
27
  },
28
28
  "dependencies": {
29
29
  "d3-shape": "^3.2.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@stylistic/eslint-plugin": "^2.6.1",
33
32
  "@types/d3-shape": "^3.1.6",
34
- "@types/eslint": "^8.56.11",
35
33
  "@types/node": "^20.17.9",
36
- "@typescript-eslint/eslint-plugin": "^8.0.0",
37
- "@typescript-eslint/parser": "^8.0.0",
38
34
  "@vitest/runner": "^2.1.9",
39
35
  "@vitest/snapshot": "^2.1.9",
40
36
  "allure-vitest": "^3.3.3",
41
- "eslint": "^8.57.0",
42
- "eslint-config-prettier": "^9.1.0",
43
- "eslint-plugin-import": "^2.29.1",
44
- "eslint-plugin-jsdoc": "^50.0.0",
45
- "eslint-plugin-n": "^17.10.1",
46
- "eslint-plugin-no-null": "^1.0.2",
47
- "eslint-plugin-prefer-arrow": "^1.2.3",
48
37
  "rimraf": "^6.0.1",
49
38
  "typescript": "^5.6.3",
50
39
  "vitest": "^2.1.9"