create-platon-plugin 0.2.4

A quick way to scaffold a new Platon Plugin
Documentation
extern crate fs_extra;
use std::{
    collections::BTreeMap,
    fs::{self, File},
    io::{BufRead, BufReader, Write},
    path::Path,
};

use clap::Parser;
use create_platon_plugin::{convert_to_camel_case, convert_to_kebab_case};
use fs_extra::dir::create;
use include_dir::{Dir, include_dir};
use regex::Regex;
use serde::{Deserialize, Serialize};

static TEMPLATE_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/template");

#[derive(Debug, Serialize, Deserialize)]
struct PackageJson {
    name: String,

    #[serde(flatten)]
    other: serde_json::Value,
}

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Cli {
    /// The name of the plugin
    #[arg(long)]
    plugin_name: String,
    /// The port number in which the plugin will be running
    #[arg(long, default_value_t = 8080)]
    port: u16,
}

fn main() {
    let args = Cli::parse();

    let plugin_name = args.plugin_name;
    let plugin_port = args.port;
    let plugin_path = Path::new("plugins").join(&plugin_name);

    create(plugin_path.clone(), true).expect("Failed to create a folder");

    TEMPLATE_DIR
        .extract(plugin_path.clone())
        .expect("Failed to extract the template folder!");

    update_package_json_name_field(plugin_name.clone());
    add_port_entry_to_ports_file(plugin_name.clone(), plugin_port);
    write_plugin_name_to_env(plugin_name.clone());
}

fn write_plugin_name_to_env(plugin_name: String) {
    let env_file_path = Path::new("plugins").join(&plugin_name).join(".env");
    let mut env_file = File::options()
        .append(true)
        .write(true)
        .open(&env_file_path)
        .expect("Failed to open .env file!");
    writeln!(env_file, "\nVUE_APP_NAME={}", plugin_name.clone())
        .expect("Failed to write the plugin's name to .env file!");

    println!(
        "✅ Added VUE_APP_NAME={} to the .env file!",
        plugin_name.clone()
    );
}

fn update_package_json_name_field(plugin_name: String) {
    let plugin_path = Path::new("plugins").join(&plugin_name);

    // modifying package.json
    let package_json_path = plugin_path.join("package.json");
    let file = File::open(&package_json_path).expect("Failed to open file");
    let reader = BufReader::new(file);
    let mut package_json: PackageJson = serde_json::from_reader(reader).expect("Failed");

    package_json.name = convert_to_kebab_case(&plugin_name);

    let updated = serde_json::to_string_pretty(&package_json).expect("Failed to update");

    fs::write(package_json_path, updated).expect("Failed to write updated content");
    println!("✅ Updated package.json")
}

fn add_port_entry_to_webpack_dev_server(plugin_name: String, plugin_port: u16) {
    // look for webpack.config.js file
    let webpack_config_path = Path::new("plugins")
        .join(plugin_name.clone())
        .join("webpack.config.js");
    let mut webpack_config = File::options()
        .write(true)
        .open(webpack_config_path)
        .expect("Failed to open webpack.config.js!");
    let reader = BufReader::new(&webpack_config);

    for line in reader.lines() {
        let line = line.expect("Failed to read the line!");
        if line.is_empty() {
            continue;
        }
        if let Some((key, mut value)) = line.split_once(":") {
            if key != "port" {
                continue;
            };
            value = &plugin_port.to_string();
        }
    }
}

fn add_port_entry_to_ports_file(plugin_name: String, plugin_port: u16) {
    let plugin_name = convert_to_camel_case(&plugin_name);

    // look for scripts/ports.js file
    let ports_file_path = Path::new("scripts/ports.js");
    let content =
        fs::read_to_string(&ports_file_path).expect("Failed to look for scripts/ports.js file");

    let regex =
        Regex::new(r#"module\.exports\s*=\s*\{([\s\S]*?)\};?"#).expect("Failed to create a regex");

    let parsed_content = regex.captures(&content).expect("Failed to parse ports.js");
    let object_body = parsed_content.get(1).unwrap().as_str();

    let mut map = BTreeMap::new();

    for line in object_body.lines() {
        let line = line.trim().trim_end_matches(",");
        if line.is_empty() {
            continue;
        }

        if let Some((key, value)) = line.split_once(":") {
            let key = key.trim().to_string();
            let value = value.trim().to_string();
            map.insert(key, value);
        }
    }

    map.insert(plugin_name.clone(), plugin_port.to_string());

    let mut new_object = String::from("module.exports = {\n");

    for (key, value) in &map {
        new_object.push_str(&format!("  {}: {},\n", key, value));
    }
    new_object.push_str("};\n");

    fs::write(ports_file_path, new_object).expect("Failed to write new content to ports.js");

    println!(
        "✅ Added {}: {} to ports.js",
        plugin_name.clone(),
        plugin_port
    );
}