1 | "use strict";
|
2 |
|
3 | const fs = require("./utils/fs");
|
4 | const modeUtil = require("./utils/mode");
|
5 | const validate = require("./utils/validate");
|
6 | const write = require("./write");
|
7 |
|
8 | const validateInput = (methodName, path, criteria) => {
|
9 | const methodSignature = `${methodName}(path, [criteria])`;
|
10 | validate.argument(methodSignature, "path", path, ["string"]);
|
11 | validate.options(methodSignature, "criteria", criteria, {
|
12 | content: ["string", "buffer", "object", "array"],
|
13 | jsonIndent: ["number"],
|
14 | mode: ["string", "number"]
|
15 | });
|
16 | };
|
17 |
|
18 | const getCriteriaDefaults = passedCriteria => {
|
19 | const criteria = passedCriteria || {};
|
20 | if (criteria.mode !== undefined) {
|
21 | criteria.mode = modeUtil.normalizeFileMode(criteria.mode);
|
22 | }
|
23 | return criteria;
|
24 | };
|
25 |
|
26 | const generatePathOccupiedByNotFileError = path => {
|
27 | return new Error(
|
28 | `Path ${path} exists but is not a file. Halting jetpack.file() call for safety reasons.`
|
29 | );
|
30 | };
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | const checkWhatAlreadyOccupiesPathSync = path => {
|
37 | let stat;
|
38 |
|
39 | try {
|
40 | stat = fs.statSync(path);
|
41 | } catch (err) {
|
42 |
|
43 | if (err.code !== "ENOENT") {
|
44 | throw err;
|
45 | }
|
46 | }
|
47 |
|
48 | if (stat && !stat.isFile()) {
|
49 | throw generatePathOccupiedByNotFileError(path);
|
50 | }
|
51 |
|
52 | return stat;
|
53 | };
|
54 |
|
55 | const checkExistingFileFulfillsCriteriaSync = (path, stat, criteria) => {
|
56 | const mode = modeUtil.normalizeFileMode(stat.mode);
|
57 |
|
58 | const checkContent = () => {
|
59 | if (criteria.content !== undefined) {
|
60 | write.sync(path, criteria.content, {
|
61 | mode,
|
62 | jsonIndent: criteria.jsonIndent
|
63 | });
|
64 | return true;
|
65 | }
|
66 | return false;
|
67 | };
|
68 |
|
69 | const checkMode = () => {
|
70 | if (criteria.mode !== undefined && criteria.mode !== mode) {
|
71 | fs.chmodSync(path, criteria.mode);
|
72 | }
|
73 | };
|
74 |
|
75 | const contentReplaced = checkContent();
|
76 | if (!contentReplaced) {
|
77 | checkMode();
|
78 | }
|
79 | };
|
80 |
|
81 | const createBrandNewFileSync = (path, criteria) => {
|
82 | let content = "";
|
83 | if (criteria.content !== undefined) {
|
84 | content = criteria.content;
|
85 | }
|
86 | write.sync(path, content, {
|
87 | mode: criteria.mode,
|
88 | jsonIndent: criteria.jsonIndent
|
89 | });
|
90 | };
|
91 |
|
92 | const fileSync = (path, passedCriteria) => {
|
93 | const criteria = getCriteriaDefaults(passedCriteria);
|
94 | const stat = checkWhatAlreadyOccupiesPathSync(path);
|
95 | if (stat !== undefined) {
|
96 | checkExistingFileFulfillsCriteriaSync(path, stat, criteria);
|
97 | } else {
|
98 | createBrandNewFileSync(path, criteria);
|
99 | }
|
100 | };
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 | const checkWhatAlreadyOccupiesPathAsync = path => {
|
107 | return new Promise((resolve, reject) => {
|
108 | fs.stat(path)
|
109 | .then(stat => {
|
110 | if (stat.isFile()) {
|
111 | resolve(stat);
|
112 | } else {
|
113 | reject(generatePathOccupiedByNotFileError(path));
|
114 | }
|
115 | })
|
116 | .catch(err => {
|
117 | if (err.code === "ENOENT") {
|
118 |
|
119 | resolve(undefined);
|
120 | } else {
|
121 |
|
122 | reject(err);
|
123 | }
|
124 | });
|
125 | });
|
126 | };
|
127 |
|
128 | const checkExistingFileFulfillsCriteriaAsync = (path, stat, criteria) => {
|
129 | const mode = modeUtil.normalizeFileMode(stat.mode);
|
130 |
|
131 | const checkContent = () => {
|
132 | return new Promise((resolve, reject) => {
|
133 | if (criteria.content !== undefined) {
|
134 | write
|
135 | .async(path, criteria.content, {
|
136 | mode,
|
137 | jsonIndent: criteria.jsonIndent
|
138 | })
|
139 | .then(() => {
|
140 | resolve(true);
|
141 | })
|
142 | .catch(reject);
|
143 | } else {
|
144 | resolve(false);
|
145 | }
|
146 | });
|
147 | };
|
148 |
|
149 | const checkMode = () => {
|
150 | if (criteria.mode !== undefined && criteria.mode !== mode) {
|
151 | return fs.chmod(path, criteria.mode);
|
152 | }
|
153 | return undefined;
|
154 | };
|
155 |
|
156 | return checkContent().then(contentReplaced => {
|
157 | if (!contentReplaced) {
|
158 | return checkMode();
|
159 | }
|
160 | return undefined;
|
161 | });
|
162 | };
|
163 |
|
164 | const createBrandNewFileAsync = (path, criteria) => {
|
165 | let content = "";
|
166 | if (criteria.content !== undefined) {
|
167 | content = criteria.content;
|
168 | }
|
169 |
|
170 | return write.async(path, content, {
|
171 | mode: criteria.mode,
|
172 | jsonIndent: criteria.jsonIndent
|
173 | });
|
174 | };
|
175 |
|
176 | const fileAsync = (path, passedCriteria) => {
|
177 | return new Promise((resolve, reject) => {
|
178 | const criteria = getCriteriaDefaults(passedCriteria);
|
179 |
|
180 | checkWhatAlreadyOccupiesPathAsync(path)
|
181 | .then(stat => {
|
182 | if (stat !== undefined) {
|
183 | return checkExistingFileFulfillsCriteriaAsync(path, stat, criteria);
|
184 | }
|
185 | return createBrandNewFileAsync(path, criteria);
|
186 | })
|
187 | .then(resolve, reject);
|
188 | });
|
189 | };
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 | exports.validateInput = validateInput;
|
196 | exports.sync = fileSync;
|
197 | exports.async = fileAsync;
|