@codeguide/core 0.0.15 → 0.0.17

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.
@@ -0,0 +1,113 @@
1
+ import { CodeGuide } from '../codeguide'
2
+ import { ConnectRepositoryRequest } from '../services/projects/project-types'
3
+ import { APIServiceConfig } from '../types'
4
+ import axios from 'axios'
5
+ import MockAdapter from 'axios-mock-adapter'
6
+
7
+ describe('CodeGuide - Repository Connection Integration', () => {
8
+ let mockAxios: MockAdapter
9
+ let codeGuide: CodeGuide
10
+ let config: APIServiceConfig
11
+
12
+ beforeEach(() => {
13
+ mockAxios = new MockAdapter(axios)
14
+ config = {
15
+ baseUrl: 'https://api.example.com',
16
+ databaseApiKey: 'sk_test123',
17
+ }
18
+ codeGuide = new CodeGuide(config, { verbose: false })
19
+ })
20
+
21
+ afterEach(() => {
22
+ mockAxios.restore()
23
+ })
24
+
25
+ describe('projects.connectRepository', () => {
26
+ const projectId = 'project-123'
27
+ const validRequest: ConnectRepositoryRequest = {
28
+ repo_url: 'https://github.com/user/my-app',
29
+ branch: 'main',
30
+ github_token: 'ghp_test123',
31
+ }
32
+
33
+ it('should connect repository successfully using projects service', async () => {
34
+ const mockResponse = {
35
+ status: 'success',
36
+ data: {
37
+ id: 'repo-connection-123',
38
+ project_id: projectId,
39
+ repo_url: validRequest.repo_url,
40
+ branch: validRequest.branch,
41
+ connection_status: 'connected' as const,
42
+ created_at: '2023-01-01T00:00:00Z',
43
+ updated_at: '2023-01-01T00:00:00Z',
44
+ },
45
+ }
46
+
47
+ mockAxios.onPost(`/projects/${projectId}/repository`, validRequest).reply(200, mockResponse)
48
+
49
+ const result = await codeGuide.projects.connectRepository(projectId, validRequest)
50
+
51
+ expect(result).toEqual(mockResponse)
52
+ })
53
+
54
+ it('should work with verbose mode enabled', async () => {
55
+ const verboseCodeGuide = new CodeGuide(config, { verbose: true })
56
+
57
+ const mockResponse = {
58
+ status: 'success',
59
+ data: {
60
+ id: 'repo-connection-123',
61
+ project_id: projectId,
62
+ repo_url: validRequest.repo_url,
63
+ branch: validRequest.branch,
64
+ connection_status: 'connected' as const,
65
+ created_at: '2023-01-01T00:00:00Z',
66
+ updated_at: '2023-01-01T00:00:00Z',
67
+ },
68
+ }
69
+
70
+ mockAxios.onPost(`/projects/${projectId}/repository`, validRequest).reply(200, mockResponse)
71
+
72
+ const result = await verboseCodeGuide.projects.connectRepository(projectId, validRequest)
73
+
74
+ expect(result).toEqual(mockResponse)
75
+ })
76
+
77
+ it('should handle validation errors through projects service', async () => {
78
+ const invalidRequest = {
79
+ repo_url: 'https://gitlab.com/user/repo', // Invalid URL
80
+ branch: 'main',
81
+ }
82
+
83
+ await expect(codeGuide.projects.connectRepository(projectId, invalidRequest))
84
+ .rejects.toThrow('Repository URL must be a valid GitHub URL')
85
+ })
86
+
87
+ it('should connect public repository without token', async () => {
88
+ const publicRepoRequest = {
89
+ repo_url: 'https://github.com/facebook/react',
90
+ branch: 'main',
91
+ }
92
+
93
+ const mockResponse = {
94
+ status: 'success',
95
+ data: {
96
+ id: 'repo-connection-456',
97
+ project_id: projectId,
98
+ repo_url: publicRepoRequest.repo_url,
99
+ branch: publicRepoRequest.branch,
100
+ connection_status: 'connected' as const,
101
+ created_at: '2023-01-01T00:00:00Z',
102
+ updated_at: '2023-01-01T00:00:00Z',
103
+ },
104
+ }
105
+
106
+ mockAxios.onPost(`/projects/${projectId}/repository`, publicRepoRequest).reply(200, mockResponse)
107
+
108
+ const result = await codeGuide.projects.connectRepository(projectId, publicRepoRequest)
109
+
110
+ expect(result).toEqual(mockResponse)
111
+ })
112
+ })
113
+ })
@@ -0,0 +1,191 @@
1
+ import { ProjectService } from '../../../services/projects/project-service'
2
+ import { ConnectRepositoryRequest } from '../../../services/projects/project-types'
3
+ import { APIServiceConfig } from '../../../types'
4
+ import axios from 'axios'
5
+ import MockAdapter from 'axios-mock-adapter'
6
+
7
+ describe('ProjectService - Repository Connection', () => {
8
+ let mockAxios: MockAdapter
9
+ let projectService: ProjectService
10
+ let mockConfig: APIServiceConfig
11
+
12
+ beforeEach(() => {
13
+ mockAxios = new MockAdapter(axios)
14
+ mockConfig = {
15
+ baseUrl: 'https://api.example.com',
16
+ databaseApiKey: 'sk_test123',
17
+ }
18
+ projectService = new ProjectService(mockConfig)
19
+ })
20
+
21
+ afterEach(() => {
22
+ mockAxios.restore()
23
+ })
24
+
25
+ describe('connectRepository', () => {
26
+ const projectId = 'project-123'
27
+ const validRequest: ConnectRepositoryRequest = {
28
+ repo_url: 'https://github.com/user/my-app',
29
+ branch: 'main',
30
+ github_token: 'ghp_test123',
31
+ }
32
+
33
+ it('should successfully connect a repository with valid data', async () => {
34
+ const mockResponse = {
35
+ status: 'success',
36
+ data: {
37
+ id: 'repo-connection-123',
38
+ project_id: projectId,
39
+ repo_url: validRequest.repo_url,
40
+ branch: validRequest.branch,
41
+ connection_status: 'connected' as const,
42
+ created_at: '2023-01-01T00:00:00Z',
43
+ updated_at: '2023-01-01T00:00:00Z',
44
+ },
45
+ }
46
+
47
+ mockAxios.onPost(`/projects/${projectId}/repository`, validRequest).reply(200, mockResponse)
48
+
49
+ const result = await projectService.connectRepository(projectId, validRequest)
50
+
51
+ expect(result).toEqual(mockResponse)
52
+ })
53
+
54
+ it('should throw error for missing repository URL', async () => {
55
+ const invalidRequest = {
56
+ repo_url: '',
57
+ branch: 'main',
58
+ }
59
+
60
+ await expect(projectService.connectRepository(projectId, invalidRequest))
61
+ .rejects.toThrow('Repository URL is required')
62
+ })
63
+
64
+ it('should throw error for missing branch name', async () => {
65
+ const invalidRequest = {
66
+ repo_url: 'https://github.com/user/my-app',
67
+ branch: '',
68
+ }
69
+
70
+ await expect(projectService.connectRepository(projectId, invalidRequest))
71
+ .rejects.toThrow('Branch name is required')
72
+ })
73
+
74
+ it('should throw error for invalid GitHub URL format', async () => {
75
+ const invalidRequest = {
76
+ repo_url: 'https://gitlab.com/user/my-app',
77
+ branch: 'main',
78
+ }
79
+
80
+ await expect(projectService.connectRepository(projectId, invalidRequest))
81
+ .rejects.toThrow('Repository URL must be a valid GitHub URL')
82
+ })
83
+
84
+ it('should throw error for invalid branch name characters', async () => {
85
+ const invalidRequest = {
86
+ repo_url: 'https://github.com/user/my-app',
87
+ branch: 'main@branch',
88
+ }
89
+
90
+ await expect(projectService.connectRepository(projectId, invalidRequest))
91
+ .rejects.toThrow('Branch name contains invalid characters')
92
+ })
93
+
94
+ it('should throw error for invalid GitHub token format', async () => {
95
+ const invalidRequest = {
96
+ repo_url: 'https://github.com/user/my-app',
97
+ branch: 'main',
98
+ github_token: 'invalid-token',
99
+ }
100
+
101
+ await expect(projectService.connectRepository(projectId, invalidRequest))
102
+ .rejects.toThrow('GitHub token must be a valid personal access token')
103
+ })
104
+
105
+ it('should accept valid GitHub token formats', async () => {
106
+ const validTokens = [
107
+ 'ghp_test123',
108
+ 'gho_test123',
109
+ 'ghu_test123',
110
+ 'ghs_test123',
111
+ 'ghr_test123',
112
+ ]
113
+
114
+ for (const token of validTokens) {
115
+ const request = {
116
+ repo_url: 'https://github.com/user/my-app',
117
+ branch: 'main',
118
+ github_token: token,
119
+ }
120
+
121
+ const mockResponse = {
122
+ status: 'success',
123
+ data: {
124
+ id: 'repo-connection-123',
125
+ project_id: projectId,
126
+ repo_url: request.repo_url,
127
+ branch: request.branch,
128
+ connection_status: 'connected' as const,
129
+ created_at: '2023-01-01T00:00:00Z',
130
+ updated_at: '2023-01-01T00:00:00Z',
131
+ },
132
+ }
133
+
134
+ mockAxios.onPost(`/projects/${projectId}/repository`, request).reply(200, mockResponse)
135
+
136
+ const result = await projectService.connectRepository(projectId, request)
137
+ expect(result).toEqual(mockResponse)
138
+ }
139
+ })
140
+
141
+ it('should work without GitHub token (for public repos)', async () => {
142
+ const requestWithoutToken = {
143
+ repo_url: 'https://github.com/user/my-app',
144
+ branch: 'main',
145
+ }
146
+
147
+ const mockResponse = {
148
+ status: 'success',
149
+ data: {
150
+ id: 'repo-connection-123',
151
+ project_id: projectId,
152
+ repo_url: requestWithoutToken.repo_url,
153
+ branch: requestWithoutToken.branch,
154
+ connection_status: 'connected' as const,
155
+ created_at: '2023-01-01T00:00:00Z',
156
+ updated_at: '2023-01-01T00:00:00Z',
157
+ },
158
+ }
159
+
160
+ mockAxios.onPost(`/projects/${projectId}/repository`, requestWithoutToken).reply(200, mockResponse)
161
+
162
+ const result = await projectService.connectRepository(projectId, requestWithoutToken)
163
+ expect(result).toEqual(mockResponse)
164
+ })
165
+
166
+ it('should accept GitHub URLs with trailing slash', async () => {
167
+ const requestWithTrailingSlash = {
168
+ repo_url: 'https://github.com/user/my-app/',
169
+ branch: 'main',
170
+ }
171
+
172
+ const mockResponse = {
173
+ status: 'success',
174
+ data: {
175
+ id: 'repo-connection-123',
176
+ project_id: projectId,
177
+ repo_url: 'https://github.com/user/my-app/',
178
+ branch: 'main',
179
+ connection_status: 'connected' as const,
180
+ created_at: '2023-01-01T00:00:00Z',
181
+ updated_at: '2023-01-01T00:00:00Z',
182
+ },
183
+ }
184
+
185
+ mockAxios.onPost(`/projects/${projectId}/repository`, requestWithTrailingSlash).reply(200, mockResponse)
186
+
187
+ const result = await projectService.connectRepository(projectId, requestWithTrailingSlash)
188
+ expect(result).toEqual(mockResponse)
189
+ })
190
+ })
191
+ })
package/codeguide.ts CHANGED
@@ -87,6 +87,7 @@ export class CodeGuide {
87
87
  return this.tasks.createTaskGroupWithCodespace(request, this.codespace)
88
88
  }
89
89
 
90
+
90
91
  setOptions(options: Partial<CodeGuideOptions>): void {
91
92
  this.options = { ...this.options, ...options }
92
93
  }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { CodeGuide } from './codeguide';
2
2
  export * from './services';
3
3
  export * from './types';
4
+ export type { ConnectRepositoryRequest, ConnectRepositoryResponse } from './services/projects/project-types';
@@ -1,5 +1,5 @@
1
1
  import { BaseService } from '../base/base-service';
2
- import { Project, CreateProjectRequest, UpdateProjectRequest, PaginatedProjectsRequest, PaginatedProjectsResponse, GetProjectDocumentsRequest, GetProjectDocumentsResponse } from './project-types';
2
+ import { Project, CreateProjectRequest, UpdateProjectRequest, PaginatedProjectsRequest, PaginatedProjectsResponse, GetProjectDocumentsRequest, GetProjectDocumentsResponse, ConnectRepositoryRequest, ConnectRepositoryResponse } from './project-types';
3
3
  export declare class ProjectService extends BaseService {
4
4
  getAllProjects(): Promise<Project[]>;
5
5
  getPaginatedProjects(params: PaginatedProjectsRequest): Promise<PaginatedProjectsResponse>;
@@ -11,4 +11,6 @@ export declare class ProjectService extends BaseService {
11
11
  message: string;
12
12
  }>;
13
13
  getProjectDocuments(projectId: string, params?: GetProjectDocumentsRequest): Promise<GetProjectDocumentsResponse>;
14
+ connectRepository(projectId: string, request: ConnectRepositoryRequest): Promise<ConnectRepositoryResponse>;
15
+ private validateConnectRepositoryRequest;
14
16
  }
@@ -49,5 +49,31 @@ class ProjectService extends base_service_1.BaseService {
49
49
  const url = `/projects/${projectId}/documents${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
50
50
  return this.get(url);
51
51
  }
52
+ async connectRepository(projectId, request) {
53
+ this.validateConnectRepositoryRequest(request);
54
+ const response = await this.post(`/projects/${projectId}/repository`, request);
55
+ return response;
56
+ }
57
+ validateConnectRepositoryRequest(request) {
58
+ if (!request.repo_url) {
59
+ throw new Error('Repository URL is required');
60
+ }
61
+ if (!request.branch) {
62
+ throw new Error('Branch name is required');
63
+ }
64
+ // Validate GitHub URL format
65
+ const githubUrlPattern = /^https:\/\/github\.com\/[^\/]+\/[^\/]+\/?$/;
66
+ if (!githubUrlPattern.test(request.repo_url)) {
67
+ throw new Error('Repository URL must be a valid GitHub URL (e.g., https://github.com/user/repo)');
68
+ }
69
+ // Validate branch name format (basic validation)
70
+ if (!/^[a-zA-Z0-9._-]+$/.test(request.branch)) {
71
+ throw new Error('Branch name contains invalid characters');
72
+ }
73
+ // Validate GitHub token format if provided
74
+ if (request.github_token && !request.github_token.startsWith('ghp_') && !request.github_token.startsWith('gho_') && !request.github_token.startsWith('ghu_') && !request.github_token.startsWith('ghs_') && !request.github_token.startsWith('ghr_')) {
75
+ throw new Error('GitHub token must be a valid personal access token');
76
+ }
77
+ }
52
78
  }
53
79
  exports.ProjectService = ProjectService;
@@ -95,3 +95,20 @@ export interface PaginatedProjectsResponse {
95
95
  total_pages: number;
96
96
  };
97
97
  }
98
+ export interface ConnectRepositoryRequest {
99
+ repo_url: string;
100
+ branch: string;
101
+ github_token?: string;
102
+ }
103
+ export interface ConnectRepositoryResponse {
104
+ status: string;
105
+ data: {
106
+ id: string;
107
+ project_id: string;
108
+ repo_url: string;
109
+ branch: string;
110
+ connection_status: 'pending' | 'connected' | 'failed';
111
+ created_at: string;
112
+ updated_at: string;
113
+ };
114
+ }
package/dist/types.d.ts CHANGED
@@ -71,13 +71,11 @@ export interface CreateApiKeyResponse {
71
71
  message?: string;
72
72
  }
73
73
  export interface ApiKeyPermissionResponse {
74
- status: string;
75
- data: {
76
- can_create: boolean;
77
- reason?: string;
78
- current_keys_count?: number;
79
- max_keys_allowed?: number;
80
- };
74
+ success: boolean;
75
+ can_create: boolean;
76
+ reason?: string;
77
+ current_keys_count?: number;
78
+ max_keys_allowed?: number;
81
79
  }
82
80
  export interface ApiKeyResponse {
83
81
  status: string;
package/index.ts CHANGED
@@ -10,3 +10,6 @@ dotenv.config({
10
10
  export { CodeGuide } from './codeguide'
11
11
  export * from './services'
12
12
  export * from './types'
13
+
14
+ // Export commonly used types for convenience
15
+ export type { ConnectRepositoryRequest, ConnectRepositoryResponse } from './services/projects/project-types'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeguide/core",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "Core package for code guidance with programmatic API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -84,4 +84,4 @@ export class ApiKeyEnhancedService extends BaseService {
84
84
  }> {
85
85
  return this.get(`/api-key-enhanced/${apiKeyId}/usage`)
86
86
  }
87
- }
87
+ }
@@ -9,6 +9,8 @@ import {
9
9
  PaginatedProjectsResponse,
10
10
  GetProjectDocumentsRequest,
11
11
  GetProjectDocumentsResponse,
12
+ ConnectRepositoryRequest,
13
+ ConnectRepositoryResponse,
12
14
  } from './project-types'
13
15
 
14
16
  export class ProjectService extends BaseService {
@@ -64,4 +66,39 @@ export class ProjectService extends BaseService {
64
66
  const url = `/projects/${projectId}/documents${queryParams.toString() ? `?${queryParams.toString()}` : ''}`
65
67
  return this.get<GetProjectDocumentsResponse>(url)
66
68
  }
69
+
70
+ async connectRepository(
71
+ projectId: string,
72
+ request: ConnectRepositoryRequest
73
+ ): Promise<ConnectRepositoryResponse> {
74
+ this.validateConnectRepositoryRequest(request)
75
+ const response = await this.post<ConnectRepositoryResponse>(`/projects/${projectId}/repository`, request)
76
+ return response
77
+ }
78
+
79
+ private validateConnectRepositoryRequest(request: ConnectRepositoryRequest): void {
80
+ if (!request.repo_url) {
81
+ throw new Error('Repository URL is required')
82
+ }
83
+
84
+ if (!request.branch) {
85
+ throw new Error('Branch name is required')
86
+ }
87
+
88
+ // Validate GitHub URL format
89
+ const githubUrlPattern = /^https:\/\/github\.com\/[^\/]+\/[^\/]+\/?$/
90
+ if (!githubUrlPattern.test(request.repo_url)) {
91
+ throw new Error('Repository URL must be a valid GitHub URL (e.g., https://github.com/user/repo)')
92
+ }
93
+
94
+ // Validate branch name format (basic validation)
95
+ if (!/^[a-zA-Z0-9._-]+$/.test(request.branch)) {
96
+ throw new Error('Branch name contains invalid characters')
97
+ }
98
+
99
+ // Validate GitHub token format if provided
100
+ if (request.github_token && !request.github_token.startsWith('ghp_') && !request.github_token.startsWith('gho_') && !request.github_token.startsWith('ghu_') && !request.github_token.startsWith('ghs_') && !request.github_token.startsWith('ghr_')) {
101
+ throw new Error('GitHub token must be a valid personal access token')
102
+ }
103
+ }
67
104
  }
@@ -106,3 +106,22 @@ export interface PaginatedProjectsResponse {
106
106
  total_pages: number
107
107
  }
108
108
  }
109
+
110
+ export interface ConnectRepositoryRequest {
111
+ repo_url: string
112
+ branch: string
113
+ github_token?: string
114
+ }
115
+
116
+ export interface ConnectRepositoryResponse {
117
+ status: string
118
+ data: {
119
+ id: string
120
+ project_id: string
121
+ repo_url: string
122
+ branch: string
123
+ connection_status: 'pending' | 'connected' | 'failed'
124
+ created_at: string
125
+ updated_at: string
126
+ }
127
+ }
package/types.ts CHANGED
@@ -87,13 +87,11 @@ export interface CreateApiKeyResponse {
87
87
  }
88
88
 
89
89
  export interface ApiKeyPermissionResponse {
90
- status: string
91
- data: {
92
- can_create: boolean
93
- reason?: string
94
- current_keys_count?: number
95
- max_keys_allowed?: number
96
- }
90
+ success: boolean
91
+ can_create: boolean
92
+ reason?: string
93
+ current_keys_count?: number
94
+ max_keys_allowed?: number
97
95
  }
98
96
 
99
97
  export interface ApiKeyResponse {
@@ -111,7 +109,14 @@ export interface RevokeApiKeyResponse {
111
109
  export interface Subscription {
112
110
  id: string
113
111
  user_id: string
114
- status: 'active' | 'canceled' | 'past_due' | 'unpaid' | 'trialing' | 'incomplete' | 'incomplete_expired'
112
+ status:
113
+ | 'active'
114
+ | 'canceled'
115
+ | 'past_due'
116
+ | 'unpaid'
117
+ | 'trialing'
118
+ | 'incomplete'
119
+ | 'incomplete_expired'
115
120
  metadata: Record<string, any>
116
121
  price_id: string
117
122
  quantity: number