bms-repository/commands/add-object/add-object.js
pfych 0029086b3f
Some checks failed
Build & Lint / build-lint (push) Failing after 2m10s
Deploy / setup (push) Failing after 1m3s
Init
2024-10-12 14:08:09 +11:00

288 lines
8.0 KiB
JavaScript
Executable File

#!/usr/bin/env node
// Template Fields
// - nameFirst
// - nameCamel
// - nameSnakeUpper
// - nameUpper
// - nameLower
// - nameKebab
// - apiCreateFields "field1, field2, field3"
// - apiUpdateFields "blankId, field1, field2, field3"
// - primaryKey "blankId"
// - seedData {"blankId": "", "field1": ""},{"blankId": "", "field1": ""}
// - mapperFields blankId: data?.blankId, field1: data?.field1
// - typeFields blankId: string; field1: string;
const fs = require('fs');
const readlineSync = require('readline-sync');
const YAML = require('js-yaml');
const functionNames = [
'And',
'Base64',
'Cidr',
'Condition',
'Equals',
'FindInMap',
'GetAtt',
'GetAZs',
'If',
'ImportValue',
'Join',
'Not',
'Or',
'Ref',
'Select',
'Split',
'Sub',
];
class CustomTag {
constructor(type, data) {
this.type = type;
this.data = data;
}
}
function yamlType(name, kind) {
const functionName = ['Ref', 'Condition'].includes(name) ? name : `!${name}`;
return new YAML.Type(`${functionName}`, {
kind,
multi: true,
representName: function (object) {
return object.type;
},
represent: function (object) {
return object.data;
},
instanceOf: CustomTag,
construct: function (data, type) {
return new CustomTag(type, data);
},
});
}
function generateTypes() {
const types = functionNames
.map((functionName) =>
['mapping', 'scalar', 'sequence'].map((kind) =>
yamlType(functionName, kind),
),
)
.flat();
return types;
}
const writeServerlessApiYaml = () => {
const yamlTypes = generateTypes();
const schema = YAML.DEFAULT_SCHEMA.extend(yamlTypes);
const serverlessFile = fs.readFileSync(
`${projectRoot}/packages/api/serverless.yml`,
'utf8',
);
const yamlJson = YAML.load(serverlessFile, { schema: schema });
const filenameName = `${toKebabCase(name.toLowerCase())}`;
const newFunction = `\${file(./src/baseblocks/${filenameName}/${filenameName}-functions.yml)}`;
const newResource = `\${file(./src/baseblocks/${filenameName}/${filenameName}-dynamodb.yml)}`;
if (
yamlJson.functions.find((i) => i === newFunction) ||
yamlJson.resources.find((i) => i === newResource)
) {
console.log('Conflicting resource/function in serverless.yml, not saving.');
return;
}
yamlJson.functions.push(newFunction);
yamlJson.resources.push(newResource);
yamlJson.provider.iam.role.statements[0].Resource.push(
new CustomTag('!Sub', `\${${toCamelCase(name)}Table.Arn}`),
new CustomTag('!Sub', `\${${toCamelCase(name)}Table.Arn}/index/*`),
);
yamlJson.custom['serverless-dynamodb'].seed.local.sources.push({
table: `\${env:APP_NAME}-\${opt:stage}-${filenameName}`,
sources: [`./src/baseblocks/${filenameName}/${filenameName}.seed.json`],
});
const yamlResult = YAML.dump(yamlJson, {
schema,
});
fs.writeFileSync(`${projectRoot}/packages/api/serverless.yml`, yamlResult);
};
const toCamelCase = (str) => {
return str
.replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
})
.replace(/\s+/g, '');
};
const toKebabCase = (str) =>
str &&
str
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
.map((x) => x.toLowerCase())
.join('-');
const cwd = process.cwd();
// console.log(`Current Working Dir: ${cwd}`);
const projectRoot = cwd.split('/commands')[0];
// console.log(`Project Root: ${projectRoot}`);
const templatePath = `${cwd}/template`;
// console.log(`Template Path: ${templatePath}`);
let name = readlineSync.question('What is the name of the new object? ');
console.log(`Creating new object [${name}]`);
// Support multi words in kebab case, pascal case or snake case
if (name.includes('-')) {
name = name.split('-').join(' ');
} else if (name.includes('_')) {
name = name.split('_').join(' ');
} else {
name = name
.replace(/([A-Z][a-z])/g, ' $1')
.replace(/(\d)/g, ' $1')
.trim();
}
const primaryKey = `${toCamelCase(name)}Id`;
const inputFields = [];
var fieldName = '';
do {
console.log('Current Fields:', [
primaryKey,
...inputFields.map((i) => i.name),
]);
fieldName = readlineSync.question('New field name (or enter to finish): ');
if (fieldName) {
const tsTypes = ['string', 'number', 'boolean', 'any', 'string[]'];
const index = readlineSync.keyInSelect(tsTypes, 'Type?');
const tsType = tsTypes[index];
const isRequired = readlineSync.keyInYN('is field required?');
console.log(
`Added field [${fieldName}${isRequired ? '' : '?'}: ${tsType}]\n`,
);
inputFields.push({
name: fieldName,
tsType: tsType,
isRequired: isRequired,
});
}
} while (fieldName);
const fields = inputFields.map((field) => field.name);
const allFields = [primaryKey, ...fields];
let dataTypeFields = ` ${primaryKey}: string;`;
inputFields.forEach((field) => {
dataTypeFields = `${dataTypeFields}\n ${field.name}${
field.isRequired ? '' : '?'
}: ${field.tsType};`;
});
let dataMapperFields = '';
allFields.forEach((field) => {
dataMapperFields = `${dataMapperFields}\n ${field}: data?.${field},`;
});
const data = {
name,
nameFirst: `${name[0].toUpperCase()}${toCamelCase(name.slice(1))}`,
nameCamel: `${toCamelCase(name)}`,
nameSnakeUpper: `${name.replace(/\s/g, '_').toUpperCase()}`,
nameUpper: `${name.toUpperCase()}`,
nameLower: `${name.toLowerCase()}`,
nameKebab: `${toKebabCase(name.toLowerCase())}`,
apiCreateFields: fields.join(', '),
apiUpdateFields: allFields.join(', '),
primaryKey: `${toCamelCase(name)}Id`,
seedData: ``,
mapperFields: dataMapperFields,
typeFields: dataTypeFields,
};
const renderTemplate = (template) => {
let generatedContent = template;
Object.keys(data).forEach((key) => {
generatedContent = generatedContent.replace(
new RegExp(`{{ ${key} }}`, 'g'),
data[key],
);
});
return generatedContent;
};
const apiOutputPath = `${projectRoot}/packages/api/src/baseblocks/${toKebabCase(
name.toLowerCase(),
)}`;
const filenameName = `${toKebabCase(name.toLowerCase())}`;
const files = [
{
templateFile: `${templatePath}/api/blank.ts`,
outputPath: apiOutputPath,
outputFilename: `${apiOutputPath}/${filenameName}.ts`,
},
{
templateFile: `${templatePath}/api/blank.service.ts`,
outputPath: apiOutputPath,
outputFilename: `${apiOutputPath}/${filenameName}.service.ts`,
},
{
templateFile: `${templatePath}/api/blank.seed.json`,
outputPath: apiOutputPath,
outputFilename: `${apiOutputPath}/${filenameName}.seed.json`,
},
{
templateFile: `${templatePath}/api/blank-functions.yml`,
outputPath: apiOutputPath,
outputFilename: `${apiOutputPath}/${filenameName}-functions.yml`,
},
{
templateFile: `${templatePath}/api/blank-dynamodb.yml`,
outputPath: apiOutputPath,
outputFilename: `${apiOutputPath}/${filenameName}-dynamodb.yml`,
},
{
templateFile: `${templatePath}/api/blank-api.ts`,
outputPath: apiOutputPath,
outputFilename: `${apiOutputPath}/${filenameName}-api.ts`,
},
{
templateFile: `${templatePath}/types/blank.d.ts`,
outputPath: `${projectRoot}/shared/types`,
outputFilename: `${projectRoot}/shared/types/${filenameName}.d.ts`,
},
{
templateFile: `${templatePath}/client-api/blank.ts`,
outputPath: `${projectRoot}/shared/client-api`,
outputFilename: `${projectRoot}/shared/client-api/${filenameName}.ts`,
},
];
const fileOperations = async (file) => {
const templateFileData = fs.readFileSync(file.templateFile).toString();
const result = renderTemplate(templateFileData);
await fs.promises.mkdir(file.outputPath, {
recursive: true,
});
console.log(`Creating ${file.outputFilename}`);
fs.writeFileSync(file.outputFilename, result);
};
(async () => {
console.log('Creating files...');
for (let filePos = 0; filePos < files.length; filePos++) {
const file = files[filePos];
await fileOperations(file);
}
console.log('Updating api serverless.yml');
writeServerlessApiYaml();
console.log('Done!');
})();