@@ -3,6 +3,9 @@ import { Document, ParsedNode } from "yaml";
3
3
import { Context } from "./Context" ;
4
4
import { getPackageDeps } from "./getPackageDeps" ;
5
5
6
+ /**
7
+ * Subset of the .pre-commit-config.yaml schema that we care about.
8
+ */
6
9
interface PreCommitConfig {
7
10
repos : {
8
11
hooks : {
@@ -14,15 +17,19 @@ interface PreCommitConfig {
14
17
}
15
18
16
19
/**
17
- * Given a tsconfig.json, update it to match our conventions. This function is
18
- * called by the pnpm `meta-updater` plugin either to check if the tsconfig.json
19
- * is up to date or to update it, depending on flags.
20
+ * Given a .pre-commit-config.yaml, update it to ensure that the versions of our
21
+ * hooks match the corresponding package versions in package.json. This
22
+ * function is called by the pnpm `meta-updater` plugin either to check if the
23
+ * .pre-commit-config.yaml is up to date or to update it, depending on flags.
20
24
* @param context Contains context such as workspace dir and parsed pnpm
21
25
* lockfile
22
- * @param rawInput The input tsconfig.json that should be checked / updated
26
+ * @param rawInput The input .pre-commit-config.yaml that should be checked /
27
+ * updated. This is a parsed yaml document in the `yaml` library's document
28
+ * representation; not a plain js object like you'd get from a json parser. We
29
+ * need it like this so that we can preserve comments.
23
30
* @param options Extra information provided by pnpm; mostly just the directory
24
- * of the package whose tsconfig.json we are updating
25
- * @returns The updated tsconfig.json
31
+ * of the package whose .pre-commit-config.yaml we are updating
32
+ * @returns The updated .pre-commit-config.yaml
26
33
*/
27
34
export async function updatePreCommit (
28
35
{ workspaceDir, pnpmLockfile } : Context ,
@@ -32,36 +39,73 @@ export async function updatePreCommit(
32
39
if ( rawInput == null ) {
33
40
return null ;
34
41
}
35
- /** Directory of the package whose tsconfig.json we are updating */
42
+ /** Directory of the package whose .pre-commit-config.yaml we are updating */
36
43
const packageDir = options . dir ;
37
44
38
45
if ( packageDir !== workspaceDir ) {
39
46
throw new Error ( "updatePreCommit should only be called on root" ) ;
40
47
}
41
48
42
49
const deps = getPackageDeps ( workspaceDir , packageDir , pnpmLockfile ) ;
43
- const prettierVersion = deps [ "prettier" ] ;
44
50
45
- const prettierHookIndex = ( rawInput . toJS ( ) as PreCommitConfig ) . repos
51
+ updateHook ( deps , rawInput , "prettier" , ( name ) => name === "prettier" ) ;
52
+
53
+ return rawInput ;
54
+ }
55
+
56
+ /**
57
+ * Updates the additional_dependencies of a hook in a .pre-commit-config.yaml to
58
+ * match the versions from the lockfile.
59
+ * @param deps Dependencies of the package whose .pre-commit-config.yaml we are
60
+ * updating
61
+ * @param rawInput The input .pre-commit-config.yaml that should be checked /
62
+ * updated
63
+ * @param hookId The id of the hook to update
64
+ * @param packageMatcher A function that returns true if the given package name
65
+ * should be added to the hook's additional_dependencies
66
+ */
67
+ function updateHook (
68
+ deps : { [ x : string ] : string } ,
69
+ rawInput : Document < ParsedNode > ,
70
+ hookId : string ,
71
+ packageMatcher : ( name : string ) => boolean ,
72
+ ) {
73
+ const packages = Object . entries ( deps ) . filter ( ( [ name ] ) =>
74
+ packageMatcher ( name ) ,
75
+ ) ;
76
+
77
+ // Find the hook in the .pre-commit-config.yaml. Easier to grab the indices
78
+ // from the raw js representation so that we can just use `setIn` to update
79
+ // the hook
80
+ const desiredHooks = ( rawInput . toJS ( ) as PreCommitConfig ) . repos
46
81
. flatMap ( ( { hooks } , repoIndex ) =>
47
82
hooks . map ( ( hook , hookIndex ) => ( { hook, repoIndex, hookIndex } ) ) ,
48
83
)
49
- . filter ( ( { hook } ) => hook . id === "prettier" ) ;
84
+ . filter ( ( { hook } ) => hook . id === hookId ) ;
50
85
51
- if ( prettierHookIndex . length === 0 ) {
52
- throw new Error ( " No prettier hook found" ) ;
86
+ if ( desiredHooks . length === 0 ) {
87
+ throw new Error ( ` No ${ hookId } hook found` ) ;
53
88
}
54
89
55
- if ( prettierHookIndex . length > 1 ) {
56
- throw new Error ( " Multiple prettier hooks found" ) ;
90
+ if ( desiredHooks . length > 1 ) {
91
+ throw new Error ( ` Multiple ${ hookId } hooks found` ) ;
57
92
}
58
93
59
- const { repoIndex, hookIndex } = prettierHookIndex [ 0 ] ;
94
+ const { repoIndex, hookIndex } = desiredHooks [ 0 ] ;
60
95
61
96
rawInput . setIn (
62
97
[ "repos" , repoIndex , "hooks" , hookIndex , "additional_dependencies" ] ,
63
- rawInput . createNode ( [ `prettier@${ prettierVersion } ` ] ) ,
98
+ rawInput . createNode (
99
+ packages
100
+ . map ( ( [ name , version ] ) => {
101
+ if ( version . includes ( "(" ) ) {
102
+ // pnpm includes the integrity hash in the version, which we don't
103
+ // need here
104
+ version = version . slice ( 0 , version . indexOf ( "(" ) ) ;
105
+ }
106
+ return `${ name } @${ version } ` ;
107
+ } )
108
+ . sort ( ) ,
109
+ ) ,
64
110
) ;
65
-
66
- return rawInput ;
67
111
}
0 commit comments