mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 20:03:37 +00:00
Merge pull request #658 from AutoMaker-Org/feature/v0.14.0rc-1769075904343-i0uw
feat: abillity to configure "Start Dev Server" command in project settings
This commit is contained in:
@@ -134,7 +134,7 @@ export function createWorktreeRoutes(
|
||||
router.post(
|
||||
'/start-dev',
|
||||
validatePathParams('projectPath', 'worktreePath'),
|
||||
createStartDevHandler()
|
||||
createStartDevHandler(settingsService)
|
||||
);
|
||||
router.post('/stop-dev', createStopDevHandler());
|
||||
router.post('/list-dev-servers', createListDevServersHandler());
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
/**
|
||||
* POST /start-dev endpoint - Start a dev server for a worktree
|
||||
*
|
||||
* Spins up a development server (npm run dev) in the worktree directory
|
||||
* on a unique port, allowing preview of the worktree's changes without
|
||||
* affecting the main dev server.
|
||||
* Spins up a development server in the worktree directory on a unique port,
|
||||
* allowing preview of the worktree's changes without affecting the main dev server.
|
||||
*
|
||||
* If a custom devCommand is configured in project settings, it will be used.
|
||||
* Otherwise, auto-detection based on package manager (npm/yarn/pnpm/bun run dev) is used.
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { SettingsService } from '../../../services/settings-service.js';
|
||||
import { getDevServerService } from '../../../services/dev-server-service.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
|
||||
export function createStartDevHandler() {
|
||||
const logger = createLogger('start-dev');
|
||||
|
||||
export function createStartDevHandler(settingsService?: SettingsService) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, worktreePath } = req.body as {
|
||||
@@ -34,8 +40,25 @@ export function createStartDevHandler() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get custom dev command from project settings (if configured)
|
||||
let customCommand: string | undefined;
|
||||
if (settingsService) {
|
||||
const projectSettings = await settingsService.getProjectSettings(projectPath);
|
||||
const devCommand = projectSettings?.devCommand?.trim();
|
||||
if (devCommand) {
|
||||
customCommand = devCommand;
|
||||
logger.debug(`Using custom dev command from project settings: ${customCommand}`);
|
||||
} else {
|
||||
logger.debug('No custom dev command configured, using auto-detection');
|
||||
}
|
||||
}
|
||||
|
||||
const devServerService = getDevServerService();
|
||||
const result = await devServerService.startDevServer(projectPath, worktreePath);
|
||||
const result = await devServerService.startDevServer(
|
||||
projectPath,
|
||||
worktreePath,
|
||||
customCommand
|
||||
);
|
||||
|
||||
if (result.success && result.result) {
|
||||
res.json({
|
||||
|
||||
@@ -273,12 +273,56 @@ class DevServerService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a custom command string into cmd and args
|
||||
* Handles quoted strings with spaces (e.g., "my command" arg1 arg2)
|
||||
*/
|
||||
private parseCustomCommand(command: string): { cmd: string; args: string[] } {
|
||||
const tokens: string[] = [];
|
||||
let current = '';
|
||||
let inQuote = false;
|
||||
let quoteChar = '';
|
||||
|
||||
for (let i = 0; i < command.length; i++) {
|
||||
const char = command[i];
|
||||
|
||||
if (inQuote) {
|
||||
if (char === quoteChar) {
|
||||
inQuote = false;
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
} else if (char === '"' || char === "'") {
|
||||
inQuote = true;
|
||||
quoteChar = char;
|
||||
} else if (char === ' ') {
|
||||
if (current) {
|
||||
tokens.push(current);
|
||||
current = '';
|
||||
}
|
||||
} else {
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
|
||||
if (current) {
|
||||
tokens.push(current);
|
||||
}
|
||||
|
||||
const [cmd, ...args] = tokens;
|
||||
return { cmd: cmd || '', args };
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a dev server for a worktree
|
||||
* @param projectPath - The project root path
|
||||
* @param worktreePath - The worktree directory path
|
||||
* @param customCommand - Optional custom command to run instead of auto-detected dev command
|
||||
*/
|
||||
async startDevServer(
|
||||
projectPath: string,
|
||||
worktreePath: string
|
||||
worktreePath: string,
|
||||
customCommand?: string
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
result?: {
|
||||
@@ -311,22 +355,41 @@ class DevServerService {
|
||||
};
|
||||
}
|
||||
|
||||
// Check for package.json
|
||||
const packageJsonPath = path.join(worktreePath, 'package.json');
|
||||
if (!(await this.fileExists(packageJsonPath))) {
|
||||
return {
|
||||
success: false,
|
||||
error: `No package.json found in: ${worktreePath}`,
|
||||
};
|
||||
}
|
||||
// Determine the dev command to use
|
||||
let devCommand: { cmd: string; args: string[] };
|
||||
|
||||
// Get dev command
|
||||
const devCommand = await this.getDevCommand(worktreePath);
|
||||
if (!devCommand) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Could not determine dev command for: ${worktreePath}`,
|
||||
};
|
||||
// Normalize custom command: trim whitespace and treat empty strings as undefined
|
||||
const normalizedCustomCommand = customCommand?.trim();
|
||||
|
||||
if (normalizedCustomCommand) {
|
||||
// Use the provided custom command
|
||||
devCommand = this.parseCustomCommand(normalizedCustomCommand);
|
||||
if (!devCommand.cmd) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid custom command: command cannot be empty',
|
||||
};
|
||||
}
|
||||
logger.debug(`Using custom command: ${normalizedCustomCommand}`);
|
||||
} else {
|
||||
// Check for package.json when auto-detecting
|
||||
const packageJsonPath = path.join(worktreePath, 'package.json');
|
||||
if (!(await this.fileExists(packageJsonPath))) {
|
||||
return {
|
||||
success: false,
|
||||
error: `No package.json found in: ${worktreePath}`,
|
||||
};
|
||||
}
|
||||
|
||||
// Get dev command from package manager detection
|
||||
const detectedCommand = await this.getDevCommand(worktreePath);
|
||||
if (!detectedCommand) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Could not determine dev command for: ${worktreePath}`,
|
||||
};
|
||||
}
|
||||
devCommand = detectedCommand;
|
||||
}
|
||||
|
||||
// Find available port
|
||||
|
||||
@@ -852,6 +852,20 @@ export class SettingsService {
|
||||
delete updated.defaultFeatureModel;
|
||||
}
|
||||
|
||||
// Handle devCommand special cases:
|
||||
// - null means delete the key (use auto-detection)
|
||||
// - string means custom command
|
||||
if ('devCommand' in updates && updates.devCommand === null) {
|
||||
delete updated.devCommand;
|
||||
}
|
||||
|
||||
// Handle testCommand special cases:
|
||||
// - null means delete the key (use auto-detection)
|
||||
// - string means custom command
|
||||
if ('testCommand' in updates && updates.testCommand === null) {
|
||||
delete updated.testCommand;
|
||||
}
|
||||
|
||||
await writeSettingsJson(settingsPath, updated);
|
||||
logger.info(`Project settings updated for ${projectPath}`);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user