@@ -94,7 +94,7 @@ function countMdFiles(dir: string): number {
9494 return count
9595}
9696
97- /** Compute a fast hash of a directory's file mtimes + sizes for change detection */
97+ /** Compute a stable content hash for change detection across fresh checkouts. */
9898function hashDir ( dir : string ) : string {
9999 const h = createHash ( 'sha256' )
100100 function walk ( d : string ) {
@@ -104,15 +104,36 @@ function hashDir(dir: string): string {
104104 if ( e . name . startsWith ( '.' ) ) continue
105105 const full = join ( d , e . name )
106106 if ( e . isDirectory ( ) ) { walk ( full ) ; continue }
107- const s = statSync ( full )
108- h . update ( `${ relative ( dir , full ) } :${ s . size } :${ s . mtimeMs } \n` )
107+ h . update ( `file:${ relative ( dir , full ) } \n` )
108+ h . update ( readFileSync ( full ) )
109+ h . update ( '\n' )
109110 }
110111 } catch { /* ignore */ }
111112 }
112113 walk ( dir )
113114 return h . digest ( 'hex' ) . substring ( 0 , 16 )
114115}
115116
117+ function hashFile ( path : string ) : string {
118+ const h = createHash ( 'sha256' )
119+ if ( ! existsSync ( path ) ) return ''
120+ h . update ( readFileSync ( path ) )
121+ return h . digest ( 'hex' ) . substring ( 0 , 16 )
122+ }
123+
124+ function hashBuildInputs ( ) : string {
125+ const h = createHash ( 'sha256' )
126+ for ( const [ label , value ] of [
127+ [ 'site' , hashDir ( MAIN_VP ) ] ,
128+ [ 'package' , hashFile ( join ( PROJECT_ROOT , 'package.json' ) ) ] ,
129+ [ 'lockfile' , hashFile ( join ( PROJECT_ROOT , 'pnpm-lock.yaml' ) ) ] ,
130+ [ 'build-script' , hashFile ( join ( PROJECT_ROOT , 'scripts' , 'build.ts' ) ) ] ,
131+ ] ) {
132+ h . update ( `${ label } :${ value } \n` )
133+ }
134+ return h . digest ( 'hex' ) . substring ( 0 , 16 )
135+ }
136+
116137// ── Manifest (incremental build state) ──────────────────────
117138
118139interface ManifestEntry { hash : string ; timestamp : string }
@@ -232,10 +253,11 @@ interface SearchIndexSource {
232253 lang : 'zh' | 'en' | 'mixed'
233254}
234255
235- function prepareVolume ( vol : Volume , lang : 'zh' | 'en' , manifest : Manifest ) : BuildTask {
256+ function prepareVolume ( vol : Volume , lang : 'zh' | 'en' , manifest : Manifest , buildInputsHash : string ) : BuildTask {
236257 const volDocDir = lang === 'en' ? join ( DOCUMENTS , 'en' , vol . srcDir ) : join ( DOCUMENTS , vol . srcDir )
237258 const id = lang === 'en' ? `${ vol . name } -en` : vol . name
238- const cacheKey = existsSync ( volDocDir ) ? hashDir ( volDocDir ) : ''
259+ const docHash = existsSync ( volDocDir ) ? hashDir ( volDocDir ) : ''
260+ const cacheKey = `${ buildInputsHash } -${ docHash } `
239261 const prev = manifest [ id ]
240262 const cached = ! FORCE_REBUILD && prev && prev . hash === cacheKey && existsSync ( join ( CACHE_DIR , 'output' , id ) )
241263 return { id, vol, lang, cacheKey, cached }
@@ -585,6 +607,7 @@ async function main() {
585607 mkdirSync ( join ( BUILD_TMP , 'output' ) , { recursive : true } )
586608
587609 const manifest = readManifest ( )
610+ const buildInputsHash = hashBuildInputs ( )
588611
589612 // ── Step 1: Build root ──────────────────────────────────
590613 logStep ( 'Step 1/4: Building root site (index, tags)' )
@@ -629,7 +652,7 @@ async function main() {
629652 for ( const lang of [ 'zh' , 'en' ] as const ) {
630653 const volDocDir = lang === 'en' ? join ( DOCUMENTS , 'en' , vol . srcDir ) : join ( DOCUMENTS , vol . srcDir )
631654 if ( ! existsSync ( volDocDir ) ) continue
632- tasks . push ( prepareVolume ( vol , lang , manifest ) )
655+ tasks . push ( prepareVolume ( vol , lang , manifest , buildInputsHash ) )
633656 }
634657 }
635658
0 commit comments