-
Notifications
You must be signed in to change notification settings - Fork 9
refactor jenkins client #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,231 @@ | ||
# Jenkins Client - Refactored Architecture | ||
|
||
This directory contains the refactored Jenkins client implementation following TypeScript best practices and design patterns. | ||
|
||
## Architecture Overview | ||
|
||
The Jenkins client has been refactored using a service-oriented architecture with the following benefits: | ||
|
||
- **Single Responsibility Principle**: Each class has one clear purpose | ||
- **Type Safety**: Comprehensive TypeScript types throughout | ||
- **Testability**: Smaller, focused classes are easier to unit test | ||
- **Maintainability**: Changes to specific functionality affect fewer files | ||
- **Reusability**: Utility classes can be reused across the codebase | ||
- **Error Handling**: Consistent, typed error handling | ||
- **Configuration**: Centralized configuration management | ||
- **Extensibility**: Easy to add new credential types or job configurations | ||
|
||
## Directory Structure | ||
|
||
``` | ||
jenkins/ | ||
├── enums/ # Enums for Jenkins constants | ||
│ └── jenkins.enums.ts | ||
├── types/ # TypeScript type definitions | ||
│ └── jenkins.types.ts | ||
├── config/ # Configuration constants | ||
│ └── jenkins.config.ts | ||
├── errors/ # Custom error classes | ||
│ └── jenkins.errors.ts | ||
├── utils/ # Utility classes | ||
│ └── jenkins.utils.ts | ||
├── strategies/ # Strategy pattern implementations | ||
│ └── credential.strategy.ts | ||
├── http/ # HTTP client abstraction | ||
│ └── jenkins-http.client.ts | ||
├── services/ # Business logic services | ||
│ ├── jenkins-job.service.ts | ||
│ ├── jenkins-build.service.ts | ||
│ └── jenkins-credential.service.ts | ||
├── jenkins.client.ts # Main client facade | ||
├── index.ts # Module exports | ||
└── README.md # This file | ||
``` | ||
|
||
## Usage Examples | ||
|
||
### Basic Usage (Backwards Compatible) | ||
|
||
```typescript | ||
import { JenkinsClient } from './jenkins'; | ||
|
||
const client = new JenkinsClient({ | ||
baseUrl: 'https://jenkins.example.com', | ||
username: 'your-username', | ||
token: 'your-api-token' | ||
}); | ||
|
||
// Create a job (legacy method signature) | ||
await client.createJob( | ||
'my-job', | ||
'https://github.com/user/repo.git', | ||
'my-folder', | ||
'main', | ||
'Jenkinsfile', | ||
'git-credentials' | ||
); | ||
|
||
// Trigger a build | ||
await client.build('my-job', 'my-folder', { PARAM1: 'value1' }); | ||
|
||
// Get build information | ||
const build = await client.getBuild('my-job', 123, 'my-folder'); | ||
``` | ||
|
||
### New Options-Based Usage | ||
|
||
```typescript | ||
import { JenkinsClient, CreateJobOptions, BuildOptions } from './jenkins'; | ||
|
||
const client = new JenkinsClient({ | ||
baseUrl: 'https://jenkins.example.com', | ||
username: 'your-username', | ||
token: 'your-api-token' | ||
}); | ||
|
||
// Create a job (new options signature) | ||
const jobOptions: CreateJobOptions = { | ||
jobName: 'my-job', | ||
repoUrl: 'https://github.com/user/repo.git', | ||
folderName: 'my-folder', | ||
branch: 'main', | ||
jenkinsfilePath: 'Jenkinsfile', | ||
credentialId: 'git-credentials' | ||
}; | ||
await client.createJob(jobOptions); | ||
|
||
// Trigger a build with options | ||
const buildOptions: BuildOptions = { | ||
jobName: 'my-job', | ||
folderName: 'my-folder', | ||
parameters: { PARAM1: 'value1' } | ||
}; | ||
await client.build(buildOptions); | ||
``` | ||
|
||
### Direct Service Access | ||
|
||
```typescript | ||
import { JenkinsClient } from './jenkins'; | ||
|
||
const client = new JenkinsClient(config); | ||
|
||
// Access individual services for advanced operations | ||
const jobs = client.jobs; | ||
const builds = client.builds; | ||
const credentials = client.credentials; | ||
|
||
// Use services directly | ||
const runningBuilds = await builds.getRunningBuilds('my-job', 'my-folder'); | ||
const jobExists = await jobs.jobExists('my-job', 'my-folder'); | ||
await credentials.createSecretTextCredential('my-folder', 'my-secret', 'secret-value'); | ||
``` | ||
|
||
### Error Handling | ||
|
||
```typescript | ||
import { | ||
JenkinsClient, | ||
JenkinsJobNotFoundError, | ||
JenkinsBuildTimeoutError, | ||
JenkinsAuthenticationError | ||
} from './jenkins'; | ||
|
||
try { | ||
const build = await client.getBuild('non-existent-job', 123); | ||
} catch (error) { | ||
if (error instanceof JenkinsJobNotFoundError) { | ||
console.log('Job not found:', error.message); | ||
} else if (error instanceof JenkinsAuthenticationError) { | ||
console.log('Authentication failed:', error.message); | ||
} else { | ||
console.log('Unexpected error:', error); | ||
} | ||
} | ||
``` | ||
|
||
## Design Patterns Used | ||
|
||
### 1. Facade Pattern | ||
- `JenkinsClient` acts as a facade providing a simple interface to the complex subsystem | ||
|
||
### 2. Strategy Pattern | ||
- `CredentialStrategy` and implementations for different credential types | ||
- Easy to add new credential types without modifying existing code | ||
|
||
### 3. Service Layer Pattern | ||
- Business logic separated into focused service classes | ||
- Each service handles one domain (jobs, builds, credentials) | ||
|
||
### 4. Builder Pattern | ||
- `JenkinsXmlBuilder` for constructing XML configurations | ||
- `JenkinsPathBuilder` for constructing API paths | ||
|
||
### 5. Factory Pattern | ||
- `CredentialStrategyFactory` for creating credential strategies | ||
|
||
### 6. Error Handling Pattern | ||
- Custom error hierarchy with specific error types | ||
- Consistent error handling across all services | ||
|
||
## Configuration | ||
|
||
All configuration constants are centralized in `JenkinsConfig`: | ||
|
||
```typescript | ||
import { JenkinsConfig } from './jenkins'; | ||
|
||
// Access default values | ||
const timeout = JenkinsConfig.DEFAULT_TIMEOUT_MS; | ||
const headers = JenkinsConfig.HEADERS.JSON; | ||
const endpoint = JenkinsConfig.ENDPOINTS.API_JSON; | ||
``` | ||
|
||
## Extending the Client | ||
|
||
### Adding New Credential Types | ||
|
||
1. Add the new type to `CredentialType` enum | ||
2. Create a new strategy class implementing `CredentialStrategy` | ||
3. Register it in `CredentialStrategyFactory` | ||
|
||
### Adding New Services | ||
|
||
1. Create a new service class in `services/` | ||
2. Add it to the main `JenkinsClient` constructor | ||
3. Expose it through the facade if needed | ||
|
||
### Adding New Error Types | ||
|
||
1. Create new error classes extending `JenkinsError` | ||
2. Export them from `errors/jenkins.errors.ts` | ||
3. Use them in appropriate services | ||
|
||
## Testing | ||
|
||
The refactored architecture makes testing much easier: | ||
|
||
```typescript | ||
// Mock individual services | ||
const mockJobService = { | ||
createJob: jest.fn(), | ||
getJob: jest.fn(), | ||
}; | ||
|
||
// Test services in isolation | ||
const jobService = new JenkinsJobService(mockHttpClient); | ||
``` | ||
|
||
## Performance Considerations | ||
|
||
- Services are lightweight and share the same HTTP client instance | ||
- Path building and XML generation are optimized | ||
- Error handling is consistent and efficient | ||
- Configuration is loaded once and reused | ||
|
||
## Security | ||
|
||
- Credentials are handled through the strategy pattern | ||
- Sensitive data is not logged | ||
- XML escaping prevents injection attacks | ||
- Type-safe parameter handling |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,44 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Jenkins configuration constants and default values | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export class JenkinsConfig { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public static readonly DEFAULT_BRANCH = 'main'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public static readonly DEFAULT_JENKINSFILE_PATH = 'Jenkinsfile'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public static readonly DEFAULT_CREDENTIAL_ID = 'GITOPS_AUTH_PASSWORD'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public static readonly DEFAULT_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public static readonly DEFAULT_POLL_INTERVAL_MS = 5000; // 5 seconds | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public static readonly DEFAULT_MAX_BUILDS_TO_CHECK = 50; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* HTTP headers for different content types | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public static readonly HEADERS = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
XML: { 'Content-Type': 'application/xml' }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
JSON: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
PLAIN: { 'Accept': 'text/plain' }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} as const; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Jenkins plugin information | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public static readonly PLUGINS = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
WORKFLOW_JOB: 'workflow-job@2.40', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GITHUB: 'github@1.37.1', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
WORKFLOW_CPS: 'workflow-cps@2.89', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GIT: 'git@4.4.5', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
PLAIN_CREDENTIALS: 'plain-credentials', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} as const; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* Jenkins API endpoints | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public static readonly ENDPOINTS = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CREATE_ITEM: 'createItem', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
API_JSON: 'api/json', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BUILD: 'build', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BUILD_WITH_PARAMETERS: 'buildWithParameters', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
LOG_TEXT: 'logText/progressiveText', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CREDENTIALS_STORE_SYSTEM: 'credentials/store/system/domain/_/createCredentials', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CREDENTIALS_STORE_FOLDER: 'credentials/store/folder/domain/_/createCredentials', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} as const; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+4
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Refactor to avoid static-only class anti-pattern. Using a class with only static members is an anti-pattern. Consider using a namespace, const object, or individual exports instead. -export class JenkinsConfig {
- public static readonly DEFAULT_BRANCH = 'main';
- public static readonly DEFAULT_JENKINSFILE_PATH = 'Jenkinsfile';
- // ... other static members
-}
+export const JenkinsConfig = {
+ DEFAULT_BRANCH: 'main',
+ DEFAULT_JENKINSFILE_PATH: 'Jenkinsfile',
+ DEFAULT_CREDENTIAL_ID: 'GITOPS_AUTH_PASSWORD',
+ DEFAULT_TIMEOUT_MS: 10 * 60 * 1000, // 10 minutes
+ DEFAULT_POLL_INTERVAL_MS: 5000, // 5 seconds
+ DEFAULT_MAX_BUILDS_TO_CHECK: 50,
+
+ HEADERS: {
+ XML: { 'Content-Type': 'application/xml' },
+ JSON: { 'Content-Type': 'application/json', Accept: 'application/json' },
+ PLAIN: { Accept: 'text/plain' },
+ } as const,
+
+ PLUGINS: {
+ WORKFLOW_JOB: 'workflow-job@2.40',
+ GITHUB: 'github@1.37.1',
+ WORKFLOW_CPS: 'workflow-cps@2.89',
+ GIT: 'git@4.4.5',
+ PLAIN_CREDENTIALS: 'plain-credentials',
+ } as const,
+
+ ENDPOINTS: {
+ CREATE_ITEM: 'createItem',
+ API_JSON: 'api/json',
+ BUILD: 'build',
+ BUILD_WITH_PARAMETERS: 'buildWithParameters',
+ LOG_TEXT: 'logText/progressiveText',
+ CREDENTIALS_STORE_SYSTEM: 'credentials/store/system/domain/_/createCredentials',
+ CREDENTIALS_STORE_FOLDER: 'credentials/store/folder/domain/_/createCredentials',
+ } as const,
+} as const; This also fixes the prettier formatting issues with quotes and spacing. 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (1.9.4)[error] 4-44: Avoid classes that contain only static members. Prefer using simple functions instead of classes with only static members. (lint/complexity/noStaticOnlyClass) 🪛 ESLint[error] 11-11: Delete (prettier/prettier) [error] 17-17: Replace (prettier/prettier) [error] 18-18: Replace (prettier/prettier) [error] 44-44: Replace (prettier/prettier) 🤖 Prompt for AI Agents
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/** | ||
* Jenkins build result status enum | ||
*/ | ||
export enum JenkinsBuildResult { | ||
SUCCESS = 'SUCCESS', | ||
FAILURE = 'FAILURE', | ||
UNSTABLE = 'UNSTABLE', | ||
ABORTED = 'ABORTED', | ||
NOT_BUILT = 'NOT_BUILT', | ||
} | ||
|
||
/** | ||
* Jenkins build trigger type enum | ||
*/ | ||
export enum JenkinsBuildTrigger { | ||
UNKNOWN = 'UNKNOWN', | ||
PULL_REQUEST = 'PULL_REQUEST', | ||
PUSH = 'PUSH', | ||
MANUAL = 'MANUAL', | ||
SCHEDULED = 'SCHEDULED', | ||
API = 'API', | ||
} | ||
|
||
/** | ||
* Jenkins credential types | ||
*/ | ||
export enum CredentialType { | ||
SECRET_TEXT = 'Secret text', | ||
USERNAME_PASSWORD = 'Username with password', | ||
SSH_USERNAME_PRIVATE_KEY = 'SSH Username with private key',//TODO: need to confirm if this is correct | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Address the TODO and improve error handling.
The method implementation needs verification and better error handling:
Consider this refactor for better error handling:
🏁 Script executed:
Length of output: 1022
Refine
getPipelineLogs
implementation and error handlingVerified that GitLab’s API for raw job logs is indeed
GET /projects/:id/jobs/:job_id/trace
and returns the full log as text. Please remove the lingering TODO and improve error propagation so consumers can handle failures more granularly.
• File: src/api/ci/gitlabciClient.ts (lines 149–166)
//TODO
comment now that the endpoint has been confirmed.Accept: text/plain
in the request if your client doesn’t default to it.Suggested diff:
📝 Committable suggestion
🤖 Prompt for AI Agents