@foundation0/git 1.2.5 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/mcp/README.md +17 -1
- package/mcp/src/server.ts +227 -7
- package/package.json +1 -1
- package/src/actions-api.ts +150 -4
- package/src/ci-api.ts +544 -0
- package/src/git-service-api.ts +87 -16
- package/src/index.ts +1 -0
- package/src/issue-dependencies.ts +80 -16
- package/src/platform/gitea-adapter.ts +16 -4
|
@@ -79,15 +79,44 @@ const fetchWithTimeout = async (url: string, init: RequestInit): Promise<Respons
|
|
|
79
79
|
const readStream = async (stream: NodeJS.ReadableStream | null): Promise<Buffer> => {
|
|
80
80
|
if (!stream) return Buffer.from([])
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
return await new Promise<Buffer>((resolve) => {
|
|
83
|
+
const chunks: Buffer[] = []
|
|
84
|
+
let settled = false
|
|
85
|
+
|
|
86
|
+
const cleanup = () => {
|
|
87
|
+
stream.removeListener('data', onData)
|
|
88
|
+
stream.removeListener('end', onDone)
|
|
89
|
+
stream.removeListener('close', onDone)
|
|
90
|
+
stream.removeListener('error', onDone)
|
|
88
91
|
}
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
|
|
93
|
+
const settle = () => {
|
|
94
|
+
if (settled) return
|
|
95
|
+
settled = true
|
|
96
|
+
cleanup()
|
|
97
|
+
resolve(Buffer.concat(chunks))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const onDone = () => settle()
|
|
101
|
+
|
|
102
|
+
const onData = (chunk: unknown) => {
|
|
103
|
+
try {
|
|
104
|
+
if (typeof chunk === 'string') {
|
|
105
|
+
chunks.push(Buffer.from(chunk))
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
chunks.push(Buffer.from(chunk as ArrayBufferView))
|
|
110
|
+
} catch {
|
|
111
|
+
// best effort
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
stream.on('data', onData)
|
|
116
|
+
stream.on('end', onDone)
|
|
117
|
+
stream.on('close', onDone)
|
|
118
|
+
stream.on('error', onDone)
|
|
119
|
+
})
|
|
91
120
|
}
|
|
92
121
|
|
|
93
122
|
const callCurl = async (
|
|
@@ -111,6 +140,7 @@ const callCurl = async (
|
|
|
111
140
|
const timeoutSeconds = requestTimeoutMs > 0 ? Math.max(1, Math.ceil(requestTimeoutMs / 1000)) : null
|
|
112
141
|
if (timeoutSeconds !== null) {
|
|
113
142
|
args.push('--max-time', String(timeoutSeconds))
|
|
143
|
+
args.push('--connect-timeout', String(Math.max(1, Math.min(30, timeoutSeconds))))
|
|
114
144
|
}
|
|
115
145
|
|
|
116
146
|
for (const [name, value] of Object.entries(init.headers)) {
|
|
@@ -128,20 +158,54 @@ const callCurl = async (
|
|
|
128
158
|
windowsHide: true,
|
|
129
159
|
})
|
|
130
160
|
|
|
161
|
+
const hardTimeoutMs = requestTimeoutMs > 0 ? requestTimeoutMs + 2_000 : null
|
|
162
|
+
let hardTimedOut = false
|
|
163
|
+
const hardTimeoutId = hardTimeoutMs
|
|
164
|
+
? setTimeout(() => {
|
|
165
|
+
hardTimedOut = true
|
|
166
|
+
try {
|
|
167
|
+
child.kill()
|
|
168
|
+
} catch {
|
|
169
|
+
// best effort
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
child.stdout?.destroy()
|
|
173
|
+
} catch {
|
|
174
|
+
// best effort
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
child.stderr?.destroy()
|
|
178
|
+
} catch {
|
|
179
|
+
// best effort
|
|
180
|
+
}
|
|
181
|
+
}, hardTimeoutMs)
|
|
182
|
+
: null
|
|
183
|
+
|
|
131
184
|
if (init.body !== undefined) {
|
|
132
185
|
child.stdin.write(init.body)
|
|
133
186
|
}
|
|
134
187
|
child.stdin.end()
|
|
135
188
|
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
readStream(child.stderr),
|
|
139
|
-
])
|
|
189
|
+
const stdoutPromise = readStream(child.stdout)
|
|
190
|
+
const stderrPromise = readStream(child.stderr)
|
|
140
191
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
192
|
+
let exitCode: number
|
|
193
|
+
try {
|
|
194
|
+
exitCode = await new Promise((resolve) => {
|
|
195
|
+
child.on('close', (code) => resolve(code ?? 0))
|
|
196
|
+
child.on('error', () => resolve(1))
|
|
197
|
+
})
|
|
198
|
+
} finally {
|
|
199
|
+
if (hardTimeoutId) {
|
|
200
|
+
clearTimeout(hardTimeoutId)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const [stdoutBytes, stderrBytes] = await Promise.all([stdoutPromise, stderrPromise])
|
|
205
|
+
|
|
206
|
+
if (hardTimedOut && requestTimeoutMs > 0) {
|
|
207
|
+
throw new Error(`Request timed out after ${requestTimeoutMs}ms: ${init.method} ${requestUrl}`)
|
|
208
|
+
}
|
|
145
209
|
|
|
146
210
|
const stdout = stdoutBytes.toString('utf8')
|
|
147
211
|
const stderr = stderrBytes.toString('utf8').trim()
|
|
@@ -386,6 +386,11 @@ export class GiteaPlatformAdapter {
|
|
|
386
386
|
return values
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
+
const isRepoScoped =
|
|
390
|
+
segments.length >= 2 &&
|
|
391
|
+
segments[0] === '{owner}' &&
|
|
392
|
+
segments[1] === '{repo}'
|
|
393
|
+
|
|
389
394
|
if (values[0].includes('/') && placeholderCount >= 2) {
|
|
390
395
|
const split = values[0].split('/')
|
|
391
396
|
if (split.length >= 2 && split[0] && split[1]) {
|
|
@@ -394,12 +399,19 @@ export class GiteaPlatformAdapter {
|
|
|
394
399
|
}
|
|
395
400
|
}
|
|
396
401
|
|
|
397
|
-
|
|
398
|
-
|
|
402
|
+
// For repo-scoped commands, allow passing the "rest" of the arguments (number/id/path) without
|
|
403
|
+
// explicitly providing owner/repo. The git-service layer can hydrate {owner}/{repo} from defaults.
|
|
404
|
+
//
|
|
405
|
+
// Example: swaggerPath "/repos/{owner}/{repo}/pulls/{number}" with args ["123"] should map to:
|
|
406
|
+
// owner="{owner}" repo="{repo}" number="123"
|
|
407
|
+
//
|
|
408
|
+
// We achieve this by left-padding args with empty strings so mapSwaggerPath leaves placeholders intact.
|
|
409
|
+
if (isRepoScoped && placeholderCount > 2 && values.length === 1 && !values[0].includes('/')) {
|
|
410
|
+
values.unshift('', '')
|
|
399
411
|
}
|
|
400
412
|
|
|
401
|
-
|
|
402
|
-
values
|
|
413
|
+
if (placeholderCount >= 2 && values.length >= 2) {
|
|
414
|
+
return values
|
|
403
415
|
}
|
|
404
416
|
|
|
405
417
|
return values
|