@nomadmystic/wordpress-scaffold-cli
Version:
This project is created to speed up WordPress development
454 lines (363 loc) • 15.2 kB
text/typescript
// Core Modules
import fs from 'fs';
import path from 'path';
// Community modules
import fse from "fs-extra";
// Package modules
// Utils
import StringUtils from '../../utils/string-utils.js';
import PathUtils from '../../utils/path-utils.js';
import MessagingUtils from '../../utils/messaging-utils.js';
import { packageRootDir } from '../../utils/package-root.js';
// Interfaces
import ActivePlugins from '../../interfaces/project/interface-active-plugins.js';
import ProjectConfig from '../../interfaces/project/interface-project-config.js';
import { scaffoldInternal } from './scaffold-internal.js';
import DebugUtils from "../../utils/debug-utils.js";
/**
* @description Updates our internal JSON with properties for use later
* @deprecated Use class ProjectJson
*
* @param {string} filePath
* @param {any} json
* @param {boolean} isPlugin
* @return {string}
*/
const updateInternalJson = async (filePath: string, json: any, isPlugin: boolean = false): Promise<string | any> => {
try {
await scaffoldInternal();
// Setup our arrays for json update logic
const dashedValues: Array<string> = [
'project-name',
'active-theme',
];
const disallowedKeys: Array<string> = [
'database-setup',
'database-name',
'database-password',
'database-username',
'site-admin-password',
'site-admin-user',
'admin-email',
];
// Get our config file
let jsonFile: string = fs.readFileSync(filePath, 'utf-8');
// Baily Early
if (!jsonFile || typeof jsonFile === 'undefined' || jsonFile === '') {
return '';
}
// Create object
let jsonFileParsed = JSON.parse(jsonFile);
let property: keyof typeof json;
for (property in json) {
// Sanity Check
if (Object.hasOwn(json, property) && property && typeof property !== 'undefined') {
// These come through the CLI as camelCase
let dashedProperty: string = await StringUtils.camelCaseToDash(property);
// What if there value isn't empty?
if (json[property] && typeof json[property] === 'undefined' && json[property] !== '') {
continue;
}
// Update the values
if (json[property] &&
typeof json[property] !== 'undefined' &&
typeof json[property] === 'string' &&
dashedValues.includes(dashedProperty)
) {
jsonFileParsed[`${dashedProperty}`] = await StringUtils.addDashesToString(json[property].trim());
// @todo Pretty specific maybe refactor and abstract this out?
if (dashedProperty === 'project-name') {
jsonFileParsed['project-namespace'] = await StringUtils.pascalCaseString(jsonFileParsed['project-name']);
continue;
}
continue;
}
// Some information we don't want to save, so do that here
if (typeof json[property] !== 'undefined' && !disallowedKeys.includes(dashedProperty)) {
jsonFileParsed[`${dashedProperty}`] = json[property];
}
} // End sanity check
} // End for
// Write our updated values
fs.writeFileSync(filePath, JSON.stringify(jsonFileParsed));
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
} catch (err) {
console.log('updateInternalJson()');
console.error(err);
}
};
/**
* @classdesc Perform tasks for updating the internal JSON file
* @class ProjectJson
* @author Keith Murphy | nomadmystics@gmail.com
*/
export class ProjectJson {
/**
* @type boolean
* @private
*/
private static isDebugFullMode: boolean = false;
/**
* @type string
* @private
*/
private static whereAmI: string = '';
/**
* @type string
* @private
*/
private static configFilePath: string = '';
// Setup our arrays for json update logic
private static dashedValues: Array<string> = [
'project-name',
'active-theme',
];
/**
* @type Array<string>
* @private
*/
private static disallowedKeys: Array<string> = [
'database-setup',
'database-name',
'database-password',
'database-username',
'site-admin-password',
'site-admin-user',
'admin-email',
'plugin-name',
'plugin-path',
'plugin-description',
'plugin-front-end-framework',
];
/**
* @description Starting point for updating the internal config
* @public
* @author Keith Murphy | nomadmystics@gmail.com
*
* @param {object} configUpdates
* @param {string} type
* @return {Promise<object | any>}
*/
public static update = async (configUpdates: object, type: string = ''): Promise<object | any> => {
try {
// Gather our location
this.whereAmI = await PathUtils.whereAmI();
// Check for debug mode values
this.isDebugFullMode = await DebugUtils.isDebugFullMode();
// Make sure we have the Project JSON scaffolded
await this.scaffoldInternal();
// Get our config file
this.configFilePath = `${this.whereAmI}/internal/project/project-config.json`;
let projectConfigObject = await this.getProjectConfigObject();
projectConfigObject = await this.performRootJsonUpdate(projectConfigObject, configUpdates);
// Some times need specific updates handel them here
if (type === 'plugin') {
projectConfigObject = await this.performPluginJsonUpdate(projectConfigObject, configUpdates);
}
await this.saveFile(projectConfigObject);
return projectConfigObject;
} catch (err: any) {
console.log('ProjectJson.update()');
console.error(err);
}
};
/**
* @description Save our internal JSON file
* @private
* @author Keith Murphy | nomadmystics@gmail.com
*
* @param {ProjectConfig} projectConfigObject
* @return {Promise<void>}
*/
private static saveFile = async (projectConfigObject: ProjectConfig): Promise<void> => {
try {
// Write our updated values
fs.writeFileSync(this.configFilePath, JSON.stringify(projectConfigObject));
// Let the user know
await MessagingUtils.displayColoredMessage('The internal project config file has been saved.', 'green');
} catch (err: any) {
console.log('ProjectJson.saveFile()');
console.error(err);
}
};
/**
* @description Make sure we have our internal folder, if not copy it over
*
* @return void
*/
public static scaffoldInternal = async (): Promise<void> => {
try {
if (!fs.existsSync(`${this.whereAmI}/internal`)) {
fse.copySync(`${path.join(packageRootDir + '/scaffolding/internal')}`, `${this.whereAmI}/internal`, { overwrite: false });
}
} catch (err: any) {
console.log('ProjectJson.scaffoldInternal()');
console.error(err);
}
};
/**
* @description Get the "Global" project config
* @public
* @author Keith Murphy | nomadmystics@gmail.com
*
* @return Promise<string | object | any>
*/
public static getProjectConfigObject = async (): Promise<ProjectConfig | any> => {
try {
let jsonFile: string = fs.readFileSync(this.configFilePath, 'utf-8');
// Baily Early
if (!jsonFile || typeof jsonFile === 'undefined' || jsonFile === '') {
return '';
}
return JSON.parse(jsonFile);
} catch (err: any) {
console.log('ProjectJson.getProjectConfigObject');
console.error(err);
}
};
/**
* @description Updates the root level config values
* @private
* @author Keith Murphy | nomadmystics@gmail.com
*
* @param {any} projectConfigObject
* @param {any} configUpdates
* @return {Promise<object | any>}
*/
private static performRootJsonUpdate = async (projectConfigObject: any, configUpdates: any): Promise<object | any> => {
try {
let property: keyof typeof configUpdates;
for (property in configUpdates) {
// Sanity Check
if (Object.hasOwn(configUpdates, property) && property && typeof property !== 'undefined') {
// These come through the CLI as camelCase
let dashedProperty: string = await StringUtils.camelCaseToDash(property);
// What if there value isn't empty?
if (configUpdates[property] &&
typeof configUpdates[property] === 'undefined' &&
configUpdates[property] !== ''
) {
continue;
}
// Update the values
if (configUpdates[property] &&
typeof configUpdates[property] !== 'undefined' &&
typeof configUpdates[property] === 'string' &&
this.dashedValues.includes(dashedProperty)
) {
projectConfigObject[`${dashedProperty}`] = await StringUtils.addDashesToString(configUpdates[property].trim());
// @todo Pretty specific maybe refactor and abstract this out?
if (dashedProperty === 'project-name') {
projectConfigObject['project-namespace'] = await StringUtils.pascalCaseString(projectConfigObject['project-name']);
continue;
}
continue;
}
// Some information we don't want to save, so do that here
if (typeof configUpdates[property] !== 'undefined' && !this.disallowedKeys.includes(dashedProperty)) {
projectConfigObject[`${dashedProperty}`] = configUpdates[property];
}
} // End sanity check
} // End for
return JSON.parse(fs.readFileSync(this.configFilePath, 'utf-8'));
} catch (err: any) {
console.log('ProjectJson.performRootJsonUpdate()');
console.error(err);
}
};
/**
* @description Update our internal config with plugin specific values
* @private
* @author Keith Murphy | nomadmystics@gmail.com
*
* @param {any} projectConfigObject
* @param {any} configUpdates
* @return { Promise<object | any>}
*/
private static performPluginJsonUpdate = async (projectConfigObject: any, configUpdates: any): Promise<object | any> => {
try {
let activePlugins: Array<ActivePlugins> = projectConfigObject['active-plugins'];
const alreadyExists: boolean | undefined = await this.cleanUpPluginArray(activePlugins, configUpdates);
// Push our update or let the user know one already exists
if (!alreadyExists) {
activePlugins.push(configUpdates);
} else {
console.log(`Plugin ${configUpdates['plugin-name']} already exists in the schema. Please try another name.`)
process.exit(1);
}
projectConfigObject['active-plugins'] = activePlugins;
if (this.isDebugFullMode) {
console.log('ProjectJson.performPluginJsonUpdate()');
console.log('projectConfigObject');
console.log(projectConfigObject);
console.log('configUpdates');
console.log(configUpdates);
}
return projectConfigObject;
} catch (err: any) {
console.log('ProjectJson.performPluginJsonUpdate()');
console.error(err);
}
}
/**
* @description Do some checks and clean up for the active-plugins object
* @private
* @author Keith Murphy | nomadmystics@gmail.com
*
* @param {Array<ActivePlugins>} activePlugins
* @param {any} configUpdates
* @return {Promise<boolean | undefined>}
*/
private static cleanUpPluginArray = async (activePlugins: Array<ActivePlugins>, configUpdates: any): Promise<boolean | undefined> => {
try {
let alreadyExists: boolean = false;
// Check if we have an active key by the same name
for (let plugin: number = 0; plugin < activePlugins.length; plugin++) {
if (activePlugins[plugin] && typeof activePlugins[plugin] !== 'undefined') {
const currentPlugin = await this.deleteUnusedPluginProperties(activePlugins[plugin]);
// Since this doesn't exist remove it from our array
if (!currentPlugin || typeof currentPlugin === 'undefined' || Object.keys(currentPlugin).length === 0) {
activePlugins.splice(plugin, 1);
}
// We have a plugin with the same name
if (activePlugins[plugin]?.['plugin-name'] && activePlugins[plugin]?.['plugin-name'] === configUpdates['plugin-name']) {
// There is a valid path and plugin with the same name
if (fs.existsSync(activePlugins[plugin]?.['plugin-path'])) {
alreadyExists = true;
}
}
}
}
return alreadyExists;
} catch (err: any) {
console.log('ProjectJson.cleanUpPluginArray()');
console.error(err);
}
};
/**
* @description
* @private
* @author Keith Murphy | nomadmystics@gmail.com
*
* @param {ActivePlugins} plugin
* @return {Promise<void>}
*/
private static deleteUnusedPluginProperties = async (plugin: ActivePlugins): Promise<ActivePlugins | any> => {
try {
let currentPlugin: ActivePlugins = plugin;
// Remove the object since the path for the plugin doesn't exist
if (!fs.existsSync(currentPlugin?.['plugin-path'])) {
delete currentPlugin['plugin-name'];
// @ts-ignore
delete currentPlugin['plugin-path'];
delete currentPlugin['plugin-description'];
delete currentPlugin['plugin-front-end-framework'];
}
return currentPlugin;
} catch (err: any) {
console.log('ProjectJson.deleteUnusedPluginProperties()');
console.error(err);
}
};
}
export default updateInternalJson;