@ventum-digital/iiq-plugin-project-generator
Version:
A npm tool to set-up the project structure for developing an IIQ Plugin.
287 lines (216 loc) • 8.41 kB
JavaScript
const fs = require('fs');
const path = require('path');
const minimist = require('minimist');
const globalDirectory = require("./borrowed/global-directory").default;
const {spawn} = require('child_process');
const rl = require('readline-sync');
const {getTemplateResourceBasePath, getAvailableSubTemplates, BASE_TEMPLATE_NAME} = require("./TemplateHelper");
function queryTemplate(baseTemplateName) {
function printItem(i, name) {
console.log(`\t[${i}] ${name}`);
}
function parseAnswer(selection) {
if (selection === "") {
return 0;
} else {
return parseInt(selection);
}
}
function isInvalidSelection(selection, max) {
if (isNaN(selection) || selection < 0 || selection > max) {
console.log(`Invalid selection. Please select a number between 0 and ${max}`);
return true;
}
return false;
}
console.log("'template' option was not specified. Please select a template from the list:");
let extensions = getAvailableSubTemplates(baseTemplateName);
for (let i = 0; i < extensions.length; i++) {
printItem(i, extensions[i]);
}
let selection;
do {
selection = parseAnswer(rl.question("> Select template [default: 0]: "));
} while (isInvalidSelection(selection, extensions.length));
return extensions[selection];
}
module.exports = {
generateProject: (
templateName,
cmdOptionsRequired,
cmdOptionsOptional = {},
additionalReplaceValuesGetter = null,
argValidator = null
) => {
if (cmdOptionsRequired.template !== undefined) {
throw new Error("'template' option is reserved");
}
if (Object.values(cmdOptionsRequired).includes("t")) {
throw new Error("'t' short option is reserved");
}
cmdOptionsRequired = {
...cmdOptionsRequired,
template: "t",
};
const cmdOptions = {...cmdOptionsRequired, ...cmdOptionsOptional};
let commandLineArgs = process.argv.slice(2);
const args = minimist(commandLineArgs, {
alias : cmdOptions,
string: Object.keys(cmdOptions)
});
// Validate required arguments
const missingArgs = Object.keys(cmdOptionsRequired).filter(arg => !args[arg]);
for (const missingArg of missingArgs) {
let arg;
if (missingArg === "template") {
arg = queryTemplate(templateName);
} else {
do {
arg = rl.question(`> Provide value for "${missingArg}": `);
} while (!arg);
}
args[missingArg] = arg;
}
const replaceValues = {
...((() => {
const result = {};
for (const key of Object.keys(cmdOptions)) {
result[key] = args[key];
}
return result;
})()),
...(additionalReplaceValuesGetter ? additionalReplaceValuesGetter(args) : {})
}
// Validate arguments
if (argValidator) {
try {
argValidator(replaceValues);
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}
//////////////////////////////
// Function to create a directory if it doesn't exist
function createDir(dirPath) {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, {recursive: true});
}
}
// Function to create a file with content
function createFile(filePath, content, overwrite) {
if (overwrite || !fs.existsSync(filePath)) {
fs.writeFileSync(filePath, content, 'utf8');
console.log(`Created: ${filePath}`);
} else {
console.log(`Skipped (already exists): ${filePath}`);
}
}
// Generates a project by copying files from the "resources" folder,
// replacing tokens (e.g., %%projectName%% and %%version%%), and recreating
function replaceTokens(content, delimiter = "__") {
for (const key of Object.keys(replaceValues)) {
const token = `${delimiter}${key}${delimiter}`;
content = content.replace(new RegExp(token, 'g'), replaceValues[key] || '');
}
return content;
}
function shouldNotBeTokenized(fileName) {
const extensions = [".png", ".jpg", ".gif", ".jar"];
return fileName && extensions.some(ext => fileName.endsWith(ext));
}
// Helper function to copy files and replace tokens
function prepareFiles(srcDir, destDir, overwrite = true) {
const entries = fs.readdirSync(srcDir, {withFileTypes: true});
entries.forEach(entry => {
let srcPath = path.join(srcDir, entry.name);
if (entry.name.startsWith("__delete__")) {
// Remove the file or folder
const name = entry.name.substring("__delete__".length);
const pathToDelete = path.join(destDir, name);
if (fs.existsSync(pathToDelete)) {
fs.rmSync(pathToDelete, {recursive: true, force: true});
console.log(`Deleted: ${pathToDelete}`);
}
return;
}
let replacedName = replaceTokens(entry.name); // Replace tokens in the file or folder name
if (replacedName === "__gitignore__") {
replacedName = ".gitignore";
}
if (replacedName !== entry.name) {
console.log(`Renaming: ${entry.name} -> ${replacedName}`);
}
let destPath = path.join(destDir, replacedName);
if (entry.isDirectory()) {
createDir(destPath);
prepareFiles(srcPath, destPath);
} else if (entry.isFile()) {
// If is a text file, replace tokens, otherwise just copy
if (shouldNotBeTokenized(replacedName)) {
fs.copyFileSync(srcPath, destPath);
console.log(`Copied: ${replacedName}`);
} else {
let content = fs.readFileSync(srcPath, 'utf8');
content = replaceTokens(content);
createFile(destPath, content, overwrite);
}
}
});
}
//////////////////////////////
const projectName = args.packageName;
const projectDir = path.resolve(projectName);
// Use require.resolve to locate the "templates/plugin" directory in the package
let resourcesDir;
try {
// Resolve the templates directory within the package
resourcesDir = getTemplateResourceBasePath(templateName);
} catch (error) {
console.error(`Error: Could not locate '${resourcesDir}' directory. Make sure the package is installed globally.`);
process.exit(1);
}
if (!fs.existsSync(resourcesDir)) {
console.error(`Resources folder not found at: ${resourcesDir}`);
process.exit(1);
}
if (fs.existsSync(projectDir)) {
console.error(`Error: Project directory already exists: ${projectDir}`);
process.exit(1);
}
// Create the main project directory
createDir(projectDir);
// Process and copy files from the resources folder
prepareFiles(path.join(resourcesDir, "base"), projectDir);
if (args.template !== BASE_TEMPLATE_NAME) {
const templateDir = path.join(resourcesDir, "extensions", args.template);
if (!fs.existsSync(templateDir)) {
console.error(`Error: Template directory not found: ${templateDir}`);
process.exit(1);
}
console.log('-------------------------------------------------------------');
console.log(`Using template: ${args.template}`
);
prepareFiles(templateDir, projectDir);
}
console.log('-------------------------------------------------------------');
console.log(`Project '${projectName}' created successfully!`);
return {
launchIntelliJ: () => {
// Define the command and arguments
const command = 'idea';
const args = [`"${projectDir}"`];
console.log(`Running: ${command} ${args.join(' ')}`);
// Spawn the process with detached mode
const child = spawn(command, args, {
detached: true, // Detach the process to disown it
stdio : 'ignore', // Ignore standard I/O streams
shell : true // Run the command in a shell
});
// Prevent the child process from being killed when the parent exits
child.unref();
console.log(`IntelliJ IDEA started: ${projectName}`);
}
}
},
}