1- import { PackageJson , TsConfigJson } from "type-fest" ;
1+ import { PackageJson , PartialDeep , TsConfigJson } from "type-fest" ;
22import detectIndent from "detect-indent" ;
33import stripBom from "strip-bom" ;
44import parseJson from "parse-json" ;
@@ -31,27 +31,30 @@ type Options = PartialDeep<{
3131 * @default "preserve"
3232 * */
3333 tabSize : null | number | "preserve" | "hard" ;
34- } >
34+ } > ;
3535
36- type GettersDeep < T extends object > = {
37- [ K in keyof T ] : ( oldValue : T [ K ] , json : T ) => T [ K ]
36+ type ModifyFields < T extends object > = {
37+ [ K in keyof T ] ?: T [ K ] | ( ( oldValue : T [ K ] , json : T ) => T [ K ] )
3838 //T[K] extends object ? ((oldValue: T[K]) => unknown)/* | GettersDeep<T[K]> */ : (oldValue: T[K]) => unknown
3939} ;
4040
41- export type ModifyJsonFileFunction < T extends object > = (
41+ type ModifyFunction < T > = ( oldJson : T ) => MaybePromise < T > ;
42+
43+ export type ModifyJsonFileFunction < T > = (
4244 path : string ,
43- modifyFields : Partial < T | GettersDeep < T > > | ( ( oldJson : T ) => MaybePromise < T > ) ,
44- options ?: Options
45+ modifyFields : T extends object ? ModifyFields < T > | ModifyFunction < T > : ModifyFunction < T > ,
46+ options ?: Partial < Options >
4547) => Promise < void > ;
4648
47- type ModifyJsonFileGenericFunction = < T extends object > (
49+ type ModifyJsonFileGenericFunction = < T extends any = object > (
4850 path : string ,
49- modifyFields : Partial < T | GettersDeep < T > > | ( ( oldJson : T ) => MaybePromise < T > ) ,
51+ modifyFields : T extends object ? ModifyFields < T > | ModifyFunction < T > : ModifyFunction < T > ,
5052 options ?: Partial < Options >
5153) => Promise < void > ;
5254
55+ type LoadJsonFileOptions = Required < Pick < Options , "encoding" | "tabSize" > > ;
5356/** returns additional info, not only JSON */
54- const loadJsonFile = async ( filePath : string , { encoding, tabSize } : Pick < Options , "encoding" | "tabSize" > ) => {
57+ const loadJsonFile = async ( filePath : string , { encoding, tabSize } : LoadJsonFileOptions ) => {
5558 const contents = stripBom (
5659 await fs . promises . readFile ( filePath , encoding )
5760 ) ;
@@ -73,7 +76,7 @@ const loadJsonFile = async (filePath: string, { encoding, tabSize }: Pick<Option
7376 * modifies **original** JSON file
7477 * You can pass generic, that reflects the structure of original JSON file
7578 *
76- * @param modifyFields Fields to merge or callback (can be async). If callback is passed, JSON fields won't be merged.
79+ * @param modifyFields If file contents is object, you can pass fields to merge or callback (can be async). If callback is passed, JSON fields won't be merged. In case if file contents is not an object, you must pass callback .
7780 */
7881export const modifyJsonFile : ModifyJsonFileGenericFunction = async (
7982 path ,
@@ -89,19 +92,22 @@ export const modifyJsonFile: ModifyJsonFileGenericFunction = async (
8992 } = options || { } ;
9093 try {
9194 let { json, indent } = await loadJsonFile ( path , { encoding, tabSize } ) ;
92- // todo remove restriction or not?
93- if ( ! json || typeof json !== "object" || Array . isArray ( json ) ) throw new TypeError ( `${ path } : JSON root type must be object` ) ;
9495 if ( typeof modifyFields === "function" ) {
9596 json = await modifyFields ( json ) ;
9697 } else {
97- for ( const [ name , value ] of Object . entries ( modifyFields ) ) {
98+ if ( typeof json !== "object" || Array . isArray ( json ) ) throw new TypeError ( `${ path } : Root type is not object. Only callback can be used` ) ;
99+ for ( const parts of Object . entries ( modifyFields ) ) {
100+ // todo fix typescript types workaround
101+ const name = parts [ 0 ] as string ;
102+ const value = parts [ 1 ] as any ;
103+
104+ const isSetter = typeof value === "function" ;
98105 if ( ! ( name in json ) ) {
99- const isSetter = typeof value === "function" ;
100106 const generalAction = isSetter ? ifFieldIsMissingForSetter : ifFieldIsMissing ;
101107 if ( generalAction === "throw" ) throw new TypeError ( `Property to modify "${ name } " is missing in ${ path } ` ) ;
102108 if ( generalAction === "skip" ) continue ;
103109 }
104- json [ name as string ] = typeof value === "function" ? value ( json [ name as string ] , json ) : value ;
110+ json [ name as string ] = isSetter ? value ( json [ name as string ] , json ) : value ;
105111 }
106112 }
107113
@@ -110,7 +116,6 @@ export const modifyJsonFile: ModifyJsonFileGenericFunction = async (
110116 JSON . stringify ( json , undefined , indent )
111117 ) ;
112118 } catch ( err ) {
113- // if (err.innerError) throw new Error(err.message);
114119 if ( throws ) throw err ;
115120 }
116121} ;
0 commit comments