@foundation0/git 1.0.0 → 1.2.1

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.
Files changed (117) hide show
  1. package/{packages/git/README.md → README.md} +14 -5
  2. package/{packages/git/mcp → mcp}/README.md +3 -1
  3. package/{packages/git/mcp → mcp}/cli.mjs +0 -0
  4. package/package.json +43 -13
  5. package/{packages/git/src → src}/git-service-api.ts +3 -0
  6. package/{packages/git/src → src}/index.ts +8 -0
  7. package/src/label-management.ts +587 -0
  8. package/src/platform/config.ts +60 -0
  9. package/.codex.example/config.toml +0 -10
  10. package/.env.example +0 -10
  11. package/packages/fs/README.md +0 -47
  12. package/packages/fs/node_modules/.bin/f0-git-mcp +0 -21
  13. package/packages/fs/node_modules/.bin/f0-git-mcp-server +0 -21
  14. package/packages/fs/node_modules/.bin/f0-git-mcp-server.CMD +0 -12
  15. package/packages/fs/node_modules/.bin/f0-git-mcp-server.ps1 +0 -41
  16. package/packages/fs/node_modules/.bin/f0-git-mcp.CMD +0 -12
  17. package/packages/fs/node_modules/.bin/f0-git-mcp.ps1 +0 -41
  18. package/packages/fs/node_modules/.bin/tsc +0 -21
  19. package/packages/fs/node_modules/.bin/tsc.CMD +0 -12
  20. package/packages/fs/node_modules/.bin/tsc.ps1 +0 -41
  21. package/packages/fs/node_modules/.bin/tsserver +0 -21
  22. package/packages/fs/node_modules/.bin/tsserver.CMD +0 -12
  23. package/packages/fs/node_modules/.bin/tsserver.ps1 +0 -41
  24. package/packages/fs/node_modules/.bin/vite +0 -21
  25. package/packages/fs/node_modules/.bin/vite.CMD +0 -12
  26. package/packages/fs/node_modules/.bin/vite.ps1 +0 -41
  27. package/packages/fs/node_modules/.bin/vitest +0 -21
  28. package/packages/fs/node_modules/.bin/vitest.CMD +0 -12
  29. package/packages/fs/node_modules/.bin/vitest.ps1 +0 -41
  30. package/packages/fs/package.json +0 -28
  31. package/packages/fs/src/cli.ts +0 -74
  32. package/packages/fs/src/git-fs.ts +0 -705
  33. package/packages/fs/src/index.ts +0 -33
  34. package/packages/fs/src/mount.ts +0 -297
  35. package/packages/fs/tsconfig.json +0 -7
  36. package/packages/git/mcp/tests/e2e/git-mcp-e2e.spec.ts +0 -157
  37. package/packages/git/mcp/tests/e2e/server.fixture.ts +0 -109
  38. package/packages/git/node_modules/.bin/tsc +0 -21
  39. package/packages/git/node_modules/.bin/tsc.CMD +0 -12
  40. package/packages/git/node_modules/.bin/tsc.ps1 +0 -41
  41. package/packages/git/node_modules/.bin/tsserver +0 -21
  42. package/packages/git/node_modules/.bin/tsserver.CMD +0 -12
  43. package/packages/git/node_modules/.bin/tsserver.ps1 +0 -41
  44. package/packages/git/node_modules/.bin/vite +0 -21
  45. package/packages/git/node_modules/.bin/vite.CMD +0 -12
  46. package/packages/git/node_modules/.bin/vite.ps1 +0 -41
  47. package/packages/git/node_modules/.bin/vitest +0 -21
  48. package/packages/git/node_modules/.bin/vitest.CMD +0 -12
  49. package/packages/git/node_modules/.bin/vitest.ps1 +0 -41
  50. package/packages/git/node_modules/.vite/vitest/results.json +0 -1
  51. package/packages/git/package.json +0 -60
  52. package/packages/git/scripts/create-issue.mjs +0 -93
  53. package/packages/git/scripts/extract-git-spec.mjs +0 -234
  54. package/packages/git/scripts/fetch-gitea-swagger.mjs +0 -22
  55. package/packages/git/src/platform/config.ts +0 -140
  56. package/packages/git/tests/api.spec.ts +0 -55
  57. package/packages/git/tests/e2e/git-service-feature-e2e.spec.ts +0 -232
  58. package/packages/git/tests/git-service-api-object.spec.ts +0 -97
  59. package/packages/git/tests/git-service-feature-matrix.spec.ts +0 -182
  60. package/packages/git/tests/issue-dependencies.spec.ts +0 -81
  61. package/packages/git/tsconfig.json +0 -7
  62. package/packages/git/vitest.config.ts +0 -7
  63. package/packages/utils/package.json +0 -9
  64. package/packages/utils/src/awk.ts +0 -6
  65. package/packages/utils/src/cat.ts +0 -6
  66. package/packages/utils/src/cd.ts +0 -6
  67. package/packages/utils/src/chgrp.ts +0 -6
  68. package/packages/utils/src/chmod.ts +0 -6
  69. package/packages/utils/src/chown.ts +0 -6
  70. package/packages/utils/src/cp.ts +0 -6
  71. package/packages/utils/src/curl.ts +0 -6
  72. package/packages/utils/src/cut.ts +0 -6
  73. package/packages/utils/src/date.ts +0 -6
  74. package/packages/utils/src/echo.ts +0 -6
  75. package/packages/utils/src/find.ts +0 -6
  76. package/packages/utils/src/grep.ts +0 -6
  77. package/packages/utils/src/gunzip.ts +0 -6
  78. package/packages/utils/src/gzip.ts +0 -6
  79. package/packages/utils/src/head.ts +0 -6
  80. package/packages/utils/src/hostname.ts +0 -6
  81. package/packages/utils/src/index.ts +0 -37
  82. package/packages/utils/src/ls.ts +0 -6
  83. package/packages/utils/src/mkdir.ts +0 -6
  84. package/packages/utils/src/mv.ts +0 -6
  85. package/packages/utils/src/ping.ts +0 -6
  86. package/packages/utils/src/pwd.ts +0 -6
  87. package/packages/utils/src/rm.ts +0 -6
  88. package/packages/utils/src/rmdir.ts +0 -6
  89. package/packages/utils/src/sed.ts +0 -6
  90. package/packages/utils/src/sort.ts +0 -6
  91. package/packages/utils/src/tail.ts +0 -6
  92. package/packages/utils/src/tar.ts +0 -6
  93. package/packages/utils/src/touch.ts +0 -6
  94. package/packages/utils/src/tr.ts +0 -6
  95. package/packages/utils/src/uname.ts +0 -6
  96. package/packages/utils/src/uniq.ts +0 -6
  97. package/packages/utils/src/unzip.ts +0 -6
  98. package/packages/utils/src/util.ts +0 -4
  99. package/packages/utils/src/wc.ts +0 -6
  100. package/packages/utils/src/wget.ts +0 -6
  101. package/packages/utils/src/whoami.ts +0 -6
  102. package/packages/utils/src/zip.ts +0 -6
  103. package/pnpm-workspace.yaml +0 -2
  104. package/tsconfig.base.json +0 -12
  105. /package/{packages/git/gitea-swagger.json → gitea-swagger.json} +0 -0
  106. /package/{packages/git/mcp → mcp}/src/cli.ts +0 -0
  107. /package/{packages/git/mcp → mcp}/src/client.ts +0 -0
  108. /package/{packages/git/mcp → mcp}/src/index.ts +0 -0
  109. /package/{packages/git/mcp → mcp}/src/server.ts +0 -0
  110. /package/{packages/git/src → src}/api.ts +0 -0
  111. /package/{packages/git/src → src}/git-service-feature-spec.generated.ts +0 -0
  112. /package/{packages/git/src → src}/issue-dependencies.ts +0 -0
  113. /package/{packages/git/src → src}/platform/gitea-adapter.ts +0 -0
  114. /package/{packages/git/src → src}/platform/gitea-rules.ts +0 -0
  115. /package/{packages/git/src → src}/platform/index.ts +0 -0
  116. /package/{packages/git/src → src}/repository.ts +0 -0
  117. /package/{packages/git/src → src}/spec-mock.ts +0 -0
@@ -1,33 +0,0 @@
1
- export { createGitFs, defaultGitFs, promises } from './git-fs'
2
- export {
3
- access,
4
- accessSync,
5
- appendFile,
6
- appendFileSync,
7
- copyFile,
8
- copyFileSync,
9
- exists,
10
- existsSync,
11
- lstat,
12
- lstatSync,
13
- mkdir,
14
- mkdirSync,
15
- readFile,
16
- readFileSync,
17
- readdir,
18
- readdirSync,
19
- rename,
20
- renameSync,
21
- rm,
22
- rmSync,
23
- rmdir,
24
- rmdirSync,
25
- stat,
26
- statSync,
27
- unlink,
28
- unlinkSync,
29
- writeFile,
30
- writeFileSync,
31
- } from './git-fs'
32
- export { mountGitFs, type MountedGitFsHandle } from './mount'
33
- export type { GitFsOptions, GitFsDirentLike, GitFsStats, GitFsInstance } from './git-fs'
@@ -1,297 +0,0 @@
1
- import { createRequire } from 'node:module'
2
- import { createGitFs, type GitFsOptions } from './git-fs'
3
-
4
- type MountedFuse = {
5
- mount: (path: string, operations: Record<string, unknown>, callback: (error?: Error | null) => void) => void
6
- unmount: (path: string, callback: (error?: Error | null) => void) => void
7
- }
8
-
9
- type MountBackend = 'auto' | 'fuse-bindings' | 'node-fuse-bindings' | 'dokan' | 'winfsp'
10
-
11
- type GitFsMountOptions = GitFsOptions & {
12
- mountPoint: string
13
- backend?: MountBackend
14
- readOnly?: boolean
15
- }
16
-
17
- type MountedGitFsHandle = {
18
- unmount(): Promise<void>
19
- }
20
-
21
- type FuseErrorMap = Record<string, number>
22
-
23
- const errnoMap: FuseErrorMap = {
24
- EACCES: -13,
25
- EEXIST: -17,
26
- EIO: -5,
27
- EISDIR: -21,
28
- ENOENT: -2,
29
- ENOTEMPTY: -39,
30
- ENOTDIR: -20,
31
- ENOSYS: -38,
32
- EROFS: -30,
33
- }
34
-
35
- const toFuseErrno = (error: unknown): number => {
36
- const code = (error as { code?: unknown })?.code
37
- if (typeof code === 'string' && code in errnoMap) {
38
- return errnoMap[code]
39
- }
40
-
41
- return -5
42
- }
43
-
44
- const toFileData = (value: unknown): Buffer => {
45
- if (Buffer.isBuffer(value)) {
46
- return value
47
- }
48
-
49
- if (typeof value === 'string') {
50
- return Buffer.from(value)
51
- }
52
-
53
- return Buffer.from([])
54
- }
55
-
56
- const toFuseNames = (entries: (string | { name: string })[]): string[] =>
57
- entries.map((entry) => (typeof entry === 'string' ? entry : entry.name))
58
-
59
- const getBackendOrder = (backend?: MountBackend): string[] => {
60
- if (backend === 'dokan') {
61
- return ['node-fuse-bindings']
62
- }
63
-
64
- if (backend === 'winfsp') {
65
- return ['node-fuse-bindings', 'fuse-bindings']
66
- }
67
-
68
- if (backend === 'fuse-bindings' || backend === 'node-fuse-bindings') {
69
- return [backend]
70
- }
71
-
72
- if (process.platform === 'win32') {
73
- return ['node-fuse-bindings', 'fuse-bindings']
74
- }
75
-
76
- return ['fuse-bindings']
77
- }
78
-
79
- const getBackendName = (backend?: MountBackend): string => {
80
- if (!backend || backend === 'auto') {
81
- return process.platform === 'win32' ? 'dokan-or-fuse' : 'fuse'
82
- }
83
-
84
- return backend
85
- }
86
-
87
- const loadFuseBindings = (backend?: MountBackend): MountedFuse => {
88
- const require = createRequire(import.meta.url)
89
- const attemptOrder = getBackendOrder(backend)
90
- const isBunRuntime = Boolean(process.versions.bun)
91
- const errors: { name: string; error: unknown }[] = []
92
-
93
- for (const name of attemptOrder) {
94
- try {
95
- return require(name) as MountedFuse
96
- } catch (error) {
97
- errors.push({ name, error })
98
- }
99
- }
100
-
101
- const winfspHint = process.platform === 'win32'
102
- ? 'For Windows, install Dokany + node-fuse-bindings (backend=dokan) or try installing fuse-bindings from source.'
103
- : ''
104
-
105
- const bunHint = isBunRuntime && process.platform === 'win32'
106
- ? 'Bun uses a separate native ABI (24.x in this environment) and these FUSE packages are not loading successfully under Bun.'
107
- + '\nUse Node.js runtime instead (for example via a TS runner) or WSL.'
108
- : ''
109
-
110
- const details = errors
111
- .map(({ name, error }) => {
112
- const message = error instanceof Error ? error.message : String(error)
113
- return `- ${name}: ${message}`
114
- })
115
- .join('\n')
116
-
117
- throw new Error(
118
- `mount backend ${getBackendName(backend)} could not be loaded from ${attemptOrder.join(', ')}.\n`
119
- + `Missing dependency or failed native binding installation.\n${details}\n${winfspHint}\n${bunHint}`
120
- .trim(),
121
- )
122
- }
123
-
124
- const runFuseOp = async <T>(task: () => Promise<T>, cb: (code: number, value?: T) => void): Promise<void> => {
125
- try {
126
- const value = await task()
127
- cb(0, value)
128
- } catch (error) {
129
- cb(toFuseErrno(error))
130
- }
131
-
132
- return Promise.resolve()
133
- }
134
-
135
- export { type MountedGitFsHandle }
136
-
137
- export const mountGitFs = async (options: GitFsMountOptions): Promise<MountedGitFsHandle> => {
138
- const {
139
- mountPoint,
140
- readOnly = false,
141
- backend,
142
- ...fsOptions
143
- } = options
144
- const fs = createGitFs(fsOptions)
145
- const fuse = loadFuseBindings(backend)
146
-
147
- const readonlyError = () => toFuseErrno({ code: 'EROFS' })
148
- const operations: Record<string, unknown> = {
149
- readdir: (path: string, cb: (code: number, names?: string[]) => void) => {
150
- void runFuseOp(async () => {
151
- const entries = await fs.readdir(path)
152
- return ['.', '..', ...toFuseNames(entries)]
153
- }, cb)
154
- },
155
-
156
- getattr: (path: string, cb: (code: number, stats?: unknown) => void) => {
157
- void runFuseOp(async () => {
158
- const stats = await fs.stat(path)
159
- return {
160
- mtime: stats.mtime,
161
- atime: stats.atime,
162
- ctime: stats.ctime,
163
- nlink: stats.isDirectory() ? 2 : 1,
164
- size: stats.size,
165
- mode: stats.isDirectory() ? 0o040755 : 0o100644,
166
- uid: 0,
167
- gid: 0,
168
- }
169
- }, cb)
170
- },
171
-
172
- open: (_path: string, cb: (code: number, fd?: number) => void) => {
173
- cb(0, 0)
174
- },
175
-
176
- release: (_path: string, _fd: number, cb: (code: number) => void) => {
177
- cb(0)
178
- },
179
-
180
- read: (
181
- path: string,
182
- _fd: number,
183
- buffer: Buffer,
184
- length: number,
185
- offset: number,
186
- cb: (code: number, bytesRead?: number) => void,
187
- ) => {
188
- void runFuseOp(async () => {
189
- const data = toFileData(await fs.readFile(path))
190
- const bytesRead = data.slice(offset, offset + length).copy(buffer)
191
- return bytesRead
192
- }, cb)
193
- },
194
-
195
- write: readOnly
196
- ? (_: string, _fd: number, _buffer: Buffer, _length: number, _offset: number, cb: (code: number, bytes?: number) => void) => {
197
- cb(readonlyError())
198
- }
199
- : (
200
- path: string,
201
- _fd: number,
202
- buffer: Buffer,
203
- length: number,
204
- offset: number,
205
- cb: (code: number, bytes?: number) => void,
206
- ) => {
207
- void runFuseOp(async () => {
208
- const current = await fs.readFile(path).catch(() => Buffer.from([]))
209
- const existing = toFileData(current)
210
- const incoming = Buffer.from(buffer).slice(0, length)
211
- const merged = Buffer.alloc(Math.max(existing.length, offset + incoming.length))
212
- existing.copy(merged)
213
- incoming.copy(merged, offset)
214
- await fs.writeFile(path, merged)
215
- return incoming.length
216
- }, cb)
217
- },
218
-
219
- create: readOnly
220
- ? (_: string, _mode: number, cb: (code: number, fd?: number) => void) => {
221
- cb(readonlyError())
222
- }
223
- : async (path: string, _mode: number, cb: (code: number, fd?: number) => void) => {
224
- void runFuseOp(async () => {
225
- await fs.writeFile(path, '')
226
- return 0
227
- }, cb)
228
- },
229
-
230
- truncate: readOnly
231
- ? (_path: string, _size: number, cb: (code: number) => void) => {
232
- cb(readonlyError())
233
- }
234
- : async (path: string, size: number, cb: (code: number) => void) => {
235
- void runFuseOp(async () => {
236
- const bytes = toFileData(await fs.readFile(path))
237
- await fs.writeFile(path, bytes.slice(0, size))
238
- }, cb as (code: number, _value?: void) => void)
239
- },
240
-
241
- unlink: readOnly
242
- ? (_path: string, cb: (code: number) => void) => {
243
- cb(readonlyError())
244
- }
245
- : async (path: string, cb: (code: number) => void) => {
246
- void runFuseOp(async () => {
247
- await fs.unlink(path)
248
- }, cb as (code: number, _value?: void) => void)
249
- },
250
-
251
- rmdir: readOnly
252
- ? (_path: string, cb: (code: number) => void) => {
253
- cb(readonlyError())
254
- }
255
- : async (path: string, cb: (code: number) => void) => {
256
- void runFuseOp(async () => {
257
- await fs.rmdir(path)
258
- }, cb as (code: number, _value?: void) => void)
259
- },
260
-
261
- rename: readOnly
262
- ? (_source: string, _destination: string, cb: (code: number) => void) => {
263
- cb(readonlyError())
264
- }
265
- : async (source: string, destination: string, cb: (code: number) => void) => {
266
- void runFuseOp(async () => {
267
- await fs.rename(source, destination)
268
- }, cb as (code: number, _value?: void) => void)
269
- },
270
- }
271
-
272
- await new Promise<void>((resolve, reject) => {
273
- fuse.mount(mountPoint, operations, (error?: Error | null) => {
274
- if (error) {
275
- reject(error)
276
- return
277
- }
278
-
279
- resolve()
280
- })
281
- })
282
-
283
- return {
284
- unmount: () => {
285
- return new Promise<void>((resolve, reject) => {
286
- fuse.unmount(mountPoint, (error?: Error | null) => {
287
- if (error) {
288
- reject(error)
289
- return
290
- }
291
-
292
- resolve()
293
- })
294
- })
295
- },
296
- }
297
- }
@@ -1,7 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "types": ["node"]
5
- },
6
- "include": ["src"]
7
- }
@@ -1,157 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it } from 'vitest'
2
- import { resolve } from 'node:path'
3
-
4
- import { GitMcpClient } from '../../src'
5
-
6
- const fixturePath = resolve(process.cwd(), 'tests/e2e/server.fixture.ts')
7
-
8
- const connectClient = async (args: string[] = []): Promise<GitMcpClient> => {
9
- const client = new GitMcpClient({
10
- name: 'mcp-e2e-client',
11
- version: '1.0.0',
12
- })
13
-
14
- await client.connect('bun', ['run', fixturePath, ...args])
15
- return client
16
- }
17
-
18
- describe('mcp e2e server/client', () => {
19
- let client: GitMcpClient
20
-
21
- beforeEach(async () => {
22
- client = await connectClient()
23
- })
24
-
25
- afterEach(async () => {
26
- await client.close()
27
- })
28
-
29
- it('registers issue tools', async () => {
30
- const tools = await client.listTools()
31
-
32
- expect(tools).toContain('repo.issue.create')
33
- expect(tools).toContain('repo.issue.list')
34
- expect(tools).toContain('repo.contents.create')
35
- expect(tools).toContain('repo.contents.view')
36
- expect(tools).toContain('repo.contents.list')
37
- expect(tools).toContain('repo.contents.update')
38
- expect(tools).toContain('repo.contents.delete')
39
- })
40
-
41
- it('executes an api tool and returns execution payload', async () => {
42
- const response = await client.call('repo.issue.create', {
43
- options: {
44
- data: {
45
- title: 'Bug report',
46
- body: 'Crash repro steps',
47
- },
48
- },
49
- })
50
-
51
- expect(response.isError).toBe(false)
52
- expect(response.data).toMatchObject({
53
- status: 201,
54
- ok: true,
55
- body: {
56
- ok: true,
57
- action: 'issue.create',
58
- method: 'POST',
59
- },
60
- })
61
- })
62
-
63
- it('executes contents create and list tools', async () => {
64
- const createResponse = await client.call('repo.contents.create', {
65
- args: ['README.md'],
66
- options: {
67
- data: {
68
- content: 'cmVhZGVtZSBmaWxl',
69
- message: 'create file',
70
- },
71
- },
72
- })
73
-
74
- expect(createResponse.isError).toBe(false)
75
- expect(createResponse.data).toMatchObject({
76
- status: 200,
77
- ok: true,
78
- body: {
79
- action: 'repo-mock',
80
- method: 'POST',
81
- path: '/repos/example-org/example-repo/contents/README.md',
82
- },
83
- })
84
-
85
- const viewResponse = await client.call('repo.contents.view', {
86
- args: ['README.md'],
87
- })
88
-
89
- expect(viewResponse.isError).toBe(false)
90
- expect(viewResponse.data).toMatchObject({
91
- status: 200,
92
- ok: true,
93
- body: {
94
- action: 'repo-mock',
95
- method: 'GET',
96
- path: '/repos/example-org/example-repo/contents/README.md',
97
- },
98
- })
99
-
100
- const listResponse = await client.call('repo.contents.list', {
101
- args: ['docs'],
102
- })
103
-
104
- expect(listResponse.isError).toBe(false)
105
- expect(listResponse.data).toMatchObject({
106
- status: 200,
107
- ok: true,
108
- body: {
109
- action: 'repo-mock',
110
- method: 'GET',
111
- path: '/repos/example-org/example-repo/contents/docs',
112
- },
113
- })
114
- })
115
-
116
- it('honors tool name prefix in server configuration', async () => {
117
- const prefixedClient = await connectClient(['--tools-prefix=git'])
118
- const tools = await prefixedClient.listTools()
119
-
120
- await prefixedClient.close()
121
-
122
- expect(tools).toContain('git.repo.issue.create')
123
- })
124
-
125
- it('registers and executes batch tool', async () => {
126
- const tools = await client.listTools()
127
- const response = await client.call('batch', {
128
- calls: [
129
- {
130
- tool: 'repo.issue.create',
131
- options: {
132
- data: {
133
- title: 'Bug report from batch',
134
- body: 'Crash repro steps',
135
- },
136
- },
137
- },
138
- {
139
- tool: 'repo.contents.list',
140
- args: ['docs'],
141
- },
142
- ],
143
- })
144
-
145
- expect(tools).toContain('batch')
146
- expect(response.isError).toBe(false)
147
- expect(Array.isArray(response.data)).toBe(true)
148
- expect(response.data).toMatchObject([
149
- { index: 0, tool: 'repo.issue.create', isError: false },
150
- { index: 1, tool: 'repo.contents.list', isError: false },
151
- ])
152
- })
153
-
154
- it('throws a transport error for unknown tools', async () => {
155
- await expect(client.call('repo.issue.invalid')).rejects.toThrow()
156
- })
157
- })
@@ -1,109 +0,0 @@
1
- import { createGitMcpServer } from '../../src/server'
2
-
3
- const toolsPrefixArg = process.argv.find((value) => value.startsWith('--tools-prefix='))
4
- const toolsPrefix = toolsPrefixArg?.slice('--tools-prefix='.length)
5
- const toolsPrefixNormalized = toolsPrefix && toolsPrefix.trim().length > 0 ? toolsPrefix : undefined
6
-
7
- type MockResponseBody = Record<string, unknown>
8
-
9
- const jsonResponse = (body: MockResponseBody, status = 200): Response =>
10
- new Response(JSON.stringify(body), {
11
- status,
12
- headers: {
13
- 'content-type': 'application/json',
14
- },
15
- })
16
-
17
- const parseJsonBody = async (body: BodyInit | null | undefined): Promise<unknown> => {
18
- if (body === null || body === undefined) {
19
- return null
20
- }
21
-
22
- if (typeof body === 'string' || body instanceof String) {
23
- try {
24
- return JSON.parse(String(body))
25
- } catch {
26
- return String(body)
27
- }
28
- }
29
-
30
- try {
31
- return await new Response(body).json()
32
- } catch {
33
- return String(body)
34
- }
35
- }
36
-
37
- // Lightweight transport-safe mock for fetch so the MCP fixture never touches the network.
38
- globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
39
- const requestUrl = String(input)
40
- const method = (init?.method ?? 'GET').toUpperCase()
41
- const parsedUrl = new URL(requestUrl)
42
- const body = await parseJsonBody(init?.body)
43
-
44
- const query: Record<string, string> = Object.fromEntries(parsedUrl.searchParams.entries())
45
-
46
- if (parsedUrl.pathname.endsWith('/repos/example-org/example-repo/issues')) {
47
- if (method === 'POST') {
48
- return jsonResponse(
49
- {
50
- ok: true,
51
- action: 'issue.create',
52
- method,
53
- path: parsedUrl.pathname,
54
- query,
55
- body,
56
- },
57
- 201,
58
- )
59
- }
60
-
61
- return jsonResponse({
62
- ok: true,
63
- action: 'issue.list',
64
- method,
65
- path: parsedUrl.pathname,
66
- query,
67
- body,
68
- })
69
- }
70
-
71
- if (parsedUrl.pathname.includes('/api/v1/repos/example-org/example-repo')) {
72
- return jsonResponse({
73
- ok: true,
74
- action: 'repo-mock',
75
- method,
76
- path: parsedUrl.pathname,
77
- query,
78
- body,
79
- })
80
- }
81
-
82
- return jsonResponse({
83
- ok: true,
84
- action: 'generic',
85
- method,
86
- path: parsedUrl.pathname,
87
- query,
88
- body,
89
- })
90
- }
91
-
92
- const fixture = createGitMcpServer({
93
- toolsPrefix: toolsPrefixNormalized,
94
- config: {
95
- platform: 'GITEA',
96
- giteaHost: 'https://gitea.example.com',
97
- giteaToken: 'e2e-token',
98
- },
99
- defaultOwner: 'example-org',
100
- defaultRepo: 'example-repo',
101
- })
102
-
103
- await fixture.run()
104
-
105
- await new Promise<void>((resolve) => {
106
- process.stdin.on('close', resolve)
107
- process.on('SIGINT', resolve)
108
- process.on('SIGTERM', resolve)
109
- })
@@ -1,21 +0,0 @@
1
- #!/bin/sh
2
- basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
-
4
- case `uname` in
5
- *CYGWIN*|*MINGW*|*MSYS*)
6
- if command -v cygpath > /dev/null 2>&1; then
7
- basedir=`cygpath -w "$basedir"`
8
- fi
9
- ;;
10
- esac
11
-
12
- if [ -z "$NODE_PATH" ]; then
13
- export NODE_PATH="/mnt/c/Users/marko/Dev/F0/git/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/mnt/c/Users/marko/Dev/F0/git/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/mnt/c/Users/marko/Dev/F0/git/node_modules/.pnpm/typescript@5.9.3/node_modules:/mnt/c/Users/marko/Dev/F0/git/node_modules/.pnpm/node_modules"
14
- else
15
- export NODE_PATH="/mnt/c/Users/marko/Dev/F0/git/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/mnt/c/Users/marko/Dev/F0/git/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/mnt/c/Users/marko/Dev/F0/git/node_modules/.pnpm/typescript@5.9.3/node_modules:/mnt/c/Users/marko/Dev/F0/git/node_modules/.pnpm/node_modules:$NODE_PATH"
16
- fi
17
- if [ -x "$basedir/node" ]; then
18
- exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
19
- else
20
- exec node "$basedir/../typescript/bin/tsc" "$@"
21
- fi
@@ -1,12 +0,0 @@
1
- @SETLOCAL
2
- @IF NOT DEFINED NODE_PATH (
3
- @SET "NODE_PATH=C:\Users\marko\Dev\F0\git\node_modules\.pnpm\typescript@5.9.3\node_modules\typescript\bin\node_modules;C:\Users\marko\Dev\F0\git\node_modules\.pnpm\typescript@5.9.3\node_modules\typescript\node_modules;C:\Users\marko\Dev\F0\git\node_modules\.pnpm\typescript@5.9.3\node_modules;C:\Users\marko\Dev\F0\git\node_modules\.pnpm\node_modules"
4
- ) ELSE (
5
- @SET "NODE_PATH=C:\Users\marko\Dev\F0\git\node_modules\.pnpm\typescript@5.9.3\node_modules\typescript\bin\node_modules;C:\Users\marko\Dev\F0\git\node_modules\.pnpm\typescript@5.9.3\node_modules\typescript\node_modules;C:\Users\marko\Dev\F0\git\node_modules\.pnpm\typescript@5.9.3\node_modules;C:\Users\marko\Dev\F0\git\node_modules\.pnpm\node_modules;%NODE_PATH%"
6
- )
7
- @IF EXIST "%~dp0\node.exe" (
8
- "%~dp0\node.exe" "%~dp0\..\typescript\bin\tsc" %*
9
- ) ELSE (
10
- @SET PATHEXT=%PATHEXT:;.JS;=;%
11
- node "%~dp0\..\typescript\bin\tsc" %*
12
- )
@@ -1,41 +0,0 @@
1
- #!/usr/bin/env pwsh
2
- $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
3
-
4
- $exe=""
5
- $pathsep=":"
6
- $env_node_path=$env:NODE_PATH
7
- $new_node_path="C:\Users\marko\Dev\F0\git\node_modules\.pnpm\typescript@5.9.3\node_modules\typescript\bin\node_modules;C:\Users\marko\Dev\F0\git\node_modules\.pnpm\typescript@5.9.3\node_modules\typescript\node_modules;C:\Users\marko\Dev\F0\git\node_modules\.pnpm\typescript@5.9.3\node_modules;C:\Users\marko\Dev\F0\git\node_modules\.pnpm\node_modules"
8
- if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
9
- # Fix case when both the Windows and Linux builds of Node
10
- # are installed in the same directory
11
- $exe=".exe"
12
- $pathsep=";"
13
- } else {
14
- $new_node_path="/mnt/c/Users/marko/Dev/F0/git/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/mnt/c/Users/marko/Dev/F0/git/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/mnt/c/Users/marko/Dev/F0/git/node_modules/.pnpm/typescript@5.9.3/node_modules:/mnt/c/Users/marko/Dev/F0/git/node_modules/.pnpm/node_modules"
15
- }
16
- if ([string]::IsNullOrEmpty($env_node_path)) {
17
- $env:NODE_PATH=$new_node_path
18
- } else {
19
- $env:NODE_PATH="$new_node_path$pathsep$env_node_path"
20
- }
21
-
22
- $ret=0
23
- if (Test-Path "$basedir/node$exe") {
24
- # Support pipeline input
25
- if ($MyInvocation.ExpectingInput) {
26
- $input | & "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
27
- } else {
28
- & "$basedir/node$exe" "$basedir/../typescript/bin/tsc" $args
29
- }
30
- $ret=$LASTEXITCODE
31
- } else {
32
- # Support pipeline input
33
- if ($MyInvocation.ExpectingInput) {
34
- $input | & "node$exe" "$basedir/../typescript/bin/tsc" $args
35
- } else {
36
- & "node$exe" "$basedir/../typescript/bin/tsc" $args
37
- }
38
- $ret=$LASTEXITCODE
39
- }
40
- $env:NODE_PATH=$env_node_path
41
- exit $ret