(
sourceId: string,
env: NodeJS.ProcessEnv = process.env,
opts: DecideRemoveOpts = {},
)
| 230 | } |
| 231 | |
| 232 | export function decideSourceRemove( |
| 233 | sourceId: string, |
| 234 | env: NodeJS.ProcessEnv = process.env, |
| 235 | opts: DecideRemoveOpts = {}, |
| 236 | ): RemoveDecision { |
| 237 | const keepStorage = opts.keepStorage ?? gbrainSupportsKeepStorage(env); |
| 238 | const extra = keepStorage ? ["--keep-storage"] : []; |
| 239 | |
| 240 | let rows: GbrainSourceRow[]; |
| 241 | try { |
| 242 | rows = (opts.fetchRows ?? fetchSources)(env); |
| 243 | } catch { |
| 244 | return { allow: false, extraArgs: [], reason: "could not read sources list; refusing remove (fail closed)" }; |
| 245 | } |
| 246 | |
| 247 | const row = rows.find((r) => r.id === sourceId); |
| 248 | if (!row) return { allow: true, extraArgs: extra, reason: "source absent (no-op)" }; |
| 249 | |
| 250 | const remoteUrl = row.config?.remote_url; |
| 251 | const userManaged = |
| 252 | !!remoteUrl && !!row.local_path && !clonesDirs(env).some((d) => isInside(row.local_path!, d)); |
| 253 | |
| 254 | if (userManaged) { |
| 255 | if (keepStorage) { |
| 256 | return { allow: true, extraArgs: ["--keep-storage"], reason: "user-managed; --keep-storage protects files" }; |
| 257 | } |
| 258 | return { |
| 259 | allow: false, |
| 260 | extraArgs: [], |
| 261 | reason: |
| 262 | `refusing remove of user-managed source "${sourceId}" (remote_url set, local_path ` + |
| 263 | `${row.local_path} outside gbrain clones) — this gbrain has no --keep-storage to ` + |
| 264 | `protect the working tree. Upgrade gbrain or remove the source manually.`, |
| 265 | }; |
| 266 | } |
| 267 | |
| 268 | return { allow: true, extraArgs: extra, reason: "gbrain-managed or path-managed without remote_url" }; |
| 269 | } |
| 270 | |
| 271 | export interface SyncDecision { |
| 272 | allow: boolean; |
no test coverage detected