@yasserkhanorg/e2e-agents 1.0.1 → 1.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/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +220 -0
- package/dist/cli/parse_args.js +1 -1
- package/dist/cli/types.d.ts +1 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/usage.d.ts.map +1 -1
- package/dist/cli/usage.js +1 -0
- package/dist/cli.js +6 -0
- package/dist/esm/cli/commands/init.js +184 -0
- package/dist/esm/cli/parse_args.js +1 -1
- package/dist/esm/cli/usage.js +1 -0
- package/dist/esm/cli.js +6 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAgIA,wBAAsB,cAAc,CAAC,GAAG,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CA6F/D"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
3
|
+
// See LICENSE.txt for license information.
|
|
4
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
+
}
|
|
10
|
+
Object.defineProperty(o, k2, desc);
|
|
11
|
+
}) : (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
o[k2] = m[k];
|
|
14
|
+
}));
|
|
15
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
16
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
17
|
+
}) : function(o, v) {
|
|
18
|
+
o["default"] = v;
|
|
19
|
+
});
|
|
20
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
21
|
+
var ownKeys = function(o) {
|
|
22
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
23
|
+
var ar = [];
|
|
24
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
25
|
+
return ar;
|
|
26
|
+
};
|
|
27
|
+
return ownKeys(o);
|
|
28
|
+
};
|
|
29
|
+
return function (mod) {
|
|
30
|
+
if (mod && mod.__esModule) return mod;
|
|
31
|
+
var result = {};
|
|
32
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
33
|
+
__setModuleDefault(result, mod);
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
})();
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.runInitCommand = runInitCommand;
|
|
39
|
+
const fs_1 = require("fs");
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const path_1 = require("path");
|
|
42
|
+
const readline = __importStar(require("readline"));
|
|
43
|
+
const CONFIG_FILENAME = 'e2e-ai-agents.config.json';
|
|
44
|
+
function createInterface() {
|
|
45
|
+
return readline.createInterface({
|
|
46
|
+
input: process.stdin,
|
|
47
|
+
output: process.stdout,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function ask(rl, question, defaultValue) {
|
|
51
|
+
const suffix = defaultValue ? ` (${defaultValue})` : '';
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
rl.question(`${question}${suffix}: `, (answer) => {
|
|
54
|
+
resolve(answer.trim() || defaultValue || '');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function detectFramework(appPath) {
|
|
59
|
+
const resolvedPath = (0, path_1.resolve)(appPath);
|
|
60
|
+
const pkgPath = (0, path_1.join)(resolvedPath, 'package.json');
|
|
61
|
+
if (!(0, fs_1.existsSync)(pkgPath)) {
|
|
62
|
+
return 'auto';
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf-8'));
|
|
66
|
+
const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
67
|
+
if (allDeps['@playwright/test'] || allDeps.playwright) {
|
|
68
|
+
return 'playwright';
|
|
69
|
+
}
|
|
70
|
+
if (allDeps.cypress) {
|
|
71
|
+
return 'cypress';
|
|
72
|
+
}
|
|
73
|
+
if (allDeps['selenium-webdriver'] || allDeps.webdriverio) {
|
|
74
|
+
return 'selenium';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// ignore
|
|
79
|
+
}
|
|
80
|
+
return 'auto';
|
|
81
|
+
}
|
|
82
|
+
function detectGitDefaultBranch(appPath) {
|
|
83
|
+
try {
|
|
84
|
+
const result = (0, child_process_1.execFileSync)('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
85
|
+
cwd: (0, path_1.resolve)(appPath),
|
|
86
|
+
encoding: 'utf-8',
|
|
87
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
88
|
+
}).trim();
|
|
89
|
+
return `origin/${result}`;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return 'origin/main';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function detectTestsRoot(appPath) {
|
|
96
|
+
const resolvedPath = (0, path_1.resolve)(appPath);
|
|
97
|
+
const candidates = [
|
|
98
|
+
'e2e-tests/playwright',
|
|
99
|
+
'e2e-tests',
|
|
100
|
+
'e2e',
|
|
101
|
+
'tests/e2e',
|
|
102
|
+
'test/e2e',
|
|
103
|
+
'tests',
|
|
104
|
+
'test',
|
|
105
|
+
];
|
|
106
|
+
for (const candidate of candidates) {
|
|
107
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(resolvedPath, candidate))) {
|
|
108
|
+
return candidate;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
function buildConfig(answers) {
|
|
114
|
+
const config = {
|
|
115
|
+
path: answers.path,
|
|
116
|
+
framework: answers.framework,
|
|
117
|
+
git: { since: answers.gitSince },
|
|
118
|
+
impact: {
|
|
119
|
+
dependencyGraph: { enabled: true, maxDepth: 3 },
|
|
120
|
+
traceability: { enabled: true },
|
|
121
|
+
aiFlow: {
|
|
122
|
+
enabled: answers.enableAi,
|
|
123
|
+
provider: answers.enableAi ? answers.provider : undefined,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
policy: {
|
|
127
|
+
enforcementMode: answers.enforcementMode,
|
|
128
|
+
blockOnActions: ['must-add-tests'],
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
if (answers.testsRoot && answers.testsRoot !== '.' && answers.testsRoot !== answers.path) {
|
|
132
|
+
config.testsRoot = answers.testsRoot;
|
|
133
|
+
}
|
|
134
|
+
return config;
|
|
135
|
+
}
|
|
136
|
+
function printNextSteps() {
|
|
137
|
+
console.log('');
|
|
138
|
+
console.log(' Next steps:');
|
|
139
|
+
console.log(' 1. Set your API key: export ANTHROPIC_API_KEY=sk-ant-...');
|
|
140
|
+
console.log(' 2. Test connectivity: npx e2e-ai-agents llm-health');
|
|
141
|
+
console.log(' 3. Run impact analysis: npx e2e-ai-agents impact');
|
|
142
|
+
console.log(' 4. Add to CI: see examples/github-actions/pr-impact.yml');
|
|
143
|
+
console.log('');
|
|
144
|
+
}
|
|
145
|
+
async function runInitCommand(yes = false) {
|
|
146
|
+
const targetDir = process.cwd();
|
|
147
|
+
const configPath = (0, path_1.join)(targetDir, CONFIG_FILENAME);
|
|
148
|
+
if ((0, fs_1.existsSync)(configPath)) {
|
|
149
|
+
console.error(`${CONFIG_FILENAME} already exists in this directory.`);
|
|
150
|
+
console.error('Remove it first if you want to re-initialize.');
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
// Non-interactive mode: auto-detect everything and write immediately
|
|
154
|
+
if (yes) {
|
|
155
|
+
const appPath = '.';
|
|
156
|
+
const answers = {
|
|
157
|
+
path: appPath,
|
|
158
|
+
testsRoot: detectTestsRoot(appPath) || '.',
|
|
159
|
+
framework: detectFramework(appPath),
|
|
160
|
+
gitSince: detectGitDefaultBranch(appPath),
|
|
161
|
+
provider: 'auto',
|
|
162
|
+
enableAi: true,
|
|
163
|
+
enforcementMode: 'advisory',
|
|
164
|
+
};
|
|
165
|
+
const config = buildConfig(answers);
|
|
166
|
+
const json = JSON.stringify(config, null, 2) + '\n';
|
|
167
|
+
(0, fs_1.writeFileSync)(configPath, json, 'utf-8');
|
|
168
|
+
console.log(`Created ${CONFIG_FILENAME}`);
|
|
169
|
+
printNextSteps();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
console.log('');
|
|
173
|
+
console.log(' e2e-ai-agents init');
|
|
174
|
+
console.log(' ==================');
|
|
175
|
+
console.log('');
|
|
176
|
+
console.log(' This will create an e2e-ai-agents.config.json in the current directory.');
|
|
177
|
+
console.log('');
|
|
178
|
+
const rl = createInterface();
|
|
179
|
+
try {
|
|
180
|
+
const appPath = await ask(rl, ' Path to your web app root', '.');
|
|
181
|
+
const detectedFramework = detectFramework(appPath);
|
|
182
|
+
const framework = await ask(rl, ' Test framework (auto | playwright | cypress | selenium)', detectedFramework);
|
|
183
|
+
const detectedTestsRoot = detectTestsRoot(appPath);
|
|
184
|
+
const testsRoot = await ask(rl, ' Path to tests root (relative to app root, "." if same)', detectedTestsRoot || '.');
|
|
185
|
+
const detectedBranch = detectGitDefaultBranch(appPath);
|
|
186
|
+
const gitSince = await ask(rl, ' Git ref to diff against', detectedBranch);
|
|
187
|
+
const providerAnswer = await ask(rl, ' LLM provider for AI features (anthropic | openai | ollama | auto)', 'auto');
|
|
188
|
+
const enableAi = providerAnswer !== 'none';
|
|
189
|
+
const enforcementMode = await ask(rl, ' Policy enforcement mode (advisory | warn | block)', 'advisory');
|
|
190
|
+
const answers = {
|
|
191
|
+
path: appPath,
|
|
192
|
+
testsRoot,
|
|
193
|
+
framework,
|
|
194
|
+
gitSince,
|
|
195
|
+
provider: providerAnswer,
|
|
196
|
+
enableAi,
|
|
197
|
+
enforcementMode,
|
|
198
|
+
};
|
|
199
|
+
const config = buildConfig(answers);
|
|
200
|
+
const json = JSON.stringify(config, null, 2) + '\n';
|
|
201
|
+
console.log('');
|
|
202
|
+
console.log(' Generated config:');
|
|
203
|
+
console.log('');
|
|
204
|
+
for (const line of json.split('\n')) {
|
|
205
|
+
console.log(` ${line}`);
|
|
206
|
+
}
|
|
207
|
+
const confirm = await ask(rl, ' Write this config? (Y/n)', 'Y');
|
|
208
|
+
if (confirm.toLowerCase() !== 'y' && confirm !== '') {
|
|
209
|
+
console.log(' Aborted.');
|
|
210
|
+
process.exit(0);
|
|
211
|
+
}
|
|
212
|
+
(0, fs_1.writeFileSync)(configPath, json, 'utf-8');
|
|
213
|
+
console.log('');
|
|
214
|
+
console.log(` Created ${CONFIG_FILENAME}`);
|
|
215
|
+
printNextSteps();
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
rl.close();
|
|
219
|
+
}
|
|
220
|
+
}
|
package/dist/cli/parse_args.js
CHANGED
|
@@ -137,7 +137,7 @@ for (const [flag, def] of Object.entries(FLAGS)) {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
const COMMANDS = new Set([
|
|
140
|
-
'impact', 'plan', 'heal', 'suggest', 'generate',
|
|
140
|
+
'init', 'impact', 'plan', 'heal', 'suggest', 'generate',
|
|
141
141
|
'finalize-generated-tests', 'feedback',
|
|
142
142
|
'traceability-capture', 'traceability-ingest',
|
|
143
143
|
'analyze', 'llm-health',
|
package/dist/cli/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AnalysisProfile, FrameworkType } from '../agent/config.js';
|
|
2
|
-
export type Command = 'impact' | 'plan' | 'heal' | 'suggest' | 'generate' | 'finalize-generated-tests' | 'feedback' | 'traceability-capture' | 'traceability-ingest' | 'analyze' | 'llm-health';
|
|
2
|
+
export type Command = 'init' | 'impact' | 'plan' | 'heal' | 'suggest' | 'generate' | 'finalize-generated-tests' | 'feedback' | 'traceability-capture' | 'traceability-ingest' | 'analyze' | 'llm-health';
|
|
3
3
|
export interface ParsedArgs {
|
|
4
4
|
command?: Command;
|
|
5
5
|
configPath?: string;
|
package/dist/cli/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,eAAe,EAAE,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAEvE,MAAM,MAAM,OAAO,GACf,QAAQ,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/usage.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/cli/usage.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,IAAI,IAAI,
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/cli/usage.ts"],"names":[],"mappings":"AAGA,wBAAgB,UAAU,IAAI,IAAI,CAmFjC"}
|
package/dist/cli/usage.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.printUsage = printUsage;
|
|
|
6
6
|
function printUsage() {
|
|
7
7
|
console.log([
|
|
8
8
|
'Usage:',
|
|
9
|
+
' e2e-ai-agents init [--yes]',
|
|
9
10
|
' e2e-ai-agents impact --path <app-root> [options]',
|
|
10
11
|
' e2e-ai-agents plan --path <app-root> [options]',
|
|
11
12
|
' e2e-ai-agents suggest --path <app-root> [options]',
|
package/dist/cli.js
CHANGED
|
@@ -15,9 +15,15 @@ const heal_js_1 = require("./cli/commands/heal.js");
|
|
|
15
15
|
const impact_js_1 = require("./cli/commands/impact.js");
|
|
16
16
|
const plan_js_1 = require("./cli/commands/plan.js");
|
|
17
17
|
const generate_js_1 = require("./cli/commands/generate.js");
|
|
18
|
+
const init_js_1 = require("./cli/commands/init.js");
|
|
18
19
|
async function main() {
|
|
19
20
|
const args = (0, parse_args_js_1.parseArgs)(process.argv.slice(2));
|
|
20
21
|
const autoConfig = (0, parse_args_js_1.resolveAutoConfig)(args);
|
|
22
|
+
if (args.command === 'init') {
|
|
23
|
+
const hasYes = process.argv.includes('--yes') || process.argv.includes('-y');
|
|
24
|
+
await (0, init_js_1.runInitCommand)(hasYes);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
21
27
|
if (args.help || !args.command) {
|
|
22
28
|
(0, usage_js_1.printUsage)();
|
|
23
29
|
process.exit(args.command ? 0 : 1);
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
2
|
+
// See LICENSE.txt for license information.
|
|
3
|
+
import { existsSync, writeFileSync, readFileSync } from 'fs';
|
|
4
|
+
import { execFileSync } from 'child_process';
|
|
5
|
+
import { join, resolve } from 'path';
|
|
6
|
+
import * as readline from 'readline';
|
|
7
|
+
const CONFIG_FILENAME = 'e2e-ai-agents.config.json';
|
|
8
|
+
function createInterface() {
|
|
9
|
+
return readline.createInterface({
|
|
10
|
+
input: process.stdin,
|
|
11
|
+
output: process.stdout,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function ask(rl, question, defaultValue) {
|
|
15
|
+
const suffix = defaultValue ? ` (${defaultValue})` : '';
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
rl.question(`${question}${suffix}: `, (answer) => {
|
|
18
|
+
resolve(answer.trim() || defaultValue || '');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function detectFramework(appPath) {
|
|
23
|
+
const resolvedPath = resolve(appPath);
|
|
24
|
+
const pkgPath = join(resolvedPath, 'package.json');
|
|
25
|
+
if (!existsSync(pkgPath)) {
|
|
26
|
+
return 'auto';
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
30
|
+
const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
31
|
+
if (allDeps['@playwright/test'] || allDeps.playwright) {
|
|
32
|
+
return 'playwright';
|
|
33
|
+
}
|
|
34
|
+
if (allDeps.cypress) {
|
|
35
|
+
return 'cypress';
|
|
36
|
+
}
|
|
37
|
+
if (allDeps['selenium-webdriver'] || allDeps.webdriverio) {
|
|
38
|
+
return 'selenium';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// ignore
|
|
43
|
+
}
|
|
44
|
+
return 'auto';
|
|
45
|
+
}
|
|
46
|
+
function detectGitDefaultBranch(appPath) {
|
|
47
|
+
try {
|
|
48
|
+
const result = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
49
|
+
cwd: resolve(appPath),
|
|
50
|
+
encoding: 'utf-8',
|
|
51
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
52
|
+
}).trim();
|
|
53
|
+
return `origin/${result}`;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return 'origin/main';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function detectTestsRoot(appPath) {
|
|
60
|
+
const resolvedPath = resolve(appPath);
|
|
61
|
+
const candidates = [
|
|
62
|
+
'e2e-tests/playwright',
|
|
63
|
+
'e2e-tests',
|
|
64
|
+
'e2e',
|
|
65
|
+
'tests/e2e',
|
|
66
|
+
'test/e2e',
|
|
67
|
+
'tests',
|
|
68
|
+
'test',
|
|
69
|
+
];
|
|
70
|
+
for (const candidate of candidates) {
|
|
71
|
+
if (existsSync(join(resolvedPath, candidate))) {
|
|
72
|
+
return candidate;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
function buildConfig(answers) {
|
|
78
|
+
const config = {
|
|
79
|
+
path: answers.path,
|
|
80
|
+
framework: answers.framework,
|
|
81
|
+
git: { since: answers.gitSince },
|
|
82
|
+
impact: {
|
|
83
|
+
dependencyGraph: { enabled: true, maxDepth: 3 },
|
|
84
|
+
traceability: { enabled: true },
|
|
85
|
+
aiFlow: {
|
|
86
|
+
enabled: answers.enableAi,
|
|
87
|
+
provider: answers.enableAi ? answers.provider : undefined,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
policy: {
|
|
91
|
+
enforcementMode: answers.enforcementMode,
|
|
92
|
+
blockOnActions: ['must-add-tests'],
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
if (answers.testsRoot && answers.testsRoot !== '.' && answers.testsRoot !== answers.path) {
|
|
96
|
+
config.testsRoot = answers.testsRoot;
|
|
97
|
+
}
|
|
98
|
+
return config;
|
|
99
|
+
}
|
|
100
|
+
function printNextSteps() {
|
|
101
|
+
console.log('');
|
|
102
|
+
console.log(' Next steps:');
|
|
103
|
+
console.log(' 1. Set your API key: export ANTHROPIC_API_KEY=sk-ant-...');
|
|
104
|
+
console.log(' 2. Test connectivity: npx e2e-ai-agents llm-health');
|
|
105
|
+
console.log(' 3. Run impact analysis: npx e2e-ai-agents impact');
|
|
106
|
+
console.log(' 4. Add to CI: see examples/github-actions/pr-impact.yml');
|
|
107
|
+
console.log('');
|
|
108
|
+
}
|
|
109
|
+
export async function runInitCommand(yes = false) {
|
|
110
|
+
const targetDir = process.cwd();
|
|
111
|
+
const configPath = join(targetDir, CONFIG_FILENAME);
|
|
112
|
+
if (existsSync(configPath)) {
|
|
113
|
+
console.error(`${CONFIG_FILENAME} already exists in this directory.`);
|
|
114
|
+
console.error('Remove it first if you want to re-initialize.');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
// Non-interactive mode: auto-detect everything and write immediately
|
|
118
|
+
if (yes) {
|
|
119
|
+
const appPath = '.';
|
|
120
|
+
const answers = {
|
|
121
|
+
path: appPath,
|
|
122
|
+
testsRoot: detectTestsRoot(appPath) || '.',
|
|
123
|
+
framework: detectFramework(appPath),
|
|
124
|
+
gitSince: detectGitDefaultBranch(appPath),
|
|
125
|
+
provider: 'auto',
|
|
126
|
+
enableAi: true,
|
|
127
|
+
enforcementMode: 'advisory',
|
|
128
|
+
};
|
|
129
|
+
const config = buildConfig(answers);
|
|
130
|
+
const json = JSON.stringify(config, null, 2) + '\n';
|
|
131
|
+
writeFileSync(configPath, json, 'utf-8');
|
|
132
|
+
console.log(`Created ${CONFIG_FILENAME}`);
|
|
133
|
+
printNextSteps();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
console.log('');
|
|
137
|
+
console.log(' e2e-ai-agents init');
|
|
138
|
+
console.log(' ==================');
|
|
139
|
+
console.log('');
|
|
140
|
+
console.log(' This will create an e2e-ai-agents.config.json in the current directory.');
|
|
141
|
+
console.log('');
|
|
142
|
+
const rl = createInterface();
|
|
143
|
+
try {
|
|
144
|
+
const appPath = await ask(rl, ' Path to your web app root', '.');
|
|
145
|
+
const detectedFramework = detectFramework(appPath);
|
|
146
|
+
const framework = await ask(rl, ' Test framework (auto | playwright | cypress | selenium)', detectedFramework);
|
|
147
|
+
const detectedTestsRoot = detectTestsRoot(appPath);
|
|
148
|
+
const testsRoot = await ask(rl, ' Path to tests root (relative to app root, "." if same)', detectedTestsRoot || '.');
|
|
149
|
+
const detectedBranch = detectGitDefaultBranch(appPath);
|
|
150
|
+
const gitSince = await ask(rl, ' Git ref to diff against', detectedBranch);
|
|
151
|
+
const providerAnswer = await ask(rl, ' LLM provider for AI features (anthropic | openai | ollama | auto)', 'auto');
|
|
152
|
+
const enableAi = providerAnswer !== 'none';
|
|
153
|
+
const enforcementMode = await ask(rl, ' Policy enforcement mode (advisory | warn | block)', 'advisory');
|
|
154
|
+
const answers = {
|
|
155
|
+
path: appPath,
|
|
156
|
+
testsRoot,
|
|
157
|
+
framework,
|
|
158
|
+
gitSince,
|
|
159
|
+
provider: providerAnswer,
|
|
160
|
+
enableAi,
|
|
161
|
+
enforcementMode,
|
|
162
|
+
};
|
|
163
|
+
const config = buildConfig(answers);
|
|
164
|
+
const json = JSON.stringify(config, null, 2) + '\n';
|
|
165
|
+
console.log('');
|
|
166
|
+
console.log(' Generated config:');
|
|
167
|
+
console.log('');
|
|
168
|
+
for (const line of json.split('\n')) {
|
|
169
|
+
console.log(` ${line}`);
|
|
170
|
+
}
|
|
171
|
+
const confirm = await ask(rl, ' Write this config? (Y/n)', 'Y');
|
|
172
|
+
if (confirm.toLowerCase() !== 'y' && confirm !== '') {
|
|
173
|
+
console.log(' Aborted.');
|
|
174
|
+
process.exit(0);
|
|
175
|
+
}
|
|
176
|
+
writeFileSync(configPath, json, 'utf-8');
|
|
177
|
+
console.log('');
|
|
178
|
+
console.log(` Created ${CONFIG_FILENAME}`);
|
|
179
|
+
printNextSteps();
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
rl.close();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -131,7 +131,7 @@ for (const [flag, def] of Object.entries(FLAGS)) {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
const COMMANDS = new Set([
|
|
134
|
-
'impact', 'plan', 'heal', 'suggest', 'generate',
|
|
134
|
+
'init', 'impact', 'plan', 'heal', 'suggest', 'generate',
|
|
135
135
|
'finalize-generated-tests', 'feedback',
|
|
136
136
|
'traceability-capture', 'traceability-ingest',
|
|
137
137
|
'analyze', 'llm-health',
|
package/dist/esm/cli/usage.js
CHANGED
package/dist/esm/cli.js
CHANGED
|
@@ -13,9 +13,15 @@ import { runHealCommand } from './cli/commands/heal.js';
|
|
|
13
13
|
import { runImpactCommand } from './cli/commands/impact.js';
|
|
14
14
|
import { runPlanCommand } from './cli/commands/plan.js';
|
|
15
15
|
import { runGenerateCommand } from './cli/commands/generate.js';
|
|
16
|
+
import { runInitCommand } from './cli/commands/init.js';
|
|
16
17
|
async function main() {
|
|
17
18
|
const args = parseArgs(process.argv.slice(2));
|
|
18
19
|
const autoConfig = resolveAutoConfig(args);
|
|
20
|
+
if (args.command === 'init') {
|
|
21
|
+
const hasYes = process.argv.includes('--yes') || process.argv.includes('-y');
|
|
22
|
+
await runInitCommand(hasYes);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
19
25
|
if (args.help || !args.command) {
|
|
20
26
|
printUsage();
|
|
21
27
|
process.exit(args.command ? 0 : 1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yasserkhanorg/e2e-agents",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "AI-powered E2E test impact analysis, generation, and healing. Analyzes code changes to identify affected Playwright tests, detects coverage gaps, and generates or repairs specs using pluggable LLM providers (Claude, OpenAI, Ollama). Includes MCP server, traceability, and CI/CD integration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|