agentic-api 1.0.5 → 2.0.26
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/README.md +118 -22
- package/dist/src/agents/agents.example.d.ts +3 -0
- package/dist/src/agents/agents.example.js +38 -0
- package/dist/src/agents/authentication.js +2 -0
- package/dist/src/agents/digestor.js +25 -16
- package/dist/src/agents/prompts.d.ts +2 -2
- package/dist/src/agents/prompts.js +112 -49
- package/dist/src/agents/reducer.core.d.ts +12 -0
- package/dist/src/agents/reducer.core.js +207 -0
- package/dist/src/agents/reducer.d.ts +3 -0
- package/dist/src/agents/reducer.example.d.ts +28 -0
- package/dist/src/agents/reducer.example.js +118 -0
- package/dist/src/agents/reducer.js +19 -0
- package/dist/src/agents/reducer.loaders.d.ts +34 -0
- package/dist/src/agents/reducer.loaders.js +122 -0
- package/dist/src/agents/reducer.process.d.ts +16 -0
- package/dist/src/agents/reducer.process.js +143 -0
- package/dist/src/agents/reducer.tools.d.ts +29 -0
- package/dist/src/agents/reducer.tools.js +157 -0
- package/dist/src/agents/reducer.types.d.ts +50 -0
- package/dist/src/agents/reducer.types.js +5 -0
- package/dist/src/agents/simulator.d.ts +47 -0
- package/dist/src/agents/simulator.executor.d.ts +26 -0
- package/dist/src/agents/simulator.executor.js +132 -0
- package/dist/src/agents/simulator.js +205 -0
- package/dist/src/agents/simulator.prompts.d.ts +16 -0
- package/dist/src/agents/simulator.prompts.js +108 -0
- package/dist/src/agents/simulator.types.d.ts +42 -0
- package/dist/src/agents/simulator.types.js +2 -0
- package/dist/src/agents/simulator.utils.d.ts +20 -0
- package/dist/src/agents/simulator.utils.js +87 -0
- package/dist/src/execute.d.ts +14 -7
- package/dist/src/execute.js +359 -84
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +14 -0
- package/dist/src/princing.openai.d.ts +9 -2
- package/dist/src/princing.openai.js +16 -11
- package/dist/src/prompts.d.ts +3 -2
- package/dist/src/prompts.js +207 -72
- package/dist/src/rag/embeddings.d.ts +103 -0
- package/dist/src/rag/embeddings.js +466 -0
- package/dist/src/rag/index.d.ts +12 -0
- package/dist/src/rag/index.js +40 -0
- package/dist/src/rag/lucene.d.ts +45 -0
- package/dist/src/rag/lucene.js +227 -0
- package/dist/src/rag/parser.d.ts +68 -0
- package/dist/src/rag/parser.js +192 -0
- package/dist/src/rag/tools.d.ts +76 -0
- package/dist/src/rag/tools.js +196 -0
- package/dist/src/rag/types.d.ts +178 -0
- package/dist/src/rag/types.js +21 -0
- package/dist/src/rag/usecase.d.ts +16 -0
- package/dist/src/rag/usecase.js +79 -0
- package/dist/src/rules/errors.d.ts +60 -0
- package/dist/src/rules/errors.js +97 -0
- package/dist/src/rules/git/git.e2e.helper.d.ts +104 -0
- package/dist/src/rules/git/git.e2e.helper.js +488 -0
- package/dist/src/rules/git/git.health.d.ts +66 -0
- package/dist/src/rules/git/git.health.js +354 -0
- package/dist/src/rules/git/git.helper.d.ts +129 -0
- package/dist/src/rules/git/git.helper.js +53 -0
- package/dist/src/rules/git/index.d.ts +6 -0
- package/dist/src/rules/git/index.js +76 -0
- package/dist/src/rules/git/repo.d.ts +128 -0
- package/dist/src/rules/git/repo.js +900 -0
- package/dist/src/rules/git/repo.pr.d.ts +137 -0
- package/dist/src/rules/git/repo.pr.js +589 -0
- package/dist/src/rules/git/repo.tools.d.ts +134 -0
- package/dist/src/rules/git/repo.tools.js +730 -0
- package/dist/src/rules/index.d.ts +8 -0
- package/dist/src/rules/index.js +25 -0
- package/dist/src/rules/messages.d.ts +17 -0
- package/dist/src/rules/messages.js +21 -0
- package/dist/src/rules/types.ctrl.d.ts +28 -0
- package/dist/src/rules/types.ctrl.js +2 -0
- package/dist/src/rules/types.d.ts +510 -0
- package/dist/src/rules/types.helpers.d.ts +132 -0
- package/dist/src/rules/types.helpers.js +2 -0
- package/dist/src/rules/types.js +33 -0
- package/dist/src/rules/user.mapper.d.ts +61 -0
- package/dist/src/rules/user.mapper.js +160 -0
- package/dist/src/rules/utils/slug.d.ts +22 -0
- package/dist/src/rules/utils/slug.js +35 -0
- package/dist/src/rules/utils.matter.d.ts +66 -0
- package/dist/src/rules/utils.matter.js +208 -0
- package/dist/src/rules/utils.slug.d.ts +22 -0
- package/dist/src/rules/utils.slug.js +35 -0
- package/dist/src/scrapper.d.ts +3 -2
- package/dist/src/scrapper.js +33 -37
- package/dist/src/stategraph/index.d.ts +8 -0
- package/dist/src/stategraph/index.js +21 -0
- package/dist/src/stategraph/stategraph.d.ts +91 -0
- package/dist/src/stategraph/stategraph.js +241 -0
- package/dist/src/stategraph/stategraph.storage.d.ts +41 -0
- package/dist/src/stategraph/stategraph.storage.js +166 -0
- package/dist/src/stategraph/types.d.ts +139 -0
- package/dist/src/stategraph/types.js +19 -0
- package/dist/src/types.d.ts +68 -39
- package/dist/src/types.js +53 -89
- package/dist/src/usecase.d.ts +4 -0
- package/dist/src/usecase.js +44 -0
- package/dist/src/utils.d.ts +12 -5
- package/dist/src/utils.js +30 -13
- package/package.json +9 -3
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { SimpleGit } from 'simple-git';
|
|
2
|
+
import { RulesGitConfig, GitCommitHistory, RuleUser, PRInfo, PRMergeResult, RulePullRequestDetails } from '../types';
|
|
3
|
+
import { IE2ETestOperations, E2ETestConfig, E2ETestResult, IRulesE2EAssertions } from './git.helper';
|
|
4
|
+
import { IConcurrencyManager, IGitNotesOperations } from '../types.helpers';
|
|
5
|
+
/**
|
|
6
|
+
* Helper E2E pour les tests d'intégration avec un vrai repository Git
|
|
7
|
+
* Implémente toutes les interfaces pour permettre des tests complets
|
|
8
|
+
*/
|
|
9
|
+
export declare class GitE2EHelper implements IE2ETestOperations, IRulesE2EAssertions {
|
|
10
|
+
readonly git: SimpleGit;
|
|
11
|
+
readonly config: RulesGitConfig;
|
|
12
|
+
readonly concurrency: IConcurrencyManager;
|
|
13
|
+
readonly notes: IGitNotesOperations;
|
|
14
|
+
private tempRepoPath;
|
|
15
|
+
private isInitialized;
|
|
16
|
+
constructor(config: E2ETestConfig);
|
|
17
|
+
initTemporaryRepo(): Promise<void>;
|
|
18
|
+
cleanup(): Promise<void>;
|
|
19
|
+
getFileContent(filePath: string, branch?: string): Promise<GitCommitHistory | null>;
|
|
20
|
+
listFilesInBranch(branch: string, pattern?: string): Promise<string[]>;
|
|
21
|
+
fileExistsInBranch(filePath: string, branch: string): Promise<boolean>;
|
|
22
|
+
isFileMerged(filePath: string, from: string, target?: string): Promise<boolean>;
|
|
23
|
+
getAllBranches(options?: {
|
|
24
|
+
unmergedOnly?: boolean;
|
|
25
|
+
}): Promise<string[]>;
|
|
26
|
+
validateConfiguration(): Promise<any>;
|
|
27
|
+
isPRClosed(branch: string): Promise<boolean>;
|
|
28
|
+
isPRClosedRobust(branch: string, config?: RulesGitConfig): Promise<boolean>;
|
|
29
|
+
closePR(branch: string, author: RuleUser, message?: string): Promise<PRMergeResult>;
|
|
30
|
+
closePRRobust(branch: string, closedBy: RuleUser, message: string, config?: RulesGitConfig): Promise<PRMergeResult>;
|
|
31
|
+
getAllPR(options?: {
|
|
32
|
+
closed?: boolean;
|
|
33
|
+
validationPrefix?: string;
|
|
34
|
+
}): Promise<PRInfo[]>;
|
|
35
|
+
getNextPRNumber(validationPrefix: string): Promise<number>;
|
|
36
|
+
newPR(files: string[], description: string, author: RuleUser, options?: {
|
|
37
|
+
content?: string[];
|
|
38
|
+
editorBranch?: string;
|
|
39
|
+
validationPrefix?: string;
|
|
40
|
+
rulesGitConfig?: RulesGitConfig;
|
|
41
|
+
}): Promise<PRInfo>;
|
|
42
|
+
getPRInfo(branch: string, config?: RulesGitConfig): Promise<PRInfo>;
|
|
43
|
+
updatePRComments(branch: string, details: RulePullRequestDetails): Promise<PRInfo>;
|
|
44
|
+
syncPR(branch: string, user: RuleUser): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Crée un fichier dans le repository
|
|
47
|
+
* @param filePath Chemin du fichier
|
|
48
|
+
* @param content Contenu du fichier
|
|
49
|
+
* @param branch Branche optionnelle (par défaut: draft)
|
|
50
|
+
*/
|
|
51
|
+
createOrEditFile(filePath: string, content: string, branch?: string, config?: RulesGitConfig): Promise<GitCommitHistory>;
|
|
52
|
+
/**
|
|
53
|
+
* Modifie un fichier existant
|
|
54
|
+
* @param filePath Chemin du fichier
|
|
55
|
+
* @param content Contenu du fichier
|
|
56
|
+
* @param branch Branche optionnelle (par défaut: draft)
|
|
57
|
+
*/
|
|
58
|
+
editFile(filePath: string, content: string, branch?: string): Promise<GitCommitHistory>;
|
|
59
|
+
validateRepositoryState(): Promise<{
|
|
60
|
+
isValid: boolean;
|
|
61
|
+
branches: string[];
|
|
62
|
+
files: Record<string, string[]>;
|
|
63
|
+
errors: string[];
|
|
64
|
+
}>;
|
|
65
|
+
runCompleteWorkflow(files: {
|
|
66
|
+
path: string;
|
|
67
|
+
content: string;
|
|
68
|
+
}[], description: string): Promise<{
|
|
69
|
+
prBranch: string;
|
|
70
|
+
commitHash: string;
|
|
71
|
+
mergeCommitHash?: string;
|
|
72
|
+
}>;
|
|
73
|
+
assertFileContent(filePath: string, expectedContent: string, branch?: string): Promise<void>;
|
|
74
|
+
assertPRState(branch: string, expectedState: 'open' | 'closed'): Promise<void>;
|
|
75
|
+
assertFilemerged(filePath: string, fromBranch: string, toBranch?: string): Promise<void>;
|
|
76
|
+
assertBranchStructure(expectedBranches: string[]): Promise<void>;
|
|
77
|
+
assertGitHistory(expectedCommits: {
|
|
78
|
+
message: string;
|
|
79
|
+
author?: string;
|
|
80
|
+
}[]): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Retourne le chemin du repository temporaire
|
|
83
|
+
*/
|
|
84
|
+
getRepositoryPath(): string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Factory pour créer des helpers E2E facilement
|
|
88
|
+
*/
|
|
89
|
+
export declare class GitE2EFactory {
|
|
90
|
+
static getDefaultHelper(): GitE2EHelper;
|
|
91
|
+
/**
|
|
92
|
+
* Crée un helper E2E avec un repository temporaire
|
|
93
|
+
*/
|
|
94
|
+
static createE2EHelper(options?: {
|
|
95
|
+
autoCleanup?: boolean;
|
|
96
|
+
customConfig?: Partial<RulesGitConfig>;
|
|
97
|
+
}): Promise<GitE2EHelper>;
|
|
98
|
+
/**
|
|
99
|
+
* Crée un helper E2E et exécute un test complet
|
|
100
|
+
*/
|
|
101
|
+
static runE2ETest(testName: string, testFunction: (helper: GitE2EHelper) => Promise<void>, options?: {
|
|
102
|
+
autoCleanup?: boolean;
|
|
103
|
+
}): Promise<E2ETestResult>;
|
|
104
|
+
}
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.GitE2EFactory = exports.GitE2EHelper = void 0;
|
|
7
|
+
const simple_git_1 = __importDefault(require("simple-git"));
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const os_1 = require("os");
|
|
11
|
+
const index_1 = require("./index");
|
|
12
|
+
/**
|
|
13
|
+
* Helper E2E pour les tests d'intégration avec un vrai repository Git
|
|
14
|
+
* Implémente toutes les interfaces pour permettre des tests complets
|
|
15
|
+
*/
|
|
16
|
+
class GitE2EHelper {
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.isInitialized = false;
|
|
19
|
+
this.tempRepoPath = config.tempDir;
|
|
20
|
+
this.config = config.gitConfig;
|
|
21
|
+
this.config.instance = this.git = (0, simple_git_1.default)({ baseDir: this.tempRepoPath, binary: 'git' });
|
|
22
|
+
delete this.config.reset;
|
|
23
|
+
// Concurrency manager réel (pas de mock)
|
|
24
|
+
this.concurrency = {
|
|
25
|
+
async withRetry(operation, options, operationName = 'unknown') {
|
|
26
|
+
let attempts = 0;
|
|
27
|
+
let lastError;
|
|
28
|
+
while (attempts < options.maxRetries) {
|
|
29
|
+
attempts++;
|
|
30
|
+
try {
|
|
31
|
+
const result = await Promise.race([
|
|
32
|
+
operation(),
|
|
33
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Operation timeout')), options.timeoutMs))
|
|
34
|
+
]);
|
|
35
|
+
return { success: true, result, attempts };
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
39
|
+
if (attempts < options.maxRetries) {
|
|
40
|
+
const delay = options.retryDelayMs * Math.pow(2, attempts - 1) + Math.random() * 50;
|
|
41
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
attempts,
|
|
48
|
+
error: lastError
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
// Capture reference to git instance for closures
|
|
53
|
+
const gitInstance = this.git;
|
|
54
|
+
// Git Notes operations réelles
|
|
55
|
+
this.notes = {
|
|
56
|
+
async readNote(branch, namespace) {
|
|
57
|
+
return (0, index_1.gitReadNote)(gitInstance, branch, namespace);
|
|
58
|
+
},
|
|
59
|
+
async writeNote(branch, metadata, namespace) {
|
|
60
|
+
return (0, index_1.gitWriteNote)(gitInstance, branch, metadata, namespace);
|
|
61
|
+
},
|
|
62
|
+
async deleteNote(branch, author, namespace) {
|
|
63
|
+
console.log(`[DEBUG][E2EHelper.notes.deleteNote] Deleting note for ${branch} by ${author.name}`);
|
|
64
|
+
try {
|
|
65
|
+
await gitInstance.raw(['notes', '--ref', namespace, 'remove', branch]);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Ignorer si la note n'existe pas
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async initTemporaryRepo() {
|
|
74
|
+
if (this.isInitialized) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
// Créer le répertoire temporaire
|
|
79
|
+
await fs_1.promises.mkdir(this.tempRepoPath, { recursive: true });
|
|
80
|
+
// Create upload directory
|
|
81
|
+
const uploadDir = this.config.uploadPath;
|
|
82
|
+
await fs_1.promises.mkdir(uploadDir, { recursive: true });
|
|
83
|
+
// Initialiser le repository Git
|
|
84
|
+
await this.git.init();
|
|
85
|
+
// Configuration Git de base
|
|
86
|
+
await this.git.addConfig('user.name', 'E2E Test');
|
|
87
|
+
await this.git.addConfig('user.email', 'e2e@test.com');
|
|
88
|
+
await this.git.addConfig('init.defaultBranch', this.config.mainBranch);
|
|
89
|
+
// Créer la branche main directement
|
|
90
|
+
await this.git.checkout(['-b', this.config.mainBranch]);
|
|
91
|
+
// Créer un commit initial
|
|
92
|
+
const readmePath = (0, path_1.join)(this.tempRepoPath, 'README.md');
|
|
93
|
+
await fs_1.promises.writeFile(readmePath, '# E2E Test Repository\n\nTemporary repository for E2E testing.');
|
|
94
|
+
await this.git.add('README.md');
|
|
95
|
+
await this.git.commit('Initial commit');
|
|
96
|
+
// Créer la branche draft à partir de main
|
|
97
|
+
await this.git.checkoutBranch(this.config.draftBranch, this.config.mainBranch);
|
|
98
|
+
// force reload of git config
|
|
99
|
+
this.config.reset = true;
|
|
100
|
+
await (0, index_1.gitLoad)(this.config);
|
|
101
|
+
// console.log(`🌶️ Helper.initTemporaryRepo: gitConfig: `,this.config.repoPath, 'reset:',this.config.reset);
|
|
102
|
+
delete this.config.reset;
|
|
103
|
+
this.isInitialized = true;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
throw new Error(`Échec de l'initialisation du repository E2E: ${error}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async cleanup() {
|
|
110
|
+
if (this.tempRepoPath) {
|
|
111
|
+
try {
|
|
112
|
+
await fs_1.promises.rm(this.tempRepoPath, { recursive: true, force: true });
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.warn(`Erreur lors du nettoyage du repository E2E: ${error}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// ===== DÉLÉGATION VERS LES FONCTIONS REPO.TS =====
|
|
120
|
+
async getFileContent(filePath, branch) {
|
|
121
|
+
return (0, index_1.gitGetFileContent)(this.git, filePath, branch);
|
|
122
|
+
}
|
|
123
|
+
async listFilesInBranch(branch, pattern) {
|
|
124
|
+
// return gitGetDiffFiles(this.git, branch, this.config.draftBranch, pattern);
|
|
125
|
+
return (0, index_1.gitListFilesInBranch)(this.git, branch, pattern || '.md');
|
|
126
|
+
}
|
|
127
|
+
async fileExistsInBranch(filePath, branch) {
|
|
128
|
+
return (0, index_1.gitFileExistsInBranch)(this.git, filePath, branch);
|
|
129
|
+
}
|
|
130
|
+
async isFileMerged(filePath, from, target) {
|
|
131
|
+
return (0, index_1.gitIsFileMerged)(this.git, filePath, from, target);
|
|
132
|
+
}
|
|
133
|
+
async getAllBranches(options) {
|
|
134
|
+
return (0, index_1.gitGetAllBranches)(this.git, options);
|
|
135
|
+
}
|
|
136
|
+
async validateConfiguration() {
|
|
137
|
+
return (0, index_1.gitCheckConfiguration)(this.git);
|
|
138
|
+
}
|
|
139
|
+
async isPRClosed(branch) {
|
|
140
|
+
return (0, index_1.gitIsPRClosed)(this.git, branch);
|
|
141
|
+
}
|
|
142
|
+
async isPRClosedRobust(branch, config) {
|
|
143
|
+
return (0, index_1.gitIsPRClosedRobust)(this.git, branch, config || this.config);
|
|
144
|
+
}
|
|
145
|
+
async closePR(branch, author, message) {
|
|
146
|
+
console.log(`[DEBUG][E2EHelper.closePR] Closing PR: ${branch} by ${author.name}`);
|
|
147
|
+
return (0, index_1.gitClosePR)(this.git, branch, author, message);
|
|
148
|
+
}
|
|
149
|
+
async closePRRobust(branch, closedBy, message, config) {
|
|
150
|
+
return (0, index_1.gitClosePRRobust)(this.git, branch, closedBy, message, config || this.config);
|
|
151
|
+
}
|
|
152
|
+
async getAllPR(options) {
|
|
153
|
+
// Underlying gitGetAllPR uses global gitLoad(), so passing specific config here has no effect yet.
|
|
154
|
+
// Keeping rulesGitConfig in signature for potential future refactor of gitGetAllPR.
|
|
155
|
+
return (0, index_1.gitGetAllPR)(this.git, options);
|
|
156
|
+
}
|
|
157
|
+
async getNextPRNumber(validationPrefix) {
|
|
158
|
+
return (0, index_1.gitGetNextPRNumber)(this.git, validationPrefix);
|
|
159
|
+
}
|
|
160
|
+
async newPR(files, description, author, options) {
|
|
161
|
+
//console.log(`[DEBUG][E2EHelper.newPR] Creating new PR by ${author.name} for files: ${files.join(', ')}`);
|
|
162
|
+
const passOptions = { ...options, rulesGitConfig: options?.rulesGitConfig || this.config };
|
|
163
|
+
return (0, index_1.gitNewValidationRequest)(this.git, files, description, author, passOptions);
|
|
164
|
+
}
|
|
165
|
+
async getPRInfo(branch, config) {
|
|
166
|
+
const effectiveConfig = config || this.config;
|
|
167
|
+
// First, determine if the PR is closed using the robust method and effectiveConfig
|
|
168
|
+
// Then, load the PR info, passing the determined closed status
|
|
169
|
+
const prInfo = await (0, index_1.gitLoadPR)(this.git, branch);
|
|
170
|
+
if (!prInfo) {
|
|
171
|
+
throw new Error(`PR info not found for branch ${branch}`); // Should not happen if branch exists
|
|
172
|
+
}
|
|
173
|
+
prInfo.open = prInfo.metadata.status == 'open';
|
|
174
|
+
return prInfo;
|
|
175
|
+
}
|
|
176
|
+
async updatePRComments(branch, details) {
|
|
177
|
+
return (0, index_1.gitPRUpdateComments)(this.git, branch, details, this.config);
|
|
178
|
+
}
|
|
179
|
+
// FIXME: Cette fonction a un problème de gestion des métadonnées manquantes. Voir gitSyncPR pour les détails.
|
|
180
|
+
async syncPR(branch, user) {
|
|
181
|
+
return (0, index_1.gitSyncPR)(this.git, branch, user);
|
|
182
|
+
}
|
|
183
|
+
// ===== MÉTHODES E2E SPÉCIFIQUES =====
|
|
184
|
+
/**
|
|
185
|
+
* Crée un fichier dans le repository
|
|
186
|
+
* @param filePath Chemin du fichier
|
|
187
|
+
* @param content Contenu du fichier
|
|
188
|
+
* @param branch Branche optionnelle (par défaut: draft)
|
|
189
|
+
*/
|
|
190
|
+
async createOrEditFile(filePath, content, branch, config) {
|
|
191
|
+
const user = { name: 'E2E Test', email: 'e2e@test.com' };
|
|
192
|
+
const targetBranch = branch || config?.draftBranch || this.config.draftBranch;
|
|
193
|
+
return (0, index_1.gitCreateOrEditFile)(this.git, filePath, targetBranch, content, user, config || this.config);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Modifie un fichier existant
|
|
197
|
+
* @param filePath Chemin du fichier
|
|
198
|
+
* @param content Contenu du fichier
|
|
199
|
+
* @param branch Branche optionnelle (par défaut: draft)
|
|
200
|
+
*/
|
|
201
|
+
async editFile(filePath, content, branch) {
|
|
202
|
+
const user = { name: 'E2E Test', email: 'e2e@test.com' };
|
|
203
|
+
const targetBranch = branch || this.config.draftBranch;
|
|
204
|
+
return (0, index_1.gitEditFile)(this.git, filePath, targetBranch, content, user, this.config);
|
|
205
|
+
}
|
|
206
|
+
async validateRepositoryState() {
|
|
207
|
+
const errors = [];
|
|
208
|
+
let isValid = true;
|
|
209
|
+
try {
|
|
210
|
+
// Vérifier que c'est un repository Git valide
|
|
211
|
+
const isRepo = await this.git.checkIsRepo();
|
|
212
|
+
if (!isRepo) {
|
|
213
|
+
errors.push('Le répertoire n\'est pas un repository Git valide');
|
|
214
|
+
isValid = false;
|
|
215
|
+
}
|
|
216
|
+
// Lister les branches
|
|
217
|
+
const branches = await this.getAllBranches();
|
|
218
|
+
// Vérifier les branches obligatoires
|
|
219
|
+
if (!branches.includes(this.config.mainBranch)) {
|
|
220
|
+
errors.push(`Branche principale manquante: ${this.config.mainBranch}`);
|
|
221
|
+
isValid = false;
|
|
222
|
+
}
|
|
223
|
+
if (!branches.includes(this.config.draftBranch)) {
|
|
224
|
+
errors.push(`Branche draft manquante: ${this.config.draftBranch}`);
|
|
225
|
+
isValid = false;
|
|
226
|
+
}
|
|
227
|
+
// Lister les fichiers par branche
|
|
228
|
+
const files = {};
|
|
229
|
+
for (const branch of branches) {
|
|
230
|
+
try {
|
|
231
|
+
files[branch] = await this.listFilesInBranch(branch);
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
errors.push(`Erreur lors de la lecture de la branche ${branch}: ${error}`);
|
|
235
|
+
isValid = false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return { isValid, branches, files, errors };
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
errors.push(`Erreur lors de la validation: ${error}`);
|
|
242
|
+
return { isValid: false, branches: [], files: {}, errors };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async runCompleteWorkflow(files, description) {
|
|
246
|
+
const user = { name: 'E2E Workflow User', email: 'e2e.workflow@example.com' };
|
|
247
|
+
const filePaths = files.map(f => f.path);
|
|
248
|
+
const contents = files.map(f => f.content);
|
|
249
|
+
// 1. Create a new PR (contribution branch) with the initial content
|
|
250
|
+
const pr = await this.newPR(filePaths, description, user, {
|
|
251
|
+
content: contents,
|
|
252
|
+
});
|
|
253
|
+
const prBranch = pr.branch;
|
|
254
|
+
// Capture the initial commit hash on the PR branch
|
|
255
|
+
// newPR makes a commit, so we get its hash.
|
|
256
|
+
// If newPR makes multiple commits for multiple files, this gets the latest one.
|
|
257
|
+
let commitHash = '';
|
|
258
|
+
try {
|
|
259
|
+
await this.git.checkout(prBranch); // Ensure we are on the prBranch
|
|
260
|
+
const log = await this.git.log({ maxCount: 1 });
|
|
261
|
+
commitHash = log.latest?.hash || 'unknown-initial-commit-hash';
|
|
262
|
+
await this.git.checkout(this.config.draftBranch); // Go back to draftBranch or main for safety before next ops
|
|
263
|
+
}
|
|
264
|
+
catch (e) {
|
|
265
|
+
console.warn(`[E2EHelper.runCompleteWorkflow] Could not get initial commit hash for ${prBranch}: ${e}`);
|
|
266
|
+
commitHash = 'error-getting-initial-commit-hash';
|
|
267
|
+
}
|
|
268
|
+
// Verify files are on the PR branch
|
|
269
|
+
for (const file of files) {
|
|
270
|
+
await this.assertFileContent(file.path, file.content, prBranch);
|
|
271
|
+
}
|
|
272
|
+
// Verify files are NOT YET on rule-editor
|
|
273
|
+
for (const file of files) {
|
|
274
|
+
const existsOnRuleEditor = await this.fileExistsInBranch(file.path, this.config.draftBranch);
|
|
275
|
+
expect(existsOnRuleEditor).toBe(false);
|
|
276
|
+
}
|
|
277
|
+
// 2. Close the PR (merge the contribution branch into rule-editor)
|
|
278
|
+
/* const closureHash = */ await this.closePRRobust(// We don't need to store closureHash anymore
|
|
279
|
+
prBranch, user, `Validation E2E terminée for: ${description}`, this.config);
|
|
280
|
+
// 3. Verify files are now on rule-editor with correct content
|
|
281
|
+
await this.git.checkout(this.config.draftBranch);
|
|
282
|
+
for (const file of files) {
|
|
283
|
+
await this.assertFileContent(file.path, file.content, this.config.draftBranch);
|
|
284
|
+
}
|
|
285
|
+
// 4. Verify the contribution branch was merged into rule-editor (content check serves as primary)
|
|
286
|
+
// Get the hash of the merge commit on rule-editor
|
|
287
|
+
let mergeCommitHash;
|
|
288
|
+
try {
|
|
289
|
+
// Ensure we are on draftBranch to get its latest log
|
|
290
|
+
await this.git.checkout(this.config.draftBranch);
|
|
291
|
+
const log = await this.git.log({ maxCount: 1 }); // Get the latest commit on rule-editor
|
|
292
|
+
mergeCommitHash = log.latest?.hash;
|
|
293
|
+
}
|
|
294
|
+
catch (e) {
|
|
295
|
+
console.warn(`[E2EHelper.runCompleteWorkflow] Could not get merge commit hash on ${this.config.draftBranch}: ${e}`);
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
prBranch,
|
|
299
|
+
commitHash, // Added initial commit hash
|
|
300
|
+
mergeCommitHash // Renamed from ruleEditorCommitHash
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
// ===== ASSERTIONS E2E =====
|
|
304
|
+
async assertFileContent(filePath, expectedContent, branch) {
|
|
305
|
+
const commit = await this.getFileContent(filePath, branch);
|
|
306
|
+
if (commit?.content !== expectedContent) {
|
|
307
|
+
throw new Error(`Contenu du fichier incorrect.\n` +
|
|
308
|
+
`Attendu: ${expectedContent}\n` +
|
|
309
|
+
`Reçu: ${commit?.content}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
async assertPRState(branch, expectedState) {
|
|
313
|
+
const isClosed = await this.isPRClosedRobust(branch, this.config);
|
|
314
|
+
const actualState = isClosed ? 'closed' : 'open';
|
|
315
|
+
if (actualState !== expectedState) {
|
|
316
|
+
throw new Error(`État du PR incorrect. Attendu: ${expectedState}, Reçu: ${actualState}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async assertFilemerged(filePath, fromBranch, toBranch) {
|
|
320
|
+
const target = toBranch || this.config.mainBranch;
|
|
321
|
+
const isMerged = await this.isFileMerged(filePath, fromBranch, target);
|
|
322
|
+
if (!isMerged) {
|
|
323
|
+
throw new Error(`Le fichier ${filePath} n'a pas été mergé de ${fromBranch} vers ${target}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
async assertBranchStructure(expectedBranches) {
|
|
327
|
+
const actualBranches = await this.getAllBranches();
|
|
328
|
+
for (const expectedBranch of expectedBranches) {
|
|
329
|
+
if (!actualBranches.includes(expectedBranch)) {
|
|
330
|
+
throw new Error(`Branche manquante: ${expectedBranch}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async assertGitHistory(expectedCommits) {
|
|
335
|
+
const log = await this.git.log({ maxCount: expectedCommits.length });
|
|
336
|
+
const actualCommits = log.all.slice(0, expectedCommits.length);
|
|
337
|
+
for (let i = 0; i < expectedCommits.length; i++) {
|
|
338
|
+
const expected = expectedCommits[i];
|
|
339
|
+
const actual = actualCommits[i];
|
|
340
|
+
if (expected.author && !actual.author_name?.includes(expected.author)) {
|
|
341
|
+
throw new Error(`Auteur de commit incorrect à l'index ${i}.\n` +
|
|
342
|
+
`Attendu: ${expected.author}\n` +
|
|
343
|
+
`Reçu: ${actual.author_name}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Retourne le chemin du repository temporaire
|
|
349
|
+
*/
|
|
350
|
+
getRepositoryPath() {
|
|
351
|
+
return this.tempRepoPath;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
exports.GitE2EHelper = GitE2EHelper;
|
|
355
|
+
const defaultConfig = (tempDir) => {
|
|
356
|
+
if (!tempDir) {
|
|
357
|
+
throw new Error('Default should be initialized from tests/setup.after.env.ts');
|
|
358
|
+
}
|
|
359
|
+
const config = {
|
|
360
|
+
instance: (0, simple_git_1.default)({ baseDir: tempDir, binary: 'git' }),
|
|
361
|
+
repoPath: tempDir,
|
|
362
|
+
uploadPath: (0, path_1.join)(tempDir, '.assets'), // Add uploadPath to default config
|
|
363
|
+
draftBranch: 'rule-editor',
|
|
364
|
+
mainBranch: 'main',
|
|
365
|
+
validationPrefix: 'rule-validation-',
|
|
366
|
+
releasePrefix: 'rule-release-',
|
|
367
|
+
validationToken: 'closed',
|
|
368
|
+
concurrency: {
|
|
369
|
+
maxRetries: 3,
|
|
370
|
+
retryDelayMs: 100,
|
|
371
|
+
timeoutMs: 5000
|
|
372
|
+
},
|
|
373
|
+
gitNotes: {
|
|
374
|
+
namespace: 'refs/notes/pr-status',
|
|
375
|
+
enabled: true,
|
|
376
|
+
fallbackToCommit: true
|
|
377
|
+
},
|
|
378
|
+
canForce: true,
|
|
379
|
+
verbose: false
|
|
380
|
+
};
|
|
381
|
+
return config;
|
|
382
|
+
};
|
|
383
|
+
let defaultHelper;
|
|
384
|
+
/**
|
|
385
|
+
* Factory pour créer des helpers E2E facilement
|
|
386
|
+
*/
|
|
387
|
+
class GitE2EFactory {
|
|
388
|
+
static getDefaultHelper() {
|
|
389
|
+
if (!defaultHelper) {
|
|
390
|
+
throw new Error('Default should be initialized from tests/setup.after.env.ts');
|
|
391
|
+
}
|
|
392
|
+
return defaultHelper;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Crée un helper E2E avec un repository temporaire
|
|
396
|
+
*/
|
|
397
|
+
static async createE2EHelper(options) {
|
|
398
|
+
const tempDir = (0, path_1.join)((0, os_1.tmpdir)(), `git-e2e-test-${Math.random() * 10000 | 0}-${Math.random() * 10000 | 0}`);
|
|
399
|
+
(0, fs_1.mkdirSync)(tempDir, { recursive: true });
|
|
400
|
+
const gitConfig = { ...defaultConfig(tempDir), ...options?.customConfig };
|
|
401
|
+
gitConfig.repoPath = tempDir;
|
|
402
|
+
const testConfig = {
|
|
403
|
+
tempDir,
|
|
404
|
+
gitConfig,
|
|
405
|
+
autoCleanup: options?.autoCleanup ?? true
|
|
406
|
+
};
|
|
407
|
+
const helper = new GitE2EHelper(testConfig);
|
|
408
|
+
// Set test environment
|
|
409
|
+
process.env.NODE_ENV = 'test';
|
|
410
|
+
// Set default test values for required environment variables
|
|
411
|
+
const defaultTestEnv = {
|
|
412
|
+
DEFAULT_BRANCH_DRAFT: helper.config.draftBranch,
|
|
413
|
+
DEFAULT_BRANCH_MAIN: helper.config.mainBranch,
|
|
414
|
+
DEFAULT_BRANCH_VALIDATION_PREFIX: helper.config.validationPrefix,
|
|
415
|
+
DEFAULT_VALIDATION_TOKEN: helper.config.validationToken,
|
|
416
|
+
GIT_CONCURRENCY_MAX_RETRIES: helper.config.concurrency.maxRetries.toString(),
|
|
417
|
+
GIT_CONCURRENCY_RETRY_DELAY_MS: helper.config.concurrency.retryDelayMs.toString(),
|
|
418
|
+
GIT_CONCURRENCY_TIMEOUT_MS: helper.config.concurrency.timeoutMs.toString(),
|
|
419
|
+
GIT_NOTES_NAMESPACE: helper.config.gitNotes.namespace,
|
|
420
|
+
GIT_NOTES_ENABLED: helper.config.gitNotes.enabled.toString(),
|
|
421
|
+
GIT_NOTES_FALLBACK_TO_COMMIT: helper.config.gitNotes.fallbackToCommit.toString()
|
|
422
|
+
};
|
|
423
|
+
// Set default values if not already set
|
|
424
|
+
Object.entries(defaultTestEnv).forEach(([key, value]) => {
|
|
425
|
+
if (!process.env[key]) {
|
|
426
|
+
process.env[key] = value;
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
await helper.initTemporaryRepo();
|
|
430
|
+
defaultHelper = helper;
|
|
431
|
+
return helper;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Crée un helper E2E et exécute un test complet
|
|
435
|
+
*/
|
|
436
|
+
static async runE2ETest(testName, testFunction, options) {
|
|
437
|
+
const startTime = Date.now();
|
|
438
|
+
const result = {
|
|
439
|
+
success: false,
|
|
440
|
+
duration: 0,
|
|
441
|
+
details: {
|
|
442
|
+
repositoryValid: false,
|
|
443
|
+
filesCreated: [],
|
|
444
|
+
prCreated: undefined,
|
|
445
|
+
prClosed: false,
|
|
446
|
+
merged: false
|
|
447
|
+
},
|
|
448
|
+
errors: []
|
|
449
|
+
};
|
|
450
|
+
let helper;
|
|
451
|
+
try {
|
|
452
|
+
// console.log(`🧪 Démarrage du test E2E: ${testName}`);
|
|
453
|
+
// Créer le helper
|
|
454
|
+
helper = await GitE2EFactory.createE2EHelper({
|
|
455
|
+
autoCleanup: options?.autoCleanup ?? true
|
|
456
|
+
});
|
|
457
|
+
// Valider l'état initial du repository
|
|
458
|
+
// Exécuter le test
|
|
459
|
+
await testFunction(helper);
|
|
460
|
+
// Marquer le test comme réussi
|
|
461
|
+
result.success = true;
|
|
462
|
+
result.duration = Date.now() - startTime;
|
|
463
|
+
// console.log(`✅ Test E2E réussi: ${testName} (${result.duration}ms)`);
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
result.success = false;
|
|
468
|
+
result.duration = Date.now() - startTime;
|
|
469
|
+
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
470
|
+
console.error(`❌ Test E2E échoué: ${testName}`);
|
|
471
|
+
console.error('Erreur:', error);
|
|
472
|
+
return result;
|
|
473
|
+
}
|
|
474
|
+
finally {
|
|
475
|
+
// Nettoyage si autoCleanup est activé
|
|
476
|
+
if (helper && (options?.autoCleanup ?? true)) {
|
|
477
|
+
try {
|
|
478
|
+
await helper.cleanup();
|
|
479
|
+
console.log(`🧹 Repository E2E nettoyé: ${helper.getRepositoryPath()}`);
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
console.warn('Erreur lors du nettoyage:', error);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
exports.GitE2EFactory = GitE2EFactory;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { RulesGitConfig, GitHealthStatus } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Git Repository Health Manager
|
|
4
|
+
*
|
|
5
|
+
* Manages repository health diagnostics, repairs, and maintenance operations.
|
|
6
|
+
* Provides safe operations that protect user work by only allowing destructive
|
|
7
|
+
* operations on temporary validation branches.
|
|
8
|
+
*/
|
|
9
|
+
export declare class GitHealthManager {
|
|
10
|
+
private git;
|
|
11
|
+
private config;
|
|
12
|
+
constructor(config?: Partial<RulesGitConfig>);
|
|
13
|
+
/**
|
|
14
|
+
* Detects and resolves any ongoing merge states in the repository
|
|
15
|
+
* This should be called before any migration operations
|
|
16
|
+
*
|
|
17
|
+
* Uses the robust gitGetBranchHealth function to diagnose issues:
|
|
18
|
+
* - git merge --abort → abandonne tout merge en cours
|
|
19
|
+
* - git reset --hard HEAD → nettoie index + working directory
|
|
20
|
+
*/
|
|
21
|
+
repairRepository(branch?: string): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Ensures we're on a safe branch before performing destructive operations
|
|
24
|
+
* Protects user work by refusing operations on main branches
|
|
25
|
+
*/
|
|
26
|
+
private ensureSafeBranch;
|
|
27
|
+
/**
|
|
28
|
+
* Force repair when git status fails
|
|
29
|
+
* PROTECTED: Only works on validation branches to protect user work
|
|
30
|
+
*/
|
|
31
|
+
private forceRepair;
|
|
32
|
+
/**
|
|
33
|
+
* Comprehensive health check for all important branches in the system
|
|
34
|
+
* Provides a complete overview of repository health
|
|
35
|
+
*/
|
|
36
|
+
diagnosticRepository(): Promise<GitHealthStatus[]>;
|
|
37
|
+
/**
|
|
38
|
+
* Display a concise health summary for a branch
|
|
39
|
+
*/
|
|
40
|
+
private displayHealthSummary;
|
|
41
|
+
/**
|
|
42
|
+
* Synchronizes and repairs all validation branches with the editor branch
|
|
43
|
+
*
|
|
44
|
+
* This function updates existing validation branches to stay in sync with rule-editor,
|
|
45
|
+
* not merging rule-editor into validation branches.
|
|
46
|
+
*
|
|
47
|
+
* Handles:
|
|
48
|
+
* - Échecs de synchronisation → tentative de réparation par branche
|
|
49
|
+
* - Branches inaccessibles → git checkout -f + git reset --hard HEAD (protected)
|
|
50
|
+
* - Working directory sale → git reset --hard HEAD (protected)
|
|
51
|
+
* - Retry automatique après réparation
|
|
52
|
+
* - Branches irréparables → log d'intervention manuelle
|
|
53
|
+
*/
|
|
54
|
+
syncValidationBranches(): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Migrates Git notes from the first commit of validation branches to the last one.
|
|
57
|
+
*
|
|
58
|
+
* Handles:
|
|
59
|
+
* - Notes déjà présentes sur HEAD → skip migration
|
|
60
|
+
* - Merge-base introuvable → skip branche avec warning
|
|
61
|
+
* - Notes orphelines sur anciens commits → copie vers HEAD + suppression
|
|
62
|
+
* - Branches sans historique de notes → ignore silencieusement
|
|
63
|
+
* - Erreurs de traitement par branche → continue avec les autres
|
|
64
|
+
*/
|
|
65
|
+
migrateNotes(): Promise<void>;
|
|
66
|
+
}
|