.archive
.gemlog
+.blog
+.blogremote
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
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;
+ }
}
}
console.error('blog --generate \t\t\t(generates the blog assets)');
console.error('blog --publish <bucket> \t\t(publishes the blog to an S3 bucket)');
console.error('blog --publish-archive <destination> \t(publishes the archive to a remote host)');
+ console.error('blog --add-remote <git_url> \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)');
}
};
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
*/
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'))),
};
'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');
const GemlogArchiver = require('./archivers/gemlog');
+// Remote Handler
+
+const Remote = require('./remote');
+
const internals = {
// Promisified functions
exec: promisify(exec),
- ncp: promisify(ncp),
debuglog: debuglog('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);
*/
async update(postLocation) {
+ try {
+ await this.syncDown();
+ }
+ catch {};
const metadata = await this._getMetadata();
await this._ensurePostsDirectoryExists();
await this._copyPost(postLocation);
await this._archive(postLocation);
await this.generate();
+ try {
+ await this.syncUp();
+ }
+ catch {};
}
/**
internals.debuglog('Finished publishing');
}
+ /**
+ * Adds a remote
+ *
+ * @function addRemote
+ * @memberof Blog
+ * @return {Promise<undefined>} empty promise, returns no value
+ * @instance
+ */
+ async addRemote(remote) {
+ await Remote.add(this.remoteConfig, remote)
+ }
+
+ /**
+ * Removes a remote
+ *
+ * @function removeRemote
+ * @memberof Blog
+ * @return {Promise<undefined>} 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<undefined>} 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<undefined>} 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() {
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) {
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.
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.
catch (error) {
if (error.code === internals.kFileNotFoundError) {
internals.debuglog(`Creating ${directory}`);
- await mkdir(directory);
+ await mkdir(directory, { recursive: true });
return;
}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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`);
+ }
+}