| 128 | } |
| 129 | |
| 130 | async writeFile(path: string, data: Uint8Array | string): Promise<void> { |
| 131 | const normalized = path.replace(/^\/+/, ''); |
| 132 | |
| 133 | if (!normalized) { |
| 134 | throw new Error('Cannot write to root'); |
| 135 | } |
| 136 | |
| 137 | // Convert to Uint8Array if string |
| 138 | const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : data; |
| 139 | |
| 140 | // Check size limit |
| 141 | if (bytes.length > MAX_OBJECT_SIZE) { |
| 142 | const sizeKB = (bytes.length / 1024).toFixed(2); |
| 143 | const maxKB = (MAX_OBJECT_SIZE / 1024).toFixed(2); |
| 144 | |
| 145 | if (normalized.includes('.git/objects/pack/')) { |
| 146 | throw new Error( |
| 147 | `Git packfile too large: ${sizeKB}KB exceeds ${maxKB}KB limit. ` + |
| 148 | `This packfile combines multiple objects. Try pushing fewer/smaller files at once.` |
| 149 | ); |
| 150 | } |
| 151 | |
| 152 | throw new Error(`File too large: ${path} (${bytes.length} bytes, max ${MAX_OBJECT_SIZE})`); |
| 153 | } |
| 154 | |
| 155 | // Check if path exists as directory |
| 156 | const existing = this.db |
| 157 | .select({ is_dir: gitObjects.is_dir }) |
| 158 | .from(gitObjects) |
| 159 | .where(eq(gitObjects.path, normalized)) |
| 160 | .all(); |
| 161 | |
| 162 | if (existing[0]?.is_dir === 1) { |
| 163 | const error: ErrnoException = new Error( |
| 164 | `EISDIR: illegal operation on a directory, open '${path}'` |
| 165 | ); |
| 166 | error.code = 'EISDIR'; |
| 167 | error.errno = -21; |
| 168 | error.path = path; |
| 169 | throw error; |
| 170 | } |
| 171 | |
| 172 | // Ensure parent directories exist (git implicitly creates them) |
| 173 | const parts = normalized.split('/'); |
| 174 | const parentPath = parts.length > 1 ? parts.slice(0, -1).join('/') : ''; |
| 175 | |
| 176 | if (parts.length > 1) { |
| 177 | const now = Date.now(); |
| 178 | for (let i = 0; i < parts.length - 1; i++) { |
| 179 | const dirPath = parts.slice(0, i + 1).join('/'); |
| 180 | const dirParent = i === 0 ? '' : parts.slice(0, i).join('/'); |
| 181 | this.db |
| 182 | .insert(gitObjects) |
| 183 | .values({ |
| 184 | path: dirPath, |
| 185 | parent_path: dirParent, |
| 186 | data: '', |
| 187 | is_dir: 1, |