1
- import fs from "fs" ;
2
1
import { PackageJson , TsConfigJson } from "type-fest" ;
3
2
import detectIndent from "detect-indent"
4
- import loadJsonFile from "load-json-file"
3
+ import stripBom from "strip-bom"
4
+ import parseJson from "parse-json"
5
+ import fs from "graceful-fs"
5
6
6
7
interface Options {
7
8
/** @default utf-8 */
8
9
encoding : BufferEncoding
9
10
/**
10
- * Will throw in case of FS error or invalid JSON
11
+ * If `false`, FS or JSON errors will be ignored
11
12
* @default true
12
13
* */
13
14
throws : boolean
14
15
// ideally this lib should integrate with json validator
15
- /** @default "throw" (even if throws is false) */
16
+ /** @default "throw" (silent if throws: false) */
16
17
ifFieldIsMissing : "throw" | "skip" | "add"
17
18
/**
18
- * - throw - throws (even if throws param is false)
19
+ * - throw - throws (silent if throws: false)
19
20
*- skip - won't call the function
20
21
* - pass - pass the `undefined` value
21
22
* @default "throw"
@@ -31,69 +32,83 @@ interface Options {
31
32
}
32
33
33
34
type GettersDeep < T extends object > = {
34
- [ K in keyof T ] : ( oldValue : T [ K ] , json : T ) => unknown
35
+ [ K in keyof T ] : ( oldValue : T [ K ] , json : T ) => T [ K ]
35
36
//T[K] extends object ? ((oldValue: T[K]) => unknown)/* | GettersDeep<T[K]> */ : (oldValue: T[K]) => unknown
36
37
}
37
38
38
39
export type ModifyJsonFileFunction < T extends object > = (
39
40
path : string ,
40
- fields : Partial < T | GettersDeep < T > > ,
41
+ modifyFields : Partial < T | GettersDeep < T > > | ( ( oldJson : T ) => T ) ,
41
42
options ?: Options
42
43
) => Promise < void > ;
43
44
44
45
type ModifyJsonFileGenericFunction = < T extends object > (
45
46
path : string ,
46
- fields : Partial < T | GettersDeep < T > > ,
47
+ modifyFields : Partial < T | GettersDeep < T > > | ( ( oldJson : T ) => T ) ,
47
48
options ?: Partial < Options >
48
49
) => Promise < void > ;
49
50
51
+ /** returns additional info, not only JSON */
52
+ const loadJsonFile = async ( filePath : string , { encoding, tabSize } : Pick < Options , "encoding" | "tabSize" > ) => {
53
+ const contents = stripBom (
54
+ await fs . promises . readFile ( filePath , encoding )
55
+ ) ;
56
+ return {
57
+ json : parseJson ( contents , filePath ) ,
58
+ indent : tabSize === "preserve" ?
59
+ detectIndent ( contents ) . indent :
60
+ tabSize === "hard" ? "\t" : tabSize === null ?
61
+ undefined : " " . repeat ( tabSize )
62
+ }
63
+ } ;
64
+
50
65
/** It's just Error */
51
- class InnerError extends Error {
52
- innerError = true ;
53
- }
66
+ // class InnerError extends Error {
67
+ // innerError = true;
68
+ // }
54
69
55
70
/**
56
71
* modifies **original** JSON file
57
72
* You can pass generic, that reflects the structure of original JSON file
58
73
*
59
- * Fields, that are functions will be skipped if they're not preset in original JSON file
74
+ * @param modifyFields Function setter or fields to merge
60
75
*/
61
76
export const modifyJsonFile : ModifyJsonFileGenericFunction = async (
62
77
path ,
63
- fields ,
78
+ modifyFields ,
64
79
options
65
80
) => {
66
81
const {
67
82
encoding = "utf-8" ,
68
83
throws = true ,
69
84
ifFieldIsMissing = "throw" ,
70
85
ifFieldIsMissingForSetter = "throw" ,
71
- tabSize : tabSizeOption = "preserve"
86
+ tabSize = "preserve"
72
87
} = options || { } ;
73
88
try {
74
- const json = await loadJsonFile ( path ) ;
89
+ let { json, indent } = await loadJsonFile ( path , { encoding , tabSize } ) ;
75
90
// todo remove restriction or not?
76
91
if ( ! json || typeof json !== "object" || Array . isArray ( json ) ) throw new TypeError ( `${ path } : JSON root type must be object` ) ;
77
- // todo we don't need to read the file twice
78
- const rawText = await fs . promises . readFile ( path , encoding ) ;
79
- const indent = tabSizeOption === "preserve" ? detectIndent ( rawText ) . indent : tabSizeOption === "hard" ? "\t" : tabSizeOption === null ? undefined : " " . repeat ( tabSizeOption ) ;
80
-
81
- for ( const [ name , value ] of Object . entries ( fields ) ) {
82
- if ( ! ( name in json ) ) {
83
- const isSetter = typeof value === "function" ;
84
- const generalAction = isSetter ? ifFieldIsMissingForSetter : ifFieldIsMissing ;
85
- if ( generalAction === "throw" ) throw new InnerError ( `Property to modify "${ name } " is missing in ${ path } ` ) ;
86
- if ( generalAction === "skip" ) continue ;
92
+ if ( typeof modifyFields === "function" ) {
93
+ json = modifyFields ( json )
94
+ } else {
95
+ for ( const [ name , value ] of Object . entries ( modifyFields ) ) {
96
+ if ( ! ( name in json ) ) {
97
+ const isSetter = typeof value === "function" ;
98
+ const generalAction = isSetter ? ifFieldIsMissingForSetter : ifFieldIsMissing ;
99
+ if ( generalAction === "throw" ) throw new TypeError ( `Property to modify "${ name } " is missing in ${ path } ` ) ;
100
+ if ( generalAction === "skip" ) continue ;
101
+ }
102
+ json [ name as string ] = typeof value === "function" ? value ( json [ name as string ] , json ) : value ;
87
103
}
88
- json [ name as string ] = typeof value === "function" ? value ( json [ name as string ] , json ) : value ;
89
104
}
90
105
91
106
await fs . promises . writeFile (
92
- path ,
107
+ path ,
93
108
JSON . stringify ( json , undefined , indent )
94
109
) ;
95
110
} catch ( err ) {
96
- if ( err . innerError ) throw new Error ( err . message ) ;
111
+ // if (err.innerError) throw new Error(err.message);
97
112
if ( throws ) throw err ;
98
113
}
99
114
}
0 commit comments