diff --git a/gatsby-config.js b/gatsby-config.js index 624defe3b..e38f56011 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -37,6 +37,93 @@ module.exports = { 'gatsby-plugin-react-helmet', 'gatsby-plugin-sitemap', 'gatsby-transformer-json', + { + resolve: 'gatsby-plugin-feed', + options: { + query: ` + { + site { + siteMetadata { + siteTitle + siteDescription + siteUrl + } + } + } + `, + feeds: [ + { + serialize: ({ + query: { site, allHistoryJson, allMarkdownRemark }, + }) => { + // Create a lookup object for rules by file path + const rulesMap = {}; + allMarkdownRemark.nodes.forEach((rule) => { + const filePath = `rules/${rule.frontmatter.uri}/rule.md`; + rulesMap[filePath] = rule; + }); + + // Get recent updates from history, filter out archived rules + return allHistoryJson.edges + .filter((edge) => { + const rule = rulesMap[edge.node.file]; + return rule && !rule.frontmatter.archivedreason; + }) + .slice(0, 50) // Limit to 50 most recent updates + .map((edge) => { + const rule = rulesMap[edge.node.file]; + const url = `${site.siteMetadata.siteUrl}/${rule.frontmatter.uri}`; + + return { + title: rule.frontmatter.title, + description: + rule.frontmatter.seoDescription || + rule.excerpt || + `Updated by ${edge.node.lastUpdatedBy}`, + date: edge.node.lastUpdated, + url: url, + guid: rule.frontmatter.guid, + custom_elements: [ + { 'dc:creator': edge.node.lastUpdatedBy }, + { pubDate: edge.node.lastUpdated }, + ], + }; + }); + }, + query: ` + { + allHistoryJson(sort: {lastUpdated: DESC}) { + edges { + node { + file + lastUpdated + lastUpdatedBy + } + } + } + allMarkdownRemark(filter: {frontmatter: {type: {eq: "rule"}}}) { + nodes { + excerpt(pruneLength: 250) + frontmatter { + title + uri + archivedreason + seoDescription + } + } + } + } + `, + output: '/rss.xml', + title: 'SSW Rules - Recent Updates', + description: 'Stay updated with the latest changes to SSW Rules', + site_url: 'https://www.ssw.com.au/rules', + feed_url: 'https://www.ssw.com.au/rules/rss.xml', + language: 'en', + }, + ], + }, + }, 'gatsby-plugin-postcss', { resolve: 'gatsby-source-git', diff --git a/package.json b/package.json index 1ac57da87..3e035330d 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "gatsby-plugin-breadcrumb": "^12.3.2", "gatsby-plugin-client-side-redirect": "^1.1.0", "gatsby-plugin-decap-cms": "^4.0.4", + "gatsby-plugin-feed": "5.13.1", "gatsby-plugin-fontawesome-css": "^1.2.0", "gatsby-plugin-google-gtag": "^5.13.1", "gatsby-plugin-google-tagmanager": "^5.13.1", diff --git a/scripts/validate-rss-config.js b/scripts/validate-rss-config.js new file mode 100755 index 000000000..030e52b2b --- /dev/null +++ b/scripts/validate-rss-config.js @@ -0,0 +1,114 @@ +#!/usr/bin/env node +/* eslint-disable no-console */ + +/** + * This script validates the RSS feed configuration in gatsby-config.js + * It checks that the required configuration is present and properly structured. + */ + +const path = require('path'); + +console.log('šŸ” Validating RSS Feed Configuration...\n'); + +try { + // Load gatsby-config.js + const configPath = path.join(__dirname, '..', 'gatsby-config.js'); + const config = require(configPath); + + // Find the RSS feed plugin + const feedPlugin = config.plugins.find((plugin) => { + if (typeof plugin === 'object' && plugin.resolve) { + return plugin.resolve === 'gatsby-plugin-feed'; + } + return false; + }); + + if (!feedPlugin) { + console.error('āŒ gatsby-plugin-feed not found in gatsby-config.js'); + process.exit(1); + } + + console.log('āœ… gatsby-plugin-feed found in configuration'); + + // Validate feed options + if (!feedPlugin.options) { + console.error('āŒ Feed plugin has no options'); + process.exit(1); + } + + console.log('āœ… Feed plugin has options'); + + // Validate feeds array + if (!feedPlugin.options.feeds || !Array.isArray(feedPlugin.options.feeds)) { + console.error('āŒ Feed plugin has no feeds array'); + process.exit(1); + } + + console.log('āœ… Feed plugin has feeds array'); + + // Validate first feed + const feed = feedPlugin.options.feeds[0]; + if (!feed) { + console.error('āŒ No feed defined'); + process.exit(1); + } + + console.log('āœ… Feed is defined'); + + // Validate feed properties + const requiredProps = ['serialize', 'query', 'output', 'title']; + const missingProps = requiredProps.filter((prop) => !feed[prop]); + + if (missingProps.length > 0) { + console.error( + `āŒ Feed missing required properties: ${missingProps.join(', ')}` + ); + process.exit(1); + } + + console.log('āœ… Feed has all required properties'); + + // Validate output path + if (feed.output !== '/rss.xml') { + console.error(`āŒ Feed output path is ${feed.output}, expected /rss.xml`); + process.exit(1); + } + + console.log('āœ… Feed output path is /rss.xml'); + + // Validate feed metadata + console.log('\nFeed Metadata:'); + console.log(` Title: ${feed.title}`); + console.log(` Description: ${feed.description}`); + console.log(` Output: ${feed.output}`); + console.log(` Site URL: ${feed.site_url}`); + console.log(` Feed URL: ${feed.feed_url}`); + + // Validate query includes required fields + const requiredQueryFields = [ + 'allHistoryJson', + 'allMarkdownRemark', + 'lastUpdated', + 'lastUpdatedBy', + ]; + const queryMissing = requiredQueryFields.filter( + (field) => !feed.query.includes(field) + ); + + if (queryMissing.length > 0) { + console.error( + `āš ļø Warning: Feed query might be missing fields: ${queryMissing.join(', ')}` + ); + } else { + console.log('\nāœ… Feed query includes all expected fields'); + } + + console.log('\nāœ… RSS Feed configuration is valid!'); + console.log( + '\nšŸ’” The RSS feed will be available at /rss.xml after the site is built.' + ); + process.exit(0); +} catch (error) { + console.error('āŒ Error validating RSS feed configuration:', error.message); + process.exit(1); +} diff --git a/yarn.lock b/yarn.lock index b657a7e04..f6dba724c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6996,6 +6996,7 @@ __metadata: gatsby-plugin-breadcrumb: "npm:^12.3.2" gatsby-plugin-client-side-redirect: "npm:^1.1.0" gatsby-plugin-decap-cms: "npm:^4.0.4" + gatsby-plugin-feed: "npm:5.13.1" gatsby-plugin-fontawesome-css: "npm:^1.2.0" gatsby-plugin-google-gtag: "npm:^5.13.1" gatsby-plugin-google-tagmanager: "npm:^5.13.1" @@ -13857,6 +13858,24 @@ __metadata: languageName: node linkType: hard +"gatsby-plugin-feed@npm:5.13.1": + version: 5.13.1 + resolution: "gatsby-plugin-feed@npm:5.13.1" + dependencies: + "@babel/runtime": "npm:^7.20.13" + common-tags: "npm:^1.8.2" + fs-extra: "npm:^11.1.1" + gatsby-plugin-utils: "npm:^4.13.1" + lodash.merge: "npm:^4.6.2" + rss: "npm:^1.2.2" + peerDependencies: + gatsby: ^5.0.0-next + react: ^18.0.0 || ^0.0.0 + react-dom: ^18.0.0 || ^0.0.0 + checksum: 35e88d70f26ebf8fc959f0e800ead7ed96ae2c77fc6499cba22656ac8252b073554a02024bb3c858d9f380fb70c70aa20341a9a8d7c13b101c22143e29900366 + languageName: node + linkType: hard + "gatsby-plugin-fontawesome-css@npm:^1.2.0": version: 1.2.0 resolution: "gatsby-plugin-fontawesome-css@npm:1.2.0" @@ -18885,6 +18904,22 @@ __metadata: languageName: node linkType: hard +"mime-db@npm:~1.25.0": + version: 1.25.0 + resolution: "mime-db@npm:1.25.0" + checksum: a90951c80700314d6131e106cbf56ae94718d046e9669a5afd7f91e603248da328aad5b63314865fd9547f4688465ac62d1ace59e1ffdb73688452a28a9c3622 + languageName: node + linkType: hard + +"mime-types@npm:2.1.13": + version: 2.1.13 + resolution: "mime-types@npm:2.1.13" + dependencies: + mime-db: "npm:~1.25.0" + checksum: a53a0453f7863fa2d823025ce92978f5142a18528316caa53cbb1d4212be847973e9a5f2466362cbd2351a512e369252fc2c88c9c5bbc85df4bd8b42214f1785 + languageName: node + linkType: hard + "mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:^2.1.30, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" @@ -23541,6 +23576,16 @@ __metadata: languageName: node linkType: hard +"rss@npm:^1.2.2": + version: 1.2.2 + resolution: "rss@npm:1.2.2" + dependencies: + mime-types: "npm:2.1.13" + xml: "npm:1.0.1" + checksum: 7e37a6ed1820b69476c76e4fefe09f69dd7505408e9fc7690441a5190aca2e2e2faf8d19575fd1efd66debfb178893374fbff9abf68e5416f5e4d4cccb7ae8b9 + languageName: node + linkType: hard + "run-async@npm:^2.4.0": version: 2.4.1 resolution: "run-async@npm:2.4.1" @@ -27520,6 +27565,13 @@ __metadata: languageName: node linkType: hard +"xml@npm:1.0.1": + version: 1.0.1 + resolution: "xml@npm:1.0.1" + checksum: 04bcc9b8b5e7b49392072fbd9c6b0f0958bd8e8f8606fee460318e43991349a68cbc5384038d179ff15aef7d222285f69ca0f067f53d071084eb14c7fdb30411 + languageName: node + linkType: hard + "xmlbuilder@npm:^15.1.1": version: 15.1.1 resolution: "xmlbuilder@npm:15.1.1"