diff --git a/README.md b/README.md index d6beeaeeb..d308f247b 100644 --- a/README.md +++ b/README.md @@ -143,8 +143,6 @@ Here are some of the features and capabiliites of Greenwood. > https://github.com/ProjectEvergreen/greenwood/issues/27 ### Templates -> TODO -> https://github.com/ProjectEvergreen/greenwood/issues/32 By default, Greenwood will supply its own [app-template](https://github.com/ProjectEvergreen/greenwood/blob/master/packages/cli/templates/app-template.js) and [page-template](https://github.com/ProjectEvergreen/greenwood/blob/master/packages/cli/templates/page-template.js). You can override these files by creating a src/templates directory in your application along with both template files. diff --git a/packages/cli/lib/init.js b/packages/cli/lib/init.js index 8f45668c1..5d89bd4cb 100644 --- a/packages/cli/lib/init.js +++ b/packages/cli/lib/init.js @@ -1,57 +1,44 @@ const fs = require('fs'); const path = require('path'); -const greenwoodWorkspace = path.join(__dirname, '..'); -const defaultTemplateDir = path.join(greenwoodWorkspace, 'templates/'); -const defaultSrc = path.join(process.cwd(), 'src'); +const defaultTemplatesDir = path.join(__dirname, '../templates/'); const scratchDir = path.join(process.cwd(), './.greenwood/'); - -const userWorkspace = fs.existsSync(defaultSrc) - ? defaultSrc - : defaultTemplateDir; - -const pagesDir = fs.existsSync(path.join(userWorkspace, 'pages')) - ? path.join(userWorkspace, 'pages/') - : defaultTemplateDir; - -const templatesDir = fs.existsSync(path.join(userWorkspace, 'templates')) - ? path.join(userWorkspace, 'templates/') - : defaultTemplateDir; +const publicDir = path.join(process.cwd(), './public'); module.exports = initContexts = async() => { return new Promise((resolve, reject) => { try { - - const context = { - userWorkspace, - pagesDir, + // TODO: replace user workspace src path based on config see issue #40 + // https://github.com/ProjectEvergreen/greenwood/issues/40 + const userWorkspace = path.join(process.cwd(), 'src'); + const userPagesDir = path.join(userWorkspace, 'pages/'); + const userTemplatesDir = path.join(userWorkspace, 'templates/'); + const userAppTemplate = path.join(userTemplatesDir, 'app-template.js'); + const userPageTemplate = path.join(userTemplatesDir, 'page-template.js'); + + const userHasWorkspace = fs.existsSync(userWorkspace); + const userHasWorkspacePages = fs.existsSync(userPagesDir); + const userHasWorkspaceTemplates = fs.existsSync(userTemplatesDir); + const userHasWorkspacePageTemplate = fs.existsSync(userPageTemplate); + const userHasWorkspaceAppTemplate = fs.existsSync(userAppTemplate); + + let context = { scratchDir, - templatesDir, - publicDir: path.join(process.cwd(), './public'), - pageTemplate: 'page-template.js', - appTemplate: 'app-template.js', + publicDir, + pagesDir: userHasWorkspacePages ? userPagesDir : defaultTemplatesDir, + templatesDir: userHasWorkspaceTemplates ? userTemplatesDir : defaultTemplatesDir, + userWorkspace: userHasWorkspace ? userWorkspace : defaultTemplatesDir, + pageTemplatePath: userHasWorkspacePageTemplate + ? userPageTemplate + : path.join(defaultTemplatesDir, 'page-template.js'), + appTemplatePath: userHasWorkspaceAppTemplate + ? userAppTemplate + : path.join(defaultTemplatesDir, 'app-template.js'), indexPageTemplate: 'index.html', notFoundPageTemplate: '404.html' }; - - // TODO allow per template overrides - if (fs.existsSync(context.templatesDir)) { - - // https://github.com/ProjectEvergreen/greenwood/issues/30 - if (!fs.existsSync(path.join(context.templatesDir, context.pageTemplate))) { - reject('It looks like you don\'t have a page template defined. \n' + - 'Please include a page-template.js in your templates directory. \n' + - 'See https://github.com/ProjectEvergreen/greenwood/blob/master/packages/cli/templates/page-template.js'); - } - // https://github.com/ProjectEvergreen/greenwood/issues/32 - if (!fs.existsSync(path.join(context.templatesDir, context.appTemplate))) { - reject('It looks like you don\'t have an app template defined. \n' + - 'Please include an app-template.js in your templates directory. \n' + - 'See https://github.com/ProjectEvergreen/greenwood/blob/master/packages/cli/templates/app-template.js'); - } - } if (!fs.existsSync(scratchDir)) { fs.mkdirSync(scratchDir); } diff --git a/packages/cli/lib/scaffold.js b/packages/cli/lib/scaffold.js index 5dc5f03c2..848d44715 100644 --- a/packages/cli/lib/scaffold.js +++ b/packages/cli/lib/scaffold.js @@ -2,11 +2,16 @@ const fs = require('fs'); const path = require('path'); const writePageComponentsFromTemplate = async (compilation) => { - const createPageComponent = async (file) => { + const createPageComponent = async (file, context) => { return new Promise(async (resolve, reject) => { try { - let data = await fs.readFileSync(path.join(compilation.context.templatesDir, `${file.template}-template.js`)); - let result = data.toString().replace(/entry/g, `wc-md-${file.label}`); + const pageTemplatePath = file.template === 'page' + ? context.pageTemplatePath + : path.join(context.templatesDir, `${file.template}-template.js`); + + const templateData = await fs.readFileSync(pageTemplatePath); + + let result = templateData.toString().replace(/entry/g, `wc-md-${file.label}`); result = result.replace(/page-template/g, `eve-${file.label}`); result = result.replace(/MDIMPORT;/, `import '${file.mdFile}';`); @@ -23,7 +28,7 @@ const writePageComponentsFromTemplate = async (compilation) => { return new Promise(async(resolve, reject) => { try { - let result = await createPageComponent(file); + let result = await createPageComponent(file, context); let relPageDir = file.filePath.substring(context.pagesDir.length, file.filePath.length); const pathLastBackslash = relPageDir.lastIndexOf('/'); @@ -66,7 +71,7 @@ const writeListImportFile = async (compilation) => { const writeRoutes = async(compilation) => { return new Promise(async (resolve, reject) => { try { - let data = await fs.readFileSync(path.join(compilation.context.templatesDir, `${compilation.context.appTemplate}`)); + let data = await fs.readFileSync(compilation.context.appTemplatePath); const routes = compilation.graph.map(file => { return `\n\t\t\t\t`; diff --git a/test/cli.spec.js b/test/cli.spec.js index 8c9e9ac2b..db41a1df4 100644 --- a/test/cli.spec.js +++ b/test/cli.spec.js @@ -5,6 +5,7 @@ const glob = require('glob-promise'); const TestSetup = require('./setup'); const jsdom = require('jsdom'); const { JSDOM } = jsdom; +let CONTEXT; describe('building greenwood with default context (no user workspace)', () => { @@ -93,7 +94,7 @@ describe('building greenwood with default context (no user workspace)', () => { }); describe('building greenwood with a user workspace w/custom nested pages directories', () => { - + before(async() => { setup = new TestSetup(); CONTEXT = await setup.init(); @@ -104,7 +105,7 @@ describe('building greenwood with a user workspace w/custom nested pages directo blogPageHtmlPath = path.join(CONTEXT.publicDir, 'blog', '20190326', 'index.html'); customFMPageHtmlPath = path.join(CONTEXT.publicDir, 'customfm', 'index.html'); }); - + it('should output one JS bundle', async() => { expect(await glob.promise(path.join(CONTEXT.publicDir, './**/index.*.bundle.js'))).to.have.lengthOf(1); }); @@ -153,13 +154,13 @@ describe('building greenwood with a user workspace w/custom nested pages directo }); it('should have the expected heading text within the customfm page in the customfm directory', async() => { - const heading = dom.window.document.querySelector('h3').textContent; + const heading = dom.window.document.querySelector('h3.wc-md-customfm').textContent; expect(heading).to.equal(defaultPageHeading); }); it('should have the expected paragraph text within the customfm page in the customfm directory', async() => { - let paragraph = dom.window.document.querySelector('p').textContent; + let paragraph = dom.window.document.querySelector('p.wc-md-customfm').textContent; expect(paragraph).to.equal(defaultPageBody); }); @@ -176,42 +177,187 @@ describe('building greenwood with a user workspace w/custom nested pages directo await fs.remove(CONTEXT.publicDir); await fs.remove(CONTEXT.scratchDir); }); - }); -// TODO - https://github.com/ProjectEvergreen/greenwood/issues/32 -// describe('building greenwood with a user workspace w/custom app-template override', () => { +describe('building greenwood with user workspace that doesn\'t contain app template', () => { + before(async() => { + setup = new TestSetup(); + CONTEXT = await setup.init(); + // copy test app + await fs.copy(CONTEXT.testApp, CONTEXT.userSrc); + await setup.run(['./packages/cli/index.js', 'build']); + await fs.removeSync(path.join(CONTEXT.userSrc, 'templates', 'app-template.js')); + + blogPageHtmlPath = path.join(CONTEXT.publicDir, 'blog', '20190326', 'index.html'); + }); + + it('should create a public directory', () => { + expect(fs.existsSync(CONTEXT.publicDir)).to.be.true; + }); + + describe('public directory output', () => { + it('should output a single index.html file (home page)', () => { + expect(fs.existsSync(path.join(CONTEXT.publicDir, './index.html'))).to.be.true; + }); -// }); + it('should output one JS bundle', async() => { + expect(await glob.promise(path.join(CONTEXT.publicDir, './**/index.*.bundle.js'))).to.have.lengthOf(1); + }); -describe('building greenwood with error handling for app and page templates', () => { - before(async () => { + it('should create a default hello page directory', () => { + expect(fs.existsSync(path.join(CONTEXT.publicDir, './hello'))).to.be.true; + }); + + describe('default generated hello page directory', () => { + const defaultHeading = 'Test App'; + const defaultBody = 'This is a test app using a custom user template!'; + let dom; + + beforeEach(async() => { + dom = await JSDOM.fromFile(path.resolve(CONTEXT.publicDir, 'hello/index.html')); + }); + + it('should output an index.html file within the default hello page directory', () => { + expect(fs.existsSync(path.join(CONTEXT.publicDir, './hello', './index.html'))).to.be.true; + }); + + it('should have the expected heading text within the hello example page in the hello directory', async() => { + const heading = dom.window.document.querySelector('h3').textContent; + + expect(heading).to.equal(defaultHeading); + }); + + it('should have the expected paragraph text within the hello example page in the hello directory', async() => { + let paragraph = dom.window.document.querySelector('p').textContent; + + expect(paragraph).to.equal(defaultBody); + }); + }); + }); + + it('should contain a nested blog page directory', () => { + expect(fs.existsSync(path.join(CONTEXT.publicDir, 'blog', '20190326'))).to.be.true; + }); + + describe('nested generated blog page directory', () => { + const defaultHeading = 'Blog Page'; + const defaultBody = 'This is the blog page built by Greenwood.'; + let dom; + + beforeEach(async() => { + dom = await JSDOM.fromFile(blogPageHtmlPath); + }); + + it('should contain a nested blog page with an index html file', () => { + expect(fs.existsSync(blogPageHtmlPath)).to.be.true; + }); + + it('should have the expected heading text within the blog page in the blog directory', async() => { + const heading = dom.window.document.querySelector('h3').textContent; + + expect(heading).to.equal(defaultHeading); + }); + + it('should have the expected paragraph text within the blog page in the blog directory', async() => { + let paragraph = dom.window.document.querySelector('p').textContent; + + expect(paragraph).to.equal(defaultBody); + }); + }); + after(async() => { + await fs.remove(CONTEXT.userSrc); + await fs.remove(CONTEXT.publicDir); + await fs.remove(CONTEXT.scratchDir); + }); +}); +describe('building greenwood with user workspace that doesn\'t contain page template', () => { + before(async() => { setup = new TestSetup(); CONTEXT = await setup.init(); + // copy test app + await fs.copy(CONTEXT.testApp, CONTEXT.userSrc); + await setup.run(['./packages/cli/index.js', 'build']); + await fs.removeSync(path.join(CONTEXT.userSrc, 'templates', 'page-template.js')); + + blogPageHtmlPath = path.join(CONTEXT.publicDir, 'blog', '20190326', 'index.html'); + }); - // create empty template directory - await fs.mkdirSync(CONTEXT.userSrc); - await fs.mkdirSync(CONTEXT.userTemplates); + it('should create a public directory', () => { + expect(fs.existsSync(CONTEXT.publicDir)).to.be.true; }); - it('should display an error if page-template.js is missing', async() => { - await setup.run(['./packages/cli/index.js'], '').catch((err) => { - expect(err).to.contain("It looks like you don't have a page template defined. "); + describe('public directory output', () => { + it('should output a single index.html file (home page)', () => { + expect(fs.existsSync(path.join(CONTEXT.publicDir, './index.html'))).to.be.true; }); - }); - it('should display an error if app-template.js is missing', async () => { - // add blank page-template - await fs.writeFileSync(path.join(CONTEXT.userTemplates, 'page-template.js'), ''); - await setup.run(['./packages/cli/index.js'], '').catch((err) => { - expect(err).to.contain("It looks like you don't have an app template defined. "); + it('should output one JS bundle', async() => { + expect(await glob.promise(path.join(CONTEXT.publicDir, './**/index.*.bundle.js'))).to.have.lengthOf(1); + }); + + it('should create a default hello page directory', () => { + expect(fs.existsSync(path.join(CONTEXT.publicDir, './hello'))).to.be.true; + }); + + describe('default generated hello page directory', () => { + const defaultHeading = 'Test App'; + const defaultBody = 'This is a test app using a custom user template!'; + let dom; + + beforeEach(async() => { + dom = await JSDOM.fromFile(path.resolve(CONTEXT.publicDir, 'hello/index.html')); + }); + + it('should output an index.html file within the default hello page directory', () => { + expect(fs.existsSync(path.join(CONTEXT.publicDir, './hello', './index.html'))).to.be.true; + }); + + it('should have the expected heading text within the hello example page in the hello directory', async() => { + const heading = dom.window.document.querySelector('h3').textContent; + + expect(heading).to.equal(defaultHeading); + }); + + it('should have the expected paragraph text within the hello example page in the hello directory', async() => { + let paragraph = dom.window.document.querySelector('p').textContent; + + expect(paragraph).to.equal(defaultBody); + }); }); }); + + it('should contain a nested blog page directory', () => { + expect(fs.existsSync(path.join(CONTEXT.publicDir, 'blog', '20190326'))).to.be.true; + }); + + describe('nested generated blog page directory', () => { + const defaultHeading = 'Blog Page'; + const defaultBody = 'This is the blog page built by Greenwood.'; + let dom; + + beforeEach(async() => { + dom = await JSDOM.fromFile(blogPageHtmlPath); + }); + + it('should contain a nested blog page with an index html file', () => { + expect(fs.existsSync(blogPageHtmlPath)).to.be.true; + }); + it('should have the expected heading text within the blog page in the blog directory', async() => { + const heading = dom.window.document.querySelector('h3').textContent; + + expect(heading).to.equal(defaultHeading); + }); + + it('should have the expected paragraph text within the blog page in the blog directory', async() => { + let paragraph = dom.window.document.querySelector('p').textContent; + + expect(paragraph).to.equal(defaultBody); + }); + }); after(async() => { await fs.remove(CONTEXT.userSrc); await fs.remove(CONTEXT.publicDir); await fs.remove(CONTEXT.scratchDir); }); - }); \ No newline at end of file