portfolio-generator
Version:
This package generates a bootstrap based portfolio website for you.
907 lines (867 loc) • 23.5 kB
JavaScript
const chalk = require("chalk");
const path = require("path");
const fs = require("fs-extra");
const { showError } = require("cybersaksham-npm-logs");
const prompts = require("prompts");
const packageJson = require("../../package.json");
const dummyData = require("./DummyData");
const datafiles = require("./datafiles.json");
// Validators
const trimmer = (val) => val.trim();
const emptyValidator = (val) => String(val).trim().length >= 1;
const birthDateChecker = (val) => {
let currDate = new Date();
if (val > currDate) return "Birthdate is in future. Welcome Alien!!";
return true;
};
const minmaxChecker = (val, min = null, max = null) => {
let value = val;
let lengthTypes = ["string", "object"];
let stringType = lengthTypes.indexOf(typeof val) !== -1;
let unit = "value";
if (stringType) {
value = val.length;
unit = "length";
}
if (min && value < min) return `Minimum ${min} ${unit} required.`;
if (max && value > max) return `Maximum ${max} ${unit} allowed.`;
return true;
};
const urlValidator = (url) => {
try {
new URL(url);
return true;
} catch (err) {
return "Enter a valid url with http or https protocols.";
}
};
const emailValidator = (email) => {
let result = email.match(
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
);
if (result) return true;
else return "Email in invalid.";
};
const applyMultipleValidators = (val, ...validators) => {
for (let i = 0; i < validators.length; i++) {
let result = validators[i](val);
if (typeof result === Boolean && result === true) continue;
return result;
}
return true;
};
const onCancel = (prompt) => {
showError({
code: 400,
errors: [
"Interactive input is interuppted by user.",
"The data was not saved. You have lost the generated data.",
`Run ${chalk.green(
"npx portfolio-generator"
)} again to re-generate data.`,
],
summary: [
`Run ${chalk.cyan(`${packageJson.name} --help`)} to see all options.`,
`Run ${chalk.cyan(
`${packageJson.name} --info`
)} to see environment information.`,
"",
`If you have any problems, do not hesitate to file an issue:`,
` ${chalk.cyan(packageJson.bugs.url)}`,
],
});
process.exit(1);
};
module.exports.aboutQuestions = async (root, dummy = false) => {
if (dummy) {
prompts.inject(dummyData.aboutData);
}
// About.js Data
let aboutData = await prompts(
[
{
type: "text",
name: "name",
message: "What is your name?",
format: trimmer,
validate: (val) => minmaxChecker(val, 3, 20),
},
{
type: "list",
separator: ",",
name: "about",
message: "Your professions seperated by ','?",
initial: "Full-Stack Developer, Designer",
format: (val) => val.join(", "),
validate: (about) => {
let arr = Array.from(about.split(",")).map((el) => el.trim());
for (let i = 0; i < arr.length; i++) {
const el = arr[i].trim();
if (!el || el.length < 1) return "Profession cannot be empty.";
if (arr.slice(0, i).indexOf(arr[i]) >= 0)
return "Duplicate professions are not allowed";
}
return true;
},
},
{
type: "text",
name: "shortBio",
min: 40,
max: 100,
message: "Give a one line bio?",
format: trimmer,
validate: (val) => minmaxChecker(val, 40, 100),
},
{
type: "text",
name: "longBio",
message: "Give a complete bio?",
format: trimmer,
validate: (val) => minmaxChecker(val, 125, 400),
},
{
type: "date",
name: "dob",
message: "Your birth date?",
validate: birthDateChecker,
},
{
type: "text",
name: "city",
message: "What is your city?",
format: trimmer,
validate: (val) => minmaxChecker(val, 3, 40),
},
{
type: "text",
name: "degree",
message: "What is your degree?",
format: trimmer,
validate: (val) => minmaxChecker(val, 2, 15),
},
],
{ onCancel }
);
if (aboutData.dob) {
// Cleaning up birthdate
aboutData.dob = new Date(aboutData.dob);
aboutData.birthDate = aboutData.dob.getDate();
aboutData.birthMonth = aboutData.dob.getMonth() + 1;
aboutData.birthYear = aboutData.dob.getFullYear();
delete aboutData.dob;
}
return aboutData;
};
module.exports.contactQuestions = async (root, dummy = false) => {
if (dummy) {
prompts.inject(dummyData.contactData);
}
// Contact.js Data
let contactData = await prompts(
[
{
type: "text",
name: "twitterLink",
message: "Twitter profile link?",
validate: urlValidator,
format: trimmer,
},
{
type: "text",
name: "instagramLink",
message: "Instagram profile link?",
validate: urlValidator,
format: trimmer,
},
{
type: "text",
name: "githubLink",
message: "Github profile link?",
validate: urlValidator,
format: trimmer,
},
{
type: "text",
name: "linkedinLink",
message: "Linkedin profile link?",
validate: urlValidator,
format: trimmer,
},
{
type: "text",
name: "phone",
message: "Phone no?",
validate: (val) => minmaxChecker(val, 1, 15),
format: trimmer,
},
{
type: "text",
name: "email",
message: "Email address?",
validate: emailValidator,
format: trimmer,
},
{
type: "text",
name: "website",
message: "Portfolio Website?",
validate: urlValidator,
format: trimmer,
},
],
{ onCancel }
);
return contactData;
};
module.exports.counterQuestions = async (root, dummy = false) => {
if (dummy) {
prompts.inject(dummyData.counterData);
}
// Counter.js Data
const { itemsCount } = await prompts(
{
type: "number",
name: "itemsCount",
message: "No of counter items you want to include",
validate: (val) => minmaxChecker(val, 2, 4),
},
{ onCancel }
);
let items = [];
for (let i = 0; i < itemsCount; i++) {
if (!dummy) console.log(`\nEnter data for counter item ${i + 1};`);
let data = await prompts(
[
{
type: "text",
name: "icon",
message: "Bootstrap icon?",
initial: "bi bi-globe",
format: trimmer,
},
{
type: "number",
name: "count",
message: "Maximum count?",
validate: emptyValidator,
},
{
type: "number",
name: "duration",
message: "Animation Duration?",
validate: emptyValidator,
},
{
type: "text",
name: "title",
message: "Title?",
validate: (val) => minmaxChecker(val, 3, 25),
format: trimmer,
},
],
{ onCancel }
);
items.push(data);
}
return { items };
};
module.exports.portfolioQuestions = async (root, dummy = false) => {
if (dummy) {
prompts.inject(dummyData.portfolioData);
}
// Portfolio.js Data
let portfolioData = {};
// filterVariables
const { filterVariablesLength } = await prompts(
{
type: "number",
name: "filterVariablesLength",
message: "No of filter variables?",
initial: 3,
validate: (val) => minmaxChecker(val, 3, 8),
},
{ onCancel }
);
let filterVariablesList = [];
for (let i = 0; i < filterVariablesLength; i++) {
if (!dummy) console.log(`\nEnter data for filter variable ${i + 1}:`);
let data = await prompts(
[
{
type: "text",
name: "varibaleName",
message: "Variable name?",
initial: `filter_var_${i + 1}`,
format: trimmer,
validate: (val) => minmaxChecker(val, null, 30),
},
{
type: "text",
name: "filterName",
message: "Filter name?",
initial: `var${i + 1}`,
format: (val) => "filter-" + trimmer(val),
validate: (val) => minmaxChecker(val, null, 10),
},
],
{ onCancel }
);
filterVariablesList.push(data);
}
let filterVariableStrings = filterVariablesList.map(
(el) => `const ${el.varibaleName} = "${el.filterName}";`
);
portfolioData.filterVariables = filterVariableStrings.join("\n");
// filterList
portfolioData.filterList = `[${filterVariablesList
.map((el) => el.varibaleName)
.toString()}]`;
// urlVariables
if (!dummy) console.log();
const { urlVariablesLength } = await prompts(
{
type: "number",
name: "urlVariablesLength",
message: "No of URL variables?",
initial: 0,
},
{ onCancel }
);
let urlVariablesList = [];
for (let i = 0; i < urlVariablesLength; i++) {
if (!dummy) console.log(`\nEnter data for urls variable ${i + 1}:`);
let data = await prompts(
[
{
type: "text",
name: "varibaleName",
message: "Variable name?",
initial: `url_var_${i + 1}`,
format: trimmer,
validate: (val) => minmaxChecker(val, null, 30),
},
{
type: "text",
name: "urlName",
message: "URL name?",
initial: `URL ${i + 1}`,
format: trimmer,
validate: (val) => minmaxChecker(val, null, 10),
},
],
{ onCancel }
);
urlVariablesList.push(data);
}
let urlVariableStrings = urlVariablesList.map(
(el) => `const ${el.varibaleName} = "${el.urlName}";`
);
portfolioData.urlVariables = urlVariableStrings.join("\n");
// catTypes
if (!dummy) console.log();
let catTypesList = [];
for (let i = 0; i < filterVariablesLength; i++) {
let { category } = await prompts(
{
type: "text",
name: "category",
message: `Category name for filter variable ${filterVariablesList[i].varibaleName}?`,
initial: `Cat ${i + 1}`,
format: trimmer,
validate: (val) => minmaxChecker(val, null, 10),
},
{ onCancel }
);
catTypesList.push(
`[${filterVariablesList[i].varibaleName}]: "${category}"`
);
}
portfolioData.catTypes = `{\n ${catTypesList.join(",\n")} \n}`;
// projectList
if (!dummy) console.log();
const { projectsLength } = await prompts(
{
type: "number",
name: "projectsLength",
message: "No of projects?",
initial: 0,
},
{ onCancel }
);
let projectsList = [];
for (let i = 0; i < projectsLength; i++) {
if (!dummy) console.log(`\nEnter data for project ${i + 1}:`);
let data = await prompts(
[
{
type: "text",
name: "name",
message: "Project name?",
initial: `Project ${i + 1}`,
format: trimmer,
validate: (val) => minmaxChecker(val, null, 20),
},
{
type: "autocompleteMultiselect",
name: "filter",
message: "Select available filters?",
format: (val) => `generateFilterString(${val.join(", ")})`,
choices: filterVariablesList.map((el) => {
return { title: el.varibaleName, value: el.varibaleName };
}),
},
{
type: "text",
name: "img",
message: "Project gallery folder name?",
initial: `project-${i + 1}`,
format: trimmer,
validate: (val) => minmaxChecker(val, null, 20),
},
],
{ onCancel }
);
let { pointCount } = await prompts(
{
type: "number",
name: "pointCount",
message: "Number of description statements?",
initial: 0,
},
{ onCancel }
);
let points = [];
for (let j = 0; j < pointCount; j++) {
let { pointData } = await prompts(
{
type: "text",
name: "pointData",
message: `Description statement ${j + 1}?`,
format: trimmer,
validate: emptyValidator,
},
{ onCancel }
);
points.push(pointData);
}
let urls = [];
if (urlVariablesLength > 0) {
let { urlsLength } = await prompts(
{
type: "number",
name: "urlsLength",
message: "No of URLs for the project?",
initial: 0,
},
{ onCancel }
);
for (let j = 0; j < urlsLength; j++) {
let urlData = await prompts([
{
type: "autocomplete",
name: "type",
message: "Select url type?",
choices: urlVariablesList.map((el) => {
return { title: el.varibaleName, value: el.varibaleName };
}),
validate: emptyValidator,
},
{
type: "text",
name: "value",
message: "Required URL?",
format: trimmer,
validate: urlValidator,
},
]);
urls.push(`[[${urlData.type}], "${urlData.value}"]`);
}
}
if (!dummy) {
// Adding Image
let addImage = await this.addFileConfirmation(
"Do you want to add an image file for this project? If you dont add then an error image (404.webp) will be shown."
);
if (addImage) {
let folderPath = path.join(root, "public/Gallery/Projects", data.img);
fs.ensureDirSync(folderPath);
let filepath = path.join(folderPath, "0.webp");
let filedata = await this.fileQuestions(
path.basename(filepath),
path.extname(filepath),
"A webp image of 800x600 size would give better results."
);
let file = fs.readFileSync(filedata);
fs.writeFileSync(filepath, file);
}
}
if (data) {
data.desc = points;
data.urls = urls;
let dataString = "{";
for (const key in data) {
if (key === "filter") {
dataString = dataString.concat(`${key}:${data[key]},`);
} else if (key === "desc") {
dataString = dataString.concat(
`${key}: [ ${data[key].map(JSON.stringify).join(",")} ],`
);
} else if (key === "urls") {
dataString = dataString.concat(`${key}: [ ${data[key].join(",")} ],`);
} else {
dataString = dataString.concat(`${key}:"${data[key]}",`);
}
}
dataString = dataString.concat("}");
projectsList.push(dataString);
}
}
portfolioData.projectList = "[" + projectsList.join(",") + "]";
return portfolioData;
};
module.exports.skillsQuestions = async (root, dummy = false) => {
if (dummy) {
prompts.inject(dummyData.skillData);
}
// Skill.js
let skillData = {};
// skillList
let { skillLength } = await prompts(
{
type: "number",
name: "skillLength",
message: "Number of skills?",
initial: 0,
},
{ onCancel }
);
let skillsList = [];
for (let i = 0; i < skillLength; i++) {
if (!dummy) console.log(`\nEnter data for skill ${i + 1}:`);
let data = await prompts(
[
{
type: "text",
name: "title",
message: "Skill name?",
format: trimmer,
validate: (val) => minmaxChecker(val, 1, 30),
},
{
type: "number",
name: "value",
message: "Value out of 100?",
initial: 0,
validate: (val) => minmaxChecker(val, 0, 100),
},
],
{ onCancel }
);
skillsList.push(data);
}
skillData.skillList = skillsList;
return skillData;
};
module.exports.resumeQuestions = async (root, dummy = false) => {
if (dummy) {
prompts.inject(dummyData.resumeData);
}
// Resume.js
let resumeData = {};
// educationList
let { educationLength } = await prompts(
{
type: "number",
name: "educationLength",
message: "Number of education items?",
initial: 0,
},
{ onCancel }
);
let educationList = [];
for (let i = 0; i < educationLength; i++) {
if (!dummy) console.log(`\nEnter data for education item ${i + 1}:`);
let data = await prompts(
[
{
type: "text",
name: "title",
message: "Education title?",
format: trimmer,
validate: (val) => minmaxChecker(val, 1, 30),
},
{
type: "text",
name: "time",
message: "Education time?",
format: trimmer,
validate: (val) => minmaxChecker(val, 1, 30),
},
{
type: "text",
name: "from",
message: "School/College/University name?",
format: trimmer,
validate: (val) => minmaxChecker(val, 1, 75),
},
{
type: "text",
name: "result",
message: "Education result?",
format: trimmer,
validate: (val) => minmaxChecker(val, 1, 30),
},
],
{ onCancel }
);
educationList.push(data);
}
resumeData.educationList = educationList;
// examList
if (!dummy) console.log();
let { examLength } = await prompts(
{
type: "number",
name: "examLength",
message: "Number of exam items?",
initial: 0,
},
{ onCancel }
);
let examList = [];
for (let i = 0; i < examLength; i++) {
if (!dummy) console.log(`\nEnter data for exam item ${i + 1}:`);
let data = await prompts(
[
{
type: "text",
name: "title",
message: "Exam title?",
format: trimmer,
validate: (val) => minmaxChecker(val, 1, 30),
},
{
type: "text",
name: "result",
message: "Exam result?",
format: trimmer,
validate: (val) => minmaxChecker(val, 1, 30),
},
],
{ onCancel }
);
examList.push(data);
}
resumeData.examList = examList;
// skillList
if (!dummy) console.log();
let { skillsLength } = await prompts(
{
type: "number",
name: "skillsLength",
message: "Number of skill items?",
initial: 0,
},
{ onCancel }
);
let skillsList = [];
for (let i = 0; i < skillsLength; i++) {
if (!dummy) console.log(`\nEnter data for skill item ${i + 1}:`);
let data = await prompts(
[
{
type: "text",
name: "title",
message: "Skill type?",
format: trimmer,
validate: (val) => minmaxChecker(val, 1, 15),
},
{
type: "list",
name: "items",
message: "Skill items (seperated by comma)?",
format: (val) => val.filter(emptyValidator).map(trimmer),
},
],
{ onCancel }
);
skillsList.push(data);
}
resumeData.skillList = skillsList;
// experienceList
if (!dummy) console.log();
let { experienceLength } = await prompts(
{
type: "number",
name: "experienceLength",
message: "Number of experience items?",
initial: 0,
},
{ onCancel }
);
let experienceList = [];
for (let i = 0; i < experienceLength; i++) {
if (!dummy) console.log(`\nEnter data for experience item ${i + 1}:`);
let data = await prompts(
[
{
type: "text",
name: "title",
message: "Experience title?",
format: trimmer,
validate: (val) => minmaxChecker(val, 1, 30),
},
{
type: "text",
name: "company",
message: "Company name?",
format: trimmer,
validate: (val) => minmaxChecker(val, 1, 30),
},
{
type: "text",
name: "time",
message: "Experience time?",
format: trimmer,
validate: (val) => minmaxChecker(val, 1, 30),
},
{
type: "text",
name: "location",
message: "Experience location?",
format: trimmer,
validate: (val) => minmaxChecker(val, 1, 20),
},
],
{ onCancel }
);
let { pointCount } = await prompts(
{
type: "number",
name: "pointCount",
message: "Number of description statements?",
initial: 0,
},
{ onCancel }
);
let points = [];
for (let j = 0; j < pointCount; j++) {
let { pointData } = await prompts(
{
type: "text",
name: "pointData",
message: `Description statement ${j + 1}?`,
format: trimmer,
validate: (val) => minmaxChecker(val, 10, 150),
},
{ onCancel }
);
points.push(pointData);
}
let { linksCount } = await prompts(
{
type: "number",
name: "linksCount",
message: "Number of urls releated to experience?",
initial: 0,
},
{ onCancel }
);
let links = [];
for (let j = 0; j < linksCount; j++) {
let { linkData } = await prompts(
{
type: "text",
name: "linkData",
message: `Related URL ${j + 1}?`,
format: trimmer,
validate: urlValidator,
},
{ onCancel }
);
links.push(linkData);
}
if (data) {
data.points = points;
data.links = links;
experienceList.push(data);
}
}
resumeData.experienceList = experienceList;
return resumeData;
};
module.exports.manifestQuestions = async (root, dummy = false) => {
if (dummy) {
prompts.inject(dummyData.manifestData);
}
// manifest.json
const manifestData = await prompts(
[
{
type: "text",
name: "author",
message: "Author name/username?",
format: trimmer,
validate: emptyValidator,
},
{
type: "text",
name: "shortName",
message: "Short name for website?",
format: trimmer,
validate: (val) => minmaxChecker(val, 5, 30),
},
{
type: "text",
name: "name",
message: "Long name for website?",
format: trimmer,
validate: (val) => minmaxChecker(val, 10, 100),
},
],
{ onCancel }
);
return manifestData;
};
module.exports.fileQuestions = async (filename, extension, message = "") => {
const { file } = await prompts(
{
type: "text",
name: "file",
message: `${
message && message.length > 0 ? message + " " : ""
}Provide absolute path of ${filename}?`,
validate: (val) => {
let filepath = path.resolve(val);
try {
fs.readFileSync(filepath);
if (path.extname(filepath) !== extension) {
return `File is not in ${extension} format`;
}
return true;
} catch (e) {
return "File not present";
}
},
},
{ onCancel }
);
return file;
};
module.exports.addFileConfirmation = async (message) => {
let { answer } = await prompts(
{
type: "confirm",
name: "answer",
message,
initial: false,
},
{
onCancel: (prompt, ans) => {
ans.answer = false;
},
}
);
return answer;
};