@andre.buzeli/git-mcp 15.12.5
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 +40 -0
- package/package.json +29 -0
- package/src/index.js +147 -0
- package/src/prompts/index.js +870 -0
- package/src/providers/providerManager.js +317 -0
- package/src/resources/index.js +276 -0
- package/src/tools/git-branches.js +126 -0
- package/src/tools/git-clone.js +137 -0
- package/src/tools/git-config.js +94 -0
- package/src/tools/git-diff.js +137 -0
- package/src/tools/git-files.js +82 -0
- package/src/tools/git-help.js +284 -0
- package/src/tools/git-history.js +90 -0
- package/src/tools/git-ignore.js +98 -0
- package/src/tools/git-issues.js +101 -0
- package/src/tools/git-merge.js +152 -0
- package/src/tools/git-pulls.js +115 -0
- package/src/tools/git-remote.js +492 -0
- package/src/tools/git-reset.js +105 -0
- package/src/tools/git-stash.js +120 -0
- package/src/tools/git-sync.js +129 -0
- package/src/tools/git-tags.js +113 -0
- package/src/tools/git-workflow.js +443 -0
- package/src/utils/env.js +104 -0
- package/src/utils/errors.js +431 -0
- package/src/utils/gitAdapter.js +996 -0
- package/src/utils/hooks.js +255 -0
- package/src/utils/metrics.js +198 -0
- package/src/utils/providerExec.js +61 -0
- package/src/utils/repoHelpers.js +216 -0
- package/src/utils/retry.js +123 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// Sistema de Hooks para git-mcp
|
|
2
|
+
// Permite executar código customizado antes/depois de operações
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tipos de hooks disponíveis
|
|
6
|
+
*/
|
|
7
|
+
export const HOOK_TYPES = {
|
|
8
|
+
// Workflow hooks
|
|
9
|
+
PRE_INIT: "pre:init",
|
|
10
|
+
POST_INIT: "post:init",
|
|
11
|
+
PRE_COMMIT: "pre:commit",
|
|
12
|
+
POST_COMMIT: "post:commit",
|
|
13
|
+
PRE_PUSH: "pre:push",
|
|
14
|
+
POST_PUSH: "post:push",
|
|
15
|
+
PRE_PULL: "pre:pull",
|
|
16
|
+
POST_PULL: "post:pull",
|
|
17
|
+
|
|
18
|
+
// Branch hooks
|
|
19
|
+
PRE_BRANCH_CREATE: "pre:branch:create",
|
|
20
|
+
POST_BRANCH_CREATE: "post:branch:create",
|
|
21
|
+
PRE_BRANCH_DELETE: "pre:branch:delete",
|
|
22
|
+
POST_BRANCH_DELETE: "post:branch:delete",
|
|
23
|
+
PRE_CHECKOUT: "pre:checkout",
|
|
24
|
+
POST_CHECKOUT: "post:checkout",
|
|
25
|
+
|
|
26
|
+
// Merge hooks
|
|
27
|
+
PRE_MERGE: "pre:merge",
|
|
28
|
+
POST_MERGE: "post:merge",
|
|
29
|
+
ON_CONFLICT: "on:conflict",
|
|
30
|
+
|
|
31
|
+
// Reset hooks
|
|
32
|
+
PRE_RESET: "pre:reset",
|
|
33
|
+
POST_RESET: "post:reset",
|
|
34
|
+
|
|
35
|
+
// Remote hooks
|
|
36
|
+
PRE_SYNC: "pre:sync",
|
|
37
|
+
POST_SYNC: "post:sync",
|
|
38
|
+
|
|
39
|
+
// Error handling
|
|
40
|
+
ON_ERROR: "on:error"
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Armazena hooks registrados
|
|
44
|
+
const registeredHooks = new Map();
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Registra um hook
|
|
48
|
+
* @param {string} hookType - Tipo do hook (usar HOOK_TYPES)
|
|
49
|
+
* @param {Function} handler - Função async (context) => result
|
|
50
|
+
* @param {Object} options - { priority: number, name: string }
|
|
51
|
+
* @returns {string} - ID do hook para remover depois
|
|
52
|
+
*/
|
|
53
|
+
export function registerHook(hookType, handler, options = {}) {
|
|
54
|
+
const { priority = 0, name = "anonymous" } = options;
|
|
55
|
+
const hookId = `${hookType}_${name}_${Date.now()}`;
|
|
56
|
+
|
|
57
|
+
if (!registeredHooks.has(hookType)) {
|
|
58
|
+
registeredHooks.set(hookType, []);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
registeredHooks.get(hookType).push({
|
|
62
|
+
id: hookId,
|
|
63
|
+
handler,
|
|
64
|
+
priority,
|
|
65
|
+
name
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Ordena por prioridade (maior primeiro)
|
|
69
|
+
registeredHooks.get(hookType).sort((a, b) => b.priority - a.priority);
|
|
70
|
+
|
|
71
|
+
return hookId;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Remove um hook específico
|
|
76
|
+
* @param {string} hookId - ID retornado por registerHook
|
|
77
|
+
*/
|
|
78
|
+
export function unregisterHook(hookId) {
|
|
79
|
+
for (const [type, hooks] of registeredHooks.entries()) {
|
|
80
|
+
const index = hooks.findIndex(h => h.id === hookId);
|
|
81
|
+
if (index !== -1) {
|
|
82
|
+
hooks.splice(index, 1);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Remove todos os hooks de um tipo
|
|
91
|
+
* @param {string} hookType - Tipo do hook
|
|
92
|
+
*/
|
|
93
|
+
export function clearHooks(hookType) {
|
|
94
|
+
if (hookType) {
|
|
95
|
+
registeredHooks.delete(hookType);
|
|
96
|
+
} else {
|
|
97
|
+
registeredHooks.clear();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Executa todos os hooks de um tipo
|
|
103
|
+
* @param {string} hookType - Tipo do hook
|
|
104
|
+
* @param {Object} context - Contexto passado para os handlers
|
|
105
|
+
* @returns {Object} - { success: boolean, results: [], errors: [] }
|
|
106
|
+
*/
|
|
107
|
+
export async function runHooks(hookType, context = {}) {
|
|
108
|
+
const hooks = registeredHooks.get(hookType) || [];
|
|
109
|
+
|
|
110
|
+
if (hooks.length === 0) {
|
|
111
|
+
return { success: true, results: [], errors: [], skipped: true };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const results = [];
|
|
115
|
+
const errors = [];
|
|
116
|
+
let shouldContinue = true;
|
|
117
|
+
|
|
118
|
+
for (const hook of hooks) {
|
|
119
|
+
if (!shouldContinue) break;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const result = await hook.handler({
|
|
123
|
+
...context,
|
|
124
|
+
hookType,
|
|
125
|
+
hookName: hook.name
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
results.push({
|
|
129
|
+
hookId: hook.id,
|
|
130
|
+
name: hook.name,
|
|
131
|
+
success: true,
|
|
132
|
+
result
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Hook pode retornar { abort: true } para parar execução
|
|
136
|
+
if (result?.abort) {
|
|
137
|
+
shouldContinue = false;
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
errors.push({
|
|
141
|
+
hookId: hook.id,
|
|
142
|
+
name: hook.name,
|
|
143
|
+
error: error.message || String(error)
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Por padrão, continua mesmo com erro
|
|
147
|
+
// Hook pode definir { stopOnError: true } nas options
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
success: errors.length === 0,
|
|
153
|
+
results,
|
|
154
|
+
errors,
|
|
155
|
+
aborted: !shouldContinue
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Wrapper para executar função com hooks pre/post
|
|
161
|
+
* @param {string} operationType - Tipo da operação (ex: "commit", "push")
|
|
162
|
+
* @param {Function} fn - Função principal async
|
|
163
|
+
* @param {Object} context - Contexto para os hooks
|
|
164
|
+
*/
|
|
165
|
+
export async function withHooks(operationType, fn, context = {}) {
|
|
166
|
+
const preHookType = `pre:${operationType}`;
|
|
167
|
+
const postHookType = `post:${operationType}`;
|
|
168
|
+
|
|
169
|
+
// Pre-hooks
|
|
170
|
+
const preResult = await runHooks(preHookType, context);
|
|
171
|
+
if (preResult.aborted) {
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
abortedByHook: true,
|
|
175
|
+
hookResults: preResult,
|
|
176
|
+
message: `Operação abortada por hook ${preHookType}`
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Operação principal
|
|
181
|
+
let mainResult;
|
|
182
|
+
let mainError = null;
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
mainResult = await fn();
|
|
186
|
+
} catch (error) {
|
|
187
|
+
mainError = error;
|
|
188
|
+
|
|
189
|
+
// Executa hook de erro
|
|
190
|
+
await runHooks(HOOK_TYPES.ON_ERROR, {
|
|
191
|
+
...context,
|
|
192
|
+
error: {
|
|
193
|
+
message: error.message,
|
|
194
|
+
code: error.code,
|
|
195
|
+
stack: error.stack
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Post-hooks
|
|
203
|
+
const postResult = await runHooks(postHookType, {
|
|
204
|
+
...context,
|
|
205
|
+
result: mainResult
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return mainResult;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Lista todos os hooks registrados
|
|
213
|
+
*/
|
|
214
|
+
export function listHooks() {
|
|
215
|
+
const list = {};
|
|
216
|
+
for (const [type, hooks] of registeredHooks.entries()) {
|
|
217
|
+
list[type] = hooks.map(h => ({
|
|
218
|
+
id: h.id,
|
|
219
|
+
name: h.name,
|
|
220
|
+
priority: h.priority
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
return list;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Verifica se há hooks registrados para um tipo
|
|
228
|
+
*/
|
|
229
|
+
export function hasHooks(hookType) {
|
|
230
|
+
const hooks = registeredHooks.get(hookType);
|
|
231
|
+
return hooks && hooks.length > 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Exemplo de registro de hooks
|
|
236
|
+
*/
|
|
237
|
+
export function exampleHookSetup() {
|
|
238
|
+
// Hook para logging
|
|
239
|
+
registerHook(HOOK_TYPES.POST_COMMIT, async (ctx) => {
|
|
240
|
+
console.log(`[Hook] Commit criado: ${ctx.result?.sha}`);
|
|
241
|
+
}, { name: "commit-logger", priority: 10 });
|
|
242
|
+
|
|
243
|
+
// Hook para validação pré-push
|
|
244
|
+
registerHook(HOOK_TYPES.PRE_PUSH, async (ctx) => {
|
|
245
|
+
// Exemplo: verificar se branch é protegida
|
|
246
|
+
if (ctx.branch === "main" && !ctx.force) {
|
|
247
|
+
console.warn("[Hook] Push para main sem force - considere criar PR");
|
|
248
|
+
}
|
|
249
|
+
}, { name: "branch-protection", priority: 100 });
|
|
250
|
+
|
|
251
|
+
// Hook para notificação de erro
|
|
252
|
+
registerHook(HOOK_TYPES.ON_ERROR, async (ctx) => {
|
|
253
|
+
console.error(`[Hook] Erro: ${ctx.error?.message}`);
|
|
254
|
+
}, { name: "error-notifier", priority: 0 });
|
|
255
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// Sistema de Métricas/Telemetria para git-mcp
|
|
2
|
+
// Opt-in via variável de ambiente ENABLE_METRICS=true
|
|
3
|
+
|
|
4
|
+
const metricsEnabled = process.env.ENABLE_METRICS === "true";
|
|
5
|
+
|
|
6
|
+
// Armazena métricas em memória
|
|
7
|
+
const metricsStore = {
|
|
8
|
+
operations: [],
|
|
9
|
+
errors: [],
|
|
10
|
+
startTime: Date.now(),
|
|
11
|
+
summary: {
|
|
12
|
+
totalOperations: 0,
|
|
13
|
+
successfulOperations: 0,
|
|
14
|
+
failedOperations: 0,
|
|
15
|
+
totalDurationMs: 0,
|
|
16
|
+
operationsByType: {}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Registra início de uma operação
|
|
22
|
+
* @param {string} operation - Nome da operação (ex: "git-workflow:push")
|
|
23
|
+
* @param {Object} metadata - Metadados adicionais
|
|
24
|
+
* @returns {string} - ID da operação para tracking
|
|
25
|
+
*/
|
|
26
|
+
export function startOperation(operation, metadata = {}) {
|
|
27
|
+
if (!metricsEnabled) return null;
|
|
28
|
+
|
|
29
|
+
const id = `${operation}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
30
|
+
|
|
31
|
+
metricsStore.operations.push({
|
|
32
|
+
id,
|
|
33
|
+
operation,
|
|
34
|
+
startTime: Date.now(),
|
|
35
|
+
endTime: null,
|
|
36
|
+
duration: null,
|
|
37
|
+
status: "running",
|
|
38
|
+
metadata,
|
|
39
|
+
error: null
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return id;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Finaliza uma operação
|
|
47
|
+
* @param {string} id - ID da operação
|
|
48
|
+
* @param {string} status - "success" ou "error"
|
|
49
|
+
* @param {Error|null} error - Erro se houver
|
|
50
|
+
*/
|
|
51
|
+
export function endOperation(id, status = "success", error = null) {
|
|
52
|
+
if (!metricsEnabled || !id) return;
|
|
53
|
+
|
|
54
|
+
const op = metricsStore.operations.find(o => o.id === id);
|
|
55
|
+
if (!op) return;
|
|
56
|
+
|
|
57
|
+
op.endTime = Date.now();
|
|
58
|
+
op.duration = op.endTime - op.startTime;
|
|
59
|
+
op.status = status;
|
|
60
|
+
|
|
61
|
+
if (error) {
|
|
62
|
+
op.error = {
|
|
63
|
+
message: error.message || String(error),
|
|
64
|
+
code: error.code || "UNKNOWN"
|
|
65
|
+
};
|
|
66
|
+
metricsStore.errors.push({
|
|
67
|
+
timestamp: Date.now(),
|
|
68
|
+
operation: op.operation,
|
|
69
|
+
error: op.error
|
|
70
|
+
});
|
|
71
|
+
metricsStore.summary.failedOperations++;
|
|
72
|
+
} else {
|
|
73
|
+
metricsStore.summary.successfulOperations++;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
metricsStore.summary.totalOperations++;
|
|
77
|
+
metricsStore.summary.totalDurationMs += op.duration;
|
|
78
|
+
|
|
79
|
+
// Atualiza contagem por tipo
|
|
80
|
+
const opType = op.operation.split(":")[0];
|
|
81
|
+
metricsStore.summary.operationsByType[opType] =
|
|
82
|
+
(metricsStore.summary.operationsByType[opType] || 0) + 1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Wrapper para medir tempo de execução de uma função
|
|
87
|
+
* @param {string} operation - Nome da operação
|
|
88
|
+
* @param {Function} fn - Função async para executar
|
|
89
|
+
* @param {Object} metadata - Metadados adicionais
|
|
90
|
+
*/
|
|
91
|
+
export async function withMetrics(operation, fn, metadata = {}) {
|
|
92
|
+
const id = startOperation(operation, metadata);
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const result = await fn();
|
|
96
|
+
endOperation(id, "success");
|
|
97
|
+
return result;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
endOperation(id, "error", error);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Retorna resumo das métricas
|
|
106
|
+
*/
|
|
107
|
+
export function getMetricsSummary() {
|
|
108
|
+
if (!metricsEnabled) {
|
|
109
|
+
return { enabled: false, message: "Métricas desabilitadas. Use ENABLE_METRICS=true para habilitar." };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const uptimeMs = Date.now() - metricsStore.startTime;
|
|
113
|
+
const avgDuration = metricsStore.summary.totalOperations > 0
|
|
114
|
+
? metricsStore.summary.totalDurationMs / metricsStore.summary.totalOperations
|
|
115
|
+
: 0;
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
enabled: true,
|
|
119
|
+
uptime: {
|
|
120
|
+
ms: uptimeMs,
|
|
121
|
+
formatted: formatDuration(uptimeMs)
|
|
122
|
+
},
|
|
123
|
+
operations: {
|
|
124
|
+
total: metricsStore.summary.totalOperations,
|
|
125
|
+
successful: metricsStore.summary.successfulOperations,
|
|
126
|
+
failed: metricsStore.summary.failedOperations,
|
|
127
|
+
successRate: metricsStore.summary.totalOperations > 0
|
|
128
|
+
? ((metricsStore.summary.successfulOperations / metricsStore.summary.totalOperations) * 100).toFixed(2) + "%"
|
|
129
|
+
: "N/A"
|
|
130
|
+
},
|
|
131
|
+
performance: {
|
|
132
|
+
totalDurationMs: metricsStore.summary.totalDurationMs,
|
|
133
|
+
avgDurationMs: avgDuration.toFixed(2),
|
|
134
|
+
operationsPerMinute: uptimeMs > 0
|
|
135
|
+
? ((metricsStore.summary.totalOperations / uptimeMs) * 60000).toFixed(2)
|
|
136
|
+
: 0
|
|
137
|
+
},
|
|
138
|
+
byType: metricsStore.summary.operationsByType,
|
|
139
|
+
recentErrors: metricsStore.errors.slice(-10)
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Retorna operações recentes
|
|
145
|
+
* @param {number} limit - Número máximo de operações
|
|
146
|
+
*/
|
|
147
|
+
export function getRecentOperations(limit = 20) {
|
|
148
|
+
if (!metricsEnabled) return [];
|
|
149
|
+
return metricsStore.operations.slice(-limit);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Limpa métricas antigas
|
|
154
|
+
* @param {number} maxAgeMs - Idade máxima em ms (default: 1 hora)
|
|
155
|
+
*/
|
|
156
|
+
export function pruneMetrics(maxAgeMs = 3600000) {
|
|
157
|
+
if (!metricsEnabled) return;
|
|
158
|
+
|
|
159
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
160
|
+
metricsStore.operations = metricsStore.operations.filter(o => o.startTime > cutoff);
|
|
161
|
+
metricsStore.errors = metricsStore.errors.filter(e => e.timestamp > cutoff);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Reseta todas as métricas
|
|
166
|
+
*/
|
|
167
|
+
export function resetMetrics() {
|
|
168
|
+
metricsStore.operations = [];
|
|
169
|
+
metricsStore.errors = [];
|
|
170
|
+
metricsStore.startTime = Date.now();
|
|
171
|
+
metricsStore.summary = {
|
|
172
|
+
totalOperations: 0,
|
|
173
|
+
successfulOperations: 0,
|
|
174
|
+
failedOperations: 0,
|
|
175
|
+
totalDurationMs: 0,
|
|
176
|
+
operationsByType: {}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Formata duração em formato legível
|
|
182
|
+
*/
|
|
183
|
+
function formatDuration(ms) {
|
|
184
|
+
const seconds = Math.floor(ms / 1000);
|
|
185
|
+
const minutes = Math.floor(seconds / 60);
|
|
186
|
+
const hours = Math.floor(minutes / 60);
|
|
187
|
+
|
|
188
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
|
|
189
|
+
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
190
|
+
return `${seconds}s`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Verifica se métricas estão habilitadas
|
|
195
|
+
*/
|
|
196
|
+
export function isMetricsEnabled() {
|
|
197
|
+
return metricsEnabled;
|
|
198
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { withRetry } from "./retry.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Executa operações em GitHub e Gitea em PARALELO com retry automático
|
|
5
|
+
* @param {ProviderManager} pm - Provider manager
|
|
6
|
+
* @param {Object} handlers - { github: async (owner) => {}, gitea: async (owner) => {} }
|
|
7
|
+
* @param {Object} options - { retry: boolean, maxRetries: number, organization: string }
|
|
8
|
+
*/
|
|
9
|
+
export async function runBoth(pm, handlers, options = {}) {
|
|
10
|
+
const { retry = true, maxRetries = 3, organization } = options;
|
|
11
|
+
const out = { github: null, gitea: null };
|
|
12
|
+
|
|
13
|
+
// Se organization fornecida, usar como owner; senão buscar owners pessoais em paralelo
|
|
14
|
+
const [ghOwner, geOwner] = organization
|
|
15
|
+
? [organization, organization]
|
|
16
|
+
: await Promise.all([
|
|
17
|
+
pm.getGitHubOwner().catch(() => ""),
|
|
18
|
+
pm.getGiteaOwner().catch(() => "")
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
// Preparar promises para execução paralela
|
|
22
|
+
const promises = [];
|
|
23
|
+
|
|
24
|
+
if (pm.github && ghOwner && handlers.github) {
|
|
25
|
+
const githubPromise = async () => {
|
|
26
|
+
const executor = () => handlers.github(ghOwner);
|
|
27
|
+
try {
|
|
28
|
+
const result = retry ? await withRetry(executor, { maxRetries }) : await executor();
|
|
29
|
+
return { provider: "github", result };
|
|
30
|
+
} catch (e) {
|
|
31
|
+
return { provider: "github", result: { ok: false, error: String(e?.message || e) } };
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
promises.push(githubPromise());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (pm.giteaUrl && pm.giteaToken && geOwner && handlers.gitea) {
|
|
38
|
+
const giteaPromise = async () => {
|
|
39
|
+
const executor = () => handlers.gitea(geOwner);
|
|
40
|
+
try {
|
|
41
|
+
const result = retry ? await withRetry(executor, { maxRetries }) : await executor();
|
|
42
|
+
return { provider: "gitea", result };
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return { provider: "gitea", result: { ok: false, error: String(e?.message || e) } };
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
promises.push(giteaPromise());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Executar em paralelo
|
|
51
|
+
const results = await Promise.all(promises);
|
|
52
|
+
|
|
53
|
+
// Mapear resultados
|
|
54
|
+
for (const { provider, result } of results) {
|
|
55
|
+
out[provider] = result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|