-
Notifications
You must be signed in to change notification settings - Fork 23
Add diffing logic #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,20 @@ | ||
const fs = require('fs'); | ||
const util = require('util'); | ||
const yaml = require('yaml'); | ||
|
||
const writeFile = util.promisify(fs.writeFile); | ||
|
||
module.exports.writeFrontmatterMarkdown = (filePath, { body = '', frontmatter = {} }) => { | ||
module.exports.writeFrontmatterMarkdown = ({ body = '', frontmatter = {} }) => { | ||
const lines = ['---', yaml.stringify(frontmatter).trim(), '---', body ? body.toString().trim() : '', '']; | ||
const content = lines.join('\n'); | ||
|
||
return writeFile(filePath, content); | ||
return content; | ||
}; | ||
|
||
module.exports.writeJSON = (filePath, data) => { | ||
module.exports.writeJSON = data => { | ||
const content = JSON.stringify(data, null, 2); | ||
|
||
return writeFile(filePath, content); | ||
return content; | ||
}; | ||
|
||
module.exports.writeYAML = function(filePath, data) { | ||
module.exports.writeYAML = function(data) { | ||
const content = yaml.stringify(data); | ||
|
||
return writeFile(filePath, content); | ||
return content; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,27 @@ | ||
const { cloneDeep } = require('lodash'); | ||
const debug = require('debug'); | ||
const { diff: generateDiff } = require('deep-diff'); | ||
const fs = require('fs'); | ||
const mkdirp = require('mkdirp'); | ||
const ora = require('ora'); | ||
const path = require('path'); | ||
const { cloneDeep } = require('lodash'); | ||
const util = require('util'); | ||
|
||
const { writeFrontmatterMarkdown, writeJSON, writeYAML } = require('./file-writers'); | ||
|
||
const FILE_WRITERS = { | ||
const fileWriters = { | ||
'frontmatter-md': writeFrontmatterMarkdown, | ||
json: writeJSON, | ||
yml: writeYAML | ||
}; | ||
const writeFile = util.promisify(fs.writeFile); | ||
|
||
class Sourcebit { | ||
constructor({ cacheFile = path.join(process.cwd(), '.sourcebit-cache.json'), runtimeParameters = {}, transformCallback } = {}) { | ||
this.cacheFilePath = cacheFile; | ||
this.context = {}; | ||
this.fileWriterCache = []; | ||
this.dataForPlugin = []; | ||
this.fileWriterCache = new Map(); | ||
this.onTransform = transformCallback; | ||
this.pluginBlocks = []; | ||
this.pluginModules = {}; | ||
|
@@ -206,7 +211,7 @@ class Sourcebit { | |
return queue; | ||
} | ||
|
||
return queue.then(data => { | ||
return queue.then(async data => { | ||
const plugin = this.pluginModules[index]; | ||
const pluginName = this.getNameOfPluginAtIndex(index); | ||
|
||
|
@@ -226,13 +231,29 @@ class Sourcebit { | |
return data; | ||
} | ||
|
||
return plugin.transform({ | ||
data, | ||
const { __diff, ...currentDataForPlugin } = data; | ||
const previousData = this.dataForPlugin[index] || initialData; | ||
const diffs = Object.keys(currentDataForPlugin).reduce((diffs, dataBucketKey) => { | ||
return { | ||
...diffs, | ||
[dataBucketKey]: generateDiff(previousData[dataBucketKey], currentDataForPlugin[dataBucketKey]) || [] | ||
}; | ||
}, {}); | ||
|
||
this.dataForPlugin[index] = currentDataForPlugin; | ||
|
||
const transformedData = await plugin.transform({ | ||
data: { | ||
...data, | ||
__diff: diffs | ||
}, | ||
debug: this.getDebugMethodForPlugin(pluginName), | ||
getPluginContext: () => contextSnapshot[pluginName] || {}, | ||
log: this.logFromPlugin.bind(this), | ||
options: this.parsePluginOptions(plugin, pluginBlock.options) | ||
}); | ||
|
||
return transformedData; | ||
Comment on lines
+234
to
+256
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like the data is passed to plugin Example:
Plugin code
This changes the value inside the Next time transform runs, the The data passed to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's true. I did consider this when I was working on the implementation, but I worry that by freezing/cloning the data we'll be adding too much effort. We already clone the entire data object on every transform, now we're talking about doing it also for every plugin, on every transform. I mean, we can do it if you feel that it's important, but it seems that we've been on the fence about the importance of the whole diff object concept, so I'm a bit weary about making it considerably more expensive. On the other hand, there's the argument that if we're doing it, we might as well do it right! 😄 What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "On every transform" you mean every transform of every plugin? Or every global transform run? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anyway, we should check if deep-freeze has any significant performance impact. If not, then I would safely use it. |
||
}); | ||
}, Promise.resolve(initialData)); | ||
|
||
|
@@ -305,11 +326,13 @@ class Sourcebit { | |
|
||
// We start by deleting any files that were previously created by this plugin | ||
// but that are not part of the site after the update. | ||
this.fileWriterCache.forEach(filePath => { | ||
this.fileWriterCache.forEach((_, filePath) => { | ||
if (!filesByPath[filePath]) { | ||
try { | ||
fs.unlinkSync(filePath); | ||
|
||
this.fileWriterCache.delete(filePath); | ||
|
||
this.log(`Deleted ${filePath}`, 'info'); | ||
} catch (error) { | ||
this.debug(error); | ||
|
@@ -318,12 +341,10 @@ class Sourcebit { | |
} | ||
}); | ||
|
||
this.fileWriterCache = Object.keys(filesByPath); | ||
|
||
// Now we write all the files that need to be created. | ||
const queue = Object.keys(filesByPath).map(async filePath => { | ||
const file = filesByPath[filePath]; | ||
const writerFunction = FILE_WRITERS[file.format]; | ||
const writerFunction = fileWriters[file.format]; | ||
|
||
if (typeof writerFunction !== 'function') { | ||
this.log(`Could not create ${filePath}. "${file.format}" is not a supported format.`, 'fail'); | ||
|
@@ -335,9 +356,21 @@ class Sourcebit { | |
mkdirp.sync(path.dirname(filePath)); | ||
|
||
try { | ||
await writerFunction(filePath, file.content); | ||
const fileContent = await writerFunction(file.content); | ||
const hasDiff = this.fileWriterCache.get(filePath) !== fileContent; | ||
|
||
// If the contents of the file hasn't changed since we last wrote it, we skip it. | ||
if (!hasDiff) { | ||
return true; | ||
} | ||
|
||
writeFile(filePath, fileContent); | ||
|
||
const isNewFile = Boolean(this.fileWriterCache.get(filePath)); | ||
|
||
this.fileWriterCache.set(filePath, fileContent); | ||
|
||
this.log(`Created ${filePath}`, 'succeed'); | ||
this.log(`${isNewFile ? 'Updated' : 'Created'} ${filePath}`, 'succeed'); | ||
|
||
return true; | ||
} catch (error) { | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eduardoboucas you can use this battle-tested utility
stringifyDataByFilePath
to write all sort of files:https://github.com/stackbithq/utils/blob/d1222b63ede765fb895668b641cb3f44c238d6ea/src/index.js#L373-L394
It takes into account several quirks that will prevent the app from crashing and store files safely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not go through the trouble of changing the implementation unless something needs fixing. The code you linked looks good, but we have slightly different requirements (e.g. we don't need to choose the writer based on the file path, that is provided to us by the user). Also we're using a different YAML package, which a different syntax.
So if you have any concerns about a specific part of the existing implementation, can you flag those specifically?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, then just look at the YAML implementation in the link I've sent you and add
noRefs
attribute to make sure that if an object has circular references it won't be split. Also what about supporting TOML and markdown files with TOML and YAML frontmatters? I know Hugo and 11ty make use of these.