portfolio-generator
Version:
This package generates a bootstrap based portfolio website for you.
292 lines (269 loc) • 8.55 kB
JavaScript
const {
showWarning,
showError,
showMultipleProgress,
} = require("cybersaksham-npm-logs");
const validateProjectName = require("validate-npm-package-name");
const { checkNodeVersion } = require("./versions");
const path = require("path");
const fs = require("fs-extra");
const chalk = require("chalk");
const prettier = require("prettier");
// Code Imports
const structure = require("./code/structure.json");
const {
aboutQuestions,
contactQuestions,
counterQuestions,
portfolioQuestions,
skillsQuestions,
resumeQuestions,
manifestQuestions,
fileQuestions,
addFileConfirmation,
} = require("./code/questions");
const datafiles = require("./code/datafiles.json");
module.exports.createApp = async (name, version, dummy = false) => {
if (!checkNodeVersion()) {
showWarning({
warnings: [
`You are using Node ${process.version} so the project will be bootstrapped with an old unsupported version of tools.`,
`Please update to Node 14 or higher for a better, fully supported experience.`,
],
summary: ["Falling to react scripts version react-scripts@0.9.x"],
});
// Fall back to latest supported react-scripts on Node 4
version = "react-scripts@0.9.x";
}
const root = path.resolve(name);
const appName = path.basename(root);
checkAppName(appName);
// Checking directory
fs.ensureDirSync(name);
isSafeToCreateProjectIn(root, name);
// Start creating the project
console.log(
`${chalk.cyan("[INFO]")} Creating a new Portfolio project in ${chalk.green(
root
)}.`
);
console.log();
await downloadFiles(root);
console.log();
console.log();
await addData(root, dummy);
};
const checkAppName = (appName) => {
const validationResult = validateProjectName(appName);
if (!validationResult.validForNewPackages) {
// Show Errors
let errors = [
`Cannot create a project named ${chalk.green(
`"${appName}"`
)} because of npm naming restrictions:`,
];
let validationErrors = [
...(validationResult.errors || []),
...(validationResult.warnings || []),
];
if (validationErrors.length > 0) {
errors.push("Found errors are:");
validationErrors.forEach((err) => {
errors.push(chalk.red(` *${err}`));
});
}
showError({
code: 400,
errors,
summary: ["Please choose a different project name."],
});
process.exit(1);
}
// TODO: fetch dependencies from package.json file to be added
const dependencies = ["react", "react-dom", "react-scripts"].sort();
if (dependencies.includes(appName)) {
let errors = [
`Cannot create a project named ${chalk.green(
`"${appName}"`
)} because a dependency with the same name exists.`,
`Due to the way npm works, the following names are not allowed:`,
];
dependencies.map((dep) => {
errors.push(chalk.cyan(` ${dep}`));
});
showError({
code: 400,
errors,
summary: ["Please choose a different project name."],
});
process.exit(1);
}
};
// If project only contains files generated by GH, it’s safe.
// Also, if project contains remnant error logs from a previous
// installation, lets remove them now.
// We also special case IJ-based products .idea because it integrates with CRA:
// https://github.com/facebook/create-react-app/pull/368#issuecomment-243446094
const isSafeToCreateProjectIn = (root, name) => {
const validFiles = [
".DS_Store",
".git",
".gitattributes",
".gitignore",
".gitlab-ci.yml",
".hg",
".hgcheck",
".hgignore",
".idea",
".npmignore",
".travis.yml",
"docs",
"LICENSE",
"README.md",
"mkdocs.yml",
"Thumbs.db",
];
// These files should be allowed to remain on a failed install, but then
// silently removed during the next create.
const errorLogFilePatterns = [
"npm-debug.log",
"yarn-error.log",
"yarn-debug.log",
];
const isErrorLog = (file) => {
return errorLogFilePatterns.some((pattern) => file.startsWith(pattern));
};
const conflicts = fs
.readdirSync(root)
.filter((file) => !validFiles.includes(file))
// IntelliJ IDEA creates module files before CRA is launched
.filter((file) => !/\.iml$/.test(file))
// Don't treat log files from previous installation as conflicts
.filter((file) => !isErrorLog(file));
if (conflicts.length > 0) {
let errors = [
`The directory ${chalk.green(name)} contains files that could conflict:`,
"",
];
for (const file of conflicts) {
try {
const stats = fs.lstatSync(path.join(root, file));
if (stats.isDirectory()) {
errors.push(` ${chalk.blue(`${file}/`)}`);
} else {
errors.push(` ${chalk.green(`${file}/`)}`);
}
} catch (e) {
// Ignore
}
}
showError({
code: 400,
errors,
summary: [
"Either try using a new directory name, or remove the files listed above.",
],
});
process.exit(1);
}
// Remove any log files from a previous installation.
fs.readdirSync(root).forEach((file) => {
if (isErrorLog(file)) {
fs.removeSync(path.join(root, file));
}
});
};
// Download files from the github repository
// https://github.com/cybersaksham/Portfolio-Generator/tree/master/Required%20Code
const downloadFiles = async (root) => {
structure.folders.forEach((dir) => {
fs.ensureDirSync(path.join(root, dir));
});
const fileList = [];
structure.files.forEach(({ name, source }) => {
fileList.push({
source,
destination: path.join(root, name),
});
});
await showMultipleProgress(fileList);
};
// Add Data to files
// Ask data from user and add to files
const addData = async (root, dummy = false) => {
insertPackageJson(root);
await insertData(root, datafiles.about, aboutQuestions, dummy);
await insertData(root, datafiles.contact, contactQuestions, dummy);
await insertData(root, datafiles.counter, counterQuestions, dummy);
await insertData(root, datafiles.portfolio, portfolioQuestions, dummy);
await insertData(root, datafiles.resume, resumeQuestions, dummy);
await insertData(root, datafiles.skills, skillsQuestions, dummy);
await insertData(root, datafiles.manifest, manifestQuestions, dummy);
if (!dummy) console.log(chalk.cyan("Image") + " Data:\n");
await insertFiles(root, datafiles.favicon, dummy, "Favicon Image.");
await insertFiles(root, datafiles["404"], dummy, "Error Image.");
await insertFiles(root, datafiles.bg, dummy, "Background Image.");
await insertFiles(
root,
datafiles.pic,
dummy,
"Upload a picture of yourself."
);
if (!dummy) {
let addResume = await addFileConfirmation(
"Do you want to add pdf file for resume?"
);
if (addResume) {
await insertFiles(
root,
datafiles.resumePdf,
dummy,
"Upload pdf of your resume."
);
} else {
fs.unlinkSync(path.join(root, datafiles.resumePdf));
}
}
};
// Insert Data in file
// Change variables in data file to given data
const insertData = async (root, fileLocation, questions, dummy) => {
let filepath = path.join(root, fileLocation);
if (!dummy) console.log(chalk.cyan(path.basename(filepath)) + " Data:\n");
const data = await questions(root, dummy);
let file = fs.readFileSync(filepath).toString();
for (const key in data) {
let replacableData = data[key];
if (typeof replacableData === "object") {
replacableData = JSON.stringify(replacableData);
}
file = file.replaceAll(`[[${key}]]`, replacableData);
}
file = prettier.format(file, { filepath });
fs.writeFileSync(filepath, file);
if (!dummy) console.log();
if (!dummy) console.log();
};
// Insert files
// Insert favicon.ico, webp image, Resume.pdf
const insertFiles = async (root, fileLocation, dummy, message = "") => {
if (dummy) {
} else {
let filepath = path.join(root, fileLocation);
let filedata = await fileQuestions(
path.basename(filepath),
path.extname(filepath),
message
);
let file = fs.readFileSync(filedata);
fs.writeFileSync(filepath, file);
}
};
// Insert package.json
// Change package name in package.json file
const insertPackageJson = (root) => {
let filepath = path.join(root, "package.json");
let file = fs.readFileSync(filepath).toString();
file = file.replaceAll("[[name]]", path.basename(root).toString());
fs.writeFileSync(filepath, file);
};