@diyor28/qa-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/context/codecs/browser-state.codec.d.ts +33 -0
- package/dist/agent/context/codecs/browser-state.codec.d.ts.map +1 -0
- package/dist/agent/context/codecs/browser-state.codec.js +46 -0
- package/dist/agent/context/codecs/index.d.ts +3 -0
- package/dist/agent/context/codecs/index.d.ts.map +1 -0
- package/dist/agent/context/codecs/index.js +2 -0
- package/dist/agent/context/codecs/qa-scenario.codec.d.ts +21 -0
- package/dist/agent/context/codecs/qa-scenario.codec.d.ts.map +1 -0
- package/dist/agent/context/codecs/qa-scenario.codec.js +44 -0
- package/dist/agent/context/index.d.ts +4 -0
- package/dist/agent/context/index.d.ts.map +1 -0
- package/dist/agent/context/index.js +3 -0
- package/dist/agent/context/qa-context.service.d.ts +24 -0
- package/dist/agent/context/qa-context.service.d.ts.map +1 -0
- package/dist/agent/context/qa-context.service.js +53 -0
- package/dist/agent/context/qa-policy.d.ts +24 -0
- package/dist/agent/context/qa-policy.d.ts.map +1 -0
- package/dist/agent/context/qa-policy.js +47 -0
- package/dist/agent/loop.d.ts +28 -0
- package/dist/agent/loop.d.ts.map +1 -0
- package/dist/agent/loop.js +111 -0
- package/dist/agent/system-prompt.d.ts +2 -0
- package/dist/agent/system-prompt.d.ts.map +1 -0
- package/dist/agent/system-prompt.js +39 -0
- package/dist/agent/tools/index.d.ts +5 -0
- package/dist/agent/tools/index.d.ts.map +1 -0
- package/dist/agent/tools/index.js +152 -0
- package/dist/browser/controller.d.ts +82 -0
- package/dist/browser/controller.d.ts.map +1 -0
- package/dist/browser/controller.js +289 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/report/builder.d.ts +23 -0
- package/dist/report/builder.d.ts.map +1 -0
- package/dist/report/builder.js +69 -0
- package/dist/runner.d.ts +10 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +143 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/vision/analyzer.d.ts +8 -0
- package/dist/vision/analyzer.d.ts.map +1 -0
- package/dist/vision/analyzer.js +49 -0
- package/dist/vision/prompts.d.ts +2 -0
- package/dist/vision/prompts.d.ts.map +1 -0
- package/dist/vision/prompts.js +28 -0
- package/package.json +49 -0
- package/src/agent/context/codecs/browser-state.codec.ts +57 -0
- package/src/agent/context/codecs/index.ts +2 -0
- package/src/agent/context/codecs/qa-scenario.codec.ts +55 -0
- package/src/agent/context/index.ts +3 -0
- package/src/agent/context/qa-context.service.ts +90 -0
- package/src/agent/context/qa-policy.ts +54 -0
- package/src/agent/loop.ts +147 -0
- package/src/agent/system-prompt.ts +39 -0
- package/src/agent/tools/index.ts +162 -0
- package/src/browser/controller.ts +321 -0
- package/src/index.ts +19 -0
- package/src/report/builder.ts +94 -0
- package/src/runner.ts +166 -0
- package/src/types.ts +68 -0
- package/src/vision/analyzer.ts +61 -0
- package/src/vision/prompts.ts +28 -0
- package/tsconfig.json +20 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { QaReport, QaFinding, QaArtifact } from '../types.js';
|
|
2
|
+
|
|
3
|
+
export interface BuildReportOptions {
|
|
4
|
+
findings: QaFinding[];
|
|
5
|
+
artifacts: QaArtifact[];
|
|
6
|
+
metadata: {
|
|
7
|
+
scenarioName: string;
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
viewport: { width: number; height: number };
|
|
10
|
+
timestamp: string;
|
|
11
|
+
};
|
|
12
|
+
duration: number;
|
|
13
|
+
summary: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class ReportBuilder {
|
|
17
|
+
static buildReport(options: BuildReportOptions): QaReport {
|
|
18
|
+
const { findings, artifacts, metadata, duration, summary } = options;
|
|
19
|
+
|
|
20
|
+
// Determine overall status
|
|
21
|
+
let status: 'passed' | 'failed' | 'issues_found' = 'passed';
|
|
22
|
+
|
|
23
|
+
if (findings.some((f) => f.severity === 'critical' || f.severity === 'high')) {
|
|
24
|
+
status = 'failed';
|
|
25
|
+
} else if (findings.length > 0) {
|
|
26
|
+
status = 'issues_found';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Generate summary if not provided
|
|
30
|
+
let finalSummary = summary;
|
|
31
|
+
if (!finalSummary) {
|
|
32
|
+
finalSummary = this.generateSummary(findings, status);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
summary: finalSummary,
|
|
37
|
+
status,
|
|
38
|
+
duration,
|
|
39
|
+
findings,
|
|
40
|
+
artifacts,
|
|
41
|
+
metadata,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private static generateSummary(findings: QaFinding[], status: string): string {
|
|
46
|
+
if (status === 'passed') {
|
|
47
|
+
return 'Test completed successfully with no issues found.';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const critical = findings.filter((f) => f.severity === 'critical').length;
|
|
51
|
+
const high = findings.filter((f) => f.severity === 'high').length;
|
|
52
|
+
const medium = findings.filter((f) => f.severity === 'medium').length;
|
|
53
|
+
const low = findings.filter((f) => f.severity === 'low').length;
|
|
54
|
+
|
|
55
|
+
const parts: string[] = [`Test completed with ${findings.length} issue(s) found:`];
|
|
56
|
+
|
|
57
|
+
if (critical > 0) parts.push(`${critical} critical`);
|
|
58
|
+
if (high > 0) parts.push(`${high} high`);
|
|
59
|
+
if (medium > 0) parts.push(`${medium} medium`);
|
|
60
|
+
if (low > 0) parts.push(`${low} low`);
|
|
61
|
+
|
|
62
|
+
return parts.join(', ') + '.';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static aggregateFindingsByCategory(findings: QaFinding[]): Record<string, QaFinding[]> {
|
|
66
|
+
const byCategory: Record<string, QaFinding[]> = {
|
|
67
|
+
functional: [],
|
|
68
|
+
ux: [],
|
|
69
|
+
ui: [],
|
|
70
|
+
accessibility: [],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
for (const finding of findings) {
|
|
74
|
+
byCategory[finding.category].push(finding);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return byCategory;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static aggregateFindingsBySeverity(findings: QaFinding[]): Record<string, QaFinding[]> {
|
|
81
|
+
const bySeverity: Record<string, QaFinding[]> = {
|
|
82
|
+
critical: [],
|
|
83
|
+
high: [],
|
|
84
|
+
medium: [],
|
|
85
|
+
low: [],
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
for (const finding of findings) {
|
|
89
|
+
bySeverity[finding.severity].push(finding);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return bySeverity;
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/runner.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { BrowserController } from './browser/controller.js';
|
|
2
|
+
import { VisionAnalyzer } from './vision/analyzer.js';
|
|
3
|
+
import { AgentLoop } from './agent/loop.js';
|
|
4
|
+
import { ReportBuilder } from './report/builder.js';
|
|
5
|
+
import type { QaReport, QaRunnerConfig, QaProgressEvent, QaArtifact } from './types.js';
|
|
6
|
+
import { readFileSync, readdirSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
|
|
9
|
+
export class QaRunner {
|
|
10
|
+
private config: QaRunnerConfig;
|
|
11
|
+
private browserController: BrowserController | null = null;
|
|
12
|
+
private visionAnalyzer: VisionAnalyzer | null = null;
|
|
13
|
+
|
|
14
|
+
constructor(config: QaRunnerConfig) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async run(): Promise<QaReport> {
|
|
19
|
+
const startTime = Date.now();
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Initialize components
|
|
23
|
+
this.emitProgress('step_started', { step: 'initialization', message: 'Initializing QA runner' });
|
|
24
|
+
|
|
25
|
+
this.browserController = new BrowserController({
|
|
26
|
+
headless: this.config.browser.headless,
|
|
27
|
+
viewport: this.config.browser.viewport,
|
|
28
|
+
enableTrace: this.config.browser.enableTrace,
|
|
29
|
+
traceDir: this.config.browser.traceDir,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
this.visionAnalyzer = new VisionAnalyzer(this.config.models.geminiApiKey);
|
|
33
|
+
|
|
34
|
+
await this.browserController.launch();
|
|
35
|
+
|
|
36
|
+
this.emitProgress('step_completed', { step: 'initialization', message: 'Browser launched successfully' });
|
|
37
|
+
|
|
38
|
+
// Run agent loop
|
|
39
|
+
this.emitProgress('step_started', { step: 'execution', message: 'Starting test execution' });
|
|
40
|
+
|
|
41
|
+
const agentLoop = new AgentLoop({
|
|
42
|
+
cerebrasApiKey: this.config.models.cerebrasApiKey,
|
|
43
|
+
browserController: this.browserController,
|
|
44
|
+
visionAnalyzer: this.visionAnalyzer,
|
|
45
|
+
scenarioName: this.config.scenario.name,
|
|
46
|
+
scenarioContent: this.config.scenario.content,
|
|
47
|
+
baseUrl: this.config.baseUrl,
|
|
48
|
+
defectsToCheck: this.config.defectsToCheck || ['Visual overflow', 'Bad UX', 'Poor contrast'],
|
|
49
|
+
maxIterations: this.config.maxIterations,
|
|
50
|
+
onProgress: this.config.onProgress,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const agentResult = await agentLoop.run();
|
|
54
|
+
|
|
55
|
+
this.emitProgress('step_completed', {
|
|
56
|
+
step: 'execution',
|
|
57
|
+
message: 'Test execution completed',
|
|
58
|
+
findings: agentResult.findings.length,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Collect artifacts
|
|
62
|
+
this.emitProgress('step_started', { step: 'artifacts', message: 'Collecting artifacts' });
|
|
63
|
+
|
|
64
|
+
const artifacts: QaArtifact[] = [];
|
|
65
|
+
|
|
66
|
+
// Add all screenshots from browser controller
|
|
67
|
+
const screenshots = this.browserController.getAllScreenshots();
|
|
68
|
+
for (const screenshot of screenshots) {
|
|
69
|
+
artifacts.push({
|
|
70
|
+
type: 'screenshot',
|
|
71
|
+
name: screenshot.name,
|
|
72
|
+
data: screenshot.buffer,
|
|
73
|
+
contentType: 'image/png',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Add trace file if enabled
|
|
78
|
+
if (this.config.browser.enableTrace && this.config.browser.traceDir) {
|
|
79
|
+
try {
|
|
80
|
+
const traceFiles = readdirSync(this.config.browser.traceDir).filter((f) => f.startsWith('trace-'));
|
|
81
|
+
|
|
82
|
+
if (traceFiles.length > 0) {
|
|
83
|
+
// Get the most recent trace file
|
|
84
|
+
const latestTrace = traceFiles.sort().pop()!;
|
|
85
|
+
const tracePath = join(this.config.browser.traceDir, latestTrace);
|
|
86
|
+
const traceBuffer = readFileSync(tracePath);
|
|
87
|
+
|
|
88
|
+
artifacts.push({
|
|
89
|
+
type: 'trace',
|
|
90
|
+
name: latestTrace,
|
|
91
|
+
data: traceBuffer,
|
|
92
|
+
contentType: 'application/zip',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.warn('[QaRunner] Failed to collect trace file:', error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.emitProgress('step_completed', {
|
|
101
|
+
step: 'artifacts',
|
|
102
|
+
message: 'Artifacts collected',
|
|
103
|
+
count: artifacts.length,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Build report
|
|
107
|
+
const duration = Date.now() - startTime;
|
|
108
|
+
|
|
109
|
+
const report = ReportBuilder.buildReport({
|
|
110
|
+
findings: agentResult.findings,
|
|
111
|
+
artifacts,
|
|
112
|
+
metadata: {
|
|
113
|
+
scenarioName: this.config.scenario.name,
|
|
114
|
+
baseUrl: this.config.baseUrl,
|
|
115
|
+
viewport: this.config.browser.viewport,
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
},
|
|
118
|
+
duration,
|
|
119
|
+
summary: agentResult.summary,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return report;
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error('[QaRunner] Error during test execution:', error);
|
|
125
|
+
|
|
126
|
+
// Build error report
|
|
127
|
+
const duration = Date.now() - startTime;
|
|
128
|
+
|
|
129
|
+
return ReportBuilder.buildReport({
|
|
130
|
+
findings: [
|
|
131
|
+
{
|
|
132
|
+
severity: 'critical',
|
|
133
|
+
title: 'Test execution failed',
|
|
134
|
+
description: `Error during test execution: ${error instanceof Error ? error.message : String(error)}`,
|
|
135
|
+
reproSteps: ['Test runner encountered an error'],
|
|
136
|
+
category: 'functional',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
artifacts: [],
|
|
140
|
+
metadata: {
|
|
141
|
+
scenarioName: this.config.scenario.name,
|
|
142
|
+
baseUrl: this.config.baseUrl,
|
|
143
|
+
viewport: this.config.browser.viewport,
|
|
144
|
+
timestamp: new Date().toISOString(),
|
|
145
|
+
},
|
|
146
|
+
duration,
|
|
147
|
+
summary: `Test execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
148
|
+
});
|
|
149
|
+
} finally {
|
|
150
|
+
// Always close browser
|
|
151
|
+
if (this.browserController) {
|
|
152
|
+
await this.browserController.close();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private emitProgress(type: QaProgressEvent['type'], data: Record<string, unknown>) {
|
|
158
|
+
if (this.config.onProgress) {
|
|
159
|
+
this.config.onProgress({
|
|
160
|
+
type,
|
|
161
|
+
data,
|
|
162
|
+
timestamp: Date.now(),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface QaScenario {
|
|
2
|
+
name: string;
|
|
3
|
+
content: string; // Markdown instructions
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface QaFinding {
|
|
7
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
8
|
+
title: string;
|
|
9
|
+
description: string;
|
|
10
|
+
reproSteps: string[];
|
|
11
|
+
expected?: string;
|
|
12
|
+
actual?: string;
|
|
13
|
+
screenshotName?: string;
|
|
14
|
+
category: 'functional' | 'ux' | 'ui' | 'accessibility';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface QaArtifact {
|
|
18
|
+
type: 'screenshot' | 'trace';
|
|
19
|
+
name: string;
|
|
20
|
+
data: Buffer;
|
|
21
|
+
contentType: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface QaReport {
|
|
25
|
+
summary: string;
|
|
26
|
+
status: 'passed' | 'failed' | 'issues_found';
|
|
27
|
+
duration: number;
|
|
28
|
+
findings: QaFinding[];
|
|
29
|
+
artifacts: QaArtifact[];
|
|
30
|
+
metadata: {
|
|
31
|
+
scenarioName: string;
|
|
32
|
+
baseUrl: string;
|
|
33
|
+
viewport: { width: number; height: number };
|
|
34
|
+
timestamp: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface QaProgressEvent {
|
|
39
|
+
type: 'step_started' | 'step_completed' | 'screenshot_captured' | 'finding_discovered' | 'context_usage';
|
|
40
|
+
data: Record<string, unknown>;
|
|
41
|
+
timestamp: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ScreenshotEntry {
|
|
45
|
+
timestamp: number;
|
|
46
|
+
name: string;
|
|
47
|
+
buffer: Buffer;
|
|
48
|
+
context: string;
|
|
49
|
+
url: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface QaRunnerConfig {
|
|
53
|
+
browser: {
|
|
54
|
+
headless?: boolean;
|
|
55
|
+
viewport: { width: number; height: number };
|
|
56
|
+
enableTrace?: boolean;
|
|
57
|
+
traceDir?: string;
|
|
58
|
+
};
|
|
59
|
+
models: {
|
|
60
|
+
cerebrasApiKey: string;
|
|
61
|
+
geminiApiKey: string;
|
|
62
|
+
};
|
|
63
|
+
baseUrl: string;
|
|
64
|
+
scenario: QaScenario;
|
|
65
|
+
defectsToCheck?: string[];
|
|
66
|
+
maxIterations?: number;
|
|
67
|
+
onProgress?: (event: QaProgressEvent) => void;
|
|
68
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
2
|
+
import type { QaFinding } from '../types.js';
|
|
3
|
+
import { buildVisionPrompt } from './prompts.js';
|
|
4
|
+
|
|
5
|
+
export class VisionAnalyzer {
|
|
6
|
+
private genAI: GoogleGenerativeAI;
|
|
7
|
+
private model: any;
|
|
8
|
+
|
|
9
|
+
constructor(apiKey: string) {
|
|
10
|
+
this.genAI = new GoogleGenerativeAI(apiKey);
|
|
11
|
+
this.model = this.genAI.getGenerativeModel({
|
|
12
|
+
model: 'gemini-3-flash-preview',
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async analyzeScreenshot(
|
|
17
|
+
screenshot: Buffer,
|
|
18
|
+
context: string,
|
|
19
|
+
url: string
|
|
20
|
+
): Promise<QaFinding[]> {
|
|
21
|
+
try {
|
|
22
|
+
const base64Image = screenshot.toString('base64');
|
|
23
|
+
const prompt = buildVisionPrompt(context, url);
|
|
24
|
+
|
|
25
|
+
const result = await this.model.generateContent([
|
|
26
|
+
{
|
|
27
|
+
inlineData: {
|
|
28
|
+
mimeType: 'image/png',
|
|
29
|
+
data: base64Image,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{ text: prompt },
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const response = await result.response;
|
|
36
|
+
const text = response.text();
|
|
37
|
+
|
|
38
|
+
// Parse JSON response
|
|
39
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
40
|
+
if (!jsonMatch) {
|
|
41
|
+
console.warn('[VisionAnalyzer] No JSON found in response:', text);
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
46
|
+
const findings = parsed.findings || [];
|
|
47
|
+
|
|
48
|
+
// Convert vision findings to QaFinding format
|
|
49
|
+
return findings.map((finding: any) => ({
|
|
50
|
+
severity: finding.severity || 'medium',
|
|
51
|
+
title: finding.title,
|
|
52
|
+
description: `${finding.description}\n\nSuggestion: ${finding.suggestion || 'N/A'}`,
|
|
53
|
+
reproSteps: [`Screenshot captured at: ${url}`, `Context: ${context}`],
|
|
54
|
+
category: finding.category,
|
|
55
|
+
}));
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error('[VisionAnalyzer] Error analyzing screenshot:', error);
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function buildVisionPrompt(context: string, url: string): string {
|
|
2
|
+
return `You are a ruthless UX/UI critic analyzing a screenshot of a web application.
|
|
3
|
+
|
|
4
|
+
Context: ${context}
|
|
5
|
+
URL: ${url}
|
|
6
|
+
|
|
7
|
+
Analyze this screenshot for:
|
|
8
|
+
- Visual overflow (content cut off, horizontal scroll)
|
|
9
|
+
- Poor color contrast (WCAG AA violations)
|
|
10
|
+
- Bad UX (confusing navigation, hidden CTAs, poor hierarchy)
|
|
11
|
+
- Accessibility issues (missing labels, poor focus indicators)
|
|
12
|
+
|
|
13
|
+
Return findings as JSON array with structure:
|
|
14
|
+
{
|
|
15
|
+
"findings": [
|
|
16
|
+
{
|
|
17
|
+
"category": "ux" | "ui" | "accessibility",
|
|
18
|
+
"severity": "low" | "medium" | "high",
|
|
19
|
+
"title": "Short title",
|
|
20
|
+
"description": "Detailed description",
|
|
21
|
+
"suggestion": "How to fix"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Be concise and specific. Only report actual issues you can clearly see in the screenshot.
|
|
27
|
+
If everything looks good, return an empty findings array.`;
|
|
28
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"outDir": "./dist",
|
|
4
|
+
"rootDir": "./src",
|
|
5
|
+
"composite": true,
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"module": "ESNext",
|
|
10
|
+
"target": "ES2022",
|
|
11
|
+
"lib": ["ES2022", "DOM"],
|
|
12
|
+
"strict": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"resolveJsonModule": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|