Skip to content

Commit c80a1fa

Browse files
committed
fix: dont read file twice, use graceful-fs, fix return type for field setter
1 parent a05ca24 commit c80a1fa

File tree

5 files changed

+86
-51
lines changed

5 files changed

+86
-51
lines changed

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,24 @@
2727
},
2828
"devDependencies": {
2929
"@ava/typescript": "^1.1.1",
30+
"@types/graceful-fs": "^4.1.5",
3031
"@types/jsonfile": "^6.0.0",
3132
"@types/node": "^15.12.2",
33+
"@types/parse-json": "^4.0.0",
3234
"@zardoy/tsconfig": "^1.0.2",
3335
"ava": "^3.15.0",
3436
"del": "^6.0.0",
3537
"jsonfile": "^6.1.0",
38+
"load-json-file": "^6.2.0",
39+
"nanoid": "^3.1.23",
3640
"ts-node": "^10.0.0",
3741
"typescript": "^4.3.2"
3842
},
3943
"dependencies": {
4044
"detect-indent": "^6.1.0",
41-
"load-json-file": "^6.2.0",
42-
"nanoid": "^3.1.23",
45+
"graceful-fs": "^4.2.6",
46+
"parse-json": "^5.2.0",
47+
"strip-bom": "^5.0.0",
4348
"type-fest": "^1.2.0"
4449
},
4550
"ava": {

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ We're using [detect-indent](https://www.npmjs.com/package/detect-indent) to pres
5656

5757
## TODO
5858

59-
- [ ] Fix. Currently, to preserve the tab size, json file readed twice
59+
- [ ] Strip bom option
6060
- [ ] Fix double tsc compilation (and test actual build/)
6161
- [ ] transformer for paths (most likely babel plugin):
6262

src/index.ts

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
1-
import fs from "fs";
21
import { PackageJson, TsConfigJson } from "type-fest";
32
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"
56

67
interface Options {
78
/** @default utf-8 */
89
encoding: BufferEncoding
910
/**
10-
* Will throw in case of FS error or invalid JSON
11+
* If `false`, FS or JSON errors will be ignored
1112
* @default true
1213
* */
1314
throws: boolean
1415
// ideally this lib should integrate with json validator
15-
/** @default "throw" (even if throws is false) */
16+
/** @default "throw" (silent if throws: false) */
1617
ifFieldIsMissing: "throw" | "skip" | "add"
1718
/**
18-
* - throw - throws (even if throws param is false)
19+
* - throw - throws (silent if throws: false)
1920
*- skip - won't call the function
2021
* - pass - pass the `undefined` value
2122
* @default "throw"
@@ -31,69 +32,83 @@ interface Options {
3132
}
3233

3334
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]
3536
//T[K] extends object ? ((oldValue: T[K]) => unknown)/* | GettersDeep<T[K]> */ : (oldValue: T[K]) => unknown
3637
}
3738

3839
export type ModifyJsonFileFunction<T extends object> = (
3940
path: string,
40-
fields: Partial<T | GettersDeep<T>>,
41+
modifyFields: Partial<T | GettersDeep<T>> | ((oldJson: T) => T),
4142
options?: Options
4243
) => Promise<void>;
4344

4445
type ModifyJsonFileGenericFunction = <T extends object>(
4546
path: string,
46-
fields: Partial<T | GettersDeep<T>>,
47+
modifyFields: Partial<T | GettersDeep<T>> | ((oldJson: T) => T),
4748
options?: Partial<Options>
4849
) => Promise<void>;
4950

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+
5065
/** It's just Error */
51-
class InnerError extends Error {
52-
innerError = true;
53-
}
66+
// class InnerError extends Error {
67+
// innerError = true;
68+
// }
5469

5570
/**
5671
* modifies **original** JSON file
5772
* You can pass generic, that reflects the structure of original JSON file
5873
*
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
6075
*/
6176
export const modifyJsonFile: ModifyJsonFileGenericFunction = async (
6277
path,
63-
fields,
78+
modifyFields,
6479
options
6580
) => {
6681
const {
6782
encoding = "utf-8",
6883
throws = true ,
6984
ifFieldIsMissing = "throw",
7085
ifFieldIsMissingForSetter = "throw",
71-
tabSize: tabSizeOption = "preserve"
86+
tabSize = "preserve"
7287
} = options || {};
7388
try {
74-
const json = await loadJsonFile(path);
89+
let {json, indent} = await loadJsonFile(path, { encoding, tabSize });
7590
// todo remove restriction or not?
7691
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;
87103
}
88-
json[name as string] = typeof value === "function" ? value(json[name as string], json) : value;
89104
}
90105

91106
await fs.promises.writeFile(
92-
path,
107+
path,
93108
JSON.stringify(json, undefined, indent)
94109
);
95110
} catch(err) {
96-
if (err.innerError) throw new Error(err.message);
111+
// if (err.innerError) throw new Error(err.message);
97112
if (throws) throw err;
98113
}
99114
}

tsconfig.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
{
22
"extends": "@zardoy/tsconfig/node",
33
"compilerOptions": {
4-
"module": "CommonJS",
5-
"esModuleInterop": true,
64
"declaration": true
75
}
86
}

yarn.lock

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,23 @@
1010
escape-string-regexp "^2.0.0"
1111

1212
"@babel/code-frame@^7.0.0":
13-
version "7.12.13"
14-
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
15-
integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==
13+
version "7.14.5"
14+
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb"
15+
integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==
1616
dependencies:
17-
"@babel/highlight" "^7.12.13"
17+
"@babel/highlight" "^7.14.5"
1818

19-
"@babel/helper-validator-identifier@^7.14.0":
20-
version "7.14.0"
21-
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288"
22-
integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==
19+
"@babel/helper-validator-identifier@^7.14.5":
20+
version "7.14.5"
21+
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz#d0f0e277c512e0c938277faa85a3968c9a44c0e8"
22+
integrity sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==
2323

24-
"@babel/highlight@^7.12.13":
25-
version "7.14.0"
26-
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf"
27-
integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==
24+
"@babel/highlight@^7.14.5":
25+
version "7.14.5"
26+
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9"
27+
integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==
2828
dependencies:
29-
"@babel/helper-validator-identifier" "^7.14.0"
29+
"@babel/helper-validator-identifier" "^7.14.5"
3030
chalk "^2.0.0"
3131
js-tokens "^4.0.0"
3232

@@ -90,6 +90,13 @@
9090
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.1.tgz#a6ca6a9a0ff366af433f42f5f0e124794ff6b8f1"
9191
integrity sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA==
9292

93+
"@types/graceful-fs@^4.1.5":
94+
version "4.1.5"
95+
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
96+
integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==
97+
dependencies:
98+
"@types/node" "*"
99+
93100
"@types/jsonfile@^6.0.0":
94101
version "6.0.0"
95102
resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.0.0.tgz#264f42991b024fd5ea45eee4186d7ff6a26e9774"
@@ -107,10 +114,15 @@
107114
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
108115
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
109116

117+
"@types/parse-json@^4.0.0":
118+
version "4.0.0"
119+
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
120+
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
121+
110122
"@zardoy/tsconfig@^1.0.2":
111-
version "1.0.2"
112-
resolved "https://registry.yarnpkg.com/@zardoy/tsconfig/-/tsconfig-1.0.2.tgz#5c4771dc57e7f52e93a3a243d0203db39d8ac66f"
113-
integrity sha512-wyGrUnwCdLNBBqbRdTPkGUSdVdkHleHfp27NP47pK1i3Ic8tNfNE2/Bme2VwZjAux34Qog9Hr5IO4icr1Xwdow==
123+
version "1.1.0"
124+
resolved "https://registry.yarnpkg.com/@zardoy/tsconfig/-/tsconfig-1.1.0.tgz#c41aa725e075598e5b609354c3a4ebd650d18d68"
125+
integrity sha512-rRVqh15H3BP5/b2SEIhXduwxiqavuZhjJhc6UN2+Cc9r1Ew/Nn1hqidsU+i7MkfuimzzQRj3OGLjXT3I54YeLw==
114126
dependencies:
115127
typed-query-selector "^2.6.0"
116128

@@ -865,7 +877,7 @@ got@^9.6.0:
865877
to-readable-stream "^1.0.0"
866878
url-parse-lax "^3.0.0"
867879

868-
graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4:
880+
graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4, graceful-fs@^4.2.6:
869881
version "4.2.6"
870882
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
871883
integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
@@ -1436,7 +1448,7 @@ parse-json@^4.0.0:
14361448
error-ex "^1.3.1"
14371449
json-parse-better-errors "^1.0.1"
14381450

1439-
parse-json@^5.0.0:
1451+
parse-json@^5.0.0, parse-json@^5.2.0:
14401452
version "5.2.0"
14411453
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
14421454
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
@@ -1809,6 +1821,11 @@ strip-bom@^4.0.0:
18091821
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
18101822
integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==
18111823

1824+
strip-bom@^5.0.0:
1825+
version "5.0.0"
1826+
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-5.0.0.tgz#88d2e135d154dca7a5e06b4a4ba9653b6bdc0dd2"
1827+
integrity sha512-p+byADHF7SzEcVnLvc/r3uognM1hUhObuHXxJcgLCfD194XAkaLbjq3Wzb0N5G2tgIjH0dgT708Z51QxMeu60A==
1828+
18121829
strip-json-comments@~2.0.1:
18131830
version "2.0.1"
18141831
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"

0 commit comments

Comments
 (0)