(projectId: string, projectPath: string)
| 89 | } |
| 90 | |
| 91 | async function ensureProjectPath(projectId: string, projectPath: string): Promise<string> { |
| 92 | const project = await getProjectById(projectId); |
| 93 | if (!project) { |
| 94 | throw new Error(`Project not found: ${projectId}`); |
| 95 | } |
| 96 | |
| 97 | const absolute = path.isAbsolute(projectPath) |
| 98 | ? path.resolve(projectPath) |
| 99 | : path.resolve(process.cwd(), projectPath); |
| 100 | const allowedBasePath = path.resolve(process.cwd(), process.env.PROJECTS_DIR || './data/projects'); |
| 101 | const relativeToBase = path.relative(allowedBasePath, absolute); |
| 102 | const isWithinBase = !relativeToBase.startsWith('..') && !path.isAbsolute(relativeToBase); |
| 103 | if (!isWithinBase) { |
| 104 | throw new Error(`Project path must be within ${allowedBasePath}. Got: ${absolute}`); |
| 105 | } |
| 106 | |
| 107 | try { |
| 108 | await fs.access(absolute); |
| 109 | } catch { |
| 110 | await fs.mkdir(absolute, { recursive: true }); |
| 111 | } |
| 112 | |
| 113 | return absolute; |
| 114 | } |
| 115 | |
| 116 | function summarizeApplyPatch(payload: CodexEvent['msg']): { content: string; metadata: Record<string, unknown> } { |
| 117 | const changes = payload?.changes; |
no test coverage detected