From aec09739a52bed17bd83d6bfa5bbae3e53c4fc28 Mon Sep 17 00:00:00 2001 From: minseo Date: Sat, 30 Aug 2025 12:43:12 +0900 Subject: [PATCH 1/5] fix: Update getConfigMetadata to retrieve metadata from table instead of bucket --- src/modules/deploy/services/import.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/deploy/services/import.service.ts b/src/modules/deploy/services/import.service.ts index c87c628..b031116 100644 --- a/src/modules/deploy/services/import.service.ts +++ b/src/modules/deploy/services/import.service.ts @@ -184,6 +184,7 @@ export class ImportService { async getConfigMetadata(configId: string): Promise { try { + // FIXME: metadata -> bucket이 아니라 table에서 가져오도록 수정 const client = this.supabaseService.getClient(); const { data, error } = await client.storage .from('taptik-configs') From 0af6654cefc532a848cf55ccc4bc4f52103dbdd3 Mon Sep 17 00:00:00 2001 From: minseo Date: Sat, 30 Aug 2025 13:01:48 +0900 Subject: [PATCH 2/5] refactor: Reorganize imports and enhance help documentation - Rearranged import statements in deploy.command.ts for clarity. - Updated HelpDocumentationService to improve relevance calculation using template literals. - Added FIXME comments in ImportService and HelpDocumentationService for future review on component lists and bucket name verification. - Ensured consistent formatting and readability across the codebase. --- src/modules/deploy/commands/deploy.command.ts | 7 ++++--- src/modules/deploy/services/help-documentation.service.ts | 7 ++++--- src/modules/deploy/services/import.service.ts | 2 ++ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/modules/deploy/commands/deploy.command.ts b/src/modules/deploy/commands/deploy.command.ts index bc5ca70..0aee1ec 100644 --- a/src/modules/deploy/commands/deploy.command.ts +++ b/src/modules/deploy/commands/deploy.command.ts @@ -8,11 +8,11 @@ import { ConflictStrategy, } from '../interfaces/deploy-options.interface'; import { DeploymentResult } from '../interfaces/deployment-result.interface'; +import { DeploymentReporterService } from '../services/deployment-reporter.service'; import { DeploymentService } from '../services/deployment.service'; -import { ImportService } from '../services/import.service'; -import { HelpDocumentationService } from '../services/help-documentation.service'; import { ErrorMessageHelperService } from '../services/error-message-helper.service'; -import { DeploymentReporterService } from '../services/deployment-reporter.service'; +import { HelpDocumentationService } from '../services/help-documentation.service'; +import { ImportService } from '../services/import.service'; interface DeployCommandOptions { platform?: SupportedPlatform; @@ -188,6 +188,7 @@ export class DeployCommand extends CommandRunner { components: options.components?.map((c) => c as ComponentType), skipComponents: options.skipComponents?.map((c) => c as ComponentType), // Task 7.2: Add Cursor-specific options to deployOptions + // FIXME: cursor specific options 점검 cursorPath: options.cursorPath, workspacePath: options.workspacePath, skipAiConfig: options.skipAiConfig, diff --git a/src/modules/deploy/services/help-documentation.service.ts b/src/modules/deploy/services/help-documentation.service.ts index 13ec002..d3d3d02 100644 --- a/src/modules/deploy/services/help-documentation.service.ts +++ b/src/modules/deploy/services/help-documentation.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { SupportedPlatform } from '../interfaces/deploy-options.interface'; import { ComponentType } from '../interfaces/component-types.interface'; import { CursorComponentType } from '../interfaces/cursor-deployment.interface'; +import { SupportedPlatform } from '../interfaces/deploy-options.interface'; export interface HelpContent { title: string; @@ -317,7 +317,7 @@ export class HelpDocumentationService { // Search component help for (const [key, help] of this.componentHelp.entries()) { - const relevance = this.calculateRelevance(lowerQuery, help.name + ' ' + help.description); + const relevance = this.calculateRelevance(lowerQuery, `${help.name } ${ help.description}`); if (relevance > 0.1) { results.push({ type: 'component', @@ -330,7 +330,7 @@ export class HelpDocumentationService { // Search error documentation for (const [code, error] of this.errorDocs.entries()) { - const relevance = this.calculateRelevance(lowerQuery, error.title + ' ' + error.description); + const relevance = this.calculateRelevance(lowerQuery, `${error.title } ${ error.description}`); if (relevance > 0.1) { results.push({ type: 'error', @@ -811,6 +811,7 @@ export class HelpDocumentationService { }); } + // FIXME: 컴포넌트 목록 점검, cli 실행시 컴포넌트 목록 help 확인 private getValidComponentsForPlatform(platform: SupportedPlatform): string[] { switch (platform) { case 'cursor-ide': diff --git a/src/modules/deploy/services/import.service.ts b/src/modules/deploy/services/import.service.ts index b031116..05d8a2c 100644 --- a/src/modules/deploy/services/import.service.ts +++ b/src/modules/deploy/services/import.service.ts @@ -210,6 +210,7 @@ export class ImportService { attempt: number, ): Promise { try { + // FIXME: bucket name 확인(push와 싱크) const client = this.supabaseService.getClient(); const { data, error } = await client.storage .from('taptik-configs') @@ -240,6 +241,7 @@ export class ImportService { private async parseConfiguration(data: Buffer): Promise { try { const jsonString = data.toString('utf8'); + // FIXME: table 설계 확인 return JSON.parse(jsonString) as TaptikContext; } catch (error) { throw new Error( From 592fe5ce84865efe799f06aa84d4f45669569e74 Mon Sep 17 00:00:00 2001 From: minseo Date: Sat, 6 Sep 2025 16:10:50 +0900 Subject: [PATCH 3/5] refactor: Remove IDE context from Taptik context interface and clean up related validation logic - Removed the IdeContext interface and its references from the Taptik context interface to streamline the structure. - Updated PlatformValidatorService to eliminate IDE-specific validation, focusing solely on tools context. - Added FIXME comments in DeploymentService for future context transformation improvements. - Adjusted import statements for clarity and consistency across deployment services. --- .../interfaces/taptik-context.interface.ts | 35 ------------------- src/modules/deploy/services/backup.service.ts | 1 + .../deploy/services/deployment.service.ts | 13 ++++--- .../services/platform-validator.service.ts | 28 +++------------ 4 files changed, 13 insertions(+), 64 deletions(-) diff --git a/src/modules/context/interfaces/taptik-context.interface.ts b/src/modules/context/interfaces/taptik-context.interface.ts index d0e0ca4..01a5c3c 100644 --- a/src/modules/context/interfaces/taptik-context.interface.ts +++ b/src/modules/context/interfaces/taptik-context.interface.ts @@ -43,8 +43,6 @@ export interface ContextContent { prompts?: PromptsContext; /** MCP servers and custom tool configurations */ tools?: ToolsContext; - /** IDE-specific settings for each platform */ - ide?: IdeContext; /** Allow additional fields for flexibility */ [key: string]: unknown; } @@ -198,39 +196,6 @@ export interface ToolsContext { }>; } -export interface IdeContext { - /** Claude Code specific settings */ - claudeCode?: { - settings?: Record; - keybindings?: Record; - extensions?: string[]; - mcp_config?: Record; - claude_md?: string; - }; - 'claude-code'?: { - settings?: Record; - keybindings?: Record; - extensions?: string[]; - mcp_config?: Record; - claude_md?: string; - }; - /** Kiro IDE specific settings */ - 'kiro-ide'?: { - settings?: Record; - specs?: Record; - steering?: Record; - hooks?: Record; - }; - /** Cursor IDE specific settings */ - 'cursor-ide'?: { - settings?: Record; - keybindings?: Record; - extensions?: string[]; - }; - /** Generic IDE settings for other platforms */ - [ideId: string]: Record | undefined; -} - export interface SecurityInfo { /** Whether API keys or secrets were detected */ hasApiKeys: boolean; diff --git a/src/modules/deploy/services/backup.service.ts b/src/modules/deploy/services/backup.service.ts index 8a5813a..b985b71 100644 --- a/src/modules/deploy/services/backup.service.ts +++ b/src/modules/deploy/services/backup.service.ts @@ -31,6 +31,7 @@ export interface BackupInfo { @Injectable() export class BackupService { + // FIXME: 백업 경로 수정 protected readonly backupDir = path.join(os.homedir(), '.taptik', 'backups'); async createBackup(filePath: string): Promise { diff --git a/src/modules/deploy/services/deployment.service.ts b/src/modules/deploy/services/deployment.service.ts index ed55a56..0a45759 100644 --- a/src/modules/deploy/services/deployment.service.ts +++ b/src/modules/deploy/services/deployment.service.ts @@ -6,20 +6,20 @@ import { Injectable } from '@nestjs/common'; import { TaptikContext } from '../../context/interfaces/taptik-context.interface'; import { PLATFORM_PATHS } from '../constants/platform-paths.constants'; +import { CursorDeploymentOptions, CursorDeploymentResult } from '../interfaces/cursor-deployment.interface'; import { DeployOptions, ComponentType, } from '../interfaces/deploy-options.interface'; import { DeploymentResult } from '../interfaces/deployment-result.interface'; -import { CursorDeploymentOptions, CursorDeploymentResult } from '../interfaces/cursor-deployment.interface'; import { BackupService } from './backup.service'; +import { CursorDeploymentService } from './cursor-deployment.service'; import { DiffService } from './diff.service'; import { ErrorRecoveryService } from './error-recovery.service'; import { KiroComponentHandlerService } from './kiro-component-handler.service'; import { KiroInstallationDetectorService } from './kiro-installation-detector.service'; import { KiroTransformerService } from './kiro-transformer.service'; -import { CursorDeploymentService } from './cursor-deployment.service'; import { LargeFileStreamerService } from './large-file-streamer.service'; import { PerformanceMonitorService } from './performance-monitor.service'; import { PlatformValidatorService } from './platform-validator.service'; @@ -137,6 +137,7 @@ export class DeploymentService { } // Step 5: Check for conflicts + // FIXME: context를 변환하고 나서 찾아야할 것 같음 const existingConfig = await this.loadExistingClaudeCodeConfig(); if (existingConfig) { const diff = this.diffService.generateDiff(context, existingConfig); @@ -171,10 +172,11 @@ export class DeploymentService { // Start component timing this.performanceMonitor.startComponentTiming(deploymentId, component); + // FIXME: context를 변환하는게 아니라 그대로 deploy 하는 중 if (component === 'settings') { // Deploy settings even if empty for testing purposes - const settings = context.content.ide?.claudeCode?.settings || {}; - await this.deployGlobalSettings(settings); + const settings = context.content['settings'] || {}; + await this.deployGlobalSettings(settings as Record); (result.deployedComponents as string[]).push('settings'); result.summary.filesDeployed++; } else if (component === 'agents' && context.content.tools?.agents) { @@ -1332,6 +1334,7 @@ export class DeploymentService { } private getComponentsToDeploy(options: DeployOptions): ComponentType[] { + // FIXME: component 정리 필요 -> personal, project, prompts, tools, settings, agents, commands ? const allComponents: ComponentType[] = [ 'settings', 'agents', @@ -1489,7 +1492,7 @@ export class DeploymentService { dryRun: options.dryRun, conflictStrategy: options.conflictStrategy, validateOnly: options.validateOnly, - context: context, // Pass the full context + context, // Pass the full context }; } diff --git a/src/modules/deploy/services/platform-validator.service.ts b/src/modules/deploy/services/platform-validator.service.ts index 371aa43..c5946a7 100644 --- a/src/modules/deploy/services/platform-validator.service.ts +++ b/src/modules/deploy/services/platform-validator.service.ts @@ -121,23 +121,18 @@ export class PlatformValidatorService { const errors: ValidationError[] = []; const warnings: ValidationWarning[] = []; - const { ide, tools } = context.content; - - // Get Claude Code specific settings - const claudeCodeSettings = ide?.['claude-code'] || ide?.claudeCode; + const { tools } = context.content; // Validate agents if present (from tools context or ide context) const agentsFromTools = tools?.agents && Array.isArray(tools.agents) ? tools.agents : []; - const agentsFromIde = - ide?.agents && Array.isArray(ide.agents) ? ide.agents : []; - const allAgents = [...agentsFromTools, ...agentsFromIde]; + const allAgents = [...agentsFromTools]; if (allAgents.length > 0) { const adaptedAgents = allAgents.map((agent) => ({ name: agent.name, description: - (agent.metadata?.description as string) || agent.description || '', + (agent.metadata?.description as string) || '', content: agent.content, metadata: { version: (agent.metadata?.version as string) || '1.0.0', @@ -156,16 +151,13 @@ export class PlatformValidatorService { // Validate commands if present (from tools context or ide context) const commandsFromTools = tools?.commands && Array.isArray(tools.commands) ? tools.commands : []; - const commandsFromIde = - ide?.commands && Array.isArray(ide.commands) ? ide.commands : []; - const allCommands = [...commandsFromTools, ...commandsFromIde]; + const allCommands = [...commandsFromTools]; if (allCommands.length > 0) { const adaptedCommands = allCommands.map((command) => ({ name: command.name, description: (command.metadata?.description as string) || - command.description || '', content: command.content, permissions: command.permissions || [], @@ -182,18 +174,6 @@ export class PlatformValidatorService { warnings.push(...(commandErrors.warnings || [])); } - // Validate settings if present - if ( - claudeCodeSettings?.settings && - typeof claudeCodeSettings.settings === 'object' - ) { - const settingsErrors = this.validateSettings( - claudeCodeSettings.settings as ClaudeCodeSettings, - ); - errors.push(...settingsErrors.errors); - warnings.push(...(settingsErrors.warnings || [])); - } - return { isValid: errors.length === 0, errors, From 032af05745ee5cded15282f9bc0d36c219dd3f0a Mon Sep 17 00:00:00 2001 From: minseo Date: Sat, 6 Sep 2025 16:49:11 +0900 Subject: [PATCH 4/5] refactor: Remove Kiro installation detection service and clean up related deployment logic - Deleted KiroInstallationDetectorService and its associated tests to streamline the deployment process. - Updated DeploymentService to remove Kiro installation checks and related logic. - Reorganized import statements for clarity and consistency across deployment services. - Added FIXME comments in various services to address future improvements in validation and security scanning logic. --- .../interfaces/kiro-deployment.interface.ts | 1 + .../services/deployment-reporter.service.ts | 12 +- .../deploy/services/deployment.service.ts | 81 +- ...kiro-installation-detector.service.spec.ts | 346 ------- .../kiro-installation-detector.service.ts | 879 ------------------ .../services/platform-validator.service.ts | 1 + .../services/security-scanner.service.ts | 2 +- 7 files changed, 11 insertions(+), 1311 deletions(-) delete mode 100644 src/modules/deploy/services/kiro-installation-detector.service.spec.ts delete mode 100644 src/modules/deploy/services/kiro-installation-detector.service.ts diff --git a/src/modules/deploy/interfaces/kiro-deployment.interface.ts b/src/modules/deploy/interfaces/kiro-deployment.interface.ts index 7a1daea..2f40103 100644 --- a/src/modules/deploy/interfaces/kiro-deployment.interface.ts +++ b/src/modules/deploy/interfaces/kiro-deployment.interface.ts @@ -11,6 +11,7 @@ import { /** * Kiro-specific deployment options */ +// FIXME: kiro 옵션 제거?? export interface KiroDeploymentOptions { platform: Extract; conflictStrategy: KiroConflictStrategy; diff --git a/src/modules/deploy/services/deployment-reporter.service.ts b/src/modules/deploy/services/deployment-reporter.service.ts index 4d71e85..6ee8c04 100644 --- a/src/modules/deploy/services/deployment-reporter.service.ts +++ b/src/modules/deploy/services/deployment-reporter.service.ts @@ -1,14 +1,14 @@ import * as fs from 'node:fs/promises'; -import * as path from 'node:path'; import * as os from 'node:os'; +import * as path from 'node:path'; import { Injectable, Logger } from '@nestjs/common'; -import { DeploymentResult, DeploymentError, DeploymentWarning } from '../interfaces/deployment-result.interface'; -import { SupportedPlatform } from '../interfaces/deploy-options.interface'; +import { TaptikContext } from '../../context/interfaces/taptik-context.interface'; import { ComponentType } from '../interfaces/component-types.interface'; import { CursorComponentType } from '../interfaces/cursor-deployment.interface'; -import { TaptikContext } from '../../context/interfaces/taptik-context.interface'; +import { SupportedPlatform } from '../interfaces/deploy-options.interface'; +import { DeploymentResult, DeploymentError, DeploymentWarning } from '../interfaces/deployment-result.interface'; export interface DeploymentReport { id: string; @@ -518,7 +518,7 @@ export class DeploymentReporterService { // Private helper methods private generateReportId(platform: SupportedPlatform, contextId: string): string { - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const timestamp = new Date().toISOString().replace(/[.:]/g, '-'); return `${platform}-${contextId}-${timestamp}`; } @@ -892,7 +892,7 @@ export class DeploymentReporterService { const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2)) } ${ sizes[i]}`; } private generateHtmlReport(report: DeploymentReport): string { diff --git a/src/modules/deploy/services/deployment.service.ts b/src/modules/deploy/services/deployment.service.ts index 0a45759..8f983e6 100644 --- a/src/modules/deploy/services/deployment.service.ts +++ b/src/modules/deploy/services/deployment.service.ts @@ -18,7 +18,6 @@ import { CursorDeploymentService } from './cursor-deployment.service'; import { DiffService } from './diff.service'; import { ErrorRecoveryService } from './error-recovery.service'; import { KiroComponentHandlerService } from './kiro-component-handler.service'; -import { KiroInstallationDetectorService } from './kiro-installation-detector.service'; import { KiroTransformerService } from './kiro-transformer.service'; import { LargeFileStreamerService } from './large-file-streamer.service'; import { PerformanceMonitorService } from './performance-monitor.service'; @@ -37,7 +36,6 @@ export class DeploymentService { private readonly largeFileStreamer: LargeFileStreamerService, private readonly kiroTransformer: KiroTransformerService, private readonly kiroComponentHandler: KiroComponentHandlerService, - private readonly kiroInstallationDetector: KiroInstallationDetectorService, private readonly cursorDeploymentService: CursorDeploymentService, ) {} @@ -324,83 +322,6 @@ export class DeploymentService { }; try { - // Step 1: Check Kiro installation and compatibility - this.performanceMonitor.recordMemoryUsage( - deploymentId, - 'installation-check-start', - ); - - const installationInfo = - await this.kiroInstallationDetector.detectKiroInstallation(); - - if (!installationInfo.isInstalled) { - result.errors.push({ - message: - 'Kiro IDE is not installed or not found in expected locations', - code: 'KIRO_NOT_INSTALLED', - severity: 'CRITICAL', - }); - this.performanceMonitor.endDeploymentTiming(deploymentId); - result.metadata!.performanceReport = - 'Kiro deployment failed - installation not found'; - return result; - } - - // Add installation info to warnings for user visibility - result.warnings.push({ - message: `Kiro IDE detected: v${installationInfo.version || 'unknown'} at ${installationInfo.installationPath}`, - code: 'KIRO_INSTALLATION_DETECTED', - }); - - // Check compatibility - if (!installationInfo.isCompatible) { - const compatibilityResult = - await this.kiroInstallationDetector.checkCompatibility( - installationInfo.version, - ); - - // Add compatibility issues as warnings or errors based on severity - for (const issue of compatibilityResult.issues) { - if (issue.severity === 'critical') { - result.errors.push({ - message: `Compatibility issue: ${issue.message}`, - code: 'KIRO_COMPATIBILITY_ERROR', - severity: 'HIGH', - }); - } else { - result.warnings.push({ - message: `Compatibility warning: ${issue.message}`, - code: 'KIRO_COMPATIBILITY_WARNING', - }); - } - } - - // Stop deployment if critical compatibility issues exist - if ( - compatibilityResult.issues.some( - (issue) => issue.severity === 'critical', - ) - ) { - this.performanceMonitor.endDeploymentTiming(deploymentId); - result.metadata!.performanceReport = - 'Kiro deployment failed - compatibility issues'; - return result; - } - - // Add recommendations - for (const recommendation of compatibilityResult.recommendations) { - result.warnings.push({ - message: `Recommendation: ${recommendation}`, - code: 'KIRO_RECOMMENDATION', - }); - } - } - - this.performanceMonitor.recordMemoryUsage( - deploymentId, - 'installation-check-end', - ); - // Step 2: Validate configuration for Kiro platform this.performanceMonitor.recordMemoryUsage( deploymentId, @@ -445,6 +366,7 @@ export class DeploymentService { // Step 4: Security scan for Kiro components this.performanceMonitor.recordMemoryUsage(deploymentId, 'security-start'); + // FIXME: 각 ide별로 transformer를 만들어야 함 // Transform TaptikContext to Kiro formats for security scanning const globalSettings = this.kiroTransformer.transformPersonalContext(context); @@ -634,6 +556,7 @@ export class DeploymentService { }); // Step 9: Handle backup strategy + // FIXME: dryRun이면 백업 만들 필요 없음 순서 바꾸기 if (options.conflictStrategy === 'backup') { try { const backupPath = await this.createKiroBackup(); diff --git a/src/modules/deploy/services/kiro-installation-detector.service.spec.ts b/src/modules/deploy/services/kiro-installation-detector.service.spec.ts deleted file mode 100644 index 0f5f287..0000000 --- a/src/modules/deploy/services/kiro-installation-detector.service.spec.ts +++ /dev/null @@ -1,346 +0,0 @@ -import * as fs from 'node:fs/promises'; -import * as os from 'node:os'; - -import { Test, TestingModule } from '@nestjs/testing'; - -import { describe, it, expect, beforeEach, vi } from 'vitest'; - -import { KiroInstallationDetectorService } from './kiro-installation-detector.service'; - -vi.mock('node:fs/promises'); -vi.mock('node:os'); -vi.mock('node:child_process', () => ({ - spawn: vi.fn(() => ({ - stdout: { - on: vi.fn(), - }, - on: vi.fn((event, callback) => { - if (event === 'close') { - setTimeout(() => callback(1), 0); // Simulate process failure - } - if (event === 'error') { - setTimeout(() => callback(new Error('Process failed')), 0); - } - }), - kill: vi.fn(), - })), -})); - -describe('KiroInstallationDetectorService', () => { - let service: KiroInstallationDetectorService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [KiroInstallationDetectorService], - }).compile(); - - service = module.get( - KiroInstallationDetectorService, - ); - vi.clearAllMocks(); - }); - - describe('detectKiroInstallation', () => { - it('should detect Kiro installation when found', async () => { - // Mock successful installation detection - vi.mocked(fs.access).mockResolvedValueOnce(undefined); - vi.mocked(fs.readFile).mockResolvedValueOnce('{"version": "2.0.0"}'); - vi.mocked(os.homedir).mockReturnValue('/home/user'); - - const result = await service.detectKiroInstallation(); - - expect(result.isInstalled).toBe(true); - expect(result.version).toBe('2.0.0'); - expect(result.isCompatible).toBe(true); - }); - - it('should return not installed when Kiro is not found', async () => { - // Mock installation not found - vi.mocked(fs.access).mockRejectedValue(new Error('ENOENT')); - - const result = await service.detectKiroInstallation(); - - expect(result.isInstalled).toBe(false); - expect(result.isCompatible).toBe(false); - expect(result.version).toBeUndefined(); - }); - - it('should detect installation with unknown version', async () => { - // Mock installation found but version detection fails - vi.mocked(fs.access).mockResolvedValueOnce(undefined); - vi.mocked(fs.readFile).mockRejectedValue(new Error('File not found')); - vi.mocked(os.homedir).mockReturnValue('/home/user'); - - const result = await service.detectKiroInstallation(); - - expect(result.isInstalled).toBe(true); - expect(result.version).toBeUndefined(); - }); - }); - - describe('checkCompatibility', () => { - it('should return compatible for supported versions', async () => { - const result = await service.checkCompatibility('2.0.0'); - - expect(result.isCompatible).toBe(true); - expect(result.issues).toHaveLength(0); - expect(result.migrationRequired).toBe(false); - }); - - it('should detect incompatible old versions', async () => { - const result = await service.checkCompatibility('0.9.0'); - - expect(result.isCompatible).toBe(false); - expect(result.issues).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - type: 'version', - severity: 'critical', - message: 'Kiro IDE version 0.9.0 is not officially supported', - }), - ]), - ); - expect(result.migrationRequired).toBe(true); - }); - - it('should handle missing version', async () => { - const result = await service.checkCompatibility(undefined); - - expect(result.isCompatible).toBe(false); - expect(result.issues).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - type: 'version', - severity: 'critical', - message: 'Unable to determine Kiro IDE version', - }), - ]), - ); - }); - - it('should detect feature compatibility issues for old versions', async () => { - const result = await service.checkCompatibility('1.0.0'); - - // Version 1.0.0 should have some feature limitations - const featureIssues = result.issues.filter( - (issue) => issue.type === 'feature', - ); - expect(featureIssues.length).toBeGreaterThan(0); - }); - - it('should generate appropriate recommendations', async () => { - const result = await service.checkCompatibility('1.1.0'); - - expect(result.recommendations).toEqual( - expect.arrayContaining([ - expect.stringContaining('Consider upgrading to Kiro IDE v2.0.0'), - ]), - ); - }); - }); - - describe('performHealthCheck', () => { - it('should pass health check for healthy installation', async () => { - // Mock healthy installation - vi.mocked(fs.access).mockResolvedValue(undefined); - vi.mocked(fs.readFile).mockResolvedValue('{"version": "2.0.0"}'); - vi.mocked(os.homedir).mockReturnValue('/home/user'); - - const result = await service.performHealthCheck(); - - expect(result.healthy).toBe(true); - expect(result.issues).toHaveLength(0); - }); - - it('should detect installation health issues', async () => { - // Mock installation not found - vi.mocked(fs.access).mockRejectedValue(new Error('ENOENT')); - - const result = await service.performHealthCheck(); - - expect(result.healthy).toBe(false); - expect(result.issues).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - category: 'installation', - severity: 'critical', - message: - 'Kiro IDE is not installed or not found in expected locations', - }), - ]), - ); - }); - - it('should detect configuration health issues', async () => { - // Mock installation found but corrupted config - vi.mocked(fs.access).mockImplementation((filePath) => { - if ( - typeof filePath === 'string' && - filePath.includes('settings.json') - ) { - return Promise.resolve(); - } - return Promise.resolve(); - }); - vi.mocked(fs.readFile).mockImplementation((filePath) => { - if ( - typeof filePath === 'string' && - filePath.includes('settings.json') - ) { - return Promise.resolve('invalid json{'); - } - return Promise.resolve('{"version": "2.0.0"}'); - }); - vi.mocked(os.homedir).mockReturnValue('/home/user'); - - const result = await service.performHealthCheck(); - - const configIssues = result.issues.filter( - (issue) => issue.category === 'configuration', - ); - expect(configIssues.length).toBeGreaterThan(0); - }); - - it('should detect permission issues', async () => { - // Mock permission errors - vi.mocked(fs.access).mockImplementation((filePath, mode) => { - if (mode === (fs.constants.R_OK | fs.constants.W_OK)) { - return Promise.reject(new Error('EACCES')); - } - return Promise.resolve(); - }); - vi.mocked(os.homedir).mockReturnValue('/home/user'); - - const result = await service.performHealthCheck(); - - const permissionIssues = result.issues.filter( - (issue) => issue.category === 'permissions', - ); - expect(permissionIssues.length).toBeGreaterThan(0); - }); - - it('should generate health recommendations', async () => { - // Mock critical issues - vi.mocked(fs.access).mockRejectedValue(new Error('ENOENT')); - - const result = await service.performHealthCheck(); - - expect(result.recommendations).toEqual( - expect.arrayContaining([ - expect.stringContaining('Address critical issues'), - ]), - ); - }); - - it('should generate auto-fixable health fixes', async () => { - // Mock configuration issues that are auto-fixable - vi.mocked(fs.access).mockImplementation((filePath) => { - if ( - typeof filePath === 'string' && - filePath.includes('settings.json') - ) { - return Promise.resolve(); - } - return Promise.resolve(); - }); - vi.mocked(fs.readFile).mockImplementation((filePath) => { - if ( - typeof filePath === 'string' && - filePath.includes('settings.json') - ) { - return Promise.resolve('invalid json{'); - } - return Promise.resolve('{"version": "2.0.0"}'); - }); - vi.mocked(os.homedir).mockReturnValue('/home/user'); - - const result = await service.performHealthCheck(); - - const autoFixableFixes = result.fixes.filter((fix) => fix.automated); - expect(autoFixableFixes.length).toBeGreaterThan(0); - }); - }); - - describe('migrateConfiguration', () => { - it('should successfully migrate supported version path', async () => { - // Mock successful migration - vi.mocked(fs.mkdir).mockResolvedValue(undefined); - - const result = await service.migrateConfiguration('1.0.0', '1.1.0'); - - expect(result.success).toBe(true); - expect(result.errors).toHaveLength(0); - expect(result.warnings).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - code: 'MIGRATION_BACKUP_CREATED', - }), - ]), - ); - }); - - it('should reject unsupported migration paths', async () => { - const result = await service.migrateConfiguration('0.5.0', '2.0.0'); - - expect(result.success).toBe(false); - expect(result.errors).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - code: 'MIGRATION_NOT_SUPPORTED', - severity: 'HIGH', - }), - ]), - ); - }); - - it('should handle migration step failures', async () => { - // Mock backup creation failure - vi.mocked(fs.mkdir).mockRejectedValue(new Error('Permission denied')); - - const result = await service.migrateConfiguration('1.0.0', '1.1.0'); - - expect(result.success).toBe(false); - expect(result.errors.length).toBeGreaterThan(0); - }); - }); - - describe('version comparison', () => { - it('should correctly compare versions', async () => { - // Test through compatibility check which uses version comparison - const olderResult = await service.checkCompatibility('1.0.0'); - const newerResult = await service.checkCompatibility('2.0.0'); - - // Older version should have more issues - expect(olderResult.issues.length).toBeGreaterThan( - newerResult.issues.length, - ); - }); - }); - - describe('error handling', () => { - it('should handle errors gracefully during detection', async () => { - vi.mocked(fs.access).mockRejectedValue(new Error('Unexpected error')); - - const result = await service.detectKiroInstallation(); - - expect(result.isInstalled).toBe(false); - expect(result.isCompatible).toBe(false); - }); - - it('should handle health check errors gracefully', async () => { - vi.mocked(fs.access).mockRejectedValue(new Error('Unexpected error')); - - const result = await service.performHealthCheck(); - - expect(result.healthy).toBe(false); - expect(result.issues).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - category: 'installation', - severity: 'critical', - }), - ]), - ); - }); - }); -}); diff --git a/src/modules/deploy/services/kiro-installation-detector.service.ts b/src/modules/deploy/services/kiro-installation-detector.service.ts deleted file mode 100644 index cd4f44b..0000000 --- a/src/modules/deploy/services/kiro-installation-detector.service.ts +++ /dev/null @@ -1,879 +0,0 @@ -import * as fs from 'node:fs/promises'; -import * as os from 'node:os'; -import * as path from 'node:path'; - -import { Injectable, Logger } from '@nestjs/common'; - -// SupportedPlatform import removed as it's not used -import { - DeploymentError, - DeploymentWarning, -} from '../interfaces/deployment-result.interface'; - -export interface KiroInstallationInfo { - isInstalled: boolean; - version?: string; - installationPath?: string; - configurationPath?: { - global: string; - project: string; - }; - isCompatible: boolean; - compatibility: { - version: boolean; - schema: boolean; - features: boolean; - }; -} - -export interface KiroCompatibilityResult { - isCompatible: boolean; - version: { - current?: string; - required: string; - supported: string[]; - }; - issues: KiroCompatibilityIssue[]; - recommendations: string[]; - migrationRequired: boolean; -} - -export interface KiroCompatibilityIssue { - type: 'version' | 'schema' | 'feature' | 'configuration'; - severity: 'low' | 'medium' | 'high' | 'critical'; - message: string; - details?: unknown; - fixable: boolean; -} - -export interface KiroHealthCheckResult { - healthy: boolean; - issues: KiroHealthIssue[]; - recommendations: string[]; - fixes: KiroHealthFix[]; -} - -export interface KiroHealthIssue { - category: 'installation' | 'configuration' | 'permissions' | 'dependencies'; - severity: 'low' | 'medium' | 'high' | 'critical'; - message: string; - details?: string; - autoFixable: boolean; -} - -export interface KiroHealthFix { - issue: string; - action: string; - automated: boolean; - riskLevel: 'low' | 'medium' | 'high'; -} - -@Injectable() -export class KiroInstallationDetectorService { - private readonly logger = new Logger(KiroInstallationDetectorService.name); - - private readonly SUPPORTED_VERSIONS = ['1.0.0', '1.1.0', '1.2.0', '2.0.0']; - private readonly MINIMUM_VERSION = '1.0.0'; - private readonly RECOMMENDED_VERSION = '2.0.0'; - - async detectKiroInstallation(): Promise { - this.logger.log('Detecting Kiro IDE installation...'); - - const installationInfo: KiroInstallationInfo = { - isInstalled: false, - isCompatible: false, - compatibility: { - version: false, - schema: false, - features: false, - }, - }; - - try { - // Check for Kiro installation in common locations - const installationPath = await this.findKiroInstallation(); - - if (!installationPath) { - this.logger.warn('Kiro IDE installation not found'); - return installationInfo; - } - - installationInfo.isInstalled = true; - installationInfo.installationPath = installationPath; - - // Detect version - const version = await this.detectKiroVersion(installationPath); - if (version) { - installationInfo.version = version; - } - - // Set configuration paths - installationInfo.configurationPath = { - global: path.join(os.homedir(), '.kiro'), - project: path.join(process.cwd(), '.kiro'), - }; - - // Check compatibility - const compatibility = await this.checkCompatibility(version); - installationInfo.isCompatible = compatibility.isCompatible; - installationInfo.compatibility = { - version: compatibility.version.current - ? this.isVersionSupported(compatibility.version.current) - : false, - schema: !compatibility.issues.some((issue) => issue.type === 'schema'), - features: !compatibility.issues.some( - (issue) => issue.type === 'feature', - ), - }; - - this.logger.log(`Kiro IDE detected: v${version} at ${installationPath}`); - return installationInfo; - } catch (error) { - this.logger.error('Error detecting Kiro installation:', error); - return installationInfo; - } - } - - async checkCompatibility( - currentVersion?: string, - ): Promise { - const result: KiroCompatibilityResult = { - isCompatible: false, - version: { - current: currentVersion, - required: this.MINIMUM_VERSION, - supported: this.SUPPORTED_VERSIONS, - }, - issues: [], - recommendations: [], - migrationRequired: false, - }; - - // Check version compatibility - if (!currentVersion) { - result.issues.push({ - type: 'version', - severity: 'critical', - message: 'Unable to determine Kiro IDE version', - fixable: false, - }); - return result; - } - - if (!this.isVersionSupported(currentVersion)) { - const severity = - this.compareVersions(currentVersion, this.MINIMUM_VERSION) < 0 - ? 'critical' - : 'medium'; - - result.issues.push({ - type: 'version', - severity, - message: `Kiro IDE version ${currentVersion} is not officially supported`, - details: { - current: currentVersion, - minimum: this.MINIMUM_VERSION, - recommended: this.RECOMMENDED_VERSION, - }, - fixable: true, - }); - } - - // Check for schema compatibility - const schemaIssues = await this.checkSchemaCompatibility(currentVersion); - result.issues.push(...schemaIssues); - - // Check feature compatibility - const featureIssues = await this.checkFeatureCompatibility(currentVersion); - result.issues.push(...featureIssues); - - // Determine if migration is required - result.migrationRequired = - this.compareVersions(currentVersion, this.MINIMUM_VERSION) < 0 || - result.issues.some( - (issue) => issue.type === 'schema' && issue.severity === 'high', - ); - - // Generate recommendations - result.recommendations = this.generateCompatibilityRecommendations(result); - - // Determine overall compatibility - result.isCompatible = - result.issues.length === 0 || - !result.issues.some((issue) => issue.severity === 'critical'); - - return result; - } - - async performHealthCheck(): Promise { - this.logger.log('Performing Kiro IDE health check...'); - - const result: KiroHealthCheckResult = { - healthy: true, - issues: [], - recommendations: [], - fixes: [], - }; - - try { - // Check installation health - const installationIssues = await this.checkInstallationHealth(); - result.issues.push(...installationIssues); - - // Check configuration health - const configIssues = await this.checkConfigurationHealth(); - result.issues.push(...configIssues); - - // Check permissions - const permissionIssues = await this.checkPermissions(); - result.issues.push(...permissionIssues); - - // Check dependencies - const dependencyIssues = await this.checkDependencies(); - result.issues.push(...dependencyIssues); - - // Generate fixes - result.fixes = this.generateHealthFixes(result.issues); - - // Generate recommendations - result.recommendations = this.generateHealthRecommendations( - result.issues, - ); - - // Determine overall health - result.healthy = - result.issues.length === 0 || - !result.issues.some( - (issue) => issue.severity === 'critical' || issue.severity === 'high', - ); - - this.logger.log( - `Health check completed: ${result.healthy ? 'healthy' : 'issues found'}`, - ); - return result; - } catch (error) { - this.logger.error('Health check failed:', error); - - result.healthy = false; - result.issues.push({ - category: 'installation', - severity: 'critical', - message: 'Health check failed due to unexpected error', - details: (error as Error).message, - autoFixable: false, - }); - - return result; - } - } - - async migrateConfiguration( - fromVersion: string, - toVersion: string, - ): Promise<{ - success: boolean; - errors: DeploymentError[]; - warnings: DeploymentWarning[]; - migratedFiles: string[]; - }> { - this.logger.log( - `Migrating configuration from v${fromVersion} to v${toVersion}...`, - ); - - const result = { - success: false, - errors: [] as DeploymentError[], - warnings: [] as DeploymentWarning[], - migratedFiles: [] as string[], - }; - - try { - // Validate migration path - if (!this.isMigrationSupported(fromVersion, toVersion)) { - result.errors.push({ - message: `Migration from v${fromVersion} to v${toVersion} is not supported`, - code: 'MIGRATION_NOT_SUPPORTED', - severity: 'HIGH', - }); - return result; - } - - // Create backup before migration - const backupPath = await this.createMigrationBackup(); - result.warnings.push({ - message: `Configuration backup created at: ${backupPath}`, - code: 'MIGRATION_BACKUP_CREATED', - }); - - // Perform version-specific migrations - const migrations = this.getMigrationSteps(fromVersion, toVersion); - - // Execute migrations concurrently and handle results - const migrationResults = await Promise.allSettled( - migrations.map(async (migration) => { - try { - const migrationResult = await migration.execute(); - return { - success: true, - migration, - result: migrationResult, - }; - } catch (migrationError) { - return { - success: false, - migration, - error: migrationError as Error, - }; - } - }), - ); - - // Process migration results - for (const migrationResult of migrationResults) { - if (migrationResult.status === 'fulfilled') { - const { - success, - migration, - result: migrationData, - error, - } = migrationResult.value; - if (success && migrationData) { - result.migratedFiles.push(...migrationData.files); - result.warnings.push(...migrationData.warnings); - } else if (error) { - result.errors.push({ - message: `Migration step failed: ${migration.name}`, - code: 'MIGRATION_STEP_FAILED', - severity: 'HIGH', - details: error.message, - }); - } - } else { - result.errors.push({ - message: 'Migration step failed unexpectedly', - code: 'MIGRATION_STEP_FAILED', - severity: 'HIGH', - details: migrationResult.reason, - }); - } - } - - result.success = result.errors.length === 0; - - if (result.success) { - this.logger.log( - `Migration completed successfully: ${result.migratedFiles.length} files migrated`, - ); - } else { - this.logger.error( - `Migration failed with ${result.errors.length} errors`, - ); - } - - return result; - } catch (error) { - result.errors.push({ - message: `Migration failed: ${(error as Error).message}`, - code: 'MIGRATION_FAILED', - severity: 'CRITICAL', - }); - - this.logger.error('Migration failed:', error); - return result; - } - } - - private async findKiroInstallation(): Promise { - const commonPaths = [ - // macOS - '/Applications/Kiro.app', - '/Applications/Kiro IDE.app', - // Windows - 'C:\\Program Files\\Kiro IDE', - 'C:\\Program Files (x86)\\Kiro IDE', - // Linux - '/opt/kiro', - '/usr/local/bin/kiro', - '/snap/kiro-ide/current', - ]; - - // Check all paths concurrently - const accessResults = await Promise.allSettled( - commonPaths.map(async (installPath) => { - await fs.access(installPath); - return installPath; - }), - ); - - // Return the first successful path - for (const result of accessResults) { - if (result.status === 'fulfilled') { - return result.value; - } - } - - // Check PATH for kiro command - try { - const { spawn } = await import('node:child_process'); - const childProcess = spawn('which', ['kiro'], { stdio: 'pipe' }); - - return new Promise((resolve) => { - let output = ''; - - childProcess.stdout?.on('data', (data) => { - output += data.toString(); - }); - - childProcess.on('close', (code) => { - if (code === 0 && output.trim()) { - resolve(output.trim()); - } else { - resolve(null); - } - }); - - childProcess.on('error', () => { - resolve(null); - }); - - // Timeout after 5 seconds - setTimeout(() => { - try { - childProcess.kill(); - } catch { - // Process might already be dead - } - resolve(null); - }, 5000); - }); - } catch { - return null; - } - } - - private async detectKiroVersion( - installationPath: string, - ): Promise { - try { - // Try to read version from package.json or version file - const versionFilePaths = [ - path.join(installationPath, 'package.json'), - path.join(installationPath, 'version.txt'), - path.join(installationPath, 'Contents', 'Resources', 'version.json'), // macOS app bundle - ]; - - // Read all version files concurrently - const versionResults = await Promise.allSettled( - versionFilePaths.map(async (versionPath) => { - const content = await fs.readFile(versionPath, 'utf8'); - return { versionPath, content }; - }), - ); - - // Process the results and extract version - for (const result of versionResults) { - if (result.status === 'fulfilled') { - const { versionPath, content } = result.value; - - if (versionPath.endsWith('.json')) { - try { - const json = JSON.parse(content); - const version = json.version || json.app_version; - if (version) return version; - } catch { - continue; - } - } else { - // Extract version from text file - const versionMatch = content.match(/(\d+\.\d+\.\d+)/); - if (versionMatch) { - return versionMatch[1]; - } - } - } - } - - // Fallback: try to execute kiro --version - try { - const { spawn } = await import('node:child_process'); - const kiroExec = path.join(installationPath, 'kiro'); - const process = spawn(kiroExec, ['--version'], { stdio: 'pipe' }); - - return new Promise((resolve) => { - let output = ''; - process.stdout?.on('data', (data) => { - output += data.toString(); - }); - - process.on('close', () => { - const versionMatch = output.match(/(\d+\.\d+\.\d+)/); - resolve(versionMatch ? versionMatch[1] : undefined); - }); - - // Timeout after 3 seconds - setTimeout(() => { - process.kill(); - resolve(undefined); - }, 3000); - }); - } catch { - return undefined; - } - } catch (error) { - this.logger.error('Error detecting Kiro version:', error); - return undefined; - } - } - - private isVersionSupported(version: string): boolean { - return ( - this.SUPPORTED_VERSIONS.includes(version) || - this.compareVersions(version, this.MINIMUM_VERSION) >= 0 - ); - } - - private compareVersions(a: string, b: string): number { - const aParts = a.split('.').map(Number); - const bParts = b.split('.').map(Number); - - for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) { - const aPart = aParts[i] || 0; - const bPart = bParts[i] || 0; - - if (aPart > bPart) return 1; - if (aPart < bPart) return -1; - } - - return 0; - } - - private async checkSchemaCompatibility( - version: string, - ): Promise { - const issues: KiroCompatibilityIssue[] = []; - - // Check if current schema version is compatible - if (this.compareVersions(version, '2.0.0') < 0) { - issues.push({ - type: 'schema', - severity: 'medium', - message: - 'Configuration schema migration may be required for newer features', - details: { currentVersion: version, schemaVersion: '1.x' }, - fixable: true, - }); - } - - return issues; - } - - private async checkFeatureCompatibility( - version: string, - ): Promise { - const issues: KiroCompatibilityIssue[] = []; - - // Check for specific feature compatibility - const featureCompatibility = { - hooks: this.compareVersions(version, '1.1.0') >= 0, - agents: this.compareVersions(version, '1.2.0') >= 0, - templates: this.compareVersions(version, '2.0.0') >= 0, - steering: this.compareVersions(version, '1.0.0') >= 0, - }; - - for (const [feature, supported] of Object.entries(featureCompatibility)) { - if (!supported) { - issues.push({ - type: 'feature', - severity: 'medium', - message: `Feature '${feature}' is not supported in version ${version}`, - details: { feature, version }, - fixable: false, - }); - } - } - - return issues; - } - - private async checkInstallationHealth(): Promise { - const issues: KiroHealthIssue[] = []; - - try { - const installationInfo = await this.detectKiroInstallation(); - - if (!installationInfo.isInstalled) { - issues.push({ - category: 'installation', - severity: 'critical', - message: - 'Kiro IDE is not installed or not found in expected locations', - autoFixable: false, - }); - } - } catch (error) { - issues.push({ - category: 'installation', - severity: 'high', - message: 'Unable to verify Kiro IDE installation', - details: (error as Error).message, - autoFixable: false, - }); - } - - return issues; - } - - private async checkConfigurationHealth(): Promise { - const issues: KiroHealthIssue[] = []; - - const configPaths = [ - path.join(os.homedir(), '.kiro', 'settings.json'), - path.join(process.cwd(), '.kiro', 'settings.json'), - ]; - - // Check all configuration files concurrently - const configResults = await Promise.allSettled( - configPaths.map(async (configPath) => { - await fs.access(configPath); - const content = await fs.readFile(configPath, 'utf8'); - JSON.parse(content); // Validate JSON - return configPath; - }), - ); - - // Process results and collect issues - for (let i = 0; i < configResults.length; i++) { - const result = configResults[i]; - const configPath = configPaths[i]; - - if (result.status === 'rejected') { - const error = result.reason as NodeJS.ErrnoException; - if (error.code !== 'ENOENT') { - issues.push({ - category: 'configuration', - severity: 'medium', - message: `Configuration file is corrupted or invalid: ${configPath}`, - details: error.message, - autoFixable: true, - }); - } - } - } - - return issues; - } - - private async checkPermissions(): Promise { - const issues: KiroHealthIssue[] = []; - - const pathsToCheck = [ - os.homedir(), - path.join(os.homedir(), '.kiro'), - process.cwd(), - path.join(process.cwd(), '.kiro'), - ]; - - // Check all paths concurrently - const permissionResults = await Promise.allSettled( - pathsToCheck.map(async (checkPath) => { - await fs.access(checkPath, fs.constants.R_OK | fs.constants.W_OK); - return checkPath; - }), - ); - - // Process results and collect issues - for (let i = 0; i < permissionResults.length; i++) { - const result = permissionResults[i]; - const checkPath = pathsToCheck[i]; - - if (result.status === 'rejected') { - issues.push({ - category: 'permissions', - severity: 'high', - message: `Insufficient permissions for path: ${checkPath}`, - autoFixable: false, - }); - } - } - - return issues; - } - - private async checkDependencies(): Promise { - const issues: KiroHealthIssue[] = []; - - // Check Node.js version - const nodeVersion = process.version; - const minNodeVersion = '16.0.0'; - - if (this.compareVersions(nodeVersion.slice(1), minNodeVersion) < 0) { - issues.push({ - category: 'dependencies', - severity: 'high', - message: `Node.js version ${nodeVersion} is below minimum required version ${minNodeVersion}`, - autoFixable: false, - }); - } - - return issues; - } - - private generateCompatibilityRecommendations( - result: KiroCompatibilityResult, - ): string[] { - const recommendations: string[] = []; - - if ( - result.version.current && - this.compareVersions(result.version.current, this.RECOMMENDED_VERSION) < 0 - ) { - recommendations.push( - `Consider upgrading to Kiro IDE v${this.RECOMMENDED_VERSION} for best compatibility`, - ); - } - - if (result.migrationRequired) { - recommendations.push( - 'Configuration migration is recommended before deployment', - ); - } - - if (result.issues.some((issue) => issue.type === 'feature')) { - recommendations.push( - 'Some features may not be available in your current Kiro IDE version', - ); - } - - return recommendations; - } - - private generateHealthRecommendations(issues: KiroHealthIssue[]): string[] { - const recommendations: string[] = []; - - const criticalIssues = issues.filter( - (issue) => issue.severity === 'critical', - ); - if (criticalIssues.length > 0) { - recommendations.push( - 'Address critical issues before attempting deployment', - ); - } - - const permissionIssues = issues.filter( - (issue) => issue.category === 'permissions', - ); - if (permissionIssues.length > 0) { - recommendations.push( - 'Ensure proper file system permissions for Kiro configuration directories', - ); - } - - const configIssues = issues.filter( - (issue) => issue.category === 'configuration', - ); - if (configIssues.length > 0) { - recommendations.push('Backup and repair corrupted configuration files'); - } - - return recommendations; - } - - private generateHealthFixes(issues: KiroHealthIssue[]): KiroHealthFix[] { - const fixes: KiroHealthFix[] = []; - - for (const issue of issues.filter((issue) => issue.autoFixable)) { - if (issue.category === 'configuration') { - fixes.push({ - issue: issue.message, - action: 'Recreate configuration file with default settings', - automated: true, - riskLevel: 'low', - }); - } - } - - return fixes; - } - - private isMigrationSupported( - fromVersion: string, - toVersion: string, - ): boolean { - // Define supported migration paths - const supportedMigrations = [ - { from: '1.0.0', to: '1.1.0' }, - { from: '1.1.0', to: '1.2.0' }, - { from: '1.2.0', to: '2.0.0' }, - ]; - - return supportedMigrations.some( - (migration) => - migration.from === fromVersion && migration.to === toVersion, - ); - } - - private async createMigrationBackup(): Promise { - const backupDir = path.join(os.homedir(), '.kiro', 'backups'); - const timestamp = new Date().toISOString().replace(/[.:]/g, '-'); - const backupPath = path.join(backupDir, `migration-backup-${timestamp}`); - - await fs.mkdir(backupDir, { recursive: true }); - - // Copy current configuration to backup - const configPaths = [ - path.join(os.homedir(), '.kiro'), - path.join(process.cwd(), '.kiro'), - ]; - - // Check all configuration paths concurrently - const backupResults = await Promise.allSettled( - configPaths.map(async (configPath) => { - await fs.access(configPath); - // Copy configuration (simplified - would need recursive copy implementation) - return configPath; - }), - ); - - // Process successful backup operations (simplified for now) - const accessiblePaths = backupResults - .filter((result) => result.status === 'fulfilled') - .map((result) => (result as PromiseFulfilledResult).value); - - // Log accessible paths for backup (actual copying logic would go here) - if (accessiblePaths.length > 0) { - this.logger.log( - `Found ${accessiblePaths.length} configuration paths for backup`, - ); - } - - return backupPath; - } - - private getMigrationSteps( - fromVersion: string, - toVersion: string, - ): Array<{ - name: string; - execute: () => Promise<{ files: string[]; warnings: DeploymentWarning[] }>; - }> { - // Return migration steps based on version differences - const steps = []; - - if (fromVersion === '1.0.0' && toVersion === '1.1.0') { - steps.push({ - name: 'Add hooks support', - execute: async () => ({ - files: ['.kiro/hooks'], - warnings: [ - { - message: 'Hooks feature enabled', - code: 'MIGRATION_FEATURE_ADDED', - }, - ], - }), - }); - } - - return steps; - } -} diff --git a/src/modules/deploy/services/platform-validator.service.ts b/src/modules/deploy/services/platform-validator.service.ts index c5946a7..7a81e3d 100644 --- a/src/modules/deploy/services/platform-validator.service.ts +++ b/src/modules/deploy/services/platform-validator.service.ts @@ -88,6 +88,7 @@ export class PlatformValidatorService { }); } + // FIXME: validation 로직 통일 // Platform-specific validation if (platform === 'claude-code') { const claudeResult = await this.validateClaudeCode(context); diff --git a/src/modules/deploy/services/security-scanner.service.ts b/src/modules/deploy/services/security-scanner.service.ts index 7dd15ff..ee99f99 100644 --- a/src/modules/deploy/services/security-scanner.service.ts +++ b/src/modules/deploy/services/security-scanner.service.ts @@ -467,7 +467,7 @@ export class SecurityScannerService { } // Kiro-specific security scanning methods - + // FIXME: security 검사 통일 async scanKiroComponents( components: Array<{ type: KiroComponentType; From 435ab6d9449566e1defef500a608f6d18f024c78 Mon Sep 17 00:00:00 2001 From: minseo Date: Sat, 6 Sep 2025 17:28:27 +0900 Subject: [PATCH 5/5] refactor: Update cursor deployment interfaces and services for improved clarity and functionality - Refactored CursorDeploymentOptions to use SupportedPlatform instead of a hardcoded string for platform. - Cleaned up import statements in cursor services for better organization. - Removed the CursorDeploymentStateService as part of streamlining the deployment process. - Added comments and fixed formatting issues for improved code readability. - Addressed FIXME comments in various services for future enhancements. --- .../interfaces/cursor-deployment.interface.ts | 8 +- .../deploy/services/cursor-backup.service.ts | 21 +- .../cursor-deployment-state.service.ts | 461 ------------------ .../services/cursor-deployment.service.ts | 35 +- .../deploy/services/deployment.service.ts | 2 +- 5 files changed, 34 insertions(+), 493 deletions(-) delete mode 100644 src/modules/deploy/services/cursor-deployment-state.service.ts diff --git a/src/modules/deploy/interfaces/cursor-deployment.interface.ts b/src/modules/deploy/interfaces/cursor-deployment.interface.ts index 8c61688..9a8a3b1 100644 --- a/src/modules/deploy/interfaces/cursor-deployment.interface.ts +++ b/src/modules/deploy/interfaces/cursor-deployment.interface.ts @@ -1,10 +1,10 @@ -import { ComponentTypes } from './component-types.interface'; -import { DeploymentOptions } from './deploy-options.interface'; -import { DeploymentResult } from './deployment-result.interface'; import { CursorComponentType } from '../constants/cursor.constants'; +import { DeploymentOptions, SupportedPlatform } from './deploy-options.interface'; +import { DeploymentResult } from './deployment-result.interface'; + export interface CursorDeploymentOptions extends DeploymentOptions { - platform: 'cursor'; + platform: SupportedPlatform; cursorPath?: string; workspacePath?: string; globalSettings?: boolean; diff --git a/src/modules/deploy/services/cursor-backup.service.ts b/src/modules/deploy/services/cursor-backup.service.ts index d31a290..00acbf7 100644 --- a/src/modules/deploy/services/cursor-backup.service.ts +++ b/src/modules/deploy/services/cursor-backup.service.ts @@ -1,7 +1,10 @@ -import { Injectable, Logger } from '@nestjs/common'; import * as fs from 'fs/promises'; import * as path from 'path'; -import { CursorDeploymentOptions, CursorComponentType } from '../interfaces/cursor-deployment.interface'; + +import { Injectable, Logger } from '@nestjs/common'; + +import { CursorComponentType } from '../constants'; +import { CursorDeploymentOptions } from '../interfaces/cursor-deployment.interface'; import { DeploymentError, DeploymentWarning } from '../interfaces/deployment-result.interface'; /** @@ -51,6 +54,7 @@ export class CursorBackupService { // Backup each component for (const component of components) { try { + // eslint-disable-next-line no-await-in-loop const componentBackup = await this.backupComponent(component, options, backupPath); backedUpFiles.push(...componentBackup.files); warnings.push(...componentBackup.warnings); @@ -267,6 +271,7 @@ export class CursorBackupService { for (const filePath of filesToBackup) { try { // Check if file exists + // eslint-disable-next-line no-await-in-loop const exists = await this.fileExists(filePath); if (!exists) { warnings.push({ @@ -279,10 +284,12 @@ export class CursorBackupService { } // Read and backup file + // eslint-disable-next-line no-await-in-loop const content = await fs.readFile(filePath, 'utf8'); const backupFileName = this.sanitizeFileName(path.basename(filePath)); const backupFilePath = path.join(componentBackupPath, backupFileName); + // eslint-disable-next-line no-await-in-loop await fs.writeFile(backupFilePath, content, 'utf8'); files.push({ @@ -328,7 +335,7 @@ export class CursorBackupService { */ private getComponentFilePaths(component: CursorComponentType, options: CursorDeploymentOptions): string[] { const cursorPath = options.cursorPath || this.getDefaultCursorPath(); - const workspacePath = options.workspacePath; + const {workspacePath} = options; switch (component) { case 'global-settings': @@ -372,7 +379,7 @@ export class CursorBackupService { case 'workspace-config': return workspacePath ? [ - path.join(workspacePath, path.basename(workspacePath) + '.code-workspace'), + path.join(workspacePath, `${path.basename(workspacePath) }.code-workspace`), ] : []; default: @@ -420,7 +427,7 @@ export class CursorBackupService { * Get default Cursor path based on OS */ private getDefaultCursorPath(): string { - const platform = process.platform; + const {platform} = process; const home = process.env.HOME || process.env.USERPROFILE || '/tmp'; switch (platform) { @@ -439,14 +446,14 @@ export class CursorBackupService { * Sanitize filename for backup */ private sanitizeFileName(fileName: string): string { - return fileName.replace(/[^a-zA-Z0-9.-]/g, '_'); + return fileName.replace(/[^\d.A-Za-z-]/g, '_'); } /** * Generate backup ID */ private generateBackupId(deploymentId: string): string { - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const timestamp = new Date().toISOString().replace(/[.:]/g, '-'); const shortDeploymentId = deploymentId.split('-').pop() || 'unknown'; return `backup-${timestamp}-${shortDeploymentId}`; } diff --git a/src/modules/deploy/services/cursor-deployment-state.service.ts b/src/modules/deploy/services/cursor-deployment-state.service.ts deleted file mode 100644 index af7173a..0000000 --- a/src/modules/deploy/services/cursor-deployment-state.service.ts +++ /dev/null @@ -1,461 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import * as fs from 'fs/promises'; -import * as path from 'path'; -import { CursorDeploymentOptions, CursorDeploymentResult, CursorComponentType } from '../interfaces/cursor-deployment.interface'; -import { DeploymentError, DeploymentWarning } from '../interfaces/deployment-result.interface'; - -/** - * Task 6.2: Cursor deployment state manager for interrupted deployment recovery - */ -@Injectable() -export class CursorDeploymentStateService { - private readonly logger = new Logger(CursorDeploymentStateService.name); - private readonly stateBasePath: string; - - constructor() { - // Create state directory in system temp or user home - this.stateBasePath = path.join( - process.env.HOME || process.env.USERPROFILE || '/tmp', - '.taptik-cli', - 'deployment-states' - ); - } - - /** - * Save deployment state for recovery - */ - async saveDeploymentState( - deploymentId: string, - options: CursorDeploymentOptions, - progress: DeploymentProgress - ): Promise { - this.logger.debug(`Saving deployment state: ${deploymentId}`); - - try { - await fs.mkdir(this.stateBasePath, { recursive: true }); - - const state: DeploymentState = { - deploymentId, - timestamp: new Date().toISOString(), - status: progress.status, - options, - progress, - version: '1.0.0', - }; - - const statePath = path.join(this.stateBasePath, `${deploymentId}.json`); - await fs.writeFile(statePath, JSON.stringify(state, null, 2), 'utf8'); - - this.logger.debug(`Deployment state saved: ${statePath}`); - } catch (error) { - this.logger.error(`Failed to save deployment state for ${deploymentId}:`, error); - // Don't throw - state saving is optional - } - } - - /** - * Load deployment state - */ - async loadDeploymentState(deploymentId: string): Promise { - try { - const statePath = path.join(this.stateBasePath, `${deploymentId}.json`); - const stateContent = await fs.readFile(statePath, 'utf8'); - const state: DeploymentState = JSON.parse(stateContent); - - this.logger.debug(`Loaded deployment state: ${deploymentId}`); - return state; - } catch (error) { - this.logger.debug(`No deployment state found for ${deploymentId}:`, error); - return null; - } - } - - /** - * Check for interrupted deployments - */ - async findInterruptedDeployments(): Promise { - this.logger.log('Checking for interrupted deployments...'); - - try { - await fs.mkdir(this.stateBasePath, { recursive: true }); - const stateFiles = await fs.readdir(this.stateBasePath); - const interruptedDeployments: DeploymentState[] = []; - - for (const stateFile of stateFiles) { - if (!stateFile.endsWith('.json')) continue; - - try { - const statePath = path.join(this.stateBasePath, stateFile); - const stateContent = await fs.readFile(statePath, 'utf8'); - const state: DeploymentState = JSON.parse(stateContent); - - // Check if deployment was interrupted - if (this.isDeploymentInterrupted(state)) { - interruptedDeployments.push(state); - this.logger.log(`Found interrupted deployment: ${state.deploymentId}`); - } - } catch (error) { - this.logger.warn(`Failed to parse state file ${stateFile}:`, error); - } - } - - this.logger.log(`Found ${interruptedDeployments.length} interrupted deployments`); - return interruptedDeployments.sort((a, b) => - new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() - ); - } catch (error) { - this.logger.error('Failed to check for interrupted deployments:', error); - return []; - } - } - - /** - * Resume interrupted deployment - */ - async resumeDeployment(deploymentId: string): Promise { - this.logger.log(`Planning recovery for interrupted deployment: ${deploymentId}`); - - const state = await this.loadDeploymentState(deploymentId); - if (!state) { - throw new Error(`Deployment state not found: ${deploymentId}`); - } - - const recoveryPlan: DeploymentRecoveryPlan = { - deploymentId, - originalOptions: state.options, - recoveryActions: [], - remainingComponents: [], - completedComponents: state.progress.completedComponents, - failedComponents: state.progress.failedComponents, - estimatedTimeRemaining: 0, - }; - - // Determine what needs to be done - const allComponents = state.options.components || this.getDefaultComponents(); - const completedComponents = state.progress.completedComponents; - const failedComponents = state.progress.failedComponents; - - recoveryPlan.remainingComponents = allComponents.filter(component => - !completedComponents.includes(component) && !failedComponents.includes(component) - ); - - // Plan recovery actions - if (failedComponents.length > 0) { - recoveryPlan.recoveryActions.push({ - type: 'retry_failed', - description: `Retry failed components: ${failedComponents.join(', ')}`, - components: failedComponents, - priority: 'high', - }); - } - - if (recoveryPlan.remainingComponents.length > 0) { - recoveryPlan.recoveryActions.push({ - type: 'complete_remaining', - description: `Complete remaining components: ${recoveryPlan.remainingComponents.join(', ')}`, - components: recoveryPlan.remainingComponents, - priority: 'medium', - }); - } - - // Check for partial component deployments that need cleanup - const partialComponents = this.detectPartialDeployments(state); - if (partialComponents.length > 0) { - recoveryPlan.recoveryActions.push({ - type: 'cleanup_partial', - description: `Clean up partially deployed components: ${partialComponents.join(', ')}`, - components: partialComponents, - priority: 'high', - }); - } - - // Estimate recovery time - recoveryPlan.estimatedTimeRemaining = this.estimateRecoveryTime(recoveryPlan); - - this.logger.log(`Recovery plan created: ${recoveryPlan.recoveryActions.length} actions, ` + - `${recoveryPlan.remainingComponents.length} remaining components`); - - return recoveryPlan; - } - - /** - * Mark deployment as completed - */ - async markDeploymentCompleted(deploymentId: string, result: CursorDeploymentResult): Promise { - this.logger.debug(`Marking deployment as completed: ${deploymentId}`); - - try { - const state = await this.loadDeploymentState(deploymentId); - if (state) { - state.status = result.success ? 'completed' : 'failed'; - state.progress.completedAt = new Date().toISOString(); - state.progress.finalResult = result; - - const statePath = path.join(this.stateBasePath, `${deploymentId}.json`); - await fs.writeFile(statePath, JSON.stringify(state, null, 2), 'utf8'); - } - - // Schedule cleanup of old completed states - this.scheduleStateCleanup(); - } catch (error) { - this.logger.error(`Failed to mark deployment as completed: ${deploymentId}:`, error); - } - } - - /** - * Update deployment progress - */ - async updateDeploymentProgress( - deploymentId: string, - component: CursorComponentType, - status: 'started' | 'completed' | 'failed', - error?: DeploymentError - ): Promise { - this.logger.debug(`Updating deployment progress: ${deploymentId}, ${component}, ${status}`); - - try { - const state = await this.loadDeploymentState(deploymentId); - if (!state) { - this.logger.warn(`No state found for deployment: ${deploymentId}`); - return; - } - - // Update progress based on status - switch (status) { - case 'started': - if (!state.progress.inProgressComponents.includes(component)) { - state.progress.inProgressComponents.push(component); - } - break; - - case 'completed': - state.progress.inProgressComponents = state.progress.inProgressComponents - .filter(c => c !== component); - if (!state.progress.completedComponents.includes(component)) { - state.progress.completedComponents.push(component); - } - break; - - case 'failed': - state.progress.inProgressComponents = state.progress.inProgressComponents - .filter(c => c !== component); - if (!state.progress.failedComponents.includes(component)) { - state.progress.failedComponents.push(component); - } - if (error) { - state.progress.componentErrors[component] = error; - } - break; - } - - // Update overall status - this.updateOverallStatus(state); - - // Save updated state - const statePath = path.join(this.stateBasePath, `${deploymentId}.json`); - await fs.writeFile(statePath, JSON.stringify(state, null, 2), 'utf8'); - } catch (error) { - this.logger.error(`Failed to update deployment progress for ${deploymentId}:`, error); - } - } - - /** - * Clean up old deployment states - */ - async cleanupOldStates(maxAge: number = 7 * 24 * 60 * 60 * 1000): Promise { - this.logger.log('Cleaning up old deployment states...'); - - try { - const stateFiles = await fs.readdir(this.stateBasePath); - const cutoffTime = Date.now() - maxAge; - let cleaned = 0; - - for (const stateFile of stateFiles) { - if (!stateFile.endsWith('.json')) continue; - - try { - const statePath = path.join(this.stateBasePath, stateFile); - const stats = await fs.stat(statePath); - - if (stats.mtime.getTime() < cutoffTime) { - // Check if it's a completed deployment - const stateContent = await fs.readFile(statePath, 'utf8'); - const state: DeploymentState = JSON.parse(stateContent); - - if (state.status === 'completed' || state.status === 'failed') { - await fs.unlink(statePath); - cleaned++; - this.logger.debug(`Cleaned up old state: ${stateFile}`); - } - } - } catch (error) { - this.logger.warn(`Failed to process state file ${stateFile}:`, error); - } - } - - this.logger.log(`Cleaned up ${cleaned} old deployment states`); - } catch (error) { - this.logger.error('Failed to cleanup old states:', error); - } - } - - /** - * Check if deployment is interrupted - */ - private isDeploymentInterrupted(state: DeploymentState): boolean { - // Consider deployment interrupted if: - // 1. Status is 'in_progress' and it's been more than 30 minutes - // 2. There are components in progress but no recent activity - - if (state.status !== 'in_progress') { - return false; - } - - const timeSinceLastUpdate = Date.now() - new Date(state.timestamp).getTime(); - const maxInactiveTime = 30 * 60 * 1000; // 30 minutes - - // If it's been too long since last update, consider it interrupted - if (timeSinceLastUpdate > maxInactiveTime) { - return true; - } - - // If there are components in progress but deployment seems stuck - if (state.progress.inProgressComponents.length > 0 && timeSinceLastUpdate > 10 * 60 * 1000) { - return true; - } - - return false; - } - - /** - * Detect partially deployed components - */ - private detectPartialDeployments(state: DeploymentState): CursorComponentType[] { - // In a real implementation, this would check file system state - // For now, return components that were in progress when deployment was interrupted - return state.progress.inProgressComponents; - } - - /** - * Estimate recovery time - */ - private estimateRecoveryTime(plan: DeploymentRecoveryPlan): number { - // Rough estimation based on component count and action types - let estimatedSeconds = 0; - - for (const action of plan.recoveryActions) { - switch (action.type) { - case 'retry_failed': - estimatedSeconds += action.components.length * 30; // 30 seconds per failed component retry - break; - case 'complete_remaining': - estimatedSeconds += action.components.length * 15; // 15 seconds per remaining component - break; - case 'cleanup_partial': - estimatedSeconds += action.components.length * 10; // 10 seconds per partial cleanup - break; - } - } - - return Math.max(estimatedSeconds, 10); // Minimum 10 seconds - } - - /** - * Update overall deployment status - */ - private updateOverallStatus(state: DeploymentState): void { - const allComponents = state.options.components || this.getDefaultComponents(); - const completed = state.progress.completedComponents.length; - const failed = state.progress.failedComponents.length; - const total = allComponents.length; - - if (completed + failed >= total) { - state.status = failed > 0 ? 'failed' : 'completed'; - } else { - state.status = 'in_progress'; - } - - state.timestamp = new Date().toISOString(); // Update last activity time - } - - /** - * Get default components list - */ - private getDefaultComponents(): CursorComponentType[] { - return [ - 'global-settings', - 'project-settings', - 'ai-config', - 'extensions-config', - 'debug-config', - 'tasks-config', - 'snippets-config', - 'workspace-config', - ]; - } - - /** - * Schedule periodic cleanup of old states - */ - private scheduleStateCleanup(): void { - // In a real implementation, this might use a job scheduler - // For now, just run cleanup occasionally - if (Math.random() < 0.1) { // 10% chance - setTimeout(() => this.cleanupOldStates(), 1000); - } - } -} - -/** - * Deployment state information - */ -export interface DeploymentState { - deploymentId: string; - timestamp: string; - status: DeploymentStatus; - options: CursorDeploymentOptions; - progress: DeploymentProgress; - version: string; -} - -/** - * Deployment progress tracking - */ -export interface DeploymentProgress { - status: DeploymentStatus; - startedAt: string; - completedAt?: string; - completedComponents: CursorComponentType[]; - failedComponents: CursorComponentType[]; - inProgressComponents: CursorComponentType[]; - componentErrors: Record; - finalResult?: CursorDeploymentResult; -} - -/** - * Deployment status types - */ -export type DeploymentStatus = 'initializing' | 'in_progress' | 'completed' | 'failed' | 'interrupted'; - -/** - * Deployment recovery plan - */ -export interface DeploymentRecoveryPlan { - deploymentId: string; - originalOptions: CursorDeploymentOptions; - recoveryActions: RecoveryAction[]; - remainingComponents: CursorComponentType[]; - completedComponents: CursorComponentType[]; - failedComponents: CursorComponentType[]; - estimatedTimeRemaining: number; -} - -/** - * Recovery action - */ -export interface RecoveryAction { - type: 'retry_failed' | 'complete_remaining' | 'cleanup_partial'; - description: string; - components: CursorComponentType[]; - priority: 'low' | 'medium' | 'high'; -} \ No newline at end of file diff --git a/src/modules/deploy/services/cursor-deployment.service.ts b/src/modules/deploy/services/cursor-deployment.service.ts index 8d54b21..4853332 100644 --- a/src/modules/deploy/services/cursor-deployment.service.ts +++ b/src/modules/deploy/services/cursor-deployment.service.ts @@ -1,5 +1,8 @@ -import { Injectable, Logger } from '@nestjs/common'; import { Readable } from 'stream'; + +import { Injectable, Logger } from '@nestjs/common'; + +import { ALL_CURSOR_COMPONENT_TYPES } from '../constants/cursor.constants'; import { CursorDeploymentOptions, CursorDeploymentResult, @@ -8,14 +11,14 @@ import { } from '../interfaces/cursor-deployment.interface'; import { DeploymentError, DeploymentWarning } from '../interfaces/deployment-result.interface'; import { TaptikContext } from '../interfaces/taptik-context.interface'; -import { CursorTransformerService } from './cursor-transformer.service'; -import { CursorValidatorService } from './cursor-validator.service'; -import { CursorFileWriterService } from './cursor-file-writer.service'; -import { CursorInstallationDetectorService } from './cursor-installation-detector.service'; + import { CursorBackupService } from './cursor-backup.service'; import { CursorConflictResolverService } from './cursor-conflict-resolver.service'; -import { CursorDeploymentStateService } from './cursor-deployment-state.service'; -import { ALL_CURSOR_COMPONENT_TYPES } from '../constants/cursor.constants'; +import { CursorFileWriterService } from './cursor-file-writer.service'; +import { CursorInstallationDetectorService } from './cursor-installation-detector.service'; +import { CursorTransformerService } from './cursor-transformer.service'; +import { CursorValidatorService } from './cursor-validator.service'; + /** * Task 6.1: Main Cursor deployment service orchestration @@ -34,10 +37,9 @@ export class CursorDeploymentService implements ICursorDeploymentService { private readonly transformerService: CursorTransformerService, private readonly validatorService: CursorValidatorService, private readonly fileWriterService: CursorFileWriterService, - private readonly installationDetectorService: CursorInstallationDetectorService, private readonly backupService: CursorBackupService, private readonly conflictResolver: CursorConflictResolverService, - private readonly stateManager: CursorDeploymentStateService, + private readonly installationDetectorService: CursorInstallationDetectorService, ) {} /** @@ -88,15 +90,6 @@ export class CursorDeploymentService implements ICursorDeploymentService { // Step 3: Initialize deployment state tracking this.logger.log('Step 3: Initializing deployment state...'); const componentTypes = this.getComponentTypesToDeploy(options); - - await this.stateManager.saveDeploymentState(this.generateDeploymentId(), deploymentOptionsWithPath, { - status: 'initializing', - startedAt: new Date().toISOString(), - completedComponents: [], - failedComponents: [], - inProgressComponents: [], - componentErrors: {}, - }); // Step 4: Create backup before deployment this.logger.log('Step 4: Creating backup...'); @@ -121,6 +114,7 @@ export class CursorDeploymentService implements ICursorDeploymentService { this.logger.log('Step 5: Processing components with parallel deployment optimization...'); // Task 6.3: Implement parallel component deployment where safe + // FIXME: transformer가 없음 const { parallelComponents, sequentialComponents } = this.categorizeComponentsForDeployment(componentTypes); // Deploy parallel-safe components first @@ -291,7 +285,7 @@ export class CursorDeploymentService implements ICursorDeploymentService { configurationFiles, errors, warnings, - deploymentId: 'preview-' + this.generateDeploymentId(), + deploymentId: `preview-${ this.generateDeploymentId()}`, timestamp: new Date().toISOString(), preview: true, }; @@ -649,7 +643,8 @@ export class CursorDeploymentService implements ICursorDeploymentService { this.logger.error('Installation detector service not available'); return null; } - return await this.installationDetectorService.detectCursorInstallation(); + const installationInfo = await this.installationDetectorService.detectCursorInstallation(); + return installationInfo.installationPath; } catch (error) { this.logger.error('Failed to detect Cursor installation:', error); return null; diff --git a/src/modules/deploy/services/deployment.service.ts b/src/modules/deploy/services/deployment.service.ts index 8f983e6..8df676d 100644 --- a/src/modules/deploy/services/deployment.service.ts +++ b/src/modules/deploy/services/deployment.service.ts @@ -1351,7 +1351,7 @@ export class DeploymentService { async deployToCursor( context: TaptikContext, options: DeployOptions, - ): Promise { + ): Promise { // FIXME: ide별로 result type이 다름 // Convert DeployOptions to CursorDeploymentOptions const cursorOptions = this.convertToCursorDeploymentOptions(context, options);