From: Ruben Beltran del Rio Date: Fri, 9 Dec 2022 13:43:41 +0000 (+0100) Subject: Add rudimentary sync support X-Git-Tag: 5.0.0~15 X-Git-Url: https://git.r.bdr.sh/rbdr/blog/commitdiff_plain/c5cbbd3835ccd509179504cdf7d5e74356d7dca5?hp=24de2f063e5dfcad0086d1dc81de3cf012a00e4c Add rudimentary sync support --- diff --git a/.gitignore b/.gitignore index aac36ae..1294012 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,5 @@ static/index.txt .archive .gemlog +.blog +.blogremote diff --git a/bin/blog.js b/bin/blog.js index 8f0ab70..dc256ee 100755 --- a/bin/blog.js +++ b/bin/blog.js @@ -8,7 +8,17 @@ const Minimist = require('minimist'); const internals = { blog: new Blog(Config), - expectedKeys: ['add', 'generate', 'update', 'publish', 'publish-archive', 'version'], + expectedKeys: [ + 'add', + 'generate', + 'update', + 'publish', + 'publish-archive', + 'add-remote', + 'remove-remote', + 'sync-up', + 'sync-down', + 'version'], // Application entry point. Reads arguments and calls the // corresponding method from the blog lib @@ -52,6 +62,26 @@ const internals = { await internals.blog.publishArchive(value); return; } + + if (argument === 'add-remote') { + await internals.blog.addRemote(value); + return; + } + + if (argument === 'remove-remote') { + await internals.blog.removeRemote(); + return; + } + + if (argument === 'sync-up') { + await internals.blog.syncUp(); + return; + } + + if (argument === 'sync-down') { + await internals.blog.syncDown(); + return; + } } } @@ -96,6 +126,10 @@ const internals = { console.error('blog --generate \t\t\t(generates the blog assets)'); console.error('blog --publish \t\t(publishes the blog to an S3 bucket)'); console.error('blog --publish-archive \t(publishes the archive to a remote host)'); + console.error('blog --add-remote \t\t(adds or updates a git remote to sync with)'); + console.error('blog --remove-remote \t\t\t(removes the git remote)'); + console.error('blog --sync-up \t\t\t\t(pushes to the git remote if configured)'); + console.error('blog --sync-down \t\t\t(pulls from the git remote if configured)'); console.error('blog --version \t\t\t\t(print the version)'); } }; diff --git a/config/config.js b/config/config.js index 476bcfa..5a1f793 100644 --- a/config/config.js +++ b/config/config.js @@ -3,7 +3,9 @@ const Path = require('path'); const Getenv = require('getenv'); -const internals = {}; +const internals = { + blogDirectory: Getenv('BLOG_DIRECTORY', Path.resolve(Path.join(__dirname, '../.blog'))), +}; /** * The main configuration object for Blog. It will be used to @@ -25,8 +27,10 @@ const internals = {}; */ module.exports = internals.Config = { maxPosts: Getenv.int('BLOG_MAX_POSTS', 3), - postsDirectory: Getenv('BLOG_POSTS_DIRECTORY', Path.resolve(Path.join(__dirname, '../.posts'))), - archiveDirectory: Getenv('BLOG_ARCHIVE_DIRECTORY', Path.resolve(Path.join(__dirname, '../.archive'))), + blogDirectory: internals.blogDirectory, + postsDirectory: Getenv('BLOG_POSTS_DIRECTORY', Path.resolve(Path.join(internals.blogDirectory, 'posts'))), + archiveDirectory: Getenv('BLOG_ARCHIVE_DIRECTORY', Path.resolve(Path.join(internals.blogDirectory, 'archive'))), staticDirectory: Getenv('BLOG_STATIC_DIRECTORY', Path.resolve(Path.join(__dirname, '../static'))), - templatesDirectory: Getenv('BLOG_TEMPLATES_DIRECTORY', Path.resolve(Path.join(__dirname, '../templates'))) + templatesDirectory: Getenv('BLOG_TEMPLATES_DIRECTORY', Path.resolve(Path.join(__dirname, '../templates'))), + remoteConfig: Getenv('BLOG_REMOTE_CONFIG', Path.resolve(Path.join(__dirname, '../.blogremote'))), }; diff --git a/lib/blog.js b/lib/blog.js index 6b831f7..ba9ae53 100644 --- a/lib/blog.js +++ b/lib/blog.js @@ -1,8 +1,7 @@ 'use strict'; -const { access, mkdir, readdir, readFile, rm, writeFile } = require('fs/promises'); +const { access, cp, mkdir, readdir, readFile, rm, writeFile } = require('fs/promises'); const { exec } = require('child_process'); -const { ncp } = require('ncp'); const { basename, resolve, join } = require('path'); const ParseGemini = require('gemini-to-html/parse'); const RenderGemini = require('gemini-to-html/render'); @@ -19,11 +18,14 @@ const TXTGenerator = require('./generators/txt'); const GemlogArchiver = require('./archivers/gemlog'); +// Remote Handler + +const Remote = require('./remote'); + const internals = { // Promisified functions exec: promisify(exec), - ncp: promisify(ncp), debuglog: debuglog('blog'), @@ -69,6 +71,10 @@ module.exports = class Blog { async add(postLocation) { await this._ensurePostsDirectoryExists(); + try { + await this.syncDown(); + } + catch {}; await this._shift(); await this._ensurePostsDirectoryExists(join(this.postsDirectory, '0')); await this.update(postLocation); @@ -86,6 +92,10 @@ module.exports = class Blog { */ async update(postLocation) { + try { + await this.syncDown(); + } + catch {}; const metadata = await this._getMetadata(); await this._ensurePostsDirectoryExists(); await this._copyPost(postLocation); @@ -94,6 +104,10 @@ module.exports = class Blog { await this._archive(postLocation); await this.generate(); + try { + await this.syncUp(); + } + catch {}; } /** @@ -158,6 +172,55 @@ module.exports = class Blog { internals.debuglog('Finished publishing'); } + /** + * Adds a remote + * + * @function addRemote + * @memberof Blog + * @return {Promise} empty promise, returns no value + * @instance + */ + async addRemote(remote) { + await Remote.add(this.remoteConfig, remote) + } + + /** + * Removes a remote + * + * @function removeRemote + * @memberof Blog + * @return {Promise} empty promise, returns no value + * @instance + */ + async removeRemote() { + await Remote.remove(this.remoteConfig) + } + + + /** + * Pulls the posts and archive from the remote + * + * @function syncDown + * @memberof Blog + * @return {Promise} empty promise, returns no value + * @instance + */ + async syncDown() { + await Remote.syncDown(this.remoteConfig, this.blogDirectory) + } + + /** + * Pushes the posts and archive to the remote + * + * @function syncUp + * @memberof Blog + * @return {Promise} empty promise, returns no value + * @instance + */ + async syncUp() { + await Remote.syncUp(this.remoteConfig, this.blogDirectory) + } + // Parses Gemini for each page, copies assets and generates index. async generate() { @@ -238,7 +301,7 @@ module.exports = class Blog { await access(sourcePath); // check the source path internals.debuglog(`Shifting blog post ${sourcePath} to ${targetPath}`); - await internals.ncp(sourcePath, targetPath); + await cp(sourcePath, targetPath, { recursive: true }); } catch (error) { if (error.code === internals.kFileNotFoundError) { @@ -260,16 +323,12 @@ module.exports = class Blog { const targetPath = join(this.archiveDirectory, post.id); - try { - internals.debuglog(`Removing ${targetPath}`); - await rm(targetPath, { recursive: true }); - } - finally { - internals.debuglog(`Adding ${post.location} to ${targetPath}`); - await this._ensureDirectoryExists(targetPath); - await internals.ncp(post.location, targetPath); - internals.debuglog(`Added ${post.location} to ${targetPath}`); - } + internals.debuglog(`Removing ${targetPath}`); + await rm(targetPath, { recursive: true, force: true }); + internals.debuglog(`Adding ${post.location} to ${targetPath}`); + await this._ensureDirectoryExists(targetPath); + await cp(post.location, targetPath, { recursive: true }); + internals.debuglog(`Added ${post.location} to ${targetPath}`); } // Attempts to read existing metadata. Otherwise generates new set. @@ -313,14 +372,11 @@ module.exports = class Blog { const targetPost = join(targetPath, postName); internals.debuglog(`Removing ${targetPath}`); - try { - await rm(targetPath, { recursive: true }); - } - finally { - await this._ensureDirectoryExists(targetPath); - internals.debuglog(`Adding ${postLocation} to ${targetPost}`); - await internals.ncp(postLocation, targetPost); - } + await rm(targetPath, { recursive: true, force: true }); + await this._ensureDirectoryExists(targetPath); + internals.debuglog(`Adding ${postLocation} to ${targetPost}`); + await cp(postLocation, targetPost, { recursive: true }); + internals.debuglog(`Added ${postLocation} to ${targetPath}`); } // Ensures a directory exists. @@ -334,7 +390,7 @@ module.exports = class Blog { catch (error) { if (error.code === internals.kFileNotFoundError) { internals.debuglog(`Creating ${directory}`); - await mkdir(directory); + await mkdir(directory, { recursive: true }); return; } diff --git a/lib/remote.js b/lib/remote.js new file mode 100644 index 0000000..d8f73f9 --- /dev/null +++ b/lib/remote.js @@ -0,0 +1,48 @@ +const { readFile, rm, writeFile } = require('fs/promises'); + +const internals = { + strings: { + configurationNotFound: 'Remote configuration not set, consult help for more info.' + }, + strategies: [ + require('./remotes/git') + ] +}; + +module.exports = { + async add(remoteConfig, remote) { + await writeFile(remoteConfig, remote); + }, + + async remove(remoteConfig) { + await rm(remoteConfig, { force: true }) + }, + + async syncUp(remoteConfig, blogDirectory) { + this._executeMethodOnStrategy(remoteConfig, 'syncUp', blogDirectory); + }, + + async syncDown(remoteConfig, blogDirectory) { + this._executeMethodOnStrategy(remoteConfig, 'syncDown', blogDirectory); + }, + + async _executeMethodOnStrategy(remoteConfig, method, blogDirectory) { + const remote = await this._ensureConfiguration(remoteConfig); + + for (const strategy of internals.strategies) { + if (strategy.canHandle(remote)) { + await strategy[method](remote, blogDirectory); + } + } + }, + + async _ensureConfiguration(remoteConfig) { + try { + const configuration = await readFile(remoteConfig, { encoding: 'utf8' }); + return configuration; + } + catch { + throw new Error(internals.strings.configurationNotFound); + } + } +} diff --git a/lib/remotes/git.js b/lib/remotes/git.js new file mode 100644 index 0000000..d8e0cfd --- /dev/null +++ b/lib/remotes/git.js @@ -0,0 +1,30 @@ +const { exec } = require('child_process'); +const { debuglog, promisify } = require('util'); + +const internals = { + // Promisified functions + exec: promisify(exec), + + debuglog: debuglog('blog'), +}; + +module.exports = { + canHandle() { + // For the future: actually check if it's a valid git url + return true; + }, + + async syncUp(remote, blogDirectory) { + await internals.exec(`cd ${blogDirectory} && git init`); + await internals.exec(`cd ${blogDirectory} && git add .`); + await internals.exec(`cd ${blogDirectory} && git commit --allow-empty -m blog-sync-up-${Date.now()}`); + await internals.exec(`cd ${blogDirectory} && git push ${remote} main --force`); + }, + + async syncDown(remote, blogDirectory) { + await internals.exec(`cd ${blogDirectory} && git init`); + await internals.exec(`cd ${blogDirectory} && git checkout .`); + await internals.exec(`cd ${blogDirectory} && git clean . -f`); + await internals.exec(`cd ${blogDirectory} && git pull ${remote} main`); + } +}