|
7 | 7 | */ |
8 | 8 | import fs from 'node:fs'; |
9 | 9 | import path from 'node:path'; |
| 10 | +import { performance } from 'node:perf_hooks'; |
10 | 11 | import { closeDb } from '../../../../db/index.js'; |
11 | 12 | import { debug, info } from '../../../../infrastructure/logger.js'; |
12 | 13 | import { normalizePath } from '../../../../shared/constants.js'; |
@@ -512,59 +513,66 @@ function handleIncrementalBuild(ctx: PipelineContext): void { |
512 | 513 | } |
513 | 514 |
|
514 | 515 | export async function detectChanges(ctx: PipelineContext): Promise<void> { |
515 | | - const { db, allFiles, rootDir, incremental, forceFullRebuild, opts } = ctx; |
516 | | - if ((opts as Record<string, unknown>).scope) { |
517 | | - handleScopedBuild(ctx); |
518 | | - return; |
519 | | - } |
520 | | - const increResult = |
521 | | - incremental && !forceFullRebuild |
522 | | - ? getChangedFiles(db, allFiles, rootDir) |
523 | | - : { |
524 | | - changed: allFiles.map((f): ChangedFile => ({ file: f })), |
525 | | - removed: [] as string[], |
526 | | - isFullBuild: true, |
527 | | - }; |
528 | | - ctx.removed = increResult.removed; |
529 | | - ctx.isFullBuild = increResult.isFullBuild; |
530 | | - ctx.parseChanges = increResult.changed |
531 | | - .filter((c) => !c.metadataOnly) |
532 | | - .map((c) => ({ |
533 | | - file: c.file, |
534 | | - relPath: c.relPath, |
535 | | - content: c.content, |
536 | | - hash: c.hash, |
537 | | - stat: c.stat ? { mtime: Math.floor(c.stat.mtimeMs), size: c.stat.size } : undefined, |
538 | | - _reverseDepOnly: c._reverseDepOnly, |
539 | | - })); |
540 | | - ctx.metadataUpdates = increResult.changed |
541 | | - .filter( |
542 | | - (c): c is ChangedFile & { relPath: string; hash: string; stat: FileStat } => |
543 | | - !!c.metadataOnly && !!c.relPath && !!c.hash && !!c.stat, |
544 | | - ) |
545 | | - .map((c) => ({ |
546 | | - relPath: c.relPath, |
547 | | - hash: c.hash, |
548 | | - stat: { mtime: Math.floor(c.stat.mtimeMs), size: c.stat.size }, |
549 | | - })); |
550 | | - if (!ctx.isFullBuild && ctx.parseChanges.length === 0 && ctx.removed.length === 0) { |
551 | | - const ranAnalysis = await runPendingAnalysis(ctx); |
552 | | - if (ranAnalysis) { |
| 516 | + const start = performance.now(); |
| 517 | + try { |
| 518 | + const { db, allFiles, rootDir, incremental, forceFullRebuild, opts } = ctx; |
| 519 | + if ((opts as Record<string, unknown>).scope) { |
| 520 | + handleScopedBuild(ctx); |
| 521 | + return; |
| 522 | + } |
| 523 | + const increResult = |
| 524 | + incremental && !forceFullRebuild |
| 525 | + ? getChangedFiles(db, allFiles, rootDir) |
| 526 | + : { |
| 527 | + changed: allFiles.map((f): ChangedFile => ({ file: f })), |
| 528 | + removed: [] as string[], |
| 529 | + isFullBuild: true, |
| 530 | + }; |
| 531 | + ctx.removed = increResult.removed; |
| 532 | + ctx.isFullBuild = increResult.isFullBuild; |
| 533 | + ctx.parseChanges = increResult.changed |
| 534 | + .filter((c) => !c.metadataOnly) |
| 535 | + .map((c) => ({ |
| 536 | + file: c.file, |
| 537 | + relPath: c.relPath, |
| 538 | + content: c.content, |
| 539 | + hash: c.hash, |
| 540 | + stat: c.stat ? { mtime: Math.floor(c.stat.mtimeMs), size: c.stat.size } : undefined, |
| 541 | + _reverseDepOnly: c._reverseDepOnly, |
| 542 | + })); |
| 543 | + ctx.metadataUpdates = increResult.changed |
| 544 | + .filter( |
| 545 | + (c): c is ChangedFile & { relPath: string; hash: string; stat: FileStat } => |
| 546 | + !!c.metadataOnly && !!c.relPath && !!c.hash && !!c.stat, |
| 547 | + ) |
| 548 | + .map((c) => ({ |
| 549 | + relPath: c.relPath, |
| 550 | + hash: c.hash, |
| 551 | + stat: { mtime: Math.floor(c.stat.mtimeMs), size: c.stat.size }, |
| 552 | + })); |
| 553 | + if (!ctx.isFullBuild && ctx.parseChanges.length === 0 && ctx.removed.length === 0) { |
| 554 | + const ranAnalysis = await runPendingAnalysis(ctx); |
| 555 | + if (ranAnalysis) { |
| 556 | + closeDb(db); |
| 557 | + writeJournalHeader(rootDir, Date.now()); |
| 558 | + ctx.earlyExit = true; |
| 559 | + return; |
| 560 | + } |
| 561 | + healMetadata(ctx); |
| 562 | + info('No changes detected. Graph is up to date.'); |
553 | 563 | closeDb(db); |
554 | 564 | writeJournalHeader(rootDir, Date.now()); |
555 | 565 | ctx.earlyExit = true; |
556 | 566 | return; |
557 | 567 | } |
558 | | - healMetadata(ctx); |
559 | | - info('No changes detected. Graph is up to date.'); |
560 | | - closeDb(db); |
561 | | - writeJournalHeader(rootDir, Date.now()); |
562 | | - ctx.earlyExit = true; |
563 | | - return; |
564 | | - } |
565 | | - if (ctx.isFullBuild) { |
566 | | - handleFullBuild(ctx); |
567 | | - } else { |
568 | | - handleIncrementalBuild(ctx); |
| 568 | + if (ctx.isFullBuild) { |
| 569 | + handleFullBuild(ctx); |
| 570 | + } else { |
| 571 | + handleIncrementalBuild(ctx); |
| 572 | + } |
| 573 | + } finally { |
| 574 | + // Additive to respect any partial detectMs contribution from collectFiles |
| 575 | + // (scoped-rebuild path splits change-detection outputs across both stages). |
| 576 | + ctx.timing.detectMs = (ctx.timing.detectMs ?? 0) + (performance.now() - start); |
569 | 577 | } |
570 | 578 | } |
0 commit comments