agentic-api 1.0.6 → 2.0.31
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 +336 -76
- 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/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 +13 -6
- package/dist/src/execute.js +351 -85
- 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 +15 -11
- package/dist/src/prompts.d.ts +3 -2
- package/dist/src/prompts.js +159 -19
- 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 +62 -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,900 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.gitEnsureRepositoryConfiguration = gitEnsureRepositoryConfiguration;
|
|
37
|
+
exports.gitEnsureRemoteConfiguration = gitEnsureRemoteConfiguration;
|
|
38
|
+
exports.gitInit = gitInit;
|
|
39
|
+
exports.gitSetupRepository = gitSetupRepository;
|
|
40
|
+
exports.gitShowConfiguration = gitShowConfiguration;
|
|
41
|
+
exports.gitCheckConfiguration = gitCheckConfiguration;
|
|
42
|
+
exports.gitCreateOrEditFile = gitCreateOrEditFile;
|
|
43
|
+
exports.gitEditFile = gitEditFile;
|
|
44
|
+
exports.gitRenameFile_git = gitRenameFile_git;
|
|
45
|
+
exports.gitRenameFile_fs = gitRenameFile_fs;
|
|
46
|
+
exports.gitRenameFile = gitRenameFile;
|
|
47
|
+
exports.gitDeleteFile = gitDeleteFile;
|
|
48
|
+
exports.gitGetBranchHealth = gitGetBranchHealth;
|
|
49
|
+
const fs_1 = require("fs");
|
|
50
|
+
const errors_1 = require("../errors");
|
|
51
|
+
const path_1 = require("path");
|
|
52
|
+
const fs = __importStar(require("fs/promises"));
|
|
53
|
+
const repo_tools_1 = require("./repo.tools");
|
|
54
|
+
/**
|
|
55
|
+
* Vérifie et configure un repository Git existant pour s'assurer qu'il a la configuration requise
|
|
56
|
+
* @param git Instance SimpleGit
|
|
57
|
+
* @param options Options de configuration
|
|
58
|
+
*/
|
|
59
|
+
async function gitEnsureRepositoryConfiguration(git, options = {}) {
|
|
60
|
+
const { remoteUrl = (0, repo_tools_1.gitLoad)().remoteUrl, remoteName = 'origin', forceUpdate = false } = options;
|
|
61
|
+
const changes = [];
|
|
62
|
+
try {
|
|
63
|
+
const gitConfig = (0, repo_tools_1.gitLoad)();
|
|
64
|
+
const isRepo = await git.checkIsRepo();
|
|
65
|
+
if (!isRepo) {
|
|
66
|
+
throw new errors_1.GitConfigurationError('Not a Git repository', 'repo_not_found');
|
|
67
|
+
}
|
|
68
|
+
// Vérifier la configuration de la branche par défaut
|
|
69
|
+
try {
|
|
70
|
+
const configs = await git.listConfig();
|
|
71
|
+
const currentDefaultBranch = configs.all['init.defaultbranch'];
|
|
72
|
+
const denyCurrentBranchConfig = configs.all['receive.denyCurrentBranch']?.toString().trim();
|
|
73
|
+
if (currentDefaultBranch !== gitConfig.mainBranch || forceUpdate) {
|
|
74
|
+
await git.addConfig('init.defaultBranch', gitConfig.mainBranch);
|
|
75
|
+
changes.push(`Set default branch to ${gitConfig.mainBranch}`);
|
|
76
|
+
}
|
|
77
|
+
if (denyCurrentBranchConfig !== 'updateInstead' && remoteUrl) {
|
|
78
|
+
await git.addConfig('receive.denyCurrentBranch', 'updateInstead');
|
|
79
|
+
changes.push('✓ Configured receive.denyCurrentBranch = updateInstead for local pushes');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
await git.addConfig('init.defaultBranch', gitConfig.mainBranch);
|
|
84
|
+
changes.push(`Set default branch to ${gitConfig.mainBranch}`);
|
|
85
|
+
}
|
|
86
|
+
// Configuration des Git Notes (avec ou sans remote)
|
|
87
|
+
try {
|
|
88
|
+
// Vérifier la configuration actuelle des notes
|
|
89
|
+
const notesConfig = await git.raw(['config', '--get', 'notes.rewriteRef']).catch(() => '');
|
|
90
|
+
if (!notesConfig.includes('refs/notes/commits') || forceUpdate) {
|
|
91
|
+
await git.addConfig('notes.rewriteRef', 'refs/notes/commits');
|
|
92
|
+
changes.push('✓ Configured Git Notes rewriteRef');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.warn('⚠️ Warning: Could not configure Git Notes:', error);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
configured: true,
|
|
100
|
+
changes
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
throw new errors_1.GitOperationError(`Failed to ensure repository configuration: ${error}`, 'repo_config', { error });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Configure le remote origin et les paramètres Git Notes
|
|
109
|
+
* @param git Instance SimpleGit
|
|
110
|
+
* @param config Configuration du remote
|
|
111
|
+
*/
|
|
112
|
+
async function gitEnsureRemoteConfiguration(git, config) {
|
|
113
|
+
const { remoteUrl = (0, repo_tools_1.gitLoad)().remoteUrl, remoteName = 'origin', quiet } = config;
|
|
114
|
+
if (!remoteUrl) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
// Vérifier si le remote existe
|
|
119
|
+
const remotes = await git.getRemotes(true);
|
|
120
|
+
const existingRemote = remotes.find(r => r.name === remoteName);
|
|
121
|
+
// Ajouter fetch pour les notes (peut déjà exister)
|
|
122
|
+
try {
|
|
123
|
+
if (!existingRemote) {
|
|
124
|
+
console.log(`🔗 Adding remote ${remoteName} with URL ${remoteUrl}`);
|
|
125
|
+
await git.addRemote(remoteName, remoteUrl);
|
|
126
|
+
await git.addConfig(`remote.${remoteName}.fetch`, '+refs/notes/*:refs/notes/*');
|
|
127
|
+
console.log(`✅ Added Git Notes fetch configuration for ${remoteName}`);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
await git.addConfig(`remote.${remoteName}.push`, 'refs/notes/*:refs/notes/*');
|
|
131
|
+
console.log(`✅ Added Git Notes push configuration`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
// Ignorer si la configuration existe déjà
|
|
136
|
+
console.log(`⚠️ Git Remote or Notes config may already exist`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (!quiet)
|
|
141
|
+
throw new errors_1.GitOperationError(`Failed to configure remote: ${error}`, 'remote_config', { error });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function gitInit(git, options = {}) {
|
|
145
|
+
try {
|
|
146
|
+
const gitConfig = (0, repo_tools_1.gitLoad)();
|
|
147
|
+
const { excludePatterns = [
|
|
148
|
+
'node_modules',
|
|
149
|
+
'.rag',
|
|
150
|
+
'.assets',
|
|
151
|
+
'.storage',
|
|
152
|
+
'dist',
|
|
153
|
+
'*.log',
|
|
154
|
+
'*.exe',
|
|
155
|
+
'*.dll',
|
|
156
|
+
'*.so',
|
|
157
|
+
'*.dylib',
|
|
158
|
+
'*.a',
|
|
159
|
+
'*.o',
|
|
160
|
+
'*.obj',
|
|
161
|
+
'*.lib',
|
|
162
|
+
'.env',
|
|
163
|
+
'.DS_Store',
|
|
164
|
+
], remoteUrl = gitConfig?.remoteUrl, remoteName = 'origin' } = options;
|
|
165
|
+
const isRepo = await git.checkIsRepo();
|
|
166
|
+
if (isRepo) {
|
|
167
|
+
// Vérifier la configuration du remote même si le repo existe
|
|
168
|
+
await gitEnsureRemoteConfiguration(git, { remoteUrl, remoteName });
|
|
169
|
+
const existingBranches = await git.branchLocal();
|
|
170
|
+
const hasMainBranch = existingBranches.all.includes(gitConfig.mainBranch);
|
|
171
|
+
const hasDraftBranch = existingBranches.all.includes(gitConfig.draftBranch);
|
|
172
|
+
//
|
|
173
|
+
// missing configuration check
|
|
174
|
+
if (hasMainBranch && hasDraftBranch) {
|
|
175
|
+
if (gitConfig.verbose)
|
|
176
|
+
console.log(`Repository already initialized with correct configuration at ${gitConfig.repoPath}`);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (gitConfig.verbose)
|
|
180
|
+
console.log(`🌶️ Repository already initialized with correct configuration at ${gitConfig.repoPath}`);
|
|
181
|
+
if (gitConfig.verbose)
|
|
182
|
+
console.log(`🌶️ Existing branches: ${existingBranches.all}`);
|
|
183
|
+
// Si le dépôt existe mais n'est pas correctement configuré
|
|
184
|
+
throw new errors_1.GitConfigurationError('Repository exists but is not properly configured. Please check branch names and configuration.', 'repo_config');
|
|
185
|
+
}
|
|
186
|
+
console.log(`🌳 Initializing repository at ${gitConfig.repoPath}`);
|
|
187
|
+
// Créer le répertoire temporaire
|
|
188
|
+
await fs.mkdir(gitConfig.repoPath, { recursive: true });
|
|
189
|
+
// Create upload directory (absolute path)
|
|
190
|
+
await fs.mkdir(gitConfig.uploadPath, { recursive: true });
|
|
191
|
+
console.log(`✅ Created upload directory: ${gitConfig.uploadPath}`);
|
|
192
|
+
// Initialiser le repository Git
|
|
193
|
+
await git.init();
|
|
194
|
+
// Configuration Git de base
|
|
195
|
+
await git.addConfig('init.defaultBranch', gitConfig.mainBranch);
|
|
196
|
+
// Configurer le remote origin et Git Notes si spécifié
|
|
197
|
+
await gitEnsureRemoteConfiguration(git, { remoteUrl, remoteName, quiet: true });
|
|
198
|
+
// Configuration des Git Notes (avec ou sans remote)
|
|
199
|
+
try {
|
|
200
|
+
await git.addConfig('notes.rewriteRef', 'refs/notes/commits');
|
|
201
|
+
console.log('✓ Configured Git Notes rewriteRef');
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
console.warn('⚠️ Warning: Could not configure Git Notes:', error);
|
|
205
|
+
}
|
|
206
|
+
// Créer .gitignore si des patterns sont spécifiés
|
|
207
|
+
if (excludePatterns.length > 0) {
|
|
208
|
+
const gitignoreContent = excludePatterns.join('\n') + '\n';
|
|
209
|
+
await fs.writeFile((0, path_1.join)(gitConfig.repoPath, '.gitignore'), gitignoreContent, 'utf8');
|
|
210
|
+
await git.add('.gitignore');
|
|
211
|
+
await git.commit('chore: Add .gitignore', ['.gitignore']);
|
|
212
|
+
}
|
|
213
|
+
// Créer la branche main directement
|
|
214
|
+
await git.checkout(['-b', gitConfig.mainBranch]);
|
|
215
|
+
// Créer la branche draft à partir de main
|
|
216
|
+
await git.checkoutBranch(gitConfig.draftBranch, gitConfig.mainBranch);
|
|
217
|
+
console.log(`🌳 Repository initialized successfully at ${gitConfig.repoPath}`);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
throw new errors_1.GitOperationError(`Failed to init repository: ${error}`, 'repo_create', { error });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Fonction helper pour initialiser ou configurer un repository avec la configuration Git standard
|
|
225
|
+
* Cette fonction combine gitInit et gitEnsureRepositoryConfiguration pour une utilisation simple
|
|
226
|
+
* @param git Instance SimpleGit
|
|
227
|
+
* @param options Options de configuration
|
|
228
|
+
*/
|
|
229
|
+
async function gitSetupRepository(git, options = {}) {
|
|
230
|
+
const { remoteUrl, // Pas de valeur par défaut - peut être undefined pour un repository local
|
|
231
|
+
remoteName = 'origin', enableGitNotes = true, excludePatterns = [], forceReconfig = false } = options;
|
|
232
|
+
let initialized = false;
|
|
233
|
+
let configured = false;
|
|
234
|
+
const changes = [];
|
|
235
|
+
try {
|
|
236
|
+
const isRepo = await git.checkIsRepo();
|
|
237
|
+
if (!isRepo) {
|
|
238
|
+
// Initialiser le repository
|
|
239
|
+
console.log('🚀 Initializing new Git repository with standard configuration...');
|
|
240
|
+
await gitInit(git, {
|
|
241
|
+
excludePatterns,
|
|
242
|
+
remoteUrl,
|
|
243
|
+
remoteName,
|
|
244
|
+
enableGitNotes
|
|
245
|
+
});
|
|
246
|
+
initialized = true;
|
|
247
|
+
configured = true;
|
|
248
|
+
changes.push('Repository initialized with full configuration');
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
console.log('📋 Repository exists, verifying configuration...');
|
|
252
|
+
// Configurer le repository existant
|
|
253
|
+
const result = await gitEnsureRepositoryConfiguration(git, {
|
|
254
|
+
remoteUrl,
|
|
255
|
+
remoteName,
|
|
256
|
+
forceUpdate: forceReconfig
|
|
257
|
+
});
|
|
258
|
+
configured = result.configured;
|
|
259
|
+
changes.push(...result.changes);
|
|
260
|
+
}
|
|
261
|
+
// Vérification finale
|
|
262
|
+
const configCheck = await gitCheckConfiguration(git);
|
|
263
|
+
console.log('📊 Configuration check results:', {
|
|
264
|
+
remoteOrigin: configCheck.remoteOrigin,
|
|
265
|
+
remoteOriginUrl: configCheck.remoteOriginUrl,
|
|
266
|
+
gitNotesRemoteFetch: configCheck.gitNotesRemoteFetch,
|
|
267
|
+
gitNotesRemotePush: configCheck.gitNotesRemotePush,
|
|
268
|
+
defaultBranch: configCheck.defaultBranch,
|
|
269
|
+
isValid: configCheck.isValid
|
|
270
|
+
});
|
|
271
|
+
return {
|
|
272
|
+
initialized,
|
|
273
|
+
configured,
|
|
274
|
+
changes
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
throw new errors_1.GitOperationError(`Failed to setup repository: ${error}`, 'repo_setup', { error });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Fonction helper pour vérifier et afficher la configuration Git d'un repository
|
|
283
|
+
* @param git Instance SimpleGit
|
|
284
|
+
* @returns Configuration détaillée
|
|
285
|
+
*/
|
|
286
|
+
async function gitShowConfiguration(git) {
|
|
287
|
+
try {
|
|
288
|
+
const config = await gitCheckConfiguration(git);
|
|
289
|
+
console.log('\n🔍 === Git Repository Configuration Report ===');
|
|
290
|
+
console.log(`Repository Path: ${config.repoPath ? '✅' : '❌'}`);
|
|
291
|
+
console.log(`Draft Branch: ${config.draftBranch ? '✅' : '❌'}`);
|
|
292
|
+
console.log(`Main Branch: ${config.mainBranch ? '✅' : '❌'}`);
|
|
293
|
+
console.log(`Validation Prefix: ${config.validationPrefix ? '✅' : '❌'}`);
|
|
294
|
+
console.log(`Git Instance: ${config.instance ? '✅' : '❌'}`);
|
|
295
|
+
console.log(`Git Notes Config: ${config.gitNotes ? '✅' : '❌'}`);
|
|
296
|
+
if (config.remoteOrigin !== undefined) {
|
|
297
|
+
console.log(`\n🔗 Remote Configuration:`);
|
|
298
|
+
console.log(`Origin Remote: ${config.remoteOrigin ? '✅' : '❌'}`);
|
|
299
|
+
console.log(`Remote URL: ${config.remoteOriginUrl}`);
|
|
300
|
+
console.log(`Git Notes Fetch: ${config.gitNotesRemoteFetch ? '✅' : '❌'}`);
|
|
301
|
+
console.log(`Git Notes Push: ${config.gitNotesRemotePush ? '✅' : '❌'}`);
|
|
302
|
+
console.log(`Default Branch: ${config.defaultBranch ? '✅' : '❌'} (${config.defaultBranchValue})`);
|
|
303
|
+
}
|
|
304
|
+
console.log(`\n🎯 Overall Status: ${config.isValid ? '✅ VALID' : '❌ NEEDS CONFIGURATION'}`);
|
|
305
|
+
console.log('===============================================\n');
|
|
306
|
+
return config;
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
throw new errors_1.GitOperationError(`Failed to show configuration: ${error}`, 'config_check', { error });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Vérifie la validité de la configuration Git (utilitaire)
|
|
314
|
+
* @param git Instance Git
|
|
315
|
+
* @returns Promise<boolean> True si la configuration est valide
|
|
316
|
+
*/
|
|
317
|
+
async function gitCheckConfiguration(git) {
|
|
318
|
+
const gitConfig = (0, repo_tools_1.gitLoad)();
|
|
319
|
+
const check = {
|
|
320
|
+
repoPath: (0, fs_1.existsSync)(gitConfig.repoPath),
|
|
321
|
+
uploadPath: (0, fs_1.existsSync)(gitConfig.uploadPath),
|
|
322
|
+
draftBranch: gitConfig.draftBranch.length > 0,
|
|
323
|
+
mainBranch: gitConfig.mainBranch.length > 0,
|
|
324
|
+
validationPrefix: gitConfig.validationPrefix.length > 0,
|
|
325
|
+
releasePrefix: gitConfig.releasePrefix.length > 0,
|
|
326
|
+
validationToken: gitConfig.validationToken.length > 0,
|
|
327
|
+
instance: gitConfig.instance && !!gitConfig.instance.add,
|
|
328
|
+
concurrency: gitConfig.concurrency &&
|
|
329
|
+
typeof gitConfig.concurrency.maxRetries === 'number' &&
|
|
330
|
+
typeof gitConfig.concurrency.retryDelayMs === 'number' &&
|
|
331
|
+
typeof gitConfig.concurrency.timeoutMs === 'number',
|
|
332
|
+
gitNotes: gitConfig.gitNotes &&
|
|
333
|
+
typeof gitConfig.gitNotes.namespace === 'string' &&
|
|
334
|
+
gitConfig.gitNotes.namespace.length > 0 &&
|
|
335
|
+
typeof gitConfig.gitNotes.enabled === 'boolean',
|
|
336
|
+
};
|
|
337
|
+
// Vérifications avancées si une instance Git est fournie
|
|
338
|
+
if (git) {
|
|
339
|
+
try {
|
|
340
|
+
check.remoteOrigin = false;
|
|
341
|
+
check.defaultBranch = false;
|
|
342
|
+
check.gitNotesRemoteFetch = false;
|
|
343
|
+
check.gitNotesRemotePush = false;
|
|
344
|
+
const isRepo = await git.checkIsRepo();
|
|
345
|
+
if (isRepo) {
|
|
346
|
+
// Vérifier la configuration du remote origin
|
|
347
|
+
const remotes = await git.getRemotes(true);
|
|
348
|
+
const originRemote = remotes.find(r => r.name === 'origin');
|
|
349
|
+
check.remoteOrigin = !!originRemote;
|
|
350
|
+
check.remoteOriginUrl = originRemote?.refs?.fetch || 'not configured';
|
|
351
|
+
// Vérifier la branche par défaut
|
|
352
|
+
try {
|
|
353
|
+
const configs = await git.listConfig();
|
|
354
|
+
const defaultBranch = configs.all['init.defaultbranch'];
|
|
355
|
+
check.defaultBranch = defaultBranch === gitConfig.mainBranch;
|
|
356
|
+
check.defaultBranchValue = defaultBranch || 'not set';
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
check.defaultBranchValue = 'error reading config';
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
check.remoteOriginUrl = 'repository not initialized';
|
|
364
|
+
check.defaultBranchValue = 'repository not initialized';
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
check.remoteOriginUrl = `error: ${error}`;
|
|
369
|
+
check.defaultBranchValue = `error: ${error}`;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// Calculate isValid based on core properties only (exclude remote-related checks)
|
|
373
|
+
const coreProperties = ['repoPath', 'uploadPath', 'draftBranch', 'mainBranch', 'validationPrefix', 'releasePrefix', 'validationToken', 'instance', 'concurrency', 'gitNotes', 'defaultBranch'];
|
|
374
|
+
check.isValid = coreProperties.every((key) => check[key] === true);
|
|
375
|
+
return check;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Crée un fichier dans la branche draft (opération bas niveau)
|
|
379
|
+
* @param git Instance Git
|
|
380
|
+
* @param filePath Chemin du fichier
|
|
381
|
+
* @param PR Nom de la branche de Pull Request
|
|
382
|
+
* @param content Contenu du fichier
|
|
383
|
+
* @param user Utilisateur qui effectue l'opération
|
|
384
|
+
* @param config Configuration Git (optionnel, utilise la config globale)
|
|
385
|
+
* @throws GitOperationError si la création échoue
|
|
386
|
+
*/
|
|
387
|
+
async function gitCreateOrEditFile(git, filePath, PR, content, user, config) {
|
|
388
|
+
//
|
|
389
|
+
// lock checkout is global
|
|
390
|
+
await (0, repo_tools_1.lock)(`checkout`);
|
|
391
|
+
let currentBranch;
|
|
392
|
+
try {
|
|
393
|
+
const gitConf = (0, repo_tools_1.gitLoad)(config);
|
|
394
|
+
//
|
|
395
|
+
// secure main and draft branches (on production)
|
|
396
|
+
if (!gitConf?.canForce && PR == gitConf.mainBranch) {
|
|
397
|
+
throw new errors_1.GitOperationError('Cannot create files in main branch', 'gitCreateOrEditFile', { filePath, PR });
|
|
398
|
+
}
|
|
399
|
+
if (!gitConf?.canForce && PR == gitConf.draftBranch) {
|
|
400
|
+
throw new errors_1.GitOperationError('Cannot create files in draft branch', 'gitCreateOrEditFile', { filePath, PR });
|
|
401
|
+
}
|
|
402
|
+
currentBranch = await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
403
|
+
if (currentBranch !== PR) {
|
|
404
|
+
await git.checkout(PR);
|
|
405
|
+
}
|
|
406
|
+
const oldHead = (await git.revparse(['HEAD'])).trim();
|
|
407
|
+
//
|
|
408
|
+
// without note the branche is not a valid PR branch
|
|
409
|
+
const oldNote = await (0, repo_tools_1.gitReadNote)(git, PR, gitConf.gitNotes.namespace, 10);
|
|
410
|
+
const commit = await _writeFileAndCommit(git, filePath, content, user, gitConf, `commit: ${filePath}`);
|
|
411
|
+
if (!oldNote) {
|
|
412
|
+
return {
|
|
413
|
+
hash: commit.commit,
|
|
414
|
+
date: new Date(),
|
|
415
|
+
message: `commit: ${filePath}`,
|
|
416
|
+
author: {
|
|
417
|
+
name: user.name,
|
|
418
|
+
email: user.email
|
|
419
|
+
},
|
|
420
|
+
branch: PR
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
// Determine the current head after the commit attempt.
|
|
424
|
+
const newHead = (await git.revparse(['HEAD'])).trim();
|
|
425
|
+
const newFiles = await (0, repo_tools_1.gitGetDiffFiles)(git, PR, oldNote.mergeBase);
|
|
426
|
+
const updatedNote = {
|
|
427
|
+
...oldNote,
|
|
428
|
+
files: newFiles,
|
|
429
|
+
};
|
|
430
|
+
// Write the note to the current HEAD of the PR branch.
|
|
431
|
+
// If no commit was made, this overwrites the note on oldHead.
|
|
432
|
+
// If a new commit was made, this writes the note on newHead.
|
|
433
|
+
await (0, repo_tools_1.gitWriteNote)(git, PR, updatedNote, gitConf.gitNotes.namespace);
|
|
434
|
+
// If a new commit was created, the old note on oldHead is now obsolete and should be removed.
|
|
435
|
+
if (newHead !== oldHead) {
|
|
436
|
+
await (0, repo_tools_1.gitDeleteNote)(git, oldHead, gitConf.gitNotes.namespace);
|
|
437
|
+
}
|
|
438
|
+
const log = await git.log(['-n', '1', PR, '--', filePath]);
|
|
439
|
+
//
|
|
440
|
+
// SHOULD NEVER HAPPEN
|
|
441
|
+
if (!log.latest) {
|
|
442
|
+
throw new errors_1.GitOperationError(`Failed to get commit history for ${filePath}`, 'gitCreateOrEditFile', { filePath });
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
hash: log.latest.hash,
|
|
446
|
+
date: new Date(log.latest.date),
|
|
447
|
+
message: log.latest.message,
|
|
448
|
+
author: {
|
|
449
|
+
name: log.latest.author_name,
|
|
450
|
+
email: log.latest.author_email
|
|
451
|
+
},
|
|
452
|
+
branch: PR
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
throw new errors_1.GitOperationError(`Failed to create or edit file "${filePath}": ${error}`, 'gitCreateOrEditFile', { filePath, error });
|
|
457
|
+
}
|
|
458
|
+
finally {
|
|
459
|
+
if (currentBranch && currentBranch !== PR) {
|
|
460
|
+
await git.checkout(currentBranch);
|
|
461
|
+
}
|
|
462
|
+
(0, repo_tools_1.unlock)(`checkout`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Modifie un fichier dans la branche draft (opération bas niveau)
|
|
467
|
+
* @param git Instance Git
|
|
468
|
+
* @param filePath Chemin du fichier
|
|
469
|
+
* @param PR Nom de la branche de Pull Request
|
|
470
|
+
* @param content Contenu du fichier
|
|
471
|
+
* @param user Utilisateur qui effectue l'opération
|
|
472
|
+
* @param config Configuration Git (optionnel, utilise la config globale)
|
|
473
|
+
* @throws GitOperationError si la modification échoue
|
|
474
|
+
*/
|
|
475
|
+
async function gitEditFile(git, filePath, PR, content, user, config) {
|
|
476
|
+
const exists = await (0, repo_tools_1.gitFileExistsInBranch)(git, filePath, PR);
|
|
477
|
+
if (!exists) {
|
|
478
|
+
throw new errors_1.FileNotFoundError(filePath, PR);
|
|
479
|
+
}
|
|
480
|
+
return await gitCreateOrEditFile(git, filePath, PR, content, user, config);
|
|
481
|
+
}
|
|
482
|
+
async function gitRenameFile_git(git, oldFileName, newFileName, branch, user, config) {
|
|
483
|
+
// Si les noms sont identiques, pas besoin de renommer
|
|
484
|
+
if (oldFileName === newFileName) {
|
|
485
|
+
return {
|
|
486
|
+
hash: '',
|
|
487
|
+
date: new Date(),
|
|
488
|
+
message: 'No rename needed - files are identical',
|
|
489
|
+
author: { name: user.name, email: user.email },
|
|
490
|
+
branch: branch
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
const gitConf = (0, repo_tools_1.gitLoad)(config);
|
|
494
|
+
const currentBranch = await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
495
|
+
try {
|
|
496
|
+
await (0, repo_tools_1.lock)(`checkout`);
|
|
497
|
+
// Vérifier que l'ancien fichier existe
|
|
498
|
+
const oldFileExists = await (0, repo_tools_1.gitFileExistsInBranch)(git, oldFileName, branch);
|
|
499
|
+
if (!oldFileExists) {
|
|
500
|
+
throw new errors_1.FileNotFoundError(oldFileName, branch);
|
|
501
|
+
}
|
|
502
|
+
// Vérifier que le nouveau fichier n'existe pas déjà
|
|
503
|
+
const newFileExists = await (0, repo_tools_1.gitFileExistsInBranch)(git, newFileName, branch);
|
|
504
|
+
if (newFileExists) {
|
|
505
|
+
throw new Error(`Le fichier de destination "${newFileName}" existe déjà dans la branche "${branch}"`);
|
|
506
|
+
}
|
|
507
|
+
await git.checkout(branch);
|
|
508
|
+
// Rename file with git mv (atomique dans Git)
|
|
509
|
+
await git.raw(['mv', oldFileName, newFileName]);
|
|
510
|
+
// Commit - git mv automatically stages changes
|
|
511
|
+
const commit = await git.commit(`rename: ${oldFileName} → ${newFileName}`, {
|
|
512
|
+
'--author': `${user.name} <${user.email}>`
|
|
513
|
+
});
|
|
514
|
+
return {
|
|
515
|
+
hash: commit.commit,
|
|
516
|
+
date: new Date(),
|
|
517
|
+
message: `rename: ${oldFileName} → ${newFileName}`,
|
|
518
|
+
author: {
|
|
519
|
+
name: user.name,
|
|
520
|
+
email: user.email
|
|
521
|
+
},
|
|
522
|
+
branch: branch
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
// En cas d'erreur, essayer de restaurer l'état initial
|
|
527
|
+
try {
|
|
528
|
+
await git.reset(['--hard']);
|
|
529
|
+
}
|
|
530
|
+
catch (resetError) {
|
|
531
|
+
console.warn('Could not reset after failed rename:', resetError);
|
|
532
|
+
}
|
|
533
|
+
throw error;
|
|
534
|
+
}
|
|
535
|
+
finally {
|
|
536
|
+
if (currentBranch) {
|
|
537
|
+
await git.checkout(currentBranch);
|
|
538
|
+
}
|
|
539
|
+
(0, repo_tools_1.unlock)(`checkout`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
async function gitRenameFile_fs(git, oldFileName, newFileName, branch, user, config) {
|
|
543
|
+
// Si les noms sont identiques, pas besoin de renommer
|
|
544
|
+
if (oldFileName === newFileName) {
|
|
545
|
+
return {
|
|
546
|
+
hash: '',
|
|
547
|
+
date: new Date(),
|
|
548
|
+
message: 'No rename needed - files are identical',
|
|
549
|
+
author: { name: user.name, email: user.email },
|
|
550
|
+
branch: branch
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
const gitConf = (0, repo_tools_1.gitLoad)(config);
|
|
554
|
+
try {
|
|
555
|
+
await (0, repo_tools_1.lock)(`checkout`);
|
|
556
|
+
const fullNewPath = (0, path_1.join)(gitConf.uploadPath, newFileName);
|
|
557
|
+
const fullOldPath = (0, path_1.join)(gitConf.uploadPath, oldFileName);
|
|
558
|
+
// Vérifier que l'ancien fichier existe
|
|
559
|
+
const oldFileExists = (0, fs_1.existsSync)(fullOldPath);
|
|
560
|
+
if (!oldFileExists) {
|
|
561
|
+
throw new errors_1.FileNotFoundError(oldFileName, branch);
|
|
562
|
+
}
|
|
563
|
+
// Vérifier que le nouveau fichier n'existe pas déjà
|
|
564
|
+
const newFileExists = (0, fs_1.existsSync)(fullNewPath);
|
|
565
|
+
if (newFileExists) {
|
|
566
|
+
throw new Error(`Le fichier de destination "${newFileName}" existe déjà sur le système de fichiers`);
|
|
567
|
+
}
|
|
568
|
+
// Renommage atomique filesystem
|
|
569
|
+
// Utiliser copyFile + unlink pour plus de sécurité que rename() sur certains systèmes
|
|
570
|
+
try {
|
|
571
|
+
await fs.copyFile(fullOldPath, fullNewPath);
|
|
572
|
+
await fs.unlink(fullOldPath);
|
|
573
|
+
}
|
|
574
|
+
catch (copyError) {
|
|
575
|
+
// En cas d'erreur, nettoyer le nouveau fichier s'il a été créé
|
|
576
|
+
if ((0, fs_1.existsSync)(fullNewPath)) {
|
|
577
|
+
try {
|
|
578
|
+
await fs.unlink(fullNewPath);
|
|
579
|
+
}
|
|
580
|
+
catch (cleanupError) {
|
|
581
|
+
console.warn('Could not cleanup destination file after failed rename:', cleanupError);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
throw copyError;
|
|
585
|
+
}
|
|
586
|
+
return {
|
|
587
|
+
hash: '',
|
|
588
|
+
date: new Date(),
|
|
589
|
+
message: `filesystem rename: ${oldFileName} → ${newFileName}`,
|
|
590
|
+
author: {
|
|
591
|
+
name: user.name,
|
|
592
|
+
email: user.email
|
|
593
|
+
},
|
|
594
|
+
branch: branch
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
throw error;
|
|
599
|
+
}
|
|
600
|
+
finally {
|
|
601
|
+
(0, repo_tools_1.unlock)(`checkout`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Renomme un fichier de manière atomique (Git ou filesystem selon le contexte)
|
|
606
|
+
* @param git Instance SimpleGit
|
|
607
|
+
* @param oldFileName Ancien nom du fichier
|
|
608
|
+
* @param newFileName Nouveau nom du fichier
|
|
609
|
+
* @param branch Branche où effectuer le renommage
|
|
610
|
+
* @param user Utilisateur effectuant l'opération
|
|
611
|
+
* @param config Configuration Git optionnelle
|
|
612
|
+
* @returns Historique du commit de renommage
|
|
613
|
+
*/
|
|
614
|
+
async function gitRenameFile(git, oldFileName, newFileName, branch, user, config) {
|
|
615
|
+
// Si les noms sont identiques, pas besoin de renommer
|
|
616
|
+
if (oldFileName === newFileName) {
|
|
617
|
+
return {
|
|
618
|
+
hash: '',
|
|
619
|
+
date: new Date(),
|
|
620
|
+
message: 'No rename needed - files are identical',
|
|
621
|
+
author: { name: user.name, email: user.email },
|
|
622
|
+
branch: branch
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
// load config
|
|
626
|
+
const gitConf = (0, repo_tools_1.gitLoad)(config);
|
|
627
|
+
// Pour les fichiers dans Git, utiliser Git uniquement (pas de fallback filesystem)
|
|
628
|
+
try {
|
|
629
|
+
//
|
|
630
|
+
//FIXME: add a check about the branch (file can be NEW and on branch)
|
|
631
|
+
const fullOldPath = (0, path_1.join)(gitConf.uploadPath, oldFileName);
|
|
632
|
+
if ((0, fs_1.existsSync)(fullOldPath)) {
|
|
633
|
+
return await gitRenameFile_fs(git, oldFileName, newFileName, branch, user, config);
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
return await gitRenameFile_git(git, oldFileName, newFileName, branch, user, config);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
// Pour les vraies branches Git, ne pas faire de fallback filesystem
|
|
641
|
+
// Seules les branches NEW utilisent le filesystem
|
|
642
|
+
throw error;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Supprime un fichier d'une branche Git (opération bas niveau)
|
|
647
|
+
* @param git Instance Git
|
|
648
|
+
* @param filePath Chemin du fichier à supprimer
|
|
649
|
+
* @param branch Nom de la branche où supprimer le fichier
|
|
650
|
+
* @param user Utilisateur qui effectue l'opération
|
|
651
|
+
* @param config Configuration Git (optionnel, utilise la config globale)
|
|
652
|
+
* @throws GitOperationError si la suppression échoue
|
|
653
|
+
*/
|
|
654
|
+
async function gitDeleteFile(git, filePath, branch, user, config) {
|
|
655
|
+
await (0, repo_tools_1.lock)(`checkout`);
|
|
656
|
+
let currentBranch;
|
|
657
|
+
try {
|
|
658
|
+
const gitConf = (0, repo_tools_1.gitLoad)(config);
|
|
659
|
+
// Sécurité : interdire la suppression dans les branches de production
|
|
660
|
+
if (!gitConf?.canForce && branch === gitConf.mainBranch) {
|
|
661
|
+
throw new errors_1.GitOperationError('Cannot delete files in main branch', 'gitDeleteFile', { filePath, branch });
|
|
662
|
+
}
|
|
663
|
+
if (!gitConf?.canForce && branch === gitConf.draftBranch) {
|
|
664
|
+
throw new errors_1.GitOperationError('Cannot delete files in draft branch', 'gitDeleteFile', { filePath, branch });
|
|
665
|
+
}
|
|
666
|
+
// Vérifier que le fichier existe avant de le supprimer
|
|
667
|
+
const exists = await (0, repo_tools_1.gitFileExistsInBranch)(git, filePath, branch);
|
|
668
|
+
if (!exists) {
|
|
669
|
+
throw new errors_1.FileNotFoundError(filePath, branch);
|
|
670
|
+
}
|
|
671
|
+
currentBranch = await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
672
|
+
if (currentBranch !== branch) {
|
|
673
|
+
await git.checkout(branch);
|
|
674
|
+
}
|
|
675
|
+
const oldHead = (await git.revparse(['HEAD'])).trim();
|
|
676
|
+
// Lire l'ancienne note pour la mettre à jour
|
|
677
|
+
const oldNote = await (0, repo_tools_1.gitReadNote)(git, branch, gitConf.gitNotes.namespace, 10);
|
|
678
|
+
// Supprimer le fichier avec git rm
|
|
679
|
+
await git.raw(['rm', filePath]);
|
|
680
|
+
// Commit la suppression
|
|
681
|
+
const commit = await git.commit(`delete: ${filePath}`, {
|
|
682
|
+
'--author': `${user.name} <${user.email}>`
|
|
683
|
+
});
|
|
684
|
+
// Mettre à jour la note si elle existe (pour les branches PR)
|
|
685
|
+
if (oldNote) {
|
|
686
|
+
const newHead = (await git.revparse(['HEAD'])).trim();
|
|
687
|
+
const newFiles = await (0, repo_tools_1.gitGetDiffFiles)(git, branch, oldNote.mergeBase);
|
|
688
|
+
const updatedNote = {
|
|
689
|
+
...oldNote,
|
|
690
|
+
files: newFiles,
|
|
691
|
+
};
|
|
692
|
+
// Écrire la note sur le nouveau HEAD
|
|
693
|
+
await (0, repo_tools_1.gitWriteNote)(git, branch, updatedNote, gitConf.gitNotes.namespace);
|
|
694
|
+
// Supprimer l'ancienne note si un nouveau commit a été créé
|
|
695
|
+
if (newHead !== oldHead) {
|
|
696
|
+
await (0, repo_tools_1.gitDeleteNote)(git, oldHead, gitConf.gitNotes.namespace);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return {
|
|
700
|
+
hash: commit.commit,
|
|
701
|
+
date: new Date(),
|
|
702
|
+
message: `delete: ${filePath}`,
|
|
703
|
+
author: {
|
|
704
|
+
name: user.name,
|
|
705
|
+
email: user.email
|
|
706
|
+
},
|
|
707
|
+
branch: branch
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
throw new errors_1.GitOperationError(`Failed to delete file "${filePath}" from branch "${branch}": ${error}`, 'gitDeleteFile', { filePath, branch, error });
|
|
712
|
+
}
|
|
713
|
+
finally {
|
|
714
|
+
if (currentBranch && currentBranch !== branch) {
|
|
715
|
+
await git.checkout(currentBranch);
|
|
716
|
+
}
|
|
717
|
+
(0, repo_tools_1.unlock)(`checkout`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* PRIVATE TOOLS
|
|
722
|
+
*/
|
|
723
|
+
const _writeFileAndCommit = async (git, filePath, content, user, config, commitMsg) => {
|
|
724
|
+
const gitConf = config;
|
|
725
|
+
if (!gitConf || !gitConf.repoPath) {
|
|
726
|
+
throw new errors_1.GitOperationError('_writeFileAndCommit requires a config with a valid repoPath.', 'internal_error');
|
|
727
|
+
}
|
|
728
|
+
// Créer les répertoires parents si nécessaire
|
|
729
|
+
const fullPath = (0, path_1.join)(gitConf.repoPath, filePath);
|
|
730
|
+
const dir = (0, path_1.dirname)(fullPath);
|
|
731
|
+
await fs.mkdir(dir, { recursive: true });
|
|
732
|
+
// Écrire le fichier
|
|
733
|
+
await fs.writeFile(fullPath, content, { encoding: 'utf8', flag: 'w' });
|
|
734
|
+
await git.add(filePath);
|
|
735
|
+
const commit = await git.commit(commitMsg || `commit`, [filePath], {
|
|
736
|
+
'--author': `${user.name} <${user.email}>`
|
|
737
|
+
});
|
|
738
|
+
return commit;
|
|
739
|
+
};
|
|
740
|
+
/**
|
|
741
|
+
* Diagnostique l'état de santé d'une branche Git et détecte les problèmes bloquants.
|
|
742
|
+
*
|
|
743
|
+
* **Problèmes bloquants détectés :**
|
|
744
|
+
* - ❌ **Branche inexistante** : La branche spécifiée n'existe pas dans le dépôt
|
|
745
|
+
* - ❌ **Branche inaccessible** : Impossible de faire un checkout vers la branche
|
|
746
|
+
* - ❌ **Merge en cours** : Opération de merge interrompue (fichiers .git/MERGE_HEAD présents)
|
|
747
|
+
* - ❌ **Fichiers en conflit** : Conflits de merge non résolus (status.conflicted)
|
|
748
|
+
* - ❌ **Index Git corrompu** : Index illisible ou corrompu (git status échoue)
|
|
749
|
+
* - ⚠️ **Fichiers modifiés** : Changements non commités dans le working directory
|
|
750
|
+
* - ⚠️ **Fichiers stagés** : Changements en attente de commit dans l'index
|
|
751
|
+
*
|
|
752
|
+
* **Méthode de diagnostic :**
|
|
753
|
+
* 1. Vérification de l'existence de la branche (git branch --list)
|
|
754
|
+
* 2. Test d'accessibilité (git checkout)
|
|
755
|
+
* 3. Analyse de l'état (git status)
|
|
756
|
+
* 4. Détection des merges en cours (fichiers .git/MERGE_*)
|
|
757
|
+
*
|
|
758
|
+
* @param git Instance SimpleGit configurée
|
|
759
|
+
* @param branch Nom de la branche à diagnostiquer (optionnel, utilise la branche courante si non spécifié)
|
|
760
|
+
* @returns Promise<GitHealthStatus> Diagnostic complet avec liste des problèmes et recommandations de réparation
|
|
761
|
+
*/
|
|
762
|
+
async function gitGetBranchHealth(git, branch) {
|
|
763
|
+
const gitConfig = (0, repo_tools_1.gitLoad)();
|
|
764
|
+
let currentBranch = branch;
|
|
765
|
+
// Si pas de branche spécifiée, utiliser la branche courante
|
|
766
|
+
if (!currentBranch) {
|
|
767
|
+
try {
|
|
768
|
+
currentBranch = await git.revparse(['--abbrev-ref', 'HEAD']);
|
|
769
|
+
}
|
|
770
|
+
catch (error) {
|
|
771
|
+
return createUnhealthyStatus('unknown', false, false, false, false, 0, 0, 0, 0, false, ['Impossible de déterminer la branche courante'], ['Vérifier l\'état du dépôt Git']);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const health = {
|
|
775
|
+
branch: currentBranch,
|
|
776
|
+
exists: false,
|
|
777
|
+
accessible: false,
|
|
778
|
+
clean: false,
|
|
779
|
+
mergeInProgress: false,
|
|
780
|
+
conflictedFiles: 0,
|
|
781
|
+
modifiedFiles: 0,
|
|
782
|
+
untrackedFiles: 0,
|
|
783
|
+
stagedFiles: 0,
|
|
784
|
+
indexReadable: false,
|
|
785
|
+
healthy: false,
|
|
786
|
+
issues: [],
|
|
787
|
+
recommendations: []
|
|
788
|
+
};
|
|
789
|
+
try {
|
|
790
|
+
// 1. Vérifier si la branche existe
|
|
791
|
+
const branches = await git.branchLocal();
|
|
792
|
+
health.exists = branches.all.includes(currentBranch);
|
|
793
|
+
if (!health.exists) {
|
|
794
|
+
health.issues.push(`La branche '${currentBranch}' n'existe pas`);
|
|
795
|
+
health.recommendations.push(`Créer la branche ou vérifier le nom`);
|
|
796
|
+
return health;
|
|
797
|
+
}
|
|
798
|
+
// 2. Tenter un checkout pour vérifier l'accessibilité
|
|
799
|
+
const originalBranch = await git.revparse(['--abbrev-ref', 'HEAD']).catch(() => null);
|
|
800
|
+
try {
|
|
801
|
+
if (originalBranch !== currentBranch) {
|
|
802
|
+
await git.checkout(currentBranch);
|
|
803
|
+
}
|
|
804
|
+
health.accessible = true;
|
|
805
|
+
// 3. Vérifier l'état avec git status
|
|
806
|
+
try {
|
|
807
|
+
const status = await git.status();
|
|
808
|
+
health.indexReadable = true;
|
|
809
|
+
health.clean = status.isClean();
|
|
810
|
+
health.conflictedFiles = status.conflicted.length;
|
|
811
|
+
health.modifiedFiles = status.modified.length;
|
|
812
|
+
health.untrackedFiles = status.not_added.length;
|
|
813
|
+
health.stagedFiles = status.staged.length;
|
|
814
|
+
// 4. Vérifier si un merge est en cours
|
|
815
|
+
health.mergeInProgress = await checkMergeInProgress(gitConfig.repoPath);
|
|
816
|
+
}
|
|
817
|
+
catch (statusError) {
|
|
818
|
+
health.indexReadable = false;
|
|
819
|
+
health.issues.push('Index Git illisible ou corrompu');
|
|
820
|
+
health.recommendations.push('Exécuter git reset --hard HEAD');
|
|
821
|
+
}
|
|
822
|
+
// Restaurer la branche originale si nécessaire
|
|
823
|
+
if (originalBranch && originalBranch !== currentBranch) {
|
|
824
|
+
await git.checkout(originalBranch).catch(() => {
|
|
825
|
+
// Si on ne peut pas revenir, ce n'est pas critique pour le diagnostic
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
catch (checkoutError) {
|
|
830
|
+
health.accessible = false;
|
|
831
|
+
health.issues.push(`Impossible d'accéder à la branche '${currentBranch}'`);
|
|
832
|
+
health.recommendations.push('Exécuter git checkout -f ' + currentBranch);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
catch (error) {
|
|
836
|
+
health.issues.push(`Erreur lors du diagnostic : ${error.message}`);
|
|
837
|
+
health.recommendations.push('Vérifier l\'état du dépôt Git');
|
|
838
|
+
}
|
|
839
|
+
// 5. Analyser les problèmes et recommandations
|
|
840
|
+
if (health.mergeInProgress) {
|
|
841
|
+
health.issues.push('Merge en cours');
|
|
842
|
+
health.recommendations.push('Exécuter git merge --abort');
|
|
843
|
+
}
|
|
844
|
+
if (health.conflictedFiles > 0) {
|
|
845
|
+
health.issues.push(`${health.conflictedFiles} fichier(s) en conflit`);
|
|
846
|
+
health.recommendations.push('Résoudre les conflits ou exécuter git reset --hard HEAD');
|
|
847
|
+
}
|
|
848
|
+
if (health.modifiedFiles > 0 || health.stagedFiles > 0) {
|
|
849
|
+
health.issues.push(`${health.modifiedFiles} fichier(s) modifié(s), ${health.stagedFiles} fichier(s) stagé(s)`);
|
|
850
|
+
health.recommendations.push('Committer les changements ou exécuter git reset --hard HEAD');
|
|
851
|
+
}
|
|
852
|
+
if (!health.indexReadable) {
|
|
853
|
+
health.issues.push('Index Git corrompu');
|
|
854
|
+
health.recommendations.push('Exécuter git reset --hard HEAD');
|
|
855
|
+
}
|
|
856
|
+
// 6. Déterminer l'état de santé global
|
|
857
|
+
health.healthy = health.exists &&
|
|
858
|
+
health.accessible &&
|
|
859
|
+
health.indexReadable &&
|
|
860
|
+
!health.mergeInProgress &&
|
|
861
|
+
health.conflictedFiles === 0 &&
|
|
862
|
+
(health.clean || (health.modifiedFiles === 0 && health.stagedFiles === 0));
|
|
863
|
+
return health;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Fonction helper pour créer un statut unhealthy
|
|
867
|
+
*/
|
|
868
|
+
function createUnhealthyStatus(branch, exists, accessible, clean, mergeInProgress, conflictedFiles, modifiedFiles, untrackedFiles, stagedFiles, indexReadable, issues, recommendations) {
|
|
869
|
+
return {
|
|
870
|
+
branch,
|
|
871
|
+
exists,
|
|
872
|
+
accessible,
|
|
873
|
+
clean,
|
|
874
|
+
mergeInProgress,
|
|
875
|
+
conflictedFiles,
|
|
876
|
+
modifiedFiles,
|
|
877
|
+
untrackedFiles,
|
|
878
|
+
stagedFiles,
|
|
879
|
+
indexReadable,
|
|
880
|
+
healthy: false,
|
|
881
|
+
issues,
|
|
882
|
+
recommendations
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Vérifie si un merge est en cours en regardant les fichiers Git
|
|
887
|
+
*/
|
|
888
|
+
async function checkMergeInProgress(repoPath) {
|
|
889
|
+
const { existsSync } = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
890
|
+
const { join } = await Promise.resolve().then(() => __importStar(require('path')));
|
|
891
|
+
const gitDir = join(repoPath, '.git');
|
|
892
|
+
const mergeFiles = [
|
|
893
|
+
'MERGE_HEAD',
|
|
894
|
+
'MERGE_MODE',
|
|
895
|
+
'MERGE_MSG',
|
|
896
|
+
'CHERRY_PICK_HEAD',
|
|
897
|
+
'REVERT_HEAD'
|
|
898
|
+
];
|
|
899
|
+
return mergeFiles.some(file => existsSync(join(gitDir, file)));
|
|
900
|
+
}
|