@nomadmystic/wordpress-scaffold-cli
Version:
This project is created to speed up WordPress development
355 lines (298 loc) • 13.1 kB
text/typescript
// Community modules
import 'dotenv/config';
import fse from "fs-extra";
// Package modules
// Classes
import InquirerCli from '../cli/inquirer-cli.js';
import AbstractScaffold from '../abstract/AbstractScaffold.js';
import updateInternalJson, { ProjectJson } from '../scaffold/common/update-internal-json.js';
// Utils
import PathUtils from '../utils/path-utils.js';
import StringUtils from '../utils/string-utils.js';
// Interfaces
import PluginAnswers from '../interfaces/plugin/interface-plugin-anwsers.js';
import PluginAnswerValues from '../interfaces/plugin/interface-plugin-answer-values.js';
import PluginConfig from '../interfaces/plugin/interface-plugin-config.js';
import ScaffoldCopyFolders from '../interfaces/common/interface-scaffold-copy-folders.js';
import ScaffoldJsonUpdates from '../interfaces/common/interface-scaffold-json-updates.js';
// Functions
import getPluginOptions from '../config/plugin-options.js';
import UpdateTypeFiles from '../scaffold/common/update-type-files.js';
import MessagingUtils from '../utils/messaging-utils.js';
import CreateObjectArrays from '../scaffold/common/create-object-arrays.js';
import DebugUtils from "../utils/debug-utils.js";
/**
* @classdesc Scaffold a new theme based on user's inputs
* @class ScaffoldTheme
* @extends AbstractScaffold
* @author Keith Murphy | nomadmystics@gmail.com
*/
class ScaffoldPlugin extends AbstractScaffold {
/**
* @type boolean
* @private
*/
private static isDebugFullMode: boolean = false;
/**
* @type boolean
* @private
*/
private static composerAlreadyExists: boolean | any = false;
/**
* @type boolean
* @private
*/
private static packageAlreadyExists: boolean | any = false;
/**
* @type PluginAnswerValues
* @private
*/
private static pluginValues: PluginAnswerValues;
/**
* {@inheritDoc AbstractScaffold}
*/
public static initializeScaffolding = async (): Promise<void> => {
try {
// Bail early
await PathUtils.checkForWordPressInstall();
// Check for debug mode values
this.isDebugFullMode = await DebugUtils.isDebugFullMode();
// Get our answers
const answers: PluginAnswers | void = await InquirerCli.performPromptsTasks(await getPluginOptions()).catch((err) => console.error(err));
// Start the scaffolding process
await this.scaffoldFiles(answers);
} catch (err: any) {
console.log('ScaffoldTheme.performScaffolding()');
console.error(err);
}
};
/**
* {@inheritDoc AbstractScaffold}
*/
protected static scaffoldFiles = async (answers: PluginAnswers | any): Promise<void> => {
try {
// Build the values we need
this.pluginValues = await this.buildValueObject(answers);
if (this.isDebugFullMode) {
console.log(this.pluginValues);
}
// Validate we aren't overwriting another plugin with the same name
await PathUtils.validateIsPathWithDisplay(this.pluginValues.finalPath, 'There is already a plugin with that name. Please use another name.', true);
// Update the internal JSON files
let pluginConfig = await this.updateProjectConfig(this.pluginValues);
// Update the individual files we need to scaffold
await this.performScaffold(this.pluginValues, pluginConfig);
await MessagingUtils.displayEndingMessages(this.pluginValues, this.composerAlreadyExists, this.packageAlreadyExists);
} catch (err: any) {
console.log('ScaffoldTheme.scaffoldFiles()');
console.error(err);
}
};
/**
* @description Build an object of our needed values for scaffolding the plugin
* @private
* @author Keith Murphy | nomadmystics@gmail.com
* @todo refactor into Abstract class method in theme and project
*
* @param {PluginAnswers | any} answers
* @return {Promise<PluginAnswerValues | any>}
*/
private static buildValueObject = async (answers: PluginAnswers | any): Promise<PluginAnswerValues | any> => {
try {
// Absolute path of the themes folder
const pluginsPath: string | undefined = await PathUtils.getPluginsFolderPath();
// User inputs
const projectName: string = answers.projectName ? answers.projectName : '';
const name: string = answers.name ? answers.name.trim() : '';
const description: string = answers.description ? answers.description.trim() : '';
const frontEndFramework: string = answers.frontEndFramework ? answers.frontEndFramework : '';
const siteUrl: string = answers.siteUrl;
const devSiteUrl: string = answers.devSiteUrl;
// Make folder "safe" if there are spaces
const safeName: string = await StringUtils.addDashesToString(name);
// Create the finalized path for the scaffolded theme
const finalPluginPath: string = `${pluginsPath}/${safeName}`;
// Create our string modification
const capAndSnakeCasePlugin: string = await StringUtils.capAndSnakeCaseString(safeName);
const pluginNamespace: string = await StringUtils.pascalCaseString(safeName);
return {
type: 'plugin',
projectName: projectName,
name: name,
safeName: safeName,
pluginsPath: pluginsPath,
finalPath: finalPluginPath,
description: description,
frontEndFramework: frontEndFramework,
siteUrl: siteUrl,
devSiteUrl: devSiteUrl,
capAndSnakeCasePlugin: capAndSnakeCasePlugin,
namespace: pluginNamespace,
};
} catch (err: any) {
console.log('ScaffoldTheme.buildValueObject()');
console.error(err);
}
};
/**
* @description Update our project config object based on user inputs
* @public
* @author Keith Murphy | nomadmystics@gmail.com
* @todo refactor into Abstract class method in theme and project
*
* @param {PluginAnswerValues} pluginValues
* @return {Promise<PluginConfig | any>}
*/
private static updateProjectConfig = async (pluginValues: PluginAnswerValues): Promise<PluginConfig | any> => {
try {
let {
projectName,
name,
finalPath,
description,
frontEndFramework,
} = pluginValues;
let configUpdates: PluginConfig = {
'plugin-name': name,
'plugin-path': finalPath,
'plugin-description': description,
'plugin-front-end-framework': frontEndFramework,
};
if (projectName && typeof projectName !== 'undefined') {
configUpdates['project-name'] = projectName;
configUpdates['project-namespace'] = await StringUtils.pascalCaseString(projectName);
}
// Update our config before we scaffold plugin, so we can use it in our scaffold functions
configUpdates = await ProjectJson.update(configUpdates, 'plugin');
return configUpdates;
} catch (err: any) {
console.log('ScaffoldPlugin.updateProjectConfig()');
console.error(err);
}
};
/**
* @description
* @private
* @author Keith Murphy | nomadmystics@gmail.com
* @todo refactor into Abstract class method in theme and project
*
* @param {PluginAnswerValues} pluginValues
* @param {PluginConfig} pluginConfig
* @return {Promise<void>}
*/
private static performScaffold = async (pluginValues: PluginAnswerValues, pluginConfig: PluginConfig): Promise<void> => {
try {
const updateObjectsArray: Array<ScaffoldJsonUpdates> = await this.buildUpdateObjectArray(pluginValues);
const foldersToCopy: Array<ScaffoldCopyFolders> = await this.buildFoldersToCopy(pluginValues);
await UpdateTypeFiles.copyFiles(foldersToCopy);
await UpdateTypeFiles.updateFiles(pluginValues, updateObjectsArray);
await UpdateTypeFiles.updateClassFiles(pluginValues);
await UpdateTypeFiles.updateWebpack(pluginValues, 'plugin');
} catch (err: any) {
console.log('ScaffoldPlugin.performScaffold()');
console.error(err);
}
};
/**
* @description
* @private
* @author Keith Murphy | nomadmystics@gmail.com
*
* @param {PluginAnswerValues} pluginValues
* @return {Promise<Array<ScaffoldCopyFolders> | any>}
*/
private static buildFoldersToCopy = async (pluginValues: PluginAnswerValues): Promise<Array<ScaffoldCopyFolders> | any> => {
try {
const foldersToCopy: Array<ScaffoldCopyFolders> = [
{
source: 'scaffolding/plugin',
destination: `${pluginValues.finalPath}`,
},
{
source: 'scaffolding/common/classes',
destination: `${pluginValues.finalPath}/classes`,
},
{
source: `scaffolding/common/front-end-scaffolding/${pluginValues.frontEndFramework?.toLowerCase()}/js`,
destination: `${pluginValues.finalPath}/src/js`,
},
{
source: `scaffolding/common/front-end-scaffolding/${pluginValues.frontEndFramework?.toLowerCase()}/scss`,
destination: `${pluginValues.finalPath}/src/scss`,
},
{
source: `scaffolding/common/front-end-scaffolding/${pluginValues.frontEndFramework?.toLowerCase()}/project-root`,
destination: `${pluginValues.finalPath}`,
},
{
source: 'scaffolding/common/project-root',
destination: `${pluginValues.finalPath}`,
},
];
return foldersToCopy;
} catch (err: any) {
console.log('ScaffoldPlugin.buildFoldersToCopy()');
console.error(err);
}
};
/**
* @description
* @private
* @author Keith Murphy | nomadmystics@gmail.com
*
* @param {PluginAnswerValues} pluginValues
* @return {Promise<Array<ScaffoldJsonUpdates> | any>}
*/
private static buildUpdateObjectArray = async (pluginValues: PluginAnswerValues): Promise<Array<ScaffoldJsonUpdates> | any> => {
try {
const updateObjectsArray: Array<ScaffoldJsonUpdates> = [
{
fileName: 'plugin-name.php',
stringToUpdate: 'SCAFFOLD_NAME',
updateString: pluginValues.name,
},
{
fileName: 'plugin-name.php',
stringToUpdate: 'SCAFFOLD_DESCRIPTION',
updateString: pluginValues.description,
},
{
fileName: 'plugin-name.php',
stringToUpdate: 'SCAFFOLD_DESCRIPTION',
updateString: pluginValues.description,
},
{
fileName: 'plugin-name.php',
stringToUpdate: 'CAPS_AND_SNAKE_NAME',
updateString: pluginValues.capAndSnakeCasePlugin,
},
{
fileName: 'webpack.config.js',
stringToUpdate: 'SCAFFOLD_NAME',
updateString: pluginValues.name,
},
{
fileName: 'classes/BootstrapClasses.php',
stringToUpdate: 'CAPS_AND_SNAKE_NAME',
updateString: pluginValues.capAndSnakeCasePlugin,
},
];
// Perform composer updates
const composerObjects: Array<ScaffoldJsonUpdates> | any = await CreateObjectArrays.readComposerObjects(pluginValues, this.composerAlreadyExists);
if (!this.composerAlreadyExists) {
updateObjectsArray.push(...composerObjects);
}
// Perform package.json updates
const packageObjects: Array<ScaffoldJsonUpdates> | any = await CreateObjectArrays.readPackageObjects(pluginValues, this.packageAlreadyExists);
if (!this.packageAlreadyExists) {
updateObjectsArray.push(...packageObjects);
}
return updateObjectsArray;
} catch (err: any) {
console.log('ScaffoldPlugin.buildUpdateObjectArray()');
console.error(err);
}
};
}
ScaffoldPlugin.initializeScaffolding().catch(err => console.error(err));