Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabling default app/page templates for user workspace #52

Merged
merged 5 commits into from
Apr 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
67 changes: 27 additions & 40 deletions packages/cli/lib/init.js
Original file line number Diff line number Diff line change
@@ -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);
}
Expand Down
15 changes: 10 additions & 5 deletions packages/cli/lib/scaffold.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}';`);
Expand All @@ -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('/');
Expand Down Expand Up @@ -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 `<lit-route path="${file.route}" component="eve-${file.label}"></lit-route>\n\t\t\t\t`;
Expand Down
192 changes: 169 additions & 23 deletions test/cli.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)', () => {

Expand Down Expand Up @@ -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();
Expand All @@ -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);
});
Expand Down Expand Up @@ -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);
});
Expand All @@ -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);
});

});