Skip to content

Commit

Permalink
Merge 80509fc into 75af06e
Browse files Browse the repository at this point in the history
  • Loading branch information
moruezabal committed Feb 10, 2022
2 parents 75af06e + 80509fc commit 9c54563
Show file tree
Hide file tree
Showing 20 changed files with 988 additions and 17 deletions.
92 changes: 92 additions & 0 deletions lib/commands/create/function.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use strict';

const promptFunctionName = require('../../prompts/function-name');
const promptFunctionDefaultPath = require('../../prompts/function-default-path');

const functionWithPayloadSchemaTemplate = require('../../templates/function-with-payload-schema');
const functionWithoutPayloadSchemaTemplate = require('../../templates/function-without-payload-schema');
const functionSchemaFs = require('../../fs/function-schema');

const functionSourceTemplate = require('../../templates/function-source');
const functionSourceFs = require('../../fs/function-source');

const functionTestTemplate = require('../../templates/function-test');
const functionTestFs = require('../../fs/function-test');

const { ensureOptions, notEmpty, upperCamelCase } = require('../../utils');
const { Report } = require('../../report');


const getPrompts = () => [

promptFunctionName,
promptFunctionDefaultPath,
{
name: 'functionNewPath',
type: prev => (prev === true ? 'text' : null),
message: 'What\'s the functión name? (in-dash-case)',
validate: notEmpty,
format: functionName => upperCamelCase(functionName.trim())
},
{
name: 'hasPayload',
type: 'toggle',
message: 'Does the function have a payload?',
initial: true,
active: 'yes',
inactive: 'no'
},
{
name: 'hasClient',
type: prev => (prev === true ? 'toggle' : null),
message: 'Must your function be invoked with a client',
initial: true,
active: 'yes',
inactive: 'no'
}
];

module.exports.command = 'create-function';

module.exports.describe = 'Create a new Lambda Function';

module.exports.builder = yargs => {

return yargs
.option('functionName', {
alias: 'f',
description: 'The function Name',
type: 'string'
})
.help();
};

module.exports.handler = async argv => {

const options = await ensureOptions(getPrompts(), argv);

const {
functionName, functionDefaultPath, functionNewPath, hasClient, hasPayload
} = options;

const functionPath = functionDefaultPath && functionNewPath;

const promises = [];

// Function Schema
const schemaContent = hasPayload ? functionWithPayloadSchemaTemplate({ functionName }) : functionWithoutPayloadSchemaTemplate({ functionName });
promises.push(functionSchemaFs.writeSchema(functionPath, functionName, schemaContent));

// Function Source
const sourceContent = functionSourceTemplate({ functionName, hasClient, hasPayload });
promises.push(functionSourceFs.writeSource(functionPath, functionName, sourceContent));

// Function Test
const testContent = functionTestTemplate({ functionPath, functionName });
promises.push(functionTestFs.writeTest(functionPath, functionName, testContent, true));

// Wait every file to be generated
await Promise.all(promises);

await Report.finish();
};
38 changes: 21 additions & 17 deletions lib/fs/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@ const YAML = require('yamljs');
const { reportEvents, Report } = require('../report');
const open = require('../wrappers/open');

const getFilePath = relativePath => {
const getFilePath = (relativePath, sanitize = true) => {

const fullPath = path.isAbsolute(relativePath) ? relativePath : path.join(process.cwd(), relativePath);

const { dir, name, ext } = path.parse(fullPath);
if(sanitize) {
const { dir, name, ext } = path.parse(fullPath);

const cleanApiPath = `${dir}/${name}`
.replace(/{\w+}\/?/g, '')
.replace(/\/+$/, '')
.split('/')
.map(kebabcase)
.join('/');
const cleanApiPath = `${dir}/${name}`
.replace(/{\w+}\/?/g, '')
.replace(/\/+$/, '')
.split('/')
.map(kebabcase)
.join('/');

return `${cleanApiPath}${ext}`;
return `${cleanApiPath}${ext}`;
}

return fullPath;
};

module.exports.getFilePath = getFilePath;
Expand All @@ -42,23 +46,23 @@ module.exports.readJson = async relativePath => {
return JSON.parse(await fs.readFile(filePath, 'utf8'));
};

const writeFile = async (relativePathWithExt, rawContent, needsChanges) => {
const filePath = getFilePath(relativePathWithExt);
const writeFile = async (relativePathWithExt, rawContent, needsChanges, needsCleaning) => {
const filePath = getFilePath(relativePathWithExt, needsCleaning);
await fs.outputFile(filePath, rawContent);
Report.add(needsChanges ? reportEvents.FILE_NEEDS_CHANGES : reportEvents.FILE_CREATED, filePath);
};

module.exports.writeRaw = writeFile;

module.exports.writeYml = (relativePath, content, needsChanges) => {
return writeFile(`${relativePath}.yml`, YAML.stringify(content, Infinity, 2), needsChanges);
module.exports.writeYml = (relativePath, content, needsChanges, needsCleaning) => {
return writeFile(`${relativePath}.yml`, YAML.stringify(content, Infinity, 2), needsChanges, needsCleaning);
};

module.exports.writeJson = (relativePath, content, needsChanges) => {
return writeFile(`${relativePath}.json`, JSON.stringify(content, null, '\t'), needsChanges);
module.exports.writeJson = (relativePath, content, needsChanges, needsCleaning) => {
return writeFile(`${relativePath}.json`, JSON.stringify(content, null, '\t'), needsChanges, needsCleaning);
};

module.exports.openFile = relativePathWithExt => {
const filePath = getFilePath(relativePathWithExt);
module.exports.openFile = (relativePathWithExt, needsCleaning) => {
const filePath = getFilePath(relativePathWithExt, needsCleaning);
open.openFile(filePath);
};
15 changes: 15 additions & 0 deletions lib/fs/function-schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use strict';

const path = require('path');

const { writeYml, openFile } = require('./base');

const getRelativePath = (functionPath, functionName) => path.join('schemas/src', functionPath || '', functionName);

module.exports.writeSchema = (functionPath, functionName, content, needsChanges) => {
return writeYml(getRelativePath(functionPath, functionName), content, needsChanges, false);
};

module.exports.openSchema = (functionPath, functionName) => {
return openFile(getRelativePath(functionPath, functionName), false);
};
17 changes: 17 additions & 0 deletions lib/fs/function-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

const path = require('path');

const { getFilePath: baseGetFilePath, writeRaw, openFile } = require('./base');

const getRelativePath = (functionPath, functionName) => path.join(process.env.MS_PATH || '', 'lambda', functionPath || '', `${functionName}.js`);

module.exports.getFilePath = (functionPath, functionName) => baseGetFilePath(getRelativePath(functionPath, functionName), false);

module.exports.writeSource = (functionPath, functionName, content, needsChanges) => {
return writeRaw(getRelativePath(functionPath, functionName), content, needsChanges, false);
};

module.exports.openSource = (functionPath, functionName) => {
return openFile(getRelativePath(functionPath, functionName), false);
};
17 changes: 17 additions & 0 deletions lib/fs/function-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

const path = require('path');

const { getFilePath: baseGetFilePath, writeRaw, openFile } = require('./base');

const getRelativePath = (functionPath, functionName) => path.join('tests/lambda', functionPath || '', `${functionName}.js`);

module.exports.getFilePath = (functionPath, functionName) => baseGetFilePath(getRelativePath(functionPath, functionName), false);

module.exports.writeTest = (functionPath, functionName, content, needsChanges) => {
return writeRaw(getRelativePath(functionPath, functionName), content, needsChanges, false);
};

module.exports.openTest = (functionPath, functionName) => {
return openFile(getRelativePath(functionPath, functionName), false);
};
12 changes: 12 additions & 0 deletions lib/prompts/function-default-path.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict';

module.exports = {
type: 'select',
name: 'functionDefaultPath',
message: 'Select a function path',
choices: [
{ title: 'Default', description: '/src/lambda/{FunctionName}.js', value: false },
{ title: 'Other', description: 'Add a new path: /src/lambda/{path}/{FunctionName}.js', value: true }
],
initial: 0
};
11 changes: 11 additions & 0 deletions lib/prompts/function-name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

const { notEmpty, areLettersAndNumbers, upperCamelCase } = require('../utils');

module.exports = {
name: 'functionName',
type: 'text',
message: 'What\'s the functión name? (in-dash-case)',
validate: notEmpty && areLettersAndNumbers,
format: functionName => upperCamelCase(functionName.trim())
};
24 changes: 24 additions & 0 deletions lib/templates/function-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

module.exports = ({
functionName,
hasClient,
hasPayload
}) => {

const typeLambda = (payload, client) => (!payload && 'Lambda') || (!client && 'LambdaWithPayload') || 'LambdaWithClientAndPayload';

return `'use strict';
const { Handler, ${typeLambda(hasPayload, hasClient)} } = require('@janiscommerce/lambda');
class ${functionName} extends ${typeLambda(hasPayload, hasClient)} {
async process() {
// Process something
}
}
module.exports.handler = (...args) => Handler.handle(${functionName}, ...args);
`;
};
39 changes: 39 additions & 0 deletions lib/templates/function-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict';

const { getFilePath } = require('../fs/function-source');

module.exports = ({
functionPath,
functionName
}) => {

return `'use strict';
const sinon = require('sinon');
const { handler: ${functionName} } = require('${getFilePath(functionPath, functionName)}');
describe('Function ${functionName}', () => {
afterEach(() => {
sinon.restore();
});
context('When event is invalid', () => {
beforeEach(() => {
// Some spies maybe?
});
afterEach(() => {
// Some notCalled maybe?
});
});
context('When event is valid', () => {
const validEvent = {};
});
});
`;
};
26 changes: 26 additions & 0 deletions lib/templates/function-with-payload-schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use strict';

const apiSchemaTemplate = require('./api-schema');

module.exports = ({
functionName
}) => apiSchemaTemplate({
path: `/${functionName}`,
method: 'post',
operationId: `${functionName}`,
tags: ['Functions'],
summary: `${functionName}`,
requestBody: {
description: `${functionName}`,
required: true,
content: {
'application/json': {
}
}
},
responses: {
200: {
$ref: '#/components/responses/GenericSuccess'
}
}
});
18 changes: 18 additions & 0 deletions lib/templates/function-without-payload-schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

const apiSchemaTemplate = require('./api-schema');

module.exports = ({
functionName
}) => apiSchemaTemplate({
path: `/${functionName}`,
method: 'post',
operationId: `${functionName}`,
tags: ['Functions'],
summary: `${functionName}`,
responses: {
200: {
$ref: '#/components/responses/GenericSuccess'
}
}
});
6 changes: 6 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const prompts = require('prompts');
const camelcase = require('lodash.camelcase');

const booleanRegexp = /^(is|has|can|should)[A-Z0-9]/;
module.exports.booleanRegexp = booleanRegexp;
Expand All @@ -20,6 +21,11 @@ module.exports.normalizePath = path => {
.replace(/\/+$/, '');
};

module.exports.upperCamelCase = string => {
const formatCamelCase = camelcase(string);
return formatCamelCase.charAt(0).toUpperCase() + formatCamelCase.slice(1);
};

module.exports.getFieldSampleValue = fieldName => {
switch(fieldName) {
case 'id':
Expand Down

0 comments on commit 9c54563

Please sign in to comment.