diff --git a/next.config.js b/next.config.js index df92e1fe..e1e15e82 100644 --- a/next.config.js +++ b/next.config.js @@ -5,17 +5,15 @@ const feed = require('./plugins/feed'); const sitemap = require('./plugins/sitemap'); const socialImages = require('./plugins/socialImages'); +// By enabling plugin debug logging, it will provide additional output details for +// diagnostic purposes. Is disabled by default. +// e.g., [feed, { debug: true }], module.exports = withPlugins([[indexSearch], [feed], [sitemap], [socialImages]], { // By default, Next.js removes the trailing slash. One reason this would be good // to include is by default, the `path` property of the router for the homepage // is `/` and by using that, would instantly create a redirect trailingSlash: true, - - // By enabling verbose logging, it will provide additional output details for - // diagnostic purposes. By default is set to false. - // verbose: true, - env: { WORDPRESS_GRAPHQL_ENDPOINT: process.env.WORDPRESS_GRAPHQL_ENDPOINT, WORDPRESS_MENU_LOCATION_NAVIGATION: process.env.WORDPRESS_MENU_LOCATION_NAVIGATION || 'PRIMARY', diff --git a/plugins/feed.js b/plugins/feed.js index 315459b7..cb0963bb 100644 --- a/plugins/feed.js +++ b/plugins/feed.js @@ -3,8 +3,14 @@ const { getFeedData, generateFeed } = require('./util'); const WebpackPluginCompiler = require('./plugin-compiler'); +class FeedPlugin extends WebpackPluginCompiler { + constructor(options = {}) { + super(options); + } +} + module.exports = function feed(nextConfig = {}) { - const { env, outputDirectory, outputName, verbose = false } = nextConfig; + const { env, outputDirectory, outputName, debug } = nextConfig; const plugin = { name: 'Feed', @@ -14,19 +20,33 @@ module.exports = function feed(nextConfig = {}) { generate: generateFeed, }; - const { WORDPRESS_GRAPHQL_ENDPOINT } = env; + // Reset properties to avoid shared configuration + if (Object.prototype.hasOwnProperty.call(nextConfig, 'outputDirectory')) nextConfig.outputDirectory = undefined; + if (Object.prototype.hasOwnProperty.call(nextConfig, 'outputName')) nextConfig.outputName = undefined; + if (Object.prototype.hasOwnProperty.call(nextConfig, 'debug')) nextConfig.debug = undefined; + const { WORDPRESS_GRAPHQL_ENDPOINT } = env; return Object.assign({}, nextConfig, { webpack(config, options) { if (config.watchOptions) { config.watchOptions.ignored.push(path.join('**', plugin.outputDirectory, plugin.outputName)); } + if (debug) { + const regex = new RegExp(plugin.name); + if (config.infrastructureLogging === undefined) { + config.infrastructureLogging = { + debug: [regex], + }; + } else { + config.infrastructureLogging.debug.push(regex); + } + } + config.plugins.push( - new WebpackPluginCompiler({ + new FeedPlugin({ url: WORDPRESS_GRAPHQL_ENDPOINT, plugin, - verbose, }) ); diff --git a/plugins/plugin-compiler.js b/plugins/plugin-compiler.js index 0a6fa256..1fed17a7 100644 --- a/plugins/plugin-compiler.js +++ b/plugins/plugin-compiler.js @@ -1,6 +1,6 @@ const path = require('path'); -const { createApolloClient, createFile, terminalColor, removeLastTrailingSlash } = require('./util'); +const { createApolloClient, createFile, removeLastTrailingSlash } = require('./util'); class WebpackPlugin { constructor(options = {}) { @@ -8,39 +8,39 @@ class WebpackPlugin { } async index(compilation, options) { - const { url, plugin, verbose = false } = options; + const { url, plugin } = options; + const logger = compilation.getInfrastructureLogger(plugin.name); try { plugin.outputLocation = path.join(plugin.outputDirectory, plugin.outputName); - - verbose && console.log(`[${plugin.name}] Compiling file ${plugin.outputLocation}`); + logger.log(`Compiling file ${plugin.outputLocation}`); const hasUrl = typeof url === 'string'; if (!hasUrl) { throw new Error( - `[${plugin.name}] Failed to compile: Please check that WORDPRESS_GRAPHQL_ENDPOINT is set and configured in your environment. WORDPRESS_HOST is no longer supported by default.` + `Failed to compile: Please check that WORDPRESS_GRAPHQL_ENDPOINT is set and configured in your environment. WORDPRESS_HOST is no longer supported by default.` ); } const apolloClient = createApolloClient(removeLastTrailingSlash(url)); - const data = await plugin.getData(apolloClient, plugin.name, verbose); + const data = await plugin.getData(apolloClient, logger); - const file = plugin.generate(data); + const file = plugin.generate(data, logger); if (file !== false) { - await createFile(file, plugin.name, plugin.outputDirectory, plugin.outputLocation, verbose); + await createFile(file, plugin.outputDirectory, plugin.outputLocation, logger); } //If there is an aditional action to perform if (plugin.postcreate) { - plugin.postcreate(plugin); + await plugin.postcreate(plugin, logger); } - !verbose && console.log(`Successfully created: ${terminalColor(plugin.outputName, 'info')}`); + logger.info(`Successfully created: ${plugin.outputName}`); } catch (e) { - console.error(`${terminalColor(e.message, 'error')}`); + logger.error(e.message); } } diff --git a/plugins/search-index.js b/plugins/search-index.js index 0d4f9f12..0fb7ce5d 100644 --- a/plugins/search-index.js +++ b/plugins/search-index.js @@ -3,8 +3,19 @@ const { getAllPosts, generateIndexSearch } = require('./util'); const WebpackPluginCompiler = require('./plugin-compiler'); +class SearchIndexPlugin extends WebpackPluginCompiler { + constructor(options = {}) { + super(options); + } +} + module.exports = function indexSearch(nextConfig = {}) { - const { env, outputDirectory, outputName, verbose = false } = nextConfig; + const { env, outputDirectory, outputName, debug } = nextConfig; + + // Reset properties to avoid shared configuration + if (Object.prototype.hasOwnProperty.call(nextConfig, 'outputDirectory')) nextConfig.outputDirectory = undefined; + if (Object.prototype.hasOwnProperty.call(nextConfig, 'outputName')) nextConfig.outputName = undefined; + if (Object.prototype.hasOwnProperty.call(nextConfig, 'debug')) nextConfig.debug = undefined; const plugin = { name: 'SearchIndex', @@ -22,11 +33,21 @@ module.exports = function indexSearch(nextConfig = {}) { config.watchOptions.ignored.push(path.join('**', plugin.outputDirectory, plugin.outputName)); } + if (debug) { + const regex = new RegExp(plugin.name); + if (config.infrastructureLogging === undefined) { + config.infrastructureLogging = { + debug: [regex], + }; + } else { + config.infrastructureLogging.debug.push(regex); + } + } + config.plugins.push( - new WebpackPluginCompiler({ + new SearchIndexPlugin({ url: WORDPRESS_GRAPHQL_ENDPOINT, plugin, - verbose, }) ); diff --git a/plugins/sitemap.js b/plugins/sitemap.js index 548a60ec..8747a488 100644 --- a/plugins/sitemap.js +++ b/plugins/sitemap.js @@ -3,8 +3,19 @@ const { getSitemapData, generateSitemap, generateRobotsTxt } = require('./util') const WebpackPluginCompiler = require('./plugin-compiler'); +class SitemapPlugin extends WebpackPluginCompiler { + constructor(options = {}) { + super(options); + } +} + module.exports = function sitemap(nextConfig = {}) { - const { env, outputDirectory, outputName, verbose = false } = nextConfig; + const { env, outputDirectory, outputName, debug } = nextConfig; + + // Reset properties to avoid shared configuration + if (Object.prototype.hasOwnProperty.call(nextConfig, 'outputDirectory')) nextConfig.outputDirectory = undefined; + if (Object.prototype.hasOwnProperty.call(nextConfig, 'outputName')) nextConfig.outputName = undefined; + if (Object.prototype.hasOwnProperty.call(nextConfig, 'debug')) nextConfig.debug = undefined; const plugin = { name: 'Sitemap', @@ -23,11 +34,21 @@ module.exports = function sitemap(nextConfig = {}) { config.watchOptions.ignored.push(path.join('**', plugin.outputDirectory, plugin.outputName)); } + if (debug) { + const regex = new RegExp(plugin.name); + if (config.infrastructureLogging === undefined) { + config.infrastructureLogging = { + debug: [regex], + }; + } else { + config.infrastructureLogging.debug.push(regex); + } + } + config.plugins.push( - new WebpackPluginCompiler({ + new SitemapPlugin({ url: WORDPRESS_GRAPHQL_ENDPOINT, plugin, - verbose, }) ); diff --git a/plugins/socialImages.js b/plugins/socialImages.js index eebd15d4..6ba3af37 100644 --- a/plugins/socialImages.js +++ b/plugins/socialImages.js @@ -5,6 +5,12 @@ const { getAllPosts, mkdirp } = require('./util'); const WebpackPluginCompiler = require('./plugin-compiler'); +class SocialImagesPlugin extends WebpackPluginCompiler { + constructor(options = {}) { + super(options); + } +} + const pkg = require('../package.json'); module.exports = function sitemap(nextConfig = {}) { @@ -12,9 +18,14 @@ module.exports = function sitemap(nextConfig = {}) { env, outputDirectory = `./public${nextConfig.env.OG_IMAGE_DIRECTORY}`, outputName = '[slug].png', - verbose = false, + debug, } = nextConfig; + // Reset properties to avoid shared configuration + if (Object.prototype.hasOwnProperty.call(nextConfig, 'outputDirectory')) nextConfig.outputDirectory = undefined; + if (Object.prototype.hasOwnProperty.call(nextConfig, 'outputName')) nextConfig.outputName = undefined; + if (Object.prototype.hasOwnProperty.call(nextConfig, 'debug')) nextConfig.debug = undefined; + const width = 1012; const height = 506; const padding = 50; @@ -26,69 +37,73 @@ module.exports = function sitemap(nextConfig = {}) { outputDirectory, outputName, getData: getAllPosts, - generate: ({ posts = [] }) => { - // Make sure our directory exists before outputting the files - - mkdirp(outputDirectory); - - posts.forEach((post) => { - const { title, slug } = post; - - const canvas = new fabric.StaticCanvas(null, { - width, - height, - backgroundColor: 'white', - }); + generate: ({ posts = [] }, logger) => { + try { + // Make sure our directory exists before outputting the files + mkdirp(outputDirectory); - const headlineWidth = (width / 3) * 2; - const headlineHeight = height - padding * 2 - footerHeight; - - const headline = new fabric.Textbox(title, { - left: (width - headlineWidth) / 2, - top: height / 2 - footerHeight, - originY: 'center', - width: headlineWidth, - height: headlineHeight, - fill: '#303030', - fontFamily: 'Arial', - fontWeight: 600, - fontSize: 60, - lineHeight: 1, - textAlign: 'center', - }); + posts.forEach((post) => { + const { title, slug } = post; - canvas.add(headline); + const canvas = new fabric.StaticCanvas(null, { + width, + height, + backgroundColor: 'white', + }); - const homepage = pkg.homepage && pkg.homepage.replace(/http(s)?:\/\//, ''); + const headlineWidth = (width / 3) * 2; + const headlineHeight = height - padding * 2 - footerHeight; - if (homepage) { - const website = new fabric.Textbox(homepage, { - left: 0, - top: height - padding / 2 - footerHeight, - width, - height: footerHeight, + const headline = new fabric.Textbox(title, { + left: (width - headlineWidth) / 2, + top: height / 2 - footerHeight, + originY: 'center', + width: headlineWidth, + height: headlineHeight, fill: '#303030', fontFamily: 'Arial', fontWeight: 600, - fontSize: 30, + fontSize: 60, + lineHeight: 1, textAlign: 'center', }); - canvas.add(website); - } + canvas.add(headline); + + const homepage = pkg.homepage && pkg.homepage.replace(/http(s)?:\/\//, ''); + + if (homepage) { + const website = new fabric.Textbox(homepage, { + left: 0, + top: height - padding / 2 - footerHeight, + width, + height: footerHeight, + fill: '#303030', + fontFamily: 'Arial', + fontWeight: 600, + fontSize: 30, + textAlign: 'center', + }); - canvas.renderAll(); + canvas.add(website); + } - const outputPath = path.join(outputDirectory, outputName.replace('[slug]', slug)); - const out = fs.createWriteStream(outputPath); - const stream = canvas.createPNGStream(); + canvas.renderAll(); - stream.on('data', function (chunk) { - out.write(chunk); + const outputPath = path.join(outputDirectory, outputName.replace('[slug]', slug)); + const out = fs.createWriteStream(outputPath); + const stream = canvas.createPNGStream(); + + stream.on('data', function (chunk) { + out.write(chunk); + }); }); - }); - return false; + logger.log(`File generated`); + return false; + } catch (e) { + throw new Error(`Failed to generate: ${e.message}`); + } }, }; @@ -100,11 +115,21 @@ module.exports = function sitemap(nextConfig = {}) { config.watchOptions.ignored.push(path.join('**', plugin.outputDirectory, plugin.outputName)); } + if (debug) { + const regex = new RegExp(plugin.name); + if (config.infrastructureLogging === undefined) { + config.infrastructureLogging = { + debug: [regex], + }; + } else { + config.infrastructureLogging.debug.push(regex); + } + } + config.plugins.push( - new WebpackPluginCompiler({ + new SocialImagesPlugin({ url: WORDPRESS_GRAPHQL_ENDPOINT, plugin, - verbose, }) ); diff --git a/plugins/util.js b/plugins/util.js index cc005558..a7da8b3c 100644 --- a/plugins/util.js +++ b/plugins/util.js @@ -10,14 +10,14 @@ const config = require('../package.json'); * createFile */ -async function createFile(file, process, directory, location, verbose = false) { +async function createFile(file, directory, location, logger) { try { mkdirp(directory); - verbose && console.log(`[${process}] Created directory ${directory}`); + logger.log(`Created directory ${directory}`); await promiseToWriteFile(location, file); - verbose && console.log(`[${process}] Successfully wrote file to ${location}`); + logger.log(`Successfully wrote file to ${location}`); } catch (e) { - throw new Error(`[${process}] Failed to create file: ${e.message}`); + throw new Error(`Failed to create file: ${e.message}`); } } @@ -69,7 +69,7 @@ function createApolloClient(url) { * getAllPosts */ -async function getAllPosts(apolloClient, process, verbose = false) { +async function getAllPosts(apolloClient, logger) { const query = gql` { posts(first: 10000) { @@ -125,12 +125,12 @@ async function getAllPosts(apolloClient, process, verbose = false) { return data; }); - verbose && console.log(`[${process}] Successfully fetched posts from ${apolloClient.link.options.uri}`); + logger.log(`Successfully fetched posts from ${apolloClient.link.options.uri}`); return { posts, }; } catch (e) { - throw new Error(`[${process}] Failed to fetch posts from ${apolloClient.link.options.uri}: ${e.message}`); + throw new Error(`Failed to fetch posts from ${apolloClient.link.options.uri}: ${e.message}`); } } @@ -138,7 +138,7 @@ async function getAllPosts(apolloClient, process, verbose = false) { * getSiteMetadata */ -async function getSiteMetadata(apolloClient, process, verbose = false) { +async function getSiteMetadata(apolloClient, logger) { const query = gql` { generalSettings { @@ -161,12 +161,12 @@ async function getSiteMetadata(apolloClient, process, verbose = false) { metadata.language = metadata.language.split('_')[0]; } - verbose && console.log(`[${process}] Successfully fetched metadata from ${apolloClient.link.options.uri}`); + logger.log(`Successfully fetched metadata from ${apolloClient.link.options.uri}`); return { metadata, }; } catch (e) { - throw new Error(`[${process}] Failed to fetch metadata from ${apolloClient.link.options.uri}: ${e.message}`); + throw new Error(`Failed to fetch metadata from ${apolloClient.link.options.uri}: ${e.message}`); } } @@ -174,7 +174,7 @@ async function getSiteMetadata(apolloClient, process, verbose = false) { * getSitePages */ -async function getPages(apolloClient, process, verbose = false) { +async function getPages(apolloClient, logger) { const query = gql` { pages(first: 10000) { @@ -201,12 +201,12 @@ async function getPages(apolloClient, process, verbose = false) { }), ]; - verbose && console.log(`[${process}] Successfully fetched page slugs from ${apolloClient.link.options.uri}`); + logger.log(`Successfully fetched page slugs from ${apolloClient.link.options.uri}`); return { pages, }; } catch (e) { - throw new Error(`[${process}] Failed to fetch page slugs from ${apolloClient.link.options.uri}: ${e.message}`); + throw new Error(`Failed to fetch page slugs from ${apolloClient.link.options.uri}: ${e.message}`); } } @@ -214,9 +214,9 @@ async function getPages(apolloClient, process, verbose = false) { * getFeedData */ -async function getFeedData(apolloClient, process, verbose = false) { - const metadata = await getSiteMetadata(apolloClient, process, verbose); - const posts = await getAllPosts(apolloClient, process, verbose); +async function getFeedData(apolloClient, logger) { + const metadata = await getSiteMetadata(apolloClient, logger); + const posts = await getAllPosts(apolloClient, logger); return { ...metadata, @@ -228,9 +228,9 @@ async function getFeedData(apolloClient, process, verbose = false) { * getFeedData */ -async function getSitemapData(apolloClient, process, verbose = false) { - const posts = await getAllPosts(apolloClient, process, verbose); - const pages = await getPages(apolloClient, process, verbose); +async function getSitemapData(apolloClient, logger) { + const posts = await getAllPosts(apolloClient, logger); + const pages = await getPages(apolloClient, logger); return { ...posts, @@ -242,69 +242,81 @@ async function getSitemapData(apolloClient, process, verbose = false) { * generateFeed */ -function generateFeed({ posts = [], metadata = {} }) { +function generateFeed({ posts = [], metadata = {} }, logger) { const { homepage = '' } = config; - const feed = new RSS({ - title: metadata.title || '', - description: metadata.description, - site_url: homepage, - feed_url: `${homepage}/feed.xml`, - copyright: `${new Date().getFullYear()} ${metadata.title}`, - language: metadata.language, - pubDate: new Date(), - }); + try { + const feed = new RSS({ + title: metadata.title || '', + description: metadata.description, + site_url: homepage, + feed_url: `${homepage}/feed.xml`, + copyright: `${new Date().getFullYear()} ${metadata.title}`, + language: metadata.language, + pubDate: new Date(), + }); - posts.map((post) => { - feed.item({ - title: post.title, - guid: `${homepage}/posts/${post.slug}`, - url: `${homepage}/posts/${post.slug}`, - date: post.date, - description: post.excerpt, - author: post.author, - categories: post.categories || [], + posts.map((post) => { + feed.item({ + title: post.title, + guid: `${homepage}/posts/${post.slug}`, + url: `${homepage}/posts/${post.slug}`, + date: post.date, + description: post.excerpt, + author: post.author, + categories: post.categories || [], + }); }); - }); - return feed.xml({ indent: true }); + logger.log(`File generated`); + + return feed.xml({ indent: true }); + } catch (e) { + throw new Error(`Failed to generate: ${e.message}`); + } } /** * generateIndexSearch */ -function generateIndexSearch({ posts }) { - const index = posts.map((post = {}) => { - // We need to decode the title because we're using the - // rendered version which assumes this value will be used - // within the DOM - - const title = he.decode(post.title); - - return { - title, - slug: post.slug, - date: post.date, - }; - }); +function generateIndexSearch({ posts }, logger) { + try { + const index = posts.map((post = {}) => { + // We need to decode the title because we're using the + // rendered version which assumes this value will be used + // within the DOM + + const title = he.decode(post.title); + + return { + title, + slug: post.slug, + date: post.date, + }; + }); - const indexJson = JSON.stringify({ - generated: Date.now(), - posts: index, - }); + const indexJson = JSON.stringify({ + generated: Date.now(), + posts: index, + }); - return indexJson; + logger.log(`File generated`); + return indexJson; + } catch (e) { + throw new Error(`Failed to generate: ${e.message}`); + } } /** * getSitemapData */ -function generateSitemap({ posts = [], pages = [] }) { - const { homepage = '' } = config; +function generateSitemap({ posts = [], pages = [] }, logger) { + try { + const { homepage = '' } = config; - const sitemap = ` + const sitemap = ` ${homepage} @@ -332,19 +344,23 @@ function generateSitemap({ posts = [], pages = [] }) { `; - const sitemapFormatted = prettier.format(sitemap, { - printWidth: 120, - parser: 'html', - }); + const sitemapFormatted = prettier.format(sitemap, { + printWidth: 120, + parser: 'html', + }); - return sitemapFormatted; + logger.log(`File generated`); + return sitemapFormatted; + } catch (e) { + throw new Error(`Failed to generate: ${e.message}`); + } } /** * generateRobotsTxt */ -async function generateRobotsTxt({ outputDirectory, outputName }) { +async function generateRobotsTxt({ outputDirectory, outputName }, logger) { const { homepage = '' } = config; try { @@ -366,9 +382,9 @@ async function generateRobotsTxt({ outputDirectory, outputName }) { const robots = `User-agent: *\nSitemap: ${sitemapUrl}`; // Create robots.txt always at root directory - await createFile(robots, 'Robots.txt', './public', './public/robots.txt'); + await createFile(robots, './public', './public/robots.txt', logger); } catch (e) { - throw new Error(`[Robots.txt] Failed to create robots.txt: ${e.message}`); + throw new Error(`Failed to generate robots.txt: ${e.message}`); } } diff --git a/public/sitemap.jpg b/public/sitemap.jpg deleted file mode 100644 index 66dc9051..00000000 --- a/public/sitemap.jpg +++ /dev/null @@ -1 +0,0 @@ -undefined \ No newline at end of file