doc-detective-common 4.0.0-beta.0-dev.4 → 4.0.0-beta.0-dev.6

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.
Files changed (200) hide show
  1. package/package.json +1 -1
  2. package/.c8rc.json +0 -8
  3. package/.mocharc.yml +0 -1
  4. package/icon.png +0 -0
  5. package/scripts/check-coverage-ratchet.cjs +0 -123
  6. package/scripts/createCjsWrapper.js +0 -25
  7. package/scripts/generateTypes.cjs +0 -54
  8. package/src/detectTests.ts +0 -660
  9. package/src/index.ts +0 -3
  10. package/src/schemas/build/checkLink_v2.schema.json +0 -81
  11. package/src/schemas/build/checkLink_v3.schema.json +0 -92
  12. package/src/schemas/build/click_v3.schema.json +0 -145
  13. package/src/schemas/build/config_v2.schema.json +0 -1079
  14. package/src/schemas/build/config_v3.schema.json +0 -696
  15. package/src/schemas/build/context_v2.schema.json +0 -135
  16. package/src/schemas/build/context_v3.schema.json +0 -229
  17. package/src/schemas/build/dragAndDrop_v3.schema.json +0 -189
  18. package/src/schemas/build/find_v2.schema.json +0 -168
  19. package/src/schemas/build/find_v3.schema.json +0 -214
  20. package/src/schemas/build/goTo_v2.schema.json +0 -63
  21. package/src/schemas/build/goTo_v3.schema.json +0 -257
  22. package/src/schemas/build/httpRequest_v2.schema.json +0 -321
  23. package/src/schemas/build/httpRequest_v3.schema.json +0 -439
  24. package/src/schemas/build/loadCookie_v3.schema.json +0 -138
  25. package/src/schemas/build/loadVariables_v3.schema.json +0 -10
  26. package/src/schemas/build/moveTo_v2.schema.json +0 -99
  27. package/src/schemas/build/openApi_v2.schema.json +0 -76
  28. package/src/schemas/build/openApi_v3.schema.json +0 -161
  29. package/src/schemas/build/record_v3.schema.json +0 -70
  30. package/src/schemas/build/report_v3.schema.json +0 -53
  31. package/src/schemas/build/resolvedTests_v3.schema.json +0 -238
  32. package/src/schemas/build/runCode_v2.schema.json +0 -201
  33. package/src/schemas/build/runCode_v3.schema.json +0 -138
  34. package/src/schemas/build/runShell_v2.schema.json +0 -189
  35. package/src/schemas/build/runShell_v3.schema.json +0 -156
  36. package/src/schemas/build/saveCookie_v3.schema.json +0 -149
  37. package/src/schemas/build/saveScreenshot_v2.schema.json +0 -150
  38. package/src/schemas/build/screenshot_v3.schema.json +0 -250
  39. package/src/schemas/build/setVariables_v2.schema.json +0 -39
  40. package/src/schemas/build/sourceIntegration_v3.schema.json +0 -51
  41. package/src/schemas/build/spec_v2.schema.json +0 -199
  42. package/src/schemas/build/spec_v3.schema.json +0 -213
  43. package/src/schemas/build/startRecording_v2.schema.json +0 -57
  44. package/src/schemas/build/step_v3.schema.json +0 -669
  45. package/src/schemas/build/stopRecord_v3.schema.json +0 -13
  46. package/src/schemas/build/stopRecording_v2.schema.json +0 -33
  47. package/src/schemas/build/test_v2.schema.json +0 -244
  48. package/src/schemas/build/test_v3.schema.json +0 -292
  49. package/src/schemas/build/typeKeys_v2.schema.json +0 -72
  50. package/src/schemas/build/type_v3.schema.json +0 -131
  51. package/src/schemas/build/wait_v2.schema.json +0 -42
  52. package/src/schemas/build/wait_v3.schema.json +0 -37
  53. package/src/schemas/dereferenceSchemas.cjs +0 -208
  54. package/src/schemas/index.ts +0 -6
  55. package/src/schemas/output_schemas/analytics_v1.schema.json +0 -585
  56. package/src/schemas/output_schemas/checkLink_v1.schema.json +0 -42
  57. package/src/schemas/output_schemas/checkLink_v2.schema.json +0 -80
  58. package/src/schemas/output_schemas/checkLink_v3.schema.json +0 -145
  59. package/src/schemas/output_schemas/click_v1.schema.json +0 -60
  60. package/src/schemas/output_schemas/click_v3.schema.json +0 -252
  61. package/src/schemas/output_schemas/config_v2.schema.json +0 -2628
  62. package/src/schemas/output_schemas/config_v3.schema.json +0 -16470
  63. package/src/schemas/output_schemas/context_v2.schema.json +0 -134
  64. package/src/schemas/output_schemas/context_v3.schema.json +0 -374
  65. package/src/schemas/output_schemas/dragAndDrop_v3.schema.json +0 -496
  66. package/src/schemas/output_schemas/endRecord_v3.schema.json +0 -12
  67. package/src/schemas/output_schemas/find_v1.schema.json +0 -171
  68. package/src/schemas/output_schemas/find_v2.schema.json +0 -184
  69. package/src/schemas/output_schemas/find_v3.schema.json +0 -1349
  70. package/src/schemas/output_schemas/goTo_v1.schema.json +0 -30
  71. package/src/schemas/output_schemas/goTo_v2.schema.json +0 -62
  72. package/src/schemas/output_schemas/goTo_v3.schema.json +0 -419
  73. package/src/schemas/output_schemas/httpRequest_v1.schema.json +0 -115
  74. package/src/schemas/output_schemas/httpRequest_v2.schema.json +0 -392
  75. package/src/schemas/output_schemas/httpRequest_v3.schema.json +0 -994
  76. package/src/schemas/output_schemas/loadCookie_v3.schema.json +0 -228
  77. package/src/schemas/output_schemas/loadVariables_v3.schema.json +0 -9
  78. package/src/schemas/output_schemas/matchText_v1.schema.json +0 -32
  79. package/src/schemas/output_schemas/moveMouse_v1.schema.json +0 -60
  80. package/src/schemas/output_schemas/moveTo_v2.schema.json +0 -98
  81. package/src/schemas/output_schemas/openApi_v2.schema.json +0 -75
  82. package/src/schemas/output_schemas/openApi_v3.schema.json +0 -162
  83. package/src/schemas/output_schemas/record_v3.schema.json +0 -101
  84. package/src/schemas/output_schemas/report_v3.schema.json +0 -16826
  85. package/src/schemas/output_schemas/resolvedTests_v3.schema.json +0 -33331
  86. package/src/schemas/output_schemas/runCode_v2.schema.json +0 -200
  87. package/src/schemas/output_schemas/runCode_v3.schema.json +0 -222
  88. package/src/schemas/output_schemas/runShell_v1.schema.json +0 -35
  89. package/src/schemas/output_schemas/runShell_v2.schema.json +0 -188
  90. package/src/schemas/output_schemas/runShell_v3.schema.json +0 -236
  91. package/src/schemas/output_schemas/saveCookie_v3.schema.json +0 -245
  92. package/src/schemas/output_schemas/saveScreenshot_v2.schema.json +0 -149
  93. package/src/schemas/output_schemas/screenshot_v1.schema.json +0 -48
  94. package/src/schemas/output_schemas/screenshot_v3.schema.json +0 -681
  95. package/src/schemas/output_schemas/scroll_v1.schema.json +0 -50
  96. package/src/schemas/output_schemas/setVariables_v2.schema.json +0 -38
  97. package/src/schemas/output_schemas/sourceIntegration_v3.schema.json +0 -50
  98. package/src/schemas/output_schemas/spec_v2.schema.json +0 -2301
  99. package/src/schemas/output_schemas/spec_v3.schema.json +0 -16630
  100. package/src/schemas/output_schemas/startRecording_v1.schema.json +0 -55
  101. package/src/schemas/output_schemas/startRecording_v2.schema.json +0 -56
  102. package/src/schemas/output_schemas/step_v3.schema.json +0 -7317
  103. package/src/schemas/output_schemas/stopRecord_v3.schema.json +0 -12
  104. package/src/schemas/output_schemas/stopRecording_v1.schema.json +0 -20
  105. package/src/schemas/output_schemas/stopRecording_v2.schema.json +0 -32
  106. package/src/schemas/output_schemas/test_v2.schema.json +0 -1903
  107. package/src/schemas/output_schemas/test_v3.schema.json +0 -15863
  108. package/src/schemas/output_schemas/typeKeys_v2.schema.json +0 -71
  109. package/src/schemas/output_schemas/type_v1.schema.json +0 -62
  110. package/src/schemas/output_schemas/type_v3.schema.json +0 -244
  111. package/src/schemas/output_schemas/wait_v1.schema.json +0 -42
  112. package/src/schemas/output_schemas/wait_v2.schema.json +0 -41
  113. package/src/schemas/output_schemas/wait_v3.schema.json +0 -41
  114. package/src/schemas/schemas.json +0 -121331
  115. package/src/schemas/src_schemas/analytics_v1.schema.json +0 -585
  116. package/src/schemas/src_schemas/checkLink_v1.schema.json +0 -42
  117. package/src/schemas/src_schemas/checkLink_v2.schema.json +0 -65
  118. package/src/schemas/src_schemas/checkLink_v3.schema.json +0 -91
  119. package/src/schemas/src_schemas/click_v1.schema.json +0 -60
  120. package/src/schemas/src_schemas/click_v3.schema.json +0 -144
  121. package/src/schemas/src_schemas/config_v2.schema.json +0 -929
  122. package/src/schemas/src_schemas/config_v3.schema.json +0 -633
  123. package/src/schemas/src_schemas/context_v2.schema.json +0 -108
  124. package/src/schemas/src_schemas/context_v3.schema.json +0 -202
  125. package/src/schemas/src_schemas/dragAndDrop_v3.schema.json +0 -185
  126. package/src/schemas/src_schemas/find_v1.schema.json +0 -137
  127. package/src/schemas/src_schemas/find_v2.schema.json +0 -155
  128. package/src/schemas/src_schemas/find_v3.schema.json +0 -210
  129. package/src/schemas/src_schemas/goTo_v1.schema.json +0 -30
  130. package/src/schemas/src_schemas/goTo_v2.schema.json +0 -58
  131. package/src/schemas/src_schemas/goTo_v3.schema.json +0 -232
  132. package/src/schemas/src_schemas/httpRequest_v1.schema.json +0 -115
  133. package/src/schemas/src_schemas/httpRequest_v2.schema.json +0 -284
  134. package/src/schemas/src_schemas/httpRequest_v3.schema.json +0 -402
  135. package/src/schemas/src_schemas/loadCookie_v3.schema.json +0 -113
  136. package/src/schemas/src_schemas/loadVariables_v3.schema.json +0 -9
  137. package/src/schemas/src_schemas/matchText_v1.schema.json +0 -32
  138. package/src/schemas/src_schemas/moveMouse_v1.schema.json +0 -60
  139. package/src/schemas/src_schemas/moveTo_v2.schema.json +0 -89
  140. package/src/schemas/src_schemas/openApi_v2.schema.json +0 -64
  141. package/src/schemas/src_schemas/openApi_v3.schema.json +0 -161
  142. package/src/schemas/src_schemas/record_v3.schema.json +0 -69
  143. package/src/schemas/src_schemas/report_v3.schema.json +0 -52
  144. package/src/schemas/src_schemas/resolvedTests_v3.schema.json +0 -237
  145. package/src/schemas/src_schemas/runCode_v2.schema.json +0 -181
  146. package/src/schemas/src_schemas/runCode_v3.schema.json +0 -137
  147. package/src/schemas/src_schemas/runShell_v1.schema.json +0 -35
  148. package/src/schemas/src_schemas/runShell_v2.schema.json +0 -166
  149. package/src/schemas/src_schemas/runShell_v3.schema.json +0 -155
  150. package/src/schemas/src_schemas/saveCookie_v3.schema.json +0 -124
  151. package/src/schemas/src_schemas/saveScreenshot_v2.schema.json +0 -129
  152. package/src/schemas/src_schemas/screenshot_v1.schema.json +0 -48
  153. package/src/schemas/src_schemas/screenshot_v3.schema.json +0 -249
  154. package/src/schemas/src_schemas/scroll_v1.schema.json +0 -50
  155. package/src/schemas/src_schemas/setVariables_v2.schema.json +0 -38
  156. package/src/schemas/src_schemas/sourceIntegration_v3.schema.json +0 -45
  157. package/src/schemas/src_schemas/spec_v2.schema.json +0 -175
  158. package/src/schemas/src_schemas/spec_v3.schema.json +0 -212
  159. package/src/schemas/src_schemas/startRecording_v1.schema.json +0 -55
  160. package/src/schemas/src_schemas/startRecording_v2.schema.json +0 -54
  161. package/src/schemas/src_schemas/step_v3.schema.json +0 -632
  162. package/src/schemas/src_schemas/stopRecord_v3.schema.json +0 -9
  163. package/src/schemas/src_schemas/stopRecording_v1.schema.json +0 -20
  164. package/src/schemas/src_schemas/stopRecording_v2.schema.json +0 -32
  165. package/src/schemas/src_schemas/test_v2.schema.json +0 -200
  166. package/src/schemas/src_schemas/test_v3.schema.json +0 -268
  167. package/src/schemas/src_schemas/typeKeys_v2.schema.json +0 -63
  168. package/src/schemas/src_schemas/type_v1.schema.json +0 -62
  169. package/src/schemas/src_schemas/type_v3.schema.json +0 -118
  170. package/src/schemas/src_schemas/wait_v1.schema.json +0 -42
  171. package/src/schemas/src_schemas/wait_v2.schema.json +0 -41
  172. package/src/schemas/src_schemas/wait_v3.schema.json +0 -36
  173. package/src/types/generated/checkLink_v3.ts +0 -29
  174. package/src/types/generated/click_v3.ts +0 -17
  175. package/src/types/generated/config_v3.ts +0 -405
  176. package/src/types/generated/context_v3.ts +0 -112
  177. package/src/types/generated/dragAndDrop_v3.ts +0 -39
  178. package/src/types/generated/endRecord_v3.ts +0 -10
  179. package/src/types/generated/find_v3.ts +0 -17
  180. package/src/types/generated/goTo_v3.ts +0 -48
  181. package/src/types/generated/httpRequest_v3.ts +0 -17
  182. package/src/types/generated/loadCookie_v3.ts +0 -17
  183. package/src/types/generated/loadVariables_v3.ts +0 -10
  184. package/src/types/generated/openApi_v3.ts +0 -64
  185. package/src/types/generated/record_v3.ts +0 -34
  186. package/src/types/generated/report_v3.ts +0 -183
  187. package/src/types/generated/resolvedTests_v3.ts +0 -585
  188. package/src/types/generated/runCode_v3.ts +0 -59
  189. package/src/types/generated/runShell_v3.ts +0 -58
  190. package/src/types/generated/saveCookie_v3.ts +0 -17
  191. package/src/types/generated/screenshot_v3.ts +0 -76
  192. package/src/types/generated/sourceIntegration_v3.ts +0 -31
  193. package/src/types/generated/spec_v3.ts +0 -166
  194. package/src/types/generated/step_v3.ts +0 -1288
  195. package/src/types/generated/stopRecord_v3.ts +0 -10
  196. package/src/types/generated/test_v3.ts +0 -3048
  197. package/src/types/generated/type_v3.ts +0 -56
  198. package/src/types/generated/wait_v3.ts +0 -13
  199. package/src/validate.ts +0 -627
  200. package/tsconfig.json +0 -22
@@ -1,660 +0,0 @@
1
- /**
2
- * Browser-compatible test detection utilities.
3
- * This module provides pure parsing functionality that works with strings/objects,
4
- * without dependencies on Node.js file system or path modules.
5
- */
6
-
7
- import YAML from "yaml";
8
- import { validate, transformToSchemaKey } from "./validate.js";
9
- import { SchemaKey } from "./schemas/index.js";
10
-
11
- /**
12
- * Creates a RegExp from a pattern string with safety checks against ReDoS.
13
- * Returns null if the pattern is invalid or potentially unsafe.
14
- *
15
- * The pattern is reconstructed character-by-character to establish a
16
- * sanitization boundary, since these patterns come from trusted file type
17
- * configuration rather than arbitrary user input.
18
- */
19
- function safeRegExp(pattern: string, flags: string): RegExp | null {
20
- if (typeof pattern !== 'string' || pattern.length === 0) return null;
21
- // Reject excessively long patterns
22
- if (pattern.length > 1500) return null;
23
- // Reconstruct pattern to establish sanitization boundary
24
- const sanitized = Array.from(pattern, c => String.fromCharCode(c.charCodeAt(0))).join('');
25
- try {
26
- return new RegExp(sanitized, flags);
27
- } catch {
28
- return null;
29
- }
30
- }
31
-
32
- // Web Crypto API compatible UUID generation
33
- /* c8 ignore next 10 - crypto.randomUUID always available in Node.js; fallback is for browsers */
34
- function generateUUID(): string {
35
- if (typeof crypto !== 'undefined' && crypto.randomUUID) {
36
- return crypto.randomUUID();
37
- }
38
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
39
- const r = Math.random() * 16 | 0;
40
- const v = c === 'x' ? r : (r & 0x3 | 0x8);
41
- return v.toString(16);
42
- });
43
- }
44
-
45
- export interface FileType {
46
- name?: string;
47
- extensions: string[];
48
- inlineStatements?: {
49
- testStart?: string[];
50
- testEnd?: string[];
51
- ignoreStart?: string[];
52
- ignoreEnd?: string[];
53
- step?: string[];
54
- };
55
- markup?: Array<{
56
- regex: string[];
57
- actions?: (string | Record<string, any>)[];
58
- batchMatches?: boolean;
59
- }>;
60
- runShell?: Record<string, any>;
61
- }
62
-
63
- export interface DetectTestsConfig {
64
- detectSteps?: boolean;
65
- origin?: string;
66
- logLevel?: string;
67
- _herettoPathMapping?: Record<string, string>;
68
- }
69
-
70
- export interface DetectedTest {
71
- testId?: string;
72
- detectSteps?: boolean;
73
- steps: Array<Record<string, any>>;
74
- [key: string]: any;
75
- }
76
-
77
- export interface DetectTestsInput {
78
- content: string;
79
- filePath: string;
80
- fileType: FileType;
81
- config?: DetectTestsConfig;
82
- }
83
-
84
- /**
85
- * Browser-compatible test detection function.
86
- * Detects tests from content string using specified file type configuration.
87
- *
88
- * This is the main entry point for test detection in Common.
89
- * It works with content strings rather than file paths, making it browser-compatible.
90
- *
91
- * @param input - Detection input
92
- * @param input.content - Content string to parse for tests
93
- * @param input.filePath - File path (for metadata only, not file I/O)
94
- * @param input.fileType - File type configuration with parsing rules
95
- * @param input.config - Optional configuration
96
- * @returns Array of detected tests
97
- *
98
- * @example
99
- * ```typescript
100
- * const tests = await detectTests({
101
- * content: markdownContent,
102
- * filePath: 'docs/test.md',
103
- * fileType: { extensions: ['md'], markup: [...] },
104
- * config: { detectSteps: true }
105
- * });
106
- * ```
107
- */
108
- export async function detectTests(input: DetectTestsInput): Promise<DetectedTest[]> {
109
- return parseContent({
110
- config: input.config || {},
111
- content: input.content,
112
- filePath: input.filePath,
113
- fileType: input.fileType,
114
- });
115
- }
116
-
117
- /**
118
- * Parses XML-style attributes to an object.
119
- * Example: 'wait=500' becomes { wait: 500 }
120
- * Example: 'testId="myTestId" detectSteps=false' becomes { testId: "myTestId", detectSteps: false }
121
- * Example: 'httpRequest.url="https://example.com"' becomes { httpRequest: { url: "https://example.com" } }
122
- */
123
- export function parseXmlAttributes({ stringifiedObject }: { stringifiedObject: string }): Record<string, any> | null {
124
- if (typeof stringifiedObject !== "string") {
125
- return null;
126
- }
127
-
128
- const str = stringifiedObject.trim();
129
-
130
- // Check if it looks like JSON or YAML - if so, return null to let JSON/YAML parsers handle it
131
- if (str.startsWith("{") || str.startsWith("[")) {
132
- return null;
133
- }
134
-
135
- // Check if it looks like YAML (key: value pattern)
136
- const yamlPattern = /^\w+:\s/;
137
- if (yamlPattern.test(str)) {
138
- return null;
139
- }
140
- if (str.startsWith("-")) {
141
- return null;
142
- }
143
-
144
- // Parse XML-style attributes
145
- const result: Record<string, any> = {};
146
- const attrRegex = /([\w.]+)=(?:"([^"]*)"|'([^']*)'|(\S+))/g;
147
- let match;
148
- let hasMatches = false;
149
-
150
- while ((match = attrRegex.exec(str)) !== null) {
151
- hasMatches = true;
152
- const keyPath = match[1];
153
- let value: any = match[2] !== undefined ? match[2] : match[3] !== undefined ? match[3] : match[4];
154
-
155
- // Try to parse as boolean
156
- if (value === "true") {
157
- value = true;
158
- } else if (value === "false") {
159
- value = false;
160
- } else if (!isNaN(value) && value !== "") {
161
- value = Number(value);
162
- }
163
-
164
- // Handle dot notation for nested objects
165
- if (keyPath.includes(".")) {
166
- const keys = keyPath.split(".");
167
- // Skip paths that could cause prototype pollution
168
- if (keys.some(k => k === '__proto__' || k === 'constructor' || k === 'prototype')) continue;
169
- let current = result;
170
-
171
- for (let i = 0; i < keys.length - 1; i++) {
172
- const key = keys[i];
173
- if (key === '__proto__' || key === 'constructor' || key === 'prototype') break;
174
- if (!current[key] || typeof current[key] !== "object") {
175
- current[key] = {};
176
- }
177
- current = current[key];
178
- }
179
-
180
- const lastKey = keys[keys.length - 1];
181
- if (lastKey !== '__proto__' && lastKey !== 'constructor' && lastKey !== 'prototype') {
182
- current[lastKey] = value;
183
- }
184
- } else if (keyPath !== '__proto__' && keyPath !== 'constructor' && keyPath !== 'prototype') {
185
- result[keyPath] = value;
186
- }
187
- }
188
-
189
- return hasMatches ? result : null;
190
- }
191
-
192
- /**
193
- * Parses a JSON or YAML object from a string.
194
- */
195
- export function parseObject({ stringifiedObject }: { stringifiedObject: string }): Record<string, any> | null {
196
- if (typeof stringifiedObject === "string") {
197
- // First, try to parse as XML attributes
198
- const xmlAttrs = parseXmlAttributes({ stringifiedObject });
199
- if (xmlAttrs !== null) {
200
- return xmlAttrs;
201
- }
202
-
203
- // Try to parse as JSON
204
- try {
205
- const json = JSON.parse(stringifiedObject);
206
- if (typeof json !== "object" || json === null || Array.isArray(json)) return null;
207
- return json;
208
- } catch (jsonError) {
209
- // JSON parsing failed - check if this looks like escaped JSON
210
- const trimmedString = stringifiedObject.trim();
211
- const looksLikeEscapedJson =
212
- (trimmedString.startsWith("{") || trimmedString.startsWith("[")) &&
213
- trimmedString.includes('\\"');
214
-
215
- if (looksLikeEscapedJson) {
216
- try {
217
- const stringToParse = JSON.parse('"' + stringifiedObject + '"');
218
- const result = JSON.parse(stringToParse);
219
- if (typeof result !== "object" || result === null || Array.isArray(result)) return null;
220
- return result;
221
- } catch {
222
- // Fallback to simple quote replacement
223
- try {
224
- const unescaped = stringifiedObject.replace(/\\"/g, '"');
225
- const result = JSON.parse(unescaped);
226
- if (typeof result !== "object" || result === null || Array.isArray(result)) return null;
227
- return result;
228
- } catch {
229
- // Continue to YAML parsing
230
- }
231
- }
232
- }
233
-
234
- // Try to parse as YAML
235
- try {
236
- const yaml = YAML.parse(stringifiedObject);
237
- if (typeof yaml !== "object" || yaml === null || Array.isArray(yaml)) return null;
238
- return yaml;
239
- } catch (yamlError) {
240
- return null;
241
- }
242
- }
243
- }
244
- return stringifiedObject as any;
245
- }
246
-
247
- /**
248
- * Replaces numeric variables ($0, $1, etc.) in strings and objects with provided values.
249
- */
250
- export function replaceNumericVariables(
251
- stringOrObjectSource: string | Record<string, any>,
252
- values: Record<string, any>
253
- ): string | Record<string, any> | null {
254
- let stringOrObject = JSON.parse(JSON.stringify(stringOrObjectSource));
255
-
256
- if (typeof stringOrObject !== "string" && typeof stringOrObject !== "object") {
257
- throw new Error("Invalid stringOrObject type");
258
- }
259
- if (typeof values !== "object") {
260
- throw new Error("Invalid values type");
261
- }
262
-
263
- if (typeof stringOrObject === "string") {
264
- const matches = stringOrObject.match(/\$[0-9]+/g);
265
- if (matches) {
266
- const allExist = matches.every((variable) => {
267
- const index = variable.substring(1);
268
- return Object.hasOwn(values, index) && typeof values[index] !== "undefined";
269
- });
270
- if (!allExist) {
271
- return null;
272
- } else {
273
- stringOrObject = stringOrObject.replace(/\$[0-9]+/g, (variable) => {
274
- const index = variable.substring(1);
275
- return values[index];
276
- });
277
- }
278
- }
279
- }
280
-
281
- if (typeof stringOrObject === "object") {
282
- Object.keys(stringOrObject).forEach((key) => {
283
- if (typeof stringOrObject[key] === "object") {
284
- const result = replaceNumericVariables(stringOrObject[key], values);
285
- /* c8 ignore next 3 - defensive guard: recursive calls on objects can't return null currently */
286
- if (result === null) {
287
- delete stringOrObject[key];
288
- } else {
289
- stringOrObject[key] = result;
290
- }
291
- } else if (typeof stringOrObject[key] === "string") {
292
- const matches = stringOrObject[key].match(/\$[0-9]+/g);
293
- if (matches) {
294
- const allExist = matches.every((variable: string) => {
295
- const index = variable.substring(1);
296
- return Object.hasOwn(values, index) && typeof values[index] !== "undefined";
297
- });
298
- if (!allExist) {
299
- delete stringOrObject[key];
300
- } else {
301
- stringOrObject[key] = stringOrObject[key].replace(/\$[0-9]+/g, (variable: string) => {
302
- const index = variable.substring(1);
303
- return values[index];
304
- });
305
- }
306
- }
307
- }
308
- });
309
- }
310
-
311
- return stringOrObject;
312
- }
313
-
314
- /**
315
- * Parses raw test content into an array of structured test objects.
316
- * This is a browser-compatible function that works with strings and doesn't require file system access.
317
- *
318
- * @param options - Options for parsing
319
- * @param options.config - Test configuration object
320
- * @param options.content - Raw file content as a string
321
- * @param options.filePath - Path to the file being parsed (for metadata, not file I/O)
322
- * @param options.fileType - File type definition containing parsing rules
323
- * @returns Array of parsed and validated test objects
324
- */
325
- export async function parseContent({
326
- config,
327
- content,
328
- filePath,
329
- fileType,
330
- }: {
331
- config: DetectTestsConfig;
332
- content: string;
333
- filePath: string;
334
- fileType: FileType;
335
- }): Promise<DetectedTest[]> {
336
- const statements: Array<any> = [];
337
- const statementTypes = ["testStart", "testEnd", "ignoreStart", "ignoreEnd", "step"];
338
-
339
- function findTest({ tests, testId }: { tests: DetectedTest[]; testId: string }): DetectedTest {
340
- let test = tests.find((t) => t.testId === testId);
341
- if (!test) {
342
- test = { testId, steps: [] };
343
- tests.push(test);
344
- }
345
- return test;
346
- }
347
-
348
- // Test for each statement type
349
- statementTypes.forEach((statementType) => {
350
- if (
351
- typeof fileType.inlineStatements === "undefined" ||
352
- typeof fileType.inlineStatements[statementType as keyof typeof fileType.inlineStatements] === "undefined"
353
- )
354
- return;
355
-
356
- fileType.inlineStatements[statementType as keyof typeof fileType.inlineStatements]!.forEach((statementRegex) => {
357
- const regex = safeRegExp(statementRegex, "g");
358
- if (!regex) return;
359
- const matches = [...content.matchAll(regex)];
360
- matches.forEach((match: any) => {
361
- match.type = statementType;
362
- match.sortIndex = match[1] ? match.index + match[1].length : match.index;
363
- });
364
- statements.push(...matches);
365
- });
366
- });
367
-
368
- if (config.detectSteps && fileType.markup) {
369
- fileType.markup.forEach((markup) => {
370
- markup.regex.forEach((pattern) => {
371
- const regex = safeRegExp(pattern, "g");
372
- if (!regex) return;
373
- const matches = [...content.matchAll(regex)];
374
- if (matches.length > 0 && markup.batchMatches) {
375
- const combinedMatch: any = {
376
- 1: matches.map((match) => match[1] || match[0]).join("\n"),
377
- type: "detectedStep",
378
- markup: markup,
379
- sortIndex: Math.min(...matches.map((match) => match.index!)),
380
- };
381
- statements.push(combinedMatch);
382
- } else if (matches.length > 0) {
383
- matches.forEach((match: any) => {
384
- match.type = "detectedStep";
385
- match.markup = markup;
386
- match.sortIndex = match[1] ? match.index + match[1].length : match.index;
387
- });
388
- statements.push(...matches);
389
- }
390
- });
391
- });
392
- }
393
-
394
- // Sort statements by index
395
- statements.sort((a, b) => a.sortIndex - b.sortIndex);
396
-
397
- // Process statements into tests and steps
398
- let tests: DetectedTest[] = [];
399
- let testId = generateUUID();
400
- let ignore = false;
401
-
402
- statements.forEach((statement) => {
403
- let test: DetectedTest | undefined;
404
- let statementContent = "";
405
- let stepsCleanup = false;
406
-
407
- switch (statement.type) {
408
- case "testStart": {
409
- statementContent = statement[1] || statement[0];
410
- const parsedTest = parseObject({ stringifiedObject: statementContent });
411
- if (!parsedTest || typeof parsedTest !== 'object') break;
412
-
413
- test = parsedTest as DetectedTest;
414
-
415
- // If v2 schema, convert to v3
416
- if (test.id || test.file || test.setup || test.cleanup) {
417
- if (!test.steps) {
418
- test.steps = [{ action: "goTo", url: "https://doc-detective.com" }];
419
- stepsCleanup = true;
420
- }
421
- const transformed = transformToSchemaKey({
422
- object: test,
423
- currentSchema: "test_v2" as SchemaKey,
424
- targetSchema: "test_v3" as SchemaKey,
425
- });
426
- test = transformed as DetectedTest;
427
- if (stepsCleanup && test) {
428
- test.steps = [];
429
- }
430
- }
431
-
432
- if (test.testId) {
433
- testId = test.testId;
434
- } else {
435
- test.testId = testId;
436
- }
437
-
438
- if (test.detectSteps === "false" as any) {
439
- test.detectSteps = false;
440
- } else if (test.detectSteps === "true" as any) {
441
- test.detectSteps = true;
442
- }
443
- if (!test.steps) {
444
- test.steps = [];
445
- }
446
- tests.push(test);
447
- break;
448
- }
449
-
450
- case "testEnd":
451
- testId = generateUUID();
452
- ignore = false;
453
- break;
454
-
455
- case "ignoreStart":
456
- ignore = true;
457
- break;
458
-
459
- case "ignoreEnd":
460
- ignore = false;
461
- break;
462
-
463
- case "detectedStep":
464
- if (ignore) break;
465
- test = findTest({ tests, testId });
466
- if (typeof test.detectSteps !== "undefined" && !test.detectSteps) {
467
- break;
468
- }
469
- if (statement?.markup?.actions) {
470
- statement.markup.actions.forEach((action: string | Record<string, any>) => {
471
- let step: Record<string, any> = {};
472
- if (typeof action === "string") {
473
- if (action === "runCode") return;
474
- step[action] = statement[1] || statement[0];
475
- if (config.origin && (action === "goTo" || action === "checkLink")) {
476
- step[action] = { url: step[action], origin: config.origin };
477
- }
478
- // Attach sourceIntegration for Heretto
479
- if (action === "screenshot" && config._herettoPathMapping) {
480
- const herettoIntegration = findHerettoIntegration(config, filePath);
481
- if (herettoIntegration) {
482
- const screenshotPath = step[action];
483
- step[action] = {
484
- path: screenshotPath,
485
- sourceIntegration: {
486
- type: "heretto",
487
- integrationName: herettoIntegration,
488
- filePath: screenshotPath,
489
- contentPath: filePath,
490
- },
491
- };
492
- }
493
- }
494
- } else {
495
- const replacedStep = replaceNumericVariables(action, statement);
496
- /* c8 ignore next - typeof string check is defensive; object actions always return objects */
497
- if (!replacedStep || typeof replacedStep === 'string') return;
498
- step = replacedStep;
499
-
500
- // Attach sourceIntegration for Heretto
501
- if (step.screenshot && config._herettoPathMapping) {
502
- const herettoIntegration = findHerettoIntegration(config, filePath);
503
- if (herettoIntegration) {
504
- if (typeof step.screenshot === "string") {
505
- step.screenshot = { path: step.screenshot };
506
- } else if (typeof step.screenshot === "boolean") {
507
- step.screenshot = {};
508
- }
509
- step.screenshot.sourceIntegration = {
510
- type: "heretto",
511
- integrationName: herettoIntegration,
512
- filePath: step.screenshot.path || "",
513
- contentPath: filePath,
514
- };
515
- }
516
- }
517
- }
518
-
519
- // Normalize step field formats
520
- if (step.httpRequest?.request) {
521
- if (typeof step.httpRequest.request.headers === "string") {
522
- try {
523
- const headers: Record<string, string> = {};
524
- step.httpRequest.request.headers.split("\n").forEach((header: string) => {
525
- const colonIndex = header.indexOf(":");
526
- if (colonIndex === -1) return;
527
- const key = header.substring(0, colonIndex).trim();
528
- const value = header.substring(colonIndex + 1).trim();
529
- /* c8 ignore next 3 - V8 phantom branch in && short-circuit */
530
- if (key && value) {
531
- headers[key] = value;
532
- }
533
- });
534
- step.httpRequest.request.headers = headers;
535
- /* c8 ignore next 2 - string split/forEach can't throw */
536
- } catch (error) {
537
- }
538
- }
539
- if (
540
- typeof step.httpRequest.request.body === "string" &&
541
- (step.httpRequest.request.body.trim().startsWith("{") ||
542
- step.httpRequest.request.body.trim().startsWith("["))
543
- ) {
544
- try {
545
- step.httpRequest.request.body = JSON.parse(step.httpRequest.request.body);
546
- } catch (error) {
547
- // Ignore parsing errors
548
- }
549
- }
550
- }
551
-
552
- // Validate step
553
- const valid = validate({
554
- schemaKey: "step_v3" as SchemaKey,
555
- object: step,
556
- addDefaults: false,
557
- });
558
- if (!valid.valid) {
559
- log(config, "warn", `Step ${JSON.stringify(step)} isn't a valid step. Skipping.`);
560
- return;
561
- }
562
- step = valid.object;
563
- test!.steps.push(step);
564
- });
565
- }
566
- break;
567
-
568
- case "step": {
569
- if (ignore) break;
570
- test = findTest({ tests, testId });
571
- statementContent = statement[1] || statement[0];
572
- const parsedStep = parseObject({ stringifiedObject: statementContent });
573
- if (!parsedStep || typeof parsedStep !== 'object') break;
574
-
575
- let step = parsedStep;
576
- const validation = validate({
577
- schemaKey: "step_v3" as SchemaKey,
578
- object: step,
579
- addDefaults: false,
580
- });
581
- /* c8 ignore start - V8 phantom branch on if-else/switch-case */
582
- if (!validation.valid) {
583
- log(config, "warn", `Step ${JSON.stringify(step)} isn't a valid step. Skipping.`);
584
- return;
585
- }
586
- step = validation.object;
587
- test.steps.push(step);
588
- break;
589
- /* c8 ignore stop */
590
- }
591
-
592
- /* c8 ignore next 2 - all statement types are handled above */
593
- default:
594
- break;
595
- }
596
- });
597
-
598
- // Validate test objects
599
- const validatedTests: DetectedTest[] = [];
600
- tests.forEach((test) => {
601
- const validation = validate({
602
- schemaKey: "test_v3" as SchemaKey,
603
- object: test,
604
- addDefaults: false,
605
- });
606
- if (!validation.valid) {
607
- log(config, "warn", `Couldn't convert test in ${filePath} to valid test. Skipping.`);
608
- return;
609
- }
610
- validatedTests.push(validation.object);
611
- });
612
-
613
- return validatedTests;
614
- }
615
-
616
- /**
617
- * Helper function to find which Heretto integration a file belongs to.
618
- */
619
- function findHerettoIntegration(config: DetectTestsConfig, filePath: string): string | null {
620
- /* c8 ignore next - callers always check _herettoPathMapping before calling */
621
- if (!config._herettoPathMapping) return null;
622
-
623
- // Simple string matching since we don't have path.resolve in browser
624
- const normalizedFilePath = filePath.replace(/\\/g, "/");
625
-
626
- for (const [outputPath, integrationName] of Object.entries(config._herettoPathMapping)) {
627
- const normalizedOutputPath = outputPath.replace(/\\/g, "/");
628
- if (normalizedFilePath.startsWith(normalizedOutputPath)) {
629
- return integrationName;
630
- }
631
- }
632
-
633
- return null;
634
- }
635
-
636
- /**
637
- * Simple browser-compatible logging function.
638
- */
639
- export function log(config: DetectTestsConfig, level: string, message: any): void {
640
- const logLevels = ["silent", "error", "warn", "info", "debug"];
641
-
642
- // Normalize 'warning' to 'warn' for both config and message levels
643
- const configLevel = (config.logLevel || "info") === "warning" ? "warn" : (config.logLevel || "info");
644
- const normalizedLevel = level === "warning" ? "warn" : level;
645
-
646
- const configLevelIndex = logLevels.indexOf(configLevel);
647
- const messageLevelIndex = logLevels.indexOf(normalizedLevel);
648
-
649
- if (configLevelIndex < 0 || messageLevelIndex < 0) return;
650
- if (messageLevelIndex > configLevelIndex) return;
651
-
652
- // Treat message-level 'silent' as a no-op to avoid calling an undefined console method
653
- if (normalizedLevel === "silent") return;
654
-
655
- if (typeof message === "object") {
656
- console[normalizedLevel as 'error' | 'warn' | 'info' | 'debug'](JSON.stringify(message, null, 2));
657
- } else {
658
- console[normalizedLevel as 'error' | 'warn' | 'info' | 'debug'](message);
659
- }
660
- }
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export { schemas, SchemaKey, Schema } from "./schemas/index.js";
2
- export { validate, transformToSchemaKey, ValidateOptions, ValidateResult, TransformOptions } from "./validate.js";
3
- export { detectTests, DetectTestsInput, DetectedTest, DetectTestsConfig, FileType } from "./detectTests.js";