UNPKG

codestate-core

Version:

Core domain models, use cases, and services for CodeState

4 lines 262 kB
{ "version": 3, "sources": ["../index.ts", "../domain/models/Result.ts", "../services/config/ConfigService.ts", "../domain/schemas/SchemaRegistry.ts", "../domain/types/ErrorTypes.ts", "../infrastructure/repositories/ConfigRepository.ts", "../infrastructure/services/FileLogger.ts", "../infrastructure/services/BasicEncryption.ts", "../services/config/ConfigFacade.ts", "../use-cases/config/GetConfig.ts", "../use-cases/config/UpdateConfig.ts", "../use-cases/config/ResetConfig.ts", "../use-cases/config/ExportConfig.ts", "../use-cases/config/ImportConfig.ts", "../services/scripts/ScriptService.ts", "../infrastructure/repositories/ScriptRepository.ts", "../services/scripts/ScriptFacade.ts", "../use-cases/scripts/CreateScript.ts", "../use-cases/scripts/CreateScripts.ts", "../use-cases/scripts/GetScripts.ts", "../use-cases/scripts/GetScriptsByRootPath.ts", "../use-cases/scripts/UpdateScript.ts", "../use-cases/scripts/DeleteScript.ts", "../use-cases/scripts/DeleteScriptsByRootPath.ts", "../use-cases/scripts/ExportScripts.ts", "../use-cases/scripts/ImportScripts.ts", "../services/session/SessionService.ts", "../infrastructure/repositories/SessionRepository.ts", "../infrastructure/services/FileStorage.ts", "../services/session/SessionFacade.ts", "../use-cases/session/SaveSession.ts", "../use-cases/session/UpdateSession.ts", "../use-cases/session/ResumeSession.ts", "../use-cases/session/ListSessions.ts", "../use-cases/session/DeleteSession.ts", "../services/git/GitService.ts", "../infrastructure/services/Terminal/TerminalService.ts", "../services/git/GitFacade.ts", "../use-cases/git/GetGitStatus.ts", "../use-cases/git/GetIsDirty.ts", "../use-cases/git/GetDirtyData.ts", "../use-cases/git/CreateStash.ts", "../use-cases/git/ApplyStash.ts", "../use-cases/git/ListStashes.ts", "../use-cases/git/DeleteStash.ts", "../use-cases/git/GetCurrentCommit.ts", "../use-cases/git/CommitChanges.ts", "../services/ide/IDEService.ts", "../infrastructure/repositories/IDERepository.ts", "../infrastructure/services/Terminal/TerminalFacade.ts", "../services/ide/IDEFacade.ts", "../use-cases/ide/OpenIDE.ts", "../use-cases/ide/OpenFiles.ts", "../use-cases/ide/GetAvailableIDEs.ts", "../domain/types/ErrorRegistry.ts", "../infrastructure/services/CLILogger/CLILogger.ts", "../infrastructure/services/CLILogger/CLILoggerFacade.ts"], "sourcesContent": ["// API entry point for external consumption (no CLI dependencies)\r\n// Core domain models\r\nexport * from '@codestate/core/domain/models/Session';\r\nexport * from '@codestate/core/domain/models/Script';\r\nexport * from '@codestate/core/domain/models/Config';\r\nexport * from '@codestate/core/domain/models/Result';\r\n\r\n// Use cases\r\nexport * from '@codestate/core/use-cases/config';\r\nexport * from '@codestate/core/use-cases/scripts';\r\nexport * from '@codestate/core/use-cases/session';\r\nexport * from '@codestate/core/use-cases/git';\r\nexport * from '@codestate/core/use-cases/ide';\r\n\r\n// Error types and registry\r\nexport * from '@codestate/core/domain/types/ErrorTypes';\r\nexport * from '@codestate/core/domain/types/ErrorRegistry';\r\n\r\n// Services (with aliases for cleaner API)\r\nexport { CLILoggerFacade as ConfigurableLogger } from '@codestate/infrastructure/services/CLILogger/CLILoggerFacade';\r\nexport { GitFacade as GitService } from '@codestate/core/services/git/GitFacade';\r\nexport { TerminalFacade as Terminal } from '@codestate/infrastructure/services/Terminal/TerminalFacade';\r\nexport { IDEFacade as IDEService } from '@codestate/core/services/ide/IDEFacade'; ", "export type Result<T, E = Error> = Success<T> | Failure<E>;\r\n\r\nexport interface Success<T> {\r\n ok: true;\r\n value: T;\r\n}\r\n\r\nexport interface Failure<E> {\r\n ok: false;\r\n error: E;\r\n}\r\n\r\n// Type guard functions\r\nexport function isSuccess<T, E = Error>(result: Result<T, E>): result is Success<T> {\r\n return result.ok === true;\r\n}\r\n\r\nexport function isFailure<T, E = Error>(result: Result<T, E>): result is Failure<E> {\r\n return result.ok === false;\r\n} ", "import { IConfigService, IConfigRepository } from '@codestate/core/domain/ports/IConfigService';\r\nimport { Config } from '@codestate/core/domain/models/Config';\r\nimport { Result, isSuccess, isFailure } from '@codestate/core/domain/models/Result';\r\nimport { ILoggerService } from '@codestate/core/domain/ports/ILoggerService';\r\n\r\nexport class ConfigService implements IConfigService {\r\n constructor(\r\n private repository: IConfigRepository,\r\n private logger: ILoggerService\r\n ) {}\r\n\r\n async getConfig(): Promise<Result<Config>> {\r\n this.logger.debug('ConfigService.getConfig called');\r\n const result = await this.repository.load();\r\n if (isFailure(result)) {\r\n this.logger.error('Failed to get config', { error: result.error });\r\n } else {\r\n this.logger.log('Config loaded', {});\r\n }\r\n return result;\r\n }\r\n\r\n async setConfig(config: Config): Promise<Result<void>> {\r\n this.logger.debug('ConfigService.setConfig called');\r\n const result = await this.repository.save(config);\r\n if (isFailure(result)) {\r\n this.logger.error('Failed to save config', { error: result.error });\r\n } else {\r\n this.logger.log('Config saved', {});\r\n }\r\n return result;\r\n }\r\n\r\n async updateConfig(partial: Partial<Config>): Promise<Result<Config>> {\r\n this.logger.debug('ConfigService.updateConfig called', { partial });\r\n const current = await this.repository.load();\r\n if (isFailure(current)) {\r\n this.logger.error('Failed to load config for update', { error: current.error });\r\n return current;\r\n }\r\n const merged = { ...current.value, ...partial };\r\n // TODO: Add validation/migration logic if needed\r\n const saveResult = await this.repository.save(merged);\r\n if (isFailure(saveResult)) {\r\n this.logger.error('Failed to save updated config', { error: saveResult.error });\r\n return { ok: false, error: saveResult.error };\r\n }\r\n this.logger.log('Config updated', {});\r\n return { ok: true, value: merged };\r\n }\r\n} ", "import { z } from 'zod';\r\n\r\n// Core schemas for configuration and validation\r\nexport const LogLevelSchema = z.enum(['ERROR', 'WARN', 'LOG', 'DEBUG']);\r\n\r\nexport const LoggerConfigSchema = z.object({\r\n level: LogLevelSchema,\r\n sinks: z.array(z.enum(['console', 'file'])),\r\n filePath: z.string().optional(),\r\n});\r\n\r\nexport const FileStorageConfigSchema = z.object({\r\n encryptionEnabled: z.boolean(),\r\n encryptionKey: z.string().optional(),\r\n dataDir: z.string(),\r\n});\r\n\r\nexport const FeatureFlagsSchema = z.object({\r\n experimentalTui: z.boolean(),\r\n experimentalIde: z.boolean(),\r\n advancedSearch: z.boolean(),\r\n cloudSync: z.boolean(),\r\n});\r\n\r\nexport const PluginEnvironmentSchema = z.enum(['cli', 'tui', 'ide']);\r\n\r\nexport const ErrorCodeSchema = z.enum([\r\n 'UNKNOWN',\r\n 'CONFIG_INVALID',\r\n 'STORAGE_INVALID_PATH',\r\n 'STORAGE_DECRYPTION_FAILED',\r\n 'STORAGE_READ_FAILED',\r\n 'STORAGE_WRITE_FAILED',\r\n 'STORAGE_DELETE_FAILED',\r\n 'ENCRYPTION_FAILED',\r\n 'ENCRYPTION_INVALID_FORMAT',\r\n 'SCRIPT_INVALID',\r\n 'SCRIPT_DUPLICATE',\r\n 'SCRIPT_NOT_FOUND',\r\n 'SCRIPT_PATH_INVALID',\r\n 'SCRIPT_MALICIOUS',\r\n 'GIT_NOT_REPOSITORY',\r\n 'GIT_COMMAND_FAILED',\r\n 'GIT_STASH_NOT_FOUND',\r\n 'GIT_STASH_CONFLICT',\r\n 'TERMINAL_COMMAND_FAILED',\r\n 'TERMINAL_TIMEOUT',\r\n 'TERMINAL_COMMAND_NOT_FOUND',\r\n]);\r\n\r\nexport const EncryptionConfigSchema = z.object({\r\n enabled: z.boolean(),\r\n encryptionKey: z.string().optional(),\r\n});\r\n\r\nexport const ConfigSchema = z.object({\r\n version: z.string(),\r\n ide: z.string(),\r\n encryption: EncryptionConfigSchema,\r\n storagePath: z.string(),\r\n logger: LoggerConfigSchema,\r\n experimental: z.record(z.string(), z.boolean()).optional(),\r\n extensions: z.record(z.string(), z.unknown()).optional(),\r\n});\r\n\r\nexport const ScriptSchema = z.object({\r\n name: z.string().min(1, 'Script name is required'),\r\n rootPath: z.string().min(1, 'Root path is required'),\r\n script: z.string().min(1, 'Script command is required'),\r\n});\r\n\r\nexport const ScriptIndexEntrySchema = z.object({\r\n rootPath: z.string().min(1, 'Root path is required'),\r\n referenceFile: z.string().min(1, 'Reference file path is required'),\r\n});\r\n\r\nexport const ScriptIndexSchema = z.object({\r\n entries: z.array(ScriptIndexEntrySchema),\r\n});\r\n\r\nexport const ScriptCollectionSchema = z.object({\r\n scripts: z.array(ScriptSchema),\r\n});\r\n\r\n// Git schemas\r\nexport const GitFileStatusSchema = z.enum(['modified', 'added', 'deleted', 'untracked', 'renamed', 'copied', 'updated']);\r\n\r\nexport const GitFileSchema = z.object({\r\n path: z.string(),\r\n status: GitFileStatusSchema,\r\n staged: z.boolean(),\r\n});\r\n\r\nexport const GitStatusSchema = z.object({\r\n isDirty: z.boolean(),\r\n dirtyFiles: z.array(GitFileSchema),\r\n newFiles: z.array(GitFileSchema),\r\n modifiedFiles: z.array(GitFileSchema),\r\n deletedFiles: z.array(GitFileSchema),\r\n untrackedFiles: z.array(GitFileSchema),\r\n});\r\n\r\nexport const GitStashSchema = z.object({\r\n id: z.string(),\r\n name: z.string(),\r\n message: z.string(),\r\n timestamp: z.number(),\r\n branch: z.string(),\r\n});\r\n\r\nexport const GitStashResultSchema = z.object({\r\n success: z.boolean(),\r\n stashId: z.string().optional(),\r\n error: z.string().optional(),\r\n});\r\n\r\nexport const GitStashApplyResultSchema = z.object({\r\n success: z.boolean(),\r\n conflicts: z.array(z.string()).optional(),\r\n error: z.string().optional(),\r\n});\r\n\r\n// Terminal schemas\r\nexport const TerminalCommandSchema = z.object({\r\n command: z.string(),\r\n args: z.array(z.string()).optional(),\r\n cwd: z.string().optional(),\r\n env: z.record(z.string(), z.string()).optional(),\r\n timeout: z.number().optional(),\r\n});\r\n\r\nexport const TerminalResultSchema = z.object({\r\n success: z.boolean(),\r\n exitCode: z.number(),\r\n stdout: z.string(),\r\n stderr: z.string(),\r\n duration: z.number(),\r\n error: z.string().optional(),\r\n});\r\n\r\nexport const TerminalOptionsSchema = z.object({\r\n cwd: z.string().optional(),\r\n env: z.record(z.string(), z.string()).optional(),\r\n timeout: z.number().optional(),\r\n shell: z.string().optional(),\r\n});\r\n\r\n// Session schemas\r\nexport const FileStateSchema = z.object({\r\n path: z.string(),\r\n cursor: z\r\n .object({ line: z.number(), column: z.number() })\r\n .optional(),\r\n scroll: z\r\n .object({ top: z.number(), left: z.number() })\r\n .optional(),\r\n isActive: z.boolean(),\r\n});\r\n\r\nexport const GitStateSchema = z.object({\r\n branch: z.string(),\r\n commit: z.string(),\r\n isDirty: z.boolean(),\r\n stashId: z.string().nullable().optional(),\r\n});\r\n\r\nexport const SessionSchema = z.object({\r\n id: z.string(),\r\n name: z.string(),\r\n projectRoot: z.string(),\r\n createdAt: z.union([z.string(), z.date()]).transform(val => typeof val === 'string' ? new Date(val) : val),\r\n updatedAt: z.union([z.string(), z.date()]).transform(val => typeof val === 'string' ? new Date(val) : val),\r\n tags: z.array(z.string()),\r\n notes: z.string().optional(),\r\n files: z.array(FileStateSchema),\r\n git: GitStateSchema,\r\n extensions: z.record(z.string(), z.unknown()).optional(),\r\n});\r\n\r\n// Session index entry schema\r\nexport const SessionIndexEntrySchema = z.object({\r\n id: z.string(),\r\n name: z.string(),\r\n projectRoot: z.string(),\r\n createdAt: z.union([z.string(), z.date()]),\r\n updatedAt: z.union([z.string(), z.date()]),\r\n tags: z.array(z.string()),\r\n notes: z.string().optional(),\r\n referenceFile: z.string(),\r\n});\r\n\r\nexport const SessionIndexSchema = z.object({\r\n version: z.string(),\r\n sessions: z.array(SessionIndexEntrySchema),\r\n});\r\n\r\n// Schema registry for easy access and validation\r\nexport const SchemaRegistry = {\r\n LogLevel: LogLevelSchema,\r\n LoggerConfig: LoggerConfigSchema,\r\n FileStorageConfig: FileStorageConfigSchema,\r\n FeatureFlags: FeatureFlagsSchema,\r\n PluginEnvironment: PluginEnvironmentSchema,\r\n ErrorCode: ErrorCodeSchema,\r\n Config: ConfigSchema,\r\n Script: ScriptSchema,\r\n ScriptIndexEntry: ScriptIndexEntrySchema,\r\n ScriptIndex: ScriptIndexSchema,\r\n ScriptCollection: ScriptCollectionSchema,\r\n GitFileStatus: GitFileStatusSchema,\r\n GitFile: GitFileSchema,\r\n GitStatus: GitStatusSchema,\r\n GitStash: GitStashSchema,\r\n GitStashResult: GitStashResultSchema,\r\n GitStashApplyResult: GitStashApplyResultSchema,\r\n TerminalCommand: TerminalCommandSchema,\r\n TerminalResult: TerminalResultSchema,\r\n TerminalOptions: TerminalOptionsSchema,\r\n FileState: FileStateSchema,\r\n GitState: GitStateSchema,\r\n Session: SessionSchema,\r\n SessionIndexEntry: SessionIndexEntrySchema,\r\n SessionIndex: SessionIndexSchema,\r\n} as const;\r\n\r\n// Type exports for use in TypeScript\r\nexport type LogLevel = z.infer<typeof LogLevelSchema>;\r\nexport type LoggerConfig = z.infer<typeof LoggerConfigSchema>;\r\nexport type FileStorageConfig = z.infer<typeof FileStorageConfigSchema>;\r\nexport type FeatureFlags = z.infer<typeof FeatureFlagsSchema>;\r\nexport type PluginEnvironment = z.infer<typeof PluginEnvironmentSchema>;\r\nexport type ErrorCode = z.infer<typeof ErrorCodeSchema>;\r\nexport type Config = z.infer<typeof ConfigSchema>;\r\nexport type Script = z.infer<typeof ScriptSchema>;\r\nexport type ScriptIndexEntry = z.infer<typeof ScriptIndexEntrySchema>;\r\nexport type ScriptIndex = z.infer<typeof ScriptIndexSchema>;\r\nexport type ScriptCollection = z.infer<typeof ScriptCollectionSchema>;\r\nexport type GitFileStatus = z.infer<typeof GitFileStatusSchema>;\r\nexport type GitFile = z.infer<typeof GitFileSchema>;\r\nexport type GitStatus = z.infer<typeof GitStatusSchema>;\r\nexport type GitStash = z.infer<typeof GitStashSchema>;\r\nexport type GitStashResult = z.infer<typeof GitStashResultSchema>;\r\nexport type GitStashApplyResult = z.infer<typeof GitStashApplyResultSchema>;\r\nexport type TerminalCommand = z.infer<typeof TerminalCommandSchema>;\r\nexport type TerminalResult = z.infer<typeof TerminalResultSchema>;\r\nexport type TerminalOptions = z.infer<typeof TerminalOptionsSchema>;\r\nexport type FileState = z.infer<typeof FileStateSchema>;\r\nexport type GitState = z.infer<typeof GitStateSchema>;\r\nexport type Session = z.infer<typeof SessionSchema>;\r\nexport type SessionIndexEntry = z.infer<typeof SessionIndexEntrySchema>;\r\nexport type SessionIndex = z.infer<typeof SessionIndexSchema>;\r\n\r\n// Validation helpers\r\nexport function validateLoggerConfig(data: unknown): LoggerConfig {\r\n return LoggerConfigSchema.parse(data);\r\n}\r\n\r\nexport function validateFileStorageConfig(data: unknown): FileStorageConfig {\r\n return FileStorageConfigSchema.parse(data);\r\n}\r\n\r\nexport function validateFeatureFlags(data: unknown): FeatureFlags {\r\n return FeatureFlagsSchema.parse(data);\r\n}\r\n\r\nexport function validateConfig(data: unknown): Config {\r\n return ConfigSchema.parse(data);\r\n}\r\n\r\nexport function validateScript(data: unknown): Script {\r\n return ScriptSchema.parse(data);\r\n}\r\n\r\nexport function validateScriptIndex(data: unknown): ScriptIndex {\r\n return ScriptIndexSchema.parse(data);\r\n}\r\n\r\nexport function validateScriptCollection(data: unknown): ScriptCollection {\r\n return ScriptCollectionSchema.parse(data);\r\n}\r\n\r\nexport function validateGitStatus(data: unknown): GitStatus {\r\n return GitStatusSchema.parse(data);\r\n}\r\n\r\nexport function validateGitStash(data: unknown): GitStash {\r\n return GitStashSchema.parse(data);\r\n}\r\n\r\nexport function validateTerminalCommand(data: unknown): TerminalCommand {\r\n return TerminalCommandSchema.parse(data);\r\n}\r\n\r\nexport function validateTerminalResult(data: unknown): TerminalResult {\r\n return TerminalResultSchema.parse(data);\r\n}\r\n\r\nexport function validateSession(data: unknown): Session {\r\n return SessionSchema.parse(data);\r\n}\r\n\r\nexport function validateSessionIndex(data: unknown): SessionIndex {\r\n return SessionIndexSchema.parse(data);\r\n} ", "export enum ErrorCode {\r\n UNKNOWN = 'UNKNOWN',\r\n CONFIG_INVALID = 'CONFIG_INVALID',\r\n STORAGE_INVALID_PATH = 'STORAGE_INVALID_PATH',\r\n STORAGE_DECRYPTION_FAILED = 'STORAGE_DECRYPTION_FAILED',\r\n STORAGE_READ_FAILED = 'STORAGE_READ_FAILED',\r\n STORAGE_WRITE_FAILED = 'STORAGE_WRITE_FAILED',\r\n STORAGE_DELETE_FAILED = 'STORAGE_DELETE_FAILED',\r\n ENCRYPTION_FAILED = 'ENCRYPTION_FAILED',\r\n ENCRYPTION_INVALID_FORMAT = 'ENCRYPTION_INVALID_FORMAT',\r\n SCRIPT_INVALID = 'SCRIPT_INVALID',\r\n SCRIPT_DUPLICATE = 'SCRIPT_DUPLICATE',\r\n SCRIPT_NOT_FOUND = 'SCRIPT_NOT_FOUND',\r\n SCRIPT_PATH_INVALID = 'SCRIPT_PATH_INVALID',\r\n SCRIPT_MALICIOUS = 'SCRIPT_MALICIOUS',\r\n GIT_NOT_REPOSITORY = 'GIT_NOT_REPOSITORY',\r\n GIT_COMMAND_FAILED = 'GIT_COMMAND_FAILED',\r\n GIT_STASH_NOT_FOUND = 'GIT_STASH_NOT_FOUND',\r\n GIT_STASH_CONFLICT = 'GIT_STASH_CONFLICT',\r\n TERMINAL_COMMAND_FAILED = 'TERMINAL_COMMAND_FAILED',\r\n TERMINAL_TIMEOUT = 'TERMINAL_TIMEOUT',\r\n TERMINAL_COMMAND_NOT_FOUND = 'TERMINAL_COMMAND_NOT_FOUND',\r\n}\r\n\r\nexport interface StandardizedErrorShape {\r\n code: ErrorCode;\r\n name: string;\r\n message: string;\r\n meta?: Record<string, unknown>;\r\n}\r\n\r\nexport class AppError extends Error implements StandardizedErrorShape {\r\n code: ErrorCode;\r\n meta?: Record<string, unknown>;\r\n constructor(message: string, code: ErrorCode = ErrorCode.UNKNOWN, meta?: Record<string, unknown>) {\r\n super(message);\r\n this.name = 'AppError';\r\n this.code = code;\r\n this.meta = meta;\r\n }\r\n}\r\n\r\nexport class ConfigError extends AppError {\r\n constructor(message: string, meta?: Record<string, unknown>) {\r\n super(message, ErrorCode.CONFIG_INVALID, meta);\r\n this.name = 'ConfigError';\r\n }\r\n}\r\n\r\nexport class StorageError extends AppError {\r\n constructor(message: string, code: ErrorCode = ErrorCode.STORAGE_READ_FAILED, meta?: Record<string, unknown>) {\r\n super(message, code, meta);\r\n this.name = 'StorageError';\r\n }\r\n}\r\n\r\nexport class EncryptionError extends AppError {\r\n constructor(message: string, code: ErrorCode = ErrorCode.ENCRYPTION_FAILED, meta?: Record<string, unknown>) {\r\n super(message, code, meta);\r\n this.name = 'EncryptionError';\r\n }\r\n}\r\n\r\nexport class ScriptError extends AppError {\r\n constructor(message: string, code: ErrorCode = ErrorCode.SCRIPT_INVALID, meta?: Record<string, unknown>) {\r\n super(message, code, meta);\r\n this.name = 'ScriptError';\r\n }\r\n}\r\n\r\nexport class GitError extends AppError {\r\n constructor(message: string, code: ErrorCode = ErrorCode.GIT_COMMAND_FAILED, meta?: Record<string, unknown>) {\r\n super(message, code, meta);\r\n this.name = 'GitError';\r\n }\r\n}\r\n\r\nexport class TerminalError extends AppError {\r\n constructor(message: string, code: ErrorCode = ErrorCode.TERMINAL_COMMAND_FAILED, meta?: Record<string, unknown>) {\r\n super(message, code, meta);\r\n this.name = 'TerminalError';\r\n }\r\n} ", "import { Config } from \"@codestate/core/domain/models/Config\";\r\nimport { Result, isFailure } from \"@codestate/core/domain/models/Result\";\r\nimport { IConfigRepository } from \"@codestate/core/domain/ports/IConfigService\";\r\nimport { IEncryptionService } from \"@codestate/core/domain/ports/IEncryptionService\";\r\nimport { ILoggerService } from \"@codestate/core/domain/ports/ILoggerService\";\r\nimport { validateConfig } from \"@codestate/core/domain/schemas/SchemaRegistry\";\r\nimport { ConfigError } from \"@codestate/core/domain/types/ErrorTypes\";\r\nimport * as fs from \"fs/promises\";\r\nimport * as path from \"path\";\r\n\r\nconst DEFAULT_CONFIG_PATH = path.join(\r\n process.env.HOME || process.env.USERPROFILE || \".\",\r\n \".codestate\",\r\n \"config.json\"\r\n);\r\nconst TEMP_SUFFIX = \".tmp\";\r\nconst BACKUP_SUFFIX = \".bak\";\r\n\r\nfunction getDefaultConfig(): Config {\r\n return {\r\n version: \"1.0.0\",\r\n ide: \"vscode\",\r\n encryption: { enabled: false },\r\n storagePath: path.join(\r\n process.env.HOME || process.env.USERPROFILE || \".\",\r\n \".codestate\"\r\n ),\r\n logger: {\r\n level: \"LOG\",\r\n sinks: [\"file\"],\r\n filePath: path.join(\r\n process.env.HOME || process.env.USERPROFILE || \".\",\r\n \".codestate\",\r\n \"logs\",\r\n \"codestate.log\"\r\n ),\r\n },\r\n experimental: {},\r\n extensions: {},\r\n };\r\n}\r\n\r\nexport class ConfigRepository implements IConfigRepository {\r\n constructor(\r\n private logger: ILoggerService,\r\n private encryption: IEncryptionService,\r\n private configPath: string = DEFAULT_CONFIG_PATH\r\n ) {}\r\n\r\n async load(): Promise<Result<Config>> {\r\n try {\r\n await this.ensureDir();\r\n this.logger.debug(\"Attempting to load config\", { path: this.configPath });\r\n const raw = await fs.readFile(this.configPath, { encoding: \"utf8\" });\r\n let data = raw;\r\n // Try decrypt if header present or config says so\r\n if (raw.startsWith(\"ENCRYPTED_v1\")) {\r\n this.logger.log(\"Config file is encrypted. Attempting decryption.\", {\r\n path: this.configPath,\r\n });\r\n // Prompt for key or use env/config (not interactive here)\r\n // For now, try with empty key and fail gracefully\r\n const key = \"\";\r\n const decrypted = await this.encryption.decrypt(raw, key);\r\n if (isFailure(decrypted)) {\r\n this.logger.error(\"Decryption failed\", { error: decrypted.error });\r\n return { ok: false, error: decrypted.error };\r\n }\r\n data = decrypted.value;\r\n }\r\n let parsed;\r\n try {\r\n parsed = JSON.parse(data);\r\n } catch (parseErr) {\r\n this.logger.error(\r\n \"Config file is corrupt (invalid JSON). Backing up and creating default.\",\r\n { path: this.configPath }\r\n );\r\n await this.backupCorruptConfig();\r\n const defaults = getDefaultConfig();\r\n await this.save(defaults);\r\n return { ok: true, value: defaults };\r\n }\r\n let config;\r\n try {\r\n config = validateConfig(parsed);\r\n } catch (validationErr) {\r\n this.logger.error(\r\n \"Config file is corrupt (schema validation failed). Backing up and creating default.\",\r\n { path: this.configPath }\r\n );\r\n await this.backupCorruptConfig();\r\n const defaults = getDefaultConfig();\r\n await this.save(defaults);\r\n return { ok: true, value: defaults };\r\n }\r\n this.logger.log(\"Config loaded successfully\", {\r\n path: this.configPath,\r\n encrypted: raw.startsWith(\"ENCRYPTED_v1\"),\r\n });\r\n return { ok: true, value: config };\r\n } catch (err: any) {\r\n if (err.code === \"ENOENT\") {\r\n this.logger.warn(\"Config file not found. Creating default config.\", {\r\n path: this.configPath,\r\n });\r\n const defaults = getDefaultConfig();\r\n await this.save(defaults);\r\n return { ok: true, value: defaults };\r\n }\r\n this.logger.error(\"Failed to load config\", {\r\n error: err.message,\r\n path: this.configPath,\r\n });\r\n return { ok: false, error: new ConfigError(err.message) };\r\n }\r\n }\r\n\r\n async save(config: Config): Promise<Result<void>> {\r\n try {\r\n await this.ensureDir();\r\n this.logger.debug(\"Attempting to save config\", { path: this.configPath });\r\n const validated = validateConfig(config);\r\n let data = JSON.stringify(validated, null, 2);\r\n let encrypted = false;\r\n if (config.encryption?.enabled && config.encryption.encryptionKey) {\r\n this.logger.log(\"Encrypting config before save\", {\r\n path: this.configPath,\r\n });\r\n const encResult = await this.encryption.encrypt(\r\n data,\r\n config.encryption.encryptionKey\r\n );\r\n if (isFailure(encResult)) {\r\n this.logger.error(\"Encryption failed\", { error: encResult.error });\r\n return { ok: false, error: encResult.error };\r\n }\r\n data = encResult.value;\r\n encrypted = true;\r\n }\r\n // Write to temp file\r\n const tempPath = this.configPath + TEMP_SUFFIX;\r\n await fs.writeFile(tempPath, data, { encoding: \"utf8\", mode: 0o600 });\r\n this.logger.debug(\"Temp config file written\", { tempPath });\r\n await fs\r\n .rename(this.configPath, this.configPath + BACKUP_SUFFIX)\r\n .then(() => {\r\n this.logger.log(\"Config backup created\", {\r\n backupPath: this.configPath + BACKUP_SUFFIX,\r\n });\r\n })\r\n .catch(() => {}); // backup old\r\n await fs.rename(tempPath, this.configPath); // atomic replace\r\n this.logger.log(\"Config saved successfully\", {\r\n path: this.configPath,\r\n encrypted,\r\n });\r\n return { ok: true, value: undefined };\r\n } catch (err: any) {\r\n this.logger.error(\"Failed to save config\", {\r\n error: err.message,\r\n path: this.configPath,\r\n });\r\n return { ok: false, error: new ConfigError(err.message) };\r\n }\r\n }\r\n\r\n private async ensureDir() {\r\n const dir = path.dirname(this.configPath);\r\n await fs\r\n .mkdir(dir, { recursive: true, mode: 0o700 })\r\n .then(() => {\r\n this.logger.debug(\"Ensured config directory exists\", { dir });\r\n })\r\n .catch(() => {});\r\n }\r\n\r\n private async backupCorruptConfig() {\r\n try {\r\n const backupPath = this.configPath + \".bak.\" + Date.now();\r\n await fs.rename(this.configPath, backupPath);\r\n this.logger.warn(\"Backed up corrupt config file\", { backupPath });\r\n } catch (err: any) {\r\n this.logger.error(\"Failed to backup corrupt config file\", {\r\n error: err.message,\r\n });\r\n }\r\n }\r\n}\r\n", "import { ILoggerService } from '@codestate/core/domain/ports/ILoggerService';\r\nimport { LoggerConfig, LogLevel } from '@codestate/core/domain/schemas/SchemaRegistry';\r\nimport { appendFileSync, mkdirSync } from 'fs';\r\nimport * as path from 'path';\r\n\r\nconst LOG_LEVEL_PRIORITY = {\r\n 'ERROR': 0,\r\n 'WARN': 1,\r\n 'LOG': 2,\r\n 'DEBUG': 3,\r\n} as const;\r\n\r\nexport class FileLogger implements ILoggerService {\r\n private level: LogLevel;\r\n private filePath: string;\r\n\r\n constructor(config: LoggerConfig) {\r\n if (!config.filePath) throw new Error('FileLogger requires filePath in LoggerConfig');\r\n this.level = config.level;\r\n this.filePath = config.filePath;\r\n this.ensureLogDirectory();\r\n }\r\n plainLog(message: string, meta?: Record<string, unknown>): void {\r\n const entry = {\r\n level: 'plain',\r\n timestamp: new Date().toISOString(),\r\n message,\r\n ...(meta ? { meta } : {})\r\n };\r\n appendFileSync(this.filePath, JSON.stringify(entry) + '\\n', { encoding: 'utf8' });\r\n }\r\n\r\n private ensureLogDirectory(): void {\r\n const logDir = path.dirname(this.filePath);\r\n try {\r\n mkdirSync(logDir, { recursive: true });\r\n } catch (error) {\r\n // Directory might already exist, which is fine\r\n }\r\n }\r\n\r\n private shouldLog(messageLevel: LogLevel): boolean {\r\n return LOG_LEVEL_PRIORITY[this.level] >= LOG_LEVEL_PRIORITY[messageLevel];\r\n }\r\n\r\n private write(level: string, message: string, meta?: Record<string, unknown>) {\r\n const entry = {\r\n level,\r\n timestamp: new Date().toISOString(),\r\n message,\r\n ...(meta ? { meta } : {})\r\n };\r\n appendFileSync(this.filePath, JSON.stringify(entry) + '\\n', { encoding: 'utf8' });\r\n }\r\n\r\n log(message: string, meta?: Record<string, unknown>): void {\r\n if (!this.shouldLog('LOG')) return;\r\n this.write('log', message, meta);\r\n }\r\n error(message: string, meta?: Record<string, unknown>): void {\r\n if (!this.shouldLog('ERROR')) return;\r\n this.write('error', message, meta);\r\n }\r\n warn(message: string, meta?: Record<string, unknown>): void {\r\n if (!this.shouldLog('WARN')) return;\r\n this.write('warn', message, meta);\r\n }\r\n debug(message: string, meta?: Record<string, unknown>): void {\r\n if (!this.shouldLog('DEBUG')) return;\r\n this.write('debug', message, meta);\r\n }\r\n} ", "import { Result } from \"@codestate/core/domain/models/Result\";\r\nimport { IEncryptionService } from \"@codestate/core/domain/ports/IEncryptionService\";\r\nimport { ILoggerService } from \"@codestate/core/domain/ports/ILoggerService\";\r\nimport {\r\n EncryptionError,\r\n ErrorCode,\r\n} from \"@codestate/core/domain/types/ErrorTypes\";\r\nimport {\r\n createCipheriv,\r\n createDecipheriv,\r\n pbkdf2Sync,\r\n randomBytes,\r\n} from \"crypto\";\r\n\r\nconst HEADER = \"ENCRYPTED_v1\";\r\nconst SALT_LENGTH = 16;\r\nconst IV_LENGTH = 12;\r\nconst KEY_LENGTH = 32; // 256 bits\r\nconst PBKDF2_ITER = 100_000;\r\n\r\nexport class BasicEncryption implements IEncryptionService {\r\n constructor(private logger: ILoggerService) {}\r\n\r\n async encrypt(\r\n data: string,\r\n key: string\r\n ): Promise<Result<string, EncryptionError>> {\r\n try {\r\n const salt = randomBytes(SALT_LENGTH);\r\n const iv = randomBytes(IV_LENGTH);\r\n const derivedKey = pbkdf2Sync(\r\n key,\r\n salt,\r\n PBKDF2_ITER,\r\n KEY_LENGTH,\r\n \"sha512\"\r\n );\r\n const cipher = createCipheriv(\"aes-256-gcm\", derivedKey, iv);\r\n const ciphertext = Buffer.concat([\r\n cipher.update(data, \"utf8\"),\r\n cipher.final(),\r\n ]);\r\n const authTag = cipher.getAuthTag();\r\n this.logger.debug(\"Data encrypted\", {\r\n algorithm: \"AES-256-GCM\",\r\n operation: \"encrypt\",\r\n });\r\n return {\r\n ok: true,\r\n value: [\r\n HEADER,\r\n iv.toString(\"base64\"),\r\n salt.toString(\"base64\"),\r\n ciphertext.toString(\"base64\"),\r\n authTag.toString(\"base64\"),\r\n ].join(\":\"),\r\n };\r\n } catch (err) {\r\n this.logger.error(\"Encryption failed\", {\r\n error: err instanceof Error ? err.message : err,\r\n operation: \"encrypt\",\r\n });\r\n return {\r\n ok: false,\r\n error: new EncryptionError(\r\n \"Encryption failed\",\r\n ErrorCode.ENCRYPTION_FAILED,\r\n {\r\n originalError: err instanceof Error ? err.message : err,\r\n operation: \"encrypt\",\r\n }\r\n ),\r\n };\r\n }\r\n }\r\n\r\n async decrypt(\r\n data: string,\r\n key: string\r\n ): Promise<Result<string, EncryptionError>> {\r\n try {\r\n const parts = data.split(\":\");\r\n if (parts[0] !== HEADER || parts.length !== 5) {\r\n this.logger.error(\"Invalid encrypted data format\", {\r\n operation: \"decrypt\",\r\n });\r\n return {\r\n ok: false,\r\n error: new EncryptionError(\r\n \"Invalid encrypted data format\",\r\n ErrorCode.ENCRYPTION_INVALID_FORMAT,\r\n { operation: \"decrypt\" }\r\n ),\r\n };\r\n }\r\n const [, ivB64, saltB64, ciphertextB64, authTagB64] = parts;\r\n const iv = Buffer.from(ivB64, \"base64\");\r\n const salt = Buffer.from(saltB64, \"base64\");\r\n const ciphertext = Buffer.from(ciphertextB64, \"base64\");\r\n const authTag = Buffer.from(authTagB64, \"base64\");\r\n const derivedKey = pbkdf2Sync(\r\n key,\r\n salt,\r\n PBKDF2_ITER,\r\n KEY_LENGTH,\r\n \"sha512\"\r\n );\r\n const decipher = createDecipheriv(\"aes-256-gcm\", derivedKey, iv);\r\n decipher.setAuthTag(authTag);\r\n const plaintext = Buffer.concat([\r\n decipher.update(ciphertext),\r\n decipher.final(),\r\n ]);\r\n this.logger.debug(\"Data decrypted\", {\r\n algorithm: \"AES-256-GCM\",\r\n operation: \"decrypt\",\r\n });\r\n return { ok: true, value: plaintext.toString(\"utf8\") };\r\n } catch (err) {\r\n this.logger.error(\"Decryption failed\", {\r\n error: err instanceof Error ? err.message : err,\r\n operation: \"decrypt\",\r\n });\r\n return {\r\n ok: false,\r\n error: new EncryptionError(\r\n \"Decryption failed\",\r\n ErrorCode.ENCRYPTION_FAILED,\r\n {\r\n originalError: err instanceof Error ? err.message : err,\r\n operation: \"decrypt\",\r\n }\r\n ),\r\n };\r\n }\r\n }\r\n}\r\n", "// Main entry point for CLI/IDE to interact with config (no DI required)\r\nimport { ConfigService } from '@codestate/core/services/config/ConfigService';\r\nimport { ConfigRepository } from '@codestate/infrastructure/repositories/ConfigRepository';\r\nimport { FileLogger } from '@codestate/infrastructure/services/FileLogger';\r\nimport { BasicEncryption } from '@codestate/infrastructure/services/BasicEncryption';\r\nimport { IConfigService } from '@codestate/core/domain/ports/IConfigService';\r\nimport { ILoggerService } from '@codestate/core/domain/ports/ILoggerService';\r\nimport { IEncryptionService } from '@codestate/core/domain/ports/IEncryptionService';\r\nimport * as path from 'path';\r\n\r\nexport class ConfigFacade implements IConfigService {\r\n private service: ConfigService;\r\n\r\n constructor(\r\n configPath?: string,\r\n logger?: ILoggerService,\r\n encryption?: IEncryptionService\r\n ) {\r\n const _logger = logger || new FileLogger({ \r\n level: 'LOG', \r\n sinks: ['file'],\r\n filePath: path.join(process.env.HOME || process.env.USERPROFILE || '.', '.codestate', 'logs', 'codestate.log')\r\n });\r\n const _encryption = encryption || new BasicEncryption(_logger);\r\n const repository = new ConfigRepository(_logger, _encryption, configPath);\r\n this.service = new ConfigService(repository, _logger);\r\n }\r\n\r\n async getConfig(...args: Parameters<IConfigService['getConfig']>) {\r\n return this.service.getConfig(...args);\r\n }\r\n async setConfig(...args: Parameters<IConfigService['setConfig']>) {\r\n return this.service.setConfig(...args);\r\n }\r\n async updateConfig(...args: Parameters<IConfigService['updateConfig']>) {\r\n return this.service.updateConfig(...args);\r\n }\r\n} ", "import { IConfigService } from '@codestate/core/domain/ports/IConfigService';\r\nimport { Config } from '@codestate/core/domain/models/Config';\r\nimport { Result } from '@codestate/core/domain/models/Result';\r\nimport { ConfigFacade } from '@codestate/core/services/config/ConfigFacade';\r\n\r\nexport class GetConfig {\r\n private configService: IConfigService;\r\n constructor(configService?: IConfigService) {\r\n this.configService = configService || new ConfigFacade();\r\n }\r\n async execute(): Promise<Result<Config>> {\r\n return this.configService.getConfig();\r\n }\r\n} ", "import { IConfigService } from '@codestate/core/domain/ports/IConfigService';\r\nimport { Config } from '@codestate/core/domain/models/Config';\r\nimport { Result } from '@codestate/core/domain/models/Result';\r\nimport { ConfigFacade } from '@codestate/core/services/config/ConfigFacade';\r\n\r\nexport class UpdateConfig {\r\n private configService: IConfigService;\r\n constructor(configService?: IConfigService) {\r\n this.configService = configService || new ConfigFacade();\r\n }\r\n async execute(partial: Partial<Config>): Promise<Result<Config>> {\r\n return this.configService.updateConfig(partial);\r\n }\r\n} ", "import { IConfigService } from '@codestate/core/domain/ports/IConfigService';\r\nimport { Config } from '@codestate/core/domain/models/Config';\r\nimport { Result, isFailure } from '@codestate/core/domain/models/Result';\r\nimport { ConfigFacade } from '@codestate/core/services/config/ConfigFacade';\r\nimport * as path from 'path';\r\n\r\nfunction getDefaultConfig(): Config {\r\n return {\r\n version: '1.0.0',\r\n ide: 'vscode',\r\n encryption: { enabled: false },\r\n storagePath: path.join(process.env.HOME || process.env.USERPROFILE || '.', '.codestate'),\r\n logger: { \r\n level: 'LOG', \r\n sinks: ['file'],\r\n filePath: path.join(process.env.HOME || process.env.USERPROFILE || '.', '.codestate', 'logs', 'codestate.log')\r\n },\r\n experimental: {},\r\n extensions: {},\r\n };\r\n}\r\n\r\nexport class ResetConfig {\r\n private configService: IConfigService;\r\n constructor(configService?: IConfigService) {\r\n this.configService = configService || new ConfigFacade();\r\n }\r\n async execute(): Promise<Result<Config>> {\r\n const result = await this.configService.setConfig(getDefaultConfig());\r\n if (isFailure(result)) return { ok: false, error: result.error };\r\n return { ok: true, value: getDefaultConfig() };\r\n }\r\n} ", "import { IConfigService } from '@codestate/core/domain/ports/IConfigService';\r\nimport { Result, isFailure } from '@codestate/core/domain/models/Result';\r\nimport { ConfigFacade } from '@codestate/core/services/config/ConfigFacade';\r\n\r\nexport class ExportConfig {\r\n private configService: IConfigService;\r\n constructor(configService?: IConfigService) {\r\n this.configService = configService || new ConfigFacade();\r\n }\r\n async execute(): Promise<Result<string>> {\r\n const result = await this.configService.getConfig();\r\n if (isFailure(result)) return { ok: false, error: result.error };\r\n return { ok: true, value: JSON.stringify(result.value, null, 2) };\r\n }\r\n} ", "import { IConfigService } from '@codestate/core/domain/ports/IConfigService';\r\nimport { Config } from '@codestate/core/domain/models/Config';\r\nimport { Result, isFailure } from '@codestate/core/domain/models/Result';\r\nimport { validateConfig } from '@codestate/core/domain/schemas/SchemaRegistry';\r\nimport { ConfigFacade } from '@codestate/core/services/config/ConfigFacade';\r\n\r\nexport class ImportConfig {\r\n private configService: IConfigService;\r\n constructor(configService?: IConfigService) {\r\n this.configService = configService || new ConfigFacade();\r\n }\r\n async execute(json: string): Promise<Result<Config>> {\r\n let parsed: Config;\r\n try {\r\n parsed = validateConfig(JSON.parse(json));\r\n } catch (err: any) {\r\n return { ok: false, error: err };\r\n }\r\n const result = await this.configService.setConfig(parsed);\r\n if (isFailure(result)) return { ok: false, error: result.error };\r\n return { ok: true, value: parsed };\r\n }\r\n} ", "import { IScriptService, IScriptRepository } from '../../domain/ports/IScriptService';\r\nimport { Script, ScriptIndex } from '../../domain/models/Script';\r\nimport { Result, isSuccess, isFailure } from '../../domain/models/Result';\r\nimport { ILoggerService } from '../../domain/ports/ILoggerService';\r\n\r\nexport class ScriptService implements IScriptService {\r\n constructor(\r\n private repository: IScriptRepository,\r\n private logger: ILoggerService\r\n ) {}\r\n\r\n async createScript(script: Script): Promise<Result<void>> {\r\n this.logger.debug('ScriptService.createScript called', { script });\r\n const result = await this.repository.createScript(script);\r\n if (isFailure(result)) {\r\n this.logger.error('Failed to create script', { error: result.error, script });\r\n } else {\r\n this.logger.log('Script created successfully', { script });\r\n }\r\n return result;\r\n }\r\n\r\n async createScripts(scripts: Script[]): Promise<Result<void>> {\r\n this.logger.debug('ScriptService.createScripts called', { count: scripts.length });\r\n const result = await this.repository.createScripts(scripts);\r\n if (isFailure(result)) {\r\n this.logger.error('Failed to create scripts', { error: result.error, count: scripts.length });\r\n } else {\r\n this.logger.log('Scripts created successfully', { count: scripts.length });\r\n }\r\n return result;\r\n }\r\n\r\n async getScriptsByRootPath(rootPath: string): Promise<Result<Script[]>> {\r\n this.logger.debug('ScriptService.getScriptsByRootPath called', { rootPath });\r\n const result = await this.repository.getScriptsByRootPath(rootPath);\r\n if (isFailure(result)) {\r\n this.logger.error('Failed to get scripts by root path', { error: result.error, rootPath });\r\n } else {\r\n this.logger.log('Scripts retrieved by root path', { rootPath, count: result.value.length });\r\n }\r\n return result;\r\n }\r\n\r\n async getAllScripts(): Promise<Result<Script[]>> {\r\n this.logger.debug('ScriptService.getAllScripts called');\r\n const result = await this.repository.getAllScripts();\r\n if (isFailure(result)) {\r\n this.logger.error('Failed to get all scripts', { error: result.error });\r\n } else {\r\n this.logger.log('All scripts retrieved', { count: result.value.length });\r\n }\r\n return result;\r\n }\r\n\r\n async updateScript(name: string, rootPath: string, scriptUpdate: Partial<Script>): Promise<Result<void>> {\r\n this.logger.debug('ScriptService.updateScript called', { name, rootPath, scriptUpdate });\r\n const result = await this.repository.updateScript(name, rootPath, scriptUpdate);\r\n if (isFailure(result)) {\r\n this.logger.error('Failed to update script', { error: result.error, name, rootPath });\r\n } else {\r\n this.logger.log('Script updated successfully', { name, rootPath });\r\n }\r\n return result;\r\n }\r\n\r\n async updateScripts(updates: Array<{ name: string; rootPath: string; script: Partial<Script> }>): Promise<Result<void>> {\r\n this.logger.debug('ScriptService.updateScripts called', { count: updates.length });\r\n const result = await this.repository.updateScripts(updates);\r\n if (isFailure(result)) {\r\n this.logger.error('Failed to update scripts', { error: result.error, count: updates.length });\r\n } else {\r\n this.logger.log('Scripts updated successfully', { count: updates.length });\r\n }\r\n return result;\r\n }\r\n\r\n async deleteScript(name: string, rootPath: string): Promise<Result<void>> {\r\n this.logger.debug('ScriptService.deleteScript called', { name, rootPath });\r\n const result = await this.repository.deleteScript(name, rootPath);\r\n if (isFailure(result)) {\r\n this.logger.error('Failed to delete script', { error: result.error, name, rootPath });\r\n } else {\r\n this.logger.log('Script deleted successfully', { name, rootPath });\r\n }\r\n return result;\r\n }\r\n\r\n async deleteScripts(scripts: Array<{ name: string; rootPath: string }>): Promise<Result<void>> {\r\n this.logger.debug('ScriptService.deleteScripts called', { count: scripts.length });\r\n const result = await this.repository.deleteScripts(scripts);\r\n if (isFailure(result)) {\r\n this.logger.error('Failed to delete scripts', { error: result.error, count: scripts.length });\r\n } else {\r\n this.logger.log('Scripts deleted successfully', { count: scripts.length });\r\n }\r\n return result;\r\n }\r\n\r\n async deleteScriptsByRootPath(rootPath: string): Promise<Result<void>> {\r\n this.logger.debug('ScriptService.deleteScriptsByRootPath called', { rootPath });\r\n const result = await this.repository.deleteScriptsByRootPath(rootPath);\r\n if (isFailure(result)) {\r\n this.logger.error('Failed to delete scripts by root path', { error: result.error, rootPath });\r\n } else {\r\n this.logger.log('Scripts deleted by root path successfully', { rootPath });\r\n }\r\n return result;\r\n }\r\n\r\n async getScriptIndex(): Promise<Result<ScriptIndex>> {\r\n this.logger.debug('ScriptService.getScriptIndex called');\r\n const result = await this.repository.loadScriptIndex();\r\n if (isFailure(result)) {\r\n this.logger.error('Failed to get script index', { error: result.error });\r\n } else {\r\n this.logger.log('Script index retrieved', { entryCount: result.value.entries.length });\r\n }\r\n return result;\r\n }\r\n\r\n async updateScriptIndex(index: ScriptIndex): Promise<Result<void>> {\r\n this.logger.debug('ScriptService.updateScriptIndex called');\r\n const result = await this.repository.saveScriptIndex(index);\r\n if (isFailure(result)) {\r\n this.logger.error('Failed to update script index', { error: result.error });\r\n } else {\r\n this.logger.log('Script index updated successfully');\r\n }\r\n return result;\r\n }\r\n} ", "import { Result, isFailure } from \"@codestate/core/domain/models/Result\";\r\nimport {\r\n Script,\r\n ScriptCollection,\r\n ScriptIndex,\r\n} from \"@codestate/core/domain/models/Script\";\r\nimport { IConfigService } from \"@codestate/core/domain/ports/IConfigService\";\r\nimport { IEncryptionService } from \"@codestate/core/domain/ports/IEncryptionService\";\r\nimport { ILoggerService } from \"@codestate/core/domain/ports/ILoggerService\";\r\nimport { IScriptRepository } from \"@codestate/core/domain/ports/IScriptService\";\r\nimport {\r\n validateScript,\r\n validateScriptCollection,\r\n validateScriptIndex,\r\n} from \"@codestate/core/domain/schemas/SchemaRegistry\";\r\nimport {\r\n ErrorCode,\r\n ScriptError,\r\n} from \"@codestate/core/domain/types/ErrorTypes\";\r\nimport * as fs from \"fs/promises\";\r\nimport * as path from \"path\";\r\n\r\nconst DEFAULT_SCRIPTS_DIR = path.join(\r\n process.env.HOME || process.env.USERPROFILE || \".\",\r\n \".codestate\",\r\n \"scripts\"\r\n);\r\nconst INDEX_FILE = \"index.json\";\r\nconst TEMP_SUFFIX = \".tmp\";\r\nconst BACKUP_SUFFIX = \".bak\";\r\n\r\nexport class ScriptRepository implements IScriptRepository {\r\n constructor(\r\n private logger: ILoggerService,\r\n private encryption: IEncryptionService,\r\n private configService: IConfigService,\r\n private scriptsDir: string = DEFAULT_SCRIPTS_DIR\r\n ) {}\r\n\r\n async createScript(script: Script): Promise<Result<void>> {\r\n try {\r\n await this.ensureScriptsDir();\r\n\r\n // Validate script\r\n const validatedScript = validateScript(script);\r\n\r\n // Check if rootPath exists\r\n if (!(await this.pathExists(validatedScript.rootPath))) {\r\n this.logger.error(\"Root path does not exist\", {\r\n rootPath: validatedScript.rootPath,\r\n });\r\n return {\r\n ok: false,\r\n error: new ScriptError(\r\n \"Root path does not exist\",\r\n ErrorCode.SCRIPT_PATH_INVALID,\r\n { rootPath: validatedScript.rootPath }\r\n ),\r\n };\r\n }\r\n\r\n // Check for duplicate script command\r\n const existingScripts = await this.getScriptsByRootPath(\r\n validatedScript.rootPath\r\n );\r\n if (existingScripts.ok) {\r\n const duplicate = existingScripts.value.find(\r\n (s) => s.script === validatedScript.script\r\n );\r\n if (duplicate) {\r\n this.logger.error(\"Duplicate script command found\", {\r\n script: validatedScript.script,\r\n rootPath: validatedScript.rootPath,\r\n });\r\n return {\r\n ok: false,\r\n error: new ScriptError(\r\n \"Script command already exists\",\r\n ErrorCode.SCRIPT_DUPLICATE,\r\n {\r\n script: validatedScript.script,\r\n rootPath: validatedScript.rootPath,\r\n }\r\n ),\r\n };\r\n }\r\n }\r\n\r\n // Get or create script collection for this rootPath\r\n const collection = await this.getOrCreateScriptCollection(\r\n validatedScript.rootPath\r\n );\r\n if (isFailure(collection)) {\r\n return { ok: false, error: collection.error };\r\n }\r\n\r\n // Add script to collection\r\n collection.value.scripts.push(validatedScript);\r\n\r\n // Save collection\r\n const saveResult = await this.saveScriptCollection(\r\n validatedScript.rootPath,\r\n collection.value\r\n );\r\n if (isFailure(saveResult)) {\r\n return { ok: false, error: saveResult.error };\r\n }\r\n\r\n // Update index\r\n await this.updateIndexForRootPath(validatedScript.rootPath);\r\n\r\n this.logger.log(\"Script created successfully\", {\r\n name: validatedScript.name,\r\n rootPath: validatedScript.rootPath,\r\n });\r\n return { ok: true, value: undefined };\r\n } catch (err: any) {\r\n this.logger.error(\"Failed to create script\", {\r\n error: err.message,\r\n script,\r\n });\r\n return {\r\n ok: false,\r\n error: new ScriptError(err.message, ErrorCode.SCRIPT_INVALID, {\r\n originalError: err.message,\r\n }),\r\n };\r\n }\r\n }\r\n\r\n async createScripts(scripts: Script[]): Promise<Result<void>> {\r\n try {\r\n if (scripts.length === 0) {\r\n return { ok: true, va