@testrelic/core 1.0.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/dist/index.cjs +166 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +154 -0
- package/dist/index.d.ts +154 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/package.json +46 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ErrorCode: () => ErrorCode,
|
|
24
|
+
TestRelicError: () => TestRelicError,
|
|
25
|
+
createError: () => createError,
|
|
26
|
+
createLogger: () => createLogger,
|
|
27
|
+
isValidConfig: () => isValidConfig,
|
|
28
|
+
isValidNavigationType: () => isValidNavigationType,
|
|
29
|
+
isValidTestRunReport: () => isValidTestRunReport
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
|
|
33
|
+
// src/logger.ts
|
|
34
|
+
var noopHandler = () => {
|
|
35
|
+
};
|
|
36
|
+
function createLogger(options) {
|
|
37
|
+
const handler = options?.handler ?? noopHandler;
|
|
38
|
+
return {
|
|
39
|
+
info(message, ...args) {
|
|
40
|
+
handler("info", message, args);
|
|
41
|
+
},
|
|
42
|
+
warn(message, ...args) {
|
|
43
|
+
handler("warn", message, args);
|
|
44
|
+
},
|
|
45
|
+
error(message, ...args) {
|
|
46
|
+
handler("error", message, args);
|
|
47
|
+
},
|
|
48
|
+
debug(message, ...args) {
|
|
49
|
+
handler("debug", message, args);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/errors.ts
|
|
55
|
+
var ErrorCode = {
|
|
56
|
+
CONFIG_INVALID: "TESTRELIC_CONFIG_INVALID",
|
|
57
|
+
OUTPUT_WRITE_FAILED: "TESTRELIC_OUTPUT_WRITE_FAILED",
|
|
58
|
+
MERGE_INVALID_SCHEMA: "TESTRELIC_MERGE_INVALID_SCHEMA",
|
|
59
|
+
MERGE_READ_FAILED: "TESTRELIC_MERGE_READ_FAILED",
|
|
60
|
+
NAVIGATION_FLUSH_FAILED: "TESTRELIC_NAVIGATION_FLUSH_FAILED",
|
|
61
|
+
CODE_EXTRACT_FAILED: "TESTRELIC_CODE_EXTRACT_FAILED",
|
|
62
|
+
HTML_REPORT_FAILED: "TESTRELIC_HTML_REPORT_FAILED"
|
|
63
|
+
};
|
|
64
|
+
var TestRelicError = class extends Error {
|
|
65
|
+
constructor(code, message, cause) {
|
|
66
|
+
super(message);
|
|
67
|
+
this.name = "TestRelicError";
|
|
68
|
+
this.code = code;
|
|
69
|
+
if (cause !== void 0) {
|
|
70
|
+
Object.defineProperty(this, "cause", { value: cause, writable: false, enumerable: false });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
function createError(code, message, cause) {
|
|
75
|
+
return new TestRelicError(code, message, cause);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/validation.ts
|
|
79
|
+
var NAVIGATION_TYPES = /* @__PURE__ */ new Set([
|
|
80
|
+
"goto",
|
|
81
|
+
"navigation",
|
|
82
|
+
"back",
|
|
83
|
+
"forward",
|
|
84
|
+
"refresh",
|
|
85
|
+
"spa_route",
|
|
86
|
+
"spa_replace",
|
|
87
|
+
"hash_change",
|
|
88
|
+
"link_click",
|
|
89
|
+
"form_submit",
|
|
90
|
+
"redirect",
|
|
91
|
+
"popstate",
|
|
92
|
+
"page_load",
|
|
93
|
+
"manual_record",
|
|
94
|
+
"fallback",
|
|
95
|
+
"dummy"
|
|
96
|
+
]);
|
|
97
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
98
|
+
function isValidNavigationType(value) {
|
|
99
|
+
return typeof value === "string" && NAVIGATION_TYPES.has(value);
|
|
100
|
+
}
|
|
101
|
+
function hasNoPrototypePollution(obj) {
|
|
102
|
+
if (typeof obj !== "object" || obj === null) return true;
|
|
103
|
+
for (const key of Object.keys(obj)) {
|
|
104
|
+
if (DANGEROUS_KEYS.has(key)) return false;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
function isValidConfig(input) {
|
|
109
|
+
if (typeof input !== "object" || input === null) return false;
|
|
110
|
+
if (!hasNoPrototypePollution(input)) return false;
|
|
111
|
+
const obj = input;
|
|
112
|
+
if (obj.outputPath !== void 0 && typeof obj.outputPath !== "string") return false;
|
|
113
|
+
if (obj.includeStackTrace !== void 0 && typeof obj.includeStackTrace !== "boolean") return false;
|
|
114
|
+
if (obj.includeCodeSnippets !== void 0 && typeof obj.includeCodeSnippets !== "boolean") return false;
|
|
115
|
+
if (obj.codeContextLines !== void 0 && typeof obj.codeContextLines !== "number") return false;
|
|
116
|
+
if (obj.includeNetworkStats !== void 0 && typeof obj.includeNetworkStats !== "boolean") return false;
|
|
117
|
+
if (obj.navigationTypes !== void 0 && obj.navigationTypes !== null) {
|
|
118
|
+
if (!Array.isArray(obj.navigationTypes)) return false;
|
|
119
|
+
for (const t of obj.navigationTypes) {
|
|
120
|
+
if (!isValidNavigationType(t)) return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (obj.redactPatterns !== void 0) {
|
|
124
|
+
if (!Array.isArray(obj.redactPatterns)) return false;
|
|
125
|
+
for (const p of obj.redactPatterns) {
|
|
126
|
+
if (typeof p !== "string" && !(p instanceof RegExp)) return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (obj.testRunId !== void 0 && obj.testRunId !== null && typeof obj.testRunId !== "string") return false;
|
|
130
|
+
if (obj.metadata !== void 0 && obj.metadata !== null) {
|
|
131
|
+
if (typeof obj.metadata !== "object") return false;
|
|
132
|
+
if (!hasNoPrototypePollution(obj.metadata)) return false;
|
|
133
|
+
}
|
|
134
|
+
if (obj.openReport !== void 0 && typeof obj.openReport !== "boolean") return false;
|
|
135
|
+
if (obj.htmlReportPath !== void 0 && (typeof obj.htmlReportPath !== "string" || obj.htmlReportPath === "")) return false;
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
function isValidTestRunReport(value) {
|
|
139
|
+
if (typeof value !== "object" || value === null) return false;
|
|
140
|
+
const obj = value;
|
|
141
|
+
if (typeof obj.schemaVersion !== "string") return false;
|
|
142
|
+
if (typeof obj.testRunId !== "string") return false;
|
|
143
|
+
if (typeof obj.startedAt !== "string") return false;
|
|
144
|
+
if (typeof obj.completedAt !== "string") return false;
|
|
145
|
+
if (typeof obj.totalDuration !== "number") return false;
|
|
146
|
+
if (typeof obj.summary !== "object" || obj.summary === null) return false;
|
|
147
|
+
if (!Array.isArray(obj.timeline)) return false;
|
|
148
|
+
const summary = obj.summary;
|
|
149
|
+
if (typeof summary.total !== "number") return false;
|
|
150
|
+
if (typeof summary.passed !== "number") return false;
|
|
151
|
+
if (typeof summary.failed !== "number") return false;
|
|
152
|
+
if (typeof summary.flaky !== "number") return false;
|
|
153
|
+
if (typeof summary.skipped !== "number") return false;
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
157
|
+
0 && (module.exports = {
|
|
158
|
+
ErrorCode,
|
|
159
|
+
TestRelicError,
|
|
160
|
+
createError,
|
|
161
|
+
createLogger,
|
|
162
|
+
isValidConfig,
|
|
163
|
+
isValidNavigationType,
|
|
164
|
+
isValidTestRunReport
|
|
165
|
+
});
|
|
166
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/errors.ts","../src/validation.ts"],"sourcesContent":["// @testrelic/core — barrel export\n// Re-exports only, no implementation (Constitution II)\n\nexport type {\n NavigationType,\n TestRunReport,\n Summary,\n CIMetadata,\n CIProvider,\n TimelineEntry,\n TestResult,\n TestStatus,\n FailureDiagnostic,\n NetworkStats,\n ResourceBreakdown,\n ReporterConfig,\n NavigationAnnotation,\n MergeOptions,\n} from './types.js';\n\nexport type { Logger, LoggerOptions, LogLevel } from './logger.js';\nexport { createLogger } from './logger.js';\n\nexport { ErrorCode, TestRelicError, createError } from './errors.js';\n\nexport {\n isValidConfig,\n isValidNavigationType,\n isValidTestRunReport,\n} from './validation.js';\n","/**\n * @testrelic/core — Configurable logger\n *\n * Provides a Logger interface with a no-op default.\n * Never uses console.* directly (Constitution SDK Constraint).\n */\n\nexport interface Logger {\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n}\n\nexport interface LoggerOptions {\n readonly handler?: (level: LogLevel, message: string, args: unknown[]) => void;\n}\n\nexport type LogLevel = 'info' | 'warn' | 'error' | 'debug';\n\ntype LogHandler = (level: LogLevel, message: string, args: unknown[]) => void;\n\nconst noopHandler: LogHandler = () => {};\n\nexport function createLogger(options?: LoggerOptions): Logger {\n const handler = options?.handler ?? noopHandler;\n\n return {\n info(message: string, ...args: unknown[]) {\n handler('info', message, args);\n },\n warn(message: string, ...args: unknown[]) {\n handler('warn', message, args);\n },\n error(message: string, ...args: unknown[]) {\n handler('error', message, args);\n },\n debug(message: string, ...args: unknown[]) {\n handler('debug', message, args);\n },\n };\n}\n","/**\n * @testrelic/core — Structured error factory\n *\n * Machine-readable error codes for programmatic handling.\n */\n\nexport const ErrorCode = {\n CONFIG_INVALID: 'TESTRELIC_CONFIG_INVALID',\n OUTPUT_WRITE_FAILED: 'TESTRELIC_OUTPUT_WRITE_FAILED',\n MERGE_INVALID_SCHEMA: 'TESTRELIC_MERGE_INVALID_SCHEMA',\n MERGE_READ_FAILED: 'TESTRELIC_MERGE_READ_FAILED',\n NAVIGATION_FLUSH_FAILED: 'TESTRELIC_NAVIGATION_FLUSH_FAILED',\n CODE_EXTRACT_FAILED: 'TESTRELIC_CODE_EXTRACT_FAILED',\n HTML_REPORT_FAILED: 'TESTRELIC_HTML_REPORT_FAILED',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nexport class TestRelicError extends Error {\n readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = 'TestRelicError';\n this.code = code;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', { value: cause, writable: false, enumerable: false });\n }\n }\n}\n\nexport function createError(\n code: ErrorCode,\n message: string,\n cause?: unknown,\n): TestRelicError {\n return new TestRelicError(code, message, cause);\n}\n","/**\n * @testrelic/core — Lightweight runtime type guards\n *\n * Hand-written guards, no runtime schema libraries (Constitution SDK Constraint).\n */\n\nimport type {\n ReporterConfig,\n NavigationType,\n TestRunReport,\n} from './types.js';\n\nconst NAVIGATION_TYPES = new Set<string>([\n 'goto', 'navigation', 'back', 'forward', 'refresh',\n 'spa_route', 'spa_replace', 'hash_change', 'link_click',\n 'form_submit', 'redirect', 'popstate', 'page_load',\n 'manual_record', 'fallback', 'dummy',\n]);\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport function isValidNavigationType(value: unknown): value is NavigationType {\n return typeof value === 'string' && NAVIGATION_TYPES.has(value);\n}\n\nfunction hasNoPrototypePollution(obj: unknown): boolean {\n if (typeof obj !== 'object' || obj === null) return true;\n for (const key of Object.keys(obj)) {\n if (DANGEROUS_KEYS.has(key)) return false;\n }\n return true;\n}\n\nexport function isValidConfig(input: unknown): input is ReporterConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.outputPath !== undefined && typeof obj.outputPath !== 'string') return false;\n if (obj.includeStackTrace !== undefined && typeof obj.includeStackTrace !== 'boolean') return false;\n if (obj.includeCodeSnippets !== undefined && typeof obj.includeCodeSnippets !== 'boolean') return false;\n if (obj.codeContextLines !== undefined && typeof obj.codeContextLines !== 'number') return false;\n if (obj.includeNetworkStats !== undefined && typeof obj.includeNetworkStats !== 'boolean') return false;\n\n if (obj.navigationTypes !== undefined && obj.navigationTypes !== null) {\n if (!Array.isArray(obj.navigationTypes)) return false;\n for (const t of obj.navigationTypes) {\n if (!isValidNavigationType(t)) return false;\n }\n }\n\n if (obj.redactPatterns !== undefined) {\n if (!Array.isArray(obj.redactPatterns)) return false;\n for (const p of obj.redactPatterns) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.testRunId !== undefined && obj.testRunId !== null && typeof obj.testRunId !== 'string') return false;\n\n if (obj.metadata !== undefined && obj.metadata !== null) {\n if (typeof obj.metadata !== 'object') return false;\n if (!hasNoPrototypePollution(obj.metadata)) return false;\n }\n\n if (obj.openReport !== undefined && typeof obj.openReport !== 'boolean') return false;\n if (obj.htmlReportPath !== undefined && (typeof obj.htmlReportPath !== 'string' || obj.htmlReportPath === '')) return false;\n\n return true;\n}\n\nexport function isValidTestRunReport(value: unknown): value is TestRunReport {\n if (typeof value !== 'object' || value === null) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (typeof obj.schemaVersion !== 'string') return false;\n if (typeof obj.testRunId !== 'string') return false;\n if (typeof obj.startedAt !== 'string') return false;\n if (typeof obj.completedAt !== 'string') return false;\n if (typeof obj.totalDuration !== 'number') return false;\n if (typeof obj.summary !== 'object' || obj.summary === null) return false;\n if (!Array.isArray(obj.timeline)) return false;\n\n const summary = obj.summary as Record<string, unknown>;\n if (typeof summary.total !== 'number') return false;\n if (typeof summary.passed !== 'number') return false;\n if (typeof summary.failed !== 'number') return false;\n if (typeof summary.flaky !== 'number') return false;\n if (typeof summary.skipped !== 'number') return false;\n\n return true;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @testrelic/core — Shared type definitions
|
|
3
|
+
*
|
|
4
|
+
* All interfaces for the JSON output schema, reporter configuration,
|
|
5
|
+
* and internal fixture-to-reporter communication.
|
|
6
|
+
*
|
|
7
|
+
* Schema Version: 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
type NavigationType = 'goto' | 'navigation' | 'back' | 'forward' | 'refresh' | 'spa_route' | 'spa_replace' | 'hash_change' | 'link_click' | 'form_submit' | 'redirect' | 'popstate' | 'page_load' | 'manual_record' | 'fallback' | 'dummy';
|
|
10
|
+
interface TestRunReport {
|
|
11
|
+
readonly schemaVersion: string;
|
|
12
|
+
readonly testRunId: string;
|
|
13
|
+
readonly startedAt: string;
|
|
14
|
+
readonly completedAt: string;
|
|
15
|
+
readonly totalDuration: number;
|
|
16
|
+
readonly summary: Summary;
|
|
17
|
+
readonly ci: CIMetadata | null;
|
|
18
|
+
readonly metadata: Record<string, unknown> | null;
|
|
19
|
+
readonly timeline: TimelineEntry[];
|
|
20
|
+
readonly shardRunIds: string[] | null;
|
|
21
|
+
}
|
|
22
|
+
interface Summary {
|
|
23
|
+
readonly total: number;
|
|
24
|
+
readonly passed: number;
|
|
25
|
+
readonly failed: number;
|
|
26
|
+
readonly flaky: number;
|
|
27
|
+
readonly skipped: number;
|
|
28
|
+
}
|
|
29
|
+
interface CIMetadata {
|
|
30
|
+
readonly provider: CIProvider;
|
|
31
|
+
readonly buildId: string | null;
|
|
32
|
+
readonly commitSha: string | null;
|
|
33
|
+
readonly branch: string | null;
|
|
34
|
+
}
|
|
35
|
+
type CIProvider = 'github-actions' | 'gitlab-ci' | 'jenkins' | 'circleci' | 'unknown';
|
|
36
|
+
interface TimelineEntry {
|
|
37
|
+
readonly url: string;
|
|
38
|
+
readonly navigationType: NavigationType;
|
|
39
|
+
readonly visitedAt: string;
|
|
40
|
+
readonly duration: number;
|
|
41
|
+
readonly specFile: string;
|
|
42
|
+
readonly domContentLoadedAt: string | null;
|
|
43
|
+
readonly networkIdleAt: string | null;
|
|
44
|
+
readonly networkStats: NetworkStats | null;
|
|
45
|
+
readonly tests: TestResult[];
|
|
46
|
+
}
|
|
47
|
+
interface TestResult {
|
|
48
|
+
readonly title: string;
|
|
49
|
+
readonly status: TestStatus;
|
|
50
|
+
readonly duration: number;
|
|
51
|
+
readonly startedAt: string;
|
|
52
|
+
readonly completedAt: string;
|
|
53
|
+
readonly retryCount: number;
|
|
54
|
+
readonly tags: string[];
|
|
55
|
+
readonly failure: FailureDiagnostic | null;
|
|
56
|
+
}
|
|
57
|
+
type TestStatus = 'passed' | 'failed' | 'flaky';
|
|
58
|
+
interface FailureDiagnostic {
|
|
59
|
+
readonly message: string;
|
|
60
|
+
readonly line: number | null;
|
|
61
|
+
readonly code: string | null;
|
|
62
|
+
readonly stack: string | null;
|
|
63
|
+
}
|
|
64
|
+
interface NetworkStats {
|
|
65
|
+
readonly totalRequests: number;
|
|
66
|
+
readonly failedRequests: number;
|
|
67
|
+
readonly totalBytes: number;
|
|
68
|
+
readonly byType: ResourceBreakdown;
|
|
69
|
+
}
|
|
70
|
+
interface ResourceBreakdown {
|
|
71
|
+
readonly xhr: number;
|
|
72
|
+
readonly document: number;
|
|
73
|
+
readonly script: number;
|
|
74
|
+
readonly stylesheet: number;
|
|
75
|
+
readonly image: number;
|
|
76
|
+
readonly font: number;
|
|
77
|
+
readonly other: number;
|
|
78
|
+
}
|
|
79
|
+
interface ReporterConfig {
|
|
80
|
+
readonly outputPath?: string;
|
|
81
|
+
readonly includeStackTrace?: boolean;
|
|
82
|
+
readonly includeCodeSnippets?: boolean;
|
|
83
|
+
readonly codeContextLines?: number;
|
|
84
|
+
readonly includeNetworkStats?: boolean;
|
|
85
|
+
readonly navigationTypes?: NavigationType[] | null;
|
|
86
|
+
readonly redactPatterns?: (string | RegExp)[];
|
|
87
|
+
readonly testRunId?: string | null;
|
|
88
|
+
readonly metadata?: Record<string, unknown> | null;
|
|
89
|
+
readonly openReport?: boolean;
|
|
90
|
+
readonly htmlReportPath?: string;
|
|
91
|
+
}
|
|
92
|
+
interface NavigationAnnotation {
|
|
93
|
+
readonly url: string;
|
|
94
|
+
readonly navigationType: NavigationType;
|
|
95
|
+
readonly timestamp: string;
|
|
96
|
+
readonly domContentLoadedAt?: string;
|
|
97
|
+
readonly networkIdleAt?: string;
|
|
98
|
+
readonly networkStats?: NetworkStats;
|
|
99
|
+
}
|
|
100
|
+
interface MergeOptions {
|
|
101
|
+
readonly output: string;
|
|
102
|
+
readonly testRunId?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @testrelic/core — Configurable logger
|
|
107
|
+
*
|
|
108
|
+
* Provides a Logger interface with a no-op default.
|
|
109
|
+
* Never uses console.* directly (Constitution SDK Constraint).
|
|
110
|
+
*/
|
|
111
|
+
interface Logger {
|
|
112
|
+
info(message: string, ...args: unknown[]): void;
|
|
113
|
+
warn(message: string, ...args: unknown[]): void;
|
|
114
|
+
error(message: string, ...args: unknown[]): void;
|
|
115
|
+
debug(message: string, ...args: unknown[]): void;
|
|
116
|
+
}
|
|
117
|
+
interface LoggerOptions {
|
|
118
|
+
readonly handler?: (level: LogLevel, message: string, args: unknown[]) => void;
|
|
119
|
+
}
|
|
120
|
+
type LogLevel = 'info' | 'warn' | 'error' | 'debug';
|
|
121
|
+
declare function createLogger(options?: LoggerOptions): Logger;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @testrelic/core — Structured error factory
|
|
125
|
+
*
|
|
126
|
+
* Machine-readable error codes for programmatic handling.
|
|
127
|
+
*/
|
|
128
|
+
declare const ErrorCode: {
|
|
129
|
+
readonly CONFIG_INVALID: "TESTRELIC_CONFIG_INVALID";
|
|
130
|
+
readonly OUTPUT_WRITE_FAILED: "TESTRELIC_OUTPUT_WRITE_FAILED";
|
|
131
|
+
readonly MERGE_INVALID_SCHEMA: "TESTRELIC_MERGE_INVALID_SCHEMA";
|
|
132
|
+
readonly MERGE_READ_FAILED: "TESTRELIC_MERGE_READ_FAILED";
|
|
133
|
+
readonly NAVIGATION_FLUSH_FAILED: "TESTRELIC_NAVIGATION_FLUSH_FAILED";
|
|
134
|
+
readonly CODE_EXTRACT_FAILED: "TESTRELIC_CODE_EXTRACT_FAILED";
|
|
135
|
+
readonly HTML_REPORT_FAILED: "TESTRELIC_HTML_REPORT_FAILED";
|
|
136
|
+
};
|
|
137
|
+
type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
138
|
+
declare class TestRelicError extends Error {
|
|
139
|
+
readonly code: ErrorCode;
|
|
140
|
+
constructor(code: ErrorCode, message: string, cause?: unknown);
|
|
141
|
+
}
|
|
142
|
+
declare function createError(code: ErrorCode, message: string, cause?: unknown): TestRelicError;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @testrelic/core — Lightweight runtime type guards
|
|
146
|
+
*
|
|
147
|
+
* Hand-written guards, no runtime schema libraries (Constitution SDK Constraint).
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
declare function isValidNavigationType(value: unknown): value is NavigationType;
|
|
151
|
+
declare function isValidConfig(input: unknown): input is ReporterConfig;
|
|
152
|
+
declare function isValidTestRunReport(value: unknown): value is TestRunReport;
|
|
153
|
+
|
|
154
|
+
export { type CIMetadata, type CIProvider, ErrorCode, type FailureDiagnostic, type LogLevel, type Logger, type LoggerOptions, type MergeOptions, type NavigationAnnotation, type NavigationType, type NetworkStats, type ReporterConfig, type ResourceBreakdown, type Summary, TestRelicError, type TestResult, type TestRunReport, type TestStatus, type TimelineEntry, createError, createLogger, isValidConfig, isValidNavigationType, isValidTestRunReport };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @testrelic/core — Shared type definitions
|
|
3
|
+
*
|
|
4
|
+
* All interfaces for the JSON output schema, reporter configuration,
|
|
5
|
+
* and internal fixture-to-reporter communication.
|
|
6
|
+
*
|
|
7
|
+
* Schema Version: 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
type NavigationType = 'goto' | 'navigation' | 'back' | 'forward' | 'refresh' | 'spa_route' | 'spa_replace' | 'hash_change' | 'link_click' | 'form_submit' | 'redirect' | 'popstate' | 'page_load' | 'manual_record' | 'fallback' | 'dummy';
|
|
10
|
+
interface TestRunReport {
|
|
11
|
+
readonly schemaVersion: string;
|
|
12
|
+
readonly testRunId: string;
|
|
13
|
+
readonly startedAt: string;
|
|
14
|
+
readonly completedAt: string;
|
|
15
|
+
readonly totalDuration: number;
|
|
16
|
+
readonly summary: Summary;
|
|
17
|
+
readonly ci: CIMetadata | null;
|
|
18
|
+
readonly metadata: Record<string, unknown> | null;
|
|
19
|
+
readonly timeline: TimelineEntry[];
|
|
20
|
+
readonly shardRunIds: string[] | null;
|
|
21
|
+
}
|
|
22
|
+
interface Summary {
|
|
23
|
+
readonly total: number;
|
|
24
|
+
readonly passed: number;
|
|
25
|
+
readonly failed: number;
|
|
26
|
+
readonly flaky: number;
|
|
27
|
+
readonly skipped: number;
|
|
28
|
+
}
|
|
29
|
+
interface CIMetadata {
|
|
30
|
+
readonly provider: CIProvider;
|
|
31
|
+
readonly buildId: string | null;
|
|
32
|
+
readonly commitSha: string | null;
|
|
33
|
+
readonly branch: string | null;
|
|
34
|
+
}
|
|
35
|
+
type CIProvider = 'github-actions' | 'gitlab-ci' | 'jenkins' | 'circleci' | 'unknown';
|
|
36
|
+
interface TimelineEntry {
|
|
37
|
+
readonly url: string;
|
|
38
|
+
readonly navigationType: NavigationType;
|
|
39
|
+
readonly visitedAt: string;
|
|
40
|
+
readonly duration: number;
|
|
41
|
+
readonly specFile: string;
|
|
42
|
+
readonly domContentLoadedAt: string | null;
|
|
43
|
+
readonly networkIdleAt: string | null;
|
|
44
|
+
readonly networkStats: NetworkStats | null;
|
|
45
|
+
readonly tests: TestResult[];
|
|
46
|
+
}
|
|
47
|
+
interface TestResult {
|
|
48
|
+
readonly title: string;
|
|
49
|
+
readonly status: TestStatus;
|
|
50
|
+
readonly duration: number;
|
|
51
|
+
readonly startedAt: string;
|
|
52
|
+
readonly completedAt: string;
|
|
53
|
+
readonly retryCount: number;
|
|
54
|
+
readonly tags: string[];
|
|
55
|
+
readonly failure: FailureDiagnostic | null;
|
|
56
|
+
}
|
|
57
|
+
type TestStatus = 'passed' | 'failed' | 'flaky';
|
|
58
|
+
interface FailureDiagnostic {
|
|
59
|
+
readonly message: string;
|
|
60
|
+
readonly line: number | null;
|
|
61
|
+
readonly code: string | null;
|
|
62
|
+
readonly stack: string | null;
|
|
63
|
+
}
|
|
64
|
+
interface NetworkStats {
|
|
65
|
+
readonly totalRequests: number;
|
|
66
|
+
readonly failedRequests: number;
|
|
67
|
+
readonly totalBytes: number;
|
|
68
|
+
readonly byType: ResourceBreakdown;
|
|
69
|
+
}
|
|
70
|
+
interface ResourceBreakdown {
|
|
71
|
+
readonly xhr: number;
|
|
72
|
+
readonly document: number;
|
|
73
|
+
readonly script: number;
|
|
74
|
+
readonly stylesheet: number;
|
|
75
|
+
readonly image: number;
|
|
76
|
+
readonly font: number;
|
|
77
|
+
readonly other: number;
|
|
78
|
+
}
|
|
79
|
+
interface ReporterConfig {
|
|
80
|
+
readonly outputPath?: string;
|
|
81
|
+
readonly includeStackTrace?: boolean;
|
|
82
|
+
readonly includeCodeSnippets?: boolean;
|
|
83
|
+
readonly codeContextLines?: number;
|
|
84
|
+
readonly includeNetworkStats?: boolean;
|
|
85
|
+
readonly navigationTypes?: NavigationType[] | null;
|
|
86
|
+
readonly redactPatterns?: (string | RegExp)[];
|
|
87
|
+
readonly testRunId?: string | null;
|
|
88
|
+
readonly metadata?: Record<string, unknown> | null;
|
|
89
|
+
readonly openReport?: boolean;
|
|
90
|
+
readonly htmlReportPath?: string;
|
|
91
|
+
}
|
|
92
|
+
interface NavigationAnnotation {
|
|
93
|
+
readonly url: string;
|
|
94
|
+
readonly navigationType: NavigationType;
|
|
95
|
+
readonly timestamp: string;
|
|
96
|
+
readonly domContentLoadedAt?: string;
|
|
97
|
+
readonly networkIdleAt?: string;
|
|
98
|
+
readonly networkStats?: NetworkStats;
|
|
99
|
+
}
|
|
100
|
+
interface MergeOptions {
|
|
101
|
+
readonly output: string;
|
|
102
|
+
readonly testRunId?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @testrelic/core — Configurable logger
|
|
107
|
+
*
|
|
108
|
+
* Provides a Logger interface with a no-op default.
|
|
109
|
+
* Never uses console.* directly (Constitution SDK Constraint).
|
|
110
|
+
*/
|
|
111
|
+
interface Logger {
|
|
112
|
+
info(message: string, ...args: unknown[]): void;
|
|
113
|
+
warn(message: string, ...args: unknown[]): void;
|
|
114
|
+
error(message: string, ...args: unknown[]): void;
|
|
115
|
+
debug(message: string, ...args: unknown[]): void;
|
|
116
|
+
}
|
|
117
|
+
interface LoggerOptions {
|
|
118
|
+
readonly handler?: (level: LogLevel, message: string, args: unknown[]) => void;
|
|
119
|
+
}
|
|
120
|
+
type LogLevel = 'info' | 'warn' | 'error' | 'debug';
|
|
121
|
+
declare function createLogger(options?: LoggerOptions): Logger;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @testrelic/core — Structured error factory
|
|
125
|
+
*
|
|
126
|
+
* Machine-readable error codes for programmatic handling.
|
|
127
|
+
*/
|
|
128
|
+
declare const ErrorCode: {
|
|
129
|
+
readonly CONFIG_INVALID: "TESTRELIC_CONFIG_INVALID";
|
|
130
|
+
readonly OUTPUT_WRITE_FAILED: "TESTRELIC_OUTPUT_WRITE_FAILED";
|
|
131
|
+
readonly MERGE_INVALID_SCHEMA: "TESTRELIC_MERGE_INVALID_SCHEMA";
|
|
132
|
+
readonly MERGE_READ_FAILED: "TESTRELIC_MERGE_READ_FAILED";
|
|
133
|
+
readonly NAVIGATION_FLUSH_FAILED: "TESTRELIC_NAVIGATION_FLUSH_FAILED";
|
|
134
|
+
readonly CODE_EXTRACT_FAILED: "TESTRELIC_CODE_EXTRACT_FAILED";
|
|
135
|
+
readonly HTML_REPORT_FAILED: "TESTRELIC_HTML_REPORT_FAILED";
|
|
136
|
+
};
|
|
137
|
+
type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
138
|
+
declare class TestRelicError extends Error {
|
|
139
|
+
readonly code: ErrorCode;
|
|
140
|
+
constructor(code: ErrorCode, message: string, cause?: unknown);
|
|
141
|
+
}
|
|
142
|
+
declare function createError(code: ErrorCode, message: string, cause?: unknown): TestRelicError;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @testrelic/core — Lightweight runtime type guards
|
|
146
|
+
*
|
|
147
|
+
* Hand-written guards, no runtime schema libraries (Constitution SDK Constraint).
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
declare function isValidNavigationType(value: unknown): value is NavigationType;
|
|
151
|
+
declare function isValidConfig(input: unknown): input is ReporterConfig;
|
|
152
|
+
declare function isValidTestRunReport(value: unknown): value is TestRunReport;
|
|
153
|
+
|
|
154
|
+
export { type CIMetadata, type CIProvider, ErrorCode, type FailureDiagnostic, type LogLevel, type Logger, type LoggerOptions, type MergeOptions, type NavigationAnnotation, type NavigationType, type NetworkStats, type ReporterConfig, type ResourceBreakdown, type Summary, TestRelicError, type TestResult, type TestRunReport, type TestStatus, type TimelineEntry, createError, createLogger, isValidConfig, isValidNavigationType, isValidTestRunReport };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// src/logger.ts
|
|
2
|
+
var noopHandler = () => {
|
|
3
|
+
};
|
|
4
|
+
function createLogger(options) {
|
|
5
|
+
const handler = options?.handler ?? noopHandler;
|
|
6
|
+
return {
|
|
7
|
+
info(message, ...args) {
|
|
8
|
+
handler("info", message, args);
|
|
9
|
+
},
|
|
10
|
+
warn(message, ...args) {
|
|
11
|
+
handler("warn", message, args);
|
|
12
|
+
},
|
|
13
|
+
error(message, ...args) {
|
|
14
|
+
handler("error", message, args);
|
|
15
|
+
},
|
|
16
|
+
debug(message, ...args) {
|
|
17
|
+
handler("debug", message, args);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/errors.ts
|
|
23
|
+
var ErrorCode = {
|
|
24
|
+
CONFIG_INVALID: "TESTRELIC_CONFIG_INVALID",
|
|
25
|
+
OUTPUT_WRITE_FAILED: "TESTRELIC_OUTPUT_WRITE_FAILED",
|
|
26
|
+
MERGE_INVALID_SCHEMA: "TESTRELIC_MERGE_INVALID_SCHEMA",
|
|
27
|
+
MERGE_READ_FAILED: "TESTRELIC_MERGE_READ_FAILED",
|
|
28
|
+
NAVIGATION_FLUSH_FAILED: "TESTRELIC_NAVIGATION_FLUSH_FAILED",
|
|
29
|
+
CODE_EXTRACT_FAILED: "TESTRELIC_CODE_EXTRACT_FAILED",
|
|
30
|
+
HTML_REPORT_FAILED: "TESTRELIC_HTML_REPORT_FAILED"
|
|
31
|
+
};
|
|
32
|
+
var TestRelicError = class extends Error {
|
|
33
|
+
constructor(code, message, cause) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "TestRelicError";
|
|
36
|
+
this.code = code;
|
|
37
|
+
if (cause !== void 0) {
|
|
38
|
+
Object.defineProperty(this, "cause", { value: cause, writable: false, enumerable: false });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
function createError(code, message, cause) {
|
|
43
|
+
return new TestRelicError(code, message, cause);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/validation.ts
|
|
47
|
+
var NAVIGATION_TYPES = /* @__PURE__ */ new Set([
|
|
48
|
+
"goto",
|
|
49
|
+
"navigation",
|
|
50
|
+
"back",
|
|
51
|
+
"forward",
|
|
52
|
+
"refresh",
|
|
53
|
+
"spa_route",
|
|
54
|
+
"spa_replace",
|
|
55
|
+
"hash_change",
|
|
56
|
+
"link_click",
|
|
57
|
+
"form_submit",
|
|
58
|
+
"redirect",
|
|
59
|
+
"popstate",
|
|
60
|
+
"page_load",
|
|
61
|
+
"manual_record",
|
|
62
|
+
"fallback",
|
|
63
|
+
"dummy"
|
|
64
|
+
]);
|
|
65
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
66
|
+
function isValidNavigationType(value) {
|
|
67
|
+
return typeof value === "string" && NAVIGATION_TYPES.has(value);
|
|
68
|
+
}
|
|
69
|
+
function hasNoPrototypePollution(obj) {
|
|
70
|
+
if (typeof obj !== "object" || obj === null) return true;
|
|
71
|
+
for (const key of Object.keys(obj)) {
|
|
72
|
+
if (DANGEROUS_KEYS.has(key)) return false;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
function isValidConfig(input) {
|
|
77
|
+
if (typeof input !== "object" || input === null) return false;
|
|
78
|
+
if (!hasNoPrototypePollution(input)) return false;
|
|
79
|
+
const obj = input;
|
|
80
|
+
if (obj.outputPath !== void 0 && typeof obj.outputPath !== "string") return false;
|
|
81
|
+
if (obj.includeStackTrace !== void 0 && typeof obj.includeStackTrace !== "boolean") return false;
|
|
82
|
+
if (obj.includeCodeSnippets !== void 0 && typeof obj.includeCodeSnippets !== "boolean") return false;
|
|
83
|
+
if (obj.codeContextLines !== void 0 && typeof obj.codeContextLines !== "number") return false;
|
|
84
|
+
if (obj.includeNetworkStats !== void 0 && typeof obj.includeNetworkStats !== "boolean") return false;
|
|
85
|
+
if (obj.navigationTypes !== void 0 && obj.navigationTypes !== null) {
|
|
86
|
+
if (!Array.isArray(obj.navigationTypes)) return false;
|
|
87
|
+
for (const t of obj.navigationTypes) {
|
|
88
|
+
if (!isValidNavigationType(t)) return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (obj.redactPatterns !== void 0) {
|
|
92
|
+
if (!Array.isArray(obj.redactPatterns)) return false;
|
|
93
|
+
for (const p of obj.redactPatterns) {
|
|
94
|
+
if (typeof p !== "string" && !(p instanceof RegExp)) return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (obj.testRunId !== void 0 && obj.testRunId !== null && typeof obj.testRunId !== "string") return false;
|
|
98
|
+
if (obj.metadata !== void 0 && obj.metadata !== null) {
|
|
99
|
+
if (typeof obj.metadata !== "object") return false;
|
|
100
|
+
if (!hasNoPrototypePollution(obj.metadata)) return false;
|
|
101
|
+
}
|
|
102
|
+
if (obj.openReport !== void 0 && typeof obj.openReport !== "boolean") return false;
|
|
103
|
+
if (obj.htmlReportPath !== void 0 && (typeof obj.htmlReportPath !== "string" || obj.htmlReportPath === "")) return false;
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
function isValidTestRunReport(value) {
|
|
107
|
+
if (typeof value !== "object" || value === null) return false;
|
|
108
|
+
const obj = value;
|
|
109
|
+
if (typeof obj.schemaVersion !== "string") return false;
|
|
110
|
+
if (typeof obj.testRunId !== "string") return false;
|
|
111
|
+
if (typeof obj.startedAt !== "string") return false;
|
|
112
|
+
if (typeof obj.completedAt !== "string") return false;
|
|
113
|
+
if (typeof obj.totalDuration !== "number") return false;
|
|
114
|
+
if (typeof obj.summary !== "object" || obj.summary === null) return false;
|
|
115
|
+
if (!Array.isArray(obj.timeline)) return false;
|
|
116
|
+
const summary = obj.summary;
|
|
117
|
+
if (typeof summary.total !== "number") return false;
|
|
118
|
+
if (typeof summary.passed !== "number") return false;
|
|
119
|
+
if (typeof summary.failed !== "number") return false;
|
|
120
|
+
if (typeof summary.flaky !== "number") return false;
|
|
121
|
+
if (typeof summary.skipped !== "number") return false;
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
export {
|
|
125
|
+
ErrorCode,
|
|
126
|
+
TestRelicError,
|
|
127
|
+
createError,
|
|
128
|
+
createLogger,
|
|
129
|
+
isValidConfig,
|
|
130
|
+
isValidNavigationType,
|
|
131
|
+
isValidTestRunReport
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logger.ts","../src/errors.ts","../src/validation.ts"],"sourcesContent":["/**\n * @testrelic/core — Configurable logger\n *\n * Provides a Logger interface with a no-op default.\n * Never uses console.* directly (Constitution SDK Constraint).\n */\n\nexport interface Logger {\n info(message: string, ...args: unknown[]): void;\n warn(message: string, ...args: unknown[]): void;\n error(message: string, ...args: unknown[]): void;\n debug(message: string, ...args: unknown[]): void;\n}\n\nexport interface LoggerOptions {\n readonly handler?: (level: LogLevel, message: string, args: unknown[]) => void;\n}\n\nexport type LogLevel = 'info' | 'warn' | 'error' | 'debug';\n\ntype LogHandler = (level: LogLevel, message: string, args: unknown[]) => void;\n\nconst noopHandler: LogHandler = () => {};\n\nexport function createLogger(options?: LoggerOptions): Logger {\n const handler = options?.handler ?? noopHandler;\n\n return {\n info(message: string, ...args: unknown[]) {\n handler('info', message, args);\n },\n warn(message: string, ...args: unknown[]) {\n handler('warn', message, args);\n },\n error(message: string, ...args: unknown[]) {\n handler('error', message, args);\n },\n debug(message: string, ...args: unknown[]) {\n handler('debug', message, args);\n },\n };\n}\n","/**\n * @testrelic/core — Structured error factory\n *\n * Machine-readable error codes for programmatic handling.\n */\n\nexport const ErrorCode = {\n CONFIG_INVALID: 'TESTRELIC_CONFIG_INVALID',\n OUTPUT_WRITE_FAILED: 'TESTRELIC_OUTPUT_WRITE_FAILED',\n MERGE_INVALID_SCHEMA: 'TESTRELIC_MERGE_INVALID_SCHEMA',\n MERGE_READ_FAILED: 'TESTRELIC_MERGE_READ_FAILED',\n NAVIGATION_FLUSH_FAILED: 'TESTRELIC_NAVIGATION_FLUSH_FAILED',\n CODE_EXTRACT_FAILED: 'TESTRELIC_CODE_EXTRACT_FAILED',\n HTML_REPORT_FAILED: 'TESTRELIC_HTML_REPORT_FAILED',\n} as const;\n\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nexport class TestRelicError extends Error {\n readonly code: ErrorCode;\n\n constructor(code: ErrorCode, message: string, cause?: unknown) {\n super(message);\n this.name = 'TestRelicError';\n this.code = code;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', { value: cause, writable: false, enumerable: false });\n }\n }\n}\n\nexport function createError(\n code: ErrorCode,\n message: string,\n cause?: unknown,\n): TestRelicError {\n return new TestRelicError(code, message, cause);\n}\n","/**\n * @testrelic/core — Lightweight runtime type guards\n *\n * Hand-written guards, no runtime schema libraries (Constitution SDK Constraint).\n */\n\nimport type {\n ReporterConfig,\n NavigationType,\n TestRunReport,\n} from './types.js';\n\nconst NAVIGATION_TYPES = new Set<string>([\n 'goto', 'navigation', 'back', 'forward', 'refresh',\n 'spa_route', 'spa_replace', 'hash_change', 'link_click',\n 'form_submit', 'redirect', 'popstate', 'page_load',\n 'manual_record', 'fallback', 'dummy',\n]);\n\nconst DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport function isValidNavigationType(value: unknown): value is NavigationType {\n return typeof value === 'string' && NAVIGATION_TYPES.has(value);\n}\n\nfunction hasNoPrototypePollution(obj: unknown): boolean {\n if (typeof obj !== 'object' || obj === null) return true;\n for (const key of Object.keys(obj)) {\n if (DANGEROUS_KEYS.has(key)) return false;\n }\n return true;\n}\n\nexport function isValidConfig(input: unknown): input is ReporterConfig {\n if (typeof input !== 'object' || input === null) return false;\n if (!hasNoPrototypePollution(input)) return false;\n\n const obj = input as Record<string, unknown>;\n\n if (obj.outputPath !== undefined && typeof obj.outputPath !== 'string') return false;\n if (obj.includeStackTrace !== undefined && typeof obj.includeStackTrace !== 'boolean') return false;\n if (obj.includeCodeSnippets !== undefined && typeof obj.includeCodeSnippets !== 'boolean') return false;\n if (obj.codeContextLines !== undefined && typeof obj.codeContextLines !== 'number') return false;\n if (obj.includeNetworkStats !== undefined && typeof obj.includeNetworkStats !== 'boolean') return false;\n\n if (obj.navigationTypes !== undefined && obj.navigationTypes !== null) {\n if (!Array.isArray(obj.navigationTypes)) return false;\n for (const t of obj.navigationTypes) {\n if (!isValidNavigationType(t)) return false;\n }\n }\n\n if (obj.redactPatterns !== undefined) {\n if (!Array.isArray(obj.redactPatterns)) return false;\n for (const p of obj.redactPatterns) {\n if (typeof p !== 'string' && !(p instanceof RegExp)) return false;\n }\n }\n\n if (obj.testRunId !== undefined && obj.testRunId !== null && typeof obj.testRunId !== 'string') return false;\n\n if (obj.metadata !== undefined && obj.metadata !== null) {\n if (typeof obj.metadata !== 'object') return false;\n if (!hasNoPrototypePollution(obj.metadata)) return false;\n }\n\n if (obj.openReport !== undefined && typeof obj.openReport !== 'boolean') return false;\n if (obj.htmlReportPath !== undefined && (typeof obj.htmlReportPath !== 'string' || obj.htmlReportPath === '')) return false;\n\n return true;\n}\n\nexport function isValidTestRunReport(value: unknown): value is TestRunReport {\n if (typeof value !== 'object' || value === null) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (typeof obj.schemaVersion !== 'string') return false;\n if (typeof obj.testRunId !== 'string') return false;\n if (typeof obj.startedAt !== 'string') return false;\n if (typeof obj.completedAt !== 'string') return false;\n if (typeof obj.totalDuration !== 'number') return false;\n if (typeof obj.summary !== 'object' || obj.summary === null) return false;\n if (!Array.isArray(obj.timeline)) return false;\n\n const summary = obj.summary as Record<string, unknown>;\n if (typeof summary.total !== 'number') return false;\n if (typeof summary.passed !== 'number') return false;\n if (typeof summary.failed !== 'number') return false;\n if (typeof summary.flaky !== 'number') return false;\n if (typeof summary.skipped !== 'number') return false;\n\n return true;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@testrelic/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared types, logger, errors, and validation for TestRelic packages",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^18.0.0",
|
|
27
|
+
"typescript": "^5.5.0",
|
|
28
|
+
"tsup": "^8.0.0",
|
|
29
|
+
"vitest": "^2.0.0"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/testrelic-ai/testrelic-sdk.git",
|
|
38
|
+
"directory": "packages/core"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"test:watch": "vitest",
|
|
44
|
+
"typecheck": "tsc --noEmit"
|
|
45
|
+
}
|
|
46
|
+
}
|