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

feat: Hapi.js starter template #189

Merged
merged 12 commits into from
Dec 5, 2020
2 changes: 2 additions & 0 deletions __e2e__/commands/add.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('mevn add', () => {
ENTER, // Choose universal as the rendering mode
ENTER, // Choose server as the deploy target
`Y${ENTER}`, // Requires server directory
ENTER, // Choose Express.js
],
tempDirPath,
);
Expand Down Expand Up @@ -126,6 +127,7 @@ describe('mevn add', () => {
[
ENTER, // Choose Default as the starter template
`Y${ENTER}`, // Requires server directory
ENTER, // Choose Express.js
],
tempDirPath,
);
Expand Down
2 changes: 2 additions & 0 deletions __e2e__/commands/codesplit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe('mevn codesplit', () => {
[
ENTER, // Choose basic template
`Y${ENTER}`, // Requires server directory
ENTER, // Choose Express.js
],
tempDirPath,
);
Expand Down Expand Up @@ -66,6 +67,7 @@ describe('mevn codesplit', () => {
`${DOWN}${ENTER}`, // Choose spa as the rendering mode
`${DOWN}${ENTER}`, // Choose static as the deploy target
`Y${ENTER}`, // Requires server directory
ENTER, // Choose Express.js
],
tempDirPath,
);
Expand Down
50 changes: 48 additions & 2 deletions __e2e__/commands/generate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('mevn generate', () => {
[
ENTER, // Choose Default as the starter template
`Y${ENTER}`, // Requires server directory
ENTER, // Choose Express.js
],
tempDirPath,
);
Expand Down Expand Up @@ -87,7 +88,7 @@ describe('mevn generate', () => {
);
});

it('generates CRUD Boilerplate within the server directory', async () => {
it('generates CRUD Boilerplate within the server(express) directory', async () => {
await runPromptWithAnswers(
['generate'],
[
Expand All @@ -98,7 +99,52 @@ describe('mevn generate', () => {
);

// .mevnrc
expect(fetchProjectConfig(genPath).isConfigured.server).toBe(true);
const projectConfig = fetchProjectConfig(genPath);
expect(projectConfig.isConfigured.server).toBe(true);
expect(projectConfig.serverTemplate).toBe('Express');

// Assert for generated files
const generatedFiles = [
'controllers/user_controller.js',
'models/user_schema.js',
'helpers/db/mongodb.js',
'.env',
];
generatedFiles.forEach((file) => {
expect(fs.existsSync(path.join(serverPath, file))).toBeTruthy();
});

// MongoDB URI path within .env
const envDotFile = fs.readFileSync(path.join(serverPath, '.env'), 'utf8');
expect(envDotFile).toBe('DB_URL=mongodb://localhost:27017/userdb');
});

it('generates CRUD Boilerplate within the server(hapi) directory', async () => {
await runPromptWithAnswers(
['init', 'my-hapi-app'],
[
ENTER, // Choose Default as the starter template
`Y${ENTER}`, // Requires server directory
`${DOWN}${ENTER}`, // Choose Hapi.js
],
tempDirPath,
);
const genPath = path.join(tempDirPath, 'my-hapi-app');
// The server directory
const serverPath = path.join(genPath, 'server');
await runPromptWithAnswers(
['generate'],
[
`${DOWN}${ENTER}`, // Choose CRUD Boilerplate
ENTER, // Default value for MongoDB URI
],
genPath,
);

// .mevnrc
const projectConfig = fetchProjectConfig(genPath);
expect(projectConfig.isConfigured.server).toBe(true);
expect(projectConfig.serverTemplate).toBe('Hapi');

// Assert for generated files
const generatedFiles = [
Expand Down
60 changes: 58 additions & 2 deletions __e2e__/commands/init.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ describe('mevn init', () => {
`${DOWN}${DOWN}${DOWN}${ENTER}`, // Choose Nuxt.js as the starter template
`${DOWN}${ENTER}`, // Choose spa as the rendering mode
`${DOWN}${ENTER}`, // Choose static as the deploy target
`${ENTER}`, // Requires server directory
`Y${ENTER}`, // Requires server directory
ENTER, // Choose Express.js
],
tempDirPath,
);
Expand All @@ -63,6 +64,7 @@ describe('mevn init', () => {
client: false,
server: false,
},
serverTemplate: 'Express',
};
expect(fetchProjectConfig(genPath)).toStrictEqual(projectConfigContent);

Expand Down Expand Up @@ -116,7 +118,8 @@ describe('mevn init', () => {
['init', 'my-app'],
[
`${DOWN}${ENTER}`, // Choose PWA as the starter template
`${ENTER}`, // Requires server directory
`Y${ENTER}`, // Requires server directory
ENTER, // Choose Express.js
],
tempDirPath,
);
Expand All @@ -141,4 +144,57 @@ describe('mevn init', () => {
fs.existsSync(path.join(clientPath, 'src', 'registerServiceWorker.js')),
).toBeTruthy();
});

it('creates webapp based on the Default starter template with Hapi server template', async () => {
const appGenPath = path.join(tempDirPath, 'default-hapi');
await runPromptWithAnswers(
['init', 'default-hapi'],
[
ENTER, // Choose Default as the starter template
`Y${ENTER}`, // Requires server directory
`${DOWN}${ENTER}`, // Choose Hapi.js as server template
],
tempDirPath,
);
// Check Hapi dependencies in package.json
const appServerPath = path.join(appGenPath, 'server');
const pkgJson = JSON.parse(
fs.readFileSync(path.join(appServerPath, 'package.json')),
);
expect(pkgJson.dependencies['@hapi/hapi']).toBeTruthy();
});

it('creates a new MEVN stack webapp based on the Default starter template with Express server template', async () => {
const appGenPath = path.join(tempDirPath, 'default-express');
await runPromptWithAnswers(
['init', 'default-express'],
[
ENTER, // Choose Default as the starter template
`Y${ENTER}`, // Requires server directory
ENTER, // Choose Express.js as server template
],
tempDirPath,
);
// Check Express dependencies in package.json
const appServerPath = path.join(appGenPath, 'server');
const pkgJson = JSON.parse(
fs.readFileSync(path.join(appServerPath, 'package.json')),
);
expect(pkgJson.dependencies['express']).toBeTruthy();
});

it('creates webapp based on the Default starter template with no server template', async () => {
const appGenPath = path.join(tempDirPath, 'default-no-server');
await runPromptWithAnswers(
['init', 'default-no-server'],
[
ENTER, // Choose Default as the starter template
`N${ENTER}`, // Server is not required
],
tempDirPath,
);
// project config for server should be undefiend
expect(fetchProjectConfig(appGenPath).isConfigured.server).toBe(undefined);
expect(fetchProjectConfig(appGenPath).isConfigured.client).toBe(false);
});
});
8 changes: 5 additions & 3 deletions src/commands/basic/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const generateFile = async () => {

// Fetch boilerplate template used from .mevnrc
const projectConfig = appData();
const { template, isConfigured } = projectConfig;
const { template, isConfigured, serverTemplate } = projectConfig;

if (type.includes('Component')) {
return generateComponent();
Expand All @@ -72,11 +72,13 @@ const generateFile = async () => {
const routesFilePath = path.join('server', 'routes', 'api.js');
fs.writeFileSync(
routesFilePath,
fs.readFileSync(path.join(templatePath, 'routes', 'index.js')),
fs.readFileSync(
path.join(templatePath, serverTemplate, 'routes', 'index.js'),
),
);

// Create controllers directory
createDir('controllers');
createDir(`${serverTemplate}/controllers`);

// Create models directory
createDir('models');
Expand Down
22 changes: 18 additions & 4 deletions src/commands/basic/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,17 +156,31 @@ const fetchTemplate = async (template) => {
client: false,
};

// Show up a suitable prompt whether if the user requires a Full stack application (Express.js)
// Show up a suitable prompt whether if the user requires a Full stack application (Default: Express.js)
const { requireServer } = await inquirer.prompt({
name: 'requireServer',
type: 'confirm',
message: 'Do you require server side template (Express.js)',
message: 'Do you require server side template',
name: 'requireServer',
});

// Copy server side template files to the destination as required
if (requireServer) {
// Configure path
const serverDir = template === 'GraphQL' ? 'GraphQL' : 'Default';
let serverDir = '';
if (template === 'GraphQL') serverDir = 'GraphQL';
else {
const { serverName } = await inquirer.prompt([
{
name: 'serverName',
type: 'list',
message: 'Please choose a server side template',
choices: ['Express', 'Hapi'],
},
]);
serverDir = serverName;
// To keep track of server template
projectConfig.serverTemplate = serverName;
}
const serverPath = ['templates', 'server', serverDir];
const source = path.join(__dirname, '..', '..', ...serverPath);
const dest = path.resolve(projectPathRelative);
Expand Down
File renamed without changes.
58 changes: 58 additions & 0 deletions src/templates/Hapi/controllers/user_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict';
const User = require('../models/user_schema');

const createData = async (request, reply) => {
try {
const user = await User.create(request.payload);
return reply.response(user).code(201);
} catch (err) {
return reply.response(err.message).code(500);
}
};

const readData = async (request, reply) => {
try {
const users = await User.find();
return reply.response(users).code(200);
} catch (err) {
return reply.response(err.message).code(500);
}
};

const updateData = async (request, reply) => {
try {
const user = await User.findByIdAndUpdate(
request.params.id,
request.payload,
{
useFindAndModify: false,
new: true,
},
);
return reply.response(user).code(201);
} catch (err) {
return reply.response(err.message).code(500);
}
};

const deleteData = async (request, reply) => {
try {
const user = await User.findById(request.params.id);

if (!user) {
return reply.response('User not available').code(400);
} else {
await user.remove();
return reply.response(user).code(200);
}
} catch (err) {
return reply.response(err.message).code(500);
}
};

module.exports = {
createData,
readData,
updateData,
deleteData,
};
39 changes: 39 additions & 0 deletions src/templates/Hapi/routes/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict';
const {
createData,
readData,
updateData,
deleteData,
} = require('../controllers/user_controller');

const user = {
name: 'user',
version: '1.0.0',
register: async function (server) {
server.route({
method: 'POST',
path: '/',
handler: createData,
});

server.route({
method: 'GET',
path: '/',
handler: readData,
});

server.route({
method: 'PUT',
path: '/{id}',
handler: updateData,
});

server.route({
method: 'DELETE',
path: '/{id}',
handler: deleteData,
});
},
};

exports.plugin = user;
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "server-template",
"name": "express-server-template",
"version": "1.0.0",
"description": "Express.js template",
"main": "server.js",
Expand Down
23 changes: 23 additions & 0 deletions src/templates/server/Hapi/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "hapi-server-template",
"version": "1.0.0",
"description": "Hapi.js template",
"main": "server.js",
"scripts": {
"dev": "node server.js",
"serve": "nodemon server.js",
"format": "prettier server.js **/*.js --write"
},
"author": "",
"license": "ISC",
"dependencies": {
"@hapi/hapi": "^20.0.1",
"@hapi/inert": "^6.0.3",
"@hapi/vision": "^6.0.1",
"dotenv": "^8.2.0"
},
"devDependencies": {
"nodemon": "^2.0.4",
"prettier": "^2.1.2"
}
}
17 changes: 17 additions & 0 deletions src/templates/server/Hapi/routes/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

const home = {
name: 'home',
version: '1.0.0',
register: async function (server) {
server.route({
method: 'GET',
path: '/',
handler: function (request, h) {
return h.file('./views/index.html');
},
});
},
};

exports.plugin = home;
Loading