@@ -3,53 +3,145 @@ import crypto from "crypto"
3
3
import path from "path"
4
4
import { validate } from "schema-utils"
5
5
6
- export default class SRIPlugin {
6
+ import { access as accessCps } from "fs"
7
+ import { execFile as execFileCps } from "child_process"
8
+ import { promisify } from "util"
9
+
10
+ class SRIPlugin {
7
11
static defaultOptions = {
8
12
algorithm : "sha512" ,
9
13
sourceFile : "assets.json"
10
14
}
11
15
12
16
constructor ( options = { } ) {
13
- const schema = {
14
- type : "object" ,
15
- properties : {
16
- outputFile : {
17
- type : "string"
18
- } ,
19
- algorithm : {
20
- type : "string"
17
+ this . options = { ...SRIPlugin . defaultOptions , ...options }
18
+
19
+ validate (
20
+ {
21
+ type : "object" ,
22
+ properties : {
23
+ sourceFile : { type : "string" } ,
24
+ outputFile : { type : "string" } ,
25
+ algorithm : { type : "string" }
21
26
}
27
+ } ,
28
+ options ,
29
+ {
30
+ name : "SRI Plugin" ,
31
+ baseDataPath : "options"
22
32
}
23
- }
33
+ )
34
+ }
24
35
25
- this . options = { ...SRIPlugin . defaultOptions , ...options }
36
+ apply ( compiler ) {
37
+ compiler . hooks . done . tap ( "SRIPlugin" , ( ) => {
38
+ const data = JSON . parse ( fs . readFileSync ( this . options . sourceFile , "utf8" ) )
39
+ const outputFile = this . options . outputFile || this . options . sourceFile
40
+ const { algorithm } = this . options
41
+
42
+ const calculateSRI = ( file ) => {
43
+ const fileContent = fs . readFileSync ( path . join ( "." , "static" , file ) )
44
+ const hash = crypto . createHash ( algorithm ) . update ( fileContent ) . digest ( "base64" )
45
+ return `${ algorithm } -${ hash } `
46
+ }
26
47
27
- validate ( schema , options , {
28
- name : "SRI Plugin" ,
29
- baseDataPath : "options"
48
+ Object . keys ( data ) . forEach ( ( key ) => {
49
+ data [ key ] . integrity = calculateSRI ( data [ key ] . src )
50
+ } )
51
+
52
+ fs . writeFileSync ( outputFile , JSON . stringify ( data , null , 2 ) , { encoding : "utf8" , flag : "w" } )
30
53
} )
31
54
}
55
+ }
56
+
57
+ class GitVersionPlugin {
58
+ static defaultOptions = {
59
+ outputFile : "VERSION"
60
+ }
61
+
62
+ constructor ( options = { } ) {
63
+ this . options = { ...GitVersionPlugin . defaultOptions , ...options }
64
+
65
+ validate (
66
+ {
67
+ type : "object" ,
68
+ properties : {
69
+ outputFile : { type : "string" }
70
+ }
71
+ } ,
72
+ options ,
73
+ {
74
+ baseDataPath : "options" ,
75
+ name : "GitVersion Plugin"
76
+ }
77
+ )
78
+ }
32
79
33
80
apply ( compiler ) {
34
- compiler . hooks . done . tap ( "SRIPlugin" , ( manifest ) => {
35
- let data = JSON . parse ( fs . readFileSync ( this . options . sourceFile , "utf8" ) )
36
- let outputFile = this . options . outputFile ? this . options . outputFile : this . options . sourceFile
81
+ const { webpack, hooks, context } = compiler
82
+ const { Compilation } = webpack
37
83
38
- const checksum = ( str , algorithm = this . options . algorithm , encoding = "base64" ) =>
39
- crypto . createHash ( algorithm ) . update ( str , "utf8" ) . digest ( encoding )
40
- const fileSum = ( file , algorithm ) => checksum ( fs . readFileSync ( file ) , algorithm )
41
- const calculateSRI = ( file , algorithm = this . options . algorithm ) =>
42
- `${ algorithm } -${ fileSum ( path . join ( "." , "static" , file ) , algorithm ) } `
84
+ hooks . beforeCompile . tapPromise ( "GitVersionPlugin" , async ( ) => {
85
+ const access = promisify ( accessCps )
43
86
44
- Object . keys ( data ) . forEach ( ( key ) => {
45
- let element = data [ key ]
46
- element . integrity = calculateSRI ( element . src )
47
- } )
87
+ try {
88
+ await access ( ".git" )
89
+ this . dependsOnGit = true
90
+ } catch {
91
+ this . dependsOnGit = false
92
+ }
93
+ } )
48
94
49
- fs . writeFileSync ( outputFile , JSON . stringify ( data , null , 2 ) , {
50
- encoding : "utf8" ,
51
- flag : "w"
52
- } )
95
+ hooks . compilation . tap ( "GitVersionPlugin" , ( compilation ) => {
96
+ if ( this . dependsOnGit ) {
97
+ compilation . fileDependencies . add ( path . join ( context , ".git/logs/HEAD" ) )
98
+ compilation . contextDependencies . add ( path . join ( context , ".git/refs/tags" ) )
99
+ }
100
+
101
+ compilation . hooks . processAssets . tapPromise (
102
+ {
103
+ name : "GitVersionPlugin" ,
104
+ stage : Compilation . PROCESS_ASSETS_STAGE_ADDITIONAL
105
+ } ,
106
+ async ( assets ) => {
107
+ try {
108
+ const v = await this . version ( )
109
+
110
+ assets [ this . options . outputFile ] = {
111
+ source : ( ) => `${ v } \n` ,
112
+ size : ( ) => v . length + 1
113
+ }
114
+ } catch {
115
+ assets [ this . options . outputFile ] = {
116
+ source : ( ) => "" ,
117
+ size : ( ) => 0
118
+ }
119
+ }
120
+ }
121
+ )
53
122
} )
54
123
}
124
+
125
+ async version ( ) {
126
+ const execFile = promisify ( execFileCps )
127
+
128
+ try {
129
+ const { stdout : describe } = await execFile ( "git" , [ "describe" , "--long" , "--tags" ] )
130
+ const [ , tag , offset ] = describe . trim ( ) . match ( / ^ ( .* ) - ( \d + ) - g [ 0 - 9 a - f ] + $ / )
131
+ return parseInt ( offset ) === 0 ? tag : this . getBranchAndHash ( )
132
+ } catch {
133
+ return this . getBranchAndHash ( )
134
+ }
135
+ }
136
+
137
+ async getBranchAndHash ( ) {
138
+ const execFile = promisify ( execFileCps )
139
+ const [ { stdout : branch } , { stdout : hash } ] = await Promise . all ( [
140
+ execFile ( "git" , [ "rev-parse" , "--abbrev-ref" , "HEAD" ] ) ,
141
+ execFile ( "git" , [ "rev-parse" , "HEAD" ] )
142
+ ] )
143
+ return `${ branch . trim ( ) } @${ hash . substring ( 0 , 7 ) } `
144
+ }
55
145
}
146
+
147
+ export { SRIPlugin , GitVersionPlugin }
0 commit comments