evophp 1.1.1 → 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
 
@@ -67,18 +67,17 @@ evophp n plugin <name>
67
67
  evophp n theme <name>
68
68
  ```
69
69
 
70
- * Creates a new plugin or theme from a template.
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
- * `<author>/<name>` → Creates in the specified GitHub account
74
+ * ⚠️ You can only create repositories under your GitHub account.
76
75
 
77
76
  * Examples:
78
77
 
79
78
  ```bash
80
- evophp new plugin MyPlugin # Uses your GitHub
81
- evophp n plugin johndoe/BlogEnhancer # Uses johndoe's GitHub
79
+ evophp new plugin MyPlugin # Creates in your GitHub
80
+ evophp n theme MyTheme # Creates in your GitHub
82
81
  ```
83
82
 
84
83
  ---
@@ -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:
@@ -165,11 +189,13 @@ When using a command with just `<name>` (e.g. `evophp new plugin MyPlugin`), Evo
165
189
  * Automatically store it in `.env.development` for future use
166
190
  * Use that token to fetch your GitHub username and manage private/public repos
167
191
 
192
+ > ⚠️ You can only create repositories in your own GitHub account.
193
+
168
194
  > To change your token later, simply edit or delete `.env.development`.
169
195
 
170
196
  ---
171
197
 
172
198
  ## Further Documentation
173
199
 
174
- More advanced use cases, templates, and configuration details coming soon at:
200
+ More advanced use cases, templates, and configuration details coming soon at:
175
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.01",
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
  };