@elliemae/encw-leak-runner 1.0.2
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/.eslintrc.cjs +10 -0
- package/.stylelintignore +4 -0
- package/CHANGELOG.md +51 -0
- package/README.md +309 -0
- package/babel.config.cjs +2 -0
- package/bin/leak-runner.ts +9 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/bin/leak-runner.js +792 -0
- package/dist/cjs/analysis/thresholdEvaluator.js +46 -0
- package/dist/cjs/browser/iframeHeapProfiler.js +46 -0
- package/dist/cjs/cli/command.js +16 -0
- package/dist/cjs/cli/commands/listCommand.js +47 -0
- package/dist/cjs/cli/commands/runCommand.js +111 -0
- package/dist/cjs/cli/index.js +42 -0
- package/dist/cjs/config/missingRequiredParamError.js +34 -0
- package/dist/cjs/config/requiredEnvParams.js +57 -0
- package/dist/cjs/config/runnerConfigLoader.js +73 -0
- package/dist/cjs/config/runnerConfigSchema.js +40 -0
- package/dist/cjs/config/sources/cliOverrideConfigSource.js +44 -0
- package/dist/cjs/config/sources/configSource.js +35 -0
- package/dist/cjs/config/sources/envVarConfigSource.js +41 -0
- package/dist/cjs/config/sources/fileConfigSource.js +62 -0
- package/dist/cjs/index.js +52 -0
- package/dist/cjs/package.json +7 -0
- package/dist/cjs/registry/scenarioRegistry.js +51 -0
- package/dist/cjs/reporting/consoleReporter.js +60 -0
- package/dist/cjs/reporting/junitReporter.js +75 -0
- package/dist/cjs/reporting/reporter.js +16 -0
- package/dist/cjs/runner/aiEnhancementStep.js +39 -0
- package/dist/cjs/runner/batchRunner.js +76 -0
- package/dist/cjs/runner/scenarioRunner.js +165 -0
- package/dist/cjs/scenarios/index.js +29 -0
- package/dist/cjs/scenarios/one-admin/export-navigation.scenario.js +50 -0
- package/dist/cjs/scenarios/one-admin/index.js +27 -0
- package/dist/cjs/scenarios/one-admin/page-models/ExportPageModel.js +43 -0
- package/dist/cjs/scenarios/one-admin/page-models/SelectSettingsPageModel.js +47 -0
- package/dist/cjs/scenarios/one-admin/page-models/index.js +26 -0
- package/dist/cjs/types/config.js +27 -0
- package/dist/cjs/types/results.js +16 -0
- package/dist/cjs/types/scenario.js +16 -0
- package/dist/esm/analysis/thresholdEvaluator.js +26 -0
- package/dist/esm/browser/iframeHeapProfiler.js +26 -0
- package/dist/esm/cli/command.js +0 -0
- package/dist/esm/cli/commands/listCommand.js +27 -0
- package/dist/esm/cli/commands/runCommand.js +93 -0
- package/dist/esm/cli/index.js +22 -0
- package/dist/esm/config/missingRequiredParamError.js +14 -0
- package/dist/esm/config/requiredEnvParams.js +37 -0
- package/dist/esm/config/runnerConfigLoader.js +53 -0
- package/dist/esm/config/runnerConfigSchema.js +20 -0
- package/dist/esm/config/sources/cliOverrideConfigSource.js +24 -0
- package/dist/esm/config/sources/configSource.js +15 -0
- package/dist/esm/config/sources/envVarConfigSource.js +21 -0
- package/dist/esm/config/sources/fileConfigSource.js +34 -0
- package/dist/esm/index.js +35 -0
- package/dist/esm/package.json +7 -0
- package/dist/esm/registry/scenarioRegistry.js +31 -0
- package/dist/esm/reporting/consoleReporter.js +40 -0
- package/dist/esm/reporting/junitReporter.js +45 -0
- package/dist/esm/reporting/reporter.js +0 -0
- package/dist/esm/runner/aiEnhancementStep.js +22 -0
- package/dist/esm/runner/batchRunner.js +56 -0
- package/dist/esm/runner/scenarioRunner.js +137 -0
- package/dist/esm/scenarios/index.js +9 -0
- package/dist/esm/scenarios/one-admin/export-navigation.scenario.js +33 -0
- package/dist/esm/scenarios/one-admin/index.js +7 -0
- package/dist/esm/scenarios/one-admin/page-models/ExportPageModel.js +23 -0
- package/dist/esm/scenarios/one-admin/page-models/SelectSettingsPageModel.js +27 -0
- package/dist/esm/scenarios/one-admin/page-models/index.js +6 -0
- package/dist/esm/types/config.js +7 -0
- package/dist/esm/types/results.js +0 -0
- package/dist/esm/types/scenario.js +0 -0
- package/dist/types/bin/leak-runner.d.ts +2 -0
- package/dist/types/lib/analysis/tests/thresholdEvaluator.test.d.ts +1 -0
- package/dist/types/lib/analysis/thresholdEvaluator.d.ts +6 -0
- package/dist/types/lib/browser/iframeHeapProfiler.d.ts +9 -0
- package/dist/types/lib/browser/tests/iframeHeapProfiler.test.d.ts +1 -0
- package/dist/types/lib/cli/command.d.ts +17 -0
- package/dist/types/lib/cli/commands/listCommand.d.ts +5 -0
- package/dist/types/lib/cli/commands/runCommand.d.ts +7 -0
- package/dist/types/lib/cli/index.d.ts +4 -0
- package/dist/types/lib/config/missingRequiredParamError.d.ts +4 -0
- package/dist/types/lib/config/requiredEnvParams.d.ts +16 -0
- package/dist/types/lib/config/runnerConfigLoader.d.ts +13 -0
- package/dist/types/lib/config/runnerConfigSchema.d.ts +78 -0
- package/dist/types/lib/config/sources/cliOverrideConfigSource.d.ts +14 -0
- package/dist/types/lib/config/sources/configSource.d.ts +14 -0
- package/dist/types/lib/config/sources/envVarConfigSource.d.ts +7 -0
- package/dist/types/lib/config/sources/fileConfigSource.d.ts +9 -0
- package/dist/types/lib/config/tests/cliOverrideConfigSource.test.d.ts +1 -0
- package/dist/types/lib/config/tests/envVarConfigSource.test.d.ts +1 -0
- package/dist/types/lib/config/tests/fileConfigSource.test.d.ts +1 -0
- package/dist/types/lib/config/tests/requiredEnvParams.test.d.ts +1 -0
- package/dist/types/lib/config/tests/runnerConfigLoader.test.d.ts +1 -0
- package/dist/types/lib/index.d.ts +18 -0
- package/dist/types/lib/registry/scenarioRegistry.d.ts +18 -0
- package/dist/types/lib/registry/tests/scenarioRegistry.test.d.ts +1 -0
- package/dist/types/lib/reporting/consoleReporter.d.ts +5 -0
- package/dist/types/lib/reporting/junitReporter.d.ts +5 -0
- package/dist/types/lib/reporting/reporter.d.ts +4 -0
- package/dist/types/lib/reporting/tests/consoleReporter.test.d.ts +1 -0
- package/dist/types/lib/reporting/tests/junitReporter.test.d.ts +1 -0
- package/dist/types/lib/runner/aiEnhancementStep.d.ts +15 -0
- package/dist/types/lib/runner/batchRunner.d.ts +14 -0
- package/dist/types/lib/runner/scenarioRunner.d.ts +15 -0
- package/dist/types/lib/runner/tests/aiEnhancementStep.test.d.ts +1 -0
- package/dist/types/lib/runner/tests/batchRunner.test.d.ts +1 -0
- package/dist/types/lib/runner/tests/scenarioRunner.test.d.ts +1 -0
- package/dist/types/lib/scenarios/index.d.ts +2 -0
- package/dist/types/lib/scenarios/one-admin/export-navigation.scenario.d.ts +2 -0
- package/dist/types/lib/scenarios/one-admin/index.d.ts +2 -0
- package/dist/types/lib/scenarios/one-admin/page-models/ExportPageModel.d.ts +8 -0
- package/dist/types/lib/scenarios/one-admin/page-models/SelectSettingsPageModel.d.ts +10 -0
- package/dist/types/lib/scenarios/one-admin/page-models/index.d.ts +2 -0
- package/dist/types/lib/types/config.d.ts +26 -0
- package/dist/types/lib/types/results.d.ts +19 -0
- package/dist/types/lib/types/scenario.d.ts +17 -0
- package/jest.config.cjs +9 -0
- package/leak-runner.config.json +13 -0
- package/leak-runner.schema.json +27 -0
- package/lib/analysis/tests/thresholdEvaluator.test.ts +125 -0
- package/lib/analysis/thresholdEvaluator.ts +36 -0
- package/lib/browser/iframeHeapProfiler.ts +30 -0
- package/lib/browser/tests/iframeHeapProfiler.test.ts +71 -0
- package/lib/cli/command.ts +19 -0
- package/lib/cli/commands/listCommand.ts +36 -0
- package/lib/cli/commands/runCommand.ts +126 -0
- package/lib/cli/index.ts +25 -0
- package/lib/config/missingRequiredParamError.ts +10 -0
- package/lib/config/requiredEnvParams.ts +50 -0
- package/lib/config/runnerConfigLoader.ts +84 -0
- package/lib/config/runnerConfigSchema.ts +27 -0
- package/lib/config/sources/cliOverrideConfigSource.ts +30 -0
- package/lib/config/sources/configSource.ts +27 -0
- package/lib/config/sources/envVarConfigSource.ts +23 -0
- package/lib/config/sources/fileConfigSource.ts +39 -0
- package/lib/config/tests/cliOverrideConfigSource.test.ts +25 -0
- package/lib/config/tests/envVarConfigSource.test.ts +57 -0
- package/lib/config/tests/fileConfigSource.test.ts +49 -0
- package/lib/config/tests/requiredEnvParams.test.ts +113 -0
- package/lib/config/tests/runnerConfigLoader.test.ts +59 -0
- package/lib/index.ts +37 -0
- package/lib/registry/scenarioRegistry.ts +48 -0
- package/lib/registry/tests/scenarioRegistry.test.ts +96 -0
- package/lib/reporting/consoleReporter.ts +48 -0
- package/lib/reporting/junitReporter.ts +62 -0
- package/lib/reporting/reporter.ts +5 -0
- package/lib/reporting/tests/consoleReporter.test.ts +82 -0
- package/lib/reporting/tests/junitReporter.test.ts +103 -0
- package/lib/runner/aiEnhancementStep.ts +39 -0
- package/lib/runner/batchRunner.ts +71 -0
- package/lib/runner/scenarioRunner.ts +189 -0
- package/lib/runner/tests/aiEnhancementStep.test.ts +174 -0
- package/lib/runner/tests/batchRunner.test.ts +133 -0
- package/lib/runner/tests/scenarioRunner.test.ts +162 -0
- package/lib/scenarios/index.ts +8 -0
- package/lib/scenarios/one-admin/export-navigation.scenario.ts +38 -0
- package/lib/scenarios/one-admin/index.ts +6 -0
- package/lib/scenarios/one-admin/page-models/ExportPageModel.ts +26 -0
- package/lib/scenarios/one-admin/page-models/SelectSettingsPageModel.ts +30 -0
- package/lib/scenarios/one-admin/page-models/index.ts +2 -0
- package/lib/types/config.ts +34 -0
- package/lib/types/results.ts +22 -0
- package/lib/types/scenario.ts +18 -0
- package/package.json +46 -0
- package/reports/analysis/index.html +116 -0
- package/reports/analysis/thresholdEvaluator.ts.html +193 -0
- package/reports/base.css +224 -0
- package/reports/block-navigation.js +87 -0
- package/reports/browser/iframeHeapProfiler.ts.html +175 -0
- package/reports/browser/index.html +116 -0
- package/reports/cli/commands/index.html +131 -0
- package/reports/cli/commands/listCommand.ts.html +193 -0
- package/reports/cli/commands/runCommand.ts.html +463 -0
- package/reports/cli/index.html +116 -0
- package/reports/cli/index.ts.html +160 -0
- package/reports/config/index.html +161 -0
- package/reports/config/missingRequiredParamError.ts.html +115 -0
- package/reports/config/requiredEnvParams.ts.html +235 -0
- package/reports/config/runnerConfigLoader.ts.html +337 -0
- package/reports/config/runnerConfigSchema.ts.html +166 -0
- package/reports/config/sources/cliOverrideConfigSource.ts.html +175 -0
- package/reports/config/sources/configSource.ts.html +166 -0
- package/reports/config/sources/envVarConfigSource.ts.html +154 -0
- package/reports/config/sources/fileConfigSource.ts.html +202 -0
- package/reports/config/sources/index.html +161 -0
- package/reports/favicon.png +0 -0
- package/reports/index.html +296 -0
- package/reports/lcov-report/analysis/index.html +116 -0
- package/reports/lcov-report/analysis/thresholdEvaluator.ts.html +193 -0
- package/reports/lcov-report/base.css +224 -0
- package/reports/lcov-report/block-navigation.js +87 -0
- package/reports/lcov-report/browser/iframeHeapProfiler.ts.html +175 -0
- package/reports/lcov-report/browser/index.html +116 -0
- package/reports/lcov-report/cli/commands/index.html +131 -0
- package/reports/lcov-report/cli/commands/listCommand.ts.html +193 -0
- package/reports/lcov-report/cli/commands/runCommand.ts.html +463 -0
- package/reports/lcov-report/cli/index.html +116 -0
- package/reports/lcov-report/cli/index.ts.html +160 -0
- package/reports/lcov-report/config/index.html +161 -0
- package/reports/lcov-report/config/missingRequiredParamError.ts.html +115 -0
- package/reports/lcov-report/config/requiredEnvParams.ts.html +235 -0
- package/reports/lcov-report/config/runnerConfigLoader.ts.html +337 -0
- package/reports/lcov-report/config/runnerConfigSchema.ts.html +166 -0
- package/reports/lcov-report/config/sources/cliOverrideConfigSource.ts.html +175 -0
- package/reports/lcov-report/config/sources/configSource.ts.html +166 -0
- package/reports/lcov-report/config/sources/envVarConfigSource.ts.html +154 -0
- package/reports/lcov-report/config/sources/fileConfigSource.ts.html +202 -0
- package/reports/lcov-report/config/sources/index.html +161 -0
- package/reports/lcov-report/favicon.png +0 -0
- package/reports/lcov-report/index.html +296 -0
- package/reports/lcov-report/prettify.css +1 -0
- package/reports/lcov-report/prettify.js +2 -0
- package/reports/lcov-report/registry/index.html +116 -0
- package/reports/lcov-report/registry/scenarioRegistry.ts.html +229 -0
- package/reports/lcov-report/reporting/consoleReporter.ts.html +229 -0
- package/reports/lcov-report/reporting/index.html +131 -0
- package/reports/lcov-report/reporting/junitReporter.ts.html +271 -0
- package/reports/lcov-report/runner/aiEnhancementStep.ts.html +202 -0
- package/reports/lcov-report/runner/batchRunner.ts.html +298 -0
- package/reports/lcov-report/runner/index.html +146 -0
- package/reports/lcov-report/runner/scenarioRunner.ts.html +652 -0
- package/reports/lcov-report/scenarios/index.html +116 -0
- package/reports/lcov-report/scenarios/index.ts.html +109 -0
- package/reports/lcov-report/scenarios/one-admin/export-navigation.scenario.ts.html +199 -0
- package/reports/lcov-report/scenarios/one-admin/index.html +131 -0
- package/reports/lcov-report/scenarios/one-admin/index.ts.html +103 -0
- package/reports/lcov-report/scenarios/one-admin/page-models/ExportPageModel.ts.html +163 -0
- package/reports/lcov-report/scenarios/one-admin/page-models/SelectSettingsPageModel.ts.html +175 -0
- package/reports/lcov-report/scenarios/one-admin/page-models/index.html +131 -0
- package/reports/lcov-report/sort-arrow-sprite.png +0 -0
- package/reports/lcov-report/sorter.js +210 -0
- package/reports/lcov-report/types/config.ts.html +187 -0
- package/reports/lcov-report/types/index.html +116 -0
- package/reports/lcov.info +883 -0
- package/reports/prettify.css +1 -0
- package/reports/prettify.js +2 -0
- package/reports/registry/index.html +116 -0
- package/reports/registry/scenarioRegistry.ts.html +229 -0
- package/reports/reporting/consoleReporter.ts.html +229 -0
- package/reports/reporting/index.html +131 -0
- package/reports/reporting/junitReporter.ts.html +271 -0
- package/reports/runner/aiEnhancementStep.ts.html +202 -0
- package/reports/runner/batchRunner.ts.html +298 -0
- package/reports/runner/index.html +146 -0
- package/reports/runner/scenarioRunner.ts.html +652 -0
- package/reports/scenarios/index.html +116 -0
- package/reports/scenarios/index.ts.html +109 -0
- package/reports/scenarios/one-admin/export-navigation.scenario.ts.html +199 -0
- package/reports/scenarios/one-admin/index.html +131 -0
- package/reports/scenarios/one-admin/index.ts.html +103 -0
- package/reports/scenarios/one-admin/page-models/ExportPageModel.ts.html +163 -0
- package/reports/scenarios/one-admin/page-models/SelectSettingsPageModel.ts.html +175 -0
- package/reports/scenarios/one-admin/page-models/index.html +131 -0
- package/reports/sort-arrow-sprite.png +0 -0
- package/reports/sorter.js +210 -0
- package/reports/types/config.ts.html +187 -0
- package/reports/types/index.html +116 -0
- package/stylelint.config.cjs +2 -0
- package/test-report.xml +100 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
runnerConfigFileSchema
|
|
4
|
+
} from "../runnerConfigSchema.js";
|
|
5
|
+
class FileConfigSource {
|
|
6
|
+
constructor(filePath) {
|
|
7
|
+
this.filePath = filePath;
|
|
8
|
+
}
|
|
9
|
+
filePath;
|
|
10
|
+
priority = 1;
|
|
11
|
+
name = "file";
|
|
12
|
+
load() {
|
|
13
|
+
if (!fs.existsSync(this.filePath)) return {};
|
|
14
|
+
const raw = fs.readFileSync(this.filePath, "utf8");
|
|
15
|
+
let parsed;
|
|
16
|
+
try {
|
|
17
|
+
parsed = JSON.parse(raw);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Failed to parse config file ${this.filePath}: ${err.message}`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
const result = runnerConfigFileSchema.safeParse(parsed);
|
|
24
|
+
if (!result.success) {
|
|
25
|
+
const issues = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
26
|
+
throw new Error(`Invalid config file ${this.filePath}:
|
|
27
|
+
${issues}`);
|
|
28
|
+
}
|
|
29
|
+
return result.data;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
FileConfigSource
|
|
34
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DEFAULT_THRESHOLDS } from "./types/config.js";
|
|
2
|
+
import { ScenarioRegistry } from "./registry/scenarioRegistry.js";
|
|
3
|
+
import { BatchRunner } from "./runner/batchRunner.js";
|
|
4
|
+
import { ScenarioRunner } from "./runner/scenarioRunner.js";
|
|
5
|
+
import { IframeHeapProfiler } from "./browser/iframeHeapProfiler.js";
|
|
6
|
+
import { ThresholdEvaluator } from "./analysis/thresholdEvaluator.js";
|
|
7
|
+
import { ConsoleReporter } from "./reporting/consoleReporter.js";
|
|
8
|
+
import { JunitReporter } from "./reporting/junitReporter.js";
|
|
9
|
+
import { RunnerConfigLoader } from "./config/runnerConfigLoader.js";
|
|
10
|
+
import { FileConfigSource } from "./config/sources/fileConfigSource.js";
|
|
11
|
+
import { EnvVarConfigSource } from "./config/sources/envVarConfigSource.js";
|
|
12
|
+
import { CliOverrideConfigSource } from "./config/sources/cliOverrideConfigSource.js";
|
|
13
|
+
import {
|
|
14
|
+
RequiredEnvParamsResolver,
|
|
15
|
+
MissingRequiredParamError
|
|
16
|
+
} from "./config/requiredEnvParams.js";
|
|
17
|
+
import { buildProgram, defaultDeps } from "./cli/index.js";
|
|
18
|
+
export {
|
|
19
|
+
BatchRunner,
|
|
20
|
+
CliOverrideConfigSource,
|
|
21
|
+
ConsoleReporter,
|
|
22
|
+
DEFAULT_THRESHOLDS,
|
|
23
|
+
EnvVarConfigSource,
|
|
24
|
+
FileConfigSource,
|
|
25
|
+
IframeHeapProfiler,
|
|
26
|
+
JunitReporter,
|
|
27
|
+
MissingRequiredParamError,
|
|
28
|
+
RequiredEnvParamsResolver,
|
|
29
|
+
RunnerConfigLoader,
|
|
30
|
+
ScenarioRegistry,
|
|
31
|
+
ScenarioRunner,
|
|
32
|
+
ThresholdEvaluator,
|
|
33
|
+
buildProgram,
|
|
34
|
+
defaultDeps
|
|
35
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class ScenarioRegistry {
|
|
2
|
+
entries = /* @__PURE__ */ new Map();
|
|
3
|
+
register(group) {
|
|
4
|
+
group.scenarios.forEach((scenario) => {
|
|
5
|
+
const key = `${group.microapp}/${scenario.id}`;
|
|
6
|
+
if (this.entries.has(key)) {
|
|
7
|
+
throw new Error(`Duplicate scenario: ${key}`);
|
|
8
|
+
}
|
|
9
|
+
this.entries.set(key, scenario);
|
|
10
|
+
});
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
get(key) {
|
|
14
|
+
return this.entries.get(key);
|
|
15
|
+
}
|
|
16
|
+
has(key) {
|
|
17
|
+
return this.entries.has(key);
|
|
18
|
+
}
|
|
19
|
+
size() {
|
|
20
|
+
return this.entries.size;
|
|
21
|
+
}
|
|
22
|
+
list() {
|
|
23
|
+
return Array.from(this.entries.entries()).map(([key, scenario]) => ({ key, scenario })).sort((a, b) => a.key.localeCompare(b.key));
|
|
24
|
+
}
|
|
25
|
+
filterByTag(tag) {
|
|
26
|
+
return this.list().filter((entry) => entry.scenario.tags?.includes(tag));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
ScenarioRegistry
|
|
31
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const GREEN = "\x1B[32m";
|
|
2
|
+
const RED = "\x1B[31m";
|
|
3
|
+
const BOLD = "\x1B[1m";
|
|
4
|
+
const RESET = "\x1B[0m";
|
|
5
|
+
function formatDuration(ms) {
|
|
6
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
7
|
+
}
|
|
8
|
+
function printLine(text) {
|
|
9
|
+
process.stdout.write(`${text}
|
|
10
|
+
`);
|
|
11
|
+
}
|
|
12
|
+
function printScenario(result) {
|
|
13
|
+
const status = result.passed ? `${GREEN}PASSED${RESET}` : `${RED}FAILED${RESET}`;
|
|
14
|
+
const duration = formatDuration(result.durationMs);
|
|
15
|
+
printLine(` ${status} ${BOLD}${result.name}${RESET} (${duration})`);
|
|
16
|
+
if (!result.passed && result.thresholdResult.reason) {
|
|
17
|
+
printLine(` ${RED}\u2192 ${result.thresholdResult.reason}${RESET}`);
|
|
18
|
+
}
|
|
19
|
+
if (!result.passed && result.error) {
|
|
20
|
+
printLine(` ${RED}\u2192 Error: ${result.error.message}${RESET}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
class ConsoleReporter {
|
|
24
|
+
write(summary) {
|
|
25
|
+
printLine("");
|
|
26
|
+
printLine(`${BOLD}MEMORY LEAK RUN RESULTS${RESET}`);
|
|
27
|
+
printLine("\u2500".repeat(50));
|
|
28
|
+
summary.results.forEach(printScenario);
|
|
29
|
+
printLine("\u2500".repeat(50));
|
|
30
|
+
const total = summary.results.length;
|
|
31
|
+
const duration = formatDuration(summary.totalDurationMs);
|
|
32
|
+
printLine(
|
|
33
|
+
`${summary.passCount} passed, ${summary.failCount} failed of ${total} scenarios (${duration})`
|
|
34
|
+
);
|
|
35
|
+
printLine("");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
ConsoleReporter
|
|
40
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function escapeXml(str) {
|
|
4
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
5
|
+
}
|
|
6
|
+
function formatSeconds(ms) {
|
|
7
|
+
return (ms / 1e3).toFixed(1);
|
|
8
|
+
}
|
|
9
|
+
function renderTestCase(result) {
|
|
10
|
+
const timeAttr = `time="${formatSeconds(result.durationMs)}"`;
|
|
11
|
+
const open = ` <testcase name="${escapeXml(
|
|
12
|
+
result.name
|
|
13
|
+
)}" classname="memory-leak" ${timeAttr}>`;
|
|
14
|
+
if (result.passed) {
|
|
15
|
+
return `${open}
|
|
16
|
+
</testcase>`;
|
|
17
|
+
}
|
|
18
|
+
const message = result.thresholdResult.reason ?? result.error?.message ?? "Unknown failure";
|
|
19
|
+
const body = result.thresholdResult.reason ? `See leak-reports/${result.name}.md for full analysis` : result.error?.stack ?? "";
|
|
20
|
+
return `${open}
|
|
21
|
+
<failure message="${escapeXml(message)}">
|
|
22
|
+
${escapeXml(body)}
|
|
23
|
+
</failure>
|
|
24
|
+
</testcase>`;
|
|
25
|
+
}
|
|
26
|
+
class JunitReporter {
|
|
27
|
+
write(summary, outputDir) {
|
|
28
|
+
const totalTime = formatSeconds(summary.totalDurationMs);
|
|
29
|
+
const testCases = summary.results.map(renderTestCase).join("\n");
|
|
30
|
+
const xml = [
|
|
31
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
32
|
+
`<testsuites name="encw-leak-runner" time="${totalTime}">`,
|
|
33
|
+
` <testsuite name="microapp-memory-leaks" tests="${summary.results.length}" failures="${summary.failCount}" time="${totalTime}">`,
|
|
34
|
+
testCases,
|
|
35
|
+
" </testsuite>",
|
|
36
|
+
"</testsuites>"
|
|
37
|
+
].join("\n");
|
|
38
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
39
|
+
fs.writeFileSync(path.join(outputDir, "test-results.xml"), xml, "utf8");
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
JunitReporter
|
|
45
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AiEnhancer,
|
|
3
|
+
renderComparisonMarkdown
|
|
4
|
+
} from "@elliemae/encw-heap-doctor";
|
|
5
|
+
async function enhanceMarkdownIfConfigured(report, config, scenarioName) {
|
|
6
|
+
if (!config.ai?.apiKey) return report.markdown;
|
|
7
|
+
try {
|
|
8
|
+
const enhancer = new AiEnhancer(config.ai);
|
|
9
|
+
await enhancer.enhance(report.afterAnalysis.leakResults);
|
|
10
|
+
return renderComparisonMarkdown(report);
|
|
11
|
+
} catch (err) {
|
|
12
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
13
|
+
process.stderr.write(
|
|
14
|
+
`Genice enhancement failed for ${scenarioName}: ${message}; writing report without AI section.
|
|
15
|
+
`
|
|
16
|
+
);
|
|
17
|
+
return report.markdown;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
enhanceMarkdownIfConfigured
|
|
22
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ScenarioRunner } from "./scenarioRunner.js";
|
|
2
|
+
function buildSummary(results) {
|
|
3
|
+
return {
|
|
4
|
+
results,
|
|
5
|
+
totalDurationMs: results.reduce((acc, r) => acc + r.durationMs, 0),
|
|
6
|
+
passCount: results.filter((r) => r.passed).length,
|
|
7
|
+
failCount: results.filter((r) => !r.passed).length
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
class BatchRunner {
|
|
11
|
+
constructor(config, registry) {
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.registry = registry;
|
|
14
|
+
this.scenarioRunner = new ScenarioRunner(config);
|
|
15
|
+
}
|
|
16
|
+
config;
|
|
17
|
+
registry;
|
|
18
|
+
scenarioRunner;
|
|
19
|
+
async runAll() {
|
|
20
|
+
const scenarios = this.registry.list().map((entry) => entry.scenario);
|
|
21
|
+
return this.executeScenarios(scenarios);
|
|
22
|
+
}
|
|
23
|
+
async runByTag(tag) {
|
|
24
|
+
const scenarios = this.registry.filterByTag(tag).map((entry) => entry.scenario);
|
|
25
|
+
return this.executeScenarios(scenarios);
|
|
26
|
+
}
|
|
27
|
+
async runByName(key) {
|
|
28
|
+
const scenario = this.registry.get(key);
|
|
29
|
+
if (!scenario) {
|
|
30
|
+
throw new Error(`No scenario registered with key "${key}"`);
|
|
31
|
+
}
|
|
32
|
+
return this.executeScenarios([scenario]);
|
|
33
|
+
}
|
|
34
|
+
async executeScenarios(scenarios) {
|
|
35
|
+
const results = await scenarios.reduce(
|
|
36
|
+
async (acc, scenario) => {
|
|
37
|
+
const prior = await acc;
|
|
38
|
+
const result = await this.runOne(scenario);
|
|
39
|
+
return [...prior, result];
|
|
40
|
+
},
|
|
41
|
+
Promise.resolve([])
|
|
42
|
+
);
|
|
43
|
+
return buildSummary(results);
|
|
44
|
+
}
|
|
45
|
+
async runOne(scenario) {
|
|
46
|
+
const browser = await this.scenarioRunner.launchBrowser();
|
|
47
|
+
try {
|
|
48
|
+
return await this.scenarioRunner.run(scenario, browser);
|
|
49
|
+
} finally {
|
|
50
|
+
await browser.close();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
BatchRunner
|
|
56
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
chromium
|
|
5
|
+
} from "@playwright/test";
|
|
6
|
+
import { AuthManager, PageSetup } from "@elliemae/smoked-suite";
|
|
7
|
+
import { IframeHeapProfiler } from "../browser/iframeHeapProfiler.js";
|
|
8
|
+
import { ThresholdEvaluator } from "../analysis/thresholdEvaluator.js";
|
|
9
|
+
import { DEFAULT_THRESHOLDS } from "../types/config.js";
|
|
10
|
+
import { enhanceMarkdownIfConfigured } from "./aiEnhancementStep.js";
|
|
11
|
+
async function resolveIframe(page, selector) {
|
|
12
|
+
const handle = await page.locator(selector).elementHandle();
|
|
13
|
+
if (!handle) throw new Error(`Iframe not found for selector: ${selector}`);
|
|
14
|
+
const frame = await handle.contentFrame();
|
|
15
|
+
if (!frame)
|
|
16
|
+
throw new Error(`Could not get content frame for selector: ${selector}`);
|
|
17
|
+
return frame;
|
|
18
|
+
}
|
|
19
|
+
async function forceGarbageCollection(page) {
|
|
20
|
+
const session = await page.context().newCDPSession(page);
|
|
21
|
+
await session.send("HeapProfiler.enable");
|
|
22
|
+
await session.send("HeapProfiler.collectGarbage");
|
|
23
|
+
await session.send("HeapProfiler.disable");
|
|
24
|
+
await session.detach();
|
|
25
|
+
await page.waitForTimeout(2e3);
|
|
26
|
+
}
|
|
27
|
+
function cleanupSnapshots(paths) {
|
|
28
|
+
if (paths.before && fs.existsSync(paths.before)) fs.unlinkSync(paths.before);
|
|
29
|
+
if (paths.after && fs.existsSync(paths.after)) fs.unlinkSync(paths.after);
|
|
30
|
+
}
|
|
31
|
+
class ScenarioRunner {
|
|
32
|
+
constructor(config) {
|
|
33
|
+
this.config = config;
|
|
34
|
+
}
|
|
35
|
+
config;
|
|
36
|
+
async launchBrowser() {
|
|
37
|
+
return chromium.launch({ headless: this.config.runner.headless });
|
|
38
|
+
}
|
|
39
|
+
async run(scenario, browser) {
|
|
40
|
+
const startTime = Date.now();
|
|
41
|
+
const context = await browser.newContext({
|
|
42
|
+
baseURL: this.config.env.baseUrl
|
|
43
|
+
});
|
|
44
|
+
const page = await context.newPage();
|
|
45
|
+
const paths = { before: "", after: "" };
|
|
46
|
+
let report = null;
|
|
47
|
+
let error = null;
|
|
48
|
+
try {
|
|
49
|
+
report = await this.runScenarioInPage(page, scenario, paths);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
error = err instanceof Error ? err : new Error(String(err));
|
|
52
|
+
} finally {
|
|
53
|
+
await context.close();
|
|
54
|
+
cleanupSnapshots(paths);
|
|
55
|
+
}
|
|
56
|
+
return this.buildResult({ scenario, report, error, startTime });
|
|
57
|
+
}
|
|
58
|
+
async runScenarioInPage(page, scenario, paths) {
|
|
59
|
+
const snapshotsDir = path.join(this.config.runner.outputDir, "snapshots");
|
|
60
|
+
const { profiler, frame } = await this.setupAndProfile(
|
|
61
|
+
page,
|
|
62
|
+
scenario,
|
|
63
|
+
snapshotsDir
|
|
64
|
+
);
|
|
65
|
+
paths.before = await profiler.captureSnapshot("before");
|
|
66
|
+
await this.repeatScenarioActions(scenario, page, frame);
|
|
67
|
+
await forceGarbageCollection(page);
|
|
68
|
+
paths.after = await profiler.captureSnapshot("after");
|
|
69
|
+
const report = await profiler.compare(
|
|
70
|
+
"before",
|
|
71
|
+
"after",
|
|
72
|
+
this.config.runner.topN
|
|
73
|
+
);
|
|
74
|
+
if (report) await this.writeReport(report, scenario);
|
|
75
|
+
return report;
|
|
76
|
+
}
|
|
77
|
+
async setupAndProfile(page, scenario, snapshotsDir) {
|
|
78
|
+
const auth = new AuthManager();
|
|
79
|
+
const pageSetup = new PageSetup();
|
|
80
|
+
await pageSetup.apply(page);
|
|
81
|
+
await auth.login(page, {
|
|
82
|
+
username: this.config.env.userId,
|
|
83
|
+
password: this.config.env.password,
|
|
84
|
+
instanceId: this.config.env.instanceId
|
|
85
|
+
});
|
|
86
|
+
await page.goto(scenario.url());
|
|
87
|
+
const frame = await resolveIframe(page, scenario.microappSelector);
|
|
88
|
+
fs.mkdirSync(snapshotsDir, { recursive: true });
|
|
89
|
+
const profiler = new IframeHeapProfiler(page, frame, snapshotsDir);
|
|
90
|
+
return { profiler, frame };
|
|
91
|
+
}
|
|
92
|
+
async repeatScenarioActions(scenario, page, frame) {
|
|
93
|
+
const repeatCount = scenario.repeat?.() ?? 3;
|
|
94
|
+
for (let i = 0; i < repeatCount; i += 1) {
|
|
95
|
+
await scenario.action(page, frame);
|
|
96
|
+
if (scenario.back) {
|
|
97
|
+
await scenario.back(page);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async writeReport(report, scenario) {
|
|
102
|
+
const reportPath = path.join(
|
|
103
|
+
this.config.runner.outputDir,
|
|
104
|
+
`${scenario.id}.md`
|
|
105
|
+
);
|
|
106
|
+
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
|
107
|
+
const markdown = await enhanceMarkdownIfConfigured(
|
|
108
|
+
report,
|
|
109
|
+
this.config,
|
|
110
|
+
scenario.name
|
|
111
|
+
);
|
|
112
|
+
fs.writeFileSync(reportPath, markdown);
|
|
113
|
+
}
|
|
114
|
+
buildResult(input) {
|
|
115
|
+
const { scenario, report, error, startTime } = input;
|
|
116
|
+
const thresholds = {
|
|
117
|
+
...DEFAULT_THRESHOLDS,
|
|
118
|
+
...scenario.thresholds
|
|
119
|
+
};
|
|
120
|
+
const failureFallback = {
|
|
121
|
+
passed: false,
|
|
122
|
+
reason: error?.message ?? "Snapshot capture failed"
|
|
123
|
+
};
|
|
124
|
+
const thresholdResult = report && !error ? ThresholdEvaluator.evaluate(report, thresholds) : failureFallback;
|
|
125
|
+
return {
|
|
126
|
+
name: scenario.name,
|
|
127
|
+
passed: thresholdResult.passed,
|
|
128
|
+
thresholdResult,
|
|
129
|
+
report,
|
|
130
|
+
durationMs: Date.now() - startTime,
|
|
131
|
+
error
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
ScenarioRunner
|
|
137
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ScenarioRegistry } from "../registry/scenarioRegistry.js";
|
|
2
|
+
import { oneAdminScenarios } from "./one-admin/index.js";
|
|
3
|
+
const scenarioRegistry = new ScenarioRegistry().register({
|
|
4
|
+
microapp: "one-admin",
|
|
5
|
+
scenarios: oneAdminScenarios
|
|
6
|
+
});
|
|
7
|
+
export {
|
|
8
|
+
scenarioRegistry
|
|
9
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SelectSettingsPageModel,
|
|
3
|
+
ExportPageModel
|
|
4
|
+
} from "./page-models/index.js";
|
|
5
|
+
const exportNavigationScenario = {
|
|
6
|
+
id: "export-navigation",
|
|
7
|
+
name: "Export Navigation",
|
|
8
|
+
description: "Navigate to export page and come back - verify iframe GC",
|
|
9
|
+
tags: ["critical"],
|
|
10
|
+
microappSelector: "iframe#pui-iframe-container-emAdminUI",
|
|
11
|
+
url: () => "/admin/oneadmin/migrate",
|
|
12
|
+
async action(page, frame) {
|
|
13
|
+
const settings = new SelectSettingsPageModel(frame);
|
|
14
|
+
await settings.container.waitFor({ state: "visible" });
|
|
15
|
+
await settings.expandTreeItem("eFolder");
|
|
16
|
+
await settings.selectTreeItem("Enhanced Conditions");
|
|
17
|
+
await settings.clickExport();
|
|
18
|
+
const exportPage = new ExportPageModel(frame);
|
|
19
|
+
await exportPage.sourceDataTab.waitFor({ state: "visible" });
|
|
20
|
+
await exportPage.dataTable.waitFor({ state: "visible" });
|
|
21
|
+
},
|
|
22
|
+
async back(page) {
|
|
23
|
+
await ExportPageModel.clickBackAndWaitForSettings(page);
|
|
24
|
+
},
|
|
25
|
+
repeat: () => 3,
|
|
26
|
+
thresholds: {
|
|
27
|
+
maxRetainedSizeDeltaBytes: 10 * 1024 * 1024,
|
|
28
|
+
maxNewLeakGroups: 0
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
export {
|
|
32
|
+
exportNavigationScenario
|
|
33
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const IFRAME_SELECTOR = "iframe#pui-iframe-container-emAdminUI";
|
|
2
|
+
const SETTINGS_URL_PATTERN = /\/admin\/oneadmin\/migrate(?:\/)?$/;
|
|
3
|
+
class ExportPageModel {
|
|
4
|
+
constructor(frame) {
|
|
5
|
+
this.frame = frame;
|
|
6
|
+
}
|
|
7
|
+
frame;
|
|
8
|
+
get sourceDataTab() {
|
|
9
|
+
return this.frame.getByRole("tab", { name: /Source Data/i });
|
|
10
|
+
}
|
|
11
|
+
get dataTable() {
|
|
12
|
+
return this.frame.locator('[role="table"]').first();
|
|
13
|
+
}
|
|
14
|
+
static async clickBackAndWaitForSettings(page) {
|
|
15
|
+
await Promise.all([
|
|
16
|
+
page.waitForURL(SETTINGS_URL_PATTERN),
|
|
17
|
+
page.frameLocator(IFRAME_SELECTOR).getByRole("button", { name: /back/i }).click()
|
|
18
|
+
]);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
ExportPageModel
|
|
23
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class SelectSettingsPageModel {
|
|
2
|
+
constructor(frame) {
|
|
3
|
+
this.frame = frame;
|
|
4
|
+
}
|
|
5
|
+
frame;
|
|
6
|
+
get container() {
|
|
7
|
+
return this.frame.locator("#one-admin-console-container");
|
|
8
|
+
}
|
|
9
|
+
get exportButton() {
|
|
10
|
+
return this.frame.getByRole("button", { name: "Export" });
|
|
11
|
+
}
|
|
12
|
+
async expandTreeItem(name) {
|
|
13
|
+
const treeItem = this.frame.locator(
|
|
14
|
+
`[role="treeitem"]:has-text("${name}")`
|
|
15
|
+
);
|
|
16
|
+
await treeItem.locator('button[aria-label*="expand"], button[aria-label*="toggle"]').click();
|
|
17
|
+
}
|
|
18
|
+
async selectTreeItem(name) {
|
|
19
|
+
await this.frame.locator(`[role="treeitem"]:has-text("${name}")`).click();
|
|
20
|
+
}
|
|
21
|
+
async clickExport() {
|
|
22
|
+
await this.exportButton.click();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
SelectSettingsPageModel
|
|
27
|
+
};
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ComparisonReport } from '@elliemae/encw-heap-doctor';
|
|
2
|
+
import type { ResolvedThresholds } from '../types/config.js';
|
|
3
|
+
import type { ThresholdResult } from '../types/results.js';
|
|
4
|
+
export declare class ThresholdEvaluator {
|
|
5
|
+
static evaluate(report: ComparisonReport, thresholds: ResolvedThresholds): ThresholdResult;
|
|
6
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { HeapMemoryProfiler } from '@elliemae/smoked-suite';
|
|
2
|
+
import type { Page, Frame } from '@playwright/test';
|
|
3
|
+
export declare class IframeHeapProfiler extends HeapMemoryProfiler {
|
|
4
|
+
private readonly profiledPage;
|
|
5
|
+
private readonly frame;
|
|
6
|
+
constructor(profiledPage: Page, frame: Frame, outputDir: string);
|
|
7
|
+
protected getCDPTarget(): Page | Frame;
|
|
8
|
+
private isSameOriginAsPage;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { ScenarioRegistry } from '../registry/scenarioRegistry.js';
|
|
3
|
+
import type { RequiredEnvParamsResolver } from '../config/requiredEnvParams.js';
|
|
4
|
+
/**
|
|
5
|
+
* Dependencies a CliCommand needs from the surrounding wiring.
|
|
6
|
+
*
|
|
7
|
+
* `RunnerConfigLoader` is intentionally NOT here — each command invocation
|
|
8
|
+
* builds its own loader so it can layer in command-line flags (which are only
|
|
9
|
+
* known per-invocation).
|
|
10
|
+
*/
|
|
11
|
+
export interface CliDependencies {
|
|
12
|
+
readonly registry: ScenarioRegistry;
|
|
13
|
+
readonly envParams: RequiredEnvParamsResolver;
|
|
14
|
+
}
|
|
15
|
+
export interface CliCommand {
|
|
16
|
+
register(program: Command, deps: CliDependencies): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { CliCommand, CliDependencies } from '../command.js';
|
|
3
|
+
export declare class RunCommand implements CliCommand {
|
|
4
|
+
register(program: Command, deps: CliDependencies): void;
|
|
5
|
+
private buildConfig;
|
|
6
|
+
private selectSummary;
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EnvironmentParams } from '../types/config.js';
|
|
2
|
+
import { MissingRequiredParamError } from './missingRequiredParamError.js';
|
|
3
|
+
export interface CliEnvOpts {
|
|
4
|
+
readonly baseUrl?: string;
|
|
5
|
+
readonly instanceId?: string;
|
|
6
|
+
readonly userId?: string;
|
|
7
|
+
}
|
|
8
|
+
export { MissingRequiredParamError };
|
|
9
|
+
export declare class RequiredEnvParamsResolver {
|
|
10
|
+
resolve(cliOpts: CliEnvOpts): EnvironmentParams;
|
|
11
|
+
private resolveBaseUrl;
|
|
12
|
+
private resolveInstanceId;
|
|
13
|
+
private resolveUserId;
|
|
14
|
+
private resolvePassword;
|
|
15
|
+
private collectMissing;
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RunnerOptions } from '../types/config.js';
|
|
2
|
+
import type { ConfigSource } from './sources/configSource.js';
|
|
3
|
+
export interface ResolvedRunnerConfigPartial {
|
|
4
|
+
readonly runner: RunnerOptions;
|
|
5
|
+
readonly aiEnabled: boolean;
|
|
6
|
+
readonly aiModel: string;
|
|
7
|
+
readonly aiTemperature: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class RunnerConfigLoader {
|
|
10
|
+
private readonly sources;
|
|
11
|
+
constructor(sources: readonly ConfigSource[]);
|
|
12
|
+
resolveOptions(): ResolvedRunnerConfigPartial;
|
|
13
|
+
}
|