evophp 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,12 +10,12 @@ EvoPHP CLI is a command-line interface designed to simplify the development and
10
10
 
11
11
  * [Installation](#installation)
12
12
  * [All Commands](#all-commands)
13
-
14
13
  * [Initialization](#initialization)
15
14
  * [Plugin/Theme Creation](#plugintheme-creation)
16
15
  * [Plugin/Theme Installation](#plugintheme-installation)
17
16
  * [Plugin/Theme Uninstallation](#plugintheme-uninstallation)
18
17
  * [Dependency Management](#dependency-management)
18
+ * [Entry Management](#entry-management)
19
19
  * [Authentication & PAT Storage](#authentication--pat-storage)
20
20
  * [Further Documentation](#further-documentation)
21
21
 
@@ -70,7 +70,6 @@ evophp n theme <name>
70
70
  * Creates a new plugin or theme from a template in **your own GitHub account**.
71
71
 
72
72
  * Supports:
73
-
74
73
  * Just `<name>` → Uses your GitHub account (token required)
75
74
  * ⚠️ You can only create repositories under your GitHub account.
76
75
 
@@ -95,7 +94,6 @@ evophp i theme <name>
95
94
  * Installs a plugin or theme from GitHub into your project.
96
95
 
97
96
  * Supports:
98
-
99
97
  * Just `<name>` → Clones from your GitHub account (token required)
100
98
  * `<author>/<name>` → Clones from public GitHub repo (no token required)
101
99
 
@@ -157,6 +155,32 @@ evophp d MyPlugin --composer
157
155
 
158
156
  ---
159
157
 
158
+ ### Entry Management
159
+
160
+ ```bash
161
+ evophp entry <plugin> <entry>
162
+ evophp e <plugin> <entry>
163
+ ```
164
+
165
+ * Adds a new entry point to a plugin.
166
+ * Prompts for:
167
+ * Landing page name (default: `index`)
168
+ * Route path
169
+ * Role access permissions (IDs from config.json)
170
+ * Page title
171
+ * Creates:
172
+ * Blade view file
173
+ * JavaScript entry and router files
174
+ * Route entry in `Routes.php`
175
+
176
+ * Example:
177
+
178
+ ```bash
179
+ evophp entry MyPlugin Dashboard
180
+ ```
181
+
182
+ ---
183
+
160
184
  ## Authentication & PAT Storage
161
185
 
162
186
  When using a command with just `<name>` (e.g. `evophp new plugin MyPlugin`), EvoPHP CLI will:
@@ -173,5 +197,5 @@ When using a command with just `<name>` (e.g. `evophp new plugin MyPlugin`), Evo
173
197
 
174
198
  ## Further Documentation
175
199
 
176
- More advanced use cases, templates, and configuration details coming soon at:
200
+ More advanced use cases, templates, and configuration details coming soon at:
177
201
  📘 [Official EvoPHP Documentation](#)
package/bin/evophp.js CHANGED
@@ -9,6 +9,7 @@ yargs(hideBin(process.argv))
9
9
  .command(require("../commands/install"))
10
10
  .command(require("../commands/uninstall"))
11
11
  .command(require("../commands/dependencies"))
12
+ .command(require("../commands/entry"))
12
13
  .command(require("../commands/help"))
13
14
  .demandCommand(1, "Please specify a valid command. Use --help to see available commands.")
14
15
  .help()
@@ -0,0 +1,41 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { readLineAsync } = require("../utils/helpers");
4
+ const { addPluginEntry } = require("../utils/entry");
5
+ const log = require("../utils/logger");
6
+
7
+ module.exports = {
8
+ command: "entry <plugin> <entry>",
9
+ aliases: ["e"],
10
+ describe: "Add a new entry point to a plugin",
11
+
12
+ builder: yargs => yargs
13
+ .positional("plugin", { describe: "Plugin name", type: "string" })
14
+ .positional("entry", { describe: "Entry folder name", type: "string" })
15
+ .option("landing", {
16
+ alias: "l",
17
+ type: "string",
18
+ describe: "Landing file name (default: index)"
19
+ }),
20
+
21
+ handler: async ({ plugin, entry, landing }) => {
22
+ try {
23
+ // 🛡️ Check if plugin exists
24
+ const pluginPath = path.join("Public", "Modules", plugin);
25
+ if (!fs.existsSync(pluginPath)) {
26
+ log.error(`Plugin '${plugin}' does not exist at ${pluginPath}`);
27
+ process.exit(1);
28
+ }
29
+
30
+ if (!landing) {
31
+ landing = await readLineAsync("Landing page filename? (default: index): ") || "index";
32
+ }
33
+
34
+ await addPluginEntry(plugin, entry, landing);
35
+ log.success(`Entry '${entry}/${landing}' successfully added to plugin '${plugin}'`);
36
+ } catch (err) {
37
+ log.error(`Failed to add entry: ${err.message}`);
38
+ process.exit(1);
39
+ }
40
+ }
41
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evophp",
3
- "version": "1.1.02",
3
+ "version": "1.2.0",
4
4
  "description": "Command-line installer for the Evolution PHP framework",
5
5
  "bin": {
6
6
  "evophp": "./bin/evophp.js"
package/utils/entry.js ADDED
@@ -0,0 +1,170 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const editJsonFile = require("edit-json-file");
4
+ const log = require("./logger");
5
+ const readline = require("readline");
6
+ const { mergeConfig } = require("./helpers");
7
+
8
+ const readLineAsync = (msg) => {
9
+ const rl = readline.createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout,
12
+ });
13
+ return new Promise((resolve) =>
14
+ rl.question(msg, (ans) => {
15
+ rl.close();
16
+ resolve(ans);
17
+ })
18
+ );
19
+ };
20
+
21
+ /**
22
+ * Add a new entry point to a plugin with optional custom landing page
23
+ * Creates both server-side view, JavaScript entry files, and route
24
+ *
25
+ * @param {string} pluginName - The plugin name
26
+ * @param {string} entryName - The entry folder name
27
+ * @param {string} landing - The landing filename (e.g. index)
28
+ */
29
+ async function addPluginEntry(pluginName, entryName, landing = "index") {
30
+ const configPath = path.resolve("config.json");
31
+
32
+ if (!fs.existsSync(configPath)) {
33
+ log.error(`Config file not found: ${configPath}`);
34
+ return;
35
+ }
36
+
37
+ const configJson = JSON.parse(fs.readFileSync(configPath, "utf8"));
38
+
39
+ // 1. Prompt for route and auth roles
40
+ const route = await readLineAsync("Enter route path (e.g. dashboard): ");
41
+
42
+ // Collect unique role IDs from Auth.roles
43
+ const rawRoles = Object.values(configJson.Auth?.roles || {}).flatMap((role) =>
44
+ Array.isArray(role.capacity) ? role.capacity : []
45
+ );
46
+ const uniqueRoles = [...new Set(rawRoles.map(String))];
47
+
48
+ console.log("Available Role IDs: ", uniqueRoles.join(", "));
49
+ const auth = await readLineAsync(
50
+ "Enter role IDs for access (comma separated): "
51
+ );
52
+ const authList = auth
53
+ .split(",")
54
+ .map((x) => x.trim())
55
+ .filter(Boolean);
56
+
57
+ const pageTitle = await readLineAsync("Enter page title: ");
58
+
59
+ // 2. Derive pluginPrefix from Controller filename
60
+ const controllerDir = path.join("Public", "Modules", pluginName);
61
+ const controllerFile = fs
62
+ .readdirSync(controllerDir)
63
+ .find((f) => f.endsWith("Controller.php"));
64
+ const pluginPrefix = controllerFile
65
+ ? controllerFile.replace("Controller.php", "")
66
+ : pluginName.slice(0, 3).toUpperCase();
67
+
68
+ // 3. Normalize entryName with prefix if missing
69
+ if (!entryName.startsWith(pluginPrefix)) {
70
+ entryName = pluginPrefix + entryName;
71
+ }
72
+
73
+ const entryPath = `${entryName}/${landing}`;
74
+
75
+ // 4. Update root config.json entry
76
+ const rootConfig = editJsonFile(configPath);
77
+ const current = rootConfig.get(`modules.${pluginName}`) || {
78
+ entry: [],
79
+ active: true,
80
+ order: 0,
81
+ };
82
+
83
+ if (!current.entry.includes(entryPath)) {
84
+ current.entry.push(entryPath);
85
+ }
86
+
87
+ rootConfig.set(`modules.${pluginName}`, current);
88
+ rootConfig.save();
89
+ log.success(`Added entry "${entryPath}" to ${pluginName} config.`);
90
+
91
+ // 5. Update EvoJsDev config.json entry
92
+ const pluginConfigPath = path.join(
93
+ "EvoJsDev",
94
+ "Modules",
95
+ pluginName,
96
+ "config.json"
97
+ );
98
+ const pluginConfig = editJsonFile(pluginConfigPath);
99
+ const currentPluginConfig = pluginConfig.get(`modules.${pluginName}`) || {
100
+ entry: [],
101
+ active: true,
102
+ order: 0,
103
+ };
104
+
105
+ if (!currentPluginConfig.entry.includes(entryPath)) {
106
+ currentPluginConfig.entry.push(entryPath);
107
+ }
108
+
109
+ pluginConfig.set(`modules.${pluginName}`, currentPluginConfig);
110
+ pluginConfig.save();
111
+ log.success(`Updated EvoJsDev config with entry "${entryPath}".`);
112
+
113
+ // Load templates
114
+ const bladeContent = fs.readFileSync(
115
+ path.join(__dirname, "../templates/blade.landing.php"),
116
+ "utf8"
117
+ );
118
+ const jsContent = fs.readFileSync(
119
+ path.join(__dirname, "../templates/js.entry.js"),
120
+ "utf8"
121
+ );
122
+ const routerContent = fs.readFileSync(
123
+ path.join(__dirname, "../templates/js.router.js"),
124
+ "utf8"
125
+ );
126
+
127
+ // 6. Create server-side landing blade file
128
+ const bladeDir = path.join(
129
+ "Public",
130
+ "Modules",
131
+ pluginName,
132
+ "Views",
133
+ entryName
134
+ );
135
+ const bladeFile = path.join(bladeDir, `${landing}.blade.php`);
136
+
137
+ if (!fs.existsSync(bladeDir)) fs.mkdirSync(bladeDir, { recursive: true });
138
+ fs.writeFileSync(bladeFile, bladeContent, "utf8");
139
+ log.success(`Created blade view: ${bladeFile}`);
140
+
141
+ // 7. Create JavaScript entry files
142
+ const jsEntryDir = path.join("EvoJsDev", "Modules", pluginName, entryName);
143
+ const jsRouterDir = path.join(jsEntryDir, "router");
144
+
145
+ if (!fs.existsSync(jsEntryDir)) fs.mkdirSync(jsEntryDir, { recursive: true });
146
+ if (!fs.existsSync(jsRouterDir)) fs.mkdirSync(jsRouterDir);
147
+
148
+ const jsFile = path.join(jsEntryDir, `${landing}.js`);
149
+ fs.writeFileSync(jsFile, jsContent, "utf8");
150
+ log.success(`Created JS entry: ${jsFile}`);
151
+
152
+ const routerIndex = path.join(jsRouterDir, "index.js");
153
+ fs.writeFileSync(routerIndex, routerContent, "utf8");
154
+ log.success(`Created JS router: ${routerIndex}`);
155
+
156
+ // 8. Add route to Public/Modules/plugin/Routes.php
157
+ const routesPath = path.join("Public", "Modules", pluginName, "Routes.php");
158
+ const routeLine =
159
+ `$router->get('/${route}', function($params){\n` +
160
+ ` $controller = new ${pluginPrefix}Controller;\n` +
161
+ ` $controller->{'${entryPath}'}($params)->auth(${authList.join(
162
+ ","
163
+ )})->setData(['pageTitle' => "${pageTitle}"]);\n` +
164
+ `});\n\n`;
165
+
166
+ fs.appendFileSync(routesPath, routeLine, "utf8");
167
+ log.success(`Route added to ${routesPath}`);
168
+ }
169
+
170
+ module.exports = { addPluginEntry };
package/utils/helpers.js CHANGED
@@ -1,5 +1,8 @@
1
1
  const readline = require("readline");
2
2
  const editJsonFile = require("edit-json-file");
3
+ const { execSync } = require("child_process");
4
+ const fs = require("fs");
5
+ const path = require("path");
3
6
 
4
7
  /**
5
8
  * Prompt user for input with a message
@@ -52,78 +55,42 @@ const isObject = (item) =>
52
55
  /**
53
56
  * Deep merge multiple objects into one
54
57
  */
55
- const mergeDeep = async (target, ...sources) => {
56
- if (!sources.length) return target;
57
- const source = sources.shift();
58
-
59
- if (isObject(target) && isObject(source)) {
60
- for (const key in source) {
61
- if (isObject(source[key])) {
62
- if (!target[key]) Object.assign(target, { [key]: {} });
63
- await mergeDeep(target[key], source[key]);
64
- } else {
65
- Object.assign(target, { [key]: source[key] });
66
- }
58
+ const mergeDeep = async (target, source) => {
59
+ for (const key in source) {
60
+ if (isObject(source[key])) {
61
+ if (!target[key]) target[key] = {};
62
+ await mergeDeep(target[key], source[key]);
63
+ } else {
64
+ target[key] = source[key];
67
65
  }
68
66
  }
69
-
70
- return mergeDeep(target, ...sources);
67
+ return target;
71
68
  };
72
69
 
73
- /**
74
- * Merge plugin config into config.json
75
- */
76
- const mergeConfig = async (config) => {
77
- const file = editJsonFile("./config.json");
78
-
70
+ const mergeConfig = async (config, filePath = "./config.json") => {
71
+ const file = editJsonFile(filePath);
79
72
  for (const key in config) {
80
73
  const existing = file.get(key);
81
74
  if (existing === undefined) {
82
75
  file.set(key, config[key]);
83
76
  } else if (isObject(existing)) {
84
77
  const merged = await mergeDeep(existing, config[key]);
78
+ file.set(key, existing); // ensure file uses the updated object reference
85
79
  file.set(key, merged);
86
80
  } else {
87
81
  file.set(key, config[key]);
88
82
  }
89
83
  }
90
-
91
84
  file.save();
92
- };
93
-
94
- /**
95
- * Add a new entry point to a plugin's config.json file
96
- * @param {string} pluginName - The plugin name
97
- * @param {string} entryName - The entry directory name
98
- */
99
- function addPluginEntry(pluginName, entryName) {
100
- const configPath = path.join("EvoJsDev", "Modules", pluginName, "config.json");
101
-
102
- if (!fs.existsSync(configPath)) {
103
- log.error(`Config file not found: ${configPath}`);
104
- return;
105
- }
106
85
 
107
- const file = editJsonFile(configPath);
108
- const entryPath = `${entryName}/index`;
109
-
110
- const current = file.get(`modules.${pluginName}`) || {
111
- entry: [],
112
- active: true,
113
- order: 0
114
- };
115
-
116
- if (!current.entry.includes(entryPath)) {
117
- current.entry.push(entryPath);
86
+ // Run build process if entry config changes
87
+ try {
88
+ console.log("\n[INFO] Running npm run dev to recompile JS bundle...");
89
+ execSync("npm run dev", { stdio: "inherit" });
90
+ } catch (error) {
91
+ console.error("[ERROR] Failed to run 'npm run dev':", error.message);
118
92
  }
119
-
120
- file.set(`modules.${pluginName}`, current);
121
- file.save();
122
- log.success(`Added entry "${entryPath}" to ${pluginName} config.`);
123
- }
124
-
125
- const fs = require("fs");
126
- const path = require("path");
93
+ };
127
94
 
128
95
  /**
129
96
  * Save a key-value pair to .env.development (creates or updates)
@@ -156,6 +123,5 @@ module.exports = {
156
123
  readLineAsync,
157
124
  splitPluginName,
158
125
  mergeConfig,
159
- addPluginEntry,
160
126
  saveToEnv
161
127
  };