gen-testid 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { GenTestId } from '../core/GenTestId.js';
4
+ import { initCommand } from './init.js';
5
+ import * as path from 'path';
6
+ import * as fs from 'fs';
7
+ const program = new Command();
8
+ program
9
+ .name('gen-testid')
10
+ .description('Автоматическая простановка data-testid в Vue проектах')
11
+ .version('1.0.1');
12
+ program
13
+ .command('init')
14
+ .description('Инициализация конфигурации')
15
+ .action(initCommand);
16
+ program
17
+ .command('inject [files...]')
18
+ .description('Проставить testid в указанные файлы')
19
+ .option('-c, --config <path>', 'путь к конфигурации')
20
+ .option('-s, --strategy <name>', 'стратегия именования')
21
+ .option('-p, --prefix <prefix>', 'префикс для testid')
22
+ .option('-d, --dry-run', 'пробный запуск без изменений')
23
+ .action(async (files, options) => {
24
+ console.log('🔍 Инжекция testid...\n');
25
+ try {
26
+ const config = loadConfig(options.config);
27
+ const genTestId = new GenTestId({
28
+ ...config,
29
+ strategy: options.strategy || config.strategy,
30
+ prefix: options.prefix || config.prefix
31
+ });
32
+ if (files.length === 0) {
33
+ console.log('📁 Сканирование всех Vue файлов...');
34
+ const results = await genTestId.processAllFiles();
35
+ printResults(results, options.dryRun);
36
+ }
37
+ else {
38
+ console.log(`📁 Обработка ${files.length} файлов...`);
39
+ const results = [];
40
+ for (const file of files) {
41
+ const fullPath = path.resolve(process.cwd(), file);
42
+ const result = await genTestId.processFile(fullPath);
43
+ results.push(result);
44
+ }
45
+ printResults(results, options.dryRun);
46
+ }
47
+ }
48
+ catch (error) {
49
+ console.error('\n❌ Ошибка:', error);
50
+ process.exit(1);
51
+ }
52
+ });
53
+ program.parse(process.argv);
54
+ function loadConfig(configPath) {
55
+ if (configPath) {
56
+ const fullPath = path.resolve(process.cwd(), configPath);
57
+ if (fs.existsSync(fullPath)) {
58
+ return JSON.parse(fs.readFileSync(fullPath, 'utf-8'));
59
+ }
60
+ }
61
+ const rcPath = path.resolve(process.cwd(), '.gentestidrc');
62
+ if (fs.existsSync(rcPath)) {
63
+ return JSON.parse(fs.readFileSync(rcPath, 'utf-8'));
64
+ }
65
+ return {};
66
+ }
67
+ function printResults(results, dryRun) {
68
+ const total = results.reduce((sum, r) => sum + r.injectedCount, 0);
69
+ const filesWithChanges = results.filter(r => r.injectedCount > 0).length;
70
+ console.log(`\n${dryRun ? '📋' : '✅'} Результаты:`);
71
+ console.log(` Всего файлов: ${results.length}`);
72
+ console.log(` Файлов с изменениями: ${filesWithChanges}`);
73
+ console.log(` ${dryRun ? 'Будет добавлено' : 'Добавлено'} testid: ${total}`);
74
+ if (total > 0) {
75
+ console.log('\n📝 Детали:');
76
+ results
77
+ .filter(r => r.injectedCount > 0)
78
+ .slice(0, 5)
79
+ .forEach(r => {
80
+ const relativePath = path.relative(process.cwd(), r.filePath);
81
+ console.log(` 📁 ${relativePath}: +${r.injectedCount} testid`);
82
+ });
83
+ if (results.filter(r => r.injectedCount > 0).length > 5) {
84
+ console.log(` ... и ещё ${results.filter(r => r.injectedCount > 0).length - 5} файлов`);
85
+ }
86
+ }
87
+ }
@@ -0,0 +1 @@
1
+ export declare function initCommand(): Promise<void>;
@@ -0,0 +1,95 @@
1
+ // src/cli/init.ts
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ import * as readline from 'readline';
5
+ export async function initCommand() {
6
+ console.log('🚀 Инициализация gen-testid для Vue...\n');
7
+ try {
8
+ console.log('📋 Доступные стратегии:');
9
+ console.log(' 1. hierarchical (default) - иерархические ID (test__Component__elem-1)');
10
+ console.log(' 2. stable-uuid - стабильные UUID');
11
+ console.log(' 3. minimal - простые номера');
12
+ console.log(' 4. semantic - умные ID через Ollama (бесплатно, локально)\n');
13
+ const strategyChoice = await askQuestion('Выберите стратегию (1-4) [1]: ');
14
+ let strategy = 'hierarchical';
15
+ let semanticModel = '';
16
+ let ollamaUrl = '';
17
+ switch (strategyChoice) {
18
+ case '2':
19
+ strategy = 'stable-uuid';
20
+ break;
21
+ case '3':
22
+ strategy = 'minimal';
23
+ break;
24
+ case '4':
25
+ strategy = 'semantic';
26
+ console.log('\n🤖 Настройка семантической генерации через Ollama:');
27
+ console.log(' Требуется Ollama');
28
+ console.log(' Установка: brew install ollama && ollama pull llama3.2:3b');
29
+ console.log(' Запуск: ollama serve\n');
30
+ const model = await askQuestion('Модель (llama3.2:3b/mistral/gemma:2b) [llama3.2:3b]: ');
31
+ semanticModel = model || 'llama3.2:3b';
32
+ const url = await askQuestion('URL Ollama [http://localhost:11434]: ');
33
+ ollamaUrl = url || 'http://localhost:11434';
34
+ console.log('\n💡 Совет: Запустите "ollama serve" в отдельном терминале');
35
+ break;
36
+ default:
37
+ strategy = 'hierarchical';
38
+ }
39
+ const prefix = await askQuestion('Префикс для testid [default: test]: ');
40
+ const config = {
41
+ strategy,
42
+ prefix: prefix || 'test',
43
+ semanticModel: semanticModel,
44
+ ollamaUrl: ollamaUrl,
45
+ includeFiles: ['src/**/*.vue'],
46
+ excludeFiles: ['**/*.test.*', '**/node_modules/**', '**/dist/**']
47
+ };
48
+ const configPath = path.join(process.cwd(), '.gentestidrc');
49
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2));
50
+ console.log(`\n✅ Конфигурация сохранена в ${configPath}`);
51
+ if (strategy === 'semantic') {
52
+ console.log('\n🤖 Семантическая генерация активирована!');
53
+ console.log(' При первом запуске ID будут иерархическими,');
54
+ console.log(' а Ollama в фоне будет генерировать умные ID.');
55
+ }
56
+ await updatePackageJson();
57
+ console.log('\n📝 Следующие шаги:');
58
+ console.log(' 1. Запустите Ollama: ollama serve');
59
+ console.log(' 2. Запустите "npx gen-testid inject" для простановки testid');
60
+ console.log(' 3. Готово! 🎉\n');
61
+ }
62
+ catch (error) {
63
+ console.error('❌ Ошибка при инициализации:', error);
64
+ process.exit(1);
65
+ }
66
+ }
67
+ async function updatePackageJson() {
68
+ try {
69
+ const pkgPath = path.join(process.cwd(), 'package.json');
70
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
71
+ if (!pkg.scripts) {
72
+ pkg.scripts = {};
73
+ }
74
+ if (!pkg.scripts['testids']) {
75
+ pkg.scripts['testids'] = 'gen-testid inject';
76
+ }
77
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2));
78
+ console.log('✅ Скрипты добавлены в package.json');
79
+ }
80
+ catch (error) {
81
+ console.log('⚠️ Не удалось обновить package.json');
82
+ }
83
+ }
84
+ function askQuestion(question) {
85
+ const rl = readline.createInterface({
86
+ input: process.stdin,
87
+ output: process.stdout
88
+ });
89
+ return new Promise(resolve => {
90
+ rl.question(question, answer => {
91
+ rl.close();
92
+ resolve(answer);
93
+ });
94
+ });
95
+ }
@@ -0,0 +1,12 @@
1
+ import { GenTestIdConfig, ElementContext, InjectionResult } from './types.js';
2
+ export declare class GenTestId {
3
+ config: Required<GenTestIdConfig>;
4
+ private strategy;
5
+ private counter;
6
+ constructor(config?: GenTestIdConfig);
7
+ private initStrategy;
8
+ generateTestId(context: Omit<ElementContext, 'occurrence'>): string;
9
+ processFile(filePath: string): Promise<InjectionResult>;
10
+ private shouldExcludeFile;
11
+ processAllFiles(): Promise<InjectionResult[]>;
12
+ }
@@ -0,0 +1,127 @@
1
+ // src/core/GenTestId.ts
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+ import fg from 'fast-glob';
5
+ import { HierarchicalStrategy, StableUuidStrategy, MinimalStrategy, SemanticStrategy } from './strategies.js';
6
+ import { ElementCounter } from '../shared/element-counter.js';
7
+ import { parseVueFile } from '../shared/parser.js';
8
+ export class GenTestId {
9
+ constructor(config = {}) {
10
+ this.config = {
11
+ prefix: 'test',
12
+ strategy: 'hierarchical',
13
+ semanticModel: '',
14
+ ollamaUrl: 'http://localhost:11434',
15
+ excludeFiles: ['**/*.test.*', '**/*.spec.*', '**/node_modules/**', '**/dist/**'],
16
+ includeFiles: ['src/**/*.vue'],
17
+ ...config
18
+ };
19
+ this.counter = new ElementCounter();
20
+ this.strategy = this.initStrategy();
21
+ }
22
+ initStrategy() {
23
+ switch (this.config.strategy) {
24
+ case 'hierarchical':
25
+ return new HierarchicalStrategy(this.config.prefix);
26
+ case 'stable-uuid':
27
+ return new StableUuidStrategy(this.config.prefix);
28
+ case 'minimal':
29
+ return new MinimalStrategy(this.config.prefix);
30
+ case 'semantic':
31
+ return new SemanticStrategy(this.config.prefix, this.config.semanticModel, this.config.ollamaUrl);
32
+ default:
33
+ return new HierarchicalStrategy(this.config.prefix);
34
+ }
35
+ }
36
+ generateTestId(context) {
37
+ const key = `${context.filePath}:${context.componentName}:${context.lineNumber || 'unknown'}`;
38
+ const occurrence = this.counter.increment(key);
39
+ const fullContext = {
40
+ ...context,
41
+ occurrence,
42
+ lineNumber: context.lineNumber || 0
43
+ };
44
+ return this.strategy.generateId(fullContext);
45
+ }
46
+ async processFile(filePath) {
47
+ try {
48
+ if (this.shouldExcludeFile(filePath)) {
49
+ return { filePath, injectedCount: 0, elements: [] };
50
+ }
51
+ let content = await fs.readFile(filePath, 'utf-8');
52
+ //Добавляем testid
53
+ let parseResult = {
54
+ injectedCount: 0,
55
+ modifiedContent: content,
56
+ elements: []
57
+ };
58
+ if (filePath.endsWith('.vue')) {
59
+ parseResult = await parseVueFile(content, filePath, this);
60
+ if (parseResult.injectedCount > 0) {
61
+ content = parseResult.modifiedContent;
62
+ }
63
+ }
64
+ //Save
65
+ if (parseResult.injectedCount > 0) {
66
+ await fs.writeFile(filePath, content, 'utf-8');
67
+ }
68
+ return {
69
+ filePath,
70
+ injectedCount: parseResult.injectedCount,
71
+ elements: parseResult.elements
72
+ };
73
+ }
74
+ catch (error) {
75
+ console.error(`❌ Error processing ${filePath}:`, error);
76
+ return { filePath, injectedCount: 0, elements: [] };
77
+ }
78
+ }
79
+ shouldExcludeFile(filePath) {
80
+ const relativePath = path.relative(process.cwd(), filePath);
81
+ for (const pattern of this.config.excludeFiles) {
82
+ if (pattern.includes('*')) {
83
+ const regex = new RegExp(pattern.replace(/\*/g, '.*'));
84
+ if (regex.test(relativePath)) {
85
+ return true;
86
+ }
87
+ }
88
+ else if (relativePath.includes(pattern)) {
89
+ return true;
90
+ }
91
+ }
92
+ return false;
93
+ }
94
+ async processAllFiles() {
95
+ console.log('🔍 Поиск файлов...');
96
+ const files = [];
97
+ for (const pattern of this.config.includeFiles) {
98
+ try {
99
+ const matches = await fg(pattern, {
100
+ ignore: this.config.excludeFiles,
101
+ absolute: true,
102
+ onlyFiles: true
103
+ });
104
+ files.push(...matches);
105
+ }
106
+ catch (error) {
107
+ console.warn(`⚠️ Ошибка при поиске по паттерну ${pattern}:`, error);
108
+ }
109
+ }
110
+ console.log(`📁 Найдено файлов: ${files.length}\n`);
111
+ const results = [];
112
+ let totalTestIds = 0;
113
+ for (const file of files) {
114
+ console.log(`📄 Обработка: ${path.basename(file)}`);
115
+ const result = await this.processFile(file);
116
+ results.push(result);
117
+ totalTestIds += result.injectedCount;
118
+ if (result.injectedCount > 0) {
119
+ console.log(` ✅ Добавлено testid: ${result.injectedCount}\n`);
120
+ }
121
+ }
122
+ if (totalTestIds > 0) {
123
+ console.log(`\n🏷️ Всего добавлено testid: ${totalTestIds}`);
124
+ }
125
+ return results;
126
+ }
127
+ }
@@ -0,0 +1,18 @@
1
+ import { ElementContext } from './types.js';
2
+ export interface SemanticGeneratorConfig {
3
+ model?: string;
4
+ ollamaUrl?: string;
5
+ }
6
+ export declare class SemanticGenerator {
7
+ private model;
8
+ private ollamaUrl;
9
+ private cache;
10
+ private idCounter;
11
+ private isAvailableCache;
12
+ constructor(config?: SemanticGeneratorConfig);
13
+ private checkAvailability;
14
+ generateSemanticIdSync(context: ElementContext): string | null;
15
+ private buildPrompt;
16
+ private generateWithOllamaSync;
17
+ private cleanId;
18
+ }
@@ -0,0 +1,142 @@
1
+ import { execSync } from 'child_process';
2
+ export class SemanticGenerator {
3
+ constructor(config = {}) {
4
+ this.cache = new Map();
5
+ this.idCounter = new Map();
6
+ this.isAvailableCache = null;
7
+ this.model = config.model || 'llama3.2:3b';
8
+ this.ollamaUrl = config.ollamaUrl || 'http://localhost:11434';
9
+ this.checkAvailability();
10
+ }
11
+ checkAvailability() {
12
+ try {
13
+ const result = execSync(`curl -s -o /dev/null -w "%{http_code}" ${this.ollamaUrl}/api/tags`, {
14
+ encoding: 'utf-8',
15
+ timeout: 2000
16
+ });
17
+ this.isAvailableCache = result === '200';
18
+ if (this.isAvailableCache) {
19
+ console.log('✅ Ollama доступен');
20
+ }
21
+ else {
22
+ console.log('❌ Ollama не отвечает');
23
+ }
24
+ }
25
+ catch (error) {
26
+ this.isAvailableCache = false;
27
+ console.log('❌ Ollama не доступен');
28
+ }
29
+ }
30
+ generateSemanticIdSync(context) {
31
+ if (!this.isAvailableCache) {
32
+ return null;
33
+ }
34
+ const cacheKey = `${context.filePath}:${context.componentName}:${context.lineNumber}`;
35
+ if (this.cache.has(cacheKey)) {
36
+ return this.cache.get(cacheKey);
37
+ }
38
+ const prompt = this.buildPrompt(context);
39
+ try {
40
+ const semanticId = this.generateWithOllamaSync(prompt);
41
+ if (semanticId && semanticId.length > 0 && semanticId !== 'id') {
42
+ let cleanedId = this.cleanId(semanticId);
43
+ //Проверяем дубликаты внутри файла одного
44
+ const fileKey = context.filePath;
45
+ const duplicateKey = `${fileKey}:${cleanedId}`;
46
+ const count = this.idCounter.get(duplicateKey) || 0;
47
+ if (count > 0) {
48
+ cleanedId = `${cleanedId}-${count + 1}`;
49
+ }
50
+ this.idCounter.set(duplicateKey, count + 1);
51
+ this.cache.set(cacheKey, cleanedId);
52
+ return cleanedId;
53
+ }
54
+ }
55
+ catch (error) {
56
+ //Ошибка - игнорируем
57
+ }
58
+ return null;
59
+ }
60
+ buildPrompt(context) {
61
+ const tagName = context.metadata?.elementType?.split(':')[1] || context.componentName;
62
+ const textContent = context.metadata?.textContent || '';
63
+ const placeholder = context.metadata?.attributes?.placeholder || '';
64
+ const inputType = context.metadata?.attributes?.type || '';
65
+ const labelText = context.metadata?.attributes?.label || '';
66
+ const isCustomComponent = context.metadata?.isCustomComponent || false;
67
+ let elementDescription = '';
68
+ if (isCustomComponent) {
69
+ // Для кастомных компонентов используем label или имя компонента
70
+ const componentName = tagName.replace(/Field$/, '').toLowerCase();
71
+ elementDescription = labelText ? `"${labelText}" ${componentName}` : `${componentName} component`;
72
+ }
73
+ else if (tagName === 'button') {
74
+ const buttonText = textContent || 'button';
75
+ elementDescription = `"${buttonText}" button`;
76
+ }
77
+ else if (tagName === 'input') {
78
+ if (inputType === 'email')
79
+ elementDescription = 'email input';
80
+ else if (inputType === 'password')
81
+ elementDescription = 'password input';
82
+ else if (inputType === 'checkbox')
83
+ elementDescription = 'checkbox';
84
+ else if (inputType === 'submit')
85
+ elementDescription = 'submit button';
86
+ else if (placeholder)
87
+ elementDescription = `"${placeholder}" input`;
88
+ else
89
+ elementDescription = 'input field';
90
+ }
91
+ else {
92
+ elementDescription = tagName;
93
+ }
94
+ return `Generate ONE short semantic test ID for: ${elementDescription}
95
+ ${textContent ? `Text: "${textContent}"` : ''}
96
+ ${placeholder ? `Placeholder: "${placeholder}"` : ''}
97
+ ${labelText ? `Label: "${labelText}"` : ''}
98
+
99
+ Return ONLY the ID (lowercase, hyphens, max 25 chars).
100
+ Examples: login-button, email-input, password-field, user-name-input, submit-form
101
+
102
+ ID:`;
103
+ }
104
+ generateWithOllamaSync(prompt) {
105
+ try {
106
+ const escapedPrompt = JSON.stringify(prompt);
107
+ const command = `curl -s -X POST ${this.ollamaUrl}/api/generate \
108
+ -H "Content-Type: application/json" \
109
+ -d '{"model":"${this.model}","prompt":${escapedPrompt},"stream":false,"options":{"temperature":0.2,"max_tokens":25}}'`;
110
+ const result = execSync(command, {
111
+ encoding: 'utf-8',
112
+ timeout: 5000,
113
+ maxBuffer: 1024 * 1024
114
+ });
115
+ const data = JSON.parse(result);
116
+ let response = data.response?.trim() || null;
117
+ if (response) {
118
+ response = response.toLowerCase()
119
+ .replace(/[^a-z0-9-]/g, '-')
120
+ .replace(/-+/g, '-')
121
+ .replace(/^-|-$/g, '');
122
+ }
123
+ return response;
124
+ }
125
+ catch (error) {
126
+ return null;
127
+ }
128
+ }
129
+ cleanId(id) {
130
+ if (!id)
131
+ return '';
132
+ let cleaned = id
133
+ .toLowerCase()
134
+ .replace(/[^a-z0-9-]/g, '-')
135
+ .replace(/-+/g, '-')
136
+ .replace(/^-|-$/g, '');
137
+ if (cleaned.length > 35) {
138
+ cleaned = cleaned.substring(0, 35);
139
+ }
140
+ return cleaned;
141
+ }
142
+ }
@@ -0,0 +1,24 @@
1
+ import { NamingStrategy, ElementContext } from './types.js';
2
+ export declare class HierarchicalStrategy implements NamingStrategy {
3
+ private prefix;
4
+ constructor(prefix?: string);
5
+ generateId(context: ElementContext): string;
6
+ }
7
+ export declare class StableUuidStrategy implements NamingStrategy {
8
+ private prefix;
9
+ private cache;
10
+ constructor(prefix?: string);
11
+ generateId(context: ElementContext): string;
12
+ }
13
+ export declare class MinimalStrategy implements NamingStrategy {
14
+ private prefix;
15
+ private globalCounter;
16
+ constructor(prefix?: string);
17
+ generateId(): string;
18
+ }
19
+ export declare class SemanticStrategy implements NamingStrategy {
20
+ private generator;
21
+ private fallbackStrategy;
22
+ constructor(prefix?: string, model?: string, ollamaUrl?: string);
23
+ generateId(context: ElementContext): string;
24
+ }
@@ -0,0 +1,73 @@
1
+ import { createHash } from 'crypto';
2
+ import { SemanticGenerator } from './semantic-generator.js';
3
+ export class HierarchicalStrategy {
4
+ constructor(prefix = 'test') {
5
+ this.prefix = prefix;
6
+ }
7
+ generateId(context) {
8
+ const { componentName, occurrence } = context;
9
+ const fileName = context.filePath
10
+ .split('/')
11
+ .pop()
12
+ ?.replace(/\.[^/.]+$/, '') || '';
13
+ const lineNumber = context.lineNumber || 0;
14
+ const tagType = context.metadata?.elementType?.split(':')[1] || componentName;
15
+ const parts = [
16
+ this.prefix,
17
+ fileName,
18
+ tagType,
19
+ `line-${lineNumber}`,
20
+ `elem-${occurrence}`
21
+ ].filter(Boolean);
22
+ return parts.join('__').toLowerCase();
23
+ }
24
+ }
25
+ export class StableUuidStrategy {
26
+ constructor(prefix = 'test') {
27
+ this.prefix = prefix;
28
+ this.cache = new Map();
29
+ }
30
+ generateId(context) {
31
+ const key = `${context.filePath}:${context.componentName}:${context.lineNumber}:${context.occurrence}`;
32
+ if (!this.cache.has(key)) {
33
+ const hash = createHash('md5').update(key).digest('hex');
34
+ const uuid = [
35
+ hash.substring(0, 8),
36
+ hash.substring(8, 12),
37
+ hash.substring(12, 16),
38
+ hash.substring(16, 20),
39
+ hash.substring(20, 32)
40
+ ].join('-');
41
+ this.cache.set(key, `${this.prefix}-${uuid}`);
42
+ }
43
+ return this.cache.get(key);
44
+ }
45
+ }
46
+ export class MinimalStrategy {
47
+ constructor(prefix = 'test') {
48
+ this.prefix = prefix;
49
+ this.globalCounter = 0;
50
+ }
51
+ generateId() {
52
+ return `${this.prefix}-${++this.globalCounter}`;
53
+ }
54
+ }
55
+ export class SemanticStrategy {
56
+ constructor(prefix = 'test', model, ollamaUrl) {
57
+ console.log('🤖 Инициализация семантической стратегии...');
58
+ this.generator = new SemanticGenerator({
59
+ model: model || 'llama3.2:3b',
60
+ ollamaUrl: ollamaUrl || 'http://localhost:11434'
61
+ });
62
+ this.fallbackStrategy = new HierarchicalStrategy(prefix);
63
+ }
64
+ generateId(context) {
65
+ // Пробуем получить семантический ID
66
+ const semanticId = this.generator.generateSemanticIdSync(context);
67
+ if (semanticId && semanticId.length > 0) {
68
+ return semanticId;
69
+ }
70
+ // Fallback
71
+ return this.fallbackStrategy.generateId(context);
72
+ }
73
+ }
@@ -0,0 +1,28 @@
1
+ export type Strategy = 'hierarchical' | 'stable-uuid' | 'minimal' | 'semantic';
2
+ export interface GenTestIdConfig {
3
+ prefix?: string;
4
+ strategy?: Strategy;
5
+ semanticModel?: string;
6
+ ollamaUrl?: string;
7
+ excludeFiles?: string[];
8
+ includeFiles?: string[];
9
+ }
10
+ export interface ElementContext {
11
+ componentName: string;
12
+ filePath: string;
13
+ lineNumber?: number;
14
+ occurrence: number;
15
+ metadata?: Record<string, any>;
16
+ }
17
+ export interface NamingStrategy {
18
+ generateId(context: ElementContext): string;
19
+ }
20
+ export interface InjectionResult {
21
+ filePath: string;
22
+ injectedCount: number;
23
+ elements: Array<{
24
+ testId: string;
25
+ lineNumber: number;
26
+ elementType: string;
27
+ }>;
28
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export { GenTestId } from './core/GenTestId.js';
2
+ export type { GenTestIdConfig, ElementContext, InjectionResult } from './core/types.js';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { GenTestId } from './core/GenTestId.js';
@@ -0,0 +1,4 @@
1
+ export declare class ElementCounter {
2
+ private counters;
3
+ increment(key: string): number;
4
+ }
@@ -0,0 +1,11 @@
1
+ export class ElementCounter {
2
+ constructor() {
3
+ this.counters = new Map();
4
+ }
5
+ increment(key) {
6
+ const current = this.counters.get(key) || 0;
7
+ const next = current + 1;
8
+ this.counters.set(key, next);
9
+ return next;
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ import { GenTestId } from '../core/GenTestId.js';
2
+ export interface ParseResult {
3
+ modifiedContent: string;
4
+ injectedCount: number;
5
+ elements: Array<{
6
+ testId: string;
7
+ lineNumber: number;
8
+ elementType: string;
9
+ }>;
10
+ }
11
+ export declare function parseVueFile(content: string, filePath: string, genTestId: GenTestId): Promise<ParseResult>;