mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
feat: implement pipeline feature for automated workflow steps
- Introduced a new pipeline service to manage custom workflow steps that execute after a feature is marked "In Progress". - Added API endpoints for configuring, saving, adding, updating, deleting, and reordering pipeline steps. - Enhanced the UI to support pipeline settings, including a dialog for managing steps and integration with the Kanban board. - Updated the application state management to handle pipeline configurations per project. - Implemented dynamic column generation in the Kanban board to display pipeline steps between "In Progress" and "Waiting Approval". - Added documentation for the new pipeline feature, including usage instructions and configuration details. This feature allows for a more structured workflow, enabling automated processes such as code reviews and testing after feature implementation.
This commit is contained in:
21
apps/server/src/routes/pipeline/common.ts
Normal file
21
apps/server/src/routes/pipeline/common.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Common utilities for pipeline routes
|
||||
*
|
||||
* Provides logger and error handling utilities shared across all pipeline endpoints.
|
||||
*/
|
||||
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js';
|
||||
|
||||
/** Logger instance for pipeline-related operations */
|
||||
export const logger = createLogger('Pipeline');
|
||||
|
||||
/**
|
||||
* Extract user-friendly error message from error objects
|
||||
*/
|
||||
export { getErrorMessageShared as getErrorMessage };
|
||||
|
||||
/**
|
||||
* Log error with automatic logger binding
|
||||
*/
|
||||
export const logError = createLogError(logger);
|
||||
77
apps/server/src/routes/pipeline/index.ts
Normal file
77
apps/server/src/routes/pipeline/index.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Pipeline routes - HTTP API for pipeline configuration management
|
||||
*
|
||||
* Provides endpoints for:
|
||||
* - Getting pipeline configuration
|
||||
* - Saving pipeline configuration
|
||||
* - Adding, updating, deleting, and reordering pipeline steps
|
||||
*
|
||||
* All endpoints use handler factories that receive the PipelineService instance.
|
||||
* Mounted at /api/pipeline in the main server.
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import type { PipelineService } from '../../services/pipeline-service.js';
|
||||
import { validatePathParams } from '../../middleware/validate-paths.js';
|
||||
import { createGetConfigHandler } from './routes/get-config.js';
|
||||
import { createSaveConfigHandler } from './routes/save-config.js';
|
||||
import { createAddStepHandler } from './routes/add-step.js';
|
||||
import { createUpdateStepHandler } from './routes/update-step.js';
|
||||
import { createDeleteStepHandler } from './routes/delete-step.js';
|
||||
import { createReorderStepsHandler } from './routes/reorder-steps.js';
|
||||
|
||||
/**
|
||||
* Create pipeline router with all endpoints
|
||||
*
|
||||
* Endpoints:
|
||||
* - POST /config - Get pipeline configuration
|
||||
* - POST /config/save - Save entire pipeline configuration
|
||||
* - POST /steps/add - Add a new pipeline step
|
||||
* - POST /steps/update - Update an existing pipeline step
|
||||
* - POST /steps/delete - Delete a pipeline step
|
||||
* - POST /steps/reorder - Reorder pipeline steps
|
||||
*
|
||||
* @param pipelineService - Instance of PipelineService for file I/O
|
||||
* @returns Express Router configured with all pipeline endpoints
|
||||
*/
|
||||
export function createPipelineRoutes(pipelineService: PipelineService): Router {
|
||||
const router = Router();
|
||||
|
||||
// Get pipeline configuration
|
||||
router.post(
|
||||
'/config',
|
||||
validatePathParams('projectPath'),
|
||||
createGetConfigHandler(pipelineService)
|
||||
);
|
||||
|
||||
// Save entire pipeline configuration
|
||||
router.post(
|
||||
'/config/save',
|
||||
validatePathParams('projectPath'),
|
||||
createSaveConfigHandler(pipelineService)
|
||||
);
|
||||
|
||||
// Pipeline step operations
|
||||
router.post(
|
||||
'/steps/add',
|
||||
validatePathParams('projectPath'),
|
||||
createAddStepHandler(pipelineService)
|
||||
);
|
||||
router.post(
|
||||
'/steps/update',
|
||||
validatePathParams('projectPath'),
|
||||
createUpdateStepHandler(pipelineService)
|
||||
);
|
||||
router.post(
|
||||
'/steps/delete',
|
||||
validatePathParams('projectPath'),
|
||||
createDeleteStepHandler(pipelineService)
|
||||
);
|
||||
router.post(
|
||||
'/steps/reorder',
|
||||
validatePathParams('projectPath'),
|
||||
createReorderStepsHandler(pipelineService)
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
54
apps/server/src/routes/pipeline/routes/add-step.ts
Normal file
54
apps/server/src/routes/pipeline/routes/add-step.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* POST /api/pipeline/steps/add - Add a new pipeline step
|
||||
*
|
||||
* Adds a new step to the pipeline configuration.
|
||||
*
|
||||
* Request body: { projectPath: string, step: { name, order, instructions, colorClass } }
|
||||
* Response: { success: true, step: PipelineStep }
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { PipelineService } from '../../../services/pipeline-service.js';
|
||||
import type { PipelineStep } from '@automaker/types';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createAddStepHandler(pipelineService: PipelineService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, step } = req.body as {
|
||||
projectPath: string;
|
||||
step: Omit<PipelineStep, 'id' | 'createdAt' | 'updatedAt'>;
|
||||
};
|
||||
|
||||
if (!projectPath) {
|
||||
res.status(400).json({ success: false, error: 'projectPath is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!step) {
|
||||
res.status(400).json({ success: false, error: 'step is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!step.name) {
|
||||
res.status(400).json({ success: false, error: 'step.name is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (step.instructions === undefined) {
|
||||
res.status(400).json({ success: false, error: 'step.instructions is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const newStep = await pipelineService.addStep(projectPath, step);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
step: newStep,
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error, 'Add pipeline step failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
}
|
||||
42
apps/server/src/routes/pipeline/routes/delete-step.ts
Normal file
42
apps/server/src/routes/pipeline/routes/delete-step.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* POST /api/pipeline/steps/delete - Delete a pipeline step
|
||||
*
|
||||
* Removes a step from the pipeline configuration.
|
||||
*
|
||||
* Request body: { projectPath: string, stepId: string }
|
||||
* Response: { success: true }
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { PipelineService } from '../../../services/pipeline-service.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createDeleteStepHandler(pipelineService: PipelineService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, stepId } = req.body as {
|
||||
projectPath: string;
|
||||
stepId: string;
|
||||
};
|
||||
|
||||
if (!projectPath) {
|
||||
res.status(400).json({ success: false, error: 'projectPath is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stepId) {
|
||||
res.status(400).json({ success: false, error: 'stepId is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
await pipelineService.deleteStep(projectPath, stepId);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error, 'Delete pipeline step failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
}
|
||||
35
apps/server/src/routes/pipeline/routes/get-config.ts
Normal file
35
apps/server/src/routes/pipeline/routes/get-config.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* POST /api/pipeline/config - Get pipeline configuration
|
||||
*
|
||||
* Returns the pipeline configuration for a project.
|
||||
*
|
||||
* Request body: { projectPath: string }
|
||||
* Response: { success: true, config: PipelineConfig }
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { PipelineService } from '../../../services/pipeline-service.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createGetConfigHandler(pipelineService: PipelineService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath } = req.body;
|
||||
|
||||
if (!projectPath) {
|
||||
res.status(400).json({ success: false, error: 'projectPath is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const config = await pipelineService.getPipelineConfig(projectPath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
config,
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error, 'Get pipeline config failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
}
|
||||
42
apps/server/src/routes/pipeline/routes/reorder-steps.ts
Normal file
42
apps/server/src/routes/pipeline/routes/reorder-steps.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* POST /api/pipeline/steps/reorder - Reorder pipeline steps
|
||||
*
|
||||
* Reorders the steps in the pipeline configuration.
|
||||
*
|
||||
* Request body: { projectPath: string, stepIds: string[] }
|
||||
* Response: { success: true }
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { PipelineService } from '../../../services/pipeline-service.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createReorderStepsHandler(pipelineService: PipelineService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, stepIds } = req.body as {
|
||||
projectPath: string;
|
||||
stepIds: string[];
|
||||
};
|
||||
|
||||
if (!projectPath) {
|
||||
res.status(400).json({ success: false, error: 'projectPath is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stepIds || !Array.isArray(stepIds)) {
|
||||
res.status(400).json({ success: false, error: 'stepIds array is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
await pipelineService.reorderSteps(projectPath, stepIds);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error, 'Reorder pipeline steps failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
}
|
||||
43
apps/server/src/routes/pipeline/routes/save-config.ts
Normal file
43
apps/server/src/routes/pipeline/routes/save-config.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* POST /api/pipeline/config/save - Save entire pipeline configuration
|
||||
*
|
||||
* Saves the complete pipeline configuration for a project.
|
||||
*
|
||||
* Request body: { projectPath: string, config: PipelineConfig }
|
||||
* Response: { success: true }
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { PipelineService } from '../../../services/pipeline-service.js';
|
||||
import type { PipelineConfig } from '@automaker/types';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createSaveConfigHandler(pipelineService: PipelineService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, config } = req.body as {
|
||||
projectPath: string;
|
||||
config: PipelineConfig;
|
||||
};
|
||||
|
||||
if (!projectPath) {
|
||||
res.status(400).json({ success: false, error: 'projectPath is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
res.status(400).json({ success: false, error: 'config is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
await pipelineService.savePipelineConfig(projectPath, config);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error, 'Save pipeline config failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
}
|
||||
50
apps/server/src/routes/pipeline/routes/update-step.ts
Normal file
50
apps/server/src/routes/pipeline/routes/update-step.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* POST /api/pipeline/steps/update - Update an existing pipeline step
|
||||
*
|
||||
* Updates a step in the pipeline configuration.
|
||||
*
|
||||
* Request body: { projectPath: string, stepId: string, updates: Partial<PipelineStep> }
|
||||
* Response: { success: true, step: PipelineStep }
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { PipelineService } from '../../../services/pipeline-service.js';
|
||||
import type { PipelineStep } from '@automaker/types';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
export function createUpdateStepHandler(pipelineService: PipelineService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, stepId, updates } = req.body as {
|
||||
projectPath: string;
|
||||
stepId: string;
|
||||
updates: Partial<Omit<PipelineStep, 'id' | 'createdAt'>>;
|
||||
};
|
||||
|
||||
if (!projectPath) {
|
||||
res.status(400).json({ success: false, error: 'projectPath is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stepId) {
|
||||
res.status(400).json({ success: false, error: 'stepId is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!updates || Object.keys(updates).length === 0) {
|
||||
res.status(400).json({ success: false, error: 'updates is required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedStep = await pipelineService.updateStep(projectPath, stepId, updates);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
step: updatedStep,
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error, 'Update pipeline step failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user