UNPKG

2.56 kBJavaScriptView Raw
1const fs = require('fs');
2const path = require('path');
3const debug = require('debug')('metalsmith-changed');
4const mm = require('micromatch');
5const DEFAULTS = {
6 force: false,
7 forcePattern: false,
8 forceAllPattern: false,
9 ctimes: 'metalsmith-changed-ctimes.json' // where to store ctimes
10};
11
12module.exports = function (opts) {
13 opts = Object.assign({}, DEFAULTS, opts);
14 debug(`options: ${JSON.stringify(opts)}`);
15
16 /**
17 * Return true if filename does not match `opts.forcePattern`.
18 */
19 const notForced = function (filename) {
20 const pattern = opts.forcePattern;
21 return pattern === false || !mm.any(filename, pattern);
22 };
23
24 return function changed (files, metalsmith, done) {
25 // files are already read => safe to write current ctimes
26 files[opts.ctimes] = createCtimes(files);
27 if (metalsmith.clean() || opts.force ||
28 contains(files, opts.forceAllPattern)) {
29 debug('building all files');
30 } else {
31 const prevCtimes = readCtimes(metalsmith.destination(), opts.ctimes);
32 const filenames = Object.keys(files).filter(notForced);
33 for (let f of filenames) {
34 if (!hasCtime(files[f])) {
35 debug(`${f} does not have ctime`);
36 continue;
37 }
38 // file has not changed
39 if (prevCtimes[f] && files[f].stats.ctime.getTime() === prevCtimes[f]) {
40 debug(`skipping ${f}`);
41 delete files[f];
42 }
43 }
44 }
45 done();
46 };
47};
48
49function hasCtime (file) {
50 return file.stats && file.stats.ctime;
51}
52
53/**
54 * Create a ctimes object of files.
55 *
56 * @param files {object}
57 * @returns {object} Ctimes, { filename: Date.getTime(), ... }
58 */
59function createCtimes (files) {
60 let ctimes = {}; // { 'index.md': 1464763631540, ... }
61 let filenames = Object.keys(files);
62 for (let f of filenames) {
63 if (!hasCtime(files[f])) {
64 continue;
65 }
66 let ctime = files[f].stats.ctime.getTime();
67 debug(`ctime ${f}: ${ctime}`);
68 ctimes[f] = ctime;
69 }
70 return { contents: JSON.stringify(ctimes, null, 2) };
71}
72
73/**
74 * Get ctimes from filename.
75 *
76 * @param folder {string} Path to destination folder.
77 * @param filename {string} Filename.
78 * @returns {object} Ctimes object.
79 */
80function readCtimes (folder, filename) {
81 try {
82 let content = fs.readFileSync(path.join(folder, filename), 'utf8');
83 return JSON.parse(content);
84 } catch (e) {
85 return {};
86 }
87}
88
89/**
90 * Returns true if some of the files matches the pattern.
91 */
92function contains (files, pattern) {
93 return mm(Object.keys(files), pattern).length !== 0;
94}