1+ import { createHash } from "node:crypto" ;
12import { access , mkdir , readFile } from "node:fs/promises" ;
23import path from "node:path" ;
34import pc from "picocolors" ;
@@ -42,10 +43,12 @@ type SyncResult = {
4243 ref : string ;
4344 resolvedCommit : string ;
4445 lockCommit : string | null ;
46+ lockRulesSha256 ?: string ;
4547 status : "up-to-date" | "changed" | "missing" ;
4648 bytes ?: number ;
4749 fileCount ?: number ;
4850 manifestSha256 ?: string ;
51+ rulesSha256 ?: string ;
4952} ;
5053
5154const formatBytes = ( value : number ) => {
@@ -79,6 +82,29 @@ const hasDocs = async (cacheDir: string, sourceId: string) => {
7982 return await exists ( path . join ( sourceDir , MANIFEST_FILENAME ) ) ;
8083} ;
8184
85+ const normalizePatterns = ( patterns ?: string [ ] ) => {
86+ if ( ! patterns || patterns . length === 0 ) {
87+ return [ ] ;
88+ }
89+ const normalized = patterns
90+ . map ( ( pattern ) => pattern . trim ( ) )
91+ . filter ( ( pattern ) => pattern . length > 0 ) ;
92+ return Array . from ( new Set ( normalized ) ) . sort ( ) ;
93+ } ;
94+
95+ const computeRulesHash = ( params : {
96+ include : string [ ] ;
97+ exclude ?: string [ ] ;
98+ } ) => {
99+ const payload = {
100+ include : normalizePatterns ( params . include ) ,
101+ exclude : normalizePatterns ( params . exclude ) ,
102+ } ;
103+ const hash = createHash ( "sha256" ) ;
104+ hash . update ( JSON . stringify ( payload ) ) ;
105+ return hash . digest ( "hex" ) ;
106+ } ;
107+
82108export const getSyncPlan = async (
83109 options : SyncOptions ,
84110 deps : SyncDeps = { } ,
@@ -108,6 +134,9 @@ export const getSyncPlan = async (
108134 const results : SyncResult [ ] = await Promise . all (
109135 filteredSources . map ( async ( source ) => {
110136 const lockEntry = lockData ?. sources ?. [ source . id ] ;
137+ const include = source . include ?? defaults . include ;
138+ const exclude = source . exclude ;
139+ const rulesSha256 = computeRulesHash ( { include, exclude } ) ;
111140 if ( options . offline ) {
112141 const docsPresent = await hasDocs ( resolvedCacheDir , source . id ) ;
113142 return {
@@ -116,10 +145,12 @@ export const getSyncPlan = async (
116145 ref : lockEntry ?. ref ?? source . ref ?? defaults . ref ,
117146 resolvedCommit : lockEntry ?. resolvedCommit ?? "offline" ,
118147 lockCommit : lockEntry ?. resolvedCommit ?? null ,
148+ lockRulesSha256 : lockEntry ?. rulesSha256 ,
119149 status : lockEntry && docsPresent ? "up-to-date" : "missing" ,
120150 bytes : lockEntry ?. bytes ,
121151 fileCount : lockEntry ?. fileCount ,
122152 manifestSha256 : lockEntry ?. manifestSha256 ,
153+ rulesSha256,
123154 } ;
124155 }
125156 const resolved = await resolveCommit ( {
@@ -128,7 +159,9 @@ export const getSyncPlan = async (
128159 allowHosts : defaults . allowHosts ,
129160 timeoutMs : options . timeoutMs ,
130161 } ) ;
131- const upToDate = lockEntry ?. resolvedCommit === resolved . resolvedCommit ;
162+ const upToDate =
163+ lockEntry ?. resolvedCommit === resolved . resolvedCommit &&
164+ lockEntry ?. rulesSha256 === rulesSha256 ;
132165 const status = lockEntry
133166 ? upToDate
134167 ? "up-to-date"
@@ -140,10 +173,12 @@ export const getSyncPlan = async (
140173 ref : resolved . ref ,
141174 resolvedCommit : resolved . resolvedCommit ,
142175 lockCommit : lockEntry ?. resolvedCommit ?? null ,
176+ lockRulesSha256 : lockEntry ?. rulesSha256 ,
143177 status,
144178 bytes : lockEntry ?. bytes ,
145179 fileCount : lockEntry ?. fileCount ,
146180 manifestSha256 : lockEntry ?. manifestSha256 ,
181+ rulesSha256,
147182 } ;
148183 } ) ,
149184 ) ;
@@ -199,6 +234,7 @@ const buildLock = async (
199234 fileCount : result . fileCount ?? prior ?. fileCount ?? 0 ,
200235 manifestSha256 :
201236 result . manifestSha256 ?? prior ?. manifestSha256 ?? result . resolvedCommit ,
237+ rulesSha256 : result . rulesSha256 ?? prior ?. rulesSha256 ,
202238 updatedAt : now ,
203239 } ;
204240 }
@@ -467,6 +503,10 @@ export const printSyncPlan = (
467503 for ( const result of plan . results ) {
468504 const shortResolved = ui . hash ( result . resolvedCommit ) ;
469505 const shortLock = ui . hash ( result . lockCommit ) ;
506+ const rulesChanged =
507+ Boolean ( result . lockRulesSha256 ) &&
508+ Boolean ( result . rulesSha256 ) &&
509+ result . lockRulesSha256 !== result . rulesSha256 ;
470510
471511 if ( result . status === "up-to-date" ) {
472512 ui . item (
@@ -477,6 +517,14 @@ export const printSyncPlan = (
477517 continue ;
478518 }
479519 if ( result . status === "changed" ) {
520+ if ( result . lockCommit === result . resolvedCommit && rulesChanged ) {
521+ ui . item (
522+ symbols . warn ,
523+ result . id ,
524+ `${ pc . dim ( "rules changed" ) } ${ pc . gray ( shortResolved ) } ` ,
525+ ) ;
526+ continue ;
527+ }
480528 ui . item (
481529 symbols . warn ,
482530 result . id ,
0 commit comments