1 | # Conartist
|
2 |
|
3 | Hate managing all your different config files across all your different
|
4 | repositories? Great. Step into my office.
|
5 |
|
6 | > Conartist is a tool that gives you a way to manage all of your config files
|
7 | > from a single source of truth. Not only will it scaffold them out, it can also
|
8 | > keep them in sync even if you modify them manually.
|
9 |
|
10 | Major use cases:
|
11 |
|
12 | - Keeping separate repos in sync.
|
13 | - Keeping monorepo packages in sync.
|
14 | - Scaffolding out new projects.
|
15 |
|
16 | ## Install
|
17 |
|
18 | ```sh
|
19 | npm i -D conartist
|
20 | ```
|
21 |
|
22 | Conartist can be configured by any one of the following:
|
23 |
|
24 | - `conartist` field in the `package.json`
|
25 | - `.conartistrc`
|
26 | - `.conartistrc.json`
|
27 | - `.conartistrc.yaml`
|
28 | - `.conartistrc.yml`
|
29 | - `.conartistrc.js`
|
30 | - `.conartist.config.js`
|
31 |
|
32 | If you use a `.js` file, you will be able to have finer-grained control if you
|
33 | require it. See the secion on [custom file handlers](#custom-file-handlers) for
|
34 | more information.
|
35 |
|
36 | ## Usage
|
37 |
|
38 | If you put the following in a `package.json`.
|
39 |
|
40 | ```json
|
41 | {
|
42 | "conartist": {
|
43 | ".gitignore": ["*.log", "node_modules"],
|
44 | ".nvmrc": "10.9.0",
|
45 | ".travis.yml": "language: node_js"
|
46 | }
|
47 | }
|
48 | ```
|
49 |
|
50 | Running `$ conartist` will create the specified files relative to the `cwd`.
|
51 | This is great for scaffolding out a project or keeping it in sync with what the
|
52 | configuration has in it.
|
53 |
|
54 | ## Built-in file handlers
|
55 |
|
56 | The following are the built-in - and exported - file handlers.
|
57 |
|
58 | - [`handeArray`](#async-handlearrayfile-data)
|
59 | - [`handleJs`](#async-handlejsfile-data)
|
60 | - [`handleJson`](#async-handlejsonfile-data)
|
61 | - [`handleString`](#async-handlestringfile-data)
|
62 |
|
63 | These handlers will handle the following file patterns:
|
64 |
|
65 | ```js
|
66 | const mapGlob = {
|
67 | ".nvmrc": handleString,
|
68 | ".*rc": handleJson,
|
69 | ".*ignore": handleArray,
|
70 | "*.js": handleJs,
|
71 | "*.json": handleJson
|
72 | };
|
73 | ```
|
74 |
|
75 | And attempt to handle the following value types:
|
76 |
|
77 | ```js
|
78 | const mapType = {
|
79 | object: handleJson
|
80 | };
|
81 | ```
|
82 |
|
83 | If a handler cannot be found, it defaults to using `handleString`.
|
84 |
|
85 | ## Custom file handlers
|
86 |
|
87 | Custom file handlers require a `.js` file in order to be applied. There are two
|
88 | types of handlers you can use:
|
89 |
|
90 | - Global
|
91 | - File-specific
|
92 |
|
93 | ### Global file handlers
|
94 |
|
95 | Global file handlers handle all files in your configuration file. When you set a
|
96 | global handler, it overwrites the existing handler, so you must call
|
97 | [`getHandler`](#gethandler) and callback to it if you want to retain its
|
98 | functionality around your new one.
|
99 |
|
100 | You set a new handler by calling [`setHandler`](#sethandler-handler). You are
|
101 | responsible for returing the string that will be output to the file and for
|
102 | handling all types of files that you might set in your configuration.
|
103 |
|
104 | ```js
|
105 | // conartist.config.js
|
106 |
|
107 | const { getHandler, setHandler } = require("conartist");
|
108 |
|
109 | const previousHandler = getHandler();
|
110 |
|
111 | setHandler(function myCustomHandler(file, data) {
|
112 | if (file === ".gitignore") {
|
113 | return data.join("\n");
|
114 | }
|
115 | return previousHandler(file, data);
|
116 | });
|
117 |
|
118 | module.exports = {
|
119 | ".gitignore": ["*.log", "node_modules"]
|
120 | };
|
121 | ```
|
122 |
|
123 | ### File-specific handlers
|
124 |
|
125 | File-specific handlers are applied as a function to the corresponding file
|
126 | instead of other data types. The global file handler will then call this and
|
127 | write its return value to the file as a priority over any other handler.
|
128 |
|
129 | _If you overwrite the default global file handler, file-specific handlers will
|
130 | not work because their behaviour is provided by the default global file handler.
|
131 | If you need to override the global file handler and still retain this behaviour,
|
132 | it's up to you to implement it or call back to the default global file handler._
|
133 |
|
134 | ```js
|
135 | // conartist.config.js
|
136 |
|
137 | module.exports = {
|
138 | ".gitignore": (file, data) => data.join("\n")
|
139 | };
|
140 | ```
|
141 |
|
142 | ## Use cases
|
143 |
|
144 | File-specific handlers are a good way to compose in your own behaviour.
|
145 |
|
146 | ### Preventing merging
|
147 |
|
148 | You may _not_ want to merge JSON data from the config with what's already there,
|
149 | by default. To override this, all you need to do is provide a function that
|
150 | returns JSON:
|
151 |
|
152 | ```js
|
153 | // conartist.config.js
|
154 |
|
155 | module.exports = {
|
156 | "somefile.json": () => ({
|
157 | my: "data"
|
158 | })
|
159 | };
|
160 | ```
|
161 |
|
162 | In the above example, the config file would always overwrite any existing
|
163 | `somefile.json`. If you would rather the existing file overwrite the config, you
|
164 | can do something like:
|
165 |
|
166 | ```js
|
167 | const { loadJson } = require("conartist");
|
168 |
|
169 | module.exports = {
|
170 | "somefile.json": file =>
|
171 | loadJson(file) || {
|
172 | my: "data"
|
173 | }
|
174 | };
|
175 | ```
|
176 |
|
177 | ### Custom merging
|
178 |
|
179 | If you would prefer to implement custom merging, you might do something like:
|
180 |
|
181 | ```js
|
182 | const { loadJson } = require("conartist");
|
183 |
|
184 | module.exports = {
|
185 | "somefile.json": file => ({
|
186 | // Putting your data here means that it can
|
187 | // be overridden by existing data.
|
188 | my: "data",
|
189 |
|
190 | // loadJson returns null if no file is found.
|
191 | ...(loadJson(file) || {})
|
192 | })
|
193 | };
|
194 | ```
|
195 |
|
196 | Another use case is composing files together into another file. This can be
|
197 | useful if you have custom files that you want to compose together into a file.
|
198 |
|
199 | The following example will take data read from a `package.json` and generate a
|
200 | `README.md` from it:
|
201 |
|
202 | ```js
|
203 | const { loadJson } = require("conartist");
|
204 | const outdent = require("outdent");
|
205 |
|
206 | module.exports = {
|
207 | "README.md": async file => {
|
208 | const pkg = await loadJson("package.json");
|
209 | return outdent`
|
210 | # ${pkg.name}
|
211 |
|
212 | > ${pkg.description}
|
213 | `;
|
214 | }
|
215 | };
|
216 | ```
|
217 |
|
218 | _As shown above, you can also use `async` functions for file handlers!_
|
219 |
|
220 | ## API
|
221 |
|
222 | All exported API points are documented below.
|
223 |
|
224 | ### Global file handling
|
225 |
|
226 | APIs for handling all files.
|
227 |
|
228 | #### `getHandler()`
|
229 |
|
230 | Returns the current file handler.
|
231 |
|
232 | ```js
|
233 | const { getHandler } = require("conartist");
|
234 |
|
235 | // function defaultHandler() { ... }
|
236 | getHandler();
|
237 | ```
|
238 |
|
239 | #### `setHandler(handler)`
|
240 |
|
241 | Sets a new file handler, overwriting any current handler. If you require
|
242 | existing handler functionality, make sure you call `getHandler()` and callback
|
243 | to it.
|
244 |
|
245 | ```js
|
246 | const { getHandler, setHandler } = require("conartist");
|
247 |
|
248 | function customHandler() {}
|
249 | setHandler(customHandler);
|
250 |
|
251 | // true
|
252 | getHandler() === customHandler;
|
253 | ```
|
254 |
|
255 | ### File-specific handling
|
256 |
|
257 | APIs for handling specific files.
|
258 |
|
259 | #### `async handleArray(file, data)`
|
260 |
|
261 | Handles an array.
|
262 |
|
263 | ```js
|
264 | const { handleArray } = require("conartist");
|
265 |
|
266 | // my\narray
|
267 | await handleArray("somefile", ["my", "array"]);
|
268 | ```
|
269 |
|
270 | #### `async handleJs(file, data)`
|
271 |
|
272 | Handles JS code depending on the value type and applies it as `module.exports`.
|
273 |
|
274 | - `typeof` `string` - it is formatted and exported.
|
275 | - `typeof` `object` - it is stringified, formatted and exported.
|
276 |
|
277 | ```js
|
278 | const { handleJs } = require("conartist");
|
279 |
|
280 | // module.exports = { some: "data" };
|
281 | await handleJs("somefile", { some: "data" });
|
282 | ```
|
283 |
|
284 | #### `async handleJson(file, data)`
|
285 |
|
286 | Handles JSON. It can be a `string` or anyting that `JSON.parse()` handles.
|
287 |
|
288 | ```js
|
289 | const { handleJson } = require("conartist");
|
290 |
|
291 | // { some: "data" };
|
292 | await handleJson("somefile", { some: "data" });
|
293 | ```
|
294 |
|
295 | #### `async handleString(file, data)`
|
296 |
|
297 | Handles a `string` by ensuring that whatever is passed in is converted to a
|
298 | `string`.
|
299 |
|
300 | ```js
|
301 | const { handleString } = require("conartist");
|
302 |
|
303 | // [object Object]
|
304 | await handleString("somefile", { some: "data" });
|
305 | ```
|
306 |
|
307 | ### Formatting
|
308 |
|
309 | APIs for formatting data types.
|
310 |
|
311 | ### `formatCode`
|
312 |
|
313 | Formats JavaScript code using Prettier and the `babel` parser.
|
314 |
|
315 | ```js
|
316 | const { formatCode } = require("conartist");
|
317 |
|
318 | // { some: "data" }
|
319 | formatCode('{"some":"data"}');
|
320 | ```
|
321 |
|
322 | ### `formatJson`
|
323 |
|
324 | Formats JSON using `JSON.stringify(json, null, 2)`.
|
325 |
|
326 | ```js
|
327 | const { formatJson } = require("conartist");
|
328 |
|
329 | // {
|
330 | // "some": "data"
|
331 | // }
|
332 | formatJson('{"some":"data"}');
|
333 | ```
|
334 |
|
335 | ### Processing
|
336 |
|
337 | #### `async process(config)`
|
338 |
|
339 | Syncs the configuration with what's on the file system using the file handlers.
|
340 |
|
341 | ```js
|
342 | const { process } = require("conartist");
|
343 |
|
344 | await process({
|
345 | "package.json": { name: "my-project" }
|
346 | });
|
347 | ```
|
348 |
|
349 | ### Utils
|
350 |
|
351 | General utils for loading configs and reading files.
|
352 |
|
353 | #### `async getConfig`
|
354 |
|
355 | Returns the current configuration from the configuration file.
|
356 |
|
357 | ```js
|
358 | const { getConfig } = require("conartist");
|
359 |
|
360 | // { ... }
|
361 | await getConfig();
|
362 | ```
|
363 |
|
364 | #### `filePath(file)`
|
365 |
|
366 | Returns the full path to the file relative to the `cwd`.
|
367 |
|
368 | ```js
|
369 | const { filePath } = require("conartist");
|
370 |
|
371 | // "/path/to/cwd/package.json"
|
372 | filePath("package.json");
|
373 | ```
|
374 |
|
375 | #### `async loadFile(file)`
|
376 |
|
377 | Loads a file using `require` relative to the `cwd`. If it does not exist, it
|
378 | returns `null`.
|
379 |
|
380 | ```js
|
381 | const { loadFile } = require("conartist");
|
382 |
|
383 | // { ... }
|
384 | loadFile("package.json");
|
385 | ```
|
386 |
|
387 | #### `async readFile(file)`
|
388 |
|
389 | Reads a file into a `string` relative to the `cwd`. If it does not exist, it
|
390 | returns `null`.
|
391 |
|
392 | ```js
|
393 | const { readFile } = require("conartist");
|
394 |
|
395 | // { ... }
|
396 | readFile("package.json");
|
397 | ```
|
398 |
|
399 | #### `async readJson(file)`
|
400 |
|
401 | Reads a file into JSON relative to the `cwd`. If it does not exist, it returns
|
402 | `null`.
|
403 |
|
404 | ```js
|
405 | const { readJson } = require("conartist");
|
406 |
|
407 | // { ... }
|
408 | readJson("package.json");
|
409 | ```
|