@foundation0/git 1.0.0 → 1.2.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.
Files changed (115) hide show
  1. package/{packages/git/mcp → mcp}/cli.mjs +0 -0
  2. package/package.json +43 -13
  3. package/.codex.example/config.toml +0 -10
  4. package/.env.example +0 -10
  5. package/packages/fs/README.md +0 -47
  6. package/packages/fs/node_modules/.bin/f0-git-mcp +0 -21
  7. package/packages/fs/node_modules/.bin/f0-git-mcp-server +0 -21
  8. package/packages/fs/node_modules/.bin/f0-git-mcp-server.CMD +0 -12
  9. package/packages/fs/node_modules/.bin/f0-git-mcp-server.ps1 +0 -41
  10. package/packages/fs/node_modules/.bin/f0-git-mcp.CMD +0 -12
  11. package/packages/fs/node_modules/.bin/f0-git-mcp.ps1 +0 -41
  12. package/packages/fs/node_modules/.bin/tsc +0 -21
  13. package/packages/fs/node_modules/.bin/tsc.CMD +0 -12
  14. package/packages/fs/node_modules/.bin/tsc.ps1 +0 -41
  15. package/packages/fs/node_modules/.bin/tsserver +0 -21
  16. package/packages/fs/node_modules/.bin/tsserver.CMD +0 -12
  17. package/packages/fs/node_modules/.bin/tsserver.ps1 +0 -41
  18. package/packages/fs/node_modules/.bin/vite +0 -21
  19. package/packages/fs/node_modules/.bin/vite.CMD +0 -12
  20. package/packages/fs/node_modules/.bin/vite.ps1 +0 -41
  21. package/packages/fs/node_modules/.bin/vitest +0 -21
  22. package/packages/fs/node_modules/.bin/vitest.CMD +0 -12
  23. package/packages/fs/node_modules/.bin/vitest.ps1 +0 -41
  24. package/packages/fs/package.json +0 -28
  25. package/packages/fs/src/cli.ts +0 -74
  26. package/packages/fs/src/git-fs.ts +0 -705
  27. package/packages/fs/src/index.ts +0 -33
  28. package/packages/fs/src/mount.ts +0 -297
  29. package/packages/fs/tsconfig.json +0 -7
  30. package/packages/git/mcp/tests/e2e/git-mcp-e2e.spec.ts +0 -157
  31. package/packages/git/mcp/tests/e2e/server.fixture.ts +0 -109
  32. package/packages/git/node_modules/.bin/tsc +0 -21
  33. package/packages/git/node_modules/.bin/tsc.CMD +0 -12
  34. package/packages/git/node_modules/.bin/tsc.ps1 +0 -41
  35. package/packages/git/node_modules/.bin/tsserver +0 -21
  36. package/packages/git/node_modules/.bin/tsserver.CMD +0 -12
  37. package/packages/git/node_modules/.bin/tsserver.ps1 +0 -41
  38. package/packages/git/node_modules/.bin/vite +0 -21
  39. package/packages/git/node_modules/.bin/vite.CMD +0 -12
  40. package/packages/git/node_modules/.bin/vite.ps1 +0 -41
  41. package/packages/git/node_modules/.bin/vitest +0 -21
  42. package/packages/git/node_modules/.bin/vitest.CMD +0 -12
  43. package/packages/git/node_modules/.bin/vitest.ps1 +0 -41
  44. package/packages/git/node_modules/.vite/vitest/results.json +0 -1
  45. package/packages/git/package.json +0 -60
  46. package/packages/git/scripts/create-issue.mjs +0 -93
  47. package/packages/git/scripts/extract-git-spec.mjs +0 -234
  48. package/packages/git/scripts/fetch-gitea-swagger.mjs +0 -22
  49. package/packages/git/tests/api.spec.ts +0 -55
  50. package/packages/git/tests/e2e/git-service-feature-e2e.spec.ts +0 -232
  51. package/packages/git/tests/git-service-api-object.spec.ts +0 -97
  52. package/packages/git/tests/git-service-feature-matrix.spec.ts +0 -182
  53. package/packages/git/tests/issue-dependencies.spec.ts +0 -81
  54. package/packages/git/tsconfig.json +0 -7
  55. package/packages/git/vitest.config.ts +0 -7
  56. package/packages/utils/package.json +0 -9
  57. package/packages/utils/src/awk.ts +0 -6
  58. package/packages/utils/src/cat.ts +0 -6
  59. package/packages/utils/src/cd.ts +0 -6
  60. package/packages/utils/src/chgrp.ts +0 -6
  61. package/packages/utils/src/chmod.ts +0 -6
  62. package/packages/utils/src/chown.ts +0 -6
  63. package/packages/utils/src/cp.ts +0 -6
  64. package/packages/utils/src/curl.ts +0 -6
  65. package/packages/utils/src/cut.ts +0 -6
  66. package/packages/utils/src/date.ts +0 -6
  67. package/packages/utils/src/echo.ts +0 -6
  68. package/packages/utils/src/find.ts +0 -6
  69. package/packages/utils/src/grep.ts +0 -6
  70. package/packages/utils/src/gunzip.ts +0 -6
  71. package/packages/utils/src/gzip.ts +0 -6
  72. package/packages/utils/src/head.ts +0 -6
  73. package/packages/utils/src/hostname.ts +0 -6
  74. package/packages/utils/src/index.ts +0 -37
  75. package/packages/utils/src/ls.ts +0 -6
  76. package/packages/utils/src/mkdir.ts +0 -6
  77. package/packages/utils/src/mv.ts +0 -6
  78. package/packages/utils/src/ping.ts +0 -6
  79. package/packages/utils/src/pwd.ts +0 -6
  80. package/packages/utils/src/rm.ts +0 -6
  81. package/packages/utils/src/rmdir.ts +0 -6
  82. package/packages/utils/src/sed.ts +0 -6
  83. package/packages/utils/src/sort.ts +0 -6
  84. package/packages/utils/src/tail.ts +0 -6
  85. package/packages/utils/src/tar.ts +0 -6
  86. package/packages/utils/src/touch.ts +0 -6
  87. package/packages/utils/src/tr.ts +0 -6
  88. package/packages/utils/src/uname.ts +0 -6
  89. package/packages/utils/src/uniq.ts +0 -6
  90. package/packages/utils/src/unzip.ts +0 -6
  91. package/packages/utils/src/util.ts +0 -4
  92. package/packages/utils/src/wc.ts +0 -6
  93. package/packages/utils/src/wget.ts +0 -6
  94. package/packages/utils/src/whoami.ts +0 -6
  95. package/packages/utils/src/zip.ts +0 -6
  96. package/pnpm-workspace.yaml +0 -2
  97. package/tsconfig.base.json +0 -12
  98. /package/{packages/git/README.md → README.md} +0 -0
  99. /package/{packages/git/gitea-swagger.json → gitea-swagger.json} +0 -0
  100. /package/{packages/git/mcp → mcp}/README.md +0 -0
  101. /package/{packages/git/mcp → mcp}/src/cli.ts +0 -0
  102. /package/{packages/git/mcp → mcp}/src/client.ts +0 -0
  103. /package/{packages/git/mcp → mcp}/src/index.ts +0 -0
  104. /package/{packages/git/mcp → mcp}/src/server.ts +0 -0
  105. /package/{packages/git/src → src}/api.ts +0 -0
  106. /package/{packages/git/src → src}/git-service-api.ts +0 -0
  107. /package/{packages/git/src → src}/git-service-feature-spec.generated.ts +0 -0
  108. /package/{packages/git/src → src}/index.ts +0 -0
  109. /package/{packages/git/src → src}/issue-dependencies.ts +0 -0
  110. /package/{packages/git/src → src}/platform/config.ts +0 -0
  111. /package/{packages/git/src → src}/platform/gitea-adapter.ts +0 -0
  112. /package/{packages/git/src → src}/platform/gitea-rules.ts +0 -0
  113. /package/{packages/git/src → src}/platform/index.ts +0 -0
  114. /package/{packages/git/src → src}/repository.ts +0 -0
  115. /package/{packages/git/src → src}/spec-mock.ts +0 -0
@@ -1,55 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { createRemoteGitApiClient, GIT_API_VERSION } from '../src'
4
-
5
- describe('remote Git API client', () => {
6
- it('builds default response that matches the mock contract', () => {
7
- const client = createRemoteGitApiClient()
8
- const response = client.request({
9
- path: ['repos', 'owner', 'repo', 'issues', '123'],
10
- query: ['state=open'],
11
- headers: ['Accept: application/vnd.github+json'],
12
- })
13
-
14
- expect(response).toEqual({
15
- endpoint: 'https://api.github.com/repos/owner/repo/issues/123',
16
- method: 'GET',
17
- path: ['repos', 'owner', 'repo', 'issues', '123'],
18
- query: ['state=open'],
19
- headers: ['Accept: application/vnd.github+json'],
20
- requestParts: [
21
- 'GET',
22
- 'repos',
23
- 'owner',
24
- 'repo',
25
- 'issues',
26
- '123',
27
- 'state=open',
28
- 'Accept: application/vnd.github+json',
29
- ],
30
- apiVersion: GIT_API_VERSION,
31
- })
32
- })
33
-
34
- it('supports API base, version, and async request custom defaults', async () => {
35
- const client = createRemoteGitApiClient({
36
- apiBase: 'https://internal.example.com',
37
- apiVersion: '2022-11-28-preview',
38
- defaultMethod: 'POST',
39
- })
40
- const response = await client.requestAsync({
41
- method: 'PATCH',
42
- path: ['repos', 'owner', 'repo', 'issues', '999'],
43
- })
44
-
45
- expect(response).toEqual({
46
- endpoint: 'https://internal.example.com/repos/owner/repo/issues/999',
47
- method: 'PATCH',
48
- path: ['repos', 'owner', 'repo', 'issues', '999'],
49
- query: [],
50
- headers: [],
51
- requestParts: ['PATCH', 'repos', 'owner', 'repo', 'issues', '999'],
52
- apiVersion: '2022-11-28-preview',
53
- })
54
- })
55
- })
@@ -1,232 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import swaggerSpec from '../../gitea-swagger.json'
4
- import { getGitPlatformConfig } from '../../src/platform/config'
5
-
6
- interface SwaggerParameter {
7
- name?: string
8
- in?: string
9
- required?: boolean
10
- schema?: {
11
- type?: string
12
- enum?: unknown[]
13
- format?: string
14
- }
15
- }
16
-
17
- interface SwaggerOperation {
18
- parameters?: SwaggerParameter[]
19
- }
20
-
21
- interface SwaggerPathEntry {
22
- [method: string]: SwaggerOperation | undefined
23
- parameters?: SwaggerParameter[]
24
- }
25
-
26
- interface SwaggerOperationCase {
27
- pathTemplate: string
28
- method: string
29
- resolvedPath: string
30
- }
31
-
32
- const normalizePlatformHost = (host: string): string => {
33
- const trimmed = host.replace(/\/$/, '')
34
- if (trimmed.endsWith('/api/v1')) {
35
- return trimmed
36
- }
37
-
38
- return `${trimmed}/api/v1`
39
- }
40
-
41
- const platformConfig = getGitPlatformConfig({ platform: 'GITEA' })
42
- const host = normalizePlatformHost(platformConfig.giteaHost)
43
- const apiToken = platformConfig.giteaToken
44
- const testOwner = process.env.GITEA_TEST_OWNER ?? 'example-org'
45
- const testRepo = process.env.GITEA_TEST_REPO ?? 'example-repo'
46
-
47
- const defaultValueFor = (name: string, schema?: SwaggerParameter['schema']): string => {
48
- const normalized = name.toLowerCase()
49
-
50
- if (schema?.type === 'integer' || schema?.type === 'number') {
51
- return '1'
52
- }
53
-
54
- if (schema?.type === 'boolean') {
55
- return 'true'
56
- }
57
-
58
- if (schema?.enum && schema.enum.length > 0) {
59
- return String(schema.enum[0])
60
- }
61
-
62
- if (normalized === 'owner' || normalized === 'org' || normalized === 'organization' || normalized === 'user' || normalized === 'username') {
63
- return testOwner
64
- }
65
-
66
- if (normalized === 'repo' || normalized === 'repository') {
67
- return testRepo
68
- }
69
-
70
- if (normalized === 'archive' || normalized.includes('archive')) {
71
- return 'zip'
72
- }
73
-
74
- if (normalized === 'difftype' || normalized.includes('diff') || normalized.includes('diff-type')) {
75
- return 'patch'
76
- }
77
-
78
- if (normalized.includes('filepath') || normalized.includes('file-path') || normalized.includes('path') || normalized.includes('filename')) {
79
- return 'README.md'
80
- }
81
-
82
- if (normalized.includes('pagename') || normalized.includes('page-name')) {
83
- return 'readme'
84
- }
85
-
86
- if (normalized.includes('sha') || normalized.includes('branch') || normalized.includes('ref')) {
87
- return 'main'
88
- }
89
-
90
- if (normalized.includes('number') || /[id]/.test(normalized) || normalized.includes('id') || normalized.includes('index') || normalized.includes('tag_id')) {
91
- return '1'
92
- }
93
-
94
- if (normalized.includes('tag') || normalized.includes('release')) {
95
- return 'v1'
96
- }
97
-
98
- if (normalized.includes('name') || normalized.includes('title') || normalized.includes('secretname') || normalized.includes('username')) {
99
- return 'sample'
100
- }
101
-
102
- return '1'
103
- }
104
-
105
- const getPathDefaults = (pathTemplate: string, pathItem: SwaggerPathEntry, operation: SwaggerOperation): Record<string, string> => {
106
- const parameterDefs = new Map<string, SwaggerParameter>()
107
-
108
- for (const parameter of [...(pathItem.parameters ?? []), ...(operation.parameters ?? [])]) {
109
- if (!parameter?.name) {
110
- continue
111
- }
112
-
113
- if (parameter.in === 'path') {
114
- parameterDefs.set(parameter.name, parameter)
115
- }
116
- }
117
-
118
- const placeholders = new Set<string>()
119
- const pattern = /\{([^}]+)\}/g
120
- let match: RegExpExecArray | null = null
121
-
122
- while ((match = pattern.exec(pathTemplate)) !== null) {
123
- placeholders.add(match[1])
124
- }
125
-
126
- const defaults: Record<string, string> = {}
127
- for (const placeholder of placeholders) {
128
- const definition = parameterDefs.get(placeholder)
129
- defaults[placeholder] = defaultValueFor(placeholder, definition?.schema)
130
- }
131
-
132
- return defaults
133
- }
134
-
135
- const resolvePath = (pathTemplate: string, defaults: Record<string, string>): string =>
136
- pathTemplate.replace(/\{([^}]+)\}/g, (_match, key: string) => {
137
- const value = defaults[key] ?? '1'
138
- return encodeURIComponent(value)
139
- })
140
-
141
- const buildSwaggerCases = (): SwaggerOperationCase[] => {
142
- const cases: SwaggerOperationCase[] = []
143
-
144
- const paths = swaggerSpec.paths as Record<string, SwaggerPathEntry> | undefined
145
- if (!paths) {
146
- return cases
147
- }
148
-
149
- const sortedPaths = Object.keys(paths).sort()
150
- for (const pathTemplate of sortedPaths) {
151
- const pathItem = paths[pathTemplate]
152
- if (!pathItem || typeof pathItem !== 'object') {
153
- continue
154
- }
155
-
156
- const methods = Object.keys(pathItem).filter(
157
- (method) => method !== 'parameters' && typeof (pathItem as Record<string, unknown>)[method] === 'object',
158
- )
159
-
160
- for (const method of methods.sort()) {
161
- const operation = pathItem[method]
162
- if (!operation || typeof operation !== 'object') {
163
- continue
164
- }
165
-
166
- const defaults = getPathDefaults(pathTemplate, pathItem, operation)
167
- const resolvedPath = resolvePath(pathTemplate, defaults)
168
-
169
- cases.push({
170
- pathTemplate,
171
- method: method.toUpperCase(),
172
- resolvedPath,
173
- })
174
- }
175
- }
176
-
177
- return cases
178
- }
179
-
180
- const headers: Record<string, string> = {
181
- Accept: 'application/json',
182
- }
183
-
184
- if (apiToken) {
185
- headers.Authorization = `token ${apiToken}`
186
- }
187
-
188
- const swaggerCases = buildSwaggerCases()
189
-
190
- const getAllowedStatuses = (method: string): number[] => {
191
- const normalizedMethod = method.toUpperCase()
192
- if (normalizedMethod === 'GET') {
193
- return [200, 201, 204, 301, 302, 304, 400, 401, 403, 404, 405, 409, 422, 429]
194
- }
195
-
196
- if (normalizedMethod === 'POST') {
197
- return [200, 201, 202, 204, 400, 401, 403, 404, 405, 409, 422, 429]
198
- }
199
-
200
- if (normalizedMethod === 'PATCH' || normalizedMethod === 'PUT') {
201
- return [200, 201, 202, 204, 400, 401, 403, 404, 405, 409, 422, 429]
202
- }
203
-
204
- if (normalizedMethod === 'DELETE') {
205
- return [200, 202, 204, 400, 401, 403, 404, 405, 409, 422, 429]
206
- }
207
-
208
- if (normalizedMethod === 'HEAD') {
209
- return [200, 204, 301, 302, 304, 401, 403, 404]
210
- }
211
-
212
- return [200, 201, 204, 400, 401, 403, 404, 405, 422, 429]
213
- }
214
-
215
- describe('e2e git service API coverage from swagger', () => {
216
- it('reads host configuration', () => {
217
- expect(host).toMatch(/^https?:\/\/.+/)
218
- })
219
-
220
- for (const caseSpec of swaggerCases) {
221
- const label = `${caseSpec.method} ${caseSpec.pathTemplate}`
222
- it(`hits ${label}`, async () => {
223
- const response = await fetch(`${host}${caseSpec.resolvedPath}`, {
224
- method: caseSpec.method,
225
- headers,
226
- })
227
-
228
- expect(response.status).toBeLessThan(500)
229
- expect(getAllowedStatuses(caseSpec.method)).toContain(response.status)
230
- })
231
- }
232
- })
@@ -1,97 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest'
2
-
3
- import { createGitServiceApi } from '../src'
4
-
5
- describe('gitServiceApi object', () => {
6
- it('exposes repo.issue.create and posts issue payload', async () => {
7
- const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
8
- new Response(
9
- JSON.stringify({
10
- id: 1,
11
- number: 5,
12
- title: 'Hello',
13
- html_url: 'http://gitea.example.com/example-org/example-repo/issues/5',
14
- }),
15
- { status: 201, headers: { 'content-type': 'application/json' } },
16
- ),
17
- )
18
-
19
- const api = createGitServiceApi({
20
- config: {
21
- platform: 'GITEA',
22
- giteaHost: 'https://gitea.example.com',
23
- giteaToken: 'token',
24
- },
25
- defaultOwner: 'example-org',
26
- defaultRepo: 'example-repo',
27
- })
28
-
29
- expect(typeof api.repo).toBe('object')
30
- expect(typeof (api.repo as Record<string, unknown>).issue).toBe('object')
31
- expect(typeof (api.repo as Record<string, unknown>).issue.create).toBe('function')
32
-
33
- const result = await (api.repo as Record<string, any>).issue.create({
34
- data: {
35
- title: 'Hello',
36
- body: 'world',
37
- },
38
- })
39
-
40
- expect(fetchSpy).toHaveBeenCalledTimes(1)
41
-
42
- const [requestUrl, requestInit] = fetchSpy.mock.calls[0]
43
- expect(requestUrl).toBe('https://gitea.example.com/api/v1/repos/example-org/example-repo/issues')
44
- expect(requestInit?.method).toBe('POST')
45
-
46
- expect(JSON.parse(requestInit?.body as string)).toEqual({
47
- title: 'Hello',
48
- body: 'world',
49
- })
50
-
51
- expect(result.status).toBe(201)
52
- expect(result.body).toEqual({
53
- id: 1,
54
- number: 5,
55
- title: 'Hello',
56
- html_url: 'http://gitea.example.com/example-org/example-repo/issues/5',
57
- })
58
-
59
- fetchSpy.mockRestore()
60
- })
61
-
62
- it('aborts requests when requestTimeoutMs elapses', async () => {
63
- const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(
64
- (_url: any, init?: any) =>
65
- new Promise((_resolve, reject) => {
66
- const signal = init?.signal as AbortSignal | undefined
67
- if (signal?.aborted) {
68
- reject(new DOMException('Aborted', 'AbortError'))
69
- return
70
- }
71
- signal?.addEventListener('abort', () => reject(new DOMException('Aborted', 'AbortError')))
72
- }) as any,
73
- )
74
-
75
- const api = createGitServiceApi({
76
- config: {
77
- platform: 'GITEA',
78
- giteaHost: 'https://gitea.example.com',
79
- giteaToken: 'token',
80
- },
81
- defaultOwner: 'example-org',
82
- defaultRepo: 'example-repo',
83
- requestTimeoutMs: 5,
84
- })
85
-
86
- await expect(
87
- (api.repo as Record<string, any>).issue.create({
88
- data: {
89
- title: 'Hello',
90
- body: 'world',
91
- },
92
- }),
93
- ).rejects.toThrow(/Request timed out after 5ms:/)
94
-
95
- fetchSpy.mockRestore()
96
- })
97
- })
@@ -1,182 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import swaggerSpec from '../gitea-swagger.json'
4
- import { GIT_API_VERSION, buildGitApiMockResponse, createGitPlatformAdapter, type GitApiFeatureContext } from '../src'
5
- import { gitServiceFeatureSpec } from '../src/git-service-feature-spec.generated'
6
- import { getGitPlatformConfig } from '../src/platform/config'
7
-
8
- interface FeatureMatrixCase {
9
- path: string[]
10
- args: string[]
11
- flagValues: Record<string, string | boolean | undefined>
12
- }
13
-
14
- const parseArgumentValue = (argument: string): string | null => {
15
- const trimmed = argument.trim()
16
- if (!trimmed) {
17
- return null
18
- }
19
-
20
- if (trimmed.startsWith('-') || trimmed === '[flags]') {
21
- return null
22
- }
23
-
24
- const firstAlternative = trimmed.split('|')[0].trim()
25
- if (!firstAlternative) {
26
- return null
27
- }
28
-
29
- const value = firstAlternative
30
- .replace(/^\[/, '')
31
- .replace(/\]$/, '')
32
- .replace(/^\{/, '')
33
- .replace(/\}$/, '')
34
- .replace(/^</, '')
35
- .replace(/>$/, '')
36
- .replace(/\.\.\.$/, '')
37
- .trim()
38
-
39
- if (!value || value.startsWith('-')) {
40
- return null
41
- }
42
-
43
- return value
44
- }
45
-
46
- const buildArgCases = (path: string[], args: string[]): FeatureMatrixCase[] => {
47
- const values = args
48
- .map((arg) => parseArgumentValue(arg))
49
- .filter((value): value is string => value !== null)
50
-
51
- if (values.length === 0) {
52
- return [{ path, args: [], flagValues: {} }]
53
- }
54
-
55
- const cases: FeatureMatrixCase[] = []
56
- for (let index = 0; index < values.length; index += 1) {
57
- cases.push({ path, args: values.slice(0, index + 1), flagValues: {} })
58
- }
59
-
60
- return cases
61
- }
62
-
63
- const buildFlagCase = (path: string[], args: string[], flag: { name: string; takesValue: boolean }): FeatureMatrixCase => ({
64
- path,
65
- args: args.map((arg) => parseArgumentValue(arg)).filter((value): value is string => value !== null),
66
- flagValues: {
67
- [flag.name]: flag.takesValue ? 'value' : true,
68
- },
69
- })
70
-
71
- const platformConfig = getGitPlatformConfig({
72
- platform: 'GITEA',
73
- })
74
- const testRepoOwner = process.env.GITEA_TEST_OWNER ?? 'example-org'
75
- const testRepoName = process.env.GITEA_TEST_REPO ?? 'example-repo'
76
-
77
- const adapter = createGitPlatformAdapter({
78
- config: platformConfig,
79
- swaggerSpec,
80
- })
81
-
82
- const prependTestRepoArgs = (args: string[]) => [testRepoOwner, testRepoName, ...args]
83
-
84
- const expectedResponse = (
85
- mappingPath: string[],
86
- mappingMethod: string,
87
- query: string[],
88
- headers: string[],
89
- apiBase: string,
90
- apiVersion?: string,
91
- ) =>
92
- buildGitApiMockResponse({
93
- path: mappingPath,
94
- method: mappingMethod,
95
- query,
96
- headers,
97
- apiBase,
98
- apiVersion,
99
- })
100
-
101
- describe('git service feature matrix', () => {
102
- it('is populated from the generated feature spec', () => {
103
- expect(gitServiceFeatureSpec.features.length).toBeGreaterThan(0)
104
- })
105
-
106
- for (const feature of gitServiceFeatureSpec.features) {
107
- const label = feature.path.join(' ')
108
- const featureContext = (args: string[], flagValues: FeatureMatrixCase['flagValues']): GitApiFeatureContext => ({
109
- feature,
110
- args,
111
- flagValues,
112
- })
113
-
114
- const argumentCases = buildArgCases(feature.path, feature.args)
115
-
116
- for (const [index, currentCase] of argumentCases.entries()) {
117
- const title =
118
- currentCase.args.length === 0
119
- ? `maps ${label} with no arguments`
120
- : `maps ${label} with first ${index + 1} argument(s)`
121
-
122
- it(title, async () => {
123
- const testArgs = prependTestRepoArgs(currentCase.args)
124
- const mapped = await adapter.mapFeature(featureContext(testArgs, currentCase.flagValues))
125
- const response = await adapter.mapFeatureResponse(featureContext(testArgs, currentCase.flagValues))
126
-
127
- expect(response).toEqual(
128
- expectedResponse(
129
- mapped.mappedPath,
130
- mapped.method,
131
- mapped.query,
132
- mapped.headers,
133
- mapped.apiBase,
134
- mapped.apiVersion ?? GIT_API_VERSION,
135
- ),
136
- )
137
- })
138
- }
139
-
140
- for (const flag of feature.flags) {
141
- const currentCase = buildFlagCase(feature.path, feature.args, flag)
142
- const title = `maps ${label} with flag ${flag.name}${flag.takesValue ? '=value' : ''}`
143
-
144
- it(title, async () => {
145
- const testArgs = prependTestRepoArgs(currentCase.args)
146
- const mapped = await adapter.mapFeature(featureContext(testArgs, currentCase.flagValues))
147
- const response = await adapter.mapFeatureResponse(featureContext(testArgs, currentCase.flagValues))
148
-
149
- expect(response).toEqual(
150
- expectedResponse(
151
- mapped.mappedPath,
152
- mapped.method,
153
- mapped.query,
154
- mapped.headers,
155
- mapped.apiBase,
156
- mapped.apiVersion ?? GIT_API_VERSION,
157
- ),
158
- )
159
- })
160
- }
161
-
162
- it(`${label} validates against local swagger spec`, async () => {
163
- const args = feature.args
164
- .map((arg) => parseArgumentValue(arg))
165
- .filter((value): value is string => value !== null)
166
- .slice(0, 2)
167
- const testArgs = prependTestRepoArgs(args)
168
-
169
- const validation = await adapter.validateFeature(featureContext(testArgs, {}))
170
-
171
- const response = await adapter.mapFeatureResponse(featureContext(testArgs, {}))
172
-
173
- expect(response.endpoint).toBe(`${validation.apiBase}/${validation.mappedPath.join('/')}`)
174
- expect(response.path).toEqual(validation.mappedPath)
175
- if (validation.mapped) {
176
- expect(validation.reason).toBeUndefined()
177
- } else {
178
- expect(validation.reason).toBeTruthy()
179
- }
180
- })
181
- }
182
- })
@@ -1,81 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest'
2
-
3
- import { extractDependencyIssueNumbers, syncIssueDependencies } from '../src/issue-dependencies'
4
-
5
- describe('extractDependencyIssueNumbers', () => {
6
- it('extracts issue numbers from direct array payload', () => {
7
- const values = extractDependencyIssueNumbers([
8
- { index: 12, owner: 'example-org', repo: 'test' },
9
- { number: '34', owner: 'example-org', repo: 'test' },
10
- ])
11
-
12
- expect(values.sort((a, b) => a - b)).toEqual([12, 34])
13
- })
14
-
15
- it('extracts issue numbers from wrapped objects', () => {
16
- const values = extractDependencyIssueNumbers({
17
- dependencies: [{ issue: { index: 5 } }, { issue: { number: 7 } }],
18
- })
19
-
20
- expect(values.sort((a, b) => a - b)).toEqual([5, 7])
21
- })
22
-
23
- it('extracts issue numbers from issue meta-like object', () => {
24
- const values = extractDependencyIssueNumbers({ issue: { index: 8 }, number: 9 })
25
- expect(values.sort((a, b) => a - b)).toEqual([8, 9])
26
- })
27
- })
28
-
29
- describe('syncIssueDependencies', () => {
30
- it('adds and removes dependencies and verifies final state', async () => {
31
- const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce(
32
- new Response(JSON.stringify([{ number: 2 }]), {
33
- status: 200,
34
- headers: { 'content-type': 'application/json' },
35
- }),
36
- ).mockResolvedValueOnce(
37
- new Response(null, {
38
- status: 204,
39
- headers: { 'content-type': 'application/json' },
40
- }),
41
- ).mockResolvedValueOnce(
42
- new Response(JSON.stringify({ status: 'ok' }), {
43
- status: 201,
44
- headers: { 'content-type': 'application/json' },
45
- }),
46
- ).mockResolvedValueOnce(
47
- new Response(JSON.stringify([{ number: 1 }]), {
48
- status: 200,
49
- headers: { 'content-type': 'application/json' },
50
- }),
51
- )
52
-
53
- const result = await syncIssueDependencies('example-org', 'example-repo', 42, [1], 'https://gitea.example.com', 'token', false)
54
-
55
- expect(result).toEqual({
56
- added: 1,
57
- removed: 1,
58
- unchanged: 0,
59
- })
60
-
61
- const calls = fetchSpy.mock.calls
62
- expect(calls).toHaveLength(4)
63
- const [url1, req1] = calls[0]
64
- const [url2, req2] = calls[1]
65
- const [url3, req3] = calls[2]
66
- const [url4] = calls[3]
67
-
68
- expect(url1).toBe('https://gitea.example.com/api/v1/repos/example-org/example-repo/issues/42/dependencies')
69
- expect(req1?.method).toBe('GET')
70
-
71
- expect(req2?.method).toBe('DELETE')
72
- expect(JSON.parse(req2?.body as string)).toMatchObject({ index: 2, owner: 'example-org', repo: 'example-repo' })
73
-
74
- expect(req3?.method).toBe('POST')
75
- expect(JSON.parse(req3?.body as string)).toMatchObject({ index: 1, owner: 'example-org', repo: 'example-repo' })
76
-
77
- expect(url4).toBe('https://gitea.example.com/api/v1/repos/example-org/example-repo/issues/42/dependencies')
78
-
79
- fetchSpy.mockRestore()
80
- })
81
- })
@@ -1,7 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "types": ["node"]
5
- },
6
- "include": ["src", "mcp/src", "tests", "mcp/tests"]
7
- }
@@ -1,7 +0,0 @@
1
- import { defineConfig } from 'vitest/config'
2
-
3
- export default defineConfig({
4
- test: {
5
- environment: 'node',
6
- },
7
- })
@@ -1,9 +0,0 @@
1
- {
2
- "name": "@workspace/utils",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "exports": {
7
- ".": "./src/index.ts"
8
- }
9
- }
@@ -1,6 +0,0 @@
1
- import { type UtilArgs, noop } from './util'
2
-
3
-
4
- export function awk(...args: UtilArgs): unknown {
5
- return noop(...args)
6
- }
@@ -1,6 +0,0 @@
1
- import { type UtilArgs, noop } from './util'
2
-
3
-
4
- export function cat(...args: UtilArgs): unknown {
5
- return noop(...args)
6
- }
@@ -1,6 +0,0 @@
1
- import { type UtilArgs, noop } from './util'
2
-
3
-
4
- export function cd(...args: UtilArgs): unknown {
5
- return noop(...args)
6
- }