diff --git a/packages/cli/src/plugins/copy/plugin-copy-sitemap.js b/packages/cli/src/plugins/copy/plugin-copy-sitemap.js new file mode 100644 index 000000000..5e80f9f21 --- /dev/null +++ b/packages/cli/src/plugins/copy/plugin-copy-sitemap.js @@ -0,0 +1,36 @@ +import fs from 'fs/promises'; + +async function writeSitemap(compilation) { + try { + const { scratchDir, projectDirectory } = compilation.context; + const adapterScratchUrl = new URL('./sitemap.xml', scratchDir); + + // Check if module exists + const sitemapModule = await import(`${projectDirectory}/src/sitemap.xml.js`); + const sitemap = await sitemapModule.generateSitemap(compilation); + + await fs.writeFile(adapterScratchUrl, sitemap); + console.info('Wrote sitemap to ./sitemap.xml'); + + return adapterScratchUrl; + } catch (error) { + console.error('Error in sitemapAdapter:', error); + } +} + +const greenwoodPluginCopySitemap = [{ + type: 'copy', + name: 'plugin-copy-sitemap', + provider: async (compilation) => { + + const { outputDir } = compilation.context; + const sitemapScratchUrl = await writeSitemap(compilation); + + return [{ + from: sitemapScratchUrl, + to: new URL('./sitemap.xml', outputDir) + }]; + } +}]; + +export { greenwoodPluginCopySitemap }; \ No newline at end of file diff --git a/packages/cli/src/plugins/resource/plugin-resource-sitemap.js b/packages/cli/src/plugins/resource/plugin-resource-sitemap.js new file mode 100644 index 000000000..0eb9b6c80 --- /dev/null +++ b/packages/cli/src/plugins/resource/plugin-resource-sitemap.js @@ -0,0 +1,37 @@ +import { ResourceInterface } from '@greenwood/cli/src/lib/resource-interface.js'; + +class SitemapResource extends ResourceInterface { + constructor(compilation, options) { + super(compilation, options); + } + + async shouldServe(url) { + return url.pathname.endsWith('sitemap.xml'); + } + + // eslint-disable-next-line no-unused-vars + async serve(url) { + + const { projectDirectory } = this.compilation.context; + + try { + const sitemapModule = await import(`${projectDirectory}/src/sitemap.xml.js`); + const sitemap = await sitemapModule.generateSitemap(this.compilation); + return new Response(sitemap, { headers: { 'Content-Type': 'text/xml' } }); + + } catch (error) { + console.error('Error loading module: ./sitemap.xml.js Does it exist?', error); + return new Response('Sitemap oops.', { headers: { 'Content-Type': 'text/xml' } }); + } + + } + +} + +const greenwoodPluginSitemap = { + type: 'resource', + name: 'plugin-resource-sitemap', + provider: (compilation, options) => new SitemapResource(compilation, options) +}; + +export { greenwoodPluginSitemap }; \ No newline at end of file diff --git a/packages/cli/test/cases/build.plugins.copy-sitemap/build.plugins.copy-sitemap.spec.js b/packages/cli/test/cases/build.plugins.copy-sitemap/build.plugins.copy-sitemap.spec.js new file mode 100644 index 000000000..1d91dbee4 --- /dev/null +++ b/packages/cli/test/cases/build.plugins.copy-sitemap/build.plugins.copy-sitemap.spec.js @@ -0,0 +1,84 @@ +/* + * Use Case + * Run Greenwood with the sitemap adapter plugin. + * + * User Result + * Should generate a static Greenwood build with a sitemap rendered. + * + * User Command + * greenwood build + * + * User Config + * import { greenwoodPluginAdapterSitemap } from '../../../src/index.js'; +* +* export default { +* plugins: [ +* greenwoodPluginAdapterSitemap() +* ] +* }; + * + * User Workspace + * TBD + */ +import chai from 'chai'; +import fs from 'fs/promises'; +import path from 'path'; +import { checkResourceExists } from '../../../../cli/src/lib/resource-utils.js'; +import { getSetupFiles } from '../../../../../test/utils.js'; +import { Runner } from 'gallinago'; +import { fileURLToPath } from 'url'; + +const expect = chai.expect; + +describe('Build Greenwood With: ', function() { + const LABEL = 'Sitemap Adapter plugin output'; + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + const publicDir = path.join(outputPath, 'public'); + + let runner; + + before(function() { + this.context = { + publicDir: path.join(outputPath, 'public') + }; + runner = new Runner(); + }); + + describe(LABEL, function() { + before(function() { + runner.setup(outputPath, getSetupFiles(outputPath)); + runner.runCommand(cliPath, 'build'); + }); + + describe('sitemap.xml', function() { + it('should be present', async function() { + const sitemapPath = path.join(publicDir, 'sitemap.xml'); + + const itExists = await checkResourceExists(new URL(`file://${sitemapPath}`)); + expect(itExists).to.be.equal(true); + + }); + + it('should have the correct first element in the list', async function() { + const sitemapPath = path.join(publicDir, 'sitemap.xml'); + const text = await fs.readFile(sitemapPath, 'utf8'); + + const regex = /(http:\/\/www\.example\.com\/about\/)<\/loc>/; + const match = text.match(regex); + + expect(match[1]).to.equal('http://www.example.com/about/'); + }); + + }); + + }); + + after(function() { + runner.stopCommand(); + runner.teardown([ + path.join(outputPath, '.greenwood') + ]); + }); + +}); \ No newline at end of file diff --git a/packages/cli/test/cases/build.plugins.copy-sitemap/greenwood.config.js b/packages/cli/test/cases/build.plugins.copy-sitemap/greenwood.config.js new file mode 100644 index 000000000..07b79d186 --- /dev/null +++ b/packages/cli/test/cases/build.plugins.copy-sitemap/greenwood.config.js @@ -0,0 +1,3 @@ +export default { + plugins: [] +}; \ No newline at end of file diff --git a/packages/cli/test/cases/build.plugins.copy-sitemap/src/pages/about.md b/packages/cli/test/cases/build.plugins.copy-sitemap/src/pages/about.md new file mode 100644 index 000000000..4093eb641 --- /dev/null +++ b/packages/cli/test/cases/build.plugins.copy-sitemap/src/pages/about.md @@ -0,0 +1,3 @@ +# About Us + +Lorem ipsum. \ No newline at end of file diff --git a/packages/cli/test/cases/build.plugins.copy-sitemap/src/pages/index.md b/packages/cli/test/cases/build.plugins.copy-sitemap/src/pages/index.md new file mode 100644 index 000000000..46c5f357e --- /dev/null +++ b/packages/cli/test/cases/build.plugins.copy-sitemap/src/pages/index.md @@ -0,0 +1,3 @@ +## Home Page + +Welcome! \ No newline at end of file diff --git a/packages/cli/test/cases/build.plugins.copy-sitemap/src/sitemap.xml.js b/packages/cli/test/cases/build.plugins.copy-sitemap/src/sitemap.xml.js new file mode 100644 index 000000000..7fbac0ba1 --- /dev/null +++ b/packages/cli/test/cases/build.plugins.copy-sitemap/src/sitemap.xml.js @@ -0,0 +1,15 @@ +async function generateSitemap(compilation) { + const urls = compilation.graph.map((page) => { + return ` + http://www.example.com${page.route} + `; + }); + return ` + + +${urls.join('\n')} + +`; +} + +export { generateSitemap }; \ No newline at end of file diff --git a/packages/cli/test/cases/develop.plugins.resource-sitemap/develop.plugins.resource-sitemap.spec.js b/packages/cli/test/cases/develop.plugins.resource-sitemap/develop.plugins.resource-sitemap.spec.js new file mode 100644 index 000000000..3e9efcee1 --- /dev/null +++ b/packages/cli/test/cases/develop.plugins.resource-sitemap/develop.plugins.resource-sitemap.spec.js @@ -0,0 +1,73 @@ + +import path from 'path'; +import { Runner } from 'gallinago'; +import { fileURLToPath } from 'url'; + +import chai from 'chai'; +const expect = chai.expect; + +describe('Develop Sitemap With: ', function() { + + const LABEL = 'Sitemap Resource plugin output'; + + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + const hostname = 'http://localhost'; + const port = 1984; + let runner; + + before(function() { + this.context = { + hostname: `${hostname}:${port}` + }; + runner = new Runner(); + }); + + describe(LABEL, function() { + + before(async function() { + runner.setup(outputPath); + + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 5000); + + runner.runCommand(cliPath, 'develop', { async: true }); + }); + }); + + describe('Sitemap.xml', function() { + let response = {}; + let text; + + before(async function() { + response = await fetch(`${hostname}:${port}/sitemap.xml`); + text = await response.text(); + }); + + it('should return a 200', function() { + expect(response.status).to.equal(200); + }); + + it('should return the correct content type', function() { + expect(response.headers.get('content-type')).to.equal('text/xml'); + }); + + it('should contain loc element', function() { + const regex = /(http:\/\/www\.example\.com\/about\/)<\/loc>/; + const match = text.match(regex); + + expect(match[1]).to.equal('http://www.example.com/about/'); + + }); + }); + }); + + after(function() { + runner.stopCommand(); + runner.teardown([ + path.join(outputPath, '.greenwood') + ]); + }); +}); \ No newline at end of file diff --git a/packages/cli/test/cases/develop.plugins.resource-sitemap/greenwood.config.js b/packages/cli/test/cases/develop.plugins.resource-sitemap/greenwood.config.js new file mode 100644 index 000000000..0e4a08694 --- /dev/null +++ b/packages/cli/test/cases/develop.plugins.resource-sitemap/greenwood.config.js @@ -0,0 +1,5 @@ + +export default { + plugins: [ + ] +}; \ No newline at end of file diff --git a/packages/cli/test/cases/develop.plugins.resource-sitemap/src/pages/about.md b/packages/cli/test/cases/develop.plugins.resource-sitemap/src/pages/about.md new file mode 100644 index 000000000..4093eb641 --- /dev/null +++ b/packages/cli/test/cases/develop.plugins.resource-sitemap/src/pages/about.md @@ -0,0 +1,3 @@ +# About Us + +Lorem ipsum. \ No newline at end of file diff --git a/packages/cli/test/cases/develop.plugins.resource-sitemap/src/pages/index.md b/packages/cli/test/cases/develop.plugins.resource-sitemap/src/pages/index.md new file mode 100644 index 000000000..46c5f357e --- /dev/null +++ b/packages/cli/test/cases/develop.plugins.resource-sitemap/src/pages/index.md @@ -0,0 +1,3 @@ +## Home Page + +Welcome! \ No newline at end of file diff --git a/packages/cli/test/cases/develop.plugins.resource-sitemap/src/sitemap.xml.js b/packages/cli/test/cases/develop.plugins.resource-sitemap/src/sitemap.xml.js new file mode 100644 index 000000000..2b1f8eba0 --- /dev/null +++ b/packages/cli/test/cases/develop.plugins.resource-sitemap/src/sitemap.xml.js @@ -0,0 +1,16 @@ +async function generateSitemap(compilation) { + const urls = compilation.graph.map((page) => { + return ` + http://www.example.com${page.route} + `; + }); + + return ` + + +${urls.join('\n')} + + `; +} + +export { generateSitemap }; \ No newline at end of file