()
| 317 | * @returns Array of full lockfile paths sorted by modification time (newest first) |
| 318 | */ |
| 319 | export async function getSortedIdeLockfiles(): Promise<string[]> { |
| 320 | try { |
| 321 | const ideLockFilePaths = await getIdeLockfilesPaths() |
| 322 | |
| 323 | // Collect all lockfiles from all directories |
| 324 | const allLockfiles: Array<{ path: string; mtime: Date }>[] = |
| 325 | await Promise.all( |
| 326 | ideLockFilePaths.map(async ideLockFilePath => { |
| 327 | try { |
| 328 | const entries = await getFsImplementation().readdir(ideLockFilePath) |
| 329 | const lockEntries = entries.filter(file => |
| 330 | file.name.endsWith('.lock'), |
| 331 | ) |
| 332 | // Stat all lockfiles in parallel; skip ones that fail |
| 333 | const stats = await Promise.all( |
| 334 | lockEntries.map(async file => { |
| 335 | const fullPath = join(ideLockFilePath, file.name) |
| 336 | try { |
| 337 | const fileStat = await getFsImplementation().stat(fullPath) |
| 338 | return { path: fullPath, mtime: fileStat.mtime } |
| 339 | } catch { |
| 340 | return null |
| 341 | } |
| 342 | }), |
| 343 | ) |
| 344 | return stats.filter(s => s !== null) |
| 345 | } catch (error) { |
| 346 | // Candidate paths are pushed without pre-checking existence, so |
| 347 | // missing/inaccessible dirs are expected here — skip silently. |
| 348 | if (!isFsInaccessible(error)) { |
| 349 | logError(error) |
| 350 | } |
| 351 | return [] |
| 352 | } |
| 353 | }), |
| 354 | ) |
| 355 | |
| 356 | // Flatten and sort all lockfiles by last modified date (newest first) |
| 357 | return allLockfiles |
| 358 | .flat() |
| 359 | .sort((a, b) => b.mtime.getTime() - a.mtime.getTime()) |
| 360 | .map(file => file.path) |
| 361 | } catch (error) { |
| 362 | logError(error as Error) |
| 363 | return [] |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | async function readIdeLockfile(path: string): Promise<IdeLockfileInfo | null> { |
| 368 | try { |
no test coverage detected