1- import { createHash } from "node:crypto"
2-
31import type { PlatformError } from "@effect/platform/Error"
42import * as FileSystem from "@effect/platform/FileSystem"
53import * as Path from "@effect/platform/Path"
@@ -23,59 +21,71 @@ const controllerRevisionInputs: ReadonlyArray<string> = [
2321const skippedDirectoryNames = new Set ( [ ".git" , "node_modules" , "dist" , "dist-test" , ".turbo" ] )
2422const skippedFileNames = new Set ( [ ".DS_Store" ] )
2523
26- const hashMissingPath = ( hash : ReturnType < typeof createHash > , relativePath : string ) : void => {
27- hash . update ( `missing:${ relativePath } \n` )
24+ const appendChunk = ( chunks : Array < string > , value : string ) : void => {
25+ chunks . push ( value )
26+ }
27+
28+ const hashMissingPath = ( chunks : Array < string > , relativePath : string ) : void => {
29+ appendChunk ( chunks , `missing:${ relativePath } \n` )
2830}
2931
30- const hashDirectoryMarker = ( hash : ReturnType < typeof createHash > , relativePath : string ) : void => {
31- hash . update ( `dir:${ relativePath } \n` )
32+ const hashDirectoryMarker = ( chunks : Array < string > , relativePath : string ) : void => {
33+ appendChunk ( chunks , `dir:${ relativePath } \n` )
3234}
3335
3436const hashFileContents = (
35- hash : ReturnType < typeof createHash > ,
37+ chunks : Array < string > ,
3638 relativePath : string ,
3739 contents : string
3840) : void => {
39- hash . update ( `file:${ relativePath } \n` )
40- hash . update ( contents )
41- hash . update ( "\n" )
41+ appendChunk ( chunks , `file:${ relativePath } \n` )
42+ appendChunk ( chunks , contents )
43+ appendChunk ( chunks , "\n" )
4244}
4345
46+ const bytesToHex = ( bytes : Uint8Array ) : string =>
47+ Array . from ( bytes , ( byte ) => byte . toString ( 16 ) . padStart ( 2 , "0" ) ) . join ( "" )
48+
49+ const digestRevision = ( chunks : ReadonlyArray < string > ) : Effect . Effect < string > =>
50+ Effect . promise ( ( ) => crypto . subtle . digest ( "SHA-256" , new TextEncoder ( ) . encode ( chunks . join ( "" ) ) ) ) . pipe (
51+ Effect . map ( ( buffer ) => bytesToHex ( new Uint8Array ( buffer ) ) . slice ( 0 , 16 ) )
52+ )
53+
4454const hashTree = (
4555 fs : FileSystem . FileSystem ,
4656 path : Path . Path ,
4757 rootDir : string ,
4858 relativePath : string ,
49- hash : ReturnType < typeof createHash >
59+ chunks : Array < string >
5060) : Effect . Effect < void , PlatformError > =>
5161 Effect . gen ( function * ( _ ) {
5262 const absolutePath = path . join ( rootDir , relativePath )
5363 const exists = yield * _ ( fs . exists ( absolutePath ) )
5464 if ( ! exists ) {
55- hashMissingPath ( hash , relativePath )
65+ hashMissingPath ( chunks , relativePath )
5666 return
5767 }
5868
5969 const info = yield * _ ( fs . stat ( absolutePath ) )
6070 if ( info . type === "Directory" ) {
61- hashDirectoryMarker ( hash , relativePath )
62- const entries = ( yield * _ ( fs . readDirectory ( absolutePath ) ) ) . sort ( ( left , right ) => left . localeCompare ( right ) )
71+ hashDirectoryMarker ( chunks , relativePath )
72+ const entries = ( yield * _ ( fs . readDirectory ( absolutePath ) ) ) . toSorted ( ( left , right ) => left . localeCompare ( right ) )
6373 for ( const entry of entries ) {
6474 if ( skippedDirectoryNames . has ( entry ) || skippedFileNames . has ( entry ) ) {
6575 continue
6676 }
67- yield * _ ( hashTree ( fs , path , rootDir , path . join ( relativePath , entry ) , hash ) )
77+ yield * _ ( hashTree ( fs , path , rootDir , path . join ( relativePath , entry ) , chunks ) )
6878 }
6979 return
7080 }
7181
7282 if ( info . type === "File" ) {
7383 const contents = yield * _ ( fs . readFileString ( absolutePath ) )
74- hashFileContents ( hash , relativePath , contents )
84+ hashFileContents ( chunks , relativePath , contents )
7585 return
7686 }
7787
78- hash . update ( `other:${ relativePath } :${ info . type } \n` )
88+ appendChunk ( chunks , `other:${ relativePath } :${ info . type } \n` )
7989 } )
8090
8191export const parseControllerRevisionEnvOutput = ( output : string ) : string | null => {
@@ -114,11 +124,11 @@ export const computeLocalControllerRevision = (
114124 const fs = yield * _ ( FileSystem . FileSystem )
115125 const path = yield * _ ( Path . Path )
116126 const repoRoot = path . dirname ( composePath )
117- const hash = createHash ( "sha256" )
127+ const chunks : Array < string > = [ ]
118128
119129 for ( const relativePath of controllerRevisionInputs ) {
120- yield * _ ( hashTree ( fs , path , repoRoot , relativePath , hash ) )
130+ yield * _ ( hashTree ( fs , path , repoRoot , relativePath , chunks ) )
121131 }
122132
123- return hash . digest ( "hex" ) . slice ( 0 , 16 )
133+ return yield * _ ( digestRevision ( chunks ) )
124134 } )
0 commit comments